1 // Package account stores and tracks accounts within a Bytom Core.
9 "github.com/golang/groupcache/lru"
10 "github.com/google/uuid"
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")
48 ErrFindMiningAddress = errors.New("Failed to find mining address")
49 ErrAccountIDEmpty = errors.New("account_id is empty")
52 // Account is structure of Bytom account
56 Alias string `json:"alias"`
59 //CtrlProgram is structure of account control program
60 type CtrlProgram struct {
65 Change bool // Mark whether this control program is for UTXO change
68 // Manager stores accounts and their associated control programs.
72 utxoKeeper *utxoKeeper
78 delayedACPsMu sync.Mutex
79 delayedACPs map[*txbuilder.TemplateBuilder][]*CtrlProgram
85 // NewManager creates a new account manager
86 func NewManager(store AccountStore, chain *protocol.Chain) *Manager {
90 utxoKeeper: newUtxoKeeper(chain.BestBlockHeight, store),
91 cache: lru.New(maxAccountCache),
92 aliasCache: lru.New(maxAccountCache),
93 delayedACPs: make(map[*txbuilder.TemplateBuilder][]*CtrlProgram),
97 // AddUnconfirmedUtxo add untxo list to utxoKeeper
98 func (m *Manager) AddUnconfirmedUtxo(utxos []*UTXO) {
99 m.utxoKeeper.AddUnconfirmedUtxo(utxos)
102 // CreateAccount creates a new Account.
103 func CreateAccount(xpubs []chainkd.XPub, quorum int, alias string, acctIndex uint64, deriveRule uint8) (*Account, error) {
104 if acctIndex >= HardenedKeyStart {
105 return nil, ErrAccountIndex
108 signer, err := signers.Create("account", xpubs, quorum, acctIndex, deriveRule)
110 return nil, errors.Wrap(err)
113 id := uuid.New().String()
114 return &Account{Signer: signer, ID: id, Alias: strings.ToLower(strings.TrimSpace(alias))}, nil
117 func (m *Manager) saveAccount(account *Account) error {
118 newStore := m.store.InitBatch()
120 // update account index
121 newStore.SetAccountIndex(account)
122 if err := newStore.SetAccount(account); err != nil {
126 if err := newStore.CommitBatch(); err != nil {
133 // SaveAccount save a new account.
134 func (m *Manager) SaveAccount(account *Account) error {
136 defer m.accountMu.Unlock()
138 _, err := m.store.GetAccountByAlias(account.Alias)
140 return ErrDuplicateAlias
143 if err != ErrFindAccount {
147 acct, err := m.GetAccountByXPubsIndex(account.XPubs, account.KeyIndex)
148 if err != nil && err != ErrFindAccount {
150 } else if acct != nil {
151 return ErrDuplicateIndex
154 return m.saveAccount(account)
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 _, err := m.store.GetAccountByAlias(alias)
164 return nil, ErrDuplicateAlias
165 } else if err != ErrFindAccount {
169 acctIndex := uint64(1)
170 if currentIndex := m.store.GetAccountIndex(xpubs); currentIndex != 0 {
171 acctIndex = currentIndex + 1
174 account, err := CreateAccount(xpubs, quorum, alias, acctIndex, deriveRule)
179 if err := m.saveAccount(account); err != nil {
186 func (m *Manager) UpdateAccountAlias(accountID string, newAlias string) error {
188 defer m.accountMu.Unlock()
190 account, err := m.FindByID(accountID)
194 oldAccount := *account
196 normalizedAlias := strings.ToLower(strings.TrimSpace(newAlias))
197 _, err = m.store.GetAccountByAlias(normalizedAlias)
199 return ErrDuplicateAlias
201 if err != ErrFindAccount {
206 m.aliasCache.Remove(oldAccount.Alias)
209 account.Alias = normalizedAlias
211 newStore := m.store.InitBatch()
213 if err := newStore.DeleteAccount(&oldAccount); err != nil {
217 if err := newStore.SetAccount(account); err != nil {
221 if err := newStore.CommitBatch(); err != nil {
228 // CreateAddress generate an address for the select account
229 func (m *Manager) CreateAddress(accountID string, change bool) (cp *CtrlProgram, err error) {
231 defer m.addressMu.Unlock()
233 account, err := m.FindByID(accountID)
238 currentIdx, err := m.getCurrentContractIndex(account, change)
243 cp, err = CreateCtrlProgram(account, currentIdx+1, change)
248 return cp, m.saveControlProgram(cp, true)
251 // CreateBatchAddresses generate a batch of addresses for the select account
252 func (m *Manager) CreateBatchAddresses(accountID string, change bool, stopIndex uint64) error {
254 defer m.addressMu.Unlock()
256 account, err := m.FindByID(accountID)
261 currentIndex, err := m.getCurrentContractIndex(account, change)
266 for currentIndex++; currentIndex <= stopIndex; currentIndex++ {
267 cp, err := CreateCtrlProgram(account, currentIndex, change)
272 if err := m.saveControlProgram(cp, true); err != nil {
280 // DeleteAccount deletes the account's ID or alias matching account ID.
281 func (m *Manager) DeleteAccount(accountID string) error {
283 defer m.accountMu.Unlock()
285 account, err := m.FindByID(accountID)
291 m.aliasCache.Remove(account.Alias)
294 return m.store.DeleteAccount(account)
297 // FindByAlias retrieves an account's Signer record by its alias
298 func (m *Manager) FindByAlias(alias string) (*Account, error) {
300 cachedID, ok := m.aliasCache.Get(alias)
303 return m.FindByID(cachedID.(string))
306 return m.store.GetAccountByAlias(alias)
309 // FindByID returns an account's Signer record by its ID.
310 func (m *Manager) FindByID(id string) (*Account, error) {
312 cachedAccount, ok := m.cache.Get(id)
315 return cachedAccount.(*Account), nil
318 account, err := m.store.GetAccountByID(id)
324 m.cache.Add(id, account)
329 // GetAccountByProgram return Account by given CtrlProgram
330 func (m *Manager) GetAccountByProgram(program *CtrlProgram) (*Account, error) {
331 return m.store.GetAccountByID(program.AccountID)
334 // GetAccountByXPubsIndex get account by xPubs and index
335 func (m *Manager) GetAccountByXPubsIndex(xPubs []chainkd.XPub, index uint64) (*Account, error) {
336 accounts, err := m.ListAccounts("")
341 for _, account := range accounts {
342 if reflect.DeepEqual(account.XPubs, xPubs) && account.KeyIndex == index {
346 return nil, ErrFindAccount
349 // GetAliasByID return the account alias by given ID
350 func (m *Manager) GetAliasByID(id string) string {
351 account, err := m.store.GetAccountByID(id)
353 log.Warn("GetAliasByID fail to find account")
359 func (m *Manager) GetCoinbaseArbitrary() []byte {
360 if arbitrary := m.store.GetCoinbaseArbitrary(); arbitrary != nil {
366 // GetCoinbaseControlProgram will return a coinbase script
367 func (m *Manager) GetCoinbaseControlProgram() ([]byte, error) {
368 cp, err := m.GetCoinbaseCtrlProgram()
369 if err == ErrFindAccount {
370 log.Warningf("GetCoinbaseControlProgram: can't find any account in db")
371 return vmutil.DefaultCoinbaseProgram()
378 return cp.ControlProgram, nil
381 // GetCoinbaseCtrlProgram will return the coinbase CtrlProgram
382 func (m *Manager) GetCoinbaseCtrlProgram() (*CtrlProgram, error) {
383 if cp, err := m.store.GetMiningAddress(); err == nil {
385 } else if err != ErrFindMiningAddress {
389 account := new(Account)
390 accounts, err := m.store.ListAccounts("")
395 if len(accounts) > 0 {
396 account = accounts[0]
398 return nil, ErrFindAccount
401 program, err := m.CreateAddress(account.ID, false)
406 if err := m.store.SetMiningAddress(program); err != nil {
413 // GetBip44ContractIndex return the current bip44 contract index
414 func (m *Manager) GetBip44ContractIndex(accountID string, change bool) uint64 {
415 return m.store.GetBip44ContractIndex(accountID, change)
418 // GetLocalCtrlProgramByAddress return CtrlProgram by given address
419 func (m *Manager) GetLocalCtrlProgramByAddress(address string) (*CtrlProgram, error) {
420 program, err := m.getProgramByAddress(address)
426 sha3pool.Sum256(hash[:], program)
428 cp, err := m.store.GetControlProgram(bc.NewHash(hash))
436 // GetMiningAddress will return the mining address
437 func (m *Manager) GetMiningAddress() (string, error) {
438 cp, err := m.GetCoinbaseCtrlProgram()
443 return cp.Address, nil
446 // IsLocalControlProgram check is the input control program belong to local
447 func (m *Manager) IsLocalControlProgram(prog []byte) bool {
449 sha3pool.Sum256(hash[:], prog)
450 cp, err := m.store.GetControlProgram(bc.NewHash(hash))
451 if err != nil || cp == nil {
457 // ListAccounts will return the accounts in the db
458 func (m *Manager) ListAccounts(id string) ([]*Account, error) {
459 return m.store.ListAccounts(id)
462 // ListControlProgram return all the local control program
463 func (m *Manager) ListControlProgram() ([]*CtrlProgram, error) {
464 return m.store.ListControlPrograms()
467 func (m *Manager) ListUnconfirmedUtxo(accountID string, isSmartContract bool) []*UTXO {
468 utxos := m.utxoKeeper.ListUnconfirmed()
470 for _, utxo := range utxos {
471 if segwit.IsP2WScript(utxo.ControlProgram) != isSmartContract && (accountID == utxo.AccountID || accountID == "") {
472 result = append(result, utxo)
478 // RemoveUnconfirmedUtxo remove utxos from the utxoKeeper
479 func (m *Manager) RemoveUnconfirmedUtxo(hashes []*bc.Hash) {
480 m.utxoKeeper.RemoveUnconfirmedUtxo(hashes)
483 // SetMiningAddress will set the mining address
484 func (m *Manager) SetMiningAddress(miningAddress string) (string, error) {
485 program, err := m.getProgramByAddress(miningAddress)
491 Address: miningAddress,
492 ControlProgram: program,
494 if err := m.store.SetMiningAddress(cp); err != nil {
495 return cp.Address, err
498 return m.GetMiningAddress()
501 func (m *Manager) SetCoinbaseArbitrary(arbitrary []byte) {
502 m.store.SetCoinbaseArbitrary(arbitrary)
505 // CreateCtrlProgram generate an address for the select account
506 func CreateCtrlProgram(account *Account, addrIdx uint64, change bool) (cp *CtrlProgram, err error) {
507 path, err := signers.Path(account.Signer, signers.AccountKeySpace, change, addrIdx)
512 if len(account.XPubs) == 1 {
513 cp, err = createP2PKH(account, path)
515 cp, err = createP2SH(account, path)
522 cp.KeyIndex, cp.Change = addrIdx, change
526 func createP2PKH(account *Account, path [][]byte) (*CtrlProgram, error) {
527 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
528 derivedPK := derivedXPubs[0].PublicKey()
529 pubHash := crypto.Ripemd160(derivedPK)
531 address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
536 control, err := vmutil.P2WPKHProgram([]byte(pubHash))
542 AccountID: account.ID,
543 Address: address.EncodeAddress(),
544 ControlProgram: control,
548 func createP2SH(account *Account, path [][]byte) (*CtrlProgram, error) {
549 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
550 derivedPKs := chainkd.XPubKeys(derivedXPubs)
551 signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
556 scriptHash := crypto.Sha256(signScript)
557 address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
562 control, err := vmutil.P2WSHProgram(scriptHash)
568 AccountID: account.ID,
569 Address: address.EncodeAddress(),
570 ControlProgram: control,
574 func (m *Manager) GetContractIndex(accountID string) uint64 {
575 return m.store.GetContractIndex(accountID)
578 func (m *Manager) getCurrentContractIndex(account *Account, change bool) (uint64, error) {
579 switch account.DeriveRule {
580 case signers.BIP0032:
581 return m.store.GetContractIndex(account.ID), nil
582 case signers.BIP0044:
583 return m.store.GetBip44ContractIndex(account.ID, change), nil
585 return 0, ErrDeriveRule
588 func (m *Manager) getProgramByAddress(address string) ([]byte, error) {
589 addr, err := common.DecodeAddress(address, &consensus.ActiveNetParams)
594 redeemContract := addr.ScriptAddress()
597 case *common.AddressWitnessPubKeyHash:
598 program, err = vmutil.P2WPKHProgram(redeemContract)
599 case *common.AddressWitnessScriptHash:
600 program, err = vmutil.P2WSHProgram(redeemContract)
602 return nil, ErrInvalidAddress
611 func (m *Manager) saveControlProgram(prog *CtrlProgram, updateIndex bool) error {
614 sha3pool.Sum256(hash[:], prog.ControlProgram)
615 acct, err := m.GetAccountByProgram(prog)
620 newStore := m.store.InitBatch()
622 if err := newStore.SetControlProgram(bc.NewHash(hash), prog); err != nil {
627 switch acct.DeriveRule {
628 case signers.BIP0032:
629 newStore.SetContractIndex(acct.ID, prog.KeyIndex)
630 case signers.BIP0044:
631 newStore.SetBip44ContractIndex(acct.ID, prog.Change, prog.KeyIndex)
635 return newStore.CommitBatch()
638 // SaveControlPrograms save account control programs
639 func (m *Manager) SaveControlPrograms(progs ...*CtrlProgram) error {
641 defer m.addressMu.Unlock()
643 for _, prog := range progs {
644 acct, err := m.GetAccountByProgram(prog)
649 currentIndex, err := m.getCurrentContractIndex(acct, prog.Change)
654 m.saveControlProgram(prog, prog.KeyIndex > currentIndex)
659 func (m *Manager) SetStandardUTXO(outputID bc.Hash, utxo *UTXO) error {
660 return m.store.SetStandardUTXO(outputID, utxo)
663 func (m *Manager) DeleteStandardUTXO(outputID bc.Hash) {
664 m.store.DeleteStandardUTXO(outputID)
667 func (m *Manager) GetControlProgram(hash bc.Hash) (*CtrlProgram, error) {
668 return m.store.GetControlProgram(hash)