OSDN Git Service

Merge branch 'dev' into dev-verify
[bytom/bytom.git] / account / accounts.go
1 // Package account stores and tracks accounts within a Bytom Core.
2 package account
3
4 import (
5         "context"
6         "encoding/json"
7         "strings"
8         "sync"
9         "time"
10
11         "github.com/golang/groupcache/lru"
12         log "github.com/sirupsen/logrus"
13         dbm "github.com/tendermint/tmlibs/db"
14
15         "github.com/bytom/blockchain/signers"
16         "github.com/bytom/blockchain/txbuilder"
17         "github.com/bytom/common"
18         "github.com/bytom/consensus"
19         "github.com/bytom/crypto"
20         "github.com/bytom/crypto/ed25519/chainkd"
21         "github.com/bytom/crypto/sha3pool"
22         "github.com/bytom/errors"
23         "github.com/bytom/protocol"
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 func aliasKey(name string) []byte {
50         return append(aliasPrefix, []byte(name)...)
51 }
52
53 // Key account store prefix
54 func Key(name string) []byte {
55         return append(accountPrefix, []byte(name)...)
56 }
57
58 // ContractKey account control promgram store prefix
59 func ContractKey(hash common.Hash) []byte {
60         return append(contractPrefix, hash[:]...)
61 }
62
63 func contractIndexKey(accountID string) []byte {
64         return append(contractIndexPrefix, []byte(accountID)...)
65 }
66
67 // NewManager creates a new account manager
68 func NewManager(walletDB dbm.DB, chain *protocol.Chain) *Manager {
69         return &Manager{
70                 db:          walletDB,
71                 chain:       chain,
72                 utxoDB:      newReserver(chain, walletDB),
73                 cache:       lru.New(maxAccountCache),
74                 aliasCache:  lru.New(maxAccountCache),
75                 delayedACPs: make(map[*txbuilder.TemplateBuilder][]*CtrlProgram),
76         }
77 }
78
79 // Manager stores accounts and their associated control programs.
80 type Manager struct {
81         db     dbm.DB
82         chain  *protocol.Chain
83         utxoDB *reserver
84
85         cacheMu    sync.Mutex
86         cache      *lru.Cache
87         aliasCache *lru.Cache
88
89         delayedACPsMu sync.Mutex
90         delayedACPs   map[*txbuilder.TemplateBuilder][]*CtrlProgram
91
92         accIndexMu sync.Mutex
93         accountMu  sync.Mutex
94 }
95
96 // ExpireReservations removes reservations that have expired periodically.
97 // It blocks until the context is canceled.
98 func (m *Manager) ExpireReservations(ctx context.Context, period time.Duration) {
99         ticks := time.Tick(period)
100         for {
101                 select {
102                 case <-ctx.Done():
103                         log.Info("Deposed, ExpireReservations exiting")
104                         return
105                 case <-ticks:
106                         err := m.utxoDB.ExpireReservations(ctx)
107                         if err != nil {
108                                 log.WithField("error", err).Error("Expire reservations")
109                         }
110                 }
111         }
112 }
113
114 // Account is structure of Bytom account
115 type Account struct {
116         *signers.Signer
117         ID    string `json:"id"`
118         Alias string `json:"alias"`
119 }
120
121 func (m *Manager) getNextAccountIndex() uint64 {
122         m.accIndexMu.Lock()
123         defer m.accIndexMu.Unlock()
124
125         var nextIndex uint64 = 1
126         if rawIndexBytes := m.db.Get(accountIndexKey); rawIndexBytes != nil {
127                 nextIndex = common.BytesToUnit64(rawIndexBytes) + 1
128         }
129
130         m.db.Set(accountIndexKey, common.Unit64ToBytes(nextIndex))
131         return nextIndex
132 }
133
134 func (m *Manager) getNextContractIndex(accountID string) uint64 {
135         m.accIndexMu.Lock()
136         defer m.accIndexMu.Unlock()
137
138         nextIndex := uint64(1)
139         if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
140                 nextIndex = common.BytesToUnit64(rawIndexBytes) + 1
141         }
142
143         m.db.Set(contractIndexKey(accountID), common.Unit64ToBytes(nextIndex))
144         return nextIndex
145 }
146
147 // Create creates a new Account.
148 func (m *Manager) Create(ctx context.Context, xpubs []chainkd.XPub, quorum int, alias string) (*Account, error) {
149         m.accountMu.Lock()
150         defer m.accountMu.Unlock()
151
152         normalizedAlias := strings.ToLower(strings.TrimSpace(alias))
153         if existed := m.db.Get(aliasKey(normalizedAlias)); existed != nil {
154                 return nil, ErrDuplicateAlias
155         }
156
157         signer, err := signers.Create("account", xpubs, quorum, m.getNextAccountIndex())
158         id := signers.IDGenerate()
159         if err != nil {
160                 return nil, errors.Wrap(err)
161         }
162
163         account := &Account{Signer: signer, ID: id, Alias: normalizedAlias}
164         rawAccount, err := json.Marshal(account)
165         if err != nil {
166                 return nil, ErrMarshalAccount
167         }
168         storeBatch := m.db.NewBatch()
169
170         accountID := Key(id)
171         storeBatch.Set(accountID, rawAccount)
172         storeBatch.Set(aliasKey(normalizedAlias), []byte(id))
173         storeBatch.Write()
174
175         return account, nil
176 }
177
178 // FindByAlias retrieves an account's Signer record by its alias
179 func (m *Manager) FindByAlias(ctx context.Context, alias string) (*Account, error) {
180         m.cacheMu.Lock()
181         cachedID, ok := m.aliasCache.Get(alias)
182         m.cacheMu.Unlock()
183         if ok {
184                 return m.FindByID(ctx, cachedID.(string))
185         }
186
187         rawID := m.db.Get(aliasKey(alias))
188         if rawID == nil {
189                 return nil, ErrFindAccount
190         }
191
192         accountID := string(rawID)
193         m.cacheMu.Lock()
194         m.aliasCache.Add(alias, accountID)
195         m.cacheMu.Unlock()
196         return m.FindByID(ctx, accountID)
197 }
198
199 // FindByID returns an account's Signer record by its ID.
200 func (m *Manager) FindByID(ctx context.Context, id string) (*Account, error) {
201         m.cacheMu.Lock()
202         cachedAccount, ok := m.cache.Get(id)
203         m.cacheMu.Unlock()
204         if ok {
205                 return cachedAccount.(*Account), nil
206         }
207
208         rawAccount := m.db.Get(Key(id))
209         if rawAccount == nil {
210                 return nil, ErrFindAccount
211         }
212
213         account := &Account{}
214         if err := json.Unmarshal(rawAccount, account); err != nil {
215                 return nil, err
216         }
217
218         m.cacheMu.Lock()
219         m.cache.Add(id, account)
220         m.cacheMu.Unlock()
221         return account, nil
222 }
223
224 // GetAliasByID return the account alias by given ID
225 func (m *Manager) GetAliasByID(id string) string {
226         account := &Account{}
227
228         rawAccount := m.db.Get(Key(id))
229         if rawAccount == nil {
230                 log.Warn("fail to find account")
231                 return ""
232         }
233
234         if err := json.Unmarshal(rawAccount, account); err != nil {
235                 log.Warn(err)
236                 return ""
237         }
238         return account.Alias
239 }
240
241 // CreateAddress generate an address for the select account
242 func (m *Manager) CreateAddress(ctx context.Context, accountID string, change bool) (cp *CtrlProgram, err error) {
243         account, err := m.FindByID(ctx, accountID)
244         if err != nil {
245                 return nil, err
246         }
247         return m.createAddress(ctx, account, change)
248 }
249
250 // CreateAddress generate an address for the select account
251 func (m *Manager) createAddress(ctx context.Context, account *Account, change bool) (cp *CtrlProgram, err error) {
252         if len(account.XPubs) == 1 {
253                 cp, err = m.createP2PKH(ctx, account, change)
254         } else {
255                 cp, err = m.createP2SH(ctx, account, change)
256         }
257         if err != nil {
258                 return nil, err
259         }
260
261         if err = m.insertAccountControlProgram(ctx, cp); err != nil {
262                 return nil, err
263         }
264         return cp, nil
265 }
266
267 func (m *Manager) createP2PKH(ctx context.Context, account *Account, change bool) (*CtrlProgram, error) {
268         idx := m.getNextContractIndex(account.ID)
269         path := signers.Path(account.Signer, signers.AccountKeySpace, idx)
270         derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
271         derivedPK := derivedXPubs[0].PublicKey()
272         pubHash := crypto.Ripemd160(derivedPK)
273
274         // TODO: pass different params due to config
275         address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
276         if err != nil {
277                 return nil, err
278         }
279
280         control, err := vmutil.P2WPKHProgram([]byte(pubHash))
281         if err != nil {
282                 return nil, err
283         }
284
285         return &CtrlProgram{
286                 AccountID:      account.ID,
287                 Address:        address.EncodeAddress(),
288                 KeyIndex:       idx,
289                 ControlProgram: control,
290                 Change:         change,
291         }, nil
292 }
293
294 func (m *Manager) createP2SH(ctx context.Context, account *Account, change bool) (*CtrlProgram, error) {
295         idx := m.getNextContractIndex(account.ID)
296         path := signers.Path(account.Signer, signers.AccountKeySpace, idx)
297         derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
298         derivedPKs := chainkd.XPubKeys(derivedXPubs)
299         signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
300         if err != nil {
301                 return nil, err
302         }
303         scriptHash := crypto.Sha256(signScript)
304
305         // TODO: pass different params due to config
306         address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
307         if err != nil {
308                 return nil, err
309         }
310
311         control, err := vmutil.P2WSHProgram(scriptHash)
312         if err != nil {
313                 return nil, err
314         }
315
316         return &CtrlProgram{
317                 AccountID:      account.ID,
318                 Address:        address.EncodeAddress(),
319                 KeyIndex:       idx,
320                 ControlProgram: control,
321                 Change:         change,
322         }, nil
323 }
324
325 //CtrlProgram is structure of account control program
326 type CtrlProgram struct {
327         AccountID      string
328         Address        string
329         KeyIndex       uint64
330         ControlProgram []byte
331         Change         bool // Mark whether this control program is for UTXO change
332 }
333
334 func (m *Manager) insertAccountControlProgram(ctx context.Context, progs ...*CtrlProgram) error {
335         var hash common.Hash
336         for _, prog := range progs {
337                 accountCP, err := json.Marshal(prog)
338                 if err != nil {
339                         return err
340                 }
341
342                 sha3pool.Sum256(hash[:], prog.ControlProgram)
343                 m.db.Set(ContractKey(hash), accountCP)
344         }
345         return nil
346 }
347
348 // IsLocalControlProgram check is the input control program belong to local
349 func (m *Manager) IsLocalControlProgram(prog []byte) bool {
350         var hash common.Hash
351         sha3pool.Sum256(hash[:], prog)
352         bytes := m.db.Get(ContractKey(hash))
353         return bytes != nil
354 }
355
356 // GetCoinbaseControlProgram will return a coinbase script
357 func (m *Manager) GetCoinbaseControlProgram() ([]byte, error) {
358         if data := m.db.Get(miningAddressKey); data != nil {
359                 cp := &CtrlProgram{}
360                 return cp.ControlProgram, json.Unmarshal(data, cp)
361         }
362
363         accountIter := m.db.IteratorPrefix([]byte(accountPrefix))
364         defer accountIter.Release()
365         if !accountIter.Next() {
366                 log.Warningf("GetCoinbaseControlProgram: can't find any account in db")
367                 return vmutil.DefaultCoinbaseProgram()
368         }
369
370         account := &Account{}
371         if err := json.Unmarshal(accountIter.Value(), account); err != nil {
372                 return nil, err
373         }
374
375         program, err := m.createAddress(nil, account, false)
376         if err != nil {
377                 return nil, err
378         }
379
380         rawCP, err := json.Marshal(program)
381         if err != nil {
382                 return nil, err
383         }
384
385         m.db.Set(miningAddressKey, rawCP)
386         return program.ControlProgram, nil
387 }
388
389 // DeleteAccount deletes the account's ID or alias matching accountInfo.
390 func (m *Manager) DeleteAccount(aliasOrID string) (err error) {
391         account := &Account{}
392         if account, err = m.FindByAlias(nil, aliasOrID); err != nil {
393                 if account, err = m.FindByID(nil, aliasOrID); err != nil {
394                         return err
395                 }
396         }
397
398         storeBatch := m.db.NewBatch()
399
400         m.cacheMu.Lock()
401         m.aliasCache.Remove(account.Alias)
402         m.cacheMu.Unlock()
403
404         storeBatch.Delete(aliasKey(account.Alias))
405         storeBatch.Delete(Key(account.ID))
406         storeBatch.Write()
407
408         return nil
409 }
410
411 // ListAccounts will return the accounts in the db
412 func (m *Manager) ListAccounts(id string) ([]*Account, error) {
413         accounts := []*Account{}
414         accountIter := m.db.IteratorPrefix(Key(strings.TrimSpace(id)))
415         defer accountIter.Release()
416
417         for accountIter.Next() {
418                 account := &Account{}
419                 if err := json.Unmarshal(accountIter.Value(), &account); err != nil {
420                         return nil, err
421                 }
422                 accounts = append(accounts, account)
423         }
424
425         return accounts, nil
426 }
427
428 // ListControlProgram return all the local control program
429 func (m *Manager) ListControlProgram() ([]*CtrlProgram, error) {
430         var cps []*CtrlProgram
431         cpIter := m.db.IteratorPrefix(contractPrefix)
432         defer cpIter.Release()
433
434         for cpIter.Next() {
435                 cp := &CtrlProgram{}
436                 if err := json.Unmarshal(cpIter.Value(), cp); err != nil {
437                         return nil, err
438                 }
439                 cps = append(cps, cp)
440         }
441
442         return cps, nil
443 }
444
445 // GetAccountByAddress return account by given address
446 func (m *Manager) GetAccountByAddress(address string) (*Account, error) {
447         addr, err := common.DecodeAddress(address, &consensus.ActiveNetParams)
448         if err != nil {
449                 return nil, err
450         }
451
452         redeemContract := addr.ScriptAddress()
453         program := []byte{}
454         switch addr.(type) {
455         case *common.AddressWitnessPubKeyHash:
456                 program, err = vmutil.P2WPKHProgram(redeemContract)
457         case *common.AddressWitnessScriptHash:
458                 program, err = vmutil.P2WSHProgram(redeemContract)
459         default:
460                 return nil, ErrInvalidAddress
461         }
462         if err != nil {
463                 return nil, err
464         }
465
466         var hash [32]byte
467         cp := CtrlProgram{}
468         sha3pool.Sum256(hash[:], program)
469         rawProgram := m.db.Get(ContractKey(hash))
470         if rawProgram == nil {
471                 return nil, ErrFindCtrlProgram
472         }
473
474         if err := json.Unmarshal(rawProgram, &cp); err != nil {
475                 return nil, err
476         }
477
478         rawAccount := m.db.Get(Key(cp.AccountID))
479         if rawAccount == nil {
480                 return nil, ErrFindAccount
481         }
482
483         account := &Account{}
484         if err := json.Unmarshal(rawAccount, account); err != nil {
485                 return nil, err
486         }
487
488         return account, nil
489 }