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 10 : constexpr std::string_view NISToString(const NetInfoStatus code)
39 : {
40 10 : 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 4 : return "invalid port";
47 : case NetInfoStatus::BadType:
48 0 : return "invalid address type";
49 : case NetInfoStatus::Duplicate:
50 4 : return "duplicate";
51 : case NetInfoStatus::NotRoutable:
52 0 : return "unroutable address";
53 : case NetInfoStatus::Malformed:
54 0 : return "malformed";
55 : case NetInfoStatus::MaxLimit:
56 2 : 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 10 : }
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 1129 : constexpr bool IsValidPurpose(const NetInfoPurpose purpose)
76 : {
77 1129 : switch (purpose) {
78 : case NetInfoPurpose::CORE_P2P:
79 : case NetInfoPurpose::PLATFORM_P2P:
80 : case NetInfoPurpose::PLATFORM_HTTPS:
81 1127 : return true;
82 : } // no default case, so the compiler can warn about missing cases
83 2 : return false;
84 1129 : }
85 :
86 : // Warning: Used in RPC code, altering existing values is a breaking change
87 22790 : constexpr std::string_view PurposeToString(const NetInfoPurpose purpose)
88 : {
89 22790 : switch (purpose) {
90 : case NetInfoPurpose::CORE_P2P:
91 21322 : return "core_p2p";
92 : case NetInfoPurpose::PLATFORM_P2P:
93 722 : return "platform_p2p";
94 : case NetInfoPurpose::PLATFORM_HTTPS:
95 746 : return "platform_https";
96 : } // no default case, so the compiler can warn about missing cases
97 0 : return "";
98 22790 : }
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 145 : std::string m_addr{};
155 145 : uint16_t m_port{0};
156 :
157 : private:
158 : static DomainPort::Status ValidateDomain(const std::string& input);
159 :
160 : public:
161 435 : DomainPort() = default;
162 : template <typename Stream>
163 : DomainPort(deserialize_type, Stream& s) { s >> *this; }
164 :
165 1514 : ~DomainPort() = default;
166 :
167 8 : bool operator<(const DomainPort& rhs) const { return std::tie(m_addr, m_port) < std::tie(rhs.m_addr, rhs.m_port); }
168 30 : 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 930 : SERIALIZE_METHODS(DomainPort, obj)
172 : {
173 310 : READWRITE(obj.m_addr);
174 310 : READWRITE(Using<BigEndianFormatter<2>>(obj.m_port));
175 310 : }
176 :
177 : bool IsEmpty() const { return m_addr.empty() && m_port == 0; }
178 423 : 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 85 : uint16_t GetPort() const { return m_port; }
182 142 : const std::string& ToStringAddr() const { return m_addr; }
183 28 : 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 250976 : uint8_t m_type{NetInfoType::Invalid};
197 250976 : std::variant<std::monostate, CService, DomainPort> m_data{std::monostate{}};
198 :
199 : public:
200 625299 : NetInfoEntry() = default;
201 40 : explicit NetInfoEntry(const DomainPort& domain)
202 20 : {
203 20 : if (!domain.IsValid()) return;
204 20 : m_type = NetInfoType::Domain;
205 20 : m_data = domain;
206 40 : }
207 85046 : explicit NetInfoEntry(const CService& service)
208 42523 : {
209 42523 : if (!service.IsValid()) return;
210 42406 : m_type = NetInfoType::Service;
211 42406 : m_data = service;
212 85046 : }
213 : template <typename Stream>
214 : explicit NetInfoEntry(deserialize_type, Stream& s) { s >> *this; }
215 :
216 1000008 : ~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 1704 : void Serialize(Stream& s_) const
224 : {
225 1704 : OverrideStream<Stream> s(&s_, /*nType=*/0, s_.GetVersion() | ADDRV2_FORMAT);
226 3279 : if (const auto* data_ptr_service{std::get_if<CService>(&m_data)};
227 1704 : m_type == NetInfoType::Service && data_ptr_service && data_ptr_service->IsValid()) {
228 1575 : s << m_type << *data_ptr_service;
229 1832 : } else if (const auto* data_ptr_domain{std::get_if<DomainPort>(&m_data)};
230 129 : m_type == NetInfoType::Domain && data_ptr_domain && data_ptr_domain->IsValid()) {
231 128 : s << m_type << *data_ptr_domain;
232 128 : } else {
233 1 : s << NetInfoType::Invalid;
234 : }
235 1704 : }
236 :
237 : template <typename Stream>
238 1560 : void Unserialize(Stream& s_)
239 : {
240 1560 : OverrideStream<Stream> s(&s_, /*nType=*/0, s_.GetVersion() | ADDRV2_FORMAT);
241 1560 : s >> m_type;
242 1560 : if (m_type == NetInfoType::Service) {
243 : try {
244 1453 : auto& service{m_data.emplace<CService>()};
245 1453 : s >> service;
246 1453 : if (!service.IsValid()) { Clear(); } // Invalid CService, mark as invalid
247 1453 : } catch (const std::ios_base::failure&) { Clear(); } // Deser failed, mark as invalid
248 1560 : } else if (m_type == NetInfoType::Domain) {
249 : try {
250 106 : auto& domain{m_data.emplace<DomainPort>()};
251 106 : s >> domain;
252 106 : if (!domain.IsValid()) { Clear(); } // Invalid DomainPort, mark as invalid
253 106 : } catch (const std::ios_base::failure&) { Clear(); } // Deser failed, mark as invalid
254 107 : } else { Clear(); } // Invalid type code, mark as invalid
255 1560 : }
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 164483 : 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 42964 : 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 26369 : bool operator==(const NetInfoInterface& rhs) const { return typeid(*this) == typeid(rhs) && this->IsEqual(rhs); }
299 9831 : 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 42388 : NetInfoEntry m_addr{};
309 :
310 : private:
311 : static NetInfoStatus ValidateService(const CService& service);
312 :
313 : public:
314 3129 : MnNetInfo() = default;
315 : template <typename Stream>
316 124035 : MnNetInfo(deserialize_type, Stream& s) { s >> *this; }
317 :
318 : template <typename Stream>
319 57683 : void Serialize(Stream& s) const
320 : {
321 115366 : if (const auto service_opt{m_addr.GetAddrPort()}) {
322 56805 : s << *service_opt;
323 56805 : } else {
324 878 : s << CService{};
325 : }
326 57683 : }
327 :
328 6457 : void Serialize(CSizeComputer& s) const
329 : {
330 6457 : s.seek(::GetSerializeSize(CService{}, s.GetVersion()));
331 6457 : }
332 :
333 : template <typename Stream>
334 41345 : void Unserialize(Stream& s)
335 : {
336 41345 : CService service;
337 41345 : s >> service;
338 41345 : m_addr = NetInfoEntry{service};
339 41345 : }
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 21548 : bool HasEntries(NetInfoPurpose purpose) const override { return purpose == NetInfoPurpose::CORE_P2P && !IsEmpty(); }
346 164476 : bool IsEmpty() const override { return m_addr.IsEmpty(); }
347 15878 : 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 26243 : bool IsEqual(const NetInfoInterface& rhs) const override
358 : {
359 : ASSERT_IF_DEBUG(typeid(*this) == typeid(rhs));
360 26243 : const auto& rhs_obj{static_cast<const MnNetInfo&>(rhs)};
361 26243 : 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 576 : uint8_t m_version{CURRENT_VERSION};
392 576 : std::map<NetInfoPurpose, NetInfoList> m_data{};
393 :
394 : // memory only
395 576 : NetInfoList m_all_entries{};
396 :
397 : public:
398 408 : ExtNetInfo() = default;
399 : template <typename Stream>
400 1320 : ExtNetInfo(deserialize_type, Stream& s) { s >> *this; }
401 :
402 : template <typename Stream>
403 498 : void Serialize(Stream& s) const
404 : {
405 498 : s << m_version;
406 498 : if (m_version == 0 || m_version > CURRENT_VERSION) {
407 0 : return; // Don't bother with unknown versions
408 : }
409 498 : s << m_data;
410 498 : }
411 :
412 : template <typename Stream>
413 440 : void Unserialize(Stream& s)
414 : {
415 440 : s >> m_version;
416 440 : if (m_version == 0 || m_version > CURRENT_VERSION) {
417 0 : return; // Don't bother with unknown versions
418 : }
419 440 : s >> m_data;
420 :
421 : // Regenerate internal cache
422 440 : m_all_entries.clear();
423 1310 : for (const auto& [_, entries] : m_data) {
424 2610 : m_all_entries.insert(m_all_entries.end(), entries.begin(), entries.end());
425 : }
426 440 : }
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 487 : bool IsEmpty() const override { return m_version == CURRENT_VERSION && m_data.empty(); }
434 470 : 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 124 : bool IsEqual(const NetInfoInterface& rhs) const override
450 : {
451 : ASSERT_IF_DEBUG(typeid(*this) == typeid(rhs));
452 124 : const auto& rhs_obj{static_cast<const ExtNetInfo&>(rhs)};
453 124 : 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 212836 : NetInfoSerWrapper(std::shared_ptr<NetInfoInterface>& data, const bool is_extended) :
467 106418 : m_data{data},
468 106418 : m_is_extended{is_extended}
469 106418 : {
470 212836 : }
471 :
472 : ~NetInfoSerWrapper() = default;
473 :
474 : template <typename Stream>
475 64633 : void Serialize(Stream& s) const
476 : {
477 129266 : if (const auto ptr{std::dynamic_pointer_cast<ExtNetInfo>(m_data)}) {
478 498 : s << *ptr;
479 128768 : } else if (const auto ptr{std::dynamic_pointer_cast<MnNetInfo>(m_data)}) {
480 64135 : s << *ptr;
481 64135 : } 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 64633 : }
487 :
488 : template <typename Stream>
489 41785 : void Unserialize(Stream& s)
490 : {
491 41785 : if (m_is_extended) {
492 440 : m_data = std::make_shared<ExtNetInfo>(deserialize, s);
493 440 : } else {
494 41345 : m_data = std::make_shared<MnNetInfo>(deserialize, s);
495 : }
496 41785 : }
497 : };
498 :
499 : #endif // BITCOIN_EVO_NETINFO_H
|