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