{
 "number": 36578,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36578",
 "title": "p2p: clean up a 1p1c package child that is already in the mempool",
 "author": "instagibbs",
 "author_association": "MEMBER",
 "created_at": "2026-10-19T21:08:45Z",
 "updated_at": "2026-10-26T10:52:53Z",
 "description_hash": "20197a09f2e4b08a",
 "age_days": 13,
 "draft": false,
 "labels": [
  "P2P"
 ],
 "milestone": "33.0",
 "base": "master",
 "head_sha": "a93bdc83202e00375b643e6df3b9daa801f7de23",
 "head_ref": "2026-10-1p1c-orphan-in-mempool",
 "head_repo": "instagibbs/bitcoin",
 "head_history": [
  {
   "t": "2026-10-21T14:04:35Z",
   "sha": "a93bdc83202e00375b643e6df3b9daa801f7de23"
  }
 ],
 "additions": 33,
 "deletions": 1,
 "changed_files": 3,
 "commit_count": 1,
 "size_bucket": "S",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "ack": [
     {
      "login": "fametrano",
      "url": "https://github.com/bitcoin/bitcoin/pull/36578#issuecomment-6395070625"
     }
    ],
    "stale_ack": [
     {
      "login": "Yudis-bit",
      "url": "https://github.com/bitcoin/bitcoin/pull/36578#issuecomment-6286975704"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {
  "Yudis-bit": {
   "kind": "ack",
   "hash": "b0dda32aff",
   "t": "2026-10-20T08:39:00Z",
   "stale": true
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 1,
  "concept_ack": 0,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "Yudis-bit",
   "fametrano",
   "ismaelsadeeq"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-10-21T14:04:35Z",
  "last_reviewer_activity": "2026-10-25T07:50:36Z",
  "last_reviewer": "fametrano",
  "author_silent_days": 11,
  "waiting_on_author_days": 7,
  "days_since_update": 6
 },
 "refs": {
  "mentioned": [
   36437,
   36594
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 36437,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "orphanage: Fix orphan reconsideration disconnect hole, add 1p1c invariant harness"
   },
   {
    "number": 36594,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-10-30",
    "title": "net: make orphan reconsideration more granular"
   }
  ],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [],
 "body": "An orphan that enters the mempool other than through peer relay (e.g. by\nRPC) stays in the orphanage, so Find1P1CPackage can pick it as the child\nof a reconsiderable parent, and package evaluation then reports it as\nMEMPOOL_ENTRY, which hits Assume(false). Have Find1P1CPackage erase such\nchildren from the orphanage and skip them.",
 "commits": [
  {
   "sha": "a93bdc83202e00375b643e6df3b9daa801f7de23",
   "date": "2026-10-21T13:51:15Z",
   "message": "p2p: clean up a 1p1c package child that is already in the mempool\n\nAn orphan that enters the mempool other than through peer relay (e.g. by\nRPC) stays in the orphanage, so Find1P1CPackage can pick it as the child\nof a reconsiderable parent, and package evaluation then reports it as\nMEMPOOL_ENTRY, which hits Assume(false). Have Find1P1CPackage erase such\nchildren from the orphanage and skip them."
  }
 ],
 "timeline": [
  {
   "t": "2026-10-19T21:09:26Z",
   "kind": "comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "text": "@ismaelsadeeq",
   "id": "c:6277377248",
   "url": "https://github.com/bitcoin/bitcoin/pull/36578#issuecomment-6277377248",
   "hash": "97a4de51883bfc71",
   "edited": null
  },
  {
   "t": "2026-10-20T08:39:00Z",
   "kind": "comment",
   "who": "Yudis-bit",
   "assoc": "NONE",
   "text": "Tested ACK b0dda32aff on Windows.",
   "id": "c:6286975704",
   "url": "https://github.com/bitcoin/bitcoin/pull/36578#issuecomment-6286975704",
   "hash": "b7c763642cda13ce",
   "edited": null
  },
  {
   "t": "2026-10-21T10:14:43Z",
   "kind": "comment",
   "who": "ismaelsadeeq",
   "assoc": "MEMBER",
   "text": "Not sure,  the `Assume(false)`  removed is a no-op in release, and the replacement cleans the orphan only reactively when it's later re-selected as a 1p1c child. If that never happens, the orphan dangles until it expires. I'd rather we clean the orphanage proactively on mempool entry, via `TransactionAddedToMempool`? so an orphan that enters by any path  is removed regardless. This is redundant ofc with the existing p2p cleanup for relayed txs, but we need both: the notification is async and can be processed before the orphan is inserted (it's EraseTx then no-ops), so the synchronous p2p path still does useful work.\n```diff\ndiff --git a/src/net_processing.cpp b/src/net_processing.cpp\nindex ae329489e6..f23303605f 100644\n--- a/src/net_processing.cpp\n+++ b/src/net_processing.cpp\n@@ -576,6 +576,8 @@ public:\n     /** Overridden from CValidationInterface. */\n     void ActiveTipChange(const CBlockIndex& new_tip, bool) override\n         EXCLUSIVE_LOCKS_REQUIRED(!m_tx_download_mutex);\n+    void TransactionAddedToMempool(const NewMempoolTransactionInfo& tx, uint64_t mempool_sequence) override\n+        EXCLUSIVE_LOCKS_REQUIRED(!m_tx_download_mutex);\n     void BlockConnected(const ChainstateRole& role, const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexConnected) override\n         EXCLUSIVE_LOCKS_REQUIRED(!m_tx_download_mutex);\n     void BlockDisconnected(const std::shared_ptr<const CBlock> &block, const CBlockIndex* pindex) override\n@@ -2190,6 +2192,12 @@ void PeerManagerImpl::ActiveTipChange(const CBlockIndex& new_tip, bool is_ibd)\n     }\n }\n\n+void PeerManagerImpl::TransactionAddedToMempool(const NewMempoolTransactionInfo& tx, uint64_t mempool_sequence)\n+{\n+    LOCK(m_tx_download_mutex);\n+    m_txdownloadman.MempoolAcceptedTx(tx.info.m_tx);\n+}\n+\n /**\n  * Evict orphan txn pool entries based on a newly connected\n  * block, remember the recently confirmed transactions, and delete tracked\ndiff --git a/test/functional/p2p_orphan_handling.py b/test/functional/p2p_orphan_handling.py\nindex b6e521dbe4..056bd72823 100755\n--- a/test/functional/p2p_orphan_handling.py\n+++ b/test/functional/p2p_orphan_handling.py\n@@ -841,7 +841,7 @@ class OrphanHandlingTest(BitcoinTestFramework):\n\n     @cleanup\n     def test_orphan_in_mempool(self):\n-        self.log.info(\"Test that an orphan in the mempool is removed from the orphanage when found as the child of a 1p1c package\")\n+        self.log.info(\"Test that an orphan is removed from the orphanage when it enters the mempool other than through peer relay\")\n         node = self.nodes[0]\n         parent = self.wallet.create_self_transfer(fee_rate=0)\n         child = self.wallet.create_self_transfer(utxo_to_spend=parent[\"new_utxo\"])\n@@ -854,12 +854,10 @@ class OrphanHandlingTest(BitcoinTestFramework):\n         peer.send_and_ping(msg_tx(child[\"tx\"]))\n         assert tx_in_orphanage(node, child[\"tx\"])\n\n-        # The child enters the mempool by RPC, which does not remove it from the orphanage.\n+        # The child enters the mempool by RPC. Even though this is not through peer relay, it is\n+        # still removed from the orphanage.\n         assert_equal(node.submitpackage([parent[\"hex\"], child[\"hex\"]])[\"package_msg\"], \"success\")\n-        assert tx_in_orphanage(node, child[\"tx\"])\n-\n-        # The same parent version is paired with the orphan, which turns out to be in the mempool.\n-        peer.send_and_ping(msg_tx(parent_bad_wit))\n+        node.syncwithvalidationinterfacequeue()\n         assert not tx_in_orphanage(node, child[\"tx\"])\n         assert child[\"txid\"] in node.getrawmempool()\n\n```",
   "id": "c:6313515799",
   "url": "https://github.com/bitcoin/bitcoin/pull/36578#issuecomment-6313515799",
   "hash": "5cac3ca84e3a0359",
   "edited": null
  },
  {
   "t": "2026-10-21T13:08:19Z",
   "kind": "comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nThis would not have been an open issue otherwise :)\n\n[quoted text omitted]\nI think if we're going to do that, we should simply have it call `EraseTx` to avoid additional `AddChildrenToWorkSet` invocations.\n\nAnother alternative to the synchronous piece in this PR would to have `Find1P1CPackage` detect children in the mempool as well as orphanage and clean it up there.",
   "id": "c:6316850266",
   "url": "https://github.com/bitcoin/bitcoin/pull/36578#issuecomment-6316850266",
   "hash": "607d6b46ce117804",
   "edited": null
  },
  {
   "t": "2026-10-21T13:33:52Z",
   "kind": "comment",
   "who": "ismaelsadeeq",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nSeems better to me; is there something I am missing about why you take this approach in this PR?",
   "id": "c:6317396533",
   "url": "https://github.com/bitcoin/bitcoin/pull/36578#issuecomment-6317396533",
   "hash": "a13106fa682266cc",
   "edited": null
  },
  {
   "t": "2026-10-21T13:56:08Z",
   "kind": "comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "text": "@ismaelsadeeq swtiched to the Find1P1CPackage strategy for now; can still be convinced the extra machinery to async sweep them and taking lock is worth it, but keeping strategy as is for now",
   "id": "c:6317864893",
   "url": "https://github.com/bitcoin/bitcoin/pull/36578#issuecomment-6317864893",
   "hash": "9eff8020a9fe8b02",
   "edited": null
  },
  {
   "t": "2026-10-21T14:04:35Z",
   "kind": "force_push",
   "who": "instagibbs",
   "commit": "a93bdc83202e00375b643e6df3b9daa801f7de23",
   "id": "push:a93bdc83202e00375b643e6df3b9daa801f7de23"
  },
  {
   "t": "2026-10-25T07:50:36Z",
   "kind": "comment",
   "who": "fametrano",
   "assoc": "NONE",
   "text": "tACK a93bdc83202e00375b643e6df3b9daa801f7de23\n\nThe `Assume(false)` is also reached through `sendrawtransaction`, not only through `submitpackage` as in the new test. I checked on master in a Debug build. The parent pays the minimum relay fee for its own size. A peer sends a copy with a padded witness, which has the same txid but falls below the minimum relay feerate, so it is rejected as reconsiderable. The same peer's child is then kept as an orphan. Two `sendrawtransaction` calls put the real parent and the child in the mempool. When the peer sends the padded parent again, the node aborts at net_processing.cpp:3477. With this PR the child leaves the orphanage at that point and stays in the mempool.\n\nIn the same scenario the child stays in the orphanage after both `sendrawtransaction` calls, with or without this PR, until the padded parent pairs with it. That is the reactive cleanup ismaelsadeeq pointed out, and I agree with keeping a mempool hook out of this PR.\n\nI tested the head merged into master a773e1e33b, in a Debug build, with p2p_orphan_handling (the new case aborts the node on master), p2p_opportunistic_1p1c and the txdownload and txpackage unit tests. I ran them again with #36594 and #36437 merged on top. I can share the functional test as a gist.",
   "id": "c:6395070625",
   "url": "https://github.com/bitcoin/bitcoin/pull/36578#issuecomment-6395070625",
   "hash": "c29672302d46939d",
   "edited": null
  }
 ],
 "labels_log": [
  {
   "t": "2026-10-19T21:08:48Z",
   "action": "labeled",
   "label": "P2P",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-10-21T15:05:36Z",
   "kind": "milestoned",
   "who": "fanquake"
  }
 ],
 "text_chars": 6413,
 "text_tokens_estimate": 1603,
 "changed_paths": [
  "src/net_processing.cpp",
  "src/node/txdownloadman_impl.cpp",
  "test/functional/p2p_orphan_handling.py"
 ],
 "files": [
  {
   "path": "src/net_processing.cpp",
   "add": 2,
   "del": 1
  },
  {
   "path": "src/node/txdownloadman_impl.cpp",
   "add": 6,
   "del": 0
  },
  {
   "path": "test/functional/p2p_orphan_handling.py",
   "add": 25,
   "del": 0
  }
 ],
 "test_lines": 25,
 "git": {
  "head": "a93bdc83202e00375b643e6df3b9daa801f7de23",
  "head_matches_backup": true,
  "base": "d0d1e49c8c1e5582056e8a843229507871f8d1b8",
  "patch_id": "a7d1c21e4ebd6db6",
  "commits": [
   {
    "sha": "a93bdc8320",
    "subject": "p2p: clean up a 1p1c package child that is already in the mempool",
    "files": 3,
    "add": 33,
    "del": 1
   }
  ],
  "patch_truncated": false
 },
 "patch_id": "a7d1c21e4ebd6db6",
 "input_hash": "298748e5ec864463",
 "extracted_at": "2026-11-02T01:09:49+00:00"
}