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

Tidy-up of BestFinalized #4505

Merged
merged 10 commits into from Jan 13, 2020
52 changes: 34 additions & 18 deletions beacon-chain/p2p/peers/status.go
Expand Up @@ -21,6 +21,7 @@ package peers

import (
"errors"
"sort"
"sync"
"time"

Expand Down Expand Up @@ -319,41 +320,56 @@ func (p *Status) Decay() {

// 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.
// Ideally, all peers would be reporting the same finalized epoch.
// Returns the best finalized root, epoch number, and list of peers that agree.
// 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 the best finalized root, epoch number, and list of peers that are at or beyond that epoch.
func (p *Status) BestFinalized(maxPeers int, ourFinalizedEpoch uint64) ([]byte, uint64, []peer.ID) {
connected := p.Connected()
finalized := make(map[[32]byte]uint64)
rootToEpoch := make(map[[32]byte]uint64)
for _, pid := range p.Connected() {
pidEpochs := make(map[peer.ID]uint64)
potentialPIDs := make([]peer.ID, 0, len(connected))
for _, pid := range connected {
peerChainState, err := p.ChainState(pid)
if err == nil && peerChainState != nil && peerChainState.FinalizedEpoch >= ourFinalizedEpoch {
r := bytesutil.ToBytes32(peerChainState.FinalizedRoot)
finalized[r]++
rootToEpoch[r] = peerChainState.FinalizedEpoch
root := bytesutil.ToBytes32(peerChainState.FinalizedRoot)
finalized[root]++
rootToEpoch[root] = peerChainState.FinalizedEpoch
pidEpochs[pid] = peerChainState.FinalizedEpoch
potentialPIDs = append(potentialPIDs, pid)
}
}

var mostVotedFinalizedRoot [32]byte
// Select the target epoch, which is the epoch most peers agree upon.
var targetRoot [32]byte
var mostVotes uint64
for root, count := range finalized {
if count > mostVotes {
mostVotes = count
mostVotedFinalizedRoot = root
targetRoot = root
}
}

var pids []peer.ID
for _, pid := range p.Connected() {
peerChainState, err := p.ChainState(pid)
if err == nil && peerChainState != nil && peerChainState.FinalizedEpoch >= rootToEpoch[mostVotedFinalizedRoot] {
pids = append(pids, pid)
if len(pids) >= maxPeers {
break
}
targetEpoch := rootToEpoch[targetRoot]
mcdee marked this conversation as resolved.
Show resolved Hide resolved

// Sort PIDs by finalized epoch, in decreasing order.
sort.Slice(potentialPIDs, func(i, j int) bool {
return pidEpochs[potentialPIDs[i]] > pidEpochs[potentialPIDs[j]]
})

// Trim potential peers to those on or after target epoch.
for i, pid := range potentialPIDs {
if pidEpochs[pid] < targetEpoch {
potentialPIDs = potentialPIDs[:i]
break
}
}

return mostVotedFinalizedRoot[:], rootToEpoch[mostVotedFinalizedRoot], pids
// Trim potential peers to at most maxPeers.
if len(potentialPIDs) > maxPeers {
potentialPIDs = potentialPIDs[:maxPeers]
}

return targetRoot[:], targetEpoch, potentialPIDs
}

// fetch is a helper function that fetches a peer status, possibly creating it.
Expand Down
52 changes: 52 additions & 0 deletions beacon-chain/p2p/peers/status_test.go
Expand Up @@ -335,6 +335,58 @@ func TestDecay(t *testing.T) {
}
}

func TestTrimmedOrderedPeers(t *testing.T) {
p := peers.NewStatus(1)

expectedTarget := uint64(2)
maxPeers := 3

// Peer 1
pid1 := addPeer(t, p, peers.PeerConnected)
p.SetChainState(pid1, &pb.Status{
FinalizedEpoch: 3,
})
// Peer 2
pid2 := addPeer(t, p, peers.PeerConnected)
p.SetChainState(pid2, &pb.Status{
FinalizedEpoch: 4,
})
// Peer 3
pid3 := addPeer(t, p, peers.PeerConnected)
p.SetChainState(pid3, &pb.Status{
FinalizedEpoch: 5,
})
// Peer 4
pid4 := addPeer(t, p, peers.PeerConnected)
p.SetChainState(pid4, &pb.Status{
FinalizedEpoch: 2,
})
// Peer 5
pid5 := addPeer(t, p, peers.PeerConnected)
p.SetChainState(pid5, &pb.Status{
FinalizedEpoch: 2,
})

_, target, pids := p.BestFinalized(maxPeers, 0)
if target != expectedTarget {
t.Errorf("Incorrect target epoch retrieved; wanted %v but got %v", expectedTarget, target)
}
if len(pids) != maxPeers {
t.Errorf("Incorrect number of peers retrieved; wanted %v but got %v", maxPeers, len(pids))
}

// Expect the returned list to be ordered by finalized epoch and trimmed to max peers.
if pids[0] != pid3 {
t.Errorf("Incorrect first peer; wanted %v but got %v", pid3, pids[0])
}
if pids[1] != pid2 {
t.Errorf("Incorrect second peer; wanted %v but got %v", pid2, pids[1])
}
if pids[2] != pid1 {
t.Errorf("Incorrect third peer; wanted %v but got %v", pid1, pids[2])
}
}

func TestBestPeer(t *testing.T) {
maxBadResponses := 2
expectedFinEpoch := uint64(4)
Expand Down