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) 2015-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 CSV soft fork activation.7This soft fork will activate the following BIPS:8BIP 68 - nSequence relative lock times9BIP 112 - CHECKSEQUENCEVERIFY10BIP 113 - MedianTimePast semantics for nLockTime12mine 83 blocks whose coinbases will be used to generate inputs for our tests13mine 344 blocks and seed block chain with the 83 inputs used for our tests at height 42714mine 2 blocks and verify soft fork not yet activated15mine 1 block and test that soft fork is activated (rules enforced for next block)16Test BIP 113 is enforced17Mine 4 blocks so next height is 580 and test BIP 68 is enforced for time and height18Mine 1 block so next height is 581 and test BIP 68 now passes time but not height19Mine 1 block so next height is 582 and test BIP 68 now passes time and height20Test that BIP 112 is enforced22Various transactions will be used to test that the BIPs rules are not enforced before the soft fork activates23And that after the soft fork activates transactions pass and fail as they should according to the rules.24For each BIP, transactions of versions 1 and 2 will be tested.25----------------26BIP 113:27bip113tx - modify the nLocktime variable29BIP 68:30bip68txs - 16 txs with nSequence relative locktime of 10 with various bits set as per the relative_locktimes below32BIP 112:33bip112txs_vary_nSequence - 16 txs with nSequence relative_locktimes of 10 evaluated against 10 OP_CSV OP_DROP34bip112txs_vary_nSequence_9 - 16 txs with nSequence relative_locktimes of 9 evaluated against 10 OP_CSV OP_DROP35bip112txs_vary_OP_CSV - 16 txs with nSequence = 10 evaluated against varying {relative_locktimes of 10} OP_CSV OP_DROP36bip112txs_vary_OP_CSV_9 - 16 txs with nSequence = 9 evaluated against varying {relative_locktimes of 10} OP_CSV OP_DROP37bip112tx_special - test negative argument to OP_CSV38bip112tx_emptystack - test empty stack (= no argument) OP_CSV39"""40from itertools import product41import time43from test_framework.blocktools import (44create_block,45)46from test_framework.p2p import P2PDataStore47from test_framework.script import (48CScript,49OP_CHECKSEQUENCEVERIFY,50OP_DROP,51)52from test_framework.test_framework import BitcoinTestFramework53from test_framework.util import (54assert_equal,55softfork_active,56)57from test_framework.wallet import (58MiniWallet,59MiniWalletMode,60)62TESTING_TX_COUNT = 83 # Number of testing transactions: 1 BIP113 tx, 16 BIP68 txs, 66 BIP112 txs (see comments above)63COINBASE_BLOCK_COUNT = TESTING_TX_COUNT # Number of coinbase blocks we need to generate as inputs for our txs64BASE_RELATIVE_LOCKTIME = 1065SEQ_DISABLE_FLAG = 1 << 3166SEQ_RANDOM_HIGH_BIT = 1 << 2567SEQ_TYPE_FLAG = 1 << 2268SEQ_RANDOM_LOW_BIT = 1 << 1871def relative_locktime(sdf, srhb, stf, srlb):72"""Returns a locktime with certain bits set."""74locktime = BASE_RELATIVE_LOCKTIME75if sdf:76locktime |= SEQ_DISABLE_FLAG77if srhb:78locktime |= SEQ_RANDOM_HIGH_BIT79if stf:80locktime |= SEQ_TYPE_FLAG81if srlb:82locktime |= SEQ_RANDOM_LOW_BIT83return locktime86def all_rlt_txs(txs):87return [tx['tx'] for tx in txs]90CSV_ACTIVATION_HEIGHT = 43293class BIP68_112_113Test(BitcoinTestFramework):94def set_test_params(self):95self.num_nodes = 196self.setup_clean_chain = True97# whitelist peers to speed up tx relay / mempool sync98self.noban_tx_relay = True99self.extra_args = [[100f'-testactivationheight=csv@{CSV_ACTIVATION_HEIGHT}',101]]103def create_self_transfer_from_utxo(self, input_tx):104utxo = self.miniwallet.get_utxo(txid=input_tx.txid_hex, mark_as_spent=False)105tx = self.miniwallet.create_self_transfer(utxo_to_spend=utxo)['tx']106return tx108def create_bip112special(self, input, txversion):109tx = self.create_self_transfer_from_utxo(input)110tx.version = txversion111self.miniwallet.sign_tx(tx)112tx.vin[0].scriptSig = CScript([-1, OP_CHECKSEQUENCEVERIFY, OP_DROP] + list(CScript(tx.vin[0].scriptSig)))113return tx115def create_bip112emptystack(self, input, txversion):116tx = self.create_self_transfer_from_utxo(input)117tx.version = txversion118self.miniwallet.sign_tx(tx)119tx.vin[0].scriptSig = CScript([OP_CHECKSEQUENCEVERIFY] + list(CScript(tx.vin[0].scriptSig)))120return tx122def send_generic_input_tx(self, coinbases):123input_txid = self.nodes[0].getblock(coinbases.pop(), 2)['tx'][0]['txid']124utxo_to_spend = self.miniwallet.get_utxo(txid=input_txid)125return self.miniwallet.send_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_to_spend)['tx']127def create_bip68txs(self, bip68inputs, txversion, locktime_delta=0):128"""Returns a list of bip68 transactions with different bits set."""129txs = []130assert len(bip68inputs) >= 16131for i, (sdf, srhb, stf, srlb) in enumerate(product(*[[True, False]] * 4)):132locktime = relative_locktime(sdf, srhb, stf, srlb)133tx = self.create_self_transfer_from_utxo(bip68inputs[i])134tx.version = txversion135tx.vin[0].nSequence = locktime + locktime_delta136self.miniwallet.sign_tx(tx)137txs.append({'tx': tx, 'sdf': sdf, 'stf': stf})139return txs141def create_bip112txs(self, bip112inputs, varyOP_CSV, txversion, locktime_delta=0):142"""Returns a list of bip68 transactions with different bits set."""143txs = []144assert len(bip112inputs) >= 16145for i, (sdf, srhb, stf, srlb) in enumerate(product(*[[True, False]] * 4)):146locktime = relative_locktime(sdf, srhb, stf, srlb)147tx = self.create_self_transfer_from_utxo(bip112inputs[i])148if varyOP_CSV: # if varying OP_CSV, nSequence is fixed149tx.vin[0].nSequence = BASE_RELATIVE_LOCKTIME + locktime_delta150else: # vary nSequence instead, OP_CSV is fixed151tx.vin[0].nSequence = locktime + locktime_delta152tx.version = txversion153self.miniwallet.sign_tx(tx)154if varyOP_CSV:155tx.vin[0].scriptSig = CScript([locktime, OP_CHECKSEQUENCEVERIFY, OP_DROP] + list(CScript(tx.vin[0].scriptSig)))156else:157tx.vin[0].scriptSig = CScript([BASE_RELATIVE_LOCKTIME, OP_CHECKSEQUENCEVERIFY, OP_DROP] + list(CScript(tx.vin[0].scriptSig)))158txs.append({'tx': tx, 'sdf': sdf, 'stf': stf})159return txs161def generate_blocks(self, number):162test_blocks = []163for _ in range(number):164block = self.create_test_block([])165test_blocks.append(block)166self.last_block_time += 600167self.tip = block.hash_int168self.tipheight += 1169return test_blocks171def create_test_block(self, txs):172block = create_block(self.tip, height=self.tipheight + 1, ntime=self.last_block_time + 600, txlist=txs)173block.solve()174return block176def send_blocks(self, blocks, success=True, reject_reason=None):177"""Sends blocks to test node. Syncs and verifies that tip has advanced to most recent block.179Call with success = False if the tip shouldn't advance to the most recent block."""180self.helper_peer.send_blocks_and_test(blocks, self.nodes[0], success=success, reject_reason=reject_reason)182def run_test(self):183self.helper_peer = self.nodes[0].add_p2p_connection(P2PDataStore())184self.miniwallet = MiniWallet(self.nodes[0], mode=MiniWalletMode.RAW_P2PK)186self.log.info("Generate blocks in the past for coinbase outputs.")187long_past_time = int(time.time()) - 600 * 1000 # enough to build up to 1000 blocks 10 minutes apart without worrying about getting into the future188self.nodes[0].setmocktime(long_past_time - 100) # enough so that the generated blocks will still all be before long_past_time189self.coinbase_blocks = self.generate(self.miniwallet, COINBASE_BLOCK_COUNT) # blocks generated for inputs190self.nodes[0].setmocktime(0) # set time back to present so yielded blocks aren't in the future as we advance last_block_time191self.tipheight = COINBASE_BLOCK_COUNT # height of the next block to build192self.last_block_time = long_past_time193self.tip = int(self.nodes[0].getbestblockhash(), 16)195# Activation height is hardcoded196# We advance to block height five below BIP112 activation for the following tests197test_blocks = self.generate_blocks(CSV_ACTIVATION_HEIGHT - 5 - COINBASE_BLOCK_COUNT)198self.send_blocks(test_blocks)199assert not softfork_active(self.nodes[0], 'csv')201# Inputs at height = 431202#203# Put inputs for all tests in the chain at height 431 (tip now = 430) (time increases by 600s per block)204# Note we reuse inputs for v1 and v2 txs so must test these separately205# 16 normal inputs206bip68inputs = []207for _ in range(16):208bip68inputs.append(self.send_generic_input_tx(self.coinbase_blocks))210# 2 sets of 16 inputs with 10 OP_CSV OP_DROP (actually will be prepended to spending scriptSig)211bip112basicinputs = []212for _ in range(2):213inputs = []214for _ in range(16):215inputs.append(self.send_generic_input_tx(self.coinbase_blocks))216bip112basicinputs.append(inputs)218# 2 sets of 16 varied inputs with (relative_lock_time) OP_CSV OP_DROP (actually will be prepended to spending scriptSig)219bip112diverseinputs = []220for _ in range(2):221inputs = []222for _ in range(16):223inputs.append(self.send_generic_input_tx(self.coinbase_blocks))224bip112diverseinputs.append(inputs)226# 1 special input with -1 OP_CSV OP_DROP (actually will be prepended to spending scriptSig)227bip112specialinput = self.send_generic_input_tx(self.coinbase_blocks)228# 1 special input with (empty stack) OP_CSV (actually will be prepended to spending scriptSig)229bip112emptystackinput = self.send_generic_input_tx(self.coinbase_blocks)231# 1 normal input232bip113input = self.send_generic_input_tx(self.coinbase_blocks)234self.nodes[0].setmocktime(self.last_block_time + 600)235inputblockhash = self.generate(self.nodes[0], 1)[0] # 1 block generated for inputs to be in chain at height 431236self.nodes[0].setmocktime(0)237self.tip = int(inputblockhash, 16)238self.tipheight += 1239self.last_block_time += 600240assert_equal(len(self.nodes[0].getblock(inputblockhash, True)["tx"]), TESTING_TX_COUNT + 1)242# 2 more version 4 blocks243test_blocks = self.generate_blocks(2)244self.send_blocks(test_blocks)246assert_equal(self.tipheight, CSV_ACTIVATION_HEIGHT - 2)247self.log.info(f"Height = {self.tipheight}, CSV not yet active (will activate for block {CSV_ACTIVATION_HEIGHT}, not {CSV_ACTIVATION_HEIGHT - 1})")248assert not softfork_active(self.nodes[0], 'csv')250# Test both version 1 and version 2 transactions for all tests
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.