Bitcoin Core 28.99.0
P2P Digital Currency
net_peer_eviction_tests.cpp
Go to the documentation of this file.
1// Copyright (c) 2021-2022 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>
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
18BOOST_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.
24bool 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.
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.count(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.count(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
62BOOST_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.
70 num_peers, [](NodeEvictionCandidate& c) {
71 c.m_connected = std::chrono::seconds{c.id};
72 c.m_is_local = false;
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.
81 num_peers, [num_peers](NodeEvictionCandidate& c) {
82 c.m_connected = std::chrono::seconds{num_peers - c.id};
83 c.m_is_local = false;
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.
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.
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.
118 num_peers, [](NodeEvictionCandidate& c) {
119 c.m_is_local = (c.id == 1 || c.id == 9 || c.id == 11);
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.
129 num_peers, [](NodeEvictionCandidate& c) {
130 c.m_connected = std::chrono::seconds{c.id};
131 c.m_is_local = (c.id > 6);
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
573bool 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.count(*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.
586bool 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
595BOOST_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.
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.
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.
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.
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.
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.
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.
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
if(!SetupNetworking())
Fast randomness source.
Definition: random.h:377
BOOST_FIXTURE_TEST_SUITE(cuckoocache_tests, BasicTestingSetup)
Test Suite for CuckooCache.
BOOST_AUTO_TEST_SUITE_END()
void ProtectEvictionCandidatesByRatio(std::vector< NodeEvictionCandidate > &eviction_candidates)
Protect desirable or disadvantaged inbound peers from eviction by ratio.
Definition: eviction.cpp:105
std::optional< NodeId > SelectNodeToEvict(std::vector< NodeEvictionCandidate > &&vEvictionCandidates)
Select an inbound peer to evict after filtering out (protecting) peers having distinct,...
Definition: eviction.cpp:178
int64_t NodeId
Definition: net.h:97
bool IsEvicted(std::vector< NodeEvictionCandidate > candidates, const std::unordered_set< NodeId > &node_ids, FastRandomContext &random_context)
BOOST_AUTO_TEST_CASE(peer_protection_test)
bool IsProtected(int num_peers, std::function< void(NodeEvictionCandidate &)> candidate_setup_fn, const std::unordered_set< NodeId > &protected_peer_ids, const std::unordered_set< NodeId > &unprotected_peer_ids, FastRandomContext &random_context)
@ NET_I2P
I2P.
Definition: netaddress.h:46
@ NET_CJDNS
CJDNS.
Definition: netaddress.h:49
@ NET_ONION
TOR (v2 or v3)
Definition: netaddress.h:43
@ NET_IPV6
IPv6.
Definition: netaddress.h:40
@ NET_IPV4
IPv4.
Definition: netaddress.h:37
#define BOOST_CHECK_EQUAL(v1, v2)
Definition: object.cpp:18
#define BOOST_CHECK(expr)
Definition: object.cpp:17
Basic testing setup.
Definition: setup_common.h:63
std::chrono::seconds m_last_tx_time
Definition: eviction.h:23
std::chrono::seconds m_connected
Definition: eviction.h:20
std::chrono::seconds m_last_block_time
Definition: eviction.h:22
std::chrono::microseconds m_min_ping_time
Definition: eviction.h:21
uint64_t nKeyedNetGroup
Definition: eviction.h:27
std::vector< NodeEvictionCandidate > GetRandomNodeEvictionCandidates(int n_candidates, FastRandomContext &random_context)
Definition: net.cpp:116
#define strprintf
Format arguments and return the string or write to given std::ostream (see tinyformat::format doc for...
Definition: tinyformat.h:1165