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 logic for skipping signature validation on old blocks.7Test logic for skipping signature validation on blocks which we've assumed8valid (https://github.com/bitcoin/bitcoin/pull/9484)10We build a chain that includes an invalid signature for one of the transactions:120: genesis block131: block 1 with coinbase transaction output.142-101: bury that block with 100 blocks so the coinbase transaction15output can be spent16102: a block containing a transaction spending the coinbase17transaction output. The transaction has an invalid signature.18103-2202: bury the bad block with just over two weeks' worth of blocks19(2100 blocks)21Start a few nodes:23- node0 has no -assumevalid parameter. Try to sync to block 2202. It will24reject block 102 and only sync as far as block 10125- node1 has -assumevalid set to the hash of block 102. Try to sync to26block 2202. node1 will sync all the way to block 2202.27- node2 has -assumevalid set to the hash of block 102. Try to sync to28block 200. node2 will reject block 102 since it's assumed valid, but it29isn't buried by at least two weeks' work.30- node3 has -assumevalid set to the hash of block 102. Feed a longer31competing headers-only branch so block #1 is not on the best header chain.32- node4 has -assumevalid set to the hash of block 102. Submit an alternative33block #1 that is not part of the assumevalid chain.34- node5 starts with no -assumevalid parameter. Reindex to hit35"assumevalid hash not in headers" and "below minimum chainwork".36"""38from test_framework.blocktools import (39COINBASE_MATURITY,40create_block,41create_coinbase,42)43from test_framework.messages import (44CBlockHeader,45COutPoint,46CTransaction,47CTxIn,48CTxOut,49msg_block,50msg_headers,51)52from test_framework.p2p import P2PInterface53from test_framework.script import (54CScript,55OP_TRUE,56)57from test_framework.test_framework import BitcoinTestFramework58from test_framework.util import assert_equal59from test_framework.wallet_util import generate_keypair62class BaseNode(P2PInterface):63def send_header_for_blocks(self, new_blocks):64headers_message = msg_headers()65headers_message.headers = [CBlockHeader(b) for b in new_blocks]66self.send_without_ping(headers_message)69class AssumeValidTest(BitcoinTestFramework):70def set_test_params(self):71self.setup_clean_chain = True72self.num_nodes = 673self.rpc_timeout = 12075def setup_network(self):76self.add_nodes(self.num_nodes)77# Start node0. We don't start the other nodes yet since78# we need to pre-mine a block with an invalid transaction79# signature so we can pass in the block hash as assumevalid.80self.start_node(0)82def run_test(self):83# Build the blockchain84self.tip = int(self.nodes[0].getbestblockhash(), 16)85self.block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] + 187self.blocks = []89# Get a pubkey for the coinbase TXO90_, coinbase_pubkey = generate_keypair()92# Create the first block with a coinbase output to our key93height = 194block = create_block(self.tip, create_coinbase(height, coinbase_pubkey), ntime=self.block_time)95self.blocks.append(block)96self.block_time += 197block.solve()98# Save the coinbase for later99self.block1 = block100self.tip = block.hash_int101height += 1103# Bury the block 100 deep so the coinbase output is spendable104for _ in range(100):105block = create_block(self.tip, height=height, ntime=self.block_time)106block.solve()107self.blocks.append(block)108self.tip = block.hash_int109self.block_time += 1110height += 1112# Create a transaction spending the coinbase output with an invalid (null) signature113tx = CTransaction()114tx.vin.append(CTxIn(COutPoint(self.block1.vtx[0].txid_int, 0), scriptSig=b""))115tx.vout.append(CTxOut(49 * 100000000, CScript([OP_TRUE])))117block102 = create_block(self.tip, height=height, ntime=self.block_time, txlist=[tx])118self.block_time += 1119block102.solve()120self.blocks.append(block102)121self.tip = block102.hash_int122self.block_time += 1123height += 1125# Bury the assumed valid block 2100 deep126for _ in range(2100):127block = create_block(self.tip, height=height, ntime=self.block_time)128block.solve()129self.blocks.append(block)130self.tip = block.hash_int131self.block_time += 1132height += 1133block_1_hash = self.blocks[0].hash_hex135self.start_node(1, extra_args=[f"-assumevalid={block102.hash_hex}"])136self.start_node(2, extra_args=[f"-assumevalid={block102.hash_hex}"])137self.start_node(3, extra_args=[f"-assumevalid={block102.hash_hex}"])138self.start_node(4, extra_args=[f"-assumevalid={block102.hash_hex}"])139self.start_node(5)141# nodes[0]142self.log.info("Send blocks to node0. Block 102 will be rejected.")143p2p0 = self.nodes[0].add_p2p_connection(BaseNode())144p2p0.send_header_for_blocks(self.blocks[0:2000])145p2p0.send_header_for_blocks(self.blocks[2000:])146with self.nodes[0].assert_debug_log(expected_msgs=[147f"Enabling script verification at block #1 ({block_1_hash}): assumevalid=0 (always verify).",148]):149p2p0.send_and_ping(msg_block(self.blocks[0]))150with self.nodes[0].assert_debug_log(expected_msgs=[151"Block validation error: block-script-verify-flag-failed",152]):153for i in range(1, 103):154p2p0.send_without_ping(msg_block(self.blocks[i]))155p2p0.wait_for_disconnect()156assert_equal(self.nodes[0].getblockcount(), COINBASE_MATURITY + 1)157assert_equal(next(filter(lambda x: x["hash"] == self.blocks[-1].hash_hex, self.nodes[0].getchaintips()))["status"], "invalid")159# nodes[1]160self.log.info("Send all blocks to node1. All blocks will be accepted.")161p2p1 = self.nodes[1].add_p2p_connection(BaseNode())162p2p1.send_header_for_blocks(self.blocks[0:2000])163p2p1.send_header_for_blocks(self.blocks[2000:])164with self.nodes[1].assert_debug_log(expected_msgs=[165f"Disabling script verification at block #1 ({self.blocks[0].hash_hex}).",166]):167p2p1.send_and_ping(msg_block(self.blocks[0]))168with self.nodes[1].assert_debug_log(expected_msgs=[169f"Enabling script verification at block #103 ({self.blocks[102].hash_hex}): block height above assumevalid height.",170]):171for i in range(1, 2202):172p2p1.send_without_ping(msg_block(self.blocks[i]))173# Syncing 2200 blocks can take a while on slow systems. Give it plenty of time to sync.174p2p1.sync_with_ping(timeout=960)175assert_equal(self.nodes[1].getblockcount(), 2202)177# nodes[2]178self.log.info("Send blocks to node2. Block 102 will be rejected.")179p2p2 = self.nodes[2].add_p2p_connection(BaseNode())180p2p2.send_header_for_blocks(self.blocks[0:200])181with self.nodes[2].assert_debug_log(expected_msgs=[182f"Enabling script verification at block #1 ({block_1_hash}): block too recent relative to best header.",183]):184p2p2.send_and_ping(msg_block(self.blocks[0]))185with self.nodes[2].assert_debug_log(expected_msgs=[186"Block validation error: block-script-verify-flag-failed",187]):188for i in range(1, 103):189p2p2.send_without_ping(msg_block(self.blocks[i]))190p2p2.wait_for_disconnect()191assert_equal(self.nodes[2].getblockcount(), COINBASE_MATURITY + 1)192assert_equal(next(filter(lambda x: x["hash"] == self.blocks[199].hash_hex, self.nodes[2].getchaintips()))["status"], "invalid")194# nodes[3]195self.log.info("Send two header chains, and a block not in the best header chain to node3.")196best_hash = self.nodes[3].getbestblockhash()197tip_block = self.nodes[3].getblock(best_hash)198second_chain_tip, second_chain_time, second_chain_height = int(best_hash, 16), tip_block["time"] + 1, tip_block["height"] + 1199second_chain = []200for _ in range(150):201block = create_block(second_chain_tip, height=second_chain_height, ntime=second_chain_time)202block.solve()203second_chain.append(block)204second_chain_tip, second_chain_time, second_chain_height = block.hash_int, second_chain_time + 1, second_chain_height + 1205p2p3 = self.nodes[3].add_p2p_connection(BaseNode())206p2p3.send_header_for_blocks(second_chain)207p2p3.send_header_for_blocks(self.blocks[0:103])208with self.nodes[3].assert_debug_log(expected_msgs=[209f"Enabling script verification at block #1 ({block_1_hash}): block not in best header chain.",210]):211p2p3.send_and_ping(msg_block(self.blocks[0]))212assert_equal(self.nodes[3].getblockcount(), 1)214# nodes[4]215self.log.info("Send a block not in the assumevalid header chain to node4.")216genesis_hash = self.nodes[4].getbestblockhash()217genesis_time = self.nodes[4].getblock(genesis_hash)['time']218alt1 = create_block(int(genesis_hash, 16), height=1, ntime=genesis_time + 2)219alt1.solve()220p2p4 = self.nodes[4].add_p2p_connection(BaseNode())221p2p4.send_header_for_blocks(self.blocks[0:103])222with self.nodes[4].assert_debug_log(expected_msgs=[223f"Enabling script verification at block #1 ({alt1.hash_hex}): block not in assumevalid chain.",224]):225p2p4.send_and_ping(msg_block(alt1))226assert_equal(self.nodes[4].getblockcount(), 1)228# nodes[5]229self.log.info("Reindex to hit specific assumevalid gates (no races with header downloads/chainwork during startup).")230p2p5 = self.nodes[5].add_p2p_connection(BaseNode())231p2p5.send_header_for_blocks(self.blocks[0:200])232p2p5.send_without_ping(msg_block(self.blocks[0]))233self.wait_until(lambda: self.nodes[5].getblockcount() == 1)234with self.nodes[5].assert_debug_log(expected_msgs=[235f"Enabling script verification at block #1 ({block_1_hash}): assumevalid hash not in headers.",236]):237self.restart_node(5, extra_args=["-reindex-chainstate", "-assumevalid=1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"])238assert_equal(self.nodes[5].getblockcount(), 1)239with self.nodes[5].assert_debug_log(expected_msgs=[240f"Enabling script verification at block #1 ({block_1_hash}): best header chainwork below minimumchainwork.",241]):242self.restart_node(5, extra_args=["-reindex-chainstate", f"-assumevalid={block102.hash_hex}", "-minimumchainwork=0xffff"])243assert_equal(self.nodes[5].getblockcount(), 1)246if __name__ == '__main__':247AssumeValidTest(__file__).main()
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.