OSDN Git Service

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