{
 "number": 36132,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36132",
 "title": "mining: preserve lookup result count without a mempool",
 "author": "l0rinc",
 "author_association": "MEMBER",
 "created_at": "2026-08-31T16:11:00Z",
 "updated_at": "2026-09-10T15:11:51Z",
 "age_days": 17,
 "draft": false,
 "labels": [
  "Mining"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "534aee84bfe813224cf1bbb5622c93137e8c713c",
 "head_ref": "l0rinc/mining-preserve-lookup-result-count",
 "head_repo": "l0rinc/bitcoin",
 "head_history": [
  {
   "t": "2026-09-02T23:33:04Z",
   "sha": "534aee84bfe813224cf1bbb5622c93137e8c713c"
  }
 ],
 "additions": 18,
 "deletions": 2,
 "changed_files": 2,
 "commit_count": 2,
 "size_bucket": "S",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "ack": [
     {
      "login": "Sjors",
      "url": "https://github.com/bitcoin/bitcoin/pull/36132#issuecomment-5539221048"
     }
    ],
    "concept_ack": [
     {
      "login": "jeanpablojp",
      "url": "https://github.com/bitcoin/bitcoin/pull/36132#pullrequestreview-5092607794"
     },
     {
      "login": "enirox001",
      "url": "https://github.com/bitcoin/bitcoin/pull/36132#issuecomment-5586736218"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {
  "jeanpablojp": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-09-02T16:57:04Z",
   "stale": false
  },
  "Sjors": {
   "kind": "ack",
   "hash": "534aee84bfe813224cf1bbb5622c93137e8c713c",
   "t": "2026-09-04T10:35:27Z",
   "stale": false
  },
  "enirox001": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-09-08T14:28:18Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 1,
  "stale_ack": 0,
  "concept_ack": 2,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "Sjors",
   "enirox001",
   "jeanpablojp",
   "optout21"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-09-02T23:33:07Z",
  "last_reviewer_activity": "2026-09-10T15:10:20Z",
  "last_reviewer": "Sjors",
  "author_silent_days": 14,
  "waiting_on_author_days": 7,
  "days_since_update": 7
 },
 "refs": {
  "mentioned": [],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/test/miner_tests.cpp"
 ],
 "body": "**Problem:** The mining interface returns one nullable transaction for every requested txid or wtxid so callers can match results by position.\nWhen its `NodeContext` has no mempool ([not sure that's possible](https://github.com/bitcoin/bitcoin/pull/34020#discussion_r2962016282), but there's a condition for it which violates the contract), both lookup methods return an empty vector for every nonempty request and discard that positional mapping, see: https://github.com/bitcoin/bitcoin/blob/7b6f9ba7bad13b0c4169259000f7802854cdda0d/src/interfaces/mining.h#L202-L203\n\n**Fix:** Return a vector sized to the request when no mempool is available.\nDefault-initialized null entries match the existing representation for identifiers absent from an available mempool, so callers receive one response position per request in either state.",
 "commits": [
  {
   "sha": "f1e9197fbe7e303a95fbed10ac6b7736fc69ec10",
   "date": "2026-09-02T23:32:14Z",
   "message": "test: characterize mining lookups without mempool"
  },
  {
   "sha": "534aee84bfe813224cf1bbb5622c93137e8c713c",
   "date": "2026-09-02T23:32:14Z",
   "message": "mining: preserve transaction lookup result count\n\nThe mining interface returns one nullable transaction for every requested txid or wtxid so callers can match results by position.\nWhen a `NodeContext` has no mempool, both lookup methods return an empty vector and discard that positional mapping.\n\nReturn a vector sized to the request in this state. Its default-initialized entries represent missing transactions, matching\nthe existing result for identifiers absent from an available mempool."
  }
 ],
 "timeline": [
  {
   "t": "2026-09-02T16:57:04Z",
   "kind": "review",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "ac2c6c4ef12a7660e0fd2c515b923bfe1c4e648f",
   "text": "Concept ACK\n\nBuilt and ran the unit tests, and reverting just `interfaces.cpp` makes the new test fail."
  },
  {
   "t": "2026-09-02T16:57:04Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/miner_tests.cpp",
   "commit": "ac2c6c4ef12a7660e0fd2c515b923bfe1c4e648f",
   "in_reply_to": null,
   "text": "nit: the case builds its own empty `NodeContext` and stands on its own, which is nice, but the suite's `MinerTestingSetup` still gets built around it with nothing using it. `chainstatemanager_args` handles that by overriding the fixture on the case itself.\n\n```suggestion\nBOOST_FIXTURE_TEST_CASE(transaction_lookups_without_mempool, BasicTestingSetup)\n```"
  },
  {
   "t": "2026-09-02T23:33:04Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "534aee84bfe813224cf1bbb5622c93137e8c713c"
  },
  {
   "t": "2026-09-02T23:33:07Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/test/miner_tests.cpp",
   "commit": "ac2c6c4ef12a7660e0fd2c515b923bfe1c4e648f",
   "in_reply_to": 3916565441,
   "text": "Nice, I switched to `BasicTestingSetup`, rebased, thanks."
  },
  {
   "t": "2026-09-04T10:35:27Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "utACK 534aee84bfe813224cf1bbb5622c93137e8c713c\n\nAn alternative is to throw if there's no mempool. That has the advantage of clients not being \"misled\" that we don't have a transaction. However, there's also a short period of time where the node has a mempool and it's still being populated from disk. In this alternative approach, that should throw too. That way the contract is: we give you the transaction if we have it, and otherwise we throw. But I'm fine with the simpler approach here."
  },
  {
   "t": "2026-09-08T14:28:18Z",
   "kind": "comment",
   "who": "enirox001",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK\n\nI would understand the rationale behind this change, having the node return a list of nullptrs is better than having it return nothing, especially if the documentation already promises to at least return an entry per requested txid or wtxid\n\nFor my understanding, is there a current production path where these lookups run without the mempool, given that IPC `makeMining()` waits for the chainstate to load? Or is this primarily keeping the interface contract consistent for internal callers and tests?"
  },
  {
   "t": "2026-09-10T12:35:43Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "@enirox001 I also suspect it can't happen."
  },
  {
   "t": "2026-09-10T13:44:07Z",
   "kind": "comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "text": "In this special case the contract (\"one entry per requested ID\") is not satisfied.\n\nIs there any report of this causing a problem with users?\n\nEven if the contract is satisfied (as done in this PR), the callers should still verify the size of the returned array before accessing elements, it's not safe to rely on the contract and access elements without length checks.\nBy this logic, wouldn't be it easier to just _change the contract_, and specify that \"a returned vector that has less elements than IDs requested should be treated as error\"? This would not mean any extra work for the callers. The code could stay as it is."
  },
  {
   "t": "2026-09-10T15:10:20Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nThere's three states:\n\n1. mempool not ready (zero length result)\n2. mempool ready but still reading from disk (normal length result, but all empty)\n3. mempool fully read from disk (normal length result, empty results mean actually missing txs)\n\nThe difference between (1) and (2) is not interesting to the client, the previous code requires clients to distinguish them, this PR hides the distinction.\n\nThe difference between (2) and (3) _is_ interesting, but we're not revealing it. Doing so would be a more involved change (probably not worth doing)."
  }
 ],
 "labels_log": [
  {
   "t": "2026-08-31T16:11:04Z",
   "action": "labeled",
   "label": "Mining",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 4200,
 "text_tokens_estimate": 1050,
 "changed_paths": [
  "src/node/interfaces.cpp",
  "src/test/miner_tests.cpp"
 ],
 "files": [
  {
   "path": "src/node/interfaces.cpp",
   "add": 2,
   "del": 2
  },
  {
   "path": "src/test/miner_tests.cpp",
   "add": 16,
   "del": 0
  }
 ],
 "test_lines": 16,
 "git": {
  "head": "534aee84bfe813224cf1bbb5622c93137e8c713c",
  "head_matches_backup": true,
  "base": "b811aeabad94ef48cd0f0fb1d2fcc456594aeedb",
  "commits": [
   {
    "sha": "f1e9197fbe",
    "subject": "test: characterize mining lookups without mempool",
    "files": 1,
    "add": 16,
    "del": 0
   },
   {
    "sha": "534aee84bf",
    "subject": "mining: preserve transaction lookup result count",
    "files": 2,
    "add": 4,
    "del": 4
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "dd865d26899ee65f",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}