Skip to content

Commit

Permalink
Implement error driven snowflake hardcoded with a single beta (#2978)
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronbuchwald authored Jun 6, 2024
1 parent f1a9d2a commit 783fdfc
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 100 deletions.
69 changes: 43 additions & 26 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),
}
}

// binarySnowflake is the implementation of a binary snowflake instance
// 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 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
sf.binarySlush.RecordSuccessfulPoll(choice)
return
// If I am changing my preference, reset confidence counters
// before recording a successful poll on the slush instance.
if choice != sf.Preference() {
clear(sf.confidence)
}
sf.binarySlush.RecordSuccessfulPoll(choice)

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.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],
sf.finalized,
&sf.binarySlush)
}
69 changes: 43 additions & 26 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
sf.nnarySlush.RecordSuccessfulPoll(choice)
return
// If I am changing my preference, reset confidence counters
// before recording a successful poll on the slush instance.
if choice != sf.Preference() {
clear(sf.confidence)
}
sf.nnarySlush.RecordSuccessfulPoll(choice)

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
}
17 changes: 10 additions & 7 deletions snow/consensus/snowball/unary_snowball.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@

package snowball

import "fmt"
import (
"fmt"
"slices"
)

var _ Unary = (*unarySnowball)(nil)

Expand Down Expand Up @@ -32,12 +35,11 @@ func (sb *unarySnowball) RecordPoll(count int) {
func (sb *unarySnowball) Extend(choice int) Binary {
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: slices.Clone(sb.confidence),
alphaPreference: sb.alphaPreference,
terminationConditions: sb.terminationConditions,
finalized: sb.Finalized(),
},
preference: choice,
}
Expand All @@ -47,6 +49,7 @@ func (sb *unarySnowball) Extend(choice int) Binary {

func (sb *unarySnowball) Clone() Unary {
newSnowball := *sb
newSnowball.confidence = slices.Clone(sb.confidence)
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

0 comments on commit 783fdfc

Please sign in to comment.