1 // Package account stores and tracks accounts within a Chain Core.
13 "github.com/golang/groupcache/lru"
14 log "github.com/sirupsen/logrus"
15 dbm "github.com/tendermint/tmlibs/db"
17 "github.com/bytom/blockchain/signers"
18 "github.com/bytom/blockchain/txbuilder"
19 "github.com/bytom/common"
20 "github.com/bytom/consensus"
21 "github.com/bytom/crypto"
22 "github.com/bytom/crypto/ed25519/chainkd"
23 "github.com/bytom/crypto/sha3pool"
24 "github.com/bytom/errors"
25 "github.com/bytom/protocol"
26 "github.com/bytom/protocol/vm/vmutil"
30 maxAccountCache = 1000
32 accountPrefix = "ACC:"
33 accountCPPrefix = "ACP:"
34 indexPrefix = "ACIDX:"
37 var 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 ErrMarshalTags = errors.New("failed marshal account to update tags")
47 func aliasKey(name string) []byte {
48 return []byte(aliasPrefix + name)
51 func indexKeys(xpubs []chainkd.XPub) []byte {
52 xpubStrings := make([]string, len(xpubs))
53 for i, xpub := range xpubs {
54 xpubStrings[i] = xpub.String()
56 sort.Strings(xpubStrings)
57 suffix := strings.Join(xpubStrings, "")
59 return []byte(indexPrefix + suffix)
62 //Key account store prefix
63 func Key(name string) []byte {
64 return []byte(accountPrefix + name)
67 //CPKey account control promgram store prefix
68 func CPKey(hash common.Hash) []byte {
69 return append([]byte(accountCPPrefix), hash[:]...)
72 func convertUnit64ToBytes(nextIndex uint64) []byte {
73 buf := make([]byte, 8)
74 binary.PutUvarint(buf, nextIndex)
78 func convertBytesToUint64(rawIndex []byte) uint64 {
79 result, _ := binary.Uvarint(rawIndex)
83 // NewManager creates a new account manager
84 func NewManager(walletDB dbm.DB, chain *protocol.Chain) *Manager {
88 utxoDB: newReserver(chain, walletDB),
89 cache: lru.New(maxAccountCache),
90 aliasCache: lru.New(maxAccountCache),
91 delayedACPs: make(map[*txbuilder.TemplateBuilder][]*CtrlProgram),
95 // Manager stores accounts and their associated control programs.
103 aliasCache *lru.Cache
105 delayedACPsMu sync.Mutex
106 delayedACPs map[*txbuilder.TemplateBuilder][]*CtrlProgram
108 accIndexMu sync.Mutex
111 // ExpireReservations removes reservations that have expired periodically.
112 // It blocks until the context is canceled.
113 func (m *Manager) ExpireReservations(ctx context.Context, period time.Duration) {
114 ticks := time.Tick(period)
118 log.Info("Deposed, ExpireReservations exiting")
121 err := m.utxoDB.ExpireReservations(ctx)
123 log.WithField("error", err).Error("Expire reservations")
129 // Account is structure of Bytom account
130 type Account struct {
134 Tags map[string]interface{}
137 func (m *Manager) getNextXpubsIndex(xpubs []chainkd.XPub) uint64 {
139 defer m.accIndexMu.Unlock()
141 var nextIndex uint64 = 1
142 if rawIndexBytes := m.db.Get(indexKeys(xpubs)); rawIndexBytes != nil {
143 nextIndex = convertBytesToUint64(rawIndexBytes) + 1
146 m.db.Set(indexKeys(xpubs), convertUnit64ToBytes(nextIndex))
151 // Create creates a new Account.
152 func (m *Manager) Create(ctx context.Context, xpubs []chainkd.XPub, quorum int, alias string, tags map[string]interface{}) (*Account, error) {
153 normalizedAlias := strings.ToLower(strings.TrimSpace(alias))
154 if existed := m.db.Get(aliasKey(normalizedAlias)); existed != nil {
155 return nil, ErrDuplicateAlias
158 signer, err := signers.Create("account", xpubs, quorum, m.getNextXpubsIndex(xpubs))
159 id := signers.IDGenerate()
161 return nil, errors.Wrap(err)
164 account := &Account{Signer: signer, ID: id, Alias: normalizedAlias, Tags: tags}
165 rawAccount, err := json.Marshal(account)
167 return nil, ErrMarshalAccount
169 storeBatch := m.db.NewBatch()
172 storeBatch.Set(accountID, rawAccount)
173 storeBatch.Set(aliasKey(alias), []byte(id))
179 // UpdateTags modifies the tags of the specified account. The account may be
180 // identified either by ID or Alias, but not both.
181 func (m *Manager) UpdateTags(ctx context.Context, accountInfo string, tags map[string]interface{}) (err error) {
182 account := &Account{}
183 if account, err = m.FindByAlias(nil, accountInfo); err != nil {
184 if account, err = m.findByID(ctx, accountInfo); err != nil {
190 rawAccount, err := json.Marshal(account)
192 return ErrMarshalTags
195 m.db.Set(Key(account.ID), rawAccount)
197 m.cache.Add(account.ID, account)
202 // FindByAlias retrieves an account's Signer record by its alias
203 func (m *Manager) FindByAlias(ctx context.Context, alias string) (*Account, error) {
205 cachedID, ok := m.aliasCache.Get(alias)
208 return m.findByID(ctx, cachedID.(string))
211 rawID := m.db.Get(aliasKey(alias))
213 return nil, ErrFindAccount
216 accountID := string(rawID)
218 m.aliasCache.Add(alias, accountID)
220 return m.findByID(ctx, accountID)
223 // findByID returns an account's Signer record by its ID.
224 func (m *Manager) findByID(ctx context.Context, id string) (*Account, error) {
226 cachedAccount, ok := m.cache.Get(id)
229 return cachedAccount.(*Account), nil
232 rawAccount := m.db.Get(Key(id))
233 if rawAccount == nil {
234 return nil, ErrFindAccount
237 account := &Account{}
238 if err := json.Unmarshal(rawAccount, account); err != nil {
243 m.cache.Add(id, account)
248 // GetAliasByID return the account alias by given ID
249 func (m *Manager) GetAliasByID(id string) string {
250 account := &Account{}
252 rawAccount := m.db.Get(Key(id))
253 if rawAccount == nil {
254 log.Warn("fail to find account")
258 if err := json.Unmarshal(rawAccount, account); err != nil {
265 // CreateAddressForChange generate an address for the UTXO change
266 func (m *Manager) CreateCtrlProgramForChange(ctx context.Context, accountID string) (cp *CtrlProgram, err error) {
267 return m.CreateAddress(ctx, accountID, true)
270 // CreateAddress generate an address for the select account
271 func (m *Manager) CreateAddress(ctx context.Context, accountID string, change bool) (cp *CtrlProgram, err error) {
272 account, err := m.findByID(ctx, accountID)
276 return m.createAddress(ctx, account, change)
279 // CreateAddress generate an address for the select account
280 func (m *Manager) createAddress(ctx context.Context, account *Account, change bool) (cp *CtrlProgram, err error) {
281 if len(account.XPubs) == 1 {
282 cp, err = m.createP2PKH(ctx, account, change)
284 cp, err = m.createP2SH(ctx, account, change)
290 if err = m.insertAccountControlProgram(ctx, cp); err != nil {
296 func (m *Manager) createP2PKH(ctx context.Context, account *Account, change bool) (*CtrlProgram, error) {
297 idx := m.getNextXpubsIndex(account.Signer.XPubs)
298 path := signers.Path(account.Signer, signers.AccountKeySpace, idx)
299 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
300 derivedPK := derivedXPubs[0].PublicKey()
301 pubHash := crypto.Ripemd160(derivedPK)
303 // TODO: pass different params due to config
304 address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.MainNetParams)
309 control, err := vmutil.P2WPKHProgram([]byte(pubHash))
315 AccountID: account.ID,
316 Address: address.EncodeAddress(),
318 ControlProgram: control,
323 func (m *Manager) createP2SH(ctx context.Context, account *Account, change bool) (*CtrlProgram, error) {
324 idx := m.getNextXpubsIndex(account.Signer.XPubs)
325 path := signers.Path(account.Signer, signers.AccountKeySpace, idx)
326 derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
327 derivedPKs := chainkd.XPubKeys(derivedXPubs)
328 signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
332 scriptHash := crypto.Sha256(signScript)
334 // TODO: pass different params due to config
335 address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.MainNetParams)
340 control, err := vmutil.P2WSHProgram(scriptHash)
346 AccountID: account.ID,
347 Address: address.EncodeAddress(),
349 ControlProgram: control,
354 //CtrlProgram is structure of account control program
355 type CtrlProgram struct {
359 ControlProgram []byte
360 Change bool // Mark whether this control program is for UTXO change
363 func (m *Manager) insertAccountControlProgram(ctx context.Context, progs ...*CtrlProgram) error {
365 for _, prog := range progs {
366 accountCP, err := json.Marshal(prog)
371 sha3pool.Sum256(hash[:], prog.ControlProgram)
372 m.db.Set(CPKey(hash), accountCP)
377 // IsLocalControlProgram check is the input control program belong to local
378 func (m *Manager) IsLocalControlProgram(prog []byte) bool {
380 sha3pool.Sum256(hash[:], prog)
381 bytes := m.db.Get(CPKey(hash))
385 // GetCoinbaseControlProgram will return a coinbase script
386 func (m *Manager) GetCoinbaseControlProgram() ([]byte, error) {
387 if data := m.db.Get(miningAddressKey); data != nil {
389 return cp.ControlProgram, json.Unmarshal(data, cp)
392 accountIter := m.db.IteratorPrefix([]byte(accountPrefix))
393 defer accountIter.Release()
394 if !accountIter.Next() {
395 log.Warningf("GetCoinbaseControlProgram: can't find any account in db")
396 return vmutil.DefaultCoinbaseProgram()
399 account := &Account{}
400 if err := json.Unmarshal(accountIter.Value(), account); err != nil {
404 program, err := m.createAddress(nil, account, false)
409 rawCP, err := json.Marshal(program)
414 m.db.Set(miningAddressKey, rawCP)
415 return program.ControlProgram, nil
418 // DeleteAccount deletes the account's ID or alias matching accountInfo.
419 func (m *Manager) DeleteAccount(in struct {
420 AccountInfo string `json:"account_info"`
422 account := &Account{}
423 if account, err = m.FindByAlias(nil, in.AccountInfo); err != nil {
424 if account, err = m.findByID(nil, in.AccountInfo); err != nil {
429 storeBatch := m.db.NewBatch()
432 m.aliasCache.Remove(account.Alias)
435 storeBatch.Delete(aliasKey(account.Alias))
436 storeBatch.Delete(Key(account.ID))
442 // ListAccounts will return the accounts in the db
443 func (m *Manager) ListAccounts(id string) ([]*Account, error) {
444 accounts := []*Account{}
445 accountIter := m.db.IteratorPrefix([]byte(accountPrefix + id))
446 defer accountIter.Release()
448 for accountIter.Next() {
449 account := &Account{}
450 if err := json.Unmarshal(accountIter.Value(), &account); err != nil {
453 accounts = append(accounts, account)
459 // ListControlProgram return all the local control program
460 func (m *Manager) ListControlProgram() ([]*CtrlProgram, error) {
461 var cps []*CtrlProgram
462 cpIter := m.db.IteratorPrefix([]byte(accountCPPrefix))
463 defer cpIter.Release()
467 if err := json.Unmarshal(cpIter.Value(), cp); err != nil {
470 cps = append(cps, cp)