OSDN Git Service

f6b29c55063556e17d051ec3ce669d2c029828a4
[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         accIndexMu  sync.Mutex
109 }
110
111 // ExpireReservations removes reservations that have expired periodically.
112 // It blocks until the context is canceled.
113 func (m *Manager) ExpireReservations(ctx context.Context, period time.Duration) {
114         ticks := time.Tick(period)
115         for {
116                 select {
117                 case <-ctx.Done():
118                         log.Info("Deposed, ExpireReservations exiting")
119                         return
120                 case <-ticks:
121                         err := m.utxoDB.ExpireReservations(ctx)
122                         if err != nil {
123                                 log.WithField("error", err).Error("Expire reservations")
124                         }
125                 }
126         }
127 }
128
129 // Account is structure of Bytom account
130 type Account struct {
131         *signers.Signer
132         ID    string
133         Alias string
134         Tags  map[string]interface{}
135 }
136
137 func (m *Manager) getNextXpubsIndex(xpubs []chainkd.XPub) uint64 {
138         m.accIndexMu.Lock()
139         defer m.accIndexMu.Unlock()
140
141         var nextIndex uint64 = 1
142         if rawIndexBytes := m.db.Get(indexKeys(xpubs)); rawIndexBytes != nil {
143                 nextIndex = convertBytesToUint64(rawIndexBytes) + 1
144         }
145
146         m.db.Set(indexKeys(xpubs), convertUnit64ToBytes(nextIndex))
147
148         return nextIndex
149 }
150
151 // Create creates a new Account.
152 func (m *Manager) Create(ctx context.Context, xpubs []chainkd.XPub, quorum int, alias string, tags map[string]interface{}) (*Account, error) {
153         normalizedAlias := strings.ToLower(strings.TrimSpace(alias))
154         if existed := m.db.Get(aliasKey(normalizedAlias)); existed != nil {
155                 return nil, ErrDuplicateAlias
156         }
157
158         signer, err := signers.Create("account", xpubs, quorum, m.getNextXpubsIndex(xpubs))
159         id := signers.IDGenerate()
160         if err != nil {
161                 return nil, errors.Wrap(err)
162         }
163
164         account := &Account{Signer: signer, ID: id, Alias: normalizedAlias, 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) (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)
272 }
273
274 // CreateAddress generate an address for the select account
275 func (m *Manager) createAddress(ctx context.Context, account *Account) (cp *CtrlProgram, err error) {
276         if len(account.XPubs) == 1 {
277                 cp, err = m.createP2PKH(ctx, account)
278         } else {
279                 cp, err = m.createP2SH(ctx, account)
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) (*CtrlProgram, error) {
292         idx := m.getNextXpubsIndex(account.Signer.XPubs)
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         }, nil
315 }
316
317 func (m *Manager) createP2SH(ctx context.Context, account *Account) (*CtrlProgram, error) {
318         idx := m.getNextXpubsIndex(account.Signer.XPubs)
319         path := signers.Path(account.Signer, signers.AccountKeySpace, idx)
320         derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
321         derivedPKs := chainkd.XPubKeys(derivedXPubs)
322         signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
323         if err != nil {
324                 return nil, err
325         }
326         scriptHash := crypto.Sha256(signScript)
327
328         // TODO: pass different params due to config
329         address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.MainNetParams)
330         if err != nil {
331                 return nil, err
332         }
333
334         control, err := vmutil.P2WSHProgram(scriptHash)
335         if err != nil {
336                 return nil, err
337         }
338
339         return &CtrlProgram{
340                 AccountID:      account.ID,
341                 Address:        address.EncodeAddress(),
342                 KeyIndex:       idx,
343                 ControlProgram: control,
344         }, nil
345 }
346
347 //CtrlProgram is structure of account control program
348 type CtrlProgram struct {
349         AccountID      string
350         Address        string
351         KeyIndex       uint64
352         ControlProgram []byte
353 }
354
355 func (m *Manager) insertAccountControlProgram(ctx context.Context, progs ...*CtrlProgram) error {
356         var hash common.Hash
357         for _, prog := range progs {
358                 accountCP, err := json.Marshal(prog)
359                 if err != nil {
360                         return err
361                 }
362
363                 sha3pool.Sum256(hash[:], prog.ControlProgram)
364                 m.db.Set(CPKey(hash), accountCP)
365         }
366         return nil
367 }
368
369 // IsLocalControlProgram check is the input control program belong to local
370 func (m *Manager) IsLocalControlProgram(prog []byte) bool {
371         var hash common.Hash
372         sha3pool.Sum256(hash[:], prog)
373         bytes := m.db.Get(CPKey(hash))
374         return bytes != nil
375 }
376
377 // GetCoinbaseControlProgram will return a coinbase script
378 func (m *Manager) GetCoinbaseControlProgram() ([]byte, error) {
379         if data := m.db.Get(miningAddressKey); data != nil {
380                 cp := &CtrlProgram{}
381                 return cp.ControlProgram, json.Unmarshal(data, cp)
382         }
383
384         accountIter := m.db.IteratorPrefix([]byte(accountPrefix))
385         defer accountIter.Release()
386         if !accountIter.Next() {
387                 log.Warningf("GetCoinbaseControlProgram: can't find any account in db")
388                 return vmutil.DefaultCoinbaseProgram()
389         }
390
391         account := &Account{}
392         if err := json.Unmarshal(accountIter.Value(), account); err != nil {
393                 return nil, err
394         }
395
396         program, err := m.createAddress(nil, account)
397         if err != nil {
398                 return nil, err
399         }
400
401         rawCP, err := json.Marshal(program)
402         if err != nil {
403                 return nil, err
404         }
405
406         m.db.Set(miningAddressKey, rawCP)
407         return program.ControlProgram, nil
408 }
409
410 // DeleteAccount deletes the account's ID or alias matching accountInfo.
411 func (m *Manager) DeleteAccount(in struct {
412         AccountInfo string `json:"account_info"`
413 }) (err error) {
414         account := &Account{}
415         if account, err = m.FindByAlias(nil, in.AccountInfo); err != nil {
416                 if account, err = m.findByID(nil, in.AccountInfo); err != nil {
417                         return err
418                 }
419         }
420
421         storeBatch := m.db.NewBatch()
422
423         m.cacheMu.Lock()
424         m.aliasCache.Remove(account.Alias)
425         m.cacheMu.Unlock()
426
427         storeBatch.Delete(aliasKey(account.Alias))
428         storeBatch.Delete(Key(account.ID))
429         storeBatch.Write()
430
431         return nil
432 }
433
434 // ListAccounts will return the accounts in the db
435 func (m *Manager) ListAccounts(id string) ([]*Account, error) {
436         accounts := []*Account{}
437         accountIter := m.db.IteratorPrefix([]byte(accountPrefix + id))
438         defer accountIter.Release()
439
440         for accountIter.Next() {
441                 account := &Account{}
442                 if err := json.Unmarshal(accountIter.Value(), &account); err != nil {
443                         return nil, err
444                 }
445                 accounts = append(accounts, account)
446         }
447
448         return accounts, nil
449 }
450
451 // ListControlProgram return all the local control program
452 func (m *Manager) ListControlProgram() ([]*CtrlProgram, error) {
453         var cps []*CtrlProgram
454         cpIter := m.db.IteratorPrefix([]byte(accountCPPrefix))
455         defer cpIter.Release()
456
457         for cpIter.Next() {
458                 cp := &CtrlProgram{}
459                 if err := json.Unmarshal(cpIter.Value(), cp); err != nil {
460                         return nil, err
461                 }
462                 cps = append(cps, cp)
463         }
464
465         return cps, nil
466 }