diff --git a/src/net.h b/src/net.h --- a/src/net.h +++ b/src/net.h @@ -868,6 +868,11 @@ bool fClient{false}; // after BIP159, set by version message bool m_limited_node{false}; + /** + * Whether the peer has signaled support for receiving ADDRv2 (BIP155) + * messages, implying a preference to receive ADDRv2 instead of ADDR ones. + */ + std::atomic_bool m_wants_addrv2{false}; std::atomic_bool fSuccessfullyConnected{false}; // Setting fDisconnect to true will cause the node to be disconnected the // next time DisconnectNodes() runs @@ -1103,11 +1108,18 @@ } void PushAddress(const CAddress &_addr, FastRandomContext &insecure_rand) { + // Whether the peer supports the address in `_addr`. For example, + // nodes that do not implement BIP155 cannot receive Tor v3 addresses + // because they require ADDRv2 (BIP155) encoding. + const bool addr_format_supported = + m_wants_addrv2 || _addr.IsAddrV1Compatible(); + // Known checking here is only to save space from duplicates. // SendMessages will filter it again for knowns that were added // after addresses were pushed. assert(m_addr_known); - if (_addr.IsValid() && !m_addr_known->contains(_addr.GetKey())) { + if (_addr.IsValid() && !m_addr_known->contains(_addr.GetKey()) && + addr_format_supported) { if (vAddrToSend.size() >= MAX_ADDR_TO_SEND) { vAddrToSend[insecure_rand.randrange(vAddrToSend.size())] = _addr; diff --git a/src/net_processing.cpp b/src/net_processing.cpp --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include // For NDEBUG compile time check @@ -2762,9 +2763,12 @@ pfrom.SetCommonVersion(greatest_common_version); pfrom.nVersion = nVersion; - m_connman.PushMessage( - &pfrom, - CNetMsgMaker(greatest_common_version).Make(NetMsgType::VERACK)); + const CNetMsgMaker msg_maker(greatest_common_version); + + m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::VERACK)); + + // Signal ADDRv2 support (BIP155). + m_connman.PushMessage(&pfrom, msg_maker.Make(NetMsgType::SENDADDRV2)); pfrom.nServices = nServices; pfrom.SetAddrLocal(addrMe); @@ -2921,17 +2925,27 @@ return; } - if (msg_type == NetMsgType::ADDR) { + if (msg_type == NetMsgType::ADDR || msg_type == NetMsgType::ADDRV2) { + int stream_version = vRecv.GetVersion(); + if (msg_type == NetMsgType::ADDRV2) { + // Add ADDRV2_FORMAT to the version so that the CNetAddr and + // CAddress unserialize methods know that an address in v2 format is + // coming. + stream_version |= ADDRV2_FORMAT; + } + + OverrideStream s(&vRecv, vRecv.GetType(), stream_version); std::vector vAddr; - vRecv >> vAddr; + + s >> vAddr; if (!pfrom.IsAddrRelayPeer()) { return; } if (vAddr.size() > 1000) { - Misbehaving(pfrom, 20, - strprintf("oversized-addr: message addr size() = %u", - vAddr.size())); + Misbehaving( + pfrom, 20, + strprintf("%s message size = %u", msg_type, vAddr.size())); return; } @@ -2984,6 +2998,11 @@ return; } + if (msg_type == NetMsgType::SENDADDRV2) { + pfrom.m_wants_addrv2 = true; + return; + } + if (msg_type == NetMsgType::SENDHEADERS) { LOCK(cs_main); State(pfrom.GetId())->fPreferHeaders = true; @@ -4855,6 +4874,17 @@ std::vector vAddr; vAddr.reserve(pto->vAddrToSend.size()); assert(pto->m_addr_known); + + const char *msg_type; + int make_flags; + if (pto->m_wants_addrv2) { + msg_type = NetMsgType::ADDRV2; + make_flags = ADDRV2_FORMAT; + } else { + msg_type = NetMsgType::ADDR; + make_flags = 0; + } + for (const CAddress &addr : pto->vAddrToSend) { if (!pto->m_addr_known->contains(addr.GetKey())) { pto->m_addr_known->insert(addr.GetKey()); @@ -4862,15 +4892,15 @@ // receiver rejects addr messages larger than 1000 if (vAddr.size() >= 1000) { m_connman.PushMessage( - pto, msgMaker.Make(NetMsgType::ADDR, vAddr)); + pto, msgMaker.Make(make_flags, msg_type, vAddr)); vAddr.clear(); } } } pto->vAddrToSend.clear(); if (!vAddr.empty()) { - m_connman.PushMessage(pto, - msgMaker.Make(NetMsgType::ADDR, vAddr)); + m_connman.PushMessage( + pto, msgMaker.Make(make_flags, msg_type, vAddr)); } // we only send the big addr message once diff --git a/src/netaddress.h b/src/netaddress.h --- a/src/netaddress.h +++ b/src/netaddress.h @@ -192,6 +192,13 @@ bool IsRoutable() const; bool IsInternal() const; bool IsValid() const; + + /** + * Check if the current object can be serialized in pre-ADDRv2/BIP155 + * format. + */ + bool IsAddrV1Compatible() const; + enum Network GetNetwork() const; std::string ToString() const; std::string ToStringIP() const; diff --git a/src/netaddress.cpp b/src/netaddress.cpp --- a/src/netaddress.cpp +++ b/src/netaddress.cpp @@ -497,6 +497,27 @@ return m_net == NET_INTERNAL; } +bool CNetAddr::IsAddrV1Compatible() const { + switch (m_net) { + case NET_IPV4: + case NET_IPV6: + case NET_INTERNAL: + return true; + case NET_ONION: + return m_addr.size() == ADDR_TORV2_SIZE; + case NET_I2P: + case NET_CJDNS: + return false; + case NET_UNROUTABLE: + // m_net is never and should not be set to NET_UNROUTABLE + case NET_MAX: + // m_net is never and should not be set to NET_MAX + assert(false); + } // no default case, so the compiler can warn about missing cases + + assert(false); +} + enum Network CNetAddr::GetNetwork() const { if (IsInternal()) { return NET_INTERNAL; @@ -775,9 +796,12 @@ } std::vector CNetAddr::GetAddrBytes() const { - uint8_t serialized[V1_SERIALIZATION_SIZE]; - SerializeV1Array(serialized); - return {std::begin(serialized), std::end(serialized)}; + if (IsAddrV1Compatible()) { + uint8_t serialized[V1_SERIALIZATION_SIZE]; + SerializeV1Array(serialized); + return {std::begin(serialized), std::end(serialized)}; + } + return std::vector(m_addr.begin(), m_addr.end()); } uint64_t CNetAddr::GetHash() const { diff --git a/src/protocol.h b/src/protocol.h --- a/src/protocol.h +++ b/src/protocol.h @@ -99,6 +99,19 @@ * @see https://bitcoin.org/en/developer-reference#addr */ extern const char *ADDR; +/** + * The addrv2 message relays connection information for peers on the network + * just like the addr message, but is extended to allow gossiping of longer node + * addresses (see BIP155). + */ +extern const char *ADDRV2; +/** + * The sendaddrv2 message signals support for receiving ADDRV2 messages + * (BIP155). It also implies that its sender can encode as ADDRV2 and would send + * ADDRV2 instead of ADDR to a peer that has signaled ADDRV2 support by sending + * SENDADDRV2. + */ +extern const char *SENDADDRV2; /** * The inv message (inventory message) transmits one or more inventories of * objects known to the transmitting peer. diff --git a/src/protocol.cpp b/src/protocol.cpp --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -21,6 +21,8 @@ const char *VERSION = "version"; const char *VERACK = "verack"; const char *ADDR = "addr"; +const char *ADDRV2 = "addrv2"; +const char *SENDADDRV2 = "sendaddrv2"; const char *INV = "inv"; const char *GETDATA = "getdata"; const char *MERKLEBLOCK = "merkleblock"; @@ -65,17 +67,17 @@ * above and in protocol.h. */ static const std::string allNetMessageTypes[] = { - NetMsgType::VERSION, NetMsgType::VERACK, NetMsgType::ADDR, - NetMsgType::INV, NetMsgType::GETDATA, NetMsgType::MERKLEBLOCK, - NetMsgType::GETBLOCKS, NetMsgType::GETHEADERS, NetMsgType::TX, - NetMsgType::HEADERS, NetMsgType::BLOCK, NetMsgType::GETADDR, - NetMsgType::MEMPOOL, NetMsgType::PING, NetMsgType::PONG, - NetMsgType::NOTFOUND, NetMsgType::FILTERLOAD, NetMsgType::FILTERADD, - NetMsgType::FILTERCLEAR, NetMsgType::SENDHEADERS, NetMsgType::FEEFILTER, - NetMsgType::SENDCMPCT, NetMsgType::CMPCTBLOCK, NetMsgType::GETBLOCKTXN, - NetMsgType::BLOCKTXN, NetMsgType::GETCFILTERS, NetMsgType::CFILTER, - NetMsgType::GETCFHEADERS, NetMsgType::CFHEADERS, NetMsgType::GETCFCHECKPT, - NetMsgType::CFCHECKPT, + NetMsgType::VERSION, NetMsgType::VERACK, NetMsgType::ADDR, + NetMsgType::ADDRV2, NetMsgType::SENDADDRV2, NetMsgType::INV, + NetMsgType::GETDATA, NetMsgType::MERKLEBLOCK, NetMsgType::GETBLOCKS, + NetMsgType::GETHEADERS, NetMsgType::TX, NetMsgType::HEADERS, + NetMsgType::BLOCK, NetMsgType::GETADDR, NetMsgType::MEMPOOL, + NetMsgType::PING, NetMsgType::PONG, NetMsgType::NOTFOUND, + NetMsgType::FILTERLOAD, NetMsgType::FILTERADD, NetMsgType::FILTERCLEAR, + NetMsgType::SENDHEADERS, NetMsgType::FEEFILTER, NetMsgType::SENDCMPCT, + NetMsgType::CMPCTBLOCK, NetMsgType::GETBLOCKTXN, NetMsgType::BLOCKTXN, + NetMsgType::GETCFILTERS, NetMsgType::CFILTER, NetMsgType::GETCFHEADERS, + NetMsgType::CFHEADERS, NetMsgType::GETCFCHECKPT, NetMsgType::CFCHECKPT, }; static const std::vector allNetMessageTypesVec(allNetMessageTypes, diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -309,6 +309,7 @@ BOOST_REQUIRE(addr.IsIPv4()); BOOST_CHECK(addr.IsBindAny()); + BOOST_CHECK(addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL(addr.ToString(), "0.0.0.0"); // IPv4, INADDR_NONE @@ -317,6 +318,7 @@ BOOST_REQUIRE(addr.IsIPv4()); BOOST_CHECK(!addr.IsBindAny()); + BOOST_CHECK(addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL(addr.ToString(), "255.255.255.255"); // IPv4, casual @@ -325,6 +327,7 @@ BOOST_REQUIRE(addr.IsIPv4()); BOOST_CHECK(!addr.IsBindAny()); + BOOST_CHECK(addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL(addr.ToString(), "12.34.56.78"); // IPv6, in6addr_any @@ -333,6 +336,7 @@ BOOST_REQUIRE(addr.IsIPv6()); BOOST_CHECK(addr.IsBindAny()); + BOOST_CHECK(addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL(addr.ToString(), "::"); // IPv6, casual @@ -342,6 +346,7 @@ BOOST_REQUIRE(addr.IsIPv6()); BOOST_CHECK(!addr.IsBindAny()); + BOOST_CHECK(addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL(addr.ToString(), "1122:3344:5566:7788:9900:aabb:ccdd:eeff"); @@ -351,6 +356,7 @@ BOOST_REQUIRE(addr.IsTor()); BOOST_CHECK(!addr.IsBindAny()); + BOOST_CHECK(addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL(addr.ToString(), "6hzph5hv6337r6p2.onion"); // TORv3 @@ -361,6 +367,7 @@ BOOST_REQUIRE(addr.IsTor()); BOOST_CHECK(!addr.IsBindAny()); + BOOST_CHECK(!addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL(addr.ToString(), torv3_addr); // TORv3, broken, with wrong checksum @@ -389,6 +396,7 @@ BOOST_REQUIRE(addr.IsInternal()); BOOST_CHECK(!addr.IsBindAny()); + BOOST_CHECK(addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL(addr.ToString(), "esffpvrt3wpeaygy.internal"); // Totally bogus @@ -486,6 +494,7 @@ s >> addr; BOOST_CHECK(addr.IsValid()); BOOST_CHECK(addr.IsIPv4()); + BOOST_CHECK(addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL(addr.ToString(), "1.2.3.4"); BOOST_REQUIRE(s.empty()); @@ -524,6 +533,7 @@ s >> addr; BOOST_CHECK(addr.IsValid()); BOOST_CHECK(addr.IsIPv6()); + BOOST_CHECK(addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL(addr.ToString(), "102:304:506:708:90a:b0c:d0e:f10"); BOOST_REQUIRE(s.empty()); @@ -536,6 +546,7 @@ // + sha256(name)[0:10] s >> addr; BOOST_CHECK(addr.IsInternal()); + BOOST_CHECK(addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL(addr.ToString(), "zklycewkdo64v6wc.internal"); BOOST_REQUIRE(s.empty()); @@ -572,6 +583,7 @@ s >> addr; BOOST_CHECK(addr.IsValid()); BOOST_CHECK(addr.IsTor()); + BOOST_CHECK(addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL(addr.ToString(), "6hzph5hv6337r6p2.onion"); BOOST_REQUIRE(s.empty()); @@ -593,6 +605,7 @@ s >> addr; BOOST_CHECK(addr.IsValid()); BOOST_CHECK(addr.IsTor()); + BOOST_CHECK(!addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL( addr.ToString(), "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion"); @@ -616,6 +629,8 @@ "f98232ae42d4b6fd2fa81952dfe36a87")); s >> addr; BOOST_CHECK(addr.IsValid()); + BOOST_CHECK(addr.IsI2P()); + BOOST_CHECK(!addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL( addr.ToString(), "ukeu3k5oycgaauneqgtnvselmt4yemvoilkln7jpvamvfx7dnkdq.b32.i2p"); @@ -639,6 +654,8 @@ )); s >> addr; BOOST_CHECK(addr.IsValid()); + BOOST_CHECK(addr.IsCJDNS()); + BOOST_CHECK(!addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL(addr.ToString(), "fc00:1:2:3:4:5:6:7"); BOOST_REQUIRE(s.empty()); diff --git a/test/functional/p2p_addr_relay.py b/test/functional/p2p_addr_relay.py --- a/test/functional/p2p_addr_relay.py +++ b/test/functional/p2p_addr_relay.py @@ -50,7 +50,7 @@ self.log.info('Send too large addr message') msg.addrs = ADDRS * 101 - with self.nodes[0].assert_debug_log(['message addr size() = 1010']): + with self.nodes[0].assert_debug_log(['addr message size = 1010']): addr_source.send_and_ping(msg) self.log.info( diff --git a/test/functional/p2p_addr_relay.py b/test/functional/p2p_addrv2_relay.py copy from test/functional/p2p_addr_relay.py copy to test/functional/p2p_addrv2_relay.py --- a/test/functional/p2p_addr_relay.py +++ b/test/functional/p2p_addrv2_relay.py @@ -3,22 +3,19 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """ -Test addr relay +Test addrv2 relay """ +import time + from test_framework.messages import ( CAddress, + msg_addrv2, NODE_NETWORK, - msg_addr, -) -from test_framework.mininode import ( - P2PInterface, ) +from test_framework.mininode import P2PInterface from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import ( - assert_equal, -) -import time +from test_framework.util import assert_equal ADDRS = [] for i in range(10): @@ -31,40 +28,51 @@ class AddrReceiver(P2PInterface): - def on_addr(self, message): + addrv2_received_and_checked = False + + def __init__(self): + super().__init__(support_addrv2=True) + + def on_addrv2(self, message): for addr in message.addrs: assert_equal(addr.nServices, NODE_NETWORK) assert addr.ip.startswith('123.123.123.') assert (8333 <= addr.port < 8343) + self.addrv2_received_and_checked = True + + def wait_for_addrv2(self): + self.wait_until(lambda: "addrv2" in self.last_message) class AddrTest(BitcoinTestFramework): def set_test_params(self): - self.setup_clean_chain = False + self.setup_clean_chain = True self.num_nodes = 1 def run_test(self): - self.log.info('Create connection that sends addr messages') + self.log.info('Create connection that sends addrv2 messages') addr_source = self.nodes[0].add_p2p_connection(P2PInterface()) - msg = msg_addr() + msg = msg_addrv2() - self.log.info('Send too large addr message') + self.log.info('Send too-large addrv2 message') msg.addrs = ADDRS * 101 - with self.nodes[0].assert_debug_log(['message addr size() = 1010']): + with self.nodes[0].assert_debug_log(['addrv2 message size = 1010']): addr_source.send_and_ping(msg) self.log.info( - 'Check that addr message content is relayed and added to addrman') + 'Check that addrv2 message content is relayed and added to addrman') addr_receiver = self.nodes[0].add_p2p_connection(AddrReceiver()) msg.addrs = ADDRS with self.nodes[0].assert_debug_log([ 'Added 10 addresses from 127.0.0.1: 0 tried', - 'received: addr (301 bytes) peer=0', - 'sending addr (301 bytes) peer=1', + 'received: addrv2 (131 bytes) peer=0', + 'sending addrv2 (131 bytes) peer=1', ]): addr_source.send_and_ping(msg) self.nodes[0].setmocktime(int(time.time()) + 30 * 60) - addr_receiver.sync_with_ping() + addr_receiver.wait_for_addrv2() + + assert addr_receiver.addrv2_received_and_checked if __name__ == '__main__': diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py --- a/test/functional/p2p_invalid_messages.py +++ b/test/functional/p2p_invalid_messages.py @@ -5,6 +5,7 @@ """Test node responses to invalid network messages.""" import asyncio import struct +import time from test_framework.messages import ( CBlockHeader, @@ -22,6 +23,7 @@ P2PInterface, ) from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import hex_str_to_bytes class msg_unrecognized: @@ -39,6 +41,11 @@ return "{}(data={})".format(self.msgtype, self.str_data) +class SenderOfAddrV2(P2PInterface): + def wait_for_sendaddrv2(self): + self.wait_until(lambda: 'sendaddrv2' in self.last_message) + + class InvalidMessagesTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 @@ -59,6 +66,10 @@ self.test_checksum() self.test_size() self.test_msgtype() + self.test_addrv2_empty() + self.test_addrv2_no_addresses() + self.test_addrv2_too_long_address() + self.test_addrv2_unrecognized_network() self.test_large_inv() node = self.nodes[0] @@ -223,15 +234,118 @@ conn.sync_with_ping(timeout=1) self.nodes[0].disconnect_p2ps() + def test_addrv2(self, label, required_log_messages, raw_addrv2): + node = self.nodes[0] + conn = node.add_p2p_connection(SenderOfAddrV2()) + + # Make sure bitcoind signals support for ADDRv2, otherwise this test + # will bombard an old node with messages it does not recognize which + # will produce unexpected results. + conn.wait_for_sendaddrv2() + + self.log.info('Test addrv2: ' + label) + + msg = msg_unrecognized(str_data=b'') + msg.msgtype = b'addrv2' + with node.assert_debug_log(required_log_messages): + # override serialize() which would include the length of the data + msg.serialize = lambda: raw_addrv2 + conn.send_raw_message(conn.build_message(msg)) + conn.sync_with_ping() + + node.disconnect_p2ps() + + def test_addrv2_empty(self): + self.test_addrv2('empty', + [ + 'received: addrv2 (0 bytes)', + 'ProcessMessages(addrv2, 0 bytes): Exception', + 'end of data', + ], + b'') + + def test_addrv2_no_addresses(self): + self.test_addrv2('no addresses', + [ + 'received: addrv2 (1 bytes)', + ], + hex_str_to_bytes('00')) + + def test_addrv2_too_long_address(self): + self.test_addrv2('too long address', + [ + 'received: addrv2 (525 bytes)', + 'ProcessMessages(addrv2, 525 bytes): Exception', + 'Address too long: 513 > 512', + ], + hex_str_to_bytes( + # number of entries + '01' + + # time, Fri Jan 9 02:54:25 UTC 2009 + '61bc6649' + + # service flags, COMPACTSIZE(NODE_NONE) + '00' + + # network type (IPv4) + '01' + + # address length (COMPACTSIZE(513)) + 'fd0102' + + # address + 'ab' * 513 + + # port + '208d')) + + def test_addrv2_unrecognized_network(self): + now_hex = struct.pack(' 20): oversized-inv: message inv size() = 50001']): + with self.nodes[0].assert_debug_log(['Misbehaving', 'peer=8 (0 -> 20): oversized-inv: message inv size() = 50001']): msg = msg_inv([CInv(MSG_TX, 1)] * 50001) conn.send_and_ping(msg) - with self.nodes[0].assert_debug_log(['Misbehaving', 'peer=4 (20 -> 40): too-many-inv: message getdata size() = 50001']): + with self.nodes[0].assert_debug_log(['Misbehaving', 'peer=8 (20 -> 40): too-many-inv: message getdata size() = 50001']): msg = msg_getdata([CInv(MSG_TX, 1)] * 50001) conn.send_and_ping(msg) - with self.nodes[0].assert_debug_log(['Misbehaving', 'peer=4 (40 -> 60): too-many-headers: headers message size = 2001']): + with self.nodes[0].assert_debug_log(['Misbehaving', 'peer=8 (40 -> 60): too-many-headers: headers message size = 2001']): msg = msg_headers([CBlockHeader()] * 2001) conn.send_and_ping(msg) self.nodes[0].disconnect_p2ps() diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -139,12 +139,17 @@ return v -def deser_vector(f, c): +# deser_function_name: Allow for an alternate deserialization function on the +# entries in the vector. +def deser_vector(f, c, deser_function_name=None): nit = deser_compact_size(f) r = [] for i in range(nit): t = c() - t.deserialize(f) + if deser_function_name: + getattr(t, deser_function_name)(f) + else: + t.deserialize(f) r.append(t) return r @@ -208,38 +213,83 @@ # Objects that map to bitcoind objects, which can be serialized/deserialized class CAddress: - __slots__ = ("ip", "nServices", "pchReserved", "port", "time") + __slots__ = ("net", "ip", "nServices", "port", "time") + + # see https://github.com/bitcoin/bips/blob/master/bip-0155.mediawiki + NET_IPV4 = 1 + + ADDRV2_NET_NAME = { + NET_IPV4: "IPv4" + } + + ADDRV2_ADDRESS_LENGTH = { + NET_IPV4: 4 + } def __init__(self): self.time = 0 self.nServices = 1 - self.pchReserved = b"\x00" * 10 + b"\xff" * 2 + self.net = self.NET_IPV4 self.ip = "0.0.0.0" self.port = 0 def deserialize(self, f, *, with_time=True): + """Deserialize from addrv1 format (pre-BIP155)""" if with_time: # VERSION messages serialize CAddress objects without time - self.time = struct.unpack("H", f.read(2))[0] def serialize(self, *, with_time=True): + """Serialize in addrv1 format (pre-BIP155)""" + assert self.net == self.NET_IPV4 r = b"" if with_time: # VERSION messages serialize CAddress objects without time - r += struct.pack("H", self.port) + return r + + def deserialize_v2(self, f): + """Deserialize from addrv2 format (BIP155)""" + self.time = struct.unpack("H", f.read(2))[0] + + def serialize_v2(self): + """Serialize in addrv2 format (BIP155)""" + assert self.net == self.NET_IPV4 + r = b"" + r += struct.pack("H", self.port) return r def __repr__(self): - return "CAddress(nServices={} ip={} port={})".format( - self.nServices, self.ip, self.port) + return ("CAddress(nServices=%i net=%s addr=%s port=%i)" + % (self.nServices, self.ADDRV2_NET_NAME[self.net], self.ip, self.port)) class CInv: @@ -1097,6 +1147,40 @@ return "msg_addr(addrs={})".format(repr(self.addrs)) +class msg_addrv2: + __slots__ = ("addrs",) + msgtype = b"addrv2" + + def __init__(self): + self.addrs = [] + + def deserialize(self, f): + self.addrs = deser_vector(f, CAddress, "deserialize_v2") + + def serialize(self): + return ser_vector(self.addrs, "serialize_v2") + + def __repr__(self): + return "msg_addrv2(addrs={})".format(repr(self.addrs)) + + +class msg_sendaddrv2: + __slots__ = () + msgtype = b"sendaddrv2" + + def __init__(self): + pass + + def deserialize(self, f): + pass + + def serialize(self): + return b"" + + def __repr__(self): + return "msg_sendaddrv2()" + + class msg_inv: __slots__ = ("inv",) msgtype = b"inv" diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py --- a/test/functional/test_framework/mininode.py +++ b/test/functional/test_framework/mininode.py @@ -28,6 +28,7 @@ CBlockHeader, MIN_VERSION_SUPPORTED, msg_addr, + msg_addrv2, msg_avapoll, msg_tcpavaresponse, msg_avahello, @@ -54,6 +55,7 @@ msg_notfound, msg_ping, msg_pong, + msg_sendaddrv2, msg_sendcmpct, msg_sendheaders, msg_tx, @@ -70,6 +72,7 @@ MESSAGEMAP = { b"addr": msg_addr, + b"addrv2": msg_addrv2, b"avapoll": msg_avapoll, b"avaresponse": msg_tcpavaresponse, b"avahello": msg_avahello, @@ -95,6 +98,7 @@ b"notfound": msg_notfound, b"ping": msg_ping, b"pong": msg_pong, + b"sendaddrv2": msg_sendaddrv2, b"sendcmpct": msg_sendcmpct, b"sendheaders": msg_sendheaders, b"tx": msg_tx, @@ -314,7 +318,7 @@ Individual testcases should subclass this and override the on_* methods if they want to alter message handling behaviour.""" - def __init__(self): + def __init__(self, support_addrv2=False): super().__init__() # Track number of messages of each type received and the most recent @@ -328,6 +332,8 @@ # The network services received from the peer self.nServices = 0 + self.support_addrv2 = support_addrv2 + def peer_connect(self, *args, services=NODE_NETWORK, send_version=True, **kwargs): create_conn = super().peer_connect(*args, **kwargs) @@ -375,6 +381,8 @@ def on_addr(self, message): pass + def on_addrv2(self, message): pass + def on_avapoll(self, message): pass def on_avaresponse(self, message): pass @@ -421,6 +429,8 @@ def on_pong(self, message): pass + def on_sendaddrv2(self, message): pass + def on_sendcmpct(self, message): pass def on_sendheaders(self, message): pass @@ -445,6 +455,8 @@ assert message.nVersion >= MIN_VERSION_SUPPORTED, "Version {} received. Test framework only supports versions greater than {}".format( message.nVersion, MIN_VERSION_SUPPORTED) self.send_message(msg_verack()) + if self.support_addrv2: + self.send_message(msg_sendaddrv2()) self.nServices = message.nServices # Connection helper methods