diff --git a/.changelog/unreleased/features/2655-move-timeout-commit-into-finalize-block.md b/.changelog/unreleased/features/2655-move-timeout-commit-into-finalize-block.md new file mode 100644 index 0000000000..9598907142 --- /dev/null +++ b/.changelog/unreleased/features/2655-move-timeout-commit-into-finalize-block.md @@ -0,0 +1,2 @@ +- `[config]` Move `timeout_commit` into the ABCI `FinalizeBlockResponse` + ([\#2655](https://github.com/cometbft/cometbft/issues/2655)) diff --git a/abci/types/application.go b/abci/types/application.go index 4ccfd229eb..0a785a9c73 100644 --- a/abci/types/application.go +++ b/abci/types/application.go @@ -1,6 +1,8 @@ package types -import "context" +import ( + "context" +) //go:generate ../../scripts/mockery_generate.sh Application diff --git a/abci/types/types.pb.go b/abci/types/types.pb.go index 203aeeeb31..3ace0abcad 100644 --- a/abci/types/types.pb.go +++ b/abci/types/types.pb.go @@ -2748,6 +2748,9 @@ type ResponseFinalizeBlock struct { ConsensusParamUpdates *types1.ConsensusParams `protobuf:"bytes,4,opt,name=consensus_param_updates,json=consensusParamUpdates,proto3" json:"consensus_param_updates,omitempty"` // app_hash is the hash of the applications' state which is used to confirm that execution of the transactions was deterministic. It is up to the application to decide which algorithm to use. AppHash []byte `protobuf:"bytes,5,opt,name=app_hash,json=appHash,proto3" json:"app_hash,omitempty"` + // delay between the time when this block is committed and the next height is started. + // previously `timeout_commit` in config.toml + NextBlockDelay time.Duration `protobuf:"bytes,6,opt,name=next_block_delay,json=nextBlockDelay,proto3,stdduration" json:"next_block_delay"` } func (m *ResponseFinalizeBlock) Reset() { *m = ResponseFinalizeBlock{} } @@ -2818,6 +2821,14 @@ func (m *ResponseFinalizeBlock) GetAppHash() []byte { return nil } +func (m *ResponseFinalizeBlock) GetNextBlockDelay() time.Duration { + if m != nil { + return m.NextBlockDelay + } + return 0 +} + +// CommitInfo contains votes for the particular round. type CommitInfo struct { Round int32 `protobuf:"varint,1,opt,name=round,proto3" json:"round,omitempty"` Votes []VoteInfo `protobuf:"bytes,2,rep,name=votes,proto3" json:"votes"` @@ -6776,6 +6787,14 @@ func (m *ResponseFinalizeBlock) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + n49, err49 := github_com_cosmos_gogoproto_types.StdDurationMarshalTo(m.NextBlockDelay, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.NextBlockDelay):]) + if err49 != nil { + return 0, err49 + } + i -= n49 + i = encodeVarintTypes(dAtA, i, uint64(n49)) + i-- + dAtA[i] = 0x32 if len(m.AppHash) > 0 { i -= len(m.AppHash) copy(dAtA[i:], m.AppHash) @@ -8547,6 +8566,8 @@ func (m *ResponseFinalizeBlock) Size() (n int) { if l > 0 { n += 1 + l + sovTypes(uint64(l)) } + l = github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.NextBlockDelay) + n += 1 + l + sovTypes(uint64(l)) return n } @@ -14791,6 +14812,39 @@ func (m *ResponseFinalizeBlock) Unmarshal(dAtA []byte) error { m.AppHash = []byte{} } iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NextBlockDelay", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_cosmos_gogoproto_types.StdDurationUnmarshal(&m.NextBlockDelay, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) diff --git a/config/config.go b/config/config.go index 5ec0c44f9a..376a08df3a 100644 --- a/config/config.go +++ b/config/config.go @@ -158,9 +158,12 @@ func (cfg *Config) ValidateBasic() error { return nil } -// CheckDeprecated returns any deprecation warnings. These are printed to the operator on startup +// CheckDeprecated returns any deprecation warnings. These are printed to the operator on startup. func (cfg *Config) CheckDeprecated() []string { var warnings []string + if cfg.Consensus.TimeoutCommit != 0 { + warnings = append(warnings, "[consensus.timeout_commit] is deprecated. Use `next_block_delay` in the ABCI `FinalizeBlockResponse`.") + } return warnings } @@ -996,6 +999,7 @@ type ConsensusConfig struct { // height (this gives us a chance to receive some more precommits, even // though we already have +2/3). // NOTE: when modifying, make sure to update time_iota_ms genesis parameter + // Deprecated: use `next_block_delay` in the ABCI application's `FinalizeBlockResponse`. TimeoutCommit time.Duration `mapstructure:"timeout_commit"` // Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) @@ -1078,6 +1082,7 @@ func (cfg *ConsensusConfig) Precommit(round int32) time.Duration { // Commit returns the amount of time to wait for straggler votes after receiving +2/3 precommits // for a single block (ie. a commit). +// Deprecated: use `next_block_delay` in the ABCI application's `FinalizeBlockResponse`. func (cfg *ConsensusConfig) Commit(t time.Time) time.Time { return t.Add(cfg.TimeoutCommit) } diff --git a/consensus/reactor_test.go b/consensus/reactor_test.go index aed400188a..91b6663295 100644 --- a/consensus/reactor_test.go +++ b/consensus/reactor_test.go @@ -637,9 +637,9 @@ func TestReactorWithTimeoutCommit(t *testing.T) { N := 4 css, cleanup := randConsensusNet(t, N, "consensus_reactor_with_timeout_commit_test", newMockTickerFunc(false), newKVStore) defer cleanup() - // override default SkipTimeoutCommit == true for tests + // override default NextBlockDelay == 0 for tests for i := 0; i < N; i++ { - css[i].config.SkipTimeoutCommit = false + css[i].state.NextBlockDelay = 1 * time.Second } reactors, blocksSubs, eventBuses := startConsensusNet(t, css, N-1) diff --git a/consensus/state.go b/consensus/state.go index f657b2e7af..7d00114fa0 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -718,15 +718,20 @@ func (cs *State) updateToState(state sm.State) { cs.updateHeight(height) cs.updateRoundStep(0, cstypes.RoundStepNewHeight) + timeoutCommit := state.NextBlockDelay + // If the ABCI app didn't set a delay, use the deprecated config value. + if timeoutCommit == 0 { + timeoutCommit = cs.config.TimeoutCommit //nolint:staticcheck + } if cs.CommitTime.IsZero() { // "Now" makes it easier to sync up dev nodes. - // We add timeoutCommit to allow transactions - // to be gathered for the first block. - // And alternative solution that relies on clocks: - // cs.StartTime = state.LastBlockTime.Add(timeoutCommit) - cs.StartTime = cs.config.Commit(cmttime.Now()) + // + // We add timeoutCommit to allow transactions to be gathered for + // the first block. An alternative solution that relies on clocks: + // `cs.StartTime = state.LastBlockTime.Add(timeoutCommit)` + cs.StartTime = cmttime.Now().Add(timeoutCommit) } else { - cs.StartTime = cs.config.Commit(cs.CommitTime) + cs.StartTime = cs.CommitTime.Add(timeoutCommit) } cs.Validators = validators @@ -1042,7 +1047,7 @@ func (cs *State) handleTxsAvailable() { // Enter: `timeoutNewHeight` by startTime (commitTime+timeoutCommit), // -// or, if SkipTimeoutCommit==true, after receiving all precommits from (height,round-1) +// or, if NextBlockDelay==0, after receiving all precommits from (height,round-1) // // Enter: `timeoutPrecommits` after any +2/3 precommits from (height,round-1) // Enter: +2/3 precommits for nil at (height,round-1) @@ -2211,7 +2216,8 @@ func (cs *State) addVote(vote *types.Vote, peerID p2p.ID) (added bool, err error cs.evsw.FireEvent(types.EventVote, vote) // if we can skip timeoutCommit and have all the votes now, - if cs.config.SkipTimeoutCommit && cs.LastCommit.HasAll() { + skipTimeoutCommit := cs.state.NextBlockDelay == 0 && cs.config.TimeoutCommit == 0 //nolint:staticcheck + if skipTimeoutCommit && cs.LastCommit.HasAll() { // go straight to new round (skip timeout commit) // cs.scheduleTimeout(time.Duration(0), cs.Height, 0, cstypes.RoundStepNewHeight) cs.enterNewRound(cs.Height, 0) @@ -2369,7 +2375,8 @@ func (cs *State) addVote(vote *types.Vote, peerID p2p.ID) (added bool, err error if !blockID.IsNil() { cs.enterCommit(height, vote.Round) - if cs.config.SkipTimeoutCommit && precommits.HasAll() { + skipTimeoutCommit := cs.state.NextBlockDelay == 0 && cs.config.TimeoutCommit == 0 //nolint:staticcheck + if skipTimeoutCommit && precommits.HasAll() { cs.enterNewRound(cs.Height, 0) } } else { diff --git a/consensus/state_test.go b/consensus/state_test.go index 2cc9d8a8a8..ace0dc465d 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -2761,6 +2761,7 @@ func (n *fakeTxNotifier) Notify() { func TestStartNextHeightCorrectlyAfterTimeout(t *testing.T) { config.Consensus.SkipTimeoutCommit = false cs1, vss := randState(4) + cs1.state.NextBlockDelay = 10 * time.Millisecond cs1.txNotifier = &fakeTxNotifier{ch: make(chan struct{})} vs2, vs3, vs4 := vss[1], vss[2], vss[3] @@ -2825,6 +2826,7 @@ func TestResetTimeoutPrecommitUponNewHeight(t *testing.T) { config.Consensus.SkipTimeoutCommit = false cs1, vss := randState(4) + cs1.state.NextBlockDelay = 10 * time.Millisecond vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round diff --git a/docs/app-dev/indexing-transactions.md b/docs/app-dev/indexing-transactions.md index 395602fa63..6b333d7649 100644 --- a/docs/app-dev/indexing-transactions.md +++ b/docs/app-dev/indexing-transactions.md @@ -118,7 +118,6 @@ transferBalance200FinalizeBlock12 1 transferNodeNothingFinalizeBlock12 1 ``` - The event number is a local variable kept by the indexer and incremented when a new event is processed. It is an `int64` variable and has no other semantics besides being used to associate attributes belonging to the same events within a height. This variable is not atomically incremented as event indexing is deterministic. **Should this ever change**, the event id generation @@ -174,32 +173,30 @@ Example: ```go func (app *Application) FinalizeBlock(_ context.Context, req *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) { - - //... - tx_results[0] := &types.ExecTxResult{ - Code: CodeTypeOK, - // With every transaction we can emit a series of events. To make it simple, we just emit the same events. - Events: []types.Event{ - { - Type: "app", - Attributes: []types.EventAttribute{ - {Key: "creator", Value: "Cosmoshi Netowoko", Index: true}, - {Key: "key", Value: key, Index: true}, - {Key: "index_key", Value: "index is working", Index: true}, - {Key: "noindex_key", Value: "index is working", Index: false}, - }, - }, - { - Type: "app", - Attributes: []types.EventAttribute{ - {Key: "creator", Value: "Cosmoshi", Index: true}, - {Key: "key", Value: value, Index: true}, - {Key: "index_key", Value: "index is working", Index: true}, - {Key: "noindex_key", Value: "index is working", Index: false}, - }, - }, - }, - } + tx_results[0] := &types.ExecTxResult{ + Code: CodeTypeOK, + // With every transaction we can emit a series of events. To make it simple, we just emit the same events. + Events: []types.Event{ + { + Type: "app", + Attributes: []types.EventAttribute{ + {Key: "creator", Value: "Cosmoshi Netowoko", Index: true}, + {Key: "key", Value: key, Index: true}, + {Key: "index_key", Value: "index is working", Index: true}, + {Key: "noindex_key", Value: "index is working", Index: false}, + }, + }, + { + Type: "app", + Attributes: []types.EventAttribute{ + {Key: "creator", Value: "Cosmoshi", Index: true}, + {Key: "key", Value: value, Index: true}, + {Key: "index_key", Value: "index is working", Index: true}, + {Key: "noindex_key", Value: "index is working", Index: false}, + }, + }, + }, + } block_events = []types.Event{ { diff --git a/docs/guides/go-built-in.md b/docs/guides/go-built-in.md index 591665c6e2..aa355004ea 100644 --- a/docs/guides/go-built-in.md +++ b/docs/guides/go-built-in.md @@ -375,10 +375,13 @@ func (app *KVStoreApplication) FinalizeBlock(_ context.Context, req *abcitypes.R return &abcitypes.ResponseFinalizeBlock{ TxResults: txs, + NextBlockDelay: 1 * time.Second, }, nil } ``` +`NextBlockDelay` is a delay between the time when the current block is committed and the next height is started. Normally you don't need to change the default value (1s). Please refer to the [spec](../../spec/abci/abci++_methods.md#finalizeblock) for more information. + Transactions are not guaranteed to be valid when they are delivered to an application, even if they were valid when they were proposed. This can happen if the application state is used to determine transaction validity. diff --git a/docs/guides/go.md b/docs/guides/go.md index 9ea354658b..ecb5124978 100644 --- a/docs/guides/go.md +++ b/docs/guides/go.md @@ -379,6 +379,8 @@ func (app *KVStoreApplication) FinalizeBlock(_ context.Context, req *abcitypes.R } ``` +`NextBlockDelay` is a delay between the time when the current block is committed and the next height is started. Normally you don't need to change the default value (1s). Please refer to the [spec](../../spec/abci/abci++_methods.md#finalizeblock) for more information. + Transactions are not guaranteed to be valid when they are delivered to an application, even if they were valid when they were proposed. This can happen if the application state is used to determine transaction validity. The application state may have changed between the initial execution of `CheckTx` and the transaction delivery in `FinalizeBlock` in a way that rendered the transaction no longer valid. diff --git a/proto/tendermint/abci/types.proto b/proto/tendermint/abci/types.proto index 89bafb6cd5..8e0029f33f 100644 --- a/proto/tendermint/abci/types.proto +++ b/proto/tendermint/abci/types.proto @@ -11,6 +11,7 @@ import "tendermint/types/params.proto"; import "tendermint/types/validator.proto"; import "google/protobuf/timestamp.proto"; import "gogoproto/gogo.proto"; +import "google/protobuf/duration.proto"; // NOTE: When using custom types, mind the warnings. // https://github.com/cosmos/gogoproto/blob/master/custom_types.md#warnings-and-issues @@ -363,6 +364,12 @@ message ResponseFinalizeBlock { tendermint.types.ConsensusParams consensus_param_updates = 4; // app_hash is the hash of the applications' state which is used to confirm that execution of the transactions was deterministic. It is up to the application to decide which algorithm to use. bytes app_hash = 5; + // delay between the time when this block is committed and the next height is started. + // previously `timeout_commit` in config.toml + google.protobuf.Duration next_block_delay = 6 [ + (gogoproto.nullable) = false, + (gogoproto.stdduration) = true + ]; } //---------------------------------------- diff --git a/proto/tendermint/state/types.pb.go b/proto/tendermint/state/types.pb.go index 4426543ed9..6410bfb98b 100644 --- a/proto/tendermint/state/types.pb.go +++ b/proto/tendermint/state/types.pb.go @@ -12,6 +12,7 @@ import ( proto "github.com/cosmos/gogoproto/proto" _ "github.com/cosmos/gogoproto/types" github_com_cosmos_gogoproto_types "github.com/cosmos/gogoproto/types" + _ "github.com/golang/protobuf/ptypes/duration" io "io" math "math" math_bits "math/bits" @@ -445,6 +446,9 @@ type State struct { LastResultsHash []byte `protobuf:"bytes,12,opt,name=last_results_hash,json=lastResultsHash,proto3" json:"last_results_hash,omitempty"` // the latest AppHash we've received from calling abci.Commit() AppHash []byte `protobuf:"bytes,13,opt,name=app_hash,json=appHash,proto3" json:"app_hash,omitempty"` + // delay between the time when this block is committed and the next height is started. + // previously `timeout_commit` in config.toml + NextBlockDelay time.Duration `protobuf:"bytes,15,opt,name=next_block_delay,json=nextBlockDelay,proto3,stdduration" json:"next_block_delay"` } func (m *State) Reset() { *m = State{} } @@ -578,6 +582,13 @@ func (m *State) GetAppHash() []byte { return nil } +func (m *State) GetNextBlockDelay() time.Duration { + if m != nil { + return m.NextBlockDelay + } + return 0 +} + func init() { proto.RegisterType((*LegacyABCIResponses)(nil), "tendermint.state.LegacyABCIResponses") proto.RegisterType((*ResponseBeginBlock)(nil), "tendermint.state.ResponseBeginBlock") @@ -1007,6 +1018,14 @@ func (m *State) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + n9, err9 := github_com_cosmos_gogoproto_types.StdDurationMarshalTo(m.NextBlockDelay, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.NextBlockDelay):]) + if err9 != nil { + return 0, err9 + } + i -= n9 + i = encodeVarintTypes(dAtA, i, uint64(n9)) + i-- + dAtA[i] = 0x7a if m.InitialHeight != 0 { i = encodeVarintTypes(dAtA, i, uint64(m.InitialHeight)) i-- @@ -1082,12 +1101,12 @@ func (m *State) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x32 } - n13, err13 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(m.LastBlockTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(m.LastBlockTime):]) - if err13 != nil { - return 0, err13 + n14, err14 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(m.LastBlockTime, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(m.LastBlockTime):]) + if err14 != nil { + return 0, err14 } - i -= n13 - i = encodeVarintTypes(dAtA, i, uint64(n13)) + i -= n14 + i = encodeVarintTypes(dAtA, i, uint64(n14)) i-- dAtA[i] = 0x2a { @@ -1314,6 +1333,8 @@ func (m *State) Size() (n int) { if m.InitialHeight != 0 { n += 1 + sovTypes(uint64(m.InitialHeight)) } + l = github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.NextBlockDelay) + n += 1 + l + sovTypes(uint64(l)) return n } @@ -2625,6 +2646,39 @@ func (m *State) Unmarshal(dAtA []byte) error { break } } + case 15: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NextBlockDelay", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_cosmos_gogoproto_types.StdDurationUnmarshal(&m.NextBlockDelay, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) diff --git a/proto/tendermint/state/types.proto b/proto/tendermint/state/types.proto index c76c25fa85..8c580b8eb9 100644 --- a/proto/tendermint/state/types.proto +++ b/proto/tendermint/state/types.proto @@ -93,4 +93,11 @@ message State { // the latest AppHash we've received from calling abci.Commit() bytes app_hash = 13; + + // delay between the time when this block is committed and the next height is started. + // previously `timeout_commit` in config.toml + google.protobuf.Duration next_block_delay = 14 [ + (gogoproto.nullable) = false, + (gogoproto.stdduration) = true + ]; } diff --git a/spec/abci/abci++_methods.md b/spec/abci/abci++_methods.md index ba4c50d714..1ce02f02fc 100644 --- a/spec/abci/abci++_methods.md +++ b/spec/abci/abci++_methods.md @@ -614,13 +614,14 @@ message for round _r_, height _h_ from validator _q_ (_q_ ≠ _p_): * **Response**: - | Name | Type | Description | Field Number | Deterministic | - |-------------------------|---------------------------------------------------|----------------------------------------------------------------------------------|--------------|---------------| - | events | repeated [Event](abci++_basic_concepts.md#events) | Type & Key-Value events for indexing | 1 | No | - | tx_results | repeated [ExecTxResult](#exectxresult) | List of structures containing the data resulting from executing the transactions | 2 | Yes | - | validator_updates | repeated [ValidatorUpdate](#validatorupdate) | Changes to validator set (set voting power to 0 to remove). | 3 | Yes | - | consensus_param_updates | [ConsensusParams](#consensusparams) | Changes to gas, size, and other consensus-related parameters. | 4 | Yes | - | app_hash | bytes | The Merkle root hash of the application state. | 5 | Yes | + | Name | Type | Description | Field Number | Deterministic | + |-------------------------|---------------------------------------------------|-------------------------------------------------------------------------------------|--------------|---------------| + | events | repeated [Event](abci++_basic_concepts.md#events) | Type & Key-Value events for indexing | 1 | No | + | tx_results | repeated [ExecTxResult](#exectxresult) | List of structures containing the data resulting from executing the transactions | 2 | Yes | + | validator_updates | repeated [ValidatorUpdate](#validatorupdate) | Changes to validator set (set voting power to 0 to remove). | 3 | Yes | + | consensus_param_updates | [ConsensusParams](#consensusparams) | Changes to gas, size, and other consensus-related parameters. | 4 | Yes | + | app_hash | bytes | The Merkle root hash of the application state. | 5 | Yes | + | next_block_delay | [google.protobuf.Duration][protobuf-duration] | Delay between the time when this block is committed and the next height is started. | 6 | No | * **Usage**: * Contains the fields of the newly decided block. @@ -887,4 +888,5 @@ enum VerifyStatus { * If `Status` is `ACCEPT`, the consensus algorithm will accept the vote as valid. * If `Status` is `REJECT`, the consensus algorithm will reject the vote as invalid. -[protobuf-timestamp]: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp +[protobuf-timestamp]: https://protobuf.dev/reference/protobuf/google.protobuf/#timestamp +[protobuf-duration]: https://protobuf.dev/reference/protobuf/google.protobuf/#duration diff --git a/state/execution.go b/state/execution.go index 20e8928142..bec0ae38f6 100644 --- a/state/execution.go +++ b/state/execution.go @@ -3,6 +3,7 @@ package state import ( "bytes" "context" + "errors" "fmt" "time" @@ -282,6 +283,11 @@ func (blockExec *BlockExecutor) ApplyBlock( blockExec.metrics.ConsensusParamUpdates.Add(1) } + err = validateNextBlockDelay(abciResponse.NextBlockDelay) + if err != nil { + return state, fmt.Errorf("error in next block delay: %w", err) + } + // Update the state with the block and responses. state, err = updateState(state, blockID, &block.Header, abciResponse, validatorUpdates) if err != nil { @@ -581,6 +587,13 @@ func validateValidatorUpdates(abciUpdates []abci.ValidatorUpdate, return nil } +func validateNextBlockDelay(nextBlockDelay time.Duration) error { + if nextBlockDelay < 0 { + return errors.New("negative duration") + } + return nil +} + // updateState returns a new State updated according to the header and responses. func updateState( state State, @@ -649,6 +662,7 @@ func updateState( LastHeightConsensusParamsChanged: lastHeightParamsChanged, LastResultsHash: TxResultsHash(abciResponse.TxResults), AppHash: nil, + NextBlockDelay: abciResponse.NextBlockDelay, }, nil } diff --git a/state/state.go b/state/state.go index 15fb8e5e62..8f44cefd81 100644 --- a/state/state.go +++ b/state/state.go @@ -77,6 +77,10 @@ type State struct { // the latest AppHash we've received from calling abci.Commit() AppHash []byte + + // delay between the time when this block is committed and the next height is started. + // previously `timeout_commit` in config.toml + NextBlockDelay time.Duration } // Copy makes a copy of the State for mutating. @@ -102,6 +106,8 @@ func (state State) Copy() State { AppHash: state.AppHash, LastResultsHash: state.LastResultsHash, + + NextBlockDelay: state.NextBlockDelay, } } @@ -170,6 +176,7 @@ func (state *State) ToProto() (*cmtstate.State, error) { sm.LastHeightConsensusParamsChanged = state.LastHeightConsensusParamsChanged sm.LastResultsHash = state.LastResultsHash sm.AppHash = state.AppHash + sm.NextBlockDelay = state.NextBlockDelay return sm, nil } @@ -221,6 +228,7 @@ func FromProto(pb *cmtstate.State) (*State, error) { //nolint:golint state.LastHeightConsensusParamsChanged = pb.LastHeightConsensusParamsChanged state.LastResultsHash = pb.LastResultsHash state.AppHash = pb.AppHash + state.NextBlockDelay = pb.NextBlockDelay return state, nil } @@ -351,5 +359,8 @@ func MakeGenesisState(genDoc *types.GenesisDoc) (State, error) { LastHeightConsensusParamsChanged: genDoc.InitialHeight, AppHash: genDoc.AppHash, + + // NextBlockDelay is set to 0 because the genesis block is committed. + NextBlockDelay: 0, }, nil } diff --git a/test/e2e/app/app.go b/test/e2e/app/app.go index 34489a892d..d497d17a64 100644 --- a/test/e2e/app/app.go +++ b/test/e2e/app/app.go @@ -277,6 +277,7 @@ func (app *Application) FinalizeBlock(_ context.Context, req *abci.RequestFinali }, }, }, + NextBlockDelay: 1 * time.Second, }, nil }