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();
49 std::unordered_map<CTransactionRef, size_t, CTransactionRefHash, CTransactionRefComp> transactions;
51 std::unordered_map<CTransactionRef, size_t, CTransactionRefHash, CTransactionRefComp> planned_sends;
52 std::unordered_set<CTransactionRef, CTransactionRefHash, CTransactionRefComp> resolved_transactions;
56 std::unordered_map<NodeId, CTransactionRef> nodes_sent_to;
57 std::unordered_set<NodeId> disconnected_nodes;
61 const auto is_pending{[&](
const auto& entry) {
62 return entry.second < std::min(planned_sends.at(entry.first), max_send_attempts);
65 const auto ExistentOrNewNodeId = [&next_nodeid, &fdp](){
78 tx = MakeTransactionRef(ConsumeTransaction(fdp, std::nullopt));
80 tx = PickIterator(fdp, transactions)->first;
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);
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);
93 std::erase_if(nodes_sent_to, [&](
const auto& entry) {
return !transactions.contains(entry.second); });
95 const bool present_before{transactions.contains(tx)};
96 const auto res{pb.Add(tx)};
98 auto tx_it{transactions.find(tx)};
99 Assert(tx_it != transactions.end());
100 if (tx_it->second < max_send_attempts) {
106 resolved_transactions.erase(tx);
107 for (
auto it = nodes_sent_to.begin(); it != nodes_sent_to.end();) {
109 it = nodes_sent_to.erase(it);
115 }
else if (transactions.size() >= cap) {
119 transactions.emplace(tx, 0);
124 if (transactions.empty()) {
127 const auto transactions_it{
PickIterator(fdp, transactions)};
131 for (
auto it = nodes_sent_to.begin(); it != nodes_sent_to.end();) {
133 it = nodes_sent_to.erase(it);
139 const auto remaining_sends{pb.Remove(tx)};
141 Assert(remaining_sends.has_value());
142 Assert(remaining_sends.value() == std::min(planned_sends.at(tx), max_send_attempts) - transactions_it->second);
143 Assert(!pb.Remove(tx).has_value());
144 planned_sends.erase(tx);
145 resolved_transactions.erase(tx);
146 transactions.erase(transactions_it);
152 Assert(pb.MarkResolved(tx) == transactions.contains(tx));
153 if (transactions.contains(tx)) resolved_transactions.insert(tx);
157 const NodeId will_send_to_nodeid{next_nodeid++};
160 const auto opt_tx{pb.PickTxForSend(will_send_to_nodeid, will_send_to_address)};
162 if (opt_tx.has_value()) {
163 Assert(transactions.contains(opt_tx.value()));
169 auto pending_transactions{transactions | std::views::filter(is_pending)};
170 const size_t min_picked{std::ranges::min_element(
171 pending_transactions, {}, [](
const auto& el) {
return el.second; })->second};
172 const auto picked_it{transactions.find(opt_tx.value())};
173 Assert(picked_it != transactions.end());
174 Assert(picked_it->second == min_picked);
177 const auto& [
_, inserted]{nodes_sent_to.emplace(will_send_to_nodeid, opt_tx.value())};
180 Assert(std::ranges::none_of(transactions, is_pending));
184 const NodeId nodeid{ExistentOrNewNodeId()};
186 const auto opt_tx{pb.GetTxForNode(nodeid)};
188 if (nodes_sent_to.contains(nodeid)) {
189 Assert(opt_tx.has_value());
190 Assert(transactions.contains(opt_tx.value()));
191 Assert(opt_tx.value() == nodes_sent_to.at(nodeid));
193 Assert(!opt_tx.has_value());
197 const NodeId nodeid{ExistentOrNewNodeId()};
199 pb.NodeConfirmedReception(nodeid);
202 const NodeId nodeid{ExistentOrNewNodeId()};
203 pb.NodeDisconnected(nodeid);
204 if (nodes_sent_to.contains(nodeid)) disconnected_nodes.insert(nodeid);
207 if (std::ranges::any_of(transactions, is_pending)) {
208 Assert(pb.HavePendingTransactions());
210 Assert(!pb.HavePendingTransactions());
214 if (transactions.empty())
return;
216 auto&
count{planned_sends.at(tx_it->first)};
217 const bool can_retry{!resolved_transactions.contains(tx_it->first) && !is_pending(*tx_it) &&
count < max_send_attempts};
218 Assert(pb.TryGrantRetry(tx_it->first) == can_retry);
219 if (can_retry) ++
count;
222 const auto stale{pb.GetStale()};
224 Assert(stale.size() <= transactions.size());
226 for (
const auto& stale_tx : stale) {
227 const auto it{transactions.find(stale_tx)};
228 Assert(it != transactions.end());
229 Assert(!resolved_transactions.contains(stale_tx));
230 Assert(planned_sends.at(stale_tx) < max_send_attempts);
234 const auto all_broadcast_info{pb.GetBroadcastInfo()};
236 Assert(all_broadcast_info.size() == transactions.size() - resolved_transactions.size());
238 for (
const auto& info : all_broadcast_info) {
239 const auto it{transactions.find(info.tx)};
240 Assert(it != transactions.end());
241 Assert(!resolved_transactions.contains(info.tx));
242 Assert(info.peers.size() == it->second);
243 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.
static constexpr size_t INITIAL_CONNECTION_COUNT
Number of connections to make for initial broadcast.
for(const auto &cache :caches)
LIMITED_WHILE(provider.remaining_bytes(), 10000)
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
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
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
size_t CallOneOf(FuzzedDataProvider &fuzzed_data_provider, Callables... callables)
auto PickIterator(FuzzedDataProvider &fuzzed_data_provider, Collection &col)
consteval auto _(util::TranslatedLiteral str)