// Copyright (c) 2012 Pieter Wuille // Copyright (c) 2012-2020 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_ADDRMAN_H #define BITCOIN_ADDRMAN_H #include #include #include #include #include #include #include #include #include #include #include class AddrManImpl; /** Default for -checkaddrman */ static constexpr int32_t DEFAULT_ADDRMAN_CONSISTENCY_CHECKS{0}; /** * Extended statistics about a CAddress */ class CAddrInfo : public CAddress { public: //! last try whatsoever by us (memory only) int64_t nLastTry{0}; //! last counted attempt (memory only) int64_t nLastCountAttempt{0}; private: //! where knowledge about this address first came from CNetAddr source; //! last successful connection by us int64_t nLastSuccess{0}; //! connection attempts since last successful attempt int nAttempts{0}; //! reference count in new sets (memory only) int nRefCount{0}; //! in tried set? (memory only) bool fInTried{false}; //! position in vRandom mutable int nRandomPos{-1}; friend class AddrManImpl; friend class CAddrManDeterministic; public: SERIALIZE_METHODS(CAddrInfo, obj) { READWRITEAS(CAddress, obj); READWRITE(obj.source, obj.nLastSuccess, obj.nAttempts); } CAddrInfo(const CAddress &addrIn, const CNetAddr &addrSource) : CAddress(addrIn), source(addrSource) { } CAddrInfo() : CAddress(), source() { } //! Calculate in which "tried" bucket this entry belongs int GetTriedBucket(const uint256 &nKey, const std::vector &asmap) const; //! Calculate in which "new" bucket this entry belongs, given a certain source int GetNewBucket(const uint256 &nKey, const CNetAddr& src, const std::vector &asmap) const; //! Calculate in which "new" bucket this entry belongs, using its default source int GetNewBucket(const uint256 &nKey, const std::vector &asmap) const { return GetNewBucket(nKey, source, asmap); } //! Calculate in which position of a bucket to store this entry. int GetBucketPosition(const uint256 &nKey, bool fNew, int nBucket) const; //! Determine whether the statistics about this entry are bad enough so that it can just be deleted bool IsTerrible(int64_t nNow = GetAdjustedTime()) const; //! Calculate the relative chance this entry should be given when selecting nodes to connect to double GetChance(int64_t nNow = GetAdjustedTime()) const; }; /** Stochastic address manager * * Design goals: * * Keep the address tables in-memory, and asynchronously dump the entire table to peers.dat. * * Make sure no (localized) attacker can fill the entire table with his nodes/addresses. * * To that end: * * Addresses are organized into buckets that can each store up to 64 entries. * * Addresses to which our node has not successfully connected go into 1024 "new" buckets. * * Based on the address range (/16 for IPv4) of the source of information, or if an asmap is provided, * the AS it belongs to (for IPv4/IPv6), 64 buckets are selected at random. * * The actual bucket is chosen from one of these, based on the range in which the address itself is located. * * The position in the bucket is chosen based on the full address. * * One single address can occur in up to 8 different buckets to increase selection chances for addresses that * are seen frequently. The chance for increasing this multiplicity decreases exponentially. * * When adding a new address to an occupied position of a bucket, it will not replace the existing entry * unless that address is also stored in another bucket or it doesn't meet one of several quality criteria * (see IsTerrible for exact criteria). * * Addresses of nodes that are known to be accessible go into 256 "tried" buckets. * * Each address range selects at random 8 of these buckets. * * The actual bucket is chosen from one of these, based on the full address. * * When adding a new good address to an occupied position of a bucket, a FEELER connection to the * old address is attempted. The old entry is only replaced and moved back to the "new" buckets if this * attempt was unsuccessful. * * Bucket selection is based on cryptographic hashing, using a randomly-generated 256-bit key, which should not * be observable by adversaries. * * Several indexes are kept for high performance. Setting m_consistency_check_ratio with the -checkaddrman * configuration option will introduce (expensive) consistency checks for the entire data structure. */ /** Total number of buckets for tried addresses */ static constexpr int32_t ADDRMAN_TRIED_BUCKET_COUNT_LOG2{8}; static constexpr int ADDRMAN_TRIED_BUCKET_COUNT{1 << ADDRMAN_TRIED_BUCKET_COUNT_LOG2}; /** Total number of buckets for new addresses */ static constexpr int32_t ADDRMAN_NEW_BUCKET_COUNT_LOG2{10}; static constexpr int ADDRMAN_NEW_BUCKET_COUNT{1 << ADDRMAN_NEW_BUCKET_COUNT_LOG2}; /** Maximum allowed number of entries in buckets for new and tried addresses */ static constexpr int32_t ADDRMAN_BUCKET_SIZE_LOG2{6}; static constexpr int ADDRMAN_BUCKET_SIZE{1 << ADDRMAN_BUCKET_SIZE_LOG2}; /** * Stochastical (IP) address manager */ class CAddrMan { const std::unique_ptr m_impl; public: explicit CAddrMan(std::vector asmap, bool deterministic, int32_t consistency_check_ratio); ~CAddrMan(); template void Serialize(Stream& s_) const; template void Unserialize(Stream& s_); //! Return the number of (unique) addresses in all tables. size_t size() const; //! Add addresses to addrman's new table. bool Add(const std::vector &vAddr, const CNetAddr& source, int64_t nTimePenalty = 0); //! Mark an entry as accessible. void Good(const CService &addr, int64_t nTime = GetAdjustedTime()); //! Mark an entry as connection attempted to. void Attempt(const CService &addr, bool fCountFailure, int64_t nTime = GetAdjustedTime()); //! See if any to-be-evicted tried table entries have been tested and if so resolve the collisions. void ResolveCollisions(); /** * Randomly select an address in the tried table that another address is * attempting to evict. * * @return CAddress The record for the selected tried peer. * int64_t The last time we attempted to connect to that peer. */ std::pair SelectTriedCollision(); /** * Choose an address to connect to. * * @param[in] newOnly Whether to only select addresses from the new table. * @return CAddress The record for the selected peer. * int64_t The last time we attempted to connect to that peer. */ std::pair Select(bool newOnly = false) const; /** * Return all or many randomly selected addresses, optionally by network. * * @param[in] max_addresses Maximum number of addresses to return (0 = all). * @param[in] max_pct Maximum percentage of addresses to return (0 = all). * @param[in] network Select only addresses of this network (nullopt = all). */ std::vector GetAddr(size_t max_addresses, size_t max_pct, std::optional network) const; //! Outer function for Connected_() void Connected(const CService &addr, int64_t nTime = GetAdjustedTime()); void SetServices(const CService &addr, ServiceFlags nServices); const std::vector& GetAsmap() const; friend class CAddrManTest; friend class CAddrManDeterministic; }; #endif // BITCOIN_ADDRMAN_H