From: yahtoo Date: Mon, 12 Aug 2019 05:19:02 +0000 (+0800) Subject: Add protocol state module test case (#380) X-Git-Tag: v1.0.5~57 X-Git-Url: http://git.osdn.net/view?p=bytom%2Fvapor.git;a=commitdiff_plain;h=39b01cd36fb3c4341a3385be78ffdcdbc6f94bd5 Add protocol state module test case (#380) * Add consensus result test case * Revert "Add consensus result test case" This reverts commit e2c1a2332391eb4111e3158127f87d6f5f249d3c. * Add protocol status test case --- diff --git a/protocol/state/consensus_result_test.go b/protocol/state/consensus_result_test.go index e960e5f9..807b13db 100644 --- a/protocol/state/consensus_result_test.go +++ b/protocol/state/consensus_result_test.go @@ -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)) + } +} diff --git a/protocol/state/utxo_view_test.go b/protocol/state/utxo_view_test.go index 3cd4f4a9..0d3ddaf6 100644 --- a/protocol/state/utxo_view_test.go +++ b/protocol/state/utxo_view_test.go @@ -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)) + } + } +}