xrpld
Loading...
Searching...
No Matches
RCLConsensus.cpp
1#include <xrpld/app/consensus/RCLConsensus.h>
2
3#include <xrpld/app/consensus/RCLCensorshipDetector.h>
4#include <xrpld/app/consensus/RCLCxLedger.h>
5#include <xrpld/app/consensus/RCLCxPeerPos.h>
6#include <xrpld/app/consensus/RCLCxTx.h>
7#include <xrpld/app/consensus/RCLValidations.h>
8#include <xrpld/app/ledger/BuildLedger.h>
9#include <xrpld/app/ledger/InboundLedger.h>
10#include <xrpld/app/ledger/InboundLedgers.h>
11#include <xrpld/app/ledger/InboundTransactions.h>
12#include <xrpld/app/ledger/LedgerMaster.h>
13#include <xrpld/app/ledger/LocalTxs.h>
14#include <xrpld/app/ledger/OpenLedger.h>
15#include <xrpld/app/misc/FeeVote.h>
16#include <xrpld/app/misc/NegativeUNLVote.h>
17#include <xrpld/app/misc/TxQ.h>
18#include <xrpld/app/misc/ValidatorKeys.h>
19#include <xrpld/app/misc/ValidatorList.h>
20#include <xrpld/consensus/Consensus.h>
21#include <xrpld/consensus/ConsensusTypes.h>
22#include <xrpld/overlay/Overlay.h>
23#include <xrpld/overlay/predicates.h>
24
25#include <xrpl/basics/Log.h>
26#include <xrpl/basics/Slice.h>
27#include <xrpl/basics/UnorderedContainers.h>
28#include <xrpl/basics/base_uint.h>
29#include <xrpl/basics/chrono.h>
30#include <xrpl/basics/contract.h>
31#include <xrpl/basics/random.h>
32#include <xrpl/beast/utility/Journal.h>
33#include <xrpl/beast/utility/Zero.h>
34#include <xrpl/beast/utility/instrumentation.h>
35#include <xrpl/core/HashRouter.h>
36#include <xrpl/core/Job.h>
37#include <xrpl/crypto/csprng.h>
38#include <xrpl/json/json_value.h>
39#include <xrpl/json/json_writer.h>
40#include <xrpl/ledger/AmendmentTable.h>
41#include <xrpl/ledger/ApplyView.h>
42#include <xrpl/ledger/Ledger.h>
43#include <xrpl/ledger/LedgerTiming.h>
44#include <xrpl/ledger/OpenView.h>
45#include <xrpl/ledger/ReadView.h>
46#include <xrpl/ledger/View.h>
47#include <xrpl/protocol/BuildInfo.h>
48#include <xrpl/protocol/Protocol.h>
49#include <xrpl/protocol/PublicKey.h>
50#include <xrpl/protocol/RippleLedgerHash.h>
51#include <xrpl/protocol/Rules.h>
52#include <xrpl/protocol/SField.h>
53#include <xrpl/protocol/STTx.h>
54#include <xrpl/protocol/STValidation.h>
55#include <xrpl/protocol/SecretKey.h>
56#include <xrpl/protocol/Serializer.h>
57#include <xrpl/protocol/UintTypes.h>
58#include <xrpl/protocol/digest.h>
59#include <xrpl/protocol/tokens.h>
60#include <xrpl/server/LoadFeeTrack.h>
61#include <xrpl/server/NetworkOPs.h>
62#include <xrpl/shamap/SHAMapItem.h>
63#include <xrpl/shamap/SHAMapMissingNode.h>
64#include <xrpl/shamap/SHAMapTreeNode.h>
65
66#include <boost/smart_ptr/intrusive_ptr.hpp>
67
68#include <xrpl.pb.h>
69
70#include <algorithm>
71#include <chrono>
72#include <cstddef>
73#include <cstdint>
74#include <exception>
75#include <iomanip>
76#include <limits>
77#include <memory>
78#include <mutex>
79#include <optional>
80#include <set>
81#include <sstream>
82#include <string>
83#include <type_traits>
84#include <utility>
85#include <vector>
86
87namespace xrpl {
88
90 Application& app,
92 LedgerMaster& ledgerMaster,
93 LocalTxs& localTxs,
94 InboundTransactions& inboundTransactions,
96 ValidatorKeys const& validatorKeys,
97 beast::Journal journal)
98 : adaptor_(
99 app,
100 std::move(feeVote),
101 ledgerMaster,
102 localTxs,
103 inboundTransactions,
104 validatorKeys,
105 journal)
106 , consensus_(clock, adaptor_, journal)
107 , j_(journal)
108{
109}
110
112 Application& app,
113 std::unique_ptr<FeeVote>&& feeVote,
114 LedgerMaster& ledgerMaster,
115 LocalTxs& localTxs,
116 InboundTransactions& inboundTransactions,
117 ValidatorKeys const& validatorKeys,
118 beast::Journal journal)
119 : app_(app)
120 , feeVote_(std::move(feeVote))
121 , ledgerMaster_(ledgerMaster)
122 , localTxs_(localTxs)
123 , inboundTransactions_{inboundTransactions}
124 , j_(journal)
125 , validatorKeys_(validatorKeys)
126 , valCookie_(1 + randInt(cryptoPrng(), std::numeric_limits<std::uint64_t>::max() - 1))
127 , nUnlVote_(validatorKeys_.nodeID, j_)
128{
129 XRPL_ASSERT(valCookie_, "xrpl::RCLConsensus::Adaptor::Adaptor : nonzero cookie");
130
131 JLOG(j_.info()) << "Consensus engine started (cookie: " + std::to_string(valCookie_) + ")";
132
133 if (validatorKeys_.nodeID != beast::kZero && validatorKeys_.keys)
134 {
135 JLOG(j_.info()) << "Validator identity: "
136 << toBase58(TokenType::NodePublic, validatorKeys_.keys->masterPublicKey);
137
138 if (validatorKeys_.keys->masterPublicKey != validatorKeys_.keys->publicKey)
139 {
140 JLOG(j_.debug()) << "Validator ephemeral signing key: "
142 << " (seq: " << std::to_string(validatorKeys_.sequence) << ")";
143 }
144 }
145}
146
149{
150 // we need to switch the ledger we're working from
151 auto built = ledgerMaster_.getLedgerByHash(hash);
152 if (!built)
153 {
154 if (acquiringLedger_ != hash)
155 {
156 // need to start acquiring the correct consensus LCL
157 JLOG(j_.warn()) << "Need consensus ledger " << hash;
158
159 // Tell the ledger acquire system that we need the consensus ledger
160 acquiringLedger_ = hash;
161
162 app_.getJobQueue().addJob(JtAdvance, "GetConsL1", [id = hash, &app = app_, this]() {
163 JLOG(j_.debug()) << "JOB advanceLedger getConsensusLedger1 started";
164 app.getInboundLedgers().acquireAsync(id, 0, InboundLedger::Reason::CONSENSUS);
165 });
166 }
167 return std::nullopt;
168 }
169
170 XRPL_ASSERT(
171 !built->open() && built->isImmutable(),
172 "xrpl::RCLConsensus::Adaptor::acquireLedger : valid ledger state");
173 XRPL_ASSERT(
174 built->header().hash == hash,
175 "xrpl::RCLConsensus::Adaptor::acquireLedger : ledger hash match");
176
177 // Notify inbound transactions of the new ledger sequence number
178 inboundTransactions_.newRound(built->header().seq);
179
180 return RCLCxLedger(built);
181}
182
183void
185{
186 protocol::TMProposeSet prop;
187
188 auto const& proposal = peerPos.proposal();
189
190 prop.set_proposeseq(proposal.proposeSeq());
191 prop.set_closetime(proposal.closeTime().time_since_epoch().count());
192
193 prop.set_currenttxhash(proposal.position().begin(), proposal.position().size());
194 prop.set_previousledger(proposal.prevLedger().begin(), proposal.prevLedger().size());
195
196 auto const pk = peerPos.publicKey().slice();
197 prop.set_nodepubkey(pk.data(), pk.size());
198
199 auto const sig = peerPos.signature();
200 prop.set_signature(sig.data(), sig.size());
201
202 app_.getOverlay().relay(prop, peerPos.suppressionID(), peerPos.publicKey());
203}
204
205void
207{
208 // If we didn't relay this transaction recently, relay it to all peers
209 if (app_.getHashRouter().shouldRelay(tx.id()))
210 {
211 JLOG(j_.debug()) << "Relaying disputed tx " << tx.id();
212 auto const slice = tx.tx->slice();
213 protocol::TMTransaction msg;
214 msg.set_rawtransaction(slice.data(), slice.size());
215 msg.set_status(protocol::tsNEW);
216 msg.set_receivetimestamp(app_.getTimeKeeper().now().time_since_epoch().count());
217 static std::set<Peer::id_t> const kSkip{};
218 app_.getOverlay().relay(tx.id(), msg, kSkip);
219 }
220 else
221 {
222 JLOG(j_.debug()) << "Not relaying disputed tx " << tx.id();
223 }
224}
225void
227{
228 JLOG(j_.trace()) << (proposal.isBowOut() ? "We bow out: " : "We propose: ")
229 << xrpl::to_string(proposal.prevLedger()) << " -> "
230 << xrpl::to_string(proposal.position());
231
232 protocol::TMProposeSet prop;
233
234 prop.set_currenttxhash(proposal.position().begin(), proposal.position().size());
235 prop.set_previousledger(proposal.prevLedger().begin(), proposal.prevLedger().size());
236 prop.set_proposeseq(proposal.proposeSeq());
237 prop.set_closetime(proposal.closeTime().time_since_epoch().count());
238
239 if (!validatorKeys_.keys)
240 {
241 JLOG(j_.warn()) << "RCLConsensus::Adaptor::propose: ValidatorKeys "
242 "not set: \n";
243 return;
244 }
245
246 auto const& keys = *validatorKeys_.keys;
247
248 prop.set_nodepubkey(keys.publicKey.data(), keys.publicKey.size());
249
250 auto sig = signDigest(keys.publicKey, keys.secretKey, proposal.signingHash());
251
252 prop.set_signature(sig.data(), sig.size());
253
254 auto const suppression = proposalUniqueId(
255 proposal.position(),
256 proposal.prevLedger(),
257 proposal.proposeSeq(),
258 proposal.closeTime(),
259 keys.publicKey,
260 sig);
261
262 app_.getHashRouter().addSuppression(suppression);
263
264 app_.getOverlay().broadcast(prop);
265}
266
267void
269{
270 inboundTransactions_.giveSet(txns.id(), txns.map, false);
271}
272
275{
276 if (auto txns = inboundTransactions_.getSet(setId, true))
277 {
278 return RCLTxSet{std::move(txns)};
279 }
280 return std::nullopt;
281}
282
283bool
285{
286 return !app_.getOpenLedger().empty();
287}
288
291{
292 return app_.getValidations().numTrustedForLedger(h);
293}
294
297{
298 RCLValidations& vals = app_.getValidations();
299 return vals.getNodesAfter(RCLValidatedLedger(ledger.ledger, vals.adaptor().journal()), h);
300}
301
304 uint256 ledgerID,
305 RCLCxLedger const& ledger,
307{
308 RCLValidations& vals = app_.getValidations();
309 uint256 netLgr = vals.getPreferred(
310 RCLValidatedLedger{ledger.ledger, vals.adaptor().journal()},
311 ledgerMaster_.getValidLedgerIndex());
312
313 if (netLgr != ledgerID)
314 {
316 app_.getOPs().consensusViewChange();
317
318 JLOG(j_.debug()) << json::Compact(app_.getValidations().getJsonTrie());
319 }
320
321 return netLgr;
322}
323
324auto
326 RCLCxLedger const& ledger,
327 NetClock::time_point const& closeTime,
329{
330 bool const wrongLCL = mode == ConsensusMode::WrongLedger;
331 bool const proposing = mode == ConsensusMode::Proposing;
332
333 notify(protocol::neCLOSING_LEDGER, ledger, !wrongLCL);
334
335 auto const& prevLedger = ledger.ledger;
336
337 ledgerMaster_.applyHeldTransactions();
338 // Tell the ledger master not to acquire the ledger we're probably building
339 ledgerMaster_.setBuildingLedger(prevLedger->header().seq + 1);
340
341 auto initialLedger = app_.getOpenLedger().current();
342
343 auto initialSet = std::make_shared<SHAMap>(SHAMapType::TRANSACTION, app_.getNodeFamily());
344 initialSet->setUnbacked();
345
346 // Build SHAMap containing all transactions in our open ledger
347 for (auto const& tx : initialLedger->txs)
348 {
349 JLOG(j_.trace()) << "Adding open ledger TX " << tx.first->getTransactionID();
350 Serializer s(2048);
351 tx.first->add(s);
352 initialSet->addItem(
354 makeShamapitem(tx.first->getTransactionID(), s.slice()));
355 }
356
357 // Add pseudo-transactions to the set
358 if (app_.config().standalone() || (proposing && !wrongLCL))
359 {
360 if (prevLedger->isFlagLedger())
361 {
362 // previous ledger was flag ledger, add fee and amendment
363 // pseudo-transactions
364 auto validations =
365 app_.getValidators().negativeUNLFilter(app_.getValidations().getTrustedForLedger(
366 prevLedger->header().parentHash, prevLedger->seq() - 1));
367 if (validations.size() >= app_.getValidators().quorum())
368 {
369 feeVote_->doVoting(prevLedger, validations, initialSet);
370 app_.getAmendmentTable().doVoting(prevLedger, validations, initialSet, j_);
371 }
372 }
373 else if (prevLedger->isVotingLedger())
374 {
375 // previous ledger was a voting ledger,
376 // so the current consensus session is for a flag ledger,
377 // add negative UNL pseudo-transactions
378 nUnlVote_.doVoting(
379 prevLedger,
380 app_.getValidators().getTrustedMasterKeys(),
381 app_.getValidations(),
382 initialSet);
383 }
384 }
385
386 // Now we need an immutable snapshot
387 initialSet = initialSet->snapShot(false);
388
389 if (!wrongLCL)
390 {
391 LedgerIndex const seq = prevLedger->header().seq + 1;
393
394 initialSet->visitLeaves(
395 [&proposed, seq](boost::intrusive_ptr<SHAMapItem const> const& item) {
396 proposed.emplace_back(item->key(), seq);
397 });
398
399 censorshipDetector_.propose(std::move(proposed));
400 }
401
402 // Needed because of the move below.
403 auto const setHash = initialSet->getHash().asUInt256();
404
405 return Result{
406 std::move(initialSet),
408 initialLedger->header().parentHash,
410 setHash,
411 closeTime,
412 app_.getTimeKeeper().closeTime(),
413 validatorKeys_.nodeID}};
414}
415
416void
418 Result const& result,
419 RCLCxLedger const& prevLedger,
420 NetClock::duration const& closeResolution,
421 ConsensusCloseTimes const& rawCloseTimes,
422 ConsensusMode const& mode,
423 json::Value&& consensusJson)
424{
425 doAccept(result, prevLedger, closeResolution, rawCloseTimes, mode, std::move(consensusJson));
426}
427
428void
430 Result const& result,
431 RCLCxLedger const& prevLedger,
432 NetClock::duration const& closeResolution,
433 ConsensusCloseTimes const& rawCloseTimes,
434 ConsensusMode const& mode,
435 json::Value&& consensusJson,
436 bool const validating)
437{
438 app_.getJobQueue().addJob(
439 JtAccept,
440 "AcceptLedger",
441 // NOLINTNEXTLINE(cppcoreguidelines-misleading-capture-default-by-value)
442 [=, this, cj = std::move(consensusJson)]() mutable {
443 // Note that no lock is held or acquired during this job.
444 // This is because generic Consensus guarantees that once a ledger
445 // is accepted, the consensus results and capture by reference state
446 // will not change until startRound is called (which happens via
447 // endConsensus).
448 RclConsensusLogger clog("onAccept", validating, j_);
449 this->doAccept(result, prevLedger, closeResolution, rawCloseTimes, mode, std::move(cj));
450 this->app_.getOPs().endConsensus(clog.ss());
451 });
452}
453
454void
456 Result const& result,
457 RCLCxLedger const& prevLedger,
458 NetClock::duration closeResolution,
459 ConsensusCloseTimes const& rawCloseTimes,
460 ConsensusMode const& mode,
461 json::Value&& consensusJson)
462{
463 prevProposers_ = result.proposers;
464 prevRoundTime_ = result.roundTime.read();
465
466 bool closeTimeCorrect = false;
467
468 bool const proposing = mode == ConsensusMode::Proposing;
469 bool const haveCorrectLCL = mode != ConsensusMode::WrongLedger;
470 bool const consensusFail = result.state == ConsensusState::MovedOn;
471
472 auto consensusCloseTime = result.position.closeTime();
473
474 if (consensusCloseTime == NetClock::time_point{})
475 {
476 // We agreed to disagree on the close time
477 using namespace std::chrono_literals;
478 consensusCloseTime = prevLedger.closeTime() + 1s;
479 closeTimeCorrect = false;
480 }
481 else
482 {
483 // We agreed on a close time
484 consensusCloseTime =
485 effCloseTime(consensusCloseTime, closeResolution, prevLedger.closeTime());
486 closeTimeCorrect = true;
487 }
488
489 JLOG(j_.debug()) << "Report: Prop=" << (proposing ? "yes" : "no")
490 << " val=" << (validating_ ? "yes" : "no")
491 << " corLCL=" << (haveCorrectLCL ? "yes" : "no")
492 << " fail=" << (consensusFail ? "yes" : "no");
493 JLOG(j_.debug()) << "Report: Prev = " << prevLedger.id() << ":" << prevLedger.seq();
494
495 //--------------------------------------------------------------------------
496 std::set<TxID> failed;
497
498 // We want to put transactions in an unpredictable but deterministic order:
499 // we use the hash of the set.
500 //
501 // FIXME: Use a std::vector and a custom sorter instead of CanonicalTXSet?
502 CanonicalTXSet retriableTxs{result.txns.map->getHash().asUInt256()};
503
504 JLOG(j_.debug()) << "Building canonical tx set: " << retriableTxs.key();
505
506 for (auto const& item : *result.txns.map)
507 {
508 try
509 {
510 retriableTxs.insert(std::make_shared<STTx const>(SerialIter{item.slice()}));
511 JLOG(j_.debug()) << " Tx: " << item.key();
512 }
513 catch (std::exception const& ex)
514 {
515 failed.insert(item.key());
516 JLOG(j_.warn()) << " Tx: " << item.key() << " throws: " << ex.what();
517 }
518 }
519
520 auto built = buildLCL(
521 prevLedger,
522 retriableTxs,
523 consensusCloseTime,
524 closeTimeCorrect,
525 closeResolution,
526 result.roundTime.read(),
527 failed);
528
529 auto const newLCLHash = built.id();
530 JLOG(j_.debug()) << "Built ledger #" << built.seq() << ": " << newLCLHash;
531
532 // Tell directly connected peers that we have a new LCL
533 notify(protocol::neACCEPTED_LEDGER, built, haveCorrectLCL);
534
535 // As long as we're in sync with the network, attempt to detect attempts
536 // at censorship of transaction by tracking which ones don't make it in
537 // after a period of time.
538 if (haveCorrectLCL && result.state == ConsensusState::Yes)
539 {
540 std::vector<TxID> accepted;
541
542 result.txns.map->visitLeaves(
543 [&accepted](boost::intrusive_ptr<SHAMapItem const> const& item) {
544 accepted.push_back(item->key());
545 });
546
547 // Track all the transactions which failed or were marked as retriable
548 for (auto const& r : retriableTxs)
549 failed.insert(r.first.getTXID());
550
552 std::move(accepted),
553 [curr = built.seq(), j = app_.getJournal("CensorshipDetector"), &failed](
554 uint256 const& id, LedgerIndex seq) {
555 if (failed.contains(id))
556 return true;
557
558 auto const wait = curr - seq;
559
560 if (wait && (wait % kCensorshipWarnInternal == 0))
561 {
562 std::ostringstream ss;
563 ss << "Potential Censorship: Eligible tx " << id
564 << ", which we are tracking since ledger " << seq
565 << " has not been included as of ledger " << curr << ".";
566
567 JLOG(j.warn()) << ss.str();
568 }
569
570 return false;
571 });
572 }
573
574 if (validating_)
575 validating_ = ledgerMaster_.isCompatible(*built.ledger, j_.warn(), "Not validating");
576
577 if (validating_ && !consensusFail && app_.getValidations().canValidateSeq(built.seq()))
578 {
579 validate(built, result.txns, proposing);
580 JLOG(j_.info()) << "CNF Val " << newLCLHash;
581 }
582 else
583 {
584 JLOG(j_.info()) << "CNF buildLCL " << newLCLHash;
585 }
586
587 // See if we can accept a ledger as fully-validated
588 ledgerMaster_.consensusBuilt(built.ledger, result.txns.id(), std::move(consensusJson));
589
590 //-------------------------------------------------------------------------
591 {
592 // Apply disputed transactions that didn't get in
593 //
594 // The first crack of transactions to get into the new
595 // open ledger goes to transactions proposed by a validator
596 // we trust but not included in the consensus set.
597 //
598 // These are done first because they are the most likely
599 // to receive agreement during consensus. They are also
600 // ordered logically "sooner" than transactions not mentioned
601 // in the previous consensus round.
602 //
603 bool anyDisputes = false;
604 for (auto const& [_, dispute] : result.disputes)
605 {
606 (void)_;
607 if (!dispute.getOurVote())
608 {
609 // we voted NO
610 try
611 {
612 JLOG(j_.debug()) << "Test applying disputed transaction that did"
613 << " not get in " << dispute.tx().id();
614
615 SerialIter sit(dispute.tx().tx->slice());
616 auto txn = std::make_shared<STTx const>(sit);
617
618 // Disputed pseudo-transactions that were not accepted
619 // can't be successfully applied in the next ledger
620 if (isPseudoTx(*txn))
621 continue;
622
623 retriableTxs.insert(txn);
624
625 anyDisputes = true;
626 }
627 catch (std::exception const& ex)
628 {
629 JLOG(j_.debug()) << "Failed to apply transaction we voted "
630 "NO on. Exception: "
631 << ex.what();
632 }
633 }
634 }
635
636 // Build new open ledger
637 std::unique_lock lock{app_.getMasterMutex(), std::defer_lock};
638 std::unique_lock sl{ledgerMaster_.peekMutex(), std::defer_lock};
639 std::lock(lock, sl);
640
641 auto const lastVal = ledgerMaster_.getValidatedLedger();
642 std::optional<Rules> rules;
643 if (lastVal)
644 {
645 rules = makeRulesGivenLedger(*lastVal, app_.config().features);
646 }
647 else
648 {
649 rules.emplace(app_.config().features);
650 }
651 app_.getOpenLedger().accept(
652 app_,
653 *rules,
654 built.ledger,
655 localTxs_.getTxSet(),
656 anyDisputes,
657 retriableTxs,
658 TapNone,
659 "consensus",
660 [&](OpenView& view, beast::Journal j) {
661 // Stuff the ledger with transactions from the queue.
662 return app_.getTxQ().accept(app_, view);
663 });
664
665 // Signal a potential fee change to subscribers after the open ledger
666 // is created
667 app_.getOPs().reportFeeChange();
668 }
669
670 //-------------------------------------------------------------------------
671 {
672 ledgerMaster_.switchLCL(built.ledger);
673
674 // Do these need to exist?
675 XRPL_ASSERT(
676 ledgerMaster_.getClosedLedger()->header().hash == built.id(),
677 "xrpl::RCLConsensus::Adaptor::doAccept : ledger hash match");
678 XRPL_ASSERT(
679 app_.getOpenLedger().current()->header().parentHash == built.id(),
680 "xrpl::RCLConsensus::Adaptor::doAccept : parent hash match");
681 }
682
683 //-------------------------------------------------------------------------
684 // we entered the round with the network,
685 // see how close our close time is to other node's
686 // close time reports, and update our clock.
687 if ((mode == ConsensusMode::Proposing || mode == ConsensusMode::Observing) && !consensusFail)
688 {
689 auto closeTime = rawCloseTimes.self;
690
691 JLOG(j_.info()) << "We closed at " << closeTime.time_since_epoch().count();
692 using usec64_t = std::chrono::duration<std::uint64_t>;
693 usec64_t closeTotal = std::chrono::duration_cast<usec64_t>(closeTime.time_since_epoch());
694 int closeCount = 1;
695
696 for (auto const& [t, v] : rawCloseTimes.peers)
697 {
698 JLOG(j_.info()) << std::to_string(v) << " time votes for "
699 << std::to_string(t.time_since_epoch().count());
700 closeCount += v;
701 closeTotal += std::chrono::duration_cast<usec64_t>(t.time_since_epoch()) * v;
702 }
703
704 closeTotal += usec64_t(closeCount / 2); // for round to nearest
705 closeTotal /= closeCount;
706
707 // Use signed times since we are subtracting
708 using duration = std::chrono::duration<std::int32_t>;
709 using time_point = std::chrono::time_point<NetClock, duration>;
710 auto offset = time_point{closeTotal} - std::chrono::time_point_cast<duration>(closeTime);
711 JLOG(j_.info()) << "Our close offset is estimated at " << offset.count() << " ("
712 << closeCount << ")";
713
714 app_.getTimeKeeper().adjustCloseTime(offset);
715 }
716}
717
718void
720 protocol::NodeEvent ne,
721 RCLCxLedger const& ledger,
722 bool haveCorrectLCL)
723{
724 protocol::TMStatusChange s;
725
726 if (!haveCorrectLCL)
727 {
728 s.set_newevent(protocol::neLOST_SYNC);
729 }
730 else
731 {
732 s.set_newevent(ne);
733 }
734
735 s.set_ledgerseq(ledger.seq());
736 s.set_networktime(app_.getTimeKeeper().now().time_since_epoch().count());
737 s.set_ledgerhashprevious(
738 ledger.parentID().begin(), std::decay_t<decltype(ledger.parentID())>::kBytes);
739 s.set_ledgerhash(ledger.id().begin(), std::decay_t<decltype(ledger.id())>::kBytes);
740
741 std::uint32_t uMin = 0, uMax = 0;
742 if (!ledgerMaster_.getFullValidatedRange(uMin, uMax))
743 {
744 uMin = 0;
745 uMax = 0;
746 }
747 else
748 {
749 // Don't advertise ledgers we're not willing to serve
750 uMin = std::max(uMin, ledgerMaster_.getEarliestFetch());
751 }
752 s.set_firstseq(uMin);
753 s.set_lastseq(uMax);
754 app_.getOverlay().foreach(SendAlways(std::make_shared<Message>(s, protocol::mtSTATUS_CHANGE)));
755 JLOG(j_.trace()) << "send status change to peer";
756}
757
760 RCLCxLedger const& previousLedger,
761 CanonicalTXSet& retriableTxs,
762 NetClock::time_point closeTime,
763 bool closeTimeCorrect,
764 NetClock::duration closeResolution,
766 std::set<TxID>& failedTxs)
767{
768 std::shared_ptr<Ledger> built = [&]() {
769 if (auto const replayData = ledgerMaster_.releaseReplay())
770 {
771 XRPL_ASSERT(
772 replayData->parent()->header().hash == previousLedger.id(),
773 "xrpl::RCLConsensus::Adaptor::buildLCL : parent hash match");
774 return buildLedger(*replayData, TapNone, app_, j_);
775 }
776 return buildLedger(
777 previousLedger.ledger,
778 closeTime,
779 closeTimeCorrect,
780 closeResolution,
781 app_,
782 retriableTxs,
783 failedTxs,
784 j_);
785 }();
786
787 // Update fee computations based on accepted txs
788 using namespace std::chrono_literals;
789 app_.getTxQ().processClosedLedger(app_, *built, roundTime > 5s);
790
791 // And stash the ledger in the ledger master
792 if (ledgerMaster_.storeLedger(built))
793 {
794 JLOG(j_.debug()) << "Consensus built ledger we already had";
795 }
796 else if (app_.getInboundLedgers().find(built->header().hash))
797 {
798 JLOG(j_.debug()) << "Consensus built ledger we were acquiring";
799 }
800 else
801 {
802 JLOG(j_.debug()) << "Consensus built new ledger";
803 }
804 return RCLCxLedger{std::move(built)};
805}
806
807void
808RCLConsensus::Adaptor::validate(RCLCxLedger const& ledger, RCLTxSet const& txns, bool proposing)
809{
810 using namespace std::chrono_literals;
811
812 auto validationTime = app_.getTimeKeeper().closeTime();
813 if (validationTime <= lastValidationTime_)
814 validationTime = lastValidationTime_ + 1s;
815 lastValidationTime_ = validationTime;
816
817 if (!validatorKeys_.keys)
818 {
819 JLOG(j_.warn()) << "RCLConsensus::Adaptor::validate: ValidatorKeys "
820 "not set\n";
821 return;
822 }
823
824 auto const& keys = *validatorKeys_.keys;
825
828 keys.publicKey,
829 keys.secretKey,
830 validatorKeys_.nodeID,
831 [&](STValidation& v) {
832 v.setFieldH256(sfLedgerHash, ledger.id());
833 v.setFieldH256(sfConsensusHash, txns.id());
834
835 v.setFieldU32(sfLedgerSequence, ledger.seq());
836
837 if (proposing)
838 v.setFlag(kVfFullValidation);
839
840 // Attest to the hash of what we consider to be the last fully
841 // validated ledger. This may be the hash of the ledger we are
842 // validating here, and that's fine.
843 if (auto const vl = ledgerMaster_.getValidatedLedger())
844 v.setFieldH256(sfValidatedHash, vl->header().hash);
845
846 v.setFieldU64(sfCookie, valCookie_);
847
848 // Report our server version every flag ledger:
849 if (ledger.ledger->isVotingLedger())
850 v.setFieldU64(sfServerVersion, BuildInfo::getEncodedVersion());
851
852 // Report our load
853 {
854 auto const& ft = app_.getFeeTrack();
855 auto const fee = std::max(ft.getLocalFee(), ft.getClusterFee());
856 if (fee > ft.getLoadBase())
857 v.setFieldU32(sfLoadFee, fee);
858 }
859
860 // If the next ledger is a flag ledger, suggest fee changes and
861 // new features:
862 if (ledger.ledger->isVotingLedger())
863 {
864 // Fees:
865 feeVote_->doValidation(ledger.ledger->fees(), ledger.ledger->rules(), v);
866
867 // Amendments
868 // FIXME: pass `v` and have the function insert the array
869 // directly?
870 auto const amendments =
871 app_.getAmendmentTable().doValidation(getEnabledAmendments(*ledger.ledger));
872
873 if (!amendments.empty())
874 v.setFieldV256(sfAmendments, STVector256(sfAmendments, amendments));
875 }
876 });
877
878 auto const serialized = v->getSerialized();
879
880 // suppress it if we receive it
881 app_.getHashRouter().addSuppression(sha512Half(makeSlice(serialized)));
882
883 handleNewValidation(app_, v, "local");
884
885 // Broadcast to all our peers:
886 protocol::TMValidation val;
887 val.set_validation(serialized.data(), serialized.size());
888 app_.getOverlay().broadcast(val);
889
890 // Publish to all our subscribers:
891 app_.getOPs().pubValidation(v);
892}
893
894void
896{
897 JLOG(j_.info()) << "Consensus mode change before=" << to_string(before)
898 << ", after=" << to_string(after);
899
900 // If we were proposing but aren't any longer, we need to reset the
901 // censorship tracking to avoid bogus warnings.
902 if ((before == ConsensusMode::Proposing || before == ConsensusMode::Observing) &&
903 before != after)
904 censorshipDetector_.reset();
905
906 mode_ = after;
907}
908
910RCLConsensus::getJson(bool full) const
911{
912 json::Value ret;
913 {
914 std::scoped_lock const _{mutex_};
915 ret = consensus_.getJson(full);
916 }
917 ret["validating"] = adaptor_.validating();
918 return ret;
919}
920
921void
923 NetClock::time_point const& now,
925{
926 try
927 {
928 std::scoped_lock const _{mutex_};
929 consensus_.timerEntry(now, clog);
930 }
931 catch (SHAMapMissingNode const& mn)
932 {
933 // This should never happen
935 ss << "During consensus timerEntry: " << mn.what();
936 JLOG(j_.error()) << ss.str();
937 CLOG(clog) << ss.str();
938 rethrow();
939 }
940}
941
942void
944{
945 try
946 {
947 std::scoped_lock const _{mutex_};
948 consensus_.gotTxSet(now, txSet);
949 }
950 catch (SHAMapMissingNode const& mn)
951 {
952 // This should never happen
953 JLOG(j_.error()) << "During consensus gotTxSet: " << mn.what();
954 rethrow();
955 }
956}
957
959
960void
962 NetClock::time_point const& now,
964{
965 std::scoped_lock const _{mutex_};
966 consensus_.simulate(now, consensusDelay);
967}
968
969bool
971{
972 std::scoped_lock const _{mutex_};
973 return consensus_.peerProposal(now, newProposal);
974}
975
976bool
978{
979 // We have a key, we do not want out of sync validations after a restart
980 // and are not amendment blocked.
981 validating_ = validatorKeys_.keys && prevLgr.seq() >= app_.getMaxDisallowedLedger() &&
982 !app_.getOPs().isBlocked();
983
984 // If we are not running in standalone mode and there's a configured UNL,
985 // check to make sure that it's not expired.
986 if (validating_ && !app_.config().standalone() && (app_.getValidators().count() != 0u))
987 {
988 auto const when = app_.getValidators().expires();
989
990 if (!when || *when < app_.getTimeKeeper().now())
991 {
992 JLOG(j_.error()) << "Voluntarily bowing out of consensus process "
993 "because of an expired validator list.";
994 validating_ = false;
995 }
996 }
997
998 bool const synced = app_.getOPs().getOperatingMode() == OperatingMode::FULL;
999
1000 if (validating_)
1001 {
1002 JLOG(j_.info()) << "Entering consensus process, validating, synced="
1003 << (synced ? "yes" : "no");
1004 }
1005 else
1006 {
1007 // Otherwise we just want to monitor the validation process.
1008 JLOG(j_.info()) << "Entering consensus process, watching, synced="
1009 << (synced ? "yes" : "no");
1010 }
1011
1012 // Notify inbound ledgers that we are starting a new round
1013 inboundTransactions_.newRound(prevLgr.seq());
1014
1015 // Notify NegativeUNLVote that new validators are added
1016 if (!nowTrusted.empty())
1017 nUnlVote_.newValidators(prevLgr.seq() + 1, nowTrusted);
1018
1019 // propose only if we're in sync with the network (and validating)
1020 return validating_ && synced;
1021}
1022
1023bool
1025{
1026 return ledgerMaster_.haveValidated();
1027}
1028
1031{
1032 return ledgerMaster_.getValidLedgerIndex();
1033}
1034
1037{
1038 return app_.getValidators().getQuorumKeys();
1039}
1040
1043 Ledger_t::Seq const seq,
1045{
1046 return app_.getValidations().laggards(seq, trustedKeys);
1047}
1048
1049bool
1051{
1052 return validatorKeys_.keys.has_value();
1053}
1054
1055void
1057{
1058 if ((positions == 0u) && app_.getOPs().isFull())
1059 app_.getOPs().setMode(OperatingMode::CONNECTED);
1060}
1061
1062void
1064 NetClock::time_point const& now,
1065 RCLCxLedger::ID const& prevLgrId,
1066 RCLCxLedger const& prevLgr,
1067 hash_set<NodeID> const& nowUntrusted,
1068 hash_set<NodeID> const& nowTrusted,
1070{
1071 std::scoped_lock const _{mutex_};
1072 consensus_.startRound(
1073 now, prevLgrId, prevLgr, nowUntrusted, adaptor_.preStartRound(prevLgr, nowTrusted), clog);
1074}
1075
1077 : j_(j)
1078{
1079 if (!validating && !j.info())
1080 return;
1083 header_ = "ConsensusLogger ";
1084 header_ += label;
1085 header_ += ": ";
1086}
1087
1089{
1090 if (!ss_)
1091 return;
1094 std::stringstream outSs;
1095 outSs << header_ << "duration " << (duration.count() / 1000) << '.' << std::setw(3)
1096 << std::setfill('0') << (duration.count() % 1000) << "s. " << ss_->str();
1097 j_.sink().writeAlways(beast::Severity::Info, outSs.str());
1098}
1099
1100} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:38
Stream debug() const
Definition Journal.h:297
Stream info() const
Definition Journal.h:303
Decorator for streaming out compact json.
Represents a JSON value.
Definition json_value.h:130
iterator begin()
Definition base_uint.h:117
static constexpr std::size_t size()
Definition base_uint.h:530
Holds transactions which were deferred to the next pass of consensus.
void insert(std::shared_ptr< STTx const > txn)
uint256 const & key() const
uint256 const & signingHash() const
The digest for this proposal, used for signing purposes.
std::uint32_t proposeSeq() const
Get the sequence number of this proposal.
Position const & position() const
Get the proposed position.
LedgerId const & prevLedger() const
Get the prior accepted ledger this position is based on.
bool isBowOut() const
Get whether this node left the consensus process.
NetClock::time_point const & closeTime() const
The current position on the consensus close time.
std::chrono::milliseconds read() const
beast::AbstractClock< std::chrono::steady_clock > clock_type
Clock type for measuring time within the consensus code.
Definition Consensus.h:314
Manages the acquisition and lifetime of transaction sets.
std::chrono::time_point< NetClock > time_point
Definition chrono.h:46
std::chrono::duration< rep, period > duration
Definition chrono.h:45
Slice slice() const noexcept
Definition PublicKey.h:103
std::vector< TxIDSeq > TxIDSeqVec
InboundTransactions & inboundTransactions_
Result onClose(RCLCxLedger const &ledger, NetClock::time_point const &closeTime, ConsensusMode mode)
Close the open ledger and return initial consensus position.
bool preStartRound(RCLCxLedger const &prevLedger, hash_set< NodeID > const &nowTrusted)
Called before kicking off a new consensus round.
bool validator() const
Whether I am a validator.
LedgerIndex getValidLedgerIndex() const
Adaptor(Application &app, std::unique_ptr< FeeVote > &&feeVote, LedgerMaster &ledgerMaster, LocalTxs &localTxs, InboundTransactions &inboundTransactions, ValidatorKeys const &validatorKeys, beast::Journal journal)
void updateOperatingMode(std::size_t const positions) const
Update operating mode based on current peer positions.
std::size_t proposersFinished(RCLCxLedger const &ledger, LedgerHash const &h) const
Number of proposers that have validated a ledger descended from requested ledger.
void onAccept(Result const &result, RCLCxLedger const &prevLedger, NetClock::duration const &closeResolution, ConsensusCloseTimes const &rawCloseTimes, ConsensusMode const &mode, json::Value &&consensusJson, bool const validating)
Process the accepted ledger.
void validate(RCLCxLedger const &ledger, RCLTxSet const &txns, bool proposing)
Validate the given ledger and share with peers as necessary.
std::unique_ptr< FeeVote > feeVote_
std::atomic< bool > validating_
void propose(RCLCxPeerPos::Proposal const &proposal)
Propose the given position to my peers.
RCLCxLedger buildLCL(RCLCxLedger const &previousLedger, CanonicalTXSet &retriableTxs, NetClock::time_point closeTime, bool closeTimeCorrect, NetClock::duration closeResolution, std::chrono::milliseconds roundTime, std::set< TxID > &failedTxs)
Build the new last closed ledger.
ConsensusMode mode() const
NetClock::time_point lastValidationTime_
std::size_t proposersValidated(LedgerHash const &h) const
Number of proposers that have validated the given ledger.
void notify(protocol::NodeEvent ne, RCLCxLedger const &ledger, bool haveCorrectLCL)
Notify peers of a consensus state change.
std::atomic< ConsensusMode > mode_
std::pair< std::size_t, hash_set< NodeKey_t > > getQuorumKeys() const
std::optional< RCLTxSet > acquireTxSet(RCLTxSet::ID const &setId)
Acquire the transaction set associated with a proposal.
ValidatorKeys const & validatorKeys_
void doAccept(Result const &result, RCLCxLedger const &prevLedger, NetClock::duration closeResolution, ConsensusCloseTimes const &rawCloseTimes, ConsensusMode const &mode, json::Value &&consensusJson)
Accept a new ledger based on the given transactions.
uint256 getPrevLedger(uint256 ledgerID, RCLCxLedger const &ledger, ConsensusMode mode)
Get the ID of the previous ledger/last closed ledger(LCL) on the network.
beast::Journal const j_
std::atomic< std::chrono::milliseconds > prevRoundTime_
void onForceAccept(Result const &result, RCLCxLedger const &prevLedger, NetClock::duration const &closeResolution, ConsensusCloseTimes const &rawCloseTimes, ConsensusMode const &mode, json::Value &&consensusJson)
Process the accepted ledger that was a result of simulation/force accept.
std::size_t laggards(Ledger_t::Seq const seq, hash_set< NodeKey_t > &trustedKeys) const
ConsensusResult< Adaptor > Result
std::optional< RCLCxLedger > acquireLedger(LedgerHash const &hash)
Attempt to acquire a specific ledger.
bool hasOpenTransactions() const
Whether the open ledger has any transactions.
RCLCensorshipDetector< TxID, LedgerIndex > censorshipDetector_
std::atomic< std::size_t > prevProposers_
void onModeChange(ConsensusMode before, ConsensusMode after)
Notified of change in consensus mode.
std::uint64_t const valCookie_
void share(RCLCxPeerPos const &peerPos)
Share the given proposal with all peers.
Consensus< Adaptor > consensus_
void timerEntry(NetClock::time_point const &now, std::unique_ptr< std::stringstream > const &clog={})
void startRound(NetClock::time_point const &now, RCLCxLedger::ID const &prevLgrId, RCLCxLedger const &prevLgr, hash_set< NodeID > const &nowUntrusted, hash_set< NodeID > const &nowTrusted, std::unique_ptr< std::stringstream > const &clog)
Adjust the set of trusted validators and kick-off the next round of consensus.
bool validating() const
Whether we are validating consensus ledgers.
json::Value getJson(bool full) const
beast::Journal const j_
bool peerProposal(NetClock::time_point const &now, RCLCxPeerPos const &newProposal)
void simulate(NetClock::time_point const &now, std::optional< std::chrono::milliseconds > consensusDelay)
std::recursive_mutex mutex_
RCLConsensus(Application &app, std::unique_ptr< FeeVote > &&feeVote, LedgerMaster &ledgerMaster, LocalTxs &localTxs, InboundTransactions &inboundTransactions, Consensus< Adaptor >::clock_type const &clock, ValidatorKeys const &validatorKeys, beast::Journal journal)
Constructor.
ConsensusMode mode() const
void gotTxSet(NetClock::time_point const &now, RCLTxSet const &txSet)
Represents a ledger in RCLConsensus.
Definition RCLCxLedger.h:16
LedgerHash ID
Unique identifier of a ledger.
Definition RCLCxLedger.h:19
LedgerIndex Seq
Sequence number of a ledger.
Definition RCLCxLedger.h:21
Seq const & seq() const
Sequence number of the ledger.
Definition RCLCxLedger.h:41
ID const & parentID() const
Unique identifier (hash) of this ledger's parent.
Definition RCLCxLedger.h:55
std::shared_ptr< Ledger const > ledger
The ledger instance.
ID const & id() const
Unique identifier (hash) of this ledger.
Definition RCLCxLedger.h:48
NetClock::time_point closeTime() const
The close time of this ledger.
Definition RCLCxLedger.h:76
A peer's signed, proposed position for use in RCLConsensus.
uint256 const & suppressionID() const
Unique id used by hash router to suppress duplicates.
PublicKey const & publicKey() const
Public key of peer that sent the proposal.
ConsensusProposal< NodeID, uint256, uint256 > Proposal
Slice signature() const
Signature of the proposal (not necessarily verified).
Proposal const & proposal() const
Represents a transaction in RCLConsensus.
Definition RCLCxTx.h:13
boost::intrusive_ptr< SHAMapItem const > tx
The SHAMapItem that represents the transaction.
Definition RCLCxTx.h:34
ID const & id() const
The unique identifier/hash of the transaction.
Definition RCLCxTx.h:28
Represents a set of transactions in RCLConsensus.
Definition RCLCxTx.h:43
uint256 ID
Unique identifier/hash of the set of transactions.
Definition RCLCxTx.h:46
ID id() const
The unique ID/hash of the transaction set.
Definition RCLCxTx.h:132
std::shared_ptr< SHAMap > map
The SHAMap representing the transactions.
Definition RCLCxTx.h:167
Wraps a ledger instance for use in generic Validations LedgerTrie.
beast::Journal journal() const
Collects logging information.
std::unique_ptr< std::stringstream > const & ss()
std::chrono::steady_clock::time_point start_
RclConsensusLogger(char const *label, bool validating, beast::Journal j)
std::unique_ptr< std::stringstream > ss_
Slice slice() const noexcept
Definition Serializer.h:44
std::size_t getNodesAfter(Ledger const &ledger, ID const &ledgerID)
Count the number of current trusted validators working on a ledger after the specified one.
Adaptor const & adaptor() const
Return the adaptor instance.
std::optional< std::pair< Seq, ID > > getPreferred(Ledger const &curr)
Return the sequence number and ID of the preferred working ledger.
Validator keys and manifest as set in configuration file.
T duration_cast(T... args)
T emplace_back(T... args)
T emplace(T... args)
T empty(T... args)
T insert(T... args)
T lock(T... args)
T make_shared(T... args)
T make_unique(T... args)
T max(T... args)
STL namespace.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
void handleNewValidation(Application &app, std::shared_ptr< STValidation > const &val, std::string const &source, BypassAccept const bypassAccept, std::optional< beast::Journal > j)
Handle a new validation.
std::chrono::time_point< Clock, Duration > effCloseTime(std::chrono::time_point< Clock, Duration > closeTime, std::chrono::duration< Rep, Period > resolution, std::chrono::time_point< Clock, Duration > priorCloseTime)
Calculate the effective ledger close time.
ConsensusMode
Represents how a node currently participates in Consensus.
@ WrongLedger
We have the wrong ledger and are attempting to acquire it.
@ Proposing
We are normal participant in consensus and propose our position.
@ Observing
We are observing peer positions, but not proposing our position.
sha512_half_hasher::result_type sha512Half(Args const &... args)
Returns the SHA512-Half of a series of objects.
Definition digest.h:204
std::uint32_t LedgerIndex
A ledger index.
Definition Protocol.h:259
std::enable_if_t< std::is_integral_v< Integral > &&detail::is_engine< Engine >::value, Integral > randInt(Engine &engine, Integral min, Integral max)
Return a uniformly distributed random integer.
Rules makeRulesGivenLedger(DigestAwareReadView const &ledger, Rules const &current)
Definition ReadView.cpp:61
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:93
std::unordered_set< Value, Hash, Pred, Allocator > hash_set
CsprngEngine & cryptoPrng()
The default cryptographically secure PRNG.
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:633
@ MovedOn
The network has consensus without us.
@ Yes
We have consensus along with the network.
XRPL_NO_SANITIZE_ADDRESS void rethrow()
Rethrow the exception currently being handled.
Definition contract.h:33
boost::intrusive_ptr< SHAMapItem > makeShamapitem(uint256 const &tag, Slice data)
Definition SHAMapItem.h:139
@ JtAccept
Definition Job.h:54
@ JtAdvance
Definition Job.h:48
uint256 LedgerHash
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:554
Validations< RCLValidationsAdaptor > RCLValidations
Alias for RCL-specific instantiation of generic Validations.
std::shared_ptr< Ledger > buildLedger(std::shared_ptr< Ledger const > const &parent, NetClock::time_point closeTime, bool const closeTimeCorrect, NetClock::duration closeResolution, Application &app, CanonicalTXSet &txns, std::set< TxID > &failedTxs, beast::Journal j)
Build a new ledger by applying consensus transactions.
Buffer signDigest(PublicKey const &pk, SecretKey const &sk, uint256 const &digest)
Generate a signature for a message digest.
@ TapNone
Definition ApplyView.h:13
uint256 proposalUniqueId(uint256 const &proposeHash, uint256 const &previousLedger, std::uint32_t proposeSeq, NetClock::time_point closeTime, Slice const &publicKey, Slice const &signature)
Calculate a unique identifier for a signed proposal.
@ CONNECTED
convinced we are talking to the network
Definition NetworkOPs.h:52
@ FULL
we have the ledger and can even validate
Definition NetworkOPs.h:55
bool isPseudoTx(STObject const &tx)
Check whether a transaction is a pseudo-transaction.
Definition STTx.cpp:810
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
T push_back(T... args)
T setfill(T... args)
T setw(T... args)
T str(T... args)
Stores the set of initial close times.
TxSet_t txns
The set of transactions consensus agrees go in the ledger.
ConsensusTimer roundTime
Proposal_t position
Our proposed position on transactions/close time.
Sends a message to all peers.
Definition predicates.h:12
T time_point_cast(T... args)
T time_since_epoch(T... args)
T to_string(T... args)
T what(T... args)