|
34 | 34 | from test_framework.wallet import MiniWallet
|
35 | 35 |
|
36 | 36 |
|
| 37 | +DIFFICULTY_ADJUSTMENT_INTERVAL = 144 |
| 38 | +MAX_FUTURE_BLOCK_TIME = 2 * 3600 |
| 39 | +MAX_TIMEWARP = 600 |
37 | 40 | VERSIONBITS_TOP_BITS = 0x20000000
|
38 | 41 | VERSIONBITS_DEPLOYMENT_TESTDUMMY_BIT = 28
|
39 | 42 | DEFAULT_BLOCK_MIN_TX_FEE = 1000 # default `-blockmintxfee` setting [sat/kvB]
|
@@ -115,6 +118,46 @@ def test_blockmintxfee_parameter(self):
|
115 | 118 | assert tx_below_min_feerate['txid'] not in block_template_txids
|
116 | 119 | assert tx_below_min_feerate['txid'] not in block_txids
|
117 | 120 |
|
| 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 | + |
118 | 161 | def run_test(self):
|
119 | 162 | node = self.nodes[0]
|
120 | 163 | self.wallet = MiniWallet(node)
|
@@ -322,6 +365,7 @@ def chain_tip(b_hash, *, status='headers-only', branchlen=1):
|
322 | 365 | assert_equal(node.submitblock(hexdata=block.serialize().hex()), 'duplicate') # valid
|
323 | 366 |
|
324 | 367 | self.test_blockmintxfee_parameter()
|
| 368 | + self.test_timewarp() |
325 | 369 |
|
326 | 370 |
|
327 | 371 | if __name__ == '__main__':
|
|
0 commit comments