LCOV - code coverage report
Current view: top level - src/evo - netinfo.h (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 109 162 67.3 %
Date: 2026-06-25 07:23:51 Functions: 59 98 60.2 %

          Line data    Source code
       1             : // Copyright (c) 2025 The Dash 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_EVO_NETINFO_H
       6             : #define BITCOIN_EVO_NETINFO_H
       7             : 
       8             : #include <netaddress.h>
       9             : #include <serialize.h>
      10             : #include <streams.h>
      11             : 
      12             : #include <variant>
      13             : 
      14             : class CChainParams;
      15             : class CService;
      16             : 
      17             : class UniValue;
      18             : 
      19             : /** Maximum entries that can be stored in an ExtNetInfo per purpose code */
      20             : static constexpr uint8_t MAX_ENTRIES_EXTNETINFO{4};
      21             : 
      22             : enum class NetInfoStatus : uint8_t {
      23             :     // Managing entries
      24             :     BadInput,
      25             :     Duplicate,
      26             :     MaxLimit,
      27             : 
      28             :     // Validation
      29             :     BadAddress,
      30             :     BadPort,
      31             :     BadType,
      32             :     NotRoutable,
      33             :     Malformed,
      34             : 
      35             :     Success
      36             : };
      37             : 
      38           0 : constexpr std::string_view NISToString(const NetInfoStatus code)
      39             : {
      40           0 :     switch (code) {
      41             :     case NetInfoStatus::BadAddress:
      42           0 :         return "invalid address";
      43             :     case NetInfoStatus::BadInput:
      44           0 :         return "invalid input";
      45             :     case NetInfoStatus::BadPort:
      46           0 :         return "invalid port";
      47             :     case NetInfoStatus::BadType:
      48           0 :         return "invalid address type";
      49             :     case NetInfoStatus::Duplicate:
      50           0 :         return "duplicate";
      51             :     case NetInfoStatus::NotRoutable:
      52           0 :         return "unroutable address";
      53             :     case NetInfoStatus::Malformed:
      54           0 :         return "malformed";
      55             :     case NetInfoStatus::MaxLimit:
      56           0 :         return "too many entries";
      57             :     case NetInfoStatus::Success:
      58           0 :         return "success";
      59             :     } // no default case, so the compiler can warn about missing cases
      60           0 :     assert(false);
      61           0 : }
      62             : 
      63             : // A purpose corresponds to the index position in the ExtNetInfo map, entries must
      64             : // be contiguous and cannot be changed once set without a format version update
      65             : enum class NetInfoPurpose : uint8_t {
      66             :     // Mandatory for masternodes
      67             :     CORE_P2P = 0,
      68             :     // Mandatory for EvoNodes
      69             :     PLATFORM_P2P = 1,
      70             :     PLATFORM_HTTPS = 2,
      71             : };
      72             : 
      73             : template<> struct is_serializable_enum<NetInfoPurpose> : std::true_type {};
      74             : 
      75          99 : constexpr bool IsValidPurpose(const NetInfoPurpose purpose)
      76             : {
      77          99 :     switch (purpose) {
      78             :     case NetInfoPurpose::CORE_P2P:
      79             :     case NetInfoPurpose::PLATFORM_P2P:
      80             :     case NetInfoPurpose::PLATFORM_HTTPS:
      81          97 :         return true;
      82             :     } // no default case, so the compiler can warn about missing cases
      83           2 :     return false;
      84          99 : }
      85             : 
      86             : // Warning: Used in RPC code, altering existing values is a breaking change
      87          77 : constexpr std::string_view PurposeToString(const NetInfoPurpose purpose)
      88             : {
      89          77 :     switch (purpose) {
      90             :     case NetInfoPurpose::CORE_P2P:
      91          77 :         return "core_p2p";
      92             :     case NetInfoPurpose::PLATFORM_P2P:
      93           0 :         return "platform_p2p";
      94             :     case NetInfoPurpose::PLATFORM_HTTPS:
      95           0 :         return "platform_https";
      96             :     } // no default case, so the compiler can warn about missing cases
      97           0 :     return "";
      98          77 : }
      99             : 
     100             : /** Will return true if node is running on mainnet */
     101             : bool IsNodeOnMainnet();
     102             : 
     103             : /** Identical to IsDeprecatedRPCEnabled("service"). For use outside of RPC code */
     104             : bool IsServiceDeprecatedRPCEnabled();
     105             : 
     106             : /** Creates a one-element array using CService::ToStringPortAddr() output */
     107             : UniValue ArrFromService(const CService& addr);
     108             : 
     109             : /** Equivalent to Params() if node is running on mainnet */
     110             : const CChainParams& MainParams();
     111             : 
     112             : class DomainPort
     113             : {
     114             : public:
     115             :     enum class Status : uint8_t {
     116             :         BadChar,
     117             :         BadCharPos,
     118             :         BadDotless,
     119             :         BadLabelCharPos,
     120             :         BadLabelLen,
     121             :         BadLen,
     122             :         BadPort,
     123             :         Malformed,
     124             : 
     125             :         Success
     126             :     };
     127             : 
     128             :     static constexpr std::string_view StatusToString(const DomainPort::Status code)
     129             :     {
     130             :         switch (code) {
     131             :         case DomainPort::Status::BadChar:
     132             :             return "invalid character";
     133             :         case DomainPort::Status::BadCharPos:
     134             :             return "bad domain character position";
     135             :         case DomainPort::Status::BadDotless:
     136             :             return "prohibited dotless";
     137             :         case DomainPort::Status::BadLabelCharPos:
     138             :             return "bad label character position";
     139             :         case DomainPort::Status::BadLabelLen:
     140             :             return "bad label length";
     141             :         case DomainPort::Status::BadLen:
     142             :             return "bad domain length";
     143             :         case DomainPort::Status::BadPort:
     144             :             return "bad port";
     145             :         case DomainPort::Status::Malformed:
     146             :             return "malformed";
     147             :         case DomainPort::Status::Success:
     148             :             return "success";
     149             :         } // no default case, so the compiler can warn about missing cases
     150             :         assert(false);
     151             :     }
     152             : 
     153             : private:
     154          25 :     std::string m_addr{};
     155          25 :     uint16_t m_port{0};
     156             : 
     157             : private:
     158             :     static DomainPort::Status ValidateDomain(const std::string& input);
     159             : 
     160             : public:
     161          75 :     DomainPort() = default;
     162             :     template <typename Stream>
     163             :     DomainPort(deserialize_type, Stream& s) { s >> *this; }
     164             : 
     165         110 :     ~DomainPort() = default;
     166             : 
     167           0 :     bool operator<(const DomainPort& rhs) const { return std::tie(m_addr, m_port) < std::tie(rhs.m_addr, rhs.m_port); }
     168           1 :     bool operator==(const DomainPort& rhs) const { return std::tie(m_addr, m_port) == std::tie(rhs.m_addr, rhs.m_port); }
     169             :     bool operator!=(const DomainPort& rhs) const { return !(*this == rhs); }
     170             : 
     171           0 :     SERIALIZE_METHODS(DomainPort, obj)
     172             :     {
     173           0 :         READWRITE(obj.m_addr);
     174           0 :         READWRITE(Using<BigEndianFormatter<2>>(obj.m_port));
     175           0 :     }
     176             : 
     177             :     bool IsEmpty() const { return m_addr.empty() && m_port == 0; }
     178          27 :     bool IsValid() const { return Validate() == DomainPort::Status::Success; }
     179             :     DomainPort::Status Set(const std::string& addr, const uint16_t port);
     180             :     DomainPort::Status Validate() const;
     181          11 :     uint16_t GetPort() const { return m_port; }
     182          14 :     const std::string& ToStringAddr() const { return m_addr; }
     183          12 :     std::string ToStringAddrPort() const { return strprintf("%s:%d", m_addr, m_port); }
     184             : };
     185             : 
     186             : class NetInfoEntry
     187             : {
     188             : public:
     189             :     enum NetInfoType : uint8_t {
     190             :         Service = 0x01,
     191             :         Domain  = 0x02,
     192             :         Invalid = 0xff
     193             :     };
     194             : 
     195             : private:
     196        2202 :     uint8_t m_type{NetInfoType::Invalid};
     197        2202 :     std::variant<std::monostate, CService, DomainPort> m_data{std::monostate{}};
     198             : 
     199             : public:
     200        5511 :     NetInfoEntry() = default;
     201          12 :     explicit NetInfoEntry(const DomainPort& domain)
     202           6 :     {
     203           6 :         if (!domain.IsValid()) return;
     204           6 :         m_type = NetInfoType::Domain;
     205           6 :         m_data = domain;
     206          12 :     }
     207         718 :     explicit NetInfoEntry(const CService& service)
     208         359 :     {
     209         359 :         if (!service.IsValid()) return;
     210         354 :         m_type = NetInfoType::Service;
     211         354 :         m_data = service;
     212         718 :     }
     213             :     template <typename Stream>
     214             :     explicit NetInfoEntry(deserialize_type, Stream& s) { s >> *this; }
     215             : 
     216        8306 :     ~NetInfoEntry() = default;
     217             : 
     218             :     bool operator<(const NetInfoEntry& rhs) const;
     219             :     bool operator==(const NetInfoEntry& rhs) const;
     220             :     bool operator!=(const NetInfoEntry& rhs) const { return !(*this == rhs); }
     221             : 
     222             :     template <typename Stream>
     223           2 :     void Serialize(Stream& s_) const
     224             :     {
     225           2 :         OverrideStream<Stream> s(&s_, /*nType=*/0, s_.GetVersion() | ADDRV2_FORMAT);
     226           3 :         if (const auto* data_ptr_service{std::get_if<CService>(&m_data)};
     227           2 :             m_type == NetInfoType::Service && data_ptr_service && data_ptr_service->IsValid()) {
     228           1 :             s << m_type << *data_ptr_service;
     229           2 :         } else if (const auto* data_ptr_domain{std::get_if<DomainPort>(&m_data)};
     230           1 :                    m_type == NetInfoType::Domain && data_ptr_domain && data_ptr_domain->IsValid()) {
     231           0 :             s << m_type << *data_ptr_domain;
     232           0 :         } else {
     233           1 :             s << NetInfoType::Invalid;
     234             :         }
     235           2 :     }
     236             : 
     237             :     template <typename Stream>
     238           6 :     void Unserialize(Stream& s_)
     239             :     {
     240           6 :         OverrideStream<Stream> s(&s_, /*nType=*/0, s_.GetVersion() | ADDRV2_FORMAT);
     241           6 :         s >> m_type;
     242           6 :         if (m_type == NetInfoType::Service) {
     243             :             try {
     244           5 :                 auto& service{m_data.emplace<CService>()};
     245           5 :                 s >> service;
     246           5 :                 if (!service.IsValid()) { Clear(); } // Invalid CService, mark as invalid
     247           5 :             } catch (const std::ios_base::failure&) { Clear(); } // Deser failed, mark as invalid
     248           6 :         } else if (m_type == NetInfoType::Domain) {
     249             :             try {
     250           0 :                 auto& domain{m_data.emplace<DomainPort>()};
     251           0 :                 s >> domain;
     252           0 :                 if (!domain.IsValid()) { Clear(); } // Invalid DomainPort, mark as invalid
     253           0 :             } catch (const std::ios_base::failure&) { Clear(); } // Deser failed, mark as invalid
     254           1 :         } else { Clear(); } // Invalid type code, mark as invalid
     255           6 :     }
     256             : 
     257           7 :     void Clear()
     258             :     {
     259           7 :         m_type = NetInfoType::Invalid;
     260           7 :         m_data = std::monostate{};
     261           7 :     }
     262             : 
     263             :     std::optional<CService> GetAddrPort() const;
     264             :     std::optional<DomainPort> GetDomainPort() const;
     265             :     uint16_t GetPort() const;
     266        1455 :     bool IsEmpty() const { return *this == NetInfoEntry{}; }
     267             :     bool IsTriviallyValid() const;
     268             :     std::string ToString() const;
     269             :     std::string ToStringAddr() const;
     270             :     std::string ToStringAddrPort() const;
     271             : };
     272             : 
     273             : template<> struct is_serializable_enum<NetInfoEntry::NetInfoType> : std::true_type {};
     274             : 
     275             : using NetInfoList = std::vector<NetInfoEntry>;
     276             : 
     277             : class NetInfoInterface
     278             : {
     279             : public:
     280             :     static std::shared_ptr<NetInfoInterface> MakeNetInfo(const uint16_t nVersion);
     281             : 
     282             : public:
     283         420 :     virtual ~NetInfoInterface() = default;
     284             : 
     285             :     virtual NetInfoStatus AddEntry(const NetInfoPurpose purpose, const std::string& service) = 0;
     286             :     virtual NetInfoList GetEntries(std::optional<NetInfoPurpose> purpose_opt = std::nullopt) const = 0;
     287             : 
     288             :     virtual CService GetPrimary() const = 0;
     289             :     virtual bool CanStorePlatform() const = 0;
     290             :     virtual bool HasEntries(NetInfoPurpose purpose) const = 0;
     291             :     virtual bool IsEmpty() const = 0;
     292             :     virtual NetInfoStatus Validate() const = 0;
     293             :     virtual UniValue ToJson(std::optional<NetInfoPurpose> purpose_opt = std::nullopt) const = 0;
     294             :     virtual std::string ToString() const = 0;
     295             : 
     296             :     virtual void Clear() = 0;
     297             : 
     298         471 :     bool operator==(const NetInfoInterface& rhs) const { return typeid(*this) == typeid(rhs) && this->IsEqual(rhs); }
     299          40 :     bool operator!=(const NetInfoInterface& rhs) const { return !(*this == rhs); }
     300             : 
     301             : private:
     302             :     virtual bool IsEqual(const NetInfoInterface& rhs) const = 0;
     303             : };
     304             : 
     305             : class MnNetInfo final : public NetInfoInterface
     306             : {
     307             : private:
     308         374 :     NetInfoEntry m_addr{};
     309             : 
     310             : private:
     311             :     static NetInfoStatus ValidateService(const CService& service);
     312             : 
     313             : public:
     314         336 :     MnNetInfo() = default;
     315             :     template <typename Stream>
     316         786 :     MnNetInfo(deserialize_type, Stream& s) { s >> *this; }
     317             : 
     318             :     template <typename Stream>
     319         493 :     void Serialize(Stream& s) const
     320             :     {
     321         986 :         if (const auto service_opt{m_addr.GetAddrPort()}) {
     322         479 :             s << *service_opt;
     323         479 :         } else {
     324          14 :             s << CService{};
     325             :         }
     326         493 :     }
     327             : 
     328          91 :     void Serialize(CSizeComputer& s) const
     329             :     {
     330          91 :         s.seek(::GetSerializeSize(CService{}, s.GetVersion()));
     331          91 :     }
     332             : 
     333             :     template <typename Stream>
     334         262 :     void Unserialize(Stream& s)
     335             :     {
     336         262 :         CService service;
     337         262 :         s >> service;
     338         262 :         m_addr = NetInfoEntry{service};
     339         262 :     }
     340             : 
     341             :     NetInfoStatus AddEntry(const NetInfoPurpose purpose, const std::string& service) override;
     342             :     NetInfoList GetEntries(std::optional<NetInfoPurpose> purpose_opt = std::nullopt) const override;
     343             : 
     344             :     CService GetPrimary() const override;
     345         298 :     bool HasEntries(NetInfoPurpose purpose) const override { return purpose == NetInfoPurpose::CORE_P2P && !IsEmpty(); }
     346        1448 :     bool IsEmpty() const override { return m_addr.IsEmpty(); }
     347         186 :     bool CanStorePlatform() const override { return false; }
     348             :     NetInfoStatus Validate() const override;
     349             :     UniValue ToJson(std::optional<NetInfoPurpose> purpose_opt = std::nullopt) const override;
     350             :     std::string ToString() const override;
     351             : 
     352           4 :     void Clear() override { m_addr.Clear(); }
     353             : 
     354             : private:
     355             :     // operator== and operator!= are defined by the parent which then leverage the child's IsEqual() override
     356             :     // IsEqual() should only be called by NetInfoInterface::operator== otherwise static_cast assumption could fail
     357         465 :     bool IsEqual(const NetInfoInterface& rhs) const override
     358             :     {
     359             :         ASSERT_IF_DEBUG(typeid(*this) == typeid(rhs));
     360         465 :         const auto& rhs_obj{static_cast<const MnNetInfo&>(rhs)};
     361         465 :         return m_addr == rhs_obj.m_addr;
     362             :     }
     363             : };
     364             : 
     365             : class ExtNetInfo final : public NetInfoInterface
     366             : {
     367             : private:
     368             :     /** Update if serialization or ruleset changed */
     369             :     static constexpr uint8_t CURRENT_VERSION{1};
     370             : 
     371             :     /** Returns true if there are addr:port duplicates in the object */
     372             :     bool HasAddrPortDuplicates() const;
     373             : 
     374             :     /** Returns true if candidate is an addr:port duplicate in the object */
     375             :     bool IsAddrPortDuplicate(const NetInfoEntry& candidate) const;
     376             : 
     377             :     /** Returns true if there are addr duplicates within a given address list */
     378             :     bool HasAddrDuplicates(const NetInfoList& entries) const;
     379             : 
     380             :     /** Returns true if candidate is an addr duplicate within a given address list */
     381             :     bool IsAddrDuplicate(const NetInfoEntry& candidate, const NetInfoList& entries) const;
     382             : 
     383             :     /** Validate uniqueness requirements and add to object if passed */
     384             :     NetInfoStatus ProcessCandidate(const NetInfoPurpose purpose, const NetInfoEntry& candidate);
     385             : 
     386             :     /** Validate CService candidate address against ruleset */
     387             :     static NetInfoStatus ValidateService(const CService& service);
     388             :     static NetInfoStatus ValidateDomainPort(const DomainPort& domain);
     389             : 
     390             : private:
     391          46 :     uint8_t m_version{CURRENT_VERSION};
     392          46 :     std::map<NetInfoPurpose, NetInfoList> m_data{};
     393             : 
     394             :     // memory only
     395          46 :     NetInfoList m_all_entries{};
     396             : 
     397             : public:
     398         138 :     ExtNetInfo() = default;
     399             :     template <typename Stream>
     400           0 :     ExtNetInfo(deserialize_type, Stream& s) { s >> *this; }
     401             : 
     402             :     template <typename Stream>
     403           0 :     void Serialize(Stream& s) const
     404             :     {
     405           0 :         s << m_version;
     406           0 :         if (m_version == 0 || m_version > CURRENT_VERSION) {
     407           0 :             return; // Don't bother with unknown versions
     408             :         }
     409           0 :         s << m_data;
     410           0 :     }
     411             : 
     412             :     template <typename Stream>
     413           0 :     void Unserialize(Stream& s)
     414             :     {
     415           0 :         s >> m_version;
     416           0 :         if (m_version == 0 || m_version > CURRENT_VERSION) {
     417           0 :             return; // Don't bother with unknown versions
     418             :         }
     419           0 :         s >> m_data;
     420             : 
     421             :         // Regenerate internal cache
     422           0 :         m_all_entries.clear();
     423           0 :         for (const auto& [_, entries] : m_data) {
     424           0 :             m_all_entries.insert(m_all_entries.end(), entries.begin(), entries.end());
     425             :         }
     426           0 :     }
     427             : 
     428             :     NetInfoStatus AddEntry(const NetInfoPurpose purpose, const std::string& input) override;
     429             :     NetInfoList GetEntries(std::optional<NetInfoPurpose> purpose_opt = std::nullopt) const override;
     430             : 
     431             :     CService GetPrimary() const override;
     432             :     bool HasEntries(NetInfoPurpose purpose) const override;
     433           3 :     bool IsEmpty() const override { return m_version == CURRENT_VERSION && m_data.empty(); }
     434           0 :     bool CanStorePlatform() const override { return true; }
     435             :     NetInfoStatus Validate() const override;
     436             :     UniValue ToJson(std::optional<NetInfoPurpose> purpose_opt = std::nullopt) const override;
     437             :     std::string ToString() const override;
     438             : 
     439           0 :     void Clear() override
     440             :     {
     441           0 :         m_version = CURRENT_VERSION;
     442           0 :         m_data.clear();
     443           0 :         m_all_entries.clear();
     444           0 :     }
     445             : 
     446             : private:
     447             :     // operator== and operator!= are defined by the parent which then leverage the child's IsEqual() override
     448             :     // IsEqual() should only be called by NetInfoInterface::operator== otherwise static_cast assumption could fail
     449           4 :     bool IsEqual(const NetInfoInterface& rhs) const override
     450             :     {
     451             :         ASSERT_IF_DEBUG(typeid(*this) == typeid(rhs));
     452           4 :         const auto& rhs_obj{static_cast<const ExtNetInfo&>(rhs)};
     453           4 :         return m_version == rhs_obj.m_version && m_data == rhs_obj.m_data;
     454             :     }
     455             : };
     456             : 
     457             : class NetInfoSerWrapper
     458             : {
     459             : private:
     460             :     std::shared_ptr<NetInfoInterface>& m_data;
     461             :     const bool m_is_extended{false};
     462             : 
     463             : public:
     464             :     NetInfoSerWrapper() = delete;
     465             :     NetInfoSerWrapper(const NetInfoSerWrapper&) = delete;
     466        1682 :     NetInfoSerWrapper(std::shared_ptr<NetInfoInterface>& data, const bool is_extended) :
     467         841 :         m_data{data},
     468         841 :         m_is_extended{is_extended}
     469         841 :     {
     470        1682 :     }
     471             : 
     472             :     ~NetInfoSerWrapper() = default;
     473             : 
     474             :     template <typename Stream>
     475         579 :     void Serialize(Stream& s) const
     476             :     {
     477        1158 :         if (const auto ptr{std::dynamic_pointer_cast<ExtNetInfo>(m_data)}) {
     478           0 :             s << *ptr;
     479        1158 :         } else if (const auto ptr{std::dynamic_pointer_cast<MnNetInfo>(m_data)}) {
     480         579 :             s << *ptr;
     481         579 :         } else {
     482             :             // NetInfoInterface::MakeNetInfo() supplied an unexpected implementation or we didn't call it and
     483             :             // are left with a nullptr. Neither should happen.
     484           0 :             assert(false);
     485             :         }
     486         579 :     }
     487             : 
     488             :     template <typename Stream>
     489         262 :     void Unserialize(Stream& s)
     490             :     {
     491         262 :         if (m_is_extended) {
     492           0 :             m_data = std::make_shared<ExtNetInfo>(deserialize, s);
     493           0 :         } else {
     494         262 :             m_data = std::make_shared<MnNetInfo>(deserialize, s);
     495             :         }
     496         262 :     }
     497             : };
     498             : 
     499             : #endif // BITCOIN_EVO_NETINFO_H

Generated by: LCOV version 1.16