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.
251self.log.info(f"Created txid={txs[1]['txid']}: for broadcast with dependency in mempool + rebroadcast")252self.log.info(f"Created txid={txs[2]['txid']}: for broadcast with dependency not in mempool")253tx_originator.sendrawtransaction(hexstring=txs[0]["hex"], maxfeerate=0.1)255self.log.info("First private broadcast: waiting for the transaction to reach the recipient")256self.wait_until(lambda: len(tx_receiver.getrawmempool()) > 0)257self.log.info("First private broadcast: the recipient received the transaction")258far_observer.wait_for_tx(txs[0]["txid"])259self.log.info("First private broadcast: the recipient further relayed the transaction")261# One already checked above, check the other NUM_PRIVATE_BROADCAST_PER_TX - 1 broadcasts.262self.check_broadcasts("Basic", txs[0], NUM_PRIVATE_BROADCAST_PER_TX - 1, 0)264self.log.info("Resending the same transaction via RPC again (it is not in the mempool yet)")265ignoring_msg = f"Ignoring unnecessary request to schedule an already scheduled transaction: txid={txs[0]['txid']}, wtxid={txs[0]['wtxid']}"266with tx_originator.busy_wait_for_debug_log(expected_msgs=[ignoring_msg.encode()]):267tx_originator.sendrawtransaction(hexstring=txs[0]["hex"], maxfeerate=0)269self.log.info("Sending a malleated transaction with an invalid witness via RPC")270malleated_invalid = malleate_tx_to_invalid_witness(txs[0])271assert_raises_rpc_error(-26, "mempool-script-verify-flag-failed",272tx_originator.sendrawtransaction,273hexstring=malleated_invalid.serialize_with_witness().hex(),274maxfeerate=0.1)276self.log.info("Checking that the transaction is not in the originator node's mempool")277assert_equal(len(tx_originator.getrawmempool()), 0)279wtxid_int = int(txs[0]["wtxid"], 16)280inv = CInv(MSG_WTX, wtxid_int)282tx_returner = None # First outbound-full-relay, will be P2PDataStore.283other_peer = None # Any other outbound-full-relay, we use the second one.285def set_tx_returner_and_other():286nonlocal tx_returner287nonlocal other_peer288tx_returner = None289other_peer = None290with self.destinations_lock:291for dest in self.destinations:292if dest["conn_type"] == "outbound-full-relay" and dest["node"] is not None:293if tx_returner is None:294assert(type(dest["node"]) is P2PDataStore)295tx_returner = dest["node"]296else:297assert(type(dest["node"]) is P2PInterface)298other_peer = dest["node"]299return True300return False302self.wait_until(set_tx_returner_and_other)304tx_returner.wait_for_connect()305other_peer.wait_for_connect()307self.log.info("Sending INV and waiting for GETDATA from node")308tx_returner.tx_store[wtxid_int] = txs[0]["tx"]309assert "getdata" not in tx_returner.last_message310received_back_msg = f"Received our privately broadcast transaction (txid={txs[0]['txid']}) from the network"311with tx_originator.assert_debug_log(expected_msgs=[received_back_msg]):312tx_returner.send_without_ping(msg_inv([inv]))313tx_returner.wait_until(lambda: "getdata" in tx_returner.last_message)314self.wait_until(lambda: len(tx_originator.getrawmempool()) > 0)316self.log.info("Waiting for normal broadcast to another peer")317other_peer.wait_for_inv([inv])319self.log.info("Checking getprivatebroadcastinfo no longer reports the transaction after it is received back")320pbinfo = tx_originator.getprivatebroadcastinfo()321pending = [t for t in pbinfo["transactions"] if t["txid"] == txs[0]["txid"] and t["wtxid"] == txs[0]["wtxid"]]322assert_equal(len(pending), 0)324self.log.info("Sending a transaction that is already in the mempool")325skip_destinations = len(self.destinations)326tx_originator.sendrawtransaction(hexstring=txs[0]["hex"], maxfeerate=0)327self.check_broadcasts("Broadcast of mempool transaction", txs[0], NUM_PRIVATE_BROADCAST_PER_TX, skip_destinations)329self.log.info("Sending a transaction with a dependency in the mempool")330skip_destinations = len(self.destinations)331tx_originator.sendrawtransaction(hexstring=txs[1]["hex"], maxfeerate=0.1)332self.check_broadcasts("Dependency in mempool", txs[1], NUM_PRIVATE_BROADCAST_PER_TX, skip_destinations)334self.log.info("Sending a transaction with a dependency not in the mempool (should be rejected)")335assert_equal(len(tx_originator.getrawmempool()), 1)336assert_raises_rpc_error(-25, "bad-txns-inputs-missingorspent",337tx_originator.sendrawtransaction, hexstring=txs[2]["hex"], maxfeerate=0.1)338assert_raises_rpc_error(-25, "bad-txns-inputs-missingorspent",339tx_originator.sendrawtransaction, hexstring=txs[2]["hex"], maxfeerate=0)341# Since txs[1] has not been received back by tx_originator,342# it should be re-broadcast after a while. Advance tx_originator's clock343# to trigger a re-broadcast. Should be more than the maximum returned by344# NextTxBroadcast() in net_processing.cpp.345self.log.info("Checking that rebroadcast works")346delta = 20 * 60 # 20min347skip_destinations = len(self.destinations)348rebroadcast_msg = f"Reattempting broadcast of stale txid={txs[1]['txid']}"349with tx_originator.busy_wait_for_debug_log(expected_msgs=[rebroadcast_msg.encode()]):350tx_originator.setmocktime(int(time.time()) + delta)351tx_originator.mockscheduler(delta)352self.check_broadcasts("Rebroadcast", txs[1], 1, skip_destinations)353tx_originator.setmocktime(0) # Let the clock tick again (it will go backwards due to this).355self.log.info("Sending a pair of transactions with the same txid but different valid wtxids via RPC")356parent = wallet.create_self_transfer()["tx"]357parent_amount = parent.vout[0].nValue - 10000358child_amount = parent_amount - 10000359siblings_parent, sibling1, sibling2 = build_malleated_tx_package(360parent=parent,361rebalance_parent_output_amount=parent_amount,362child_amount=child_amount)363self.log.info(f" - sibling1: txid={sibling1.txid_hex}, wtxid={sibling1.wtxid_hex}")364self.log.info(f" - sibling2: txid={sibling2.txid_hex}, wtxid={sibling2.wtxid_hex}")365assert_equal(sibling1.txid_hex, sibling2.txid_hex)366assert_not_equal(sibling1.wtxid_hex, sibling2.wtxid_hex)367assert_equal(len(tx_originator.getrawmempool()), 1)368tx_returner.send_without_ping(msg_tx(siblings_parent))369self.wait_until(lambda: len(tx_originator.getrawmempool()) > 1)370self.log.info(" - siblings' parent added to the mempool")371tx_originator.sendrawtransaction(hexstring=sibling1.serialize_with_witness().hex(), maxfeerate=0.1)372self.log.info(" - sent sibling1: ok")373tx_originator.sendrawtransaction(hexstring=sibling2.serialize_with_witness().hex(), maxfeerate=0.1)374self.log.info(" - sent sibling2: ok")376self.log.info("Checking abortprivatebroadcast removes a pending private-broadcast transaction")377tx_abort = wallet.create_self_transfer()378tx_originator.sendrawtransaction(hexstring=tx_abort["hex"], maxfeerate=0.1)379assert tx_abort["wtxid"] in [t["wtxid"] for t in tx_originator.getprivatebroadcastinfo()["transactions"]]380abort_res = tx_originator.abortprivatebroadcast(tx_abort["txid"])381assert_equal(len(abort_res["removed_transactions"]), 1)382assert_equal(abort_res["removed_transactions"][0]["txid"], tx_abort["txid"])383assert_equal(abort_res["removed_transactions"][0]["wtxid"], tx_abort["wtxid"])384assert_equal(abort_res["removed_transactions"][0]["hex"].lower(), tx_abort["hex"].lower())385assert all(t["wtxid"] != tx_abort["wtxid"] for t in tx_originator.getprivatebroadcastinfo()["transactions"])387self.log.info("Checking abortprivatebroadcast fails for non-existent transaction")388assert_raises_rpc_error(389-5,390"Transaction not in private broadcast queue",391tx_originator.abortprivatebroadcast,392"0" * 64,393)395self.log.info("Checking that a private broadcast destination signaling relay=false gets disconnected")396tx_no_relay = wallet.create_self_transfer()397disconnect_msg = "Disconnecting: does not support transaction relay (connected in vain)"398with tx_originator.assert_debug_log(expected_msgs=[disconnect_msg]):399with self.destinations_lock:400self.no_relay_peer = None401self.trigger_no_relay_peer = True402tx_originator.sendrawtransaction(hexstring=tx_no_relay["hex"], maxfeerate=0.1)403self.wait_until(lambda: self.no_relay_peer is not None)404self.no_relay_peer.wait_until(lambda: self.no_relay_peer.message_count["version"] == 1, check_connected=False)405self.no_relay_peer.wait_for_disconnect()406assert_equal(self.no_relay_peer.message_count, {"version": 1})408# Stop the SOCKS5 proxy server to avoid it being upset by the bitcoin409# node disconnecting in the middle of the SOCKS5 handshake when we410# restart below.411self.socks5_server.stop()413self.log.info("Trying to send a transaction when none of Tor or I2P is reachable")414self.restart_node(0, extra_args=[415"-privatebroadcast",416"-v2transport=0",417# A location where definitely a Tor control is not listening. This would allow418# Bitcoin Core to start, hoping/assuming that the location of the Tor proxy419# may be retrieved after startup from the Tor control, but it will not be, so420# the RPC should throw.421"-torcontrol=127.0.0.1:1",422"-listenonion",423])424assert_raises_rpc_error(-1, "none of the Tor or I2P networks is reachable",425tx_originator.sendrawtransaction, hexstring=txs[0]["hex"], maxfeerate=0.1)428if __name__ == "__main__":429P2PPrivateBroadcast(__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.