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
72{
73 ++dispositions[d];
74}
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(
576 std::make_shared<Message>(
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;
1152 auto [result, pubKeyOpt] = verify(lock, list, manifest, blob, signature);
1153
1154 if (!pubKeyOpt)
1155 {
1156 JLOG(j_.info()) << "ValidatorList::applyList unable to retrieve the "
1157 "master public key from the verify function\n";
1158 return PublisherListStats{result};
1159 }
1160
1161 if (!publicKeyType(*pubKeyOpt))
1162 {
1163 JLOG(j_.info()) << "ValidatorList::applyList Invalid Public Key type"
1164 " retrieved from the verify function\n ";
1165 return PublisherListStats{result};
1166 }
1167
1168 PublicKey pubKey = *pubKeyOpt;
1169 if (result > ListDisposition::pending)
1170 {
1171 if (publisherLists_.count(pubKey))
1172 {
1173 auto const& pubCollection = publisherLists_[pubKey];
1174 if (pubCollection.maxSequence &&
1175 (result == ListDisposition::same_sequence ||
1177 {
1178 // We've seen something valid list for this publisher
1179 // already, so return what we know about it.
1180 return PublisherListStats{
1181 result,
1182 pubKey,
1183 pubCollection.status,
1184 *pubCollection.maxSequence};
1185 }
1186 }
1187 return PublisherListStats{result};
1188 }
1189
1190 // Update publisher's list
1191 auto& pubCollection = publisherLists_[pubKey];
1192 auto const sequence = list[jss::sequence].asUInt();
1193 auto const accepted =
1194 (result == ListDisposition::accepted ||
1195 result == ListDisposition::expired);
1196
1197 if (accepted)
1198 pubCollection.status = result == ListDisposition::accepted
1201 pubCollection.rawManifest = globalManifest;
1202 if (!pubCollection.maxSequence || sequence > *pubCollection.maxSequence)
1203 pubCollection.maxSequence = sequence;
1204
1205 Json::Value const& newList = list[jss::validators];
1206 std::vector<PublicKey> oldList;
1207 if (accepted && pubCollection.remaining.count(sequence) != 0)
1208 {
1209 // We've seen this list before and stored it in "remaining". The
1210 // normal expected process is that the processed list would have
1211 // already been moved in to "current" by "updateTrusted()", but race
1212 // conditions are possible, or the node may have lost sync, so do
1213 // some of that work here.
1214 auto& publisher = pubCollection.current;
1215 // Copy the old validator list
1216 oldList = std::move(pubCollection.current.list);
1217 // Move the publisher info from "remaining" to "current"
1218 publisher = std::move(pubCollection.remaining[sequence]);
1219 // Remove the entry in "remaining"
1220 pubCollection.remaining.erase(sequence);
1221 // Done
1222 XRPL_ASSERT(
1223 publisher.sequence == sequence,
1224 "ripple::ValidatorList::applyList : publisher sequence match");
1225 }
1226 else
1227 {
1228 auto& publisher = accepted ? pubCollection.current
1229 : pubCollection.remaining[sequence];
1230 publisher.sequence = sequence;
1231 publisher.validFrom = TimeKeeper::time_point{TimeKeeper::duration{
1232 list.isMember(jss::effective) ? list[jss::effective].asUInt() : 0}};
1233 publisher.validUntil = TimeKeeper::time_point{
1234 TimeKeeper::duration{list[jss::expiration].asUInt()}};
1235 publisher.siteUri = std::move(siteUri);
1236 publisher.rawBlob = blob;
1237 publisher.rawSignature = signature;
1238 publisher.rawManifest = localManifest;
1239 if (hash)
1240 publisher.hash = *hash;
1241
1242 std::vector<PublicKey>& publisherList = publisher.list;
1243 std::vector<std::string>& manifests = publisher.manifests;
1244
1245 // Copy the old validator list
1246 oldList = std::move(publisherList);
1247 // Build the new validator list from "newList"
1248 publisherList.clear();
1249 publisherList.reserve(newList.size());
1250 for (auto const& val : newList)
1251 {
1252 if (val.isObject() && val.isMember(jss::validation_public_key) &&
1253 val[jss::validation_public_key].isString())
1254 {
1255 std::optional<Blob> const ret =
1256 strUnHex(val[jss::validation_public_key].asString());
1257
1258 if (!ret || !publicKeyType(makeSlice(*ret)))
1259 {
1260 JLOG(j_.error())
1261 << "Invalid node identity: "
1262 << val[jss::validation_public_key].asString();
1263 }
1264 else
1265 {
1266 publisherList.push_back(
1267 PublicKey(Slice{ret->data(), ret->size()}));
1268 }
1269
1270 if (val.isMember(jss::manifest) &&
1271 val[jss::manifest].isString())
1272 manifests.push_back(val[jss::manifest].asString());
1273 }
1274 }
1275
1276 // Standardize the list order by sorting
1277 std::sort(publisherList.begin(), publisherList.end());
1278 }
1279 // If this publisher has ever sent a more updated version than the one
1280 // in this file, keep it. This scenario is unlikely, but legal.
1281 pubCollection.rawVersion = std::max(pubCollection.rawVersion, version);
1282 if (!pubCollection.remaining.empty())
1283 {
1284 // If there are any pending VLs, then this collection must be at least
1285 // version 2.
1286 pubCollection.rawVersion = std::max(pubCollection.rawVersion, 2u);
1287 }
1288
1289 PublisherListStats const applyResult{
1290 result, pubKey, pubCollection.status, *pubCollection.maxSequence};
1291
1292 if (accepted)
1293 {
1294 updatePublisherList(pubKey, pubCollection.current, oldList, lock);
1295 }
1296
1297 return applyResult;
1298}
1299
1302{
1303 using namespace std::string_literals;
1304 using namespace boost::filesystem;
1305 using namespace boost::system::errc;
1306
1307 std::lock_guard lock{mutex_};
1308
1310 sites.reserve(publisherLists_.size());
1311 for (auto const& [pubKey, publisherCollection] : publisherLists_)
1312 {
1313 boost::system::error_code ec;
1314
1315 if (publisherCollection.status == PublisherStatus::available)
1316 continue;
1317
1318 boost::filesystem::path const filename = getCacheFileName(lock, pubKey);
1319
1320 auto const fullPath{canonical(filename, ec)};
1321 if (ec)
1322 continue;
1323
1324 auto size = file_size(fullPath, ec);
1325 if (!ec && !size)
1326 {
1327 // Treat an empty file as a missing file, because
1328 // nobody else is going to write it.
1329 ec = make_error_code(no_such_file_or_directory);
1330 }
1331 if (ec)
1332 continue;
1333
1334 std::string const prefix = [&fullPath]() {
1335#if _MSC_VER // MSVC: Windows paths need a leading / added
1336 {
1337 return fullPath.root_path() == "/"s ? "file://" : "file:///";
1338 }
1339#else
1340 {
1341 (void)fullPath;
1342 return "file://";
1343 }
1344#endif
1345 }();
1346 sites.emplace_back(prefix + fullPath.string());
1347 }
1348
1349 // Then let the ValidatorSites do the rest of the work.
1350 return sites;
1351}
1352
1353// The returned PublicKey value is read from the manifest. Manifests do not
1354// contain the default-constructed public keys
1357 ValidatorList::lock_guard const& lock,
1358 Json::Value& list,
1359 std::string const& manifest,
1360 std::string const& blob,
1361 std::string const& signature)
1362{
1364
1365 if (!m || !publisherLists_.count(m->masterKey))
1366 return {ListDisposition::untrusted, {}};
1367
1368 PublicKey masterPubKey = m->masterKey;
1369 auto const revoked = m->revoked();
1370
1371 auto const result = publisherManifests_.applyManifest(std::move(*m));
1372
1373 if (revoked && result == ManifestDisposition::accepted)
1374 {
1375 removePublisherList(lock, masterPubKey, PublisherStatus::revoked);
1376 // If the manifest is revoked, no future list is valid either
1377 publisherLists_[masterPubKey].remaining.clear();
1378 }
1379
1380 auto const signingKey = publisherManifests_.getSigningKey(masterPubKey);
1381
1382 if (revoked || !signingKey || result == ManifestDisposition::invalid)
1383 return {ListDisposition::untrusted, masterPubKey};
1384
1385 auto const sig = strUnHex(signature);
1386 auto const data = base64_decode(blob);
1387 if (!sig || !ripple::verify(*signingKey, makeSlice(data), makeSlice(*sig)))
1388 return {ListDisposition::invalid, masterPubKey};
1389
1390 Json::Reader r;
1391 if (!r.parse(data, list))
1392 return {ListDisposition::invalid, masterPubKey};
1393
1394 if (list.isMember(jss::sequence) && list[jss::sequence].isInt() &&
1395 list.isMember(jss::expiration) && list[jss::expiration].isInt() &&
1396 (!list.isMember(jss::effective) || list[jss::effective].isInt()) &&
1397 list.isMember(jss::validators) && list[jss::validators].isArray())
1398 {
1399 auto const sequence = list[jss::sequence].asUInt();
1400 auto const validFrom = TimeKeeper::time_point{TimeKeeper::duration{
1401 list.isMember(jss::effective) ? list[jss::effective].asUInt() : 0}};
1402 auto const validUntil = TimeKeeper::time_point{
1403 TimeKeeper::duration{list[jss::expiration].asUInt()}};
1404 auto const now = timeKeeper_.now();
1405 auto const& listCollection = publisherLists_[masterPubKey];
1406 if (validUntil <= validFrom)
1407 return {ListDisposition::invalid, masterPubKey};
1408 else if (sequence < listCollection.current.sequence)
1409 return {ListDisposition::stale, masterPubKey};
1410 else if (sequence == listCollection.current.sequence)
1411 return {ListDisposition::same_sequence, masterPubKey};
1412 else if (validUntil <= now)
1413 return {ListDisposition::expired, masterPubKey};
1414 else if (validFrom > now)
1415 // Not yet valid. Return pending if one of the following is true
1416 // * There's no maxSequence, indicating this is the first blob seen
1417 // for this publisher
1418 // * The sequence is larger than the maxSequence, indicating this
1419 // blob is new
1420 // * There's no entry for this sequence AND this blob is valid
1421 // before the last blob, indicating blobs may be processing out of
1422 // order. This may result in some duplicated processing, but
1423 // prevents the risk of missing valid data. Else return
1424 // known_sequence
1425 return !listCollection.maxSequence ||
1426 sequence > *listCollection.maxSequence ||
1427 (listCollection.remaining.count(sequence) == 0 &&
1428 validFrom < listCollection.remaining
1429 .at(*listCollection.maxSequence)
1430 .validFrom)
1433 }
1434 else
1435 {
1436 return {ListDisposition::invalid, masterPubKey};
1437 }
1438
1439 return {ListDisposition::accepted, masterPubKey};
1440}
1441
1442bool
1443ValidatorList::listed(PublicKey const& identity) const
1444{
1445 std::shared_lock read_lock{mutex_};
1446
1447 auto const pubKey = validatorManifests_.getMasterKey(identity);
1448 return keyListings_.find(pubKey) != keyListings_.end();
1449}
1450
1451bool
1454 PublicKey const& identity) const
1455{
1456 auto const pubKey = validatorManifests_.getMasterKey(identity);
1457 return trustedMasterKeys_.find(pubKey) != trustedMasterKeys_.end();
1458}
1459
1460bool
1461ValidatorList::trusted(PublicKey const& identity) const
1462{
1463 std::shared_lock read_lock{mutex_};
1464 return trusted(read_lock, identity);
1465}
1466
1469{
1470 std::shared_lock read_lock{mutex_};
1471
1472 auto const pubKey = validatorManifests_.getMasterKey(identity);
1473 if (keyListings_.find(pubKey) != keyListings_.end())
1474 return pubKey;
1475 return std::nullopt;
1476}
1477
1481 PublicKey const& identity) const
1482{
1483 auto const pubKey = validatorManifests_.getMasterKey(identity);
1484 if (trustedMasterKeys_.find(pubKey) != trustedMasterKeys_.end())
1485 return pubKey;
1486 return std::nullopt;
1487}
1488
1491{
1492 std::shared_lock read_lock{mutex_};
1493
1494 return getTrustedKey(read_lock, identity);
1495}
1496
1497bool
1499{
1500 std::shared_lock read_lock{mutex_};
1501 return identity.size() && publisherLists_.count(identity) &&
1502 publisherLists_.at(identity).status < PublisherStatus::revoked;
1503}
1504
1507{
1508 std::shared_lock read_lock{mutex_};
1509 return localPubKey_;
1510}
1511
1512bool
1515 PublicKey const& publisherKey,
1516 PublisherStatus reason)
1517{
1518 XRPL_ASSERT(
1519 reason != PublisherStatus::available &&
1521 "ripple::ValidatorList::removePublisherList : valid reason input");
1522 auto const iList = publisherLists_.find(publisherKey);
1523 if (iList == publisherLists_.end())
1524 return false;
1525
1526 JLOG(j_.debug()) << "Removing validator list for publisher "
1527 << strHex(publisherKey);
1528
1529 for (auto const& val : iList->second.current.list)
1530 {
1531 auto const& iVal = keyListings_.find(val);
1532 if (iVal == keyListings_.end())
1533 continue;
1534
1535 if (iVal->second <= 1)
1536 keyListings_.erase(iVal);
1537 else
1538 --iVal->second;
1539 }
1540
1541 iList->second.current.list.clear();
1542 iList->second.status = reason;
1543
1544 return true;
1545}
1546
1549{
1550 return publisherLists_.size() + (localPublisherList.list.size() > 0);
1551}
1552
1555{
1556 std::shared_lock read_lock{mutex_};
1557 return count(read_lock);
1558}
1559
1562{
1564 for (auto const& [_, collection] : publisherLists_)
1565 {
1566 // Unfetched
1567 auto const& current = collection.current;
1568 if (current.validUntil == TimeKeeper::time_point{})
1569 {
1570 return std::nullopt;
1571 }
1572
1573 // Find the latest validUntil in a chain where the next validFrom
1574 // overlaps with the previous validUntil. applyLists has already cleaned
1575 // up the list so the validFrom dates are guaranteed increasing.
1576 auto chainedExpiration = current.validUntil;
1577 for (auto const& [sequence, check] : collection.remaining)
1578 {
1579 (void)sequence;
1580 if (check.validFrom <= chainedExpiration)
1581 chainedExpiration = check.validUntil;
1582 else
1583 break;
1584 }
1585
1586 // Earliest
1587 if (!res || chainedExpiration < *res)
1588 {
1589 res = chainedExpiration;
1590 }
1591 }
1592
1593 if (localPublisherList.list.size() > 0)
1594 {
1595 PublisherList collection = localPublisherList;
1596 // Unfetched
1597 auto const& current = collection;
1598 auto chainedExpiration = current.validUntil;
1599
1600 // Earliest
1601 if (!res || chainedExpiration < *res)
1602 {
1603 res = chainedExpiration;
1604 }
1605 }
1606 return res;
1607}
1608
1611{
1612 std::shared_lock read_lock{mutex_};
1613 return expires(read_lock);
1614}
1615
1618{
1620
1621 std::shared_lock read_lock{mutex_};
1622
1623 res[jss::validation_quorum] = static_cast<Json::UInt>(quorum_);
1624
1625 {
1626 auto& x = (res[jss::validator_list] = Json::objectValue);
1627
1628 x[jss::count] = static_cast<Json::UInt>(count(read_lock));
1629
1630 if (auto when = expires(read_lock))
1631 {
1632 if (*when == TimeKeeper::time_point::max())
1633 {
1634 x[jss::expiration] = "never";
1635 x[jss::status] = "active";
1636 }
1637 else
1638 {
1639 x[jss::expiration] = to_string(*when);
1640
1641 if (*when > timeKeeper_.now())
1642 x[jss::status] = "active";
1643 else
1644 x[jss::status] = "expired";
1645 }
1646 }
1647 else
1648 {
1649 x[jss::status] = "unknown";
1650 x[jss::expiration] = "unknown";
1651 }
1652
1653 x[jss::validator_list_threshold] = Json::UInt(listThreshold_);
1654 }
1655
1656 // Validator keys listed in the local config file
1657 Json::Value& jLocalStaticKeys =
1658 (res[jss::local_static_keys] = Json::arrayValue);
1659
1660 for (auto const& key : localPublisherList.list)
1661 jLocalStaticKeys.append(toBase58(TokenType::NodePublic, key));
1662
1663 // Publisher lists
1664 Json::Value& jPublisherLists =
1665 (res[jss::publisher_lists] = Json::arrayValue);
1666 for (auto const& [publicKey, pubCollection] : publisherLists_)
1667 {
1668 Json::Value& curr = jPublisherLists.append(Json::objectValue);
1669 curr[jss::pubkey_publisher] = strHex(publicKey);
1670 curr[jss::available] =
1671 pubCollection.status == PublisherStatus::available;
1672
1673 auto appendList = [](PublisherList const& publisherList,
1674 Json::Value& target) {
1675 target[jss::uri] = publisherList.siteUri;
1676 if (publisherList.validUntil != TimeKeeper::time_point{})
1677 {
1678 target[jss::seq] =
1679 static_cast<Json::UInt>(publisherList.sequence);
1680 target[jss::expiration] = to_string(publisherList.validUntil);
1681 }
1682 if (publisherList.validFrom != TimeKeeper::time_point{})
1683 target[jss::effective] = to_string(publisherList.validFrom);
1684 Json::Value& keys = (target[jss::list] = Json::arrayValue);
1685 for (auto const& key : publisherList.list)
1686 {
1688 }
1689 };
1690 {
1691 auto const& current = pubCollection.current;
1692 appendList(current, curr);
1693 if (current.validUntil != TimeKeeper::time_point{})
1694 {
1695 curr[jss::version] = pubCollection.rawVersion;
1696 }
1697 }
1698
1699 Json::Value remaining(Json::arrayValue);
1700 for (auto const& [sequence, future] : pubCollection.remaining)
1701 {
1702 using namespace std::chrono_literals;
1703
1704 (void)sequence;
1705 Json::Value& r = remaining.append(Json::objectValue);
1706 appendList(future, r);
1707 // Race conditions can happen, so make this check "fuzzy"
1708 XRPL_ASSERT(
1709 future.validFrom > timeKeeper_.now() + 600s,
1710 "ripple::ValidatorList::getJson : minimum valid from");
1711 }
1712 if (remaining.size())
1713 curr[jss::remaining] = std::move(remaining);
1714 }
1715
1716 // Trusted validator keys
1717 Json::Value& jValidatorKeys =
1718 (res[jss::trusted_validator_keys] = Json::arrayValue);
1719 for (auto const& k : trustedMasterKeys_)
1720 {
1721 jValidatorKeys.append(toBase58(TokenType::NodePublic, k));
1722 }
1723
1724 // signing keys
1725 Json::Value& jSigningKeys = (res[jss::signing_keys] = Json::objectValue);
1726 validatorManifests_.for_each_manifest([&jSigningKeys,
1727 this](Manifest const& manifest) {
1728 auto it = keyListings_.find(manifest.masterKey);
1729 if (it != keyListings_.end() && manifest.signingKey)
1730 {
1731 jSigningKeys[toBase58(TokenType::NodePublic, manifest.masterKey)] =
1733 }
1734 });
1735
1736 // Negative UNL
1737 if (!negativeUNL_.empty())
1738 {
1739 Json::Value& jNegativeUNL = (res[jss::NegativeUNL] = Json::arrayValue);
1740 for (auto const& k : negativeUNL_)
1741 {
1742 jNegativeUNL.append(toBase58(TokenType::NodePublic, k));
1743 }
1744 }
1745
1746 return res;
1747}
1748
1749void
1750ValidatorList::for_each_listed(
1751 std::function<void(PublicKey const&, bool)> func) const
1752{
1753 std::shared_lock read_lock{mutex_};
1754
1755 for (auto const& v : keyListings_)
1756 func(v.first, trusted(read_lock, v.first));
1757}
1758
1759void
1760ValidatorList::for_each_available(
1761 std::function<void(
1762 std::string const& manifest,
1763 std::uint32_t version,
1765 PublicKey const& pubKey,
1766 std::size_t maxSequence,
1767 uint256 const& hash)> func) const
1768{
1769 std::shared_lock read_lock{mutex_};
1770
1771 for (auto const& [key, plCollection] : publisherLists_)
1772 {
1773 if (plCollection.status != PublisherStatus::available)
1774 continue;
1775 XRPL_ASSERT(
1776 plCollection.maxSequence != 0,
1777 "ripple::ValidatorList::for_each_available : nonzero maxSequence");
1778 func(
1779 plCollection.rawManifest,
1780 plCollection.rawVersion,
1781 buildBlobInfos(plCollection),
1782 key,
1783 plCollection.maxSequence.value_or(0),
1784 plCollection.fullHash);
1785 }
1786}
1787
1789ValidatorList::getAvailable(
1790 std::string_view pubKey,
1791 std::optional<std::uint32_t> forceVersion /* = {} */)
1792{
1793 std::shared_lock read_lock{mutex_};
1794
1795 auto const keyBlob = strViewUnHex(pubKey);
1796
1797 if (!keyBlob || !publicKeyType(makeSlice(*keyBlob)))
1798 {
1799 JLOG(j_.info()) << "Invalid requested validator list publisher key: "
1800 << pubKey;
1801 return {};
1802 }
1803
1804 auto id = PublicKey(makeSlice(*keyBlob));
1805
1806 auto const iter = publisherLists_.find(id);
1807
1808 if (iter == publisherLists_.end() ||
1809 iter->second.status != PublisherStatus::available)
1810 return {};
1811
1812 Json::Value value =
1813 buildFileData(std::string{pubKey}, iter->second, forceVersion, j_);
1814
1815 return value;
1816}
1817
1819ValidatorList::calculateQuorum(
1820 std::size_t unlSize,
1821 std::size_t effectiveUnlSize,
1822 std::size_t seenSize)
1823{
1824 // Use quorum if specified via command line.
1825 if (minimumQuorum_ > 0)
1826 {
1827 JLOG(j_.warn()) << "Using potentially unsafe quorum of "
1828 << *minimumQuorum_
1829 << " as specified on the command line";
1830 return *minimumQuorum_;
1831 }
1832
1833 if (!publisherLists_.empty())
1834 {
1835 // Do not use achievable quorum until lists from a sufficient number of
1836 // configured publishers are available
1838 for (auto const& list : publisherLists_)
1839 {
1840 if (list.second.status != PublisherStatus::available)
1841 unavailable += 1;
1842 }
1843 // There are two, subtly different, sides to list threshold:
1844 //
1845 // 1. The minimum required intersection between lists listThreshold_
1846 // for a validator to be included in trustedMasterKeys_.
1847 // If this many (or more) publishers are unavailable, we are likely
1848 // to NOT include a validator which otherwise would have been used.
1849 // We disable quorum if this happens.
1850 // 2. The minimum number of publishers which, when unavailable, will
1851 // prevent us from hitting the above threshold on ANY validator.
1852 // This is calculated as:
1853 // N - M + 1
1854 // where
1855 // N: number of publishers i.e. publisherLists_.size()
1856 // M: minimum required intersection i.e. listThreshold_
1857 // If this happens, we still have this local validator and we do not
1858 // want it to form a quorum of 1, so we disable quorum as well.
1859 //
1860 // We disable quorum if the number of unavailable publishers exceeds
1861 // either of the above thresholds
1862 auto const errorThreshold = std::min(
1863 listThreshold_, //
1864 publisherLists_.size() - listThreshold_ + 1);
1865 XRPL_ASSERT(
1866 errorThreshold > 0,
1867 "ripple::ValidatorList::calculateQuorum : nonzero error threshold");
1868 if (unavailable >= errorThreshold)
1870 }
1871
1872 // Use an 80% quorum to balance fork safety, liveness, and required UNL
1873 // overlap.
1874 //
1875 // Theorem 8 of the Analysis of the XRP Ledger Consensus Protocol
1876 // (https://arxiv.org/abs/1802.07242) says:
1877 // XRP LCP guarantees fork safety if Oi,j > nj/2 + ni − qi + ti,j
1878 // for every pair of nodes Pi, Pj.
1879 //
1880 // ni: size of Pi's UNL
1881 // nj: size of Pj's UNL
1882 // Oi,j: number of validators in both UNLs
1883 // qi: validation quorum for Pi's UNL
1884 // ti, tj: maximum number of allowed Byzantine faults in Pi and Pj's
1885 // UNLs ti,j: min{ti, tj, Oi,j}
1886 //
1887 // Assume ni < nj, meaning and ti,j = ti
1888 //
1889 // For qi = .8*ni, we make ti <= .2*ni
1890 // (We could make ti lower and tolerate less UNL overlap. However in
1891 // order to prioritize safety over liveness, we need ti >= ni - qi)
1892 //
1893 // An 80% quorum allows two UNLs to safely have < .2*ni unique
1894 // validators between them:
1895 //
1896 // pi = ni - Oi,j
1897 // pj = nj - Oi,j
1898 //
1899 // Oi,j > nj/2 + ni − qi + ti,j
1900 // ni - pi > (ni - pi + pj)/2 + ni − .8*ni + .2*ni
1901 // pi + pj < .2*ni
1902 //
1903 // Note that the negative UNL protocol introduced the
1904 // AbsoluteMinimumQuorum which is 60% of the original UNL size. The
1905 // effective quorum should not be lower than it.
1906 return static_cast<std::size_t>(std::max(
1907 std::ceil(effectiveUnlSize * 0.8f), std::ceil(unlSize * 0.6f)));
1908}
1909
1911ValidatorList::updateTrusted(
1912 hash_set<NodeID> const& seenValidators,
1913 NetClock::time_point closeTime,
1914 NetworkOPs& ops,
1915 Overlay& overlay,
1916 HashRouter& hashRouter)
1917{
1918 using namespace std::chrono_literals;
1919 if (timeKeeper_.now() > closeTime + 30s)
1920 closeTime = timeKeeper_.now();
1921
1922 std::lock_guard lock{mutex_};
1923
1924 // Rotate pending and remove expired published lists
1925 bool good = true;
1926 // localPublisherList is not processed here. This is because the
1927 // Validators specified in the local config file do not expire nor do
1928 // they have a "remaining" section of PublisherList.
1929 for (auto& [pubKey, collection] : publisherLists_)
1930 {
1931 {
1932 auto& remaining = collection.remaining;
1933 auto const firstIter = remaining.begin();
1934 auto iter = firstIter;
1935 if (iter != remaining.end() && iter->second.validFrom <= closeTime)
1936 {
1937 // Find the LAST candidate that is ready to go live.
1938 for (auto next = std::next(iter); next != remaining.end() &&
1939 next->second.validFrom <= closeTime;
1940 ++iter, ++next)
1941 {
1942 XRPL_ASSERT(
1943 std::next(iter) == next,
1944 "ripple::ValidatorList::updateTrusted : sequential "
1945 "remaining");
1946 }
1947 XRPL_ASSERT(
1948 iter != remaining.end(),
1949 "ripple::ValidatorList::updateTrusted : non-end of "
1950 "remaining");
1951
1952 // Rotate the pending list in to current
1953 auto sequence = iter->first;
1954 auto& candidate = iter->second;
1955 auto& current = collection.current;
1956 XRPL_ASSERT(
1957 candidate.validFrom <= closeTime,
1958 "ripple::ValidatorList::updateTrusted : maximum time");
1959
1960 auto const oldList = current.list;
1961 current = std::move(candidate);
1962 if (collection.status != PublisherStatus::available)
1963 collection.status = PublisherStatus::available;
1964 XRPL_ASSERT(
1965 current.sequence == sequence,
1966 "ripple::ValidatorList::updateTrusted : sequence match");
1967 // If the list is expired, remove the validators so they don't
1968 // get processed in. The expiration check below will do the rest
1969 // of the work
1970 if (current.validUntil <= closeTime)
1971 current.list.clear();
1972
1973 updatePublisherList(pubKey, current, oldList, lock);
1974
1975 // Only broadcast the current, which will consequently only
1976 // send to peers that don't understand v2, or which are
1977 // unknown (unlikely). Those that do understand v2 should
1978 // already have this list and are in the process of
1979 // switching themselves.
1980 broadcastBlobs(
1981 pubKey,
1982 collection,
1983 sequence,
1984 current.hash,
1985 overlay,
1986 hashRouter,
1987 j_);
1988
1989 // Erase any candidates that we skipped over, plus this one
1990 remaining.erase(firstIter, std::next(iter));
1991 }
1992 }
1993 // Remove if expired
1994 // ValidatorLists specified in the local config file never expire.
1995 // Hence, the below steps are not relevant for localPublisherList
1996 if (collection.status == PublisherStatus::available &&
1997 collection.current.validUntil <= closeTime)
1998 {
1999 removePublisherList(lock, pubKey, PublisherStatus::expired);
2000 ops.setUNLBlocked();
2001 }
2002 if (collection.status != PublisherStatus::available)
2003 good = false;
2004 }
2005 if (good)
2006 ops.clearUNLBlocked();
2007
2008 TrustChanges trustChanges;
2009
2010 auto it = trustedMasterKeys_.cbegin();
2011 while (it != trustedMasterKeys_.cend())
2012 {
2013 auto const kit = keyListings_.find(*it);
2014 if (kit == keyListings_.end() || //
2015 kit->second < listThreshold_ || //
2016 validatorManifests_.revoked(*it))
2017 {
2018 trustChanges.removed.insert(calcNodeID(*it));
2019 it = trustedMasterKeys_.erase(it);
2020 }
2021 else
2022 {
2023 XRPL_ASSERT(
2024 kit->second >= listThreshold_,
2025 "ripple::ValidatorList::updateTrusted : count meets threshold");
2026 ++it;
2027 }
2028 }
2029
2030 for (auto const& val : keyListings_)
2031 {
2032 if (val.second >= listThreshold_ &&
2033 !validatorManifests_.revoked(val.first) &&
2034 trustedMasterKeys_.emplace(val.first).second)
2035 trustChanges.added.insert(calcNodeID(val.first));
2036 }
2037
2038 // If there were any changes, we need to update the ephemeral signing
2039 // keys:
2040 if (!trustChanges.added.empty() || !trustChanges.removed.empty())
2041 {
2042 trustedSigningKeys_.clear();
2043
2044 // trustedMasterKeys_ contain non-revoked manifests only. Hence the
2045 // manifests must contain a valid signingKey
2046 for (auto const& k : trustedMasterKeys_)
2047 {
2048 std::optional<PublicKey> const signingKey =
2049 validatorManifests_.getSigningKey(k);
2050 XRPL_ASSERT(
2051 signingKey,
2052 "ripple::ValidatorList::updateTrusted : found signing key");
2053 trustedSigningKeys_.insert(*signingKey);
2054 }
2055 }
2056
2057 JLOG(j_.debug())
2058 << trustedMasterKeys_.size() << " of " << keyListings_.size()
2059 << " listed validators eligible for inclusion in the trusted set";
2060
2061 auto const unlSize = trustedMasterKeys_.size();
2062 auto effectiveUnlSize = unlSize;
2063 auto seenSize = seenValidators.size();
2064 if (!negativeUNL_.empty())
2065 {
2066 for (auto const& k : trustedMasterKeys_)
2067 {
2068 if (negativeUNL_.count(k))
2069 --effectiveUnlSize;
2070 }
2071 hash_set<NodeID> negUnlNodeIDs;
2072 for (auto const& k : negativeUNL_)
2073 {
2074 negUnlNodeIDs.emplace(calcNodeID(k));
2075 }
2076 for (auto const& nid : seenValidators)
2077 {
2078 if (negUnlNodeIDs.count(nid))
2079 --seenSize;
2080 }
2081 }
2082 quorum_ = calculateQuorum(unlSize, effectiveUnlSize, seenSize);
2083
2084 JLOG(j_.debug()) << "Using quorum of " << quorum_ << " for new set of "
2085 << unlSize << " trusted validators ("
2086 << trustChanges.added.size() << " added, "
2087 << trustChanges.removed.size() << " removed)";
2088
2089 if (unlSize < quorum_)
2090 {
2091 JLOG(j_.warn()) << "New quorum of " << quorum_
2092 << " exceeds the number of trusted validators ("
2093 << unlSize << ")";
2094 }
2095
2096 if ((publisherLists_.size() || localPublisherList.list.size()) &&
2097 unlSize == 0)
2098 {
2099 // No validators. Lock down.
2100 ops.setUNLBlocked();
2101 }
2102
2103 return trustChanges;
2104}
2105
2107ValidatorList::getTrustedMasterKeys() const
2108{
2109 std::shared_lock read_lock{mutex_};
2110 return trustedMasterKeys_;
2111}
2112
2114ValidatorList::getListThreshold() const
2115{
2116 std::shared_lock read_lock{mutex_};
2117 return listThreshold_;
2118}
2119
2121ValidatorList::getNegativeUNL() const
2122{
2123 std::shared_lock read_lock{mutex_};
2124 return negativeUNL_;
2125}
2126
2127void
2128ValidatorList::setNegativeUNL(hash_set<PublicKey> const& negUnl)
2129{
2130 std::lock_guard lock{mutex_};
2131 negativeUNL_ = negUnl;
2132}
2133
2135ValidatorList::negativeUNLFilter(
2136 std::vector<std::shared_ptr<STValidation>>&& validations) const
2137{
2138 // Remove validations that are from validators on the negative UNL.
2139 auto ret = std::move(validations);
2140
2141 std::shared_lock read_lock{mutex_};
2142 if (!negativeUNL_.empty())
2143 {
2144 ret.erase(
2146 ret.begin(),
2147 ret.end(),
2148 [&](auto const& v) -> bool {
2149 if (auto const masterKey =
2150 getTrustedKey(read_lock, v->getSignerPublic());
2151 masterKey)
2152 {
2153 return negativeUNL_.count(*masterKey);
2154 }
2155 else
2156 {
2157 return false;
2158 }
2159 }),
2160 ret.end());
2161 }
2162
2163 return ret;
2164}
2165
2166} // namespace ripple
T accumulate(T... args)
T back(T... args)
T begin(T... args)
T ceil(T... args)
Unserialize a JSON document into a Value.
Definition: json_reader.h:39
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Definition: json_reader.cpp:78
Represents a JSON value.
Definition: json_value.h:150
bool isArray() const
Value & append(Value const &value)
Append value to array at the end.
Definition: json_value.cpp:910
UInt size() const
Number of values in array or object.
Definition: json_value.cpp:719
std::string toStyledString() const
bool isString() const
UInt asUInt() const
Definition: json_value.cpp:558
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:482
bool isMember(char const *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:962
bool isInt() const
std::string to_string() const
Returns a string representing the endpoint.
Definition: IPEndpoint.cpp:67
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 info() const
Definition: Journal.h:334
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:57
std::optional< std::set< PeerShortID > > shouldRelay(uint256 const &key)
Determines whether the hashed item should be relayed.
Definition: HashRouter.cpp:119
bool addSuppressionPeer(uint256 const &key, PeerShortID peer)
Definition: HashRouter.cpp:52
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:62
std::size_t size() const noexcept
Definition: PublicKey.h:93
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 &)
TimeKeeper & timeKeeper_
static Json::Value buildFileData(std::string const &pubKey, PublisherListCollection const &pubCollection, beast::Journal j)
Build a Json representation of the collection, suitable for writing to a cache file,...
static void buildBlobInfos(std::map< std::size_t, ValidatorBlobInfo > &blobInfos, PublisherListCollection const &lists)
hash_map< PublicKey, std::size_t > keyListings_
bool listed(PublicKey const &identity) const
Returns true if public key is included on any lists.
std::size_t listThreshold_
void cacheValidatorFile(lock_guard const &lock, PublicKey const &pubKey) const
Write a JSON UNL to a cache file.
hash_set< PublicKey > trustedMasterKeys_
PublisherListStats applyList(std::string const &globalManifest, std::optional< std::string > const &localManifest, std::string const &blob, std::string const &signature, std::uint32_t version, std::string siteUri, std::optional< uint256 > const &hash, lock_guard const &)
Apply published list of public keys.
Json::Value getJson() const
Return a JSON representation of the state of the validator list.
std::optional< TimeKeeper::time_point > expires() const
Return the time when the validator list will expire.
PublisherListStats applyListsAndBroadcast(std::string const &manifest, std::uint32_t version, std::vector< ValidatorBlobInfo > const &blobs, std::string siteUri, uint256 const &hash, Overlay &overlay, HashRouter &hashRouter, NetworkOPs &networkOPs)
Apply multiple published lists of public keys, then broadcast it to all peers that have not seen it o...
beast::Journal const j_
bool trustedPublisher(PublicKey const &identity) const
Returns true if public key is a trusted publisher.
std::optional< PublicKey > getListedKey(PublicKey const &identity) const
Returns listed master public if public key is included on any lists.
boost::filesystem::path const dataPath_
boost::shared_mutex mutex_
bool removePublisherList(lock_guard const &, PublicKey const &publisherKey, PublisherStatus reason)
Stop trusting publisher's list of keys.
bool trusted(PublicKey const &identity) const
Returns true if public key is trusted.
static constexpr std::size_t maxSupportedBlobs
ValidatorList(ManifestCache &validatorManifests, ManifestCache &publisherManifests, TimeKeeper &timeKeeper, std::string const &databasePath, beast::Journal j, std::optional< std::size_t > minimumQuorum=std::nullopt)
std::pair< ListDisposition, std::optional< PublicKey > > verify(lock_guard const &, Json::Value &list, std::string const &manifest, std::string const &blob, std::string const &signature)
Check response for trusted valid published list.
std::optional< PublicKey > localPubKey_
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_
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 make_pair(T... args)
T max(T... args)
T min(T... args)
@ nullValue
'null' value
Definition: json_value.h:39
@ arrayValue
array value (ordered list)
Definition: json_value.h:45
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:46
unsigned int UInt
Definition: json_forwards.h:27
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:114
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.
Definition: PublicKey.cpp:288
std::string base64_decode(std::string_view data)
Definition: base64.cpp:248
std::size_t splitMessage(std::vector< ValidatorList::MessageWithHash > &messages, protocol::TMValidatorListCollection const &largeMsg, std::size_t maxSize, std::size_t begin=0, std::size_t end=0)
std::size_t splitMessageParts(std::vector< ValidatorList::MessageWithHash > &messages, protocol::TMValidatorListCollection const &largeMsg, std::size_t maxSize, std::size_t begin, std::size_t end)
@ current
This was a new validation and was added.
@ unsupported_version
List version is not supported.
@ stale
Trusted publisher key, but seq is too old.
@ accepted
List is valid.
@ untrusted
List signed by untrusted publisher key.
@ same_sequence
Same sequence as current list.
@ pending
List will be valid in the future.
@ known_sequence
Future sequence already seen.
@ expired
List is expired, but has the largest non-pending sequence seen so far.
@ invalid
Invalid format or signature.
std::error_code make_error_code(ripple::TokenCodecErrc e)
Definition: token_errors.h:97
std::optional< KeyType > publicKeyType(Slice const &slice)
Returns the type of public key.
Definition: PublicKey.cpp:223
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.
Definition: PublicKey.cpp:319
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:225
std::size_t buildValidatorListMessage(std::vector< ValidatorList::MessageWithHash > &messages, std::uint32_t rawVersion, std::string const &rawManifest, ValidatorBlobInfo const &currentBlob, std::size_t maxSize)
T next(T... args)
T push_back(T... args)
T remove_if(T... args)
T reserve(T... args)
T reset(T... args)
T size(T... args)
T sort(T... args)
Changes in trusted nodes after updating validator list.
hash_set< NodeID > added
hash_set< NodeID > removed
Used to represent the information stored in the blobs_v2 Json array.
std::optional< std::string > manifest
std::map< std::size_t, PublisherList > remaining
Describes the result of processing a Validator List (UNL), including some of the information from the...
void mergeDispositions(PublisherListStats const &src)
std::map< ListDisposition, std::size_t > dispositions
std::optional< PublicKey > publisherKey
std::vector< PublicKey > list
TimeKeeper::time_point validUntil
TimeKeeper::time_point validFrom