@@ -194,9 +194,9 @@ NetInfoStatus MnNetInfo::ValidateService(const CService& service)
194194 return NetInfoStatus::Success;
195195}
196196
197- NetInfoStatus MnNetInfo::AddEntry (const std::string& input)
197+ NetInfoStatus MnNetInfo::AddEntry (const NetInfoPurpose purpose, const std::string& input)
198198{
199- if (!IsEmpty ()) {
199+ if (purpose != NetInfoPurpose::CORE_P2P || !IsEmpty ()) {
200200 return NetInfoStatus::MaxLimit;
201201 }
202202
@@ -248,48 +248,61 @@ NetInfoStatus MnNetInfo::Validate() const
248248
249249UniValue MnNetInfo::ToJson () const
250250{
251- if (IsEmpty ()) {
252- return UniValue{UniValue::VARR};
251+ UniValue ret{UniValue::VOBJ};
252+ if (!IsEmpty ()) {
253+ ret.pushKV (PurposeToString (NetInfoPurpose::CORE_P2P).data (), ArrFromService (GetPrimary ()));
253254 }
254- return ArrFromService ( GetPrimary ()) ;
255+ return ret ;
255256}
256257
257258std::string MnNetInfo::ToString () const
258259{
259- return IsEmpty () ? " MnNetInfo()" : strprintf (" MnNetInfo([%s])" , m_addr.ToString ());
260+ return IsEmpty () ? " MnNetInfo()"
261+ : strprintf (" MnNetInfo(NetInfo(purpose=%s, [%s]))" , PurposeToString (NetInfoPurpose::CORE_P2P),
262+ m_addr.ToString ());
260263}
261264
262265bool ExtNetInfo::HasDuplicates () const
263266{
264267 std::unordered_set<std::string> known{};
265- for (const NetInfoEntry & entry : m_data ) {
268+ for (const auto & entry : m_all_entries ) {
266269 if (auto [_, inserted] = known.insert (entry.ToStringAddr ()); !inserted) {
267270 return true ;
268271 }
269272 }
270- ASSERT_IF_DEBUG (known.size () == m_data .size ());
273+ ASSERT_IF_DEBUG (known.size () == m_all_entries .size ());
271274 return false ;
272275}
273276
274277bool ExtNetInfo::IsDuplicateCandidate (const NetInfoEntry& candidate) const
275278{
276279 const std::string& candidate_str{candidate.ToStringAddr ()};
277- return std::any_of (m_data .begin (), m_data .end (),
280+ return std::any_of (m_all_entries .begin (), m_all_entries .end (),
278281 [&candidate_str](const auto & entry) { return candidate_str == entry.ToStringAddr (); });
279282}
280283
281- NetInfoStatus ExtNetInfo::ProcessCandidate (const NetInfoEntry& candidate)
284+ NetInfoStatus ExtNetInfo::ProcessCandidate (const NetInfoPurpose purpose, const NetInfoEntry& candidate)
282285{
283286 assert (candidate.IsTriviallyValid ());
284287
285- if (m_data.size () >= MAX_ENTRIES_EXTNETINFO) {
286- return NetInfoStatus::MaxLimit;
287- }
288288 if (IsDuplicateCandidate (candidate)) {
289289 return NetInfoStatus::Duplicate;
290290 }
291- m_data.push_back (candidate);
291+ if (auto it{m_data.find (purpose)}; it != m_data.end ()) {
292+ // Existing entries list found, check limit
293+ auto & [_, entries] = *it;
294+ if (entries.size () >= MAX_ENTRIES_EXTNETINFO) {
295+ return NetInfoStatus::MaxLimit;
296+ }
297+ entries.push_back (candidate);
298+ } else {
299+ // First entry for purpose code, create new entries list
300+ auto [_, status] = m_data.try_emplace (purpose, std::vector<NetInfoEntry>({candidate}));
301+ assert (status); // We did just check to see if our value already existed, try_emplace shouldn't fail
302+ }
292303
304+ // Candidate succesfully added, update cache
305+ m_all_entries.push_back (candidate);
293306 return NetInfoStatus::Success;
294307}
295308
@@ -311,8 +324,12 @@ NetInfoStatus ExtNetInfo::ValidateService(const CService& service)
311324 return NetInfoStatus::Success;
312325}
313326
314- NetInfoStatus ExtNetInfo::AddEntry (const std::string& input)
327+ NetInfoStatus ExtNetInfo::AddEntry (const NetInfoPurpose purpose, const std::string& input)
315328{
329+ if (!IsValidPurpose (purpose)) {
330+ return NetInfoStatus::MaxLimit;
331+ }
332+
316333 // We don't allow assuming ports, so we set the default value to 0 so that if no port is specified
317334 // it uses a fallback value of 0, which will return a NetInfoStatus::BadPort
318335 std::string addr;
@@ -326,7 +343,7 @@ NetInfoStatus ExtNetInfo::AddEntry(const std::string& input)
326343 if (auto service_opt{Lookup (addr, /* portDefault=*/ port, /* fAllowLookup=*/ false )}) {
327344 const auto ret{ValidateService (*service_opt)};
328345 if (ret == NetInfoStatus::Success) {
329- return ProcessCandidate (NetInfoEntry{*service_opt});
346+ return ProcessCandidate (purpose, NetInfoEntry{*service_opt});
330347 }
331348 return ret; /* ValidateService() failed */
332349 }
@@ -335,19 +352,33 @@ NetInfoStatus ExtNetInfo::AddEntry(const std::string& input)
335352
336353NetInfoList ExtNetInfo::GetEntries () const
337354{
338- return m_data;
355+ // ExtNetInfo is an append-only structure, we can avoid re-calculating
356+ // a list of all entries by maintaining a small cache.
357+ return m_all_entries;
339358}
340359
341360CService ExtNetInfo::GetPrimary () const
342361{
343- if (m_data.size () >= 1 ) {
344- if (const auto & service_opt{m_data[0 ].GetAddrPort ()}) {
345- return *service_opt;
362+ if (const auto & it{m_data.find (NetInfoPurpose::CORE_P2P)}; it != m_data.end ()) {
363+ const auto & [_, entries] = *it;
364+ // If a purpose code is in the map, there should be at least one entry
365+ ASSERT_IF_DEBUG (!entries.empty ());
366+ if (entries.size () >= 1 ) {
367+ if (const auto & service_opt{entries[0 ].GetAddrPort ()}) {
368+ return *service_opt;
369+ }
346370 }
347371 }
348372 return CService{};
349373}
350374
375+ bool ExtNetInfo::HasEntries (NetInfoPurpose purpose) const
376+ {
377+ if (!IsValidPurpose (purpose)) return false ;
378+ const auto & it{m_data.find (purpose)};
379+ return it != m_data.end () && !it->second .empty ();
380+ }
381+
351382NetInfoStatus ExtNetInfo::Validate () const
352383{
353384 if (m_version == 0 || m_version > CURRENT_VERSION || m_data.empty ()) {
@@ -356,36 +387,60 @@ NetInfoStatus ExtNetInfo::Validate() const
356387 if (HasDuplicates ()) {
357388 return NetInfoStatus::Duplicate;
358389 }
359- for (const auto & entry : m_data) {
360- if (!entry.IsTriviallyValid ()) {
361- // Trivially invalid NetInfoEntry, no point checking against consensus rules
390+ for (const auto & [purpose, entries] : m_data) {
391+ if (!IsValidPurpose (purpose)) {
362392 return NetInfoStatus::Malformed;
363393 }
364- if (const auto & service_opt{entry.GetAddrPort ()}) {
365- if (auto ret{ValidateService (*service_opt)}; ret != NetInfoStatus::Success) {
366- // Stores CService underneath but doesn't pass validation rules
367- return ret;
368- }
369- } else {
370- // Doesn't store valid type underneath
394+ if (entries.empty ()) {
395+ // Purpose if present in map must have at least one entry
371396 return NetInfoStatus::Malformed;
372397 }
398+ for (const auto & entry : entries) {
399+ if (!entry.IsTriviallyValid ()) {
400+ // Trivially invalid NetInfoEntry, no point checking against consensus rules
401+ return NetInfoStatus::Malformed;
402+ }
403+ if (const auto & service_opt{entry.GetAddrPort ()}) {
404+ if (auto ret{ValidateService (*service_opt)}; ret != NetInfoStatus::Success) {
405+ // Stores CService underneath but doesn't pass validation rules
406+ return ret;
407+ }
408+ } else {
409+ // Doesn't store valid type underneath
410+ return NetInfoStatus::Malformed;
411+ }
412+ }
373413 }
374414 return NetInfoStatus::Success;
375415}
376416
377417UniValue ExtNetInfo::ToJson () const
378418{
379- UniValue ret (UniValue::VARR);
380- for (const auto & entry : m_data) {
381- ret.push_back (entry.ToStringAddrPort ());
419+ UniValue ret (UniValue::VOBJ);
420+ for (const auto & [purpose, entries] : m_data) {
421+ UniValue arr (UniValue::VARR);
422+ for (const auto & entry : entries) {
423+ arr.push_back (entry.ToStringAddrPort ());
424+ }
425+ ret.pushKV (PurposeToString (purpose).data (), arr);
382426 }
383427 return ret;
384428}
385429
386430std::string ExtNetInfo::ToString () const
387431{
388- return IsEmpty ()
389- ? " ExtNetInfo()"
390- : strprintf (" ExtNetInfo([%s])" , Join (m_data, " , " , [](const auto & entry) { return entry.ToString (); }));
432+ return IsEmpty () ? " ExtNetInfo()" : strprintf (" ExtNetInfo(%s)" , [&]() -> std::string {
433+ std::string ret{};
434+ bool first{true };
435+ for (const auto & [purpose, entries] : m_data) {
436+ if (!first) { ret += " , " ; } else { first = false ; }
437+ ret += strprintf (" NetInfo(purpose=%s, [%s])" , PurposeToString (purpose), [&]() -> std::string {
438+ if (entries.empty ()) {
439+ return " invalid list" ;
440+ }
441+ return Join (entries, " , " , [](const auto & entry) { return entry.ToString (); });
442+ }());
443+ }
444+ return ret;
445+ }());
391446}
0 commit comments