Merge bitcoin/bitcoin#22211: net: relay I2P addresses even if not reachable (by us)

7593b06bd1 test: ensure I2P addresses are relayed (Vasil Dimov)
e7468139a1 test: make CAddress in functional tests comparable (Vasil Dimov)
33e211d2a4 test: implement ser/unser of I2P addresses in functional tests (Vasil Dimov)
86742811ce test: use NODE_* constants instead of magic numbers (Vasil Dimov)
ba45f02708 net: relay I2P addresses even if not reachable (by us) (Vasil Dimov)

Pull request description:

  Nodes that can reach the I2P network (have set `-i2psam=`) will relay
  I2P addresses even without this patch. However, nodes that can't reach
  the I2P network will not. This was done as a precaution in
  https://github.com/bitcoin/bitcoin/pull/20119 before anybody could
  connect to I2P because then, for sure, it would have been useless.

  Now, however, we have I2P support and a bunch of I2P nodes, so get all
  nodes on the network to relay I2P addresses to help with propagation,
  similarly to what we do with Tor addresses.

ACKs for top commit:
  jonatack:
    ACK 7593b06bd1
  naumenkogs:
    ACK 7593b06bd1.
  laanwj:
    Code review ACK 7593b06bd1
  kristapsk:
    ACK 7593b06bd1. Code looks correct, tested that functional test suite passes and also that `test/functional/p2p_addrv2_replay.py` fails if I undo changes in `IsRelayable()`.

Tree-SHA512: c9feec4a9546cc06bc2fec6d74f999a3c0abd3d15b7c421c21fcf2d610eb94611489e33d61bdcd5a4f42041a6d84aa892f7ae293b0d4f755309a8560b113b735
pull/826/head
W. J. van der Laan 3 years ago
commit a88fa1a555
No known key found for this signature in database
GPG Key ID: 1E4AED62986CD25D

@ -227,7 +227,7 @@ class CNetAddr
*/ */
bool IsRelayable() const bool IsRelayable() const
{ {
return IsIPv4() || IsIPv6() || IsTor(); return IsIPv4() || IsIPv6() || IsTor() || IsI2P();
} }
/** /**

@ -18,12 +18,19 @@ 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 = [] ADDRS = []
for i in range(10): for i in range(10):
addr = CAddress() addr = CAddress()
addr.time = int(time.time()) + i addr.time = int(time.time()) + i
addr.nServices = NODE_NETWORK | NODE_WITNESS addr.nServices = NODE_NETWORK | NODE_WITNESS
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)
@ -35,11 +42,8 @@ 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, 9) self.addrv2_received_and_checked = True
assert addr.ip.startswith('123.123.123.')
assert (8333 <= addr.port < 8343)
self.addrv2_received_and_checked = True
def wait_for_addrv2(self): def wait_for_addrv2(self):
self.wait_until(lambda: "addrv2" in self.last_message) self.wait_until(lambda: "addrv2" in self.last_message)
@ -64,15 +68,18 @@ class AddrTest(BitcoinTestFramework):
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=0', # I2P reachability (thus 10 - 1 = 9).
'sending addrv2 (131 bytes) peer=1', 'Added 9 addresses from 127.0.0.1: 0 tried',
'received: addrv2 (159 bytes) peer=0',
'sending addrv2 (159 bytes) peer=1',
]): ]):
addr_source.send_and_ping(msg) addr_source.send_and_ping(msg)
self.nodes[0].setmocktime(int(time.time()) + 30 * 60) self.nodes[0].setmocktime(int(time.time()) + 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)
if __name__ == '__main__': if __name__ == '__main__':

@ -18,6 +18,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
from codecs import encode from codecs import encode
import copy import copy
import hashlib import hashlib
@ -213,15 +214,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
@ -229,6 +235,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:
@ -261,24 +270,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])
r += socket.inet_aton(self.ip) if self.net == self.NET_IPV4:
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

Loading…
Cancel
Save