Coverage Report

Created: 2025-04-14 16:24

/Users/mcomp/contrib/bitcoin/src/node/txreconciliation.cpp
Line
Count
Source (jump to first uncovered line)
1
// Copyright (c) 2022 The Bitcoin Core developers
2
// Distributed under the MIT software license, see the accompanying
3
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5
#include <node/txreconciliation.h>
6
7
#include <common/system.h>
8
#include <logging.h>
9
#include <util/check.h>
10
11
#include <unordered_map>
12
#include <variant>
13
14
15
namespace {
16
17
/** Static salt component used to compute short txids for sketch construction, see BIP-330. */
18
const std::string RECON_STATIC_SALT = "Tx Relay Salting";
19
const HashWriter RECON_SALT_HASHER = TaggedHash(RECON_STATIC_SALT);
20
21
/**
22
 * Salt (specified by BIP-330) constructed from contributions from both peers. It is used
23
 * to compute transaction short IDs, which are then used to construct a sketch representing a set
24
 * of transactions we want to announce to the peer.
25
 */
26
uint256 ComputeSalt(uint64_t salt1, uint64_t salt2)
27
0
{
28
    // According to BIP-330, salts should be combined in ascending order.
29
0
    return (HashWriter(RECON_SALT_HASHER) << std::min(salt1, salt2) << std::max(salt1, salt2)).GetSHA256();
30
0
}
31
32
/**
33
 * Keeps track of txreconciliation-related per-peer state.
34
 */
35
class TxReconciliationState
36
{
37
public:
38
    /**
39
     * TODO: This field is public to ignore -Wunused-private-field. Make private once used in
40
     * the following commits.
41
     *
42
     * Reconciliation protocol assumes using one role consistently: either a reconciliation
43
     * initiator (requesting sketches), or responder (sending sketches). This defines our role,
44
     * based on the direction of the p2p connection.
45
     *
46
     */
47
    bool m_we_initiate;
48
49
    /**
50
     * TODO: These fields are public to ignore -Wunused-private-field. Make private once used in
51
     * the following commits.
52
     *
53
     * These values are used to salt short IDs, which is necessary for transaction reconciliations.
54
     */
55
    uint64_t m_k0, m_k1;
56
57
0
    TxReconciliationState(bool we_initiate, uint64_t k0, uint64_t k1) : m_we_initiate(we_initiate), m_k0(k0), m_k1(k1) {}
58
};
59
60
} // namespace
61
62
/** Actual implementation for TxReconciliationTracker's data structure. */
63
class TxReconciliationTracker::Impl
64
{
65
private:
66
    mutable Mutex m_txreconciliation_mutex;
67
68
    // Local protocol version
69
    uint32_t m_recon_version;
70
71
    /**
72
     * Keeps track of txreconciliation states of eligible peers.
73
     * For pre-registered peers, the locally generated salt is stored.
74
     * For registered peers, the locally generated salt is forgotten, and the state (including
75
     * "full" salt) is stored instead.
76
     */
77
    std::unordered_map<NodeId, std::variant<uint64_t, TxReconciliationState>> m_states GUARDED_BY(m_txreconciliation_mutex);
78
79
public:
80
0
    explicit Impl(uint32_t recon_version) : m_recon_version(recon_version) {}
81
82
    uint64_t PreRegisterPeer(NodeId peer_id) EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
83
0
    {
84
0
        AssertLockNotHeld(m_txreconciliation_mutex);
85
0
        LOCK(m_txreconciliation_mutex);
86
87
0
        LogPrintLevel(BCLog::TXRECONCILIATION, BCLog::Level::Debug, "Pre-register peer=%d\n", peer_id);
88
0
        const uint64_t local_salt{FastRandomContext().rand64()};
89
90
        // We do this exactly once per peer (which are unique by NodeId, see GetNewNodeId) so it's
91
        // safe to assume we don't have this record yet.
92
0
        Assume(m_states.emplace(peer_id, local_salt).second);
93
0
        return local_salt;
94
0
    }
95
96
    ReconciliationRegisterResult RegisterPeer(NodeId peer_id, bool is_peer_inbound, uint32_t peer_recon_version,
97
                                              uint64_t remote_salt) EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
98
0
    {
99
0
        AssertLockNotHeld(m_txreconciliation_mutex);
100
0
        LOCK(m_txreconciliation_mutex);
101
0
        auto recon_state = m_states.find(peer_id);
102
103
0
        if (recon_state == m_states.end()) return ReconciliationRegisterResult::NOT_FOUND;
104
105
0
        if (std::holds_alternative<TxReconciliationState>(recon_state->second)) {
106
0
            return ReconciliationRegisterResult::ALREADY_REGISTERED;
107
0
        }
108
109
0
        uint64_t local_salt = *std::get_if<uint64_t>(&recon_state->second);
110
111
        // If the peer supports the version which is lower than ours, we downgrade to the version
112
        // it supports. For now, this only guarantees that nodes with future reconciliation
113
        // versions have the choice of reconciling with this current version. However, they also
114
        // have the choice to refuse supporting reconciliations if the common version is not
115
        // satisfactory (e.g. too low).
116
0
        const uint32_t recon_version{std::min(peer_recon_version, m_recon_version)};
117
        // v1 is the lowest version, so suggesting something below must be a protocol violation.
118
0
        if (recon_version < 1) return ReconciliationRegisterResult::PROTOCOL_VIOLATION;
119
120
0
        LogPrintLevel(BCLog::TXRECONCILIATION, BCLog::Level::Debug, "Register peer=%d (inbound=%i)\n",
121
0
                      peer_id, is_peer_inbound);
122
123
0
        const uint256 full_salt{ComputeSalt(local_salt, remote_salt)};
124
0
        recon_state->second = TxReconciliationState(!is_peer_inbound, full_salt.GetUint64(0), full_salt.GetUint64(1));
125
0
        return ReconciliationRegisterResult::SUCCESS;
126
0
    }
127
128
    void ForgetPeer(NodeId peer_id) EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
129
0
    {
130
0
        AssertLockNotHeld(m_txreconciliation_mutex);
131
0
        LOCK(m_txreconciliation_mutex);
132
0
        if (m_states.erase(peer_id)) {
133
0
            LogPrintLevel(BCLog::TXRECONCILIATION, BCLog::Level::Debug, "Forget txreconciliation state of peer=%d\n", peer_id);
134
0
        }
135
0
    }
136
137
    bool IsPeerRegistered(NodeId peer_id) const EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
138
0
    {
139
0
        AssertLockNotHeld(m_txreconciliation_mutex);
140
0
        LOCK(m_txreconciliation_mutex);
141
0
        auto recon_state = m_states.find(peer_id);
142
0
        return (recon_state != m_states.end() &&
143
0
                std::holds_alternative<TxReconciliationState>(recon_state->second));
144
0
    }
145
};
146
147
0
TxReconciliationTracker::TxReconciliationTracker(uint32_t recon_version) : m_impl{std::make_unique<TxReconciliationTracker::Impl>(recon_version)} {}
148
149
0
TxReconciliationTracker::~TxReconciliationTracker() = default;
150
151
uint64_t TxReconciliationTracker::PreRegisterPeer(NodeId peer_id)
152
0
{
153
0
    return m_impl->PreRegisterPeer(peer_id);
154
0
}
155
156
ReconciliationRegisterResult TxReconciliationTracker::RegisterPeer(NodeId peer_id, bool is_peer_inbound,
157
                                                          uint32_t peer_recon_version, uint64_t remote_salt)
158
0
{
159
0
    return m_impl->RegisterPeer(peer_id, is_peer_inbound, peer_recon_version, remote_salt);
160
0
}
161
162
void TxReconciliationTracker::ForgetPeer(NodeId peer_id)
163
0
{
164
0
    m_impl->ForgetPeer(peer_id);
165
0
}
166
167
bool TxReconciliationTracker::IsPeerRegistered(NodeId peer_id) const
168
0
{
169
0
    return m_impl->IsPeerRegistered(peer_id);
170
0
}