diff --git a/doc/release-notes-coinbase-rolling.md b/doc/release-notes-coinbase-rolling.md new file mode 100644 index 000000000000..c6c7d0869371 --- /dev/null +++ b/doc/release-notes-coinbase-rolling.md @@ -0,0 +1,32 @@ +Rolling coinbase maturity until RDTS expiry +------------------------------------------ + +On mainnet, rewards mined from height 973440 require a rolling 6480-block +maturity while the spending block's parent median time past is below +`RdtsExpiryTime` (2027-09-01 00:00 UTC). Every reward matures individually; +there is no hold until expiry. At expiry, consensus maturity returns to +100 for all rewards, including rewards mined immediately before expiry. +Until expiry, the rule matches #419 before height 979920 and only restricts +#419's valid-block set from 979920 onward. Testnet4 keeps its original +height-based window; other public networks and default regtest are unchanged. + +The existing revalidation marker extends beyond #419's height window, bounded +by parent MTP. A #419 node upgrading after 979920 rewinds to 979919, even +when its tip is already past expiry, to recheck pre-expiry history and cached +descendants. Without a marker, revalidation begins at enforcement minus one. +Pruned nodes missing required history refuse startup before disconnecting +blocks and must reindex. Nodes that ran different experimental rules sharing +the marker must also reindex. Assumeutxo background validation must complete +pre-snapshot coverage. + +Mempool admission, reorg filtering, wallet credit, coin selection and GUI +maturity retain #419 behavior: every coinbase needs the configured long +depth for policy and wallet availability, including after consensus expiry. +Policy therefore remains stricter than consensus after expiry. Wallet credit +retains its one-confirmation margin. RPC category help describes applicable +maturity; deployment reporting shows the configured long depth, parent-MTP +expiry and next-block active state. GBT stops advertising the rule at expiry. + +Configured regtest accepts `-testcoinbasematuritylong=start:enforce:release[:maturity]` +and `-testcoinbasematuritylongexpiry=time`. The expiry defaults to the configured +RDTS expiry, or INT64_MAX if none, and must match `-rdtsexpiry` when both are set. diff --git a/src/chainparams.cpp b/src/chainparams.cpp index e2851d88c986..1a2c3243f4d9 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -103,31 +103,50 @@ void ReadRegTestArgs(const ArgsManager& args, CChainParams::RegTestOptions& opti if (const auto arg{args.GetArg("-testcoinbasematuritylong")}; arg) { const auto values{SplitString(*arg, ':')}; - if (values.size() != 3) { - throw std::runtime_error(strprintf("Invalid format (%s) for -testcoinbasematuritylong=::.", *arg)); + if (values.size() < 3 || values.size() > 4) { + throw std::runtime_error(strprintf("Invalid format (%s) for -testcoinbasematuritylong=::[:].", *arg)); } int32_t start_height; int32_t enforce_height; int32_t release_height; 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 -testcoinbasematuritylong=::.", values[0])); + throw std::runtime_error(strprintf("Invalid start height (%s) for -testcoinbasematuritylong=::[:].", 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 -testcoinbasematuritylong=::.", values[1])); + throw std::runtime_error(strprintf("Invalid enforce height (%s) for -testcoinbasematuritylong=::[:].", values[1])); } if (!ParseInt32(values[2], &release_height) || release_height <= start_height || release_height <= enforce_height || release_height >= std::numeric_limits::max()) { - throw std::runtime_error(strprintf("Invalid release height (%s) for -testcoinbasematuritylong=::.", values[2])); + throw std::runtime_error(strprintf("Invalid release height (%s) for -testcoinbasematuritylong=::[:].", values[2])); } if (release_height - start_height <= COINBASE_MATURITY) { throw std::runtime_error(strprintf("Invalid height range (%s) for -testcoinbasematuritylong=::: release_height - start_height must exceed COINBASE_MATURITY.", *arg)); } + if (values.size() == 4) { + int32_t maturity; + if (!ParseInt32(values[3], &maturity) || maturity < COINBASE_MATURITY || maturity >= std::numeric_limits::max()) { + throw std::runtime_error(strprintf("Invalid maturity (%s) for -testcoinbasematuritylong.", values[3])); + } + options.coinbase_maturity_long_maturity = maturity; + } + options.coinbase_maturity_long_start_height = start_height; options.coinbase_maturity_long_enforce_height = enforce_height; options.coinbase_maturity_long_release_height = release_height; } + if (const auto arg{args.GetArg("-testcoinbasematuritylongexpiry")}; arg) { + int64_t expiry; + if (!options.coinbase_maturity_long_start_height || !ParseInt64(*arg, &expiry) || expiry <= 1296688602) { + throw std::runtime_error("Invalid expiry for -testcoinbasematuritylongexpiry: requires -testcoinbasematuritylong and a time after regtest genesis (1296688602)."); + } + if (options.rdts_expiry_time && *options.rdts_expiry_time != expiry) { + throw std::runtime_error("-testcoinbasematuritylongexpiry must match -rdtsexpiry when both are specified."); + } + options.coinbase_maturity_long_expiry_time = expiry; + } + 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..88bebb86dbfd 100644 --- a/src/chainparamsbase.cpp +++ b/src/chainparamsbase.cpp @@ -19,7 +19,8 @@ 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=