Skip to content

Commit e929054

Browse files
committed
Add timewarp attack mitigation test
1 parent e85f386 commit e929054

File tree

1 file changed

+44
-0
lines changed

1 file changed

+44
-0
lines changed

test/functional/mining_basic.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
from test_framework.wallet import MiniWallet
3535

3636

37+
DIFFICULTY_ADJUSTMENT_INTERVAL = 144
38+
MAX_FUTURE_BLOCK_TIME = 2 * 3600
39+
MAX_TIMEWARP = 600
3740
VERSIONBITS_TOP_BITS = 0x20000000
3841
VERSIONBITS_DEPLOYMENT_TESTDUMMY_BIT = 28
3942
DEFAULT_BLOCK_MIN_TX_FEE = 1000 # default `-blockmintxfee` setting [sat/kvB]
@@ -115,6 +118,46 @@ def test_blockmintxfee_parameter(self):
115118
assert tx_below_min_feerate['txid'] not in block_template_txids
116119
assert tx_below_min_feerate['txid'] not in block_txids
117120

121+
def test_timewarp(self):
122+
self.log.info("Test timewarp attack mitigation (BIP94)")
123+
node = self.nodes[0]
124+
125+
self.log.info("Mine until the last block of the retarget period")
126+
blockchain_info = self.nodes[0].getblockchaininfo()
127+
n = DIFFICULTY_ADJUSTMENT_INTERVAL - blockchain_info['blocks'] % DIFFICULTY_ADJUSTMENT_INTERVAL - 2
128+
t = blockchain_info['time']
129+
130+
for _ in range(n):
131+
t += 600
132+
self.nodes[0].setmocktime(t)
133+
self.generate(self.wallet, 1, sync_fun=self.no_op)
134+
135+
self.log.info("Create block two hours in the future")
136+
self.nodes[0].setmocktime(t + MAX_FUTURE_BLOCK_TIME)
137+
self.generate(self.wallet, 1, sync_fun=self.no_op)
138+
assert_equal(node.getblock(node.getbestblockhash())['time'], t + MAX_FUTURE_BLOCK_TIME)
139+
140+
self.log.info("First block template of retarget period can't use wall clock time")
141+
self.nodes[0].setmocktime(t)
142+
assert_raises_rpc_error(-1, "time-timewarp-attack, block's timestamp is too early on diff adjustment block",
143+
lambda: node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS))
144+
145+
# Create template with an acceptable timestamp and then modify it
146+
self.nodes[0].setmocktime(t + MAX_FUTURE_BLOCK_TIME)
147+
tmpl = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)
148+
149+
block = CBlock()
150+
block.nVersion = tmpl["version"]
151+
block.hashPrevBlock = int(tmpl["previousblockhash"], 16)
152+
block.nTime = t
153+
block.nBits = int(tmpl["bits"], 16)
154+
block.nNonce = 0
155+
block.vtx = [create_coinbase(height=int(tmpl["height"]))]
156+
block.solve()
157+
158+
self.nodes[0].setmocktime(t)
159+
assert_raises_rpc_error(-25, 'time-timewarp-attack', lambda: node.submitheader(hexdata=CBlockHeader(block).serialize().hex()))
160+
118161
def run_test(self):
119162
node = self.nodes[0]
120163
self.wallet = MiniWallet(node)
@@ -322,6 +365,7 @@ def chain_tip(b_hash, *, status='headers-only', branchlen=1):
322365
assert_equal(node.submitblock(hexdata=block.serialize().hex()), 'duplicate') # valid
323366

324367
self.test_blockmintxfee_parameter()
368+
self.test_timewarp()
325369

326370

327371
if __name__ == '__main__':

0 commit comments

Comments
 (0)