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 2 : /** Static salt component used to compute short txids for sketch construction, see BIP-330. */ 18 4 : 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 : 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 2 : * 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 2 : { 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 : }