OSDN Git Service

Fix pool bug (#1254)
[bytom/bytom.git] / account / accounts.go
1 // Package account stores and tracks accounts within a Bytom Core.
2 package account
3
4 import (
5         "encoding/json"
6         "strings"
7         "sync"
8
9         "github.com/golang/groupcache/lru"
10         log "github.com/sirupsen/logrus"
11         dbm "github.com/tendermint/tmlibs/db"
12
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"
25 )
26
27 const (
28         maxAccountCache = 1000
29 )
30
31 var (
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")
39 )
40
41 // pre-define errors for supporting bytom errorFormatter
42 var (
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")
48 )
49
50 // ContractKey account control promgram store prefix
51 func ContractKey(hash common.Hash) []byte {
52         return append(contractPrefix, hash[:]...)
53 }
54
55 // Key account store prefix
56 func Key(name string) []byte {
57         return append(accountPrefix, []byte(name)...)
58 }
59
60 func aliasKey(name string) []byte {
61         return append(aliasPrefix, []byte(name)...)
62 }
63
64 func contractIndexKey(accountID string) []byte {
65         return append(contractIndexPrefix, []byte(accountID)...)
66 }
67
68 // Account is structure of Bytom account
69 type Account struct {
70         *signers.Signer
71         ID    string `json:"id"`
72         Alias string `json:"alias"`
73 }
74
75 //CtrlProgram is structure of account control program
76 type CtrlProgram struct {
77         AccountID      string
78         Address        string
79         KeyIndex       uint64
80         ControlProgram []byte
81         Change         bool // Mark whether this control program is for UTXO change
82 }
83
84 // Manager stores accounts and their associated control programs.
85 type Manager struct {
86         db         dbm.DB
87         chain      *protocol.Chain
88         utxoKeeper *utxoKeeper
89
90         cacheMu    sync.Mutex
91         cache      *lru.Cache
92         aliasCache *lru.Cache
93
94         delayedACPsMu sync.Mutex
95         delayedACPs   map[*txbuilder.TemplateBuilder][]*CtrlProgram
96
97         accIndexMu sync.Mutex
98         accountMu  sync.Mutex
99 }
100
101 // NewManager creates a new account manager
102 func NewManager(walletDB dbm.DB, chain *protocol.Chain) *Manager {
103         return &Manager{
104                 db:          walletDB,
105                 chain:       chain,
106                 utxoKeeper:  newUtxoKeeper(chain.BestBlockHeight, walletDB),
107                 cache:       lru.New(maxAccountCache),
108                 aliasCache:  lru.New(maxAccountCache),
109                 delayedACPs: make(map[*txbuilder.TemplateBuilder][]*CtrlProgram),
110         }
111 }
112
113 // AddUnconfirmedUtxo add untxo list to utxoKeeper
114 func (m *Manager) AddUnconfirmedUtxo(utxos []*UTXO) {
115         m.utxoKeeper.AddUnconfirmedUtxo(utxos)
116 }
117
118 // Create creates a new Account.
119 func (m *Manager) Create(xpubs []chainkd.XPub, quorum int, alias string) (*Account, error) {
120         m.accountMu.Lock()
121         defer m.accountMu.Unlock()
122
123         normalizedAlias := strings.ToLower(strings.TrimSpace(alias))
124         if existed := m.db.Get(aliasKey(normalizedAlias)); existed != nil {
125                 return nil, ErrDuplicateAlias
126         }
127
128         signer, err := signers.Create("account", xpubs, quorum, m.getNextAccountIndex())
129         id := signers.IDGenerate()
130         if err != nil {
131                 return nil, errors.Wrap(err)
132         }
133
134         account := &Account{Signer: signer, ID: id, Alias: normalizedAlias}
135         rawAccount, err := json.Marshal(account)
136         if err != nil {
137                 return nil, ErrMarshalAccount
138         }
139
140         accountID := Key(id)
141         storeBatch := m.db.NewBatch()
142         storeBatch.Set(accountID, rawAccount)
143         storeBatch.Set(aliasKey(normalizedAlias), []byte(id))
144         storeBatch.Write()
145         return account, nil
146 }
147
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)
151         if err != nil {
152                 return nil, err
153         }
154         return m.createAddress(account, change)
155 }
156
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 {
162                         return err
163                 }
164         }
165
166         m.cacheMu.Lock()
167         m.aliasCache.Remove(account.Alias)
168         m.cacheMu.Unlock()
169
170         storeBatch := m.db.NewBatch()
171         storeBatch.Delete(aliasKey(account.Alias))
172         storeBatch.Delete(Key(account.ID))
173         storeBatch.Write()
174         return nil
175 }
176
177 // FindByAlias retrieves an account's Signer record by its alias
178 func (m *Manager) FindByAlias(alias string) (*Account, error) {
179         m.cacheMu.Lock()
180         cachedID, ok := m.aliasCache.Get(alias)
181         m.cacheMu.Unlock()
182         if ok {
183                 return m.FindByID(cachedID.(string))
184         }
185
186         rawID := m.db.Get(aliasKey(alias))
187         if rawID == nil {
188                 return nil, ErrFindAccount
189         }
190
191         accountID := string(rawID)
192         m.cacheMu.Lock()
193         m.aliasCache.Add(alias, accountID)
194         m.cacheMu.Unlock()
195         return m.FindByID(accountID)
196 }
197
198 // FindByID returns an account's Signer record by its ID.
199 func (m *Manager) FindByID(id string) (*Account, error) {
200         m.cacheMu.Lock()
201         cachedAccount, ok := m.cache.Get(id)
202         m.cacheMu.Unlock()
203         if ok {
204                 return cachedAccount.(*Account), nil
205         }
206
207         rawAccount := m.db.Get(Key(id))
208         if rawAccount == nil {
209                 return nil, ErrFindAccount
210         }
211
212         account := &Account{}
213         if err := json.Unmarshal(rawAccount, account); err != nil {
214                 return nil, err
215         }
216
217         m.cacheMu.Lock()
218         m.cache.Add(id, account)
219         m.cacheMu.Unlock()
220         return account, nil
221 }
222
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
228         }
229
230         account := &Account{}
231         return account, json.Unmarshal(rawAccount, account)
232 }
233
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")
239                 return ""
240         }
241
242         account := &Account{}
243         if err := json.Unmarshal(rawAccount, account); err != nil {
244                 log.Warn(err)
245         }
246         return account.Alias
247 }
248
249 func (m *Manager) GetCoinbaseArbitrary() []byte {
250         if arbitrary := m.db.Get(CoinbaseAbKey); arbitrary != nil {
251                 return arbitrary
252         }
253         return []byte{}
254 }
255
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()
262         }
263         if err != nil {
264                 return nil, err
265         }
266         return cp.ControlProgram, nil
267 }
268
269 // GetCoinbaseCtrlProgram will return the coinbase CtrlProgram
270 func (m *Manager) GetCoinbaseCtrlProgram() (*CtrlProgram, error) {
271         if data := m.db.Get(miningAddressKey); data != nil {
272                 cp := &CtrlProgram{}
273                 return cp, json.Unmarshal(data, cp)
274         }
275
276         accountIter := m.db.IteratorPrefix([]byte(accountPrefix))
277         defer accountIter.Release()
278         if !accountIter.Next() {
279                 return nil, ErrFindAccount
280         }
281
282         account := &Account{}
283         if err := json.Unmarshal(accountIter.Value(), account); err != nil {
284                 return nil, err
285         }
286
287         program, err := m.createAddress(account, false)
288         if err != nil {
289                 return nil, err
290         }
291
292         rawCP, err := json.Marshal(program)
293         if err != nil {
294                 return nil, err
295         }
296
297         m.db.Set(miningAddressKey, rawCP)
298         return program, nil
299 }
300
301 // GetContractIndex return the current index
302 func (m *Manager) GetContractIndex(accountID string) uint64 {
303         m.accIndexMu.Lock()
304         defer m.accIndexMu.Unlock()
305
306         index := uint64(1)
307         if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
308                 index = common.BytesToUnit64(rawIndexBytes)
309         }
310         return index
311 }
312
313 // GetLocalCtrlProgramByAddress return CtrlProgram by given address
314 func (m *Manager) GetLocalCtrlProgramByAddress(address string) (*CtrlProgram, error) {
315         program, err := m.getProgramByAddress(address)
316         if err != nil {
317                 return nil, err
318         }
319
320         var hash [32]byte
321         sha3pool.Sum256(hash[:], program)
322         rawProgram := m.db.Get(ContractKey(hash))
323         if rawProgram == nil {
324                 return nil, ErrFindCtrlProgram
325         }
326
327         cp := &CtrlProgram{}
328         return cp, json.Unmarshal(rawProgram, cp)
329 }
330
331 // GetMiningAddress will return the mining address
332 func (m *Manager) GetMiningAddress() (string, error) {
333         cp, err := m.GetCoinbaseCtrlProgram()
334         if err != nil {
335                 return "", err
336         }
337         return cp.Address, nil
338 }
339
340 // IsLocalControlProgram check is the input control program belong to local
341 func (m *Manager) IsLocalControlProgram(prog []byte) bool {
342         var hash common.Hash
343         sha3pool.Sum256(hash[:], prog)
344         bytes := m.db.Get(ContractKey(hash))
345         return bytes != nil
346 }
347
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()
353
354         for accountIter.Next() {
355                 account := &Account{}
356                 if err := json.Unmarshal(accountIter.Value(), &account); err != nil {
357                         return nil, err
358                 }
359                 accounts = append(accounts, account)
360         }
361         return accounts, nil
362 }
363
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()
369
370         for cpIter.Next() {
371                 cp := &CtrlProgram{}
372                 if err := json.Unmarshal(cpIter.Value(), cp); err != nil {
373                         return nil, err
374                 }
375                 cps = append(cps, cp)
376         }
377         return cps, nil
378 }
379
380 func (m *Manager) ListUnconfirmedUtxo(isSmartContract bool) []*UTXO {
381         utxos := m.utxoKeeper.ListUnconfirmed()
382         result := []*UTXO{}
383         for _, utxo := range utxos {
384                 if segwit.IsP2WScript(utxo.ControlProgram) != isSmartContract {
385                         result = append(result, utxo)
386                 }
387         }
388         return result
389 }
390
391 // RemoveUnconfirmedUtxo remove utxos from the utxoKeeper
392 func (m *Manager) RemoveUnconfirmedUtxo(hashes []*bc.Hash) {
393         m.utxoKeeper.RemoveUnconfirmedUtxo(hashes)
394 }
395
396 // SetMiningAddress will set the mining address
397 func (m *Manager) SetMiningAddress(miningAddress string) (string, error) {
398         program, err := m.getProgramByAddress(miningAddress)
399         if err != nil {
400                 return "", err
401         }
402
403         cp := &CtrlProgram{
404                 Address:        miningAddress,
405                 ControlProgram: program,
406         }
407         rawCP, err := json.Marshal(cp)
408         if err != nil {
409                 return "", err
410         }
411
412         m.db.Set(miningAddressKey, rawCP)
413         return m.GetMiningAddress()
414 }
415
416 func (m *Manager) SetCoinbaseArbitrary(arbitrary []byte) {
417         m.db.Set(CoinbaseAbKey, arbitrary)
418 }
419
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)
424         } else {
425                 cp, err = m.createP2SH(account, change)
426         }
427         if err != nil {
428                 return nil, err
429         }
430         return cp, m.insertControlPrograms(cp)
431 }
432
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)
439
440         address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
441         if err != nil {
442                 return nil, err
443         }
444
445         control, err := vmutil.P2WPKHProgram([]byte(pubHash))
446         if err != nil {
447                 return nil, err
448         }
449
450         return &CtrlProgram{
451                 AccountID:      account.ID,
452                 Address:        address.EncodeAddress(),
453                 KeyIndex:       idx,
454                 ControlProgram: control,
455                 Change:         change,
456         }, nil
457 }
458
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)
465         if err != nil {
466                 return nil, err
467         }
468         scriptHash := crypto.Sha256(signScript)
469
470         address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
471         if err != nil {
472                 return nil, err
473         }
474
475         control, err := vmutil.P2WSHProgram(scriptHash)
476         if err != nil {
477                 return nil, err
478         }
479
480         return &CtrlProgram{
481                 AccountID:      account.ID,
482                 Address:        address.EncodeAddress(),
483                 KeyIndex:       idx,
484                 ControlProgram: control,
485                 Change:         change,
486         }, nil
487 }
488
489 func (m *Manager) getNextAccountIndex() uint64 {
490         m.accIndexMu.Lock()
491         defer m.accIndexMu.Unlock()
492
493         var nextIndex uint64 = 1
494         if rawIndexBytes := m.db.Get(accountIndexKey); rawIndexBytes != nil {
495                 nextIndex = common.BytesToUnit64(rawIndexBytes) + 1
496         }
497         m.db.Set(accountIndexKey, common.Unit64ToBytes(nextIndex))
498         return nextIndex
499 }
500
501 func (m *Manager) getNextContractIndex(accountID string) uint64 {
502         m.accIndexMu.Lock()
503         defer m.accIndexMu.Unlock()
504
505         nextIndex := uint64(1)
506         if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
507                 nextIndex = common.BytesToUnit64(rawIndexBytes) + 1
508         }
509         m.db.Set(contractIndexKey(accountID), common.Unit64ToBytes(nextIndex))
510         return nextIndex
511 }
512
513 func (m *Manager) getProgramByAddress(address string) ([]byte, error) {
514         addr, err := common.DecodeAddress(address, &consensus.ActiveNetParams)
515         if err != nil {
516                 return nil, err
517         }
518
519         redeemContract := addr.ScriptAddress()
520         program := []byte{}
521         switch addr.(type) {
522         case *common.AddressWitnessPubKeyHash:
523                 program, err = vmutil.P2WPKHProgram(redeemContract)
524         case *common.AddressWitnessScriptHash:
525                 program, err = vmutil.P2WSHProgram(redeemContract)
526         default:
527                 return nil, ErrInvalidAddress
528         }
529         if err != nil {
530                 return nil, err
531         }
532         return program, nil
533 }
534
535 func (m *Manager) insertControlPrograms(progs ...*CtrlProgram) error {
536         var hash common.Hash
537         for _, prog := range progs {
538                 accountCP, err := json.Marshal(prog)
539                 if err != nil {
540                         return err
541                 }
542
543                 sha3pool.Sum256(hash[:], prog.ControlProgram)
544                 m.db.Set(ContractKey(hash), accountCP)
545         }
546         return nil
547 }