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