LCOV - code coverage report
Current view: top level - src/test - net_peer_eviction_tests.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 88 92 95.7 %
Date: 2026-06-25 07:23:43 Functions: 59 59 100.0 %

          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()

Generated by: LCOV version 1.16