Skip to content

rpc: getblocktemplate mintime is one past the tip median - #1078

Merged
reardencode merged 2 commits into
reardencode:masterfrom
smolcars:rpc/gbt-mintime
Oct 19, 2026
Merged

reardencode merged 2 commits into
reardencode:masterfrom
smolcars:rpc/gbt-mintime

Conversation

@niteshbalusu11

@niteshbalusu11 niteshbalusu11 commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

getblocktemplate returned the tip's median time as mintime. A block can't carry that time (time-too-old: it must be above the median). Core returns GetMinimumTime, which is MTP + 1.

 getblocktemplate (mine.rs)
-  mintime = MTP(tip)            // genesis: its time
+  mintime = MTP(tip) + 1        // genesis: its time + 1

Core also raises mintime to prev_time - 600 at retarget heights (its BIP94-ready template policy, applied on every network). That floor isn't part of this fix.

Related, not changed here: proposal mode (check_block_proposal_with in chain.rs) deliberately accepts a block time equal to the median (< where Core uses <=; the comment there explains why). A proposal stamped at the median therefore passes while submitblock rejects it. The relaxation may date from when mintime was the median; I've left it for a separate change.

Evidence

Regtest, same chain on both nodes:

Core 31.1 master this branch
mintime (tip MTP = M) M + 1 M M + 1
submitblock of a block stamped at rbitcoin's mintime — time-too-old on both nodes accepted on both nodes
  • New test getblocktemplate_mintime_is_one_past_the_tip_median (mature tip and genesis).
    • Before: left: 1792357196, right: 1792357197.
    • After: passes.
  • fmt, clippy, ast-grep, cargo test --workspace, and ci-os-smoke.sh pass locally.

Merge Danger

Door: two-way

Blast Radius: RPC

Only the mintime field changes, in getblocktemplate and the Esplora GET /block-template JSON that shares it. curtime and the rest of the template are unchanged.

🤖 Generated with Claude Code

mintime was the tip's median time past. A block must be above that
median (time-too-old), so a template stamped at mintime was invalid.
Core's GetMinimumTime starts from MTP + 1; this fixes that lower bound.
Core's extra floor at retarget heights (previous block time minus 600)
is not part of this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@rearden-grok rearden-grok Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

getblocktemplate mintime is one past the tip median, which is the consensus floor (time > MTP, reject time-too-old). The saturating_add(1) sits on the whole if, so genesis is incremented once. At height 0 the median window is that single header, so median_time_past(0) equals genesis time. The test pins both a 101-block tip and genesis. The old assignment (the median, or genesis time) fails it.

mintime is only the JSON field. generatetoaddress and generateblock stamp generate_block_time, which is already max(now, MTP+1). Esplora GET /block-template calls the same gbt_template.

curtime stays max(tip_time+1, now). On a chain that already satisfied time > parent MTP, the tip median is at most the tip timestamp, so mintime = MTP+1 is at most tip_time+1. A clock behind the median still leaves curtime at least tip_time+1.

Core's retarget floor max(MTP+1, prev_time-600) is template policy on every network. A block below that floor is a consensus failure only on testnet4 and on regtest with -test=bip94. This node has neither. Regtest here keeps a 2016-block interval and no_pow_retargeting, so it does not retarget every block. On mainnet, testnet3, signet, and default regtest, MTP+1 is the consensus minimum. The changelog already says that floor is out of scope. The comment on the assignment names Core GetMinimumTime, which also applies prev_time - 600 at retarget heights. The changelog is the accurate description of what this change includes.

Proposal mode still accepts time == median (time < mtp in check_block_proposal_with). A stamp at the new mintime is above the median, so it passes proposal and submitblock. A stamp exactly at the median still passes proposal and fails submitblock. That split is unchanged and called out in the PR body.

The test checks the JSON number and does not submit a block stamped at mintime. It would fail on the old median, so that extra submit is optional. mintime is a u32. Saturating add leaves u32::MAX only when no greater u32 time exists, and that number stays exact in JSON.

Issue counts by severity

  • bugs: 0
  • suggestions: 1
  • nits: 1

The comment named GetMinimumTime, which also applies a retarget-height
floor. This change takes only the MTP + 1 part.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@niteshbalusu11

Copy link
Copy Markdown
Contributor Author

@rearden-grok Thanks.

  • Comment: fixed in 3f933f0. It now says Core's GetMinimumTime also raises the minimum at retarget heights and that this floor isn't applied here.
  • Submitting a block at mintime: I left the test as is. It already fails on the old median. The paths that stamp real blocks (generatetoaddress, generateblock) use generate_block_time, which is already max(now, MTP+1).

Please re-review the latest commit (3f933f0).

@reardencode
reardencode merged commit 78f6cac into reardencode:master Oct 19, 2026
18 checks passed
@niteshbalusu11
niteshbalusu11 deleted the rpc/gbt-mintime branch October 19, 2026 00:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants