diff --git a/internal/consensus/state.go b/internal/consensus/state.go index 2388f4d77..4646426a6 100644 --- a/internal/consensus/state.go +++ b/internal/consensus/state.go @@ -2412,3 +2412,15 @@ func repairWalFile(src, dst string) error { return nil } + +// proposerWaitUntil determines when the proposer should propose its next block +// 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 proposerWaitUntil(lt tmtime.Source, h types.Header) time.Time { + t := lt.Now() + if t.After(h.Time) { + return t + } + return h.Time +} diff --git a/internal/consensus/state_test.go b/internal/consensus/state_test.go index 2916a6559..767f5defc 100644 --- a/internal/consensus/state_test.go +++ b/internal/consensus/state_test.go @@ -17,6 +17,7 @@ import ( "github.com/tendermint/tendermint/libs/log" tmpubsub "github.com/tendermint/tendermint/libs/pubsub" tmrand "github.com/tendermint/tendermint/libs/rand" + tmtimemocks "github.com/tendermint/tendermint/libs/time/mocks" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/types" ) @@ -2440,6 +2441,51 @@ func TestSignSameVoteTwice(t *testing.T) { require.Equal(t, vote, vote2) } +func TestProposerWaitUntil(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 + expectedTime time.Time + }{ + { + name: "block time greater than local time", + blockTime: genesisTime.Add(5 * time.Nanosecond), + localTime: genesisTime.Add(1 * time.Nanosecond), + expectedTime: genesisTime.Add(5 * time.Nanosecond), + }, + { + name: "local time greater than block time", + blockTime: genesisTime.Add(1 * time.Nanosecond), + localTime: genesisTime.Add(5 * time.Nanosecond), + expectedTime: genesisTime.Add(5 * time.Nanosecond), + }, + { + name: "both times equal", + blockTime: genesisTime.Add(5 * time.Nanosecond), + localTime: genesisTime.Add(5 * time.Nanosecond), + expectedTime: genesisTime.Add(5 * time.Nanosecond), + }, + } + 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 := proposerWaitUntil(mockSource, b.Header) + assert.Equal(t, testCase.expectedTime, 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)