OSDN Git Service

Wallet store interface (#217)
[bytom/vapor.git] / account / accounts.go
1 // Package account stores and tracks accounts within a Bytom Core.
2 package account
3
4 import (
5         "reflect"
6         "strings"
7         "sync"
8
9         "github.com/golang/groupcache/lru"
10         "github.com/google/uuid"
11         log "github.com/sirupsen/logrus"
12
13         "github.com/vapor/blockchain/signers"
14         "github.com/vapor/blockchain/txbuilder"
15         "github.com/vapor/common"
16         "github.com/vapor/consensus"
17         "github.com/vapor/consensus/segwit"
18         "github.com/vapor/crypto"
19         "github.com/vapor/crypto/ed25519/chainkd"
20         "github.com/vapor/crypto/sha3pool"
21         "github.com/vapor/errors"
22         "github.com/vapor/protocol"
23         "github.com/vapor/protocol/bc"
24         "github.com/vapor/protocol/vm/vmutil"
25 )
26
27 const (
28         maxAccountCache = 1000
29
30         // HardenedKeyStart bip32 hierarchical deterministic wallets
31         // keys with index ≥ 0x80000000 are hardened keys
32         HardenedKeyStart = 0x80000000
33         logModule        = "account"
34 )
35
36 // pre-define errors for supporting bytom errorFormatter
37 var (
38         ErrDuplicateAlias    = errors.New("Duplicate account alias")
39         ErrDuplicateIndex    = errors.New("Duplicate account with same xPubs and index")
40         ErrFindAccount       = errors.New("Failed to find account")
41         ErrMarshalAccount    = errors.New("Failed to marshal account")
42         ErrInvalidAddress    = errors.New("Invalid address")
43         ErrFindCtrlProgram   = errors.New("Failed to find account control program")
44         ErrDeriveRule        = errors.New("Invalid key derivation rule")
45         ErrContractIndex     = errors.New("Exceeded maximum addresses per account")
46         ErrAccountIndex      = errors.New("Exceeded maximum accounts per xpub")
47         ErrFindTransaction   = errors.New("No transaction")
48         ErrFindMiningAddress = errors.New("Failed to find mining address")
49         ErrAccountIDEmpty    = errors.New("account_id is empty")
50 )
51
52 // Account is structure of Bytom account
53 type Account struct {
54         *signers.Signer
55         ID    string `json:"id"`
56         Alias string `json:"alias"`
57 }
58
59 //CtrlProgram is structure of account control program
60 type CtrlProgram struct {
61         AccountID      string
62         Address        string
63         KeyIndex       uint64
64         ControlProgram []byte
65         Change         bool // Mark whether this control program is for UTXO change
66 }
67
68 // Manager stores accounts and their associated control programs.
69 type Manager struct {
70         store      AccountStore
71         chain      *protocol.Chain
72         utxoKeeper *utxoKeeper
73
74         cacheMu    sync.Mutex
75         cache      *lru.Cache
76         aliasCache *lru.Cache
77
78         delayedACPsMu sync.Mutex
79         delayedACPs   map[*txbuilder.TemplateBuilder][]*CtrlProgram
80
81         addressMu sync.Mutex
82         accountMu sync.Mutex
83 }
84
85 // NewManager creates a new account manager
86 func NewManager(store AccountStore, chain *protocol.Chain) *Manager {
87         return &Manager{
88                 store:       store,
89                 chain:       chain,
90                 utxoKeeper:  newUtxoKeeper(chain.BestBlockHeight, store),
91                 cache:       lru.New(maxAccountCache),
92                 aliasCache:  lru.New(maxAccountCache),
93                 delayedACPs: make(map[*txbuilder.TemplateBuilder][]*CtrlProgram),
94         }
95 }
96
97 // AddUnconfirmedUtxo add untxo list to utxoKeeper
98 func (m *Manager) AddUnconfirmedUtxo(utxos []*UTXO) {
99         m.utxoKeeper.AddUnconfirmedUtxo(utxos)
100 }
101
102 // CreateAccount creates a new Account.
103 func CreateAccount(xpubs []chainkd.XPub, quorum int, alias string, acctIndex uint64, deriveRule uint8) (*Account, error) {
104         if acctIndex >= HardenedKeyStart {
105                 return nil, ErrAccountIndex
106         }
107
108         signer, err := signers.Create("account", xpubs, quorum, acctIndex, deriveRule)
109         if err != nil {
110                 return nil, errors.Wrap(err)
111         }
112
113         id := uuid.New().String()
114         return &Account{Signer: signer, ID: id, Alias: strings.ToLower(strings.TrimSpace(alias))}, nil
115 }
116
117 func (m *Manager) saveAccount(account *Account) error {
118         newStore := m.store.InitBatch()
119
120         // update account index
121         newStore.SetAccountIndex(account)
122         if err := newStore.SetAccount(account); err != nil {
123                 return err
124         }
125
126         if err := newStore.CommitBatch(); err != nil {
127                 return err
128         }
129
130         return nil
131 }
132
133 // SaveAccount save a new account.
134 func (m *Manager) SaveAccount(account *Account) error {
135         m.accountMu.Lock()
136         defer m.accountMu.Unlock()
137
138         _, err := m.store.GetAccountByAlias(account.Alias)
139         if err == nil {
140                 return ErrDuplicateAlias
141         }
142
143         if err != ErrFindAccount {
144                 return err
145         }
146
147         acct, err := m.GetAccountByXPubsIndex(account.XPubs, account.KeyIndex)
148         if err != nil && err != ErrFindAccount {
149                 return err
150         } else if acct != nil {
151                 return ErrDuplicateIndex
152         }
153
154         return m.saveAccount(account)
155 }
156
157 // Create creates and save a new Account.
158 func (m *Manager) Create(xpubs []chainkd.XPub, quorum int, alias string, deriveRule uint8) (*Account, error) {
159         m.accountMu.Lock()
160         defer m.accountMu.Unlock()
161
162         _, err := m.store.GetAccountByAlias(alias)
163         if err == nil {
164                 return nil, ErrDuplicateAlias
165         } else if err != ErrFindAccount {
166                 return nil, err
167         }
168
169         acctIndex := uint64(1)
170         if currentIndex := m.store.GetAccountIndex(xpubs); currentIndex != 0 {
171                 acctIndex = currentIndex + 1
172         }
173
174         account, err := CreateAccount(xpubs, quorum, alias, acctIndex, deriveRule)
175         if err != nil {
176                 return nil, err
177         }
178
179         if err := m.saveAccount(account); err != nil {
180                 return nil, err
181         }
182
183         return account, nil
184 }
185
186 func (m *Manager) UpdateAccountAlias(accountID string, newAlias string) error {
187         m.accountMu.Lock()
188         defer m.accountMu.Unlock()
189
190         account, err := m.FindByID(accountID)
191         if err != nil {
192                 return err
193         }
194         oldAccount := *account
195
196         normalizedAlias := strings.ToLower(strings.TrimSpace(newAlias))
197         _, err = m.store.GetAccountByAlias(normalizedAlias)
198         if err == nil {
199                 return ErrDuplicateAlias
200         }
201         if err != ErrFindAccount {
202                 return err
203         }
204
205         m.cacheMu.Lock()
206         m.aliasCache.Remove(oldAccount.Alias)
207         m.cacheMu.Unlock()
208
209         account.Alias = normalizedAlias
210
211         newStore := m.store.InitBatch()
212
213         if err := newStore.DeleteAccount(&oldAccount); err != nil {
214                 return err
215         }
216
217         if err := newStore.SetAccount(account); err != nil {
218                 return err
219         }
220
221         if err := newStore.CommitBatch(); err != nil {
222                 return err
223         }
224
225         return nil
226 }
227
228 // CreateAddress generate an address for the select account
229 func (m *Manager) CreateAddress(accountID string, change bool) (cp *CtrlProgram, err error) {
230         m.addressMu.Lock()
231         defer m.addressMu.Unlock()
232
233         account, err := m.FindByID(accountID)
234         if err != nil {
235                 return nil, err
236         }
237
238         currentIdx, err := m.getCurrentContractIndex(account, change)
239         if err != nil {
240                 return nil, err
241         }
242
243         cp, err = CreateCtrlProgram(account, currentIdx+1, change)
244         if err != nil {
245                 return nil, err
246         }
247
248         return cp, m.saveControlProgram(cp, true)
249 }
250
251 // CreateBatchAddresses generate a batch of addresses for the select account
252 func (m *Manager) CreateBatchAddresses(accountID string, change bool, stopIndex uint64) error {
253         m.addressMu.Lock()
254         defer m.addressMu.Unlock()
255
256         account, err := m.FindByID(accountID)
257         if err != nil {
258                 return err
259         }
260
261         currentIndex, err := m.getCurrentContractIndex(account, change)
262         if err != nil {
263                 return err
264         }
265
266         for currentIndex++; currentIndex <= stopIndex; currentIndex++ {
267                 cp, err := CreateCtrlProgram(account, currentIndex, change)
268                 if err != nil {
269                         return err
270                 }
271
272                 if err := m.saveControlProgram(cp, true); err != nil {
273                         return err
274                 }
275         }
276
277         return nil
278 }
279
280 // DeleteAccount deletes the account's ID or alias matching account ID.
281 func (m *Manager) DeleteAccount(accountID string) error {
282         m.accountMu.Lock()
283         defer m.accountMu.Unlock()
284
285         account, err := m.FindByID(accountID)
286         if err != nil {
287                 return err
288         }
289
290         m.cacheMu.Lock()
291         m.aliasCache.Remove(account.Alias)
292         m.cacheMu.Unlock()
293
294         return m.store.DeleteAccount(account)
295 }
296
297 // FindByAlias retrieves an account's Signer record by its alias
298 func (m *Manager) FindByAlias(alias string) (*Account, error) {
299         m.cacheMu.Lock()
300         cachedID, ok := m.aliasCache.Get(alias)
301         m.cacheMu.Unlock()
302         if ok {
303                 return m.FindByID(cachedID.(string))
304         }
305
306         return m.store.GetAccountByAlias(alias)
307 }
308
309 // FindByID returns an account's Signer record by its ID.
310 func (m *Manager) FindByID(id string) (*Account, error) {
311         m.cacheMu.Lock()
312         cachedAccount, ok := m.cache.Get(id)
313         m.cacheMu.Unlock()
314         if ok {
315                 return cachedAccount.(*Account), nil
316         }
317
318         account, err := m.store.GetAccountByID(id)
319         if err != nil {
320                 return nil, err
321         }
322
323         m.cacheMu.Lock()
324         m.cache.Add(id, account)
325         m.cacheMu.Unlock()
326         return account, nil
327 }
328
329 // GetAccountByProgram return Account by given CtrlProgram
330 func (m *Manager) GetAccountByProgram(program *CtrlProgram) (*Account, error) {
331         return m.store.GetAccountByID(program.AccountID)
332 }
333
334 // GetAccountByXPubsIndex get account by xPubs and index
335 func (m *Manager) GetAccountByXPubsIndex(xPubs []chainkd.XPub, index uint64) (*Account, error) {
336         accounts, err := m.ListAccounts("")
337         if err != nil {
338                 return nil, err
339         }
340
341         for _, account := range accounts {
342                 if reflect.DeepEqual(account.XPubs, xPubs) && account.KeyIndex == index {
343                         return account, nil
344                 }
345         }
346         return nil, ErrFindAccount
347 }
348
349 // GetAliasByID return the account alias by given ID
350 func (m *Manager) GetAliasByID(id string) string {
351         account, err := m.store.GetAccountByID(id)
352         if err != nil {
353                 log.Warn("GetAliasByID fail to find account")
354                 return ""
355         }
356         return account.Alias
357 }
358
359 func (m *Manager) GetCoinbaseArbitrary() []byte {
360         if arbitrary := m.store.GetCoinbaseArbitrary(); arbitrary != nil {
361                 return arbitrary
362         }
363         return []byte{}
364 }
365
366 // GetCoinbaseControlProgram will return a coinbase script
367 func (m *Manager) GetCoinbaseControlProgram() ([]byte, error) {
368         cp, err := m.GetCoinbaseCtrlProgram()
369         if err == ErrFindAccount {
370                 log.Warningf("GetCoinbaseControlProgram: can't find any account in db")
371                 return vmutil.DefaultCoinbaseProgram()
372         }
373
374         if err != nil {
375                 return nil, err
376         }
377
378         return cp.ControlProgram, nil
379 }
380
381 // GetCoinbaseCtrlProgram will return the coinbase CtrlProgram
382 func (m *Manager) GetCoinbaseCtrlProgram() (*CtrlProgram, error) {
383         if cp, err := m.store.GetMiningAddress(); err == nil {
384                 return cp, nil
385         } else if err != ErrFindMiningAddress {
386                 return nil, err
387         }
388
389         account := new(Account)
390         accounts, err := m.store.ListAccounts("")
391         if err != nil {
392                 return nil, err
393         }
394
395         if len(accounts) > 0 {
396                 account = accounts[0]
397         } else {
398                 return nil, ErrFindAccount
399         }
400
401         program, err := m.CreateAddress(account.ID, false)
402         if err != nil {
403                 return nil, err
404         }
405
406         if err := m.store.SetMiningAddress(program); err != nil {
407                 return nil, err
408         }
409
410         return program, nil
411 }
412
413 // GetBip44ContractIndex return the current bip44 contract index
414 func (m *Manager) GetBip44ContractIndex(accountID string, change bool) uint64 {
415         return m.store.GetBip44ContractIndex(accountID, change)
416 }
417
418 // GetLocalCtrlProgramByAddress return CtrlProgram by given address
419 func (m *Manager) GetLocalCtrlProgramByAddress(address string) (*CtrlProgram, error) {
420         program, err := m.getProgramByAddress(address)
421         if err != nil {
422                 return nil, err
423         }
424
425         var hash [32]byte
426         sha3pool.Sum256(hash[:], program)
427
428         cp, err := m.store.GetControlProgram(bc.NewHash(hash))
429         if err != nil {
430                 return nil, err
431         }
432
433         return cp, nil
434 }
435
436 // GetMiningAddress will return the mining address
437 func (m *Manager) GetMiningAddress() (string, error) {
438         cp, err := m.GetCoinbaseCtrlProgram()
439         if err != nil {
440                 return "", err
441         }
442
443         return cp.Address, nil
444 }
445
446 // IsLocalControlProgram check is the input control program belong to local
447 func (m *Manager) IsLocalControlProgram(prog []byte) bool {
448         var hash [32]byte
449         sha3pool.Sum256(hash[:], prog)
450         cp, err := m.store.GetControlProgram(bc.NewHash(hash))
451         if err != nil || cp == nil {
452                 return false
453         }
454         return true
455 }
456
457 // ListAccounts will return the accounts in the db
458 func (m *Manager) ListAccounts(id string) ([]*Account, error) {
459         return m.store.ListAccounts(id)
460 }
461
462 // ListControlProgram return all the local control program
463 func (m *Manager) ListControlProgram() ([]*CtrlProgram, error) {
464         return m.store.ListControlPrograms()
465 }
466
467 func (m *Manager) ListUnconfirmedUtxo(accountID string, isSmartContract bool) []*UTXO {
468         utxos := m.utxoKeeper.ListUnconfirmed()
469         result := []*UTXO{}
470         for _, utxo := range utxos {
471                 if segwit.IsP2WScript(utxo.ControlProgram) != isSmartContract && (accountID == utxo.AccountID || accountID == "") {
472                         result = append(result, utxo)
473                 }
474         }
475         return result
476 }
477
478 // RemoveUnconfirmedUtxo remove utxos from the utxoKeeper
479 func (m *Manager) RemoveUnconfirmedUtxo(hashes []*bc.Hash) {
480         m.utxoKeeper.RemoveUnconfirmedUtxo(hashes)
481 }
482
483 // SetMiningAddress will set the mining address
484 func (m *Manager) SetMiningAddress(miningAddress string) (string, error) {
485         program, err := m.getProgramByAddress(miningAddress)
486         if err != nil {
487                 return "", err
488         }
489
490         cp := &CtrlProgram{
491                 Address:        miningAddress,
492                 ControlProgram: program,
493         }
494         if err := m.store.SetMiningAddress(cp); err != nil {
495                 return cp.Address, err
496         }
497
498         return m.GetMiningAddress()
499 }
500
501 func (m *Manager) SetCoinbaseArbitrary(arbitrary []byte) {
502         m.store.SetCoinbaseArbitrary(arbitrary)
503 }
504
505 // CreateCtrlProgram generate an address for the select account
506 func CreateCtrlProgram(account *Account, addrIdx uint64, change bool) (cp *CtrlProgram, err error) {
507         path, err := signers.Path(account.Signer, signers.AccountKeySpace, change, addrIdx)
508         if err != nil {
509                 return nil, err
510         }
511
512         if len(account.XPubs) == 1 {
513                 cp, err = createP2PKH(account, path)
514         } else {
515                 cp, err = createP2SH(account, path)
516         }
517
518         if err != nil {
519                 return nil, err
520         }
521
522         cp.KeyIndex, cp.Change = addrIdx, change
523         return cp, nil
524 }
525
526 func createP2PKH(account *Account, path [][]byte) (*CtrlProgram, error) {
527         derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
528         derivedPK := derivedXPubs[0].PublicKey()
529         pubHash := crypto.Ripemd160(derivedPK)
530
531         address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
532         if err != nil {
533                 return nil, err
534         }
535
536         control, err := vmutil.P2WPKHProgram([]byte(pubHash))
537         if err != nil {
538                 return nil, err
539         }
540
541         return &CtrlProgram{
542                 AccountID:      account.ID,
543                 Address:        address.EncodeAddress(),
544                 ControlProgram: control,
545         }, nil
546 }
547
548 func createP2SH(account *Account, path [][]byte) (*CtrlProgram, error) {
549         derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
550         derivedPKs := chainkd.XPubKeys(derivedXPubs)
551         signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
552         if err != nil {
553                 return nil, err
554         }
555
556         scriptHash := crypto.Sha256(signScript)
557         address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
558         if err != nil {
559                 return nil, err
560         }
561
562         control, err := vmutil.P2WSHProgram(scriptHash)
563         if err != nil {
564                 return nil, err
565         }
566
567         return &CtrlProgram{
568                 AccountID:      account.ID,
569                 Address:        address.EncodeAddress(),
570                 ControlProgram: control,
571         }, nil
572 }
573
574 func (m *Manager) GetContractIndex(accountID string) uint64 {
575         return m.store.GetContractIndex(accountID)
576 }
577
578 func (m *Manager) getCurrentContractIndex(account *Account, change bool) (uint64, error) {
579         switch account.DeriveRule {
580         case signers.BIP0032:
581                 return m.store.GetContractIndex(account.ID), nil
582         case signers.BIP0044:
583                 return m.store.GetBip44ContractIndex(account.ID, change), nil
584         }
585         return 0, ErrDeriveRule
586 }
587
588 func (m *Manager) getProgramByAddress(address string) ([]byte, error) {
589         addr, err := common.DecodeAddress(address, &consensus.ActiveNetParams)
590         if err != nil {
591                 return nil, err
592         }
593
594         redeemContract := addr.ScriptAddress()
595         program := []byte{}
596         switch addr.(type) {
597         case *common.AddressWitnessPubKeyHash:
598                 program, err = vmutil.P2WPKHProgram(redeemContract)
599         case *common.AddressWitnessScriptHash:
600                 program, err = vmutil.P2WSHProgram(redeemContract)
601         default:
602                 return nil, ErrInvalidAddress
603         }
604         if err != nil {
605                 return nil, err
606         }
607
608         return program, nil
609 }
610
611 func (m *Manager) saveControlProgram(prog *CtrlProgram, updateIndex bool) error {
612         var hash [32]byte
613
614         sha3pool.Sum256(hash[:], prog.ControlProgram)
615         acct, err := m.GetAccountByProgram(prog)
616         if err != nil {
617                 return err
618         }
619
620         newStore := m.store.InitBatch()
621
622         if err := newStore.SetControlProgram(bc.NewHash(hash), prog); err != nil {
623                 return nil
624         }
625
626         if updateIndex {
627                 switch acct.DeriveRule {
628                 case signers.BIP0032:
629                         newStore.SetContractIndex(acct.ID, prog.KeyIndex)
630                 case signers.BIP0044:
631                         newStore.SetBip44ContractIndex(acct.ID, prog.Change, prog.KeyIndex)
632                 }
633         }
634
635         return newStore.CommitBatch()
636 }
637
638 // SaveControlPrograms save account control programs
639 func (m *Manager) SaveControlPrograms(progs ...*CtrlProgram) error {
640         m.addressMu.Lock()
641         defer m.addressMu.Unlock()
642
643         for _, prog := range progs {
644                 acct, err := m.GetAccountByProgram(prog)
645                 if err != nil {
646                         return err
647                 }
648
649                 currentIndex, err := m.getCurrentContractIndex(acct, prog.Change)
650                 if err != nil {
651                         return err
652                 }
653
654                 m.saveControlProgram(prog, prog.KeyIndex > currentIndex)
655         }
656         return nil
657 }
658
659 func (m *Manager) SetStandardUTXO(outputID bc.Hash, utxo *UTXO) error {
660         return m.store.SetStandardUTXO(outputID, utxo)
661 }
662
663 func (m *Manager) DeleteStandardUTXO(outputID bc.Hash) {
664         m.store.DeleteStandardUTXO(outputID)
665 }
666
667 func (m *Manager) GetControlProgram(hash bc.Hash) (*CtrlProgram, error) {
668         return m.store.GetControlProgram(hash)
669 }