diff --git a/src/addrman.cpp b/src/addrman.cpp index 80b753f5fe..2caedd3f12 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -949,6 +949,29 @@ std::pair AddrManImpl::SelectTriedCollision_() return {info_old, info_old.nLastTry}; } +std::optional AddrManImpl::FindAddressEntry_(const CAddress& addr) +{ + AssertLockHeld(cs); + + AddrInfo* addr_info = Find(addr); + + if (!addr_info) return std::nullopt; + + if(addr_info->fInTried) { + int bucket{addr_info->GetTriedBucket(nKey, m_asmap)}; + return AddressPosition(/*tried=*/true, + /*multiplicity=*/1, + /*bucket=*/bucket, + /*position=*/addr_info->GetBucketPosition(nKey, false, bucket)); + } else { + int bucket{addr_info->GetNewBucket(nKey, m_asmap)}; + return AddressPosition(/*tried=*/false, + /*multiplicity=*/addr_info->nRefCount, + /*bucket=*/bucket, + /*position=*/addr_info->GetBucketPosition(nKey, true, bucket)); + } +} + void AddrManImpl::Check() const { AssertLockHeld(cs); @@ -1135,6 +1158,15 @@ void AddrManImpl::SetServices(const CService& addr, ServiceFlags nServices) Check(); } +std::optional AddrManImpl::FindAddressEntry(const CAddress& addr) +{ + LOCK(cs); + Check(); + auto entry = FindAddressEntry_(addr); + Check(); + return entry; +} + AddrInfo AddrManImpl::GetAddressInfo(const CService& addr) { AddrInfo addrRet; @@ -1236,3 +1268,8 @@ const std::vector& AddrMan::GetAsmap() const { return m_impl->GetAsmap(); } + +std::optional AddrMan::FindAddressEntry(const CAddress& addr) +{ + return m_impl->FindAddressEntry(addr); +} diff --git a/src/addrman.h b/src/addrman.h index 8b5a8af738..10b81728bf 100644 --- a/src/addrman.h +++ b/src/addrman.h @@ -23,6 +23,31 @@ class AddrManImpl; /** Default for -checkaddrman */ static constexpr int32_t DEFAULT_ADDRMAN_CONSISTENCY_CHECKS{0}; +/** Test-only struct, capturing info about an address in AddrMan */ +struct AddressPosition { + // Whether the address is in the new or tried table + const bool tried; + + // Addresses in the tried table should always have a multiplicity of 1. + // Addresses in the new table can have multiplicity between 1 and + // ADDRMAN_NEW_BUCKETS_PER_ADDRESS + const int multiplicity; + + // If the address is in the new table, the bucket and position are + // populated based on the first source who sent the address. + // In certain edge cases, this may not be where the address is currently + // located. + const int bucket; + const int position; + + bool operator==(AddressPosition other) { + return std::tie(tried, multiplicity, bucket, position) == + std::tie(other.tried, other.multiplicity, other.bucket, other.position); + } + explicit AddressPosition(bool tried_in, int multiplicity_in, int bucket_in, int position_in) + : tried{tried_in}, multiplicity{multiplicity_in}, bucket{bucket_in}, position{position_in} {} +}; + class DbInconsistentError : public std::exception { using std::exception::exception; @@ -156,6 +181,15 @@ public: AddrInfo GetAddressInfo(const CService& addr); const std::vector& GetAsmap() const; + + /** Test-only function + * Find the address record in AddrMan and return information about its + * position. + * @param[in] addr The address record to look up. + * @return Information about the address record in AddrMan + * or nullopt if address is not found. + */ + std::optional FindAddressEntry(const CAddress& addr); }; #endif // BITCOIN_ADDRMAN_H diff --git a/src/addrman_impl.h b/src/addrman_impl.h index 78a9efb57b..dab89b752d 100644 --- a/src/addrman_impl.h +++ b/src/addrman_impl.h @@ -137,11 +137,14 @@ public: void SetServices(const CService& addr, ServiceFlags nServices) EXCLUSIVE_LOCKS_REQUIRED(!cs); - AddrInfo GetAddressInfo(const CService& addr); + std::optional FindAddressEntry(const CAddress& addr) + EXCLUSIVE_LOCKS_REQUIRED(!cs); + + AddrInfo GetAddressInfo(const CService& addr) + EXCLUSIVE_LOCKS_REQUIRED(!cs); const std::vector& GetAsmap() const; - friend class AddrManTest; friend class AddrManDeterministic; private: @@ -270,6 +273,8 @@ private: std::pair SelectTriedCollision_() EXCLUSIVE_LOCKS_REQUIRED(cs); + std::optional FindAddressEntry_(const CAddress& addr) EXCLUSIVE_LOCKS_REQUIRED(cs); + //! Consistency check, taking into account m_consistency_check_ratio. //! Will std::abort if an inconsistency is detected. void Check() const EXCLUSIVE_LOCKS_REQUIRED(cs); diff --git a/src/test/addrman_tests.cpp b/src/test/addrman_tests.cpp index a5018b3a27..534a3b7152 100644 --- a/src/test/addrman_tests.cpp +++ b/src/test/addrman_tests.cpp @@ -20,59 +20,6 @@ using namespace std::literals; -class AddrManTest : public AddrMan -{ -public: - explicit AddrManTest(std::vector asmap = std::vector()) - : AddrMan(asmap, /*deterministic=*/true, /* consistency_check_ratio */ 100) - {} - - AddrInfo* Find(const CService& addr) - { - LOCK(m_impl->cs); - return m_impl->Find(addr); - } - - AddrInfo* Create(const CAddress& addr, const CNetAddr& addrSource, int* pnId) - { - LOCK(m_impl->cs); - return m_impl->Create(addr, addrSource, pnId); - } - - void Delete(int nId) - { - LOCK(m_impl->cs); - m_impl->Delete(nId); - } - - // Used to test deserialization - std::pair GetBucketAndEntry(const CAddress& addr) - { - LOCK(m_impl->cs); - int nId = m_impl->mapAddr[addr]; - for (int bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; ++bucket) { - for (int entry = 0; entry < ADDRMAN_BUCKET_SIZE; ++entry) { - if (nId == m_impl->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) - { - int64_t nLastSuccess = 1; - // Set last good connection in the deep past. - Good(addr, nLastSuccess); - - bool count_failure = false; - int64_t nLastTry = GetAdjustedTime() - 61; - Attempt(addr, count_failure, nLastTry); - } -}; - static CNetAddr ResolveIP(const std::string& ip) { CNetAddr addr; @@ -100,12 +47,17 @@ static std::vector FromBytes(const unsigned char* source, int vector_size) return result; } +/* Utility function to create a deterministic addrman, as used in most tests */ +static std::unique_ptr TestAddrMan(std::vector asmap = std::vector()) +{ + return std::make_unique(asmap, /*deterministic=*/true, /*consistency_check_ratio=*/100); +} BOOST_FIXTURE_TEST_SUITE(addrman_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(addrman_simple) { - auto addrman = std::make_unique(); + auto addrman = TestAddrMan(); CNetAddr source = ResolveIP("252.2.2.2"); @@ -139,7 +91,7 @@ BOOST_AUTO_TEST_CASE(addrman_simple) BOOST_CHECK(addrman->size() >= 1); // Test: reset addrman and test AddrMan::Add multiple addresses works as expected - addrman = std::make_unique(); + addrman = TestAddrMan(); 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)); @@ -149,57 +101,57 @@ BOOST_AUTO_TEST_CASE(addrman_simple) BOOST_AUTO_TEST_CASE(addrman_ports) { - AddrManTest addrman; + auto addrman = TestAddrMan(); CNetAddr source = ResolveIP("252.2.2.2"); - BOOST_CHECK_EQUAL(addrman.size(), 0U); + BOOST_CHECK_EQUAL(addrman->size(), 0U); // Test 7; 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); + 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(), 2U); - auto addr_ret2 = addrman.Select().first; + BOOST_CHECK(addrman->Add({CAddress(addr1_port, NODE_NONE)}, source)); + BOOST_CHECK_EQUAL(addrman->size(), 2U); + auto addr_ret2 = addrman->Select().first; BOOST_CHECK(addr_ret2.ToString() == "250.1.1.1:8333" || addr_ret2.ToString() == "250.1.1.1:8334"); // Test: Add same IP but diff port to tried table; this converts the entry with // the specified port to tried, but not the other. - addrman.Good(CAddress(addr1_port, NODE_NONE)); - BOOST_CHECK_EQUAL(addrman.size(), 2U); + addrman->Good(CAddress(addr1_port, NODE_NONE)); + BOOST_CHECK_EQUAL(addrman->size(), 2U); bool newOnly = true; - auto addr_ret3 = addrman.Select(newOnly).first; + auto addr_ret3 = addrman->Select(newOnly).first; BOOST_CHECK_EQUAL(addr_ret3.ToString(), "250.1.1.1:8333"); } BOOST_AUTO_TEST_CASE(addrman_select) { - AddrManTest addrman; + auto addrman = TestAddrMan(); 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); + BOOST_CHECK(addrman->Add({CAddress(addr1, NODE_NONE)}, source)); + BOOST_CHECK_EQUAL(addrman->size(), 1U); bool newOnly = true; - auto addr_ret1 = addrman.Select(newOnly).first; + auto addr_ret1 = addrman->Select(newOnly).first; BOOST_CHECK_EQUAL(addr_ret1.ToString(), "250.1.1.1:8333"); // Test: move addr to tried, select from new expected nothing returned. - BOOST_CHECK(addrman.Good(CAddress(addr1, NODE_NONE))); - BOOST_CHECK_EQUAL(addrman.size(), 1U); - auto addr_ret2 = addrman.Select(newOnly).first; + BOOST_CHECK(addrman->Good(CAddress(addr1, NODE_NONE))); + BOOST_CHECK_EQUAL(addrman->size(), 1U); + auto addr_ret2 = addrman->Select(newOnly).first; BOOST_CHECK_EQUAL(addr_ret2.ToString(), "[::]:0"); - auto addr_ret3 = addrman.Select().first; + auto addr_ret3 = addrman->Select().first; BOOST_CHECK_EQUAL(addr_ret3.ToString(), "250.1.1.1:8333"); - BOOST_CHECK_EQUAL(addrman.size(), 1U); + BOOST_CHECK_EQUAL(addrman->size(), 1U); // Add three addresses to new table. @@ -207,65 +159,97 @@ BOOST_AUTO_TEST_CASE(addrman_select) 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))); + 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))); - BOOST_CHECK(addrman.Good(CAddress(addr5, NODE_NONE))); - BOOST_CHECK(addrman.Add({CAddress(addr6, NODE_NONE)}, ResolveService("250.3.1.1", 8333))); - BOOST_CHECK(addrman.Good(CAddress(addr6, NODE_NONE))); - BOOST_CHECK(addrman.Add({CAddress(addr7, NODE_NONE)}, ResolveService("250.1.1.3", 8333))); - BOOST_CHECK(addrman.Good(CAddress(addr7, NODE_NONE))); + BOOST_CHECK(addrman->Add({CAddress(addr5, NODE_NONE)}, ResolveService("250.3.1.1", 8333))); + BOOST_CHECK(addrman->Good(CAddress(addr5, NODE_NONE))); + BOOST_CHECK(addrman->Add({CAddress(addr6, NODE_NONE)}, ResolveService("250.3.1.1", 8333))); + BOOST_CHECK(addrman->Good(CAddress(addr6, NODE_NONE))); + BOOST_CHECK(addrman->Add({CAddress(addr7, NODE_NONE)}, ResolveService("250.1.1.3", 8333))); + BOOST_CHECK(addrman->Good(CAddress(addr7, NODE_NONE))); // Test: 6 addrs + 1 addr from last test = 7. - BOOST_CHECK_EQUAL(addrman.size(), 7U); + 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().first.GetPort()); + ports.insert(addrman->Select().first.GetPort()); } BOOST_CHECK_EQUAL(ports.size(), 3U); } BOOST_AUTO_TEST_CASE(addrman_new_collisions) { - AddrManTest addrman; + auto addrman = TestAddrMan(); CNetAddr source = ResolveIP("252.2.2.2"); uint32_t num_addrs{0}; - BOOST_CHECK_EQUAL(addrman.size(), num_addrs); + BOOST_CHECK_EQUAL(addrman->size(), num_addrs); while (num_addrs < 22) { // Magic number! 250.1.1.1 - 250.1.1.22 do not collide with deterministic key = 1 CService addr = ResolveService("250.1.1." + ToString(++num_addrs)); - BOOST_CHECK(addrman.Add({CAddress(addr, NODE_NONE)}, source)); + BOOST_CHECK(addrman->Add({CAddress(addr, NODE_NONE)}, source)); // Test: No collision in new table yet. - BOOST_CHECK_EQUAL(addrman.size(), num_addrs); + BOOST_CHECK_EQUAL(addrman->size(), num_addrs); } // Test: new table collision! CService addr1 = ResolveService("250.1.1." + ToString(++num_addrs)); uint32_t collisions{1}; - BOOST_CHECK(addrman.Add({CAddress(addr1, NODE_NONE)}, source)); - BOOST_CHECK_EQUAL(addrman.size(), num_addrs - collisions); + BOOST_CHECK(addrman->Add({CAddress(addr1, NODE_NONE)}, source)); + BOOST_CHECK_EQUAL(addrman->size(), num_addrs - collisions); CService addr2 = ResolveService("250.1.1." + ToString(++num_addrs)); - BOOST_CHECK(addrman.Add({CAddress(addr2, NODE_NONE)}, source)); - BOOST_CHECK_EQUAL(addrman.size(), num_addrs - collisions); + BOOST_CHECK(addrman->Add({CAddress(addr2, NODE_NONE)}, source)); + BOOST_CHECK_EQUAL(addrman->size(), num_addrs - collisions); +} + +BOOST_AUTO_TEST_CASE(addrman_new_multiplicity) +{ + auto addrman = TestAddrMan(); + CAddress addr{CAddress(ResolveService("253.3.3.3", 8333), NODE_NONE)}; + int64_t start_time{GetAdjustedTime()}; + addr.nTime = start_time; + + // test that multiplicity stays at 1 if nTime doesn't increase + for (unsigned int i = 1; i < 20; ++i) { + std::string addr_ip{ToString(i % 256) + "." + ToString(i >> 8 % 256) + ".1.1"}; + CNetAddr source{ResolveIP(addr_ip)}; + addrman->Add({addr}, source); + } + AddressPosition addr_pos = addrman->FindAddressEntry(addr).value(); + BOOST_CHECK_EQUAL(addr_pos.multiplicity, 1U); + BOOST_CHECK_EQUAL(addrman->size(), 1U); + + // if nTime increases, an addr can occur in up to 8 buckets + // The acceptance probability decreases exponentially with existing multiplicity - + // choose number of iterations such that it gets to 8 with deterministic addrman. + for (unsigned int i = 1; i < 400; ++i) { + std::string addr_ip{ToString(i % 256) + "." + ToString(i >> 8 % 256) + ".1.1"}; + CNetAddr source{ResolveIP(addr_ip)}; + addr.nTime = start_time + i; + addrman->Add({addr}, source); + } + AddressPosition addr_pos_multi = addrman->FindAddressEntry(addr).value(); + BOOST_CHECK_EQUAL(addr_pos_multi.multiplicity, 8U); + // multiplicity doesn't affect size + BOOST_CHECK_EQUAL(addrman->size(), 1U); } BOOST_AUTO_TEST_CASE(addrman_tried_collisions) { - auto addrman = std::make_unique(std::vector(), /*deterministic=*/true, /*consistency_check_ratio=*/100); + auto addrman = TestAddrMan(); CNetAddr source = ResolveIP("252.2.2.2"); @@ -293,87 +277,15 @@ BOOST_AUTO_TEST_CASE(addrman_tried_collisions) BOOST_CHECK(addrman->Good(CAddress(addr2, NODE_NONE))); } -BOOST_AUTO_TEST_CASE(addrman_find) -{ - AddrManTest 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/port matching what we searched on. - AddrInfo* info1 = addrman.Find(addr1); - BOOST_REQUIRE(info1); - BOOST_CHECK_EQUAL(info1->ToString(), "250.1.2.1:8333"); - - // Test; Find discriminates by port number. - AddrInfo* info2 = addrman.Find(addr2); - BOOST_REQUIRE(info2); - BOOST_CHECK_EQUAL(info2->ToString(), "250.1.2.1:9999"); - - // Test: Find returns another IP matching what we searched on. - AddrInfo* info3 = addrman.Find(addr3); - BOOST_REQUIRE(info3); - BOOST_CHECK_EQUAL(info3->ToString(), "251.255.2.1:8333"); -} - -BOOST_AUTO_TEST_CASE(addrman_create) -{ - AddrManTest 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; - AddrInfo* 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"); - - AddrInfo* info2 = addrman.Find(addr1); - BOOST_CHECK_EQUAL(info2->ToString(), "250.1.2.1:8333"); -} - - -BOOST_AUTO_TEST_CASE(addrman_delete) -{ - AddrManTest 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); - AddrInfo* info2 = addrman.Find(addr1); - BOOST_CHECK(info2 == nullptr); -} BOOST_AUTO_TEST_CASE(addrman_getaddr) { - AddrManTest addrman; + auto addrman = TestAddrMan(); // 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(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); @@ -390,18 +302,18 @@ BOOST_AUTO_TEST_CASE(addrman_getaddr) CNetAddr source2 = ResolveIP("250.2.3.3"); // Test: Ensure GetAddr works with new addresses. - BOOST_CHECK(addrman.Add({addr1, addr3, addr5}, source1)); - BOOST_CHECK(addrman.Add({addr2, addr4}, source2)); + BOOST_CHECK(addrman->Add({addr1, addr3, addr5}, source1)); + BOOST_CHECK(addrman->Add({addr2, addr4}, source2)); - BOOST_CHECK_EQUAL(addrman.GetAddr(/* max_addresses */ 0, /* max_pct */ 0, /* network */ std::nullopt).size(), 5U); + 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); + 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); + BOOST_CHECK(addrman->Good(CAddress(addr1, NODE_NONE))); + BOOST_CHECK(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++) { @@ -412,24 +324,22 @@ BOOST_AUTO_TEST_CASE(addrman_getaddr) // Ensure that for all addrs in addrman, isTerrible == false. addr.nTime = GetAdjustedTime(); - addrman.Add({addr}, ResolveIP(strAddr)); + addrman->Add({addr}, ResolveIP(strAddr)); if (i % 8 == 0) - addrman.Good(addr); + addrman->Good(addr); } - std::vector vAddr = addrman.GetAddr(/* max_addresses */ 2500, /* max_pct */ 23, /* network */ std::nullopt); + std::vector vAddr = addrman->GetAddr(/*max_addresses=*/2500, /*max_pct=*/23, /*network=*/std::nullopt); - size_t percent23 = (addrman.size() * 23) / 100; + 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_CHECK_EQUAL(addrman->size(), 2006U); } BOOST_AUTO_TEST_CASE(caddrinfo_get_tried_bucket_legacy) { - AddrManTest addrman; - CAddress addr1 = CAddress(ResolveService("250.1.1.1", 8333), NODE_NONE); CAddress addr2 = CAddress(ResolveService("250.1.1.1", 9999), NODE_NONE); @@ -483,8 +393,6 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_tried_bucket_legacy) BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket_legacy) { - AddrManTest addrman; - CAddress addr1 = CAddress(ResolveService("250.1.2.1", 8333), NODE_NONE); CAddress addr2 = CAddress(ResolveService("250.1.2.1", 9999), NODE_NONE); @@ -561,8 +469,6 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket_legacy) // 101.8.0.0/16 AS8 BOOST_AUTO_TEST_CASE(caddrinfo_get_tried_bucket) { - AddrManTest addrman; - CAddress addr1 = CAddress(ResolveService("250.1.1.1", 8333), NODE_NONE); CAddress addr2 = CAddress(ResolveService("250.1.1.1", 9999), NODE_NONE); @@ -616,8 +522,6 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_tried_bucket) BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket) { - AddrManTest addrman; - CAddress addr1 = CAddress(ResolveService("250.1.2.1", 8333), NODE_NONE); CAddress addr2 = CAddress(ResolveService("250.1.2.1", 9999), NODE_NONE); @@ -697,72 +601,69 @@ BOOST_AUTO_TEST_CASE(addrman_serialization) { std::vector asmap1 = FromBytes(raw_tests::asmap, sizeof(raw_tests::asmap) * 8); - auto addrman_asmap1 = std::make_unique(asmap1); - auto addrman_asmap1_dup = std::make_unique(asmap1); - auto addrman_noasmap = std::make_unique(); + auto addrman_asmap1 = TestAddrMan(asmap1); + auto addrman_asmap1_dup = TestAddrMan(asmap1); + auto addrman_noasmap = TestAddrMan(); 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); + AddressPosition addr_pos1 = addrman_asmap1->FindAddressEntry(addr).value(); + AddressPosition addr_pos2 = addrman_asmap1_dup->FindAddressEntry(addr).value(); + BOOST_CHECK(addr_pos1.multiplicity != 0); + BOOST_CHECK(addr_pos2.multiplicity != 0); - BOOST_CHECK(bucketAndEntry_asmap1.first == bucketAndEntry_asmap1_dup.first); - BOOST_CHECK(bucketAndEntry_asmap1.second == bucketAndEntry_asmap1_dup.second); + BOOST_CHECK(addr_pos1 == addr_pos2); // 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); + AddressPosition addr_pos3 = addrman_noasmap->FindAddressEntry(addr).value(); + BOOST_CHECK(addr_pos3.multiplicity != 0); + BOOST_CHECK(addr_pos1.bucket != addr_pos3.bucket); + BOOST_CHECK(addr_pos1.position != addr_pos3.position); // deserializing non-asmaped peers.dat to asmaped addrman - addrman_asmap1 = std::make_unique(asmap1); - addrman_noasmap = std::make_unique(); + addrman_asmap1 = TestAddrMan(asmap1); + addrman_noasmap = TestAddrMan(); 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); + + AddressPosition addr_pos4 = addrman_asmap1->FindAddressEntry(addr).value(); + BOOST_CHECK(addr_pos4.multiplicity != 0); + BOOST_CHECK(addr_pos4.bucket != addr_pos3.bucket); + BOOST_CHECK(addr_pos4 == addr_pos2); // used to map to different buckets, now maps to the same bucket. - addrman_asmap1 = std::make_unique(asmap1); - addrman_noasmap = std::make_unique(); + addrman_asmap1 = TestAddrMan(asmap1); + addrman_noasmap = TestAddrMan(); CAddress addr1 = CAddress(ResolveService("250.1.1.1"), NODE_NONE); CAddress addr2 = CAddress(ResolveService("250.2.1.1"), NODE_NONE); addrman_noasmap->Add({addr, 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); + AddressPosition addr_pos5 = addrman_noasmap->FindAddressEntry(addr1).value(); + AddressPosition addr_pos6 = addrman_noasmap->FindAddressEntry(addr2).value(); + BOOST_CHECK(addr_pos5.bucket != addr_pos6.bucket); 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); + AddressPosition addr_pos7 = addrman_asmap1->FindAddressEntry(addr1).value(); + AddressPosition addr_pos8 = addrman_asmap1->FindAddressEntry(addr2).value(); + BOOST_CHECK(addr_pos7.bucket == addr_pos8.bucket); + BOOST_CHECK(addr_pos7.position != addr_pos8.position); } BOOST_AUTO_TEST_CASE(remove_invalid) { // Confirm that invalid addresses are ignored in unserialization. - auto addrman = std::make_unique(); + auto addrman = TestAddrMan(); CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); const CAddress new1{ResolveService("5.5.5.5"), NODE_NONE}; @@ -794,29 +695,29 @@ BOOST_AUTO_TEST_CASE(remove_invalid) BOOST_REQUIRE(pos + sizeof(tried2_raw_replacement) <= stream.size()); memcpy(stream.data() + pos, tried2_raw_replacement, sizeof(tried2_raw_replacement)); - addrman = std::make_unique(); + addrman = TestAddrMan(); stream >> *addrman; BOOST_CHECK_EQUAL(addrman->size(), 2); } BOOST_AUTO_TEST_CASE(addrman_selecttriedcollision) { - AddrManTest addrman; + auto addrman = TestAddrMan(); - BOOST_CHECK(addrman.size() == 0); + BOOST_CHECK(addrman->size() == 0); // Empty addrman should return blank addrman info. - BOOST_CHECK(addrman.SelectTriedCollision().first.ToString() == "[::]:0"); + BOOST_CHECK(addrman->SelectTriedCollision().first.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)); + BOOST_CHECK(addrman->Add({CAddress(addr, NODE_NONE)}, source)); // No collisions in tried. - BOOST_CHECK(addrman.Good(addr)); - BOOST_CHECK(addrman.SelectTriedCollision().first.ToString() == "[::]:0"); + BOOST_CHECK(addrman->Good(addr)); + BOOST_CHECK(addrman->SelectTriedCollision().first.ToString() == "[::]:0"); } // Ensure Good handles duplicates well. @@ -825,114 +726,125 @@ BOOST_AUTO_TEST_CASE(addrman_selecttriedcollision) CService addr = ResolveService("250.1.1." + ToString(i)); // Unable to add duplicate address to tried table. - BOOST_CHECK(!addrman.Good(addr)); + BOOST_CHECK(!addrman->Good(addr)); // Verify duplicate address not marked as a collision. - BOOST_CHECK(addrman.SelectTriedCollision().first.ToString() == "[::]:0"); + BOOST_CHECK(addrman->SelectTriedCollision().first.ToString() == "[::]:0"); } } BOOST_AUTO_TEST_CASE(addrman_noevict) { - AddrManTest addrman; + auto addrman = TestAddrMan(); // Add 35 addresses. CNetAddr source = ResolveIP("252.2.2.2"); for (unsigned int i = 1; i < 36; i++) { CService addr = ResolveService("250.1.1." + ToString(i)); - BOOST_CHECK(addrman.Add({CAddress(addr, NODE_NONE)}, source)); + BOOST_CHECK(addrman->Add({CAddress(addr, NODE_NONE)}, source)); // No collision yet. - BOOST_CHECK(addrman.Good(addr)); + BOOST_CHECK(addrman->Good(addr)); } // Collision in tried table between 36 and 19. CService addr36 = ResolveService("250.1.1.36"); - BOOST_CHECK(addrman.Add({CAddress(addr36, NODE_NONE)}, source)); - BOOST_CHECK(!addrman.Good(addr36)); - BOOST_CHECK_EQUAL(addrman.SelectTriedCollision().first.ToString(), "250.1.1.19:0"); + BOOST_CHECK(addrman->Add({CAddress(addr36, NODE_NONE)}, source)); + BOOST_CHECK(!addrman->Good(addr36)); + BOOST_CHECK_EQUAL(addrman->SelectTriedCollision().first.ToString(), "250.1.1.19:0"); // 36 should be discarded and 19 not evicted. // This means we keep 19 in the tried table and // 36 stays in the new table. - addrman.ResolveCollisions(); - BOOST_CHECK(addrman.SelectTriedCollision().first.ToString() == "[::]:0"); + addrman->ResolveCollisions(); + BOOST_CHECK(addrman->SelectTriedCollision().first.ToString() == "[::]:0"); // Lets create two collisions. for (unsigned int i = 37; i < 59; i++) { CService addr = ResolveService("250.1.1." + ToString(i)); - BOOST_CHECK(addrman.Add({CAddress(addr, NODE_NONE)}, source)); - BOOST_CHECK(addrman.Good(addr)); + BOOST_CHECK(addrman->Add({CAddress(addr, NODE_NONE)}, source)); + BOOST_CHECK(addrman->Good(addr)); } // Cause a collision in the tried table. CService addr59 = ResolveService("250.1.1.59"); - BOOST_CHECK(addrman.Add({CAddress(addr59, NODE_NONE)}, source)); - BOOST_CHECK(!addrman.Good(addr59)); + BOOST_CHECK(addrman->Add({CAddress(addr59, NODE_NONE)}, source)); + BOOST_CHECK(!addrman->Good(addr59)); - BOOST_CHECK_EQUAL(addrman.SelectTriedCollision().first.ToString(), "250.1.1.10:0"); + BOOST_CHECK_EQUAL(addrman->SelectTriedCollision().first.ToString(), "250.1.1.10:0"); // Cause a second collision in the new table. - BOOST_CHECK(!addrman.Add({CAddress(addr36, NODE_NONE)}, source)); + BOOST_CHECK(!addrman->Add({CAddress(addr36, NODE_NONE)}, source)); // 36 still cannot be moved from new to tried due to colliding with 19 - BOOST_CHECK(!addrman.Good(addr36)); - BOOST_CHECK(addrman.SelectTriedCollision().first.ToString() != "[::]:0"); + BOOST_CHECK(!addrman->Good(addr36)); + BOOST_CHECK(addrman->SelectTriedCollision().first.ToString() != "[::]:0"); // Resolve all collisions. - addrman.ResolveCollisions(); - BOOST_CHECK(addrman.SelectTriedCollision().first.ToString() == "[::]:0"); + addrman->ResolveCollisions(); + BOOST_CHECK(addrman->SelectTriedCollision().first.ToString() == "[::]:0"); } BOOST_AUTO_TEST_CASE(addrman_evictionworks) { - AddrManTest addrman; + auto addrman = TestAddrMan(); - BOOST_CHECK(addrman.size() == 0); + BOOST_CHECK(addrman->size() == 0); // Empty addrman should return blank addrman info. - BOOST_CHECK(addrman.SelectTriedCollision().first.ToString() == "[::]:0"); + BOOST_CHECK(addrman->SelectTriedCollision().first.ToString() == "[::]:0"); // Add 35 addresses CNetAddr source = ResolveIP("252.2.2.2"); for (unsigned int i = 1; i < 36; i++) { CService addr = ResolveService("250.1.1." + ToString(i)); - BOOST_CHECK(addrman.Add({CAddress(addr, NODE_NONE)}, source)); + BOOST_CHECK(addrman->Add({CAddress(addr, NODE_NONE)}, source)); // No collision yet. - BOOST_CHECK(addrman.Good(addr)); + BOOST_CHECK(addrman->Good(addr)); } // Collision between 36 and 19. CService addr = ResolveService("250.1.1.36"); - BOOST_CHECK(addrman.Add({CAddress(addr, NODE_NONE)}, source)); - BOOST_CHECK(!addrman.Good(addr)); + BOOST_CHECK(addrman->Add({CAddress(addr, NODE_NONE)}, source)); + BOOST_CHECK(!addrman->Good(addr)); - auto info = addrman.SelectTriedCollision().first; + auto info = addrman->SelectTriedCollision().first; BOOST_CHECK_EQUAL(info.ToString(), "250.1.1.19:0"); // Ensure test of address fails, so that it is evicted. - addrman.SimConnFail(info); + // Update entry in tried by setting last good connection in the deep past. + BOOST_CHECK(!addrman->Good(info, /*nTime=*/1)); + addrman->Attempt(info, /*fCountFailure=*/false, /*nTime=*/GetAdjustedTime() - 61); // Should swap 36 for 19. - addrman.ResolveCollisions(); - BOOST_CHECK(addrman.SelectTriedCollision().first.ToString() == "[::]:0"); + addrman->ResolveCollisions(); + BOOST_CHECK(addrman->SelectTriedCollision().first.ToString() == "[::]:0"); + AddressPosition addr_pos{addrman->FindAddressEntry(CAddress(addr, NODE_NONE)).value()}; + BOOST_CHECK(addr_pos.tried); // If 36 was swapped for 19, then adding 36 to tried should fail because we // are attempting to add a duplicate. // We check this by verifying Good() returns false and also verifying that // we have no collisions. - BOOST_CHECK(!addrman.Good(addr)); - BOOST_CHECK(addrman.SelectTriedCollision().first.ToString() == "[::]:0"); + BOOST_CHECK(!addrman->Good(addr)); + BOOST_CHECK(addrman->SelectTriedCollision().first.ToString() == "[::]:0"); // 19 should fail as a collision (not a duplicate) if we now attempt to move // it to the tried table. CService addr19 = ResolveService("250.1.1.19"); - BOOST_CHECK(!addrman.Good(addr19)); - BOOST_CHECK_EQUAL(addrman.SelectTriedCollision().first.ToString(), "250.1.1.36:0"); + BOOST_CHECK(!addrman->Good(addr19)); + BOOST_CHECK_EQUAL(addrman->SelectTriedCollision().first.ToString(), "250.1.1.36:0"); - addrman.ResolveCollisions(); - BOOST_CHECK(addrman.SelectTriedCollision().first.ToString() == "[::]:0"); + // Eviction is also successful if too much time has passed since last try + SetMockTime(GetTime() + 4 * 60 *60); + addrman->ResolveCollisions(); + BOOST_CHECK(addrman->SelectTriedCollision().first.ToString() == "[::]:0"); + //Now 19 is in tried again, and 36 back to new + AddressPosition addr_pos19{addrman->FindAddressEntry(CAddress(addr19, NODE_NONE)).value()}; + BOOST_CHECK(addr_pos19.tried); + AddressPosition addr_pos36{addrman->FindAddressEntry(CAddress(addr, NODE_NONE)).value()}; + BOOST_CHECK(!addr_pos36.tried); } static CDataStream AddrmanToStream(const AddrMan& addrman) @@ -1041,4 +953,35 @@ BOOST_AUTO_TEST_CASE(load_addrman_corrupted) BOOST_CHECK_THROW(ReadFromStream(addrman2, ssPeers2), std::ios_base::failure); } +BOOST_AUTO_TEST_CASE(addrman_update_address) +{ + // Tests updating nTime via Connected() and nServices via SetServices() + auto addrman = TestAddrMan(); + CNetAddr source{ResolveIP("252.2.2.2")}; + CAddress addr{CAddress(ResolveService("250.1.1.1", 8333), NODE_NONE)}; + + int64_t start_time{GetAdjustedTime() - 10000}; + addr.nTime = start_time; + BOOST_CHECK(addrman->Add({addr}, source)); + BOOST_CHECK_EQUAL(addrman->size(), 1U); + + // Updating an addrman entry with a different port doesn't change it + CAddress addr_diff_port{CAddress(ResolveService("250.1.1.1", 8334), NODE_NONE)}; + addr_diff_port.nTime = start_time; + addrman->Connected(addr_diff_port); + addrman->SetServices(addr_diff_port, NODE_NETWORK_LIMITED); + std::vector vAddr1{addrman->GetAddr(/*max_addresses=*/0, /*max_pct=*/0, /*network=*/std::nullopt)}; + BOOST_CHECK_EQUAL(vAddr1.size(), 1U); + BOOST_CHECK_EQUAL(vAddr1.at(0).nTime, start_time); + BOOST_CHECK_EQUAL(vAddr1.at(0).nServices, NODE_NONE); + + // Updating an addrman entry with the correct port is successful + addrman->Connected(addr); + addrman->SetServices(addr, NODE_NETWORK_LIMITED); + std::vector vAddr2 = addrman->GetAddr(/*max_addresses=*/0, /*max_pct=*/0, /*network=*/std::nullopt); + BOOST_CHECK_EQUAL(vAddr2.size(), 1U); + BOOST_CHECK(vAddr2.at(0).nTime >= start_time + 10000); + BOOST_CHECK_EQUAL(vAddr2.at(0).nServices, NODE_NETWORK_LIMITED); +} + BOOST_AUTO_TEST_SUITE_END()