// Package peers provides information about peers at the Ethereum consensus protocol level. // // "Protocol level" is the level above the network level, so this layer never sees or interacts with // (for example) hosts that are uncontactable due to being down, firewalled, etc. Instead, this works // with peers that are contactable but may or may not be of the correct fork version, not currently // required due to the number of current connections, etc. // // A peer can have one of a number of states: // // - connected if we are able to talk to the remote peer // - connecting if we are attempting to be able to talk to the remote peer // - disconnecting if we are attempting to stop being able to talk to the remote peer // - disconnected if we are not able to talk to the remote peer // // For convenience, there are two aggregate states expressed in functions: // // - active if we are connecting or connected // - inactive if we are disconnecting or disconnected // // Peer information is persistent for the run of the service. This allows for collection of useful // long-term statistics such as number of bad responses obtained from the peer, giving the basis for // decisions to not talk to known-bad peers (by de-scoring them). package peers import ( "context" "math" "sort" "time" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" ma "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr/net" "github.com/prysmaticlabs/go-bitfield" "github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/peers/peerdata" "github.com/prysmaticlabs/prysm/v4/beacon-chain/p2p/peers/scorers" "github.com/prysmaticlabs/prysm/v4/config/params" "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v4/crypto/rand" pmath "github.com/prysmaticlabs/prysm/v4/math" pb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1/metadata" prysmTime "github.com/prysmaticlabs/prysm/v4/time" "github.com/prysmaticlabs/prysm/v4/time/slots" ) const ( // PeerDisconnected means there is no connection to the peer. PeerDisconnected peerdata.PeerConnectionState = iota // PeerDisconnecting means there is an on-going attempt to disconnect from the peer. PeerDisconnecting // PeerConnected means the peer has an active connection. PeerConnected // PeerConnecting means there is an on-going attempt to connect to the peer. PeerConnecting ) const ( // ColocationLimit restricts how many peer identities we can see from a single ip or ipv6 subnet. ColocationLimit = 5 // Additional buffer beyond current peer limit, from which we can store the relevant peer statuses. maxLimitBuffer = 150 // InboundRatio is the proportion of our connected peer limit at which we will allow inbound peers. InboundRatio = float64(0.8) // MinBackOffDuration minimum amount (in milliseconds) to wait before peer is re-dialed. // When node and peer are dialing each other simultaneously connection may fail. In order, to break // of constant dialing, peer is assigned some backoff period, and only dialed again once that backoff is up. MinBackOffDuration = 100 // MaxBackOffDuration maximum amount (in milliseconds) to wait before peer is re-dialed. MaxBackOffDuration = 5000 ) // Status is the structure holding the peer status information. type Status struct { ctx context.Context scorers *scorers.Service store *peerdata.Store ipTracker map[string]uint64 rand *rand.Rand } // StatusConfig represents peer status service params. type StatusConfig struct { // PeerLimit specifies maximum amount of concurrent peers that are expected to be connect to the node. PeerLimit int // ScorerParams holds peer scorer configuration params. ScorerParams *scorers.Config } // NewStatus creates a new status entity. func NewStatus(ctx context.Context, config *StatusConfig) *Status { store := peerdata.NewStore(ctx, &peerdata.StoreConfig{ MaxPeers: maxLimitBuffer + config.PeerLimit, }) return &Status{ ctx: ctx, store: store, scorers: scorers.NewService(ctx, store, config.ScorerParams), ipTracker: map[string]uint64{}, // Random generator used to calculate dial backoff period. // It is ok to use deterministic generator, no need for true entropy. rand: rand.NewDeterministicGenerator(), } } // Scorers exposes peer scoring management service. func (p *Status) Scorers() *scorers.Service { return p.scorers } // MaxPeerLimit returns the max peer limit stored in the current peer store. func (p *Status) MaxPeerLimit() int { return p.store.Config().MaxPeers } // Add adds a peer. // If a peer already exists with this ID its address and direction are updated with the supplied data. func (p *Status) Add(record *enr.Record, pid peer.ID, address ma.Multiaddr, direction network.Direction) { p.store.Lock() defer p.store.Unlock() if peerData, ok := p.store.PeerData(pid); ok { // Peer already exists, just update its address info. prevAddress := peerData.Address peerData.Address = address peerData.Direction = direction if record != nil { peerData.Enr = record } if !sameIP(prevAddress, address) { p.addIpToTracker(pid) } return } peerData := &peerdata.PeerData{ Address: address, Direction: direction, // Peers start disconnected; state will be updated when the handshake process begins. ConnState: PeerDisconnected, } if record != nil { peerData.Enr = record } p.store.SetPeerData(pid, peerData) p.addIpToTracker(pid) } // Address returns the multiaddress of the given remote peer. // This will error if the peer does not exist. func (p *Status) Address(pid peer.ID) (ma.Multiaddr, error) { p.store.RLock() defer p.store.RUnlock() if peerData, ok := p.store.PeerData(pid); ok { return peerData.Address, nil } return nil, peerdata.ErrPeerUnknown } // Direction returns the direction of the given remote peer. // This will error if the peer does not exist. func (p *Status) Direction(pid peer.ID) (network.Direction, error) { p.store.RLock() defer p.store.RUnlock() if peerData, ok := p.store.PeerData(pid); ok { return peerData.Direction, nil } return network.DirUnknown, peerdata.ErrPeerUnknown } // ENR returns the enr for the corresponding peer id. func (p *Status) ENR(pid peer.ID) (*enr.Record, error) { p.store.RLock() defer p.store.RUnlock() if peerData, ok := p.store.PeerData(pid); ok { return peerData.Enr, nil } return nil, peerdata.ErrPeerUnknown } // SetChainState sets the chain state of the given remote peer. func (p *Status) SetChainState(pid peer.ID, chainState *pb.Status) { p.scorers.PeerStatusScorer().SetPeerStatus(pid, chainState, nil) } // ChainState gets the chain state of the given remote peer. // This will error if the peer does not exist. // This will error if there is no known chain state for the peer. func (p *Status) ChainState(pid peer.ID) (*pb.Status, error) { return p.scorers.PeerStatusScorer().PeerStatus(pid) } // IsActive checks if a peers is active and returns the result appropriately. func (p *Status) IsActive(pid peer.ID) bool { p.store.RLock() defer p.store.RUnlock() peerData, ok := p.store.PeerData(pid) return ok && (peerData.ConnState == PeerConnected || peerData.ConnState == PeerConnecting) } // IsAboveInboundLimit checks if we are above our current inbound // peer limit. func (p *Status) IsAboveInboundLimit() bool { p.store.RLock() defer p.store.RUnlock() totalInbound := 0 for _, peerData := range p.store.Peers() { if peerData.ConnState == PeerConnected && peerData.Direction == network.DirInbound { totalInbound += 1 } } inboundLimit := int(float64(p.ConnectedPeerLimit()) * InboundRatio) return totalInbound > inboundLimit } // InboundLimit returns the current inbound // peer limit. func (p *Status) InboundLimit() int { p.store.RLock() defer p.store.RUnlock() return int(float64(p.ConnectedPeerLimit()) * InboundRatio) } // SetMetadata sets the metadata of the given remote peer. func (p *Status) SetMetadata(pid peer.ID, metaData metadata.Metadata) { p.store.Lock() defer p.store.Unlock() peerData := p.store.PeerDataGetOrCreate(pid) peerData.MetaData = metaData.Copy() } // Metadata returns a copy of the metadata corresponding to the provided // peer id. func (p *Status) Metadata(pid peer.ID) (metadata.Metadata, error) { p.store.RLock() defer p.store.RUnlock() if peerData, ok := p.store.PeerData(pid); ok { if peerData.MetaData == nil || peerData.MetaData.IsNil() { return nil, nil } return peerData.MetaData.Copy(), nil } return nil, peerdata.ErrPeerUnknown } // CommitteeIndices retrieves the committee subnets the peer is subscribed to. func (p *Status) CommitteeIndices(pid peer.ID) ([]uint64, error) { p.store.RLock() defer p.store.RUnlock() if peerData, ok := p.store.PeerData(pid); ok { if peerData.Enr == nil || peerData.MetaData == nil || peerData.MetaData.IsNil() { return []uint64{}, nil } return indicesFromBitfield(peerData.MetaData.AttnetsBitfield()), nil } return nil, peerdata.ErrPeerUnknown } // SubscribedToSubnet retrieves the peers subscribed to the given // committee subnet. func (p *Status) SubscribedToSubnet(index uint64) []peer.ID { p.store.RLock() defer p.store.RUnlock() peers := make([]peer.ID, 0) for pid, peerData := range p.store.Peers() { // look at active peers connectedStatus := peerData.ConnState == PeerConnecting || peerData.ConnState == PeerConnected if connectedStatus && peerData.MetaData != nil && !peerData.MetaData.IsNil() && peerData.MetaData.AttnetsBitfield() != nil { indices := indicesFromBitfield(peerData.MetaData.AttnetsBitfield()) for _, idx := range indices { if idx == index { peers = append(peers, pid) break } } } } return peers } // SetConnectionState sets the connection state of the given remote peer. func (p *Status) SetConnectionState(pid peer.ID, state peerdata.PeerConnectionState) { p.store.Lock() defer p.store.Unlock() peerData := p.store.PeerDataGetOrCreate(pid) peerData.ConnState = state } // ConnectionState gets the connection state of the given remote peer. // This will error if the peer does not exist. func (p *Status) ConnectionState(pid peer.ID) (peerdata.PeerConnectionState, error) { p.store.RLock() defer p.store.RUnlock() if peerData, ok := p.store.PeerData(pid); ok { return peerData.ConnState, nil } return PeerDisconnected, peerdata.ErrPeerUnknown } // ChainStateLastUpdated gets the last time the chain state of the given remote peer was updated. // This will error if the peer does not exist. func (p *Status) ChainStateLastUpdated(pid peer.ID) (time.Time, error) { p.store.RLock() defer p.store.RUnlock() if peerData, ok := p.store.PeerData(pid); ok { return peerData.ChainStateLastUpdated, nil } return prysmTime.Now(), peerdata.ErrPeerUnknown } // IsBad states if the peer is to be considered bad (by *any* of the registered scorers). // If the peer is unknown this will return `false`, which makes using this function easier than returning an error. func (p *Status) IsBad(pid peer.ID) bool { p.store.RLock() defer p.store.RUnlock() return p.isBad(pid) } // isBad is the lock-free version of IsBad. func (p *Status) isBad(pid peer.ID) bool { return p.isfromBadIP(pid) || p.scorers.IsBadPeerNoLock(pid) } // NextValidTime gets the earliest possible time it is to contact/dial // a peer again. This is used to back-off from peers in the event // they are 'full' or have banned us. func (p *Status) NextValidTime(pid peer.ID) (time.Time, error) { p.store.RLock() defer p.store.RUnlock() if peerData, ok := p.store.PeerData(pid); ok { return peerData.NextValidTime, nil } return prysmTime.Now(), peerdata.ErrPeerUnknown } // SetNextValidTime sets the earliest possible time we are // able to contact this peer again. func (p *Status) SetNextValidTime(pid peer.ID, nextTime time.Time) { p.store.Lock() defer p.store.Unlock() peerData := p.store.PeerDataGetOrCreate(pid) peerData.NextValidTime = nextTime } // RandomizeBackOff adds extra backoff period during which peer will not be dialed. func (p *Status) RandomizeBackOff(pid peer.ID) { p.store.Lock() defer p.store.Unlock() peerData := p.store.PeerDataGetOrCreate(pid) // No need to add backoff period, if the previous one hasn't expired yet. if !time.Now().After(peerData.NextValidTime) { return } duration := time.Duration(math.Max(MinBackOffDuration, float64(p.rand.Intn(MaxBackOffDuration)))) * time.Millisecond peerData.NextValidTime = time.Now().Add(duration) } // IsReadyToDial checks where the given peer is ready to be // dialed again. func (p *Status) IsReadyToDial(pid peer.ID) bool { p.store.RLock() defer p.store.RUnlock() if peerData, ok := p.store.PeerData(pid); ok { timeIsZero := peerData.NextValidTime.IsZero() isInvalidTime := peerData.NextValidTime.After(time.Now()) return timeIsZero || !isInvalidTime } // If no record exists, we don't restrict dials to the // peer. return true } // Connecting returns the peers that are connecting. func (p *Status) Connecting() []peer.ID { p.store.RLock() defer p.store.RUnlock() peers := make([]peer.ID, 0) for pid, peerData := range p.store.Peers() { if peerData.ConnState == PeerConnecting { peers = append(peers, pid) } } return peers } // Connected returns the peers that are connected. func (p *Status) Connected() []peer.ID { p.store.RLock() defer p.store.RUnlock() peers := make([]peer.ID, 0) for pid, peerData := range p.store.Peers() { if peerData.ConnState == PeerConnected { peers = append(peers, pid) } } return peers } // Inbound returns the current batch of inbound peers. func (p *Status) Inbound() []peer.ID { p.store.RLock() defer p.store.RUnlock() peers := make([]peer.ID, 0) for pid, peerData := range p.store.Peers() { if peerData.Direction == network.DirInbound { peers = append(peers, pid) } } return peers } // InboundConnected returns the current batch of inbound peers that are connected. func (p *Status) InboundConnected() []peer.ID { p.store.RLock() defer p.store.RUnlock() peers := make([]peer.ID, 0) for pid, peerData := range p.store.Peers() { if peerData.ConnState == PeerConnected && peerData.Direction == network.DirInbound { peers = append(peers, pid) } } return peers } // Outbound returns the current batch of outbound peers. func (p *Status) Outbound() []peer.ID { p.store.RLock() defer p.store.RUnlock() peers := make([]peer.ID, 0) for pid, peerData := range p.store.Peers() { if peerData.Direction == network.DirOutbound { peers = append(peers, pid) } } return peers } // OutboundConnected returns the current batch of outbound peers that are connected. func (p *Status) OutboundConnected() []peer.ID { p.store.RLock() defer p.store.RUnlock() peers := make([]peer.ID, 0) for pid, peerData := range p.store.Peers() { if peerData.ConnState == PeerConnected && peerData.Direction == network.DirOutbound { peers = append(peers, pid) } } return peers } // Active returns the peers that are connecting or connected. func (p *Status) Active() []peer.ID { p.store.RLock() defer p.store.RUnlock() peers := make([]peer.ID, 0) for pid, peerData := range p.store.Peers() { if peerData.ConnState == PeerConnecting || peerData.ConnState == PeerConnected { peers = append(peers, pid) } } return peers } // Disconnecting returns the peers that are disconnecting. func (p *Status) Disconnecting() []peer.ID { p.store.RLock() defer p.store.RUnlock() peers := make([]peer.ID, 0) for pid, peerData := range p.store.Peers() { if peerData.ConnState == PeerDisconnecting { peers = append(peers, pid) } } return peers } // Disconnected returns the peers that are disconnected. func (p *Status) Disconnected() []peer.ID { p.store.RLock() defer p.store.RUnlock() peers := make([]peer.ID, 0) for pid, peerData := range p.store.Peers() { if peerData.ConnState == PeerDisconnected { peers = append(peers, pid) } } return peers } // Inactive returns the peers that are disconnecting or disconnected. func (p *Status) Inactive() []peer.ID { p.store.RLock() defer p.store.RUnlock() peers := make([]peer.ID, 0) for pid, peerData := range p.store.Peers() { if peerData.ConnState == PeerDisconnecting || peerData.ConnState == PeerDisconnected { peers = append(peers, pid) } } return peers } // Bad returns the peers that are bad. func (p *Status) Bad() []peer.ID { return p.scorers.BadResponsesScorer().BadPeers() } // All returns all the peers regardless of state. func (p *Status) All() []peer.ID { p.store.RLock() defer p.store.RUnlock() pids := make([]peer.ID, 0, len(p.store.Peers())) for pid := range p.store.Peers() { pids = append(pids, pid) } return pids } // Prune clears out and removes outdated and disconnected peers. func (p *Status) Prune() { p.store.Lock() defer p.store.Unlock() // Exit early if there is nothing to prune. if len(p.store.Peers()) <= p.store.Config().MaxPeers { return } notBadPeer := func(pid peer.ID) bool { return !p.isBad(pid) } type peerResp struct { pid peer.ID score float64 } peersToPrune := make([]*peerResp, 0) // Select disconnected peers with a smaller bad response count. for pid, peerData := range p.store.Peers() { if peerData.ConnState == PeerDisconnected && notBadPeer(pid) { peersToPrune = append(peersToPrune, &peerResp{ pid: pid, score: p.Scorers().ScoreNoLock(pid), }) } } // Sort peers in descending order, so the peers with the // highest score are pruned first. This // is to protect the node from malicious/lousy peers so // that their memory is still kept. sort.Slice(peersToPrune, func(i, j int) bool { return peersToPrune[i].score > peersToPrune[j].score }) limitDiff := len(p.store.Peers()) - p.store.Config().MaxPeers if limitDiff > len(peersToPrune) { limitDiff = len(peersToPrune) } peersToPrune = peersToPrune[:limitDiff] // Delete peers from map. for _, peerData := range peersToPrune { p.store.DeletePeerData(peerData.pid) } p.tallyIPTracker() } // BestFinalized returns the highest finalized epoch equal to or higher than ours that is agreed // upon by the majority of peers. This method may not return the absolute highest finalized, but // the finalized epoch in which most peers can serve blocks (plurality voting). // Ideally, all peers would be reporting the same finalized epoch but some may be behind due to their // own latency, or because of their finalized epoch at the time we queried them. // Returns epoch number and list of peers that are at or beyond that epoch. func (p *Status) BestFinalized(maxPeers int, ourFinalizedEpoch primitives.Epoch) (primitives.Epoch, []peer.ID) { connected := p.Connected() finalizedEpochVotes := make(map[primitives.Epoch]uint64) pidEpoch := make(map[peer.ID]primitives.Epoch, len(connected)) pidHead := make(map[peer.ID]primitives.Slot, len(connected)) potentialPIDs := make([]peer.ID, 0, len(connected)) for _, pid := range connected { peerChainState, err := p.ChainState(pid) if err == nil && peerChainState != nil && peerChainState.FinalizedEpoch >= ourFinalizedEpoch { finalizedEpochVotes[peerChainState.FinalizedEpoch]++ pidEpoch[pid] = peerChainState.FinalizedEpoch potentialPIDs = append(potentialPIDs, pid) pidHead[pid] = peerChainState.HeadSlot } } // Select the target epoch, which is the epoch most peers agree upon. var targetEpoch primitives.Epoch var mostVotes uint64 for epoch, count := range finalizedEpochVotes { if count > mostVotes || (count == mostVotes && epoch > targetEpoch) { mostVotes = count targetEpoch = epoch } } // Sort PIDs by finalized epoch, in decreasing order. sort.Slice(potentialPIDs, func(i, j int) bool { if pidEpoch[potentialPIDs[i]] == pidEpoch[potentialPIDs[j]] { return pidHead[potentialPIDs[i]] > pidHead[potentialPIDs[j]] } return pidEpoch[potentialPIDs[i]] > pidEpoch[potentialPIDs[j]] }) // Trim potential peers to those on or after target epoch. for i, pid := range potentialPIDs { if pidEpoch[pid] < targetEpoch { potentialPIDs = potentialPIDs[:i] break } } // Trim potential peers to at most maxPeers. if len(potentialPIDs) > maxPeers { potentialPIDs = potentialPIDs[:maxPeers] } return targetEpoch, potentialPIDs } // BestNonFinalized returns the highest known epoch, higher than ours, // and is shared by at least minPeers. func (p *Status) BestNonFinalized(minPeers int, ourHeadEpoch primitives.Epoch) (primitives.Epoch, []peer.ID) { connected := p.Connected() epochVotes := make(map[primitives.Epoch]uint64) pidEpoch := make(map[peer.ID]primitives.Epoch, len(connected)) pidHead := make(map[peer.ID]primitives.Slot, len(connected)) potentialPIDs := make([]peer.ID, 0, len(connected)) ourHeadSlot := params.BeaconConfig().SlotsPerEpoch.Mul(uint64(ourHeadEpoch)) for _, pid := range connected { peerChainState, err := p.ChainState(pid) if err == nil && peerChainState != nil && peerChainState.HeadSlot > ourHeadSlot { epoch := slots.ToEpoch(peerChainState.HeadSlot) epochVotes[epoch]++ pidEpoch[pid] = epoch pidHead[pid] = peerChainState.HeadSlot potentialPIDs = append(potentialPIDs, pid) } } // Select the target epoch, which has enough peers' votes (>= minPeers). var targetEpoch primitives.Epoch for epoch, votes := range epochVotes { if votes >= uint64(minPeers) && targetEpoch < epoch { targetEpoch = epoch } } // Sort PIDs by head slot, in decreasing order. sort.Slice(potentialPIDs, func(i, j int) bool { return pidHead[potentialPIDs[i]] > pidHead[potentialPIDs[j]] }) // Trim potential peers to those on or after target epoch. for i, pid := range potentialPIDs { if pidEpoch[pid] < targetEpoch { potentialPIDs = potentialPIDs[:i] break } } return targetEpoch, potentialPIDs } // PeersToPrune selects the most suitable inbound peers // to disconnect the host peer from. As of this moment // the pruning relies on simple heuristics such as // bad response count. In the future scoring will be used // to determine the most suitable peers to take out. func (p *Status) PeersToPrune() []peer.ID { connLimit := p.ConnectedPeerLimit() inBoundLimit := uint64(p.InboundLimit()) activePeers := p.Active() numInboundPeers := uint64(len(p.InboundConnected())) // Exit early if we are still below our max // limit. if uint64(len(activePeers)) <= connLimit { return []peer.ID{} } p.store.Lock() defer p.store.Unlock() type peerResp struct { pid peer.ID score float64 } peersToPrune := make([]*peerResp, 0) // Select connected and inbound peers to prune. for pid, peerData := range p.store.Peers() { if peerData.ConnState == PeerConnected && peerData.Direction == network.DirInbound { peersToPrune = append(peersToPrune, &peerResp{ pid: pid, score: p.scorers.ScoreNoLock(pid), }) } } // Sort in ascending order to favour pruning peers with a // lower score. sort.Slice(peersToPrune, func(i, j int) bool { return peersToPrune[i].score < peersToPrune[j].score }) // Determine amount of peers to prune using our // max connection limit. amountToPrune, err := pmath.Sub64(uint64(len(activePeers)), connLimit) if err != nil { // This should never happen. log.WithError(err).Error("Failed to determine amount of peers to prune") return []peer.ID{} } // Also check for inbound peers above our limit. excessInbound := uint64(0) if numInboundPeers > inBoundLimit { excessInbound = numInboundPeers - inBoundLimit } // Prune the largest amount between excess peers and // excess inbound peers. if excessInbound > amountToPrune { amountToPrune = excessInbound } if amountToPrune < uint64(len(peersToPrune)) { peersToPrune = peersToPrune[:amountToPrune] } ids := make([]peer.ID, 0, len(peersToPrune)) for _, pr := range peersToPrune { ids = append(ids, pr.pid) } return ids } // HighestEpoch returns the highest epoch reported epoch amongst peers. func (p *Status) HighestEpoch() primitives.Epoch { p.store.RLock() defer p.store.RUnlock() var highestSlot primitives.Slot for _, peerData := range p.store.Peers() { if peerData != nil && peerData.ChainState != nil && peerData.ChainState.HeadSlot > highestSlot { highestSlot = peerData.ChainState.HeadSlot } } return slots.ToEpoch(highestSlot) } // ConnectedPeerLimit returns the peer limit of // concurrent peers connected to the beacon-node. func (p *Status) ConnectedPeerLimit() uint64 { maxLim := p.MaxPeerLimit() if maxLim <= maxLimitBuffer { return 0 } return uint64(maxLim) - maxLimitBuffer } // this method assumes the store lock is acquired before // executing the method. func (p *Status) isfromBadIP(pid peer.ID) bool { peerData, ok := p.store.PeerData(pid) if !ok { return false } if peerData.Address == nil { return false } ip, err := manet.ToIP(peerData.Address) if err != nil { return true } if val, ok := p.ipTracker[ip.String()]; ok { if val > ColocationLimit { return true } } return false } func (p *Status) addIpToTracker(pid peer.ID) { data, ok := p.store.PeerData(pid) if !ok { return } if data.Address == nil { return } ip, err := manet.ToIP(data.Address) if err != nil { // Should never happen, it is // assumed every IP coming in // is a valid ip. return } // Ignore loopback addresses. if ip.IsLoopback() { return } stringIP := ip.String() p.ipTracker[stringIP] += 1 } func (p *Status) tallyIPTracker() { tracker := map[string]uint64{} // Iterate through all peers. for _, peerData := range p.store.Peers() { if peerData.Address == nil { continue } ip, err := manet.ToIP(peerData.Address) if err != nil { // Should never happen, it is // assumed every IP coming in // is a valid ip. continue } stringIP := ip.String() tracker[stringIP] += 1 } p.ipTracker = tracker } func sameIP(firstAddr, secondAddr ma.Multiaddr) bool { // Exit early if we do get nil multiaddresses if firstAddr == nil || secondAddr == nil { return false } firstIP, err := manet.ToIP(firstAddr) if err != nil { return false } secondIP, err := manet.ToIP(secondAddr) if err != nil { return false } return firstIP.Equal(secondIP) } func indicesFromBitfield(bitV bitfield.Bitvector64) []uint64 { committeeIdxs := make([]uint64, 0, bitV.Count()) for i := uint64(0); i < 64; i++ { if bitV.BitAt(i) { committeeIdxs = append(committeeIdxs, i) } } return committeeIdxs }