2020-03-10 12:20:34 -07:00
|
|
|
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
|
|
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
|
|
|
|
package snowball
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/ava-labs/gecko/ids"
|
|
|
|
)
|
|
|
|
|
|
|
|
// TreeFactory implements Factory by returning a tree struct
|
|
|
|
type TreeFactory struct{}
|
|
|
|
|
|
|
|
// New implements Factory
|
|
|
|
func (TreeFactory) New() Consensus { return &Tree{} }
|
|
|
|
|
|
|
|
// Tree implements the snowball interface by using a modified patricia tree.
|
|
|
|
type Tree struct {
|
2020-03-20 23:36:37 -07:00
|
|
|
// node is the root that represents the first snowball instance in the tree,
|
|
|
|
// and contains references to all the other snowball instances in the tree.
|
|
|
|
node
|
|
|
|
|
2020-03-10 12:20:34 -07:00
|
|
|
// params contains all the configurations of a snowball instance
|
|
|
|
params Parameters
|
|
|
|
|
|
|
|
// shouldReset is used as an optimization to prevent needless tree
|
|
|
|
// traversals. If a snowball instance does not get an alpha majority, that
|
|
|
|
// instance needs to reset by calling RecordUnsuccessfulPoll. Because the
|
|
|
|
// tree splits votes based on the branch, when an instance doesn't get an
|
|
|
|
// alpha majority none of the children of this instance can get an alpha
|
|
|
|
// majority. To avoid calling RecordUnsuccessfulPoll on the full sub-tree of
|
|
|
|
// a node that didn't get an alpha majority, shouldReset is used to indicate
|
|
|
|
// that any later traversal into this sub-tree should call
|
|
|
|
// RecordUnsuccessfulPoll before performing any other action.
|
|
|
|
shouldReset bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize implements the Consensus interface
|
|
|
|
func (t *Tree) Initialize(params Parameters, choice ids.ID) {
|
|
|
|
t.params = params
|
|
|
|
|
|
|
|
snowball := &unarySnowball{}
|
|
|
|
snowball.Initialize(params.BetaVirtuous)
|
|
|
|
|
2020-03-20 23:36:37 -07:00
|
|
|
t.node = &unaryNode{
|
2020-03-10 12:20:34 -07:00
|
|
|
tree: t,
|
|
|
|
preference: choice,
|
|
|
|
commonPrefix: ids.NumBits, // The initial state has no conflicts
|
|
|
|
snowball: snowball,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parameters implements the Consensus interface
|
|
|
|
func (t *Tree) Parameters() Parameters { return t.params }
|
|
|
|
|
|
|
|
// Add implements the Consensus interface
|
|
|
|
func (t *Tree) Add(choice ids.ID) {
|
2020-03-20 23:36:37 -07:00
|
|
|
prefix := t.node.DecidedPrefix()
|
2020-03-10 12:20:34 -07:00
|
|
|
// Make sure that we haven't already decided against this new id
|
|
|
|
if ids.EqualSubset(0, prefix, t.Preference(), choice) {
|
2020-03-20 23:36:37 -07:00
|
|
|
t.node = t.node.Add(choice)
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// RecordPoll implements the Consensus interface
|
|
|
|
func (t *Tree) RecordPoll(votes ids.Bag) {
|
|
|
|
// Get the assumed decided prefix of the root node.
|
2020-03-20 23:36:37 -07:00
|
|
|
decidedPrefix := t.node.DecidedPrefix()
|
2020-03-10 12:20:34 -07:00
|
|
|
|
|
|
|
// If any of the bits differ from the preference in this prefix, the vote is
|
|
|
|
// for a rejected operation. So, we filter out these invalid votes.
|
|
|
|
filteredVotes := votes.Filter(0, decidedPrefix, t.Preference())
|
|
|
|
|
|
|
|
// Now that the votes have been restricted to valid votes, pass them into
|
|
|
|
// the first snowball instance
|
2020-03-20 23:36:37 -07:00
|
|
|
t.node = t.node.RecordPoll(filteredVotes, t.shouldReset)
|
2020-03-10 12:20:34 -07:00
|
|
|
|
|
|
|
// Because we just passed the reset into the snowball instance, we should no
|
|
|
|
// longer reset.
|
|
|
|
t.shouldReset = false
|
|
|
|
}
|
|
|
|
|
|
|
|
// RecordUnsuccessfulPoll implements the Consensus interface
|
|
|
|
func (t *Tree) RecordUnsuccessfulPoll() { t.shouldReset = true }
|
|
|
|
|
|
|
|
func (t *Tree) String() string {
|
|
|
|
builder := strings.Builder{}
|
|
|
|
|
|
|
|
prefixes := []string{""}
|
2020-03-20 23:36:37 -07:00
|
|
|
nodes := []node{t.node}
|
2020-03-10 12:20:34 -07:00
|
|
|
|
|
|
|
for len(prefixes) > 0 {
|
|
|
|
newSize := len(prefixes) - 1
|
|
|
|
|
|
|
|
prefix := prefixes[newSize]
|
|
|
|
prefixes = prefixes[:newSize]
|
|
|
|
|
|
|
|
node := nodes[newSize]
|
|
|
|
nodes = nodes[:newSize]
|
|
|
|
|
|
|
|
s, newNodes := node.Printable()
|
|
|
|
|
|
|
|
builder.WriteString(prefix)
|
|
|
|
builder.WriteString(s)
|
|
|
|
builder.WriteString("\n")
|
|
|
|
|
|
|
|
newPrefix := prefix + " "
|
|
|
|
for range newNodes {
|
|
|
|
prefixes = append(prefixes, newPrefix)
|
|
|
|
}
|
|
|
|
nodes = append(nodes, newNodes...)
|
|
|
|
}
|
|
|
|
|
|
|
|
return strings.TrimSuffix(builder.String(), "\n")
|
|
|
|
}
|
|
|
|
|
|
|
|
type node interface {
|
|
|
|
// Preference returns the preferred choice of this sub-tree
|
|
|
|
Preference() ids.ID
|
|
|
|
// Return the number of assumed decided bits of this node
|
|
|
|
DecidedPrefix() int
|
|
|
|
// Adds a new choice to vote on
|
|
|
|
Add(newChoice ids.ID) node
|
|
|
|
// Apply the votes, reset the model if needed
|
|
|
|
RecordPoll(votes ids.Bag, shouldReset bool) (newChild node)
|
|
|
|
// Returns true if consensus has been reached on this node
|
|
|
|
Finalized() bool
|
|
|
|
|
|
|
|
Printable() (string, []node)
|
|
|
|
}
|
|
|
|
|
|
|
|
// unary is a node with either no children, or a single child. It handles the
|
|
|
|
// voting on a range of identical, virtuous, snowball instances.
|
|
|
|
type unaryNode struct {
|
|
|
|
// tree references the tree that contains this node
|
|
|
|
tree *Tree
|
|
|
|
|
|
|
|
// preference is the choice that is preferred at every branch in this
|
|
|
|
// sub-tree
|
|
|
|
preference ids.ID
|
|
|
|
|
|
|
|
// decidedPrefix is the last bit in the prefix that is assumed to be decided
|
|
|
|
decidedPrefix int // Will be in the range [0, 255)
|
|
|
|
|
|
|
|
// commonPrefix is the last bit in the prefix that this node transitively
|
|
|
|
// references
|
|
|
|
commonPrefix int // Will be in the range (decidedPrefix, 256)
|
|
|
|
|
|
|
|
// snowball wraps the snowball logic
|
|
|
|
snowball UnarySnowball
|
|
|
|
|
|
|
|
// shouldReset is used as an optimization to prevent needless tree
|
|
|
|
// traversals. It is the continuation of shouldReset in the Tree struct.
|
|
|
|
shouldReset bool
|
|
|
|
|
|
|
|
// child is the, possibly nil, node that votes on the next bits in the
|
|
|
|
// decision
|
|
|
|
child node
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *unaryNode) Preference() ids.ID { return u.preference }
|
|
|
|
func (u *unaryNode) DecidedPrefix() int { return u.decidedPrefix }
|
|
|
|
|
|
|
|
// This is by far the most complicated function in this algorithm.
|
|
|
|
// The intuition is that this instance represents a series of consecutive unary
|
|
|
|
// snowball instances, and this function's purpose is convert one of these unary
|
|
|
|
// snowball instances into a binary snowball instance.
|
|
|
|
// There are 5 possible cases.
|
|
|
|
// 1. None of these instances should be split, we should attempt to split a
|
|
|
|
// child
|
|
|
|
//
|
|
|
|
// For example, attempting to insert the value "00001" in this node:
|
|
|
|
//
|
|
|
|
// +-------------------+ <-- This node will not be split
|
|
|
|
// | |
|
|
|
|
// | 0 0 0 |
|
|
|
|
// | |
|
|
|
|
// +-------------------+ <-- Pass the add to the child
|
|
|
|
// ^
|
|
|
|
// |
|
|
|
|
//
|
|
|
|
// Results in:
|
|
|
|
//
|
|
|
|
// +-------------------+
|
|
|
|
// | |
|
|
|
|
// | 0 0 0 |
|
|
|
|
// | |
|
|
|
|
// +-------------------+ <-- With the modified child
|
|
|
|
// ^
|
|
|
|
// |
|
|
|
|
//
|
|
|
|
// 2. This instance represents a series of only one unary instance and it must
|
|
|
|
// be split
|
|
|
|
// This will return a binary choice, with one child the same as my child,
|
|
|
|
// and another (possibly nil child) representing a new chain to the end of
|
|
|
|
// the hash
|
|
|
|
//
|
|
|
|
// For example, attempting to insert the value "1" in this tree:
|
|
|
|
//
|
|
|
|
// +-------------------+
|
|
|
|
// | |
|
|
|
|
// | 0 |
|
|
|
|
// | |
|
|
|
|
// +-------------------+
|
|
|
|
//
|
|
|
|
// Results in:
|
|
|
|
//
|
|
|
|
// +-------------------+
|
|
|
|
// | | |
|
|
|
|
// | 0 | 1 |
|
|
|
|
// | | |
|
|
|
|
// +-------------------+
|
|
|
|
//
|
|
|
|
// 3. This instance must be split on the first bit
|
|
|
|
// This will return a binary choice, with one child equal to this instance
|
|
|
|
// with decidedPrefix increased by one, and another representing a new
|
|
|
|
// chain to the end of the hash
|
|
|
|
//
|
|
|
|
// For example, attempting to insert the value "10" in this tree:
|
|
|
|
//
|
|
|
|
// +-------------------+
|
|
|
|
// | |
|
|
|
|
// | 0 0 |
|
|
|
|
// | |
|
|
|
|
// +-------------------+
|
|
|
|
//
|
|
|
|
// Results in:
|
|
|
|
//
|
|
|
|
// +-------------------+
|
|
|
|
// | | |
|
|
|
|
// | 0 | 1 |
|
|
|
|
// | | |
|
|
|
|
// +-------------------+
|
|
|
|
// ^ ^
|
|
|
|
// / \
|
|
|
|
// +-------------------+ +-------------------+
|
|
|
|
// | | | |
|
|
|
|
// | 0 | | 0 |
|
|
|
|
// | | | |
|
|
|
|
// +-------------------+ +-------------------+
|
|
|
|
//
|
|
|
|
// 4. This instance must be split on the last bit
|
|
|
|
// This will modify this unary choice. The commonPrefix is decreased by
|
|
|
|
// one. The child is set to a binary instance that has a child equal to
|
|
|
|
// the current child and another child equal to a new unary instance to
|
|
|
|
// the end of the hash
|
|
|
|
//
|
|
|
|
// For example, attempting to insert the value "01" in this tree:
|
|
|
|
//
|
|
|
|
// +-------------------+
|
|
|
|
// | |
|
|
|
|
// | 0 0 |
|
|
|
|
// | |
|
|
|
|
// +-------------------+
|
|
|
|
//
|
|
|
|
// Results in:
|
|
|
|
//
|
|
|
|
// +-------------------+
|
|
|
|
// | |
|
|
|
|
// | 0 |
|
|
|
|
// | |
|
|
|
|
// +-------------------+
|
|
|
|
// ^
|
|
|
|
// |
|
|
|
|
// +-------------------+
|
|
|
|
// | | |
|
|
|
|
// | 0 | 1 |
|
|
|
|
// | | |
|
|
|
|
// +-------------------+
|
|
|
|
//
|
|
|
|
// 5. This instance must be split on an interior bit
|
|
|
|
// This will modify this unary choice. The commonPrefix is set to the
|
|
|
|
// interior bit. The child is set to a binary instance that has a child
|
|
|
|
// equal to this unary choice with the decidedPrefix equal to the interior
|
|
|
|
// bit and another child equal to a new unary instance to the end of the
|
|
|
|
// hash
|
|
|
|
//
|
|
|
|
// For example, attempting to insert the value "010" in this tree:
|
|
|
|
//
|
|
|
|
// +-------------------+
|
|
|
|
// | |
|
|
|
|
// | 0 0 0 |
|
|
|
|
// | |
|
|
|
|
// +-------------------+
|
|
|
|
//
|
|
|
|
// Results in:
|
|
|
|
//
|
|
|
|
// +-------------------+
|
|
|
|
// | |
|
|
|
|
// | 0 |
|
|
|
|
// | |
|
|
|
|
// +-------------------+
|
|
|
|
// ^
|
|
|
|
// |
|
|
|
|
// +-------------------+
|
|
|
|
// | | |
|
|
|
|
// | 0 | 1 |
|
|
|
|
// | | |
|
|
|
|
// +-------------------+
|
|
|
|
// ^ ^
|
|
|
|
// / \
|
|
|
|
// +-------------------+ +-------------------+
|
|
|
|
// | | | |
|
|
|
|
// | 0 | | 0 |
|
|
|
|
// | | | |
|
|
|
|
// +-------------------+ +-------------------+
|
|
|
|
func (u *unaryNode) Add(newChoice ids.ID) node {
|
|
|
|
if u.Finalized() {
|
|
|
|
return u // Only happens if the tree is finalized, or it's a leaf node
|
|
|
|
}
|
|
|
|
|
|
|
|
if index, found := ids.FirstDifferenceSubset(
|
|
|
|
u.decidedPrefix, u.commonPrefix, u.preference, newChoice); !found {
|
|
|
|
// If the first difference doesn't exist, then this node shouldn't be
|
|
|
|
// split
|
2020-03-31 21:52:50 -07:00
|
|
|
if u.child != nil {
|
|
|
|
// Because this node will finalize before any children could
|
|
|
|
// finalize, it must be that the newChoice will match my child's
|
|
|
|
// prefix
|
2020-03-10 12:20:34 -07:00
|
|
|
u.child = u.child.Add(newChoice)
|
|
|
|
}
|
2020-03-31 21:52:50 -07:00
|
|
|
// if u.child is nil, then we are attempting to add the same choice into
|
|
|
|
// the tree, which should be a noop
|
2020-03-10 12:20:34 -07:00
|
|
|
} else {
|
|
|
|
// The difference was found, so this node must be split
|
|
|
|
|
|
|
|
bit := u.preference.Bit(uint(index)) // The currently preferred bit
|
|
|
|
b := &binaryNode{
|
|
|
|
tree: u.tree,
|
|
|
|
bit: index,
|
|
|
|
snowball: u.snowball.Extend(u.tree.params.BetaRogue, bit),
|
|
|
|
shouldReset: [2]bool{u.shouldReset, u.shouldReset},
|
|
|
|
}
|
|
|
|
b.preferences[bit] = u.preference
|
|
|
|
b.preferences[1-bit] = newChoice
|
|
|
|
|
|
|
|
newChildSnowball := &unarySnowball{}
|
|
|
|
newChildSnowball.Initialize(u.tree.params.BetaVirtuous)
|
|
|
|
newChild := &unaryNode{
|
|
|
|
tree: u.tree,
|
|
|
|
preference: newChoice,
|
|
|
|
decidedPrefix: index + 1, // The new child assumes this branch has decided in it's favor
|
|
|
|
commonPrefix: ids.NumBits, // The new child has no conflicts under this branch
|
|
|
|
snowball: newChildSnowball,
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case u.decidedPrefix == u.commonPrefix-1:
|
|
|
|
// This node was only voting over one bit. (Case 2. from above)
|
|
|
|
b.children[bit] = u.child
|
|
|
|
if u.child != nil {
|
|
|
|
b.children[1-bit] = newChild
|
|
|
|
}
|
|
|
|
return b
|
|
|
|
case index == u.decidedPrefix:
|
|
|
|
// This node was split on the first bit. (Case 3. from above)
|
|
|
|
u.decidedPrefix++
|
|
|
|
b.children[bit] = u
|
|
|
|
b.children[1-bit] = newChild
|
|
|
|
return b
|
|
|
|
case index == u.commonPrefix-1:
|
|
|
|
// This node was split on the last bit. (Case 4. from above)
|
|
|
|
u.commonPrefix--
|
|
|
|
b.children[bit] = u.child
|
|
|
|
if u.child != nil {
|
|
|
|
b.children[1-bit] = newChild
|
|
|
|
}
|
|
|
|
u.child = b
|
|
|
|
return u
|
|
|
|
default:
|
|
|
|
// This node was split on an interior bit. (Case 5. from above)
|
|
|
|
originalDecidedPrefix := u.decidedPrefix
|
|
|
|
u.decidedPrefix = index + 1
|
|
|
|
b.children[bit] = u
|
|
|
|
b.children[1-bit] = newChild
|
|
|
|
return &unaryNode{
|
|
|
|
tree: u.tree,
|
|
|
|
preference: u.preference,
|
|
|
|
decidedPrefix: originalDecidedPrefix,
|
|
|
|
commonPrefix: index,
|
|
|
|
snowball: u.snowball.Clone(),
|
|
|
|
child: b,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return u // Do nothing, the choice was already rejected
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *unaryNode) RecordPoll(votes ids.Bag, reset bool) node {
|
|
|
|
// This ensures that votes for rejected colors are dropped
|
|
|
|
votes = votes.Filter(u.decidedPrefix, u.commonPrefix, u.preference)
|
|
|
|
|
|
|
|
// If my parent didn't get enough votes previously, then neither did I
|
|
|
|
if reset {
|
|
|
|
u.snowball.RecordUnsuccessfulPoll()
|
|
|
|
u.shouldReset = true // Make sure my child is also reset correctly
|
|
|
|
}
|
|
|
|
|
|
|
|
// If I got enough votes this time
|
|
|
|
if votes.Len() >= u.tree.params.Alpha {
|
|
|
|
u.snowball.RecordSuccessfulPoll()
|
|
|
|
|
|
|
|
if u.child != nil {
|
2020-03-17 20:29:38 -07:00
|
|
|
// We are guaranteed that u.commonPrefix will equal
|
|
|
|
// u.child.DecidedPrefix(). Otherwise, there must have been a
|
|
|
|
// decision under this node, which isn't possible because
|
|
|
|
// beta1 <= beta2. That means that filtering the votes between
|
|
|
|
// u.commonPrefix and u.child.DecidedPrefix() would always result in
|
|
|
|
// the same set being returned.
|
|
|
|
|
2020-03-10 12:20:34 -07:00
|
|
|
// If I'm now decided, return my child
|
|
|
|
if u.Finalized() {
|
2020-03-17 20:29:38 -07:00
|
|
|
return u.child.RecordPoll(votes, u.shouldReset)
|
2020-03-10 12:20:34 -07:00
|
|
|
}
|
2020-03-17 20:29:38 -07:00
|
|
|
u.child = u.child.RecordPoll(votes, u.shouldReset)
|
2020-03-10 12:20:34 -07:00
|
|
|
// The child's preference may have changed
|
|
|
|
u.preference = u.child.Preference()
|
|
|
|
}
|
|
|
|
// Now that I have passed my votes to my child, I don't need to reset
|
|
|
|
// them
|
|
|
|
u.shouldReset = false
|
|
|
|
} else {
|
|
|
|
// I didn't get enough votes, I must reset and my child must reset as
|
|
|
|
// well
|
|
|
|
u.snowball.RecordUnsuccessfulPoll()
|
|
|
|
u.shouldReset = true
|
|
|
|
}
|
|
|
|
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *unaryNode) Finalized() bool { return u.snowball.Finalized() }
|
|
|
|
|
|
|
|
func (u *unaryNode) Printable() (string, []node) {
|
|
|
|
s := fmt.Sprintf("%s Bits = [%d, %d)",
|
|
|
|
u.snowball, u.decidedPrefix, u.commonPrefix)
|
|
|
|
if u.child == nil {
|
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
return s, []node{u.child}
|
|
|
|
}
|
|
|
|
|
|
|
|
// binaryNode is a node with either no children, or two children. It handles the
|
|
|
|
// voting of a single, rogue, snowball instance.
|
|
|
|
type binaryNode struct {
|
|
|
|
// tree references the tree that contains this node
|
|
|
|
tree *Tree
|
|
|
|
|
|
|
|
// preferences are the choices that are preferred at every branch in their
|
|
|
|
// sub-tree
|
|
|
|
preferences [2]ids.ID
|
|
|
|
|
|
|
|
// bit is the index in the id of the choice this node is deciding on
|
|
|
|
bit int // Will be in the range [0, 256)
|
|
|
|
|
|
|
|
// snowball wraps the snowball logic
|
|
|
|
snowball BinarySnowball
|
|
|
|
|
|
|
|
// shouldReset is used as an optimization to prevent needless tree
|
|
|
|
// traversals. It is the continuation of shouldReset in the Tree struct.
|
|
|
|
shouldReset [2]bool
|
|
|
|
|
|
|
|
// children are the, possibly nil, nodes that vote on the next bits in the
|
|
|
|
// decision
|
|
|
|
children [2]node
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *binaryNode) Preference() ids.ID { return b.preferences[b.snowball.Preference()] }
|
|
|
|
func (b *binaryNode) DecidedPrefix() int { return b.bit }
|
|
|
|
|
|
|
|
func (b *binaryNode) Add(id ids.ID) node {
|
|
|
|
bit := id.Bit(uint(b.bit))
|
|
|
|
child := b.children[bit]
|
|
|
|
// If child is nil, then we are running an instance on the last bit. Finding
|
|
|
|
// two hashes that are equal up to the last bit would be really cool though.
|
|
|
|
// Regardless, the case is handled
|
|
|
|
if child != nil &&
|
|
|
|
// + 1 is used because we already explicitly check the p.bit bit
|
|
|
|
ids.EqualSubset(b.bit+1, child.DecidedPrefix(), b.preferences[bit], id) {
|
|
|
|
b.children[bit] = child.Add(id)
|
|
|
|
}
|
2020-03-31 22:08:47 -07:00
|
|
|
// If child is nil, then the id has already been added to the tree, so
|
|
|
|
// nothing should be done
|
|
|
|
// If the decided prefix isn't matched, then a previous decision has made
|
|
|
|
// the id that is being added to have already been rejected
|
2020-03-10 12:20:34 -07:00
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *binaryNode) RecordPoll(votes ids.Bag, reset bool) node {
|
|
|
|
// The list of votes we are passed is split into votes for bit 0 and votes
|
|
|
|
// for bit 1
|
|
|
|
splitVotes := votes.Split(uint(b.bit))
|
|
|
|
|
|
|
|
bit := 0 // Because alpha > k/2, only the larger count could be increased
|
|
|
|
if splitVotes[0].Len() < splitVotes[1].Len() {
|
|
|
|
bit = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
if reset {
|
|
|
|
b.snowball.RecordUnsuccessfulPoll()
|
|
|
|
b.shouldReset[bit] = true
|
|
|
|
// 1-bit isn't set here because it is set below anyway
|
|
|
|
}
|
|
|
|
b.shouldReset[1-bit] = true // They didn't get the threshold of votes
|
|
|
|
|
|
|
|
prunedVotes := splitVotes[bit]
|
|
|
|
// If this bit got alpha votes, it was a successful poll
|
|
|
|
if prunedVotes.Len() >= b.tree.params.Alpha {
|
|
|
|
b.snowball.RecordSuccessfulPoll(bit)
|
|
|
|
|
|
|
|
if child := b.children[bit]; child != nil {
|
|
|
|
// The votes are filtered to ensure that they are votes that should
|
|
|
|
// count for the child
|
|
|
|
filteredVotes := prunedVotes.Filter(
|
|
|
|
b.bit+1, child.DecidedPrefix(), b.preferences[bit])
|
|
|
|
|
|
|
|
if b.snowball.Finalized() {
|
|
|
|
// If we are decided here, that means we must have decided due
|
|
|
|
// to this poll. Therefore, we must have decided on bit.
|
|
|
|
return child.RecordPoll(filteredVotes, b.shouldReset[bit])
|
|
|
|
}
|
|
|
|
newChild := child.RecordPoll(filteredVotes, b.shouldReset[bit])
|
|
|
|
b.children[bit] = newChild
|
|
|
|
b.preferences[bit] = newChild.Preference()
|
|
|
|
}
|
|
|
|
b.shouldReset[bit] = false // We passed the reset down
|
|
|
|
} else {
|
|
|
|
b.snowball.RecordUnsuccessfulPoll()
|
|
|
|
// The winning child didn't get enough votes either
|
|
|
|
b.shouldReset[bit] = true
|
|
|
|
}
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *binaryNode) Finalized() bool { return b.snowball.Finalized() }
|
|
|
|
|
|
|
|
func (b *binaryNode) Printable() (string, []node) {
|
|
|
|
s := fmt.Sprintf("%s Bit = %d", b.snowball, b.bit)
|
|
|
|
if b.children[0] == nil {
|
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
return s, []node{b.children[1], b.children[0]}
|
|
|
|
}
|