Skip to content

Commit 4ca6542

Browse files
committed
evo: introduce the ability to store multiple lists of addresses
1 parent a35d9c6 commit 4ca6542

14 files changed

+234
-101
lines changed

contrib/seeds/makeseeds.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ def main():
164164
mns = filtermulticollateraladdress(mns)
165165
mns = filtermultipayoutaddress(mns)
166166
# Extract IPs
167-
ips = [parseip(mn['state']['addresses'][0]) for mn in mns]
167+
ips = [parseip(mn['state']['addresses']['core_p2p'][0]) for mn in mns]
168168
for onion in onions:
169169
parsed = parseip(onion)
170170
if parsed is not None:

src/evo/deterministicmns.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -895,6 +895,9 @@ void CDeterministicMNManager::CleanupCache(int nHeight)
895895
template <typename ProTx>
896896
static bool CheckService(const ProTx& proTx, TxValidationState& state)
897897
{
898+
if (!proTx.netInfo->HasEntries(NetInfoPurpose::CORE_P2P)) {
899+
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-empty");
900+
}
898901
switch (proTx.netInfo->Validate()) {
899902
case NetInfoStatus::BadAddress:
900903
return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-addr");

src/evo/netinfo.cpp

Lines changed: 91 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -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

249249
UniValue 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

257258
std::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

262265
bool 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

274277
bool 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

336353
NetInfoList 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

341360
CService 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+
351382
NetInfoStatus 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

377417
UniValue 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

386430
std::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
}

src/evo/netinfo.h

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class CService;
1515

1616
class UniValue;
1717

18-
/** Maximum entries that can be stored in an ExtNetInfo */
18+
/** Maximum entries that can be stored in an ExtNetInfo per purpose code */
1919
static constexpr uint8_t MAX_ENTRIES_EXTNETINFO{4};
2020

2121
enum class NetInfoStatus : uint8_t {
@@ -59,6 +59,34 @@ constexpr std::string_view NISToString(const NetInfoStatus code)
5959
assert(false);
6060
}
6161

62+
// A purpose corresponds to the index position in the ExtNetInfo map, entries must
63+
// be contiguous and cannot be changed once set without a format version update
64+
enum class NetInfoPurpose : uint8_t {
65+
// Mandatory for masternodes
66+
CORE_P2P = 0,
67+
};
68+
69+
template<> struct is_serializable_enum<NetInfoPurpose> : std::true_type {};
70+
71+
constexpr bool IsValidPurpose(const NetInfoPurpose purpose)
72+
{
73+
switch (purpose) {
74+
case NetInfoPurpose::CORE_P2P:
75+
return true;
76+
} // no default case, so the compiler can warn about missing cases
77+
return false;
78+
}
79+
80+
// Warning: Used in RPC code, altering existing values is a breaking change
81+
constexpr std::string_view PurposeToString(const NetInfoPurpose purpose)
82+
{
83+
switch (purpose) {
84+
case NetInfoPurpose::CORE_P2P:
85+
return "core_p2p";
86+
} // no default case, so the compiler can warn about missing cases
87+
return "";
88+
}
89+
6290
/* Identical to IsDeprecatedRPCEnabled("service"). For use outside of RPC code. */
6391
bool IsServiceDeprecatedRPCEnabled();
6492

@@ -145,11 +173,12 @@ class NetInfoInterface
145173
public:
146174
virtual ~NetInfoInterface() = default;
147175

148-
virtual NetInfoStatus AddEntry(const std::string& service) = 0;
176+
virtual NetInfoStatus AddEntry(const NetInfoPurpose purpose, const std::string& service) = 0;
149177
virtual NetInfoList GetEntries() const = 0;
150178

151179
virtual CService GetPrimary() const = 0;
152180
virtual bool CanStorePlatform() const = 0;
181+
virtual bool HasEntries(NetInfoPurpose purpose) const = 0;
153182
virtual bool IsEmpty() const = 0;
154183
virtual NetInfoStatus Validate() const = 0;
155184
virtual UniValue ToJson() const = 0;
@@ -202,10 +231,11 @@ class MnNetInfo final : public NetInfoInterface
202231
m_addr = NetInfoEntry{service};
203232
}
204233

205-
NetInfoStatus AddEntry(const std::string& service) override;
234+
NetInfoStatus AddEntry(const NetInfoPurpose purpose, const std::string& service) override;
206235
NetInfoList GetEntries() const override;
207236

208237
CService GetPrimary() const override;
238+
bool HasEntries(NetInfoPurpose purpose) const override { return purpose == NetInfoPurpose::CORE_P2P && !IsEmpty(); }
209239
bool IsEmpty() const override { return m_addr.IsEmpty(); }
210240
bool CanStorePlatform() const override { return false; }
211241
NetInfoStatus Validate() const override;
@@ -238,14 +268,17 @@ class ExtNetInfo final : public NetInfoInterface
238268
bool IsDuplicateCandidate(const NetInfoEntry& candidate) const;
239269

240270
/** Validate uniqueness requirements and add to object if passed */
241-
NetInfoStatus ProcessCandidate(const NetInfoEntry& candidate);
271+
NetInfoStatus ProcessCandidate(const NetInfoPurpose purpose, const NetInfoEntry& candidate);
242272

243273
/** Validate CService candidate address against ruleset */
244274
static NetInfoStatus ValidateService(const CService& service);
245275

246276
private:
247277
uint8_t m_version{CURRENT_VERSION};
248-
NetInfoList m_data{};
278+
std::map<NetInfoPurpose, NetInfoList> m_data{};
279+
280+
// memory only
281+
NetInfoList m_all_entries{};
249282

250283
public:
251284
ExtNetInfo() = default;
@@ -254,19 +287,37 @@ class ExtNetInfo final : public NetInfoInterface
254287

255288
~ExtNetInfo() = default;
256289

257-
SERIALIZE_METHODS(ExtNetInfo, obj)
290+
template <typename Stream>
291+
void Serialize(Stream& s) const
292+
{
293+
s << m_version;
294+
if (m_version == 0 || m_version > CURRENT_VERSION) {
295+
return; // Don't bother with unknown versions
296+
}
297+
s << m_data;
298+
}
299+
300+
template <typename Stream>
301+
void Unserialize(Stream& s)
258302
{
259-
READWRITE(obj.m_version);
260-
if (obj.m_version == 0 || obj.m_version > CURRENT_VERSION) {
303+
s >> m_version;
304+
if (m_version == 0 || m_version > CURRENT_VERSION) {
261305
return; // Don't bother with unknown versions
262306
}
263-
READWRITE(obj.m_data);
307+
s >> m_data;
308+
309+
// Regenerate internal cache
310+
m_all_entries.clear();
311+
for (const auto& [_, entries] : m_data) {
312+
m_all_entries.insert(m_all_entries.end(), entries.begin(), entries.end());
313+
}
264314
}
265315

266-
NetInfoStatus AddEntry(const std::string& input) override;
316+
NetInfoStatus AddEntry(const NetInfoPurpose purpose, const std::string& input) override;
267317
NetInfoList GetEntries() const override;
268318

269319
CService GetPrimary() const override;
320+
bool HasEntries(NetInfoPurpose purpose) const override;
270321
bool IsEmpty() const override { return m_version == CURRENT_VERSION && m_data.empty(); }
271322
bool CanStorePlatform() const override
272323
{
@@ -282,6 +333,7 @@ class ExtNetInfo final : public NetInfoInterface
282333
{
283334
m_version = CURRENT_VERSION;
284335
m_data.clear();
336+
m_all_entries.clear();
285337
}
286338

287339
private:

0 commit comments

Comments
 (0)