diff options
| author | Bitflash <btf01@proton.me> | 2026-07-31 14:26:50 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-07-31 14:26:50 -0300 |
| commit | e2d5c1bbe01370dbf75e2118d697a159c4ee582b (patch) | |
| tree | f109392ac356c46bdb248c554110813c8f7ad19c /src/net.cpp | |
| parent | 3268ea95c1443238d380dea26e7b1bb3bd108aed (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/net.cpp')
| -rw-r--r-- | src/net.cpp | 11 |
1 files changed, 11 insertions, 0 deletions
diff --git a/src/net.cpp b/src/net.cpp index ac8df82..37c081f 100644 --- a/src/net.cpp +++ b/src/net.cpp | |||
| @@ -181,6 +181,17 @@ bool AnySubscribed(unsigned int nChannel) | |||
| 181 | return false; | 181 | return false; |
| 182 | } | 182 | } |
| 183 | 183 | ||
| 184 | void CNode::PushGetBlocks(CBlockIndex* pindexBegin, uint256 hashEnd) | ||
| 185 | { | ||
| 186 | // Filter out duplicate requests. | ||
| 187 | if (pindexBegin == pindexLastGetBlocksBegin && hashEnd == hashLastGetBlocksEnd) | ||
| 188 | return; | ||
| 189 | pindexLastGetBlocksBegin = pindexBegin; | ||
| 190 | hashLastGetBlocksEnd = hashEnd; | ||
| 191 | |||
| 192 | PushMessage("getblocks", CBlockLocator(pindexBegin), hashEnd); | ||
| 193 | } | ||
| 194 | |||
| 184 | bool CNode::IsSubscribed(unsigned int nChannel) | 195 | bool CNode::IsSubscribed(unsigned int nChannel) |
| 185 | { | 196 | { |
| 186 | if (nChannel >= vfSubscribe.size()) | 197 | if (nChannel >= vfSubscribe.size()) |