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) 2023-present The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or https://opensource.org/license/mit/.
4
5#include <private_broadcast.h>
6
7#include <util/check.h>
8
9#include <algorithm>
10#include <ranges>
11
12
15{
16 LOCK(m_mutex);
17 // Cleanup finished transactions
18 std::erase_if(m_transactions, [this](const auto& entry) {
19 const auto& state{entry.second};
20 return state.resolved && !IsPending(state) &&
21 std::ranges::all_of(state.send_statuses, [](const auto& status) { return status.disconnected; });
22 });
23
24 if (const auto it{m_transactions.find(tx)}; it != m_transactions.end()) {
25 if (it->second.send_statuses.size() < m_max_send_attempts) return AddResult::AlreadyPresent;
26
27 // A transaction that has reached m_max_send_attempts can be explicitly retried by adding it again.
28 it->second.time_added = NodeClock::now();
29 it->second.send_statuses.clear();
30 it->second.planned_sends = INITIAL_CONNECTION_COUNT;
31 it->second.resolved = false;
32 return AddResult::Added;
33 }
34
35 if (m_transactions.size() >= m_max_transactions) return AddResult::QueueFull;
36
37 m_transactions.try_emplace(tx);
38 return AddResult::Added;
39}
40
41std::optional<size_t> PrivateBroadcast::Remove(const CTransactionRef& tx)
43{
44 LOCK(m_mutex);
45 const auto handle{m_transactions.extract(tx)};
46 if (handle) {
47 const auto& state{handle.mapped()};
48 const size_t planned{std::min(state.planned_sends, m_max_send_attempts)};
49 const size_t actual{state.send_statuses.size()};
50 return planned > actual ? planned - actual : 0;
51 }
52 return std::nullopt;
53}
54
56{
58 const auto it{m_transactions.find(tx)};
59 if (it == m_transactions.end()) return false;
60 it->second.resolved = true;
61 return true;
62}
63
65{
67 if (const auto entry{GetSendStatusByNode(nodeid)}) {
68 entry->send_status.disconnected = true;
69 }
70}
71
73{
75 const auto it{m_transactions.find(tx)};
76 if (it == m_transactions.end() ||
77 it->second.resolved ||
78 IsPending(it->second) ||
79 it->second.planned_sends >= m_max_send_attempts) return false;
80 ++it->second.planned_sends;
81 return true;
82}
83
86{
87 LOCK(m_mutex);
88
89 if (GetSendStatusByNode(will_send_to_nodeid).has_value()) { // nodeid reuse, shouldn't send >1 tx to a given node
90 Assume(false);
91 return std::nullopt;
92 }
93
94 auto pending_transactions{m_transactions | std::views::filter([this](const auto& entry) { return IsPending(entry.second); })};
95 const auto it{std::ranges::max_element(
97 [](const auto& a, const auto& b) { return a < b; },
98 [](const auto& el) { return DerivePriority(el.second.send_statuses); })};
99
100 if (it != pending_transactions.end()) {
101 auto& [tx, state]{*it};
102 state.send_statuses.emplace_back(will_send_to_nodeid, will_send_to_address, NodeClock::now());
103 return tx;
104 }
105
106 return std::nullopt;
107}
108
109std::optional<CTransactionRef> PrivateBroadcast::GetTxForNode(const NodeId& nodeid)
111{
112 LOCK(m_mutex);
113 const auto tx_and_status{GetSendStatusByNode(nodeid)};
114 if (tx_and_status.has_value()) {
115 return tx_and_status.value().tx;
116 }
117 return std::nullopt;
118}
119
122{
123 LOCK(m_mutex);
124 const auto tx_and_status{GetSendStatusByNode(nodeid)};
125 if (tx_and_status.has_value()) {
126 tx_and_status.value().send_status.confirmed = NodeClock::now();
127 }
128}
129
132{
133 LOCK(m_mutex);
134 return std::ranges::any_of(m_transactions, [this](const auto& entry) { return IsPending(entry.second); });
135}
136
137std::vector<CTransactionRef> PrivateBroadcast::GetStale() const
139{
140 LOCK(m_mutex);
141 const auto now{NodeClock::now()};
142 std::vector<CTransactionRef> stale;
143 for (const auto& [tx, state] : m_transactions) {
144 if (state.resolved || state.planned_sends >= m_max_send_attempts) continue;
145 const Priority p{DerivePriority(state.send_statuses)};
146 if (p.num_confirmed == 0) {
147 if (state.time_added < now - INITIAL_STALE_DURATION) stale.push_back(tx);
148 } else {
149 if (p.last_confirmed < now - STALE_DURATION) stale.push_back(tx);
150 }
151 }
152 return stale;
153}
154
155std::vector<PrivateBroadcast::TxBroadcastInfo> PrivateBroadcast::GetBroadcastInfo() const
157{
158 LOCK(m_mutex);
159 std::vector<TxBroadcastInfo> entries;
160 entries.reserve(m_transactions.size());
161
162 for (const auto& [tx, state] : m_transactions) {
163 if (state.resolved) continue;
164 std::vector<PeerSendInfo> peers;
165 peers.reserve(state.send_statuses.size());
166 for (const auto& status : state.send_statuses) {
167 peers.emplace_back(PeerSendInfo{.address = status.address, .sent = status.picked, .received = status.confirmed});
168 }
169 const size_t attempts_remaining{m_max_send_attempts - std::min(state.send_statuses.size(), m_max_send_attempts)};
170 entries.emplace_back(TxBroadcastInfo{.tx = tx, .time_added = state.time_added, .attempts_remaining = attempts_remaining, .peers = std::move(peers)});
171 }
172
173 return entries;
174}
175
177{
178 // Deliberately ignore resolved so all initially scheduled connections can complete.
179 const size_t limit{std::min(status.planned_sends, m_max_send_attempts)};
180 return status.send_statuses.size() < limit;
181}
182
184{
185 Priority p;
186 p.num_picked = sent_to.size();
187 for (const auto& send_status : sent_to) {
188 p.last_picked = std::max(p.last_picked, send_status.picked);
189 if (send_status.confirmed.has_value()) {
190 ++p.num_confirmed;
191 p.last_confirmed = std::max(p.last_confirmed, send_status.confirmed.value());
192 }
193 }
194 return p;
195}
196
197std::optional<PrivateBroadcast::TxAndSendStatusForNode> PrivateBroadcast::GetSendStatusByNode(const NodeId& nodeid)
199{
200 AssertLockHeld(m_mutex);
201 for (auto& [tx, state] : m_transactions) {
202 for (auto& send_status : state.send_statuses) {
203 if (send_status.nodeid == nodeid) {
204 return TxAndSendStatusForNode{.tx = tx, .send_status = send_status};
205 }
206 }
207 }
208 return std::nullopt;
209}
#define Assume(val)
Assume is the identity function.
Definition check.h:131
A combination of a network address (CNetAddr) and a (TCP) port.
Definition netaddress.h:531
static Priority DerivePriority(const std::vector< SendStatus > &sent_to)
Derive the sending priority of a transaction.
void NodeConfirmedReception(const NodeId &nodeid) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Mark that the node has confirmed reception of the transaction we sent it by responding with PONG to o...
AddResult Add(const CTransactionRef &tx) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Add a transaction to the storage, or reset an exhausted transaction so it can be broadcast again.
std::vector< TxBroadcastInfo > GetBroadcastInfo() const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Get stats about all transactions currently being privately broadcast.
bool MarkResolved(const CTransactionRef &tx) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Mark a transaction as resolved because it was received back from the network or is no longer acceptab...
AddResult
Outcome of Add().
bool HavePendingTransactions() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Check if there are transactions with send attempts remaining.
static constexpr auto STALE_DURATION
If a transaction is not received back from the network for this duration after it is broadcast,...
std::optional< size_t > Remove(const CTransactionRef &tx) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Forget a transaction.
void NodeDisconnected(NodeId nodeid) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Mark a connection finished.
std::optional< CTransactionRef > PickTxForSend(const NodeId &will_send_to_nodeid, const CService &will_send_to_address) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Pick the transaction with the fewest send attempts, and confirmations, and oldest send/confirm times.
bool IsPending(const TxSendStatus &status) const
bool TryGrantRetry(const CTransactionRef &tx) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Grant one additional send attempt if the transaction is unresolved, all planned sends have been picke...
std::optional< TxAndSendStatusForNode > GetSendStatusByNode(const NodeId &nodeid) EXCLUSIVE_LOCKS_REQUIRED(m_mutex)
Find which transaction we sent to a given node (marked by PickTxForSend()).
const size_t m_max_send_attempts
Cap on the number of send attempts per transaction (see PickTxForSend()).
std::optional< CTransactionRef > GetTxForNode(const NodeId &nodeid) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Get the transaction that was picked for sending to a given node by PickTxForSend().
std::vector< CTransactionRef > GetStale() const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Get the transactions that have not been broadcast recently and have send attempts remaining.
static constexpr auto INITIAL_STALE_DURATION
If a transaction is not sent to any peer for this duration, then we consider it stale / for rebroadca...
int64_t NodeId
Definition net.h:108
std::shared_ptr< const CTransaction > CTransactionRef
static time_point now() noexcept
Return current system time or mocked time, if set.
Definition time.cpp:38
Cumulative stats from all the send attempts for a transaction. Used to prioritize transactions.
size_t num_picked
Number of times the transaction was picked for sending.
A pair of a transaction and a sent status for a given node. Convenience return type of GetSendStatusB...
std::vector< SendStatus > send_statuses
size_t planned_sends
Total number of sends granted, including initial count.
#define LOCK(cs)
Definition sync.h:268
#define AssertLockHeld(cs)
Definition sync.h:144
#define EXCLUSIVE_LOCKS_REQUIRED(...)
constexpr auto Ticks(Dur2 d)
Helper to count the seconds of a duration/time_point.
Definition time.h:82