Tidy-up of BestFinalized (#4505)

* Tidy up BestFinalized
* Ensure no more than maxPeers returned
* Merge branch 'master' into bestfinalized
* Merge branch 'master' into bestfinalized
* Merge branch 'master' into bestfinalized
* Merge branch 'master' into bestfinalized
* Remove swap file
* Provide potential PIDs array with capacity
* Add test for trimming and ordering in BestFinalized
* Merge branch 'master' into bestfinalized
This commit is contained in:
Jim McDonald 2020-01-13 18:12:10 +00:00 committed by prylabs-bulldozer[bot]
parent 888e8925ee
commit 3d24a85121
2 changed files with 86 additions and 18 deletions

View File

@ -21,6 +21,7 @@ package peers
import (
"errors"
"sort"
"sync"
"time"
@ -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
}
}
targetEpoch := rootToEpoch[targetRoot]
// 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
}
}
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
}
}
// Trim potential peers to at most maxPeers.
if len(potentialPIDs) > maxPeers {
potentialPIDs = potentialPIDs[:maxPeers]
}
return mostVotedFinalizedRoot[:], rootToEpoch[mostVotedFinalizedRoot], pids
return targetRoot[:], targetEpoch, potentialPIDs
}
// fetch is a helper function that fetches a peer status, possibly creating it.

View File

@ -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)