/ test / functional / p2p_addr_selfannouncement.py
p2p_addr_selfannouncement.py
  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  Additionally, this checks that the first self-announcement arrives
 10  in its own message and that this message is the first we receive.
 11  """
 12  
 13  import time
 14  
 15  from test_framework.messages import (
 16      CAddress,
 17      from_hex,
 18      msg_headers,
 19      CBlockHeader,
 20  )
 21  from test_framework.p2p import P2PInterface
 22  from test_framework.test_framework import BitcoinTestFramework
 23  from test_framework.util import assert_equal, assert_greater_than
 24  
 25  IP_TO_ANNOUNCE = "42.42.42.42"
 26  ONE_DAY = 60 * 60 * 24
 27  
 28  
 29  class SelfAnnouncementReceiver(P2PInterface):
 30      self_announcements_received = 0
 31      addresses_received = 0
 32      addr_messages_received = 0
 33  
 34      expected = None
 35      addrv2_test = False
 36  
 37      def __init__(self, *, expected, addrv2):
 38          super().__init__(support_addrv2=addrv2)
 39          self.expected = expected
 40          self.addrv2_test = addrv2
 41  
 42      def handle_addr_message(self, message):
 43          self.addr_messages_received += 1
 44          for addr in message.addrs:
 45              self.addresses_received += 1
 46              if addr == self.expected:
 47                  self.self_announcements_received += 1
 48                  if self.self_announcements_received == 1:
 49                      # If it's the first self-announcement, check that it is
 50                      # in the first addr message we receive, and that this message
 51                      # only contains one address. This also implies that it is
 52                      # the first address we receive.
 53                      assert_equal(self.addr_messages_received, 1)
 54                      assert_equal(len(message.addrs), 1)
 55  
 56      def on_addrv2(self, message):
 57          assert (self.addrv2_test)
 58          self.handle_addr_message(message)
 59  
 60      def on_addr(self, message):
 61          assert (not self.addrv2_test)
 62          self.handle_addr_message(message)
 63  
 64  
 65  class AddrSelfAnnouncementTest(BitcoinTestFramework):
 66      def set_test_params(self):
 67          self.num_nodes = 1
 68          self.extra_args = [[f"-externalip={IP_TO_ANNOUNCE}"]]
 69  
 70      def run_test(self):
 71          # populate addrman to have some addresses for a GETADDR response
 72          for i in range(50):
 73              a = f"{1 + i}.{i}.1.1"
 74              self.nodes[0].addpeeraddress(a, 8333)
 75  
 76          self.self_announcement_test(outbound=False, addrv2=False)
 77          self.self_announcement_test(outbound=False, addrv2=True)
 78          self.self_announcement_test(outbound=True, addrv2=False)
 79          self.self_announcement_test(outbound=True, addrv2=True)
 80  
 81      @staticmethod
 82      def inbound_connection_open_assertions(addr_receiver):
 83          # In response to a GETADDR, we expect a message with the self-announcement
 84          # and an addr message containing the GETADDR response.
 85          assert_equal(addr_receiver.self_announcements_received, 1)
 86          assert_equal(addr_receiver.addr_messages_received, 2)
 87          assert_greater_than(addr_receiver.addresses_received, 1)
 88  
 89      @staticmethod
 90      def outbound_connection_open_assertions(addr_receiver):
 91          # We expect only the self-announcement.
 92          assert_equal(addr_receiver.self_announcements_received, 1)
 93          assert_equal(addr_receiver.addr_messages_received, 1)
 94          assert_equal(addr_receiver.addresses_received, 1)
 95  
 96      def self_announcement_test(self, *, outbound, addrv2):
 97          connection_type = "outbound" if outbound else "inbound"
 98          addr_version = "addrv2" if addrv2 else "addrv1"
 99          self.log.info(f"Test that the node does an address self-announcement to {connection_type} connections ({addr_version})")
100  
101          # We only self-announce after initial block download is done
102          assert (not self.nodes[0].getblockchaininfo()["initialblockdownload"])
103  
104          netinfo = self.nodes[0].getnetworkinfo()
105          port = netinfo["localaddresses"][0]["port"]
106          self.nodes[0].setmocktime(int(time.time()))
107  
108          expected = CAddress()
109          expected.nServices = int(netinfo["localservices"], 16)
110          expected.ip = IP_TO_ANNOUNCE
111          expected.port = port
112          expected.time = self.nodes[0].mocktime
113  
114          with self.nodes[0].assert_debug_log([f'Advertising address {IP_TO_ANNOUNCE}:{port}']):
115              if outbound:
116                  self.log.info(f"Check that we get an initial self-announcement on an outbound connection from the node ({connection_type}, {addr_version})")
117                  addr_receiver = self.nodes[0].add_outbound_p2p_connection(SelfAnnouncementReceiver(expected=expected, addrv2=addrv2), p2p_idx=0, connection_type="outbound-full-relay")
118              else:
119                  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})")
120                  addr_receiver = self.nodes[0].add_p2p_connection(SelfAnnouncementReceiver(expected=expected, addrv2=addrv2))
121              addr_receiver.sync_with_ping()
122  
123          if outbound:
124              self.outbound_connection_open_assertions(addr_receiver)
125          else:
126              self.inbound_connection_open_assertions(addr_receiver)
127  
128          if outbound:
129              # to avoid the node evicting the outbound peer, protect it by announcing the most recent header to it
130              tip_header = from_hex(CBlockHeader(), self.nodes[0].getblockheader(self.nodes[0].getbestblockhash(), False))
131              addr_receiver.send_and_ping(msg_headers([tip_header]))
132  
133          self.log.info(f"Check that we get more self-announcements sometime later ({connection_type}, {addr_version})")
134          for _ in range(5):
135              last_self_announcements_received = addr_receiver.self_announcements_received
136              last_addr_messages_received = addr_receiver.addr_messages_received
137              last_addresses_received = addr_receiver.addresses_received
138              with self.nodes[0].assert_debug_log([f'Advertising address {IP_TO_ANNOUNCE}:{port}']):
139                  # m_next_local_addr_send and AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL:
140                  # self-announcements are sent on an exponential distribution with mean interval of 24h.
141                  # Setting the mocktime 20d forward gives a probability of (1 - e^-(480/24)) that
142                  # the event will occur (i.e. this fails once in ~500 million repeats).
143                  addr_receiver.expected.time = self.nodes[0].mocktime + 20 * ONE_DAY
144                  self.nodes[0].bumpmocktime(20 * ONE_DAY)
145                  addr_receiver.sync_with_ping()
146  
147              assert_equal(addr_receiver.self_announcements_received, last_self_announcements_received + 1)
148              assert_equal(addr_receiver.addr_messages_received, last_addr_messages_received + 1)
149              assert_equal(addr_receiver.addresses_received, last_addresses_received + 1)
150  
151          self.nodes[0].disconnect_p2ps()
152  
153  if __name__ == '__main__':
154      AddrSelfAnnouncementTest(__file__).main()