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 : #include <evo/netinfo.h>
6 :
7 : #include <chainparams.h>
8 : #include <evo/providertx.h>
9 : #include <netbase.h>
10 : #include <span.h>
11 : #include <util/check.h>
12 : #include <util/string.h>
13 : #include <util/system.h>
14 :
15 : #include <univalue.h>
16 :
17 : namespace {
18 : static std::unique_ptr<const CChainParams> g_main_params{nullptr};
19 : static std::once_flag g_main_params_flag;
20 :
21 : /** Maximum length of a label in a domain per RFC 1035 */
22 : static constexpr uint8_t DOMAIN_LABEL_MAX_LEN{63};
23 : /** Maximum possible length of a ASCII FQDN */
24 : static constexpr uint8_t DOMAIN_MAX_LEN{253};
25 : /** Minimum length of a FQDN */
26 : static constexpr uint8_t DOMAIN_MIN_LEN{3};
27 :
28 : static constexpr std::string_view SAFE_CHARS_ALPHA{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"};
29 : static constexpr std::string_view SAFE_CHARS_IPV4{"1234567890."};
30 : static constexpr std::string_view SAFE_CHARS_IPV4_6{"abcdefABCDEF1234567890.:[]"};
31 : static constexpr std::string_view SAFE_CHARS_RFC1035{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-"};
32 : static constexpr std::array<std::string_view, 13> TLDS_BAD{
33 : // ICANN resolution 2018.02.04.12
34 : ".mail",
35 : // Infrastructure TLD
36 : ".arpa",
37 : // RFC 6761
38 : ".example", ".invalid", ".localhost", ".test",
39 : // RFC 6762
40 : ".local",
41 : // RFC 6762, Appendix G
42 : ".corp", ".home", ".internal", ".intranet", ".lan", ".private",
43 : };
44 : static constexpr std::array<std::string_view, 2> TLDS_PRIVACY{".i2p", ".onion"};
45 :
46 213 : bool MatchCharsFilter(std::string_view input, std::string_view filter)
47 : {
48 2406 : return std::all_of(input.begin(), input.end(), [&filter](char c) { return filter.find(c) != std::string_view::npos; });
49 : }
50 :
51 36 : bool MatchSuffix(const std::string& str, Span<const std::string_view> list)
52 : {
53 36 : if (str.empty()) return false;
54 202 : for (const auto& suffix : list) {
55 174 : if (suffix.size() > str.size()) continue;
56 174 : if (std::string_view{str}.ends_with(suffix)) return true;
57 : }
58 28 : return false;
59 36 : }
60 :
61 6 : bool IsAllowedPlatformHTTPPort(uint16_t port)
62 : {
63 6 : switch (port) {
64 : case 443:
65 5 : return true;
66 : }
67 1 : return false;
68 6 : }
69 : } // anonymous namespace
70 :
71 140 : bool IsNodeOnMainnet() { return Params().NetworkIDString() == CBaseChainParams::MAIN; }
72 :
73 0 : bool IsServiceDeprecatedRPCEnabled()
74 : {
75 0 : const auto args = gArgs.GetArgs("-deprecatedrpc");
76 0 : return std::find(args.begin(), args.end(), "service") != args.end();
77 0 : }
78 :
79 140 : const CChainParams& MainParams()
80 : {
81 144 : std::call_once(g_main_params_flag, [&]() { g_main_params = CreateChainParams(::gArgs, CBaseChainParams::MAIN); });
82 140 : return *Assert(g_main_params);
83 : }
84 :
85 0 : UniValue ArrFromService(const CService& addr)
86 : {
87 0 : UniValue obj(UniValue::VARR);
88 0 : obj.push_back(addr.ToStringAddrPort());
89 0 : return obj;
90 0 : }
91 :
92 53 : DomainPort::Status DomainPort::ValidateDomain(const std::string& addr)
93 : {
94 53 : if (addr.length() > DOMAIN_MAX_LEN || addr.length() < DOMAIN_MIN_LEN) {
95 2 : return DomainPort::Status::BadLen;
96 : }
97 51 : if (!MatchCharsFilter(addr, SAFE_CHARS_RFC1035)) {
98 2 : return DomainPort::Status::BadChar;
99 : }
100 49 : if (addr.front() == '.' || addr.back() == '.') {
101 2 : return DomainPort::Status::BadCharPos;
102 : }
103 47 : std::vector<std::string> labels{SplitString(addr, '.')};
104 47 : if (labels.size() < 2) {
105 2 : return DomainPort::Status::BadDotless;
106 : }
107 148 : for (const auto& label : labels) {
108 106 : if (label.empty() || label.length() > DOMAIN_LABEL_MAX_LEN) {
109 2 : return DomainPort::Status::BadLabelLen;
110 : }
111 104 : if (label.front() == '-' || label.back() == '-') {
112 1 : return DomainPort::Status::BadLabelCharPos;
113 : }
114 : }
115 42 : return DomainPort::Status::Success;
116 53 : }
117 :
118 25 : DomainPort::Status DomainPort::Set(const std::string& addr, const uint16_t port)
119 : {
120 25 : if (port == 0) {
121 1 : return DomainPort::Status::BadPort;
122 : }
123 24 : const auto ret{ValidateDomain(addr)};
124 24 : if (ret == DomainPort::Status::Success) {
125 : // Convert to lowercase to avoid duplication by changing case (domains are case-insensitive)
126 13 : m_addr = ToLower(addr);
127 13 : m_port = port;
128 13 : }
129 24 : return ret;
130 25 : }
131 :
132 40 : DomainPort::Status DomainPort::Validate() const
133 : {
134 40 : if (m_addr.empty() || m_addr != ToLower(m_addr)) {
135 11 : return DomainPort::Status::Malformed;
136 : }
137 29 : if (m_port == 0) {
138 0 : return DomainPort::Status::BadPort;
139 : }
140 29 : return ValidateDomain(m_addr);
141 40 : }
142 :
143 1940 : bool NetInfoEntry::operator==(const NetInfoEntry& rhs) const
144 : {
145 1940 : if (m_type != rhs.m_type) return false;
146 585 : return std::visit(
147 585 : [](auto&& lhs, auto&& rhs) -> bool {
148 : if constexpr (std::is_same_v<decltype(lhs), decltype(rhs)>) {
149 585 : return lhs == rhs;
150 : }
151 0 : return false;
152 : },
153 585 : m_data, rhs.m_data);
154 1940 : }
155 :
156 35 : bool NetInfoEntry::operator<(const NetInfoEntry& rhs) const
157 : {
158 35 : if (m_type != rhs.m_type) return m_type < rhs.m_type;
159 34 : return std::visit(
160 34 : [](auto&& lhs, auto&& rhs) -> bool {
161 : using T1 = std::decay_t<decltype(lhs)>;
162 : using T2 = std::decay_t<decltype(rhs)>;
163 : if constexpr (std::is_same_v<T1, T2>) {
164 : // Both the same type, compare as usual
165 34 : return lhs < rhs;
166 : } else if constexpr ((std::is_same_v<T1, CService> || std::is_same_v<T1, DomainPort>) &&
167 : (std::is_same_v<T2, CService> || std::is_same_v<T2, DomainPort>)) {
168 : // Differing types but both implement ToStringAddrPort(), lexicographical compare strings
169 0 : return lhs.ToStringAddrPort() < rhs.ToStringAddrPort();
170 : }
171 : // If lhs is monostate, it less than rhs; otherwise rhs is greater
172 0 : return std::is_same_v<T1, std::monostate>;
173 0 : },
174 34 : m_data, rhs.m_data);
175 35 : }
176 :
177 1355 : std::optional<CService> NetInfoEntry::GetAddrPort() const
178 : {
179 1355 : if (const auto* data_ptr{std::get_if<CService>(&m_data)}; m_type == NetInfoType::Service && data_ptr) {
180 : ASSERT_IF_DEBUG(data_ptr->IsValid());
181 1339 : return *data_ptr;
182 : }
183 16 : return std::nullopt;
184 1355 : }
185 :
186 29 : std::optional<DomainPort> NetInfoEntry::GetDomainPort() const
187 : {
188 29 : if (const auto* data_ptr{std::get_if<DomainPort>(&m_data)}; m_type == NetInfoType::Domain && data_ptr) {
189 : ASSERT_IF_DEBUG(data_ptr->IsValid());
190 8 : return *data_ptr;
191 : }
192 21 : return std::nullopt;
193 29 : }
194 :
195 2 : uint16_t NetInfoEntry::GetPort() const
196 : {
197 2 : return std::visit(
198 2 : [](auto&& input) -> uint16_t {
199 : using T1 = std::decay_t<decltype(input)>;
200 : if constexpr (std::is_same_v<T1, CService> || std::is_same_v<T1, DomainPort>) {
201 1 : return input.GetPort();
202 : }
203 1 : return 0;
204 : },
205 2 : m_data);
206 : }
207 :
208 : // NetInfoEntry is a dumb object that doesn't enforce validation rules, that is the responsibility of
209 : // types that utilize NetInfoEntry (MnNetInfo and others). IsTriviallyValid() is there to check if a
210 : // NetInfoEntry object is properly constructed.
211 254 : bool NetInfoEntry::IsTriviallyValid() const
212 : {
213 254 : if (m_type == NetInfoType::Invalid) return false;
214 235 : return std::visit(
215 470 : [this](auto&& input) -> bool {
216 : using T1 = std::decay_t<decltype(input)>;
217 : static_assert(std::is_same_v<T1, std::monostate> || std::is_same_v<T1, CService> || std::is_same_v<T1, DomainPort>,
218 : "Unexpected type");
219 : if constexpr (std::is_same_v<T1, std::monostate>) {
220 : // Empty underlying data isn't a valid entry
221 0 : return false;
222 : } else if constexpr (std::is_same_v<T1, CService>) {
223 : // Type code should be truthful as it decides what underlying type is used when (de)serializing
224 225 : if (m_type != NetInfoType::Service) return false;
225 : // Underlying data must meet surface-level validity checks for its type
226 225 : if (!input.IsValid()) return false;
227 : } else if constexpr (std::is_same_v<T1, DomainPort>) {
228 : // Type code should be truthful as it decides what underlying type is used when (de)serializing
229 10 : if (m_type != NetInfoType::Domain) return false;
230 : // Underlying data should at least meet surface-level validity checks
231 10 : if (!input.IsValid()) return false;
232 : }
233 235 : return true;
234 235 : },
235 235 : m_data);
236 254 : }
237 :
238 79 : std::string NetInfoEntry::ToString() const
239 : {
240 79 : return std::visit(
241 79 : [](auto&& input) -> std::string {
242 : using T1 = std::decay_t<decltype(input)>;
243 : if constexpr (std::is_same_v<T1, CService>) {
244 78 : return strprintf("CService(addr=%s, port=%u)", input.ToStringAddr(), input.GetPort());
245 : } else if constexpr (std::is_same_v<T1, DomainPort>) {
246 0 : return strprintf("DomainPort(addr=%s, port=%u)", input.ToStringAddr(), input.GetPort());
247 : }
248 1 : return "[invalid entry]";
249 0 : },
250 79 : m_data);
251 : }
252 :
253 30 : std::string NetInfoEntry::ToStringAddr() const
254 : {
255 30 : return std::visit(
256 30 : [](auto&& input) -> std::string {
257 : using T1 = std::decay_t<decltype(input)>;
258 : if constexpr (std::is_same_v<T1, CService> || std::is_same_v<T1, DomainPort>) {
259 29 : return input.ToStringAddr();
260 : }
261 1 : return "[invalid entry]";
262 : },
263 30 : m_data);
264 : }
265 :
266 2 : std::string NetInfoEntry::ToStringAddrPort() const
267 : {
268 2 : return std::visit(
269 2 : [](auto&& input) -> std::string {
270 : using T1 = std::decay_t<decltype(input)>;
271 : if constexpr (std::is_same_v<T1, CService> || std::is_same_v<T1, DomainPort>) {
272 1 : return input.ToStringAddrPort();
273 : }
274 1 : return "[invalid entry]";
275 : },
276 2 : m_data);
277 : }
278 :
279 87 : std::shared_ptr<NetInfoInterface> NetInfoInterface::MakeNetInfo(const uint16_t nVersion)
280 : {
281 87 : assert(nVersion > 0);
282 87 : if (nVersion >= ProTxVersion::ExtAddr) {
283 0 : return std::make_shared<ExtNetInfo>();
284 : }
285 87 : return std::make_shared<MnNetInfo>();
286 87 : }
287 :
288 142 : NetInfoStatus MnNetInfo::ValidateService(const CService& service)
289 : {
290 142 : if (!service.IsValid()) {
291 1 : return NetInfoStatus::BadAddress;
292 : }
293 141 : if (!service.IsIPv4()) {
294 0 : return NetInfoStatus::BadType;
295 : }
296 141 : if (Params().RequireRoutableExternalIP() && !service.IsRoutable()) {
297 1 : return NetInfoStatus::NotRoutable;
298 : }
299 :
300 140 : if (IsNodeOnMainnet() != (service.GetPort() == MainParams().GetDefaultPort())) {
301 : // Must use mainnet port on mainnet.
302 : // Must NOT use mainnet port on other networks.
303 2 : return NetInfoStatus::BadPort;
304 : }
305 :
306 138 : return NetInfoStatus::Success;
307 142 : }
308 :
309 92 : NetInfoStatus MnNetInfo::AddEntry(const NetInfoPurpose purpose, const std::string& input)
310 : {
311 92 : if (purpose != NetInfoPurpose::CORE_P2P || !IsEmpty()) {
312 9 : return NetInfoStatus::MaxLimit;
313 : }
314 :
315 83 : std::string addr;
316 83 : uint16_t port{Params().GetDefaultPort()};
317 83 : SplitHostPort(input, port, addr);
318 : // Contains invalid characters, unlikely to pass Lookup(), fast-fail
319 83 : if (!MatchCharsFilter(addr, SAFE_CHARS_IPV4)) {
320 6 : return NetInfoStatus::BadInput;
321 : }
322 :
323 152 : if (auto service_opt{Lookup(addr, /*portDefault=*/port, /*fAllowLookup=*/false)}) {
324 75 : const auto ret{ValidateService(*service_opt)};
325 75 : if (ret == NetInfoStatus::Success) {
326 71 : m_addr = NetInfoEntry{*service_opt};
327 : ASSERT_IF_DEBUG(m_addr.GetAddrPort().has_value());
328 71 : }
329 75 : return ret;
330 : }
331 2 : return NetInfoStatus::BadInput;
332 92 : }
333 :
334 955 : NetInfoList MnNetInfo::GetEntries(std::optional<NetInfoPurpose> purpose_opt) const
335 : {
336 955 : if (!IsEmpty() && (!purpose_opt || *purpose_opt == NetInfoPurpose::CORE_P2P)) {
337 : ASSERT_IF_DEBUG(m_addr.GetAddrPort().has_value());
338 914 : return {m_addr};
339 : }
340 : // If MnNetInfo is empty or we are given an unexpected purpose code, we don't
341 : // expect any entries to show up, so we return a blank set instead.
342 41 : return NetInfoList{};
343 955 : }
344 :
345 71 : CService MnNetInfo::GetPrimary() const
346 : {
347 142 : if (const auto service_opt{m_addr.GetAddrPort()}) {
348 71 : return *service_opt;
349 : }
350 0 : return CService{};
351 71 : }
352 :
353 83 : NetInfoStatus MnNetInfo::Validate() const
354 : {
355 83 : if (!m_addr.IsTriviallyValid()) {
356 16 : return NetInfoStatus::Malformed;
357 : }
358 67 : return ValidateService(GetPrimary());
359 83 : }
360 :
361 0 : UniValue MnNetInfo::ToJson(std::optional<NetInfoPurpose> purpose_opt) const
362 : {
363 0 : if (purpose_opt) {
364 0 : UniValue arr(UniValue::VARR);
365 0 : for (const auto& entry : GetEntries(purpose_opt)) {
366 0 : arr.push_back(entry.ToStringAddrPort());
367 : }
368 0 : return arr;
369 0 : }
370 :
371 0 : UniValue ret{UniValue::VOBJ};
372 0 : if (!IsEmpty()) {
373 0 : ret.pushKV(PurposeToString(NetInfoPurpose::CORE_P2P).data(), ArrFromService(GetPrimary()));
374 0 : }
375 0 : return ret;
376 0 : }
377 :
378 77 : std::string MnNetInfo::ToString() const
379 : {
380 154 : return IsEmpty() ? "MnNetInfo()"
381 154 : : strprintf("MnNetInfo(NetInfo(purpose=%s, [%s]))", PurposeToString(NetInfoPurpose::CORE_P2P),
382 77 : m_addr.ToString());
383 0 : }
384 :
385 12 : bool ExtNetInfo::HasAddrPortDuplicates() const
386 : {
387 12 : std::set<NetInfoEntry> known{};
388 29 : for (const auto& entry : m_all_entries) {
389 17 : if (auto [_, inserted] = known.insert(entry); !inserted) {
390 0 : return true;
391 : }
392 : }
393 : ASSERT_IF_DEBUG(known.size() == m_all_entries.size());
394 12 : return false;
395 12 : }
396 :
397 29 : bool ExtNetInfo::IsAddrPortDuplicate(const NetInfoEntry& candidate) const
398 : {
399 58 : return std::any_of(m_all_entries.begin(), m_all_entries.end(),
400 47 : [&candidate](const auto& entry) { return candidate == entry; });
401 : }
402 :
403 14 : bool ExtNetInfo::HasAddrDuplicates(const NetInfoList& entries) const
404 : {
405 14 : std::unordered_set<std::string> known{};
406 31 : for (const auto& entry : entries) {
407 17 : if (auto [_, inserted] = known.insert(entry.ToStringAddr()); !inserted) {
408 0 : return true;
409 : }
410 : }
411 : ASSERT_IF_DEBUG(known.size() == entries.size());
412 14 : return false;
413 14 : }
414 :
415 4 : bool ExtNetInfo::IsAddrDuplicate(const NetInfoEntry& candidate, const NetInfoList& entries) const
416 : {
417 4 : const std::string& candidate_str{candidate.ToStringAddr()};
418 8 : return std::any_of(entries.begin(), entries.end(),
419 11 : [&candidate_str](const auto& entry) { return candidate_str == entry.ToStringAddr(); });
420 4 : }
421 :
422 29 : NetInfoStatus ExtNetInfo::ProcessCandidate(const NetInfoPurpose purpose, const NetInfoEntry& candidate)
423 : {
424 29 : assert(candidate.IsTriviallyValid());
425 :
426 29 : if (IsAddrPortDuplicate(candidate)) {
427 2 : return NetInfoStatus::Duplicate;
428 : }
429 27 : if (candidate.GetDomainPort().has_value() && purpose != NetInfoPurpose::PLATFORM_HTTPS) {
430 : // Domains only allowed for Platform HTTPS API
431 2 : return NetInfoStatus::BadInput;
432 : }
433 25 : if (auto it{m_data.find(purpose)}; it != m_data.end()) {
434 : // Existing entries list found, check limit
435 5 : auto& [_, entries] = *it;
436 5 : if (entries.size() >= MAX_ENTRIES_EXTNETINFO) {
437 1 : return NetInfoStatus::MaxLimit;
438 : }
439 8 : if (IsAddrDuplicate(candidate, entries)) {
440 1 : return NetInfoStatus::Duplicate;
441 : }
442 3 : entries.push_back(candidate);
443 3 : } else {
444 : // First entry for purpose code, create new entries list
445 20 : auto [_, status] = m_data.try_emplace(purpose, std::vector<NetInfoEntry>({candidate}));
446 20 : assert(status); // We did just check to see if our value already existed, try_emplace shouldn't fail
447 : }
448 :
449 : // Candidate successfully added, update cache
450 23 : m_all_entries.push_back(candidate);
451 23 : return NetInfoStatus::Success;
452 29 : }
453 :
454 45 : NetInfoStatus ExtNetInfo::ValidateService(const CService& service)
455 : {
456 45 : if (!service.IsValid()) {
457 1 : return NetInfoStatus::BadAddress;
458 : }
459 44 : if (!service.IsCJDNS() && !service.IsI2P() && !service.IsIPv4() && !service.IsIPv6() && !service.IsTor()) {
460 0 : return NetInfoStatus::BadType;
461 : }
462 44 : if (Params().RequireRoutableExternalIP() && !service.IsRoutable()) {
463 1 : return NetInfoStatus::NotRoutable;
464 : }
465 43 : const uint16_t service_port{service.GetPort()};
466 43 : if (service.IsI2P()) {
467 2 : if (service_port != I2P_SAM31_PORT) {
468 : // I2P SAM 3.1 and earlier don't support arbitrary ports
469 1 : return NetInfoStatus::BadPort;
470 : }
471 1 : } else {
472 41 : if (service_port == 0 || IsBadPort(service_port)) {
473 4 : return NetInfoStatus::BadPort;
474 : }
475 : }
476 :
477 38 : return NetInfoStatus::Success;
478 45 : }
479 :
480 11 : NetInfoStatus ExtNetInfo::ValidateDomainPort(const DomainPort& domain)
481 : {
482 11 : if (!domain.IsValid()) {
483 0 : return NetInfoStatus::BadInput;
484 : }
485 11 : const uint16_t domain_port{domain.GetPort()};
486 11 : if (domain_port == 0 || (IsBadPort(domain_port) && !IsAllowedPlatformHTTPPort(domain_port))) {
487 1 : return NetInfoStatus::BadPort;
488 : }
489 10 : const std::string& addr{domain.ToStringAddr()};
490 10 : if (MatchSuffix(addr, TLDS_BAD) || MatchSuffix(addr, TLDS_PRIVACY)) {
491 1 : return NetInfoStatus::BadInput;
492 : }
493 10 : if (const auto labels{SplitString(addr, '.')}; !MatchCharsFilter(labels.at(labels.size() - 1), SAFE_CHARS_ALPHA)) {
494 1 : return NetInfoStatus::BadInput;
495 : }
496 :
497 8 : return NetInfoStatus::Success;
498 11 : }
499 :
500 54 : NetInfoStatus ExtNetInfo::AddEntry(const NetInfoPurpose purpose, const std::string& input)
501 : {
502 54 : if (!IsValidPurpose(purpose)) {
503 1 : return NetInfoStatus::MaxLimit;
504 : }
505 :
506 : // We don't allow assuming ports, so we set the default value to 0 so that if no port is specified
507 : // it uses a fallback value of 0, which will return a NetInfoStatus::BadPort
508 53 : std::string addr;
509 53 : uint16_t port{0};
510 53 : SplitHostPort(input, port, addr);
511 :
512 53 : if (!MatchCharsFilter(addr, SAFE_CHARS_IPV4_6)) {
513 17 : if (!MatchCharsFilter(addr, SAFE_CHARS_RFC1035)) {
514 : // Neither IP:port safe nor domain-safe, we can safely assume it's bad input
515 0 : return NetInfoStatus::BadInput;
516 : }
517 :
518 : // Not IP:port safe but domain safe
519 17 : if (MatchSuffix(addr, TLDS_PRIVACY)) {
520 : // Special domain, try storing it as CService
521 7 : CNetAddr netaddr;
522 7 : if (netaddr.SetSpecial(addr)) {
523 7 : const CService service{netaddr, port};
524 7 : const auto ret{ValidateService(service)};
525 7 : if (ret == NetInfoStatus::Success) {
526 5 : return ProcessCandidate(purpose, NetInfoEntry{service});
527 : }
528 2 : return ret; /* ValidateService() failed */
529 7 : }
530 26 : } else if (DomainPort domain; domain.Set(addr, port) == DomainPort::Status::Success) {
531 : // Regular domain
532 9 : const auto ret{ValidateDomainPort(domain)};
533 9 : if (ret == NetInfoStatus::Success) {
534 6 : return ProcessCandidate(purpose, NetInfoEntry{domain});
535 : }
536 3 : return ret; /* ValidateDomainPort() failed */
537 : }
538 1 : return NetInfoStatus::BadInput; /* CNetAddr::SetSpecial() or DomainPort::Set() failed */
539 : }
540 :
541 : // IP:port safe, try to parse it as IP:port
542 59 : if (auto service_opt{Lookup(addr, /*portDefault=*/port, /*fAllowLookup=*/false)}) {
543 23 : const auto service{MaybeFlipIPv6toCJDNS(*service_opt)};
544 23 : const auto ret{ValidateService(service)};
545 23 : if (ret == NetInfoStatus::Success) {
546 18 : return ProcessCandidate(purpose, NetInfoEntry{service});
547 : }
548 5 : return ret; /* ValidateService() failed */
549 23 : }
550 13 : return NetInfoStatus::BadInput; /* Lookup() failed */
551 54 : }
552 :
553 35 : NetInfoList ExtNetInfo::GetEntries(std::optional<NetInfoPurpose> purpose_opt) const
554 : {
555 35 : if (!purpose_opt) {
556 : // Without a purpose code specified, we're being requested for a flattened list of all
557 : // entries. ExtNetInfo is an append-only structure, so we can avoid re-calculating a
558 : // list of all entries by maintaining a small cache.
559 35 : return m_all_entries;
560 : }
561 0 : if (!IsValidPurpose(*purpose_opt)) return NetInfoList{};
562 0 : const auto& it{m_data.find(*purpose_opt)};
563 0 : return it != m_data.end() ? it->second : NetInfoList{};
564 35 : }
565 :
566 0 : CService ExtNetInfo::GetPrimary() const
567 : {
568 0 : if (const auto& it{m_data.find(NetInfoPurpose::CORE_P2P)}; it != m_data.end()) {
569 0 : const auto& [_, entries] = *it;
570 : // If a purpose code is in the map, there should be at least one entry
571 : ASSERT_IF_DEBUG(!entries.empty());
572 0 : if (entries.size() >= 1) {
573 0 : if (const auto& service_opt{entries[0].GetAddrPort()}) {
574 0 : return *service_opt;
575 : }
576 0 : }
577 0 : }
578 0 : return CService{};
579 0 : }
580 :
581 31 : bool ExtNetInfo::HasEntries(NetInfoPurpose purpose) const
582 : {
583 31 : if (!IsValidPurpose(purpose)) return false;
584 30 : const auto& it{m_data.find(purpose)};
585 30 : return it != m_data.end() && !it->second.empty();
586 31 : }
587 :
588 27 : NetInfoStatus ExtNetInfo::Validate() const
589 : {
590 27 : if (m_version == 0 || m_version > CURRENT_VERSION || m_data.empty()) {
591 15 : return NetInfoStatus::Malformed;
592 : }
593 12 : if (HasAddrPortDuplicates()) {
594 0 : return NetInfoStatus::Duplicate;
595 : }
596 28 : for (const auto& [purpose, entries] : m_data) {
597 14 : if (!IsValidPurpose(purpose)) {
598 0 : return NetInfoStatus::Malformed;
599 : }
600 14 : if (entries.empty()) {
601 : // Purpose if present in map must have at least one entry
602 0 : return NetInfoStatus::Malformed;
603 : }
604 14 : if (HasAddrDuplicates(entries)) {
605 0 : return NetInfoStatus::Duplicate;
606 : }
607 31 : for (const auto& entry : entries) {
608 17 : if (!entry.IsTriviallyValid()) {
609 : // Trivially invalid NetInfoEntry, no point checking against consensus rules
610 0 : return NetInfoStatus::Malformed;
611 : }
612 34 : if (const auto& service_opt{entry.GetAddrPort()}) {
613 15 : if (auto ret{ValidateService(*service_opt)}; ret != NetInfoStatus::Success) {
614 : // Stores CService underneath but doesn't pass validation rules
615 0 : return ret;
616 : }
617 19 : } else if (const auto domain_opt{entry.GetDomainPort()}) {
618 2 : if (purpose != NetInfoPurpose::PLATFORM_HTTPS) {
619 : // Domains only allowed for Platform HTTPS API
620 0 : return NetInfoStatus::BadInput;
621 : }
622 2 : if (auto ret{ValidateDomainPort(*domain_opt)}; ret != NetInfoStatus::Success) {
623 : // Stores DomainPort underneath but doesn't pass validation rules
624 0 : return ret;
625 : }
626 2 : } else {
627 : // Doesn't store valid type underneath
628 0 : return NetInfoStatus::Malformed;
629 : }
630 : }
631 : }
632 12 : return NetInfoStatus::Success;
633 27 : }
634 :
635 0 : UniValue ExtNetInfo::ToJson(std::optional<NetInfoPurpose> purpose_opt) const
636 : {
637 : // Report only a subset of all addresses if supplied a purpose code
638 0 : if (purpose_opt) {
639 0 : UniValue arr(UniValue::VARR);
640 0 : for (const auto& entry : GetEntries(purpose_opt)) {
641 0 : arr.push_back(entry.ToStringAddrPort());
642 : }
643 0 : return arr;
644 0 : }
645 :
646 0 : UniValue ret(UniValue::VOBJ);
647 0 : for (const auto& [purpose, entries] : m_data) {
648 0 : UniValue arr(UniValue::VARR);
649 0 : for (const auto& entry : entries) {
650 0 : arr.push_back(entry.ToStringAddrPort());
651 : }
652 0 : ret.pushKV(PurposeToString(purpose).data(), arr);
653 0 : }
654 0 : return ret;
655 0 : }
656 :
657 0 : std::string ExtNetInfo::ToString() const
658 : {
659 0 : return IsEmpty() ? "ExtNetInfo()" : strprintf("ExtNetInfo(%s)", [&]() -> std::string {
660 0 : std::string ret{};
661 0 : bool first{true};
662 0 : for (const auto& [purpose, entries] : m_data) {
663 0 : if (!first) { ret += ", "; } else { first = false; }
664 0 : ret += strprintf("NetInfo(purpose=%s, [%s])", PurposeToString(purpose), [&]() -> std::string {
665 0 : if (entries.empty()) {
666 0 : return "invalid list";
667 : }
668 0 : return Join(entries, ", ", [](const auto& entry) { return entry.ToString(); });
669 0 : }());
670 : }
671 0 : return ret;
672 0 : }());
673 0 : }
|