txreconciliation.cpp raw

   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