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
|