{
 "number": 36192,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36192",
 "title": "test: cover unsatisfiable mining timestamp",
 "author": "Sjors",
 "author_association": "MEMBER",
 "created_at": "2026-09-08T11:46:13Z",
 "updated_at": "2026-09-16T09:08:30Z",
 "age_days": 9,
 "draft": false,
 "labels": [
  "Tests"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "db7a485c330ed154c2aa705f553d323c7b2276c6",
 "head_ref": "2026/09/impossible-time",
 "head_repo": "Sjors/bitcoin",
 "head_history": [
  {
   "t": "2026-09-08T12:25:10Z",
   "sha": "38e23501c34f74fe5b1f65df3eaf3076b318e59b"
  },
  {
   "t": "2026-09-08T12:46:47Z",
   "sha": "f9e72b898981c554f286168784b04fab0cc12f50"
  },
  {
   "t": "2026-09-10T12:27:36Z",
   "sha": "1f6694ecb332d80a31b80b2af5735e73613a240b"
  },
  {
   "t": "2026-09-14T12:37:46Z",
   "sha": "db7a485c330ed154c2aa705f553d323c7b2276c6"
  }
 ],
 "additions": 124,
 "deletions": 29,
 "changed_files": 6,
 "commit_count": 5,
 "size_bucket": "M",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "ack": [
     {
      "login": "fjahr",
      "url": "https://github.com/bitcoin/bitcoin/pull/36192#pullrequestreview-5200714481"
     }
    ],
    "concept_ack": [
     {
      "login": "adezo24h1",
      "url": "https://github.com/bitcoin/bitcoin/pull/36192#issuecomment-5666440105"
     }
    ]
   },
   "conflicts": [
    {
     "number": 36257,
     "title": "qa: assert_equals -> assert_true/assert_false",
     "author": "hodlinator"
    },
    {
     "number": 35793,
     "title": "Implement BIP 54 (Consensus Cleanup) without mainnet activation",
     "author": "darosior"
    }
   ]
  }
 },
 "acks_parsed": {
  "fjahr": {
   "kind": "ack",
   "hash": "db7a485c330ed154c2aa705f553d323c7b2276c6",
   "t": "2026-09-14T17:21:21Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 1,
  "stale_ack": 0,
  "concept_ack": 0,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "adezo24h1",
   "fjahr"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-09-15T07:16:55Z",
  "last_reviewer_activity": "2026-09-14T17:21:21Z",
  "last_reviewer": "fjahr",
  "author_silent_days": 2,
  "waiting_on_author_days": 0,
  "days_since_update": 1
 },
 "refs": {
  "mentioned": [
   30681,
   31981,
   35949
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 30681,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2024-08-22",
    "title": "Have miner account for timewarp mitigation, activate on regtest, lower nPowTargetTimespan to 144 and add test"
   },
   {
    "number": 35949,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-09-07",
    "title": "miner: Enforce Murch-Zawy rule (BIP54)"
   },
   {
    "number": 31981,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2025-06-19",
    "title": "Add checkBlock() to Mining interface"
   }
  ],
  "conflicts": [
   36257,
   35793
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "test/functional/interface_ipc_mining.py",
  "test/functional/mining_basic.py",
  "test/functional/test_framework/blocktools.py"
 ],
 "body": "This PR adds test coverage for what happens in the (extremely unlikely) event that the miner can't satisfy the constraints. This is inspired by the recently added timewarp-protection and Murch-Zawy rules (for the miner, not for consensus, see #30681 and #35949), but this is a pre-existing issue. It is not made worse by these rules.\n\nThe test has an attacker mine 6 blocks, which set MTP at the victim's future-time limit. It calls `getblocktemplate` and the equivalent IPC method, which both fail. It then moves mock time one second forward, and demonstrates mining works again (and we don't drop the IPC connection).\n\nThis (impractical) attack can be done at any height, be we illustrate it for blocks 142, 143 and 0 of a retarget period, to clarify that the timewarp-protection and Murch-Zawy rules do not matter.\n\nThe test was added to `ipc_mining.py`, to avoid having to add IPC support to `mining_basic.py`. Mining coverage is currently split between various test files and not always mirrored between RPC and IPC. A followup could unify these, while still skipping the IPC side when that's not compiled.\n\nA few refactor commits to prepare:\n\n- fix incorrect name: `REGTEST_RETARGET_PERIOD` -> `HALVING_INTERVAL`\n- drop `REGTEST_` prefix from `REGTEST_N_BITS` and `REGTEST_TARGET` (consistent with `HALVING_INTERVAL`, `DIFFICULTY_ADJUSTMENT_INTERVAL` and `TIME_GENESIS_BLOCK`).\n- move `DIFFICULTY_ADJUSTMENT_INTERVAL` to the framework\n\nThe last commit picks up a Murch-Zawy followup suggested in https://github.com/bitcoin/bitcoin/pull/35949#discussion_r3944011135 and takes advantage of our refactor, but is otherwise unrelated.",
 "commits": [
  {
   "sha": "8a45b25d3bf7f7f2923df2174b105b558f4929b1",
   "date": "2026-09-08T12:23:44Z",
   "message": "test: rename REGTEST_RETARGET_PERIOD to HALVING_INTERVAL\n\nThe constant is the regtest nSubsidyHalvingInterval and is only used\nas the default halving_period in create_coinbase."
  },
  {
   "sha": "af7c45ef2ff20b1c7e772975561fd6df62462026",
   "date": "2026-09-14T10:54:25Z",
   "message": "test: drop REGTEST_ prefix from block difficulty constants\n\nConsistently omit the REGTEST_ prefix for constants."
  },
  {
   "sha": "327bd124cbe4210b8bc8aed8799f89cb2327eaf4",
   "date": "2026-09-14T10:54:27Z",
   "message": "test: share block timing constants through blocktools\n\nMove DIFFICULTY_ADJUSTMENT_INTERVAL to blocktools, since mining_basic.py\nand rpc_blockchain.py both defined the regtest difficulty adjustment\ninterval. Also import the existing MAX_FUTURE_BLOCK_TIME constant in\nmining_basic.py instead of defining it locally."
  },
  {
   "sha": "3840a9048a2e939db0ed4b055caba0ff9c1660cc",
   "date": "2026-09-14T12:35:21Z",
   "message": "test: cover unsatisfiable mining timestamp\n\nSimulate attacker blocks setting MTP at the victim's future-time\nlimit. Check getblocktemplate and IPC failure, then recovery one\nsecond later.\n\nThis attack is impractical, but the code is reachable."
  },
  {
   "sha": "db7a485c330ed154c2aa705f553d323c7b2276c6",
   "date": "2026-09-14T12:35:21Z",
   "message": "test: assert Murch-Zawy period boundaries\n\nCo-authored-by: sedited <seb.kung@gmail.com>"
  }
 ],
 "timeline": [
  {
   "t": "2026-09-08T12:25:10Z",
   "kind": "force_push",
   "who": "Sjors",
   "commit": "38e23501c34f74fe5b1f65df3eaf3076b318e59b"
  },
  {
   "t": "2026-09-08T12:46:47Z",
   "kind": "force_push",
   "who": "Sjors",
   "commit": "f9e72b898981c554f286168784b04fab0cc12f50"
  },
  {
   "t": "2026-09-08T14:42:06Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "path": "test/functional/test_framework/blocktools.py",
   "commit": "db7a485c330ed154c2aa705f553d323c7b2276c6",
   "in_reply_to": null,
   "text": "nit: I would have preferred to the `REGTEST_` prefix"
  },
  {
   "t": "2026-09-08T14:45:08Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "path": "test/functional/test_framework/blocktools.py",
   "commit": "db7a485c330ed154c2aa705f553d323c7b2276c6",
   "in_reply_to": null,
   "text": "nit: Similarly, would suggest to have a `REGTEST_` prefix here too"
  },
  {
   "t": "2026-09-08T16:19:11Z",
   "kind": "review",
   "who": "fjahr",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "f9e72b898981c554f286168784b04fab0cc12f50",
   "text": "Concept ACK on the first three commits, not sure yet about the unsatisfiable test\n\nThe PR description was pretty confusing to me. The third commit adds the suggested test edits from @sedited which are in the MZ-related test. But then the new test for the unsatisfiable mining timestamp doesn't seem to be related to MZ. The PR description kind of makes it seem like it is.\n\nI am also not sure this test has to be in ipc interface test. It looks nice the way it\u2019s implemented but adding it in mining_basic is probably possible too and runs this test more regularly. But then again, this is more for documentation purposes and not so much regression testing, so I guess it doesn\u2019t matter. Would still be good to explicitly state the motivation though."
  },
  {
   "t": "2026-09-10T12:18:04Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "test/functional/test_framework/blocktools.py",
   "commit": "db7a485c330ed154c2aa705f553d323c7b2276c6",
   "in_reply_to": 3959105639,
   "text": "I prefer to only prefix other networks."
  },
  {
   "t": "2026-09-10T12:27:36Z",
   "kind": "force_push",
   "who": "Sjors",
   "commit": "1f6694ecb332d80a31b80b2af5735e73613a240b"
  },
  {
   "t": "2026-09-10T12:31:24Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "@fjahr I swapped the last two commits and clarified the PR description to point out that:\n- Murch-Zawy was the inspiration, but no the actual issue.\n- the last commit is just a hitch-hiker\n\n[quoted text omitted]\nBoth are covered by CI. And we should cover both IPC and RPC because they have different failure modes (in particular the IPC test checks it doesn't disconnect).\n\nAdding it to mining_basic means having to add IPC support to that test, and skipping part of the test if that's not compiled (which is less clear than skipping the whole test)."
  },
  {
   "t": "2026-09-11T19:36:36Z",
   "kind": "comment",
   "who": "adezo24h1",
   "assoc": "NONE",
   "text": "Tested 1f6694e on Ubuntu 24.04 WSL (ENABLE_IPC=OFF, bitcoind from ~/src/bitcoin):\n  python3 build/test/functional/test_runner.py mining_basic.py rpc_blockchain.py\nResult: pass\nDid not run interface_ipc_mining.py (IPC not built).\nI read the Python test files but I am still learning the timestamp / MTP logic, so this is not an ACK ;-)"
  },
  {
   "t": "2026-09-12T13:55:20Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "path": "test/functional/test_framework/blocktools.py",
   "commit": "db7a485c330ed154c2aa705f553d323c7b2276c6",
   "in_reply_to": 3959105639,
   "text": "Could your remove them from `REGTEST_N_BITS` and `REGTEST_TARGET` then so it is at least consistent?"
  },
  {
   "t": "2026-09-12T14:09:10Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "path": "test/functional/mining_basic.py",
   "commit": "1f6694ecb332d80a31b80b2af5735e73613a240b",
   "in_reply_to": null,
   "text": "Can import `MAX_FUTURE_BLOCK_TIME` from `blocktools.py` as well."
  },
  {
   "t": "2026-09-12T14:32:14Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "path": "test/functional/interface_ipc_mining.py",
   "commit": "1f6694ecb332d80a31b80b2af5735e73613a240b",
   "in_reply_to": null,
   "text": "The test can be simplified because it doesn't seem to need the mining to the end of the difficulty period in order to work.\n\n```diff\ndiff --git a/test/functional/interface_ipc_mining.py b/test/functional/interface_ipc_mining.py\nindex 66aa5e6eddf..43ac5fe2dee 100755\n--- a/test/functional/interface_ipc_mining.py\n+++ b/test/functional/interface_ipc_mining.py\n@@ -10,7 +10,6 @@ from copy import deepcopy\n from decimal import Decimal\n from io import BytesIO\n from test_framework.blocktools import (\n-    DIFFICULTY_ADJUSTMENT_INTERVAL,\n     MAX_FUTURE_BLOCK_TIME,\n     NORMAL_GBT_REQUEST_PARAMS,\n     NULL_OUTPOINT,\n@@ -777,25 +776,12 @@ class IPCMiningTest(BitcoinTestFramework):\n         victim = self.nodes[0]\n         attacker = self.nodes[1]\n\n-        # Mine to the end of the difficulty period, with room for six attack\n-        # blocks that determine its median time past.\n-        num_attack_blocks = 6\n-        blocks_to_mine = (\n-            DIFFICULTY_ADJUSTMENT_INTERVAL - 2 - num_attack_blocks\n-            - victim.getblockcount()\n-        ) % DIFFICULTY_ADJUSTMENT_INTERVAL\n-        self.generate(attacker, blocks_to_mine)\n-\n+        # Six of the last eleven blocks determine the median time past.\n         victim_time = int(time.time())\n         attacker_time = victim_time + MAX_FUTURE_BLOCK_TIME\n         victim.setmocktime(victim_time)\n         attacker.setmocktime(attacker_time)\n-        self.generate(attacker, num_attack_blocks)\n-\n-        assert_equal(\n-            (victim.getblockcount() + 1) % DIFFICULTY_ADJUSTMENT_INTERVAL,\n-            DIFFICULTY_ADJUSTMENT_INTERVAL - 1,\n-        )\n+        self.generate(attacker, 6)\n         assert_equal(victim.getblockchaininfo()[\"mediantime\"], attacker_time)\n\n         # The next block must be later than MTP, but future timestamps are only\n```"
  },
  {
   "t": "2026-09-12T14:48:23Z",
   "kind": "review",
   "who": "fjahr",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "1f6694ecb332d80a31b80b2af5735e73613a240b",
   "text": "Thanks for addressing my comments, I think the description can still be improved unless I have a misunderstanding on my side.\n\n[quoted text omitted]\nI don't understand how the timewarp rule plays a role here. It's just a conflict between the plain MTP + 1 rule and the 2 hour max future rule, no?\n\n[quoted text omitted]\nIt doesn't have to be at the end of the adjustment period since MZ doesn't play a role for this test, it works on any height afaict, see also my comment about simplifying the test.\n\nUnrelated to the description:\n\n[quoted text omitted]\nOk, that's fine for me as a goal, but that probably applies to most of the other tests in `miner_basic.py` as well and also possible future tests right? Is there a plan to unify those test files so that we don't have to discuss where new mining tests go every time? I guess it would make sense that we have one miner test file that runs every test always in basic mode and then also runs each of them ipc when possible. But I didn't look into what that would look like in practice."
  },
  {
   "t": "2026-09-14T12:36:04Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "test/functional/test_framework/blocktools.py",
   "commit": "db7a485c330ed154c2aa705f553d323c7b2276c6",
   "in_reply_to": 3959105639,
   "text": "Done.\n\nI did look at either dropping `REGTEST_` from `REGTEST_N_BITS` and `REGTEST_TARGET`, or adding it to `DIFFICULTY_ADJUSTMENT_INTERVAL` and `TIME_GENESIS_BLOCK`. The latter is a bigger change, and leads to longer names."
  },
  {
   "t": "2026-09-14T12:36:20Z",
   "kind": "comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nYou're right, it doesn't. Since I was confused myself, I modified the test instead to repeat for blocks `N - 2`, `N - 1` and `0` of a retarget period, with brief comments for why neither rule matters. I also updated the PR description.\n\n[quoted text omitted]\nThere isn't, but I made the suggestion explicit in the PR description. Since we introduced IPC, and its various methods, more new mining test coverage was added on the IPC side, with some of it mirrored to RPC, see e.g. #31981."
  },
  {
   "t": "2026-09-14T12:36:50Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "test/functional/interface_ipc_mining.py",
   "commit": "1f6694ecb332d80a31b80b2af5735e73613a240b",
   "in_reply_to": 3996509310,
   "text": "See https://github.com/bitcoin/bitcoin/pull/36192#issuecomment-5664027162"
  },
  {
   "t": "2026-09-14T12:37:46Z",
   "kind": "force_push",
   "who": "Sjors",
   "commit": "db7a485c330ed154c2aa705f553d323c7b2276c6"
  },
  {
   "t": "2026-09-14T15:30:00Z",
   "kind": "comment",
   "who": "adezo24h1",
   "assoc": "NONE",
   "text": "Tested db7a485c330ed154c2aa705f553d323c7b2276c6\nNot an ACK.\nRan on Ubuntu 24.04 WSL (ENABLE_IPC=OFF):\n  python3 build/test/functional/test_runner.py mining_basic.py rpc_blockchain.py\nResult: pass\nI didn't run interface_ipc_mining.py, fyi"
  },
  {
   "t": "2026-09-14T17:17:25Z",
   "kind": "review_comment",
   "who": "fjahr",
   "assoc": "MEMBER",
   "path": "test/functional/interface_ipc_mining.py",
   "commit": "db7a485c330ed154c2aa705f553d323c7b2276c6",
   "in_reply_to": null,
   "text": "nit: I find it pretty heavy that this test now mines ~450 blocks just to prove that these special heights and their BIP54 rules do not have an impact on this super edge case scenario that is documented with the test. I would have prefered that the test had been simplified to be more focussed on the edge case only and with mining only 6 blocks. But regtest mining is cheap enough that this isn't a blocker for me and maybe there is some value in the added documentation here."
  },
  {
   "t": "2026-09-14T17:21:21Z",
   "kind": "review",
   "who": "fjahr",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "db7a485c330ed154c2aa705f553d323c7b2276c6",
   "text": "Code review ACK db7a485c330ed154c2aa705f553d323c7b2276c6\n\nThanks for addressing my feedback!"
  },
  {
   "t": "2026-09-15T07:16:55Z",
   "kind": "review_comment",
   "who": "Sjors",
   "assoc": "MEMBER",
   "path": "test/functional/interface_ipc_mining.py",
   "commit": "db7a485c330ed154c2aa705f553d323c7b2276c6",
   "in_reply_to": 4007699285,
   "text": "An earlier version mined up to N-2 and then performed the check on blocks N - 1, N and 0. But I found that confusing, hence the three rounds. If it's actually a performance problem, then we can drop it, but as you say, regtest is cheap."
  }
 ],
 "labels_log": [
  {
   "t": "2026-09-08T11:46:17Z",
   "action": "labeled",
   "label": "Tests",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-08T12:25:39Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-08T13:55:06Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 9283,
 "text_tokens_estimate": 2320,
 "changed_paths": [
  "test/functional/feature_assumeutxo.py",
  "test/functional/feature_block.py",
  "test/functional/interface_ipc_mining.py",
  "test/functional/mining_basic.py",
  "test/functional/rpc_blockchain.py",
  "test/functional/test_framework/blocktools.py"
 ],
 "files": [
  {
   "path": "test/functional/feature_assumeutxo.py",
   "add": 4,
   "del": 4
  },
  {
   "path": "test/functional/feature_block.py",
   "add": 4,
   "del": 4
  },
  {
   "path": "test/functional/interface_ipc_mining.py",
   "add": 92,
   "del": 0
  },
  {
   "path": "test/functional/mining_basic.py",
   "add": 10,
   "del": 8
  },
  {
   "path": "test/functional/rpc_blockchain.py",
   "add": 7,
   "del": 7
  },
  {
   "path": "test/functional/test_framework/blocktools.py",
   "add": 7,
   "del": 6
  }
 ],
 "test_lines": 153,
 "git": {
  "head": "db7a485c330ed154c2aa705f553d323c7b2276c6",
  "head_matches_backup": true,
  "base": "33a363ea250839ca31ea043b7500789d2e5d844b",
  "commits": [
   {
    "sha": "8a45b25d3b",
    "subject": "test: rename REGTEST_RETARGET_PERIOD to HALVING_INTERVAL",
    "files": 1,
    "add": 2,
    "del": 2
   },
   {
    "sha": "af7c45ef2f",
    "subject": "test: drop REGTEST_ prefix from block difficulty constants",
    "files": 5,
    "add": 24,
    "del": 24
   },
   {
    "sha": "327bd124cb",
    "subject": "test: share block timing constants through blocktools",
    "files": 3,
    "add": 4,
    "del": 3
   },
   {
    "sha": "3840a9048a",
    "subject": "test: cover unsatisfiable mining timestamp",
    "files": 1,
    "add": 92,
    "del": 0
   },
   {
    "sha": "db7a485c33",
    "subject": "test: assert Murch-Zawy period boundaries",
    "files": 1,
    "add": 2,
    "del": 0
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "3f7c96d225bc6d96",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}