LCOV - code coverage report
Current view: top level - src/test/fuzz - private_broadcast.cpp (source / functions) Coverage Total Hit
Test: fuzz_coverage.info Lines: 100.0 % 114 114
Test Date: 2026-07-21 06:28:42 Functions: 100.0 % 13 13
Branches: 65.2 % 132 86

             Branch data     Line data    Source code
       1                 :             : // Copyright (c) 2025-present 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 <consensus/tx_check.h>
       6                 :             : #include <consensus/validation.h>
       7                 :             : #include <net.h>
       8                 :             : #include <primitives/transaction.h>
       9                 :             : #include <private_broadcast.h>
      10                 :             : #include <test/fuzz/FuzzedDataProvider.h>
      11                 :             : #include <test/fuzz/fuzz.h>
      12                 :             : #include <test/fuzz/util.h>
      13                 :             : #include <test/fuzz/util/net.h>
      14                 :             : #include <test/util/setup_common.h>
      15                 :             : #include <test/util/time.h>
      16                 :             : #include <util/overflow.h>
      17                 :             : #include <util/time.h>
      18                 :             : 
      19                 :             : #include <unordered_set>
      20                 :             : 
      21                 :             : struct CTransactionRefHash {
      22                 :       49451 :     size_t operator()(const CTransactionRef& tx) const
      23                 :             :     {
      24         [ +  + ]:       49451 :         return static_cast<size_t>(tx->GetWitnessHash().ToUint256().GetUint64(0));
      25                 :             :     }
      26                 :             : };
      27                 :             : 
      28                 :             : struct CTransactionRefComp {
      29                 :       38602 :     bool operator()(const CTransactionRef& a, const CTransactionRef& b) const
      30                 :             :     {
      31         [ -  - ]:       38602 :         return a->GetWitnessHash() == b->GetWitnessHash();
      32                 :             :     }
      33                 :             : };
      34                 :             : 
      35         [ +  - ]:         972 : FUZZ_TARGET(private_broadcast)
      36                 :             : {
      37                 :         498 :     SeedRandomStateForTest(SeedRand::ZEROS);
      38                 :         498 :     FuzzedDataProvider fdp(buffer.data(), buffer.size());
      39                 :         498 :     FakeNodeClock clock_ctx{ConsumeTime(fdp)};
      40                 :             : 
      41                 :         498 :     const size_t cap{fdp.ConsumeIntegralInRange<size_t>(1, 12)};
      42                 :         498 :     PrivateBroadcast pb{cap};
      43                 :             : 
      44                 :             :     // Random transaction that the test generated and passed to Add(). Trimmed when Remove() is called.
      45                 :             :     // The values are the number of times a transaction was picked for sending.
      46                 :         498 :     std::unordered_map<CTransactionRef, size_t, CTransactionRefHash, CTransactionRefComp> transactions;
      47                 :             : 
      48                 :             :     // Ids of nodes that were passed to PickTxForSend(). Trimmed when Remove() is called.
      49                 :         498 :     std::unordered_set<NodeId> nodes_sent_to;
      50                 :             : 
      51                 :             :     // A subset of `nodes_sent_to`, node ids passed to NodeConfirmedReception(). Trimmed when Remove() is called.
      52                 :         498 :     std::unordered_set<NodeId> nodes_that_confirmed_reception;
      53                 :             : 
      54                 :         498 :     NodeId next_nodeid{0}; // Generate unique node ids.
      55                 :             : 
      56                 :       53742 :     const auto ExistentOrNewNodeId = [&next_nodeid, &fdp](){
      57   [ +  +  +  + ]:       53244 :         if (next_nodeid == 0 || fdp.ConsumeBool()) {
      58                 :       41635 :             return next_nodeid++;
      59                 :             :         }
      60                 :       11609 :         return fdp.ConsumeIntegralInRange<NodeId>(0, next_nodeid - 1);
      61                 :         498 :     };
      62                 :             : 
      63   [ +  +  +  + ]:      123368 :     LIMITED_WHILE (fdp.ConsumeBool(), 10000) {
      64         [ +  - ]:      122870 :         CallOneOf(
      65                 :             :             fdp,
      66                 :        8582 :             [&] { // Add()
      67                 :        8582 :                 CTransactionRef tx;
      68   [ +  +  +  + ]:        8582 :                 if (transactions.empty() || fdp.ConsumeBool()) {
      69   [ +  -  -  + ]:       21825 :                     tx = MakeTransactionRef(ConsumeTransaction(fdp, std::nullopt));
      70                 :             :                 } else {
      71                 :        1307 :                     tx = PickIterator(fdp, transactions)->first;
      72                 :             :                 }
      73                 :             : 
      74                 :        8582 :                 const bool present_before{transactions.contains(tx)};
      75         [ +  - ]:        8582 :                 const auto res{pb.Add(tx)};
      76         [ +  + ]:        8582 :                 if (present_before) {
      77         [ -  + ]:        2772 :                     Assert(res == PrivateBroadcast::AddResult::AlreadyPresent);
      78         [ +  + ]:        5810 :                 } else if (transactions.size() >= cap) {
      79   [ -  +  +  - ]:        8582 :                     Assert(res == PrivateBroadcast::AddResult::QueueFull);
      80                 :             :                 } else {
      81         [ -  + ]:        5039 :                     Assert(res == PrivateBroadcast::AddResult::Added);
      82         [ +  - ]:        5039 :                     transactions.emplace(tx, 0);
      83                 :             :                 }
      84                 :        8582 :             },
      85                 :       24516 :             [&] { // Remove()
      86         [ +  + ]:       24516 :                 if (transactions.empty()) {
      87                 :             :                     return;
      88                 :             :                 }
      89                 :        4157 :                 const auto transactions_it{PickIterator(fdp, transactions)};
      90                 :        4157 :                 const CTransactionRef& tx{transactions_it->first};
      91                 :             : 
      92                 :        4157 :                 size_t num_nodes_that_confirmed_tx{0};
      93                 :             : 
      94                 :             :                 // Remove relevant entries from nodes_sent_to[] and nodes_that_confirmed_reception[] if any.
      95         [ +  + ]:       21952 :                 for (auto it = nodes_sent_to.begin(); it != nodes_sent_to.end();) {
      96                 :       17795 :                     const NodeId nodeid{*it};
      97                 :       17795 :                     const auto opt_tx_for_node{pb.GetTxForNode(nodeid)};
      98   [ +  +  +  - ]:       17795 :                     if (opt_tx_for_node.has_value() && opt_tx_for_node.value() == tx) {
      99                 :        7872 :                         it = nodes_sent_to.erase(it);
     100         [ +  + ]:        7872 :                         if (nodes_that_confirmed_reception.erase(nodeid) > 0) {
     101                 :         676 :                             ++num_nodes_that_confirmed_tx;
     102                 :             :                         }
     103                 :             :                     } else {
     104                 :        9923 :                         ++it;
     105                 :             :                     }
     106                 :       17795 :                 }
     107                 :             : 
     108                 :        4157 :                 const auto opt_num_confirmed{pb.Remove(tx)};
     109                 :             : 
     110         [ -  + ]:        4157 :                 Assert(opt_num_confirmed.has_value());
     111   [ +  -  -  + ]:        4157 :                 Assert(opt_num_confirmed.value() == num_nodes_that_confirmed_tx);
     112         [ -  + ]:        4157 :                 Assert(!pb.Remove(tx).has_value());
     113                 :        4157 :                 transactions.erase(transactions_it);
     114                 :             :             },
     115                 :       21280 :             [&] { // PickTxForSend()
     116                 :             :                 // Only give pristine node ids to PickTxForSend() as required.
     117                 :       21280 :                 const NodeId will_send_to_nodeid{next_nodeid++};
     118                 :       21280 :                 const CService will_send_to_address{ConsumeService(fdp)};
     119                 :             : 
     120         [ +  - ]:       21280 :                 const auto opt_tx{pb.PickTxForSend(will_send_to_nodeid, will_send_to_address)};
     121                 :             : 
     122         [ +  + ]:       21280 :                 if (opt_tx.has_value()) {
     123         [ -  + ]:       13056 :                     Assert(transactions.contains(opt_tx.value()));
     124                 :             : 
     125                 :             :                     // "Number of times picked for sending" is the primary key in Priority's comparison
     126                 :             :                     // (fewest sends = highest priority), so PickTxForSend() must return a transaction
     127                 :             :                     // with the minimum send count of any in the queue. Ties are broken by state we
     128                 :             :                     // don't model, so only check this key.
     129         [ +  - ]:       13056 :                     const size_t min_picked{std::ranges::min_element(
     130         [ +  + ]:       15621 :                         transactions, {}, [](const auto& el) { return el.second; })->second};
     131         [ +  - ]:       13056 :                     const auto picked_it{transactions.find(opt_tx.value())};
     132   [ +  -  -  + ]:       26112 :                     Assert(picked_it != transactions.end());
     133         [ -  + ]:       13056 :                     Assert(picked_it->second == min_picked); // picked the least-sent transaction
     134         [ +  - ]:       13056 :                     ++picked_it->second; // PickTxForSend() recorded exactly one send
     135                 :             : 
     136   [ +  -  -  + ]:       13056 :                     const auto& [_, inserted]{nodes_sent_to.emplace(will_send_to_nodeid)};
     137         [ -  + ]:       13056 :                     Assert(inserted);
     138                 :             :                 } else {
     139         [ -  + ]:       21280 :                     Assert(transactions.empty());
     140                 :             :                 }
     141                 :       21280 :             },
     142                 :       23456 :             [&] { // GetTxForNode()
     143                 :       23456 :                 const NodeId nodeid{ExistentOrNewNodeId()};
     144                 :             : 
     145                 :       23456 :                 const auto opt_tx{pb.GetTxForNode(nodeid)};
     146                 :             : 
     147         [ +  + ]:       23456 :                 if (nodes_sent_to.contains(nodeid)) {
     148         [ -  + ]:         625 :                     Assert(opt_tx.has_value());
     149   [ +  -  -  + ]:         625 :                     Assert(transactions.contains(opt_tx.value()));
     150                 :             :                 } else {
     151         [ -  + ]:       23456 :                     Assert(!opt_tx.has_value());
     152                 :             :                 }
     153                 :       23456 :             },
     154                 :        7071 :             [&] { // NodeConfirmedReception()
     155                 :        7071 :                 const NodeId nodeid{ExistentOrNewNodeId()};
     156                 :             : 
     157                 :        7071 :                 pb.NodeConfirmedReception(nodeid);
     158                 :             : 
     159         [ +  + ]:        7071 :                 if (nodes_sent_to.contains(nodeid)) {
     160                 :             :                     // nodeid was previously passed to PickTxForSend(), so NodeConfirmedReception()
     161                 :             :                     // must have changed the internal state. Remember this to later check that
     162                 :             :                     // DidNodeConfirmReception() works correctly.
     163                 :        2725 :                     nodes_that_confirmed_reception.emplace(nodeid);
     164                 :             :                 }
     165                 :        7071 :             },
     166                 :       22717 :             [&] { // DidNodeConfirmReception()
     167                 :       22717 :                 const NodeId nodeid{ExistentOrNewNodeId()};
     168                 :             : 
     169                 :       22717 :                 const bool confirmed{pb.DidNodeConfirmReception(nodeid)};
     170                 :             : 
     171         [ +  + ]:       22717 :                 if (nodes_that_confirmed_reception.contains(nodeid)) {
     172         [ -  + ]:         772 :                     Assert(confirmed);
     173                 :             :                 } else {
     174         [ -  + ]:       21945 :                     Assert(!confirmed);
     175                 :             :                 }
     176                 :       22717 :             },
     177                 :        1176 :             [&] { // HavePendingTransactions()
     178         [ +  + ]:        1176 :                 if (pb.HavePendingTransactions()) {
     179         [ -  + ]:         841 :                     Assert(!transactions.empty());
     180                 :             :                 } else {
     181         [ -  + ]:         335 :                     Assert(transactions.empty());
     182                 :             :                 }
     183                 :        1176 :             },
     184                 :        7830 :             [&] { // GetStale()
     185                 :        7830 :                 const auto stale{pb.GetStale()};
     186                 :             : 
     187   [ -  +  -  + ]:        7830 :                 Assert(stale.size() <= transactions.size());
     188                 :             : 
     189         [ +  + ]:       13430 :                 for (const auto& stale_tx : stale) {
     190         [ -  + ]:        5600 :                     Assert(transactions.contains(stale_tx));
     191                 :             :                 }
     192                 :        7830 :             },
     193                 :        1700 :             [&] { // GetBroadcastInfo()
     194                 :        1700 :                 const auto all_broadcast_info{pb.GetBroadcastInfo()};
     195                 :             : 
     196   [ -  +  -  + ]:        1700 :                 Assert(all_broadcast_info.size() == transactions.size());
     197                 :             : 
     198         [ +  + ]:        5193 :                 for (const auto& info : all_broadcast_info) {
     199                 :        3493 :                     const auto it{transactions.find(info.tx)};
     200   [ +  -  -  + ]:        6986 :                     Assert(it != transactions.end());
     201   [ -  +  -  + ]:        3493 :                     Assert(info.peers.size() == it->second); // exactly the sends we recorded
     202                 :             :                 }
     203                 :        1700 :             },
     204                 :        4542 :             [&] {
     205                 :        4542 :                 clock_ctx.set(ConsumeTime(fdp));
     206                 :        4542 :             });
     207                 :             :     }
     208                 :         498 : }
        

Generated by: LCOV version 2.0-1