Branch data Line data Source code
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>
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 : 0 :
18 : 0 : 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 : 0 : 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 : 0 : std::vector<NodeEvictionCandidate> candidates{GetRandomNodeEvictionCandidates(num_peers, random_context)};
31 : 0 : for (NodeEvictionCandidate& candidate : candidates) {
32 : 0 : candidate_setup_fn(candidate);
33 : : }
34 : 0 : Shuffle(candidates.begin(), candidates.end(), random_context);
35 : :
36 : 0 : const size_t size{candidates.size()};
37 : 0 : const size_t expected{size - size / 2}; // Expect half the candidates will be protected.
38 : 0 : ProtectEvictionCandidatesByRatio(candidates);
39 : 0 : BOOST_CHECK_EQUAL(candidates.size(), expected);
40 : :
41 : 0 : size_t unprotected_count{0};
42 : 0 : for (const NodeEvictionCandidate& candidate : candidates) {
43 : 0 : 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 : 0 : if (unprotected_peer_ids.count(candidate.id)) {
49 : : // this peer remains in the eviction candidates, as expected
50 : 0 : ++unprotected_count;
51 : 0 : }
52 : : }
53 : :
54 : 0 : const bool is_protected{unprotected_count == unprotected_peer_ids.size()};
55 : 0 : if (!is_protected) {
56 : 0 : BOOST_TEST_MESSAGE(strprintf("unprotected: expected %d, actual %d",
57 : : unprotected_peer_ids.size(), unprotected_count));
58 : 0 : }
59 : 0 : return is_protected;
60 : 0 : }
61 : :
62 : 0 : BOOST_AUTO_TEST_CASE(peer_protection_test)
63 : : {
64 : 0 : FastRandomContext random_context{true};
65 : 0 : 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 : 0 : 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 : 0 : },
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 : 0 : BOOST_CHECK(IsProtected(
81 : : num_peers, [num_peers](NodeEvictionCandidate& c) {
82 : : c.m_connected = std::chrono::seconds{num_peers - c.id};
83 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : // uptime; stable sort breaks ties with the array order of localhost first.
228 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : }
571 : :
572 : : // Returns true if any of the node ids in node_ids are selected for eviction.
573 : 0 : bool IsEvicted(std::vector<NodeEvictionCandidate> candidates, const std::unordered_set<NodeId>& node_ids, FastRandomContext& random_context)
574 : : {
575 : 0 : Shuffle(candidates.begin(), candidates.end(), random_context);
576 : 0 : const std::optional<NodeId> evicted_node_id = SelectNodeToEvict(std::move(candidates));
577 : 0 : if (!evicted_node_id) {
578 : 0 : return false;
579 : : }
580 : 0 : return node_ids.count(*evicted_node_id);
581 : 0 : }
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 : 0 : 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 : 0 : std::vector<NodeEvictionCandidate> candidates = GetRandomNodeEvictionCandidates(number_of_nodes, random_context);
589 : 0 : for (NodeEvictionCandidate& candidate : candidates) {
590 : 0 : candidate_setup_fn(candidate);
591 : : }
592 : 0 : return IsEvicted(candidates, node_ids, random_context);
593 : 0 : }
594 : :
595 : 0 : BOOST_AUTO_TEST_CASE(peer_eviction_test)
596 : : {
597 : 0 : FastRandomContext random_context{true};
598 : :
599 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : 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 : 0 : if (number_of_nodes >= 29) {
669 : 0 : BOOST_CHECK(SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context)));
670 : 0 : }
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 : 0 : if (number_of_nodes <= 20) {
676 : 0 : BOOST_CHECK(!SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context)));
677 : 0 : }
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 : 0 : }
683 : 0 : }
684 : :
685 : 0 : BOOST_AUTO_TEST_SUITE_END()
|