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) 2017-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 the listsinceblock RPC."""7from test_framework.address import key_to_p2wpkh8from test_framework.blocktools import COINBASE_MATURITY9from test_framework.descriptors import descsum_create10from test_framework.test_framework import BitcoinTestFramework11from test_framework.messages import MAX_BIP125_RBF_SEQUENCE12from test_framework.util import (13assert_array_result,14assert_equal,15assert_raises_rpc_error,16wallet_importprivkey,17)18from test_framework.wallet_util import generate_keypair20from decimal import Decimal22class ListSinceBlockTest(BitcoinTestFramework):23def set_test_params(self):24self.num_nodes = 425self.setup_clean_chain = True26# whitelist peers to speed up tx relay / mempool sync27self.noban_tx_relay = True29def skip_test_if_missing_module(self):30self.skip_if_no_wallet()32def run_test(self):33# All nodes are in IBD from genesis, so they'll need the miner (node2) to be an outbound connection, or have34# only one connection. (See fPreferredDownload in net_processing)35self.connect_nodes(1, 2)36self.generate(self.nodes[2], COINBASE_MATURITY + 1)38self.test_no_blockhash()39self.test_invalid_blockhash()40self.test_reorg()41self.test_cant_read_block()42self.test_double_spend()43self.test_double_send()44self.double_spends_filtered()45self.test_targetconfirmations()46self.test_desc()47self.test_send_to_self()48self.test_op_return()49self.test_label()51def test_no_blockhash(self):52self.log.info("Test no blockhash")53txid = self.nodes[2].sendtoaddress(self.nodes[0].getnewaddress(), 1)54self.sync_all()55assert_array_result(self.nodes[0].listtransactions(), {"txid": txid}, {56"category": "receive",57"amount": 1,58"confirmations": 0,59"trusted": False,60})62blockhash, = self.generate(self.nodes[2], 1)63blockheight = self.nodes[2].getblockheader(blockhash)['height']65txs = self.nodes[0].listtransactions()66assert_array_result(txs, {"txid": txid}, {67"category": "receive",68"amount": 1,69"blockhash": blockhash,70"blockheight": blockheight,71"confirmations": 1,72})73assert_equal(len(txs), 1)74assert "trusted" not in txs[0]76assert_equal(77self.nodes[0].listsinceblock(),78{"lastblock": blockhash,79"removed": [],80"transactions": txs})81assert_equal(82self.nodes[0].listsinceblock(""),83{"lastblock": blockhash,84"removed": [],85"transactions": txs})87def test_invalid_blockhash(self):88self.log.info("Test invalid blockhash")89assert_raises_rpc_error(-5, "Block not found", self.nodes[0].listsinceblock,90"42759cde25462784395a337460bde75f58e73d3f08bd31fdc3507cbac856a2c4")91assert_raises_rpc_error(-5, "Block not found", self.nodes[0].listsinceblock,92"0000000000000000000000000000000000000000000000000000000000000000")93assert_raises_rpc_error(-8, "blockhash must be of length 64 (not 11, for 'invalid-hex')", self.nodes[0].listsinceblock,94"invalid-hex")95assert_raises_rpc_error(-8, "blockhash must be hexadecimal string (not 'Z000000000000000000000000000000000000000000000000000000000000000')", self.nodes[0].listsinceblock,96"Z000000000000000000000000000000000000000000000000000000000000000")98def test_targetconfirmations(self):99'''100This tests when the value of target_confirmations exceeds the number of101blocks in the main chain. In this case, the genesis block hash should be102given for the `lastblock` property. If target_confirmations is < 1, then103a -8 invalid parameter error is thrown.104'''105self.log.info("Test target_confirmations")106blockhash, = self.generate(self.nodes[2], 1)107blockheight = self.nodes[2].getblockheader(blockhash)['height']109assert_equal(110self.nodes[0].getblockhash(0),111self.nodes[0].listsinceblock(blockhash, blockheight + 1)['lastblock'])112assert_equal(113self.nodes[0].getblockhash(0),114self.nodes[0].listsinceblock(blockhash, blockheight + 1000)['lastblock'])115assert_raises_rpc_error(-8, "Invalid parameter",116self.nodes[0].listsinceblock, blockhash, 0)118def test_reorg(self):119'''120`listsinceblock` did not behave correctly when handed a block that was121no longer in the main chain:123ab0124/ \125aa1 [tx0] bb1126| |127aa2 bb2128| |129aa3 bb3130|131bb4133Consider a client that has only seen block `aa3` above. It asks the node134to `listsinceblock aa3`. But at some point prior the main chain switched135to the bb chain.137Previously: listsinceblock would find height=4 for block aa3 and compare138this to height=5 for the tip of the chain (bb4). It would then return139results restricted to bb3-bb4.141Now: listsinceblock finds the fork at ab0 and returns results in the142range bb1-bb4.144This test only checks that [tx0] is present.145'''146self.log.info("Test reorg")148# Split network into two149self.split_network()151# send to nodes[0] from nodes[2]152senttx = self.nodes[2].sendtoaddress(self.nodes[0].getnewaddress(), 1)154# generate on both sides155nodes1_last_blockhash = self.generate(self.nodes[1], 6, sync_fun=lambda: self.sync_all(self.nodes[:2]))[-1]156nodes2_first_blockhash = self.generate(self.nodes[2], 7, sync_fun=lambda: self.sync_all(self.nodes[2:]))[0]157self.log.debug("nodes[1] last blockhash = {}".format(nodes1_last_blockhash))158self.log.debug("nodes[2] first blockhash = {}".format(nodes2_first_blockhash))160self.join_network()162# listsinceblock(nodes1_last_blockhash) should now include tx as seen from nodes[0]163# and return the block height which listsinceblock now exposes since a5e7795.164transactions = self.nodes[0].listsinceblock(nodes1_last_blockhash)['transactions']165found = next(tx for tx in transactions if tx['txid'] == senttx)166assert_equal(found['blockheight'], self.nodes[0].getblockheader(nodes2_first_blockhash)['height'])168def test_cant_read_block(self):169self.log.info('Test the RPC error "Can\'t read block from disk"')171# Split network into two172self.split_network()174# generate on both sides175nodes1_last_blockhash = self.generate(self.nodes[1], 6, sync_fun=lambda: self.sync_all(self.nodes[:2]))[-1]176self.generate(self.nodes[2], 7, sync_fun=lambda: self.sync_all(self.nodes[2:]))[0]178self.join_network()180# Renaming the block file to induce unsuccessful block read181blk_dat = (self.nodes[0].blocks_path / "blk00000.dat")182blk_dat_moved = blk_dat.rename(self.nodes[0].blocks_path / "blk00000.dat.moved")183assert not blk_dat.exists()185# listsinceblock(nodes1_last_blockhash) should now fail as blocks are not accessible186assert_raises_rpc_error(-32603, "Can't read block from disk",187self.nodes[0].listsinceblock, nodes1_last_blockhash)189# Restoring block file190blk_dat_moved.rename(self.nodes[0].blocks_path / "blk00000.dat")191assert blk_dat.exists()193def test_double_spend(self):194'''195This tests the case where the same UTXO is spent twice on two separate196blocks as part of a reorg.198ab0199/ \200aa1 [tx1] bb1 [tx2]201| |202aa2 bb2203| |204aa3 bb3205|206bb4208Problematic case:2101. User 1 receives BTC in tx1 from utxo1 in block aa1.2112. User 2 receives BTC in tx2 from utxo1 (same) in block bb12123. User 1 sees 2 confirmations at block aa3.2134. Reorg into bb chain.2145. User 1 asks `listsinceblock aa3` and does not see that tx1 is now215invalidated.217Currently the solution to this is to detect that a reorg'd block is218asked for in listsinceblock, and to iterate back over existing blocks up219until the fork point, and to include all transactions that relate to the220node wallet.221'''222self.log.info("Test double spend")224self.sync_all()226# share utxo between nodes[1] and nodes[2]227privkey, pubkey = generate_keypair(wif=True)228address = key_to_p2wpkh(pubkey)229self.nodes[2].sendtoaddress(address, 10)230self.generate(self.nodes[2], 6)231wallet_importprivkey(self.nodes[2], privkey, "now")232utxos = self.nodes[2].listunspent()233utxo = [u for u in utxos if u["address"] == address][0]234wallet_importprivkey(self.nodes[1], privkey, "now")236# Split network into two237self.split_network()239# send from nodes[1] using utxo to nodes[0]240change = '%.8f' % (float(utxo['amount']) - 1.0003)241recipient_dict = {242self.nodes[0].getnewaddress(): 1,243self.nodes[1].getnewaddress(): change,244}245utxo_dicts = [{246'txid': utxo['txid'],247'vout': utxo['vout'],248}]249txid1 = self.nodes[1].sendrawtransaction(250self.nodes[1].signrawtransactionwithwallet(
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.