{
 "number": 35598,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/35598",
 "title": "test: cover feeThreshold = MAX_MONEY in interface_ipc_mining.py",
 "author": "Sjors",
 "author_association": "MEMBER",
 "created_at": "2026-06-24T19:35:34Z",
 "updated_at": "2026-08-28T12:53:40Z",
 "age_days": 84,
 "draft": false,
 "labels": [
  "Tests"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "8fb2dbc0d6e788da43c8e471b6e8027b03b8b6ec",
 "head_ref": "2026/06/max-money",
 "head_repo": "Sjors/bitcoin",
 "head_history": [
  {
   "t": "2026-06-26T12:56:35Z",
   "sha": "bd1771fc0cd248438e87b028b096f59ffc976d18"
  },
  {
   "t": "2026-07-02T11:54:44Z",
   "sha": "0de7c2bf7912d5632c33195b7764e06c46e5f7b0"
  },
  {
   "t": "2026-08-28T12:53:26Z",
   "sha": "8fb2dbc0d6e788da43c8e471b6e8027b03b8b6ec"
  }
 ],
 "additions": 27,
 "deletions": 11,
 "changed_files": 2,
 "commit_count": 1,
 "size_bucket": "S",
 "mergeable_state": "blocked",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "approach_ack": [
     {
      "login": "jeanpablojp",
      "url": "https://github.com/bitcoin/bitcoin/pull/35598#pullrequestreview-5040791065"
     }
    ],
    "stale_ack": [
     {
      "login": "enirox001",
      "url": "https://github.com/bitcoin/bitcoin/pull/35598#pullrequestreview-5049908342"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {
  "enirox001": {
   "kind": "ack",
   "hash": null,
   "t": "2026-08-28T09:44:44Z",
   "stale": true
  },
  "jeanpablojp": {
   "kind": "approach_ack",
   "hash": null,
   "t": "2026-08-27T12:33:12Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 1,
  "concept_ack": 0,
  "approach_ack": 1,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "davidgumberg",
   "enirox001",
   "jeanpablojp"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "blocked",
  "last_author_activity": "2026-08-28T12:53:32Z",
  "last_reviewer_activity": "2026-08-28T09:46:40Z",
  "last_reviewer": "enirox001",
  "author_silent_days": 20,
  "waiting_on_author_days": 0,
  "days_since_update": 20
 },
 "refs": {
  "mentioned": [
   33922
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 33922,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "mining: add getMemoryLoad() and track template non-mempool memory footprint"
   }
  ],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "test/functional/interface_ipc_mining.py"
 ],
 "body": "This adds coverage for when [BlockWaitOptions](https://github.com/bitcoin/bitcoin/blob/master/src/node/mining_types.h#L94-L113)'s `feeThreshold` is set to `MAX_MONEY`. In other words, when the IPC client is only interested in tip updates, not in fee increases.\n\nThis tested ended up in #33922 at some point  https://github.com/bitcoin/bitcoin/pull/33922#issuecomment-3674495132, but doesn't really belong there.",
 "commits": [
  {
   "sha": "8fb2dbc0d6e788da43c8e471b6e8027b03b8b6ec",
   "date": "2026-08-28T12:51:10Z",
   "message": "test: cover feeThreshold = MAX_MONEY\n\nCo-authored-by: David Gumberg <davidzgumberg@gmail.com>"
  }
 ],
 "timeline": [
  {
   "t": "2026-06-26T11:55:37Z",
   "kind": "review_comment",
   "who": "enirox001",
   "assoc": "CONTRIBUTOR",
   "path": "test/functional/interface_ipc_mining.py",
   "commit": "4f74b7302f9261831c43065e1876bff937a9b0b7",
   "in_reply_to": null,
   "text": "In 4f74b73 (_test: cover feeThreshold = MAX_MONEY_)\n\nWould be nice if there were a comment explaining the assertion here. Something like\n\n```\nIf waitNext had returned for the fee increase instead of the tip update,\nthis template would include the mempool transaction.\n```"
  },
  {
   "t": "2026-06-26T11:55:43Z",
   "kind": "review",
   "who": "enirox001",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "4f74b7302f9261831c43065e1876bff937a9b0b7",
   "text": "ACK 4f74b73\n\n_Mostly_ reviewing this in isolation, as I have not looked at #33922 in depth, it makes sense that this should be a PR on its own\n\nThe change updates the functional test to explicitly ignore mempool fee increases while waiting for a tip update, then restores the lower fee threshold for subsequent checks.\n\nThese changes look good to me. Left a minor non-blocking suggestion below,"
  },
  {
   "t": "2026-06-26T12:56:35Z",
   "kind": "force_push",
   "who": "Sjors",
   "commit": "bd1771fc0cd248438e87b028b096f59ffc976d18"
  },
  {
   "t": "2026-06-26T12:56:52Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "Added the suggested comment.\n\n[quoted text omitted]\nIt's unrelated. That PR is about memory tracking and management, this test is about adding coverages to pre-existing `waitNext()` behavior. The PR touches the same test file, so it was easy to add. But now that I'm considering alternative approaches for #33922, the test becomes a distraction - and it might be a long time before it lands."
  },
  {
   "t": "2026-07-02T00:21:50Z",
   "kind": "review_comment",
   "who": "davidgumberg",
   "assoc": "CONTRIBUTOR",
   "path": "test/functional/interface_ipc_mining.py",
   "commit": "bd1771fc0cd248438e87b028b096f59ffc976d18",
   "in_reply_to": null,
   "text": "https://github.com/bitcoin/bitcoin/pull/35598/changes/bd1771fc0cd248438e87b028b096f59ffc976d18 (_test: cover feeThreshold = MAX_MONEY_)\n\n---\n\nThe test still passes with `waitoptions.feeThreshold = 1`\n\nThat is because `WaitAndCreateNewBlock()` checks for a tip update immediately and then goes to sleep waiting for a tip update, only waking up 1 second after being called or at the timeout deadline before checking for a fee-based update:\n\nhttps://github.com/bitcoin/bitcoin/blob/a8823c099618559fdf6116fe17c9afd311e88890/src/node/miner.cpp#L452-L460\n\nand `wait_and_do` only sleeps for 0.1s:\n\nhttps://github.com/bitcoin/bitcoin/blob/a8823c099618559fdf6116fe17c9afd311e88890/test/functional/test_framework/ipc_util.py#L59-L67\n\nso the fee check never even has a chance, since `WaitAndCreateNewBlock()` is still asleep waiting for a new tip when `wait_and_do` produces one, returns,  and the fee check has never even had a chance..\n\nTo fix it, easier shown than explained I think:\n\n```diff\n-                waitoptions.timeout = self.default_ipc_timeout\n+\n+                # Temporarily increase the timeout to allow a fee ticks (1s) to\n+                # happen before the deadline\n+                waitoptions.timeout = 2000\n                 # Ignore fee increases, wait only for the tip update\n                 waitoptions.feeThreshold = MAX_MONEY\n                 self.miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0])\n                 template2 = await wait_and_do(\n                     mining_wait_next_template(template, stack, ctx, waitoptions),\n                     # This mines the transaction, so it won't be in the next template\n-                    lambda: self.generate(self.nodes[0], 1))\n+                    lambda: self.generate(self.nodes[0], 1),\n+                    sleep_time=1.1)\n```\n[quoted text omitted]\n\nand adding an arg to `wait_and_do`:\n\n```diff\n--- a/test/functional/test_framework/ipc_util.py\n+++ b/test/functional/test_framework/ipc_util.py\n@@ -45,7 +45,7 @@ async def destroying(obj, ctx):\n         await obj.destroy(ctx)\n\n-async def wait_and_do(wait_fn, do_fn):\n+async def wait_and_do(wait_fn, do_fn, sleep_time=0.1):\n     \"\"\"Call wait_fn, then sleep, then call do_fn in a parallel task. Wait for\n     both tasks to complete.\"\"\"\n     wait_started = asyncio.Event()\n@@ -58,7 +58,7 @@ async def wait_and_do(wait_fn, do_fn):\n\n     async def do():\n         await wait_started.wait()\n-        await asyncio.sleep(0.1)\n+        await asyncio.sleep(sleep_time)\n         # Let do_fn be either a callable or an awaitable object\n         if inspect.isawaitable(do_fn):\n             await do_fn\n```"
  },
  {
   "t": "2026-07-02T00:25:44Z",
   "kind": "review_comment",
   "who": "davidgumberg",
   "assoc": "CONTRIBUTOR",
   "path": "test/functional/interface_ipc_mining.py",
   "commit": "8fb2dbc0d6e788da43c8e471b6e8027b03b8b6ec",
   "in_reply_to": null,
   "text": "https://github.com/bitcoin/bitcoin/pull/35598/changes/bd1771fc0cd248438e87b028b096f59ffc976d18 (_test: cover feeThreshold = MAX_MONEY_)\n\n---\n\n(see comment above: https://github.com/bitcoin/bitcoin/pull/35598/changes/bd1771fc0cd248438e87b028b096f59ffc976d18#r3509745119)\n\nShould probably add a transaction here to check in another way that MAX_MONEY works to prevent fee-based template updates:\n\n```diff\n@@ -280,9 +284,14 @@ class IPCMiningTest(BitcoinTestFramework):\n                 assert_equal(len(block2.vtx), 1)\n\n                 self.log.debug(\"Wait for another, but time out\")\n+                # Temporarily decrease the timeout since we want to hit the\n+                # deadline and perform a fee check and see what happens.\n+                waitoptions.timeout = 100\n+                self.miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0])\n                 template3 = await mining_wait_next_template(template2, stack, ctx, waitoptions)\n                 assert template3 is None\n\n+                waitoptions.timeout = self.default_ipc_timeout\n                 self.log.debug(\"Wait for another, get one after increase in fees in the mempool\")\n                 waitoptions.feeThreshold = 1\n                 template4 = await wait_and_do(\n@@ -290,14 +299,14 @@ class IPCMiningTest(BitcoinTestFramework):\n                     lambda: self.miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0]))\n                 assert template4 is not None\n                 block3 = await mining_get_block(template4, ctx)\n-                assert_equal(len(block3.vtx), 2)\n+                assert_equal(len(block3.vtx), 3)\n\n                 self.log.debug(\"Wait again, this should return the same template, since the fee threshold is zero\")\n                 waitoptions.feeThreshold = 0\n                 template5 = await mining_wait_next_template(template4, stack, ctx, waitoptions)\n                 assert template5 is not None\n                 block4 = await mining_get_block(template5, ctx)\n-                assert_equal(len(block4.vtx), 2)\n+                assert_equal(len(block4.vtx), 3)\n                 waitoptions.feeThreshold = 1\n\n                 self.log.debug(\"Wait for another, get one after increase in fees in the mempool\")\n@@ -306,7 +315,7 @@ class IPCMiningTest(BitcoinTestFramework):\n                     lambda: self.miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0]))\n                 assert template6 is not None\n                 block4 = await mining_get_block(template6, ctx)\n-                assert_equal(len(block4.vtx), 3)\n+                assert_equal(len(block4.vtx), 4)\n\n                 self.log.debug(\"Wait for another, but time out, since the fee threshold is set now\")\n                 template7 = await mining_wait_next_template(template6, stack, ctx, waitoptions)\n```"
  },
  {
   "t": "2026-07-02T11:54:44Z",
   "kind": "force_push",
   "who": "Sjors",
   "commit": "0de7c2bf7912d5632c33195b7764e06c46e5f7b0"
  },
  {
   "t": "2026-07-02T11:54:47Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "test/functional/interface_ipc_mining.py",
   "commit": "bd1771fc0cd248438e87b028b096f59ffc976d18",
   "in_reply_to": 3509745119,
   "text": "Ah oops. The counter intuitive thing here is that `waitNext()` will ignore fee changes in the first second. This might change with cluster mempool when we get rid of the internal tick mechanism, see https://github.com/bitcoin/bitcoin/pull/35581. But the adjusted test should continue to work.\n\nTook your suggestion with some adjustments."
  },
  {
   "t": "2026-07-02T11:55:08Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "test/functional/interface_ipc_mining.py",
   "commit": "8fb2dbc0d6e788da43c8e471b6e8027b03b8b6ec",
   "in_reply_to": 3509756746,
   "text": "Also taken"
  },
  {
   "t": "2026-07-02T11:55:31Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "Turns out it was a a good idea to make this a separate PR. Fixed https://github.com/bitcoin/bitcoin/pull/35598#discussion_r3509745119"
  },
  {
   "t": "2026-08-27T12:33:12Z",
   "kind": "review",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "0de7c2bf7912d5632c33195b7764e06c46e5f7b0",
   "text": "Approach ACK\n\nLeft two comments."
  },
  {
   "t": "2026-08-27T12:33:12Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "test/functional/interface_ipc_mining.py",
   "commit": "0de7c2bf7912d5632c33195b7764e06c46e5f7b0",
   "in_reply_to": null,
   "text": "Nit, feel free to ignore. With `feeThreshold` at MAX_MONEY the fee block in `WaitAndCreateNewBlock` is skipped, so nothing is checked at the deadline either. The assert is right, the comment isn't."
  },
  {
   "t": "2026-08-27T12:33:12Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "test/functional/interface_ipc_mining.py",
   "commit": "0de7c2bf7912d5632c33195b7764e06c46e5f7b0",
   "in_reply_to": null,
   "text": "`mining_types.h` describes MAX_MONEY as an optimization, and no fee delta can reach it, so the MAX_MONEY half of that guard only exists to skip building the template. Dropping it, and the `Assume` it makes true, leaves the test green in both release and debug builds. With the wrap below it fails. What do you think about covering that here too?\n\n```suggestion\n                # MAX_MONEY is documented as an optimization, so waitNext()\n                # should not build a template at all here.\n                with self.nodes[0].assert_debug_log(expected_msgs=[], unexpected_msgs=[\"CreateNewBlock()\"]):\n                    template3 = await mining_wait_next_template(template2, stack, ctx, waitoptions)\n```"
  },
  {
   "t": "2026-08-28T09:44:44Z",
   "kind": "review",
   "who": "enirox001",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "0de7c2bf7912d5632c33195b7764e06c46e5f7b0",
   "text": "reACK https://github.com/bitcoin/bitcoin/pull/35598/changes/0de7c2bf7912d5632c33195b7764e06c46e5f7b0\n\nChanges look good to me. It is useful to have this coverage, and it makes sense as a standalone pr.  Left non-blocking comment"
  },
  {
   "t": "2026-08-28T09:46:40Z",
   "kind": "review_comment",
   "who": "enirox001",
   "assoc": "CONTRIBUTOR",
   "path": "test/functional/interface_ipc_mining.py",
   "commit": "0de7c2bf7912d5632c33195b7764e06c46e5f7b0",
   "in_reply_to": null,
   "text": "In https://github.com/bitcoin/bitcoin/pull/35598/changes/0de7c2bf7912d5632c33195b7764e06c46e5f7b0: _test: cover feeThreshold = MAX_MONEY_\n\nnon_blocking nit\n\nLooking at this again, my understanding is that the transaction added above is intentionally kept in the mempool to verify that `feeThreshold = MAX_MONEY` ignores a fee increase and still times out. That part makes sense.\n\nAfter the timeout, however, that transaction remains in the mempool and is not included in `template2`. Therefore, once `feeThreshold` is changed to 1, it already satisfies the threshold before this second wait starts. `waitNext` is allowed to return without waiting for the transaction added by this lambda.\n\nVerified this by delaying the lambda\n\n```diff --git a/test/functional/interface_ipc_mining.py b/test/functional/interface_ipc_mining.py\nindex 85a7b8bf11..fe5b861614 100755\n--- a/test/functional/interface_ipc_mining.py\n+++ b/test/functional/interface_ipc_mining.py\n@@ -299,7 +299,8 @@ class IPCMiningTest(BitcoinTestFramework):\n                 waitoptions.feeThreshold = 1\n                 template4 = await wait_and_do(\n                     mining_wait_next_template(template2, stack, ctx, waitoptions),\n-                    lambda: self.miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0]))\n+                    lambda: self.miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0]),\n+                    sleep_time=1.1 * self.options.timeout_factor)\n                 assert template4 is not None\n                 block3 = await mining_get_block(template4, ctx)\n                 assert_equal(len(block3.vtx), 3)\n```\n\nThe test fails when asserting the transaction count because it only contains 2 transactions. Would it be clearer to consume the existing fee increase directly here?\n\n```diff --git a/test/functional/interface_ipc_mining.py b/test/functional/interface_ipc_mining.py\nindex 85a7b8bf11..57c1b60b98 100755\n--- a/test/functional/interface_ipc_mining.py\n+++ b/test/functional/interface_ipc_mining.py\n@@ -295,21 +295,19 @@ class IPCMiningTest(BitcoinTestFramework):\n                 assert template3 is None\n\n                 waitoptions.timeout = self.default_ipc_timeout\n-                self.log.debug(\"Wait for another, get one after increase in fees in the mempool\")\n+                self.log.debug(\"Wait for another, get one for the fees already in the mempool\")\n                 waitoptions.feeThreshold = 1\n-                template4 = await wait_and_do(\n-                    mining_wait_next_template(template2, stack, ctx, waitoptions),\n-                    lambda: self.miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0]))\n+                template4 = await mining_wait_next_template(template2, stack, ctx, waitoptions)\n                 assert template4 is not None\n                 block3 = await mining_get_block(template4, ctx)\n-                assert_equal(len(block3.vtx), 3)\n+                assert_equal(len(block3.vtx), 2)\n\n                 self.log.debug(\"Wait again, this should return the same template, since the fee threshold is zero\")\n                 waitoptions.feeThreshold = 0\n                 template5 = await mining_wait_next_template(template4, stack, ctx, waitoptions)\n                 assert template5 is not None\n                 block4 = await mining_get_block(template5, ctx)\n-                assert_equal(len(block4.vtx), 3)\n+                assert_equal(len(block4.vtx), 2)\n                 waitoptions.feeThreshold = 1\n\n                 self.log.debug(\"Wait for another, get one after increase in fees in the mempool\")\n@@ -318,7 +316,7 @@ class IPCMiningTest(BitcoinTestFramework):\n                     lambda: self.miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0]))\n                 assert template6 is not None\n                 block4 = await mining_get_block(template6, ctx)\n-                assert_equal(len(block4.vtx), 4)\n+                assert_equal(len(block4.vtx), 3)\n\n                 self.log.debug(\"Wait for another, but time out, since the fee threshold is set now\")\n                 template7 = await mining_wait_next_template(template6, stack, ctx, waitoptions)\n```\n\nLater on `template6` would still test adding a transaction while `waitNext` is active, verifying that a new template is returned"
  },
  {
   "t": "2026-08-28T12:53:26Z",
   "kind": "force_push",
   "who": "Sjors",
   "commit": "8fb2dbc0d6e788da43c8e471b6e8027b03b8b6ec"
  },
  {
   "t": "2026-08-28T12:53:32Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "Rebased just in case, and addressed feedback from @jeanpablojp and @enirox001."
  }
 ],
 "labels_log": [
  {
   "t": "2026-06-24T19:35:38Z",
   "action": "labeled",
   "label": "Tests",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 13150,
 "text_tokens_estimate": 3287,
 "changed_paths": [
  "test/functional/interface_ipc_mining.py",
  "test/functional/test_framework/ipc_util.py"
 ],
 "files": [
  {
   "path": "test/functional/interface_ipc_mining.py",
   "add": 25,
   "del": 9
  },
  {
   "path": "test/functional/test_framework/ipc_util.py",
   "add": 2,
   "del": 2
  }
 ],
 "test_lines": 38,
 "git": {
  "head": "8fb2dbc0d6e788da43c8e471b6e8027b03b8b6ec",
  "head_matches_backup": true,
  "base": "05e49b342faa1412266951429c135e9f5daa30c2",
  "commits": [
   {
    "sha": "8fb2dbc0d6",
    "subject": "test: cover feeThreshold = MAX_MONEY",
    "files": 2,
    "add": 27,
    "del": 11
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "a322375c31944556",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}