Repository navigation
Softfork: 1-year rolling coinbase maturity, no release cliff, independent of RDTS - #476
satoshipuzzles wants to merge 5 commits into
Conversation
…dent of RDTS On mainnet, from block 979920, every generated coin mined at height 973440 or later must be buried 52560 blocks (365 days of 144 blocks) before it can be spent. Each reward counts from its own block, so exactly one reward unlocks per block, the first at 1026000. This takes over from the long coinbase maturity window (bitcoinknots#419) at its release height, so the rewards it holds are not all released together at 979920. Through 979919 the rules are unchanged; rewards mined before 973440 keep ordinary maturity. The rule is decided by heights alone, has no expiry and does not depend on RdtsExpiryTime or the BLAKE2b fork. Mempool acceptance and reorg cleanup enforce it for the next block. The wallet and GUI ask for the maturity at each coin's height. getdeploymentinfo reports rolling_coinbase_maturity and getblocktemplate lists it in rules once active. Upgrading nodes check every block from 979920 again under the rolling_coinbase_maturity_1y revalidation marker. Regtest can opt in with -testcoinbasematurityrolling=<start>:<enforce>:<maturity>. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6434ff1 to
808a1c3
Compare
|
Concept NACK. This PR applies a new 52,560-block maturity to rewards that have already been mined under the existing 6,480-block rule (heights 973440–979918). Those coinbases were accepted under one set of consensus rules. Changing the number of blocks that must pass before they may be spent is a retroactive alteration of the terms that applied when the blocks were produced. A forward-looking maturity extension that only affects rewards mined after a defined activation height is a checkable consensus rule. Rewriting the maturity of already-accepted outputs is not. Nodes cannot derive the new longer lock from the blocks alone; they must apply a rule that did not exist when those blocks were mined. That reintroduces a trusted third party to determine the spendability of outputs the network has already accepted, which is the mechanism the protocol was designed to remove. I support temporary, height-based maturity extensions that apply only to future rewards. I do not support re-locking coinbases that have already been mined. |
|
Concept ACK. This seems good to me. Many plebs would like this, based on polling data. |
|
nack |
|
Tested ACK 808a1c3
I prefer a one-year rolling maturity with no release cliff and no dependence on RDTS. |
|
Concept ACK on the depth, the permanence, and the RDTS independence — this is the design I announced I was preparing in my #468 comment, and I'm glad to see it on the table. I have a working branch implementing it (consensus + policy + tests green on top of rc2), plus an on-chain evidence package measured on my unpruned node, and I'd offer two amendments that directly address the objections already raised in this thread: 1. Start coverage at 979919, not 973440. @SeventyEi8ht's NACK is about re-encumbering rewards mined under shipped rules, and it will keep costing this proposal ACKs. Dropping retroactivity loses less than it seems: relay policy already holds the 973440–979918 window to 6,480 depth regardless, so the "6,400 rewards in one block" release at 979920 is blunted in practice — only direct miner inclusion bypasses it. 2. Don't race the 979920 flag day. rc2 (#468) is already deployed on live nodes. Activating instead at any height below 1,005,839 (= 979919 + 25920) is provably clean: no covered reward will ever have been spendable under the 180-day rule, so nothing ever flips from spendable back to locked — the objection that sank re-locking proposals before simply cannot arise. That removes the 8-day review crunch entirely; my branch carries a unit test asserting this property. Evidence (full package with figures and reproducible scripts available): measured on this chain, 34.3% of pre-CBM coinbase value was spent within 110 blocks of mining — within 10 blocks of the earliest legal moment. That fast-flip is what any expiring deterrent reverts to. And the covered-era freeze is fully binding: 15,452 XBT across 421,674 outputs, zero value moved. Happy to share the branch and coordinate rather than open a competing PR — the outcome matters more than the authorship. |
|
I like the idea, I would prefer a shorter permanent though. I do think that we need a permanent baseline maturation length, I think a permanent should be on the low side though and a temporary should be higher than permanent. |
The cliff is why I prefer a single rolling period: when a temporary higher maturity ends, everything it held back becomes spendable at once. A permanent rolling period releases coins one block at a time. If the permanent length is reduced later, it can be done the same way, as a rolling change with no cliff. |
|
Thanks for the review, and @Kilombino for the tested ACK. @SeventyEi8ht: the window rewards were not mined under a promise of 45 days. The 29.4.2 release notes said maturity would be extended "up to 350 days" and put miners "on notice" that they "may need to wait much longer". The 45 days was the first step, announced as such. This PR treats every reward from 973440 on the same way, one year from its own block, instead of giving the earliest rewards a shorter lock than everyone after them. @MarcanoFilms: thanks, and I'd welcome working together. On amendment 1, starting at 979919 would give rewards mined in the window a 45-day lock and everyone after them a year, so I'd rather keep one rule for all of them. You're right that default relay policy already holds each reward to 6,480 confirmations, so the 979920 release is only reachable through direct miner inclusion. I'll correct the description. That still leaves the miners who produced those rewards able to spend them in their own blocks, which this closes. On amendment 2, because this covers the window it has to ship before 979920. If it misses that, I'll move the start height forward rather than re-lock anything that has already become spendable. @FlyTheElephant1: as @Kilombino said, a later reduction can also be done on a rolling basis, without a cliff. |
|
Concept ACK. This is by far the most consequential PR on the table because it stays true to the path previously agreed upon by the community. While the transition from 45 days to 1 year in #419 was technically fixed, the vast majority of plebs undoubtedly understood it as a rolling mechanism. PR #476 finally aligns the project’s direction with the spirit of that original community plan. It sends the correct signal, honors our most devoted plebs, and purges the influence of adversaries trying to weaken the baseline. |
|
If the permanent needs to be reduced its a hardfork @satoshipuzzles a later reduction cannot be done on a rolling basis if the rule is permanent without doing a hardfork, which is why i think we need a low baseline permanent, I'd go slightly lower than the lowest number that the average person can rationalize for the permanent. maybe 45 or 60 days. I am in support of a higher maturity length temporary, but I think even if we have one of those we need a lower permanent one as well. |
|
Thanks — glad to work together, and the description correction sounds right. On uniformity: I agree one rule for every reward from 973440 is the cleanest form, and your reading of the 29.4.2 notice is fair. My concern is practical: rc3 just replaced #468 with #429 in the release spec, so the 979920 slot is spoken for and the pre-flag-day path for this PR is effectively closed. That makes the fallback you described — moving the start height forward rather than re-locking anything — the version that will actually ship. I'd suggest we prepare it now rather than the day after the flag day. Concretely, I have a working branch (two commits on top of rc2) that implements exactly that fallback shape, and I'd be glad to hand it over or send patches so it lands under this PR:
One property worth noting for whichever rule wins at 979920: this fallback composes cleanly on top of either. Stacked over #429's freeze it even absorbs the expiry cliff for covered rewards — every reward mined from activation onward reaches its 52,560th block after the RDTS expiry, so release stays one-reward-per-block instead of everything at once. The on-chain evidence package (measured spend-depth distribution, the 973440+ freeze verification, release-profile comparisons) is ready for the PR body as well. If you're open to it, tell me where to send the branch — or I can open it against your fork so the commits land under this PR. |
|
@FlyTheElephant1 good catch, and you're right. I put it badly. Lowering a permanent maturity later would be a hard fork, rolling or not: nodes still enforcing 52,560 would reject blocks that spend a reward sooner. Phasing a reduction in avoids the release cliff, not the fork. @Kilombino's point about the cliff stands; the fork point is yours. For anyone skimming: this PR itself is a soft fork. It only adds a restriction, so non-upgraded nodes accept every block upgraded nodes produce. The hard-fork question only comes up if someone later wants to loosen it. So the real choice is which direction should be easy:
I chose the first on purpose. The lock exists to make attacks on the chain's incentives expensive, and a deterrent with an end date is one an attacker can simply wait out. Every expiry also becomes another fight over extending it. A baseline that protects the chain should be hard to weaken, and lowering it should take the broad agreement a hard fork requires. If reviewers prefer your shape, it can be done without bringing the cliff back: end the temporary layer by the height a reward was mined at, not the height it's spent at. Rewards mined before the end height keep their full year from their own block; later rewards get the baseline. Nothing already locked is released early. I'd consider that as an amendment if there's support for it, but I'd rather not change the design days before 979920 without it. |
|
Thanks, and yes, let's work together. Small correction on timing: rc4 (16:55 UTC today) went back to #468, so the 979920 slot is still in flux. Your bigger point stands, though. Neither candidate carries this PR, so the fallback is the version most likely to ship, and it makes sense to have it ready now. Please open your branch as a PR against satoshipuzzles/bitcoin (base Ending coverage at the first zero-subsidy height (6,930,000) is fine with me: it's effectively permanent and ends by mined height, so there's no cliff. And yes, please put the evidence package in; measured data will help reviewers more than anything I can argue. |
…ight The rolling rule exists to protect subsidy-bearing rewards, so there is nothing left for it to cover once issuance ends. Bound its coverage by a new CoinbaseMaturityRollingEndCoinHeight: generated coins mined at or above it keep the maturity the earlier rules require, while every coin below it stays covered for its full rolling depth, so no covered reward is ever released early. On mainnet the bound is 6930000, the first zero-subsidy height (33 halvings), so the rule retires together with issuance instead of regulating fee-only coinbases forever. -testcoinbasematurityrolling gains an optional fourth field to schedule the bound on regtest, and getdeploymentinfo reports the last covered coin height as coinbase_end_height when the coverage ends.
Pin down the property that makes a later rolling enforce height safe to ship: as long as the enforce height does not exceed the covered start height plus the maturity the earlier rules already require, no covered reward was ever spendable before enforcement, so the rule upgrade only extends pending maturities and cannot re-lock a coin a node might have seen spent. The synthetic schedule mirrors the mainnet situation of an interim rule (a bitcoinknots#419-style long window here) holding every covered reward past the enforce height, and the test comment maps the bound to the candidate interim rules for mainnet.
Exercise the end coin height and the enforce height end to end on regtest with the four-field -testcoinbasematurityrolling schedule: before enforcement a covered reward spends at the historical 100 in the mempool and in a block (policy follows consensus, there is no early hold); from enforcement a covered reward is rejected by both below the full depth, with the exact boundary checked via directly submitted blocks (depth-1 fails, depth passes); rewards mined before the start height or at the end coin height keep the historical 100 even while the rule is enforced; the schedule and its reported coinbase_end_height survive a restart.
The wallet and GUI maturity helper still covered every reward from the start height on, so rewards mined at or after the end coin height would show (and be held at) 52560 blocks while consensus and relay accept them at the earlier rules. Also describe the end bound in the release note. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
tested ACK 83a1f68 Re-tested after the end-bound commits (5f5affa..83a1f68):
Ending coverage by the height a reward was mined at keeps the property I cared about: no covered reward is released early, and there is still no cliff. |
Mutation testing of the stacked maturity ruleRan a jasonsopko-style pass over the two sites that enforce this rule — 7 killed immediately. The 2 survivors were the same defect in both sites: On the mainnet schedule the long window releases exactly where rolling begins A test that locks it at both sites (
|
|
Concept ACK. |
|
Concept ACK. I believe this permanently establishes proper miner incentives while addressing the "cliff" concerns. |
|
Following up on the collaboration here with a technical case for this design over the other rolling candidates, grounded in the diff and measured on my own unpruned node. For context on where I'm coming from: I mine this chain independently — my own Knots node and DATUM gateway, no pool — so whatever depth ships sits on my own paycheck. 1. This is the only rolling proposal that also removes the 979920 releaseThe whole reason the thread converged on rolling was to avoid releasing a year of rewards in a single block. #468 does that going forward, but it leaves #419's window (heights 973440–979919, 6,480 rewards = exactly 20,250 XBT in subsidy, plus fees) releasing at 979920 under consensus. Measured on my own node at the current tip (978824), the 5,385 of those blocks already mined carry 16,828.125 XBT subsidy plus 12.80 XBT in fees — fees run about 0.0024 XBT/block here, so the whole window lands near 20,265 XBT. Of those, the rewards at 100+ confirmations when #419's rule lapses — heights 973440–979820, 6,381 rewards = 19,940.625 XBT in subsidy plus fees — become spendable in block 979920 itself under consensus; the last 99 follow at their own height + 100. Default relay policy still holds each to 6,480 confirmations, so in practice only a miner including the spends directly in its block can take that release — but it is valid under consensus, and that is exactly the "all at once" event the rolling shape exists to prevent, sitting at the front door. This PR takes over at 979920 and puts those same rewards on the per-reward schedule, so nothing is released there. #468 can only match that with an add-on (#477/#478/#479) that has to merge on top of it; here it's the base behavior, covered by the clean-activation test that proves an enforce height no later than start + the earlier rule's depth never re-locks a reward that was already spendable. 2. Height-only, so there is no second discontinuity and no coupling to RDTS#468 binds maturity to This rule reads block height alone — not 3. The "rolling encumbers more coins/day" argument points at the longer windowThe strongest case made for 180 days was that a rolling window encumbers more coins per day than an expiring one-year rule that releases everything at once — true, but that comparison is against #429, not against another rolling window. At steady state a rolling rule holds exactly its depth in flight (one unlock per block), so this PR keeps 52,560 coinbases encumbered at any instant versus 25,920 — twice the standing deterrent — while still being rolling and still having no cliff. On the metric used to argue for rolling, the longer window wins. 4. On the retroactivity objection (@SeventyEi8ht)I think this is the one concern that deserves a concrete answer rather than a rebuttal. Two points:
Test statusBuilt green on top of rc2 (consensus + policy + tests). I contributed the end-bound coverage, the clean-activation test, and |
What
On mainnet, from block 979920, every reward mined at height 973440 or later must be buried 52,560 blocks (365 days × 144) before it can be spent. Each reward is measured from its own block, so exactly one reward unlocks per block, a year after it was mined. Coverage ends with issuance: rewards mined at 6,930,000 (the first zero-subsidy height, about 2140) or later keep ordinary maturity.
No cliff
#419 holds the rewards mined from 973440 to 979919 and then releases them all together at block 979920, which is about 6,400 rewards in one block. Default relay policy already holds each coinbase to 6,480 confirmations, so in practice that release is only reachable by miners including the spends directly in their own blocks. This PR takes over at that block and makes the per-reward schedule a consensus rule, so that shared release never happens:
The first reward unlocks at 1,026,000, and one more unlocks at every block after that. The end is set by the height a reward was mined at, not the height it is spent at, so there is no end-date release either: the last covered reward (mined at 6,929,999) still waits its full year and unlocks on its own at 6,982,559. Proposals tied to RDTS release rewards early as expiry approaches; this one doesn't.
Independent of RDTS
The rule is decided by block heights alone. It does not read
RdtsExpiryTime, the BLAKE2b fork height, or any median time past. Whether RDTS is extended, replaced or lapses in September 2027, coinbase maturity stays a year. Any later change to it is its own decision.How this differs from the open proposals
As of October 23. #477, #478 and #479 are add-ons to #468 and need it merged first.
Consensus rule
spend_height >= 979920, a coinbase output with973440 <= coin.nHeight < 6930000needsspend_height - coin.nHeight >= 52560.std::max), so it never shortens a maturity another rule requires.Relay, wallet, GUI, RPC
interfaces::Chain::coinbaseMaturity()andinterfaces::Node::coinbaseMaturity()now take the coin's height. Covered rewards show 52,560, and older rewards keep their current maturity.getdeploymentinfohas a newrolling_coinbase_maturityentry of typeflagday. Itsheightis 979920, itscoinbase_start_heightis 973440 and itscoinbase_end_height(last covered reward) is 6929999, along withmaturityandactive. It has noheight_endorexpiry_time.getblocktemplatelistsrolling_coinbase_maturityinrulesonce the rule is active.Upgrade
A new chainstate revalidation deployment,
rolling_coinbase_maturity_1y, runs from 979920 with no stop height. A node that upgrades before 979920 rewinds nothing. One that upgrades later checks every block from 979920 again, and a pruned node missing that history must reindex.Other networks
Testnet4, signet and regtest are unchanged. Regtest can opt in with
-testcoinbasematurityrolling=<start_height>:<enforce_height>:<maturity>[:<end_coin_height>].Tests
Unit tests (
coinbase_maturity_tests):CheckTxInputsboundaries (one block short fails, exact depth passes)CheckTxInputsFunctional test (
mempool_rolling_coinbase_maturity.py): uses a scaled long window and rolling rule. It runs on three nodes: without RDTS, with RDTS active, and with RDTS already expired. On each node it checks that:With the rolling rule removed, this test fails at the cliff check.
Functional test (
feature_rolling_maturity_end_bound.py): rewards before enforcement spend at 100, covered rewards are refused from enforcement until exactly their full depth (one block short fails, by block submission), rewards past the end height and before the start height keep 100, the rule survives a restart, andgetdeploymentinforeports the end. With the end bound removed, this test fails.The end bound, clean-activation test and end-bound functional test are by @MarcanoFilms.
Local results on macOS (Release, no GUI): the
coinbase_maturity_tests,txvalidation_tests,mempool_tests,wallet_testsandvalidation_chainstatemanager_testsunit suites pass. These functional tests also pass:mempool_rolling_coinbase_maturity.py,feature_rolling_maturity_end_bound.py,mempool_long_coinbase_maturity.py,mempool_spend_coinbase.py,wallet_coinbase_category.py --descriptors,feature_chainstate_revalidation.py,feature_rdts.py,feature_rdts_migration.py,rpc_blockchain.py(v1 and v2 transport) andmining_basic.py. The GUI change, one line intransactiondesc.cpp, was not compiled locally.Notes for reviewers
What happens to the rewards in #419's 45-day window (mined at 973440–979919)
That is 6,480 rewards, about 20,000 XBT in subsidy plus fees.
Under #419 today:
Under this PR:
From 1026000 on, one reward (about 3.125 XBT plus fees) unlocks per block, with no gap between the window's rewards and later ones.
Trade-off: Rewards in the window were mined while a 45-day hold was in force, and this PR extends them to a year. The earliest wait a year instead of 45 days; the last wait a year instead of about 100 blocks. #467 and #469 also extend these same rewards. The alternative is to cover only rewards mined from 979920 on. That leaves these rewards at #419's terms, but keeps the release of about 6,380 rewards in block 979920, which is the cliff this PR exists to remove.
🤖 Generated with Claude Code