OSDN Git Service

4763ee91c6d6d6196b4b0e13914ff48ad73d73de
[bytom/bytom.git] / account / accounts.go
1 // Package account stores and tracks accounts within a Bytom 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         accountMu  sync.Mutex
92 }
93
94 // ExpireReservations removes reservations that have expired periodically.
95 // It blocks until the context is canceled.
96 func (m *Manager) ExpireReservations(ctx context.Context, period time.Duration) {
97         ticks := time.Tick(period)
98         for {
99                 select {
100                 case <-ctx.Done():
101                         log.Info("Deposed, ExpireReservations exiting")
102                         return
103                 case <-ticks:
104                         err := m.utxoDB.ExpireReservations(ctx)
105                         if err != nil {
106                                 log.WithField("error", err).Error("Expire reservations")
107                         }
108                 }
109         }
110 }
111
112 // Account is structure of Bytom account
113 type Account struct {
114         *signers.Signer
115         ID    string `json:"id"`
116         Alias string `json:"alias"`
117 }
118
119 func (m *Manager) getNextAccountIndex() uint64 {
120         m.accIndexMu.Lock()
121         defer m.accIndexMu.Unlock()
122
123         var nextIndex uint64 = 1
124         if rawIndexBytes := m.db.Get(accountIndexKey); rawIndexBytes != nil {
125                 nextIndex = common.BytesToUnit64(rawIndexBytes) + 1
126         }
127
128         m.db.Set(accountIndexKey, common.Unit64ToBytes(nextIndex))
129         return nextIndex
130 }
131
132 func (m *Manager) getNextContractIndex(accountID string) uint64 {
133         m.accIndexMu.Lock()
134         defer m.accIndexMu.Unlock()
135
136         nextIndex := uint64(1)
137         if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
138                 nextIndex = common.BytesToUnit64(rawIndexBytes) + 1
139         }
140
141         m.db.Set(contractIndexKey(accountID), common.Unit64ToBytes(nextIndex))
142         return nextIndex
143 }
144
145 // Create creates a new Account.
146 func (m *Manager) Create(ctx context.Context, xpubs []chainkd.XPub, quorum int, alias string) (*Account, error) {
147         m.accountMu.Lock()
148         defer m.accountMu.Unlock()
149
150         normalizedAlias := strings.ToLower(strings.TrimSpace(alias))
151         if existed := m.db.Get(aliasKey(normalizedAlias)); existed != nil {
152                 return nil, ErrDuplicateAlias
153         }
154
155         signer, err := signers.Create("account", xpubs, quorum, m.getNextAccountIndex())
156         id := signers.IDGenerate()
157         if err != nil {
158                 return nil, errors.Wrap(err)
159         }
160
161         account := &Account{Signer: signer, ID: id, Alias: normalizedAlias}
162         rawAccount, err := json.Marshal(account)
163         if err != nil {
164                 return nil, ErrMarshalAccount
165         }
166         storeBatch := m.db.NewBatch()
167
168         accountID := Key(id)
169         storeBatch.Set(accountID, rawAccount)
170         storeBatch.Set(aliasKey(alias), []byte(id))
171         storeBatch.Write()
172
173         return account, nil
174 }
175
176 // FindByAlias retrieves an account's Signer record by its alias
177 func (m *Manager) FindByAlias(ctx context.Context, alias string) (*Account, error) {
178         m.cacheMu.Lock()
179         cachedID, ok := m.aliasCache.Get(alias)
180         m.cacheMu.Unlock()
181         if ok {
182                 return m.FindByID(ctx, cachedID.(string))
183         }
184
185         rawID := m.db.Get(aliasKey(alias))
186         if rawID == nil {
187                 return nil, ErrFindAccount
188         }
189
190         accountID := string(rawID)
191         m.cacheMu.Lock()
192         m.aliasCache.Add(alias, accountID)
193         m.cacheMu.Unlock()
194         return m.FindByID(ctx, accountID)
195 }
196
197 // FindByID returns an account's Signer record by its ID.
198 func (m *Manager) FindByID(ctx context.Context, id string) (*Account, error) {
199         m.cacheMu.Lock()
200         cachedAccount, ok := m.cache.Get(id)
201         m.cacheMu.Unlock()
202         if ok {
203                 return cachedAccount.(*Account), nil
204         }
205
206         rawAccount := m.db.Get(Key(id))
207         if rawAccount == nil {
208                 return nil, ErrFindAccount
209         }
210
211         account := &Account{}
212         if err := json.Unmarshal(rawAccount, account); err != nil {
213                 return nil, err
214         }
215
216         m.cacheMu.Lock()
217         m.cache.Add(id, account)
218         m.cacheMu.Unlock()
219         return account, nil
220 }
221
222 // GetAliasByID return the account alias by given ID
223 func (m *Manager) GetAliasByID(id string) string {
224         account := &Account{}
225
226         rawAccount := m.db.Get(Key(id))
227         if rawAccount == nil {
228                 log.Warn("fail to find account")
229                 return ""
230         }
231
232         if err := json.Unmarshal(rawAccount, account); err != nil {
233                 log.Warn(err)
234                 return ""
235         }
236         return account.Alias
237 }
238
239 // CreateAddress generate an address for the select account
240 func (m *Manager) CreateAddress(ctx context.Context, accountID string, change bool) (cp *CtrlProgram, err error) {
241         account, err := m.FindByID(ctx, accountID)
242         if err != nil {
243                 return nil, err
244         }
245         return m.createAddress(ctx, account, change)
246 }
247
248 // CreateAddress generate an address for the select account
249 func (m *Manager) createAddress(ctx context.Context, account *Account, change bool) (cp *CtrlProgram, err error) {
250         if len(account.XPubs) == 1 {
251                 cp, err = m.createP2PKH(ctx, account, change)
252         } else {
253                 cp, err = m.createP2SH(ctx, account, change)
254         }
255         if err != nil {
256                 return nil, err
257         }
258
259         if err = m.insertAccountControlProgram(ctx, cp); err != nil {
260                 return nil, err
261         }
262         return cp, nil
263 }
264
265 func (m *Manager) createP2PKH(ctx context.Context, account *Account, change bool) (*CtrlProgram, error) {
266         idx := m.getNextContractIndex(account.ID)
267         path := signers.Path(account.Signer, signers.AccountKeySpace, idx)
268         derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
269         derivedPK := derivedXPubs[0].PublicKey()
270         pubHash := crypto.Ripemd160(derivedPK)
271
272         // TODO: pass different params due to config
273         address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
274         if err != nil {
275                 return nil, err
276         }
277
278         control, err := vmutil.P2WPKHProgram([]byte(pubHash))
279         if err != nil {
280                 return nil, err
281         }
282
283         return &CtrlProgram{
284                 AccountID:      account.ID,
285                 Address:        address.EncodeAddress(),
286                 KeyIndex:       idx,
287                 ControlProgram: control,
288                 Change:         change,
289         }, nil
290 }
291
292 func (m *Manager) createP2SH(ctx context.Context, account *Account, change bool) (*CtrlProgram, error) {
293         idx := m.getNextContractIndex(account.ID)
294         path := signers.Path(account.Signer, signers.AccountKeySpace, idx)
295         derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
296         derivedPKs := chainkd.XPubKeys(derivedXPubs)
297         signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
298         if err != nil {
299                 return nil, err
300         }
301         scriptHash := crypto.Sha256(signScript)
302
303         // TODO: pass different params due to config
304         address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
305         if err != nil {
306                 return nil, err
307         }
308
309         control, err := vmutil.P2WSHProgram(scriptHash)
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 //CtrlProgram is structure of account control program
324 type CtrlProgram struct {
325         AccountID      string
326         Address        string
327         KeyIndex       uint64
328         ControlProgram []byte
329         Change         bool // Mark whether this control program is for UTXO change
330 }
331
332 func (m *Manager) insertAccountControlProgram(ctx context.Context, progs ...*CtrlProgram) error {
333         var hash common.Hash
334         for _, prog := range progs {
335                 accountCP, err := json.Marshal(prog)
336                 if err != nil {
337                         return err
338                 }
339
340                 sha3pool.Sum256(hash[:], prog.ControlProgram)
341                 m.db.Set(ContractKey(hash), accountCP)
342         }
343         return nil
344 }
345
346 // IsLocalControlProgram check is the input control program belong to local
347 func (m *Manager) IsLocalControlProgram(prog []byte) bool {
348         var hash common.Hash
349         sha3pool.Sum256(hash[:], prog)
350         bytes := m.db.Get(ContractKey(hash))
351         return bytes != nil
352 }
353
354 // GetCoinbaseControlProgram will return a coinbase script
355 func (m *Manager) GetCoinbaseControlProgram() ([]byte, error) {
356         if data := m.db.Get(miningAddressKey); data != nil {
357                 cp := &CtrlProgram{}
358                 return cp.ControlProgram, json.Unmarshal(data, cp)
359         }
360
361         accountIter := m.db.IteratorPrefix([]byte(accountPrefix))
362         defer accountIter.Release()
363         if !accountIter.Next() {
364                 log.Warningf("GetCoinbaseControlProgram: can't find any account in db")
365                 return vmutil.DefaultCoinbaseProgram()
366         }
367
368         account := &Account{}
369         if err := json.Unmarshal(accountIter.Value(), account); err != nil {
370                 return nil, err
371         }
372
373         program, err := m.createAddress(nil, account, false)
374         if err != nil {
375                 return nil, err
376         }
377
378         rawCP, err := json.Marshal(program)
379         if err != nil {
380                 return nil, err
381         }
382
383         m.db.Set(miningAddressKey, rawCP)
384         return program.ControlProgram, nil
385 }
386
387 // DeleteAccount deletes the account's ID or alias matching accountInfo.
388 func (m *Manager) DeleteAccount(aliasOrID string) (err error) {
389         account := &Account{}
390         if account, err = m.FindByAlias(nil, aliasOrID); err != nil {
391                 if account, err = m.FindByID(nil, aliasOrID); err != nil {
392                         return err
393                 }
394         }
395
396         storeBatch := m.db.NewBatch()
397
398         m.cacheMu.Lock()
399         m.aliasCache.Remove(account.Alias)
400         m.cacheMu.Unlock()
401
402         storeBatch.Delete(aliasKey(account.Alias))
403         storeBatch.Delete(Key(account.ID))
404         storeBatch.Write()
405
406         return nil
407 }
408
409 // ListAccounts will return the accounts in the db
410 func (m *Manager) ListAccounts(id string) ([]*Account, error) {
411         accounts := []*Account{}
412         accountIter := m.db.IteratorPrefix(Key(id))
413         defer accountIter.Release()
414
415         for accountIter.Next() {
416                 account := &Account{}
417                 if err := json.Unmarshal(accountIter.Value(), &account); err != nil {
418                         return nil, err
419                 }
420                 accounts = append(accounts, account)
421         }
422
423         return accounts, nil
424 }
425
426 // ListControlProgram return all the local control program
427 func (m *Manager) ListControlProgram() ([]*CtrlProgram, error) {
428         var cps []*CtrlProgram
429         cpIter := m.db.IteratorPrefix(contractPrefix)
430         defer cpIter.Release()
431
432         for cpIter.Next() {
433                 cp := &CtrlProgram{}
434                 if err := json.Unmarshal(cpIter.Value(), cp); err != nil {
435                         return nil, err
436                 }
437                 cps = append(cps, cp)
438         }
439
440         return cps, nil
441 }