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

Refactor bootstrapper implementation into consensus #2300

Merged
merged 19 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions snow/consensus/snowman/bootstrapper/majority.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package bootstrapper

import (
"context"

"go.uber.org/zap"

"golang.org/x/exp/maps"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/logging"
"github.com/ava-labs/avalanchego/utils/math"
"github.com/ava-labs/avalanchego/utils/set"
)

var _ Poll = (*Majority)(nil)

// Majority implements the bootstrapping poll to filter the initial set of
// potentially accaptable blocks into a set of accepted blocks to sync to.
//
// Once the last accepted blocks have been fetched from the initial set of
// peers, the set of blocks are sent to all peers. Each peer is expected to
// filter the provided blocks and report which of them they consider accepted.
// If a majority of the peers report that a block is accepted, then the node
// will consider that block to be accepted by the network. This assumes that a
// majority of the network is correct. If a majority of the network is
// malicious, the node may accept an incorrect block.
type Majority struct {
requests

log logging.Logger
nodeWeights map[ids.NodeID]uint64

// received maps the blockID to the total sum of weight that has reported
// that block as accepted.
received map[ids.ID]uint64
accepted []ids.ID
}

func NewMajority(
log logging.Logger,
nodeWeights map[ids.NodeID]uint64,
maxOutstanding int,
) *Majority {
return &Majority{
requests: requests{
maxOutstanding: maxOutstanding,
pendingSend: set.Of(maps.Keys(nodeWeights)...),
},
log: log,
nodeWeights: nodeWeights,
received: make(map[ids.ID]uint64),
}
}

func (m *Majority) RecordOpinion(_ context.Context, nodeID ids.NodeID, blkIDs set.Set[ids.ID]) error {
if !m.recordResponse(nodeID) {
// The chain router should have already dropped unexpected messages.
m.log.Error("received unexpected opinion",
zap.String("pollType", "majority"),
zap.Stringer("nodeID", nodeID),
zap.Reflect("blkIDs", blkIDs),
)
return nil
StephenButtolph marked this conversation as resolved.
Show resolved Hide resolved
}

weight := m.nodeWeights[nodeID]
for blkID := range blkIDs {
newWeight, err := math.Add64(m.received[blkID], weight)
if err != nil {
return err
}
m.received[blkID] = newWeight
}

if !m.finished() {
return nil
}

var (
totalWeight uint64
err error
)
for _, weight := range m.nodeWeights {
totalWeight, err = math.Add64(totalWeight, weight)
if err != nil {
return err
}
}

requiredWeight := totalWeight/2 + 1
for blkID, weight := range m.received {
if weight >= requiredWeight {
m.accepted = append(m.accepted, blkID)
}
}
Comment on lines +79 to +99
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we should also consider adding bool to the return signature here so instead of calling both RecordOpinion and Result to figure out if we need to keep poling peers we can just call RecordOpinion in a loop. This way we can also move all this code into Result, and get rid of the m.accepted field.

Copy link
Contributor

@joshua-kim joshua-kim Nov 15, 2023

Choose a reason for hiding this comment

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

Left an offline comment on slack but we can also consider supplying a filter function func(map[ids.ID]uint64) bool that Result can use to filter out any valid blocks which will get rid of the duplicated code in Minority and the Poll interface.

Copy link
Contributor Author

@StephenButtolph StephenButtolph Nov 16, 2023

Choose a reason for hiding this comment

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

We need Result on the interface for the Minority implementation to be usable because the minority result is used repeatedly to send the majority requests. I'm sure that we could combine the implementations somehow... But I feel like currently there really isn't much duplicated code... The structure of the code may look similar but the logic is pretty different


m.log.Debug("finalized bootstrapping poll",
zap.String("pollType", "majority"),
zap.Stringers("accepted", m.accepted),
)
return nil
}

func (m *Majority) Result(context.Context) ([]ids.ID, bool) {
return m.accepted, m.finished()
}
Loading
Loading