From 017d45f8e3782150ca84e86fff8d0ecbc007ec04 Mon Sep 17 00:00:00 2001 From: William Banfield Date: Thu, 16 Dec 2021 16:12:48 -0500 Subject: [PATCH] base synchrony on amount of voting power observed on the network from proposals --- types/light.go | 17 ++++++++++++++++ types/light_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++ types/proposal.go | 7 ++++--- types/proposal_test.go | 23 +++++++++++++++++++++- types/validator_set.go | 11 +++++++++++ 5 files changed, 98 insertions(+), 4 deletions(-) diff --git a/types/light.go b/types/light.go index 5a650a159..c1c458d96 100644 --- a/types/light.go +++ b/types/light.go @@ -4,7 +4,9 @@ import ( "bytes" "errors" "fmt" + "time" + "github.com/tendermint/tendermint/proto/tendermint/types" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) @@ -43,6 +45,21 @@ func (lb LightBlock) ValidateBasic(chainID string) error { return nil } +func (lb LightBlock) CalculateSynchronyVotingPower(t types.TimingParams) time.Duration { + // Synchrony calculated as a function of the amount of voting power that has been seen since + // since the beginning of the height exclusive of the round in which the proposal succeeded. + votingPowerSeen := lb.ValidatorSet.VotingPowerBeforeRound(lb.SignedHeader.Commit.Round) + nonFaultyThreshold := lb.ValidatorSet.TotalVotingPower()*1/3 + 1 + + thresholdsObserved := (votingPowerSeen / nonFaultyThreshold) + return t.Precision + t.MessageDelay + t.MessageDelay*time.Duration(thresholdsObserved) +} + +func (lb LightBlock) CalculateSynchronyRounds(t types.TimingParams) time.Duration { + // hard coded value of '10' used in this example + return t.Precision + t.MessageDelay + t.MessageDelay*time.Duration(lb.Commit.Round/10) +} + // String returns a string representation of the LightBlock func (lb LightBlock) String() string { return lb.StringIndented("") diff --git a/types/light_test.go b/types/light_test.go index 94b2c4b4f..4af706c3a 100644 --- a/types/light_test.go +++ b/types/light_test.go @@ -6,8 +6,10 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/version" ) @@ -163,3 +165,45 @@ func TestSignedHeaderValidateBasic(t *testing.T) { }) } } + +func TestCalculateSynchronyVotingPower(t *testing.T) { + vals, _ := randValidatorPrivValSet(5, 10) + sh := &SignedHeader{ + Commit: &Commit{ + Round: 4, + }, + } + lb := LightBlock{ + ValidatorSet: vals, + SignedHeader: sh, + } + + tp := types.TimingParams{ + MessageDelay: 100 * time.Millisecond, + Precision: 10 * time.Millisecond, + } + + s := lb.CalculateSynchronyVotingPower(tp) + require.Equal(t, s, tp.Precision+tp.MessageDelay+tp.MessageDelay*2) +} + +func TestCalculateSynchronyRound(t *testing.T) { + vals, _ := randValidatorPrivValSet(5, 10) + sh := &SignedHeader{ + Commit: &Commit{ + Round: 11, + }, + } + lb := LightBlock{ + ValidatorSet: vals, + SignedHeader: sh, + } + + tp := types.TimingParams{ + MessageDelay: 100 * time.Millisecond, + Precision: 10 * time.Millisecond, + } + + s := lb.CalculateSynchronyRounds(tp) + require.Equal(t, s, tp.Precision+tp.MessageDelay+tp.MessageDelay*1) +} diff --git a/types/proposal.go b/types/proposal.go index 288abd6be..16b8c8c51 100644 --- a/types/proposal.go +++ b/types/proposal.go @@ -88,10 +88,11 @@ func (p *Proposal) ValidateBasic() error { // // 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 TimingParams) bool { +func (p *Proposal) IsTimely(clock tmtime.Source, tp TimingParams, vs *ValidatorSet) bool { + thirdsSeen := vs.VotingPowerBeforeRound(p.Round) / (vs.TotalVotingPower()*1/3 + 1) lt := clock.Now() - lhs := lt.Add(-tp.Precision) - rhs := lt.Add(tp.Precision).Add(tp.MessageDelay) + rhs := lt.Add(tp.Precision) + lhs := lt.Add(-tp.Precision).Add(-tp.MessageDelay - tp.MessageDelay*time.Duration(thirdsSeen)) if lhs.Before(p.Timestamp) && rhs.After(p.Timestamp) { return true } diff --git a/types/proposal_test.go b/types/proposal_test.go index a7a6083f8..60ec312f6 100644 --- a/types/proposal_test.go +++ b/types/proposal_test.go @@ -200,6 +200,7 @@ func TestIsTimely(t *testing.T) { testCases := []struct { name string proposalTime time.Time + round int32 localTime time.Time precision time.Duration msgDelay time.Duration @@ -235,11 +236,30 @@ func TestIsTimely(t *testing.T) { msgDelay: time.Nanosecond, expectTimely: false, }, + { + name: "long proposal in later round", + proposalTime: genesisTime, + round: 2, // should see 1/3 +1 of the network power + localTime: genesisTime.Add(3 * time.Nanosecond), + precision: time.Nanosecond * 2, + msgDelay: time.Nanosecond, + expectTimely: true, + }, + { + name: "long proposal in earlier round", + proposalTime: genesisTime, + round: 1, // should see 1/3 +1 of the network power + localTime: genesisTime.Add(3 * time.Nanosecond), + 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, + Round: testCase.round, } tp := TimingParams{ @@ -250,7 +270,8 @@ func TestIsTimely(t *testing.T) { mockSource := new(tmtimemocks.Source) mockSource.On("Now").Return(testCase.localTime) - ti := p.IsTimely(mockSource, tp) + vals, _ := randValidatorPrivValSet(5, 15) + ti := p.IsTimely(mockSource, tp, vals) assert.Equal(t, testCase.expectTimely, ti) }) } diff --git a/types/validator_set.go b/types/validator_set.go index 8675dec2a..cd8afdb64 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -342,6 +342,17 @@ func (vals *ValidatorSet) findProposer() *Validator { return proposer } +// VotingPowerSeenBeforeRound calculates the amount of voting power seen on the network +// from proposals before round r. +func (vals *ValidatorSet) VotingPowerBeforeRound(r int32) int64 { + var votingPowerSeen int64 + for i := int32(0); i < r; i++ { + vals.IncrementProposerPriority(1) + votingPowerSeen += vals.GetProposer().VotingPower + } + return votingPowerSeen +} + // Hash returns the Merkle root hash build using validators (as leaves) in the // set. func (vals *ValidatorSet) Hash() []byte {