Branch data Line data Source code
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 : : #ifndef BITCOIN_PRIVATE_BROADCAST_H
6 : : #define BITCOIN_PRIVATE_BROADCAST_H
7 : :
8 : : #include <net.h>
9 : : #include <primitives/transaction.h>
10 : : #include <primitives/transaction_identifier.h>
11 : : #include <sync.h>
12 : : #include <util/time.h>
13 : :
14 : : #include <optional>
15 : : #include <tuple>
16 : : #include <unordered_map>
17 : : #include <vector>
18 : :
19 : : /**
20 : : * Store a list of transactions to be broadcast privately. Supports the following operations:
21 : : * - Add a new transaction
22 : : * - Remove a transaction
23 : : * - Mark a transaction as resolved, so no more retry send attempts are granted
24 : : * - Mark a connection finished
25 : : * - Grant an additional send attempt for an unresolved transaction
26 : : * - Pick a transaction for sending to one recipient
27 : : * - Query which transaction has been picked for sending to a given recipient node
28 : : * - Mark that a given recipient node has confirmed receipt of a transaction
29 : : * - Query whether any transactions that need sending are currently on the list
30 : : */
31 : 6 : class PrivateBroadcast
32 : : {
33 : : public:
34 : :
35 : : /// Number of connections to make for initial broadcast.
36 : : static constexpr size_t INITIAL_CONNECTION_COUNT{3};
37 : :
38 : : /// If a transaction is not sent to any peer for this duration,
39 : : /// then we consider it stale / for rebroadcasting.
40 : : static constexpr auto INITIAL_STALE_DURATION{5min};
41 : :
42 : : /// If a transaction is not received back from the network for this duration
43 : : /// after it is broadcast, then we consider it stale / for rebroadcasting.
44 : : static constexpr auto STALE_DURATION{1min};
45 : :
46 : : /// Maximum number of transactions tracked simultaneously.
47 : : /// Additions that would exceed this are rejected (see Add()).
48 : : static constexpr size_t MAX_TRANSACTIONS{10'000};
49 : :
50 : : /// Maximum number of send attempts for a transaction. Once this limit is
51 : : /// reached, the transaction remains tracked but is not sent again unless
52 : : /// explicitly re-added.
53 : : static constexpr size_t MAX_SEND_ATTEMPTS{1'000};
54 : :
55 : : /// @param[in] max_transactions Cap on the number of simultaneously tracked
56 : : /// transactions. Defaults to MAX_TRANSACTIONS.
57 : : /// @param[in] max_send_attempts Cap on the number of send attempts per
58 : : /// transaction. Defaults to MAX_SEND_ATTEMPTS.
59 : 201 : explicit PrivateBroadcast(size_t max_transactions = MAX_TRANSACTIONS,
60 : : size_t max_send_attempts = MAX_SEND_ATTEMPTS)
61 [ + - + - : 201 : : m_max_transactions{max_transactions}, m_max_send_attempts{max_send_attempts} {}
+ - + - +
- + - ]
62 : :
63 : 39 : struct PeerSendInfo {
64 : : CService address;
65 : : NodeClock::time_point sent;
66 : : std::optional<NodeClock::time_point> received;
67 : : };
68 : :
69 : : struct TxBroadcastInfo {
70 : : CTransactionRef tx;
71 : : NodeClock::time_point time_added;
72 : : /// Number of additional send attempts allowed for this transaction (0 if exhausted).
73 : : size_t attempts_remaining;
74 : : std::vector<PeerSendInfo> peers;
75 : : };
76 : :
77 : : /// Outcome of Add().
78 : : enum class AddResult {
79 : : //! The transaction was newly added or reset after exhausting its send attempts.
80 : : Added,
81 : : //! The transaction was already present with send attempts remaining; no change.
82 : : AlreadyPresent,
83 : : //! Rejected: the queue is already at MAX_TRANSACTIONS.
84 : : QueueFull,
85 : : };
86 : :
87 : : /**
88 : : * Add a transaction to the storage, or reset an exhausted transaction so it
89 : : * can be broadcast again.
90 : : * @param[in] tx The transaction to add.
91 : : * @return Whether the transaction was newly added or reset, was already
92 : : * present with send attempts remaining, or was rejected because the queue is
93 : : * full (see AddResult).
94 : : */
95 : : [[nodiscard]] AddResult Add(const CTransactionRef& tx)
96 : : EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
97 : :
98 : : /**
99 : : * Forget a transaction.
100 : : * @param[in] tx Transaction to forget.
101 : : * @retval !nullopt The number of planned send attempts not yet picked
102 : : * (if the transaction existed and was removed).
103 : : * @retval nullopt The transaction was not in the storage.
104 : : */
105 : : std::optional<size_t> Remove(const CTransactionRef& tx)
106 : : EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
107 : :
108 : : /**
109 : : * Mark a transaction as resolved because it was received back from the network
110 : : * or is no longer acceptable to the mempool.
111 : : * @param[in] tx Transaction to resolve.
112 : : * @return Whether the transaction was found in the storage.
113 : : */
114 : : bool MarkResolved(const CTransactionRef& tx)
115 : : EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
116 : :
117 : : /**
118 : : * Mark a connection finished.
119 : : * @param[in] nodeid Node whose connection has ended.
120 : : */
121 : : void NodeDisconnected(NodeId nodeid)
122 : : EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
123 : :
124 : : /**
125 : : * Grant one additional send attempt if the transaction is unresolved, all
126 : : * planned sends have been picked, and the attempt limit has not been reached.
127 : : * The caller schedules the connection separately.
128 : : * @param[in] tx Transaction to retry.
129 : : * @return Whether an additional send attempt was granted.
130 : : */
131 : : bool TryGrantRetry(const CTransactionRef& tx)
132 : : EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
133 : :
134 : : /**
135 : : * Pick the transaction with the fewest send attempts, and confirmations,
136 : : * and oldest send/confirm times.
137 : : * @param[in] will_send_to_nodeid Will remember that the returned transaction
138 : : * was picked for sending to this node. Calling this method more than once with
139 : : * the same `will_send_to_nodeid` is not allowed because sending more than one
140 : : * transaction to one node would be a privacy leak.
141 : : * @param[in] will_send_to_address Address of the peer to which this transaction
142 : : * will be sent.
143 : : * @return Most urgent transaction or nullopt if there are no transactions
144 : : * with send attempts remaining.
145 : : */
146 : : std::optional<CTransactionRef> PickTxForSend(const NodeId& will_send_to_nodeid, const CService& will_send_to_address)
147 : : EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
148 : :
149 : : /**
150 : : * Get the transaction that was picked for sending to a given node by PickTxForSend().
151 : : * @param[in] nodeid Node to which a transaction is being (or was) sent.
152 : : * @return Transaction or nullopt if the nodeid is unknown.
153 : : */
154 : : std::optional<CTransactionRef> GetTxForNode(const NodeId& nodeid)
155 : : EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
156 : :
157 : : /**
158 : : * Mark that the node has confirmed reception of the transaction we sent it by
159 : : * responding with `PONG` to our `PING` message.
160 : : * @param[in] nodeid Node that we sent a transaction to.
161 : : */
162 : : void NodeConfirmedReception(const NodeId& nodeid)
163 : : EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
164 : :
165 : : /**
166 : : * Check if there are transactions with send attempts remaining.
167 : : */
168 : : bool HavePendingTransactions()
169 : : EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
170 : :
171 : : /**
172 : : * Get the transactions that have not been broadcast recently and have send
173 : : * attempts remaining.
174 : : */
175 : : std::vector<CTransactionRef> GetStale() const
176 : : EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
177 : :
178 : : /**
179 : : * Get stats about all transactions currently being privately broadcast.
180 : : */
181 : : std::vector<TxBroadcastInfo> GetBroadcastInfo() const
182 : : EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
183 : :
184 : : private:
185 : : /// Status of a transaction sent to a given node.
186 : 23 : struct SendStatus {
187 : : /// Node to which the transaction will be sent (or was sent).
188 : : const NodeId nodeid;
189 : : /// Address of the node.
190 : : const CService address;
191 : : /// When was the transaction picked for sending to the node.
192 : : const NodeClock::time_point picked;
193 : : /// When was the transaction reception confirmed by the node (by PONG).
194 : : std::optional<NodeClock::time_point> confirmed;
195 : : /// Whether the node has disconnected.
196 : : bool disconnected{false};
197 : :
198 : 13 : SendStatus(const NodeId& nodeid, const CService& address, const NodeClock::time_point& picked) : nodeid{nodeid}, address{address}, picked{picked} {}
199 : : };
200 : :
201 : : /// Cumulative stats from all the send attempts for a transaction. Used to prioritize transactions.
202 : : struct Priority {
203 : : size_t num_picked{0}; ///< Number of times the transaction was picked for sending.
204 : : NodeClock::time_point last_picked{}; ///< The most recent time when the transaction was picked for sending.
205 : : size_t num_confirmed{0}; ///< Number of nodes that have confirmed reception of a transaction (by PONG).
206 : : NodeClock::time_point last_confirmed{}; ///< The most recent time when the transaction was confirmed.
207 : :
208 : 3 : auto operator<=>(const Priority& other) const
209 : : {
210 : : // Invert `other` and `this` in the comparison because smaller num_picked, num_confirmed or
211 : : // earlier times mean greater priority. In other words, if this.num_picked < other.num_picked
212 : : // then this > other.
213 : 3 : return std::tie(other.num_picked, other.num_confirmed, other.last_picked, other.last_confirmed) <=>
214 : 3 : std::tie(num_picked, num_confirmed, last_picked, last_confirmed);
215 : : }
216 : : };
217 : :
218 : : /// A pair of a transaction and a sent status for a given node. Convenience return type of GetSendStatusByNode().
219 : : struct TxAndSendStatusForNode {
220 : : const CTransactionRef& tx;
221 : : SendStatus& send_status;
222 : : };
223 : :
224 : : // No need for salted hasher because we are going to store just a bunch of locally originating transactions.
225 : :
226 : : struct CTransactionRefHash {
227 : 20048 : size_t operator()(const CTransactionRef& tx) const
228 : : {
229 [ + + ]: 20048 : return static_cast<size_t>(tx->GetWitnessHash().ToUint256().GetUint64(0));
230 : : }
231 : : };
232 : :
233 : : struct CTransactionRefComp {
234 : 16 : bool operator()(const CTransactionRef& a, const CTransactionRef& b) const
235 : : {
236 [ - + - - ]: 16 : return a->GetWitnessHash() == b->GetWitnessHash(); // If wtxid equals, then txid also equals.
237 : : }
238 : : };
239 : :
240 : : /**
241 : : * Derive the sending priority of a transaction.
242 : : * @param[in] sent_to List of nodes that the transaction has been sent to.
243 : : */
244 : : static Priority DerivePriority(const std::vector<SendStatus>& sent_to);
245 : :
246 : : /**
247 : : * Find which transaction we sent to a given node (marked by PickTxForSend()).
248 : : * @return That transaction together with the send status or nullopt if we did not
249 : : * send any transaction to the given node.
250 : : */
251 : : std::optional<TxAndSendStatusForNode> GetSendStatusByNode(const NodeId& nodeid)
252 : : EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
253 : 10009 : struct TxSendStatus {
254 : : NodeClock::time_point time_added{NodeClock::now()};
255 : : std::vector<SendStatus> send_statuses;
256 : : /// Total number of sends granted, including initial count.
257 : : size_t planned_sends{INITIAL_CONNECTION_COUNT};
258 : : /// Whether the transaction no longer needs to be retried.
259 : : bool resolved{false};
260 : : };
261 : : bool IsPending(const TxSendStatus& status) const;
262 : : /// Cap on the number of simultaneously tracked transactions (see Add()).
263 : : const size_t m_max_transactions;
264 : : /// Cap on the number of send attempts per transaction (see PickTxForSend()).
265 : : const size_t m_max_send_attempts;
266 : : mutable Mutex m_mutex;
267 : : std::unordered_map<CTransactionRef, TxSendStatus, CTransactionRefHash, CTransactionRefComp>
268 : : m_transactions GUARDED_BY(m_mutex);
269 : : };
270 : :
271 : : #endif // BITCOIN_PRIVATE_BROADCAST_H
|