Coverage Report

Created: 2026-09-14 20:36

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