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