consensus: bump COINBASE_MATURITY 10 → 240 — activate at 1,055,555 #32

Closed
opened 2026-06-15 23:03:34 +00:00 by dobbscoin · 1 comment
dobbscoin commented 2026-06-15 23:03:34 +00:00

Revised 2026-06-16: activation height changed from 1,025,000 → 1,055,555 to comply with the feature-freeze policy in #20 (no consensus merges to main between h=998,000 and h=1,050,667). The mid-OFFSIG argument has been replaced with a post-freeze + bundle-with-#6 argument. See § Why activate at 1,055,555.

Goal

Tighten coinbase-spend maturity from 10 to 240 blocks so it sits comfortably above the existing MAX_REORG_DEPTH=100 finality guard. At 60s blocks: 10 minutes → 4 hours.

Flagged by @9019x on 2026-06-14 with a 💀 — credited as one of the headline structural issues in the codebase. (His original priority list also includes BIP66, BIP65, BIP32/39/44 HD wallets, Electrum-server, libnatpmp migration — separate issues.)

Problem

At 60s block target × COINBASE_MATURITY=10, a coinbase becomes spendable after 10 minutes. MAX_REORG_DEPTH=100 (src/pow.h:31, enforced in src/main.cpp:2522) allows legal reorgs up to 99 blocks deep. The two are mis-tuned relative to each other: an attacker who triggers a legal 90-block reorg can invalidate a coinbase that has already matured, been spent, and seen its descendant tx confirm 50+ blocks deep. The cascade invalidates every downstream tx that touched the coinbase output.

For coinbase maturity to be a real invariant — "if you can spend it, the chain has finalized the underlying coinbase" — maturity must exceed MAX_REORG_DEPTH. 240 is a 140-block buffer past the reorg ceiling.

Comparator chains: Bitcoin 100 / Litecoin 100 / Dogecoin 30 (60s blocks). OFF at 10 is the structural outlier.

Constants

Constant Value Rationale
COINBASE_MATURITY_LEGACY 10 Current value. Retained for pre-fork spends.
COINBASE_MATURITY_HARDENED 240 4h at 60s. Floor recommended by @9019x. 240 - 100 = 140-block buffer past MAX_REORG_DEPTH.
HARDFORK_COINBASE_MAT_MAIN_OFF 1,055,555 4,889 blocks past freeze-end at h=1,050,667 (~3.4 days). Bundled with #6 rolling-checkpoints at the same height. ~54,000 blocks past current tip → ~37 days from this revision.
HARDFORK_COINBASE_MAT_TESTNET_OFF 100 Trivial; mirrors LWMA-3 testnet activation.

Why activate at 1,055,555 (post-freeze, bundled with #6)

Original draft placed activation at h=1,025,000 inside the OFFSIG window, on the "Conclave-only mining → zero split risk" rationale. Revised 2026-06-16 to honor the feature-freeze policy in #20, which prohibits merging consensus changes to main between h=998,000 and h=1,050,667. Activation moves to the post-freeze slot.

Why 1,055,555 specifically:

  • Bundles with #6 (rolling checkpoints) at the same height — one upgrade cycle, one BCT post, one Conclave deploy.
  • 4,889 blocks (~3.4 days at 60s) past freeze-end at h=1,050,667. Announce at freeze-end with a 3-day countdown; permissionless miners returning at 1,050,667 have lead time to pull the new binary before the rule trips.
  • Repeats the 5s numerology already used by #6.

Trade vs the original mid-OFFSIG activation: outsider miners can in principle produce blocks between h=1,050,667 and h=1,055,555 under the old rule. The freeze-end upgrade-coordination announcement closes that gap. A 3.4-day lead time is short but standard for post-freeze coordination on a small chain.

Files touched

  • src/main.h — replace COINBASE_MATURITY constant with COINBASE_MATURITY_LEGACY + COINBASE_MATURITY_HARDENED.
  • src/pow.h — add HARDFORK_COINBASE_MAT_MAIN_OFF / HARDFORK_COINBASE_MAT_TESTNET_OFF constants next to the LWMA-3 fork heights.
  • src/main.cpp — new helper int GetCoinbaseMaturity(int nHeight); swap consensus site at CheckInputs() line 1888 (nSpendHeight - coins.nHeight < GetCoinbaseMaturity(nSpendHeight)); swap wallet site at CMerkleTx::GetBlocksToMaturity() line 1022.
  • src/checkpoints.cpp — sync-checkpoint distance at line 500 calls GetCoinbaseMaturity(chainActive.Tip()->nHeight).
  • src/qt/transactiondesc.cpp — GUI string at line 251 calls GetCoinbaseMaturity(chainActive.Height()) + 1 (existing %1 interpolation auto-updates).

Approx 30 LoC total.

Semantics

The new rule applies to any spend at nSpendHeight ≥ HARDFORK_COINBASE_MAT_MAIN_OFF, regardless of when the coinbase was mined. A holder with a 50-block-old coinbase from h=1,055,505 attempting to spend at h=1,055,556 has the tx rejected until h=1,055,745 (the coinbase reaches its new 240-block maturity). Mild brief inconvenience; no permanent loss.

Pre-fork: behavior unchanged (10-block maturity).

Test plan

  1. Regtest: extend qa/rpc-tests/ with a scenario that mines past HARDFORK_COINBASE_MAT_TESTNET_OFF=100, attempts to spend a 10-block-old coinbase, expects mempool reject bad-txns-premature-spend-of-coinbase.
  2. Testnet activation: per @9019x's process note ("run consensus changes on testnet first"), cut a v2.0.x-rc-cbmat tag with testnet activation at h=100. Mine across the fork height, confirm legacy maturity pre-fork and hardened maturity post-fork on the same node.
  3. Boundary case: coinbase mined at fork-50, spend attempted at fork+1 → reject; spend at fork+190 → accept.
  4. Wallet UX: confirm transactiondesc.cpp displays "240 blocks" post-fork in the GUI.

Risks / mitigations

Risk Mitigation
Pool payout disruption (coinbase matures 4h instead of 10min) Pool operators notified at announcement. Pool tx batching absorbs 4h cadence trivially.
User confusion at fork boundary (in-flight tx rejected) Activation announced at freeze-end (h=1,050,667) with a ~3.4-day countdown to activation. GUI string auto-updates post-fork.
Old binary mining a "spends 50-block coinbase" tx between freeze-end and activation Rule only trips at h=1,055,555. ~3.4-day announce-and-countdown at freeze-end (h=1,050,667) gives returning permissionless miners lead time to pull the new binary.
Bundle with rolling-checkpoints (#6) at h=1,055,555 Same activation height; one upgrade cycle. Independent rule sets (tx-level vs chain-level), distinct code paths.
Interaction with Codex inscription None. Coinbase output amounts/recipients unaffected.

Process

Bundle into v2.0.x-rc-bipsoft with #33 and #34. All three rules share HARDFORK_*_MAIN_OFF = 1,055,555. Cut tag with testnet activation at h=100; live-test on testnet for ~3 days; promote to mainnet activation tag once green. Branch development on feat/v2.0.x-rc-bipsoft; merge to main after freeze-end (post h=1,050,667).

Community chat for live discussion: https://23skidoo.info/discord

Activation milestone (to add to WHERE_WE_LEFT_OFF.md)

coinbase maturity 10 → 240: h=1,055,555 (bundled in v2.0.x-rc-bipsoft)

References

  • src/main.h:60 — current COINBASE_MATURITY = 10
  • src/main.cpp:1888 — consensus enforcement (CheckInputs)
  • src/pow.h:31 — MAX_REORG_DEPTH = 100
  • src/main.cpp:2504-2532 — MAX_REORG_DEPTH enforcement in ActivateBestChain (v2.0.0-rc3, gated on LWMA-3)
  • src/chainparams.cpp:183 — nOpenMiningHeight = 1050666 (OFFSIG window end)
  • Issue #6 — rolling checkpoints at 1,055,555 (bundled at same height)
  • Issue #20 — feature-freeze policy h=998,000 → h=1,050,667 (the constraint that forced this revision)
  • Issue #38 — public OFF testnet (live-test prerequisite)
> **Revised 2026-06-16:** activation height changed from 1,025,000 → 1,055,555 to comply with the feature-freeze policy in #20 (no consensus merges to `main` between h=998,000 and h=1,050,667). The mid-OFFSIG argument has been replaced with a post-freeze + bundle-with-#6 argument. See § Why activate at 1,055,555. ## Goal Tighten coinbase-spend maturity from 10 to 240 blocks so it sits comfortably above the existing `MAX_REORG_DEPTH=100` finality guard. At 60s blocks: 10 minutes → 4 hours. Flagged by @9019x on 2026-06-14 with a 💀 — credited as one of the headline structural issues in the codebase. (His original priority list also includes BIP66, BIP65, BIP32/39/44 HD wallets, Electrum-server, libnatpmp migration — separate issues.) ## Problem At 60s block target × `COINBASE_MATURITY=10`, a coinbase becomes spendable after 10 minutes. `MAX_REORG_DEPTH=100` (`src/pow.h:31`, enforced in `src/main.cpp:2522`) allows legal reorgs up to 99 blocks deep. The two are mis-tuned relative to each other: an attacker who triggers a legal 90-block reorg can invalidate a coinbase that has already matured, been spent, and seen its descendant tx confirm 50+ blocks deep. The cascade invalidates every downstream tx that touched the coinbase output. For coinbase maturity to be a real invariant — "if you can spend it, the chain has finalized the underlying coinbase" — **maturity must exceed `MAX_REORG_DEPTH`**. 240 is a 140-block buffer past the reorg ceiling. Comparator chains: Bitcoin 100 / Litecoin 100 / Dogecoin 30 (60s blocks). OFF at 10 is the structural outlier. ## Constants | Constant | Value | Rationale | |---|---|---| | `COINBASE_MATURITY_LEGACY` | 10 | Current value. Retained for pre-fork spends. | | `COINBASE_MATURITY_HARDENED` | **240** | 4h at 60s. Floor recommended by @9019x. 240 - 100 = 140-block buffer past `MAX_REORG_DEPTH`. | | `HARDFORK_COINBASE_MAT_MAIN_OFF` | **1,055,555** | 4,889 blocks past freeze-end at h=1,050,667 (~3.4 days). Bundled with #6 rolling-checkpoints at the same height. ~54,000 blocks past current tip → ~37 days from this revision. | | `HARDFORK_COINBASE_MAT_TESTNET_OFF` | 100 | Trivial; mirrors LWMA-3 testnet activation. | ## Why activate at 1,055,555 (post-freeze, bundled with #6) **Original draft** placed activation at h=1,025,000 inside the OFFSIG window, on the "Conclave-only mining → zero split risk" rationale. **Revised 2026-06-16** to honor the feature-freeze policy in #20, which prohibits merging consensus changes to `main` between h=998,000 and h=1,050,667. Activation moves to the post-freeze slot. Why 1,055,555 specifically: - Bundles with #6 (rolling checkpoints) at the same height — one upgrade cycle, one BCT post, one Conclave deploy. - 4,889 blocks (~3.4 days at 60s) past freeze-end at h=1,050,667. Announce at freeze-end with a 3-day countdown; permissionless miners returning at 1,050,667 have lead time to pull the new binary before the rule trips. - Repeats the 5s numerology already used by #6. Trade vs the original mid-OFFSIG activation: outsider miners can in principle produce blocks between h=1,050,667 and h=1,055,555 under the old rule. The freeze-end upgrade-coordination announcement closes that gap. A 3.4-day lead time is short but standard for post-freeze coordination on a small chain. ## Files touched - `src/main.h` — replace `COINBASE_MATURITY` constant with `COINBASE_MATURITY_LEGACY` + `COINBASE_MATURITY_HARDENED`. - `src/pow.h` — add `HARDFORK_COINBASE_MAT_MAIN_OFF` / `HARDFORK_COINBASE_MAT_TESTNET_OFF` constants next to the LWMA-3 fork heights. - `src/main.cpp` — new helper `int GetCoinbaseMaturity(int nHeight)`; swap consensus site at `CheckInputs()` line 1888 (`nSpendHeight - coins.nHeight < GetCoinbaseMaturity(nSpendHeight)`); swap wallet site at `CMerkleTx::GetBlocksToMaturity()` line 1022. - `src/checkpoints.cpp` — sync-checkpoint distance at line 500 calls `GetCoinbaseMaturity(chainActive.Tip()->nHeight)`. - `src/qt/transactiondesc.cpp` — GUI string at line 251 calls `GetCoinbaseMaturity(chainActive.Height()) + 1` (existing `%1` interpolation auto-updates). Approx 30 LoC total. ## Semantics The new rule applies to any **spend at** `nSpendHeight ≥ HARDFORK_COINBASE_MAT_MAIN_OFF`, regardless of when the coinbase was mined. A holder with a 50-block-old coinbase from h=1,055,505 attempting to spend at h=1,055,556 has the tx rejected until h=1,055,745 (the coinbase reaches its new 240-block maturity). Mild brief inconvenience; no permanent loss. Pre-fork: behavior unchanged (10-block maturity). ## Test plan 1. **Regtest**: extend `qa/rpc-tests/` with a scenario that mines past `HARDFORK_COINBASE_MAT_TESTNET_OFF=100`, attempts to spend a 10-block-old coinbase, expects mempool reject `bad-txns-premature-spend-of-coinbase`. 2. **Testnet activation**: per @9019x's process note ("run consensus changes on testnet first"), cut a `v2.0.x-rc-cbmat` tag with testnet activation at h=100. Mine across the fork height, confirm legacy maturity pre-fork and hardened maturity post-fork on the same node. 3. **Boundary case**: coinbase mined at fork-50, spend attempted at fork+1 → reject; spend at fork+190 → accept. 4. **Wallet UX**: confirm `transactiondesc.cpp` displays "240 blocks" post-fork in the GUI. ## Risks / mitigations | Risk | Mitigation | |---|---| | Pool payout disruption (coinbase matures 4h instead of 10min) | Pool operators notified at announcement. Pool tx batching absorbs 4h cadence trivially. | | User confusion at fork boundary (in-flight tx rejected) | Activation announced at freeze-end (h=1,050,667) with a ~3.4-day countdown to activation. GUI string auto-updates post-fork. | | Old binary mining a "spends 50-block coinbase" tx between freeze-end and activation | Rule only trips at h=1,055,555. ~3.4-day announce-and-countdown at freeze-end (h=1,050,667) gives returning permissionless miners lead time to pull the new binary. | | Bundle with rolling-checkpoints (#6) at h=1,055,555 | Same activation height; one upgrade cycle. Independent rule sets (tx-level vs chain-level), distinct code paths. | | Interaction with Codex inscription | None. Coinbase output amounts/recipients unaffected. | ## Process Bundle into `v2.0.x-rc-bipsoft` with #33 and #34. All three rules share `HARDFORK_*_MAIN_OFF = 1,055,555`. Cut tag with testnet activation at h=100; live-test on testnet for ~3 days; promote to mainnet activation tag once green. Branch development on `feat/v2.0.x-rc-bipsoft`; merge to `main` after freeze-end (post h=1,050,667). Community chat for live discussion: https://23skidoo.info/discord ## Activation milestone (to add to WHERE_WE_LEFT_OFF.md) `coinbase maturity 10 → 240: h=1,055,555 (bundled in v2.0.x-rc-bipsoft)` ## References - `src/main.h:60` — current `COINBASE_MATURITY = 10` - `src/main.cpp:1888` — consensus enforcement (`CheckInputs`) - `src/pow.h:31` — `MAX_REORG_DEPTH = 100` - `src/main.cpp:2504-2532` — `MAX_REORG_DEPTH` enforcement in `ActivateBestChain` (v2.0.0-rc3, gated on LWMA-3) - `src/chainparams.cpp:183` — `nOpenMiningHeight = 1050666` (OFFSIG window end) - Issue #6 — rolling checkpoints at 1,055,555 (bundled at same height) - Issue #20 — feature-freeze policy h=998,000 → h=1,050,667 (the constraint that forced this revision) - Issue #38 — public OFF testnet (live-test prerequisite)
dobbscoin commented 2026-07-27 21:50:34 +00:00

Live on mainnet. Activated on schedule at h=1,055,555 (2026-07-23), block 00000000438cf73291060c7c2caeadd568b7e432c192f4f44b3d84eafb157c7c.

Coinbase maturity is now 240 blocks (~4 hours at target spacing) — newly mined OFF stays locked well past any permitted reorg (MAX_REORG_DEPTH=100), closing the counterfeit-spend mechanics of 2018. ~5,800 blocks mined under the rule since activation with zero incident; the mixed-version soak (v2.0.8.7 / v2.0.9 / v2.1.0-rc) remains fork-free at delta 0.

Shipped in v2.0.9-Eldersign. The wards hold. Closing.

Live on mainnet. Activated on schedule at h=1,055,555 (2026-07-23), block `00000000438cf73291060c7c2caeadd568b7e432c192f4f44b3d84eafb157c7c`. Coinbase maturity is now 240 blocks (~4 hours at target spacing) — newly mined OFF stays locked well past any permitted reorg (`MAX_REORG_DEPTH=100`), closing the counterfeit-spend mechanics of 2018. ~5,800 blocks mined under the rule since activation with zero incident; the mixed-version soak (v2.0.8.7 / v2.0.9 / v2.1.0-rc) remains fork-free at delta 0. Shipped in v2.0.9-Eldersign. The wards hold. Closing.
dobbscoin closed this issue 2026-07-27 21:50:35 +00:00
Sign in to join this conversation.
No labels
enhancement
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
SubGeniusFinance/Offerings-to-Cthulhu#32
No description provided.