#36015 txorphanage: bound orphan memory by storing transactions serialized

ReviewabilityReviewsAgreementSize
#36015 brunoerg txorphanage: bound orphan memory by storing transactions serializedWaiting on author1 (+1)Strong193 + 241 tests
  • Prevent peers from bypassing orphan memory limits and mounting remote memory exhaustion attacks
  • Keep actual orphan memory usage in line with accounted limits by storing transactions serialized
  • Ready for review
  • Substantive support from multiple reviewers based on review and functional testing (jeanpablojp, nervana21)
  • Concept approval without detailed reviews (w0xlt, l0rinc, Crypt-iQ)
  • Approach objection: prefers alternative PR #36521 if it proves viable (instagibbs)
  • Author is willing to close if #36521 is viable, pending consensus on that alternative
  • 193 lines added or modified outside tests
  • 241 lines added or modified in tests
  • 146 lines removed in total
  • 10 files, 7 commits
P2P#2 of 71
P1 · DoS protection
  • P1 because it prevents remote DoS memory exhaustion attacks against nodes
  • Peers can bypass orphanage caps with witness-heavy transactions that use 28x their accounted weight
Reviewability: ReadyStalePaused
Agreement: StrongPositivePositive w/ caveatsNeutralMildDisputedBlockedCrickets
Reviews: current code-review ACKs, then (+stale ACKs) and -NACKs; greener = more ACKs.
Size: lines added or modified outside tests, then in tests; greener = smaller.

Goal

TxOrphanage is changed to store orphan transactions in serialized byte form instead of deserialized CTransaction objects, deserializing them only when retrieved for validation or RPC. It also adjusts orphan lookup interfaces such as package candidate selection and announcement processing to operate on txids and wtxids without deserializing candidates unless selected.

Problem: The orphanage uses transaction weight as a proxy for memory usage to enforce per-peer and global memory limits. For deserialized transactions with many tiny witness stack elements, vector heap allocation overhead causes real memory consumption to drastically outstrip weight; for example, a standard-weight orphan with 199,000 1-byte witness elements consumes ~11.1MB (28 times its accounted weight limit), allowing peers to bypass orphanage memory caps and mount memory exhaustion attacks.

Evidence of importance:

  • "A transaction of 199,000 1-byte witness elements weighs 398,247WU (i.e. it is of standard weight, and witness standardness cannot be checked while the inputs are missing), but uses 11.1MB of memory: 28 times what is accounted for it, and one such orphan can be retained per peer." (PR description and commit 933a9f95af)
  • "Otherwise a peer holding ~40MB of witness-heavy orphans (many tiny witness elements, each a separate heap allocation once deserialized) could make a single call cost ~0.9s of CPU and ~700MB of memory." (commit 0557bc81d5)

Scope: Changes several TxOrphanage methods to work on ID structs rather than full CTransactionRef instances to avoid accidental deserialization overhead during 1P1C candidate evaluation. The largest implementation and test files (src/node/txorphanage.cpp and src/test/orphanage_tests.cpp) were truncated from the patch.

Reviewability

Paused · Waiting on author

Open objections from nervana21 with no author reply for 17 days; review would repeat what has been asked.

Waiting on (from the thread read):

  • decision: consensus on whether alternative PR #36521 is viable 2026-10-15
  • author: respond to fuzz test suggestions from nervana21 2026-09-24

Agreement

Strong

substantive support, no open objection (jeanpablojp, nervana21)

Thread notes: instagibbs expressed an Approach NACK favoring alternative PR #36521 if it proves viable; author is willing to close if so, and instagibbs suggested holding off until consensus is reached on #36521.

Objections, suggestions, and questions:

ReviewerTypeHarm, request, or questionStatusMust beAuthor repliedQuote
l0rinc (member)objection, scopemixing refactors and hardening increases review friction and conflicts with concurrent PRsfixed · fix pushed · author replied in c:5335429601; fixed by c:5414146027answeredyes2026-08-18: 'the change mixes refactors and hardening - can you check if it's possible to simplify and do refactors that aren't strictly related in follow-ups instead?'
Status from: 2026-08-25: brunoerg: 'Force-pushed addressing @instagibbs' approach.'
jeanpablojp (contributor)suggestion, correctnessuse wait_for_getdata with specific wtxid so the test exercises inv/getdata on subsequent callsfixed · fix pushed · superseded when test approach changed in c:5414146027answeredno2026-08-21: 'This `wait_until` matches the getdata from the previous call, since `last_message` isn't cleared.'
Status from: 2026-08-25: brunoerg: 'Force-pushed addressing @instagibbs' approach.'
instagibbs (member)objection, approachchanging the metric could cause subtle user breakage for standard witness-heavy transactions like BitVMfixed · fix pushed · author adopted serialized storage in c:5414146027answeredyes2026-08-24: 'I'm pretty worried that the new behavior would cause subtle user breakage, epsecially considering something BitVM-like'
Status from: 2026-08-25: instagibbs: 'title and OP will need updating :+1:'
jeanpablojp (contributor)objection, safetyeagerly rebuilding all matching child orphans creates an unaccounted ~1GB memory spike during Find1P1CPackagefixed · fix pushed · fixed by push in c:5607026571answeredyes2026-08-26: 'Every matching child is rebuilt before `Find1P1CPackage` looks at the first one... that's 1,047.6 MB live against the 37.4 MB accounted for it'
Status from: 2026-09-09: brunoerg: 'Force-pushed: Addressed suggestion from @instagibbs - fixing `Find1P1CPackage`.'
jeanpablojp (contributor)objection, correctnessunit test passes without the implementation change because it fails to verify pointer inequality of deserialized transactionsfixed · fix pushed · addressed by adding pointer inequality assertion in c:5607026571answeredyes2026-08-26: 'This test passes without the change. Reverting just `src/node/txorphanage.cpp` to before 493561a34285, the whole suite still passes'
Status from: 2026-09-09: brunoerg: 'Addressed https://github.com/bitcoin/bitcoin/pull/36015#discussion_r3862075438'
instagibbs (member)objection, safetydeserializing candidate orphans in Find1P1CPackage causes ~1s CPU delay and ~1GB memory spikefixed · fix pushed · fixed by push in c:5607026571answeredyes2026-09-08: 'we're now deserializing potentially many orphans, which on my machine can cause ~1s of cpu time at ~1GB of memory usage'
Status from: 2026-09-09: brunoerg: 'Force-pushed: Addressed suggestion from @instagibbs - fixing `Find1P1CPackage`.'
nervana21 (contributor)suggestion, correctnesstrack the orphan usage metric in src/test/fuzz/txorphan.cppopenansweredno2026-09-24: 'Can add the usage metric'
nervana21 (contributor)suggestion, correctnessuse tx_usage instead of weight in the peer usage assertion in src/test/fuzz/txorphan.cppopenansweredno2026-09-24: 'Can use usage instead of weight.'
instagibbs (member)suggestion, approachprefer alternative PR #36521 overall if viableanswered · author replied agreeing to close if #36521 is viable, but instagibbs advised waiting for consensus firstansweredyes2026-10-15: 'I think I approach nack this overall, if #36521 is viable'
Status from: 2026-10-15: brunoerg: 'If that is viable, happy to close this.'

Support:

  • w0xlt (contributor) (Concept ACK, 2026-08-18): (no reason given) [not substantive]
  • l0rinc (member) (Concept ACK, 2026-08-18): (no reason given) [not substantive]
  • jeanpablojp (contributor) (Tested ACK, 2026-08-21) [tested]: Reverted `txorphanage` back to master and the new functional test fails like it should, "no orphan was evicted". With 10 peers sending one memory-heavy orphan each, master keeps all 10 and the head no
  • Crypt-iQ (member) (Concept ACK, 2026-08-29): (no reason given) [not substantive]
  • nervana21 (contributor) (Concept ACK, 2026-09-23): (no reason given) [not substantive]
  • nervana21 (contributor) (Tested ACK, 2026-09-24) [both]: the logic is sound based on my review and testing

Participants:

  • l0rinc (member, objection, 1 statements 2026-08-18 to 2026-08-18): Concept ACK, but objected to mixing refactors with hardening
  • w0xlt (contributor, support, 1 statements 2026-08-18 to 2026-08-18): Concept ACK
  • jeanpablojp (contributor, objection, 6 statements 2026-08-21 to 2026-08-26): Tested ACK on original branch, but raised concerns on Find1P1CPackage memory spike and test assertions
  • instagibbs (member, objection, 7 statements 2026-08-24 to 2026-10-15): Approach NACK in favor of #36521 if viable; previously raised concerns on BitVM txs and 1P1C memory/CPU spike
  • Crypt-iQ (member, support, 1 statements 2026-08-29 to 2026-08-29): Concept ACK
  • nervana21 (contributor, support, 15 statements 2026-09-23 to 2026-09-24): Concept ACK and Tested ACK after testing and reviewing logic; left suggestions on fuzz tests

Files

316 lines under test/bench/ci.

  • src/node/txorphanage.cpp +146/-54
  • src/test/orphanage_tests.cpp +118/-37
  • src/test/txdownload_tests.cpp +62/-0
  • src/bench/txorphanage.cpp +33/-22
  • src/test/fuzz/txorphan.cpp +21/-16
  • src/node/txorphanage.h +26/-8
  • src/node/txdownloadman_impl.cpp +10/-7
  • src/policy/packages.cpp +8/-2
  • src/test/txpackage_tests.cpp +7/-0
  • src/policy/packages.h +3/-0

Card

The short text the ranking pass compares across PRs.

This PR modifies TxOrphanage to store orphan transactions in serialized form, deserializing them only when handed out for processing. The orphanage relies on transaction weight to bound memory usage, but heap overhead for deserialized witness stacks allows standard-weight transactions with many small witness elements to consume up to 28 times more memory than accounted for (11.1MB instead of ~400KB per peer). By storing serialized bytes, a transaction's memory footprint is strictly bounded by its weight, closing an orphan-based memory exhaustion vector without rejecting valid standard transactions.

Data

dossier JSON · extract JSON · model ledger, generated 2026-10-16T01:16, confidence high, input hash 15e7fa1faac27ccc
thread read through 2026-10-15 (42 statements), head e9381aac15
labels: none

Categories

P2P

P1 · DoS protection

Prevents remote memory exhaustion DoS attacks where peers bypass orphanage memory limits. Using transaction weight as a proxy for memory allows standard-weight orphans with many tiny witness elements to consume 11.1MB each (28 times accounted weight), enabling attackers to exhaust node memory.

Membership: Changes orphan handling and resource accounting in `src/node/txorphanage.cpp` and `src/node/txdownloadman_impl.cpp`.

Factors: security/stability 2, bug 2, performance 1, user value 0, leverage 0