1 // Package account stores and tracks accounts within a Bytom Core.
9 "github.com/golang/groupcache/lru"
10 log "github.com/sirupsen/logrus"
11 dbm "github.com/tendermint/tmlibs/db"
13 "github.com/bytom/blockchain/signers"
14 "github.com/bytom/blockchain/txbuilder"
15 "github.com/bytom/common"
16 "github.com/bytom/consensus"
17 "github.com/bytom/consensus/segwit"
18 "github.com/bytom/crypto"
19 "github.com/bytom/crypto/ed25519/chainkd"
20 "github.com/bytom/crypto/sha3pool"
21 "github.com/bytom/errors"
22 "github.com/bytom/protocol"
23 "github.com/bytom/protocol/bc"
24 "github.com/bytom/protocol/vm/vmutil"
28 maxAccountCache = 1000
32 accountIndexKey = []byte("AccountIndex")
33 accountPrefix = []byte("Account:")
34 aliasPrefix = []byte("AccountAlias:")
35 contractIndexPrefix = []byte("ContractIndex")
36 contractPrefix = []byte("Contract:")
37 miningAddressKey = []byte("MiningAddress")
38 CoinbaseAbKey = []byte("CoinbaseArbitrary")
41 // pre-define errors for supporting bytom errorFormatter
43 ErrDuplicateAlias = errors.New("duplicate account alias")
44 ErrFindAccount = errors.New("fail to find account")
45 ErrMarshalAccount = errors.New("failed marshal account")
46 ErrInvalidAddress = errors.New("invalid address")
47 ErrFindCtrlProgram = errors.New("fail to find account control program")
50 // ContractKey account control promgram store prefix
51 func ContractKey(hash common.Hash) []byte {
52 return append(contractPrefix, hash[:]...)
55 // Key account store prefix
56 func Key(name string) []byte {
57 return append(accountPrefix, []byte(name)...)
60 func aliasKey(name string) []byte {
61 return append(aliasPrefix, []byte(name)...)
64 func contractIndexKey(accountID string) []byte {
65 return append(contractIndexPrefix, []byte(accountID)...)
68 // Account is structure of Bytom account
72 Alias string `json:"alias"`
75 //CtrlProgram is structure of account control program
76 type CtrlProgram struct {
81 Change bool // Mark whether this control program is for UTXO change
84 // Manager stores accounts and their associated control programs.
88 utxoKeeper *utxoKeeper
94 delayedACPsMu sync.Mutex
95 delayedACPs map[*txbuilder.TemplateBuilder][]*CtrlProgram
101 // NewManager creates a new account manager
102 func NewManager(walletDB dbm.DB, chain *protocol.Chain) *Manager {
106 utxoKeeper: newUtxoKeeper(chain.BestBlockHeight, walletDB),
107 cache: lru.New(maxAccountCache),
108 aliasCache: lru.New(maxAccountCache),
109 delayedACPs: make(map[*txbuilder.TemplateBuilder][]*CtrlProgram),
113 // AddUnconfirmedUtxo add untxo list to utxoKeeper
114 func (m *Manager) AddUnconfirmedUtxo(utxos []*UTXO) {
115 m.utxoKeeper.AddUnconfirmedUtxo(utxos)
118 // Create creates a new Account.
119 func (m *Manager) Create(xpubs []chainkd.XPub, quorum int, alias string) (*Account, error) {
121 defer m.accountMu.Unlock()
123 normalizedAlias := strings.ToLower(strings.TrimSpace(alias))
124 if existed := m.db.Get(aliasKey(normalizedAlias)); existed != nil {
125 return nil, ErrDuplicateAlias
128 signer, err := signers.Create("account", xpubs, quorum, m.getNextAccountIndex())
129 id := signers.IDGenerate()
131 return nil, errors.Wrap(err)
134 account := &Account{Signer: signer, ID: id, Alias: normalizedAlias}
135 rawAccount, err := json.Marshal(account)
137 return nil, ErrMarshalAccount
141 storeBatch := m.db.NewBatch()
142 storeBatch.Set(accountID, rawAccount)
143 storeBatch.Set(aliasKey(normalizedAlias), []byte(id))
148 // CreateAddress generate an address for the select account
149 func (m *Manager) CreateAddress(accountID string, change bool) (cp *CtrlProgram, err error) {
150 account, err := m.FindByID(accountID)
154 return m.createAddress(account, change)
157 // DeleteAccount deletes the account's ID or alias matching accountInfo.
158 func (m *Manager) DeleteAccount(aliasOrID string) (err error) {
159 account := &Account{}
160 if account, err = m.FindByAlias(aliasOrID); err != nil {
161 if account, err = m.FindByID(aliasOrID); err != nil {
167 m.aliasCache.Remove(account.Alias)
170 storeBatch := m.db.NewBatch()
171 storeBatch.Delete(aliasKey(account.Alias))
172 storeBatch.Delete(Key(account.ID))
177 // FindByAlias retrieves an account's Signer record by its alias
178 func (m *Manager) FindByAlias(alias string) (*Account, error) {
180 cachedID, ok := m.aliasCache.Get(alias)
183 return m.FindByID(cachedID.(string))
186 rawID := m.db.Get(aliasKey(alias))
188 return nil, ErrFindAccount
191 accountID := string(rawID)
193 m.aliasCache.Add(alias, accountID)
195 return m.FindByID(accountID)
198 // FindByID returns an account's Signer record by its ID.
199 func (m *Manager) FindByID(id string) (*Account, error) {
201 cachedAccount, ok := m.cache.Get(id)
204 return cachedAccount.(*Account), nil
207 rawAccount := m.db.Get(Key(id))
208 if rawAccount == nil {
209 return nil, ErrFindAccount
212 account := &Account{}
213 if err := json.Unmarshal(rawAccount, account); err != nil {
218 m.cache.Add(id, account)
223 // GetAccountByProgram return Account by given CtrlProgram
224 func (m *Manager) GetAccountByProgram(program *CtrlProgram) (*Account, error) {
225 rawAccount := m.db.Get(Key(program.AccountID))
226 if rawAccount == nil {
227 return nil, ErrFindAccount
230 account := &Account{}
231 return account, json.Unmarshal(rawAccount, account)
234 // GetAliasByID return the account alias by given ID
235 func (m *Manager) GetAliasByID(id string) string {
236 rawAccount := m.db.Get(Key(id))
237 if rawAccount == nil {
238 log.Warn("GetAliasByID fail to find account")
242 account := &Account{}
243 if err := json.Unmarshal(rawAccount, account); err != nil {
249 func (m *Manager) GetCoinbaseArbitrary() []byte {
250 if arbitrary := m.db.Get(CoinbaseAbKey); arbitrary != nil {
256 // GetCoinbaseControlProgram will return a coinbase script
257 func (m *Manager) GetCoinbaseControlProgram() ([]byte, error) {
258 cp, err := m.GetCoinbaseCtrlProgram()
259 if err == ErrFindAccount {
260 log.Warningf("GetCoinbaseControlProgram: can't find any account in db")
261 return vmutil.DefaultCoinbaseProgram()
266 return cp.ControlProgram, nil
269 // GetCoinbaseCtrlProgram will return the coinbase CtrlProgram
270 func (m *Manager) GetCoinbaseCtrlProgram() (*CtrlProgram, error) {
271 if data := m.db.Get(miningAddressKey); data != nil {
273 return cp, json.Unmarshal(data, cp)
276 accountIter := m.db.IteratorPrefix([]byte(accountPrefix))
277 defer accountIter.Release()
278 if !accountIter.Next() {
279 return nil, ErrFindAccount
282 account := &Account{}
283 if err := json.Unmarshal(accountIter.Value(), account); err != nil {
287 program, err := m.createAddress(account, false)
292 rawCP, err := json.Marshal(program)
297 m.db.Set(miningAddressKey, rawCP)
301 // GetContractIndex return the current index
302 func (m *Manager) GetContractIndex(accountID string) uint64 {
304 defer m.accIndexMu.Unlock()
307 if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
308 index = common.BytesToUnit64(rawIndexBytes)
313 // GetLocalCtrlProgramByAddress return CtrlProgram by given address
314 func (m *Manager) GetLocalCtrlProgramByAddress(address string) (*CtrlProgram, error) {
315 program, err := m.getProgramByAddress(address)
321 sha3pool.Sum256(hash[:], program)
322 rawProgram := m.db.Get(ContractKey(hash))
323 if rawProgram == nil {
324 return nil, ErrFindCtrlProgram
328 return cp, json.Unmarshal(rawProgram, cp)
331 // GetMiningAddress will return the mining address
332 func (m *Manager) GetMiningAddress() (string, error) {
333 cp, err := m.GetCoinbaseCtrlProgram()
337 return cp.Address, nil
340 // IsLocalControlProgram check is the input control program belong to local
341 func (m *Manager) IsLocalControlProgram(prog []byte) bool {
343 sha3pool.Sum256(hash[:], prog)
344 bytes := m.db.Get(ContractKey(hash))
348 // ListAccounts will return the accounts in the db
349 func (m *Manager) ListAccounts(id string) ([]*Account, error) {
350 accounts := []*Account{}
351 accountIter := m.db.IteratorPrefix(Key(strings.TrimSpace(id)))
352 defer accountIter.Release()
354 for accountIter.Next() {
355 account := &Account{}
356 if err := json.Unmarshal(accountIter.Value(), &account); err != nil {
359 accounts = append(accounts, account)
364 // ListControlProgram return all the local control program
365 func (m *Manager) ListControlProgram() ([]*CtrlProgram, error) {
366 cps := []*CtrlProgram{}
367 cpIter := m.db.IteratorPrefix(contractPrefix)
368 defer cpIter.Release()
372 if err := json.Unmarshal(cpIter.Value(), cp); err != nil {
375 cps = append(cps, cp)
380 func (m *Manager) ListUnconfirmedUtxo(isSmartContract bool) []*UTXO {
381 utxos := m.utxoKeeper.ListUnconfirmed()
383 for _, utxo := range utxos {
384 if segwit.IsP2WScript(utxo.ControlProgram) != isSmartContract {
385 result = append(result, utxo)
391 // RemoveUnconfirmedUtxo remove utxos from the utxoKeeper
392 func (m *Manager) RemoveUnconfirmedUtxo(hashes []*bc.Hash) {
393 m.utxoKeeper.RemoveUnconfirmedUtxo(hashes)
396 // SetMiningAddress will set the mining address
397 func (m *Manager) SetMiningAddress(miningAddress string) (string, error) {
398 program, err := m.getProgramByAddress(miningAddress)
404 Address: miningAddress,
405 ControlProgram: program,
407 rawCP, err := json.Marshal(cp)
412 m.db.Set(miningAddressKey, rawCP)
413 return m.GetMiningAddress()
416 func (m *Manager) SetCoinbaseArbitrary(arbitrary []byte) {
417 m.db.Set(CoinbaseAbKey, arbitrary)
420 // CreateAddress generate an address for the select account
421 func (m *Manager) createAddress(account *Account, change bool) (cp *CtrlProgram, err error) {
422 if len(account.XPubs) == 1 {
423 cp, err = m.createP2PKH(account, change)
425 cp, err = m.createP2SH(account, change)
430 return cp, m.insertControlPrograms(cp)
433 func (m *Manager) createP2PKH(account *Account, change bool) (*CtrlProgram, error) {
434 idx := m.getNextContractIndex(account.ID)
435 path := signers.Path(account.Signer, signers.AccountKeySpace, idx)
436 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
437 derivedPK := derivedXPubs[0].PublicKey()
438 pubHash := crypto.Ripemd160(derivedPK)
440 address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
445 control, err := vmutil.P2WPKHProgram([]byte(pubHash))
451 AccountID: account.ID,
452 Address: address.EncodeAddress(),
454 ControlProgram: control,
459 func (m *Manager) createP2SH(account *Account, change bool) (*CtrlProgram, error) {
460 idx := m.getNextContractIndex(account.ID)
461 path := signers.Path(account.Signer, signers.AccountKeySpace, idx)
462 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
463 derivedPKs := chainkd.XPubKeys(derivedXPubs)
464 signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
468 scriptHash := crypto.Sha256(signScript)
470 address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
475 control, err := vmutil.P2WSHProgram(scriptHash)
481 AccountID: account.ID,
482 Address: address.EncodeAddress(),
484 ControlProgram: control,
489 func (m *Manager) getNextAccountIndex() uint64 {
491 defer m.accIndexMu.Unlock()
493 var nextIndex uint64 = 1
494 if rawIndexBytes := m.db.Get(accountIndexKey); rawIndexBytes != nil {
495 nextIndex = common.BytesToUnit64(rawIndexBytes) + 1
497 m.db.Set(accountIndexKey, common.Unit64ToBytes(nextIndex))
501 func (m *Manager) getNextContractIndex(accountID string) uint64 {
503 defer m.accIndexMu.Unlock()
505 nextIndex := uint64(1)
506 if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
507 nextIndex = common.BytesToUnit64(rawIndexBytes) + 1
509 m.db.Set(contractIndexKey(accountID), common.Unit64ToBytes(nextIndex))
513 func (m *Manager) getProgramByAddress(address string) ([]byte, error) {
514 addr, err := common.DecodeAddress(address, &consensus.ActiveNetParams)
519 redeemContract := addr.ScriptAddress()
522 case *common.AddressWitnessPubKeyHash:
523 program, err = vmutil.P2WPKHProgram(redeemContract)
524 case *common.AddressWitnessScriptHash:
525 program, err = vmutil.P2WSHProgram(redeemContract)
527 return nil, ErrInvalidAddress
535 func (m *Manager) insertControlPrograms(progs ...*CtrlProgram) error {
537 for _, prog := range progs {
538 accountCP, err := json.Marshal(prog)
543 sha3pool.Sum256(hash[:], prog.ControlProgram)
544 m.db.Set(ContractKey(hash), accountCP)