{
 "number": 35671,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/35671",
 "title": "mining: add TxCollection to bandwidth-efficiently validate external block templates",
 "author": "Sjors",
 "author_association": "MEMBER",
 "created_at": "2026-07-06T19:36:53Z",
 "updated_at": "2026-09-16T21:50:26Z",
 "age_days": 72,
 "draft": false,
 "labels": [
  "Mining"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154",
 "head_ref": "2026/03/collect-txs",
 "head_repo": "Sjors/bitcoin",
 "head_history": [
  {
   "t": "2026-07-06T19:50:20Z",
   "sha": "cf3aea1131ac52b13696d3f7d5a1608766eb9c8e"
  },
  {
   "t": "2026-07-07T10:49:15Z",
   "sha": "8106852afc82aace27ccb642a83e79bda7030bc6"
  },
  {
   "t": "2026-07-08T06:38:25Z",
   "sha": "9bb89df02fc3dbcb870b36d4a339986089ea40e2"
  },
  {
   "t": "2026-07-23T18:40:28Z",
   "sha": "b53031224708f287c957b982d555b2bc54c1ee01"
  },
  {
   "t": "2026-07-23T19:08:29Z",
   "sha": "af9a1157f663680b937087ccb4c54ccd0401c7b6"
  },
  {
   "t": "2026-07-24T11:23:48Z",
   "sha": "d0c20776abe1ad14073c3328ef8c64434b0f92c7"
  },
  {
   "t": "2026-08-20T10:36:31Z",
   "sha": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154"
  }
 ],
 "additions": 732,
 "deletions": 57,
 "changed_files": 9,
 "commit_count": 6,
 "size_bucket": "L",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "ismaelsadeeq",
      "url": "https://github.com/bitcoin/bitcoin/pull/35671#issuecomment-4902377507"
     },
     {
      "login": "pablomartin4btc",
      "url": "https://github.com/bitcoin/bitcoin/pull/35671#pullrequestreview-4769906047"
     },
     {
      "login": "enirox001",
      "url": "https://github.com/bitcoin/bitcoin/pull/35671#pullrequestreview-4907232495"
     },
     {
      "login": "jeanpablojp",
      "url": "https://github.com/bitcoin/bitcoin/pull/35671#pullrequestreview-5125552680"
     }
    ]
   },
   "conflicts": [
    {
     "number": 36244,
     "title": "validation, net: Process blocks asynchronously and reduce cs_main contention",
     "author": "w0xlt"
    },
    {
     "number": 36097,
     "title": "mining: replace interrupt methods with cancellation arguments",
     "author": "xyzconstant"
    },
    {
     "number": 35675,
     "title": "mining: add block template manager",
     "author": "ismaelsadeeq"
    },
    {
     "number": 35581,
     "title": "node: add block template manager and track waitNext fee inflow",
     "author": "ismaelsadeeq"
    },
    {
     "number": 35569,
     "title": "Encapsulation for CTransaction",
     "author": "purpleKarrot"
    },
    {
     "number": 33922,
     "title": "mining: add getMemoryLoad() and track template non-mempool memory footprint",
     "author": "Sjors"
    }
   ]
  }
 },
 "acks_parsed": {
  "ismaelsadeeq": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-07-07T09:41:38Z",
   "stale": false
  },
  "pablomartin4btc": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-07-24T03:49:09Z",
   "stale": false
  },
  "enirox001": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-08-11T14:20:56Z",
   "stale": false
  },
  "jeanpablojp": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-09-06T14:07:06Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 4,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "ViniciusCestarii",
   "enirox001",
   "gmaxwell",
   "ismaelsadeeq",
   "jeanpablojp",
   "pablomartin4btc"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-08-20T10:36:54Z",
  "last_reviewer_activity": "2026-09-06T14:07:07Z",
  "last_reviewer": "jeanpablojp",
  "author_silent_days": 28,
  "waiting_on_author_days": 11,
  "days_since_update": 0
 },
 "refs": {
  "mentioned": [
   34020,
   34672
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 34020,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-07-07",
    "title": "mining: add getTransactions(ByWitnessID) IPC methods"
   },
   {
    "number": 34672,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-07-22",
    "title": "mining: add reason/debug to `submitSolution` and unify with `submitBlock`"
   }
  ],
  "conflicts": [
   36244,
   36097,
   35675,
   35581,
   35569,
   33922
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/interfaces/mining.h",
  "src/node/interfaces.cpp",
  "src/node/miner.cpp",
  "src/node/miner.h",
  "test/functional/interface_ipc_mining.py",
  "test/functional/interface_ipc_mining_tx_collection.py"
 ],
 "body": "The Stratum v2 spec defines a [Job Declarator Server](https://stratumprotocol.org/specification/06-job-declaration-protocol) (JDS), whose job it is to approve custom miner templates. It currently maintains its own mempool mirror, which it populates by repeatedly calling `getBlock()` and `waitNext()` and then requesting any missing transactions from the miner (since #34020 it can use `getTransactionsByWitnessID()` to first ask the node).\n\nThe new `TxCollection` interface introduced by this PR removes the need for such a mirror mempool. We build the block template ourselves now, based on the requested transaction list and our own mempool.\n\nThe following flow is demonstrated in https://github.com/stratum-mining/sv2-apps/pull/599:\n\n1. Miner generates a template, e.g. using our `createNewBlock()` IPC method\n2. They send the list of `Wtxid`s to the pool's JDS\n3. JDS calls `TxCollection::collectTxs()` (this PR) with that `Wtxid` list\n4. We check the list against our mempool and hold a `CTransactionRef` for each match\n5. The JDS queries `unknownTxPos()` to learn what's missing in our mempool\n6. The JDS requests the fully serialized missing transactions from the miner (using native Stratum v2 messages)\n7. JDS calls `addMissingTxs()` which completes the collection\n8. JDS calls makeTemplate() which reconstructs the block and validates it, optionally with the coinbase it intends to use (e.g. to check the reward amount); a node-generated dummy is used otherwise. The JDS needs the check to succeed, may relay the BIP-22 failure reason to the miner.\n9. It returns a  `BlockTemplate` which works as if we generated the template ourselves, but with methods like `waitNext()` disabled. The JDS can call `submitSolution()` (the sv2 spec allows for redundant block reconstruction by both the miner node and the JDS).\n\nIf this sounds very similar to compact block relay: it is! In fact, it would be easy to expand the above methods to take transaction short ids instead of `Wtxid`. That may be useful for the [p2pool revival project](https://www.vinteum.org/blog/reviving-p2pool-welcoming-johnny-santos-as-a-vinteum-grantee), which needs a way to validate and relay weak blocks as compact blocks. The p2pool client software would need to (trivially) verify the PoW, but could use the `TxCollection` interface to reconstruct and verify the block content. That said, the SRI [JDS](https://github.com/stratum-mining/sv2-apps/issues?q=is%3Aissue%20state%3Aopen%20JDS) implementation is much further along and likely to be the first consumer of this.\n\nCommits:\n\n- _ipc: add TxCollection scaffold_ - step 3 and 4\n- _mining: add TxCollection unknownTxPos_ - step 5\n- _mining: add TxCollection addMissingTxs_ - step 7\n- _mining: add coinbase transaction helper_ - we need a dummy coinbase to verify the block, so extract a helper from `BlockAssembler::CreateNewBlock()`\n- _mining: make TxCollection create a BlockTemplate_ - step 8 and 9\n- _mining: restrict externally generated templates_ - step 9\n\nPotential followups:\n\n- https://github.com/Sjors/bitcoin/pull/122\n- have `addMissingTxs()` (optionally) insert favorable transactions into our mempool\n- add a data structure to store transactions that don't meet our mempool threshold (so `TxCollection` can grab them, instead of needing another round trip)",
 "commits": [
  {
   "sha": "b100f601bf5fc286a1058aca387aeaf95d27dc39",
   "date": "2026-08-20T09:49:31Z",
   "message": "ipc: add TxCollection scaffold\n\nAdd the TxCollection interface and the node::TxCollection class that\nbacks it. The constructor looks up each requested wtxid in the mempool\nand keeps a reference to any transaction that is already present, so\nlater commits can report which requested transactions are still missing\nand let the client fill them in."
  },
  {
   "sha": "00730be04035dd958570ea919b8f656231f9c3d9",
   "date": "2026-08-20T09:49:31Z",
   "message": "mining: add TxCollection unknownTxPos\n\nCo-authored-by: Enoch Azariah <enirox001@gmail.com>"
  },
  {
   "sha": "a75f17d21fec91fe12dc2e909df36c77e43a3196",
   "date": "2026-08-20T09:49:32Z",
   "message": "mining: add TxCollection addMissingTxs\n\nNow that the collection can be mutated after construction, guard the\ncollected transactions with a mutex: IPC clients may call TxCollection\nmethods concurrently from different threads."
  },
  {
   "sha": "e8a75a3ad304907b346a890cb4e2b805c1098a33",
   "date": "2026-08-20T10:31:16Z",
   "message": "mining: add coinbase transaction helper\n\nMove the coinbase construction out of CreateNewBlock() into a reusable\nBlockAssembler::CreateCoinbaseTx() helper, so a later commit can build a\ncoinbase when assembling a template from externally collected\ntransactions.\n\nThe helper uses a local variable height{pindexPrev.nHeight + 1} to make it a\nconst member function.\n\nThis does not change behavior. Review with --color-moved=dimmed-zebra.\n\nCo-authored-by: Enoch Azariah <enirox001@gmail.com>"
  },
  {
   "sha": "f6732942fa0c18b297fe6c4c5d9771e898e8440e",
   "date": "2026-08-20T10:31:16Z",
   "message": "mining: make TxCollection create a BlockTemplate\n\nAdd TxCollection::makeTemplate(), which assembles the collected\ntransactions, in the requested order, into a block that builds on the\ngiven prevhash, and validates it.\n\nThe block is validated by the same TestBlockValidity() call used by\ncheckBlock(), with the proof-of-work and merkle-root checks disabled.\n\nA dummy coinbase is added only so the transactions can be validated as\npart of a block. Its output pays no fees (fees=0), because the total fee\namount is not tracked here. Clients construct the real coinbase\nthemselves, which is why the next commit disables getCoinbaseTx() for\nexternally generated templates.\n\nWhen the requested prevhash does not match the active tip, we mirror the\ngetblocktemplate proposal reasons: 'stale-prevblk' when prevhash is an\nancestor of the current tip, and 'inconclusive-not-best-prevblk' when it\nis unknown or on a fork."
  },
  {
   "sha": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154",
   "date": "2026-08-20T10:31:17Z",
   "message": "mining: restrict externally generated templates\n\nDisable methods that are not expected to be used in the externally\nprovided template use case.\n\ngetTxFees() and getTxSigops() could be implemented, but it's not\nworth doing since the external software that constructed the\ntemplate is expected to know this.\n\ngetCoinbaseTx() is disabled because the dummy coinbase is only added to\nallow validating the collected transactions as a block. It pays no fees\n(fees=0) and should not be used by clients, which construct the real\ncoinbase themselves."
  }
 ],
 "timeline": [
  {
   "t": "2026-07-06T19:37:44Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "@plebhash it would be very useful to have a (very) rough JDS draft that uses this interface, to see if it actually gets rid of the mirror mempool."
  },
  {
   "t": "2026-07-06T19:50:20Z",
   "kind": "force_push",
   "who": "Sjors",
   "commit": "cf3aea1131ac52b13696d3f7d5a1608766eb9c8e"
  },
  {
   "t": "2026-07-07T09:41:38Z",
   "kind": "comment",
   "who": "ismaelsadeeq",
   "assoc": "MEMBER",
   "text": "Concept ACK\n\n[quoted text omitted]\nThis could be collapsed to the response \"JDS calls TxCollection::collectTxs() (this PR) with that Wtxid list\" to reduce the round trip?"
  },
  {
   "t": "2026-07-07T10:49:15Z",
   "kind": "force_push",
   "who": "Sjors",
   "commit": "8106852afc82aace27ccb642a83e79bda7030bc6"
  },
  {
   "t": "2026-07-07T10:49:32Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "I added an optional `coinbase` argument to `makeTemplate()`. Based on my draft JDS implementation https://github.com/stratum-mining/sv2-apps/pull/599 this does appear useful, e.g. for checking the reward amount. We still generate a dummy if no coinbase transaction is provided.\n\n@ismaelsadeeq `collectTxs()` is the constructor so it needs to return an interface. I don't think it can return both the `TxCollection` interface _and_ the list of missing transactions. But clients [can chain requests](https://capnproto.org/rpc.html) (not sure if we've fully implemented the on the libmultiprocess side).\n\nI can also imagine a (slight) benefit in clients being able to call `unknownTxPos()` multiple times, e.g. maybe in a future upgrade we could have the node request missing transactions from peers - not sure how useful that is though."
  },
  {
   "t": "2026-07-08T06:38:25Z",
   "kind": "force_push",
   "who": "Sjors",
   "commit": "9bb89df02fc3dbcb870b36d4a339986089ea40e2"
  },
  {
   "t": "2026-07-08T06:41:09Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "Rebased after https://github.com/bitcoin/bitcoin/pull/34020:\n- first test commit no longer needed\n- bumped `collectTxs()` to `@10`"
  },
  {
   "t": "2026-07-08T14:29:06Z",
   "kind": "review_comment",
   "who": "ViniciusCestarii",
   "assoc": "CONTRIBUTOR",
   "path": "src/node/miner.cpp",
   "commit": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154",
   "in_reply_to": null,
   "text": "In \"mining: add TxCollection addMissingTxs\" ee1c43e3ceb21fdf38340801221e9fa42c8e6993\n\nAddMissingTxs is order-dependent, a bad entry aborts the loop at that point, so [valid, invalid] keeps valid but [invalid, valid] doesn't. Is this intentional? Correct me if I am wrong but validate-then-apply seems to be a safer approach."
  },
  {
   "t": "2026-07-09T18:45:43Z",
   "kind": "comment",
   "who": "ViniciusCestarii",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nCapnp pipelining works for any client hitting the RPC socket, regardless of libmultiprocess, because it's a protocol-level feature the server doesn't need to implement. So the client (JDS) just needs to pipeline requests and it will work.\n\n(The caveat only applies to Bitcoin Core's own libmultiprocess generated C++ client code, which currently blocks per call in clientInvoke and doesn't exploit pipelining internally. But since this interface is expected to be consumed by external clients this doens't apply)"
  },
  {
   "t": "2026-07-09T19:27:27Z",
   "kind": "review_comment",
   "who": "ViniciusCestarii",
   "assoc": "CONTRIBUTOR",
   "path": "test/functional/interface_ipc_mining.py",
   "commit": "ee1c43e3ceb21fdf38340801221e9fa42c8e6993",
   "in_reply_to": null,
   "text": "In \"mining: add TxCollection addMissingTxs\" ee1c43e3ceb21fdf38340801221e9fa42c8e6993\n\nnit: there could be a test case for \"unexpected null transaction\" when calling addMissingTxs with a null tx"
  },
  {
   "t": "2026-07-09T21:02:23Z",
   "kind": "review_comment",
   "who": "ViniciusCestarii",
   "assoc": "CONTRIBUTOR",
   "path": "src/interfaces/mining.h",
   "commit": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154",
   "in_reply_to": null,
   "text": "In \"mining: add TxCollection to bandwidth-efficiently validate external block templates\" 9bb89df02fc3dbcb870b36d4a339986089ea40e2\n\nnot blocking/design question: would it be worth splitting into two capnp interfaces instead of throwing on methods: an ExternalBlockTemplate with just getBlockHeader/getBlock/getCoinbaseMerklePath/submitSolution and BlockTemplate extends(ExternalBlockTemplate) adding getTxFees/getTxSigops/getCoinbaseTx/waitNext/interruptWait? Then TxCollection::makeTemplate() returns the narrower type and the disallowed methods aren't callable, rather than compiling and throwing on call.\n\nI took the liberty to prototype this here: https://github.com/ViniciusCestarii/bitcoin/compare/9bb89df02fc3dbcb870b36d4a339986089ea40e2...pr-35671-new-interface"
  },
  {
   "t": "2026-07-09T21:06:34Z",
   "kind": "review_comment",
   "who": "ViniciusCestarii",
   "assoc": "CONTRIBUTOR",
   "path": "src/interfaces/mining.h",
   "commit": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154",
   "in_reply_to": null,
   "text": "In \"ipc: add TxCollection scaffold\" 19bae00aa17be0964e28529ecc348a94522204af\n\nnit: commit states that it adds CollectedTxs class but there's no CollectedTxs class"
  },
  {
   "t": "2026-07-10T11:50:10Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "@ViniciusCestarii wrote:\n\n[quoted text omitted]\nInteresting, so we don't need to change anything in libmultiprocess on the server (node) side?\n\nI'll take your inline comments into account on the next rebase. As I mentioned in the PR description, the code is still a bit rough."
  },
  {
   "t": "2026-07-13T19:48:02Z",
   "kind": "comment",
   "who": "ViniciusCestarii",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nYes, pipelining is already possible: I have made [this client demo](https://github.com/ViniciusCestarii/bitcoin-ipc-demo/blob/mining/src/ipc.cpp) that chains calls, including the new interface TxCollection, in a single round trip with only one .wait() at the end.\n\nOBS: [Only interface fields (and struct fields that transitively lead to one) can be actually pipelined](https://github.com/capnproto/capnproto/blob/v2/c%2B%2B/src/capnp/dynamic.c%2B%2B#L396C15-L396C84). (in the example I only pipelined interfaces), any other value like int, bool, enum, text, data, list, etc cannot be pipelined."
  },
  {
   "t": "2026-07-14T22:03:12Z",
   "kind": "comment",
   "who": "gmaxwell",
   "assoc": "CONTRIBUTOR",
   "text": "compact blocks uses a differential index not primarily for efficiency (though it does save quite a bit when there are many misses) but so that the wire format is safe by construction against an attack that applies the same large transaction over and over again (e.g. if index 5 is some giant txn, you request 5,5,5,5,5,5...)  and overflows a buffer or OOMs an implementation explicitly constructs the block and without having to have expensive duplication checks."
  },
  {
   "t": "2026-07-15T08:59:14Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "@gmaxwell the Mining IPC client[^0] is expected to run on the same machine, so `unknownTxPos()` and `addMissingTxs()` are not designed for bandwidth efficiency.\n\nWe also (currently) more or less trust the IPC client. It needs to deal with any hardening   against _its_ clients itself.[^1]\n\nUsing absolute positions matches the Stratum v2 spec[^2], so there's probably no point in hardening it at the node level. But cc @TheBlueMatt as to why it didn't use a differential index?\n\nIf we later expand `TxCollection` to support compact blocks, we would probably add new `unknownTxDifferentialPos()` and `addMissingTxsForCompactBlock()` methods. Those should then match the p2p pattern you describe.\n\n[^0]: for `TxCollection` it's the Job Declarator Server: https://github.com/stratum-mining/sv2-apps/tree/main/pool-apps/jd-server\n\n[^1]: not having to deal with another network protocol and its DoS risks was one of the motivations for not supporting Stratum v2 p2p messages directly, see discussion that starts at: https://github.com/bitcoin/bitcoin/pull/29432#pullrequestreview-2132699185. That said, over time we could gradually harden the IPC to where the Stratum v2 client can be a very thin wrapper.\n\n[^2]: [ProvideMissingTransactions](https://stratumprotocol.org/specification/06-job-declaration-protocol/#647-providemissingtransactions-server-client) and [ProvideMissingTransactions.Success](https://stratumprotocol.org/specification/06-job-declaration-protocol/#648-providemissingtransactionssuccess-client-server)"
  },
  {
   "t": "2026-07-23T18:31:48Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/interfaces/mining.h",
   "commit": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154",
   "in_reply_to": 3554795720,
   "text": "Two interfaces might require a bunch of code duplication on the Rust client side though? Also several disabled methods _could_ be made to work with external templates, if someone actually needs them."
  },
  {
   "t": "2026-07-23T18:32:23Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "test/functional/interface_ipc_mining.py",
   "commit": "ee1c43e3ceb21fdf38340801221e9fa42c8e6993",
   "in_reply_to": 3554266552,
   "text": "Added."
  },
  {
   "t": "2026-07-23T18:32:28Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/interfaces/mining.h",
   "commit": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154",
   "in_reply_to": 3554816758,
   "text": "Fixed commit message."
  },
  {
   "t": "2026-07-23T18:40:28Z",
   "kind": "force_push",
   "who": "Sjors",
   "commit": "b53031224708f287c957b982d555b2bc54c1ee01"
  },
  {
   "t": "2026-07-23T18:42:00Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "Rebased after and made consistent with #34672. Addressed @ViniciusCestarii's inline comments https://github.com/bitcoin/bitcoin/pull/35671#pullrequestreview-4655098156. Ready for review."
  },
  {
   "t": "2026-07-23T19:08:29Z",
   "kind": "force_push",
   "who": "Sjors",
   "commit": "af9a1157f663680b937087ccb4c54ccd0401c7b6"
  },
  {
   "t": "2026-07-23T19:08:34Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "Used a lambda to avoid `MakeTemplateInternal`, reduced intra-commit churn."
  },
  {
   "t": "2026-07-23T19:54:54Z",
   "kind": "review_comment",
   "who": "ViniciusCestarii",
   "assoc": "CONTRIBUTOR",
   "path": "src/interfaces/mining.h",
   "commit": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154",
   "in_reply_to": 3554795720,
   "text": "On the duplication concern: `extends()` gives upcasting, so shared logic that only needs the common subset doesn't need duplicating. Where branching is needed, it's because the type system is now correctly rejecting a call that would've thrown at runtime anyway and that's the point of the split, not an added cost.\n\nOn the disabled methods: if they could be implemented for external templates later, they could just as well be added to the split interfaces later too. The split just removes what's currently unclear to a client consuming the interface (methods that appear callable but are guaranteed to throw).\n\nJust my two cents. I'm fine either way"
  },
  {
   "t": "2026-07-24T03:31:01Z",
   "kind": "review_comment",
   "who": "pablomartin4btc",
   "assoc": "MEMBER",
   "path": "src/node/miner.cpp",
   "commit": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154",
   "in_reply_to": null,
   "text": "Should the size of `wtxids` be checked before the loop (e.g. perhaps using existing constants \u2014 `MAX_BLOCK_WEIGHT / MIN_TRANSACTION_WEIGHT`)?"
  },
  {
   "t": "2026-07-24T03:36:52Z",
   "kind": "review_comment",
   "who": "pablomartin4btc",
   "assoc": "MEMBER",
   "path": "src/node/miner.cpp",
   "commit": "af9a1157f663680b937087ccb4c54ccd0401c7b6",
   "in_reply_to": null,
   "text": "nit: does the `m_mutex` need to be locked in the constructor? \u2014 the object isn't shared until after construction returns, so no concurrent access is possible yet."
  },
  {
   "t": "2026-07-24T03:39:50Z",
   "kind": "review_comment",
   "who": "pablomartin4btc",
   "assoc": "MEMBER",
   "path": "src/node/miner.cpp",
   "commit": "af9a1157f663680b937087ccb4c54ccd0401c7b6",
   "in_reply_to": null,
   "text": "nit: these invariants are guaranteed by the constructor and the `any_of` check above \u2014 I think `Assert` would be stronger than `Assume` here to catch future regressions."
  },
  {
   "t": "2026-07-24T03:49:09Z",
   "kind": "review",
   "who": "pablomartin4btc",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "af9a1157f663680b937087ccb4c54ccd0401c7b6",
   "text": "Concept ACK\n\nLeft a few comments."
  },
  {
   "t": "2026-07-24T11:23:48Z",
   "kind": "force_push",
   "who": "Sjors",
   "commit": "d0c20776abe1ad14073c3328ef8c64434b0f92c7"
  },
  {
   "t": "2026-07-24T11:24:56Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/node/miner.cpp",
   "commit": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154",
   "in_reply_to": 3642746508,
   "text": "Added (similar check as in `merkleblock.cpp`), along with a test.\n\nI added a similar guard to `addMissingTxs()`, but for it to be actually useful, we'd need to modify libmultiprocess to enforce things like a maximum param size."
  },
  {
   "t": "2026-07-24T11:24:59Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/node/miner.cpp",
   "commit": "af9a1157f663680b937087ccb4c54ccd0401c7b6",
   "in_reply_to": 3642761322,
   "text": "Dropped"
  },
  {
   "t": "2026-07-24T11:25:03Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/node/miner.cpp",
   "commit": "af9a1157f663680b937087ccb4c54ccd0401c7b6",
   "in_reply_to": 3642768768,
   "text": "More importantly `it->second` below would crash, so `Assert` is appropriate. Fixed."
  },
  {
   "t": "2026-07-24T11:25:05Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "Addressed @pablomartin4btc's feedback. This PR grew `interface_ipc_mining.py` to over 1000 lines and the new tests add significant runtime. So I add `interface_ipc_mining_tx_collection.py`."
  },
  {
   "t": "2026-08-11T14:20:56Z",
   "kind": "review",
   "who": "enirox001",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "d0c20776abe1ad14073c3328ef8c64434b0f92c7",
   "text": "Concept ACK, I intend to benchmark this approach with the previous one to measure the improvement made here"
  },
  {
   "t": "2026-08-12T09:01:10Z",
   "kind": "review_comment",
   "who": "enirox001",
   "assoc": "CONTRIBUTOR",
   "path": "src/node/interfaces.cpp",
   "commit": "02ed6eafc42e73cc0637bc3c681ee208487dd2ab",
   "in_reply_to": null,
   "text": "In commit https://github.com/bitcoin/bitcoin/pull/35671/changes/02ed6eafc42e73cc0637bc3c681ee208487dd2ab _ipc: add TxCollection scaffold_\n\nnit:\n\nI am a bit confused with the naming here, I initially read `m_collected_txs` as a transaction container. I assume the name refers to the transactions held internally in `m_transactions`, since most operations inspect or update them.\n\nBut i think this would be better named `m_tx_collection`, as this would make it read less as a container and more as an object"
  },
  {
   "t": "2026-08-12T10:02:24Z",
   "kind": "review_comment",
   "who": "enirox001",
   "assoc": "CONTRIBUTOR",
   "path": "src/node/miner.cpp",
   "commit": "02ed6eafc42e73cc0637bc3c681ee208487dd2ab",
   "in_reply_to": null,
   "text": "In commit https://github.com/bitcoin/bitcoin/pull/35671/changes/02ed6eafc42e73cc0637bc3c681ee208487dd2ab _ipc: add TxCollection scaffold_\n\nSupposing a client passes `[A, B, C, A]` wtxids. The second `A` would be rejected as the same transaction is requested twice. The current code here would check and recheck this invalid transaction while the mempool is locked.\n\nI think the constructor could check for duplicates before locking the mempool? As the duplicate is entirely visible from the input. A possible implementation could be\n\n```diff --git a/src/node/miner.cpp b/src/node/miner.cpp\nindex 5d7440fd90..950d1abe9c 100644\n--- a/src/node/miner.cpp\n+++ b/src/node/miner.cpp\n@@ -66,6 +66,15 @@ TxCollection::TxCollection(std::vector<Wtxid> wtxids, const NodeContext& node)\n     if (m_wtxids.size() > MAX_BLOCK_WEIGHT / MIN_TRANSACTION_WEIGHT) {\n         throw std::runtime_error(strprintf(\"too many wtxids (%d > %d)\", m_wtxids.size(), MAX_BLOCK_WEIGHT / MIN_TRANSACTION_WEIGHT));\n     }\n+\n+    std::unordered_set<Wtxid, SaltedWtxidHasher> seen;\n+\n+    for (const auto& wtxid : m_wtxids) {\n+        if (!seen.insert(wtxid).second) {\n+            throw(std::runtime_error(\"duplicate wtxid\"));\n+        }\n+    }\n+\n     CTxMemPool& mempool{*Assert(m_node.mempool)};\n     LOCK(mempool.cs);\n     for (const auto& wtxid : m_wtxids) {\n```"
  },
  {
   "t": "2026-08-12T10:25:40Z",
   "kind": "review_comment",
   "who": "enirox001",
   "assoc": "CONTRIBUTOR",
   "path": "src/node/miner.cpp",
   "commit": "02ed6eafc42e73cc0637bc3c681ee208487dd2ab",
   "in_reply_to": null,
   "text": "In commit 02ed6ea ipc: add TxCollection scaffold\n\nHere, the entries are inserted into an `unordered_map`, this has internal storage that might need to be reallocated depending on the number of entries.\n\nThe check above this line helps keep it within the proper limit, but since the collection size is known, we could reserve it before acquiring the mempool lock to avoid the reallocating and rehashing while the  mempool is locked\n\n```\nm_transactions.reserve(m_wtxids.size());\n```"
  },
  {
   "t": "2026-08-12T12:48:08Z",
   "kind": "review_comment",
   "who": "enirox001",
   "assoc": "CONTRIBUTOR",
   "path": "test/functional/interface_ipc_mining_tx_collection.py",
   "commit": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154",
   "in_reply_to": null,
   "text": "In commit https://github.com/bitcoin/bitcoin/pull/35671/changes/e23f21b5cba0883e5d667321ffb0d5f7dabe87f0 mining: add TxCollection unknownTxPos\n\nI think an improvement to this test would be adding a test to verify the mempool snapshot behavior. such that it tests that after the collection is constructed with a transaction in the mempool, if the transaction is removed from the mempool it does not report it as missing.\n\na rough implementation would look somehting like\n\n```diff --git a/test/functional/interface_ipc_mining_tx_collection.py b/test/functional/interface_ipc_mining_tx_collection.py\nindex 83cc2a6462..ced11bbbe5 100755\n--- a/test/functional/interface_ipc_mining_tx_collection.py\n+++ b/test/functional/interface_ipc_mining_tx_collection.py\n@@ -99,8 +99,31 @@ class IPCMiningTxCollectionTest(BitcoinTestFramework):\n                         assert_equal(e.description, f\"remote exception: std::exception: {method_name} is unavailable for externally generated templates\")\n                         assert_equal(e.type, \"FAILED\")\n\n-            self.log.debug(\"Run the TxCollection workflow\")\n             remote_wallet.rescan_utxos()\n+\n+            self.log.debug(\"TxCollection should retain transactions after they leave the mempool\")\n+            snapshot_tx = remote_wallet.send_self_transfer(\n+                from_node=remote_node,\n+                fee_rate=10,\n+                confirmed_only=True,\n+            )\n+            self.sync_mempools()\n+            assert snapshot_tx[\"txid\"] in node.getrawmempool()\n+\n+            async with AsyncExitStack() as snapshot_stack:\n+                snapshot_collection = await mining_collect_txs(\n+                    mining0,\n+                    snapshot_stack,\n+                    ctx0,\n+                    [snapshot_tx[\"tx\"].wtxid],\n+                )\n+                assert_equal(await tx_collection_unknown_pos(snapshot_collection, ctx0), [])\n+\n+                # collection should continue holding its CTransactionRef.\n+                self.generate(remote_node, 1)\n+                assert snapshot_tx[\"txid\"] not in node.getrawmempool()\n+                assert_equal(await tx_collection_unknown_pos(snapshot_collection, ctx0), [])\n+\n             self.log.debug(\"Create a transaction that is shared by both mempools before disconnecting\")\n             shared_tx = remote_wallet.send_self_transfer(\n                 from_node=remote_node,\n```"
  },
  {
   "t": "2026-08-12T14:09:37Z",
   "kind": "review_comment",
   "who": "enirox001",
   "assoc": "CONTRIBUTOR",
   "path": "src/node/miner.h",
   "commit": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154",
   "in_reply_to": null,
   "text": "In commit https://github.com/bitcoin/bitcoin/pull/35671/changes/02d8f439592026975ca6adceb19222adf91976ff _mining: add coinbase transaction helper_\n\nThis method does more than create a basic coinbase transaction, as it also creates the witness commitment.\n\n```\n    m_chainstate.m_chainman.GenerateCoinbaseCommitment(block, &pindexPrev);\n\n```\n\nAnd since the witness commitment commits to all transactions in the block, adding a transaction after calling this method would make it outdated?\n\nThe current documentation here does not mention that all the transactions must be present before calling this so that this does not happen. An improvement could be\n\n```diff --git a/src/node/miner.h b/src/node/miner.h\nindex 43dfa52fea..486534add7 100644\n--- a/src/node/miner.h\n+++ b/src/node/miner.h\n@@ -90,6 +90,9 @@ public:\n     /**\n      * Create a coinbase transaction and insert it into block.vtx[0].\n      *\n+     * All non coinbase transactions must already be present so the generated\n+     * witness commitment commits to the transaction set\n+     *\n      * @param[in,out] block       Block whose coinbase transaction is replaced.\n      * @param[in]     pindexPrev  Previous block index. Used to derive the\n      *                            coinbase height, subsidy, and commitment.\n```"
  },
  {
   "t": "2026-08-12T14:35:13Z",
   "kind": "review_comment",
   "who": "enirox001",
   "assoc": "CONTRIBUTOR",
   "path": "src/node/miner.cpp",
   "commit": "02d8f439592026975ca6adceb19222adf91976ff",
   "in_reply_to": null,
   "text": "In commit 02d8f43959 _mining: add coinbase transaction helper_\n\nThis updates the height since the block being created must have a height one greater than its previous block. The ordinary `createNewBlock` already sets this member, and so does this helper; it is harmless but redundant.\n\n Seems it is needed here because in later commits this helper is called directly on a new `BlockAssembler` without first calling `CreateNewBlock`. In that situation, the member might not have been initialized\n\nI think a cleaner approach to this helper would be to have it calculate its own height\n\n```\nconst int height{pIndexPrev.nHeight + 1}\n```\n\nand then we use in the code\n```\nconst CAmount block_reward{\n      fees + GetBlockSubsidy(height, chainparams.GetConsensus())\n  };\n\n  coinbaseTx.vin[0].scriptSig = CScript() << height;\n  coinbaseTx.nLockTime = static_cast<uint32_t>(height - 1);\n```\nThe difference is that changing `nHeight` changes the `BlockAssembler` object even if it is not needer here, while this approach creates a value used only inside this helper. This makes this helper easier to reason about and safer to call independently"
  },
  {
   "t": "2026-08-12T15:57:01Z",
   "kind": "review_comment",
   "who": "enirox001",
   "assoc": "CONTRIBUTOR",
   "path": "src/node/miner.cpp",
   "commit": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154",
   "in_reply_to": null,
   "text": "In commit https://github.com/bitcoin/bitcoin/pull/35671/changes/a4a3db4577a43578951758406c01aa982452f9c5 _mining: make TxCollection create a BlockTemplate_\n\nnit:\nthe commit follows the intended flow for creating a template without using `CreateNewBlock` but i do have a concern about the commit structure.\n\nIn this commit the usual template metadata such as `vTxFees`, `vTxSigOpsCost`, `m_coinbase_tx` are not populated. The next commit addresses this by marking the template as external and causing them to throw. But i think this makes this specific commit a bit incomplete.\n\nPerhaps the sixth commit should be introduced before this one? or together in this commit?"
  },
  {
   "t": "2026-08-12T16:03:35Z",
   "kind": "review_comment",
   "who": "enirox001",
   "assoc": "CONTRIBUTOR",
   "path": "src/node/miner.cpp",
   "commit": "a4a3db4577a43578951758406c01aa982452f9c5",
   "in_reply_to": null,
   "text": "In commit a4a3db4 _mining: make TxCollection create a BlockTemplate_\n\nnit:\nIt looks like `m_mutex` is held longer than necessary here? Could we limit its scope to checking completeness and copying the ordered `m_transactions` into a local vector?\n\nThe references remain valid after the lock is released, so block construction and `TestBlockValidity` would not need to hold the collection mutex. This would also mirror `CreateNewBlock`, which releases the mempool lock after selecting transactions and before final validation\n\nThis is a rough implementation of this\n\n```diff --git a/src/node/miner.cpp b/src/node/miner.cpp\nindex 5d7440fd90..6f1b47423f 100644\n--- a/src/node/miner.cpp\n+++ b/src/node/miner.cpp\n@@ -121,12 +121,23 @@ std::unique_ptr<CBlockTemplate> TxCollection::MakeTemplate(const uint256& prevha\n     debug.clear();\n\n     auto block_template{[&]() -> std::unique_ptr<CBlockTemplate> {\n-        LOCK(m_mutex);\n-        if (std::ranges::any_of(m_transactions, [](const auto& entry) { return !entry.second; })) {\n-            reason = \"missing-txs\";\n-            debug = \"collected transaction(s) still missing\";\n-            return nullptr;\n+        std::vector<CTransactionRef> transactions;\n+        {\n+            LOCK(m_mutex);\n+            if (std::ranges::any_of(m_transactions, [](const auto& entry) { return !entry.second; })) {\n+                reason = \"missing-txs\";\n+                debug = \"collected transaction(s) still missing\";\n+                return nullptr;\n+            }\n+            transactions.reserve(m_wtxids.size());\n+            for (const auto& wtxid : m_wtxids) {\n+                const auto it{m_transactions.find(wtxid)};\n+                Assert(it != m_transactions.end());\n+                Assert(it->second);\n+                transactions.push_back(it->second);\n+            }\n         }\n+\n         ChainstateManager& chainman{*Assert(m_node.chainman)};\n         LOCK(chainman.GetMutex());\n         const auto current_tip{GetTip(chainman)};\n@@ -157,11 +168,8 @@ std::unique_ptr<CBlockTemplate> TxCollection::MakeTemplate(const uint256& prevha\n         // transactions are added so the witness commitment is current.\n         block.vtx.emplace_back();\n\n-        for (const auto& wtxid : m_wtxids) {\n-            const auto it{m_transactions.find(wtxid)};\n-            Assert(it != m_transactions.end());\n-            Assert(it->second);\n-            block.vtx.push_back(it->second);\n+        for (const auto& tx : transactions) {\n+            block.vtx.push_back(tx);\n         }\n\n         if (coinbase) {\n```"
  },
  {
   "t": "2026-08-12T16:22:33Z",
   "kind": "review_comment",
   "who": "enirox001",
   "assoc": "CONTRIBUTOR",
   "path": "src/node/interfaces.cpp",
   "commit": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154",
   "in_reply_to": null,
   "text": "In commit https://github.com/bitcoin/bitcoin/pull/35671/changes/d0c20776abe1ad14073c3328ef8c64434b0f92c7 _mining: restrict externally generated templates_\n\n`waitNext` is disabled for external templates, but `interruptWait` is still allowed? Is there a reason for this?\n\nI believe the relationship is that while `waitNext` executes on a thread, `interruptWait` can be called to stop the wait. For external clients, `waitNext` will throw, so even though `interruptWait` can execute, it seems redundant; a call to it would just set the flag to true and wake unrelated waiters unnecessarily"
  },
  {
   "t": "2026-08-12T16:38:58Z",
   "kind": "review_comment",
   "who": "enirox001",
   "assoc": "CONTRIBUTOR",
   "path": "src/node/miner.cpp",
   "commit": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154",
   "in_reply_to": null,
   "text": "In commit https://github.com/bitcoin/bitcoin/pull/35671/changes/f4d53d163615d09650375317faa617eaaf6a07dc _mining: add TxCollection addMissingTxs_\n\nThe transaction passed here can be much larger than the one rejected by the constructor for `TxCollection`. The check here is against the size, but it does not check the weight. So even though it can reject a million tiny transactions, it can accept 16666 large ones\n\nThis would be eventually rejected as oversized in `makeTemplate` when `TestBlockValidity` is called, but that is a bit too late.\n\nA way to track the weight of the transactions currently stored in `m_transactions` and then calculate the weight when adding here could resolve the issue"
  },
  {
   "t": "2026-08-12T16:46:22Z",
   "kind": "review",
   "who": "enirox001",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "d0c20776abe1ad14073c3328ef8c64434b0f92c7",
   "text": "Code Review d0c20776abe1ad14073c3328ef8c64434b0f92c7\n\nI initially intended to benchmark this approach against the existing JDS mempool mirror, but I no longer think that is particularly relevant for evaluating the concept, since the main benefit is architectural rather than necessarily a raw speed improvement.\n\nReusing Bitcoin Core's mempool avoids duplicating transaction storage and continuous mirror synchronization, transfers only transactions the node is missing, and lets external templates be reconstructed and validated using existing validation logic. I plan to continue reviewing the implementation.\n\nleft some questions and nits"
  },
  {
   "t": "2026-08-20T10:25:47Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/node/miner.cpp",
   "commit": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154",
   "in_reply_to": 3768404613,
   "text": "I don't think we should take over too much work that `TestBlockValidity()` does for us. Duplicating consensus checks could introduce new bugs."
  },
  {
   "t": "2026-08-20T10:36:31Z",
   "kind": "force_push",
   "who": "Sjors",
   "commit": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154"
  },
  {
   "t": "2026-08-20T10:36:34Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/node/interfaces.cpp",
   "commit": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154",
   "in_reply_to": 3768262990,
   "text": "Good catch, I probably missed this in a rebase (before I opened the PR)."
  },
  {
   "t": "2026-08-20T10:36:36Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/node/miner.cpp",
   "commit": "a4a3db4577a43578951758406c01aa982452f9c5",
   "in_reply_to": 3768124604,
   "text": "Done"
  },
  {
   "t": "2026-08-20T10:36:38Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/node/miner.cpp",
   "commit": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154",
   "in_reply_to": 3768072131,
   "text": "I'd rather not grow the commit, and reversing them makes it hard to test. Going to leave this alone for now. The commits are bisect-safe."
  },
  {
   "t": "2026-08-20T10:36:39Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/node/miner.cpp",
   "commit": "02d8f439592026975ca6adceb19222adf91976ff",
   "in_reply_to": 3767376523,
   "text": "Taken, and made it a `const` member function."
  },
  {
   "t": "2026-08-20T10:36:42Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/node/miner.h",
   "commit": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154",
   "in_reply_to": 3767165227,
   "text": "Taken."
  },
  {
   "t": "2026-08-20T10:36:44Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "test/functional/interface_ipc_mining_tx_collection.py",
   "commit": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154",
   "in_reply_to": 3766518873,
   "text": "Taken"
  },
  {
   "t": "2026-08-20T10:36:46Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/node/miner.cpp",
   "commit": "02ed6eafc42e73cc0637bc3c681ee208487dd2ab",
   "in_reply_to": 3765562606,
   "text": "Done"
  },
  {
   "t": "2026-08-20T10:36:48Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/node/miner.cpp",
   "commit": "02ed6eafc42e73cc0637bc3c681ee208487dd2ab",
   "in_reply_to": 3765415437,
   "text": "Done, checking duplicates first without a lock now."
  },
  {
   "t": "2026-08-20T10:36:50Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "src/node/interfaces.cpp",
   "commit": "02ed6eafc42e73cc0637bc3c681ee208487dd2ab",
   "in_reply_to": 3764996133,
   "text": "Renamed."
  },
  {
   "t": "2026-08-20T10:36:54Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "Rebased and addressed @enirox001's feedback."
  },
  {
   "t": "2026-09-06T14:07:06Z",
   "kind": "review",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154",
   "text": "Concept ACK\n\nRan the new tests and the mining suite, and checked the coinbase commit against its parent.\n\nA question about the reject reasons. `makeTemplate()` reports an ancestor prevhash as `stale-prevblk`, but `Mining::checkBlock()` reports that same case as `inconclusive-not-best-prevblk`, since it lands in `TestBlockValidity()`, which uses that reason for any prevhash other than the tip. Nothing in the tree used `stale-prevblk` before this PR. The `make TxCollection create a BlockTemplate` commit message says this mirrors the `getblocktemplate` proposal reasons, but the proposal goes out through that same call, unless I'm misreading it. Was that difference deliberate?"
  },
  {
   "t": "2026-09-06T14:07:06Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "test/functional/interface_ipc_mining_tx_collection.py",
   "commit": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154",
   "in_reply_to": null,
   "text": "The amount case is covered, the witness commitment one isn't, and that's the one that pins which transactions the coinbase commits to. I flipped a bit of the commitment and it gives `bad-witness-merkle-match`. Would a case next to this one be worth it?\n\n```suggestion\n                )\n\n                self.log.debug(\"makeTemplate() rejects a client-provided coinbase whose witness commitment does not match\")\n                bad_commitment_coinbase = deepcopy(remote_coinbase)\n                for out in bad_commitment_coinbase.vout:\n                    if out.scriptPubKey[:6] == bytes.fromhex(\"6a24aa21a9ed\"):\n                        spk = bytearray(out.scriptPubKey)\n                        spk[-1] ^= 0x01\n                        out.scriptPubKey = bytes(spk)\n                await tx_collection_make_template(\n                    tx_collection, stack, ctx0, remote_tip,\n                    coinbase=bad_commitment_coinbase.serialize(),\n                    reject_reason=\"bad-witness-merkle-match\",\n                )\n```"
  },
  {
   "t": "2026-09-06T14:07:06Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "src/interfaces/mining.h",
   "commit": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154",
   "in_reply_to": null,
   "text": "nit: the two methods above have `@param[in]` and `@returns`, and this one has neither. The constructor also throws when the list goes past `MAX_BLOCK_WEIGHT / MIN_TRANSACTION_WEIGHT`, and that one is left out, while `addMissingTxs()` documents all three of its cases."
  },
  {
   "t": "2026-09-06T14:07:07Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "src/interfaces/mining.h",
   "commit": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154",
   "in_reply_to": null,
   "text": "nit: for an externally generated template `getCoinbaseTx()` throws now, so there is nothing to compare against, and when the client passed a coinbase to `makeTemplate()` the one in `vtx[0]` is theirs rather than a dummy. Worth saying which coinbase this returns?"
  }
 ],
 "labels_log": [
  {
   "t": "2026-07-06T19:45:47Z",
   "action": "labeled",
   "label": "Mining",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-06T19:50:57Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-06T20:55:03Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-08T00:00:31Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-08T08:34:00Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-23T19:09:14Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-23T20:08:46Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-29T20:45:44Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-20T12:31:18Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-07-23T18:42:02Z",
   "kind": "ready_for_review",
   "who": "Sjors"
  }
 ],
 "text_chars": 29484,
 "text_tokens_estimate": 7371,
 "changed_paths": [
  "src/interfaces/mining.h",
  "src/ipc/capnp/mining.capnp",
  "src/node/interfaces.cpp",
  "src/node/miner.cpp",
  "src/node/miner.h",
  "test/functional/interface_ipc_mining_tx_collection.py",
  "test/functional/test_framework/ipc_util.py",
  "test/functional/test_framework/messages.py",
  "test/functional/test_runner.py"
 ],
 "files": [
  {
   "path": "src/interfaces/mining.h",
   "add": 85,
   "del": 4
  },
  {
   "path": "src/ipc/capnp/mining.capnp",
   "add": 8,
   "del": 0
  },
  {
   "path": "src/node/interfaces.cpp",
   "add": 52,
   "del": 4
  },
  {
   "path": "src/node/miner.cpp",
   "add": 215,
   "del": 48
  },
  {
   "path": "src/node/miner.h",
   "add": 52,
   "del": 1
  },
  {
   "path": "test/functional/interface_ipc_mining_tx_collection.py",
   "add": 292,
   "del": 0
  },
  {
   "path": "test/functional/test_framework/ipc_util.py",
   "add": 26,
   "del": 0
  },
  {
   "path": "test/functional/test_framework/messages.py",
   "add": 1,
   "del": 0
  },
  {
   "path": "test/functional/test_runner.py",
   "add": 1,
   "del": 0
  }
 ],
 "test_lines": 320,
 "git": {
  "head": "60e44edb3cb15f40e8e7e935ba5fc5742e74b154",
  "head_matches_backup": true,
  "base": "08dfaa04f4ca4bd58adc9263fa27f1e7017dff62",
  "commits": [
   {
    "sha": "b100f601bf",
    "subject": "ipc: add TxCollection scaffold",
    "files": 8,
    "add": 163,
    "del": 0
   },
   {
    "sha": "00730be040",
    "subject": "mining: add TxCollection unknownTxPos",
    "files": 7,
    "add": 97,
    "del": 2
   },
   {
    "sha": "a75f17d21f",
    "subject": "mining: add TxCollection addMissingTxs",
    "files": 6,
    "add": 88,
    "del": 3
   },
   {
    "sha": "e8a75a3ad3",
    "subject": "mining: add coinbase transaction helper",
    "files": 2,
    "add": 75,
    "del": 48
   },
   {
    "sha": "f6732942fa",
    "subject": "mining: make TxCollection create a BlockTemplate",
    "files": 7,
    "add": 274,
    "del": 5
   },
   {
    "sha": "60e44edb3c",
    "subject": "mining: restrict externally generated templates",
    "files": 3,
    "add": 45,
    "del": 9
   }
  ],
  "patch_truncated": true
 },
 "input_hash": "f4063ae01bd3e4fa",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}