diff --git a/internal/consensus/state.go b/internal/consensus/state.go index 4adc00fa7..5ec2c0370 100644 --- a/internal/consensus/state.go +++ b/internal/consensus/state.go @@ -2424,3 +2424,19 @@ func proposerWaitTime(lt tmtime.Source, h types.Header) time.Duration { } return h.Time.Sub(t) } + +// proposalStepWaitingTime determines how long a validator should wait for a block +// to be sent from a proposer. This duration is determined as a result of the +// previous block's time as well as by the Accuracy and MsgDelay timestamp parameter. +// The validator and the proposer's clock should differ from eachother by at most +// 2 * Accuracy and the proposal should take at most MsgDelay to arrive at the validator. +// The validator must therefore wait at least 2 * Accuracy + MsgDelay for the proposal +// to arrive. +func proposalStepWaitingTime(lt tmtime.Source, h types.Header, tp types.TimestampParams) time.Duration { + t := lt.Now() + wt := h.Time.Add(2 * tp.Accuracy).Add(tp.MsgDelay) + if t.After(wt) { + return 0 + } + return wt.Sub(t) +} diff --git a/internal/consensus/state_test.go b/internal/consensus/state_test.go index f0b193422..fee073c37 100644 --- a/internal/consensus/state_test.go +++ b/internal/consensus/state_test.go @@ -2441,7 +2441,7 @@ func TestSignSameVoteTwice(t *testing.T) { require.Equal(t, vote, vote2) } -func TestProposerWaitUntil(t *testing.T) { +func TestProposerWaitTime(t *testing.T) { genesisTime, err := time.Parse(time.RFC3339, "2019-03-13T23:00:00Z") require.NoError(t, err) testCases := []struct { @@ -2486,6 +2486,64 @@ func TestProposerWaitUntil(t *testing.T) { } } +func TestProposalTimeout(t *testing.T) { + genesisTime, err := time.Parse(time.RFC3339, "2019-03-13T23:00:00Z") + require.NoError(t, err) + testCases := []struct { + name string + localTime time.Time + previousBlockTime time.Time + accuracy time.Duration + msgDelay time.Duration + expectedDuration time.Duration + }{ + { + name: "MsgDelay + 2 * Accuracy has not quite elapsed", + localTime: genesisTime.Add(525 * time.Millisecond), + previousBlockTime: genesisTime.Add(6 * time.Millisecond), + accuracy: time.Millisecond * 10, + msgDelay: time.Millisecond * 500, + expectedDuration: 1 * time.Millisecond, + }, + { + name: "MsgDelay + 2 * Accuracy equals current time", + localTime: genesisTime.Add(525 * time.Millisecond), + previousBlockTime: genesisTime.Add(5 * time.Millisecond), + accuracy: time.Millisecond * 10, + msgDelay: time.Millisecond * 500, + expectedDuration: 0, + }, + { + name: "MsgDelay + 2 * Accuracy has elapsed", + localTime: genesisTime.Add(725 * time.Millisecond), + previousBlockTime: genesisTime.Add(5 * time.Millisecond), + accuracy: time.Millisecond * 10, + msgDelay: time.Millisecond * 500, + expectedDuration: 0, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + b := types.Block{ + Header: types.Header{ + Time: testCase.previousBlockTime, + }, + } + + mockSource := new(tmtimemocks.Source) + mockSource.On("Now").Return(testCase.localTime) + + tp := types.TimestampParams{ + Accuracy: testCase.accuracy, + MsgDelay: testCase.msgDelay, + } + + ti := proposalStepWaitingTime(mockSource, b.Header, tp) + assert.Equal(t, testCase.expectedDuration, ti) + }) + } +} + // subscribe subscribes test client to the given query and returns a channel with cap = 1. func subscribe(eventBus *types.EventBus, q tmpubsub.Query) <-chan tmpubsub.Message { sub, err := eventBus.Subscribe(context.Background(), testSubscriber, q)