From: oysheng <33340252+oysheng@users.noreply.github.com> Date: Fri, 19 Jul 2019 10:13:53 +0000 (+0800) Subject: modify consensusResult for block (#324) X-Git-Tag: v1.0.5~96 X-Git-Url: http://git.osdn.net/view?p=bytom%2Fvapor.git;a=commitdiff_plain;h=aece9c02c1f342d7ec301981c2afa4b8d1fe0e6a modify consensusResult for block (#324) * modify consensusResult for block * delete * optimise --- diff --git a/proposal/proposal.go b/proposal/proposal.go index a4c51e43..50d85d1b 100644 --- a/proposal/proposal.go +++ b/proposal/proposal.go @@ -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") } diff --git a/proposal/proposal_test.go b/proposal/proposal_test.go index d8f7abf4..e211b143 100644 --- a/proposal/proposal_test.go +++ b/proposal/proposal_test.go @@ -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) } } } diff --git a/protocol/block.go b/protocol/block.go index 63009513..6799969c 100644 --- a/protocol/block.go +++ b/protocol/block.go @@ -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 } diff --git a/protocol/state/consensus_result.go b/protocol/state/consensus_result.go index 53bdbeb1..9fd571b3 100644 --- a/protocol/state/consensus_result.go +++ b/protocol/state/consensus_result.go @@ -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 index 00000000..e960e5f9 --- /dev/null +++ b/protocol/state/consensus_result_test.go @@ -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) + } + } +}