merge bitcoin#22211: relay I2P addresses even if not reachable (by us)

This commit is contained in:
Kittywhiskers Van Gogh 2024-04-04 17:13:03 +00:00
parent 7e08db55fe
commit fe66202c05
No known key found for this signature in database
GPG Key ID: 30CD0C065E5C4AAD
3 changed files with 43 additions and 20 deletions

View File

@ -232,7 +232,7 @@ public:
*/ */
bool IsRelayable() const bool IsRelayable() const
{ {
return IsIPv4() || IsIPv6() || IsTor(); return IsIPv4() || IsIPv6() || IsTor() || IsI2P();
} }
/** /**

View File

@ -15,6 +15,10 @@ from test_framework.p2p import P2PInterface
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal from test_framework.util import assert_equal
I2P_ADDR = "c4gfnttsuwqomiygupdqqqyy5y5emnk5c73hrfvatri67prd7vyq.b32.i2p"
ADDRS = []
class AddrReceiver(P2PInterface): class AddrReceiver(P2PInterface):
addrv2_received_and_checked = False addrv2_received_and_checked = False
@ -23,10 +27,7 @@ class AddrReceiver(P2PInterface):
super().__init__(support_addrv2 = True) super().__init__(support_addrv2 = True)
def on_addrv2(self, message): def on_addrv2(self, message):
for addr in message.addrs: if ADDRS == message.addrs:
assert_equal(addr.nServices, 1)
assert addr.ip.startswith('123.123.123.')
assert 8333 <= addr.port < 8343
self.addrv2_received_and_checked = True self.addrv2_received_and_checked = True
def wait_for_addrv2(self): def wait_for_addrv2(self):
@ -39,18 +40,21 @@ class AddrTest(BitcoinTestFramework):
self.num_nodes = 1 self.num_nodes = 1
def run_test(self): def run_test(self):
ADDRS = []
for i in range(10): for i in range(10):
addr = CAddress() addr = CAddress()
addr.time = int(self.mocktime) + i addr.time = int(self.mocktime) + i
addr.nServices = NODE_NETWORK addr.nServices = NODE_NETWORK
addr.ip = "123.123.123.{}".format(i % 256) # Add one I2P address at an arbitrary position.
if i == 5:
addr.net = addr.NET_I2P
addr.ip = I2P_ADDR
else:
addr.ip = f"123.123.123.{i % 256}"
addr.port = 8333 + i addr.port = 8333 + i
ADDRS.append(addr) ADDRS.append(addr)
self.log.info('Create connection that sends addrv2 messages') self.log.info('Create connection that sends addrv2 messages')
addr_source = self.nodes[0].add_p2p_connection(P2PInterface()) addr_source = self.nodes[0].add_p2p_connection(P2PInterface())
msg = msg_addrv2() msg = msg_addrv2()
self.log.info('Send too-large addrv2 message') self.log.info('Send too-large addrv2 message')
@ -63,22 +67,24 @@ class AddrTest(BitcoinTestFramework):
self.log.info('Check that addrv2 message content is relayed and added to addrman') self.log.info('Check that addrv2 message content is relayed and added to addrman')
addr_source = self.nodes[0].add_p2p_connection(P2PInterface()) addr_source = self.nodes[0].add_p2p_connection(P2PInterface())
addr_receiver = self.nodes[0].add_p2p_connection(AddrReceiver()) addr_receiver = self.nodes[0].add_p2p_connection(AddrReceiver())
msg.addrs = ADDRS msg.addrs = ADDRS
with self.nodes[0].assert_debug_log([ with self.nodes[0].assert_debug_log([
'Added 10 addresses from 127.0.0.1: 0 tried', # The I2P address is not added to node's own addrman because it has no
'received: addrv2 (131 bytes) peer=1', # I2P reachability (thus 10 - 1 = 9).
'Added 9 addresses from 127.0.0.1: 0 tried',
'received: addrv2 (159 bytes) peer=1',
]): ]):
addr_source.send_and_ping(msg) addr_source.send_and_ping(msg)
# Wait until "Added ..." before bumping mocktime to make sure addv2 is (almost) fully processed # Wait until "Added ..." before bumping mocktime to make sure addv2 is (almost) fully processed
with self.nodes[0].assert_debug_log([ with self.nodes[0].assert_debug_log([
'sending addrv2 (131 bytes) peer=2', 'sending addrv2 (159 bytes) peer=2',
]): ]):
self.bump_mocktime(30 * 60) self.bump_mocktime(30 * 60)
addr_receiver.wait_for_addrv2() addr_receiver.wait_for_addrv2()
assert addr_receiver.addrv2_received_and_checked assert addr_receiver.addrv2_received_and_checked
assert_equal(len(self.nodes[0].getnodeaddresses(count=0, network="i2p")), 0)
self.nodes[0].disconnect_p2ps() self.nodes[0].disconnect_p2ps()

View File

@ -16,7 +16,7 @@ ser_*, deser_*: functions that handle serialization/deserialization.
Classes use __slots__ to ensure extraneous attributes aren't accidentally added Classes use __slots__ to ensure extraneous attributes aren't accidentally added
by tests, compromising their intended effect. by tests, compromising their intended effect.
""" """
from base64 import b32decode, b32encode
import copy import copy
from collections import namedtuple from collections import namedtuple
import hashlib import hashlib
@ -251,15 +251,20 @@ class CAddress:
# see https://github.com/bitcoin/bips/blob/master/bip-0155.mediawiki # see https://github.com/bitcoin/bips/blob/master/bip-0155.mediawiki
NET_IPV4 = 1 NET_IPV4 = 1
NET_I2P = 5
ADDRV2_NET_NAME = { ADDRV2_NET_NAME = {
NET_IPV4: "IPv4" NET_IPV4: "IPv4",
NET_I2P: "I2P"
} }
ADDRV2_ADDRESS_LENGTH = { ADDRV2_ADDRESS_LENGTH = {
NET_IPV4: 4 NET_IPV4: 4,
NET_I2P: 32
} }
I2P_PAD = "===="
def __init__(self): def __init__(self):
self.time = 0 self.time = 0
self.nServices = 1 self.nServices = 1
@ -267,6 +272,9 @@ class CAddress:
self.ip = "0.0.0.0" self.ip = "0.0.0.0"
self.port = 0 self.port = 0
def __eq__(self, other):
return self.net == other.net and self.ip == other.ip and self.nServices == other.nServices and self.port == other.port and self.time == other.time
def deserialize(self, f, *, with_time=True): def deserialize(self, f, *, with_time=True):
"""Deserialize from addrv1 format (pre-BIP155)""" """Deserialize from addrv1 format (pre-BIP155)"""
if with_time: if with_time:
@ -299,24 +307,33 @@ class CAddress:
self.nServices = deser_compact_size(f) self.nServices = deser_compact_size(f)
self.net = struct.unpack("B", f.read(1))[0] self.net = struct.unpack("B", f.read(1))[0]
assert self.net == self.NET_IPV4 assert self.net in (self.NET_IPV4, self.NET_I2P)
address_length = deser_compact_size(f) address_length = deser_compact_size(f)
assert address_length == self.ADDRV2_ADDRESS_LENGTH[self.net] assert address_length == self.ADDRV2_ADDRESS_LENGTH[self.net]
self.ip = socket.inet_ntoa(f.read(4)) addr_bytes = f.read(address_length)
if self.net == self.NET_IPV4:
self.ip = socket.inet_ntoa(addr_bytes)
else:
self.ip = b32encode(addr_bytes)[0:-len(self.I2P_PAD)].decode("ascii").lower() + ".b32.i2p"
self.port = struct.unpack(">H", f.read(2))[0] self.port = struct.unpack(">H", f.read(2))[0]
def serialize_v2(self): def serialize_v2(self):
"""Serialize in addrv2 format (BIP155)""" """Serialize in addrv2 format (BIP155)"""
assert self.net == self.NET_IPV4 assert self.net in (self.NET_IPV4, self.NET_I2P)
r = b"" r = b""
r += struct.pack("<I", self.time) r += struct.pack("<I", self.time)
r += ser_compact_size(self.nServices) r += ser_compact_size(self.nServices)
r += struct.pack("B", self.net) r += struct.pack("B", self.net)
r += ser_compact_size(self.ADDRV2_ADDRESS_LENGTH[self.net]) r += ser_compact_size(self.ADDRV2_ADDRESS_LENGTH[self.net])
if self.net == self.NET_IPV4:
r += socket.inet_aton(self.ip) r += socket.inet_aton(self.ip)
else:
sfx = ".b32.i2p"
assert self.ip.endswith(sfx)
r += b32decode(self.ip[0:-len(sfx)] + self.I2P_PAD, True)
r += struct.pack(">H", self.port) r += struct.pack(">H", self.port)
return r return r