Bitcoin Core 31.99.0
P2P Digital Currency
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 if (const auto it{m_transactions.find(tx)}; it != m_transactions.end()) {
18 if (IsPending(it->second)) return AddResult::AlreadyPresent;
19
20 // An exhausted transaction can be explicitly retried by adding it again.
21 it->second.time_added = NodeClock::now();
22 it->second.send_statuses.clear();
23 return AddResult::Added;
24 }
25
26 if (m_transactions.size() >= m_max_transactions) return AddResult::QueueFull;
27
28 m_transactions.try_emplace(tx);
29 return AddResult::Added;
30}
31
32std::optional<size_t> PrivateBroadcast::Remove(const CTransactionRef& tx)
34{
35 LOCK(m_mutex);
36 const auto handle{m_transactions.extract(tx)};
37 if (handle) {
38 const auto p{DerivePriority(handle.mapped().send_statuses)};
39 return p.num_confirmed;
40 }
41 return std::nullopt;
42}
43
44std::optional<CTransactionRef> PrivateBroadcast::PickTxForSend(const NodeId& will_send_to_nodeid, const CService& will_send_to_address)
46{
47 LOCK(m_mutex);
48
49 if (GetSendStatusByNode(will_send_to_nodeid).has_value()) { // nodeid reuse, shouldn't send >1 tx to a given node
50 Assume(false);
51 return std::nullopt;
52 }
53
54 auto pending_transactions{m_transactions | std::views::filter([this](const auto& entry) { return IsPending(entry.second); })};
55 const auto it{std::ranges::max_element(
56 pending_transactions,
57 [](const auto& a, const auto& b) { return a < b; },
58 [](const auto& el) { return DerivePriority(el.second.send_statuses); })};
59
60 if (it != pending_transactions.end()) {
61 auto& [tx, state]{*it};
62 state.send_statuses.emplace_back(will_send_to_nodeid, will_send_to_address, NodeClock::now());
63 return tx;
64 }
65
66 return std::nullopt;
67}
68
69std::optional<CTransactionRef> PrivateBroadcast::GetTxForNode(const NodeId& nodeid)
71{
72 LOCK(m_mutex);
73 const auto tx_and_status{GetSendStatusByNode(nodeid)};
74 if (tx_and_status.has_value()) {
75 return tx_and_status.value().tx;
76 }
77 return std::nullopt;
78}
79
82{
83 LOCK(m_mutex);
84 const auto tx_and_status{GetSendStatusByNode(nodeid)};
85 if (tx_and_status.has_value()) {
86 tx_and_status.value().send_status.confirmed = NodeClock::now();
87 }
88}
89
92{
93 LOCK(m_mutex);
94 const auto tx_and_status{GetSendStatusByNode(nodeid)};
95 if (tx_and_status.has_value()) {
96 return tx_and_status.value().send_status.confirmed.has_value();
97 }
98 return false;
99}
100
103{
104 LOCK(m_mutex);
105 return std::ranges::any_of(m_transactions, [this](const auto& entry) { return IsPending(entry.second); });
106}
107
108std::vector<CTransactionRef> PrivateBroadcast::GetStale() const
110{
111 LOCK(m_mutex);
112 const auto now{NodeClock::now()};
113 std::vector<CTransactionRef> stale;
114 for (const auto& [tx, state] : m_transactions) {
115 if (!IsPending(state)) continue;
116 const Priority p{DerivePriority(state.send_statuses)};
117 if (p.num_confirmed == 0) {
118 if (state.time_added < now - INITIAL_STALE_DURATION) stale.push_back(tx);
119 } else {
120 if (p.last_confirmed < now - STALE_DURATION) stale.push_back(tx);
121 }
122 }
123 return stale;
124}
125
126std::vector<PrivateBroadcast::TxBroadcastInfo> PrivateBroadcast::GetBroadcastInfo() const
128{
129 LOCK(m_mutex);
130 std::vector<TxBroadcastInfo> entries;
131 entries.reserve(m_transactions.size());
132
133 for (const auto& [tx, state] : m_transactions) {
134 std::vector<PeerSendInfo> peers;
135 peers.reserve(state.send_statuses.size());
136 for (const auto& status : state.send_statuses) {
137 peers.emplace_back(PeerSendInfo{.address = status.address, .sent = status.picked, .received = status.confirmed});
138 }
139 const size_t attempts_remaining{m_max_send_attempts - std::min(state.send_statuses.size(), m_max_send_attempts)};
140 entries.emplace_back(TxBroadcastInfo{.tx = tx, .time_added = state.time_added, .attempts_remaining = attempts_remaining, .peers = std::move(peers)});
141 }
142
143 return entries;
144}
145
147{
148 return status.send_statuses.size() < m_max_send_attempts;
149}
150
152{
153 Priority p;
154 p.num_picked = sent_to.size();
155 for (const auto& send_status : sent_to) {
156 p.last_picked = std::max(p.last_picked, send_status.picked);
157 if (send_status.confirmed.has_value()) {
158 ++p.num_confirmed;
159 p.last_confirmed = std::max(p.last_confirmed, send_status.confirmed.value());
160 }
161 }
162 return p;
163}
164
165std::optional<PrivateBroadcast::TxAndSendStatusForNode> PrivateBroadcast::GetSendStatusByNode(const NodeId& nodeid)
167{
168 AssertLockHeld(m_mutex);
169 for (auto& [tx, state] : m_transactions) {
170 for (auto& send_status : state.send_statuses) {
171 if (send_status.nodeid == nodeid) {
172 return TxAndSendStatusForNode{.tx = tx, .send_status = send_status};
173 }
174 }
175 }
176 return std::nullopt;
177}
#define Assume(val)
Assume is the identity function.
Definition: check.h:128
A combination of a network address (CNetAddr) and a (TCP) port.
Definition: netaddress.h:530
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.
AddResult
Outcome of Add().
bool HavePendingTransactions() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Check if there are transactions with send attempts remaining.
bool DidNodeConfirmReception(const NodeId &nodeid) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Check if the node has confirmed reception of the transaction.
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.
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
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:105
std::shared_ptr< const CTransaction > CTransactionRef
Definition: transaction.h:403
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.
NodeClock::time_point last_confirmed
The most recent time when the transaction was confirmed.
size_t num_confirmed
Number of nodes that have confirmed reception of a transaction (by PONG).
NodeClock::time_point last_picked
The most recent time when 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
#define LOCK(cs)
Definition: sync.h:268
#define EXCLUSIVE_LOCKS_REQUIRED(...)
Definition: threadsafety.h:49
AssertLockHeld(pool.cs)