diff --git a/doc/release-notes-rolling-coinbase-maturity.md b/doc/release-notes-rolling-coinbase-maturity.md new file mode 100644 index 000000000000..7f5c384cd243 --- /dev/null +++ b/doc/release-notes-rolling-coinbase-maturity.md @@ -0,0 +1,34 @@ +Consensus +--------- + +- Rolling coinbase maturity (1 year). 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 replaces the shared release of the existing long coinbase maturity + window: the rewards it holds (973440 to 979919) are no longer all released + together at block 979920. Through block 979919 the rules are unchanged. + Rewards mined before 973440 keep ordinary 100-block maturity. + + This rule is separate from RDTS. It is decided by block heights alone and + does not depend on `RdtsExpiryTime` or the BLAKE2b fork, so it stays in force + whether RDTS is extended or lapses. + + Coverage ends with issuance: rewards mined at height 6930000 (the first + zero-subsidy height, 33 halvings) or later keep the maturity the other rules + require. The end is set by the height a reward was mined at, not the height + it is spent at, so every covered reward still waits its full 52560 blocks and + nothing is released early. The last covered reward, mined at 6929999, + unlocks at 6982559. + + Upgrading nodes check every block from 979920 again using the existing + chainstate revalidation, under the marker `rolling_coinbase_maturity_1y`. + A node that upgrades before block 979920 rewinds nothing. A pruned node that + upgrades later and no longer has those blocks must reindex. + + `getdeploymentinfo` reports the rule as `rolling_coinbase_maturity`, and + `getblocktemplate` lists `rolling_coinbase_maturity` in `rules` once it is + active. Wallets and the GUI show the maturity that applies to each reward. + Testnet4, signet and regtest are unchanged; regtest can opt in with + `-testcoinbasematurityrolling=::[:]`. diff --git a/src/chainparams.cpp b/src/chainparams.cpp index e2851d88c986..aa8f9835746d 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -128,6 +128,37 @@ void ReadRegTestArgs(const ArgsManager& args, CChainParams::RegTestOptions& opti options.coinbase_maturity_long_release_height = release_height; } + if (const auto arg{args.GetArg("-testcoinbasematurityrolling")}; arg) { + const auto values{SplitString(*arg, ':')}; + if (values.size() != 3 && values.size() != 4) { + throw std::runtime_error(strprintf("Invalid format (%s) for -testcoinbasematurityrolling=::[:].", *arg)); + } + + int32_t start_height; + int32_t enforce_height; + int32_t maturity; + if (!ParseInt32(values[0], &start_height) || start_height < 0 || start_height >= std::numeric_limits::max()) { + throw std::runtime_error(strprintf("Invalid start height (%s) for -testcoinbasematurityrolling=::[:].", values[0])); + } + if (!ParseInt32(values[1], &enforce_height) || enforce_height < 2 || enforce_height >= std::numeric_limits::max()) { + throw std::runtime_error(strprintf("Invalid enforce height (%s) for -testcoinbasematurityrolling=::[:].", values[1])); + } + if (!ParseInt32(values[2], &maturity) || maturity <= COINBASE_MATURITY || maturity >= std::numeric_limits::max()) { + throw std::runtime_error(strprintf("Invalid maturity (%s) for -testcoinbasematurityrolling=::[:]: must exceed COINBASE_MATURITY.", values[2])); + } + if (values.size() == 4) { + int32_t end_coin_height; + if (!ParseInt32(values[3], &end_coin_height) || end_coin_height <= start_height || end_coin_height >= std::numeric_limits::max()) { + throw std::runtime_error(strprintf("Invalid end coin height (%s) for -testcoinbasematurityrolling=::[:]: must exceed start_height.", values[3])); + } + options.coinbase_maturity_rolling_end = end_coin_height; + } + + options.coinbase_maturity_rolling_start_height = start_height; + options.coinbase_maturity_rolling_enforce_height = enforce_height; + options.coinbase_maturity_rolling = maturity; + } + for (const std::string& strDeployment : args.GetArgs("-vbparams")) { std::vector vDeploymentParams = SplitString(strDeployment, ':'); if (vDeploymentParams.size() < 3 || 7 < vDeploymentParams.size()) { diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp index de5a866c7c4f..1993e222089a 100644 --- a/src/chainparamsbase.cpp +++ b/src/chainparamsbase.cpp @@ -19,6 +19,7 @@ void SetupChainParamsBaseOptions(ArgsManager& argsman) argsman.AddArg("-testactivationheight=name@height.", "Set the activation height of 'name' (segwit, bip34, dersig, cltv, csv, blake2b). (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-rdtsexpiry=