OSDN Git Service

Bytom spv client implement
[bytom/bytom-spv.git] / account / accounts.go
1 // Package account stores and tracks accounts within a Bytom Core.
2 package account
3
4 import (
5         "encoding/json"
6         "strings"
7         "sync"
8
9         "github.com/golang/groupcache/lru"
10         log "github.com/sirupsen/logrus"
11         dbm "github.com/tendermint/tmlibs/db"
12
13         "github.com/bytom/blockchain/signers"
14         "github.com/bytom/blockchain/txbuilder"
15         "github.com/bytom/common"
16         "github.com/bytom/consensus"
17         "github.com/bytom/consensus/segwit"
18         "github.com/bytom/crypto"
19         "github.com/bytom/crypto/ed25519/chainkd"
20         "github.com/bytom/crypto/sha3pool"
21         "github.com/bytom/errors"
22         "github.com/bytom/protocol"
23         "github.com/bytom/protocol/bc"
24         "github.com/bytom/protocol/vm/vmutil"
25 )
26
27 const (
28         maxAccountCache = 1000
29         maxAddrSize     = 10000
30 )
31
32 var (
33         accountIndexKey     = []byte("AccountIndex")
34         accountPrefix       = []byte("Account:")
35         aliasPrefix         = []byte("AccountAlias:")
36         contractIndexPrefix = []byte("ContractIndex")
37         contractPrefix      = []byte("Contract:")
38         miningAddressKey    = []byte("MiningAddress")
39         CoinbaseAbKey       = []byte("CoinbaseArbitrary")
40 )
41
42 // pre-define errors for supporting bytom errorFormatter
43 var (
44         ErrDuplicateAlias  = errors.New("duplicate account alias")
45         ErrFindAccount     = errors.New("fail to find account")
46         ErrMarshalAccount  = errors.New("failed marshal account")
47         ErrInvalidAddress  = errors.New("invalid address")
48         ErrFindCtrlProgram = errors.New("fail to find account control program")
49 )
50
51 // ContractKey account control promgram store prefix
52 func ContractKey(hash common.Hash) []byte {
53         return append(contractPrefix, hash[:]...)
54 }
55
56 // Key account store prefix
57 func Key(name string) []byte {
58         return append(accountPrefix, []byte(name)...)
59 }
60
61 func aliasKey(name string) []byte {
62         return append(aliasPrefix, []byte(name)...)
63 }
64
65 func contractIndexKey(accountID string) []byte {
66         return append(contractIndexPrefix, []byte(accountID)...)
67 }
68
69 // Account is structure of Bytom account
70 type Account struct {
71         *signers.Signer
72         ID    string `json:"id"`
73         Alias string `json:"alias"`
74 }
75
76 //CtrlProgram is structure of account control program
77 type CtrlProgram struct {
78         AccountID      string
79         Address        string
80         KeyIndex       uint64
81         ControlProgram []byte
82         Change         bool // Mark whether this control program is for UTXO change
83 }
84
85 // Manager stores accounts and their associated control programs.
86 type Manager struct {
87         db         dbm.DB
88         chain      *protocol.Chain
89         utxoKeeper *utxoKeeper
90
91         cacheMu    sync.Mutex
92         cache      *lru.Cache
93         aliasCache *lru.Cache
94
95         delayedACPsMu sync.Mutex
96         delayedACPs   map[*txbuilder.TemplateBuilder][]*CtrlProgram
97
98         NewAddrCh chan *CtrlProgram
99
100         accIndexMu sync.Mutex
101         accountMu  sync.Mutex
102 }
103
104 // NewManager creates a new account manager
105 func NewManager(walletDB dbm.DB, chain *protocol.Chain) *Manager {
106         return &Manager{
107                 db:          walletDB,
108                 chain:       chain,
109                 utxoKeeper:  newUtxoKeeper(chain.BestBlockHeight, walletDB),
110                 cache:       lru.New(maxAccountCache),
111                 aliasCache:  lru.New(maxAccountCache),
112                 delayedACPs: make(map[*txbuilder.TemplateBuilder][]*CtrlProgram),
113                 NewAddrCh:   make(chan *CtrlProgram, maxAddrSize),
114         }
115 }
116
117 // AddUnconfirmedUtxo add untxo list to utxoKeeper
118 func (m *Manager) AddUnconfirmedUtxo(utxos []*UTXO) {
119         m.utxoKeeper.AddUnconfirmedUtxo(utxos)
120 }
121
122 // Create creates a new Account.
123 func (m *Manager) Create(xpubs []chainkd.XPub, quorum int, alias string) (*Account, error) {
124         m.accountMu.Lock()
125         defer m.accountMu.Unlock()
126
127         normalizedAlias := strings.ToLower(strings.TrimSpace(alias))
128         if existed := m.db.Get(aliasKey(normalizedAlias)); existed != nil {
129                 return nil, ErrDuplicateAlias
130         }
131
132         signer, err := signers.Create("account", xpubs, quorum, m.getNextAccountIndex())
133         id := signers.IDGenerate()
134         if err != nil {
135                 return nil, errors.Wrap(err)
136         }
137
138         account := &Account{Signer: signer, ID: id, Alias: normalizedAlias}
139         rawAccount, err := json.Marshal(account)
140         if err != nil {
141                 return nil, ErrMarshalAccount
142         }
143
144         accountID := Key(id)
145         storeBatch := m.db.NewBatch()
146         storeBatch.Set(accountID, rawAccount)
147         storeBatch.Set(aliasKey(normalizedAlias), []byte(id))
148         storeBatch.Write()
149         return account, nil
150 }
151
152 // CreateAddress generate an address for the select account
153 func (m *Manager) CreateAddress(accountID string, change bool) (cp *CtrlProgram, err error) {
154         account, err := m.FindByID(accountID)
155         if err != nil {
156                 return nil, err
157         }
158         return m.createAddress(account, change)
159 }
160
161 // DeleteAccount deletes the account's ID or alias matching accountInfo.
162 func (m *Manager) DeleteAccount(aliasOrID string) (err error) {
163         account := &Account{}
164         if account, err = m.FindByAlias(aliasOrID); err != nil {
165                 if account, err = m.FindByID(aliasOrID); err != nil {
166                         return err
167                 }
168         }
169
170         m.cacheMu.Lock()
171         m.aliasCache.Remove(account.Alias)
172         m.cacheMu.Unlock()
173
174         storeBatch := m.db.NewBatch()
175         storeBatch.Delete(aliasKey(account.Alias))
176         storeBatch.Delete(Key(account.ID))
177         storeBatch.Write()
178         return nil
179 }
180
181 // FindByAlias retrieves an account's Signer record by its alias
182 func (m *Manager) FindByAlias(alias string) (*Account, error) {
183         m.cacheMu.Lock()
184         cachedID, ok := m.aliasCache.Get(alias)
185         m.cacheMu.Unlock()
186         if ok {
187                 return m.FindByID(cachedID.(string))
188         }
189
190         rawID := m.db.Get(aliasKey(alias))
191         if rawID == nil {
192                 return nil, ErrFindAccount
193         }
194
195         accountID := string(rawID)
196         m.cacheMu.Lock()
197         m.aliasCache.Add(alias, accountID)
198         m.cacheMu.Unlock()
199         return m.FindByID(accountID)
200 }
201
202 // FindByID returns an account's Signer record by its ID.
203 func (m *Manager) FindByID(id string) (*Account, error) {
204         m.cacheMu.Lock()
205         cachedAccount, ok := m.cache.Get(id)
206         m.cacheMu.Unlock()
207         if ok {
208                 return cachedAccount.(*Account), nil
209         }
210
211         rawAccount := m.db.Get(Key(id))
212         if rawAccount == nil {
213                 return nil, ErrFindAccount
214         }
215
216         account := &Account{}
217         if err := json.Unmarshal(rawAccount, account); err != nil {
218                 return nil, err
219         }
220
221         m.cacheMu.Lock()
222         m.cache.Add(id, account)
223         m.cacheMu.Unlock()
224         return account, nil
225 }
226
227 // GetAccountByProgram return Account by given CtrlProgram
228 func (m *Manager) GetAccountByProgram(program *CtrlProgram) (*Account, error) {
229         rawAccount := m.db.Get(Key(program.AccountID))
230         if rawAccount == nil {
231                 return nil, ErrFindAccount
232         }
233
234         account := &Account{}
235         return account, json.Unmarshal(rawAccount, account)
236 }
237
238 // GetAliasByID return the account alias by given ID
239 func (m *Manager) GetAliasByID(id string) string {
240         rawAccount := m.db.Get(Key(id))
241         if rawAccount == nil {
242                 log.Warn("GetAliasByID fail to find account")
243                 return ""
244         }
245
246         account := &Account{}
247         if err := json.Unmarshal(rawAccount, account); err != nil {
248                 log.Warn(err)
249         }
250         return account.Alias
251 }
252
253 func (m *Manager) GetCoinbaseArbitrary() []byte {
254         if arbitrary := m.db.Get(CoinbaseAbKey); arbitrary != nil {
255                 return arbitrary
256         }
257         return []byte{}
258 }
259
260 // GetCoinbaseControlProgram will return a coinbase script
261 func (m *Manager) GetCoinbaseControlProgram() ([]byte, error) {
262         cp, err := m.GetCoinbaseCtrlProgram()
263         if err == ErrFindAccount {
264                 log.Warningf("GetCoinbaseControlProgram: can't find any account in db")
265                 return vmutil.DefaultCoinbaseProgram()
266         }
267         if err != nil {
268                 return nil, err
269         }
270         return cp.ControlProgram, nil
271 }
272
273 // GetCoinbaseCtrlProgram will return the coinbase CtrlProgram
274 func (m *Manager) GetCoinbaseCtrlProgram() (*CtrlProgram, error) {
275         if data := m.db.Get(miningAddressKey); data != nil {
276                 cp := &CtrlProgram{}
277                 return cp, json.Unmarshal(data, cp)
278         }
279
280         accountIter := m.db.IteratorPrefix([]byte(accountPrefix))
281         defer accountIter.Release()
282         if !accountIter.Next() {
283                 return nil, ErrFindAccount
284         }
285
286         account := &Account{}
287         if err := json.Unmarshal(accountIter.Value(), account); err != nil {
288                 return nil, err
289         }
290
291         program, err := m.createAddress(account, false)
292         if err != nil {
293                 return nil, err
294         }
295
296         rawCP, err := json.Marshal(program)
297         if err != nil {
298                 return nil, err
299         }
300
301         m.db.Set(miningAddressKey, rawCP)
302         return program, nil
303 }
304
305 // GetContractIndex return the current index
306 func (m *Manager) GetContractIndex(accountID string) uint64 {
307         m.accIndexMu.Lock()
308         defer m.accIndexMu.Unlock()
309
310         index := uint64(1)
311         if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
312                 index = common.BytesToUnit64(rawIndexBytes)
313         }
314         return index
315 }
316
317 // GetLocalCtrlProgramByAddress return CtrlProgram by given address
318 func (m *Manager) GetLocalCtrlProgramByAddress(address string) (*CtrlProgram, error) {
319         program, err := m.getProgramByAddress(address)
320         if err != nil {
321                 return nil, err
322         }
323
324         var hash [32]byte
325         sha3pool.Sum256(hash[:], program)
326         rawProgram := m.db.Get(ContractKey(hash))
327         if rawProgram == nil {
328                 return nil, ErrFindCtrlProgram
329         }
330
331         cp := &CtrlProgram{}
332         return cp, json.Unmarshal(rawProgram, cp)
333 }
334
335 // GetMiningAddress will return the mining address
336 func (m *Manager) GetMiningAddress() (string, error) {
337         cp, err := m.GetCoinbaseCtrlProgram()
338         if err != nil {
339                 return "", err
340         }
341         return cp.Address, nil
342 }
343
344 // IsLocalControlProgram check is the input control program belong to local
345 func (m *Manager) IsLocalControlProgram(prog []byte) bool {
346         var hash common.Hash
347         sha3pool.Sum256(hash[:], prog)
348         bytes := m.db.Get(ContractKey(hash))
349         return bytes != nil
350 }
351
352 // ListAccounts will return the accounts in the db
353 func (m *Manager) ListAccounts(id string) ([]*Account, error) {
354         accounts := []*Account{}
355         accountIter := m.db.IteratorPrefix(Key(strings.TrimSpace(id)))
356         defer accountIter.Release()
357
358         for accountIter.Next() {
359                 account := &Account{}
360                 if err := json.Unmarshal(accountIter.Value(), &account); err != nil {
361                         return nil, err
362                 }
363                 accounts = append(accounts, account)
364         }
365         return accounts, nil
366 }
367
368 // ListControlProgram return all the local control program
369 func (m *Manager) ListControlProgram() ([]*CtrlProgram, error) {
370         cps := []*CtrlProgram{}
371         cpIter := m.db.IteratorPrefix(contractPrefix)
372         defer cpIter.Release()
373
374         for cpIter.Next() {
375                 cp := &CtrlProgram{}
376                 if err := json.Unmarshal(cpIter.Value(), cp); err != nil {
377                         return nil, err
378                 }
379                 cps = append(cps, cp)
380         }
381         return cps, nil
382 }
383
384 func (m *Manager) ListUnconfirmedUtxo(isSmartContract bool) []*UTXO {
385         utxos := m.utxoKeeper.ListUnconfirmed()
386         result := []*UTXO{}
387         for _, utxo := range utxos {
388                 if segwit.IsP2WScript(utxo.ControlProgram) != isSmartContract {
389                         result = append(result, utxo)
390                 }
391         }
392         return result
393 }
394
395 // RemoveUnconfirmedUtxo remove utxos from the utxoKeeper
396 func (m *Manager) RemoveUnconfirmedUtxo(hashes []*bc.Hash) {
397         m.utxoKeeper.RemoveUnconfirmedUtxo(hashes)
398 }
399
400 // SetMiningAddress will set the mining address
401 func (m *Manager) SetMiningAddress(miningAddress string) (string, error) {
402         program, err := m.getProgramByAddress(miningAddress)
403         if err != nil {
404                 return "", err
405         }
406
407         cp := &CtrlProgram{
408                 Address:        miningAddress,
409                 ControlProgram: program,
410         }
411         rawCP, err := json.Marshal(cp)
412         if err != nil {
413                 return "", err
414         }
415
416         m.db.Set(miningAddressKey, rawCP)
417         return m.GetMiningAddress()
418 }
419
420 func (m *Manager) SetCoinbaseArbitrary(arbitrary []byte) {
421         m.db.Set(CoinbaseAbKey, arbitrary)
422 }
423
424 // CreateAddress generate an address for the select account
425 func (m *Manager) createAddress(account *Account, change bool) (cp *CtrlProgram, err error) {
426         if len(account.XPubs) == 1 {
427                 cp, err = m.createP2PKH(account, change)
428         } else {
429                 cp, err = m.createP2SH(account, change)
430         }
431         if err != nil {
432                 return nil, err
433         }
434         if err = m.insertControlPrograms(cp); err != nil {
435                 return nil, err
436         }
437         if !change {
438                 m.NewAddrCh <- cp
439         }
440         return cp, nil
441 }
442
443 func (m *Manager) createP2PKH(account *Account, change bool) (*CtrlProgram, error) {
444         idx := m.getNextContractIndex(account.ID)
445         path := signers.Path(account.Signer, signers.AccountKeySpace, idx)
446         derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
447         derivedPK := derivedXPubs[0].PublicKey()
448         pubHash := crypto.Ripemd160(derivedPK)
449
450         address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
451         if err != nil {
452                 return nil, err
453         }
454
455         control, err := vmutil.P2WPKHProgram([]byte(pubHash))
456         if err != nil {
457                 return nil, err
458         }
459
460         return &CtrlProgram{
461                 AccountID:      account.ID,
462                 Address:        address.EncodeAddress(),
463                 KeyIndex:       idx,
464                 ControlProgram: control,
465                 Change:         change,
466         }, nil
467 }
468
469 func (m *Manager) createP2SH(account *Account, change bool) (*CtrlProgram, error) {
470         idx := m.getNextContractIndex(account.ID)
471         path := signers.Path(account.Signer, signers.AccountKeySpace, idx)
472         derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
473         derivedPKs := chainkd.XPubKeys(derivedXPubs)
474         signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
475         if err != nil {
476                 return nil, err
477         }
478         scriptHash := crypto.Sha256(signScript)
479
480         address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
481         if err != nil {
482                 return nil, err
483         }
484
485         control, err := vmutil.P2WSHProgram(scriptHash)
486         if err != nil {
487                 return nil, err
488         }
489
490         return &CtrlProgram{
491                 AccountID:      account.ID,
492                 Address:        address.EncodeAddress(),
493                 KeyIndex:       idx,
494                 ControlProgram: control,
495                 Change:         change,
496         }, nil
497 }
498
499 func (m *Manager) getNextAccountIndex() uint64 {
500         m.accIndexMu.Lock()
501         defer m.accIndexMu.Unlock()
502
503         var nextIndex uint64 = 1
504         if rawIndexBytes := m.db.Get(accountIndexKey); rawIndexBytes != nil {
505                 nextIndex = common.BytesToUnit64(rawIndexBytes) + 1
506         }
507         m.db.Set(accountIndexKey, common.Unit64ToBytes(nextIndex))
508         return nextIndex
509 }
510
511 func (m *Manager) getNextContractIndex(accountID string) uint64 {
512         m.accIndexMu.Lock()
513         defer m.accIndexMu.Unlock()
514
515         nextIndex := uint64(1)
516         if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
517                 nextIndex = common.BytesToUnit64(rawIndexBytes) + 1
518         }
519         m.db.Set(contractIndexKey(accountID), common.Unit64ToBytes(nextIndex))
520         return nextIndex
521 }
522
523 func (m *Manager) getProgramByAddress(address string) ([]byte, error) {
524         addr, err := common.DecodeAddress(address, &consensus.ActiveNetParams)
525         if err != nil {
526                 return nil, err
527         }
528
529         redeemContract := addr.ScriptAddress()
530         program := []byte{}
531         switch addr.(type) {
532         case *common.AddressWitnessPubKeyHash:
533                 program, err = vmutil.P2WPKHProgram(redeemContract)
534         case *common.AddressWitnessScriptHash:
535                 program, err = vmutil.P2WSHProgram(redeemContract)
536         default:
537                 return nil, ErrInvalidAddress
538         }
539         if err != nil {
540                 return nil, err
541         }
542         return program, nil
543 }
544
545 func (m *Manager) insertControlPrograms(progs ...*CtrlProgram) error {
546         var hash common.Hash
547         for _, prog := range progs {
548                 accountCP, err := json.Marshal(prog)
549                 if err != nil {
550                         return err
551                 }
552
553                 sha3pool.Sum256(hash[:], prog.ControlProgram)
554                 m.db.Set(ContractKey(hash), accountCP)
555         }
556         return nil
557 }