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 {
117 defer m.store.CommitBatch()
119 if err := m.store.SetAccount(account, updateIndex); err != nil {
126 // SaveAccount save a new account.
127 func (m *Manager) SaveAccount(account *Account) error {
129 defer m.accountMu.Unlock()
131 if existed := m.store.GetAccountIDByAccountAlias(account.Alias); existed != "" {
132 return ErrDuplicateAlias
135 acct, err := m.GetAccountByXPubsIndex(account.XPubs, account.KeyIndex)
141 return ErrDuplicateIndex
144 currentIndex := m.store.GetAccountIndex(account.XPubs)
145 return m.saveAccount(account, account.KeyIndex > currentIndex)
148 // Create creates and save a new Account.
149 func (m *Manager) Create(xpubs []chainkd.XPub, quorum int, alias string, deriveRule uint8) (*Account, error) {
151 defer m.accountMu.Unlock()
153 if existed := m.store.GetAccountIDByAccountAlias(alias); existed != "" {
154 return nil, ErrDuplicateAlias
157 acctIndex := uint64(1)
158 if currentIndex := m.store.GetAccountIndex(xpubs); currentIndex != 0 {
159 acctIndex = currentIndex + 1
161 account, err := CreateAccount(xpubs, quorum, alias, acctIndex, deriveRule)
166 if err := m.saveAccount(account, true); err != nil {
173 func (m *Manager) UpdateAccountAlias(accountID string, newAlias string) error {
175 defer m.accountMu.Unlock()
177 account, err := m.FindByID(accountID)
181 oldAlias := account.Alias
183 normalizedAlias := strings.ToLower(strings.TrimSpace(newAlias))
184 if existed := m.store.GetAccountIDByAccountAlias(normalizedAlias); existed != "" {
185 return ErrDuplicateAlias
189 m.aliasCache.Remove(oldAlias)
192 account.Alias = normalizedAlias
195 defer m.store.CommitBatch()
197 m.store.DeleteAccountByAccountAlias(oldAlias)
198 if err := m.store.SetAccount(account, false); err != nil {
205 // CreateAddress generate an address for the select account
206 func (m *Manager) CreateAddress(accountID string, change bool) (cp *CtrlProgram, err error) {
208 defer m.addressMu.Unlock()
210 account, err := m.FindByID(accountID)
215 currentIdx, err := m.getCurrentContractIndex(account, change)
220 cp, err = CreateCtrlProgram(account, currentIdx+1, change)
225 return cp, m.saveControlProgram(cp, true)
228 // CreateBatchAddresses generate a batch of addresses for the select account
229 func (m *Manager) CreateBatchAddresses(accountID string, change bool, stopIndex uint64) error {
231 defer m.addressMu.Unlock()
233 account, err := m.FindByID(accountID)
238 currentIndex, err := m.getCurrentContractIndex(account, change)
243 for currentIndex++; currentIndex <= stopIndex; currentIndex++ {
244 cp, err := CreateCtrlProgram(account, currentIndex, change)
249 if err := m.saveControlProgram(cp, true); err != nil {
257 // deleteAccountControlPrograms deletes control program matching accountID
258 func (m *Manager) deleteAccountControlPrograms(accountID string) error {
259 cps, err := m.ListControlProgram()
265 for _, cp := range cps {
266 if cp.AccountID == accountID {
267 sha3pool.Sum256(hash[:], cp.ControlProgram)
268 m.store.DeleteRawProgram(hash)
272 m.store.DeleteBip44ContractIndex(accountID)
273 m.store.DeleteContractIndex(accountID)
278 // deleteAccountUtxos deletes utxos matching accountID
279 func (m *Manager) deleteAccountUtxos(accountID string) error {
280 rawUTXOs := m.store.GetAccountUTXOs(accountID)
282 for _, rawUTXO := range rawUTXOs {
284 if err := json.Unmarshal(rawUTXO, utxo); err != nil {
287 if accountID == utxo.AccountID {
288 m.store.DeleteStandardUTXO(utxo.OutputID)
295 // DeleteAccount deletes the account's ID or alias matching account ID.
296 func (m *Manager) DeleteAccount(accountID string) (err error) {
298 defer m.accountMu.Unlock()
300 account, err := m.FindByID(accountID)
305 if err := m.deleteAccountControlPrograms(accountID); err != nil {
308 if err := m.deleteAccountUtxos(accountID); err != nil {
313 m.aliasCache.Remove(account.Alias)
317 defer m.store.CommitBatch()
319 m.store.DeleteAccountByAccountAlias(account.Alias)
320 m.store.DeleteAccountByAccountID(account.ID)
324 // FindByAlias retrieves an account's Signer record by its alias
325 func (m *Manager) FindByAlias(alias string) (*Account, error) {
327 cachedID, ok := m.aliasCache.Get(alias)
330 return m.FindByID(cachedID.(string))
333 accountID := m.store.GetAccountIDByAccountAlias(alias)
335 return nil, ErrFindAccount
339 m.aliasCache.Add(alias, accountID)
341 return m.FindByID(accountID)
344 // FindByID returns an account's Signer record by its ID.
345 func (m *Manager) FindByID(id string) (*Account, error) {
347 cachedAccount, ok := m.cache.Get(id)
350 return cachedAccount.(*Account), nil
353 account, err := m.store.GetAccountByAccountID(id)
359 m.cache.Add(id, account)
364 // GetAccountByProgram return Account by given CtrlProgram
365 func (m *Manager) GetAccountByProgram(program *CtrlProgram) (*Account, error) {
366 account, err := m.store.GetAccountByAccountID(program.AccountID)
374 // GetAccountByXPubsIndex get account by xPubs and index
375 func (m *Manager) GetAccountByXPubsIndex(xPubs []chainkd.XPub, index uint64) (*Account, error) {
376 accounts, err := m.ListAccounts("")
381 for _, account := range accounts {
382 if reflect.DeepEqual(account.XPubs, xPubs) && account.KeyIndex == index {
389 // GetAliasByID return the account alias by given ID
390 func (m *Manager) GetAliasByID(id string) string {
391 account, err := m.store.GetAccountByAccountID(id)
393 log.Warn("GetAliasByID fail to find account")
399 func (m *Manager) GetCoinbaseArbitrary() []byte {
400 if arbitrary := m.store.GetCoinbaseArbitrary(); arbitrary != nil {
406 // GetCoinbaseControlProgram will return a coinbase script
407 func (m *Manager) GetCoinbaseControlProgram() ([]byte, error) {
408 cp, err := m.GetCoinbaseCtrlProgram()
409 if err == ErrFindAccount {
410 log.Warningf("GetCoinbaseControlProgram: can't find any account in db")
411 return vmutil.DefaultCoinbaseProgram()
416 return cp.ControlProgram, nil
419 // GetCoinbaseCtrlProgram will return the coinbase CtrlProgram
420 func (m *Manager) GetCoinbaseCtrlProgram() (*CtrlProgram, error) {
421 if data := m.store.GetMiningAddress(); data != nil {
423 return cp, json.Unmarshal(data, cp)
426 firstAccount := make([]byte, 0)
427 accounts := m.store.GetAccounts("")
428 if len(accounts) > 0 {
429 firstAccount = accounts[0]
431 return nil, ErrFindAccount
434 account := &Account{}
435 if err := json.Unmarshal(firstAccount, account); err != nil {
439 program, err := m.CreateAddress(account.ID, false)
444 rawCP, err := json.Marshal(program)
449 m.store.SetMiningAddress(rawCP)
454 // GetContractIndex return the current index
455 func (m *Manager) GetContractIndex(accountID string) uint64 {
457 if rawIndexBytes := m.store.GetContractIndex(accountID); rawIndexBytes != nil {
458 index = common.BytesToUnit64(rawIndexBytes)
463 // GetBip44ContractIndex return the current bip44 contract index
464 func (m *Manager) GetBip44ContractIndex(accountID string, change bool) uint64 {
466 if rawIndexBytes := m.store.GetBip44ContractIndex(accountID, change); rawIndexBytes != nil {
467 index = common.BytesToUnit64(rawIndexBytes)
472 // GetLocalCtrlProgramByAddress return CtrlProgram by given address
473 func (m *Manager) GetLocalCtrlProgramByAddress(address string) (*CtrlProgram, error) {
474 program, err := m.getProgramByAddress(address)
480 sha3pool.Sum256(hash[:], program)
481 rawProgram := m.store.GetRawProgram(hash)
482 if rawProgram == nil {
483 return nil, ErrFindCtrlProgram
487 return cp, json.Unmarshal(rawProgram, cp)
490 // GetMiningAddress will return the mining address
491 func (m *Manager) GetMiningAddress() (string, error) {
492 cp, err := m.GetCoinbaseCtrlProgram()
496 return cp.Address, nil
499 // IsLocalControlProgram check is the input control program belong to local
500 func (m *Manager) IsLocalControlProgram(prog []byte) bool {
502 sha3pool.Sum256(hash[:], prog)
503 bytes := m.store.GetRawProgram(hash)
507 // ListAccounts will return the accounts in the db
508 func (m *Manager) ListAccounts(id string) ([]*Account, error) {
509 rawAccounts := m.store.GetAccounts(id)
511 accounts := []*Account{}
512 for _, rawAccount := range rawAccounts {
513 account := new(Account)
514 if err := json.Unmarshal(rawAccount, &account); err != nil {
517 accounts = append(accounts, account)
523 // ListControlProgram return all the local control program
524 func (m *Manager) ListControlProgram() ([]*CtrlProgram, error) {
525 rawControlPrograms, err := m.store.GetControlPrograms()
530 controlPrograms := []*CtrlProgram{}
531 for _, rawControlProgram := range rawControlPrograms {
532 controlProgram := new(CtrlProgram)
533 if err := json.Unmarshal(rawControlProgram, controlProgram); err != nil {
536 controlPrograms = append(controlPrograms, controlProgram)
539 return controlPrograms, nil
542 func (m *Manager) ListUnconfirmedUtxo(accountID string, isSmartContract bool) []*UTXO {
543 utxos := m.utxoKeeper.ListUnconfirmed()
545 for _, utxo := range utxos {
546 if segwit.IsP2WScript(utxo.ControlProgram) != isSmartContract && (accountID == utxo.AccountID || accountID == "") {
547 result = append(result, utxo)
553 // RemoveUnconfirmedUtxo remove utxos from the utxoKeeper
554 func (m *Manager) RemoveUnconfirmedUtxo(hashes []*bc.Hash) {
555 m.utxoKeeper.RemoveUnconfirmedUtxo(hashes)
558 // SetMiningAddress will set the mining address
559 func (m *Manager) SetMiningAddress(miningAddress string) (string, error) {
560 program, err := m.getProgramByAddress(miningAddress)
566 Address: miningAddress,
567 ControlProgram: program,
569 rawCP, err := json.Marshal(cp)
574 m.store.SetMiningAddress(rawCP)
575 return m.GetMiningAddress()
578 func (m *Manager) SetCoinbaseArbitrary(arbitrary []byte) {
579 m.store.SetCoinbaseArbitrary(arbitrary)
582 // CreateCtrlProgram generate an address for the select account
583 func CreateCtrlProgram(account *Account, addrIdx uint64, change bool) (cp *CtrlProgram, err error) {
584 path, err := signers.Path(account.Signer, signers.AccountKeySpace, change, addrIdx)
589 if len(account.XPubs) == 1 {
590 cp, err = createP2PKH(account, path)
592 cp, err = createP2SH(account, path)
597 cp.KeyIndex, cp.Change = addrIdx, change
601 func createP2PKH(account *Account, path [][]byte) (*CtrlProgram, error) {
602 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
603 derivedPK := derivedXPubs[0].PublicKey()
604 pubHash := crypto.Ripemd160(derivedPK)
606 address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
611 control, err := vmutil.P2WPKHProgram([]byte(pubHash))
617 AccountID: account.ID,
618 Address: address.EncodeAddress(),
619 ControlProgram: control,
623 func createP2SH(account *Account, path [][]byte) (*CtrlProgram, error) {
624 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
625 derivedPKs := chainkd.XPubKeys(derivedXPubs)
626 signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
630 scriptHash := crypto.Sha256(signScript)
632 address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
637 control, err := vmutil.P2WSHProgram(scriptHash)
643 AccountID: account.ID,
644 Address: address.EncodeAddress(),
645 ControlProgram: control,
649 func (m *Manager) getCurrentContractIndex(account *Account, change bool) (uint64, error) {
650 switch account.DeriveRule {
651 case signers.BIP0032:
652 return m.GetContractIndex(account.ID), nil
653 case signers.BIP0044:
654 return m.GetBip44ContractIndex(account.ID, change), nil
656 return 0, ErrDeriveRule
659 func (m *Manager) getProgramByAddress(address string) ([]byte, error) {
660 addr, err := common.DecodeAddress(address, &consensus.ActiveNetParams)
664 redeemContract := addr.ScriptAddress()
667 case *common.AddressWitnessPubKeyHash:
668 program, err = vmutil.P2WPKHProgram(redeemContract)
669 case *common.AddressWitnessScriptHash:
670 program, err = vmutil.P2WSHProgram(redeemContract)
672 return nil, ErrInvalidAddress
680 func (m *Manager) saveControlProgram(prog *CtrlProgram, updateIndex bool) error {
683 sha3pool.Sum256(hash[:], prog.ControlProgram)
684 acct, err := m.GetAccountByProgram(prog)
689 accountCP, err := json.Marshal(prog)
695 defer m.store.CommitBatch()
697 m.store.SetRawProgram(hash, accountCP)
699 switch acct.DeriveRule {
700 case signers.BIP0032:
701 m.store.SetContractIndex(acct.ID, prog.KeyIndex)
702 case signers.BIP0044:
703 m.store.SetBip44ContractIndex(acct.ID, prog.Change, prog.KeyIndex)
710 // SaveControlPrograms save account control programs
711 func (m *Manager) SaveControlPrograms(progs ...*CtrlProgram) error {
713 defer m.addressMu.Unlock()
715 for _, prog := range progs {
716 acct, err := m.GetAccountByProgram(prog)
721 currentIndex, err := m.getCurrentContractIndex(acct, prog.Change)
726 m.saveControlProgram(prog, prog.KeyIndex > currentIndex)