From a84d857e17dbfe5c6778982eb24f81084fb439a7 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 21 May 2024 11:14:26 +0400 Subject: [PATCH] feat(abci): move `timeout_commit` into `FinalizeBlockResponse` (#3089) as `next_block_delay` ADR-115: https://github.com/cometbft/cometbft/pull/2966 Closes #2655 --- - [ ] ~~Tests written/updated~~ - [x] Changelog entry added in `.changelog` (we use [unclog](https://github.com/informalsystems/unclog) to manage our changelog) - [x] Updated relevant documentation (`docs/` or `spec/`) and code comments - [x] Title follows the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) spec --------- Co-authored-by: Sergio Mena --- ...move-timeout-commit-into-finalize-block.md | 2 + abci/types/application.go | 4 +- abci/types/types.pb.go | 54 ++++++++++++++++ config/config.go | 7 +- consensus/reactor_test.go | 4 +- consensus/state.go | 25 +++++--- consensus/state_test.go | 2 + docs/app-dev/indexing-transactions.md | 51 +++++++-------- docs/guides/go-built-in.md | 3 + docs/guides/go.md | 2 + proto/tendermint/abci/types.proto | 7 ++ proto/tendermint/state/types.pb.go | 64 +++++++++++++++++-- proto/tendermint/state/types.proto | 7 ++ spec/abci/abci++_methods.md | 18 +++--- state/execution.go | 14 ++++ state/state.go | 11 ++++ test/e2e/app/app.go | 1 + 17 files changed, 223 insertions(+), 53 deletions(-) create mode 100644 .changelog/unreleased/features/2655-move-timeout-commit-into-finalize-block.md 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 00000000000..95989071422 --- /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 4ccfd229ebc..0a785a9c737 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 203aeeeb313..3ace0abcad3 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 5ec0c44f9ac..376a08df3a3 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 aed400188ac..91b6663295a 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 f657b2e7afa..7d00114fa00 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 2cc9d8a8a84..ace0dc465d7 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 395602fa633..6b333d7649f 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 591665c6e25..aa355004ea9 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 9ea354658b4..ecb5124978a 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 89bafb6cd54..8e0029f33f7 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 4426543ed9b..6410bfb98b2 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 c76c25fa852..8c580b8eb9c 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 ba4c50d714c..1ce02f02fcc 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 20e8928142d..bec0ae38f6f 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 15fb8e5e62b..8f44cefd81e 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 34489a892d3..d497d17a645 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 }