OSDN Git Service

957aad83d72d9f1e01aa05538fd0167a1fbd5d58
[bytom/bytom.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         "reflect"
7         "sort"
8         "strings"
9         "sync"
10
11         "github.com/golang/groupcache/lru"
12         log "github.com/sirupsen/logrus"
13         dbm "github.com/tendermint/tmlibs/db"
14
15         "github.com/bytom/blockchain/signers"
16         "github.com/bytom/blockchain/txbuilder"
17         "github.com/bytom/common"
18         "github.com/bytom/consensus"
19         "github.com/bytom/consensus/segwit"
20         "github.com/bytom/crypto"
21         "github.com/bytom/crypto/ed25519/chainkd"
22         "github.com/bytom/crypto/sha3pool"
23         "github.com/bytom/errors"
24         "github.com/bytom/protocol"
25         "github.com/bytom/protocol/bc"
26         "github.com/bytom/protocol/vm/vmutil"
27 )
28
29 const (
30         maxAccountCache = 1000
31
32         // HardenedKeyStart bip32 hierarchical deterministic wallets
33         // keys with index ≥ 0x80000000 are hardened keys
34         HardenedKeyStart = 0x80000000
35 )
36
37 var (
38         accountIndexPrefix  = []byte("AccountIndex:")
39         accountPrefix       = []byte("Account:")
40         aliasPrefix         = []byte("AccountAlias:")
41         contractIndexPrefix = []byte("ContractIndex")
42         contractPrefix      = []byte("Contract:")
43         miningAddressKey    = []byte("MiningAddress")
44         CoinbaseAbKey       = []byte("CoinbaseArbitrary")
45 )
46
47 // pre-define errors for supporting bytom errorFormatter
48 var (
49         ErrDuplicateAlias  = errors.New("duplicate account alias")
50         ErrDuplicateIndex  = errors.New("duplicate account with same xPubs and index")
51         ErrFindAccount     = errors.New("fail to find account")
52         ErrMarshalAccount  = errors.New("failed marshal account")
53         ErrInvalidAddress  = errors.New("invalid address")
54         ErrFindCtrlProgram = errors.New("fail to find account control program")
55         ErrDeriveRule      = errors.New("invalid key derive rule")
56         ErrContractIndex   = errors.New("exceed the maximum addresses per account")
57         ErrAccountIndex    = errors.New("exceed the maximum accounts per xpub")
58 )
59
60 // ContractKey account control promgram store prefix
61 func ContractKey(hash common.Hash) []byte {
62         return append(contractPrefix, hash[:]...)
63 }
64
65 // Key account store prefix
66 func Key(name string) []byte {
67         return append(accountPrefix, []byte(name)...)
68 }
69
70 func aliasKey(name string) []byte {
71         return append(aliasPrefix, []byte(name)...)
72 }
73
74 func bip44ContractIndexKey(accountID string, change bool) []byte {
75         key := append(contractIndexPrefix, accountID...)
76         if change {
77                 return append(key, []byte{1}...)
78         }
79         return append(key, []byte{0}...)
80 }
81
82 func contractIndexKey(accountID string) []byte {
83         return append(contractIndexPrefix, []byte(accountID)...)
84 }
85
86 // Account is structure of Bytom account
87 type Account struct {
88         *signers.Signer
89         ID    string `json:"id"`
90         Alias string `json:"alias"`
91 }
92
93 //CtrlProgram is structure of account control program
94 type CtrlProgram struct {
95         AccountID      string
96         Address        string
97         KeyIndex       uint64
98         ControlProgram []byte
99         Change         bool // Mark whether this control program is for UTXO change
100 }
101
102 // Manager stores accounts and their associated control programs.
103 type Manager struct {
104         db         dbm.DB
105         chain      *protocol.Chain
106         utxoKeeper *utxoKeeper
107
108         cacheMu    sync.Mutex
109         cache      *lru.Cache
110         aliasCache *lru.Cache
111
112         delayedACPsMu sync.Mutex
113         delayedACPs   map[*txbuilder.TemplateBuilder][]*CtrlProgram
114
115         addressMu sync.Mutex
116         accountMu sync.Mutex
117 }
118
119 // NewManager creates a new account manager
120 func NewManager(walletDB dbm.DB, chain *protocol.Chain) *Manager {
121         return &Manager{
122                 db:          walletDB,
123                 chain:       chain,
124                 utxoKeeper:  newUtxoKeeper(chain.BestBlockHeight, walletDB),
125                 cache:       lru.New(maxAccountCache),
126                 aliasCache:  lru.New(maxAccountCache),
127                 delayedACPs: make(map[*txbuilder.TemplateBuilder][]*CtrlProgram),
128         }
129 }
130
131 // AddUnconfirmedUtxo add untxo list to utxoKeeper
132 func (m *Manager) AddUnconfirmedUtxo(utxos []*UTXO) {
133         m.utxoKeeper.AddUnconfirmedUtxo(utxos)
134 }
135
136 // CreateAccount creates a new Account.
137 func CreateAccount(xpubs []chainkd.XPub, quorum int, alias string, acctIndex uint64, deriveRule uint8) (*Account, error) {
138         if acctIndex >= HardenedKeyStart {
139                 return nil, ErrAccountIndex
140         }
141
142         signer, err := signers.Create("account", xpubs, quorum, acctIndex, deriveRule)
143         if err != nil {
144                 return nil, errors.Wrap(err)
145         }
146
147         id := signers.IDGenerate()
148         return &Account{Signer: signer, ID: id, Alias: strings.ToLower(strings.TrimSpace(alias))}, nil
149 }
150
151 func (m *Manager) saveAccount(account *Account, updateIndex bool) error {
152         rawAccount, err := json.Marshal(account)
153         if err != nil {
154                 return ErrMarshalAccount
155         }
156
157         storeBatch := m.db.NewBatch()
158         storeBatch.Set(Key(account.ID), rawAccount)
159         storeBatch.Set(aliasKey(account.Alias), []byte(account.ID))
160         if updateIndex {
161                 storeBatch.Set(GetAccountIndexKey(account.XPubs), common.Unit64ToBytes(account.KeyIndex))
162         }
163         storeBatch.Write()
164         return nil
165 }
166
167 // SaveAccount save a new account.
168 func (m *Manager) SaveAccount(account *Account) error {
169         m.accountMu.Lock()
170         defer m.accountMu.Unlock()
171
172         if existed := m.db.Get(aliasKey(account.Alias)); existed != nil {
173                 return ErrDuplicateAlias
174         }
175
176         acct, err := m.GetAccountByXPubsIndex(account.XPubs, account.KeyIndex)
177         if err != nil {
178                 return err
179         }
180
181         if acct != nil {
182                 return ErrDuplicateIndex
183         }
184
185         currentIndex := uint64(0)
186         if rawIndexBytes := m.db.Get(GetAccountIndexKey(account.XPubs)); rawIndexBytes != nil {
187                 currentIndex = common.BytesToUnit64(rawIndexBytes)
188         }
189         return m.saveAccount(account, account.KeyIndex > currentIndex)
190 }
191
192 // Create creates and save a new Account.
193 func (m *Manager) Create(xpubs []chainkd.XPub, quorum int, alias string, deriveRule uint8) (*Account, error) {
194         m.accountMu.Lock()
195         defer m.accountMu.Unlock()
196
197         if existed := m.db.Get(aliasKey(alias)); existed != nil {
198                 return nil, ErrDuplicateAlias
199         }
200
201         acctIndex := uint64(1)
202         if rawIndexBytes := m.db.Get(GetAccountIndexKey(xpubs)); rawIndexBytes != nil {
203                 acctIndex = common.BytesToUnit64(rawIndexBytes) + 1
204         }
205         account, err := CreateAccount(xpubs, quorum, alias, acctIndex, deriveRule)
206         if err != nil {
207                 return nil, err
208         }
209
210         if err := m.saveAccount(account, true); err != nil {
211                 return nil, err
212         }
213
214         return account, nil
215 }
216
217 func (m *Manager) UpdateAccountAlias(accountID string, newAlias string) (err error) {
218         m.accountMu.Lock()
219         defer m.accountMu.Unlock()
220
221         account, err := m.FindByID(accountID)
222         if err != nil {
223                 return err
224         }
225         oldAlias := account.Alias
226
227         normalizedAlias := strings.ToLower(strings.TrimSpace(newAlias))
228         if existed := m.db.Get(aliasKey(normalizedAlias)); existed != nil {
229                 return ErrDuplicateAlias
230         }
231
232         m.cacheMu.Lock()
233         m.aliasCache.Remove(oldAlias)
234         m.cacheMu.Unlock()
235
236         account.Alias = normalizedAlias
237         rawAccount, err := json.Marshal(account)
238         if err != nil {
239                 return ErrMarshalAccount
240         }
241
242         storeBatch := m.db.NewBatch()
243         storeBatch.Delete(aliasKey(oldAlias))
244         storeBatch.Set(Key(accountID), rawAccount)
245         storeBatch.Set(aliasKey(normalizedAlias), []byte(accountID))
246         storeBatch.Write()
247         return nil
248 }
249
250 // CreateAddress generate an address for the select account
251 func (m *Manager) CreateAddress(accountID string, change bool) (cp *CtrlProgram, err error) {
252         m.addressMu.Lock()
253         defer m.addressMu.Unlock()
254
255         account, err := m.FindByID(accountID)
256         if err != nil {
257                 return nil, err
258         }
259
260         currentIdx, err := m.getCurrentContractIndex(account, change)
261         if err != nil {
262                 return nil, err
263         }
264
265         cp, err = CreateCtrlProgram(account, currentIdx+1, change)
266         if err != nil {
267                 return nil, err
268         }
269
270         return cp, m.saveControlProgram(cp, true)
271 }
272
273 // CreateBatchAddresses generate a batch of addresses for the select account
274 func (m *Manager) CreateBatchAddresses(accountID string, change bool, stopIndex uint64) error {
275         m.addressMu.Lock()
276         defer m.addressMu.Unlock()
277
278         account, err := m.FindByID(accountID)
279         if err != nil {
280                 return err
281         }
282
283         currentIndex, err := m.getCurrentContractIndex(account, change)
284         if err != nil {
285                 return err
286         }
287
288         for currentIndex++; currentIndex <= stopIndex; currentIndex++ {
289                 cp, err := CreateCtrlProgram(account, currentIndex, change)
290                 if err != nil {
291                         return err
292                 }
293
294                 if err := m.saveControlProgram(cp, true); err != nil {
295                         return err
296                 }
297         }
298
299         return nil
300 }
301
302 // DeleteAccount deletes the account's ID or alias matching accountInfo.
303 func (m *Manager) DeleteAccount(aliasOrID string) (err error) {
304         account := &Account{}
305         if account, err = m.FindByAlias(aliasOrID); err != nil {
306                 if account, err = m.FindByID(aliasOrID); err != nil {
307                         return err
308                 }
309         }
310
311         m.cacheMu.Lock()
312         m.aliasCache.Remove(account.Alias)
313         m.cacheMu.Unlock()
314
315         storeBatch := m.db.NewBatch()
316         storeBatch.Delete(aliasKey(account.Alias))
317         storeBatch.Delete(Key(account.ID))
318         storeBatch.Write()
319         return nil
320 }
321
322 // FindByAlias retrieves an account's Signer record by its alias
323 func (m *Manager) FindByAlias(alias string) (*Account, error) {
324         m.cacheMu.Lock()
325         cachedID, ok := m.aliasCache.Get(alias)
326         m.cacheMu.Unlock()
327         if ok {
328                 return m.FindByID(cachedID.(string))
329         }
330
331         rawID := m.db.Get(aliasKey(alias))
332         if rawID == nil {
333                 return nil, ErrFindAccount
334         }
335
336         accountID := string(rawID)
337         m.cacheMu.Lock()
338         m.aliasCache.Add(alias, accountID)
339         m.cacheMu.Unlock()
340         return m.FindByID(accountID)
341 }
342
343 // FindByID returns an account's Signer record by its ID.
344 func (m *Manager) FindByID(id string) (*Account, error) {
345         m.cacheMu.Lock()
346         cachedAccount, ok := m.cache.Get(id)
347         m.cacheMu.Unlock()
348         if ok {
349                 return cachedAccount.(*Account), nil
350         }
351
352         rawAccount := m.db.Get(Key(id))
353         if rawAccount == nil {
354                 return nil, ErrFindAccount
355         }
356
357         account := &Account{}
358         if err := json.Unmarshal(rawAccount, account); err != nil {
359                 return nil, err
360         }
361
362         m.cacheMu.Lock()
363         m.cache.Add(id, account)
364         m.cacheMu.Unlock()
365         return account, nil
366 }
367
368 // GetAccountByProgram return Account by given CtrlProgram
369 func (m *Manager) GetAccountByProgram(program *CtrlProgram) (*Account, error) {
370         rawAccount := m.db.Get(Key(program.AccountID))
371         if rawAccount == nil {
372                 return nil, ErrFindAccount
373         }
374
375         account := &Account{}
376         return account, json.Unmarshal(rawAccount, account)
377 }
378
379 // GetAccountByXPubsIndex get account by xPubs and index
380 func (m *Manager) GetAccountByXPubsIndex(xPubs []chainkd.XPub, index uint64) (*Account, error) {
381         accounts, err := m.ListAccounts("")
382         if err != nil {
383                 return nil, err
384         }
385
386         for _, account := range accounts {
387                 if reflect.DeepEqual(account.XPubs, xPubs) && account.KeyIndex == index {
388                         return account, nil
389                 }
390         }
391         return nil, nil
392 }
393
394 // GetAliasByID return the account alias by given ID
395 func (m *Manager) GetAliasByID(id string) string {
396         rawAccount := m.db.Get(Key(id))
397         if rawAccount == nil {
398                 log.Warn("GetAliasByID fail to find account")
399                 return ""
400         }
401
402         account := &Account{}
403         if err := json.Unmarshal(rawAccount, account); err != nil {
404                 log.Warn(err)
405         }
406         return account.Alias
407 }
408
409 func (m *Manager) GetCoinbaseArbitrary() []byte {
410         if arbitrary := m.db.Get(CoinbaseAbKey); arbitrary != nil {
411                 return arbitrary
412         }
413         return []byte{}
414 }
415
416 // GetCoinbaseControlProgram will return a coinbase script
417 func (m *Manager) GetCoinbaseControlProgram() ([]byte, error) {
418         cp, err := m.GetCoinbaseCtrlProgram()
419         if err == ErrFindAccount {
420                 log.Warningf("GetCoinbaseControlProgram: can't find any account in db")
421                 return vmutil.DefaultCoinbaseProgram()
422         }
423         if err != nil {
424                 return nil, err
425         }
426         return cp.ControlProgram, nil
427 }
428
429 // GetCoinbaseCtrlProgram will return the coinbase CtrlProgram
430 func (m *Manager) GetCoinbaseCtrlProgram() (*CtrlProgram, error) {
431         if data := m.db.Get(miningAddressKey); data != nil {
432                 cp := &CtrlProgram{}
433                 return cp, json.Unmarshal(data, cp)
434         }
435
436         accountIter := m.db.IteratorPrefix([]byte(accountPrefix))
437         defer accountIter.Release()
438         if !accountIter.Next() {
439                 return nil, ErrFindAccount
440         }
441
442         account := &Account{}
443         if err := json.Unmarshal(accountIter.Value(), account); err != nil {
444                 return nil, err
445         }
446
447         program, err := m.CreateAddress(account.ID, false)
448         if err != nil {
449                 return nil, err
450         }
451
452         rawCP, err := json.Marshal(program)
453         if err != nil {
454                 return nil, err
455         }
456
457         m.db.Set(miningAddressKey, rawCP)
458         return program, nil
459 }
460
461 // GetContractIndex return the current index
462 func (m *Manager) GetContractIndex(accountID string) uint64 {
463         index := uint64(0)
464         if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
465                 index = common.BytesToUnit64(rawIndexBytes)
466         }
467         return index
468 }
469
470 // GetBip44ContractIndex return the current bip44 contract index
471 func (m *Manager) GetBip44ContractIndex(accountID string, change bool) uint64 {
472         index := uint64(0)
473         if rawIndexBytes := m.db.Get(bip44ContractIndexKey(accountID, change)); rawIndexBytes != nil {
474                 index = common.BytesToUnit64(rawIndexBytes)
475         }
476         return index
477 }
478
479 // GetLocalCtrlProgramByAddress return CtrlProgram by given address
480 func (m *Manager) GetLocalCtrlProgramByAddress(address string) (*CtrlProgram, error) {
481         program, err := m.getProgramByAddress(address)
482         if err != nil {
483                 return nil, err
484         }
485
486         var hash [32]byte
487         sha3pool.Sum256(hash[:], program)
488         rawProgram := m.db.Get(ContractKey(hash))
489         if rawProgram == nil {
490                 return nil, ErrFindCtrlProgram
491         }
492
493         cp := &CtrlProgram{}
494         return cp, json.Unmarshal(rawProgram, cp)
495 }
496
497 // GetMiningAddress will return the mining address
498 func (m *Manager) GetMiningAddress() (string, error) {
499         cp, err := m.GetCoinbaseCtrlProgram()
500         if err != nil {
501                 return "", err
502         }
503         return cp.Address, nil
504 }
505
506 // IsLocalControlProgram check is the input control program belong to local
507 func (m *Manager) IsLocalControlProgram(prog []byte) bool {
508         var hash common.Hash
509         sha3pool.Sum256(hash[:], prog)
510         bytes := m.db.Get(ContractKey(hash))
511         return bytes != nil
512 }
513
514 // ListAccounts will return the accounts in the db
515 func (m *Manager) ListAccounts(id string) ([]*Account, error) {
516         accounts := []*Account{}
517         accountIter := m.db.IteratorPrefix(Key(strings.TrimSpace(id)))
518         defer accountIter.Release()
519
520         for accountIter.Next() {
521                 account := &Account{}
522                 if err := json.Unmarshal(accountIter.Value(), &account); err != nil {
523                         return nil, err
524                 }
525                 accounts = append(accounts, account)
526         }
527         return accounts, nil
528 }
529
530 // ListControlProgram return all the local control program
531 func (m *Manager) ListControlProgram() ([]*CtrlProgram, error) {
532         cps := []*CtrlProgram{}
533         cpIter := m.db.IteratorPrefix(contractPrefix)
534         defer cpIter.Release()
535
536         for cpIter.Next() {
537                 cp := &CtrlProgram{}
538                 if err := json.Unmarshal(cpIter.Value(), cp); err != nil {
539                         return nil, err
540                 }
541                 cps = append(cps, cp)
542         }
543         return cps, nil
544 }
545
546 func (m *Manager) ListUnconfirmedUtxo(accountID string, isSmartContract bool) []*UTXO {
547         utxos := m.utxoKeeper.ListUnconfirmed()
548         result := []*UTXO{}
549         for _, utxo := range utxos {
550                 if segwit.IsP2WScript(utxo.ControlProgram) != isSmartContract && (accountID == utxo.AccountID || accountID == "") {
551                         result = append(result, utxo)
552                 }
553         }
554         return result
555 }
556
557 // RemoveUnconfirmedUtxo remove utxos from the utxoKeeper
558 func (m *Manager) RemoveUnconfirmedUtxo(hashes []*bc.Hash) {
559         m.utxoKeeper.RemoveUnconfirmedUtxo(hashes)
560 }
561
562 // SetMiningAddress will set the mining address
563 func (m *Manager) SetMiningAddress(miningAddress string) (string, error) {
564         program, err := m.getProgramByAddress(miningAddress)
565         if err != nil {
566                 return "", err
567         }
568
569         cp := &CtrlProgram{
570                 Address:        miningAddress,
571                 ControlProgram: program,
572         }
573         rawCP, err := json.Marshal(cp)
574         if err != nil {
575                 return "", err
576         }
577
578         m.db.Set(miningAddressKey, rawCP)
579         return m.GetMiningAddress()
580 }
581
582 func (m *Manager) SetCoinbaseArbitrary(arbitrary []byte) {
583         m.db.Set(CoinbaseAbKey, arbitrary)
584 }
585
586 // CreateCtrlProgram generate an address for the select account
587 func CreateCtrlProgram(account *Account, addrIdx uint64, change bool) (cp *CtrlProgram, err error) {
588         path, err := signers.Path(account.Signer, signers.AccountKeySpace, change, addrIdx)
589         if err != nil {
590                 return nil, err
591         }
592
593         if len(account.XPubs) == 1 {
594                 cp, err = createP2PKH(account, path)
595         } else {
596                 cp, err = createP2SH(account, path)
597         }
598         if err != nil {
599                 return nil, err
600         }
601         cp.KeyIndex, cp.Change = addrIdx, change
602         return cp, nil
603 }
604
605 func createP2PKH(account *Account, path [][]byte) (*CtrlProgram, error) {
606         derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
607         derivedPK := derivedXPubs[0].PublicKey()
608         pubHash := crypto.Ripemd160(derivedPK)
609
610         address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
611         if err != nil {
612                 return nil, err
613         }
614
615         control, err := vmutil.P2WPKHProgram([]byte(pubHash))
616         if err != nil {
617                 return nil, err
618         }
619
620         return &CtrlProgram{
621                 AccountID:      account.ID,
622                 Address:        address.EncodeAddress(),
623                 ControlProgram: control,
624         }, nil
625 }
626
627 func createP2SH(account *Account, path [][]byte) (*CtrlProgram, error) {
628         derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
629         derivedPKs := chainkd.XPubKeys(derivedXPubs)
630         signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
631         if err != nil {
632                 return nil, err
633         }
634         scriptHash := crypto.Sha256(signScript)
635
636         address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
637         if err != nil {
638                 return nil, err
639         }
640
641         control, err := vmutil.P2WSHProgram(scriptHash)
642         if err != nil {
643                 return nil, err
644         }
645
646         return &CtrlProgram{
647                 AccountID:      account.ID,
648                 Address:        address.EncodeAddress(),
649                 ControlProgram: control,
650         }, nil
651 }
652
653 func GetAccountIndexKey(xpubs []chainkd.XPub) []byte {
654         var hash [32]byte
655         var xPubs []byte
656         cpy := append([]chainkd.XPub{}, xpubs[:]...)
657         sort.Sort(signers.SortKeys(cpy))
658         for _, xpub := range cpy {
659                 xPubs = append(xPubs, xpub[:]...)
660         }
661         sha3pool.Sum256(hash[:], xPubs)
662         return append(accountIndexPrefix, hash[:]...)
663 }
664
665 func (m *Manager) getCurrentContractIndex(account *Account, change bool) (uint64, error) {
666         switch account.DeriveRule {
667         case signers.BIP0032:
668                 return m.GetContractIndex(account.ID), nil
669         case signers.BIP0044:
670                 return m.GetBip44ContractIndex(account.ID, change), nil
671         }
672         return 0, ErrDeriveRule
673 }
674
675 func (m *Manager) getProgramByAddress(address string) ([]byte, error) {
676         addr, err := common.DecodeAddress(address, &consensus.ActiveNetParams)
677         if err != nil {
678                 return nil, err
679         }
680         redeemContract := addr.ScriptAddress()
681         program := []byte{}
682         switch addr.(type) {
683         case *common.AddressWitnessPubKeyHash:
684                 program, err = vmutil.P2WPKHProgram(redeemContract)
685         case *common.AddressWitnessScriptHash:
686                 program, err = vmutil.P2WSHProgram(redeemContract)
687         default:
688                 return nil, ErrInvalidAddress
689         }
690         if err != nil {
691                 return nil, err
692         }
693         return program, nil
694 }
695
696 func (m *Manager) saveControlProgram(prog *CtrlProgram, updateIndex bool) error {
697         var hash common.Hash
698
699         sha3pool.Sum256(hash[:], prog.ControlProgram)
700         acct, err := m.GetAccountByProgram(prog)
701         if err != nil {
702                 return err
703         }
704
705         accountCP, err := json.Marshal(prog)
706         if err != nil {
707                 return err
708         }
709
710         storeBatch := m.db.NewBatch()
711         storeBatch.Set(ContractKey(hash), accountCP)
712         if updateIndex {
713                 switch acct.DeriveRule {
714                 case signers.BIP0032:
715                         storeBatch.Set(contractIndexKey(acct.ID), common.Unit64ToBytes(prog.KeyIndex))
716                 case signers.BIP0044:
717                         storeBatch.Set(bip44ContractIndexKey(acct.ID, prog.Change), common.Unit64ToBytes(prog.KeyIndex))
718                 }
719         }
720         storeBatch.Write()
721
722         return nil
723 }
724
725 // SaveControlPrograms save account control programs
726 func (m *Manager) SaveControlPrograms(progs ...*CtrlProgram) error {
727         m.addressMu.Lock()
728         defer m.addressMu.Unlock()
729
730         for _, prog := range progs {
731                 acct, err := m.GetAccountByProgram(prog)
732                 if err != nil {
733                         return err
734                 }
735
736                 currentIndex, err := m.getCurrentContractIndex(acct, prog.Change)
737                 if err != nil {
738                         return err
739                 }
740
741                 m.saveControlProgram(prog, prog.KeyIndex > currentIndex)
742         }
743         return nil
744 }