upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/main.cpp
diff options
context:
space:
mode:
authorBitflash <btf01@proton.me>2026-07-31 14:26:50 -0300
committerGitHub <noreply@github.com>2026-07-31 14:26:50 -0300
commite2d5c1bbe01370dbf75e2118d697a159c4ee582b (patch)
treef109392ac356c46bdb248c554110813c8f7ad19c /src/main.cpp
parent3268ea95c1443238d380dea26e7b1bb3bd108aed (diff)
net: bound orphan blocks and stop asking a peer the same question twice (#70)
Two defects that feed each other. Measured on a 32-core production node: 1287 of 4117 processed blocks arrived with a missing parent, 97 arrived that the node already had, and 490 transactions arrived whose inputs were unknown. mapOrphanBlocks had no ceiling at all. Orphan transactions have had one since 0.1.0 -- MAX_ORPHAN_TRANSACTIONS with LimitOrphanTx -- and blocks never did. An orphan block whose parent never turns up stays in memory for the life of the process, each one holding a whole block, and a peer can send an unlimited number of them just by inventing the previous-block hash. LimitOrphanBlocks mirrors the transaction one: random eviction, both maps kept in step, and the CBlock deleted because mapOrphanBlocks owns it. Every orphan block also fired a getblocks at the sender, and an inv for a block already held as an orphan fired another, with no filtering. So a node missing part of the chain asked the same peer the same question over and over, and the duplicate answers made more orphans, which asked again. CNode::PushGetBlocks remembers the last request and drops a repeat -- the same fix Bitcoin made in e2c2648c1. One trap found by testing rather than by reading: eviction picks at random and may pick the block just inserted, so GetOrphanRoot has to be read out of pblock before LimitOrphanBlocks runs, not after. The first version had that backwards and would have read freed memory. Verified with fabricated orphans, four of the seven sharing one hashPrevBlock so the multimap path is exercised: 7 in, limit(3) leaves 3, limit(0) leaves 0, both maps in step at every stage, no crash. Then a run against the live network to 4348 with no scaffolding left behind. The storm itself was not reproducible on demand -- the network is healthy today and produced one orphan block in seven minutes -- so there is no before/after on the metric that motivated this.
Diffstat (limited to 'src/main.cpp')
-rw-r--r--src/main.cpp50
1 files changed, 47 insertions, 3 deletions
diff --git a/src/main.cpp b/src/main.cpp
index b147ddd..4bc9c4d 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -293,6 +293,43 @@ void AddOrphanTx(const CDataStream& vMsg)
293// grow this map until the node runs out of memory. Evict at random rather than 293// grow this map until the node runs out of memory. Evict at random rather than
294// in map order, so an attacker cannot keep its own orphans resident by 294// in map order, so an attacker cannot keep its own orphans resident by
295// choosing hashes that sort low. 295// choosing hashes that sort low.
296// Drop orphan blocks at random until we are back under the ceiling.
297//
298// Random rather than oldest-first, matching LimitOrphanTx below: there is no
299// arrival order recorded, and an attacker who knew the eviction rule could aim
300// at it. Dropping one costs nothing that matters -- if its parent ever shows
301// up, the block is requested again like any other.
302//
303// Both maps have to be kept in step, and the CBlock itself has to be deleted:
304// mapOrphanBlocks owns it.
305void LimitOrphanBlocks(unsigned int nMaxOrphans)
306{
307 while (mapOrphanBlocks.size() > nMaxOrphans)
308 {
309 uint256 randomhash;
310 for (int i = 0; i < 4; i++)
311 ((uint64*)&randomhash)[i] = GetRand(_UI64_MAX);
312 map<uint256, CBlock*>::iterator it = mapOrphanBlocks.lower_bound(randomhash);
313 if (it == mapOrphanBlocks.end())
314 it = mapOrphanBlocks.begin();
315
316 CBlock* pblock = it->second;
317 uint256 hashPrev = pblock->hashPrevBlock;
318
319 for (multimap<uint256, CBlock*>::iterator mi = mapOrphanBlocksByPrev.lower_bound(hashPrev);
320 mi != mapOrphanBlocksByPrev.upper_bound(hashPrev);)
321 {
322 if (mi->second == pblock)
323 mapOrphanBlocksByPrev.erase(mi++);
324 else
325 ++mi;
326 }
327
328 mapOrphanBlocks.erase(it);
329 delete pblock;
330 }
331}
332
296void LimitOrphanTx(unsigned int nMaxOrphans) 333void LimitOrphanTx(unsigned int nMaxOrphans)
297{ 334{
298 while (mapOrphanTransactions.size() > nMaxOrphans) 335 while (mapOrphanTransactions.size() > nMaxOrphans)
@@ -1468,9 +1505,16 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
1468 mapOrphanBlocks.insert(make_pair(hash, pblock)); 1505 mapOrphanBlocks.insert(make_pair(hash, pblock));
1469 mapOrphanBlocksByPrev.insert(make_pair(pblock->hashPrevBlock, pblock)); 1506 mapOrphanBlocksByPrev.insert(make_pair(pblock->hashPrevBlock, pblock));
1470 1507
1508 // Read what we need out of pblock before trimming: eviction picks at
1509 // random and is perfectly entitled to pick the block just inserted,
1510 // which would leave the GetOrphanRoot call below reading freed memory.
1511 uint256 hashOrphanRoot = GetOrphanRoot(pblock);
1512
1513 LimitOrphanBlocks(MAX_ORPHAN_BLOCKS);
1514
1471 // Ask this guy to fill in what we're missing 1515 // Ask this guy to fill in what we're missing
1472 if (pfrom) 1516 if (pfrom)
1473 pfrom->PushMessage("getblocks", CBlockLocator(pindexBest), GetOrphanRoot(pblock)); 1517 pfrom->PushGetBlocks(pindexBest, hashOrphanRoot);
1474 return true; 1518 return true;
1475 } 1519 }
1476 1520
@@ -2047,7 +2091,7 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
2047 if (!fAskedForBlocks && !pfrom->fClient) 2091 if (!fAskedForBlocks && !pfrom->fClient)
2048 { 2092 {
2049 fAskedForBlocks = true; 2093 fAskedForBlocks = true;
2050 pfrom->PushMessage("getblocks", CBlockLocator(pindexBest), uint256(0)); 2094 pfrom->PushGetBlocks(pindexBest, uint256(0));
2051 } 2095 }
2052 2096
2053 // Introduce ourselves and hand over the .btf peers that answered us. 2097 // Introduce ourselves and hand over the .btf peers that answered us.
@@ -2110,7 +2154,7 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
2110 if (!fAlreadyHave) 2154 if (!fAlreadyHave)
2111 pfrom->AskFor(inv); 2155 pfrom->AskFor(inv);
2112 else if (inv.type == MSG_BLOCK && mapOrphanBlocks.count(inv.hash)) 2156 else if (inv.type == MSG_BLOCK && mapOrphanBlocks.count(inv.hash))
2113 pfrom->PushMessage("getblocks", CBlockLocator(pindexBest), GetOrphanRoot(mapOrphanBlocks[inv.hash])); 2157 pfrom->PushGetBlocks(pindexBest, GetOrphanRoot(mapOrphanBlocks[inv.hash]));
2114 } 2158 }
2115 } 2159 }
2116 2160