From 2e41eef1ae64a283f90b2fe54e07df90b47cc5a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 12:06:19 -0500 Subject: [PATCH 01/16] Build: bump jinja2 from 3.1.2 to 3.1.3 in /test/heapwatch (#5903) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- test/heapwatch/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/heapwatch/requirements.txt b/test/heapwatch/requirements.txt index b46aead08e..d8ee41d7e6 100644 --- a/test/heapwatch/requirements.txt +++ b/test/heapwatch/requirements.txt @@ -1,6 +1,6 @@ dash==2.11.1 dash-table==5.0.0 -Jinja2==3.1.2 +Jinja2==3.1.3 matplotlib==3.7.2 plotly==5.16.0 py-algorand-sdk==2.3.0 From 86319605bebbc2d48f68b6d4360ebc917e018e85 Mon Sep 17 00:00:00 2001 From: Gary <982483+gmalouf@users.noreply.github.com> Date: Thu, 25 Jan 2024 13:26:03 -0500 Subject: [PATCH 02/16] Archival Support: Remove configurable support for catching up from "archivers" (#5920) Co-authored-by: John Jannotti --- catchup/catchpointService.go | 19 +--- catchup/peerSelector_test.go | 69 ++++++------ catchup/service.go | 80 ++++--------- catchup/service_test.go | 104 ++++++----------- config/localTemplate.go | 20 +--- config/local_defaults.go | 2 - installer/config.json.example | 2 - network/wsNetwork.go | 15 ++- network/wsNetwork_test.go | 132 ++++++++++------------ rpcs/blockService.go | 32 +----- rpcs/blockService_test.go | 156 ++++++++++---------------- test/testdata/configs/config-v33.json | 2 - 12 files changed, 227 insertions(+), 406 deletions(-) diff --git a/catchup/catchpointService.go b/catchup/catchpointService.go index f71a4209ae..2c4f6dfc41 100644 --- a/catchup/catchpointService.go +++ b/catchup/catchpointService.go @@ -806,20 +806,11 @@ func (cs *CatchpointCatchupService) initDownloadPeerSelector() { } func (cs *CatchpointCatchupService) makeCatchpointPeerSelector() *peerSelector { - if cs.config.EnableCatchupFromArchiveServers { - return makePeerSelector( - cs.net, - []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}, - }) - } else { - return makePeerSelector( - cs.net, - []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookRelays}, - }) - } + return makePeerSelector( + cs.net, + []peerClass{ + {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookRelays}, + }) } // checkLedgerDownload sends a HEAD request to the ledger endpoint of peers to validate the catchpoint's availability diff --git a/catchup/peerSelector_test.go b/catchup/peerSelector_test.go index 2a907934a8..aa8d348d43 100644 --- a/catchup/peerSelector_test.go +++ b/catchup/peerSelector_test.go @@ -134,7 +134,7 @@ func TestPeerSelector_RankPeer(t *testing.T) { peerSelector := makePeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) []network.Peer { return peers - }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}}, + }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}}, ) psp, err := peerSelector.getNextPeer() @@ -194,14 +194,14 @@ func TestPeerSelector_PeerDownloadRanking(t *testing.T) { peerSelector := makePeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { - if opt == network.PeersPhonebookArchivers { + if opt == network.PeersPhonebookArchivalNodes { peers = append(peers, peers1...) } else { peers = append(peers, peers2...) } } return - }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, + }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}}, ) archivalPeer, err := peerSelector.getNextPeer() @@ -243,7 +243,7 @@ func TestPeerSelector_FindMissingPeer(t *testing.T) { peerSelector := makePeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) []network.Peer { return []network.Peer{} - }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}}, + }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}}, ) poolIdx, peerIdx := peerSelector.findPeer(&peerSelectorPeer{mockHTTPPeer{address: "abcd"}, 0}) @@ -261,14 +261,14 @@ func TestPeerSelector_HistoricData(t *testing.T) { peerSelector := makePeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { - if opt == network.PeersPhonebookArchivers { + if opt == network.PeersPhonebookArchivalNodes { peers = append(peers, peers1...) } else { peers = append(peers, peers2...) } } return - }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, + }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}}, ) @@ -335,14 +335,14 @@ func TestPeerSelector_PeersDownloadFailed(t *testing.T) { peerSelector := makePeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { - if opt == network.PeersPhonebookArchivers { + if opt == network.PeersPhonebookArchivalNodes { peers = append(peers, peers1...) } else { peers = append(peers, peers2...) } } return - }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, + }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}}, ) @@ -411,14 +411,14 @@ func TestPeerSelector_Penalty(t *testing.T) { peerSelector := makePeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { - if opt == network.PeersPhonebookArchivers { + if opt == network.PeersPhonebookArchivalNodes { peers = append(peers, peers1...) } else { peers = append(peers, peers2...) } } return - }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, + }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}}, ) @@ -468,44 +468,39 @@ func TestPeerSelector_PeerDownloadDurationToRank(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) { for _, opt := range options { - if opt == network.PeersPhonebookArchivers { + if opt == network.PeersPhonebookRelays { peers = append(peers, peers1...) - } else if opt == network.PeersPhonebookRelays { - peers = append(peers, peers2...) } else if opt == network.PeersConnectedOut { - peers = append(peers, peers3...) + peers = append(peers, peers2...) } else if opt == network.PeersPhonebookArchivalNodes { - peers = append(peers, peers4...) + peers = append(peers, peers3...) } else { // PeersConnectedIn - peers = append(peers, peers5...) + peers = append(peers, peers4...) } } return - }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialFifthPriority, peerClass: network.PeersConnectedIn}}, + }), []peerClass{ + {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookRelays}, + {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersConnectedOut}, + {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookArchivalNodes}, + {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersConnectedIn}}, ) _, err := peerSelector.getNextPeer() require.NoError(t, err) require.Equal(t, downloadDurationToRank(500*time.Millisecond, lowBlockDownloadThreshold, highBlockDownloadThreshold, peerRank0LowBlockTime, peerRank0HighBlockTime), - peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers1[0], network.PeersPhonebookArchivers}, 500*time.Millisecond)) + peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers1[0], network.PeersPhonebookRelays}, 500*time.Millisecond)) require.Equal(t, downloadDurationToRank(500*time.Millisecond, lowBlockDownloadThreshold, highBlockDownloadThreshold, peerRank1LowBlockTime, peerRank1HighBlockTime), - peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers2[0], network.PeersPhonebookRelays}, 500*time.Millisecond)) + peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers2[0], network.PeersConnectedOut}, 500*time.Millisecond)) require.Equal(t, downloadDurationToRank(500*time.Millisecond, lowBlockDownloadThreshold, highBlockDownloadThreshold, peerRank2LowBlockTime, peerRank2HighBlockTime), - peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers3[0], network.PeersConnectedOut}, 500*time.Millisecond)) + peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers3[0], network.PeersPhonebookArchivalNodes}, 500*time.Millisecond)) require.Equal(t, downloadDurationToRank(500*time.Millisecond, lowBlockDownloadThreshold, highBlockDownloadThreshold, peerRank3LowBlockTime, peerRank3HighBlockTime), - 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)) + peerSelector.peerDownloadDurationToRank(&peerSelectorPeer{peers4[0], network.PeersConnectedIn}, 500*time.Millisecond)) } @@ -513,7 +508,7 @@ func TestPeerSelector_LowerUpperBounds(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - classes := []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, + classes := []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}, {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersConnectedOut}, {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersConnectedIn}, @@ -536,7 +531,7 @@ func TestPeerSelector_FullResetRequestPenalty(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - class := peerClass{initialRank: 0, peerClass: network.PeersPhonebookArchivers} + class := peerClass{initialRank: 0, peerClass: network.PeersPhonebookArchivalNodes} hs := makeHistoricStatus(10, class) hs.push(5, 1, class) require.Equal(t, 1, len(hs.requestGaps)) @@ -551,7 +546,7 @@ func TestPeerSelector_PenaltyBounds(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - class := peerClass{initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookArchivers} + class := peerClass{initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookArchivalNodes} hs := makeHistoricStatus(peerHistoryWindowSize, class) for x := 0; x < 65; x++ { r0 := hs.push(peerRank2LowBlockTime+50, uint64(x+1), class) @@ -578,11 +573,11 @@ func TestPeerSelector_ClassUpperBound(t *testing.T) { t.Parallel() peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}, &mockHTTPPeer{address: "a2"}} - pClass := peerClass{initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivers} + pClass := peerClass{initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes} peerSelector := makePeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { - if opt == network.PeersPhonebookArchivers { + if opt == network.PeersPhonebookArchivalNodes { peers = append(peers, peers1...) } } @@ -613,11 +608,11 @@ func TestPeerSelector_ClassLowerBound(t *testing.T) { t.Parallel() peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}, &mockHTTPPeer{address: "a2"}} - pClass := peerClass{initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivers} + pClass := peerClass{initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes} peerSelector := makePeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { - if opt == network.PeersPhonebookArchivers { + if opt == network.PeersPhonebookArchivalNodes { peers = append(peers, peers1...) } } @@ -647,14 +642,14 @@ func TestPeerSelector_EvictionAndUpgrade(t *testing.T) { peerSelector := makePeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { - if opt == network.PeersPhonebookArchivers { + if opt == network.PeersPhonebookArchivalNodes { peers = append(peers, peers1...) } else { peers = append(peers, peers2...) } } return - }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivers}, + }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookRelays}}, ) diff --git a/catchup/service.go b/catchup/service.go index 1093f851a2..58fc3ae6b8 100644 --- a/catchup/service.go +++ b/catchup/service.go @@ -868,72 +868,34 @@ func (s *Service) roundIsNotSupported(nextRound basics.Round) bool { func createPeerSelector(net network.GossipNode, cfg config.Local, pipelineFetch bool) *peerSelector { var peerClasses []peerClass - if cfg.EnableCatchupFromArchiveServers { - if pipelineFetch { - if cfg.NetAddress != "" { // Relay node - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {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.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivers}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookRelays}, - } + if pipelineFetch { + if cfg.NetAddress != "" && cfg.EnableGossipService { // Relay node + peerClasses = []peerClass{ + {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, + {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}, + {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, + {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersConnectedIn}, } } else { - if cfg.NetAddress != "" { // Relay node - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersConnectedIn}, - {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.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookArchivers}, - } + peerClasses = []peerClass{ + {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, + {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersConnectedOut}, + {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, } } } else { - if pipelineFetch { - if cfg.NetAddress != "" { // Relay node - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersConnectedIn}, - } - } else { - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, - } + if cfg.NetAddress != "" && cfg.EnableGossipService { // Relay node + peerClasses = []peerClass{ + {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, + {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersConnectedIn}, + {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookArchivalNodes}, + {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookRelays}, } } else { - if cfg.NetAddress != "" { // Relay node - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersConnectedIn}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookRelays}, - } - } else { - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, - } + peerClasses = []peerClass{ + {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, + {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}, + {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, } } } diff --git a/catchup/service_test.go b/catchup/service_test.go index c5b8cfccab..8deb692b0d 100644 --- a/catchup/service_test.go +++ b/catchup/service_test.go @@ -954,71 +954,18 @@ func TestCatchupUnmatchedCertificate(t *testing.T) { } } -// TestCreatePeerSelector tests if the correct peer selector coonfigurations are prepared +// TestCreatePeerSelector tests if the correct peer selector configurations are prepared func TestCreatePeerSelector(t *testing.T) { partitiontest.PartitionTest(t) // Make Service cfg := defaultConfig - cfg.EnableCatchupFromArchiveServers = true - + // cfg.NetAddress != ""; cfg.EnableGossipService = true; pipelineFetch = true cfg.NetAddress = "someAddress" + cfg.EnableGossipService = true 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, 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.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, 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.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 - cfg.NetAddress = "someAddress" - 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, 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.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 - cfg.NetAddress = "" - 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, peerRankInitialFirstPriority, ps.peerClasses[0].initialRank) @@ -1029,28 +976,26 @@ func TestCreatePeerSelector(t *testing.T) { require.Equal(t, network.PeersConnectedOut, ps.peerClasses[0].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) + require.Equal(t, network.PeersConnectedIn, ps.peerClasses[3].peerClass) - // cfg.EnableCatchupFromArchiveServers = false; cfg.NetAddress != ""; pipelineFetch = true - cfg.EnableCatchupFromArchiveServers = false - cfg.NetAddress = "someAddress" + // cfg.NetAddress == ""; cfg.EnableGossipService = true; pipelineFetch = true + cfg.NetAddress = "" + cfg.EnableGossipService = true 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, 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, peerRankInitialFourthPriority, ps.peerClasses[3].initialRank) - require.Equal(t, network.PeersConnectedOut, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersPhonebookArchivalNodes, 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) - require.Equal(t, network.PeersConnectedIn, ps.peerClasses[3].peerClass) - // cfg.EnableCatchupFromArchiveServers = false; cfg.NetAddress == ""; pipelineFetch = true - cfg.EnableCatchupFromArchiveServers = false - cfg.NetAddress = "" + // cfg.NetAddress != ""; cfg.EnableGossipService = false; pipelineFetch = true + cfg.NetAddress = "someAddress" + cfg.EnableGossipService = false s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) ps = createPeerSelector(s.net, s.cfg, true) @@ -1063,9 +1008,9 @@ func TestCreatePeerSelector(t *testing.T) { 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 + // cfg.NetAddress != ""; cfg.EnableGossipService = true; pipelineFetch = false cfg.NetAddress = "someAddress" + cfg.EnableGossipService = true s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) ps = createPeerSelector(s.net, s.cfg, false) @@ -1080,9 +1025,24 @@ func TestCreatePeerSelector(t *testing.T) { 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 + // cfg.NetAddress == ""; cfg.EnableGossipService = true; pipelineFetch = false cfg.NetAddress = "" + cfg.EnableGossipService = true + 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, 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.PeersPhonebookArchivalNodes, ps.peerClasses[1].peerClass) + require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) + + // cfg.NetAddress != ""; cfg.EnableGossipService = false; pipelineFetch = false + cfg.NetAddress = "someAddress" + cfg.EnableGossipService = false s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) ps = createPeerSelector(s.net, s.cfg, false) diff --git a/config/localTemplate.go b/config/localTemplate.go index c6e1ba0a2c..618facfd29 100644 --- a/config/localTemplate.go +++ b/config/localTemplate.go @@ -44,13 +44,13 @@ type Local struct { // for an existing parameter. This field tag must be updated any time we add a new version. Version uint32 `version[0]:"0" version[1]:"1" version[2]:"2" version[3]:"3" version[4]:"4" version[5]:"5" version[6]:"6" version[7]:"7" version[8]:"8" version[9]:"9" version[10]:"10" version[11]:"11" version[12]:"12" version[13]:"13" version[14]:"14" version[15]:"15" version[16]:"16" version[17]:"17" version[18]:"18" version[19]:"19" version[20]:"20" version[21]:"21" version[22]:"22" version[23]:"23" version[24]:"24" version[25]:"25" version[26]:"26" version[27]:"27" version[28]:"28" version[29]:"29" version[30]:"30" version[31]:"31" version[32]:"32" version[33]:"33"` - // Archival nodes retain a full copy of the block history. Non-Archival nodes will delete old blocks and only retain what's need to properly validate blockchain messages (the precise number of recent blocks depends on the consensus parameters. Currently the last 1321 blocks are required). This means that non-Archival nodes require significantly less storage than Archival nodes. Relays (nodes with a valid NetAddress) are always Archival, regardless of this setting. This may change in the future. If setting this to true for the first time, the existing ledger may need to be deleted to get the historical values stored as the setting only effects current blocks forward. To do this, shutdown the node and delete all .sqlite files within the data/testnet-version directory, except the crash.sqlite file. Restart the node and wait for the node to sync. + // Archival nodes retain a full copy of the block history. Non-Archival nodes will delete old blocks and only retain what's need to properly validate blockchain messages (the precise number of recent blocks depends on the consensus parameters. Currently the last 1321 blocks are required). This means that non-Archival nodes require significantly less storage than Archival nodes. If setting this to true for the first time, the existing ledger may need to be deleted to get the historical values stored as the setting only affects current blocks forward. To do this, shutdown the node and delete all .sqlite files within the data/testnet-version directory, except the crash.sqlite file. Restart the node and wait for the node to sync. Archival bool `version[0]:"false"` // GossipFanout sets the maximum number of peers the node will connect to with outgoing connections. If the list of peers is less than this setting, fewer connections will be made. The node will not connect to the same peer multiple times (with outgoing connections). GossipFanout int `version[0]:"4"` - // NetAddress is the address and/or port on which the relay node listens for incoming connections, or blank to ignore incoming connections. Specify an IP and port or just a port. For example, 127.0.0.1:0 will listen on a random port on the localhost. + // NetAddress is the address and/or port on which a node listens for incoming connections, or blank to ignore incoming connections. Specify an IP and port or just a port. For example, 127.0.0.1:0 will listen on a random port on the localhost. NetAddress string `version[0]:""` // ReconnectTime is deprecated and unused. @@ -454,28 +454,16 @@ type Local struct { // VerifiedTranscationsCacheSize defines the number of transactions that the verified transactions cache would hold before cycling the cache storage in a round-robin fashion. VerifiedTranscationsCacheSize int `version[14]:"30000" version[23]:"150000"` - // EnableCatchupFromArchiveServers controls which peers the catchup service would use in order to catchup. - // When enabled, the catchup service would use the archive servers before falling back to the relays. - // On networks that don't have archive servers, this becomes a no-op, as the catchup service would have no - // archive server to pick from, and therefore automatically selects one of the relay nodes. - EnableCatchupFromArchiveServers bool `version[15]:"false"` - // DisableLocalhostConnectionRateLimit controls whether the incoming connection rate limit would apply for // connections that are originating from the local machine. Setting this to "true", allow to create large // local-machine networks that won't trip the incoming connection limit observed by relays. DisableLocalhostConnectionRateLimit bool `version[16]:"true"` // BlockServiceCustomFallbackEndpoints is a comma delimited list of endpoints which the block service uses to - // redirect the http requests to in case it does not have the round. If it is not specified, will check - // EnableBlockServiceFallbackToArchiver. + // redirect the http requests to in case it does not have the round. If empty, the block service will return + // StatusNotFound (404) BlockServiceCustomFallbackEndpoints string `version[16]:""` - // EnableBlockServiceFallbackToArchiver controls whether the block service redirects the http requests to - // an archiver or return StatusNotFound (404) when in does not have the requested round, and - // BlockServiceCustomFallbackEndpoints is empty. - // The archiver is randomly selected, if none is available, will return StatusNotFound (404). - EnableBlockServiceFallbackToArchiver bool `version[16]:"true" version[31]:"false"` - // CatchupBlockValidateMode is a development and testing configuration used by the catchup service. // It can be used to omit certain validations to speed up the catchup process, or to apply extra validations which are redundant in normal operation. // This field is a bit-field with: diff --git a/config/local_defaults.go b/config/local_defaults.go index 791e6e2225..d2a73d4c6f 100644 --- a/config/local_defaults.go +++ b/config/local_defaults.go @@ -64,8 +64,6 @@ var defaultLocal = Local{ EnableAgreementTimeMetrics: false, EnableAssembleStats: false, EnableBlockService: false, - EnableBlockServiceFallbackToArchiver: false, - EnableCatchupFromArchiveServers: false, EnableDeveloperAPI: false, EnableExperimentalAPI: false, EnableFollowMode: false, diff --git a/installer/config.json.example b/installer/config.json.example index aa1cb71712..d9188ef748 100644 --- a/installer/config.json.example +++ b/installer/config.json.example @@ -43,8 +43,6 @@ "EnableAgreementTimeMetrics": false, "EnableAssembleStats": false, "EnableBlockService": false, - "EnableBlockServiceFallbackToArchiver": false, - "EnableCatchupFromArchiveServers": false, "EnableDeveloperAPI": false, "EnableExperimentalAPI": false, "EnableFollowMode": false, diff --git a/network/wsNetwork.go b/network/wsNetwork.go index 9ce5f331ae..5bfabbaf2b 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -1895,15 +1895,14 @@ func (wn *WebsocketNetwork) getDNSAddrs(dnsBootstrap string) (relaysAddresses [] } relaysAddresses = nil } - if wn.config.EnableCatchupFromArchiveServers || wn.config.EnableBlockServiceFallbackToArchiver { - archiverAddresses, err = wn.resolveSRVRecords("archive", "tcp", dnsBootstrap, wn.config.FallbackDNSResolverAddress, wn.config.DNSSecuritySRVEnforced()) - if err != nil { - // only log this warning on testnet or devnet - if wn.NetworkID == config.Devnet || wn.NetworkID == config.Testnet { - wn.log.Warnf("Cannot lookup archive SRV record for %s: %v", dnsBootstrap, err) - } - archiverAddresses = nil + + archiverAddresses, err = wn.resolveSRVRecords("archive", "tcp", dnsBootstrap, wn.config.FallbackDNSResolverAddress, wn.config.DNSSecuritySRVEnforced()) + if err != nil { + // only log this warning on testnet or devnet + if wn.NetworkID == config.Devnet || wn.NetworkID == config.Testnet { + wn.log.Warnf("Cannot lookup archive SRV record for %s: %v", dnsBootstrap, err) } + archiverAddresses = nil } return } diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go index b7cd873bcf..e35cc7d177 100644 --- a/network/wsNetwork_test.go +++ b/network/wsNetwork_test.go @@ -4127,95 +4127,83 @@ func TestRefreshRelayArchivePhonebookAddresses(t *testing.T) { var netA *WebsocketNetwork var refreshRelayDNSBootstrapID = ".algorand.network?backup=.algorand.net&dedup=.algorand-.(network|net)" - testRefreshWithConfig := func(refreshTestConf config.Local) { - rapid.Check(t, func(t1 *rapid.T) { - refreshTestConf.DNSBootstrapID = refreshRelayDNSBootstrapID - netA = makeTestWebsocketNodeWithConfig(t, refreshTestConf) - netA.NetworkID = nonHardcodedNetworkIDGen().Draw(t1, "network") - - primarySRVBootstrap := strings.Replace(".algorand.network", "", string(netA.NetworkID), -1) - backupSRVBootstrap := strings.Replace(".algorand.net", "", string(netA.NetworkID), -1) - var primaryRelayResolvedRecords []string - var secondaryRelayResolvedRecords []string - var primaryArchiveResolvedRecords []string - var secondaryArchiveResolvedRecords []string - - for _, record := range []string{"r1.algorand-.network", - "r2.algorand-.network", "r3.algorand-.network"} { - var recordSub = strings.Replace(record, "", string(netA.NetworkID), -1) - primaryRelayResolvedRecords = append(primaryRelayResolvedRecords, recordSub) - secondaryRelayResolvedRecords = append(secondaryRelayResolvedRecords, strings.Replace(recordSub, "network", "net", -1)) - } - - for _, record := range []string{"r1archive.algorand-.network", - "r2archive.algorand-.network", "r3archive.algorand-.network"} { - var recordSub = strings.Replace(record, "", string(netA.NetworkID), -1) - primaryArchiveResolvedRecords = append(primaryArchiveResolvedRecords, recordSub) - secondaryArchiveResolvedRecords = append(secondaryArchiveResolvedRecords, strings.Replace(recordSub, "network", "net", -1)) - } + refreshTestConf := defaultConfig - // Mock the SRV record lookup - netA.resolveSRVRecords = func(service string, protocol string, name string, fallbackDNSResolverAddress string, - secure bool) (addrs []string, err error) { - if service == "algobootstrap" && protocol == "tcp" && name == primarySRVBootstrap { - return primaryRelayResolvedRecords, nil - } else if service == "algobootstrap" && protocol == "tcp" && name == backupSRVBootstrap { - return secondaryRelayResolvedRecords, nil - } + rapid.Check(t, func(t1 *rapid.T) { + refreshTestConf.DNSBootstrapID = refreshRelayDNSBootstrapID + netA = makeTestWebsocketNodeWithConfig(t, refreshTestConf) + netA.NetworkID = nonHardcodedNetworkIDGen().Draw(t1, "network") + + primarySRVBootstrap := strings.Replace(".algorand.network", "", string(netA.NetworkID), -1) + backupSRVBootstrap := strings.Replace(".algorand.net", "", string(netA.NetworkID), -1) + var primaryRelayResolvedRecords []string + var secondaryRelayResolvedRecords []string + var primaryArchiveResolvedRecords []string + var secondaryArchiveResolvedRecords []string + + for _, record := range []string{"r1.algorand-.network", + "r2.algorand-.network", "r3.algorand-.network"} { + var recordSub = strings.Replace(record, "", string(netA.NetworkID), -1) + primaryRelayResolvedRecords = append(primaryRelayResolvedRecords, recordSub) + secondaryRelayResolvedRecords = append(secondaryRelayResolvedRecords, strings.Replace(recordSub, "network", "net", -1)) + } - if service == "archive" && protocol == "tcp" && name == primarySRVBootstrap { - return primaryArchiveResolvedRecords, nil - } else if service == "archive" && protocol == "tcp" && name == backupSRVBootstrap { - return secondaryArchiveResolvedRecords, nil - } + for _, record := range []string{"r1archive.algorand-.network", + "r2archive.algorand-.network", "r3archive.algorand-.network"} { + var recordSub = strings.Replace(record, "", string(netA.NetworkID), -1) + primaryArchiveResolvedRecords = append(primaryArchiveResolvedRecords, recordSub) + secondaryArchiveResolvedRecords = append(secondaryArchiveResolvedRecords, strings.Replace(recordSub, "network", "net", -1)) + } - return + // Mock the SRV record lookup + netA.resolveSRVRecords = func(service string, protocol string, name string, fallbackDNSResolverAddress string, + secure bool) (addrs []string, err error) { + if service == "algobootstrap" && protocol == "tcp" && name == primarySRVBootstrap { + return primaryRelayResolvedRecords, nil + } else if service == "algobootstrap" && protocol == "tcp" && name == backupSRVBootstrap { + return secondaryRelayResolvedRecords, nil } - relayPeers := netA.GetPeers(PeersPhonebookRelays) - assert.Equal(t, 0, len(relayPeers)) - - archivePeers := netA.GetPeers(PeersPhonebookArchivers) - assert.Equal(t, 0, len(archivePeers)) - - netA.refreshRelayArchivePhonebookAddresses() + if service == "archive" && protocol == "tcp" && name == primarySRVBootstrap { + return primaryArchiveResolvedRecords, nil + } else if service == "archive" && protocol == "tcp" && name == backupSRVBootstrap { + return secondaryArchiveResolvedRecords, nil + } - relayPeers = netA.GetPeers(PeersPhonebookRelays) + return + } - assert.Equal(t, 3, len(relayPeers)) - relayAddrs := make([]string, 0, len(relayPeers)) - for _, peer := range relayPeers { - relayAddrs = append(relayAddrs, peer.(HTTPPeer).GetAddress()) - } + relayPeers := netA.GetPeers(PeersPhonebookRelays) + assert.Equal(t, 0, len(relayPeers)) - assert.ElementsMatch(t, primaryRelayResolvedRecords, relayAddrs) + archivePeers := netA.GetPeers(PeersPhonebookArchivers) + assert.Equal(t, 0, len(archivePeers)) - archivePeers = netA.GetPeers(PeersPhonebookArchivers) + netA.refreshRelayArchivePhonebookAddresses() - if refreshTestConf.EnableBlockServiceFallbackToArchiver { - // For the time being, we do not dedup resolved archive nodes - assert.Equal(t, len(primaryArchiveResolvedRecords)+len(secondaryArchiveResolvedRecords), len(archivePeers)) + relayPeers = netA.GetPeers(PeersPhonebookRelays) - archiveAddrs := make([]string, 0, len(archivePeers)) - for _, peer := range archivePeers { - archiveAddrs = append(archiveAddrs, peer.(HTTPPeer).GetAddress()) - } + assert.Equal(t, 3, len(relayPeers)) + relayAddrs := make([]string, 0, len(relayPeers)) + for _, peer := range relayPeers { + relayAddrs = append(relayAddrs, peer.(HTTPPeer).GetAddress()) + } - assert.ElementsMatch(t, append(primaryArchiveResolvedRecords, secondaryArchiveResolvedRecords...), archiveAddrs) + assert.ElementsMatch(t, primaryRelayResolvedRecords, relayAddrs) - } else { - assert.Equal(t, 0, len(archivePeers)) - } + archivePeers = netA.GetPeers(PeersPhonebookArchivers) - }) - } + // TODO: For the time being, we do not dedup resolved archive nodes + assert.Equal(t, len(primaryArchiveResolvedRecords)+len(secondaryArchiveResolvedRecords), len(archivePeers)) - testRefreshWithConfig(defaultConfig) + archiveAddrs := make([]string, 0, len(archivePeers)) + for _, peer := range archivePeers { + archiveAddrs = append(archiveAddrs, peer.(HTTPPeer).GetAddress()) + } - configWithBlockServiceFallbackToArchiverEnabled := config.GetDefaultLocal() - configWithBlockServiceFallbackToArchiverEnabled.EnableBlockServiceFallbackToArchiver = true + assert.ElementsMatch(t, append(primaryArchiveResolvedRecords, secondaryArchiveResolvedRecords...), archiveAddrs) - testRefreshWithConfig(configWithBlockServiceFallbackToArchiverEnabled) + }) } /* diff --git a/rpcs/blockService.go b/rpcs/blockService.go index 7245eb1881..8231b5a98b 100644 --- a/rpcs/blockService.go +++ b/rpcs/blockService.go @@ -36,7 +36,6 @@ import ( "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" - "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/ledger/ledgercore" @@ -103,7 +102,6 @@ type BlockService struct { enableService bool enableServiceOverGossip bool fallbackEndpoints fallbackEndpoints - enableArchiverFallback bool log logging.Logger closeWaitGroup sync.WaitGroup mu deadlock.Mutex @@ -144,7 +142,6 @@ func MakeBlockService(log logging.Logger, config config.Local, ledger LedgerForB enableService: config.EnableBlockService, enableServiceOverGossip: config.EnableGossipBlockService, fallbackEndpoints: makeFallbackEndpoints(log, config.BlockServiceCustomFallbackEndpoints), - enableArchiverFallback: config.EnableBlockServiceFallbackToArchiver, log: log, memoryCap: config.BlockServiceMemCap, } @@ -384,13 +381,10 @@ func (bs *BlockService) handleCatchupReq(ctx context.Context, reqMsg network.Inc return } -// redirectRequest redirects the request to the next round robin fallback endpoing if available, otherwise, -// if EnableBlockServiceFallbackToArchiver is enabled, redirects to a random archiver. +// redirectRequest redirects the request to the next round robin fallback endpoint if available func (bs *BlockService) redirectRequest(round uint64, response http.ResponseWriter, request *http.Request) (ok bool) { peerAddress := bs.getNextCustomFallbackEndpoint() - if peerAddress == "" && bs.enableArchiverFallback { - peerAddress = bs.getRandomArchiver() - } + if peerAddress == "" { return false } @@ -411,30 +405,14 @@ func (bs *BlockService) getNextCustomFallbackEndpoint() (endpointAddress string) if len(bs.fallbackEndpoints.endpoints) == 0 { return } + + bs.mu.Lock() + defer bs.mu.Unlock() endpointAddress = bs.fallbackEndpoints.endpoints[bs.fallbackEndpoints.lastUsed] bs.fallbackEndpoints.lastUsed = (bs.fallbackEndpoints.lastUsed + 1) % len(bs.fallbackEndpoints.endpoints) return } -// getRandomArchiver returns a random archiver address -func (bs *BlockService) getRandomArchiver() (endpointAddress string) { - peers := bs.net.GetPeers(network.PeersPhonebookArchivers) - httpPeers := make([]network.HTTPPeer, 0, len(peers)) - - for _, peer := range peers { - httpPeer, validHTTPPeer := peer.(network.HTTPPeer) - if validHTTPPeer { - httpPeers = append(httpPeers, httpPeer) - } - } - if len(httpPeers) == 0 { - return - } - randIndex := crypto.RandUint64() % uint64(len(httpPeers)) - endpointAddress = httpPeers[randIndex].GetAddress() - return -} - // rawBlockBytes returns the block/cert for a given round, while taking the lock // to ensure the block service is currently active. func (bs *BlockService) rawBlockBytes(round basics.Round) ([]byte, error) { diff --git a/rpcs/blockService_test.go b/rpcs/blockService_test.go index e77fc7aa0e..3aab7c4abb 100644 --- a/rpcs/blockService_test.go +++ b/rpcs/blockService_test.go @@ -125,8 +125,9 @@ func TestHandleCatchupReqNegative(t *testing.T) { require.Equal(t, roundNumberParseErrMsg, string(val)) } -// TestRedirectFallbackArchiver tests the case when the block service fallback to another in the absence of a given block. -func TestRedirectFallbackArchiver(t *testing.T) { +// TestRedirectFallbackEndpoints tests the case when the block service falls back to another from +// BlockServiceCustomFallbackEndpoints in the absence of a given block. +func TestRedirectFallbackEndpoints(t *testing.T) { partitiontest.PartitionTest(t) log := logging.TestingLog(t) @@ -142,25 +143,23 @@ func TestRedirectFallbackArchiver(t *testing.T) { net1 := &httpTestPeerSource{} net2 := &httpTestPeerSource{} - config := config.GetDefaultLocal() - // Need to enable block service fallbacks - config.EnableBlockServiceFallbackToArchiver = true - - bs1 := MakeBlockService(log, config, ledger1, net1, "test-genesis-ID") - bs2 := MakeBlockService(log, config, ledger2, net2, "test-genesis-ID") - nodeA := &basicRPCNode{} nodeB := &basicRPCNode{} - - nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) nodeA.start() defer nodeA.stop() - - nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) nodeB.start() defer nodeB.stop() - net1.addPeer(nodeB.rootURL()) + config := config.GetDefaultLocal() + // Set the first to a bad address, the second to self, and the third to the one that has the block. + // If RR is right, should succeed. + config.BlockServiceCustomFallbackEndpoints = fmt.Sprintf("://badaddress,%s,%s", nodeA.rootURL(), nodeB.rootURL()) + + bs1 := MakeBlockService(log, config, ledger1, net1, "test-genesis-ID") + bs2 := MakeBlockService(log, config, ledger2, net2, "test-genesis-ID") + + nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) + nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) parsedURL, err := network.ParseHostOrURL(nodeA.rootURL()) require.NoError(t, err) @@ -235,60 +234,8 @@ func TestBlockServiceShutdown(t *testing.T) { <-requestDone } -// TestRedirectBasic tests the case when the block service redirects the request to elsewhere -func TestRedirectFallbackEndpoints(t *testing.T) { - partitiontest.PartitionTest(t) - - log := logging.TestingLog(t) - - ledger1 := makeLedger(t, "l1") - defer ledger1.Close() - ledger2 := makeLedger(t, "l2") - defer ledger2.Close() - addBlock(t, ledger2) - - net1 := &httpTestPeerSource{} - net2 := &httpTestPeerSource{} - - nodeA := &basicRPCNode{} - nodeB := &basicRPCNode{} - nodeA.start() - defer nodeA.stop() - nodeB.start() - defer nodeB.stop() - - config := config.GetDefaultLocal() - // Set the first to a bad address, the second to self, and the third to the one that has the block. - // If RR is right, should succeed. - config.BlockServiceCustomFallbackEndpoints = fmt.Sprintf("://badaddress,%s,%s", nodeA.rootURL(), nodeB.rootURL()) - bs1 := MakeBlockService(log, config, ledger1, net1, "{genesisID}") - bs2 := MakeBlockService(log, config, ledger2, net2, "{genesisID}") - - nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) - nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) - - parsedURL, err := network.ParseHostOrURL(nodeA.rootURL()) - require.NoError(t, err) - - client := http.Client{} - - ctx := context.Background() - parsedURL.Path = FormatBlockQuery(uint64(1), parsedURL.Path, net1) - blockURL := parsedURL.String() - request, err := http.NewRequest("GET", blockURL, nil) - require.NoError(t, err) - requestCtx, requestCancel := context.WithTimeout(ctx, time.Duration(config.CatchupHTTPBlockFetchTimeoutSec)*time.Second) - defer requestCancel() - request = request.WithContext(requestCtx) - network.SetUserAgentHeader(request.Header) - response, err := client.Do(request) - require.NoError(t, err) - - require.Equal(t, http.StatusOK, response.StatusCode) -} - -// TestRedirectFallbackArchiver tests the case when the block service -// fallback to another because its memory use it at capacity +// TestRedirectOnFullCapacity tests the case when the block service +// fallback to another because its memory use is at capacity func TestRedirectOnFullCapacity(t *testing.T) { partitiontest.PartitionTest(t) @@ -313,27 +260,31 @@ func TestRedirectOnFullCapacity(t *testing.T) { net1 := &httpTestPeerSource{} net2 := &httpTestPeerSource{} - config := config.GetDefaultLocal() - // Need to enable block service fallbacks - config.EnableBlockServiceFallbackToArchiver = true - bs1 := MakeBlockService(log1, config, ledger1, net1, "test-genesis-ID") - bs2 := MakeBlockService(log2, config, ledger2, net2, "test-genesis-ID") - // set the memory cap so that it can serve only 1 block at a time - bs1.memoryCap = 250 - bs2.memoryCap = 250 - nodeA := &basicRPCNode{} nodeB := &basicRPCNode{} - - nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) nodeA.start() defer nodeA.stop() - - nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) nodeB.start() defer nodeB.stop() - net1.addPeer(nodeB.rootURL()) + configWithRedirects := config.GetDefaultLocal() + + configWithRedirects.BlockServiceCustomFallbackEndpoints = nodeB.rootURL() + + bs1 := MakeBlockService(log1, configWithRedirects, ledger1, net1, "test-genesis-ID") + + // config with no redirects + configNoRedirects := config.GetDefaultLocal() + configNoRedirects.BlockServiceCustomFallbackEndpoints = "" + + bs2 := MakeBlockService(log2, configNoRedirects, ledger2, net2, "test-genesis-ID") + // set the memory cap so that it can serve only 1 block at a time + bs1.memoryCap = 250 + bs2.memoryCap = 250 + + nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) + + nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) parsedURL, err := network.ParseHostOrURL(nodeA.rootURL()) require.NoError(t, err) @@ -483,27 +434,36 @@ func TestWsBlockLimiting(t *testing.T) { func TestRedirectExceptions(t *testing.T) { partitiontest.PartitionTest(t) - log := logging.TestingLog(t) + log1 := logging.TestingLog(t) + log2 := logging.TestingLog(t) ledger1 := makeLedger(t, "l1") + ledger2 := makeLedger(t, "l2") defer ledger1.Close() + defer ledger2.Close() addBlock(t, ledger1) net1 := &httpTestPeerSource{} - - config := config.GetDefaultLocal() - // Need to enable block service fallbacks - config.EnableBlockServiceFallbackToArchiver = true - - bs1 := MakeBlockService(log, config, ledger1, net1, "{genesisID}") + net2 := &httpTestPeerSource{} nodeA := &basicRPCNode{} - - nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) + nodeB := &basicRPCNode{} nodeA.start() defer nodeA.stop() + nodeB.start() + defer nodeB.stop() + + configInvalidRedirects := config.GetDefaultLocal() + configInvalidRedirects.BlockServiceCustomFallbackEndpoints = "badAddress" + + configWithRedirectToSelf := config.GetDefaultLocal() + configWithRedirectToSelf.BlockServiceCustomFallbackEndpoints = nodeB.rootURL() - net1.peers = append(net1.peers, "invalidPeer") + bs1 := MakeBlockService(log1, configInvalidRedirects, ledger1, net1, "{genesisID}") + bs2 := MakeBlockService(log2, configWithRedirectToSelf, ledger2, net2, "{genesisID}") + + nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1) + nodeB.RegisterHTTPHandler(BlockServiceBlockPath, bs2) parsedURL, err := network.ParseHostOrURL(nodeA.rootURL()) require.NoError(t, err) @@ -515,7 +475,7 @@ func TestRedirectExceptions(t *testing.T) { blockURL := parsedURL.String() request, err := http.NewRequest("GET", blockURL, nil) require.NoError(t, err) - requestCtx, requestCancel := context.WithTimeout(ctx, time.Duration(config.CatchupHTTPBlockFetchTimeoutSec)*time.Second) + requestCtx, requestCancel := context.WithTimeout(ctx, time.Duration(configInvalidRedirects.CatchupHTTPBlockFetchTimeoutSec)*time.Second) defer requestCancel() request = request.WithContext(requestCtx) network.SetUserAgentHeader(request.Header) @@ -524,8 +484,14 @@ func TestRedirectExceptions(t *testing.T) { require.NoError(t, err) require.Equal(t, response.StatusCode, http.StatusNotFound) - net1.addPeer(nodeA.rootURL()) - _, err = client.Do(request) + parsedURLNodeB, err := network.ParseHostOrURL(nodeB.rootURL()) + require.NoError(t, err) + + parsedURLNodeB.Path = FormatBlockQuery(uint64(4), parsedURLNodeB.Path, net2) + blockURLNodeB := parsedURLNodeB.String() + requestNodeB, err := http.NewRequest("GET", blockURLNodeB, nil) + _, err = client.Do(requestNodeB) + require.Error(t, err) require.Contains(t, err.Error(), "stopped after 10 redirects") } diff --git a/test/testdata/configs/config-v33.json b/test/testdata/configs/config-v33.json index aa1cb71712..d9188ef748 100644 --- a/test/testdata/configs/config-v33.json +++ b/test/testdata/configs/config-v33.json @@ -43,8 +43,6 @@ "EnableAgreementTimeMetrics": false, "EnableAssembleStats": false, "EnableBlockService": false, - "EnableBlockServiceFallbackToArchiver": false, - "EnableCatchupFromArchiveServers": false, "EnableDeveloperAPI": false, "EnableExperimentalAPI": false, "EnableFollowMode": false, From b5927a6aa668f40e4c2ae8c683a694add48ab7f9 Mon Sep 17 00:00:00 2001 From: nullun Date: Fri, 26 Jan 2024 20:12:41 +0000 Subject: [PATCH 03/16] netgoal: Use --last-part-key-round when generating a goalnet template (#5912) --- cmd/netgoal/generate.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/netgoal/generate.go b/cmd/netgoal/generate.go index 882cd0f9d0..9e87004a05 100644 --- a/cmd/netgoal/generate.go +++ b/cmd/netgoal/generate.go @@ -475,6 +475,9 @@ func saveTemplateToDisk(template remote.DeployedNetworkConfig, filename string) } func saveGoalTemplateToDisk(template netdeploy.NetworkTemplate, filename string) error { + if lastPartKeyRound != 0 { + template.Genesis.LastPartKeyRound = lastPartKeyRound + } f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err == nil { defer f.Close() From edf122de16f1602b562002434678238ad6d42ba1 Mon Sep 17 00:00:00 2001 From: DevOps Service Date: Mon, 5 Feb 2024 20:40:29 +0000 Subject: [PATCH 04/16] Bump Version, Remove buildnumber.dat and genesistimestamp.dat files. --- buildnumber.dat | 1 - config/version.go | 2 +- genesistimestamp.dat | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 buildnumber.dat delete mode 100644 genesistimestamp.dat diff --git a/buildnumber.dat b/buildnumber.dat deleted file mode 100644 index 573541ac97..0000000000 --- a/buildnumber.dat +++ /dev/null @@ -1 +0,0 @@ -0 diff --git a/config/version.go b/config/version.go index 272cff9f93..dce2bd533e 100644 --- a/config/version.go +++ b/config/version.go @@ -33,7 +33,7 @@ const VersionMajor = 3 // VersionMinor is the Minor semantic version number (x.#.z) - changed when backwards-compatible features are introduced. // Not enforced until after initial public release (x > 0). -const VersionMinor = 22 +const VersionMinor = 23 // Version is the type holding our full version information. type Version struct { diff --git a/genesistimestamp.dat b/genesistimestamp.dat deleted file mode 100644 index c72c6a7795..0000000000 --- a/genesistimestamp.dat +++ /dev/null @@ -1 +0,0 @@ -1558657885 From 1747abac65490cc1fb338daef8def45988dbc262 Mon Sep 17 00:00:00 2001 From: Gary <982483+gmalouf@users.noreply.github.com> Date: Mon, 5 Feb 2024 17:34:49 -0500 Subject: [PATCH 05/16] Blocks: Bump min to save based on catchpoint support (#5927) --- ledger/ledger.go | 16 ++++++++++- ledger/ledger_test.go | 64 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/ledger/ledger.go b/ledger/ledger.go index fa1be1a766..110a72fc18 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -462,11 +462,25 @@ func (l *Ledger) notifyCommit(r basics.Round) basics.Round { if l.archival { // Do not forget any blocks. minToSave = 0 + } else { + catchpointsMinToSave := r.SubSaturate(l.calcMinCatchpointRoundsLookback()) + if catchpointsMinToSave < minToSave { + minToSave = catchpointsMinToSave + } } return minToSave } +func (l *Ledger) calcMinCatchpointRoundsLookback() basics.Round { + // cfg.StoresCatchpoints checks that CatchpointInterval is positive + if !l.cfg.StoresCatchpoints() || l.cfg.CatchpointFileHistoryLength == 0 { + return 0 + } + + return basics.Round(2 * l.cfg.CatchpointInterval) +} + // GetLastCatchpointLabel returns the latest catchpoint label that was written to the // database. func (l *Ledger) GetLastCatchpointLabel() string { @@ -901,7 +915,7 @@ func (l *Ledger) FlushCaches() { // Validate uses the ledger to validate block blk as a candidate next block. // It returns an error if blk is not the expected next block, or if blk is // not a valid block (e.g., it has duplicate transactions, overspends some -// account, etc). +// account, etc.). func (l *Ledger) Validate(ctx context.Context, blk bookkeeping.Block, executionPool execpool.BacklogPool) (*ledgercore.ValidatedBlock, error) { delta, err := eval.Eval(ctx, l, blk, true, l.verifiedTxnCache, executionPool, l.tracer) if err != nil { diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index ab5bc293a3..b18428741b 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -3287,3 +3287,67 @@ func TestLedgerMaxBlockHistoryLookback(t *testing.T) { require.Error(t, err) require.Empty(t, blk) } + +func TestLedgerRetainMinOffCatchpointInterval(t *testing.T) { + partitiontest.PartitionTest(t) + // This test is to ensure that the ledger retains the minimum number of blocks off the catchpoint interval. + blocksToMake := 2000 + + // Cases: + // 1. Base Case: Archival = false, Stores catchpoints returns true, CatchpointFileHistoryLength = >= 1 - implies catchpoint interval > 0 - min formula + // 2. Archival = true, stores catchpoints returns false - we keep all blocks anyway + // 3. Archival = false, stores catchpoints returns false - we don't modify minToSave + // 4. Condition: Archival = false, storesCatchpoints returns true, CatchpointFileHistoryLength is -1 - keep all catchpoint files + // 5. Condition: Archival = false, storesCatchpoints returns true, CatchpointFileHistoryLength is 365 - the config default setting + + catchpointIntervalBlockRetentionTestCases := []struct { + storeCatchpoints bool + archival bool + catchpointFileHistoryLength int + }{ + {true, false, 1}, // should use min catchpoint formula + {false, true, 1}, // all blocks get retained, archival mode dictates + {false, false, 1}, // should not modify min blocks retained based on catchpoint interval + {true, false, -1}, // should use min formula, this is the keep all catchpoints setting + {true, false, 365}, // should use min formula, this is the default setting for catchpoint file history length + } + for _, tc := range catchpointIntervalBlockRetentionTestCases { + func() { + var genHash crypto.Digest + crypto.RandBytes(genHash[:]) + cfg := config.GetDefaultLocal() + // set config properties based on test case + cfg.MaxBlockHistoryLookback = 0 // max block history lookback is not used in this test + if tc.storeCatchpoints { + cfg.CatchpointTracking = config.CatchpointTrackingModeStored + cfg.CatchpointInterval = 100 + } else { + cfg.CatchpointInterval = 0 // sufficient for cfg.StoresCatchpoints() to return false + } + cfg.CatchpointFileHistoryLength = tc.catchpointFileHistoryLength + cfg.Archival = tc.archival + + l := &Ledger{} + l.cfg = cfg + l.archival = cfg.Archival + + for i := 1; i <= blocksToMake; i++ { + minBlockToKeep := l.notifyCommit(basics.Round(i)) + + // In archival mode, all blocks should always be kept + if cfg.Archival { + require.Equal(t, basics.Round(0), minBlockToKeep) + } else { + // This happens to work for the test case where we don't store catchpoints since mintosave is always + // 0 in that case. + expectedCatchpointLookback := 2 * cfg.CatchpointInterval + + expectedMinBlockToKeep := basics.Round(uint64(i)).SubSaturate( + basics.Round(expectedCatchpointLookback)) + require.Equal(t, expectedMinBlockToKeep, minBlockToKeep) + } + } + }() + } + +} From e1db9e12612cd0640b9e069e83ac32363c549085 Mon Sep 17 00:00:00 2001 From: Halimao <1065621723@qq.com> Date: Wed, 7 Feb 2024 01:57:56 +0800 Subject: [PATCH 06/16] feat: add support to display mnemonic discreetly for `algokey generate` (#5886) --- cmd/algokey/common.go | 16 ++++++++++++++++ cmd/algokey/common_test.go | 39 ++++++++++++++++++++++++++++++++++++++ cmd/algokey/generate.go | 12 +++++++++++- go.mod | 5 +++++ go.sum | 10 ++++++++++ 5 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 cmd/algokey/common_test.go diff --git a/cmd/algokey/common.go b/cmd/algokey/common.go index 8fc5867db9..0259dc5e05 100644 --- a/cmd/algokey/common.go +++ b/cmd/algokey/common.go @@ -23,6 +23,7 @@ import ( "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/passphrase" + "github.com/muesli/termenv" ) const ( @@ -122,3 +123,18 @@ func readFile(filename string) ([]byte, error) { } return os.ReadFile(filename) } + +// printDiscreetly Print a secret string to an alternate screen, +// so the string isn't printed to the terminal. +func printDiscreetly(w io.Writer, promptMsg, secretMsg string) error { + output := termenv.NewOutput(w) + output.AltScreen() + defer output.ExitAltScreen() + if _, err := fmt.Fprintf(output, "%s\n\n%s\n\nPress 'Enter' key to continue.", promptMsg, secretMsg); err != nil { + return err + } + if _, err := fmt.Scanln(); err != nil { + return err + } + return nil +} diff --git a/cmd/algokey/common_test.go b/cmd/algokey/common_test.go new file mode 100644 index 0000000000..2256342616 --- /dev/null +++ b/cmd/algokey/common_test.go @@ -0,0 +1,39 @@ +// Copyright (C) 2019-2024 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 main + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" +) + +func Test_printDiscreetly(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + buf := new(bytes.Buffer) + var ( + promptMsg = "Prompt Message" + secretMsg = "Secret Message" + ) + require.NoError(t, printDiscreetly(buf, promptMsg, secretMsg)) + require.Contains(t, buf.String(), promptMsg) + require.Contains(t, buf.String(), secretMsg) +} diff --git a/cmd/algokey/generate.go b/cmd/algokey/generate.go index 6382fa3a0c..6b1c409e68 100644 --- a/cmd/algokey/generate.go +++ b/cmd/algokey/generate.go @@ -18,6 +18,7 @@ package main import ( "fmt" + "os" "github.com/spf13/cobra" @@ -27,10 +28,12 @@ import ( var generateKeyfile string var generatePubkeyfile string +var generateDiscreet bool func init() { generateCmd.Flags().StringVarP(&generateKeyfile, "keyfile", "f", "", "Private key filename") generateCmd.Flags().StringVarP(&generatePubkeyfile, "pubkeyfile", "p", "", "Public key filename") + generateCmd.Flags().BoolVar(&generateDiscreet, "discreet", false, "Print mnemonic discreetly to an alternate screen") } var generateCmd = &cobra.Command{ @@ -46,7 +49,14 @@ var generateCmd = &cobra.Command{ key := crypto.GenerateSignatureSecrets(seed) publicKeyChecksummed := basics.Address(key.SignatureVerifier).String() - fmt.Printf("Private key mnemonic: %s\n", mnemonic) + if generateDiscreet { + if err := printDiscreetly(os.Stderr, "**Important** write this private key mnemonic phrase in a safe place. Do not share it to anyone", fmt.Sprintf("Private key mnemonic: %s", mnemonic)); err != nil { + fmt.Fprintf(os.Stderr, "Fail to print mnemonic: %v", err) + os.Exit(1) + } + } else { + fmt.Printf("Private key mnemonic: %s\n", mnemonic) + } fmt.Printf("Public key: %s\n", publicKeyChecksummed) if generateKeyfile != "" { diff --git a/go.mod b/go.mod index 8844a80585..0d85043883 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,7 @@ require ( github.com/libp2p/go-yamux/v4 v4.0.1 github.com/mattn/go-sqlite3 v1.14.16 github.com/miekg/dns v1.1.55 + github.com/muesli/termenv v0.15.2 github.com/multiformats/go-multiaddr v0.10.1 github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/olivere/elastic v6.2.14+incompatible @@ -51,6 +52,7 @@ require ( require ( github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.7.0 // indirect @@ -107,10 +109,12 @@ require ( github.com/libp2p/go-nat v0.2.0 // indirect github.com/libp2p/go-netroute v0.2.1 // indirect github.com/libp2p/go-reuseport v0.3.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect @@ -142,6 +146,7 @@ require ( github.com/quic-go/quic-go v0.36.3 // indirect github.com/quic-go/webtransport-go v0.5.3 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect + github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect diff --git a/go.sum b/go.sum index 1d4e5abf63..f8d0ac3a04 100644 --- a/go.sum +++ b/go.sum @@ -81,6 +81,8 @@ github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/aws/aws-sdk-go v1.34.0 h1:brux2dRrlwCF5JhTL7MUT3WUwo9zfDHZZp3+g3Mvlmo= github.com/aws/aws-sdk-go v1.34.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -430,6 +432,8 @@ github.com/libp2p/go-reuseport v0.3.0 h1:iiZslO5byUYZEg9iCwJGf5h+sf1Agmqx2V2FDjP github.com/libp2p/go-reuseport v0.3.0/go.mod h1:laea40AimhtfEqysZ71UpYj4S+R9VpH8PgqLo7L+SwI= github.com/libp2p/go-yamux/v4 v4.0.1 h1:FfDR4S1wj6Bw2Pqbc8Uz7pCxeRBPbwsBbEdfwiCypkQ= github.com/libp2p/go-yamux/v4 v4.0.1/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -451,6 +455,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= @@ -491,6 +497,8 @@ github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOA github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= @@ -591,6 +599,8 @@ github.com/quic-go/webtransport-go v0.5.3 h1:5XMlzemqB4qmOlgIus5zB45AcZ2kCgCy2Ep github.com/quic-go/webtransport-go v0.5.3/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU= github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk= github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= From caec33dfe083327994572d77b66a8a0c6d40bc2c Mon Sep 17 00:00:00 2001 From: John Lee Date: Tue, 13 Feb 2024 12:35:28 -0500 Subject: [PATCH 07/16] CICD: fix package signing issues (#5934) --- package-deploy.yaml | 1 - .../mule/common/ensure_centos8_image.sh | 17 ++++++++ scripts/release/mule/deploy/deb/deploy.sh | 25 +++-------- scripts/release/mule/deploy/docker/docker.sh | 6 +-- .../releases_page/generate_releases_page.sh | 9 +--- scripts/release/mule/deploy/rpm/deploy.sh | 42 ++++++++++--------- scripts/release/mule/sign/sign.sh | 24 ++++++----- test/muleCI/mule.yaml | 35 ---------------- 8 files changed, 64 insertions(+), 95 deletions(-) create mode 100755 scripts/release/mule/common/ensure_centos8_image.sh diff --git a/package-deploy.yaml b/package-deploy.yaml index 8daf262acc..9b67a2fe6c 100644 --- a/package-deploy.yaml +++ b/package-deploy.yaml @@ -56,7 +56,6 @@ agents: - NETWORK=$NETWORK - NO_DEPLOY=$NO_DEPLOY - PACKAGES_DIR=$PACKAGES_DIR - - S3_SOURCE=$S3_SOURCE - STAGING=$STAGING - VERSION=$VERSION volumes: diff --git a/scripts/release/mule/common/ensure_centos8_image.sh b/scripts/release/mule/common/ensure_centos8_image.sh new file mode 100755 index 0000000000..1ebd3475fe --- /dev/null +++ b/scripts/release/mule/common/ensure_centos8_image.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -exo pipefail + +# Ensure the centos8 docker image is built and available + +DOCKER_IMAGE="algorand/go-algorand-ci-linux-centos8:amd64-$(sha1sum scripts/configure_dev-deps.sh | cut -f1 -d' ')" +MATCH=${DOCKER_IMAGE/:*/} + +echo "Checking for RPM image" +if docker images $DOCKER_IMAGE | grep -qs $MATCH > /dev/null 2>&1; then + echo "Image exists" +else + echo "RPM image doesn't exist, building" + docker build --platform=linux/amd64 --build-arg ARCH=amd64 \ + --build-arg GOLANG_VERSION=$(./scripts/get_golang_version.sh) -t $DOCKER_IMAGE -f docker/build/cicd.centos8.Dockerfile . +fi diff --git a/scripts/release/mule/deploy/deb/deploy.sh b/scripts/release/mule/deploy/deb/deploy.sh index c9c4b4b6c4..2584b84123 100755 --- a/scripts/release/mule/deploy/deb/deploy.sh +++ b/scripts/release/mule/deploy/deb/deploy.sh @@ -2,28 +2,13 @@ set -ex -if [ -z "$NETWORK" ] -then - echo "[$0] Network is a required parameter." - exit 1 -fi - -if [ -z "$STAGING" ] -then - echo "[$0] Staging is a required parameter." - exit 1 -fi - -CHANNEL=$("./scripts/release/mule/common/get_channel.sh" "$NETWORK") +CHANNEL=${CHANNEL:-$("./scripts/release/mule/common/get_channel.sh" "$NETWORK")} VERSION=${VERSION:-$(./scripts/compute_build_number.sh -f)} +PACKAGES_DIR=${PACKAGES_DIR:-~/packages} +SNAPSHOT=${SNAPSHOT:-"${CHANNEL}-${VERSION}"} -if [ -z "$SNAPSHOT" ] -then - SNAPSHOT="$CHANNEL-$VERSION" -fi - -PACKAGES_DIR=/root/packages -mkdir -p /root/packages +mkdir -p $PACKAGES_DIR +rm -f $PACKAGES_DIR/*.deb aptly mirror update stable aptly mirror update beta diff --git a/scripts/release/mule/deploy/docker/docker.sh b/scripts/release/mule/deploy/docker/docker.sh index ee0c55fe0c..093922fd3d 100755 --- a/scripts/release/mule/deploy/docker/docker.sh +++ b/scripts/release/mule/deploy/docker/docker.sh @@ -13,9 +13,9 @@ if [ -z "$NETWORK" ] || [ -z "$VERSION" ]; then exit 1 fi -if [[ ! "$NETWORK" =~ ^mainnet$|^testnet$|^betanet$|^alphanet$ ]] +if [[ ! "$NETWORK" =~ ^mainnet$|^testnet$|^betanet$ ]] then - echo "[$0] Network values must be either \`mainnet\`, \`testnet\`, \`betanet\`, or \`alphanet\`." + echo "[$0] Network values must be either \`mainnet\`, \`testnet\`, or \`betanet\`." exit 1 fi @@ -28,7 +28,7 @@ then # Build and push testnet. ./build_releases.sh --tagname "$VERSION" --network testnet --cached -elif [ "$NETWORK" = betanet ] || [ "$NETWORK" = alphanet ] +elif [ "$NETWORK" = betanet ] then ./build_releases.sh --tagname "$VERSION" --network "$NETWORK" fi diff --git a/scripts/release/mule/deploy/releases_page/generate_releases_page.sh b/scripts/release/mule/deploy/releases_page/generate_releases_page.sh index 5b6a488ce2..75df10d524 100755 --- a/scripts/release/mule/deploy/releases_page/generate_releases_page.sh +++ b/scripts/release/mule/deploy/releases_page/generate_releases_page.sh @@ -8,13 +8,8 @@ set -ex -if [ -z "$NETWORK" ] || [ -z "$VERSION" ] -then - echo "[$0] Network and version are required parameters." - exit 1 -fi - -CHANNEL=$(./scripts/release/mule/common/get_channel.sh "$NETWORK") +CHANNEL=${CHANNEL:-$(./scripts/release/mule/common/get_channel.sh "$NETWORK")} +VERSION=${VERSION:-$(./scripts/compute_build_number.sh -f)} cd scripts/release/mule/deploy/releases_page diff --git a/scripts/release/mule/deploy/rpm/deploy.sh b/scripts/release/mule/deploy/rpm/deploy.sh index f660f1d01b..b96cccd6cb 100755 --- a/scripts/release/mule/deploy/rpm/deploy.sh +++ b/scripts/release/mule/deploy/rpm/deploy.sh @@ -7,28 +7,25 @@ echo date "+build_release begin DEPLOY rpm stage %Y%m%d_%H%M%S" echo -if [ -z "$NETWORK" ]; then - echo "[$0] NETWORK is missing." - exit 1 -fi - -CHANNEL=$(./scripts/release/mule/common/get_channel.sh "$NETWORK") +CHANNEL=${CHANNEL:-$(./scripts/release/mule/common/get_channel.sh "$NETWORK")} VERSION=${VERSION:-$(./scripts/compute_build_number.sh -f)} NO_DEPLOY=${NO_DEPLOY:-false} -OS_TYPE=$(./scripts/release/mule/common/ostype.sh) -PACKAGES_DIR=${PACKAGES_DIR:-"./tmp/node_pkgs/$OS_TYPE/$ARCH_TYPE"} -STAGING=${STAGING:-"algorand-staging/releases"} +PACKAGES_DIR=${PACKAGES_DIR:-"tmp"} if [ -n "$S3_SOURCE" ] then PREFIX="$S3_SOURCE/$CHANNEL/$VERSION" if [ "$CHANNEL" == "beta" ] then - aws s3 cp "s3://$PREFIX/algorand-beta-$VERSION-1.x86_64.rpm" /root - aws s3 cp "s3://$PREFIX/algorand-devtools-beta-$VERSION-1.x86_64.rpm" /root + aws s3 cp "s3://$PREFIX/algorand-beta-$VERSION-1.x86_64.rpm" $PACKAGES_DIR + aws s3 cp "s3://$PREFIX/algorand-devtools-beta-$VERSION-1.x86_64.rpm" $PACKAGES_DIR + aws s3 cp "s3://$PREFIX/algorand-beta-$VERSION-1.aarch64.rpm" $PACKAGES_DIR + aws s3 cp "s3://$PREFIX/algorand-devtools-beta-$VERSION-1.aarch64.rpm" $PACKAGES_DIR else - aws s3 cp "s3://$PREFIX/algorand-$VERSION-1.x86_64.rpm" /root - aws s3 cp "s3://$PREFIX/algorand-devtools-$VERSION-1.x86_64.rpm" /root + aws s3 cp "s3://$PREFIX/algorand-$VERSION-1.x86_64.rpm" $PACKAGES_DIR + aws s3 cp "s3://$PREFIX/algorand-devtools-$VERSION-1.x86_64.rpm" $PACKAGES_DIR + aws s3 cp "s3://$PREFIX/algorand-$VERSION-1.aarch64.rpm" $PACKAGES_DIR + aws s3 cp "s3://$PREFIX/algorand-devtools-$VERSION-1.aarch64.rpm" $PACKAGES_DIR fi else cp "$PACKAGES_DIR"/*"$VERSION"*.rpm /root @@ -56,11 +53,7 @@ cat << EOF > .rpmmacros EOF mkdir rpmrepo -for rpm in $(ls *"$VERSION"*.rpm) -do - rpmsign --addsign "$rpm" - cp -p "$rpm" rpmrepo -done +mv -f *"$VERSION"*.rpm rpmrepo createrepo --database rpmrepo rm -f rpmrepo/repodata/repomd.xml.asc @@ -72,8 +65,19 @@ then cp -r /root/rpmrepo . else aws s3 sync rpmrepo "s3://algorand-releases/rpm/$CHANNEL/" + # sync signatures to releases so that the .sig files load from there - aws s3 sync s3://$STAGING/releases/$CHANNEL/ s3://algorand-releases/rpm/sigs/$CHANNEL/ --exclude='*' --include='*.rpm.sig' + if [ -n "$S3_SOURCE" ]; then + # if S3_SOURCE exists, we copied files from s3 + echo "Copy signatures from s3 staging to s3 releases" + aws s3 sync s3://algorand-staging/releases/$CHANNEL/ s3://algorand-releases/rpm/sigs/$CHANNEL/ --exclude='*' --include='*.rpm.sig' + + else + # We are working with files locally + popd + echo "Copy local signatures to s3 releases" + aws s3 sync "$PACKAGES_DIR" "s3://algorand-releases/rpm/sigs/$CHANNEL/" --exclude='*' --include='*.rpm.sig' + fi fi echo diff --git a/scripts/release/mule/sign/sign.sh b/scripts/release/mule/sign/sign.sh index e08e2d52d3..89baedb9ce 100755 --- a/scripts/release/mule/sign/sign.sh +++ b/scripts/release/mule/sign/sign.sh @@ -10,18 +10,13 @@ echo date "+build_release begin SIGN stage %Y%m%d_%H%M%S" echo -if [ -z "$NETWORK" ]; then - echo "[$0] NETWORK is missing." - exit 1 -fi - -CHANNEL=$(./scripts/release/mule/common/get_channel.sh "$NETWORK") +CHANNEL=${CHANNEL:-$(./scripts/release/mule/common/get_channel.sh "$NETWORK")} VERSION=${VERSION:-$(./scripts/compute_build_number.sh -f)} PKG_DIR="./tmp/node_pkgs" SIGNING_KEY_ADDR=dev@algorand.com OS_TYPE=$(./scripts/release/mule/common/ostype.sh) -ARCHS=(amd64 arm arm64) -ARCH_BITS=(x86_64 armv7l aarch64) +ARCHS=(amd64 arm64) +ARCH_BITS=(x86_64 aarch64) # Note that we don't want to use $GNUPGHOME here because that is a documented env var for the gnupg # project and if it's set in the environment mule will automatically pick it up, which could have # unintended consequences and be hard to debug. @@ -40,6 +35,14 @@ then find "$GPG_DIR" -type f -exec chmod 600 {} \; fi +pushd /root +cat << EOF > .rpmmacros +%_gpg_name Algorand RPM +%__gpg /usr/bin/gpg2 +%__gpg_check_password_cmd true +EOF +popd + # Note that when downloading from the cloud that we'll get all packages for all architectures. if [ -n "$S3_SOURCE" ] then @@ -87,13 +90,14 @@ for os in "${OS_TYPES[@]}"; do for file in *.rpm do + rpmsign --addsign "$file" gpg -u rpm@algorand.com --detach-sign "$file" done HASHFILE="hashes_${CHANNEL}_${os}_${arch}_${VERSION}" md5sum *.tar.gz *.deb *.rpm >> "$HASHFILE" - shasum -a 256 *.tar.gz *.deb *.rpm >> "$HASHFILE" - shasum -a 512 *.tar.gz *.deb *.rpm >> "$HASHFILE" + sha256sum *.tar.gz *.deb *.rpm >> "$HASHFILE" + sha512sum *.tar.gz *.deb *.rpm >> "$HASHFILE" gpg -u "$SIGNING_KEY_ADDR" --detach-sign "$HASHFILE" gpg -u "$SIGNING_KEY_ADDR" --clearsign "$HASHFILE" diff --git a/test/muleCI/mule.yaml b/test/muleCI/mule.yaml index cb75187db4..afa73ae2c1 100644 --- a/test/muleCI/mule.yaml +++ b/test/muleCI/mule.yaml @@ -61,22 +61,6 @@ agents: - GOLANG_VERSION=`./scripts/get_golang_version.sh` - ARCH=arm64v8 - GOARCH=arm64 - - name: cicd.ubuntu.arm - dockerFilePath: docker/build/cicd.ubuntu.Dockerfile - image: algorand/go-algorand-ci-linux - version: scripts/configure_dev-deps.sh - arch: arm32v7 - env: - - TRAVIS_BRANCH=${GIT_BRANCH} - - NETWORK=$NETWORK - - VERSION=$VERSION - - BUILD_NUMBER=$BUILD_NUMBER - - GOHOSTARCH=arm - - FULLVERSION=${FULLVERSION} - buildArgs: - - GOLANG_VERSION=`./scripts/get_golang_version.sh` - - ARCH=arm32v7 - - GOARCH=armv6l - name: docker-ubuntu dockerFilePath: docker/build/docker.ubuntu.Dockerfile image: algorand/go-algorand-docker-linux-ubuntu @@ -122,10 +106,6 @@ tasks: name: build.arm64 agent: cicd.ubuntu.arm64 target: ci-build - - task: docker.Make - name: build.arm - agent: cicd.ubuntu.arm - target: ci-build - task: docker.Make name: archive @@ -167,12 +147,6 @@ tasks: stashId: ${JENKINS_JOB_CACHE_ID}/linux-arm64 globSpecs: - tmp/node_pkgs/**/* - - task: stash.Stash - name: linux-arm - bucketName: go-algorand-ci-cache - stashId: ${JENKINS_JOB_CACHE_ID}/linux-arm - globSpecs: - - tmp/node_pkgs/**/* - task: stash.Stash name: packages bucketName: go-algorand-ci-cache @@ -193,10 +167,6 @@ tasks: name: darwin-amd64 bucketName: go-algorand-ci-cache stashId: ${JENKINS_JOB_CACHE_ID}/darwin-amd64 - - task: stash.Unstash - name: linux-arm - bucketName: go-algorand-ci-cache - stashId: ${JENKINS_JOB_CACHE_ID}/linux-arm - task: stash.Unstash name: darwin-arm64 bucketName: go-algorand-ci-cache @@ -233,15 +203,10 @@ jobs: tasks: - docker.Make.build.arm64 - stash.Stash.linux-arm64 - build-linux-arm32: - tasks: - - docker.Make.build.arm - - stash.Stash.linux-arm package-all: tasks: - stash.Unstash.linux-amd64 - stash.Unstash.linux-arm64 - - stash.Unstash.linux-arm - stash.Unstash.darwin-arm64 - stash.Unstash.darwin-amd64 - docker.Make.deb.amd64 From d8c825d96b90b4f1c84a5b3771987ab53bfc8568 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Tue, 13 Feb 2024 16:43:32 -0500 Subject: [PATCH 08/16] network: use network context for DNS operations in readFromSRV (#5936) --- cmd/catchpointdump/net.go | 2 +- network/wsNetwork.go | 6 +++--- network/wsNetwork_test.go | 2 +- tools/network/bootstrap.go | 14 +++++++------- tools/network/bootstrap_test.go | 5 +++-- tools/network/telemetryURIUpdateService.go | 3 ++- 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/cmd/catchpointdump/net.go b/cmd/catchpointdump/net.go index 2de40c3c2d..41e1fd1dd0 100644 --- a/cmd/catchpointdump/net.go +++ b/cmd/catchpointdump/net.go @@ -78,7 +78,7 @@ var netCmd = &cobra.Command{ if relayAddress != "" { addrs = []string{relayAddress} } else { - addrs, err = tools.ReadFromSRV("algobootstrap", "tcp", networkName, "", false) + addrs, err = tools.ReadFromSRV(context.Background(), "algobootstrap", "tcp", networkName, "", false) if err != nil || len(addrs) == 0 { reportErrorf("Unable to bootstrap records for '%s' : %v", networkName, err) } diff --git a/network/wsNetwork.go b/network/wsNetwork.go index 5bfabbaf2b..92a02976b4 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -289,7 +289,7 @@ type WebsocketNetwork struct { protocolVersion string // resolveSRVRecords is a function that resolves SRV records for a given service, protocol and name - resolveSRVRecords func(service string, protocol string, name string, fallbackDNSResolverAddress string, secure bool) (addrs []string, err error) + resolveSRVRecords func(ctx context.Context, service string, protocol string, name string, fallbackDNSResolverAddress string, secure bool) (addrs []string, err error) } const ( @@ -1887,7 +1887,7 @@ func (wn *WebsocketNetwork) mergePrimarySecondaryRelayAddressSlices(network prot func (wn *WebsocketNetwork) getDNSAddrs(dnsBootstrap string) (relaysAddresses []string, archiverAddresses []string) { var err error - relaysAddresses, err = wn.resolveSRVRecords("algobootstrap", "tcp", dnsBootstrap, wn.config.FallbackDNSResolverAddress, wn.config.DNSSecuritySRVEnforced()) + relaysAddresses, err = wn.resolveSRVRecords(wn.ctx, "algobootstrap", "tcp", dnsBootstrap, wn.config.FallbackDNSResolverAddress, wn.config.DNSSecuritySRVEnforced()) if err != nil { // only log this warning on testnet or devnet if wn.NetworkID == config.Devnet || wn.NetworkID == config.Testnet { @@ -1896,7 +1896,7 @@ func (wn *WebsocketNetwork) getDNSAddrs(dnsBootstrap string) (relaysAddresses [] relaysAddresses = nil } - archiverAddresses, err = wn.resolveSRVRecords("archive", "tcp", dnsBootstrap, wn.config.FallbackDNSResolverAddress, wn.config.DNSSecuritySRVEnforced()) + archiverAddresses, err = wn.resolveSRVRecords(wn.ctx, "archive", "tcp", dnsBootstrap, wn.config.FallbackDNSResolverAddress, wn.config.DNSSecuritySRVEnforced()) if err != nil { // only log this warning on testnet or devnet if wn.NetworkID == config.Devnet || wn.NetworkID == config.Testnet { diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go index e35cc7d177..ef5769bcff 100644 --- a/network/wsNetwork_test.go +++ b/network/wsNetwork_test.go @@ -4156,7 +4156,7 @@ func TestRefreshRelayArchivePhonebookAddresses(t *testing.T) { } // Mock the SRV record lookup - netA.resolveSRVRecords = func(service string, protocol string, name string, fallbackDNSResolverAddress string, + netA.resolveSRVRecords = func(ctx context.Context, service string, protocol string, name string, fallbackDNSResolverAddress string, secure bool) (addrs []string, err error) { if service == "algobootstrap" && protocol == "tcp" && name == primarySRVBootstrap { return primaryRelayResolvedRecords, nil diff --git a/tools/network/bootstrap.go b/tools/network/bootstrap.go index f04c67528c..d30ae4bda0 100644 --- a/tools/network/bootstrap.go +++ b/tools/network/bootstrap.go @@ -24,7 +24,7 @@ import ( "github.com/algorand/go-algorand/logging" ) -func readFromSRV(service string, protocol string, name string, fallbackDNSResolverAddress string, secure bool) (records []*net.SRV, err error) { +func readFromSRV(ctx context.Context, 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") @@ -38,14 +38,14 @@ func readFromSRV(service string, protocol string, name string, fallbackDNSResolv controller := NewResolveController(secure, fallbackDNSResolverAddress, log) systemResolver := controller.SystemResolver() - _, records, sysLookupErr := systemResolver.LookupSRV(context.Background(), service, protocol, name) + _, records, sysLookupErr := systemResolver.LookupSRV(ctx, service, protocol, name) if sysLookupErr != nil { log.Infof("ReadFromBootstrap: DNS LookupSRV failed when using system resolver: %v", sysLookupErr) var fallbackLookupErr error if fallbackDNSResolverAddress != "" { fallbackResolver := controller.FallbackResolver() - _, records, fallbackLookupErr = fallbackResolver.LookupSRV(context.Background(), service, protocol, name) + _, records, fallbackLookupErr = fallbackResolver.LookupSRV(ctx, service, protocol, name) } if fallbackLookupErr != nil { log.Infof("ReadFromBootstrap: DNS LookupSRV failed when using fallback '%s' resolver: %v", fallbackDNSResolverAddress, fallbackLookupErr) @@ -54,7 +54,7 @@ func readFromSRV(service string, protocol string, name string, fallbackDNSResolv if fallbackLookupErr != nil || fallbackDNSResolverAddress == "" { fallbackResolver := controller.DefaultResolver() var defaultLookupErr error - _, records, defaultLookupErr = fallbackResolver.LookupSRV(context.Background(), service, protocol, name) + _, records, defaultLookupErr = fallbackResolver.LookupSRV(ctx, service, protocol, name) if defaultLookupErr != nil { err = fmt.Errorf("ReadFromBootstrap: DNS LookupSRV failed when using system resolver(%v), fallback resolver(%v), as well as using default resolver due to %v", sysLookupErr, fallbackLookupErr, defaultLookupErr) return @@ -65,8 +65,8 @@ func readFromSRV(service string, protocol string, name string, fallbackDNSResolv } // 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) +func ReadFromSRV(ctx context.Context, service string, protocol string, name string, fallbackDNSResolverAddress string, secure bool) (addrs []string, err error) { + records, err := readFromSRV(ctx, service, protocol, name, fallbackDNSResolverAddress, secure) if err != nil { return addrs, err } @@ -88,7 +88,7 @@ func ReadFromSRV(service string, protocol string, name string, fallbackDNSResolv // 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) + records, err := readFromSRV(context.Background(), service, protocol, name, fallbackDNSResolverAddress, secure) if err != nil { return prioAddrs, err } diff --git a/tools/network/bootstrap_test.go b/tools/network/bootstrap_test.go index 155615c704..a24bea422e 100644 --- a/tools/network/bootstrap_test.go +++ b/tools/network/bootstrap_test.go @@ -17,6 +17,7 @@ package network import ( + "context" "testing" "github.com/algorand/go-algorand/test/partitiontest" @@ -55,10 +56,10 @@ func TestReadFromSRV(t *testing.T) { fallback := "" secure := true - addrs, err := ReadFromSRV("", protocol, name, fallback, secure) + addrs, err := ReadFromSRV(context.Background(), "", protocol, name, fallback, secure) require.Error(t, err) - addrs, err = ReadFromSRV(service, protocol, name, fallback, secure) + addrs, err = ReadFromSRV(context.Background(), service, protocol, name, fallback, secure) require.NoError(t, err) require.GreaterOrEqual(t, len(addrs), 1) addr := addrs[0] diff --git a/tools/network/telemetryURIUpdateService.go b/tools/network/telemetryURIUpdateService.go index 66dd87dd0e..2b4e614261 100644 --- a/tools/network/telemetryURIUpdateService.go +++ b/tools/network/telemetryURIUpdateService.go @@ -17,6 +17,7 @@ package network import ( + "context" "net/url" "strings" "time" @@ -132,5 +133,5 @@ func (t *telemetryURIUpdater) lookupTelemetryURL() (url *url.URL) { } func (t *telemetryURIUpdater) readFromSRV(protocol string, bootstrapID string) (addrs []string, err error) { - return ReadFromSRV("telemetry", protocol, bootstrapID, t.cfg.FallbackDNSResolverAddress, t.cfg.DNSSecuritySRVEnforced()) + return ReadFromSRV(context.Background(), "telemetry", protocol, bootstrapID, t.cfg.FallbackDNSResolverAddress, t.cfg.DNSSecuritySRVEnforced()) } From 52964edd9795c4cacc7d107ac9f41f73374fce71 Mon Sep 17 00:00:00 2001 From: Gary <982483+gmalouf@users.noreply.github.com> Date: Wed, 21 Feb 2024 15:33:35 -0500 Subject: [PATCH 09/16] Network: Class-based Peer Selector (#5937) --- catchup/catchpointService.go | 80 ++-- catchup/classBasedPeerSelector.go | 156 ++++++++ catchup/classBasedPeerSelector_test.go | 498 +++++++++++++++++++++++++ catchup/peerSelector.go | 34 +- catchup/peerSelector_test.go | 22 +- catchup/service.go | 108 +++--- catchup/service_test.go | 111 +----- 7 files changed, 789 insertions(+), 220 deletions(-) create mode 100644 catchup/classBasedPeerSelector.go create mode 100644 catchup/classBasedPeerSelector_test.go diff --git a/catchup/catchpointService.go b/catchup/catchpointService.go index 2c4f6dfc41..3c11d0db73 100644 --- a/catchup/catchpointService.go +++ b/catchup/catchpointService.go @@ -18,6 +18,7 @@ package catchup import ( "context" + "errors" "fmt" "sync" "time" @@ -69,7 +70,7 @@ type CatchpointCatchupStats struct { type CatchpointCatchupService struct { // stats is the statistics object, updated async while downloading the ledger stats CatchpointCatchupStats - // statsMu synchronizes access to stats, as we could attempt to update it while querying for it's current state + // statsMu synchronizes access to stats, as we could attempt to update it while querying for its current state statsMu deadlock.Mutex node CatchpointCatchupNodeServices // ctx is the node cancellation context, used when the node is being stopped. @@ -98,7 +99,7 @@ type CatchpointCatchupService struct { abortCtx context.Context abortCtxFunc context.CancelFunc // blocksDownloadPeerSelector is the peer selector used for downloading blocks. - blocksDownloadPeerSelector *peerSelector + blocksDownloadPeerSelector peerSelector } // MakeResumedCatchpointCatchupService creates a catchpoint catchup service for a node that is already in catchpoint catchup mode @@ -280,51 +281,50 @@ func (cs *CatchpointCatchupService) processStageInactive() (err error) { } // processStageLedgerDownload is the second catchpoint catchup stage. It downloads the ledger. -func (cs *CatchpointCatchupService) processStageLedgerDownload() (err error) { +func (cs *CatchpointCatchupService) processStageLedgerDownload() error { cs.statsMu.Lock() label := cs.stats.CatchpointLabel cs.statsMu.Unlock() - round, _, err0 := ledgercore.ParseCatchpointLabel(label) + round, _, err := ledgercore.ParseCatchpointLabel(label) - if err0 != nil { - return cs.abort(fmt.Errorf("processStageLedgerDownload failed to parse label : %v", err0)) + if err != nil { + return cs.abort(fmt.Errorf("processStageLedgerDownload failed to parse label : %v", err)) } // download balances file. - peerSelector := cs.makeCatchpointPeerSelector() - ledgerFetcher := makeLedgerFetcher(cs.net, cs.ledgerAccessor, cs.log, cs, cs.config) + lf := makeLedgerFetcher(cs.net, cs.ledgerAccessor, cs.log, cs, cs.config) attemptsCount := 0 for { attemptsCount++ - err = cs.ledgerAccessor.ResetStagingBalances(cs.ctx, true) - if err != nil { + err0 := cs.ledgerAccessor.ResetStagingBalances(cs.ctx, true) + if err0 != nil { if cs.ctx.Err() != nil { return cs.stopOrAbort() } - return cs.abort(fmt.Errorf("processStageLedgerDownload failed to reset staging balances : %v", err)) + return cs.abort(fmt.Errorf("processStageLedgerDownload failed to reset staging balances : %v", err0)) } - psp, err := peerSelector.getNextPeer() - if err != nil { - err = fmt.Errorf("processStageLedgerDownload: catchpoint catchup was unable to obtain a list of peers to retrieve the catchpoint file from") - return cs.abort(err) + psp, err0 := cs.blocksDownloadPeerSelector.getNextPeer() + if err0 != nil { + err0 = fmt.Errorf("processStageLedgerDownload: catchpoint catchup was unable to obtain a list of peers to retrieve the catchpoint file from") + return cs.abort(err0) } peer := psp.Peer start := time.Now() - err = ledgerFetcher.downloadLedger(cs.ctx, peer, round) - if err == nil { + err0 = lf.downloadLedger(cs.ctx, peer, round) + if err0 == nil { cs.log.Infof("ledger downloaded in %d seconds", time.Since(start)/time.Second) start = time.Now() - err = cs.ledgerAccessor.BuildMerkleTrie(cs.ctx, cs.updateVerifiedCounts) - if err == nil { + err0 = cs.ledgerAccessor.BuildMerkleTrie(cs.ctx, cs.updateVerifiedCounts) + if err0 == nil { cs.log.Infof("built merkle trie in %d seconds", time.Since(start)/time.Second) break } // failed to build the merkle trie for the above catchpoint file. - peerSelector.rankPeer(psp, peerRankInvalidDownload) + cs.blocksDownloadPeerSelector.rankPeer(psp, peerRankInvalidDownload) } else { - peerSelector.rankPeer(psp, peerRankDownloadFailed) + cs.blocksDownloadPeerSelector.rankPeer(psp, peerRankDownloadFailed) } // instead of testing for err == cs.ctx.Err() , we'll check on the context itself. @@ -335,10 +335,10 @@ func (cs *CatchpointCatchupService) processStageLedgerDownload() (err error) { } if attemptsCount >= cs.config.CatchupLedgerDownloadRetryAttempts { - err = fmt.Errorf("processStageLedgerDownload: catchpoint catchup exceeded number of attempts to retrieve ledger") - return cs.abort(err) + err0 = fmt.Errorf("processStageLedgerDownload: catchpoint catchup exceeded number of attempts to retrieve ledger") + return cs.abort(err0) } - cs.log.Warnf("unable to download ledger : %v", err) + cs.log.Warnf("unable to download ledger : %v", err0) } err = cs.updateStage(ledger.CatchpointCatchupStateLatestBlockDownload) @@ -506,14 +506,14 @@ func lookbackForStateproofsSupport(topBlock *bookkeeping.Block) uint64 { return uint64(topBlock.Round().SubSaturate(lowestStateProofRound)) } -// processStageBlocksDownload is the fourth catchpoint catchup stage. It downloads all the reminder of the blocks, verifying each one of them against it's predecessor. +// processStageBlocksDownload is the fourth catchpoint catchup stage. It downloads all the reminder of the blocks, verifying each one of them against its predecessor. func (cs *CatchpointCatchupService) processStageBlocksDownload() (err error) { topBlock, err := cs.ledgerAccessor.EnsureFirstBlock(cs.ctx) if err != nil { return cs.abort(fmt.Errorf("processStageBlocksDownload failed, unable to ensure first block : %v", err)) } - // pick the lookback with the greater of + // pick the lookback with the greatest of // either (MaxTxnLife+DeeperBlockHeaderHistory+CatchpointLookback) or MaxBalLookback // Explanation: // 1. catchpoint snapshots accounts at round X-CatchpointLookback @@ -531,13 +531,13 @@ func (cs *CatchpointCatchupService) processStageBlocksDownload() (err error) { } // in case the effective lookback is going before our rounds count, trim it there. - // ( a catchpoint is generated starting round MaxBalLookback, and this is a possible in any round in the range of MaxBalLookback..MaxTxnLife) + // ( a catchpoint is generated starting round MaxBalLookback, and this is a possible in any round in the range of MaxBalLookback...MaxTxnLife) if lookback >= uint64(topBlock.Round()) { lookback = uint64(topBlock.Round() - 1) } cs.statsMu.Lock() - cs.stats.TotalBlocks = uint64(lookback) + cs.stats.TotalBlocks = lookback cs.stats.AcquiredBlocks = 0 cs.stats.VerifiedBlocks = 0 cs.statsMu.Unlock() @@ -558,8 +558,9 @@ func (cs *CatchpointCatchupService) processStageBlocksDownload() (err error) { blk = &ledgerBlock cert = &ledgerCert } else { - switch err0.(type) { - case ledgercore.ErrNoEntry: + var errNoEntry ledgercore.ErrNoEntry + switch { + case errors.As(err0, &errNoEntry): // this is expected, ignore this one. default: cs.log.Warnf("processStageBlocksDownload encountered the following error when attempting to retrieve the block for round %d : %v", topBlock.Round()-basics.Round(blocksFetched), err0) @@ -658,7 +659,7 @@ func (cs *CatchpointCatchupService) processStageBlocksDownload() (err error) { func (cs *CatchpointCatchupService) fetchBlock(round basics.Round, retryCount uint64) (blk *bookkeeping.Block, cert *agreement.Certificate, downloadDuration time.Duration, psp *peerSelectorPeer, stop bool, err error) { psp, err = cs.blocksDownloadPeerSelector.getNextPeer() if err != nil { - if err == errPeerSelectorNoPeerPoolsAvailable { + if errors.Is(err, errPeerSelectorNoPeerPoolsAvailable) { cs.log.Infof("fetchBlock: unable to obtain a list of peers to retrieve the latest block from; will retry shortly.") // this is a possible on startup, since the network package might have yet to retrieve the list of peers. time.Sleep(noPeersAvailableSleepInterval) @@ -718,7 +719,7 @@ func (cs *CatchpointCatchupService) processStageSwitch() (err error) { // stopOrAbort is called when any of the stage processing function sees that cs.ctx has been canceled. It can be // due to the end user attempting to abort the current catchpoint catchup operation or due to a node shutdown. func (cs *CatchpointCatchupService) stopOrAbort() error { - if cs.abortCtx.Err() == context.Canceled { + if errors.Is(cs.abortCtx.Err(), context.Canceled) { return cs.abort(context.Canceled) } return nil @@ -749,7 +750,7 @@ func (cs *CatchpointCatchupService) updateStage(newStage ledger.CatchpointCatchu return nil } -// updateNodeCatchupMode requests the node to change it's operational mode from +// updateNodeCatchupMode requests the node to change its operational mode from // catchup mode to normal mode and vice versa. func (cs *CatchpointCatchupService) updateNodeCatchupMode(catchupModeEnabled bool) { newCtxCh := cs.node.SetCatchpointCatchupMode(catchupModeEnabled) @@ -802,15 +803,7 @@ func (cs *CatchpointCatchupService) updateBlockRetrievalStatistics(acquiredBlock } func (cs *CatchpointCatchupService) initDownloadPeerSelector() { - cs.blocksDownloadPeerSelector = cs.makeCatchpointPeerSelector() -} - -func (cs *CatchpointCatchupService) makeCatchpointPeerSelector() *peerSelector { - return makePeerSelector( - cs.net, - []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookRelays}, - }) + cs.blocksDownloadPeerSelector = makeCatchpointPeerSelector(cs.net) } // checkLedgerDownload sends a HEAD request to the ledger endpoint of peers to validate the catchpoint's availability @@ -821,10 +814,9 @@ func (cs *CatchpointCatchupService) checkLedgerDownload() error { if err != nil { return fmt.Errorf("failed to parse catchpoint label : %v", err) } - peerSelector := cs.makeCatchpointPeerSelector() ledgerFetcher := makeLedgerFetcher(cs.net, cs.ledgerAccessor, cs.log, cs, cs.config) for i := 0; i < cs.config.CatchupLedgerDownloadRetryAttempts; i++ { - psp, peerError := peerSelector.getNextPeer() + psp, peerError := cs.blocksDownloadPeerSelector.getNextPeer() if peerError != nil { return err } diff --git a/catchup/classBasedPeerSelector.go b/catchup/classBasedPeerSelector.go new file mode 100644 index 0000000000..9ab9e6d71d --- /dev/null +++ b/catchup/classBasedPeerSelector.go @@ -0,0 +1,156 @@ +// Copyright (C) 2019-2024 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 catchup + +import ( + "errors" + "github.com/algorand/go-algorand/network" + "github.com/algorand/go-deadlock" + "time" +) + +// classBasedPeerSelector is a rankPooledPeerSelector that tracks and ranks classes of peers based on their response behavior. +// It is used to select the most appropriate peers to download blocks from - this is most useful when catching up +// and needing to figure out whether the blocks can be retrieved from relay nodes or require archive nodes. +// The ordering of the peerSelectors directly determines the priority of the classes of peers. +type classBasedPeerSelector struct { + mu deadlock.Mutex + peerSelectors []*wrappedPeerSelector +} + +func makeClassBasedPeerSelector(peerSelectors []*wrappedPeerSelector) *classBasedPeerSelector { + return &classBasedPeerSelector{ + peerSelectors: peerSelectors, + } +} + +func (c *classBasedPeerSelector) rankPeer(psp *peerSelectorPeer, rank int) (int, int) { + c.mu.Lock() + defer c.mu.Unlock() + + oldRank, newRank := -1, -1 + for _, wp := range c.peerSelectors { + // See if the peer is in the class, ranking it appropriately if so + if psp.peerClass != wp.peerClass { + continue + } + + oldRank, newRank = wp.peerSelector.rankPeer(psp, rank) + if oldRank < 0 || newRank < 0 { + // Peer not found in this selector + continue + } + + // Peer was in this class, if there was any kind of download issue, we increment the failure count + if rank >= peerRankNoBlockForRound { + wp.downloadFailures++ + } + + break + } + + return oldRank, newRank +} + +func (c *classBasedPeerSelector) peerDownloadDurationToRank(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) { + c.mu.Lock() + defer c.mu.Unlock() + + for _, wp := range c.peerSelectors { + rank = wp.peerSelector.peerDownloadDurationToRank(psp, blockDownloadDuration) + // If rank is peerRankInvalidDownload, we check the next class's rankPooledPeerSelector + if rank >= peerRankInvalidDownload { + continue + } + // Should be a legit ranking, we return it + return rank + } + // If we reached here, we have exhausted all classes without finding the peer + return peerRankInvalidDownload +} + +func (c *classBasedPeerSelector) getNextPeer() (psp *peerSelectorPeer, err error) { + c.mu.Lock() + defer c.mu.Unlock() + return c.internalGetNextPeer(0) +} + +// internalGetNextPeer is a helper function that should be called with the lock held +func (c *classBasedPeerSelector) internalGetNextPeer(recurseCount int8) (psp *peerSelectorPeer, err error) { + // Safety check to prevent infinite recursion + if recurseCount > 1 { + return nil, errPeerSelectorNoPeerPoolsAvailable + } + selectorDisabledCount := 0 + for _, wp := range c.peerSelectors { + if wp.downloadFailures > wp.toleranceFactor { + // peerSelector is disabled for now, we move to the next one + selectorDisabledCount++ + continue + } + psp, err = wp.peerSelector.getNextPeer() + + if err != nil { + // This is mostly just future-proofing, as we don't expect any other errors from getNextPeer + if errors.Is(err, errPeerSelectorNoPeerPoolsAvailable) { + // We penalize this class the equivalent of one download failure (in case this is transient) + wp.downloadFailures++ + } + continue + } + return psp, nil + } + // If we reached here, we have exhausted all classes and still have no peers + // IFF all classes are disabled, we reset the downloadFailures for all classes and start over + if len(c.peerSelectors) != 0 && selectorDisabledCount == len(c.peerSelectors) { + for _, wp := range c.peerSelectors { + wp.downloadFailures = 0 + } + // Recurse to try again, we should have at least one class enabled now + return c.internalGetNextPeer(recurseCount + 1) + } + // If we reached here, we have exhausted all classes without finding a peer, not due to all classes being disabled + return nil, errPeerSelectorNoPeerPoolsAvailable +} + +type wrappedPeerSelector struct { + peerSelector peerSelector // The underlying peerSelector for this class + peerClass network.PeerOption // The class of peers the peerSelector is responsible for + toleranceFactor int // The number of times we can net fail for any reason before we move to the next class's rankPooledPeerSelector + downloadFailures int // The number of times we have failed to download a block from this class's rankPooledPeerSelector since it was last reset +} + +// makeCatchpointPeerSelector returns a classBasedPeerSelector that selects peers based on their class and response behavior. +// These are the preferred configurations for the catchpoint service. +func makeCatchpointPeerSelector(net peersRetriever) peerSelector { + wrappedPeerSelectors := []*wrappedPeerSelector{ + { + peerClass: network.PeersPhonebookRelays, + peerSelector: makeRankPooledPeerSelector(net, + []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookRelays}}), + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookArchivalNodes, + peerSelector: makeRankPooledPeerSelector(net, + []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}}), + toleranceFactor: 10, + }, + } + + return makeClassBasedPeerSelector(wrappedPeerSelectors) +} diff --git a/catchup/classBasedPeerSelector_test.go b/catchup/classBasedPeerSelector_test.go new file mode 100644 index 0000000000..0110663f87 --- /dev/null +++ b/catchup/classBasedPeerSelector_test.go @@ -0,0 +1,498 @@ +// Copyright (C) 2019-2024 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 catchup + +import ( + "github.com/algorand/go-algorand/network" + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" + "testing" + "time" +) + +// Use to mock the wrapped peer selectors where warranted +type mockPeerSelector struct { + mockRankPeer func(psp *peerSelectorPeer, rank int) (int, int) + mockPeerDownloadDurationToRank func(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) + mockGetNextPeer func() (psp *peerSelectorPeer, err error) +} + +func (m mockPeerSelector) rankPeer(psp *peerSelectorPeer, rank int) (int, int) { + return m.mockRankPeer(psp, rank) +} + +func (m mockPeerSelector) peerDownloadDurationToRank(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) { + return m.mockPeerDownloadDurationToRank(psp, blockDownloadDuration) +} + +func (m mockPeerSelector) getNextPeer() (psp *peerSelectorPeer, err error) { + return m.mockGetNextPeer() +} + +func TestClassBasedPeerSelector_makeClassBasedPeerSelector(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + wrappedPeerSelectors := []*wrappedPeerSelector{ + { + peerClass: network.PeersPhonebookRelays, + peerSelector: mockPeerSelector{}, + toleranceFactor: 3, + }, + { + peerClass: network.PeersConnectedOut, + peerSelector: mockPeerSelector{}, + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookArchivalNodes, + peerSelector: mockPeerSelector{}, + toleranceFactor: 10, + }, + } + + cps := makeClassBasedPeerSelector(wrappedPeerSelectors) + + // The selectors should be sorted by priority + require.Equal(t, 3, len(cps.peerSelectors)) + require.Equal(t, network.PeersPhonebookRelays, cps.peerSelectors[0].peerClass) + require.Equal(t, network.PeersConnectedOut, cps.peerSelectors[1].peerClass) + require.Equal(t, network.PeersPhonebookArchivalNodes, cps.peerSelectors[2].peerClass) +} + +func TestClassBasedPeerSelector_rankPeer(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + mockPeer := &peerSelectorPeer{ + peerClass: network.PeersPhonebookRelays, + } + + // Create a class based peer selector initially with the first wrapped peer selector not having the peer, + // second one having it, and a third one not having it + wrappedPeerSelectors := []*wrappedPeerSelector{ + { + peerClass: network.PeersConnectedOut, + peerSelector: mockPeerSelector{ + mockRankPeer: func(psp *peerSelectorPeer, rank int) (int, int) { + return -1, -1 + }, + }, + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookRelays, + peerSelector: mockPeerSelector{ + mockRankPeer: func(psp *peerSelectorPeer, rank int) (int, int) { + if psp == mockPeer { + return 10, rank + } + return -1, -1 + }, + }, + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookArchivalNodes, + peerSelector: mockPeerSelector{ + mockRankPeer: func(psp *peerSelectorPeer, rank int) (int, int) { + return -1, -1 + }, + }, + toleranceFactor: 3, + }, + } + cps := makeClassBasedPeerSelector(wrappedPeerSelectors) + + // Peer is found in second selector, rank is within range for a block found + oldRank, newRank := cps.rankPeer(mockPeer, 50) + + require.Equal(t, 10, oldRank) + require.Equal(t, 50, newRank) + require.Equal(t, 0, cps.peerSelectors[1].downloadFailures) + + // Peer is found in second selector, rank is >= peerRankNoBlockForRound + oldRank, newRank = cps.rankPeer(mockPeer, peerRankNoBlockForRound) + + require.Equal(t, 10, oldRank) + require.Equal(t, peerRankNoBlockForRound, newRank) + require.Equal(t, 1, cps.peerSelectors[1].downloadFailures) + + // We fail to find a block for round 3 more times, download failures should reflect that. + cps.rankPeer(mockPeer, peerRankNoBlockForRound) + oldRank, newRank = cps.rankPeer(mockPeer, peerRankNoBlockForRound) + + require.Equal(t, 10, oldRank) + require.Equal(t, peerRankNoBlockForRound, newRank) + require.Equal(t, 3, cps.peerSelectors[1].downloadFailures) + + oldRank, newRank = cps.rankPeer(mockPeer, peerRankNoBlockForRound) + require.Equal(t, 10, oldRank) + require.Equal(t, peerRankNoBlockForRound, newRank) + require.Equal(t, 4, cps.peerSelectors[1].downloadFailures) + + // Now, feed peers that are not in any of the selectors - it should return -1, -1 + mockPeer2 := &peerSelectorPeer{ + peerClass: network.PeersConnectedIn, + } + + oldRank, newRank = cps.rankPeer(mockPeer2, 50) + require.Equal(t, -1, oldRank) + require.Equal(t, -1, newRank) + + // While this will match class, the selectors will not have it + mockPeer3 := &peerSelectorPeer{ + peerClass: network.PeersConnectedOut, + } + + oldRank, newRank = cps.rankPeer(mockPeer3, 50) + require.Equal(t, -1, oldRank) + require.Equal(t, -1, newRank) + + // Last sanity check, we should have zero download failures for the first and third selectors + require.Equal(t, 0, cps.peerSelectors[0].downloadFailures) + require.Equal(t, 0, cps.peerSelectors[2].downloadFailures) +} + +func TestClassBasedPeerSelector_peerDownloadDurationToRank(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + mockPeer := &peerSelectorPeer{} + testDuration := 50 * time.Millisecond + + // Create a class based peer selector initially with the first wrapped peer selector not having the peer, + // second one having it, and a third one not having it + wrappedPeerSelectors := []*wrappedPeerSelector{ + { + peerClass: network.PeersConnectedOut, + peerSelector: mockPeerSelector{ + mockPeerDownloadDurationToRank: func(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) { + return peerRankInvalidDownload + }, + }, + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookRelays, + peerSelector: mockPeerSelector{ + mockPeerDownloadDurationToRank: func(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) { + if psp == mockPeer && blockDownloadDuration == testDuration { + return peerRank0HighBlockTime + } + return peerRankInvalidDownload + }, + }, + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookArchivalNodes, + peerSelector: mockPeerSelector{ + mockPeerDownloadDurationToRank: func(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) { + return peerRankInvalidDownload + }, + }, + toleranceFactor: 3, + }, + } + cps := makeClassBasedPeerSelector(wrappedPeerSelectors) + + // The peer is found in the second selector, so the rank should be peerRank0HighBlockTime + rank := cps.peerDownloadDurationToRank(mockPeer, testDuration) + require.Equal(t, peerRank0HighBlockTime, rank) + + // The peer is not found in any of the selectors, so the rank should be peerRankInvalidDownload + mockPeer2 := &peerSelectorPeer{} + + rank = cps.peerDownloadDurationToRank(mockPeer2, testDuration) + require.Equal(t, peerRankInvalidDownload, rank) +} + +func TestClassBasedPeerSelector_getNextPeer(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + mockPeer := &peerSelectorPeer{ + peerClass: network.PeersPhonebookRelays, + } + + // Create a class based peer selector initially with the first wrapped peer selector not having any peers, + // second one having a peer, and a third one not having any peers + wrappedPeerSelectors := []*wrappedPeerSelector{ + { + peerClass: network.PeersConnectedOut, + peerSelector: mockPeerSelector{ + mockGetNextPeer: func() (psp *peerSelectorPeer, err error) { + return nil, errPeerSelectorNoPeerPoolsAvailable + }, + }, + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookRelays, + peerSelector: mockPeerSelector{ + mockGetNextPeer: func() (psp *peerSelectorPeer, err error) { + return mockPeer, nil + }, + }, + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookArchivalNodes, + peerSelector: mockPeerSelector{ + mockGetNextPeer: func() (psp *peerSelectorPeer, err error) { + return nil, errPeerSelectorNoPeerPoolsAvailable + }, + }, + toleranceFactor: 3, + }, + } + + cps := makeClassBasedPeerSelector(wrappedPeerSelectors) + + peerResult, err := cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockPeer, peerResult) + + // Update selector to not return any peers + wrappedPeerSelectors[1].peerSelector = mockPeerSelector{ + mockGetNextPeer: func() (psp *peerSelectorPeer, err error) { + return nil, errPeerSelectorNoPeerPoolsAvailable + }, + } + + peerResult, err = cps.getNextPeer() + require.Nil(t, peerResult) + require.Equal(t, errPeerSelectorNoPeerPoolsAvailable, err) + + // Create a class based peer selector initially with all wrapped peer selectors having peers. + // The peers should always come from the first one repeatedly since rankings are not changed. + mockPeer = &peerSelectorPeer{ + peerClass: network.PeersConnectedOut, + } + mockPeer2 := &peerSelectorPeer{ + peerClass: network.PeersPhonebookRelays, + } + mockPeer3 := &peerSelectorPeer{ + peerClass: network.PeersPhonebookArchivalNodes, + } + + wrappedPeerSelectors = []*wrappedPeerSelector{ + { + peerClass: network.PeersConnectedOut, + peerSelector: mockPeerSelector{ + mockGetNextPeer: func() (psp *peerSelectorPeer, err error) { + return mockPeer, nil + }, + mockRankPeer: func(psp *peerSelectorPeer, rank int) (int, int) { + if psp == mockPeer { + return 10, rank + } + return -1, -1 + }, + }, + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookRelays, + peerSelector: mockPeerSelector{ + mockGetNextPeer: func() (psp *peerSelectorPeer, err error) { + return mockPeer2, nil + }, + mockRankPeer: func(psp *peerSelectorPeer, rank int) (int, int) { + if psp == mockPeer2 { + return 10, rank + } + return -1, -1 + }, + }, + toleranceFactor: 10, + }, + { + peerClass: network.PeersPhonebookArchivalNodes, + peerSelector: mockPeerSelector{ + mockGetNextPeer: func() (psp *peerSelectorPeer, err error) { + return mockPeer3, nil + }, + mockRankPeer: func(psp *peerSelectorPeer, rank int) (int, int) { + if psp == mockPeer3 { + return 10, rank + } + return -1, -1 + }, + }, + toleranceFactor: 3, + }, + } + + cps = makeClassBasedPeerSelector(wrappedPeerSelectors) + + // We should always get the peer from the top priority selector since rankings are not updated/list is not re-sorted. + for i := 0; i < 10; i++ { + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockPeer, peerResult) + } + + // Okay, record enough download failures to disable the first selector + for i := 0; i < 4; i++ { + cps.rankPeer(mockPeer, peerRankNoBlockForRound) + } + + // Now, we should get the peer from the second selector + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockPeer2, peerResult) + + // Sanity check the download failures for each selector + require.Equal(t, 4, cps.peerSelectors[0].downloadFailures) + require.Equal(t, 0, cps.peerSelectors[1].downloadFailures) + require.Equal(t, 0, cps.peerSelectors[2].downloadFailures) + + // Now, record download failures just up to the tolerance factor for the second selector + for i := 0; i < 10; i++ { + cps.rankPeer(mockPeer2, peerRankNoBlockForRound) + } + + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockPeer2, peerResult) + + // One more should push us to the third selector + cps.rankPeer(mockPeer2, peerRankNoBlockForRound) + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockPeer3, peerResult) + + // Check of the download failures for each selector + require.Equal(t, 4, cps.peerSelectors[0].downloadFailures) + require.Equal(t, 11, cps.peerSelectors[1].downloadFailures) + require.Equal(t, 0, cps.peerSelectors[2].downloadFailures) + + // Now, record download failures just up to the tolerance factor for the third selector + for i := 0; i < 3; i++ { + cps.rankPeer(mockPeer3, peerRankNoBlockForRound) + } + + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockPeer3, peerResult) + + require.Equal(t, 4, cps.peerSelectors[0].downloadFailures) + require.Equal(t, 11, cps.peerSelectors[1].downloadFailures) + require.Equal(t, 3, cps.peerSelectors[2].downloadFailures) + + // One more failure should reset ALL download failures (and grab a peer from the first selector) + cps.rankPeer(mockPeer3, peerRankNoBlockForRound) + + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockPeer, peerResult) + + // Check of the download failures for each selector, should have been reset + require.Equal(t, 0, cps.peerSelectors[0].downloadFailures) + require.Equal(t, 0, cps.peerSelectors[1].downloadFailures) + require.Equal(t, 0, cps.peerSelectors[2].downloadFailures) +} + +func TestClassBasedPeerSelector_integration(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + mockP1Peer := mockHTTPPeer{address: "p1"} + mockP2Peer := mockHTTPPeer{address: "p2"} + + mockP1WrappedPeer := &peerSelectorPeer{&mockP1Peer, network.PeersPhonebookRelays} + mockP2WrappedPeer := &peerSelectorPeer{&mockP2Peer, network.PeersPhonebookArchivalNodes} + + net := makePeersRetrieverStub(func(options ...network.PeerOption) []network.Peer { + if len(options) > 0 { + switch options[0] { + case network.PeersPhonebookRelays: + return []network.Peer{&mockP1Peer} + case network.PeersPhonebookArchivalNodes: + return []network.Peer{&mockP2Peer} + default: + return []network.Peer{&mockP1Peer, &mockP2Peer} + } + } + return nil + }) + // Create a class based peer selector with a few wrapped peer selectors + cps := makeCatchpointPeerSelector(net).(*classBasedPeerSelector) + + // We should get the peer from the first priority selector, PeersPhonebookRelays + peerResult, err := cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockP1WrappedPeer, peerResult) + + // Normal expected usage: rank the peer + durationRank := cps.peerDownloadDurationToRank(mockP1WrappedPeer, 500) + oldRank, newRank := cps.rankPeer(mockP1WrappedPeer, durationRank) + + require.Equal(t, 0, oldRank) + require.Equal(t, durationRank, newRank) + + // Let's simulate a few download failures (not enough to disable the selector) + for i := 0; i < 3; i++ { + expectedOldRank := newRank + peerResult, err = cps.getNextPeer() + + require.Nil(t, err) + require.Equal(t, mockP1WrappedPeer, peerResult) + + oldRank, newRank = cps.rankPeer(mockP1WrappedPeer, peerRankNoBlockForRound) + + require.Equal(t, expectedOldRank, oldRank) + // Should be increasing with no block penalties + require.True(t, newRank >= oldRank) + } + + // Sanity check, still should be the same peer (from phonebook selector) + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockP1WrappedPeer, peerResult) + + // Rank the peer to follow normal usage + durationRank = cps.peerDownloadDurationToRank(mockP1WrappedPeer, 500) + expectedOldRank := newRank + oldRank, newRank = cps.rankPeer(mockP1WrappedPeer, durationRank) + + require.Equal(t, expectedOldRank, oldRank) + // Rank should not go up after successful download + require.True(t, newRank <= oldRank) + + // Now, let's simulate enough download failures to disable the first selector + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockP1WrappedPeer, peerResult) + cps.rankPeer(mockP1WrappedPeer, peerRankNoBlockForRound) + + peerResult, err = cps.getNextPeer() + require.Nil(t, err) + require.Equal(t, mockP2WrappedPeer, peerResult) + + // Normal expected usage: rank the peer + durationRank = cps.peerDownloadDurationToRank(mockP2WrappedPeer, 500) + oldRank, newRank = cps.rankPeer(mockP2WrappedPeer, durationRank) + + require.Equal(t, 0, oldRank) + require.Equal(t, durationRank, newRank) + + require.Equal(t, 4, cps.peerSelectors[0].downloadFailures) + require.Equal(t, 0, cps.peerSelectors[1].downloadFailures) +} diff --git a/catchup/peerSelector.go b/catchup/peerSelector.go index 4ceda8d42d..1485295581 100644 --- a/catchup/peerSelector.go +++ b/catchup/peerSelector.go @@ -88,7 +88,7 @@ type peerClass struct { peerClass network.PeerOption } -// the peersRetriever is a subset of the network.GossipNode used to ensure that we can create an instance of the peerSelector +// the peersRetriever is a subset of the network.GossipNode used to ensure that we can create an instance of the rankPooledPeerSelector // for testing purposes, providing just the above function. type peersRetriever interface { // Get a list of Peers we could potentially send a direct message to. @@ -109,14 +109,20 @@ type peerPool struct { peers []peerPoolEntry } -// peerSelector is a helper struct used to select the next peer to try and connect to +type peerSelector interface { + rankPeer(psp *peerSelectorPeer, rank int) (int, int) + peerDownloadDurationToRank(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) + getNextPeer() (psp *peerSelectorPeer, err error) +} + +// rankPooledPeerSelector is a helper struct used to select the next peer to try and connect to // for various catchup purposes. Unlike the underlying network GetPeers(), it allows the // client to provide feedback regarding the peer's performance, and to have the subsequent // query(s) take advantage of that intel. -type peerSelector struct { +type rankPooledPeerSelector struct { mu deadlock.Mutex net peersRetriever - // peerClasses is the list of peer classes we want to have in the peerSelector. + // peerClasses is the list of peer classes we want to have in the rankPooledPeerSelector. peerClasses []peerClass // pools is the list of peer pools, each pool contains a list of peers with the same rank. pools []peerPool @@ -284,9 +290,9 @@ func (hs *historicStats) push(value int, counter uint64, class peerClass) (avera return bounded } -// makePeerSelector creates a peerSelector, given a peersRetriever and peerClass array. -func makePeerSelector(net peersRetriever, initialPeersClasses []peerClass) *peerSelector { - selector := &peerSelector{ +// makeRankPooledPeerSelector creates a rankPooledPeerSelector, given a peersRetriever and peerClass array. +func makeRankPooledPeerSelector(net peersRetriever, initialPeersClasses []peerClass) *rankPooledPeerSelector { + selector := &rankPooledPeerSelector{ net: net, peerClasses: initialPeersClasses, } @@ -296,7 +302,7 @@ func makePeerSelector(net peersRetriever, initialPeersClasses []peerClass) *peer // getNextPeer returns the next peer. It randomally selects a peer from a pool that has // the lowest rank value. Given that the peers are grouped by their ranks, allow us to // prioritize peers based on their class and/or performance. -func (ps *peerSelector) getNextPeer() (psp *peerSelectorPeer, err error) { +func (ps *rankPooledPeerSelector) getNextPeer() (psp *peerSelectorPeer, err error) { ps.mu.Lock() defer ps.mu.Unlock() ps.refreshAvailablePeers() @@ -317,7 +323,7 @@ func (ps *peerSelector) getNextPeer() (psp *peerSelectorPeer, err error) { // rankPeer ranks a given peer. // return the old value and the new updated value. // updated value could be different from the input rank. -func (ps *peerSelector) rankPeer(psp *peerSelectorPeer, rank int) (int, int) { +func (ps *rankPooledPeerSelector) rankPeer(psp *peerSelectorPeer, rank int) (int, int) { if psp == nil { return -1, -1 } @@ -384,7 +390,7 @@ func (ps *peerSelector) rankPeer(psp *peerSelectorPeer, rank int) (int, int) { } // peerDownloadDurationToRank calculates the rank for a peer given a peer and the block download time. -func (ps *peerSelector) peerDownloadDurationToRank(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) { +func (ps *rankPooledPeerSelector) peerDownloadDurationToRank(psp *peerSelectorPeer, blockDownloadDuration time.Duration) (rank int) { ps.mu.Lock() defer ps.mu.Unlock() poolIdx, peerIdx := ps.findPeer(psp) @@ -409,7 +415,7 @@ func (ps *peerSelector) peerDownloadDurationToRank(psp *peerSelectorPeer, blockD // addToPool adds a given peer to the correct group. If no group exists for that peer's rank, // a new group is created. // The method return true if a new group was created ( suggesting that the pools list would need to be re-ordered ), or false otherwise. -func (ps *peerSelector) addToPool(peer network.Peer, rank int, class peerClass, peerHistory *historicStats) bool { +func (ps *rankPooledPeerSelector) addToPool(peer network.Peer, rank int, class peerClass, peerHistory *historicStats) bool { // see if we already have a list with that rank: for i, pool := range ps.pools { if pool.rank == rank { @@ -423,7 +429,7 @@ func (ps *peerSelector) addToPool(peer network.Peer, rank int, class peerClass, } // sort the pools array in an ascending order according to the rank of each pool. -func (ps *peerSelector) sort() { +func (ps *rankPooledPeerSelector) sort() { sort.SliceStable(ps.pools, func(i, j int) bool { return ps.pools[i].rank < ps.pools[j].rank }) @@ -443,7 +449,7 @@ func peerAddress(peer network.Peer) string { // refreshAvailablePeers reload the available peers from the network package, add new peers along with their // corresponding initial rank, and deletes peers that have been dropped by the network package. -func (ps *peerSelector) refreshAvailablePeers() { +func (ps *rankPooledPeerSelector) refreshAvailablePeers() { existingPeers := make(map[network.PeerOption]map[string]bool) for _, pool := range ps.pools { for _, localPeer := range pool.peers { @@ -501,7 +507,7 @@ func (ps *peerSelector) refreshAvailablePeers() { // findPeer look into the peer pool and find the given peer. // The method returns the pool and peer indices if a peer was found, or (-1, -1) otherwise. -func (ps *peerSelector) findPeer(psp *peerSelectorPeer) (poolIdx, peerIdx int) { +func (ps *rankPooledPeerSelector) findPeer(psp *peerSelectorPeer) (poolIdx, peerIdx int) { peerAddr := peerAddress(psp.Peer) if peerAddr == "" { return -1, -1 diff --git a/catchup/peerSelector_test.go b/catchup/peerSelector_test.go index aa8d348d43..7aa373d280 100644 --- a/catchup/peerSelector_test.go +++ b/catchup/peerSelector_test.go @@ -131,7 +131,7 @@ func TestPeerSelector_RankPeer(t *testing.T) { peers := []network.Peer{&mockHTTPPeer{address: "12345"}} - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) []network.Peer { return peers }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}}, @@ -191,7 +191,7 @@ func TestPeerSelector_PeerDownloadRanking(t *testing.T) { peers1 := []network.Peer{&mockHTTPPeer{address: "1234"}, &mockHTTPPeer{address: "5678"}} peers2 := []network.Peer{&mockHTTPPeer{address: "abcd"}, &mockHTTPPeer{address: "efgh"}} - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { if opt == network.PeersPhonebookArchivalNodes { @@ -240,7 +240,7 @@ func TestPeerSelector_FindMissingPeer(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) []network.Peer { return []network.Peer{} }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}}, @@ -258,7 +258,7 @@ func TestPeerSelector_HistoricData(t *testing.T) { peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}, &mockHTTPPeer{address: "a2"}, &mockHTTPPeer{address: "a3"}} peers2 := []network.Peer{&mockHTTPPeer{address: "b1"}, &mockHTTPPeer{address: "b2"}} - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { if opt == network.PeersPhonebookArchivalNodes { @@ -332,7 +332,7 @@ func TestPeerSelector_PeersDownloadFailed(t *testing.T) { peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}, &mockHTTPPeer{address: "a2"}, &mockHTTPPeer{address: "a3"}} peers2 := []network.Peer{&mockHTTPPeer{address: "b1"}, &mockHTTPPeer{address: "b2"}} - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { if opt == network.PeersPhonebookArchivalNodes { @@ -408,7 +408,7 @@ func TestPeerSelector_Penalty(t *testing.T) { peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}, &mockHTTPPeer{address: "a2"}, &mockHTTPPeer{address: "a3"}} peers2 := []network.Peer{&mockHTTPPeer{address: "b1"}, &mockHTTPPeer{address: "b2"}} - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { if opt == network.PeersPhonebookArchivalNodes { @@ -469,7 +469,7 @@ func TestPeerSelector_PeerDownloadDurationToRank(t *testing.T) { peers3 := []network.Peer{&mockHTTPPeer{address: "c1"}, &mockHTTPPeer{address: "c2"}} peers4 := []network.Peer{&mockHTTPPeer{address: "d1"}, &mockHTTPPeer{address: "b2"}} - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { if opt == network.PeersPhonebookRelays { @@ -574,7 +574,7 @@ func TestPeerSelector_ClassUpperBound(t *testing.T) { peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}, &mockHTTPPeer{address: "a2"}} pClass := peerClass{initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes} - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { if opt == network.PeersPhonebookArchivalNodes { @@ -609,7 +609,7 @@ func TestPeerSelector_ClassLowerBound(t *testing.T) { peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}, &mockHTTPPeer{address: "a2"}} pClass := peerClass{initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes} - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { if opt == network.PeersPhonebookArchivalNodes { @@ -639,7 +639,7 @@ func TestPeerSelector_EvictionAndUpgrade(t *testing.T) { peers1 := []network.Peer{&mockHTTPPeer{address: "a1"}} peers2 := []network.Peer{&mockHTTPPeer{address: "a1"}} - peerSelector := makePeerSelector( + peerSelector := makeRankPooledPeerSelector( makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) { for _, opt := range options { if opt == network.PeersPhonebookArchivalNodes { @@ -677,7 +677,7 @@ func TestPeerSelector_RefreshAvailablePeers(t *testing.T) { // check new peers added to the pool p1 := mockHTTPPeer{address: "p1"} p2 := mockHTTPPeer{address: "p2"} - ps := peerSelector{ + ps := rankPooledPeerSelector{ peerClasses: []peerClass{ {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}, diff --git a/catchup/service.go b/catchup/service.go index 58fc3ae6b8..5c6609b236 100644 --- a/catchup/service.go +++ b/catchup/service.go @@ -38,10 +38,7 @@ import ( "github.com/algorand/go-algorand/util/execpool" ) -const catchupPeersForSync = 10 -const blockQueryPeerLimit = 10 - -// uncapParallelDownloadRate is a simple threshold to detect whether or not the node is caught up. +// uncapParallelDownloadRate is a simple threshold to detect whether the node is caught up. // If a block is downloaded in less than this duration, it's assumed that the node is not caught up // and allow the block downloader to start N=parallelBlocks concurrent fetches. const uncapParallelDownloadRate = time.Second @@ -76,7 +73,7 @@ type Ledger interface { WaitMem(r basics.Round) chan struct{} } -// Service represents the catchup service. Once started and until it is stopped, it ensures that the ledger is up to date with network. +// Service represents the catchup service. Once started and until it is stopped, it ensures that the ledger is up-to-date with network. type Service struct { // disableSyncRound, provided externally, is the first round we will _not_ fetch from the network // any round >= disableSyncRound will not be fetched. If set to 0, it will be disregarded. @@ -266,7 +263,7 @@ const errNoBlockForRoundThreshold = 5 // - If we couldn't fetch the block (e.g. if there are no peers available, or we've reached the catchupRetryLimit) // - If the block is already in the ledger (e.g. if agreement service has already written it) // - If the retrieval of the previous block was unsuccessful -func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCompleteChan chan struct{}, lookbackComplete chan struct{}, peerSelector *peerSelector) bool { +func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCompleteChan chan struct{}, lookbackComplete chan struct{}, peerSelector peerSelector) bool { // If sync-ing this round is not intended, don't fetch it if dontSyncRound := s.GetDisableSyncRound(); dontSyncRound != 0 && r >= basics.Round(dontSyncRound) { return false @@ -318,7 +315,7 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo block, cert, blockDownloadDuration, err := s.innerFetch(ctx, r, peer) if err != nil { - if err == errLedgerAlreadyHasBlock { + if errors.Is(err, errLedgerAlreadyHasBlock) { // ledger already has the block, no need to request this block. // only the agreement could have added this block into the ledger, catchup is complete s.log.Infof("fetchAndWrite(%d): the block is already in the ledger. The catchup is complete", r) @@ -329,7 +326,7 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo if errors.As(err, &nbfe) { failureRank = peerRankNoBlockForRound // remote peer doesn't have the block, try another peer - // quit if the the same peer peer encountered errNoBlockForRound more than errNoBlockForRoundThreshold times + // quit if the same peer encountered errNoBlockForRound more than errNoBlockForRoundThreshold times if s.followLatest { // back off between retries to allow time for the next block to appear; // this will provide 50s (catchupRetryLimit * followLatestBackoff) of @@ -427,7 +424,8 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo // if the context expired, just exit. return false } - if errNSBE, ok := err.(ledgercore.ErrNonSequentialBlockEval); ok && errNSBE.EvaluatorRound <= errNSBE.LatestRound { + var errNSBE ledgercore.ErrNonSequentialBlockEval + if errors.As(err, &errNSBE) && errNSBE.EvaluatorRound <= errNSBE.LatestRound { // the block was added to the ledger from elsewhere after fetching it here // only the agreement could have added this block into the ledger, catchup is complete s.log.Infof("fetchAndWrite(%d): after fetching the block, it is already in the ledger. The catchup is complete", r) @@ -442,16 +440,19 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo } if err != nil { - switch err.(type) { - case ledgercore.ErrNonSequentialBlockEval: + var errNonSequentialBlockEval ledgercore.ErrNonSequentialBlockEval + var blockInLedgerError ledgercore.BlockInLedgerError + var protocolErr protocol.Error + switch { + case errors.As(err, &errNonSequentialBlockEval): s.log.Infof("fetchAndWrite(%d): no need to re-evaluate historical block", r) return true - case ledgercore.BlockInLedgerError: + case errors.As(err, &blockInLedgerError): // the block was added to the ledger from elsewhere after fetching it here // only the agreement could have added this block into the ledger, catchup is complete s.log.Infof("fetchAndWrite(%d): after fetching the block, it is already in the ledger. The catchup is complete", r) return false - case protocol.Error: + case errors.As(err, &protocolErr): if !s.protocolErrorLogged { logging.Base().Errorf("fetchAndWrite(%v): unrecoverable protocol error detected: %v", r, err) s.protocolErrorLogged = true @@ -491,8 +492,8 @@ func (s *Service) pipelinedFetch(seedLookback uint64) { } }() - peerSelector := createPeerSelector(s.net, s.cfg, true) - if _, err := peerSelector.getNextPeer(); err == errPeerSelectorNoPeerPoolsAvailable { + ps := createPeerSelector(s.net) + if _, err := ps.getNextPeer(); err != nil { s.log.Debugf("pipelinedFetch: was unable to obtain a peer to retrieve the block from") return } @@ -527,7 +528,7 @@ func (s *Service) pipelinedFetch(seedLookback uint64) { go func(r basics.Round) { prev := s.ledger.WaitMem(r - 1) seed := s.ledger.WaitMem(r.SubSaturate(basics.Round(seedLookback))) - done <- s.fetchAndWrite(ctx, r, prev, seed, peerSelector) + done <- s.fetchAndWrite(ctx, r, prev, seed, ps) wg.Done() }(nextRound) @@ -751,9 +752,9 @@ func (s *Service) fetchRound(cert agreement.Certificate, verifier *agreement.Asy peerErrors := map[network.Peer]int{} blockHash := bookkeeping.BlockHash(cert.Proposal.BlockDigest) // semantic digest (i.e., hash of the block header), not byte-for-byte digest - peerSelector := createPeerSelector(s.net, s.cfg, false) + ps := createPeerSelector(s.net) for s.ledger.LastRound() < cert.Round { - psp, getPeerErr := peerSelector.getNextPeer() + psp, getPeerErr := ps.getNextPeer() if getPeerErr != nil { s.log.Debugf("fetchRound: was unable to obtain a peer to retrieve the block from") s.net.RequestConnectOutgoing(true, s.ctx.Done()) @@ -783,19 +784,19 @@ func (s *Service) fetchRound(cert agreement.Certificate, verifier *agreement.Asy time.Sleep(50 * time.Millisecond) } if count > errNoBlockForRoundThreshold*10 { - // for the low number of connected peers (like 2) the following scenatio is possible: + // for the low number of connected peers (like 2) the following scenario is possible: // - both peers do not have the block // - peer selector punishes one of the peers more than the other - // - the punoshed peer gets the block, and the less punished peer stucks. + // - the punished peer gets the block, and the less punished peer stucks. // It this case reset the peer selector to let it re-learn priorities. - peerSelector = createPeerSelector(s.net, s.cfg, false) + ps = createPeerSelector(s.net) } } peerErrors[peer]++ } // remote peer doesn't have the block, try another peer logging.Base().Warnf("fetchRound could not acquire block, fetcher errored out: %v", err) - peerSelector.rankPeer(psp, failureRank) + ps.rankPeer(psp, failureRank) continue } @@ -805,7 +806,7 @@ func (s *Service) fetchRound(cert agreement.Certificate, verifier *agreement.Asy } // Otherwise, fetcher gave us the wrong block logging.Base().Warnf("fetcher gave us bad/wrong block (for round %d): fetched hash %v; want hash %v", cert.Round, block.Hash(), blockHash) - peerSelector.rankPeer(psp, peerRankInvalidDownload) + ps.rankPeer(psp, peerRankInvalidDownload) // As a failsafe, if the cert we fetched is valid but for the wrong block, panic as loudly as possible if cert.Round == fetchedCert.Round && @@ -866,38 +867,33 @@ func (s *Service) roundIsNotSupported(nextRound basics.Round) bool { return true } -func createPeerSelector(net network.GossipNode, cfg config.Local, pipelineFetch bool) *peerSelector { - var peerClasses []peerClass - if pipelineFetch { - if cfg.NetAddress != "" && cfg.EnableGossipService { // Relay node - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersConnectedIn}, - } - } else { - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, - } - } - } else { - if cfg.NetAddress != "" && cfg.EnableGossipService { // Relay node - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersConnectedIn}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialFourthPriority, peerClass: network.PeersPhonebookRelays}, - } - } else { - peerClasses = []peerClass{ - {initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}, - {initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}, - {initialRank: peerRankInitialThirdPriority, peerClass: network.PeersPhonebookRelays}, - } - } +func createPeerSelector(net network.GossipNode) peerSelector { + wrappedPeerSelectors := []*wrappedPeerSelector{ + { + peerClass: network.PeersConnectedOut, + peerSelector: makeRankPooledPeerSelector(net, + []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedOut}}), + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookRelays, + peerSelector: makeRankPooledPeerSelector(net, + []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookRelays}}), + toleranceFactor: 3, + }, + { + peerClass: network.PeersPhonebookArchivalNodes, + peerSelector: makeRankPooledPeerSelector(net, + []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}}), + toleranceFactor: 10, + }, + { + peerClass: network.PeersConnectedIn, + peerSelector: makeRankPooledPeerSelector(net, + []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersConnectedIn}}), + toleranceFactor: 3, + }, } - return makePeerSelector(net, peerClasses) + + return makeClassBasedPeerSelector(wrappedPeerSelectors) } diff --git a/catchup/service_test.go b/catchup/service_test.go index 8deb692b0d..045a0438f2 100644 --- a/catchup/service_test.go +++ b/catchup/service_test.go @@ -958,102 +958,23 @@ func TestCatchupUnmatchedCertificate(t *testing.T) { func TestCreatePeerSelector(t *testing.T) { partitiontest.PartitionTest(t) - // Make Service - cfg := defaultConfig + s := MakeService(logging.Base(), defaultConfig, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) + ps := createPeerSelector(s.net) + + cps, ok := ps.(*classBasedPeerSelector) + require.True(t, ok) + + require.Equal(t, 4, len(cps.peerSelectors)) + + require.Equal(t, network.PeersConnectedOut, cps.peerSelectors[0].peerClass) + require.Equal(t, network.PeersPhonebookRelays, cps.peerSelectors[1].peerClass) + require.Equal(t, network.PeersPhonebookArchivalNodes, cps.peerSelectors[2].peerClass) + require.Equal(t, network.PeersConnectedIn, cps.peerSelectors[3].peerClass) - // cfg.NetAddress != ""; cfg.EnableGossipService = true; pipelineFetch = true - cfg.NetAddress = "someAddress" - cfg.EnableGossipService = true - 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, 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.PeersPhonebookArchivalNodes, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) - require.Equal(t, network.PeersConnectedIn, ps.peerClasses[3].peerClass) - - // cfg.NetAddress == ""; cfg.EnableGossipService = true; pipelineFetch = true - cfg.NetAddress = "" - cfg.EnableGossipService = true - 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, 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.PeersPhonebookArchivalNodes, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersConnectedOut, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) - - // cfg.NetAddress != ""; cfg.EnableGossipService = false; pipelineFetch = true - cfg.NetAddress = "someAddress" - cfg.EnableGossipService = false - 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, 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.PeersPhonebookArchivalNodes, ps.peerClasses[0].peerClass) - require.Equal(t, network.PeersConnectedOut, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) - - // cfg.NetAddress != ""; cfg.EnableGossipService = true; pipelineFetch = false - cfg.NetAddress = "someAddress" - cfg.EnableGossipService = true - 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, 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.PeersPhonebookArchivalNodes, ps.peerClasses[2].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[3].peerClass) - - // cfg.NetAddress == ""; cfg.EnableGossipService = true; pipelineFetch = false - cfg.NetAddress = "" - cfg.EnableGossipService = true - 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, 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.PeersPhonebookArchivalNodes, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) - - // cfg.NetAddress != ""; cfg.EnableGossipService = false; pipelineFetch = false - cfg.NetAddress = "someAddress" - cfg.EnableGossipService = false - 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, 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.PeersPhonebookArchivalNodes, ps.peerClasses[1].peerClass) - require.Equal(t, network.PeersPhonebookRelays, ps.peerClasses[2].peerClass) + require.Equal(t, 3, cps.peerSelectors[0].toleranceFactor) + require.Equal(t, 3, cps.peerSelectors[1].toleranceFactor) + require.Equal(t, 10, cps.peerSelectors[2].toleranceFactor) + require.Equal(t, 3, cps.peerSelectors[3].toleranceFactor) } func TestServiceStartStop(t *testing.T) { From 787f758f97985d1cf72a3f4d18a24c16499234e8 Mon Sep 17 00:00:00 2001 From: Gary <982483+gmalouf@users.noreply.github.com> Date: Fri, 23 Feb 2024 14:00:47 -0500 Subject: [PATCH 10/16] Network: Archival node DNS Resolution (#5940) --- catchup/peerSelector.go | 4 +-- config/localTemplate.go | 1 + network/gossipNode.go | 2 -- network/phonebook.go | 6 ++-- network/phonebook_test.go | 6 ++-- network/wsNetwork.go | 60 ++++++++++++++++++--------------------- network/wsNetwork_test.go | 27 +++++++++--------- 7 files changed, 51 insertions(+), 55 deletions(-) diff --git a/catchup/peerSelector.go b/catchup/peerSelector.go index 1485295581..05556bb24b 100644 --- a/catchup/peerSelector.go +++ b/catchup/peerSelector.go @@ -29,12 +29,12 @@ import ( ) const ( - // peerRankInitialFirstPriority is the high-priority peers group ( typically, archivers ) + // peerRankInitialFirstPriority is the high-priority peers group peerRankInitialFirstPriority = 0 peerRank0LowBlockTime = 1 peerRank0HighBlockTime = 199 - // peerRankInitialSecondPriority is the second priority peers group ( typically, relays ) + // peerRankInitialSecondPriority is the second priority peers group peerRankInitialSecondPriority = 200 peerRank1LowBlockTime = 201 peerRank1HighBlockTime = 399 diff --git a/config/localTemplate.go b/config/localTemplate.go index 618facfd29..ce4294de01 100644 --- a/config/localTemplate.go +++ b/config/localTemplate.go @@ -171,6 +171,7 @@ type Local struct { RestWriteTimeoutSeconds int `version[4]:"120"` // DNSBootstrapID specifies the names of a set of DNS SRV records that identify the set of nodes available to connect to. + // This is applicable to both relay and archival nodes - they are assumed to use the same DNSBootstrapID today. // When resolving the bootstrap ID will be replaced by the genesis block's network name. This string uses a URL // parsing library and supports optional backup and dedup parameters. 'backup' is used to provide a second DNS entry to use // in case the primary is unavailable. dedup is intended to be used to deduplicate SRV records returned from the primary diff --git a/network/gossipNode.go b/network/gossipNode.go index 7a916fda36..3ac5cc7df0 100644 --- a/network/gossipNode.go +++ b/network/gossipNode.go @@ -42,8 +42,6 @@ const ( PeersPhonebookRelays PeerOption = iota // PeersPhonebookArchivalNodes specifies all archival nodes (relay or p2p) PeersPhonebookArchivalNodes PeerOption = iota - // PeersPhonebookArchivers specifies all archivers in the phonebook - PeersPhonebookArchivers PeerOption = iota ) // GossipNode represents a node in the gossip network diff --git a/network/phonebook.go b/network/phonebook.go index 3f196e0605..0c431fd2f3 100644 --- a/network/phonebook.go +++ b/network/phonebook.go @@ -30,7 +30,7 @@ import ( const getAllAddresses = math.MaxInt32 // PhoneBookEntryRoles defines the roles that a single entry on the phonebook can take. -// currently, we have two roles : relay role and archiver role, which are mutually exclusive. +// currently, we have two roles : relay role and archival role, which are mutually exclusive. // //msgp:ignore PhoneBookEntryRoles type PhoneBookEntryRoles int @@ -39,8 +39,8 @@ type PhoneBookEntryRoles int // or via a configuration file. const PhoneBookEntryRelayRole = 1 -// PhoneBookEntryArchiverRole used for all the archivers that are provided via the archive SRV record. -const PhoneBookEntryArchiverRole = 2 +// PhoneBookEntryArchivalRole used for all the archival nodes that are provided via the archive SRV record. +const PhoneBookEntryArchivalRole = 2 // Phonebook stores or looks up addresses of nodes we might contact type Phonebook interface { diff --git a/network/phonebook_test.go b/network/phonebook_test.go index 36365c5916..2643e722ea 100644 --- a/network/phonebook_test.go +++ b/network/phonebook_test.go @@ -346,11 +346,11 @@ func TestPhonebookRoles(t *testing.T) { ph := MakePhonebook(1, 1).(*phonebookImpl) ph.ReplacePeerList(relaysSet, "default", PhoneBookEntryRelayRole) - ph.ReplacePeerList(archiverSet, "default", PhoneBookEntryArchiverRole) + ph.ReplacePeerList(archiverSet, "default", PhoneBookEntryArchivalRole) require.Equal(t, len(relaysSet)+len(archiverSet), len(ph.data)) require.Equal(t, len(relaysSet)+len(archiverSet), ph.Length()) - for _, role := range []PhoneBookEntryRoles{PhoneBookEntryRelayRole, PhoneBookEntryArchiverRole} { + for _, role := range []PhoneBookEntryRoles{PhoneBookEntryRelayRole, PhoneBookEntryArchivalRole} { for k := 0; k < 100; k++ { for l := 0; l < 3; l++ { entries := ph.GetAddresses(l, role) @@ -358,7 +358,7 @@ func TestPhonebookRoles(t *testing.T) { for _, entry := range entries { require.Contains(t, entry, "relay") } - } else if role == PhoneBookEntryArchiverRole { + } else if role == PhoneBookEntryArchivalRole { for _, entry := range entries { require.Contains(t, entry, "archiver") } diff --git a/network/wsNetwork.go b/network/wsNetwork.go index 92a02976b4..295bd71155 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -548,15 +548,7 @@ func (wn *WebsocketNetwork) GetPeers(options ...PeerOption) []Peer { } case PeersPhonebookArchivalNodes: var addrs []string - addrs = wn.phonebook.GetAddresses(1000, PhoneBookEntryRelayRole) - for _, addr := range addrs { - peerCore := makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, 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 - addrs = wn.phonebook.GetAddresses(1000, PhoneBookEntryArchiverRole) + addrs = wn.phonebook.GetAddresses(1000, PhoneBookEntryArchivalRole) for _, addr := range addrs { peerCore := makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, addr, wn.GetRoundTripper(), "" /*origin address*/) outPeers = append(outPeers, &peerCore) @@ -1607,15 +1599,17 @@ func (wn *WebsocketNetwork) refreshRelayArchivePhonebookAddresses() { dnsBootstrapArray := wn.config.DNSBootstrapArray(wn.NetworkID) for _, dnsBootstrap := range dnsBootstrapArray { - primaryRelayAddrs, primaryArchiveAddrs := wn.getDNSAddrs(dnsBootstrap.PrimarySRVBootstrap) + primaryRelayAddrs, primaryArchivalAddrs := wn.getDNSAddrs(dnsBootstrap.PrimarySRVBootstrap) if dnsBootstrap.BackupSRVBootstrap != "" { - backupRelayAddrs, backupArchiveAddrs := wn.getDNSAddrs(dnsBootstrap.BackupSRVBootstrap) - dedupedRelayAddresses := wn.mergePrimarySecondaryRelayAddressSlices(wn.NetworkID, primaryRelayAddrs, + backupRelayAddrs, backupArchivalAddrs := wn.getDNSAddrs(dnsBootstrap.BackupSRVBootstrap) + dedupedRelayAddresses := wn.mergePrimarySecondaryAddressSlices(primaryRelayAddrs, backupRelayAddrs, dnsBootstrap.DedupExp) - wn.updatePhonebookAddresses(dedupedRelayAddresses, append(primaryArchiveAddrs, backupArchiveAddrs...)) + dedupedArchivalAddresses := wn.mergePrimarySecondaryAddressSlices(primaryArchivalAddrs, + backupArchivalAddrs, dnsBootstrap.DedupExp) + wn.updatePhonebookAddresses(dedupedRelayAddresses, dedupedArchivalAddresses) } else { - wn.updatePhonebookAddresses(primaryRelayAddrs, primaryArchiveAddrs) + wn.updatePhonebookAddresses(primaryRelayAddrs, primaryArchivalAddrs) } } } @@ -1628,7 +1622,9 @@ func (wn *WebsocketNetwork) updatePhonebookAddresses(relayAddrs []string, archiv wn.log.Infof("got no relay DNS addrs for network %s", wn.NetworkID) } if len(archiveAddrs) > 0 { - wn.phonebook.ReplacePeerList(archiveAddrs, string(wn.NetworkID), PhoneBookEntryArchiverRole) + wn.phonebook.ReplacePeerList(archiveAddrs, string(wn.NetworkID), PhoneBookEntryArchivalRole) + } else { + wn.log.Infof("got no archive DNS addrs for network %s", wn.NetworkID) } } @@ -1846,46 +1842,46 @@ func (wn *WebsocketNetwork) prioWeightRefresh() { } } -// This logic assumes that the relay address suffixes +// This logic assumes that the address suffixes // correspond to the primary/backup network conventions. If this proves to be false, i.e. one network's // suffix is a substring of another network's suffix, then duplicates can end up in the merged slice. -func (wn *WebsocketNetwork) mergePrimarySecondaryRelayAddressSlices(network protocol.NetworkID, - primaryRelayAddresses []string, secondaryRelayAddresses []string, dedupExp *regexp.Regexp) (dedupedRelayAddresses []string) { +func (wn *WebsocketNetwork) mergePrimarySecondaryAddressSlices( + primaryAddresses []string, secondaryAddresses []string, dedupExp *regexp.Regexp) (dedupedAddresses []string) { if dedupExp == nil { // No expression provided, so just append the slices without deduping - return append(primaryRelayAddresses, secondaryRelayAddresses...) + return append(primaryAddresses, secondaryAddresses...) } - var relayAddressPrefixToValue = make(map[string]string, 2*len(primaryRelayAddresses)) + var addressPrefixToValue = make(map[string]string, 2*len(primaryAddresses)) - for _, pra := range primaryRelayAddresses { + for _, pra := range primaryAddresses { var normalizedPra = strings.ToLower(pra) var pfxKey = dedupExp.ReplaceAllString(normalizedPra, "") - if _, exists := relayAddressPrefixToValue[pfxKey]; !exists { - relayAddressPrefixToValue[pfxKey] = normalizedPra + if _, exists := addressPrefixToValue[pfxKey]; !exists { + addressPrefixToValue[pfxKey] = normalizedPra } } - for _, sra := range secondaryRelayAddresses { + for _, sra := range secondaryAddresses { var normalizedSra = strings.ToLower(sra) var pfxKey = dedupExp.ReplaceAllString(normalizedSra, "") - if _, exists := relayAddressPrefixToValue[pfxKey]; !exists { - relayAddressPrefixToValue[pfxKey] = normalizedSra + if _, exists := addressPrefixToValue[pfxKey]; !exists { + addressPrefixToValue[pfxKey] = normalizedSra } } - dedupedRelayAddresses = make([]string, 0, len(relayAddressPrefixToValue)) - for _, value := range relayAddressPrefixToValue { - dedupedRelayAddresses = append(dedupedRelayAddresses, value) + dedupedAddresses = make([]string, 0, len(addressPrefixToValue)) + for _, value := range addressPrefixToValue { + dedupedAddresses = append(dedupedAddresses, value) } return } -func (wn *WebsocketNetwork) getDNSAddrs(dnsBootstrap string) (relaysAddresses []string, archiverAddresses []string) { +func (wn *WebsocketNetwork) getDNSAddrs(dnsBootstrap string) (relaysAddresses []string, archivalAddresses []string) { var err error relaysAddresses, err = wn.resolveSRVRecords(wn.ctx, "algobootstrap", "tcp", dnsBootstrap, wn.config.FallbackDNSResolverAddress, wn.config.DNSSecuritySRVEnforced()) if err != nil { @@ -1896,13 +1892,13 @@ func (wn *WebsocketNetwork) getDNSAddrs(dnsBootstrap string) (relaysAddresses [] relaysAddresses = nil } - archiverAddresses, err = wn.resolveSRVRecords(wn.ctx, "archive", "tcp", dnsBootstrap, wn.config.FallbackDNSResolverAddress, wn.config.DNSSecuritySRVEnforced()) + archivalAddresses, err = wn.resolveSRVRecords(wn.ctx, "archive", "tcp", dnsBootstrap, wn.config.FallbackDNSResolverAddress, wn.config.DNSSecuritySRVEnforced()) if err != nil { // only log this warning on testnet or devnet if wn.NetworkID == config.Devnet || wn.NetworkID == config.Testnet { wn.log.Warnf("Cannot lookup archive SRV record for %s: %v", dnsBootstrap, err) } - archiverAddresses = nil + archivalAddresses = nil } return } diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go index ef5769bcff..8daf4d196f 100644 --- a/network/wsNetwork_test.go +++ b/network/wsNetwork_test.go @@ -1183,6 +1183,9 @@ func TestGetPeers(t *testing.T) { phbMulti.ReplacePeerList([]string{"a", "b", "c"}, "ph", PhoneBookEntryRelayRole) + // A few for archival node roles + phbMulti.ReplacePeerList([]string{"d", "e", "f"}, "ph", PhoneBookEntryArchivalRole) + //addrB, _ := netB.Address() // A has only an inbound connection from B @@ -1206,14 +1209,13 @@ func TestGetPeers(t *testing.T) { 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) + assert.Equal(t, []string{"d", "e", "f"}, peerAddrs2) } @@ -4176,7 +4178,7 @@ func TestRefreshRelayArchivePhonebookAddresses(t *testing.T) { relayPeers := netA.GetPeers(PeersPhonebookRelays) assert.Equal(t, 0, len(relayPeers)) - archivePeers := netA.GetPeers(PeersPhonebookArchivers) + archivePeers := netA.GetPeers(PeersPhonebookArchivalNodes) assert.Equal(t, 0, len(archivePeers)) netA.refreshRelayArchivePhonebookAddresses() @@ -4191,17 +4193,16 @@ func TestRefreshRelayArchivePhonebookAddresses(t *testing.T) { assert.ElementsMatch(t, primaryRelayResolvedRecords, relayAddrs) - archivePeers = netA.GetPeers(PeersPhonebookArchivers) + archivePeers = netA.GetPeers(PeersPhonebookArchivalNodes) - // TODO: For the time being, we do not dedup resolved archive nodes - assert.Equal(t, len(primaryArchiveResolvedRecords)+len(secondaryArchiveResolvedRecords), len(archivePeers)) + assert.Equal(t, 3, len(archivePeers)) archiveAddrs := make([]string, 0, len(archivePeers)) for _, peer := range archivePeers { archiveAddrs = append(archiveAddrs, peer.(HTTPPeer).GetAddress()) } - assert.ElementsMatch(t, append(primaryArchiveResolvedRecords, secondaryArchiveResolvedRecords...), archiveAddrs) + assert.ElementsMatch(t, primaryArchiveResolvedRecords, archiveAddrs) }) } @@ -4219,7 +4220,7 @@ func TestUpdatePhonebookAddresses(t *testing.T) { relayPeers := netA.GetPeers(PeersPhonebookRelays) assert.Equal(t, 0, len(relayPeers)) - archivePeers := netA.GetPeers(PeersPhonebookArchivers) + archivePeers := netA.GetPeers(PeersPhonebookArchivalNodes) assert.Equal(t, 0, len(archivePeers)) domainGen := rapidgen.Domain() @@ -4248,7 +4249,7 @@ func TestUpdatePhonebookAddresses(t *testing.T) { assert.ElementsMatch(t, dedupedRelayDomains, relayAddrs) - archivePeers = netA.GetPeers(PeersPhonebookArchivers) + archivePeers = netA.GetPeers(PeersPhonebookArchivalNodes) assert.Equal(t, len(dedupedArchiveDomains), len(archivePeers)) archiveAddrs := make([]string, 0, len(archivePeers)) @@ -4288,7 +4289,7 @@ func TestUpdatePhonebookAddresses(t *testing.T) { assert.ElementsMatch(t, dedupedRelayDomains, relayAddrs) - archivePeers = netA.GetPeers(PeersPhonebookArchivers) + archivePeers = netA.GetPeers(PeersPhonebookArchivalNodes) assert.Equal(t, len(dedupedArchiveDomains), len(archivePeers)) archiveAddrs = nil @@ -4349,7 +4350,7 @@ func TestMergePrimarySecondaryRelayAddressListsMinOverlap(t *testing.T) { primaryRelayAddresses := domainsGen.Draw(t1, "primaryRelayAddresses") secondaryRelayAddresses := domainsGen.Draw(t1, "secondaryRelayAddresses") - mergedRelayAddresses := netA.mergePrimarySecondaryRelayAddressSlices(protocol.NetworkID(network), + mergedRelayAddresses := netA.mergePrimarySecondaryAddressSlices( primaryRelayAddresses, secondaryRelayAddresses, dedupExp) expectedRelayAddresses := removeDuplicateStr(append(primaryRelayAddresses, secondaryRelayAddresses...), true) @@ -4402,7 +4403,7 @@ func TestMergePrimarySecondaryRelayAddressListsPartialOverlap(t *testing.T) { } secondaryRelayAddresses = append(secondaryRelayAddresses, extraSecondaryRelayAddresses...) - mergedRelayAddresses := netA.mergePrimarySecondaryRelayAddressSlices(network, + mergedRelayAddresses := netA.mergePrimarySecondaryAddressSlices( primaryRelayAddresses, secondaryRelayAddresses, dedupExp) // We expect the primary addresses to take precedence over a "matching" secondary address, extra non-duplicate @@ -4445,7 +4446,7 @@ func TestMergePrimarySecondaryRelayAddressListsNoDedupExp(t *testing.T) { generatedSecondaryRelayAddresses := secondaryDomainsGen.Draw(t1, "secondaryRelayAddresses") secondaryRelayAddresses = append(secondaryRelayAddresses, generatedSecondaryRelayAddresses...) - mergedRelayAddresses := netA.mergePrimarySecondaryRelayAddressSlices(protocol.NetworkID(network), + mergedRelayAddresses := netA.mergePrimarySecondaryAddressSlices( primaryRelayAddresses, secondaryRelayAddresses, nil) // We expect non deduplication, so all addresses _should_ be present (note that no lower casing happens either) From e7ee984cea957f2c59f344f0bb10f08bb58d8820 Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Wed, 6 Mar 2024 13:06:10 -0500 Subject: [PATCH 11/16] agreement: update voteValidatedAt description in comment (#5945) --- agreement/actions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agreement/actions.go b/agreement/actions.go index 9a6fa7138e..c174233103 100644 --- a/agreement/actions.go +++ b/agreement/actions.go @@ -232,7 +232,7 @@ type ensureAction struct { Payload proposal // the certificate proving commitment Certificate Certificate - // The time that the winning proposal-vote was validated for round credentialRoundLag back from the current one + // The time that the lowest proposal-vote was validated for `credentialRoundLag` rounds ago (R-credentialRoundLag). This may not have been the winning proposal, since we wait `credentialRoundLag` rounds to see if there was a better one. voteValidatedAt time.Duration // The dynamic filter timeout calculated for this round, even if not enabled, for reporting to telemetry. dynamicFilterTimeout time.Duration From 86ae7e63f0dc8fc5e8497c8f7e4f6a267ac3b0b9 Mon Sep 17 00:00:00 2001 From: Gary <982483+gmalouf@users.noreply.github.com> Date: Thu, 7 Mar 2024 13:08:44 -0500 Subject: [PATCH 12/16] Network: Support simple liveness check via http on gossip server port. (#5944) --- node/follower_node.go | 5 ++++ node/node.go | 5 ++++ rpcs/healthService.go | 41 ++++++++++++++++++++++++++++++ rpcs/healthService_test.go | 52 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+) create mode 100644 rpcs/healthService.go create mode 100644 rpcs/healthService_test.go diff --git a/node/follower_node.go b/node/follower_node.go index 277c48a24e..8483f14679 100644 --- a/node/follower_node.go +++ b/node/follower_node.go @@ -127,6 +127,11 @@ func MakeFollower(log logging.Logger, rootDir string, cfg config.Local, phoneboo } node.ledger.RegisterBlockListeners(blockListeners) + + if cfg.IsGossipServer() { + rpcs.MakeHealthService(node.net) + } + node.blockService = rpcs.MakeBlockService(node.log, cfg, node.ledger, p2pNode, node.genesisID) node.catchupBlockAuth = blockAuthenticatorImpl{Ledger: node.ledger, AsyncVoteVerifier: agreement.MakeAsyncVoteVerifier(node.lowPriorityCryptoVerificationPool)} node.catchupService = catchup.MakeService(node.log, node.config, p2pNode, node.ledger, node.catchupBlockAuth, make(chan catchup.PendingUnmatchedCertificate), node.lowPriorityCryptoVerificationPool) diff --git a/node/node.go b/node/node.go index f59bd67dab..53c6c492a9 100644 --- a/node/node.go +++ b/node/node.go @@ -251,6 +251,11 @@ func MakeFull(log logging.Logger, rootDir string, cfg config.Local, phonebookAdd return nil, err } + // The health service registers itself with the network + if cfg.IsGossipServer() { + rpcs.MakeHealthService(node.net) + } + node.blockService = rpcs.MakeBlockService(node.log, cfg, node.ledger, p2pNode, node.genesisID) node.ledgerService = rpcs.MakeLedgerService(cfg, node.ledger, p2pNode, node.genesisID) rpcs.RegisterTxService(node.transactionPool, p2pNode, node.genesisID, cfg.TxPoolSize, cfg.TxSyncServeResponseSize) diff --git a/rpcs/healthService.go b/rpcs/healthService.go new file mode 100644 index 0000000000..d3121d8bd2 --- /dev/null +++ b/rpcs/healthService.go @@ -0,0 +1,41 @@ +// Copyright (C) 2019-2024 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 rpcs + +import ( + "github.com/algorand/go-algorand/network" + "net/http" +) + +// HealthServiceStatusPath is the path to register HealthService as a handler for when using gorilla/mux +const HealthServiceStatusPath = "/status" + +// HealthService is a service that provides health information endpoints for the node +type HealthService struct{} + +// MakeHealthService creates a new HealthService and registers it with the provided network if enabled +func MakeHealthService(net network.GossipNode) HealthService { + service := HealthService{} + + net.RegisterHTTPHandler(HealthServiceStatusPath, service) + + return service +} + +func (h HealthService) ServeHTTP(writer http.ResponseWriter, _ *http.Request) { + writer.WriteHeader(http.StatusOK) +} diff --git a/rpcs/healthService_test.go b/rpcs/healthService_test.go new file mode 100644 index 0000000000..9d0bb215c2 --- /dev/null +++ b/rpcs/healthService_test.go @@ -0,0 +1,52 @@ +// Copyright (C) 2019-2024 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 rpcs + +import ( + "github.com/algorand/go-algorand/network" + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" + "io" + "net/http" + "path" + "testing" +) + +func TestHealthService_ServeHTTP(t *testing.T) { + partitiontest.PartitionTest(t) + + nodeA := &basicRPCNode{} + nodeA.start() + defer nodeA.stop() + + _ = MakeHealthService(nodeA) + + parsedURL, err := network.ParseHostOrURL(nodeA.rootURL()) + require.NoError(t, err) + + client := http.Client{} + + parsedURL.Path = path.Join(parsedURL.Path, HealthServiceStatusPath) + + response, err := client.Get(parsedURL.String()) + require.NoError(t, err) + + require.Equal(t, http.StatusOK, response.StatusCode) + bodyData, err := io.ReadAll(response.Body) + require.NoError(t, err) + require.Empty(t, bodyData) +} From 13e66ff9ba5073637f69f9dd4e5572f19b77e38c Mon Sep 17 00:00:00 2001 From: ohill <145173879+ohill@users.noreply.github.com> Date: Thu, 7 Mar 2024 13:41:17 -0500 Subject: [PATCH 13/16] build: upgrade to go 1.20.14 (#5949) --- Dockerfile | 2 +- scripts/get_golang_version.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 468d257360..c6802506af 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM ubuntu:20.04 as builder -ARG GO_VERSION="1.20.7" +ARG GO_VERSION="1.20.14" ARG CHANNEL ARG URL diff --git a/scripts/get_golang_version.sh b/scripts/get_golang_version.sh index 10bdb8630d..13dfca5bf1 100755 --- a/scripts/get_golang_version.sh +++ b/scripts/get_golang_version.sh @@ -11,7 +11,7 @@ # Our build task-runner `mule` will refer to this script and will automatically # build a new image whenever the version number has been changed. -BUILD=1.20.7 +BUILD=1.20.14 MIN=1.20 GO_MOD_SUPPORT=1.20 From dd22cff42da3eef29dd1c3f6965198fde9a492a1 Mon Sep 17 00:00:00 2001 From: John Lee Date: Thu, 14 Mar 2024 11:46:54 -0400 Subject: [PATCH 14/16] Docker: Bump debian version in algod container (#5955) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index c6802506af..48bc652dcd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -41,7 +41,7 @@ RUN /dist/files/build/install.sh \ -b "${BRANCH}" \ -s "${SHA}" -FROM debian:bookworm-20230703-slim as final +FROM debian:bookworm-20240311-slim as final ENV PATH="/node/bin:${PATH}" ALGOD_PORT="8080" KMD_PORT="7833" ALGORAND_DATA="/algod/data" From 98ac36a21232294ae984077665fbfb92940f87da Mon Sep 17 00:00:00 2001 From: Gary <982483+gmalouf@users.noreply.github.com> Date: Fri, 15 Mar 2024 19:46:46 -0400 Subject: [PATCH 15/16] Network: Penalize peer/class rankings when peer does not have catchpoint (#5959) --- catchup/catchpointService.go | 2 ++ catchup/peerSelector.go | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/catchup/catchpointService.go b/catchup/catchpointService.go index 3c11d0db73..3954c1cd6c 100644 --- a/catchup/catchpointService.go +++ b/catchup/catchpointService.go @@ -824,6 +824,8 @@ func (cs *CatchpointCatchupService) checkLedgerDownload() error { if err == nil { return nil } + // a non-nil error means that the catchpoint is not available, so we should rank it accordingly + cs.blocksDownloadPeerSelector.rankPeer(psp, peerRankNoCatchpointForRound) } return fmt.Errorf("checkLedgerDownload(): catchpoint '%s' unavailable from peers: %s", cs.stats.CatchpointLabel, err) } diff --git a/catchup/peerSelector.go b/catchup/peerSelector.go index 05556bb24b..a8eefb0958 100644 --- a/catchup/peerSelector.go +++ b/catchup/peerSelector.go @@ -55,6 +55,10 @@ const ( // This indicates a peer is either behind or a block has not happened yet, or does not have a block that is old enough. peerRankNoBlockForRound = 2000 + // peerRankNoCatchpointForRound is used for responses failed because of no catchpoint for round + // This indicates a peer is either behind or a catchpoint has not been produced, or this node did not retain this catchpoint (aged out). + peerRankNoCatchpointForRound = 2000 + // peerRankDownloadFailed is used for responses which could be temporary, such as missing files, or such that we don't // have clear resolution peerRankDownloadFailed = 10000 From ef9a85766008245a2fdb87fac1811111b0b146ff Mon Sep 17 00:00:00 2001 From: John Lee Date: Fri, 15 Mar 2024 20:27:45 -0400 Subject: [PATCH 16/16] Update the Version, BuildNumber, genesistimestamp.data --- buildnumber.dat | 1 + genesistimestamp.dat | 1 + 2 files changed, 2 insertions(+) create mode 100644 buildnumber.dat create mode 100644 genesistimestamp.dat diff --git a/buildnumber.dat b/buildnumber.dat new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/buildnumber.dat @@ -0,0 +1 @@ +1 diff --git a/genesistimestamp.dat b/genesistimestamp.dat new file mode 100644 index 0000000000..c72c6a7795 --- /dev/null +++ b/genesistimestamp.dat @@ -0,0 +1 @@ +1558657885