LCOV - code coverage report
Current view: top level - src/test - net_peer_eviction_tests.cpp (source / functions) Hit Total Coverage
Test: coverage.lcov Lines: 475 481 98.8 %
Date: 2022-04-21 14:51:19 Functions: 5 5 100.0 %
Legend: Modified by patch:
Lines: hit not hit | Branches: + taken - not taken # not executed

Not modified by patch:
Lines: hit not hit | Branches: + taken - not taken # not executed
Branches: 18 20 90.0 %

           Branch data     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                 :            : 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                 :         64 : {
#      30                 :         64 :     std::vector<NodeEvictionCandidate> candidates{GetRandomNodeEvictionCandidates(num_peers, random_context)};
#      31         [ +  + ]:        828 :     for (NodeEvictionCandidate& candidate : candidates) {
#      32                 :        828 :         candidate_setup_fn(candidate);
#      33                 :        828 :     }
#      34                 :         64 :     Shuffle(candidates.begin(), candidates.end(), random_context);
#      35                 :            : 
#      36                 :         64 :     const size_t size{candidates.size()};
#      37                 :         64 :     const size_t expected{size - size / 2}; // Expect half the candidates will be protected.
#      38                 :         64 :     ProtectEvictionCandidatesByRatio(candidates);
#      39                 :         64 :     BOOST_CHECK_EQUAL(candidates.size(), expected);
#      40                 :            : 
#      41                 :         64 :     size_t unprotected_count{0};
#      42         [ +  + ]:        418 :     for (const NodeEvictionCandidate& candidate : candidates) {
#      43         [ -  + ]:        418 :         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                 :          0 :         }
#      48         [ +  + ]:        418 :         if (unprotected_peer_ids.count(candidate.id)) {
#      49                 :            :             // this peer remains in the eviction candidates, as expected
#      50                 :        368 :             ++unprotected_count;
#      51                 :        368 :         }
#      52                 :        418 :     }
#      53                 :            : 
#      54                 :         64 :     const bool is_protected{unprotected_count == unprotected_peer_ids.size()};
#      55         [ -  + ]:         64 :     if (!is_protected) {
#      56                 :          0 :         BOOST_TEST_MESSAGE(strprintf("unprotected: expected %d, actual %d",
#      57                 :          0 :                                      unprotected_peer_ids.size(), unprotected_count));
#      58                 :          0 :     }
#      59                 :         64 :     return is_protected;
#      60                 :         64 : }
#      61                 :            : 
#      62                 :            : BOOST_AUTO_TEST_CASE(peer_protection_test)
#      63                 :          2 : {
#      64                 :          2 :     FastRandomContext random_context{true};
#      65                 :          2 :     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                 :          2 :     BOOST_CHECK(IsProtected(
#      70                 :          2 :         num_peers, [](NodeEvictionCandidate& c) {
#      71                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#      72                 :          2 :             c.m_is_local = false;
#      73                 :          2 :             c.m_network = NET_IPV4;
#      74                 :          2 :         },
#      75                 :          2 :         /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5},
#      76                 :          2 :         /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11},
#      77                 :          2 :         random_context));
#      78                 :            : 
#      79                 :            :     // Verify in the opposite direction.
#      80                 :          2 :     BOOST_CHECK(IsProtected(
#      81                 :          2 :         num_peers, [num_peers](NodeEvictionCandidate& c) {
#      82                 :          2 :             c.m_connected = std::chrono::seconds{num_peers - c.id};
#      83                 :          2 :             c.m_is_local = false;
#      84                 :          2 :             c.m_network = NET_IPV6;
#      85                 :          2 :         },
#      86                 :          2 :         /*protected_peer_ids=*/{6, 7, 8, 9, 10, 11},
#      87                 :          2 :         /*unprotected_peer_ids=*/{0, 1, 2, 3, 4, 5},
#      88                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#      95                 :          2 :         num_peers, [](NodeEvictionCandidate& c) {
#      96                 :          2 :             c.m_is_local = false;
#      97                 :          2 :             c.m_network = (c.id == 3 || c.id == 8 || c.id == 9) ? NET_ONION : NET_IPV4;
#      98                 :          2 :         },
#      99                 :          2 :         /*protected_peer_ids=*/{3, 8, 9},
#     100                 :          2 :         /*unprotected_peer_ids=*/{},
#     101                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#     106                 :          2 :         num_peers, [](NodeEvictionCandidate& c) {
#     107                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#     108                 :          2 :             c.m_is_local = false;
#     109                 :          2 :             c.m_network = (c.id == 3 || c.id > 7) ? NET_ONION : NET_IPV6;
#     110                 :          2 :         },
#     111                 :          2 :         /*protected_peer_ids=*/{0, 1, 2, 3, 8, 9},
#     112                 :          2 :         /*unprotected_peer_ids=*/{4, 5, 6, 7, 10, 11},
#     113                 :          2 :         random_context));
#     114                 :            : 
#     115                 :            :     // Expect 1/4 localhost peers to be protected from eviction,
#     116                 :            :     // if no onion, I2P, or CJDNS peers.
#     117                 :          2 :     BOOST_CHECK(IsProtected(
#     118                 :          2 :         num_peers, [](NodeEvictionCandidate& c) {
#     119                 :          2 :             c.m_is_local = (c.id == 1 || c.id == 9 || c.id == 11);
#     120                 :          2 :             c.m_network = NET_IPV4;
#     121                 :          2 :         },
#     122                 :          2 :         /*protected_peer_ids=*/{1, 9, 11},
#     123                 :          2 :         /*unprotected_peer_ids=*/{},
#     124                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#     129                 :          2 :         num_peers, [](NodeEvictionCandidate& c) {
#     130                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#     131                 :          2 :             c.m_is_local = (c.id > 6);
#     132                 :          2 :             c.m_network = NET_IPV6;
#     133                 :          2 :         },
#     134                 :          2 :         /*protected_peer_ids=*/{0, 1, 2, 7, 8, 9},
#     135                 :          2 :         /*unprotected_peer_ids=*/{3, 4, 5, 6, 10, 11},
#     136                 :          2 :         random_context));
#     137                 :            : 
#     138                 :            :     // Expect 1/4 I2P peers to be protected from eviction,
#     139                 :            :     // if no onion, localhost, or CJDNS peers.
#     140                 :          2 :     BOOST_CHECK(IsProtected(
#     141                 :          2 :         num_peers, [](NodeEvictionCandidate& c) {
#     142                 :          2 :             c.m_is_local = false;
#     143                 :          2 :             c.m_network = (c.id == 2 || c.id == 7 || c.id == 10) ? NET_I2P : NET_IPV4;
#     144                 :          2 :         },
#     145                 :          2 :         /*protected_peer_ids=*/{2, 7, 10},
#     146                 :          2 :         /*unprotected_peer_ids=*/{},
#     147                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#     152                 :          2 :         num_peers, [](NodeEvictionCandidate& c) {
#     153                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#     154                 :          2 :             c.m_is_local = false;
#     155                 :          2 :             c.m_network = (c.id == 4 || c.id > 8) ? NET_I2P : NET_IPV6;
#     156                 :          2 :         },
#     157                 :          2 :         /*protected_peer_ids=*/{0, 1, 2, 4, 9, 10},
#     158                 :          2 :         /*unprotected_peer_ids=*/{3, 5, 6, 7, 8, 11},
#     159                 :          2 :         random_context));
#     160                 :            : 
#     161                 :            :     // Expect 1/4 CJDNS peers to be protected from eviction,
#     162                 :            :     // if no onion, localhost, or I2P peers.
#     163                 :          2 :     BOOST_CHECK(IsProtected(
#     164                 :          2 :         num_peers, [](NodeEvictionCandidate& c) {
#     165                 :          2 :             c.m_is_local = false;
#     166                 :          2 :             c.m_network = (c.id == 2 || c.id == 7 || c.id == 10) ? NET_CJDNS : NET_IPV4;
#     167                 :          2 :         },
#     168                 :          2 :         /*protected_peer_ids=*/{2, 7, 10},
#     169                 :          2 :         /*unprotected_peer_ids=*/{},
#     170                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#     175                 :          2 :         num_peers, [](NodeEvictionCandidate& c) {
#     176                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#     177                 :          2 :             c.m_is_local = false;
#     178                 :          2 :             c.m_network = (c.id == 4 || c.id > 8) ? NET_CJDNS : NET_IPV6;
#     179                 :          2 :         },
#     180                 :          2 :         /*protected_peer_ids=*/{0, 1, 2, 4, 9, 10},
#     181                 :          2 :         /*unprotected_peer_ids=*/{3, 5, 6, 7, 8, 11},
#     182                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#     190                 :          2 :         4, [](NodeEvictionCandidate& c) {
#     191                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#     192                 :          2 :             c.m_is_local = (c.id == 4);
#     193                 :          2 :             c.m_network = (c.id == 3) ? NET_ONION : NET_IPV4;
#     194                 :          2 :         },
#     195                 :          2 :         /*protected_peer_ids=*/{0, 4},
#     196                 :          2 :         /*unprotected_peer_ids=*/{1, 2},
#     197                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#     203                 :          2 :         7, [](NodeEvictionCandidate& c) {
#     204                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#     205                 :          2 :             c.m_is_local = (c.id == 6);
#     206                 :          2 :             c.m_network = (c.id == 5) ? NET_ONION : NET_IPV4;
#     207                 :          2 :         },
#     208                 :          2 :         /*protected_peer_ids=*/{0, 1, 6},
#     209                 :          2 :         /*unprotected_peer_ids=*/{2, 3, 4, 5},
#     210                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#     216                 :          2 :         8, [](NodeEvictionCandidate& c) {
#     217                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#     218                 :          2 :             c.m_is_local = (c.id == 6);
#     219                 :          2 :             c.m_network = (c.id == 5) ? NET_ONION : NET_IPV4;
#     220                 :          2 :         },
#     221                 :          2 :         /*protected_peer_ids=*/{0, 1, 5, 6},
#     222                 :          2 :         /*unprotected_peer_ids=*/{2, 3, 4, 7},
#     223                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#     229                 :          2 :         num_peers, [](NodeEvictionCandidate& c) {
#     230                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#     231                 :          2 :             c.m_is_local = (c.id == 6 || c.id == 9 || c.id == 11);
#     232                 :          2 :             c.m_network = (c.id == 7 || c.id == 8 || c.id == 10) ? NET_ONION : NET_IPV6;
#     233                 :          2 :         },
#     234                 :          2 :         /*protected_peer_ids=*/{0, 1, 2, 6, 7, 9},
#     235                 :          2 :         /*unprotected_peer_ids=*/{3, 4, 5, 8, 10, 11},
#     236                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#     241                 :          2 :         num_peers, [](NodeEvictionCandidate& c) {
#     242                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#     243                 :          2 :             c.m_is_local = (c.id > 4 && c.id < 9);
#     244                 :          2 :             c.m_network = (c.id == 10) ? NET_ONION : NET_IPV4;
#     245                 :          2 :         },
#     246                 :          2 :         /*protected_peer_ids=*/{0, 1, 2, 5, 6, 10},
#     247                 :          2 :         /*unprotected_peer_ids=*/{3, 4, 7, 8, 9, 11},
#     248                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#     253                 :          2 :         16, [](NodeEvictionCandidate& c) {
#     254                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#     255                 :          2 :             c.m_is_local = (c.id == 6 || c.id == 9 || c.id == 11 || c.id == 12);
#     256                 :          2 :             c.m_network = (c.id == 8 || c.id == 10) ? NET_ONION : NET_IPV6;
#     257                 :          2 :         },
#     258                 :          2 :         /*protected_peer_ids=*/{0, 1, 2, 3, 6, 8, 9, 10},
#     259                 :          2 :         /*unprotected_peer_ids=*/{4, 5, 7, 11, 12, 13, 14, 15},
#     260                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#     266                 :          2 :         16, [](NodeEvictionCandidate& c) {
#     267                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#     268                 :          2 :             c.m_is_local = (c.id > 10);
#     269                 :          2 :             c.m_network = (c.id == 10) ? NET_ONION : NET_IPV4;
#     270                 :          2 :         },
#     271                 :          2 :         /*protected_peer_ids=*/{0, 1, 2, 3, 10, 11, 12, 13},
#     272                 :          2 :         /*unprotected_peer_ids=*/{4, 5, 6, 7, 8, 9, 14, 15},
#     273                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#     279                 :          2 :         16, [](NodeEvictionCandidate& c) {
#     280                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#     281                 :          2 :             c.m_is_local = (c.id == 15);
#     282                 :          2 :             c.m_network = (c.id > 6 && c.id < 11) ? NET_ONION : NET_IPV6;
#     283                 :          2 :         },
#     284                 :          2 :         /*protected_peer_ids=*/{0, 1, 2, 3, 7, 8, 9, 15},
#     285                 :          2 :         /*unprotected_peer_ids=*/{5, 6, 10, 11, 12, 13, 14},
#     286                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#     292                 :          2 :         num_peers, [](NodeEvictionCandidate& c) {
#     293                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#     294                 :          2 :             c.m_is_local = false;
#     295                 :          2 :             if (c.id == 8 || c.id == 10) {
#     296                 :          2 :                 c.m_network = NET_ONION;
#     297                 :          2 :             } else if (c.id == 6 || c.id == 9 || c.id == 11 || c.id == 12) {
#     298                 :          2 :                 c.m_network = NET_I2P;
#     299                 :          2 :             } else {
#     300                 :          2 :                 c.m_network = NET_IPV4;
#     301                 :          2 :             }
#     302                 :          2 :         },
#     303                 :          2 :         /*protected_peer_ids=*/{0, 1, 2, 6, 8, 10},
#     304                 :          2 :         /*unprotected_peer_ids=*/{3, 4, 5, 7, 9, 11},
#     305                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#     313                 :          2 :         4, [](NodeEvictionCandidate& c) {
#     314                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#     315                 :          2 :             c.m_is_local = (c.id == 2);
#     316                 :          2 :             if (c.id == 3) {
#     317                 :          2 :                 c.m_network = NET_I2P;
#     318                 :          2 :             } else if (c.id == 1) {
#     319                 :          2 :                 c.m_network = NET_ONION;
#     320                 :          2 :             } else {
#     321                 :          2 :                 c.m_network = NET_IPV6;
#     322                 :          2 :             }
#     323                 :          2 :         },
#     324                 :          2 :         /*protected_peer_ids=*/{0, 3},
#     325                 :          2 :         /*unprotected_peer_ids=*/{1, 2},
#     326                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#     332                 :          2 :         7, [](NodeEvictionCandidate& c) {
#     333                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#     334                 :          2 :             c.m_is_local = (c.id == 4);
#     335                 :          2 :             if (c.id == 6) {
#     336                 :          2 :                 c.m_network = NET_I2P;
#     337                 :          2 :             } else if (c.id == 5) {
#     338                 :          2 :                 c.m_network = NET_ONION;
#     339                 :          2 :             } else {
#     340                 :          2 :                 c.m_network = NET_IPV6;
#     341                 :          2 :             }
#     342                 :          2 :         },
#     343                 :          2 :         /*protected_peer_ids=*/{0, 1, 6},
#     344                 :          2 :         /*unprotected_peer_ids=*/{2, 3, 4, 5},
#     345                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#     351                 :          2 :         8, [](NodeEvictionCandidate& c) {
#     352                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#     353                 :          2 :             c.m_is_local = (c.id == 6);
#     354                 :          2 :             if (c.id == 5) {
#     355                 :          2 :                 c.m_network = NET_I2P;
#     356                 :          2 :             } else if (c.id == 4) {
#     357                 :          2 :                 c.m_network = NET_ONION;
#     358                 :          2 :             } else {
#     359                 :          2 :                 c.m_network = NET_IPV6;
#     360                 :          2 :             }
#     361                 :          2 :         },
#     362                 :          2 :         /*protected_peer_ids=*/{0, 1, 5, 6},
#     363                 :          2 :         /*unprotected_peer_ids=*/{2, 3, 4, 7},
#     364                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#     370                 :          2 :         16, [](NodeEvictionCandidate& c) {
#     371                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#     372                 :          2 :             c.m_is_local = (c.id == 6 || c.id > 11);
#     373                 :          2 :             if (c.id == 7 || c.id == 11) {
#     374                 :          2 :                 c.m_network = NET_I2P;
#     375                 :          2 :             } else if (c.id == 9 || c.id == 10) {
#     376                 :          2 :                 c.m_network = NET_ONION;
#     377                 :          2 :             } else {
#     378                 :          2 :                 c.m_network = NET_IPV4;
#     379                 :          2 :             }
#     380                 :          2 :         },
#     381                 :          2 :         /*protected_peer_ids=*/{0, 1, 2, 3, 6, 7, 9, 11},
#     382                 :          2 :         /*unprotected_peer_ids=*/{4, 5, 8, 10, 12, 13, 14, 15},
#     383                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#     389                 :          2 :         24, [](NodeEvictionCandidate& c) {
#     390                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#     391                 :          2 :             c.m_is_local = (c.id == 12);
#     392                 :          2 :             if (c.id > 14 && c.id < 23) { // 4 protected instead of usual 2
#     393                 :          2 :                 c.m_network = NET_I2P;
#     394                 :          2 :             } else if (c.id == 23) {
#     395                 :          2 :                 c.m_network = NET_ONION;
#     396                 :          2 :             } else {
#     397                 :          2 :                 c.m_network = NET_IPV6;
#     398                 :          2 :             }
#     399                 :          2 :         },
#     400                 :          2 :         /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5, 12, 15, 16, 17, 18, 23},
#     401                 :          2 :         /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11, 13, 14, 19, 20, 21, 22},
#     402                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#     408                 :          2 :         24, [](NodeEvictionCandidate& c) {
#     409                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#     410                 :          2 :             c.m_is_local = (c.id == 15);
#     411                 :          2 :             if (c.id == 12 || c.id == 14 || c.id == 17) {
#     412                 :          2 :                 c.m_network = NET_I2P;
#     413                 :          2 :             } else if (c.id > 17) { // 4 protected instead of usual 2
#     414                 :          2 :                 c.m_network = NET_ONION;
#     415                 :          2 :             } else {
#     416                 :          2 :                 c.m_network = NET_IPV4;
#     417                 :          2 :             }
#     418                 :          2 :         },
#     419                 :          2 :         /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5, 12, 14, 15, 17, 18, 19},
#     420                 :          2 :         /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11, 13, 16, 20, 21, 22, 23},
#     421                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#     427                 :          2 :         24, [](NodeEvictionCandidate& c) {
#     428                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#     429                 :          2 :             c.m_is_local = (c.id == 13);
#     430                 :          2 :             if (c.id > 16) {
#     431                 :          2 :                 c.m_network = NET_I2P;
#     432                 :          2 :             } else if (c.id == 12 || c.id == 14 || c.id == 15 || c.id == 16) {
#     433                 :          2 :                 c.m_network = NET_ONION;
#     434                 :          2 :             } else {
#     435                 :          2 :                 c.m_network = NET_IPV6;
#     436                 :          2 :             }
#     437                 :          2 :         },
#     438                 :          2 :         /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5, 12, 13, 14, 15, 17, 18},
#     439                 :          2 :         /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11, 16, 19, 20, 21, 22, 23},
#     440                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#     446                 :          2 :         24, [](NodeEvictionCandidate& c) {
#     447                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#     448                 :          2 :             c.m_is_local = (c.id > 15);
#     449                 :          2 :             if (c.id > 10 && c.id < 15) {
#     450                 :          2 :                 c.m_network = NET_CJDNS;
#     451                 :          2 :             } else if (c.id > 6 && c.id < 10) {
#     452                 :          2 :                 c.m_network = NET_ONION;
#     453                 :          2 :             } else {
#     454                 :          2 :                 c.m_network = NET_IPV4;
#     455                 :          2 :             }
#     456                 :          2 :         },
#     457                 :          2 :         /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5, 7, 8, 11, 12, 16, 17},
#     458                 :          2 :         /*unprotected_peer_ids=*/{6, 9, 10, 13, 14, 15, 18, 19, 20, 21, 22, 23},
#     459                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#     468                 :          2 :         5, [](NodeEvictionCandidate& c) {
#     469                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#     470                 :          2 :             c.m_is_local = (c.id == 3);
#     471                 :          2 :             if (c.id == 4) {
#     472                 :          2 :                 c.m_network = NET_CJDNS;
#     473                 :          2 :             } else if (c.id == 1) {
#     474                 :          2 :                 c.m_network = NET_I2P;
#     475                 :          2 :             } else if (c.id == 2) {
#     476                 :          2 :                 c.m_network = NET_ONION;
#     477                 :          2 :             } else {
#     478                 :          2 :                 c.m_network = NET_IPV6;
#     479                 :          2 :             }
#     480                 :          2 :         },
#     481                 :          2 :         /*protected_peer_ids=*/{0, 4},
#     482                 :          2 :         /*unprotected_peer_ids=*/{1, 2, 3},
#     483                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#     490                 :          2 :         7, [](NodeEvictionCandidate& c) {
#     491                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#     492                 :          2 :             c.m_is_local = (c.id == 4);
#     493                 :          2 :             if (c.id == 6) {
#     494                 :          2 :                 c.m_network = NET_CJDNS;
#     495                 :          2 :             } else if (c.id == 5) {
#     496                 :          2 :                 c.m_network = NET_I2P;
#     497                 :          2 :             } else if (c.id == 3) {
#     498                 :          2 :                 c.m_network = NET_ONION;
#     499                 :          2 :             } else {
#     500                 :          2 :                 c.m_network = NET_IPV4;
#     501                 :          2 :             }
#     502                 :          2 :         },
#     503                 :          2 :         /*protected_peer_ids=*/{0, 1, 6},
#     504                 :          2 :         /*unprotected_peer_ids=*/{2, 3, 4, 5},
#     505                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#     512                 :          2 :         8, [](NodeEvictionCandidate& c) {
#     513                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#     514                 :          2 :             c.m_is_local = (c.id == 3);
#     515                 :          2 :             if (c.id == 5) {
#     516                 :          2 :                 c.m_network = NET_CJDNS;
#     517                 :          2 :             } else if (c.id == 6) {
#     518                 :          2 :                 c.m_network = NET_I2P;
#     519                 :          2 :             } else if (c.id == 3) {
#     520                 :          2 :                 c.m_network = NET_ONION;
#     521                 :          2 :             } else {
#     522                 :          2 :                 c.m_network = NET_IPV6;
#     523                 :          2 :             }
#     524                 :          2 :         },
#     525                 :          2 :         /*protected_peer_ids=*/{0, 1, 5, 6},
#     526                 :          2 :         /*unprotected_peer_ids=*/{2, 3, 4, 7},
#     527                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#     533                 :          2 :         16, [](NodeEvictionCandidate& c) {
#     534                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#     535                 :          2 :             c.m_is_local = (c.id > 5);
#     536                 :          2 :             if (c.id == 11 || c.id == 15) {
#     537                 :          2 :                 c.m_network = NET_CJDNS;
#     538                 :          2 :             } else if (c.id == 10 || c.id == 14) {
#     539                 :          2 :                 c.m_network = NET_I2P;
#     540                 :          2 :             } else if (c.id == 8 || c.id == 9) {
#     541                 :          2 :                 c.m_network = NET_ONION;
#     542                 :          2 :             } else {
#     543                 :          2 :                 c.m_network = NET_IPV4;
#     544                 :          2 :             }
#     545                 :          2 :         },
#     546                 :          2 :         /*protected_peer_ids=*/{0, 1, 2, 3, 6, 8, 10, 11},
#     547                 :          2 :         /*unprotected_peer_ids=*/{4, 5, 7, 9, 12, 13, 14, 15},
#     548                 :          2 :         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                 :          2 :     BOOST_CHECK(IsProtected(
#     554                 :          2 :         24, [](NodeEvictionCandidate& c) {
#     555                 :          2 :             c.m_connected = std::chrono::seconds{c.id};
#     556                 :          2 :             c.m_is_local = (c.id == 13);
#     557                 :          2 :             if (c.id > 17) {
#     558                 :          2 :                 c.m_network = NET_CJDNS;
#     559                 :          2 :             } else if (c.id == 17) {
#     560                 :          2 :                 c.m_network = NET_I2P;
#     561                 :          2 :             } else if (c.id == 12 || c.id == 14 || c.id == 15 || c.id == 16) {
#     562                 :          2 :                 c.m_network = NET_ONION;
#     563                 :          2 :             } else {
#     564                 :          2 :                 c.m_network = NET_IPV6;
#     565                 :          2 :             }
#     566                 :          2 :         },
#     567                 :          2 :         /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5, 12, 13, 14, 17, 18, 19},
#     568                 :          2 :         /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11, 15, 16, 20, 21, 22, 23},
#     569                 :          2 :         random_context));
#     570                 :          2 : }
#     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                 :       2800 : {
#     575                 :       2800 :     Shuffle(candidates.begin(), candidates.end(), random_context);
#     576                 :       2800 :     const std::optional<NodeId> evicted_node_id = SelectNodeToEvict(std::move(candidates));
#     577         [ +  + ]:       2800 :     if (!evicted_node_id) {
#     578                 :        322 :         return false;
#     579                 :        322 :     }
#     580                 :       2478 :     return node_ids.count(*evicted_node_id);
#     581                 :       2800 : }
#     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                 :       2800 : {
#     588                 :       2800 :     std::vector<NodeEvictionCandidate> candidates = GetRandomNodeEvictionCandidates(number_of_nodes, random_context);
#     589         [ +  + ]:     278600 :     for (NodeEvictionCandidate& candidate : candidates) {
#     590                 :     278600 :         candidate_setup_fn(candidate);
#     591                 :     278600 :     }
#     592                 :       2800 :     return IsEvicted(candidates, node_ids, random_context);
#     593                 :       2800 : }
#     594                 :            : 
#     595                 :            : BOOST_AUTO_TEST_CASE(peer_eviction_test)
#     596                 :          2 : {
#     597                 :          2 :     FastRandomContext random_context{true};
#     598                 :            : 
#     599         [ +  + ]:        402 :     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                 :        400 :         BOOST_CHECK(!IsEvicted(
#     603                 :        400 :                         number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
#     604                 :        400 :                             candidate.nKeyedNetGroup = number_of_nodes - candidate.id;
#     605                 :        400 :                         },
#     606                 :        400 :                         {0, 1, 2, 3}, random_context));
#     607                 :            : 
#     608                 :            :         // Eight nodes with the lowest minimum ping time should be protected
#     609                 :            :         // from eviction.
#     610                 :        400 :         BOOST_CHECK(!IsEvicted(
#     611                 :        400 :                         number_of_nodes, [](NodeEvictionCandidate& candidate) {
#     612                 :        400 :                             candidate.m_min_ping_time = std::chrono::microseconds{candidate.id};
#     613                 :        400 :                         },
#     614                 :        400 :                         {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                 :        400 :         BOOST_CHECK(!IsEvicted(
#     619                 :        400 :                         number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
#     620                 :        400 :                             candidate.m_last_tx_time = std::chrono::seconds{number_of_nodes - candidate.id};
#     621                 :        400 :                         },
#     622                 :        400 :                         {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                 :        400 :         BOOST_CHECK(!IsEvicted(
#     627                 :        400 :                         number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
#     628                 :        400 :                             candidate.m_last_block_time = std::chrono::seconds{number_of_nodes - candidate.id};
#     629                 :        400 :                             if (candidate.id <= 7) {
#     630                 :        400 :                                 candidate.m_relay_txs = false;
#     631                 :        400 :                                 candidate.fRelevantServices = true;
#     632                 :        400 :                             }
#     633                 :        400 :                         },
#     634                 :        400 :                         {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                 :        400 :         BOOST_CHECK(!IsEvicted(
#     639                 :        400 :                         number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
#     640                 :        400 :                             candidate.m_last_block_time = std::chrono::seconds{number_of_nodes - candidate.id};
#     641                 :        400 :                         },
#     642                 :        400 :                         {0, 1, 2, 3}, random_context));
#     643                 :            : 
#     644                 :            :         // Combination of the previous two tests.
#     645                 :        400 :         BOOST_CHECK(!IsEvicted(
#     646                 :        400 :                         number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
#     647                 :        400 :                             candidate.m_last_block_time = std::chrono::seconds{number_of_nodes - candidate.id};
#     648                 :        400 :                             if (candidate.id <= 7) {
#     649                 :        400 :                                 candidate.m_relay_txs = false;
#     650                 :        400 :                                 candidate.fRelevantServices = true;
#     651                 :        400 :                             }
#     652                 :        400 :                         },
#     653                 :        400 :                         {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, random_context));
#     654                 :            : 
#     655                 :            :         // Combination of all tests above.
#     656                 :        400 :         BOOST_CHECK(!IsEvicted(
#     657                 :        400 :                         number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
#     658                 :        400 :                             candidate.nKeyedNetGroup = number_of_nodes - candidate.id;           // 4 protected
#     659                 :        400 :                             candidate.m_min_ping_time = std::chrono::microseconds{candidate.id}; // 8 protected
#     660                 :        400 :                             candidate.m_last_tx_time = std::chrono::seconds{number_of_nodes - candidate.id};    // 4 protected
#     661                 :        400 :                             candidate.m_last_block_time = std::chrono::seconds{number_of_nodes - candidate.id}; // 4 protected
#     662                 :        400 :                         },
#     663                 :        400 :                         {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         [ +  + ]:        400 :         if (number_of_nodes >= 29) {
#     669                 :        342 :             BOOST_CHECK(SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context)));
#     670                 :        342 :         }
#     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         [ +  + ]:        400 :         if (number_of_nodes <= 20) {
#     676                 :         42 :             BOOST_CHECK(!SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context)));
#     677                 :         42 :         }
#     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                 :        400 :     }
#     683                 :          2 : }
#     684                 :            : 
#     685                 :            : BOOST_AUTO_TEST_SUITE_END()

Generated by: LCOV version 0-eol-96201-ge66f56f4af6a