OSDN Git Service

Wallet store interface (#217)
[bytom/vapor.git] / wallet / wallet_test.go
index ac966f4..49ad693 100644 (file)
@@ -1,23 +1,30 @@
 package wallet
 
 import (
+       "encoding/binary"
+       "encoding/hex"
        "encoding/json"
+       "fmt"
        "io/ioutil"
        "os"
-       "reflect"
+       "sort"
+       "strings"
        "testing"
        "time"
 
        "github.com/vapor/account"
+       acc "github.com/vapor/account"
        "github.com/vapor/asset"
-       "github.com/vapor/blockchain/pseudohsm"
+       "github.com/vapor/blockchain/query"
        "github.com/vapor/blockchain/signers"
        "github.com/vapor/blockchain/txbuilder"
+       "github.com/vapor/common"
        "github.com/vapor/config"
        "github.com/vapor/consensus"
        "github.com/vapor/crypto/ed25519/chainkd"
-       "github.com/vapor/database"
+       "github.com/vapor/crypto/sha3pool"
        dbm "github.com/vapor/database/leveldb"
+       "github.com/vapor/errors"
        "github.com/vapor/event"
        "github.com/vapor/protocol"
        "github.com/vapor/protocol/bc"
@@ -33,7 +40,7 @@ func TestEncodeDecodeGlobalTxIndex(t *testing.T) {
                Position:  1,
        }
 
-       globalTxIdx := calcGlobalTxIndex(&want.BlockHash, want.Position)
+       globalTxIdx := CalcGlobalTxIndex(&want.BlockHash, want.Position)
        blockHashGot, positionGot := parseGlobalTxIdx(globalTxIdx)
        if *blockHashGot != want.BlockHash {
                t.Errorf("blockHash mismatch. Get: %v. Expect: %v", *blockHashGot, want.BlockHash)
@@ -53,35 +60,25 @@ func TestWalletVersion(t *testing.T) {
        defer os.RemoveAll(dirPath)
 
        testDB := dbm.NewDB("testdb", "leveldb", "temp")
+       walletStore := NewMockWalletStore(testDB)
        defer func() {
                testDB.Close()
                os.RemoveAll("temp")
        }()
 
        dispatcher := event.NewDispatcher()
-       w := mockWallet(testDB, nil, nil, nil, dispatcher, false)
+       w := mockWallet(walletStore, nil, nil, nil, dispatcher, false)
 
-       // legacy status test case
-       type legacyStatusInfo struct {
-               WorkHeight uint64
-               WorkHash   bc.Hash
-               BestHeight uint64
-               BestHash   bc.Hash
-       }
-       rawWallet, err := json.Marshal(legacyStatusInfo{})
-       if err != nil {
-               t.Fatal("Marshal legacyStatusInfo")
-       }
-
-       w.DB.Set(walletKey, rawWallet)
-       rawWallet = w.DB.Get(walletKey)
-       if rawWallet == nil {
-               t.Fatal("fail to load wallet StatusInfo")
+       walletStatus := new(StatusInfo)
+       if err := w.Store.SetWalletInfo(walletStatus); err != nil {
+               t.Fatal(err)
        }
 
-       if err := json.Unmarshal(rawWallet, &w.status); err != nil {
+       status, err := w.Store.GetWalletInfo()
+       if err != nil {
                t.Fatal(err)
        }
+       w.Status = *status
 
        if err := w.checkWalletInfo(); err != errWalletVersionMismatch {
                t.Fatal("fail to detect legacy wallet version")
@@ -89,315 +86,946 @@ func TestWalletVersion(t *testing.T) {
 
        // lower wallet version test case
        lowerVersion := StatusInfo{Version: currentVersion - 1}
-       rawWallet, err = json.Marshal(lowerVersion)
-       if err != nil {
-               t.Fatal("save wallet info")
-       }
-
-       w.DB.Set(walletKey, rawWallet)
-       rawWallet = w.DB.Get(walletKey)
-       if rawWallet == nil {
-               t.Fatal("fail to load wallet StatusInfo")
+       if err := w.Store.SetWalletInfo(&lowerVersion); err != nil {
+               t.Fatal(err)
        }
 
-       if err := json.Unmarshal(rawWallet, &w.status); err != nil {
+       status, err = w.Store.GetWalletInfo()
+       if err != nil {
                t.Fatal(err)
        }
+       w.Status = *status
 
        if err := w.checkWalletInfo(); err != errWalletVersionMismatch {
                t.Fatal("fail to detect expired wallet version")
        }
 }
 
-func TestWalletUpdate(t *testing.T) {
-       dirPath, err := ioutil.TempDir(".", "")
-       if err != nil {
-               t.Fatal(err)
-       }
-       defer os.RemoveAll(dirPath)
+func mockUTXO(controlProg *account.CtrlProgram, assetID *bc.AssetID) *account.UTXO {
+       utxo := &account.UTXO{}
+       utxo.OutputID = bc.Hash{V0: 1}
+       utxo.SourceID = bc.Hash{V0: 2}
+       utxo.AssetID = *assetID
+       utxo.Amount = 1000000000
+       utxo.SourcePos = 0
+       utxo.ControlProgram = controlProg.ControlProgram
+       utxo.AccountID = controlProg.AccountID
+       utxo.Address = controlProg.Address
+       utxo.ControlProgramIndex = controlProg.KeyIndex
+       return utxo
+}
 
-       config.CommonConfig = config.DefaultConfig()
-       testDB := dbm.NewDB("testdb", "leveldb", "temp")
-       defer func() {
-               testDB.Close()
-               os.RemoveAll("temp")
-       }()
+func mockTxData(utxos []*account.UTXO, testAccount *account.Account) (*txbuilder.Template, *types.TxData, error) {
+       tplBuilder := txbuilder.NewBuilder(time.Now())
 
-       store := database.NewStore(testDB)
-       dispatcher := event.NewDispatcher()
-       txPool := protocol.NewTxPool(store, dispatcher)
+       for _, utxo := range utxos {
+               txInput, sigInst, err := account.UtxoToInputs(testAccount.Signer, utxo)
+               if err != nil {
+                       return nil, nil, err
+               }
+               tplBuilder.AddInput(txInput, sigInst)
 
-       chain, err := protocol.NewChain(store, txPool, dispatcher)
-       if err != nil {
-               t.Fatal(err)
+               out := &types.TxOutput{}
+               if utxo.AssetID == *consensus.BTMAssetID {
+                       out = types.NewIntraChainOutput(utxo.AssetID, 100, utxo.ControlProgram)
+               } else {
+                       out = types.NewIntraChainOutput(utxo.AssetID, utxo.Amount, utxo.ControlProgram)
+               }
+               tplBuilder.AddOutput(out)
        }
 
-       accountManager := account.NewManager(testDB, chain)
-       hsm, err := pseudohsm.New(dirPath)
-       if err != nil {
-               t.Fatal(err)
+       return tplBuilder.Build()
+}
+
+func mockWallet(store WalletStore, account *account.Manager, asset *asset.Registry, chain *protocol.Chain, dispatcher *event.Dispatcher, txIndexFlag bool) *Wallet {
+       wallet := &Wallet{
+               Store:           store,
+               AccountMgr:      account,
+               AssetReg:        asset,
+               Chain:           chain,
+               RecoveryMgr:     NewRecoveryManager(store, account),
+               EventDispatcher: dispatcher,
+               TxIndexFlag:     txIndexFlag,
        }
+       wallet.TxMsgSub, _ = wallet.EventDispatcher.Subscribe(protocol.TxMsgEvent{})
+       return wallet
+}
 
-       xpub1, _, err := hsm.XCreate("test_pub1", "password", "en")
-       if err != nil {
-               t.Fatal(err)
+func mockSingleBlock(tx *types.Tx) *types.Block {
+       return &types.Block{
+               BlockHeader: types.BlockHeader{
+                       Version: 1,
+                       Height:  1,
+               },
+               Transactions: []*types.Tx{config.GenesisTx(), tx},
        }
+}
 
-       testAccount, err := accountManager.Create([]chainkd.XPub{xpub1.XPub}, 1, "testAccount", signers.BIP0044)
-       if err != nil {
-               t.Fatal(err)
+// errors
+var (
+       errAccntTxIDNotFound = errors.New("account TXID not found")
+       errGetAsset          = errors.New("Failed to find asset definition")
+)
+
+const (
+       utxoPrefix byte = iota //UTXOPrefix is StandardUTXOKey prefix
+       contractPrefix
+       contractIndexPrefix
+       accountPrefix // AccountPrefix is account ID prefix
+       accountIndexPrefix
+)
+
+// leveldb key prefix
+var (
+       colon               byte = 0x3a
+       accountStore             = []byte("AS:")
+       UTXOPrefix               = append(accountStore, utxoPrefix, colon)
+       ContractPrefix           = append(accountStore, contractPrefix, colon)
+       ContractIndexPrefix      = append(accountStore, contractIndexPrefix, colon)
+       AccountPrefix            = append(accountStore, accountPrefix, colon) // AccountPrefix is account ID prefix
+       AccountIndexPrefix       = append(accountStore, accountIndexPrefix, colon)
+)
+
+const (
+       sutxoPrefix byte = iota //SUTXOPrefix is ContractUTXOKey prefix
+       accountAliasPrefix
+       txPrefix            //TxPrefix is wallet database transactions prefix
+       txIndexPrefix       //TxIndexPrefix is wallet database tx index prefix
+       unconfirmedTxPrefix //UnconfirmedTxPrefix is txpool unconfirmed transactions prefix
+       globalTxIndexPrefix //GlobalTxIndexPrefix is wallet database global tx index prefix
+       walletKey
+       miningAddressKey
+       coinbaseAbKey
+       recoveryKey //recoveryKey key for db store recovery info.
+)
+
+var (
+       walletStore         = []byte("WS:")
+       SUTXOPrefix         = append(walletStore, sutxoPrefix, colon)
+       AccountAliasPrefix  = append(walletStore, accountAliasPrefix, colon)
+       TxPrefix            = append(walletStore, txPrefix, colon)            //TxPrefix is wallet database transactions prefix
+       TxIndexPrefix       = append(walletStore, txIndexPrefix, colon)       //TxIndexPrefix is wallet database tx index prefix
+       UnconfirmedTxPrefix = append(walletStore, unconfirmedTxPrefix, colon) //UnconfirmedTxPrefix is txpool unconfirmed transactions prefix
+       GlobalTxIndexPrefix = append(walletStore, globalTxIndexPrefix, colon) //GlobalTxIndexPrefix is wallet database global tx index prefix
+       WalletKey           = append(walletStore, walletKey)
+       MiningAddressKey    = append(walletStore, miningAddressKey)
+       CoinbaseAbKey       = append(walletStore, coinbaseAbKey)
+       RecoveryKey         = append(walletStore, recoveryKey)
+)
+
+func accountIndexKey(xpubs []chainkd.XPub) []byte {
+       var hash [32]byte
+       var xPubs []byte
+       cpy := append([]chainkd.XPub{}, xpubs[:]...)
+       sort.Sort(signers.SortKeys(cpy))
+       for _, xpub := range cpy {
+               xPubs = append(xPubs, xpub[:]...)
        }
+       sha3pool.Sum256(hash[:], xPubs)
+       return append([]byte(AccountIndexPrefix), hash[:]...)
+}
 
-       controlProg, err := accountManager.CreateAddress(testAccount.ID, false)
-       if err != nil {
-               t.Fatal(err)
+func Bip44ContractIndexKey(accountID string, change bool) []byte {
+       key := append([]byte(ContractIndexPrefix), accountID...)
+       if change {
+               return append(key, []byte{1}...)
        }
+       return append(key, []byte{0}...)
+}
 
-       controlProg.KeyIndex = 1
+// ContractKey account control promgram store prefix
+func ContractKey(hash bc.Hash) []byte {
+       return append([]byte(ContractPrefix), hash.Bytes()...)
+}
 
-       reg := asset.NewRegistry(testDB, chain)
-       asset := bc.AssetID{V0: 5}
+// AccountIDKey account id store prefix
+func AccountIDKey(accountID string) []byte {
+       return append([]byte(AccountPrefix), []byte(accountID)...)
+}
 
-       utxos := []*account.UTXO{}
-       btmUtxo := mockUTXO(controlProg, consensus.BTMAssetID)
-       utxos = append(utxos, btmUtxo)
-       OtherUtxo := mockUTXO(controlProg, &asset)
-       utxos = append(utxos, OtherUtxo)
+// StandardUTXOKey makes an account unspent outputs key to store
+func StandardUTXOKey(id bc.Hash) []byte {
+       return append(UTXOPrefix, id.Bytes()...)
+}
 
-       _, txData, err := mockTxData(utxos, testAccount)
-       if err != nil {
-               t.Fatal(err)
+// ContractUTXOKey makes a smart contract unspent outputs key to store
+func ContractUTXOKey(id bc.Hash) []byte {
+       return append(SUTXOPrefix, id.Bytes()...)
+}
+
+func calcDeleteKey(blockHeight uint64) []byte {
+       return []byte(fmt.Sprintf("%s%016x", TxPrefix, blockHeight))
+}
+
+func calcTxIndexKey(txID string) []byte {
+       return append(TxIndexPrefix, []byte(txID)...)
+}
+
+func calcAnnotatedKey(formatKey string) []byte {
+       return append(TxPrefix, []byte(formatKey)...)
+}
+
+func calcUnconfirmedTxKey(formatKey string) []byte {
+       return append(UnconfirmedTxPrefix, []byte(formatKey)...)
+}
+
+func CalcGlobalTxIndexKey(txID string) []byte {
+       return append(GlobalTxIndexPrefix, []byte(txID)...)
+}
+
+func CalcGlobalTxIndex(blockHash *bc.Hash, position uint64) []byte {
+       txIdx := make([]byte, 40)
+       copy(txIdx[:32], blockHash.Bytes())
+       binary.BigEndian.PutUint64(txIdx[32:], position)
+       return txIdx
+}
+
+func formatKey(blockHeight uint64, position uint32) string {
+       return fmt.Sprintf("%016x%08x", blockHeight, position)
+}
+
+func contractIndexKey(accountID string) []byte {
+       return append([]byte(ContractIndexPrefix), []byte(accountID)...)
+}
+
+func accountAliasKey(name string) []byte {
+       return append([]byte(AccountAliasPrefix), []byte(name)...)
+}
+
+// MockWalletStore store wallet using leveldb
+type MockWalletStore struct {
+       db    dbm.DB
+       batch dbm.Batch
+}
+
+// NewMockWalletStore create new MockWalletStore struct
+func NewMockWalletStore(db dbm.DB) *MockWalletStore {
+       return &MockWalletStore{
+               db:    db,
+               batch: nil,
        }
+}
 
-       tx := types.NewTx(*txData)
-       block := mockSingleBlock(tx)
-       txStatus := bc.NewTransactionStatus()
-       txStatus.SetStatus(0, false)
-       txStatus.SetStatus(1, false)
-       store.SaveBlock(block, txStatus)
+// InitBatch initial new wallet store
+func (store *MockWalletStore) InitBatch() WalletStore {
+       newStore := NewMockWalletStore(store.db)
+       newStore.batch = newStore.db.NewBatch()
+       return newStore
+}
 
-       w := mockWallet(testDB, accountManager, reg, chain, dispatcher, true)
-       err = w.AttachBlock(block)
-       if err != nil {
-               t.Fatal(err)
+// CommitBatch commit batch
+func (store *MockWalletStore) CommitBatch() error {
+       if store.batch == nil {
+               return errors.New("MockWalletStore commit fail, store batch is nil.")
        }
+       store.batch.Write()
+       store.batch = nil
+       return nil
+}
 
-       if _, err := w.GetTransactionByTxID(tx.ID.String()); err != nil {
-               t.Fatal(err)
+// DeleteContractUTXO delete contract utxo by outputID
+func (store *MockWalletStore) DeleteContractUTXO(outputID bc.Hash) {
+       if store.batch == nil {
+               store.db.Delete(ContractUTXOKey(outputID))
+       } else {
+               store.batch.Delete(ContractUTXOKey(outputID))
        }
+}
 
-       wants, err := w.GetTransactions(testAccount.ID, "", 1, false)
-       if len(wants) != 1 {
-               t.Fatal(err)
+// DeleteRecoveryStatus delete recovery status
+func (store *MockWalletStore) DeleteRecoveryStatus() {
+       if store.batch == nil {
+               store.db.Delete(RecoveryKey)
+       } else {
+               store.batch.Delete(RecoveryKey)
        }
+}
 
-       if wants[0].ID != tx.ID {
-               t.Fatal("account txID mismatch")
+// DeleteTransactions delete transactions when orphan block rollback
+func (store *MockWalletStore) DeleteTransactions(height uint64) {
+       batch := store.db.NewBatch()
+       if store.batch != nil {
+               batch = store.batch
        }
+       txIter := store.db.IteratorPrefix(calcDeleteKey(height))
+       defer txIter.Release()
 
-       for position, tx := range block.Transactions {
-               get := w.DB.Get(calcGlobalTxIndexKey(tx.ID.String()))
-               bh := block.BlockHeader.Hash()
-               expect := calcGlobalTxIndex(&bh, uint64(position))
-               if !reflect.DeepEqual(get, expect) {
-                       t.Fatalf("position#%d: compare retrieved globalTxIdx err", position)
+       tmpTx := query.AnnotatedTx{}
+       for txIter.Next() {
+               if err := json.Unmarshal(txIter.Value(), &tmpTx); err == nil {
+                       batch.Delete(calcTxIndexKey(tmpTx.ID.String()))
                }
+               batch.Delete(txIter.Key())
+       }
+       if store.batch == nil {
+               batch.Write()
        }
 }
 
-func TestRescanWallet(t *testing.T) {
-       // prepare wallet & db
-       dirPath, err := ioutil.TempDir(".", "")
-       if err != nil {
-               t.Fatal(err)
+// DeleteUnconfirmedTransaction delete unconfirmed tx by txID
+func (store *MockWalletStore) DeleteUnconfirmedTransaction(txID string) {
+       if store.batch == nil {
+               store.db.Delete(calcUnconfirmedTxKey(txID))
+       } else {
+               store.batch.Delete(calcUnconfirmedTxKey(txID))
        }
-       defer os.RemoveAll(dirPath)
+}
 
-       config.CommonConfig = config.DefaultConfig()
-       testDB := dbm.NewDB("testdb", "leveldb", "temp")
-       defer func() {
-               testDB.Close()
-               os.RemoveAll("temp")
-       }()
+// DeleteWalletTransactions delete all txs in wallet
+func (store *MockWalletStore) DeleteWalletTransactions() {
+       batch := store.db.NewBatch()
+       if store.batch != nil {
+               batch = store.batch
+       }
+       txIter := store.db.IteratorPrefix([]byte(TxPrefix))
+       defer txIter.Release()
 
-       store := database.NewStore(testDB)
-       dispatcher := event.NewDispatcher()
-       txPool := protocol.NewTxPool(store, dispatcher)
-       chain, err := protocol.NewChain(store, txPool, dispatcher)
-       if err != nil {
-               t.Fatal(err)
+       for txIter.Next() {
+               batch.Delete(txIter.Key())
        }
 
-       statusInfo := StatusInfo{
-               Version:  currentVersion,
-               WorkHash: bc.Hash{V0: 0xff},
+       txIndexIter := store.db.IteratorPrefix([]byte(TxIndexPrefix))
+       defer txIndexIter.Release()
+
+       for txIndexIter.Next() {
+               batch.Delete(txIndexIter.Key())
        }
-       rawWallet, err := json.Marshal(statusInfo)
-       if err != nil {
-               t.Fatal("save wallet info")
+       if store.batch == nil {
+               batch.Write()
        }
+}
 
-       w := mockWallet(testDB, nil, nil, chain, dispatcher, false)
-       w.DB.Set(walletKey, rawWallet)
-       rawWallet = w.DB.Get(walletKey)
-       if rawWallet == nil {
-               t.Fatal("fail to load wallet StatusInfo")
+// DeleteWalletUTXOs delete all txs in wallet
+func (store *MockWalletStore) DeleteWalletUTXOs() {
+       batch := store.db.NewBatch()
+       if store.batch != nil {
+               batch = store.batch
+       }
+       ruIter := store.db.IteratorPrefix([]byte(UTXOPrefix))
+       defer ruIter.Release()
+       for ruIter.Next() {
+               batch.Delete(ruIter.Key())
        }
 
-       if err := json.Unmarshal(rawWallet, &w.status); err != nil {
-               t.Fatal(err)
+       suIter := store.db.IteratorPrefix([]byte(SUTXOPrefix))
+       defer suIter.Release()
+       for suIter.Next() {
+               batch.Delete(suIter.Key())
+       }
+       if store.batch == nil {
+               batch.Write()
        }
+}
 
-       // rescan wallet
-       if err := w.loadWalletInfo(); err != nil {
-               t.Fatal(err)
+// GetAsset get asset by assetID
+func (store *MockWalletStore) GetAsset(assetID *bc.AssetID) (*asset.Asset, error) {
+       definitionByte := store.db.Get(asset.ExtAssetKey(assetID))
+       if definitionByte == nil {
+               return nil, errGetAsset
+       }
+       definitionMap := make(map[string]interface{})
+       if err := json.Unmarshal(definitionByte, &definitionMap); err != nil {
+               return nil, err
        }
+       alias := assetID.String()
+       externalAsset := &asset.Asset{
+               AssetID:           *assetID,
+               Alias:             &alias,
+               DefinitionMap:     definitionMap,
+               RawDefinitionByte: definitionByte,
+       }
+       return externalAsset, nil
+}
 
-       block := config.GenesisBlock()
-       if w.status.WorkHash != block.Hash() {
-               t.Fatal("reattach from genesis block")
+// GetGlobalTransactionIndex get global tx by txID
+func (store *MockWalletStore) GetGlobalTransactionIndex(txID string) []byte {
+       return store.db.Get(CalcGlobalTxIndexKey(txID))
+}
+
+// GetStandardUTXO get standard utxo by id
+func (store *MockWalletStore) GetStandardUTXO(outid bc.Hash) (*acc.UTXO, error) {
+       rawUTXO := store.db.Get(StandardUTXOKey(outid))
+       if rawUTXO == nil {
+               return nil, fmt.Errorf("failed get standard UTXO, outputID: %s ", outid.String())
+       }
+       UTXO := new(acc.UTXO)
+       if err := json.Unmarshal(rawUTXO, UTXO); err != nil {
+               return nil, err
        }
+       return UTXO, nil
 }
 
-func TestMemPoolTxQueryLoop(t *testing.T) {
-       dirPath, err := ioutil.TempDir(".", "")
+// GetTransaction get tx by txid
+func (store *MockWalletStore) GetTransaction(txID string) (*query.AnnotatedTx, error) {
+       formatKey := store.db.Get(calcTxIndexKey(txID))
+       if formatKey == nil {
+               return nil, errAccntTxIDNotFound
+       }
+       rawTx := store.db.Get(calcAnnotatedKey(string(formatKey)))
+       tx := new(query.AnnotatedTx)
+       if err := json.Unmarshal(rawTx, tx); err != nil {
+               return nil, err
+       }
+       return tx, nil
+}
+
+// GetUnconfirmedTransaction get unconfirmed tx by txID
+func (store *MockWalletStore) GetUnconfirmedTransaction(txID string) (*query.AnnotatedTx, error) {
+       rawUnconfirmedTx := store.db.Get(calcUnconfirmedTxKey(txID))
+       if rawUnconfirmedTx == nil {
+               return nil, fmt.Errorf("failed get unconfirmed tx, txID: %s ", txID)
+       }
+       tx := new(query.AnnotatedTx)
+       if err := json.Unmarshal(rawUnconfirmedTx, tx); err != nil {
+               return nil, err
+       }
+       return tx, nil
+}
+
+// GetRecoveryStatus delete recovery status
+func (store *MockWalletStore) GetRecoveryStatus() (*RecoveryState, error) {
+       rawStatus := store.db.Get(RecoveryKey)
+       if rawStatus == nil {
+               return nil, ErrGetRecoveryStatus
+       }
+       state := new(RecoveryState)
+       if err := json.Unmarshal(rawStatus, state); err != nil {
+               return nil, err
+       }
+       return state, nil
+}
+
+// GetWalletInfo get wallet information
+func (store *MockWalletStore) GetWalletInfo() (*StatusInfo, error) {
+       rawStatus := store.db.Get([]byte(WalletKey))
+       if rawStatus == nil {
+               return nil, fmt.Errorf("failed get wallet info")
+       }
+       status := new(StatusInfo)
+       if err := json.Unmarshal(rawStatus, status); err != nil {
+               return nil, err
+       }
+       return status, nil
+}
+
+// ListAccountUTXOs get all account unspent outputs
+func (store *MockWalletStore) ListAccountUTXOs(id string, isSmartContract bool) ([]*acc.UTXO, error) {
+       prefix := UTXOPrefix
+       if isSmartContract {
+               prefix = SUTXOPrefix
+       }
+
+       idBytes, err := hex.DecodeString(id)
        if err != nil {
-               t.Fatal(err)
+               return nil, err
        }
-       config.CommonConfig = config.DefaultConfig()
-       testDB := dbm.NewDB("testdb", "leveldb", dirPath)
-       defer func() {
-               testDB.Close()
-               os.RemoveAll(dirPath)
-       }()
 
-       store := database.NewStore(testDB)
-       dispatcher := event.NewDispatcher()
-       txPool := protocol.NewTxPool(store, dispatcher)
+       accountUtxoIter := store.db.IteratorPrefix(append(prefix, idBytes...))
+       defer accountUtxoIter.Release()
+
+       confirmedUTXOs := []*acc.UTXO{}
+       for accountUtxoIter.Next() {
+               utxo := new(acc.UTXO)
+               if err := json.Unmarshal(accountUtxoIter.Value(), utxo); err != nil {
+                       return nil, err
+               }
+
+               confirmedUTXOs = append(confirmedUTXOs, utxo)
+       }
+       return confirmedUTXOs, nil
+}
+
+func (store *MockWalletStore) ListTransactions(accountID string, StartTxID string, count uint, unconfirmed bool) ([]*query.AnnotatedTx, error) {
+       annotatedTxs := []*query.AnnotatedTx{}
+       var startKey []byte
+       preFix := TxPrefix
+
+       if StartTxID != "" {
+               if unconfirmed {
+                       startKey = calcUnconfirmedTxKey(StartTxID)
+               } else {
+                       formatKey := store.db.Get(calcTxIndexKey(StartTxID))
+                       if formatKey == nil {
+                               return nil, errAccntTxIDNotFound
+                       }
+                       startKey = calcAnnotatedKey(string(formatKey))
+               }
+       }
+
+       if unconfirmed {
+               preFix = UnconfirmedTxPrefix
+       }
+
+       itr := store.db.IteratorPrefixWithStart([]byte(preFix), startKey, true)
+       defer itr.Release()
 
-       chain, err := protocol.NewChain(store, txPool, dispatcher)
+       for txNum := count; itr.Next() && txNum > 0; txNum-- {
+               annotatedTx := new(query.AnnotatedTx)
+               if err := json.Unmarshal(itr.Value(), &annotatedTx); err != nil {
+                       return nil, err
+               }
+               annotatedTxs = append(annotatedTxs, annotatedTx)
+       }
+
+       return annotatedTxs, nil
+}
+
+// ListUnconfirmedTransactions get all unconfirmed txs
+func (store *MockWalletStore) ListUnconfirmedTransactions() ([]*query.AnnotatedTx, error) {
+       annotatedTxs := []*query.AnnotatedTx{}
+       txIter := store.db.IteratorPrefix([]byte(UnconfirmedTxPrefix))
+       defer txIter.Release()
+
+       for txIter.Next() {
+               annotatedTx := &query.AnnotatedTx{}
+               if err := json.Unmarshal(txIter.Value(), &annotatedTx); err != nil {
+                       return nil, err
+               }
+               annotatedTxs = append(annotatedTxs, annotatedTx)
+       }
+       return annotatedTxs, nil
+}
+
+// SetAssetDefinition set assetID and definition
+func (store *MockWalletStore) SetAssetDefinition(assetID *bc.AssetID, definition []byte) {
+       if store.batch == nil {
+               store.db.Set(asset.ExtAssetKey(assetID), definition)
+       } else {
+               store.batch.Set(asset.ExtAssetKey(assetID), definition)
+       }
+}
+
+// SetContractUTXO set standard utxo
+func (store *MockWalletStore) SetContractUTXO(outputID bc.Hash, utxo *acc.UTXO) error {
+       data, err := json.Marshal(utxo)
        if err != nil {
-               t.Fatal(err)
+               return err
        }
+       if store.batch == nil {
+               store.db.Set(ContractUTXOKey(outputID), data)
+       } else {
+               store.batch.Set(ContractUTXOKey(outputID), data)
+       }
+       return nil
+}
 
-       accountManager := account.NewManager(testDB, chain)
-       hsm, err := pseudohsm.New(dirPath)
+// SetGlobalTransactionIndex set global tx index by blockhash and position
+func (store *MockWalletStore) SetGlobalTransactionIndex(globalTxID string, blockHash *bc.Hash, position uint64) {
+       if store.batch == nil {
+               store.db.Set(CalcGlobalTxIndexKey(globalTxID), CalcGlobalTxIndex(blockHash, position))
+       } else {
+               store.batch.Set(CalcGlobalTxIndexKey(globalTxID), CalcGlobalTxIndex(blockHash, position))
+       }
+}
+
+// SetRecoveryStatus set recovery status
+func (store *MockWalletStore) SetRecoveryStatus(recoveryState *RecoveryState) error {
+       rawStatus, err := json.Marshal(recoveryState)
        if err != nil {
-               t.Fatal(err)
+               return err
+       }
+       if store.batch == nil {
+               store.db.Set(RecoveryKey, rawStatus)
+       } else {
+               store.batch.Set(RecoveryKey, rawStatus)
+       }
+       return nil
+}
+
+// SetTransaction set raw transaction by block height and tx position
+func (store *MockWalletStore) SetTransaction(height uint64, tx *query.AnnotatedTx) error {
+       batch := store.db.NewBatch()
+       if store.batch != nil {
+               batch = store.batch
        }
 
-       xpub1, _, err := hsm.XCreate("test_pub1", "password", "en")
+       rawTx, err := json.Marshal(tx)
        if err != nil {
-               t.Fatal(err)
+               return err
        }
+       batch.Set(calcAnnotatedKey(formatKey(height, tx.Position)), rawTx)
+       batch.Set(calcTxIndexKey(tx.ID.String()), []byte(formatKey(height, tx.Position)))
+
+       if store.batch == nil {
+               batch.Write()
+       }
+       return nil
+}
 
-       testAccount, err := accountManager.Create([]chainkd.XPub{xpub1.XPub}, 1, "testAccount", signers.BIP0044)
+// SetUnconfirmedTransaction set unconfirmed tx by txID
+func (store *MockWalletStore) SetUnconfirmedTransaction(txID string, tx *query.AnnotatedTx) error {
+       rawTx, err := json.Marshal(tx)
        if err != nil {
-               t.Fatal(err)
+               return err
+       }
+       if store.batch == nil {
+               store.db.Set(calcUnconfirmedTxKey(txID), rawTx)
+       } else {
+               store.batch.Set(calcUnconfirmedTxKey(txID), rawTx)
        }
+       return nil
+}
 
-       controlProg, err := accountManager.CreateAddress(testAccount.ID, false)
+// SetWalletInfo get wallet information
+func (store *MockWalletStore) SetWalletInfo(status *StatusInfo) error {
+       rawWallet, err := json.Marshal(status)
        if err != nil {
-               t.Fatal(err)
+               return err
+       }
+
+       if store.batch == nil {
+               store.db.Set([]byte(WalletKey), rawWallet)
+       } else {
+               store.batch.Set([]byte(WalletKey), rawWallet)
        }
+       return nil
+}
+
+type MockAccountStore struct {
+       db    dbm.DB
+       batch dbm.Batch
+}
 
-       controlProg.KeyIndex = 1
+// NewAccountStore create new MockAccountStore.
+func NewMockAccountStore(db dbm.DB) *MockAccountStore {
+       return &MockAccountStore{
+               db:    db,
+               batch: nil,
+       }
+}
 
-       reg := asset.NewRegistry(testDB, chain)
-       asset := bc.AssetID{V0: 5}
+// InitBatch initial new account store
+func (store *MockAccountStore) InitBatch() acc.AccountStore {
+       newStore := NewMockAccountStore(store.db)
+       newStore.batch = newStore.db.NewBatch()
+       return newStore
+}
 
-       utxos := []*account.UTXO{}
-       btmUtxo := mockUTXO(controlProg, consensus.BTMAssetID)
-       utxos = append(utxos, btmUtxo)
-       OtherUtxo := mockUTXO(controlProg, &asset)
-       utxos = append(utxos, OtherUtxo)
+// CommitBatch commit batch
+func (store *MockAccountStore) CommitBatch() error {
+       if store.batch == nil {
+               return errors.New("MockAccountStore commit fail, store batch is nil.")
+       }
+       store.batch.Write()
+       store.batch = nil
+       return nil
+}
 
-       _, txData, err := mockTxData(utxos, testAccount)
-       if err != nil {
-               t.Fatal(err)
+// DeleteAccount set account account ID, account alias and raw account.
+func (store *MockAccountStore) DeleteAccount(account *acc.Account) error {
+       batch := store.db.NewBatch()
+       if store.batch != nil {
+               batch = store.batch
        }
 
-       tx := types.NewTx(*txData)
-       //block := mockSingleBlock(tx)
-       txStatus := bc.NewTransactionStatus()
-       txStatus.SetStatus(0, false)
-       w, err := NewWallet(testDB, accountManager, reg, hsm, chain, dispatcher, false)
-       go w.memPoolTxQueryLoop()
-       w.eventDispatcher.Post(protocol.TxMsgEvent{TxMsg: &protocol.TxPoolMsg{TxDesc: &protocol.TxDesc{Tx: tx}, MsgType: protocol.MsgNewTx}})
-       time.Sleep(time.Millisecond * 10)
-       if _, err = w.GetUnconfirmedTxByTxID(tx.ID.String()); err != nil {
-               t.Fatal("disaptch new tx msg error:", err)
-       }
-       w.eventDispatcher.Post(protocol.TxMsgEvent{TxMsg: &protocol.TxPoolMsg{TxDesc: &protocol.TxDesc{Tx: tx}, MsgType: protocol.MsgRemoveTx}})
-       time.Sleep(time.Millisecond * 10)
-       txs, err := w.GetUnconfirmedTxs(testAccount.ID)
+       // delete account utxos
+       store.deleteAccountUTXOs(account.ID, batch)
+
+       // delete account control program
+       if err := store.deleteAccountControlPrograms(account.ID, batch); err != nil {
+               return err
+       }
+
+       // delete bip44 contract index
+       batch.Delete(Bip44ContractIndexKey(account.ID, false))
+       batch.Delete(Bip44ContractIndexKey(account.ID, true))
+
+       // delete contract index
+       batch.Delete(contractIndexKey(account.ID))
+
+       // delete account id
+       batch.Delete(AccountIDKey(account.ID))
+       batch.Delete(accountAliasKey(account.Alias))
+       if store.batch == nil {
+               batch.Write()
+       }
+       return nil
+}
+
+// deleteAccountUTXOs delete account utxos by accountID
+func (store *MockAccountStore) deleteAccountUTXOs(accountID string, batch dbm.Batch) error {
+       accountUtxoIter := store.db.IteratorPrefix([]byte(UTXOPrefix))
+       defer accountUtxoIter.Release()
+
+       for accountUtxoIter.Next() {
+               accountUtxo := new(acc.UTXO)
+               if err := json.Unmarshal(accountUtxoIter.Value(), accountUtxo); err != nil {
+                       return err
+               }
+
+               if accountID == accountUtxo.AccountID {
+                       batch.Delete(StandardUTXOKey(accountUtxo.OutputID))
+               }
+       }
+
+       return nil
+}
+
+// deleteAccountControlPrograms deletes account control program
+func (store *MockAccountStore) deleteAccountControlPrograms(accountID string, batch dbm.Batch) error {
+       cps, err := store.ListControlPrograms()
        if err != nil {
-               t.Fatal("get unconfirmed tx error:", err)
+               return err
        }
 
-       if len(txs) != 0 {
-               t.Fatal("disaptch remove tx msg error")
+       var hash [32]byte
+       for _, cp := range cps {
+               if cp.AccountID == accountID {
+                       sha3pool.Sum256(hash[:], cp.ControlProgram)
+                       batch.Delete(ContractKey(bc.NewHash(hash)))
+               }
        }
+       return nil
+}
 
-       w.eventDispatcher.Post(protocol.TxMsgEvent{TxMsg: &protocol.TxPoolMsg{TxDesc: &protocol.TxDesc{Tx: tx}, MsgType: 2}})
+// DeleteStandardUTXO delete utxo by outpu id
+func (store *MockAccountStore) DeleteStandardUTXO(outputID bc.Hash) {
+       if store.batch == nil {
+               store.db.Delete(StandardUTXOKey(outputID))
+       } else {
+               store.batch.Delete(StandardUTXOKey(outputID))
+       }
 }
 
-func mockUTXO(controlProg *account.CtrlProgram, assetID *bc.AssetID) *account.UTXO {
-       utxo := &account.UTXO{}
-       utxo.OutputID = bc.Hash{V0: 1}
-       utxo.SourceID = bc.Hash{V0: 2}
-       utxo.AssetID = *assetID
-       utxo.Amount = 1000000000
-       utxo.SourcePos = 0
-       utxo.ControlProgram = controlProg.ControlProgram
-       utxo.AccountID = controlProg.AccountID
-       utxo.Address = controlProg.Address
-       utxo.ControlProgramIndex = controlProg.KeyIndex
-       return utxo
+// GetAccountByAlias get account by account alias
+func (store *MockAccountStore) GetAccountByAlias(accountAlias string) (*acc.Account, error) {
+       accountID := store.db.Get(accountAliasKey(accountAlias))
+       if accountID == nil {
+               return nil, acc.ErrFindAccount
+       }
+       return store.GetAccountByID(string(accountID))
 }
 
-func mockTxData(utxos []*account.UTXO, testAccount *account.Account) (*txbuilder.Template, *types.TxData, error) {
-       tplBuilder := txbuilder.NewBuilder(time.Now())
+// GetAccountByID get account by accountID
+func (store *MockAccountStore) GetAccountByID(accountID string) (*acc.Account, error) {
+       rawAccount := store.db.Get(AccountIDKey(accountID))
+       if rawAccount == nil {
+               return nil, acc.ErrFindAccount
+       }
+       account := new(acc.Account)
+       if err := json.Unmarshal(rawAccount, account); err != nil {
+               return nil, err
+       }
+       return account, nil
+}
 
-       for _, utxo := range utxos {
-               txInput, sigInst, err := account.UtxoToInputs(testAccount.Signer, utxo)
-               if err != nil {
-                       return nil, nil, err
+// GetAccountIndex get account index by account xpubs
+func (store *MockAccountStore) GetAccountIndex(xpubs []chainkd.XPub) uint64 {
+       currentIndex := uint64(0)
+       if rawIndexBytes := store.db.Get(accountIndexKey(xpubs)); rawIndexBytes != nil {
+               currentIndex = common.BytesToUnit64(rawIndexBytes)
+       }
+       return currentIndex
+}
+
+// GetBip44ContractIndex get bip44 contract index
+func (store *MockAccountStore) GetBip44ContractIndex(accountID string, change bool) uint64 {
+       index := uint64(0)
+       if rawIndexBytes := store.db.Get(Bip44ContractIndexKey(accountID, change)); rawIndexBytes != nil {
+               index = common.BytesToUnit64(rawIndexBytes)
+       }
+       return index
+}
+
+// GetCoinbaseArbitrary get coinbase arbitrary
+func (store *MockAccountStore) GetCoinbaseArbitrary() []byte {
+       return store.db.Get([]byte(CoinbaseAbKey))
+}
+
+// GetContractIndex get contract index
+func (store *MockAccountStore) GetContractIndex(accountID string) uint64 {
+       index := uint64(0)
+       if rawIndexBytes := store.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
+               index = common.BytesToUnit64(rawIndexBytes)
+       }
+       return index
+}
+
+// GetControlProgram get control program
+func (store *MockAccountStore) GetControlProgram(hash bc.Hash) (*acc.CtrlProgram, error) {
+       rawProgram := store.db.Get(ContractKey(hash))
+       if rawProgram == nil {
+               return nil, acc.ErrFindCtrlProgram
+       }
+       cp := new(acc.CtrlProgram)
+       if err := json.Unmarshal(rawProgram, cp); err != nil {
+               return nil, err
+       }
+       return cp, nil
+}
+
+// GetMiningAddress get mining address
+func (store *MockAccountStore) GetMiningAddress() (*acc.CtrlProgram, error) {
+       rawCP := store.db.Get([]byte(MiningAddressKey))
+       if rawCP == nil {
+               return nil, acc.ErrFindMiningAddress
+       }
+       cp := new(acc.CtrlProgram)
+       if err := json.Unmarshal(rawCP, cp); err != nil {
+               return nil, err
+       }
+       return cp, nil
+}
+
+// GetUTXO get standard utxo by id
+func (store *MockAccountStore) GetUTXO(outid bc.Hash) (*acc.UTXO, error) {
+       u := new(acc.UTXO)
+       if data := store.db.Get(StandardUTXOKey(outid)); data != nil {
+               return u, json.Unmarshal(data, u)
+       }
+       if data := store.db.Get(ContractUTXOKey(outid)); data != nil {
+               return u, json.Unmarshal(data, u)
+       }
+       return nil, acc.ErrMatchUTXO
+}
+
+// ListAccounts get all accounts which name prfix is id.
+func (store *MockAccountStore) ListAccounts(id string) ([]*acc.Account, error) {
+       accounts := []*acc.Account{}
+       accountIter := store.db.IteratorPrefix(AccountIDKey(strings.TrimSpace(id)))
+       defer accountIter.Release()
+
+       for accountIter.Next() {
+               account := new(acc.Account)
+               if err := json.Unmarshal(accountIter.Value(), &account); err != nil {
+                       return nil, err
                }
-               tplBuilder.AddInput(txInput, sigInst)
+               accounts = append(accounts, account)
+       }
+       return accounts, nil
+}
 
-               out := &types.TxOutput{}
-               if utxo.AssetID == *consensus.BTMAssetID {
-                       out = types.NewIntraChainOutput(utxo.AssetID, 100, utxo.ControlProgram)
+// ListControlPrograms get all local control programs
+func (store *MockAccountStore) ListControlPrograms() ([]*acc.CtrlProgram, error) {
+       cps := []*acc.CtrlProgram{}
+       cpIter := store.db.IteratorPrefix([]byte(ContractPrefix))
+       defer cpIter.Release()
+
+       for cpIter.Next() {
+               cp := new(acc.CtrlProgram)
+               if err := json.Unmarshal(cpIter.Value(), cp); err != nil {
+                       return nil, err
+               }
+               cps = append(cps, cp)
+       }
+       return cps, nil
+}
+
+// ListUTXOs get utxos by accountID
+func (store *MockAccountStore) ListUTXOs() ([]*acc.UTXO, error) {
+       utxoIter := store.db.IteratorPrefix([]byte(UTXOPrefix))
+       defer utxoIter.Release()
+
+       utxos := []*acc.UTXO{}
+       for utxoIter.Next() {
+               utxo := new(acc.UTXO)
+               if err := json.Unmarshal(utxoIter.Value(), utxo); err != nil {
+                       return nil, err
+               }
+               utxos = append(utxos, utxo)
+       }
+       return utxos, nil
+}
+
+// SetAccount set account account ID, account alias and raw account.
+func (store *MockAccountStore) SetAccount(account *acc.Account) error {
+       rawAccount, err := json.Marshal(account)
+       if err != nil {
+               return acc.ErrMarshalAccount
+       }
+
+       batch := store.db.NewBatch()
+       if store.batch != nil {
+               batch = store.batch
+       }
+
+       batch.Set(AccountIDKey(account.ID), rawAccount)
+       batch.Set(accountAliasKey(account.Alias), []byte(account.ID))
+
+       if store.batch == nil {
+               batch.Write()
+       }
+       return nil
+}
+
+// SetAccountIndex update account index
+func (store *MockAccountStore) SetAccountIndex(account *acc.Account) {
+       currentIndex := store.GetAccountIndex(account.XPubs)
+       if account.KeyIndex > currentIndex {
+               if store.batch == nil {
+                       store.db.Set(accountIndexKey(account.XPubs), common.Unit64ToBytes(account.KeyIndex))
                } else {
-                       out = types.NewIntraChainOutput(utxo.AssetID, utxo.Amount, utxo.ControlProgram)
+                       store.batch.Set(accountIndexKey(account.XPubs), common.Unit64ToBytes(account.KeyIndex))
                }
-               tplBuilder.AddOutput(out)
        }
+}
 
-       return tplBuilder.Build()
+// SetBip44ContractIndex set contract index
+func (store *MockAccountStore) SetBip44ContractIndex(accountID string, change bool, index uint64) {
+       if store.batch == nil {
+               store.db.Set(Bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(index))
+       } else {
+               store.batch.Set(Bip44ContractIndexKey(accountID, change), common.Unit64ToBytes(index))
+       }
 }
 
-func mockWallet(walletDB dbm.DB, account *account.Manager, asset *asset.Registry, chain *protocol.Chain, dispatcher *event.Dispatcher, txIndexFlag bool) *Wallet {
-       wallet := &Wallet{
-               DB:              walletDB,
-               AccountMgr:      account,
-               AssetReg:        asset,
-               chain:           chain,
-               RecoveryMgr:     newRecoveryManager(walletDB, account),
-               eventDispatcher: dispatcher,
-               TxIndexFlag:     txIndexFlag,
+// SetCoinbaseArbitrary set coinbase arbitrary
+func (store *MockAccountStore) SetCoinbaseArbitrary(arbitrary []byte) {
+       if store.batch == nil {
+               store.db.Set([]byte(CoinbaseAbKey), arbitrary)
+       } else {
+               store.batch.Set([]byte(CoinbaseAbKey), arbitrary)
        }
-       wallet.txMsgSub, _ = wallet.eventDispatcher.Subscribe(protocol.TxMsgEvent{})
-       return wallet
 }
 
-func mockSingleBlock(tx *types.Tx) *types.Block {
-       return &types.Block{
-               BlockHeader: types.BlockHeader{
-                       Version: 1,
-                       Height:  1,
-               },
-               Transactions: []*types.Tx{config.GenesisTx(), tx},
+// SetContractIndex set contract index
+func (store *MockAccountStore) SetContractIndex(accountID string, index uint64) {
+       if store.batch == nil {
+               store.db.Set(contractIndexKey(accountID), common.Unit64ToBytes(index))
+       } else {
+               store.batch.Set(contractIndexKey(accountID), common.Unit64ToBytes(index))
+       }
+}
+
+// SetControlProgram set raw program
+func (store *MockAccountStore) SetControlProgram(hash bc.Hash, program *acc.CtrlProgram) error {
+       accountCP, err := json.Marshal(program)
+       if err != nil {
+               return err
+       }
+       if store.batch == nil {
+               store.db.Set(ContractKey(hash), accountCP)
+       } else {
+               store.batch.Set(ContractKey(hash), accountCP)
+       }
+       return nil
+}
+
+// SetMiningAddress set mining address
+func (store *MockAccountStore) SetMiningAddress(program *acc.CtrlProgram) error {
+       rawProgram, err := json.Marshal(program)
+       if err != nil {
+               return err
+       }
+
+       if store.batch == nil {
+               store.db.Set([]byte(MiningAddressKey), rawProgram)
+       } else {
+               store.batch.Set([]byte(MiningAddressKey), rawProgram)
+       }
+       return nil
+}
+
+// SetStandardUTXO set standard utxo
+func (store *MockAccountStore) SetStandardUTXO(outputID bc.Hash, utxo *acc.UTXO) error {
+       data, err := json.Marshal(utxo)
+       if err != nil {
+               return err
+       }
+       if store.batch == nil {
+               store.db.Set(StandardUTXOKey(outputID), data)
+       } else {
+               store.batch.Set(StandardUTXOKey(outputID), data)
        }
+       return nil
 }