/tmp/bitcoin/src/private_broadcast.cpp
Line | Count | Source |
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 | | |
13 | | PrivateBroadcast::AddResult PrivateBroadcast::Add(const CTransactionRef& tx) |
14 | | EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) |
15 | 20.0k | { |
16 | 20.0k | LOCK(m_mutex); |
17 | | // Cleanup finished transactions |
18 | 100M | std::erase_if(m_transactions, [this](const auto& entry) { |
19 | 100M | const auto& state{entry.second}; |
20 | 100M | return state.resolved && !IsPending(state) && |
21 | 100M | std::ranges::all_of(state.send_statuses, [](const auto& status) { return status.disconnected; }); |
22 | 100M | }); |
23 | | |
24 | 20.0k | if (const auto it{m_transactions.find(tx)}; it != m_transactions.end()) { |
25 | 9 | 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 | 2 | it->second.time_added = NodeClock::now(); |
29 | 2 | it->second.send_statuses.clear(); |
30 | 2 | it->second.planned_sends = INITIAL_CONNECTION_COUNT; |
31 | 2 | it->second.resolved = false; |
32 | 2 | return AddResult::Added; |
33 | 9 | } |
34 | | |
35 | 20.0k | if (m_transactions.size() >= m_max_transactions) return AddResult::QueueFull; |
36 | | |
37 | 20.0k | m_transactions.try_emplace(tx); |
38 | 20.0k | return AddResult::Added; |
39 | 20.0k | } |
40 | | |
41 | | std::optional<size_t> PrivateBroadcast::Remove(const CTransactionRef& tx) |
42 | | EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) |
43 | 9 | { |
44 | 9 | LOCK(m_mutex); |
45 | 9 | const auto handle{m_transactions.extract(tx)}; |
46 | 9 | if (handle) { |
47 | 7 | const auto& state{handle.mapped()}; |
48 | 7 | const size_t planned{std::min(state.planned_sends, m_max_send_attempts)}; |
49 | 7 | const size_t actual{state.send_statuses.size()}; |
50 | 7 | return planned > actual ? planned - actual : 0; |
51 | 7 | } |
52 | 2 | return std::nullopt; |
53 | 9 | } |
54 | | |
55 | | bool PrivateBroadcast::MarkResolved(const CTransactionRef& tx) |
56 | 17.1k | { |
57 | 17.1k | LOCK(m_mutex); |
58 | 17.1k | const auto it{m_transactions.find(tx)}; |
59 | 17.1k | if (it == m_transactions.end()) return false; |
60 | 5 | it->second.resolved = true; |
61 | 5 | return true; |
62 | 17.1k | } |
63 | | |
64 | | void PrivateBroadcast::NodeDisconnected(NodeId nodeid) |
65 | 26 | { |
66 | 26 | LOCK(m_mutex); |
67 | 26 | if (const auto entry{GetSendStatusByNode(nodeid)}) { |
68 | 20 | entry->send_status.disconnected = true; |
69 | 20 | } |
70 | 26 | } |
71 | | |
72 | | bool PrivateBroadcast::TryGrantRetry(const CTransactionRef& tx) |
73 | 4 | { |
74 | 4 | LOCK(m_mutex); |
75 | 4 | const auto it{m_transactions.find(tx)}; |
76 | 4 | if (it == m_transactions.end() || |
77 | 4 | it->second.resolved || |
78 | 4 | IsPending(it->second) || |
79 | 4 | it->second.planned_sends >= m_max_send_attempts) return false; |
80 | 3 | ++it->second.planned_sends; |
81 | 3 | return true; |
82 | 4 | } |
83 | | |
84 | | std::optional<CTransactionRef> PrivateBroadcast::PickTxForSend(const NodeId& will_send_to_nodeid, const CService& will_send_to_address) |
85 | | EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) |
86 | 36 | { |
87 | 36 | LOCK(m_mutex); |
88 | | |
89 | 36 | if (GetSendStatusByNode(will_send_to_nodeid).has_value()) { // nodeid reuse, shouldn't send >1 tx to a given node |
90 | 0 | Assume(false); |
91 | 0 | return std::nullopt; |
92 | 0 | } |
93 | | |
94 | 42 | auto pending_transactions{m_transactions | std::views::filter([this](const auto& entry) { return IsPending(entry.second); })}; |
95 | 36 | const auto it{std::ranges::max_element( |
96 | 36 | pending_transactions, |
97 | 36 | [](const auto& a, const auto& b) { return a < b; }, |
98 | 36 | [](const auto& el) { return DerivePriority(el.second.send_statuses); })}; |
99 | | |
100 | 36 | if (it != pending_transactions.end()) { |
101 | 32 | auto& [tx, state]{*it}; |
102 | 32 | state.send_statuses.emplace_back(will_send_to_nodeid, will_send_to_address, NodeClock::now()); |
103 | 32 | return tx; |
104 | 32 | } |
105 | | |
106 | 4 | return std::nullopt; |
107 | 36 | } |
108 | | |
109 | | std::optional<CTransactionRef> PrivateBroadcast::GetTxForNode(const NodeId& nodeid) |
110 | | EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) |
111 | 24 | { |
112 | 24 | LOCK(m_mutex); |
113 | 24 | const auto tx_and_status{GetSendStatusByNode(nodeid)}; |
114 | 24 | if (tx_and_status.has_value()) { |
115 | 22 | return tx_and_status.value().tx; |
116 | 22 | } |
117 | 2 | return std::nullopt; |
118 | 24 | } |
119 | | |
120 | | void PrivateBroadcast::NodeConfirmedReception(const NodeId& nodeid) |
121 | | EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) |
122 | 22 | { |
123 | 22 | LOCK(m_mutex); |
124 | 22 | const auto tx_and_status{GetSendStatusByNode(nodeid)}; |
125 | 22 | if (tx_and_status.has_value()) { |
126 | 21 | tx_and_status.value().send_status.confirmed = NodeClock::now(); |
127 | 21 | } |
128 | 22 | } |
129 | | |
130 | | bool PrivateBroadcast::HavePendingTransactions() |
131 | | EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) |
132 | 17 | { |
133 | 17 | LOCK(m_mutex); |
134 | 17 | return std::ranges::any_of(m_transactions, [this](const auto& entry) { return IsPending(entry.second); }); |
135 | 17 | } |
136 | | |
137 | | std::vector<CTransactionRef> PrivateBroadcast::GetStale() const |
138 | | EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) |
139 | 18 | { |
140 | 18 | LOCK(m_mutex); |
141 | 18 | const auto now{NodeClock::now()}; |
142 | 18 | std::vector<CTransactionRef> stale; |
143 | 18 | for (const auto& [tx, state] : m_transactions) { |
144 | 16 | if (state.resolved || state.planned_sends >= m_max_send_attempts) continue; |
145 | 15 | const Priority p{DerivePriority(state.send_statuses)}; |
146 | 15 | if (p.num_confirmed == 0) { |
147 | 11 | if (state.time_added < now - INITIAL_STALE_DURATION) stale.push_back(tx); |
148 | 11 | } else { |
149 | 4 | if (p.last_confirmed < now - STALE_DURATION) stale.push_back(tx); |
150 | 4 | } |
151 | 15 | } |
152 | 18 | return stale; |
153 | 18 | } |
154 | | |
155 | | std::vector<PrivateBroadcast::TxBroadcastInfo> PrivateBroadcast::GetBroadcastInfo() const |
156 | | EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) |
157 | 33 | { |
158 | 33 | LOCK(m_mutex); |
159 | 33 | std::vector<TxBroadcastInfo> entries; |
160 | 33 | entries.reserve(m_transactions.size()); |
161 | | |
162 | 130k | for (const auto& [tx, state] : m_transactions) { |
163 | 130k | if (state.resolved) continue; |
164 | 130k | std::vector<PeerSendInfo> peers; |
165 | 130k | peers.reserve(state.send_statuses.size()); |
166 | 130k | for (const auto& status : state.send_statuses) { |
167 | 45 | peers.emplace_back(PeerSendInfo{.address = status.address, .sent = status.picked, .received = status.confirmed}); |
168 | 45 | } |
169 | 130k | const size_t attempts_remaining{m_max_send_attempts - std::min(state.send_statuses.size(), m_max_send_attempts)}; |
170 | 130k | entries.emplace_back(TxBroadcastInfo{.tx = tx, .time_added = state.time_added, .attempts_remaining = attempts_remaining, .peers = std::move(peers)}); |
171 | 130k | } |
172 | | |
173 | 33 | return entries; |
174 | 33 | } |
175 | | |
176 | | bool PrivateBroadcast::IsPending(const TxSendStatus& status) const |
177 | 64 | { |
178 | | // Deliberately ignore resolved so all initially scheduled connections can complete. |
179 | 64 | const size_t limit{std::min(status.planned_sends, m_max_send_attempts)}; |
180 | 64 | return status.send_statuses.size() < limit; |
181 | 64 | } |
182 | | |
183 | | PrivateBroadcast::Priority PrivateBroadcast::DerivePriority(const std::vector<SendStatus>& sent_to) |
184 | 21 | { |
185 | 21 | Priority p; |
186 | 21 | p.num_picked = sent_to.size(); |
187 | 21 | for (const auto& send_status : sent_to) { |
188 | 17 | p.last_picked = std::max(p.last_picked, send_status.picked); |
189 | 17 | if (send_status.confirmed.has_value()) { |
190 | 8 | ++p.num_confirmed; |
191 | 8 | p.last_confirmed = std::max(p.last_confirmed, send_status.confirmed.value()); |
192 | 8 | } |
193 | 17 | } |
194 | 21 | return p; |
195 | 21 | } |
196 | | |
197 | | std::optional<PrivateBroadcast::TxAndSendStatusForNode> PrivateBroadcast::GetSendStatusByNode(const NodeId& nodeid) |
198 | | EXCLUSIVE_LOCKS_REQUIRED(m_mutex) |
199 | 108 | { |
200 | 108 | AssertLockHeld(m_mutex); |
201 | 117 | for (auto& [tx, state] : m_transactions) { |
202 | 199 | for (auto& send_status : state.send_statuses) { |
203 | 199 | if (send_status.nodeid == nodeid) { |
204 | 63 | return TxAndSendStatusForNode{.tx = tx, .send_status = send_status}; |
205 | 63 | } |
206 | 199 | } |
207 | 117 | } |
208 | 45 | return std::nullopt; |
209 | 108 | } |