{
 "number": 35820,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/35820",
 "title": "refactor: keep duration calculations typed",
 "author": "l0rinc",
 "author_association": "MEMBER",
 "created_at": "2026-07-27T05:06:59Z",
 "updated_at": "2026-08-31T16:52:46Z",
 "age_days": 52,
 "draft": false,
 "labels": [
  "Refactoring",
  "Needs rebase"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "0716f3ef0411a7990d9446cf6f49f7a1ea2296c6",
 "head_ref": "l0rinc/compile-time-chrono-hardening",
 "head_repo": "l0rinc/bitcoin",
 "head_history": [
  {
   "t": "2026-07-28T01:32:02Z",
   "sha": "96f64536aef4e3ac66c5869a971844bd8c862755"
  },
  {
   "t": "2026-07-28T01:38:08Z",
   "sha": "46fe6173bfd8e411cc14d5494a6645016e734aae"
  },
  {
   "t": "2026-07-28T01:45:56Z",
   "sha": "7104b41d04705efd018cd6db499074736450398d"
  },
  {
   "t": "2026-07-29T06:01:08Z",
   "sha": "7baa431d4c3e856e5a88068030723350f685bbbd"
  },
  {
   "t": "2026-07-29T06:35:33Z",
   "sha": "178eb282e3e076357283f412eb62060bbb6414c8"
  },
  {
   "t": "2026-08-18T00:51:03Z",
   "sha": "0716f3ef0411a7990d9446cf6f49f7a1ea2296c6"
  }
 ],
 "additions": 107,
 "deletions": 97,
 "changed_files": 26,
 "commit_count": 13,
 "size_bucket": "M",
 "mergeable_state": "unknown",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "janb84",
      "url": "https://github.com/bitcoin/bitcoin/pull/35820#pullrequestreview-4786803820"
     }
    ]
   },
   "conflicts": [
    {
     "number": 35906,
     "title": "First steps towards a stateless, side-effect free validation library",
     "author": "purpleKarrot"
    },
    {
     "number": 35716,
     "title": "wallet: Replace mapWallet and wtxOrdered with a boost::multi_index",
     "author": "achow101"
    },
    {
     "number": 35570,
     "title": "refactor: Change some validation.cpp methods to return BlockValidationState",
     "author": "optout21"
    },
    {
     "number": 35557,
     "title": "kernel, validation: Add btck_chainstate_manager_set_clock_time",
     "author": "ryanofsky"
    },
    {
     "number": 35351,
     "title": "net: Disallow invalid HeadersSyncState due to lagging clock",
     "author": "hodlinator"
    }
   ]
  }
 },
 "acks_parsed": {
  "janb84": {
   "kind": "concept_ack",
   "hash": "7bb44a4b757c1d84091cd5518b5fa2abeb90168c",
   "t": "2026-07-27T12:38:25Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 1,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "janb84",
   "maflcko",
   "stickies-v"
  ]
 },
 "signals": {
  "needs_rebase": true,
  "ci_failed": false,
  "mergeable_state": "unknown",
  "last_author_activity": "2026-08-18T00:51:05Z",
  "last_reviewer_activity": "2026-07-29T06:21:50Z",
  "last_reviewer": "maflcko",
  "author_silent_days": 30,
  "waiting_on_author_days": 0,
  "days_since_update": 16
 },
 "refs": {
  "mentioned": [
   35792
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 35792,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-07-24",
    "title": "refactor: Make all `const static` class members `constexpr`"
   }
  ],
  "conflicts": [
   35906,
   35716,
   35570,
   35557,
   35351
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/net_processing.cpp",
  "src/qt/bitcoingui.cpp",
  "src/qt/guiutil.cpp",
  "src/qt/modaloverlay.cpp"
 ],
 "body": "**Problem:** Several fixed-duration calculations use raw integer time counts, leaving their units implicit and allowing unrelated integer values to be mixed.\n\n**Fix:** Use `std::chrono` types through calculations where the surrounding APIs already support them, including GUI formatting, peer bans, scheduler forwarding, block timestamps, and target-spacing arithmetic.\nWhere carrying chrono through an integer interface would broaden the change or make the result harder to read, this PR leaves the expression unchanged or keeps the conversion simply `duration / 1s`.\nThe remaining integer interfaces receive the same values, and characterization tests pin the PoW timing and inclusive assumevalid threshold.",
 "commits": [
  {
   "sha": "cfd3c93bff1bea77153d5ab3bb980c347e29daac",
   "date": "2026-08-18T00:47:30Z",
   "message": "rpc: type mockscheduler delta\n\nParse the requested delta as seconds, compare it against typed limits, and pass it directly to the scheduler interfaces."
  },
  {
   "sha": "c8e857271457c51ef726886533be6917c400d86c",
   "date": "2026-08-18T00:47:30Z",
   "message": "qt: type nice time offsets\n\nMake `formatNiceTimeOffset` accept seconds so its comparisons and divisions retain units.\nCallers now construct durations from raw values provided by Qt or consensus code.\n\nCo-authored-by: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz>"
  },
  {
   "sha": "10b4b5e4ee26f53fdb7e4f3556cd7c9b2ecc7537",
   "date": "2026-08-18T00:47:31Z",
   "message": "qt: type peer ban durations\n\nPass typed durations from the peer menu to `banSelectedNode`, converting only at the integer `Node::ban` interface.\nKeep the one-year action exactly 365 days.\n\nCo-authored-by: janb84 <githubjanb.drainer976@passmail.net>"
  },
  {
   "sha": "737cd057ae2abf85847e76d23c14712560b812ed",
   "date": "2026-08-18T00:47:31Z",
   "message": "qt: name blocks per day in prune estimate\n\nName the assumed blocks per day directly in the prune backup estimate."
  },
  {
   "sha": "57dab7d170084da8cfade1b66415faaa0341ffba",
   "date": "2026-08-18T00:47:31Z",
   "message": "mempool: type rolling fee halflife as duration\n\nStore the rolling fee halflife as `std::chrono::seconds` and convert it to a scalar only for the decay calculation.\nThe existing decay test independently pins the duration to 43,200 seconds."
  },
  {
   "sha": "09d03781b217b74b5a0df240a42cfcb4cbce8269",
   "date": "2026-08-18T00:47:31Z",
   "message": "net, rpc: use typed target spacing accessor\n\nUse `Consensus::Params::PowTargetSpacing()` so the pruning and RPC calculations divide durations directly.\nThis keeps their units visible and avoids converting either operand to raw seconds."
  },
  {
   "sha": "781a15da5743f02bb4b63543944ff1cf67504613",
   "date": "2026-08-18T00:47:45Z",
   "message": "refactor: use chrono for fixed durations\n\nState fixed durations in their natural units while preserving integer interfaces and exact values."
  },
  {
   "sha": "b7388c2cad0a3cd1a1b6c2781b6aca1564b0bfe8",
   "date": "2026-08-18T00:47:48Z",
   "message": "net: type block relay age limits\n\nStore relay age limits as durations and use typed block timestamps for the time arithmetic.\nConvert only where `GetBlockProofEquivalentTime` still returns integer seconds.\n\nCo-authored-by: janb84 <githubjanb.drainer976@passmail.net>"
  },
  {
   "sha": "2bcde266180223642ffec2345dc31aa89f446a28",
   "date": "2026-08-18T00:47:48Z",
   "message": "test: characterize PoW target timing\n\nPin literal target timespan and spacing values for every built-in chain.\nThese assertions provide a redundant units check before the following representation-only refactor."
  },
  {
   "sha": "2d6378168df9b4d194ed496a9e71feca83babca0",
   "date": "2026-08-18T00:47:48Z",
   "message": "kernel: use chrono types for PoW timing\n\nExpress each built-in chain\u2019s target timespan and spacing in its natural duration unit.\nThe preceding characterization pins the exact integer values consumed by consensus code.\n\nCo-authored-by: janb84 <githubjanb.drainer976@passmail.net>"
  },
  {
   "sha": "5b05115a923c4b534ae5d903611b06536ad6addf",
   "date": "2026-08-18T00:47:55Z",
   "message": "validation: type future block time limit\n\nStore the two-hour allowance as a duration and use it directly in time-point comparisons.\nConvert it only where existing interfaces still operate on integer seconds.\n\nThe `chain_tests/basic_tests` assertion pins the duration to 7,200 seconds."
  },
  {
   "sha": "7afe41099c325e78ab5aaea3fe5ce64476865944",
   "date": "2026-08-18T00:47:58Z",
   "message": "test: characterize assumevalid work threshold\n\nMake the assumevalid test hit exactly two weeks of equivalent work and pin that scripts remain checked at the inclusive threshold.\nSplit longer header sequences at `MAX_HEADERS_RESULTS`."
  },
  {
   "sha": "0716f3ef0411a7990d9446cf6f49f7a1ea2296c6",
   "date": "2026-08-18T00:47:58Z",
   "message": "validation: use chrono for assumevalid horizon\n\nExpress the inclusive two-week comparison directly in integer seconds.\nThe preceding functional characterization pins the exact threshold."
  }
 ],
 "timeline": [
  {
   "t": "2026-07-27T12:18:04Z",
   "kind": "review_comment",
   "who": "janb84",
   "assoc": "MEMBER",
   "path": "src/qt/guiutil.cpp",
   "commit": "7bb44a4b757c1d84091cd5518b5fa2abeb90168c",
   "in_reply_to": null,
   "text": "Just to unify this section I would opt for using `std::chrono` for every line. (not Blocking bike-shedding NIT)\n\n```suggestion\n    constexpr int HOUR_IN_SECONDS{TicksSeconds(std::chrono::hours{1})};\n    constexpr int DAY_IN_SECONDS{TicksSeconds(std::chrono::days{1})};\n    constexpr int WEEK_IN_SECONDS{TicksSeconds(std::chrono::weeks{1})};\n    constexpr int YEAR_IN_SECONDS{TicksSeconds(std::chrono::years{1})};\n```"
  },
  {
   "t": "2026-07-27T12:33:29Z",
   "kind": "review_comment",
   "who": "janb84",
   "assoc": "MEMBER",
   "path": "src/net_processing.cpp",
   "commit": "7bb44a4b757c1d84091cd5518b5fa2abeb90168c",
   "in_reply_to": null,
   "text": "NIT: why not go one step further for even more clarity ?\n```suggestion\nstatic constexpr int STALE_RELAY_AGE_LIMIT{TicksSeconds(std::chrono::days{30})};\n```\nNote: not to use std::chrono::month{1};"
  },
  {
   "t": "2026-07-27T12:34:10Z",
   "kind": "review_comment",
   "who": "janb84",
   "assoc": "MEMBER",
   "path": "src/net_processing.cpp",
   "commit": "7bb44a4b757c1d84091cd5518b5fa2abeb90168c",
   "in_reply_to": null,
   "text": "```suggestion\nstatic constexpr int HISTORICAL_BLOCK_AGE{TicksSeconds(std::chrono::weeks{1})};\n```"
  },
  {
   "t": "2026-07-27T12:38:25Z",
   "kind": "review",
   "who": "janb84",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "7bb44a4b757c1d84091cd5518b5fa2abeb90168c",
   "text": "Concept ACK 7bb44a4b757c1d84091cd5518b5fa2abeb90168c\n\nI like the clarity / readability this changes gives. It will even provide less proficient in C++ users to read consensus code.\n\nMaybe split commit in kernel/consensus touching and the rest. In that way it's easy to ACK / review the consensus relevant changes.\n\nSome NIT suggestions to use `std::chrono::<<duration>>` can be done on more places but I have limited the NITS to hear the current thinking first :)"
  },
  {
   "t": "2026-07-27T13:07:08Z",
   "kind": "comment",
   "who": "stickies-v",
   "assoc": "MEMBER",
   "text": "~0, some minor clarity improvements here and there but for the most part I don't see the point as this doesn't add any actual type safety or clarity. I don't find `14 * 24 * 60 * 60; // two weeks` harder to read than `TicksSeconds(2 * 7 * 24h);`"
  },
  {
   "t": "2026-07-28T01:32:02Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "96f64536aef4e3ac66c5869a971844bd8c862755"
  },
  {
   "t": "2026-07-28T01:36:14Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "text": "Thanks for the review and feedback.\n\nI agree that `14 * 24 * 60 * 60; // two weeks` is readable in isolation - the main benefit is moving units from comments into expressions and (where possible), into types.\n\nE.g. `MIN_BLOCKS_TO_KEEP - 3600 / nPowTargetSpacing` needed a `(1 hour)` comment (proving that others didn't consider them trivial), while `MIN_BLOCKS_TO_KEEP - 1h / PowTargetSpacing()` is self-describing and keeps both operands typed.\n\nLikewise, `8 * 60 * 60; // in seconds` becomes `TicksSeconds(8h)`, making the conversion and units explicit.\n\nThe branch is now split by risk, with exact-value coverage for the consensus-sensitive changes."
  },
  {
   "t": "2026-07-28T01:38:08Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "46fe6173bfd8e411cc14d5494a6645016e734aae"
  },
  {
   "t": "2026-07-28T01:45:56Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "7104b41d04705efd018cd6db499074736450398d"
  },
  {
   "t": "2026-07-28T18:03:43Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/qt/bitcoingui.cpp",
   "commit": "7104b41d04705efd018cd6db499074736450398d",
   "in_reply_to": null,
   "text": "This seems like a step in the wrong direction. The docstring of `TicksSeconds` also says that it should be avoided in code.\n\nthe correct approach would be to write `constexpr auto ...{90min};` and then use proper chrono types below: `std::chrono::seconds secs{blockDate.secsTo(currentDate)};`"
  },
  {
   "t": "2026-07-28T18:04:47Z",
   "kind": "review",
   "who": "maflcko",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "7104b41d04705efd018cd6db499074736450398d",
   "text": "Tend to agree with stickies. The changes as-is also violate the util/time.h docs."
  },
  {
   "t": "2026-07-28T18:25:47Z",
   "kind": "comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nAlso, the pull description seems wrong. I fail to see how any of the changes here are related to ODR?"
  },
  {
   "t": "2026-07-29T06:01:08Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "7baa431d4c3e856e5a88068030723350f685bbbd"
  },
  {
   "t": "2026-07-29T06:06:06Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nThey're not, I noticed these raw duration expressions while reviewing #35792's changes, which is why I originally described this as a follow-up.\nSince that wording can read as grouping the two changes, I removed it from the PR description.\n\n[quoted text omitted]\nThanks for the suggestion, I reworked the branch so durations remain typed where the surrounding APIs already support chrono, and I like it a lot better this way.\nWhere carrying chrono farther would broaden the change or make call sites harder to read, I left the expression unchanged or converted only where an existing interface still takes integer seconds."
  },
  {
   "t": "2026-07-29T06:21:50Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/qt/modaloverlay.cpp",
   "commit": "7baa431d4c3e856e5a88068030723350f685bbbd",
   "in_reply_to": null,
   "text": "not sure what exactly this is trying to enforce or harden. A plain `300 /* ms */` seems more readable and direct than a `count_milliseconds(300ms)` or the alternative used in other parts of this pull: `300ms / 1ms`.\n\nI think the chrono types should only be used when the type adds some compile-time checks. And I don't think replacing a 300-raw-int literal with a 300-chrono-literal, and a cast to int is adding any checks."
  },
  {
   "t": "2026-07-29T06:35:33Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "178eb282e3e076357283f412eb62060bbb6414c8"
  },
  {
   "t": "2026-07-29T06:35:34Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/qt/modaloverlay.cpp",
   "commit": "7baa431d4c3e856e5a88068030723350f685bbbd",
   "in_reply_to": 3671539734,
   "text": "Sure, I\u2019ve reverted the ones that were arguably noisier than the original code. Thanks for the pushback!"
  },
  {
   "t": "2026-07-29T06:38:30Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/qt/modaloverlay.cpp",
   "commit": "7baa431d4c3e856e5a88068030723350f685bbbd",
   "in_reply_to": 3671539734,
   "text": "[quoted text omitted]\n\nI'd argue it's also valuable when we can remove comments that had to explain the values (e.g. `14 * 24 * 60 * 60; // two weeks`) or when we can use the literals in range checks, e.g. `if (delta_seconds <= 0 || delta_seconds > 3600) {`."
  },
  {
   "t": "2026-08-18T00:51:03Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "0716f3ef0411a7990d9446cf6f49f7a1ea2296c6"
  },
  {
   "t": "2026-08-18T00:51:05Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "text": "Rebased after https://github.com/bitcoin/bitcoin/pull/35852"
  }
 ],
 "labels_log": [
  {
   "t": "2026-07-27T05:07:03Z",
   "action": "labeled",
   "label": "Refactoring",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-28T01:38:39Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-28T02:51:03Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-29T06:36:02Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-29T07:43:02Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-14T17:50:14Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-18T01:08:18Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-18T06:51:41Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-18T11:28:48Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-31T16:52:45Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-07-29T06:01:59Z",
   "kind": "renamed",
   "who": "l0rinc",
   "from": "refactor: use chrono literals for durations",
   "to": "refactor: keep duration calculations typed"
  }
 ],
 "text_chars": 7677,
 "text_tokens_estimate": 1919,
 "changed_paths": [
  "src/banman.h",
  "src/chain.h",
  "src/headerssync.cpp",
  "src/kernel/chainparams.cpp",
  "src/net.cpp",
  "src/net_processing.cpp",
  "src/node/chainstate.cpp",
  "src/qt/bitcoingui.cpp",
  "src/qt/guiutil.cpp",
  "src/qt/guiutil.h",
  "src/qt/intro.cpp",
  "src/qt/modaloverlay.cpp",
  "src/qt/rpcconsole.cpp",
  "src/qt/rpcconsole.h",
  "src/qt/sendcoinsdialog.cpp",
  "src/rpc/blockchain.cpp",
  "src/rpc/node.cpp",
  "src/test/chain_tests.cpp",
  "src/test/mempool_tests.cpp",
  "src/test/pow_tests.cpp",
  "src/txmempool.cpp",
  "src/txmempool.h",
  "src/validation.cpp",
  "src/wallet/spend.cpp",
  "src/wallet/wallet.cpp",
  "test/functional/feature_assumevalid.py"
 ],
 "files": [
  {
   "path": "src/banman.h",
   "add": 2,
   "del": 1
  },
  {
   "path": "src/chain.h",
   "add": 2,
   "del": 2
  },
  {
   "path": "src/headerssync.cpp",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/kernel/chainparams.cpp",
   "add": 11,
   "del": 10
  },
  {
   "path": "src/net.cpp",
   "add": 2,
   "del": 2
  },
  {
   "path": "src/net_processing.cpp",
   "add": 9,
   "del": 9
  },
  {
   "path": "src/node/chainstate.cpp",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/qt/bitcoingui.cpp",
   "add": 3,
   "del": 2
  },
  {
   "path": "src/qt/guiutil.cpp",
   "add": 15,
   "del": 30
  },
  {
   "path": "src/qt/guiutil.h",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/qt/intro.cpp",
   "add": 3,
   "del": 2
  },
  {
   "path": "src/qt/modaloverlay.cpp",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/qt/rpcconsole.cpp",
   "add": 6,
   "del": 6
  },
  {
   "path": "src/qt/rpcconsole.h",
   "add": 3,
   "del": 1
  },
  {
   "path": "src/qt/sendcoinsdialog.cpp",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/rpc/blockchain.cpp",
   "add": 2,
   "del": 1
  },
  {
   "path": "src/rpc/node.cpp",
   "add": 4,
   "del": 4
  },
  {
   "path": "src/test/chain_tests.cpp",
   "add": 2,
   "del": 0
  },
  {
   "path": "src/test/mempool_tests.cpp",
   "add": 1,
   "del": 0
  },
  {
   "path": "src/test/pow_tests.cpp",
   "add": 10,
   "del": 6
  },
  {
   "path": "src/txmempool.cpp",
   "add": 1,
   "del": 1
  },
  {
   "path": "src/txmempool.h",
   "add": 2,
   "del": 1
  },
  {
   "path": "src/validation.cpp",
   "add": 2,
   "del": 3
  },
  {
   "path": "src/wallet/spend.cpp",
   "add": 2,
   "del": 1
  },
  {
   "path": "src/wallet/wallet.cpp",
   "add": 1,
   "del": 1
  },
  {
   "path": "test/functional/feature_assumevalid.py",
   "add": 19,
   "del": 9
  }
 ],
 "test_lines": 47,
 "git": {
  "head": "0716f3ef0411a7990d9446cf6f49f7a1ea2296c6",
  "head_matches_backup": true,
  "base": "f72537037d3350e2974efd760eaa7b04f820880c",
  "commits": [
   {
    "sha": "cfd3c93bff",
    "subject": "rpc: type mockscheduler delta",
    "files": 1,
    "add": 4,
    "del": 4
   },
   {
    "sha": "c8e8572714",
    "subject": "qt: type nice time offsets",
    "files": 5,
    "add": 21,
    "del": 35
   },
   {
    "sha": "10b4b5e4ee",
    "subject": "qt: type peer ban durations",
    "files": 2,
    "add": 9,
    "del": 7
   },
   {
    "sha": "737cd057ae",
    "subject": "qt: name blocks per day in prune estimate",
    "files": 1,
    "add": 3,
    "del": 2
   },
   {
    "sha": "57dab7d170",
    "subject": "mempool: type rolling fee halflife as duration",
    "files": 3,
    "add": 4,
    "del": 2
   },
   {
    "sha": "09d03781b2",
    "subject": "net, rpc: use typed target spacing accessor",
    "files": 2,
    "add": 4,
    "del": 3
   },
   {
    "sha": "781a15da57",
    "subject": "refactor: use chrono for fixed durations",
    "files": 4,
    "add": 7,
    "del": 5
   },
   {
    "sha": "b7388c2cad",
    "subject": "net: type block relay age limits",
    "files": 1,
    "add": 7,
    "del": 7
   },
   {
    "sha": "2bcde26618",
    "subject": "test: characterize PoW target timing",
    "files": 1,
    "add": 10,
    "del": 6
   },
   {
    "sha": "2d6378168d",
    "subject": "kernel: use chrono types for PoW timing",
    "files": 1,
    "add": 11,
    "del": 10
   },
   {
    "sha": "5b05115a92",
    "subject": "validation: type future block time limit",
    "files": 5,
    "add": 7,
    "del": 5
   },
   {
    "sha": "7afe41099c",
    "subject": "test: characterize assumevalid work threshold",
    "files": 1,
    "add": 19,
    "del": 9
   },
   {
    "sha": "0716f3ef04",
    "subject": "validation: use chrono for assumevalid horizon",
    "files": 1,
    "add": 1,
    "del": 2
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "23f5e2f84c55c4e8",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}