/ test / functional / feature_bip68_sequence.py
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()