Anti-fork hardening: faster convergence for small Tor-only network (v5.8.3)
Build All Platforms / test-linux-unit (push) Failing after 40s
Build All Platforms / build-linux-qt (push) Failing after 40s
Build All Platforms / build-linux-daemon (push) Failing after 40s
Build All Platforms / build-windows-qt (push) Has been cancelled
Build All Platforms / build-windows-daemon (push) Has been cancelled
Build All Platforms / build-macos (push) Has been cancelled
Build All Platforms / release (push) Has been cancelled
Build All Platforms / Trigger TRI-PI ARM64 Build (push) Has been cancelled

- Reduce equal-trust reorg cooldown from 10min to 2min for faster convergence
- Tighten future block drift from 3min to 90sec to shrink competing-block window
- Require 2+ peers before staking (was 1) to prevent isolated fork creation
- Push full blocks directly to peers instead of inv-only (saves 1-2s Tor roundtrip)
- Add periodic 45-second chain-tip sync to detect and resolve silent forks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-19 19:32:52 -07:00
parent be90d39cd4
commit a671708f0b
6 changed files with 32 additions and 10 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ if(POLICY CMP0167)
endif() endif()
project(Triangles project(Triangles
VERSION 5.8.2 VERSION 5.8.3
DESCRIPTION "Cryptographic Triangles Wallet" DESCRIPTION "Cryptographic Triangles Wallet"
LANGUAGES C CXX LANGUAGES C CXX
) )
+1 -1
View File
@@ -8,7 +8,7 @@
// These need to be macros, as version.cpp's and triangles-qt.rc's voodoo requires it // These need to be macros, as version.cpp's and triangles-qt.rc's voodoo requires it
#define CLIENT_VERSION_MAJOR 5 #define CLIENT_VERSION_MAJOR 5
#define CLIENT_VERSION_MINOR 8 #define CLIENT_VERSION_MINOR 8
#define CLIENT_VERSION_REVISION 2 #define CLIENT_VERSION_REVISION 3
#define CLIENT_VERSION_BUILD 0 #define CLIENT_VERSION_BUILD 0
// Converts the parameter X to a string after macro replacement on X has been performed. // Converts the parameter X to a string after macro replacement on X has been performed.
+25 -5
View File
@@ -2970,15 +2970,15 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos, const u
// 2. Equal trust: deterministic hash tiebreaker — lower tip hash wins. // 2. Equal trust: deterministic hash tiebreaker — lower tip hash wins.
// This ensures all nodes converge on the same chain even when two // This ensures all nodes converge on the same chain even when two
// forks have identical cumulative difficulty (common in PoS). // forks have identical cumulative difficulty (common in PoS).
// Rate-limited to one equal-trust reorg per 10 minutes to prevent // Rate-limited to one equal-trust reorg per 2 minutes — short enough
// oscillation from Tor-latency-induced competing announcements. // for fast convergence but long enough to prevent oscillation on Tor.
bool fNewBest = false; bool fNewBest = false;
static int64_t nLastEqualTrustReorg = 0; static int64_t nLastEqualTrustReorg = 0;
if (pindexNew->nChainTrust > nBestChainTrust) if (pindexNew->nChainTrust > nBestChainTrust)
fNewBest = true; fNewBest = true;
else if (pindexNew->nChainTrust == nBestChainTrust && pindexBest && else if (pindexNew->nChainTrust == nBestChainTrust && pindexBest &&
pindexNew->GetBlockHash() < pindexBest->GetBlockHash() && pindexNew->GetBlockHash() < pindexBest->GetBlockHash() &&
GetTime() - nLastEqualTrustReorg > 10 * 60) GetTime() - nLastEqualTrustReorg > 2 * 60)
{ {
fNewBest = true; fNewBest = true;
nLastEqualTrustReorg = GetTime(); nLastEqualTrustReorg = GetTime();
@@ -3190,14 +3190,20 @@ bool CBlock::AcceptBlock()
if (!AddToBlockIndex(nFile, nBlockPos, hashProofOfStake)) if (!AddToBlockIndex(nFile, nBlockPos, hashProofOfStake))
return error("AcceptBlock() : AddToBlockIndex failed"); return error("AcceptBlock() : AddToBlockIndex failed");
// Relay inventory, but don't relay old inventory during initial block download // Push new tip block directly to all peers. On a small Tor-only
// network the inv→getdata→block round-trip adds 1-2 seconds of latency
// per hop — enough for a competing staker to create a fork. Pushing
// the full block immediately cuts propagation to a single hop.
int nBlockEstimate = Checkpoints::GetTotalBlocksEstimate(); int nBlockEstimate = Checkpoints::GetTotalBlocksEstimate();
if (hashBestChain == hash) if (hashBestChain == hash)
{ {
LOCK(cs_vNodes); LOCK(cs_vNodes);
for (CNode* pnode : vNodes) for (CNode* pnode : vNodes)
if (nBestHeight > (pnode->nStartingHeight != -1 ? pnode->nStartingHeight - 2000 : nBlockEstimate)) if (nBestHeight > (pnode->nStartingHeight != -1 ? pnode->nStartingHeight - 2000 : nBlockEstimate))
pnode->PushInventory(CInv(MSG_BLOCK, hash)); {
pnode->PushMessage("block", *this);
pnode->AddInventoryKnown(CInv(MSG_BLOCK, hash));
}
} }
return true; return true;
@@ -5308,6 +5314,20 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
pto->PushMessage("inv", vInv); pto->PushMessage("inv", vInv);
//
// Periodic chain-tip sync: every 45 seconds, ask each peer if they
// have blocks we don't. On a small Tor-only network, transient
// partitions can cause forks that persist silently — this ensures
// nodes discover the longer chain even without explicit announcement.
//
if (!IsInitialBlockDownload() && !pto->fClient && pindexBest &&
GetTime() - pto->nLastTipCheck > 45)
{
pto->nLastTipCheck = GetTime();
pto->pindexLastGetBlocksBegin = NULL; // reset dedup to force request
pto->PushGetBlocks(pindexBest, uint256(0));
}
// //
// Stall detection: if we're still catching up and no new blocks for // Stall detection: if we're still catching up and no new blocks for
// a while, re-request. Active during IBD (5s timeout) and also // a while, re-request. Active during IBD (5s timeout) and also
+2 -2
View File
@@ -59,10 +59,10 @@ static const int fHaveUPnP = false;
static const uint256 hashGenesisBlockOfficial("0x7e7a6e4dd5fe895106fca912dfbacaeaf2a89e76c6a588df8ff96e0e18b96021"); static const uint256 hashGenesisBlockOfficial("0x7e7a6e4dd5fe895106fca912dfbacaeaf2a89e76c6a588df8ff96e0e18b96021");
static const uint256 hashGenesisBlockTestNet ("0x7e7a6e4dd5fe895106fca912dfbacaeaf2a89e76c6a588df8ff96e0e18b96021"); static const uint256 hashGenesisBlockTestNet ("0x7e7a6e4dd5fe895106fca912dfbacaeaf2a89e76c6a588df8ff96e0e18b96021");
inline int64_t GetMaxTimeDrift(int nHeight) { return (nHeight >= FORK_HEIGHT_V5_4) ? 3 * 60 : 10 * 60; } inline int64_t GetMaxTimeDrift(int nHeight) { return (nHeight >= FORK_HEIGHT_V5_4) ? 90 : 10 * 60; }
inline int64_t PastDrift(int64_t nTime, int nHeight) { return nTime - GetMaxTimeDrift(nHeight); } inline int64_t PastDrift(int64_t nTime, int nHeight) { return nTime - GetMaxTimeDrift(nHeight); }
inline int64_t FutureDrift(int64_t nTime, int nHeight) { return nTime + GetMaxTimeDrift(nHeight); } inline int64_t FutureDrift(int64_t nTime, int nHeight) { return nTime + GetMaxTimeDrift(nHeight); }
// Height-less overloads always use post-V5.4 rules (3-min drift). // Height-less overloads always use post-V5.4 rules (90-second drift).
// All nodes are well past FORK_HEIGHT_V5_4; using the global nBestHeight // All nodes are well past FORK_HEIGHT_V5_4; using the global nBestHeight
// here previously caused nodes at different heights to disagree on block // here previously caused nodes at different heights to disagree on block
// validity during the fork transition — a consensus-splitting bug. // validity during the fork transition — a consensus-splitting bug.
+1 -1
View File
@@ -413,7 +413,7 @@ void StakeMiner(CWallet *pwallet)
if (fTryToSync) if (fTryToSync)
{ {
fTryToSync = false; fTryToSync = false;
if (vNodes.size() < 1 || nBestHeight < GetNumBlocksOfPeers()) if (vNodes.size() < 2 || nBestHeight < GetNumBlocksOfPeers())
{ {
MilliSleep(60000); MilliSleep(60000);
continue; continue;
+2
View File
@@ -272,6 +272,7 @@ public:
CBlockIndex* pindexLastGetHeadersBegin; CBlockIndex* pindexLastGetHeadersBegin;
uint256 hashLastGetHeadersEnd; uint256 hashLastGetHeadersEnd;
int nStartingHeight; int nStartingHeight;
int64_t nLastTipCheck; // last time we asked this peer for chain tip
// flood relay // flood relay
std::vector<CAddress> vAddrToSend; std::vector<CAddress> vAddrToSend;
@@ -319,6 +320,7 @@ public:
pindexLastGetHeadersBegin = 0; pindexLastGetHeadersBegin = 0;
hashLastGetHeadersEnd = 0; hashLastGetHeadersEnd = 0;
nStartingHeight = -1; nStartingHeight = -1;
nLastTipCheck = 0;
fGetAddr = false; fGetAddr = false;
nMisbehavior = 0; nMisbehavior = 0;
hashCheckpointKnown = 0; hashCheckpointKnown = 0;