OSDN Git Service

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