base synchrony on amount of voting power observed on the network from proposals

This commit is contained in:
William Banfield
2021-12-16 16:12:48 -05:00
parent 56a20056ec
commit 017d45f8e3
5 changed files with 98 additions and 4 deletions

View File

@@ -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("")

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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)
})
}

View File

@@ -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 {