Branch data Line data Source code
# 1 : : // Copyright (c) 2021 The Bitcoin Core developers
# 2 : : // Distributed under the MIT software license, see the accompanying
# 3 : : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
# 4 : :
# 5 : : #include <netaddress.h>
# 6 : : #include <net.h>
# 7 : : #include <test/util/net.h>
# 8 : : #include <test/util/setup_common.h>
# 9 : :
# 10 : : #include <boost/test/unit_test.hpp>
# 11 : :
# 12 : : #include <algorithm>
# 13 : : #include <functional>
# 14 : : #include <optional>
# 15 : : #include <unordered_set>
# 16 : : #include <vector>
# 17 : :
# 18 : : BOOST_FIXTURE_TEST_SUITE(net_peer_eviction_tests, BasicTestingSetup)
# 19 : :
# 20 : : // Create `num_peers` random nodes, apply setup function `candidate_setup_fn`,
# 21 : : // call ProtectEvictionCandidatesByRatio() to apply protection logic, and then
# 22 : : // return true if all of `protected_peer_ids` and none of `unprotected_peer_ids`
# 23 : : // are protected from eviction, i.e. removed from the eviction candidates.
# 24 : : bool IsProtected(int num_peers,
# 25 : : std::function<void(NodeEvictionCandidate&)> candidate_setup_fn,
# 26 : : const std::unordered_set<NodeId>& protected_peer_ids,
# 27 : : const std::unordered_set<NodeId>& unprotected_peer_ids,
# 28 : : FastRandomContext& random_context)
# 29 : 64 : {
# 30 : 64 : std::vector<NodeEvictionCandidate> candidates{GetRandomNodeEvictionCandidates(num_peers, random_context)};
# 31 [ + + ]: 828 : for (NodeEvictionCandidate& candidate : candidates) {
# 32 : 828 : candidate_setup_fn(candidate);
# 33 : 828 : }
# 34 : 64 : Shuffle(candidates.begin(), candidates.end(), random_context);
# 35 : :
# 36 : 64 : const size_t size{candidates.size()};
# 37 : 64 : const size_t expected{size - size / 2}; // Expect half the candidates will be protected.
# 38 : 64 : ProtectEvictionCandidatesByRatio(candidates);
# 39 : 64 : BOOST_CHECK_EQUAL(candidates.size(), expected);
# 40 : :
# 41 : 64 : size_t unprotected_count{0};
# 42 [ + + ]: 418 : for (const NodeEvictionCandidate& candidate : candidates) {
# 43 [ - + ]: 418 : if (protected_peer_ids.count(candidate.id)) {
# 44 : : // this peer should have been removed from the eviction candidates
# 45 : 0 : BOOST_TEST_MESSAGE(strprintf("expected candidate to be protected: %d", candidate.id));
# 46 : 0 : return false;
# 47 : 0 : }
# 48 [ + + ]: 418 : if (unprotected_peer_ids.count(candidate.id)) {
# 49 : : // this peer remains in the eviction candidates, as expected
# 50 : 368 : ++unprotected_count;
# 51 : 368 : }
# 52 : 418 : }
# 53 : :
# 54 : 64 : const bool is_protected{unprotected_count == unprotected_peer_ids.size()};
# 55 [ - + ]: 64 : if (!is_protected) {
# 56 : 0 : BOOST_TEST_MESSAGE(strprintf("unprotected: expected %d, actual %d",
# 57 : 0 : unprotected_peer_ids.size(), unprotected_count));
# 58 : 0 : }
# 59 : 64 : return is_protected;
# 60 : 64 : }
# 61 : :
# 62 : : BOOST_AUTO_TEST_CASE(peer_protection_test)
# 63 : 2 : {
# 64 : 2 : FastRandomContext random_context{true};
# 65 : 2 : int num_peers{12};
# 66 : :
# 67 : : // Expect half of the peers with greatest uptime (the lowest m_connected)
# 68 : : // to be protected from eviction.
# 69 : 2 : BOOST_CHECK(IsProtected(
# 70 : 2 : num_peers, [](NodeEvictionCandidate& c) {
# 71 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 72 : 2 : c.m_is_local = false;
# 73 : 2 : c.m_network = NET_IPV4;
# 74 : 2 : },
# 75 : 2 : /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5},
# 76 : 2 : /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11},
# 77 : 2 : random_context));
# 78 : :
# 79 : : // Verify in the opposite direction.
# 80 : 2 : BOOST_CHECK(IsProtected(
# 81 : 2 : num_peers, [num_peers](NodeEvictionCandidate& c) {
# 82 : 2 : c.m_connected = std::chrono::seconds{num_peers - c.id};
# 83 : 2 : c.m_is_local = false;
# 84 : 2 : c.m_network = NET_IPV6;
# 85 : 2 : },
# 86 : 2 : /*protected_peer_ids=*/{6, 7, 8, 9, 10, 11},
# 87 : 2 : /*unprotected_peer_ids=*/{0, 1, 2, 3, 4, 5},
# 88 : 2 : random_context));
# 89 : :
# 90 : : // Test protection of onion, localhost, and I2P peers...
# 91 : :
# 92 : : // Expect 1/4 onion peers to be protected from eviction,
# 93 : : // if no localhost, I2P, or CJDNS peers.
# 94 : 2 : BOOST_CHECK(IsProtected(
# 95 : 2 : num_peers, [](NodeEvictionCandidate& c) {
# 96 : 2 : c.m_is_local = false;
# 97 : 2 : c.m_network = (c.id == 3 || c.id == 8 || c.id == 9) ? NET_ONION : NET_IPV4;
# 98 : 2 : },
# 99 : 2 : /*protected_peer_ids=*/{3, 8, 9},
# 100 : 2 : /*unprotected_peer_ids=*/{},
# 101 : 2 : random_context));
# 102 : :
# 103 : : // Expect 1/4 onion peers and 1/4 of the other peers to be protected,
# 104 : : // sorted by longest uptime (lowest m_connected), if no localhost, I2P or CJDNS peers.
# 105 : 2 : BOOST_CHECK(IsProtected(
# 106 : 2 : num_peers, [](NodeEvictionCandidate& c) {
# 107 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 108 : 2 : c.m_is_local = false;
# 109 : 2 : c.m_network = (c.id == 3 || c.id > 7) ? NET_ONION : NET_IPV6;
# 110 : 2 : },
# 111 : 2 : /*protected_peer_ids=*/{0, 1, 2, 3, 8, 9},
# 112 : 2 : /*unprotected_peer_ids=*/{4, 5, 6, 7, 10, 11},
# 113 : 2 : random_context));
# 114 : :
# 115 : : // Expect 1/4 localhost peers to be protected from eviction,
# 116 : : // if no onion, I2P, or CJDNS peers.
# 117 : 2 : BOOST_CHECK(IsProtected(
# 118 : 2 : num_peers, [](NodeEvictionCandidate& c) {
# 119 : 2 : c.m_is_local = (c.id == 1 || c.id == 9 || c.id == 11);
# 120 : 2 : c.m_network = NET_IPV4;
# 121 : 2 : },
# 122 : 2 : /*protected_peer_ids=*/{1, 9, 11},
# 123 : 2 : /*unprotected_peer_ids=*/{},
# 124 : 2 : random_context));
# 125 : :
# 126 : : // Expect 1/4 localhost peers and 1/4 of the other peers to be protected,
# 127 : : // sorted by longest uptime (lowest m_connected), if no onion, I2P, or CJDNS peers.
# 128 : 2 : BOOST_CHECK(IsProtected(
# 129 : 2 : num_peers, [](NodeEvictionCandidate& c) {
# 130 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 131 : 2 : c.m_is_local = (c.id > 6);
# 132 : 2 : c.m_network = NET_IPV6;
# 133 : 2 : },
# 134 : 2 : /*protected_peer_ids=*/{0, 1, 2, 7, 8, 9},
# 135 : 2 : /*unprotected_peer_ids=*/{3, 4, 5, 6, 10, 11},
# 136 : 2 : random_context));
# 137 : :
# 138 : : // Expect 1/4 I2P peers to be protected from eviction,
# 139 : : // if no onion, localhost, or CJDNS peers.
# 140 : 2 : BOOST_CHECK(IsProtected(
# 141 : 2 : num_peers, [](NodeEvictionCandidate& c) {
# 142 : 2 : c.m_is_local = false;
# 143 : 2 : c.m_network = (c.id == 2 || c.id == 7 || c.id == 10) ? NET_I2P : NET_IPV4;
# 144 : 2 : },
# 145 : 2 : /*protected_peer_ids=*/{2, 7, 10},
# 146 : 2 : /*unprotected_peer_ids=*/{},
# 147 : 2 : random_context));
# 148 : :
# 149 : : // Expect 1/4 I2P peers and 1/4 of the other peers to be protected, sorted
# 150 : : // by longest uptime (lowest m_connected), if no onion, localhost, or CJDNS peers.
# 151 : 2 : BOOST_CHECK(IsProtected(
# 152 : 2 : num_peers, [](NodeEvictionCandidate& c) {
# 153 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 154 : 2 : c.m_is_local = false;
# 155 : 2 : c.m_network = (c.id == 4 || c.id > 8) ? NET_I2P : NET_IPV6;
# 156 : 2 : },
# 157 : 2 : /*protected_peer_ids=*/{0, 1, 2, 4, 9, 10},
# 158 : 2 : /*unprotected_peer_ids=*/{3, 5, 6, 7, 8, 11},
# 159 : 2 : random_context));
# 160 : :
# 161 : : // Expect 1/4 CJDNS peers to be protected from eviction,
# 162 : : // if no onion, localhost, or I2P peers.
# 163 : 2 : BOOST_CHECK(IsProtected(
# 164 : 2 : num_peers, [](NodeEvictionCandidate& c) {
# 165 : 2 : c.m_is_local = false;
# 166 : 2 : c.m_network = (c.id == 2 || c.id == 7 || c.id == 10) ? NET_CJDNS : NET_IPV4;
# 167 : 2 : },
# 168 : 2 : /*protected_peer_ids=*/{2, 7, 10},
# 169 : 2 : /*unprotected_peer_ids=*/{},
# 170 : 2 : random_context));
# 171 : :
# 172 : : // Expect 1/4 CJDNS peers and 1/4 of the other peers to be protected, sorted
# 173 : : // by longest uptime (lowest m_connected), if no onion, localhost, or I2P peers.
# 174 : 2 : BOOST_CHECK(IsProtected(
# 175 : 2 : num_peers, [](NodeEvictionCandidate& c) {
# 176 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 177 : 2 : c.m_is_local = false;
# 178 : 2 : c.m_network = (c.id == 4 || c.id > 8) ? NET_CJDNS : NET_IPV6;
# 179 : 2 : },
# 180 : 2 : /*protected_peer_ids=*/{0, 1, 2, 4, 9, 10},
# 181 : 2 : /*unprotected_peer_ids=*/{3, 5, 6, 7, 8, 11},
# 182 : 2 : random_context));
# 183 : :
# 184 : : // Tests with 2 networks...
# 185 : :
# 186 : : // Combined test: expect having 1 localhost and 1 onion peer out of 4 to
# 187 : : // protect 1 localhost, 0 onion and 1 other peer, sorted by longest uptime;
# 188 : : // stable sort breaks tie with array order of localhost first.
# 189 : 2 : BOOST_CHECK(IsProtected(
# 190 : 2 : 4, [](NodeEvictionCandidate& c) {
# 191 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 192 : 2 : c.m_is_local = (c.id == 4);
# 193 : 2 : c.m_network = (c.id == 3) ? NET_ONION : NET_IPV4;
# 194 : 2 : },
# 195 : 2 : /*protected_peer_ids=*/{0, 4},
# 196 : 2 : /*unprotected_peer_ids=*/{1, 2},
# 197 : 2 : random_context));
# 198 : :
# 199 : : // Combined test: expect having 1 localhost and 1 onion peer out of 7 to
# 200 : : // protect 1 localhost, 0 onion, and 2 other peers (3 total), sorted by
# 201 : : // uptime; stable sort breaks tie with array order of localhost first.
# 202 : 2 : BOOST_CHECK(IsProtected(
# 203 : 2 : 7, [](NodeEvictionCandidate& c) {
# 204 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 205 : 2 : c.m_is_local = (c.id == 6);
# 206 : 2 : c.m_network = (c.id == 5) ? NET_ONION : NET_IPV4;
# 207 : 2 : },
# 208 : 2 : /*protected_peer_ids=*/{0, 1, 6},
# 209 : 2 : /*unprotected_peer_ids=*/{2, 3, 4, 5},
# 210 : 2 : random_context));
# 211 : :
# 212 : : // Combined test: expect having 1 localhost and 1 onion peer out of 8 to
# 213 : : // protect protect 1 localhost, 1 onion and 2 other peers (4 total), sorted
# 214 : : // by uptime; stable sort breaks tie with array order of localhost first.
# 215 : 2 : BOOST_CHECK(IsProtected(
# 216 : 2 : 8, [](NodeEvictionCandidate& c) {
# 217 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 218 : 2 : c.m_is_local = (c.id == 6);
# 219 : 2 : c.m_network = (c.id == 5) ? NET_ONION : NET_IPV4;
# 220 : 2 : },
# 221 : 2 : /*protected_peer_ids=*/{0, 1, 5, 6},
# 222 : 2 : /*unprotected_peer_ids=*/{2, 3, 4, 7},
# 223 : 2 : random_context));
# 224 : :
# 225 : : // Combined test: expect having 3 localhost and 3 onion peers out of 12 to
# 226 : : // protect 2 localhost and 1 onion, plus 3 other peers, sorted by longest
# 227 : : // uptime; stable sort breaks ties with the array order of localhost first.
# 228 : 2 : BOOST_CHECK(IsProtected(
# 229 : 2 : num_peers, [](NodeEvictionCandidate& c) {
# 230 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 231 : 2 : c.m_is_local = (c.id == 6 || c.id == 9 || c.id == 11);
# 232 : 2 : c.m_network = (c.id == 7 || c.id == 8 || c.id == 10) ? NET_ONION : NET_IPV6;
# 233 : 2 : },
# 234 : 2 : /*protected_peer_ids=*/{0, 1, 2, 6, 7, 9},
# 235 : 2 : /*unprotected_peer_ids=*/{3, 4, 5, 8, 10, 11},
# 236 : 2 : random_context));
# 237 : :
# 238 : : // Combined test: expect having 4 localhost and 1 onion peer out of 12 to
# 239 : : // protect 2 localhost and 1 onion, plus 3 other peers, sorted by longest uptime.
# 240 : 2 : BOOST_CHECK(IsProtected(
# 241 : 2 : num_peers, [](NodeEvictionCandidate& c) {
# 242 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 243 : 2 : c.m_is_local = (c.id > 4 && c.id < 9);
# 244 : 2 : c.m_network = (c.id == 10) ? NET_ONION : NET_IPV4;
# 245 : 2 : },
# 246 : 2 : /*protected_peer_ids=*/{0, 1, 2, 5, 6, 10},
# 247 : 2 : /*unprotected_peer_ids=*/{3, 4, 7, 8, 9, 11},
# 248 : 2 : random_context));
# 249 : :
# 250 : : // Combined test: expect having 4 localhost and 2 onion peers out of 16 to
# 251 : : // protect 2 localhost and 2 onions, plus 4 other peers, sorted by longest uptime.
# 252 : 2 : BOOST_CHECK(IsProtected(
# 253 : 2 : 16, [](NodeEvictionCandidate& c) {
# 254 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 255 : 2 : c.m_is_local = (c.id == 6 || c.id == 9 || c.id == 11 || c.id == 12);
# 256 : 2 : c.m_network = (c.id == 8 || c.id == 10) ? NET_ONION : NET_IPV6;
# 257 : 2 : },
# 258 : 2 : /*protected_peer_ids=*/{0, 1, 2, 3, 6, 8, 9, 10},
# 259 : 2 : /*unprotected_peer_ids=*/{4, 5, 7, 11, 12, 13, 14, 15},
# 260 : 2 : random_context));
# 261 : :
# 262 : : // Combined test: expect having 5 localhost and 1 onion peer out of 16 to
# 263 : : // protect 3 localhost (recovering the unused onion slot), 1 onion, and 4
# 264 : : // others, sorted by longest uptime.
# 265 : 2 : BOOST_CHECK(IsProtected(
# 266 : 2 : 16, [](NodeEvictionCandidate& c) {
# 267 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 268 : 2 : c.m_is_local = (c.id > 10);
# 269 : 2 : c.m_network = (c.id == 10) ? NET_ONION : NET_IPV4;
# 270 : 2 : },
# 271 : 2 : /*protected_peer_ids=*/{0, 1, 2, 3, 10, 11, 12, 13},
# 272 : 2 : /*unprotected_peer_ids=*/{4, 5, 6, 7, 8, 9, 14, 15},
# 273 : 2 : random_context));
# 274 : :
# 275 : : // Combined test: expect having 1 localhost and 4 onion peers out of 16 to
# 276 : : // protect 1 localhost and 3 onions (recovering the unused localhost slot),
# 277 : : // plus 4 others, sorted by longest uptime.
# 278 : 2 : BOOST_CHECK(IsProtected(
# 279 : 2 : 16, [](NodeEvictionCandidate& c) {
# 280 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 281 : 2 : c.m_is_local = (c.id == 15);
# 282 : 2 : c.m_network = (c.id > 6 && c.id < 11) ? NET_ONION : NET_IPV6;
# 283 : 2 : },
# 284 : 2 : /*protected_peer_ids=*/{0, 1, 2, 3, 7, 8, 9, 15},
# 285 : 2 : /*unprotected_peer_ids=*/{5, 6, 10, 11, 12, 13, 14},
# 286 : 2 : random_context));
# 287 : :
# 288 : : // Combined test: expect having 2 onion and 4 I2P out of 12 peers to protect
# 289 : : // 2 onion (prioritized for having fewer candidates) and 1 I2P, plus 3
# 290 : : // others, sorted by longest uptime.
# 291 : 2 : BOOST_CHECK(IsProtected(
# 292 : 2 : num_peers, [](NodeEvictionCandidate& c) {
# 293 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 294 : 2 : c.m_is_local = false;
# 295 : 2 : if (c.id == 8 || c.id == 10) {
# 296 : 2 : c.m_network = NET_ONION;
# 297 : 2 : } else if (c.id == 6 || c.id == 9 || c.id == 11 || c.id == 12) {
# 298 : 2 : c.m_network = NET_I2P;
# 299 : 2 : } else {
# 300 : 2 : c.m_network = NET_IPV4;
# 301 : 2 : }
# 302 : 2 : },
# 303 : 2 : /*protected_peer_ids=*/{0, 1, 2, 6, 8, 10},
# 304 : 2 : /*unprotected_peer_ids=*/{3, 4, 5, 7, 9, 11},
# 305 : 2 : random_context));
# 306 : :
# 307 : : // Tests with 3 networks...
# 308 : :
# 309 : : // Combined test: expect having 1 localhost, 1 I2P and 1 onion peer out of 4
# 310 : : // to protect 1 I2P, 0 localhost, 0 onion and 1 other peer (2 total), sorted
# 311 : : // by longest uptime; stable sort breaks tie with array order of I2P first.
# 312 : 2 : BOOST_CHECK(IsProtected(
# 313 : 2 : 4, [](NodeEvictionCandidate& c) {
# 314 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 315 : 2 : c.m_is_local = (c.id == 2);
# 316 : 2 : if (c.id == 3) {
# 317 : 2 : c.m_network = NET_I2P;
# 318 : 2 : } else if (c.id == 1) {
# 319 : 2 : c.m_network = NET_ONION;
# 320 : 2 : } else {
# 321 : 2 : c.m_network = NET_IPV6;
# 322 : 2 : }
# 323 : 2 : },
# 324 : 2 : /*protected_peer_ids=*/{0, 3},
# 325 : 2 : /*unprotected_peer_ids=*/{1, 2},
# 326 : 2 : random_context));
# 327 : :
# 328 : : // Combined test: expect having 1 localhost, 1 I2P and 1 onion peer out of 7
# 329 : : // to protect 1 I2P, 0 localhost, 0 onion and 2 other peers (3 total) sorted
# 330 : : // by longest uptime; stable sort breaks tie with array order of I2P first.
# 331 : 2 : BOOST_CHECK(IsProtected(
# 332 : 2 : 7, [](NodeEvictionCandidate& c) {
# 333 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 334 : 2 : c.m_is_local = (c.id == 4);
# 335 : 2 : if (c.id == 6) {
# 336 : 2 : c.m_network = NET_I2P;
# 337 : 2 : } else if (c.id == 5) {
# 338 : 2 : c.m_network = NET_ONION;
# 339 : 2 : } else {
# 340 : 2 : c.m_network = NET_IPV6;
# 341 : 2 : }
# 342 : 2 : },
# 343 : 2 : /*protected_peer_ids=*/{0, 1, 6},
# 344 : 2 : /*unprotected_peer_ids=*/{2, 3, 4, 5},
# 345 : 2 : random_context));
# 346 : :
# 347 : : // Combined test: expect having 1 localhost, 1 I2P and 1 onion peer out of 8
# 348 : : // to protect 1 I2P, 1 localhost, 0 onion and 2 other peers (4 total) sorted
# 349 : : // by uptime; stable sort breaks tie with array order of I2P then localhost.
# 350 : 2 : BOOST_CHECK(IsProtected(
# 351 : 2 : 8, [](NodeEvictionCandidate& c) {
# 352 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 353 : 2 : c.m_is_local = (c.id == 6);
# 354 : 2 : if (c.id == 5) {
# 355 : 2 : c.m_network = NET_I2P;
# 356 : 2 : } else if (c.id == 4) {
# 357 : 2 : c.m_network = NET_ONION;
# 358 : 2 : } else {
# 359 : 2 : c.m_network = NET_IPV6;
# 360 : 2 : }
# 361 : 2 : },
# 362 : 2 : /*protected_peer_ids=*/{0, 1, 5, 6},
# 363 : 2 : /*unprotected_peer_ids=*/{2, 3, 4, 7},
# 364 : 2 : random_context));
# 365 : :
# 366 : : // Combined test: expect having 4 localhost, 2 I2P, and 2 onion peers out of
# 367 : : // 16 to protect 1 localhost, 2 I2P, and 1 onion (4/16 total), plus 4 others
# 368 : : // for 8 total, sorted by longest uptime.
# 369 : 2 : BOOST_CHECK(IsProtected(
# 370 : 2 : 16, [](NodeEvictionCandidate& c) {
# 371 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 372 : 2 : c.m_is_local = (c.id == 6 || c.id > 11);
# 373 : 2 : if (c.id == 7 || c.id == 11) {
# 374 : 2 : c.m_network = NET_I2P;
# 375 : 2 : } else if (c.id == 9 || c.id == 10) {
# 376 : 2 : c.m_network = NET_ONION;
# 377 : 2 : } else {
# 378 : 2 : c.m_network = NET_IPV4;
# 379 : 2 : }
# 380 : 2 : },
# 381 : 2 : /*protected_peer_ids=*/{0, 1, 2, 3, 6, 7, 9, 11},
# 382 : 2 : /*unprotected_peer_ids=*/{4, 5, 8, 10, 12, 13, 14, 15},
# 383 : 2 : random_context));
# 384 : :
# 385 : : // Combined test: expect having 1 localhost, 8 I2P and 1 onion peer out of
# 386 : : // 24 to protect 1, 4, and 1 (6 total), plus 6 others for 12/24 total,
# 387 : : // sorted by longest uptime.
# 388 : 2 : BOOST_CHECK(IsProtected(
# 389 : 2 : 24, [](NodeEvictionCandidate& c) {
# 390 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 391 : 2 : c.m_is_local = (c.id == 12);
# 392 : 2 : if (c.id > 14 && c.id < 23) { // 4 protected instead of usual 2
# 393 : 2 : c.m_network = NET_I2P;
# 394 : 2 : } else if (c.id == 23) {
# 395 : 2 : c.m_network = NET_ONION;
# 396 : 2 : } else {
# 397 : 2 : c.m_network = NET_IPV6;
# 398 : 2 : }
# 399 : 2 : },
# 400 : 2 : /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5, 12, 15, 16, 17, 18, 23},
# 401 : 2 : /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11, 13, 14, 19, 20, 21, 22},
# 402 : 2 : random_context));
# 403 : :
# 404 : : // Combined test: expect having 1 localhost, 3 I2P and 6 onion peers out of
# 405 : : // 24 to protect 1, 3, and 2 (6 total, I2P has fewer candidates and so gets the
# 406 : : // unused localhost slot), plus 6 others for 12/24 total, sorted by longest uptime.
# 407 : 2 : BOOST_CHECK(IsProtected(
# 408 : 2 : 24, [](NodeEvictionCandidate& c) {
# 409 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 410 : 2 : c.m_is_local = (c.id == 15);
# 411 : 2 : if (c.id == 12 || c.id == 14 || c.id == 17) {
# 412 : 2 : c.m_network = NET_I2P;
# 413 : 2 : } else if (c.id > 17) { // 4 protected instead of usual 2
# 414 : 2 : c.m_network = NET_ONION;
# 415 : 2 : } else {
# 416 : 2 : c.m_network = NET_IPV4;
# 417 : 2 : }
# 418 : 2 : },
# 419 : 2 : /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5, 12, 14, 15, 17, 18, 19},
# 420 : 2 : /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11, 13, 16, 20, 21, 22, 23},
# 421 : 2 : random_context));
# 422 : :
# 423 : : // Combined test: expect having 1 localhost, 7 I2P and 4 onion peers out of
# 424 : : // 24 to protect 1 localhost, 2 I2P, and 3 onions (6 total), plus 6 others
# 425 : : // for 12/24 total, sorted by longest uptime.
# 426 : 2 : BOOST_CHECK(IsProtected(
# 427 : 2 : 24, [](NodeEvictionCandidate& c) {
# 428 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 429 : 2 : c.m_is_local = (c.id == 13);
# 430 : 2 : if (c.id > 16) {
# 431 : 2 : c.m_network = NET_I2P;
# 432 : 2 : } else if (c.id == 12 || c.id == 14 || c.id == 15 || c.id == 16) {
# 433 : 2 : c.m_network = NET_ONION;
# 434 : 2 : } else {
# 435 : 2 : c.m_network = NET_IPV6;
# 436 : 2 : }
# 437 : 2 : },
# 438 : 2 : /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5, 12, 13, 14, 15, 17, 18},
# 439 : 2 : /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11, 16, 19, 20, 21, 22, 23},
# 440 : 2 : random_context));
# 441 : :
# 442 : : // Combined test: expect having 8 localhost, 4 CJDNS, and 3 onion peers out
# 443 : : // of 24 to protect 2 of each (6 total), plus 6 others for 12/24 total,
# 444 : : // sorted by longest uptime.
# 445 : 2 : BOOST_CHECK(IsProtected(
# 446 : 2 : 24, [](NodeEvictionCandidate& c) {
# 447 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 448 : 2 : c.m_is_local = (c.id > 15);
# 449 : 2 : if (c.id > 10 && c.id < 15) {
# 450 : 2 : c.m_network = NET_CJDNS;
# 451 : 2 : } else if (c.id > 6 && c.id < 10) {
# 452 : 2 : c.m_network = NET_ONION;
# 453 : 2 : } else {
# 454 : 2 : c.m_network = NET_IPV4;
# 455 : 2 : }
# 456 : 2 : },
# 457 : 2 : /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5, 7, 8, 11, 12, 16, 17},
# 458 : 2 : /*unprotected_peer_ids=*/{6, 9, 10, 13, 14, 15, 18, 19, 20, 21, 22, 23},
# 459 : 2 : random_context));
# 460 : :
# 461 : : // Tests with 4 networks...
# 462 : :
# 463 : : // Combined test: expect having 1 CJDNS, 1 I2P, 1 localhost and 1 onion peer
# 464 : : // out of 5 to protect 1 CJDNS, 0 I2P, 0 localhost, 0 onion and 1 other peer
# 465 : : // (2 total), sorted by longest uptime; stable sort breaks tie with array
# 466 : : // order of CJDNS first.
# 467 : 2 : BOOST_CHECK(IsProtected(
# 468 : 2 : 5, [](NodeEvictionCandidate& c) {
# 469 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 470 : 2 : c.m_is_local = (c.id == 3);
# 471 : 2 : if (c.id == 4) {
# 472 : 2 : c.m_network = NET_CJDNS;
# 473 : 2 : } else if (c.id == 1) {
# 474 : 2 : c.m_network = NET_I2P;
# 475 : 2 : } else if (c.id == 2) {
# 476 : 2 : c.m_network = NET_ONION;
# 477 : 2 : } else {
# 478 : 2 : c.m_network = NET_IPV6;
# 479 : 2 : }
# 480 : 2 : },
# 481 : 2 : /*protected_peer_ids=*/{0, 4},
# 482 : 2 : /*unprotected_peer_ids=*/{1, 2, 3},
# 483 : 2 : random_context));
# 484 : :
# 485 : : // Combined test: expect having 1 CJDNS, 1 I2P, 1 localhost and 1 onion peer
# 486 : : // out of 7 to protect 1 CJDNS, 0, I2P, 0 localhost, 0 onion and 2 other
# 487 : : // peers (3 total) sorted by longest uptime; stable sort breaks tie with
# 488 : : // array order of CJDNS first.
# 489 : 2 : BOOST_CHECK(IsProtected(
# 490 : 2 : 7, [](NodeEvictionCandidate& c) {
# 491 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 492 : 2 : c.m_is_local = (c.id == 4);
# 493 : 2 : if (c.id == 6) {
# 494 : 2 : c.m_network = NET_CJDNS;
# 495 : 2 : } else if (c.id == 5) {
# 496 : 2 : c.m_network = NET_I2P;
# 497 : 2 : } else if (c.id == 3) {
# 498 : 2 : c.m_network = NET_ONION;
# 499 : 2 : } else {
# 500 : 2 : c.m_network = NET_IPV4;
# 501 : 2 : }
# 502 : 2 : },
# 503 : 2 : /*protected_peer_ids=*/{0, 1, 6},
# 504 : 2 : /*unprotected_peer_ids=*/{2, 3, 4, 5},
# 505 : 2 : random_context));
# 506 : :
# 507 : : // Combined test: expect having 1 CJDNS, 1 I2P, 1 localhost and 1 onion peer
# 508 : : // out of 8 to protect 1 CJDNS, 1 I2P, 0 localhost, 0 onion and 2 other
# 509 : : // peers (4 total) sorted by longest uptime; stable sort breaks tie with
# 510 : : // array order of CJDNS first.
# 511 : 2 : BOOST_CHECK(IsProtected(
# 512 : 2 : 8, [](NodeEvictionCandidate& c) {
# 513 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 514 : 2 : c.m_is_local = (c.id == 3);
# 515 : 2 : if (c.id == 5) {
# 516 : 2 : c.m_network = NET_CJDNS;
# 517 : 2 : } else if (c.id == 6) {
# 518 : 2 : c.m_network = NET_I2P;
# 519 : 2 : } else if (c.id == 3) {
# 520 : 2 : c.m_network = NET_ONION;
# 521 : 2 : } else {
# 522 : 2 : c.m_network = NET_IPV6;
# 523 : 2 : }
# 524 : 2 : },
# 525 : 2 : /*protected_peer_ids=*/{0, 1, 5, 6},
# 526 : 2 : /*unprotected_peer_ids=*/{2, 3, 4, 7},
# 527 : 2 : random_context));
# 528 : :
# 529 : : // Combined test: expect having 2 CJDNS, 2 I2P, 4 localhost, and 2 onion
# 530 : : // peers out of 16 to protect 1 CJDNS, 1 I2P, 1 localhost, 1 onion (4/16
# 531 : : // total), plus 4 others for 8 total, sorted by longest uptime.
# 532 : 2 : BOOST_CHECK(IsProtected(
# 533 : 2 : 16, [](NodeEvictionCandidate& c) {
# 534 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 535 : 2 : c.m_is_local = (c.id > 5);
# 536 : 2 : if (c.id == 11 || c.id == 15) {
# 537 : 2 : c.m_network = NET_CJDNS;
# 538 : 2 : } else if (c.id == 10 || c.id == 14) {
# 539 : 2 : c.m_network = NET_I2P;
# 540 : 2 : } else if (c.id == 8 || c.id == 9) {
# 541 : 2 : c.m_network = NET_ONION;
# 542 : 2 : } else {
# 543 : 2 : c.m_network = NET_IPV4;
# 544 : 2 : }
# 545 : 2 : },
# 546 : 2 : /*protected_peer_ids=*/{0, 1, 2, 3, 6, 8, 10, 11},
# 547 : 2 : /*unprotected_peer_ids=*/{4, 5, 7, 9, 12, 13, 14, 15},
# 548 : 2 : random_context));
# 549 : :
# 550 : : // Combined test: expect having 6 CJDNS, 1 I2P, 1 localhost, and 4 onion
# 551 : : // peers out of 24 to protect 2 CJDNS, 1 I2P, 1 localhost, and 2 onions (6
# 552 : : // total), plus 6 others for 12/24 total, sorted by longest uptime.
# 553 : 2 : BOOST_CHECK(IsProtected(
# 554 : 2 : 24, [](NodeEvictionCandidate& c) {
# 555 : 2 : c.m_connected = std::chrono::seconds{c.id};
# 556 : 2 : c.m_is_local = (c.id == 13);
# 557 : 2 : if (c.id > 17) {
# 558 : 2 : c.m_network = NET_CJDNS;
# 559 : 2 : } else if (c.id == 17) {
# 560 : 2 : c.m_network = NET_I2P;
# 561 : 2 : } else if (c.id == 12 || c.id == 14 || c.id == 15 || c.id == 16) {
# 562 : 2 : c.m_network = NET_ONION;
# 563 : 2 : } else {
# 564 : 2 : c.m_network = NET_IPV6;
# 565 : 2 : }
# 566 : 2 : },
# 567 : 2 : /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5, 12, 13, 14, 17, 18, 19},
# 568 : 2 : /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11, 15, 16, 20, 21, 22, 23},
# 569 : 2 : random_context));
# 570 : 2 : }
# 571 : :
# 572 : : // Returns true if any of the node ids in node_ids are selected for eviction.
# 573 : : bool IsEvicted(std::vector<NodeEvictionCandidate> candidates, const std::unordered_set<NodeId>& node_ids, FastRandomContext& random_context)
# 574 : 2800 : {
# 575 : 2800 : Shuffle(candidates.begin(), candidates.end(), random_context);
# 576 : 2800 : const std::optional<NodeId> evicted_node_id = SelectNodeToEvict(std::move(candidates));
# 577 [ + + ]: 2800 : if (!evicted_node_id) {
# 578 : 322 : return false;
# 579 : 322 : }
# 580 : 2478 : return node_ids.count(*evicted_node_id);
# 581 : 2800 : }
# 582 : :
# 583 : : // Create number_of_nodes random nodes, apply setup function candidate_setup_fn,
# 584 : : // apply eviction logic and then return true if any of the node ids in node_ids
# 585 : : // are selected for eviction.
# 586 : : bool IsEvicted(const int number_of_nodes, std::function<void(NodeEvictionCandidate&)> candidate_setup_fn, const std::unordered_set<NodeId>& node_ids, FastRandomContext& random_context)
# 587 : 2800 : {
# 588 : 2800 : std::vector<NodeEvictionCandidate> candidates = GetRandomNodeEvictionCandidates(number_of_nodes, random_context);
# 589 [ + + ]: 278600 : for (NodeEvictionCandidate& candidate : candidates) {
# 590 : 278600 : candidate_setup_fn(candidate);
# 591 : 278600 : }
# 592 : 2800 : return IsEvicted(candidates, node_ids, random_context);
# 593 : 2800 : }
# 594 : :
# 595 : : BOOST_AUTO_TEST_CASE(peer_eviction_test)
# 596 : 2 : {
# 597 : 2 : FastRandomContext random_context{true};
# 598 : :
# 599 [ + + ]: 402 : for (int number_of_nodes = 0; number_of_nodes < 200; ++number_of_nodes) {
# 600 : : // Four nodes with the highest keyed netgroup values should be
# 601 : : // protected from eviction.
# 602 : 400 : BOOST_CHECK(!IsEvicted(
# 603 : 400 : number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
# 604 : 400 : candidate.nKeyedNetGroup = number_of_nodes - candidate.id;
# 605 : 400 : },
# 606 : 400 : {0, 1, 2, 3}, random_context));
# 607 : :
# 608 : : // Eight nodes with the lowest minimum ping time should be protected
# 609 : : // from eviction.
# 610 : 400 : BOOST_CHECK(!IsEvicted(
# 611 : 400 : number_of_nodes, [](NodeEvictionCandidate& candidate) {
# 612 : 400 : candidate.m_min_ping_time = std::chrono::microseconds{candidate.id};
# 613 : 400 : },
# 614 : 400 : {0, 1, 2, 3, 4, 5, 6, 7}, random_context));
# 615 : :
# 616 : : // Four nodes that most recently sent us novel transactions accepted
# 617 : : // into our mempool should be protected from eviction.
# 618 : 400 : BOOST_CHECK(!IsEvicted(
# 619 : 400 : number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
# 620 : 400 : candidate.m_last_tx_time = std::chrono::seconds{number_of_nodes - candidate.id};
# 621 : 400 : },
# 622 : 400 : {0, 1, 2, 3}, random_context));
# 623 : :
# 624 : : // Up to eight non-tx-relay peers that most recently sent us novel
# 625 : : // blocks should be protected from eviction.
# 626 : 400 : BOOST_CHECK(!IsEvicted(
# 627 : 400 : number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
# 628 : 400 : candidate.m_last_block_time = std::chrono::seconds{number_of_nodes - candidate.id};
# 629 : 400 : if (candidate.id <= 7) {
# 630 : 400 : candidate.m_relay_txs = false;
# 631 : 400 : candidate.fRelevantServices = true;
# 632 : 400 : }
# 633 : 400 : },
# 634 : 400 : {0, 1, 2, 3, 4, 5, 6, 7}, random_context));
# 635 : :
# 636 : : // Four peers that most recently sent us novel blocks should be
# 637 : : // protected from eviction.
# 638 : 400 : BOOST_CHECK(!IsEvicted(
# 639 : 400 : number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
# 640 : 400 : candidate.m_last_block_time = std::chrono::seconds{number_of_nodes - candidate.id};
# 641 : 400 : },
# 642 : 400 : {0, 1, 2, 3}, random_context));
# 643 : :
# 644 : : // Combination of the previous two tests.
# 645 : 400 : BOOST_CHECK(!IsEvicted(
# 646 : 400 : number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
# 647 : 400 : candidate.m_last_block_time = std::chrono::seconds{number_of_nodes - candidate.id};
# 648 : 400 : if (candidate.id <= 7) {
# 649 : 400 : candidate.m_relay_txs = false;
# 650 : 400 : candidate.fRelevantServices = true;
# 651 : 400 : }
# 652 : 400 : },
# 653 : 400 : {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, random_context));
# 654 : :
# 655 : : // Combination of all tests above.
# 656 : 400 : BOOST_CHECK(!IsEvicted(
# 657 : 400 : number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
# 658 : 400 : candidate.nKeyedNetGroup = number_of_nodes - candidate.id; // 4 protected
# 659 : 400 : candidate.m_min_ping_time = std::chrono::microseconds{candidate.id}; // 8 protected
# 660 : 400 : candidate.m_last_tx_time = std::chrono::seconds{number_of_nodes - candidate.id}; // 4 protected
# 661 : 400 : candidate.m_last_block_time = std::chrono::seconds{number_of_nodes - candidate.id}; // 4 protected
# 662 : 400 : },
# 663 : 400 : {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}, random_context));
# 664 : :
# 665 : : // An eviction is expected given >= 29 random eviction candidates. The eviction logic protects at most
# 666 : : // four peers by net group, eight by lowest ping time, four by last time of novel tx, up to eight non-tx-relay
# 667 : : // peers by last novel block time, and four more peers by last novel block time.
# 668 [ + + ]: 400 : if (number_of_nodes >= 29) {
# 669 : 342 : BOOST_CHECK(SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context)));
# 670 : 342 : }
# 671 : :
# 672 : : // No eviction is expected given <= 20 random eviction candidates. The eviction logic protects at least
# 673 : : // four peers by net group, eight by lowest ping time, four by last time of novel tx and four peers by last
# 674 : : // novel block time.
# 675 [ + + ]: 400 : if (number_of_nodes <= 20) {
# 676 : 42 : BOOST_CHECK(!SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context)));
# 677 : 42 : }
# 678 : :
# 679 : : // Cases left to test:
# 680 : : // * "If any remaining peers are preferred for eviction consider only them. [...]"
# 681 : : // * "Identify the network group with the most connections and youngest member. [...]"
# 682 : 400 : }
# 683 : 2 : }
# 684 : :
# 685 : : BOOST_AUTO_TEST_SUITE_END()
|