Skip to content

Commit c2c6463

Browse files
staheri14evan-forbes
andauthoredOct 15, 2024··
feat: retrieves timeout propose and timeout commit dynamically per height according to the app version (#1494)
The celestia-core portion of [#3859](celestiaorg/celestia-app#3859) --------- Co-authored-by: evan-forbes <[email protected]>
1 parent a263c2c commit c2c6463

27 files changed

+1028
-406
lines changed
 

‎abci/types/types.pb.go

+653-267
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎blockchain/v0/reactor.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -410,8 +410,7 @@ FOR_LOOP:
410410
// TODO: batch saves so we dont persist to disk every block
411411
bcR.store.SaveBlock(first, firstParts, second.LastCommit)
412412

413-
// TODO: same thing for app - but we would need a way to
414-
// get the hash without persisting the state
413+
// TODO: same thing for app - but we would need a way to get the hash without persisting the state
415414
state, _, err = bcR.blockExec.ApplyBlock(state, firstID, first, second.LastCommit)
416415
if err != nil {
417416
// TODO This is bad, are we zombie?

‎config/config.go

+36-6
Original file line numberDiff line numberDiff line change
@@ -167,14 +167,14 @@ type BaseConfig struct { //nolint: maligned
167167
chainID string
168168

169169
// The root directory for all data.
170-
// This should be set in viper so it can unmarshal into this struct
170+
// This should be set in viper so that it can unmarshal into this struct
171171
RootDir string `mapstructure:"home"`
172172

173173
// TCP or UNIX socket address of the ABCI application,
174174
// or the name of an ABCI application compiled in with the CometBFT binary
175175
ProxyApp string `mapstructure:"proxy_app"`
176176

177-
// A custom human readable name for this node
177+
// A custom human-readable name for this node
178178
Moniker string `mapstructure:"moniker"`
179179

180180
// If this node is many blocks behind the tip of the chain, FastSync
@@ -212,7 +212,7 @@ type BaseConfig struct { //nolint: maligned
212212
// Output format: 'plain' (colored text) or 'json'
213213
LogFormat string `mapstructure:"log_format"`
214214

215-
// Path to the JSON file containing the initial validator set and other meta data
215+
// Path to the JSON file containing the initial validator set and other metadata
216216
Genesis string `mapstructure:"genesis_file"`
217217

218218
// Path to the JSON file containing the private key to use as a validator in the consensus protocol
@@ -279,7 +279,7 @@ func (cfg BaseConfig) PrivValidatorKeyFile() string {
279279
return rootify(cfg.PrivValidatorKey, cfg.RootDir)
280280
}
281281

282-
// PrivValidatorFile returns the full path to the priv_validator_state.json file
282+
// PrivValidatorStateFile returns the full path to the priv_validator_state.json file
283283
func (cfg BaseConfig) PrivValidatorStateFile() string {
284284
return rootify(cfg.PrivValidatorState, cfg.RootDir)
285285
}
@@ -871,7 +871,7 @@ func DefaultStateSyncConfig() *StateSyncConfig {
871871
}
872872
}
873873

874-
// TestFastSyncConfig returns a default configuration for the state sync service
874+
// TestStateSyncConfig returns a default configuration for the state sync service
875875
func TestStateSyncConfig() *StateSyncConfig {
876876
return DefaultStateSyncConfig()
877877
}
@@ -1055,6 +1055,20 @@ func (cfg *ConsensusConfig) Propose(round int32) time.Duration {
10551055
) * time.Nanosecond
10561056
}
10571057

1058+
// ProposeWithCustomTimeout is identical to Propose. However,
1059+
// it calculates the amount of time to wait for a proposal using the supplied
1060+
// customTimeout.
1061+
// If customTimeout is 0, the TimeoutPropose from cfg is used.
1062+
func (cfg *ConsensusConfig) ProposeWithCustomTimeout(round int32, customTimeout time.Duration) time.Duration {
1063+
// this is to capture any unforeseen cases where the customTimeout is 0
1064+
var timeoutPropose = customTimeout
1065+
if timeoutPropose == 0 {
1066+
// falling back to default timeout
1067+
timeoutPropose = cfg.TimeoutPropose
1068+
}
1069+
return time.Duration(timeoutPropose.Nanoseconds()+cfg.TimeoutProposeDelta.Nanoseconds()*int64(round)) * time.Nanosecond
1070+
}
1071+
10581072
// Prevote returns the amount of time to wait for straggler votes after receiving any +2/3 prevotes
10591073
func (cfg *ConsensusConfig) Prevote(round int32) time.Duration {
10601074
return time.Duration(
@@ -1070,11 +1084,23 @@ func (cfg *ConsensusConfig) Precommit(round int32) time.Duration {
10701084
}
10711085

10721086
// Commit returns the amount of time to wait for straggler votes after receiving +2/3 precommits
1073-
// for a single block (ie. a commit).
1087+
// for a single block (i.e., a commit).
10741088
func (cfg *ConsensusConfig) Commit(t time.Time) time.Time {
10751089
return t.Add(cfg.TimeoutCommit)
10761090
}
10771091

1092+
// CommitWithCustomTimeout is identical to Commit. However, it calculates the time for commit using the supplied customTimeout.
1093+
// If customTimeout is 0, the TimeoutCommit from cfg is used.
1094+
func (cfg *ConsensusConfig) CommitWithCustomTimeout(t time.Time, customTimeout time.Duration) time.Time {
1095+
// this is to capture any unforeseen cases where the customTimeout is 0
1096+
var timeoutCommit = customTimeout
1097+
if timeoutCommit == 0 {
1098+
// falling back to default timeout
1099+
timeoutCommit = cfg.TimeoutCommit
1100+
}
1101+
return t.Add(timeoutCommit)
1102+
}
1103+
10781104
// WalFile returns the full path to the write-ahead log file
10791105
func (cfg *ConsensusConfig) WalFile() string {
10801106
if cfg.walFile != "" {
@@ -1091,6 +1117,8 @@ func (cfg *ConsensusConfig) SetWalFile(walFile string) {
10911117
// ValidateBasic performs basic validation (checking param bounds, etc.) and
10921118
// returns an error if any check fails.
10931119
func (cfg *ConsensusConfig) ValidateBasic() error {
1120+
// TODO we may want to remove this check if TimeoutPropose is removed from
1121+
// the config
10941122
if cfg.TimeoutPropose < 0 {
10951123
return errors.New("timeout_propose can't be negative")
10961124
}
@@ -1109,6 +1137,8 @@ func (cfg *ConsensusConfig) ValidateBasic() error {
11091137
if cfg.TimeoutPrecommitDelta < 0 {
11101138
return errors.New("timeout_precommit_delta can't be negative")
11111139
}
1140+
// TODO we may want to remove this check if TimeoutCommit is removed from
1141+
// the config
11121142
if cfg.TimeoutCommit < 0 {
11131143
return errors.New("timeout_commit can't be negative")
11141144
}

‎config/config_test.go

+28
Original file line numberDiff line numberDiff line change
@@ -190,3 +190,31 @@ func TestInstrumentationConfigValidateBasic(t *testing.T) {
190190
cfg.MaxOpenConnections = -1
191191
assert.Error(t, cfg.ValidateBasic())
192192
}
193+
194+
func TestProposeWithCustomTimeout(t *testing.T) {
195+
cfg := DefaultConsensusConfig()
196+
197+
// customTimeout is 0, should fallback to default timeout
198+
round := int32(1)
199+
expectedTimeout := time.Duration(cfg.TimeoutPropose.Nanoseconds()+cfg.TimeoutProposeDelta.Nanoseconds()*int64(round)) * time.Nanosecond
200+
assert.Equal(t, expectedTimeout, cfg.ProposeWithCustomTimeout(round, time.Duration(0)))
201+
202+
// customTimeout is not 0
203+
customTimeout := 2 * time.Second
204+
expectedTimeout = time.Duration(customTimeout.Nanoseconds()+cfg.TimeoutProposeDelta.Nanoseconds()*int64(round)) * time.Nanosecond
205+
assert.Equal(t, expectedTimeout, cfg.ProposeWithCustomTimeout(round, customTimeout))
206+
}
207+
208+
func TestCommitWithCustomTimeout(t *testing.T) {
209+
cfg := DefaultConsensusConfig()
210+
211+
// customTimeout is 0, should fallback to default timeout
212+
inputTime := time.Now()
213+
expectedTime := inputTime.Add(cfg.TimeoutCommit)
214+
assert.Equal(t, expectedTime, cfg.CommitWithCustomTimeout(inputTime, time.Duration(0)))
215+
216+
// customTimeout is not 0
217+
customTimeout := 2 * time.Second
218+
expectedTime = inputTime.Add(customTimeout)
219+
assert.Equal(t, expectedTime, cfg.CommitWithCustomTimeout(inputTime, customTimeout))
220+
}

‎consensus/mempool_test.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func TestMempoolProgressInHigherRound(t *testing.T) {
7575
timeoutCh := subscribe(cs.eventBus, types.EventQueryTimeoutPropose)
7676
cs.setProposal = func(proposal *types.Proposal) error {
7777
if cs.Height == 2 && cs.Round == 0 {
78-
// dont set the proposal in round 0 so we timeout and
78+
// don't set the proposal in round 0 so we timeout and
7979
// go to next round
8080
cs.Logger.Info("Ignoring set proposal at height 2, round 0")
8181
return nil
@@ -91,7 +91,11 @@ func TestMempoolProgressInHigherRound(t *testing.T) {
9191
round = 0
9292

9393
ensureNewRound(newRoundCh, height, round) // first round at next height
94-
deliverTxsRange(cs, 0, 1) // we deliver txs, but dont set a proposal so we get the next round
94+
deliverTxsRange(cs, 0, 1) // we deliver txs, but don't set a proposal so we get the next round
95+
// The use of cs.config.TimeoutPropose.Nanoseconds() as the timeout propose is acceptable in this test case, the following line.
96+
// Even though timeouts are version-dependent, cs is created with an empty previous state in this scenario.
97+
// As there's no timeout propose in the previous state, we default to the timeout propose in the config.
98+
// This makes the test case valid.
9599
ensureNewTimeout(timeoutCh, height, round, cs.config.TimeoutPropose.Nanoseconds())
96100

97101
round++ // moving to the next round

‎consensus/replay.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,8 @@ LOOP:
156156
}
157157

158158
// NOTE: since the priv key is set when the msgs are received
159-
// it will attempt to eg double sign but we can just ignore it
160-
// since the votes will be replayed and we'll get to the next step
159+
// it will attempt to e.g., double sign, but we can just ignore it
160+
// since the votes will be replayed, and we'll get to the next step
161161
if err := cs.readReplayMessage(msg, nil); err != nil {
162162
return err
163163
}
@@ -362,6 +362,11 @@ func (h *Handshaker) ReplayBlocksWithContext(
362362
state.ConsensusParams = types.UpdateConsensusParams(state.ConsensusParams, res.ConsensusParams)
363363
state.Version.Consensus.App = state.ConsensusParams.Version.AppVersion
364364
}
365+
366+
// update timeouts based on the InitChainSync response
367+
state.TimeoutCommit = res.Timeouts.TimeoutCommit
368+
state.TimeoutPropose = res.Timeouts.TimeoutPropose
369+
365370
// We update the last results hash with the empty hash, to conform with RFC-6962.
366371
state.LastResultsHash = merkle.HashFromByteSlices(nil)
367372
if err := h.stateStore.Save(state); err != nil {

‎consensus/replay_file.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const (
2929
//--------------------------------------------------------
3030
// replay messages interactively or all at once
3131

32-
// replay the wal file
32+
// RunReplayFile replays the wal file
3333
func RunReplayFile(config cfg.BaseConfig, csConfig *cfg.ConsensusConfig, console bool) {
3434
consensusState := newConsensusStateForReplay(config, csConfig)
3535

@@ -38,7 +38,7 @@ func RunReplayFile(config cfg.BaseConfig, csConfig *cfg.ConsensusConfig, console
3838
}
3939
}
4040

41-
// Replay msgs in file or start the console
41+
// ReplayFile replays msgs in file or start the console
4242
func (cs *State) ReplayFile(file string, console bool) error {
4343

4444
if cs.IsRunning() {

‎consensus/state.go

+18-6
Original file line numberDiff line numberDiff line change
@@ -674,9 +674,21 @@ func (cs *State) updateToState(state sm.State) {
674674
// to be gathered for the first block.
675675
// And alternative solution that relies on clocks:
676676
// cs.StartTime = state.LastBlockTime.Add(timeoutCommit)
677-
cs.StartTime = cs.config.Commit(cmttime.Now())
677+
678+
if state.LastBlockHeight == 0 {
679+
// Don't use cs.state.TimeoutCommit because that is zero
680+
cs.StartTime = cs.config.CommitWithCustomTimeout(cmttime.Now(), state.TimeoutCommit)
681+
} else {
682+
cs.StartTime = cs.config.CommitWithCustomTimeout(cmttime.Now(), cs.state.TimeoutCommit)
683+
}
684+
678685
} else {
679-
cs.StartTime = cs.config.Commit(cs.CommitTime)
686+
if state.LastBlockHeight == 0 {
687+
cs.StartTime = cs.config.CommitWithCustomTimeout(cs.CommitTime, state.TimeoutCommit)
688+
} else {
689+
cs.StartTime = cs.config.CommitWithCustomTimeout(cs.CommitTime, cs.state.TimeoutCommit)
690+
}
691+
680692
}
681693

682694
cs.Validators = validators
@@ -1113,7 +1125,7 @@ func (cs *State) enterPropose(height int64, round int32) {
11131125
}()
11141126

11151127
// If we don't get the proposal and all block parts quick enough, enterPrevote
1116-
cs.scheduleTimeout(cs.config.Propose(round), height, round, cstypes.RoundStepPropose)
1128+
cs.scheduleTimeout(cs.config.ProposeWithCustomTimeout(round, cs.state.TimeoutPropose), height, round, cstypes.RoundStepPropose)
11171129

11181130
// Nothing more to do if we're not a validator
11191131
if cs.privValidator == nil {
@@ -1682,7 +1694,7 @@ func (cs *State) finalizeCommit(height int64) {
16821694
// exists.
16831695
//
16841696
// Either way, the State should not be resumed until we
1685-
// successfully call ApplyBlock (ie. later here, or in Handshake after
1697+
// successfully call ApplyBlock (i.e., later here, or in Handshake after
16861698
// restart).
16871699
endMsg := EndHeightMessage{height}
16881700
if err := cs.wal.WriteSync(endMsg); err != nil { // NOTE: fsync
@@ -1698,7 +1710,7 @@ func (cs *State) finalizeCommit(height int64) {
16981710
stateCopy := cs.state.Copy()
16991711

17001712
// Execute and commit the block, update and save the state, and update the mempool.
1701-
// NOTE The block.AppHash wont reflect these txs until the next block.
1713+
// NOTE The block.AppHash won't reflect these txs until the next block.
17021714
var (
17031715
err error
17041716
retainHeight int64
@@ -1741,7 +1753,7 @@ func (cs *State) finalizeCommit(height int64) {
17411753

17421754
fail.Fail() // XXX
17431755

1744-
// Private validator might have changed it's key pair => refetch pubkey.
1756+
// Private validator might have changed its key pair => refetch pubkey.
17451757
if err := cs.updatePrivValidatorPubKey(); err != nil {
17461758
logger.Error("failed to get private validator pubkey", "err", err)
17471759
}

‎consensus/state_test.go

+16-8
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,10 @@ func TestStateEnterProposeNoPrivValidator(t *testing.T) {
144144
startTestRound(cs, height, round)
145145

146146
// if we're not a validator, EnterPropose should timeout
147+
// The use of cs.config.TimeoutPropose.Nanoseconds() as the timeout propose is acceptable in this test case.
148+
// Even though timeouts are version-dependent, cs is created with an empty previous state in this scenario.
149+
// As there's no timeout propose in the previous state, we default to the timeout propose in the config.
150+
// This makes the test case valid.
147151
ensureNewTimeout(timeoutCh, height, round, cs.config.TimeoutPropose.Nanoseconds())
148152

149153
if cs.GetRoundState().Proposal != nil {
@@ -179,6 +183,10 @@ func TestStateEnterProposeYesPrivValidator(t *testing.T) {
179183
}
180184

181185
// if we're a validator, enterPropose should not timeout
186+
// The use of cs.config.TimeoutPropose.Nanoseconds() as the timeout propose is acceptable in this test case.
187+
// Even though timeouts are version-dependent, cs is created with an empty previous state in this scenario.
188+
// As there's no timeout propose in the previous state, we default to the timeout propose in the config.
189+
// This makes the test case valid.
182190
ensureNoNewTimeout(timeoutCh, cs.config.TimeoutPropose.Nanoseconds())
183191
}
184192

@@ -310,7 +318,7 @@ func TestStateOversizedBlock(t *testing.T) {
310318
lockedRound = -1
311319
// if the block is oversized cs1 should log an error with the block part message as it exceeds
312320
// the consensus params. The block is not added to cs.ProposalBlock so the node timeouts.
313-
ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.Propose(round).Nanoseconds())
321+
ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.ProposeWithCustomTimeout(round, cs1.state.TimeoutPropose).Nanoseconds())
314322
// and then should send nil prevote and precommit regardless of whether other validators prevote and
315323
// precommit on it
316324
}
@@ -497,7 +505,7 @@ func TestStateLockNoPOL(t *testing.T) {
497505
incrementRound(vs2)
498506

499507
// now we're on a new round and not the proposer, so wait for timeout
500-
ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.Propose(round).Nanoseconds())
508+
ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.ProposeWithCustomTimeout(round, cs1.state.TimeoutPropose).Nanoseconds())
501509

502510
rs := cs1.GetRoundState()
503511

@@ -1029,7 +1037,7 @@ func TestStateLockPOLSafety1(t *testing.T) {
10291037
*/
10301038

10311039
// timeout of propose
1032-
ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.Propose(round).Nanoseconds())
1040+
ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.ProposeWithCustomTimeout(round, cs1.state.TimeoutPropose).Nanoseconds())
10331041

10341042
// finish prevote
10351043
ensurePrevote(voteCh, height, round)
@@ -1199,7 +1207,7 @@ func TestProposeValidBlock(t *testing.T) {
11991207
t.Log("### ONTO ROUND 2")
12001208

12011209
// timeout of propose
1202-
ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.Propose(round).Nanoseconds())
1210+
ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.ProposeWithCustomTimeout(round, cs1.state.TimeoutPropose).Nanoseconds())
12031211

12041212
ensurePrevote(voteCh, height, round)
12051213
validatePrevote(t, cs1, round, vss[0], propBlockHash)
@@ -1326,7 +1334,7 @@ func TestSetValidBlockOnDelayedProposal(t *testing.T) {
13261334
startTestRound(cs1, cs1.Height, round)
13271335
ensureNewRound(newRoundCh, height, round)
13281336

1329-
ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.Propose(round).Nanoseconds())
1337+
ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.ProposeWithCustomTimeout(round, cs1.state.TimeoutPropose).Nanoseconds())
13301338

13311339
ensurePrevote(voteCh, height, round)
13321340
validatePrevote(t, cs1, round, vss[0], nil)
@@ -1407,7 +1415,7 @@ func TestWaitingTimeoutProposeOnNewRound(t *testing.T) {
14071415
rs := cs1.GetRoundState()
14081416
assert.True(t, rs.Step == cstypes.RoundStepPropose) // P0 does not prevote before timeoutPropose expires
14091417

1410-
ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.Propose(round).Nanoseconds())
1418+
ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.ProposeWithCustomTimeout(round, cs1.state.TimeoutPropose).Nanoseconds())
14111419

14121420
ensurePrevote(voteCh, height, round)
14131421
validatePrevote(t, cs1, round, vss[0], nil)
@@ -1471,7 +1479,7 @@ func TestWaitTimeoutProposeOnNilPolkaForTheCurrentRound(t *testing.T) {
14711479
incrementRound(vss[1:]...)
14721480
signAddVotes(cs1, cmtproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4)
14731481

1474-
ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.Propose(round).Nanoseconds())
1482+
ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.ProposeWithCustomTimeout(round, cs1.state.TimeoutPropose).Nanoseconds())
14751483

14761484
ensurePrevote(voteCh, height, round)
14771485
validatePrevote(t, cs1, round, vss[0], nil)
@@ -1619,7 +1627,7 @@ func TestStartNextHeightCorrectlyAfterTimeout(t *testing.T) {
16191627

16201628
cs1.txNotifier.(*fakeTxNotifier).Notify()
16211629

1622-
ensureNewTimeout(timeoutProposeCh, height+1, round, cs1.config.Propose(round).Nanoseconds())
1630+
ensureNewTimeout(timeoutProposeCh, height+1, round, cs1.config.ProposeWithCustomTimeout(round, cs1.state.TimeoutPropose).Nanoseconds())
16231631
rs = cs1.GetRoundState()
16241632
assert.False(
16251633
t,

0 commit comments

Comments
 (0)
Please sign in to comment.