rippled
Loading...
Searching...
No Matches
ValidatorList.cpp
1#include <xrpld/app/misc/HashRouter.h>
2#include <xrpld/app/misc/NetworkOPs.h>
3#include <xrpld/app/misc/ValidatorList.h>
4#include <xrpld/overlay/Overlay.h>
5
6#include <xrpl/basics/FileUtilities.h>
7#include <xrpl/basics/Slice.h>
8#include <xrpl/basics/StringUtilities.h>
9#include <xrpl/basics/base64.h>
10#include <xrpl/json/json_reader.h>
11#include <xrpl/protocol/PublicKey.h>
12#include <xrpl/protocol/STValidation.h>
13#include <xrpl/protocol/digest.h>
14#include <xrpl/protocol/jss.h>
15#include <xrpl/protocol/messages.h>
16
17#include <boost/regex.hpp>
18
19#include <cmath>
20#include <numeric>
21#include <shared_mutex>
22
23namespace xrpl {
24
27{
28 switch (disposition)
29 {
31 return "accepted";
33 return "expired";
35 return "same_sequence";
37 return "pending";
39 return "known_sequence";
41 return "unsupported_version";
43 return "untrusted";
45 return "stale";
47 return "invalid";
48 }
49 return "unknown";
50}
51
56
59 PublicKey key,
60 PublisherStatus stat,
61 std::size_t seq)
62 : publisherKey(key), status(stat), sequence(seq)
63{
64 ++dispositions[d];
65}
66
69{
70 return dispositions.empty() ? ListDisposition::invalid : dispositions.begin()->first;
71}
72
75{
76 return dispositions.empty() ? ListDisposition::invalid : dispositions.rbegin()->first;
77}
78
79void
81{
82 for (auto const& [disp, count] : src.dispositions)
83 {
84 dispositions[disp] += count;
85 }
86}
87
89 std::shared_ptr<Message> const& message_,
90 uint256 hash_,
91 std::size_t num_)
92 : message(message_), hash(hash_), numVLs(num_)
93{
94}
95
97
99 ManifestCache& validatorManifests,
100 ManifestCache& publisherManifests,
101 TimeKeeper& timeKeeper,
102 std::string const& databasePath,
104 std::optional<std::size_t> minimumQuorum)
105 : validatorManifests_(validatorManifests)
106 , publisherManifests_(publisherManifests)
107 , timeKeeper_(timeKeeper)
108 , dataPath_(databasePath)
109 , j_(j)
110 , quorum_(minimumQuorum.value_or(1)) // Genesis ledger quorum
111 , minimumQuorum_(minimumQuorum)
112 , listThreshold_(1)
113{
114}
115
116bool
118 std::optional<PublicKey> const& localSigningKey,
119 std::vector<std::string> const& configKeys,
120 std::vector<std::string> const& publisherKeys,
121 std::optional<std::size_t> listThreshold)
122{
123 static boost::regex const re(
124 "[[:space:]]*" // skip leading whitespace
125 "([[:alnum:]]+)" // node identity
126 "(?:" // begin optional comment block
127 "[[:space:]]+" // (skip all leading whitespace)
128 "(?:" // begin optional comment
129 "(.*[^[:space:]]+)" // the comment
130 "[[:space:]]*" // (skip all trailing whitespace)
131 ")?" // end optional comment
132 ")?" // end optional comment block
133 );
134
136
137 JLOG(j_.debug()) << "Loading configured trusted validator list publisher keys";
138
139 std::size_t count = 0;
140 for (auto key : publisherKeys)
141 {
142 JLOG(j_.trace()) << "Processing '" << key << "'";
143
144 auto const ret = strUnHex(key);
145
146 if (!ret || !publicKeyType(makeSlice(*ret)))
147 {
148 JLOG(j_.error()) << "Invalid validator list publisher key: " << key;
149 return false;
150 }
151
152 auto id = PublicKey(makeSlice(*ret));
153 auto status = PublisherStatus::unavailable;
154
156 {
157 JLOG(j_.warn()) << "Configured validator list publisher key is revoked: " << key;
159 }
160
161 if (publisherLists_.count(id))
162 {
163 JLOG(j_.warn()) << "Duplicate validator list publisher key: " << key;
164 continue;
165 }
166
167 publisherLists_[id].status = status;
168 ++count;
169 }
170
171 if (listThreshold)
172 {
173 listThreshold_ = *listThreshold;
174 // This should be enforced by Config class
175 XRPL_ASSERT(
177 "xrpl::ValidatorList::load : list threshold inside range");
178 JLOG(j_.debug()) << "Validator list threshold set in configuration to " << listThreshold_;
179 }
180 else
181 {
182 // Want truncated result when dividing an odd integer
183 listThreshold_ = (publisherLists_.size() < 3) ? 1 //
184 : publisherLists_.size() / 2 + 1;
185 JLOG(j_.debug()) << "Validator list threshold computed as " << listThreshold_;
186 }
187
188 JLOG(j_.debug()) << "Loaded " << count << " keys";
189
190 if (localSigningKey)
192
193 // Treat local validator key as though it was listed in the config
194 if (localPubKey_)
195 {
196 // The local validator must meet listThreshold_ so the validator does
197 // not ignore itself.
198 auto const [_, inserted] = keyListings_.insert({*localPubKey_, listThreshold_});
199 if (inserted)
200 {
201 JLOG(j_.debug()) << "Added own master key " << toBase58(TokenType::NodePublic, *localPubKey_);
202 }
203 }
204
205 JLOG(j_.debug()) << "Loading configured validator keys";
206
207 count = 0;
208 for (auto const& n : configKeys)
209 {
210 JLOG(j_.trace()) << "Processing '" << n << "'";
211
212 boost::smatch match;
213
214 if (!boost::regex_match(n, match, re))
215 {
216 JLOG(j_.error()) << "Malformed entry: '" << n << "'";
217 return false;
218 }
219
220 auto const id = parseBase58<PublicKey>(TokenType::NodePublic, match[1].str());
221
222 if (!id)
223 {
224 JLOG(j_.error()) << "Invalid node identity: " << match[1];
225 return false;
226 }
227
228 // Skip local key which was already added
229 if (*id == localPubKey_ || *id == localSigningKey)
230 continue;
231
232 auto ret = keyListings_.insert({*id, listThreshold_});
233 if (!ret.second)
234 {
235 JLOG(j_.warn()) << "Duplicate node identity: " << match[1];
236 continue;
237 }
238 localPublisherList.list.emplace_back(*id);
239 ++count;
240 }
241
242 // Config listed keys never expire
243 // set the expiration time for the newly created publisher list
244 // exactly once
245 if (count > 0)
246 localPublisherList.validUntil = TimeKeeper::time_point::max();
247
248 JLOG(j_.debug()) << "Loaded " << count << " entries";
249
250 return true;
251}
252
253boost::filesystem::path
255{
256 return dataPath_ / (filePrefix_ + strHex(pubKey));
257}
258
259// static
262 std::string const& pubKey,
263 ValidatorList::PublisherListCollection const& pubCollection,
265{
266 return buildFileData(pubKey, pubCollection, {}, j);
267}
268
269// static
272 std::string const& pubKey,
273 ValidatorList::PublisherListCollection const& pubCollection,
274 std::optional<std::uint32_t> forceVersion,
276{
278
279 XRPL_ASSERT(
280 pubCollection.rawVersion == 2 || pubCollection.remaining.empty(),
281 "xrpl::ValidatorList::buildFileData : valid publisher list input");
282 auto const effectiveVersion = forceVersion ? *forceVersion : pubCollection.rawVersion;
283
284 value[jss::manifest] = pubCollection.rawManifest;
285 value[jss::version] = effectiveVersion;
286 value[jss::public_key] = pubKey;
287
288 switch (effectiveVersion)
289 {
290 case 1: {
291 auto const& current = pubCollection.current;
292 value[jss::blob] = current.rawBlob;
293 value[jss::signature] = current.rawSignature;
294 // This is only possible if "downgrading" a v2 UNL to v1, for
295 // example for the /vl/ endpoint.
296 if (current.rawManifest && *current.rawManifest != pubCollection.rawManifest)
297 value[jss::manifest] = *current.rawManifest;
298 break;
299 }
300 case 2: {
302
303 auto add = [&blobs, &outerManifest = pubCollection.rawManifest](PublisherList const& pubList) {
304 auto& blob = blobs.append(Json::objectValue);
305 blob[jss::blob] = pubList.rawBlob;
306 blob[jss::signature] = pubList.rawSignature;
307 if (pubList.rawManifest && *pubList.rawManifest != outerManifest)
308 blob[jss::manifest] = *pubList.rawManifest;
309 };
310
311 add(pubCollection.current);
312 for (auto const& [_, pending] : pubCollection.remaining)
313 {
314 (void)_;
315 add(pending);
316 }
317
318 value[jss::blobs_v2] = std::move(blobs);
319 break;
320 }
321 default:
322 JLOG(j.trace()) << "Invalid VL version provided: " << effectiveVersion;
323 value = Json::nullValue;
324 }
325
326 return value;
327}
328
329void
331{
332 if (dataPath_.empty())
333 return;
334
335 boost::filesystem::path const filename = getCacheFileName(lock, pubKey);
336
337 boost::system::error_code ec;
338
339 Json::Value value = buildFileData(strHex(pubKey), publisherLists_.at(pubKey), j_);
340 // rippled should be the only process writing to this file, so
341 // if it ever needs to be read, it is not expected to change externally, so
342 // delay the refresh as long as possible: 24 hours. (See also
343 // `ValidatorSite::missingSite()`)
344 value[jss::refresh_interval] = 24 * 60;
345
346 writeFileContents(ec, filename, value.toStyledString());
347
348 if (ec)
349 {
350 // Log and ignore any file I/O exceptions
351 JLOG(j_.error()) << "Problem writing " << filename << " " << ec.value() << ": " << ec.message();
352 }
353}
354
355// static
358{
360 switch (version)
361 {
362 case 1: {
363 if (!body.isMember(jss::blob) || !body[jss::blob].isString() || !body.isMember(jss::signature) ||
364 !body[jss::signature].isString() ||
365 // If the v2 field is present, the VL is malformed
366 body.isMember(jss::blobs_v2))
367 return {};
368 ValidatorBlobInfo& info = result.emplace_back();
369 info.blob = body[jss::blob].asString();
370 info.signature = body[jss::signature].asString();
371 XRPL_ASSERT(result.size() == 1, "xrpl::ValidatorList::parseBlobs : single element result");
372 return result;
373 }
374 // Treat unknown versions as if they're the latest version. This
375 // will likely break a bunch of unit tests each time we introduce a
376 // new version, so don't do it casually. Note that the version is
377 // validated elsewhere.
378 case 2:
379 default: {
380 if (!body.isMember(jss::blobs_v2) || !body[jss::blobs_v2].isArray() ||
381 body[jss::blobs_v2].size() > maxSupportedBlobs ||
382 // If any of the v1 fields are present, the VL is malformed
383 body.isMember(jss::blob) || body.isMember(jss::signature))
384 return {};
385 auto const& blobs = body[jss::blobs_v2];
386 result.reserve(blobs.size());
387 for (auto const& blobInfo : blobs)
388 {
389 if (!blobInfo.isObject() || !blobInfo.isMember(jss::signature) ||
390 !blobInfo[jss::signature].isString() || !blobInfo.isMember(jss::blob) ||
391 !blobInfo[jss::blob].isString())
392 return {};
393 ValidatorBlobInfo& info = result.emplace_back();
394 info.blob = blobInfo[jss::blob].asString();
395 info.signature = blobInfo[jss::signature].asString();
396 if (blobInfo.isMember(jss::manifest))
397 {
398 if (!blobInfo[jss::manifest].isString())
399 return {};
400 info.manifest = blobInfo[jss::manifest].asString();
401 }
402 }
403 XRPL_ASSERT(
404 result.size() == blobs.size(),
405 "xrpl::ValidatorList::parseBlobs(version, Jason::Value) : "
406 "result size matches");
407 return result;
408 }
409 }
410}
411
412// static
414ValidatorList::parseBlobs(protocol::TMValidatorList const& body)
415{
416 return {{body.blob(), body.signature(), {}}};
417}
418
419// static
421ValidatorList::parseBlobs(protocol::TMValidatorListCollection const& body)
422{
423 if (body.blobs_size() > maxSupportedBlobs)
424 return {};
426 result.reserve(body.blobs_size());
427 for (auto const& blob : body.blobs())
428 {
429 ValidatorBlobInfo& info = result.emplace_back();
430 info.blob = blob.blob();
431 info.signature = blob.signature();
432 if (blob.has_manifest())
433 {
434 info.manifest = blob.manifest();
435 }
436 }
437 XRPL_ASSERT(
438 result.size() == body.blobs_size(),
439 "xrpl::ValidatorList::parseBlobs(TMValidatorList) : result size "
440 "match");
441 return result;
442}
443
447 protocol::TMValidatorListCollection const& largeMsg,
448 std::size_t maxSize,
449 std::size_t begin,
450 std::size_t end);
451
455 protocol::TMValidatorListCollection const& largeMsg,
456 std::size_t maxSize,
457 std::size_t begin = 0,
458 std::size_t end = 0)
459{
460 if (begin == 0 && end == 0)
461 end = largeMsg.blobs_size();
462 XRPL_ASSERT(begin < end, "xrpl::splitMessage : valid inputs");
463 if (end <= begin)
464 return 0;
465
466 auto mid = (begin + end) / 2;
467 // The parts function will do range checking
468 // Use two separate calls to ensure deterministic order
469 auto result = splitMessageParts(messages, largeMsg, maxSize, begin, mid);
470 return result + splitMessageParts(messages, largeMsg, maxSize, mid, end);
471}
472
476 protocol::TMValidatorListCollection const& largeMsg,
477 std::size_t maxSize,
478 std::size_t begin,
479 std::size_t end)
480{
481 if (end <= begin)
482 return 0;
483 if (end - begin == 1)
484 {
485 protocol::TMValidatorList smallMsg;
486 smallMsg.set_version(1);
487 smallMsg.set_manifest(largeMsg.manifest());
488
489 auto const& blob = largeMsg.blobs(begin);
490 smallMsg.set_blob(blob.blob());
491 smallMsg.set_signature(blob.signature());
492 // This is only possible if "downgrading" a v2 UNL to v1.
493 if (blob.has_manifest())
494 smallMsg.set_manifest(blob.manifest());
495
496 XRPL_ASSERT(
497 Message::totalSize(smallMsg) <= maximumMessageSize, "xrpl::splitMessageParts : maximum message size");
498
499 messages.emplace_back(std::make_shared<Message>(smallMsg, protocol::mtVALIDATOR_LIST), sha512Half(smallMsg), 1);
500 return messages.back().numVLs;
501 }
502 else
503 {
505 smallMsg.emplace();
506 smallMsg->set_version(largeMsg.version());
507 smallMsg->set_manifest(largeMsg.manifest());
508
509 for (std::size_t i = begin; i < end; ++i)
510 {
511 *smallMsg->add_blobs() = largeMsg.blobs(i);
512 }
513
514 if (Message::totalSize(*smallMsg) > maxSize)
515 {
516 // free up the message space
517 smallMsg.reset();
518 return splitMessage(messages, largeMsg, maxSize, begin, end);
519 }
520 else
521 {
522 messages.emplace_back(
523 std::make_shared<Message>(*smallMsg, protocol::mtVALIDATOR_LIST_COLLECTION),
524 sha512Half(*smallMsg),
525 smallMsg->blobs_size());
526 return messages.back().numVLs;
527 }
528 }
529 return 0;
530}
531
532// Build a v1 protocol message using only the current VL
536 std::uint32_t rawVersion,
537 std::string const& rawManifest,
538 ValidatorBlobInfo const& currentBlob,
539 std::size_t maxSize)
540{
541 XRPL_ASSERT(
542 messages.empty(),
543 "xrpl::buildValidatorListMessage(ValidatorBlobInfo) : empty messages "
544 "input");
545 protocol::TMValidatorList msg;
546 auto const manifest = currentBlob.manifest ? *currentBlob.manifest : rawManifest;
547 auto const version = 1;
548 msg.set_manifest(manifest);
549 msg.set_blob(currentBlob.blob);
550 msg.set_signature(currentBlob.signature);
551 // Override the version
552 msg.set_version(version);
553
554 XRPL_ASSERT(
556 "xrpl::buildValidatorListMessage(ValidatorBlobInfo) : maximum "
557 "message size");
558 messages.emplace_back(std::make_shared<Message>(msg, protocol::mtVALIDATOR_LIST), sha512Half(msg), 1);
559 return 1;
560}
561
562// Build a v2 protocol message using all the VLs with sequence larger than the
563// peer's
567 std::uint64_t peerSequence,
568 std::uint32_t rawVersion,
569 std::string const& rawManifest,
571 std::size_t maxSize)
572{
573 XRPL_ASSERT(
574 messages.empty(),
575 "xrpl::buildValidatorListMessage(std::map<std::size_t, "
576 "ValidatorBlobInfo>) : empty messages input");
577 protocol::TMValidatorListCollection msg;
578 auto const version = rawVersion < 2 ? 2 : rawVersion;
579 msg.set_version(version);
580 msg.set_manifest(rawManifest);
581
582 for (auto const& [sequence, blobInfo] : blobInfos)
583 {
584 if (sequence <= peerSequence)
585 continue;
586 protocol::ValidatorBlobInfo& blob = *msg.add_blobs();
587 blob.set_blob(blobInfo.blob);
588 blob.set_signature(blobInfo.signature);
589 if (blobInfo.manifest)
590 blob.set_manifest(*blobInfo.manifest);
591 }
592 XRPL_ASSERT(
593 msg.blobs_size() > 0,
594 "xrpl::buildValidatorListMessage(std::map<std::size_t, "
595 "ValidatorBlobInfo>) : minimum message blobs");
596 if (Message::totalSize(msg) > maxSize)
597 {
598 // split into smaller messages
599 return splitMessage(messages, msg, maxSize);
600 }
601 else
602 {
603 messages.emplace_back(
604 std::make_shared<Message>(msg, protocol::mtVALIDATOR_LIST_COLLECTION), sha512Half(msg), msg.blobs_size());
605 return messages.back().numVLs;
606 }
607}
608
609[[nodiscard]]
610// static
613 std::size_t messageVersion,
614 std::uint64_t peerSequence,
615 std::size_t maxSequence,
616 std::uint32_t rawVersion,
617 std::string const& rawManifest,
620 std::size_t maxSize /*= maximumMessageSize*/)
621{
622 XRPL_ASSERT(
623 !blobInfos.empty(),
624 "xrpl::ValidatorList::buildValidatorListMessages : empty messages "
625 "input");
626 auto const& [currentSeq, currentBlob] = *blobInfos.begin();
627 auto numVLs = std::accumulate(messages.begin(), messages.end(), 0, [](std::size_t total, MessageWithHash const& m) {
628 return total + m.numVLs;
629 });
630 if (messageVersion == 2 && peerSequence < maxSequence)
631 {
632 // Version 2
633 if (messages.empty())
634 {
635 numVLs = buildValidatorListMessage(messages, peerSequence, rawVersion, rawManifest, blobInfos, maxSize);
636 if (messages.empty())
637 // No message was generated. Create an empty placeholder so we
638 // dont' repeat the work later.
639 messages.emplace_back();
640 }
641
642 // Don't send it next time.
643 return {maxSequence, numVLs};
644 }
645 else if (messageVersion == 1 && peerSequence < currentSeq)
646 {
647 // Version 1
648 if (messages.empty())
649 {
651 messages, rawVersion, currentBlob.manifest ? *currentBlob.manifest : rawManifest, currentBlob, maxSize);
652 if (messages.empty())
653 // No message was generated. Create an empty placeholder so we
654 // dont' repeat the work later.
655 messages.emplace_back();
656 }
657
658 // Don't send it next time.
659 return {currentSeq, numVLs};
660 }
661 return {0, 0};
662}
663
664// static
665void
667 Peer& peer,
668 std::uint64_t peerSequence,
669 PublicKey const& publisherKey,
670 std::size_t maxSequence,
671 std::uint32_t rawVersion,
672 std::string const& rawManifest,
675 HashRouter& hashRouter,
677{
680 : 0;
681 if (!messageVersion)
682 return;
683 auto const [newPeerSequence, numVLs] = buildValidatorListMessages(
684 messageVersion, peerSequence, maxSequence, rawVersion, rawManifest, blobInfos, messages);
685 if (newPeerSequence)
686 {
687 XRPL_ASSERT(
688 !messages.empty(),
689 "xrpl::ValidatorList::sendValidatorList : non-empty messages "
690 "input");
691 // Don't send it next time.
692 peer.setPublisherListSequence(publisherKey, newPeerSequence);
693
694 bool sent = false;
695 for (auto const& message : messages)
696 {
697 if (message.message)
698 {
699 peer.send(message.message);
700 hashRouter.addSuppressionPeer(message.hash, peer.id());
701 sent = true;
702 }
703 }
704 // The only way sent wil be false is if the messages was too big, and
705 // thus there will only be one entry without a message
706 XRPL_ASSERT(sent || messages.size() == 1, "xrpl::ValidatorList::sendValidatorList : sent or one message");
707 if (sent)
708 {
709 if (messageVersion > 1)
710 JLOG(j.debug()) << "Sent " << messages.size() << " validator list collection(s) containing " << numVLs
711 << " validator list(s) for " << strHex(publisherKey) << " with sequence range "
712 << peerSequence << ", " << newPeerSequence << " to " << peer.fingerprint();
713 else
714 {
715 XRPL_ASSERT(
716 numVLs == 1,
717 "xrpl::ValidatorList::sendValidatorList : one validator "
718 "list");
719 JLOG(j.debug()) << "Sent validator list for " << strHex(publisherKey) << " with sequence "
720 << newPeerSequence << " to " << peer.fingerprint();
721 }
722 }
723 }
724}
725
726// static
727void
729 Peer& peer,
730 std::uint64_t peerSequence,
731 PublicKey const& publisherKey,
732 std::size_t maxSequence,
733 std::uint32_t rawVersion,
734 std::string const& rawManifest,
736 HashRouter& hashRouter,
738{
741 peer, peerSequence, publisherKey, maxSequence, rawVersion, rawManifest, blobInfos, messages, hashRouter, j);
742}
743
744// static
745void
749{
750 auto const& current = lists.current;
751 auto const& remaining = lists.remaining;
752 blobInfos[current.sequence] = {current.rawBlob, current.rawSignature, current.rawManifest};
753 for (auto const& [sequence, vl] : remaining)
754 {
755 blobInfos[sequence] = {vl.rawBlob, vl.rawSignature, vl.rawManifest};
756 }
757}
758
759// static
767
768// static
769void
771 PublicKey const& publisherKey,
773 std::size_t maxSequence,
774 uint256 const& hash,
775 Overlay& overlay,
776 HashRouter& hashRouter,
778{
779 auto const toSkip = hashRouter.shouldRelay(hash);
780
781 if (toSkip)
782 {
783 // We don't know what messages or message versions we're sending
784 // until we examine our peer's properties. Build the message(s) on
785 // demand, but reuse them when possible.
786
787 // This will hold a v1 message with only the current VL if we have
788 // any peers that don't support v2
790 // This will hold v2 messages indexed by the peer's
791 // `publisherListSequence`. For each `publisherListSequence`, we'll
792 // only send the VLs with higher sequences.
794 // If any peers are found that are worth considering, this list will
795 // be built to hold info for all of the valid VLs.
797
798 XRPL_ASSERT(
799 lists.current.sequence == maxSequence || lists.remaining.count(maxSequence) == 1,
800 "xrpl::ValidatorList::broadcastBlobs : valid sequence");
801 // Can't use overlay.foreach here because we need to modify
802 // the peer, and foreach provides a const&
803 for (auto& peer : overlay.getActivePeers())
804 {
805 if (toSkip->count(peer->id()) == 0)
806 {
807 auto const peerSequence = peer->publisherListSequence(publisherKey).value_or(0);
808 if (peerSequence < maxSequence)
809 {
810 if (blobInfos.empty())
811 buildBlobInfos(blobInfos, lists);
812 auto const v2 = peer->supportsFeature(ProtocolFeature::ValidatorList2Propagation);
814 *peer,
815 peerSequence,
816 publisherKey,
817 maxSequence,
818 lists.rawVersion,
819 lists.rawManifest,
820 blobInfos,
821 v2 ? messages2[peerSequence] : messages1,
822 hashRouter,
823 j);
824 // Even if the peer doesn't support the messages,
825 // suppress it so it'll be ignored next time.
826 hashRouter.addSuppressionPeer(hash, peer->id());
827 }
828 }
829 }
830 }
831}
832
835 std::string const& manifest,
836 std::uint32_t version,
838 std::string siteUri,
839 uint256 const& hash,
840 Overlay& overlay,
841 HashRouter& hashRouter,
842 NetworkOPs& networkOPs)
843{
844 auto const result = applyLists(manifest, version, blobs, std::move(siteUri), hash);
845 auto const disposition = result.bestDisposition();
846
847 if (disposition == ListDisposition::accepted)
848 {
849 bool good = true;
850
851 // localPublisherList never expires, so localPublisherList is excluded
852 // from the below check.
853 for (auto const& [_, listCollection] : publisherLists_)
854 {
855 if (listCollection.status != PublisherStatus::available)
856 {
857 good = false;
858 break;
859 }
860 }
861 if (good)
862 {
863 networkOPs.clearUNLBlocked();
864 }
865 }
866 bool broadcast = disposition <= ListDisposition::known_sequence;
867
868 // this function is only called for PublicKeys which are not specified
869 // in the config file (Note: Keys specified in the local config file are
870 // stored in ValidatorList::localPublisherList data member).
871 if (broadcast && result.status <= PublisherStatus::expired && result.publisherKey &&
872 publisherLists_[*result.publisherKey].maxSequence)
873 {
874 auto const& pubCollection = publisherLists_[*result.publisherKey];
875
876 broadcastBlobs(*result.publisherKey, pubCollection, *pubCollection.maxSequence, hash, overlay, hashRouter, j_);
877 }
878
879 return result;
880}
881
884 std::string const& manifest,
885 std::uint32_t version,
887 std::string siteUri,
888 std::optional<uint256> const& hash /* = {} */)
889{
892
894
895 PublisherListStats result;
896 for (auto const& blobInfo : blobs)
897 {
898 auto stats =
899 applyList(manifest, blobInfo.manifest, blobInfo.blob, blobInfo.signature, version, siteUri, hash, lock);
900
901 if (stats.bestDisposition() < result.bestDisposition() ||
902 (stats.bestDisposition() == result.bestDisposition() && stats.sequence > result.sequence))
903 {
904 stats.mergeDispositions(result);
905 result = std::move(stats);
906 }
907 else
908 result.mergeDispositions(stats);
910 }
911
912 // Clean up the collection, because some of the processing may have made it
913 // inconsistent
914 if (result.publisherKey && publisherLists_.count(*result.publisherKey))
915 {
916 auto& pubCollection = publisherLists_[*result.publisherKey];
917 auto& remaining = pubCollection.remaining;
918 auto const& current = pubCollection.current;
919 for (auto iter = remaining.begin(); iter != remaining.end();)
920 {
921 auto next = std::next(iter);
922 XRPL_ASSERT(
923 next == remaining.end() || next->first > iter->first,
924 "xrpl::ValidatorList::applyLists : next is valid");
925 if (iter->first <= current.sequence ||
926 (next != remaining.end() && next->second.validFrom <= iter->second.validFrom))
927 {
928 iter = remaining.erase(iter);
929 }
930 else
931 {
932 iter = next;
933 }
934 }
935
936 cacheValidatorFile(lock, *result.publisherKey);
937
938 pubCollection.fullHash = sha512Half(pubCollection);
939
940 result.sequence = *pubCollection.maxSequence;
941 }
942
943 return result;
944}
945
946void
948 PublicKey const& pubKey,
949 PublisherList const& current,
950 std::vector<PublicKey> const& oldList,
952{
953 // Update keyListings_ for added and removed keys
954 std::vector<PublicKey> const& publisherList = current.list;
955 std::vector<std::string> const& manifests = current.manifests;
956 auto iNew = publisherList.begin();
957 auto iOld = oldList.begin();
958 while (iNew != publisherList.end() || iOld != oldList.end())
959 {
960 if (iOld == oldList.end() || (iNew != publisherList.end() && *iNew < *iOld))
961 {
962 // Increment list count for added keys
963 ++keyListings_[*iNew];
964 ++iNew;
965 }
966 else if (iNew == publisherList.end() || (iOld != oldList.end() && *iOld < *iNew))
967 {
968 // Decrement list count for removed keys
969 if (keyListings_[*iOld] <= 1)
970 keyListings_.erase(*iOld);
971 else
972 --keyListings_[*iOld];
973 ++iOld;
974 }
975 else
976 {
977 ++iNew;
978 ++iOld;
979 }
980 }
981
982 if (publisherList.empty())
983 {
984 JLOG(j_.warn()) << "No validator keys included in valid list";
985 }
986
987 for (auto const& valManifest : manifests)
988 {
989 auto m = deserializeManifest(base64_decode(valManifest));
990
991 if (!m || !keyListings_.count(m->masterKey))
992 {
993 JLOG(j_.warn()) << "List for " << strHex(pubKey) << " contained untrusted validator manifest";
994 continue;
995 }
996
997 if (auto const r = validatorManifests_.applyManifest(std::move(*m)); r == ManifestDisposition::invalid)
998 {
999 JLOG(j_.warn()) << "List for " << strHex(pubKey) << " contained invalid validator manifest";
1000 }
1001 }
1002}
1003
1006 std::string const& globalManifest,
1007 std::optional<std::string> const& localManifest,
1008 std::string const& blob,
1009 std::string const& signature,
1010 std::uint32_t version,
1011 std::string siteUri,
1012 std::optional<uint256> const& hash,
1013 ValidatorList::lock_guard const& lock)
1014{
1015 using namespace std::string_literals;
1016
1017 Json::Value list;
1018 auto const& manifest = localManifest ? *localManifest : globalManifest;
1020 if (!m)
1021 {
1022 JLOG(j_.warn()) << "UNL manifest cannot be deserialized";
1024 }
1025
1026 auto [result, pubKeyOpt] = verify(lock, list, std::move(*m), blob, signature);
1027
1028 if (!pubKeyOpt)
1029 {
1030 JLOG(j_.warn()) << "UNL manifest is signed with an unrecognized master public key";
1031 return PublisherListStats{result};
1032 }
1033
1034 if (!publicKeyType(*pubKeyOpt))
1035 {
1036 // This is an impossible situation because we will never load an
1037 // invalid public key type (see checks in `ValidatorList::load`) however
1038 // we can only arrive here if the key used by the manifest matched one
1039 // of the loaded keys
1040 // LCOV_EXCL_START
1041 UNREACHABLE("xrpl::ValidatorList::applyList : invalid public key type");
1042 return PublisherListStats{result};
1043 // LCOV_EXCL_STOP
1044 }
1045
1046 PublicKey pubKey = *pubKeyOpt;
1047 if (result > ListDisposition::pending)
1048 {
1049 if (publisherLists_.count(pubKey))
1050 {
1051 auto const& pubCollection = publisherLists_[pubKey];
1052 if (pubCollection.maxSequence &&
1054 {
1055 // We've seen something valid list for this publisher
1056 // already, so return what we know about it.
1057 return PublisherListStats{result, pubKey, pubCollection.status, *pubCollection.maxSequence};
1058 }
1059 }
1060 return PublisherListStats{result};
1061 }
1062
1063 // Update publisher's list
1064 auto& pubCollection = publisherLists_[pubKey];
1065 auto const sequence = list[jss::sequence].asUInt();
1066 auto const accepted = (result == ListDisposition::accepted || result == ListDisposition::expired);
1067
1068 if (accepted)
1069 pubCollection.status =
1071 pubCollection.rawManifest = globalManifest;
1072 if (!pubCollection.maxSequence || sequence > *pubCollection.maxSequence)
1073 pubCollection.maxSequence = sequence;
1074
1075 Json::Value const& newList = list[jss::validators];
1076 std::vector<PublicKey> oldList;
1077 if (accepted && pubCollection.remaining.count(sequence) != 0)
1078 {
1079 // We've seen this list before and stored it in "remaining". The
1080 // normal expected process is that the processed list would have
1081 // already been moved in to "current" by "updateTrusted()", but race
1082 // conditions are possible, or the node may have lost sync, so do
1083 // some of that work here.
1084 auto& publisher = pubCollection.current;
1085 // Copy the old validator list
1086 oldList = std::move(pubCollection.current.list);
1087 // Move the publisher info from "remaining" to "current"
1088 publisher = std::move(pubCollection.remaining[sequence]);
1089 // Remove the entry in "remaining"
1090 pubCollection.remaining.erase(sequence);
1091 // Done
1092 XRPL_ASSERT(publisher.sequence == sequence, "xrpl::ValidatorList::applyList : publisher sequence match");
1093 }
1094 else
1095 {
1096 auto& publisher = accepted ? pubCollection.current : pubCollection.remaining[sequence];
1097 publisher.sequence = sequence;
1098 publisher.validFrom = TimeKeeper::time_point{
1099 TimeKeeper::duration{list.isMember(jss::effective) ? list[jss::effective].asUInt() : 0}};
1100 publisher.validUntil = TimeKeeper::time_point{TimeKeeper::duration{list[jss::expiration].asUInt()}};
1101 publisher.siteUri = std::move(siteUri);
1102 publisher.rawBlob = blob;
1103 publisher.rawSignature = signature;
1104 publisher.rawManifest = localManifest;
1105 if (hash)
1106 publisher.hash = *hash;
1107
1108 std::vector<PublicKey>& publisherList = publisher.list;
1109 std::vector<std::string>& manifests = publisher.manifests;
1110
1111 // Copy the old validator list
1112 oldList = std::move(publisherList);
1113 // Build the new validator list from "newList"
1114 publisherList.clear();
1115 publisherList.reserve(newList.size());
1116 for (auto const& val : newList)
1117 {
1118 if (val.isObject() && val.isMember(jss::validation_public_key) &&
1119 val[jss::validation_public_key].isString())
1120 {
1121 std::optional<Blob> const ret = strUnHex(val[jss::validation_public_key].asString());
1122
1123 if (!ret || !publicKeyType(makeSlice(*ret)))
1124 {
1125 JLOG(j_.error()) << "Invalid node identity: " << val[jss::validation_public_key].asString();
1126 }
1127 else
1128 {
1129 publisherList.push_back(PublicKey(Slice{ret->data(), ret->size()}));
1130 }
1131
1132 if (val.isMember(jss::manifest) && val[jss::manifest].isString())
1133 manifests.push_back(val[jss::manifest].asString());
1134 }
1135 }
1136
1137 // Standardize the list order by sorting
1138 std::sort(publisherList.begin(), publisherList.end());
1139 }
1140 // If this publisher has ever sent a more updated version than the one
1141 // in this file, keep it. This scenario is unlikely, but legal.
1142 pubCollection.rawVersion = std::max(pubCollection.rawVersion, version);
1143 if (!pubCollection.remaining.empty())
1144 {
1145 // If there are any pending VLs, then this collection must be at least
1146 // version 2.
1147 pubCollection.rawVersion = std::max(pubCollection.rawVersion, 2u);
1148 }
1149
1150 PublisherListStats const applyResult{result, pubKey, pubCollection.status, *pubCollection.maxSequence};
1151
1152 if (accepted)
1153 {
1154 updatePublisherList(pubKey, pubCollection.current, oldList, lock);
1155 }
1156
1157 return applyResult;
1158}
1159
1162{
1163 using namespace std::string_literals;
1164 using namespace boost::filesystem;
1165 using namespace boost::system::errc;
1166
1167 std::lock_guard lock{mutex_};
1168
1170 sites.reserve(publisherLists_.size());
1171 for (auto const& [pubKey, publisherCollection] : publisherLists_)
1172 {
1173 boost::system::error_code ec;
1174
1175 if (publisherCollection.status == PublisherStatus::available)
1176 continue;
1177
1178 boost::filesystem::path const filename = getCacheFileName(lock, pubKey);
1179
1180 auto const fullPath{canonical(filename, ec)};
1181 if (ec)
1182 continue;
1183
1184 auto size = file_size(fullPath, ec);
1185 if (!ec && !size)
1186 {
1187 // Treat an empty file as a missing file, because
1188 // nobody else is going to write it.
1189 ec = make_error_code(no_such_file_or_directory);
1190 }
1191 if (ec)
1192 continue;
1193
1194 std::string const prefix = [&fullPath]() {
1195#if _MSC_VER // MSVC: Windows paths need a leading / added
1196 {
1197 return fullPath.root_path() == "/"s ? "file://" : "file:///";
1198 }
1199#else
1200 {
1201 (void)fullPath;
1202 return "file://";
1203 }
1204#endif
1205 }();
1206 sites.emplace_back(prefix + fullPath.string());
1207 }
1208
1209 // Then let the ValidatorSites do the rest of the work.
1210 return sites;
1211}
1212
1213// The returned PublicKey value is read from the manifest. Manifests do not
1214// contain the default-constructed public keys
1217 ValidatorList::lock_guard const& lock,
1218 Json::Value& list,
1220 std::string const& blob,
1221 std::string const& signature)
1222{
1223 if (!publisherLists_.count(manifest.masterKey))
1224 return {ListDisposition::untrusted, {}};
1225
1226 PublicKey masterPubKey = manifest.masterKey;
1227 auto const revoked = manifest.revoked();
1228
1229 auto const result = publisherManifests_.applyManifest(std::move(manifest));
1230
1231 if (revoked && result == ManifestDisposition::accepted)
1232 {
1233 removePublisherList(lock, masterPubKey, PublisherStatus::revoked);
1234 // If the manifest is revoked, no future list is valid either
1235 publisherLists_[masterPubKey].remaining.clear();
1236 }
1237
1238 auto const signingKey = publisherManifests_.getSigningKey(masterPubKey);
1239
1240 if (revoked || !signingKey || result == ManifestDisposition::invalid)
1241 return {ListDisposition::untrusted, masterPubKey};
1242
1243 auto const sig = strUnHex(signature);
1244 auto const data = base64_decode(blob);
1245 if (!sig || !xrpl::verify(*signingKey, makeSlice(data), makeSlice(*sig)))
1246 return {ListDisposition::invalid, masterPubKey};
1247
1248 Json::Reader r;
1249 if (!r.parse(data, list))
1250 return {ListDisposition::invalid, masterPubKey};
1251
1252 if (list.isMember(jss::sequence) && list[jss::sequence].isInt() && list.isMember(jss::expiration) &&
1253 list[jss::expiration].isInt() && (!list.isMember(jss::effective) || list[jss::effective].isInt()) &&
1254 list.isMember(jss::validators) && list[jss::validators].isArray())
1255 {
1256 auto const sequence = list[jss::sequence].asUInt();
1257 auto const validFrom = TimeKeeper::time_point{
1258 TimeKeeper::duration{list.isMember(jss::effective) ? list[jss::effective].asUInt() : 0}};
1259 auto const validUntil = TimeKeeper::time_point{TimeKeeper::duration{list[jss::expiration].asUInt()}};
1260 auto const now = timeKeeper_.now();
1261 auto const& listCollection = publisherLists_[masterPubKey];
1262 if (validUntil <= validFrom)
1263 return {ListDisposition::invalid, masterPubKey};
1264 else if (sequence < listCollection.current.sequence)
1265 return {ListDisposition::stale, masterPubKey};
1266 else if (sequence == listCollection.current.sequence)
1267 return {ListDisposition::same_sequence, masterPubKey};
1268 else if (validUntil <= now)
1269 return {ListDisposition::expired, masterPubKey};
1270 else if (validFrom > now)
1271 // Not yet valid. Return pending if one of the following is true
1272 // * There's no maxSequence, indicating this is the first blob seen
1273 // for this publisher
1274 // * The sequence is larger than the maxSequence, indicating this
1275 // blob is new
1276 // * There's no entry for this sequence AND this blob is valid
1277 // before the last blob, indicating blobs may be processing out of
1278 // order. This may result in some duplicated processing, but
1279 // prevents the risk of missing valid data. Else return
1280 // known_sequence
1281 return !listCollection.maxSequence || sequence > *listCollection.maxSequence ||
1282 (listCollection.remaining.count(sequence) == 0 &&
1283 validFrom < listCollection.remaining.at(*listCollection.maxSequence).validFrom)
1286 }
1287 else
1288 {
1289 return {ListDisposition::invalid, masterPubKey};
1290 }
1291
1292 return {ListDisposition::accepted, masterPubKey};
1293}
1294
1295bool
1296ValidatorList::listed(PublicKey const& identity) const
1297{
1298 std::shared_lock read_lock{mutex_};
1299
1300 auto const pubKey = validatorManifests_.getMasterKey(identity);
1301 return keyListings_.find(pubKey) != keyListings_.end();
1302}
1303
1304bool
1306{
1307 auto const pubKey = validatorManifests_.getMasterKey(identity);
1308 return trustedMasterKeys_.find(pubKey) != trustedMasterKeys_.end();
1309}
1310
1311bool
1312ValidatorList::trusted(PublicKey const& identity) const
1313{
1314 std::shared_lock read_lock{mutex_};
1315 return trusted(read_lock, identity);
1316}
1317
1320{
1321 std::shared_lock read_lock{mutex_};
1322
1323 auto const pubKey = validatorManifests_.getMasterKey(identity);
1324 if (keyListings_.find(pubKey) != keyListings_.end())
1325 return pubKey;
1326 return std::nullopt;
1327}
1328
1331{
1332 auto const pubKey = validatorManifests_.getMasterKey(identity);
1333 if (trustedMasterKeys_.find(pubKey) != trustedMasterKeys_.end())
1334 return pubKey;
1335 return std::nullopt;
1336}
1337
1340{
1341 std::shared_lock read_lock{mutex_};
1342
1343 return getTrustedKey(read_lock, identity);
1344}
1345
1346bool
1348{
1349 std::shared_lock read_lock{mutex_};
1350 return identity.size() && publisherLists_.count(identity) &&
1351 publisherLists_.at(identity).status < PublisherStatus::revoked;
1352}
1353
1356{
1357 std::shared_lock read_lock{mutex_};
1358 return localPubKey_;
1359}
1360
1361bool
1364 PublicKey const& publisherKey,
1365 PublisherStatus reason)
1366{
1367 XRPL_ASSERT(
1369 "xrpl::ValidatorList::removePublisherList : valid reason input");
1370 auto const iList = publisherLists_.find(publisherKey);
1371 if (iList == publisherLists_.end())
1372 return false;
1373
1374 JLOG(j_.debug()) << "Removing validator list for publisher " << strHex(publisherKey);
1375
1376 for (auto const& val : iList->second.current.list)
1377 {
1378 auto const& iVal = keyListings_.find(val);
1379 if (iVal == keyListings_.end())
1380 continue;
1381
1382 if (iVal->second <= 1)
1383 keyListings_.erase(iVal);
1384 else
1385 --iVal->second;
1386 }
1387
1388 iList->second.current.list.clear();
1389 iList->second.status = reason;
1390
1391 return true;
1392}
1393
1396{
1397 return publisherLists_.size() + (localPublisherList.list.size() > 0);
1398}
1399
1402{
1403 std::shared_lock read_lock{mutex_};
1404 return count(read_lock);
1405}
1406
1409{
1411 for (auto const& [_, collection] : publisherLists_)
1412 {
1413 // Unfetched
1414 auto const& current = collection.current;
1415 if (current.validUntil == TimeKeeper::time_point{})
1416 {
1417 return std::nullopt;
1418 }
1419
1420 // Find the latest validUntil in a chain where the next validFrom
1421 // overlaps with the previous validUntil. applyLists has already cleaned
1422 // up the list so the validFrom dates are guaranteed increasing.
1423 auto chainedExpiration = current.validUntil;
1424 for (auto const& [sequence, check] : collection.remaining)
1425 {
1426 (void)sequence;
1427 if (check.validFrom <= chainedExpiration)
1428 chainedExpiration = check.validUntil;
1429 else
1430 break;
1431 }
1432
1433 // Earliest
1434 if (!res || chainedExpiration < *res)
1435 {
1436 res = chainedExpiration;
1437 }
1438 }
1439
1440 if (localPublisherList.list.size() > 0)
1441 {
1442 PublisherList collection = localPublisherList;
1443 // Unfetched
1444 auto const& current = collection;
1445 auto chainedExpiration = current.validUntil;
1446
1447 // Earliest
1448 if (!res || chainedExpiration < *res)
1449 {
1450 res = chainedExpiration;
1451 }
1452 }
1453 return res;
1454}
1455
1457ValidatorList::expires() const
1458{
1459 std::shared_lock read_lock{mutex_};
1460 return expires(read_lock);
1461}
1462
1464ValidatorList::getJson() const
1465{
1467
1468 std::shared_lock read_lock{mutex_};
1469
1470 res[jss::validation_quorum] = static_cast<Json::UInt>(quorum_);
1471
1472 {
1473 auto& x = (res[jss::validator_list] = Json::objectValue);
1474
1475 x[jss::count] = static_cast<Json::UInt>(count(read_lock));
1476
1477 if (auto when = expires(read_lock))
1478 {
1479 if (*when == TimeKeeper::time_point::max())
1480 {
1481 x[jss::expiration] = "never";
1482 x[jss::status] = "active";
1483 }
1484 else
1485 {
1486 x[jss::expiration] = to_string(*when);
1487
1488 if (*when > timeKeeper_.now())
1489 x[jss::status] = "active";
1490 else
1491 x[jss::status] = "expired";
1492 }
1493 }
1494 else
1495 {
1496 x[jss::status] = "unknown";
1497 x[jss::expiration] = "unknown";
1498 }
1499
1500 x[jss::validator_list_threshold] = Json::UInt(listThreshold_);
1501 }
1502
1503 // Validator keys listed in the local config file
1504 Json::Value& jLocalStaticKeys = (res[jss::local_static_keys] = Json::arrayValue);
1505
1506 for (auto const& key : localPublisherList.list)
1507 jLocalStaticKeys.append(toBase58(TokenType::NodePublic, key));
1508
1509 // Publisher lists
1510 Json::Value& jPublisherLists = (res[jss::publisher_lists] = Json::arrayValue);
1511 for (auto const& [publicKey, pubCollection] : publisherLists_)
1512 {
1513 Json::Value& curr = jPublisherLists.append(Json::objectValue);
1514 curr[jss::pubkey_publisher] = strHex(publicKey);
1515 curr[jss::available] = pubCollection.status == PublisherStatus::available;
1516
1517 auto appendList = [](PublisherList const& publisherList, Json::Value& target) {
1518 target[jss::uri] = publisherList.siteUri;
1519 if (publisherList.validUntil != TimeKeeper::time_point{})
1520 {
1521 target[jss::seq] = static_cast<Json::UInt>(publisherList.sequence);
1522 target[jss::expiration] = to_string(publisherList.validUntil);
1523 }
1524 if (publisherList.validFrom != TimeKeeper::time_point{})
1525 target[jss::effective] = to_string(publisherList.validFrom);
1526 Json::Value& keys = (target[jss::list] = Json::arrayValue);
1527 for (auto const& key : publisherList.list)
1528 {
1529 keys.append(toBase58(TokenType::NodePublic, key));
1530 }
1531 };
1532 {
1533 auto const& current = pubCollection.current;
1534 appendList(current, curr);
1535 if (current.validUntil != TimeKeeper::time_point{})
1536 {
1537 curr[jss::version] = pubCollection.rawVersion;
1538 }
1539 }
1540
1541 Json::Value remaining(Json::arrayValue);
1542 for (auto const& [sequence, future] : pubCollection.remaining)
1543 {
1544 using namespace std::chrono_literals;
1545
1546 (void)sequence;
1547 Json::Value& r = remaining.append(Json::objectValue);
1548 appendList(future, r);
1549 // Race conditions can happen, so make this check "fuzzy"
1550 XRPL_ASSERT(
1551 future.validFrom > timeKeeper_.now() + 600s, "xrpl::ValidatorList::getJson : minimum valid from");
1552 }
1553 if (remaining.size())
1554 curr[jss::remaining] = std::move(remaining);
1555 }
1556
1557 // Trusted validator keys
1558 Json::Value& jValidatorKeys = (res[jss::trusted_validator_keys] = Json::arrayValue);
1559 for (auto const& k : trustedMasterKeys_)
1560 {
1561 jValidatorKeys.append(toBase58(TokenType::NodePublic, k));
1562 }
1563
1564 // signing keys
1565 Json::Value& jSigningKeys = (res[jss::signing_keys] = Json::objectValue);
1566 validatorManifests_.for_each_manifest([&jSigningKeys, this](Manifest const& manifest) {
1567 auto it = keyListings_.find(manifest.masterKey);
1568 if (it != keyListings_.end() && manifest.signingKey)
1569 {
1570 jSigningKeys[toBase58(TokenType::NodePublic, manifest.masterKey)] =
1571 toBase58(TokenType::NodePublic, *manifest.signingKey);
1572 }
1573 });
1574
1575 // Negative UNL
1576 if (!negativeUNL_.empty())
1577 {
1578 Json::Value& jNegativeUNL = (res[jss::NegativeUNL] = Json::arrayValue);
1579 for (auto const& k : negativeUNL_)
1580 {
1581 jNegativeUNL.append(toBase58(TokenType::NodePublic, k));
1582 }
1583 }
1584
1585 return res;
1586}
1587
1588void
1589ValidatorList::for_each_listed(std::function<void(PublicKey const&, bool)> func) const
1590{
1591 std::shared_lock read_lock{mutex_};
1592
1593 for (auto const& v : keyListings_)
1594 func(v.first, trusted(read_lock, v.first));
1595}
1596
1597void
1598ValidatorList::for_each_available(std::function<void(
1599 std::string const& manifest,
1600 std::uint32_t version,
1602 PublicKey const& pubKey,
1603 std::size_t maxSequence,
1604 uint256 const& hash)> func) const
1605{
1606 std::shared_lock read_lock{mutex_};
1607
1608 for (auto const& [key, plCollection] : publisherLists_)
1609 {
1610 if (plCollection.status != PublisherStatus::available)
1611 continue;
1612 XRPL_ASSERT(plCollection.maxSequence != 0, "xrpl::ValidatorList::for_each_available : nonzero maxSequence");
1613 func(
1614 plCollection.rawManifest,
1615 plCollection.rawVersion,
1616 buildBlobInfos(plCollection),
1617 key,
1618 plCollection.maxSequence.value_or(0),
1619 plCollection.fullHash);
1620 }
1621}
1622
1624ValidatorList::getAvailable(std::string_view pubKey, std::optional<std::uint32_t> forceVersion /* = {} */)
1625{
1626 std::shared_lock read_lock{mutex_};
1627
1628 auto const keyBlob = strViewUnHex(pubKey);
1629
1630 if (!keyBlob || !publicKeyType(makeSlice(*keyBlob)))
1631 {
1632 JLOG(j_.warn()) << "Invalid requested validator list publisher key: " << pubKey;
1633 return {};
1634 }
1635
1636 auto id = PublicKey(makeSlice(*keyBlob));
1637
1638 auto const iter = publisherLists_.find(id);
1639
1640 if (iter == publisherLists_.end() || iter->second.status != PublisherStatus::available)
1641 return {};
1642
1643 Json::Value value = buildFileData(std::string{pubKey}, iter->second, forceVersion, j_);
1644
1645 return value;
1646}
1647
1649ValidatorList::calculateQuorum(std::size_t unlSize, std::size_t effectiveUnlSize, std::size_t seenSize)
1650{
1651 // Use quorum if specified via command line.
1652 if (minimumQuorum_ > 0)
1653 {
1654 JLOG(j_.warn()) << "Using potentially unsafe quorum of " << *minimumQuorum_
1655 << " as specified on the command line";
1656 return *minimumQuorum_;
1657 }
1658
1659 if (!publisherLists_.empty())
1660 {
1661 // Do not use achievable quorum until lists from a sufficient number of
1662 // configured publishers are available
1664 for (auto const& list : publisherLists_)
1665 {
1666 if (list.second.status != PublisherStatus::available)
1667 unavailable += 1;
1668 }
1669 // There are two, subtly different, sides to list threshold:
1670 //
1671 // 1. The minimum required intersection between lists listThreshold_
1672 // for a validator to be included in trustedMasterKeys_.
1673 // If this many (or more) publishers are unavailable, we are likely
1674 // to NOT include a validator which otherwise would have been used.
1675 // We disable quorum if this happens.
1676 // 2. The minimum number of publishers which, when unavailable, will
1677 // prevent us from hitting the above threshold on ANY validator.
1678 // This is calculated as:
1679 // N - M + 1
1680 // where
1681 // N: number of publishers i.e. publisherLists_.size()
1682 // M: minimum required intersection i.e. listThreshold_
1683 // If this happens, we still have this local validator and we do not
1684 // want it to form a quorum of 1, so we disable quorum as well.
1685 //
1686 // We disable quorum if the number of unavailable publishers exceeds
1687 // either of the above thresholds
1688 auto const errorThreshold = std::min(
1689 listThreshold_, //
1690 publisherLists_.size() - listThreshold_ + 1);
1691 XRPL_ASSERT(errorThreshold > 0, "xrpl::ValidatorList::calculateQuorum : nonzero error threshold");
1692 if (unavailable >= errorThreshold)
1694 }
1695
1696 // Use an 80% quorum to balance fork safety, liveness, and required UNL
1697 // overlap.
1698 //
1699 // Theorem 8 of the Analysis of the XRP Ledger Consensus Protocol
1700 // (https://arxiv.org/abs/1802.07242) says:
1701 // XRP LCP guarantees fork safety if Oi,j > nj/2 + ni − qi + ti,j
1702 // for every pair of nodes Pi, Pj.
1703 //
1704 // ni: size of Pi's UNL
1705 // nj: size of Pj's UNL
1706 // Oi,j: number of validators in both UNLs
1707 // qi: validation quorum for Pi's UNL
1708 // ti, tj: maximum number of allowed Byzantine faults in Pi and Pj's
1709 // UNLs ti,j: min{ti, tj, Oi,j}
1710 //
1711 // Assume ni < nj, meaning and ti,j = ti
1712 //
1713 // For qi = .8*ni, we make ti <= .2*ni
1714 // (We could make ti lower and tolerate less UNL overlap. However in
1715 // order to prioritize safety over liveness, we need ti >= ni - qi)
1716 //
1717 // An 80% quorum allows two UNLs to safely have < .2*ni unique
1718 // validators between them:
1719 //
1720 // pi = ni - Oi,j
1721 // pj = nj - Oi,j
1722 //
1723 // Oi,j > nj/2 + ni − qi + ti,j
1724 // ni - pi > (ni - pi + pj)/2 + ni − .8*ni + .2*ni
1725 // pi + pj < .2*ni
1726 //
1727 // Note that the negative UNL protocol introduced the
1728 // AbsoluteMinimumQuorum which is 60% of the original UNL size. The
1729 // effective quorum should not be lower than it.
1730 return static_cast<std::size_t>(std::max(std::ceil(effectiveUnlSize * 0.8f), std::ceil(unlSize * 0.6f)));
1731}
1732
1734ValidatorList::updateTrusted(
1735 hash_set<NodeID> const& seenValidators,
1736 NetClock::time_point closeTime,
1737 NetworkOPs& ops,
1738 Overlay& overlay,
1739 HashRouter& hashRouter)
1740{
1741 using namespace std::chrono_literals;
1742 if (timeKeeper_.now() > closeTime + 30s)
1743 closeTime = timeKeeper_.now();
1744
1745 std::lock_guard lock{mutex_};
1746
1747 // Rotate pending and remove expired published lists
1748 bool good = true;
1749 // localPublisherList is not processed here. This is because the
1750 // Validators specified in the local config file do not expire nor do
1751 // they have a "remaining" section of PublisherList.
1752 for (auto& [pubKey, collection] : publisherLists_)
1753 {
1754 {
1755 auto& remaining = collection.remaining;
1756 auto const firstIter = remaining.begin();
1757 auto iter = firstIter;
1758 if (iter != remaining.end() && iter->second.validFrom <= closeTime)
1759 {
1760 // Find the LAST candidate that is ready to go live.
1761 for (auto next = std::next(iter); next != remaining.end() && next->second.validFrom <= closeTime;
1762 ++iter, ++next)
1763 {
1764 XRPL_ASSERT(
1765 std::next(iter) == next,
1766 "xrpl::ValidatorList::updateTrusted : sequential "
1767 "remaining");
1768 }
1769 XRPL_ASSERT(
1770 iter != remaining.end(),
1771 "xrpl::ValidatorList::updateTrusted : non-end of "
1772 "remaining");
1773
1774 // Rotate the pending list in to current
1775 auto sequence = iter->first;
1776 auto& candidate = iter->second;
1777 auto& current = collection.current;
1778 XRPL_ASSERT(candidate.validFrom <= closeTime, "xrpl::ValidatorList::updateTrusted : maximum time");
1779
1780 auto const oldList = current.list;
1781 current = std::move(candidate);
1782 if (collection.status != PublisherStatus::available)
1783 collection.status = PublisherStatus::available;
1784 XRPL_ASSERT(current.sequence == sequence, "xrpl::ValidatorList::updateTrusted : sequence match");
1785 // If the list is expired, remove the validators so they don't
1786 // get processed in. The expiration check below will do the rest
1787 // of the work
1788 if (current.validUntil <= closeTime)
1789 current.list.clear();
1790
1791 updatePublisherList(pubKey, current, oldList, lock);
1792
1793 // Only broadcast the current, which will consequently only
1794 // send to peers that don't understand v2, or which are
1795 // unknown (unlikely). Those that do understand v2 should
1796 // already have this list and are in the process of
1797 // switching themselves.
1798 broadcastBlobs(pubKey, collection, sequence, current.hash, overlay, hashRouter, j_);
1799
1800 // Erase any candidates that we skipped over, plus this one
1801 remaining.erase(firstIter, std::next(iter));
1802 }
1803 }
1804 // Remove if expired
1805 // ValidatorLists specified in the local config file never expire.
1806 // Hence, the below steps are not relevant for localPublisherList
1807 if (collection.status == PublisherStatus::available && collection.current.validUntil <= closeTime)
1808 {
1809 removePublisherList(lock, pubKey, PublisherStatus::expired);
1810 ops.setUNLBlocked();
1811 }
1812 if (collection.status != PublisherStatus::available)
1813 good = false;
1814 }
1815 if (good)
1816 ops.clearUNLBlocked();
1817
1818 TrustChanges trustChanges;
1819
1820 auto it = trustedMasterKeys_.cbegin();
1821 while (it != trustedMasterKeys_.cend())
1822 {
1823 auto const kit = keyListings_.find(*it);
1824 if (kit == keyListings_.end() || //
1825 kit->second < listThreshold_ || //
1826 validatorManifests_.revoked(*it))
1827 {
1828 trustChanges.removed.insert(calcNodeID(*it));
1829 it = trustedMasterKeys_.erase(it);
1830 }
1831 else
1832 {
1833 XRPL_ASSERT(kit->second >= listThreshold_, "xrpl::ValidatorList::updateTrusted : count meets threshold");
1834 ++it;
1835 }
1836 }
1837
1838 for (auto const& val : keyListings_)
1839 {
1840 if (val.second >= listThreshold_ && !validatorManifests_.revoked(val.first) &&
1841 trustedMasterKeys_.emplace(val.first).second)
1842 trustChanges.added.insert(calcNodeID(val.first));
1843 }
1844
1845 // If there were any changes, we need to update the ephemeral signing
1846 // keys:
1847 if (!trustChanges.added.empty() || !trustChanges.removed.empty())
1848 {
1849 trustedSigningKeys_.clear();
1850
1851 // trustedMasterKeys_ contain non-revoked manifests only. Hence the
1852 // manifests must contain a valid signingKey
1853 for (auto const& k : trustedMasterKeys_)
1854 {
1855 std::optional<PublicKey> const signingKey = validatorManifests_.getSigningKey(k);
1856 XRPL_ASSERT(signingKey, "xrpl::ValidatorList::updateTrusted : found signing key");
1857 trustedSigningKeys_.insert(*signingKey);
1858 }
1859 }
1860
1861 JLOG(j_.debug()) << trustedMasterKeys_.size() << " of " << keyListings_.size()
1862 << " listed validators eligible for inclusion in the trusted set";
1863
1864 auto const unlSize = trustedMasterKeys_.size();
1865 auto effectiveUnlSize = unlSize;
1866 auto seenSize = seenValidators.size();
1867 if (!negativeUNL_.empty())
1868 {
1869 for (auto const& k : trustedMasterKeys_)
1870 {
1871 if (negativeUNL_.count(k))
1872 --effectiveUnlSize;
1873 }
1874 hash_set<NodeID> negUnlNodeIDs;
1875 for (auto const& k : negativeUNL_)
1876 {
1877 negUnlNodeIDs.emplace(calcNodeID(k));
1878 }
1879 for (auto const& nid : seenValidators)
1880 {
1881 if (negUnlNodeIDs.count(nid))
1882 --seenSize;
1883 }
1884 }
1885 quorum_ = calculateQuorum(unlSize, effectiveUnlSize, seenSize);
1886
1887 JLOG(j_.debug()) << "Using quorum of " << quorum_ << " for new set of " << unlSize << " trusted validators ("
1888 << trustChanges.added.size() << " added, " << trustChanges.removed.size() << " removed)";
1889
1890 if (unlSize < quorum_)
1891 {
1892 JLOG(j_.warn()) << "New quorum of " << quorum_ << " exceeds the number of trusted validators (" << unlSize
1893 << ")";
1894 }
1895
1896 if ((publisherLists_.size() || localPublisherList.list.size()) && unlSize == 0)
1897 {
1898 // No validators. Lock down.
1899 ops.setUNLBlocked();
1900 }
1901
1902 return trustChanges;
1903}
1904
1906ValidatorList::getTrustedMasterKeys() const
1907{
1908 std::shared_lock read_lock{mutex_};
1909 return trustedMasterKeys_;
1910}
1911
1913ValidatorList::getListThreshold() const
1914{
1915 std::shared_lock read_lock{mutex_};
1916 return listThreshold_;
1917}
1918
1920ValidatorList::getNegativeUNL() const
1921{
1922 std::shared_lock read_lock{mutex_};
1923 return negativeUNL_;
1924}
1925
1926void
1927ValidatorList::setNegativeUNL(hash_set<PublicKey> const& negUnl)
1928{
1929 std::lock_guard lock{mutex_};
1930 negativeUNL_ = negUnl;
1931}
1932
1934ValidatorList::negativeUNLFilter(std::vector<std::shared_ptr<STValidation>>&& validations) const
1935{
1936 // Remove validations that are from validators on the negative UNL.
1937 auto ret = std::move(validations);
1938
1939 std::shared_lock read_lock{mutex_};
1940 if (!negativeUNL_.empty())
1941 {
1942 ret.erase(
1944 ret.begin(),
1945 ret.end(),
1946 [&](auto const& v) -> bool {
1947 if (auto const masterKey = getTrustedKey(read_lock, v->getSignerPublic()); masterKey)
1948 {
1949 return negativeUNL_.count(*masterKey);
1950 }
1951 else
1952 {
1953 return false;
1954 }
1955 }),
1956 ret.end());
1957 }
1958
1959 return ret;
1960}
1961
1962} // namespace xrpl
T accumulate(T... args)
T at(T... args)
T back(T... args)
T begin(T... args)
T ceil(T... args)
Unserialize a JSON document into a Value.
Definition json_reader.h:18
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Represents a JSON value.
Definition json_value.h:131
bool isArray() const
Value & append(Value const &value)
Append value to array at the end.
UInt size() const
Number of values in array or object.
std::string toStyledString() const
bool isString() const
UInt asUInt() const
std::string asString() const
Returns the unquoted string value.
bool isMember(char const *key) const
Return true if the object has a member named key.
bool isInt() const
A generic endpoint for log messages.
Definition Journal.h:41
Stream error() const
Definition Journal.h:319
Stream debug() const
Definition Journal.h:301
Stream trace() const
Severity stream access functions.
Definition Journal.h:295
Stream warn() const
Definition Journal.h:313
typename Clock::time_point time_point
typename Clock::duration duration
Routing table for objects identified by hash.
Definition HashRouter.h:78
std::optional< std::set< PeerShortID > > shouldRelay(uint256 const &key)
Determines whether the hashed item should be relayed.
bool addSuppressionPeer(uint256 const &key, PeerShortID peer)
Remembers manifests with the highest sequence number.
Definition Manifest.h:225
std::optional< PublicKey > getSigningKey(PublicKey const &pk) const
Returns master key's current signing key.
Definition Manifest.cpp:273
ManifestDisposition applyManifest(Manifest m)
Add manifest to cache.
Definition Manifest.cpp:344
PublicKey getMasterKey(PublicKey const &pk) const
Returns ephemeral signing key's master public key.
Definition Manifest.cpp:285
bool revoked(PublicKey const &pk) const
Returns true if master key has been revoked in a manifest.
Definition Manifest.cpp:332
static std::size_t totalSize(::google::protobuf::Message const &message)
Definition Message.cpp:43
Provides server functionality for clients.
Definition NetworkOPs.h:70
virtual void clearUNLBlocked()=0
virtual void setUNLBlocked()=0
Manages the set of connected peers.
Definition Overlay.h:30
virtual PeerSequence getActivePeers() const =0
Returns a sequence representing the current list of peers.
Represents a peer connection in the overlay.
virtual std::string const & fingerprint() const =0
virtual void setPublisherListSequence(PublicKey const &, std::size_t const)=0
virtual void send(std::shared_ptr< Message > const &m)=0
virtual id_t id() const =0
virtual bool supportsFeature(ProtocolFeature f) const =0
A public key.
Definition PublicKey.h:43
std::size_t size() const noexcept
Definition PublicKey.h:74
const_iterator end() const noexcept
Definition PublicKey.h:92
An immutable linear range of bytes.
Definition Slice.h:27
Manages various times used by the server.
Definition TimeKeeper.h:13
time_point now() const override
Returns the current time, using the server's clock.
Definition TimeKeeper.h:44
static std::pair< std::size_t, std::size_t > buildValidatorListMessages(std::size_t messageVersion, std::uint64_t peerSequence, std::size_t maxSequence, std::uint32_t rawVersion, std::string const &rawManifest, std::map< std::size_t, ValidatorBlobInfo > const &blobInfos, std::vector< MessageWithHash > &messages, std::size_t maxSize=maximumMessageSize)
static void sendValidatorList(Peer &peer, std::uint64_t peerSequence, PublicKey const &publisherKey, std::size_t maxSequence, std::uint32_t rawVersion, std::string const &rawManifest, std::map< std::size_t, ValidatorBlobInfo > const &blobInfos, HashRouter &hashRouter, beast::Journal j)
TimeKeeper & timeKeeper_
static constexpr std::uint32_t supportedListVersions[]
PublisherList localPublisherList
hash_set< PublicKey > trustedMasterKeys_
bool trustedPublisher(PublicKey const &identity) const
Returns true if public key is a trusted publisher.
static constexpr std::size_t maxSupportedBlobs
ValidatorList(ManifestCache &validatorManifests, ManifestCache &publisherManifests, TimeKeeper &timeKeeper, std::string const &databasePath, beast::Journal j, std::optional< std::size_t > minimumQuorum=std::nullopt)
static Json::Value buildFileData(std::string const &pubKey, PublisherListCollection const &pubCollection, beast::Journal j)
Build a Json representation of the collection, suitable for writing to a cache file,...
std::vector< std::string > loadLists()
std::optional< PublicKey > localPubKey_
std::atomic< std::size_t > quorum_
std::pair< ListDisposition, std::optional< PublicKey > > verify(lock_guard const &, Json::Value &list, Manifest manifest, std::string const &blob, std::string const &signature)
Check response for trusted valid published list.
beast::Journal const j_
boost::filesystem::path const dataPath_
std::shared_mutex mutex_
bool load(std::optional< PublicKey > const &localSigningKey, std::vector< std::string > const &configKeys, std::vector< std::string > const &publisherKeys, std::optional< std::size_t > listThreshold={})
Load configured trusted keys.
bool removePublisherList(lock_guard const &, PublicKey const &publisherKey, PublisherStatus reason)
Stop trusting publisher's list of keys.
PublisherListStats applyLists(std::string const &manifest, std::uint32_t version, std::vector< ValidatorBlobInfo > const &blobs, std::string siteUri, std::optional< uint256 > const &hash={})
Apply multiple published lists of public keys.
std::optional< PublicKey > localPublicKey() const
This function returns the local validator public key or a std::nullopt.
PublisherListStats applyList(std::string const &globalManifest, std::optional< std::string > const &localManifest, std::string const &blob, std::string const &signature, std::uint32_t version, std::string siteUri, std::optional< uint256 > const &hash, lock_guard const &)
Apply published list of public keys.
std::optional< PublicKey > getListedKey(PublicKey const &identity) const
Returns listed master public if public key is included on any lists.
void cacheValidatorFile(lock_guard const &lock, PublicKey const &pubKey) const
Write a JSON UNL to a cache file.
std::optional< std::size_t > minimumQuorum_
static std::vector< ValidatorBlobInfo > parseBlobs(std::uint32_t version, Json::Value const &body)
Pull the blob/signature/manifest information out of the appropriate Json body fields depending on the...
ManifestCache & publisherManifests_
hash_map< PublicKey, std::size_t > keyListings_
std::optional< TimeKeeper::time_point > expires() const
Return the time when the validator list will expire.
static void buildBlobInfos(std::map< std::size_t, ValidatorBlobInfo > &blobInfos, PublisherListCollection const &lists)
ManifestCache & validatorManifests_
std::size_t listThreshold_
static void broadcastBlobs(PublicKey const &publisherKey, PublisherListCollection const &lists, std::size_t maxSequence, uint256 const &hash, Overlay &overlay, HashRouter &hashRouter, beast::Journal j)
std::size_t count() const
Return the number of configured validator list sites.
std::optional< PublicKey > getTrustedKey(PublicKey const &identity) const
Returns master public key if public key is trusted.
void updatePublisherList(PublicKey const &pubKey, PublisherList const &current, std::vector< PublicKey > const &oldList, lock_guard const &)
PublisherListStats applyListsAndBroadcast(std::string const &manifest, std::uint32_t version, std::vector< ValidatorBlobInfo > const &blobs, std::string siteUri, uint256 const &hash, Overlay &overlay, HashRouter &hashRouter, NetworkOPs &networkOPs)
Apply multiple published lists of public keys, then broadcast it to all peers that have not seen it o...
boost::filesystem::path getCacheFileName(lock_guard const &, PublicKey const &pubKey) const
Get the filename used for caching UNLs.
bool trusted(PublicKey const &identity) const
Returns true if public key is trusted.
static std::string const filePrefix_
bool listed(PublicKey const &identity) const
Returns true if public key is included on any lists.
hash_map< PublicKey, PublisherListCollection > publisherLists_
T clear(T... args)
T count(T... args)
T emplace_back(T... args)
T emplace(T... args)
T empty(T... args)
T end(T... args)
T is_same_v
T make_pair(T... args)
T max(T... args)
T min(T... args)
@ nullValue
'null' value
Definition json_value.h:20
@ arrayValue
array value (ordered list)
Definition json_value.h:26
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:27
unsigned int UInt
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
std::error_code make_error_code(xrpl::TokenCodecErrc e)
sha512_half_hasher::result_type sha512Half(Args const &... args)
Returns the SHA512-Half of a series of objects.
Definition digest.h:205
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:598
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:11
std::string base64_decode(std::string_view data)
ListDisposition
@ unsupported_version
List version is not supported.
@ stale
Trusted publisher key, but seq is too old.
@ accepted
List is valid.
@ untrusted
List signed by untrusted publisher key.
@ same_sequence
Same sequence as current list.
@ pending
List will be valid in the future.
@ known_sequence
Future sequence already seen.
@ expired
List is expired, but has the largest non-pending sequence seen so far.
@ invalid
Invalid format or signature.
bool verify(PublicKey const &publicKey, Slice const &m, Slice const &sig) noexcept
Verify a signature on a message.
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:92
std::size_t splitMessage(std::vector< ValidatorList::MessageWithHash > &messages, protocol::TMValidatorListCollection const &largeMsg, std::size_t maxSize, std::size_t begin=0, std::size_t end=0)
std::size_t splitMessageParts(std::vector< ValidatorList::MessageWithHash > &messages, protocol::TMValidatorListCollection const &largeMsg, std::size_t maxSize, std::size_t begin, std::size_t end)
@ current
This was a new validation and was added.
std::optional< KeyType > publicKeyType(Slice const &slice)
Returns the type of public key.
std::optional< Manifest > deserializeManifest(Slice s, beast::Journal journal)
Constructs Manifest from serialized string.
Definition Manifest.cpp:35
std::size_t buildValidatorListMessage(std::vector< ValidatorList::MessageWithHash > &messages, std::uint32_t rawVersion, std::string const &rawManifest, ValidatorBlobInfo const &currentBlob, std::size_t maxSize)
std::optional< Blob > strUnHex(std::size_t strSize, Iterator begin, Iterator end)
NodeID calcNodeID(PublicKey const &)
Calculate the 160-bit node ID from a node public key.
void writeFileContents(boost::system::error_code &ec, boost::filesystem::path const &destPath, std::string const &contents)
PublisherStatus
std::optional< Blob > strViewUnHex(std::string_view strSrc)
constexpr std::size_t maximumMessageSize
Definition Message.h:15
@ manifest
Manifest.
std::enable_if_t< std::is_same< T, char >::value||std::is_same< T, unsigned char >::value, Slice > makeSlice(std::array< T, N > const &a)
Definition Slice.h:214
@ accepted
Manifest is valid.
@ invalid
Timely, but invalid signature.
T next(T... args)
T push_back(T... args)
T remove_if(T... args)
T reserve(T... args)
T reset(T... args)
T size(T... args)
T sort(T... args)
Changes in trusted nodes after updating validator list.
hash_set< NodeID > added
hash_set< NodeID > removed
Used to represent the information stored in the blobs_v2 Json array.
std::optional< std::string > manifest
std::map< std::size_t, PublisherList > remaining
Describes the result of processing a Validator List (UNL), including some of the information from the...
void mergeDispositions(PublisherListStats const &src)
std::optional< PublicKey > publisherKey
std::map< ListDisposition, std::size_t > dispositions
std::vector< PublicKey > list
TimeKeeper::time_point validFrom
TimeKeeper::time_point validUntil