mirror of
https://github.com/bitcoin/bitcoin.git
synced 2024-11-19 09:53:47 +01:00
Merge bitcoin/bitcoin#30681: Have miner account for timewarp mitigation, activate on regtest, lower nPowTargetTimespan to 144 and add test
59ff17e5af
miner: adjust clock to timewarp rule (Sjors Provoost)e929054e12
Add timewarp attack mitigation test (Sjors Provoost)e85f386c4b
consensus: enable BIP94 on regtest (Sjors Provoost)dd154b0568
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: ACK59ff17e5af
fjahr: ACK59ff17e5af
glozow: ACK59ff17e5af
Tree-SHA512: 50af9fdcba9b0d5c57e1efd5feffd870bd11b5318f1f8b0aabf684657f2d33ab108d5f00b1475fe0d38e8e0badc97249ef8dda20c7f47fcc1698bc1008798830
This commit is contained in:
commit
338b9d82dc
4
doc/release-notes-30647.md
Normal file
4
doc/release-notes-30647.md
Normal file
@ -0,0 +1,4 @@
|
||||
Tests
|
||||
-----
|
||||
|
||||
- The BIP94 timewarp attack mitigation is now active on the `regtest` network
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -537,10 +537,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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
@ -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
|
||||
|
@ -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 = 1000 # default `-blockmintxfee` setting [sat/kvB]
|
||||
@ -115,6 +119,46 @@ 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.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)
|
||||
@ -322,6 +366,7 @@ class MiningTest(BitcoinTestFramework):
|
||||
assert_equal(node.submitblock(hexdata=block.serialize().hex()), 'duplicate') # valid
|
||||
|
||||
self.test_blockmintxfee_parameter()
|
||||
self.test_timewarp()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -58,7 +58,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):
|
||||
|
Loading…
Reference in New Issue
Block a user