orphanage: Fix orphan reconsideration disconnect hole, add 1p1c invariant harness #36437

pull instagibbs wants to merge 2 commits into bitcoin:master from instagibbs:2026-10-orphan-reconsider-handoff changing 6 files +640 −10
  1. instagibbs commented at 12:58 PM on October 5, 2026: member

    The first commit adds p2p_1p1c_liveness, an end-to-end fuzz target for parent and child relay. It drives a real PeerManager with real messages and validation, and checks that if an honest peer announces the child of an acceptable parent and child, the child reaches the mempool within a bounded time, whatever other peers do. Known liveness holes are marked in the target as flags that turn the check off once opened, including this one. With that flag turned off, the target finds this bug on master from an empty corpus in under an hour on one core; an earlier version found it first (https://github.com/bitcoin/bitcoin/pull/36371#issuecomment-5937814761).

    The second commit hands reconsideration over to a remaining announcer when the marked announcement is erased, and removes the flag.

    Follow-up PR to fill the holes is here: https://github.com/bitcoin/bitcoin/pull/36506

  2. DrahtBot commented at 12:58 PM on October 5, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    External sites

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process.

    Type Reviewers
    Concept ACK w0xlt

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #36015 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36015.svg"></sub> (txorphanage: bound orphan memory by storing transactions serialized by brunoerg)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  3. instagibbs marked this as ready for review on Oct 5, 2026
  4. instagibbs renamed this:
    p2p/fuzz: Fix orphan reconsideration disconnect hole, add 1p1c invariant harness
    orphanage: Fix orphan reconsideration disconnect hole, add 1p1c invariant harness
    on Oct 5, 2026
  5. instagibbs commented at 1:12 PM on October 5, 2026: member

    Prior harness found this PR's issue that is fixed, and this harness development found "R11", so I think it's already paid for itself

    <details> <summary>Concept review notes</summary>

    Diff. Two commits.

    • 294d6ffcd0 changes TxOrphanageImpl::Erase in about 20 lines. When the erased announcement was the one marked for reconsideration, the mark moves to the remaining announcer with the lowest NodeId. The comment in AddChildrenToWorkSet and the doc comment for EraseForPeer are updated, and txorphanage_sim and orphanage_tests are updated to match.
    • 345497515a adds the p2p_1p1c_liveness fuzz target (547 lines, test-only).

    The description matches the diff.

    Problem. The node is sent a parent and a child (for example a CPFP), and the child does not reach the mempool until the orphan expires or is evicted. The trigger is one announcer leaving at the wrong moment, and an adversary can try to cause it. The evidence is a fuzzer reproduction on master. There is no reported incident. The goal one level up is that 1p1c relay gets a child accepted in bounded time whatever adversarial peers do.

    Baseline. The child is delayed until orphan expiry or eviction. The attacker needs to be the randomly chosen announcer, which is likelier with many sybil connections, and it must disconnect before its next ProcessMessages turn. In that turn ProcessOrphanTx runs first, and the turn is skipped once fDisconnect is set. If the attacker also supplies the parent and then closes the socket, the race is plausible. The cost is delay, not loss.

    Payoff. The fix closes the hole for both the disconnect and the eviction path. The new unit and sim tests demonstrate it, and the liveness harness checks it end to end. The payoff is modest but real.

    Risks.

    • The invariant: "an orphan whose inputs became available keeps a reconsiderable announcement while any announcement exists." It is enforced by construction, because every removal of an announcement goes through Erase<Tag>.
    • The change stays inside txorphanage. net_processing and txdownloadman are untouched.
    • Blast radius is relay liveness and, at worst, extra orphan-validation work. No consensus or fund exposure, and no new Asserts.
    • The change is internal and easy to reverse.
    • Review cost is mostly the harness. It drives a real PeerManager, so it is a possible source of maintenance work and flakiness.

    Prior discussion. This came up in #31666, which introduced the single random announcer:

    That proposal was deferred, not rejected. This PR implements it, and the fuzzer reproduction is new evidence against the impracticality point. No closed PRs attempt this fix.

    Alternatives.

    • Kept, both trades rather than improvements:
      • Wtxid-keyed reconsider queue that any announcer drains. Removes per-peer ownership structurally, but changes the TxOrphanage interface and moves the cost of other peers' work into each peer's processing turn.
      • Detect-and-heal sweep for orphans with available inputs and no reconsider mark. Catches causes not yet known, but adds a second mechanism and CPU cost.
    • Rejected:
      • Mark every announcer reconsiderable. It undoes ed24e01696 (at most one reconsiderable announcement per wtxid) and wastes work. This is glozow's argument from #31666.
      • Process one workset item at disconnect. An attacker defeats it by putting two items in the workset. Also glozow's argument from #31666.
      • Process children synchronously after the parent is accepted. It changes net_processing's per-peer processing budget, a larger change in a costlier place.
      • Docs-only or tests-only. They leave the hole open.
      • Re-request the child via txrequest. Adds a new bandwidth path an attacker can trigger.

    The PR takes the shallowest placement of the candidates.

    Dominance: none.

    Verdict: Concept ACK. The fix delivers the payoff without the harness. The harness is test-only and has already found bugs, so splitting it out would not change the verdict.

  6. instagibbs force-pushed on Oct 5, 2026
  7. DrahtBot added the label CI failed on Oct 5, 2026
  8. instagibbs commented at 2:25 PM on October 5, 2026: member

    pushed an update to address ralph review: https://git.fish.foo/bitcoin/bitcoin/pulls/36437#author-ralph

  9. instagibbs force-pushed on Oct 5, 2026
  10. instagibbs force-pushed on Oct 5, 2026
  11. DrahtBot removed the label CI failed on Oct 5, 2026
  12. w0xlt commented at 1:34 AM on October 8, 2026: contributor

    Concept ACK

  13. instagibbs force-pushed on Oct 11, 2026
  14. fuzz: add end-to-end liveness target for parent and child relay
    Check that if an honest peer announces the child of an acceptable
    parent and child, the child ends up in the mempool within a bounded
    time, whatever other peers do. The target drives a real PeerManager
    with real messages and validation, which the existing targets cannot,
    as they model validation results.
    
    After an adversarial prefix the honest peer only answers requests. With
    an outbound honest peer the adversaries keep acting; with an inbound
    one they go quiet. Adversary connections are limited, and adversaries
    do not refill outbound slots.
    
    Known liveness holes are marked in the target and turn the check off
    once opened, including the lost orphan reconsideration fixed in the
    next commit.
    47776a909e
  15. txorphanage: hand reconsideration over when its announcement is erased
    A child that arrives before its parent is kept as an orphan. Once the
    parent is accepted on its own, the orphan is put in one randomly chosen
    announcer's work set to be retried. If that announcer disconnects or is
    evicted first, the orphan is never retried, and re-announcements by the
    other announcers don't fetch it again since it is already held. This
    keeps e.g. a CPFP child of a parent that pays for itself out of the
    mempool until the orphan is evicted, and an announcer can cause it on
    purpose.
    
    Hand reconsideration over to the remaining announcer with the lowest
    NodeId instead.
    
    Remove its hole from the liveness target.
    65b48ba1f1
  16. instagibbs force-pushed on Oct 12, 2026
  17. instagibbs commented at 9:24 PM on October 12, 2026: member

    added coinbase-based censorship cast stuff to the PR. Does not add any new holes per se, but covers more ground. @FlashWayne this PR and the follow-up in the OP should cover known holes. If we can get this in first, then subtracting holes just becomes a matter of implementation debate, and which are worth filling. parent_stripped does seem worth filling, for sure.


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-10-24 07:51 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me