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