OSDN Git Service

Add protocol state module test case (#380)
authoryahtoo <yahtoo.ma@gmail.com>
Mon, 12 Aug 2019 05:19:02 +0000 (13:19 +0800)
committerPaladz <yzhu101@uottawa.ca>
Mon, 12 Aug 2019 05:19:02 +0000 (13:19 +0800)
* Add consensus result test case

* Revert "Add consensus result test case"

This reverts commit e2c1a2332391eb4111e3158127f87d6f5f249d3c.

* Add protocol status test case

protocol/state/consensus_result_test.go
protocol/state/utxo_view_test.go

index e960e5f..807b13d 100644 (file)
@@ -2,9 +2,14 @@ package state
 
 import (
        "encoding/hex"
+       "math"
+       "reflect"
        "testing"
 
+       "github.com/davecgh/go-spew/spew"
+
        "github.com/vapor/consensus"
+       "github.com/vapor/crypto/ed25519/chainkd"
        "github.com/vapor/errors"
        "github.com/vapor/math/checked"
        "github.com/vapor/protocol/bc"
@@ -12,6 +17,178 @@ import (
        "github.com/vapor/testutil"
 )
 
+func TestApplyTransaction(t *testing.T) {
+       testXpub, _ := hex.DecodeString("a8018a1ba4d85fc7118bbd065612da78b2c503e61a1a093d9c659567c5d3a591b3752569fbcafa951b2304b8f576f3f220e03b957ca819840e7c29e4b7fb2c4d")
+
+       cases := []struct {
+               desc                string
+               tx                  *types.Tx
+               prevConsensusResult *ConsensusResult
+               postConsensusResult *ConsensusResult
+               wantErr             error
+       }{
+               {
+                       desc: "test num Of vote overflow",
+                       tx: &types.Tx{
+                               TxData: types.TxData{
+                                       Inputs:  []*types.TxInput{types.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), *consensus.BTMAssetID, 600000000, 0, nil)},
+                                       Outputs: []*types.TxOutput{types.NewVoteOutput(*consensus.BTMAssetID, math.MaxUint64-1000, []byte{0x51}, testXpub)},
+                               },
+                       },
+                       prevConsensusResult: &ConsensusResult{
+                               NumOfVote: map[string]uint64{
+                                       hex.EncodeToString(testXpub): 1000000,
+                               },
+                       },
+                       postConsensusResult: &ConsensusResult{
+                               NumOfVote: map[string]uint64{},
+                       },
+                       wantErr: checked.ErrOverflow,
+               },
+               {
+                       desc: "test num Of veto overflow",
+                       tx: &types.Tx{
+                               TxData: types.TxData{
+                                       Inputs:  []*types.TxInput{types.NewVetoInput(nil, bc.NewHash([32]byte{0xff}), *consensus.BTMAssetID, 100000000, 0, []byte{0x51}, testXpub)},
+                                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*consensus.BTMAssetID, 100000000, []byte{0x51})},
+                               },
+                       },
+                       prevConsensusResult: &ConsensusResult{
+                               NumOfVote: map[string]uint64{
+                                       hex.EncodeToString(testXpub): 1000000,
+                               },
+                       },
+                       postConsensusResult: &ConsensusResult{
+                               NumOfVote: map[string]uint64{},
+                       },
+                       wantErr: checked.ErrOverflow,
+               },
+               {
+                       desc: "test del pubkey from NumOfVote",
+                       tx: &types.Tx{
+                               TxData: types.TxData{
+                                       Inputs:  []*types.TxInput{types.NewVetoInput(nil, bc.NewHash([32]byte{0xff}), *consensus.BTMAssetID, 1000000, 0, []byte{0x51}, testXpub)},
+                                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*consensus.BTMAssetID, 100000000, []byte{0x51})},
+                               },
+                       },
+                       prevConsensusResult: &ConsensusResult{
+                               NumOfVote: map[string]uint64{
+                                       hex.EncodeToString(testXpub): 1000000,
+                               },
+                       },
+                       postConsensusResult: &ConsensusResult{
+                               NumOfVote: map[string]uint64{},
+                       },
+                       wantErr: nil,
+               },
+       }
+
+       for i, c := range cases {
+               if err := c.prevConsensusResult.ApplyTransaction(c.tx); err != nil {
+                       if err != c.wantErr {
+                               t.Errorf("test case #%d want err = %v, got err = %v", i, c.wantErr, err)
+                       }
+                       continue
+               }
+
+               if !testutil.DeepEqual(c.prevConsensusResult, c.postConsensusResult) {
+                       t.Errorf("test case #%d, want %v, got %v", i, c.postConsensusResult, c.prevConsensusResult)
+               }
+       }
+}
+
+func TestAttachCoinbaseReward(t *testing.T) {
+       cases := []struct {
+               desc                string
+               block               *types.Block
+               prevConsensusResult *ConsensusResult
+               postConsensusResult *ConsensusResult
+               wantErr             error
+       }{
+               {
+                       desc: "normal test with block contain coinbase tx and other tx",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{
+                                       Height: 1,
+                               },
+                               Transactions: []*types.Tx{
+                                       {
+                                               TxData: types.TxData{
+                                                       Inputs:  []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+                                               },
+                                       },
+                                       {
+                                               TxData: types.TxData{
+                                                       Inputs:  []*types.TxInput{types.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), *consensus.BTMAssetID, 300000000, 0, nil)},
+                                                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*consensus.BTMAssetID, 250000000, []byte{0x51})},
+                                               },
+                                       },
+                               },
+                       },
+                       prevConsensusResult: &ConsensusResult{
+                               CoinbaseReward: map[string]uint64{
+                                       hex.EncodeToString([]byte{0x51}): 50000000,
+                                       hex.EncodeToString([]byte{0x52}): 80000000,
+                               },
+                       },
+                       postConsensusResult: &ConsensusResult{
+                               CoinbaseReward: map[string]uint64{
+                                       hex.EncodeToString([]byte{0x51}): consensus.BlockSubsidy(1) + 50000000,
+                               },
+                       },
+                       wantErr: nil,
+               },
+               {
+                       desc: "test coinbase reward overflow",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{
+                                       Height: 100,
+                               },
+                               Transactions: []*types.Tx{
+                                       {
+                                               TxData: types.TxData{
+                                                       Inputs:  []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+                                               },
+                                       },
+                                       {
+                                               TxData: types.TxData{
+                                                       Inputs:  []*types.TxInput{types.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), *consensus.BTMAssetID, math.MaxUint64-80000000, 0, nil)},
+                                                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*consensus.BTMAssetID, 0, []byte{0x52})},
+                                               },
+                                       },
+                               },
+                       },
+                       prevConsensusResult: &ConsensusResult{
+                               CoinbaseReward: map[string]uint64{
+                                       hex.EncodeToString([]byte{0x51}): 80000000,
+                                       hex.EncodeToString([]byte{0x52}): 50000000,
+                               },
+                       },
+                       postConsensusResult: &ConsensusResult{
+                               CoinbaseReward: map[string]uint64{
+                                       hex.EncodeToString([]byte{0x51}): consensus.BlockSubsidy(1) + 50000000,
+                               },
+                       },
+                       wantErr: checked.ErrOverflow,
+               },
+       }
+
+       for i, c := range cases {
+               if err := c.prevConsensusResult.AttachCoinbaseReward(c.block); err != nil {
+                       if err != c.wantErr {
+                               t.Errorf("test case #%d want err = %v, got err = %v", i, c.wantErr, err)
+                       }
+                       continue
+               }
+
+               if !testutil.DeepEqual(c.prevConsensusResult, c.postConsensusResult) {
+                       t.Errorf("test case #%d, want %v, got %v", i, c.postConsensusResult, c.prevConsensusResult)
+               }
+       }
+}
+
 func TestCalCoinbaseReward(t *testing.T) {
        cases := []struct {
                desc       string
@@ -658,8 +835,100 @@ func TestConsensusDetachBlock(t *testing.T) {
                        },
                        wantErr: errors.New("not found coinbase receiver"),
                },
+               {
+                       desc: "test number of vote overflow",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{
+                                       Height:            consensus.MainNetParams.RoundVoteBlockNums - 1,
+                                       PreviousBlockHash: bc.Hash{V0: 1},
+                               },
+                               Transactions: []*types.Tx{
+                                       &types.Tx{
+                                               TxData: types.TxData{
+                                                       Inputs:  []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+                                               },
+                                       },
+                                       &types.Tx{
+                                               TxData: types.TxData{
+                                                       Inputs:  []*types.TxInput{types.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), *consensus.BTMAssetID, 600000000, 0, nil)},
+                                                       Outputs: []*types.TxOutput{types.NewVoteOutput(*consensus.BTMAssetID, 600000000, []byte{0x51}, testXpub)},
+                                               },
+                                       },
+                               },
+                       },
+                       consensusResult: &ConsensusResult{
+                               BlockHash: testutil.MustDecodeHash("4ebd9e7c00d3e0370931689c6eb9e2131c6700fe66e6b9718028dd75d7a4e329"),
+                               CoinbaseReward: map[string]uint64{
+                                       "51": 100000000,
+                               },
+                               NumOfVote: map[string]uint64{},
+                       },
+                       wantErr: checked.ErrOverflow,
+               },
+               {
+                       desc: "test number of veto overflow",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{
+                                       Height:            consensus.MainNetParams.RoundVoteBlockNums - 1,
+                                       PreviousBlockHash: bc.Hash{V0: 1},
+                               },
+                               Transactions: []*types.Tx{
+                                       &types.Tx{
+                                               TxData: types.TxData{
+                                                       Inputs:  []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+                                               },
+                                       },
+                                       &types.Tx{
+                                               TxData: types.TxData{
+                                                       Inputs:  []*types.TxInput{types.NewVetoInput(nil, bc.NewHash([32]byte{0xff}), *consensus.BTMAssetID, math.MaxUint64, 0, []byte{0x51}, testXpub)},
+                                                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*consensus.BTMAssetID, math.MaxUint64, []byte{0x51})},
+                                               },
+                                       },
+                               },
+                       },
+                       consensusResult: &ConsensusResult{
+                               BlockHash: testutil.MustDecodeHash("4ebd9e7c00d3e0370931689c6eb9e2131c6700fe66e6b9718028dd75d7a4e329"),
+                               CoinbaseReward: map[string]uint64{
+                                       "51": 100000000,
+                               },
+                               NumOfVote: map[string]uint64{
+                                       "a8018a1ba4d85fc7118bbd065612da78b2c503e61a1a093d9c659567c5d3a591b3752569fbcafa951b2304b8f576f3f220e03b957ca819840e7c29e4b7fb2c4d": 100,
+                               },
+                       },
+                       wantErr: checked.ErrOverflow,
+               },
+               {
+                       desc: "test detch coinbase overflow",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{
+                                       Height:            consensus.MainNetParams.RoundVoteBlockNums - 1,
+                                       PreviousBlockHash: bc.Hash{V0: 1},
+                               },
+                               Transactions: []*types.Tx{
+                                       &types.Tx{
+                                               TxData: types.TxData{
+                                                       Inputs:  []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
+                                               },
+                                       },
+                                       &types.Tx{
+                                               TxData: types.TxData{
+                                                       Inputs:  []*types.TxInput{types.NewVetoInput(nil, bc.NewHash([32]byte{0xff}), *consensus.BTMAssetID, math.MaxUint64, 0, []byte{0x51}, testXpub)},
+                                                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*consensus.BTMAssetID, math.MaxUint64, []byte{0x51})},
+                                               },
+                                       },
+                               },
+                       },
+                       consensusResult: &ConsensusResult{
+                               BlockHash:      testutil.MustDecodeHash("4ebd9e7c00d3e0370931689c6eb9e2131c6700fe66e6b9718028dd75d7a4e329"),
+                               CoinbaseReward: map[string]uint64{},
+                               NumOfVote:      map[string]uint64{},
+                       },
+                       wantErr: checked.ErrOverflow,
+               },
        }
-
        for i, c := range cases {
                if err := c.consensusResult.DetachBlock(c.block); err != nil {
                        if err.Error() != c.wantErr.Error() {
@@ -772,3 +1041,86 @@ func TestGetCoinbaseRewards(t *testing.T) {
                }
        }
 }
+
+func TestConsensusNodes(t *testing.T) {
+       var xpub1, xpub2, xpub3, xpub4, xpub5, xpub6, xpub7 chainkd.XPub
+       strPub1 := "0f8669abbd3cc0a167156188e428f940088d5b2f36bb3449df71d2bdc5e077814ea3f68628eef279ed435f51ee26cff00f8bd28fabfd500bedb2a9e369f5c825"
+       strPub2 := "e7f458ee8d2ba19b0fdc7410d1fd57e9c2e1a79377c661d66c55effe49d7ffc920e40510442d4a10b7bea06c09fb0b41f52601135adaaa7136204db36106c093"
+       strPub3 := "1bec3a35da038ec7a76c40986e80b5af2dcef60341970e3fc58b4db0797bd4ca9b2cbf3d7ab820832e22a80b5b86ae1427f7f706a7780089958b2862e7bc0842"
+       strPub4 := "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9"
+       strPub5 := "b928e46bb01e834fdf167185e31b15de7cc257af8bbdf17f9c7fefd5bb97b306d048b6bc0da2097152c1c2ff38333c756a543adbba7030a447dcc776b8ac64ef"
+       strPub6 := "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67"
+       strPub7 := "123"
+
+       xpub1.UnmarshalText([]byte(strPub1))
+       xpub2.UnmarshalText([]byte(strPub2))
+       xpub3.UnmarshalText([]byte(strPub3))
+       xpub4.UnmarshalText([]byte(strPub4))
+       xpub5.UnmarshalText([]byte(strPub5))
+       xpub6.UnmarshalText([]byte(strPub6))
+       xpub7.UnmarshalText([]byte(strPub7))
+
+       cases := []struct {
+               consensusResult *ConsensusResult
+               consensusNode   map[string]*ConsensusNode
+               wantErr         error
+       }{
+               {
+                       consensusResult: &ConsensusResult{
+                               NumOfVote: map[string]uint64{
+                                       strPub1: 838063475500000,  //1
+                                       strPub2: 474794800000000,  //3
+                                       strPub3: 833812985000000,  //2
+                                       strPub4: 285918061999999,  //4
+                                       strPub5: 1228455289930297, //0
+                                       strPub6: 274387690000000,  //5
+                                       strPub7: 1028455289930297,
+                               },
+                       },
+                       consensusNode: map[string]*ConsensusNode{
+                               strPub1: &ConsensusNode{XPub: xpub1, VoteNum: 838063475500000, Order: 1},
+                               strPub2: &ConsensusNode{XPub: xpub2, VoteNum: 474794800000000, Order: 3},
+                               strPub3: &ConsensusNode{XPub: xpub3, VoteNum: 833812985000000, Order: 2},
+                               strPub4: &ConsensusNode{XPub: xpub4, VoteNum: 285918061999999, Order: 4},
+                               strPub5: &ConsensusNode{XPub: xpub5, VoteNum: 1228455289930297, Order: 0},
+                               strPub6: &ConsensusNode{XPub: xpub6, VoteNum: 274387690000000, Order: 5},
+                       },
+                       wantErr: chainkd.ErrBadKeyStr,
+               },
+       }
+
+       for i, c := range cases {
+               consensusNode, err := c.consensusResult.ConsensusNodes()
+               if err != nil {
+                       if err != c.wantErr {
+                               t.Errorf("test case #%d want err = %v, got err = %v", i, c.wantErr, err)
+                       }
+                       continue
+               }
+
+               if !testutil.DeepEqual(consensusNode, c.consensusNode) {
+                       t.Errorf("test case #%d, want %v, got %v", i, c.consensusNode, consensusNode)
+               }
+       }
+}
+
+func TestFork(t *testing.T) {
+       consensusResult := &ConsensusResult{
+               Seq: 100,
+               NumOfVote: map[string]uint64{
+                       "a": 100,
+                       "b": 200,
+               },
+               CoinbaseReward: map[string]uint64{
+                       "c": 300,
+                       "d": 400,
+               },
+               BlockHash:   bc.NewHash([32]byte{0x1, 0x2}),
+               BlockHeight: 1024,
+       }
+       copy := consensusResult.Fork()
+
+       if !reflect.DeepEqual(consensusResult, copy) {
+               t.Fatalf("failed on test consensusResult got %s want %s", spew.Sdump(copy), spew.Sdump(consensusResult))
+       }
+}
index 3cd4f4a..0d3ddaf 100644 (file)
@@ -3,8 +3,11 @@ package state
 import (
        "testing"
 
+       "github.com/davecgh/go-spew/spew"
+
        "github.com/vapor/consensus"
        "github.com/vapor/database/storage"
+       "github.com/vapor/errors"
        "github.com/vapor/protocol/bc"
        "github.com/vapor/testutil"
 )
@@ -595,3 +598,832 @@ func TestDetachBlock(t *testing.T) {
                }
        }
 }
+
+func TestApplyCrossChainUTXO(t *testing.T) {
+       cases := []struct {
+               desc         string
+               block        *bc.Block
+               tx           *bc.Tx
+               prevUTXOView *UtxoViewpoint
+               postUTXOView *UtxoViewpoint
+               err          error
+       }{
+               {
+                       desc: "normal test",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{
+                                       Height: 100,
+                               },
+                       },
+                       tx: &bc.Tx{
+                               TxHeader: &bc.TxHeader{
+                                       ResultIds: []*bc.Hash{},
+                               },
+                               MainchainOutputIDs: []bc.Hash{
+                                       bc.Hash{V0: 0},
+                               },
+                               Entries: voteEntry,
+                       },
+                       prevUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.CrosschainUTXOType, 0, false),
+                               },
+                       },
+                       postUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.CrosschainUTXOType, 100, true),
+                               },
+                       },
+                       err: nil,
+               },
+               {
+                       desc: "test failed to find mainchain output entry",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{},
+                       },
+                       tx: &bc.Tx{
+                               TxHeader: &bc.TxHeader{
+                                       ResultIds: []*bc.Hash{},
+                               },
+                               MainchainOutputIDs: []bc.Hash{
+                                       bc.Hash{V0: 0},
+                               },
+                               Entries: voteEntry,
+                       },
+                       prevUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{},
+                       },
+                       postUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{},
+                       },
+                       err: errors.New("fail to find mainchain output entry"),
+               },
+               {
+                       desc: "test mainchain output has been spent",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{},
+                       },
+                       tx: &bc.Tx{
+                               TxHeader: &bc.TxHeader{
+                                       ResultIds: []*bc.Hash{},
+                               },
+                               MainchainOutputIDs: []bc.Hash{
+                                       bc.Hash{V0: 0},
+                               },
+                               Entries: voteEntry,
+                       },
+                       prevUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.CrosschainUTXOType, 0, true),
+                               },
+                       },
+                       postUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{},
+                       },
+                       err: errors.New("mainchain output has been spent"),
+               },
+       }
+
+       for i, c := range cases {
+               if err := c.prevUTXOView.applyCrossChainUtxo(c.block, c.tx); err != nil {
+                       if err.Error() != c.err.Error() {
+                               t.Errorf("test case #%d want err = %v, got err = %v", i, c.err, err)
+                       }
+                       continue
+               }
+
+               if !testutil.DeepEqual(c.prevUTXOView, c.postUTXOView) {
+                       t.Errorf("test case #%d, want %v, got %v", i, c.postUTXOView, c.prevUTXOView)
+               }
+       }
+}
+
+func TestApplyOutputUTXO(t *testing.T) {
+       cases := []struct {
+               desc         string
+               block        *bc.Block
+               tx           *bc.Tx
+               statusFail   bool
+               prevUTXOView *UtxoViewpoint
+               postUTXOView *UtxoViewpoint
+               err          error
+       }{
+               {
+                       desc: "normal test IntraChainOutput,VoteOutput,Retirement",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{},
+                       },
+                       tx: &bc.Tx{
+                               TxHeader: &bc.TxHeader{
+                                       ResultIds: []*bc.Hash{&bc.Hash{V0: 0}, &bc.Hash{V0: 1}, &bc.Hash{V0: 2}},
+                               },
+                               Entries: map[bc.Hash]bc.Entry{
+                                       bc.Hash{V0: 0}: &bc.IntraChainOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: &bc.AssetID{V0: 0},
+                                                               Amount:  100,
+                                                       },
+                                               },
+                                       },
+                                       bc.Hash{V0: 1}: &bc.VoteOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: &bc.AssetID{V0: 1},
+                                                       },
+                                               },
+                                       },
+                                       bc.Hash{V0: 2}: &bc.Retirement{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: &bc.AssetID{V0: 1},
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       prevUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{},
+                       },
+                       postUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.NormalUTXOType, 0, false),
+                                       bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.VoteUTXOType, 0, false),
+                               },
+                       },
+                       err: nil,
+               },
+               {
+                       desc: "test statusFail",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{},
+                       },
+                       tx: &bc.Tx{
+                               TxHeader: &bc.TxHeader{
+                                       ResultIds: []*bc.Hash{&bc.Hash{V0: 0}, &bc.Hash{V0: 1}, &bc.Hash{V0: 2}},
+                               },
+                               Entries: map[bc.Hash]bc.Entry{
+                                       bc.Hash{V0: 0}: &bc.IntraChainOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: &bc.AssetID{V0: 0},
+                                                               Amount:  100,
+                                                       },
+                                               },
+                                       },
+                                       bc.Hash{V0: 1}: &bc.VoteOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: consensus.BTMAssetID,
+                                                       },
+                                               },
+                                       },
+                                       bc.Hash{V0: 2}: &bc.Retirement{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: &bc.AssetID{V0: 1},
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       statusFail: true,
+                       prevUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{},
+                       },
+                       postUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.VoteUTXOType, 0, false),
+                               },
+                       },
+                       err: nil,
+               },
+               {
+                       desc: "test failed on found id from tx entry",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{},
+                       },
+                       tx: &bc.Tx{
+                               TxHeader: &bc.TxHeader{
+                                       ResultIds: []*bc.Hash{&bc.Hash{V0: 0}, &bc.Hash{V0: 1}, &bc.Hash{V0: 2}},
+                               },
+                               Entries: map[bc.Hash]bc.Entry{
+                                       bc.Hash{V0: 1}: &bc.VoteOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: consensus.BTMAssetID,
+                                                       },
+                                               },
+                                       },
+                                       bc.Hash{V0: 2}: &bc.Retirement{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: &bc.AssetID{V0: 1},
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       statusFail: false,
+                       prevUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{},
+                       },
+                       postUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{},
+                       },
+                       err: bc.ErrMissingEntry,
+               },
+       }
+
+       for i, c := range cases {
+               if err := c.prevUTXOView.applyOutputUtxo(c.block, c.tx, c.statusFail); err != nil {
+                       if errors.Root(err) != errors.Root(c.err) {
+                               t.Errorf("test case #%d want err = %v, got err = %v", i, c.err.Error(), err.Error())
+                       }
+                       continue
+               }
+
+               if !testutil.DeepEqual(c.prevUTXOView, c.postUTXOView) {
+                       t.Errorf("test case #%d, want %v, got %v", i, c.postUTXOView, c.prevUTXOView)
+               }
+       }
+}
+
+func TestApplySpendUTXO(t *testing.T) {
+       cases := []struct {
+               desc         string
+               block        *bc.Block
+               tx           *bc.Tx
+               statusFail   bool
+               prevUTXOView *UtxoViewpoint
+               postUTXOView *UtxoViewpoint
+               err          error
+       }{
+               {
+                       desc: "normal test",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{
+                                       Height: consensus.ActiveNetParams.VotePendingBlockNumber,
+                               },
+                       },
+                       tx: &bc.Tx{
+                               TxHeader:       &bc.TxHeader{},
+                               SpentOutputIDs: []bc.Hash{{V0: 0}, {V0: 1}, {V0: 2}},
+                               Entries: map[bc.Hash]bc.Entry{
+                                       bc.Hash{V0: 0}: &bc.IntraChainOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: &bc.AssetID{V0: 0},
+                                                               Amount:  100,
+                                                       },
+                                               },
+                                       },
+                                       bc.Hash{V0: 1}: &bc.VoteOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: &bc.AssetID{V0: 1},
+                                                       },
+                                               },
+                                       },
+                                       bc.Hash{V0: 2}: &bc.IntraChainOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: consensus.BTMAssetID,
+                                                               Amount:  100,
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       prevUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.NormalUTXOType, 0, false),
+                                       bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.VoteUTXOType, 0, false),
+                                       bc.Hash{V0: 2}: storage.NewUtxoEntry(storage.CoinbaseUTXOType, 0, false),
+                               },
+                       },
+                       postUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.NormalUTXOType, 0, true),
+                                       bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.VoteUTXOType, 0, true),
+                                       bc.Hash{V0: 2}: storage.NewUtxoEntry(storage.CoinbaseUTXOType, 0, true),
+                               },
+                       },
+                       err: nil,
+               },
+               {
+                       desc: "test coinbase is not ready for use",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{
+                                       Height: consensus.ActiveNetParams.CoinbasePendingBlockNumber - 1,
+                               },
+                       },
+                       tx: &bc.Tx{
+                               TxHeader:       &bc.TxHeader{},
+                               SpentOutputIDs: []bc.Hash{{V0: 1}, {V0: 2}},
+                               Entries: map[bc.Hash]bc.Entry{
+                                       bc.Hash{V0: 1}: &bc.VoteOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: &bc.AssetID{V0: 1},
+                                                       },
+                                               },
+                                       },
+                                       bc.Hash{V0: 2}: &bc.IntraChainOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: consensus.BTMAssetID,
+                                                               Amount:  100,
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       prevUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.NormalUTXOType, 0, false),
+                                       bc.Hash{V0: 2}: storage.NewUtxoEntry(storage.CoinbaseUTXOType, 0, false),
+                               },
+                       },
+                       postUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{},
+                       },
+                       err: errors.New("coinbase utxo is not ready for use"),
+               },
+               {
+                       desc: "test Coin is  within the voting lock time",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{
+                                       Height: consensus.ActiveNetParams.VotePendingBlockNumber - 1,
+                               },
+                       },
+                       tx: &bc.Tx{
+                               TxHeader:       &bc.TxHeader{},
+                               SpentOutputIDs: []bc.Hash{{V0: 1}, {V0: 2}},
+                               Entries: map[bc.Hash]bc.Entry{
+                                       bc.Hash{V0: 1}: &bc.VoteOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: &bc.AssetID{V0: 1},
+                                                       },
+                                               },
+                                       },
+                                       bc.Hash{V0: 2}: &bc.IntraChainOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: consensus.BTMAssetID,
+                                                               Amount:  100,
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       prevUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.NormalUTXOType, 0, false),
+                                       bc.Hash{V0: 2}: storage.NewUtxoEntry(storage.VoteUTXOType, 0, false),
+                               },
+                       },
+                       postUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{},
+                       },
+                       err: errors.New("Coin is  within the voting lock time"),
+               },
+               {
+                       desc: "test utxo has been spent",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{
+                                       Height: 0,
+                               },
+                       },
+                       tx: &bc.Tx{
+                               TxHeader:       &bc.TxHeader{},
+                               SpentOutputIDs: []bc.Hash{{V0: 0}, {V0: 1}, {V0: 2}},
+                               Entries: map[bc.Hash]bc.Entry{
+                                       bc.Hash{V0: 0}: &bc.IntraChainOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: &bc.AssetID{V0: 0},
+                                                               Amount:  100,
+                                                       },
+                                               },
+                                       },
+                                       bc.Hash{V0: 1}: &bc.VoteOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: &bc.AssetID{V0: 1},
+                                                       },
+                                               },
+                                       },
+                                       bc.Hash{V0: 2}: &bc.IntraChainOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: consensus.BTMAssetID,
+                                                               Amount:  100,
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       prevUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.NormalUTXOType, 0, true),
+                                       bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.VoteUTXOType, 0, false),
+                                       bc.Hash{V0: 2}: storage.NewUtxoEntry(storage.CoinbaseUTXOType, 0, false),
+                               },
+                       },
+                       postUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{},
+                       },
+                       err: errors.New("utxo has been spent"),
+               },
+               {
+                       desc: "test faild to find utxo entry",
+                       block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{
+                                       Height: 0,
+                               },
+                       },
+                       tx: &bc.Tx{
+                               TxHeader:       &bc.TxHeader{},
+                               SpentOutputIDs: []bc.Hash{{V0: 0}, {V0: 1}, {V0: 2}},
+                               Entries: map[bc.Hash]bc.Entry{
+                                       bc.Hash{V0: 0}: &bc.IntraChainOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: &bc.AssetID{V0: 0},
+                                                               Amount:  100,
+                                                       },
+                                               },
+                                       },
+                                       bc.Hash{V0: 1}: &bc.VoteOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: &bc.AssetID{V0: 1},
+                                                       },
+                                               },
+                                       },
+                                       bc.Hash{V0: 2}: &bc.IntraChainOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: consensus.BTMAssetID,
+                                                               Amount:  100,
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       prevUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.VoteUTXOType, 0, false),
+                                       bc.Hash{V0: 2}: storage.NewUtxoEntry(storage.CoinbaseUTXOType, 0, false),
+                               },
+                       },
+                       postUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{},
+                       },
+                       err: errors.New("fail to find utxo entry"),
+               },
+       }
+
+       for i, c := range cases {
+               if err := c.prevUTXOView.applySpendUtxo(c.block, c.tx, c.statusFail); err != nil {
+                       if err.Error() != c.err.Error() {
+                               t.Errorf("test case #%d want err = %v, got err = %v", i, err.Error(), c.err.Error())
+                       }
+                       continue
+               }
+
+               if !testutil.DeepEqual(c.prevUTXOView, c.postUTXOView) {
+                       t.Errorf("test case #%d, want %v, got %v", i, spew.Sdump(c.postUTXOView), spew.Sdump(c.prevUTXOView))
+               }
+       }
+}
+
+func TestDetachCrossChainUTXO(t *testing.T) {
+       cases := []struct {
+               desc         string
+               tx           *bc.Tx
+               prevUTXOView *UtxoViewpoint
+               postUTXOView *UtxoViewpoint
+               err          error
+       }{
+               {
+                       desc: "normal test",
+                       tx: &bc.Tx{
+                               TxHeader: &bc.TxHeader{
+                                       ResultIds: []*bc.Hash{},
+                               },
+                               MainchainOutputIDs: []bc.Hash{
+                                       bc.Hash{V0: 0},
+                               },
+                               Entries: voteEntry,
+                       },
+                       prevUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.CrosschainUTXOType, 0, true),
+                               },
+                       },
+                       postUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.CrosschainUTXOType, 0, false),
+                               },
+                       },
+                       err: nil,
+               },
+               {
+                       desc: "test failed to find mainchain output entry",
+                       tx: &bc.Tx{
+                               TxHeader: &bc.TxHeader{
+                                       ResultIds: []*bc.Hash{},
+                               },
+                               MainchainOutputIDs: []bc.Hash{
+                                       bc.Hash{V0: 0},
+                               },
+                               Entries: voteEntry,
+                       },
+                       prevUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{},
+                       },
+                       postUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{},
+                       },
+                       err: errors.New("fail to find mainchain output entry"),
+               },
+               {
+                       desc: "test revert output is unspent",
+                       tx: &bc.Tx{
+                               TxHeader: &bc.TxHeader{
+                                       ResultIds: []*bc.Hash{},
+                               },
+                               MainchainOutputIDs: []bc.Hash{
+                                       bc.Hash{V0: 0},
+                               },
+                               Entries: voteEntry,
+                       },
+                       prevUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.CrosschainUTXOType, 0, false),
+                               },
+                       },
+                       postUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{},
+                       },
+                       err: errors.New("mainchain output is unspent"),
+               },
+       }
+
+       for i, c := range cases {
+               if err := c.prevUTXOView.detachCrossChainUtxo(c.tx); err != nil {
+                       if err.Error() != c.err.Error() {
+                               t.Errorf("test case #%d want err = %v, got err = %v", i, c.err, err)
+                       }
+                       continue
+               }
+
+               if !testutil.DeepEqual(c.prevUTXOView, c.postUTXOView) {
+                       t.Errorf("test case #%d, want %v, got %v", i, c.postUTXOView, c.prevUTXOView)
+               }
+       }
+}
+
+func TestDetachOutputUTXO(t *testing.T) {
+       cases := []struct {
+               desc         string
+               tx           *bc.Tx
+               statusFail   bool
+               prevUTXOView *UtxoViewpoint
+               postUTXOView *UtxoViewpoint
+               err          error
+       }{
+               {
+                       desc: "normal test IntraChainOutput,VoteOutput",
+                       tx: &bc.Tx{
+                               TxHeader: &bc.TxHeader{
+                                       ResultIds: []*bc.Hash{&bc.Hash{V0: 0}, &bc.Hash{V0: 1}, &bc.Hash{V0: 2}},
+                               },
+                               Entries: map[bc.Hash]bc.Entry{
+                                       bc.Hash{V0: 0}: &bc.IntraChainOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: &bc.AssetID{V0: 0},
+                                                               Amount:  100,
+                                                       },
+                                               },
+                                       },
+                                       bc.Hash{V0: 1}: &bc.VoteOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: &bc.AssetID{V0: 1},
+                                                       },
+                                               },
+                                       },
+                                       bc.Hash{V0: 2}: &bc.Retirement{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: &bc.AssetID{V0: 1},
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       prevUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.NormalUTXOType, 0, true),
+                                       bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.VoteUTXOType, 0, true),
+                               },
+                       },
+                       postUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.NormalUTXOType, 0, true),
+                                       bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.VoteUTXOType, 0, true),
+                               },
+                       },
+                       err: nil,
+               },
+               {
+                       desc: "test statusFail",
+                       tx: &bc.Tx{
+                               TxHeader: &bc.TxHeader{
+                                       ResultIds: []*bc.Hash{&bc.Hash{V0: 0}, &bc.Hash{V0: 1}},
+                               },
+                               Entries: map[bc.Hash]bc.Entry{
+                                       bc.Hash{V0: 0}: &bc.IntraChainOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: &bc.AssetID{V0: 0},
+                                                               Amount:  100,
+                                                       },
+                                               },
+                                       },
+                                       bc.Hash{V0: 1}: &bc.VoteOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: consensus.BTMAssetID,
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       statusFail: true,
+                       prevUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{},
+                       },
+                       postUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.VoteUTXOType, 0, true),
+                               },
+                       },
+                       err: nil,
+               },
+               {
+                       desc: "test failed on found id from tx entry",
+                       tx: &bc.Tx{
+                               TxHeader: &bc.TxHeader{
+                                       ResultIds: []*bc.Hash{&bc.Hash{V0: 0}, &bc.Hash{V0: 1}, &bc.Hash{V0: 2}},
+                               },
+                               Entries: map[bc.Hash]bc.Entry{
+                                       bc.Hash{V0: 1}: &bc.VoteOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: consensus.BTMAssetID,
+                                                       },
+                                               },
+                                       },
+                                       bc.Hash{V0: 2}: &bc.Retirement{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: &bc.AssetID{V0: 1},
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       statusFail: false,
+                       prevUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{},
+                       },
+                       postUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{},
+                       },
+                       err: bc.ErrMissingEntry,
+               },
+       }
+
+       for i, c := range cases {
+               if err := c.prevUTXOView.detachOutputUtxo(c.tx, c.statusFail); err != nil {
+                       if errors.Root(err) != errors.Root(c.err) {
+                               t.Errorf("test case #%d want err = %v, got err = %v", i, c.err.Error(), err.Error())
+                       }
+                       continue
+               }
+
+               if !testutil.DeepEqual(c.prevUTXOView, c.postUTXOView) {
+                       t.Errorf("test case #%d, want %v, got %v", i, c.postUTXOView, c.prevUTXOView)
+               }
+       }
+}
+
+func TestDetachSpendUTXO(t *testing.T) {
+       cases := []struct {
+               desc         string
+               tx           *bc.Tx
+               statusFail   bool
+               prevUTXOView *UtxoViewpoint
+               postUTXOView *UtxoViewpoint
+               err          error
+       }{
+               {
+                       desc: "normal test",
+                       tx: &bc.Tx{
+                               TxHeader:       &bc.TxHeader{},
+                               SpentOutputIDs: []bc.Hash{{V0: 0}, {V0: 1}},
+                               Entries: map[bc.Hash]bc.Entry{
+                                       bc.Hash{V0: 0}: &bc.IntraChainOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: &bc.AssetID{V0: 0},
+                                                               Amount:  100,
+                                                       },
+                                               },
+                                       },
+                                       bc.Hash{V0: 1}: &bc.VoteOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: &bc.AssetID{V0: 1},
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       prevUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.NormalUTXOType, 0, true),
+                                       bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.VoteUTXOType, 0, true),
+                               },
+                       },
+                       postUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.NormalUTXOType, 0, false),
+                                       bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.VoteUTXOType, 0, false),
+                               },
+                       },
+                       err: nil,
+               },
+               {
+                       desc: "test utxo has been spent",
+                       tx: &bc.Tx{
+                               TxHeader:       &bc.TxHeader{},
+                               SpentOutputIDs: []bc.Hash{{V0: 0}, {V0: 1}, {V0: 2}},
+                               Entries: map[bc.Hash]bc.Entry{
+                                       bc.Hash{V0: 0}: &bc.IntraChainOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: consensus.BTMAssetID,
+                                                               Amount:  100,
+                                                       },
+                                               },
+                                       },
+                                       bc.Hash{V0: 1}: &bc.VoteOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: &bc.AssetID{V0: 1},
+                                                       },
+                                               },
+                                       },
+                                       bc.Hash{V0: 2}: &bc.IntraChainOutput{
+                                               Source: &bc.ValueSource{
+                                                       Value: &bc.AssetAmount{
+                                                               AssetId: consensus.BTMAssetID,
+                                                               Amount:  100,
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       prevUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.NormalUTXOType, 0, false),
+                                       bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.VoteUTXOType, 0, true),
+                               },
+                       },
+                       postUTXOView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{},
+                       },
+                       err: errors.New("try to revert an unspent utxo"),
+               },
+       }
+
+       for i, c := range cases {
+               if err := c.prevUTXOView.detachSpendUtxo(c.tx, c.statusFail); err != nil {
+                       if err.Error() != c.err.Error() {
+                               t.Errorf("test case #%d want err = %v, got err = %v", i, err.Error(), c.err.Error())
+                       }
+                       continue
+               }
+
+               if !testutil.DeepEqual(c.prevUTXOView, c.postUTXOView) {
+                       t.Errorf("test case #%d, want %v, got %v", i, spew.Sdump(c.postUTXOView), spew.Sdump(c.prevUTXOView))
+               }
+       }
+}