|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# Copyright (c) 2025-present The Bitcoin Core developers |
| 3 | +# Distributed under the MIT software license, see the accompanying |
| 4 | +# file COPYING or http://www.opensource.org/licenses/mit-license.php. |
| 5 | +""" |
| 6 | +Test that a node sends a self-announcement with its external IP to |
| 7 | +in- and outbound peers after connection open and again sometime later. |
| 8 | +""" |
| 9 | + |
| 10 | +import time |
| 11 | + |
| 12 | +from test_framework.messages import ( |
| 13 | + CAddress, |
| 14 | + from_hex, |
| 15 | + msg_headers, |
| 16 | + CBlockHeader, |
| 17 | +) |
| 18 | +from test_framework.p2p import P2PInterface |
| 19 | +from test_framework.test_framework import BitcoinTestFramework |
| 20 | +from test_framework.util import assert_equal, assert_greater_than |
| 21 | + |
| 22 | +IP_TO_ANNOUNCE = "42.42.42.42" |
| 23 | +ONE_DAY = 60 * 60 * 24 |
| 24 | + |
| 25 | + |
| 26 | +class SelfAnnouncementReceiver(P2PInterface): |
| 27 | + self_announcements_received = 0 |
| 28 | + addresses_received = 0 |
| 29 | + addr_messages_received = 0 |
| 30 | + |
| 31 | + expected = None |
| 32 | + addrv2_test = False |
| 33 | + |
| 34 | + def __init__(self, *, expected, addrv2): |
| 35 | + super().__init__(support_addrv2=addrv2) |
| 36 | + self.expected = expected |
| 37 | + self.addrv2_test = addrv2 |
| 38 | + |
| 39 | + def handle_addr_message(self, message): |
| 40 | + self.addr_messages_received += 1 |
| 41 | + for addr in message.addrs: |
| 42 | + self.addresses_received += 1 |
| 43 | + if addr == self.expected: |
| 44 | + self.self_announcements_received += 1 |
| 45 | + |
| 46 | + def on_addrv2(self, message): |
| 47 | + assert (self.addrv2_test) |
| 48 | + self.handle_addr_message(message) |
| 49 | + |
| 50 | + def on_addr(self, message): |
| 51 | + assert (not self.addrv2_test) |
| 52 | + self.handle_addr_message(message) |
| 53 | + |
| 54 | + |
| 55 | +class AddrSelfAnnouncementTest(BitcoinTestFramework): |
| 56 | + def set_test_params(self): |
| 57 | + self.num_nodes = 1 |
| 58 | + self.extra_args = [[f"-externalip={IP_TO_ANNOUNCE}"]] |
| 59 | + |
| 60 | + def run_test(self): |
| 61 | + # populate addrman to have some addresses for a GETADDR response |
| 62 | + for i in range(50): |
| 63 | + a = f"{1 + i}.{i}.1.1" |
| 64 | + self.nodes[0].addpeeraddress(a, 8333) |
| 65 | + |
| 66 | + self.self_announcement_test(outbound=False, addrv2=False) |
| 67 | + self.self_announcement_test(outbound=False, addrv2=True) |
| 68 | + self.self_announcement_test(outbound=True, addrv2=False) |
| 69 | + self.self_announcement_test(outbound=True, addrv2=True) |
| 70 | + |
| 71 | + def inbound_connection_open_assertions(self, addr_receiver): |
| 72 | + # We expect one self-announcement and multiple other addresses in |
| 73 | + # response to a GETADDR in a single addr / addrv2 message. |
| 74 | + assert_equal(addr_receiver.self_announcements_received, 1) |
| 75 | + assert_equal(addr_receiver.addr_messages_received, 1) |
| 76 | + assert_greater_than(addr_receiver.addresses_received, 1) |
| 77 | + |
| 78 | + def outbound_connection_open_assertions(self, addr_receiver): |
| 79 | + # We expect only the self-announcement. |
| 80 | + assert_equal(addr_receiver.self_announcements_received, 1) |
| 81 | + assert_equal(addr_receiver.addr_messages_received, 1) |
| 82 | + assert_equal(addr_receiver.addresses_received, 1) |
| 83 | + |
| 84 | + def self_announcement_test(self, *, outbound, addrv2): |
| 85 | + connection_type = "outbound" if outbound else "inbound" |
| 86 | + addr_version = "addrv2" if addrv2 else "addrv1" |
| 87 | + self.log.info(f"Test that the node does an address self-announcement to {connection_type} connections ({addr_version})") |
| 88 | + |
| 89 | + # We only self-announce after initial block download is done |
| 90 | + assert (not self.nodes[0].getblockchaininfo()["initialblockdownload"]) |
| 91 | + |
| 92 | + netinfo = self.nodes[0].getnetworkinfo() |
| 93 | + port = netinfo["localaddresses"][0]["port"] |
| 94 | + self.nodes[0].setmocktime(int(time.time())) |
| 95 | + |
| 96 | + expected = CAddress() |
| 97 | + expected.nServices = int(netinfo["localservices"], 16) |
| 98 | + expected.ip = IP_TO_ANNOUNCE |
| 99 | + expected.port = port |
| 100 | + expected.time = self.nodes[0].mocktime |
| 101 | + |
| 102 | + with self.nodes[0].assert_debug_log([f'Advertising address {IP_TO_ANNOUNCE}:{port}']): |
| 103 | + if outbound: |
| 104 | + self.log.info(f"Check that we get an initial self-announcement on an outbound connection from the node ({connection_type}, {addr_version})") |
| 105 | + addr_receiver = self.nodes[0].add_outbound_p2p_connection(SelfAnnouncementReceiver(expected=expected, addrv2=addrv2), p2p_idx=0, connection_type="outbound-full-relay") |
| 106 | + else: |
| 107 | + self.log.info(f"Check that we get an initial self-announcement when connecting to a node and sending a GETADDR ({connection_type}, {addr_version})") |
| 108 | + addr_receiver = self.nodes[0].add_p2p_connection(SelfAnnouncementReceiver(expected=expected, addrv2=addrv2)) |
| 109 | + addr_receiver.sync_with_ping() |
| 110 | + |
| 111 | + if outbound: |
| 112 | + self.outbound_connection_open_assertions(addr_receiver) |
| 113 | + else: |
| 114 | + self.inbound_connection_open_assertions(addr_receiver) |
| 115 | + |
| 116 | + if outbound: |
| 117 | + # to avoid the node evicting the outbound peer, protect it by announcing the most recent header to it |
| 118 | + tip_header = from_hex(CBlockHeader(), self.nodes[0].getblockheader(self.nodes[0].getbestblockhash(), False)) |
| 119 | + addr_receiver.send_and_ping(msg_headers([tip_header])) |
| 120 | + |
| 121 | + self.log.info(f"Check that we get more self-announcements sometime later ({connection_type}, {addr_version})") |
| 122 | + for _ in range(5): |
| 123 | + last_self_announcements_received = addr_receiver.self_announcements_received |
| 124 | + last_addr_messages_received = addr_receiver.addr_messages_received |
| 125 | + last_addresses_received = addr_receiver.addresses_received |
| 126 | + with self.nodes[0].assert_debug_log([f'Advertising address {IP_TO_ANNOUNCE}:{port}']): |
| 127 | + # m_next_local_addr_send and AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL: |
| 128 | + # self-announcements are sent on an exponential distribution with mean interval of 24h. |
| 129 | + # Setting the mocktime 20d forward gives a probability of (1 - e^-(480/24)) that |
| 130 | + # the event will occur (i.e. this fails once in ~500 million repeats). |
| 131 | + self.nodes[0].bumpmocktime(20 * ONE_DAY) |
| 132 | + addr_receiver.expected.time = self.nodes[0].mocktime |
| 133 | + addr_receiver.sync_with_ping() |
| 134 | + |
| 135 | + assert_equal(addr_receiver.self_announcements_received, last_self_announcements_received + 1) |
| 136 | + assert_equal(addr_receiver.addr_messages_received, last_addr_messages_received + 1) |
| 137 | + assert_equal(addr_receiver.addresses_received, last_addresses_received + 1) |
| 138 | + |
| 139 | + self.nodes[0].disconnect_p2ps() |
| 140 | + |
| 141 | +if __name__ == '__main__': |
| 142 | + AddrSelfAnnouncementTest(__file__).main() |
0 commit comments