1 // Package account stores and tracks accounts within a Bytom Core.
11 "github.com/golang/groupcache/lru"
12 log "github.com/sirupsen/logrus"
13 dbm "github.com/tendermint/tmlibs/db"
15 "github.com/vapor/blockchain/signers"
16 "github.com/vapor/blockchain/txbuilder"
17 "github.com/vapor/common"
18 "github.com/vapor/consensus"
19 "github.com/vapor/consensus/segwit"
20 "github.com/vapor/crypto"
21 "github.com/vapor/crypto/ed25519/chainkd"
22 "github.com/vapor/crypto/sha3pool"
23 "github.com/vapor/errors"
24 "github.com/vapor/protocol"
25 "github.com/vapor/protocol/bc"
26 "github.com/vapor/protocol/vm/vmutil"
30 maxAccountCache = 1000
32 // HardenedKeyStart bip32 hierarchical deterministic wallets
33 // keys with index ≥ 0x80000000 are hardened keys
34 HardenedKeyStart = 0x80000000
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")
47 // pre-define errors for supporting bytom errorFormatter
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 ErrFindTransaction = errors.New("no transaction")
61 // ContractKey account control promgram store prefix
62 func ContractKey(hash common.Hash) []byte {
63 return append(contractPrefix, hash[:]...)
66 // Key account store prefix
67 func Key(name string) []byte {
68 return append(accountPrefix, []byte(name)...)
71 func aliasKey(name string) []byte {
72 return append(aliasPrefix, []byte(name)...)
75 func bip44ContractIndexKey(accountID string, change bool) []byte {
76 key := append(contractIndexPrefix, accountID...)
78 return append(key, []byte{1}...)
80 return append(key, []byte{0}...)
83 func contractIndexKey(accountID string) []byte {
84 return append(contractIndexPrefix, []byte(accountID)...)
87 // Account is structure of Bytom account
91 Alias string `json:"alias"`
94 //CtrlProgram is structure of account control program
95 type CtrlProgram struct {
100 Change bool // Mark whether this control program is for UTXO change
103 // Manager stores accounts and their associated control programs.
104 type Manager struct {
106 chain *protocol.Chain
107 utxoKeeper *utxoKeeper
111 aliasCache *lru.Cache
113 delayedACPsMu sync.Mutex
114 delayedACPs map[*txbuilder.TemplateBuilder][]*CtrlProgram
120 // NewManager creates a new account manager
121 func NewManager(walletDB dbm.DB, chain *protocol.Chain) *Manager {
125 utxoKeeper: newUtxoKeeper(chain.BestBlockHeight, walletDB),
126 cache: lru.New(maxAccountCache),
127 aliasCache: lru.New(maxAccountCache),
128 delayedACPs: make(map[*txbuilder.TemplateBuilder][]*CtrlProgram),
132 // AddUnconfirmedUtxo add untxo list to utxoKeeper
133 func (m *Manager) AddUnconfirmedUtxo(utxos []*UTXO) {
134 m.utxoKeeper.AddUnconfirmedUtxo(utxos)
137 // CreateAccount creates a new Account.
138 func CreateAccount(xpubs []chainkd.XPub, quorum int, alias string, acctIndex uint64, deriveRule uint8) (*Account, error) {
139 if acctIndex >= HardenedKeyStart {
140 return nil, ErrAccountIndex
143 signer, err := signers.Create("account", xpubs, quorum, acctIndex, deriveRule)
145 return nil, errors.Wrap(err)
148 id := signers.IDGenerate()
149 return &Account{Signer: signer, ID: id, Alias: strings.ToLower(strings.TrimSpace(alias))}, nil
152 func (m *Manager) saveAccount(account *Account, updateIndex bool) error {
153 rawAccount, err := json.Marshal(account)
155 return ErrMarshalAccount
158 storeBatch := m.db.NewBatch()
159 storeBatch.Set(Key(account.ID), rawAccount)
160 storeBatch.Set(aliasKey(account.Alias), []byte(account.ID))
162 storeBatch.Set(GetAccountIndexKey(account.XPubs), common.Unit64ToBytes(account.KeyIndex))
168 // SaveAccount save a new account.
169 func (m *Manager) SaveAccount(account *Account) error {
171 defer m.accountMu.Unlock()
173 if existed := m.db.Get(aliasKey(account.Alias)); existed != nil {
174 return ErrDuplicateAlias
177 acct, err := m.GetAccountByXPubsIndex(account.XPubs, account.KeyIndex)
183 return ErrDuplicateIndex
186 currentIndex := uint64(0)
187 if rawIndexBytes := m.db.Get(GetAccountIndexKey(account.XPubs)); rawIndexBytes != nil {
188 currentIndex = common.BytesToUnit64(rawIndexBytes)
190 return m.saveAccount(account, account.KeyIndex > currentIndex)
193 // Create creates and save a new Account.
194 func (m *Manager) Create(xpubs []chainkd.XPub, quorum int, alias string, deriveRule uint8) (*Account, error) {
196 defer m.accountMu.Unlock()
198 if existed := m.db.Get(aliasKey(alias)); existed != nil {
199 return nil, ErrDuplicateAlias
202 acctIndex := uint64(1)
203 if rawIndexBytes := m.db.Get(GetAccountIndexKey(xpubs)); rawIndexBytes != nil {
204 acctIndex = common.BytesToUnit64(rawIndexBytes) + 1
206 account, err := CreateAccount(xpubs, quorum, alias, acctIndex, deriveRule)
211 if err := m.saveAccount(account, true); err != nil {
218 func (m *Manager) UpdateAccountAlias(accountID string, newAlias string) (err error) {
220 defer m.accountMu.Unlock()
222 account, err := m.FindByID(accountID)
226 oldAlias := account.Alias
228 normalizedAlias := strings.ToLower(strings.TrimSpace(newAlias))
229 if existed := m.db.Get(aliasKey(normalizedAlias)); existed != nil {
230 return ErrDuplicateAlias
234 m.aliasCache.Remove(oldAlias)
237 account.Alias = normalizedAlias
238 rawAccount, err := json.Marshal(account)
240 return ErrMarshalAccount
243 storeBatch := m.db.NewBatch()
244 storeBatch.Delete(aliasKey(oldAlias))
245 storeBatch.Set(Key(accountID), rawAccount)
246 storeBatch.Set(aliasKey(normalizedAlias), []byte(accountID))
251 // CreateAddress generate an address for the select account
252 func (m *Manager) CreateAddress(accountID string, change bool) (cp *CtrlProgram, err error) {
254 defer m.addressMu.Unlock()
256 account, err := m.FindByID(accountID)
261 currentIdx, err := m.getCurrentContractIndex(account, change)
266 cp, err = CreateCtrlProgram(account, currentIdx+1, change)
271 return cp, m.saveControlProgram(cp, true)
274 // CreateBatchAddresses generate a batch of addresses for the select account
275 func (m *Manager) CreateBatchAddresses(accountID string, change bool, stopIndex uint64) error {
277 defer m.addressMu.Unlock()
279 account, err := m.FindByID(accountID)
284 currentIndex, err := m.getCurrentContractIndex(account, change)
289 for currentIndex++; currentIndex <= stopIndex; currentIndex++ {
290 cp, err := CreateCtrlProgram(account, currentIndex, change)
295 if err := m.saveControlProgram(cp, true); err != nil {
303 // deleteAccountControlPrograms deletes control program matching accountID
304 func (m *Manager) deleteAccountControlPrograms(accountID string) error {
305 cps, err := m.ListControlProgram()
311 for _, cp := range cps {
312 if cp.AccountID == accountID {
313 sha3pool.Sum256(hash[:], cp.ControlProgram)
314 m.db.Delete(ContractKey(hash))
320 // deleteAccountUtxos deletes utxos matching accountID
321 func (m *Manager) deleteAccountUtxos(accountID string) error {
322 accountUtxoIter := m.db.IteratorPrefix([]byte(UTXOPreFix))
323 defer accountUtxoIter.Release()
324 for accountUtxoIter.Next() {
325 accountUtxo := &UTXO{}
326 if err := json.Unmarshal(accountUtxoIter.Value(), accountUtxo); err != nil {
330 if accountID == accountUtxo.AccountID {
331 m.db.Delete(StandardUTXOKey(accountUtxo.OutputID))
337 // DeleteAccount deletes the account's ID or alias matching account ID.
338 func (m *Manager) DeleteAccount(accountID string) (err error) {
340 defer m.accountMu.Unlock()
342 account, err := m.FindByID(accountID)
347 if err := m.deleteAccountControlPrograms(accountID); err != nil {
350 if err := m.deleteAccountUtxos(accountID); err != nil {
355 m.aliasCache.Remove(account.Alias)
358 storeBatch := m.db.NewBatch()
359 storeBatch.Delete(aliasKey(account.Alias))
360 storeBatch.Delete(Key(account.ID))
365 // FindByAlias retrieves an account's Signer record by its alias
366 func (m *Manager) FindByAlias(alias string) (*Account, error) {
368 cachedID, ok := m.aliasCache.Get(alias)
371 return m.FindByID(cachedID.(string))
374 rawID := m.db.Get(aliasKey(alias))
376 return nil, ErrFindAccount
379 accountID := string(rawID)
381 m.aliasCache.Add(alias, accountID)
383 return m.FindByID(accountID)
386 // FindByID returns an account's Signer record by its ID.
387 func (m *Manager) FindByID(id string) (*Account, error) {
389 cachedAccount, ok := m.cache.Get(id)
392 return cachedAccount.(*Account), nil
395 rawAccount := m.db.Get(Key(id))
396 if rawAccount == nil {
397 return nil, ErrFindAccount
400 account := &Account{}
401 if err := json.Unmarshal(rawAccount, account); err != nil {
406 m.cache.Add(id, account)
411 // GetAccountByProgram return Account by given CtrlProgram
412 func (m *Manager) GetAccountByProgram(program *CtrlProgram) (*Account, error) {
413 rawAccount := m.db.Get(Key(program.AccountID))
414 if rawAccount == nil {
415 return nil, ErrFindAccount
418 account := &Account{}
419 return account, json.Unmarshal(rawAccount, account)
422 // GetAccountByXPubsIndex get account by xPubs and index
423 func (m *Manager) GetAccountByXPubsIndex(xPubs []chainkd.XPub, index uint64) (*Account, error) {
424 accounts, err := m.ListAccounts("")
429 for _, account := range accounts {
430 if reflect.DeepEqual(account.XPubs, xPubs) && account.KeyIndex == index {
437 // GetAliasByID return the account alias by given ID
438 func (m *Manager) GetAliasByID(id string) string {
439 rawAccount := m.db.Get(Key(id))
440 if rawAccount == nil {
441 log.Warn("GetAliasByID fail to find account")
445 account := &Account{}
446 if err := json.Unmarshal(rawAccount, account); err != nil {
452 func (m *Manager) GetCoinbaseArbitrary() []byte {
453 if arbitrary := m.db.Get(CoinbaseAbKey); arbitrary != nil {
459 // GetCoinbaseControlProgram will return a coinbase script
460 func (m *Manager) GetCoinbaseControlProgram() ([]byte, error) {
461 cp, err := m.GetCoinbaseCtrlProgram()
462 if err == ErrFindAccount {
463 log.Warningf("GetCoinbaseControlProgram: can't find any account in db")
464 return vmutil.DefaultCoinbaseProgram()
469 return cp.ControlProgram, nil
472 // GetCoinbaseCtrlProgram will return the coinbase CtrlProgram
473 func (m *Manager) GetCoinbaseCtrlProgram() (*CtrlProgram, error) {
474 if data := m.db.Get(miningAddressKey); data != nil {
476 return cp, json.Unmarshal(data, cp)
479 accountIter := m.db.IteratorPrefix([]byte(accountPrefix))
480 defer accountIter.Release()
481 if !accountIter.Next() {
482 return nil, ErrFindAccount
485 account := &Account{}
486 if err := json.Unmarshal(accountIter.Value(), account); err != nil {
490 program, err := m.CreateAddress(account.ID, false)
495 rawCP, err := json.Marshal(program)
500 m.db.Set(miningAddressKey, rawCP)
504 // GetContractIndex return the current index
505 func (m *Manager) GetContractIndex(accountID string) uint64 {
507 if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
508 index = common.BytesToUnit64(rawIndexBytes)
513 // GetBip44ContractIndex return the current bip44 contract index
514 func (m *Manager) GetBip44ContractIndex(accountID string, change bool) uint64 {
516 if rawIndexBytes := m.db.Get(bip44ContractIndexKey(accountID, change)); rawIndexBytes != nil {
517 index = common.BytesToUnit64(rawIndexBytes)
522 // GetLocalCtrlProgramByAddress return CtrlProgram by given address
523 func (m *Manager) GetLocalCtrlProgramByAddress(address string) (*CtrlProgram, error) {
524 program, err := m.getProgramByAddress(address)
530 sha3pool.Sum256(hash[:], program)
531 rawProgram := m.db.Get(ContractKey(hash))
532 if rawProgram == nil {
533 return nil, ErrFindCtrlProgram
537 return cp, json.Unmarshal(rawProgram, cp)
540 // GetMiningAddress will return the mining address
541 func (m *Manager) GetMiningAddress() (string, error) {
542 cp, err := m.GetCoinbaseCtrlProgram()
546 return cp.Address, nil
549 // IsLocalControlProgram check is the input control program belong to local
550 func (m *Manager) IsLocalControlProgram(prog []byte) bool {
552 sha3pool.Sum256(hash[:], prog)
553 bytes := m.db.Get(ContractKey(hash))
557 // ListAccounts will return the accounts in the db
558 func (m *Manager) ListAccounts(id string) ([]*Account, error) {
559 accounts := []*Account{}
560 accountIter := m.db.IteratorPrefix(Key(strings.TrimSpace(id)))
561 defer accountIter.Release()
563 for accountIter.Next() {
564 account := &Account{}
565 if err := json.Unmarshal(accountIter.Value(), &account); err != nil {
568 accounts = append(accounts, account)
573 // ListControlProgram return all the local control program
574 func (m *Manager) ListControlProgram() ([]*CtrlProgram, error) {
575 cps := []*CtrlProgram{}
576 cpIter := m.db.IteratorPrefix(contractPrefix)
577 defer cpIter.Release()
581 if err := json.Unmarshal(cpIter.Value(), cp); err != nil {
584 cps = append(cps, cp)
589 func (m *Manager) ListUnconfirmedUtxo(accountID string, isSmartContract bool) []*UTXO {
590 utxos := m.utxoKeeper.ListUnconfirmed()
592 for _, utxo := range utxos {
593 if segwit.IsP2WScript(utxo.ControlProgram) != isSmartContract && (accountID == utxo.AccountID || accountID == "") {
594 result = append(result, utxo)
600 // RemoveUnconfirmedUtxo remove utxos from the utxoKeeper
601 func (m *Manager) RemoveUnconfirmedUtxo(hashes []*bc.Hash) {
602 m.utxoKeeper.RemoveUnconfirmedUtxo(hashes)
605 // SetMiningAddress will set the mining address
606 func (m *Manager) SetMiningAddress(miningAddress string) (string, error) {
607 program, err := m.getProgramByAddress(miningAddress)
613 Address: miningAddress,
614 ControlProgram: program,
616 rawCP, err := json.Marshal(cp)
621 m.db.Set(miningAddressKey, rawCP)
622 return m.GetMiningAddress()
625 func (m *Manager) SetCoinbaseArbitrary(arbitrary []byte) {
626 m.db.Set(CoinbaseAbKey, arbitrary)
629 // CreateCtrlProgram generate an address for the select account
630 func CreateCtrlProgram(account *Account, addrIdx uint64, change bool) (cp *CtrlProgram, err error) {
631 path, err := signers.Path(account.Signer, signers.AccountKeySpace, change, addrIdx)
636 if len(account.XPubs) == 1 {
637 cp, err = createP2PKH(account, path)
639 cp, err = createP2SH(account, path)
644 cp.KeyIndex, cp.Change = addrIdx, change
648 func createP2PKH(account *Account, path [][]byte) (*CtrlProgram, error) {
649 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
650 derivedPK := derivedXPubs[0].PublicKey()
651 pubHash := crypto.Ripemd160(derivedPK)
653 address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
658 control, err := vmutil.P2WPKHProgram([]byte(pubHash))
664 AccountID: account.ID,
665 Address: address.EncodeAddress(),
666 ControlProgram: control,
670 func createP2SH(account *Account, path [][]byte) (*CtrlProgram, error) {
671 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
672 derivedPKs := chainkd.XPubKeys(derivedXPubs)
673 signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
677 scriptHash := crypto.Sha256(signScript)
679 address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
684 control, err := vmutil.P2WSHProgram(scriptHash)
690 AccountID: account.ID,
691 Address: address.EncodeAddress(),
692 ControlProgram: control,
696 func GetAccountIndexKey(xpubs []chainkd.XPub) []byte {
699 cpy := append([]chainkd.XPub{}, xpubs[:]...)
700 sort.Sort(signers.SortKeys(cpy))
701 for _, xpub := range cpy {
702 xPubs = append(xPubs, xpub[:]...)
704 sha3pool.Sum256(hash[:], xPubs)
705 return append(accountIndexPrefix, hash[:]...)
708 func (m *Manager) getCurrentContractIndex(account *Account, change bool) (uint64, error) {
709 switch account.DeriveRule {
710 case signers.BIP0032:
711 return m.GetContractIndex(account.ID), nil
712 case signers.BIP0044:
713 return m.GetBip44ContractIndex(account.ID, change), nil
715 return 0, ErrDeriveRule
718 func (m *Manager) getProgramByAddress(address string) ([]byte, error) {
719 addr, err := common.DecodeAddress(address, &consensus.ActiveNetParams)
723 redeemContract := addr.ScriptAddress()
726 case *common.AddressWitnessPubKeyHash:
727 program, err = vmutil.P2WPKHProgram(redeemContract)
728 case *common.AddressWitnessScriptHash:
729 program, err = vmutil.P2WSHProgram(redeemContract)
731 return nil, ErrInvalidAddress
739 func (m *Manager) saveControlProgram(prog *CtrlProgram, updateIndex bool) error {
742 sha3pool.Sum256(hash[:], prog.ControlProgram)
743 acct, err := m.GetAccountByProgram(prog)
748 accountCP, err := json.Marshal(prog)
753 storeBatch := m.db.NewBatch()
754 storeBatch.Set(ContractKey(hash), accountCP)
756 switch acct.DeriveRule {
757 case signers.BIP0032:
758 storeBatch.Set(contractIndexKey(acct.ID), common.Unit64ToBytes(prog.KeyIndex))
759 case signers.BIP0044:
760 storeBatch.Set(bip44ContractIndexKey(acct.ID, prog.Change), common.Unit64ToBytes(prog.KeyIndex))
768 // SaveControlPrograms save account control programs
769 func (m *Manager) SaveControlPrograms(progs ...*CtrlProgram) error {
771 defer m.addressMu.Unlock()
773 for _, prog := range progs {
774 acct, err := m.GetAccountByProgram(prog)
779 currentIndex, err := m.getCurrentContractIndex(acct, prog.Change)
784 m.saveControlProgram(prog, prog.KeyIndex > currentIndex)
789 func (m *Manager) CreatePeginAddress(accountID string, change bool) (string, []byte, error) {
791 claimCtrlProg, _ := m.CreateAddress(accountID, change)
792 claimScript := claimCtrlProg.ControlProgram
794 federationRedeemScript := vmutil.CalculateContract(consensus.ActiveNetParams.FedpegXPubs, claimScript)
796 scriptHash := crypto.Sha256(federationRedeemScript)
798 address, err := common.NewPeginAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
803 return address.EncodeAddress(), claimScript, nil
807 func (m *Manager) GetPeginControlPrograms(claimScript []byte) (string, []byte) {
808 federationRedeemScript := vmutil.CalculateContract(consensus.ActiveNetParams.FedpegXPubs, claimScript)
809 scriptHash := crypto.Sha256(federationRedeemScript)
811 address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
816 redeemContract := address.ScriptAddress()
819 program, err = vmutil.P2WSHProgram(redeemContract)
824 return address.EncodeAddress(), program