OSDN Git Service

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