cleanup: dedup addrman Select_ table walk and pow-limit-by-algo selection

Two behavior-preserving refactors flagged by the Phase-6 hygiene scoping:

* addrman: CAddrMan::Select_ contained two ~40-line copies of the same
  bucket-table random walk, differing only in the table (vvTried/vvNew),
  its bucket count, and a log label. Extract the shared loop into
  SelectFromTable_(vvTable, nBucketCount, tableName); Select_ now just
  dispatches to it. Verbatim move — clean compile proves self-containment.

* pow: the "Equihash uses powLimit, everything else uses powAlternate"
  selection was copy-pasted as an if/else into GetNextWorkRequired,
  CalculateNextWorkRequired, and lwmaCalculateNextWorkRequired. Extract
  into PowLimitForAlgo(params). The CheckProofOfWork site (line ~892) is
  left as-is: it has an extra `height <= 1` genesis special-case and is
  NOT the same selection. On DragonX (RandomX) this always returns
  powAlternate, exactly as before.

Validated: full build of dragonxd/cli/tx, isolated self-mine to height
336, verifychain 4 0 -> true (exercises PowLimitForAlgo on every block).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-27 16:51:16 -05:00
parent e55c67b0eb
commit 004808403f
3 changed files with 74 additions and 107 deletions

View File

@@ -473,25 +473,6 @@ CAddrInfo CAddrMan::Select_(bool newOnly)
if (size() == 0) if (size() == 0)
return CAddrInfo(); return CAddrInfo();
// Track number of attempts to find a table entry, before giving up to avoid infinite loop
const int kMaxRetries = 200000; // magic number so unit tests can pass
const int kRetriesBetweenSleep = 1000;
const int kRetrySleepInterval = 100; // milliseconds
// Peer-selection tuning factors (networking heuristics, not consensus).
// On each rejected candidate the running chance factor is scaled up by this
// amount so the loop is guaranteed to eventually accept a peer.
const double kChanceFactorGrowth = 1.2;
// Candidates on unreachable networks are deprioritized to this fraction of
// their base chance.
const double kUnreachableDeprioritize = 0.25;
// Candidates that were just tried are deprioritized to this fraction of
// their base chance.
const double kJustTriedDeprioritize = 0.10;
// Fixed-point scale for the acceptance probability test: draw a random int in
// [0, kChanceScale) and accept if it falls below (factors * chance) * kChanceScale.
const int kChanceScale = 1 << 30;
if (newOnly && nNew == 0) if (newOnly && nNew == 0)
return CAddrInfo(); return CAddrInfo();
@@ -499,6 +480,32 @@ CAddrInfo CAddrMan::Select_(bool newOnly)
if (!newOnly && if (!newOnly &&
(nTried > 0 && (nNew == 0 || RandomInt(2) == 0))) { (nTried > 0 && (nNew == 0 || RandomInt(2) == 0))) {
// use a tried node // use a tried node
return SelectFromTable_(vvTried, ADDRMAN_TRIED_BUCKET_COUNT, "tried");
} else {
// use a new node
return SelectFromTable_(vvNew, ADDRMAN_NEW_BUCKET_COUNT, "new");
}
return CAddrInfo();
}
// Random-walk one addrman bucket table (tried or new) and return an accepted peer,
// applying the reachable/just-tried deprioritization and the growing chance factor.
// Extracted verbatim from Select_'s two previously copy-pasted branches; the only
// differences were the table (vvTried/vvNew), its bucket count, and the log label.
CAddrInfo CAddrMan::SelectFromTable_(int (*vvTable)[ADDRMAN_BUCKET_SIZE], int nBucketCount, const char *tableName)
{
// Track number of attempts to find a table entry, before giving up to avoid infinite loop
const int kMaxRetries = 200000; // magic number so unit tests can pass
const int kRetriesBetweenSleep = 1000;
const int kRetrySleepInterval = 100; // milliseconds
// Peer-selection tuning factors (networking heuristics, not consensus).
const double kChanceFactorGrowth = 1.2;
const double kUnreachableDeprioritize = 0.25;
const double kJustTriedDeprioritize = 0.10;
const int kChanceScale = 1 << 30;
double fChanceFactor = 1.0; double fChanceFactor = 1.0;
double fReachableFactor = 1.0; double fReachableFactor = 1.0;
double fJustTried = 1.0; double fJustTried = 1.0;
@@ -507,20 +514,20 @@ CAddrInfo CAddrMan::Select_(bool newOnly)
return CAddrInfo(); return CAddrInfo();
int i = 0; int i = 0;
int nKBucket = RandomInt(ADDRMAN_TRIED_BUCKET_COUNT); int nKBucket = RandomInt(nBucketCount);
int nKBucketPos = RandomInt(ADDRMAN_BUCKET_SIZE); int nKBucketPos = RandomInt(ADDRMAN_BUCKET_SIZE);
while (vvTried[nKBucket][nKBucketPos] == -1) { while (vvTable[nKBucket][nKBucketPos] == -1) {
nKBucket = (nKBucket + insecure_rand()) % ADDRMAN_TRIED_BUCKET_COUNT; nKBucket = (nKBucket + insecure_rand()) % nBucketCount;
nKBucketPos = (nKBucketPos + insecure_rand()) % ADDRMAN_BUCKET_SIZE; nKBucketPos = (nKBucketPos + insecure_rand()) % ADDRMAN_BUCKET_SIZE;
if (i++ > kMaxRetries) if (i++ > kMaxRetries)
return CAddrInfo(); return CAddrInfo();
if (i % kRetriesBetweenSleep == 0 && !nKey.IsNull()) if (i % kRetriesBetweenSleep == 0 && !nKey.IsNull())
MilliSleep(kRetrySleepInterval); MilliSleep(kRetrySleepInterval);
} }
int nId = vvTried[nKBucket][nKBucketPos]; int nId = vvTable[nKBucket][nKBucketPos];
// assert(mapInfo.count(nId) == 1); // assert(mapInfo.count(nId) == 1);
if(mapInfo.count(nId) != 1) { if(mapInfo.count(nId) != 1) {
fprintf(stderr,"%s: Could not find tried node with nId=%d=vvTried[%d][%d], mapInfo.count(%d)=%lu\n", __func__, nId, nKBucket, nKBucketPos, nId, mapInfo.count(nId) ); fprintf(stderr,"%s: Could not find %s node with nId=%d=vvTable[%d][%d], mapInfo.count(%d)=%lu\n", __func__, tableName, nId, nKBucket, nKBucketPos, nId, mapInfo.count(nId) );
continue; continue;
} }
@@ -537,49 +544,6 @@ CAddrInfo CAddrMan::Select_(bool newOnly)
return info; return info;
fChanceFactor *= kChanceFactorGrowth; fChanceFactor *= kChanceFactorGrowth;
} }
} else {
// use a new node
double fChanceFactor = 1.0;
double fReachableFactor = 1.0;
double fJustTried = 1.0;
while (1) {
if (ShutdownRequested()) //break loop on shutdown request
return CAddrInfo();
int i = 0;
int nUBucket = RandomInt(ADDRMAN_NEW_BUCKET_COUNT);
int nUBucketPos = RandomInt(ADDRMAN_BUCKET_SIZE);
while (vvNew[nUBucket][nUBucketPos] == -1) {
nUBucket = (nUBucket + insecure_rand()) % ADDRMAN_NEW_BUCKET_COUNT;
nUBucketPos = (nUBucketPos + insecure_rand()) % ADDRMAN_BUCKET_SIZE;
if (i++ > kMaxRetries)
return CAddrInfo();
if (i % kRetriesBetweenSleep == 0 && !nKey.IsNull())
MilliSleep(kRetrySleepInterval);
}
int nId = vvNew[nUBucket][nUBucketPos];
if(mapInfo.count(nId) != 1) {
fprintf(stderr,"%s: Could not find new node with nId=%d=vvNew[%d][%d], mapInfo.count(%d)=%lu\n", __func__, nId, nUBucket, nUBucketPos, nId, mapInfo.count(nId) );
continue;
}
// assert(mapInfo.count(nId) == 1);
CAddrInfo& info = mapInfo[nId];
if (info.IsReachableNetwork()) {
//deprioritize unreachable networks
fReachableFactor = kUnreachableDeprioritize;
}
if (info.IsJustTried()) {
//deprioritize entries just tried
fJustTried = kJustTriedDeprioritize;
}
if (RandomInt(kChanceScale) < fChanceFactor * fReachableFactor * fJustTried * info.GetChance() * kChanceScale)
return info;
fChanceFactor *= kChanceFactorGrowth;
}
}
return CAddrInfo();
} }
#ifdef DEBUG_ADDRMAN #ifdef DEBUG_ADDRMAN

View File

@@ -300,6 +300,10 @@ protected:
//! Select an address to connect to, if newOnly is set to true, only the new table is selected from. //! Select an address to connect to, if newOnly is set to true, only the new table is selected from.
CAddrInfo Select_(bool newOnly); CAddrInfo Select_(bool newOnly);
//! Random-walk one bucket table (tried or new) and return an accepted peer.
//! Shared implementation for Select_'s two (previously copy-pasted) branches.
CAddrInfo SelectFromTable_(int (*vvTable)[ADDRMAN_BUCKET_SIZE], int nBucketCount, const char *tableName);
//! Wraps GetRandInt to allow tests to override RandomInt and make it deterministic. //! Wraps GetRandInt to allow tests to override RandomInt and make it deterministic.
virtual int RandomInt(int nMax); virtual int RandomInt(int nMax);

View File

@@ -97,6 +97,14 @@ bnTarget = RT_CST_RST (bnTarget, ts, cw, numerator, denominator, W, T, past);
#define T ASSETCHAINS_BLOCKTIME #define T ASSETCHAINS_BLOCKTIME
#define K ((int64_t)1000000) #define K ((int64_t)1000000)
// The proof-of-work limit for the active algorithm: Equihash chains use params.powLimit,
// everything else (DragonX = RandomX) uses params.powAlternate. Shared by the retarget
// functions below, where this selection was previously copy-pasted as an if/else.
static arith_uint256 PowLimitForAlgo(const Consensus::Params& params)
{
return UintToArith256(ASSETCHAINS_ALGO == ASSETCHAINS_EQUIHASH ? params.powLimit : params.powAlternate);
}
arith_uint256 RT_CST_RST_outer(int32_t height,uint32_t nTime,arith_uint256 bnTarget,uint32_t *ts,arith_uint256 *ct,int32_t numerator,int32_t denominator,int32_t W,int32_t past) arith_uint256 RT_CST_RST_outer(int32_t height,uint32_t nTime,arith_uint256 bnTarget,uint32_t *ts,arith_uint256 *ct,int32_t numerator,int32_t denominator,int32_t W,int32_t past)
{ {
int64_t outerK; int32_t cmpval; arith_uint256 mintarget = bnTarget / arith_uint256(2); int64_t outerK; int32_t cmpval; arith_uint256 mintarget = bnTarget / arith_uint256(2);
@@ -211,10 +219,7 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHead
} }
arith_uint256 bnLimit; arith_uint256 bnLimit;
if (ASSETCHAINS_ALGO == ASSETCHAINS_EQUIHASH) bnLimit = PowLimitForAlgo(params);
bnLimit = UintToArith256(params.powLimit);
else
bnLimit = UintToArith256(params.powAlternate);
unsigned int nProofOfWorkLimit = bnLimit.GetCompact(); unsigned int nProofOfWorkLimit = bnLimit.GetCompact();
// Genesis block // Genesis block
if (pindexLast == NULL ) if (pindexLast == NULL )
@@ -461,10 +466,7 @@ unsigned int CalculateNextWorkRequired(arith_uint256 bnAvg,
} }
// Retarget // Retarget
arith_uint256 bnLimit; arith_uint256 bnLimit;
if (ASSETCHAINS_ALGO == ASSETCHAINS_EQUIHASH) bnLimit = PowLimitForAlgo(params);
bnLimit = UintToArith256(params.powLimit);
else
bnLimit = UintToArith256(params.powAlternate);
const arith_uint256 bnPowLimit = bnLimit; //UintToArith256(params.powLimit); const arith_uint256 bnPowLimit = bnLimit; //UintToArith256(params.powLimit);
arith_uint256 bnNew {bnAvg}; arith_uint256 bnNew {bnAvg};
@@ -498,10 +500,7 @@ unsigned int lwmaGetNextWorkRequired(const CBlockIndex* pindexLast, const CBlock
unsigned int lwmaCalculateNextWorkRequired(const CBlockIndex* pindexLast, const Consensus::Params& params) unsigned int lwmaCalculateNextWorkRequired(const CBlockIndex* pindexLast, const Consensus::Params& params)
{ {
arith_uint256 nextTarget {0}, sumTarget {0}, bnTmp, bnLimit; arith_uint256 nextTarget {0}, sumTarget {0}, bnTmp, bnLimit;
if (ASSETCHAINS_ALGO == ASSETCHAINS_EQUIHASH) bnLimit = PowLimitForAlgo(params);
bnLimit = UintToArith256(params.powLimit);
else
bnLimit = UintToArith256(params.powAlternate);
unsigned int nProofOfWorkLimit = bnLimit.GetCompact(); unsigned int nProofOfWorkLimit = bnLimit.GetCompact();