1 // Copyright (c) 2022-present 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 <util/check.h>
9 #include <util/log.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 {
28 // According to BIP-330, salts should be combined in ascending order.
29 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 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 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 {
84 AssertLockNotHeld(m_txreconciliation_mutex);
85 LOCK(m_txreconciliation_mutex);
86 87 LogDebug(BCLog::TXRECONCILIATION, "Pre-register peer=%d\n", peer_id);
88 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 Assume(m_states.emplace(peer_id, local_salt).second);
93 return local_salt;
94 }
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 {
99 AssertLockNotHeld(m_txreconciliation_mutex);
100 LOCK(m_txreconciliation_mutex);
101 auto recon_state = m_states.find(peer_id);
102 103 if (recon_state == m_states.end()) return ReconciliationRegisterResult::NOT_FOUND;
104 105 if (std::holds_alternative<TxReconciliationState>(recon_state->second)) {
106 return ReconciliationRegisterResult::ALREADY_REGISTERED;
107 }
108 109 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 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 if (recon_version < 1) return ReconciliationRegisterResult::PROTOCOL_VIOLATION;
119 120 LogDebug(BCLog::TXRECONCILIATION, "Register peer=%d (inbound=%i)\n",
121 peer_id, is_peer_inbound);
122 123 const uint256 full_salt{ComputeSalt(local_salt, remote_salt)};
124 recon_state->second = TxReconciliationState(!is_peer_inbound, full_salt.GetUint64(0), full_salt.GetUint64(1));
125 return ReconciliationRegisterResult::SUCCESS;
126 }
127 128 void ForgetPeer(NodeId peer_id) EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
129 {
130 AssertLockNotHeld(m_txreconciliation_mutex);
131 LOCK(m_txreconciliation_mutex);
132 if (m_states.erase(peer_id)) {
133 LogDebug(BCLog::TXRECONCILIATION, "Forget txreconciliation state of peer=%d\n", peer_id);
134 }
135 }
136 137 bool IsPeerRegistered(NodeId peer_id) const EXCLUSIVE_LOCKS_REQUIRED(!m_txreconciliation_mutex)
138 {
139 AssertLockNotHeld(m_txreconciliation_mutex);
140 LOCK(m_txreconciliation_mutex);
141 auto recon_state = m_states.find(peer_id);
142 return (recon_state != m_states.end() &&
143 std::holds_alternative<TxReconciliationState>(recon_state->second));
144 }
145 };
146 147 TxReconciliationTracker::TxReconciliationTracker(uint32_t recon_version) : m_impl{std::make_unique<TxReconciliationTracker::Impl>(recon_version)} {}
148 149 TxReconciliationTracker::~TxReconciliationTracker() = default;
150 151 uint64_t TxReconciliationTracker::PreRegisterPeer(NodeId peer_id)
152 {
153 return m_impl->PreRegisterPeer(peer_id);
154 }
155 156 ReconciliationRegisterResult TxReconciliationTracker::RegisterPeer(NodeId peer_id, bool is_peer_inbound,
157 uint32_t peer_recon_version, uint64_t remote_salt)
158 {
159 return m_impl->RegisterPeer(peer_id, is_peer_inbound, peer_recon_version, remote_salt);
160 }
161 162 void TxReconciliationTracker::ForgetPeer(NodeId peer_id)
163 {
164 m_impl->ForgetPeer(peer_id);
165 }
166 167 bool TxReconciliationTracker::IsPeerRegistered(NodeId peer_id) const
168 {
169 return m_impl->IsPeerRegistered(peer_id);
170 }
171