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 % 131 131
Test Date: 2026-08-29 05:53:03 Functions: 100.0 % 13 13
Branches: 67.1 % 170 114

             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 <algorithm>
      20                 :             : #include <ranges>
      21                 :             : #include <unordered_map>
      22                 :             : #include <unordered_set>
      23                 :             : 
      24                 :             : struct CTransactionRefHash {
      25                 :      102864 :     size_t operator()(const CTransactionRef& tx) const
      26                 :             :     {
      27         [ +  + ]:      102864 :         return static_cast<size_t>(tx->GetWitnessHash().ToUint256().GetUint64(0));
      28                 :             :     }
      29                 :             : };
      30                 :             : 
      31                 :             : struct CTransactionRefComp {
      32                 :      177007 :     bool operator()(const CTransactionRef& a, const CTransactionRef& b) const
      33                 :             :     {
      34   [ -  +  -  -  :      114493 :         return a->GetWitnessHash() == b->GetWitnessHash();
                   +  + ]
      35                 :             :     }
      36                 :             : };
      37                 :             : 
      38         [ +  - ]:        1411 : FUZZ_TARGET(private_broadcast)
      39                 :             : {
      40                 :         935 :     SeedRandomStateForTest(SeedRand::ZEROS);
      41                 :         935 :     FuzzedDataProvider fdp(buffer.data(), buffer.size());
      42                 :         935 :     FakeNodeClock clock_ctx{ConsumeTime(fdp)};
      43                 :             : 
      44                 :         935 :     const size_t cap{fdp.ConsumeIntegralInRange<size_t>(1, 12)};
      45                 :         935 :     const size_t max_send_attempts{fdp.ConsumeIntegralInRange<size_t>(1, 12)};
      46                 :         935 :     PrivateBroadcast pb{cap, max_send_attempts};
      47                 :             : 
      48                 :             :     // Random transaction that the test generated and passed to Add(). Trimmed when Remove() is called.
      49                 :             :     // The values are the number of times a transaction was picked for sending.
      50                 :         935 :     std::unordered_map<CTransactionRef, size_t, CTransactionRefHash, CTransactionRefComp> transactions;
      51                 :             : 
      52                 :             :     // Transactions passed to PickTxForSend(), indexed by node id. Trimmed when
      53                 :             :     // Remove() is called or a transaction is reset by Add().
      54                 :         935 :     std::unordered_map<NodeId, CTransactionRef> nodes_sent_to;
      55                 :             : 
      56                 :             :     // A subset of `nodes_sent_to`, node ids passed to NodeConfirmedReception().
      57                 :             :     // Trimmed when Remove() is called or a transaction is reset by Add().
      58                 :         935 :     std::unordered_set<NodeId> nodes_that_confirmed_reception;
      59                 :             : 
      60                 :         935 :     NodeId next_nodeid{0}; // Generate unique node ids.
      61                 :             : 
      62                 :      136980 :     const auto is_pending{[max_send_attempts](const auto& entry) {
      63                 :      201165 :         return entry.second < max_send_attempts;
           [ +  +  +  -  
          +  +  +  +  -  
                +  +  + ]
      64                 :         935 :     }};
      65                 :             : 
      66                 :      115097 :     const auto ExistentOrNewNodeId = [&next_nodeid, &fdp](){
      67   [ +  +  +  + ]:      114162 :         if (next_nodeid == 0 || fdp.ConsumeBool()) {
      68                 :       96807 :             return next_nodeid++;
      69                 :             :         }
      70                 :       17355 :         return fdp.ConsumeIntegralInRange<NodeId>(0, next_nodeid - 1);
      71                 :         935 :     };
      72                 :             : 
      73   [ +  +  +  + ]:      277804 :     LIMITED_WHILE (fdp.ConsumeBool(), 10000) {
      74         [ +  - ]:      276869 :         CallOneOf(
      75                 :             :             fdp,
      76                 :       16147 :             [&] { // Add()
      77                 :       16147 :                 CTransactionRef tx;
      78   [ +  +  +  + ]:       16147 :                 if (transactions.empty() || fdp.ConsumeBool()) {
      79   [ +  -  -  + ]:       36369 :                     tx = MakeTransactionRef(ConsumeTransaction(fdp, std::nullopt));
      80                 :             :                 } else {
      81                 :        4024 :                     tx = PickIterator(fdp, transactions)->first;
      82                 :             :                 }
      83                 :             : 
      84                 :       16147 :                 const bool present_before{transactions.contains(tx)};
      85         [ +  - ]:       16147 :                 const auto res{pb.Add(tx)};
      86         [ +  + ]:       16147 :                 if (present_before) {
      87                 :        6567 :                     auto tx_it{transactions.find(tx)};
      88   [ +  -  -  + ]:       13134 :                     Assert(tx_it != transactions.end());
      89         [ +  + ]:        6567 :                     if (is_pending(*tx_it)) {
      90         [ -  + ]:        5092 :                         Assert(res == PrivateBroadcast::AddResult::AlreadyPresent);
      91                 :             :                     } else {
      92         [ -  + ]:        1475 :                         Assert(res == PrivateBroadcast::AddResult::Added);
      93                 :        1475 :                         tx_it->second = 0;
      94         [ +  + ]:       29867 :                         for (auto it = nodes_sent_to.begin(); it != nodes_sent_to.end();) {
      95         [ +  + ]:       28392 :                             if (CTransactionRefComp{}(it->second, tx)) {
      96                 :        7860 :                                 nodes_that_confirmed_reception.erase(it->first);
      97                 :        7860 :                                 it = nodes_sent_to.erase(it);
      98                 :             :                             } else {
      99                 :       20532 :                                 ++it;
     100                 :             :                             }
     101                 :             :                         }
     102                 :             :                     }
     103         [ +  + ]:        9580 :                 } else if (transactions.size() >= cap) {
     104   [ -  +  +  - ]:       16147 :                     Assert(res == PrivateBroadcast::AddResult::QueueFull);
     105                 :             :                 } else {
     106         [ -  + ]:        7183 :                     Assert(res == PrivateBroadcast::AddResult::Added);
     107         [ +  - ]:        7183 :                     transactions.emplace(tx, 0);
     108                 :             :                 }
     109                 :       16147 :             },
     110                 :       50060 :             [&] { // Remove()
     111         [ +  + ]:       50060 :                 if (transactions.empty()) {
     112                 :             :                     return;
     113                 :             :                 }
     114                 :        6129 :                 const auto transactions_it{PickIterator(fdp, transactions)};
     115                 :        6129 :                 const CTransactionRef& tx{transactions_it->first};
     116                 :             : 
     117                 :        6129 :                 size_t num_nodes_that_confirmed_tx{0};
     118                 :             : 
     119                 :             :                 // Remove relevant entries from nodes_sent_to[] and nodes_that_confirmed_reception[] if any.
     120         [ +  + ]:       68643 :                 for (auto it = nodes_sent_to.begin(); it != nodes_sent_to.end();) {
     121         [ +  + ]:       62514 :                     const NodeId nodeid{it->first};
     122         [ +  + ]:       62514 :                     if (CTransactionRefComp{}(it->second, tx)) {
     123                 :       11244 :                         it = nodes_sent_to.erase(it);
     124         [ +  + ]:       11244 :                         if (nodes_that_confirmed_reception.erase(nodeid) > 0) {
     125                 :         646 :                             ++num_nodes_that_confirmed_tx;
     126                 :             :                         }
     127                 :             :                     } else {
     128                 :       51270 :                         ++it;
     129                 :             :                     }
     130                 :             :                 }
     131                 :             : 
     132                 :        6129 :                 const auto opt_num_confirmed{pb.Remove(tx)};
     133                 :             : 
     134         [ -  + ]:        6129 :                 Assert(opt_num_confirmed.has_value());
     135         [ -  + ]:        6129 :                 Assert(opt_num_confirmed.value() == num_nodes_that_confirmed_tx);
     136         [ -  + ]:        6129 :                 Assert(!pb.Remove(tx).has_value());
     137                 :        6129 :                 transactions.erase(transactions_it);
     138                 :             :             },
     139                 :       52210 :             [&] { // PickTxForSend()
     140                 :             :                 // Only give pristine node ids to PickTxForSend() as required.
     141                 :       52210 :                 const NodeId will_send_to_nodeid{next_nodeid++};
     142                 :       52210 :                 const CService will_send_to_address{ConsumeService(fdp)};
     143                 :             : 
     144         [ +  - ]:       52210 :                 const auto opt_tx{pb.PickTxForSend(will_send_to_nodeid, will_send_to_address)};
     145                 :             : 
     146         [ +  + ]:       52210 :                 if (opt_tx.has_value()) {
     147         [ -  + ]:       22374 :                     Assert(transactions.contains(opt_tx.value()));
     148                 :             : 
     149                 :             :                     // "Number of times picked for sending" is the primary key in Priority's comparison
     150                 :             :                     // (fewest sends = highest priority), so PickTxForSend() must return a transaction
     151                 :             :                     // with the minimum send count of any in the queue. Ties are broken by state we
     152                 :             :                     // don't model, so only check this key.
     153                 :       22374 :                     auto pending_transactions{transactions | std::views::filter(is_pending)};
     154         [ +  - ]:       22374 :                     const size_t min_picked{std::ranges::min_element(
     155   [ +  +  +  - ]:       88151 :                         pending_transactions, {}, [](const auto& el) { return el.second; })->second};
     156         [ +  - ]:       22374 :                     const auto picked_it{transactions.find(opt_tx.value())};
     157   [ +  -  -  + ]:       44748 :                     Assert(picked_it != transactions.end());
     158         [ -  + ]:       22374 :                     Assert(picked_it->second == min_picked); // picked the least-sent transaction
     159         [ +  - ]:       22374 :                     ++picked_it->second; // PickTxForSend() recorded exactly one send
     160                 :             : 
     161   [ +  -  -  + ]:       22374 :                     const auto& [_, inserted]{nodes_sent_to.emplace(will_send_to_nodeid, opt_tx.value())};
     162         [ -  + ]:       22374 :                     Assert(inserted);
     163                 :             :                 } else {
     164         [ -  + ]:       52210 :                     Assert(std::ranges::none_of(transactions, is_pending));
     165                 :             :                 }
     166                 :       52210 :             },
     167                 :       37062 :             [&] { // GetTxForNode()
     168                 :       37062 :                 const NodeId nodeid{ExistentOrNewNodeId()};
     169                 :             : 
     170                 :       37062 :                 const auto opt_tx{pb.GetTxForNode(nodeid)};
     171                 :             : 
     172         [ +  + ]:       37062 :                 if (nodes_sent_to.contains(nodeid)) {
     173         [ -  + ]:         790 :                     Assert(opt_tx.has_value());
     174         [ -  + ]:         790 :                     Assert(transactions.contains(opt_tx.value()));
     175   [ +  -  +  -  :         790 :                     Assert(opt_tx.value() == nodes_sent_to.at(nodeid));
                   -  + ]
     176                 :             :                 } else {
     177         [ -  + ]:       37062 :                     Assert(!opt_tx.has_value());
     178                 :             :                 }
     179                 :       37062 :             },
     180                 :       11112 :             [&] { // NodeConfirmedReception()
     181                 :       11112 :                 const NodeId nodeid{ExistentOrNewNodeId()};
     182                 :             : 
     183                 :       11112 :                 pb.NodeConfirmedReception(nodeid);
     184                 :             : 
     185         [ +  + ]:       11112 :                 if (nodes_sent_to.contains(nodeid)) {
     186                 :             :                     // nodeid was previously passed to PickTxForSend(), so NodeConfirmedReception()
     187                 :             :                     // must have changed the internal state. Remember this to later check that
     188                 :             :                     // DidNodeConfirmReception() works correctly.
     189                 :        3140 :                     nodes_that_confirmed_reception.emplace(nodeid);
     190                 :             :                 }
     191                 :       11112 :             },
     192                 :       65988 :             [&] { // DidNodeConfirmReception()
     193                 :       65988 :                 const NodeId nodeid{ExistentOrNewNodeId()};
     194                 :             : 
     195                 :       65988 :                 const bool confirmed{pb.DidNodeConfirmReception(nodeid)};
     196                 :             : 
     197         [ +  + ]:       65988 :                 if (nodes_that_confirmed_reception.contains(nodeid)) {
     198         [ -  + ]:         524 :                     Assert(confirmed);
     199                 :             :                 } else {
     200         [ -  + ]:       65464 :                     Assert(!confirmed);
     201                 :             :                 }
     202                 :       65988 :             },
     203                 :        6496 :             [&] { // HavePendingTransactions()
     204         [ +  + ]:        6496 :                 if (std::ranges::any_of(transactions, is_pending)) {
     205         [ -  + ]:        2787 :                     Assert(pb.HavePendingTransactions());
     206                 :             :                 } else {
     207         [ -  + ]:        3709 :                     Assert(!pb.HavePendingTransactions());
     208                 :             :                 }
     209                 :        6496 :             },
     210                 :       15147 :             [&] { // GetStale()
     211                 :       15147 :                 const auto stale{pb.GetStale()};
     212                 :             : 
     213   [ -  +  -  + ]:       15147 :                 Assert(stale.size() <= transactions.size());
     214                 :             : 
     215         [ +  + ]:       35575 :                 for (const auto& stale_tx : stale) {
     216                 :       20428 :                     const auto it{transactions.find(stale_tx)};
     217   [ +  -  -  + ]:       40856 :                     Assert(it != transactions.end());
     218         [ -  + ]:       20428 :                     Assert(is_pending(*it));
     219                 :             :                 }
     220                 :       15147 :             },
     221                 :        2229 :             [&] { // GetBroadcastInfo()
     222                 :        2229 :                 const auto all_broadcast_info{pb.GetBroadcastInfo()};
     223                 :             : 
     224   [ -  +  -  + ]:        2229 :                 Assert(all_broadcast_info.size() == transactions.size());
     225                 :             : 
     226         [ +  + ]:        9230 :                 for (const auto& info : all_broadcast_info) {
     227                 :        7001 :                     const auto it{transactions.find(info.tx)};
     228   [ +  -  -  + ]:       14002 :                     Assert(it != transactions.end());
     229   [ -  +  -  + ]:        7001 :                     Assert(info.peers.size() == it->second); // exactly the sends we recorded
     230         [ -  + ]:        7001 :                     Assert(info.attempts_remaining == max_send_attempts - it->second);
     231                 :             :                 }
     232                 :        2229 :             },
     233                 :       20418 :             [&] {
     234                 :       20418 :                 clock_ctx.set(ConsumeTime(fdp));
     235                 :       20418 :             });
     236                 :             :     }
     237                 :         935 : }
        

Generated by: LCOV version 2.5.0-full