diff --git a/blockchain/reactor.go b/blockchain/reactor.go index 60626b3d8..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.BlockSizeParams.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 e652df3fd..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.BlockGossipParams.BlockPartSizeBytes) + firstParts := firstBlock.MakePartSet(state.ConsensusParams.BlockGossip.BlockPartSizeBytes) blockStore.SaveBlock(firstBlock, firstParts, secondBlock.LastCommit) } @@ -105,14 +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.Params.BlockGossipParams.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 8a2692a22..67a1d8218 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -863,10 +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.Params.BlockPartSizeBytes) + return cs.state.MakeBlock(cs.Height, txs, commit) } // Enter: `timeoutPropose` after entering Propose. @@ -1306,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.BlockSizeParams.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() { @@ -1341,7 +1338,8 @@ func (cs *ConsensusState) tryAddVote(vote *types.Vote, peerKey string) error { return err } else { - // Probably an invalid signature. Bad peer. + // Probably an invalid signature / Bad peer. + // Seems this can also err sometimes with "Unexpected step" - perhaps not from a bad peer ? cs.Logger.Error("Error attempting to add vote", "err", err) return ErrAddingVote } 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/consensus/wal.go b/consensus/wal.go index 7dc8d2e8b..dfbef8790 100644 --- a/consensus/wal.go +++ b/consensus/wal.go @@ -18,7 +18,7 @@ import ( ) const ( - // must be greater than params.BlockGossipParams.BlockPartSizeBytes + a few bytes + // must be greater than params.BlockGossip.BlockPartSizeBytes + a few bytes maxMsgSizeBytes = 1024 * 1024 // 1MB ) diff --git a/consensus/wal_generator.go b/consensus/wal_generator.go index 36de6bf86..b46ef93d1 100644 --- a/consensus/wal_generator.go +++ b/consensus/wal_generator.go @@ -35,7 +35,6 @@ func WALWithNBlocks(numBlocks int) (data []byte, err error) { logger := log.TestingLogger().With("wal_generator", "wal_generator") logger.Info("generating WAL (last height msg excluded)", "numBlocks", numBlocks) - ///////////////////////////////////////////////////////////////////////////// // COPY PASTE FROM node.go WITH A FEW MODIFICATIONS // NOTE: we can't import node package because of circular dependency @@ -95,7 +94,7 @@ func WALWithNBlocks(numBlocks int) (data []byte, err error) { wr.Flush() return b.Bytes(), nil case <-time.After(1 * time.Minute): - wr.Flush() + wr.Flush() return b.Bytes(), fmt.Errorf("waited too long for tendermint to produce %d blocks (grep logs for `wal_generator`)", numBlocks) } } @@ -147,7 +146,7 @@ type byteBufferWAL struct { heightToStop int64 signalWhenStopsTo chan<- struct{} - logger log.Logger + logger log.Logger } // needed for determinism diff --git a/docs/architecture/adr-005-consensus-params.md b/docs/architecture/adr-005-consensus-params.md index 678de42eb..6656d35b2 100644 --- a/docs/architecture/adr-005-consensus-params.md +++ b/docs/architecture/adr-005-consensus-params.md @@ -22,30 +22,30 @@ The parameters are used to determine the validity of a block (and tx) via the un ``` type ConsensusParams struct { - BlockSizeParams - TxSizeParams - BlockGossipParams + BlockSize + TxSize + BlockGossip } -type BlockSizeParams struct { +type BlockSize struct { MaxBytes int MaxTxs int MaxGas int } -type TxSizeParams struct { +type TxSize struct { MaxBytes int MaxGas int } -type BlockGossipParams struct { +type BlockGossip struct { BlockPartSizeBytes int } ``` The `ConsensusParams` can evolve over time by adding new structs that cover different aspects of the consensus rules. -The `BlockPartSizeBytes` and the `BlockSizeParams.MaxBytes` are enforced to be greater than 0. +The `BlockPartSizeBytes` and the `BlockSize.MaxBytes` are enforced to be greater than 0. The former because we need a part size, the latter so that we always have at least some sanity check over the size of blocks. ### ABCI @@ -58,7 +58,7 @@ like the BlockPartSize, that the app shouldn't really know about. #### EndBlock -The EndBlock response includes a `ConsensusParams`, which includes BlockSizeParams and TxSizeParams, but not BlockGossipParams. +The EndBlock response includes a `ConsensusParams`, which includes BlockSize and TxSize, but not BlockGossip. Other param struct can be added to `ConsensusParams` in the future. The `0` value is used to denote no change. Any other value will update that parameter in the `State.ConsensusParams`, to be applied for the next block. @@ -82,4 +82,5 @@ Proposed. ### Neutral -- The TxSizeParams, which checks validity, may be in conflict with the config's `max_block_size_tx`, which determines proposal sizes +- The TxSize, which checks validity, may be in conflict with the config's `max_block_size_tx`, which determines proposal sizes + diff --git a/glide.lock b/glide.lock index 05e74faa8..c29eea4de 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: f420f1f858100218dad50997d939eaaf129ff654a0648a47ddc60d626ab0b8e9 -updated: 2017-12-10T05:37:46.41123196Z +hash: bd982742a0aee39426f3866914a59f7c47e68dc8dedb87219e2099c0d9c19f7e +updated: 2017-12-21T17:45:25.372327218Z imports: - name: github.com/btcsuite/btcd version: 2e60448ffcc6bf78332d1fe590260095f554dd78 @@ -10,7 +10,7 @@ imports: - name: github.com/fsnotify/fsnotify version: 4da3e2cfbabc9f751898f250b49f2439785783a1 - name: github.com/go-kit/kit - version: e3b2152e0063c5f05efea89ecbe297852af2a92d + version: 953e747656a7bbb5e1f998608b460458958b70cc subpackages: - log - log/level @@ -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: 4e9e0ee19b60b13eb79915933f44d8ed5f268bdd + 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 @@ -88,7 +90,7 @@ imports: - name: github.com/spf13/viper version: 25b30aa063fc18e48662b86996252eabdcf2f0c7 - name: github.com/syndtr/goleveldb - version: adf24ef3f94bd13ec4163060b21a5678f22b429b + version: 34011bf325bce385408353a30b101fe5e923eb6e subpackages: - leveldb - leveldb/cache @@ -103,7 +105,7 @@ imports: - leveldb/table - leveldb/util - name: github.com/tendermint/abci - version: fca2b508c185b855af1446ec4afc19bdfc7b315d + version: e4b9f1abe794a2117a59738a1294e09b46d0fa00 subpackages: - client - example/code @@ -129,7 +131,7 @@ imports: subpackages: - iavl - name: github.com/tendermint/tmlibs - version: a483e1ff486b577ba94e6a20f08bf52fbb7bff14 + version: e236218516289f91bde3273e99fe5c1fc20eb304 subpackages: - autofile - cli @@ -144,7 +146,7 @@ imports: - pubsub/query - test - name: golang.org/x/crypto - version: 94eea52f7b742c7cbe0b03b22f0c4c8631ece122 + version: d585fd2cc9195196078f516b69daff6744ef5e84 subpackages: - curve25519 - nacl/box @@ -155,7 +157,7 @@ imports: - ripemd160 - salsa20/salsa - name: golang.org/x/net - version: a8b9294777976932365dabb6640cf1468d95c70f + version: d866cfc389cec985d6fda2859936a575a55a3ab6 subpackages: - context - http2 @@ -165,18 +167,18 @@ imports: - lex/httplex - trace - name: golang.org/x/sys - version: 8b4580aae2a0dd0c231a45d3ccb8434ff533b840 + version: d818ba11af4465e00c1998bd3f8a55603b422290 subpackages: - unix - name: golang.org/x/text - version: 75cc3cad82b5f47d3fb229ddda8c5167da14f294 + version: eb22672bea55af56d225d4e35405f4d2e9f062a0 subpackages: - secure/bidirule - transform - unicode/bidi - unicode/norm - name: google.golang.org/genproto - version: 7f0da29060c682909f650ad8ed4e515bd74fa12a + version: a8101f21cf983e773d0c1133ebc5424792003214 subpackages: - googleapis/rpc/status - name: google.golang.org/grpc @@ -199,9 +201,9 @@ imports: - tap - transport - name: gopkg.in/go-playground/validator.v9 - version: 61caf9d3038e1af346dbf5c2e16f6678e1548364 + 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 e614d0a1e..b14a3b8a5 100644 --- a/glide.yaml +++ b/glide.yaml @@ -18,7 +18,7 @@ import: - package: github.com/spf13/viper version: v1.0.0 - package: github.com/tendermint/abci - version: ~v0.8.0 + version: develop subpackages: - client - example/dummy @@ -34,7 +34,7 @@ import: subpackages: - iavl - package: github.com/tendermint/tmlibs - version: e4ef2835f0081c2ece83b9c1f777cf071f956e81 + version: develop subpackages: - autofile - cli diff --git a/lite/dynamic_test.go b/lite/dynamic_test.go index 12db19466..acbd1e651 100644 --- a/lite/dynamic_test.go +++ b/lite/dynamic_test.go @@ -46,7 +46,7 @@ func TestDynamicCert(t *testing.T) { for _, tc := range cases { check := tc.keys.GenCommit(chainID, tc.height, nil, tc.vals, - []byte("bar"), tc.first, tc.last) + []byte("bar"), []byte("params"), tc.first, tc.last) err := cert.Certify(check) if tc.proper { assert.Nil(err, "%+v", err) @@ -71,7 +71,7 @@ func TestDynamicUpdate(t *testing.T) { // one valid block to give us a sense of time h := int64(100) - good := keys.GenCommit(chainID, h, nil, vals, []byte("foo"), 0, len(keys)) + good := keys.GenCommit(chainID, h, nil, vals, []byte("foo"), []byte("params"), 0, len(keys)) err := cert.Certify(good) require.Nil(err, "%+v", err) @@ -109,7 +109,7 @@ func TestDynamicUpdate(t *testing.T) { for _, tc := range cases { fc := tc.keys.GenFullCommit(chainID, tc.height, nil, tc.vals, - []byte("bar"), tc.first, tc.last) + []byte("bar"), []byte("params"), tc.first, tc.last) err := cert.Update(fc) if tc.proper { assert.Nil(err, "%d: %+v", tc.height, err) diff --git a/lite/files/commit_test.go b/lite/files/commit_test.go index c2124379a..f6cb2a734 100644 --- a/lite/files/commit_test.go +++ b/lite/files/commit_test.go @@ -29,7 +29,7 @@ func TestSerializeFullCommits(t *testing.T) { // build a fc keys := lite.GenValKeys(5) vals := keys.ToValidators(10, 0) - fc := keys.GenFullCommit(chainID, h, nil, vals, appHash, 0, 5) + fc := keys.GenFullCommit(chainID, h, nil, vals, appHash, []byte("params"), 0, 5) require.Equal(h, fc.Height()) require.Equal(vals.Hash(), fc.ValidatorsHash()) diff --git a/lite/files/provider_test.go b/lite/files/provider_test.go index b8d8e88bb..e50d34614 100644 --- a/lite/files/provider_test.go +++ b/lite/files/provider_test.go @@ -46,7 +46,7 @@ func TestFileProvider(t *testing.T) { // (10, 0), (10, 1), (10, 1), (10, 2), (10, 2), ... vals := keys.ToValidators(10, int64(count/2)) h := int64(20 + 10*i) - check := keys.GenCommit(chainID, h, nil, vals, appHash, 0, 5) + check := keys.GenCommit(chainID, h, nil, vals, appHash, []byte("params"), 0, 5) seeds[i] = lite.NewFullCommit(check, vals) } diff --git a/lite/helpers.go b/lite/helpers.go index f8d90de5a..394cd666d 100644 --- a/lite/helpers.go +++ b/lite/helpers.go @@ -110,7 +110,7 @@ func makeVote(header *types.Header, vals *types.ValidatorSet, key crypto.PrivKey // Silences warning that vals can also be merkle.Hashable // nolint: interfacer func genHeader(chainID string, height int64, txs types.Txs, - vals *types.ValidatorSet, appHash []byte) *types.Header { + vals *types.ValidatorSet, appHash, consHash []byte) *types.Header { return &types.Header{ ChainID: chainID, @@ -123,14 +123,15 @@ func genHeader(chainID string, height int64, txs types.Txs, ValidatorsHash: vals.Hash(), DataHash: txs.Hash(), AppHash: appHash, + ConsensusHash: consHash, } } // GenCommit calls genHeader and signHeader and combines them into a Commit. func (v ValKeys) GenCommit(chainID string, height int64, txs types.Txs, - vals *types.ValidatorSet, appHash []byte, first, last int) Commit { + vals *types.ValidatorSet, appHash, consHash []byte, first, last int) Commit { - header := genHeader(chainID, height, txs, vals, appHash) + header := genHeader(chainID, height, txs, vals, appHash, consHash) check := Commit{ Header: header, Commit: v.signHeader(header, first, last), @@ -140,9 +141,9 @@ func (v ValKeys) GenCommit(chainID string, height int64, txs types.Txs, // GenFullCommit calls genHeader and signHeader and combines them into a Commit. func (v ValKeys) GenFullCommit(chainID string, height int64, txs types.Txs, - vals *types.ValidatorSet, appHash []byte, first, last int) FullCommit { + vals *types.ValidatorSet, appHash, consHash []byte, first, last int) FullCommit { - header := genHeader(chainID, height, txs, vals, appHash) + header := genHeader(chainID, height, txs, vals, appHash, consHash) commit := Commit{ Header: header, Commit: v.signHeader(header, first, last), diff --git a/lite/inquirer_test.go b/lite/inquirer_test.go index c30d82091..eb45eb3cb 100644 --- a/lite/inquirer_test.go +++ b/lite/inquirer_test.go @@ -22,6 +22,7 @@ func TestInquirerValidPath(t *testing.T) { // construct a bunch of commits, each with one more height than the last chainID := "inquiry-test" + consHash := []byte("params") count := 50 commits := make([]lite.FullCommit, count) for i := 0; i < count; i++ { @@ -30,7 +31,7 @@ func TestInquirerValidPath(t *testing.T) { vals := keys.ToValidators(vote, 0) h := int64(20 + 10*i) appHash := []byte(fmt.Sprintf("h=%d", h)) - commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, 0, len(keys)) + commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, consHash, 0, len(keys)) } // initialize a certifier with the initial state @@ -69,6 +70,7 @@ func TestInquirerMinimalPath(t *testing.T) { // construct a bunch of commits, each with one more height than the last chainID := "minimal-path" + consHash := []byte("other-params") count := 12 commits := make([]lite.FullCommit, count) for i := 0; i < count; i++ { @@ -77,7 +79,7 @@ func TestInquirerMinimalPath(t *testing.T) { vals := keys.ToValidators(vote, 0) h := int64(5 + 10*i) appHash := []byte(fmt.Sprintf("h=%d", h)) - commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, 0, len(keys)) + commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, consHash, 0, len(keys)) } // initialize a certifier with the initial state @@ -117,6 +119,7 @@ func TestInquirerVerifyHistorical(t *testing.T) { // construct a bunch of commits, each with one more height than the last chainID := "inquiry-test" count := 10 + consHash := []byte("special-params") commits := make([]lite.FullCommit, count) for i := 0; i < count; i++ { // extend the keys by 1 each time @@ -124,7 +127,7 @@ func TestInquirerVerifyHistorical(t *testing.T) { vals := keys.ToValidators(vote, 0) h := int64(20 + 10*i) appHash := []byte(fmt.Sprintf("h=%d", h)) - commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, 0, len(keys)) + commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, consHash, 0, len(keys)) } // initialize a certifier with the initial state diff --git a/lite/performance_test.go b/lite/performance_test.go index e01b89936..af6eacc33 100644 --- a/lite/performance_test.go +++ b/lite/performance_test.go @@ -33,7 +33,7 @@ func benchmarkGenCommit(b *testing.B, keys lite.ValKeys) { for i := 0; i < b.N; i++ { h := int64(1 + i) appHash := []byte(fmt.Sprintf("h=%d", h)) - keys.GenCommit(chainID, h, nil, vals, appHash, 0, len(keys)) + keys.GenCommit(chainID, h, nil, vals, appHash, []byte("params"), 0, len(keys)) } } @@ -105,7 +105,7 @@ func benchmarkCertifyCommit(b *testing.B, keys lite.ValKeys) { chainID := "bench-certify" vals := keys.ToValidators(20, 10) cert := lite.NewStatic(chainID, vals) - check := keys.GenCommit(chainID, 123, nil, vals, []byte("foo"), 0, len(keys)) + check := keys.GenCommit(chainID, 123, nil, vals, []byte("foo"), []byte("params"), 0, len(keys)) for i := 0; i < b.N; i++ { err := cert.Certify(check) if err != nil { diff --git a/lite/provider_test.go b/lite/provider_test.go index f1165619f..09ad0aa8a 100644 --- a/lite/provider_test.go +++ b/lite/provider_test.go @@ -58,7 +58,7 @@ func checkProvider(t *testing.T, p lite.Provider, chainID, app string) { // (10, 0), (10, 1), (10, 1), (10, 2), (10, 2), ... vals := keys.ToValidators(10, int64(count/2)) h := int64(20 + 10*i) - commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, 0, 5) + commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, []byte("params"), 0, 5) } // check provider is empty @@ -129,7 +129,7 @@ func TestCacheGetsBestHeight(t *testing.T) { for i := 0; i < count; i++ { vals := keys.ToValidators(10, int64(count/2)) h := int64(10 * (i + 1)) - fc := keys.GenFullCommit(chainID, h, nil, vals, appHash, 0, 5) + fc := keys.GenFullCommit(chainID, h, nil, vals, appHash, []byte("params"), 0, 5) err := p2.StoreCommit(fc) require.NoError(err) } diff --git a/lite/static_test.go b/lite/static_test.go index e4bf435c5..d0a1e6859 100644 --- a/lite/static_test.go +++ b/lite/static_test.go @@ -44,7 +44,7 @@ func TestStaticCert(t *testing.T) { for _, tc := range cases { check := tc.keys.GenCommit(chainID, tc.height, nil, tc.vals, - []byte("foo"), tc.first, tc.last) + []byte("foo"), []byte("params"), tc.first, tc.last) err := cert.Certify(check) if tc.proper { assert.Nil(err, "%+v", err) 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 7ba4ac3e9..e90c8e710 100644 --- a/state/execution.go +++ b/state/execution.go @@ -1,6 +1,7 @@ package state import ( + "bytes" "errors" "fmt" @@ -111,11 +112,11 @@ func execBlockOnProxyApp(txEventPublisher types.TxEventPublisher, proxyAppConn p return nil, err } - valDiff := abciResponses.EndBlock.Diffs + valUpdates := abciResponses.EndBlock.ValidatorUpdates logger.Info("Executed block", "height", block.Height, "validTxs", validTxs, "invalidTxs", invalidTxs) - if len(valDiff) > 0 { - logger.Info("Update to validator set", "updates", abci.ValidatorsString(valDiff)) + if len(valUpdates) > 0 { + logger.Info("Updates to validators", "updates", abci.ValidatorsString(valUpdates)) } return abciResponses, nil @@ -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) - 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 ac5c72f26..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, 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, 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 f8a8d1a03..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,37 +28,50 @@ 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 logger log.Logger @@ -112,18 +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, 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, } } @@ -133,6 +155,7 @@ func (s *State) Save() { defer s.mtx.Unlock() s.saveValidatorsInfo() + s.saveConsensusParamsInfo() s.db.SetSync(stateKey, s.Bytes()) } @@ -166,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)) @@ -182,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 @@ -217,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()) @@ -230,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 @@ -238,11 +316,10 @@ func (s *State) SetBlockAndValidators(header *types.Header, blockPartsHeader typ nextValSet := prevValSet.Copy() // update the validator set with the latest abciResponses - if len(abciResponses.EndBlock.Diffs) > 0 { - err := updateValidators(nextValSet, abciResponses.EndBlock.Diffs) + 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 @@ -251,24 +328,43 @@ func (s *State) SetBlockAndValidators(header *types.Header, blockPartsHeader typ // Update validator accums and set state variables nextValSet.IncrementAccum(1) + // 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) + return nil } func (s *State) setBlockAndValidators(height int64, newTxs int64, blockID types.BlockID, blockTime time.Time, - prevValSet, nextValSet *types.ValidatorSet) { + 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.LastValidators = s.Validators.Copy() + s.Validators = valSet + + s.LastConsensusParams = s.ConsensusParams + s.ConsensusParams = params } // GetValidators returns the last and current validator sets. @@ -316,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 @@ -369,14 +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, 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 9b78b3871..1840d5e15 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -76,11 +76,11 @@ 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{}} - abciResponses.EndBlock = &abci.ResponseEndBlock{Diffs: []*abci.Validator{ + abciResponses.EndBlock = &abci.ResponseEndBlock{ValidatorUpdates: []*abci.Validator{ { PubKey: crypto.GenPrivKeyEd25519().PubKey().Bytes(), Power: 10, @@ -192,20 +192,144 @@ 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 { + + return types.ConsensusParams{ + BlockSize: types.BlockSize{ + MaxBytes: blockBytes, + MaxTxs: blockTx, + MaxGas: int64(blockGas), + }, + TxSize: types.TxSize{ + MaxBytes: txBytes, + MaxGas: int64(txGas), + }, + BlockGossip: types.BlockGossip{ + BlockPartSizeBytes: partSize, + }, + } +} + +func TestApplyUpdates(t *testing.T) { + initParams := makeParams(1, 2, 3, 4, 5, 6) + + cases := [...]struct { + init types.ConsensusParams + updates *abci.ConsensusParams + expected types.ConsensusParams + }{ + 0: {initParams, nil, initParams}, + 1: {initParams, &abci.ConsensusParams{}, initParams}, + 2: {initParams, + &abci.ConsensusParams{ + TxSize: &abci.TxSize{ + MaxBytes: 123, + }, + }, + makeParams(1, 2, 3, 123, 5, 6)}, + 3: {initParams, + &abci.ConsensusParams{ + BlockSize: &abci.BlockSize{ + MaxTxs: 44, + MaxGas: 55, + }, + }, + makeParams(1, 44, 55, 4, 5, 6)}, + 4: {initParams, + &abci.ConsensusParams{ + BlockSize: &abci.BlockSize{ + MaxTxs: 789, + }, + TxSize: &abci.TxSize{ + MaxGas: 888, + }, + BlockGossip: &abci.BlockGossip{ + BlockPartSizeBytes: 2002, + }, + }, + makeParams(1, 789, 3, 4, 888, 2002)}, + } + + for i, tc := range cases { + res := tc.init.Update(tc.updates) + assert.Equal(t, tc.expected, res, "case %d", i) + } +} + 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, - EndBlock: &abci.ResponseEndBlock{Diffs: []*abci.Validator{}}, + EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []*abci.Validator{}}, } // if the pubkey is new, remove the old and add the new if !bytes.Equal(pubkey.Bytes(), val.PubKey.Bytes()) { abciResponses.EndBlock = &abci.ResponseEndBlock{ - Diffs: []*abci.Validator{ + ValidatorUpdates: []*abci.Validator{ {val.PubKey.Bytes(), 0}, {pubkey.Bytes(), 10}, }, @@ -219,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 ff3d3811b..7a1ed04e9 100644 --- a/types/block.go +++ b/types/block.go @@ -15,30 +15,21 @@ import ( ) // Block defines the atomic unit of a Tendermint blockchain. +// TODO: add Version byte type Block struct { *Header `json:"header"` *Data `json:"data"` LastCommit *Commit `json:"last_commit"` } -// MakeBlock returns a new block and corresponding partset from the given information. -// TODO: Add version information to the Block struct. -func MakeBlock(height int64, chainID string, txs []Tx, - totalTxs int64, commit *Commit, - prevBlockID BlockID, valHash, appHash []byte, - partSize int) (*Block, *PartSet) { - - newTxs := int64(len(txs)) +// MakeBlock returns a new block with an empty header, except what can be computed from itself. +// It populates the same set of fields validated by ValidateBasic +func MakeBlock(height int64, txs []Tx, commit *Commit) *Block { block := &Block{ Header: &Header{ - ChainID: chainID, - Height: height, - Time: time.Now(), - NumTxs: newTxs, - TotalTxs: totalTxs + newTxs, - LastBlockID: prevBlockID, - ValidatorsHash: valHash, - AppHash: appHash, // state merkle root of txs from the previous block. + Height: height, + Time: time.Now(), + NumTxs: int64(len(txs)), }, LastCommit: commit, Data: &Data{ @@ -46,39 +37,18 @@ func MakeBlock(height int64, chainID string, txs []Tx, }, } block.FillHeader() - return block, block.MakePartSet(partSize) + return block } // ValidateBasic performs basic validation that doesn't involve state data. -func (b *Block) ValidateBasic(chainID string, lastBlockHeight int64, - lastBlockTotalTx int64, lastBlockID BlockID, - lastBlockTime time.Time, appHash []byte) error { - - if b.ChainID != chainID { - return errors.New(cmn.Fmt("Wrong Block.Header.ChainID. Expected %v, got %v", chainID, b.ChainID)) - } - if b.Height != lastBlockHeight+1 { - return errors.New(cmn.Fmt("Wrong Block.Header.Height. Expected %v, got %v", lastBlockHeight+1, b.Height)) - } - /* TODO: Determine bounds for Time - See blockchain/reactor "stopSyncingDurationMinutes" - - if !b.Time.After(lastBlockTime) { - return errors.New("Invalid Block.Header.Time") - } - */ +// It checks the internal consistency of the block. +func (b *Block) ValidateBasic() error { newTxs := int64(len(b.Data.Txs)) if b.NumTxs != newTxs { - return errors.New(cmn.Fmt("Wrong Block.Header.NumTxs. Expected %v, got %v", newTxs, b.NumTxs)) - } - if b.TotalTxs != lastBlockTotalTx+newTxs { - return errors.New(cmn.Fmt("Wrong Block.Header.TotalTxs. Expected %v, got %v", lastBlockTotalTx+newTxs, b.TotalTxs)) - } - if !b.LastBlockID.Equals(lastBlockID) { - return errors.New(cmn.Fmt("Wrong Block.Header.LastBlockID. Expected %v, got %v", lastBlockID, b.LastBlockID)) + return fmt.Errorf("Wrong Block.Header.NumTxs. Expected %v, got %v", newTxs, b.NumTxs) } if !bytes.Equal(b.LastCommitHash, b.LastCommit.Hash()) { - return errors.New(cmn.Fmt("Wrong Block.Header.LastCommitHash. Expected %v, got %v", b.LastCommitHash, b.LastCommit.Hash())) + return fmt.Errorf("Wrong Block.Header.LastCommitHash. Expected %v, got %v", b.LastCommitHash, b.LastCommit.Hash()) } if b.Header.Height != 1 { if err := b.LastCommit.ValidateBasic(); err != nil { @@ -86,12 +56,8 @@ func (b *Block) ValidateBasic(chainID string, lastBlockHeight int64, } } if !bytes.Equal(b.DataHash, b.Data.Hash()) { - return errors.New(cmn.Fmt("Wrong Block.Header.DataHash. Expected %v, got %v", b.DataHash, b.Data.Hash())) + return fmt.Errorf("Wrong Block.Header.DataHash. Expected %v, got %v", b.DataHash, b.Data.Hash()) } - if !bytes.Equal(b.AppHash, appHash) { - return errors.New(cmn.Fmt("Wrong Block.Header.AppHash. Expected %X, got %v", appHash, b.AppHash)) - } - // NOTE: the AppHash and ValidatorsHash are validated later. return nil } @@ -167,17 +133,26 @@ func (b *Block) StringShort() string { //----------------------------------------------------------------------------- // Header defines the structure of a Tendermint block header +// TODO: limit header size type Header struct { - ChainID string `json:"chain_id"` - Height int64 `json:"height"` - Time time.Time `json:"time"` - NumTxs int64 `json:"num_txs"` // XXX: Can we get rid of this? - TotalTxs int64 `json:"total_txs"` - LastBlockID BlockID `json:"last_block_id"` + // basic block info + ChainID string `json:"chain_id"` + Height int64 `json:"height"` + Time time.Time `json:"time"` + NumTxs int64 `json:"num_txs"` + + // prev block info + LastBlockID BlockID `json:"last_block_id"` + TotalTxs int64 `json:"total_txs"` + + // hashes of block data LastCommitHash data.Bytes `json:"last_commit_hash"` // commit from validators from the last block DataHash data.Bytes `json:"data_hash"` // transactions - ValidatorsHash data.Bytes `json:"validators_hash"` // validators for the current block - AppHash data.Bytes `json:"app_hash"` // state after txs from the previous block + + // hashes from the app + ValidatorsHash data.Bytes `json:"validators_hash"` // validators for the current block + ConsensusHash data.Bytes `json:"consensus_hash"` // consensus params for current block + AppHash data.Bytes `json:"app_hash"` // state after txs from the previous block } // Hash returns the hash of the header. @@ -197,6 +172,7 @@ func (h *Header) Hash() data.Bytes { "Data": h.DataHash, "Validators": h.ValidatorsHash, "App": h.AppHash, + "Consensus": h.ConsensusHash, }) } @@ -216,6 +192,7 @@ func (h *Header) StringIndented(indent string) string { %s Data: %v %s Validators: %v %s App: %v +%s Conensus: %v %s}#%v`, indent, h.ChainID, indent, h.Height, @@ -227,6 +204,7 @@ func (h *Header) StringIndented(indent string) string { indent, h.DataHash, indent, h.ValidatorsHash, indent, h.AppHash, + indent, h.ConsensusHash, indent, h.Hash()) } diff --git a/types/block_test.go b/types/block_test.go index 25e5a76e7..bde474403 100644 --- a/types/block_test.go +++ b/types/block_test.go @@ -2,81 +2,63 @@ package types import ( "testing" - "time" "github.com/stretchr/testify/require" crypto "github.com/tendermint/go-crypto" + cmn "github.com/tendermint/tmlibs/common" ) func TestValidateBlock(t *testing.T) { txs := []Tx{Tx("foo"), Tx("bar")} lastID := makeBlockID() - valHash := []byte("val") - appHash := []byte("app") h := int64(3) voteSet, _, vals := randVoteSet(h-1, 1, VoteTypePrecommit, 10, 1) - commit, err := makeCommit(lastID, h-1, 1, voteSet, vals) + commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals) require.NoError(t, err) - block, _ := MakeBlock(h, "hello", txs, 10, commit, - lastID, valHash, appHash, 2) + block := MakeBlock(h, txs, commit) require.NotNil(t, block) // proper block must pass - err = block.ValidateBasic("hello", h-1, 10, lastID, block.Time, appHash) + err = block.ValidateBasic() require.NoError(t, err) - // wrong chain fails - err = block.ValidateBasic("other", h-1, 10, lastID, block.Time, appHash) + // tamper with NumTxs + block = MakeBlock(h, txs, commit) + block.NumTxs += 1 + err = block.ValidateBasic() require.Error(t, err) - // wrong height fails - err = block.ValidateBasic("hello", h+4, 10, lastID, block.Time, appHash) + // remove 1/2 the commits + block = MakeBlock(h, txs, commit) + block.LastCommit.Precommits = commit.Precommits[:commit.Size()/2] + block.LastCommit.hash = nil // clear hash or change wont be noticed + err = block.ValidateBasic() require.Error(t, err) - // wrong total tx fails - err = block.ValidateBasic("hello", h-1, 15, lastID, block.Time, appHash) + // tamper with LastCommitHash + block = MakeBlock(h, txs, commit) + block.LastCommitHash = []byte("something else") + err = block.ValidateBasic() require.Error(t, err) - // wrong blockid fails - err = block.ValidateBasic("hello", h-1, 10, makeBlockID(), block.Time, appHash) + // tamper with data + block = MakeBlock(h, txs, commit) + block.Data.Txs[0] = Tx("something else") + block.Data.hash = nil // clear hash or change wont be noticed + err = block.ValidateBasic() require.Error(t, err) - // wrong app hash fails - err = block.ValidateBasic("hello", h-1, 10, lastID, block.Time, []byte("bad-hash")) + // tamper with DataHash + block = MakeBlock(h, txs, commit) + block.DataHash = cmn.RandBytes(len(block.DataHash)) + err = block.ValidateBasic() require.Error(t, err) - } func makeBlockID() BlockID { blockHash, blockPartsHeader := crypto.CRandBytes(32), PartSetHeader{123, crypto.CRandBytes(32)} return BlockID{blockHash, blockPartsHeader} } - -func makeCommit(blockID BlockID, height int64, round int, - voteSet *VoteSet, - validators []*PrivValidatorFS) (*Commit, error) { - - voteProto := &Vote{ - ValidatorAddress: nil, - ValidatorIndex: -1, - Height: height, - Round: round, - Type: VoteTypePrecommit, - BlockID: blockID, - Timestamp: time.Now().UTC(), - } - - // all sign - for i := 0; i < len(validators); i++ { - vote := withValidator(voteProto, validators[i].GetAddress(), i) - _, err := signAddVote(validators[i], vote, voteSet) - if err != nil { - return nil, err - } - } - - return voteSet.MakeCommit(), nil -} diff --git a/types/genesis_test.go b/types/genesis_test.go index 214bae40e..bdef3b925 100644 --- a/types/genesis_test.go +++ b/types/genesis_test.go @@ -54,7 +54,7 @@ func TestGenesis(t *testing.T) { assert.NoError(t, err, "expected no error for valid genDoc json") // test with invalid consensus params - genDoc.ConsensusParams.BlockSizeParams.MaxBytes = 0 + genDoc.ConsensusParams.BlockSize.MaxBytes = 0 genDocBytes, err = json.Marshal(genDoc) assert.NoError(t, err, "error marshalling genDoc") genDoc, err = GenesisDocFromJSON(genDocBytes) diff --git a/types/params.go b/types/params.go index 322cba612..19e86d449 100644 --- a/types/params.go +++ b/types/params.go @@ -2,6 +2,9 @@ package types import ( "github.com/pkg/errors" + + abci "github.com/tendermint/abci/types" + "github.com/tendermint/tmlibs/merkle" ) const ( @@ -11,58 +14,58 @@ const ( // ConsensusParams contains consensus critical parameters // that determine the validity of blocks. type ConsensusParams struct { - BlockSizeParams `json:"block_size_params"` - TxSizeParams `json:"tx_size_params"` - BlockGossipParams `json:"block_gossip_params"` + BlockSize `json:"block_size_params"` + TxSize `json:"tx_size_params"` + BlockGossip `json:"block_gossip_params"` } -// BlockSizeParams contain limits on the block size. -type BlockSizeParams struct { - MaxBytes int `json:"max_bytes"` // NOTE: must not be 0 nor greater than 100MB - MaxTxs int `json:"max_txs"` - MaxGas int `json:"max_gas"` +// BlockSize contain limits on the block size. +type BlockSize struct { + MaxBytes int `json:"max_bytes"` // NOTE: must not be 0 nor greater than 100MB + MaxTxs int `json:"max_txs"` + MaxGas int64 `json:"max_gas"` } -// TxSizeParams contain limits on the tx size. -type TxSizeParams struct { - MaxBytes int `json:"max_bytes"` - MaxGas int `json:"max_gas"` +// TxSize contain limits on the tx size. +type TxSize struct { + MaxBytes int `json:"max_bytes"` + MaxGas int64 `json:"max_gas"` } -// BlockGossipParams determine consensus critical elements of how blocks are gossiped -type BlockGossipParams struct { +// BlockGossip determine consensus critical elements of how blocks are gossiped +type BlockGossip struct { BlockPartSizeBytes int `json:"block_part_size_bytes"` // NOTE: must not be 0 } // DefaultConsensusParams returns a default ConsensusParams. func DefaultConsensusParams() *ConsensusParams { return &ConsensusParams{ - DefaultBlockSizeParams(), - DefaultTxSizeParams(), - DefaultBlockGossipParams(), + DefaultBlockSize(), + DefaultTxSize(), + DefaultBlockGossip(), } } -// DefaultBlockSizeParams returns a default BlockSizeParams. -func DefaultBlockSizeParams() BlockSizeParams { - return BlockSizeParams{ +// DefaultBlockSize returns a default BlockSize. +func DefaultBlockSize() BlockSize { + return BlockSize{ MaxBytes: 22020096, // 21MB MaxTxs: 100000, MaxGas: -1, } } -// DefaultTxSizeParams returns a default TxSizeParams. -func DefaultTxSizeParams() TxSizeParams { - return TxSizeParams{ +// DefaultTxSize returns a default TxSize. +func DefaultTxSize() TxSize { + return TxSize{ MaxBytes: 10240, // 10kB MaxGas: -1, } } -// DefaultBlockGossipParams returns a default BlockGossipParams. -func DefaultBlockGossipParams() BlockGossipParams { - return BlockGossipParams{ +// DefaultBlockGossip returns a default BlockGossip. +func DefaultBlockGossip() BlockGossip { + return BlockGossip{ BlockPartSizeBytes: 65536, // 64kB, } } @@ -71,17 +74,69 @@ func DefaultBlockGossipParams() BlockGossipParams { // are within their allowed limits, and returns an error if they are not. func (params *ConsensusParams) Validate() error { // ensure some values are greater than 0 - if params.BlockSizeParams.MaxBytes <= 0 { - return errors.Errorf("BlockSizeParams.MaxBytes must be greater than 0. Got %d", params.BlockSizeParams.MaxBytes) + if params.BlockSize.MaxBytes <= 0 { + return errors.Errorf("BlockSize.MaxBytes must be greater than 0. Got %d", params.BlockSize.MaxBytes) } - if params.BlockGossipParams.BlockPartSizeBytes <= 0 { - return errors.Errorf("BlockGossipParams.BlockPartSizeBytes must be greater than 0. Got %d", params.BlockGossipParams.BlockPartSizeBytes) + if params.BlockGossip.BlockPartSizeBytes <= 0 { + return errors.Errorf("BlockGossip.BlockPartSizeBytes must be greater than 0. Got %d", params.BlockGossip.BlockPartSizeBytes) } // ensure blocks aren't too big - if params.BlockSizeParams.MaxBytes > maxBlockSizeBytes { - return errors.Errorf("BlockSizeParams.MaxBytes is too big. %d > %d", - params.BlockSizeParams.MaxBytes, maxBlockSizeBytes) + if params.BlockSize.MaxBytes > maxBlockSizeBytes { + return errors.Errorf("BlockSize.MaxBytes is too big. %d > %d", + params.BlockSize.MaxBytes, maxBlockSizeBytes) } return nil } + +// Hash returns a merkle hash of the parameters to store +// in the block header +func (params *ConsensusParams) Hash() []byte { + return merkle.SimpleHashFromMap(map[string]interface{}{ + "block_gossip_part_size_bytes": params.BlockGossip.BlockPartSizeBytes, + "block_size_max_bytes": params.BlockSize.MaxBytes, + "block_size_max_gas": params.BlockSize.MaxGas, + "block_size_max_txs": params.BlockSize.MaxTxs, + "tx_size_max_bytes": params.TxSize.MaxBytes, + "tx_size_max_gas": params.TxSize.MaxGas, + }) +} + +// Update returns a copy of the params with updates from the non-zero fields of p2. +// NOTE: note: must not modify the original +func (params ConsensusParams) Update(params2 *abci.ConsensusParams) ConsensusParams { + res := params // explicit copy + + if params2 == nil { + return res + } + + // we must defensively consider any structs may be nil + // XXX: it's cast city over here. It's ok because we only do int32->int + // but still, watch it champ. + if params2.BlockSize != nil { + if params2.BlockSize.MaxBytes > 0 { + res.BlockSize.MaxBytes = int(params2.BlockSize.MaxBytes) + } + if params2.BlockSize.MaxTxs > 0 { + res.BlockSize.MaxTxs = int(params2.BlockSize.MaxTxs) + } + if params2.BlockSize.MaxGas > 0 { + res.BlockSize.MaxGas = params2.BlockSize.MaxGas + } + } + if params2.TxSize != nil { + if params2.TxSize.MaxBytes > 0 { + res.TxSize.MaxBytes = int(params2.TxSize.MaxBytes) + } + if params2.TxSize.MaxGas > 0 { + res.TxSize.MaxGas = params2.TxSize.MaxGas + } + } + if params2.BlockGossip != nil { + if params2.BlockGossip.BlockPartSizeBytes > 0 { + res.BlockGossip.BlockPartSizeBytes = int(params2.BlockGossip.BlockPartSizeBytes) + } + } + return res +} diff --git a/types/params_test.go b/types/params_test.go index 507c85139..f645585eb 100644 --- a/types/params_test.go +++ b/types/params_test.go @@ -1,6 +1,8 @@ package types import ( + "bytes" + "sort" "testing" "github.com/stretchr/testify/assert" @@ -8,8 +10,8 @@ import ( func newConsensusParams(blockSize, partSize int) ConsensusParams { return ConsensusParams{ - BlockSizeParams: BlockSizeParams{MaxBytes: blockSize}, - BlockGossipParams: BlockGossipParams{BlockPartSizeBytes: partSize}, + BlockSize: BlockSize{MaxBytes: blockSize}, + BlockGossip: BlockGossip{BlockPartSizeBytes: partSize}, } } @@ -38,3 +40,49 @@ func TestConsensusParamsValidation(t *testing.T) { } } } + +func makeParams(blockBytes, blockTx, blockGas, txBytes, + txGas, partSize int) ConsensusParams { + + return ConsensusParams{ + BlockSize: BlockSize{ + MaxBytes: blockBytes, + MaxTxs: blockTx, + MaxGas: int64(blockGas), + }, + TxSize: TxSize{ + MaxBytes: txBytes, + MaxGas: int64(txGas), + }, + BlockGossip: BlockGossip{ + BlockPartSizeBytes: partSize, + }, + } +} + +func TestConsensusParamsHash(t *testing.T) { + params := []ConsensusParams{ + makeParams(1, 2, 3, 4, 5, 6), + makeParams(7, 2, 3, 4, 5, 6), + makeParams(1, 7, 3, 4, 5, 6), + makeParams(1, 2, 7, 4, 5, 6), + makeParams(1, 2, 3, 7, 5, 6), + makeParams(1, 2, 3, 4, 7, 6), + makeParams(1, 2, 3, 4, 5, 7), + makeParams(6, 5, 4, 3, 2, 1), + } + + hashes := make([][]byte, len(params)) + for i := range params { + hashes[i] = params[i].Hash() + } + + // make sure there are no duplicates... + // sort, then check in order for matches + sort.Slice(hashes, func(i, j int) bool { + return bytes.Compare(hashes[i], hashes[j]) < 0 + }) + for i := 0; i < len(hashes)-1; i++ { + assert.NotEqual(t, hashes[i], hashes[i+1]) + } +} diff --git a/types/protobuf.go b/types/protobuf.go index c97b53872..43c8f4505 100644 --- a/types/protobuf.go +++ b/types/protobuf.go @@ -12,11 +12,11 @@ type tm2pb struct{} func (tm2pb) Header(header *Header) *types.Header { return &types.Header{ - ChainId: header.ChainID, + ChainID: header.ChainID, Height: header.Height, Time: header.Time.Unix(), NumTxs: int32(header.NumTxs), // XXX: overflow - LastBlockId: TM2PB.BlockID(header.LastBlockID), + LastBlockID: TM2PB.BlockID(header.LastBlockID), LastCommitHash: header.LastCommitHash, DataHash: header.DataHash, AppHash: header.AppHash, @@ -51,3 +51,21 @@ func (tm2pb) Validators(vals *ValidatorSet) []*types.Validator { } return validators } + +func (tm2pb) ConsensusParams(params *ConsensusParams) *types.ConsensusParams { + return &types.ConsensusParams{ + BlockSize: &types.BlockSize{ + + MaxBytes: int32(params.BlockSize.MaxBytes), + MaxTxs: int32(params.BlockSize.MaxTxs), + MaxGas: params.BlockSize.MaxGas, + }, + TxSize: &types.TxSize{ + MaxBytes: int32(params.TxSize.MaxBytes), + MaxGas: params.TxSize.MaxGas, + }, + BlockGossip: &types.BlockGossip{ + BlockPartSizeBytes: int32(params.BlockGossip.BlockPartSizeBytes), + }, + } +} 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)