1 // Package account stores and tracks accounts within a Bytom Core.
12 "github.com/golang/groupcache/lru"
13 log "github.com/sirupsen/logrus"
14 dbm "github.com/tendermint/tmlibs/db"
16 "github.com/vapor/blockchain/signers"
17 "github.com/vapor/blockchain/txbuilder"
18 "github.com/vapor/common"
19 "github.com/vapor/consensus"
20 "github.com/vapor/consensus/segwit"
21 "github.com/vapor/crypto"
22 "github.com/vapor/crypto/ed25519/chainkd"
23 "github.com/vapor/crypto/sha3pool"
24 "github.com/vapor/equity/pegin_contract"
25 "github.com/vapor/errors"
26 "github.com/vapor/protocol"
27 "github.com/vapor/protocol/bc"
28 "github.com/vapor/protocol/vm/vmutil"
32 maxAccountCache = 1000
34 // HardenedKeyStart bip32 hierarchical deterministic wallets
35 // keys with index ≥ 0x80000000 are hardened keys
36 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("fail to find account")
54 ErrMarshalAccount = errors.New("failed marshal account")
55 ErrInvalidAddress = errors.New("invalid address")
56 ErrFindCtrlProgram = errors.New("fail to find account control program")
57 ErrDeriveRule = errors.New("invalid key derive rule")
58 ErrContractIndex = errors.New("exceed the maximum addresses per account")
59 ErrAccountIndex = errors.New("exceed the maximum accounts per xpub")
60 ErrFindTransaction = errors.New("no transaction")
63 // ContractKey account control promgram store prefix
64 func ContractKey(hash common.Hash) []byte {
65 return append(contractPrefix, hash[:]...)
68 // Key account store prefix
69 func Key(name string) []byte {
70 return append(accountPrefix, []byte(name)...)
73 func aliasKey(name string) []byte {
74 return append(aliasPrefix, []byte(name)...)
77 func bip44ContractIndexKey(accountID string, change bool) []byte {
78 key := append(contractIndexPrefix, accountID...)
80 return append(key, []byte{1}...)
82 return append(key, []byte{0}...)
85 func contractIndexKey(accountID string) []byte {
86 return append(contractIndexPrefix, []byte(accountID)...)
89 // Account is structure of Bytom account
93 Alias string `json:"alias"`
96 //CtrlProgram is structure of account control program
97 type CtrlProgram struct {
101 ControlProgram []byte
102 Change bool // Mark whether this control program is for UTXO change
105 // Manager stores accounts and their associated control programs.
106 type Manager struct {
108 chain *protocol.Chain
109 utxoKeeper *utxoKeeper
113 aliasCache *lru.Cache
115 delayedACPsMu sync.Mutex
116 delayedACPs map[*txbuilder.TemplateBuilder][]*CtrlProgram
122 // NewManager creates a new account manager
123 func NewManager(walletDB dbm.DB, chain *protocol.Chain) *Manager {
127 utxoKeeper: newUtxoKeeper(chain.BestBlockHeight, walletDB),
128 cache: lru.New(maxAccountCache),
129 aliasCache: lru.New(maxAccountCache),
130 delayedACPs: make(map[*txbuilder.TemplateBuilder][]*CtrlProgram),
134 // AddUnconfirmedUtxo add untxo list to utxoKeeper
135 func (m *Manager) AddUnconfirmedUtxo(utxos []*UTXO) {
136 m.utxoKeeper.AddUnconfirmedUtxo(utxos)
139 // CreateAccount creates a new Account.
140 func CreateAccount(xpubs []chainkd.XPub, quorum int, alias string, acctIndex uint64, deriveRule uint8) (*Account, error) {
141 if acctIndex >= HardenedKeyStart {
142 return nil, ErrAccountIndex
145 signer, err := signers.Create("account", xpubs, quorum, acctIndex, deriveRule)
147 return nil, errors.Wrap(err)
150 id := signers.IDGenerate()
151 return &Account{Signer: signer, ID: id, Alias: strings.ToLower(strings.TrimSpace(alias))}, nil
154 func (m *Manager) saveAccount(account *Account, updateIndex bool) error {
155 rawAccount, err := json.Marshal(account)
157 return ErrMarshalAccount
160 storeBatch := m.db.NewBatch()
161 storeBatch.Set(Key(account.ID), rawAccount)
162 storeBatch.Set(aliasKey(account.Alias), []byte(account.ID))
164 storeBatch.Set(GetAccountIndexKey(account.XPubs), common.Unit64ToBytes(account.KeyIndex))
170 // SaveAccount save a new account.
171 func (m *Manager) SaveAccount(account *Account) error {
173 defer m.accountMu.Unlock()
175 if existed := m.db.Get(aliasKey(account.Alias)); existed != nil {
176 return ErrDuplicateAlias
179 acct, err := m.GetAccountByXPubsIndex(account.XPubs, account.KeyIndex)
185 return ErrDuplicateIndex
188 currentIndex := uint64(0)
189 if rawIndexBytes := m.db.Get(GetAccountIndexKey(account.XPubs)); rawIndexBytes != nil {
190 currentIndex = common.BytesToUnit64(rawIndexBytes)
192 return m.saveAccount(account, account.KeyIndex > currentIndex)
195 // Create creates and save a new Account.
196 func (m *Manager) Create(xpubs []chainkd.XPub, quorum int, alias string, deriveRule uint8) (*Account, error) {
198 defer m.accountMu.Unlock()
200 if existed := m.db.Get(aliasKey(alias)); existed != nil {
201 return nil, ErrDuplicateAlias
204 acctIndex := uint64(1)
205 if rawIndexBytes := m.db.Get(GetAccountIndexKey(xpubs)); rawIndexBytes != nil {
206 acctIndex = common.BytesToUnit64(rawIndexBytes) + 1
208 account, err := CreateAccount(xpubs, quorum, alias, acctIndex, deriveRule)
213 if err := m.saveAccount(account, true); err != nil {
220 func (m *Manager) UpdateAccountAlias(accountID string, newAlias string) (err error) {
222 defer m.accountMu.Unlock()
224 account, err := m.FindByID(accountID)
228 oldAlias := account.Alias
230 normalizedAlias := strings.ToLower(strings.TrimSpace(newAlias))
231 if existed := m.db.Get(aliasKey(normalizedAlias)); existed != nil {
232 return ErrDuplicateAlias
236 m.aliasCache.Remove(oldAlias)
239 account.Alias = normalizedAlias
240 rawAccount, err := json.Marshal(account)
242 return ErrMarshalAccount
245 storeBatch := m.db.NewBatch()
246 storeBatch.Delete(aliasKey(oldAlias))
247 storeBatch.Set(Key(accountID), rawAccount)
248 storeBatch.Set(aliasKey(normalizedAlias), []byte(accountID))
253 // CreateAddress generate an address for the select account
254 func (m *Manager) CreateAddress(accountID string, change bool) (cp *CtrlProgram, err error) {
256 defer m.addressMu.Unlock()
258 account, err := m.FindByID(accountID)
263 currentIdx, err := m.getCurrentContractIndex(account, change)
268 cp, err = CreateCtrlProgram(account, currentIdx+1, change)
273 return cp, m.saveControlProgram(cp, true)
276 // CreateBatchAddresses generate a batch of addresses for the select account
277 func (m *Manager) CreateBatchAddresses(accountID string, change bool, stopIndex uint64) error {
279 defer m.addressMu.Unlock()
281 account, err := m.FindByID(accountID)
286 currentIndex, err := m.getCurrentContractIndex(account, change)
291 for currentIndex++; currentIndex <= stopIndex; currentIndex++ {
292 cp, err := CreateCtrlProgram(account, currentIndex, change)
297 if err := m.saveControlProgram(cp, true); err != nil {
305 // deleteAccountControlPrograms deletes control program matching accountID
306 func (m *Manager) deleteAccountControlPrograms(accountID string) error {
307 cps, err := m.ListControlProgram()
313 for _, cp := range cps {
314 if cp.AccountID == accountID {
315 sha3pool.Sum256(hash[:], cp.ControlProgram)
316 m.db.Delete(ContractKey(hash))
322 // deleteAccountUtxos deletes utxos matching accountID
323 func (m *Manager) deleteAccountUtxos(accountID string) error {
324 accountUtxoIter := m.db.IteratorPrefix([]byte(UTXOPreFix))
325 defer accountUtxoIter.Release()
326 for accountUtxoIter.Next() {
327 accountUtxo := &UTXO{}
328 if err := json.Unmarshal(accountUtxoIter.Value(), accountUtxo); err != nil {
332 if accountID == accountUtxo.AccountID {
333 m.db.Delete(StandardUTXOKey(accountUtxo.OutputID))
339 // DeleteAccount deletes the account's ID or alias matching account ID.
340 func (m *Manager) DeleteAccount(accountID string) (err error) {
342 defer m.accountMu.Unlock()
344 account, err := m.FindByID(accountID)
349 if err := m.deleteAccountControlPrograms(accountID); err != nil {
352 if err := m.deleteAccountUtxos(accountID); err != nil {
357 m.aliasCache.Remove(account.Alias)
360 storeBatch := m.db.NewBatch()
361 storeBatch.Delete(aliasKey(account.Alias))
362 storeBatch.Delete(Key(account.ID))
367 // FindByAlias retrieves an account's Signer record by its alias
368 func (m *Manager) FindByAlias(alias string) (*Account, error) {
370 cachedID, ok := m.aliasCache.Get(alias)
373 return m.FindByID(cachedID.(string))
376 rawID := m.db.Get(aliasKey(alias))
378 return nil, ErrFindAccount
381 accountID := string(rawID)
383 m.aliasCache.Add(alias, accountID)
385 return m.FindByID(accountID)
388 // FindByID returns an account's Signer record by its ID.
389 func (m *Manager) FindByID(id string) (*Account, error) {
391 cachedAccount, ok := m.cache.Get(id)
394 return cachedAccount.(*Account), nil
397 rawAccount := m.db.Get(Key(id))
398 if rawAccount == nil {
399 return nil, ErrFindAccount
402 account := &Account{}
403 if err := json.Unmarshal(rawAccount, account); err != nil {
408 m.cache.Add(id, account)
413 // GetAccountByProgram return Account by given CtrlProgram
414 func (m *Manager) GetAccountByProgram(program *CtrlProgram) (*Account, error) {
415 rawAccount := m.db.Get(Key(program.AccountID))
416 if rawAccount == nil {
417 return nil, ErrFindAccount
420 account := &Account{}
421 return account, json.Unmarshal(rawAccount, account)
424 // GetAccountByXPubsIndex get account by xPubs and index
425 func (m *Manager) GetAccountByXPubsIndex(xPubs []chainkd.XPub, index uint64) (*Account, error) {
426 accounts, err := m.ListAccounts("")
431 for _, account := range accounts {
432 if reflect.DeepEqual(account.XPubs, xPubs) && account.KeyIndex == index {
439 // GetAliasByID return the account alias by given ID
440 func (m *Manager) GetAliasByID(id string) string {
441 rawAccount := m.db.Get(Key(id))
442 if rawAccount == nil {
443 log.Warn("GetAliasByID fail to find account")
447 account := &Account{}
448 if err := json.Unmarshal(rawAccount, account); err != nil {
454 func (m *Manager) GetCoinbaseArbitrary() []byte {
455 if arbitrary := m.db.Get(CoinbaseAbKey); arbitrary != nil {
461 // GetCoinbaseControlProgram will return a coinbase script
462 func (m *Manager) GetCoinbaseControlProgram() ([]byte, error) {
463 cp, err := m.GetCoinbaseCtrlProgram()
464 if err == ErrFindAccount {
465 log.Warningf("GetCoinbaseControlProgram: can't find any account in db")
466 return vmutil.DefaultCoinbaseProgram()
471 return cp.ControlProgram, nil
474 // GetCoinbaseCtrlProgram will return the coinbase CtrlProgram
475 func (m *Manager) GetCoinbaseCtrlProgram() (*CtrlProgram, error) {
476 if data := m.db.Get(miningAddressKey); data != nil {
478 return cp, json.Unmarshal(data, cp)
481 accountIter := m.db.IteratorPrefix([]byte(accountPrefix))
482 defer accountIter.Release()
483 if !accountIter.Next() {
484 return nil, ErrFindAccount
487 account := &Account{}
488 if err := json.Unmarshal(accountIter.Value(), account); err != nil {
492 program, err := m.CreateAddress(account.ID, false)
497 rawCP, err := json.Marshal(program)
502 m.db.Set(miningAddressKey, rawCP)
506 // GetContractIndex return the current index
507 func (m *Manager) GetContractIndex(accountID string) uint64 {
509 if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
510 index = common.BytesToUnit64(rawIndexBytes)
515 // GetBip44ContractIndex return the current bip44 contract index
516 func (m *Manager) GetBip44ContractIndex(accountID string, change bool) uint64 {
518 if rawIndexBytes := m.db.Get(bip44ContractIndexKey(accountID, change)); rawIndexBytes != nil {
519 index = common.BytesToUnit64(rawIndexBytes)
524 // GetLocalCtrlProgramByAddress return CtrlProgram by given address
525 func (m *Manager) GetLocalCtrlProgramByAddress(address string) (*CtrlProgram, error) {
526 program, err := m.getProgramByAddress(address)
532 sha3pool.Sum256(hash[:], program)
533 rawProgram := m.db.Get(ContractKey(hash))
534 if rawProgram == nil {
535 return nil, ErrFindCtrlProgram
539 return cp, json.Unmarshal(rawProgram, cp)
542 // GetMiningAddress will return the mining address
543 func (m *Manager) GetMiningAddress() (string, error) {
544 cp, err := m.GetCoinbaseCtrlProgram()
548 return cp.Address, nil
551 // IsLocalControlProgram check is the input control program belong to local
552 func (m *Manager) IsLocalControlProgram(prog []byte) bool {
554 sha3pool.Sum256(hash[:], prog)
555 bytes := m.db.Get(ContractKey(hash))
559 // ListAccounts will return the accounts in the db
560 func (m *Manager) ListAccounts(id string) ([]*Account, error) {
561 accounts := []*Account{}
562 accountIter := m.db.IteratorPrefix(Key(strings.TrimSpace(id)))
563 defer accountIter.Release()
565 for accountIter.Next() {
566 account := &Account{}
567 if err := json.Unmarshal(accountIter.Value(), &account); err != nil {
570 accounts = append(accounts, account)
575 // ListControlProgram return all the local control program
576 func (m *Manager) ListControlProgram() ([]*CtrlProgram, error) {
577 cps := []*CtrlProgram{}
578 cpIter := m.db.IteratorPrefix(contractPrefix)
579 defer cpIter.Release()
583 if err := json.Unmarshal(cpIter.Value(), cp); err != nil {
586 cps = append(cps, cp)
591 func (m *Manager) ListUnconfirmedUtxo(accountID string, isSmartContract bool) []*UTXO {
592 utxos := m.utxoKeeper.ListUnconfirmed()
594 for _, utxo := range utxos {
595 if segwit.IsP2WScript(utxo.ControlProgram) != isSmartContract && (accountID == utxo.AccountID || accountID == "") {
596 result = append(result, utxo)
602 // RemoveUnconfirmedUtxo remove utxos from the utxoKeeper
603 func (m *Manager) RemoveUnconfirmedUtxo(hashes []*bc.Hash) {
604 m.utxoKeeper.RemoveUnconfirmedUtxo(hashes)
607 // SetMiningAddress will set the mining address
608 func (m *Manager) SetMiningAddress(miningAddress string) (string, error) {
609 program, err := m.getProgramByAddress(miningAddress)
615 Address: miningAddress,
616 ControlProgram: program,
618 rawCP, err := json.Marshal(cp)
623 m.db.Set(miningAddressKey, rawCP)
624 return m.GetMiningAddress()
627 func (m *Manager) SetCoinbaseArbitrary(arbitrary []byte) {
628 m.db.Set(CoinbaseAbKey, arbitrary)
631 // CreateCtrlProgram generate an address for the select account
632 func CreateCtrlProgram(account *Account, addrIdx uint64, change bool) (cp *CtrlProgram, err error) {
633 path, err := signers.Path(account.Signer, signers.AccountKeySpace, change, addrIdx)
638 if len(account.XPubs) == 1 {
639 cp, err = createP2PKH(account, path)
641 cp, err = createP2SH(account, path)
646 cp.KeyIndex, cp.Change = addrIdx, change
650 func createP2PKH(account *Account, path [][]byte) (*CtrlProgram, error) {
651 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
652 derivedPK := derivedXPubs[0].PublicKey()
653 pubHash := crypto.Ripemd160(derivedPK)
655 address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
660 control, err := vmutil.P2WPKHProgram([]byte(pubHash))
666 AccountID: account.ID,
667 Address: address.EncodeAddress(),
668 ControlProgram: control,
672 func createP2SH(account *Account, path [][]byte) (*CtrlProgram, error) {
673 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
674 derivedPKs := chainkd.XPubKeys(derivedXPubs)
675 signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
679 scriptHash := crypto.Sha256(signScript)
681 address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
686 control, err := vmutil.P2WSHProgram(scriptHash)
692 AccountID: account.ID,
693 Address: address.EncodeAddress(),
694 ControlProgram: control,
698 func GetAccountIndexKey(xpubs []chainkd.XPub) []byte {
701 cpy := append([]chainkd.XPub{}, xpubs[:]...)
702 sort.Sort(signers.SortKeys(cpy))
703 for _, xpub := range cpy {
704 xPubs = append(xPubs, xpub[:]...)
706 sha3pool.Sum256(hash[:], xPubs)
707 return append(accountIndexPrefix, hash[:]...)
710 func (m *Manager) getCurrentContractIndex(account *Account, change bool) (uint64, error) {
711 switch account.DeriveRule {
712 case signers.BIP0032:
713 return m.GetContractIndex(account.ID), nil
714 case signers.BIP0044:
715 return m.GetBip44ContractIndex(account.ID, change), nil
717 return 0, ErrDeriveRule
720 func (m *Manager) getProgramByAddress(address string) ([]byte, error) {
721 addr, err := common.DecodeAddress(address, &consensus.ActiveNetParams)
725 redeemContract := addr.ScriptAddress()
728 case *common.AddressWitnessPubKeyHash:
729 program, err = vmutil.P2WPKHProgram(redeemContract)
730 case *common.AddressWitnessScriptHash:
731 program, err = vmutil.P2WSHProgram(redeemContract)
733 return nil, ErrInvalidAddress
741 func (m *Manager) saveControlProgram(prog *CtrlProgram, updateIndex bool) error {
744 sha3pool.Sum256(hash[:], prog.ControlProgram)
745 acct, err := m.GetAccountByProgram(prog)
750 accountCP, err := json.Marshal(prog)
755 storeBatch := m.db.NewBatch()
756 storeBatch.Set(ContractKey(hash), accountCP)
758 switch acct.DeriveRule {
759 case signers.BIP0032:
760 storeBatch.Set(contractIndexKey(acct.ID), common.Unit64ToBytes(prog.KeyIndex))
761 case signers.BIP0044:
762 storeBatch.Set(bip44ContractIndexKey(acct.ID, prog.Change), common.Unit64ToBytes(prog.KeyIndex))
770 // SaveControlPrograms save account control programs
771 func (m *Manager) SaveControlPrograms(progs ...*CtrlProgram) error {
773 defer m.addressMu.Unlock()
775 for _, prog := range progs {
776 acct, err := m.GetAccountByProgram(prog)
781 currentIndex, err := m.getCurrentContractIndex(acct, prog.Change)
786 m.saveControlProgram(prog, prog.KeyIndex > currentIndex)
791 func (m *Manager) CreatePeginAddress(accountID string, change bool) (string, []byte, error) {
793 claimCtrlProg, err := m.CreateAddress(accountID, change)
797 claimScript := claimCtrlProg.ControlProgram
799 federationRedeemScript := vmutil.CalculateContract(consensus.ActiveNetParams.FedpegXPubs, claimScript)
801 scriptHash := crypto.Sha256(federationRedeemScript)
803 address, err := common.NewPeginAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
808 return address.EncodeAddress(), claimScript, nil
812 func (m *Manager) GetPeginControlPrograms(claimScript []byte) (string, []byte) {
813 federationRedeemScript := vmutil.CalculateContract(consensus.ActiveNetParams.FedpegXPubs, claimScript)
814 scriptHash := crypto.Sha256(federationRedeemScript)
816 address, err := common.NewPeginAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
821 redeemContract := address.ScriptAddress()
824 program, err = vmutil.P2WSHProgram(redeemContract)
829 return address.EncodeAddress(), program
832 func (m *Manager) CreatePeginContractPrograms(accountID string, change bool) (string, []byte, error) {
834 claimCtrlProg, err := m.CreateAddress(accountID, change)
838 claimScript := claimCtrlProg.ControlProgram
840 peginContractPrograms, err := pegin_contract.GetPeginContractPrograms(claimScript)
844 return hex.EncodeToString(peginContractPrograms), claimScript, nil
848 func (m *Manager) CreatePeginContractAddress(accountID string, change bool) (string, []byte, []byte, error) {
850 claimCtrlProg, err := m.CreateAddress(accountID, change)
852 return "", nil, nil, err
854 claimScript := claimCtrlProg.ControlProgram
856 peginContractPrograms, err := pegin_contract.GetPeginContractPrograms(claimScript)
858 return "", nil, nil, err
861 scriptHash := crypto.Sha256(peginContractPrograms)
863 address, err := common.NewPeginAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
865 return "", nil, nil, err
868 redeemContract := address.ScriptAddress()
871 program, err = vmutil.P2WSHProgram(redeemContract)
873 return "", nil, nil, err
876 return address.EncodeAddress(), program, claimScript, nil
880 func (m *Manager) GetPeginContractControlPrograms(claimScript []byte) (string, []byte) {
882 peginContractPrograms, err := pegin_contract.GetPeginContractPrograms(claimScript)
886 scriptHash := crypto.Sha256(peginContractPrograms)
888 address, err := common.NewPeginAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
893 redeemContract := address.ScriptAddress()
896 program, err = vmutil.P2WSHProgram(redeemContract)
901 return address.EncodeAddress(), program