diff --git a/src/addrman.h b/src/addrman.h index 13db48c58..e2febea15 100644 --- a/src/addrman.h +++ b/src/addrman.h @@ -1,803 +1,819 @@ // Copyright (c) 2012 Pieter Wuille // Copyright (c) 2012-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_ADDRMAN_H #define BITCOIN_ADDRMAN_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** * Extended statistics about a CAddress */ class CAddrInfo : public CAddress { public: //! last try whatsoever by us (memory only) int64_t nLastTry{0}; //! last counted attempt (memory only) int64_t nLastCountAttempt{0}; private: //! where knowledge about this address first came from CNetAddr source; //! last successful connection by us int64_t nLastSuccess{0}; //! connection attempts since last successful attempt int nAttempts{0}; //! reference count in new sets (memory only) int nRefCount{0}; //! in tried set? (memory only) bool fInTried{false}; //! position in vRandom int nRandomPos{-1}; friend class CAddrMan; public: SERIALIZE_METHODS(CAddrInfo, obj) { READWRITEAS(CAddress, obj); READWRITE(obj.source, obj.nLastSuccess, obj.nAttempts); } CAddrInfo(const CAddress &addrIn, const CNetAddr &addrSource) : CAddress(addrIn), source(addrSource) {} CAddrInfo() : CAddress(), source() {} //! Calculate in which "tried" bucket this entry belongs int GetTriedBucket(const uint256 &nKey, const std::vector &asmap) const; //! Calculate in which "new" bucket this entry belongs, given a certain //! source int GetNewBucket(const uint256 &nKey, const CNetAddr &src, const std::vector &asmap) const; //! Calculate in which "new" bucket this entry belongs, using its default //! source int GetNewBucket(const uint256 &nKey, const std::vector &asmap) const { return GetNewBucket(nKey, source, asmap); } //! Calculate in which position of a bucket to store this entry. int GetBucketPosition(const uint256 &nKey, bool fNew, int nBucket) const; //! Determine whether the statistics about this entry are bad enough so that //! it can just be deleted bool IsTerrible(int64_t nNow = GetAdjustedTime()) const; //! Calculate the relative chance this entry should be given when selecting //! nodes to connect to double GetChance(int64_t nNow = GetAdjustedTime()) const; }; /** Stochastic address manager * * Design goals: * * Keep the address tables in-memory, and asynchronously dump the entire * table to peers.dat. * * Make sure no (localized) attacker can fill the entire table with his * nodes/addresses. * * To that end: * * Addresses are organized into buckets. * * Addresses that have not yet been tried go into 1024 "new" buckets. * * Based on the address range (/16 for IPv4) of the source of * information, 64 buckets are selected at random. * * The actual bucket is chosen from one of these, based on the range in * which the address itself is located. * * One single address can occur in up to 8 different buckets to increase * selection chances for addresses that * are seen frequently. The chance for increasing this multiplicity * decreases exponentially. * * When adding a new address to a full bucket, a randomly chosen entry * (with a bias favoring less recently seen * ones) is removed from it first. * * Addresses of nodes that are known to be accessible go into 256 "tried" * buckets. * * Each address range selects at random 8 of these buckets. * * The actual bucket is chosen from one of these, based on the full * address. * * When adding a new good address to a full bucket, a randomly chosen * entry (with a bias favoring less recently * tried ones) is evicted from it, back to the "new" buckets. * * Bucket selection is based on cryptographic hashing, using a * randomly-generated 256-bit key, which should not * be observable by adversaries. * * Several indexes are kept for high performance. Defining DEBUG_ADDRMAN * will introduce frequent (and expensive) * consistency checks for the entire data structure. */ //! total number of buckets for tried addresses #define ADDRMAN_TRIED_BUCKET_COUNT_LOG2 8 //! total number of buckets for new addresses #define ADDRMAN_NEW_BUCKET_COUNT_LOG2 10 //! maximum allowed number of entries in buckets for new and tried addresses #define ADDRMAN_BUCKET_SIZE_LOG2 6 //! over how many buckets entries with tried addresses from a single group (/16 //! for IPv4) are spread #define ADDRMAN_TRIED_BUCKETS_PER_GROUP 8 //! over how many buckets entries with new addresses originating from a single //! group are spread #define ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP 64 //! in how many buckets for entries with new addresses a single address may //! occur #define ADDRMAN_NEW_BUCKETS_PER_ADDRESS 8 //! how old addresses can maximally be #define ADDRMAN_HORIZON_DAYS 30 //! after how many failed attempts we give up on a new node #define ADDRMAN_RETRIES 3 //! how many successive failures are allowed ... #define ADDRMAN_MAX_FAILURES 10 //! ... in at least this many days #define ADDRMAN_MIN_FAIL_DAYS 7 //! how recent a successful connection should be before we allow an address to //! be evicted from tried #define ADDRMAN_REPLACEMENT_SECONDS (4 * 60 * 60) //! Convenience #define ADDRMAN_TRIED_BUCKET_COUNT (1 << ADDRMAN_TRIED_BUCKET_COUNT_LOG2) #define ADDRMAN_NEW_BUCKET_COUNT (1 << ADDRMAN_NEW_BUCKET_COUNT_LOG2) #define ADDRMAN_BUCKET_SIZE (1 << ADDRMAN_BUCKET_SIZE_LOG2) //! the maximum number of tried addr collisions to store #define ADDRMAN_SET_TRIED_COLLISION_SIZE 10 //! the maximum time we'll spend trying to resolve a tried table collision, in //! seconds (40 minutes) static const int64_t ADDRMAN_TEST_WINDOW = 40 * 60; /** * Stochastical (IP) address manager */ class CAddrMan { friend class CAddrManTest; protected: //! critical section to protect the inner data structures mutable RecursiveMutex cs; private: //! Serialization versions. enum 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, }; //! The maximum format this software knows it can unserialize. Also, we //! always serialize in this format. The format (first byte in the //! serialized stream) can be higher than this and still this software may //! be able to unserialize the file - if the second byte (see //! `lowest_compatible` in `Unserialize()`) is less or equal to this. static constexpr Format FILE_FORMAT = Format::V3_BIP155; //! The initial value of a field that is incremented every time an //! incompatible format change is made (such that old software versions //! would not be able to parse and understand the new file format). This is //! 32 because we overtook the "key size" field which was 32 historically. //! @note Don't increment this. Increment `lowest_compatible` in //! `Serialize()` instead. static constexpr uint8_t INCOMPATIBILITY_BASE = 32; //! last used nId int nIdCount GUARDED_BY(cs); //! table with information about all nIds std::map mapInfo GUARDED_BY(cs); //! find an nId based on its network address std::map mapAddr GUARDED_BY(cs); //! randomly-ordered vector of all nIds std::vector vRandom GUARDED_BY(cs); // number of "tried" entries int nTried GUARDED_BY(cs); //! list of "tried" buckets int vvTried[ADDRMAN_TRIED_BUCKET_COUNT][ADDRMAN_BUCKET_SIZE] GUARDED_BY(cs); //! number of (unique) "new" entries int nNew GUARDED_BY(cs); //! list of "new" buckets int vvNew[ADDRMAN_NEW_BUCKET_COUNT][ADDRMAN_BUCKET_SIZE] GUARDED_BY(cs); //! last time Good was called (memory only) int64_t nLastGood GUARDED_BY(cs); //! Holds addrs inserted into tried table that collide with existing //! entries. Test-before-evict discipline used to resolve these collisions. std::set m_tried_collisions; + //! Source of random numbers for randomization in inner loops + FastRandomContext insecure_rand; + + //! Use deterministic bucket selection and inner loops randomization. + //! For testing purpose only. + bool deterministic = false; + protected: //! secret key to randomize bucket select with uint256 nKey; - //! Source of random numbers for randomization in inner loops - FastRandomContext insecure_rand; - //! Find an entry. CAddrInfo *Find(const CNetAddr &addr, int *pnId = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs); //! find an entry, creating it if necessary. //! nTime and nServices of the found node are updated, if necessary. CAddrInfo *Create(const CAddress &addr, const CNetAddr &addrSource, int *pnId = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs); //! Swap two elements in vRandom. void SwapRandom(unsigned int nRandomPos1, unsigned int nRandomPos2) EXCLUSIVE_LOCKS_REQUIRED(cs); //! Move an entry from the "new" table(s) to the "tried" table void MakeTried(CAddrInfo &info, int nId) EXCLUSIVE_LOCKS_REQUIRED(cs); //! Delete an entry. It must not be in tried, and have refcount 0. void Delete(int nId) EXCLUSIVE_LOCKS_REQUIRED(cs); //! Clear a position in a "new" table. This is the only place where entries //! are actually deleted. void ClearNew(int nUBucket, int nUBucketPos) EXCLUSIVE_LOCKS_REQUIRED(cs); //! Mark an entry "good", possibly moving it from "new" to "tried". void Good_(const CService &addr, bool test_before_evict, int64_t time) EXCLUSIVE_LOCKS_REQUIRED(cs); //! Add an entry to the "new" table. bool Add_(const CAddress &addr, const CNetAddr &source, int64_t nTimePenalty) EXCLUSIVE_LOCKS_REQUIRED(cs); //! Mark an entry as attempted to connect. void Attempt_(const CService &addr, bool fCountFailure, int64_t nTime) EXCLUSIVE_LOCKS_REQUIRED(cs); //! Select an address to connect to, if newOnly is set to true, only the new //! table is selected from. CAddrInfo Select_(bool newOnly) EXCLUSIVE_LOCKS_REQUIRED(cs); //! See if any to-be-evicted tried table entries have been tested and if so //! resolve the collisions. void ResolveCollisions_() EXCLUSIVE_LOCKS_REQUIRED(cs); //! Return a random to-be-evicted tried table address. CAddrInfo SelectTriedCollision_() EXCLUSIVE_LOCKS_REQUIRED(cs); #ifdef DEBUG_ADDRMAN //! Perform consistency check. Returns an error code or zero. int Check_() EXCLUSIVE_LOCKS_REQUIRED(cs); #endif /** * Return all or many randomly selected addresses, optionally by network. * * @param[out] vAddr Vector of randomly selected addresses from * vRandom. * @param[in] max_addresses Maximum number of addresses to return * (0 = all). * @param[in] max_pct Maximum percentage of addresses to return * (0 = all). * @param[in] network Select only addresses of this network * (nullopt = all). */ void GetAddr_(std::vector &vAddr, size_t max_addresses, size_t max_pct, std::optional network) EXCLUSIVE_LOCKS_REQUIRED(cs); /** * We have successfully connected to this peer. Calling this function * updates the CAddress's nTime, which is used in our IsTerrible() * decisions and gossiped to peers. Callers should be careful that updating * this information doesn't leak topology information to network spies. * * net_processing calls this function when it *disconnects* from a peer to * not leak information about currently connected peers. * * @param[in] addr The address of the peer we were connected to * @param[in] nTime The time that we were last connected to this peer */ //! Mark an entry as currently-connected-to. void Connected_(const CService &addr, int64_t nTime) EXCLUSIVE_LOCKS_REQUIRED(cs); //! Update an entry's service bits. void SetServices_(const CService &addr, ServiceFlags nServices) EXCLUSIVE_LOCKS_REQUIRED(cs); public: // 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. // // If asmap is provided, nodes will be bucketed by // AS they belong to, in order to make impossible for a node // to connect to several nodes hosted in a single AS. // This is done in response to Erebus attack, but also to generally // diversify the connections every node creates, // especially useful when a large fraction of nodes // operate under a couple of cloud providers. // // If a new asmap was provided, the existing records // would be re-bucketed accordingly. std::vector m_asmap; /** * Serialized format. * * format version byte (@see `Format`) * * lowest compatible format version byte. This is used to help old * software decide whether to parse the file. For example: * * Bitcoin ABC version N knows how to parse up to format=3. If a new * format=4 is introduced in version N+1 that is compatible with format=3 * and it is known that version N will be able to parse it, then version N+1 * will write (format=4, lowest_compatible=3) in the first two bytes of the * file, and so version N will still try to parse it. * * Bitcoin ABC version N+2 introduces a new incompatible format=5. It * will write (format=5, lowest_compatible=5) and so any versions that do * not know how to parse format=5 will not try to read the file. * * nKey * * nNew * * nTried * * number of "new" buckets XOR 2**30 * * all nNew addrinfos in vvNew * * all nTried addrinfos in vvTried * * for each bucket: * * number of elements * * for each element: index * * 2**30 is xorred with the number of buckets to make addrman deserializer * v0 detect it as incompatible. This is necessary because it did not check * the version number on deserialization. * * Notice that vvTried, mapAddr and vVector are never encoded explicitly; * they are instead reconstructed from the other information. * * vvNew is serialized, but only used if ADDRMAN_UNKNOWN_BUCKET_COUNT didn't * change, otherwise it is reconstructed as well. * * This format is more complex, but significantly smaller (at most 1.5 MiB), * and supports changes to the ADDRMAN_ parameters without breaking the * on-disk structure. * * We don't use SERIALIZE_METHODS since the serialization and * deserialization code has very little in common. */ template void Serialize(Stream &s_) const { LOCK(cs); // Always serialize in the latest version (FILE_FORMAT). OverrideStream s(&s_, s_.GetType(), s_.GetVersion() | ADDRV2_FORMAT); s << static_cast(FILE_FORMAT); // Increment `lowest_compatible` iff a newly introduced format is // incompatible with the previous one. static constexpr uint8_t lowest_compatible = Format::V3_BIP155; s << static_cast(INCOMPATIBILITY_BASE + lowest_compatible); s << nKey; s << nNew; s << nTried; int nUBuckets = ADDRMAN_NEW_BUCKET_COUNT ^ (1 << 30); s << nUBuckets; std::map mapUnkIds; int nIds = 0; for (const auto &entry : mapInfo) { mapUnkIds[entry.first] = nIds; const CAddrInfo &info = entry.second; if (info.nRefCount) { // this means nNew was wrong, oh ow assert(nIds != nNew); s << info; nIds++; } } nIds = 0; for (const auto &entry : mapInfo) { const CAddrInfo &info = entry.second; if (info.fInTried) { // this means nTried was wrong, oh ow assert(nIds != nTried); s << info; nIds++; } } for (int bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; bucket++) { int nSize = 0; for (int i = 0; i < ADDRMAN_BUCKET_SIZE; i++) { if (vvNew[bucket][i] != -1) nSize++; } s << nSize; for (int i = 0; i < ADDRMAN_BUCKET_SIZE; i++) { if (vvNew[bucket][i] != -1) { int nIndex = mapUnkIds[vvNew[bucket][i]]; s << nIndex; } } } // Store asmap version after bucket entries so that it // can be ignored by older clients for backward compatibility. uint256 asmap_version; if (m_asmap.size() != 0) { asmap_version = SerializeHash(m_asmap); } s << asmap_version; } template void Unserialize(Stream &s_) { LOCK(cs); Clear(); Format format; s_ >> Using>(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 compat; s >> compat; const uint8_t lowest_compatible = compat - INCOMPATIBILITY_BASE; if (lowest_compatible > FILE_FORMAT) { throw std::ios_base::failure(strprintf( "Unsupported format of addrman database: %u. It is compatible " "with formats >=%u, " "but the maximum supported by this version of %s is %u.", format, lowest_compatible, PACKAGE_NAME, static_cast(FILE_FORMAT))); } s >> nKey; s >> nNew; s >> nTried; int nUBuckets = 0; s >> nUBuckets; if (format >= Format::V1_DETERMINISTIC) { nUBuckets ^= (1 << 30); } if (nNew > ADDRMAN_NEW_BUCKET_COUNT * ADDRMAN_BUCKET_SIZE) { throw std::ios_base::failure( "Corrupt CAddrMan serialization, nNew exceeds limit."); } if (nTried > ADDRMAN_TRIED_BUCKET_COUNT * ADDRMAN_BUCKET_SIZE) { throw std::ios_base::failure( "Corrupt CAddrMan serialization, nTried exceeds limit."); } // Deserialize entries from the new table. for (int n = 0; n < nNew; n++) { CAddrInfo &info = mapInfo[n]; s >> info; mapAddr[info] = n; info.nRandomPos = vRandom.size(); vRandom.push_back(n); } nIdCount = nNew; // Deserialize entries from the tried table. int nLost = 0; for (int n = 0; n < nTried; n++) { CAddrInfo info; s >> info; int nKBucket = info.GetTriedBucket(nKey, m_asmap); int nKBucketPos = info.GetBucketPosition(nKey, false, nKBucket); if (vvTried[nKBucket][nKBucketPos] == -1) { info.nRandomPos = vRandom.size(); info.fInTried = true; vRandom.push_back(nIdCount); mapInfo[nIdCount] = info; mapAddr[info] = nIdCount; vvTried[nKBucket][nKBucketPos] = nIdCount; nIdCount++; } else { nLost++; } } nTried -= nLost; // Store positions in the new table buckets to apply later (if // possible). Represents which entry belonged to which bucket when // serializing std::map entryToBucket; for (int bucket = 0; bucket < nUBuckets; bucket++) { int nSize = 0; s >> nSize; for (int n = 0; n < nSize; n++) { int nIndex = 0; s >> nIndex; if (nIndex >= 0 && nIndex < nNew) { entryToBucket[nIndex] = bucket; } } } uint256 supplied_asmap_version; if (m_asmap.size() != 0) { supplied_asmap_version = SerializeHash(m_asmap); } uint256 serialized_asmap_version; if (format >= Format::V2_ASMAP) { s >> serialized_asmap_version; } for (int n = 0; n < nNew; n++) { CAddrInfo &info = mapInfo[n]; int bucket = entryToBucket[n]; int nUBucketPos = info.GetBucketPosition(nKey, true, bucket); 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) { // Bucketing has not changed, using existing bucket positions // for the new table vvNew[bucket][nUBucketPos] = n; info.nRefCount++; } else { // 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, "Bucketing method was updated, re-bucketing addrman " "entries from disk\n"); bucket = info.GetNewBucket(nKey, m_asmap); nUBucketPos = info.GetBucketPosition(nKey, true, bucket); if (vvNew[bucket][nUBucketPos] == -1) { vvNew[bucket][nUBucketPos] = n; info.nRefCount++; } } } // Prune new entries with refcount 0 (as a result of collisions). int nLostUnk = 0; for (std::map::const_iterator it = mapInfo.begin(); it != mapInfo.end();) { if (it->second.fInTried == false && it->second.nRefCount == 0) { std::map::const_iterator itCopy = it++; Delete(itCopy->first); nLostUnk++; } else { it++; } } if (nLost + nLostUnk > 0) { LogPrint(BCLog::ADDRMAN, "addrman lost %i new and %i tried addresses due to " "collisions\n", nLostUnk, nLost); } Check(); } void Clear() { LOCK(cs); std::vector().swap(vRandom); - nKey = insecure_rand.rand256(); + + if (deterministic) { + nKey.SetNull(); + insecure_rand = FastRandomContext(true); + } else { + nKey = insecure_rand.rand256(); + } for (size_t bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; bucket++) { for (size_t entry = 0; entry < ADDRMAN_BUCKET_SIZE; entry++) { vvNew[bucket][entry] = -1; } } for (size_t bucket = 0; bucket < ADDRMAN_TRIED_BUCKET_COUNT; bucket++) { for (size_t entry = 0; entry < ADDRMAN_BUCKET_SIZE; entry++) { vvTried[bucket][entry] = -1; } } nIdCount = 0; nTried = 0; nNew = 0; // Initially at 1 so that "never" is strictly worse. nLastGood = 1; mapInfo.clear(); mapAddr.clear(); } CAddrMan() { Clear(); } ~CAddrMan() { nKey.SetNull(); } //! Return the number of (unique) addresses in all tables. size_t size() const { // TODO: Cache this in an atomic to avoid this overhead LOCK(cs); return vRandom.size(); } //! Consistency check void Check() { #ifdef DEBUG_ADDRMAN { LOCK(cs); int err; if ((err = Check_())) { LogPrintf("ADDRMAN CONSISTENCY CHECK FAILED!!! err=%i\n", err); } } #endif } //! Add a single address. bool Add(const CAddress &addr, const CNetAddr &source, int64_t nTimePenalty = 0) { LOCK(cs); bool fRet = false; Check(); fRet |= Add_(addr, source, nTimePenalty); Check(); if (fRet) { LogPrint(BCLog::ADDRMAN, "Added %s from %s: %i tried, %i new\n", addr.ToStringIPPort(), source.ToString(), nTried, nNew); } return fRet; } //! Add multiple addresses. bool Add(const std::vector &vAddr, const CNetAddr &source, int64_t nTimePenalty = 0) { LOCK(cs); int nAdd = 0; Check(); for (const CAddress &a : vAddr) { nAdd += Add_(a, source, nTimePenalty) ? 1 : 0; } Check(); if (nAdd) { LogPrint(BCLog::ADDRMAN, "Added %i addresses from %s: %i tried, %i new\n", nAdd, source.ToString(), nTried, nNew); } return nAdd > 0; } //! Mark an entry as accessible. void Good(const CService &addr, bool test_before_evict = true, int64_t nTime = GetAdjustedTime()) { LOCK(cs); Check(); Good_(addr, test_before_evict, nTime); Check(); } //! Mark an entry as connection attempted to. void Attempt(const CService &addr, bool fCountFailure, int64_t nTime = GetAdjustedTime()) { LOCK(cs); Check(); Attempt_(addr, fCountFailure, nTime); Check(); } //! See if any to-be-evicted tried table entries have been tested and if so //! resolve the collisions. void ResolveCollisions() { LOCK(cs); Check(); ResolveCollisions_(); Check(); } //! Randomly select an address in tried that another address is attempting //! to evict. CAddrInfo SelectTriedCollision() { CAddrInfo ret; { LOCK(cs); Check(); ret = SelectTriedCollision_(); Check(); } return ret; } /** * Choose an address to connect to. */ CAddrInfo Select(bool newOnly = false) { CAddrInfo addrRet; { LOCK(cs); Check(); addrRet = Select_(newOnly); Check(); } return addrRet; } /** * Return all or many randomly selected addresses, optionally by network. * * @param[in] max_addresses Maximum number of addresses to return * (0 = all). * @param[in] max_pct Maximum percentage of addresses to return * (0 = all). * @param[in] network Select only addresses of this network * (nullopt = all). */ std::vector GetAddr(size_t max_addresses, size_t max_pct, std::optional network) { Check(); std::vector vAddr; { LOCK(cs); GetAddr_(vAddr, max_addresses, max_pct, network); } Check(); return vAddr; } //! Outer function for Connected_() void Connected(const CService &addr, int64_t nTime = GetAdjustedTime()) { LOCK(cs); Check(); Connected_(addr, nTime); Check(); } void SetServices(const CService &addr, ServiceFlags nServices) { LOCK(cs); Check(); SetServices_(addr, nServices); Check(); } + + //! Ensure that bucket placement is always the same for testing purposes. + void MakeDeterministic() { + deterministic = true; + Clear(); + } }; #endif // BITCOIN_ADDRMAN_H diff --git a/src/test/addrman_tests.cpp b/src/test/addrman_tests.cpp index 5a9bee0d6..73ca7b2b1 100644 --- a/src/test/addrman_tests.cpp +++ b/src/test/addrman_tests.cpp @@ -1,948 +1,927 @@ // Copyright (c) 2012-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include #include #include class CAddrManTest : public CAddrMan { -private: - bool deterministic; - public: - explicit CAddrManTest(bool makeDeterministic = true, - std::vector asmap = std::vector()) { - if (makeDeterministic) { - // Set addrman addr placement to be deterministic. - MakeDeterministic(); - } - deterministic = makeDeterministic; + explicit CAddrManTest(std::vector asmap = std::vector()) { + // Set addrman addr placement to be deterministic. + MakeDeterministic(); m_asmap = asmap; } - //! Ensure that bucket placement is always the same for testing purposes. - void MakeDeterministic() { - nKey.SetNull(); - insecure_rand = FastRandomContext(true); - } - CAddrInfo *Find(const CNetAddr &addr, int *pnId = nullptr) { LOCK(cs); return CAddrMan::Find(addr, pnId); } CAddrInfo *Create(const CAddress &addr, const CNetAddr &addrSource, int *pnId = nullptr) { LOCK(cs); return CAddrMan::Create(addr, addrSource, pnId); } void Delete(int nId) { LOCK(cs); CAddrMan::Delete(nId); } // Used to test deserialization std::pair GetBucketAndEntry(const CAddress &addr) { LOCK(cs); int nId = mapAddr[addr]; for (int bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; ++bucket) { for (int entry = 0; entry < ADDRMAN_BUCKET_SIZE; ++entry) { if (nId == vvNew[bucket][entry]) { return std::pair(bucket, entry); } } } return std::pair(-1, -1); } // Simulates connection failure so that we can test eviction of offline // nodes void SimConnFail(const CService &addr) { LOCK(cs); int64_t nLastSuccess = 1; // Set last good connection in the deep past. Good_(addr, true, nLastSuccess); bool count_failure = false; int64_t nLastTry = GetAdjustedTime() - 61; Attempt(addr, count_failure, nLastTry); } - - void Clear() { - CAddrMan::Clear(); - if (deterministic) { - nKey.SetNull(); - insecure_rand = FastRandomContext(true); - } - } }; static CNetAddr ResolveIP(const std::string &ip) { CNetAddr addr; BOOST_CHECK_MESSAGE(LookupHost(ip, addr, false), strprintf("failed to resolve: %s", ip)); return addr; } static CService ResolveService(const std::string &ip, uint16_t port = 0) { CService serv; BOOST_CHECK_MESSAGE(Lookup(ip, serv, port, false), strprintf("failed to resolve: %s:%i", ip, port)); return serv; } static std::vector FromBytes(const uint8_t *source, int vector_size) { std::vector result(vector_size); for (int byte_i = 0; byte_i < vector_size / 8; ++byte_i) { uint8_t cur_byte = source[byte_i]; for (int bit_i = 0; bit_i < 8; ++bit_i) { result[byte_i * 8 + bit_i] = (cur_byte >> bit_i) & 1; } } return result; } BOOST_FIXTURE_TEST_SUITE(addrman_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(addrman_simple) { CAddrManTest addrman; CNetAddr source = ResolveIP("252.2.2.2"); // Test: Does Addrman respond correctly when empty. BOOST_CHECK_EQUAL(addrman.size(), 0U); CAddrInfo addr_null = addrman.Select(); BOOST_CHECK_EQUAL(addr_null.ToString(), "[::]:0"); // Test: Does Addrman::Add work as expected. CService addr1 = ResolveService("250.1.1.1", 8333); BOOST_CHECK(addrman.Add(CAddress(addr1, NODE_NONE), source)); BOOST_CHECK_EQUAL(addrman.size(), 1U); CAddrInfo addr_ret1 = addrman.Select(); BOOST_CHECK_EQUAL(addr_ret1.ToString(), "250.1.1.1:8333"); // Test: Does IP address deduplication work correctly. // Expected dup IP should not be added. CService addr1_dup = ResolveService("250.1.1.1", 8333); BOOST_CHECK(!addrman.Add(CAddress(addr1_dup, NODE_NONE), source)); BOOST_CHECK_EQUAL(addrman.size(), 1U); // Test: New table has one addr and we add a diff addr we should // have at least one addr. // Note that addrman's size cannot be tested reliably after insertion, as // hash collisions may occur. But we can always be sure of at least one // success. CService addr2 = ResolveService("250.1.1.2", 8333); BOOST_CHECK(addrman.Add(CAddress(addr2, NODE_NONE), source)); BOOST_CHECK(addrman.size() >= 1); // Test: AddrMan::Clear() should empty the new table. addrman.Clear(); BOOST_CHECK_EQUAL(addrman.size(), 0U); CAddrInfo addr_null2 = addrman.Select(); BOOST_CHECK_EQUAL(addr_null2.ToString(), "[::]:0"); // Test: AddrMan::Add multiple addresses works as expected std::vector vAddr; vAddr.push_back(CAddress(ResolveService("250.1.1.3", 8333), NODE_NONE)); vAddr.push_back(CAddress(ResolveService("250.1.1.4", 8333), NODE_NONE)); BOOST_CHECK(addrman.Add(vAddr, source)); BOOST_CHECK(addrman.size() >= 1); } BOOST_AUTO_TEST_CASE(addrman_ports) { CAddrManTest addrman; CNetAddr source = ResolveIP("252.2.2.2"); BOOST_CHECK_EQUAL(addrman.size(), 0U); // Test: Addr with same IP but diff port does not replace existing addr. CService addr1 = ResolveService("250.1.1.1", 8333); BOOST_CHECK(addrman.Add(CAddress(addr1, NODE_NONE), source)); BOOST_CHECK_EQUAL(addrman.size(), 1U); CService addr1_port = ResolveService("250.1.1.1", 8334); BOOST_CHECK(!addrman.Add(CAddress(addr1_port, NODE_NONE), source)); BOOST_CHECK_EQUAL(addrman.size(), 1U); CAddrInfo addr_ret2 = addrman.Select(); BOOST_CHECK_EQUAL(addr_ret2.ToString(), "250.1.1.1:8333"); // Test: Add same IP but diff port to tried table, it doesn't get added. // Perhaps this is not ideal behavior but it is the current behavior. addrman.Good(CAddress(addr1_port, NODE_NONE)); BOOST_CHECK_EQUAL(addrman.size(), 1U); bool newOnly = true; CAddrInfo addr_ret3 = addrman.Select(newOnly); BOOST_CHECK_EQUAL(addr_ret3.ToString(), "250.1.1.1:8333"); } BOOST_AUTO_TEST_CASE(addrman_select) { CAddrManTest addrman; CNetAddr source = ResolveIP("252.2.2.2"); // Test: Select from new with 1 addr in new. CService addr1 = ResolveService("250.1.1.1", 8333); BOOST_CHECK(addrman.Add(CAddress(addr1, NODE_NONE), source)); BOOST_CHECK_EQUAL(addrman.size(), 1U); bool newOnly = true; CAddrInfo addr_ret1 = addrman.Select(newOnly); BOOST_CHECK_EQUAL(addr_ret1.ToString(), "250.1.1.1:8333"); // Test: move addr to tried, select from new expected nothing returned. addrman.Good(CAddress(addr1, NODE_NONE)); BOOST_CHECK_EQUAL(addrman.size(), 1U); CAddrInfo addr_ret2 = addrman.Select(newOnly); BOOST_CHECK_EQUAL(addr_ret2.ToString(), "[::]:0"); CAddrInfo addr_ret3 = addrman.Select(); BOOST_CHECK_EQUAL(addr_ret3.ToString(), "250.1.1.1:8333"); BOOST_CHECK_EQUAL(addrman.size(), 1U); // Add three addresses to new table. CService addr2 = ResolveService("250.3.1.1", 8333); CService addr3 = ResolveService("250.3.2.2", 9999); CService addr4 = ResolveService("250.3.3.3", 9999); BOOST_CHECK(addrman.Add(CAddress(addr2, NODE_NONE), ResolveService("250.3.1.1", 8333))); BOOST_CHECK(addrman.Add(CAddress(addr3, NODE_NONE), ResolveService("250.3.1.1", 8333))); BOOST_CHECK(addrman.Add(CAddress(addr4, NODE_NONE), ResolveService("250.4.1.1", 8333))); // Add three addresses to tried table. CService addr5 = ResolveService("250.4.4.4", 8333); CService addr6 = ResolveService("250.4.5.5", 7777); CService addr7 = ResolveService("250.4.6.6", 8333); BOOST_CHECK(addrman.Add(CAddress(addr5, NODE_NONE), ResolveService("250.3.1.1", 8333))); addrman.Good(CAddress(addr5, NODE_NONE)); BOOST_CHECK(addrman.Add(CAddress(addr6, NODE_NONE), ResolveService("250.3.1.1", 8333))); addrman.Good(CAddress(addr6, NODE_NONE)); BOOST_CHECK(addrman.Add(CAddress(addr7, NODE_NONE), ResolveService("250.1.1.3", 8333))); addrman.Good(CAddress(addr7, NODE_NONE)); // Test: 6 addrs + 1 addr from last test = 7. BOOST_CHECK_EQUAL(addrman.size(), 7U); // Test: Select pulls from new and tried regardless of port number. std::set ports; for (int i = 0; i < 20; ++i) { ports.insert(addrman.Select().GetPort()); } BOOST_CHECK_EQUAL(ports.size(), 3U); } BOOST_AUTO_TEST_CASE(addrman_new_collisions) { CAddrManTest addrman; CNetAddr source = ResolveIP("252.2.2.2"); BOOST_CHECK_EQUAL(addrman.size(), 0U); for (unsigned int i = 1; i < 18; i++) { CService addr = ResolveService("250.1.1." + ToString(i)); BOOST_CHECK(addrman.Add(CAddress(addr, NODE_NONE), source)); // Test: No collision in new table yet. BOOST_CHECK_EQUAL(addrman.size(), i); } // Test: new table collision! CService addr1 = ResolveService("250.1.1.18"); BOOST_CHECK(addrman.Add(CAddress(addr1, NODE_NONE), source)); BOOST_CHECK_EQUAL(addrman.size(), 17U); CService addr2 = ResolveService("250.1.1.19"); BOOST_CHECK(addrman.Add(CAddress(addr2, NODE_NONE), source)); BOOST_CHECK_EQUAL(addrman.size(), 18U); } BOOST_AUTO_TEST_CASE(addrman_tried_collisions) { CAddrManTest addrman; CNetAddr source = ResolveIP("252.2.2.2"); BOOST_CHECK_EQUAL(addrman.size(), 0U); for (unsigned int i = 1; i < 80; i++) { CService addr = ResolveService("250.1.1." + ToString(i)); BOOST_CHECK(addrman.Add(CAddress(addr, NODE_NONE), source)); addrman.Good(CAddress(addr, NODE_NONE)); // Test: No collision in tried table yet. BOOST_CHECK_EQUAL(addrman.size(), i); } // Test: tried table collision! CService addr1 = ResolveService("250.1.1.80"); BOOST_CHECK(addrman.Add(CAddress(addr1, NODE_NONE), source)); BOOST_CHECK_EQUAL(addrman.size(), 79U); CService addr2 = ResolveService("250.1.1.81"); BOOST_CHECK(addrman.Add(CAddress(addr2, NODE_NONE), source)); BOOST_CHECK_EQUAL(addrman.size(), 80U); } BOOST_AUTO_TEST_CASE(addrman_find) { CAddrManTest addrman; BOOST_CHECK_EQUAL(addrman.size(), 0U); CAddress addr1 = CAddress(ResolveService("250.1.2.1", 8333), NODE_NONE); CAddress addr2 = CAddress(ResolveService("250.1.2.1", 9999), NODE_NONE); CAddress addr3 = CAddress(ResolveService("251.255.2.1", 8333), NODE_NONE); CNetAddr source1 = ResolveIP("250.1.2.1"); CNetAddr source2 = ResolveIP("250.1.2.2"); BOOST_CHECK(addrman.Add(addr1, source1)); BOOST_CHECK(!addrman.Add(addr2, source2)); BOOST_CHECK(addrman.Add(addr3, source1)); // Test: ensure Find returns an IP matching what we searched on. CAddrInfo *info1 = addrman.Find(addr1); BOOST_REQUIRE(info1); BOOST_CHECK_EQUAL(info1->ToString(), "250.1.2.1:8333"); // Test: Find does not discriminate by port number. CAddrInfo *info2 = addrman.Find(addr2); BOOST_REQUIRE(info2); BOOST_CHECK_EQUAL(info2->ToString(), info1->ToString()); // Test: Find returns another IP matching what we searched on. CAddrInfo *info3 = addrman.Find(addr3); BOOST_REQUIRE(info3); BOOST_CHECK_EQUAL(info3->ToString(), "251.255.2.1:8333"); } BOOST_AUTO_TEST_CASE(addrman_create) { CAddrManTest addrman; BOOST_CHECK_EQUAL(addrman.size(), 0U); CAddress addr1 = CAddress(ResolveService("250.1.2.1", 8333), NODE_NONE); CNetAddr source1 = ResolveIP("250.1.2.1"); int nId; CAddrInfo *pinfo = addrman.Create(addr1, source1, &nId); // Test: The result should be the same as the input addr. BOOST_CHECK_EQUAL(pinfo->ToString(), "250.1.2.1:8333"); CAddrInfo *info2 = addrman.Find(addr1); BOOST_CHECK_EQUAL(info2->ToString(), "250.1.2.1:8333"); } BOOST_AUTO_TEST_CASE(addrman_delete) { CAddrManTest addrman; BOOST_CHECK_EQUAL(addrman.size(), 0U); CAddress addr1 = CAddress(ResolveService("250.1.2.1", 8333), NODE_NONE); CNetAddr source1 = ResolveIP("250.1.2.1"); int nId; addrman.Create(addr1, source1, &nId); // Test: Delete should actually delete the addr. BOOST_CHECK_EQUAL(addrman.size(), 1U); addrman.Delete(nId); BOOST_CHECK_EQUAL(addrman.size(), 0U); CAddrInfo *info2 = addrman.Find(addr1); BOOST_CHECK(info2 == nullptr); } BOOST_AUTO_TEST_CASE(addrman_getaddr) { CAddrManTest addrman; // Test: Sanity check, GetAddr should never return anything if addrman // is empty. BOOST_CHECK_EQUAL(addrman.size(), 0U); std::vector vAddr1 = addrman.GetAddr( /* max_addresses */ 0, /* max_pct */ 0, /* network */ std::nullopt); BOOST_CHECK_EQUAL(vAddr1.size(), 0U); CAddress addr1 = CAddress(ResolveService("250.250.2.1", 8333), NODE_NONE); addr1.nTime = GetAdjustedTime(); // Set time so isTerrible = false CAddress addr2 = CAddress(ResolveService("250.251.2.2", 9999), NODE_NONE); addr2.nTime = GetAdjustedTime(); CAddress addr3 = CAddress(ResolveService("251.252.2.3", 8333), NODE_NONE); addr3.nTime = GetAdjustedTime(); CAddress addr4 = CAddress(ResolveService("252.253.3.4", 8333), NODE_NONE); addr4.nTime = GetAdjustedTime(); CAddress addr5 = CAddress(ResolveService("252.254.4.5", 8333), NODE_NONE); addr5.nTime = GetAdjustedTime(); CNetAddr source1 = ResolveIP("250.1.2.1"); CNetAddr source2 = ResolveIP("250.2.3.3"); // Test: Ensure GetAddr works with new addresses. BOOST_CHECK(addrman.Add(addr1, source1)); BOOST_CHECK(addrman.Add(addr2, source2)); BOOST_CHECK(addrman.Add(addr3, source1)); BOOST_CHECK(addrman.Add(addr4, source2)); BOOST_CHECK(addrman.Add(addr5, source1)); BOOST_CHECK_EQUAL(addrman .GetAddr(/* max_addresses */ 0, /* max_pct */ 0, /* network */ std::nullopt) .size(), 5U); // Net processing asks for 23% of addresses. 23% of 5 is 1 rounded down. BOOST_CHECK_EQUAL(addrman .GetAddr(/* max_addresses */ 2500, /* max_pct */ 23, /* network */ std::nullopt) .size(), 1U); // Test: Ensure GetAddr works with new and tried addresses. addrman.Good(CAddress(addr1, NODE_NONE)); addrman.Good(CAddress(addr2, NODE_NONE)); BOOST_CHECK_EQUAL(addrman .GetAddr(/* max_addresses */ 0, /* max_pct */ 0, /* network */ std::nullopt) .size(), 5U); BOOST_CHECK_EQUAL(addrman .GetAddr(/* max_addresses */ 2500, /* max_pct */ 23, /* network */ std::nullopt) .size(), 1U); // Test: Ensure GetAddr still returns 23% when addrman has many addrs. for (unsigned int i = 1; i < (8 * 256); i++) { int octet1 = i % 256; int octet2 = i >> 8 % 256; std::string strAddr = ToString(octet1) + "." + ToString(octet2) + ".1.23"; CAddress addr = CAddress(ResolveService(strAddr), NODE_NONE); // Ensure that for all addrs in addrman, isTerrible == false. addr.nTime = GetAdjustedTime(); addrman.Add(addr, ResolveIP(strAddr)); if (i % 8 == 0) { addrman.Good(addr); } } std::vector vAddr = addrman.GetAddr( /* max_addresses */ 2500, /* max_pct */ 23, /* network */ std::nullopt); size_t percent23 = (addrman.size() * 23) / 100; BOOST_CHECK_EQUAL(vAddr.size(), percent23); BOOST_CHECK_EQUAL(vAddr.size(), 461U); // (Addrman.size() < number of addresses added) due to address collisions. BOOST_CHECK_EQUAL(addrman.size(), 2006U); } BOOST_AUTO_TEST_CASE(caddrinfo_get_tried_bucket_legacy) { CAddrManTest addrman; CAddress addr1 = CAddress(ResolveService("250.1.1.1", 8333), NODE_NONE); CAddress addr2 = CAddress(ResolveService("250.1.1.1", 9999), NODE_NONE); CNetAddr source1 = ResolveIP("250.1.1.1"); CAddrInfo info1 = CAddrInfo(addr1, source1); uint256 nKey1 = (uint256)(CHashWriter(SER_GETHASH, 0) << 1).GetHash(); uint256 nKey2 = (uint256)(CHashWriter(SER_GETHASH, 0) << 2).GetHash(); // use /16 std::vector asmap; BOOST_CHECK_EQUAL(info1.GetTriedBucket(nKey1, asmap), 40); // Test: Make sure key actually randomizes bucket placement. A fail on // this test could be a security issue. BOOST_CHECK(info1.GetTriedBucket(nKey1, asmap) != info1.GetTriedBucket(nKey2, asmap)); // Test: Two addresses with same IP but different ports can map to // different buckets because they have different keys. CAddrInfo info2 = CAddrInfo(addr2, source1); BOOST_CHECK(info1.GetKey() != info2.GetKey()); BOOST_CHECK(info1.GetTriedBucket(nKey1, asmap) != info2.GetTriedBucket(nKey1, asmap)); std::set buckets; for (int i = 0; i < 255; i++) { CAddrInfo infoi = CAddrInfo( CAddress(ResolveService("250.1.1." + ToString(i)), NODE_NONE), ResolveIP("250.1.1." + ToString(i))); int bucket = infoi.GetTriedBucket(nKey1, asmap); buckets.insert(bucket); } // Test: IP addresses in the same /16 prefix should never get more than 8 // buckets with legacy grouping BOOST_CHECK_EQUAL(buckets.size(), 8U); buckets.clear(); for (int j = 0; j < 255; j++) { CAddrInfo infoj = CAddrInfo( CAddress(ResolveService("250." + ToString(j) + ".1.1"), NODE_NONE), ResolveIP("250." + ToString(j) + ".1.1")); int bucket = infoj.GetTriedBucket(nKey1, asmap); buckets.insert(bucket); } // Test: IP addresses in the different /16 prefix should map to more than 8 // buckets with legacy grouping BOOST_CHECK_EQUAL(buckets.size(), 160U); } BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket_legacy) { CAddrManTest addrman; CAddress addr1 = CAddress(ResolveService("250.1.2.1", 8333), NODE_NONE); CAddress addr2 = CAddress(ResolveService("250.1.2.1", 9999), NODE_NONE); CNetAddr source1 = ResolveIP("250.1.2.1"); CAddrInfo info1 = CAddrInfo(addr1, source1); uint256 nKey1 = (uint256)(CHashWriter(SER_GETHASH, 0) << 1).GetHash(); uint256 nKey2 = (uint256)(CHashWriter(SER_GETHASH, 0) << 2).GetHash(); // use /16 std::vector asmap; // Test: Make sure the buckets are what we expect BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, asmap), 786); BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, source1, asmap), 786); // Test: Make sure key actually randomizes bucket placement. A fail on // this test could be a security issue. BOOST_CHECK(info1.GetNewBucket(nKey1, asmap) != info1.GetNewBucket(nKey2, asmap)); // Test: Ports should not affect bucket placement in the addr CAddrInfo info2 = CAddrInfo(addr2, source1); BOOST_CHECK(info1.GetKey() != info2.GetKey()); BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, asmap), info2.GetNewBucket(nKey1, asmap)); std::set buckets; for (int i = 0; i < 255; i++) { CAddrInfo infoi = CAddrInfo( CAddress(ResolveService("250.1.1." + ToString(i)), NODE_NONE), ResolveIP("250.1.1." + ToString(i))); int bucket = infoi.GetNewBucket(nKey1, asmap); buckets.insert(bucket); } // Test: IP addresses in the same group (\16 prefix for IPv4) should // always map to the same bucket. BOOST_CHECK_EQUAL(buckets.size(), 1U); buckets.clear(); for (int j = 0; j < 4 * 255; j++) { CAddrInfo infoj = CAddrInfo(CAddress(ResolveService(ToString(250 + (j / 255)) + "." + ToString(j % 256) + ".1.1"), NODE_NONE), ResolveIP("251.4.1.1")); int bucket = infoj.GetNewBucket(nKey1, asmap); buckets.insert(bucket); } // Test: IP addresses in the same source groups should map to NO MORE // than 64 buckets. BOOST_CHECK(buckets.size() <= 64); buckets.clear(); for (int p = 0; p < 255; p++) { CAddrInfo infoj = CAddrInfo(CAddress(ResolveService("250.1.1.1"), NODE_NONE), ResolveIP("250." + ToString(p) + ".1.1")); int bucket = infoj.GetNewBucket(nKey1, asmap); buckets.insert(bucket); } // Test: IP addresses in the different source groups should map to more // than 64 buckets. BOOST_CHECK(buckets.size() > 64); } // The following three test cases use asmap.raw // We use an artificial minimal mock mapping // 250.0.0.0/8 AS1000 // 101.1.0.0/16 AS1 // 101.2.0.0/16 AS2 // 101.3.0.0/16 AS3 // 101.4.0.0/16 AS4 // 101.5.0.0/16 AS5 // 101.6.0.0/16 AS6 // 101.7.0.0/16 AS7 // 101.8.0.0/16 AS8 BOOST_AUTO_TEST_CASE(caddrinfo_get_tried_bucket) { CAddrManTest addrman; CAddress addr1 = CAddress(ResolveService("250.1.1.1", 8333), NODE_NONE); CAddress addr2 = CAddress(ResolveService("250.1.1.1", 9999), NODE_NONE); CNetAddr source1 = ResolveIP("250.1.1.1"); CAddrInfo info1 = CAddrInfo(addr1, source1); uint256 nKey1 = (uint256)(CHashWriter(SER_GETHASH, 0) << 1).GetHash(); uint256 nKey2 = (uint256)(CHashWriter(SER_GETHASH, 0) << 2).GetHash(); std::vector asmap = FromBytes(asmap_raw, sizeof(asmap_raw) * 8); BOOST_CHECK_EQUAL(info1.GetTriedBucket(nKey1, asmap), 236); // Test: Make sure key actually randomizes bucket placement. A fail on // this test could be a security issue. BOOST_CHECK(info1.GetTriedBucket(nKey1, asmap) != info1.GetTriedBucket(nKey2, asmap)); // Test: Two addresses with same IP but different ports can map to // different buckets because they have different keys. CAddrInfo info2 = CAddrInfo(addr2, source1); BOOST_CHECK(info1.GetKey() != info2.GetKey()); BOOST_CHECK(info1.GetTriedBucket(nKey1, asmap) != info2.GetTriedBucket(nKey1, asmap)); std::set buckets; for (int j = 0; j < 255; j++) { CAddrInfo infoj = CAddrInfo( CAddress(ResolveService("101." + ToString(j) + ".1.1"), NODE_NONE), ResolveIP("101." + ToString(j) + ".1.1")); int bucket = infoj.GetTriedBucket(nKey1, asmap); buckets.insert(bucket); } // Test: IP addresses in the different /16 prefix MAY map to more than // 8 buckets. BOOST_CHECK(buckets.size() > 8); buckets.clear(); for (int j = 0; j < 255; j++) { CAddrInfo infoj = CAddrInfo( CAddress(ResolveService("250." + ToString(j) + ".1.1"), NODE_NONE), ResolveIP("250." + ToString(j) + ".1.1")); int bucket = infoj.GetTriedBucket(nKey1, asmap); buckets.insert(bucket); } // Test: IP addresses in the different /16 prefix MAY NOT map to more than // 8 buckets. BOOST_CHECK(buckets.size() == 8); } BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket) { CAddrManTest addrman; CAddress addr1 = CAddress(ResolveService("250.1.2.1", 8333), NODE_NONE); CAddress addr2 = CAddress(ResolveService("250.1.2.1", 9999), NODE_NONE); CNetAddr source1 = ResolveIP("250.1.2.1"); CAddrInfo info1 = CAddrInfo(addr1, source1); uint256 nKey1 = (uint256)(CHashWriter(SER_GETHASH, 0) << 1).GetHash(); uint256 nKey2 = (uint256)(CHashWriter(SER_GETHASH, 0) << 2).GetHash(); std::vector asmap = FromBytes(asmap_raw, sizeof(asmap_raw) * 8); // Test: Make sure the buckets are what we expect BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, asmap), 795); BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, source1, asmap), 795); // Test: Make sure key actually randomizes bucket placement. A fail on // this test could be a security issue. BOOST_CHECK(info1.GetNewBucket(nKey1, asmap) != info1.GetNewBucket(nKey2, asmap)); // Test: Ports should not affect bucket placement in the addr CAddrInfo info2 = CAddrInfo(addr2, source1); BOOST_CHECK(info1.GetKey() != info2.GetKey()); BOOST_CHECK_EQUAL(info1.GetNewBucket(nKey1, asmap), info2.GetNewBucket(nKey1, asmap)); std::set buckets; for (int i = 0; i < 255; i++) { CAddrInfo infoi = CAddrInfo( CAddress(ResolveService("250.1.1." + ToString(i)), NODE_NONE), ResolveIP("250.1.1." + ToString(i))); int bucket = infoi.GetNewBucket(nKey1, asmap); buckets.insert(bucket); } // Test: IP addresses in the same /16 prefix // usually map to the same bucket. BOOST_CHECK_EQUAL(buckets.size(), 1U); buckets.clear(); for (int j = 0; j < 4 * 255; j++) { CAddrInfo infoj = CAddrInfo(CAddress(ResolveService(ToString(250 + (j / 255)) + "." + ToString(j % 256) + ".1.1"), NODE_NONE), ResolveIP("251.4.1.1")); int bucket = infoj.GetNewBucket(nKey1, asmap); buckets.insert(bucket); } // Test: IP addresses in the same source /16 prefix should not map to more // than 64 buckets. BOOST_CHECK(buckets.size() <= 64); buckets.clear(); for (int p = 0; p < 255; p++) { CAddrInfo infoj = CAddrInfo(CAddress(ResolveService("250.1.1.1"), NODE_NONE), ResolveIP("101." + ToString(p) + ".1.1")); int bucket = infoj.GetNewBucket(nKey1, asmap); buckets.insert(bucket); } // Test: IP addresses in the different source /16 prefixes usually map to // MORE than 1 bucket. BOOST_CHECK(buckets.size() > 1); buckets.clear(); for (int p = 0; p < 255; p++) { CAddrInfo infoj = CAddrInfo(CAddress(ResolveService("250.1.1.1"), NODE_NONE), ResolveIP("250." + ToString(p) + ".1.1")); int bucket = infoj.GetNewBucket(nKey1, asmap); buckets.insert(bucket); } // Test: IP addresses in the different source /16 prefixes sometimes map to // NO MORE than 1 bucket. BOOST_CHECK(buckets.size() == 1); } BOOST_AUTO_TEST_CASE(addrman_serialization) { std::vector asmap1 = FromBytes(asmap_raw, sizeof(asmap_raw) * 8); - CAddrManTest addrman_asmap1(true, asmap1); - CAddrManTest addrman_asmap1_dup(true, asmap1); + CAddrManTest addrman_asmap1(asmap1); + CAddrManTest addrman_asmap1_dup(asmap1); CAddrManTest addrman_noasmap; CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); CAddress addr = CAddress(ResolveService("250.1.1.1"), NODE_NONE); CNetAddr default_source; addrman_asmap1.Add(addr, default_source); stream << addrman_asmap1; // serizalizing/deserializing addrman with the same asmap stream >> addrman_asmap1_dup; std::pair bucketAndEntry_asmap1 = addrman_asmap1.GetBucketAndEntry(addr); std::pair bucketAndEntry_asmap1_dup = addrman_asmap1_dup.GetBucketAndEntry(addr); BOOST_CHECK(bucketAndEntry_asmap1.second != -1); BOOST_CHECK(bucketAndEntry_asmap1_dup.second != -1); BOOST_CHECK(bucketAndEntry_asmap1.first == bucketAndEntry_asmap1_dup.first); BOOST_CHECK(bucketAndEntry_asmap1.second == bucketAndEntry_asmap1_dup.second); // deserializing asmaped peers.dat to non-asmaped addrman stream << addrman_asmap1; stream >> addrman_noasmap; std::pair bucketAndEntry_noasmap = addrman_noasmap.GetBucketAndEntry(addr); BOOST_CHECK(bucketAndEntry_noasmap.second != -1); BOOST_CHECK(bucketAndEntry_asmap1.first != bucketAndEntry_noasmap.first); BOOST_CHECK(bucketAndEntry_asmap1.second != bucketAndEntry_noasmap.second); // deserializing non-asmaped peers.dat to asmaped addrman addrman_asmap1.Clear(); addrman_noasmap.Clear(); addrman_noasmap.Add(addr, default_source); stream << addrman_noasmap; stream >> addrman_asmap1; std::pair bucketAndEntry_asmap1_deser = addrman_asmap1.GetBucketAndEntry(addr); BOOST_CHECK(bucketAndEntry_asmap1_deser.second != -1); BOOST_CHECK(bucketAndEntry_asmap1_deser.first != bucketAndEntry_noasmap.first); BOOST_CHECK(bucketAndEntry_asmap1_deser.first == bucketAndEntry_asmap1_dup.first); BOOST_CHECK(bucketAndEntry_asmap1_deser.second == bucketAndEntry_asmap1_dup.second); // used to map to different buckets, now maps to the same bucket. addrman_asmap1.Clear(); addrman_noasmap.Clear(); CAddress addr1 = CAddress(ResolveService("250.1.1.1"), NODE_NONE); CAddress addr2 = CAddress(ResolveService("250.2.1.1"), NODE_NONE); addrman_noasmap.Add(addr, default_source); addrman_noasmap.Add(addr2, default_source); std::pair bucketAndEntry_noasmap_addr1 = addrman_noasmap.GetBucketAndEntry(addr1); std::pair bucketAndEntry_noasmap_addr2 = addrman_noasmap.GetBucketAndEntry(addr2); BOOST_CHECK(bucketAndEntry_noasmap_addr1.first != bucketAndEntry_noasmap_addr2.first); BOOST_CHECK(bucketAndEntry_noasmap_addr1.second != bucketAndEntry_noasmap_addr2.second); stream << addrman_noasmap; stream >> addrman_asmap1; std::pair bucketAndEntry_asmap1_deser_addr1 = addrman_asmap1.GetBucketAndEntry(addr1); std::pair bucketAndEntry_asmap1_deser_addr2 = addrman_asmap1.GetBucketAndEntry(addr2); BOOST_CHECK(bucketAndEntry_asmap1_deser_addr1.first == bucketAndEntry_asmap1_deser_addr2.first); BOOST_CHECK(bucketAndEntry_asmap1_deser_addr1.second != bucketAndEntry_asmap1_deser_addr2.second); } BOOST_AUTO_TEST_CASE(addrman_selecttriedcollision) { CAddrManTest addrman; BOOST_CHECK(addrman.size() == 0); // Empty addrman should return blank addrman info. BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); // Add twenty two addresses. CNetAddr source = ResolveIP("252.2.2.2"); for (unsigned int i = 1; i < 23; i++) { CService addr = ResolveService("250.1.1." + ToString(i)); BOOST_CHECK(addrman.Add(CAddress(addr, NODE_NONE), source)); addrman.Good(addr); // No collisions yet. BOOST_CHECK(addrman.size() == i); BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); } // Ensure Good handles duplicates well. for (unsigned int i = 1; i < 23; i++) { CService addr = ResolveService("250.1.1." + ToString(i)); addrman.Good(addr); BOOST_CHECK(addrman.size() == 22); BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); } } BOOST_AUTO_TEST_CASE(addrman_noevict) { CAddrManTest addrman; // Add twenty two addresses. CNetAddr source = ResolveIP("252.2.2.2"); for (unsigned int i = 1; i < 23; i++) { CService addr = ResolveService("250.1.1." + ToString(i)); BOOST_CHECK(addrman.Add(CAddress(addr, NODE_NONE), source)); addrman.Good(addr); // No collision yet. BOOST_CHECK(addrman.size() == i); BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); } // Collision between 23 and 19. CService addr23 = ResolveService("250.1.1.23"); BOOST_CHECK(addrman.Add(CAddress(addr23, NODE_NONE), source)); addrman.Good(addr23); BOOST_CHECK(addrman.size() == 23); BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "250.1.1.19:0"); // 23 should be discarded and 19 not evicted. addrman.ResolveCollisions(); BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); // Lets create two collisions. for (unsigned int i = 24; i < 33; i++) { CService addr = ResolveService("250.1.1." + ToString(i)); BOOST_CHECK(addrman.Add(CAddress(addr, NODE_NONE), source)); addrman.Good(addr); BOOST_CHECK(addrman.size() == i); BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); } // Cause a collision. CService addr33 = ResolveService("250.1.1.33"); BOOST_CHECK(addrman.Add(CAddress(addr33, NODE_NONE), source)); addrman.Good(addr33); BOOST_CHECK(addrman.size() == 33); BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "250.1.1.27:0"); // Cause a second collision. BOOST_CHECK(!addrman.Add(CAddress(addr23, NODE_NONE), source)); addrman.Good(addr23); BOOST_CHECK(addrman.size() == 33); BOOST_CHECK(addrman.SelectTriedCollision().ToString() != "[::]:0"); addrman.ResolveCollisions(); BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); } BOOST_AUTO_TEST_CASE(addrman_evictionworks) { CAddrManTest addrman; BOOST_CHECK(addrman.size() == 0); // Empty addrman should return blank addrman info. BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); // Add twenty two addresses. CNetAddr source = ResolveIP("252.2.2.2"); for (unsigned int i = 1; i < 23; i++) { CService addr = ResolveService("250.1.1." + ToString(i)); BOOST_CHECK(addrman.Add(CAddress(addr, NODE_NONE), source)); addrman.Good(addr); // No collision yet. BOOST_CHECK(addrman.size() == i); BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); } // Collision between 23 and 19. CService addr = ResolveService("250.1.1.23"); BOOST_CHECK(addrman.Add(CAddress(addr, NODE_NONE), source)); addrman.Good(addr); BOOST_CHECK(addrman.size() == 23); CAddrInfo info = addrman.SelectTriedCollision(); BOOST_CHECK(info.ToString() == "250.1.1.19:0"); // Ensure test of address fails, so that it is evicted. addrman.SimConnFail(info); // Should swap 23 for 19. addrman.ResolveCollisions(); BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); // If 23 was swapped for 19, then this should cause no collisions. BOOST_CHECK(!addrman.Add(CAddress(addr, NODE_NONE), source)); addrman.Good(addr); BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); // If we insert 19 is should collide with 23. CService addr19 = ResolveService("250.1.1.19"); BOOST_CHECK(!addrman.Add(CAddress(addr19, NODE_NONE), source)); addrman.Good(addr19); BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "250.1.1.23:0"); addrman.ResolveCollisions(); BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index 44ae4bd85..52bd07826 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -1,1011 +1,1005 @@ // Copyright (c) 2012-2019 The Bitcoin Core developers // Copyright (c) 2017-2019 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std::literals; namespace { struct CConnmanTest : public CConnman { using CConnman::CConnman; NodeId nodeid = 0; void AddNode(ConnectionType type) { ServiceFlags services = NODE_NETWORK; if (type == ConnectionType::AVALANCHE_OUTBOUND) { services = ServiceFlags(services | NODE_AVALANCHE); } struct in_addr s; s.s_addr = GetRandInt(0xffffffff); CAddress addr(CService(CNetAddr(s), Params().GetDefaultPort()), NODE_NONE); CNode *pnode = new CNode(nodeid++, services, INVALID_SOCKET, addr, /* nKeyedNetGroupIn */ 0, /* nLocalHostNonceIn */ 0, /* nLocalExtraEntropyIn */ 0, CAddress(), /* pszDest */ "", type, /* inbound_onion */ false); LOCK(cs_vNodes); vNodes.push_back(pnode); pnode->fSuccessfullyConnected = true; } void ClearNodes() { LOCK(cs_vNodes); for (CNode *node : vNodes) { delete node; } vNodes.clear(); } void SetMaxOutbounds(int maxFullRelayOutbounds, int maxAvalancheOutbounds) { Options options; options.nMaxConnections = DEFAULT_MAX_PEER_CONNECTIONS; options.m_max_outbound_full_relay = maxFullRelayOutbounds; options.m_max_avalanche_outbound = maxAvalancheOutbounds; Init(options); }; }; } // namespace class CAddrManSerializationMock : public CAddrMan { public: virtual void Serialize(CDataStream &s) const = 0; - - //! Ensure that bucket placement is always the same for testing purposes. - void MakeDeterministic() { - nKey.SetNull(); - insecure_rand = FastRandomContext(true); - } }; class CAddrManUncorrupted : public CAddrManSerializationMock { public: void Serialize(CDataStream &s) const override { CAddrMan::Serialize(s); } }; class CAddrManCorrupted : public CAddrManSerializationMock { public: void Serialize(CDataStream &s) const override { // Produces corrupt output that claims addrman has 20 addrs when it only // has one addr. uint8_t nVersion = 1; s << nVersion; s << uint8_t(32); s << nKey; s << 10; // nNew s << 10; // nTried int nUBuckets = ADDRMAN_NEW_BUCKET_COUNT ^ (1 << 30); s << nUBuckets; CService serv; BOOST_CHECK(Lookup("252.1.1.1", serv, 7777, false)); CAddress addr = CAddress(serv, NODE_NONE); CNetAddr resolved; BOOST_CHECK(LookupHost("252.2.2.2", resolved, false)); CAddrInfo info = CAddrInfo(addr, resolved); s << info; } }; class NetTestConfig : public DummyConfig { public: bool SetMaxBlockSize(uint64_t maxBlockSize) override { nMaxBlockSize = maxBlockSize; return true; } uint64_t GetMaxBlockSize() const override { return nMaxBlockSize; } private: uint64_t nMaxBlockSize; }; static CDataStream AddrmanToStream(const CAddrManSerializationMock &_addrman) { CDataStream ssPeersIn(SER_DISK, CLIENT_VERSION); ssPeersIn << Params().DiskMagic(); ssPeersIn << _addrman; std::string str = ssPeersIn.str(); std::vector vchData(str.begin(), str.end()); return CDataStream(vchData, SER_DISK, CLIENT_VERSION); } BOOST_FIXTURE_TEST_SUITE(net_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(cnode_listen_port) { // test default uint16_t port{GetListenPort()}; BOOST_CHECK(port == Params().GetDefaultPort()); // test set port uint16_t altPort = 12345; BOOST_CHECK(gArgs.SoftSetArg("-port", ToString(altPort))); port = GetListenPort(); BOOST_CHECK(port == altPort); } BOOST_AUTO_TEST_CASE(caddrdb_read) { CAddrManUncorrupted addrmanUncorrupted; addrmanUncorrupted.MakeDeterministic(); CService addr1, addr2, addr3; BOOST_CHECK(Lookup("250.7.1.1", addr1, 8333, false)); BOOST_CHECK(Lookup("250.7.2.2", addr2, 9999, false)); BOOST_CHECK(Lookup("250.7.3.3", addr3, 9999, false)); BOOST_CHECK(Lookup("250.7.3.3"s, addr3, 9999, false)); BOOST_CHECK(!Lookup("250.7.3.3\0example.com"s, addr3, 9999, false)); // Add three addresses to new table. CService source; BOOST_CHECK(Lookup("252.5.1.1", source, 8333, false)); BOOST_CHECK(addrmanUncorrupted.Add(CAddress(addr1, NODE_NONE), source)); BOOST_CHECK(addrmanUncorrupted.Add(CAddress(addr2, NODE_NONE), source)); BOOST_CHECK(addrmanUncorrupted.Add(CAddress(addr3, NODE_NONE), source)); // Test that the de-serialization does not throw an exception. CDataStream ssPeers1 = AddrmanToStream(addrmanUncorrupted); bool exceptionThrown = false; CAddrMan addrman1; BOOST_CHECK(addrman1.size() == 0); try { uint8_t pchMsgTmp[4]; ssPeers1 >> pchMsgTmp; ssPeers1 >> addrman1; } catch (const std::exception &) { exceptionThrown = true; } BOOST_CHECK(addrman1.size() == 3); BOOST_CHECK(exceptionThrown == false); // Test that CAddrDB::Read creates an addrman with the correct number of // addrs. CDataStream ssPeers2 = AddrmanToStream(addrmanUncorrupted); CAddrMan addrman2; CAddrDB adb(Params()); BOOST_CHECK(addrman2.size() == 0); BOOST_CHECK(adb.Read(addrman2, ssPeers2)); BOOST_CHECK(addrman2.size() == 3); } BOOST_AUTO_TEST_CASE(caddrdb_read_corrupted) { CAddrManCorrupted addrmanCorrupted; addrmanCorrupted.MakeDeterministic(); // Test that the de-serialization of corrupted addrman throws an exception. CDataStream ssPeers1 = AddrmanToStream(addrmanCorrupted); bool exceptionThrown = false; CAddrMan addrman1; BOOST_CHECK(addrman1.size() == 0); try { uint8_t pchMsgTmp[4]; ssPeers1 >> pchMsgTmp; ssPeers1 >> addrman1; } catch (const std::exception &) { exceptionThrown = true; } // Even through de-serialization failed addrman is not left in a clean // state. BOOST_CHECK(addrman1.size() == 1); BOOST_CHECK(exceptionThrown); // Test that CAddrDB::Read leaves addrman in a clean state if // de-serialization fails. CDataStream ssPeers2 = AddrmanToStream(addrmanCorrupted); CAddrMan addrman2; CAddrDB adb(Params()); BOOST_CHECK(addrman2.size() == 0); BOOST_CHECK(!adb.Read(addrman2, ssPeers2)); BOOST_CHECK(addrman2.size() == 0); } BOOST_AUTO_TEST_CASE(cnode_simple_test) { SOCKET hSocket = INVALID_SOCKET; NodeId id = 0; in_addr ipv4Addr; ipv4Addr.s_addr = 0xa0b0c001; CAddress addr = CAddress(CService(ipv4Addr, 7777), NODE_NETWORK); std::string pszDest; auto pnode1 = std::make_unique(id++, NODE_NETWORK, hSocket, addr, /* nKeyedNetGroupIn = */ 0, /* nLocalHostNonceIn = */ 0, /* nLocalExtraEntropyIn */ 0, CAddress(), pszDest, ConnectionType::OUTBOUND_FULL_RELAY, /* inbound_onion = */ false); BOOST_CHECK(pnode1->IsFullOutboundConn() == true); BOOST_CHECK(pnode1->IsManualConn() == false); BOOST_CHECK(pnode1->IsBlockOnlyConn() == false); BOOST_CHECK(pnode1->IsFeelerConn() == false); BOOST_CHECK(pnode1->IsAddrFetchConn() == false); BOOST_CHECK(pnode1->IsInboundConn() == false); BOOST_CHECK(pnode1->m_inbound_onion == false); BOOST_CHECK_EQUAL(pnode1->ConnectedThroughNetwork(), Network::NET_IPV4); auto pnode2 = std::make_unique(id++, NODE_NETWORK, hSocket, addr, 1, 1, 1, CAddress(), pszDest, ConnectionType::INBOUND, false); BOOST_CHECK(pnode2->IsFullOutboundConn() == false); BOOST_CHECK(pnode2->IsManualConn() == false); BOOST_CHECK(pnode2->IsBlockOnlyConn() == false); BOOST_CHECK(pnode2->IsFeelerConn() == false); BOOST_CHECK(pnode2->IsAddrFetchConn() == false); BOOST_CHECK(pnode2->IsInboundConn() == true); BOOST_CHECK(pnode2->m_inbound_onion == false); BOOST_CHECK_EQUAL(pnode2->ConnectedThroughNetwork(), Network::NET_IPV4); auto pnode3 = std::make_unique( id++, NODE_NETWORK, hSocket, addr, 0, 0, 0, CAddress(), pszDest, ConnectionType::OUTBOUND_FULL_RELAY, false); BOOST_CHECK(pnode3->IsFullOutboundConn() == true); BOOST_CHECK(pnode3->IsManualConn() == false); BOOST_CHECK(pnode3->IsBlockOnlyConn() == false); BOOST_CHECK(pnode3->IsFeelerConn() == false); BOOST_CHECK(pnode3->IsAddrFetchConn() == false); BOOST_CHECK(pnode3->IsInboundConn() == false); BOOST_CHECK(pnode3->m_inbound_onion == false); BOOST_CHECK_EQUAL(pnode3->ConnectedThroughNetwork(), Network::NET_IPV4); auto pnode4 = std::make_unique(id++, NODE_NETWORK, hSocket, addr, 1, 1, 1, CAddress(), pszDest, ConnectionType::INBOUND, true); BOOST_CHECK(pnode4->IsFullOutboundConn() == false); BOOST_CHECK(pnode4->IsManualConn() == false); BOOST_CHECK(pnode4->IsBlockOnlyConn() == false); BOOST_CHECK(pnode4->IsFeelerConn() == false); BOOST_CHECK(pnode4->IsAddrFetchConn() == false); BOOST_CHECK(pnode4->IsInboundConn() == true); BOOST_CHECK(pnode4->m_inbound_onion == true); BOOST_CHECK_EQUAL(pnode4->ConnectedThroughNetwork(), Network::NET_ONION); } BOOST_AUTO_TEST_CASE(test_getSubVersionEB) { BOOST_CHECK_EQUAL(getSubVersionEB(13800000000), "13800.0"); BOOST_CHECK_EQUAL(getSubVersionEB(3800000000), "3800.0"); BOOST_CHECK_EQUAL(getSubVersionEB(14000000), "14.0"); BOOST_CHECK_EQUAL(getSubVersionEB(1540000), "1.5"); BOOST_CHECK_EQUAL(getSubVersionEB(1560000), "1.5"); BOOST_CHECK_EQUAL(getSubVersionEB(210000), "0.2"); BOOST_CHECK_EQUAL(getSubVersionEB(10000), "0.0"); BOOST_CHECK_EQUAL(getSubVersionEB(0), "0.0"); } BOOST_AUTO_TEST_CASE(test_userAgent) { NetTestConfig config; config.SetMaxBlockSize(8000000); const std::string uacomment = "A very nice comment"; gArgs.ForceSetMultiArg("-uacomment", {uacomment}); const std::string versionMessage = "/Bitcoin ABC:" + ToString(CLIENT_VERSION_MAJOR) + "." + ToString(CLIENT_VERSION_MINOR) + "." + ToString(CLIENT_VERSION_REVISION) + "(EB8.0; " + uacomment + ")/"; BOOST_CHECK_EQUAL(userAgent(config), versionMessage); } BOOST_AUTO_TEST_CASE(LimitedAndReachable_Network) { BOOST_CHECK_EQUAL(IsReachable(NET_IPV4), true); BOOST_CHECK_EQUAL(IsReachable(NET_IPV6), true); BOOST_CHECK_EQUAL(IsReachable(NET_ONION), true); SetReachable(NET_IPV4, false); SetReachable(NET_IPV6, false); SetReachable(NET_ONION, false); BOOST_CHECK_EQUAL(IsReachable(NET_IPV4), false); BOOST_CHECK_EQUAL(IsReachable(NET_IPV6), false); BOOST_CHECK_EQUAL(IsReachable(NET_ONION), false); SetReachable(NET_IPV4, true); SetReachable(NET_IPV6, true); SetReachable(NET_ONION, true); BOOST_CHECK_EQUAL(IsReachable(NET_IPV4), true); BOOST_CHECK_EQUAL(IsReachable(NET_IPV6), true); BOOST_CHECK_EQUAL(IsReachable(NET_ONION), true); } BOOST_AUTO_TEST_CASE(LimitedAndReachable_NetworkCaseUnroutableAndInternal) { BOOST_CHECK_EQUAL(IsReachable(NET_UNROUTABLE), true); BOOST_CHECK_EQUAL(IsReachable(NET_INTERNAL), true); SetReachable(NET_UNROUTABLE, false); SetReachable(NET_INTERNAL, false); // Ignored for both networks BOOST_CHECK_EQUAL(IsReachable(NET_UNROUTABLE), true); BOOST_CHECK_EQUAL(IsReachable(NET_INTERNAL), true); } CNetAddr UtilBuildAddress(uint8_t p1, uint8_t p2, uint8_t p3, uint8_t p4) { uint8_t ip[] = {p1, p2, p3, p4}; struct sockaddr_in sa; // initialize the memory block memset(&sa, 0, sizeof(sockaddr_in)); memcpy(&(sa.sin_addr), &ip, sizeof(ip)); return CNetAddr(sa.sin_addr); } BOOST_AUTO_TEST_CASE(LimitedAndReachable_CNetAddr) { // 1.1.1.1 CNetAddr addr = UtilBuildAddress(0x001, 0x001, 0x001, 0x001); SetReachable(NET_IPV4, true); BOOST_CHECK_EQUAL(IsReachable(addr), true); SetReachable(NET_IPV4, false); BOOST_CHECK_EQUAL(IsReachable(addr), false); // have to reset this, because this is stateful. SetReachable(NET_IPV4, true); } BOOST_AUTO_TEST_CASE(LocalAddress_BasicLifecycle) { // 2.1.1.1:1000 CService addr = CService(UtilBuildAddress(0x002, 0x001, 0x001, 0x001), 1000); SetReachable(NET_IPV4, true); BOOST_CHECK_EQUAL(IsLocal(addr), false); BOOST_CHECK_EQUAL(AddLocal(addr, 1000), true); BOOST_CHECK_EQUAL(IsLocal(addr), true); RemoveLocal(addr); BOOST_CHECK_EQUAL(IsLocal(addr), false); } BOOST_AUTO_TEST_CASE(cnetaddr_basic) { CNetAddr addr; // IPv4, INADDR_ANY BOOST_REQUIRE(LookupHost("0.0.0.0", addr, false)); BOOST_REQUIRE(!addr.IsValid()); BOOST_REQUIRE(addr.IsIPv4()); BOOST_CHECK(addr.IsBindAny()); BOOST_CHECK(addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL(addr.ToString(), "0.0.0.0"); // IPv4, INADDR_NONE BOOST_REQUIRE(LookupHost("255.255.255.255", addr, false)); BOOST_REQUIRE(!addr.IsValid()); BOOST_REQUIRE(addr.IsIPv4()); BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK(addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL(addr.ToString(), "255.255.255.255"); // IPv4, casual BOOST_REQUIRE(LookupHost("12.34.56.78", addr, false)); BOOST_REQUIRE(addr.IsValid()); BOOST_REQUIRE(addr.IsIPv4()); BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK(addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL(addr.ToString(), "12.34.56.78"); // IPv6, in6addr_any BOOST_REQUIRE(LookupHost("::", addr, false)); BOOST_REQUIRE(!addr.IsValid()); BOOST_REQUIRE(addr.IsIPv6()); BOOST_CHECK(addr.IsBindAny()); BOOST_CHECK(addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL(addr.ToString(), "::"); // IPv6, casual BOOST_REQUIRE( LookupHost("1122:3344:5566:7788:9900:aabb:ccdd:eeff", addr, false)); BOOST_REQUIRE(addr.IsValid()); 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"); // IPv6, scoped/link-local. See https://tools.ietf.org/html/rfc4007 // We support non-negative decimal integers (uint32_t) as zone id indices. // Test with a fairly-high value, e.g. 32, to avoid locally reserved ids. const std::string link_local{"fe80::1"}; const std::string scoped_addr{link_local + "%32"}; BOOST_REQUIRE(LookupHost(scoped_addr, addr, false)); BOOST_REQUIRE(addr.IsValid()); BOOST_REQUIRE(addr.IsIPv6()); BOOST_CHECK(!addr.IsBindAny()); const std::string addr_str{addr.ToString()}; BOOST_CHECK(addr_str == scoped_addr || addr_str == "fe80:0:0:0:0:0:0:1"); // The fallback case "fe80:0:0:0:0:0:0:1" is needed for macOS 10.14/10.15 // and (probably) later. Test that the delimiter "%" and default zone id of // 0 can be omitted for the default scope. BOOST_REQUIRE(LookupHost(link_local + "%0", addr, false)); BOOST_REQUIRE(addr.IsValid()); BOOST_REQUIRE(addr.IsIPv6()); BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK_EQUAL(addr.ToString(), link_local); // TORv2 BOOST_REQUIRE(addr.SetSpecial("6hzph5hv6337r6p2.onion")); BOOST_REQUIRE(addr.IsValid()); BOOST_REQUIRE(addr.IsTor()); BOOST_CHECK(!addr.IsI2P()); BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK(addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL(addr.ToString(), "6hzph5hv6337r6p2.onion"); // TORv3 const char *torv3_addr = "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion"; BOOST_REQUIRE(addr.SetSpecial(torv3_addr)); BOOST_REQUIRE(addr.IsValid()); BOOST_REQUIRE(addr.IsTor()); BOOST_CHECK(!addr.IsI2P()); BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK(!addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL(addr.ToString(), torv3_addr); // TORv3, broken, with wrong checksum BOOST_CHECK(!addr.SetSpecial( "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscsad.onion")); // TORv3, broken, with wrong version BOOST_CHECK(!addr.SetSpecial( "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscrye.onion")); // TORv3, malicious BOOST_CHECK(!addr.SetSpecial(std::string{ "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd\0wtf.onion", 66})); // TOR, bogus length BOOST_CHECK(!addr.SetSpecial(std::string{"mfrggzak.onion"})); // TOR, invalid base32 BOOST_CHECK(!addr.SetSpecial(std::string{"mf*g zak.onion"})); // I2P const char *i2p_addr = "UDHDrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna.b32.I2P"; BOOST_REQUIRE(addr.SetSpecial(i2p_addr)); BOOST_REQUIRE(addr.IsValid()); BOOST_REQUIRE(addr.IsI2P()); BOOST_CHECK(!addr.IsTor()); BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK(!addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL(addr.ToString(), ToLower(i2p_addr)); // I2P, correct length, but decodes to less than the expected number of // bytes. BOOST_CHECK(!addr.SetSpecial( "udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jn=.b32.i2p")); // I2P, extra unnecessary padding BOOST_CHECK(!addr.SetSpecial( "udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna=.b32.i2p")); // I2P, malicious BOOST_CHECK(!addr.SetSpecial( "udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v\0wtf.b32.i2p"s)); // I2P, valid but unsupported (56 Base32 characters) // See "Encrypted LS with Base 32 Addresses" in // https://geti2p.net/spec/encryptedleaseset.txt BOOST_CHECK(!addr.SetSpecial( "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscsad.b32.i2p")); // I2P, invalid base32 BOOST_CHECK(!addr.SetSpecial(std::string{"tp*szydbh4dp.b32.i2p"})); // Internal addr.SetInternal("esffpp"); // "internal" is considered invalid BOOST_REQUIRE(!addr.IsValid()); BOOST_REQUIRE(addr.IsInternal()); BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK(addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL(addr.ToString(), "esffpvrt3wpeaygy.internal"); // Totally bogus BOOST_CHECK(!addr.SetSpecial("totally bogus")); } BOOST_AUTO_TEST_CASE(cnetaddr_serialize_v1) { CNetAddr addr; CDataStream s(SER_NETWORK, PROTOCOL_VERSION); s << addr; BOOST_CHECK_EQUAL(HexStr(s), "00000000000000000000000000000000"); s.clear(); BOOST_REQUIRE(LookupHost("1.2.3.4", addr, false)); s << addr; BOOST_CHECK_EQUAL(HexStr(s), "00000000000000000000ffff01020304"); s.clear(); BOOST_REQUIRE( LookupHost("1a1b:2a2b:3a3b:4a4b:5a5b:6a6b:7a7b:8a8b", addr, false)); s << addr; BOOST_CHECK_EQUAL(HexStr(s), "1a1b2a2b3a3b4a4b5a5b6a6b7a7b8a8b"); s.clear(); BOOST_REQUIRE(addr.SetSpecial("6hzph5hv6337r6p2.onion")); s << addr; BOOST_CHECK_EQUAL(HexStr(s), "fd87d87eeb43f1f2f3f4f5f6f7f8f9fa"); s.clear(); BOOST_REQUIRE(addr.SetSpecial( "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion")); s << addr; BOOST_CHECK_EQUAL(HexStr(s), "00000000000000000000000000000000"); s.clear(); addr.SetInternal("a"); s << addr; BOOST_CHECK_EQUAL(HexStr(s), "fd6b88c08724ca978112ca1bbdcafac2"); s.clear(); } BOOST_AUTO_TEST_CASE(cnetaddr_serialize_v2) { CNetAddr addr; CDataStream s(SER_NETWORK, PROTOCOL_VERSION); // Add ADDRV2_FORMAT to the version so that the CNetAddr // serialize method produces an address in v2 format. s.SetVersion(s.GetVersion() | ADDRV2_FORMAT); s << addr; BOOST_CHECK_EQUAL(HexStr(s), "021000000000000000000000000000000000"); s.clear(); BOOST_REQUIRE(LookupHost("1.2.3.4", addr, false)); s << addr; BOOST_CHECK_EQUAL(HexStr(s), "010401020304"); s.clear(); BOOST_REQUIRE( LookupHost("1a1b:2a2b:3a3b:4a4b:5a5b:6a6b:7a7b:8a8b", addr, false)); s << addr; BOOST_CHECK_EQUAL(HexStr(s), "02101a1b2a2b3a3b4a4b5a5b6a6b7a7b8a8b"); s.clear(); BOOST_REQUIRE(addr.SetSpecial("6hzph5hv6337r6p2.onion")); s << addr; BOOST_CHECK_EQUAL(HexStr(s), "030af1f2f3f4f5f6f7f8f9fa"); s.clear(); BOOST_REQUIRE(addr.SetSpecial( "kpgvmscirrdqpekbqjsvw5teanhatztpp2gl6eee4zkowvwfxwenqaid.onion")); s << addr; BOOST_CHECK_EQUAL( HexStr(s), "042053cd5648488c4707914182655b7664034e09e66f7e8cbf1084e654eb56c5bd88"); s.clear(); BOOST_REQUIRE(addr.SetInternal("a")); s << addr; BOOST_CHECK_EQUAL(HexStr(s), "0210fd6b88c08724ca978112ca1bbdcafac2"); s.clear(); } BOOST_AUTO_TEST_CASE(cnetaddr_unserialize_v2) { CNetAddr addr; CDataStream s(SER_NETWORK, PROTOCOL_VERSION); // Add ADDRV2_FORMAT to the version so that the CNetAddr // unserialize method expects an address in v2 format. s.SetVersion(s.GetVersion() | ADDRV2_FORMAT); // Valid IPv4. s << MakeSpan(ParseHex("01" // network type (IPv4) "04" // address length "01020304")); // address 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()); // Invalid IPv4, valid length but address itself is shorter. s << MakeSpan(ParseHex("01" // network type (IPv4) "04" // address length "0102")); // address BOOST_CHECK_EXCEPTION(s >> addr, std::ios_base::failure, HasReason("end of data")); BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input. s.clear(); // Invalid IPv4, with bogus length. s << MakeSpan(ParseHex("01" // network type (IPv4) "05" // address length "01020304")); // address BOOST_CHECK_EXCEPTION( s >> addr, std::ios_base::failure, HasReason("BIP155 IPv4 address with length 5 (should be 4)")); BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input. s.clear(); // Invalid IPv4, with extreme length. s << MakeSpan(ParseHex("01" // network type (IPv4) "fd0102" // address length (513 as CompactSize) "01020304")); // address BOOST_CHECK_EXCEPTION(s >> addr, std::ios_base::failure, HasReason("Address too long: 513 > 512")); BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input. s.clear(); // Valid IPv6. s << MakeSpan(ParseHex("02" // network type (IPv6) "10" // address length "0102030405060708090a0b0c0d0e0f10")); // address 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()); // Valid IPv6, contains embedded "internal". s << MakeSpan( ParseHex("02" // network type (IPv6) "10" // address length "fd6b88c08724ca978112ca1bbdcafac2")); // address: 0xfd + // sha256("bitcoin")[0:5] // + 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()); // Invalid IPv6, with bogus length. s << MakeSpan(ParseHex("02" // network type (IPv6) "04" // address length "00")); // address BOOST_CHECK_EXCEPTION( s >> addr, std::ios_base::failure, HasReason("BIP155 IPv6 address with length 4 (should be 16)")); BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input. s.clear(); // Invalid IPv6, contains embedded IPv4. s << MakeSpan(ParseHex("02" // network type (IPv6) "10" // address length "00000000000000000000ffff01020304")); // address s >> addr; BOOST_CHECK(!addr.IsValid()); BOOST_REQUIRE(s.empty()); // Invalid IPv6, contains embedded TORv2. s << MakeSpan(ParseHex("02" // network type (IPv6) "10" // address length "fd87d87eeb430102030405060708090a")); // address s >> addr; BOOST_CHECK(!addr.IsValid()); BOOST_REQUIRE(s.empty()); // Valid TORv2. s << MakeSpan(ParseHex("03" // network type (TORv2) "0a" // address length "f1f2f3f4f5f6f7f8f9fa")); // address 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()); // Invalid TORv2, with bogus length. s << MakeSpan(ParseHex("03" // network type (TORv2) "07" // address length "00")); // address BOOST_CHECK_EXCEPTION( s >> addr, std::ios_base::failure, HasReason("BIP155 TORv2 address with length 7 (should be 10)")); BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input. s.clear(); // Valid TORv3. s << MakeSpan(ParseHex("04" // network type (TORv3) "20" // address length "79bcc625184b05194975c28b66b66b04" // address "69f7f6556fb1ac3189a79b40dda32f1f")); s >> addr; BOOST_CHECK(addr.IsValid()); BOOST_CHECK(addr.IsTor()); BOOST_CHECK(!addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL( addr.ToString(), "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion"); BOOST_REQUIRE(s.empty()); // Invalid TORv3, with bogus length. s << MakeSpan(ParseHex("04" // network type (TORv3) "00" // address length "00" // address )); BOOST_CHECK_EXCEPTION( s >> addr, std::ios_base::failure, HasReason("BIP155 TORv3 address with length 0 (should be 32)")); BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input. s.clear(); // Valid I2P. s << MakeSpan(ParseHex("05" // network type (I2P) "20" // address length "a2894dabaec08c0051a481a6dac88b64" // address "f98232ae42d4b6fd2fa81952dfe36a87")); s >> addr; BOOST_CHECK(addr.IsValid()); BOOST_CHECK(addr.IsI2P()); BOOST_CHECK(!addr.IsAddrV1Compatible()); BOOST_CHECK_EQUAL( addr.ToString(), "ukeu3k5oycgaauneqgtnvselmt4yemvoilkln7jpvamvfx7dnkdq.b32.i2p"); BOOST_REQUIRE(s.empty()); // Invalid I2P, with bogus length. s << MakeSpan(ParseHex("05" // network type (I2P) "03" // address length "00" // address )); BOOST_CHECK_EXCEPTION( s >> addr, std::ios_base::failure, HasReason("BIP155 I2P address with length 3 (should be 32)")); BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input. s.clear(); // Valid CJDNS. s << MakeSpan(ParseHex("06" // network type (CJDNS) "10" // address length "fc000001000200030004000500060007" // address )); 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()); // Invalid CJDNS, with bogus length. s << MakeSpan(ParseHex("06" // network type (CJDNS) "01" // address length "00" // address )); BOOST_CHECK_EXCEPTION( s >> addr, std::ios_base::failure, HasReason("BIP155 CJDNS address with length 1 (should be 16)")); BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input. s.clear(); // Unknown, with extreme length. s << MakeSpan( ParseHex("aa" // network type (unknown) "fe00000002" // address length (CompactSize's MAX_SIZE) "01020304050607" // address )); BOOST_CHECK_EXCEPTION(s >> addr, std::ios_base::failure, HasReason("Address too long: 33554432 > 512")); BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input. s.clear(); // Unknown, with reasonable length. s << MakeSpan(ParseHex("aa" // network type (unknown) "04" // address length "01020304" // address )); s >> addr; BOOST_CHECK(!addr.IsValid()); BOOST_REQUIRE(s.empty()); // Unknown, with zero length. s << MakeSpan(ParseHex("aa" // network type (unknown) "00" // address length "" // address )); s >> addr; BOOST_CHECK(!addr.IsValid()); BOOST_REQUIRE(s.empty()); } // prior to PR #14728, this test triggers an undefined behavior BOOST_AUTO_TEST_CASE(ipv4_peer_with_ipv6_addrMe_test) { // set up local addresses; all that's necessary to reproduce the bug is // that a normal IPv4 address is among the entries, but if this address is // !IsRoutable the undefined behavior is easier to trigger deterministically { LOCK(cs_mapLocalHost); in_addr ipv4AddrLocal; ipv4AddrLocal.s_addr = 0x0100007f; CNetAddr addr = CNetAddr(ipv4AddrLocal); LocalServiceInfo lsi; lsi.nScore = 23; lsi.nPort = 42; mapLocalHost[addr] = lsi; } // create a peer with an IPv4 address in_addr ipv4AddrPeer; ipv4AddrPeer.s_addr = 0xa0b0c001; CAddress addr = CAddress(CService(ipv4AddrPeer, 7777), NODE_NETWORK); std::unique_ptr pnode = std::make_unique( 0, NODE_NETWORK, INVALID_SOCKET, addr, /* nKeyedNetGroupIn */ 0, /* nLocalHostNonceIn */ 0, /* nLocalExtraEntropyIn */ 0, CAddress{}, /* pszDest */ std::string{}, ConnectionType::OUTBOUND_FULL_RELAY, /* inbound_onion = */ false); pnode->fSuccessfullyConnected.store(true); // the peer claims to be reaching us via IPv6 in6_addr ipv6AddrLocal; memset(ipv6AddrLocal.s6_addr, 0, 16); ipv6AddrLocal.s6_addr[0] = 0xcc; CAddress addrLocal = CAddress(CService(ipv6AddrLocal, 7777), NODE_NETWORK); pnode->SetAddrLocal(addrLocal); // before patch, this causes undefined behavior detectable with clang's // -fsanitize=memory GetLocalAddrForPeer(&*pnode); // suppress no-checks-run warning; if this test fails, it's by triggering a // sanitizer BOOST_CHECK(1); } BOOST_AUTO_TEST_CASE(avalanche_statistics) { const uint32_t step = AVALANCHE_STATISTICS_REFRESH_PERIOD.count(); const uint32_t tau = AVALANCHE_STATISTICS_TIME_CONSTANT.count(); CNode::AvalancheState avastats; double previousScore = avastats.getAvailabilityScore(); BOOST_CHECK_SMALL(previousScore, 1e-6); // Check the statistics follow an exponential response for 1 to 10 tau for (size_t i = 1; i <= 10; i++) { for (uint32_t j = 0; j < tau; j += step) { avastats.invsPolled(1); // Always respond to everything correctly avastats.invsVoted(1); avastats.updateAvailabilityScore(); // Expect a monotonic rise double currentScore = avastats.getAvailabilityScore(); BOOST_CHECK_GE(currentScore, previousScore); previousScore = currentScore; } // We expect (1 - e^-i) after i * tau. The tolerance is expressed // as a percentage, and we add a (large) 0.1% margin to account for // floating point errors. BOOST_CHECK_CLOSE(previousScore, -1 * std::expm1(-1. * i), 100.1 / tau); } // After 10 tau we should be very close to 100% (about 99.995%) BOOST_CHECK_CLOSE(previousScore, 1., 0.01); for (size_t i = 1; i <= 3; i++) { for (uint32_t j = 0; j < tau; j += step) { avastats.invsPolled(2); // Stop responding to the polls. avastats.invsVoted(1); avastats.updateAvailabilityScore(); // Expect a monotonic fall double currentScore = avastats.getAvailabilityScore(); BOOST_CHECK_LE(currentScore, previousScore); previousScore = currentScore; } // There is a slight error in the expected value because we did not // start the decay at exactly 100%, but the 0.1% margin is at least an // order of magnitude larger than the expected error so it doesn't // matter. BOOST_CHECK_CLOSE(previousScore, 1. + std::expm1(-1. * i), 100.1 / tau); } // After 3 more tau we should be under 5% BOOST_CHECK_LT(previousScore, .05); for (size_t i = 1; i <= 100; i++) { avastats.invsPolled(10); // Completely stop responding to the polls. avastats.invsVoted(0); avastats.updateAvailabilityScore(); // It's still a monotonic fall, and the score should turn negative. double currentScore = avastats.getAvailabilityScore(); BOOST_CHECK_LE(currentScore, previousScore); BOOST_CHECK_LE(currentScore, 0.); previousScore = currentScore; } } BOOST_AUTO_TEST_CASE(get_extra_full_outbound_count) { CConnmanTest connman(GetConfig(), 0x1337, 0x1337); auto checkExtraFullOutboundCount = [&](size_t fullOutboundCount, size_t avalancheOutboundCount, int expectedExtraCount) { connman.ClearNodes(); for (size_t i = 0; i < fullOutboundCount; i++) { connman.AddNode(ConnectionType::OUTBOUND_FULL_RELAY); } for (size_t i = 0; i < avalancheOutboundCount; i++) { connman.AddNode(ConnectionType::AVALANCHE_OUTBOUND); } BOOST_CHECK_EQUAL(connman.GetExtraFullOutboundCount(), expectedExtraCount); }; connman.SetMaxOutbounds(0, 0); checkExtraFullOutboundCount(0, 0, 0); checkExtraFullOutboundCount(1, 0, 1); checkExtraFullOutboundCount(0, 1, 1); checkExtraFullOutboundCount(5, 5, 10); connman.SetMaxOutbounds(4, 0); checkExtraFullOutboundCount(0, 0, 0); checkExtraFullOutboundCount(1, 0, 0); checkExtraFullOutboundCount(0, 1, 0); checkExtraFullOutboundCount(4, 0, 0); checkExtraFullOutboundCount(0, 4, 0); checkExtraFullOutboundCount(2, 2, 0); checkExtraFullOutboundCount(5, 5, 6); connman.SetMaxOutbounds(4, 4); checkExtraFullOutboundCount(0, 0, 0); checkExtraFullOutboundCount(1, 0, 0); checkExtraFullOutboundCount(0, 1, 0); checkExtraFullOutboundCount(4, 0, 0); checkExtraFullOutboundCount(0, 4, 0); checkExtraFullOutboundCount(4, 4, 0); checkExtraFullOutboundCount(5, 5, 2); } BOOST_AUTO_TEST_SUITE_END()