{
 "number": 36015,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36015",
 "title": "txorphanage: bound orphan memory by storing transactions serialized",
 "author": "brunoerg",
 "author_association": "MEMBER",
 "created_at": "2026-08-18T21:11:30Z",
 "updated_at": "2026-09-14T02:00:23Z",
 "age_days": 29,
 "draft": false,
 "labels": [],
 "milestone": null,
 "base": "master",
 "head_sha": "e9381aac1570875f395c1f8f9169179726534e06",
 "head_ref": "2026-08-orphanage-mem-bug",
 "head_repo": "brunoerg/bitcoin",
 "head_history": [
  {
   "t": "2026-08-18T23:19:10Z",
   "sha": "eca91cf0090348451c7c63b08d222f18b8dfafaa"
  },
  {
   "t": "2026-08-19T00:08:53Z",
   "sha": "d48b8afcaee6d08cc5e0c00262d0b7defdc6009f"
  },
  {
   "t": "2026-08-25T17:20:07Z",
   "sha": "c377e4af3002af716092efb79cb009377a380768"
  },
  {
   "t": "2026-09-09T18:38:46Z",
   "sha": "0ac8f3135393b47aedd5bc5a0333991a10bfa3b3"
  },
  {
   "t": "2026-09-09T18:41:23Z",
   "sha": "804ff362df45ec6e6245126cbf2af18491c0fbc1"
  },
  {
   "t": "2026-09-09T18:58:14Z",
   "sha": "15b3f85567cbbd41714df1409ae855c8bd745698"
  },
  {
   "t": "2026-09-09T19:25:37Z",
   "sha": "b5e921c62daa1bc194455e1fe41b405101322e56"
  },
  {
   "t": "2026-09-09T19:58:04Z",
   "sha": "388c536b66fc14827535cd3b8dbe8ce7db74979b"
  },
  {
   "t": "2026-09-10T19:22:26Z",
   "sha": "e9381aac1570875f395c1f8f9169179726534e06"
  }
 ],
 "additions": 434,
 "deletions": 146,
 "changed_files": 10,
 "commit_count": 7,
 "size_bucket": "L",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "w0xlt",
      "url": "https://github.com/bitcoin/bitcoin/pull/36015#issuecomment-5334161944"
     },
     {
      "login": "l0rinc",
      "url": "https://github.com/bitcoin/bitcoin/pull/36015#issuecomment-5335302033"
     },
     {
      "login": "Crypt-iQ",
      "url": "https://github.com/bitcoin/bitcoin/pull/36015#issuecomment-5461045168"
     }
    ],
    "stale_ack": [
     {
      "login": "jeanpablojp",
      "url": "https://github.com/bitcoin/bitcoin/pull/36015#pullrequestreview-4994649322"
     }
    ]
   },
   "conflicts": [
    {
     "number": 35919,
     "title": "p2p: avoid orphanage abort at high peer counts",
     "author": "l0rinc"
    },
    {
     "number": 35581,
     "title": "node: add block template manager and track waitNext fee inflow",
     "author": "ismaelsadeeq"
    },
    {
     "number": 35569,
     "title": "Encapsulation for CTransaction",
     "author": "purpleKarrot"
    }
   ]
  }
 },
 "acks_parsed": {
  "w0xlt": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-08-18T21:15:49Z",
   "stale": false
  },
  "l0rinc": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-08-18T23:08:32Z",
   "stale": false
  },
  "Crypt-iQ": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-08-29T07:24:30Z",
   "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": [
   "Crypt-iQ",
   "instagibbs",
   "jeanpablojp",
   "l0rinc",
   "w0xlt"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-09-10T19:22:26Z",
  "last_reviewer_activity": "2026-09-08T21:43:25Z",
  "last_reviewer": "instagibbs",
  "author_silent_days": 6,
  "waiting_on_author_days": 0,
  "days_since_update": 3
 },
 "refs": {
  "mentioned": [
   35919,
   35923
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 35919,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "p2p: avoid orphanage abort at high peer counts"
   },
   {
    "number": 35923,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "mempool: count unbroadcast txids in memory usage"
   }
  ],
  "conflicts": [
   35919,
   35581,
   35569
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/node/txorphanage.cpp",
  "src/test/orphanage_tests.cpp",
  "test/functional/p2p_orphan_memory_accounting.py"
 ],
 "body": "The orphanage limits the \"usage\" of the orphans it stores, per peer and globally, to bound the amount of memory an attacker can make us hold on to. It uses weight as a proxy for that memory, on the assumption that weight is \"often higher than the actual memory usage of the transaction\".\n\nThat assumption does not hold for a deserialized transaction. Every witness stack element is an individually heap-allocated vector, costing its 24-byte slot in the stack vector plus a 32-byte minimum allocation, while only weighing 2WU. 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.\n\nRather than change the accounting metric, keep orphans in serialized form, deserializing them again on the paths that hand them back out. Serialized, a transaction's memory usage is bounded by its weight, so the existing weight-based accounting becomes a true upper bound on memory and the worst case is the peers' combined allowances.\n\nAdmission, eviction and accounting behavior are unchanged: no transaction that was previously accepted is refused, so orphan resolution (and thus 1p1c package relay) keeps working for standard-weight transactions whose witnesses consist of many small elements, such as BitVM-style transactions.",
 "commits": [
  {
   "sha": "9d6e93f5c7dc5afd08e872d9c7e39623c487a9c8",
   "date": "2026-09-10T19:22:14Z",
   "message": "iwyu: fix includes in node/txorphanage.h\n\nDrop includes the header does not use, add the ones it does, and forward\ndeclare FastRandomContext. The IWYU job checks the files a change touches,\nand the following commits modify this header."
  },
  {
   "sha": "f9643f9347a9f29cb1c503b401f668e3377c3675",
   "date": "2026-09-10T19:22:14Z",
   "message": "scripted-diff: rename DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER\n\nThis constant is the default for TxOrphanage::m_reserved_usage_per_peer,\nreturned by ReservedPeerUsage() and compared against UsageByPeer(), all\nof which are named after the \"usage\" they bound (of type\nTxOrphanage::Usage). Name the constant after that same quantity rather\nthan after weight, the metric that happens to measure it, so that it\nmatches the field it initializes and the rest of the usage-based API.\n\nThe orphanage keeps accounting usage by weight, so this is a pure\nnaming change with no change in behavior.\n\n-BEGIN VERIFY SCRIPT-\nsed -i 's/DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER/DEFAULT_RESERVED_ORPHAN_USAGE_PER_PEER/g' $(git grep -l DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER)\n-END VERIFY SCRIPT-"
  },
  {
   "sha": "f4d43b530a142fea0db9488028c5e510bf78d1ee",
   "date": "2026-09-10T19:22:14Z",
   "message": "txorphanage: add GetOrphanUsage() and cache it per announcement\n\nThe orphanage's notion of an orphan's \"usage\" is currently duplicated in\nthe unit tests, the benchmarks and the fuzz targets, all of which call\nGetTransactionWeight() to predict what the orphanage will account. Move\nthat knowledge into a single function so that a change of metric only\nhas to happen in one place, and so that all users agree on it.\n\nAlso cache the value in the Announcement instead of recomputing it on\nevery operation. Announcements hold an immutable CTransactionRef, so the\nvalue never changes; caching it guarantees that PeerDoSInfo::Add() and\n::Subtract() always use the same number, and makes Erase() (and therefore\nLimitOrphans()) independent of the transaction's size.\n\nNo behavior change."
  },
  {
   "sha": "87a2791f5438672cba63de2c10b5da1d82425d51",
   "date": "2026-09-10T19:22:14Z",
   "message": "txorphanage: add GetParentTxids() and use it for orphan resolution candidates\n\nAddTxAnnouncement() only needs the deduplicated prevout txids of an\nannounced orphan to consider a peer as an orphan resolution candidate,\nbut obtains them by retrieving the full transaction with GetTx(). Serve\nthem from the orphanage directly, so that handling an announcement of a\nknown orphan does not depend on how the orphanage stores the\ntransaction. The following commit stores orphans in serialized form,\nwhich would otherwise make every such announcement deserialize the\norphan."
  },
  {
   "sha": "0557bc81d577ab3ed4354e79dfa6bf5b5a2f0c6c",
   "date": "2026-09-10T19:22:14Z",
   "message": "txorphanage: return orphan ids from GetChildrenFromSamePeer\n\nFind1P1CPackage only needs each candidate child's txid and wtxid to\ncheck the reject filters, and materializes at most one of them. Have\nGetChildrenFromSamePeer() return those ids instead of a CTransactionRef\nper child, add GetPackageHashFromWtxids() so the package-reject check\ncan be done from ids, and look up the selected child with GetTx().\n\nThis prepares for storing orphans serialized: with this change, finding\na 1P1C package deserializes only the child that is actually selected.\nOtherwise a peer holding ~40MB of witness-heavy orphans (many tiny\nwitness elements, each a separate heap allocation once deserialized)\ncould make a single call cost ~0.9s of CPU and ~700MB of memory.\n\nCo-authored-by: Greg Sanders <gsanders87@gmail.com>"
  },
  {
   "sha": "933a9f95af2ec52e0804dd8f66d229e49e53c426",
   "date": "2026-09-10T19:22:14Z",
   "message": "txorphanage: store orphans serialized, making weight bound their memory\n\nThe orphanage limits the \"usage\" of the orphans it stores, per peer and\nglobally, to bound the amount of memory an attacker can make us hold on\nto, using weight as a proxy for that memory on the assumption that it\nis \"often higher than the actual memory usage of the transaction\".\n\nThat assumption does not hold for a deserialized transaction: every\nwitness stack element is an individually heap-allocated vector, costing\nits 24-byte slot in the stack vector plus a 32-byte minimum allocation,\nwhile only weighing 2WU. A transaction of 199,000 1-byte witness\nelements weighs 398,247WU (i.e. it is of standard weight, and witness\nstandardness cannot be checked while the inputs are missing), but uses\n11.1MB of memory: 28 times what is accounted for it, and one such\norphan can be retained per peer.\n\nKeep orphans in serialized form instead, deserializing them again on\nthe paths that hand them back out, all of which feed into full\n(re)validation or RPC whose cost dwarfs a deserialization. Serialized,\na transaction's memory usage is bounded by its weight, so the existing\nweight-based accounting becomes a true upper bound on memory and the\nworst case is the peers' combined allowances. Admission, eviction and\naccounting behavior are unchanged: no transaction that was previously\naccepted is refused, so orphan resolution (and thus 1p1c package relay)\nkeeps working for standard-weight transactions whose witnesses consist\nof many small elements, such as BitVM-style transactions.\n\nThe remaining per-orphan overhead not covered by weight (the entry in\nm_orphans, the entries in m_outpoint_to_orphan_wtxids, and the cached\nprevouts and hashes) is bounded by the latency score limits."
  },
  {
   "sha": "e9381aac1570875f395c1f8f9169179726534e06",
   "date": "2026-09-10T19:22:14Z",
   "message": "test: cover storing a witness-heavy orphan at its serialized cost\n\nA standard-weight transaction of 199,000 1-byte witness elements, which\nwould use over 10 times its weight in memory if stored deserialized, is\nstored (accounted its weight, which now bounds its memory), fits within\nthe announcer's reservation alongside normal orphans, and round-trips\nthrough the orphanage intact."
  }
 ],
 "timeline": [
  {
   "t": "2026-08-18T21:15:49Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK."
  },
  {
   "t": "2026-08-18T23:08:32Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "text": "Concept ACK\nThe failing orphanage tests seem related (the musl one is the new test, the fuzz ones need a rebase, iwyu is the bench) and 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?\nEspecially since #35923 is related and complements this change, and #35919 is also touching the same area."
  },
  {
   "t": "2026-08-18T23:19:10Z",
   "kind": "force_push",
   "who": "brunoerg",
   "commit": "eca91cf0090348451c7c63b08d222f18b8dfafaa"
  },
  {
   "t": "2026-08-18T23:22:05Z",
   "kind": "comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nMy bad on that, forgot to push since my latest local change. Just did it."
  },
  {
   "t": "2026-08-19T00:08:53Z",
   "kind": "force_push",
   "who": "brunoerg",
   "commit": "d48b8afcaee6d08cc5e0c00262d0b7defdc6009f"
  },
  {
   "t": "2026-08-19T11:33:51Z",
   "kind": "comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "text": "Ready for review."
  },
  {
   "t": "2026-08-21T15:03:30Z",
   "kind": "review",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "d48b8afcaee6d08cc5e0c00262d0b7defdc6009f",
   "text": "tACK d48b8afcaee6d08cc5e0c00262d0b7defdc6009f\n\nReverted `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 none.\n\nI also checked the two numbers in the `MAX_ORPHAN_TX_USAGE` comment and got 1.24x and 1.47x, matching.\n\nLeft a comment on `send_orphan()` in the new test, nothing blocking."
  },
  {
   "t": "2026-08-21T15:03:30Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "test/functional/p2p_orphan_memory_accounting.py",
   "commit": "d48b8afcaee6d08cc5e0c00262d0b7defdc6009f",
   "in_reply_to": null,
   "text": "This `wait_until` matches the getdata from the previous call, since `last_message` isn't cleared. From the second `send_orphan()` on, the getdata doesn't hold the current tx's wtxid, so the tx goes out unrequested and the inv/getdata path is only exercised on the first call.\n\n`wait_for_getdata([int(tx.wtxid_hex, 16)])` fixes it, which is what `p2p_orphan_handling.py` does. Swapped it here and it still passes."
  },
  {
   "t": "2026-08-24T09:28:11Z",
   "kind": "comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "text": "I'm pretty worried that the new behavior would cause subtle user breakage, epsecially considering something BitVM-like (with lots of 20b and 1b elements in witness data i.e. winternitz sigs).\n\nRather than change the metrics entirely, could you consider an alternative where the stored transactions are in serialized form, and deserialized only when required? https://github.com/instagibbs/bitcoin/tree/2026-08-orphanage-serialized-weight\n\nIt's significantly less code, doesn't require the weight metric to be swapped out (it would truly be an overestimation now modulo small constant), limiting the scope of the change. The one cost is the serialize<->deserialize that would be added on round-tripping. From my benchmarks it looks like a non-adversarial large tx would take ~1.5ms on `ReconsiderTx`, and a BitVM-like one ~3ms. The other costs look negligible.\n\nLet me know what you think. I'm happy for you to take the code, or to open my own PR."
  },
  {
   "t": "2026-08-24T21:05:07Z",
   "kind": "comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "text": "@instagibbs I was curious so I built your branch, and I'm sharing some numbers I got in my tests.\n\nThe timing below is for rebuilding the transaction, one input with N items of k bytes.\n\n```\nwitness stack         weight    deserialize   with teardown\n100 x 80B              8,343        0.07 ms         0.08 ms\n1 x 380,000B         380,248         2.4 ms          2.4 ms\n8,000 x 20B          168,245         2.2 ms          2.8 ms\n199,000 x 1B         398,247          28 ms           42 ms\n```\n\nYour ~3ms checks out. It's the item count driving this and not the bytes, and it's paid once, both of the paths a peer can trigger consume the orphan.\n\nThe limit here is 600,000 bytes of memory, not weight. Varying the item size, this is the weight at which it's reached.\n\n```\nitem size    stored up to    % of the range to 400,000 WU lost\n  1 byte       21,661 WU                  94.6%\n 20 bytes     175,133 WU                  56.2%\n 64 bytes     375,035 WU                   6.2%\n 65 bytes     330,047 WU                  17.5%\n```\n\n64 to 65 is a MallocUsage step, so the threshold tracks the allocator's buckets and not the transaction.\n\nI tested on x86-64, best case of several runs."
  },
  {
   "t": "2026-08-25T11:01:25Z",
   "kind": "comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "text": "Note that it's a pretty expensive \"attack\" in that to cause deserialization in the orphanage you'd have to enter in a valid parent tx into the mempool."
  },
  {
   "t": "2026-08-25T12:11:24Z",
   "kind": "comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nGood point. I haven't tried that approach, but it seems simpler and achieves the same. I'll take a look at your branch, but you can open the PR and move on, no problem. Happy to review."
  },
  {
   "t": "2026-08-25T12:12:32Z",
   "kind": "comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "text": "@brunoerg I'm a little busy, please take it on"
  },
  {
   "t": "2026-08-25T17:20:07Z",
   "kind": "force_push",
   "who": "brunoerg",
   "commit": "c377e4af3002af716092efb79cb009377a380768"
  },
  {
   "t": "2026-08-25T17:24:31Z",
   "kind": "comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "text": "Force-pushed addressing @instagibbs' approach."
  },
  {
   "t": "2026-08-25T19:45:59Z",
   "kind": "comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "text": "title and OP will need updating :+1:"
  },
  {
   "t": "2026-08-25T20:15:45Z",
   "kind": "comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nDone."
  },
  {
   "t": "2026-08-26T11:00:32Z",
   "kind": "review",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "c377e4af3002af716092efb79cb009377a380768",
   "text": "Built the merge with master and tested again."
  },
  {
   "t": "2026-08-26T11:00:32Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "src/node/txorphanage.cpp",
   "commit": "c377e4af3002af716092efb79cb009377a380768",
   "in_reply_to": null,
   "text": "Every matching child is rebuilt before `Find1P1CPackage` looks at the first one. With 94 orphans of 199,000 1-byte items, one per peer, all announced by a single peer, that's 1,047.6 MB live against the 37.4 MB accounted for it, where one at a time would be 11.1 MB.\n\nThe trigger costs no fee, `min relay fee not met` already returns `TX_RECONSIDERABLE`."
  },
  {
   "t": "2026-08-26T11:00:32Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/orphanage_tests.cpp",
   "commit": "e9381aac1570875f395c1f8f9169179726534e06",
   "in_reply_to": null,
   "text": "This test passes without the change. Reverting just `src/node/txorphanage.cpp` to before 493561a34285, the whole suite still passes, since the only difference the API exposes is the identity of the returned object.\n\n```suggestion\n    BOOST_CHECK(ptx_out != ptx);\n    BOOST_CHECK(ptx_out->GetWitnessHash() == ptx->GetWitnessHash());\n```\n\nFails without the change and passes with it."
  },
  {
   "t": "2026-08-29T07:24:30Z",
   "kind": "comment",
   "who": "Crypt-iQ",
   "assoc": "MEMBER",
   "text": "Concept ACK"
  },
  {
   "t": "2026-09-08T21:43:25Z",
   "kind": "comment",
   "who": "instagibbs",
   "assoc": "MEMBER",
   "text": "When finding potential 1P1C packages, we're now deserializing potentially many orphans, which on my machine can cause ~1s of cpu time at ~1GB of memory usage on top of normal. astra slop for your consideration:\n\nhttps://github.com/instagibbs/bitcoin/commit/847d2dc2a0dc76340e269f3f60977b8a5d6bab2a"
  },
  {
   "t": "2026-09-09T14:19:24Z",
   "kind": "comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nInteresting, I benchmarked it (with and without your suggestion) and got +700 MB of peak (I think around 40MB of stored orphans) without and basically zero with. Will review the code and address it here."
  },
  {
   "t": "2026-09-09T17:46:44Z",
   "kind": "review_comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "path": "src/test/orphanage_tests.cpp",
   "commit": "e9381aac1570875f395c1f8f9169179726534e06",
   "in_reply_to": 3862075438,
   "text": "Good point, I could add a `BOOST_CHECK(ptx_out != ptx)` right after it."
  },
  {
   "t": "2026-09-09T18:38:46Z",
   "kind": "force_push",
   "who": "brunoerg",
   "commit": "0ac8f3135393b47aedd5bc5a0333991a10bfa3b3"
  },
  {
   "t": "2026-09-09T18:41:23Z",
   "kind": "force_push",
   "who": "brunoerg",
   "commit": "804ff362df45ec6e6245126cbf2af18491c0fbc1"
  },
  {
   "t": "2026-09-09T18:48:28Z",
   "kind": "comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "text": "Force-pushed:\n\n- Addressed suggestion from @instagibbs  - fixing `Find1P1CPackage`. I got [instagibbs@847d2dc](https://github.com/instagibbs/bitcoin/commit/847d2dc2a0dc76340e269f3f60977b8a5d6bab2a) but changed some things; most nits that I found and test improvements. I also changed `Find1P1CPackage` to store the `GetTx `result and skips the candidate under `Assume` if it is null.\n\n- Addressed https://github.com/bitcoin/bitcoin/pull/36015#discussion_r3862075438"
  },
  {
   "t": "2026-09-09T18:58:14Z",
   "kind": "force_push",
   "who": "brunoerg",
   "commit": "15b3f85567cbbd41714df1409ae855c8bd745698"
  },
  {
   "t": "2026-09-09T19:19:16Z",
   "kind": "comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "text": "Missing rebase, doing it now."
  },
  {
   "t": "2026-09-09T19:25:37Z",
   "kind": "force_push",
   "who": "brunoerg",
   "commit": "b5e921c62daa1bc194455e1fe41b405101322e56"
  },
  {
   "t": "2026-09-09T19:58:04Z",
   "kind": "force_push",
   "who": "brunoerg",
   "commit": "388c536b66fc14827535cd3b8dbe8ce7db74979b"
  },
  {
   "t": "2026-09-10T03:17:14Z",
   "kind": "comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "text": "CI failure is unrelated"
  },
  {
   "t": "2026-09-10T19:22:26Z",
   "kind": "force_push",
   "who": "brunoerg",
   "commit": "e9381aac1570875f395c1f8f9169179726534e06"
  }
 ],
 "labels_log": [
  {
   "t": "2026-08-18T22:42:06Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-19T01:26:53Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-09T18:43:05Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-10T20:25:31Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-08-18T21:32:41Z",
   "kind": "convert_to_draft",
   "who": "brunoerg"
  },
  {
   "t": "2026-08-19T11:33:50Z",
   "kind": "ready_for_review",
   "who": "brunoerg"
  },
  {
   "t": "2026-08-25T20:14:23Z",
   "kind": "renamed",
   "who": "brunoerg",
   "from": "txorphanage: account memory usage instead of weight",
   "to": "txorphanage: bound orphan memory by storing transactions serialized"
  }
 ],
 "text_chars": 12746,
 "text_tokens_estimate": 3186,
 "changed_paths": [
  "src/bench/txorphanage.cpp",
  "src/node/txdownloadman_impl.cpp",
  "src/node/txorphanage.cpp",
  "src/node/txorphanage.h",
  "src/policy/packages.cpp",
  "src/policy/packages.h",
  "src/test/fuzz/txorphan.cpp",
  "src/test/orphanage_tests.cpp",
  "src/test/txdownload_tests.cpp",
  "src/test/txpackage_tests.cpp"
 ],
 "files": [
  {
   "path": "src/bench/txorphanage.cpp",
   "add": 33,
   "del": 22
  },
  {
   "path": "src/node/txdownloadman_impl.cpp",
   "add": 10,
   "del": 7
  },
  {
   "path": "src/node/txorphanage.cpp",
   "add": 146,
   "del": 54
  },
  {
   "path": "src/node/txorphanage.h",
   "add": 26,
   "del": 8
  },
  {
   "path": "src/policy/packages.cpp",
   "add": 8,
   "del": 2
  },
  {
   "path": "src/policy/packages.h",
   "add": 3,
   "del": 0
  },
  {
   "path": "src/test/fuzz/txorphan.cpp",
   "add": 21,
   "del": 16
  },
  {
   "path": "src/test/orphanage_tests.cpp",
   "add": 118,
   "del": 37
  },
  {
   "path": "src/test/txdownload_tests.cpp",
   "add": 62,
   "del": 0
  },
  {
   "path": "src/test/txpackage_tests.cpp",
   "add": 7,
   "del": 0
  }
 ],
 "test_lines": 316,
 "git": {
  "head": "e9381aac1570875f395c1f8f9169179726534e06",
  "head_matches_backup": true,
  "base": "fc6923cec5b440b611700f6629d8c6a61c6f11bd",
  "commits": [
   {
    "sha": "9d6e93f5c7",
    "subject": "iwyu: fix includes in node/txorphanage.h",
    "files": 1,
    "add": 6,
    "del": 4
   },
   {
    "sha": "f9643f9347",
    "subject": "scripted-diff: rename DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER",
    "files": 4,
    "add": 21,
    "del": 21
   },
   {
    "sha": "f4d43b530a",
    "subject": "txorphanage: add GetOrphanUsage() and cache it per announcement",
    "files": 5,
    "add": 87,
    "del": 54
   },
   {
    "sha": "87a2791f54",
    "subject": "txorphanage: add GetParentTxids() and use it for orphan resolution candidates",
    "files": 3,
    "add": 22,
    "del": 3
   },
   {
    "sha": "0557bc81d5",
    "subject": "txorphanage: return orphan ids from GetChildrenFromSamePeer",
    "files": 9,
    "add": 123,
    "del": 20
   },
   {
    "sha": "933a9f95af",
    "subject": "txorphanage: store orphans serialized, making weight bound their memory",
    "files": 2,
    "add": 133,
    "del": 58
   },
   {
    "sha": "e9381aac15",
    "subject": "test: cover storing a witness-heavy orphan at its serialized cost",
    "files": 1,
    "add": 56,
    "del": 0
   }
  ],
  "patch_truncated": true
 },
 "input_hash": "d96c7530e2091f77",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}