OSDN Git Service

add InitStore
[bytom/vapor.git] / wallet / wallet_test.go
1 package wallet
2
3 import (
4         "encoding/binary"
5         "encoding/hex"
6         "encoding/json"
7         "fmt"
8         "io/ioutil"
9         "os"
10         "sort"
11         "strings"
12         "testing"
13         "time"
14
15         "github.com/vapor/account"
16         acc "github.com/vapor/account"
17         "github.com/vapor/asset"
18         "github.com/vapor/blockchain/query"
19         "github.com/vapor/blockchain/signers"
20         "github.com/vapor/blockchain/txbuilder"
21         "github.com/vapor/common"
22         "github.com/vapor/config"
23         "github.com/vapor/consensus"
24         "github.com/vapor/crypto/ed25519/chainkd"
25         "github.com/vapor/crypto/sha3pool"
26         dbm "github.com/vapor/database/leveldb"
27         "github.com/vapor/errors"
28         "github.com/vapor/event"
29         "github.com/vapor/protocol"
30         "github.com/vapor/protocol/bc"
31         "github.com/vapor/protocol/bc/types"
32 )
33
34 func TestEncodeDecodeGlobalTxIndex(t *testing.T) {
35         want := &struct {
36                 BlockHash bc.Hash
37                 Position  uint64
38         }{
39                 BlockHash: bc.NewHash([32]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20}),
40                 Position:  1,
41         }
42
43         globalTxIdx := CalcGlobalTxIndex(&want.BlockHash, want.Position)
44         blockHashGot, positionGot := parseGlobalTxIdx(globalTxIdx)
45         if *blockHashGot != want.BlockHash {
46                 t.Errorf("blockHash mismatch. Get: %v. Expect: %v", *blockHashGot, want.BlockHash)
47         }
48
49         if positionGot != want.Position {
50                 t.Errorf("position mismatch. Get: %v. Expect: %v", positionGot, want.Position)
51         }
52 }
53
54 func TestWalletVersion(t *testing.T) {
55         // prepare wallet
56         dirPath, err := ioutil.TempDir(".", "")
57         if err != nil {
58                 t.Fatal(err)
59         }
60         defer os.RemoveAll(dirPath)
61
62         testDB := dbm.NewDB("testdb", "leveldb", "temp")
63         walletStore := NewMockWalletStore(testDB)
64         defer func() {
65                 testDB.Close()
66                 os.RemoveAll("temp")
67         }()
68
69         dispatcher := event.NewDispatcher()
70         w := mockWallet(walletStore, nil, nil, nil, dispatcher, false)
71
72         walletStatus := new(StatusInfo)
73         if err := w.store.SetWalletInfo(walletStatus); err != nil {
74                 t.Fatal(err)
75         }
76
77         status, err := w.store.GetWalletInfo()
78         if err != nil {
79                 t.Fatal(err)
80         }
81         w.Status = *status
82
83         if err := w.checkWalletInfo(); err != errWalletVersionMismatch {
84                 t.Fatal("fail to detect legacy wallet version")
85         }
86
87         // lower wallet version test case
88         lowerVersion := StatusInfo{Version: currentVersion - 1}
89         if err := w.store.SetWalletInfo(&lowerVersion); err != nil {
90                 t.Fatal(err)
91         }
92
93         status, err = w.store.GetWalletInfo()
94         if err != nil {
95                 t.Fatal(err)
96         }
97         w.Status = *status
98
99         if err := w.checkWalletInfo(); err != errWalletVersionMismatch {
100                 t.Fatal("fail to detect expired wallet version")
101         }
102 }
103
104 func mockUTXO(controlProg *account.CtrlProgram, assetID *bc.AssetID) *account.UTXO {
105         utxo := &account.UTXO{}
106         utxo.OutputID = bc.Hash{V0: 1}
107         utxo.SourceID = bc.Hash{V0: 2}
108         utxo.AssetID = *assetID
109         utxo.Amount = 1000000000
110         utxo.SourcePos = 0
111         utxo.ControlProgram = controlProg.ControlProgram
112         utxo.AccountID = controlProg.AccountID
113         utxo.Address = controlProg.Address
114         utxo.ControlProgramIndex = controlProg.KeyIndex
115         return utxo
116 }
117
118 func mockTxData(utxos []*account.UTXO, testAccount *account.Account) (*txbuilder.Template, *types.TxData, error) {
119         tplBuilder := txbuilder.NewBuilder(time.Now())
120
121         for _, utxo := range utxos {
122                 txInput, sigInst, err := account.UtxoToInputs(testAccount.Signer, utxo)
123                 if err != nil {
124                         return nil, nil, err
125                 }
126                 tplBuilder.AddInput(txInput, sigInst)
127
128                 out := &types.TxOutput{}
129                 if utxo.AssetID == *consensus.BTMAssetID {
130                         out = types.NewIntraChainOutput(utxo.AssetID, 100, utxo.ControlProgram)
131                 } else {
132                         out = types.NewIntraChainOutput(utxo.AssetID, utxo.Amount, utxo.ControlProgram)
133                 }
134                 tplBuilder.AddOutput(out)
135         }
136
137         return tplBuilder.Build()
138 }
139
140 func mockWallet(store WalletStore, account *account.Manager, asset *asset.Registry, chain *protocol.Chain, dispatcher *event.Dispatcher, txIndexFlag bool) *Wallet {
141         wallet := &Wallet{
142                 store:           store,
143                 AccountMgr:      account,
144                 AssetReg:        asset,
145                 chain:           chain,
146                 RecoveryMgr:     newRecoveryManager(store, account),
147                 EventDispatcher: dispatcher,
148                 TxIndexFlag:     txIndexFlag,
149         }
150         wallet.txMsgSub, _ = wallet.EventDispatcher.Subscribe(protocol.TxMsgEvent{})
151         return wallet
152 }
153
154 func mockSingleBlock(tx *types.Tx) *types.Block {
155         return &types.Block{
156                 BlockHeader: types.BlockHeader{
157                         Version: 1,
158                         Height:  1,
159                 },
160                 Transactions: []*types.Tx{config.GenesisTx(), tx},
161         }
162 }
163
164 // errors
165 var (
166         errAccntTxIDNotFound = errors.New("account TXID not found")
167         errGetAsset          = errors.New("Failed to find asset definition")
168 )
169
170 const (
171         utxoPrefix byte = iota //UTXOPrefix is StandardUTXOKey prefix
172         contractPrefix
173         contractIndexPrefix
174         accountPrefix // AccountPrefix is account ID prefix
175         accountIndexPrefix
176 )
177
178 // leveldb key prefix
179 var (
180         colon               byte = 0x3a
181         accountStore             = []byte("AS:")
182         UTXOPrefix               = append(accountStore, utxoPrefix, colon)
183         ContractPrefix           = append(accountStore, contractPrefix, colon)
184         ContractIndexPrefix      = append(accountStore, contractIndexPrefix, colon)
185         AccountPrefix            = append(accountStore, accountPrefix, colon) // AccountPrefix is account ID prefix
186         AccountIndexPrefix       = append(accountStore, accountIndexPrefix, colon)
187 )
188
189 const (
190         sutxoPrefix byte = iota //SUTXOPrefix is ContractUTXOKey prefix
191         accountAliasPrefix
192         txPrefix            //TxPrefix is wallet database transactions prefix
193         txIndexPrefix       //TxIndexPrefix is wallet database tx index prefix
194         unconfirmedTxPrefix //UnconfirmedTxPrefix is txpool unconfirmed transactions prefix
195         globalTxIndexPrefix //GlobalTxIndexPrefix is wallet database global tx index prefix
196         walletKey
197         miningAddressKey
198         coinbaseAbKey
199         recoveryKey //recoveryKey key for db store recovery info.
200 )
201
202 var (
203         walletStore         = []byte("WS:")
204         SUTXOPrefix         = append(walletStore, sutxoPrefix, colon)
205         AccountAliasPrefix  = append(walletStore, accountAliasPrefix, colon)
206         TxPrefix            = append(walletStore, txPrefix, colon)            //TxPrefix is wallet database transactions prefix
207         TxIndexPrefix       = append(walletStore, txIndexPrefix, colon)       //TxIndexPrefix is wallet database tx index prefix
208         UnconfirmedTxPrefix = append(walletStore, unconfirmedTxPrefix, colon) //UnconfirmedTxPrefix is txpool unconfirmed transactions prefix
209         GlobalTxIndexPrefix = append(walletStore, globalTxIndexPrefix, colon) //GlobalTxIndexPrefix is wallet database global tx index prefix
210         WalletKey           = append(walletStore, walletKey)
211         MiningAddressKey    = append(walletStore, miningAddressKey)
212         CoinbaseAbKey       = append(walletStore, coinbaseAbKey)
213         RecoveryKey         = append(walletStore, recoveryKey)
214 )
215
216 func accountIndexKey(xpubs []chainkd.XPub) []byte {
217         var hash [32]byte
218         var xPubs []byte
219         cpy := append([]chainkd.XPub{}, xpubs[:]...)
220         sort.Sort(signers.SortKeys(cpy))
221         for _, xpub := range cpy {
222                 xPubs = append(xPubs, xpub[:]...)
223         }
224         sha3pool.Sum256(hash[:], xPubs)
225         return append([]byte(AccountIndexPrefix), hash[:]...)
226 }
227
228 func Bip44ContractIndexKey(accountID string, change bool) []byte {
229         key := append([]byte(ContractIndexPrefix), accountID...)
230         if change {
231                 return append(key, []byte{1}...)
232         }
233         return append(key, []byte{0}...)
234 }
235
236 // ContractKey account control promgram store prefix
237 func ContractKey(hash bc.Hash) []byte {
238         return append([]byte(ContractPrefix), hash.Bytes()...)
239 }
240
241 // AccountIDKey account id store prefix
242 func AccountIDKey(accountID string) []byte {
243         return append([]byte(AccountPrefix), []byte(accountID)...)
244 }
245
246 // StandardUTXOKey makes an account unspent outputs key to store
247 func StandardUTXOKey(id bc.Hash) []byte {
248         return append(UTXOPrefix, id.Bytes()...)
249 }
250
251 // ContractUTXOKey makes a smart contract unspent outputs key to store
252 func ContractUTXOKey(id bc.Hash) []byte {
253         return append(SUTXOPrefix, id.Bytes()...)
254 }
255
256 func calcDeleteKey(blockHeight uint64) []byte {
257         return []byte(fmt.Sprintf("%s%016x", TxPrefix, blockHeight))
258 }
259
260 func calcTxIndexKey(txID string) []byte {
261         return append(TxIndexPrefix, []byte(txID)...)
262 }
263
264 func calcAnnotatedKey(formatKey string) []byte {
265         return append(TxPrefix, []byte(formatKey)...)
266 }
267
268 func calcUnconfirmedTxKey(formatKey string) []byte {
269         return append(UnconfirmedTxPrefix, []byte(formatKey)...)
270 }
271
272 func CalcGlobalTxIndexKey(txID string) []byte {
273         return append(GlobalTxIndexPrefix, []byte(txID)...)
274 }
275
276 func CalcGlobalTxIndex(blockHash *bc.Hash, position uint64) []byte {
277         txIdx := make([]byte, 40)
278         copy(txIdx[:32], blockHash.Bytes())
279         binary.BigEndian.PutUint64(txIdx[32:], position)
280         return txIdx
281 }
282
283 func formatKey(blockHeight uint64, position uint32) string {
284         return fmt.Sprintf("%016x%08x", blockHeight, position)
285 }
286
287 func contractIndexKey(accountID string) []byte {
288         return append([]byte(ContractIndexPrefix), []byte(accountID)...)
289 }
290
291 func accountAliasKey(name string) []byte {
292         return append([]byte(AccountAliasPrefix), []byte(name)...)
293 }
294
295 // MockWalletStore store wallet using leveldb
296 type MockWalletStore struct {
297         db    dbm.DB
298         batch dbm.Batch
299 }
300
301 // NewMockWalletStore create new MockWalletStore struct
302 func NewMockWalletStore(db dbm.DB) *MockWalletStore {
303         return &MockWalletStore{
304                 db:    db,
305                 batch: nil,
306         }
307 }
308
309 // InitBatch initial batch
310 func (store *MockWalletStore) InitBatch() error {
311         if store.batch != nil {
312                 return errors.New("MockWalletStore initail fail, store batch is not nil.")
313         }
314         store.batch = store.db.NewBatch()
315         return nil
316 }
317
318 // CommitBatch commit batch
319 func (store *MockWalletStore) CommitBatch() error {
320         if store.batch == nil {
321                 return errors.New("MockWalletStore commit fail, store batch is nil.")
322         }
323         store.batch.Write()
324         store.batch = nil
325         return nil
326 }
327
328 // DeleteContractUTXO delete contract utxo by outputID
329 func (store *MockWalletStore) DeleteContractUTXO(outputID bc.Hash) {
330         if store.batch == nil {
331                 store.db.Delete(ContractUTXOKey(outputID))
332         } else {
333                 store.batch.Delete(ContractUTXOKey(outputID))
334         }
335 }
336
337 // DeleteRecoveryStatus delete recovery status
338 func (store *MockWalletStore) DeleteRecoveryStatus() {
339         if store.batch == nil {
340                 store.db.Delete(RecoveryKey)
341         } else {
342                 store.batch.Delete(RecoveryKey)
343         }
344 }
345
346 // DeleteTransactions delete transactions when orphan block rollback
347 func (store *MockWalletStore) DeleteTransactions(height uint64) {
348         batch := store.db.NewBatch()
349         if store.batch != nil {
350                 batch = store.batch
351         }
352         txIter := store.db.IteratorPrefix(calcDeleteKey(height))
353         defer txIter.Release()
354
355         tmpTx := query.AnnotatedTx{}
356         for txIter.Next() {
357                 if err := json.Unmarshal(txIter.Value(), &tmpTx); err == nil {
358                         batch.Delete(calcTxIndexKey(tmpTx.ID.String()))
359                 }
360                 batch.Delete(txIter.Key())
361         }
362         if store.batch == nil {
363                 batch.Write()
364         }
365 }
366
367 // DeleteUnconfirmedTransaction delete unconfirmed tx by txID
368 func (store *MockWalletStore) DeleteUnconfirmedTransaction(txID string) {
369         if store.batch == nil {
370                 store.db.Delete(calcUnconfirmedTxKey(txID))
371         } else {
372                 store.batch.Delete(calcUnconfirmedTxKey(txID))
373         }
374 }
375
376 // DeleteWalletTransactions delete all txs in wallet
377 func (store *MockWalletStore) DeleteWalletTransactions() {
378         batch := store.db.NewBatch()
379         if store.batch != nil {
380                 batch = store.batch
381         }
382         txIter := store.db.IteratorPrefix([]byte(TxPrefix))
383         defer txIter.Release()
384
385         for txIter.Next() {
386                 batch.Delete(txIter.Key())
387         }
388
389         txIndexIter := store.db.IteratorPrefix([]byte(TxIndexPrefix))
390         defer txIndexIter.Release()
391
392         for txIndexIter.Next() {
393                 batch.Delete(txIndexIter.Key())
394         }
395         if store.batch == nil {
396                 batch.Write()
397         }
398 }
399
400 // DeleteWalletUTXOs delete all txs in wallet
401 func (store *MockWalletStore) DeleteWalletUTXOs() {
402         batch := store.db.NewBatch()
403         if store.batch != nil {
404                 batch = store.batch
405         }
406         ruIter := store.db.IteratorPrefix([]byte(UTXOPrefix))
407         defer ruIter.Release()
408         for ruIter.Next() {
409                 batch.Delete(ruIter.Key())
410         }
411
412         suIter := store.db.IteratorPrefix([]byte(SUTXOPrefix))
413         defer suIter.Release()
414         for suIter.Next() {
415                 batch.Delete(suIter.Key())
416         }
417         if store.batch == nil {
418                 batch.Write()
419         }
420 }
421
422 // GetAsset get asset by assetID
423 func (store *MockWalletStore) GetAsset(assetID *bc.AssetID) (*asset.Asset, error) {
424         definitionByte := store.db.Get(asset.ExtAssetKey(assetID))
425         if definitionByte == nil {
426                 return nil, errGetAsset
427         }
428         definitionMap := make(map[string]interface{})
429         if err := json.Unmarshal(definitionByte, &definitionMap); err != nil {
430                 return nil, err
431         }
432         alias := assetID.String()
433         externalAsset := &asset.Asset{
434                 AssetID:           *assetID,
435                 Alias:             &alias,
436                 DefinitionMap:     definitionMap,
437                 RawDefinitionByte: definitionByte,
438         }
439         return externalAsset, nil
440 }
441
442 // GetGlobalTransactionIndex get global tx by txID
443 func (store *MockWalletStore) GetGlobalTransactionIndex(txID string) []byte {
444         return store.db.Get(CalcGlobalTxIndexKey(txID))
445 }
446
447 // GetStandardUTXO get standard utxo by id
448 func (store *MockWalletStore) GetStandardUTXO(outid bc.Hash) (*acc.UTXO, error) {
449         rawUTXO := store.db.Get(StandardUTXOKey(outid))
450         if rawUTXO == nil {
451                 return nil, fmt.Errorf("failed get standard UTXO, outputID: %s ", outid.String())
452         }
453         UTXO := new(acc.UTXO)
454         if err := json.Unmarshal(rawUTXO, UTXO); err != nil {
455                 return nil, err
456         }
457         return UTXO, nil
458 }
459
460 // GetTransaction get tx by txid
461 func (store *MockWalletStore) GetTransaction(txID string) (*query.AnnotatedTx, error) {
462         formatKey := store.db.Get(calcTxIndexKey(txID))
463         if formatKey == nil {
464                 return nil, errAccntTxIDNotFound
465         }
466         rawTx := store.db.Get(calcAnnotatedKey(string(formatKey)))
467         tx := new(query.AnnotatedTx)
468         if err := json.Unmarshal(rawTx, tx); err != nil {
469                 return nil, err
470         }
471         return tx, nil
472 }
473
474 // GetUnconfirmedTransaction get unconfirmed tx by txID
475 func (store *MockWalletStore) GetUnconfirmedTransaction(txID string) (*query.AnnotatedTx, error) {
476         rawUnconfirmedTx := store.db.Get(calcUnconfirmedTxKey(txID))
477         if rawUnconfirmedTx == nil {
478                 return nil, fmt.Errorf("failed get unconfirmed tx, txID: %s ", txID)
479         }
480         tx := new(query.AnnotatedTx)
481         if err := json.Unmarshal(rawUnconfirmedTx, tx); err != nil {
482                 return nil, err
483         }
484         return tx, nil
485 }
486
487 // GetRecoveryStatus delete recovery status
488 func (store *MockWalletStore) GetRecoveryStatus() (*RecoveryState, error) {
489         rawStatus := store.db.Get(RecoveryKey)
490         if rawStatus == nil {
491                 return nil, ErrGetRecoveryStatus
492         }
493         state := new(RecoveryState)
494         if err := json.Unmarshal(rawStatus, state); err != nil {
495                 return nil, err
496         }
497         return state, nil
498 }
499
500 // GetWalletInfo get wallet information
501 func (store *MockWalletStore) GetWalletInfo() (*StatusInfo, error) {
502         rawStatus := store.db.Get([]byte(WalletKey))
503         if rawStatus == nil {
504                 return nil, fmt.Errorf("failed get wallet info")
505         }
506         status := new(StatusInfo)
507         if err := json.Unmarshal(rawStatus, status); err != nil {
508                 return nil, err
509         }
510         return status, nil
511 }
512
513 // ListAccountUTXOs get all account unspent outputs
514 func (store *MockWalletStore) ListAccountUTXOs(id string, isSmartContract bool) ([]*acc.UTXO, error) {
515         prefix := UTXOPrefix
516         if isSmartContract {
517                 prefix = SUTXOPrefix
518         }
519
520         idBytes, err := hex.DecodeString(id)
521         if err != nil {
522                 return nil, err
523         }
524
525         accountUtxoIter := store.db.IteratorPrefix(append(prefix, idBytes...))
526         defer accountUtxoIter.Release()
527
528         confirmedUTXOs := []*acc.UTXO{}
529         for accountUtxoIter.Next() {
530                 utxo := new(acc.UTXO)
531                 if err := json.Unmarshal(accountUtxoIter.Value(), utxo); err != nil {
532                         return nil, err
533                 }
534
535                 confirmedUTXOs = append(confirmedUTXOs, utxo)
536         }
537         return confirmedUTXOs, nil
538 }
539
540 func (store *MockWalletStore) ListTransactions(accountID string, StartTxID string, count uint, unconfirmed bool) ([]*query.AnnotatedTx, error) {
541         annotatedTxs := []*query.AnnotatedTx{}
542         var startKey []byte
543         preFix := TxPrefix
544
545         if StartTxID != "" {
546                 if unconfirmed {
547                         startKey = calcUnconfirmedTxKey(StartTxID)
548                 } else {
549                         formatKey := store.db.Get(calcTxIndexKey(StartTxID))
550                         if formatKey == nil {
551                                 return nil, errAccntTxIDNotFound
552                         }
553                         startKey = calcAnnotatedKey(string(formatKey))
554                 }
555         }
556
557         if unconfirmed {
558                 preFix = UnconfirmedTxPrefix
559         }
560
561         itr := store.db.IteratorPrefixWithStart([]byte(preFix), startKey, true)
562         defer itr.Release()
563
564         for txNum := count; itr.Next() && txNum > 0; txNum-- {
565                 annotatedTx := new(query.AnnotatedTx)
566                 if err := json.Unmarshal(itr.Value(), &annotatedTx); err != nil {
567                         return nil, err
568                 }
569                 annotatedTxs = append(annotatedTxs, annotatedTx)
570         }
571
572         return annotatedTxs, nil
573 }
574
575 // ListUnconfirmedTransactions get all unconfirmed txs
576 func (store *MockWalletStore) ListUnconfirmedTransactions() ([]*query.AnnotatedTx, error) {
577         annotatedTxs := []*query.AnnotatedTx{}
578         txIter := store.db.IteratorPrefix([]byte(UnconfirmedTxPrefix))
579         defer txIter.Release()
580
581         for txIter.Next() {
582                 annotatedTx := &query.AnnotatedTx{}
583                 if err := json.Unmarshal(txIter.Value(), &annotatedTx); err != nil {
584                         return nil, err
585                 }
586                 annotatedTxs = append(annotatedTxs, annotatedTx)
587         }
588         return annotatedTxs, nil
589 }
590
591 // SetAssetDefinition set assetID and definition
592 func (store *MockWalletStore) SetAssetDefinition(assetID *bc.AssetID, definition []byte) {
593         if store.batch == nil {
594                 store.db.Set(asset.ExtAssetKey(assetID), definition)
595         } else {
596                 store.batch.Set(asset.ExtAssetKey(assetID), definition)
597         }
598 }
599
600 // SetContractUTXO set standard utxo
601 func (store *MockWalletStore) SetContractUTXO(outputID bc.Hash, utxo *acc.UTXO) error {
602         data, err := json.Marshal(utxo)
603         if err != nil {
604                 return err
605         }
606         if store.batch == nil {
607                 store.db.Set(ContractUTXOKey(outputID), data)
608         } else {
609                 store.batch.Set(ContractUTXOKey(outputID), data)
610         }
611         return nil
612 }
613
614 // SetGlobalTransactionIndex set global tx index by blockhash and position
615 func (store *MockWalletStore) SetGlobalTransactionIndex(globalTxID string, blockHash *bc.Hash, position uint64) {
616         if store.batch == nil {
617                 store.db.Set(CalcGlobalTxIndexKey(globalTxID), CalcGlobalTxIndex(blockHash, position))
618         } else {
619                 store.batch.Set(CalcGlobalTxIndexKey(globalTxID), CalcGlobalTxIndex(blockHash, position))
620         }
621 }
622
623 // SetRecoveryStatus set recovery status
624 func (store *MockWalletStore) SetRecoveryStatus(recoveryState *RecoveryState) error {
625         rawStatus, err := json.Marshal(recoveryState)
626         if err != nil {
627                 return err
628         }
629         if store.batch == nil {
630                 store.db.Set(RecoveryKey, rawStatus)
631         } else {
632                 store.batch.Set(RecoveryKey, rawStatus)
633         }
634         return nil
635 }
636
637 // SetTransaction set raw transaction by block height and tx position
638 func (store *MockWalletStore) SetTransaction(height uint64, tx *query.AnnotatedTx) error {
639         batch := store.db.NewBatch()
640         if store.batch != nil {
641                 batch = store.batch
642         }
643
644         rawTx, err := json.Marshal(tx)
645         if err != nil {
646                 return err
647         }
648         batch.Set(calcAnnotatedKey(formatKey(height, tx.Position)), rawTx)
649         batch.Set(calcTxIndexKey(tx.ID.String()), []byte(formatKey(height, tx.Position)))
650
651         if store.batch == nil {
652                 batch.Write()
653         }
654         return nil
655 }
656
657 // SetUnconfirmedTransaction set unconfirmed tx by txID
658 func (store *MockWalletStore) SetUnconfirmedTransaction(txID string, tx *query.AnnotatedTx) error {
659         rawTx, err := json.Marshal(tx)
660         if err != nil {
661                 return err
662         }
663         if store.batch == nil {
664                 store.db.Set(calcUnconfirmedTxKey(txID), rawTx)
665         } else {
666                 store.batch.Set(calcUnconfirmedTxKey(txID), rawTx)
667         }
668         return nil
669 }
670
671 // SetWalletInfo get wallet information
672 func (store *MockWalletStore) SetWalletInfo(status *StatusInfo) error {
673         rawWallet, err := json.Marshal(status)
674         if err != nil {
675                 return err
676         }
677
678         if store.batch == nil {
679                 store.db.Set([]byte(WalletKey), rawWallet)
680         } else {
681                 store.batch.Set([]byte(WalletKey), rawWallet)
682         }
683         return nil
684 }
685
686 type MockAccountStore struct {
687         db    dbm.DB
688         batch dbm.Batch
689 }
690
691 // NewAccountStore create new MockAccountStore.
692 func NewMockAccountStore(db dbm.DB) *MockAccountStore {
693         return &MockAccountStore{
694                 db:    db,
695                 batch: nil,
696         }
697 }
698
699 // InitStore initial batch
700 func (store *MockAccountStore) InitStore() acc.AccountStore {
701         newStore := NewMockAccountStore(store.db)
702         newStore.batch = newStore.db.NewBatch()
703         return newStore
704 }
705
706 // CommitBatch commit batch
707 func (store *MockAccountStore) CommitBatch() error {
708         if store.batch == nil {
709                 return errors.New("MockAccountStore commit fail, store batch is nil.")
710         }
711         store.batch.Write()
712         store.batch = nil
713         return nil
714 }
715
716 // DeleteAccount set account account ID, account alias and raw account.
717 func (store *MockAccountStore) DeleteAccount(account *acc.Account) error {
718         batch := store.db.NewBatch()
719         if store.batch != nil {
720                 batch = store.batch
721         }
722
723         // delete account utxos
724         store.deleteAccountUTXOs(account.ID, batch)
725
726         // delete account control program
727         if err := store.deleteAccountControlPrograms(account.ID, batch); err != nil {
728                 return err
729         }
730
731         // delete bip44 contract index
732         batch.Delete(Bip44ContractIndexKey(account.ID, false))
733         batch.Delete(Bip44ContractIndexKey(account.ID, true))
734
735         // delete contract index
736         batch.Delete(contractIndexKey(account.ID))
737
738         // delete account id
739         batch.Delete(AccountIDKey(account.ID))
740         batch.Delete(accountAliasKey(account.Alias))
741         if store.batch == nil {
742                 batch.Write()
743         }
744         return nil
745 }
746
747 // deleteAccountUTXOs delete account utxos by accountID
748 func (store *MockAccountStore) deleteAccountUTXOs(accountID string, batch dbm.Batch) error {
749         accountUtxoIter := store.db.IteratorPrefix([]byte(UTXOPrefix))
750         defer accountUtxoIter.Release()
751
752         for accountUtxoIter.Next() {
753                 accountUtxo := new(acc.UTXO)
754                 if err := json.Unmarshal(accountUtxoIter.Value(), accountUtxo); err != nil {
755                         return err
756                 }
757
758                 if accountID == accountUtxo.AccountID {
759                         batch.Delete(StandardUTXOKey(accountUtxo.OutputID))
760                 }
761         }
762
763         return nil
764 }
765
766 // deleteAccountControlPrograms deletes account control program
767 func (store *MockAccountStore) deleteAccountControlPrograms(accountID string, batch dbm.Batch) error {
768         cps, err := store.ListControlPrograms()
769         if err != nil {
770                 return err
771         }
772
773         var hash [32]byte
774         for _, cp := range cps {
775                 if cp.AccountID == accountID {
776                         sha3pool.Sum256(hash[:], cp.ControlProgram)
777                         batch.Delete(ContractKey(bc.NewHash(hash)))
778                 }
779         }
780         return nil
781 }
782
783 // DeleteStandardUTXO delete utxo by outpu id
784 func (store *MockAccountStore) DeleteStandardUTXO(outputID bc.Hash) {
785         if store.batch == nil {
786                 store.db.Delete(StandardUTXOKey(outputID))
787         } else {
788                 store.batch.Delete(StandardUTXOKey(outputID))
789         }
790 }
791
792 // GetAccountByAlias get account by account alias
793 func (store *MockAccountStore) GetAccountByAlias(accountAlias string) (*acc.Account, error) {
794         accountID := store.db.Get(accountAliasKey(accountAlias))
795         if accountID == nil {
796                 return nil, acc.ErrFindAccount
797         }
798         return store.GetAccountByID(string(accountID))
799 }
800
801 // GetAccountByID get account by accountID
802 func (store *MockAccountStore) GetAccountByID(accountID string) (*acc.Account, error) {
803         rawAccount := store.db.Get(AccountIDKey(accountID))
804         if rawAccount == nil {
805                 return nil, acc.ErrFindAccount
806         }
807         account := new(acc.Account)
808         if err := json.Unmarshal(rawAccount, account); err != nil {
809                 return nil, err
810         }
811         return account, nil
812 }
813
814 // GetAccountIndex get account index by account xpubs
815 func (store *MockAccountStore) GetAccountIndex(xpubs []chainkd.XPub) uint64 {
816         currentIndex := uint64(0)
817         if rawIndexBytes := store.db.Get(accountIndexKey(xpubs)); rawIndexBytes != nil {
818                 currentIndex = common.BytesToUnit64(rawIndexBytes)
819         }
820         return currentIndex
821 }
822
823 // GetBip44ContractIndex get bip44 contract index
824 func (store *MockAccountStore) GetBip44ContractIndex(accountID string, change bool) uint64 {
825         index := uint64(0)
826         if rawIndexBytes := store.db.Get(Bip44ContractIndexKey(accountID, change)); rawIndexBytes != nil {
827                 index = common.BytesToUnit64(rawIndexBytes)
828         }
829         return index
830 }
831
832 // GetCoinbaseArbitrary get coinbase arbitrary
833 func (store *MockAccountStore) GetCoinbaseArbitrary() []byte {
834         return store.db.Get([]byte(CoinbaseAbKey))
835 }
836
837 // GetContractIndex get contract index
838 func (store *MockAccountStore) GetContractIndex(accountID string) uint64 {
839         index := uint64(0)
840         if rawIndexBytes := store.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
841                 index = common.BytesToUnit64(rawIndexBytes)
842         }
843         return index
844 }
845
846 // GetControlProgram get control program
847 func (store *MockAccountStore) GetControlProgram(hash bc.Hash) (*acc.CtrlProgram, error) {
848         rawProgram := store.db.Get(ContractKey(hash))
849         if rawProgram == nil {
850                 return nil, acc.ErrFindCtrlProgram
851         }
852         cp := new(acc.CtrlProgram)
853         if err := json.Unmarshal(rawProgram, cp); err != nil {
854                 return nil, err
855         }
856         return cp, nil
857 }
858
859 // GetMiningAddress get mining address
860 func (store *MockAccountStore) GetMiningAddress() (*acc.CtrlProgram, error) {
861         rawCP := store.db.Get([]byte(MiningAddressKey))
862         if rawCP == nil {
863                 return nil, acc.ErrFindMiningAddress
864         }
865         cp := new(acc.CtrlProgram)
866         if err := json.Unmarshal(rawCP, cp); err != nil {
867                 return nil, err
868         }
869         return cp, nil
870 }
871
872 // GetUTXO get standard utxo by id
873 func (store *MockAccountStore) GetUTXO(outid bc.Hash) (*acc.UTXO, error) {
874         u := new(acc.UTXO)
875         if data := store.db.Get(StandardUTXOKey(outid)); data != nil {
876                 return u, json.Unmarshal(data, u)
877         }
878         if data := store.db.Get(ContractUTXOKey(outid)); data != nil {
879                 return u, json.Unmarshal(data, u)
880         }
881         return nil, acc.ErrMatchUTXO
882 }
883
884 // ListAccounts get all accounts which name prfix is id.
885 func (store *MockAccountStore) ListAccounts(id string) ([]*acc.Account, error) {
886         accounts := []*acc.Account{}
887         accountIter := store.db.IteratorPrefix(AccountIDKey(strings.TrimSpace(id)))
888         defer accountIter.Release()
889
890         for accountIter.Next() {
891                 account := new(acc.Account)
892                 if err := json.Unmarshal(accountIter.Value(), &account); err != nil {
893                         return nil, err
894                 }
895                 accounts = append(accounts, account)
896         }
897         return accounts, nil
898 }
899
900 // ListControlPrograms get all local control programs
901 func (store *MockAccountStore) ListControlPrograms() ([]*acc.CtrlProgram, error) {
902         cps := []*acc.CtrlProgram{}
903         cpIter := store.db.IteratorPrefix([]byte(ContractPrefix))
904         defer cpIter.Release()
905
906         for cpIter.Next() {
907                 cp := new(acc.CtrlProgram)
908                 if err := json.Unmarshal(cpIter.Value(), cp); err != nil {
909                         return nil, err
910                 }
911                 cps = append(cps, cp)
912         }
913         return cps, nil
914 }
915
916 // ListUTXOs get utxos by accountID
917 func (store *MockAccountStore) ListUTXOs() ([]*acc.UTXO, error) {
918         utxoIter := store.db.IteratorPrefix([]byte(UTXOPrefix))
919         defer utxoIter.Release()
920
921         utxos := []*acc.UTXO{}
922         for utxoIter.Next() {
923                 utxo := new(acc.UTXO)
924                 if err := json.Unmarshal(utxoIter.Value(), utxo); err != nil {
925                         return nil, err
926                 }
927                 utxos = append(utxos, utxo)
928         }
929         return utxos, nil
930 }
931
932 // SetAccount set account account ID, account alias and raw account.
933 func (store *MockAccountStore) SetAccount(account *acc.Account) error {
934         rawAccount, err := json.Marshal(account)
935         if err != nil {
936                 return acc.ErrMarshalAccount
937         }
938
939         batch := store.db.NewBatch()
940         if store.batch != nil {
941                 batch = store.batch
942         }
943
944         batch.Set(AccountIDKey(account.ID), rawAccount)
945         batch.Set(accountAliasKey(account.Alias), []byte(account.ID))
946
947         if store.batch == nil {
948                 batch.Write()
949         }
950         return nil
951 }
952
953 // SetAccountIndex update account index
954 func (store *MockAccountStore) SetAccountIndex(account *acc.Account) {
955         currentIndex := store.GetAccountIndex(account.XPubs)
956         if account.KeyIndex > currentIndex {
957                 if store.batch == nil {
958                         store.db.Set(accountIndexKey(account.XPubs), common.Unit64ToBytes(account.KeyIndex))
959                 } else {
960                         store.batch.Set(accountIndexKey(account.XPubs), common.Unit64ToBytes(account.KeyIndex))
961                 }
962         }
963 }
964
965 // SetBip44ContractIndex set contract index
966 func (store *MockAccountStore) SetBip44ContractIndex(accountID string, change bool, index uint64) {
967         if store.batch == nil {
968                 store.db.Set(Bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(index))
969         } else {
970                 store.batch.Set(Bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(index))
971         }
972 }
973
974 // SetCoinbaseArbitrary set coinbase arbitrary
975 func (store *MockAccountStore) SetCoinbaseArbitrary(arbitrary []byte) {
976         if store.batch == nil {
977                 store.db.Set([]byte(CoinbaseAbKey), arbitrary)
978         } else {
979                 store.batch.Set([]byte(CoinbaseAbKey), arbitrary)
980         }
981 }
982
983 // SetContractIndex set contract index
984 func (store *MockAccountStore) SetContractIndex(accountID string, index uint64) {
985         if store.batch == nil {
986                 store.db.Set(contractIndexKey(accountID), common.Unit64ToBytes(index))
987         } else {
988                 store.batch.Set(contractIndexKey(accountID), common.Unit64ToBytes(index))
989         }
990 }
991
992 // SetControlProgram set raw program
993 func (store *MockAccountStore) SetControlProgram(hash bc.Hash, program *acc.CtrlProgram) error {
994         accountCP, err := json.Marshal(program)
995         if err != nil {
996                 return err
997         }
998         if store.batch == nil {
999                 store.db.Set(ContractKey(hash), accountCP)
1000         } else {
1001                 store.batch.Set(ContractKey(hash), accountCP)
1002         }
1003         return nil
1004 }
1005
1006 // SetMiningAddress set mining address
1007 func (store *MockAccountStore) SetMiningAddress(program *acc.CtrlProgram) error {
1008         rawProgram, err := json.Marshal(program)
1009         if err != nil {
1010                 return err
1011         }
1012
1013         if store.batch == nil {
1014                 store.db.Set([]byte(MiningAddressKey), rawProgram)
1015         } else {
1016                 store.batch.Set([]byte(MiningAddressKey), rawProgram)
1017         }
1018         return nil
1019 }
1020
1021 // SetStandardUTXO set standard utxo
1022 func (store *MockAccountStore) SetStandardUTXO(outputID bc.Hash, utxo *acc.UTXO) error {
1023         data, err := json.Marshal(utxo)
1024         if err != nil {
1025                 return err
1026         }
1027         if store.batch == nil {
1028                 store.db.Set(StandardUTXOKey(outputID), data)
1029         } else {
1030                 store.batch.Set(StandardUTXOKey(outputID), data)
1031         }
1032         return nil
1033 }