rippled
Loading...
Searching...
No Matches
Consensus.h
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012-2017 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#ifndef RIPPLE_CONSENSUS_CONSENSUS_H_INCLUDED
21#define RIPPLE_CONSENSUS_CONSENSUS_H_INCLUDED
22
23#include <xrpld/consensus/ConsensusParms.h>
24#include <xrpld/consensus/ConsensusProposal.h>
25#include <xrpld/consensus/ConsensusTypes.h>
26#include <xrpld/consensus/DisputedTx.h>
27#include <xrpld/consensus/LedgerTiming.h>
28#include <xrpl/basics/Log.h>
29#include <xrpl/basics/chrono.h>
30#include <xrpl/beast/utility/Journal.h>
31#include <xrpl/json/json_writer.h>
32#include <boost/logic/tribool.hpp>
33
34#include <chrono>
35#include <deque>
36#include <optional>
37#include <sstream>
38
39namespace ripple {
40
59bool
61 bool anyTransactions,
62 std::size_t prevProposers,
63 std::size_t proposersClosed,
64 std::size_t proposersValidated,
65 std::chrono::milliseconds prevRoundTime,
66 std::chrono::milliseconds timeSincePrevClose,
68 std::chrono::milliseconds idleInterval,
69 ConsensusParms const& parms,
71
88 std::size_t prevProposers,
89 std::size_t currentProposers,
90 std::size_t currentAgree,
91 std::size_t currentFinished,
92 std::chrono::milliseconds previousAgreeTime,
93 std::chrono::milliseconds currentAgreeTime,
94 ConsensusParms const& parms,
95 bool proposing,
97
286template <class Adaptor>
288{
289 using Ledger_t = typename Adaptor::Ledger_t;
290 using TxSet_t = typename Adaptor::TxSet_t;
291 using NodeID_t = typename Adaptor::NodeID_t;
292 using Tx_t = typename TxSet_t::Tx;
293 using PeerPosition_t = typename Adaptor::PeerPosition_t;
295 NodeID_t,
296 typename Ledger_t::ID,
297 typename TxSet_t::ID>;
298
300
301 // Helper class to ensure adaptor is notified whenever the ConsensusMode
302 // changes
304 {
306
307 public:
309 {
310 }
312 get() const
313 {
314 return mode_;
315 }
316
317 void
318 set(ConsensusMode mode, Adaptor& a)
319 {
320 a.onModeChange(mode_, mode);
321 mode_ = mode;
322 }
323 };
324
325public:
328
329 Consensus(Consensus&&) noexcept = default;
330
337 Consensus(clock_type const& clock, Adaptor& adaptor, beast::Journal j);
338
352 void
354 NetClock::time_point const& now,
355 typename Ledger_t::ID const& prevLedgerID,
356 Ledger_t prevLedger,
357 hash_set<NodeID_t> const& nowUntrusted,
358 bool proposing);
359
366 bool
368 NetClock::time_point const& now,
369 PeerPosition_t const& newProposal);
370
375 void
376 timerEntry(NetClock::time_point const& now);
377
383 void
384 gotTxSet(NetClock::time_point const& now, TxSet_t const& txSet);
385
402 void
404 NetClock::time_point const& now,
405 std::optional<std::chrono::milliseconds> consensusDelay);
406
414 typename Ledger_t::ID
416 {
417 return prevLedgerID_;
418 }
419
421 phase() const
422 {
423 return phase_;
424 }
425
434 getJson(bool full) const;
435
436private:
437 void
439 NetClock::time_point const& now,
440 typename Ledger_t::ID const& prevLedgerID,
441 Ledger_t const& prevLedger,
442 ConsensusMode mode);
443
444 // Change our view of the previous ledger
445 void
446 handleWrongLedger(typename Ledger_t::ID const& lgrId);
447
453 void
455
459 void
461
464 bool
466 NetClock::time_point const& now,
467 PeerPosition_t const& newProposal);
468
475 void
477
486 void
488
511 bool
512 shouldPause() const;
513
514 // Close the open ledger and establish initial position.
515 void
517
518 // Adjust our positions to try to agree with other validators.
519 void
521
522 bool
524
525 // Create disputes between our position and the provided one.
526 void
528
529 // Update our disputes given that this node has adopted a new position.
530 // Will call createDisputes as needed.
531 void
532 updateDisputes(NodeID_t const& node, TxSet_t const& other);
533
534 // Revoke our outstanding proposal, if any, and cease proposing
535 // until this round ends.
536 void
538
539 // The rounded or effective close time estimate from a proposer
542
543private:
544 Adaptor& adaptor_;
545
548 bool firstRound_ = true;
550
552
553 // How long the consensus convergence has taken, expressed as
554 // a percentage of the time that we expected it to take.
556
557 // How long has this round been open
559
561
562 // Time it took for the last consensus round to converge
564
565 //-------------------------------------------------------------------------
566 // Network time measurements of consensus progress
567
568 // The current network adjusted time. This is the network time the
569 // ledger would close if it closed now
572
573 //-------------------------------------------------------------------------
574 // Non-peer (self) consensus data
575
576 // Last validated ledger ID provided to consensus
577 typename Ledger_t::ID prevLedgerID_;
578 // Last validated ledger seen by consensus
580
581 // Transaction Sets, indexed by hash of transaction tree
583
586
587 //-------------------------------------------------------------------------
588 // Peer related consensus data
589
590 // Peer proposed positions for the current round
592
593 // Recently received peer positions, available when transitioning between
594 // ledgers or rounds
596
597 // The number of proposers who participated in the last consensus round
599
600 // nodes that have bowed out of this consensus process
602
603 // Journal for debugging
605};
606
607template <class Adaptor>
609 clock_type const& clock,
610 Adaptor& adaptor,
611 beast::Journal journal)
612 : adaptor_(adaptor), clock_(clock), j_{journal}
613{
614 JLOG(j_.debug()) << "Creating consensus object";
615}
616
617template <class Adaptor>
618void
620 NetClock::time_point const& now,
621 typename Ledger_t::ID const& prevLedgerID,
622 Ledger_t prevLedger,
623 hash_set<NodeID_t> const& nowUntrusted,
624 bool proposing)
625{
626 if (firstRound_)
627 {
628 // take our initial view of closeTime_ from the seed ledger
629 prevRoundTime_ = adaptor_.parms().ledgerIDLE_INTERVAL;
630 prevCloseTime_ = prevLedger.closeTime();
631 firstRound_ = false;
632 }
633 else
634 {
635 prevCloseTime_ = rawCloseTimes_.self;
636 }
637
638 for (NodeID_t const& n : nowUntrusted)
639 recentPeerPositions_.erase(n);
640
641 ConsensusMode startMode =
643
644 // We were handed the wrong ledger
645 if (prevLedger.id() != prevLedgerID)
646 {
647 // try to acquire the correct one
648 if (auto newLedger = adaptor_.acquireLedger(prevLedgerID))
649 {
650 prevLedger = *newLedger;
651 }
652 else // Unable to acquire the correct ledger
653 {
654 startMode = ConsensusMode::wrongLedger;
655 JLOG(j_.info())
656 << "Entering consensus with: " << previousLedger_.id();
657 JLOG(j_.info()) << "Correct LCL is: " << prevLedgerID;
658 }
659 }
660
661 startRoundInternal(now, prevLedgerID, prevLedger, startMode);
662}
663template <class Adaptor>
664void
666 NetClock::time_point const& now,
667 typename Ledger_t::ID const& prevLedgerID,
668 Ledger_t const& prevLedger,
669 ConsensusMode mode)
670{
671 phase_ = ConsensusPhase::open;
672 JLOG(j_.debug()) << "transitioned to ConsensusPhase::open";
673 mode_.set(mode, adaptor_);
674 now_ = now;
675 prevLedgerID_ = prevLedgerID;
676 previousLedger_ = prevLedger;
677 result_.reset();
678 convergePercent_ = 0;
679 haveCloseTimeConsensus_ = false;
680 openTime_.reset(clock_.now());
681 currPeerPositions_.clear();
682 acquired_.clear();
683 rawCloseTimes_.peers.clear();
684 rawCloseTimes_.self = {};
685 deadNodes_.clear();
686
687 closeResolution_ = getNextLedgerTimeResolution(
688 previousLedger_.closeTimeResolution(),
689 previousLedger_.closeAgree(),
690 previousLedger_.seq() + typename Ledger_t::Seq{1});
691
692 playbackProposals();
693 if (currPeerPositions_.size() > (prevProposers_ / 2))
694 {
695 // We may be falling behind, don't wait for the timer
696 // consider closing the ledger immediately
697 timerEntry(now_);
698 }
699}
700
701template <class Adaptor>
702bool
704 NetClock::time_point const& now,
705 PeerPosition_t const& newPeerPos)
706{
707 auto const& peerID = newPeerPos.proposal().nodeID();
708
709 // Always need to store recent positions
710 {
711 auto& props = recentPeerPositions_[peerID];
712
713 if (props.size() >= 10)
714 props.pop_front();
715
716 props.push_back(newPeerPos);
717 }
718 return peerProposalInternal(now, newPeerPos);
719}
720
721template <class Adaptor>
722bool
724 NetClock::time_point const& now,
725 PeerPosition_t const& newPeerPos)
726{
727 // Nothing to do for now if we are currently working on a ledger
728 if (phase_ == ConsensusPhase::accepted)
729 return false;
730
731 now_ = now;
732
733 auto const& newPeerProp = newPeerPos.proposal();
734
735 if (newPeerProp.prevLedger() != prevLedgerID_)
736 {
737 JLOG(j_.debug()) << "Got proposal for " << newPeerProp.prevLedger()
738 << " but we are on " << prevLedgerID_;
739 return false;
740 }
741
742 auto const& peerID = newPeerProp.nodeID();
743
744 if (deadNodes_.find(peerID) != deadNodes_.end())
745 {
746 JLOG(j_.info()) << "Position from dead node: " << peerID;
747 return false;
748 }
749
750 {
751 // update current position
752 auto peerPosIt = currPeerPositions_.find(peerID);
753
754 if (peerPosIt != currPeerPositions_.end())
755 {
756 if (newPeerProp.proposeSeq() <=
757 peerPosIt->second.proposal().proposeSeq())
758 {
759 return false;
760 }
761 }
762
763 if (newPeerProp.isBowOut())
764 {
765 JLOG(j_.info()) << "Peer " << peerID << " bows out";
766 if (result_)
767 {
768 for (auto& it : result_->disputes)
769 it.second.unVote(peerID);
770 }
771 if (peerPosIt != currPeerPositions_.end())
772 currPeerPositions_.erase(peerID);
773 deadNodes_.insert(peerID);
774
775 return true;
776 }
777
778 if (peerPosIt != currPeerPositions_.end())
779 peerPosIt->second = newPeerPos;
780 else
781 currPeerPositions_.emplace(peerID, newPeerPos);
782 }
783
784 if (newPeerProp.isInitial())
785 {
786 // Record the close time estimate
787 JLOG(j_.trace()) << "Peer reports close time as "
788 << newPeerProp.closeTime().time_since_epoch().count();
789 ++rawCloseTimes_.peers[newPeerProp.closeTime()];
790 }
791
792 JLOG(j_.trace()) << "Processing peer proposal " << newPeerProp.proposeSeq()
793 << "/" << newPeerProp.position();
794
795 {
796 auto const ait = acquired_.find(newPeerProp.position());
797 if (ait == acquired_.end())
798 {
799 // acquireTxSet will return the set if it is available, or
800 // spawn a request for it and return nullopt/nullptr. It will call
801 // gotTxSet once it arrives
802 if (auto set = adaptor_.acquireTxSet(newPeerProp.position()))
803 gotTxSet(now_, *set);
804 else
805 JLOG(j_.debug()) << "Don't have tx set for peer";
806 }
807 else if (result_)
808 {
809 updateDisputes(newPeerProp.nodeID(), ait->second);
810 }
811 }
812
813 return true;
814}
815
816template <class Adaptor>
817void
819{
820 // Nothing to do if we are currently working on a ledger
821 if (phase_ == ConsensusPhase::accepted)
822 return;
823
824 now_ = now;
825
826 // Check we are on the proper ledger (this may change phase_)
827 checkLedger();
828
829 if (phase_ == ConsensusPhase::open)
830 {
831 phaseOpen();
832 }
833 else if (phase_ == ConsensusPhase::establish)
834 {
835 phaseEstablish();
836 }
837}
838
839template <class Adaptor>
840void
842 NetClock::time_point const& now,
843 TxSet_t const& txSet)
844{
845 // Nothing to do if we've finished work on a ledger
846 if (phase_ == ConsensusPhase::accepted)
847 return;
848
849 now_ = now;
850
851 auto id = txSet.id();
852
853 // If we've already processed this transaction set since requesting
854 // it from the network, there is nothing to do now
855 if (!acquired_.emplace(id, txSet).second)
856 return;
857
858 if (!result_)
859 {
860 JLOG(j_.debug()) << "Not creating disputes: no position yet.";
861 }
862 else
863 {
864 // Our position is added to acquired_ as soon as we create it,
865 // so this txSet must differ
866 XRPL_ASSERT(
867 id != result_->position.position(),
868 "ripple::Consensus::gotTxSet : updated transaction set");
869 bool any = false;
870 for (auto const& [nodeId, peerPos] : currPeerPositions_)
871 {
872 if (peerPos.proposal().position() == id)
873 {
874 updateDisputes(nodeId, txSet);
875 any = true;
876 }
877 }
878
879 if (!any)
880 {
881 JLOG(j_.warn())
882 << "By the time we got " << id << " no peers were proposing it";
883 }
884 }
885}
886
887template <class Adaptor>
888void
890 NetClock::time_point const& now,
892{
893 using namespace std::chrono_literals;
894 JLOG(j_.info()) << "Simulating consensus";
895 now_ = now;
896 closeLedger();
897 result_->roundTime.tick(consensusDelay.value_or(100ms));
898 result_->proposers = prevProposers_ = currPeerPositions_.size();
899 prevRoundTime_ = result_->roundTime.read();
901 adaptor_.onForceAccept(
902 *result_,
903 previousLedger_,
904 closeResolution_,
905 rawCloseTimes_,
906 mode_.get(),
907 getJson(true));
908 JLOG(j_.info()) << "Simulation complete";
909}
910
911template <class Adaptor>
914{
915 using std::to_string;
916 using Int = Json::Value::Int;
917
919
920 ret["proposing"] = (mode_.get() == ConsensusMode::proposing);
921 ret["proposers"] = static_cast<int>(currPeerPositions_.size());
922
923 if (mode_.get() != ConsensusMode::wrongLedger)
924 {
925 ret["synched"] = true;
926 ret["ledger_seq"] =
927 static_cast<std::uint32_t>(previousLedger_.seq()) + 1;
928 ret["close_granularity"] = static_cast<Int>(closeResolution_.count());
929 }
930 else
931 ret["synched"] = false;
932
933 ret["phase"] = to_string(phase_);
934
935 if (result_ && !result_->disputes.empty() && !full)
936 ret["disputes"] = static_cast<Int>(result_->disputes.size());
937
938 if (result_)
939 ret["our_position"] = result_->position.getJson();
940
941 if (full)
942 {
943 if (result_)
944 ret["current_ms"] =
945 static_cast<Int>(result_->roundTime.read().count());
946 ret["converge_percent"] = convergePercent_;
947 ret["close_resolution"] = static_cast<Int>(closeResolution_.count());
948 ret["have_time_consensus"] = haveCloseTimeConsensus_;
949 ret["previous_proposers"] = static_cast<Int>(prevProposers_);
950 ret["previous_mseconds"] = static_cast<Int>(prevRoundTime_.count());
951
952 if (!currPeerPositions_.empty())
953 {
955
956 for (auto const& [nodeId, peerPos] : currPeerPositions_)
957 {
958 ppj[to_string(nodeId)] = peerPos.getJson();
959 }
960 ret["peer_positions"] = std::move(ppj);
961 }
962
963 if (!acquired_.empty())
964 {
966 for (auto const& at : acquired_)
967 {
968 acq.append(to_string(at.first));
969 }
970 ret["acquired"] = std::move(acq);
971 }
972
973 if (result_ && !result_->disputes.empty())
974 {
976 for (auto const& [txId, dispute] : result_->disputes)
977 {
978 dsj[to_string(txId)] = dispute.getJson();
979 }
980 ret["disputes"] = std::move(dsj);
981 }
982
983 if (!rawCloseTimes_.peers.empty())
984 {
986 for (auto const& ct : rawCloseTimes_.peers)
987 {
988 ctj[std::to_string(ct.first.time_since_epoch().count())] =
989 ct.second;
990 }
991 ret["close_times"] = std::move(ctj);
992 }
993
994 if (!deadNodes_.empty())
995 {
997 for (auto const& dn : deadNodes_)
998 {
999 dnj.append(to_string(dn));
1000 }
1001 ret["dead_nodes"] = std::move(dnj);
1002 }
1003 }
1004
1005 return ret;
1006}
1007
1008// Handle a change in the prior ledger during a consensus round
1009template <class Adaptor>
1010void
1011Consensus<Adaptor>::handleWrongLedger(typename Ledger_t::ID const& lgrId)
1012{
1013 XRPL_ASSERT(
1014 lgrId != prevLedgerID_ || previousLedger_.id() != lgrId,
1015 "ripple::Consensus::handleWrongLedger : have wrong ledger");
1016
1017 // Stop proposing because we are out of sync
1018 leaveConsensus();
1019
1020 // First time switching to this ledger
1021 if (prevLedgerID_ != lgrId)
1022 {
1023 prevLedgerID_ = lgrId;
1024
1025 // Clear out state
1026 if (result_)
1027 {
1028 result_->disputes.clear();
1029 result_->compares.clear();
1030 }
1031
1032 currPeerPositions_.clear();
1033 rawCloseTimes_.peers.clear();
1034 deadNodes_.clear();
1035
1036 // Get back in sync, this will also recreate disputes
1037 playbackProposals();
1038 }
1039
1040 if (previousLedger_.id() == prevLedgerID_)
1041 return;
1042
1043 // we need to switch the ledger we're working from
1044 if (auto newLedger = adaptor_.acquireLedger(prevLedgerID_))
1045 {
1046 JLOG(j_.info()) << "Have the consensus ledger " << prevLedgerID_;
1047 startRoundInternal(
1048 now_, lgrId, *newLedger, ConsensusMode::switchedLedger);
1049 }
1050 else
1051 {
1052 mode_.set(ConsensusMode::wrongLedger, adaptor_);
1053 }
1054}
1055
1056template <class Adaptor>
1057void
1059{
1060 auto netLgr =
1061 adaptor_.getPrevLedger(prevLedgerID_, previousLedger_, mode_.get());
1062
1063 if (netLgr != prevLedgerID_)
1064 {
1065 JLOG(j_.warn()) << "View of consensus changed during "
1066 << to_string(phase_) << " status=" << to_string(phase_)
1067 << ", " << " mode=" << to_string(mode_.get());
1068 JLOG(j_.warn()) << prevLedgerID_ << " to " << netLgr;
1069 JLOG(j_.warn()) << Json::Compact{previousLedger_.getJson()};
1070 JLOG(j_.debug()) << "State on consensus change "
1071 << Json::Compact{getJson(true)};
1072 handleWrongLedger(netLgr);
1073 }
1074 else if (previousLedger_.id() != prevLedgerID_)
1075 handleWrongLedger(netLgr);
1076}
1077
1078template <class Adaptor>
1079void
1081{
1082 for (auto const& it : recentPeerPositions_)
1083 {
1084 for (auto const& pos : it.second)
1085 {
1086 if (pos.proposal().prevLedger() == prevLedgerID_)
1087 {
1088 if (peerProposalInternal(now_, pos))
1089 adaptor_.share(pos);
1090 }
1091 }
1092 }
1093}
1094
1095template <class Adaptor>
1096void
1098{
1099 using namespace std::chrono;
1100
1101 // it is shortly before ledger close time
1102 bool anyTransactions = adaptor_.hasOpenTransactions();
1103 auto proposersClosed = currPeerPositions_.size();
1104 auto proposersValidated = adaptor_.proposersValidated(prevLedgerID_);
1105
1106 openTime_.tick(clock_.now());
1107
1108 // This computes how long since last ledger's close time
1109 milliseconds sinceClose;
1110 {
1111 bool previousCloseCorrect =
1112 (mode_.get() != ConsensusMode::wrongLedger) &&
1113 previousLedger_.closeAgree() &&
1114 (previousLedger_.closeTime() !=
1115 (previousLedger_.parentCloseTime() + 1s));
1116
1117 auto lastCloseTime = previousCloseCorrect
1118 ? previousLedger_.closeTime() // use consensus timing
1119 : prevCloseTime_; // use the time we saw internally
1120
1121 if (now_ >= lastCloseTime)
1122 sinceClose = duration_cast<milliseconds>(now_ - lastCloseTime);
1123 else
1124 sinceClose = -duration_cast<milliseconds>(lastCloseTime - now_);
1125 }
1126
1127 auto const idleInterval = std::max<milliseconds>(
1128 adaptor_.parms().ledgerIDLE_INTERVAL,
1129 2 * previousLedger_.closeTimeResolution());
1130
1131 // Decide if we should close the ledger
1133 anyTransactions,
1134 prevProposers_,
1135 proposersClosed,
1136 proposersValidated,
1137 prevRoundTime_,
1138 sinceClose,
1139 openTime_.read(),
1140 idleInterval,
1141 adaptor_.parms(),
1142 j_))
1143 {
1144 closeLedger();
1145 }
1146}
1147
1148template <class Adaptor>
1149bool
1151{
1152 auto const& parms = adaptor_.parms();
1153 std::uint32_t const ahead(
1154 previousLedger_.seq() -
1155 std::min(adaptor_.getValidLedgerIndex(), previousLedger_.seq()));
1156 auto [quorum, trustedKeys] = adaptor_.getQuorumKeys();
1157 std::size_t const totalValidators = trustedKeys.size();
1158 std::size_t laggards =
1159 adaptor_.laggards(previousLedger_.seq(), trustedKeys);
1160 std::size_t const offline = trustedKeys.size();
1161
1162 std::stringstream vars;
1163 vars << " consensuslog (working seq: " << previousLedger_.seq() << ", "
1164 << "validated seq: " << adaptor_.getValidLedgerIndex() << ", "
1165 << "am validator: " << adaptor_.validator() << ", "
1166 << "have validated: " << adaptor_.haveValidated() << ", "
1167 << "roundTime: " << result_->roundTime.read().count() << ", "
1168 << "max consensus time: " << parms.ledgerMAX_CONSENSUS.count() << ", "
1169 << "validators: " << totalValidators << ", "
1170 << "laggards: " << laggards << ", " << "offline: " << offline << ", "
1171 << "quorum: " << quorum << ")";
1172
1173 if (!ahead || !laggards || !totalValidators || !adaptor_.validator() ||
1174 !adaptor_.haveValidated() ||
1175 result_->roundTime.read() > parms.ledgerMAX_CONSENSUS)
1176 {
1177 j_.debug() << "not pausing (early)" << vars.str();
1178 return false;
1179 }
1180
1181 bool willPause = false;
1182
1196 constexpr static std::size_t maxPausePhase = 4;
1197
1217 std::size_t const phase = (ahead - 1) % (maxPausePhase + 1);
1218
1219 // validators that remain after the laggards() function are considered
1220 // offline, and should be considered as laggards for purposes of
1221 // evaluating whether the threshold for non-laggards has been reached.
1222 switch (phase)
1223 {
1224 case 0:
1225 // Laggards and offline shouldn't preclude consensus.
1226 if (laggards + offline > totalValidators - quorum)
1227 willPause = true;
1228 break;
1229 case maxPausePhase:
1230 // No tolerance.
1231 willPause = true;
1232 break;
1233 default:
1234 // Ensure that sufficient validators are known to be not lagging.
1235 // Their sufficiently most recent validation sequence was equal to
1236 // or greater than our own.
1237 //
1238 // The threshold is the amount required for quorum plus
1239 // the proportion of the remainder based on number of intermediate
1240 // phases between 0 and max.
1241 float const nonLaggards = totalValidators - (laggards + offline);
1242 float const quorumRatio =
1243 static_cast<float>(quorum) / totalValidators;
1244 float const allowedDissent = 1.0f - quorumRatio;
1245 float const phaseFactor = static_cast<float>(phase) / maxPausePhase;
1246
1247 if (nonLaggards / totalValidators <
1248 quorumRatio + (allowedDissent * phaseFactor))
1249 {
1250 willPause = true;
1251 }
1252 }
1253
1254 if (willPause)
1255 j_.warn() << "pausing" << vars.str();
1256 else
1257 j_.debug() << "not pausing" << vars.str();
1258 return willPause;
1259}
1260
1261template <class Adaptor>
1262void
1264{
1265 // can only establish consensus if we already took a stance
1266 XRPL_ASSERT(result_, "ripple::Consensus::phaseEstablish : result is set");
1267
1268 using namespace std::chrono;
1269 ConsensusParms const& parms = adaptor_.parms();
1270
1271 result_->roundTime.tick(clock_.now());
1272 result_->proposers = currPeerPositions_.size();
1273
1274 convergePercent_ = result_->roundTime.read() * 100 /
1275 std::max<milliseconds>(prevRoundTime_, parms.avMIN_CONSENSUS_TIME);
1276
1277 // Give everyone a chance to take an initial position
1278 if (result_->roundTime.read() < parms.ledgerMIN_CONSENSUS)
1279 return;
1280
1281 updateOurPositions();
1282
1283 // Nothing to do if too many laggards or we don't have consensus.
1284 if (shouldPause() || !haveConsensus())
1285 return;
1286
1287 if (!haveCloseTimeConsensus_)
1288 {
1289 JLOG(j_.info()) << "We have TX consensus but not CT consensus";
1290 return;
1291 }
1292
1293 JLOG(j_.info()) << "Converge cutoff (" << currPeerPositions_.size()
1294 << " participants)";
1295 adaptor_.updateOperatingMode(currPeerPositions_.size());
1296 prevProposers_ = currPeerPositions_.size();
1297 prevRoundTime_ = result_->roundTime.read();
1298 phase_ = ConsensusPhase::accepted;
1299 JLOG(j_.debug()) << "transitioned to ConsensusPhase::accepted";
1300 adaptor_.onAccept(
1301 *result_,
1302 previousLedger_,
1303 closeResolution_,
1304 rawCloseTimes_,
1305 mode_.get(),
1306 getJson(true));
1307}
1308
1309template <class Adaptor>
1310void
1312{
1313 // We should not be closing if we already have a position
1314 XRPL_ASSERT(!result_, "ripple::Consensus::closeLedger : result is not set");
1315
1317 JLOG(j_.debug()) << "transitioned to ConsensusPhase::establish";
1318 rawCloseTimes_.self = now_;
1319
1320 result_.emplace(adaptor_.onClose(previousLedger_, now_, mode_.get()));
1321 result_->roundTime.reset(clock_.now());
1322 // Share the newly created transaction set if we haven't already
1323 // received it from a peer
1324 if (acquired_.emplace(result_->txns.id(), result_->txns).second)
1325 adaptor_.share(result_->txns);
1326
1327 if (mode_.get() == ConsensusMode::proposing)
1328 adaptor_.propose(result_->position);
1329
1330 // Create disputes with any peer positions we have transactions for
1331 for (auto const& pit : currPeerPositions_)
1332 {
1333 auto const& pos = pit.second.proposal().position();
1334 auto const it = acquired_.find(pos);
1335 if (it != acquired_.end())
1336 {
1337 createDisputes(it->second);
1338 }
1339 }
1340}
1341
1354inline int
1355participantsNeeded(int participants, int percent)
1356{
1357 int result = ((participants * percent) + (percent / 2)) / 100;
1358
1359 return (result == 0) ? 1 : result;
1360}
1361
1362template <class Adaptor>
1363void
1365{
1366 // We must have a position if we are updating it
1367 XRPL_ASSERT(
1368 result_, "ripple::Consensus::updateOurPositions : result is set");
1369 ConsensusParms const& parms = adaptor_.parms();
1370
1371 // Compute a cutoff time
1372 auto const peerCutoff = now_ - parms.proposeFRESHNESS;
1373 auto const ourCutoff = now_ - parms.proposeINTERVAL;
1374
1375 // Verify freshness of peer positions and compute close times
1377 {
1378 auto it = currPeerPositions_.begin();
1379 while (it != currPeerPositions_.end())
1380 {
1381 Proposal_t const& peerProp = it->second.proposal();
1382 if (peerProp.isStale(peerCutoff))
1383 {
1384 // peer's proposal is stale, so remove it
1385 NodeID_t const& peerID = peerProp.nodeID();
1386 JLOG(j_.warn()) << "Removing stale proposal from " << peerID;
1387 for (auto& dt : result_->disputes)
1388 dt.second.unVote(peerID);
1389 it = currPeerPositions_.erase(it);
1390 }
1391 else
1392 {
1393 // proposal is still fresh
1394 ++closeTimeVotes[asCloseTime(peerProp.closeTime())];
1395 ++it;
1396 }
1397 }
1398 }
1399
1400 // This will stay unseated unless there are any changes
1401 std::optional<TxSet_t> ourNewSet;
1402
1403 // Update votes on disputed transactions
1404 {
1406 for (auto& [txId, dispute] : result_->disputes)
1407 {
1408 // Because the threshold for inclusion increases,
1409 // time can change our position on a dispute
1410 if (dispute.updateVote(
1411 convergePercent_,
1412 mode_.get() == ConsensusMode::proposing,
1413 parms))
1414 {
1415 if (!mutableSet)
1416 mutableSet.emplace(result_->txns);
1417
1418 if (dispute.getOurVote())
1419 {
1420 // now a yes
1421 mutableSet->insert(dispute.tx());
1422 }
1423 else
1424 {
1425 // now a no
1426 mutableSet->erase(txId);
1427 }
1428 }
1429 }
1430
1431 if (mutableSet)
1432 ourNewSet.emplace(std::move(*mutableSet));
1433 }
1434
1435 NetClock::time_point consensusCloseTime = {};
1436 haveCloseTimeConsensus_ = false;
1437
1438 if (currPeerPositions_.empty())
1439 {
1440 // no other times
1441 haveCloseTimeConsensus_ = true;
1442 consensusCloseTime = asCloseTime(result_->position.closeTime());
1443 }
1444 else
1445 {
1446 int neededWeight;
1447
1448 if (convergePercent_ < parms.avMID_CONSENSUS_TIME)
1449 neededWeight = parms.avINIT_CONSENSUS_PCT;
1450 else if (convergePercent_ < parms.avLATE_CONSENSUS_TIME)
1451 neededWeight = parms.avMID_CONSENSUS_PCT;
1452 else if (convergePercent_ < parms.avSTUCK_CONSENSUS_TIME)
1453 neededWeight = parms.avLATE_CONSENSUS_PCT;
1454 else
1455 neededWeight = parms.avSTUCK_CONSENSUS_PCT;
1456
1457 int participants = currPeerPositions_.size();
1458 if (mode_.get() == ConsensusMode::proposing)
1459 {
1460 ++closeTimeVotes[asCloseTime(result_->position.closeTime())];
1461 ++participants;
1462 }
1463
1464 // Threshold for non-zero vote
1465 int threshVote = participantsNeeded(participants, neededWeight);
1466
1467 // Threshold to declare consensus
1468 int const threshConsensus =
1469 participantsNeeded(participants, parms.avCT_CONSENSUS_PCT);
1470
1471 JLOG(j_.info()) << "Proposers:" << currPeerPositions_.size()
1472 << " nw:" << neededWeight << " thrV:" << threshVote
1473 << " thrC:" << threshConsensus;
1474
1475 for (auto const& [t, v] : closeTimeVotes)
1476 {
1477 JLOG(j_.debug())
1478 << "CCTime: seq "
1479 << static_cast<std::uint32_t>(previousLedger_.seq()) + 1 << ": "
1480 << t.time_since_epoch().count() << " has " << v << ", "
1481 << threshVote << " required";
1482
1483 if (v >= threshVote)
1484 {
1485 // A close time has enough votes for us to try to agree
1486 consensusCloseTime = t;
1487 threshVote = v;
1488
1489 if (threshVote >= threshConsensus)
1490 haveCloseTimeConsensus_ = true;
1491 }
1492 }
1493
1494 if (!haveCloseTimeConsensus_)
1495 {
1496 JLOG(j_.debug())
1497 << "No CT consensus:" << " Proposers:"
1498 << currPeerPositions_.size()
1499 << " Mode:" << to_string(mode_.get())
1500 << " Thresh:" << threshConsensus
1501 << " Pos:" << consensusCloseTime.time_since_epoch().count();
1502 }
1503 }
1504
1505 if (!ourNewSet &&
1506 ((consensusCloseTime != asCloseTime(result_->position.closeTime())) ||
1507 result_->position.isStale(ourCutoff)))
1508 {
1509 // close time changed or our position is stale
1510 ourNewSet.emplace(result_->txns);
1511 }
1512
1513 if (ourNewSet)
1514 {
1515 auto newID = ourNewSet->id();
1516
1517 result_->txns = std::move(*ourNewSet);
1518
1519 JLOG(j_.info()) << "Position change: CTime "
1520 << consensusCloseTime.time_since_epoch().count()
1521 << ", tx " << newID;
1522
1523 result_->position.changePosition(newID, consensusCloseTime, now_);
1524
1525 // Share our new transaction set and update disputes
1526 // if we haven't already received it
1527 if (acquired_.emplace(newID, result_->txns).second)
1528 {
1529 if (!result_->position.isBowOut())
1530 adaptor_.share(result_->txns);
1531
1532 for (auto const& [nodeId, peerPos] : currPeerPositions_)
1533 {
1534 Proposal_t const& p = peerPos.proposal();
1535 if (p.position() == newID)
1536 updateDisputes(nodeId, result_->txns);
1537 }
1538 }
1539
1540 // Share our new position if we are still participating this round
1541 if (!result_->position.isBowOut() &&
1542 (mode_.get() == ConsensusMode::proposing))
1543 adaptor_.propose(result_->position);
1544 }
1545}
1546
1547template <class Adaptor>
1548bool
1550{
1551 // Must have a stance if we are checking for consensus
1552 XRPL_ASSERT(result_, "ripple::Consensus::haveConsensus : has result");
1553
1554 // CHECKME: should possibly count unacquired TX sets as disagreeing
1555 int agree = 0, disagree = 0;
1556
1557 auto ourPosition = result_->position.position();
1558
1559 // Count number of agreements/disagreements with our position
1560 for (auto const& [nodeId, peerPos] : currPeerPositions_)
1561 {
1562 Proposal_t const& peerProp = peerPos.proposal();
1563 if (peerProp.position() == ourPosition)
1564 {
1565 ++agree;
1566 }
1567 else
1568 {
1569 JLOG(j_.debug()) << nodeId << " has " << peerProp.position();
1570 ++disagree;
1571 }
1572 }
1573 auto currentFinished =
1574 adaptor_.proposersFinished(previousLedger_, prevLedgerID_);
1575
1576 JLOG(j_.debug()) << "Checking for TX consensus: agree=" << agree
1577 << ", disagree=" << disagree;
1578
1579 // Determine if we actually have consensus or not
1580 result_->state = checkConsensus(
1581 prevProposers_,
1582 agree + disagree,
1583 agree,
1584 currentFinished,
1585 prevRoundTime_,
1586 result_->roundTime.read(),
1587 adaptor_.parms(),
1588 mode_.get() == ConsensusMode::proposing,
1589 j_);
1590
1591 if (result_->state == ConsensusState::No)
1592 return false;
1593
1594 // There is consensus, but we need to track if the network moved on
1595 // without us.
1596 if (result_->state == ConsensusState::MovedOn)
1597 {
1598 JLOG(j_.error()) << "Unable to reach consensus";
1599 JLOG(j_.error()) << Json::Compact{getJson(true)};
1600 }
1601
1602 return true;
1603}
1604
1605template <class Adaptor>
1606void
1608{
1609 if (mode_.get() == ConsensusMode::proposing)
1610 {
1611 if (result_ && !result_->position.isBowOut())
1612 {
1613 result_->position.bowOut(now_);
1614 adaptor_.propose(result_->position);
1615 }
1616
1617 mode_.set(ConsensusMode::observing, adaptor_);
1618 JLOG(j_.info()) << "Bowing out of consensus";
1619 }
1620}
1621
1622template <class Adaptor>
1623void
1625{
1626 // Cannot create disputes without our stance
1627 XRPL_ASSERT(result_, "ripple::Consensus::createDisputes : result is set");
1628
1629 // Only create disputes if this is a new set
1630 if (!result_->compares.emplace(o.id()).second)
1631 return;
1632
1633 // Nothing to dispute if we agree
1634 if (result_->txns.id() == o.id())
1635 return;
1636
1637 JLOG(j_.debug()) << "createDisputes " << result_->txns.id() << " to "
1638 << o.id();
1639
1640 auto differences = result_->txns.compare(o);
1641
1642 int dc = 0;
1643
1644 for (auto const& [txId, inThisSet] : differences)
1645 {
1646 ++dc;
1647 // create disputed transactions (from the ledger that has them)
1648 XRPL_ASSERT(
1649 (inThisSet && result_->txns.find(txId) && !o.find(txId)) ||
1650 (!inThisSet && !result_->txns.find(txId) && o.find(txId)),
1651 "ripple::Consensus::createDisputes : has disputed transactions");
1652
1653 Tx_t tx = inThisSet ? result_->txns.find(txId) : o.find(txId);
1654 auto txID = tx.id();
1655
1656 if (result_->disputes.find(txID) != result_->disputes.end())
1657 continue;
1658
1659 JLOG(j_.debug()) << "Transaction " << txID << " is disputed";
1660
1661 typename Result::Dispute_t dtx{
1662 tx,
1663 result_->txns.exists(txID),
1664 std::max(prevProposers_, currPeerPositions_.size()),
1665 j_};
1666
1667 // Update all of the available peer's votes on the disputed transaction
1668 for (auto const& [nodeId, peerPos] : currPeerPositions_)
1669 {
1670 Proposal_t const& peerProp = peerPos.proposal();
1671 auto const cit = acquired_.find(peerProp.position());
1672 if (cit != acquired_.end())
1673 dtx.setVote(nodeId, cit->second.exists(txID));
1674 }
1675 adaptor_.share(dtx.tx());
1676
1677 result_->disputes.emplace(txID, std::move(dtx));
1678 }
1679 JLOG(j_.debug()) << dc << " differences found";
1680}
1681
1682template <class Adaptor>
1683void
1685{
1686 // Cannot updateDisputes without our stance
1687 XRPL_ASSERT(result_, "ripple::Consensus::updateDisputes : result is set");
1688
1689 // Ensure we have created disputes against this set if we haven't seen
1690 // it before
1691 if (result_->compares.find(other.id()) == result_->compares.end())
1692 createDisputes(other);
1693
1694 for (auto& it : result_->disputes)
1695 {
1696 auto& d = it.second;
1697 d.setVote(node, other.exists(d.tx().id()));
1698 }
1699}
1700
1701template <class Adaptor>
1704{
1705 return roundCloseTime(raw, closeResolution_);
1706}
1707
1708} // namespace ripple
1709
1710#endif
T begin(T... args)
Decorator for streaming out compact json.
Definition: json_writer.h:317
Represents a JSON value.
Definition: json_value.h:147
Value & append(const Value &value)
Append value to array at the end.
Definition: json_value.cpp:891
Json::Int Int
Definition: json_value.h:155
A generic endpoint for log messages.
Definition: Journal.h:59
Stream error() const
Definition: Journal.h:335
Stream debug() const
Definition: Journal.h:317
Stream info() const
Definition: Journal.h:323
Stream trace() const
Severity stream access functions.
Definition: Journal.h:311
Stream warn() const
Definition: Journal.h:329
NodeID_t const & nodeID() const
Identifying which peer took this position.
NetClock::time_point const & closeTime() const
The current position on the consensus close time.
Position_t const & position() const
Get the proposed position.
bool isStale(NetClock::time_point cutoff) const
Get whether this position is stale relative to the provided cutoff.
Measures the duration of phases of consensus.
void set(ConsensusMode mode, Adaptor &a)
Definition: Consensus.h:318
MonitoredMode(ConsensusMode m)
Definition: Consensus.h:308
ConsensusMode get() const
Definition: Consensus.h:312
Generic implementation of consensus algorithm.
Definition: Consensus.h:288
void playbackProposals()
If we radically changed our consensus context for some reason, we need to replay recent proposals so ...
Definition: Consensus.h:1080
void timerEntry(NetClock::time_point const &now)
Call periodically to drive consensus forward.
Definition: Consensus.h:818
void updateOurPositions()
Definition: Consensus.h:1364
ConsensusTimer openTime_
Definition: Consensus.h:558
typename Adaptor::PeerPosition_t PeerPosition_t
Definition: Consensus.h:293
ConsensusPhase phase_
Definition: Consensus.h:546
NetClock::time_point prevCloseTime_
Definition: Consensus.h:571
clock_type const & clock_
Definition: Consensus.h:551
bool haveConsensus()
Definition: Consensus.h:1549
void updateDisputes(NodeID_t const &node, TxSet_t const &other)
Definition: Consensus.h:1684
Ledger_t previousLedger_
Definition: Consensus.h:579
typename Adaptor::TxSet_t TxSet_t
Definition: Consensus.h:290
Ledger_t::ID prevLedgerID() const
Get the previous ledger ID.
Definition: Consensus.h:415
hash_map< NodeID_t, std::deque< PeerPosition_t > > recentPeerPositions_
Definition: Consensus.h:595
void simulate(NetClock::time_point const &now, std::optional< std::chrono::milliseconds > consensusDelay)
Simulate the consensus process without any network traffic.
Definition: Consensus.h:889
Json::Value getJson(bool full) const
Get the Json state of the consensus process.
Definition: Consensus.h:913
typename TxSet_t::Tx Tx_t
Definition: Consensus.h:292
void createDisputes(TxSet_t const &o)
Definition: Consensus.h:1624
Consensus(Consensus &&) noexcept=default
void leaveConsensus()
Definition: Consensus.h:1607
void phaseOpen()
Handle pre-close phase.
Definition: Consensus.h:1097
void handleWrongLedger(typename Ledger_t::ID const &lgrId)
Definition: Consensus.h:1011
NetClock::time_point now_
Definition: Consensus.h:570
std::size_t prevProposers_
Definition: Consensus.h:598
NetClock::time_point asCloseTime(NetClock::time_point raw) const
Definition: Consensus.h:1703
beast::Journal const j_
Definition: Consensus.h:604
void gotTxSet(NetClock::time_point const &now, TxSet_t const &txSet)
Process a transaction set acquired from the network.
Definition: Consensus.h:841
void checkLedger()
Check if our previous ledger matches the network's.
Definition: Consensus.h:1058
bool shouldPause() const
Evaluate whether pausing increases likelihood of validation.
Definition: Consensus.h:1150
void startRoundInternal(NetClock::time_point const &now, typename Ledger_t::ID const &prevLedgerID, Ledger_t const &prevLedger, ConsensusMode mode)
Definition: Consensus.h:665
Adaptor & adaptor_
Definition: Consensus.h:544
typename Adaptor::Ledger_t Ledger_t
Definition: Consensus.h:289
ConsensusPhase phase() const
Definition: Consensus.h:421
hash_map< typename TxSet_t::ID, const TxSet_t > acquired_
Definition: Consensus.h:582
void phaseEstablish()
Handle establish phase.
Definition: Consensus.h:1263
typename Adaptor::NodeID_t NodeID_t
Definition: Consensus.h:291
NetClock::duration closeResolution_
Definition: Consensus.h:560
bool peerProposal(NetClock::time_point const &now, PeerPosition_t const &newProposal)
A peer has proposed a new position, adjust our tracking.
Definition: Consensus.h:703
bool peerProposalInternal(NetClock::time_point const &now, PeerPosition_t const &newProposal)
Handle a replayed or a new peer proposal.
Definition: Consensus.h:723
MonitoredMode mode_
Definition: Consensus.h:547
hash_map< NodeID_t, PeerPosition_t > currPeerPositions_
Definition: Consensus.h:591
void startRound(NetClock::time_point const &now, typename Ledger_t::ID const &prevLedgerID, Ledger_t prevLedger, hash_set< NodeID_t > const &nowUntrusted, bool proposing)
Kick-off the next round of consensus.
Definition: Consensus.h:619
ConsensusCloseTimes rawCloseTimes_
Definition: Consensus.h:585
std::chrono::milliseconds prevRoundTime_
Definition: Consensus.h:563
std::optional< Result > result_
Definition: Consensus.h:584
hash_set< NodeID_t > deadNodes_
Definition: Consensus.h:601
Ledger_t::ID prevLedgerID_
Definition: Consensus.h:577
bool haveCloseTimeConsensus_
Definition: Consensus.h:549
A transaction discovered to be in dispute during consensus.
Definition: DisputedTx.h:51
T emplace(T... args)
T max(T... args)
T min(T... args)
@ arrayValue
array value (ordered list)
Definition: json_value.h:42
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:43
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
ConsensusState checkConsensus(std::size_t prevProposers, std::size_t currentProposers, std::size_t currentAgree, std::size_t currentFinished, std::chrono::milliseconds previousAgreeTime, std::chrono::milliseconds currentAgreeTime, ConsensusParms const &parms, bool proposing, beast::Journal j)
Determine whether the network reached consensus and whether we joined.
Definition: Consensus.cpp:121
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.
@ switchedLedger
We switched ledgers since we started this consensus round but are now running on what we believe is t...
@ observing
We are observing peer positions, but not proposing our position.
bool shouldCloseLedger(bool anyTransactions, std::size_t prevProposers, std::size_t proposersClosed, std::size_t proposersValidated, std::chrono::milliseconds prevRoundTime, std::chrono::milliseconds timeSincePrevClose, std::chrono::milliseconds openTime, std::chrono::milliseconds idleInterval, ConsensusParms const &parms, beast::Journal j)
Determines whether the current ledger should close at this time.
Definition: Consensus.cpp:26
std::chrono::time_point< Clock, Duration > roundCloseTime(std::chrono::time_point< Clock, Duration > closeTime, std::chrono::duration< Rep, Period > closeResolution)
Calculates the close time for a ledger, given a close time resolution.
Definition: LedgerTiming.h:133
bool set(T &target, std::string const &name, Section const &section)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
Definition: BasicConfig.h:316
auto constexpr ledgerDefaultTimeResolution
Initial resolution of ledger close time.
Definition: LedgerTiming.h:44
ConsensusPhase
Phases of consensus for a single ledger round.
@ accepted
We have accepted a new last closed ledger and are waiting on a call to startRound to begin the next c...
@ open
We haven't closed our ledger yet, but others might have.
@ establish
Establishing consensus by exchanging proposals with our peers.
ConsensusState
Whether we have or don't have a consensus.
@ MovedOn
The network has consensus without us.
@ No
We do not have consensus.
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:629
Json::Value getJson(LedgerFill const &fill)
Return a new Json::Value representing the ledger with given options.
int participantsNeeded(int participants, int percent)
How many of the participants must agree to reach a given threshold?
Definition: Consensus.h:1355
std::chrono::duration< Rep, Period > getNextLedgerTimeResolution(std::chrono::duration< Rep, Period > previousResolution, bool previousAgree, Seq ledgerSeq)
Calculates the close time resolution for the specified ledger.
Definition: LedgerTiming.h:80
STL namespace.
T read(T... args)
T str(T... args)
Stores the set of initial close times.
Consensus algorithm parameters.
std::size_t avINIT_CONSENSUS_PCT
Percentage of nodes on our UNL that must vote yes.
std::chrono::milliseconds ledgerMIN_CONSENSUS
The number of seconds we wait minimum to ensure participation.
std::chrono::milliseconds avMIN_CONSENSUS_TIME
The minimum amount of time to consider the previous round to have taken.
std::size_t avLATE_CONSENSUS_PCT
Percentage of nodes that most vote yes after advancing.
std::size_t avSTUCK_CONSENSUS_PCT
Percentage of nodes that must vote yes after we are stuck.
std::chrono::seconds proposeFRESHNESS
How long we consider a proposal fresh.
std::size_t avLATE_CONSENSUS_TIME
Percentage of previous round duration before we advance.
std::chrono::seconds proposeINTERVAL
How often we force generating a new proposal to keep ours fresh.
std::size_t avCT_CONSENSUS_PCT
Percentage of nodes required to reach agreement on ledger close time.
std::size_t avMID_CONSENSUS_PCT
Percentage of nodes that most vote yes after advancing.
std::size_t avSTUCK_CONSENSUS_TIME
Percentage of previous round duration before we are stuck.
std::size_t avMID_CONSENSUS_TIME
Percentage of previous round duration before we advance.
Encapsulates the result of consensus.
T time_since_epoch(T... args)
T to_string(T... args)
T value_or(T... args)