Merge pull request #994 from tendermint/clean/block-validation
Clean/block validation
This commit is contained in:
commit
2b634dab32
|
@ -183,7 +183,7 @@ func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte)
|
|||
// maxMsgSize returns the maximum allowable size of a
|
||||
// message on the blockchain reactor.
|
||||
func (bcR *BlockchainReactor) maxMsgSize() int {
|
||||
return bcR.state.Params.BlockSize.MaxBytes + 2
|
||||
return bcR.state.ConsensusParams.BlockSize.MaxBytes + 2
|
||||
}
|
||||
|
||||
// Handle messages from the poolReactor telling the reactor what to do.
|
||||
|
@ -251,7 +251,7 @@ FOR_LOOP:
|
|||
// We need both to sync the first block.
|
||||
break SYNC_LOOP
|
||||
}
|
||||
firstParts := first.MakePartSet(bcR.state.Params.BlockPartSizeBytes)
|
||||
firstParts := first.MakePartSet(bcR.state.ConsensusParams.BlockPartSizeBytes)
|
||||
firstPartsHeader := firstParts.Header()
|
||||
// Finally, verify the first block using the second's commit
|
||||
// NOTE: we can probably make this more efficient, but note that calling
|
||||
|
|
|
@ -41,7 +41,7 @@ func newBlockchainReactor(logger log.Logger, maxBlockHeight int64) *BlockchainRe
|
|||
for blockHeight := int64(1); blockHeight <= maxBlockHeight; blockHeight++ {
|
||||
firstBlock := makeBlock(blockHeight, state)
|
||||
secondBlock := makeBlock(blockHeight+1, state)
|
||||
firstParts := firstBlock.MakePartSet(state.Params.BlockGossip.BlockPartSizeBytes)
|
||||
firstParts := firstBlock.MakePartSet(state.ConsensusParams.BlockGossip.BlockPartSizeBytes)
|
||||
blockStore.SaveBlock(firstBlock, firstParts, secondBlock.LastCommit)
|
||||
}
|
||||
|
||||
|
@ -105,15 +105,7 @@ func makeTxs(height int64) (txs []types.Tx) {
|
|||
}
|
||||
|
||||
func makeBlock(height int64, state *sm.State) *types.Block {
|
||||
prevHash := state.LastBlockID.Hash
|
||||
prevParts := types.PartSetHeader{}
|
||||
valHash := state.Validators.Hash()
|
||||
prevBlockID := types.BlockID{prevHash, prevParts}
|
||||
block, _ := types.MakeBlock(height, "test_chain", makeTxs(height),
|
||||
state.LastBlockTotalTx, new(types.Commit),
|
||||
prevBlockID, valHash, state.AppHash,
|
||||
state.LastConsensusHash,
|
||||
state.Params.BlockGossip.BlockPartSizeBytes)
|
||||
block, _ := state.MakeBlock(height, makeTxs(height), new(types.Commit))
|
||||
return block
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ dependencies:
|
|||
|
||||
test:
|
||||
override:
|
||||
- cd "$PROJECT_PATH" && make tools && make get_vendor_deps && make metalinter_test
|
||||
- cd "$PROJECT_PATH" && set -o pipefail && make test_integrations 2>&1 | tee test_integrations.log:
|
||||
timeout: 1800
|
||||
post:
|
||||
|
|
|
@ -107,9 +107,9 @@ func TestWALCrash(t *testing.T) {
|
|||
{"block with a smaller part size",
|
||||
func(cs *ConsensusState, ctx context.Context) {
|
||||
// XXX: is there a better way to change BlockPartSizeBytes?
|
||||
params := cs.state.Params
|
||||
params := cs.state.ConsensusParams
|
||||
params.BlockPartSizeBytes = 512
|
||||
cs.state.Params = params
|
||||
cs.state.ConsensusParams = params
|
||||
sendTxs(cs, ctx)
|
||||
},
|
||||
1},
|
||||
|
@ -392,7 +392,7 @@ func testHandshakeReplay(t *testing.T, nBlocks int, mode uint) {
|
|||
}
|
||||
|
||||
func applyBlock(st *sm.State, blk *types.Block, proxyApp proxy.AppConns) {
|
||||
testPartSize := st.Params.BlockPartSizeBytes
|
||||
testPartSize := st.ConsensusParams.BlockPartSizeBytes
|
||||
err := st.ApplyBlock(types.NopEventBus{}, proxyApp.Consensus(), blk, blk.MakePartSet(testPartSize).Header(), mempool)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -590,7 +590,7 @@ func stateAndStore(config *cfg.Config, pubKey crypto.PubKey) (*sm.State, *mockBl
|
|||
state, _ := sm.MakeGenesisStateFromFile(stateDB, config.GenesisFile())
|
||||
state.SetLogger(log.TestingLogger().With("module", "state"))
|
||||
|
||||
store := NewMockBlockStore(config, state.Params)
|
||||
store := NewMockBlockStore(config, state.ConsensusParams)
|
||||
return state, store
|
||||
}
|
||||
|
||||
|
|
|
@ -863,11 +863,7 @@ 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,
|
||||
cs.state.LastBlockTotalTx, commit,
|
||||
cs.state.LastBlockID, cs.state.Validators.Hash(),
|
||||
cs.state.AppHash, cs.state.LastConsensusHash,
|
||||
cs.state.Params.BlockPartSizeBytes)
|
||||
return cs.state.MakeBlock(cs.Height, txs, commit)
|
||||
}
|
||||
|
||||
// Enter: `timeoutPropose` after entering Propose.
|
||||
|
@ -1307,7 +1303,7 @@ func (cs *ConsensusState) addProposalBlockPart(height int64, part *types.Part, v
|
|||
var n int
|
||||
var err error
|
||||
cs.ProposalBlock = wire.ReadBinary(&types.Block{}, cs.ProposalBlockParts.GetReader(),
|
||||
cs.state.Params.BlockSize.MaxBytes, &n, &err).(*types.Block)
|
||||
cs.state.ConsensusParams.BlockSize.MaxBytes, &n, &err).(*types.Block)
|
||||
// NOTE: it's possible to receive complete proposal blocks for future rounds without having the proposal
|
||||
cs.Logger.Info("Received complete proposal block", "height", cs.ProposalBlock.Height, "hash", cs.ProposalBlock.Hash())
|
||||
if cs.Step == cstypes.RoundStepPropose && cs.isProposalComplete() {
|
||||
|
|
|
@ -184,7 +184,7 @@ func TestBadProposal(t *testing.T) {
|
|||
height, round := cs1.Height, cs1.Round
|
||||
vs2 := vss[1]
|
||||
|
||||
partSize := cs1.state.Params.BlockPartSizeBytes
|
||||
partSize := cs1.state.ConsensusParams.BlockPartSizeBytes
|
||||
|
||||
proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal)
|
||||
voteCh := subscribe(cs1.eventBus, types.EventQueryVote)
|
||||
|
@ -339,7 +339,7 @@ func TestLockNoPOL(t *testing.T) {
|
|||
vs2 := vss[1]
|
||||
height := cs1.Height
|
||||
|
||||
partSize := cs1.state.Params.BlockPartSizeBytes
|
||||
partSize := cs1.state.ConsensusParams.BlockPartSizeBytes
|
||||
|
||||
timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||
|
@ -507,7 +507,7 @@ func TestLockPOLRelock(t *testing.T) {
|
|||
cs1, vss := randConsensusState(4)
|
||||
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
|
||||
|
||||
partSize := cs1.state.Params.BlockPartSizeBytes
|
||||
partSize := cs1.state.ConsensusParams.BlockPartSizeBytes
|
||||
|
||||
timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||
|
@ -622,7 +622,7 @@ func TestLockPOLUnlock(t *testing.T) {
|
|||
cs1, vss := randConsensusState(4)
|
||||
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
|
||||
|
||||
partSize := cs1.state.Params.BlockPartSizeBytes
|
||||
partSize := cs1.state.ConsensusParams.BlockPartSizeBytes
|
||||
|
||||
proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal)
|
||||
timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
||||
|
@ -719,7 +719,7 @@ func TestLockPOLSafety1(t *testing.T) {
|
|||
cs1, vss := randConsensusState(4)
|
||||
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
|
||||
|
||||
partSize := cs1.state.Params.BlockPartSizeBytes
|
||||
partSize := cs1.state.ConsensusParams.BlockPartSizeBytes
|
||||
|
||||
proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal)
|
||||
timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
||||
|
@ -842,7 +842,7 @@ func TestLockPOLSafety2(t *testing.T) {
|
|||
cs1, vss := randConsensusState(4)
|
||||
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
|
||||
|
||||
partSize := cs1.state.Params.BlockPartSizeBytes
|
||||
partSize := cs1.state.ConsensusParams.BlockPartSizeBytes
|
||||
|
||||
proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal)
|
||||
timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose)
|
||||
|
@ -1021,7 +1021,7 @@ func TestHalt1(t *testing.T) {
|
|||
cs1, vss := randConsensusState(4)
|
||||
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
|
||||
|
||||
partSize := cs1.state.Params.BlockPartSizeBytes
|
||||
partSize := cs1.state.ConsensusParams.BlockPartSizeBytes
|
||||
|
||||
proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal)
|
||||
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
|
||||
|
|
|
@ -64,17 +64,19 @@ imports:
|
|||
- name: github.com/kr/logfmt
|
||||
version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0
|
||||
- name: github.com/magiconair/properties
|
||||
version: 49d762b9817ba1c2e9d0c69183c2b4a8b8f1d934
|
||||
version: 8d7837e64d3c1ee4e54a880c5a920ab4316fc90a
|
||||
- name: github.com/mitchellh/mapstructure
|
||||
version: 06020f85339e21b2478f756a78e295255ffa4d6a
|
||||
- name: github.com/pelletier/go-buffruneio
|
||||
version: c37440a7cf42ac63b919c752ca73a85067e05992
|
||||
- name: github.com/pelletier/go-toml
|
||||
version: b8b5e7696574464b2f9bf303a7b37781bb52889f
|
||||
- name: github.com/pkg/errors
|
||||
version: 645ef00459ed84a119197bfb8d8205042c6df63d
|
||||
- name: github.com/rcrowley/go-metrics
|
||||
version: e181e095bae94582363434144c61a9653aff6e50
|
||||
version: 1f30fe9094a513ce4c700b9a54458bbb0c96996c
|
||||
- name: github.com/spf13/afero
|
||||
version: 8d919cbe7e2627e417f3e45c3c0e489a5b7e2536
|
||||
version: 5660eeed305fe5f69c8fc6cf899132a459a97064
|
||||
subpackages:
|
||||
- mem
|
||||
- name: github.com/spf13/cast
|
||||
|
@ -129,7 +131,7 @@ imports:
|
|||
subpackages:
|
||||
- iavl
|
||||
- name: github.com/tendermint/tmlibs
|
||||
version: e4ef2835f0081c2ece83b9c1f777cf071f956e81
|
||||
version: e2d7f1aa41dde5f29057dd08e64371a574b84c86
|
||||
subpackages:
|
||||
- autofile
|
||||
- cli
|
||||
|
@ -201,7 +203,7 @@ imports:
|
|||
- name: gopkg.in/go-playground/validator.v9
|
||||
version: b1f51f36f1c98cc97f777d6fc9d4b05eaa0cabb5
|
||||
- name: gopkg.in/yaml.v2
|
||||
version: 287cf08546ab5e7e37d55a84f7ed3fd1db036de5
|
||||
version: eb3733d160e74a9c7e442f435eb3bea458e1d19f
|
||||
testImports:
|
||||
- name: github.com/davecgh/go-spew
|
||||
version: 04cdfd42973bb9c8589fd6a731800cf222fde1a9
|
||||
|
|
|
@ -34,7 +34,7 @@ import:
|
|||
subpackages:
|
||||
- iavl
|
||||
- package: github.com/tendermint/tmlibs
|
||||
version: e4ef2835f0081c2ece83b9c1f777cf071f956e81
|
||||
version: develop
|
||||
subpackages:
|
||||
- autofile
|
||||
- cli
|
||||
|
|
|
@ -3,7 +3,6 @@ package rpctest
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
@ -11,6 +10,8 @@ import (
|
|||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
nm "github.com/tendermint/tendermint/node"
|
||||
"github.com/tendermint/tendermint/proxy"
|
||||
|
@ -57,9 +58,7 @@ func makePathname() string {
|
|||
}
|
||||
|
||||
func randPort() int {
|
||||
// returns between base and base + spread
|
||||
base, spread := 20000, 20000
|
||||
return base + rand.Intn(spread)
|
||||
return int(cmn.RandUint16()/2 + 10000)
|
||||
}
|
||||
|
||||
func makeAddrs() (string, string, string) {
|
||||
|
|
|
@ -37,6 +37,10 @@ type (
|
|||
ErrNoValSetForHeight struct {
|
||||
Height int64
|
||||
}
|
||||
|
||||
ErrNoConsensusParamsForHeight struct {
|
||||
Height int64
|
||||
}
|
||||
)
|
||||
|
||||
func (e ErrUnknownBlock) Error() string {
|
||||
|
@ -61,3 +65,7 @@ func (e ErrStateMismatch) Error() string {
|
|||
func (e ErrNoValSetForHeight) Error() string {
|
||||
return cmn.Fmt("Could not find validator set for height #%d", e.Height)
|
||||
}
|
||||
|
||||
func (e ErrNoConsensusParamsForHeight) Error() string {
|
||||
return cmn.Fmt("Could not find consensus params for height #%d", e.Height)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
|
@ -184,26 +185,69 @@ func (s *State) ValidateBlock(block *types.Block) error {
|
|||
return s.validateBlock(block)
|
||||
}
|
||||
|
||||
func (s *State) validateBlock(block *types.Block) error {
|
||||
// MakeBlock builds a block with the given txs and commit from the current state.
|
||||
func (s *State) MakeBlock(height int64, txs []types.Tx, commit *types.Commit) (*types.Block, *types.PartSet) {
|
||||
// build base block
|
||||
block := types.MakeBlock(height, txs, commit)
|
||||
|
||||
// fill header with state data
|
||||
block.ChainID = s.ChainID
|
||||
block.TotalTxs = s.LastBlockTotalTx + block.NumTxs
|
||||
block.LastBlockID = s.LastBlockID
|
||||
block.ValidatorsHash = s.Validators.Hash()
|
||||
block.AppHash = s.AppHash
|
||||
block.ConsensusHash = s.LastConsensusParams.Hash()
|
||||
|
||||
return block, block.MakePartSet(s.ConsensusParams.BlockGossip.BlockPartSizeBytes)
|
||||
}
|
||||
|
||||
func (s *State) validateBlock(b *types.Block) error {
|
||||
// Basic block validation.
|
||||
err := block.ValidateBasic(s.ChainID, s.LastBlockHeight,
|
||||
s.LastBlockTotalTx, s.LastBlockID, s.LastBlockTime, s.AppHash, s.LastConsensusHash)
|
||||
if err != nil {
|
||||
if err := b.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if b.ChainID != s.ChainID {
|
||||
return fmt.Errorf("Wrong Block.Header.ChainID. Expected %v, got %v", s.ChainID, b.ChainID)
|
||||
}
|
||||
if b.Height != s.LastBlockHeight+1 {
|
||||
return fmt.Errorf("Wrong Block.Header.Height. Expected %v, got %v", s.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")
|
||||
}
|
||||
*/
|
||||
|
||||
newTxs := int64(len(b.Data.Txs))
|
||||
if b.TotalTxs != s.LastBlockTotalTx+newTxs {
|
||||
return fmt.Errorf("Wrong Block.Header.TotalTxs. Expected %v, got %v", s.LastBlockTotalTx+newTxs, b.TotalTxs)
|
||||
}
|
||||
if !b.LastBlockID.Equals(s.LastBlockID) {
|
||||
return fmt.Errorf("Wrong Block.Header.LastBlockID. Expected %v, got %v", s.LastBlockID, b.LastBlockID)
|
||||
}
|
||||
|
||||
if !bytes.Equal(b.AppHash, s.AppHash) {
|
||||
return fmt.Errorf("Wrong Block.Header.AppHash. Expected %X, got %v", s.AppHash, b.AppHash)
|
||||
}
|
||||
if !bytes.Equal(b.ConsensusHash, s.LastConsensusParams.Hash()) {
|
||||
return fmt.Errorf("Wrong Block.Header.ConsensusHash. Expected %X, got %v", s.LastConsensusParams.Hash(), b.ConsensusHash)
|
||||
}
|
||||
|
||||
// Validate block LastCommit.
|
||||
if block.Height == 1 {
|
||||
if len(block.LastCommit.Precommits) != 0 {
|
||||
if b.Height == 1 {
|
||||
if len(b.LastCommit.Precommits) != 0 {
|
||||
return errors.New("Block at height 1 (first block) should have no LastCommit precommits")
|
||||
}
|
||||
} else {
|
||||
if len(block.LastCommit.Precommits) != s.LastValidators.Size() {
|
||||
if len(b.LastCommit.Precommits) != s.LastValidators.Size() {
|
||||
return errors.New(cmn.Fmt("Invalid block commit size. Expected %v, got %v",
|
||||
s.LastValidators.Size(), len(block.LastCommit.Precommits)))
|
||||
s.LastValidators.Size(), len(b.LastCommit.Precommits)))
|
||||
}
|
||||
err := s.LastValidators.VerifyCommit(
|
||||
s.ChainID, s.LastBlockID, block.Height-1, block.LastCommit)
|
||||
s.ChainID, s.LastBlockID, b.Height-1, b.LastCommit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -235,7 +279,10 @@ func (s *State) ApplyBlock(txEventPublisher types.TxEventPublisher, proxyAppConn
|
|||
fail.Fail() // XXX
|
||||
|
||||
// now update the block and validators
|
||||
s.SetBlockAndValidators(block.Header, partsHeader, abciResponses)
|
||||
err = s.SetBlockAndValidators(block.Header, partsHeader, abciResponses)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Commit failed for application: %v", err)
|
||||
}
|
||||
|
||||
// lock mempool, commit state, update mempoool
|
||||
err = s.CommitStateUpdateMempool(proxyAppConn, block, mempool)
|
||||
|
|
|
@ -23,6 +23,52 @@ var (
|
|||
nTxsPerBlock = 10
|
||||
)
|
||||
|
||||
func TestValidateBlock(t *testing.T) {
|
||||
state := state()
|
||||
state.SetLogger(log.TestingLogger())
|
||||
|
||||
// proper block must pass
|
||||
block := makeBlock(state, 1)
|
||||
err := state.ValidateBlock(block)
|
||||
require.NoError(t, err)
|
||||
|
||||
// wrong chain fails
|
||||
block = makeBlock(state, 1)
|
||||
block.ChainID = "not-the-real-one"
|
||||
err = state.ValidateBlock(block)
|
||||
require.Error(t, err)
|
||||
|
||||
// wrong height fails
|
||||
block = makeBlock(state, 1)
|
||||
block.Height += 10
|
||||
err = state.ValidateBlock(block)
|
||||
require.Error(t, err)
|
||||
|
||||
// wrong total tx fails
|
||||
block = makeBlock(state, 1)
|
||||
block.TotalTxs += 10
|
||||
err = state.ValidateBlock(block)
|
||||
require.Error(t, err)
|
||||
|
||||
// wrong blockid fails
|
||||
block = makeBlock(state, 1)
|
||||
block.LastBlockID.PartsHeader.Total += 10
|
||||
err = state.ValidateBlock(block)
|
||||
require.Error(t, err)
|
||||
|
||||
// wrong app hash fails
|
||||
block = makeBlock(state, 1)
|
||||
block.AppHash = []byte("wrong app hash")
|
||||
err = state.ValidateBlock(block)
|
||||
require.Error(t, err)
|
||||
|
||||
// wrong consensus hash fails
|
||||
block = makeBlock(state, 1)
|
||||
block.ConsensusHash = []byte("wrong consensus hash")
|
||||
err = state.ValidateBlock(block)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestApplyBlock(t *testing.T) {
|
||||
cc := proxy.NewLocalClientCreator(dummy.NewDummyApplication())
|
||||
proxyApp := proxy.NewAppConns(cc, nil)
|
||||
|
@ -33,7 +79,7 @@ func TestApplyBlock(t *testing.T) {
|
|||
state := state()
|
||||
state.SetLogger(log.TestingLogger())
|
||||
|
||||
block := makeBlock(1, state)
|
||||
block := makeBlock(state, 1)
|
||||
|
||||
err = state.ApplyBlock(types.NopEventBus{}, proxyApp.Consensus(), block, block.MakePartSet(testPartSize).Header(), types.MockMempool{})
|
||||
require.Nil(t, err)
|
||||
|
@ -79,10 +125,7 @@ func TestBeginBlockAbsentValidators(t *testing.T) {
|
|||
for _, tc := range testCases {
|
||||
lastCommit := &types.Commit{BlockID: prevBlockID, Precommits: tc.lastCommitPrecommits}
|
||||
|
||||
valHash := state.Validators.Hash()
|
||||
block, _ := types.MakeBlock(2, chainID, makeTxs(2), state.LastBlockTotalTx, lastCommit,
|
||||
prevBlockID, valHash, state.AppHash, state.LastConsensusHash, testPartSize)
|
||||
|
||||
block, _ := state.MakeBlock(2, makeTxs(2), lastCommit)
|
||||
_, err = ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), lastValidators)
|
||||
require.Nil(t, err, tc.desc)
|
||||
|
||||
|
@ -112,15 +155,8 @@ func state() *State {
|
|||
return s
|
||||
}
|
||||
|
||||
func makeBlock(height int64, state *State) *types.Block {
|
||||
prevHash := state.LastBlockID.Hash
|
||||
prevParts := types.PartSetHeader{}
|
||||
valHash := state.Validators.Hash()
|
||||
prevBlockID := types.BlockID{prevHash, prevParts}
|
||||
block, _ := types.MakeBlock(height, chainID,
|
||||
makeTxs(height), state.LastBlockTotalTx,
|
||||
new(types.Commit), prevBlockID, valHash,
|
||||
state.AppHash, state.LastConsensusHash, testPartSize)
|
||||
func makeBlock(state *State, height int64) *types.Block {
|
||||
block, _ := state.MakeBlock(height, makeTxs(state.LastBlockHeight), new(types.Commit))
|
||||
return block
|
||||
}
|
||||
|
||||
|
|
240
state/state.go
240
state/state.go
|
@ -18,6 +18,7 @@ import (
|
|||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// database keys
|
||||
var (
|
||||
stateKey = []byte("stateKey")
|
||||
abciResponsesKey = []byte("abciResponsesKey")
|
||||
|
@ -27,40 +28,51 @@ func calcValidatorsKey(height int64) []byte {
|
|||
return []byte(cmn.Fmt("validatorsKey:%v", height))
|
||||
}
|
||||
|
||||
func calcConsensusParamsKey(height int64) []byte {
|
||||
return []byte(cmn.Fmt("consensusParamsKey:%v", height))
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// State represents the latest committed state of the Tendermint consensus,
|
||||
// including the last committed block and validator set.
|
||||
// Newly committed blocks are validated and executed against the State.
|
||||
// State is a short description of the latest committed block of the Tendermint consensus.
|
||||
// It keeps all information necessary to validate new blocks,
|
||||
// including the last validator set and the consensus params.
|
||||
// All fields are exposed so the struct can be easily serialized,
|
||||
// but the fields should only be changed by calling state.SetBlockAndValidators.
|
||||
// NOTE: not goroutine-safe.
|
||||
type State struct {
|
||||
// mtx for writing to db
|
||||
mtx sync.Mutex
|
||||
db dbm.DB
|
||||
|
||||
// Immutable
|
||||
ChainID string
|
||||
// Consensus parameters used for validating blocks
|
||||
Params types.ConsensusParams
|
||||
|
||||
// These fields are updated by SetBlockAndValidators.
|
||||
// Exposed 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
|
||||
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,
|
||||
|
||||
// LastValidators is used to validate block.LastCommit.
|
||||
// Validators are persisted to the database separately every time they change,
|
||||
// so we can query for historical validator sets.
|
||||
// Note that if s.LastBlockHeight causes a valset change,
|
||||
// we set s.LastHeightValidatorsChanged = s.LastBlockHeight + 1
|
||||
Validators *types.ValidatorSet
|
||||
LastValidators *types.ValidatorSet
|
||||
LastHeightValidatorsChanged int64
|
||||
|
||||
// AppHash is updated after Commit
|
||||
// Consensus parameters used for validating blocks.
|
||||
// Changes returned by EndBlock and updated after Commit.
|
||||
ConsensusParams types.ConsensusParams
|
||||
LastConsensusParams types.ConsensusParams
|
||||
LastHeightConsensusParamsChanged int64
|
||||
|
||||
// The latest AppHash we've received from calling abci.Commit()
|
||||
AppHash []byte
|
||||
// LastConsensusHash is updated after Commit
|
||||
LastConsensusHash []byte
|
||||
|
||||
logger log.Logger
|
||||
}
|
||||
|
@ -114,19 +126,26 @@ func (s *State) SetLogger(l log.Logger) {
|
|||
// Copy makes a copy of the State for mutating.
|
||||
func (s *State) Copy() *State {
|
||||
return &State{
|
||||
db: s.db,
|
||||
LastBlockHeight: s.LastBlockHeight,
|
||||
LastBlockTotalTx: s.LastBlockTotalTx,
|
||||
LastBlockID: s.LastBlockID,
|
||||
LastBlockTime: s.LastBlockTime,
|
||||
db: s.db,
|
||||
|
||||
ChainID: s.ChainID,
|
||||
|
||||
LastBlockHeight: s.LastBlockHeight,
|
||||
LastBlockTotalTx: s.LastBlockTotalTx,
|
||||
LastBlockID: s.LastBlockID,
|
||||
LastBlockTime: s.LastBlockTime,
|
||||
|
||||
Validators: s.Validators.Copy(),
|
||||
LastValidators: s.LastValidators.Copy(),
|
||||
AppHash: s.AppHash,
|
||||
LastConsensusHash: s.LastConsensusHash,
|
||||
LastHeightValidatorsChanged: s.LastHeightValidatorsChanged,
|
||||
logger: s.logger,
|
||||
ChainID: s.ChainID,
|
||||
Params: s.Params,
|
||||
|
||||
ConsensusParams: s.ConsensusParams,
|
||||
LastConsensusParams: s.LastConsensusParams,
|
||||
LastHeightConsensusParamsChanged: s.LastHeightConsensusParamsChanged,
|
||||
|
||||
AppHash: s.AppHash,
|
||||
|
||||
logger: s.logger,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -136,6 +155,7 @@ func (s *State) Save() {
|
|||
defer s.mtx.Unlock()
|
||||
|
||||
s.saveValidatorsInfo()
|
||||
s.saveConsensusParamsInfo()
|
||||
s.db.SetSync(stateKey, s.Bytes())
|
||||
}
|
||||
|
||||
|
@ -169,13 +189,13 @@ func (s *State) LoadABCIResponses() *ABCIResponses {
|
|||
|
||||
// LoadValidators loads the ValidatorSet for a given height.
|
||||
func (s *State) LoadValidators(height int64) (*types.ValidatorSet, error) {
|
||||
valInfo := s.loadValidators(height)
|
||||
valInfo := s.loadValidatorsInfo(height)
|
||||
if valInfo == nil {
|
||||
return nil, ErrNoValSetForHeight{height}
|
||||
}
|
||||
|
||||
if valInfo.ValidatorSet == nil {
|
||||
valInfo = s.loadValidators(valInfo.LastHeightChanged)
|
||||
valInfo = s.loadValidatorsInfo(valInfo.LastHeightChanged)
|
||||
if valInfo == nil {
|
||||
cmn.PanicSanity(fmt.Sprintf(`Couldn't find validators at height %d as
|
||||
last changed from height %d`, valInfo.LastHeightChanged, height))
|
||||
|
@ -185,7 +205,7 @@ func (s *State) LoadValidators(height int64) (*types.ValidatorSet, error) {
|
|||
return valInfo.ValidatorSet, nil
|
||||
}
|
||||
|
||||
func (s *State) loadValidators(height int64) *ValidatorsInfo {
|
||||
func (s *State) loadValidatorsInfo(height int64) *ValidatorsInfo {
|
||||
buf := s.db.Get(calcValidatorsKey(height))
|
||||
if len(buf) == 0 {
|
||||
return nil
|
||||
|
@ -220,6 +240,61 @@ func (s *State) saveValidatorsInfo() {
|
|||
s.db.SetSync(calcValidatorsKey(nextHeight), valInfo.Bytes())
|
||||
}
|
||||
|
||||
// LoadConsensusParams loads the ConsensusParams for a given height.
|
||||
func (s *State) LoadConsensusParams(height int64) (types.ConsensusParams, error) {
|
||||
empty := types.ConsensusParams{}
|
||||
|
||||
paramsInfo := s.loadConsensusParamsInfo(height)
|
||||
if paramsInfo == nil {
|
||||
return empty, ErrNoConsensusParamsForHeight{height}
|
||||
}
|
||||
|
||||
if paramsInfo.ConsensusParams == empty {
|
||||
paramsInfo = s.loadConsensusParamsInfo(paramsInfo.LastHeightChanged)
|
||||
if paramsInfo == nil {
|
||||
cmn.PanicSanity(fmt.Sprintf(`Couldn't find consensus params at height %d as
|
||||
last changed from height %d`, paramsInfo.LastHeightChanged, height))
|
||||
}
|
||||
}
|
||||
|
||||
return paramsInfo.ConsensusParams, nil
|
||||
}
|
||||
|
||||
func (s *State) loadConsensusParamsInfo(height int64) *ConsensusParamsInfo {
|
||||
buf := s.db.Get(calcConsensusParamsKey(height))
|
||||
if len(buf) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
paramsInfo := new(ConsensusParamsInfo)
|
||||
r, n, err := bytes.NewReader(buf), new(int), new(error)
|
||||
wire.ReadBinaryPtr(paramsInfo, r, 0, n, err)
|
||||
if *err != nil {
|
||||
// DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
|
||||
cmn.Exit(cmn.Fmt(`LoadConsensusParams: Data has been corrupted or its spec has changed:
|
||||
%v\n`, *err))
|
||||
}
|
||||
// TODO: ensure that buf is completely read.
|
||||
|
||||
return paramsInfo
|
||||
}
|
||||
|
||||
// saveConsensusParamsInfo persists the consensus params for the next block to disk.
|
||||
// It should be called from s.Save(), right before the state itself is persisted.
|
||||
// If the consensus params did not change after processing the latest block,
|
||||
// only the last height for which they changed is persisted.
|
||||
func (s *State) saveConsensusParamsInfo() {
|
||||
changeHeight := s.LastHeightConsensusParamsChanged
|
||||
nextHeight := s.LastBlockHeight + 1
|
||||
paramsInfo := &ConsensusParamsInfo{
|
||||
LastHeightChanged: changeHeight,
|
||||
}
|
||||
if changeHeight == nextHeight {
|
||||
paramsInfo.ConsensusParams = s.ConsensusParams
|
||||
}
|
||||
s.db.SetSync(calcConsensusParamsKey(nextHeight), paramsInfo.Bytes())
|
||||
}
|
||||
|
||||
// Equals returns true if the States are identical.
|
||||
func (s *State) Equals(s2 *State) bool {
|
||||
return bytes.Equal(s.Bytes(), s2.Bytes())
|
||||
|
@ -233,7 +308,7 @@ func (s *State) Bytes() []byte {
|
|||
// SetBlockAndValidators mutates State variables
|
||||
// to update block and validators after running EndBlock.
|
||||
func (s *State) SetBlockAndValidators(header *types.Header, blockPartsHeader types.PartSetHeader,
|
||||
abciResponses *ABCIResponses) {
|
||||
abciResponses *ABCIResponses) error {
|
||||
|
||||
// copy the valset so we can apply changes from EndBlock
|
||||
// and update s.LastValidators and s.Validators
|
||||
|
@ -244,8 +319,7 @@ func (s *State) SetBlockAndValidators(header *types.Header, blockPartsHeader typ
|
|||
if len(abciResponses.EndBlock.ValidatorUpdates) > 0 {
|
||||
err := updateValidators(nextValSet, abciResponses.EndBlock.ValidatorUpdates)
|
||||
if err != nil {
|
||||
s.logger.Error("Error changing validator set", "err", err)
|
||||
// TODO: err or carry on?
|
||||
return fmt.Errorf("Error changing validator set: %v", err)
|
||||
}
|
||||
// change results from this height but only applies to the next height
|
||||
s.LastHeightValidatorsChanged = header.Height + 1
|
||||
|
@ -254,76 +328,43 @@ func (s *State) SetBlockAndValidators(header *types.Header, blockPartsHeader typ
|
|||
// Update validator accums and set state variables
|
||||
nextValSet.IncrementAccum(1)
|
||||
|
||||
nextParams := applyUpdates(s.Params,
|
||||
abciResponses.EndBlock.ConsensusParamUpdates)
|
||||
err := nextParams.Validate()
|
||||
if err != nil {
|
||||
s.logger.Error("Error updating consensus params", "err", err)
|
||||
// TODO: err or carry on?
|
||||
nextParams = s.Params
|
||||
// update the params with the latest abciResponses
|
||||
nextParams := s.ConsensusParams
|
||||
if abciResponses.EndBlock.ConsensusParamUpdates != nil {
|
||||
// NOTE: must not mutate s.ConsensusParams
|
||||
nextParams = s.ConsensusParams.Update(abciResponses.EndBlock.ConsensusParamUpdates)
|
||||
err := nextParams.Validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error updating consensus params: %v", err)
|
||||
}
|
||||
// change results from this height but only applies to the next height
|
||||
s.LastHeightConsensusParamsChanged = header.Height + 1
|
||||
}
|
||||
|
||||
s.setBlockAndValidators(header.Height,
|
||||
header.NumTxs,
|
||||
types.BlockID{header.Hash(), blockPartsHeader},
|
||||
header.Time,
|
||||
prevValSet, nextValSet,
|
||||
nextValSet,
|
||||
nextParams)
|
||||
|
||||
}
|
||||
|
||||
// applyUpdates returns new ConsensusParams
|
||||
// whose fields are set to any non-zero fields of c.
|
||||
// If c is nil, it returns p unmodified, as it was passed in.
|
||||
func applyUpdates(p types.ConsensusParams,
|
||||
c *abci.ConsensusParams) types.ConsensusParams {
|
||||
|
||||
if c == nil {
|
||||
return p
|
||||
}
|
||||
res := p
|
||||
// we must defensively consider any structs may be nil
|
||||
if c.BlockSize != nil {
|
||||
|
||||
if c.BlockSize.MaxBytes != 0 {
|
||||
res.BlockSize.MaxBytes = int(c.BlockSize.MaxBytes)
|
||||
}
|
||||
if c.BlockSize.MaxTxs != 0 {
|
||||
res.BlockSize.MaxTxs = int(c.BlockSize.MaxTxs)
|
||||
}
|
||||
if c.BlockSize.MaxGas != 0 {
|
||||
res.BlockSize.MaxGas = int(c.BlockSize.MaxGas)
|
||||
}
|
||||
}
|
||||
if c.TxSize != nil {
|
||||
if c.TxSize.MaxBytes != 0 {
|
||||
res.TxSize.MaxBytes = int(c.TxSize.MaxBytes)
|
||||
}
|
||||
if c.TxSize.MaxGas != 0 {
|
||||
res.TxSize.MaxGas = int(c.TxSize.MaxGas)
|
||||
}
|
||||
}
|
||||
if c.BlockGossip != nil {
|
||||
if c.BlockGossip.BlockPartSizeBytes != 0 {
|
||||
res.BlockGossip.BlockPartSizeBytes = int(c.BlockGossip.BlockPartSizeBytes)
|
||||
}
|
||||
}
|
||||
return res
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *State) setBlockAndValidators(height int64,
|
||||
newTxs int64, blockID types.BlockID, blockTime time.Time,
|
||||
prevValSet, nextValSet *types.ValidatorSet,
|
||||
nextParams types.ConsensusParams) {
|
||||
valSet *types.ValidatorSet,
|
||||
params types.ConsensusParams) {
|
||||
|
||||
s.LastBlockHeight = height
|
||||
s.LastBlockTotalTx += newTxs
|
||||
s.LastBlockID = blockID
|
||||
s.LastBlockTime = blockTime
|
||||
s.Validators = nextValSet
|
||||
s.LastValidators = prevValSet
|
||||
s.LastConsensusHash = s.Params.Hash()
|
||||
s.Params = nextParams
|
||||
|
||||
s.LastValidators = s.Validators.Copy()
|
||||
s.Validators = valSet
|
||||
|
||||
s.LastConsensusParams = s.ConsensusParams
|
||||
s.ConsensusParams = params
|
||||
}
|
||||
|
||||
// GetValidators returns the last and current validator sets.
|
||||
|
@ -371,6 +412,19 @@ func (valInfo *ValidatorsInfo) Bytes() []byte {
|
|||
return wire.BinaryBytes(*valInfo)
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// ConsensusParamsInfo represents the latest consensus params, or the last height it changed
|
||||
type ConsensusParamsInfo struct {
|
||||
ConsensusParams types.ConsensusParams
|
||||
LastHeightChanged int64
|
||||
}
|
||||
|
||||
// Bytes serializes the ConsensusParamsInfo using go-wire
|
||||
func (params ConsensusParamsInfo) Bytes() []byte {
|
||||
return wire.BinaryBytes(params)
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Genesis
|
||||
|
||||
|
@ -424,15 +478,19 @@ func MakeGenesisState(db dbm.DB, genDoc *types.GenesisDoc) (*State, error) {
|
|||
db: db,
|
||||
|
||||
ChainID: genDoc.ChainID,
|
||||
Params: *genDoc.ConsensusParams,
|
||||
|
||||
LastBlockHeight: 0,
|
||||
LastBlockID: types.BlockID{},
|
||||
LastBlockTime: genDoc.GenesisTime,
|
||||
LastBlockHeight: 0,
|
||||
LastBlockID: types.BlockID{},
|
||||
LastBlockTime: genDoc.GenesisTime,
|
||||
|
||||
Validators: types.NewValidatorSet(validators),
|
||||
LastValidators: types.NewValidatorSet(nil),
|
||||
AppHash: genDoc.AppHash,
|
||||
LastConsensusHash: genDoc.ConsensusParams.Hash(),
|
||||
LastHeightValidatorsChanged: 1,
|
||||
|
||||
ConsensusParams: *genDoc.ConsensusParams,
|
||||
LastConsensusParams: types.ConsensusParams{},
|
||||
LastHeightConsensusParamsChanged: 1,
|
||||
|
||||
AppHash: genDoc.AppHash,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ func TestABCIResponsesSaveLoad(t *testing.T) {
|
|||
state.LastBlockHeight++
|
||||
|
||||
// build mock responses
|
||||
block := makeBlock(2, state)
|
||||
block := makeBlock(state, 2)
|
||||
abciResponses := NewABCIResponses(block)
|
||||
abciResponses.DeliverTx[0] = &abci.ResponseDeliverTx{Data: []byte("foo"), Tags: []*abci.KVPair{}}
|
||||
abciResponses.DeliverTx[1] = &abci.ResponseDeliverTx{Data: []byte("bar"), Log: "ok", Tags: []*abci.KVPair{}}
|
||||
|
@ -192,6 +192,65 @@ func TestValidatorChangesSaveLoad(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestConsensusParamsChangesSaveLoad tests saving and loading consensus params with changes.
|
||||
func TestConsensusParamsChangesSaveLoad(t *testing.T) {
|
||||
tearDown, _, state := setupTestCase(t)
|
||||
defer tearDown(t)
|
||||
// nolint: vetshadow
|
||||
assert := assert.New(t)
|
||||
|
||||
// change vals at these heights
|
||||
changeHeights := []int64{1, 2, 4, 5, 10, 15, 16, 17, 20}
|
||||
N := len(changeHeights)
|
||||
|
||||
// each valset is just one validator.
|
||||
// create list of them
|
||||
params := make([]types.ConsensusParams, N+1)
|
||||
params[0] = state.ConsensusParams
|
||||
for i := 1; i < N+1; i++ {
|
||||
params[i] = *types.DefaultConsensusParams()
|
||||
params[i].BlockSize.MaxBytes += i
|
||||
}
|
||||
|
||||
// build the params history by running SetBlockAndValidators
|
||||
// with the right params set for each height
|
||||
highestHeight := changeHeights[N-1] + 5
|
||||
changeIndex := 0
|
||||
cp := params[changeIndex]
|
||||
for i := int64(1); i < highestHeight; i++ {
|
||||
// when we get to a change height,
|
||||
// use the next params
|
||||
if changeIndex < len(changeHeights) && i == changeHeights[changeIndex] {
|
||||
changeIndex++
|
||||
cp = params[changeIndex]
|
||||
}
|
||||
header, parts, responses := makeHeaderPartsResponsesParams(state, i, cp)
|
||||
state.SetBlockAndValidators(header, parts, responses)
|
||||
state.saveConsensusParamsInfo()
|
||||
}
|
||||
|
||||
// make all the test cases by using the same params until after the change
|
||||
testCases := make([]paramsChangeTestCase, highestHeight)
|
||||
changeIndex = 0
|
||||
cp = params[changeIndex]
|
||||
for i := int64(1); i < highestHeight+1; i++ {
|
||||
// we we get to the height after a change height
|
||||
// use the next pubkey (note our counter starts at 0 this time)
|
||||
if changeIndex < len(changeHeights) && i == changeHeights[changeIndex]+1 {
|
||||
changeIndex++
|
||||
cp = params[changeIndex]
|
||||
}
|
||||
testCases[i-1] = paramsChangeTestCase{i, cp}
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
p, err := state.LoadConsensusParams(testCase.height)
|
||||
assert.Nil(err, fmt.Sprintf("expected no err at height %d", testCase.height))
|
||||
assert.Equal(testCase.params, p, fmt.Sprintf(`unexpected consensus params at
|
||||
height %d`, testCase.height))
|
||||
}
|
||||
}
|
||||
|
||||
func makeParams(blockBytes, blockTx, blockGas, txBytes,
|
||||
txGas, partSize int) types.ConsensusParams {
|
||||
|
||||
|
@ -199,11 +258,11 @@ func makeParams(blockBytes, blockTx, blockGas, txBytes,
|
|||
BlockSize: types.BlockSize{
|
||||
MaxBytes: blockBytes,
|
||||
MaxTxs: blockTx,
|
||||
MaxGas: blockGas,
|
||||
MaxGas: int64(blockGas),
|
||||
},
|
||||
TxSize: types.TxSize{
|
||||
MaxBytes: txBytes,
|
||||
MaxGas: txGas,
|
||||
MaxGas: int64(txGas),
|
||||
},
|
||||
BlockGossip: types.BlockGossip{
|
||||
BlockPartSizeBytes: partSize,
|
||||
|
@ -252,7 +311,7 @@ func TestApplyUpdates(t *testing.T) {
|
|||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
res := applyUpdates(tc.init, tc.updates)
|
||||
res := tc.init.Update(tc.updates)
|
||||
assert.Equal(t, tc.expected, res, "case %d", i)
|
||||
}
|
||||
}
|
||||
|
@ -260,7 +319,7 @@ func TestApplyUpdates(t *testing.T) {
|
|||
func makeHeaderPartsResponses(state *State, height int64,
|
||||
pubkey crypto.PubKey) (*types.Header, types.PartSetHeader, *ABCIResponses) {
|
||||
|
||||
block := makeBlock(height, state)
|
||||
block := makeBlock(state, height)
|
||||
_, val := state.Validators.GetByIndex(0)
|
||||
abciResponses := &ABCIResponses{
|
||||
Height: height,
|
||||
|
@ -284,3 +343,19 @@ type valChangeTestCase struct {
|
|||
height int64
|
||||
vals crypto.PubKey
|
||||
}
|
||||
|
||||
func makeHeaderPartsResponsesParams(state *State, height int64,
|
||||
params types.ConsensusParams) (*types.Header, types.PartSetHeader, *ABCIResponses) {
|
||||
|
||||
block := makeBlock(state, height)
|
||||
abciResponses := &ABCIResponses{
|
||||
Height: height,
|
||||
EndBlock: &abci.ResponseEndBlock{ConsensusParamUpdates: types.TM2PB.ConsensusParams(¶ms)},
|
||||
}
|
||||
return block.Header, types.PartSetHeader{}, abciResponses
|
||||
}
|
||||
|
||||
type paramsChangeTestCase struct {
|
||||
height int64
|
||||
params types.ConsensusParams
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package types
|
|||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
"github.com/tendermint/tmlibs/merkle"
|
||||
)
|
||||
|
||||
|
@ -20,15 +21,15 @@ type ConsensusParams struct {
|
|||
|
||||
// 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 int `json:"max_gas"`
|
||||
MaxBytes int `json:"max_bytes"` // NOTE: must not be 0 nor greater than 100MB
|
||||
MaxTxs int `json:"max_txs"`
|
||||
MaxGas int64 `json:"max_gas"`
|
||||
}
|
||||
|
||||
// TxSize contain limits on the tx size.
|
||||
type TxSize struct {
|
||||
MaxBytes int `json:"max_bytes"`
|
||||
MaxGas int `json:"max_gas"`
|
||||
MaxBytes int `json:"max_bytes"`
|
||||
MaxGas int64 `json:"max_gas"`
|
||||
}
|
||||
|
||||
// BlockGossip determine consensus critical elements of how blocks are gossiped
|
||||
|
@ -100,3 +101,42 @@ func (params *ConsensusParams) Hash() []byte {
|
|||
"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
|
||||
}
|
||||
|
|
|
@ -48,11 +48,11 @@ func makeParams(blockBytes, blockTx, blockGas, txBytes,
|
|||
BlockSize: BlockSize{
|
||||
MaxBytes: blockBytes,
|
||||
MaxTxs: blockTx,
|
||||
MaxGas: blockGas,
|
||||
MaxGas: int64(blockGas),
|
||||
},
|
||||
TxSize: TxSize{
|
||||
MaxBytes: txBytes,
|
||||
MaxGas: txGas,
|
||||
MaxGas: int64(txGas),
|
||||
},
|
||||
BlockGossip: BlockGossip{
|
||||
BlockPartSizeBytes: partSize,
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue