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
39 accountIndexPrefix = []byte("AccountIndex:")
40 accountPrefix = []byte("Account:")
41 aliasPrefix = []byte("AccountAlias:")
42 contractIndexPrefix = []byte("ContractIndex")
43 contractPrefix = []byte("Contract:")
44 miningAddressKey = []byte("MiningAddress")
45 CoinbaseAbKey = []byte("CoinbaseArbitrary")
48 // pre-define errors for supporting bytom errorFormatter
50 ErrDuplicateAlias = errors.New("duplicate account alias")
51 ErrDuplicateIndex = errors.New("duplicate account with same xPubs and index")
52 ErrFindAccount = errors.New("fail to find account")
53 ErrMarshalAccount = errors.New("failed marshal account")
54 ErrInvalidAddress = errors.New("invalid address")
55 ErrFindCtrlProgram = errors.New("fail to find account control program")
56 ErrDeriveRule = errors.New("invalid key derive rule")
57 ErrContractIndex = errors.New("exceed the maximum addresses per account")
58 ErrAccountIndex = errors.New("exceed the maximum accounts per xpub")
59 ErrFindTransaction = errors.New("no transaction")
62 // ContractKey account control promgram store prefix
63 func ContractKey(hash common.Hash) []byte {
64 return append(contractPrefix, hash[:]...)
67 // Key account store prefix
68 func Key(name string) []byte {
69 return append(accountPrefix, []byte(name)...)
72 func aliasKey(name string) []byte {
73 return append(aliasPrefix, []byte(name)...)
76 func bip44ContractIndexKey(accountID string, change bool) []byte {
77 key := append(contractIndexPrefix, accountID...)
79 return append(key, []byte{1}...)
81 return append(key, []byte{0}...)
84 func contractIndexKey(accountID string) []byte {
85 return append(contractIndexPrefix, []byte(accountID)...)
88 // Account is structure of Bytom account
92 Alias string `json:"alias"`
95 //CtrlProgram is structure of account control program
96 type CtrlProgram struct {
100 ControlProgram []byte
101 Change bool // Mark whether this control program is for UTXO change
104 // Manager stores accounts and their associated control programs.
105 type Manager struct {
107 chain *protocol.Chain
108 utxoKeeper *utxoKeeper
112 aliasCache *lru.Cache
114 delayedACPsMu sync.Mutex
115 delayedACPs map[*txbuilder.TemplateBuilder][]*CtrlProgram
121 // NewManager creates a new account manager
122 func NewManager(walletDB dbm.DB, chain *protocol.Chain) *Manager {
126 utxoKeeper: newUtxoKeeper(chain.BestBlockHeight, walletDB),
127 cache: lru.New(maxAccountCache),
128 aliasCache: lru.New(maxAccountCache),
129 delayedACPs: make(map[*txbuilder.TemplateBuilder][]*CtrlProgram),
133 // AddUnconfirmedUtxo add untxo list to utxoKeeper
134 func (m *Manager) AddUnconfirmedUtxo(utxos []*UTXO) {
135 m.utxoKeeper.AddUnconfirmedUtxo(utxos)
138 // CreateAccount creates a new Account.
139 func CreateAccount(xpubs []chainkd.XPub, quorum int, alias string, acctIndex uint64, deriveRule uint8) (*Account, error) {
140 if acctIndex >= HardenedKeyStart {
141 return nil, ErrAccountIndex
144 signer, err := signers.Create("account", xpubs, quorum, acctIndex, deriveRule)
146 return nil, errors.Wrap(err)
149 id := signers.IDGenerate()
150 return &Account{Signer: signer, ID: id, Alias: strings.ToLower(strings.TrimSpace(alias))}, nil
153 func (m *Manager) saveAccount(account *Account, updateIndex bool) error {
154 rawAccount, err := json.Marshal(account)
156 return ErrMarshalAccount
159 storeBatch := m.db.NewBatch()
160 storeBatch.Set(Key(account.ID), rawAccount)
161 storeBatch.Set(aliasKey(account.Alias), []byte(account.ID))
163 storeBatch.Set(GetAccountIndexKey(account.XPubs), common.Unit64ToBytes(account.KeyIndex))
169 // SaveAccount save a new account.
170 func (m *Manager) SaveAccount(account *Account) error {
172 defer m.accountMu.Unlock()
174 if existed := m.db.Get(aliasKey(account.Alias)); existed != nil {
175 return ErrDuplicateAlias
178 acct, err := m.GetAccountByXPubsIndex(account.XPubs, account.KeyIndex)
184 return ErrDuplicateIndex
187 currentIndex := uint64(0)
188 if rawIndexBytes := m.db.Get(GetAccountIndexKey(account.XPubs)); rawIndexBytes != nil {
189 currentIndex = common.BytesToUnit64(rawIndexBytes)
191 return m.saveAccount(account, account.KeyIndex > currentIndex)
194 // Create creates and save a new Account.
195 func (m *Manager) Create(xpubs []chainkd.XPub, quorum int, alias string, deriveRule uint8) (*Account, error) {
197 defer m.accountMu.Unlock()
199 if existed := m.db.Get(aliasKey(alias)); existed != nil {
200 return nil, ErrDuplicateAlias
203 acctIndex := uint64(1)
204 if rawIndexBytes := m.db.Get(GetAccountIndexKey(xpubs)); rawIndexBytes != nil {
205 acctIndex = common.BytesToUnit64(rawIndexBytes) + 1
207 account, err := CreateAccount(xpubs, quorum, alias, acctIndex, deriveRule)
212 if err := m.saveAccount(account, true); err != nil {
219 func (m *Manager) UpdateAccountAlias(accountID string, newAlias string) (err error) {
221 defer m.accountMu.Unlock()
223 account, err := m.FindByID(accountID)
227 oldAlias := account.Alias
229 normalizedAlias := strings.ToLower(strings.TrimSpace(newAlias))
230 if existed := m.db.Get(aliasKey(normalizedAlias)); existed != nil {
231 return ErrDuplicateAlias
235 m.aliasCache.Remove(oldAlias)
238 account.Alias = normalizedAlias
239 rawAccount, err := json.Marshal(account)
241 return ErrMarshalAccount
244 storeBatch := m.db.NewBatch()
245 storeBatch.Delete(aliasKey(oldAlias))
246 storeBatch.Set(Key(accountID), rawAccount)
247 storeBatch.Set(aliasKey(normalizedAlias), []byte(accountID))
252 // CreateAddress generate an address for the select account
253 func (m *Manager) CreateAddress(accountID string, change bool) (cp *CtrlProgram, err error) {
255 defer m.addressMu.Unlock()
257 account, err := m.FindByID(accountID)
262 currentIdx, err := m.getCurrentContractIndex(account, change)
267 cp, err = CreateCtrlProgram(account, currentIdx+1, change)
272 return cp, m.saveControlProgram(cp, true)
275 // CreateBatchAddresses generate a batch of addresses for the select account
276 func (m *Manager) CreateBatchAddresses(accountID string, change bool, stopIndex uint64) error {
278 defer m.addressMu.Unlock()
280 account, err := m.FindByID(accountID)
285 currentIndex, err := m.getCurrentContractIndex(account, change)
290 for currentIndex++; currentIndex <= stopIndex; currentIndex++ {
291 cp, err := CreateCtrlProgram(account, currentIndex, change)
296 if err := m.saveControlProgram(cp, true); err != nil {
304 // deleteAccountControlPrograms deletes control program matching accountID
305 func (m *Manager) deleteAccountControlPrograms(accountID string) error {
306 cps, err := m.ListControlProgram()
312 for _, cp := range cps {
313 if cp.AccountID == accountID {
314 sha3pool.Sum256(hash[:], cp.ControlProgram)
315 m.db.Delete(ContractKey(hash))
321 // deleteAccountUtxos deletes utxos matching accountID
322 func (m *Manager) deleteAccountUtxos(accountID string) error {
323 accountUtxoIter := m.db.IteratorPrefix([]byte(UTXOPreFix))
324 defer accountUtxoIter.Release()
325 for accountUtxoIter.Next() {
326 accountUtxo := &UTXO{}
327 if err := json.Unmarshal(accountUtxoIter.Value(), accountUtxo); err != nil {
331 if accountID == accountUtxo.AccountID {
332 m.db.Delete(StandardUTXOKey(accountUtxo.OutputID))
338 // DeleteAccount deletes the account's ID or alias matching account ID.
339 func (m *Manager) DeleteAccount(accountID string) (err error) {
341 defer m.accountMu.Unlock()
343 account, err := m.FindByID(accountID)
348 if err := m.deleteAccountControlPrograms(accountID); err != nil {
351 if err := m.deleteAccountUtxos(accountID); err != nil {
356 m.aliasCache.Remove(account.Alias)
359 storeBatch := m.db.NewBatch()
360 storeBatch.Delete(aliasKey(account.Alias))
361 storeBatch.Delete(Key(account.ID))
366 // FindByAlias retrieves an account's Signer record by its alias
367 func (m *Manager) FindByAlias(alias string) (*Account, error) {
369 cachedID, ok := m.aliasCache.Get(alias)
372 return m.FindByID(cachedID.(string))
375 rawID := m.db.Get(aliasKey(alias))
377 return nil, ErrFindAccount
380 accountID := string(rawID)
382 m.aliasCache.Add(alias, accountID)
384 return m.FindByID(accountID)
387 // FindByID returns an account's Signer record by its ID.
388 func (m *Manager) FindByID(id string) (*Account, error) {
390 cachedAccount, ok := m.cache.Get(id)
393 return cachedAccount.(*Account), nil
396 rawAccount := m.db.Get(Key(id))
397 if rawAccount == nil {
398 return nil, ErrFindAccount
401 account := &Account{}
402 if err := json.Unmarshal(rawAccount, account); err != nil {
407 m.cache.Add(id, account)
412 // GetAccountByProgram return Account by given CtrlProgram
413 func (m *Manager) GetAccountByProgram(program *CtrlProgram) (*Account, error) {
414 rawAccount := m.db.Get(Key(program.AccountID))
415 if rawAccount == nil {
416 return nil, ErrFindAccount
419 account := &Account{}
420 return account, json.Unmarshal(rawAccount, account)
423 // GetAccountByXPubsIndex get account by xPubs and index
424 func (m *Manager) GetAccountByXPubsIndex(xPubs []chainkd.XPub, index uint64) (*Account, error) {
425 accounts, err := m.ListAccounts("")
430 for _, account := range accounts {
431 if reflect.DeepEqual(account.XPubs, xPubs) && account.KeyIndex == index {
438 // GetAliasByID return the account alias by given ID
439 func (m *Manager) GetAliasByID(id string) string {
440 rawAccount := m.db.Get(Key(id))
441 if rawAccount == nil {
442 log.Warn("GetAliasByID fail to find account")
446 account := &Account{}
447 if err := json.Unmarshal(rawAccount, account); err != nil {
453 func (m *Manager) GetCoinbaseArbitrary() []byte {
454 if arbitrary := m.db.Get(CoinbaseAbKey); arbitrary != nil {
460 // GetCoinbaseControlProgram will return a coinbase script
461 func (m *Manager) GetCoinbaseControlProgram() ([]byte, error) {
462 cp, err := m.GetCoinbaseCtrlProgram()
463 if err == ErrFindAccount {
464 log.Warningf("GetCoinbaseControlProgram: can't find any account in db")
465 return vmutil.DefaultCoinbaseProgram()
470 return cp.ControlProgram, nil
473 // GetCoinbaseCtrlProgram will return the coinbase CtrlProgram
474 func (m *Manager) GetCoinbaseCtrlProgram() (*CtrlProgram, error) {
475 if data := m.db.Get(miningAddressKey); data != nil {
477 return cp, json.Unmarshal(data, cp)
480 accountIter := m.db.IteratorPrefix([]byte(accountPrefix))
481 defer accountIter.Release()
482 if !accountIter.Next() {
483 return nil, ErrFindAccount
486 account := &Account{}
487 if err := json.Unmarshal(accountIter.Value(), account); err != nil {
491 program, err := m.CreateAddress(account.ID, false)
496 rawCP, err := json.Marshal(program)
501 m.db.Set(miningAddressKey, rawCP)
505 // GetContractIndex return the current index
506 func (m *Manager) GetContractIndex(accountID string) uint64 {
508 if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
509 index = common.BytesToUnit64(rawIndexBytes)
514 // GetBip44ContractIndex return the current bip44 contract index
515 func (m *Manager) GetBip44ContractIndex(accountID string, change bool) uint64 {
517 if rawIndexBytes := m.db.Get(bip44ContractIndexKey(accountID, change)); rawIndexBytes != nil {
518 index = common.BytesToUnit64(rawIndexBytes)
523 // GetLocalCtrlProgramByAddress return CtrlProgram by given address
524 func (m *Manager) GetLocalCtrlProgramByAddress(address string) (*CtrlProgram, error) {
525 program, err := m.getProgramByAddress(address)
531 sha3pool.Sum256(hash[:], program)
532 rawProgram := m.db.Get(ContractKey(hash))
533 if rawProgram == nil {
534 return nil, ErrFindCtrlProgram
538 return cp, json.Unmarshal(rawProgram, cp)
541 // GetMiningAddress will return the mining address
542 func (m *Manager) GetMiningAddress() (string, error) {
543 cp, err := m.GetCoinbaseCtrlProgram()
547 return cp.Address, nil
550 // IsLocalControlProgram check is the input control program belong to local
551 func (m *Manager) IsLocalControlProgram(prog []byte) bool {
553 sha3pool.Sum256(hash[:], prog)
554 bytes := m.db.Get(ContractKey(hash))
558 // ListAccounts will return the accounts in the db
559 func (m *Manager) ListAccounts(id string) ([]*Account, error) {
560 accounts := []*Account{}
561 accountIter := m.db.IteratorPrefix(Key(strings.TrimSpace(id)))
562 defer accountIter.Release()
564 for accountIter.Next() {
565 account := &Account{}
566 if err := json.Unmarshal(accountIter.Value(), &account); err != nil {
569 accounts = append(accounts, account)
574 // ListControlProgram return all the local control program
575 func (m *Manager) ListControlProgram() ([]*CtrlProgram, error) {
576 cps := []*CtrlProgram{}
577 cpIter := m.db.IteratorPrefix(contractPrefix)
578 defer cpIter.Release()
582 if err := json.Unmarshal(cpIter.Value(), cp); err != nil {
585 cps = append(cps, cp)
590 func (m *Manager) ListUnconfirmedUtxo(accountID string, isSmartContract bool) []*UTXO {
591 utxos := m.utxoKeeper.ListUnconfirmed()
593 for _, utxo := range utxos {
594 if segwit.IsP2WScript(utxo.ControlProgram) != isSmartContract && (accountID == utxo.AccountID || accountID == "") {
595 result = append(result, utxo)
601 // RemoveUnconfirmedUtxo remove utxos from the utxoKeeper
602 func (m *Manager) RemoveUnconfirmedUtxo(hashes []*bc.Hash) {
603 m.utxoKeeper.RemoveUnconfirmedUtxo(hashes)
606 // SetMiningAddress will set the mining address
607 func (m *Manager) SetMiningAddress(miningAddress string) (string, error) {
608 program, err := m.getProgramByAddress(miningAddress)
614 Address: miningAddress,
615 ControlProgram: program,
617 rawCP, err := json.Marshal(cp)
622 m.db.Set(miningAddressKey, rawCP)
623 return m.GetMiningAddress()
626 func (m *Manager) SetCoinbaseArbitrary(arbitrary []byte) {
627 m.db.Set(CoinbaseAbKey, arbitrary)
630 // CreateCtrlProgram generate an address for the select account
631 func CreateCtrlProgram(account *Account, addrIdx uint64, change bool) (cp *CtrlProgram, err error) {
632 path, err := signers.Path(account.Signer, signers.AccountKeySpace, change, addrIdx)
637 if len(account.XPubs) == 1 {
638 cp, err = createP2PKH(account, path)
640 cp, err = createP2SH(account, path)
645 cp.KeyIndex, cp.Change = addrIdx, change
649 func createP2PKH(account *Account, path [][]byte) (*CtrlProgram, error) {
650 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
651 derivedPK := derivedXPubs[0].PublicKey()
652 pubHash := crypto.Ripemd160(derivedPK)
654 address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
659 control, err := vmutil.P2WPKHProgram([]byte(pubHash))
665 AccountID: account.ID,
666 Address: address.EncodeAddress(),
667 ControlProgram: control,
671 func createP2SH(account *Account, path [][]byte) (*CtrlProgram, error) {
672 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
673 derivedPKs := chainkd.XPubKeys(derivedXPubs)
674 signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
678 scriptHash := crypto.Sha256(signScript)
680 address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
685 control, err := vmutil.P2WSHProgram(scriptHash)
691 AccountID: account.ID,
692 Address: address.EncodeAddress(),
693 ControlProgram: control,
697 func GetAccountIndexKey(xpubs []chainkd.XPub) []byte {
700 cpy := append([]chainkd.XPub{}, xpubs[:]...)
701 sort.Sort(signers.SortKeys(cpy))
702 for _, xpub := range cpy {
703 xPubs = append(xPubs, xpub[:]...)
705 sha3pool.Sum256(hash[:], xPubs)
706 return append(accountIndexPrefix, hash[:]...)
709 func (m *Manager) getCurrentContractIndex(account *Account, change bool) (uint64, error) {
710 switch account.DeriveRule {
711 case signers.BIP0032:
712 return m.GetContractIndex(account.ID), nil
713 case signers.BIP0044:
714 return m.GetBip44ContractIndex(account.ID, change), nil
716 return 0, ErrDeriveRule
719 func (m *Manager) getProgramByAddress(address string) ([]byte, error) {
720 addr, err := common.DecodeAddress(address, &consensus.ActiveNetParams)
724 redeemContract := addr.ScriptAddress()
727 case *common.AddressWitnessPubKeyHash:
728 program, err = vmutil.P2WPKHProgram(redeemContract)
729 case *common.AddressWitnessScriptHash:
730 program, err = vmutil.P2WSHProgram(redeemContract)
732 return nil, ErrInvalidAddress
740 func (m *Manager) saveControlProgram(prog *CtrlProgram, updateIndex bool) error {
743 sha3pool.Sum256(hash[:], prog.ControlProgram)
744 acct, err := m.GetAccountByProgram(prog)
749 accountCP, err := json.Marshal(prog)
754 storeBatch := m.db.NewBatch()
755 storeBatch.Set(ContractKey(hash), accountCP)
757 switch acct.DeriveRule {
758 case signers.BIP0032:
759 storeBatch.Set(contractIndexKey(acct.ID), common.Unit64ToBytes(prog.KeyIndex))
760 case signers.BIP0044:
761 storeBatch.Set(bip44ContractIndexKey(acct.ID, prog.Change), common.Unit64ToBytes(prog.KeyIndex))
769 // SaveControlPrograms save account control programs
770 func (m *Manager) SaveControlPrograms(progs ...*CtrlProgram) error {
772 defer m.addressMu.Unlock()
774 for _, prog := range progs {
775 acct, err := m.GetAccountByProgram(prog)
780 currentIndex, err := m.getCurrentContractIndex(acct, prog.Change)
785 m.saveControlProgram(prog, prog.KeyIndex > currentIndex)