diff --git a/catchup/peerSelector.go b/catchup/peerSelector.go index b78d802faa..a75e7c61f1 100644 --- a/catchup/peerSelector.go +++ b/catchup/peerSelector.go @@ -47,12 +47,16 @@ const ( peerRank3LowBlockTime = 601 peerRank3HighBlockTime = 799 + peerRankInitialFifthPriority = 800 + peerRank4LowBlockTime = 801 + peerRank4HighBlockTime = 999 + // peerRankDownloadFailed is used for responses which could be temporary, such as missing files, or such that we don't // have clear resolution - peerRankDownloadFailed = 900 + peerRankDownloadFailed = 10000 // peerRankInvalidDownload is used for responses which are likely to be invalid - whether it's serving the wrong content // or attempting to serve malicious content - peerRankInvalidDownload = 1000 + peerRankInvalidDownload = 12000 // once a block is downloaded, the download duration is clamped into the range of [lowBlockDownloadThreshold..highBlockDownloadThreshold] and // then mapped into the a ranking range. @@ -383,8 +387,10 @@ func (ps *peerSelector) peerDownloadDurationToRank(psp *peerSelectorPeer, blockD return downloadDurationToRank(blockDownloadDuration, lowBlockDownloadThreshold, highBlockDownloadThreshold, peerRank1LowBlockTime, peerRank1HighBlockTime) case peerRankInitialThirdPriority: return downloadDurationToRank(blockDownloadDuration, lowBlockDownloadThreshold, highBlockDownloadThreshold, peerRank2LowBlockTime, peerRank2HighBlockTime) - default: // i.e. peerRankInitialFourthPriority + case peerRankInitialFourthPriority: return downloadDurationToRank(blockDownloadDuration, lowBlockDownloadThreshold, highBlockDownloadThreshold, peerRank3LowBlockTime, peerRank3HighBlockTime) + default: // i.e. peerRankInitialFifthPriority + return downloadDurationToRank(blockDownloadDuration, lowBlockDownloadThreshold, highBlockDownloadThreshold, peerRank4LowBlockTime, peerRank4HighBlockTime) } } @@ -520,8 +526,10 @@ func lowerBound(class peerClass) int { return peerRank1LowBlockTime case peerRankInitialThirdPriority: return peerRank2LowBlockTime - default: // i.e. peerRankInitialFourthPriority + case peerRankInitialFourthPriority: return peerRank3LowBlockTime + default: // i.e. peerRankInitialFifthPriority + return peerRank4LowBlockTime } } @@ -533,8 +541,10 @@ func upperBound(class peerClass) int { return peerRank1HighBlockTime case peerRankInitialThirdPriority: return peerRank2HighBlockTime - default: // i.e. peerRankInitialFourthPriority + case peerRankInitialFourthPriority: return peerRank3HighBlockTime + default: // i.e. peerRankInitialFifthPriority + return peerRank4HighBlockTime } } diff --git a/catchup/peerSelector_test.go b/catchup/peerSelector_test.go index 8e66b9ab82..5d8f911623 100644 --- a/catchup/peerSelector_test.go +++ b/catchup/peerSelector_test.go @@ -381,12 +381,12 @@ func TestPeersDownloadFailed(t *testing.T) { if len(peerSelector.pools) == 2 { b1orb2 := peerAddress(peerSelector.pools[0].peers[1].peer) == "b1" || peerAddress(peerSelector.pools[0].peers[1].peer) == "b2" require.True(t, b1orb2) - require.Equal(t, peerSelector.pools[1].rank, 900) + require.Equal(t, peerSelector.pools[1].rank, peerRankDownloadFailed) require.Equal(t, len(peerSelector.pools[1].peers), 3) } else { // len(pools) == 3 b1orb2 := peerAddress(peerSelector.pools[1].peers[0].peer) == "b1" || peerAddress(peerSelector.pools[1].peers[0].peer) == "b2" require.True(t, b1orb2) - require.Equal(t, peerSelector.pools[2].rank, 900) + require.Equal(t, peerSelector.pools[2].rank, peerRankDownloadFailed) require.Equal(t, len(peerSelector.pools[2].peers), 3) } @@ -459,6 +459,7 @@ func TestPeerDownloadDurationToRank(t *testing.T) { peers2 := []network.Peer{&mockHTTPPeer{address: "b1"}, &mockHTTPPeer{address: "b2"}} peers3 := []network.Peer{&mockHTTPPeer{address: "c1"}, &mockHTTPPeer{address: "c2"}} peers4 := []network.Peer{&mockHTTPPeer{address: "d1"}, &mockHTTPPeer{address: "b2"}} + peers5 := []network.Peer{&mockHTTPPeer{address: "e1"}, &mockHTTPPeer{address: "b2"}} peerSelector := makePeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { @@ -469,15 +470,18 @@ func TestPeerDownloadDurationToRank(t *testing.T) { peers = append(peers, peers2...) } else if opt == network.PeersConnectedOut { peers = append(peers, peers3...) - } else { + } else if opt == network.PeersPhonebookArchivalNodes { peers = append(peers, peers4...) + } else { // PeersConnectedIn + peers = append(peers, peers5...) } } return }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}, {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersConnectedIn}}, + {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookArchivalNodes}, + {initialRank: peerRankInitialFifthPriority, peerClass: network.PeersConnectedIn}}, ) _, err := peerSelector.getNextPeer() @@ -490,7 +494,10 @@ func TestPeerDownloadDurationToRank(t *testing.T) { require.Equal(t, downloadDurationToRank(500*time.Millisecond, lowBlockDownloadThreshold, highBlockDownloadThreshold, peerRank2LowBlockTime, peerRank2HighBlockTime), peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers3[0], network.PeersConnectedOut}, 500*time.Millisecond)) require.Equal(t, downloadDurationToRank(500*time.Millisecond, lowBlockDownloadThreshold, highBlockDownloadThreshold, peerRank3LowBlockTime, peerRank3HighBlockTime), - peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers4[0], network.PeersConnectedIn}, 500*time.Millisecond)) + peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers4[0], network.PeersPhonebookArchivalNodes}, 500*time.Millisecond)) + require.Equal(t, downloadDurationToRank(500*time.Millisecond, lowBlockDownloadThreshold, highBlockDownloadThreshold, peerRank4LowBlockTime, peerRank4HighBlockTime), + peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers5[0], network.PeersConnectedIn}, 500*time.Millisecond)) + } func TestLowerUpperBounds(t *testing.T) { @@ -499,23 +506,26 @@ func TestLowerUpperBounds(t *testing.T) { classes := []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}, {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersConnectedIn}} + {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersConnectedIn}, + {initialRank: peerRankInitialFifthPriority, peerClass: network.PeersConnectedIn}} require.Equal(t, peerRank0LowBlockTime, lowerBound(classes[0])) require.Equal(t, peerRank1LowBlockTime, lowerBound(classes[1])) require.Equal(t, peerRank2LowBlockTime, lowerBound(classes[2])) require.Equal(t, peerRank3LowBlockTime, lowerBound(classes[3])) + require.Equal(t, peerRank4LowBlockTime, lowerBound(classes[4])) require.Equal(t, peerRank0HighBlockTime, upperBound(classes[0])) require.Equal(t, peerRank1HighBlockTime, upperBound(classes[1])) require.Equal(t, peerRank2HighBlockTime, upperBound(classes[2])) require.Equal(t, peerRank3HighBlockTime, upperBound(classes[3])) + require.Equal(t, peerRank4HighBlockTime, upperBound(classes[4])) } func TestFullResetRequestPenalty(t *testing.T) { partitiontest.PartitionTest(t) - class := peerClass{initialRank: 10, peerClass: network.PeersPhonebookArchivers} + class := peerClass{initialRank: 0, peerClass: network.PeersPhonebookArchivers} hs := makeHistoricStatus(10, class) hs.push(5, 1, class) require.Equal(t, 1, len(hs.requestGaps)) @@ -524,6 +534,30 @@ func TestFullResetRequestPenalty(t *testing.T) { require.Equal(t, 0, len(hs.requestGaps)) } +// TesPenaltyBounds makes sure that the penalty does not demote the peer to a lower class, +// and resetting the penalty of a demoted peer does not promote it back +func TestPenaltyBounds(t *testing.T) { + partitiontest.PartitionTest(t) + + class := peerClass{initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookArchivers} + hs := makeHistoricStatus(peerHistoryWindowSize, class) + for x := 0; x < 65; x++ { + r0 := hs.push(peerRank2LowBlockTime+50, uint64(x+1), class) + require.LessOrEqual(t, peerRank2LowBlockTime, r0) + require.GreaterOrEqual(t, peerRank2HighBlockTime, r0) + } + + r1 := hs.resetRequestPenalty(4, peerRankInitialThirdPriority, class) + r2 := hs.resetRequestPenalty(10, peerRankInitialThirdPriority, class) + r3 := hs.resetRequestPenalty(10, peerRankDownloadFailed, class) + + // r2 is at a better rank than r1 because it has one penalty less + require.Greater(t, r1, r2) + + // r3 is worse rank at peerRankDownloadFailed because it was demoted and resetRequestPenalty should not improve it + require.Equal(t, peerRankDownloadFailed, r3) +} + // TestClassUpperBound makes sure the peer rank does not exceed the class upper bound // This was a bug where the resetRequestPenalty was not bounding the returned rank, and was having download failures. // Initializing rankSamples to 0 makes this works, since the dropped value subtracts 0 from rankSum. @@ -613,7 +647,7 @@ func TestEvictionAndUpgrade(t *testing.T) { _, err := peerSelector.getNextPeer() require.NoError(t, err) for i := 0; i < 10; i++ { - if peerSelector.pools[len(peerSelector.pools)-1].rank == 900 { + if peerSelector.pools[len(peerSelector.pools)-1].rank == peerRankDownloadFailed { require.Equal(t, 6, i) break } diff --git a/catchup/service.go b/catchup/service.go index ac5fa730d4..766783fc33 100644 --- a/catchup/service.go +++ b/catchup/service.go @@ -816,15 +816,17 @@ func createPeerSelector(net network.GossipNode, cfg config.Local, pipelineFetch if cfg.NetAddress != "" { // Relay node peerClasses = []peerClass{ {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivers}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersConnectedIn}, + {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}, + {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookArchivers}, + {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookRelays}, + {initialRank: peerRankInitialFifthPriority, peerClass: network.PeersConnectedIn}, } } else { peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, + {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, + {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivers}, + {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersConnectedOut}, + {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookRelays}, } } } else { @@ -832,14 +834,16 @@ func createPeerSelector(net network.GossipNode, cfg config.Local, pipelineFetch peerClasses = []peerClass{ {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersConnectedIn}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookArchivers}, + {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookArchivalNodes}, + {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookRelays}, + {initialRank: peerRankInitialFifthPriority, peerClass: network.PeersPhonebookArchivers}, } } else { peerClasses = []peerClass{ {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookArchivers}, + {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}, + {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, + {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookArchivers}, } } } @@ -848,13 +852,15 @@ func createPeerSelector(net network.GossipNode, cfg config.Local, pipelineFetch if cfg.NetAddress != "" { // Relay node peerClasses = []peerClass{ {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersConnectedIn}, + {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}, + {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, + {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersConnectedIn}, } } else { peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}, + {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, + {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersConnectedOut}, + {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, } } } else { @@ -862,12 +868,14 @@ func createPeerSelector(net network.GossipNode, cfg config.Local, pipelineFetch peerClasses = []peerClass{ {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersConnectedIn}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, + {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookArchivalNodes}, + {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookRelays}, } } else { peerClasses = []peerClass{ {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}, + {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}, + {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, } } } diff --git a/catchup/service_test.go b/catchup/service_test.go index 217c23bf77..b0a6501328 100644 --- a/catchup/service_test.go +++ b/catchup/service_test.go @@ -925,30 +925,34 @@ func TestCreatePeerSelector(t *testing.T) { cfg.NetAddress = "someAddress" s := MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) ps := createPeerSelector(s.net, s.cfg, true) - require.Equal(t, 4, len(ps.peerClasses)) + require.Equal(t, 5, len(ps.peerClasses)) require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) require.Equal(t, peerRankInitialFourthPriority, ps.peerClasses[3].initialRank) + require.Equal(t, peerRankInitialFifthPriority, ps.peerClasses[4].initialRank) require.Equal(t, network.PeersConnectedOut, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersPhonebookArchivers, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) - require.Equal(t, network.PeersConnectedIn, ps.peerClasses[3].peerClass) + require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[1].peerClass) + require.Equal(t, network.PeersPhonebookArchivers, ps.peerClasses[2].peerClass) + require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[3].peerClass) + require.Equal(t, network.PeersConnectedIn, ps.peerClasses[4].peerClass) // cfg.EnableCatchupFromArchiveServers = true; cfg.NetAddress == ""; pipelineFetch = true; cfg.EnableCatchupFromArchiveServers = true cfg.NetAddress = "" s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) ps = createPeerSelector(s.net, s.cfg, true) - require.Equal(t, 3, len(ps.peerClasses)) + require.Equal(t, 4, len(ps.peerClasses)) require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) + require.Equal(t, peerRankInitialFourthPriority, ps.peerClasses[3].initialRank) - require.Equal(t, network.PeersPhonebookArchivers, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersConnectedOut, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) + require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[0].peerClass) + require.Equal(t, network.PeersPhonebookArchivers, ps.peerClasses[1].peerClass) + require.Equal(t, network.PeersConnectedOut, ps.peerClasses[2].peerClass) + require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[3].peerClass) // cfg.EnableCatchupFromArchiveServers = true; cfg.NetAddress != ""; pipelineFetch = false cfg.EnableCatchupFromArchiveServers = true @@ -956,16 +960,18 @@ func TestCreatePeerSelector(t *testing.T) { s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) ps = createPeerSelector(s.net, s.cfg, false) - require.Equal(t, 4, len(ps.peerClasses)) + require.Equal(t, 5, len(ps.peerClasses)) require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) require.Equal(t, peerRankInitialFourthPriority, ps.peerClasses[3].initialRank) + require.Equal(t, peerRankInitialFifthPriority, ps.peerClasses[4].initialRank) require.Equal(t, network.PeersConnectedOut, ps.peerClasses[0].peerClass) require.Equal(t, network.PeersConnectedIn, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) - require.Equal(t, network.PeersPhonebookArchivers, ps.peerClasses[3].peerClass) + require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[2].peerClass) + require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[3].peerClass) + require.Equal(t, network.PeersPhonebookArchivers, ps.peerClasses[4].peerClass) // cfg.EnableCatchupFromArchiveServers = true; cfg.NetAddress == ""; pipelineFetch = false cfg.EnableCatchupFromArchiveServers = true @@ -973,14 +979,16 @@ func TestCreatePeerSelector(t *testing.T) { s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) ps = createPeerSelector(s.net, s.cfg, false) - require.Equal(t, 3, len(ps.peerClasses)) + require.Equal(t, 4, len(ps.peerClasses)) require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) + require.Equal(t, peerRankInitialFourthPriority, ps.peerClasses[3].initialRank) require.Equal(t, network.PeersConnectedOut, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersPhonebookArchivers, ps.peerClasses[2].peerClass) + require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[1].peerClass) + require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) + require.Equal(t, network.PeersPhonebookArchivers, ps.peerClasses[3].peerClass) // cfg.EnableCatchupFromArchiveServers = false; cfg.NetAddress != ""; pipelineFetch = true cfg.EnableCatchupFromArchiveServers = false @@ -988,14 +996,16 @@ func TestCreatePeerSelector(t *testing.T) { s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) ps = createPeerSelector(s.net, s.cfg, true) - require.Equal(t, 3, len(ps.peerClasses)) + require.Equal(t, 4, len(ps.peerClasses)) require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) + require.Equal(t, peerRankInitialFourthPriority, ps.peerClasses[3].initialRank) require.Equal(t, network.PeersConnectedOut, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersConnectedIn, ps.peerClasses[2].peerClass) + require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[1].peerClass) + require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) + require.Equal(t, network.PeersConnectedIn, ps.peerClasses[3].peerClass) // cfg.EnableCatchupFromArchiveServers = false; cfg.NetAddress == ""; pipelineFetch = true cfg.EnableCatchupFromArchiveServers = false @@ -1003,12 +1013,14 @@ func TestCreatePeerSelector(t *testing.T) { s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) ps = createPeerSelector(s.net, s.cfg, true) - require.Equal(t, 2, len(ps.peerClasses)) + require.Equal(t, 3, len(ps.peerClasses)) require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) + require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) - require.Equal(t, network.PeersConnectedOut, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[1].peerClass) + require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[0].peerClass) + require.Equal(t, network.PeersConnectedOut, ps.peerClasses[1].peerClass) + require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) // cfg.EnableCatchupFromArchiveServers = false; cfg.NetAddress != ""; pipelineFetch = false cfg.EnableCatchupFromArchiveServers = false @@ -1016,14 +1028,16 @@ func TestCreatePeerSelector(t *testing.T) { s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) ps = createPeerSelector(s.net, s.cfg, false) - require.Equal(t, 3, len(ps.peerClasses)) + require.Equal(t, 4, len(ps.peerClasses)) require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) + require.Equal(t, peerRankInitialFourthPriority, ps.peerClasses[3].initialRank) require.Equal(t, network.PeersConnectedOut, ps.peerClasses[0].peerClass) require.Equal(t, network.PeersConnectedIn, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) + require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[2].peerClass) + require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[3].peerClass) // cfg.EnableCatchupFromArchiveServers = false; cfg.NetAddress == ""; pipelineFetch = false cfg.EnableCatchupFromArchiveServers = false @@ -1031,12 +1045,14 @@ func TestCreatePeerSelector(t *testing.T) { s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) ps = createPeerSelector(s.net, s.cfg, false) - require.Equal(t, 2, len(ps.peerClasses)) + require.Equal(t, 3, len(ps.peerClasses)) require.Equal(t, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) require.Equal(t, peerRankInitialSecondPriority, ps.peerClasses[1].initialRank) + require.Equal(t, peerRankInitialThirdPriority, ps.peerClasses[2].initialRank) require.Equal(t, network.PeersConnectedOut, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[1].peerClass) + require.Equal(t, network.PeersPhonebookArchivalNodes, ps.peerClasses[1].peerClass) + require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) } func TestServiceStartStop(t *testing.T) { diff --git a/network/wsNetwork.go b/network/wsNetwork.go index 9dcf9aab6a..02784393cc 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -163,6 +163,8 @@ const ( PeersConnectedIn PeerOption = iota // PeersPhonebookRelays specifies all relays in the phonebook PeersPhonebookRelays PeerOption = iota + // PeersPhonebookArchivalNodes specifies all archival nodes (relay or p2p) + PeersPhonebookArchivalNodes PeerOption = iota // PeersPhonebookArchivers specifies all archivers in the phonebook PeersPhonebookArchivers PeerOption = iota ) @@ -688,6 +690,13 @@ func (wn *WebsocketNetwork) GetPeers(options ...PeerOption) []Peer { peerCore := makePeerCore(wn, addr, wn.GetRoundTripper(), "" /*origin address*/) outPeers = append(outPeers, &peerCore) } + case PeersPhonebookArchivalNodes: + var addrs []string + addrs = wn.phonebook.GetAddresses(1000, PhoneBookEntryRelayRole) + for _, addr := range addrs { + peerCore := makePeerCore(wn, addr, wn.GetRoundTripper(), "" /*origin address*/) + outPeers = append(outPeers, &peerCore) + } case PeersPhonebookArchivers: // return copy of phonebook, which probably also contains peers we're connected to, but if it doesn't maybe we shouldn't be making new connections to those peers (because they disappeared from the directory) var addrs []string diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go index 93afba1ed4..bafc4f2b39 100644 --- a/network/wsNetwork_test.go +++ b/network/wsNetwork_test.go @@ -1136,6 +1136,16 @@ func TestGetPeers(t *testing.T) { expectAddrs := []string{addrA, "a", "b", "c"} sort.Strings(expectAddrs) assert.Equal(t, expectAddrs, peerAddrs) + + // For now, PeersPhonebookArchivalNodes and PeersPhonebookRelays will return the same set of nodes + bPeers2 := netB.GetPeers(PeersPhonebookArchivalNodes) + peerAddrs2 := make([]string, len(bPeers2)) + for pi2, peer2 := range bPeers2 { + peerAddrs2[pi2] = peer2.(HTTPPeer).GetAddress() + } + sort.Strings(peerAddrs2) + assert.Equal(t, expectAddrs, peerAddrs2) + } // confirms that if the config PublicAddress is set to "testing", diff --git a/tools/network/bootstrap.go b/tools/network/bootstrap.go index b3bd79b23c..7558100a65 100644 --- a/tools/network/bootstrap.go +++ b/tools/network/bootstrap.go @@ -19,12 +19,12 @@ package network import ( "context" "fmt" + "net" "github.com/algorand/go-algorand/logging" ) -// ReadFromSRV is a helper to collect SRV addresses for a given name. -func ReadFromSRV(service string, protocol string, name string, fallbackDNSResolverAddress string, secure bool) (addrs []string, err error) { +func readFromSRV(service string, protocol string, name string, fallbackDNSResolverAddress string, secure bool) (records []*net.SRV, err error) { log := logging.Base() if name == "" { log.Debug("no dns lookup due to empty name") @@ -61,6 +61,38 @@ func ReadFromSRV(service string, protocol string, name string, fallbackDNSResolv } } } + return records, err +} + +// ReadFromSRV is a helper to collect SRV addresses for a given name +func ReadFromSRV(service string, protocol string, name string, fallbackDNSResolverAddress string, secure bool) (addrs []string, err error) { + records, err := readFromSRV(service, protocol, name, fallbackDNSResolverAddress, secure) + if err != nil { + return addrs, err + } + + for _, srv := range records { + // empty target won't take us far; skip these + if srv.Target == "" { + continue + } + // according to the SRV spec, each target need to end with a dot. While this would make a valid host name, including the + // last dot could lead to a non-canonical domain name representation, which is better avoided. + if srv.Target[len(srv.Target)-1:] == "." { + srv.Target = srv.Target[:len(srv.Target)-1] + } + addrs = append(addrs, fmt.Sprintf("%s:%d", srv.Target, srv.Port)) + } + return +} + +// ReadFromSRVPriority is a helper to collect SRV addresses with priorities for a given name +func ReadFromSRVPriority(service string, protocol string, name string, fallbackDNSResolverAddress string, secure bool) (prioAddrs map[uint16][]string, err error) { + records, err := readFromSRV(service, protocol, name, fallbackDNSResolverAddress, secure) + if err != nil { + return prioAddrs, err + } + prioAddrs = make(map[uint16][]string, 4) for _, srv := range records { // empty target won't take us far; skip these if srv.Target == "" { @@ -71,7 +103,9 @@ func ReadFromSRV(service string, protocol string, name string, fallbackDNSResolv if srv.Target[len(srv.Target)-1:] == "." { srv.Target = srv.Target[:len(srv.Target)-1] } + addrs := prioAddrs[srv.Priority] addrs = append(addrs, fmt.Sprintf("%s:%d", srv.Target, srv.Port)) + prioAddrs[srv.Priority] = addrs } return } diff --git a/tools/network/bootstrap_test.go b/tools/network/bootstrap_test.go new file mode 100644 index 0000000000..69ed39b2de --- /dev/null +++ b/tools/network/bootstrap_test.go @@ -0,0 +1,66 @@ +// Copyright (C) 2019-2023 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package network + +import ( + "testing" + + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" +) + +func TestReadFromSRVPriority(t *testing.T) { + t.Parallel() + partitiontest.PartitionTest(t) + + service := "telemetry" + protocol := "tls" + name := "devnet.algodev.network" + fallback := "" + secure := true + + prioAddrs, err := ReadFromSRVPriority("", protocol, name, fallback, secure) + require.Error(t, err) + + prioAddrs, err = ReadFromSRVPriority(service, protocol, name, fallback, secure) + require.NoError(t, err) + addrs, ok := prioAddrs[1] + require.True(t, ok) + require.GreaterOrEqual(t, len(addrs), 1) + addr := addrs[0] + require.Greater(t, len(addr), 1) +} + +func TestReadFromSRV(t *testing.T) { + t.Parallel() + partitiontest.PartitionTest(t) + + service := "telemetry" + protocol := "tls" + name := "devnet.algodev.network" + fallback := "" + secure := true + + addrs, err := ReadFromSRV("", protocol, name, fallback, secure) + require.Error(t, err) + + addrs, err = ReadFromSRV(service, protocol, name, fallback, secure) + require.NoError(t, err) + require.GreaterOrEqual(t, len(addrs), 1) + addr := addrs[0] + require.Greater(t, len(addr), 1) +}