Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1#!/usr/bin/env python32# Copyright (c) 2014-present The Bitcoin Core developers3# Distributed under the MIT software license, see the accompanying4# file COPYING or http://www.opensource.org/licenses/mit-license.php.5"""Test BIP68 implementation."""7import random8import time10from test_framework.blocktools import (11NORMAL_GBT_REQUEST_PARAMS,12add_witness_commitment,13create_block,14script_to_p2wsh_script,15)16from test_framework.messages import (17COIN,18COutPoint,19CTransaction,20CTxIn,21CTxInWitness,22CTxOut,23tx_from_hex,24)25from test_framework.script import (26CScript,27OP_TRUE,28SEQUENCE_LOCKTIME_DISABLE_FLAG,29SEQUENCE_LOCKTIME_TYPE_FLAG,30SEQUENCE_LOCKTIME_GRANULARITY,31SEQUENCE_LOCKTIME_MASK,32)33from test_framework.test_framework import BitcoinTestFramework34from test_framework.util import (35assert_equal,36assert_greater_than,37assert_raises_rpc_error,38softfork_active,39)40from test_framework.wallet import MiniWallet42SCRIPT_W0_SH_OP_TRUE = script_to_p2wsh_script(CScript([OP_TRUE]))44# RPC error for non-BIP68 final transactions45NOT_FINAL_ERROR = "non-BIP68-final"47class BIP68Test(BitcoinTestFramework):48def set_test_params(self):49self.num_nodes = 250self.extra_args = [51[52'-testactivationheight=csv@432',53],54[55'-testactivationheight=csv@432',56],57]59def run_test(self):60self.relayfee = self.nodes[0].getnetworkinfo()["relayfee"]61self.wallet = MiniWallet(self.nodes[0])63self.log.info("Running test disable flag")64self.test_disable_flag()66self.log.info("Running test sequence-lock-confirmed-inputs")67self.test_sequence_lock_confirmed_inputs()69self.log.info("Running test sequence-lock-unconfirmed-inputs")70self.test_sequence_lock_unconfirmed_inputs()72self.log.info("Running test BIP68 not consensus before activation")73self.test_bip68_not_consensus()75self.log.info("Activating BIP68 (and 112/113)")76self.activateCSV()78self.log.info("Verifying version=2 transactions are standard.")79self.log.info("Note that version=2 transactions are always standard (independent of BIP68 activation status).")80self.test_version2_relay()82self.log.info("Passed")84# Test that BIP68 is not in effect if tx version is 1, or if85# the first sequence bit is set.86def test_disable_flag(self):87# Create some unconfirmed inputs88utxo = self.wallet.send_self_transfer(from_node=self.nodes[0])["new_utxo"]90tx1 = CTransaction()91value = int((utxo["value"] - self.relayfee) * COIN)93# Check that the disable flag disables relative locktime.94# If sequence locks were used, this would require 1 block for the95# input to mature.96sequence_value = SEQUENCE_LOCKTIME_DISABLE_FLAG | 197tx1.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), nSequence=sequence_value)]98tx1.vout = [CTxOut(value, SCRIPT_W0_SH_OP_TRUE)]100self.wallet.sign_tx(tx=tx1)101tx1_id = self.wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=tx1.serialize().hex())102tx1_id = int(tx1_id, 16)104# This transaction will enable sequence-locks, so this transaction should105# fail106tx2 = CTransaction()107tx2.version = 2108sequence_value = sequence_value & 0x7fffffff109tx2.vin = [CTxIn(COutPoint(tx1_id, 0), nSequence=sequence_value)]110tx2.wit.vtxinwit = [CTxInWitness()]111tx2.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])]112tx2.vout = [CTxOut(int(value - self.relayfee * COIN), SCRIPT_W0_SH_OP_TRUE)]114assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.wallet.sendrawtransaction, from_node=self.nodes[0], tx_hex=tx2.serialize().hex())116# Setting the version back down to 1 should disable the sequence lock,117# so this should be accepted.118tx2.version = 1120self.wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=tx2.serialize().hex())122# Calculate the median time past of a prior block ("confirmations" before123# the current tip).124def get_median_time_past(self, confirmations):125block_hash = self.nodes[0].getblockhash(self.nodes[0].getblockcount()-confirmations)126return self.nodes[0].getblockheader(block_hash)["mediantime"]128# Test that sequence locks are respected for transactions spending confirmed inputs.129def test_sequence_lock_confirmed_inputs(self):130# Create lots of confirmed utxos, and use them to generate lots of random131# transactions.132max_outputs = 50133while len(self.wallet.get_utxos(include_immature_coinbase=False, mark_as_spent=False)) < 200:134num_outputs = random.randint(1, max_outputs)135self.wallet.send_self_transfer_multi(from_node=self.nodes[0], num_outputs=num_outputs)136self.generate(self.wallet, 1)138utxos = self.wallet.get_utxos(include_immature_coinbase=False)140# Try creating a lot of random transactions.141# Each time, choose a random number of inputs, and randomly set142# some of those inputs to be sequence locked (and randomly choose143# between height/time locking). Small random chance of making the locks144# all pass.145for _ in range(400):146available_utxos = len(utxos)148# Randomly choose up to 10 inputs149num_inputs = random.randint(1, min(10, available_utxos))150random.shuffle(utxos)152# Track whether any sequence locks used should fail153should_pass = True155# Track whether this transaction was built with sequence locks156using_sequence_locks = False158tx = CTransaction()159tx.version = 2160value = 0161for j in range(num_inputs):162sequence_value = 0xfffffffe # this disables sequence locks164# 50% chance we enable sequence locks165if random.randint(0,1):166using_sequence_locks = True168# 10% of the time, make the input sequence value pass169input_will_pass = (random.randint(1,10) == 1)170sequence_value = utxos[j]["confirmations"]171if not input_will_pass:172sequence_value += 1173should_pass = False175# Figure out what the median-time-past was for the confirmed input176# Note that if an input has N confirmations, we're going back N blocks177# from the tip so that we're looking up MTP of the block178# PRIOR to the one the input appears in, as per the BIP68 spec.179orig_time = self.get_median_time_past(utxos[j]["confirmations"])180cur_time = self.get_median_time_past(0) # MTP of the tip182# can only timelock this input if it's not too old -- otherwise use height183can_time_lock = True184if ((cur_time - orig_time) >> SEQUENCE_LOCKTIME_GRANULARITY) >= SEQUENCE_LOCKTIME_MASK:185can_time_lock = False187# if time-lockable, then 50% chance we make this a time lock188if random.randint(0,1) and can_time_lock:189# Find first time-lock value that fails, or latest one that succeeds190time_delta = sequence_value << SEQUENCE_LOCKTIME_GRANULARITY191if input_will_pass and time_delta > cur_time - orig_time:192sequence_value = ((cur_time - orig_time) >> SEQUENCE_LOCKTIME_GRANULARITY)193elif (not input_will_pass and time_delta <= cur_time - orig_time):194sequence_value = ((cur_time - orig_time) >> SEQUENCE_LOCKTIME_GRANULARITY)+1195sequence_value |= SEQUENCE_LOCKTIME_TYPE_FLAG196tx.vin.append(CTxIn(COutPoint(int(utxos[j]["txid"], 16), utxos[j]["vout"]), nSequence=sequence_value))197value += utxos[j]["value"]*COIN198# Overestimate the size of the tx - signatures should be less than 120 bytes, and leave 50 for the output199tx_size = len(tx.serialize().hex())//2 + 120*num_inputs + 50200tx.vout.append(CTxOut(int(value - self.relayfee * tx_size * COIN / 1000), self.wallet.get_output_script()))201self.wallet.sign_tx(tx=tx)203if (using_sequence_locks and not should_pass):204# This transaction should be rejected205assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.wallet.sendrawtransaction, from_node=self.nodes[0], tx_hex=tx.serialize().hex())206else:207# This raw transaction should be accepted208self.wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=tx.serialize().hex())209self.wallet.rescan_utxos()210utxos = self.wallet.get_utxos(include_immature_coinbase=False)212# Test that sequence locks on unconfirmed inputs must have nSequence213# height or time of 0 to be accepted.214# Then test that BIP68-invalid transactions are removed from the mempool215# after a reorg.216def test_sequence_lock_unconfirmed_inputs(self):217# Store height so we can easily reset the chain at the end of the test218cur_height = self.nodes[0].getblockcount()220# Create a mempool tx.221self.wallet.rescan_utxos()222tx1 = self.wallet.send_self_transfer(from_node=self.nodes[0])["tx"]224# Anyone-can-spend mempool tx.225# Sequence lock of 0 should pass.226tx2 = CTransaction()227tx2.version = 2228tx2.vin = [CTxIn(COutPoint(tx1.txid_int, 0), nSequence=0)]229tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee * COIN), SCRIPT_W0_SH_OP_TRUE)]230self.wallet.sign_tx(tx=tx2)231tx2_raw = tx2.serialize().hex()233self.wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=tx2_raw)235# Create a spend of the 0th output of orig_tx with a sequence lock236# of 1, and test what happens when submitting.237# orig_tx.vout[0] must be an anyone-can-spend output238def test_nonzero_locks(orig_tx, node, relayfee, use_height_lock):239sequence_value = 1240if not use_height_lock:241sequence_value |= SEQUENCE_LOCKTIME_TYPE_FLAG243tx = CTransaction()244tx.version = 2245tx.vin = [CTxIn(COutPoint(orig_tx.txid_int, 0), nSequence=sequence_value)]246tx.wit.vtxinwit = [CTxInWitness()]247tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])]248tx.vout = [CTxOut(int(orig_tx.vout[0].nValue - relayfee * COIN), SCRIPT_W0_SH_OP_TRUE)]250if (orig_tx.txid_hex in node.getrawmempool()):
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.