diff --git a/app/featureset/featureset.go b/app/featureset/featureset.go index 26ec52a3f..14cec27b3 100644 --- a/app/featureset/featureset.go +++ b/app/featureset/featureset.go @@ -46,9 +46,9 @@ const ( // unless the user disabled this feature explicitly. GnosisBlockHotfix Feature = "gnosis_block_hotfix" - // Exponential enables Exponential round timer for consensus rounds. + // Linear enables Linear round timer for consensus rounds. // When active has precedence over EagerDoubleLinear round timer. - Exponential Feature = "exponential" + Linear Feature = "linear" ) var ( @@ -60,7 +60,7 @@ var ( AggSigDBV2: statusAlpha, JSONRequests: statusAlpha, GnosisBlockHotfix: statusAlpha, - Exponential: statusAlpha, + Linear: statusAlpha, // Add all features and there status here. } diff --git a/app/featureset/featureset_internal_test.go b/app/featureset/featureset_internal_test.go index fbee90065..92ca2cf72 100644 --- a/app/featureset/featureset_internal_test.go +++ b/app/featureset/featureset_internal_test.go @@ -16,7 +16,7 @@ func TestAllFeatureStatus(t *testing.T) { ConsensusParticipate, JSONRequests, GnosisBlockHotfix, - Exponential, + Linear, } for _, feature := range features { diff --git a/core/consensus/utils/roundtimer.go b/core/consensus/utils/roundtimer.go index c752c4ed7..4364156b5 100644 --- a/core/consensus/utils/roundtimer.go +++ b/core/consensus/utils/roundtimer.go @@ -3,7 +3,6 @@ package utils import ( - "math" "strings" "sync" "time" @@ -25,9 +24,16 @@ type TimerFunc func(core.Duty) RoundTimer // GetTimerFunc returns a timer function based on the enabled features. func GetTimerFunc() TimerFunc { - if featureset.Enabled(featureset.Exponential) { - return func(core.Duty) RoundTimer { - return NewExponentialRoundTimer() + if featureset.Enabled(featureset.Linear) { + return func(duty core.Duty) RoundTimer { + // Linear timer only affects Proposer duty + if duty.Type == core.DutyProposer { + return NewLinearRoundTimer() + } else if featureset.Enabled(featureset.EagerDoubleLinear) { + return NewDoubleEagerLinearRoundTimer() + } + + return NewIncreasingRoundTimer() } } @@ -54,7 +60,7 @@ func (t TimerType) Eager() bool { const ( TimerIncreasing TimerType = "inc" TimerEagerDoubleLinear TimerType = "eager_dlinear" - TimerExponential TimerType = "exponential" + TimerLinear TimerType = "linear" ) // increasingRoundTimeout returns the duration for a round that starts at incRoundStart in round 1 @@ -159,40 +165,40 @@ func (t *doubleEagerLinearRoundTimer) Timer(round int64) (<-chan time.Time, func return timer.Chan(), func() { timer.Stop() } } -// exponentialRoundTimer implements a round timerType with the following properties: +// linearRoundTimer implements a round timerType with the following properties: // // The first round has one second to complete consensus // If this round fails then other peers already had time to fetch proposal and therefore // won't need as much time to reach a consensus. Therefore start timeout with lower value -// which will increase exponentially -type exponentialRoundTimer struct { +// which will increase linearly +type linearRoundTimer struct { clock clockwork.Clock } -func (*exponentialRoundTimer) Type() TimerType { - return TimerExponential +func (*linearRoundTimer) Type() TimerType { + return TimerLinear } -func (t *exponentialRoundTimer) Timer(round int64) (<-chan time.Time, func()) { +func (t *linearRoundTimer) Timer(round int64) (<-chan time.Time, func()) { var timer clockwork.Timer if round == 1 { // First round has 1 second timer = t.clock.NewTimer(time.Second) } else { - // Subsequent rounds have exponentially more time starting at 200 milliseconds - timer = t.clock.NewTimer(time.Millisecond * time.Duration(math.Pow(2, float64(round-1))*100)) + // Subsequent rounds have linearly more time starting at 400 milliseconds + timer = t.clock.NewTimer(time.Duration(200*(round-1) + 200)) } return timer.Chan(), func() { timer.Stop() } } -// NewExponentialRoundTimer returns a new exponential round timer type. -func NewExponentialRoundTimer() RoundTimer { - return NewExponentialRoundTimerWithClock(clockwork.NewRealClock()) +// NewLinearRoundTimer returns a new linear round timer type. +func NewLinearRoundTimer() RoundTimer { + return NewLinearRoundTimerWithClock(clockwork.NewRealClock()) } -func NewExponentialRoundTimerWithClock(clock clockwork.Clock) RoundTimer { - return &exponentialRoundTimer{ +func NewLinearRoundTimerWithClock(clock clockwork.Clock) RoundTimer { + return &linearRoundTimer{ clock: clock, } } diff --git a/core/consensus/utils/roundtimer_test.go b/core/consensus/utils/roundtimer_test.go index 418c233f8..daaaaaba4 100644 --- a/core/consensus/utils/roundtimer_test.go +++ b/core/consensus/utils/roundtimer_test.go @@ -111,7 +111,7 @@ func TestDoubleEagerLinearRoundTimer(t *testing.T) { stop() } -func TestExponentialRoundTimer(t *testing.T) { +func TestLinearRoundTimer(t *testing.T) { tests := []struct { name string round int64 @@ -125,12 +125,12 @@ func TestExponentialRoundTimer(t *testing.T) { { name: "round 2", round: 2, - want: 200 * time.Millisecond, + want: 400 * time.Millisecond, }, { name: "round 3", round: 3, - want: 400 * time.Millisecond, + want: 600 * time.Millisecond, }, { name: "round 4", @@ -141,7 +141,7 @@ func TestExponentialRoundTimer(t *testing.T) { for _, tt := range tests { fakeClock := clockwork.NewFakeClock() - timer := utils.NewExponentialRoundTimerWithClock(fakeClock) + timer := utils.NewLinearRoundTimerWithClock(fakeClock) t.Run(tt.name, func(t *testing.T) { // Start the timerType @@ -170,17 +170,28 @@ func TestGetTimerFunc(t *testing.T) { require.Equal(t, utils.TimerEagerDoubleLinear, timerFunc(core.NewAttesterDuty(2)).Type()) featureset.DisableForT(t, featureset.EagerDoubleLinear) - featureset.EnableForT(t, featureset.Exponential) timerFunc = utils.GetTimerFunc() - require.Equal(t, utils.TimerExponential, timerFunc(core.NewAttesterDuty(0)).Type()) - require.Equal(t, utils.TimerExponential, timerFunc(core.NewAttesterDuty(1)).Type()) - require.Equal(t, utils.TimerExponential, timerFunc(core.NewAttesterDuty(2)).Type()) + require.Equal(t, utils.TimerIncreasing, timerFunc(core.NewAttesterDuty(0)).Type()) + require.Equal(t, utils.TimerIncreasing, timerFunc(core.NewAttesterDuty(1)).Type()) + require.Equal(t, utils.TimerIncreasing, timerFunc(core.NewAttesterDuty(2)).Type()) - featureset.DisableForT(t, featureset.Exponential) + featureset.EnableForT(t, featureset.Linear) timerFunc = utils.GetTimerFunc() + // non proposer duty, defaults to increasing require.Equal(t, utils.TimerIncreasing, timerFunc(core.NewAttesterDuty(0)).Type()) require.Equal(t, utils.TimerIncreasing, timerFunc(core.NewAttesterDuty(1)).Type()) require.Equal(t, utils.TimerIncreasing, timerFunc(core.NewAttesterDuty(2)).Type()) + + featureset.EnableForT(t, featureset.EagerDoubleLinear) + // non proposer duty, defaults to eager + require.Equal(t, utils.TimerEagerDoubleLinear, timerFunc(core.NewAttesterDuty(0)).Type()) + require.Equal(t, utils.TimerEagerDoubleLinear, timerFunc(core.NewAttesterDuty(1)).Type()) + require.Equal(t, utils.TimerEagerDoubleLinear, timerFunc(core.NewAttesterDuty(2)).Type()) + + // proposer duty, uses linear + require.Equal(t, utils.TimerLinear, timerFunc(core.NewProposerDuty(0)).Type()) + require.Equal(t, utils.TimerLinear, timerFunc(core.NewProposerDuty(1)).Type()) + require.Equal(t, utils.TimerLinear, timerFunc(core.NewProposerDuty(2)).Type()) } diff --git a/docs/consensus.md b/docs/consensus.md index 42c775634..f9015c2c1 100644 --- a/docs/consensus.md +++ b/docs/consensus.md @@ -67,9 +67,9 @@ The `IncreasingRoundTimer` uses a linear increment strategy for round durations. The `EagerDoubleLinearRoundTimer` aligns start times across participants by starting at an absolute time and doubling the round duration when the leader is active. The round duration increases linearly according to `LinearRoundInc`. This aims to fix an issue with the original solution where the leader resets the timer at the start of the round while others reset when they receive the justified pre-prepare which leads to leaders getting out of sync with the rest. This timer is enabled by default and doesn't require additional flags. -### Exponential Round Timer +### Linear Round Timer -The `ExponentialRoundTimer` increases round durations exponentially. It provides a sufficient timeout for the initial round and grows from a smaller base timeout for subsequent rounds. The idea behind this timer is, that the consensus timeout includes fetching the signing data. As all nodes do that at the start, irregardless if they are leader or not, after the first timeout, the remaining nodes already had time to fetch their signing data. Therefore they won't need as much time to reach consensus as the leader for the first round did. The shorter subsequent rounds allow us to more quickly skip underperforming leader when compared to both `IncreasingRoundTimer` and `EagerDoubleLinearRoundTimer`, giving more leaders a chance to advance the protocol before its too late. To enable this timer, use the flag `--feature-set-enable "exponential"`. Since this timer has precedence over the `EagerDoubleLinearRoundTimer` there is no need to disable the default timer. +The `LinearRoundTimer` increases round durations linearly. It provides a sufficient timeout for the initial round and grows from a smaller base timeout for subsequent rounds. The idea behind this timer is, that the consensus timeout includes fetching the signing data. As all nodes do that at the start, irregardless if they are leader or not, after the first timeout, the remaining nodes already had time to fetch their signing data. Therefore they won't need as much time to reach consensus as the leader for the first round did. The shorter subsequent rounds allow us to more quickly skip underperforming leader when compared to both `IncreasingRoundTimer` and `EagerDoubleLinearRoundTimer`, giving more leaders a chance to advance the protocol before its too late. This timer only affects Proposer duties, for the remaning ones it fallbacks to either `EagerDoubleLinearRoundTimer` or `IncreasingRoundTimer` depending on feature set flags. To enable it, use the flag `--feature-set-enable "linear"`. Since this timer has precedence over the `EagerDoubleLinearRoundTimer` there is no need to disable the default timer. ## Observability