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 : }
|