Coverage Report

Created: 2026-10-07 13:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/tmp/bitcoin/src/private_broadcast.h
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
#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
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
    explicit PrivateBroadcast(size_t max_transactions = MAX_TRANSACTIONS,
60
                              size_t max_send_attempts = MAX_SEND_ATTEMPTS)
61
1.31k
        : m_max_transactions{max_transactions}, m_max_send_attempts{max_send_attempts} {}
62
63
    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
    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
32
        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
        auto operator<=>(const Priority& other) const
209
3
        {
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
3
        }
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
        size_t operator()(const CTransactionRef& tx) const
228
57.1k
        {
229
57.1k
            return static_cast<size_t>(tx->GetWitnessHash().ToUint256().GetUint64(0));
230
57.1k
        }
231
    };
232
233
    struct CTransactionRefComp {
234
        bool operator()(const CTransactionRef& a, const CTransactionRef& b) const
235
25
        {
236
25
            return a->GetWitnessHash() == b->GetWitnessHash(); // If wtxid equals, then txid also equals.
237
25
        }
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
    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