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
33 accountIndexKey = []byte("AccountIndex")
34 accountPrefix = []byte("Account:")
35 aliasPrefix = []byte("AccountAlias:")
36 contractIndexPrefix = []byte("ContractIndex")
37 contractPrefix = []byte("Contract:")
38 miningAddressKey = []byte("MiningAddress")
39 CoinbaseAbKey = []byte("CoinbaseArbitrary")
42 // pre-define errors for supporting bytom errorFormatter
44 ErrDuplicateAlias = errors.New("duplicate account alias")
45 ErrFindAccount = errors.New("fail to find account")
46 ErrMarshalAccount = errors.New("failed marshal account")
47 ErrInvalidAddress = errors.New("invalid address")
48 ErrFindCtrlProgram = errors.New("fail to find account control program")
51 // ContractKey account control promgram store prefix
52 func ContractKey(hash common.Hash) []byte {
53 return append(contractPrefix, hash[:]...)
56 // Key account store prefix
57 func Key(name string) []byte {
58 return append(accountPrefix, []byte(name)...)
61 func aliasKey(name string) []byte {
62 return append(aliasPrefix, []byte(name)...)
65 func contractIndexKey(accountID string) []byte {
66 return append(contractIndexPrefix, []byte(accountID)...)
69 // Account is structure of Bytom account
73 Alias string `json:"alias"`
76 //CtrlProgram is structure of account control program
77 type CtrlProgram struct {
82 Change bool // Mark whether this control program is for UTXO change
85 // Manager stores accounts and their associated control programs.
89 utxoKeeper *utxoKeeper
95 delayedACPsMu sync.Mutex
96 delayedACPs map[*txbuilder.TemplateBuilder][]*CtrlProgram
98 NewAddrCh chan *CtrlProgram
100 accIndexMu sync.Mutex
104 // NewManager creates a new account manager
105 func NewManager(walletDB dbm.DB, chain *protocol.Chain) *Manager {
109 utxoKeeper: newUtxoKeeper(chain.BestBlockHeight, walletDB),
110 cache: lru.New(maxAccountCache),
111 aliasCache: lru.New(maxAccountCache),
112 delayedACPs: make(map[*txbuilder.TemplateBuilder][]*CtrlProgram),
113 NewAddrCh: make(chan *CtrlProgram, maxAddrSize),
117 // AddUnconfirmedUtxo add untxo list to utxoKeeper
118 func (m *Manager) AddUnconfirmedUtxo(utxos []*UTXO) {
119 m.utxoKeeper.AddUnconfirmedUtxo(utxos)
122 // Create creates a new Account.
123 func (m *Manager) Create(xpubs []chainkd.XPub, quorum int, alias string) (*Account, error) {
125 defer m.accountMu.Unlock()
127 normalizedAlias := strings.ToLower(strings.TrimSpace(alias))
128 if existed := m.db.Get(aliasKey(normalizedAlias)); existed != nil {
129 return nil, ErrDuplicateAlias
132 signer, err := signers.Create("account", xpubs, quorum, m.getNextAccountIndex())
133 id := signers.IDGenerate()
135 return nil, errors.Wrap(err)
138 account := &Account{Signer: signer, ID: id, Alias: normalizedAlias}
139 rawAccount, err := json.Marshal(account)
141 return nil, ErrMarshalAccount
145 storeBatch := m.db.NewBatch()
146 storeBatch.Set(accountID, rawAccount)
147 storeBatch.Set(aliasKey(normalizedAlias), []byte(id))
152 // CreateAddress generate an address for the select account
153 func (m *Manager) CreateAddress(accountID string, change bool) (cp *CtrlProgram, err error) {
154 account, err := m.FindByID(accountID)
158 return m.createAddress(account, change)
161 // DeleteAccount deletes the account's ID or alias matching accountInfo.
162 func (m *Manager) DeleteAccount(aliasOrID string) (err error) {
163 account := &Account{}
164 if account, err = m.FindByAlias(aliasOrID); err != nil {
165 if account, err = m.FindByID(aliasOrID); err != nil {
171 m.aliasCache.Remove(account.Alias)
174 storeBatch := m.db.NewBatch()
175 storeBatch.Delete(aliasKey(account.Alias))
176 storeBatch.Delete(Key(account.ID))
181 // FindByAlias retrieves an account's Signer record by its alias
182 func (m *Manager) FindByAlias(alias string) (*Account, error) {
184 cachedID, ok := m.aliasCache.Get(alias)
187 return m.FindByID(cachedID.(string))
190 rawID := m.db.Get(aliasKey(alias))
192 return nil, ErrFindAccount
195 accountID := string(rawID)
197 m.aliasCache.Add(alias, accountID)
199 return m.FindByID(accountID)
202 // FindByID returns an account's Signer record by its ID.
203 func (m *Manager) FindByID(id string) (*Account, error) {
205 cachedAccount, ok := m.cache.Get(id)
208 return cachedAccount.(*Account), nil
211 rawAccount := m.db.Get(Key(id))
212 if rawAccount == nil {
213 return nil, ErrFindAccount
216 account := &Account{}
217 if err := json.Unmarshal(rawAccount, account); err != nil {
222 m.cache.Add(id, account)
227 // GetAccountByProgram return Account by given CtrlProgram
228 func (m *Manager) GetAccountByProgram(program *CtrlProgram) (*Account, error) {
229 rawAccount := m.db.Get(Key(program.AccountID))
230 if rawAccount == nil {
231 return nil, ErrFindAccount
234 account := &Account{}
235 return account, json.Unmarshal(rawAccount, account)
238 // GetAliasByID return the account alias by given ID
239 func (m *Manager) GetAliasByID(id string) string {
240 rawAccount := m.db.Get(Key(id))
241 if rawAccount == nil {
242 log.Warn("GetAliasByID fail to find account")
246 account := &Account{}
247 if err := json.Unmarshal(rawAccount, account); err != nil {
253 func (m *Manager) GetCoinbaseArbitrary() []byte {
254 if arbitrary := m.db.Get(CoinbaseAbKey); arbitrary != nil {
260 // GetCoinbaseControlProgram will return a coinbase script
261 func (m *Manager) GetCoinbaseControlProgram() ([]byte, error) {
262 cp, err := m.GetCoinbaseCtrlProgram()
263 if err == ErrFindAccount {
264 log.Warningf("GetCoinbaseControlProgram: can't find any account in db")
265 return vmutil.DefaultCoinbaseProgram()
270 return cp.ControlProgram, nil
273 // GetCoinbaseCtrlProgram will return the coinbase CtrlProgram
274 func (m *Manager) GetCoinbaseCtrlProgram() (*CtrlProgram, error) {
275 if data := m.db.Get(miningAddressKey); data != nil {
277 return cp, json.Unmarshal(data, cp)
280 accountIter := m.db.IteratorPrefix([]byte(accountPrefix))
281 defer accountIter.Release()
282 if !accountIter.Next() {
283 return nil, ErrFindAccount
286 account := &Account{}
287 if err := json.Unmarshal(accountIter.Value(), account); err != nil {
291 program, err := m.createAddress(account, false)
296 rawCP, err := json.Marshal(program)
301 m.db.Set(miningAddressKey, rawCP)
305 // GetContractIndex return the current index
306 func (m *Manager) GetContractIndex(accountID string) uint64 {
308 defer m.accIndexMu.Unlock()
311 if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
312 index = common.BytesToUnit64(rawIndexBytes)
317 // GetLocalCtrlProgramByAddress return CtrlProgram by given address
318 func (m *Manager) GetLocalCtrlProgramByAddress(address string) (*CtrlProgram, error) {
319 program, err := m.getProgramByAddress(address)
325 sha3pool.Sum256(hash[:], program)
326 rawProgram := m.db.Get(ContractKey(hash))
327 if rawProgram == nil {
328 return nil, ErrFindCtrlProgram
332 return cp, json.Unmarshal(rawProgram, cp)
335 // GetMiningAddress will return the mining address
336 func (m *Manager) GetMiningAddress() (string, error) {
337 cp, err := m.GetCoinbaseCtrlProgram()
341 return cp.Address, nil
344 // IsLocalControlProgram check is the input control program belong to local
345 func (m *Manager) IsLocalControlProgram(prog []byte) bool {
347 sha3pool.Sum256(hash[:], prog)
348 bytes := m.db.Get(ContractKey(hash))
352 // ListAccounts will return the accounts in the db
353 func (m *Manager) ListAccounts(id string) ([]*Account, error) {
354 accounts := []*Account{}
355 accountIter := m.db.IteratorPrefix(Key(strings.TrimSpace(id)))
356 defer accountIter.Release()
358 for accountIter.Next() {
359 account := &Account{}
360 if err := json.Unmarshal(accountIter.Value(), &account); err != nil {
363 accounts = append(accounts, account)
368 // ListControlProgram return all the local control program
369 func (m *Manager) ListControlProgram() ([]*CtrlProgram, error) {
370 cps := []*CtrlProgram{}
371 cpIter := m.db.IteratorPrefix(contractPrefix)
372 defer cpIter.Release()
376 if err := json.Unmarshal(cpIter.Value(), cp); err != nil {
379 cps = append(cps, cp)
384 func (m *Manager) ListUnconfirmedUtxo(isSmartContract bool) []*UTXO {
385 utxos := m.utxoKeeper.ListUnconfirmed()
387 for _, utxo := range utxos {
388 if segwit.IsP2WScript(utxo.ControlProgram) != isSmartContract {
389 result = append(result, utxo)
395 // RemoveUnconfirmedUtxo remove utxos from the utxoKeeper
396 func (m *Manager) RemoveUnconfirmedUtxo(hashes []*bc.Hash) {
397 m.utxoKeeper.RemoveUnconfirmedUtxo(hashes)
400 // SetMiningAddress will set the mining address
401 func (m *Manager) SetMiningAddress(miningAddress string) (string, error) {
402 program, err := m.getProgramByAddress(miningAddress)
408 Address: miningAddress,
409 ControlProgram: program,
411 rawCP, err := json.Marshal(cp)
416 m.db.Set(miningAddressKey, rawCP)
417 return m.GetMiningAddress()
420 func (m *Manager) SetCoinbaseArbitrary(arbitrary []byte) {
421 m.db.Set(CoinbaseAbKey, arbitrary)
424 // CreateAddress generate an address for the select account
425 func (m *Manager) createAddress(account *Account, change bool) (cp *CtrlProgram, err error) {
426 if len(account.XPubs) == 1 {
427 cp, err = m.createP2PKH(account, change)
429 cp, err = m.createP2SH(account, change)
434 if err = m.insertControlPrograms(cp); err != nil {
443 func (m *Manager) createP2PKH(account *Account, change bool) (*CtrlProgram, error) {
444 idx := m.getNextContractIndex(account.ID)
445 path := signers.Path(account.Signer, signers.AccountKeySpace, idx)
446 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
447 derivedPK := derivedXPubs[0].PublicKey()
448 pubHash := crypto.Ripemd160(derivedPK)
450 address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
455 control, err := vmutil.P2WPKHProgram([]byte(pubHash))
461 AccountID: account.ID,
462 Address: address.EncodeAddress(),
464 ControlProgram: control,
469 func (m *Manager) createP2SH(account *Account, change bool) (*CtrlProgram, error) {
470 idx := m.getNextContractIndex(account.ID)
471 path := signers.Path(account.Signer, signers.AccountKeySpace, idx)
472 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
473 derivedPKs := chainkd.XPubKeys(derivedXPubs)
474 signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
478 scriptHash := crypto.Sha256(signScript)
480 address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
485 control, err := vmutil.P2WSHProgram(scriptHash)
491 AccountID: account.ID,
492 Address: address.EncodeAddress(),
494 ControlProgram: control,
499 func (m *Manager) getNextAccountIndex() uint64 {
501 defer m.accIndexMu.Unlock()
503 var nextIndex uint64 = 1
504 if rawIndexBytes := m.db.Get(accountIndexKey); rawIndexBytes != nil {
505 nextIndex = common.BytesToUnit64(rawIndexBytes) + 1
507 m.db.Set(accountIndexKey, common.Unit64ToBytes(nextIndex))
511 func (m *Manager) getNextContractIndex(accountID string) uint64 {
513 defer m.accIndexMu.Unlock()
515 nextIndex := uint64(1)
516 if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
517 nextIndex = common.BytesToUnit64(rawIndexBytes) + 1
519 m.db.Set(contractIndexKey(accountID), common.Unit64ToBytes(nextIndex))
523 func (m *Manager) getProgramByAddress(address string) ([]byte, error) {
524 addr, err := common.DecodeAddress(address, &consensus.ActiveNetParams)
529 redeemContract := addr.ScriptAddress()
532 case *common.AddressWitnessPubKeyHash:
533 program, err = vmutil.P2WPKHProgram(redeemContract)
534 case *common.AddressWitnessScriptHash:
535 program, err = vmutil.P2WSHProgram(redeemContract)
537 return nil, ErrInvalidAddress
545 func (m *Manager) insertControlPrograms(progs ...*CtrlProgram) error {
547 for _, prog := range progs {
548 accountCP, err := json.Marshal(prog)
553 sha3pool.Sum256(hash[:], prog.ControlProgram)
554 m.db.Set(ContractKey(hash), accountCP)