diff --git a/doc/release-notes-31600.md b/doc/release-notes-31600.md new file mode 100644 index 0000000000..54575e4cfd --- /dev/null +++ b/doc/release-notes-31600.md @@ -0,0 +1,11 @@ +Updated RPCs +--- +- the `getblocktemplate` RPC `curtime` (BIP22) and `mintime` (BIP23) fields now + account for the timewarp fix proposed in BIP94 on all networks. This ensures + that, in the event a timewarp fix softfork activates on mainnet, un-upgraded + miners will not accidentally violate the timewarp rule. (#31376, #31600) + +As a reminder, it's important that any software which uses the `getblocktemplate` +RPC takes these values into account (either `curtime` or `mintime` is fine). +Relying only on a clock can lead to invalid blocks under some circumstances, +especially once a timewarp fix is deployed. diff --git a/src/node/miner.cpp b/src/node/miner.cpp index 1e6f1c53a9..9995adc0d0 100644 --- a/src/node/miner.cpp +++ b/src/node/miner.cpp @@ -28,16 +28,25 @@ #include namespace node { + +int64_t GetMinimumTime(const CBlockIndex* pindexPrev, const int64_t difficulty_adjustment_interval) +{ + int64_t min_time{pindexPrev->GetMedianTimePast() + 1}; + // Height of block to be mined. + const int height{pindexPrev->nHeight + 1}; + // Account for BIP94 timewarp rule on all networks. This makes future + // activation safer. + if (height % difficulty_adjustment_interval == 0) { + min_time = std::max(min_time, pindexPrev->GetBlockTime() - MAX_TIMEWARP); + } + return min_time; +} + int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev) { int64_t nOldTime = pblock->nTime; - int64_t nNewTime{std::max(pindexPrev->GetMedianTimePast() + 1, TicksSinceEpoch(NodeClock::now()))}; - - // Height of block to be mined. - const int height{pindexPrev->nHeight + 1}; - if (height % consensusParams.DifficultyAdjustmentInterval() == 0) { - nNewTime = std::max(nNewTime, pindexPrev->GetBlockTime() - MAX_TIMEWARP); - } + int64_t nNewTime{std::max(GetMinimumTime(pindexPrev, consensusParams.DifficultyAdjustmentInterval()), + TicksSinceEpoch(NodeClock::now()))}; if (nOldTime < nNewTime) { pblock->nTime = nNewTime; diff --git a/src/node/miner.h b/src/node/miner.h index 3c4c66b0ba..5e5d0b25f3 100644 --- a/src/node/miner.h +++ b/src/node/miner.h @@ -211,6 +211,13 @@ private: void SortForBlock(const CTxMemPool::setEntries& package, std::vector& sortedEntries); }; +/** + * Get the minimum time a miner should use in the next block. This always + * accounts for the BIP94 timewarp rule, so does not necessarily reflect the + * consensus limit. + */ +int64_t GetMinimumTime(const CBlockIndex* pindexPrev, const int64_t difficulty_adjustment_interval); + int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev); /** Update an old GenerateCoinbaseCommitment from CreateNewBlock after the block txs have changed */ diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 5e3d1120e3..fbfa7b8637 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -49,6 +49,7 @@ using interfaces::BlockTemplate; using interfaces::Mining; using node::BlockAssembler; +using node::GetMinimumTime; using node::NodeContext; using node::RegenerateCommitments; using node::UpdateTime; @@ -674,7 +675,7 @@ static RPCHelpMan getblocktemplate() {RPCResult::Type::NUM, "coinbasevalue", "maximum allowable input to coinbase transaction, including the generation award and transaction fees (in satoshis)"}, {RPCResult::Type::STR, "longpollid", "an id to include with a request to longpoll on an update to this template"}, {RPCResult::Type::STR, "target", "The hash target"}, - {RPCResult::Type::NUM_TIME, "mintime", "The minimum timestamp appropriate for the next block time, expressed in " + UNIX_EPOCH_TIME}, + {RPCResult::Type::NUM_TIME, "mintime", "The minimum timestamp appropriate for the next block time, expressed in " + UNIX_EPOCH_TIME + ". Adjusted for the proposed BIP94 timewarp rule."}, {RPCResult::Type::ARR, "mutable", "list of ways the block template may be changed", { {RPCResult::Type::STR, "value", "A way the block template may be changed, e.g. 'time', 'transactions', 'prevblock'"}, @@ -683,7 +684,7 @@ static RPCHelpMan getblocktemplate() {RPCResult::Type::NUM, "sigoplimit", "limit of sigops in blocks"}, {RPCResult::Type::NUM, "sizelimit", "limit of block size"}, {RPCResult::Type::NUM, "weightlimit", /*optional=*/true, "limit of block weight"}, - {RPCResult::Type::NUM_TIME, "curtime", "current timestamp in " + UNIX_EPOCH_TIME}, + {RPCResult::Type::NUM_TIME, "curtime", "current timestamp in " + UNIX_EPOCH_TIME + ". Adjusted for the proposed BIP94 timewarp rule."}, {RPCResult::Type::STR, "bits", "compressed target of next block"}, {RPCResult::Type::NUM, "height", "The height of the next block"}, {RPCResult::Type::STR_HEX, "signet_challenge", /*optional=*/true, "Only on signet"}, @@ -977,7 +978,7 @@ static RPCHelpMan getblocktemplate() result.pushKV("coinbasevalue", (int64_t)block.vtx[0]->vout[0].nValue); result.pushKV("longpollid", tip.GetHex() + ToString(nTransactionsUpdatedLast)); result.pushKV("target", hashTarget.GetHex()); - result.pushKV("mintime", (int64_t)pindexPrev->GetMedianTimePast()+1); + result.pushKV("mintime", GetMinimumTime(pindexPrev, consensusParams.DifficultyAdjustmentInterval())); result.pushKV("mutable", std::move(aMutable)); result.pushKV("noncerange", "00000000ffffffff"); int64_t nSigOpLimit = MAX_BLOCK_SIGOPS_COST; diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py index 979eda7d04..827e05dcfe 100755 --- a/test/functional/mining_basic.py +++ b/test/functional/mining_basic.py @@ -153,6 +153,8 @@ class MiningTest(BitcoinTestFramework): # The template will have an adjusted timestamp, which we then modify tmpl = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS) assert_greater_than_or_equal(tmpl['curtime'], t + MAX_FUTURE_BLOCK_TIME - MAX_TIMEWARP) + # mintime and curtime should match + assert_equal(tmpl['mintime'], tmpl['curtime']) block = CBlock() block.nVersion = tmpl["version"]