diff --git a/src/net.cpp b/src/net.cpp index 15616613d2..9bb264a38a 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -928,17 +928,17 @@ void ProtectEvictionCandidatesByRatio(std::vector& evicti // Protect the half of the remaining nodes which have been connected the longest. // This replicates the non-eviction implicit behavior, and precludes attacks that start later. // To favorise the diversity of our peer connections, reserve up to half of these protected - // spots for Tor/onion, localhost and I2P peers, even if they're not longest uptime overall. - // This helps protect these higher-latency peers that tend to be otherwise + // spots for Tor/onion, localhost, I2P, and CJDNS peers, even if they're not longest uptime + // overall. This helps protect these higher-latency peers that tend to be otherwise // disadvantaged under our eviction criteria. const size_t initial_size = eviction_candidates.size(); const size_t total_protect_size{initial_size / 2}; - // Disadvantaged networks to protect: I2P, localhost, Tor/onion. In case of equal counts, earlier - // array members have first opportunity to recover unused slots from the previous iteration. + // Disadvantaged networks to protect. In the case of equal counts, earlier array members + // have the first opportunity to recover unused slots from the previous iteration. struct Net { bool is_local; Network id; size_t count; }; - std::array networks{ - {{false, NET_I2P, 0}, {/* localhost */ true, NET_MAX, 0}, {false, NET_ONION, 0}}}; + std::array networks{ + {{false, NET_CJDNS, 0}, {false, NET_I2P, 0}, {/*localhost=*/true, NET_MAX, 0}, {false, NET_ONION, 0}}}; // Count and store the number of eviction candidates per network. for (Net& n : networks) { diff --git a/src/net.h b/src/net.h index 3f4c8e38ec..bbc253e7ff 100644 --- a/src/net.h +++ b/src/net.h @@ -1316,6 +1316,8 @@ struct NodeEvictionCandidate * * - I2P peers * + * - CJDNS peers + * * This helps protect these privacy network peers, which tend to be otherwise * disadvantaged under our eviction criteria for their higher min ping times * relative to IPv4/IPv6 peers, and favorise the diversity of peer connections. diff --git a/src/test/net_peer_eviction_tests.cpp b/src/test/net_peer_eviction_tests.cpp index 78ad24a408..6ec3fb0c6b 100644 --- a/src/test/net_peer_eviction_tests.cpp +++ b/src/test/net_peer_eviction_tests.cpp @@ -90,7 +90,7 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) // Test protection of onion, localhost, and I2P peers... // Expect 1/4 onion peers to be protected from eviction, - // if no localhost or I2P peers. + // if no localhost, I2P, or CJDNS peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.m_is_local = false; @@ -101,7 +101,7 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) random_context)); // Expect 1/4 onion peers and 1/4 of the other peers to be protected, - // sorted by longest uptime (lowest m_connected), if no localhost or I2P peers. + // sorted by longest uptime (lowest m_connected), if no localhost, I2P or CJDNS peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.m_connected = std::chrono::seconds{c.id}; @@ -113,7 +113,7 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) random_context)); // Expect 1/4 localhost peers to be protected from eviction, - // if no onion or I2P peers. + // if no onion, I2P, or CJDNS peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.m_is_local = (c.id == 1 || c.id == 9 || c.id == 11); @@ -124,7 +124,7 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) random_context)); // Expect 1/4 localhost peers and 1/4 of the other peers to be protected, - // sorted by longest uptime (lowest m_connected), if no onion or I2P peers. + // sorted by longest uptime (lowest m_connected), if no onion, I2P, or CJDNS peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.m_connected = std::chrono::seconds{c.id}; @@ -136,7 +136,7 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) random_context)); // Expect 1/4 I2P peers to be protected from eviction, - // if no onion or localhost peers. + // if no onion, localhost, or CJDNS peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.m_is_local = false; @@ -146,8 +146,8 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) /*unprotected_peer_ids=*/{}, random_context)); - // Expect 1/4 I2P peers and 1/4 of the other peers to be protected, - // sorted by longest uptime (lowest m_connected), if no onion or localhost peers. + // Expect 1/4 I2P peers and 1/4 of the other peers to be protected, sorted + // by longest uptime (lowest m_connected), if no onion, localhost, or CJDNS peers. BOOST_CHECK(IsProtected( num_peers, [](NodeEvictionCandidate& c) { c.m_connected = std::chrono::seconds{c.id}; @@ -158,6 +158,29 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) /*unprotected_peer_ids=*/{3, 5, 6, 7, 8, 11}, random_context)); + // Expect 1/4 CJDNS peers to be protected from eviction, + // if no onion, localhost, or I2P peers. + BOOST_CHECK(IsProtected( + num_peers, [](NodeEvictionCandidate& c) { + c.m_is_local = false; + c.m_network = (c.id == 2 || c.id == 7 || c.id == 10) ? NET_CJDNS : NET_IPV4; + }, + /*protected_peer_ids=*/{2, 7, 10}, + /*unprotected_peer_ids=*/{}, + random_context)); + + // Expect 1/4 CJDNS peers and 1/4 of the other peers to be protected, sorted + // by longest uptime (lowest m_connected), if no onion, localhost, or I2P peers. + BOOST_CHECK(IsProtected( + num_peers, [](NodeEvictionCandidate& c) { + c.m_connected = std::chrono::seconds{c.id}; + c.m_is_local = false; + c.m_network = (c.id == 4 || c.id > 8) ? NET_CJDNS : NET_IPV6; + }, + /*protected_peer_ids=*/{0, 1, 2, 4, 9, 10}, + /*unprotected_peer_ids=*/{3, 5, 6, 7, 8, 11}, + random_context)); + // Tests with 2 networks... // Combined test: expect having 1 localhost and 1 onion peer out of 4 to @@ -289,16 +312,16 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) BOOST_CHECK(IsProtected( 4, [](NodeEvictionCandidate& c) { c.m_connected = std::chrono::seconds{c.id}; - c.m_is_local = (c.id == 3); - if (c.id == 4) { + c.m_is_local = (c.id == 2); + if (c.id == 3) { c.m_network = NET_I2P; - } else if (c.id == 2) { + } else if (c.id == 1) { c.m_network = NET_ONION; } else { c.m_network = NET_IPV6; } }, - /*protected_peer_ids=*/{0, 4}, + /*protected_peer_ids=*/{0, 3}, /*unprotected_peer_ids=*/{1, 2}, random_context)); @@ -416,15 +439,15 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11, 16, 19, 20, 21, 22, 23}, random_context)); - // Combined test: expect having 8 localhost, 4 I2P, and 3 onion peers out of - // 24 to protect 2 of each (6 total), plus 6 others for 12/24 total, sorted - // by longest uptime. + // Combined test: expect having 8 localhost, 4 CJDNS, and 3 onion peers out + // of 24 to protect 2 of each (6 total), plus 6 others for 12/24 total, + // sorted by longest uptime. BOOST_CHECK(IsProtected( 24, [](NodeEvictionCandidate& c) { c.m_connected = std::chrono::seconds{c.id}; c.m_is_local = (c.id > 15); if (c.id > 10 && c.id < 15) { - c.m_network = NET_I2P; + c.m_network = NET_CJDNS; } else if (c.id > 6 && c.id < 10) { c.m_network = NET_ONION; } else { @@ -434,6 +457,116 @@ BOOST_AUTO_TEST_CASE(peer_protection_test) /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5, 7, 8, 11, 12, 16, 17}, /*unprotected_peer_ids=*/{6, 9, 10, 13, 14, 15, 18, 19, 20, 21, 22, 23}, random_context)); + + // Tests with 4 networks... + + // Combined test: expect having 1 CJDNS, 1 I2P, 1 localhost and 1 onion peer + // out of 5 to protect 1 CJDNS, 0 I2P, 0 localhost, 0 onion and 1 other peer + // (2 total), sorted by longest uptime; stable sort breaks tie with array + // order of CJDNS first. + BOOST_CHECK(IsProtected( + 5, [](NodeEvictionCandidate& c) { + c.m_connected = std::chrono::seconds{c.id}; + c.m_is_local = (c.id == 3); + if (c.id == 4) { + c.m_network = NET_CJDNS; + } else if (c.id == 1) { + c.m_network = NET_I2P; + } else if (c.id == 2) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV6; + } + }, + /* protected_peer_ids */ {0, 4}, + /* unprotected_peer_ids */ {1, 2, 3}, + random_context)); + + // Combined test: expect having 1 CJDNS, 1 I2P, 1 localhost and 1 onion peer + // out of 7 to protect 1 CJDNS, 0, I2P, 0 localhost, 0 onion and 2 other + // peers (3 total) sorted by longest uptime; stable sort breaks tie with + // array order of CJDNS first. + BOOST_CHECK(IsProtected( + 7, [](NodeEvictionCandidate& c) { + c.m_connected = std::chrono::seconds{c.id}; + c.m_is_local = (c.id == 4); + if (c.id == 6) { + c.m_network = NET_CJDNS; + } else if (c.id == 5) { + c.m_network = NET_I2P; + } else if (c.id == 3) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV4; + } + }, + /*protected_peer_ids=*/{0, 1, 6}, + /*unprotected_peer_ids=*/{2, 3, 4, 5}, + random_context)); + + // Combined test: expect having 1 CJDNS, 1 I2P, 1 localhost and 1 onion peer + // out of 8 to protect 1 CJDNS, 1 I2P, 0 localhost, 0 onion and 2 other + // peers (4 total) sorted by longest uptime; stable sort breaks tie with + // array order of CJDNS first. + BOOST_CHECK(IsProtected( + 8, [](NodeEvictionCandidate& c) { + c.m_connected = std::chrono::seconds{c.id}; + c.m_is_local = (c.id == 3); + if (c.id == 5) { + c.m_network = NET_CJDNS; + } else if (c.id == 6) { + c.m_network = NET_I2P; + } else if (c.id == 3) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV6; + } + }, + /*protected_peer_ids=*/{0, 1, 5, 6}, + /*unprotected_peer_ids=*/{2, 3, 4, 7}, + random_context)); + + // Combined test: expect having 2 CJDNS, 2 I2P, 4 localhost, and 2 onion + // peers out of 16 to protect 1 CJDNS, 1 I2P, 1 localhost, 1 onion (4/16 + // total), plus 4 others for 8 total, sorted by longest uptime. + BOOST_CHECK(IsProtected( + 16, [](NodeEvictionCandidate& c) { + c.m_connected = std::chrono::seconds{c.id}; + c.m_is_local = (c.id > 5); + if (c.id == 11 || c.id == 15) { + c.m_network = NET_CJDNS; + } else if (c.id == 10 || c.id == 14) { + c.m_network = NET_I2P; + } else if (c.id == 8 || c.id == 9) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV4; + } + }, + /*protected_peer_ids=*/{0, 1, 2, 3, 6, 8, 10, 11}, + /*unprotected_peer_ids=*/{4, 5, 7, 9, 12, 13, 14, 15}, + random_context)); + + // Combined test: expect having 6 CJDNS, 1 I2P, 1 localhost, and 4 onion + // peers out of 24 to protect 2 CJDNS, 1 I2P, 1 localhost, and 2 onions (6 + // total), plus 6 others for 12/24 total, sorted by longest uptime. + BOOST_CHECK(IsProtected( + 24, [](NodeEvictionCandidate& c) { + c.m_connected = std::chrono::seconds{c.id}; + c.m_is_local = (c.id == 13); + if (c.id > 17) { + c.m_network = NET_CJDNS; + } else if (c.id == 17) { + c.m_network = NET_I2P; + } else if (c.id == 12 || c.id == 14 || c.id == 15 || c.id == 16) { + c.m_network = NET_ONION; + } else { + c.m_network = NET_IPV6; + } + }, + /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5, 12, 13, 14, 17, 18, 19}, + /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11, 15, 16, 20, 21, 22, 23}, + random_context)); } // Returns true if any of the node ids in node_ids are selected for eviction.