/ src / addrman_impl.h
addrman_impl.h
  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.h>
  9  #include <logging/timer.h>
 10  #include <netaddress.h>
 11  #include <protocol.h>
 12  #include <serialize.h>
 13  #include <sync.h>
 14  #include <uint256.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