Skip to content

Commit

Permalink
Merge bitcoin#30681: Have miner account for timewarp mitigation, acti…
Browse files Browse the repository at this point in the history
…vate 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 bitcoin#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
  • Loading branch information
achow101 committed Aug 22, 2024
2 parents 5ce2285 + 59ff17e commit 338b9d8
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 11 deletions.
4 changes: 4 additions & 0 deletions doc/release-notes-30647.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Tests
-----

- The BIP94 timewarp attack mitigation is now active on the `regtest` network
7 changes: 7 additions & 0 deletions src/consensus/consensus.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 4 additions & 0 deletions src/consensus/params.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,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;
Expand Down
4 changes: 2 additions & 2 deletions src/kernel/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -537,10 +537,10 @@ class CRegTestParams : public CChainParams
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)
Expand Down
8 changes: 8 additions & 0 deletions src/node/miner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,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;
}
Expand Down
9 changes: 1 addition & 8 deletions src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,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;
Expand Down Expand Up @@ -4189,7 +4182,7 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio
if (block.GetBlockTime() <= pindexPrev->GetMedianTimePast())
return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "time-too-old", "block's timestamp is too early");

// 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
Expand Down
45 changes: 45 additions & 0 deletions test/functional/mining_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,16 @@
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 = 1000 # default `-blockmintxfee` setting [sat/kvB]
Expand Down Expand Up @@ -115,6 +119,46 @@ def test_blockmintxfee_parameter(self):
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.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)
Expand Down Expand Up @@ -322,6 +366,7 @@ def chain_tip(b_hash, *, status='headers-only', branchlen=1):
assert_equal(node.submitblock(hexdata=block.serialize().hex()), 'duplicate') # valid

self.test_blockmintxfee_parameter()
self.test_timewarp()


if __name__ == '__main__':
Expand Down
2 changes: 1 addition & 1 deletion test/functional/rpc_blockchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
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):
Expand Down

0 comments on commit 338b9d8

Please sign in to comment.