Skip to content

Commit 338b9d8

Browse files
committed
Merge bitcoin/bitcoin#30681: Have miner account for timewarp mitigation, activate on regtest, lower nPowTargetTimespan to 144 and add test
59ff17e miner: adjust clock to timewarp rule (Sjors Provoost) e929054 Add timewarp attack mitigation test (Sjors Provoost) e85f386 consensus: enable BIP94 on regtest (Sjors Provoost) dd154b0 consensus: lower regtest nPowTargetTimespan to 144 (Sjors Provoost) Pull request description: Because #30647 reduced the timewarp attack threshold from 7200s to 600s, our miner code will fail to propose a block template (on testnet4) if the last block of the previous period has a timestamp two hours in the future. This PR fixes that and also adds a test. The non-test changes in the last commit should be in v28, otherwise miners have to patch it themselves. If necessary I can split that out into a separate PR, but I prefer to get the tests in as well. In order to add the test, we activate BIP94 on regtest. In order for the test to run faster, we reduce its difficulty retarget period to 144, the same number that's already used for softfork activation logic. Regtest does not actually adjust its difficulty, so this change has no effect (except for `getnetworkhashps`, see commit). An alternative approach would be to run this test on testnet4, by hardcoding its first 2015 in the test suite. But since the timewarp mitigation is a serious candidate for a future mainnet softfork, it seems better to just deploy it on regtest. The next commits add a test and fix the miner code. The `MAX_TIMEWARP` constant is moved to `consensus.h` so both validation and miner code have access to it. ACKs for top commit: achow101: ACK 59ff17e fjahr: ACK 59ff17e glozow: ACK 59ff17e Tree-SHA512: 50af9fdcba9b0d5c57e1efd5feffd870bd11b5318f1f8b0aabf684657f2d33ab108d5f00b1475fe0d38e8e0badc97249ef8dda20c7f47fcc1698bc1008798830
2 parents 5ce2285 + 59ff17e commit 338b9d8

File tree

8 files changed

+72
-11
lines changed

8 files changed

+72
-11
lines changed

doc/release-notes-30647.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Tests
2+
-----
3+
4+
- The BIP94 timewarp attack mitigation is now active on the `regtest` network

src/consensus/consensus.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,11 @@ static const size_t MIN_SERIALIZABLE_TRANSACTION_WEIGHT = WITNESS_SCALE_FACTOR *
2727
/** Interpret sequence numbers as relative lock-time constraints. */
2828
static constexpr unsigned int LOCKTIME_VERIFY_SEQUENCE = (1 << 0);
2929

30+
/**
31+
* Maximum number of seconds that the timestamp of the first
32+
* block of a difficulty adjustment period is allowed to
33+
* be earlier than the last block of the previous period (BIP94).
34+
*/
35+
static constexpr int64_t MAX_TIMEWARP = 600;
36+
3037
#endif // BITCOIN_CONSENSUS_CONSENSUS_H

src/consensus/params.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ struct Params {
108108
/** Proof of work parameters */
109109
uint256 powLimit;
110110
bool fPowAllowMinDifficultyBlocks;
111+
/**
112+
* Enfore BIP94 timewarp attack mitigation. On testnet4 this also enforces
113+
* the block storm mitigation.
114+
*/
111115
bool enforce_BIP94;
112116
bool fPowNoRetargeting;
113117
int64_t nPowTargetSpacing;

src/kernel/chainparams.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -537,10 +537,10 @@ class CRegTestParams : public CChainParams
537537
consensus.SegwitHeight = 0; // Always active unless overridden
538538
consensus.MinBIP9WarningHeight = 0;
539539
consensus.powLimit = uint256{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"};
540-
consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks
540+
consensus.nPowTargetTimespan = 24 * 60 * 60; // one day
541541
consensus.nPowTargetSpacing = 10 * 60;
542542
consensus.fPowAllowMinDifficultyBlocks = true;
543-
consensus.enforce_BIP94 = false;
543+
consensus.enforce_BIP94 = true;
544544
consensus.fPowNoRetargeting = true;
545545
consensus.nRuleChangeActivationThreshold = 108; // 75% for testchains
546546
consensus.nMinerConfirmationWindow = 144; // Faster than normal for regtest (144 instead of 2016)

src/node/miner.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParam
3333
int64_t nOldTime = pblock->nTime;
3434
int64_t nNewTime{std::max<int64_t>(pindexPrev->GetMedianTimePast() + 1, TicksSinceEpoch<std::chrono::seconds>(NodeClock::now()))};
3535

36+
if (consensusParams.enforce_BIP94) {
37+
// Height of block to be mined.
38+
const int height{pindexPrev->nHeight + 1};
39+
if (height % consensusParams.DifficultyAdjustmentInterval() == 0) {
40+
nNewTime = std::max<int64_t>(nNewTime, pindexPrev->GetBlockTime() - MAX_TIMEWARP);
41+
}
42+
}
43+
3644
if (nOldTime < nNewTime) {
3745
pblock->nTime = nNewTime;
3846
}

src/validation.cpp

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,13 +107,6 @@ const std::vector<std::string> CHECKLEVEL_DOC {
107107
* */
108108
static constexpr int PRUNE_LOCK_BUFFER{10};
109109

110-
/**
111-
* Maximum number of seconds that the timestamp of the first
112-
* block of a difficulty adjustment period is allowed to
113-
* be earlier than the last block of the previous period (BIP94).
114-
*/
115-
static constexpr int64_t MAX_TIMEWARP = 600;
116-
117110
GlobalMutex g_best_block_mutex;
118111
std::condition_variable g_best_block_cv;
119112
uint256 g_best_block;
@@ -4189,7 +4182,7 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio
41894182
if (block.GetBlockTime() <= pindexPrev->GetMedianTimePast())
41904183
return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "time-too-old", "block's timestamp is too early");
41914184

4192-
// Testnet4 only: Check timestamp against prev for difficulty-adjustment
4185+
// Testnet4 and regtest only: Check timestamp against prev for difficulty-adjustment
41934186
// blocks to prevent timewarp attacks (see https://github.com/bitcoin/bitcoin/pull/15482).
41944187
if (consensusParams.enforce_BIP94) {
41954188
// Check timestamp for the first block of each difficulty adjustment

test/functional/mining_basic.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,16 @@
2828
from test_framework.test_framework import BitcoinTestFramework
2929
from test_framework.util import (
3030
assert_equal,
31+
assert_greater_than_or_equal,
3132
assert_raises_rpc_error,
3233
get_fee,
3334
)
3435
from test_framework.wallet import MiniWallet
3536

3637

38+
DIFFICULTY_ADJUSTMENT_INTERVAL = 144
39+
MAX_FUTURE_BLOCK_TIME = 2 * 3600
40+
MAX_TIMEWARP = 600
3741
VERSIONBITS_TOP_BITS = 0x20000000
3842
VERSIONBITS_DEPLOYMENT_TESTDUMMY_BIT = 28
3943
DEFAULT_BLOCK_MIN_TX_FEE = 1000 # default `-blockmintxfee` setting [sat/kvB]
@@ -115,6 +119,46 @@ def test_blockmintxfee_parameter(self):
115119
assert tx_below_min_feerate['txid'] not in block_template_txids
116120
assert tx_below_min_feerate['txid'] not in block_txids
117121

122+
def test_timewarp(self):
123+
self.log.info("Test timewarp attack mitigation (BIP94)")
124+
node = self.nodes[0]
125+
126+
self.log.info("Mine until the last block of the retarget period")
127+
blockchain_info = self.nodes[0].getblockchaininfo()
128+
n = DIFFICULTY_ADJUSTMENT_INTERVAL - blockchain_info['blocks'] % DIFFICULTY_ADJUSTMENT_INTERVAL - 2
129+
t = blockchain_info['time']
130+
131+
for _ in range(n):
132+
t += 600
133+
self.nodes[0].setmocktime(t)
134+
self.generate(self.wallet, 1, sync_fun=self.no_op)
135+
136+
self.log.info("Create block two hours in the future")
137+
self.nodes[0].setmocktime(t + MAX_FUTURE_BLOCK_TIME)
138+
self.generate(self.wallet, 1, sync_fun=self.no_op)
139+
assert_equal(node.getblock(node.getbestblockhash())['time'], t + MAX_FUTURE_BLOCK_TIME)
140+
141+
self.log.info("First block template of retarget period can't use wall clock time")
142+
self.nodes[0].setmocktime(t)
143+
# The template will have an adjusted timestamp, which we then modify
144+
tmpl = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)
145+
assert_greater_than_or_equal(tmpl['curtime'], t + MAX_FUTURE_BLOCK_TIME - MAX_TIMEWARP)
146+
147+
block = CBlock()
148+
block.nVersion = tmpl["version"]
149+
block.hashPrevBlock = int(tmpl["previousblockhash"], 16)
150+
block.nTime = tmpl["curtime"]
151+
block.nBits = int(tmpl["bits"], 16)
152+
block.nNonce = 0
153+
block.vtx = [create_coinbase(height=int(tmpl["height"]))]
154+
block.solve()
155+
assert_template(node, block, None)
156+
157+
bad_block = copy.deepcopy(block)
158+
bad_block.nTime = t
159+
bad_block.solve()
160+
assert_raises_rpc_error(-25, 'time-timewarp-attack', lambda: node.submitheader(hexdata=CBlockHeader(bad_block).serialize().hex()))
161+
118162
def run_test(self):
119163
node = self.nodes[0]
120164
self.wallet = MiniWallet(node)
@@ -322,6 +366,7 @@ def chain_tip(b_hash, *, status='headers-only', branchlen=1):
322366
assert_equal(node.submitblock(hexdata=block.serialize().hex()), 'duplicate') # valid
323367

324368
self.test_blockmintxfee_parameter()
369+
self.test_timewarp()
325370

326371

327372
if __name__ == '__main__':

test/functional/rpc_blockchain.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
TIME_RANGE_MTP = TIME_GENESIS_BLOCK + (HEIGHT - 6) * TIME_RANGE_STEP
5959
TIME_RANGE_TIP = TIME_GENESIS_BLOCK + (HEIGHT - 1) * TIME_RANGE_STEP
6060
TIME_RANGE_END = TIME_GENESIS_BLOCK + HEIGHT * TIME_RANGE_STEP
61-
DIFFICULTY_ADJUSTMENT_INTERVAL = 2016
61+
DIFFICULTY_ADJUSTMENT_INTERVAL = 144
6262

6363

6464
class BlockchainTest(BitcoinTestFramework):

0 commit comments

Comments
 (0)