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.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
}