OSDN Git Service

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