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")
40 // pre-define errors for supporting bytom errorFormatter
42 ErrDuplicateAlias = errors.New("duplicate account alias")
43 ErrFindAccount = errors.New("fail to find account")
44 ErrMarshalAccount = errors.New("failed marshal account")
45 ErrInvalidAddress = errors.New("invalid address")
46 ErrFindCtrlProgram = errors.New("fail to find account control program")
49 // ContractKey account control promgram store prefix
50 func ContractKey(hash common.Hash) []byte {
51 return append(contractPrefix, hash[:]...)
54 // Key account store prefix
55 func Key(name string) []byte {
56 return append(accountPrefix, []byte(name)...)
59 func aliasKey(name string) []byte {
60 return append(aliasPrefix, []byte(name)...)
63 func contractIndexKey(accountID string) []byte {
64 return append(contractIndexPrefix, []byte(accountID)...)
67 // Account is structure of Bytom account
71 Alias string `json:"alias"`
74 //CtrlProgram is structure of account control program
75 type CtrlProgram struct {
80 Change bool // Mark whether this control program is for UTXO change
83 // Manager stores accounts and their associated control programs.
87 utxoKeeper *utxoKeeper
93 delayedACPsMu sync.Mutex
94 delayedACPs map[*txbuilder.TemplateBuilder][]*CtrlProgram
100 // NewManager creates a new account manager
101 func NewManager(walletDB dbm.DB, chain *protocol.Chain) *Manager {
105 utxoKeeper: newUtxoKeeper(chain.BestBlockHeight, walletDB),
106 cache: lru.New(maxAccountCache),
107 aliasCache: lru.New(maxAccountCache),
108 delayedACPs: make(map[*txbuilder.TemplateBuilder][]*CtrlProgram),
112 // AddUnconfirmedUtxo add untxo list to utxoKeeper
113 func (m *Manager) AddUnconfirmedUtxo(utxos []*UTXO) {
114 m.utxoKeeper.AddUnconfirmedUtxo(utxos)
117 // Create creates a new Account.
118 func (m *Manager) Create(xpubs []chainkd.XPub, quorum int, alias string) (*Account, error) {
120 defer m.accountMu.Unlock()
122 normalizedAlias := strings.ToLower(strings.TrimSpace(alias))
123 if existed := m.db.Get(aliasKey(normalizedAlias)); existed != nil {
124 return nil, ErrDuplicateAlias
127 signer, err := signers.Create("account", xpubs, quorum, m.getNextAccountIndex())
128 id := signers.IDGenerate()
130 return nil, errors.Wrap(err)
133 account := &Account{Signer: signer, ID: id, Alias: normalizedAlias}
134 rawAccount, err := json.Marshal(account)
136 return nil, ErrMarshalAccount
140 storeBatch := m.db.NewBatch()
141 storeBatch.Set(accountID, rawAccount)
142 storeBatch.Set(aliasKey(normalizedAlias), []byte(id))
147 // CreateAddress generate an address for the select account
148 func (m *Manager) CreateAddress(accountID string, change bool) (cp *CtrlProgram, err error) {
149 account, err := m.FindByID(accountID)
153 return m.createAddress(account, change)
156 // DeleteAccount deletes the account's ID or alias matching accountInfo.
157 func (m *Manager) DeleteAccount(aliasOrID string) (err error) {
158 account := &Account{}
159 if account, err = m.FindByAlias(aliasOrID); err != nil {
160 if account, err = m.FindByID(aliasOrID); err != nil {
166 m.aliasCache.Remove(account.Alias)
169 storeBatch := m.db.NewBatch()
170 storeBatch.Delete(aliasKey(account.Alias))
171 storeBatch.Delete(Key(account.ID))
176 // FindByAlias retrieves an account's Signer record by its alias
177 func (m *Manager) FindByAlias(alias string) (*Account, error) {
179 cachedID, ok := m.aliasCache.Get(alias)
182 return m.FindByID(cachedID.(string))
185 rawID := m.db.Get(aliasKey(alias))
187 return nil, ErrFindAccount
190 accountID := string(rawID)
192 m.aliasCache.Add(alias, accountID)
194 return m.FindByID(accountID)
197 // FindByID returns an account's Signer record by its ID.
198 func (m *Manager) FindByID(id string) (*Account, error) {
200 cachedAccount, ok := m.cache.Get(id)
203 return cachedAccount.(*Account), nil
206 rawAccount := m.db.Get(Key(id))
207 if rawAccount == nil {
208 return nil, ErrFindAccount
211 account := &Account{}
212 if err := json.Unmarshal(rawAccount, account); err != nil {
217 m.cache.Add(id, account)
222 // GetAccountByProgram return Account by given CtrlProgram
223 func (m *Manager) GetAccountByProgram(program *CtrlProgram) (*Account, error) {
224 rawAccount := m.db.Get(Key(program.AccountID))
225 if rawAccount == nil {
226 return nil, ErrFindAccount
229 account := &Account{}
230 return account, json.Unmarshal(rawAccount, account)
233 // GetAliasByID return the account alias by given ID
234 func (m *Manager) GetAliasByID(id string) string {
235 rawAccount := m.db.Get(Key(id))
236 if rawAccount == nil {
237 log.Warn("GetAliasByID fail to find account")
241 account := &Account{}
242 if err := json.Unmarshal(rawAccount, account); err != nil {
248 // GetCoinbaseControlProgram will return a coinbase script
249 func (m *Manager) GetCoinbaseControlProgram() ([]byte, error) {
250 cp, err := m.GetCoinbaseCtrlProgram()
251 if err == ErrFindAccount {
252 log.Warningf("GetCoinbaseControlProgram: can't find any account in db")
253 return vmutil.DefaultCoinbaseProgram()
258 return cp.ControlProgram, nil
261 // GetCoinbaseCtrlProgram will return the coinbase CtrlProgram
262 func (m *Manager) GetCoinbaseCtrlProgram() (*CtrlProgram, error) {
263 if data := m.db.Get(miningAddressKey); data != nil {
265 return cp, json.Unmarshal(data, cp)
268 accountIter := m.db.IteratorPrefix([]byte(accountPrefix))
269 defer accountIter.Release()
270 if !accountIter.Next() {
271 return nil, ErrFindAccount
274 account := &Account{}
275 if err := json.Unmarshal(accountIter.Value(), account); err != nil {
279 program, err := m.createAddress(account, false)
284 rawCP, err := json.Marshal(program)
289 m.db.Set(miningAddressKey, rawCP)
293 // GetContractIndex return the current index
294 func (m *Manager) GetContractIndex(accountID string) uint64 {
296 defer m.accIndexMu.Unlock()
299 if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
300 index = common.BytesToUnit64(rawIndexBytes)
305 // GetLocalCtrlProgramByAddress return CtrlProgram by given address
306 func (m *Manager) GetLocalCtrlProgramByAddress(address string) (*CtrlProgram, error) {
307 program, err := m.getProgramByAddress(address)
313 sha3pool.Sum256(hash[:], program)
314 rawProgram := m.db.Get(ContractKey(hash))
315 if rawProgram == nil {
316 return nil, ErrFindCtrlProgram
320 return cp, json.Unmarshal(rawProgram, cp)
323 // GetMiningAddress will return the mining address
324 func (m *Manager) GetMiningAddress() (string, error) {
325 cp, err := m.GetCoinbaseCtrlProgram()
329 return cp.Address, nil
332 // IsLocalControlProgram check is the input control program belong to local
333 func (m *Manager) IsLocalControlProgram(prog []byte) bool {
335 sha3pool.Sum256(hash[:], prog)
336 bytes := m.db.Get(ContractKey(hash))
340 // ListAccounts will return the accounts in the db
341 func (m *Manager) ListAccounts(id string) ([]*Account, error) {
342 accounts := []*Account{}
343 accountIter := m.db.IteratorPrefix(Key(strings.TrimSpace(id)))
344 defer accountIter.Release()
346 for accountIter.Next() {
347 account := &Account{}
348 if err := json.Unmarshal(accountIter.Value(), &account); err != nil {
351 accounts = append(accounts, account)
356 // ListControlProgram return all the local control program
357 func (m *Manager) ListControlProgram() ([]*CtrlProgram, error) {
358 cps := []*CtrlProgram{}
359 cpIter := m.db.IteratorPrefix(contractPrefix)
360 defer cpIter.Release()
364 if err := json.Unmarshal(cpIter.Value(), cp); err != nil {
367 cps = append(cps, cp)
372 func (m *Manager) ListUnconfirmedUtxo(isSmartContract bool) []*UTXO {
373 utxos := m.utxoKeeper.ListUnconfirmed()
375 for _, utxo := range utxos {
376 if segwit.IsP2WScript(utxo.ControlProgram) != isSmartContract {
377 result = append(result, utxo)
383 // RemoveUnconfirmedUtxo remove utxos from the utxoKeeper
384 func (m *Manager) RemoveUnconfirmedUtxo(hashes []*bc.Hash) {
385 m.utxoKeeper.RemoveUnconfirmedUtxo(hashes)
388 // SetMiningAddress will set the mining address
389 func (m *Manager) SetMiningAddress(miningAddress string) (string, error) {
390 program, err := m.getProgramByAddress(miningAddress)
396 Address: miningAddress,
397 ControlProgram: program,
399 rawCP, err := json.Marshal(cp)
404 m.db.Set(miningAddressKey, rawCP)
405 return m.GetMiningAddress()
408 // CreateAddress generate an address for the select account
409 func (m *Manager) createAddress(account *Account, change bool) (cp *CtrlProgram, err error) {
410 if len(account.XPubs) == 1 {
411 cp, err = m.createP2PKH(account, change)
413 cp, err = m.createP2SH(account, change)
418 return cp, m.insertControlPrograms(cp)
421 func (m *Manager) createP2PKH(account *Account, change bool) (*CtrlProgram, error) {
422 idx := m.getNextContractIndex(account.ID)
423 path := signers.Path(account.Signer, signers.AccountKeySpace, idx)
424 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
425 derivedPK := derivedXPubs[0].PublicKey()
426 pubHash := crypto.Ripemd160(derivedPK)
428 address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
433 control, err := vmutil.P2WPKHProgram([]byte(pubHash))
439 AccountID: account.ID,
440 Address: address.EncodeAddress(),
442 ControlProgram: control,
447 func (m *Manager) createP2SH(account *Account, change bool) (*CtrlProgram, error) {
448 idx := m.getNextContractIndex(account.ID)
449 path := signers.Path(account.Signer, signers.AccountKeySpace, idx)
450 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
451 derivedPKs := chainkd.XPubKeys(derivedXPubs)
452 signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
456 scriptHash := crypto.Sha256(signScript)
458 address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
463 control, err := vmutil.P2WSHProgram(scriptHash)
469 AccountID: account.ID,
470 Address: address.EncodeAddress(),
472 ControlProgram: control,
477 func (m *Manager) getNextAccountIndex() uint64 {
479 defer m.accIndexMu.Unlock()
481 var nextIndex uint64 = 1
482 if rawIndexBytes := m.db.Get(accountIndexKey); rawIndexBytes != nil {
483 nextIndex = common.BytesToUnit64(rawIndexBytes) + 1
485 m.db.Set(accountIndexKey, common.Unit64ToBytes(nextIndex))
489 func (m *Manager) getNextContractIndex(accountID string) uint64 {
491 defer m.accIndexMu.Unlock()
493 nextIndex := uint64(1)
494 if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
495 nextIndex = common.BytesToUnit64(rawIndexBytes) + 1
497 m.db.Set(contractIndexKey(accountID), common.Unit64ToBytes(nextIndex))
501 func (m *Manager) getProgramByAddress(address string) ([]byte, error) {
502 addr, err := common.DecodeAddress(address, &consensus.ActiveNetParams)
507 redeemContract := addr.ScriptAddress()
510 case *common.AddressWitnessPubKeyHash:
511 program, err = vmutil.P2WPKHProgram(redeemContract)
512 case *common.AddressWitnessScriptHash:
513 program, err = vmutil.P2WSHProgram(redeemContract)
515 return nil, ErrInvalidAddress
523 func (m *Manager) insertControlPrograms(progs ...*CtrlProgram) error {
525 for _, prog := range progs {
526 accountCP, err := json.Marshal(prog)
531 sha3pool.Sum256(hash[:], prog.ControlProgram)
532 m.db.Set(ContractKey(hash), accountCP)