github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/account/accounts.go (about) 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 }