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