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/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
diff --git a/buildnumber.dat b/buildnumber.dat
index d00491fd7e..573541ac97 100644
--- a/buildnumber.dat
+++ b/buildnumber.dat
@@ -1 +1 @@
-1
+0
diff --git a/catchup/catchpointService.go b/catchup/catchpointService.go
index f71a4209ae..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,24 +803,7 @@ func (cs *CatchpointCatchupService) updateBlockRetrievalStatistics(acquiredBlock
}
func (cs *CatchpointCatchupService) initDownloadPeerSelector() {
- cs.blocksDownloadPeerSelector = cs.makeCatchpointPeerSelector()
-}
-
-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},
- })
- }
+ cs.blocksDownloadPeerSelector = makeCatchpointPeerSelector(cs.net)
}
// checkLedgerDownload sends a HEAD request to the ledger endpoint of peers to validate the catchpoint's availability
@@ -830,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..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
@@ -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 2a907934a8..7aa373d280 100644
--- a/catchup/peerSelector_test.go
+++ b/catchup/peerSelector_test.go
@@ -131,10 +131,10 @@ 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.PeersPhonebookArchivers}},
+ }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}},
)
psp, err := peerSelector.getNextPeer()
@@ -191,17 +191,17 @@ 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.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()
@@ -240,10 +240,10 @@ 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.PeersPhonebookArchivers}},
+ }), []peerClass{{initialRank: peerRankInitialFirstPriority, peerClass: network.PeersPhonebookArchivalNodes}},
)
poolIdx, peerIdx := peerSelector.findPeer(&peerSelectorPeer{mockHTTPPeer{address: "abcd"}, 0})
@@ -258,17 +258,17 @@ 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.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}},
)
@@ -332,17 +332,17 @@ 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.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}},
)
@@ -408,17 +408,17 @@ 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.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(
+ peerSelector := makeRankPooledPeerSelector(
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}
- peerSelector := makePeerSelector(
+ pClass := peerClass{initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}
+ peerSelector := makeRankPooledPeerSelector(
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}
- peerSelector := makePeerSelector(
+ pClass := peerClass{initialRank: peerRankInitialSecondPriority, peerClass: network.PeersPhonebookArchivalNodes}
+ peerSelector := makeRankPooledPeerSelector(
makePeersRetrieverStub(func(options ...network.PeerOption) (peers []network.Peer) {
for _, opt := range options {
- if opt == network.PeersPhonebookArchivers {
+ if opt == network.PeersPhonebookArchivalNodes {
peers = append(peers, peers1...)
}
}
@@ -644,17 +639,17 @@ 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.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}},
)
@@ -682,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 1093f851a2..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,76 +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 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},
- }
- }
- } 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},
- }
- }
- }
- } 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},
- }
- }
- } 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},
- }
- }
- }
+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 c5b8cfccab..045a0438f2 100644
--- a/catchup/service_test.go
+++ b/catchup/service_test.go
@@ -954,146 +954,27 @@ 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
+ 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.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, 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)
- 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.PeersPhonebookArchivers, ps.peerClasses[3].peerClass)
-
- // cfg.EnableCatchupFromArchiveServers = false; cfg.NetAddress != ""; pipelineFetch = true
- cfg.EnableCatchupFromArchiveServers = false
- cfg.NetAddress = "someAddress"
- s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil)
- ps = createPeerSelector(s.net, s.cfg, true)
-
- require.Equal(t, 4, len(ps.peerClasses))
- require.Equal(t, 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.EnableCatchupFromArchiveServers = false; cfg.NetAddress == ""; pipelineFetch = true
- cfg.EnableCatchupFromArchiveServers = false
- cfg.NetAddress = ""
- s = MakeService(logging.Base(), cfg, &httpTestPeerSource{}, new(mockedLedger), &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil)
- ps = createPeerSelector(s.net, s.cfg, true)
-
- require.Equal(t, 3, len(ps.peerClasses))
- require.Equal(t, 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.EnableCatchupFromArchiveServers = false; cfg.NetAddress != ""; pipelineFetch = false
- cfg.EnableCatchupFromArchiveServers = false
- 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, 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.EnableCatchupFromArchiveServers = false; cfg.NetAddress == ""; pipelineFetch = false
- cfg.EnableCatchupFromArchiveServers = false
- 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, 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) {
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/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/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()
diff --git a/config/localTemplate.go b/config/localTemplate.go
index c6e1ba0a2c..ce4294de01 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.
@@ -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
@@ -454,28 +455,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/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/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=
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/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)
+ }
+ }
+ }()
+ }
+
+}
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 9ce5f331ae..295bd71155 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 (
@@ -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,48 +1842,48 @@ 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("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 {
@@ -1895,15 +1891,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
+
+ 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)
}
+ archivalAddresses = nil
}
return
}
diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go
index b7cd873bcf..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)
}
@@ -4127,95 +4129,82 @@ 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))
- }
+ refreshTestConf := defaultConfig
- 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))
- }
-
- // 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(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
+ } 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(PeersPhonebookArchivalNodes)
+ 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(PeersPhonebookArchivalNodes)
- })
- }
+ assert.Equal(t, 3, 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, primaryArchiveResolvedRecords, archiveAddrs)
- testRefreshWithConfig(configWithBlockServiceFallbackToArchiverEnabled)
+ })
}
/*
@@ -4231,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()
@@ -4260,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))
@@ -4300,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
@@ -4361,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)
@@ -4414,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
@@ -4457,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)
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/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/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)
+}
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
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
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,
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())
}