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 {
30     std::vector<NodeEvictionCandidate> candidates{GetRandomNodeEvictionCandidates(num_peers, random_context)};
31     for (NodeEvictionCandidate& candidate : candidates) {
32         candidate_setup_fn(candidate);
33     }
34     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.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));
check_success_fraction(const char * function,const RealType & p,RealType * result,const Policy &)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));
check_dist(const char * function,const RealType & p,RealType * result,const Policy &,const boost::true_type &)58     }
59     return is_protected;
60 }
61 
62 BOOST_AUTO_TEST_CASE(peer_protection_test)
check_dist(const char *,const RealType &,RealType *,const Policy &,const boost::false_type &)63 {
64     FastRandomContext random_context{true};
65     int num_peers{12};
66 
67     // Expect half of the peers with greatest uptime (the lowest nTimeConnected)
68     // to be protected from eviction.
69     BOOST_CHECK(IsProtected(
70         num_peers, [](NodeEvictionCandidate& c) {
71             c.nTimeConnected = 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.nTimeConnected = 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 or I2P 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 nTimeConnected), if no localhost or I2P peers.
105     BOOST_CHECK(IsProtected(
106         num_peers, [](NodeEvictionCandidate& c) {
107             c.nTimeConnected = 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 or I2P 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 nTimeConnected), if no onion or I2P peers.
128     BOOST_CHECK(IsProtected(
129         num_peers, [](NodeEvictionCandidate& c) {
130             c.nTimeConnected = 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 or localhost 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,
150     // sorted by longest uptime (lowest nTimeConnected), if no onion or localhost peers.
151     BOOST_CHECK(IsProtected(
152         num_peers, [](NodeEvictionCandidate& c) {
153             c.nTimeConnected = 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     // Tests with 2 networks...
162 
163     // Combined test: expect having 1 localhost and 1 onion peer out of 4 to
164     // protect 1 localhost, 0 onion and 1 other peer, sorted by longest uptime;
165     // stable sort breaks tie with array order of localhost first.
166     BOOST_CHECK(IsProtected(
167         4, [](NodeEvictionCandidate& c) {
168             c.nTimeConnected = c.id;
169             c.m_is_local = (c.id == 4);
170             c.m_network = (c.id == 3) ? NET_ONION : NET_IPV4;
171         },
172         /* protected_peer_ids */ {0, 4},
173         /* unprotected_peer_ids */ {1, 2},
174         random_context));
175 
176     // Combined test: expect having 1 localhost and 1 onion peer out of 7 to
177     // protect 1 localhost, 0 onion, and 2 other peers (3 total), sorted by
178     // uptime; stable sort breaks tie with array order of localhost first.
179     BOOST_CHECK(IsProtected(
180         7, [](NodeEvictionCandidate& c) {
181             c.nTimeConnected = c.id;
182             c.m_is_local = (c.id == 6);
183             c.m_network = (c.id == 5) ? NET_ONION : NET_IPV4;
184         },
185         /* protected_peer_ids */ {0, 1, 6},
186         /* unprotected_peer_ids */ {2, 3, 4, 5},
187         random_context));
188 
189     // Combined test: expect having 1 localhost and 1 onion peer out of 8 to
190     // protect protect 1 localhost, 1 onion and 2 other peers (4 total), sorted
191     // by uptime; stable sort breaks tie with array order of localhost first.
192     BOOST_CHECK(IsProtected(
193         8, [](NodeEvictionCandidate& c) {
194             c.nTimeConnected = c.id;
195             c.m_is_local = (c.id == 6);
196             c.m_network = (c.id == 5) ? NET_ONION : NET_IPV4;
197         },
198         /* protected_peer_ids */ {0, 1, 5, 6},
199         /* unprotected_peer_ids */ {2, 3, 4, 7},
200         random_context));
201 
202     // Combined test: expect having 3 localhost and 3 onion peers out of 12 to
203     // protect 2 localhost and 1 onion, plus 3 other peers, sorted by longest
204     // uptime; stable sort breaks ties with the array order of localhost first.
205     BOOST_CHECK(IsProtected(
206         num_peers, [](NodeEvictionCandidate& c) {
207             c.nTimeConnected = c.id;
208             c.m_is_local = (c.id == 6 || c.id == 9 || c.id == 11);
209             c.m_network = (c.id == 7 || c.id == 8 || c.id == 10) ? NET_ONION : NET_IPV6;
210         },
211         /* protected_peer_ids */ {0, 1, 2, 6, 7, 9},
212         /* unprotected_peer_ids */ {3, 4, 5, 8, 10, 11},
213         random_context));
214 
215     // Combined test: expect having 4 localhost and 1 onion peer out of 12 to
216     // protect 2 localhost and 1 onion, plus 3 other peers, sorted by longest uptime.
217     BOOST_CHECK(IsProtected(
218         num_peers, [](NodeEvictionCandidate& c) {
219             c.nTimeConnected = c.id;
220             c.m_is_local = (c.id > 4 && c.id < 9);
221             c.m_network = (c.id == 10) ? NET_ONION : NET_IPV4;
222         },
223         /* protected_peer_ids */ {0, 1, 2, 5, 6, 10},
224         /* unprotected_peer_ids */ {3, 4, 7, 8, 9, 11},
225         random_context));
226 
227     // Combined test: expect having 4 localhost and 2 onion peers out of 16 to
228     // protect 2 localhost and 2 onions, plus 4 other peers, sorted by longest uptime.
229     BOOST_CHECK(IsProtected(
230         16, [](NodeEvictionCandidate& c) {
231             c.nTimeConnected = c.id;
232             c.m_is_local = (c.id == 6 || c.id == 9 || c.id == 11 || c.id == 12);
233             c.m_network = (c.id == 8 || c.id == 10) ? NET_ONION : NET_IPV6;
234         },
235         /* protected_peer_ids */ {0, 1, 2, 3, 6, 8, 9, 10},
236         /* unprotected_peer_ids */ {4, 5, 7, 11, 12, 13, 14, 15},
237         random_context));
238 
239     // Combined test: expect having 5 localhost and 1 onion peer out of 16 to
240     // protect 3 localhost (recovering the unused onion slot), 1 onion, and 4
241     // others, sorted by longest uptime.
242     BOOST_CHECK(IsProtected(
243         16, [](NodeEvictionCandidate& c) {
244             c.nTimeConnected = c.id;
245             c.m_is_local = (c.id > 10);
246             c.m_network = (c.id == 10) ? NET_ONION : NET_IPV4;
247         },
248         /* protected_peer_ids */ {0, 1, 2, 3, 10, 11, 12, 13},
249         /* unprotected_peer_ids */ {4, 5, 6, 7, 8, 9, 14, 15},
250         random_context));
251 
252     // Combined test: expect having 1 localhost and 4 onion peers out of 16 to
253     // protect 1 localhost and 3 onions (recovering the unused localhost slot),
254     // plus 4 others, sorted by longest uptime.
255     BOOST_CHECK(IsProtected(
256         16, [](NodeEvictionCandidate& c) {
257             c.nTimeConnected = c.id;
258             c.m_is_local = (c.id == 15);
259             c.m_network = (c.id > 6 && c.id < 11) ? NET_ONION : NET_IPV6;
260         },
261         /* protected_peer_ids */ {0, 1, 2, 3, 7, 8, 9, 15},
262         /* unprotected_peer_ids */ {5, 6, 10, 11, 12, 13, 14},
263         random_context));
264 
265     // Combined test: expect having 2 onion and 4 I2P out of 12 peers to protect
266     // 2 onion (prioritized for having fewer candidates) and 1 I2P, plus 3
267     // others, sorted by longest uptime.
268     BOOST_CHECK(IsProtected(
269         num_peers, [](NodeEvictionCandidate& c) {
270             c.nTimeConnected = c.id;
271             c.m_is_local = false;
272             if (c.id == 8 || c.id == 10) {
273                 c.m_network = NET_ONION;
274             } else if (c.id == 6 || c.id == 9 || c.id == 11 || c.id == 12) {
275                 c.m_network = NET_I2P;
276             } else {
277                 c.m_network = NET_IPV4;
278             }
279         },
280         /* protected_peer_ids */ {0, 1, 2, 6, 8, 10},
281         /* unprotected_peer_ids */ {3, 4, 5, 7, 9, 11},
282         random_context));
283 
284     // Tests with 3 networks...
285 
286     // Combined test: expect having 1 localhost, 1 I2P and 1 onion peer out of 4
287     // to protect 1 I2P, 0 localhost, 0 onion and 1 other peer (2 total), sorted
288     // by longest uptime; stable sort breaks tie with array order of I2P first.
289     BOOST_CHECK(IsProtected(
290         4, [](NodeEvictionCandidate& c) {
291             c.nTimeConnected = c.id;
292             c.m_is_local = (c.id == 3);
293             if (c.id == 4) {
294                 c.m_network = NET_I2P;
295             } else if (c.id == 2) {
296                 c.m_network = NET_ONION;
297             } else {
298                 c.m_network = NET_IPV6;
299             }
300         },
301         /* protected_peer_ids */ {0, 4},
302         /* unprotected_peer_ids */ {1, 2},
303         random_context));
304 
305     // Combined test: expect having 1 localhost, 1 I2P and 1 onion peer out of 7
306     // to protect 1 I2P, 0 localhost, 0 onion and 2 other peers (3 total) sorted
307     // by longest uptime; stable sort breaks tie with array order of I2P first.
308     BOOST_CHECK(IsProtected(
309         7, [](NodeEvictionCandidate& c) {
310             c.nTimeConnected = c.id;
311             c.m_is_local = (c.id == 4);
312             if (c.id == 6) {
313                 c.m_network = NET_I2P;
314             } else if (c.id == 5) {
315                 c.m_network = NET_ONION;
316             } else {
317                 c.m_network = NET_IPV6;
318             }
319         },
320         /* protected_peer_ids */ {0, 1, 6},
321         /* unprotected_peer_ids */ {2, 3, 4, 5},
322         random_context));
323 
324     // Combined test: expect having 1 localhost, 1 I2P and 1 onion peer out of 8
325     // to protect 1 I2P, 1 localhost, 0 onion and 2 other peers (4 total) sorted
326     // by uptime; stable sort breaks tie with array order of I2P then localhost.
327     BOOST_CHECK(IsProtected(
328         8, [](NodeEvictionCandidate& c) {
329             c.nTimeConnected = c.id;
330             c.m_is_local = (c.id == 6);
331             if (c.id == 5) {
332                 c.m_network = NET_I2P;
333             } else if (c.id == 4) {
334                 c.m_network = NET_ONION;
335             } else {
336                 c.m_network = NET_IPV6;
337             }
338         },
339         /* protected_peer_ids */ {0, 1, 5, 6},
340         /* unprotected_peer_ids */ {2, 3, 4, 7},
341         random_context));
342 
343     // Combined test: expect having 4 localhost, 2 I2P, and 2 onion peers out of
344     // 16 to protect 1 localhost, 2 I2P, and 1 onion (4/16 total), plus 4 others
345     // for 8 total, sorted by longest uptime.
346     BOOST_CHECK(IsProtected(
347         16, [](NodeEvictionCandidate& c) {
348             c.nTimeConnected = c.id;
349             c.m_is_local = (c.id == 6 || c.id > 11);
350             if (c.id == 7 || c.id == 11) {
351                 c.m_network = NET_I2P;
352             } else if (c.id == 9 || c.id == 10) {
353                 c.m_network = NET_ONION;
354             } else {
355                 c.m_network = NET_IPV4;
356             }
357         },
358         /* protected_peer_ids */ {0, 1, 2, 3, 6, 7, 9, 11},
359         /* unprotected_peer_ids */ {4, 5, 8, 10, 12, 13, 14, 15},
360         random_context));
361 
362     // Combined test: expect having 1 localhost, 8 I2P and 1 onion peer out of
363     // 24 to protect 1, 4, and 1 (6 total), plus 6 others for 12/24 total,
364     // sorted by longest uptime.
365     BOOST_CHECK(IsProtected(
366         24, [](NodeEvictionCandidate& c) {
367             c.nTimeConnected = c.id;
368             c.m_is_local = (c.id == 12);
369             if (c.id > 14 && c.id < 23) { // 4 protected instead of usual 2
370                 c.m_network = NET_I2P;
371             } else if (c.id == 23) {
372                 c.m_network = NET_ONION;
373             } else {
374                 c.m_network = NET_IPV6;
375             }
376         },
377         /* protected_peer_ids */ {0, 1, 2, 3, 4, 5, 12, 15, 16, 17, 18, 23},
378         /* unprotected_peer_ids */ {6, 7, 8, 9, 10, 11, 13, 14, 19, 20, 21, 22},
379         random_context));
380 
381     // Combined test: expect having 1 localhost, 3 I2P and 6 onion peers out of
382     // 24 to protect 1, 3, and 2 (6 total, I2P has fewer candidates and so gets the
383     // unused localhost slot), plus 6 others for 12/24 total, sorted by longest uptime.
384     BOOST_CHECK(IsProtected(
385         24, [](NodeEvictionCandidate& c) {
386             c.nTimeConnected = c.id;
387             c.m_is_local = (c.id == 15);
388             if (c.id == 12 || c.id == 14 || c.id == 17) {
389                 c.m_network = NET_I2P;
390             } else if (c.id > 17) { // 4 protected instead of usual 2
391                 c.m_network = NET_ONION;
392             } else {
393                 c.m_network = NET_IPV4;
394             }
395         },
396         /* protected_peer_ids */ {0, 1, 2, 3, 4, 5, 12, 14, 15, 17, 18, 19},
397         /* unprotected_peer_ids */ {6, 7, 8, 9, 10, 11, 13, 16, 20, 21, 22, 23},
398         random_context));
399 
400     // Combined test: expect having 1 localhost, 7 I2P and 4 onion peers out of
401     // 24 to protect 1 localhost, 2 I2P, and 3 onions (6 total), plus 6 others
402     // for 12/24 total, sorted by longest uptime.
403     BOOST_CHECK(IsProtected(
404         24, [](NodeEvictionCandidate& c) {
405             c.nTimeConnected = c.id;
406             c.m_is_local = (c.id == 13);
407             if (c.id > 16) {
408                 c.m_network = NET_I2P;
409             } else if (c.id == 12 || c.id == 14 || c.id == 15 || c.id == 16) {
410                 c.m_network = NET_ONION;
411             } else {
412                 c.m_network = NET_IPV6;
413             }
414         },
415         /* protected_peer_ids */ {0, 1, 2, 3, 4, 5, 12, 13, 14, 15, 17, 18},
416         /* unprotected_peer_ids */ {6, 7, 8, 9, 10, 11, 16, 19, 20, 21, 22, 23},
417         random_context));
418 
419     // Combined test: expect having 8 localhost, 4 I2P, and 3 onion peers out of
420     // 24 to protect 2 of each (6 total), plus 6 others for 12/24 total, sorted
421     // by longest uptime.
422     BOOST_CHECK(IsProtected(
423         24, [](NodeEvictionCandidate& c) {
424             c.nTimeConnected = c.id;
425             c.m_is_local = (c.id > 15);
426             if (c.id > 10 && c.id < 15) {
427                 c.m_network = NET_I2P;
428             } else if (c.id > 6 && c.id < 10) {
429                 c.m_network = NET_ONION;
430             } else {
431                 c.m_network = NET_IPV4;
432             }
433         },
434         /* protected_peer_ids */ {0, 1, 2, 3, 4, 5, 7, 8, 11, 12, 16, 17},
435         /* unprotected_peer_ids */ {6, 9, 10, 13, 14, 15, 18, 19, 20, 21, 22, 23},
436         random_context));
437 }
438 
439 // Returns true if any of the node ids in node_ids are selected for eviction.
440 bool IsEvicted(std::vector<NodeEvictionCandidate> candidates, const std::unordered_set<NodeId>& node_ids, FastRandomContext& random_context)
441 {
442     Shuffle(candidates.begin(), candidates.end(), random_context);
443     const std::optional<NodeId> evicted_node_id = SelectNodeToEvict(std::move(candidates));
444     if (!evicted_node_id) {
445         return false;
446     }
447     return node_ids.count(*evicted_node_id);
448 }
449 
450 // Create number_of_nodes random nodes, apply setup function candidate_setup_fn,
451 // apply eviction logic and then return true if any of the node ids in node_ids
452 // are selected for eviction.
453 bool IsEvicted(const int number_of_nodes, std::function<void(NodeEvictionCandidate&)> candidate_setup_fn, const std::unordered_set<NodeId>& node_ids, FastRandomContext& random_context)
454 {
455     std::vector<NodeEvictionCandidate> candidates = GetRandomNodeEvictionCandidates(number_of_nodes, random_context);
456     for (NodeEvictionCandidate& candidate : candidates) {
457         candidate_setup_fn(candidate);
458     }
459     return IsEvicted(candidates, node_ids, random_context);
460 }
461 
462 BOOST_AUTO_TEST_CASE(peer_eviction_test)
463 {
464     FastRandomContext random_context{true};
465 
466     for (int number_of_nodes = 0; number_of_nodes < 200; ++number_of_nodes) {
467         // Four nodes with the highest keyed netgroup values should be
468         // protected from eviction.
469         BOOST_CHECK(!IsEvicted(
470                         number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
471                             candidate.nKeyedNetGroup = number_of_nodes - candidate.id;
472                         },
473                         {0, 1, 2, 3}, random_context));
474 
475         // Eight nodes with the lowest minimum ping time should be protected
476         // from eviction.
477         BOOST_CHECK(!IsEvicted(
478                         number_of_nodes, [](NodeEvictionCandidate& candidate) {
479                             candidate.m_min_ping_time = std::chrono::microseconds{candidate.id};
480                         },
481                         {0, 1, 2, 3, 4, 5, 6, 7}, random_context));
482 
483         // Four nodes that most recently sent us novel transactions accepted
484         // into our mempool should be protected from eviction.
485         BOOST_CHECK(!IsEvicted(
486                         number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
487                             candidate.nLastTXTime = number_of_nodes - candidate.id;
488                         },
489                         {0, 1, 2, 3}, random_context));
490 
491         // Up to eight non-tx-relay peers that most recently sent us novel
492         // blocks should be protected from eviction.
493         BOOST_CHECK(!IsEvicted(
494                         number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
495                             candidate.nLastBlockTime = number_of_nodes - candidate.id;
496                             if (candidate.id <= 7) {
497                                 candidate.fRelayTxes = false;
498                                 candidate.fRelevantServices = true;
499                             }
500                         },
501                         {0, 1, 2, 3, 4, 5, 6, 7}, random_context));
502 
503         // Four peers that most recently sent us novel blocks should be
504         // protected from eviction.
505         BOOST_CHECK(!IsEvicted(
506                         number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
507                             candidate.nLastBlockTime = number_of_nodes - candidate.id;
508                         },
509                         {0, 1, 2, 3}, random_context));
510 
511         // Combination of the previous two tests.
512         BOOST_CHECK(!IsEvicted(
513                         number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
514                             candidate.nLastBlockTime = number_of_nodes - candidate.id;
515                             if (candidate.id <= 7) {
516                                 candidate.fRelayTxes = false;
517                                 candidate.fRelevantServices = true;
518                             }
519                         },
520                         {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, random_context));
521 
522         // Combination of all tests above.
523         BOOST_CHECK(!IsEvicted(
524                         number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
525                             candidate.nKeyedNetGroup = number_of_nodes - candidate.id;           // 4 protected
526                             candidate.m_min_ping_time = std::chrono::microseconds{candidate.id}; // 8 protected
527                             candidate.nLastTXTime = number_of_nodes - candidate.id;              // 4 protected
528                             candidate.nLastBlockTime = number_of_nodes - candidate.id;           // 4 protected
529                         },
530                         {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}, random_context));
531 
532         // An eviction is expected given >= 29 random eviction candidates. The eviction logic protects at most
533         // four peers by net group, eight by lowest ping time, four by last time of novel tx, up to eight non-tx-relay
534         // peers by last novel block time, and four more peers by last novel block time.
535         if (number_of_nodes >= 29) {
536             BOOST_CHECK(SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context)));
537         }
538 
539         // No eviction is expected given <= 20 random eviction candidates. The eviction logic protects at least
540         // four peers by net group, eight by lowest ping time, four by last time of novel tx and four peers by last
541         // novel block time.
542         if (number_of_nodes <= 20) {
543             BOOST_CHECK(!SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context)));
544         }
545 
546         // Cases left to test:
547         // * "If any remaining peers are preferred for eviction consider only them. [...]"
548         // * "Identify the network group with the most connections and youngest member. [...]"
549     }
550 }
551 
552 BOOST_AUTO_TEST_SUITE_END()
553