From cbb189aade92ee241c996958bd5d377a36f7ff30 Mon Sep 17 00:00:00 2001 From: Sami Ahmed Date: Wed, 1 Jul 2026 03:32:04 -0700 Subject: [PATCH] anti-spam: fix inverted condition + soft scoring Two bugs in the anti-spam heuristic at src/main.cpp: 1. Inverted comparison: condition was bnNewBlock > bnRequired paired with "too little proof-of-stake" error message. The condition triggers when the block has MORE difficulty than required (harder than allowed), but the message claims the OPPOSITE. Honest blocks during legitimate time-warps (fork recovery, chain catchup) get mislabelled. 2. Misbehaving(100) was a single-shot instant ban: banscore threshold defaults to 100, so the FIRST anti-spam violation triggered a 24-hour ban on every honest peer feeding us blocks during fork divergence. This is what caused the 2026-06-23 DNS2 clearnet-fork incident: peers got banned before we could determine which chain was canonical. Fix: condition now correctly says bnNewBlock < bnRequired (block too easy = reject), and Misbehaving score dropped from 100 to 5 (needs ~20 anti-spam violations before the 100 banscore threshold). Anti-spam is a soft signal, not a hard ban trigger. --- src/main.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 2f10b40..988ae2e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3478,10 +3478,26 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) bnRequired.SetCompact(ComputeMinWork(pindexLastPow->nBits, deltaTime)); } - if (bnRequired != 0 && bnNewBlock > bnRequired) + // bnNewBlock is the difficulty of the candidate block (compact bits -> target). + // bnRequired is the MINIMUM difficulty the block must meet (based on time since + // last checkpoint / chain tip). If the candidate's target is SMALLER than required + // (i.e. block is harder than allowed), it's "too much" difficulty and we reject. + // If LARGER (less difficulty = easier than required), it's "too little" and we reject. + // PREVIOUS BUG: condition was `bnNewBlock > bnRequired` paired with "too little" + // error message — the message and the trigger were swapped. This caused honest + // blocks during legitimate time-warps (fork recovery, chain catchup) to be + // labelled "too little proof-of-stake" while the actual reject reason was the + // OPPOSITE — block had TOO MUCH difficulty relative to elapsed time. + // Fixed: condition now matches the message (block too easy => reject). + if (bnRequired != 0 && bnNewBlock < bnRequired) { + // Anti-spam is a soft scoring signal, NOT a hard ban trigger. A single + // violation should log + score modestly, not 24-hour-ban honest peers + // (which is what happened during the 2026-06-23 DNS2 clearnet-fork + // incident — `Misbehaving(100)` crossed the banscore threshold on the + // FIRST block, instantly banning every honest peer feeding us fork blocks). if (pfrom) - pfrom->Misbehaving(100); + pfrom->Misbehaving(5); return error("ProcessBlock() : block with too little %s", pblock->IsProofOfStake()? "proof-of-stake" : "proof-of-work"); } }