OSDN Git Service

Remove unused code (#548)
[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         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: normalizedAlias, 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.getNextXpubsIndex(account.Signer.XPubs)
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.getNextXpubsIndex(account.Signer.XPubs)
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 // DeleteAccount deletes the account's ID or alias matching accountInfo.
416 func (m *Manager) DeleteAccount(in struct {
417         AccountInfo string `json:"account_info"`
418 }) (err error) {
419         account := &Account{}
420         if account, err = m.FindByAlias(nil, in.AccountInfo); err != nil {
421                 if account, err = m.findByID(nil, in.AccountInfo); err != nil {
422                         return err
423                 }
424         }
425
426         storeBatch := m.db.NewBatch()
427
428         m.cacheMu.Lock()
429         m.aliasCache.Remove(account.Alias)
430         m.cacheMu.Unlock()
431
432         storeBatch.Delete(aliasKey(account.Alias))
433         storeBatch.Delete(Key(account.ID))
434         storeBatch.Write()
435
436         return nil
437 }
438
439 // ListAccounts will return the accounts in the db
440 func (m *Manager) ListAccounts(id string) ([]*Account, error) {
441         accounts := []*Account{}
442         accountIter := m.db.IteratorPrefix([]byte(accountPrefix + id))
443         defer accountIter.Release()
444
445         for accountIter.Next() {
446                 account := &Account{}
447                 if err := json.Unmarshal(accountIter.Value(), &account); err != nil {
448                         return nil, err
449                 }
450                 accounts = append(accounts, account)
451         }
452
453         return accounts, nil
454 }
455
456 // ListControlProgram return all the local control program
457 func (m *Manager) ListControlProgram() ([]*CtrlProgram, error) {
458         cps := []*CtrlProgram{}
459         cpIter := m.db.IteratorPrefix([]byte(accountCPPrefix))
460         defer cpIter.Release()
461
462         for cpIter.Next() {
463                 cp := &CtrlProgram{}
464                 if err := json.Unmarshal(cpIter.Value(), cp); err != nil {
465                         return nil, err
466                 }
467                 cps = append(cps, cp)
468         }
469
470         return cps, nil
471 }