Branch data Line data Source code
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 [ + - ]: 2 : const std::string RECON_STATIC_SALT = "Tx Relay Salting";
19 : 2 : 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 : 0 : uint256 ComputeSalt(uint64_t salt1, uint64_t salt2)
27 : : {
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 : : }
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 : 0 : 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 : 0 : uint64_t PreRegisterPeer(NodeId peer_id) EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
83 : : {
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{GetRand(UINT64_MAX)};
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 : 0 : 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 : : {
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 : : }
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 : : 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 : 0 : void ForgetPeer(NodeId peer_id) EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
129 : : {
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 : 0 : bool IsPeerRegistered(NodeId peer_id) const EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
138 : : {
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 : 0 : uint64_t TxReconciliationTracker::PreRegisterPeer(NodeId peer_id)
152 : : {
153 : 0 : return m_impl->PreRegisterPeer(peer_id);
154 : : }
155 : :
156 : 0 : ReconciliationRegisterResult TxReconciliationTracker::RegisterPeer(NodeId peer_id, bool is_peer_inbound,
157 : : uint32_t peer_recon_version, uint64_t remote_salt)
158 : : {
159 : 0 : return m_impl->RegisterPeer(peer_id, is_peer_inbound, peer_recon_version, remote_salt);
160 : : }
161 : :
162 : 0 : void TxReconciliationTracker::ForgetPeer(NodeId peer_id)
163 : : {
164 : 0 : m_impl->ForgetPeer(peer_id);
165 : 0 : }
166 : :
167 : 0 : bool TxReconciliationTracker::IsPeerRegistered(NodeId peer_id) const
168 : : {
169 : 0 : return m_impl->IsPeerRegistered(peer_id);
170 : : }
|