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 transaction upload"""7from test_framework.messages import msg_getdata, CInv, MSG_TX, MSG_WTX8from test_framework.p2p import p2p_lock, P2PDataStore, P2PTxInvStore9from test_framework.test_framework import BitcoinTestFramework10from test_framework.util import (11assert_equal,12)13from test_framework.wallet import MiniWallet15import time17class P2PNode(P2PDataStore):18def on_inv(self, message):19pass22class P2PLeakTxTest(BitcoinTestFramework):23def set_test_params(self):24self.num_nodes = 126def run_test(self):27self.gen_node = self.nodes[0] # The block and tx generating node28self.miniwallet = MiniWallet(self.gen_node)29self.mocktime = int(time.time())31self.test_tx_in_block()32self.test_notfound_on_replaced_tx()33self.test_notfound_on_unannounced_tx()35def test_tx_in_block(self):36self.log.info("Check that a transaction in the last block is uploaded (beneficial for compact block relay)")37self.gen_node.setmocktime(self.mocktime)38inbound_peer = self.gen_node.add_p2p_connection(P2PNode())40self.log.debug("Generate transaction and block")41inbound_peer.last_message.pop("inv", None)43wtxid = self.miniwallet.send_self_transfer(from_node=self.gen_node)["wtxid"]44rawmp = self.gen_node.getrawmempool(False, True)45pi = self.gen_node.getpeerinfo()[0]46assert_equal(rawmp["mempool_sequence"], 2) # our tx cause mempool activity47assert_equal(pi["last_inv_sequence"], 1) # that is after the last inv48assert_equal(pi["inv_to_send"], 1) # and our tx has been queued49self.mocktime += 12050self.gen_node.setmocktime(self.mocktime)51inbound_peer.wait_until(lambda: "inv" in inbound_peer.last_message and inbound_peer.last_message.get("inv").inv[0].hash == int(wtxid, 16))53rawmp = self.gen_node.getrawmempool(False, True)54pi = self.gen_node.getpeerinfo()[0]55assert_equal(rawmp["mempool_sequence"], 2) # no mempool update56assert_equal(pi["last_inv_sequence"], 2) # announced the current mempool57assert_equal(pi["inv_to_send"], 0) # nothing left in the queue59want_tx = msg_getdata(inv=inbound_peer.last_message.get("inv").inv)60self.generate(self.gen_node, 1)62self.log.debug("Request transaction")63inbound_peer.last_message.pop("tx", None)64inbound_peer.send_and_ping(want_tx)65assert_equal(inbound_peer.last_message.get("tx").tx.wtxid_hex, wtxid)67def test_notfound_on_replaced_tx(self):68self.gen_node.disconnect_p2ps()69self.gen_node.setmocktime(self.mocktime)70inbound_peer = self.gen_node.add_p2p_connection(P2PTxInvStore())72self.log.info("Transaction tx_a is broadcast")73tx_a = self.miniwallet.send_self_transfer(from_node=self.gen_node)74self.mocktime += 12075self.gen_node.setmocktime(self.mocktime)76inbound_peer.wait_for_broadcast(txns=[tx_a["wtxid"]])78tx_b = tx_a["tx"]79tx_b.vout[0].nValue -= 900080self.gen_node.sendrawtransaction(tx_b.serialize().hex())81self.mocktime += 12082self.gen_node.setmocktime(self.mocktime)83inbound_peer.wait_until(lambda: "tx" in inbound_peer.last_message and inbound_peer.last_message.get("tx").tx.wtxid_hex == tx_b.wtxid_hex)85self.log.info("Re-request of tx_a after replacement is answered with notfound")86req_vec = [87CInv(t=MSG_TX, h=int(tx_a["txid"], 16)),88CInv(t=MSG_WTX, h=int(tx_a["wtxid"], 16)),89]90want_tx = msg_getdata()91want_tx.inv = req_vec92with p2p_lock:93inbound_peer.last_message.pop("notfound", None)94inbound_peer.last_message.pop("tx", None)95inbound_peer.send_and_ping(want_tx)97assert_equal(inbound_peer.last_message.get("notfound").vec, req_vec)98assert "tx" not in inbound_peer.last_message100def test_notfound_on_unannounced_tx(self):101self.log.info("Check that we don't leak txs to inbound peers that we haven't yet announced to")102self.gen_node.disconnect_p2ps()103inbound_peer = self.gen_node.add_p2p_connection(P2PNode()) # An "attacking" inbound peer105# Set a mock time so that time does not pass, and gen_node never announces the transaction106self.gen_node.setmocktime(self.mocktime)107wtxid = int(self.miniwallet.send_self_transfer(from_node=self.gen_node)["wtxid"], 16)109want_tx = msg_getdata()110want_tx.inv.append(CInv(t=MSG_WTX, h=wtxid))111with p2p_lock:112inbound_peer.last_message.pop('notfound', None)113inbound_peer.send_and_ping(want_tx)114inbound_peer.wait_until(lambda: "notfound" in inbound_peer.last_message)115with p2p_lock:116assert_equal(inbound_peer.last_message.get("notfound").vec[0].hash, wtxid)117inbound_peer.last_message.pop('notfound')119# Move mocktime forward and wait for the announcement.120inbound_peer.last_message.pop('inv', None)121self.mocktime += 120122self.gen_node.setmocktime(self.mocktime)123inbound_peer.wait_for_inv([CInv(t=MSG_WTX, h=wtxid)], timeout=120)125# Send the getdata again, this time the node should send us a TX message.126inbound_peer.last_message.pop('tx', None)127inbound_peer.send_and_ping(want_tx)128self.wait_until(lambda: "tx" in inbound_peer.last_message)129assert_equal(wtxid, int(inbound_peer.last_message["tx"].tx.wtxid_hex, 16))132if __name__ == '__main__':133P2PLeakTxTest(__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.