Bitcoin Core  24.99.0
P2P Digital Currency
net_peer_eviction_tests.cpp
Go to the documentation of this file.
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>
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.
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 
62 BOOST_AUTO_TEST_CASE(peer_protection_test)
63 {
64  FastRandomContext random_context{true};
65  int num_peers{12};
66 
67  // Expect half of the peers with greatest uptime (the lowest m_connected)
68  // to be protected from eviction.
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.
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.
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);
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.
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.
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.
573 bool IsEvicted(std::vector<NodeEvictionCandidate> candidates, const std::unordered_set<NodeId>& node_ids, FastRandomContext& random_context)
574 {
575  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.
586 bool IsEvicted(const int number_of_nodes, std::function<void(NodeEvictionCandidate&)> candidate_setup_fn, const std::unordered_set<NodeId>& node_ids, FastRandomContext& random_context)
587 {
588  std::vector<NodeEvictionCandidate> candidates = GetRandomNodeEvictionCandidates(number_of_nodes, random_context);
589  for (NodeEvictionCandidate& candidate : candidates) {
590  candidate_setup_fn(candidate);
591  }
592  return IsEvicted(candidates, node_ids, random_context);
593 }
594 
595 BOOST_AUTO_TEST_CASE(peer_eviction_test)
596 {
597  FastRandomContext random_context{true};
598 
599  for (int number_of_nodes = 0; number_of_nodes < 200; ++number_of_nodes) {
600  // Four nodes with the highest keyed netgroup values should be
601  // protected from eviction.
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:143
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:92
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:58
@ NET_CJDNS
CJDNS.
Definition: netaddress.h:61
@ NET_ONION
TOR (v2 or v3)
Definition: netaddress.h:55
@ NET_IPV6
IPv6.
Definition: netaddress.h:52
@ NET_IPV4
IPv4.
Definition: netaddress.h:49
#define BOOST_CHECK_EQUAL(v1, v2)
Definition: object.cpp:17
#define BOOST_CHECK(expr)
Definition: object.cpp:16
void Shuffle(I first, I last, R &&rng)
More efficient than using std::shuffle on a FastRandomContext.
Definition: random.h:271
Basic testing setup.
Definition: setup_common.h:83
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:96
#define strprintf
Format arguments and return the string or write to given std::ostream (see tinyformat::format doc for...
Definition: tinyformat.h:1164