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.restart_node(0)252self.nodes[0].setmocktime(int(time.time()))254# Peer that is immediately asked, but never responds.255# This will set us up to have two ready requests, one256# of which is preferred and one which is not257unresponsive_peer = self.nodes[0].add_outbound_p2p_connection(258TestP2PConn(), wait_for_verack=True, p2p_idx=0, connection_type="outbound-full-relay")259unresponsive_peer.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=0xff00ff00)]))260unresponsive_peer.wait_until(lambda: unresponsive_peer.tx_getdata_count >= 1, timeout=1)262# A bunch of incoming (non-preferred) connections that advertise the same tx263non_pref_peers = []264NUM_INBOUND = 10265for _ in range(NUM_INBOUND):266non_pref_peers.append(self.nodes[0].add_p2p_connection(TestP2PConn()))267non_pref_peers[-1].send_and_ping(msg_inv([CInv(t=MSG_WTX, h=0xff00ff00)]))269# Check that no request made due to in-flight270self.nodes[0].bumpmocktime(NONPREF_PEER_TX_DELAY)271with p2p_lock:272for peer in non_pref_peers:273assert_equal(peer.tx_getdata_count, 0)275# Now add another outbound (preferred) which is immediately ready for consideration276# upon advertisement277pref_peer = self.nodes[0].add_outbound_p2p_connection(278TestP2PConn(), wait_for_verack=True, p2p_idx=1, connection_type="outbound-full-relay")279pref_peer.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=0xff00ff00)]))281assert_equal(len(self.nodes[0].getpeerinfo()), NUM_INBOUND + 2)283# Still have to wait for in-flight to timeout284with p2p_lock:285assert_equal(pref_peer.tx_getdata_count, 0)287# Timeout in-flight288self.nodes[0].bumpmocktime(GETDATA_TX_INTERVAL - NONPREF_PEER_TX_DELAY)290# Preferred peers are *always* selected next if ready291pref_peer.wait_until(lambda: pref_peer.tx_getdata_count >= 1, timeout=10)293# And none for non-preferred294for non_pref_peer in non_pref_peers:295with p2p_lock:296assert_equal(non_pref_peer.tx_getdata_count, 0)298def test_txid_inv_delay(self, glob_wtxid=False):299self.log.info('Check that inv from a txid-relay peers are delayed by {} s, with a wtxid peer {}'.format(TXID_RELAY_DELAY, glob_wtxid))300self.restart_node(0, extra_args=['[email protected]'])301mock_time = int(time.time() + 1)302self.nodes[0].setmocktime(mock_time)303peer = self.nodes[0].add_p2p_connection(TestP2PConn(wtxidrelay=False))304if glob_wtxid:305# Add a second wtxid-relay connection otherwise TXID_RELAY_DELAY is waived in306# lack of wtxid-relay peers307self.nodes[0].add_p2p_connection(TestP2PConn(wtxidrelay=True))308peer.send_and_ping(msg_inv([CInv(t=MSG_TX, h=0xff11ff11)]))309with p2p_lock:310assert_equal(peer.tx_getdata_count, 0 if glob_wtxid else 1)311self.nodes[0].setmocktime(mock_time + TXID_RELAY_DELAY)312peer.wait_until(lambda: peer.tx_getdata_count >= 1, timeout=1)314def test_large_inv_batch(self):315self.log.info('Test how large inv batches are handled with relay permission')316self.restart_node(0, extra_args=['[email protected]'])317peer = self.nodes[0].add_p2p_connection(TestP2PConn())318peer.send_without_ping(msg_inv([CInv(t=MSG_WTX, h=wtxid) for wtxid in range(MAX_PEER_TX_ANNOUNCEMENTS + 1)]))319peer.wait_until(lambda: peer.tx_getdata_count == MAX_PEER_TX_ANNOUNCEMENTS + 1)321self.log.info('Test how large inv batches are handled without relay permission')322self.restart_node(0)323peer = self.nodes[0].add_p2p_connection(TestP2PConn())324peer.send_without_ping(msg_inv([CInv(t=MSG_WTX, h=wtxid) for wtxid in range(MAX_PEER_TX_ANNOUNCEMENTS + 1)]))325peer.wait_until(lambda: peer.tx_getdata_count == MAX_PEER_TX_ANNOUNCEMENTS)326peer.sync_with_ping()328def test_duplicate_tx_inv(self):329self.log.info('Check that duplicate transaction identifiers in one inv message are processed once')330node = self.nodes[0]331node.logging(include=['net'])333def send_invs_and_read_log(peer, invs):334log_start = node.debug_log_size(encoding='utf-8')335peer.send_and_ping(msg_inv(invs))336with open(node.debug_log_path, encoding='utf-8', errors='replace') as debug_log:337debug_log.seek(log_start)338return debug_log.read()340for wtxidrelay, inv_type, inv_name, mismatched_type, mismatched_name, hash_a, hash_b in [341(False, MSG_TX, 'tx', MSG_WTX, 'wtx', 0xaabbcc, 0xddeeff),342(True, MSG_WTX, 'wtx', MSG_TX, 'tx', 0x112233, 0x445566),343]:344peer = node.add_p2p_connection(TestP2PConn(wtxidrelay=wtxidrelay))345inv_a_log = f"got inv: {inv_name} {hash_a:064x}"346inv_b_log = f"got inv: {inv_name} {hash_b:064x}"347mismatched_inv_log = f"got inv: {mismatched_name} {hash_a:064x}"349log = send_invs_and_read_log(peer, [350CInv(t=mismatched_type, h=hash_a),351CInv(t=inv_type, h=hash_a),352CInv(t=inv_type, h=hash_b),353CInv(t=inv_type, h=hash_a),354CInv(t=inv_type, h=hash_b),355])356assert_equal(log.count(inv_a_log), 1)357assert_equal(log.count(inv_b_log), 1)358assert_equal(log.count(mismatched_inv_log), 0)360# The duplicate filter is scoped to a single INV message.361log = send_invs_and_read_log(peer, [362CInv(t=inv_type, h=hash_a),363CInv(t=inv_type, h=hash_a),364])365assert_equal(log.count(inv_a_log), 1)366assert_equal(log.count(inv_b_log), 0)368self.log.info('Check that MSG_TX and MSG_WITNESS_TX are deduplicated as txids')369peer = node.add_p2p_connection(TestP2PConn(wtxidrelay=False))370for first_type, first_name, second_type, second_name, hash_a in [371(MSG_TX, 'tx', MSG_WITNESS_TX, 'witness-tx', 0x667788),372(MSG_WITNESS_TX, 'witness-tx', MSG_TX, 'tx', 0x778899),373]:374log = send_invs_and_read_log(peer, [375CInv(t=first_type, h=hash_a),376CInv(t=second_type, h=hash_a),377])378assert_equal(log.count(f"got inv: {first_name} {hash_a:064x}"), 1)379assert_equal(log.count(f"got inv: {second_name} {hash_a:064x}"), 0)381self.log.info('Check that txids and wtxids are deduplicated separately')382peer = node.add_p2p_connection(TestP2PConn(wtxidrelay=True))383for first_type, second_type, hash_a in [384(MSG_WITNESS_TX, MSG_WTX, 0x8899aa),385(MSG_WTX, MSG_WITNESS_TX, 0x99aabb),386]:387log = send_invs_and_read_log(peer, [388CInv(t=first_type, h=hash_a),389CInv(t=second_type, h=hash_a),390])391assert_equal(log.count(f"got inv: witness-tx {hash_a:064x}"), 1)392assert_equal(log.count(f"got inv: wtx {hash_a:064x}"), 1)394def test_spurious_notfound(self):395self.log.info('Check that spurious notfound is ignored')396self.nodes[0].p2ps[0].send_without_ping(msg_notfound(vec=[CInv(MSG_TX, 1)]))398def test_rejects_filter_reset(self):399self.log.info('Check that rejected tx is not requested again')400node = self.nodes[0]401fill_mempool(self, node, tx_sync_fun=self.no_op)402self.wallet.rescan_utxos()403mempoolminfee = node.getmempoolinfo()['mempoolminfee']404peer = node.add_p2p_connection(TestP2PConn())405low_fee_tx = self.wallet.create_self_transfer(fee_rate=Decimal("0.9")*mempoolminfee)406assert_equal(node.testmempoolaccept([low_fee_tx['hex']])[0]["reject-reason"], "mempool min fee not met")407peer.send_and_ping(msg_tx(low_fee_tx['tx']))408peer.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=int(low_fee_tx['wtxid'], 16))]))409node.setmocktime(int(time.time()))410node.bumpmocktime(MAX_GETDATA_INBOUND_WAIT)411peer.sync_with_ping()412assert_equal(peer.tx_getdata_count, 0)414self.log.info('Check that rejection filter is cleared after new block comes in')415self.generate(self.wallet, 1, sync_fun=self.no_op)416peer.sync_with_ping()417peer.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=int(low_fee_tx['wtxid'], 16))]))418node.bumpmocktime(MAX_GETDATA_INBOUND_WAIT)419peer.wait_for_getdata([int(low_fee_tx['wtxid'], 16)])421def test_inv_wtxidrelay_mismatch(self):422self.log.info("Check that INV messages that don't match the wtxidrelay setting are ignored")423node = self.nodes[0]424wtxidrelay_on_peer = node.add_p2p_connection(TestP2PConn(wtxidrelay=True))425wtxidrelay_off_peer = node.add_p2p_connection(TestP2PConn(wtxidrelay=False))426random_tx = self.wallet.create_self_transfer()428# MSG_TX INV from wtxidrelay=True peer -> mismatch, ignored429wtxidrelay_on_peer.send_and_ping(msg_inv([CInv(t=MSG_TX, h=int(random_tx['txid'], 16))]))430node.setmocktime(int(time.time()))431node.bumpmocktime(MAX_GETDATA_INBOUND_WAIT)432wtxidrelay_on_peer.sync_with_ping()433assert_equal(wtxidrelay_on_peer.tx_getdata_count, 0)435# MSG_WTX INV from wtxidrelay=False peer -> mismatch, ignored436wtxidrelay_off_peer.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=int(random_tx['wtxid'], 16))]))437node.bumpmocktime(MAX_GETDATA_INBOUND_WAIT)438wtxidrelay_off_peer.sync_with_ping()439assert_equal(wtxidrelay_off_peer.tx_getdata_count, 0)441# MSG_TX INV from wtxidrelay=False peer works442wtxidrelay_off_peer.send_and_ping(msg_inv([CInv(t=MSG_TX, h=int(random_tx['txid'], 16))]))443node.bumpmocktime(MAX_GETDATA_INBOUND_WAIT)444wtxidrelay_off_peer.wait_for_getdata([int(random_tx['txid'], 16)])446# MSG_WTX INV from wtxidrelay=True peer works447wtxidrelay_on_peer.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=int(random_tx['wtxid'], 16))]))448node.bumpmocktime(MAX_GETDATA_INBOUND_WAIT)449wtxidrelay_on_peer.wait_for_getdata([int(random_tx['wtxid'], 16)])451def run_test(self):452self.wallet = MiniWallet(self.nodes[0])454# Run tests without mocktime that only need one peer-connection first, to avoid restarting the nodes455self.test_expiry_fallback()456self.test_disconnect_fallback()457self.test_notfound_fallback()458self.test_preferred_tiebreaker_inv()459self.test_preferred_inv(ConnectionType.INBOUND)460self.test_preferred_inv(ConnectionType.OUTBOUND)461self.test_preferred_inv(ConnectionType.WHITELIST)462self.test_txid_inv_delay()463self.test_txid_inv_delay(True)464self.test_large_inv_batch()465self.test_duplicate_tx_inv()466self.test_spurious_notfound()468# Run each test against new bitcoind instances, as setting mocktimes has long-term effects on when469# the next trickle relay event happens.470for test, with_inbounds in [471(self.test_in_flight_max, True),472(self.test_inv_block, True),473(self.test_tx_requests, True),474(self.test_rejects_filter_reset, False),475(self.test_inv_wtxidrelay_mismatch, False),476]:477self.stop_nodes()478self.start_nodes()479self.connect_nodes(1, 0)480# Setup the p2p connections481self.peers = []482if with_inbounds:483for node in self.nodes:484for _ in range(NUM_INBOUND):485self.peers.append(node.add_p2p_connection(TestP2PConn()))486self.log.info("Nodes are setup with {} incoming connections each".format(NUM_INBOUND))487test()489if __name__ == '__main__':490TxDownloadTest(__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.