github.com/MetalBlockchain/metalgo@v1.11.9/snow/consensus/snowman/bootstrapper/majority.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package bootstrapper 5 6 import ( 7 "context" 8 9 "go.uber.org/zap" 10 "golang.org/x/exp/maps" 11 12 "github.com/MetalBlockchain/metalgo/ids" 13 "github.com/MetalBlockchain/metalgo/utils/logging" 14 "github.com/MetalBlockchain/metalgo/utils/math" 15 "github.com/MetalBlockchain/metalgo/utils/set" 16 ) 17 18 var _ Poll = (*Majority)(nil) 19 20 // Majority implements the bootstrapping poll to filter the initial set of 21 // potentially accaptable blocks into a set of accepted blocks to sync to. 22 // 23 // Once the last accepted blocks have been fetched from the initial set of 24 // peers, the set of blocks are sent to all peers. Each peer is expected to 25 // filter the provided blocks and report which of them they consider accepted. 26 // If a majority of the peers report that a block is accepted, then the node 27 // will consider that block to be accepted by the network. This assumes that a 28 // majority of the network is correct. If a majority of the network is 29 // malicious, the node may accept an incorrect block. 30 type Majority struct { 31 requests 32 33 log logging.Logger 34 nodeWeights map[ids.NodeID]uint64 35 36 // received maps the blockID to the total sum of weight that has reported 37 // that block as accepted. 38 received map[ids.ID]uint64 39 accepted []ids.ID 40 } 41 42 func NewMajority( 43 log logging.Logger, 44 nodeWeights map[ids.NodeID]uint64, 45 maxOutstanding int, 46 ) *Majority { 47 return &Majority{ 48 requests: requests{ 49 maxOutstanding: maxOutstanding, 50 pendingSend: set.Of(maps.Keys(nodeWeights)...), 51 }, 52 log: log, 53 nodeWeights: nodeWeights, 54 received: make(map[ids.ID]uint64), 55 } 56 } 57 58 func (m *Majority) RecordOpinion(_ context.Context, nodeID ids.NodeID, blkIDs set.Set[ids.ID]) error { 59 if !m.recordResponse(nodeID) { 60 // The chain router should have already dropped unexpected messages. 61 m.log.Error("received unexpected opinion", 62 zap.String("pollType", "majority"), 63 zap.Stringer("nodeID", nodeID), 64 zap.Reflect("blkIDs", blkIDs), 65 ) 66 return nil 67 } 68 69 weight := m.nodeWeights[nodeID] 70 for blkID := range blkIDs { 71 newWeight, err := math.Add64(m.received[blkID], weight) 72 if err != nil { 73 return err 74 } 75 m.received[blkID] = newWeight 76 } 77 78 if !m.finished() { 79 return nil 80 } 81 82 var ( 83 totalWeight uint64 84 err error 85 ) 86 for _, weight := range m.nodeWeights { 87 totalWeight, err = math.Add64(totalWeight, weight) 88 if err != nil { 89 return err 90 } 91 } 92 93 requiredWeight := totalWeight/2 + 1 94 for blkID, weight := range m.received { 95 if weight >= requiredWeight { 96 m.accepted = append(m.accepted, blkID) 97 } 98 } 99 100 m.log.Debug("finalized bootstrapping poll", 101 zap.String("pollType", "majority"), 102 zap.Stringers("accepted", m.accepted), 103 ) 104 return nil 105 } 106 107 func (m *Majority) Result(context.Context) ([]ids.ID, bool) { 108 return m.accepted, m.finished() 109 }