Merge pull request #972 from tendermint/feature/enhance-endblock

Update EndBlock parameters
This commit is contained in:
Ethan Buchman
2017-12-22 01:30:58 -05:00
committed by GitHub
33 changed files with 750 additions and 302 deletions
+33 -55
View File
@@ -15,30 +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 []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.
Height: height,
Time: time.Now(),
NumTxs: int64(len(txs)),
},
LastCommit: commit,
Data: &Data{
@@ -46,39 +37,18 @@ 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 []byte) error {
if b.ChainID != chainID {
return errors.New(cmn.Fmt("Wrong Block.Header.ChainID. Expected %v, got %v", chainID, b.ChainID))
}
if b.Height != lastBlockHeight+1 {
return errors.New(cmn.Fmt("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 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))
return fmt.Errorf("Wrong Block.Header.NumTxs. Expected %v, got %v", newTxs, b.NumTxs)
}
if !bytes.Equal(b.LastCommitHash, b.LastCommit.Hash()) {
return errors.New(cmn.Fmt("Wrong Block.Header.LastCommitHash. Expected %v, got %v", b.LastCommitHash, b.LastCommit.Hash()))
return fmt.Errorf("Wrong Block.Header.LastCommitHash. Expected %v, got %v", b.LastCommitHash, b.LastCommit.Hash())
}
if b.Header.Height != 1 {
if err := b.LastCommit.ValidateBasic(); err != nil {
@@ -86,12 +56,8 @@ func (b *Block) ValidateBasic(chainID string, lastBlockHeight int64,
}
}
if !bytes.Equal(b.DataHash, b.Data.Hash()) {
return errors.New(cmn.Fmt("Wrong Block.Header.DataHash. Expected %v, got %v", 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 errors.New(cmn.Fmt("Wrong Block.Header.AppHash. Expected %X, got %v", appHash, b.AppHash))
}
// NOTE: the AppHash and ValidatorsHash are validated later.
return nil
}
@@ -167,17 +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
// 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.
@@ -197,6 +172,7 @@ func (h *Header) Hash() data.Bytes {
"Data": h.DataHash,
"Validators": h.ValidatorsHash,
"App": h.AppHash,
"Consensus": h.ConsensusHash,
})
}
@@ -216,6 +192,7 @@ func (h *Header) StringIndented(indent string) string {
%s Data: %v
%s Validators: %v
%s App: %v
%s Conensus: %v
%s}#%v`,
indent, h.ChainID,
indent, h.Height,
@@ -227,6 +204,7 @@ func (h *Header) StringIndented(indent string) string {
indent, h.DataHash,
indent, h.ValidatorsHash,
indent, h.AppHash,
indent, h.ConsensusHash,
indent, h.Hash())
}
+26 -44
View File
@@ -2,81 +2,63 @@ 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")
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, 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)
err = block.ValidateBasic()
require.NoError(t, err)
// wrong chain fails
err = block.ValidateBasic("other", h-1, 10, lastID, block.Time, appHash)
// 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)
// 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)
// 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)
// 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"))
// tamper with DataHash
block = MakeBlock(h, txs, commit)
block.DataHash = cmn.RandBytes(len(block.DataHash))
err = block.ValidateBasic()
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,
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
}
+1 -1
View File
@@ -54,7 +54,7 @@ func TestGenesis(t *testing.T) {
assert.NoError(t, err, "expected no error for valid genDoc json")
// test with invalid consensus params
genDoc.ConsensusParams.BlockSizeParams.MaxBytes = 0
genDoc.ConsensusParams.BlockSize.MaxBytes = 0
genDocBytes, err = json.Marshal(genDoc)
assert.NoError(t, err, "error marshalling genDoc")
genDoc, err = GenesisDocFromJSON(genDocBytes)
+88 -33
View File
@@ -2,6 +2,9 @@ package types
import (
"github.com/pkg/errors"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/tmlibs/merkle"
)
const (
@@ -11,58 +14,58 @@ const (
// ConsensusParams contains consensus critical parameters
// that determine the validity of blocks.
type ConsensusParams struct {
BlockSizeParams `json:"block_size_params"`
TxSizeParams `json:"tx_size_params"`
BlockGossipParams `json:"block_gossip_params"`
BlockSize `json:"block_size_params"`
TxSize `json:"tx_size_params"`
BlockGossip `json:"block_gossip_params"`
}
// BlockSizeParams contain limits on the block size.
type BlockSizeParams struct {
MaxBytes int `json:"max_bytes"` // NOTE: must not be 0 nor greater than 100MB
MaxTxs int `json:"max_txs"`
MaxGas int `json:"max_gas"`
// BlockSize contain limits on the block size.
type BlockSize struct {
MaxBytes int `json:"max_bytes"` // NOTE: must not be 0 nor greater than 100MB
MaxTxs int `json:"max_txs"`
MaxGas int64 `json:"max_gas"`
}
// TxSizeParams contain limits on the tx size.
type TxSizeParams struct {
MaxBytes int `json:"max_bytes"`
MaxGas int `json:"max_gas"`
// TxSize contain limits on the tx size.
type TxSize struct {
MaxBytes int `json:"max_bytes"`
MaxGas int64 `json:"max_gas"`
}
// BlockGossipParams determine consensus critical elements of how blocks are gossiped
type BlockGossipParams struct {
// BlockGossip determine consensus critical elements of how blocks are gossiped
type BlockGossip struct {
BlockPartSizeBytes int `json:"block_part_size_bytes"` // NOTE: must not be 0
}
// DefaultConsensusParams returns a default ConsensusParams.
func DefaultConsensusParams() *ConsensusParams {
return &ConsensusParams{
DefaultBlockSizeParams(),
DefaultTxSizeParams(),
DefaultBlockGossipParams(),
DefaultBlockSize(),
DefaultTxSize(),
DefaultBlockGossip(),
}
}
// DefaultBlockSizeParams returns a default BlockSizeParams.
func DefaultBlockSizeParams() BlockSizeParams {
return BlockSizeParams{
// DefaultBlockSize returns a default BlockSize.
func DefaultBlockSize() BlockSize {
return BlockSize{
MaxBytes: 22020096, // 21MB
MaxTxs: 100000,
MaxGas: -1,
}
}
// DefaultTxSizeParams returns a default TxSizeParams.
func DefaultTxSizeParams() TxSizeParams {
return TxSizeParams{
// DefaultTxSize returns a default TxSize.
func DefaultTxSize() TxSize {
return TxSize{
MaxBytes: 10240, // 10kB
MaxGas: -1,
}
}
// DefaultBlockGossipParams returns a default BlockGossipParams.
func DefaultBlockGossipParams() BlockGossipParams {
return BlockGossipParams{
// DefaultBlockGossip returns a default BlockGossip.
func DefaultBlockGossip() BlockGossip {
return BlockGossip{
BlockPartSizeBytes: 65536, // 64kB,
}
}
@@ -71,17 +74,69 @@ func DefaultBlockGossipParams() BlockGossipParams {
// are within their allowed limits, and returns an error if they are not.
func (params *ConsensusParams) Validate() error {
// ensure some values are greater than 0
if params.BlockSizeParams.MaxBytes <= 0 {
return errors.Errorf("BlockSizeParams.MaxBytes must be greater than 0. Got %d", params.BlockSizeParams.MaxBytes)
if params.BlockSize.MaxBytes <= 0 {
return errors.Errorf("BlockSize.MaxBytes must be greater than 0. Got %d", params.BlockSize.MaxBytes)
}
if params.BlockGossipParams.BlockPartSizeBytes <= 0 {
return errors.Errorf("BlockGossipParams.BlockPartSizeBytes must be greater than 0. Got %d", params.BlockGossipParams.BlockPartSizeBytes)
if params.BlockGossip.BlockPartSizeBytes <= 0 {
return errors.Errorf("BlockGossip.BlockPartSizeBytes must be greater than 0. Got %d", params.BlockGossip.BlockPartSizeBytes)
}
// ensure blocks aren't too big
if params.BlockSizeParams.MaxBytes > maxBlockSizeBytes {
return errors.Errorf("BlockSizeParams.MaxBytes is too big. %d > %d",
params.BlockSizeParams.MaxBytes, maxBlockSizeBytes)
if params.BlockSize.MaxBytes > maxBlockSizeBytes {
return errors.Errorf("BlockSize.MaxBytes is too big. %d > %d",
params.BlockSize.MaxBytes, maxBlockSizeBytes)
}
return nil
}
// Hash returns a merkle hash of the parameters to store
// in the block header
func (params *ConsensusParams) Hash() []byte {
return merkle.SimpleHashFromMap(map[string]interface{}{
"block_gossip_part_size_bytes": params.BlockGossip.BlockPartSizeBytes,
"block_size_max_bytes": params.BlockSize.MaxBytes,
"block_size_max_gas": params.BlockSize.MaxGas,
"block_size_max_txs": params.BlockSize.MaxTxs,
"tx_size_max_bytes": params.TxSize.MaxBytes,
"tx_size_max_gas": params.TxSize.MaxGas,
})
}
// Update returns a copy of the params with updates from the non-zero fields of p2.
// NOTE: note: must not modify the original
func (params ConsensusParams) Update(params2 *abci.ConsensusParams) ConsensusParams {
res := params // explicit copy
if params2 == nil {
return res
}
// we must defensively consider any structs may be nil
// XXX: it's cast city over here. It's ok because we only do int32->int
// but still, watch it champ.
if params2.BlockSize != nil {
if params2.BlockSize.MaxBytes > 0 {
res.BlockSize.MaxBytes = int(params2.BlockSize.MaxBytes)
}
if params2.BlockSize.MaxTxs > 0 {
res.BlockSize.MaxTxs = int(params2.BlockSize.MaxTxs)
}
if params2.BlockSize.MaxGas > 0 {
res.BlockSize.MaxGas = params2.BlockSize.MaxGas
}
}
if params2.TxSize != nil {
if params2.TxSize.MaxBytes > 0 {
res.TxSize.MaxBytes = int(params2.TxSize.MaxBytes)
}
if params2.TxSize.MaxGas > 0 {
res.TxSize.MaxGas = params2.TxSize.MaxGas
}
}
if params2.BlockGossip != nil {
if params2.BlockGossip.BlockPartSizeBytes > 0 {
res.BlockGossip.BlockPartSizeBytes = int(params2.BlockGossip.BlockPartSizeBytes)
}
}
return res
}
+50 -2
View File
@@ -1,6 +1,8 @@
package types
import (
"bytes"
"sort"
"testing"
"github.com/stretchr/testify/assert"
@@ -8,8 +10,8 @@ import (
func newConsensusParams(blockSize, partSize int) ConsensusParams {
return ConsensusParams{
BlockSizeParams: BlockSizeParams{MaxBytes: blockSize},
BlockGossipParams: BlockGossipParams{BlockPartSizeBytes: partSize},
BlockSize: BlockSize{MaxBytes: blockSize},
BlockGossip: BlockGossip{BlockPartSizeBytes: partSize},
}
}
@@ -38,3 +40,49 @@ func TestConsensusParamsValidation(t *testing.T) {
}
}
}
func makeParams(blockBytes, blockTx, blockGas, txBytes,
txGas, partSize int) ConsensusParams {
return ConsensusParams{
BlockSize: BlockSize{
MaxBytes: blockBytes,
MaxTxs: blockTx,
MaxGas: int64(blockGas),
},
TxSize: TxSize{
MaxBytes: txBytes,
MaxGas: int64(txGas),
},
BlockGossip: BlockGossip{
BlockPartSizeBytes: partSize,
},
}
}
func TestConsensusParamsHash(t *testing.T) {
params := []ConsensusParams{
makeParams(1, 2, 3, 4, 5, 6),
makeParams(7, 2, 3, 4, 5, 6),
makeParams(1, 7, 3, 4, 5, 6),
makeParams(1, 2, 7, 4, 5, 6),
makeParams(1, 2, 3, 7, 5, 6),
makeParams(1, 2, 3, 4, 7, 6),
makeParams(1, 2, 3, 4, 5, 7),
makeParams(6, 5, 4, 3, 2, 1),
}
hashes := make([][]byte, len(params))
for i := range params {
hashes[i] = params[i].Hash()
}
// make sure there are no duplicates...
// sort, then check in order for matches
sort.Slice(hashes, func(i, j int) bool {
return bytes.Compare(hashes[i], hashes[j]) < 0
})
for i := 0; i < len(hashes)-1; i++ {
assert.NotEqual(t, hashes[i], hashes[i+1])
}
}
+20 -2
View File
@@ -12,11 +12,11 @@ type tm2pb struct{}
func (tm2pb) Header(header *Header) *types.Header {
return &types.Header{
ChainId: header.ChainID,
ChainID: header.ChainID,
Height: header.Height,
Time: header.Time.Unix(),
NumTxs: int32(header.NumTxs), // XXX: overflow
LastBlockId: TM2PB.BlockID(header.LastBlockID),
LastBlockID: TM2PB.BlockID(header.LastBlockID),
LastCommitHash: header.LastCommitHash,
DataHash: header.DataHash,
AppHash: header.AppHash,
@@ -51,3 +51,21 @@ func (tm2pb) Validators(vals *ValidatorSet) []*types.Validator {
}
return validators
}
func (tm2pb) ConsensusParams(params *ConsensusParams) *types.ConsensusParams {
return &types.ConsensusParams{
BlockSize: &types.BlockSize{
MaxBytes: int32(params.BlockSize.MaxBytes),
MaxTxs: int32(params.BlockSize.MaxTxs),
MaxGas: params.BlockSize.MaxGas,
},
TxSize: &types.TxSize{
MaxBytes: int32(params.TxSize.MaxBytes),
MaxGas: params.TxSize.MaxGas,
},
BlockGossip: &types.BlockGossip{
BlockPartSizeBytes: int32(params.BlockGossip.BlockPartSizeBytes),
},
}
}
+37
View File
@@ -0,0 +1,37 @@
package types
import "time"
func MakeCommit(blockID BlockID, height int64, round int,
voteSet *VoteSet,
validators []*PrivValidatorFS) (*Commit, error) {
// all sign
for i := 0; i < len(validators); i++ {
vote := &Vote{
ValidatorAddress: validators[i].GetAddress(),
ValidatorIndex: i,
Height: height,
Round: round,
Type: VoteTypePrecommit,
BlockID: blockID,
Timestamp: time.Now().UTC(),
}
_, err := signAddVote(validators[i], vote, voteSet)
if err != nil {
return nil, err
}
}
return voteSet.MakeCommit(), nil
}
func signAddVote(privVal *PrivValidatorFS, vote *Vote, voteSet *VoteSet) (signed bool, err error) {
vote.Signature, err = privVal.Signer.Sign(SignBytes(voteSet.ChainID(), vote))
if err != nil {
return false, err
}
return voteSet.AddVote(vote)
}
-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)