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 JLOG(j_.debug()) << "PROPOSAL " << newPeerPos.render();
708 auto const& peerID = newPeerPos.proposal().nodeID();
709
710 // Always need to store recent positions
711 {
712 auto& props = recentPeerPositions_[peerID];
713
714 if (props.size() >= 10)
715 props.pop_front();
716
717 props.push_back(newPeerPos);
718 }
719 return peerProposalInternal(now, newPeerPos);
720}
721
722template <class Adaptor>
723bool
725 NetClock::time_point const& now,
726 PeerPosition_t const& newPeerPos)
727{
728 // Nothing to do for now if we are currently working on a ledger
729 if (phase_ == ConsensusPhase::accepted)
730 return false;
731
732 now_ = now;
733
734 auto const& newPeerProp = newPeerPos.proposal();
735
736 if (newPeerProp.prevLedger() != prevLedgerID_)
737 {
738 JLOG(j_.debug()) << "Got proposal for " << newPeerProp.prevLedger()
739 << " but we are on " << prevLedgerID_;
740 return false;
741 }
742
743 auto const& peerID = newPeerProp.nodeID();
744
745 if (deadNodes_.find(peerID) != deadNodes_.end())
746 {
747 JLOG(j_.info()) << "Position from dead node: " << peerID;
748 return false;
749 }
750
751 {
752 // update current position
753 auto peerPosIt = currPeerPositions_.find(peerID);
754
755 if (peerPosIt != currPeerPositions_.end())
756 {
757 if (newPeerProp.proposeSeq() <=
758 peerPosIt->second.proposal().proposeSeq())
759 {
760 return false;
761 }
762 }
763
764 if (newPeerProp.isBowOut())
765 {
766 JLOG(j_.info()) << "Peer " << peerID << " bows out";
767 if (result_)
768 {
769 for (auto& it : result_->disputes)
770 it.second.unVote(peerID);
771 }
772 if (peerPosIt != currPeerPositions_.end())
773 currPeerPositions_.erase(peerID);
774 deadNodes_.insert(peerID);
775
776 return true;
777 }
778
779 if (peerPosIt != currPeerPositions_.end())
780 peerPosIt->second = newPeerPos;
781 else
782 currPeerPositions_.emplace(peerID, newPeerPos);
783 }
784
785 if (newPeerProp.isInitial())
786 {
787 // Record the close time estimate
788 JLOG(j_.trace()) << "Peer reports close time as "
789 << newPeerProp.closeTime().time_since_epoch().count();
790 ++rawCloseTimes_.peers[newPeerProp.closeTime()];
791 }
792
793 JLOG(j_.trace()) << "Processing peer proposal " << newPeerProp.proposeSeq()
794 << "/" << newPeerProp.position();
795
796 {
797 auto const ait = acquired_.find(newPeerProp.position());
798 if (ait == acquired_.end())
799 {
800 // acquireTxSet will return the set if it is available, or
801 // spawn a request for it and return nullopt/nullptr. It will call
802 // gotTxSet once it arrives
803 if (auto set = adaptor_.acquireTxSet(newPeerProp.position()))
804 gotTxSet(now_, *set);
805 else
806 JLOG(j_.debug()) << "Don't have tx set for peer";
807 }
808 else if (result_)
809 {
810 updateDisputes(newPeerProp.nodeID(), ait->second);
811 }
812 }
813
814 return true;
815}
816
817template <class Adaptor>
818void
820{
821 // Nothing to do if we are currently working on a ledger
822 if (phase_ == ConsensusPhase::accepted)
823 return;
824
825 now_ = now;
826
827 // Check we are on the proper ledger (this may change phase_)
828 checkLedger();
829
830 if (phase_ == ConsensusPhase::open)
831 {
832 phaseOpen();
833 }
834 else if (phase_ == ConsensusPhase::establish)
835 {
836 phaseEstablish();
837 }
838}
839
840template <class Adaptor>
841void
843 NetClock::time_point const& now,
844 TxSet_t const& txSet)
845{
846 // Nothing to do if we've finished work on a ledger
847 if (phase_ == ConsensusPhase::accepted)
848 return;
849
850 now_ = now;
851
852 auto id = txSet.id();
853
854 // If we've already processed this transaction set since requesting
855 // it from the network, there is nothing to do now
856 if (!acquired_.emplace(id, txSet).second)
857 return;
858
859 if (!result_)
860 {
861 JLOG(j_.debug()) << "Not creating disputes: no position yet.";
862 }
863 else
864 {
865 // Our position is added to acquired_ as soon as we create it,
866 // so this txSet must differ
867 XRPL_ASSERT(
868 id != result_->position.position(),
869 "ripple::Consensus::gotTxSet : updated transaction set");
870 bool any = false;
871 for (auto const& [nodeId, peerPos] : currPeerPositions_)
872 {
873 if (peerPos.proposal().position() == id)
874 {
875 updateDisputes(nodeId, txSet);
876 any = true;
877 }
878 }
879
880 if (!any)
881 {
882 JLOG(j_.warn())
883 << "By the time we got " << id << " no peers were proposing it";
884 }
885 }
886}
887
888template <class Adaptor>
889void
891 NetClock::time_point const& now,
893{
894 using namespace std::chrono_literals;
895 JLOG(j_.info()) << "Simulating consensus";
896 now_ = now;
897 closeLedger();
898 result_->roundTime.tick(consensusDelay.value_or(100ms));
899 result_->proposers = prevProposers_ = currPeerPositions_.size();
900 prevRoundTime_ = result_->roundTime.read();
902 adaptor_.onForceAccept(
903 *result_,
904 previousLedger_,
905 closeResolution_,
906 rawCloseTimes_,
907 mode_.get(),
908 getJson(true));
909 JLOG(j_.info()) << "Simulation complete";
910}
911
912template <class Adaptor>
915{
916 using std::to_string;
917 using Int = Json::Value::Int;
918
920
921 ret["proposing"] = (mode_.get() == ConsensusMode::proposing);
922 ret["proposers"] = static_cast<int>(currPeerPositions_.size());
923
924 if (mode_.get() != ConsensusMode::wrongLedger)
925 {
926 ret["synched"] = true;
927 ret["ledger_seq"] =
928 static_cast<std::uint32_t>(previousLedger_.seq()) + 1;
929 ret["close_granularity"] = static_cast<Int>(closeResolution_.count());
930 }
931 else
932 ret["synched"] = false;
933
934 ret["phase"] = to_string(phase_);
935
936 if (result_ && !result_->disputes.empty() && !full)
937 ret["disputes"] = static_cast<Int>(result_->disputes.size());
938
939 if (result_)
940 ret["our_position"] = result_->position.getJson();
941
942 if (full)
943 {
944 if (result_)
945 ret["current_ms"] =
946 static_cast<Int>(result_->roundTime.read().count());
947 ret["converge_percent"] = convergePercent_;
948 ret["close_resolution"] = static_cast<Int>(closeResolution_.count());
949 ret["have_time_consensus"] = haveCloseTimeConsensus_;
950 ret["previous_proposers"] = static_cast<Int>(prevProposers_);
951 ret["previous_mseconds"] = static_cast<Int>(prevRoundTime_.count());
952
953 if (!currPeerPositions_.empty())
954 {
956
957 for (auto const& [nodeId, peerPos] : currPeerPositions_)
958 {
959 ppj[to_string(nodeId)] = peerPos.getJson();
960 }
961 ret["peer_positions"] = std::move(ppj);
962 }
963
964 if (!acquired_.empty())
965 {
967 for (auto const& at : acquired_)
968 {
969 acq.append(to_string(at.first));
970 }
971 ret["acquired"] = std::move(acq);
972 }
973
974 if (result_ && !result_->disputes.empty())
975 {
977 for (auto const& [txId, dispute] : result_->disputes)
978 {
979 dsj[to_string(txId)] = dispute.getJson();
980 }
981 ret["disputes"] = std::move(dsj);
982 }
983
984 if (!rawCloseTimes_.peers.empty())
985 {
987 for (auto const& ct : rawCloseTimes_.peers)
988 {
989 ctj[std::to_string(ct.first.time_since_epoch().count())] =
990 ct.second;
991 }
992 ret["close_times"] = std::move(ctj);
993 }
994
995 if (!deadNodes_.empty())
996 {
998 for (auto const& dn : deadNodes_)
999 {
1000 dnj.append(to_string(dn));
1001 }
1002 ret["dead_nodes"] = std::move(dnj);
1003 }
1004 }
1005
1006 return ret;
1007}
1008
1009// Handle a change in the prior ledger during a consensus round
1010template <class Adaptor>
1011void
1012Consensus<Adaptor>::handleWrongLedger(typename Ledger_t::ID const& lgrId)
1013{
1014 XRPL_ASSERT(
1015 lgrId != prevLedgerID_ || previousLedger_.id() != lgrId,
1016 "ripple::Consensus::handleWrongLedger : have wrong ledger");
1017
1018 // Stop proposing because we are out of sync
1019 leaveConsensus();
1020
1021 // First time switching to this ledger
1022 if (prevLedgerID_ != lgrId)
1023 {
1024 prevLedgerID_ = lgrId;
1025
1026 // Clear out state
1027 if (result_)
1028 {
1029 result_->disputes.clear();
1030 result_->compares.clear();
1031 }
1032
1033 currPeerPositions_.clear();
1034 rawCloseTimes_.peers.clear();
1035 deadNodes_.clear();
1036
1037 // Get back in sync, this will also recreate disputes
1038 playbackProposals();
1039 }
1040
1041 if (previousLedger_.id() == prevLedgerID_)
1042 return;
1043
1044 // we need to switch the ledger we're working from
1045 if (auto newLedger = adaptor_.acquireLedger(prevLedgerID_))
1046 {
1047 JLOG(j_.info()) << "Have the consensus ledger " << prevLedgerID_;
1048 startRoundInternal(
1049 now_, lgrId, *newLedger, ConsensusMode::switchedLedger);
1050 }
1051 else
1052 {
1053 mode_.set(ConsensusMode::wrongLedger, adaptor_);
1054 }
1055}
1056
1057template <class Adaptor>
1058void
1060{
1061 auto netLgr =
1062 adaptor_.getPrevLedger(prevLedgerID_, previousLedger_, mode_.get());
1063
1064 if (netLgr != prevLedgerID_)
1065 {
1066 JLOG(j_.warn()) << "View of consensus changed during "
1067 << to_string(phase_) << " status=" << to_string(phase_)
1068 << ", " << " mode=" << to_string(mode_.get());
1069 JLOG(j_.warn()) << prevLedgerID_ << " to " << netLgr;
1070 JLOG(j_.warn()) << Json::Compact{previousLedger_.getJson()};
1071 JLOG(j_.debug()) << "State on consensus change "
1072 << Json::Compact{getJson(true)};
1073 handleWrongLedger(netLgr);
1074 }
1075 else if (previousLedger_.id() != prevLedgerID_)
1076 handleWrongLedger(netLgr);
1077}
1078
1079template <class Adaptor>
1080void
1082{
1083 for (auto const& it : recentPeerPositions_)
1084 {
1085 for (auto const& pos : it.second)
1086 {
1087 if (pos.proposal().prevLedger() == prevLedgerID_)
1088 {
1089 if (peerProposalInternal(now_, pos))
1090 adaptor_.share(pos);
1091 }
1092 }
1093 }
1094}
1095
1096template <class Adaptor>
1097void
1099{
1100 using namespace std::chrono;
1101
1102 // it is shortly before ledger close time
1103 bool anyTransactions = adaptor_.hasOpenTransactions();
1104 auto proposersClosed = currPeerPositions_.size();
1105 auto proposersValidated = adaptor_.proposersValidated(prevLedgerID_);
1106
1107 openTime_.tick(clock_.now());
1108
1109 // This computes how long since last ledger's close time
1110 milliseconds sinceClose;
1111 {
1112 bool previousCloseCorrect =
1113 (mode_.get() != ConsensusMode::wrongLedger) &&
1114 previousLedger_.closeAgree() &&
1115 (previousLedger_.closeTime() !=
1116 (previousLedger_.parentCloseTime() + 1s));
1117
1118 auto lastCloseTime = previousCloseCorrect
1119 ? previousLedger_.closeTime() // use consensus timing
1120 : prevCloseTime_; // use the time we saw internally
1121
1122 if (now_ >= lastCloseTime)
1123 sinceClose = duration_cast<milliseconds>(now_ - lastCloseTime);
1124 else
1125 sinceClose = -duration_cast<milliseconds>(lastCloseTime - now_);
1126 }
1127
1128 auto const idleInterval = std::max<milliseconds>(
1129 adaptor_.parms().ledgerIDLE_INTERVAL,
1130 2 * previousLedger_.closeTimeResolution());
1131
1132 // Decide if we should close the ledger
1134 anyTransactions,
1135 prevProposers_,
1136 proposersClosed,
1137 proposersValidated,
1138 prevRoundTime_,
1139 sinceClose,
1140 openTime_.read(),
1141 idleInterval,
1142 adaptor_.parms(),
1143 j_))
1144 {
1145 closeLedger();
1146 }
1147}
1148
1149template <class Adaptor>
1150bool
1152{
1153 auto const& parms = adaptor_.parms();
1154 std::uint32_t const ahead(
1155 previousLedger_.seq() -
1156 std::min(adaptor_.getValidLedgerIndex(), previousLedger_.seq()));
1157 auto [quorum, trustedKeys] = adaptor_.getQuorumKeys();
1158 std::size_t const totalValidators = trustedKeys.size();
1159 std::size_t laggards =
1160 adaptor_.laggards(previousLedger_.seq(), trustedKeys);
1161 std::size_t const offline = trustedKeys.size();
1162
1163 std::stringstream vars;
1164 vars << " consensuslog (working seq: " << previousLedger_.seq() << ", "
1165 << "validated seq: " << adaptor_.getValidLedgerIndex() << ", "
1166 << "am validator: " << adaptor_.validator() << ", "
1167 << "have validated: " << adaptor_.haveValidated() << ", "
1168 << "roundTime: " << result_->roundTime.read().count() << ", "
1169 << "max consensus time: " << parms.ledgerMAX_CONSENSUS.count() << ", "
1170 << "validators: " << totalValidators << ", "
1171 << "laggards: " << laggards << ", " << "offline: " << offline << ", "
1172 << "quorum: " << quorum << ")";
1173
1174 if (!ahead || !laggards || !totalValidators || !adaptor_.validator() ||
1175 !adaptor_.haveValidated() ||
1176 result_->roundTime.read() > parms.ledgerMAX_CONSENSUS)
1177 {
1178 j_.debug() << "not pausing (early)" << vars.str();
1179 return false;
1180 }
1181
1182 bool willPause = false;
1183
1197 constexpr static std::size_t maxPausePhase = 4;
1198
1218 std::size_t const phase = (ahead - 1) % (maxPausePhase + 1);
1219
1220 // validators that remain after the laggards() function are considered
1221 // offline, and should be considered as laggards for purposes of
1222 // evaluating whether the threshold for non-laggards has been reached.
1223 switch (phase)
1224 {
1225 case 0:
1226 // Laggards and offline shouldn't preclude consensus.
1227 if (laggards + offline > totalValidators - quorum)
1228 willPause = true;
1229 break;
1230 case maxPausePhase:
1231 // No tolerance.
1232 willPause = true;
1233 break;
1234 default:
1235 // Ensure that sufficient validators are known to be not lagging.
1236 // Their sufficiently most recent validation sequence was equal to
1237 // or greater than our own.
1238 //
1239 // The threshold is the amount required for quorum plus
1240 // the proportion of the remainder based on number of intermediate
1241 // phases between 0 and max.
1242 float const nonLaggards = totalValidators - (laggards + offline);
1243 float const quorumRatio =
1244 static_cast<float>(quorum) / totalValidators;
1245 float const allowedDissent = 1.0f - quorumRatio;
1246 float const phaseFactor = static_cast<float>(phase) / maxPausePhase;
1247
1248 if (nonLaggards / totalValidators <
1249 quorumRatio + (allowedDissent * phaseFactor))
1250 {
1251 willPause = true;
1252 }
1253 }
1254
1255 if (willPause)
1256 j_.warn() << "pausing" << vars.str();
1257 else
1258 j_.debug() << "not pausing" << vars.str();
1259 return willPause;
1260}
1261
1262template <class Adaptor>
1263void
1265{
1266 // can only establish consensus if we already took a stance
1267 XRPL_ASSERT(result_, "ripple::Consensus::phaseEstablish : result is set");
1268
1269 using namespace std::chrono;
1270 ConsensusParms const& parms = adaptor_.parms();
1271
1272 result_->roundTime.tick(clock_.now());
1273 result_->proposers = currPeerPositions_.size();
1274
1275 convergePercent_ = result_->roundTime.read() * 100 /
1276 std::max<milliseconds>(prevRoundTime_, parms.avMIN_CONSENSUS_TIME);
1277
1278 // Give everyone a chance to take an initial position
1279 if (result_->roundTime.read() < parms.ledgerMIN_CONSENSUS)
1280 return;
1281
1282 updateOurPositions();
1283
1284 // Nothing to do if too many laggards or we don't have consensus.
1285 if (shouldPause() || !haveConsensus())
1286 return;
1287
1288 if (!haveCloseTimeConsensus_)
1289 {
1290 JLOG(j_.info()) << "We have TX consensus but not CT consensus";
1291 return;
1292 }
1293
1294 JLOG(j_.info()) << "Converge cutoff (" << currPeerPositions_.size()
1295 << " participants)";
1296 adaptor_.updateOperatingMode(currPeerPositions_.size());
1297 prevProposers_ = currPeerPositions_.size();
1298 prevRoundTime_ = result_->roundTime.read();
1299 phase_ = ConsensusPhase::accepted;
1300 JLOG(j_.debug()) << "transitioned to ConsensusPhase::accepted";
1301 adaptor_.onAccept(
1302 *result_,
1303 previousLedger_,
1304 closeResolution_,
1305 rawCloseTimes_,
1306 mode_.get(),
1307 getJson(true));
1308}
1309
1310template <class Adaptor>
1311void
1313{
1314 // We should not be closing if we already have a position
1315 XRPL_ASSERT(!result_, "ripple::Consensus::closeLedger : result is not set");
1316
1318 JLOG(j_.debug()) << "transitioned to ConsensusPhase::establish";
1319 rawCloseTimes_.self = now_;
1320
1321 result_.emplace(adaptor_.onClose(previousLedger_, now_, mode_.get()));
1322 result_->roundTime.reset(clock_.now());
1323 // Share the newly created transaction set if we haven't already
1324 // received it from a peer
1325 if (acquired_.emplace(result_->txns.id(), result_->txns).second)
1326 adaptor_.share(result_->txns);
1327
1328 if (mode_.get() == ConsensusMode::proposing)
1329 adaptor_.propose(result_->position);
1330
1331 // Create disputes with any peer positions we have transactions for
1332 for (auto const& pit : currPeerPositions_)
1333 {
1334 auto const& pos = pit.second.proposal().position();
1335 auto const it = acquired_.find(pos);
1336 if (it != acquired_.end())
1337 {
1338 createDisputes(it->second);
1339 }
1340 }
1341}
1342
1355inline int
1356participantsNeeded(int participants, int percent)
1357{
1358 int result = ((participants * percent) + (percent / 2)) / 100;
1359
1360 return (result == 0) ? 1 : result;
1361}
1362
1363template <class Adaptor>
1364void
1366{
1367 // We must have a position if we are updating it
1368 XRPL_ASSERT(
1369 result_, "ripple::Consensus::updateOurPositions : result is set");
1370 ConsensusParms const& parms = adaptor_.parms();
1371
1372 // Compute a cutoff time
1373 auto const peerCutoff = now_ - parms.proposeFRESHNESS;
1374 auto const ourCutoff = now_ - parms.proposeINTERVAL;
1375
1376 // Verify freshness of peer positions and compute close times
1378 {
1379 auto it = currPeerPositions_.begin();
1380 while (it != currPeerPositions_.end())
1381 {
1382 Proposal_t const& peerProp = it->second.proposal();
1383 if (peerProp.isStale(peerCutoff))
1384 {
1385 // peer's proposal is stale, so remove it
1386 NodeID_t const& peerID = peerProp.nodeID();
1387 JLOG(j_.warn()) << "Removing stale proposal from " << peerID;
1388 for (auto& dt : result_->disputes)
1389 dt.second.unVote(peerID);
1390 it = currPeerPositions_.erase(it);
1391 }
1392 else
1393 {
1394 // proposal is still fresh
1395 ++closeTimeVotes[asCloseTime(peerProp.closeTime())];
1396 ++it;
1397 }
1398 }
1399 }
1400
1401 // This will stay unseated unless there are any changes
1402 std::optional<TxSet_t> ourNewSet;
1403
1404 // Update votes on disputed transactions
1405 {
1407 for (auto& [txId, dispute] : result_->disputes)
1408 {
1409 // Because the threshold for inclusion increases,
1410 // time can change our position on a dispute
1411 if (dispute.updateVote(
1412 convergePercent_,
1413 mode_.get() == ConsensusMode::proposing,
1414 parms))
1415 {
1416 if (!mutableSet)
1417 mutableSet.emplace(result_->txns);
1418
1419 if (dispute.getOurVote())
1420 {
1421 // now a yes
1422 mutableSet->insert(dispute.tx());
1423 }
1424 else
1425 {
1426 // now a no
1427 mutableSet->erase(txId);
1428 }
1429 }
1430 }
1431
1432 if (mutableSet)
1433 ourNewSet.emplace(std::move(*mutableSet));
1434 }
1435
1436 NetClock::time_point consensusCloseTime = {};
1437 haveCloseTimeConsensus_ = false;
1438
1439 if (currPeerPositions_.empty())
1440 {
1441 // no other times
1442 haveCloseTimeConsensus_ = true;
1443 consensusCloseTime = asCloseTime(result_->position.closeTime());
1444 }
1445 else
1446 {
1447 int neededWeight;
1448
1449 if (convergePercent_ < parms.avMID_CONSENSUS_TIME)
1450 neededWeight = parms.avINIT_CONSENSUS_PCT;
1451 else if (convergePercent_ < parms.avLATE_CONSENSUS_TIME)
1452 neededWeight = parms.avMID_CONSENSUS_PCT;
1453 else if (convergePercent_ < parms.avSTUCK_CONSENSUS_TIME)
1454 neededWeight = parms.avLATE_CONSENSUS_PCT;
1455 else
1456 neededWeight = parms.avSTUCK_CONSENSUS_PCT;
1457
1458 int participants = currPeerPositions_.size();
1459 if (mode_.get() == ConsensusMode::proposing)
1460 {
1461 ++closeTimeVotes[asCloseTime(result_->position.closeTime())];
1462 ++participants;
1463 }
1464
1465 // Threshold for non-zero vote
1466 int threshVote = participantsNeeded(participants, neededWeight);
1467
1468 // Threshold to declare consensus
1469 int const threshConsensus =
1470 participantsNeeded(participants, parms.avCT_CONSENSUS_PCT);
1471
1472 JLOG(j_.info()) << "Proposers:" << currPeerPositions_.size()
1473 << " nw:" << neededWeight << " thrV:" << threshVote
1474 << " thrC:" << threshConsensus;
1475
1476 for (auto const& [t, v] : closeTimeVotes)
1477 {
1478 JLOG(j_.debug())
1479 << "CCTime: seq "
1480 << static_cast<std::uint32_t>(previousLedger_.seq()) + 1 << ": "
1481 << t.time_since_epoch().count() << " has " << v << ", "
1482 << threshVote << " required";
1483
1484 if (v >= threshVote)
1485 {
1486 // A close time has enough votes for us to try to agree
1487 consensusCloseTime = t;
1488 threshVote = v;
1489
1490 if (threshVote >= threshConsensus)
1491 haveCloseTimeConsensus_ = true;
1492 }
1493 }
1494
1495 if (!haveCloseTimeConsensus_)
1496 {
1497 JLOG(j_.debug())
1498 << "No CT consensus:" << " Proposers:"
1499 << currPeerPositions_.size()
1500 << " Mode:" << to_string(mode_.get())
1501 << " Thresh:" << threshConsensus
1502 << " Pos:" << consensusCloseTime.time_since_epoch().count();
1503 }
1504 }
1505
1506 if (!ourNewSet &&
1507 ((consensusCloseTime != asCloseTime(result_->position.closeTime())) ||
1508 result_->position.isStale(ourCutoff)))
1509 {
1510 // close time changed or our position is stale
1511 ourNewSet.emplace(result_->txns);
1512 }
1513
1514 if (ourNewSet)
1515 {
1516 auto newID = ourNewSet->id();
1517
1518 result_->txns = std::move(*ourNewSet);
1519
1520 JLOG(j_.info()) << "Position change: CTime "
1521 << consensusCloseTime.time_since_epoch().count()
1522 << ", tx " << newID;
1523
1524 result_->position.changePosition(newID, consensusCloseTime, now_);
1525
1526 // Share our new transaction set and update disputes
1527 // if we haven't already received it
1528 if (acquired_.emplace(newID, result_->txns).second)
1529 {
1530 if (!result_->position.isBowOut())
1531 adaptor_.share(result_->txns);
1532
1533 for (auto const& [nodeId, peerPos] : currPeerPositions_)
1534 {
1535 Proposal_t const& p = peerPos.proposal();
1536 if (p.position() == newID)
1537 updateDisputes(nodeId, result_->txns);
1538 }
1539 }
1540
1541 // Share our new position if we are still participating this round
1542 if (!result_->position.isBowOut() &&
1543 (mode_.get() == ConsensusMode::proposing))
1544 adaptor_.propose(result_->position);
1545 }
1546}
1547
1548template <class Adaptor>
1549bool
1551{
1552 // Must have a stance if we are checking for consensus
1553 XRPL_ASSERT(result_, "ripple::Consensus::haveConsensus : has result");
1554
1555 // CHECKME: should possibly count unacquired TX sets as disagreeing
1556 int agree = 0, disagree = 0;
1557
1558 auto ourPosition = result_->position.position();
1559
1560 // Count number of agreements/disagreements with our position
1561 for (auto const& [nodeId, peerPos] : currPeerPositions_)
1562 {
1563 Proposal_t const& peerProp = peerPos.proposal();
1564 if (peerProp.position() == ourPosition)
1565 {
1566 ++agree;
1567 }
1568 else
1569 {
1570 JLOG(j_.debug()) << nodeId << " has " << peerProp.position();
1571 ++disagree;
1572 }
1573 }
1574 auto currentFinished =
1575 adaptor_.proposersFinished(previousLedger_, prevLedgerID_);
1576
1577 JLOG(j_.debug()) << "Checking for TX consensus: agree=" << agree
1578 << ", disagree=" << disagree;
1579
1580 // Determine if we actually have consensus or not
1581 result_->state = checkConsensus(
1582 prevProposers_,
1583 agree + disagree,
1584 agree,
1585 currentFinished,
1586 prevRoundTime_,
1587 result_->roundTime.read(),
1588 adaptor_.parms(),
1589 mode_.get() == ConsensusMode::proposing,
1590 j_);
1591
1592 if (result_->state == ConsensusState::No)
1593 return false;
1594
1595 // There is consensus, but we need to track if the network moved on
1596 // without us.
1597 if (result_->state == ConsensusState::MovedOn)
1598 {
1599 JLOG(j_.error()) << "Unable to reach consensus";
1600 JLOG(j_.error()) << Json::Compact{getJson(true)};
1601 }
1602
1603 return true;
1604}
1605
1606template <class Adaptor>
1607void
1609{
1610 if (mode_.get() == ConsensusMode::proposing)
1611 {
1612 if (result_ && !result_->position.isBowOut())
1613 {
1614 result_->position.bowOut(now_);
1615 adaptor_.propose(result_->position);
1616 }
1617
1618 mode_.set(ConsensusMode::observing, adaptor_);
1619 JLOG(j_.info()) << "Bowing out of consensus";
1620 }
1621}
1622
1623template <class Adaptor>
1624void
1626{
1627 // Cannot create disputes without our stance
1628 XRPL_ASSERT(result_, "ripple::Consensus::createDisputes : result is set");
1629
1630 // Only create disputes if this is a new set
1631 if (!result_->compares.emplace(o.id()).second)
1632 return;
1633
1634 // Nothing to dispute if we agree
1635 if (result_->txns.id() == o.id())
1636 return;
1637
1638 JLOG(j_.debug()) << "createDisputes " << result_->txns.id() << " to "
1639 << o.id();
1640
1641 auto differences = result_->txns.compare(o);
1642
1643 int dc = 0;
1644
1645 for (auto const& [txId, inThisSet] : differences)
1646 {
1647 ++dc;
1648 // create disputed transactions (from the ledger that has them)
1649 XRPL_ASSERT(
1650 (inThisSet && result_->txns.find(txId) && !o.find(txId)) ||
1651 (!inThisSet && !result_->txns.find(txId) && o.find(txId)),
1652 "ripple::Consensus::createDisputes : has disputed transactions");
1653
1654 Tx_t tx = inThisSet ? result_->txns.find(txId) : o.find(txId);
1655 auto txID = tx.id();
1656
1657 if (result_->disputes.find(txID) != result_->disputes.end())
1658 continue;
1659
1660 JLOG(j_.debug()) << "Transaction " << txID << " is disputed";
1661
1662 typename Result::Dispute_t dtx{
1663 tx,
1664 result_->txns.exists(txID),
1665 std::max(prevProposers_, currPeerPositions_.size()),
1666 j_};
1667
1668 // Update all of the available peer's votes on the disputed transaction
1669 for (auto const& [nodeId, peerPos] : currPeerPositions_)
1670 {
1671 Proposal_t const& peerProp = peerPos.proposal();
1672 auto const cit = acquired_.find(peerProp.position());
1673 if (cit != acquired_.end())
1674 dtx.setVote(nodeId, cit->second.exists(txID));
1675 }
1676 adaptor_.share(dtx.tx());
1677
1678 result_->disputes.emplace(txID, std::move(dtx));
1679 }
1680 JLOG(j_.debug()) << dc << " differences found";
1681}
1682
1683template <class Adaptor>
1684void
1686{
1687 // Cannot updateDisputes without our stance
1688 XRPL_ASSERT(result_, "ripple::Consensus::updateDisputes : result is set");
1689
1690 // Ensure we have created disputes against this set if we haven't seen
1691 // it before
1692 if (result_->compares.find(other.id()) == result_->compares.end())
1693 createDisputes(other);
1694
1695 for (auto& it : result_->disputes)
1696 {
1697 auto& d = it.second;
1698 d.setVote(node, other.exists(d.tx().id()));
1699 }
1700}
1701
1702template <class Adaptor>
1705{
1706 return roundCloseTime(raw, closeResolution_);
1707}
1708
1709} // namespace ripple
1710
1711#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:1081
void timerEntry(NetClock::time_point const &now)
Call periodically to drive consensus forward.
Definition: Consensus.h:819
void updateOurPositions()
Definition: Consensus.h:1365
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:1550
void updateDisputes(NodeID_t const &node, TxSet_t const &other)
Definition: Consensus.h:1685
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:890
Json::Value getJson(bool full) const
Get the Json state of the consensus process.
Definition: Consensus.h:914
typename TxSet_t::Tx Tx_t
Definition: Consensus.h:292
void createDisputes(TxSet_t const &o)
Definition: Consensus.h:1625
Consensus(Consensus &&) noexcept=default
void leaveConsensus()
Definition: Consensus.h:1608
void phaseOpen()
Handle pre-close phase.
Definition: Consensus.h:1098
void handleWrongLedger(typename Ledger_t::ID const &lgrId)
Definition: Consensus.h:1012
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:1704
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:842
void checkLedger()
Check if our previous ledger matches the network's.
Definition: Consensus.h:1059
bool shouldPause() const
Evaluate whether pausing increases likelihood of validation.
Definition: Consensus.h:1151
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:1264
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:724
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:1356
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)