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