diff --git a/blockchain/reactor.go b/blockchain/reactor.go index e070830aa..e41300d5e 100644 --- a/blockchain/reactor.go +++ b/blockchain/reactor.go @@ -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 diff --git a/blockchain/reactor_test.go b/blockchain/reactor_test.go index dd782ea3f..36cdc080d 100644 --- a/blockchain/reactor_test.go +++ b/blockchain/reactor_test.go @@ -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 } diff --git a/circle.yml b/circle.yml index 3805eb80f..9d03bc46e 100644 --- a/circle.yml +++ b/circle.yml @@ -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: diff --git a/consensus/replay_test.go b/consensus/replay_test.go index 32f7f4376..fbab03d20 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -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 } diff --git a/consensus/state.go b/consensus/state.go index 3a64746ee..9c89ae4ed 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -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() { diff --git a/consensus/state_test.go b/consensus/state_test.go index ecccafed4..6beb7da54 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -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) diff --git a/glide.lock b/glide.lock index b52090574..b74ffd78c 100644 --- a/glide.lock +++ b/glide.lock @@ -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 diff --git a/glide.yaml b/glide.yaml index 3e699b373..b14a3b8a5 100644 --- a/glide.yaml +++ b/glide.yaml @@ -34,7 +34,7 @@ import: subpackages: - iavl - package: github.com/tendermint/tmlibs - version: e4ef2835f0081c2ece83b9c1f777cf071f956e81 + version: develop subpackages: - autofile - cli diff --git a/rpc/test/helpers.go b/rpc/test/helpers.go index 69fe7aa8c..c8671d68e 100644 --- a/rpc/test/helpers.go +++ b/rpc/test/helpers.go @@ -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) { diff --git a/state/errors.go b/state/errors.go index f7520cf6c..98f44281a 100644 --- a/state/errors.go +++ b/state/errors.go @@ -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) +} diff --git a/state/execution.go b/state/execution.go index b2b98b835..e90c8e710 100644 --- a/state/execution.go +++ b/state/execution.go @@ -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) diff --git a/state/execution_test.go b/state/execution_test.go index 379ac6732..a639d39a5 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -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 } diff --git a/state/state.go b/state/state.go index df6e44370..0bed708a6 100644 --- a/state/state.go +++ b/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 } diff --git a/state/state_test.go b/state/state_test.go index 80bb9341d..1840d5e15 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -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 +} diff --git a/types/block.go b/types/block.go index a00e75a62..7a1ed04e9 100644 --- a/types/block.go +++ b/types/block.go @@ -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. diff --git a/types/block_test.go b/types/block_test.go index a874508a4..bde474403 100644 --- a/types/block_test.go +++ b/types/block_test.go @@ -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 -} diff --git a/types/params.go b/types/params.go index e7c32f48f..19e86d449 100644 --- a/types/params.go +++ b/types/params.go @@ -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 +} diff --git a/types/params_test.go b/types/params_test.go index a864ce83e..f645585eb 100644 --- a/types/params_test.go +++ b/types/params_test.go @@ -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, diff --git a/types/protobuf.go b/types/protobuf.go index e97864fb6..43c8f4505 100644 --- a/types/protobuf.go +++ b/types/protobuf.go @@ -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), + }, + } +} diff --git a/types/test_util.go b/types/test_util.go new file mode 100644 index 000000000..d13de04e2 --- /dev/null +++ b/types/test_util.go @@ -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) +} diff --git a/types/vote_set_test.go b/types/vote_set_test.go index d125c5502..4a09d04d3 100644 --- a/types/vote_set_test.go +++ b/types/vote_set_test.go @@ -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)