diff --git a/doc/release-notes.md b/doc/release-notes.md --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -5,3 +5,10 @@ This release includes the following features and fixes: + - The node's known peers are persisted to disk in a file called `peers.dat`. The + format of this file has been changed in a backwards-incompatible way in order to + accommodate the storage of Tor v3 and other BIP155 addresses. This means that if + the file is modified by 0.23.0 or newer then older versions will not be able to + read it. Those old versions, in the event of a downgrade, will log an error + message that deserialization has failed and will continue normal operation + as if the file was missing, creating a new empty one. diff --git a/src/addrman.h b/src/addrman.h --- a/src/addrman.h +++ b/src/addrman.h @@ -17,6 +17,8 @@ #include #include +#include + #include #include #include @@ -299,6 +301,18 @@ EXCLUSIVE_LOCKS_REQUIRED(cs); public: + //! Serialization versions. + enum class Format : uint8_t { + //! historic format, before commit e6b343d88 + V0_HISTORICAL = 0, + //! for pre-asmap files + V1_DETERMINISTIC = 1, + //! for files including asmap version + V2_ASMAP = 2, + //! same as V2_ASMAP plus addresses are in BIP155 format + V3_BIP155 = 3, + }; + // Compressed IP->ASN mapping, loaded from a file when a node starts. // Should be always empty if no file was provided. // This mapping is then used for bucketing nodes in Addrman. @@ -319,9 +333,8 @@ static std::vector DecodeAsmap(fs::path path); /** - * serialized format: - * * version byte (1 for pre-asmap files, 2 for files including asmap - * version) + * Serialized format. + * * version byte (@see `Format`) * * 0x20 + nKey (serialized as if it were a vector, for backward * compatibility) * * nNew @@ -350,11 +363,15 @@ * We don't use SERIALIZE_METHODS since the serialization and * deserialization code has very little in common. */ - template void Serialize(Stream &s) const { + template void Serialize(Stream &s_) const { LOCK(cs); - uint8_t nVersion = 2; - s << nVersion; + // Always serialize in the latest version (currently Format::V3_BIP155). + + OverrideStream s(&s_, s_.GetType(), + s_.GetVersion() | ADDRV2_FORMAT); + + s << static_cast(Format::V3_BIP155); s << uint8_t(32); s << nKey; s << nNew; @@ -406,12 +423,34 @@ s << asmap_version; } - template void Unserialize(Stream &s) { + template void Unserialize(Stream &s_) { LOCK(cs); Clear(); - uint8_t nVersion; - s >> nVersion; + + Format format; + s_ >> Using>(format); + + static constexpr Format maximum_supported_format = Format::V3_BIP155; + if (format > maximum_supported_format) { + throw std::ios_base::failure(strprintf( + "Unsupported format of addrman database: %u. Maximum supported " + "is %u. " + "Continuing operation without using the saved list of peers.", + static_cast(format), + static_cast(maximum_supported_format))); + } + + int stream_version = s_.GetVersion(); + if (format >= Format::V3_BIP155) { + // Add ADDRV2_FORMAT to the version so that the CNetAddr and + // CAddress unserialize methods know that an address in addrv2 + // format is coming. + stream_version |= ADDRV2_FORMAT; + } + + OverrideStream s(&s_, s_.GetType(), stream_version); + uint8_t nKeySize; s >> nKeySize; if (nKeySize != 32) { @@ -424,7 +463,7 @@ s >> nTried; int nUBuckets = 0; s >> nUBuckets; - if (nVersion != 0) { + if (format >= Format::V1_DETERMINISTIC) { nUBuckets ^= (1 << 30); } @@ -491,7 +530,7 @@ supplied_asmap_version = SerializeHash(m_asmap); } uint256 serialized_asmap_version; - if (nVersion > 1) { + if (format >= Format::V2_ASMAP) { s >> serialized_asmap_version; } @@ -499,7 +538,8 @@ CAddrInfo &info = mapInfo[n]; int bucket = entryToBucket[n]; int nUBucketPos = info.GetBucketPosition(nKey, true, bucket); - if (nVersion == 2 && nUBuckets == ADDRMAN_NEW_BUCKET_COUNT && + if (format >= Format::V2_ASMAP && + nUBuckets == ADDRMAN_NEW_BUCKET_COUNT && vvNew[bucket][nUBucketPos] == -1 && info.nRefCount < ADDRMAN_NEW_BUCKETS_PER_ADDRESS && serialized_asmap_version == supplied_asmap_version) { @@ -508,7 +548,7 @@ vvNew[bucket][nUBucketPos] = n; info.nRefCount++; } else { - // In case the new table data cannot be used (nVersion unknown, + // In case the new table data cannot be used (format unknown, // bucket count wrong or new asmap), try to give them a // reference based on their primary source address. LogPrint(BCLog::ADDRMAN, diff --git a/src/protocol.h b/src/protocol.h --- a/src/protocol.h +++ b/src/protocol.h @@ -424,8 +424,10 @@ public: CAddress() : CService{} {}; - explicit CAddress(CService ipIn, ServiceFlags nServicesIn) + CAddress(CService ipIn, ServiceFlags nServicesIn) : CService{ipIn}, nServices{nServicesIn} {}; + CAddress(CService ipIn, ServiceFlags nServicesIn, uint32_t nTimeIn) + : CService{ipIn}, nTime{nTimeIn}, nServices{nServicesIn} {}; void Init(); @@ -445,14 +447,22 @@ // nTime. READWRITE(obj.nTime); } - READWRITE(Using>(obj.nServices)); + if (nVersion & ADDRV2_FORMAT) { + uint64_t services_tmp; + SER_WRITE(obj, services_tmp = obj.nServices); + READWRITE(Using>(services_tmp)); + SER_READ(obj, + obj.nServices = static_cast(services_tmp)); + } else { + READWRITE(Using>(obj.nServices)); + } READWRITEAS(CService, obj); } - ServiceFlags nServices{NODE_NONE}; - // disk and network only uint32_t nTime{TIME_INIT}; + + ServiceFlags nServices{NODE_NONE}; }; /** getdata message type flags */ diff --git a/src/streams.h b/src/streams.h --- a/src/streams.h +++ b/src/streams.h @@ -48,6 +48,7 @@ int GetVersion() const { return nVersion; } int GetType() const { return nType; } + void ignore(size_t size) { return stream->ignore(size); } }; template OverrideStream WithOrVersion(S *s, int nVersionFlag) { diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp --- a/src/test/netbase_tests.cpp +++ b/src/test/netbase_tests.cpp @@ -5,8 +5,12 @@ #include #include +#include +#include +#include #include #include +#include #include @@ -521,4 +525,132 @@ std::string("5wyqrzbvrdsumnok.onion\0example.com\0", 35), ret)); } +// Since CNetAddr (un)ser is tested separately in net_tests.cpp here we only +// try a few edge cases for port, service flags and time. + +static const std::vector fixture_addresses( + {CAddress(CService(CNetAddr(in6_addr(IN6ADDR_LOOPBACK_INIT)), 0 /* port */), + NODE_NONE, 0x4966bc61U /* Fri Jan 9 02:54:25 UTC 2009 */ + ), + CAddress(CService(CNetAddr(in6_addr(IN6ADDR_LOOPBACK_INIT)), + 0x00f1 /* port */), + NODE_NETWORK, 0x83766279U /* Tue Nov 22 11:22:33 UTC 2039 */ + ), + CAddress( + CService(CNetAddr(in6_addr(IN6ADDR_LOOPBACK_INIT)), 0xf1f2 /* port */), + static_cast(NODE_COMPACT_FILTERS | NODE_NETWORK_LIMITED), + 0xffffffffU /* Sun Feb 7 06:28:15 UTC 2106 */ + )}); + +// fixture_addresses should equal to this when serialized in V1 format. +// When this is unserialized from V1 format it should equal to +// fixture_addresses. +static constexpr const char *stream_addrv1_hex = + // number of entries + "03" + + // time, Fri Jan 9 02:54:25 UTC 2009 + "61bc6649" + // service flags, NODE_NONE + "0000000000000000" + // address, fixed 16 bytes (IPv4 embedded in IPv6) + "00000000000000000000000000000001" + // port + "0000" + + // time, Tue Nov 22 11:22:33 UTC 2039 + "79627683" + // service flags, NODE_NETWORK + "0100000000000000" + // address, fixed 16 bytes (IPv6) + "00000000000000000000000000000001" + // port + "00f1" + + // time, Sun Feb 7 06:28:15 UTC 2106 + "ffffffff" + // service flags, NODE_COMPACT_FILTERS | NODE_NETWORK_LIMITED + "4004000000000000" + // address, fixed 16 bytes (IPv6) + "00000000000000000000000000000001" + // port + "f1f2"; + +// fixture_addresses should equal to this when serialized in V2 format. +// When this is unserialized from V2 format it should equal to +// fixture_addresses. +static constexpr const char *stream_addrv2_hex = + // number of entries + "03" + + // time, Fri Jan 9 02:54:25 UTC 2009 + "61bc6649" + // service flags, COMPACTSIZE(NODE_NONE) + "00" + // network id, IPv6 + "02" + // address length, COMPACTSIZE(16) + "10" + // address + "00000000000000000000000000000001" + // port + "0000" + + // time, Tue Nov 22 11:22:33 UTC 2039 + "79627683" + // service flags, COMPACTSIZE(NODE_NETWORK) + "01" + // network id, IPv6 + "02" + // address length, COMPACTSIZE(16) + "10" + // address + "00000000000000000000000000000001" + // port + "00f1" + + // time, Sun Feb 7 06:28:15 UTC 2106 + "ffffffff" + // service flags, COMPACTSIZE(NODE_COMPACT_FILTERS | NODE_NETWORK_LIMITED) + "fd4004" + // network id, IPv6 + "02" + // address length, COMPACTSIZE(16) + "10" + // address + "00000000000000000000000000000001" + // port + "f1f2"; + +BOOST_AUTO_TEST_CASE(caddress_serialize_v1) { + CDataStream s(SER_NETWORK, PROTOCOL_VERSION); + + s << fixture_addresses; + BOOST_CHECK_EQUAL(HexStr(s), stream_addrv1_hex); +} + +BOOST_AUTO_TEST_CASE(caddress_unserialize_v1) { + CDataStream s(ParseHex(stream_addrv1_hex), SER_NETWORK, PROTOCOL_VERSION); + std::vector addresses_unserialized; + + s >> addresses_unserialized; + BOOST_CHECK(fixture_addresses == addresses_unserialized); +} + +BOOST_AUTO_TEST_CASE(caddress_serialize_v2) { + CDataStream s(SER_NETWORK, PROTOCOL_VERSION | ADDRV2_FORMAT); + + s << fixture_addresses; + BOOST_CHECK_EQUAL(HexStr(s), stream_addrv2_hex); +} + +BOOST_AUTO_TEST_CASE(caddress_unserialize_v2) { + CDataStream s(ParseHex(stream_addrv2_hex), SER_NETWORK, + PROTOCOL_VERSION | ADDRV2_FORMAT); + std::vector addresses_unserialized; + + s >> addresses_unserialized; + BOOST_CHECK(fixture_addresses == addresses_unserialized); +} + BOOST_AUTO_TEST_SUITE_END()