OSDN Git Service

show whether it is change for unspent utxos
[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 // CreateAddressForChange generate an address for the UTXO change
266 func (m *Manager) CreateCtrlProgramForChange(ctx context.Context, accountID string) (cp *CtrlProgram, err error) {
267         return m.CreateAddress(ctx, accountID, true)
268 }
269
270 // CreateAddress generate an address for the select account
271 func (m *Manager) CreateAddress(ctx context.Context, accountID string, change bool) (cp *CtrlProgram, err error) {
272         account, err := m.findByID(ctx, accountID)
273         if err != nil {
274                 return nil, err
275         }
276         return m.createAddress(ctx, account, change)
277 }
278
279 // CreateAddress generate an address for the select account
280 func (m *Manager) createAddress(ctx context.Context, account *Account, change bool) (cp *CtrlProgram, err error) {
281         if len(account.XPubs) == 1 {
282                 cp, err = m.createP2PKH(ctx, account, change)
283         } else {
284                 cp, err = m.createP2SH(ctx, account, change)
285         }
286         if err != nil {
287                 return nil, err
288         }
289
290         if err = m.insertAccountControlProgram(ctx, cp); err != nil {
291                 return nil, err
292         }
293         return cp, nil
294 }
295
296 func (m *Manager) createP2PKH(ctx context.Context, account *Account, change bool) (*CtrlProgram, error) {
297         idx := m.getNextXpubsIndex(account.Signer.XPubs)
298         path := signers.Path(account.Signer, signers.AccountKeySpace, idx)
299         derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
300         derivedPK := derivedXPubs[0].PublicKey()
301         pubHash := crypto.Ripemd160(derivedPK)
302
303         // TODO: pass different params due to config
304         address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.MainNetParams)
305         if err != nil {
306                 return nil, err
307         }
308
309         control, err := vmutil.P2WPKHProgram([]byte(pubHash))
310         if err != nil {
311                 return nil, err
312         }
313
314         return &CtrlProgram{
315                 AccountID:      account.ID,
316                 Address:        address.EncodeAddress(),
317                 KeyIndex:       idx,
318                 ControlProgram: control,
319                 Change:         change,
320         }, nil
321 }
322
323 func (m *Manager) createP2SH(ctx context.Context, account *Account, change bool) (*CtrlProgram, error) {
324         idx := m.getNextXpubsIndex(account.Signer.XPubs)
325         path := signers.Path(account.Signer, signers.AccountKeySpace, idx)
326         derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
327         derivedPKs := chainkd.XPubKeys(derivedXPubs)
328         signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
329         if err != nil {
330                 return nil, err
331         }
332         scriptHash := crypto.Sha256(signScript)
333
334         // TODO: pass different params due to config
335         address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.MainNetParams)
336         if err != nil {
337                 return nil, err
338         }
339
340         control, err := vmutil.P2WSHProgram(scriptHash)
341         if err != nil {
342                 return nil, err
343         }
344
345         return &CtrlProgram{
346                 AccountID:      account.ID,
347                 Address:        address.EncodeAddress(),
348                 KeyIndex:       idx,
349                 ControlProgram: control,
350                 Change:         change,
351         }, nil
352 }
353
354 //CtrlProgram is structure of account control program
355 type CtrlProgram struct {
356         AccountID      string
357         Address        string
358         KeyIndex       uint64
359         ControlProgram []byte
360         Change         bool // Mark whether this control program is for UTXO change
361 }
362
363 func (m *Manager) insertAccountControlProgram(ctx context.Context, progs ...*CtrlProgram) error {
364         var hash common.Hash
365         for _, prog := range progs {
366                 accountCP, err := json.Marshal(prog)
367                 if err != nil {
368                         return err
369                 }
370
371                 sha3pool.Sum256(hash[:], prog.ControlProgram)
372                 m.db.Set(CPKey(hash), accountCP)
373         }
374         return nil
375 }
376
377 // IsLocalControlProgram check is the input control program belong to local
378 func (m *Manager) IsLocalControlProgram(prog []byte) bool {
379         var hash common.Hash
380         sha3pool.Sum256(hash[:], prog)
381         bytes := m.db.Get(CPKey(hash))
382         return bytes != nil
383 }
384
385 // GetCoinbaseControlProgram will return a coinbase script
386 func (m *Manager) GetCoinbaseControlProgram() ([]byte, error) {
387         if data := m.db.Get(miningAddressKey); data != nil {
388                 cp := &CtrlProgram{}
389                 return cp.ControlProgram, json.Unmarshal(data, cp)
390         }
391
392         accountIter := m.db.IteratorPrefix([]byte(accountPrefix))
393         defer accountIter.Release()
394         if !accountIter.Next() {
395                 log.Warningf("GetCoinbaseControlProgram: can't find any account in db")
396                 return vmutil.DefaultCoinbaseProgram()
397         }
398
399         account := &Account{}
400         if err := json.Unmarshal(accountIter.Value(), account); err != nil {
401                 return nil, err
402         }
403
404         program, err := m.createAddress(nil, account, false)
405         if err != nil {
406                 return nil, err
407         }
408
409         rawCP, err := json.Marshal(program)
410         if err != nil {
411                 return nil, err
412         }
413
414         m.db.Set(miningAddressKey, rawCP)
415         return program.ControlProgram, nil
416 }
417
418 // DeleteAccount deletes the account's ID or alias matching accountInfo.
419 func (m *Manager) DeleteAccount(in struct {
420         AccountInfo string `json:"account_info"`
421 }) (err error) {
422         account := &Account{}
423         if account, err = m.FindByAlias(nil, in.AccountInfo); err != nil {
424                 if account, err = m.findByID(nil, in.AccountInfo); err != nil {
425                         return err
426                 }
427         }
428
429         storeBatch := m.db.NewBatch()
430
431         m.cacheMu.Lock()
432         m.aliasCache.Remove(account.Alias)
433         m.cacheMu.Unlock()
434
435         storeBatch.Delete(aliasKey(account.Alias))
436         storeBatch.Delete(Key(account.ID))
437         storeBatch.Write()
438
439         return nil
440 }
441
442 // ListAccounts will return the accounts in the db
443 func (m *Manager) ListAccounts(id string) ([]*Account, error) {
444         accounts := []*Account{}
445         accountIter := m.db.IteratorPrefix([]byte(accountPrefix + id))
446         defer accountIter.Release()
447
448         for accountIter.Next() {
449                 account := &Account{}
450                 if err := json.Unmarshal(accountIter.Value(), &account); err != nil {
451                         return nil, err
452                 }
453                 accounts = append(accounts, account)
454         }
455
456         return accounts, nil
457 }
458
459 // ListControlProgram return all the local control program
460 func (m *Manager) ListControlProgram() ([]*CtrlProgram, error) {
461         var cps []*CtrlProgram
462         cpIter := m.db.IteratorPrefix([]byte(accountCPPrefix))
463         defer cpIter.Release()
464
465         for cpIter.Next() {
466                 cp := &CtrlProgram{}
467                 if err := json.Unmarshal(cpIter.Value(), cp); err != nil {
468                         return nil, err
469                 }
470                 cps = append(cps, cp)
471         }
472
473         return cps, nil
474 }