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
60bool
62 bool anyTransactions,
63 std::size_t prevProposers,
64 std::size_t proposersClosed,
65 std::size_t proposersValidated,
66 std::chrono::milliseconds prevRoundTime,
67 std::chrono::milliseconds timeSincePrevClose,
69 std::chrono::milliseconds idleInterval,
70 ConsensusParms const& parms,
72 std::unique_ptr<std::stringstream> const& clog = {});
73
91 std::size_t prevProposers,
92 std::size_t currentProposers,
93 std::size_t currentAgree,
94 std::size_t currentFinished,
95 std::chrono::milliseconds previousAgreeTime,
96 std::chrono::milliseconds currentAgreeTime,
97 ConsensusParms const& parms,
98 bool proposing,
100 std::unique_ptr<std::stringstream> const& clog = {});
101
290template <class Adaptor>
292{
293 using Ledger_t = typename Adaptor::Ledger_t;
294 using TxSet_t = typename Adaptor::TxSet_t;
295 using NodeID_t = typename Adaptor::NodeID_t;
296 using Tx_t = typename TxSet_t::Tx;
297 using PeerPosition_t = typename Adaptor::PeerPosition_t;
299 NodeID_t,
300 typename Ledger_t::ID,
301 typename TxSet_t::ID>;
302
304
305 // Helper class to ensure adaptor is notified whenever the ConsensusMode
306 // changes
308 {
310
311 public:
313 {
314 }
316 get() const
317 {
318 return mode_;
319 }
320
321 void
322 set(ConsensusMode mode, Adaptor& a)
323 {
324 a.onModeChange(mode_, mode);
325 mode_ = mode;
326 }
327 };
328
329public:
332
333 Consensus(Consensus&&) noexcept = default;
334
341 Consensus(clock_type const& clock, Adaptor& adaptor, beast::Journal j);
342
358 void
360 NetClock::time_point const& now,
361 typename Ledger_t::ID const& prevLedgerID,
362 Ledger_t prevLedger,
363 hash_set<NodeID_t> const& nowUntrusted,
364 bool proposing,
365 std::unique_ptr<std::stringstream> const& clog = {});
366
373 bool
375 NetClock::time_point const& now,
376 PeerPosition_t const& newProposal);
377
383 void
385 NetClock::time_point const& now,
386 std::unique_ptr<std::stringstream> const& clog = {});
387
393 void
394 gotTxSet(NetClock::time_point const& now, TxSet_t const& txSet);
395
412 void
414 NetClock::time_point const& now,
416
424 typename Ledger_t::ID
426 {
427 return prevLedgerID_;
428 }
429
431 phase() const
432 {
433 return phase_;
434 }
435
444 getJson(bool full) const;
445
446private:
447 void
449 NetClock::time_point const& now,
450 typename Ledger_t::ID const& prevLedgerID,
451 Ledger_t const& prevLedger,
452 ConsensusMode mode,
454
455 // Change our view of the previous ledger
456 void
458 typename Ledger_t::ID const& lgrId,
460
466 void
468
472 void
474
477 bool
479 NetClock::time_point const& now,
480 PeerPosition_t const& newProposal);
481
488 void
490
499 void
501
524 bool
526
527 // Close the open ledger and establish initial position.
528 void
530
531 // Adjust our positions to try to agree with other validators.
532 void
534
535 bool
537
538 // Create disputes between our position and the provided one.
539 void
541 TxSet_t const& o,
542 std::unique_ptr<std::stringstream> const& clog = {});
543
544 // Update our disputes given that this node has adopted a new position.
545 // Will call createDisputes as needed.
546 void
547 updateDisputes(NodeID_t const& node, TxSet_t const& other);
548
549 // Revoke our outstanding proposal, if any, and cease proposing
550 // until this round ends.
551 void
553
554 // The rounded or effective close time estimate from a proposer
557
558private:
559 Adaptor& adaptor_;
560
563 bool firstRound_ = true;
565
567
568 // How long the consensus convergence has taken, expressed as
569 // a percentage of the time that we expected it to take.
571
572 // How long has this round been open
574
576
577 // Time it took for the last consensus round to converge
579
580 //-------------------------------------------------------------------------
581 // Network time measurements of consensus progress
582
583 // The current network adjusted time. This is the network time the
584 // ledger would close if it closed now
587
588 //-------------------------------------------------------------------------
589 // Non-peer (self) consensus data
590
591 // Last validated ledger ID provided to consensus
592 typename Ledger_t::ID prevLedgerID_;
593 // Last validated ledger seen by consensus
595
596 // Transaction Sets, indexed by hash of transaction tree
598
601
602 //-------------------------------------------------------------------------
603 // Peer related consensus data
604
605 // Peer proposed positions for the current round
607
608 // Recently received peer positions, available when transitioning between
609 // ledgers or rounds
611
612 // The number of proposers who participated in the last consensus round
614
615 // nodes that have bowed out of this consensus process
617
618 // Journal for debugging
620};
621
622template <class Adaptor>
624 clock_type const& clock,
625 Adaptor& adaptor,
626 beast::Journal journal)
627 : adaptor_(adaptor), clock_(clock), j_{journal}
628{
629 JLOG(j_.debug()) << "Creating consensus object";
630}
631
632template <class Adaptor>
633void
635 NetClock::time_point const& now,
636 typename Ledger_t::ID const& prevLedgerID,
637 Ledger_t prevLedger,
638 hash_set<NodeID_t> const& nowUntrusted,
639 bool proposing,
641{
642 if (firstRound_)
643 {
644 // take our initial view of closeTime_ from the seed ledger
645 prevRoundTime_ = adaptor_.parms().ledgerIDLE_INTERVAL;
646 prevCloseTime_ = prevLedger.closeTime();
647 firstRound_ = false;
648 }
649 else
650 {
651 prevCloseTime_ = rawCloseTimes_.self;
652 }
653
654 for (NodeID_t const& n : nowUntrusted)
655 recentPeerPositions_.erase(n);
656
657 ConsensusMode startMode =
659
660 // We were handed the wrong ledger
661 if (prevLedger.id() != prevLedgerID)
662 {
663 // try to acquire the correct one
664 if (auto newLedger = adaptor_.acquireLedger(prevLedgerID))
665 {
666 prevLedger = *newLedger;
667 }
668 else // Unable to acquire the correct ledger
669 {
670 startMode = ConsensusMode::wrongLedger;
671 JLOG(j_.info())
672 << "Entering consensus with: " << previousLedger_.id();
673 JLOG(j_.info()) << "Correct LCL is: " << prevLedgerID;
674 }
675 }
676
677 startRoundInternal(now, prevLedgerID, prevLedger, startMode, clog);
678}
679template <class Adaptor>
680void
682 NetClock::time_point const& now,
683 typename Ledger_t::ID const& prevLedgerID,
684 Ledger_t const& prevLedger,
685 ConsensusMode mode,
687{
688 phase_ = ConsensusPhase::open;
689 JLOG(j_.debug()) << "transitioned to ConsensusPhase::open ";
690 CLOG(clog) << "startRoundInternal transitioned to ConsensusPhase::open, "
691 "previous ledgerID: "
692 << prevLedgerID << ", seq: " << prevLedger.seq() << ". ";
693 mode_.set(mode, adaptor_);
694 now_ = now;
695 prevLedgerID_ = prevLedgerID;
696 previousLedger_ = prevLedger;
697 result_.reset();
698 convergePercent_ = 0;
699 haveCloseTimeConsensus_ = false;
700 openTime_.reset(clock_.now());
701 currPeerPositions_.clear();
702 acquired_.clear();
703 rawCloseTimes_.peers.clear();
704 rawCloseTimes_.self = {};
705 deadNodes_.clear();
706
707 closeResolution_ = getNextLedgerTimeResolution(
708 previousLedger_.closeTimeResolution(),
709 previousLedger_.closeAgree(),
710 previousLedger_.seq() + typename Ledger_t::Seq{1});
711
712 playbackProposals();
713 CLOG(clog) << "number of peer proposals,previous proposers: "
714 << currPeerPositions_.size() << ',' << prevProposers_ << ". ";
715 if (currPeerPositions_.size() > (prevProposers_ / 2))
716 {
717 // We may be falling behind, don't wait for the timer
718 // consider closing the ledger immediately
719 CLOG(clog) << "consider closing the ledger immediately. ";
720 timerEntry(now_, clog);
721 }
722}
723
724template <class Adaptor>
725bool
727 NetClock::time_point const& now,
728 PeerPosition_t const& newPeerPos)
729{
730 JLOG(j_.debug()) << "PROPOSAL " << newPeerPos.render();
731 auto const& peerID = newPeerPos.proposal().nodeID();
732
733 // Always need to store recent positions
734 {
735 auto& props = recentPeerPositions_[peerID];
736
737 if (props.size() >= 10)
738 props.pop_front();
739
740 props.push_back(newPeerPos);
741 }
742 return peerProposalInternal(now, newPeerPos);
743}
744
745template <class Adaptor>
746bool
748 NetClock::time_point const& now,
749 PeerPosition_t const& newPeerPos)
750{
751 // Nothing to do for now if we are currently working on a ledger
752 if (phase_ == ConsensusPhase::accepted)
753 return false;
754
755 now_ = now;
756
757 auto const& newPeerProp = newPeerPos.proposal();
758
759 if (newPeerProp.prevLedger() != prevLedgerID_)
760 {
761 JLOG(j_.debug()) << "Got proposal for " << newPeerProp.prevLedger()
762 << " but we are on " << prevLedgerID_;
763 return false;
764 }
765
766 auto const& peerID = newPeerProp.nodeID();
767
768 if (deadNodes_.find(peerID) != deadNodes_.end())
769 {
770 JLOG(j_.info()) << "Position from dead node: " << peerID;
771 return false;
772 }
773
774 {
775 // update current position
776 auto peerPosIt = currPeerPositions_.find(peerID);
777
778 if (peerPosIt != currPeerPositions_.end())
779 {
780 if (newPeerProp.proposeSeq() <=
781 peerPosIt->second.proposal().proposeSeq())
782 {
783 return false;
784 }
785 }
786
787 if (newPeerProp.isBowOut())
788 {
789 JLOG(j_.info()) << "Peer " << peerID << " bows out";
790 if (result_)
791 {
792 for (auto& it : result_->disputes)
793 it.second.unVote(peerID);
794 }
795 if (peerPosIt != currPeerPositions_.end())
796 currPeerPositions_.erase(peerID);
797 deadNodes_.insert(peerID);
798
799 return true;
800 }
801
802 if (peerPosIt != currPeerPositions_.end())
803 peerPosIt->second = newPeerPos;
804 else
805 currPeerPositions_.emplace(peerID, newPeerPos);
806 }
807
808 if (newPeerProp.isInitial())
809 {
810 // Record the close time estimate
811 JLOG(j_.trace()) << "Peer reports close time as "
812 << newPeerProp.closeTime().time_since_epoch().count();
813 ++rawCloseTimes_.peers[newPeerProp.closeTime()];
814 }
815
816 JLOG(j_.trace()) << "Processing peer proposal " << newPeerProp.proposeSeq()
817 << "/" << newPeerProp.position();
818
819 {
820 auto const ait = acquired_.find(newPeerProp.position());
821 if (ait == acquired_.end())
822 {
823 // acquireTxSet will return the set if it is available, or
824 // spawn a request for it and return nullopt/nullptr. It will call
825 // gotTxSet once it arrives
826 if (auto set = adaptor_.acquireTxSet(newPeerProp.position()))
827 gotTxSet(now_, *set);
828 else
829 JLOG(j_.debug()) << "Don't have tx set for peer";
830 }
831 else if (result_)
832 {
833 updateDisputes(newPeerProp.nodeID(), ait->second);
834 }
835 }
836
837 return true;
838}
839
840template <class Adaptor>
841void
843 NetClock::time_point const& now,
845{
846 CLOG(clog) << "Consensus<Adaptor>::timerEntry. ";
847 // Nothing to do if we are currently working on a ledger
848 if (phase_ == ConsensusPhase::accepted)
849 {
850 CLOG(clog) << "Nothing to do during accepted phase. ";
851 return;
852 }
853
854 now_ = now;
855 CLOG(clog) << "Set network adjusted time to " << to_string(now) << ". ";
856
857 // Check we are on the proper ledger (this may change phase_)
858 const auto phaseOrig = phase_;
859 CLOG(clog) << "Phase " << to_string(phaseOrig) << ". ";
860 checkLedger(clog);
861 if (phaseOrig != phase_)
862 {
863 CLOG(clog) << "Changed phase to << " << to_string(phase_) << ". ";
864 }
865
866 if (phase_ == ConsensusPhase::open)
867 phaseOpen(clog);
868 else if (phase_ == ConsensusPhase::establish)
869 phaseEstablish(clog);
870 CLOG(clog) << "timerEntry finishing in phase " << to_string(phase_) << ". ";
871}
872
873template <class Adaptor>
874void
876 NetClock::time_point const& now,
877 TxSet_t const& txSet)
878{
879 // Nothing to do if we've finished work on a ledger
880 if (phase_ == ConsensusPhase::accepted)
881 return;
882
883 now_ = now;
884
885 auto id = txSet.id();
886
887 // If we've already processed this transaction set since requesting
888 // it from the network, there is nothing to do now
889 if (!acquired_.emplace(id, txSet).second)
890 return;
891
892 if (!result_)
893 {
894 JLOG(j_.debug()) << "Not creating disputes: no position yet.";
895 }
896 else
897 {
898 // Our position is added to acquired_ as soon as we create it,
899 // so this txSet must differ
900 XRPL_ASSERT(
901 id != result_->position.position(),
902 "ripple::Consensus::gotTxSet : updated transaction set");
903 bool any = false;
904 for (auto const& [nodeId, peerPos] : currPeerPositions_)
905 {
906 if (peerPos.proposal().position() == id)
907 {
908 updateDisputes(nodeId, txSet);
909 any = true;
910 }
911 }
912
913 if (!any)
914 {
915 JLOG(j_.warn())
916 << "By the time we got " << id << " no peers were proposing it";
917 }
918 }
919}
920
921template <class Adaptor>
922void
924 NetClock::time_point const& now,
926{
927 using namespace std::chrono_literals;
928 JLOG(j_.info()) << "Simulating consensus";
929 now_ = now;
930 closeLedger({});
931 result_->roundTime.tick(consensusDelay.value_or(100ms));
932 result_->proposers = prevProposers_ = currPeerPositions_.size();
933 prevRoundTime_ = result_->roundTime.read();
935 adaptor_.onForceAccept(
936 *result_,
937 previousLedger_,
938 closeResolution_,
939 rawCloseTimes_,
940 mode_.get(),
941 getJson(true));
942 JLOG(j_.info()) << "Simulation complete";
943}
944
945template <class Adaptor>
948{
949 using std::to_string;
950 using Int = Json::Value::Int;
951
953
954 ret["proposing"] = (mode_.get() == ConsensusMode::proposing);
955 ret["proposers"] = static_cast<int>(currPeerPositions_.size());
956
957 if (mode_.get() != ConsensusMode::wrongLedger)
958 {
959 ret["synched"] = true;
960 ret["ledger_seq"] =
961 static_cast<std::uint32_t>(previousLedger_.seq()) + 1;
962 ret["close_granularity"] = static_cast<Int>(closeResolution_.count());
963 }
964 else
965 ret["synched"] = false;
966
967 ret["phase"] = to_string(phase_);
968
969 if (result_ && !result_->disputes.empty() && !full)
970 ret["disputes"] = static_cast<Int>(result_->disputes.size());
971
972 if (result_)
973 ret["our_position"] = result_->position.getJson();
974
975 if (full)
976 {
977 if (result_)
978 ret["current_ms"] =
979 static_cast<Int>(result_->roundTime.read().count());
980 ret["converge_percent"] = convergePercent_;
981 ret["close_resolution"] = static_cast<Int>(closeResolution_.count());
982 ret["have_time_consensus"] = haveCloseTimeConsensus_;
983 ret["previous_proposers"] = static_cast<Int>(prevProposers_);
984 ret["previous_mseconds"] = static_cast<Int>(prevRoundTime_.count());
985
986 if (!currPeerPositions_.empty())
987 {
989
990 for (auto const& [nodeId, peerPos] : currPeerPositions_)
991 {
992 ppj[to_string(nodeId)] = peerPos.getJson();
993 }
994 ret["peer_positions"] = std::move(ppj);
995 }
996
997 if (!acquired_.empty())
998 {
1000 for (auto const& at : acquired_)
1001 {
1002 acq.append(to_string(at.first));
1003 }
1004 ret["acquired"] = std::move(acq);
1005 }
1006
1007 if (result_ && !result_->disputes.empty())
1008 {
1010 for (auto const& [txId, dispute] : result_->disputes)
1011 {
1012 dsj[to_string(txId)] = dispute.getJson();
1013 }
1014 ret["disputes"] = std::move(dsj);
1015 }
1016
1017 if (!rawCloseTimes_.peers.empty())
1018 {
1020 for (auto const& ct : rawCloseTimes_.peers)
1021 {
1022 ctj[std::to_string(ct.first.time_since_epoch().count())] =
1023 ct.second;
1024 }
1025 ret["close_times"] = std::move(ctj);
1026 }
1027
1028 if (!deadNodes_.empty())
1029 {
1031 for (auto const& dn : deadNodes_)
1032 {
1033 dnj.append(to_string(dn));
1034 }
1035 ret["dead_nodes"] = std::move(dnj);
1036 }
1037 }
1038
1039 return ret;
1040}
1041
1042// Handle a change in the prior ledger during a consensus round
1043template <class Adaptor>
1044void
1046 typename Ledger_t::ID const& lgrId,
1048{
1049 CLOG(clog) << "handleWrongLedger. ";
1050 XRPL_ASSERT(
1051 lgrId != prevLedgerID_ || previousLedger_.id() != lgrId,
1052 "ripple::Consensus::handleWrongLedger : have wrong ledger");
1053
1054 // Stop proposing because we are out of sync
1055 leaveConsensus(clog);
1056
1057 // First time switching to this ledger
1058 if (prevLedgerID_ != lgrId)
1059 {
1060 prevLedgerID_ = lgrId;
1061
1062 // Clear out state
1063 if (result_)
1064 {
1065 result_->disputes.clear();
1066 result_->compares.clear();
1067 }
1068
1069 currPeerPositions_.clear();
1070 rawCloseTimes_.peers.clear();
1071 deadNodes_.clear();
1072
1073 // Get back in sync, this will also recreate disputes
1074 playbackProposals();
1075 }
1076
1077 if (previousLedger_.id() == prevLedgerID_)
1078 {
1079 CLOG(clog) << "previousLedger_.id() == prevLeverID_ " << prevLedgerID_
1080 << ". ";
1081 return;
1082 }
1083
1084 // we need to switch the ledger we're working from
1085 if (auto newLedger = adaptor_.acquireLedger(prevLedgerID_))
1086 {
1087 JLOG(j_.info()) << "Have the consensus ledger " << prevLedgerID_;
1088 CLOG(clog) << "Have the consensus ledger " << prevLedgerID_ << ". ";
1089 startRoundInternal(
1090 now_, lgrId, *newLedger, ConsensusMode::switchedLedger, clog);
1091 }
1092 else
1093 {
1094 CLOG(clog) << "Still on wrong ledger. ";
1095 mode_.set(ConsensusMode::wrongLedger, adaptor_);
1096 }
1097}
1098
1099template <class Adaptor>
1100void
1102{
1103 CLOG(clog) << "checkLedger. ";
1104
1105 auto netLgr =
1106 adaptor_.getPrevLedger(prevLedgerID_, previousLedger_, mode_.get());
1107 CLOG(clog) << "network ledgerid " << netLgr << ", " << "previous ledger "
1108 << prevLedgerID_ << ". ";
1109
1110 if (netLgr != prevLedgerID_)
1111 {
1113 ss << "View of consensus changed during " << to_string(phase_)
1114 << " mode=" << to_string(mode_.get()) << ", " << prevLedgerID_
1115 << " to " << netLgr << ", "
1116 << Json::Compact{previousLedger_.getJson()} << ". ";
1117 JLOG(j_.warn()) << ss.str();
1118 CLOG(clog) << ss.str();
1119 CLOG(clog) << "State on consensus change "
1120 << Json::Compact{getJson(true)} << ". ";
1121 handleWrongLedger(netLgr, clog);
1122 }
1123 else if (previousLedger_.id() != prevLedgerID_)
1124 {
1125 CLOG(clog) << "previousLedger_.id() != prevLedgerID_: "
1126 << previousLedger_.id() << ',' << to_string(prevLedgerID_)
1127 << ". ";
1128 handleWrongLedger(netLgr, clog);
1129 }
1130}
1131
1132template <class Adaptor>
1133void
1135{
1136 for (auto const& it : recentPeerPositions_)
1137 {
1138 for (auto const& pos : it.second)
1139 {
1140 if (pos.proposal().prevLedger() == prevLedgerID_)
1141 {
1142 if (peerProposalInternal(now_, pos))
1143 adaptor_.share(pos);
1144 }
1145 }
1146 }
1147}
1148
1149template <class Adaptor>
1150void
1152{
1153 CLOG(clog) << "phaseOpen. ";
1154 using namespace std::chrono;
1155
1156 // it is shortly before ledger close time
1157 bool anyTransactions = adaptor_.hasOpenTransactions();
1158 auto proposersClosed = currPeerPositions_.size();
1159 auto proposersValidated = adaptor_.proposersValidated(prevLedgerID_);
1160
1161 openTime_.tick(clock_.now());
1162
1163 // This computes how long since last ledger's close time
1164 milliseconds sinceClose;
1165 {
1166 auto const mode = mode_.get();
1167 bool const closeAgree = previousLedger_.closeAgree();
1168 auto const prevCloseTime = previousLedger_.closeTime();
1169 auto const prevParentCloseTimePlus1 =
1170 previousLedger_.parentCloseTime() + 1s;
1171 bool const previousCloseCorrect =
1172 (mode != ConsensusMode::wrongLedger) && closeAgree &&
1173 (prevCloseTime != prevParentCloseTimePlus1);
1174
1175 auto const lastCloseTime = previousCloseCorrect
1176 ? prevCloseTime // use consensus timing
1177 : prevCloseTime_; // use the time we saw internally
1178
1179 if (now_ >= lastCloseTime)
1180 sinceClose = duration_cast<milliseconds>(now_ - lastCloseTime);
1181 else
1182 sinceClose = -duration_cast<milliseconds>(lastCloseTime - now_);
1183 CLOG(clog) << "calculating how long since last ledger's close time "
1184 "based on mode : "
1185 << to_string(mode) << ", previous closeAgree: " << closeAgree
1186 << ", previous close time: " << to_string(prevCloseTime)
1187 << ", previous parent close time + 1s: "
1188 << to_string(prevParentCloseTimePlus1)
1189 << ", previous close time seen internally: "
1190 << to_string(prevCloseTime_)
1191 << ", last close time: " << to_string(lastCloseTime)
1192 << ", since close: " << sinceClose.count() << ". ";
1193 }
1194
1195 auto const idleInterval = std::max<milliseconds>(
1196 adaptor_.parms().ledgerIDLE_INTERVAL,
1197 2 * previousLedger_.closeTimeResolution());
1198 CLOG(clog) << "idle interval set to " << idleInterval.count()
1199 << "ms based on "
1200 << "ledgerIDLE_INTERVAL: "
1201 << adaptor_.parms().ledgerIDLE_INTERVAL.count()
1202 << ", previous ledger close time resolution: "
1203 << previousLedger_.closeTimeResolution().count() << "ms. ";
1204
1205 // Decide if we should close the ledger
1207 anyTransactions,
1208 prevProposers_,
1209 proposersClosed,
1210 proposersValidated,
1211 prevRoundTime_,
1212 sinceClose,
1213 openTime_.read(),
1214 idleInterval,
1215 adaptor_.parms(),
1216 j_,
1217 clog))
1218 {
1219 CLOG(clog) << "closing ledger. ";
1220 closeLedger(clog);
1221 }
1222}
1223
1224template <class Adaptor>
1225bool
1228{
1229 CLOG(clog) << "shouldPause? ";
1230 auto const& parms = adaptor_.parms();
1231 std::uint32_t const ahead(
1232 previousLedger_.seq() -
1233 std::min(adaptor_.getValidLedgerIndex(), previousLedger_.seq()));
1234 auto [quorum, trustedKeys] = adaptor_.getQuorumKeys();
1235 std::size_t const totalValidators = trustedKeys.size();
1236 std::size_t laggards =
1237 adaptor_.laggards(previousLedger_.seq(), trustedKeys);
1238 std::size_t const offline = trustedKeys.size();
1239
1240 std::stringstream vars;
1241 vars << " consensuslog (working seq: " << previousLedger_.seq() << ", "
1242 << "validated seq: " << adaptor_.getValidLedgerIndex() << ", "
1243 << "am validator: " << adaptor_.validator() << ", "
1244 << "have validated: " << adaptor_.haveValidated() << ", "
1245 << "roundTime: " << result_->roundTime.read().count() << ", "
1246 << "max consensus time: " << parms.ledgerMAX_CONSENSUS.count() << ", "
1247 << "validators: " << totalValidators << ", "
1248 << "laggards: " << laggards << ", " << "offline: " << offline << ", "
1249 << "quorum: " << quorum << ")";
1250
1251 if (!ahead || !laggards || !totalValidators || !adaptor_.validator() ||
1252 !adaptor_.haveValidated() ||
1253 result_->roundTime.read() > parms.ledgerMAX_CONSENSUS)
1254 {
1255 j_.debug() << "not pausing (early)" << vars.str();
1256 CLOG(clog) << "Not pausing (early). ";
1257 return false;
1258 }
1259
1260 bool willPause = false;
1261
1275 constexpr static std::size_t maxPausePhase = 4;
1276
1296 std::size_t const phase = (ahead - 1) % (maxPausePhase + 1);
1297
1298 // validators that remain after the laggards() function are considered
1299 // offline, and should be considered as laggards for purposes of
1300 // evaluating whether the threshold for non-laggards has been reached.
1301 switch (phase)
1302 {
1303 case 0:
1304 // Laggards and offline shouldn't preclude consensus.
1305 if (laggards + offline > totalValidators - quorum)
1306 willPause = true;
1307 break;
1308 case maxPausePhase:
1309 // No tolerance.
1310 willPause = true;
1311 break;
1312 default:
1313 // Ensure that sufficient validators are known to be not lagging.
1314 // Their sufficiently most recent validation sequence was equal to
1315 // or greater than our own.
1316 //
1317 // The threshold is the amount required for quorum plus
1318 // the proportion of the remainder based on number of intermediate
1319 // phases between 0 and max.
1320 float const nonLaggards = totalValidators - (laggards + offline);
1321 float const quorumRatio =
1322 static_cast<float>(quorum) / totalValidators;
1323 float const allowedDissent = 1.0f - quorumRatio;
1324 float const phaseFactor = static_cast<float>(phase) / maxPausePhase;
1325
1326 if (nonLaggards / totalValidators <
1327 quorumRatio + (allowedDissent * phaseFactor))
1328 {
1329 willPause = true;
1330 }
1331 }
1332
1333 if (willPause)
1334 {
1335 j_.warn() << "pausing" << vars.str();
1336 CLOG(clog) << "pausing " << vars.str() << ". ";
1337 }
1338 else
1339 {
1340 j_.debug() << "not pausing" << vars.str();
1341 CLOG(clog) << "not pausing. ";
1342 }
1343 return willPause;
1344}
1345
1346template <class Adaptor>
1347void
1350{
1351 CLOG(clog) << "phaseEstablish. ";
1352 // can only establish consensus if we already took a stance
1353 XRPL_ASSERT(result_, "ripple::Consensus::phaseEstablish : result is set");
1354
1355 using namespace std::chrono;
1356 ConsensusParms const& parms = adaptor_.parms();
1357
1358 result_->roundTime.tick(clock_.now());
1359 result_->proposers = currPeerPositions_.size();
1360
1361 convergePercent_ = result_->roundTime.read() * 100 /
1362 std::max<milliseconds>(prevRoundTime_, parms.avMIN_CONSENSUS_TIME);
1363 CLOG(clog) << "convergePercent_ " << convergePercent_
1364 << " is based on round duration so far: "
1365 << result_->roundTime.read().count() << "ms, "
1366 << "previous round duration: " << prevRoundTime_.count()
1367 << "ms, "
1368 << "avMIN_CONSENSUS_TIME: " << parms.avMIN_CONSENSUS_TIME.count()
1369 << "ms. ";
1370
1371 // Give everyone a chance to take an initial position
1372 if (result_->roundTime.read() < parms.ledgerMIN_CONSENSUS)
1373 {
1374 CLOG(clog) << "ledgerMIN_CONSENSUS not reached: "
1375 << parms.ledgerMIN_CONSENSUS.count() << "ms. ";
1376 return;
1377 }
1378
1379 updateOurPositions(clog);
1380
1381 // Nothing to do if too many laggards or we don't have consensus.
1382 if (shouldPause(clog) || !haveConsensus(clog))
1383 return;
1384
1385 if (!haveCloseTimeConsensus_)
1386 {
1387 JLOG(j_.info()) << "We have TX consensus but not CT consensus";
1388 CLOG(clog) << "We have TX consensus but not CT consensus. ";
1389 return;
1390 }
1391
1392 JLOG(j_.info()) << "Converge cutoff (" << currPeerPositions_.size()
1393 << " participants)";
1394 CLOG(clog) << "Converge cutoff (" << currPeerPositions_.size()
1395 << " participants). Transitioned to ConsensusPhase::accepted. ";
1396 adaptor_.updateOperatingMode(currPeerPositions_.size());
1397 prevProposers_ = currPeerPositions_.size();
1398 prevRoundTime_ = result_->roundTime.read();
1399 phase_ = ConsensusPhase::accepted;
1400 JLOG(j_.debug()) << "transitioned to ConsensusPhase::accepted";
1401 adaptor_.onAccept(
1402 *result_,
1403 previousLedger_,
1404 closeResolution_,
1405 rawCloseTimes_,
1406 mode_.get(),
1407 getJson(true),
1408 adaptor_.validating());
1409}
1410
1411template <class Adaptor>
1412void
1414{
1415 // We should not be closing if we already have a position
1416 XRPL_ASSERT(!result_, "ripple::Consensus::closeLedger : result is not set");
1417
1419 JLOG(j_.debug()) << "transitioned to ConsensusPhase::establish";
1420 rawCloseTimes_.self = now_;
1421
1422 result_.emplace(adaptor_.onClose(previousLedger_, now_, mode_.get()));
1423 result_->roundTime.reset(clock_.now());
1424 // Share the newly created transaction set if we haven't already
1425 // received it from a peer
1426 if (acquired_.emplace(result_->txns.id(), result_->txns).second)
1427 adaptor_.share(result_->txns);
1428
1429 const auto mode = mode_.get();
1430 CLOG(clog)
1431 << "closeLedger transitioned to ConsensusPhase::establish, mode: "
1432 << to_string(mode)
1433 << ", number of peer positions: " << currPeerPositions_.size() << ". ";
1434 if (mode == ConsensusMode::proposing)
1435 adaptor_.propose(result_->position);
1436
1437 // Create disputes with any peer positions we have transactions for
1438 for (auto const& pit : currPeerPositions_)
1439 {
1440 auto const& pos = pit.second.proposal().position();
1441 auto const it = acquired_.find(pos);
1442 if (it != acquired_.end())
1443 createDisputes(it->second, clog);
1444 }
1445}
1446
1459inline int
1460participantsNeeded(int participants, int percent)
1461{
1462 int result = ((participants * percent) + (percent / 2)) / 100;
1463
1464 return (result == 0) ? 1 : result;
1465}
1466
1467template <class Adaptor>
1468void
1471{
1472 // We must have a position if we are updating it
1473 XRPL_ASSERT(
1474 result_, "ripple::Consensus::updateOurPositions : result is set");
1475 ConsensusParms const& parms = adaptor_.parms();
1476
1477 // Compute a cutoff time
1478 auto const peerCutoff = now_ - parms.proposeFRESHNESS;
1479 auto const ourCutoff = now_ - parms.proposeINTERVAL;
1480 CLOG(clog) << "updateOurPositions. peerCutoff " << to_string(peerCutoff)
1481 << ", ourCutoff " << to_string(ourCutoff) << ". ";
1482
1483 // Verify freshness of peer positions and compute close times
1485 {
1486 auto it = currPeerPositions_.begin();
1487 while (it != currPeerPositions_.end())
1488 {
1489 Proposal_t const& peerProp = it->second.proposal();
1490 if (peerProp.isStale(peerCutoff))
1491 {
1492 // peer's proposal is stale, so remove it
1493 NodeID_t const& peerID = peerProp.nodeID();
1494 JLOG(j_.warn()) << "Removing stale proposal from " << peerID;
1495 for (auto& dt : result_->disputes)
1496 dt.second.unVote(peerID);
1497 it = currPeerPositions_.erase(it);
1498 }
1499 else
1500 {
1501 // proposal is still fresh
1502 ++closeTimeVotes[asCloseTime(peerProp.closeTime())];
1503 ++it;
1504 }
1505 }
1506 }
1507
1508 // This will stay unseated unless there are any changes
1509 std::optional<TxSet_t> ourNewSet;
1510
1511 // Update votes on disputed transactions
1512 {
1514 for (auto& [txId, dispute] : result_->disputes)
1515 {
1516 // Because the threshold for inclusion increases,
1517 // time can change our position on a dispute
1518 if (dispute.updateVote(
1519 convergePercent_,
1520 mode_.get() == ConsensusMode::proposing,
1521 parms))
1522 {
1523 if (!mutableSet)
1524 mutableSet.emplace(result_->txns);
1525
1526 if (dispute.getOurVote())
1527 {
1528 // now a yes
1529 mutableSet->insert(dispute.tx());
1530 }
1531 else
1532 {
1533 // now a no
1534 mutableSet->erase(txId);
1535 }
1536 }
1537 }
1538
1539 if (mutableSet)
1540 ourNewSet.emplace(std::move(*mutableSet));
1541 }
1542
1543 NetClock::time_point consensusCloseTime = {};
1544 haveCloseTimeConsensus_ = false;
1545
1546 if (currPeerPositions_.empty())
1547 {
1548 // no other times
1549 haveCloseTimeConsensus_ = true;
1550 consensusCloseTime = asCloseTime(result_->position.closeTime());
1551 }
1552 else
1553 {
1554 int neededWeight;
1555
1556 if (convergePercent_ < parms.avMID_CONSENSUS_TIME)
1557 neededWeight = parms.avINIT_CONSENSUS_PCT;
1558 else if (convergePercent_ < parms.avLATE_CONSENSUS_TIME)
1559 neededWeight = parms.avMID_CONSENSUS_PCT;
1560 else if (convergePercent_ < parms.avSTUCK_CONSENSUS_TIME)
1561 neededWeight = parms.avLATE_CONSENSUS_PCT;
1562 else
1563 neededWeight = parms.avSTUCK_CONSENSUS_PCT;
1564 CLOG(clog) << "neededWeight " << neededWeight << ". ";
1565
1566 int participants = currPeerPositions_.size();
1567 if (mode_.get() == ConsensusMode::proposing)
1568 {
1569 ++closeTimeVotes[asCloseTime(result_->position.closeTime())];
1570 ++participants;
1571 }
1572
1573 // Threshold for non-zero vote
1574 int threshVote = participantsNeeded(participants, neededWeight);
1575
1576 // Threshold to declare consensus
1577 int const threshConsensus =
1578 participantsNeeded(participants, parms.avCT_CONSENSUS_PCT);
1579
1581 ss << "Proposers:" << currPeerPositions_.size()
1582 << " nw:" << neededWeight << " thrV:" << threshVote
1583 << " thrC:" << threshConsensus;
1584 JLOG(j_.info()) << ss.str();
1585 CLOG(clog) << ss.str();
1586
1587 for (auto const& [t, v] : closeTimeVotes)
1588 {
1589 JLOG(j_.debug())
1590 << "CCTime: seq "
1591 << static_cast<std::uint32_t>(previousLedger_.seq()) + 1 << ": "
1592 << t.time_since_epoch().count() << " has " << v << ", "
1593 << threshVote << " required";
1594
1595 if (v >= threshVote)
1596 {
1597 // A close time has enough votes for us to try to agree
1598 consensusCloseTime = t;
1599 threshVote = v;
1600
1601 if (threshVote >= threshConsensus)
1602 haveCloseTimeConsensus_ = true;
1603 }
1604 }
1605
1606 if (!haveCloseTimeConsensus_)
1607 {
1608 JLOG(j_.debug())
1609 << "No CT consensus:" << " Proposers:"
1610 << currPeerPositions_.size()
1611 << " Mode:" << to_string(mode_.get())
1612 << " Thresh:" << threshConsensus
1613 << " Pos:" << consensusCloseTime.time_since_epoch().count();
1614 CLOG(clog) << "No close time consensus. ";
1615 }
1616 }
1617
1618 if (!ourNewSet &&
1619 ((consensusCloseTime != asCloseTime(result_->position.closeTime())) ||
1620 result_->position.isStale(ourCutoff)))
1621 {
1622 // close time changed or our position is stale
1623 ourNewSet.emplace(result_->txns);
1624 }
1625
1626 if (ourNewSet)
1627 {
1628 auto newID = ourNewSet->id();
1629
1630 result_->txns = std::move(*ourNewSet);
1631
1633 ss << "Position change: CTime "
1634 << consensusCloseTime.time_since_epoch().count() << ", tx " << newID;
1635 JLOG(j_.info()) << ss.str();
1636 CLOG(clog) << ss.str();
1637
1638 result_->position.changePosition(newID, consensusCloseTime, now_);
1639
1640 // Share our new transaction set and update disputes
1641 // if we haven't already received it
1642 if (acquired_.emplace(newID, result_->txns).second)
1643 {
1644 if (!result_->position.isBowOut())
1645 adaptor_.share(result_->txns);
1646
1647 for (auto const& [nodeId, peerPos] : currPeerPositions_)
1648 {
1649 Proposal_t const& p = peerPos.proposal();
1650 if (p.position() == newID)
1651 updateDisputes(nodeId, result_->txns);
1652 }
1653 }
1654
1655 // Share our new position if we are still participating this round
1656 if (!result_->position.isBowOut() &&
1657 (mode_.get() == ConsensusMode::proposing))
1658 adaptor_.propose(result_->position);
1659 }
1660}
1661
1662template <class Adaptor>
1663bool
1666{
1667 // Must have a stance if we are checking for consensus
1668 XRPL_ASSERT(result_, "ripple::Consensus::haveConsensus : has result");
1669
1670 // CHECKME: should possibly count unacquired TX sets as disagreeing
1671 int agree = 0, disagree = 0;
1672
1673 auto ourPosition = result_->position.position();
1674
1675 // Count number of agreements/disagreements with our position
1676 for (auto const& [nodeId, peerPos] : currPeerPositions_)
1677 {
1678 Proposal_t const& peerProp = peerPos.proposal();
1679 if (peerProp.position() == ourPosition)
1680 {
1681 ++agree;
1682 }
1683 else
1684 {
1685 JLOG(j_.debug()) << nodeId << " has " << peerProp.position();
1686 ++disagree;
1687 }
1688 }
1689 auto currentFinished =
1690 adaptor_.proposersFinished(previousLedger_, prevLedgerID_);
1691
1692 JLOG(j_.debug()) << "Checking for TX consensus: agree=" << agree
1693 << ", disagree=" << disagree;
1694
1695 // Determine if we actually have consensus or not
1696 result_->state = checkConsensus(
1697 prevProposers_,
1698 agree + disagree,
1699 agree,
1700 currentFinished,
1701 prevRoundTime_,
1702 result_->roundTime.read(),
1703 adaptor_.parms(),
1704 mode_.get() == ConsensusMode::proposing,
1705 j_,
1706 clog);
1707
1708 if (result_->state == ConsensusState::No)
1709 {
1710 CLOG(clog) << "No consensus. ";
1711 return false;
1712 }
1713
1714 // There is consensus, but we need to track if the network moved on
1715 // without us.
1716 if (result_->state == ConsensusState::MovedOn)
1717 {
1718 JLOG(j_.error()) << "Unable to reach consensus";
1719 JLOG(j_.error()) << Json::Compact{getJson(true)};
1720 CLOG(clog) << "Unable to reach consensus "
1721 << Json::Compact{getJson(true)} << ". ";
1722 }
1723
1724 CLOG(clog) << "Consensus has been reached. ";
1725 return true;
1726}
1727
1728template <class Adaptor>
1729void
1732{
1733 if (mode_.get() == ConsensusMode::proposing)
1734 {
1735 if (result_ && !result_->position.isBowOut())
1736 {
1737 result_->position.bowOut(now_);
1738 adaptor_.propose(result_->position);
1739 }
1740
1741 mode_.set(ConsensusMode::observing, adaptor_);
1742 JLOG(j_.info()) << "Bowing out of consensus";
1743 CLOG(clog) << "Bowing out of consensus. ";
1744 }
1745}
1746
1747template <class Adaptor>
1748void
1750 TxSet_t const& o,
1752{
1753 // Cannot create disputes without our stance
1754 XRPL_ASSERT(result_, "ripple::Consensus::createDisputes : result is set");
1755
1756 // Only create disputes if this is a new set
1757 auto const emplaced = result_->compares.emplace(o.id()).second;
1758 CLOG(clog) << "createDisputes: new set? " << !emplaced << ". ";
1759 if (!emplaced)
1760 return;
1761
1762 // Nothing to dispute if we agree
1763 if (result_->txns.id() == o.id())
1764 {
1765 CLOG(clog) << "both sets are identical. ";
1766 return;
1767 }
1768
1769 CLOG(clog) << "comparing existing with new set: " << result_->txns.id()
1770 << ',' << o.id() << ". ";
1771 JLOG(j_.debug()) << "createDisputes " << result_->txns.id() << " to "
1772 << o.id();
1773
1774 auto differences = result_->txns.compare(o);
1775
1776 int dc = 0;
1777
1778 for (auto const& [txId, inThisSet] : differences)
1779 {
1780 ++dc;
1781 // create disputed transactions (from the ledger that has them)
1782 XRPL_ASSERT(
1783 (inThisSet && result_->txns.find(txId) && !o.find(txId)) ||
1784 (!inThisSet && !result_->txns.find(txId) && o.find(txId)),
1785 "ripple::Consensus::createDisputes : has disputed transactions");
1786
1787 Tx_t tx = inThisSet ? result_->txns.find(txId) : o.find(txId);
1788 auto txID = tx.id();
1789
1790 if (result_->disputes.find(txID) != result_->disputes.end())
1791 continue;
1792
1793 JLOG(j_.debug()) << "Transaction " << txID << " is disputed";
1794
1795 typename Result::Dispute_t dtx{
1796 tx,
1797 result_->txns.exists(txID),
1798 std::max(prevProposers_, currPeerPositions_.size()),
1799 j_};
1800
1801 // Update all of the available peer's votes on the disputed transaction
1802 for (auto const& [nodeId, peerPos] : currPeerPositions_)
1803 {
1804 Proposal_t const& peerProp = peerPos.proposal();
1805 auto const cit = acquired_.find(peerProp.position());
1806 if (cit != acquired_.end())
1807 dtx.setVote(nodeId, cit->second.exists(txID));
1808 }
1809 adaptor_.share(dtx.tx());
1810
1811 result_->disputes.emplace(txID, std::move(dtx));
1812 }
1813 JLOG(j_.debug()) << dc << " differences found";
1814 CLOG(clog) << "disputes: " << dc << ". ";
1815}
1816
1817template <class Adaptor>
1818void
1820{
1821 // Cannot updateDisputes without our stance
1822 XRPL_ASSERT(result_, "ripple::Consensus::updateDisputes : result is set");
1823
1824 // Ensure we have created disputes against this set if we haven't seen
1825 // it before
1826 if (result_->compares.find(other.id()) == result_->compares.end())
1827 createDisputes(other);
1828
1829 for (auto& it : result_->disputes)
1830 {
1831 auto& d = it.second;
1832 d.setVote(node, other.exists(d.tx().id()));
1833 }
1834}
1835
1836template <class Adaptor>
1839{
1840 return roundCloseTime(raw, closeResolution_);
1841}
1842
1843} // namespace ripple
1844
1845#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:345
Stream debug() const
Definition: Journal.h:327
Stream info() const
Definition: Journal.h:333
Stream trace() const
Severity stream access functions.
Definition: Journal.h:321
Stream warn() const
Definition: Journal.h:339
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:322
MonitoredMode(ConsensusMode m)
Definition: Consensus.h:312
ConsensusMode get() const
Definition: Consensus.h:316
Generic implementation of consensus algorithm.
Definition: Consensus.h:292
void playbackProposals()
If we radically changed our consensus context for some reason, we need to replay recent proposals so ...
Definition: Consensus.h:1134
void timerEntry(NetClock::time_point const &now, std::unique_ptr< std::stringstream > const &clog={})
Call periodically to drive consensus forward.
Definition: Consensus.h:842
ConsensusTimer openTime_
Definition: Consensus.h:573
void startRoundInternal(NetClock::time_point const &now, typename Ledger_t::ID const &prevLedgerID, Ledger_t const &prevLedger, ConsensusMode mode, std::unique_ptr< std::stringstream > const &clog)
Definition: Consensus.h:681
void phaseEstablish(std::unique_ptr< std::stringstream > const &clog)
Handle establish phase.
Definition: Consensus.h:1348
typename Adaptor::PeerPosition_t PeerPosition_t
Definition: Consensus.h:297
ConsensusPhase phase_
Definition: Consensus.h:561
NetClock::time_point prevCloseTime_
Definition: Consensus.h:586
clock_type const & clock_
Definition: Consensus.h:566
void leaveConsensus(std::unique_ptr< std::stringstream > const &clog)
Definition: Consensus.h:1730
void updateDisputes(NodeID_t const &node, TxSet_t const &other)
Definition: Consensus.h:1819
Ledger_t previousLedger_
Definition: Consensus.h:594
typename Adaptor::TxSet_t TxSet_t
Definition: Consensus.h:294
void updateOurPositions(std::unique_ptr< std::stringstream > const &clog)
Definition: Consensus.h:1469
bool haveConsensus(std::unique_ptr< std::stringstream > const &clog)
Definition: Consensus.h:1664
Ledger_t::ID prevLedgerID() const
Get the previous ledger ID.
Definition: Consensus.h:425
void handleWrongLedger(typename Ledger_t::ID const &lgrId, std::unique_ptr< std::stringstream > const &clog)
Definition: Consensus.h:1045
void checkLedger(std::unique_ptr< std::stringstream > const &clog)
Check if our previous ledger matches the network's.
Definition: Consensus.h:1101
hash_map< NodeID_t, std::deque< PeerPosition_t > > recentPeerPositions_
Definition: Consensus.h:610
void simulate(NetClock::time_point const &now, std::optional< std::chrono::milliseconds > consensusDelay)
Simulate the consensus process without any network traffic.
Definition: Consensus.h:923
Json::Value getJson(bool full) const
Get the Json state of the consensus process.
Definition: Consensus.h:947
typename TxSet_t::Tx Tx_t
Definition: Consensus.h:296
void startRound(NetClock::time_point const &now, typename Ledger_t::ID const &prevLedgerID, Ledger_t prevLedger, hash_set< NodeID_t > const &nowUntrusted, bool proposing, std::unique_ptr< std::stringstream > const &clog={})
Kick-off the next round of consensus.
Definition: Consensus.h:634
Consensus(Consensus &&) noexcept=default
NetClock::time_point now_
Definition: Consensus.h:585
std::size_t prevProposers_
Definition: Consensus.h:613
NetClock::time_point asCloseTime(NetClock::time_point raw) const
Definition: Consensus.h:1838
beast::Journal const j_
Definition: Consensus.h:619
void createDisputes(TxSet_t const &o, std::unique_ptr< std::stringstream > const &clog={})
Definition: Consensus.h:1749
void gotTxSet(NetClock::time_point const &now, TxSet_t const &txSet)
Process a transaction set acquired from the network.
Definition: Consensus.h:875
Adaptor & adaptor_
Definition: Consensus.h:559
typename Adaptor::Ledger_t Ledger_t
Definition: Consensus.h:293
ConsensusPhase phase() const
Definition: Consensus.h:431
hash_map< typename TxSet_t::ID, const TxSet_t > acquired_
Definition: Consensus.h:597
typename Adaptor::NodeID_t NodeID_t
Definition: Consensus.h:295
void closeLedger(std::unique_ptr< std::stringstream > const &clog)
Definition: Consensus.h:1413
NetClock::duration closeResolution_
Definition: Consensus.h:575
bool peerProposal(NetClock::time_point const &now, PeerPosition_t const &newProposal)
A peer has proposed a new position, adjust our tracking.
Definition: Consensus.h:726
bool peerProposalInternal(NetClock::time_point const &now, PeerPosition_t const &newProposal)
Handle a replayed or a new peer proposal.
Definition: Consensus.h:747
MonitoredMode mode_
Definition: Consensus.h:562
hash_map< NodeID_t, PeerPosition_t > currPeerPositions_
Definition: Consensus.h:606
bool shouldPause(std::unique_ptr< std::stringstream > const &clog) const
Evaluate whether pausing increases likelihood of validation.
Definition: Consensus.h:1226
void phaseOpen(std::unique_ptr< std::stringstream > const &clog)
Handle pre-close phase.
Definition: Consensus.h:1151
ConsensusCloseTimes rawCloseTimes_
Definition: Consensus.h:600
std::chrono::milliseconds prevRoundTime_
Definition: Consensus.h:578
std::optional< Result > result_
Definition: Consensus.h:599
hash_set< NodeID_t > deadNodes_
Definition: Consensus.h:616
Ledger_t::ID prevLedgerID_
Definition: Consensus.h:592
bool haveCloseTimeConsensus_
Definition: Consensus.h:564
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, std::unique_ptr< std::stringstream > const &clog)
Determine whether the network reached consensus and whether we joined.
Definition: Consensus.cpp:163
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.
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
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.
Definition: Consensus.cpp:26
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:1460
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 size(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)