OSDN Git Service

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