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