feature_bip68_sequence.py
1 #!/usr/bin/env python3 2 # Copyright (c) 2014-2022 The Bitcoin Core developers 3 # Distributed under the MIT software license, see the accompanying 4 # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 """Test BIP68 implementation.""" 6 7 import time 8 9 from test_framework.blocktools import ( 10 NORMAL_GBT_REQUEST_PARAMS, 11 add_witness_commitment, 12 create_block, 13 script_to_p2wsh_script, 14 ) 15 from test_framework.messages import ( 16 COIN, 17 COutPoint, 18 CTransaction, 19 CTxIn, 20 CTxInWitness, 21 CTxOut, 22 tx_from_hex, 23 ) 24 from test_framework.script import ( 25 CScript, 26 OP_TRUE, 27 ) 28 from test_framework.test_framework import BitcoinTestFramework 29 from test_framework.util import ( 30 assert_equal, 31 assert_greater_than, 32 assert_raises_rpc_error, 33 softfork_active, 34 ) 35 from test_framework.wallet import MiniWallet 36 37 SCRIPT_W0_SH_OP_TRUE = script_to_p2wsh_script(CScript([OP_TRUE])) 38 39 SEQUENCE_LOCKTIME_DISABLE_FLAG = (1<<31) 40 SEQUENCE_LOCKTIME_TYPE_FLAG = (1<<22) # this means use time (0 means height) 41 SEQUENCE_LOCKTIME_GRANULARITY = 9 # this is a bit-shift 42 SEQUENCE_LOCKTIME_MASK = 0x0000ffff 43 44 # RPC error for non-BIP68 final transactions 45 NOT_FINAL_ERROR = "non-BIP68-final" 46 47 class BIP68Test(BitcoinTestFramework): 48 def set_test_params(self): 49 self.num_nodes = 2 50 self.extra_args = [ 51 [ 52 '-testactivationheight=csv@432', 53 ], 54 [ 55 '-testactivationheight=csv@432', 56 ], 57 ] 58 59 def run_test(self): 60 self.relayfee = self.nodes[0].getnetworkinfo()["relayfee"] 61 self.wallet = MiniWallet(self.nodes[0]) 62 63 self.log.info("Running test disable flag") 64 self.test_disable_flag() 65 66 self.log.info("Running test sequence-lock-confirmed-inputs") 67 self.test_sequence_lock_confirmed_inputs() 68 69 self.log.info("Running test sequence-lock-unconfirmed-inputs") 70 self.test_sequence_lock_unconfirmed_inputs() 71 72 self.log.info("Running test BIP68 not consensus before activation") 73 self.test_bip68_not_consensus() 74 75 self.log.info("Activating BIP68 (and 112/113)") 76 self.activateCSV() 77 78 self.log.info("Verifying version=2 transactions are standard.") 79 self.log.info("Note that version=2 transactions are always standard (independent of BIP68 activation status).") 80 self.test_version2_relay() 81 82 self.log.info("Passed") 83 84 # Test that BIP68 is not in effect if tx version is 1, or if 85 # the first sequence bit is set. 86 def test_disable_flag(self): 87 # Create some unconfirmed inputs 88 utxo = self.wallet.send_self_transfer(from_node=self.nodes[0])["new_utxo"] 89 90 tx1 = CTransaction() 91 value = int((utxo["value"] - self.relayfee) * COIN) 92 93 # Check that the disable flag disables relative locktime. 94 # If sequence locks were used, this would require 1 block for the 95 # input to mature. 96 sequence_value = SEQUENCE_LOCKTIME_DISABLE_FLAG | 1 97 tx1.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), nSequence=sequence_value)] 98 tx1.vout = [CTxOut(value, SCRIPT_W0_SH_OP_TRUE)] 99 100 self.wallet.sign_tx(tx=tx1) 101 tx1_id = self.wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=tx1.serialize().hex()) 102 tx1_id = int(tx1_id, 16) 103 104 # This transaction will enable sequence-locks, so this transaction should 105 # fail 106 tx2 = CTransaction() 107 tx2.version = 2 108 sequence_value = sequence_value & 0x7fffffff 109 tx2.vin = [CTxIn(COutPoint(tx1_id, 0), nSequence=sequence_value)] 110 tx2.wit.vtxinwit = [CTxInWitness()] 111 tx2.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])] 112 tx2.vout = [CTxOut(int(value - self.relayfee * COIN), SCRIPT_W0_SH_OP_TRUE)] 113 114 assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.wallet.sendrawtransaction, from_node=self.nodes[0], tx_hex=tx2.serialize().hex()) 115 116 # Setting the version back down to 1 should disable the sequence lock, 117 # so this should be accepted. 118 tx2.version = 1 119 120 self.wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=tx2.serialize().hex()) 121 122 # Calculate the median time past of a prior block ("confirmations" before 123 # the current tip). 124 def get_median_time_past(self, confirmations): 125 block_hash = self.nodes[0].getblockhash(self.nodes[0].getblockcount()-confirmations) 126 return self.nodes[0].getblockheader(block_hash)["mediantime"] 127 128 # Test that sequence locks are respected for transactions spending confirmed inputs. 129 def test_sequence_lock_confirmed_inputs(self): 130 # Create lots of confirmed utxos, and use them to generate lots of random 131 # transactions. 132 max_outputs = 50 133 while len(self.wallet.get_utxos(include_immature_coinbase=False, mark_as_spent=False)) < 200: 134 import random 135 num_outputs = random.randint(1, max_outputs) 136 self.wallet.send_self_transfer_multi(from_node=self.nodes[0], num_outputs=num_outputs) 137 self.generate(self.wallet, 1) 138 139 utxos = self.wallet.get_utxos(include_immature_coinbase=False) 140 141 # Try creating a lot of random transactions. 142 # Each time, choose a random number of inputs, and randomly set 143 # some of those inputs to be sequence locked (and randomly choose 144 # between height/time locking). Small random chance of making the locks 145 # all pass. 146 for _ in range(400): 147 available_utxos = len(utxos) 148 149 # Randomly choose up to 10 inputs 150 num_inputs = random.randint(1, min(10, available_utxos)) 151 random.shuffle(utxos) 152 153 # Track whether any sequence locks used should fail 154 should_pass = True 155 156 # Track whether this transaction was built with sequence locks 157 using_sequence_locks = False 158 159 tx = CTransaction() 160 tx.version = 2 161 value = 0 162 for j in range(num_inputs): 163 sequence_value = 0xfffffffe # this disables sequence locks 164 165 # 50% chance we enable sequence locks 166 if random.randint(0,1): 167 using_sequence_locks = True 168 169 # 10% of the time, make the input sequence value pass 170 input_will_pass = (random.randint(1,10) == 1) 171 sequence_value = utxos[j]["confirmations"] 172 if not input_will_pass: 173 sequence_value += 1 174 should_pass = False 175 176 # Figure out what the median-time-past was for the confirmed input 177 # Note that if an input has N confirmations, we're going back N blocks 178 # from the tip so that we're looking up MTP of the block 179 # PRIOR to the one the input appears in, as per the BIP68 spec. 180 orig_time = self.get_median_time_past(utxos[j]["confirmations"]) 181 cur_time = self.get_median_time_past(0) # MTP of the tip 182 183 # can only timelock this input if it's not too old -- otherwise use height 184 can_time_lock = True 185 if ((cur_time - orig_time) >> SEQUENCE_LOCKTIME_GRANULARITY) >= SEQUENCE_LOCKTIME_MASK: 186 can_time_lock = False 187 188 # if time-lockable, then 50% chance we make this a time lock 189 if random.randint(0,1) and can_time_lock: 190 # Find first time-lock value that fails, or latest one that succeeds 191 time_delta = sequence_value << SEQUENCE_LOCKTIME_GRANULARITY 192 if input_will_pass and time_delta > cur_time - orig_time: 193 sequence_value = ((cur_time - orig_time) >> SEQUENCE_LOCKTIME_GRANULARITY) 194 elif (not input_will_pass and time_delta <= cur_time - orig_time): 195 sequence_value = ((cur_time - orig_time) >> SEQUENCE_LOCKTIME_GRANULARITY)+1 196 sequence_value |= SEQUENCE_LOCKTIME_TYPE_FLAG 197 tx.vin.append(CTxIn(COutPoint(int(utxos[j]["txid"], 16), utxos[j]["vout"]), nSequence=sequence_value)) 198 value += utxos[j]["value"]*COIN 199 # Overestimate the size of the tx - signatures should be less than 120 bytes, and leave 50 for the output 200 tx_size = len(tx.serialize().hex())//2 + 120*num_inputs + 50 201 tx.vout.append(CTxOut(int(value - self.relayfee * tx_size * COIN / 1000), SCRIPT_W0_SH_OP_TRUE)) 202 self.wallet.sign_tx(tx=tx) 203 204 if (using_sequence_locks and not should_pass): 205 # This transaction should be rejected 206 assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.wallet.sendrawtransaction, from_node=self.nodes[0], tx_hex=tx.serialize().hex()) 207 else: 208 # This raw transaction should be accepted 209 self.wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=tx.serialize().hex()) 210 self.wallet.rescan_utxos() 211 utxos = self.wallet.get_utxos(include_immature_coinbase=False) 212 213 # Test that sequence locks on unconfirmed inputs must have nSequence 214 # height or time of 0 to be accepted. 215 # Then test that BIP68-invalid transactions are removed from the mempool 216 # after a reorg. 217 def test_sequence_lock_unconfirmed_inputs(self): 218 # Store height so we can easily reset the chain at the end of the test 219 cur_height = self.nodes[0].getblockcount() 220 221 # Create a mempool tx. 222 self.wallet.rescan_utxos() 223 tx1 = self.wallet.send_self_transfer(from_node=self.nodes[0])["tx"] 224 225 # Anyone-can-spend mempool tx. 226 # Sequence lock of 0 should pass. 227 tx2 = CTransaction() 228 tx2.version = 2 229 tx2.vin = [CTxIn(COutPoint(tx1.txid_int, 0), nSequence=0)] 230 tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee * COIN), SCRIPT_W0_SH_OP_TRUE)] 231 self.wallet.sign_tx(tx=tx2) 232 tx2_raw = tx2.serialize().hex() 233 234 self.wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=tx2_raw) 235 236 # Create a spend of the 0th output of orig_tx with a sequence lock 237 # of 1, and test what happens when submitting. 238 # orig_tx.vout[0] must be an anyone-can-spend output 239 def test_nonzero_locks(orig_tx, node, relayfee, use_height_lock): 240 sequence_value = 1 241 if not use_height_lock: 242 sequence_value |= SEQUENCE_LOCKTIME_TYPE_FLAG 243 244 tx = CTransaction() 245 tx.version = 2 246 tx.vin = [CTxIn(COutPoint(orig_tx.txid_int, 0), nSequence=sequence_value)] 247 tx.wit.vtxinwit = [CTxInWitness()] 248 tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])] 249 tx.vout = [CTxOut(int(orig_tx.vout[0].nValue - relayfee * COIN), SCRIPT_W0_SH_OP_TRUE)] 250 251 if (orig_tx.txid_hex in node.getrawmempool()): 252 # sendrawtransaction should fail if the tx is in the mempool 253 assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.wallet.sendrawtransaction, from_node=node, tx_hex=tx.serialize().hex()) 254 else: 255 # sendrawtransaction should succeed if the tx is not in the mempool 256 self.wallet.sendrawtransaction(from_node=node, tx_hex=tx.serialize().hex()) 257 258 return tx 259 260 test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=True) 261 test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=False) 262 263 # Now mine some blocks, but make sure tx2 doesn't get mined. 264 # Use prioritisetransaction to lower the effective feerate to 0 265 self.nodes[0].prioritisetransaction(txid=tx2.txid_hex, fee_delta=int(-self.relayfee*COIN)) 266 cur_time = int(time.time()) 267 for _ in range(10): 268 self.nodes[0].setmocktime(cur_time + 600) 269 self.generate(self.wallet, 1, sync_fun=self.no_op) 270 cur_time += 600 271 272 assert tx2.txid_hex in self.nodes[0].getrawmempool() 273 274 test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=True) 275 test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=False) 276 277 # Mine tx2, and then try again 278 self.nodes[0].prioritisetransaction(txid=tx2.txid_hex, fee_delta=int(self.relayfee*COIN)) 279 280 # Advance the time on the node so that we can test timelocks 281 self.nodes[0].setmocktime(cur_time+600) 282 # Save block template now to use for the reorg later 283 tmpl = self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS) 284 self.generate(self.nodes[0], 1) 285 assert tx2.txid_hex not in self.nodes[0].getrawmempool() 286 287 # Now that tx2 is not in the mempool, a sequence locked spend should 288 # succeed 289 tx3 = test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=False) 290 assert tx3.txid_hex in self.nodes[0].getrawmempool() 291 292 self.generate(self.nodes[0], 1) 293 assert tx3.txid_hex not in self.nodes[0].getrawmempool() 294 295 # One more test, this time using height locks 296 tx4 = test_nonzero_locks(tx3, self.nodes[0], self.relayfee, use_height_lock=True) 297 assert tx4.txid_hex in self.nodes[0].getrawmempool() 298 299 # Now try combining confirmed and unconfirmed inputs 300 tx5 = test_nonzero_locks(tx4, self.nodes[0], self.relayfee, use_height_lock=True) 301 assert tx5.txid_hex not in self.nodes[0].getrawmempool() 302 303 utxo = self.wallet.get_utxo() 304 tx5.vin.append(CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), nSequence=1)) 305 tx5.vout[0].nValue += int(utxo["value"]*COIN) 306 self.wallet.sign_tx(tx=tx5) 307 308 assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.wallet.sendrawtransaction, from_node=self.nodes[0], tx_hex=tx5.serialize().hex()) 309 310 # Test mempool-BIP68 consistency after reorg 311 # 312 # State of the transactions in the last blocks: 313 # ... -> [ tx2 ] -> [ tx3 ] 314 # tip-1 tip 315 # And currently tx4 is in the mempool. 316 # 317 # If we invalidate the tip, tx3 should get added to the mempool, causing 318 # tx4 to be removed (fails sequence-lock). 319 self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) 320 assert tx4.txid_hex not in self.nodes[0].getrawmempool() 321 assert tx3.txid_hex in self.nodes[0].getrawmempool() 322 323 # Now mine 2 empty blocks to reorg out the current tip (labeled tip-1 in 324 # diagram above). 325 # This would cause tx2 to be added back to the mempool, which in turn causes 326 # tx3 to be removed. 327 for i in range(2): 328 block = create_block(tmpl=tmpl, ntime=cur_time) 329 block.solve() 330 tip = block.hash_int 331 assert_equal(None if i == 1 else 'inconclusive', self.nodes[0].submitblock(block.serialize().hex())) 332 tmpl = self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS) 333 tmpl['previousblockhash'] = '%x' % tip 334 tmpl['transactions'] = [] 335 cur_time += 1 336 337 mempool = self.nodes[0].getrawmempool() 338 assert tx3.txid_hex not in mempool 339 assert tx2.txid_hex in mempool 340 341 # Reset the chain and get rid of the mocktimed-blocks 342 self.nodes[0].setmocktime(0) 343 self.nodes[0].invalidateblock(self.nodes[0].getblockhash(cur_height+1)) 344 self.generate(self.wallet, 10, sync_fun=self.no_op) 345 346 # Make sure that BIP68 isn't being used to validate blocks prior to 347 # activation height. If more blocks are mined prior to this test 348 # being run, then it's possible the test has activated the soft fork, and 349 # this test should be moved to run earlier, or deleted. 350 def test_bip68_not_consensus(self): 351 assert not softfork_active(self.nodes[0], 'csv') 352 353 tx1 = self.wallet.send_self_transfer(from_node=self.nodes[0])["tx"] 354 355 # Make an anyone-can-spend transaction 356 tx2 = CTransaction() 357 tx2.version = 1 358 tx2.vin = [CTxIn(COutPoint(tx1.txid_int, 0), nSequence=0)] 359 tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee * COIN), SCRIPT_W0_SH_OP_TRUE)] 360 361 # sign tx2 362 self.wallet.sign_tx(tx=tx2) 363 tx2_raw = tx2.serialize().hex() 364 tx2 = tx_from_hex(tx2_raw) 365 366 self.wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=tx2_raw) 367 368 # Now make an invalid spend of tx2 according to BIP68 369 sequence_value = 100 # 100 block relative locktime 370 371 tx3 = CTransaction() 372 tx3.version = 2 373 tx3.vin = [CTxIn(COutPoint(tx2.txid_int, 0), nSequence=sequence_value)] 374 tx3.wit.vtxinwit = [CTxInWitness()] 375 tx3.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])] 376 tx3.vout = [CTxOut(int(tx2.vout[0].nValue - self.relayfee * COIN), SCRIPT_W0_SH_OP_TRUE)] 377 378 assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.wallet.sendrawtransaction, from_node=self.nodes[0], tx_hex=tx3.serialize().hex()) 379 380 # make a block that violates bip68; ensure that the tip updates 381 block = create_block(tmpl=self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS), txlist=[tx1, tx2, tx3]) 382 add_witness_commitment(block) 383 block.solve() 384 385 assert_equal(None, self.nodes[0].submitblock(block.serialize().hex())) 386 assert_equal(self.nodes[0].getbestblockhash(), block.hash_hex) 387 388 def activateCSV(self): 389 # activation should happen at block height 432 (3 periods) 390 # getblockchaininfo will show CSV as active at block 431 (144 * 3 -1) since it's returning whether CSV is active for the next block. 391 min_activation_height = 432 392 height = self.nodes[0].getblockcount() 393 assert_greater_than(min_activation_height - height, 2) 394 self.generate(self.wallet, min_activation_height - height - 2, sync_fun=self.no_op) 395 assert not softfork_active(self.nodes[0], 'csv') 396 self.generate(self.wallet, 1, sync_fun=self.no_op) 397 assert softfork_active(self.nodes[0], 'csv') 398 self.sync_blocks() 399 400 # Use self.nodes[1] to test that version 2 transactions are standard. 401 def test_version2_relay(self): 402 mini_wallet = MiniWallet(self.nodes[1]) 403 mini_wallet.send_self_transfer(from_node=self.nodes[1], version=2) 404 405 406 if __name__ == '__main__': 407 BIP68Test(__file__).main()