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 "
799 << peer.getRemoteAddress().to_string() << " [" << peer.id()
800 << "]";
801 else
802 {
803 XRPL_ASSERT(
804 numVLs == 1,
805 "ripple::ValidatorList::sendValidatorList : one validator "
806 "list");
807 JLOG(j.debug())
808 << "Sent validator list for " << strHex(publisherKey)
809 << " with sequence " << newPeerSequence << " to "
810 << peer.getRemoteAddress().to_string() << " [" << peer.id()
811 << "]";
812 }
813 }
814 }
815}
816
817// static
818void
820 Peer& peer,
821 std::uint64_t peerSequence,
822 PublicKey const& publisherKey,
823 std::size_t maxSequence,
824 std::uint32_t rawVersion,
825 std::string const& rawManifest,
827 HashRouter& hashRouter,
829{
832 peer,
833 peerSequence,
834 publisherKey,
835 maxSequence,
836 rawVersion,
837 rawManifest,
838 blobInfos,
839 messages,
840 hashRouter,
841 j);
842}
843
844// static
845void
849{
850 auto const& current = lists.current;
851 auto const& remaining = lists.remaining;
852 blobInfos[current.sequence] = {
853 current.rawBlob, current.rawSignature, current.rawManifest};
854 for (auto const& [sequence, vl] : remaining)
855 {
856 blobInfos[sequence] = {vl.rawBlob, vl.rawSignature, vl.rawManifest};
857 }
858}
859
860// static
864{
866 buildBlobInfos(result, lists);
867 return result;
868}
869
870// static
871void
873 PublicKey const& publisherKey,
875 std::size_t maxSequence,
876 uint256 const& hash,
877 Overlay& overlay,
878 HashRouter& hashRouter,
880{
881 auto const toSkip = hashRouter.shouldRelay(hash);
882
883 if (toSkip)
884 {
885 // We don't know what messages or message versions we're sending
886 // until we examine our peer's properties. Build the message(s) on
887 // demand, but reuse them when possible.
888
889 // This will hold a v1 message with only the current VL if we have
890 // any peers that don't support v2
892 // This will hold v2 messages indexed by the peer's
893 // `publisherListSequence`. For each `publisherListSequence`, we'll
894 // only send the VLs with higher sequences.
896 messages2;
897 // If any peers are found that are worth considering, this list will
898 // be built to hold info for all of the valid VLs.
900
901 XRPL_ASSERT(
902 lists.current.sequence == maxSequence ||
903 lists.remaining.count(maxSequence) == 1,
904 "ripple::ValidatorList::broadcastBlobs : valid sequence");
905 // Can't use overlay.foreach here because we need to modify
906 // the peer, and foreach provides a const&
907 for (auto& peer : overlay.getActivePeers())
908 {
909 if (toSkip->count(peer->id()) == 0)
910 {
911 auto const peerSequence =
912 peer->publisherListSequence(publisherKey).value_or(0);
913 if (peerSequence < maxSequence)
914 {
915 if (blobInfos.empty())
916 buildBlobInfos(blobInfos, lists);
917 auto const v2 = peer->supportsFeature(
920 *peer,
921 peerSequence,
922 publisherKey,
923 maxSequence,
924 lists.rawVersion,
925 lists.rawManifest,
926 blobInfos,
927 v2 ? messages2[peerSequence] : messages1,
928 hashRouter,
929 j);
930 // Even if the peer doesn't support the messages,
931 // suppress it so it'll be ignored next time.
932 hashRouter.addSuppressionPeer(hash, peer->id());
933 }
934 }
935 }
936 }
937}
938
941 std::string const& manifest,
942 std::uint32_t version,
944 std::string siteUri,
945 uint256 const& hash,
946 Overlay& overlay,
947 HashRouter& hashRouter,
948 NetworkOPs& networkOPs)
949{
950 auto const result =
951 applyLists(manifest, version, blobs, std::move(siteUri), hash);
952 auto const disposition = result.bestDisposition();
953
954 if (disposition == ListDisposition::accepted)
955 {
956 bool good = true;
957
958 // localPublisherList never expires, so localPublisherList is excluded
959 // from the below check.
960 for (auto const& [_, listCollection] : publisherLists_)
961 {
962 if (listCollection.status != PublisherStatus::available)
963 {
964 good = false;
965 break;
966 }
967 }
968 if (good)
969 {
970 networkOPs.clearUNLBlocked();
971 }
972 }
973 bool broadcast = disposition <= ListDisposition::known_sequence;
974
975 // this function is only called for PublicKeys which are not specified
976 // in the config file (Note: Keys specified in the local config file are
977 // stored in ValidatorList::localPublisherList data member).
978 if (broadcast && result.status <= PublisherStatus::expired &&
979 result.publisherKey &&
980 publisherLists_[*result.publisherKey].maxSequence)
981 {
982 auto const& pubCollection = publisherLists_[*result.publisherKey];
983
985 *result.publisherKey,
986 pubCollection,
987 *pubCollection.maxSequence,
988 hash,
989 overlay,
990 hashRouter,
991 j_);
992 }
993
994 return result;
995}
996
999 std::string const& manifest,
1000 std::uint32_t version,
1001 std::vector<ValidatorBlobInfo> const& blobs,
1002 std::string siteUri,
1003 std::optional<uint256> const& hash /* = {} */)
1004{
1005 if (std::count(
1008 version) != 1)
1010
1011 std::lock_guard lock{mutex_};
1012
1013 PublisherListStats result;
1014 for (auto const& blobInfo : blobs)
1015 {
1016 auto stats = applyList(
1017 manifest,
1018 blobInfo.manifest,
1019 blobInfo.blob,
1020 blobInfo.signature,
1021 version,
1022 siteUri,
1023 hash,
1024 lock);
1025
1026 if (stats.bestDisposition() < result.bestDisposition() ||
1027 (stats.bestDisposition() == result.bestDisposition() &&
1028 stats.sequence > result.sequence))
1029 {
1030 stats.mergeDispositions(result);
1031 result = std::move(stats);
1032 }
1033 else
1034 result.mergeDispositions(stats);
1036 }
1037
1038 // Clean up the collection, because some of the processing may have made it
1039 // inconsistent
1040 if (result.publisherKey && publisherLists_.count(*result.publisherKey))
1041 {
1042 auto& pubCollection = publisherLists_[*result.publisherKey];
1043 auto& remaining = pubCollection.remaining;
1044 auto const& current = pubCollection.current;
1045 for (auto iter = remaining.begin(); iter != remaining.end();)
1046 {
1047 auto next = std::next(iter);
1048 XRPL_ASSERT(
1049 next == remaining.end() || next->first > iter->first,
1050 "ripple::ValidatorList::applyLists : next is valid");
1051 if (iter->first <= current.sequence ||
1052 (next != remaining.end() &&
1053 next->second.validFrom <= iter->second.validFrom))
1054 {
1055 iter = remaining.erase(iter);
1056 }
1057 else
1058 {
1059 iter = next;
1060 }
1061 }
1062
1063 cacheValidatorFile(lock, *result.publisherKey);
1064
1065 pubCollection.fullHash = sha512Half(pubCollection);
1066
1067 result.sequence = *pubCollection.maxSequence;
1068 }
1069
1070 return result;
1071}
1072
1073void
1075 PublicKey const& pubKey,
1076 PublisherList const& current,
1077 std::vector<PublicKey> const& oldList,
1079{
1080 // Update keyListings_ for added and removed keys
1081 std::vector<PublicKey> const& publisherList = current.list;
1082 std::vector<std::string> const& manifests = current.manifests;
1083 auto iNew = publisherList.begin();
1084 auto iOld = oldList.begin();
1085 while (iNew != publisherList.end() || iOld != oldList.end())
1086 {
1087 if (iOld == oldList.end() ||
1088 (iNew != publisherList.end() && *iNew < *iOld))
1089 {
1090 // Increment list count for added keys
1091 ++keyListings_[*iNew];
1092 ++iNew;
1093 }
1094 else if (
1095 iNew == publisherList.end() ||
1096 (iOld != oldList.end() && *iOld < *iNew))
1097 {
1098 // Decrement list count for removed keys
1099 if (keyListings_[*iOld] <= 1)
1100 keyListings_.erase(*iOld);
1101 else
1102 --keyListings_[*iOld];
1103 ++iOld;
1104 }
1105 else
1106 {
1107 ++iNew;
1108 ++iOld;
1109 }
1110 }
1111
1112 if (publisherList.empty())
1113 {
1114 JLOG(j_.warn()) << "No validator keys included in valid list";
1115 }
1116
1117 for (auto const& valManifest : manifests)
1118 {
1119 auto m = deserializeManifest(base64_decode(valManifest));
1120
1121 if (!m || !keyListings_.count(m->masterKey))
1122 {
1123 JLOG(j_.warn()) << "List for " << strHex(pubKey)
1124 << " contained untrusted validator manifest";
1125 continue;
1126 }
1127
1128 if (auto const r = validatorManifests_.applyManifest(std::move(*m));
1130 {
1131 JLOG(j_.warn()) << "List for " << strHex(pubKey)
1132 << " contained invalid validator manifest";
1133 }
1134 }
1135}
1136
1139 std::string const& globalManifest,
1140 std::optional<std::string> const& localManifest,
1141 std::string const& blob,
1142 std::string const& signature,
1143 std::uint32_t version,
1144 std::string siteUri,
1145 std::optional<uint256> const& hash,
1146 ValidatorList::lock_guard const& lock)
1147{
1148 using namespace std::string_literals;
1149
1150 Json::Value list;
1151 auto const& manifest = localManifest ? *localManifest : globalManifest;
1153 if (!m)
1154 {
1155 JLOG(j_.warn()) << "UNL manifest cannot be deserialized";
1157 }
1158
1159 auto [result, pubKeyOpt] =
1160 verify(lock, list, std::move(*m), blob, signature);
1161
1162 if (!pubKeyOpt)
1163 {
1164 JLOG(j_.warn())
1165 << "UNL manifest is signed with an unrecognized master public key";
1166 return PublisherListStats{result};
1167 }
1168
1169 if (!publicKeyType(*pubKeyOpt))
1170 { // LCOV_EXCL_START
1171 // This is an impossible situation because we will never load an
1172 // invalid public key type (see checks in `ValidatorList::load`) however
1173 // we can only arrive here if the key used by the manifest matched one of
1174 // the loaded keys
1175 UNREACHABLE(
1176 "ripple::ValidatorList::applyList : invalid public key type");
1177 return PublisherListStats{result};
1178 } // LCOV_EXCL_STOP
1179
1180 PublicKey pubKey = *pubKeyOpt;
1181 if (result > ListDisposition::pending)
1182 {
1183 if (publisherLists_.count(pubKey))
1184 {
1185 auto const& pubCollection = publisherLists_[pubKey];
1186 if (pubCollection.maxSequence &&
1187 (result == ListDisposition::same_sequence ||
1189 {
1190 // We've seen something valid list for this publisher
1191 // already, so return what we know about it.
1192 return PublisherListStats{
1193 result,
1194 pubKey,
1195 pubCollection.status,
1196 *pubCollection.maxSequence};
1197 }
1198 }
1199 return PublisherListStats{result};
1200 }
1201
1202 // Update publisher's list
1203 auto& pubCollection = publisherLists_[pubKey];
1204 auto const sequence = list[jss::sequence].asUInt();
1205 auto const accepted =
1206 (result == ListDisposition::accepted ||
1207 result == ListDisposition::expired);
1208
1209 if (accepted)
1210 pubCollection.status = result == ListDisposition::accepted
1213 pubCollection.rawManifest = globalManifest;
1214 if (!pubCollection.maxSequence || sequence > *pubCollection.maxSequence)
1215 pubCollection.maxSequence = sequence;
1216
1217 Json::Value const& newList = list[jss::validators];
1218 std::vector<PublicKey> oldList;
1219 if (accepted && pubCollection.remaining.count(sequence) != 0)
1220 {
1221 // We've seen this list before and stored it in "remaining". The
1222 // normal expected process is that the processed list would have
1223 // already been moved in to "current" by "updateTrusted()", but race
1224 // conditions are possible, or the node may have lost sync, so do
1225 // some of that work here.
1226 auto& publisher = pubCollection.current;
1227 // Copy the old validator list
1228 oldList = std::move(pubCollection.current.list);
1229 // Move the publisher info from "remaining" to "current"
1230 publisher = std::move(pubCollection.remaining[sequence]);
1231 // Remove the entry in "remaining"
1232 pubCollection.remaining.erase(sequence);
1233 // Done
1234 XRPL_ASSERT(
1235 publisher.sequence == sequence,
1236 "ripple::ValidatorList::applyList : publisher sequence match");
1237 }
1238 else
1239 {
1240 auto& publisher = accepted ? pubCollection.current
1241 : pubCollection.remaining[sequence];
1242 publisher.sequence = sequence;
1243 publisher.validFrom = TimeKeeper::time_point{TimeKeeper::duration{
1244 list.isMember(jss::effective) ? list[jss::effective].asUInt() : 0}};
1245 publisher.validUntil = TimeKeeper::time_point{
1246 TimeKeeper::duration{list[jss::expiration].asUInt()}};
1247 publisher.siteUri = std::move(siteUri);
1248 publisher.rawBlob = blob;
1249 publisher.rawSignature = signature;
1250 publisher.rawManifest = localManifest;
1251 if (hash)
1252 publisher.hash = *hash;
1253
1254 std::vector<PublicKey>& publisherList = publisher.list;
1255 std::vector<std::string>& manifests = publisher.manifests;
1256
1257 // Copy the old validator list
1258 oldList = std::move(publisherList);
1259 // Build the new validator list from "newList"
1260 publisherList.clear();
1261 publisherList.reserve(newList.size());
1262 for (auto const& val : newList)
1263 {
1264 if (val.isObject() && val.isMember(jss::validation_public_key) &&
1265 val[jss::validation_public_key].isString())
1266 {
1267 std::optional<Blob> const ret =
1268 strUnHex(val[jss::validation_public_key].asString());
1269
1270 if (!ret || !publicKeyType(makeSlice(*ret)))
1271 {
1272 JLOG(j_.error())
1273 << "Invalid node identity: "
1274 << val[jss::validation_public_key].asString();
1275 }
1276 else
1277 {
1278 publisherList.push_back(
1279 PublicKey(Slice{ret->data(), ret->size()}));
1280 }
1281
1282 if (val.isMember(jss::manifest) &&
1283 val[jss::manifest].isString())
1284 manifests.push_back(val[jss::manifest].asString());
1285 }
1286 }
1287
1288 // Standardize the list order by sorting
1289 std::sort(publisherList.begin(), publisherList.end());
1290 }
1291 // If this publisher has ever sent a more updated version than the one
1292 // in this file, keep it. This scenario is unlikely, but legal.
1293 pubCollection.rawVersion = std::max(pubCollection.rawVersion, version);
1294 if (!pubCollection.remaining.empty())
1295 {
1296 // If there are any pending VLs, then this collection must be at least
1297 // version 2.
1298 pubCollection.rawVersion = std::max(pubCollection.rawVersion, 2u);
1299 }
1300
1301 PublisherListStats const applyResult{
1302 result, pubKey, pubCollection.status, *pubCollection.maxSequence};
1303
1304 if (accepted)
1305 {
1306 updatePublisherList(pubKey, pubCollection.current, oldList, lock);
1307 }
1308
1309 return applyResult;
1310}
1311
1314{
1315 using namespace std::string_literals;
1316 using namespace boost::filesystem;
1317 using namespace boost::system::errc;
1318
1319 std::lock_guard lock{mutex_};
1320
1322 sites.reserve(publisherLists_.size());
1323 for (auto const& [pubKey, publisherCollection] : publisherLists_)
1324 {
1325 boost::system::error_code ec;
1326
1327 if (publisherCollection.status == PublisherStatus::available)
1328 continue;
1329
1330 boost::filesystem::path const filename = getCacheFileName(lock, pubKey);
1331
1332 auto const fullPath{canonical(filename, ec)};
1333 if (ec)
1334 continue;
1335
1336 auto size = file_size(fullPath, ec);
1337 if (!ec && !size)
1338 {
1339 // Treat an empty file as a missing file, because
1340 // nobody else is going to write it.
1341 ec = make_error_code(no_such_file_or_directory);
1342 }
1343 if (ec)
1344 continue;
1345
1346 std::string const prefix = [&fullPath]() {
1347#if _MSC_VER // MSVC: Windows paths need a leading / added
1348 {
1349 return fullPath.root_path() == "/"s ? "file://" : "file:///";
1350 }
1351#else
1352 {
1353 (void)fullPath;
1354 return "file://";
1355 }
1356#endif
1357 }();
1358 sites.emplace_back(prefix + fullPath.string());
1359 }
1360
1361 // Then let the ValidatorSites do the rest of the work.
1362 return sites;
1363}
1364
1365// The returned PublicKey value is read from the manifest. Manifests do not
1366// contain the default-constructed public keys
1369 ValidatorList::lock_guard const& lock,
1370 Json::Value& list,
1372 std::string const& blob,
1373 std::string const& signature)
1374{
1375 if (!publisherLists_.count(manifest.masterKey))
1376 return {ListDisposition::untrusted, {}};
1377
1378 PublicKey masterPubKey = manifest.masterKey;
1379 auto const revoked = manifest.revoked();
1380
1381 auto const result = publisherManifests_.applyManifest(std::move(manifest));
1382
1383 if (revoked && result == ManifestDisposition::accepted)
1384 {
1385 removePublisherList(lock, masterPubKey, PublisherStatus::revoked);
1386 // If the manifest is revoked, no future list is valid either
1387 publisherLists_[masterPubKey].remaining.clear();
1388 }
1389
1390 auto const signingKey = publisherManifests_.getSigningKey(masterPubKey);
1391
1392 if (revoked || !signingKey || result == ManifestDisposition::invalid)
1393 return {ListDisposition::untrusted, masterPubKey};
1394
1395 auto const sig = strUnHex(signature);
1396 auto const data = base64_decode(blob);
1397 if (!sig || !ripple::verify(*signingKey, makeSlice(data), makeSlice(*sig)))
1398 return {ListDisposition::invalid, masterPubKey};
1399
1400 Json::Reader r;
1401 if (!r.parse(data, list))
1402 return {ListDisposition::invalid, masterPubKey};
1403
1404 if (list.isMember(jss::sequence) && list[jss::sequence].isInt() &&
1405 list.isMember(jss::expiration) && list[jss::expiration].isInt() &&
1406 (!list.isMember(jss::effective) || list[jss::effective].isInt()) &&
1407 list.isMember(jss::validators) && list[jss::validators].isArray())
1408 {
1409 auto const sequence = list[jss::sequence].asUInt();
1410 auto const validFrom = TimeKeeper::time_point{TimeKeeper::duration{
1411 list.isMember(jss::effective) ? list[jss::effective].asUInt() : 0}};
1412 auto const validUntil = TimeKeeper::time_point{
1413 TimeKeeper::duration{list[jss::expiration].asUInt()}};
1414 auto const now = timeKeeper_.now();
1415 auto const& listCollection = publisherLists_[masterPubKey];
1416 if (validUntil <= validFrom)
1417 return {ListDisposition::invalid, masterPubKey};
1418 else if (sequence < listCollection.current.sequence)
1419 return {ListDisposition::stale, masterPubKey};
1420 else if (sequence == listCollection.current.sequence)
1421 return {ListDisposition::same_sequence, masterPubKey};
1422 else if (validUntil <= now)
1423 return {ListDisposition::expired, masterPubKey};
1424 else if (validFrom > now)
1425 // Not yet valid. Return pending if one of the following is true
1426 // * There's no maxSequence, indicating this is the first blob seen
1427 // for this publisher
1428 // * The sequence is larger than the maxSequence, indicating this
1429 // blob is new
1430 // * There's no entry for this sequence AND this blob is valid
1431 // before the last blob, indicating blobs may be processing out of
1432 // order. This may result in some duplicated processing, but
1433 // prevents the risk of missing valid data. Else return
1434 // known_sequence
1435 return !listCollection.maxSequence ||
1436 sequence > *listCollection.maxSequence ||
1437 (listCollection.remaining.count(sequence) == 0 &&
1438 validFrom < listCollection.remaining
1439 .at(*listCollection.maxSequence)
1440 .validFrom)
1443 }
1444 else
1445 {
1446 return {ListDisposition::invalid, masterPubKey};
1447 }
1448
1449 return {ListDisposition::accepted, masterPubKey};
1450}
1451
1452bool
1453ValidatorList::listed(PublicKey const& identity) const
1454{
1455 std::shared_lock read_lock{mutex_};
1456
1457 auto const pubKey = validatorManifests_.getMasterKey(identity);
1458 return keyListings_.find(pubKey) != keyListings_.end();
1459}
1460
1461bool
1464 PublicKey const& identity) const
1465{
1466 auto const pubKey = validatorManifests_.getMasterKey(identity);
1467 return trustedMasterKeys_.find(pubKey) != trustedMasterKeys_.end();
1468}
1469
1470bool
1471ValidatorList::trusted(PublicKey const& identity) const
1472{
1473 std::shared_lock read_lock{mutex_};
1474 return trusted(read_lock, identity);
1475}
1476
1479{
1480 std::shared_lock read_lock{mutex_};
1481
1482 auto const pubKey = validatorManifests_.getMasterKey(identity);
1483 if (keyListings_.find(pubKey) != keyListings_.end())
1484 return pubKey;
1485 return std::nullopt;
1486}
1487
1491 PublicKey const& identity) const
1492{
1493 auto const pubKey = validatorManifests_.getMasterKey(identity);
1494 if (trustedMasterKeys_.find(pubKey) != trustedMasterKeys_.end())
1495 return pubKey;
1496 return std::nullopt;
1497}
1498
1501{
1502 std::shared_lock read_lock{mutex_};
1503
1504 return getTrustedKey(read_lock, identity);
1505}
1506
1507bool
1509{
1510 std::shared_lock read_lock{mutex_};
1511 return identity.size() && publisherLists_.count(identity) &&
1512 publisherLists_.at(identity).status < PublisherStatus::revoked;
1513}
1514
1517{
1518 std::shared_lock read_lock{mutex_};
1519 return localPubKey_;
1520}
1521
1522bool
1525 PublicKey const& publisherKey,
1526 PublisherStatus reason)
1527{
1528 XRPL_ASSERT(
1529 reason != PublisherStatus::available &&
1531 "ripple::ValidatorList::removePublisherList : valid reason input");
1532 auto const iList = publisherLists_.find(publisherKey);
1533 if (iList == publisherLists_.end())
1534 return false;
1535
1536 JLOG(j_.debug()) << "Removing validator list for publisher "
1537 << strHex(publisherKey);
1538
1539 for (auto const& val : iList->second.current.list)
1540 {
1541 auto const& iVal = keyListings_.find(val);
1542 if (iVal == keyListings_.end())
1543 continue;
1544
1545 if (iVal->second <= 1)
1546 keyListings_.erase(iVal);
1547 else
1548 --iVal->second;
1549 }
1550
1551 iList->second.current.list.clear();
1552 iList->second.status = reason;
1553
1554 return true;
1555}
1556
1559{
1560 return publisherLists_.size() + (localPublisherList.list.size() > 0);
1561}
1562
1565{
1566 std::shared_lock read_lock{mutex_};
1567 return count(read_lock);
1568}
1569
1572{
1574 for (auto const& [_, collection] : publisherLists_)
1575 {
1576 // Unfetched
1577 auto const& current = collection.current;
1578 if (current.validUntil == TimeKeeper::time_point{})
1579 {
1580 return std::nullopt;
1581 }
1582
1583 // Find the latest validUntil in a chain where the next validFrom
1584 // overlaps with the previous validUntil. applyLists has already cleaned
1585 // up the list so the validFrom dates are guaranteed increasing.
1586 auto chainedExpiration = current.validUntil;
1587 for (auto const& [sequence, check] : collection.remaining)
1588 {
1589 (void)sequence;
1590 if (check.validFrom <= chainedExpiration)
1591 chainedExpiration = check.validUntil;
1592 else
1593 break;
1594 }
1595
1596 // Earliest
1597 if (!res || chainedExpiration < *res)
1598 {
1599 res = chainedExpiration;
1600 }
1601 }
1602
1603 if (localPublisherList.list.size() > 0)
1604 {
1605 PublisherList collection = localPublisherList;
1606 // Unfetched
1607 auto const& current = collection;
1608 auto chainedExpiration = current.validUntil;
1609
1610 // Earliest
1611 if (!res || chainedExpiration < *res)
1612 {
1613 res = chainedExpiration;
1614 }
1615 }
1616 return res;
1617}
1618
1620ValidatorList::expires() const
1621{
1622 std::shared_lock read_lock{mutex_};
1623 return expires(read_lock);
1624}
1625
1627ValidatorList::getJson() const
1628{
1630
1631 std::shared_lock read_lock{mutex_};
1632
1633 res[jss::validation_quorum] = static_cast<Json::UInt>(quorum_);
1634
1635 {
1636 auto& x = (res[jss::validator_list] = Json::objectValue);
1637
1638 x[jss::count] = static_cast<Json::UInt>(count(read_lock));
1639
1640 if (auto when = expires(read_lock))
1641 {
1642 if (*when == TimeKeeper::time_point::max())
1643 {
1644 x[jss::expiration] = "never";
1645 x[jss::status] = "active";
1646 }
1647 else
1648 {
1649 x[jss::expiration] = to_string(*when);
1650
1651 if (*when > timeKeeper_.now())
1652 x[jss::status] = "active";
1653 else
1654 x[jss::status] = "expired";
1655 }
1656 }
1657 else
1658 {
1659 x[jss::status] = "unknown";
1660 x[jss::expiration] = "unknown";
1661 }
1662
1663 x[jss::validator_list_threshold] = Json::UInt(listThreshold_);
1664 }
1665
1666 // Validator keys listed in the local config file
1667 Json::Value& jLocalStaticKeys =
1668 (res[jss::local_static_keys] = Json::arrayValue);
1669
1670 for (auto const& key : localPublisherList.list)
1671 jLocalStaticKeys.append(toBase58(TokenType::NodePublic, key));
1672
1673 // Publisher lists
1674 Json::Value& jPublisherLists =
1675 (res[jss::publisher_lists] = Json::arrayValue);
1676 for (auto const& [publicKey, pubCollection] : publisherLists_)
1677 {
1678 Json::Value& curr = jPublisherLists.append(Json::objectValue);
1679 curr[jss::pubkey_publisher] = strHex(publicKey);
1680 curr[jss::available] =
1681 pubCollection.status == PublisherStatus::available;
1682
1683 auto appendList = [](PublisherList const& publisherList,
1684 Json::Value& target) {
1685 target[jss::uri] = publisherList.siteUri;
1686 if (publisherList.validUntil != TimeKeeper::time_point{})
1687 {
1688 target[jss::seq] =
1689 static_cast<Json::UInt>(publisherList.sequence);
1690 target[jss::expiration] = to_string(publisherList.validUntil);
1691 }
1692 if (publisherList.validFrom != TimeKeeper::time_point{})
1693 target[jss::effective] = to_string(publisherList.validFrom);
1694 Json::Value& keys = (target[jss::list] = Json::arrayValue);
1695 for (auto const& key : publisherList.list)
1696 {
1697 keys.append(toBase58(TokenType::NodePublic, key));
1698 }
1699 };
1700 {
1701 auto const& current = pubCollection.current;
1702 appendList(current, curr);
1703 if (current.validUntil != TimeKeeper::time_point{})
1704 {
1705 curr[jss::version] = pubCollection.rawVersion;
1706 }
1707 }
1708
1709 Json::Value remaining(Json::arrayValue);
1710 for (auto const& [sequence, future] : pubCollection.remaining)
1711 {
1712 using namespace std::chrono_literals;
1713
1714 (void)sequence;
1715 Json::Value& r = remaining.append(Json::objectValue);
1716 appendList(future, r);
1717 // Race conditions can happen, so make this check "fuzzy"
1718 XRPL_ASSERT(
1719 future.validFrom > timeKeeper_.now() + 600s,
1720 "ripple::ValidatorList::getJson : minimum valid from");
1721 }
1722 if (remaining.size())
1723 curr[jss::remaining] = std::move(remaining);
1724 }
1725
1726 // Trusted validator keys
1727 Json::Value& jValidatorKeys =
1728 (res[jss::trusted_validator_keys] = Json::arrayValue);
1729 for (auto const& k : trustedMasterKeys_)
1730 {
1731 jValidatorKeys.append(toBase58(TokenType::NodePublic, k));
1732 }
1733
1734 // signing keys
1735 Json::Value& jSigningKeys = (res[jss::signing_keys] = Json::objectValue);
1736 validatorManifests_.for_each_manifest([&jSigningKeys,
1737 this](Manifest const& manifest) {
1738 auto it = keyListings_.find(manifest.masterKey);
1739 if (it != keyListings_.end() && manifest.signingKey)
1740 {
1741 jSigningKeys[toBase58(TokenType::NodePublic, manifest.masterKey)] =
1742 toBase58(TokenType::NodePublic, *manifest.signingKey);
1743 }
1744 });
1745
1746 // Negative UNL
1747 if (!negativeUNL_.empty())
1748 {
1749 Json::Value& jNegativeUNL = (res[jss::NegativeUNL] = Json::arrayValue);
1750 for (auto const& k : negativeUNL_)
1751 {
1752 jNegativeUNL.append(toBase58(TokenType::NodePublic, k));
1753 }
1754 }
1755
1756 return res;
1757}
1758
1759void
1760ValidatorList::for_each_listed(
1761 std::function<void(PublicKey const&, bool)> func) const
1762{
1763 std::shared_lock read_lock{mutex_};
1764
1765 for (auto const& v : keyListings_)
1766 func(v.first, trusted(read_lock, v.first));
1767}
1768
1769void
1770ValidatorList::for_each_available(
1771 std::function<void(
1772 std::string const& manifest,
1773 std::uint32_t version,
1775 PublicKey const& pubKey,
1776 std::size_t maxSequence,
1777 uint256 const& hash)> func) const
1778{
1779 std::shared_lock read_lock{mutex_};
1780
1781 for (auto const& [key, plCollection] : publisherLists_)
1782 {
1783 if (plCollection.status != PublisherStatus::available)
1784 continue;
1785 XRPL_ASSERT(
1786 plCollection.maxSequence != 0,
1787 "ripple::ValidatorList::for_each_available : nonzero maxSequence");
1788 func(
1789 plCollection.rawManifest,
1790 plCollection.rawVersion,
1791 buildBlobInfos(plCollection),
1792 key,
1793 plCollection.maxSequence.value_or(0),
1794 plCollection.fullHash);
1795 }
1796}
1797
1799ValidatorList::getAvailable(
1800 std::string_view pubKey,
1801 std::optional<std::uint32_t> forceVersion /* = {} */)
1802{
1803 std::shared_lock read_lock{mutex_};
1804
1805 auto const keyBlob = strViewUnHex(pubKey);
1806
1807 if (!keyBlob || !publicKeyType(makeSlice(*keyBlob)))
1808 {
1809 JLOG(j_.warn()) << "Invalid requested validator list publisher key: "
1810 << pubKey;
1811 return {};
1812 }
1813
1814 auto id = PublicKey(makeSlice(*keyBlob));
1815
1816 auto const iter = publisherLists_.find(id);
1817
1818 if (iter == publisherLists_.end() ||
1819 iter->second.status != PublisherStatus::available)
1820 return {};
1821
1822 Json::Value value =
1823 buildFileData(std::string{pubKey}, iter->second, forceVersion, j_);
1824
1825 return value;
1826}
1827
1829ValidatorList::calculateQuorum(
1830 std::size_t unlSize,
1831 std::size_t effectiveUnlSize,
1832 std::size_t seenSize)
1833{
1834 // Use quorum if specified via command line.
1835 if (minimumQuorum_ > 0)
1836 {
1837 JLOG(j_.warn()) << "Using potentially unsafe quorum of "
1838 << *minimumQuorum_
1839 << " as specified on the command line";
1840 return *minimumQuorum_;
1841 }
1842
1843 if (!publisherLists_.empty())
1844 {
1845 // Do not use achievable quorum until lists from a sufficient number of
1846 // configured publishers are available
1848 for (auto const& list : publisherLists_)
1849 {
1850 if (list.second.status != PublisherStatus::available)
1851 unavailable += 1;
1852 }
1853 // There are two, subtly different, sides to list threshold:
1854 //
1855 // 1. The minimum required intersection between lists listThreshold_
1856 // for a validator to be included in trustedMasterKeys_.
1857 // If this many (or more) publishers are unavailable, we are likely
1858 // to NOT include a validator which otherwise would have been used.
1859 // We disable quorum if this happens.
1860 // 2. The minimum number of publishers which, when unavailable, will
1861 // prevent us from hitting the above threshold on ANY validator.
1862 // This is calculated as:
1863 // N - M + 1
1864 // where
1865 // N: number of publishers i.e. publisherLists_.size()
1866 // M: minimum required intersection i.e. listThreshold_
1867 // If this happens, we still have this local validator and we do not
1868 // want it to form a quorum of 1, so we disable quorum as well.
1869 //
1870 // We disable quorum if the number of unavailable publishers exceeds
1871 // either of the above thresholds
1872 auto const errorThreshold = std::min(
1873 listThreshold_, //
1874 publisherLists_.size() - listThreshold_ + 1);
1875 XRPL_ASSERT(
1876 errorThreshold > 0,
1877 "ripple::ValidatorList::calculateQuorum : nonzero error threshold");
1878 if (unavailable >= errorThreshold)
1880 }
1881
1882 // Use an 80% quorum to balance fork safety, liveness, and required UNL
1883 // overlap.
1884 //
1885 // Theorem 8 of the Analysis of the XRP Ledger Consensus Protocol
1886 // (https://arxiv.org/abs/1802.07242) says:
1887 // XRP LCP guarantees fork safety if Oi,j > nj/2 + ni − qi + ti,j
1888 // for every pair of nodes Pi, Pj.
1889 //
1890 // ni: size of Pi's UNL
1891 // nj: size of Pj's UNL
1892 // Oi,j: number of validators in both UNLs
1893 // qi: validation quorum for Pi's UNL
1894 // ti, tj: maximum number of allowed Byzantine faults in Pi and Pj's
1895 // UNLs ti,j: min{ti, tj, Oi,j}
1896 //
1897 // Assume ni < nj, meaning and ti,j = ti
1898 //
1899 // For qi = .8*ni, we make ti <= .2*ni
1900 // (We could make ti lower and tolerate less UNL overlap. However in
1901 // order to prioritize safety over liveness, we need ti >= ni - qi)
1902 //
1903 // An 80% quorum allows two UNLs to safely have < .2*ni unique
1904 // validators between them:
1905 //
1906 // pi = ni - Oi,j
1907 // pj = nj - Oi,j
1908 //
1909 // Oi,j > nj/2 + ni − qi + ti,j
1910 // ni - pi > (ni - pi + pj)/2 + ni − .8*ni + .2*ni
1911 // pi + pj < .2*ni
1912 //
1913 // Note that the negative UNL protocol introduced the
1914 // AbsoluteMinimumQuorum which is 60% of the original UNL size. The
1915 // effective quorum should not be lower than it.
1916 return static_cast<std::size_t>(std::max(
1917 std::ceil(effectiveUnlSize * 0.8f), std::ceil(unlSize * 0.6f)));
1918}
1919
1921ValidatorList::updateTrusted(
1922 hash_set<NodeID> const& seenValidators,
1923 NetClock::time_point closeTime,
1924 NetworkOPs& ops,
1925 Overlay& overlay,
1926 HashRouter& hashRouter)
1927{
1928 using namespace std::chrono_literals;
1929 if (timeKeeper_.now() > closeTime + 30s)
1930 closeTime = timeKeeper_.now();
1931
1932 std::lock_guard lock{mutex_};
1933
1934 // Rotate pending and remove expired published lists
1935 bool good = true;
1936 // localPublisherList is not processed here. This is because the
1937 // Validators specified in the local config file do not expire nor do
1938 // they have a "remaining" section of PublisherList.
1939 for (auto& [pubKey, collection] : publisherLists_)
1940 {
1941 {
1942 auto& remaining = collection.remaining;
1943 auto const firstIter = remaining.begin();
1944 auto iter = firstIter;
1945 if (iter != remaining.end() && iter->second.validFrom <= closeTime)
1946 {
1947 // Find the LAST candidate that is ready to go live.
1948 for (auto next = std::next(iter); next != remaining.end() &&
1949 next->second.validFrom <= closeTime;
1950 ++iter, ++next)
1951 {
1952 XRPL_ASSERT(
1953 std::next(iter) == next,
1954 "ripple::ValidatorList::updateTrusted : sequential "
1955 "remaining");
1956 }
1957 XRPL_ASSERT(
1958 iter != remaining.end(),
1959 "ripple::ValidatorList::updateTrusted : non-end of "
1960 "remaining");
1961
1962 // Rotate the pending list in to current
1963 auto sequence = iter->first;
1964 auto& candidate = iter->second;
1965 auto& current = collection.current;
1966 XRPL_ASSERT(
1967 candidate.validFrom <= closeTime,
1968 "ripple::ValidatorList::updateTrusted : maximum time");
1969
1970 auto const oldList = current.list;
1971 current = std::move(candidate);
1972 if (collection.status != PublisherStatus::available)
1973 collection.status = PublisherStatus::available;
1974 XRPL_ASSERT(
1975 current.sequence == sequence,
1976 "ripple::ValidatorList::updateTrusted : sequence match");
1977 // If the list is expired, remove the validators so they don't
1978 // get processed in. The expiration check below will do the rest
1979 // of the work
1980 if (current.validUntil <= closeTime)
1981 current.list.clear();
1982
1983 updatePublisherList(pubKey, current, oldList, lock);
1984
1985 // Only broadcast the current, which will consequently only
1986 // send to peers that don't understand v2, or which are
1987 // unknown (unlikely). Those that do understand v2 should
1988 // already have this list and are in the process of
1989 // switching themselves.
1990 broadcastBlobs(
1991 pubKey,
1992 collection,
1993 sequence,
1994 current.hash,
1995 overlay,
1996 hashRouter,
1997 j_);
1998
1999 // Erase any candidates that we skipped over, plus this one
2000 remaining.erase(firstIter, std::next(iter));
2001 }
2002 }
2003 // Remove if expired
2004 // ValidatorLists specified in the local config file never expire.
2005 // Hence, the below steps are not relevant for localPublisherList
2006 if (collection.status == PublisherStatus::available &&
2007 collection.current.validUntil <= closeTime)
2008 {
2009 removePublisherList(lock, pubKey, PublisherStatus::expired);
2010 ops.setUNLBlocked();
2011 }
2012 if (collection.status != PublisherStatus::available)
2013 good = false;
2014 }
2015 if (good)
2016 ops.clearUNLBlocked();
2017
2018 TrustChanges trustChanges;
2019
2020 auto it = trustedMasterKeys_.cbegin();
2021 while (it != trustedMasterKeys_.cend())
2022 {
2023 auto const kit = keyListings_.find(*it);
2024 if (kit == keyListings_.end() || //
2025 kit->second < listThreshold_ || //
2026 validatorManifests_.revoked(*it))
2027 {
2028 trustChanges.removed.insert(calcNodeID(*it));
2029 it = trustedMasterKeys_.erase(it);
2030 }
2031 else
2032 {
2033 XRPL_ASSERT(
2034 kit->second >= listThreshold_,
2035 "ripple::ValidatorList::updateTrusted : count meets threshold");
2036 ++it;
2037 }
2038 }
2039
2040 for (auto const& val : keyListings_)
2041 {
2042 if (val.second >= listThreshold_ &&
2043 !validatorManifests_.revoked(val.first) &&
2044 trustedMasterKeys_.emplace(val.first).second)
2045 trustChanges.added.insert(calcNodeID(val.first));
2046 }
2047
2048 // If there were any changes, we need to update the ephemeral signing
2049 // keys:
2050 if (!trustChanges.added.empty() || !trustChanges.removed.empty())
2051 {
2052 trustedSigningKeys_.clear();
2053
2054 // trustedMasterKeys_ contain non-revoked manifests only. Hence the
2055 // manifests must contain a valid signingKey
2056 for (auto const& k : trustedMasterKeys_)
2057 {
2058 std::optional<PublicKey> const signingKey =
2059 validatorManifests_.getSigningKey(k);
2060 XRPL_ASSERT(
2061 signingKey,
2062 "ripple::ValidatorList::updateTrusted : found signing key");
2063 trustedSigningKeys_.insert(*signingKey);
2064 }
2065 }
2066
2067 JLOG(j_.debug())
2068 << trustedMasterKeys_.size() << " of " << keyListings_.size()
2069 << " listed validators eligible for inclusion in the trusted set";
2070
2071 auto const unlSize = trustedMasterKeys_.size();
2072 auto effectiveUnlSize = unlSize;
2073 auto seenSize = seenValidators.size();
2074 if (!negativeUNL_.empty())
2075 {
2076 for (auto const& k : trustedMasterKeys_)
2077 {
2078 if (negativeUNL_.count(k))
2079 --effectiveUnlSize;
2080 }
2081 hash_set<NodeID> negUnlNodeIDs;
2082 for (auto const& k : negativeUNL_)
2083 {
2084 negUnlNodeIDs.emplace(calcNodeID(k));
2085 }
2086 for (auto const& nid : seenValidators)
2087 {
2088 if (negUnlNodeIDs.count(nid))
2089 --seenSize;
2090 }
2091 }
2092 quorum_ = calculateQuorum(unlSize, effectiveUnlSize, seenSize);
2093
2094 JLOG(j_.debug()) << "Using quorum of " << quorum_ << " for new set of "
2095 << unlSize << " trusted validators ("
2096 << trustChanges.added.size() << " added, "
2097 << trustChanges.removed.size() << " removed)";
2098
2099 if (unlSize < quorum_)
2100 {
2101 JLOG(j_.warn()) << "New quorum of " << quorum_
2102 << " exceeds the number of trusted validators ("
2103 << unlSize << ")";
2104 }
2105
2106 if ((publisherLists_.size() || localPublisherList.list.size()) &&
2107 unlSize == 0)
2108 {
2109 // No validators. Lock down.
2110 ops.setUNLBlocked();
2111 }
2112
2113 return trustChanges;
2114}
2115
2117ValidatorList::getTrustedMasterKeys() const
2118{
2119 std::shared_lock read_lock{mutex_};
2120 return trustedMasterKeys_;
2121}
2122
2124ValidatorList::getListThreshold() const
2125{
2126 std::shared_lock read_lock{mutex_};
2127 return listThreshold_;
2128}
2129
2131ValidatorList::getNegativeUNL() const
2132{
2133 std::shared_lock read_lock{mutex_};
2134 return negativeUNL_;
2135}
2136
2137void
2138ValidatorList::setNegativeUNL(hash_set<PublicKey> const& negUnl)
2139{
2140 std::lock_guard lock{mutex_};
2141 negativeUNL_ = negUnl;
2142}
2143
2145ValidatorList::negativeUNLFilter(
2146 std::vector<std::shared_ptr<STValidation>>&& validations) const
2147{
2148 // Remove validations that are from validators on the negative UNL.
2149 auto ret = std::move(validations);
2150
2151 std::shared_lock read_lock{mutex_};
2152 if (!negativeUNL_.empty())
2153 {
2154 ret.erase(
2156 ret.begin(),
2157 ret.end(),
2158 [&](auto const& v) -> bool {
2159 if (auto const masterKey =
2160 getTrustedKey(read_lock, v->getSignerPublic());
2161 masterKey)
2162 {
2163 return negativeUNL_.count(*masterKey);
2164 }
2165 else
2166 {
2167 return false;
2168 }
2169 }),
2170 ret.end());
2171 }
2172
2173 return ret;
2174}
2175
2176} // 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
std::string to_string() const
Returns a string representing the endpoint.
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 beast::IP::Endpoint getRemoteAddress() const =0
virtual void send(std::shared_ptr< Message > const &m)=0
virtual void setPublisherListSequence(PublicKey const &, std::size_t const)=0
virtual id_t id() const =0
A public key.
Definition PublicKey.h:61
std::size_t size() const noexcept
Definition PublicKey.h:92
const_iterator end() const noexcept
Definition PublicKey.h:110
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