addrman_impl.h raw

   1  // Copyright (c) 2021-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  #ifndef BITCOIN_ADDRMAN_IMPL_H
   6  #define BITCOIN_ADDRMAN_IMPL_H
   7  
   8  #include <logging/timer.h>
   9  #include <netaddress.h>
  10  #include <protocol.h>
  11  #include <serialize.h>
  12  #include <sync.h>
  13  #include <uint256.h>
  14  #include <util/log.h>
  15  #include <util/time.h>
  16  
  17  #include <cstdint>
  18  #include <optional>
  19  #include <set>
  20  #include <unordered_map>
  21  #include <unordered_set>
  22  #include <utility>
  23  #include <vector>
  24  
  25  /** Total number of buckets for tried addresses */
  26  static constexpr int32_t ADDRMAN_TRIED_BUCKET_COUNT_LOG2{8};
  27  static constexpr int ADDRMAN_TRIED_BUCKET_COUNT{1 << ADDRMAN_TRIED_BUCKET_COUNT_LOG2};
  28  /** Total number of buckets for new addresses */
  29  static constexpr int32_t ADDRMAN_NEW_BUCKET_COUNT_LOG2{10};
  30  static constexpr int ADDRMAN_NEW_BUCKET_COUNT{1 << ADDRMAN_NEW_BUCKET_COUNT_LOG2};
  31  /** Maximum allowed number of entries in buckets for new and tried addresses */
  32  static constexpr int32_t ADDRMAN_BUCKET_SIZE_LOG2{6};
  33  static constexpr int ADDRMAN_BUCKET_SIZE{1 << ADDRMAN_BUCKET_SIZE_LOG2};
  34  
  35  /**
  36   * User-defined type for the internally used nIds
  37   * This used to be int, making it feasible for attackers to cause an overflow,
  38   * see https://bitcoincore.org/en/2024/07/31/disclose-addrman-int-overflow/
  39   */
  40  using nid_type = int64_t;
  41  
  42  /**
  43   * Extended statistics about a CAddress
  44   */
  45  class AddrInfo : public CAddress
  46  {
  47  public:
  48      //! last try whatsoever by us (memory only)
  49      NodeSeconds m_last_try{0s};
  50  
  51      //! last counted attempt (memory only)
  52      NodeSeconds m_last_count_attempt{0s};
  53  
  54      //! where knowledge about this address first came from
  55      CNetAddr source;
  56  
  57      //! last successful connection by us
  58      NodeSeconds m_last_success{0s};
  59  
  60      //! connection attempts since last successful attempt
  61      int nAttempts{0};
  62  
  63      //! reference count in new sets (memory only)
  64      int nRefCount{0};
  65  
  66      //! in tried set? (memory only)
  67      bool fInTried{false};
  68  
  69      //! position in vRandom
  70      mutable int nRandomPos{-1};
  71  
  72      SERIALIZE_METHODS(AddrInfo, obj)
  73      {
  74          READWRITE(AsBase<CAddress>(obj), obj.source, Using<ChronoFormatter<int64_t>>(obj.m_last_success), obj.nAttempts);
  75      }
  76  
  77      AddrInfo(const CAddress &addrIn, const CNetAddr &addrSource) : CAddress(addrIn), source(addrSource)
  78      {
  79      }
  80  
  81      AddrInfo() : CAddress(), source()
  82      {
  83      }
  84  
  85      //! Calculate in which "tried" bucket this entry belongs
  86      int GetTriedBucket(const uint256& nKey, const NetGroupManager& netgroupman) const;
  87  
  88      //! Calculate in which "new" bucket this entry belongs, given a certain source
  89      int GetNewBucket(const uint256& nKey, const CNetAddr& src, const NetGroupManager& netgroupman) const;
  90  
  91      //! Calculate in which "new" bucket this entry belongs, using its default source
  92      int GetNewBucket(const uint256& nKey, const NetGroupManager& netgroupman) const
  93      {
  94          return GetNewBucket(nKey, source, netgroupman);
  95      }
  96  
  97      //! Calculate in which position of a bucket to store this entry.
  98      int GetBucketPosition(const uint256 &nKey, bool fNew, int bucket) const;
  99  
 100      //! Determine whether the statistics about this entry are bad enough so that it can just be deleted
 101      bool IsTerrible(NodeSeconds now = Now<NodeSeconds>()) const;
 102  
 103      //! Calculate the relative chance this entry should be given when selecting nodes to connect to
 104      double GetChance(NodeSeconds now = Now<NodeSeconds>()) const;
 105  };
 106  
 107  class AddrManImpl
 108  {
 109  public:
 110      AddrManImpl(const NetGroupManager& netgroupman, bool deterministic, int32_t consistency_check_ratio);
 111  
 112      ~AddrManImpl();
 113  
 114      template <typename Stream>
 115      void Serialize(Stream& s_) const EXCLUSIVE_LOCKS_REQUIRED(!cs);
 116  
 117      template <typename Stream>
 118      void Unserialize(Stream& s_) EXCLUSIVE_LOCKS_REQUIRED(!cs);
 119  
 120      size_t Size(std::optional<Network> net, std::optional<bool> in_new) const EXCLUSIVE_LOCKS_REQUIRED(!cs);
 121  
 122      bool Add(const std::vector<CAddress>& vAddr, const CNetAddr& source, std::chrono::seconds time_penalty)
 123          EXCLUSIVE_LOCKS_REQUIRED(!cs);
 124  
 125      bool Good(const CService& addr, NodeSeconds time)
 126          EXCLUSIVE_LOCKS_REQUIRED(!cs);
 127  
 128      void Attempt(const CService& addr, bool fCountFailure, NodeSeconds time)
 129          EXCLUSIVE_LOCKS_REQUIRED(!cs);
 130  
 131      void ResolveCollisions() EXCLUSIVE_LOCKS_REQUIRED(!cs);
 132  
 133      std::pair<CAddress, NodeSeconds> SelectTriedCollision() EXCLUSIVE_LOCKS_REQUIRED(!cs);
 134  
 135      std::pair<CAddress, NodeSeconds> Select(bool new_only, const std::unordered_set<Network>& networks) const
 136          EXCLUSIVE_LOCKS_REQUIRED(!cs);
 137  
 138      std::vector<CAddress> GetAddr(size_t max_addresses, size_t max_pct, std::optional<Network> network, bool filtered = true) const
 139          EXCLUSIVE_LOCKS_REQUIRED(!cs);
 140  
 141      std::vector<std::pair<AddrInfo, AddressPosition>> GetEntries(bool from_tried) const
 142          EXCLUSIVE_LOCKS_REQUIRED(!cs);
 143  
 144      void Connected(const CService& addr, NodeSeconds time)
 145          EXCLUSIVE_LOCKS_REQUIRED(!cs);
 146  
 147      void SetServices(const CService& addr, ServiceFlags nServices)
 148          EXCLUSIVE_LOCKS_REQUIRED(!cs);
 149  
 150      std::optional<AddressPosition> FindAddressEntry(const CAddress& addr)
 151          EXCLUSIVE_LOCKS_REQUIRED(!cs);
 152  
 153      friend class AddrManDeterministic;
 154  
 155  private:
 156      //! A mutex to protect the inner data structures.
 157      mutable Mutex cs;
 158  
 159      //! Source of random numbers for randomization in inner loops
 160      mutable FastRandomContext insecure_rand GUARDED_BY(cs);
 161  
 162      //! secret key to randomize bucket select with
 163      uint256 nKey;
 164  
 165      //! Serialization versions.
 166      enum Format : uint8_t {
 167          V0_HISTORICAL = 0,    //!< historic format, before commit e6b343d88
 168          V1_DETERMINISTIC = 1, //!< for pre-asmap files
 169          V2_ASMAP = 2,         //!< for files including asmap version
 170          V3_BIP155 = 3,        //!< same as V2_ASMAP plus addresses are in BIP155 format
 171          V4_MULTIPORT = 4,     //!< adds support for multiple ports per IP
 172      };
 173  
 174      //! The maximum format this software knows it can unserialize. Also, we always serialize
 175      //! in this format.
 176      //! The format (first byte in the serialized stream) can be higher than this and
 177      //! still this software may be able to unserialize the file - if the second byte
 178      //! (see `lowest_compatible` in `Unserialize()`) is less or equal to this.
 179      static constexpr Format FILE_FORMAT = Format::V4_MULTIPORT;
 180  
 181      //! The initial value of a field that is incremented every time an incompatible format
 182      //! change is made (such that old software versions would not be able to parse and
 183      //! understand the new file format). This is 32 because we overtook the "key size"
 184      //! field which was 32 historically.
 185      //! @note Don't increment this. Increment `lowest_compatible` in `Serialize()` instead.
 186      static constexpr uint8_t INCOMPATIBILITY_BASE = 32;
 187  
 188      //! last used nId
 189      nid_type nIdCount GUARDED_BY(cs){0};
 190  
 191      //! table with information about all nIds
 192      std::unordered_map<nid_type, AddrInfo> mapInfo GUARDED_BY(cs);
 193  
 194      //! find an nId based on its network address and port.
 195      std::unordered_map<CService, nid_type, CServiceHash> mapAddr GUARDED_BY(cs);
 196  
 197      //! randomly-ordered vector of all nIds
 198      //! This is mutable because it is unobservable outside the class, so any
 199      //! changes to it (even in const methods) are also unobservable.
 200      mutable std::vector<nid_type> vRandom GUARDED_BY(cs);
 201  
 202      // number of "tried" entries
 203      int nTried GUARDED_BY(cs){0};
 204  
 205      //! list of "tried" buckets
 206      nid_type vvTried[ADDRMAN_TRIED_BUCKET_COUNT][ADDRMAN_BUCKET_SIZE] GUARDED_BY(cs);
 207  
 208      //! number of (unique) "new" entries
 209      int nNew GUARDED_BY(cs){0};
 210  
 211      //! list of "new" buckets
 212      nid_type vvNew[ADDRMAN_NEW_BUCKET_COUNT][ADDRMAN_BUCKET_SIZE] GUARDED_BY(cs);
 213  
 214      //! last time Good was called (memory only). Initially set to 1 so that "never" is strictly worse.
 215      NodeSeconds m_last_good GUARDED_BY(cs){1s};
 216  
 217      //! Holds addrs inserted into tried table that collide with existing entries. Test-before-evict discipline used to resolve these collisions.
 218      std::set<nid_type> m_tried_collisions;
 219  
 220      /** Perform consistency checks every m_consistency_check_ratio operations (if non-zero). */
 221      const int32_t m_consistency_check_ratio;
 222  
 223      /** Reference to the netgroup manager. netgroupman must be constructed before addrman and destructed after. */
 224      const NetGroupManager& m_netgroupman;
 225  
 226      struct NewTriedCount {
 227          size_t n_new;
 228          size_t n_tried;
 229      };
 230  
 231      /** Number of entries in addrman per network and new/tried table. */
 232      std::unordered_map<Network, NewTriedCount> m_network_counts GUARDED_BY(cs);
 233  
 234      //! Find an entry.
 235      AddrInfo* Find(const CService& addr, nid_type* pnId = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs);
 236  
 237      //! Create a new entry and add it to the internal data structures mapInfo, mapAddr and vRandom.
 238      AddrInfo* Create(const CAddress& addr, const CNetAddr& addrSource, nid_type* pnId = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs);
 239  
 240      //! Swap two elements in vRandom.
 241      void SwapRandom(unsigned int nRandomPos1, unsigned int nRandomPos2) const EXCLUSIVE_LOCKS_REQUIRED(cs);
 242  
 243      //! Delete an entry. It must not be in tried, and have refcount 0.
 244      void Delete(nid_type nId) EXCLUSIVE_LOCKS_REQUIRED(cs);
 245  
 246      //! Clear a position in a "new" table. This is the only place where entries are actually deleted.
 247      void ClearNew(int nUBucket, int nUBucketPos) EXCLUSIVE_LOCKS_REQUIRED(cs);
 248  
 249      //! Move an entry from the "new" table(s) to the "tried" table
 250      void MakeTried(AddrInfo& info, nid_type nId) EXCLUSIVE_LOCKS_REQUIRED(cs);
 251  
 252      /** Attempt to add a single address to addrman's new table.
 253       *  @see AddrMan::Add() for parameters. */
 254      bool AddSingle(const CAddress& addr, const CNetAddr& source, std::chrono::seconds time_penalty) EXCLUSIVE_LOCKS_REQUIRED(cs);
 255  
 256      bool Good_(const CService& addr, bool test_before_evict, NodeSeconds time) EXCLUSIVE_LOCKS_REQUIRED(cs);
 257  
 258      bool Add_(const std::vector<CAddress>& vAddr, const CNetAddr& source, std::chrono::seconds time_penalty) EXCLUSIVE_LOCKS_REQUIRED(cs);
 259  
 260      void Attempt_(const CService& addr, bool fCountFailure, NodeSeconds time) EXCLUSIVE_LOCKS_REQUIRED(cs);
 261  
 262      std::pair<CAddress, NodeSeconds> Select_(bool new_only, const std::unordered_set<Network>& networks) const EXCLUSIVE_LOCKS_REQUIRED(cs);
 263  
 264      /** Helper to generalize looking up an addrman entry from either table.
 265       *
 266       *  @return  nid_type The nid of the entry. If the addrman position is empty or not found, returns -1.
 267       * */
 268      nid_type GetEntry(bool use_tried, size_t bucket, size_t position) const EXCLUSIVE_LOCKS_REQUIRED(cs);
 269  
 270      std::vector<CAddress> GetAddr_(size_t max_addresses, size_t max_pct, std::optional<Network> network, bool filtered = true) const EXCLUSIVE_LOCKS_REQUIRED(cs);
 271  
 272      std::vector<std::pair<AddrInfo, AddressPosition>> GetEntries_(bool from_tried) const EXCLUSIVE_LOCKS_REQUIRED(cs);
 273  
 274      void Connected_(const CService& addr, NodeSeconds time) EXCLUSIVE_LOCKS_REQUIRED(cs);
 275  
 276      void SetServices_(const CService& addr, ServiceFlags nServices) EXCLUSIVE_LOCKS_REQUIRED(cs);
 277  
 278      void ResolveCollisions_() EXCLUSIVE_LOCKS_REQUIRED(cs);
 279  
 280      std::pair<CAddress, NodeSeconds> SelectTriedCollision_() EXCLUSIVE_LOCKS_REQUIRED(cs);
 281  
 282      std::optional<AddressPosition> FindAddressEntry_(const CAddress& addr) EXCLUSIVE_LOCKS_REQUIRED(cs);
 283  
 284      size_t Size_(std::optional<Network> net, std::optional<bool> in_new) const EXCLUSIVE_LOCKS_REQUIRED(cs);
 285  
 286      //! Consistency check, taking into account m_consistency_check_ratio.
 287      //! Will std::abort if an inconsistency is detected.
 288      void Check() const EXCLUSIVE_LOCKS_REQUIRED(cs);
 289  
 290      //! Perform consistency check, regardless of m_consistency_check_ratio.
 291      //! @returns an error code or zero.
 292      int CheckAddrman() const EXCLUSIVE_LOCKS_REQUIRED(cs);
 293  };
 294  
 295  #endif // BITCOIN_ADDRMAN_IMPL_H
 296