1 // Package account stores and tracks accounts within a Bytom Core.
10 "github.com/golang/groupcache/lru"
11 log "github.com/sirupsen/logrus"
13 "github.com/vapor/blockchain/signers"
14 "github.com/vapor/blockchain/txbuilder"
15 "github.com/vapor/common"
16 "github.com/vapor/consensus"
17 "github.com/vapor/consensus/segwit"
18 "github.com/vapor/crypto"
19 "github.com/vapor/crypto/ed25519/chainkd"
20 "github.com/vapor/crypto/sha3pool"
21 "github.com/vapor/errors"
22 "github.com/vapor/protocol"
23 "github.com/vapor/protocol/bc"
24 "github.com/vapor/protocol/vm/vmutil"
28 maxAccountCache = 1000
30 // HardenedKeyStart bip32 hierarchical deterministic wallets
31 // keys with index ≥ 0x80000000 are hardened keys
32 HardenedKeyStart = 0x80000000
36 // pre-define errors for supporting bytom errorFormatter
38 ErrDuplicateAlias = errors.New("Duplicate account alias")
39 ErrDuplicateIndex = errors.New("Duplicate account with same xPubs and index")
40 ErrFindAccount = errors.New("Failed to find account")
41 ErrMarshalAccount = errors.New("Failed to marshal account")
42 ErrInvalidAddress = errors.New("Invalid address")
43 ErrFindCtrlProgram = errors.New("Failed to find account control program")
44 ErrDeriveRule = errors.New("Invalid key derivation rule")
45 ErrContractIndex = errors.New("Exceeded maximum addresses per account")
46 ErrAccountIndex = errors.New("Exceeded maximum accounts per xpub")
47 ErrFindTransaction = errors.New("No transaction")
50 // Account is structure of Bytom account
54 Alias string `json:"alias"`
57 //CtrlProgram is structure of account control program
58 type CtrlProgram struct {
63 Change bool // Mark whether this control program is for UTXO change
66 // Manager stores accounts and their associated control programs.
70 utxoKeeper *utxoKeeper
76 delayedACPsMu sync.Mutex
77 delayedACPs map[*txbuilder.TemplateBuilder][]*CtrlProgram
83 // NewManager creates a new account manager
84 func NewManager(store AccountStorer, chain *protocol.Chain) *Manager {
88 utxoKeeper: newUtxoKeeper(chain.BestBlockHeight, store),
89 cache: lru.New(maxAccountCache),
90 aliasCache: lru.New(maxAccountCache),
91 delayedACPs: make(map[*txbuilder.TemplateBuilder][]*CtrlProgram),
95 // AddUnconfirmedUtxo add untxo list to utxoKeeper
96 func (m *Manager) AddUnconfirmedUtxo(utxos []*UTXO) {
97 m.utxoKeeper.AddUnconfirmedUtxo(utxos)
100 // CreateAccount creates a new Account.
101 func CreateAccount(xpubs []chainkd.XPub, quorum int, alias string, acctIndex uint64, deriveRule uint8) (*Account, error) {
102 if acctIndex >= HardenedKeyStart {
103 return nil, ErrAccountIndex
106 signer, err := signers.Create("account", xpubs, quorum, acctIndex, deriveRule)
108 return nil, errors.Wrap(err)
111 id := signers.IDGenerate()
112 return &Account{Signer: signer, ID: id, Alias: strings.ToLower(strings.TrimSpace(alias))}, nil
115 func (m *Manager) saveAccount(account *Account, updateIndex bool) error {
116 rawAccount, err := json.Marshal(account)
118 return ErrMarshalAccount
122 defer m.store.CommitBatch()
124 m.store.SetAccount(account.ID, account.Alias, rawAccount)
126 m.store.SetAccountIndex(account.XPubs, account.KeyIndex)
132 // SaveAccount save a new account.
133 func (m *Manager) SaveAccount(account *Account) error {
135 defer m.accountMu.Unlock()
137 if existed := m.store.GetAccountByAccountAlias(account.Alias); existed != nil {
138 return ErrDuplicateAlias
141 acct, err := m.GetAccountByXPubsIndex(account.XPubs, account.KeyIndex)
147 return ErrDuplicateIndex
150 currentIndex := uint64(0)
151 if rawIndexBytes := m.store.GetAccountIndex(account.XPubs); rawIndexBytes != nil {
152 currentIndex = common.BytesToUnit64(rawIndexBytes)
154 return m.saveAccount(account, account.KeyIndex > currentIndex)
157 // Create creates and save a new Account.
158 func (m *Manager) Create(xpubs []chainkd.XPub, quorum int, alias string, deriveRule uint8) (*Account, error) {
160 defer m.accountMu.Unlock()
162 if existed := m.store.GetAccountByAccountAlias(alias); existed != nil {
163 return nil, ErrDuplicateAlias
166 acctIndex := uint64(1)
167 if rawIndexBytes := m.store.GetAccountIndex(xpubs); rawIndexBytes != nil {
168 acctIndex = common.BytesToUnit64(rawIndexBytes) + 1
170 account, err := CreateAccount(xpubs, quorum, alias, acctIndex, deriveRule)
175 if err := m.saveAccount(account, true); err != nil {
182 func (m *Manager) UpdateAccountAlias(accountID string, newAlias string) (err error) {
184 defer m.accountMu.Unlock()
186 account, err := m.FindByID(accountID)
190 oldAlias := account.Alias
192 normalizedAlias := strings.ToLower(strings.TrimSpace(newAlias))
193 if existed := m.store.GetAccountByAccountAlias(normalizedAlias); existed != nil {
194 return ErrDuplicateAlias
198 m.aliasCache.Remove(oldAlias)
201 account.Alias = normalizedAlias
202 rawAccount, err := json.Marshal(account)
204 return ErrMarshalAccount
208 defer m.store.CommitBatch()
210 m.store.DeleteAccountByAccountAlias(oldAlias)
211 m.store.SetAccount(accountID, normalizedAlias, rawAccount)
216 // CreateAddress generate an address for the select account
217 func (m *Manager) CreateAddress(accountID string, change bool) (cp *CtrlProgram, err error) {
219 defer m.addressMu.Unlock()
221 account, err := m.FindByID(accountID)
226 currentIdx, err := m.getCurrentContractIndex(account, change)
231 cp, err = CreateCtrlProgram(account, currentIdx+1, change)
236 return cp, m.saveControlProgram(cp, true)
239 // CreateBatchAddresses generate a batch of addresses for the select account
240 func (m *Manager) CreateBatchAddresses(accountID string, change bool, stopIndex uint64) error {
242 defer m.addressMu.Unlock()
244 account, err := m.FindByID(accountID)
249 currentIndex, err := m.getCurrentContractIndex(account, change)
254 for currentIndex++; currentIndex <= stopIndex; currentIndex++ {
255 cp, err := CreateCtrlProgram(account, currentIndex, change)
260 if err := m.saveControlProgram(cp, true); err != nil {
268 // deleteAccountControlPrograms deletes control program matching accountID
269 func (m *Manager) deleteAccountControlPrograms(accountID string) error {
270 cps, err := m.ListControlProgram()
276 for _, cp := range cps {
277 if cp.AccountID == accountID {
278 sha3pool.Sum256(hash[:], cp.ControlProgram)
279 m.store.DeleteRawProgram(hash)
283 m.store.DeleteBip44ContractIndex(accountID)
284 m.store.DeleteContractIndex(accountID)
289 // deleteAccountUtxos deletes utxos matching accountID
290 func (m *Manager) deleteAccountUtxos(accountID string) error {
291 rawUTXOs := m.store.GetAccountUTXOs(accountID)
293 for _, rawUTXO := range rawUTXOs {
295 if err := json.Unmarshal(rawUTXO, utxo); err != nil {
298 if accountID == utxo.AccountID {
299 m.store.DeleteStandardUTXO(utxo.OutputID)
306 // DeleteAccount deletes the account's ID or alias matching account ID.
307 func (m *Manager) DeleteAccount(accountID string) (err error) {
309 defer m.accountMu.Unlock()
311 account, err := m.FindByID(accountID)
316 if err := m.deleteAccountControlPrograms(accountID); err != nil {
319 if err := m.deleteAccountUtxos(accountID); err != nil {
324 m.aliasCache.Remove(account.Alias)
328 defer m.store.CommitBatch()
330 m.store.DeleteAccountByAccountAlias(account.Alias)
331 m.store.DeleteAccountByAccountID(account.ID)
335 // FindByAlias retrieves an account's Signer record by its alias
336 func (m *Manager) FindByAlias(alias string) (*Account, error) {
338 cachedID, ok := m.aliasCache.Get(alias)
341 return m.FindByID(cachedID.(string))
344 rawID := m.store.GetAccountByAccountAlias(alias)
346 return nil, ErrFindAccount
349 accountID := string(rawID)
351 m.aliasCache.Add(alias, accountID)
353 return m.FindByID(accountID)
356 // FindByID returns an account's Signer record by its ID.
357 func (m *Manager) FindByID(id string) (*Account, error) {
359 cachedAccount, ok := m.cache.Get(id)
362 return cachedAccount.(*Account), nil
365 rawAccount := m.store.GetAccountByAccountID(id)
366 if rawAccount == nil {
367 return nil, ErrFindAccount
370 account := &Account{}
371 if err := json.Unmarshal(rawAccount, account); err != nil {
376 m.cache.Add(id, account)
381 // GetAccountByProgram return Account by given CtrlProgram
382 func (m *Manager) GetAccountByProgram(program *CtrlProgram) (*Account, error) {
383 rawAccount := m.store.GetAccountByAccountID(program.AccountID)
384 if rawAccount == nil {
385 return nil, ErrFindAccount
388 account := &Account{}
389 return account, json.Unmarshal(rawAccount, account)
392 // GetAccountByXPubsIndex get account by xPubs and index
393 func (m *Manager) GetAccountByXPubsIndex(xPubs []chainkd.XPub, index uint64) (*Account, error) {
394 accounts, err := m.ListAccounts("")
399 for _, account := range accounts {
400 if reflect.DeepEqual(account.XPubs, xPubs) && account.KeyIndex == index {
407 // GetAliasByID return the account alias by given ID
408 func (m *Manager) GetAliasByID(id string) string {
409 rawAccount := m.store.GetAccountByAccountID(id)
410 if rawAccount == nil {
411 log.Warn("GetAliasByID fail to find account")
415 account := &Account{}
416 if err := json.Unmarshal(rawAccount, account); err != nil {
422 func (m *Manager) GetCoinbaseArbitrary() []byte {
423 if arbitrary := m.store.GetCoinbaseArbitrary(); arbitrary != nil {
429 // GetCoinbaseControlProgram will return a coinbase script
430 func (m *Manager) GetCoinbaseControlProgram() ([]byte, error) {
431 cp, err := m.GetCoinbaseCtrlProgram()
432 if err == ErrFindAccount {
433 log.Warningf("GetCoinbaseControlProgram: can't find any account in db")
434 return vmutil.DefaultCoinbaseProgram()
439 return cp.ControlProgram, nil
442 // GetCoinbaseCtrlProgram will return the coinbase CtrlProgram
443 func (m *Manager) GetCoinbaseCtrlProgram() (*CtrlProgram, error) {
444 if data := m.store.GetMiningAddress(); data != nil {
446 return cp, json.Unmarshal(data, cp)
449 firstAccount := make([]byte, 0)
450 accounts := m.store.GetAccounts("")
451 if len(accounts) > 0 {
452 firstAccount = accounts[0]
454 return nil, ErrFindAccount
457 account := &Account{}
458 if err := json.Unmarshal(firstAccount, account); err != nil {
462 program, err := m.CreateAddress(account.ID, false)
467 rawCP, err := json.Marshal(program)
472 m.store.SetMiningAddress(rawCP)
477 // GetContractIndex return the current index
478 func (m *Manager) GetContractIndex(accountID string) uint64 {
480 if rawIndexBytes := m.store.GetContractIndex(accountID); rawIndexBytes != nil {
481 index = common.BytesToUnit64(rawIndexBytes)
486 // GetBip44ContractIndex return the current bip44 contract index
487 func (m *Manager) GetBip44ContractIndex(accountID string, change bool) uint64 {
489 if rawIndexBytes := m.store.GetBip44ContractIndex(accountID, change); rawIndexBytes != nil {
490 index = common.BytesToUnit64(rawIndexBytes)
495 // GetLocalCtrlProgramByAddress return CtrlProgram by given address
496 func (m *Manager) GetLocalCtrlProgramByAddress(address string) (*CtrlProgram, error) {
497 program, err := m.getProgramByAddress(address)
503 sha3pool.Sum256(hash[:], program)
504 rawProgram := m.store.GetRawProgram(hash)
505 if rawProgram == nil {
506 return nil, ErrFindCtrlProgram
510 return cp, json.Unmarshal(rawProgram, cp)
513 // GetMiningAddress will return the mining address
514 func (m *Manager) GetMiningAddress() (string, error) {
515 cp, err := m.GetCoinbaseCtrlProgram()
519 return cp.Address, nil
522 // IsLocalControlProgram check is the input control program belong to local
523 func (m *Manager) IsLocalControlProgram(prog []byte) bool {
525 sha3pool.Sum256(hash[:], prog)
526 bytes := m.store.GetRawProgram(hash)
530 // ListAccounts will return the accounts in the db
531 func (m *Manager) ListAccounts(id string) ([]*Account, error) {
532 rawAccounts := m.store.GetAccounts(id)
534 accounts := []*Account{}
535 for _, rawAccount := range rawAccounts {
536 account := new(Account)
537 if err := json.Unmarshal(rawAccount, &account); err != nil {
540 accounts = append(accounts, account)
546 // ListControlProgram return all the local control program
547 func (m *Manager) ListControlProgram() ([]*CtrlProgram, error) {
548 rawControlPrograms, err := m.store.GetControlPrograms()
553 controlPrograms := []*CtrlProgram{}
554 for _, rawControlProgram := range rawControlPrograms {
555 controlProgram := new(CtrlProgram)
556 if err := json.Unmarshal(rawControlProgram, controlProgram); err != nil {
559 controlPrograms = append(controlPrograms, controlProgram)
562 return controlPrograms, nil
565 func (m *Manager) ListUnconfirmedUtxo(accountID string, isSmartContract bool) []*UTXO {
566 utxos := m.utxoKeeper.ListUnconfirmed()
568 for _, utxo := range utxos {
569 if segwit.IsP2WScript(utxo.ControlProgram) != isSmartContract && (accountID == utxo.AccountID || accountID == "") {
570 result = append(result, utxo)
576 // RemoveUnconfirmedUtxo remove utxos from the utxoKeeper
577 func (m *Manager) RemoveUnconfirmedUtxo(hashes []*bc.Hash) {
578 m.utxoKeeper.RemoveUnconfirmedUtxo(hashes)
581 // SetMiningAddress will set the mining address
582 func (m *Manager) SetMiningAddress(miningAddress string) (string, error) {
583 program, err := m.getProgramByAddress(miningAddress)
589 Address: miningAddress,
590 ControlProgram: program,
592 rawCP, err := json.Marshal(cp)
597 m.store.SetMiningAddress(rawCP)
598 return m.GetMiningAddress()
601 func (m *Manager) SetCoinbaseArbitrary(arbitrary []byte) {
602 m.store.SetCoinbaseArbitrary(arbitrary)
605 // CreateCtrlProgram generate an address for the select account
606 func CreateCtrlProgram(account *Account, addrIdx uint64, change bool) (cp *CtrlProgram, err error) {
607 path, err := signers.Path(account.Signer, signers.AccountKeySpace, change, addrIdx)
612 if len(account.XPubs) == 1 {
613 cp, err = createP2PKH(account, path)
615 cp, err = createP2SH(account, path)
620 cp.KeyIndex, cp.Change = addrIdx, change
624 func createP2PKH(account *Account, path [][]byte) (*CtrlProgram, error) {
625 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
626 derivedPK := derivedXPubs[0].PublicKey()
627 pubHash := crypto.Ripemd160(derivedPK)
629 address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
634 control, err := vmutil.P2WPKHProgram([]byte(pubHash))
640 AccountID: account.ID,
641 Address: address.EncodeAddress(),
642 ControlProgram: control,
646 func createP2SH(account *Account, path [][]byte) (*CtrlProgram, error) {
647 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
648 derivedPKs := chainkd.XPubKeys(derivedXPubs)
649 signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
653 scriptHash := crypto.Sha256(signScript)
655 address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
660 control, err := vmutil.P2WSHProgram(scriptHash)
666 AccountID: account.ID,
667 Address: address.EncodeAddress(),
668 ControlProgram: control,
672 func (m *Manager) getCurrentContractIndex(account *Account, change bool) (uint64, error) {
673 switch account.DeriveRule {
674 case signers.BIP0032:
675 return m.GetContractIndex(account.ID), nil
676 case signers.BIP0044:
677 return m.GetBip44ContractIndex(account.ID, change), nil
679 return 0, ErrDeriveRule
682 func (m *Manager) getProgramByAddress(address string) ([]byte, error) {
683 addr, err := common.DecodeAddress(address, &consensus.ActiveNetParams)
687 redeemContract := addr.ScriptAddress()
690 case *common.AddressWitnessPubKeyHash:
691 program, err = vmutil.P2WPKHProgram(redeemContract)
692 case *common.AddressWitnessScriptHash:
693 program, err = vmutil.P2WSHProgram(redeemContract)
695 return nil, ErrInvalidAddress
703 func (m *Manager) saveControlProgram(prog *CtrlProgram, updateIndex bool) error {
706 sha3pool.Sum256(hash[:], prog.ControlProgram)
707 acct, err := m.GetAccountByProgram(prog)
712 accountCP, err := json.Marshal(prog)
718 defer m.store.CommitBatch()
720 m.store.SetRawProgram(hash, accountCP)
722 switch acct.DeriveRule {
723 case signers.BIP0032:
724 m.store.SetContractIndex(acct.ID, prog.KeyIndex)
725 case signers.BIP0044:
726 m.store.SetBip44ContractIndex(acct.ID, prog.Change, prog.KeyIndex)
733 // SaveControlPrograms save account control programs
734 func (m *Manager) SaveControlPrograms(progs ...*CtrlProgram) error {
736 defer m.addressMu.Unlock()
738 for _, prog := range progs {
739 acct, err := m.GetAccountByProgram(prog)
744 currentIndex, err := m.getCurrentContractIndex(acct, prog.Change)
749 m.saveControlProgram(prog, prog.KeyIndex > currentIndex)