diff --git a/blockchain/reactor_test.go b/blockchain/reactor_test.go index 7342b72c0..9c631f5f9 100644 --- a/blockchain/reactor_test.go +++ b/blockchain/reactor_test.go @@ -106,7 +106,9 @@ func makeBlock(height int64, state *sm.State) *types.Block { valHash := state.Validators.Hash() prevBlockID := types.BlockID{prevHash, prevParts} block, _ := types.MakeBlock(height, "test_chain", makeTxs(height), - new(types.Commit), prevBlockID, valHash, state.AppHash, state.Params.BlockGossipParams.BlockPartSizeBytes) + state.LastBlockTotalTx, new(types.Commit), + prevBlockID, valHash, state.AppHash, + state.Params.BlockGossipParams.BlockPartSizeBytes) return block } diff --git a/consensus/mempool_test.go b/consensus/mempool_test.go index b35bdc536..099767e61 100644 --- a/consensus/mempool_test.go +++ b/consensus/mempool_test.go @@ -108,7 +108,8 @@ func TestTxConcurrentWithCommit(t *testing.T) { for nTxs := 0; nTxs < NTxs; { select { case b := <-newBlockCh: - nTxs += b.(types.TMEventData).Unwrap().(types.EventDataNewBlock).Block.Header.NumTxs + evt := b.(types.TMEventData).Unwrap().(types.EventDataNewBlock) + nTxs += int(evt.Block.Header.NumTxs) case <-ticker.C: panic("Timed out waiting to commit blocks with transactions") } diff --git a/consensus/state.go b/consensus/state.go index 68133f53a..8a2692a22 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -863,7 +863,8 @@ func (cs *ConsensusState) createProposalBlock() (block *types.Block, blockParts // Mempool validated transactions txs := cs.mempool.Reap(cs.config.MaxBlockSizeTxs) - return types.MakeBlock(cs.Height, cs.state.ChainID, txs, commit, + return types.MakeBlock(cs.Height, cs.state.ChainID, txs, + cs.state.LastBlockTotalTx, commit, cs.state.LastBlockID, cs.state.Validators.Hash(), cs.state.AppHash, cs.state.Params.BlockPartSizeBytes) } @@ -1200,7 +1201,7 @@ func (cs *ConsensusState) finalizeCommit(height int64) { // Create a copy of the state for staging // and an event cache for txs stateCopy := cs.state.Copy() - txEventBuffer := types.NewTxEventBuffer(cs.eventBus, block.NumTxs) + txEventBuffer := types.NewTxEventBuffer(cs.eventBus, int(block.NumTxs)) // Execute and commit the block, update and save the state, and update the mempool. // All calls to the proxyAppConn come here. diff --git a/lite/helpers.go b/lite/helpers.go index 5702203ba..f8d90de5a 100644 --- a/lite/helpers.go +++ b/lite/helpers.go @@ -113,10 +113,11 @@ func genHeader(chainID string, height int64, txs types.Txs, vals *types.ValidatorSet, appHash []byte) *types.Header { return &types.Header{ - ChainID: chainID, - Height: height, - Time: time.Now(), - NumTxs: len(txs), + ChainID: chainID, + Height: height, + Time: time.Now(), + NumTxs: int64(len(txs)), + TotalTxs: int64(len(txs)), // LastBlockID // LastCommitHash ValidatorsHash: vals.Hash(), diff --git a/state/execution.go b/state/execution.go index 1905b62f5..a5eabed27 100644 --- a/state/execution.go +++ b/state/execution.go @@ -178,7 +178,8 @@ func (s *State) ValidateBlock(block *types.Block) error { func (s *State) validateBlock(block *types.Block) error { // Basic block validation. - err := block.ValidateBasic(s.ChainID, s.LastBlockHeight, s.LastBlockID, s.LastBlockTime, s.AppHash) + err := block.ValidateBasic(s.ChainID, s.LastBlockHeight, + s.LastBlockTotalTx, s.LastBlockID, s.LastBlockTime, s.AppHash) if err != nil { return err } diff --git a/state/execution_test.go b/state/execution_test.go index 64f17094f..b946a75a3 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -66,7 +66,9 @@ func makeBlock(height int64, state *State) *types.Block { prevParts := types.PartSetHeader{} valHash := state.Validators.Hash() prevBlockID := types.BlockID{prevHash, prevParts} - block, _ := types.MakeBlock(height, chainID, makeTxs(height), new(types.Commit), - prevBlockID, valHash, state.AppHash, testPartSize) + block, _ := types.MakeBlock(height, chainID, + makeTxs(height), state.LastBlockTotalTx, + new(types.Commit), prevBlockID, valHash, + state.AppHash, testPartSize) return block } diff --git a/state/state.go b/state/state.go index 47de859e9..f8a8d1a03 100644 --- a/state/state.go +++ b/state/state.go @@ -45,11 +45,12 @@ type State struct { // These fields are updated by SetBlockAndValidators. // LastBlockHeight=0 at genesis (ie. block(H=0) does not exist) // LastValidators is used to validate block.LastCommit. - LastBlockHeight int64 - LastBlockID types.BlockID - LastBlockTime time.Time - Validators *types.ValidatorSet - LastValidators *types.ValidatorSet + LastBlockHeight int64 + LastBlockTotalTx int64 + LastBlockID types.BlockID + LastBlockTime time.Time + Validators *types.ValidatorSet + LastValidators *types.ValidatorSet // When a block returns a validator set change via EndBlock, // the change only applies to the next block. // So, if s.LastBlockHeight causes a valset change, @@ -113,6 +114,7 @@ func (s *State) Copy() *State { return &State{ db: s.db, LastBlockHeight: s.LastBlockHeight, + LastBlockTotalTx: s.LastBlockTotalTx, LastBlockID: s.LastBlockID, LastBlockTime: s.LastBlockTime, Validators: s.Validators.Copy(), @@ -250,16 +252,19 @@ func (s *State) SetBlockAndValidators(header *types.Header, blockPartsHeader typ nextValSet.IncrementAccum(1) s.setBlockAndValidators(header.Height, + header.NumTxs, types.BlockID{header.Hash(), blockPartsHeader}, header.Time, prevValSet, nextValSet) } -func (s *State) setBlockAndValidators(height int64, blockID types.BlockID, blockTime time.Time, +func (s *State) setBlockAndValidators(height int64, + newTxs int64, blockID types.BlockID, blockTime time.Time, prevValSet, nextValSet *types.ValidatorSet) { s.LastBlockHeight = height + s.LastBlockTotalTx += newTxs s.LastBlockID = blockID s.LastBlockTime = blockTime s.Validators = nextValSet diff --git a/types/block.go b/types/block.go index 4c91c5fe1..ff3d3811b 100644 --- a/types/block.go +++ b/types/block.go @@ -23,14 +23,19 @@ type Block struct { // MakeBlock returns a new block and corresponding partset from the given information. // TODO: Add version information to the Block struct. -func MakeBlock(height int64, chainID string, txs []Tx, commit *Commit, - prevBlockID BlockID, valHash, appHash []byte, partSize int) (*Block, *PartSet) { +func MakeBlock(height int64, chainID string, txs []Tx, + totalTxs int64, commit *Commit, + prevBlockID BlockID, valHash, appHash []byte, + partSize int) (*Block, *PartSet) { + + newTxs := int64(len(txs)) block := &Block{ Header: &Header{ ChainID: chainID, Height: height, Time: time.Now(), - NumTxs: len(txs), + NumTxs: newTxs, + TotalTxs: totalTxs + newTxs, LastBlockID: prevBlockID, ValidatorsHash: valHash, AppHash: appHash, // state merkle root of txs from the previous block. @@ -45,8 +50,10 @@ func MakeBlock(height int64, chainID string, txs []Tx, commit *Commit, } // ValidateBasic performs basic validation that doesn't involve state data. -func (b *Block) ValidateBasic(chainID string, lastBlockHeight int64, lastBlockID BlockID, +func (b *Block) ValidateBasic(chainID string, lastBlockHeight int64, + lastBlockTotalTx int64, lastBlockID BlockID, lastBlockTime time.Time, appHash []byte) error { + if b.ChainID != chainID { return errors.New(cmn.Fmt("Wrong Block.Header.ChainID. Expected %v, got %v", chainID, b.ChainID)) } @@ -60,8 +67,12 @@ func (b *Block) ValidateBasic(chainID string, lastBlockHeight int64, lastBlockID return errors.New("Invalid Block.Header.Time") } */ - if b.NumTxs != len(b.Data.Txs) { - return errors.New(cmn.Fmt("Wrong Block.Header.NumTxs. Expected %v, got %v", len(b.Data.Txs), b.NumTxs)) + newTxs := int64(len(b.Data.Txs)) + if b.NumTxs != newTxs { + return errors.New(cmn.Fmt("Wrong Block.Header.NumTxs. Expected %v, got %v", newTxs, b.NumTxs)) + } + if b.TotalTxs != lastBlockTotalTx+newTxs { + return errors.New(cmn.Fmt("Wrong Block.Header.TotalTxs. Expected %v, got %v", lastBlockTotalTx+newTxs, b.TotalTxs)) } if !b.LastBlockID.Equals(lastBlockID) { return errors.New(cmn.Fmt("Wrong Block.Header.LastBlockID. Expected %v, got %v", lastBlockID, b.LastBlockID)) @@ -160,7 +171,8 @@ type Header struct { ChainID string `json:"chain_id"` Height int64 `json:"height"` Time time.Time `json:"time"` - NumTxs int `json:"num_txs"` // XXX: Can we get rid of this? + NumTxs int64 `json:"num_txs"` // XXX: Can we get rid of this? + TotalTxs int64 `json:"total_txs"` LastBlockID BlockID `json:"last_block_id"` LastCommitHash data.Bytes `json:"last_commit_hash"` // commit from validators from the last block DataHash data.Bytes `json:"data_hash"` // transactions @@ -179,6 +191,7 @@ func (h *Header) Hash() data.Bytes { "Height": h.Height, "Time": h.Time, "NumTxs": h.NumTxs, + "TotalTxs": h.TotalTxs, "LastBlockID": h.LastBlockID, "LastCommit": h.LastCommitHash, "Data": h.DataHash, @@ -197,6 +210,7 @@ func (h *Header) StringIndented(indent string) string { %s Height: %v %s Time: %v %s NumTxs: %v +%s TotalTxs: %v %s LastBlockID: %v %s LastCommit: %v %s Data: %v @@ -207,6 +221,7 @@ func (h *Header) StringIndented(indent string) string { indent, h.Height, indent, h.Time, indent, h.NumTxs, + indent, h.TotalTxs, indent, h.LastBlockID, indent, h.LastCommitHash, indent, h.DataHash, diff --git a/types/block_test.go b/types/block_test.go new file mode 100644 index 000000000..f69de33f4 --- /dev/null +++ b/types/block_test.go @@ -0,0 +1,80 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" + crypto "github.com/tendermint/go-crypto" +) + +func TestValidateBlock(t *testing.T) { + txs := []Tx{Tx("foo"), Tx("bar")} + lastID := makeBlockID() + valHash := []byte("val") + appHash := []byte("app") + h := int64(3) + + voteSet, _, vals := randVoteSet(h-1, 1, VoteTypePrecommit, + 10, 1) + commit, err := makeCommit(lastID, h-1, 1, voteSet, vals) + require.NoError(t, err) + + block, _ := MakeBlock(h, "hello", txs, 10, commit, + lastID, valHash, appHash, 2) + require.NotNil(t, block) + + // proper block must pass + err = block.ValidateBasic("hello", h-1, 10, lastID, block.Time, appHash) + require.NoError(t, err) + + // wrong chain fails + err = block.ValidateBasic("other", h-1, 10, lastID, block.Time, appHash) + require.Error(t, err) + + // wrong height fails + err = block.ValidateBasic("hello", h+4, 10, lastID, block.Time, appHash) + require.Error(t, err) + + // wrong total tx fails + err = block.ValidateBasic("hello", h-1, 15, lastID, block.Time, appHash) + require.Error(t, err) + + // wrong blockid fails + err = block.ValidateBasic("hello", h-1, 10, makeBlockID(), block.Time, appHash) + require.Error(t, err) + + // wrong app hash fails + err = block.ValidateBasic("hello", h-1, 10, lastID, block.Time, []byte("bad-hash")) + require.Error(t, err) + +} + +func makeBlockID() BlockID { + blockHash, blockPartsHeader := crypto.CRandBytes(32), PartSetHeader{123, crypto.CRandBytes(32)} + return BlockID{blockHash, blockPartsHeader} +} + +func makeCommit(blockID BlockID, height int64, round int, + voteSet *VoteSet, + validators []*PrivValidatorFS) (*Commit, error) { + + voteProto := &Vote{ + ValidatorAddress: nil, + ValidatorIndex: -1, + Height: height, + Round: round, + Type: VoteTypePrecommit, + BlockID: blockID, + } + + // all sign + for i := 0; i < len(validators); i++ { + vote := withValidator(voteProto, validators[i].GetAddress(), i) + _, err := signAddVote(validators[i], vote, voteSet) + if err != nil { + return nil, err + } + } + + return voteSet.MakeCommit(), nil +}