OSDN Git Service

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