xrpld
Loading...
Searching...
No Matches
Consensus.h
1#pragma once
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/UnorderedContainers.h>
5#include <xrpl/basics/chrono.h>
6#include <xrpl/beast/clock/abstract_clock.h>
7#include <xrpl/beast/utility/Journal.h>
8#include <xrpl/beast/utility/instrumentation.h>
9#include <xrpl/consensus/ConsensusParms.h>
10#include <xrpl/consensus/ConsensusProposal.h>
11#include <xrpl/consensus/ConsensusTypes.h>
12#include <xrpl/json/json_value.h>
13#include <xrpl/json/json_writer.h>
14#include <xrpl/ledger/LedgerTiming.h>
15
16#include <algorithm>
17#include <chrono>
18#include <cstddef>
19#include <cstdint>
20#include <deque>
21#include <map>
22#include <memory>
23#include <optional>
24#include <ranges>
25#include <sstream>
26#include <string>
27#include <utility>
28
29namespace xrpl {
30
51bool
53 bool anyTransactions,
54 std::size_t prevProposers,
55 std::size_t proposersClosed,
56 std::size_t proposersValidated,
57 std::chrono::milliseconds prevRoundTime,
58 std::chrono::milliseconds timeSincePrevClose,
59 std::chrono::milliseconds openTime,
60 std::chrono::milliseconds idleInterval,
61 ConsensusParms const& parms,
62 beast::Journal j,
63 std::unique_ptr<std::stringstream> const& clog = {});
64
87 std::size_t prevProposers,
88 std::size_t currentProposers,
89 std::size_t currentAgree,
90 std::size_t currentFinished,
91 std::chrono::milliseconds previousAgreeTime,
92 std::chrono::milliseconds currentAgreeTime,
93 bool stalled,
94 ConsensusParms const& parms,
95 bool proposing,
96 beast::Journal j,
97 std::unique_ptr<std::stringstream> const& clog = {});
98
288template <class Adaptor>
290{
291 using Ledger_t = Adaptor::Ledger_t;
292 using TxSet_t = Adaptor::TxSet_t;
293 using NodeID_t = Adaptor::NodeID_t;
294 using Tx_t = TxSet_t::Tx;
295 using PeerPosition_t = Adaptor::PeerPosition_t;
297
299
300 // Helper class to ensure adaptor is notified whenever the ConsensusMode
301 // changes
303 {
305
306 public:
308 {
309 }
310 [[nodiscard]] ConsensusMode
311 get() const
312 {
313 return mode_;
314 }
315
316 void
317 set(ConsensusMode mode, Adaptor& a)
318 {
319 a.onModeChange(mode_, mode);
320 mode_ = mode;
321 }
322 };
323
324public:
329
330 Consensus(Consensus&&) noexcept = default;
331
339 Consensus(clock_type const& clock, Adaptor& adaptor, beast::Journal j);
340
357 void
359 NetClock::time_point const& now,
360 Ledger_t::ID const& prevLedgerID,
361 Ledger_t prevLedger,
362 hash_set<NodeID_t> const& nowUntrusted,
363 bool proposing,
364 std::unique_ptr<std::stringstream> const& clog = {});
365
373 bool
374 peerProposal(NetClock::time_point const& now, PeerPosition_t const& newProposal);
375
382 void
384 NetClock::time_point const& now,
385 std::unique_ptr<std::stringstream> const& clog = {});
386
393 void
394 gotTxSet(NetClock::time_point const& now, TxSet_t const& txSet);
395
413 void
415 NetClock::time_point const& now,
417
426 Ledger_t::ID
428 {
429 return prevLedgerID_;
430 }
431
432 [[nodiscard]] ConsensusPhase
433 phase() const
434 {
435 return phase_;
436 }
437
446 [[nodiscard]] json::Value
447 getJson(bool full) const;
448
449private:
450 void
452 NetClock::time_point const& now,
453 Ledger_t::ID const& prevLedgerID,
454 Ledger_t const& prevLedger,
455 ConsensusMode mode,
457
458 // Change our view of the previous ledger
459 void
460 handleWrongLedger(Ledger_t::ID const& lgrId, std::unique_ptr<std::stringstream> const& clog);
461
468 void
470
475 void
477
481 bool
483
491 void
493
503 void
505
529 [[nodiscard]] bool
531
532 // Close the open ledger and establish initial position.
533 void
535
536 // Adjust our positions to try to agree with other validators.
537 void
539
540 bool
542
543 // Create disputes between our position and the provided one.
544 void
546
547 // Update our disputes given that this node has adopted a new position.
548 // Will call createDisputes as needed.
549 void
550 updateDisputes(NodeID_t const& node, TxSet_t const& other);
551
552 // Revoke our outstanding proposal, if any, and cease proposing
553 // until this round ends.
554 void
556
557 // The rounded or effective close time estimate from a proposer
558 [[nodiscard]] NetClock::time_point
560
561private:
562 Adaptor& adaptor_;
563
566 bool firstRound_ = true;
568
570
571 // How long the consensus convergence has taken, expressed as
572 // a percentage of the time that we expected it to take.
574
575 // How long has this round been open
577
579
581
582 // Time it took for the last consensus round to converge
584
585 //-------------------------------------------------------------------------
586 // Network time measurements of consensus progress
587
588 // The current network adjusted time. This is the network time the
589 // ledger would close if it closed now
592
593 //-------------------------------------------------------------------------
594 // Non-peer (self) consensus data
595
596 // Last validated ledger ID provided to consensus
597 Ledger_t::ID prevLedgerID_;
598 // Last validated ledger seen by consensus
600
601 // Transaction Sets, indexed by hash of transaction tree
603
606
607 // The number of calls to phaseEstablish where none of our peers
608 // have changed any votes on disputed transactions.
610
611 // The total number of times we have called phaseEstablish
613
614 //-------------------------------------------------------------------------
615 // Peer related consensus data
616
617 // Peer proposed positions for the current round
619
620 // Recently received peer positions, available when transitioning between
621 // ledgers or rounds
623
624 // The number of proposers who participated in the last consensus round
626
627 // nodes that have bowed out of this consensus process
629
630 // Journal for debugging
632};
633
634template <class Adaptor>
635Consensus<Adaptor>::Consensus(clock_type const& clock, Adaptor& adaptor, beast::Journal journal)
636 : adaptor_(adaptor), clock_(clock), j_{journal}
637{
638 JLOG(j_.debug()) << "Creating consensus object";
639}
640
641template <class Adaptor>
642void
644 NetClock::time_point const& now,
645 Ledger_t::ID const& prevLedgerID,
646 Ledger_t prevLedger,
647 hash_set<NodeID_t> const& nowUntrusted,
648 bool proposing,
650{
651 if (firstRound_)
652 {
653 // take our initial view of closeTime_ from the seed ledger
654 prevRoundTime_ = adaptor_.parms().ledgerIdleInterval;
655 prevCloseTime_ = prevLedger.closeTime();
656 firstRound_ = false;
657 }
658 else
659 {
661 }
662
663 for (NodeID_t const& n : nowUntrusted)
664 recentPeerPositions_.erase(n);
665
667
668 // We were handed the wrong ledger
669 if (prevLedger.id() != prevLedgerID)
670 {
671 // try to acquire the correct one
672 if (auto newLedger = adaptor_.acquireLedger(prevLedgerID))
673 {
674 prevLedger = *newLedger;
675 }
676 else // Unable to acquire the correct ledger
677 {
678 startMode = ConsensusMode::WrongLedger;
679 JLOG(j_.info()) << "Entering consensus with: " << previousLedger_.id();
680 JLOG(j_.info()) << "Correct LCL is: " << prevLedgerID;
681 }
682 }
683
684 startRoundInternal(now, prevLedgerID, prevLedger, startMode, clog);
685}
686template <class Adaptor>
687void
689 NetClock::time_point const& now,
690 Ledger_t::ID const& prevLedgerID,
691 Ledger_t const& prevLedger,
692 ConsensusMode mode,
694{
696 JLOG(j_.debug()) << "transitioned to ConsensusPhase::Open ";
697 CLOG(clog) << "startRoundInternal transitioned to ConsensusPhase::Open, "
698 "previous ledgerID: "
699 << prevLedgerID << ", seq: " << prevLedger.seq() << ". ";
700 mode_.set(mode, adaptor_);
701 now_ = now;
703 previousLedger_ = prevLedger;
704 result_.reset();
708 openTime_.reset(clock_.now());
709 currPeerPositions_.clear();
710 acquired_.clear();
711 rawCloseTimes_.peers.clear();
712 rawCloseTimes_.self = {};
713 deadNodes_.clear();
714
716 previousLedger_.closeTimeResolution(),
717 previousLedger_.closeAgree(),
718 previousLedger_.seq() + typename Ledger_t::Seq{1});
719
721 CLOG(clog) << "number of peer proposals,previous proposers: " << currPeerPositions_.size()
722 << ',' << prevProposers_ << ". ";
723 if (currPeerPositions_.size() > (prevProposers_ / 2))
724 {
725 // We may be falling behind, don't wait for the timer
726 // consider closing the ledger immediately
727 CLOG(clog) << "consider closing the ledger immediately. ";
728 timerEntry(now_, clog);
729 }
730}
731
732template <class Adaptor>
733bool
735{
736 JLOG(j_.debug()) << "PROPOSAL " << newPeerPos.render();
737 auto const& peerID = newPeerPos.proposal().nodeID();
738
739 // Always need to store recent positions
740 {
741 auto& props = recentPeerPositions_[peerID];
742
743 if (props.size() >= 10)
744 props.pop_front();
745
746 props.push_back(newPeerPos);
747 }
748 return peerProposalInternal(now, newPeerPos);
749}
750
751template <class Adaptor>
752bool
754 NetClock::time_point const& now,
755 PeerPosition_t const& newPeerPos)
756{
757 // Nothing to do for now if we are currently working on a ledger
759 return false;
760
761 now_ = now;
762
763 auto const& newPeerProp = newPeerPos.proposal();
764
765 if (newPeerProp.prevLedger() != prevLedgerID_)
766 {
767 JLOG(j_.debug()) << "Got proposal for " << newPeerProp.prevLedger() << " but we are on "
768 << prevLedgerID_;
769 return false;
770 }
771
772 auto const& peerID = newPeerProp.nodeID();
773
774 if (deadNodes_.find(peerID) != deadNodes_.end())
775 {
776 JLOG(j_.info()) << "Position from dead node: " << peerID;
777 return false;
778 }
779
780 {
781 // update current position
782 auto peerPosIt = currPeerPositions_.find(peerID);
783
784 if (peerPosIt != currPeerPositions_.end())
785 {
786 if (newPeerProp.proposeSeq() <= peerPosIt->second.proposal().proposeSeq())
787 {
788 return false;
789 }
790 }
791
792 if (newPeerProp.isBowOut())
793 {
794 JLOG(j_.info()) << "Peer " << peerID << " bows out";
795 if (result_)
796 {
797 for (auto& it : result_->disputes)
798 it.second.unVote(peerID);
799 }
800 if (peerPosIt != currPeerPositions_.end())
801 currPeerPositions_.erase(peerID);
802 deadNodes_.insert(peerID);
803
804 return true;
805 }
806
807 if (peerPosIt != currPeerPositions_.end())
808 {
809 peerPosIt->second = newPeerPos;
810 }
811 else
812 {
813 currPeerPositions_.emplace(peerID, newPeerPos);
814 }
815 }
816
817 if (newPeerProp.isInitial())
818 {
819 // Record the close time estimate
820 JLOG(j_.trace()) << "Peer reports close time as "
821 << newPeerProp.closeTime().time_since_epoch().count();
822 ++rawCloseTimes_.peers[newPeerProp.closeTime()];
823 }
824
825 JLOG(j_.trace()) << "Processing peer proposal " << newPeerProp.proposeSeq() << "/"
826 << newPeerProp.position();
827
828 {
829 auto const ait = acquired_.find(newPeerProp.position());
830 if (ait == acquired_.end())
831 {
832 // acquireTxSet will return the set if it is available, or
833 // spawn a request for it and return nullopt/nullptr. It will call
834 // gotTxSet once it arrives
835 if (auto set = adaptor_.acquireTxSet(newPeerProp.position()))
836 {
837 gotTxSet(now_, *set);
838 }
839 else
840 {
841 JLOG(j_.debug()) << "Don't have tx set for peer";
842 }
843 }
844 else if (result_)
845 {
846 updateDisputes(newPeerProp.nodeID(), ait->second);
847 }
848 }
849
850 return true;
851}
852
853template <class Adaptor>
854void
856 NetClock::time_point const& now,
858{
859 CLOG(clog) << "Consensus<Adaptor>::timerEntry. ";
860 // Nothing to do if we are currently working on a ledger
862 {
863 CLOG(clog) << "Nothing to do during accepted phase. ";
864 return;
865 }
866
867 now_ = now;
868 CLOG(clog) << "Set network adjusted time to " << to_string(now) << ". ";
869
870 // Check we are on the proper ledger (this may change phase_)
871 auto const phaseOrig = phase_;
872 CLOG(clog) << "Phase " << to_string(phaseOrig) << ". ";
873 checkLedger(clog);
874 if (phaseOrig != phase_)
875 {
876 CLOG(clog) << "Changed phase to << " << to_string(phase_) << ". ";
877 }
878
880 {
881 phaseOpen(clog);
882 }
884 {
885 phaseEstablish(clog);
886 }
887 CLOG(clog) << "timerEntry finishing in phase " << to_string(phase_) << ". ";
888}
889
890template <class Adaptor>
891void
893{
894 // Nothing to do if we've finished work on a ledger
896 return;
897
898 now_ = now;
899
900 auto id = txSet.id();
901
902 // If we've already processed this transaction set since requesting
903 // it from the network, there is nothing to do now
904 if (!acquired_.emplace(id, txSet).second)
905 return;
906
907 if (!result_)
908 {
909 JLOG(j_.debug()) << "Not creating disputes: no position yet.";
910 }
911 else
912 {
913 // Our position is added to acquired_ as soon as we create it,
914 // so this txSet must differ
915 XRPL_ASSERT(
916 id != result_->position.position(),
917 "xrpl::Consensus::gotTxSet : updated transaction set");
918 bool any = false;
919 for (auto const& [nodeId, peerPos] : currPeerPositions_)
920 {
921 if (peerPos.proposal().position() == id)
922 {
923 updateDisputes(nodeId, txSet);
924 any = true;
925 }
926 }
927
928 if (!any)
929 {
930 JLOG(j_.warn()) << "By the time we got " << id << " no peers were proposing it";
931 }
932 }
933}
934
935template <class Adaptor>
936void
938 NetClock::time_point const& now,
940{
941 using namespace std::chrono_literals;
942 JLOG(j_.info()) << "Simulating consensus";
943 now_ = now;
944 closeLedger({});
945 // NOLINTBEGIN(bugprone-unchecked-optional-access) closeLedger sets result_
946 result_->roundTime.tick(consensusDelay.value_or(100ms));
947 result_->proposers = prevProposers_ = currPeerPositions_.size();
948 prevRoundTime_ = result_->roundTime.read();
950 adaptor_.onForceAccept(
952 // NOLINTEND(bugprone-unchecked-optional-access)
953 JLOG(j_.info()) << "Simulation complete";
954}
955
956template <class Adaptor>
959{
960 using std::to_string;
961 using Int = json::Value::Int;
962
964
965 ret["proposing"] = (mode_.get() == ConsensusMode::Proposing);
966 ret["proposers"] = static_cast<int>(currPeerPositions_.size());
967
969 {
970 ret["synched"] = true;
971 ret["ledger_seq"] = static_cast<std::uint32_t>(previousLedger_.seq()) + 1;
972 ret["close_granularity"] = static_cast<Int>(closeResolution_.count());
973 }
974 else
975 {
976 ret["synched"] = false;
977 }
978
979 ret["phase"] = to_string(phase_);
980
981 if (result_ && !result_->disputes.empty() && !full)
982 ret["disputes"] = static_cast<Int>(result_->disputes.size());
983
984 if (result_)
985 ret["our_position"] = result_->position.getJson();
986
987 if (full)
988 {
989 if (result_)
990 ret["current_ms"] = static_cast<Int>(result_->roundTime.read().count());
991 ret["converge_percent"] = convergePercent_;
992 ret["close_resolution"] = static_cast<Int>(closeResolution_.count());
993 ret["have_time_consensus"] = haveCloseTimeConsensus_;
994 ret["previous_proposers"] = static_cast<Int>(prevProposers_);
995 ret["previous_mseconds"] = static_cast<Int>(prevRoundTime_.count());
996
997 if (!currPeerPositions_.empty())
998 {
1000
1001 for (auto const& [nodeId, peerPos] : currPeerPositions_)
1002 {
1003 ppj[to_string(nodeId)] = peerPos.getJson();
1004 }
1005 ret["peer_positions"] = std::move(ppj);
1006 }
1007
1008 if (!acquired_.empty())
1009 {
1011 for (auto const& at : acquired_)
1012 {
1013 acq.append(to_string(at.first));
1014 }
1015 ret["acquired"] = std::move(acq);
1016 }
1017
1018 if (result_ && !result_->disputes.empty())
1019 {
1021 for (auto const& [txId, dispute] : result_->disputes)
1022 {
1023 dsj[to_string(txId)] = dispute.getJson();
1024 }
1025 ret["disputes"] = std::move(dsj);
1026 }
1027
1028 if (!rawCloseTimes_.peers.empty())
1029 {
1031 for (auto const& ct : rawCloseTimes_.peers)
1032 {
1033 ctj[std::to_string(ct.first.time_since_epoch().count())] = ct.second;
1034 }
1035 ret["close_times"] = std::move(ctj);
1036 }
1037
1038 if (!deadNodes_.empty())
1039 {
1041 for (auto const& dn : deadNodes_)
1042 {
1043 dnj.append(to_string(dn));
1044 }
1045 ret["dead_nodes"] = std::move(dnj);
1046 }
1047 }
1048
1049 return ret;
1050}
1051
1052// Handle a change in the prior ledger during a consensus round
1053template <class Adaptor>
1054void
1056 Ledger_t::ID const& lgrId,
1058{
1059 CLOG(clog) << "handleWrongLedger. ";
1060 XRPL_ASSERT(
1061 lgrId != prevLedgerID_ || previousLedger_.id() != lgrId,
1062 "xrpl::Consensus::handleWrongLedger : have wrong ledger");
1063
1064 // Stop proposing because we are out of sync
1065 leaveConsensus(clog);
1066
1067 // First time switching to this ledger
1068 if (prevLedgerID_ != lgrId)
1069 {
1070 prevLedgerID_ = lgrId;
1071
1072 // Clear out state
1073 if (result_)
1074 {
1075 result_->disputes.clear();
1076 result_->compares.clear();
1077 }
1078
1079 currPeerPositions_.clear();
1080 rawCloseTimes_.peers.clear();
1081 deadNodes_.clear();
1082
1083 // Get back in sync, this will also recreate disputes
1085 }
1086
1087 if (previousLedger_.id() == prevLedgerID_)
1088 {
1089 CLOG(clog) << "previousLedger_.id() == prevLeverID_ " << prevLedgerID_ << ". ";
1090 return;
1091 }
1092
1093 // we need to switch the ledger we're working from
1094 if (auto newLedger = adaptor_.acquireLedger(prevLedgerID_))
1095 {
1096 JLOG(j_.info()) << "Have the consensus ledger " << prevLedgerID_;
1097 CLOG(clog) << "Have the consensus ledger " << prevLedgerID_ << ". ";
1098 startRoundInternal(now_, lgrId, *newLedger, ConsensusMode::SwitchedLedger, clog);
1099 }
1100 else
1101 {
1102 CLOG(clog) << "Still on wrong ledger. ";
1104 }
1105}
1106
1107template <class Adaptor>
1108void
1110{
1111 CLOG(clog) << "checkLedger. ";
1112
1113 auto netLgr = adaptor_.getPrevLedger(prevLedgerID_, previousLedger_, mode_.get());
1114 CLOG(clog) << "network ledgerid " << netLgr << ", "
1115 << "previous ledger " << prevLedgerID_ << ". ";
1116
1117 if (netLgr != prevLedgerID_)
1118 {
1120 ss << "View of consensus changed during " << to_string(phase_)
1121 << " mode=" << to_string(mode_.get()) << ", " << prevLedgerID_ << " to " << netLgr
1122 << ", " << json::Compact{previousLedger_.getJson()} << ". ";
1123 JLOG(j_.warn()) << ss.str();
1124 CLOG(clog) << ss.str();
1125 CLOG(clog) << "State on consensus change " << json::Compact{getJson(true)} << ". ";
1126 handleWrongLedger(netLgr, clog);
1127 }
1128 else if (previousLedger_.id() != prevLedgerID_)
1129 {
1130 CLOG(clog) << "previousLedger_.id() != prevLedgerID_: " << previousLedger_.id() << ','
1131 << to_string(prevLedgerID_) << ". ";
1132 handleWrongLedger(netLgr, clog);
1133 }
1134}
1135
1136template <class Adaptor>
1137void
1139{
1140 for (auto const& it : recentPeerPositions_)
1141 {
1142 for (auto const& pos : it.second)
1143 {
1144 if (pos.proposal().prevLedger() == prevLedgerID_)
1145 {
1146 if (peerProposalInternal(now_, pos))
1147 adaptor_.share(pos);
1148 }
1149 }
1150 }
1151}
1152
1153template <class Adaptor>
1154void
1156{
1157 CLOG(clog) << "phaseOpen. ";
1158 using namespace std::chrono;
1159
1160 // it is shortly before ledger close time
1161 bool const anyTransactions = adaptor_.hasOpenTransactions();
1162 auto proposersClosed = currPeerPositions_.size();
1163 auto proposersValidated = adaptor_.proposersValidated(prevLedgerID_);
1164
1165 openTime_.tick(clock_.now());
1166
1167 // This computes how long since last ledger's close time
1168 milliseconds sinceClose;
1169 {
1170 auto const mode = mode_.get();
1171 bool const closeAgree = previousLedger_.closeAgree();
1172 auto const prevCloseTime = previousLedger_.closeTime();
1173 auto const prevParentCloseTimePlus1 = previousLedger_.parentCloseTime() + 1s;
1174 bool const previousCloseCorrect = (mode != ConsensusMode::WrongLedger) && closeAgree &&
1175 (prevCloseTime != prevParentCloseTimePlus1);
1176
1177 auto const lastCloseTime = previousCloseCorrect
1178 ? prevCloseTime // use consensus timing
1179 : prevCloseTime_; // use the time we saw internally
1180
1181 if (now_ >= lastCloseTime)
1182 {
1183 sinceClose = duration_cast<milliseconds>(now_ - lastCloseTime);
1184 }
1185 else
1186 {
1187 sinceClose = -duration_cast<milliseconds>(lastCloseTime - now_);
1188 }
1189 CLOG(clog) << "calculating how long since last ledger's close time "
1190 "based on mode : "
1191 << to_string(mode) << ", previous closeAgree: " << closeAgree
1192 << ", previous close time: " << to_string(prevCloseTime)
1193 << ", previous parent close time + 1s: " << to_string(prevParentCloseTimePlus1)
1194 << ", previous close time seen internally: " << to_string(prevCloseTime_)
1195 << ", last close time: " << to_string(lastCloseTime)
1196 << ", since close: " << sinceClose.count() << ". ";
1197 }
1198
1199 auto const idleInterval = std::max<milliseconds>(
1200 adaptor_.parms().ledgerIdleInterval, 2 * previousLedger_.closeTimeResolution());
1201 CLOG(clog) << "idle interval set to " << idleInterval.count() << "ms based on "
1202 << "ledgerIDLE_INTERVAL: " << adaptor_.parms().ledgerIdleInterval.count()
1203 << ", previous ledger close time resolution: "
1204 << previousLedger_.closeTimeResolution().count() << "ms. ";
1205
1206 // Decide if we should close the ledger
1208 anyTransactions,
1210 proposersClosed,
1211 proposersValidated,
1213 sinceClose,
1214 openTime_.read(),
1215 idleInterval,
1216 adaptor_.parms(),
1217 j_,
1218 clog))
1219 {
1220 CLOG(clog) << "closing ledger. ";
1222 }
1223}
1224
1225template <class Adaptor>
1226bool
1228{
1229 CLOG(clog) << "shouldPause? ";
1230 auto const& parms = adaptor_.parms();
1231 std::uint32_t const ahead(
1232 previousLedger_.seq() - std::min(adaptor_.getValidLedgerIndex(), previousLedger_.seq()));
1233 auto [quorum, trustedKeys] = adaptor_.getQuorumKeys();
1234 std::size_t const totalValidators = trustedKeys.size();
1235 std::size_t const laggards = adaptor_.laggards(previousLedger_.seq(), trustedKeys);
1236 std::size_t const offline = trustedKeys.size();
1237
1238 std::stringstream vars;
1239 vars << " consensuslog (working seq: " << previousLedger_.seq() << ", "
1240 << "validated seq: " << adaptor_.getValidLedgerIndex() << ", "
1241 << "am validator: " << adaptor_.validator() << ", "
1242 << "have validated: " << adaptor_.haveValidated()
1243 << ", "
1244 // NOLINTBEGIN(bugprone-unchecked-optional-access) result_ is always set when shouldPause
1245 // is called (from phaseEstablish after assert)
1246 << "roundTime: " << result_->roundTime.read().count()
1247 << ", "
1248 // NOLINTEND(bugprone-unchecked-optional-access)
1249 << "max consensus time: " << parms.ledgerMaxConsensus.count() << ", "
1250 << "validators: " << totalValidators << ", "
1251 << "laggards: " << laggards << ", "
1252 << "offline: " << offline << ", "
1253 << "quorum: " << quorum << ")";
1254
1255 if ((ahead == 0u) || (laggards == 0u) || (totalValidators == 0u) || !adaptor_.validator() ||
1256 !adaptor_.haveValidated() ||
1257 // NOLINTNEXTLINE(bugprone-unchecked-optional-access) result_ set as shouldPause called
1258 result_->roundTime.read() > parms.ledgerMaxConsensus)
1259 {
1260 j_.debug() << "not pausing (early)" << vars.str();
1261 CLOG(clog) << "Not pausing (early). ";
1262 return false;
1263 }
1264
1265 bool willPause = false;
1266
1281 static constexpr std::size_t kMaxPausePhase = 4;
1282
1302 std::size_t const phase = (ahead - 1) % (kMaxPausePhase + 1);
1303
1304 // validators that remain after the laggards() function are considered
1305 // offline, and should be considered as laggards for purposes of
1306 // evaluating whether the threshold for non-laggards has been reached.
1307 switch (phase)
1308 {
1309 case 0:
1310 // Laggards and offline shouldn't preclude consensus.
1311 if (laggards + offline > totalValidators - quorum)
1312 willPause = true;
1313 break;
1314 case kMaxPausePhase:
1315 // No tolerance.
1316 willPause = true;
1317 break;
1318 default:
1319 // Ensure that sufficient validators are known to be not lagging.
1320 // Their sufficiently most recent validation sequence was equal to
1321 // or greater than our own.
1322 //
1323 // The threshold is the amount required for quorum plus
1324 // the proportion of the remainder based on number of intermediate
1325 // phases between 0 and max.
1326 float const nonLaggards = totalValidators - (laggards + offline);
1327 float const quorumRatio = static_cast<float>(quorum) / totalValidators;
1328 float const allowedDissent = 1.0f - quorumRatio;
1329 float const phaseFactor = static_cast<float>(phase) / kMaxPausePhase;
1330
1331 if (nonLaggards / totalValidators < quorumRatio + (allowedDissent * phaseFactor))
1332 {
1333 willPause = true;
1334 }
1335 }
1336
1337 if (willPause)
1338 {
1339 j_.warn() << "pausing" << vars.str();
1340 CLOG(clog) << "pausing " << vars.str() << ". ";
1341 }
1342 else
1343 {
1344 j_.debug() << "not pausing" << vars.str();
1345 CLOG(clog) << "not pausing. ";
1346 }
1347 return willPause;
1348}
1349
1350template <class Adaptor>
1351void
1353{
1354 CLOG(clog) << "phaseEstablish. ";
1355 // can only establish consensus if we already took a stance
1356 XRPL_ASSERT(result_, "xrpl::Consensus::phaseEstablish : result is set");
1357 // NOLINTBEGIN(bugprone-unchecked-optional-access) assert above
1358
1361
1362 using namespace std::chrono;
1363 ConsensusParms const& parms = adaptor_.parms();
1364
1365 result_->roundTime.tick(clock_.now());
1366 result_->proposers = currPeerPositions_.size();
1367
1368 convergePercent_ = result_->roundTime.read() * 100 /
1370 CLOG(clog) << "convergePercent_ " << convergePercent_
1371 << " is based on round duration so far: " << result_->roundTime.read().count()
1372 << "ms, "
1373 << "previous round duration: " << prevRoundTime_.count() << "ms, "
1374 << "avMIN_CONSENSUS_TIME: " << parms.avMinConsensusTime.count() << "ms. ";
1375
1376 // Give everyone a chance to take an initial position
1377 if (result_->roundTime.read() < parms.ledgerMinConsensus)
1378 {
1379 CLOG(clog) << "ledgerMIN_CONSENSUS not reached: " << parms.ledgerMinConsensus.count()
1380 << "ms. ";
1381 return;
1382 }
1383
1385
1386 // Nothing to do if too many laggards or we don't have consensus.
1388 return;
1389
1391 {
1392 JLOG(j_.info()) << "We have TX consensus but not CT consensus";
1393 CLOG(clog) << "We have TX consensus but not CT consensus. ";
1394 return;
1395 }
1396
1397 JLOG(j_.info()) << "Converge cutoff (" << currPeerPositions_.size() << " participants)";
1398 CLOG(clog) << "Converge cutoff (" << currPeerPositions_.size()
1399 << " participants). Transitioned to ConsensusPhase::Accepted. ";
1400 adaptor_.updateOperatingMode(currPeerPositions_.size());
1402 prevRoundTime_ = result_->roundTime.read();
1404 JLOG(j_.debug()) << "transitioned to ConsensusPhase::Accepted";
1405 adaptor_.onAccept(
1406 *result_,
1410 mode_.get(),
1411 getJson(true),
1412 adaptor_.validating());
1413 // NOLINTEND(bugprone-unchecked-optional-access)
1414}
1415
1416template <class Adaptor>
1417void
1419{
1420 // We should not be closing if we already have a position
1421 XRPL_ASSERT(!result_, "xrpl::Consensus::closeLedger : result is not set");
1422
1424 JLOG(j_.debug()) << "transitioned to ConsensusPhase::Establish";
1425 rawCloseTimes_.self = now_;
1428
1429 result_.emplace(adaptor_.onClose(previousLedger_, now_, mode_.get()));
1430 result_->roundTime.reset(clock_.now());
1431 // Share the newly created transaction set if we haven't already
1432 // received it from a peer
1433 if (acquired_.emplace(result_->txns.id(), result_->txns).second)
1434 adaptor_.share(result_->txns);
1435
1436 auto const mode = mode_.get();
1437 CLOG(clog) << "closeLedger transitioned to ConsensusPhase::Establish, mode: " << to_string(mode)
1438 << ", number of peer positions: " << currPeerPositions_.size() << ". ";
1439 if (mode == ConsensusMode::Proposing)
1440 adaptor_.propose(result_->position);
1441
1442 // Create disputes with any peer positions we have transactions for
1443 for (auto const& pit : currPeerPositions_)
1444 {
1445 auto const& pos = pit.second.proposal().position();
1446 auto const it = acquired_.find(pos);
1447 if (it != acquired_.end())
1448 createDisputes(it->second, clog);
1449 }
1450}
1451
1465inline int
1466participantsNeeded(int participants, int percent)
1467{
1468 int const result = ((participants * percent) + (percent / 2)) / 100;
1469
1470 return (result == 0) ? 1 : result;
1471}
1472
1473template <class Adaptor>
1474void
1476{
1477 // We must have a position if we are updating it
1478 XRPL_ASSERT(result_, "xrpl::Consensus::updateOurPositions : result is set");
1479 // NOLINTBEGIN(bugprone-unchecked-optional-access) assert above
1480 ConsensusParms const& parms = adaptor_.parms();
1481
1482 // Compute a cutoff time
1483 auto const peerCutoff = now_ - parms.proposeFRESHNESS;
1484 auto const ourCutoff = now_ - parms.proposeINTERVAL;
1485 CLOG(clog) << "updateOurPositions. peerCutoff " << to_string(peerCutoff) << ", ourCutoff "
1486 << to_string(ourCutoff) << ". ";
1487
1488 // Verify freshness of peer positions and compute close times
1490 {
1491 auto it = currPeerPositions_.begin();
1492 while (it != currPeerPositions_.end())
1493 {
1494 Proposal_t const& peerProp = it->second.proposal();
1495 if (peerProp.isStale(peerCutoff))
1496 {
1497 // peer's proposal is stale, so remove it
1498 NodeID_t const& peerID = peerProp.nodeID();
1499 JLOG(j_.warn()) << "Removing stale proposal from " << peerID;
1500 for (auto& dt : result_->disputes)
1501 dt.second.unVote(peerID);
1502 it = currPeerPositions_.erase(it);
1503 }
1504 else
1505 {
1506 // proposal is still fresh
1507 ++closeTimeVotes[asCloseTime(peerProp.closeTime())];
1508 ++it;
1509 }
1510 }
1511 }
1512
1513 // This will stay unseated unless there are any changes
1514 std::optional<TxSet_t> ourNewSet;
1515
1516 // Update votes on disputed transactions
1517 {
1519 for (auto& [txId, dispute] : result_->disputes)
1520 {
1521 // Because the threshold for inclusion increases,
1522 // time can change our position on a dispute
1523 if (dispute.updateVote(
1525 {
1526 if (!mutableSet)
1527 mutableSet.emplace(result_->txns);
1528
1529 if (dispute.getOurVote())
1530 {
1531 // now a yes
1532 mutableSet->insert(dispute.tx());
1533 }
1534 else
1535 {
1536 // now a no
1537 mutableSet->erase(txId);
1538 }
1539 }
1540 }
1541
1542 if (mutableSet)
1543 ourNewSet.emplace(std::move(*mutableSet));
1544 }
1545
1546 NetClock::time_point consensusCloseTime = {};
1548
1549 if (currPeerPositions_.empty())
1550 {
1551 // no other times
1553 consensusCloseTime = asCloseTime(result_->position.closeTime());
1554 }
1555 else
1556 {
1557 // We don't track rounds for close time, so just pass 0s
1558 auto const [neededWeight, newState] =
1560 if (newState)
1561 closeTimeAvalancheState_ = *newState;
1562 CLOG(clog) << "neededWeight " << neededWeight << ". ";
1563
1564 int participants = currPeerPositions_.size();
1565 if (mode_.get() == ConsensusMode::Proposing)
1566 {
1567 ++closeTimeVotes[asCloseTime(result_->position.closeTime())];
1568 ++participants;
1569 }
1570
1571 // Threshold for non-zero vote
1572 int threshVote = participantsNeeded(participants, neededWeight);
1573
1574 // Threshold to declare consensus
1575 int const threshConsensus = participantsNeeded(participants, parms.avCtConsensusPct);
1576
1578 ss << "Proposers:" << currPeerPositions_.size() << " nw:" << neededWeight
1579 << " thrV:" << threshVote << " thrC:" << threshConsensus;
1580 JLOG(j_.info()) << ss.str();
1581 CLOG(clog) << ss.str();
1582
1583 // Walk the votes highest-time first so that, among close times tied
1584 // for the most votes, the earliest wins. The smaller value is the
1585 // safer choice: without close-time consensus this round, the winner
1586 // only updates our position for the next proposal, and a too-early
1587 // time is bounded below by the prior ledger's close time. Only the
1588 // tie-break changes; the bin with the most votes still wins.
1589 for (auto const& [t, v] : std::views::reverse(closeTimeVotes))
1590 {
1591 JLOG(j_.debug()) << "CCTime: seq "
1592 << static_cast<std::uint32_t>(previousLedger_.seq()) + 1 << ": "
1593 << t.time_since_epoch().count() << " has " << v << ", " << threshVote
1594 << " required";
1595
1596 if (v >= threshVote)
1597 {
1598 // A close time has enough votes for us to try to agree
1599 consensusCloseTime = t;
1600 threshVote = v;
1601
1602 if (threshVote >= threshConsensus)
1604 }
1605 }
1606
1608 {
1609 JLOG(j_.debug()) << "No CT consensus:"
1610 << " Proposers:" << currPeerPositions_.size()
1611 << " Mode:" << to_string(mode_.get()) << " Thresh:" << threshConsensus
1612 << " Pos:" << consensusCloseTime.time_since_epoch().count();
1613 CLOG(clog) << "No close time consensus. ";
1614 }
1615 }
1616
1617 if (!ourNewSet &&
1618 ((consensusCloseTime != asCloseTime(result_->position.closeTime())) ||
1619 result_->position.isStale(ourCutoff)))
1620 {
1621 // close time changed or our position is stale
1622 ourNewSet.emplace(result_->txns);
1623 }
1624
1625 if (ourNewSet)
1626 {
1627 auto newID = ourNewSet->id();
1628
1629 result_->txns = std::move(*ourNewSet);
1630
1632 ss << "Position change: CTime " << consensusCloseTime.time_since_epoch().count() << ", tx "
1633 << newID;
1634 JLOG(j_.info()) << ss.str();
1635 CLOG(clog) << ss.str();
1636
1637 result_->position.changePosition(newID, consensusCloseTime, now_);
1638
1639 // Share our new transaction set and update disputes
1640 // if we haven't already received it
1641 if (acquired_.emplace(newID, result_->txns).second)
1642 {
1643 if (!result_->position.isBowOut())
1644 adaptor_.share(result_->txns);
1645
1646 for (auto const& [nodeId, peerPos] : currPeerPositions_)
1647 {
1648 Proposal_t const& p = peerPos.proposal();
1649 if (p.position() == newID)
1650 updateDisputes(nodeId, result_->txns);
1651 }
1652 }
1653
1654 // Share our new position if we are still participating this round
1655 if (!result_->position.isBowOut() && (mode_.get() == ConsensusMode::Proposing))
1656 adaptor_.propose(result_->position);
1657 }
1658 // NOLINTEND(bugprone-unchecked-optional-access)
1659}
1660
1661template <class Adaptor>
1662bool
1664{
1665 // Must have a stance if we are checking for consensus
1666 XRPL_ASSERT(result_, "xrpl::Consensus::haveConsensus : has result");
1667 // NOLINTBEGIN(bugprone-unchecked-optional-access) assert above
1668
1669 // CHECKME: should possibly count unacquired TX sets as disagreeing
1670 int agree = 0, disagree = 0;
1671
1672 auto ourPosition = result_->position.position();
1673
1674 // Count number of agreements/disagreements with our position
1675 for (auto const& [nodeId, peerPos] : currPeerPositions_)
1676 {
1677 Proposal_t const& peerProp = peerPos.proposal();
1678 if (peerProp.position() == ourPosition)
1679 {
1680 ++agree;
1681 }
1682 else
1683 {
1684 JLOG(j_.debug()) << "Proposal disagreement: Peer " << nodeId << " has "
1685 << peerProp.position();
1686 ++disagree;
1687 }
1688 }
1689 auto currentFinished = adaptor_.proposersFinished(previousLedger_, prevLedgerID_);
1690
1691 JLOG(j_.debug()) << "Checking for TX consensus: agree=" << agree << ", disagree=" << disagree;
1692
1693 ConsensusParms const& parms = adaptor_.parms();
1694 // Stalling is BAD. It means that we have a consensus on the close time, so
1695 // peers are talking, but we have disputed transactions that peers are
1696 // unable or unwilling to come to agreement on one way or the other.
1697 bool const stalled =
1698 haveCloseTimeConsensus_ && !result_->disputes.empty() &&
1699 std::ranges::all_of(result_->disputes, [this, &parms, &clog](auto const& dispute) {
1700 return dispute.second.stalled(
1701 parms, mode_.get() == ConsensusMode::Proposing, peerUnchangedCounter_, j_, clog);
1702 });
1703 if (stalled)
1704 {
1706 ss << "Consensus detects as stalled with " << (agree + disagree) << "/" << prevProposers_
1707 << " proposers, and " << result_->disputes.size() << " stalled disputed transactions.";
1708 JLOG(j_.error()) << ss.str();
1709 CLOG(clog) << ss.str();
1710 }
1711
1712 // Determine if we actually have consensus or not
1713 result_->state = checkConsensus(
1715 agree + disagree,
1716 agree,
1717 currentFinished,
1719 result_->roundTime.read(),
1720 stalled,
1721 parms,
1723 j_,
1724 clog);
1725
1726 if (result_->state == ConsensusState::No)
1727 {
1728 CLOG(clog) << "No consensus. ";
1729 return false;
1730 }
1731
1732 // Consensus has taken far too long. Drop out of the round.
1733 if (result_->state == ConsensusState::Expired)
1734 {
1735 static auto const kMinimumCounter = parms.avalancheCutoffs.size() * parms.avMinRounds;
1737 if (establishCounter_ < kMinimumCounter)
1738 {
1739 // If each round of phaseEstablish takes a very long time, we may
1740 // "expire" before we've given consensus enough time at each
1741 // avalanche level to actually come to a consensus. In that case,
1742 // keep trying. This should only happen if there are an extremely
1743 // large number of disputes such that each round takes an inordinate
1744 // amount of time.
1745
1746 ss << "Consensus time has expired in round " << establishCounter_
1747 << "; continue until round " << kMinimumCounter << ". "
1748 << json::Compact{getJson(false)};
1749 JLOG(j_.error()) << ss.str();
1750 CLOG(clog) << ss.str() << ". ";
1751 return false;
1752 }
1753 ss << "Consensus expired. " << json::Compact{getJson(true)};
1754 JLOG(j_.error()) << ss.str();
1755 CLOG(clog) << ss.str() << ". ";
1757 }
1758 // There is consensus, but we need to track if the network moved on
1759 // without us.
1760 if (result_->state == ConsensusState::MovedOn)
1761 {
1762 JLOG(j_.error()) << "Unable to reach consensus";
1763 JLOG(j_.error()) << json::Compact{getJson(true)};
1764 CLOG(clog) << "Unable to reach consensus " << json::Compact{getJson(true)} << ". ";
1765 }
1766
1767 CLOG(clog) << "Consensus has been reached. ";
1768 // NOLINTEND(bugprone-unchecked-optional-access)
1769 return true;
1770}
1771
1772template <class Adaptor>
1773void
1775{
1776 if (mode_.get() == ConsensusMode::Proposing)
1777 {
1778 if (result_ && !result_->position.isBowOut())
1779 {
1780 result_->position.bowOut(now_);
1781 adaptor_.propose(result_->position);
1782 }
1783
1785 JLOG(j_.info()) << "Bowing out of consensus";
1786 CLOG(clog) << "Bowing out of consensus. ";
1787 }
1788}
1789
1790template <class Adaptor>
1791void
1793{
1794 // Cannot create disputes without our stance
1795 XRPL_ASSERT(result_, "xrpl::Consensus::createDisputes : result is set");
1796 // NOLINTBEGIN(bugprone-unchecked-optional-access) assert above
1797
1798 // Only create disputes if this is a new set
1799 auto const emplaced = result_->compares.emplace(o.id()).second;
1800 CLOG(clog) << "createDisputes: new set? " << !emplaced << ". ";
1801 if (!emplaced)
1802 return;
1803
1804 // Nothing to dispute if we agree
1805 if (result_->txns.id() == o.id())
1806 {
1807 CLOG(clog) << "both sets are identical. ";
1808 return;
1809 }
1810
1811 CLOG(clog) << "comparing existing with new set: " << result_->txns.id() << ',' << o.id()
1812 << ". ";
1813 JLOG(j_.debug()) << "createDisputes " << result_->txns.id() << " to " << o.id();
1814
1815 auto differences = result_->txns.compare(o);
1816
1817 int dc = 0;
1818
1819 for (auto const& [txId, inThisSet] : differences)
1820 {
1821 ++dc;
1822 // create disputed transactions (from the ledger that has them)
1823 XRPL_ASSERT(
1824 (inThisSet && result_->txns.find(txId) && !o.find(txId)) ||
1825 (!inThisSet && !result_->txns.find(txId) && o.find(txId)),
1826 "xrpl::Consensus::createDisputes : has disputed transactions");
1827
1828 Tx_t const tx = inThisSet ? result_->txns.find(txId) : o.find(txId);
1829 auto txID = tx.id();
1830
1831 if (result_->disputes.find(txID) != result_->disputes.end())
1832 continue;
1833
1834 JLOG(j_.debug()) << "Transaction " << txID << " is disputed";
1835
1836 typename Result::Dispute_t dtx{
1837 tx,
1838 result_->txns.exists(txID),
1840 j_};
1841
1842 // Update all of the available peer's votes on the disputed transaction
1843 for (auto const& [nodeId, peerPos] : currPeerPositions_)
1844 {
1845 Proposal_t const& peerProp = peerPos.proposal();
1846 auto const cit = acquired_.find(peerProp.position());
1847 if (cit != acquired_.end() && dtx.setVote(nodeId, cit->second.exists(txID)))
1849 }
1850 adaptor_.share(dtx.tx());
1851
1852 result_->disputes.emplace(txID, std::move(dtx));
1853 }
1854 JLOG(j_.debug()) << dc << " differences found";
1855 CLOG(clog) << "disputes: " << dc << ". ";
1856 // NOLINTEND(bugprone-unchecked-optional-access)
1857}
1858
1859template <class Adaptor>
1860void
1862{
1863 // Cannot updateDisputes without our stance
1864 XRPL_ASSERT(result_, "xrpl::Consensus::updateDisputes : result is set");
1865 // NOLINTBEGIN(bugprone-unchecked-optional-access) assert above
1866
1867 // Ensure we have created disputes against this set if we haven't seen
1868 // it before
1869 if (result_->compares.find(other.id()) == result_->compares.end())
1870 createDisputes(other);
1871
1872 for (auto& it : result_->disputes)
1873 {
1874 auto& d = it.second;
1875 if (d.setVote(node, other.exists(d.tx().id())))
1877 }
1878 // NOLINTEND(bugprone-unchecked-optional-access)
1879}
1880
1881template <class Adaptor>
1887
1888} // namespace xrpl
T all_of(T... args)
Abstract interface to a clock.
A generic endpoint for log messages.
Definition Journal.h:44
Decorator for streaming out compact json.
Represents a JSON value.
Definition json_value.h:117
json::Int Int
Definition json_value.h:125
Value & append(Value const &value)
Append value to array at the end.
Represents a proposed position taken during a round of consensus.
Position const & position() const
Get the proposed position.
NodeId const & nodeID() const
Identifying which peer took this position.
NetClock::time_point const & closeTime() const
The current position on the consensus close time.
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.
ConsensusMode get() const
Definition Consensus.h:311
MonitoredMode(ConsensusMode m)
Definition Consensus.h:307
void set(ConsensusMode mode, Adaptor &a)
Definition Consensus.h:317
bool peerProposalInternal(NetClock::time_point const &now, PeerPosition_t const &newProposal)
Handle a replayed or a new peer proposal.
Definition Consensus.h:753
void updateDisputes(NodeID_t const &node, TxSet_t const &other)
Definition Consensus.h:1861
TxSet_t::Tx Tx_t
Definition Consensus.h:294
bool haveCloseTimeConsensus_
Definition Consensus.h:567
ConsensusProposal< NodeID_t, typename Ledger_t::ID, typename TxSet_t::ID > Proposal_t
Definition Consensus.h:296
MonitoredMode mode_
Definition Consensus.h:565
hash_map< NodeID_t, std::deque< PeerPosition_t > > recentPeerPositions_
Definition Consensus.h:622
NetClock::duration closeResolution_
Definition Consensus.h:578
ConsensusTimer openTime_
Definition Consensus.h:576
void simulate(NetClock::time_point const &now, std::optional< std::chrono::milliseconds > consensusDelay)
Simulate the consensus process without any network traffic.
Definition Consensus.h:937
void gotTxSet(NetClock::time_point const &now, TxSet_t const &txSet)
Process a transaction set acquired from the network.
Definition Consensus.h:892
bool shouldPause(std::unique_ptr< std::stringstream > const &clog) const
Evaluate whether pausing increases likelihood of validation.
Definition Consensus.h:1227
void phaseEstablish(std::unique_ptr< std::stringstream > const &clog)
Handle establish phase.
Definition Consensus.h:1352
hash_set< NodeID_t > deadNodes_
Definition Consensus.h:628
Adaptor::Ledger_t Ledger_t
Definition Consensus.h:291
std::size_t peerUnchangedCounter_
Definition Consensus.h:609
void leaveConsensus(std::unique_ptr< std::stringstream > const &clog)
Definition Consensus.h:1774
beast::AbstractClock< std::chrono::steady_clock > clock_type
Clock type for measuring time within the consensus code.
Definition Consensus.h:328
NetClock::time_point prevCloseTime_
Definition Consensus.h:591
json::Value getJson(bool full) const
Get the Json state of the consensus process.
Definition Consensus.h:958
ConsensusResult< Adaptor > Result
Definition Consensus.h:298
hash_map< NodeID_t, PeerPosition_t > currPeerPositions_
Definition Consensus.h:618
std::chrono::milliseconds prevRoundTime_
Definition Consensus.h:583
void checkLedger(std::unique_ptr< std::stringstream > const &clog)
Check if our previous ledger matches the network's.
Definition Consensus.h:1109
Consensus(Consensus &&) noexcept=default
void startRound(NetClock::time_point const &now, Ledger_t::ID const &prevLedgerID, Ledger_t prevLedger, hash_set< NodeID_t > const &nowUntrusted, bool proposing, std::unique_ptr< std::stringstream > const &clog={})
Definition Consensus.h:643
NetClock::time_point asCloseTime(NetClock::time_point raw) const
Definition Consensus.h:1883
ConsensusParms::AvalancheState closeTimeAvalancheState_
Definition Consensus.h:580
ConsensusPhase phase() const
Definition Consensus.h:433
ConsensusPhase phase_
Definition Consensus.h:564
Adaptor::PeerPosition_t PeerPosition_t
Definition Consensus.h:295
void playbackProposals()
If we radically changed our consensus context for some reason, we need to replay recent proposals so ...
Definition Consensus.h:1138
void handleWrongLedger(Ledger_t::ID const &lgrId, std::unique_ptr< std::stringstream > const &clog)
Definition Consensus.h:1055
Ledger_t previousLedger_
Definition Consensus.h:599
beast::Journal const j_
Definition Consensus.h:631
Ledger_t::ID prevLedgerID_
Definition Consensus.h:597
hash_map< typename TxSet_t::ID, TxSet_t const > acquired_
Definition Consensus.h:602
void startRoundInternal(NetClock::time_point const &now, Ledger_t::ID const &prevLedgerID, Ledger_t const &prevLedger, ConsensusMode mode, std::unique_ptr< std::stringstream > const &clog)
Definition Consensus.h:688
void phaseOpen(std::unique_ptr< std::stringstream > const &clog)
Handle pre-close phase.
Definition Consensus.h:1155
void closeLedger(std::unique_ptr< std::stringstream > const &clog)
Definition Consensus.h:1418
std::size_t prevProposers_
Definition Consensus.h:625
Adaptor & adaptor_
Definition Consensus.h:562
std::size_t establishCounter_
Definition Consensus.h:612
NetClock::time_point now_
Definition Consensus.h:590
clock_type const & clock_
Definition Consensus.h:569
void createDisputes(TxSet_t const &o, std::unique_ptr< std::stringstream > const &clog={})
Definition Consensus.h:1792
bool peerProposal(NetClock::time_point const &now, PeerPosition_t const &newProposal)
A peer has proposed a new position, adjust our tracking.
Definition Consensus.h:734
Adaptor::TxSet_t TxSet_t
Definition Consensus.h:292
std::optional< Result > result_
Definition Consensus.h:604
void timerEntry(NetClock::time_point const &now, std::unique_ptr< std::stringstream > const &clog={})
Call periodically to drive consensus forward.
Definition Consensus.h:855
bool haveConsensus(std::unique_ptr< std::stringstream > const &clog)
Definition Consensus.h:1663
void updateOurPositions(std::unique_ptr< std::stringstream > const &clog)
Definition Consensus.h:1475
ConsensusCloseTimes rawCloseTimes_
Definition Consensus.h:605
Adaptor::NodeID_t NodeID_t
Definition Consensus.h:293
bool setVote(NodeId const &peer, bool votesYes)
Change a peer's vote.
Definition DisputedTx.h:224
Tx const & tx() const
The disputed transaction.
Definition DisputedTx.h:145
std::chrono::time_point< NetClock > time_point
Definition chrono.h:48
std::chrono::duration< rep, period > duration
Definition chrono.h:47
T duration_cast(T... args)
T emplace(T... args)
T max(T... args)
T min(T... args)
@ Array
array value (ordered list)
Definition json_value.h:28
@ Object
object value (collection of name/value pairs).
Definition json_value.h:29
STL namespace.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
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,...
ConsensusMode
Represents how a node currently participates in Consensus.
@ WrongLedger
We have the wrong ledger and are attempting to acquire it.
@ SwitchedLedger
We switched ledgers since we started this consensus round but are now running on what we believe is t...
@ Proposing
We are normal participant in consensus and propose our position.
@ Observing
We are observing peer positions, but not proposing our position.
std::unordered_set< Value, Hash, Pred, Allocator > hash_set
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.
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
ConsensusState
Whether we have or don't have a consensus.
@ Expired
Consensus time limit has hard-expired.
@ MovedOn
The network has consensus without us.
@ No
We do not have consensus.
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.
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...
@ Establish
Establishing consensus by exchanging proposals with our peers.
@ Open
We haven't closed our ledger yet, but others might have.
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:1466
std::unordered_map< Key, Value, Hash, Pred, Allocator > hash_map
std::pair< std::size_t, std::optional< ConsensusParms::AvalancheState > > getNeededWeight(ConsensusParms const &p, ConsensusParms::AvalancheState currentState, int percentTime, std::size_t currentRounds, std::size_t minimumRounds)
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, bool stalled, ConsensusParms const &parms, bool proposing, beast::Journal j, std::unique_ptr< std::stringstream > const &clog={})
Determine whether the network reached consensus and whether we joined.
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, std::unique_ptr< std::stringstream > const &clog={})
Determines whether the current ledger should close at this time.
constexpr auto kLedgerDefaultTimeResolution
Initial resolution of ledger close time.
constexpr bool any(HashRouterFlags flags)
Definition HashRouter.h:71
T str(T... args)
Stores the set of initial close times.
Consensus algorithm parameters.
std::size_t const avCtConsensusPct
Percentage of nodes required to reach agreement on ledger close time.
std::chrono::milliseconds const ledgerMinConsensus
The number of seconds we wait minimum to ensure participation.
std::size_t const avMinRounds
Number of rounds before certain actions can happen.
std::chrono::seconds const proposeFRESHNESS
How long we consider a proposal fresh.
std::chrono::seconds const proposeINTERVAL
How often we force generating a new proposal to keep ours fresh.
std::map< AvalancheState, AvalancheCutoff > const avalancheCutoffs
Map the consensus requirement avalanche state to the amount of time that must pass before moving to t...
std::chrono::milliseconds const avMinConsensusTime
The minimum amount of time to consider the previous round to have taken.
Encapsulates the result of consensus.
DisputedTx< Tx_t, NodeID_t > Dispute_t
T time_since_epoch(T... args)
T to_string(T... args)
T value_or(T... args)