Merge 338b9d82dc into merged_master (Bitcoin PR bitcoin/bitcoin#30681)

This commit is contained in:
Tom Trevethan 2026-03-13 12:19:27 +00:00
commit f60016e6ad
8 changed files with 73 additions and 11 deletions

View file

@ -0,0 +1,4 @@
Tests
-----
- The BIP94 timewarp attack mitigation is now active on the `regtest` network

View file

@ -27,4 +27,11 @@ static const size_t MIN_SERIALIZABLE_TRANSACTION_WEIGHT = WITNESS_SCALE_FACTOR *
/** Interpret sequence numbers as relative lock-time constraints. */
static constexpr unsigned int LOCKTIME_VERIFY_SEQUENCE = (1 << 0);
/**
* Maximum number of seconds that the timestamp of the first
* block of a difficulty adjustment period is allowed to
* be earlier than the last block of the previous period (BIP94).
*/
static constexpr int64_t MAX_TIMEWARP = 600;
#endif // BITCOIN_CONSENSUS_CONSENSUS_H

View file

@ -122,6 +122,10 @@ struct Params {
/** Proof of work parameters */
uint256 powLimit;
bool fPowAllowMinDifficultyBlocks;
/**
* Enfore BIP94 timewarp attack mitigation. On testnet4 this also enforces
* the block storm mitigation.
*/
bool enforce_BIP94;
bool fPowNoRetargeting;
int64_t nPowTargetSpacing;

View file

@ -762,10 +762,10 @@ public:
consensus.SegwitHeight = 0; // Always active unless overridden
consensus.MinBIP9WarningHeight = 0;
consensus.powLimit = uint256{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"};
consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks
consensus.nPowTargetTimespan = 24 * 60 * 60; // one day
consensus.nPowTargetSpacing = 10 * 60;
consensus.fPowAllowMinDifficultyBlocks = true;
consensus.enforce_BIP94 = false;
consensus.enforce_BIP94 = true;
consensus.fPowNoRetargeting = true;
consensus.nRuleChangeActivationThreshold = 108; // 75% for testchains
consensus.nMinerConfirmationWindow = 144; // Faster than normal for regtest (144 instead of 2016)

View file

@ -45,6 +45,14 @@ int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParam
int64_t nOldTime = pblock->nTime;
int64_t nNewTime{std::max<int64_t>(pindexPrev->GetMedianTimePast() + 1, TicksSinceEpoch<std::chrono::seconds>(NodeClock::now()))};
if (consensusParams.enforce_BIP94) {
// Height of block to be mined.
const int height{pindexPrev->nHeight + 1};
if (height % consensusParams.DifficultyAdjustmentInterval() == 0) {
nNewTime = std::max<int64_t>(nNewTime, pindexPrev->GetBlockTime() - MAX_TIMEWARP);
}
}
if (nOldTime < nNewTime) {
pblock->nTime = nNewTime;
}

View file

@ -114,13 +114,6 @@ const std::vector<std::string> CHECKLEVEL_DOC {
* */
static constexpr int PRUNE_LOCK_BUFFER{10};
/**
* Maximum number of seconds that the timestamp of the first
* block of a difficulty adjustment period is allowed to
* be earlier than the last block of the previous period (BIP94).
*/
static constexpr int64_t MAX_TIMEWARP = 600;
GlobalMutex g_best_block_mutex;
std::condition_variable g_best_block_cv;
uint256 g_best_block;
@ -4775,7 +4768,7 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio
return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "bad-header-height");
}
// Testnet4 only: Check timestamp against prev for difficulty-adjustment
// Testnet4 and regtest only: Check timestamp against prev for difficulty-adjustment
// blocks to prevent timewarp attacks (see https://github.com/bitcoin/bitcoin/pull/15482).
if (consensusParams.enforce_BIP94) {
// Check timestamp for the first block of each difficulty adjustment

View file

@ -28,12 +28,16 @@ from test_framework.p2p import P2PDataStore
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_greater_than_or_equal,
assert_raises_rpc_error,
get_fee,
)
from test_framework.wallet import MiniWallet
DIFFICULTY_ADJUSTMENT_INTERVAL = 144
MAX_FUTURE_BLOCK_TIME = 2 * 3600
MAX_TIMEWARP = 600
VERSIONBITS_TOP_BITS = 0x20000000
VERSIONBITS_DEPLOYMENT_TESTDUMMY_BIT = 28
DEFAULT_BLOCK_MIN_TX_FEE = 100 # default `-blockmintxfee` setting [sat/kvB] # ELEMENTS
@ -115,6 +119,47 @@ class MiningTest(BitcoinTestFramework):
assert tx_below_min_feerate['txid'] not in block_template_txids
assert tx_below_min_feerate['txid'] not in block_txids
def test_timewarp(self):
self.log.info("Test timewarp attack mitigation (BIP94)")
node = self.nodes[0]
self.log.info("Mine until the last block of the retarget period")
blockchain_info = self.nodes[0].getblockchaininfo()
n = DIFFICULTY_ADJUSTMENT_INTERVAL - blockchain_info['blocks'] % DIFFICULTY_ADJUSTMENT_INTERVAL - 2
t = blockchain_info['time']
for _ in range(n):
t += 600
self.nodes[0].setmocktime(t)
self.generate(self.wallet, 1, sync_fun=self.no_op)
self.log.info("Create block two hours in the future")
self.nodes[0].setmocktime(t + MAX_FUTURE_BLOCK_TIME)
self.generate(self.wallet, 1, sync_fun=self.no_op)
assert_equal(node.getblock(node.getbestblockhash())['time'], t + MAX_FUTURE_BLOCK_TIME)
self.log.info("First block template of retarget period can't use wall clock time")
self.nodes[0].setmocktime(t)
# 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)
block = CBlock()
block.nVersion = tmpl["version"]
block.hashPrevBlock = int(tmpl["previousblockhash"], 16)
block.nTime = tmpl["curtime"]
block.nBits = int(tmpl["bits"], 16)
block.block_height = node.getblockcount()+1
block.nNonce = 0
block.vtx = [create_coinbase(height=int(tmpl["height"]))]
block.solve()
assert_template(node, block, None)
bad_block = copy.deepcopy(block)
bad_block.nTime = t
bad_block.solve()
assert_raises_rpc_error(-25, 'time-timewarp-attack', lambda: node.submitheader(hexdata=CBlockHeader(bad_block).serialize().hex()))
def run_test(self):
node = self.nodes[0]
self.wallet = MiniWallet(node)
@ -330,6 +375,7 @@ class MiningTest(BitcoinTestFramework):
assert_equal(node.submitblock(hexdata=block.serialize().hex()), 'duplicate') # valid
self.test_blockmintxfee_parameter()
self.test_timewarp()
if __name__ == '__main__':

View file

@ -59,7 +59,7 @@ TIME_RANGE_STEP = 600 # ten-minute steps
TIME_RANGE_MTP = TIME_GENESIS_BLOCK + (HEIGHT - 6) * TIME_RANGE_STEP
TIME_RANGE_TIP = TIME_GENESIS_BLOCK + (HEIGHT - 1) * TIME_RANGE_STEP
TIME_RANGE_END = TIME_GENESIS_BLOCK + HEIGHT * TIME_RANGE_STEP
DIFFICULTY_ADJUSTMENT_INTERVAL = 2016
DIFFICULTY_ADJUSTMENT_INTERVAL = 144
class BlockchainTest(BitcoinTestFramework):