OSDN Git Service

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