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