OSDN Git Service

modify consensusResult for block (#324)
authoroysheng <33340252+oysheng@users.noreply.github.com>
Fri, 19 Jul 2019 10:13:53 +0000 (18:13 +0800)
committerPaladz <yzhu101@uottawa.ca>
Fri, 19 Jul 2019 10:13:53 +0000 (18:13 +0800)
* modify consensusResult for block

* delete

* optimise

proposal/proposal.go
proposal/proposal_test.go
protocol/block.go
protocol/state/consensus_result.go
protocol/state/consensus_result_test.go [new file with mode: 0644]

index a4c51e4..50d85d1 100644 (file)
@@ -24,7 +24,7 @@ const logModule = "mining"
 // createCoinbaseTx returns a coinbase transaction paying an appropriate subsidy
 // based on the passed block height to the provided address.  When the address
 // is nil, the coinbase transaction will instead be redeemable by anyone.
-func createCoinbaseTx(accountManager *account.Manager, blockHeight uint64) (tx *types.Tx, err error) {
+func createCoinbaseTx(accountManager *account.Manager, blockHeight uint64, rewards []state.CoinbaseReward) (tx *types.Tx, err error) {
        arbitrary := append([]byte{0x00}, []byte(strconv.FormatUint(blockHeight, 10))...)
        var script []byte
        if accountManager == nil {
@@ -49,11 +49,23 @@ func createCoinbaseTx(accountManager *account.Manager, blockHeight uint64) (tx *
                return nil, err
        }
 
+       for _, r := range rewards {
+               if err = builder.AddOutput(types.NewIntraChainOutput(*consensus.BTMAssetID, r.Amount, r.ControlProgram)); err != nil {
+                       return nil, err
+               }
+       }
+
        _, txData, err := builder.Build()
        if err != nil {
                return nil, err
        }
 
+       byteData, err := txData.MarshalText()
+       if err != nil {
+               return nil, err
+       }
+
+       txData.SerializedSize = uint64(len(byteData))
        tx = &types.Tx{
                TxData: *txData,
                Tx:     types.MapTx(txData),
@@ -61,22 +73,6 @@ func createCoinbaseTx(accountManager *account.Manager, blockHeight uint64) (tx *
        return tx, nil
 }
 
-// restructCoinbaseTx build coinbase transaction with aggregate outputs when it achieved the specified block height
-func restructCoinbaseTx(tx *types.Tx, rewards []state.CoinbaseReward) error {
-       for _, r := range rewards {
-               tx.Outputs = append(tx.Outputs, types.NewIntraChainOutput(*consensus.BTMAssetID, r.Amount, r.ControlProgram))
-       }
-
-       byteData, err := tx.TxData.MarshalText()
-       if err != nil {
-               return err
-       }
-
-       tx.TxData.SerializedSize = uint64(len(byteData))
-       tx.Tx = types.MapTx(&tx.TxData)
-       return nil
-}
-
 // NewBlockTemplate returns a new block template that is ready to be solved
 func NewBlockTemplate(c *protocol.Chain, txPool *protocol.TxPool, accountManager *account.Manager, timestamp uint64) (b *types.Block, err error) {
        view := state.NewUtxoViewpoint()
@@ -155,28 +151,19 @@ func NewBlockTemplate(c *protocol.Chain, txPool *protocol.TxPool, accountManager
 
        }
 
-       // create coinbase transaction
-       b.Transactions[0], err = createCoinbaseTx(accountManager, nextBlockHeight)
-       if err != nil {
-               return nil, errors.Wrap(err, "fail on createCoinbaseTx")
-       }
-
        consensusResult, err := c.GetConsensusResultByHash(&preBlockHash)
        if err != nil {
                return nil, err
        }
 
-       if err := consensusResult.AttachCoinbaseReward(b); err != nil {
-               return nil, err
-       }
-
-       rewards, err := consensusResult.GetCoinbaseRewards(nextBlockHeight)
+       rewards, err := consensusResult.GetCoinbaseRewards(preBlockHeader.Height)
        if err != nil {
                return nil, err
        }
 
-       // restruct coinbase transaction
-       if err = restructCoinbaseTx(b.Transactions[0], rewards); err != nil {
+       // create coinbase transaction
+       b.Transactions[0], err = createCoinbaseTx(accountManager, nextBlockHeight, rewards)
+       if err != nil {
                return nil, errors.Wrap(err, "fail on createCoinbaseTx")
        }
 
index d8f7abf..e211b14 100644 (file)
@@ -1,13 +1,12 @@
 package proposal
 
 import (
-       "encoding/hex"
        "testing"
 
        "github.com/vapor/consensus"
+       "github.com/vapor/protocol/bc"
        "github.com/vapor/protocol/bc/types"
        "github.com/vapor/protocol/state"
-       "github.com/vapor/protocol/vm/vmutil"
        "github.com/vapor/testutil"
 )
 
@@ -21,10 +20,9 @@ func TestCalCoinbaseTxReward(t *testing.T) {
        reductionInterval := uint64(840000)
 
        cases := []struct {
-               desc            string
-               block           *types.Block
-               consensusResult *state.ConsensusResult
-               wantReward      state.CoinbaseReward
+               desc       string
+               block      *types.Block
+               wantReward state.CoinbaseReward
        }{
                {
                        desc: "the block height is reductionInterval - 1",
@@ -32,10 +30,14 @@ func TestCalCoinbaseTxReward(t *testing.T) {
                                BlockHeader: types.BlockHeader{
                                        Height: reductionInterval - 1,
                                },
-                               Transactions: []*types.Tx{nil},
-                       },
-                       consensusResult: &state.ConsensusResult{
-                               CoinbaseReward: map[string]uint64{},
+                               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})},
+                                               },
+                                       },
+                               },
                        },
                        wantReward: state.CoinbaseReward{
                                Amount:         24,
@@ -48,10 +50,14 @@ func TestCalCoinbaseTxReward(t *testing.T) {
                                BlockHeader: types.BlockHeader{
                                        Height: reductionInterval,
                                },
-                               Transactions: []*types.Tx{nil},
-                       },
-                       consensusResult: &state.ConsensusResult{
-                               CoinbaseReward: map[string]uint64{},
+                               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})},
+                                               },
+                                       },
+                               },
                        },
                        wantReward: state.CoinbaseReward{
                                Amount:         24,
@@ -64,10 +70,14 @@ func TestCalCoinbaseTxReward(t *testing.T) {
                                BlockHeader: types.BlockHeader{
                                        Height: reductionInterval + 1,
                                },
-                               Transactions: []*types.Tx{nil},
-                       },
-                       consensusResult: &state.ConsensusResult{
-                               CoinbaseReward: map[string]uint64{},
+                               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})},
+                                               },
+                                       },
+                               },
                        },
                        wantReward: state.CoinbaseReward{
                                Amount:         12,
@@ -80,10 +90,14 @@ func TestCalCoinbaseTxReward(t *testing.T) {
                                BlockHeader: types.BlockHeader{
                                        Height: reductionInterval * 2,
                                },
-                               Transactions: []*types.Tx{nil},
-                       },
-                       consensusResult: &state.ConsensusResult{
-                               CoinbaseReward: map[string]uint64{},
+                               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})},
+                                               },
+                                       },
+                               },
                        },
                        wantReward: state.CoinbaseReward{
                                Amount:         12,
@@ -96,10 +110,14 @@ func TestCalCoinbaseTxReward(t *testing.T) {
                                BlockHeader: types.BlockHeader{
                                        Height: 2*reductionInterval + 1,
                                },
-                               Transactions: []*types.Tx{nil},
-                       },
-                       consensusResult: &state.ConsensusResult{
-                               CoinbaseReward: map[string]uint64{},
+                               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})},
+                                               },
+                                       },
+                               },
                        },
                        wantReward: state.CoinbaseReward{
                                Amount:         6,
@@ -108,25 +126,14 @@ func TestCalCoinbaseTxReward(t *testing.T) {
                },
        }
 
-       var err error
        for i, c := range cases {
-               c.block.Transactions[0], err = createCoinbaseTx(nil, c.block.Height)
+               gotReward, err := state.CalCoinbaseReward(c.block)
                if err != nil {
                        t.Fatal(err)
                }
 
-               if err := c.consensusResult.AttachCoinbaseReward(c.block); err != nil {
-                       t.Fatal(err)
-               }
-
-               defaultProgram, _ := vmutil.DefaultCoinbaseProgram()
-               gotReward := state.CoinbaseReward{
-                       Amount:         c.consensusResult.CoinbaseReward[hex.EncodeToString(defaultProgram)],
-                       ControlProgram: defaultProgram,
-               }
-
-               if !testutil.DeepEqual(gotReward, c.wantReward) {
-                       t.Fatalf("test case %d: %s, the coinbase reward got: %v, want: %v", i, c.desc, gotReward, c.wantReward)
+               if !testutil.DeepEqual(*gotReward, c.wantReward) {
+                       t.Fatalf("test case %d: %s, the coinbase reward got: %v, want: %v", i, c.desc, *gotReward, c.wantReward)
                }
        }
 }
@@ -140,10 +147,11 @@ func TestCountCoinbaseTxRewards(t *testing.T) {
        }
 
        cases := []struct {
-               desc            string
-               block           *types.Block
-               consensusResult *state.ConsensusResult
-               wantRewards     []state.CoinbaseReward
+               desc                string
+               block               *types.Block
+               consensusResult     *state.ConsensusResult
+               wantRewards         []state.CoinbaseReward
+               wantConsensusResult *state.ConsensusResult
        }{
                {
                        desc: "the block height is RoundVoteBlockNums - 1",
@@ -159,6 +167,11 @@ func TestCountCoinbaseTxRewards(t *testing.T) {
                                },
                        },
                        wantRewards: []state.CoinbaseReward{},
+                       wantConsensusResult: &state.ConsensusResult{
+                               CoinbaseReward: map[string]uint64{
+                                       "51": 34,
+                               },
+                       },
                },
                {
                        desc: "the block height is RoundVoteBlockNums",
@@ -174,14 +187,11 @@ func TestCountCoinbaseTxRewards(t *testing.T) {
                                        "52": 20,
                                },
                        },
-                       wantRewards: []state.CoinbaseReward{
-                               state.CoinbaseReward{
-                                       Amount:         34,
-                                       ControlProgram: []byte{0x51},
-                               },
-                               state.CoinbaseReward{
-                                       Amount:         20,
-                                       ControlProgram: []byte{0x52},
+                       wantRewards: []state.CoinbaseReward{},
+                       wantConsensusResult: &state.ConsensusResult{
+                               CoinbaseReward: map[string]uint64{
+                                       "51": 34,
+                                       "52": 20,
                                },
                        },
                },
@@ -198,7 +208,17 @@ func TestCountCoinbaseTxRewards(t *testing.T) {
                                        "51": 10,
                                },
                        },
-                       wantRewards: []state.CoinbaseReward{},
+                       wantRewards: []state.CoinbaseReward{
+                               state.CoinbaseReward{
+                                       Amount:         10,
+                                       ControlProgram: []byte{0x51},
+                               },
+                       },
+                       wantConsensusResult: &state.ConsensusResult{
+                               CoinbaseReward: map[string]uint64{
+                                       "51": 24,
+                               },
+                       },
                },
                {
                        desc: "the block height is RoundVoteBlockNums * 2",
@@ -210,28 +230,13 @@ func TestCountCoinbaseTxRewards(t *testing.T) {
                        },
                        consensusResult: &state.ConsensusResult{
                                CoinbaseReward: map[string]uint64{
-                                       "50": 20,
                                        "51": 10,
-                                       "52": 20,
-                                       "53": 30,
                                },
                        },
-                       wantRewards: []state.CoinbaseReward{
-                               state.CoinbaseReward{
-                                       Amount:         34,
-                                       ControlProgram: []byte{0x51},
-                               },
-                               state.CoinbaseReward{
-                                       Amount:         30,
-                                       ControlProgram: []byte{0x53},
-                               },
-                               state.CoinbaseReward{
-                                       Amount:         20,
-                                       ControlProgram: []byte{0x52},
-                               },
-                               state.CoinbaseReward{
-                                       Amount:         20,
-                                       ControlProgram: []byte{0x50},
+                       wantRewards: []state.CoinbaseReward{},
+                       wantConsensusResult: &state.ConsensusResult{
+                               CoinbaseReward: map[string]uint64{
+                                       "51": 34,
                                },
                        },
                },
@@ -244,29 +249,51 @@ func TestCountCoinbaseTxRewards(t *testing.T) {
                                Transactions: []*types.Tx{nil},
                        },
                        consensusResult: &state.ConsensusResult{
-                               CoinbaseReward: map[string]uint64{},
+                               CoinbaseReward: map[string]uint64{
+                                       "51": 10,
+                                       "52": 20,
+                               },
+                       },
+                       wantRewards: []state.CoinbaseReward{
+                               state.CoinbaseReward{
+                                       Amount:         20,
+                                       ControlProgram: []byte{0x52},
+                               },
+                               state.CoinbaseReward{
+                                       Amount:         10,
+                                       ControlProgram: []byte{0x51},
+                               },
+                       },
+                       wantConsensusResult: &state.ConsensusResult{
+                               CoinbaseReward: map[string]uint64{
+                                       "51": 24,
+                               },
                        },
-                       wantRewards: []state.CoinbaseReward{},
                },
        }
 
-       var err error
        for i, c := range cases {
-               c.block.Transactions[0], err = createCoinbaseTx(nil, c.block.Height)
+               rewards, err := c.consensusResult.GetCoinbaseRewards(c.block.Height - 1)
                if err != nil {
                        t.Fatal(err)
                }
 
-               if err := c.consensusResult.AttachCoinbaseReward(c.block); err != nil {
-                       t.Fatal(err)
+               if !testutil.DeepEqual(rewards, c.wantRewards) {
+                       t.Fatalf("test case %d: %s, the coinbase reward got: %v, want: %v", i, c.desc, rewards, c.wantRewards)
                }
 
-               rewards, err := c.consensusResult.GetCoinbaseRewards(c.block.Height)
+               // create coinbase transaction
+               c.block.Transactions[0], err = createCoinbaseTx(nil, c.block.Height, rewards)
                if err != nil {
                        t.Fatal(err)
                }
-               if !testutil.DeepEqual(rewards, c.wantRewards) {
-                       t.Fatalf("test case %d: %s, the coinbase reward got: %v, want: %v", i, c.desc, rewards, c.wantRewards)
+
+               if err := c.consensusResult.AttachCoinbaseReward(c.block); err != nil {
+                       t.Fatal(err)
+               }
+
+               if !testutil.DeepEqual(c.consensusResult, c.wantConsensusResult) {
+                       t.Fatalf("test case %d: %s, the consensusResult got: %v, want: %v", i, c.desc, c.consensusResult, c.wantConsensusResult)
                }
        }
 }
index 6300951..6799969 100644 (file)
@@ -265,11 +265,7 @@ func (c *Chain) saveBlock(block *types.Block) error {
                return err
        }
 
-       if err := consensusResult.AttachCoinbaseReward(block); err != nil {
-               return err
-       }
-
-       rewards, err := consensusResult.GetCoinbaseRewards(block.Height)
+       rewards, err := consensusResult.GetCoinbaseRewards(parent.Height)
        if err != nil {
                return err
        }
index 53bdbeb..9fd571b 100644 (file)
@@ -251,16 +251,6 @@ func (c *ConsensusResult) DetachBlock(block *types.Block) error {
 
 // DetachCoinbaseReward detach coinbase reward
 func (c *ConsensusResult) DetachCoinbaseReward(block *types.Block) error {
-       if block.Height%consensus.ActiveNetParams.RoundVoteBlockNums == 0 {
-               for i, output := range block.Transactions[0].Outputs {
-                       if i == 0 {
-                               continue
-                       }
-                       program := output.ControlProgram()
-                       c.CoinbaseReward[hex.EncodeToString(program)] = output.AssetAmount().Amount
-               }
-       }
-
        reward, err := CalCoinbaseReward(block)
        if err != nil {
                return err
@@ -275,6 +265,17 @@ func (c *ConsensusResult) DetachCoinbaseReward(block *types.Block) error {
        if c.CoinbaseReward[program] == 0 {
                delete(c.CoinbaseReward, program)
        }
+
+       if block.Height%consensus.ActiveNetParams.RoundVoteBlockNums == 1 {
+               c.CoinbaseReward = map[string]uint64{}
+               for i, output := range block.Transactions[0].Outputs {
+                       if i == 0 {
+                               continue
+                       }
+                       program := output.ControlProgram()
+                       c.CoinbaseReward[hex.EncodeToString(program)] = output.AssetAmount().Amount
+               }
+       }
        return nil
 }
 
diff --git a/protocol/state/consensus_result_test.go b/protocol/state/consensus_result_test.go
new file mode 100644 (file)
index 0000000..e960e5f
--- /dev/null
@@ -0,0 +1,774 @@
+package state
+
+import (
+       "encoding/hex"
+       "testing"
+
+       "github.com/vapor/consensus"
+       "github.com/vapor/errors"
+       "github.com/vapor/math/checked"
+       "github.com/vapor/protocol/bc"
+       "github.com/vapor/protocol/bc/types"
+       "github.com/vapor/testutil"
+)
+
+func TestCalCoinbaseReward(t *testing.T) {
+       cases := []struct {
+               desc       string
+               block      *types.Block
+               wantReward *CoinbaseReward
+               wantErr    error
+       }{
+               {
+                       desc: "normal test with block contain coinbase tx and other tx",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{
+                                       Height: 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, 300000000, 0, nil)},
+                                                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*consensus.BTMAssetID, 250000000, []byte{0x51})},
+                                               },
+                                       },
+                               },
+                       },
+                       wantReward: &CoinbaseReward{
+                               Amount:         consensus.BlockSubsidy(1) + 50000000,
+                               ControlProgram: []byte{0x51},
+                       },
+               },
+               {
+                       desc: "normal test with block only contain coinbase tx",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{
+                                       Height: 1200,
+                               },
+                               Transactions: []*types.Tx{
+                                       &types.Tx{
+                                               TxData: types.TxData{
+                                                       Inputs:  []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
+                                                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 1000000000, []byte{0x51})},
+                                               },
+                                       },
+                               },
+                       },
+                       wantReward: &CoinbaseReward{
+                               Amount:         consensus.BlockSubsidy(1),
+                               ControlProgram: []byte{0x51},
+                       },
+               },
+               {
+                       desc: "abnormal test with block not contain coinbase tx",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{
+                                       Height: 1,
+                               },
+                               Transactions: []*types.Tx{},
+                       },
+                       wantErr: errors.New("not found coinbase receiver"),
+               },
+       }
+
+       for i, c := range cases {
+               coinbaseReward, err := CalCoinbaseReward(c.block)
+               if err != nil {
+                       if err.Error() != c.wantErr.Error() {
+                               t.Errorf("test case #%d want err = %v, got err = %v", i, c.wantErr, err)
+                       }
+                       continue
+               }
+
+               if !testutil.DeepEqual(coinbaseReward, c.wantReward) {
+                       t.Errorf("test case #%d, want %v, got %v", i, c.wantReward, coinbaseReward)
+               }
+       }
+}
+
+func TestConsensusApplyBlock(t *testing.T) {
+       testXpub, _ := hex.DecodeString("a8018a1ba4d85fc7118bbd065612da78b2c503e61a1a093d9c659567c5d3a591b3752569fbcafa951b2304b8f576f3f220e03b957ca819840e7c29e4b7fb2c4d")
+       cases := []struct {
+               desc            string
+               block           *types.Block
+               consensusResult *ConsensusResult
+               wantResult      *ConsensusResult
+               wantErr         error
+       }{
+               {
+                       desc: "normal test with block height is equal to 1",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{
+                                       Height:            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, 300000000, 0, nil)},
+                                                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*consensus.BTMAssetID, 250000000, []byte{0x51})},
+                                               },
+                                       },
+                               },
+                       },
+                       consensusResult: &ConsensusResult{
+                               Seq:            0,
+                               NumOfVote:      map[string]uint64{},
+                               CoinbaseReward: map[string]uint64{},
+                               BlockHash:      bc.Hash{V0: 1},
+                               BlockHeight:    0,
+                       },
+                       wantResult: &ConsensusResult{
+                               Seq:       1,
+                               NumOfVote: map[string]uint64{},
+                               CoinbaseReward: map[string]uint64{
+                                       "51": consensus.BlockSubsidy(1) + 50000000,
+                               },
+                               BlockHash:   testutil.MustDecodeHash("50da6990965a16e97b739accca7eb8a0fadd47c8a742f77d18fa51ab60dd8724"),
+                               BlockHeight: 1,
+                       },
+               },
+               {
+                       desc: "normal test with block height is equal to RoundVoteBlockNums - 1",
+                       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{
+                               NumOfVote:      map[string]uint64{},
+                               CoinbaseReward: map[string]uint64{},
+                               BlockHash:      bc.Hash{V0: 1},
+                               BlockHeight:    consensus.MainNetParams.RoundVoteBlockNums - 2,
+                       },
+                       wantResult: &ConsensusResult{
+                               Seq: 1,
+                               NumOfVote: map[string]uint64{
+                                       "a8018a1ba4d85fc7118bbd065612da78b2c503e61a1a093d9c659567c5d3a591b3752569fbcafa951b2304b8f576f3f220e03b957ca819840e7c29e4b7fb2c4d": 600000000,
+                               },
+                               CoinbaseReward: map[string]uint64{
+                                       "51": consensus.BlockSubsidy(consensus.MainNetParams.RoundVoteBlockNums - 1),
+                               },
+                               BlockHash:   testutil.MustDecodeHash("4ebd9e7c00d3e0370931689c6eb9e2131c6700fe66e6b9718028dd75d7a4e329"),
+                               BlockHeight: consensus.MainNetParams.RoundVoteBlockNums - 1,
+                       },
+               },
+               {
+                       desc: "normal test with block height is equal to RoundVoteBlockNums",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{
+                                       Height:            consensus.MainNetParams.RoundVoteBlockNums,
+                                       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)},
+                                               },
+                                       },
+                                       &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})},
+                                               },
+                                       },
+                               },
+                       },
+                       consensusResult: &ConsensusResult{
+                               NumOfVote: map[string]uint64{},
+                               CoinbaseReward: map[string]uint64{
+                                       "51": 10000000,
+                                       "52": 20000000,
+                                       "53": 30000000,
+                               },
+                               BlockHash:   bc.Hash{V0: 1},
+                               BlockHeight: consensus.MainNetParams.RoundVoteBlockNums - 1,
+                       },
+                       wantResult: &ConsensusResult{
+                               Seq: 1,
+                               NumOfVote: map[string]uint64{
+                                       "a8018a1ba4d85fc7118bbd065612da78b2c503e61a1a093d9c659567c5d3a591b3752569fbcafa951b2304b8f576f3f220e03b957ca819840e7c29e4b7fb2c4d": 500000000,
+                               },
+                               CoinbaseReward: map[string]uint64{
+                                       "51": consensus.BlockSubsidy(consensus.MainNetParams.RoundVoteBlockNums) + 10000000,
+                                       "52": 20000000,
+                                       "53": 30000000,
+                               },
+                               BlockHash:   testutil.MustDecodeHash("1b449ba1f9b0ae41e31238b32943b95e9ab292d0b4a93d690ef9bc689c31d362"),
+                               BlockHeight: consensus.MainNetParams.RoundVoteBlockNums,
+                       },
+               },
+               {
+                       desc: "normal test with block height is equal to RoundVoteBlockNums + 1",
+                       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.NewIntraChainOutput(bc.AssetID{}, consensus.BlockSubsidy(consensus.MainNetParams.RoundVoteBlockNums)+10000000, []byte{0x51}),
+                                                               types.NewIntraChainOutput(bc.AssetID{}, 20000000, []byte{0x53}),
+                                                               types.NewIntraChainOutput(bc.AssetID{}, 30000000, []byte{0x52}),
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       consensusResult: &ConsensusResult{
+                               NumOfVote: map[string]uint64{},
+                               CoinbaseReward: map[string]uint64{
+                                       "51": consensus.BlockSubsidy(consensus.MainNetParams.RoundVoteBlockNums) + 10000000,
+                                       "52": 20000000,
+                                       "53": 30000000,
+                               },
+                               BlockHash:   bc.Hash{V0: 1},
+                               BlockHeight: consensus.MainNetParams.RoundVoteBlockNums,
+                       },
+                       wantResult: &ConsensusResult{
+                               Seq:       2,
+                               NumOfVote: map[string]uint64{},
+                               CoinbaseReward: map[string]uint64{
+                                       "51": consensus.BlockSubsidy(consensus.MainNetParams.RoundVoteBlockNums + 1),
+                               },
+                               BlockHash:   testutil.MustDecodeHash("52681d209ab811359f92daaf46a771ecd0f28505ae5e0ac2f0feb80f76fdda59"),
+                               BlockHeight: consensus.MainNetParams.RoundVoteBlockNums + 1,
+                       },
+               },
+               {
+                       desc: "normal test with block height is equal to RoundVoteBlockNums + 2",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{
+                                       Height:            consensus.MainNetParams.RoundVoteBlockNums + 2,
+                                       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}),
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       consensusResult: &ConsensusResult{
+                               NumOfVote: map[string]uint64{},
+                               CoinbaseReward: map[string]uint64{
+                                       "51": consensus.BlockSubsidy(consensus.MainNetParams.RoundVoteBlockNums + 1),
+                               },
+                               BlockHash:   bc.Hash{V0: 1},
+                               BlockHeight: consensus.MainNetParams.RoundVoteBlockNums + 1,
+                       },
+                       wantResult: &ConsensusResult{
+                               Seq:       2,
+                               NumOfVote: map[string]uint64{},
+                               CoinbaseReward: map[string]uint64{
+                                       "51": consensus.BlockSubsidy(consensus.MainNetParams.RoundVoteBlockNums+1) + consensus.BlockSubsidy(consensus.MainNetParams.RoundVoteBlockNums+2),
+                               },
+                               BlockHash:   testutil.MustDecodeHash("3de69f8af48b77e81232c71d30b25dd4ac482be45402a0fd417a4a040c135b76"),
+                               BlockHeight: consensus.MainNetParams.RoundVoteBlockNums + 2,
+                       },
+               },
+               {
+                       desc: "abnormal test with block parent hash is not equals last block hash of vote result",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{
+                                       Height:            1,
+                                       PreviousBlockHash: bc.Hash{V0: 0},
+                               },
+                               Transactions: []*types.Tx{},
+                       },
+                       consensusResult: &ConsensusResult{
+                               NumOfVote:      map[string]uint64{},
+                               CoinbaseReward: map[string]uint64{},
+                               BlockHash:      bc.Hash{V0: 1},
+                               BlockHeight:    2,
+                       },
+                       wantErr: errors.New("block parent hash is not equals last block hash of vote result"),
+               },
+               {
+                       desc: "abnormal test with arithmetic overflow for calculate transaction fee",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{
+                                       Height:            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, 100000000, 0, nil)},
+                                                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*consensus.BTMAssetID, 200000000, []byte{0x51})},
+                                               },
+                                       },
+                               },
+                       },
+                       consensusResult: &ConsensusResult{
+                               NumOfVote:      map[string]uint64{},
+                               CoinbaseReward: map[string]uint64{},
+                               BlockHash:      bc.Hash{V0: 1},
+                               BlockHeight:    2,
+                       },
+                       wantErr: errors.Wrap(checked.ErrOverflow, "calculate transaction fee"),
+               },
+               {
+                       desc: "abnormal test with not found coinbase receiver",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{
+                                       Height:            1,
+                                       PreviousBlockHash: bc.Hash{V0: 1},
+                               },
+                               Transactions: []*types.Tx{},
+                       },
+                       consensusResult: &ConsensusResult{
+                               NumOfVote:      map[string]uint64{},
+                               CoinbaseReward: map[string]uint64{},
+                               BlockHash:      bc.Hash{V0: 1},
+                               BlockHeight:    2,
+                       },
+                       wantErr: errors.New("not found coinbase receiver"),
+               },
+       }
+
+       for i, c := range cases {
+               if err := c.consensusResult.ApplyBlock(c.block); err != nil {
+                       if err.Error() != c.wantErr.Error() {
+                               t.Errorf("test case #%d want err = %v, got err = %v", i, c.wantErr, err)
+                       }
+                       continue
+               }
+
+               if !testutil.DeepEqual(c.consensusResult, c.wantResult) {
+                       t.Errorf("test case #%d, want %v, got %v", i, c.wantResult, c.consensusResult)
+               }
+       }
+}
+
+func TestConsensusDetachBlock(t *testing.T) {
+       testXpub, _ := hex.DecodeString("a8018a1ba4d85fc7118bbd065612da78b2c503e61a1a093d9c659567c5d3a591b3752569fbcafa951b2304b8f576f3f220e03b957ca819840e7c29e4b7fb2c4d")
+       cases := []struct {
+               desc            string
+               block           *types.Block
+               consensusResult *ConsensusResult
+               wantResult      *ConsensusResult
+               wantErr         error
+       }{
+               {
+                       desc: "normal test with block height is equal to 1",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{
+                                       Height:            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, 300000000, 0, nil)},
+                                                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*consensus.BTMAssetID, 250000000, []byte{0x51})},
+                                               },
+                                       },
+                               },
+                       },
+                       consensusResult: &ConsensusResult{
+                               Seq:       1,
+                               NumOfVote: map[string]uint64{},
+                               CoinbaseReward: map[string]uint64{
+                                       "51": consensus.BlockSubsidy(1) + 50000000,
+                               },
+                               BlockHash:   testutil.MustDecodeHash("50da6990965a16e97b739accca7eb8a0fadd47c8a742f77d18fa51ab60dd8724"),
+                               BlockHeight: 1,
+                       },
+                       wantResult: &ConsensusResult{
+                               Seq:            0,
+                               NumOfVote:      map[string]uint64{},
+                               CoinbaseReward: map[string]uint64{},
+                               BlockHash:      bc.Hash{V0: 1},
+                               BlockHeight:    0,
+                       },
+               },
+               {
+                       desc: "normal test with block height is equal to RoundVoteBlockNums - 1",
+                       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{
+                               NumOfVote: map[string]uint64{
+                                       "a8018a1ba4d85fc7118bbd065612da78b2c503e61a1a093d9c659567c5d3a591b3752569fbcafa951b2304b8f576f3f220e03b957ca819840e7c29e4b7fb2c4d": 600000000,
+                               },
+                               CoinbaseReward: map[string]uint64{
+                                       "51": consensus.BlockSubsidy(consensus.MainNetParams.RoundVoteBlockNums - 1),
+                               },
+                               BlockHash:   testutil.MustDecodeHash("4ebd9e7c00d3e0370931689c6eb9e2131c6700fe66e6b9718028dd75d7a4e329"),
+                               BlockHeight: consensus.MainNetParams.RoundVoteBlockNums - 1,
+                       },
+                       wantResult: &ConsensusResult{
+                               Seq:            1,
+                               NumOfVote:      map[string]uint64{},
+                               CoinbaseReward: map[string]uint64{},
+                               BlockHash:      bc.Hash{V0: 1},
+                               BlockHeight:    consensus.MainNetParams.RoundVoteBlockNums - 2,
+                       },
+               },
+               {
+                       desc: "normal test with block height is equal to RoundVoteBlockNums",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{
+                                       Height:            consensus.MainNetParams.RoundVoteBlockNums,
+                                       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}),
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       consensusResult: &ConsensusResult{
+                               NumOfVote: map[string]uint64{
+                                       "a8018a1ba4d85fc7118bbd065612da78b2c503e61a1a093d9c659567c5d3a591b3752569fbcafa951b2304b8f576f3f220e03b957ca819840e7c29e4b7fb2c4d": 500000000,
+                               },
+                               CoinbaseReward: map[string]uint64{
+                                       "51": consensus.BlockSubsidy(consensus.MainNetParams.RoundVoteBlockNums) + 100000000,
+                               },
+                               BlockHash:   testutil.MustDecodeHash("1b449ba1f9b0ae41e31238b32943b95e9ab292d0b4a93d690ef9bc689c31d362"),
+                               BlockHeight: consensus.MainNetParams.RoundVoteBlockNums,
+                       },
+                       wantResult: &ConsensusResult{
+                               Seq: 1,
+                               NumOfVote: map[string]uint64{
+                                       "a8018a1ba4d85fc7118bbd065612da78b2c503e61a1a093d9c659567c5d3a591b3752569fbcafa951b2304b8f576f3f220e03b957ca819840e7c29e4b7fb2c4d": 500000000,
+                               },
+                               CoinbaseReward: map[string]uint64{
+                                       "51": 100000000,
+                               },
+                               BlockHash:   bc.Hash{V0: 1},
+                               BlockHeight: consensus.MainNetParams.RoundVoteBlockNums - 1,
+                       },
+               },
+               {
+                       desc: "normal test with block height is equal to RoundVoteBlockNums + 1",
+                       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.NewIntraChainOutput(bc.AssetID{}, consensus.BlockSubsidy(consensus.MainNetParams.RoundVoteBlockNums)+10000000, []byte{0x51}),
+                                                               types.NewIntraChainOutput(bc.AssetID{}, 20000000, []byte{0x52}),
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       consensusResult: &ConsensusResult{
+                               NumOfVote: map[string]uint64{},
+                               CoinbaseReward: map[string]uint64{
+                                       "51": consensus.BlockSubsidy(consensus.MainNetParams.RoundVoteBlockNums + 1),
+                               },
+                               BlockHash:   testutil.MustDecodeHash("52681d209ab811359f92daaf46a771ecd0f28505ae5e0ac2f0feb80f76fdda59"),
+                               BlockHeight: consensus.MainNetParams.RoundVoteBlockNums,
+                       },
+                       wantResult: &ConsensusResult{
+                               Seq:       1,
+                               NumOfVote: map[string]uint64{},
+                               CoinbaseReward: map[string]uint64{
+                                       "51": consensus.BlockSubsidy(consensus.MainNetParams.RoundVoteBlockNums) + 10000000,
+                                       "52": 20000000,
+                               },
+                               BlockHash:   bc.Hash{V0: 1},
+                               BlockHeight: consensus.MainNetParams.RoundVoteBlockNums,
+                       },
+               },
+               {
+                       desc: "normal test with block height is equal to RoundVoteBlockNums + 2",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{
+                                       Height:            consensus.MainNetParams.RoundVoteBlockNums + 2,
+                                       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}),
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       consensusResult: &ConsensusResult{
+                               NumOfVote: map[string]uint64{},
+                               CoinbaseReward: map[string]uint64{
+                                       "51": consensus.BlockSubsidy(consensus.MainNetParams.RoundVoteBlockNums+1) + 1000000,
+                               },
+                               BlockHash:   testutil.MustDecodeHash("3de69f8af48b77e81232c71d30b25dd4ac482be45402a0fd417a4a040c135b76"),
+                               BlockHeight: consensus.MainNetParams.RoundVoteBlockNums + 1,
+                       },
+                       wantResult: &ConsensusResult{
+                               Seq:       2,
+                               NumOfVote: map[string]uint64{},
+                               CoinbaseReward: map[string]uint64{
+                                       "51": 1000000,
+                               },
+                               BlockHash:   bc.Hash{V0: 1},
+                               BlockHeight: consensus.MainNetParams.RoundVoteBlockNums + 1,
+                       },
+               },
+               {
+                       desc: "abnormal test with block hash is not equals last block hash of vote result",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{
+                                       Height:            2,
+                                       PreviousBlockHash: bc.Hash{V0: 0},
+                               },
+                               Transactions: []*types.Tx{},
+                       },
+                       consensusResult: &ConsensusResult{
+                               NumOfVote:      map[string]uint64{},
+                               CoinbaseReward: map[string]uint64{},
+                               BlockHash:      bc.Hash{V0: 1},
+                               BlockHeight:    1,
+                       },
+                       wantErr: errors.New("block hash is not equals last block hash of vote result"),
+               },
+               {
+                       desc: "abnormal test with arithmetic overflow for calculate transaction fee",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{
+                                       Height:            2,
+                                       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, 100000000, 0, nil)},
+                                                       Outputs: []*types.TxOutput{types.NewIntraChainOutput(*consensus.BTMAssetID, 200000000, []byte{0x51})},
+                                               },
+                                       },
+                               },
+                       },
+                       consensusResult: &ConsensusResult{
+                               NumOfVote:      map[string]uint64{},
+                               CoinbaseReward: map[string]uint64{},
+                               BlockHash:      testutil.MustDecodeHash("02b7fb48defc4f4a3e1ef8403f7c0be78c4414ee66aa81fd702caa1e41a906df"),
+                               BlockHeight:    1,
+                       },
+                       wantErr: errors.Wrap(checked.ErrOverflow, "calculate transaction fee"),
+               },
+               {
+                       desc: "abnormal test with not found coinbase receiver",
+                       block: &types.Block{
+                               BlockHeader: types.BlockHeader{
+                                       Height:            1,
+                                       PreviousBlockHash: bc.Hash{V0: 1},
+                               },
+                               Transactions: []*types.Tx{},
+                       },
+                       consensusResult: &ConsensusResult{
+                               NumOfVote:      map[string]uint64{},
+                               CoinbaseReward: map[string]uint64{},
+                               BlockHash:      testutil.MustDecodeHash("50da6990965a16e97b739accca7eb8a0fadd47c8a742f77d18fa51ab60dd8724"),
+                               BlockHeight:    1,
+                       },
+                       wantErr: errors.New("not found coinbase receiver"),
+               },
+       }
+
+       for i, c := range cases {
+               if err := c.consensusResult.DetachBlock(c.block); err != nil {
+                       if err.Error() != c.wantErr.Error() {
+                               t.Errorf("test case #%d want err = %v, got err = %v", i, c.wantErr, err)
+                       }
+                       continue
+               }
+
+               if !testutil.DeepEqual(c.consensusResult, c.wantResult) {
+                       t.Errorf("test case #%d, want %v, got %v", i, c.wantResult, c.consensusResult)
+               }
+       }
+}
+
+func TestGetCoinbaseRewards(t *testing.T) {
+       cases := []struct {
+               desc            string
+               blockHeight     uint64
+               consensusResult *ConsensusResult
+               wantRewards     []CoinbaseReward
+       }{
+               {
+                       desc:        "the block height is RoundVoteBlockNums - 1",
+                       blockHeight: consensus.ActiveNetParams.RoundVoteBlockNums - 1,
+                       consensusResult: &ConsensusResult{
+                               CoinbaseReward: map[string]uint64{
+                                       "51": 10,
+                               },
+                       },
+                       wantRewards: []CoinbaseReward{},
+               },
+               {
+                       desc:        "the block height is RoundVoteBlockNums",
+                       blockHeight: consensus.ActiveNetParams.RoundVoteBlockNums,
+                       consensusResult: &ConsensusResult{
+                               CoinbaseReward: map[string]uint64{
+                                       "51": 10,
+                                       "52": 20,
+                               },
+                       },
+                       wantRewards: []CoinbaseReward{
+                               CoinbaseReward{
+                                       Amount:         20,
+                                       ControlProgram: []byte{0x52},
+                               },
+                               CoinbaseReward{
+                                       Amount:         10,
+                                       ControlProgram: []byte{0x51},
+                               },
+                       },
+               },
+               {
+                       desc:        "the block height is RoundVoteBlockNums + 1",
+                       blockHeight: consensus.ActiveNetParams.RoundVoteBlockNums + 1,
+                       consensusResult: &ConsensusResult{
+                               CoinbaseReward: map[string]uint64{
+                                       "51": 10,
+                               },
+                       },
+                       wantRewards: []CoinbaseReward{},
+               },
+               {
+                       desc:        "the block height is RoundVoteBlockNums * 2",
+                       blockHeight: consensus.ActiveNetParams.RoundVoteBlockNums * 2,
+                       consensusResult: &ConsensusResult{
+                               CoinbaseReward: map[string]uint64{
+                                       "50": 20,
+                                       "51": 10,
+                                       "52": 20,
+                                       "53": 30,
+                               },
+                       },
+                       wantRewards: []CoinbaseReward{
+                               CoinbaseReward{
+                                       Amount:         30,
+                                       ControlProgram: []byte{0x53},
+                               },
+                               CoinbaseReward{
+                                       Amount:         20,
+                                       ControlProgram: []byte{0x52},
+                               },
+                               CoinbaseReward{
+                                       Amount:         20,
+                                       ControlProgram: []byte{0x50},
+                               },
+                               CoinbaseReward{
+                                       Amount:         10,
+                                       ControlProgram: []byte{0x51},
+                               },
+                       },
+               },
+               {
+                       desc:        "the block height is 2*RoundVoteBlockNums + 1",
+                       blockHeight: 2*consensus.ActiveNetParams.RoundVoteBlockNums + 1,
+                       consensusResult: &ConsensusResult{
+                               CoinbaseReward: map[string]uint64{},
+                       },
+                       wantRewards: []CoinbaseReward{},
+               },
+       }
+
+       for i, c := range cases {
+               rewards, err := c.consensusResult.GetCoinbaseRewards(c.blockHeight)
+               if err != nil {
+                       t.Fatal(err)
+               }
+
+               if !testutil.DeepEqual(rewards, c.wantRewards) {
+                       t.Errorf("test case #%d, want %v, got %v", i, c.wantRewards, rewards)
+               }
+       }
+}