From 6be5efaaa9c82e0ea170af7f0926241f5390c9ca Mon Sep 17 00:00:00 2001 From: William Banfield <4561443+williambanfield@users.noreply.github.com> Date: Tue, 23 Nov 2021 15:32:24 -0500 Subject: [PATCH] consensus: add calculation for proposal step waits from pbts (#7290) * initial proposerWaitsUntil implementation * switch to duration for easier use with timeout scheduling * add proposal step waiting time with tests * minor aesthetic change to IsTimely * minor language fix * Update internal/consensus/state.go Co-authored-by: M. J. Fromberger * reword comment * change accuracy to precision * move tests to separate pbts test file Co-authored-by: M. J. Fromberger --- internal/consensus/pbts_test.go | 115 ++++++++++++++++++++++++++++++++ internal/consensus/state.go | 36 ++++++++++ types/proposal.go | 2 +- 3 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 internal/consensus/pbts_test.go diff --git a/internal/consensus/pbts_test.go b/internal/consensus/pbts_test.go new file mode 100644 index 000000000..0d3095214 --- /dev/null +++ b/internal/consensus/pbts_test.go @@ -0,0 +1,115 @@ +package consensus + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + tmtimemocks "github.com/tendermint/tendermint/libs/time/mocks" + "github.com/tendermint/tendermint/types" +) + +func TestProposerWaitTime(t *testing.T) { + genesisTime, err := time.Parse(time.RFC3339, "2019-03-13T23:00:00Z") + require.NoError(t, err) + testCases := []struct { + name string + blockTime time.Time + localTime time.Time + expectedWait time.Duration + }{ + { + name: "block time greater than local time", + blockTime: genesisTime.Add(5 * time.Nanosecond), + localTime: genesisTime.Add(1 * time.Nanosecond), + expectedWait: 4 * time.Nanosecond, + }, + { + name: "local time greater than block time", + blockTime: genesisTime.Add(1 * time.Nanosecond), + localTime: genesisTime.Add(5 * time.Nanosecond), + expectedWait: 0, + }, + { + name: "both times equal", + blockTime: genesisTime.Add(5 * time.Nanosecond), + localTime: genesisTime.Add(5 * time.Nanosecond), + expectedWait: 0, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + b := types.Block{ + Header: types.Header{ + Time: testCase.blockTime, + }, + } + + mockSource := new(tmtimemocks.Source) + mockSource.On("Now").Return(testCase.localTime) + + ti := proposerWaitTime(mockSource, b.Header) + assert.Equal(t, testCase.expectedWait, ti) + }) + } +} + +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 + precision time.Duration + msgDelay time.Duration + expectedDuration time.Duration + }{ + { + name: "MsgDelay + Precision has not quite elapsed", + localTime: genesisTime.Add(525 * time.Millisecond), + previousBlockTime: genesisTime.Add(6 * time.Millisecond), + precision: time.Millisecond * 20, + msgDelay: time.Millisecond * 500, + expectedDuration: 1 * time.Millisecond, + }, + { + name: "MsgDelay + Precision equals current time", + localTime: genesisTime.Add(525 * time.Millisecond), + previousBlockTime: genesisTime.Add(5 * time.Millisecond), + precision: time.Millisecond * 20, + msgDelay: time.Millisecond * 500, + expectedDuration: 0, + }, + { + name: "MsgDelay + Precision has elapsed", + localTime: genesisTime.Add(725 * time.Millisecond), + previousBlockTime: genesisTime.Add(5 * time.Millisecond), + precision: time.Millisecond * 20, + 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{ + Precision: testCase.precision, + MsgDelay: testCase.msgDelay, + } + + ti := proposalStepWaitingTime(mockSource, b.Header, tp) + assert.Equal(t, testCase.expectedDuration, ti) + }) + } +} diff --git a/internal/consensus/state.go b/internal/consensus/state.go index 270a0b568..5ecaad0d6 100644 --- a/internal/consensus/state.go +++ b/internal/consensus/state.go @@ -2416,3 +2416,39 @@ func repairWalFile(src, dst string) error { return nil } + +// proposerWaitTime determines how long the proposer should wait to propose its next block. +// If the result is zero, a block can be proposed immediately. +// +// Block times must be monotonically increasing, so if the block time of the previous +// block is larger than the proposer's current time, then the proposer will sleep +// until its local clock exceeds the previous block time. +func proposerWaitTime(lt tmtime.Source, h types.Header) time.Duration { + t := lt.Now() + if h.Time.After(t) { + return h.Time.Sub(t) + } + return 0 +} + +// proposalStepWaitingTime is used along with the `timeout-propose` configuration +// parameter to determines how long a validator will wait for a block to be sent from a proposer. +// proposalStepWaitingTime ensures that the validator waits long enough for the proposer to +// deliver a block with a monotically increasing timestamp. +// +// To ensure that the validator waits long enough, it must wait until the previous +// block's timestamp. It also must account for the difference between its own clock and +// the proposer's clock, i.e. the 'Precision', and the amount of time for the message to be transmitted, +// i.e. the MsgDelay. +// +// The result of proposalStepWaitingTime is compared with the configured `timeout-propose` duration, +// and the validator waits for whichever duration is larger before advancing to the next step +// and prevoting nil. +func proposalStepWaitingTime(lt tmtime.Source, h types.Header, tp types.TimestampParams) time.Duration { + t := lt.Now() + wt := h.Time.Add(tp.Precision).Add(tp.MsgDelay) + if t.After(wt) { + return 0 + } + return wt.Sub(t) +} diff --git a/types/proposal.go b/types/proposal.go index 0bd90fd4d..b3646cf1d 100644 --- a/types/proposal.go +++ b/types/proposal.go @@ -92,7 +92,7 @@ func (p *Proposal) IsTimely(clock tmtime.Source, tp TimestampParams) bool { lt := clock.Now() lhs := lt.Add(-tp.Precision) rhs := lt.Add(tp.Precision).Add(tp.MsgDelay) - if lhs.Before(p.Timestamp) && p.Timestamp.Before(rhs) { + if lhs.Before(p.Timestamp) && rhs.After(p.Timestamp) { return true } return false