From 9a89b5e802bd740fce00bfaa1de88875ba301440 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Wed, 1 May 2024 13:40:56 -0400 Subject: [PATCH] Consolidate record poll (#2970) --- snow/consensus/snowball/binary_snowball.go | 27 ++++----- .../snowball/binary_snowball_test.go | 55 ++++++++++--------- snow/consensus/snowball/binary_snowflake.go | 36 +++++++----- .../snowball/binary_snowflake_test.go | 15 ++--- snow/consensus/snowball/consensus.go | 49 +++++------------ snow/consensus/snowball/factory.go | 8 +-- snow/consensus/snowball/flat.go | 15 +---- snow/consensus/snowball/nnary_snowball.go | 31 ++++------- .../consensus/snowball/nnary_snowball_test.go | 40 ++++++++------ snow/consensus/snowball/nnary_snowflake.go | 36 +++++++----- .../snowball/nnary_snowflake_test.go | 31 ++++++----- snow/consensus/snowball/tree.go | 28 +++------- snow/consensus/snowball/unary_snowball.go | 27 +++++---- .../consensus/snowball/unary_snowball_test.go | 17 +++--- snow/consensus/snowball/unary_snowflake.go | 36 +++++++----- .../snowball/unary_snowflake_test.go | 15 ++--- 16 files changed, 226 insertions(+), 240 deletions(-) diff --git a/snow/consensus/snowball/binary_snowball.go b/snow/consensus/snowball/binary_snowball.go index 2e17bc93501a..e8a424378a89 100644 --- a/snow/consensus/snowball/binary_snowball.go +++ b/snow/consensus/snowball/binary_snowball.go @@ -7,9 +7,9 @@ import "fmt" var _ Binary = (*binarySnowball)(nil) -func newBinarySnowball(beta, choice int) binarySnowball { +func newBinarySnowball(alphaPreference int, alphaConfidence int, beta int, choice int) binarySnowball { return binarySnowball{ - binarySnowflake: newBinarySnowflake(beta, choice), + binarySnowflake: newBinarySnowflake(alphaPreference, alphaConfidence, beta, choice), preference: choice, } } @@ -39,14 +39,14 @@ func (sb *binarySnowball) Preference() int { return sb.preference } -func (sb *binarySnowball) RecordSuccessfulPoll(choice int) { - sb.increasePreferenceStrength(choice) - sb.binarySnowflake.RecordSuccessfulPoll(choice) -} - -func (sb *binarySnowball) RecordPollPreference(choice int) { - sb.increasePreferenceStrength(choice) - sb.binarySnowflake.RecordPollPreference(choice) +func (sb *binarySnowball) RecordPoll(count, choice int) { + if count >= sb.alphaPreference { + sb.preferenceStrength[choice]++ + if sb.preferenceStrength[choice] > sb.preferenceStrength[1-choice] { + sb.preference = choice + } + } + sb.binarySnowflake.RecordPoll(count, choice) } func (sb *binarySnowball) String() string { @@ -57,10 +57,3 @@ func (sb *binarySnowball) String() string { sb.preferenceStrength[1], &sb.binarySnowflake) } - -func (sb *binarySnowball) increasePreferenceStrength(choice int) { - sb.preferenceStrength[choice]++ - if sb.preferenceStrength[choice] > sb.preferenceStrength[1-choice] { - sb.preference = choice - } -} diff --git a/snow/consensus/snowball/binary_snowball_test.go b/snow/consensus/snowball/binary_snowball_test.go index 2c2a8421e043..118b3c7913a7 100644 --- a/snow/consensus/snowball/binary_snowball_test.go +++ b/snow/consensus/snowball/binary_snowball_test.go @@ -15,25 +15,26 @@ func TestBinarySnowball(t *testing.T) { red := 0 blue := 1 + alphaPreference, alphaConfidence := 2, 3 beta := 2 - sb := newBinarySnowball(beta, red) + sb := newBinarySnowball(alphaPreference, alphaConfidence, beta, red) require.Equal(red, sb.Preference()) require.False(sb.Finalized()) - sb.RecordSuccessfulPoll(blue) + sb.RecordPoll(alphaConfidence, blue) require.Equal(blue, sb.Preference()) require.False(sb.Finalized()) - sb.RecordSuccessfulPoll(red) + sb.RecordPoll(alphaConfidence, red) require.Equal(blue, sb.Preference()) require.False(sb.Finalized()) - sb.RecordSuccessfulPoll(blue) + sb.RecordPoll(alphaConfidence, blue) require.Equal(blue, sb.Preference()) require.False(sb.Finalized()) - sb.RecordSuccessfulPoll(blue) + sb.RecordPoll(alphaConfidence, blue) require.Equal(blue, sb.Preference()) require.True(sb.Finalized()) } @@ -44,29 +45,30 @@ func TestBinarySnowballRecordPollPreference(t *testing.T) { red := 0 blue := 1 + alphaPreference, alphaConfidence := 1, 2 beta := 2 - sb := newBinarySnowball(beta, red) + sb := newBinarySnowball(alphaPreference, alphaConfidence, beta, red) require.Equal(red, sb.Preference()) require.False(sb.Finalized()) - sb.RecordSuccessfulPoll(blue) + sb.RecordPoll(alphaConfidence, blue) require.Equal(blue, sb.Preference()) require.False(sb.Finalized()) - sb.RecordSuccessfulPoll(red) + sb.RecordPoll(alphaConfidence, red) require.Equal(blue, sb.Preference()) require.False(sb.Finalized()) - sb.RecordPollPreference(red) + sb.RecordPoll(alphaPreference, red) require.Equal(red, sb.Preference()) require.False(sb.Finalized()) - sb.RecordSuccessfulPoll(red) + sb.RecordPoll(alphaConfidence, red) require.Equal(red, sb.Preference()) require.False(sb.Finalized()) - sb.RecordSuccessfulPoll(red) + sb.RecordPoll(alphaConfidence, red) require.Equal(red, sb.Preference()) require.True(sb.Finalized()) @@ -80,23 +82,24 @@ func TestBinarySnowballRecordUnsuccessfulPoll(t *testing.T) { red := 0 blue := 1 + alphaPreference, alphaConfidence := 1, 2 beta := 2 - sb := newBinarySnowball(beta, red) + sb := newBinarySnowball(alphaPreference, alphaConfidence, beta, red) require.Equal(red, sb.Preference()) require.False(sb.Finalized()) - sb.RecordSuccessfulPoll(blue) + sb.RecordPoll(alphaConfidence, blue) require.Equal(blue, sb.Preference()) require.False(sb.Finalized()) sb.RecordUnsuccessfulPoll() - sb.RecordSuccessfulPoll(blue) + sb.RecordPoll(alphaConfidence, blue) require.Equal(blue, sb.Preference()) require.False(sb.Finalized()) - sb.RecordSuccessfulPoll(blue) + sb.RecordPoll(alphaConfidence, blue) require.Equal(blue, sb.Preference()) require.True(sb.Finalized()) @@ -110,32 +113,33 @@ func TestBinarySnowballAcceptWeirdColor(t *testing.T) { blue := 0 red := 1 + alphaPreference, alphaConfidence := 1, 2 beta := 2 - sb := newBinarySnowball(beta, red) + sb := newBinarySnowball(alphaPreference, alphaConfidence, beta, red) require.Equal(red, sb.Preference()) require.False(sb.Finalized()) - sb.RecordSuccessfulPoll(red) + sb.RecordPoll(alphaConfidence, red) sb.RecordUnsuccessfulPoll() require.Equal(red, sb.Preference()) require.False(sb.Finalized()) - sb.RecordSuccessfulPoll(red) + sb.RecordPoll(alphaConfidence, red) sb.RecordUnsuccessfulPoll() require.Equal(red, sb.Preference()) require.False(sb.Finalized()) - sb.RecordSuccessfulPoll(blue) + sb.RecordPoll(alphaConfidence, blue) require.Equal(red, sb.Preference()) require.False(sb.Finalized()) - sb.RecordSuccessfulPoll(blue) + sb.RecordPoll(alphaConfidence, blue) require.Equal(blue, sb.Preference()) require.True(sb.Finalized()) @@ -150,22 +154,23 @@ func TestBinarySnowballLockColor(t *testing.T) { red := 0 blue := 1 + alphaPreference, alphaConfidence := 1, 2 beta := 1 - sb := newBinarySnowball(beta, red) + sb := newBinarySnowball(alphaPreference, alphaConfidence, beta, red) - sb.RecordSuccessfulPoll(red) + sb.RecordPoll(alphaConfidence, red) require.Equal(red, sb.Preference()) require.True(sb.Finalized()) - sb.RecordSuccessfulPoll(blue) + sb.RecordPoll(alphaConfidence, blue) require.Equal(red, sb.Preference()) require.True(sb.Finalized()) - sb.RecordPollPreference(blue) - sb.RecordSuccessfulPoll(blue) + sb.RecordPoll(alphaPreference, blue) + sb.RecordPoll(alphaConfidence, blue) require.Equal(red, sb.Preference()) require.True(sb.Finalized()) diff --git a/snow/consensus/snowball/binary_snowflake.go b/snow/consensus/snowball/binary_snowflake.go index 5f897af88430..6349fd3975a6 100644 --- a/snow/consensus/snowball/binary_snowflake.go +++ b/snow/consensus/snowball/binary_snowflake.go @@ -7,10 +7,12 @@ import "fmt" var _ Binary = (*binarySnowflake)(nil) -func newBinarySnowflake(beta, choice int) binarySnowflake { +func newBinarySnowflake(alphaPreference, alphaConfidence, beta, choice int) binarySnowflake { return binarySnowflake{ - binarySlush: newBinarySlush(choice), - beta: beta, + binarySlush: newBinarySlush(choice), + alphaPreference: alphaPreference, + alphaConfidence: alphaConfidence, + beta: beta, } } @@ -23,6 +25,12 @@ type binarySnowflake struct { // returned the preference confidence int + // alphaPreference is the threshold required to update the preference + alphaPreference int + + // alphaConfidence is the threshold required to increment the confidence counter + alphaConfidence int + // beta is the number of consecutive successful queries required for // finalization. beta int @@ -32,11 +40,22 @@ type binarySnowflake struct { finalized bool } -func (sf *binarySnowflake) RecordSuccessfulPoll(choice int) { +func (sf *binarySnowflake) RecordPoll(count, choice int) { if sf.finalized { return // This instance is already decided. } + if count < sf.alphaPreference { + sf.RecordUnsuccessfulPoll() + return + } + + if count < sf.alphaConfidence { + sf.confidence = 0 + sf.binarySlush.RecordSuccessfulPoll(choice) + return + } + if preference := sf.Preference(); preference == choice { sf.confidence++ } else { @@ -49,15 +68,6 @@ func (sf *binarySnowflake) RecordSuccessfulPoll(choice int) { sf.binarySlush.RecordSuccessfulPoll(choice) } -func (sf *binarySnowflake) RecordPollPreference(choice int) { - if sf.finalized { - return // This instance is already decided. - } - - sf.confidence = 0 - sf.binarySlush.RecordSuccessfulPoll(choice) -} - func (sf *binarySnowflake) RecordUnsuccessfulPoll() { sf.confidence = 0 } diff --git a/snow/consensus/snowball/binary_snowflake_test.go b/snow/consensus/snowball/binary_snowflake_test.go index 085b94c5f450..16944b5b2082 100644 --- a/snow/consensus/snowball/binary_snowflake_test.go +++ b/snow/consensus/snowball/binary_snowflake_test.go @@ -15,37 +15,38 @@ func TestBinarySnowflake(t *testing.T) { blue := 0 red := 1 + alphaPreference, alphaConfidence := 1, 2 beta := 2 - sf := newBinarySnowflake(beta, red) + sf := newBinarySnowflake(alphaPreference, alphaConfidence, beta, red) require.Equal(red, sf.Preference()) require.False(sf.Finalized()) - sf.RecordSuccessfulPoll(blue) + sf.RecordPoll(alphaConfidence, blue) require.Equal(blue, sf.Preference()) require.False(sf.Finalized()) - sf.RecordSuccessfulPoll(red) + sf.RecordPoll(alphaConfidence, red) require.Equal(red, sf.Preference()) require.False(sf.Finalized()) - sf.RecordSuccessfulPoll(blue) + sf.RecordPoll(alphaConfidence, blue) require.Equal(blue, sf.Preference()) require.False(sf.Finalized()) - sf.RecordPollPreference(red) + sf.RecordPoll(alphaPreference, red) require.Equal(red, sf.Preference()) require.False(sf.Finalized()) - sf.RecordSuccessfulPoll(blue) + sf.RecordPoll(alphaConfidence, blue) require.Equal(blue, sf.Preference()) require.False(sf.Finalized()) - sf.RecordSuccessfulPoll(blue) + sf.RecordPoll(alphaConfidence, blue) require.Equal(blue, sf.Preference()) require.True(sf.Finalized()) } diff --git a/snow/consensus/snowball/consensus.go b/snow/consensus/snowball/consensus.go index befe7a64865b..19df9e75fa9a 100644 --- a/snow/consensus/snowball/consensus.go +++ b/snow/consensus/snowball/consensus.go @@ -47,10 +47,9 @@ type Factory interface { } // Nnary is a snow instance deciding between an unbounded number of values. -// The caller samples k nodes and then calls -// 1. RecordSuccessfulPoll if choice collects >= alphaConfidence votes -// 2. RecordPollPreference if choice collects >= alphaPreference votes -// 3. RecordUnsuccessfulPoll otherwise +// The caller samples k nodes and calls RecordPoll with the result. +// RecordUnsuccessfulPoll resets the confidence counters when one or +// more consecutive polls fail to reach alphaPreference votes. type Nnary interface { fmt.Stringer @@ -60,14 +59,8 @@ type Nnary interface { // Returns the currently preferred choice to be finalized Preference() ids.ID - // RecordSuccessfulPoll records a successful poll towards finalizing the - // specified choice. Assumes the choice was previously added. - RecordSuccessfulPoll(choice ids.ID) - - // RecordPollPreference records a poll that preferred the specified choice - // but did not contribute towards finalizing the specified choice. Assumes - // the choice was previously added. - RecordPollPreference(choice ids.ID) + // RecordPoll records the results of a network poll + RecordPoll(count int, choice ids.ID) // RecordUnsuccessfulPoll resets the snowflake counter of this instance RecordUnsuccessfulPoll() @@ -77,23 +70,17 @@ type Nnary interface { } // Binary is a snow instance deciding between two values. -// The caller samples k nodes and then calls -// 1. RecordSuccessfulPoll if choice collects >= alphaConfidence votes -// 2. RecordPollPreference if choice collects >= alphaPreference votes -// 3. RecordUnsuccessfulPoll otherwise +// The caller samples k nodes and calls RecordPoll with the result. +// RecordUnsuccessfulPoll resets the confidence counters when one or +// more consecutive polls fail to reach alphaPreference votes. type Binary interface { fmt.Stringer // Returns the currently preferred choice to be finalized Preference() int - // RecordSuccessfulPoll records a successful poll towards finalizing the - // specified choice - RecordSuccessfulPoll(choice int) - - // RecordPollPreference records a poll that preferred the specified choice - // but did not contribute towards finalizing the specified choice - RecordPollPreference(choice int) + // RecordPoll records the results of a network poll + RecordPoll(count, choice int) // RecordUnsuccessfulPoll resets the snowflake counter of this instance RecordUnsuccessfulPoll() @@ -103,20 +90,14 @@ type Binary interface { } // Unary is a snow instance deciding on one value. -// The caller samples k nodes and then calls -// 1. RecordSuccessfulPoll if choice collects >= alphaConfidence votes -// 2. RecordPollPreference if choice collects >= alphaPreference votes -// 3. RecordUnsuccessfulPoll otherwise +// The caller samples k nodes and calls RecordPoll with the result. +// RecordUnsuccessfulPoll resets the confidence counters when one or +// more consecutive polls fail to reach alphaPreference votes. type Unary interface { fmt.Stringer - // RecordSuccessfulPoll records a successful poll that reaches an alpha - // confidence threshold. - RecordSuccessfulPoll() - - // RecordPollPreference records a poll that receives an alpha preference - // threshold, but not an alpha confidence threshold. - RecordPollPreference() + // RecordPoll records the results of a network poll + RecordPoll(count int) // RecordUnsuccessfulPoll resets the snowflake counter of this instance RecordUnsuccessfulPoll() diff --git a/snow/consensus/snowball/factory.go b/snow/consensus/snowball/factory.go index cc26f3d28043..eea827202bc3 100644 --- a/snow/consensus/snowball/factory.go +++ b/snow/consensus/snowball/factory.go @@ -13,23 +13,23 @@ var ( type snowballFactory struct{} func (snowballFactory) NewNnary(params Parameters, choice ids.ID) Nnary { - sb := newNnarySnowball(params.Beta, choice) + sb := newNnarySnowball(params.AlphaPreference, params.AlphaConfidence, params.Beta, choice) return &sb } func (snowballFactory) NewUnary(params Parameters) Unary { - sb := newUnarySnowball(params.Beta) + sb := newUnarySnowball(params.AlphaPreference, params.AlphaConfidence, params.Beta) return &sb } type snowflakeFactory struct{} func (snowflakeFactory) NewNnary(params Parameters, choice ids.ID) Nnary { - sf := newNnarySnowflake(params.Beta, choice) + sf := newNnarySnowflake(params.AlphaPreference, params.AlphaConfidence, params.Beta, choice) return &sf } func (snowflakeFactory) NewUnary(params Parameters) Unary { - sf := newUnarySnowflake(params.Beta) + sf := newUnarySnowflake(params.AlphaPreference, params.AlphaConfidence, params.Beta) return &sf } diff --git a/snow/consensus/snowball/flat.go b/snow/consensus/snowball/flat.go index 01b5975cba0a..3d159ca7a352 100644 --- a/snow/consensus/snowball/flat.go +++ b/snow/consensus/snowball/flat.go @@ -28,17 +28,6 @@ type Flat struct { func (f *Flat) RecordPoll(votes bag.Bag[ids.ID]) bool { pollMode, numVotes := votes.Mode() - switch { - // AlphaConfidence is guaranteed to be >= AlphaPreference, so we must check - // if the poll had enough votes to increase the confidence first. - case numVotes >= f.params.AlphaConfidence: - f.RecordSuccessfulPoll(pollMode) - return true - case numVotes >= f.params.AlphaPreference: - f.RecordPollPreference(pollMode) - return true - default: - f.RecordUnsuccessfulPoll() - return false - } + f.Nnary.RecordPoll(numVotes, pollMode) + return numVotes >= f.params.AlphaPreference } diff --git a/snow/consensus/snowball/nnary_snowball.go b/snow/consensus/snowball/nnary_snowball.go index 723d8a62f7b8..98b63cd9dbcb 100644 --- a/snow/consensus/snowball/nnary_snowball.go +++ b/snow/consensus/snowball/nnary_snowball.go @@ -11,9 +11,9 @@ import ( var _ Nnary = (*nnarySnowball)(nil) -func newNnarySnowball(beta int, choice ids.ID) nnarySnowball { +func newNnarySnowball(alphaPreference, alphaConfidence, beta int, choice ids.ID) nnarySnowball { return nnarySnowball{ - nnarySnowflake: newNnarySnowflake(beta, choice), + nnarySnowflake: newNnarySnowflake(alphaPreference, alphaConfidence, beta, choice), preference: choice, preferenceStrength: make(map[ids.ID]int), } @@ -47,27 +47,20 @@ func (sb *nnarySnowball) Preference() ids.ID { return sb.preference } -func (sb *nnarySnowball) RecordSuccessfulPoll(choice ids.ID) { - sb.increasePreferenceStrength(choice) - sb.nnarySnowflake.RecordSuccessfulPoll(choice) -} +func (sb *nnarySnowball) RecordPoll(count int, choice ids.ID) { + if count >= sb.alphaPreference { + preferenceStrength := sb.preferenceStrength[choice] + 1 + sb.preferenceStrength[choice] = preferenceStrength -func (sb *nnarySnowball) RecordPollPreference(choice ids.ID) { - sb.increasePreferenceStrength(choice) - sb.nnarySnowflake.RecordPollPreference(choice) + if preferenceStrength > sb.maxPreferenceStrength { + sb.preference = choice + sb.maxPreferenceStrength = preferenceStrength + } + } + sb.nnarySnowflake.RecordPoll(count, choice) } func (sb *nnarySnowball) String() string { return fmt.Sprintf("SB(Preference = %s, PreferenceStrength = %d, %s)", sb.preference, sb.maxPreferenceStrength, &sb.nnarySnowflake) } - -func (sb *nnarySnowball) increasePreferenceStrength(choice ids.ID) { - preferenceStrength := sb.preferenceStrength[choice] + 1 - sb.preferenceStrength[choice] = preferenceStrength - - if preferenceStrength > sb.maxPreferenceStrength { - sb.preference = choice - sb.maxPreferenceStrength = preferenceStrength - } -} diff --git a/snow/consensus/snowball/nnary_snowball_test.go b/snow/consensus/snowball/nnary_snowball_test.go index e01372b1019e..466337bc00d5 100644 --- a/snow/consensus/snowball/nnary_snowball_test.go +++ b/snow/consensus/snowball/nnary_snowball_test.go @@ -12,40 +12,41 @@ import ( func TestNnarySnowball(t *testing.T) { require := require.New(t) + alphaPreference, alphaConfidence := 1, 2 beta := 2 - sb := newNnarySnowball(beta, Red) + sb := newNnarySnowball(alphaPreference, alphaConfidence, beta, Red) sb.Add(Blue) sb.Add(Green) require.Equal(Red, sb.Preference()) require.False(sb.Finalized()) - sb.RecordSuccessfulPoll(Blue) + sb.RecordPoll(alphaConfidence, Blue) require.Equal(Blue, sb.Preference()) require.False(sb.Finalized()) - sb.RecordSuccessfulPoll(Red) + sb.RecordPoll(alphaConfidence, Red) require.Equal(Blue, sb.Preference()) require.False(sb.Finalized()) - sb.RecordPollPreference(Red) + sb.RecordPoll(alphaPreference, Red) require.Equal(Red, sb.Preference()) require.False(sb.Finalized()) - sb.RecordSuccessfulPoll(Red) + sb.RecordPoll(alphaConfidence, Red) require.Equal(Red, sb.Preference()) require.False(sb.Finalized()) - sb.RecordPollPreference(Blue) + sb.RecordPoll(alphaPreference, Blue) require.Equal(Red, sb.Preference()) require.False(sb.Finalized()) - sb.RecordSuccessfulPoll(Blue) + sb.RecordPoll(alphaConfidence, Blue) require.Equal(Red, sb.Preference()) require.False(sb.Finalized()) - sb.RecordSuccessfulPoll(Blue) + sb.RecordPoll(alphaConfidence, Blue) require.Equal(Blue, sb.Preference()) require.True(sb.Finalized()) } @@ -53,14 +54,15 @@ func TestNnarySnowball(t *testing.T) { func TestVirtuousNnarySnowball(t *testing.T) { require := require.New(t) + alphaPreference, alphaConfidence := 1, 2 beta := 1 - sb := newNnarySnowball(beta, Red) + sb := newNnarySnowball(alphaPreference, alphaConfidence, beta, Red) require.Equal(Red, sb.Preference()) require.False(sb.Finalized()) - sb.RecordSuccessfulPoll(Red) + sb.RecordPoll(alphaConfidence, Red) require.Equal(Red, sb.Preference()) require.True(sb.Finalized()) } @@ -68,26 +70,27 @@ func TestVirtuousNnarySnowball(t *testing.T) { func TestNarySnowballRecordUnsuccessfulPoll(t *testing.T) { require := require.New(t) + alphaPreference, alphaConfidence := 1, 2 beta := 2 - sb := newNnarySnowball(beta, Red) + sb := newNnarySnowball(alphaPreference, alphaConfidence, beta, Red) sb.Add(Blue) require.Equal(Red, sb.Preference()) require.False(sb.Finalized()) - sb.RecordSuccessfulPoll(Blue) + sb.RecordPoll(alphaConfidence, Blue) require.Equal(Blue, sb.Preference()) require.False(sb.Finalized()) sb.RecordUnsuccessfulPoll() - sb.RecordSuccessfulPoll(Blue) + sb.RecordPoll(alphaConfidence, Blue) require.Equal(Blue, sb.Preference()) require.False(sb.Finalized()) - sb.RecordSuccessfulPoll(Blue) + sb.RecordPoll(alphaConfidence, Blue) require.Equal(Blue, sb.Preference()) require.True(sb.Finalized()) @@ -96,7 +99,7 @@ func TestNarySnowballRecordUnsuccessfulPoll(t *testing.T) { require.Equal(expected, sb.String()) for i := 0; i < 4; i++ { - sb.RecordSuccessfulPoll(Red) + sb.RecordPoll(alphaConfidence, Red) require.Equal(Blue, sb.Preference()) require.True(sb.Finalized()) @@ -106,19 +109,20 @@ func TestNarySnowballRecordUnsuccessfulPoll(t *testing.T) { func TestNarySnowballDifferentSnowflakeColor(t *testing.T) { require := require.New(t) + alphaPreference, alphaConfidence := 1, 2 beta := 2 - sb := newNnarySnowball(beta, Red) + sb := newNnarySnowball(alphaPreference, alphaConfidence, beta, Red) sb.Add(Blue) require.Equal(Red, sb.Preference()) require.False(sb.Finalized()) - sb.RecordSuccessfulPoll(Blue) + sb.RecordPoll(alphaConfidence, Blue) require.Equal(Blue, sb.nnarySnowflake.Preference()) - sb.RecordSuccessfulPoll(Red) + sb.RecordPoll(alphaConfidence, Red) require.Equal(Blue, sb.Preference()) require.Equal(Red, sb.nnarySnowflake.Preference()) diff --git a/snow/consensus/snowball/nnary_snowflake.go b/snow/consensus/snowball/nnary_snowflake.go index 5fb339afaa43..9433078e36b8 100644 --- a/snow/consensus/snowball/nnary_snowflake.go +++ b/snow/consensus/snowball/nnary_snowflake.go @@ -11,10 +11,12 @@ import ( var _ Nnary = (*nnarySnowflake)(nil) -func newNnarySnowflake(beta int, choice ids.ID) nnarySnowflake { +func newNnarySnowflake(alphaPreference, alphaConfidence, beta int, choice ids.ID) nnarySnowflake { return nnarySnowflake{ - nnarySlush: newNnarySlush(choice), - beta: beta, + nnarySlush: newNnarySlush(choice), + alphaPreference: alphaPreference, + alphaConfidence: alphaConfidence, + beta: beta, } } @@ -28,6 +30,12 @@ type nnarySnowflake struct { // finalization. beta int + // alphaPreference is the threshold required to update the preference + alphaPreference int + + // alphaConfidence is the threshold required to increment the confidence counter + alphaConfidence int + // confidence tracks the number of successful polls in a row that have // returned the preference confidence int @@ -39,11 +47,22 @@ type nnarySnowflake struct { func (*nnarySnowflake) Add(_ ids.ID) {} -func (sf *nnarySnowflake) RecordSuccessfulPoll(choice ids.ID) { +func (sf *nnarySnowflake) RecordPoll(count int, choice ids.ID) { if sf.finalized { return // This instance is already decided. } + if count < sf.alphaPreference { + sf.RecordUnsuccessfulPoll() + return + } + + if count < sf.alphaConfidence { + sf.confidence = 0 + sf.nnarySlush.RecordSuccessfulPoll(choice) + return + } + if preference := sf.Preference(); preference == choice { sf.confidence++ } else { @@ -56,15 +75,6 @@ func (sf *nnarySnowflake) RecordSuccessfulPoll(choice ids.ID) { sf.nnarySlush.RecordSuccessfulPoll(choice) } -func (sf *nnarySnowflake) RecordPollPreference(choice ids.ID) { - if sf.finalized { - return // This instance is already decided. - } - - sf.confidence = 0 - sf.nnarySlush.RecordSuccessfulPoll(choice) -} - func (sf *nnarySnowflake) RecordUnsuccessfulPoll() { sf.confidence = 0 } diff --git a/snow/consensus/snowball/nnary_snowflake_test.go b/snow/consensus/snowball/nnary_snowflake_test.go index 56e878974b5d..714a6bae9b07 100644 --- a/snow/consensus/snowball/nnary_snowflake_test.go +++ b/snow/consensus/snowball/nnary_snowflake_test.go @@ -12,36 +12,37 @@ import ( func TestNnarySnowflake(t *testing.T) { require := require.New(t) + alphaPreference, alphaConfidence := 1, 2 beta := 2 - sf := newNnarySnowflake(beta, Red) + sf := newNnarySnowflake(alphaPreference, alphaConfidence, beta, Red) sf.Add(Blue) sf.Add(Green) require.Equal(Red, sf.Preference()) require.False(sf.Finalized()) - sf.RecordSuccessfulPoll(Blue) + sf.RecordPoll(alphaConfidence, Blue) require.Equal(Blue, sf.Preference()) require.False(sf.Finalized()) - sf.RecordPollPreference(Red) + sf.RecordPoll(alphaPreference, Red) require.Equal(Red, sf.Preference()) require.False(sf.Finalized()) - sf.RecordSuccessfulPoll(Red) + sf.RecordPoll(alphaConfidence, Red) require.Equal(Red, sf.Preference()) require.False(sf.Finalized()) - sf.RecordSuccessfulPoll(Red) + sf.RecordPoll(alphaConfidence, Red) require.Equal(Red, sf.Preference()) require.True(sf.Finalized()) - sf.RecordPollPreference(Blue) + sf.RecordPoll(alphaPreference, Blue) require.Equal(Red, sf.Preference()) require.True(sf.Finalized()) - sf.RecordSuccessfulPoll(Blue) + sf.RecordPoll(alphaConfidence, Blue) require.Equal(Red, sf.Preference()) require.True(sf.Finalized()) } @@ -49,9 +50,10 @@ func TestNnarySnowflake(t *testing.T) { func TestNnarySnowflakeConfidenceReset(t *testing.T) { require := require.New(t) + alphaPreference, alphaConfidence := 1, 2 beta := 4 - sf := newNnarySnowflake(beta, Red) + sf := newNnarySnowflake(alphaPreference, alphaConfidence, beta, Red) sf.Add(Blue) sf.Add(Green) @@ -60,20 +62,20 @@ func TestNnarySnowflakeConfidenceReset(t *testing.T) { // Increase Blue's confidence without finalizing for i := 0; i < beta-1; i++ { - sf.RecordSuccessfulPoll(Blue) + sf.RecordPoll(alphaConfidence, Blue) require.Equal(Blue, sf.Preference()) require.False(sf.Finalized()) } // Increase Red's confidence without finalizing for i := 0; i < beta-1; i++ { - sf.RecordSuccessfulPoll(Red) + sf.RecordPoll(alphaConfidence, Red) require.Equal(Red, sf.Preference()) require.False(sf.Finalized()) } // One more round of voting for Red should accept Red - sf.RecordSuccessfulPoll(Red) + sf.RecordPoll(alphaConfidence, Red) require.Equal(Red, sf.Preference()) require.True(sf.Finalized()) } @@ -81,17 +83,18 @@ func TestNnarySnowflakeConfidenceReset(t *testing.T) { func TestVirtuousNnarySnowflake(t *testing.T) { require := require.New(t) + alphaPreference, alphaConfidence := 1, 2 beta := 2 - sb := newNnarySnowflake(beta, Red) + sb := newNnarySnowflake(alphaPreference, alphaConfidence, beta, Red) require.Equal(Red, sb.Preference()) require.False(sb.Finalized()) - sb.RecordSuccessfulPoll(Red) + sb.RecordPoll(alphaConfidence, Red) require.Equal(Red, sb.Preference()) require.False(sb.Finalized()) - sb.RecordSuccessfulPoll(Red) + sb.RecordPoll(alphaConfidence, Red) require.Equal(Red, sb.Preference()) require.True(sb.Finalized()) } diff --git a/snow/consensus/snowball/tree.go b/snow/consensus/snowball/tree.go index ff8656f60a81..c6773e30c54e 100644 --- a/snow/consensus/snowball/tree.go +++ b/snow/consensus/snowball/tree.go @@ -416,22 +416,15 @@ func (u *unaryNode) RecordPoll(votes bag.Bag[ids.ID], reset bool) (node, bool) { u.shouldReset = true // Make sure my child is also reset correctly } - switch numVotes := votes.Len(); { - case numVotes >= u.tree.params.AlphaConfidence: - // I got enough votes to increase my confidence - u.snow.RecordSuccessfulPoll() - case numVotes >= u.tree.params.AlphaPreference: - // I got enough votes to update my preference, but not increase my - // confidence. - u.snow.RecordPollPreference() - default: - // I didn't get enough votes, I must reset and my child must reset as - // well + numVotes := votes.Len() + if numVotes < u.tree.params.AlphaPreference { u.snow.RecordUnsuccessfulPoll() u.shouldReset = true return u, false } + u.snow.RecordPoll(numVotes) + if u.child != nil { // We are guaranteed that u.commonPrefix will equal // u.child.DecidedPrefix(). Otherwise, there must have been a @@ -538,21 +531,16 @@ func (b *binaryNode) RecordPoll(votes bag.Bag[ids.ID], reset bool) (node, bool) b.shouldReset[1-bit] = true // They didn't get the threshold of votes prunedVotes := splitVotes[bit] - switch numVotes := prunedVotes.Len(); { - case numVotes >= b.tree.params.AlphaConfidence: - // I got enough votes to increase my confidence. - b.snow.RecordSuccessfulPoll(bit) - case numVotes >= b.tree.params.AlphaPreference: - // I got enough votes to update my preference, but not increase my - // confidence. - b.snow.RecordPollPreference(bit) - default: + numVotes := prunedVotes.Len() + if numVotes < b.tree.params.AlphaPreference { b.snow.RecordUnsuccessfulPoll() // The winning child didn't get enough votes either b.shouldReset[bit] = true return b, false } + b.snow.RecordPoll(numVotes, bit) + if child := b.children[bit]; child != nil { newChild, _ := child.RecordPoll(prunedVotes, b.shouldReset[bit]) if b.snow.Finalized() { diff --git a/snow/consensus/snowball/unary_snowball.go b/snow/consensus/snowball/unary_snowball.go index a6e83971fc19..2c15a58cf971 100644 --- a/snow/consensus/snowball/unary_snowball.go +++ b/snow/consensus/snowball/unary_snowball.go @@ -7,9 +7,9 @@ import "fmt" var _ Unary = (*unarySnowball)(nil) -func newUnarySnowball(beta int) unarySnowball { +func newUnarySnowball(alphaPreference, alphaConfidence, beta int) unarySnowball { return unarySnowball{ - unarySnowflake: newUnarySnowflake(beta), + unarySnowflake: newUnarySnowflake(alphaPreference, alphaConfidence, beta), } } @@ -22,23 +22,22 @@ type unarySnowball struct { preferenceStrength int } -func (sb *unarySnowball) RecordSuccessfulPoll() { - sb.preferenceStrength++ - sb.unarySnowflake.RecordSuccessfulPoll() -} - -func (sb *unarySnowball) RecordPollPreference() { - sb.preferenceStrength++ - sb.unarySnowflake.RecordUnsuccessfulPoll() +func (sb *unarySnowball) RecordPoll(count int) { + if count >= sb.alphaPreference { + sb.preferenceStrength++ + } + sb.unarySnowflake.RecordPoll(count) } func (sb *unarySnowball) Extend(choice int) Binary { bs := &binarySnowball{ binarySnowflake: binarySnowflake{ - binarySlush: binarySlush{preference: choice}, - confidence: sb.confidence, - beta: sb.beta, - finalized: sb.Finalized(), + binarySlush: binarySlush{preference: choice}, + confidence: sb.confidence, + alphaPreference: sb.alphaPreference, + alphaConfidence: sb.alphaConfidence, + beta: sb.beta, + finalized: sb.Finalized(), }, preference: choice, } diff --git a/snow/consensus/snowball/unary_snowball_test.go b/snow/consensus/snowball/unary_snowball_test.go index 81fcfaa8f8a6..1178ca6bdaa9 100644 --- a/snow/consensus/snowball/unary_snowball_test.go +++ b/snow/consensus/snowball/unary_snowball_test.go @@ -20,23 +20,24 @@ func UnarySnowballStateTest(t *testing.T, sb *unarySnowball, expectedPreferenceS func TestUnarySnowball(t *testing.T) { require := require.New(t) + alphaPreference, alphaConfidence := 1, 2 beta := 2 - sb := newUnarySnowball(beta) + sb := newUnarySnowball(alphaPreference, alphaConfidence, beta) - sb.RecordSuccessfulPoll() + sb.RecordPoll(alphaConfidence) UnarySnowballStateTest(t, &sb, 1, 1, false) - sb.RecordPollPreference() + sb.RecordPoll(alphaPreference) UnarySnowballStateTest(t, &sb, 2, 0, false) - sb.RecordSuccessfulPoll() + sb.RecordPoll(alphaConfidence) UnarySnowballStateTest(t, &sb, 3, 1, false) sb.RecordUnsuccessfulPoll() UnarySnowballStateTest(t, &sb, 3, 0, false) - sb.RecordSuccessfulPoll() + sb.RecordPoll(alphaConfidence) UnarySnowballStateTest(t, &sb, 4, 1, false) sbCloneIntf := sb.Clone() @@ -54,18 +55,18 @@ func TestUnarySnowball(t *testing.T) { for i := 0; i < 5; i++ { require.Zero(binarySnowball.Preference()) require.False(binarySnowball.Finalized()) - binarySnowball.RecordSuccessfulPoll(1) + binarySnowball.RecordPoll(alphaConfidence, 1) binarySnowball.RecordUnsuccessfulPoll() } require.Equal(1, binarySnowball.Preference()) require.False(binarySnowball.Finalized()) - binarySnowball.RecordSuccessfulPoll(1) + binarySnowball.RecordPoll(alphaConfidence, 1) require.Equal(1, binarySnowball.Preference()) require.False(binarySnowball.Finalized()) - binarySnowball.RecordSuccessfulPoll(1) + binarySnowball.RecordPoll(alphaConfidence, 1) require.Equal(1, binarySnowball.Preference()) require.True(binarySnowball.Finalized()) diff --git a/snow/consensus/snowball/unary_snowflake.go b/snow/consensus/snowball/unary_snowflake.go index 1e4c78d31e87..edf5fdbd256f 100644 --- a/snow/consensus/snowball/unary_snowflake.go +++ b/snow/consensus/snowball/unary_snowflake.go @@ -7,9 +7,11 @@ import "fmt" var _ Unary = (*unarySnowflake)(nil) -func newUnarySnowflake(beta int) unarySnowflake { +func newUnarySnowflake(alphaPreference, alphaConfidence, beta int) unarySnowflake { return unarySnowflake{ - beta: beta, + alphaPreference: alphaPreference, + alphaConfidence: alphaConfidence, + beta: beta, } } @@ -19,6 +21,12 @@ type unarySnowflake struct { // finalization. beta int + // alphaPreference is the threshold required to update the preference + alphaPreference int + + // alphaConfidence is the threshold required to increment the confidence counter + alphaConfidence int + // confidence tracks the number of successful polls in a row that have // returned the preference confidence int @@ -28,18 +36,16 @@ type unarySnowflake struct { finalized bool } -func (sf *unarySnowflake) RecordSuccessfulPoll() { +func (sf *unarySnowflake) RecordPoll(count int) { + if count < sf.alphaConfidence { + sf.RecordUnsuccessfulPoll() + return + } + sf.confidence++ sf.finalized = sf.finalized || sf.confidence >= sf.beta } -// RecordPollPreference fails to reach an alpha threshold to increase our -// confidence, so this calls RecordUnsuccessfulPoll to reset the confidence -// counter. -func (sf *unarySnowflake) RecordPollPreference() { - sf.RecordUnsuccessfulPoll() -} - func (sf *unarySnowflake) RecordUnsuccessfulPoll() { sf.confidence = 0 } @@ -50,10 +56,12 @@ func (sf *unarySnowflake) Finalized() bool { func (sf *unarySnowflake) Extend(choice int) Binary { return &binarySnowflake{ - binarySlush: binarySlush{preference: choice}, - confidence: sf.confidence, - beta: sf.beta, - finalized: sf.finalized, + binarySlush: binarySlush{preference: choice}, + confidence: sf.confidence, + alphaPreference: sf.alphaPreference, + alphaConfidence: sf.alphaConfidence, + beta: sf.beta, + finalized: sf.finalized, } } diff --git a/snow/consensus/snowball/unary_snowflake_test.go b/snow/consensus/snowball/unary_snowflake_test.go index cbfd620c9448..6a3348f53502 100644 --- a/snow/consensus/snowball/unary_snowflake_test.go +++ b/snow/consensus/snowball/unary_snowflake_test.go @@ -19,17 +19,18 @@ func UnarySnowflakeStateTest(t *testing.T, sf *unarySnowflake, expectedConfidenc func TestUnarySnowflake(t *testing.T) { require := require.New(t) + alphaPreference, alphaConfidence := 1, 2 beta := 2 - sf := newUnarySnowflake(beta) + sf := newUnarySnowflake(alphaPreference, alphaConfidence, beta) - sf.RecordSuccessfulPoll() + sf.RecordPoll(alphaConfidence) UnarySnowflakeStateTest(t, &sf, 1, false) sf.RecordUnsuccessfulPoll() UnarySnowflakeStateTest(t, &sf, 0, false) - sf.RecordSuccessfulPoll() + sf.RecordPoll(alphaConfidence) UnarySnowflakeStateTest(t, &sf, 1, false) sfCloneIntf := sf.Clone() @@ -42,21 +43,21 @@ func TestUnarySnowflake(t *testing.T) { binarySnowflake.RecordUnsuccessfulPoll() - binarySnowflake.RecordSuccessfulPoll(1) + binarySnowflake.RecordPoll(alphaConfidence, 1) require.False(binarySnowflake.Finalized()) - binarySnowflake.RecordSuccessfulPoll(1) + binarySnowflake.RecordPoll(alphaConfidence, 1) require.Equal(1, binarySnowflake.Preference()) require.True(binarySnowflake.Finalized()) - sf.RecordSuccessfulPoll() + sf.RecordPoll(alphaConfidence) UnarySnowflakeStateTest(t, &sf, 2, true) sf.RecordUnsuccessfulPoll() UnarySnowflakeStateTest(t, &sf, 0, true) - sf.RecordSuccessfulPoll() + sf.RecordPoll(alphaConfidence) UnarySnowflakeStateTest(t, &sf, 1, true) }