upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/main.h
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.h
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.h')
-rw-r--r--src/main.h9
1 files changed, 9 insertions, 0 deletions
diff --git a/src/main.h b/src/main.h
index 2d5cb86..3bc1219 100644
--- a/src/main.h
+++ b/src/main.h
@@ -18,6 +18,13 @@ static const unsigned int MAX_SIZE = 0x02000000;
18// Ceiling on transactions held waiting for a parent that has not arrived. 18// Ceiling on transactions held waiting for a parent that has not arrived.
19// They cost a peer nothing to send and are never validated, only stored. 19// They cost a peer nothing to send and are never validated, only stored.
20static const unsigned int MAX_ORPHAN_TRANSACTIONS = 100; 20static const unsigned int MAX_ORPHAN_TRANSACTIONS = 100;
21// Blocks held waiting for a parent that has not arrived. Orphan transactions
22// have had a ceiling since 0.1.0 and orphan blocks never did: one whose parent
23// never turns up stays in memory for the life of the process, and a peer can
24// send an unlimited number of them by inventing the previous-block hash. They
25// are not free either -- each holds a whole block. A production node was
26// seeing 1287 of them against 4117 blocks processed.
27static const unsigned int MAX_ORPHAN_BLOCKS = 200;
21// Signature operations allowed in one block. Size alone does not bound what a 28// Signature operations allowed in one block. Size alone does not bound what a
22// block costs to validate: every OP_CHECKSIG is an elliptic-curve verification, 29// block costs to validate: every OP_CHECKSIG is an elliptic-curve verification,
23// so a block within the size limit could still hold millions of them and take 30// so a block within the size limit could still hold millions of them and take
@@ -125,6 +132,8 @@ void PrintBlockTree();
125void AddOrphanTx(const CDataStream& vMsg); 132void AddOrphanTx(const CDataStream& vMsg);
126void EraseOrphanTx(uint256 hash); 133void EraseOrphanTx(uint256 hash);
127void LimitOrphanTx(unsigned int nMaxOrphans); 134void LimitOrphanTx(unsigned int nMaxOrphans);
135// Same ceiling idea for orphan blocks, which never had one.
136void LimitOrphanBlocks(unsigned int nMaxOrphans);
128// nThreadId is 1..N, only for logging: it makes each miner's lines 137// nThreadId is 1..N, only for logging: it makes each miner's lines
129// distinguishable, which the dedup filter would otherwise collapse into one. 138// distinguishable, which the dedup filter would otherwise collapse into one.
130bool BitcoinMiner(int nThreadId = 1); 139bool BitcoinMiner(int nThreadId = 1);