21#include <unordered_map>
22#include <unordered_set>
27 return static_cast<size_t>(tx->GetWitnessHash().ToUint256().GetUint64(0));
34 return a->GetWitnessHash() == b->GetWitnessHash();
50 std::unordered_map<CTransactionRef, size_t, CTransactionRefHash, CTransactionRefComp> transactions;
54 std::unordered_map<NodeId, CTransactionRef> nodes_sent_to;
58 std::unordered_set<NodeId> nodes_that_confirmed_reception;
62 const auto is_pending{[max_send_attempts](
const auto& entry) {
63 return entry.second < max_send_attempts;
66 const auto ExistentOrNewNodeId = [&next_nodeid, &fdp](){
79 tx = MakeTransactionRef(ConsumeTransaction(fdp, std::nullopt));
81 tx = PickIterator(fdp, transactions)->first;
84 const bool present_before{transactions.contains(tx)};
85 const auto res{pb.Add(tx)};
87 auto tx_it{transactions.find(tx)};
88 Assert(tx_it != transactions.end());
89 if (is_pending(*tx_it)) {
94 for (
auto it = nodes_sent_to.begin(); it != nodes_sent_to.end();) {
96 nodes_that_confirmed_reception.erase(it->first);
97 it = nodes_sent_to.erase(it);
103 }
else if (transactions.size() >= cap) {
107 transactions.emplace(tx, 0);
111 if (transactions.empty()) {
114 const auto transactions_it{
PickIterator(fdp, transactions)};
117 size_t num_nodes_that_confirmed_tx{0};
120 for (
auto it = nodes_sent_to.begin(); it != nodes_sent_to.end();) {
121 const NodeId nodeid{it->first};
123 it = nodes_sent_to.erase(it);
124 if (nodes_that_confirmed_reception.erase(nodeid) > 0) {
125 ++num_nodes_that_confirmed_tx;
132 const auto opt_num_confirmed{pb.Remove(tx)};
134 Assert(opt_num_confirmed.has_value());
135 Assert(opt_num_confirmed.value() == num_nodes_that_confirmed_tx);
136 Assert(!pb.Remove(tx).has_value());
137 transactions.erase(transactions_it);
141 const NodeId will_send_to_nodeid{next_nodeid++};
144 const auto opt_tx{pb.PickTxForSend(will_send_to_nodeid, will_send_to_address)};
146 if (opt_tx.has_value()) {
147 Assert(transactions.contains(opt_tx.value()));
153 auto pending_transactions{transactions | std::views::filter(is_pending)};
154 const size_t min_picked{std::ranges::min_element(
155 pending_transactions, {}, [](
const auto& el) {
return el.second; })->second};
156 const auto picked_it{transactions.find(opt_tx.value())};
157 Assert(picked_it != transactions.end());
158 Assert(picked_it->second == min_picked);
161 const auto& [
_, inserted]{nodes_sent_to.emplace(will_send_to_nodeid, opt_tx.value())};
164 Assert(std::ranges::none_of(transactions, is_pending));
168 const NodeId nodeid{ExistentOrNewNodeId()};
170 const auto opt_tx{pb.GetTxForNode(nodeid)};
172 if (nodes_sent_to.contains(nodeid)) {
173 Assert(opt_tx.has_value());
174 Assert(transactions.contains(opt_tx.value()));
175 Assert(opt_tx.value() == nodes_sent_to.at(nodeid));
177 Assert(!opt_tx.has_value());
181 const NodeId nodeid{ExistentOrNewNodeId()};
183 pb.NodeConfirmedReception(nodeid);
185 if (nodes_sent_to.contains(nodeid)) {
189 nodes_that_confirmed_reception.emplace(nodeid);
193 const NodeId nodeid{ExistentOrNewNodeId()};
195 const bool confirmed{pb.DidNodeConfirmReception(nodeid)};
197 if (nodes_that_confirmed_reception.contains(nodeid)) {
204 if (std::ranges::any_of(transactions, is_pending)) {
205 Assert(pb.HavePendingTransactions());
207 Assert(!pb.HavePendingTransactions());
211 const auto stale{pb.GetStale()};
213 Assert(stale.size() <= transactions.size());
215 for (
const auto& stale_tx : stale) {
216 const auto it{transactions.find(stale_tx)};
217 Assert(it != transactions.end());
222 const auto all_broadcast_info{pb.GetBroadcastInfo()};
224 Assert(all_broadcast_info.size() == transactions.size());
226 for (
const auto& info : all_broadcast_info) {
227 const auto it{transactions.find(info.tx)};
228 Assert(it != transactions.end());
229 Assert(info.peers.size() == it->second);
230 Assert(info.attempts_remaining == max_send_attempts - it->second);
#define Assert(val)
Identity function.
A combination of a network address (CNetAddr) and a (TCP) port.
Helper to initialize the global NodeClock, let a duration elapse, and reset it after use in a test.
T ConsumeIntegralInRange(T min, T max)
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.
LIMITED_WHILE(provider.remaining_bytes(), 10000)
std::shared_ptr< const CTransaction > CTransactionRef
bool operator()(const CTransactionRef &a, const CTransactionRef &b) const
size_t operator()(const CTransactionRef &tx) const
SeedRandomStateForTest(SeedRand::ZEROS)
FUZZ_TARGET(private_broadcast)
CService ConsumeService(FuzzedDataProvider &fuzzed_data_provider) noexcept
NodeSeconds ConsumeTime(FuzzedDataProvider &fuzzed_data_provider, const std::optional< int64_t > &min, const std::optional< int64_t > &max) noexcept
size_t CallOneOf(FuzzedDataProvider &fuzzed_data_provider, Callables... callables)
auto PickIterator(FuzzedDataProvider &fuzzed_data_provider, Collection &col)
@ ZEROS
Seed with a compile time constant of zeros.
consteval auto _(util::TranslatedLiteral str)