LCOV - code coverage report
Current view: top level - src/test - net_peer_eviction_tests.cpp (source / functions) Hit Total Coverage
Test: fuzz_coverage.info Lines: 0 96 0.0 %
Date: 2023-10-05 12:38:51 Functions: 0 59 0.0 %
Branches: 0 0 -

           Branch data     Line data    Source code
       1                 :            : // Copyright (c) 2021-2022 The Bitcoin Core developers
       2                 :            : // Distributed under the MIT software license, see the accompanying
       3                 :            : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4                 :            : 
       5                 :            : #include <netaddress.h>
       6                 :            : #include <net.h>
       7                 :            : #include <test/util/net.h>
       8                 :            : #include <test/util/setup_common.h>
       9                 :            : 
      10                 :            : #include <boost/test/unit_test.hpp>
      11                 :            : 
      12                 :            : #include <algorithm>
      13                 :            : #include <functional>
      14                 :            : #include <optional>
      15                 :            : #include <unordered_set>
      16                 :            : #include <vector>
      17                 :          0 : 
      18                 :          0 : BOOST_FIXTURE_TEST_SUITE(net_peer_eviction_tests, BasicTestingSetup)
      19                 :            : 
      20                 :            : // Create `num_peers` random nodes, apply setup function `candidate_setup_fn`,
      21                 :            : // call ProtectEvictionCandidatesByRatio() to apply protection logic, and then
      22                 :            : // return true if all of `protected_peer_ids` and none of `unprotected_peer_ids`
      23                 :            : // are protected from eviction, i.e. removed from the eviction candidates.
      24                 :          0 : bool IsProtected(int num_peers,
      25                 :            :                  std::function<void(NodeEvictionCandidate&)> candidate_setup_fn,
      26                 :            :                  const std::unordered_set<NodeId>& protected_peer_ids,
      27                 :            :                  const std::unordered_set<NodeId>& unprotected_peer_ids,
      28                 :            :                  FastRandomContext& random_context)
      29                 :            : {
      30                 :          0 :     std::vector<NodeEvictionCandidate> candidates{GetRandomNodeEvictionCandidates(num_peers, random_context)};
      31                 :          0 :     for (NodeEvictionCandidate& candidate : candidates) {
      32                 :          0 :         candidate_setup_fn(candidate);
      33                 :            :     }
      34                 :          0 :     Shuffle(candidates.begin(), candidates.end(), random_context);
      35                 :            : 
      36                 :          0 :     const size_t size{candidates.size()};
      37                 :          0 :     const size_t expected{size - size / 2}; // Expect half the candidates will be protected.
      38                 :          0 :     ProtectEvictionCandidatesByRatio(candidates);
      39                 :          0 :     BOOST_CHECK_EQUAL(candidates.size(), expected);
      40                 :            : 
      41                 :          0 :     size_t unprotected_count{0};
      42                 :          0 :     for (const NodeEvictionCandidate& candidate : candidates) {
      43                 :          0 :         if (protected_peer_ids.count(candidate.id)) {
      44                 :            :             // this peer should have been removed from the eviction candidates
      45                 :          0 :             BOOST_TEST_MESSAGE(strprintf("expected candidate to be protected: %d", candidate.id));
      46                 :          0 :             return false;
      47                 :            :         }
      48                 :          0 :         if (unprotected_peer_ids.count(candidate.id)) {
      49                 :            :             // this peer remains in the eviction candidates, as expected
      50                 :          0 :             ++unprotected_count;
      51                 :          0 :         }
      52                 :            :     }
      53                 :            : 
      54                 :          0 :     const bool is_protected{unprotected_count == unprotected_peer_ids.size()};
      55                 :          0 :     if (!is_protected) {
      56                 :          0 :         BOOST_TEST_MESSAGE(strprintf("unprotected: expected %d, actual %d",
      57                 :            :                                      unprotected_peer_ids.size(), unprotected_count));
      58                 :          0 :     }
      59                 :          0 :     return is_protected;
      60                 :          0 : }
      61                 :            : 
      62                 :          0 : BOOST_AUTO_TEST_CASE(peer_protection_test)
      63                 :            : {
      64                 :          0 :     FastRandomContext random_context{true};
      65                 :          0 :     int num_peers{12};
      66                 :            : 
      67                 :            :     // Expect half of the peers with greatest uptime (the lowest m_connected)
      68                 :            :     // to be protected from eviction.
      69                 :          0 :     BOOST_CHECK(IsProtected(
      70                 :            :         num_peers, [](NodeEvictionCandidate& c) {
      71                 :            :             c.m_connected = std::chrono::seconds{c.id};
      72                 :            :             c.m_is_local = false;
      73                 :            :             c.m_network = NET_IPV4;
      74                 :          0 :         },
      75                 :            :         /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5},
      76                 :            :         /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11},
      77                 :            :         random_context));
      78                 :            : 
      79                 :            :     // Verify in the opposite direction.
      80                 :          0 :     BOOST_CHECK(IsProtected(
      81                 :            :         num_peers, [num_peers](NodeEvictionCandidate& c) {
      82                 :            :             c.m_connected = std::chrono::seconds{num_peers - c.id};
      83                 :          0 :             c.m_is_local = false;
      84                 :            :             c.m_network = NET_IPV6;
      85                 :            :         },
      86                 :            :         /*protected_peer_ids=*/{6, 7, 8, 9, 10, 11},
      87                 :            :         /*unprotected_peer_ids=*/{0, 1, 2, 3, 4, 5},
      88                 :            :         random_context));
      89                 :            : 
      90                 :            :     // Test protection of onion, localhost, and I2P peers...
      91                 :            : 
      92                 :            :     // Expect 1/4 onion peers to be protected from eviction,
      93                 :            :     // if no localhost, I2P, or CJDNS peers.
      94                 :          0 :     BOOST_CHECK(IsProtected(
      95                 :            :         num_peers, [](NodeEvictionCandidate& c) {
      96                 :            :             c.m_is_local = false;
      97                 :            :             c.m_network = (c.id == 3 || c.id == 8 || c.id == 9) ? NET_ONION : NET_IPV4;
      98                 :            :         },
      99                 :            :         /*protected_peer_ids=*/{3, 8, 9},
     100                 :            :         /*unprotected_peer_ids=*/{},
     101                 :            :         random_context));
     102                 :            : 
     103                 :            :     // Expect 1/4 onion peers and 1/4 of the other peers to be protected,
     104                 :            :     // sorted by longest uptime (lowest m_connected), if no localhost, I2P or CJDNS peers.
     105                 :          0 :     BOOST_CHECK(IsProtected(
     106                 :            :         num_peers, [](NodeEvictionCandidate& c) {
     107                 :            :             c.m_connected = std::chrono::seconds{c.id};
     108                 :            :             c.m_is_local = false;
     109                 :            :             c.m_network = (c.id == 3 || c.id > 7) ? NET_ONION : NET_IPV6;
     110                 :            :         },
     111                 :            :         /*protected_peer_ids=*/{0, 1, 2, 3, 8, 9},
     112                 :            :         /*unprotected_peer_ids=*/{4, 5, 6, 7, 10, 11},
     113                 :            :         random_context));
     114                 :            : 
     115                 :            :     // Expect 1/4 localhost peers to be protected from eviction,
     116                 :            :     // if no onion, I2P, or CJDNS peers.
     117                 :          0 :     BOOST_CHECK(IsProtected(
     118                 :            :         num_peers, [](NodeEvictionCandidate& c) {
     119                 :            :             c.m_is_local = (c.id == 1 || c.id == 9 || c.id == 11);
     120                 :            :             c.m_network = NET_IPV4;
     121                 :            :         },
     122                 :            :         /*protected_peer_ids=*/{1, 9, 11},
     123                 :            :         /*unprotected_peer_ids=*/{},
     124                 :            :         random_context));
     125                 :            : 
     126                 :            :     // Expect 1/4 localhost peers and 1/4 of the other peers to be protected,
     127                 :            :     // sorted by longest uptime (lowest m_connected), if no onion, I2P, or CJDNS peers.
     128                 :          0 :     BOOST_CHECK(IsProtected(
     129                 :            :         num_peers, [](NodeEvictionCandidate& c) {
     130                 :            :             c.m_connected = std::chrono::seconds{c.id};
     131                 :            :             c.m_is_local = (c.id > 6);
     132                 :            :             c.m_network = NET_IPV6;
     133                 :            :         },
     134                 :            :         /*protected_peer_ids=*/{0, 1, 2, 7, 8, 9},
     135                 :            :         /*unprotected_peer_ids=*/{3, 4, 5, 6, 10, 11},
     136                 :            :         random_context));
     137                 :            : 
     138                 :            :     // Expect 1/4 I2P peers to be protected from eviction,
     139                 :            :     // if no onion, localhost, or CJDNS peers.
     140                 :          0 :     BOOST_CHECK(IsProtected(
     141                 :            :         num_peers, [](NodeEvictionCandidate& c) {
     142                 :            :             c.m_is_local = false;
     143                 :            :             c.m_network = (c.id == 2 || c.id == 7 || c.id == 10) ? NET_I2P : NET_IPV4;
     144                 :            :         },
     145                 :            :         /*protected_peer_ids=*/{2, 7, 10},
     146                 :            :         /*unprotected_peer_ids=*/{},
     147                 :            :         random_context));
     148                 :            : 
     149                 :            :     // Expect 1/4 I2P peers and 1/4 of the other peers to be protected, sorted
     150                 :            :     // by longest uptime (lowest m_connected), if no onion, localhost, or CJDNS peers.
     151                 :          0 :     BOOST_CHECK(IsProtected(
     152                 :            :         num_peers, [](NodeEvictionCandidate& c) {
     153                 :            :             c.m_connected = std::chrono::seconds{c.id};
     154                 :            :             c.m_is_local = false;
     155                 :            :             c.m_network = (c.id == 4 || c.id > 8) ? NET_I2P : NET_IPV6;
     156                 :            :         },
     157                 :            :         /*protected_peer_ids=*/{0, 1, 2, 4, 9, 10},
     158                 :            :         /*unprotected_peer_ids=*/{3, 5, 6, 7, 8, 11},
     159                 :            :         random_context));
     160                 :            : 
     161                 :            :     // Expect 1/4 CJDNS peers to be protected from eviction,
     162                 :            :     // if no onion, localhost, or I2P peers.
     163                 :          0 :     BOOST_CHECK(IsProtected(
     164                 :            :         num_peers, [](NodeEvictionCandidate& c) {
     165                 :            :             c.m_is_local = false;
     166                 :            :             c.m_network = (c.id == 2 || c.id == 7 || c.id == 10) ? NET_CJDNS : NET_IPV4;
     167                 :            :         },
     168                 :            :         /*protected_peer_ids=*/{2, 7, 10},
     169                 :            :         /*unprotected_peer_ids=*/{},
     170                 :            :         random_context));
     171                 :            : 
     172                 :            :     // Expect 1/4 CJDNS peers and 1/4 of the other peers to be protected, sorted
     173                 :            :     // by longest uptime (lowest m_connected), if no onion, localhost, or I2P peers.
     174                 :          0 :     BOOST_CHECK(IsProtected(
     175                 :            :         num_peers, [](NodeEvictionCandidate& c) {
     176                 :            :             c.m_connected = std::chrono::seconds{c.id};
     177                 :            :             c.m_is_local = false;
     178                 :            :             c.m_network = (c.id == 4 || c.id > 8) ? NET_CJDNS : NET_IPV6;
     179                 :            :         },
     180                 :            :         /*protected_peer_ids=*/{0, 1, 2, 4, 9, 10},
     181                 :            :         /*unprotected_peer_ids=*/{3, 5, 6, 7, 8, 11},
     182                 :            :         random_context));
     183                 :            : 
     184                 :            :     // Tests with 2 networks...
     185                 :            : 
     186                 :            :     // Combined test: expect having 1 localhost and 1 onion peer out of 4 to
     187                 :            :     // protect 1 localhost, 0 onion and 1 other peer, sorted by longest uptime;
     188                 :            :     // stable sort breaks tie with array order of localhost first.
     189                 :          0 :     BOOST_CHECK(IsProtected(
     190                 :            :         4, [](NodeEvictionCandidate& c) {
     191                 :            :             c.m_connected = std::chrono::seconds{c.id};
     192                 :            :             c.m_is_local = (c.id == 4);
     193                 :            :             c.m_network = (c.id == 3) ? NET_ONION : NET_IPV4;
     194                 :            :         },
     195                 :            :         /*protected_peer_ids=*/{0, 4},
     196                 :            :         /*unprotected_peer_ids=*/{1, 2},
     197                 :            :         random_context));
     198                 :            : 
     199                 :            :     // Combined test: expect having 1 localhost and 1 onion peer out of 7 to
     200                 :            :     // protect 1 localhost, 0 onion, and 2 other peers (3 total), sorted by
     201                 :            :     // uptime; stable sort breaks tie with array order of localhost first.
     202                 :          0 :     BOOST_CHECK(IsProtected(
     203                 :            :         7, [](NodeEvictionCandidate& c) {
     204                 :            :             c.m_connected = std::chrono::seconds{c.id};
     205                 :            :             c.m_is_local = (c.id == 6);
     206                 :            :             c.m_network = (c.id == 5) ? NET_ONION : NET_IPV4;
     207                 :            :         },
     208                 :            :         /*protected_peer_ids=*/{0, 1, 6},
     209                 :            :         /*unprotected_peer_ids=*/{2, 3, 4, 5},
     210                 :            :         random_context));
     211                 :            : 
     212                 :            :     // Combined test: expect having 1 localhost and 1 onion peer out of 8 to
     213                 :            :     // protect protect 1 localhost, 1 onion and 2 other peers (4 total), sorted
     214                 :            :     // by uptime; stable sort breaks tie with array order of localhost first.
     215                 :          0 :     BOOST_CHECK(IsProtected(
     216                 :            :         8, [](NodeEvictionCandidate& c) {
     217                 :            :             c.m_connected = std::chrono::seconds{c.id};
     218                 :            :             c.m_is_local = (c.id == 6);
     219                 :            :             c.m_network = (c.id == 5) ? NET_ONION : NET_IPV4;
     220                 :            :         },
     221                 :            :         /*protected_peer_ids=*/{0, 1, 5, 6},
     222                 :            :         /*unprotected_peer_ids=*/{2, 3, 4, 7},
     223                 :            :         random_context));
     224                 :            : 
     225                 :            :     // Combined test: expect having 3 localhost and 3 onion peers out of 12 to
     226                 :            :     // protect 2 localhost and 1 onion, plus 3 other peers, sorted by longest
     227                 :          0 :     // uptime; stable sort breaks ties with the array order of localhost first.
     228                 :          0 :     BOOST_CHECK(IsProtected(
     229                 :            :         num_peers, [](NodeEvictionCandidate& c) {
     230                 :            :             c.m_connected = std::chrono::seconds{c.id};
     231                 :            :             c.m_is_local = (c.id == 6 || c.id == 9 || c.id == 11);
     232                 :            :             c.m_network = (c.id == 7 || c.id == 8 || c.id == 10) ? NET_ONION : NET_IPV6;
     233                 :            :         },
     234                 :            :         /*protected_peer_ids=*/{0, 1, 2, 6, 7, 9},
     235                 :            :         /*unprotected_peer_ids=*/{3, 4, 5, 8, 10, 11},
     236                 :            :         random_context));
     237                 :            : 
     238                 :            :     // Combined test: expect having 4 localhost and 1 onion peer out of 12 to
     239                 :            :     // protect 2 localhost and 1 onion, plus 3 other peers, sorted by longest uptime.
     240                 :          0 :     BOOST_CHECK(IsProtected(
     241                 :            :         num_peers, [](NodeEvictionCandidate& c) {
     242                 :            :             c.m_connected = std::chrono::seconds{c.id};
     243                 :            :             c.m_is_local = (c.id > 4 && c.id < 9);
     244                 :            :             c.m_network = (c.id == 10) ? NET_ONION : NET_IPV4;
     245                 :            :         },
     246                 :            :         /*protected_peer_ids=*/{0, 1, 2, 5, 6, 10},
     247                 :            :         /*unprotected_peer_ids=*/{3, 4, 7, 8, 9, 11},
     248                 :            :         random_context));
     249                 :            : 
     250                 :            :     // Combined test: expect having 4 localhost and 2 onion peers out of 16 to
     251                 :            :     // protect 2 localhost and 2 onions, plus 4 other peers, sorted by longest uptime.
     252                 :          0 :     BOOST_CHECK(IsProtected(
     253                 :            :         16, [](NodeEvictionCandidate& c) {
     254                 :            :             c.m_connected = std::chrono::seconds{c.id};
     255                 :            :             c.m_is_local = (c.id == 6 || c.id == 9 || c.id == 11 || c.id == 12);
     256                 :            :             c.m_network = (c.id == 8 || c.id == 10) ? NET_ONION : NET_IPV6;
     257                 :            :         },
     258                 :            :         /*protected_peer_ids=*/{0, 1, 2, 3, 6, 8, 9, 10},
     259                 :            :         /*unprotected_peer_ids=*/{4, 5, 7, 11, 12, 13, 14, 15},
     260                 :            :         random_context));
     261                 :            : 
     262                 :            :     // Combined test: expect having 5 localhost and 1 onion peer out of 16 to
     263                 :            :     // protect 3 localhost (recovering the unused onion slot), 1 onion, and 4
     264                 :            :     // others, sorted by longest uptime.
     265                 :          0 :     BOOST_CHECK(IsProtected(
     266                 :            :         16, [](NodeEvictionCandidate& c) {
     267                 :            :             c.m_connected = std::chrono::seconds{c.id};
     268                 :            :             c.m_is_local = (c.id > 10);
     269                 :            :             c.m_network = (c.id == 10) ? NET_ONION : NET_IPV4;
     270                 :            :         },
     271                 :            :         /*protected_peer_ids=*/{0, 1, 2, 3, 10, 11, 12, 13},
     272                 :            :         /*unprotected_peer_ids=*/{4, 5, 6, 7, 8, 9, 14, 15},
     273                 :            :         random_context));
     274                 :            : 
     275                 :            :     // Combined test: expect having 1 localhost and 4 onion peers out of 16 to
     276                 :            :     // protect 1 localhost and 3 onions (recovering the unused localhost slot),
     277                 :            :     // plus 4 others, sorted by longest uptime.
     278                 :          0 :     BOOST_CHECK(IsProtected(
     279                 :            :         16, [](NodeEvictionCandidate& c) {
     280                 :            :             c.m_connected = std::chrono::seconds{c.id};
     281                 :            :             c.m_is_local = (c.id == 15);
     282                 :            :             c.m_network = (c.id > 6 && c.id < 11) ? NET_ONION : NET_IPV6;
     283                 :            :         },
     284                 :            :         /*protected_peer_ids=*/{0, 1, 2, 3, 7, 8, 9, 15},
     285                 :            :         /*unprotected_peer_ids=*/{5, 6, 10, 11, 12, 13, 14},
     286                 :            :         random_context));
     287                 :            : 
     288                 :            :     // Combined test: expect having 2 onion and 4 I2P out of 12 peers to protect
     289                 :            :     // 2 onion (prioritized for having fewer candidates) and 1 I2P, plus 3
     290                 :            :     // others, sorted by longest uptime.
     291                 :          0 :     BOOST_CHECK(IsProtected(
     292                 :            :         num_peers, [](NodeEvictionCandidate& c) {
     293                 :            :             c.m_connected = std::chrono::seconds{c.id};
     294                 :            :             c.m_is_local = false;
     295                 :            :             if (c.id == 8 || c.id == 10) {
     296                 :            :                 c.m_network = NET_ONION;
     297                 :            :             } else if (c.id == 6 || c.id == 9 || c.id == 11 || c.id == 12) {
     298                 :            :                 c.m_network = NET_I2P;
     299                 :            :             } else {
     300                 :            :                 c.m_network = NET_IPV4;
     301                 :            :             }
     302                 :            :         },
     303                 :            :         /*protected_peer_ids=*/{0, 1, 2, 6, 8, 10},
     304                 :            :         /*unprotected_peer_ids=*/{3, 4, 5, 7, 9, 11},
     305                 :            :         random_context));
     306                 :            : 
     307                 :            :     // Tests with 3 networks...
     308                 :            : 
     309                 :            :     // Combined test: expect having 1 localhost, 1 I2P and 1 onion peer out of 4
     310                 :            :     // to protect 1 I2P, 0 localhost, 0 onion and 1 other peer (2 total), sorted
     311                 :            :     // by longest uptime; stable sort breaks tie with array order of I2P first.
     312                 :          0 :     BOOST_CHECK(IsProtected(
     313                 :            :         4, [](NodeEvictionCandidate& c) {
     314                 :            :             c.m_connected = std::chrono::seconds{c.id};
     315                 :            :             c.m_is_local = (c.id == 2);
     316                 :            :             if (c.id == 3) {
     317                 :            :                 c.m_network = NET_I2P;
     318                 :            :             } else if (c.id == 1) {
     319                 :            :                 c.m_network = NET_ONION;
     320                 :            :             } else {
     321                 :            :                 c.m_network = NET_IPV6;
     322                 :            :             }
     323                 :            :         },
     324                 :            :         /*protected_peer_ids=*/{0, 3},
     325                 :            :         /*unprotected_peer_ids=*/{1, 2},
     326                 :            :         random_context));
     327                 :            : 
     328                 :            :     // Combined test: expect having 1 localhost, 1 I2P and 1 onion peer out of 7
     329                 :            :     // to protect 1 I2P, 0 localhost, 0 onion and 2 other peers (3 total) sorted
     330                 :            :     // by longest uptime; stable sort breaks tie with array order of I2P first.
     331                 :          0 :     BOOST_CHECK(IsProtected(
     332                 :            :         7, [](NodeEvictionCandidate& c) {
     333                 :            :             c.m_connected = std::chrono::seconds{c.id};
     334                 :            :             c.m_is_local = (c.id == 4);
     335                 :            :             if (c.id == 6) {
     336                 :            :                 c.m_network = NET_I2P;
     337                 :            :             } else if (c.id == 5) {
     338                 :            :                 c.m_network = NET_ONION;
     339                 :            :             } else {
     340                 :            :                 c.m_network = NET_IPV6;
     341                 :            :             }
     342                 :            :         },
     343                 :            :         /*protected_peer_ids=*/{0, 1, 6},
     344                 :            :         /*unprotected_peer_ids=*/{2, 3, 4, 5},
     345                 :            :         random_context));
     346                 :            : 
     347                 :            :     // Combined test: expect having 1 localhost, 1 I2P and 1 onion peer out of 8
     348                 :            :     // to protect 1 I2P, 1 localhost, 0 onion and 2 other peers (4 total) sorted
     349                 :            :     // by uptime; stable sort breaks tie with array order of I2P then localhost.
     350                 :          0 :     BOOST_CHECK(IsProtected(
     351                 :            :         8, [](NodeEvictionCandidate& c) {
     352                 :            :             c.m_connected = std::chrono::seconds{c.id};
     353                 :            :             c.m_is_local = (c.id == 6);
     354                 :            :             if (c.id == 5) {
     355                 :            :                 c.m_network = NET_I2P;
     356                 :            :             } else if (c.id == 4) {
     357                 :            :                 c.m_network = NET_ONION;
     358                 :            :             } else {
     359                 :            :                 c.m_network = NET_IPV6;
     360                 :            :             }
     361                 :            :         },
     362                 :            :         /*protected_peer_ids=*/{0, 1, 5, 6},
     363                 :            :         /*unprotected_peer_ids=*/{2, 3, 4, 7},
     364                 :            :         random_context));
     365                 :            : 
     366                 :            :     // Combined test: expect having 4 localhost, 2 I2P, and 2 onion peers out of
     367                 :            :     // 16 to protect 1 localhost, 2 I2P, and 1 onion (4/16 total), plus 4 others
     368                 :            :     // for 8 total, sorted by longest uptime.
     369                 :          0 :     BOOST_CHECK(IsProtected(
     370                 :            :         16, [](NodeEvictionCandidate& c) {
     371                 :            :             c.m_connected = std::chrono::seconds{c.id};
     372                 :            :             c.m_is_local = (c.id == 6 || c.id > 11);
     373                 :            :             if (c.id == 7 || c.id == 11) {
     374                 :            :                 c.m_network = NET_I2P;
     375                 :            :             } else if (c.id == 9 || c.id == 10) {
     376                 :            :                 c.m_network = NET_ONION;
     377                 :            :             } else {
     378                 :            :                 c.m_network = NET_IPV4;
     379                 :            :             }
     380                 :            :         },
     381                 :            :         /*protected_peer_ids=*/{0, 1, 2, 3, 6, 7, 9, 11},
     382                 :            :         /*unprotected_peer_ids=*/{4, 5, 8, 10, 12, 13, 14, 15},
     383                 :            :         random_context));
     384                 :            : 
     385                 :            :     // Combined test: expect having 1 localhost, 8 I2P and 1 onion peer out of
     386                 :            :     // 24 to protect 1, 4, and 1 (6 total), plus 6 others for 12/24 total,
     387                 :            :     // sorted by longest uptime.
     388                 :          0 :     BOOST_CHECK(IsProtected(
     389                 :            :         24, [](NodeEvictionCandidate& c) {
     390                 :            :             c.m_connected = std::chrono::seconds{c.id};
     391                 :            :             c.m_is_local = (c.id == 12);
     392                 :            :             if (c.id > 14 && c.id < 23) { // 4 protected instead of usual 2
     393                 :            :                 c.m_network = NET_I2P;
     394                 :            :             } else if (c.id == 23) {
     395                 :            :                 c.m_network = NET_ONION;
     396                 :            :             } else {
     397                 :            :                 c.m_network = NET_IPV6;
     398                 :            :             }
     399                 :            :         },
     400                 :            :         /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5, 12, 15, 16, 17, 18, 23},
     401                 :            :         /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11, 13, 14, 19, 20, 21, 22},
     402                 :            :         random_context));
     403                 :            : 
     404                 :            :     // Combined test: expect having 1 localhost, 3 I2P and 6 onion peers out of
     405                 :            :     // 24 to protect 1, 3, and 2 (6 total, I2P has fewer candidates and so gets the
     406                 :            :     // unused localhost slot), plus 6 others for 12/24 total, sorted by longest uptime.
     407                 :          0 :     BOOST_CHECK(IsProtected(
     408                 :            :         24, [](NodeEvictionCandidate& c) {
     409                 :            :             c.m_connected = std::chrono::seconds{c.id};
     410                 :            :             c.m_is_local = (c.id == 15);
     411                 :            :             if (c.id == 12 || c.id == 14 || c.id == 17) {
     412                 :            :                 c.m_network = NET_I2P;
     413                 :            :             } else if (c.id > 17) { // 4 protected instead of usual 2
     414                 :            :                 c.m_network = NET_ONION;
     415                 :            :             } else {
     416                 :            :                 c.m_network = NET_IPV4;
     417                 :            :             }
     418                 :            :         },
     419                 :            :         /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5, 12, 14, 15, 17, 18, 19},
     420                 :            :         /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11, 13, 16, 20, 21, 22, 23},
     421                 :            :         random_context));
     422                 :            : 
     423                 :            :     // Combined test: expect having 1 localhost, 7 I2P and 4 onion peers out of
     424                 :            :     // 24 to protect 1 localhost, 2 I2P, and 3 onions (6 total), plus 6 others
     425                 :            :     // for 12/24 total, sorted by longest uptime.
     426                 :          0 :     BOOST_CHECK(IsProtected(
     427                 :            :         24, [](NodeEvictionCandidate& c) {
     428                 :            :             c.m_connected = std::chrono::seconds{c.id};
     429                 :            :             c.m_is_local = (c.id == 13);
     430                 :            :             if (c.id > 16) {
     431                 :            :                 c.m_network = NET_I2P;
     432                 :            :             } else if (c.id == 12 || c.id == 14 || c.id == 15 || c.id == 16) {
     433                 :            :                 c.m_network = NET_ONION;
     434                 :            :             } else {
     435                 :            :                 c.m_network = NET_IPV6;
     436                 :            :             }
     437                 :            :         },
     438                 :            :         /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5, 12, 13, 14, 15, 17, 18},
     439                 :            :         /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11, 16, 19, 20, 21, 22, 23},
     440                 :            :         random_context));
     441                 :            : 
     442                 :            :     // Combined test: expect having 8 localhost, 4 CJDNS, and 3 onion peers out
     443                 :            :     // of 24 to protect 2 of each (6 total), plus 6 others for 12/24 total,
     444                 :            :     // sorted by longest uptime.
     445                 :          0 :     BOOST_CHECK(IsProtected(
     446                 :            :         24, [](NodeEvictionCandidate& c) {
     447                 :            :             c.m_connected = std::chrono::seconds{c.id};
     448                 :            :             c.m_is_local = (c.id > 15);
     449                 :            :             if (c.id > 10 && c.id < 15) {
     450                 :            :                 c.m_network = NET_CJDNS;
     451                 :            :             } else if (c.id > 6 && c.id < 10) {
     452                 :            :                 c.m_network = NET_ONION;
     453                 :            :             } else {
     454                 :            :                 c.m_network = NET_IPV4;
     455                 :            :             }
     456                 :            :         },
     457                 :            :         /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5, 7, 8, 11, 12, 16, 17},
     458                 :            :         /*unprotected_peer_ids=*/{6, 9, 10, 13, 14, 15, 18, 19, 20, 21, 22, 23},
     459                 :            :         random_context));
     460                 :            : 
     461                 :            :     // Tests with 4 networks...
     462                 :            : 
     463                 :            :     // Combined test: expect having 1 CJDNS, 1 I2P, 1 localhost and 1 onion peer
     464                 :            :     // out of 5 to protect 1 CJDNS, 0 I2P, 0 localhost, 0 onion and 1 other peer
     465                 :            :     // (2 total), sorted by longest uptime; stable sort breaks tie with array
     466                 :            :     // order of CJDNS first.
     467                 :          0 :     BOOST_CHECK(IsProtected(
     468                 :            :         5, [](NodeEvictionCandidate& c) {
     469                 :            :             c.m_connected = std::chrono::seconds{c.id};
     470                 :            :             c.m_is_local = (c.id == 3);
     471                 :            :             if (c.id == 4) {
     472                 :            :                 c.m_network = NET_CJDNS;
     473                 :            :             } else if (c.id == 1) {
     474                 :            :                 c.m_network = NET_I2P;
     475                 :            :             } else if (c.id == 2) {
     476                 :            :                 c.m_network = NET_ONION;
     477                 :            :             } else {
     478                 :            :                 c.m_network = NET_IPV6;
     479                 :            :             }
     480                 :            :         },
     481                 :            :         /*protected_peer_ids=*/{0, 4},
     482                 :            :         /*unprotected_peer_ids=*/{1, 2, 3},
     483                 :            :         random_context));
     484                 :            : 
     485                 :            :     // Combined test: expect having 1 CJDNS, 1 I2P, 1 localhost and 1 onion peer
     486                 :            :     // out of 7 to protect 1 CJDNS, 0, I2P, 0 localhost, 0 onion and 2 other
     487                 :            :     // peers (3 total) sorted by longest uptime; stable sort breaks tie with
     488                 :            :     // array order of CJDNS first.
     489                 :          0 :     BOOST_CHECK(IsProtected(
     490                 :            :         7, [](NodeEvictionCandidate& c) {
     491                 :            :             c.m_connected = std::chrono::seconds{c.id};
     492                 :            :             c.m_is_local = (c.id == 4);
     493                 :            :             if (c.id == 6) {
     494                 :            :                 c.m_network = NET_CJDNS;
     495                 :            :             } else if (c.id == 5) {
     496                 :            :                 c.m_network = NET_I2P;
     497                 :            :             } else if (c.id == 3) {
     498                 :            :                 c.m_network = NET_ONION;
     499                 :            :             } else {
     500                 :            :                 c.m_network = NET_IPV4;
     501                 :            :             }
     502                 :            :         },
     503                 :            :         /*protected_peer_ids=*/{0, 1, 6},
     504                 :            :         /*unprotected_peer_ids=*/{2, 3, 4, 5},
     505                 :            :         random_context));
     506                 :            : 
     507                 :            :     // Combined test: expect having 1 CJDNS, 1 I2P, 1 localhost and 1 onion peer
     508                 :            :     // out of 8 to protect 1 CJDNS, 1 I2P, 0 localhost, 0 onion and 2 other
     509                 :            :     // peers (4 total) sorted by longest uptime; stable sort breaks tie with
     510                 :            :     // array order of CJDNS first.
     511                 :          0 :     BOOST_CHECK(IsProtected(
     512                 :            :         8, [](NodeEvictionCandidate& c) {
     513                 :            :             c.m_connected = std::chrono::seconds{c.id};
     514                 :            :             c.m_is_local = (c.id == 3);
     515                 :            :             if (c.id == 5) {
     516                 :            :                 c.m_network = NET_CJDNS;
     517                 :            :             } else if (c.id == 6) {
     518                 :            :                 c.m_network = NET_I2P;
     519                 :            :             } else if (c.id == 3) {
     520                 :            :                 c.m_network = NET_ONION;
     521                 :            :             } else {
     522                 :            :                 c.m_network = NET_IPV6;
     523                 :            :             }
     524                 :            :         },
     525                 :            :         /*protected_peer_ids=*/{0, 1, 5, 6},
     526                 :            :         /*unprotected_peer_ids=*/{2, 3, 4, 7},
     527                 :            :         random_context));
     528                 :            : 
     529                 :            :     // Combined test: expect having 2 CJDNS, 2 I2P, 4 localhost, and 2 onion
     530                 :            :     // peers out of 16 to protect 1 CJDNS, 1 I2P, 1 localhost, 1 onion (4/16
     531                 :            :     // total), plus 4 others for 8 total, sorted by longest uptime.
     532                 :          0 :     BOOST_CHECK(IsProtected(
     533                 :            :         16, [](NodeEvictionCandidate& c) {
     534                 :            :             c.m_connected = std::chrono::seconds{c.id};
     535                 :            :             c.m_is_local = (c.id > 5);
     536                 :            :             if (c.id == 11 || c.id == 15) {
     537                 :            :                 c.m_network = NET_CJDNS;
     538                 :            :             } else if (c.id == 10 || c.id == 14) {
     539                 :            :                 c.m_network = NET_I2P;
     540                 :            :             } else if (c.id == 8 || c.id == 9) {
     541                 :            :                 c.m_network = NET_ONION;
     542                 :            :             } else {
     543                 :            :                 c.m_network = NET_IPV4;
     544                 :            :             }
     545                 :            :         },
     546                 :            :         /*protected_peer_ids=*/{0, 1, 2, 3, 6, 8, 10, 11},
     547                 :            :         /*unprotected_peer_ids=*/{4, 5, 7, 9, 12, 13, 14, 15},
     548                 :            :         random_context));
     549                 :            : 
     550                 :            :     // Combined test: expect having 6 CJDNS, 1 I2P, 1 localhost, and 4 onion
     551                 :            :     // peers out of 24 to protect 2 CJDNS, 1 I2P, 1 localhost, and 2 onions (6
     552                 :            :     // total), plus 6 others for 12/24 total, sorted by longest uptime.
     553                 :          0 :     BOOST_CHECK(IsProtected(
     554                 :            :         24, [](NodeEvictionCandidate& c) {
     555                 :            :             c.m_connected = std::chrono::seconds{c.id};
     556                 :            :             c.m_is_local = (c.id == 13);
     557                 :            :             if (c.id > 17) {
     558                 :            :                 c.m_network = NET_CJDNS;
     559                 :            :             } else if (c.id == 17) {
     560                 :            :                 c.m_network = NET_I2P;
     561                 :            :             } else if (c.id == 12 || c.id == 14 || c.id == 15 || c.id == 16) {
     562                 :            :                 c.m_network = NET_ONION;
     563                 :            :             } else {
     564                 :            :                 c.m_network = NET_IPV6;
     565                 :            :             }
     566                 :            :         },
     567                 :            :         /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5, 12, 13, 14, 17, 18, 19},
     568                 :            :         /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11, 15, 16, 20, 21, 22, 23},
     569                 :            :         random_context));
     570                 :          0 : }
     571                 :            : 
     572                 :            : // Returns true if any of the node ids in node_ids are selected for eviction.
     573                 :          0 : bool IsEvicted(std::vector<NodeEvictionCandidate> candidates, const std::unordered_set<NodeId>& node_ids, FastRandomContext& random_context)
     574                 :            : {
     575                 :          0 :     Shuffle(candidates.begin(), candidates.end(), random_context);
     576                 :          0 :     const std::optional<NodeId> evicted_node_id = SelectNodeToEvict(std::move(candidates));
     577                 :          0 :     if (!evicted_node_id) {
     578                 :          0 :         return false;
     579                 :            :     }
     580                 :          0 :     return node_ids.count(*evicted_node_id);
     581                 :          0 : }
     582                 :            : 
     583                 :            : // Create number_of_nodes random nodes, apply setup function candidate_setup_fn,
     584                 :            : // apply eviction logic and then return true if any of the node ids in node_ids
     585                 :            : // are selected for eviction.
     586                 :          0 : bool IsEvicted(const int number_of_nodes, std::function<void(NodeEvictionCandidate&)> candidate_setup_fn, const std::unordered_set<NodeId>& node_ids, FastRandomContext& random_context)
     587                 :            : {
     588                 :          0 :     std::vector<NodeEvictionCandidate> candidates = GetRandomNodeEvictionCandidates(number_of_nodes, random_context);
     589                 :          0 :     for (NodeEvictionCandidate& candidate : candidates) {
     590                 :          0 :         candidate_setup_fn(candidate);
     591                 :            :     }
     592                 :          0 :     return IsEvicted(candidates, node_ids, random_context);
     593                 :          0 : }
     594                 :            : 
     595                 :          0 : BOOST_AUTO_TEST_CASE(peer_eviction_test)
     596                 :            : {
     597                 :          0 :     FastRandomContext random_context{true};
     598                 :            : 
     599                 :          0 :     for (int number_of_nodes = 0; number_of_nodes < 200; ++number_of_nodes) {
     600                 :            :         // Four nodes with the highest keyed netgroup values should be
     601                 :            :         // protected from eviction.
     602                 :          0 :         BOOST_CHECK(!IsEvicted(
     603                 :            :                         number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
     604                 :            :                             candidate.nKeyedNetGroup = number_of_nodes - candidate.id;
     605                 :            :                         },
     606                 :            :                         {0, 1, 2, 3}, random_context));
     607                 :            : 
     608                 :            :         // Eight nodes with the lowest minimum ping time should be protected
     609                 :            :         // from eviction.
     610                 :          0 :         BOOST_CHECK(!IsEvicted(
     611                 :            :                         number_of_nodes, [](NodeEvictionCandidate& candidate) {
     612                 :            :                             candidate.m_min_ping_time = std::chrono::microseconds{candidate.id};
     613                 :            :                         },
     614                 :            :                         {0, 1, 2, 3, 4, 5, 6, 7}, random_context));
     615                 :            : 
     616                 :            :         // Four nodes that most recently sent us novel transactions accepted
     617                 :            :         // into our mempool should be protected from eviction.
     618                 :          0 :         BOOST_CHECK(!IsEvicted(
     619                 :            :                         number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
     620                 :            :                             candidate.m_last_tx_time = std::chrono::seconds{number_of_nodes - candidate.id};
     621                 :            :                         },
     622                 :            :                         {0, 1, 2, 3}, random_context));
     623                 :            : 
     624                 :            :         // Up to eight non-tx-relay peers that most recently sent us novel
     625                 :            :         // blocks should be protected from eviction.
     626                 :          0 :         BOOST_CHECK(!IsEvicted(
     627                 :            :                         number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
     628                 :            :                             candidate.m_last_block_time = std::chrono::seconds{number_of_nodes - candidate.id};
     629                 :            :                             if (candidate.id <= 7) {
     630                 :            :                                 candidate.m_relay_txs = false;
     631                 :            :                                 candidate.fRelevantServices = true;
     632                 :            :                             }
     633                 :            :                         },
     634                 :            :                         {0, 1, 2, 3, 4, 5, 6, 7}, random_context));
     635                 :            : 
     636                 :            :         // Four peers that most recently sent us novel blocks should be
     637                 :            :         // protected from eviction.
     638                 :          0 :         BOOST_CHECK(!IsEvicted(
     639                 :            :                         number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
     640                 :            :                             candidate.m_last_block_time = std::chrono::seconds{number_of_nodes - candidate.id};
     641                 :            :                         },
     642                 :            :                         {0, 1, 2, 3}, random_context));
     643                 :            : 
     644                 :            :         // Combination of the previous two tests.
     645                 :          0 :         BOOST_CHECK(!IsEvicted(
     646                 :            :                         number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
     647                 :            :                             candidate.m_last_block_time = std::chrono::seconds{number_of_nodes - candidate.id};
     648                 :            :                             if (candidate.id <= 7) {
     649                 :            :                                 candidate.m_relay_txs = false;
     650                 :            :                                 candidate.fRelevantServices = true;
     651                 :            :                             }
     652                 :            :                         },
     653                 :            :                         {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, random_context));
     654                 :            : 
     655                 :            :         // Combination of all tests above.
     656                 :          0 :         BOOST_CHECK(!IsEvicted(
     657                 :            :                         number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
     658                 :            :                             candidate.nKeyedNetGroup = number_of_nodes - candidate.id;           // 4 protected
     659                 :            :                             candidate.m_min_ping_time = std::chrono::microseconds{candidate.id}; // 8 protected
     660                 :            :                             candidate.m_last_tx_time = std::chrono::seconds{number_of_nodes - candidate.id};    // 4 protected
     661                 :            :                             candidate.m_last_block_time = std::chrono::seconds{number_of_nodes - candidate.id}; // 4 protected
     662                 :            :                         },
     663                 :            :                         {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}, random_context));
     664                 :            : 
     665                 :            :         // An eviction is expected given >= 29 random eviction candidates. The eviction logic protects at most
     666                 :            :         // four peers by net group, eight by lowest ping time, four by last time of novel tx, up to eight non-tx-relay
     667                 :            :         // peers by last novel block time, and four more peers by last novel block time.
     668                 :          0 :         if (number_of_nodes >= 29) {
     669                 :          0 :             BOOST_CHECK(SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context)));
     670                 :          0 :         }
     671                 :            : 
     672                 :            :         // No eviction is expected given <= 20 random eviction candidates. The eviction logic protects at least
     673                 :            :         // four peers by net group, eight by lowest ping time, four by last time of novel tx and four peers by last
     674                 :            :         // novel block time.
     675                 :          0 :         if (number_of_nodes <= 20) {
     676                 :          0 :             BOOST_CHECK(!SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context)));
     677                 :          0 :         }
     678                 :            : 
     679                 :            :         // Cases left to test:
     680                 :            :         // * "If any remaining peers are preferred for eviction consider only them. [...]"
     681                 :            :         // * "Identify the network group with the most connections and youngest member. [...]"
     682                 :          0 :     }
     683                 :          0 : }
     684                 :            : 
     685                 :          0 : BOOST_AUTO_TEST_SUITE_END()

Generated by: LCOV version 1.14