{
 "number": 36231,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36231",
 "title": "test: add pow tests covering live mutants",
 "author": "ViniciusCestarii",
 "author_association": "CONTRIBUTOR",
 "created_at": "2026-09-12T15:19:31Z",
 "updated_at": "2026-09-16T11:46:13Z",
 "age_days": 5,
 "draft": false,
 "labels": [
  "Tests"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "e6975adf58c354dbd167b419ad7aadc7b8053af6",
 "head_ref": "kill-pow-mutants",
 "head_repo": "ViniciusCestarii/bitcoin",
 "head_history": [
  {
   "t": "2026-09-14T19:08:20Z",
   "sha": "e6975adf58c354dbd167b419ad7aadc7b8053af6"
  }
 ],
 "additions": 67,
 "deletions": 0,
 "changed_files": 1,
 "commit_count": 4,
 "size_bucket": "S",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "ack": [
     {
      "login": "brunoerg",
      "url": "https://github.com/bitcoin/bitcoin/pull/36231#pullrequestreview-5222188124"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {
  "brunoerg": {
   "kind": "ack",
   "hash": "e6975adf58c354dbd167b419ad7aadc7b8053af6",
   "t": "2026-09-16T11:46:10Z",
   "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": 1,
  "changes_requested": 0,
  "distinct_reviewers": [
   "brunoerg"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-09-14T19:17:23Z",
  "last_reviewer_activity": "2026-09-16T11:46:10Z",
  "last_reviewer": "brunoerg",
  "author_silent_days": 2,
  "waiting_on_author_days": 1,
  "days_since_update": 1
 },
 "refs": {
  "mentioned": [],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/test/pow_tests.cpp"
 ],
 "body": "Kills 4 live mutants on pow.cpp found with https://github.com/ViniciusCestarii/mutant-harness. The first 2 affect consensus. The third affect header sync and could stall IBD. The forth cover header sync hardening against DoS. They are:\n\npow.cpp (killed by 1811735255b98257e7bf385325599c8d9bb69ad0): <code>GetNextWorkRequired</code>: <code>pindexFirst->GetBlockTime()</code> -> <code>GetMedianTimePast()</code>\n\n```diff\ndiff --git a/src/pow.cpp b/src/pow.cpp\nindex 9a9f4e5..9872b2c 100644\n--- a/src/pow.cpp\n+++ b/src/pow.cpp\n@@ -44,7 +44,7 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHead\n     const CBlockIndex* pindexFirst = pindexLast->GetAncestor(nHeightFirst);\n     assert(pindexFirst);\n\n-    return CalculateNextWorkRequired(pindexLast, pindexFirst->GetBlockTime(), params);\n+    return CalculateNextWorkRequired(pindexLast, pindexFirst->GetMedianTimePast(), params);\n }\n\n unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nFirstBlockTime, const Consensus::Params& params)\n```\n\npow.cpp (killed by 7834db23914a3faa3660cb609144cd1c345a1ef3): <code>CalculateNextWorkRequired</code>: <code>int64_t nActualTimespan</code> -> <code>uint32_t</code>.\n\n```diff\ndiff --git a/src/pow.cpp b/src/pow.cpp\nindex 9a9f4e5..1917b7b 100644\n--- a/src/pow.cpp\n+++ b/src/pow.cpp\n@@ -53,7 +53,7 @@ unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nF\n         return pindexLast->nBits;\n\n     // Limit adjustment step\n-    int64_t nActualTimespan = pindexLast->GetBlockTime() - nFirstBlockTime;\n+    uint32_t nActualTimespan = pindexLast->GetBlockTime() - nFirstBlockTime;\n     if (nActualTimespan < params.nPowTargetTimespan/4)\n         nActualTimespan = params.nPowTargetTimespan/4;\n     if (nActualTimespan > params.nPowTargetTimespan*4)\n```\n\npow.cpp (killed by a9873943e259b23884fb4295591a82168c831026): <code>PermittedDifficultyTransition</code>: compares <code>smallest_difficulty_target</code> directly instead of round-tripping it through <code>SetCompact(GetCompact())</code>\n\n```diff\ndiff --git a/src/pow.cpp b/src/pow.cpp\nindex 9a9f4e5..028fa9f 100644\n--- a/src/pow.cpp\n+++ b/src/pow.cpp\n@@ -124,11 +124,8 @@ bool PermittedDifficultyTransition(const Consensus::Params& params, int64_t heig\n             smallest_difficulty_target = pow_limit;\n         }\n\n-        // Round and then compare this new calculated value to what is\n-        // observed.\n-        arith_uint256 minimum_new_target;\n-        minimum_new_target.SetCompact(smallest_difficulty_target.GetCompact());\n-        if (minimum_new_target > observed_new_target) return false;\n+        // Compare this new calculated value to what is observed.\n+        if (smallest_difficulty_target > observed_new_target) return false;\n     } else if (old_nbits != new_nbits) {\n         return false;\n     }\n```\n\npow.cpp (killed by e6975adf58c354dbd167b419ad7aadc7b8053af6): <code>PermittedDifficultyTransition</code>: drops the non-retarget height check that requires <code>old_nbits == new_nbits</code>\n\n```diff\ndiff --git a/src/pow.cpp b/src/pow.cpp\nindex 9a9f4e5..ef2e788 100644\n--- a/src/pow.cpp\n+++ b/src/pow.cpp\n@@ -129,8 +129,6 @@ bool PermittedDifficultyTransition(const Consensus::Params& params, int64_t heig\n         arith_uint256 minimum_new_target;\n         minimum_new_target.SetCompact(smallest_difficulty_target.GetCompact());\n         if (minimum_new_target > observed_new_target) return false;\n-    } else if (old_nbits != new_nbits) {\n-        return false;\n     }\n     return true;\n }\n```\n\nRecommend reviewing per commit.",
 "commits": [
  {
   "sha": "1811735255b98257e7bf385325599c8d9bb69ad0",
   "date": "2026-09-14T19:05:54Z",
   "message": "test: cover first block time selection in GetNextWorkRequired\n\nCo-authored-by: brunoerg <brunoely.gc@gmail.com>"
  },
  {
   "sha": "7834db23914a3faa3660cb609144cd1c345a1ef3",
   "date": "2026-09-14T19:06:29Z",
   "message": "test: cover negative timespan in CalculateNextWorkRequired"
  },
  {
   "sha": "a9873943e259b23884fb4295591a82168c831026",
   "date": "2026-09-14T19:06:29Z",
   "message": "test: cover lower bound rounding in PermittedDifficultyTransition"
  },
  {
   "sha": "e6975adf58c354dbd167b419ad7aadc7b8053af6",
   "date": "2026-09-14T19:06:29Z",
   "message": "test: cover non-retarget heights in PermittedDifficultyTransition"
  }
 ],
 "timeline": [
  {
   "t": "2026-09-14T17:30:48Z",
   "kind": "review_comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "path": "src/test/pow_tests.cpp",
   "commit": "b2b97b9f3541eb74ad451cbafd92af8d3b003e68",
   "in_reply_to": null,
   "text": "nit: Why not using the same pattern of `get_next_work_lower_limit_rounding`?\n\n```suggestion\n    const auto consensus = CreateChainParams(*m_node.args, ChainType::MAIN)->GetConsensus();\n```"
  },
  {
   "t": "2026-09-14T17:45:42Z",
   "kind": "review_comment",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "path": "src/test/pow_tests.cpp",
   "commit": "e6975adf58c354dbd167b419ad7aadc7b8053af6",
   "in_reply_to": null,
   "text": "This is good but I think `get_next_work_first_block_time` could be written in way that is simpler to understand (e.g. for me it is not clear why +5000?), perhaps:\n\n```cpp\nBOOST_AUTO_TEST_CASE(get_next_work_first_block_time)\n  {\n      const auto consensus{CreateChainParams(*m_node.args, ChainType::MAIN)->GetConsensus()};\n      const int64_t interval{consensus.DifficultyAdjustmentInterval()};\n\n      // Two full retarget windows, so that the first block of the second one\n      // has ancestors and hence a median time past distinct from its own time.\n      std::vector<CBlockIndex> blocks(2 * interval);\n      for (int64_t i = 0; i < 2 * interval; i++) {\n          blocks[i].pprev = i ? &blocks[i - 1] : nullptr;\n          blocks[i].nHeight = i;\n          blocks[i].nTime = 1269211443 + i * consensus.nPowTargetSpacing;\n          blocks[i].nBits = 0x1c05a3f4;\n      }\n\n      const CBlockIndex& first{blocks[interval]};\n      const CBlockIndex& last{blocks[2 * interval - 1]};\n      BOOST_CHECK_NE(first.GetBlockTime(), first.GetMedianTimePast());\n      BOOST_CHECK_EQUAL(GetNextWorkRequired(&last, nullptr, consensus),\n                        CalculateNextWorkRequired(&last, first.GetBlockTime(), consensus));\n  }\n```"
  },
  {
   "t": "2026-09-14T19:08:20Z",
   "kind": "force_push",
   "who": "ViniciusCestarii",
   "commit": "e6975adf58c354dbd167b419ad7aadc7b8053af6"
  },
  {
   "t": "2026-09-14T19:10:27Z",
   "kind": "review_comment",
   "who": "ViniciusCestarii",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/pow_tests.cpp",
   "commit": "b2b97b9f3541eb74ad451cbafd92af8d3b003e68",
   "in_reply_to": 4007809837,
   "text": "Thanks for spotting this, I forgot to change this one. Done 7834db23914a3faa3660cb609144cd1c345a1ef3"
  },
  {
   "t": "2026-09-14T19:12:13Z",
   "kind": "review_comment",
   "who": "ViniciusCestarii",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/pow_tests.cpp",
   "commit": "e6975adf58c354dbd167b419ad7aadc7b8053af6",
   "in_reply_to": 4007932139,
   "text": "I agree, I thought the 5000 was needed but it isn't. I have included most of this diff on 1811735255b98257e7bf385325599c8d9bb69ad0. I didn't include the last line because the pattern in pow_tests.cpp is to hardcode the expected_nbits."
  },
  {
   "t": "2026-09-14T19:17:23Z",
   "kind": "comment",
   "who": "ViniciusCestarii",
   "assoc": "CONTRIBUTOR",
   "text": "Thanks for the review! Forced-push e6975adf58c354dbd167b419ad7aadc7b8053af6 addressing @brunoerg comments."
  },
  {
   "t": "2026-09-16T11:46:10Z",
   "kind": "review",
   "who": "brunoerg",
   "assoc": "MEMBER",
   "state": "APPROVED",
   "commit": "e6975adf58c354dbd167b419ad7aadc7b8053af6",
   "text": "ACK e6975adf58c354dbd167b419ad7aadc7b8053af6"
  }
 ],
 "labels_log": [
  {
   "t": "2026-09-12T15:19:35Z",
   "action": "labeled",
   "label": "Tests",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-09-14T16:17:02Z",
   "kind": "renamed",
   "who": "fanquake",
   "from": "test: add tests in pow_tests.cpp covering live mutants",
   "to": "test: add pow tests covering live mutants"
  }
 ],
 "text_chars": 5818,
 "text_tokens_estimate": 1454,
 "changed_paths": [
  "src/test/pow_tests.cpp"
 ],
 "files": [
  {
   "path": "src/test/pow_tests.cpp",
   "add": 67,
   "del": 0
  }
 ],
 "test_lines": 67,
 "git": {
  "head": "e6975adf58c354dbd167b419ad7aadc7b8053af6",
  "head_matches_backup": true,
  "base": "d2e24e951de45e7e8d328ef36b80c055c90a6fdd",
  "commits": [
   {
    "sha": "1811735255",
    "subject": "test: cover first block time selection in GetNextWorkRequired",
    "files": 1,
    "add": 25,
    "del": 0
   },
   {
    "sha": "7834db2391",
    "subject": "test: cover negative timespan in CalculateNextWorkRequired",
    "files": 1,
    "add": 15,
    "del": 0
   },
   {
    "sha": "a9873943e2",
    "subject": "test: cover lower bound rounding in PermittedDifficultyTransition",
    "files": 1,
    "add": 15,
    "del": 0
   },
   {
    "sha": "e6975adf58",
    "subject": "test: cover non-retarget heights in PermittedDifficultyTransition",
    "files": 1,
    "add": 12,
    "del": 0
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "ac644701c34c9464",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}