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/bytom/blockchain/signers"
16 "github.com/bytom/blockchain/txbuilder"
17 "github.com/bytom/common"
18 "github.com/bytom/consensus"
19 "github.com/bytom/consensus/segwit"
20 "github.com/bytom/crypto"
21 "github.com/bytom/crypto/ed25519/chainkd"
22 "github.com/bytom/crypto/sha3pool"
23 "github.com/bytom/errors"
24 "github.com/bytom/protocol"
25 "github.com/bytom/protocol/bc"
26 "github.com/bytom/protocol/vm/vmutil"
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")
60 // ContractKey account control promgram store prefix
61 func ContractKey(hash common.Hash) []byte {
62 return append(contractPrefix, hash[:]...)
65 // Key account store prefix
66 func Key(name string) []byte {
67 return append(accountPrefix, []byte(name)...)
70 func aliasKey(name string) []byte {
71 return append(aliasPrefix, []byte(name)...)
74 func bip44ContractIndexKey(accountID string, change bool) []byte {
75 key := append(contractIndexPrefix, accountID...)
77 return append(key, []byte{1}...)
79 return append(key, []byte{0}...)
82 func contractIndexKey(accountID string) []byte {
83 return append(contractIndexPrefix, []byte(accountID)...)
86 // Account is structure of Bytom account
90 Alias string `json:"alias"`
93 //CtrlProgram is structure of account control program
94 type CtrlProgram struct {
99 Change bool // Mark whether this control program is for UTXO change
102 // Manager stores accounts and their associated control programs.
103 type Manager struct {
105 chain *protocol.Chain
106 utxoKeeper *utxoKeeper
110 aliasCache *lru.Cache
112 delayedACPsMu sync.Mutex
113 delayedACPs map[*txbuilder.TemplateBuilder][]*CtrlProgram
119 // NewManager creates a new account manager
120 func NewManager(walletDB dbm.DB, chain *protocol.Chain) *Manager {
124 utxoKeeper: newUtxoKeeper(chain.BestBlockHeight, walletDB),
125 cache: lru.New(maxAccountCache),
126 aliasCache: lru.New(maxAccountCache),
127 delayedACPs: make(map[*txbuilder.TemplateBuilder][]*CtrlProgram),
131 // AddUnconfirmedUtxo add untxo list to utxoKeeper
132 func (m *Manager) AddUnconfirmedUtxo(utxos []*UTXO) {
133 m.utxoKeeper.AddUnconfirmedUtxo(utxos)
136 // CreateAccount creates a new Account.
137 func CreateAccount(xpubs []chainkd.XPub, quorum int, alias string, acctIndex uint64, deriveRule uint8) (*Account, error) {
138 if acctIndex >= HardenedKeyStart {
139 return nil, ErrAccountIndex
142 signer, err := signers.Create("account", xpubs, quorum, acctIndex, deriveRule)
144 return nil, errors.Wrap(err)
147 id := signers.IDGenerate()
148 return &Account{Signer: signer, ID: id, Alias: strings.ToLower(strings.TrimSpace(alias))}, nil
151 func (m *Manager) saveAccount(account *Account, updateIndex bool) error {
152 rawAccount, err := json.Marshal(account)
154 return ErrMarshalAccount
157 storeBatch := m.db.NewBatch()
158 storeBatch.Set(Key(account.ID), rawAccount)
159 storeBatch.Set(aliasKey(account.Alias), []byte(account.ID))
161 storeBatch.Set(GetAccountIndexKey(account.XPubs), common.Unit64ToBytes(account.KeyIndex))
167 // SaveAccount save a new account.
168 func (m *Manager) SaveAccount(account *Account) error {
170 defer m.accountMu.Unlock()
172 if existed := m.db.Get(aliasKey(account.Alias)); existed != nil {
173 return ErrDuplicateAlias
176 acct, err := m.GetAccountByXPubsIndex(account.XPubs, account.KeyIndex)
182 return ErrDuplicateIndex
185 currentIndex := uint64(0)
186 if rawIndexBytes := m.db.Get(GetAccountIndexKey(account.XPubs)); rawIndexBytes != nil {
187 currentIndex = common.BytesToUnit64(rawIndexBytes)
189 return m.saveAccount(account, account.KeyIndex > currentIndex)
192 // Create creates and save a new Account.
193 func (m *Manager) Create(xpubs []chainkd.XPub, quorum int, alias string, deriveRule uint8) (*Account, error) {
195 defer m.accountMu.Unlock()
197 if existed := m.db.Get(aliasKey(alias)); existed != nil {
198 return nil, ErrDuplicateAlias
201 acctIndex := uint64(1)
202 if rawIndexBytes := m.db.Get(GetAccountIndexKey(xpubs)); rawIndexBytes != nil {
203 acctIndex = common.BytesToUnit64(rawIndexBytes) + 1
205 account, err := CreateAccount(xpubs, quorum, alias, acctIndex, deriveRule)
210 if err := m.saveAccount(account, true); err != nil {
217 func (m *Manager) UpdateAccountAlias(accountID string, newAlias string) (err error) {
219 defer m.accountMu.Unlock()
221 account, err := m.FindByID(accountID)
225 oldAlias := account.Alias
227 normalizedAlias := strings.ToLower(strings.TrimSpace(newAlias))
228 if existed := m.db.Get(aliasKey(normalizedAlias)); existed != nil {
229 return ErrDuplicateAlias
233 m.aliasCache.Remove(oldAlias)
236 account.Alias = normalizedAlias
237 rawAccount, err := json.Marshal(account)
239 return ErrMarshalAccount
242 storeBatch := m.db.NewBatch()
243 storeBatch.Delete(aliasKey(oldAlias))
244 storeBatch.Set(Key(accountID), rawAccount)
245 storeBatch.Set(aliasKey(normalizedAlias), []byte(accountID))
250 // CreateAddress generate an address for the select account
251 func (m *Manager) CreateAddress(accountID string, change bool) (cp *CtrlProgram, err error) {
253 defer m.addressMu.Unlock()
255 account, err := m.FindByID(accountID)
260 currentIdx, err := m.getCurrentContractIndex(account, change)
265 cp, err = CreateCtrlProgram(account, currentIdx+1, change)
270 return cp, m.saveControlProgram(cp, true)
273 // CreateBatchAddresses generate a batch of addresses for the select account
274 func (m *Manager) CreateBatchAddresses(accountID string, change bool, stopIndex uint64) error {
276 defer m.addressMu.Unlock()
278 account, err := m.FindByID(accountID)
283 currentIndex, err := m.getCurrentContractIndex(account, change)
288 for currentIndex++; currentIndex <= stopIndex; currentIndex++ {
289 cp, err := CreateCtrlProgram(account, currentIndex, change)
294 if err := m.saveControlProgram(cp, true); err != nil {
302 // DeleteAccount deletes the account's ID or alias matching accountInfo.
303 func (m *Manager) DeleteAccount(aliasOrID string) (err error) {
304 account := &Account{}
305 if account, err = m.FindByAlias(aliasOrID); err != nil {
306 if account, err = m.FindByID(aliasOrID); err != nil {
312 m.aliasCache.Remove(account.Alias)
315 storeBatch := m.db.NewBatch()
316 storeBatch.Delete(aliasKey(account.Alias))
317 storeBatch.Delete(Key(account.ID))
322 // FindByAlias retrieves an account's Signer record by its alias
323 func (m *Manager) FindByAlias(alias string) (*Account, error) {
325 cachedID, ok := m.aliasCache.Get(alias)
328 return m.FindByID(cachedID.(string))
331 rawID := m.db.Get(aliasKey(alias))
333 return nil, ErrFindAccount
336 accountID := string(rawID)
338 m.aliasCache.Add(alias, accountID)
340 return m.FindByID(accountID)
343 // FindByID returns an account's Signer record by its ID.
344 func (m *Manager) FindByID(id string) (*Account, error) {
346 cachedAccount, ok := m.cache.Get(id)
349 return cachedAccount.(*Account), nil
352 rawAccount := m.db.Get(Key(id))
353 if rawAccount == nil {
354 return nil, ErrFindAccount
357 account := &Account{}
358 if err := json.Unmarshal(rawAccount, account); err != nil {
363 m.cache.Add(id, account)
368 // GetAccountByProgram return Account by given CtrlProgram
369 func (m *Manager) GetAccountByProgram(program *CtrlProgram) (*Account, error) {
370 rawAccount := m.db.Get(Key(program.AccountID))
371 if rawAccount == nil {
372 return nil, ErrFindAccount
375 account := &Account{}
376 return account, json.Unmarshal(rawAccount, account)
379 // GetAccountByXPubsIndex get account by xPubs and index
380 func (m *Manager) GetAccountByXPubsIndex(xPubs []chainkd.XPub, index uint64) (*Account, error) {
381 accounts, err := m.ListAccounts("")
386 for _, account := range accounts {
387 if reflect.DeepEqual(account.XPubs, xPubs) && account.KeyIndex == index {
394 // GetAliasByID return the account alias by given ID
395 func (m *Manager) GetAliasByID(id string) string {
396 rawAccount := m.db.Get(Key(id))
397 if rawAccount == nil {
398 log.Warn("GetAliasByID fail to find account")
402 account := &Account{}
403 if err := json.Unmarshal(rawAccount, account); err != nil {
409 func (m *Manager) GetCoinbaseArbitrary() []byte {
410 if arbitrary := m.db.Get(CoinbaseAbKey); arbitrary != nil {
416 // GetCoinbaseControlProgram will return a coinbase script
417 func (m *Manager) GetCoinbaseControlProgram() ([]byte, error) {
418 cp, err := m.GetCoinbaseCtrlProgram()
419 if err == ErrFindAccount {
420 log.Warningf("GetCoinbaseControlProgram: can't find any account in db")
421 return vmutil.DefaultCoinbaseProgram()
426 return cp.ControlProgram, nil
429 // GetCoinbaseCtrlProgram will return the coinbase CtrlProgram
430 func (m *Manager) GetCoinbaseCtrlProgram() (*CtrlProgram, error) {
431 if data := m.db.Get(miningAddressKey); data != nil {
433 return cp, json.Unmarshal(data, cp)
436 accountIter := m.db.IteratorPrefix([]byte(accountPrefix))
437 defer accountIter.Release()
438 if !accountIter.Next() {
439 return nil, ErrFindAccount
442 account := &Account{}
443 if err := json.Unmarshal(accountIter.Value(), account); err != nil {
447 program, err := m.CreateAddress(account.ID, false)
452 rawCP, err := json.Marshal(program)
457 m.db.Set(miningAddressKey, rawCP)
461 // GetContractIndex return the current index
462 func (m *Manager) GetContractIndex(accountID string) uint64 {
464 if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
465 index = common.BytesToUnit64(rawIndexBytes)
470 // GetBip44ContractIndex return the current bip44 contract index
471 func (m *Manager) GetBip44ContractIndex(accountID string, change bool) uint64 {
473 if rawIndexBytes := m.db.Get(bip44ContractIndexKey(accountID, change)); rawIndexBytes != nil {
474 index = common.BytesToUnit64(rawIndexBytes)
479 // GetLocalCtrlProgramByAddress return CtrlProgram by given address
480 func (m *Manager) GetLocalCtrlProgramByAddress(address string) (*CtrlProgram, error) {
481 program, err := m.getProgramByAddress(address)
487 sha3pool.Sum256(hash[:], program)
488 rawProgram := m.db.Get(ContractKey(hash))
489 if rawProgram == nil {
490 return nil, ErrFindCtrlProgram
494 return cp, json.Unmarshal(rawProgram, cp)
497 // GetMiningAddress will return the mining address
498 func (m *Manager) GetMiningAddress() (string, error) {
499 cp, err := m.GetCoinbaseCtrlProgram()
503 return cp.Address, nil
506 // IsLocalControlProgram check is the input control program belong to local
507 func (m *Manager) IsLocalControlProgram(prog []byte) bool {
509 sha3pool.Sum256(hash[:], prog)
510 bytes := m.db.Get(ContractKey(hash))
514 // ListAccounts will return the accounts in the db
515 func (m *Manager) ListAccounts(id string) ([]*Account, error) {
516 accounts := []*Account{}
517 accountIter := m.db.IteratorPrefix(Key(strings.TrimSpace(id)))
518 defer accountIter.Release()
520 for accountIter.Next() {
521 account := &Account{}
522 if err := json.Unmarshal(accountIter.Value(), &account); err != nil {
525 accounts = append(accounts, account)
530 // ListControlProgram return all the local control program
531 func (m *Manager) ListControlProgram() ([]*CtrlProgram, error) {
532 cps := []*CtrlProgram{}
533 cpIter := m.db.IteratorPrefix(contractPrefix)
534 defer cpIter.Release()
538 if err := json.Unmarshal(cpIter.Value(), cp); err != nil {
541 cps = append(cps, cp)
546 func (m *Manager) ListUnconfirmedUtxo(accountID string, isSmartContract bool) []*UTXO {
547 utxos := m.utxoKeeper.ListUnconfirmed()
549 for _, utxo := range utxos {
550 if segwit.IsP2WScript(utxo.ControlProgram) != isSmartContract && (accountID == utxo.AccountID || accountID == "") {
551 result = append(result, utxo)
557 // RemoveUnconfirmedUtxo remove utxos from the utxoKeeper
558 func (m *Manager) RemoveUnconfirmedUtxo(hashes []*bc.Hash) {
559 m.utxoKeeper.RemoveUnconfirmedUtxo(hashes)
562 // SetMiningAddress will set the mining address
563 func (m *Manager) SetMiningAddress(miningAddress string) (string, error) {
564 program, err := m.getProgramByAddress(miningAddress)
570 Address: miningAddress,
571 ControlProgram: program,
573 rawCP, err := json.Marshal(cp)
578 m.db.Set(miningAddressKey, rawCP)
579 return m.GetMiningAddress()
582 func (m *Manager) SetCoinbaseArbitrary(arbitrary []byte) {
583 m.db.Set(CoinbaseAbKey, arbitrary)
586 // CreateCtrlProgram generate an address for the select account
587 func CreateCtrlProgram(account *Account, addrIdx uint64, change bool) (cp *CtrlProgram, err error) {
588 path, err := signers.Path(account.Signer, signers.AccountKeySpace, change, addrIdx)
593 if len(account.XPubs) == 1 {
594 cp, err = createP2PKH(account, path)
596 cp, err = createP2SH(account, path)
601 cp.KeyIndex, cp.Change = addrIdx, change
605 func createP2PKH(account *Account, path [][]byte) (*CtrlProgram, error) {
606 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
607 derivedPK := derivedXPubs[0].PublicKey()
608 pubHash := crypto.Ripemd160(derivedPK)
610 address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
615 control, err := vmutil.P2WPKHProgram([]byte(pubHash))
621 AccountID: account.ID,
622 Address: address.EncodeAddress(),
623 ControlProgram: control,
627 func createP2SH(account *Account, path [][]byte) (*CtrlProgram, error) {
628 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
629 derivedPKs := chainkd.XPubKeys(derivedXPubs)
630 signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
634 scriptHash := crypto.Sha256(signScript)
636 address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
641 control, err := vmutil.P2WSHProgram(scriptHash)
647 AccountID: account.ID,
648 Address: address.EncodeAddress(),
649 ControlProgram: control,
653 func GetAccountIndexKey(xpubs []chainkd.XPub) []byte {
656 cpy := append([]chainkd.XPub{}, xpubs[:]...)
657 sort.Sort(signers.SortKeys(cpy))
658 for _, xpub := range cpy {
659 xPubs = append(xPubs, xpub[:]...)
661 sha3pool.Sum256(hash[:], xPubs)
662 return append(accountIndexPrefix, hash[:]...)
665 func (m *Manager) getCurrentContractIndex(account *Account, change bool) (uint64, error) {
666 switch account.DeriveRule {
667 case signers.BIP0032:
668 return m.GetContractIndex(account.ID), nil
669 case signers.BIP0044:
670 return m.GetBip44ContractIndex(account.ID, change), nil
672 return 0, ErrDeriveRule
675 func (m *Manager) getProgramByAddress(address string) ([]byte, error) {
676 addr, err := common.DecodeAddress(address, &consensus.ActiveNetParams)
680 redeemContract := addr.ScriptAddress()
683 case *common.AddressWitnessPubKeyHash:
684 program, err = vmutil.P2WPKHProgram(redeemContract)
685 case *common.AddressWitnessScriptHash:
686 program, err = vmutil.P2WSHProgram(redeemContract)
688 return nil, ErrInvalidAddress
696 func (m *Manager) saveControlProgram(prog *CtrlProgram, updateIndex bool) error {
699 sha3pool.Sum256(hash[:], prog.ControlProgram)
700 acct, err := m.GetAccountByProgram(prog)
705 accountCP, err := json.Marshal(prog)
710 storeBatch := m.db.NewBatch()
711 storeBatch.Set(ContractKey(hash), accountCP)
713 switch acct.DeriveRule {
714 case signers.BIP0032:
715 storeBatch.Set(contractIndexKey(acct.ID), common.Unit64ToBytes(prog.KeyIndex))
716 case signers.BIP0044:
717 storeBatch.Set(bip44ContractIndexKey(acct.ID, prog.Change), common.Unit64ToBytes(prog.KeyIndex))
725 // SaveControlPrograms save account control programs
726 func (m *Manager) SaveControlPrograms(progs ...*CtrlProgram) error {
728 defer m.addressMu.Unlock()
730 for _, prog := range progs {
731 acct, err := m.GetAccountByProgram(prog)
736 currentIndex, err := m.getCurrentContractIndex(acct, prog.Change)
741 m.saveControlProgram(prog, prog.KeyIndex > currentIndex)