1 // Package account stores and tracks accounts within a Bytom Core.
11 "github.com/golang/groupcache/lru"
12 "github.com/google/uuid"
13 log "github.com/sirupsen/logrus"
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 dbm "github.com/vapor/database/leveldb"
24 "github.com/vapor/errors"
25 "github.com/vapor/protocol"
26 "github.com/vapor/protocol/bc"
27 "github.com/vapor/protocol/vm/vmutil"
31 maxAccountCache = 1000
33 // HardenedKeyStart bip32 hierarchical deterministic wallets
34 // keys with index ≥ 0x80000000 are hardened keys
35 HardenedKeyStart = 0x80000000
40 accountIndexPrefix = []byte("AccountIndex:")
41 accountPrefix = []byte("Account:")
42 aliasPrefix = []byte("AccountAlias:")
43 contractIndexPrefix = []byte("ContractIndex")
44 contractPrefix = []byte("Contract:")
45 miningAddressKey = []byte("MiningAddress")
46 CoinbaseAbKey = []byte("CoinbaseArbitrary")
49 // pre-define errors for supporting bytom errorFormatter
51 ErrDuplicateAlias = errors.New("Duplicate account alias")
52 ErrDuplicateIndex = errors.New("Duplicate account with same xPubs and index")
53 ErrFindAccount = errors.New("Failed to find account")
54 ErrMarshalAccount = errors.New("Failed to marshal account")
55 ErrInvalidAddress = errors.New("Invalid address")
56 ErrFindCtrlProgram = errors.New("Failed to find account control program")
57 ErrDeriveRule = errors.New("Invalid key derivation rule")
58 ErrContractIndex = errors.New("Exceeded maximum addresses per account")
59 ErrAccountIndex = errors.New("Exceeded maximum accounts per xpub")
60 ErrFindTransaction = errors.New("No transaction")
61 ErrAccountIDEmpty = errors.New("account_id is empty")
64 // ContractKey account control promgram store prefix
65 func ContractKey(hash common.Hash) []byte {
66 return append(contractPrefix, hash[:]...)
69 // Key account store prefix
70 func Key(name string) []byte {
71 return append(accountPrefix, []byte(name)...)
74 func aliasKey(name string) []byte {
75 return append(aliasPrefix, []byte(name)...)
78 func bip44ContractIndexKey(accountID string, change bool) []byte {
79 key := append(contractIndexPrefix, accountID...)
81 return append(key, []byte{1}...)
83 return append(key, []byte{0}...)
86 func contractIndexKey(accountID string) []byte {
87 return append(contractIndexPrefix, []byte(accountID)...)
90 // Account is structure of Bytom account
94 Alias string `json:"alias"`
97 //CtrlProgram is structure of account control program
98 type CtrlProgram struct {
102 ControlProgram []byte
103 Change bool // Mark whether this control program is for UTXO change
106 // Manager stores accounts and their associated control programs.
107 type Manager struct {
109 chain *protocol.Chain
110 utxoKeeper *utxoKeeper
114 aliasCache *lru.Cache
116 delayedACPsMu sync.Mutex
117 delayedACPs map[*txbuilder.TemplateBuilder][]*CtrlProgram
123 // NewManager creates a new account manager
124 func NewManager(walletDB dbm.DB, chain *protocol.Chain) *Manager {
128 utxoKeeper: newUtxoKeeper(chain.BestBlockHeight, walletDB),
129 cache: lru.New(maxAccountCache),
130 aliasCache: lru.New(maxAccountCache),
131 delayedACPs: make(map[*txbuilder.TemplateBuilder][]*CtrlProgram),
135 // AddUnconfirmedUtxo add untxo list to utxoKeeper
136 func (m *Manager) AddUnconfirmedUtxo(utxos []*UTXO) {
137 m.utxoKeeper.AddUnconfirmedUtxo(utxos)
140 // CreateAccount creates a new Account.
141 func CreateAccount(xpubs []chainkd.XPub, quorum int, alias string, acctIndex uint64, deriveRule uint8) (*Account, error) {
142 if acctIndex >= HardenedKeyStart {
143 return nil, ErrAccountIndex
146 signer, err := signers.Create("account", xpubs, quorum, acctIndex, deriveRule)
148 return nil, errors.Wrap(err)
151 id := uuid.New().String()
152 return &Account{Signer: signer, ID: id, Alias: strings.ToLower(strings.TrimSpace(alias))}, nil
155 func (m *Manager) saveAccount(account *Account, updateIndex bool) error {
156 rawAccount, err := json.Marshal(account)
158 return ErrMarshalAccount
161 storeBatch := m.db.NewBatch()
162 storeBatch.Set(Key(account.ID), rawAccount)
163 storeBatch.Set(aliasKey(account.Alias), []byte(account.ID))
165 storeBatch.Set(GetAccountIndexKey(account.XPubs), common.Unit64ToBytes(account.KeyIndex))
171 // SaveAccount save a new account.
172 func (m *Manager) SaveAccount(account *Account) error {
174 defer m.accountMu.Unlock()
176 if existed := m.db.Get(aliasKey(account.Alias)); existed != nil {
177 return ErrDuplicateAlias
180 acct, err := m.GetAccountByXPubsIndex(account.XPubs, account.KeyIndex)
186 return ErrDuplicateIndex
189 currentIndex := uint64(0)
190 if rawIndexBytes := m.db.Get(GetAccountIndexKey(account.XPubs)); rawIndexBytes != nil {
191 currentIndex = common.BytesToUnit64(rawIndexBytes)
193 return m.saveAccount(account, account.KeyIndex > currentIndex)
196 // Create creates and save a new Account.
197 func (m *Manager) Create(xpubs []chainkd.XPub, quorum int, alias string, deriveRule uint8) (*Account, error) {
199 defer m.accountMu.Unlock()
201 if existed := m.db.Get(aliasKey(alias)); existed != nil {
202 return nil, ErrDuplicateAlias
205 acctIndex := uint64(1)
206 if rawIndexBytes := m.db.Get(GetAccountIndexKey(xpubs)); rawIndexBytes != nil {
207 acctIndex = common.BytesToUnit64(rawIndexBytes) + 1
209 account, err := CreateAccount(xpubs, quorum, alias, acctIndex, deriveRule)
214 if err := m.saveAccount(account, true); err != nil {
221 func (m *Manager) UpdateAccountAlias(accountID string, newAlias string) (err error) {
223 defer m.accountMu.Unlock()
225 account, err := m.FindByID(accountID)
229 oldAlias := account.Alias
231 normalizedAlias := strings.ToLower(strings.TrimSpace(newAlias))
232 if existed := m.db.Get(aliasKey(normalizedAlias)); existed != nil {
233 return ErrDuplicateAlias
237 m.aliasCache.Remove(oldAlias)
240 account.Alias = normalizedAlias
241 rawAccount, err := json.Marshal(account)
243 return ErrMarshalAccount
246 storeBatch := m.db.NewBatch()
247 storeBatch.Delete(aliasKey(oldAlias))
248 storeBatch.Set(Key(accountID), rawAccount)
249 storeBatch.Set(aliasKey(normalizedAlias), []byte(accountID))
254 // CreateAddress generate an address for the select account
255 func (m *Manager) CreateAddress(accountID string, change bool) (cp *CtrlProgram, err error) {
257 defer m.addressMu.Unlock()
259 account, err := m.FindByID(accountID)
264 currentIdx, err := m.getCurrentContractIndex(account, change)
269 cp, err = CreateCtrlProgram(account, currentIdx+1, change)
274 return cp, m.saveControlProgram(cp, true)
277 // CreateBatchAddresses generate a batch of addresses for the select account
278 func (m *Manager) CreateBatchAddresses(accountID string, change bool, stopIndex uint64) error {
280 defer m.addressMu.Unlock()
282 account, err := m.FindByID(accountID)
287 currentIndex, err := m.getCurrentContractIndex(account, change)
292 for currentIndex++; currentIndex <= stopIndex; currentIndex++ {
293 cp, err := CreateCtrlProgram(account, currentIndex, change)
298 if err := m.saveControlProgram(cp, true); err != nil {
306 // deleteAccountControlPrograms deletes control program matching accountID
307 func (m *Manager) deleteAccountControlPrograms(accountID string) error {
308 cps, err := m.ListControlProgram()
314 for _, cp := range cps {
315 if cp.AccountID == accountID {
316 sha3pool.Sum256(hash[:], cp.ControlProgram)
317 m.db.Delete(ContractKey(hash))
320 m.db.Delete(bip44ContractIndexKey(accountID, false))
321 m.db.Delete(bip44ContractIndexKey(accountID, true))
322 m.db.Delete(contractIndexKey(accountID))
326 // deleteAccountUtxos deletes utxos matching accountID
327 func (m *Manager) deleteAccountUtxos(accountID string) error {
328 accountUtxoIter := m.db.IteratorPrefix([]byte(UTXOPreFix))
329 defer accountUtxoIter.Release()
330 for accountUtxoIter.Next() {
331 accountUtxo := &UTXO{}
332 if err := json.Unmarshal(accountUtxoIter.Value(), accountUtxo); err != nil {
336 if accountID == accountUtxo.AccountID {
337 m.db.Delete(StandardUTXOKey(accountUtxo.OutputID))
343 // DeleteAccount deletes the account's ID or alias matching account ID.
344 func (m *Manager) DeleteAccount(accountID string) (err error) {
346 defer m.accountMu.Unlock()
348 account, err := m.FindByID(accountID)
353 if err := m.deleteAccountControlPrograms(accountID); err != nil {
356 if err := m.deleteAccountUtxos(accountID); err != nil {
361 m.aliasCache.Remove(account.Alias)
364 storeBatch := m.db.NewBatch()
365 storeBatch.Delete(aliasKey(account.Alias))
366 storeBatch.Delete(Key(account.ID))
371 // FindByAlias retrieves an account's Signer record by its alias
372 func (m *Manager) FindByAlias(alias string) (*Account, error) {
374 cachedID, ok := m.aliasCache.Get(alias)
377 return m.FindByID(cachedID.(string))
380 rawID := m.db.Get(aliasKey(alias))
382 return nil, ErrFindAccount
385 accountID := string(rawID)
387 m.aliasCache.Add(alias, accountID)
389 return m.FindByID(accountID)
392 // FindByID returns an account's Signer record by its ID.
393 func (m *Manager) FindByID(id string) (*Account, error) {
395 cachedAccount, ok := m.cache.Get(id)
398 return cachedAccount.(*Account), nil
401 rawAccount := m.db.Get(Key(id))
402 if rawAccount == nil {
403 return nil, ErrFindAccount
406 account := &Account{}
407 if err := json.Unmarshal(rawAccount, account); err != nil {
412 m.cache.Add(id, account)
417 // GetAccountByProgram return Account by given CtrlProgram
418 func (m *Manager) GetAccountByProgram(program *CtrlProgram) (*Account, error) {
419 rawAccount := m.db.Get(Key(program.AccountID))
420 if rawAccount == nil {
421 return nil, ErrFindAccount
424 account := &Account{}
425 return account, json.Unmarshal(rawAccount, account)
428 // GetAccountByXPubsIndex get account by xPubs and index
429 func (m *Manager) GetAccountByXPubsIndex(xPubs []chainkd.XPub, index uint64) (*Account, error) {
430 accounts, err := m.ListAccounts("")
435 for _, account := range accounts {
436 if reflect.DeepEqual(account.XPubs, xPubs) && account.KeyIndex == index {
443 // GetAliasByID return the account alias by given ID
444 func (m *Manager) GetAliasByID(id string) string {
445 rawAccount := m.db.Get(Key(id))
446 if rawAccount == nil {
447 log.Warn("GetAliasByID fail to find account")
451 account := &Account{}
452 if err := json.Unmarshal(rawAccount, account); err != nil {
458 func (m *Manager) GetCoinbaseArbitrary() []byte {
459 if arbitrary := m.db.Get(CoinbaseAbKey); arbitrary != nil {
465 // GetCoinbaseControlProgram will return a coinbase script
466 func (m *Manager) GetCoinbaseControlProgram() ([]byte, error) {
467 cp, err := m.GetCoinbaseCtrlProgram()
468 if err == ErrFindAccount {
469 log.Warningf("GetCoinbaseControlProgram: can't find any account in db")
470 return vmutil.DefaultCoinbaseProgram()
475 return cp.ControlProgram, nil
478 // GetCoinbaseCtrlProgram will return the coinbase CtrlProgram
479 func (m *Manager) GetCoinbaseCtrlProgram() (*CtrlProgram, error) {
480 if data := m.db.Get(miningAddressKey); data != nil {
482 return cp, json.Unmarshal(data, cp)
485 accountIter := m.db.IteratorPrefix([]byte(accountPrefix))
486 defer accountIter.Release()
487 if !accountIter.Next() {
488 return nil, ErrFindAccount
491 account := &Account{}
492 if err := json.Unmarshal(accountIter.Value(), account); err != nil {
496 program, err := m.CreateAddress(account.ID, false)
501 rawCP, err := json.Marshal(program)
506 m.db.Set(miningAddressKey, rawCP)
510 // GetContractIndex return the current index
511 func (m *Manager) GetContractIndex(accountID string) uint64 {
513 if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
514 index = common.BytesToUnit64(rawIndexBytes)
519 // GetBip44ContractIndex return the current bip44 contract index
520 func (m *Manager) GetBip44ContractIndex(accountID string, change bool) uint64 {
522 if rawIndexBytes := m.db.Get(bip44ContractIndexKey(accountID, change)); rawIndexBytes != nil {
523 index = common.BytesToUnit64(rawIndexBytes)
528 // GetLocalCtrlProgramByAddress return CtrlProgram by given address
529 func (m *Manager) GetLocalCtrlProgramByAddress(address string) (*CtrlProgram, error) {
530 program, err := m.getProgramByAddress(address)
536 sha3pool.Sum256(hash[:], program)
537 rawProgram := m.db.Get(ContractKey(hash))
538 if rawProgram == nil {
539 return nil, ErrFindCtrlProgram
543 return cp, json.Unmarshal(rawProgram, cp)
546 // GetMiningAddress will return the mining address
547 func (m *Manager) GetMiningAddress() (string, error) {
548 cp, err := m.GetCoinbaseCtrlProgram()
552 return cp.Address, nil
555 // IsLocalControlProgram check is the input control program belong to local
556 func (m *Manager) IsLocalControlProgram(prog []byte) bool {
558 sha3pool.Sum256(hash[:], prog)
559 bytes := m.db.Get(ContractKey(hash))
563 // ListAccounts will return the accounts in the db
564 func (m *Manager) ListAccounts(id string) ([]*Account, error) {
565 accounts := []*Account{}
566 accountIter := m.db.IteratorPrefix(Key(strings.TrimSpace(id)))
567 defer accountIter.Release()
569 for accountIter.Next() {
570 account := &Account{}
571 if err := json.Unmarshal(accountIter.Value(), &account); err != nil {
574 accounts = append(accounts, account)
579 // ListControlProgram return all the local control program
580 func (m *Manager) ListControlProgram() ([]*CtrlProgram, error) {
581 cps := []*CtrlProgram{}
582 cpIter := m.db.IteratorPrefix(contractPrefix)
583 defer cpIter.Release()
587 if err := json.Unmarshal(cpIter.Value(), cp); err != nil {
590 cps = append(cps, cp)
595 func (m *Manager) ListUnconfirmedUtxo(accountID string, isSmartContract bool) []*UTXO {
596 utxos := m.utxoKeeper.ListUnconfirmed()
598 for _, utxo := range utxos {
599 if segwit.IsP2WScript(utxo.ControlProgram) != isSmartContract && (accountID == utxo.AccountID || accountID == "") {
600 result = append(result, utxo)
606 // RemoveUnconfirmedUtxo remove utxos from the utxoKeeper
607 func (m *Manager) RemoveUnconfirmedUtxo(hashes []*bc.Hash) {
608 m.utxoKeeper.RemoveUnconfirmedUtxo(hashes)
611 // SetMiningAddress will set the mining address
612 func (m *Manager) SetMiningAddress(miningAddress string) (string, error) {
613 program, err := m.getProgramByAddress(miningAddress)
619 Address: miningAddress,
620 ControlProgram: program,
622 rawCP, err := json.Marshal(cp)
627 m.db.Set(miningAddressKey, rawCP)
628 return m.GetMiningAddress()
631 func (m *Manager) SetCoinbaseArbitrary(arbitrary []byte) {
632 m.db.Set(CoinbaseAbKey, arbitrary)
635 // CreateCtrlProgram generate an address for the select account
636 func CreateCtrlProgram(account *Account, addrIdx uint64, change bool) (cp *CtrlProgram, err error) {
637 path, err := signers.Path(account.Signer, signers.AccountKeySpace, change, addrIdx)
642 if len(account.XPubs) == 1 {
643 cp, err = createP2PKH(account, path)
645 cp, err = createP2SH(account, path)
650 cp.KeyIndex, cp.Change = addrIdx, change
654 func createP2PKH(account *Account, path [][]byte) (*CtrlProgram, error) {
655 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
656 derivedPK := derivedXPubs[0].PublicKey()
657 pubHash := crypto.Ripemd160(derivedPK)
659 address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
664 control, err := vmutil.P2WPKHProgram([]byte(pubHash))
670 AccountID: account.ID,
671 Address: address.EncodeAddress(),
672 ControlProgram: control,
676 func createP2SH(account *Account, path [][]byte) (*CtrlProgram, error) {
677 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
678 derivedPKs := chainkd.XPubKeys(derivedXPubs)
679 signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
683 scriptHash := crypto.Sha256(signScript)
685 address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
690 control, err := vmutil.P2WSHProgram(scriptHash)
696 AccountID: account.ID,
697 Address: address.EncodeAddress(),
698 ControlProgram: control,
702 func GetAccountIndexKey(xpubs []chainkd.XPub) []byte {
705 cpy := append([]chainkd.XPub{}, xpubs[:]...)
706 sort.Sort(signers.SortKeys(cpy))
707 for _, xpub := range cpy {
708 xPubs = append(xPubs, xpub[:]...)
710 sha3pool.Sum256(hash[:], xPubs)
711 return append(accountIndexPrefix, hash[:]...)
714 func (m *Manager) getCurrentContractIndex(account *Account, change bool) (uint64, error) {
715 switch account.DeriveRule {
716 case signers.BIP0032:
717 return m.GetContractIndex(account.ID), nil
718 case signers.BIP0044:
719 return m.GetBip44ContractIndex(account.ID, change), nil
721 return 0, ErrDeriveRule
724 func (m *Manager) getProgramByAddress(address string) ([]byte, error) {
725 addr, err := common.DecodeAddress(address, &consensus.ActiveNetParams)
729 redeemContract := addr.ScriptAddress()
732 case *common.AddressWitnessPubKeyHash:
733 program, err = vmutil.P2WPKHProgram(redeemContract)
734 case *common.AddressWitnessScriptHash:
735 program, err = vmutil.P2WSHProgram(redeemContract)
737 return nil, ErrInvalidAddress
745 func (m *Manager) saveControlProgram(prog *CtrlProgram, updateIndex bool) error {
748 sha3pool.Sum256(hash[:], prog.ControlProgram)
749 acct, err := m.GetAccountByProgram(prog)
754 accountCP, err := json.Marshal(prog)
759 storeBatch := m.db.NewBatch()
760 storeBatch.Set(ContractKey(hash), accountCP)
762 switch acct.DeriveRule {
763 case signers.BIP0032:
764 storeBatch.Set(contractIndexKey(acct.ID), common.Unit64ToBytes(prog.KeyIndex))
765 case signers.BIP0044:
766 storeBatch.Set(bip44ContractIndexKey(acct.ID, prog.Change), common.Unit64ToBytes(prog.KeyIndex))
774 // SaveControlPrograms save account control programs
775 func (m *Manager) SaveControlPrograms(progs ...*CtrlProgram) error {
777 defer m.addressMu.Unlock()
779 for _, prog := range progs {
780 acct, err := m.GetAccountByProgram(prog)
785 currentIndex, err := m.getCurrentContractIndex(acct, prog.Change)
790 m.saveControlProgram(prog, prog.KeyIndex > currentIndex)