diff --git a/blockchain/pool.go b/blockchain/pool.go index e1288c9fa..924880c08 100644 --- a/blockchain/pool.go +++ b/blockchain/pool.go @@ -352,7 +352,7 @@ func (peer *bpPeer) setLogger(l log.Logger) { func (peer *bpPeer) resetMonitor() { peer.recvMonitor = flow.New(time.Second, time.Second*40) - var initialValue = float64(minRecvRate) * math.E + initialValue := float64(minRecvRate) * math.E peer.recvMonitor.SetREMA(initialValue) } diff --git a/blockchain/reactor.go b/blockchain/reactor.go index efa6e2f05..fb68aadda 100644 --- a/blockchain/reactor.go +++ b/blockchain/reactor.go @@ -28,7 +28,6 @@ const ( statusUpdateIntervalSeconds = 10 // check if we should switch to consensus reactor switchToConsensusIntervalSeconds = 1 - maxBlockchainResponseSize = types.MaxBlockSize + 2 ) type consensusReactor interface { @@ -124,7 +123,7 @@ func (bcR *BlockchainReactor) RemovePeer(peer p2p.Peer, reason interface{}) { // Receive implements Reactor by handling 4 types of messages (look below). func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { - _, msg, err := DecodeMessage(msgBytes) + _, msg, err := DecodeMessage(msgBytes, bcR.maxMsgSize()) if err != nil { bcR.Logger.Error("Error decoding message", "err", err) return @@ -163,6 +162,12 @@ 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 +} + // Handle messages from the poolReactor telling the reactor what to do. // NOTE: Don't sleep in the FOR_LOOP or otherwise slow it down! // (Except for the SYNC_LOOP, which is the primary purpose and must be synchronous.) @@ -221,7 +226,7 @@ FOR_LOOP: // We need both to sync the first block. break SYNC_LOOP } - firstParts := first.MakePartSet(types.DefaultBlockPartSize) + firstParts := first.MakePartSet(bcR.state.Params().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 @@ -290,11 +295,11 @@ var _ = wire.RegisterInterface( // DecodeMessage decodes BlockchainMessage. // TODO: ensure that bz is completely read. -func DecodeMessage(bz []byte) (msgType byte, msg BlockchainMessage, err error) { +func DecodeMessage(bz []byte, maxSize int) (msgType byte, msg BlockchainMessage, err error) { msgType = bz[0] n := int(0) r := bytes.NewReader(bz) - msg = wire.ReadBinary(struct{ BlockchainMessage }{}, r, maxBlockchainResponseSize, &n, &err).(struct{ BlockchainMessage }).BlockchainMessage + msg = wire.ReadBinary(struct{ BlockchainMessage }{}, r, maxSize, &n, &err).(struct{ BlockchainMessage }).BlockchainMessage if err != nil && n != len(bz) { err = errors.New("DecodeMessage() had bytes left over") } diff --git a/cmd/tendermint/commands/init.go b/cmd/tendermint/commands/init.go index cd16707cc..ce900defa 100644 --- a/cmd/tendermint/commands/init.go +++ b/cmd/tendermint/commands/init.go @@ -34,7 +34,7 @@ func initFiles(cmd *cobra.Command, args []string) { } genDoc.Validators = []types.GenesisValidator{types.GenesisValidator{ PubKey: privValidator.PubKey, - Amount: 10, + Power: 10, }} genDoc.SaveAs(genFile) diff --git a/cmd/tendermint/commands/testnet.go b/cmd/tendermint/commands/testnet.go index 58767eb05..315f3b498 100644 --- a/cmd/tendermint/commands/testnet.go +++ b/cmd/tendermint/commands/testnet.go @@ -7,8 +7,8 @@ import ( "github.com/spf13/cobra" - cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tendermint/types" + cmn "github.com/tendermint/tmlibs/common" ) var testnetFilesCmd = &cobra.Command{ @@ -48,7 +48,7 @@ func testnetFiles(cmd *cobra.Command, args []string) { privVal := types.LoadPrivValidator(privValFile) genVals[i] = types.GenesisValidator{ PubKey: privVal.PubKey, - Amount: 1, + Power: 1, Name: mach, } } diff --git a/config/config.go b/config/config.go index ec1f85edb..23da4f405 100644 --- a/config/config.go +++ b/config/config.go @@ -4,8 +4,6 @@ import ( "fmt" "path/filepath" "time" - - "github.com/tendermint/tendermint/types" // TODO: remove ) // Config defines the top level configuration for a Tendermint node @@ -320,10 +318,6 @@ type ConsensusConfig struct { CreateEmptyBlocks bool `mapstructure:"create_empty_blocks"` CreateEmptyBlocksInterval int `mapstructure:"create_empty_blocks_interval"` - // TODO: This probably shouldn't be exposed but it makes it - // easy to write tests for the wal/replay - BlockPartSize int `mapstructure:"block_part_size"` - // Reactor sleep duration parameters are in ms PeerGossipSleepDuration int `mapstructure:"peer_gossip_sleep_duration"` PeerQueryMaj23SleepDuration int `mapstructure:"peer_query_maj23_sleep_duration"` @@ -386,7 +380,6 @@ func DefaultConsensusConfig() *ConsensusConfig { MaxBlockSizeBytes: 1, // TODO CreateEmptyBlocks: true, CreateEmptyBlocksInterval: 0, - BlockPartSize: types.DefaultBlockPartSize, // TODO: we shouldnt be importing types PeerGossipSleepDuration: 100, PeerQueryMaj23SleepDuration: 2000, } diff --git a/config/toml.go b/config/toml.go index 999a05946..5dcbe5332 100644 --- a/config/toml.go +++ b/config/toml.go @@ -119,7 +119,7 @@ var testGenesis = `{ "type": "ed25519", "data":"3B3069C422E19688B45CBFAE7BB009FC0FA1B1EA86593519318B7214853803C8" }, - "amount": 10, + "power": 10, "name": "" } ], diff --git a/consensus/common_test.go b/consensus/common_test.go index 84f47d021..6a05b74e3 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -400,7 +400,7 @@ func randGenesisDoc(numValidators int, randPower bool, minPower int64) (*types.G val, privVal := types.RandValidator(randPower, minPower) validators[i] = types.GenesisValidator{ PubKey: val.PubKey, - Amount: val.VotingPower, + Power: val.VotingPower, } privValidators[i] = privVal } diff --git a/consensus/replay_test.go b/consensus/replay_test.go index 78cdaf7bc..03ca6c8d7 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -267,8 +267,6 @@ func testReplayCrashBeforeWriteVote(t *testing.T, thisCase *testCase, lineNum in var ( NUM_BLOCKS = 6 // number of blocks in the test_data/many_blocks.cswal mempool = types.MockMempool{} - - testPartSize int ) //--------------------------------------- @@ -320,7 +318,6 @@ func testHandshakeReplay(t *testing.T, nBlocks int, mode uint) { config.Consensus.SetWalFile(walFile) privVal := types.LoadPrivValidator(config.PrivValidatorFile()) - testPartSize = config.Consensus.BlockPartSize wal, err := NewWAL(walFile, false) if err != nil { @@ -384,6 +381,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 err := st.ApplyBlock(nil, proxyApp.Consensus(), blk, blk.MakePartSet(testPartSize).Header(), mempool) if err != nil { panic(err) @@ -503,7 +501,7 @@ func makeBlockchainFromWAL(wal *WAL) ([]*types.Block, []*types.Commit, error) { // if its not the first one, we have a full block if blockParts != nil { var n int - block := wire.ReadBinary(&types.Block{}, blockParts.GetReader(), types.MaxBlockSize, &n, &err).(*types.Block) + block := wire.ReadBinary(&types.Block{}, blockParts.GetReader(), 0, &n, &err).(*types.Block) blocks = append(blocks, block) } blockParts = types.NewPartSetFromHeader(*p) @@ -524,7 +522,7 @@ func makeBlockchainFromWAL(wal *WAL) ([]*types.Block, []*types.Commit, error) { } // grab the last block too var n int - block := wire.ReadBinary(&types.Block{}, blockParts.GetReader(), types.MaxBlockSize, &n, &err).(*types.Block) + block := wire.ReadBinary(&types.Block{}, blockParts.GetReader(), 0, &n, &err).(*types.Block) blocks = append(blocks, block) return blocks, commits, nil } @@ -563,7 +561,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) + store := NewMockBlockStore(config, state.Params()) return state, store } @@ -572,13 +570,14 @@ func stateAndStore(config *cfg.Config, pubKey crypto.PubKey) (*sm.State, *mockBl type mockBlockStore struct { config *cfg.Config + params types.ConsensusParams chain []*types.Block commits []*types.Commit } // TODO: NewBlockStore(db.NewMemDB) ... -func NewMockBlockStore(config *cfg.Config) *mockBlockStore { - return &mockBlockStore{config, nil, nil} +func NewMockBlockStore(config *cfg.Config, params types.ConsensusParams) *mockBlockStore { + return &mockBlockStore{config, params, nil, nil} } func (bs *mockBlockStore) Height() int { return len(bs.chain) } @@ -586,7 +585,7 @@ func (bs *mockBlockStore) LoadBlock(height int) *types.Block { return bs.chain[h func (bs *mockBlockStore) LoadBlockMeta(height int) *types.BlockMeta { block := bs.chain[height-1] return &types.BlockMeta{ - BlockID: types.BlockID{block.Hash(), block.MakePartSet(bs.config.Consensus.BlockPartSize).Header()}, + BlockID: types.BlockID{block.Hash(), block.MakePartSet(bs.params.BlockPartSizeBytes).Header()}, Header: block.Header, } } diff --git a/consensus/state.go b/consensus/state.go index 2523aac60..ac1656209 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -983,7 +983,8 @@ func (cs *ConsensusState) createProposalBlock() (block *types.Block, blockParts txs := cs.mempool.Reap(cs.config.MaxBlockSizeTxs) return types.MakeBlock(cs.Height, cs.state.ChainID, txs, commit, - cs.state.LastBlockID, cs.state.Validators.Hash(), cs.state.AppHash, cs.config.BlockPartSize) + cs.state.LastBlockID, cs.state.Validators.Hash(), + cs.state.AppHash, cs.state.Params().BlockPartSizeBytes) } // Enter: `timeoutPropose` after entering Propose. @@ -1417,7 +1418,8 @@ func (cs *ConsensusState) addProposalBlockPart(height int, part *types.Part, ver // Added and completed! var n int var err error - cs.ProposalBlock = wire.ReadBinary(&types.Block{}, cs.ProposalBlockParts.GetReader(), types.MaxBlockSize, &n, &err).(*types.Block) + cs.ProposalBlock = wire.ReadBinary(&types.Block{}, cs.ProposalBlockParts.GetReader(), + cs.state.Params().BlockSizeParams.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 == RoundStepPropose && cs.isProposalComplete() { diff --git a/consensus/state_test.go b/consensus/state_test.go index 81ef016be..9ae052033 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -180,7 +180,7 @@ func TestBadProposal(t *testing.T) { height, round := cs1.Height, cs1.Round vs2 := vss[1] - partSize := config.Consensus.BlockPartSize + partSize := cs1.state.Params().BlockPartSizeBytes proposalCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringCompleteProposal(), 1) voteCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringVote(), 1) @@ -327,7 +327,7 @@ func TestLockNoPOL(t *testing.T) { vs2 := vss[1] height := cs1.Height - partSize := config.Consensus.BlockPartSize + partSize := cs1.state.Params().BlockPartSizeBytes timeoutProposeCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutPropose(), 1) timeoutWaitCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutWait(), 1) @@ -493,7 +493,7 @@ func TestLockPOLRelock(t *testing.T) { cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] - partSize := config.Consensus.BlockPartSize + partSize := cs1.state.Params().BlockPartSizeBytes timeoutProposeCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutPropose(), 1) timeoutWaitCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutWait(), 1) @@ -608,7 +608,7 @@ func TestLockPOLUnlock(t *testing.T) { cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] - partSize := config.Consensus.BlockPartSize + partSize := cs1.state.Params().BlockPartSizeBytes proposalCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringCompleteProposal(), 1) timeoutProposeCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutPropose(), 1) @@ -703,7 +703,7 @@ func TestLockPOLSafety1(t *testing.T) { cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] - partSize := config.Consensus.BlockPartSize + partSize := cs1.state.Params().BlockPartSizeBytes proposalCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringCompleteProposal(), 1) timeoutProposeCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutPropose(), 1) @@ -824,7 +824,7 @@ func TestLockPOLSafety2(t *testing.T) { cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] - partSize := config.Consensus.BlockPartSize + partSize := cs1.state.Params().BlockPartSizeBytes proposalCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringCompleteProposal(), 1) timeoutProposeCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutPropose(), 1) @@ -999,7 +999,7 @@ func TestHalt1(t *testing.T) { cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] - partSize := config.Consensus.BlockPartSize + partSize := cs1.state.Params().BlockPartSizeBytes proposalCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringCompleteProposal(), 1) timeoutWaitCh := subscribeToEvent(cs1.evsw, "tester", types.EventStringTimeoutWait(), 1) diff --git a/docs/architecture/adr-005-consensus-params.md b/docs/architecture/adr-005-consensus-params.md new file mode 100644 index 000000000..678de42eb --- /dev/null +++ b/docs/architecture/adr-005-consensus-params.md @@ -0,0 +1,85 @@ +# ADR 005: Consensus Params + +## Context + +Consensus critical parameters controlling blockchain capacity have until now been hard coded, loaded from a local config, or neglected. +Since they may be need to be different in different networks, and potentially to evolve over time within +networks, we seek to initialize them in a genesis file, and expose them through the ABCI. + +While we have some specific parameters now, like maximum block and transaction size, we expect to have more in the future, +such as a period over which evidence is valid, or the frequency of checkpoints. + +## Decision + +### ConsensusParams + +No consensus critical parameters should ever be found in the `config.toml`. + +A new `ConsensusParams` is optionally included in the `genesis.json` file, +and loaded into the `State`. Any items not included are set to their default value. +A value of 0 is undefined (see ABCI, below). A value of -1 is used to indicate the parameter does not apply. +The parameters are used to determine the validity of a block (and tx) via the union of all relevant parameters. + +``` +type ConsensusParams struct { + BlockSizeParams + TxSizeParams + BlockGossipParams +} + +type BlockSizeParams struct { + MaxBytes int + MaxTxs int + MaxGas int +} + +type TxSizeParams struct { + MaxBytes int + MaxGas int +} + +type BlockGossipParams 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 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 + +#### InitChain + +InitChain currently takes the initial validator set. It should be extended to also take parts of the ConsensusParams. +There is some case to be made for it to take the entire Genesis, except there may be things in the genesis, +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. +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. +Tendermint should have hard-coded upper limits as sanity checks. + +## Status + +Proposed. + +## Consequences + +### Positive + +- Alternative capacity limits and consensus parameters can be specified without re-compiling the software. +- They can also change over time under the control of the application + +### Negative + +- More exposed parameters is more complexity +- Different rules at different heights in the blockchain complicates fast sync + +### Neutral + +- The TxSizeParams, which checks validity, may be in conflict with the config's `max_block_size_tx`, which determines proposal sizes diff --git a/docs/specification/genesis.rst b/docs/specification/genesis.rst index 32dd19632..2187eb572 100644 --- a/docs/specification/genesis.rst +++ b/docs/specification/genesis.rst @@ -21,7 +21,7 @@ Fields - ``validators``: - ``pub_key``: The first element specifies the pub\_key type. 1 == Ed25519. The second element are the pubkey bytes. -- ``amount``: The validator's voting power. +- ``power``: The validator's voting power. - ``name``: Name of the validator (optional). - ``app_hash``: The expected application hash (as returned by the ``Commit`` ABCI message) upon genesis. If the app's hash does not @@ -41,7 +41,7 @@ Sample genesis.json 1, "9BC5112CB9614D91CE423FA8744885126CD9D08D9FC9D1F42E552D662BAA411E" ], - "amount": 1, + "power": 1, "name": "mach1" }, { @@ -49,7 +49,7 @@ Sample genesis.json 1, "F46A5543D51F31660D9F59653B4F96061A740FF7433E0DC1ECBC30BE8494DE06" ], - "amount": 1, + "power": 1, "name": "mach2" }, { @@ -57,7 +57,7 @@ Sample genesis.json 1, "0E7B423C1635FD07C0FC3603B736D5D27953C1C6CA865BB9392CD79DE1A682BB" ], - "amount": 1, + "power": 1, "name": "mach3" }, { @@ -65,7 +65,7 @@ Sample genesis.json 1, "4F49237B9A32EB50682EDD83C48CE9CDB1D02A7CFDADCFF6EC8C1FAADB358879" ], - "amount": 1, + "power": 1, "name": "mach4" } ], diff --git a/docs/using-tendermint.rst b/docs/using-tendermint.rst index fb9882f6b..1cb3ad86e 100644 --- a/docs/using-tendermint.rst +++ b/docs/using-tendermint.rst @@ -177,7 +177,7 @@ When ``tendermint init`` is run, both a ``genesis.json`` and "genesis_time": "0001-01-01T00:00:00.000Z", "validators": [ { - "amount": 10, + "power": 10, "name": "", "pub_key": [ 1, @@ -310,7 +310,7 @@ then the new ``genesis.json`` will be: "genesis_time": "0001-01-01T00:00:00.000Z", "validators": [ { - "amount": 10, + "power": 10, "name": "", "pub_key": [ 1, @@ -318,7 +318,7 @@ then the new ``genesis.json`` will be: ] }, { - "amount": 10, + "power": 10, "name": "", "pub_key": [ 1, diff --git a/rpc/client/rpc_test.go b/rpc/client/rpc_test.go index fee92b4a9..33cd4b432 100644 --- a/rpc/client/rpc_test.go +++ b/rpc/client/rpc_test.go @@ -93,7 +93,7 @@ func TestGenesisAndValidators(t *testing.T) { val := vals.Validators[0] // make sure the current set is also the genesis set - assert.Equal(t, gval.Amount, val.VotingPower) + assert.Equal(t, gval.Power, val.VotingPower) assert.Equal(t, gval.PubKey, val.PubKey) } } diff --git a/rpc/core/net.go b/rpc/core/net.go index c6749941c..b3f1c7ce5 100644 --- a/rpc/core/net.go +++ b/rpc/core/net.go @@ -90,7 +90,7 @@ func UnsafeDialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { // "validators": [ // { // "name": "", -// "amount": 10, +// "power": 10, // "pub_key": { // "data": "68DFDA7E50F82946E7E8546BED37944A422CD1B831E70DF66BA3B8430593944D", // "type": "ed25519" diff --git a/state/state.go b/state/state.go index 7e22e04d1..2ebbec10e 100644 --- a/state/state.go +++ b/state/state.go @@ -65,6 +65,19 @@ type State struct { logger log.Logger } +// GetState loads the most recent state from the database, +// or creates a new one from the given genesisFile and persists the result +// to the database. +func GetState(stateDB dbm.DB, genesisFile string) *State { + state := LoadState(stateDB) + if state == nil { + state = MakeGenesisStateFromFile(stateDB, genesisFile) + state.Save() + } + + return state +} + // LoadState loads the State from the database. func LoadState(db dbm.DB) *State { return loadState(db, stateKey) @@ -248,17 +261,12 @@ func (s *State) GetValidators() (*types.ValidatorSet, *types.ValidatorSet) { return s.LastValidators, s.Validators } -// GetState loads the most recent state from the database, -// or creates a new one from the given genesisFile and persists the result -// to the database. -func GetState(stateDB dbm.DB, genesisFile string) *State { - state := LoadState(stateDB) - if state == nil { - state = MakeGenesisStateFromFile(stateDB, genesisFile) - state.Save() - } - - return state +// Params returns the consensus parameters used for +// validating blocks +func (s *State) Params() types.ConsensusParams { + // TODO: this should move into the State proper + // when we allow the app to change it + return *s.GenesisDoc.ConsensusParams } //------------------------------------------------------------------------ @@ -324,12 +332,9 @@ func MakeGenesisStateFromFile(db dbm.DB, genDocFile string) *State { // // Used in tests. func MakeGenesisState(db dbm.DB, genDoc *types.GenesisDoc) *State { - if len(genDoc.Validators) == 0 { - cmn.Exit(cmn.Fmt("The genesis file has no validators")) - } - - if genDoc.GenesisTime.IsZero() { - genDoc.GenesisTime = time.Now() + err := genDoc.ValidateAndComplete() + if err != nil { + cmn.Exit(cmn.Fmt("Error in genesis file: %v", err)) } // Make validators slice @@ -342,7 +347,7 @@ func MakeGenesisState(db dbm.DB, genDoc *types.GenesisDoc) *State { validators[i] = &types.Validator{ Address: address, PubKey: pubKey, - VotingPower: val.Amount, + VotingPower: val.Power, } } diff --git a/test/p2p/data/mach1/core/genesis.json b/test/p2p/data/mach1/core/genesis.json index 522f9831a..8d5c6c7b9 100644 --- a/test/p2p/data/mach1/core/genesis.json +++ b/test/p2p/data/mach1/core/genesis.json @@ -4,7 +4,7 @@ "genesis_time": "2016-06-24T20:01:19.322Z", "validators": [ { - "amount": 1, + "power": 1, "name": "mach1", "pub_key": { "type": "ed25519", @@ -12,7 +12,7 @@ } }, { - "amount": 1, + "power": 1, "name": "mach2", "pub_key": { "type": "ed25519", @@ -20,7 +20,7 @@ } }, { - "amount": 1, + "power": 1, "name": "mach3", "pub_key": { "type": "ed25519", @@ -28,7 +28,7 @@ } }, { - "amount": 1, + "power": 1, "name": "mach4", "pub_key": { "type": "ed25519", diff --git a/test/p2p/data/mach2/core/genesis.json b/test/p2p/data/mach2/core/genesis.json index 522f9831a..8d5c6c7b9 100644 --- a/test/p2p/data/mach2/core/genesis.json +++ b/test/p2p/data/mach2/core/genesis.json @@ -4,7 +4,7 @@ "genesis_time": "2016-06-24T20:01:19.322Z", "validators": [ { - "amount": 1, + "power": 1, "name": "mach1", "pub_key": { "type": "ed25519", @@ -12,7 +12,7 @@ } }, { - "amount": 1, + "power": 1, "name": "mach2", "pub_key": { "type": "ed25519", @@ -20,7 +20,7 @@ } }, { - "amount": 1, + "power": 1, "name": "mach3", "pub_key": { "type": "ed25519", @@ -28,7 +28,7 @@ } }, { - "amount": 1, + "power": 1, "name": "mach4", "pub_key": { "type": "ed25519", diff --git a/test/p2p/data/mach3/core/genesis.json b/test/p2p/data/mach3/core/genesis.json index 522f9831a..8d5c6c7b9 100644 --- a/test/p2p/data/mach3/core/genesis.json +++ b/test/p2p/data/mach3/core/genesis.json @@ -4,7 +4,7 @@ "genesis_time": "2016-06-24T20:01:19.322Z", "validators": [ { - "amount": 1, + "power": 1, "name": "mach1", "pub_key": { "type": "ed25519", @@ -12,7 +12,7 @@ } }, { - "amount": 1, + "power": 1, "name": "mach2", "pub_key": { "type": "ed25519", @@ -20,7 +20,7 @@ } }, { - "amount": 1, + "power": 1, "name": "mach3", "pub_key": { "type": "ed25519", @@ -28,7 +28,7 @@ } }, { - "amount": 1, + "power": 1, "name": "mach4", "pub_key": { "type": "ed25519", diff --git a/test/p2p/data/mach4/core/genesis.json b/test/p2p/data/mach4/core/genesis.json index 522f9831a..8d5c6c7b9 100644 --- a/test/p2p/data/mach4/core/genesis.json +++ b/test/p2p/data/mach4/core/genesis.json @@ -4,7 +4,7 @@ "genesis_time": "2016-06-24T20:01:19.322Z", "validators": [ { - "amount": 1, + "power": 1, "name": "mach1", "pub_key": { "type": "ed25519", @@ -12,7 +12,7 @@ } }, { - "amount": 1, + "power": 1, "name": "mach2", "pub_key": { "type": "ed25519", @@ -20,7 +20,7 @@ } }, { - "amount": 1, + "power": 1, "name": "mach3", "pub_key": { "type": "ed25519", @@ -28,7 +28,7 @@ } }, { - "amount": 1, + "power": 1, "name": "mach4", "pub_key": { "type": "ed25519", diff --git a/types/block.go b/types/block.go index fee62e98c..c8cdf81a9 100644 --- a/types/block.go +++ b/types/block.go @@ -14,11 +14,6 @@ import ( "github.com/tendermint/tmlibs/merkle" ) -const ( - MaxBlockSize = 22020096 // 21MB TODO make it configurable - DefaultBlockPartSize = 65536 // 64kB TODO: put part size in parts header? -) - // Block defines the atomic unit of a Tendermint blockchain type Block struct { *Header `json:"header"` diff --git a/types/genesis.go b/types/genesis.go index 23b14c9e9..f1b2736f8 100644 --- a/types/genesis.go +++ b/types/genesis.go @@ -12,27 +12,23 @@ import ( cmn "github.com/tendermint/tmlibs/common" ) -//------------------------------------------------------------ -// we store the gendoc in the db - -var GenDocKey = []byte("GenDocKey") - //------------------------------------------------------------ // core types for a genesis definition // GenesisValidator is an initial validator. type GenesisValidator struct { PubKey crypto.PubKey `json:"pub_key"` - Amount int64 `json:"amount"` + Power int64 `json:"power"` Name string `json:"name"` } // GenesisDoc defines the initial conditions for a tendermint blockchain, in particular its validator set. type GenesisDoc struct { - GenesisTime time.Time `json:"genesis_time"` - ChainID string `json:"chain_id"` - Validators []GenesisValidator `json:"validators"` - AppHash data.Bytes `json:"app_hash"` + GenesisTime time.Time `json:"genesis_time"` + ChainID string `json:"chain_id"` + ConsensusParams *ConsensusParams `json:"consensus_params,omitempty"` + Validators []GenesisValidator `json:"validators"` + AppHash data.Bytes `json:"app_hash"` } // SaveAs is a utility method for saving GenensisDoc as a JSON file. @@ -48,12 +44,45 @@ func (genDoc *GenesisDoc) SaveAs(file string) error { func (genDoc *GenesisDoc) ValidatorHash() []byte { vals := make([]*Validator, len(genDoc.Validators)) for i, v := range genDoc.Validators { - vals[i] = NewValidator(v.PubKey, v.Amount) + vals[i] = NewValidator(v.PubKey, v.Power) } vset := NewValidatorSet(vals) return vset.Hash() } +// ValidateAndComplete checks that all necessary fields are present +// and fills in defaults for optional fields left empty +func (genDoc *GenesisDoc) ValidateAndComplete() error { + + if genDoc.ChainID == "" { + return errors.Errorf("Genesis doc must include non-empty chain_id") + } + + if genDoc.ConsensusParams == nil { + genDoc.ConsensusParams = DefaultConsensusParams() + } else { + if err := genDoc.ConsensusParams.Validate(); err != nil { + return err + } + } + + if len(genDoc.Validators) == 0 { + return errors.Errorf("The genesis file must have at least one validator") + } + + for _, v := range genDoc.Validators { + if v.Power == 0 { + return errors.Errorf("The genesis file cannot contain validators with no voting power: %v", v) + } + } + + if genDoc.GenesisTime.IsZero() { + genDoc.GenesisTime = time.Now() + } + + return nil +} + //------------------------------------------------------------ // Make genesis state from file @@ -61,6 +90,14 @@ func (genDoc *GenesisDoc) ValidatorHash() []byte { func GenesisDocFromJSON(jsonBlob []byte) (*GenesisDoc, error) { genDoc := GenesisDoc{} err := json.Unmarshal(jsonBlob, &genDoc) + if err != nil { + return nil, err + } + + if err := genDoc.ValidateAndComplete(); err != nil { + return nil, err + } + return &genDoc, err } @@ -72,10 +109,7 @@ func GenesisDocFromFile(genDocFile string) (*GenesisDoc, error) { } genDoc, err := GenesisDocFromJSON(jsonBlob) if err != nil { - return nil, errors.Wrap(err, "Error reading GenesisDoc") - } - if genDoc.ChainID == "" { - return nil, errors.Errorf("Genesis doc %v must include non-empty chain_id", genDocFile) + return nil, errors.Wrap(err, cmn.Fmt("Error reading GenesisDoc at %v", genDocFile)) } return genDoc, nil } diff --git a/types/genesis_test.go b/types/genesis_test.go new file mode 100644 index 000000000..fe997b690 --- /dev/null +++ b/types/genesis_test.go @@ -0,0 +1,89 @@ +package types + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + crypto "github.com/tendermint/go-crypto" +) + +func TestGenesis(t *testing.T) { + + // test some bad ones from raw json + testCases := [][]byte{ + []byte{}, // empty + []byte{1, 1, 1, 1, 1}, // junk + []byte(`{}`), // empty + []byte(`{"chain_id": "mychain"}`), // missing validators + []byte(`{"chain_id": "mychain", "validators": []`), // missing validators + []byte(`{"chain_id": "mychain", "validators": [{}]`), // missing validators + []byte(`{"validators":[{"pub_key": + {"type":"ed25519","data":"961EAB8752E51A03618502F55C2B6E09C38C65635C64CCF3173ED452CF86C957"}, + "power":10,"name":""}]}`), // missing chain_id + } + + for _, testCase := range testCases { + _, err := GenesisDocFromJSON(testCase) + assert.Error(t, err, "expected error for empty genDoc json") + } + + // test a good one by raw json + genDocBytes := []byte(`{"genesis_time":"0001-01-01T00:00:00Z","chain_id":"test-chain-QDKdJr","consensus_params":null,"validators":[{"pub_key":{"type":"ed25519","data":"961EAB8752E51A03618502F55C2B6E09C38C65635C64CCF3173ED452CF86C957"},"power":10,"name":""}],"app_hash":""}`) + _, err := GenesisDocFromJSON(genDocBytes) + assert.NoError(t, err, "expected no error for good genDoc json") + + // create a base gendoc from struct + baseGenDoc := &GenesisDoc{ + ChainID: "abc", + Validators: []GenesisValidator{{crypto.GenPrivKeyEd25519().PubKey(), 10, "myval"}}, + } + genDocBytes, err = json.Marshal(baseGenDoc) + assert.NoError(t, err, "error marshalling genDoc") + + // test base gendoc and check consensus params were filled + genDoc, err := GenesisDocFromJSON(genDocBytes) + assert.NoError(t, err, "expected no error for valid genDoc json") + assert.NotNil(t, genDoc.ConsensusParams, "expected consensus params to be filled in") + + // create json with consensus params filled + genDocBytes, err = json.Marshal(genDoc) + assert.NoError(t, err, "error marshalling genDoc") + genDoc, err = GenesisDocFromJSON(genDocBytes) + assert.NoError(t, err, "expected no error for valid genDoc json") + + // test with invalid consensus params + genDoc.ConsensusParams.BlockSizeParams.MaxBytes = 0 + genDocBytes, err = json.Marshal(genDoc) + assert.NoError(t, err, "error marshalling genDoc") + genDoc, err = GenesisDocFromJSON(genDocBytes) + assert.Error(t, err, "expected error for genDoc json with block size of 0") +} + +func newConsensusParams(blockSize, partSize int) ConsensusParams { + return ConsensusParams{ + BlockSizeParams: BlockSizeParams{MaxBytes: blockSize}, + BlockGossipParams: BlockGossipParams{BlockPartSizeBytes: partSize}, + } + +} + +func TestConsensusParams(t *testing.T) { + + testCases := []struct { + params ConsensusParams + valid bool + }{ + {newConsensusParams(1, 1), true}, + {newConsensusParams(1, 0), false}, + {newConsensusParams(0, 1), false}, + {newConsensusParams(0, 0), false}, + } + for _, testCase := range testCases { + if testCase.valid { + assert.NoError(t, testCase.params.Validate(), "expected no error for valid params") + } else { + assert.Error(t, testCase.params.Validate(), "expected error for non valid params") + } + } +} diff --git a/types/params.go b/types/params.go new file mode 100644 index 000000000..495d1fd48 --- /dev/null +++ b/types/params.go @@ -0,0 +1,87 @@ +package types + +import ( + "github.com/pkg/errors" +) + +const ( + maxBlockSizeBytes = 104857600 // 100MB +) + +// 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"` +} + +// BlockSizeParams contain limits on the block size. +type BlockSizeParams struct { + MaxBytes int `json:"max_bytes"` // NOTE: must not be 0 + MaxTxs int `json:"max_txs"` + MaxGas int `json:"max_gas"` +} + +// TxSizeParams contain limits on the tx size. +type TxSizeParams struct { + MaxBytes int `json:"max_bytes"` + MaxGas int `json:"max_gas"` +} + +// BlockGossipParams determine consensus critical elements of how blocks are gossiped +type BlockGossipParams 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(), + } +} + +// DefaultBlockSizeParams returns a default BlockSizeParams. +func DefaultBlockSizeParams() BlockSizeParams { + return BlockSizeParams{ + MaxBytes: 22020096, // 21MB + MaxTxs: 100000, + MaxGas: -1, + } +} + +// DefaultTxSizeParams returns a default TxSizeParams. +func DefaultTxSizeParams() TxSizeParams { + return TxSizeParams{ + MaxBytes: 10240, // 10kB + MaxGas: -1, + } +} + +// DefaultBlockGossipParams returns a default BlockGossipParams. +func DefaultBlockGossipParams() BlockGossipParams { + return BlockGossipParams{ + BlockPartSizeBytes: 65536, // 64kB, + } +} + +// Validate validates the ConsensusParams to ensure all values +// 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.BlockGossipParams.BlockPartSizeBytes <= 0 { + return errors.Errorf("BlockGossipParams.BlockPartSizeBytes must be greater than 0. Got %d", params.BlockGossipParams.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) + } + return nil +} diff --git a/types/priv_validator.go b/types/priv_validator.go index b96e988db..3e84e7f36 100644 --- a/types/priv_validator.go +++ b/types/priv_validator.go @@ -15,6 +15,7 @@ import ( "github.com/tendermint/tmlibs/log" ) +// TODO: type ? const ( stepNone = 0 // Used to distinguish the initial state stepPropose = 1