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