Skip to content

Consensus: Extended coinbase maturity temporary softfork (expires with RDTS) - #402

Closed
chrisguida wants to merge 7 commits into
bitcoinknots:29.x-knotsfrom
chrisguida:extended-coinbase-maturity-29+knots
Closed

chrisguida wants to merge 7 commits into
bitcoinknots:29.x-knotsfrom
chrisguida:extended-coinbase-maturity-29+knots

Conversation

@chrisguida

@chrisguida chrisguida commented Sep 10, 2026 •

Copy link
Copy Markdown

Temporarily extend coinbase maturity to 26280 blocks (~6 months, could be as little as 2 months depending on hashrate), to deter opportunistic mining and give honest miners an edge. Expires with RDTS. Mainnet/testnet params unset for now, activates with a flag day as soon as the community agrees.

Edit: Changed 6 months to 45 days based on community polls

@chrisguida chrisguida changed the title Extended coinbase maturity 29+knots Consensus: Extended coinbase maturity temporary softfork (expires with RDTS) Sep 10, 2026
Comment thread src/consensus/params.h
}

/** Whether the extended coinbase maturity rule applies to a block whose
* parent has the given median-time-past. It expires with RDTS, so it is

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The rule expires with RDTS and outputs revert to COINBASE_MATURITY at that point, so the effective lock is min(EXTENDED_COINBASE_MATURITY, blocks until expiry). The expiry is public in chainparams, so an opportunistic miner can mine the tail of the window and pay almost nothing, while miners present at activation absorb the full six months.

Is the truncation deliberate? Attaching the maturity to when the output was created rather than when it is spent would make the cost uniform, at the price of outliving RDTS.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

It was intentional, but it's certainly still open for discussion

Comment thread src/consensus/consensus.h Outdated
static const int COINBASE_MATURITY = 100;
/** Coinbase maturity applied by the temporary extended-maturity soft fork to coinbase outputs created
* while it is active: about six months of ten-minute blocks (see Consensus::Params::ExtendedCoinbaseMaturityActiveAt) */
static const int EXTENDED_COINBASE_MATURITY = 26280;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Duration is a block count while activation and expiry are median-time-past. 26280 is six months only at 600s, so the realized delay drifts with difficulty while the boundaries don't.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

yeah maybe this is a bad design, i'll make a new branch that uses mtp and we can have both on the table

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I made a version with MTP here.

@jasonsopko

Copy link
Copy Markdown

Tested f7b36e2 on Ubuntu 24.04, gcc 13.3, wallet on, no GUI. Unit validation_tests and feature_extended_coinbase_maturity.py pass, along with feature_rdts, both reduced-data tests, feature_reindex, mempool_reorg, mempool_spend_coinbase, mining_basic, rpc_blockchain and wallet_coinbase_category.

I also ran a case this branch's test does not cover: a peer that does not enforce the rule mines a block spending an in-window coinbase at 100 confirmations, and the enforcing node has to refuse that block, hold its own tip against the longer chain, and take the other node back once its own chain has more work. It does, the rejected block shows as invalid in getchaintips, submitpackage is not a way around the rule, and a -checklevel=4 restart reproduces the chain. Test in https://gist.github.com/jasonsopko/e9982ab8bd4d013dbdbc3196de192c2e, it applies to either branch.

On the block count, you already say it could be as little as two months depending on hashrate. On this chain that is the current case rather than the corner. Mean spacing measured from the node here, 2026-09-11 at height 970821:

window mean spacing 26280 blocks
last 144 184 s 56 days
last 1008 224 s 68 days
last 2016 167 s 51 days
last 4032 156 s 47 days
last 8064 107 s 33 days

So the rule buys somewhere between 33 and 68 days depending on which window you measure, not 182.5. The spread across those windows is the other half of it: the realized lock is not only short, it is not predictable, because the hashrate here is still moving. The script is in the same gist.

The release note is where that matters most. "Block rewards mined inside the window cannot be spent for six months" is the claim that does not hold; the first paragraph's "26280 blocks (about six months of ten-minute blocks)" is accurate and the Miners paragraph should read the same way.

nit: CTxMemPool::check() now passes std::numeric_limits<int>::max(), so -checkmempool no longer covers the new rule. That is the one automated check that would catch a miss in the reorg eviction path, which is the part of this with the most ways to go wrong.

@chrisguida

Copy link
Copy Markdown
Author

Closing: superseded by #419 (shipped in 29.4.2) and its follow-up #429.

@chrisguida chrisguida closed this Sep 30, 2026
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.

4 participants