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/crypto"
18 "github.com/bytom/crypto/ed25519/chainkd"
19 "github.com/bytom/crypto/sha3pool"
20 "github.com/bytom/errors"
21 "github.com/bytom/protocol"
22 "github.com/bytom/protocol/bc"
23 "github.com/bytom/protocol/vm/vmutil"
27 maxAccountCache = 1000
31 accountIndexKey = []byte("AccountIndex")
32 accountPrefix = []byte("Account:")
33 aliasPrefix = []byte("AccountAlias:")
34 contractIndexPrefix = []byte("ContractIndex")
35 contractPrefix = []byte("Contract:")
36 miningAddressKey = []byte("MiningAddress")
39 // pre-define errors for supporting bytom errorFormatter
41 ErrDuplicateAlias = errors.New("duplicate account alias")
42 ErrFindAccount = errors.New("fail to find account")
43 ErrMarshalAccount = errors.New("failed marshal account")
44 ErrInvalidAddress = errors.New("invalid address")
45 ErrFindCtrlProgram = errors.New("fail to find account control program")
48 func aliasKey(name string) []byte {
49 return append(aliasPrefix, []byte(name)...)
52 // Key account store prefix
53 func Key(name string) []byte {
54 return append(accountPrefix, []byte(name)...)
57 // ContractKey account control promgram store prefix
58 func ContractKey(hash common.Hash) []byte {
59 return append(contractPrefix, hash[:]...)
62 func contractIndexKey(accountID string) []byte {
63 return append(contractIndexPrefix, []byte(accountID)...)
66 // Account is structure of Bytom account
70 Alias string `json:"alias"`
73 //CtrlProgram is structure of account control program
74 type CtrlProgram struct {
79 Change bool // Mark whether this control program is for UTXO change
82 // Manager stores accounts and their associated control programs.
86 utxoKeeper *utxoKeeper
92 delayedACPsMu sync.Mutex
93 delayedACPs map[*txbuilder.TemplateBuilder][]*CtrlProgram
99 // NewManager creates a new account manager
100 func NewManager(walletDB dbm.DB, chain *protocol.Chain) *Manager {
104 utxoKeeper: newUtxoKeeper(chain.BestBlockHeight, walletDB),
105 cache: lru.New(maxAccountCache),
106 aliasCache: lru.New(maxAccountCache),
107 delayedACPs: make(map[*txbuilder.TemplateBuilder][]*CtrlProgram),
111 // AddUnconfirmedUtxo add untxo list to utxoKeeper
112 func (m *Manager) AddUnconfirmedUtxo(utxos []*UTXO) {
113 m.utxoKeeper.AddUnconfirmedUtxo(utxos)
116 // Create creates a new Account.
117 func (m *Manager) Create(xpubs []chainkd.XPub, quorum int, alias string) (*Account, error) {
119 defer m.accountMu.Unlock()
121 normalizedAlias := strings.ToLower(strings.TrimSpace(alias))
122 if existed := m.db.Get(aliasKey(normalizedAlias)); existed != nil {
123 return nil, ErrDuplicateAlias
126 signer, err := signers.Create("account", xpubs, quorum, m.getNextAccountIndex())
127 id := signers.IDGenerate()
129 return nil, errors.Wrap(err)
132 account := &Account{Signer: signer, ID: id, Alias: normalizedAlias}
133 rawAccount, err := json.Marshal(account)
135 return nil, ErrMarshalAccount
139 storeBatch := m.db.NewBatch()
140 storeBatch.Set(accountID, rawAccount)
141 storeBatch.Set(aliasKey(normalizedAlias), []byte(id))
146 // CreateAddress generate an address for the select account
147 func (m *Manager) CreateAddress(accountID string, change bool) (cp *CtrlProgram, err error) {
148 account, err := m.FindByID(accountID)
152 return m.createAddress(account, change)
155 // DeleteAccount deletes the account's ID or alias matching accountInfo.
156 func (m *Manager) DeleteAccount(aliasOrID string) (err error) {
157 account := &Account{}
158 if account, err = m.FindByAlias(aliasOrID); err != nil {
159 if account, err = m.FindByID(aliasOrID); err != nil {
165 m.aliasCache.Remove(account.Alias)
168 storeBatch := m.db.NewBatch()
169 storeBatch.Delete(aliasKey(account.Alias))
170 storeBatch.Delete(Key(account.ID))
175 // FindByAlias retrieves an account's Signer record by its alias
176 func (m *Manager) FindByAlias(alias string) (*Account, error) {
178 cachedID, ok := m.aliasCache.Get(alias)
181 return m.FindByID(cachedID.(string))
184 rawID := m.db.Get(aliasKey(alias))
186 return nil, ErrFindAccount
189 accountID := string(rawID)
191 m.aliasCache.Add(alias, accountID)
193 return m.FindByID(accountID)
196 // FindByID returns an account's Signer record by its ID.
197 func (m *Manager) FindByID(id string) (*Account, error) {
199 cachedAccount, ok := m.cache.Get(id)
202 return cachedAccount.(*Account), nil
205 rawAccount := m.db.Get(Key(id))
206 if rawAccount == nil {
207 return nil, ErrFindAccount
210 account := &Account{}
211 if err := json.Unmarshal(rawAccount, account); err != nil {
216 m.cache.Add(id, account)
221 // GetAccountByProgram return Account by given CtrlProgram
222 func (m *Manager) GetAccountByProgram(program *CtrlProgram) (*Account, error) {
223 rawAccount := m.db.Get(Key(program.AccountID))
224 if rawAccount == nil {
225 return nil, ErrFindAccount
228 account := &Account{}
229 return account, json.Unmarshal(rawAccount, account)
232 // GetAliasByID return the account alias by given ID
233 func (m *Manager) GetAliasByID(id string) string {
234 rawAccount := m.db.Get(Key(id))
235 if rawAccount == nil {
236 log.Warn("GetAliasByID fail to find account")
240 account := &Account{}
241 if err := json.Unmarshal(rawAccount, account); err != nil {
247 // GetCoinbaseControlProgram will return a coinbase script
248 func (m *Manager) GetCoinbaseControlProgram() ([]byte, error) {
249 if data := m.db.Get(miningAddressKey); data != nil {
251 return cp.ControlProgram, json.Unmarshal(data, cp)
254 accountIter := m.db.IteratorPrefix([]byte(accountPrefix))
255 defer accountIter.Release()
256 if !accountIter.Next() {
257 log.Warningf("GetCoinbaseControlProgram: can't find any account in db")
258 return vmutil.DefaultCoinbaseProgram()
261 account := &Account{}
262 if err := json.Unmarshal(accountIter.Value(), account); err != nil {
266 program, err := m.createAddress(account, false)
271 rawCP, err := json.Marshal(program)
276 m.db.Set(miningAddressKey, rawCP)
277 return program.ControlProgram, nil
280 // GetContractIndex return the current index
281 func (m *Manager) GetContractIndex(accountID string) uint64 {
283 defer m.accIndexMu.Unlock()
286 if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
287 index = common.BytesToUnit64(rawIndexBytes)
292 // GetProgramByAddress return CtrlProgram by given address
293 func (m *Manager) GetProgramByAddress(address string) (*CtrlProgram, error) {
294 addr, err := common.DecodeAddress(address, &consensus.ActiveNetParams)
299 redeemContract := addr.ScriptAddress()
302 case *common.AddressWitnessPubKeyHash:
303 program, err = vmutil.P2WPKHProgram(redeemContract)
304 case *common.AddressWitnessScriptHash:
305 program, err = vmutil.P2WSHProgram(redeemContract)
307 return nil, ErrInvalidAddress
314 sha3pool.Sum256(hash[:], program)
315 rawProgram := m.db.Get(ContractKey(hash))
316 if rawProgram == nil {
317 return nil, ErrFindCtrlProgram
321 return cp, json.Unmarshal(rawProgram, cp)
324 // IsLocalControlProgram check is the input control program belong to local
325 func (m *Manager) IsLocalControlProgram(prog []byte) bool {
327 sha3pool.Sum256(hash[:], prog)
328 bytes := m.db.Get(ContractKey(hash))
332 // ListAccounts will return the accounts in the db
333 func (m *Manager) ListAccounts(id string) ([]*Account, error) {
334 accounts := []*Account{}
335 accountIter := m.db.IteratorPrefix(Key(strings.TrimSpace(id)))
336 defer accountIter.Release()
338 for accountIter.Next() {
339 account := &Account{}
340 if err := json.Unmarshal(accountIter.Value(), &account); err != nil {
343 accounts = append(accounts, account)
348 // ListControlProgram return all the local control program
349 func (m *Manager) ListControlProgram() ([]*CtrlProgram, error) {
350 cps := []*CtrlProgram{}
351 cpIter := m.db.IteratorPrefix(contractPrefix)
352 defer cpIter.Release()
356 if err := json.Unmarshal(cpIter.Value(), cp); err != nil {
359 cps = append(cps, cp)
364 // RemoveUnconfirmedUtxo remove utxos from the utxoKeeper
365 func (m *Manager) RemoveUnconfirmedUtxo(hashes []*bc.Hash) {
366 m.utxoKeeper.RemoveUnconfirmedUtxo(hashes)
369 // CreateAddress generate an address for the select account
370 func (m *Manager) createAddress(account *Account, change bool) (cp *CtrlProgram, err error) {
371 if len(account.XPubs) == 1 {
372 cp, err = m.createP2PKH(account, change)
374 cp, err = m.createP2SH(account, change)
379 return cp, m.insertControlPrograms(cp)
382 func (m *Manager) createP2PKH(account *Account, change bool) (*CtrlProgram, error) {
383 idx := m.getNextContractIndex(account.ID)
384 path := signers.Path(account.Signer, signers.AccountKeySpace, idx)
385 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
386 derivedPK := derivedXPubs[0].PublicKey()
387 pubHash := crypto.Ripemd160(derivedPK)
389 address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
394 control, err := vmutil.P2WPKHProgram([]byte(pubHash))
400 AccountID: account.ID,
401 Address: address.EncodeAddress(),
403 ControlProgram: control,
408 func (m *Manager) createP2SH(account *Account, change bool) (*CtrlProgram, error) {
409 idx := m.getNextContractIndex(account.ID)
410 path := signers.Path(account.Signer, signers.AccountKeySpace, idx)
411 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
412 derivedPKs := chainkd.XPubKeys(derivedXPubs)
413 signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
417 scriptHash := crypto.Sha256(signScript)
419 address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
424 control, err := vmutil.P2WSHProgram(scriptHash)
430 AccountID: account.ID,
431 Address: address.EncodeAddress(),
433 ControlProgram: control,
438 func (m *Manager) getNextAccountIndex() uint64 {
440 defer m.accIndexMu.Unlock()
442 var nextIndex uint64 = 1
443 if rawIndexBytes := m.db.Get(accountIndexKey); rawIndexBytes != nil {
444 nextIndex = common.BytesToUnit64(rawIndexBytes) + 1
446 m.db.Set(accountIndexKey, common.Unit64ToBytes(nextIndex))
450 func (m *Manager) getNextContractIndex(accountID string) uint64 {
452 defer m.accIndexMu.Unlock()
454 nextIndex := uint64(1)
455 if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
456 nextIndex = common.BytesToUnit64(rawIndexBytes) + 1
458 m.db.Set(contractIndexKey(accountID), common.Unit64ToBytes(nextIndex))
462 func (m *Manager) insertControlPrograms(progs ...*CtrlProgram) error {
464 for _, prog := range progs {
465 accountCP, err := json.Marshal(prog)
470 sha3pool.Sum256(hash[:], prog.ControlProgram)
471 m.db.Set(ContractKey(hash), accountCP)