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 146 : 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 32 : 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 32 : std::vector<NodeEvictionCandidate> candidates{GetRandomNodeEvictionCandidates(num_peers, random_context)};
31 446 : for (NodeEvictionCandidate& candidate : candidates) {
32 414 : candidate_setup_fn(candidate);
33 : }
34 32 : Shuffle(candidates.begin(), candidates.end(), random_context);
35 :
36 32 : const size_t size{candidates.size()};
37 32 : const size_t expected{size - size / 2}; // Expect half the candidates will be protected.
38 32 : ProtectEvictionCandidatesByRatio(candidates);
39 32 : BOOST_CHECK_EQUAL(candidates.size(), expected);
40 :
41 32 : size_t unprotected_count{0};
42 241 : for (const NodeEvictionCandidate& candidate : candidates) {
43 209 : 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 : }
48 209 : if (unprotected_peer_ids.count(candidate.id)) {
49 : // this peer remains in the eviction candidates, as expected
50 184 : ++unprotected_count;
51 184 : }
52 : }
53 :
54 32 : const bool is_protected{unprotected_count == unprotected_peer_ids.size()};
55 32 : if (!is_protected) {
56 0 : BOOST_TEST_MESSAGE(strprintf("unprotected: expected %d, actual %d",
57 : unprotected_peer_ids.size(), unprotected_count));
58 0 : }
59 32 : return is_protected;
60 32 : }
61 :
62 149 : BOOST_AUTO_TEST_CASE(peer_protection_test)
63 : {
64 1 : FastRandomContext random_context{true};
65 1 : 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 13 : 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 13 : 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 13 : 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 13 : 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 13 : 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 13 : 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 13 : 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 13 : 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 13 : 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 13 : 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 5 : 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 8 : 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 9 : 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 13 : 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 13 : 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 17 : 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 17 : 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 17 : 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 23 : 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 8 : 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 14 : 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 16 : 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 31 : 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 41 : 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 46 : 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 42 : 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 45 : 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 10 : 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 14 : 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 16 : 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 31 : 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 43 : 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 1 : }
571 :
572 : // Returns true if any of the node ids in node_ids are selected for eviction.
573 1400 : bool IsEvicted(std::vector<NodeEvictionCandidate> candidates, const std::unordered_set<NodeId>& node_ids, FastRandomContext& random_context)
574 : {
575 1400 : Shuffle(candidates.begin(), candidates.end(), random_context);
576 1400 : const std::optional<NodeId> evicted_node_id = SelectNodeToEvict(std::move(candidates));
577 1400 : if (!evicted_node_id) {
578 161 : return false;
579 : }
580 1239 : return node_ids.count(*evicted_node_id);
581 1400 : }
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 1400 : 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 1400 : std::vector<NodeEvictionCandidate> candidates = GetRandomNodeEvictionCandidates(number_of_nodes, random_context);
589 140700 : for (NodeEvictionCandidate& candidate : candidates) {
590 139300 : candidate_setup_fn(candidate);
591 : }
592 1400 : return IsEvicted(candidates, node_ids, random_context);
593 1400 : }
594 :
595 149 : BOOST_AUTO_TEST_CASE(peer_eviction_test)
596 : {
597 1 : FastRandomContext random_context{true};
598 :
599 201 : 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 20100 : 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 20100 : 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 20100 : 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 20100 : 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 20100 : 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 20100 : 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 20100 : 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 200 : if (number_of_nodes >= 29) {
669 171 : BOOST_CHECK(SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context)));
670 171 : }
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 200 : if (number_of_nodes <= 20) {
676 21 : BOOST_CHECK(!SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context)));
677 21 : }
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 200 : }
683 1 : }
684 :
685 146 : BOOST_AUTO_TEST_SUITE_END()
|