{
 "number": 36540,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36540",
 "title": "mempool: remove time-based transaction expiry",
 "author": "ismaelsadeeq",
 "author_association": "MEMBER",
 "created_at": "2026-10-15T09:14:08Z",
 "updated_at": "2026-10-27T12:58:16Z",
 "description_hash": "29aa5e2d291da9a8",
 "age_days": 17,
 "draft": false,
 "labels": [
  "Mempool"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "1556184fbdd5fda5a21371872ed0265176d854da",
 "head_ref": "10-2026-remove-mempool-expiry",
 "head_repo": "ismaelsadeeq/bitcoin",
 "head_history": [],
 "additions": 46,
 "deletions": 233,
 "changed_files": 20,
 "commit_count": 3,
 "size_bucket": "M",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "stickies-v",
      "url": "https://github.com/bitcoin/bitcoin/pull/36540#issuecomment-6216612525"
     },
     {
      "login": "maflcko",
      "url": "https://github.com/bitcoin/bitcoin/pull/36540#issuecomment-6219869245"
     },
     {
      "login": "tumutech",
      "url": "https://github.com/bitcoin/bitcoin/pull/36540#issuecomment-6221116492"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {
  "stickies-v": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-10-16T13:31:56Z",
   "stale": false
  },
  "maflcko": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-10-16T16:22:30Z",
   "stale": false
  },
  "tumutech": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-10-16T17:33:05Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 3,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "ajtowns",
   "fametrano",
   "maflcko",
   "musaHaruna",
   "stickies-v",
   "tumutech"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-10-15T08:09:24Z",
  "last_reviewer_activity": "2026-10-27T12:58:15Z",
  "last_reviewer": "musaHaruna",
  "author_silent_days": 17,
  "waiting_on_author_days": 5,
  "days_since_update": 5
 },
 "refs": {
  "mentioned": [
   32521,
   33510,
   34075
  ],
  "depends_on": [],
  "fixes": [
   33510
  ],
  "linked_issues": [
   {
    "number": 33510,
    "type": "issue",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "Mempool Expiry eviction might remove txs that could be mined in the next block"
   }
  ],
  "references": [
   {
    "number": 33510,
    "type": "issue",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "Mempool Expiry eviction might remove txs that could be mined in the next block"
   },
   {
    "number": 32521,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2025-07-18",
    "title": "policy: make pathological transactions packed with legacy sigops non-standard"
   },
   {
    "number": 34075,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-08-21",
    "title": "fees: Introduce Mempool Based Fee Estimation to reduce overestimation"
   }
  ],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/txmempool.cpp"
 ],
 "body": "This is a simple mempool PR that fixes #33510.\n\nEvicting mempool transactions by age (`-mempoolexpiry`, default two weeks) is\nincentive-incompatible: it may drop transactions that could still be mined\nAs discussed IRL and in #33510, this PR hence removes age-based eviction entirely.\n\nTransactions now leave the mempool only via size limiting (`-maxmempool`),\nreplacement, conflicts, or inclusion in a block. The `EXPIRY` removal reason is\ndropped. `-mempoolexpiry` is retained as a hidden no-op that warns when set, so\nexisting configurations keep starting.\n\nNice side effects:\n- With expiry gone, the `entry_time` index has no readers. Removing it saves an\n  ordered-tree node per mempool entry and drops a now unused Boost\n  include.\n- The `DynamicMemoryUsage()` per-entry estimate drops from three indexes to\n  two, so within the same `-maxmempool` budget the mempool fits slightly more\n  transactions.",
 "commits": [
  {
   "sha": "8d4b1abbece5f62d9d79253cf4cf06af9b413622",
   "date": "2026-10-15T08:07:38Z",
   "message": "test: stop relying on mempool expiry in tests\n\nRework the tests that exercise time-based mempool expiry so they no\nlonger depend on it, before the feature is removed: trigger\nmempool:removed via RBF in the USDT test, check chain rebroadcast via\npeer invs in the wallet resend test, and drop the expiry knobs from the\nmempool fuzz targets."
  },
  {
   "sha": "d40e511d9d6742771190bff38df2085cb138ed00",
   "date": "2026-10-15T08:08:36Z",
   "message": "mempool: remove time-based transaction expiry\n\nEvicting transactions purely by age (-mempoolexpiry, default two weeks)\nis incentive-incompatible: it can drop still-mineable transactions, such\nas a low-feerate parent waiting on a fee-bumping child. Remove it;\n-mempoolexpiry is kept as a no-op that warns when set."
  },
  {
   "sha": "1556184fbdd5fda5a21371872ed0265176d854da",
   "date": "2026-10-15T08:09:24Z",
   "message": "mempool: remove now-unused entry_time index and includes\n\nExpire() was the entry_time index's only user; drop the index, its tag\nand comparator, and the boost headers only it needed. Also tidy includes:\nmove util/time.h into txmempool.h, drop <chrono>/<memory> from\nmempool_args.cpp, and remove the now-unused identity.hpp from\nEXPECTED_BOOST_INCLUDES."
  }
 ],
 "timeline": [
  {
   "t": "2026-10-16T01:41:22Z",
   "kind": "comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "text": "I don't think it's a good idea to change the node's behaviour in a way that doesn't allow users/miners to revert to the previous default in case the change turns out to be undesirable or harmful for some or many users.\n\nIs there any reason why this change shouldn't be phased in via setting `DEFAULT_MEMPOOL_EXPIRY_HOURS` to a large value, such as 35040 (4 years), instead?",
   "id": "c:6206090292",
   "url": "https://github.com/bitcoin/bitcoin/pull/36540#issuecomment-6206090292",
   "hash": "1da7a6345c9fad26",
   "edited": null
  },
  {
   "t": "2026-10-16T13:31:56Z",
   "kind": "comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "text": "Concept ACK\n\nNode operators should assume that at any point in time, and for any duration of time, their mempool can consume up to `-maxmempool` of resources. Of course, we should make the mempool maximally useful within that constraint, and transactions that should (from the node's perspective) get mined, but don't, can affect its ability to hold transactions that actually will/can get mined.\n\nThe main downside from this removal is that in a non-adversarial case (an adversary can just rebroadcast every 2 weeks), transactions can remain in the mempool forever with a high enough fee rate, e.g. in case of diverging policy or consensus rules, and this reduces a node's effective mempool capacity. While that is not ideal, I also don't think we need to have startup options that make it easier to run a node that is out of sync with the policy and consensus rules that the network actually operates on.\n\nI think the other historically mentioned reasons don't hold up anymore either:\n- it's nice to be able to replace a large, low fee rate transaction without having to pay the RBF \"tax\", but RBF mitigates free relay for a reason, I don't think we need a carve-out, even a conservatively rate-limited one\n- pinning attacks also seem largely irrelevant, given the 2 week delay and the fact that we have TRUC now\n\n[quoted text omitted]\nI think it's nice that we can clean up the code right away. If people can come up with good reasons to use this setting, it might make sense to keep it, or deprecate rather than remove. But if we can't, then I think it's sensible to remove it?",
   "id": "c:6216612525",
   "url": "https://github.com/bitcoin/bitcoin/pull/36540#issuecomment-6216612525",
   "hash": "73665333e619b53c",
   "edited": null
  },
  {
   "t": "2026-10-16T16:22:30Z",
   "kind": "comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "text": "Concept ACK, per https://github.com/bitcoin/bitcoin/pull/16409 :sweat_smile:",
   "id": "c:6219869245",
   "url": "https://github.com/bitcoin/bitcoin/pull/36540#issuecomment-6219869245",
   "hash": "24decc82f0d3d30b",
   "edited": null
  },
  {
   "t": "2026-10-16T17:33:05Z",
   "kind": "comment",
   "who": "tumutech",
   "assoc": "NONE",
   "text": "Concept ACK",
   "id": "c:6221116492",
   "url": "https://github.com/bitcoin/bitcoin/pull/36540#issuecomment-6221116492",
   "hash": "e8334cc43879b73c",
   "edited": null
  },
  {
   "t": "2026-10-16T18:19:59Z",
   "kind": "review_comment",
   "who": "tumutech",
   "assoc": "NONE",
   "path": "src/txmempool.cpp",
   "commit": "d40e511d9d6742771190bff38df2085cb138ed00",
   "in_reply_to": null,
   "text": "@ismaelsadeeq  since you removed entry_time index meaning we are left with 2 indexes.\n2 x 3 = 6 pointers.\nwould it be fine to use 6 in MallocUsage() so that the same -maxmempool budget can hold extra txn you talked about in the nice side-effect?\n\n[quoted text omitted]",
   "id": "rc:4297923468",
   "url": "https://github.com/bitcoin/bitcoin/pull/36540#discussion_r4297923468",
   "hash": "2bfa59ab04635a66",
   "edited": null
  },
  {
   "t": "2026-10-25T07:35:33Z",
   "kind": "comment",
   "who": "fametrano",
   "assoc": "NONE",
   "text": "Two things look unfinished: the memory estimate still counts three indexes, and `wallet_resendwallettransactions.py` no longer checks that a parent is resubmitted before its child.\n\ntumutech's inline question on the first is still open. `getmempoolinfo` reports `usage` 1192 for one transaction on both 649bc0a (base) and 1556184 (head), since `DynamicMemoryUsage()` still counts 9 pointers per entry. The old resend test expired a parent and child and needed both resubmitted, which works only in insertion order. With that order reversed, base's test fails (`resubmit 1`) and head's passes.\n\nCould this PR use 6 pointers, as tumutech suggests, or drop the \"fits slightly more transactions\" sentence? For the test, restarting with `-persistmempool=0` and checking that both transactions are back would make the order matter again, since the wallet resubmits them on load.\n\nThe rest matches the release note. I measured it with a functional-test script on regtest with mocktime, and the touched functional tests and the unit suite pass on head on macOS (the USDT test skipped). Head keeps transactions past 336 h, loads older `mempool.dat` entries with their entry time, and warns that `-mempoolexpiry` has no effect. One change may deserve a line in the note: `abandontransaction` on a wallet transaction older than two weeks now fails with \"Transaction not eligible for abandonment\". On base it worked after expiry, until the wallet's next resubmission, within 36 h, put it back. I can share the script as a gist.",
   "id": "c:6394943202",
   "url": "https://github.com/bitcoin/bitcoin/pull/36540#issuecomment-6394943202",
   "hash": "c6de6579ecd1f708",
   "edited": null
  },
  {
   "t": "2026-10-27T12:58:15Z",
   "kind": "review",
   "who": "musaHaruna",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "1556184fbdd5fda5a21371872ed0265176d854da",
   "text": "I've spent some time going through the discussion in[ #33510](https://github.com/bitcoin/bitcoin/issues/33510) and the history of mempool expiry. I agree that removing transactions simply because they've been in the mempool for 14 days isn't ideal, especially when an old parent could become attractive through a new high-fee child.\n\nMy main concern with removing expiry entirely is the possibility of retaining high-feerate transactions that miners won't include, for example when an outdated node accepts transactions that violate newer policies adopted by miners, such as[ #32521](https://github.com/bitcoin/bitcoin/pull/32521). `TrimToSize()` wouldn't necessarily help here, since those transactions are still highly ranked locally. Under memory pressure, they could displace lower-feerate transactions that miners would actually include, potentially causing more `SIZELIMIT` removals and raising the minimum mempool feerate.\n\nI agree with **stickies-v** that this mostly concerns outdated nodes, and expiry isn't particularly effective against an attacker who keeps rebroadcasting. However, it does provide eventual recovery from a one-time attack or persistent policy disagreement when the transactions are no longer being submitted.\n\nThe data shared by **0xB10C** shows that expiry removes transactions that can later be mined, but I'm not sure it tells us enough about the tradeoff. How often does expiry remove economically attractive transactions or CPFP packages, compared with how often it actually frees capacity occupied by transactions miners won't include? And would removing it lead to more size-based evictions or a higher minimum mempool feerates under pressure?\n\nLooking at the hybrid approach suggested by **Ismaelsadeeq**, i.e combining transaction age with missed mining opportunities, possibly only under memory pressure. I'm leaning toward exploring this longer term, but it has its own problems. Local template inclusion doesn't establish that miners received or rejected a transaction, and counting missed opportunities could penalize censored transactions. There are also the costs of template construction and the question of what happens to previous observations when a parent receives a new high-fee child.\n\nThere's also the interaction with fee estimation following[ #34075](https://github.com/bitcoin/bitcoin/pull/34075). Persistently unmined, high-feerate transactions could affect the mempool-based estimator despite its health checks. But I think that such transactions for estimation don't require removing them from the mempool. I think this is worth investigating separately, rather than using fee estimation as a reason to retain expiry.\n\nGiven this, I'm leaning toward deprecating `-mempoolexpiry` rather than removing its functionality immediately, with age expiry disabled by default but still available explicitly during the deprecation period. This would preserve a recovery option for operators while giving us a chance to understand the consequences of the changed default.\n\nFinally, Is there already evidence that the protection expiry provides against persistent policy disagreement is negligible enough to justify removing the mechanism entirely, rather than just changing the default?\n\nI'd appreciate corrections in case I missed something.",
   "id": "r:5667388831",
   "url": "https://github.com/bitcoin/bitcoin/pull/36540#pullrequestreview-5667388831",
   "hash": "790f524a359f5ad3",
   "edited": null
  }
 ],
 "labels_log": [
  {
   "t": "2026-10-15T09:14:13Z",
   "action": "labeled",
   "label": "Mempool",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 9079,
 "text_tokens_estimate": 2269,
 "changed_paths": [
  "doc/release-notes-xxxxx.md",
  "src/init.cpp",
  "src/kernel/mempool_options.h",
  "src/kernel/mempool_removal_reason.cpp",
  "src/kernel/mempool_removal_reason.h",
  "src/node/mempool_args.cpp",
  "src/node/mempool_persist.cpp",
  "src/test/fuzz/package_eval.cpp",
  "src/test/fuzz/tx_pool.cpp",
  "src/txmempool.cpp",
  "src/txmempool.h",
  "src/validation.cpp",
  "src/validation.h",
  "src/validationinterface.h",
  "test/functional/interface_usdt_mempool.py",
  "test/functional/mempool_expiry.py",
  "test/functional/test_framework/messages.py",
  "test/functional/test_runner.py",
  "test/functional/wallet_resendwallettransactions.py",
  "test/lint/lint-includes.py"
 ],
 "files": [
  {
   "path": "doc/release-notes-xxxxx.md",
   "add": 5,
   "del": 0
  },
  {
   "path": "src/init.cpp",
   "add": 4,
   "del": 1
  },
  {
   "path": "src/kernel/mempool_options.h",
   "add": 0,
   "del": 4
  },
  {
   "path": "src/kernel/mempool_removal_reason.cpp",
   "add": 0,
   "del": 1
  },
  {
   "path": "src/kernel/mempool_removal_reason.h",
   "add": 0,
   "del": 1
  },
  {
   "path": "src/node/mempool_args.cpp",
   "add": 0,
   "del": 5
  },
  {
   "path": "src/node/mempool_persist.cpp",
   "add": 2,
   "del": 5
  },
  {
   "path": "src/test/fuzz/package_eval.cpp",
   "add": 0,
   "del": 1
  },
  {
   "path": "src/test/fuzz/tx_pool.cpp",
   "add": 0,
   "del": 7
  },
  {
   "path": "src/txmempool.cpp",
   "add": 0,
   "del": 18
  },
  {
   "path": "src/txmempool.h",
   "add": 4,
   "del": 24
  },
  {
   "path": "src/validation.cpp",
   "add": 0,
   "del": 5
  },
  {
   "path": "src/validation.h",
   "add": 0,
   "del": 1
  },
  {
   "path": "src/validationinterface.h",
   "add": 0,
   "del": 1
  },
  {
   "path": "test/functional/interface_usdt_mempool.py",
   "add": 8,
   "del": 12
  },
  {
   "path": "test/functional/mempool_expiry.py",
   "add": 0,
   "del": 117
  },
  {
   "path": "test/functional/test_framework/messages.py",
   "add": 0,
   "del": 2
  },
  {
   "path": "test/functional/test_runner.py",
   "add": 0,
   "del": 1
  },
  {
   "path": "test/functional/wallet_resendwallettransactions.py",
   "add": 23,
   "del": 26
  },
  {
   "path": "test/lint/lint-includes.py",
   "add": 0,
   "del": 1
  }
 ],
 "test_lines": 198,
 "git": {
  "head": "1556184fbdd5fda5a21371872ed0265176d854da",
  "head_matches_backup": true,
  "base": "649bc0a645eb127a9b1e6d078f8d129be6380c87",
  "patch_id": "10cf3d30449330fb",
  "commits": [
   {
    "sha": "8d4b1abbec",
    "subject": "test: stop relying on mempool expiry in tests",
    "files": 4,
    "add": 31,
    "del": 46
   },
   {
    "sha": "d40e511d9d",
    "subject": "mempool: remove time-based transaction expiry",
    "files": 15,
    "add": 14,
    "del": 164
   },
   {
    "sha": "1556184fbd",
    "subject": "mempool: remove now-unused entry_time index and includes",
    "files": 4,
    "add": 1,
    "del": 23
   }
  ],
  "patch_truncated": false
 },
 "patch_id": "10cf3d30449330fb",
 "input_hash": "84ba51721867fa0c",
 "extracted_at": "2026-11-02T01:09:49+00:00"
}