Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement error driven snowflake hardcoded to support a single beta #2978

Merged
merged 8 commits into from
Jun 6, 2024
67 changes: 42 additions & 25 deletions snow/consensus/snowball/binary_snowflake.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,38 @@ func newBinarySnowflake(alphaPreference, alphaConfidence, beta, choice int) bina
return binarySnowflake{
binarySlush: newBinarySlush(choice),
alphaPreference: alphaPreference,
alphaConfidence: alphaConfidence,
beta: beta,
terminationConditions: []terminationCondition{
{
alphaConfidence: alphaConfidence,
beta: beta,
},
},
confidence: make([]int, 1),
marun marked this conversation as resolved.
Show resolved Hide resolved
}
}

// binarySnowflake is the implementation of a binary snowflake instance
// Invariant:
marun marked this conversation as resolved.
Show resolved Hide resolved
// len(terminationConditions) == len(confidence)
// terminationConditions[i].alphaConfidence < terminationConditions[i+1].alphaConfidence
// terminationConditions[i].beta <= terminationConditions[i+1].beta
// confidence[i] >= confidence[i+1] (except after finalizing due to early termination)
type binarySnowflake struct {
// wrap the binary slush logic
binarySlush

// confidence tracks the number of successful polls in a row that have
// 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
// terminationConditions gives the ascending ordered list of alphaConfidence values
// required to increment the corresponding confidence counter.
// The corresponding beta values give the threshold required to finalize this instance.
terminationConditions []terminationCondition

// beta is the number of consecutive successful queries required for
// finalization.
beta int
// confidence is the number of consecutive succcessful polls for a given
// alphaConfidence threshold.
// This instance finalizes when confidence[i] >= terminationConditions[i].beta for any i
confidence []int

// finalized prevents the state from changing after the required number of
// consecutive polls has been reached
Expand All @@ -50,26 +59,34 @@ func (sf *binarySnowflake) RecordPoll(count, choice int) {
return
}

if count < sf.alphaConfidence {
sf.confidence = 0
// If I need to change my preference, record the new preference
// and reset all my confidence counters.
if choice != sf.Preference() {
sf.binarySlush.RecordSuccessfulPoll(choice)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as this other place... just feels odd only to call RecordSuccessfulPoll if it is for a different color...

return
clear(sf.confidence)
marun marked this conversation as resolved.
Show resolved Hide resolved
}

if preference := sf.Preference(); preference == choice {
sf.confidence++
} else {
// confidence is set to 1 because there has already been 1 successful
// poll, namely this poll.
sf.confidence = 1
for i, terminationCondition := range sf.terminationConditions {
// If I did not reach this alpha threshold, I did not
// reach any more alpha thresholds.
// Clear the remaining confidence counters.
if count < terminationCondition.alphaConfidence {
clear(sf.confidence[i:])
return
}

// I reached this alpha threshold, increment the confidence counter
// and check if I can finalize.
sf.confidence[i]++
if sf.confidence[i] >= terminationCondition.beta {
marun marked this conversation as resolved.
Show resolved Hide resolved
sf.finalized = true
return
}
}

sf.finalized = sf.confidence >= sf.beta
sf.binarySlush.RecordSuccessfulPoll(choice)
}

func (sf *binarySnowflake) RecordUnsuccessfulPoll() {
sf.confidence = 0
clear(sf.confidence)
}

func (sf *binarySnowflake) Finalized() bool {
Expand All @@ -78,7 +95,7 @@ func (sf *binarySnowflake) Finalized() bool {

func (sf *binarySnowflake) String() string {
return fmt.Sprintf("SF(Confidence = %d, Finalized = %v, %s)",
sf.confidence,
sf.confidence[0],
marun marked this conversation as resolved.
Show resolved Hide resolved
sf.finalized,
&sf.binarySlush)
}
67 changes: 42 additions & 25 deletions snow/consensus/snowball/nnary_snowflake.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,39 @@ func newNnarySnowflake(alphaPreference, alphaConfidence, beta int, choice ids.ID
return nnarySnowflake{
nnarySlush: newNnarySlush(choice),
alphaPreference: alphaPreference,
alphaConfidence: alphaConfidence,
beta: beta,
terminationConditions: []terminationCondition{
{
alphaConfidence: alphaConfidence,
beta: beta,
},
},
confidence: make([]int, 1),
}
}

// nnarySnowflake is the implementation of a snowflake instance with an
// unbounded number of choices
// Invariant:
// len(terminationConditions) == len(confidence)
// terminationConditions[i].alphaConfidence < terminationConditions[i+1].alphaConfidence
// terminationConditions[i].beta <= terminationConditions[i+1].beta
// confidence[i] >= confidence[i+1] (except after finalizing due to early termination)
type nnarySnowflake struct {
// wrap the n-nary slush logic
nnarySlush

// beta is the number of consecutive successful queries required for
// 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
// terminationConditions gives the ascending ordered list of alphaConfidence values
// required to increment the corresponding confidence counter.
// The corresponding beta values give the threshold required to finalize this instance.
terminationConditions []terminationCondition

// confidence tracks the number of successful polls in a row that have
// returned the preference
confidence int
// confidence is the number of consecutive succcessful polls for a given
// alphaConfidence threshold.
// This instance finalizes when confidence[i] >= terminationConditions[i].beta for any i
confidence []int

// finalized prevents the state from changing after the required number of
// consecutive polls has been reached
Expand All @@ -57,26 +66,34 @@ func (sf *nnarySnowflake) RecordPoll(count int, choice ids.ID) {
return
}

if count < sf.alphaConfidence {
sf.confidence = 0
// If I need to change my preference, record the new preference
// and reset all my confidence counters.
if choice != sf.Preference() {
clear(sf.confidence)
sf.nnarySlush.RecordSuccessfulPoll(choice)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this doesn't matter (and tbh might be an efficiency improvement) but it feels weird not to call nnarySlush. RecordSuccessfulPoll in every case here... Might just be me though

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't feel strongly about this. I can't imagine it's a meaningful performance improvement, so will go ahead and update it in both places.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed and updated the corresponding comment in both places

return
}

if preference := sf.Preference(); preference == choice {
sf.confidence++
} else {
// confidence is set to 1 because there has already been 1 successful
// poll, namely this poll.
sf.confidence = 1
for i, terminationCondition := range sf.terminationConditions {
// If I did not reach this alpha threshold, I did not
// reach any more alpha thresholds.
// Clear the remaining confidence counters.
if count < terminationCondition.alphaConfidence {
clear(sf.confidence[i:])
return
}

// I reached this alpha threshold, increment the confidence counter
// and check if I can finalize.
sf.confidence[i]++
if sf.confidence[i] >= terminationCondition.beta {
sf.finalized = true
return
}
}

sf.finalized = sf.confidence >= sf.beta
sf.nnarySlush.RecordSuccessfulPoll(choice)
}

func (sf *nnarySnowflake) RecordUnsuccessfulPoll() {
sf.confidence = 0
clear(sf.confidence)
}

func (sf *nnarySnowflake) Finalized() bool {
Expand All @@ -85,7 +102,7 @@ func (sf *nnarySnowflake) Finalized() bool {

func (sf *nnarySnowflake) String() string {
return fmt.Sprintf("SF(Confidence = %d, Finalized = %v, %s)",
sf.confidence,
sf.confidence[0],
sf.finalized,
&sf.nnarySlush)
}
5 changes: 5 additions & 0 deletions snow/consensus/snowball/parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,8 @@ func (p Parameters) MinPercentConnectedHealthy() float64 {
alphaRatio := float64(p.AlphaConfidence) / float64(p.K)
return alphaRatio*(1-MinPercentConnectedBuffer) + MinPercentConnectedBuffer
}

type terminationCondition struct {
alphaConfidence int
beta int
}
16 changes: 10 additions & 6 deletions snow/consensus/snowball/unary_snowball.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@ func (sb *unarySnowball) RecordPoll(count int) {
}

func (sb *unarySnowball) Extend(choice int) Binary {
confidence := make([]int, len(sb.confidence))
copy(confidence, sb.confidence)
bs := &binarySnowball{
binarySnowflake: binarySnowflake{
binarySlush: binarySlush{preference: choice},
confidence: sb.confidence,
alphaPreference: sb.alphaPreference,
alphaConfidence: sb.alphaConfidence,
beta: sb.beta,
finalized: sb.Finalized(),
binarySlush: binarySlush{preference: choice},
confidence: confidence,
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
alphaPreference: sb.alphaPreference,
terminationConditions: sb.terminationConditions,
finalized: sb.Finalized(),
},
preference: choice,
}
Expand All @@ -47,6 +48,9 @@ func (sb *unarySnowball) Extend(choice int) Binary {

func (sb *unarySnowball) Clone() Unary {
newSnowball := *sb
// Copy the confidence slice
newSnowball.confidence = make([]int, len(sb.confidence))
copy(newSnowball.confidence, sb.confidence)
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
return &newSnowball
}

Expand Down
14 changes: 7 additions & 7 deletions snow/consensus/snowball/unary_snowball_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/require"
)

func UnarySnowballStateTest(t *testing.T, sb *unarySnowball, expectedPreferenceStrength, expectedConfidence int, expectedFinalized bool) {
func UnarySnowballStateTest(t *testing.T, sb *unarySnowball, expectedPreferenceStrength int, expectedConfidence []int, expectedFinalized bool) {
require := require.New(t)

require.Equal(expectedPreferenceStrength, sb.preferenceStrength)
Expand All @@ -26,25 +26,25 @@ func TestUnarySnowball(t *testing.T) {
sb := newUnarySnowball(alphaPreference, alphaConfidence, beta)

sb.RecordPoll(alphaConfidence)
UnarySnowballStateTest(t, &sb, 1, 1, false)
UnarySnowballStateTest(t, &sb, 1, []int{1}, false)

sb.RecordPoll(alphaPreference)
UnarySnowballStateTest(t, &sb, 2, 0, false)
UnarySnowballStateTest(t, &sb, 2, []int{0}, false)

sb.RecordPoll(alphaConfidence)
UnarySnowballStateTest(t, &sb, 3, 1, false)
UnarySnowballStateTest(t, &sb, 3, []int{1}, false)

sb.RecordUnsuccessfulPoll()
UnarySnowballStateTest(t, &sb, 3, 0, false)
UnarySnowballStateTest(t, &sb, 3, []int{0}, false)

sb.RecordPoll(alphaConfidence)
UnarySnowballStateTest(t, &sb, 4, 1, false)
UnarySnowballStateTest(t, &sb, 4, []int{1}, false)

sbCloneIntf := sb.Clone()
require.IsType(&unarySnowball{}, sbCloneIntf)
sbClone := sbCloneIntf.(*unarySnowball)

UnarySnowballStateTest(t, sbClone, 4, 1, false)
UnarySnowballStateTest(t, sbClone, 4, []int{1}, false)

binarySnowball := sbClone.Extend(0)

Expand Down
Loading
Loading