LCOV - code coverage report
Current view: top level - src/test - net_peer_eviction_tests.cpp (source / functions) Coverage Total Hit
Test: total_coverage.info Lines: 96.3 % 82 79
Test Date: 2024-11-04 05:10:19 Functions: 100.0 % 31 31
Branches: 58.8 % 634 373

             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                 :             : 
      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                 :          32 : bool IsProtected(int num_peers,
      25                 :             :                  std::function<void(NodeEvictionCandidate&)> candidate_setup_fn,
      26                 :             :                  const std::unordered_set<NodeId>& protected_peer_ids,
      27                 :             :                  const std::unordered_set<NodeId>& unprotected_peer_ids,
      28                 :             :                  FastRandomContext& random_context)
      29                 :             : {
      30                 :          32 :     std::vector<NodeEvictionCandidate> candidates{GetRandomNodeEvictionCandidates(num_peers, random_context)};
      31         [ +  + ]:         446 :     for (NodeEvictionCandidate& candidate : candidates) {
      32         [ +  - ]:         414 :         candidate_setup_fn(candidate);
      33                 :             :     }
      34                 :          32 :     std::shuffle(candidates.begin(), candidates.end(), random_context);
      35                 :             : 
      36         [ +  - ]:          32 :     const size_t size{candidates.size()};
      37                 :          32 :     const size_t expected{size - size / 2}; // Expect half the candidates will be protected.
      38         [ +  - ]:          32 :     ProtectEvictionCandidatesByRatio(candidates);
      39   [ +  -  +  - ]:          32 :     BOOST_CHECK_EQUAL(candidates.size(), expected);
      40                 :             : 
      41                 :          32 :     size_t unprotected_count{0};
      42         [ +  + ]:         241 :     for (const NodeEvictionCandidate& candidate : candidates) {
      43         [ -  + ]:         209 :         if (protected_peer_ids.count(candidate.id)) {
      44                 :             :             // this peer should have been removed from the eviction candidates
      45   [ #  #  #  #  :           0 :             BOOST_TEST_MESSAGE(strprintf("expected candidate to be protected: %d", candidate.id));
             #  #  #  # ]
      46                 :           0 :             return false;
      47                 :             :         }
      48         [ +  + ]:         209 :         if (unprotected_peer_ids.count(candidate.id)) {
      49                 :             :             // this peer remains in the eviction candidates, as expected
      50                 :         184 :             ++unprotected_count;
      51                 :             :         }
      52                 :             :     }
      53                 :             : 
      54         [ -  + ]:          32 :     const bool is_protected{unprotected_count == unprotected_peer_ids.size()};
      55         [ -  + ]:          32 :     if (!is_protected) {
      56   [ #  #  #  #  :           0 :         BOOST_TEST_MESSAGE(strprintf("unprotected: expected %d, actual %d",
             #  #  #  # ]
      57                 :             :                                      unprotected_peer_ids.size(), unprotected_count));
      58                 :             :     }
      59                 :             :     return is_protected;
      60                 :          32 : }
      61                 :             : 
      62   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(peer_protection_test)
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
      63                 :             : {
      64                 :           1 :     FastRandomContext random_context{true};
      65                 :           1 :     int num_peers{12};
      66                 :             : 
      67                 :             :     // Expect half of the peers with greatest uptime (the lowest m_connected)
      68                 :             :     // to be protected from eviction.
      69   [ +  -  +  -  :          16 :     BOOST_CHECK(IsProtected(
          +  -  +  -  +  
                -  +  - ]
      70                 :             :         num_peers, [](NodeEvictionCandidate& c) {
      71                 :             :             c.m_connected = std::chrono::seconds{c.id};
      72                 :             :             c.m_is_local = false;
      73                 :             :             c.m_network = NET_IPV4;
      74                 :             :         },
      75                 :             :         /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5},
      76                 :             :         /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11},
      77                 :             :         random_context));
      78                 :             : 
      79                 :             :     // Verify in the opposite direction.
      80   [ +  -  +  -  :          16 :     BOOST_CHECK(IsProtected(
          +  -  +  -  +  
                -  +  - ]
      81                 :             :         num_peers, [num_peers](NodeEvictionCandidate& c) {
      82                 :             :             c.m_connected = std::chrono::seconds{num_peers - c.id};
      83                 :             :             c.m_is_local = false;
      84                 :             :             c.m_network = NET_IPV6;
      85                 :             :         },
      86                 :             :         /*protected_peer_ids=*/{6, 7, 8, 9, 10, 11},
      87                 :             :         /*unprotected_peer_ids=*/{0, 1, 2, 3, 4, 5},
      88                 :             :         random_context));
      89                 :             : 
      90                 :             :     // Test protection of onion, localhost, and I2P peers...
      91                 :             : 
      92                 :             :     // Expect 1/4 onion peers to be protected from eviction,
      93                 :             :     // if no localhost, I2P, or CJDNS peers.
      94   [ +  +  +  -  :          15 :     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   [ +  +  +  -  :          16 :     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   [ +  +  +  -  :          15 :     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   [ +  -  +  -  :          16 :     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   [ +  +  +  -  :          15 :     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   [ +  +  +  -  :          16 :     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   [ +  +  +  -  :          15 :     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   [ +  +  +  -  :          16 :     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   [ +  +  +  -  :           8 :     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   [ +  +  +  -  :          11 :     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   [ +  +  +  -  :          12 :     BOOST_CHECK(IsProtected(
          +  -  +  -  +  
                -  +  - ]
     216                 :             :         8, [](NodeEvictionCandidate& c) {
     217                 :             :             c.m_connected = std::chrono::seconds{c.id};
     218                 :             :             c.m_is_local = (c.id == 6);
     219                 :             :             c.m_network = (c.id == 5) ? NET_ONION : NET_IPV4;
     220                 :             :         },
     221                 :             :         /*protected_peer_ids=*/{0, 1, 5, 6},
     222                 :             :         /*unprotected_peer_ids=*/{2, 3, 4, 7},
     223                 :             :         random_context));
     224                 :             : 
     225                 :             :     // Combined test: expect having 3 localhost and 3 onion peers out of 12 to
     226                 :             :     // protect 2 localhost and 1 onion, plus 3 other peers, sorted by longest
     227                 :             :     // uptime; stable sort breaks ties with the array order of localhost first.
     228   [ +  +  +  +  :          19 :     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   [ +  +  +  -  :          16 :     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   [ +  +  +  +  :          24 :     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   [ +  +  +  -  :          20 :     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   [ +  +  +  -  :          20 :     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   [ +  +  +  -  :          16 :     BOOST_CHECK(IsProtected(
          +  -  +  -  +  
                -  +  - ]
     292                 :             :         num_peers, [](NodeEvictionCandidate& c) {
     293                 :             :             c.m_connected = std::chrono::seconds{c.id};
     294                 :             :             c.m_is_local = false;
     295                 :             :             if (c.id == 8 || c.id == 10) {
     296                 :             :                 c.m_network = NET_ONION;
     297                 :             :             } else if (c.id == 6 || c.id == 9 || c.id == 11 || c.id == 12) {
     298                 :             :                 c.m_network = NET_I2P;
     299                 :             :             } else {
     300                 :             :                 c.m_network = NET_IPV4;
     301                 :             :             }
     302                 :             :         },
     303                 :             :         /*protected_peer_ids=*/{0, 1, 2, 6, 8, 10},
     304                 :             :         /*unprotected_peer_ids=*/{3, 4, 5, 7, 9, 11},
     305                 :             :         random_context));
     306                 :             : 
     307                 :             :     // Tests with 3 networks...
     308                 :             : 
     309                 :             :     // Combined test: expect having 1 localhost, 1 I2P and 1 onion peer out of 4
     310                 :             :     // to protect 1 I2P, 0 localhost, 0 onion and 1 other peer (2 total), sorted
     311                 :             :     // by longest uptime; stable sort breaks tie with array order of I2P first.
     312   [ +  +  +  +  :           8 :     BOOST_CHECK(IsProtected(
          +  -  +  -  +  
                -  +  - ]
     313                 :             :         4, [](NodeEvictionCandidate& c) {
     314                 :             :             c.m_connected = std::chrono::seconds{c.id};
     315                 :             :             c.m_is_local = (c.id == 2);
     316                 :             :             if (c.id == 3) {
     317                 :             :                 c.m_network = NET_I2P;
     318                 :             :             } else if (c.id == 1) {
     319                 :             :                 c.m_network = NET_ONION;
     320                 :             :             } else {
     321                 :             :                 c.m_network = NET_IPV6;
     322                 :             :             }
     323                 :             :         },
     324                 :             :         /*protected_peer_ids=*/{0, 3},
     325                 :             :         /*unprotected_peer_ids=*/{1, 2},
     326                 :             :         random_context));
     327                 :             : 
     328                 :             :     // Combined test: expect having 1 localhost, 1 I2P and 1 onion peer out of 7
     329                 :             :     // to protect 1 I2P, 0 localhost, 0 onion and 2 other peers (3 total) sorted
     330                 :             :     // by longest uptime; stable sort breaks tie with array order of I2P first.
     331   [ +  +  +  +  :          11 :     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   [ +  +  +  +  :          12 :     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   [ +  +  +  +  :          20 :     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   [ +  +  +  +  :          28 :     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   [ +  +  +  +  :          28 :     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   [ +  +  +  +  :          28 :     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   [ +  +  +  +  :          28 :     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   [ +  +  +  +  :           9 :     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   [ +  +  +  +  :          11 :     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   [ +  +  +  +  :          12 :     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   [ +  +  +  +  :          20 :     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   [ +  +  +  +  :          28 :     BOOST_CHECK(IsProtected(
          +  +  +  +  +  
                      + ]
     554                 :             :         24, [](NodeEvictionCandidate& c) {
     555                 :             :             c.m_connected = std::chrono::seconds{c.id};
     556                 :             :             c.m_is_local = (c.id == 13);
     557                 :             :             if (c.id > 17) {
     558                 :             :                 c.m_network = NET_CJDNS;
     559                 :             :             } else if (c.id == 17) {
     560                 :             :                 c.m_network = NET_I2P;
     561                 :             :             } else if (c.id == 12 || c.id == 14 || c.id == 15 || c.id == 16) {
     562                 :             :                 c.m_network = NET_ONION;
     563                 :             :             } else {
     564                 :             :                 c.m_network = NET_IPV6;
     565                 :             :             }
     566                 :             :         },
     567                 :             :         /*protected_peer_ids=*/{0, 1, 2, 3, 4, 5, 12, 13, 14, 17, 18, 19},
     568                 :             :         /*unprotected_peer_ids=*/{6, 7, 8, 9, 10, 11, 15, 16, 20, 21, 22, 23},
     569                 :             :         random_context));
     570                 :           1 : }
     571                 :             : 
     572                 :             : // Returns true if any of the node ids in node_ids are selected for eviction.
     573                 :        1400 : bool IsEvicted(std::vector<NodeEvictionCandidate> candidates, const std::unordered_set<NodeId>& node_ids, FastRandomContext& random_context)
     574                 :             : {
     575                 :        1400 :     std::shuffle(candidates.begin(), candidates.end(), random_context);
     576                 :        1400 :     const std::optional<NodeId> evicted_node_id = SelectNodeToEvict(std::move(candidates));
     577         [ +  + ]:        1400 :     if (!evicted_node_id) {
     578                 :             :         return false;
     579                 :             :     }
     580                 :        1233 :     return node_ids.count(*evicted_node_id);
     581                 :             : }
     582                 :             : 
     583                 :             : // Create number_of_nodes random nodes, apply setup function candidate_setup_fn,
     584                 :             : // apply eviction logic and then return true if any of the node ids in node_ids
     585                 :             : // are selected for eviction.
     586                 :        1400 : bool IsEvicted(const int number_of_nodes, std::function<void(NodeEvictionCandidate&)> candidate_setup_fn, const std::unordered_set<NodeId>& node_ids, FastRandomContext& random_context)
     587                 :             : {
     588                 :        1400 :     std::vector<NodeEvictionCandidate> candidates = GetRandomNodeEvictionCandidates(number_of_nodes, random_context);
     589         [ +  + ]:      140700 :     for (NodeEvictionCandidate& candidate : candidates) {
     590         [ +  - ]:      139300 :         candidate_setup_fn(candidate);
     591                 :             :     }
     592   [ +  -  +  - ]:        1400 :     return IsEvicted(candidates, node_ids, random_context);
     593                 :        1400 : }
     594                 :             : 
     595   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(peer_eviction_test)
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
     596                 :             : {
     597                 :           1 :     FastRandomContext random_context{true};
     598                 :             : 
     599         [ +  + ]:         201 :     for (int number_of_nodes = 0; number_of_nodes < 200; ++number_of_nodes) {
     600                 :             :         // Four nodes with the highest keyed netgroup values should be
     601                 :             :         // protected from eviction.
     602   [ +  -  +  -  :       20500 :         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   [ +  -  +  -  :       20500 :         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   [ +  -  +  -  :       20500 :         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   [ +  +  +  -  :       20500 :         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   [ +  -  +  -  :       20500 :         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   [ +  +  +  -  :       20500 :         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   [ +  -  +  -  :       20500 :         BOOST_CHECK(!IsEvicted(
          +  -  +  -  +  
                      + ]
     657                 :             :                         number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
     658                 :             :                             candidate.nKeyedNetGroup = number_of_nodes - candidate.id;           // 4 protected
     659                 :             :                             candidate.m_min_ping_time = std::chrono::microseconds{candidate.id}; // 8 protected
     660                 :             :                             candidate.m_last_tx_time = std::chrono::seconds{number_of_nodes - candidate.id};    // 4 protected
     661                 :             :                             candidate.m_last_block_time = std::chrono::seconds{number_of_nodes - candidate.id}; // 4 protected
     662                 :             :                         },
     663                 :             :                         {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}, random_context));
     664                 :             : 
     665                 :             :         // An eviction is expected given >= 29 random eviction candidates. The eviction logic protects at most
     666                 :             :         // four peers by net group, eight by lowest ping time, four by last time of novel tx, up to eight non-tx-relay
     667                 :             :         // peers by last novel block time, and four more peers by last novel block time.
     668         [ +  + ]:         200 :         if (number_of_nodes >= 29) {
     669   [ +  -  +  -  :         342 :             BOOST_CHECK(SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context)));
             +  -  +  - ]
     670                 :             :         }
     671                 :             : 
     672                 :             :         // No eviction is expected given <= 20 random eviction candidates. The eviction logic protects at least
     673                 :             :         // four peers by net group, eight by lowest ping time, four by last time of novel tx and four peers by last
     674                 :             :         // novel block time.
     675         [ +  + ]:         200 :         if (number_of_nodes <= 20) {
     676   [ +  -  +  -  :          42 :             BOOST_CHECK(!SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context)));
             +  -  +  - ]
     677                 :             :         }
     678                 :             : 
     679                 :             :         // Cases left to test:
     680                 :             :         // * "If any remaining peers are preferred for eviction consider only them. [...]"
     681                 :             :         // * "Identify the network group with the most connections and youngest member. [...]"
     682                 :             :     }
     683                 :           1 : }
     684                 :             : 
     685                 :             : BOOST_AUTO_TEST_SUITE_END()
        

Generated by: LCOV version 2.0-1