diff --git a/libs/time/mocks/source.go b/libs/time/mocks/source.go new file mode 100644 index 000000000..a8e49b314 --- /dev/null +++ b/libs/time/mocks/source.go @@ -0,0 +1,28 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + time "time" + + mock "github.com/stretchr/testify/mock" +) + +// Source is an autogenerated mock type for the Source type +type Source struct { + mock.Mock +} + +// Now provides a mock function with given fields: +func (_m *Source) Now() time.Time { + ret := _m.Called() + + var r0 time.Time + if rf, ok := ret.Get(0).(func() time.Time); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(time.Time) + } + + return r0 +} diff --git a/libs/time/time.go b/libs/time/time.go index 786f9bbb4..7ab45d8f1 100644 --- a/libs/time/time.go +++ b/libs/time/time.go @@ -15,3 +15,17 @@ func Now() time.Time { func Canonical(t time.Time) time.Time { return t.Round(0).UTC() } + +//go:generate ../../scripts/mockery_generate.sh Source + +// Source is an interface that defines a way to fetch the current time. +type Source interface { + Now() time.Time +} + +// DefaultSource implements the Source interface using the system clock provided by the standard library. +type DefaultSource struct{} + +func (DefaultSource) Now() time.Time { + return Now() +} diff --git a/types/params.go b/types/params.go index 32d0f71c8..8c0217a44 100644 --- a/types/params.go +++ b/types/params.go @@ -41,6 +41,7 @@ type ConsensusParams struct { Evidence EvidenceParams `json:"evidence"` Validator ValidatorParams `json:"validator"` Version VersionParams `json:"version"` + Timestamp TimestampParams `json:"timestamp"` } // HashedParams is a subset of ConsensusParams. @@ -75,6 +76,14 @@ type VersionParams struct { AppVersion uint64 `json:"app_version"` } +// TimestampParams influence the validity of block timestamps. +// TODO (@wbanfield): add link to proposer-based timestamp spec when completed. +type TimestampParams struct { + Precision time.Duration `json:"precision"` + Accuracy time.Duration `json:"accuracy"` + MsgDelay time.Duration `json:"msg_delay"` +} + // DefaultConsensusParams returns a default ConsensusParams. func DefaultConsensusParams() *ConsensusParams { return &ConsensusParams{ @@ -116,6 +125,16 @@ func DefaultVersionParams() VersionParams { } } +func DefaultTimestampParams() TimestampParams { + // TODO(@wbanfield): Determine experimental values for these defaults + // https://github.com/tendermint/tendermint/issues/7202 + return TimestampParams{ + Precision: 2 * time.Second, + Accuracy: 500 * time.Millisecond, + MsgDelay: 3 * time.Second, + } +} + func (val *ValidatorParams) IsValidPubkeyType(pubkeyType string) bool { for i := 0; i < len(val.PubKeyTypes); i++ { if val.PubKeyTypes[i] == pubkeyType { diff --git a/types/proposal.go b/types/proposal.go index 5f3f50bf7..6e07e87c1 100644 --- a/types/proposal.go +++ b/types/proposal.go @@ -79,6 +79,25 @@ func (p *Proposal) ValidateBasic() error { return nil } +// IsTimely validates that the block timestamp is 'timely' according to the proposer-based timestamp algorithm. +// To evaluate if a block is timely, its timestamp is compared to the local time of the validator along with the +// configured Precision and MsgDelay parameters. +// Specifically, a proposed block timestamp is considered timely if it is satisfies the following inequalities: +// +// proposedBlockTime > validatorLocaltime - Precision && proposedBlockTime < validatorLocalTime + Precision + MsgDelay. +// +// For more information on the meaning of 'timely', see the proposer-based timestamp specification: +// https://github.com/tendermint/spec/tree/master/spec/consensus/proposer-based-timestamp +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) { + return true + } + return false +} + // String returns a string representation of the Proposal. // // 1. height diff --git a/types/proposal_test.go b/types/proposal_test.go index 472c66af5..fd2f9f295 100644 --- a/types/proposal_test.go +++ b/types/proposal_test.go @@ -13,6 +13,7 @@ import ( "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/internal/libs/protoio" tmrand "github.com/tendermint/tendermint/libs/rand" + tmtimemocks "github.com/tendermint/tendermint/libs/time/mocks" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) @@ -210,3 +211,65 @@ func TestProposalProtoBuf(t *testing.T) { } } } + +func TestIsTimely(t *testing.T) { + genesisTime, err := time.Parse(time.RFC3339, "2019-03-13T23:00:00Z") + require.NoError(t, err) + testCases := []struct { + name string + proposalTime time.Time + localTime time.Time + precision time.Duration + msgDelay time.Duration + expectTimely bool + }{ + { + // Checking that the following inequality evaluates to true: + // 1 - 2 < 0 < 1 + 2 + 1 + name: "basic timely", + proposalTime: genesisTime, + localTime: genesisTime.Add(1 * time.Nanosecond), + precision: time.Nanosecond * 2, + msgDelay: time.Nanosecond, + expectTimely: true, + }, + { + // Checking that the following inequality evaluates to false: + // 3 - 2 < 0 < 3 + 2 + 1 + name: "local time too large", + proposalTime: genesisTime, + localTime: genesisTime.Add(3 * time.Nanosecond), + precision: time.Nanosecond * 2, + msgDelay: time.Nanosecond, + expectTimely: false, + }, + { + // Checking that the following inequality evaluates to false: + // 0 - 2 < 2 < 2 + 1 + name: "proposal time too large", + proposalTime: genesisTime.Add(4 * time.Nanosecond), + localTime: genesisTime, + precision: time.Nanosecond * 2, + msgDelay: time.Nanosecond, + expectTimely: false, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + p := Proposal{ + Timestamp: testCase.proposalTime, + } + + tp := TimestampParams{ + Precision: testCase.precision, + MsgDelay: testCase.msgDelay, + } + + mockSource := new(tmtimemocks.Source) + mockSource.On("Now").Return(testCase.localTime) + + ti := p.IsTimely(mockSource, tp) + assert.Equal(t, testCase.expectTimely, ti) + }) + } +}