Bitcoin Core 32.99.0
P2P Digital Currency
Loading...
Searching...
No Matches
private_broadcast.cpp
Go to the documentation of this file.
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
7#include <net.h>
9#include <private_broadcast.h>
11#include <test/fuzz/fuzz.h>
12#include <test/fuzz/util.h>
13#include <test/fuzz/util/net.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
25 size_t operator()(const CTransactionRef& tx) const
26 {
27 return static_cast<size_t>(tx->GetWitnessHash().ToUint256().GetUint64(0));
28 }
29};
30
32 bool operator()(const CTransactionRef& a, const CTransactionRef& b) const
33 {
34 return a->GetWitnessHash() == b->GetWitnessHash();
35 }
36};
37
38FUZZ_TARGET(private_broadcast)
39{
40 FuzzedDataProvider fdp(buffer.data(), buffer.size());
42
43 const size_t cap{fdp.ConsumeIntegralInRange<size_t>(1, 12)};
44 const size_t max_send_attempts{fdp.ConsumeIntegralInRange<size_t>(1, 12)};
46
47 // Random transactions that the test generated and passed to Add(). Trimmed by Remove() and Add() cleanup.
48 // The values are the number of times a transaction was picked for sending.
49 std::unordered_map<CTransactionRef, size_t, CTransactionRefHash, CTransactionRefComp> transactions;
50 // The values are the number of times a transaction is planned to be sent.
51 std::unordered_map<CTransactionRef, size_t, CTransactionRefHash, CTransactionRefComp> planned_sends;
52 std::unordered_set<CTransactionRef, CTransactionRefHash, CTransactionRefComp> resolved_transactions;
53
54 // Transactions passed to PickTxForSend(), indexed by node id. Trimmed when
55 // Remove() is called or a transaction is reset or cleaned up by Add().
56 std::unordered_map<NodeId, CTransactionRef> nodes_sent_to;
57 std::unordered_set<NodeId> disconnected_nodes;
58
59 NodeId next_nodeid{0}; // Generate unique node ids.
60
61 const auto is_pending{[&](const auto& entry) {
62 return entry.second < std::min(planned_sends.at(entry.first), max_send_attempts);
63 }};
64
65 const auto ExistentOrNewNodeId = [&next_nodeid, &fdp](){
66 if (next_nodeid == 0 || fdp.ConsumeBool()) {
67 return next_nodeid++;
68 }
69 return fdp.ConsumeIntegralInRange<NodeId>(0, next_nodeid - 1);
70 };
71
72 LIMITED_WHILE (fdp.ConsumeBool(), 10000) {
74 fdp,
75 [&] { // Add()
77 if (transactions.empty() || fdp.ConsumeBool()) {
78 tx = MakeTransactionRef(ConsumeTransaction(fdp, std::nullopt));
79 } else {
80 tx = PickIterator(fdp, transactions)->first;
81 }
82
83 auto cleanup_candidates{resolved_transactions};
84 for (const auto& [nodeid, sent_tx] : nodes_sent_to) {
85 if (!disconnected_nodes.contains(nodeid)) cleanup_candidates.erase(sent_tx);
86 }
87 std::erase_if(transactions, [&](const auto& entry) {
88 if (!cleanup_candidates.contains(entry.first) || is_pending(entry)) return false;
89 planned_sends.erase(entry.first);
90 resolved_transactions.erase(entry.first);
91 return true;
92 });
93 std::erase_if(nodes_sent_to, [&](const auto& entry) { return !transactions.contains(entry.second); });
94
95 const bool present_before{transactions.contains(tx)};
96 const auto res{pb.Add(tx)};
97 if (present_before) {
98 auto tx_it{transactions.find(tx)};
99 Assert(tx_it != transactions.end());
100 if (tx_it->second < max_send_attempts) {
102 } else {
104 tx_it->second = 0;
106 resolved_transactions.erase(tx);
107 std::erase_if(nodes_sent_to, [&](const auto& entry) { return CTransactionRefComp{}(entry.second, tx); });
108 }
109 } else if (transactions.size() >= cap) {
111 } else {
113 transactions.emplace(tx, 0);
114 planned_sends.emplace(tx, PrivateBroadcast::INITIAL_CONNECTION_COUNT);
115 }
116 },
117 [&] { // Remove()
118 if (transactions.empty()) {
119 return;
120 }
121 const auto transactions_it{PickIterator(fdp, transactions)};
122 const CTransactionRef& tx{transactions_it->first};
123
124 // Remove relevant entries from nodes_sent_to[] if any.
125 std::erase_if(nodes_sent_to, [&](const auto& entry) { return CTransactionRefComp{}(entry.second, tx); });
126
127 const auto remaining_sends{pb.Remove(tx)};
128
129 Assert(remaining_sends.has_value());
130 Assert(remaining_sends.value() == std::min(planned_sends.at(tx), max_send_attempts) - transactions_it->second);
131 Assert(!pb.Remove(tx).has_value());
132 planned_sends.erase(tx);
133 resolved_transactions.erase(tx);
134 transactions.erase(transactions_it);
135 },
136 [&] { // MarkResolved()
137 const CTransactionRef tx{transactions.empty() || fdp.ConsumeBool() ?
139 PickIterator(fdp, transactions)->first};
140 Assert(pb.MarkResolved(tx) == transactions.contains(tx));
141 if (transactions.contains(tx)) resolved_transactions.insert(tx);
142 },
143 [&] { // PickTxForSend()
144 // Only give pristine node ids to PickTxForSend() as required.
147
148 const auto opt_tx{pb.PickTxForSend(will_send_to_nodeid, will_send_to_address)};
149
150 if (opt_tx.has_value()) {
151 Assert(transactions.contains(opt_tx.value()));
152
153 // "Number of times picked for sending" is the primary key in Priority's comparison
154 // (fewest sends = highest priority), so PickTxForSend() must return a transaction
155 // with the minimum send count of any in the queue. Ties are broken by state we
156 // don't model, so only check this key.
157 auto pending_transactions{transactions | std::views::filter(is_pending)};
158 const size_t min_picked{std::ranges::min_element(
159 pending_transactions, {}, [](const auto& el) { return el.second; })->second};
160 const auto picked_it{transactions.find(opt_tx.value())};
161 Assert(picked_it != transactions.end());
162 Assert(picked_it->second == min_picked); // picked the least-sent transaction
163 ++picked_it->second; // PickTxForSend() recorded exactly one send
164
165 const auto& [_, inserted]{nodes_sent_to.emplace(will_send_to_nodeid, opt_tx.value())};
167 } else {
168 Assert(std::ranges::none_of(transactions, is_pending));
169 }
170 },
171 [&] { // GetTxForNode()
172 const NodeId nodeid{ExistentOrNewNodeId()};
173
174 const auto opt_tx{pb.GetTxForNode(nodeid)};
175
176 if (nodes_sent_to.contains(nodeid)) {
177 Assert(opt_tx.has_value());
178 Assert(transactions.contains(opt_tx.value()));
179 Assert(opt_tx.value() == nodes_sent_to.at(nodeid));
180 } else {
181 Assert(!opt_tx.has_value());
182 }
183 },
184 [&] { // NodeConfirmedReception()
185 const NodeId nodeid{ExistentOrNewNodeId()};
186
187 pb.NodeConfirmedReception(nodeid);
188 },
189 [&] { // NodeDisconnected()
190 const NodeId nodeid{ExistentOrNewNodeId()};
191 pb.NodeDisconnected(nodeid);
192 if (nodes_sent_to.contains(nodeid)) disconnected_nodes.insert(nodeid);
193 },
194 [&] { // HavePendingTransactions()
195 if (std::ranges::any_of(transactions, is_pending)) {
196 Assert(pb.HavePendingTransactions());
197 } else {
198 Assert(!pb.HavePendingTransactions());
199 }
200 },
201 [&] { // TryGrantRetry()
202 if (transactions.empty()) return;
203 const auto tx_it{PickIterator(fdp, transactions)};
204 auto& count{planned_sends.at(tx_it->first)};
205 const bool can_retry{!resolved_transactions.contains(tx_it->first) && !is_pending(*tx_it) && count < max_send_attempts};
206 Assert(pb.TryGrantRetry(tx_it->first) == can_retry);
207 if (can_retry) ++count;
208 },
209 [&] { // GetStale()
210 const auto stale{pb.GetStale()};
211
212 Assert(stale.size() <= transactions.size());
213
214 for (const auto& stale_tx : stale) {
215 const auto it{transactions.find(stale_tx)};
216 Assert(it != transactions.end());
218 Assert(planned_sends.at(stale_tx) < max_send_attempts);
219 }
220 },
221 [&] { // GetBroadcastInfo()
222 const auto all_broadcast_info{pb.GetBroadcastInfo()};
223
224 Assert(all_broadcast_info.size() == transactions.size() - resolved_transactions.size());
225
226 for (const auto& info : all_broadcast_info) {
227 const auto it{transactions.find(info.tx)};
228 Assert(it != transactions.end());
229 Assert(!resolved_transactions.contains(info.tx));
230 Assert(info.peers.size() == it->second); // exactly the sends we recorded
231 Assert(info.attempts_remaining == max_send_attempts - it->second);
232 }
233 },
234 [&] {
236 });
237 }
238}
#define Assert(val)
Identity function.
Definition check.h:119
A combination of a network address (CNetAddr) and a (TCP) port.
Definition netaddress.h:531
Helper to initialize the global NodeClock, let a duration elapse, and reset it after use in a test.
Definition time.h:54
Store a list of transactions to be broadcast privately.
@ QueueFull
Rejected: the queue is already at MAX_TRANSACTIONS.
@ AlreadyPresent
The transaction was already present with send attempts remaining; no change.
@ Added
The transaction was newly added or reset after exhausting its send attempts.
static constexpr size_t INITIAL_CONNECTION_COUNT
Number of connections to make for initial broadcast.
#define FUZZ_TARGET(...)
Definition fuzz.h:35
#define LIMITED_WHILE(condition, limit)
Can be used to limit a theoretically unbounded loop.
Definition fuzz.h:22
int64_t NodeId
Definition net.h:108
static CTransactionRef MakeTransactionRef(Tx &&txIn)
std::shared_ptr< const CTransaction > CTransactionRef
bool operator()(const CTransactionRef &a, const CTransactionRef &b) const
size_t operator()(const CTransactionRef &tx) const
CService ConsumeService(FuzzedDataProvider &fuzzed_data_provider) noexcept
Definition net.h:250
NodeSeconds ConsumeTime(FuzzedDataProvider &fuzzed_data_provider, const std::optional< int64_t > &min, const std::optional< int64_t > &max) noexcept
Definition util.cpp:34
CMutableTransaction ConsumeTransaction(FuzzedDataProvider &fuzzed_data_provider, const std::optional< std::vector< Txid > > &prevout_txids, const int max_num_in, const int max_num_out) noexcept
Definition util.cpp:42
size_t CallOneOf(FuzzedDataProvider &fuzzed_data_provider, Callables... callables)
Definition util.h:37
auto PickIterator(FuzzedDataProvider &fuzzed_data_provider, Collection &col)
Definition util.h:49
static int count
consteval auto _(util::TranslatedLiteral str)
Definition translation.h:79
constexpr auto Ticks(Dur2 d)
Helper to count the seconds of a duration/time_point.
Definition time.h:82