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