private_broadcast.cpp raw
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
11
12 PrivateBroadcast::AddResult PrivateBroadcast::Add(const CTransactionRef& tx)
13 EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
14 {
15 LOCK(m_mutex);
16 // Re-adding an already-tracked transaction is a no-op regardless of the cap.
17 if (m_transactions.contains(tx)) return AddResult::AlreadyPresent;
18
19 if (m_transactions.size() >= m_max_transactions) return AddResult::QueueFull;
20
21 m_transactions.try_emplace(tx);
22 return AddResult::Added;
23 }
24
25 std::optional<size_t> PrivateBroadcast::Remove(const CTransactionRef& tx)
26 EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
27 {
28 LOCK(m_mutex);
29 const auto handle{m_transactions.extract(tx)};
30 if (handle) {
31 const auto p{DerivePriority(handle.mapped().send_statuses)};
32 return p.num_confirmed;
33 }
34 return std::nullopt;
35 }
36
37 std::optional<CTransactionRef> PrivateBroadcast::PickTxForSend(const NodeId& will_send_to_nodeid, const CService& will_send_to_address)
38 EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
39 {
40 LOCK(m_mutex);
41
42 if (GetSendStatusByNode(will_send_to_nodeid).has_value()) { // nodeid reuse, shouldn't send >1 tx to a given node
43 Assume(false);
44 return std::nullopt;
45 }
46
47 const auto it{std::ranges::max_element(
48 m_transactions,
49 [](const auto& a, const auto& b) { return a < b; },
50 [](const auto& el) { return DerivePriority(el.second.send_statuses); })};
51
52 if (it != m_transactions.end()) {
53 auto& [tx, state]{*it};
54 state.send_statuses.emplace_back(will_send_to_nodeid, will_send_to_address, NodeClock::now());
55 return tx;
56 }
57
58 return std::nullopt;
59 }
60
61 std::optional<CTransactionRef> PrivateBroadcast::GetTxForNode(const NodeId& nodeid)
62 EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
63 {
64 LOCK(m_mutex);
65 const auto tx_and_status{GetSendStatusByNode(nodeid)};
66 if (tx_and_status.has_value()) {
67 return tx_and_status.value().tx;
68 }
69 return std::nullopt;
70 }
71
72 void PrivateBroadcast::NodeConfirmedReception(const NodeId& nodeid)
73 EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
74 {
75 LOCK(m_mutex);
76 const auto tx_and_status{GetSendStatusByNode(nodeid)};
77 if (tx_and_status.has_value()) {
78 tx_and_status.value().send_status.confirmed = NodeClock::now();
79 }
80 }
81
82 bool PrivateBroadcast::DidNodeConfirmReception(const NodeId& nodeid)
83 EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
84 {
85 LOCK(m_mutex);
86 const auto tx_and_status{GetSendStatusByNode(nodeid)};
87 if (tx_and_status.has_value()) {
88 return tx_and_status.value().send_status.confirmed.has_value();
89 }
90 return false;
91 }
92
93 bool PrivateBroadcast::HavePendingTransactions()
94 EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
95 {
96 LOCK(m_mutex);
97 return !m_transactions.empty();
98 }
99
100 std::vector<CTransactionRef> PrivateBroadcast::GetStale() const
101 EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
102 {
103 LOCK(m_mutex);
104 const auto now{NodeClock::now()};
105 std::vector<CTransactionRef> stale;
106 for (const auto& [tx, state] : m_transactions) {
107 const Priority p{DerivePriority(state.send_statuses)};
108 if (p.num_confirmed == 0) {
109 if (state.time_added < now - INITIAL_STALE_DURATION) stale.push_back(tx);
110 } else {
111 if (p.last_confirmed < now - STALE_DURATION) stale.push_back(tx);
112 }
113 }
114 return stale;
115 }
116
117 std::vector<PrivateBroadcast::TxBroadcastInfo> PrivateBroadcast::GetBroadcastInfo() const
118 EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
119 {
120 LOCK(m_mutex);
121 std::vector<TxBroadcastInfo> entries;
122 entries.reserve(m_transactions.size());
123
124 for (const auto& [tx, state] : m_transactions) {
125 std::vector<PeerSendInfo> peers;
126 peers.reserve(state.send_statuses.size());
127 for (const auto& status : state.send_statuses) {
128 peers.emplace_back(PeerSendInfo{.address = status.address, .sent = status.picked, .received = status.confirmed});
129 }
130 entries.emplace_back(TxBroadcastInfo{.tx = tx, .time_added = state.time_added, .peers = std::move(peers)});
131 }
132
133 return entries;
134 }
135
136 PrivateBroadcast::Priority PrivateBroadcast::DerivePriority(const std::vector<SendStatus>& sent_to)
137 {
138 Priority p;
139 p.num_picked = sent_to.size();
140 for (const auto& send_status : sent_to) {
141 p.last_picked = std::max(p.last_picked, send_status.picked);
142 if (send_status.confirmed.has_value()) {
143 ++p.num_confirmed;
144 p.last_confirmed = std::max(p.last_confirmed, send_status.confirmed.value());
145 }
146 }
147 return p;
148 }
149
150 std::optional<PrivateBroadcast::TxAndSendStatusForNode> PrivateBroadcast::GetSendStatusByNode(const NodeId& nodeid)
151 EXCLUSIVE_LOCKS_REQUIRED(m_mutex)
152 {
153 AssertLockHeld(m_mutex);
154 for (auto& [tx, state] : m_transactions) {
155 for (auto& send_status : state.send_statuses) {
156 if (send_status.nodeid == nodeid) {
157 return TxAndSendStatusForNode{.tx = tx, .send_status = send_status};
158 }
159 }
160 }
161 return std::nullopt;
162 }
163