LCOV - code coverage report
Current view: top level - src/test - net_peer_eviction_tests.cpp (source / functions) Hit Total Coverage
Test: coverage.lcov Lines: 222 228 97.4 %
Date: 2021-06-29 14:35:33 Functions: 6 6 100.0 %
Legend: Modified by patch:
Lines: hit not hit | Branches: + taken - not taken # not executed

Not modified by patch:
Lines: hit not hit | Branches: + taken - not taken # not executed
Branches: 22 24 91.7 %

           Branch data     Line data    Source code
#       1                 :            : // Copyright (c) 2021 The Bitcoin Core developers
#       2                 :            : // Distributed under the MIT software license, see the accompanying
#       3                 :            : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
#       4                 :            : 
#       5                 :            : #include <net.h>
#       6                 :            : #include <test/util/setup_common.h>
#       7                 :            : 
#       8                 :            : #include <boost/test/unit_test.hpp>
#       9                 :            : 
#      10                 :            : #include <algorithm>
#      11                 :            : #include <functional>
#      12                 :            : #include <optional>
#      13                 :            : #include <unordered_set>
#      14                 :            : #include <vector>
#      15                 :            : 
#      16                 :            : BOOST_FIXTURE_TEST_SUITE(net_peer_eviction_tests, BasicTestingSetup)
#      17                 :            : 
#      18                 :            : namespace {
#      19                 :            : constexpr int NODE_EVICTION_TEST_ROUNDS{10};
#      20                 :            : constexpr int NODE_EVICTION_TEST_UP_TO_N_NODES{200};
#      21                 :            : } // namespace
#      22                 :            : 
#      23                 :            : std::vector<NodeEvictionCandidate> GetRandomNodeEvictionCandidates(const int n_candidates, FastRandomContext& random_context)
#      24                 :      31864 : {
#      25                 :      31864 :     std::vector<NodeEvictionCandidate> candidates;
#      26         [ +  + ]:    3212220 :     for (int id = 0; id < n_candidates; ++id) {
#      27                 :    3180356 :         candidates.push_back({
#      28                 :    3180356 :             /* id */ id,
#      29                 :    3180356 :             /* nTimeConnected */ static_cast<int64_t>(random_context.randrange(100)),
#      30                 :    3180356 :             /* m_min_ping_time */ std::chrono::microseconds{random_context.randrange(100)},
#      31                 :    3180356 :             /* nLastBlockTime */ static_cast<int64_t>(random_context.randrange(100)),
#      32                 :    3180356 :             /* nLastTXTime */ static_cast<int64_t>(random_context.randrange(100)),
#      33                 :    3180356 :             /* fRelevantServices */ random_context.randbool(),
#      34                 :    3180356 :             /* fRelayTxes */ random_context.randbool(),
#      35                 :    3180356 :             /* fBloomFilter */ random_context.randbool(),
#      36                 :    3180356 :             /* nKeyedNetGroup */ random_context.randrange(100),
#      37                 :    3180356 :             /* prefer_evict */ random_context.randbool(),
#      38                 :    3180356 :             /* m_is_local */ random_context.randbool(),
#      39                 :    3180356 :             /* m_is_onion */ random_context.randbool(),
#      40                 :    3180356 :         });
#      41                 :    3180356 :     }
#      42                 :      31864 :     return candidates;
#      43                 :      31864 : }
#      44                 :            : 
#      45                 :            : // Create `num_peers` random nodes, apply setup function `candidate_setup_fn`,
#      46                 :            : // call ProtectEvictionCandidatesByRatio() to apply protection logic, and then
#      47                 :            : // return true if all of `protected_peer_ids` and none of `unprotected_peer_ids`
#      48                 :            : // are protected from eviction, i.e. removed from the eviction candidates.
#      49                 :            : bool IsProtected(int num_peers,
#      50                 :            :                  std::function<void(NodeEvictionCandidate&)> candidate_setup_fn,
#      51                 :            :                  const std::unordered_set<NodeId>& protected_peer_ids,
#      52                 :            :                  const std::unordered_set<NodeId>& unprotected_peer_ids,
#      53                 :            :                  FastRandomContext& random_context)
#      54                 :         24 : {
#      55                 :         24 :     std::vector<NodeEvictionCandidate> candidates{GetRandomNodeEvictionCandidates(num_peers, random_context)};
#      56         [ +  + ]:        276 :     for (NodeEvictionCandidate& candidate : candidates) {
#      57                 :        276 :         candidate_setup_fn(candidate);
#      58                 :        276 :     }
#      59                 :         24 :     Shuffle(candidates.begin(), candidates.end(), random_context);
#      60                 :            : 
#      61                 :         24 :     const size_t size{candidates.size()};
#      62                 :         24 :     const size_t expected{size - size / 2}; // Expect half the candidates will be protected.
#      63                 :         24 :     ProtectEvictionCandidatesByRatio(candidates);
#      64                 :         24 :     BOOST_CHECK_EQUAL(candidates.size(), expected);
#      65                 :            : 
#      66                 :         24 :     size_t unprotected_count{0};
#      67         [ +  + ]:        138 :     for (const NodeEvictionCandidate& candidate : candidates) {
#      68         [ -  + ]:        138 :         if (protected_peer_ids.count(candidate.id)) {
#      69                 :            :             // this peer should have been removed from the eviction candidates
#      70                 :          0 :             BOOST_TEST_MESSAGE(strprintf("expected candidate to be protected: %d", candidate.id));
#      71                 :          0 :             return false;
#      72                 :          0 :         }
#      73         [ +  + ]:        138 :         if (unprotected_peer_ids.count(candidate.id)) {
#      74                 :            :             // this peer remains in the eviction candidates, as expected
#      75                 :        114 :             ++unprotected_count;
#      76                 :        114 :         }
#      77                 :        138 :     }
#      78                 :            : 
#      79                 :         24 :     const bool is_protected{unprotected_count == unprotected_peer_ids.size()};
#      80         [ -  + ]:         24 :     if (!is_protected) {
#      81                 :          0 :         BOOST_TEST_MESSAGE(strprintf("unprotected: expected %d, actual %d",
#      82                 :          0 :                                      unprotected_peer_ids.size(), unprotected_count));
#      83                 :          0 :     }
#      84                 :         24 :     return is_protected;
#      85                 :         24 : }
#      86                 :            : 
#      87                 :            : BOOST_AUTO_TEST_CASE(peer_protection_test)
#      88                 :          2 : {
#      89                 :          2 :     FastRandomContext random_context{true};
#      90                 :          2 :     int num_peers{12};
#      91                 :            : 
#      92                 :            :     // Expect half of the peers with greatest uptime (the lowest nTimeConnected)
#      93                 :            :     // to be protected from eviction.
#      94                 :          2 :     BOOST_CHECK(IsProtected(
#      95                 :          2 :         num_peers, [](NodeEvictionCandidate& c) {
#      96                 :          2 :             c.nTimeConnected = c.id;
#      97                 :          2 :             c.m_is_onion = c.m_is_local = false;
#      98                 :          2 :         },
#      99                 :          2 :         /* protected_peer_ids */ {0, 1, 2, 3, 4, 5},
#     100                 :          2 :         /* unprotected_peer_ids */ {6, 7, 8, 9, 10, 11},
#     101                 :          2 :         random_context));
#     102                 :            : 
#     103                 :            :     // Verify in the opposite direction.
#     104                 :          2 :     BOOST_CHECK(IsProtected(
#     105                 :          2 :         num_peers, [num_peers](NodeEvictionCandidate& c) {
#     106                 :          2 :             c.nTimeConnected = num_peers - c.id;
#     107                 :          2 :             c.m_is_onion = c.m_is_local = false;
#     108                 :          2 :         },
#     109                 :          2 :         /* protected_peer_ids */ {6, 7, 8, 9, 10, 11},
#     110                 :          2 :         /* unprotected_peer_ids */ {0, 1, 2, 3, 4, 5},
#     111                 :          2 :         random_context));
#     112                 :            : 
#     113                 :            :     // Test protection of onion and localhost peers...
#     114                 :            : 
#     115                 :            :     // Expect 1/4 onion peers to be protected from eviction,
#     116                 :            :     // independently of other characteristics.
#     117                 :          2 :     BOOST_CHECK(IsProtected(
#     118                 :          2 :         num_peers, [](NodeEvictionCandidate& c) {
#     119                 :          2 :             c.m_is_onion = (c.id == 3 || c.id == 8 || c.id == 9);
#     120                 :          2 :         },
#     121                 :          2 :         /* protected_peer_ids */ {3, 8, 9},
#     122                 :          2 :         /* unprotected_peer_ids */ {},
#     123                 :          2 :         random_context));
#     124                 :            : 
#     125                 :            :     // Expect 1/4 onion peers and 1/4 of the others to be protected
#     126                 :            :     // from eviction, sorted by longest uptime (lowest nTimeConnected).
#     127                 :          2 :     BOOST_CHECK(IsProtected(
#     128                 :          2 :         num_peers, [](NodeEvictionCandidate& c) {
#     129                 :          2 :             c.nTimeConnected = c.id;
#     130                 :          2 :             c.m_is_local = false;
#     131                 :          2 :             c.m_is_onion = (c.id == 3 || c.id > 7);
#     132                 :          2 :         },
#     133                 :          2 :         /* protected_peer_ids */ {0, 1, 2, 3, 8, 9},
#     134                 :          2 :         /* unprotected_peer_ids */ {4, 5, 6, 7, 10, 11},
#     135                 :          2 :         random_context));
#     136                 :            : 
#     137                 :            :     // Expect 1/4 localhost peers to be protected from eviction,
#     138                 :            :     // if no onion peers.
#     139                 :          2 :     BOOST_CHECK(IsProtected(
#     140                 :          2 :         num_peers, [](NodeEvictionCandidate& c) {
#     141                 :          2 :             c.m_is_onion = false;
#     142                 :          2 :             c.m_is_local = (c.id == 1 || c.id == 9 || c.id == 11);
#     143                 :          2 :         },
#     144                 :          2 :         /* protected_peer_ids */ {1, 9, 11},
#     145                 :          2 :         /* unprotected_peer_ids */ {},
#     146                 :          2 :         random_context));
#     147                 :            : 
#     148                 :            :     // Expect 1/4 localhost peers and 1/4 of the other peers to be protected,
#     149                 :            :     // sorted by longest uptime (lowest nTimeConnected), if no onion peers.
#     150                 :          2 :     BOOST_CHECK(IsProtected(
#     151                 :          2 :         num_peers, [](NodeEvictionCandidate& c) {
#     152                 :          2 :             c.nTimeConnected = c.id;
#     153                 :          2 :             c.m_is_onion = false;
#     154                 :          2 :             c.m_is_local = (c.id > 6);
#     155                 :          2 :         },
#     156                 :          2 :         /* protected_peer_ids */ {0, 1, 2, 7, 8, 9},
#     157                 :          2 :         /* unprotected_peer_ids */ {3, 4, 5, 6, 10, 11},
#     158                 :          2 :         random_context));
#     159                 :            : 
#     160                 :            :     // Combined test: expect 1/4 onion and 2 localhost peers to be protected
#     161                 :            :     // from eviction, sorted by longest uptime.
#     162                 :          2 :     BOOST_CHECK(IsProtected(
#     163                 :          2 :         num_peers, [](NodeEvictionCandidate& c) {
#     164                 :          2 :             c.nTimeConnected = c.id;
#     165                 :          2 :             c.m_is_onion = (c.id == 0 || c.id == 5 || c.id == 10);
#     166                 :          2 :             c.m_is_local = (c.id == 1 || c.id == 9 || c.id == 11);
#     167                 :          2 :         },
#     168                 :          2 :         /* protected_peer_ids */ {0, 1, 2, 5, 9, 10},
#     169                 :          2 :         /* unprotected_peer_ids */ {3, 4, 6, 7, 8, 11},
#     170                 :          2 :         random_context));
#     171                 :            : 
#     172                 :            :     // Combined test: expect having only 1 onion to allow allocating the
#     173                 :            :     // remaining 2 of the 1/4 to localhost peers, sorted by longest uptime.
#     174                 :          2 :     BOOST_CHECK(IsProtected(
#     175                 :          2 :         num_peers + 4, [](NodeEvictionCandidate& c) {
#     176                 :          2 :             c.nTimeConnected = c.id;
#     177                 :          2 :             c.m_is_onion = (c.id == 15);
#     178                 :          2 :             c.m_is_local = (c.id > 6 && c.id < 11);
#     179                 :          2 :         },
#     180                 :          2 :         /* protected_peer_ids */ {0, 1, 2, 3, 7, 8, 9, 15},
#     181                 :          2 :         /* unprotected_peer_ids */ {4, 5, 6, 10, 11, 12, 13, 14},
#     182                 :          2 :         random_context));
#     183                 :            : 
#     184                 :            :     // Combined test: expect 2 onions (< 1/4) to allow allocating the minimum 2
#     185                 :            :     // localhost peers, sorted by longest uptime.
#     186                 :          2 :     BOOST_CHECK(IsProtected(
#     187                 :          2 :         num_peers, [](NodeEvictionCandidate& c) {
#     188                 :          2 :             c.nTimeConnected = c.id;
#     189                 :          2 :             c.m_is_onion = (c.id == 7 || c.id == 9);
#     190                 :          2 :             c.m_is_local = (c.id == 6 || c.id == 11);
#     191                 :          2 :         },
#     192                 :          2 :         /* protected_peer_ids */ {0, 1, 6, 7, 9, 11},
#     193                 :          2 :         /* unprotected_peer_ids */ {2, 3, 4, 5, 8, 10},
#     194                 :          2 :         random_context));
#     195                 :            : 
#     196                 :            :     // Combined test: when > 1/4, expect max 1/4 onion and 2 localhost peers
#     197                 :            :     // to be protected from eviction, sorted by longest uptime.
#     198                 :          2 :     BOOST_CHECK(IsProtected(
#     199                 :          2 :         num_peers, [](NodeEvictionCandidate& c) {
#     200                 :          2 :             c.nTimeConnected = c.id;
#     201                 :          2 :             c.m_is_onion = (c.id > 3 && c.id < 8);
#     202                 :          2 :             c.m_is_local = (c.id > 7);
#     203                 :          2 :         },
#     204                 :          2 :         /* protected_peer_ids */ {0, 4, 5, 6, 8, 9},
#     205                 :          2 :         /* unprotected_peer_ids */ {1, 2, 3, 7, 10, 11},
#     206                 :          2 :         random_context));
#     207                 :            : 
#     208                 :            :     // Combined test: idem > 1/4 with only 8 peers: expect 2 onion and 2
#     209                 :            :     // localhost peers (1/4 + 2) to be protected, sorted by longest uptime.
#     210                 :          2 :     BOOST_CHECK(IsProtected(
#     211                 :          2 :         8, [](NodeEvictionCandidate& c) {
#     212                 :          2 :             c.nTimeConnected = c.id;
#     213                 :          2 :             c.m_is_onion = (c.id > 1 && c.id < 5);
#     214                 :          2 :             c.m_is_local = (c.id > 4);
#     215                 :          2 :         },
#     216                 :          2 :         /* protected_peer_ids */ {2, 3, 5, 6},
#     217                 :          2 :         /* unprotected_peer_ids */ {0, 1, 4, 7},
#     218                 :          2 :         random_context));
#     219                 :            : 
#     220                 :            :     // Combined test: idem > 1/4 with only 6 peers: expect 1 onion peer and no
#     221                 :            :     // localhost peers (1/4 + 0) to be protected, sorted by longest uptime.
#     222                 :          2 :     BOOST_CHECK(IsProtected(
#     223                 :          2 :         6, [](NodeEvictionCandidate& c) {
#     224                 :          2 :             c.nTimeConnected = c.id;
#     225                 :          2 :             c.m_is_onion = (c.id == 4 || c.id == 5);
#     226                 :          2 :             c.m_is_local = (c.id == 3);
#     227                 :          2 :         },
#     228                 :          2 :         /* protected_peer_ids */ {0, 1, 4},
#     229                 :          2 :         /* unprotected_peer_ids */ {2, 3, 5},
#     230                 :          2 :         random_context));
#     231                 :          2 : }
#     232                 :            : 
#     233                 :            : // Returns true if any of the node ids in node_ids are selected for eviction.
#     234                 :            : bool IsEvicted(std::vector<NodeEvictionCandidate> candidates, const std::unordered_set<NodeId>& node_ids, FastRandomContext& random_context)
#     235                 :      28000 : {
#     236                 :      28000 :     Shuffle(candidates.begin(), candidates.end(), random_context);
#     237                 :      28000 :     const std::optional<NodeId> evicted_node_id = SelectNodeToEvict(std::move(candidates));
#     238         [ +  + ]:      28000 :     if (!evicted_node_id) {
#     239                 :       3282 :         return false;
#     240                 :       3282 :     }
#     241                 :      24718 :     return node_ids.count(*evicted_node_id);
#     242                 :      24718 : }
#     243                 :            : 
#     244                 :            : // Create number_of_nodes random nodes, apply setup function candidate_setup_fn,
#     245                 :            : // apply eviction logic and then return true if any of the node ids in node_ids
#     246                 :            : // are selected for eviction.
#     247                 :            : bool IsEvicted(const int number_of_nodes, std::function<void(NodeEvictionCandidate&)> candidate_setup_fn, const std::unordered_set<NodeId>& node_ids, FastRandomContext& random_context)
#     248                 :      28000 : {
#     249                 :      28000 :     std::vector<NodeEvictionCandidate> candidates = GetRandomNodeEvictionCandidates(number_of_nodes, random_context);
#     250         [ +  + ]:    2786000 :     for (NodeEvictionCandidate& candidate : candidates) {
#     251                 :    2786000 :         candidate_setup_fn(candidate);
#     252                 :    2786000 :     }
#     253                 :      28000 :     return IsEvicted(candidates, node_ids, random_context);
#     254                 :      28000 : }
#     255                 :            : 
#     256                 :            : BOOST_AUTO_TEST_CASE(peer_eviction_test)
#     257                 :          2 : {
#     258                 :          2 :     FastRandomContext random_context{true};
#     259                 :            : 
#     260         [ +  + ]:         22 :     for (int i = 0; i < NODE_EVICTION_TEST_ROUNDS; ++i) {
#     261         [ +  + ]:       4020 :         for (int number_of_nodes = 0; number_of_nodes < NODE_EVICTION_TEST_UP_TO_N_NODES; ++number_of_nodes) {
#     262                 :            :             // Four nodes with the highest keyed netgroup values should be
#     263                 :            :             // protected from eviction.
#     264                 :       4000 :             BOOST_CHECK(!IsEvicted(
#     265                 :       4000 :                 number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
#     266                 :       4000 :                     candidate.nKeyedNetGroup = number_of_nodes - candidate.id;
#     267                 :       4000 :                 },
#     268                 :       4000 :                 {0, 1, 2, 3}, random_context));
#     269                 :            : 
#     270                 :            :             // Eight nodes with the lowest minimum ping time should be protected
#     271                 :            :             // from eviction.
#     272                 :       4000 :             BOOST_CHECK(!IsEvicted(
#     273                 :       4000 :                 number_of_nodes, [](NodeEvictionCandidate& candidate) {
#     274                 :       4000 :                     candidate.m_min_ping_time = std::chrono::microseconds{candidate.id};
#     275                 :       4000 :                 },
#     276                 :       4000 :                 {0, 1, 2, 3, 4, 5, 6, 7}, random_context));
#     277                 :            : 
#     278                 :            :             // Four nodes that most recently sent us novel transactions accepted
#     279                 :            :             // into our mempool should be protected from eviction.
#     280                 :       4000 :             BOOST_CHECK(!IsEvicted(
#     281                 :       4000 :                 number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
#     282                 :       4000 :                     candidate.nLastTXTime = number_of_nodes - candidate.id;
#     283                 :       4000 :                 },
#     284                 :       4000 :                 {0, 1, 2, 3}, random_context));
#     285                 :            : 
#     286                 :            :             // Up to eight non-tx-relay peers that most recently sent us novel
#     287                 :            :             // blocks should be protected from eviction.
#     288                 :       4000 :             BOOST_CHECK(!IsEvicted(
#     289                 :       4000 :                 number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
#     290                 :       4000 :                     candidate.nLastBlockTime = number_of_nodes - candidate.id;
#     291                 :       4000 :                     if (candidate.id <= 7) {
#     292                 :       4000 :                         candidate.fRelayTxes = false;
#     293                 :       4000 :                         candidate.fRelevantServices = true;
#     294                 :       4000 :                     }
#     295                 :       4000 :                 },
#     296                 :       4000 :                 {0, 1, 2, 3, 4, 5, 6, 7}, random_context));
#     297                 :            : 
#     298                 :            :             // Four peers that most recently sent us novel blocks should be
#     299                 :            :             // protected from eviction.
#     300                 :       4000 :             BOOST_CHECK(!IsEvicted(
#     301                 :       4000 :                 number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
#     302                 :       4000 :                     candidate.nLastBlockTime = number_of_nodes - candidate.id;
#     303                 :       4000 :                 },
#     304                 :       4000 :                 {0, 1, 2, 3}, random_context));
#     305                 :            : 
#     306                 :            :             // Combination of the previous two tests.
#     307                 :       4000 :             BOOST_CHECK(!IsEvicted(
#     308                 :       4000 :                 number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
#     309                 :       4000 :                     candidate.nLastBlockTime = number_of_nodes - candidate.id;
#     310                 :       4000 :                     if (candidate.id <= 7) {
#     311                 :       4000 :                         candidate.fRelayTxes = false;
#     312                 :       4000 :                         candidate.fRelevantServices = true;
#     313                 :       4000 :                     }
#     314                 :       4000 :                 },
#     315                 :       4000 :                 {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, random_context));
#     316                 :            : 
#     317                 :            :             // Combination of all tests above.
#     318                 :       4000 :             BOOST_CHECK(!IsEvicted(
#     319                 :       4000 :                 number_of_nodes, [number_of_nodes](NodeEvictionCandidate& candidate) {
#     320                 :       4000 :                     candidate.nKeyedNetGroup = number_of_nodes - candidate.id;           // 4 protected
#     321                 :       4000 :                     candidate.m_min_ping_time = std::chrono::microseconds{candidate.id}; // 8 protected
#     322                 :       4000 :                     candidate.nLastTXTime = number_of_nodes - candidate.id;              // 4 protected
#     323                 :       4000 :                     candidate.nLastBlockTime = number_of_nodes - candidate.id;           // 4 protected
#     324                 :       4000 :                 },
#     325                 :       4000 :                 {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}, random_context));
#     326                 :            : 
#     327                 :            :             // An eviction is expected given >= 29 random eviction candidates. The eviction logic protects at most
#     328                 :            :             // four peers by net group, eight by lowest ping time, four by last time of novel tx, up to eight non-tx-relay
#     329                 :            :             // peers by last novel block time, and four more peers by last novel block time.
#     330         [ +  + ]:       4000 :             if (number_of_nodes >= 29) {
#     331                 :       3420 :                 BOOST_CHECK(SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context)));
#     332                 :       3420 :             }
#     333                 :            : 
#     334                 :            :             // No eviction is expected given <= 20 random eviction candidates. The eviction logic protects at least
#     335                 :            :             // four peers by net group, eight by lowest ping time, four by last time of novel tx and four peers by last
#     336                 :            :             // novel block time.
#     337         [ +  + ]:       4000 :             if (number_of_nodes <= 20) {
#     338                 :        420 :                 BOOST_CHECK(!SelectNodeToEvict(GetRandomNodeEvictionCandidates(number_of_nodes, random_context)));
#     339                 :        420 :             }
#     340                 :            : 
#     341                 :            :             // Cases left to test:
#     342                 :            :             // * "If any remaining peers are preferred for eviction consider only them. [...]"
#     343                 :            :             // * "Identify the network group with the most connections and youngest member. [...]"
#     344                 :       4000 :         }
#     345                 :         20 :     }
#     346                 :          2 : }
#     347                 :            : 
#     348                 :            : BOOST_AUTO_TEST_SUITE_END()

Generated by: LCOV version 1.14