OSDN Git Service

init push for pay-to-script-hash (#235)
[bytom/bytom.git] / blockchain / account / accounts.go
1 // Package account stores and tracks accounts within a Chain Core.
2 package account
3
4 import (
5         "context"
6         "encoding/binary"
7         "encoding/json"
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         aliasPrefix     = "ALI:"
30         accountPrefix   = "ACC:"
31         accountCPPrefix = "ACP:"
32         keyNextIndex    = "NextIndex"
33 )
34
35 // pre-define errors for supporting bytom errorFormatter
36 var (
37         ErrDuplicateAlias = errors.New("duplicate account alias")
38         ErrFindAccount    = errors.New("fail to find account")
39         ErrMarshalAccount = errors.New("failed marshal account")
40         ErrMarshalTags    = errors.New("failed marshal account to update tags")
41         ErrStandardQuorum = errors.New("need single key pair account to create standard transaction")
42 )
43
44 func aliasKey(name string) []byte {
45         return []byte(aliasPrefix + name)
46 }
47
48 //Key account store prefix
49 func Key(name string) []byte {
50         return []byte(accountPrefix + name)
51 }
52
53 //CPKey account control promgram store prefix
54 func CPKey(hash common.Hash) []byte {
55         return append([]byte(accountCPPrefix), hash[:]...)
56 }
57
58 // NewManager creates a new account manager
59 func NewManager(walletDB dbm.DB, chain *protocol.Chain) *Manager {
60         var nextIndex uint64
61         if index := walletDB.Get([]byte(keyNextIndex)); index != nil {
62                 nextIndex = uint64(binary.LittleEndian.Uint64(index))
63         }
64         return &Manager{
65                 db:           walletDB,
66                 chain:        chain,
67                 utxoDB:       newReserver(chain, walletDB),
68                 cache:        lru.New(maxAccountCache),
69                 aliasCache:   lru.New(maxAccountCache),
70                 delayedACPs:  make(map[*txbuilder.TemplateBuilder][]*CtrlProgram),
71                 acpIndexNext: nextIndex,
72         }
73 }
74
75 // Manager stores accounts and their associated control programs.
76 type Manager struct {
77         db     dbm.DB
78         chain  *protocol.Chain
79         utxoDB *reserver
80
81         cacheMu    sync.Mutex
82         cache      *lru.Cache
83         aliasCache *lru.Cache
84
85         delayedACPsMu sync.Mutex
86         delayedACPs   map[*txbuilder.TemplateBuilder][]*CtrlProgram
87
88         acpMu        sync.Mutex
89         acpIndexNext uint64 // next acp index in our block
90         acpIndexCap  uint64 // points to end of block
91 }
92
93 // ExpireReservations removes reservations that have expired periodically.
94 // It blocks until the context is canceled.
95 func (m *Manager) ExpireReservations(ctx context.Context, period time.Duration) {
96         ticks := time.Tick(period)
97         for {
98                 select {
99                 case <-ctx.Done():
100                         log.Info("Deposed, ExpireReservations exiting")
101                         return
102                 case <-ticks:
103                         err := m.utxoDB.ExpireReservations(ctx)
104                         if err != nil {
105                                 log.WithField("error", err).Error("Expire reservations")
106                         }
107                 }
108         }
109 }
110
111 // Account is structure of Bytom account
112 type Account struct {
113         *signers.Signer
114         Alias string
115         Tags  map[string]interface{} `json:"tags,omitempty"`
116 }
117
118 // Create creates a new Account.
119 func (m *Manager) Create(ctx context.Context, xpubs []chainkd.XPub, quorum int, alias string, tags map[string]interface{}, accessToken string) (*Account, error) {
120         if existed := m.db.Get(aliasKey(alias)); existed != nil {
121                 return nil, ErrDuplicateAlias
122         }
123
124         signer, err := signers.Create(ctx, m.db, "account", xpubs, quorum, accessToken)
125         if err != nil {
126                 return nil, errors.Wrap(err)
127         }
128
129         account := &Account{Signer: signer, Alias: alias, Tags: tags}
130         rawAccount, err := json.Marshal(account)
131         if err != nil {
132                 return nil, ErrMarshalAccount
133         }
134         storeBatch := m.db.NewBatch()
135
136         accountID := Key(signer.ID)
137         storeBatch.Set(accountID, rawAccount)
138         storeBatch.Set(aliasKey(alias), []byte(signer.ID))
139         storeBatch.Write()
140
141         return account, nil
142 }
143
144 // UpdateTags modifies the tags of the specified account. The account may be
145 // identified either by ID or Alias, but not both.
146 func (m *Manager) UpdateTags(ctx context.Context, accountInfo string, tags map[string]interface{}) error {
147         var account Account
148
149         accountID := accountInfo
150         if s, err := m.FindByAlias(nil, accountInfo); err == nil {
151                 accountID = s.ID
152         }
153
154         rawAccount := m.db.Get(Key(accountID))
155         if rawAccount == nil {
156                 return ErrFindAccount
157         }
158         if err := json.Unmarshal(rawAccount, &account); err != nil {
159                 return err
160         }
161
162         for k, v := range tags {
163                 switch v {
164                 case "":
165                         delete(account.Tags, k)
166                 default:
167                         if account.Tags == nil {
168                                 account.Tags = make(map[string]interface{})
169                         }
170                         account.Tags[k] = v
171                 }
172         }
173
174         rawAccount, err := json.Marshal(account)
175         if err != nil {
176                 return ErrMarshalTags
177         }
178
179         m.db.Set(Key(accountID), rawAccount)
180         return nil
181 }
182
183 // FindByAlias retrieves an account's Signer record by its alias
184 func (m *Manager) FindByAlias(ctx context.Context, alias string) (*signers.Signer, error) {
185         m.cacheMu.Lock()
186         cachedID, ok := m.aliasCache.Get(alias)
187         m.cacheMu.Unlock()
188         if ok {
189                 return m.findByID(ctx, cachedID.(string))
190         }
191
192         rawID := m.db.Get(aliasKey(alias))
193         if rawID == nil {
194                 return nil, ErrFindAccount
195         }
196
197         accountID := string(rawID)
198         m.cacheMu.Lock()
199         m.aliasCache.Add(alias, accountID)
200         m.cacheMu.Unlock()
201         return m.findByID(ctx, accountID)
202 }
203
204 // findByID returns an account's Signer record by its ID.
205 func (m *Manager) findByID(ctx context.Context, id string) (*signers.Signer, error) {
206         m.cacheMu.Lock()
207         cachedSigner, ok := m.cache.Get(id)
208         m.cacheMu.Unlock()
209         if ok {
210                 return cachedSigner.(*signers.Signer), nil
211         }
212
213         rawAccount := m.db.Get(Key(id))
214         if rawAccount == nil {
215                 return nil, ErrFindAccount
216         }
217
218         var account Account
219         if err := json.Unmarshal(rawAccount, &account); err != nil {
220                 return nil, err
221         }
222
223         m.cacheMu.Lock()
224         m.cache.Add(id, account.Signer)
225         m.cacheMu.Unlock()
226         return account.Signer, nil
227 }
228
229 // GetAliasByID return the account alias by given ID
230 func (m *Manager) GetAliasByID(id string) string {
231         var account Account
232
233         rawAccount := m.db.Get(Key(id))
234         if rawAccount == nil {
235                 log.Warn("fail to find account")
236                 return ""
237         }
238
239         if err := json.Unmarshal(rawAccount, &account); err != nil {
240                 log.Warn(err)
241                 return ""
242         }
243
244         return account.Alias
245 }
246
247 // CreateAddress generate an address for the select account
248 func (m *Manager) CreateAddress(ctx context.Context, accountID string, change bool, expiresAt time.Time) (cp *CtrlProgram, err error) {
249         account, err := m.findByID(ctx, accountID)
250         if err != nil {
251                 return nil, err
252         }
253
254         if account.Quorum == 1 {
255                 cp, err = m.createP2PKH(ctx, account, change, expiresAt)
256         } else {
257                 cp, err = m.createP2SH(ctx, account, change, expiresAt)
258         }
259         if err != nil {
260                 return nil, err
261         }
262
263         if err = m.insertAccountControlProgram(ctx, cp); err != nil {
264                 return nil, err
265         }
266         return cp, nil
267 }
268
269 func (m *Manager) createP2PKH(ctx context.Context, account *signers.Signer, change bool, expiresAt time.Time) (*CtrlProgram, error) {
270         idx, err := m.nextIndex(ctx)
271         if err != nil {
272                 return nil, err
273         }
274         path := signers.Path(account, signers.AccountKeySpace, idx)
275         derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
276         derivedPK := derivedXPubs[0].PublicKey()
277         pubHash := crypto.Ripemd160(derivedPK)
278
279         // TODO: pass different params due to config
280         address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.MainNetParams)
281         if err != nil {
282                 return nil, err
283         }
284
285         control, err := vmutil.P2PKHSigProgram([]byte(pubHash))
286         if err != nil {
287                 return nil, err
288         }
289
290         return &CtrlProgram{
291                 AccountID:      account.ID,
292                 Address:        address.EncodeAddress(),
293                 KeyIndex:       idx,
294                 ControlProgram: control,
295                 Change:         change,
296                 ExpiresAt:      expiresAt,
297         }, nil
298 }
299
300 func (m *Manager) createP2SH(ctx context.Context, account *signers.Signer, change bool, expiresAt time.Time) (*CtrlProgram, error) {
301         idx, err := m.nextIndex(ctx)
302         if err != nil {
303                 return nil, err
304         }
305
306         path := signers.Path(account, signers.AccountKeySpace, idx)
307         derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
308         derivedPKs := chainkd.XPubKeys(derivedXPubs)
309         signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
310         if err != nil {
311                 return nil, err
312         }
313         scriptHash := crypto.Sha256(signScript)
314
315         // TODO: pass different params due to config
316         address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.MainNetParams)
317         if err != nil {
318                 return nil, err
319         }
320
321         control, err := vmutil.P2SHProgram(scriptHash)
322         if err != nil {
323                 return nil, err
324         }
325
326         return &CtrlProgram{
327                 AccountID:      account.ID,
328                 Address:        address.EncodeAddress(),
329                 KeyIndex:       idx,
330                 ControlProgram: control,
331                 Change:         change,
332                 ExpiresAt:      expiresAt,
333         }, nil
334 }
335
336 func (m *Manager) createControlProgram(ctx context.Context, accountID string, change bool, expiresAt time.Time) (*CtrlProgram, error) {
337         account, err := m.findByID(ctx, accountID)
338         if err != nil {
339                 return nil, err
340         }
341
342         idx, err := m.nextIndex(ctx)
343         if err != nil {
344                 return nil, err
345         }
346
347         path := signers.Path(account, signers.AccountKeySpace, idx)
348         derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
349         derivedPKs := chainkd.XPubKeys(derivedXPubs)
350         control, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
351         if err != nil {
352                 return nil, err
353         }
354
355         return &CtrlProgram{
356                 AccountID:      account.ID,
357                 KeyIndex:       idx,
358                 ControlProgram: control,
359                 Change:         change,
360                 ExpiresAt:      expiresAt,
361         }, nil
362 }
363
364 // CreateControlProgram creates a control program
365 // that is tied to the Account and stores it in the database.
366 func (m *Manager) CreateControlProgram(ctx context.Context, accountID string, change bool, expiresAt time.Time) ([]byte, error) {
367         cp, err := m.createControlProgram(ctx, accountID, change, expiresAt)
368         if err != nil {
369                 return nil, err
370         }
371
372         if err = m.insertAccountControlProgram(ctx, cp); err != nil {
373                 return nil, err
374         }
375         return cp.ControlProgram, nil
376 }
377
378 //CtrlProgram is structure of account control program
379 type CtrlProgram struct {
380         AccountID      string
381         Address        string
382         KeyIndex       uint64
383         ControlProgram []byte
384         Change         bool
385         ExpiresAt      time.Time
386 }
387
388 func (m *Manager) insertAccountControlProgram(ctx context.Context, progs ...*CtrlProgram) error {
389         var hash common.Hash
390         for _, prog := range progs {
391                 accountCP, err := json.Marshal(prog)
392                 if err != nil {
393                         return err
394                 }
395
396                 sha3pool.Sum256(hash[:], prog.ControlProgram)
397                 m.db.Set(CPKey(hash), accountCP)
398         }
399         return nil
400 }
401
402 // GetCoinbaseControlProgram will return a coinbase script
403 func (m *Manager) GetCoinbaseControlProgram(height uint64) ([]byte, error) {
404         signerIter := m.db.IteratorPrefix([]byte(accountPrefix))
405         if !signerIter.Next() {
406                 log.Warningf("GetCoinbaseControlProgram: can't find any account in db")
407                 return vmutil.CoinbaseProgram(nil, 0, height)
408         }
409         rawSigner := signerIter.Value()
410         signerIter.Release()
411
412         signer := &signers.Signer{}
413         if err := json.Unmarshal(rawSigner, signer); err != nil {
414                 log.Errorf("GetCoinbaseControlProgram: fail to unmarshal signer %v", err)
415                 return vmutil.CoinbaseProgram(nil, 0, height)
416         }
417
418         ctx := context.Background()
419         idx, err := m.nextIndex(ctx)
420         if err != nil {
421                 log.Errorf("GetCoinbaseControlProgram: fail to get nextIndex %v", err)
422                 return vmutil.CoinbaseProgram(nil, 0, height)
423         }
424         path := signers.Path(signer, signers.AccountKeySpace, idx)
425         derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
426         derivedPKs := chainkd.XPubKeys(derivedXPubs)
427
428         script, err := vmutil.CoinbaseProgram(derivedPKs, signer.Quorum, height)
429         if err != nil {
430                 return script, err
431         }
432
433         err = m.insertAccountControlProgram(ctx, &CtrlProgram{
434                 AccountID:      signer.ID,
435                 KeyIndex:       idx,
436                 ControlProgram: script,
437                 Change:         false,
438         })
439         if err != nil {
440                 log.Errorf("GetCoinbaseControlProgram: fail to insertAccountControlProgram %v", err)
441         }
442         return script, nil
443 }
444
445 func saveIndex(db dbm.DB, index uint64) {
446         buf := make([]byte, 8)
447         binary.LittleEndian.PutUint64(buf, index)
448         db.Set([]byte(keyNextIndex), buf)
449 }
450
451 func (m *Manager) nextIndex(ctx context.Context) (uint64, error) {
452         m.acpMu.Lock()
453         defer m.acpMu.Unlock()
454
455         n := m.acpIndexNext
456         m.acpIndexNext++
457         saveIndex(m.db, m.acpIndexNext)
458         return n, nil
459 }
460
461 // DeleteAccount deletes the account's ID or alias matching accountInfo.
462 func (m *Manager) DeleteAccount(in struct {
463         AccountInfo string `json:"account_info"`
464 }) error {
465
466         account := Account{}
467         storeBatch := m.db.NewBatch()
468
469         accountID := in.AccountInfo
470         if s, err := m.FindByAlias(nil, in.AccountInfo); err == nil {
471                 accountID = s.ID
472         }
473
474         rawAccount := m.db.Get(Key(accountID))
475         if rawAccount == nil {
476                 return nil
477         }
478         if err := json.Unmarshal(rawAccount, &account); err != nil {
479                 return err
480         }
481
482         storeBatch.Delete(aliasKey(account.Alias))
483         storeBatch.Delete(Key(account.ID))
484         storeBatch.Write()
485
486         return nil
487 }
488
489 type annotatedAccount struct {
490         Alias    string           `json:"alias"`
491         ID       string           `json:"id"`
492         Quorum   int              `json:"quorum"`
493         KeyIndex uint64           `json:"key_index"`
494         XPubs    []chainkd.XPub   `json:"xpubs"`
495         Tags     *json.RawMessage `json:"tags"`
496 }
497
498 // ListAccounts will return the accounts in the db
499 func (m *Manager) ListAccounts(id string) ([]annotatedAccount, error) {
500         account := Account{}
501         tmpAccount := annotatedAccount{}
502         accounts := make([]annotatedAccount, 0)
503         jsonTags := json.RawMessage(`{}`)
504
505         accountIter := m.db.IteratorPrefix([]byte(accountPrefix + id))
506         defer accountIter.Release()
507
508         for accountIter.Next() {
509                 if err := json.Unmarshal(accountIter.Value(), &account); err != nil {
510                         return nil, err
511                 }
512
513                 tmpAccount.Alias = account.Alias
514                 tmpAccount.ID = account.ID
515                 tmpAccount.Quorum = account.Quorum
516                 tmpAccount.KeyIndex = account.KeyIndex
517                 tmpAccount.XPubs = account.XPubs
518                 if account.Tags != nil {
519                         t, err := json.Marshal(account.Tags)
520                         if err != nil {
521                                 return nil, err
522                         }
523                         jsonTags = t
524                 }
525                 tmpAccount.Tags = &jsonTags
526
527                 accounts = append(accounts, tmpAccount)
528         }
529
530         return accounts, nil
531 }