net_peer_eviction_tests.cpp
1 // Copyright (c) 2021-present 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 { 30 std::vector<NodeEvictionCandidate> candidates{GetRandomNodeEvictionCandidates(num_peers, random_context)}; 31 for (NodeEvictionCandidate& candidate : candidates) { 32 candidate_setup_fn(candidate); 33 } 34 std::shuffle(candidates.begin(), candidates.end(), random_context); 35 36 const size_t size{candidates.size()}; 37 const size_t expected{size - size / 2}; // Expect half the candidates will be protected. 38 ProtectEvictionCandidatesByRatio(candidates); 39 BOOST_CHECK_EQUAL(candidates.size(), expected); 40 41 size_t unprotected_count{0}; 42 for (const NodeEvictionCandidate& candidate : candidates) { 43 if (protected_peer_ids.contains(candidate.id)) { 44 // this peer should have been removed from the eviction candidates 45 BOOST_TEST_MESSAGE(strprintf("expected candidate to be protected: %d", candidate.id)); 46 return false; 47 } 48 if (unprotected_peer_ids.contains(candidate.id)) { 49 // this peer remains in the eviction candidates, as expected 50 ++unprotected_count; 51 } 52 } 53 54 const bool is_protected{unprotected_count == unprotected_peer_ids.size()}; 55 if (!is_protected) { 56 BOOST_TEST_MESSAGE(strprintf("unprotected: expected %d, actual %d", 57 unprotected_peer_ids.size(), unprotected_count)); 58 } 59 return is_protected; 60 } 61 62 BOOST_AUTO_TEST_CASE(peer_protection_test) 63 { 64 FastRandomContext random_context{true}; 65 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 BOOST_CHECK(IsProtected( 70 num_peers, [](NodeEvictionCandidate& c) { 71 c.m_connected = std::chrono::seconds{c.id}; 72 c.m_is_local = false; 73 c.m_network = NET_IPV4; 74 }, 75 /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5}, 76 /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11}, 77 random_context)); 78 79 // Verify in the opposite direction. 80 BOOST_CHECK(IsProtected( 81 num_peers, [num_peers](NodeEvictionCandidate& c) { 82 c.m_connected = std::chrono::seconds{num_peers - c.id}; 83 c.m_is_local = false; 84 c.m_network = NET_IPV6; 85 }, 86 /*protected_peer_ids=*/{6, 7, 8, 9, 10, 11}, 87 /*unprotected_peer_ids=*/{0, 1, 2, 3, 4, 5}, 88 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 BOOST_CHECK(IsProtected( 95 num_peers, [](NodeEvictionCandidate& c) { 96 c.m_is_local = false; 97 c.m_network = (c.id == 3 || c.id == 8 || c.id == 9) ? NET_ONION : NET_IPV4; 98 }, 99 /*protected_peer_ids=*/{3, 8, 9}, 100 /*unprotected_peer_ids=*/{}, 101 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 BOOST_CHECK(IsProtected( 106 num_peers, [](NodeEvictionCandidate& c) { 107 c.m_connected = std::chrono::seconds{c.id}; 108 c.m_is_local = false; 109 c.m_network = (c.id == 3 || c.id > 7) ? NET_ONION : NET_IPV6; 110 }, 111 /*protected_peer_ids=*/{0, 1, 2, 3, 8, 9}, 112 /*unprotected_peer_ids=*/{4, 5, 6, 7, 10, 11}, 113 random_context)); 114 115 // Expect 1/4 localhost peers to be protected from eviction, 116 // if no onion, I2P, or CJDNS peers. 117 BOOST_CHECK(IsProtected( 118 num_peers, [](NodeEvictionCandidate& c) { 119 c.m_is_local = (c.id == 1 || c.id == 9 || c.id == 11); 120 c.m_network = NET_IPV4; 121 }, 122 /*protected_peer_ids=*/{1, 9, 11}, 123 /*unprotected_peer_ids=*/{}, 124 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 BOOST_CHECK(IsProtected( 129 num_peers, [](NodeEvictionCandidate& c) { 130 c.m_connected = std::chrono::seconds{c.id}; 131 c.m_is_local = (c.id > 6); 132 c.m_network = NET_IPV6; 133 }, 134 /*protected_peer_ids=*/{0, 1, 2, 7, 8, 9}, 135 /*unprotected_peer_ids=*/{3, 4, 5, 6, 10, 11}, 136 random_context)); 137 138 // Expect 1/4 I2P peers to be protected from eviction, 139 // if no onion, localhost, or CJDNS peers. 140 BOOST_CHECK(IsProtected( 141 num_peers, [](NodeEvictionCandidate& c) { 142 c.m_is_local = false; 143 c.m_network = (c.id == 2 || c.id == 7 || c.id == 10) ? NET_I2P : NET_IPV4; 144 }, 145 /*protected_peer_ids=*/{2, 7, 10}, 146 /*unprotected_peer_ids=*/{}, 147 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 BOOST_CHECK(IsProtected( 152 num_peers, [](NodeEvictionCandidate& c) { 153 c.m_connected = std::chrono::seconds{c.id}; 154 c.m_is_local = false; 155 c.m_network = (c.id == 4 || c.id > 8) ? NET_I2P : NET_IPV6; 156 }, 157 /*protected_peer_ids=*/{0, 1, 2, 4, 9, 10}, 158 /*unprotected_peer_ids=*/{3, 5, 6, 7, 8, 11}, 159 random_context)); 160 161 // Expect 1/4 CJDNS peers to be protected from eviction, 162 // if no onion, localhost, or I2P peers. 163 BOOST_CHECK(IsProtected( 164 num_peers, [](NodeEvictionCandidate& c) { 165 c.m_is_local = false; 166 c.m_network = (c.id == 2 || c.id == 7 || c.id == 10) ? NET_CJDNS : NET_IPV4; 167 }, 168 /*protected_peer_ids=*/{2, 7, 10}, 169 /*unprotected_peer_ids=*/{}, 170 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 BOOST_CHECK(IsProtected( 175 num_peers, [](NodeEvictionCandidate& c) { 176 c.m_connected = std::chrono::seconds{c.id}; 177 c.m_is_local = false; 178 c.m_network = (c.id == 4 || c.id > 8) ? NET_CJDNS : NET_IPV6; 179 }, 180 /*protected_peer_ids=*/{0, 1, 2, 4, 9, 10}, 181 /*unprotected_peer_ids=*/{3, 5, 6, 7, 8, 11}, 182 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 BOOST_CHECK(IsProtected( 190 4, [](NodeEvictionCandidate& c) { 191 c.m_connected = std::chrono::seconds{c.id}; 192 c.m_is_local = (c.id == 4); 193 c.m_network = (c.id == 3) ? NET_ONION : NET_IPV4; 194 }, 195 /*protected_peer_ids=*/{0, 4}, 196 /*unprotected_peer_ids=*/{1, 2}, 197 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 BOOST_CHECK(IsProtected( 203 7, [](NodeEvictionCandidate& c) { 204 c.m_connected = std::chrono::seconds{c.id}; 205 c.m_is_local = (c.id == 6); 206 c.m_network = (c.id == 5) ? NET_ONION : NET_IPV4; 207 }, 208 /*protected_peer_ids=*/{0, 1, 6}, 209 /*unprotected_peer_ids=*/{2, 3, 4, 5}, 210 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 BOOST_CHECK(IsProtected( 216 8, [](NodeEvictionCandidate& c) { 217 c.m_connected = std::chrono::seconds{c.id}; 218 c.m_is_local = (c.id == 6); 219 c.m_network = (c.id == 5) ? NET_ONION : NET_IPV4; 220 }, 221 /*protected_peer_ids=*/{0, 1, 5, 6}, 222 /*unprotected_peer_ids=*/{2, 3, 4, 7}, 223 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 BOOST_CHECK(IsProtected( 229 num_peers, [](NodeEvictionCandidate& c) { 230 c.m_connected = std::chrono::seconds{c.id}; 231 c.m_is_local = (c.id == 6 || c.id == 9 || c.id == 11); 232 c.m_network = (c.id == 7 || c.id == 8 || c.id == 10) ? NET_ONION : NET_IPV6; 233 }, 234 /*protected_peer_ids=*/{0, 1, 2, 6, 7, 9}, 235 /*unprotected_peer_ids=*/{3, 4, 5, 8, 10, 11}, 236 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 BOOST_CHECK(IsProtected( 241 num_peers, [](NodeEvictionCandidate& c) { 242 c.m_connected = std::chrono::seconds{c.id}; 243 c.m_is_local = (c.id > 4 && c.id < 9); 244 c.m_network = (c.id == 10) ? NET_ONION : NET_IPV4; 245 }, 246 /*protected_peer_ids=*/{0, 1, 2, 5, 6, 10}, 247 /*unprotected_peer_ids=*/{3, 4, 7, 8, 9, 11}, 248 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 BOOST_CHECK(IsProtected( 253 16, [](NodeEvictionCandidate& c) { 254 c.m_connected = std::chrono::seconds{c.id}; 255 c.m_is_local = (c.id == 6 || c.id == 9 || c.id == 11 || c.id == 12); 256 c.m_network = (c.id == 8 || c.id == 10) ? NET_ONION : NET_IPV6; 257 }, 258 /*protected_peer_ids=*/{0, 1, 2, 3, 6, 8, 9, 10}, 259 /*unprotected_peer_ids=*/{4, 5, 7, 11, 12, 13, 14, 15}, 260 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 BOOST_CHECK(IsProtected( 266 16, [](NodeEvictionCandidate& c) { 267 c.m_connected = std::chrono::seconds{c.id}; 268 c.m_is_local = (c.id > 10); 269 c.m_network = (c.id == 10) ? NET_ONION : NET_IPV4; 270 }, 271 /*protected_peer_ids=*/{0, 1, 2, 3, 10, 11, 12, 13}, 272 /*unprotected_peer_ids=*/{4, 5, 6, 7, 8, 9, 14, 15}, 273 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 BOOST_CHECK(IsProtected( 279 16, [](NodeEvictionCandidate& c) { 280 c.m_connected = std::chrono::seconds{c.id}; 281 c.m_is_local = (c.id == 15); 282 c.m_network = (c.id > 6 && c.id < 11) ? NET_ONION : NET_IPV6; 283 }, 284 /*protected_peer_ids=*/{0, 1, 2, 3, 7, 8, 9, 15}, 285 /*unprotected_peer_ids=*/{5, 6, 10, 11, 12, 13, 14}, 286 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 BOOST_CHECK(IsProtected( 292 num_peers, [](NodeEvictionCandidate& c) { 293 c.m_connected = std::chrono::seconds{c.id}; 294 c.m_is_local = false; 295 if (c.id == 8 || c.id == 10) { 296 c.m_network = NET_ONION; 297 } else if (c.id == 6 || c.id == 9 || c.id == 11 || c.id == 12) { 298 c.m_network = NET_I2P; 299 } else { 300 c.m_network = NET_IPV4; 301 } 302 }, 303 /*protected_peer_ids=*/{0, 1, 2, 6, 8, 10}, 304 /*unprotected_peer_ids=*/{3, 4, 5, 7, 9, 11}, 305 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 BOOST_CHECK(IsProtected( 313 4, [](NodeEvictionCandidate& c) { 314 c.m_connected = std::chrono::seconds{c.id}; 315 c.m_is_local = (c.id == 2); 316 if (c.id == 3) { 317 c.m_network = NET_I2P; 318 } else if (c.id == 1) { 319 c.m_network = NET_ONION; 320 } else { 321 c.m_network = NET_IPV6; 322 } 323 }, 324 /*protected_peer_ids=*/{0, 3}, 325 /*unprotected_peer_ids=*/{1, 2}, 326 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 BOOST_CHECK(IsProtected( 332 7, [](NodeEvictionCandidate& c) { 333 c.m_connected = std::chrono::seconds{c.id}; 334 c.m_is_local = (c.id == 4); 335 if (c.id == 6) { 336 c.m_network = NET_I2P; 337 } else if (c.id == 5) { 338 c.m_network = NET_ONION; 339 } else { 340 c.m_network = NET_IPV6; 341 } 342 }, 343 /*protected_peer_ids=*/{0, 1, 6}, 344 /*unprotected_peer_ids=*/{2, 3, 4, 5}, 345 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 BOOST_CHECK(IsProtected( 351 8, [](NodeEvictionCandidate& c) { 352 c.m_connected = std::chrono::seconds{c.id}; 353 c.m_is_local = (c.id == 6); 354 if (c.id == 5) { 355 c.m_network = NET_I2P; 356 } else if (c.id == 4) { 357 c.m_network = NET_ONION; 358 } else { 359 c.m_network = NET_IPV6; 360 } 361 }, 362 /*protected_peer_ids=*/{0, 1, 5, 6}, 363 /*unprotected_peer_ids=*/{2, 3, 4, 7}, 364 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 BOOST_CHECK(IsProtected( 370 16, [](NodeEvictionCandidate& c) { 371 c.m_connected = std::chrono::seconds{c.id}; 372 c.m_is_local = (c.id == 6 || c.id > 11); 373 if (c.id == 7 || c.id == 11) { 374 c.m_network = NET_I2P; 375 } else if (c.id == 9 || c.id == 10) { 376 c.m_network = NET_ONION; 377 } else { 378 c.m_network = NET_IPV4; 379 } 380 }, 381 /*protected_peer_ids=*/{0, 1, 2, 3, 6, 7, 9, 11}, 382 /*unprotected_peer_ids=*/{4, 5, 8, 10, 12, 13, 14, 15}, 383 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 BOOST_CHECK(IsProtected( 389 24, [](NodeEvictionCandidate& c) { 390 c.m_connected = std::chrono::seconds{c.id}; 391 c.m_is_local = (c.id == 12); 392 if (c.id > 14 && c.id < 23) { // 4 protected instead of usual 2 393 c.m_network = NET_I2P; 394 } else if (c.id == 23) { 395 c.m_network = NET_ONION; 396 } else { 397 c.m_network = NET_IPV6; 398 } 399 }, 400 /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5, 12, 15, 16, 17, 18, 23}, 401 /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11, 13, 14, 19, 20, 21, 22}, 402 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 BOOST_CHECK(IsProtected( 408 24, [](NodeEvictionCandidate& c) { 409 c.m_connected = std::chrono::seconds{c.id}; 410 c.m_is_local = (c.id == 15); 411 if (c.id == 12 || c.id == 14 || c.id == 17) { 412 c.m_network = NET_I2P; 413 } else if (c.id > 17) { // 4 protected instead of usual 2 414 c.m_network = NET_ONION; 415 } else { 416 c.m_network = NET_IPV4; 417 } 418 }, 419 /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5, 12, 14, 15, 17, 18, 19}, 420 /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11, 13, 16, 20, 21, 22, 23}, 421 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 BOOST_CHECK(IsProtected( 427 24, [](NodeEvictionCandidate& c) { 428 c.m_connected = std::chrono::seconds{c.id}; 429 c.m_is_local = (c.id == 13); 430 if (c.id > 16) { 431 c.m_network = NET_I2P; 432 } else if (c.id == 12 || c.id == 14 || c.id == 15 || c.id == 16) { 433 c.m_network = NET_ONION; 434 } else { 435 c.m_network = NET_IPV6; 436 } 437 }, 438 /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5, 12, 13, 14, 15, 17, 18}, 439 /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11, 16, 19, 20, 21, 22, 23}, 440 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 BOOST_CHECK(IsProtected( 446 24, [](NodeEvictionCandidate& c) { 447 c.m_connected = std::chrono::seconds{c.id}; 448 c.m_is_local = (c.id > 15); 449 if (c.id > 10 && c.id < 15) { 450 c.m_network = NET_CJDNS; 451 } else if (c.id > 6 && c.id < 10) { 452 c.m_network = NET_ONION; 453 } else { 454 c.m_network = NET_IPV4; 455 } 456 }, 457 /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5, 7, 8, 11, 12, 16, 17}, 458 /*unprotected_peer_ids=*/{6, 9, 10, 13, 14, 15, 18, 19, 20, 21, 22, 23}, 459 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 BOOST_CHECK(IsProtected( 468 5, [](NodeEvictionCandidate& c) { 469 c.m_connected = std::chrono::seconds{c.id}; 470 c.m_is_local = (c.id == 3); 471 if (c.id == 4) { 472 c.m_network = NET_CJDNS; 473 } else if (c.id == 1) { 474 c.m_network = NET_I2P; 475 } else if (c.id == 2) { 476 c.m_network = NET_ONION; 477 } else { 478 c.m_network = NET_IPV6; 479 } 480 }, 481 /*protected_peer_ids=*/{0, 4}, 482 /*unprotected_peer_ids=*/{1, 2, 3}, 483 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 BOOST_CHECK(IsProtected( 490 7, [](NodeEvictionCandidate& c) { 491 c.m_connected = std::chrono::seconds{c.id}; 492 c.m_is_local = (c.id == 4); 493 if (c.id == 6) { 494 c.m_network = NET_CJDNS; 495 } else if (c.id == 5) { 496 c.m_network = NET_I2P; 497 } else if (c.id == 3) { 498 c.m_network = NET_ONION; 499 } else { 500 c.m_network = NET_IPV4; 501 } 502 }, 503 /*protected_peer_ids=*/{0, 1, 6}, 504 /*unprotected_peer_ids=*/{2, 3, 4, 5}, 505 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 BOOST_CHECK(IsProtected( 512 8, [](NodeEvictionCandidate& c) { 513 c.m_connected = std::chrono::seconds{c.id}; 514 c.m_is_local = (c.id == 3); 515 if (c.id == 5) { 516 c.m_network = NET_CJDNS; 517 } else if (c.id == 6) { 518 c.m_network = NET_I2P; 519 } else if (c.id == 3) { 520 c.m_network = NET_ONION; 521 } else { 522 c.m_network = NET_IPV6; 523 } 524 }, 525 /*protected_peer_ids=*/{0, 1, 5, 6}, 526 /*unprotected_peer_ids=*/{2, 3, 4, 7}, 527 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 BOOST_CHECK(IsProtected( 533 16, [](NodeEvictionCandidate& c) { 534 c.m_connected = std::chrono::seconds{c.id}; 535 c.m_is_local = (c.id > 5); 536 if (c.id == 11 || c.id == 15) { 537 c.m_network = NET_CJDNS; 538 } else if (c.id == 10 || c.id == 14) { 539 c.m_network = NET_I2P; 540 } else if (c.id == 8 || c.id == 9) { 541 c.m_network = NET_ONION; 542 } else { 543 c.m_network = NET_IPV4; 544 } 545 }, 546 /*protected_peer_ids=*/{0, 1, 2, 3, 6, 8, 10, 11}, 547 /*unprotected_peer_ids=*/{4, 5, 7, 9, 12, 13, 14, 15}, 548 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 BOOST_CHECK(IsProtected( 554 24, [](NodeEvictionCandidate& c) { 555 c.m_connected = std::chrono::seconds{c.id}; 556 c.m_is_local = (c.id == 13); 557 if (c.id > 17) { 558 c.m_network = NET_CJDNS; 559 } else if (c.id == 17) { 560 c.m_network = NET_I2P; 561 } else if (c.id == 12 || c.id == 14 || c.id == 15 || c.id == 16) { 562 c.m_network = NET_ONION; 563 } else { 564 c.m_network = NET_IPV6; 565 } 566 }, 567 /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5, 12, 13, 14, 17, 18, 19}, 568 /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11, 15, 16, 20, 21, 22, 23}, 569 random_context)); 570 } 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 { 575 std::shuffle(candidates.begin(), candidates.end(), random_context); 576 const std::optional<NodeId> evicted_node_id = SelectNodeToEvict(std::move(candidates)); 577 if (!evicted_node_id) { 578 return false; 579 } 580 return node_ids.contains(*evicted_node_id); 581 } 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 { 588 std::vector<NodeEvictionCandidate> candidates = GetRandomNodeEvictionCandidates(number_of_nodes, random_context); 589 for (NodeEvictionCandidate& candidate : candidates) { 590 candidate_setup_fn(candidate); 591 } 592 return IsEvicted(candidates, node_ids, random_context); 593 } 594 595 BOOST_AUTO_TEST_CASE(peer_eviction_test) 596 { 597 FastRandomContext random_context{true}; 598 599 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 BOOST_CHECK(!IsEvicted( 603 number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { 604 candidate.nKeyedNetGroup = number_of_nodes - candidate.id; 605 }, 606 {0, 1, 2, 3}, random_context)); 607 608 // Eight nodes with the lowest minimum ping time should be protected 609 // from eviction. 610 BOOST_CHECK(!IsEvicted( 611 number_of_nodes, [](NodeEvictionCandidate& candidate) { 612 candidate.m_min_ping_time = std::chrono::microseconds{candidate.id}; 613 }, 614 {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 BOOST_CHECK(!IsEvicted( 619 number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { 620 candidate.m_last_tx_time = std::chrono::seconds{number_of_nodes - candidate.id}; 621 }, 622 {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 BOOST_CHECK(!IsEvicted( 627 number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { 628 candidate.m_last_block_time = std::chrono::seconds{number_of_nodes - candidate.id}; 629 if (candidate.id <= 7) { 630 candidate.m_relay_txs = false; 631 candidate.fRelevantServices = true; 632 } 633 }, 634 {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 BOOST_CHECK(!IsEvicted( 639 number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { 640 candidate.m_last_block_time = std::chrono::seconds{number_of_nodes - candidate.id}; 641 }, 642 {0, 1, 2, 3}, random_context)); 643 644 // Combination of the previous two tests. 645 BOOST_CHECK(!IsEvicted( 646 number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { 647 candidate.m_last_block_time = std::chrono::seconds{number_of_nodes - candidate.id}; 648 if (candidate.id <= 7) { 649 candidate.m_relay_txs = false; 650 candidate.fRelevantServices = true; 651 } 652 }, 653 {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, random_context)); 654 655 // Combination of all tests above. 656 BOOST_CHECK(!IsEvicted( 657 number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) { 658 candidate.nKeyedNetGroup = number_of_nodes - candidate.id; // 4 protected 659 candidate.m_min_ping_time = std::chrono::microseconds{candidate.id}; // 8 protected 660 candidate.m_last_tx_time = std::chrono::seconds{number_of_nodes - candidate.id}; // 4 protected 661 candidate.m_last_block_time = std::chrono::seconds{number_of_nodes - candidate.id}; // 4 protected 662 }, 663 {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 if (number_of_nodes >= 29) { 669 BOOST_CHECK(SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context))); 670 } 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 if (number_of_nodes <= 20) { 676 BOOST_CHECK(!SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context))); 677 } 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 } 683 } 684 685 BOOST_AUTO_TEST_SUITE_END()