separate block vs state based validation

This commit is contained in:
Ethan Buchman
2017-12-21 16:49:47 -05:00
parent c2912d612a
commit 3d00c477fc
12 changed files with 231 additions and 209 deletions
+27 -57
View File
@@ -15,31 +15,21 @@ import (
)
// Block defines the atomic unit of a Tendermint blockchain.
// TODO: add Version byte
type Block struct {
*Header `json:"header"`
*Data `json:"data"`
LastCommit *Commit `json:"last_commit"`
}
// 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,
totalTxs int64, commit *Commit,
prevBlockID BlockID, valHash, appHash, consensusHash []byte,
partSize int) (*Block, *PartSet) {
newTxs := int64(len(txs))
// MakeBlock returns a new block with an empty header, except what can be computed from itself.
// It populates the same set of fields validated by ValidateBasic
func MakeBlock(height int64, txs []Tx, commit *Commit) *Block {
block := &Block{
Header: &Header{
ChainID: chainID,
Height: height,
Time: time.Now(),
NumTxs: newTxs,
TotalTxs: totalTxs + newTxs,
LastBlockID: prevBlockID,
ValidatorsHash: valHash,
AppHash: appHash, // state merkle root of txs from the previous block.
ConsensusHash: consensusHash,
Height: height,
Time: time.Now(),
NumTxs: int64(len(txs)),
},
LastCommit: commit,
Data: &Data{
@@ -47,37 +37,16 @@ func MakeBlock(height int64, chainID string, txs []Tx,
},
}
block.FillHeader()
return block, block.MakePartSet(partSize)
return block
}
// ValidateBasic performs basic validation that doesn't involve state data.
func (b *Block) ValidateBasic(chainID string, lastBlockHeight int64,
lastBlockTotalTx int64, lastBlockID BlockID,
lastBlockTime time.Time, appHash, consensusHash []byte) error {
if b.ChainID != chainID {
return fmt.Errorf("Wrong Block.Header.ChainID. Expected %v, got %v", chainID, b.ChainID)
}
if b.Height != lastBlockHeight+1 {
return fmt.Errorf("Wrong Block.Header.Height. Expected %v, got %v", lastBlockHeight+1, b.Height)
}
/* TODO: Determine bounds for Time
See blockchain/reactor "stopSyncingDurationMinutes"
if !b.Time.After(lastBlockTime) {
return errors.New("Invalid Block.Header.Time")
}
*/
// It checks the internal consistency of the block.
func (b *Block) ValidateBasic() error {
newTxs := int64(len(b.Data.Txs))
if b.NumTxs != newTxs {
return fmt.Errorf("Wrong Block.Header.NumTxs. Expected %v, got %v", newTxs, b.NumTxs)
}
if b.TotalTxs != lastBlockTotalTx+newTxs {
return fmt.Errorf("Wrong Block.Header.TotalTxs. Expected %v, got %v", lastBlockTotalTx+newTxs, b.TotalTxs)
}
if !b.LastBlockID.Equals(lastBlockID) {
return fmt.Errorf("Wrong Block.Header.LastBlockID. Expected %v, got %v", lastBlockID, b.LastBlockID)
}
if !bytes.Equal(b.LastCommitHash, b.LastCommit.Hash()) {
return fmt.Errorf("Wrong Block.Header.LastCommitHash. Expected %v, got %v", b.LastCommitHash, b.LastCommit.Hash())
}
@@ -89,13 +58,6 @@ func (b *Block) ValidateBasic(chainID string, lastBlockHeight int64,
if !bytes.Equal(b.DataHash, b.Data.Hash()) {
return fmt.Errorf("Wrong Block.Header.DataHash. Expected %v, got %v", b.DataHash, b.Data.Hash())
}
if !bytes.Equal(b.AppHash, appHash) {
return fmt.Errorf("Wrong Block.Header.AppHash. Expected %X, got %v", appHash, b.AppHash)
}
if !bytes.Equal(b.ConsensusHash, consensusHash) {
return fmt.Errorf("Wrong Block.Header.ConsensusHash. Expected %X, got %v", consensusHash, b.ConsensusHash)
}
// NOTE: the AppHash and ValidatorsHash are validated later.
return nil
}
@@ -171,18 +133,26 @@ func (b *Block) StringShort() string {
//-----------------------------------------------------------------------------
// Header defines the structure of a Tendermint block header
// TODO: limit header size
type Header struct {
ChainID string `json:"chain_id"`
Height int64 `json:"height"`
Time time.Time `json:"time"`
NumTxs int64 `json:"num_txs"` // XXX: Can we get rid of this?
TotalTxs int64 `json:"total_txs"`
LastBlockID BlockID `json:"last_block_id"`
// basic block info
ChainID string `json:"chain_id"`
Height int64 `json:"height"`
Time time.Time `json:"time"`
NumTxs int64 `json:"num_txs"`
// prev block info
LastBlockID BlockID `json:"last_block_id"`
TotalTxs int64 `json:"total_txs"`
// hashes of block data
LastCommitHash data.Bytes `json:"last_commit_hash"` // commit from validators from the last block
DataHash data.Bytes `json:"data_hash"` // transactions
ValidatorsHash data.Bytes `json:"validators_hash"` // validators for the current block
AppHash data.Bytes `json:"app_hash"` // state after txs from the previous block
ConsensusHash data.Bytes `json:"consensus_hash"` // consensus params for current block
// hashes from the app
ValidatorsHash data.Bytes `json:"validators_hash"` // validators for the current block
ConsensusHash data.Bytes `json:"consensus_hash"` // consensus params for current block
AppHash data.Bytes `json:"app_hash"` // state after txs from the previous block
}
// Hash returns the hash of the header.
+26 -48
View File
@@ -2,55 +2,59 @@ package types
import (
"testing"
"time"
"github.com/stretchr/testify/require"
crypto "github.com/tendermint/go-crypto"
cmn "github.com/tendermint/tmlibs/common"
)
func TestValidateBlock(t *testing.T) {
txs := []Tx{Tx("foo"), Tx("bar")}
lastID := makeBlockID()
valHash := []byte("val")
appHash := []byte("app")
consensusHash := []byte("consensus-params")
h := int64(3)
voteSet, _, vals := randVoteSet(h-1, 1, VoteTypePrecommit,
10, 1)
commit, err := makeCommit(lastID, h-1, 1, voteSet, vals)
commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals)
require.NoError(t, err)
block, _ := MakeBlock(h, "hello", txs, 10, commit,
lastID, valHash, appHash, consensusHash, 2)
block := MakeBlock(h, txs, commit)
require.NotNil(t, block)
// proper block must pass
err = block.ValidateBasic("hello", h-1, 10, lastID, block.Time, appHash, consensusHash)
err = block.ValidateBasic()
require.NoError(t, err)
// wrong chain fails
err = block.ValidateBasic("other", h-1, 10, lastID, block.Time, appHash, consensusHash)
// tamper with NumTxs
block = MakeBlock(h, txs, commit)
block.NumTxs += 1
err = block.ValidateBasic()
require.Error(t, err)
// wrong height fails
err = block.ValidateBasic("hello", h+4, 10, lastID, block.Time, appHash, consensusHash)
// remove 1/2 the commits
block = MakeBlock(h, txs, commit)
block.LastCommit.Precommits = commit.Precommits[:commit.Size()/2]
block.LastCommit.hash = nil // clear hash or change wont be noticed
err = block.ValidateBasic()
require.Error(t, err)
// wrong total tx fails
err = block.ValidateBasic("hello", h-1, 15, lastID, block.Time, appHash, consensusHash)
// tamper with LastCommitHash
block = MakeBlock(h, txs, commit)
block.LastCommitHash = []byte("something else")
err = block.ValidateBasic()
require.Error(t, err)
// wrong blockid fails
err = block.ValidateBasic("hello", h-1, 10, makeBlockID(), block.Time, appHash, consensusHash)
// tamper with data
block = MakeBlock(h, txs, commit)
block.Data.Txs[0] = Tx("something else")
block.Data.hash = nil // clear hash or change wont be noticed
err = block.ValidateBasic()
require.Error(t, err)
// wrong app hash fails
err = block.ValidateBasic("hello", h-1, 10, lastID, block.Time, []byte("bad-hash"), consensusHash)
require.Error(t, err)
// wrong consensus hash fails
err = block.ValidateBasic("hello", h-1, 10, lastID, block.Time, appHash, []byte("wrong-params"))
// tamper with DataHash
block = MakeBlock(h, txs, commit)
block.DataHash = cmn.RandBytes(len(block.DataHash))
err = block.ValidateBasic()
require.Error(t, err)
}
@@ -58,29 +62,3 @@ 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,
Timestamp: time.Now().UTC(),
}
// 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
}
-10
View File
@@ -59,16 +59,6 @@ func withBlockPartsHeader(vote *Vote, blockPartsHeader PartSetHeader) *Vote {
return vote
}
func signAddVote(privVal *PrivValidatorFS, vote *Vote, voteSet *VoteSet) (bool, error) {
var err error
vote.Signature, err = privVal.Signer.Sign(SignBytes(voteSet.ChainID(), vote))
if err != nil {
return false, err
}
added, err := voteSet.AddVote(vote)
return added, err
}
func TestAddVote(t *testing.T) {
height, round := int64(1), 0
voteSet, _, privValidators := randVoteSet(height, round, VoteTypePrevote, 10, 1)