diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c233a264..0be66e3d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ ## 0.10.3 (TBD) FEATURES: -- New `--consensus.no_empty_blocks` flag prevents the creation of blocks until there are transactions available +- New `--consensus.create_empty_blocks` flag; when set to false, only creates blocks when there are txs or when the AppHash changes +- New `consensus.create_empty_blocks_interval` config option; when greater than 0, will create an empty block after waiting that many seconds ## 0.10.2 (July 10, 2017) diff --git a/cmd/tendermint/commands/run_node.go b/cmd/tendermint/commands/run_node.go index b9df28ab8..e92911da2 100644 --- a/cmd/tendermint/commands/run_node.go +++ b/cmd/tendermint/commands/run_node.go @@ -47,7 +47,7 @@ func AddNodeFlags(cmd *cobra.Command) { cmd.Flags().Bool("p2p.pex", config.P2P.PexReactor, "Enable Peer-Exchange (dev feature)") // consensus flags - cmd.Flags().Bool("consensus.no_empty_blocks", config.Consensus.NoEmptyBlocks, "Only produce blocks when there are txs and when the AppHash changes") + cmd.Flags().Bool("consensus.create_empty_blocks", config.Consensus.CreateEmptyBlocks, "Set this to false to only produce blocks when there are txs or when the AppHash changes") } // Users wishing to: diff --git a/config/config.go b/config/config.go index 323b36a25..a16fa7103 100644 --- a/config/config.go +++ b/config/config.go @@ -301,9 +301,12 @@ type ConsensusConfig struct { SkipTimeoutCommit bool `mapstructure:"skip_timeout_commit"` // BlockSize - MaxBlockSizeTxs int `mapstructure:"max_block_size_txs"` - MaxBlockSizeBytes int `mapstructure:"max_block_size_bytes"` - NoEmptyBlocks bool `mapstructure:"no_empty_blocks"` + MaxBlockSizeTxs int `mapstructure:"max_block_size_txs"` + MaxBlockSizeBytes int `mapstructure:"max_block_size_bytes"` + + // EmptyBlocks mode and possible interval between empty blocks in seconds + 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 @@ -314,6 +317,16 @@ type ConsensusConfig struct { PeerQueryMaj23SleepDuration int `mapstructure:"peer_query_maj23_sleep_duration"` } +// WaitForTxs returns true if the consensus should wait for transactions before entering the propose step +func (cfg *ConsensusConfig) WaitForTxs() bool { + return !cfg.CreateEmptyBlocks || cfg.CreateEmptyBlocksInterval > 0 +} + +// EmptyBlocks returns the amount of time to wait before proposing an empty block or starting the propose timer if there are no txs available +func (cfg *ConsensusConfig) EmptyBlocks() time.Duration { + return time.Duration(cfg.CreateEmptyBlocksInterval) * time.Second +} + // Propose returns the amount of time to wait for a proposal func (cfg *ConsensusConfig) Propose(round int) time.Duration { return time.Duration(cfg.TimeoutPropose+cfg.TimeoutProposeDelta*round) * time.Millisecond @@ -359,7 +372,8 @@ func DefaultConsensusConfig() *ConsensusConfig { SkipTimeoutCommit: false, MaxBlockSizeTxs: 10000, MaxBlockSizeBytes: 1, // TODO - NoEmptyBlocks: false, + CreateEmptyBlocks: true, + CreateEmptyBlocksInterval: 0, BlockPartSize: types.DefaultBlockPartSize, // TODO: we shouldnt be importing types PeerGossipSleepDuration: 100, PeerQueryMaj23SleepDuration: 2000, diff --git a/consensus/common_test.go b/consensus/common_test.go index 6b96ec311..8197d6d65 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -242,7 +242,7 @@ func newConsensusStateWithConfig(thisConfig *cfg.Config, state *sm.State, pv *ty // Make Mempool mempool := mempl.NewMempool(thisConfig.Mempool, proxyAppConnMem, 0) mempool.SetLogger(log.TestingLogger().With("module", "mempool")) - if thisConfig.Consensus.NoEmptyBlocks { + if thisConfig.Consensus.WaitForTxs() { mempool.EnableTxsAvailable() } diff --git a/consensus/mempool_test.go b/consensus/mempool_test.go index fdb4349ad..e4069b876 100644 --- a/consensus/mempool_test.go +++ b/consensus/mempool_test.go @@ -17,7 +17,7 @@ func init() { func TestNoProgressUntilTxsAvailable(t *testing.T) { config := ResetConfig("consensus_mempool_txs_available_test") - config.Consensus.NoEmptyBlocks = true + config.Consensus.CreateEmptyBlocks = false state, privVals := randGenesisState(1, false, 10) cs := newConsensusStateWithConfig(config, state, privVals[0], NewCounterApplication()) cs.mempool.EnableTxsAvailable() @@ -36,7 +36,7 @@ func TestNoProgressUntilTxsAvailable(t *testing.T) { func TestProgressInHigherRound(t *testing.T) { config := ResetConfig("consensus_mempool_txs_available_test") - config.Consensus.NoEmptyBlocks = true + config.Consensus.CreateEmptyBlocks = false state, privVals := randGenesisState(1, false, 10) cs := newConsensusStateWithConfig(config, state, privVals[0], NewCounterApplication()) cs.mempool.EnableTxsAvailable() diff --git a/consensus/state.go b/consensus/state.go index e3a153651..0a763f914 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -718,6 +718,8 @@ func (cs *ConsensusState) handleTimeout(ti timeoutInfo, rs RoundState) { // NewRound event fired from enterNewRound. // XXX: should we fire timeout here (for timeout commit)? cs.enterNewRound(ti.Height, 0) + case RoundStepNewRound: + cs.enterPropose(ti.Height, 0) case RoundStepPropose: types.FireEventTimeoutPropose(cs.evsw, cs.RoundStateEvent()) cs.enterPrevote(ti.Height, ti.Round) @@ -790,8 +792,11 @@ func (cs *ConsensusState) enterNewRound(height int, round int) { // Wait for txs to be available in the mempool // before we enterPropose in round 0. If the last block changed the app hash, // we may need an empty "proof" block, and enterPropose immediately. - waitForTxs := cs.config.NoEmptyBlocks && round == 0 && !cs.needProofBlock(height) + waitForTxs := cs.config.WaitForTxs() && round == 0 && !cs.needProofBlock(height) if waitForTxs { + if cs.config.CreateEmptyBlocksInterval > 0 { + cs.scheduleTimeout(cs.config.EmptyBlocks(), height, round, RoundStepNewRound) + } go cs.proposalHeartbeat(height, round) } else { cs.enterPropose(height, round) @@ -841,8 +846,9 @@ func (cs *ConsensusState) proposalHeartbeat(height, round int) { } } -// Enter (!NoEmptyBlocks): from enterNewRound(height,round) -// Enter (NoEmptyBlocks) : after enterNewRound(height,round), once txs are in the mempool +// Enter (CreateEmptyBlocks): from enterNewRound(height,round) +// Enter (CreateEmptyBlocks, CreateEmptyBlocksInterval > 0 ): after enterNewRound(height,round), after timeout of CreateEmptyBlocksInterval +// Enter (!CreateEmptyBlocks) : after enterNewRound(height,round), once txs are in the mempool func (cs *ConsensusState) enterPropose(height int, round int) { if cs.Height != height || round < cs.Round || (cs.Round == round && RoundStepPropose <= cs.Step) { cs.Logger.Debug(cmn.Fmt("enterPropose(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) diff --git a/node/node.go b/node/node.go index 0e8cc8fae..e55731f4f 100644 --- a/node/node.go +++ b/node/node.go @@ -142,7 +142,7 @@ func NewNode(config *cfg.Config, privValidator *types.PrivValidator, clientCreat mempoolReactor := mempl.NewMempoolReactor(config.Mempool, mempool) mempoolReactor.SetLogger(mempoolLogger) - if config.Consensus.NoEmptyBlocks { + if config.Consensus.WaitForTxs() { mempool.EnableTxsAvailable() }