rippled
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 <ripple/basics/Log.h>
24 #include <ripple/basics/chrono.h>
25 #include <ripple/beast/container/aged_unordered_map.h>
26 #include <ripple/beast/utility/Journal.h>
27 #include <ripple/consensus/ConsensusParms.h>
28 #include <ripple/consensus/ConsensusProposal.h>
29 #include <ripple/consensus/ConsensusTypes.h>
30 #include <ripple/consensus/DisputedTx.h>
31 #include <ripple/consensus/LedgerTiming.h>
32 #include <ripple/json/json_writer.h>
33 #include <ripple/shamap/SHAMap.h>
34 #include <boost/logic/tribool.hpp>
35 
36 #include <chrono>
37 #include <deque>
38 #include <iterator>
39 #include <optional>
40 #include <sstream>
41 
42 namespace ripple {
43 
63 bool
65  bool anyTransactions,
66  std::size_t prevProposers,
67  std::size_t proposersClosed,
68  std::size_t proposersValidated,
69  std::chrono::milliseconds prevRoundTime,
70  std::chrono::milliseconds timeSincePrevClose,
73  std::chrono::milliseconds idleInterval,
74  ConsensusParms const& parms,
75  beast::Journal j);
76 
93  std::size_t prevProposers,
94  std::size_t currentProposers,
95  std::size_t currentAgree,
96  std::size_t currentFinished,
97  std::chrono::milliseconds previousAgreeTime,
98  std::chrono::milliseconds currentAgreeTime,
99  ConsensusParms const& parms,
100  bool proposing,
101  beast::Journal j);
102 
313 template <class Adaptor>
315 {
316  using Ledger_t = typename Adaptor::Ledger_t;
317  using TxSet_t = typename Adaptor::TxSet_t;
318  using NodeID_t = typename Adaptor::NodeID_t;
319  using Tx_t = typename TxSet_t::Tx;
320  using PeerPosition_t = typename Adaptor::PeerPosition_t;
322  NodeID_t,
323  typename Ledger_t::ID,
324  typename TxSet_t::ID,
325  typename Ledger_t::Seq>;
326 
328 
329  // Helper class to ensure adaptor is notified whenever the ConsensusMode
330  // changes
332  {
334 
335  public:
337  {
338  }
340  get() const
341  {
342  return mode_;
343  }
344 
345  void
346  set(ConsensusMode mode, Adaptor& a)
347  {
348  a.onModeChange(mode_, mode);
349  mode_ = mode;
350  }
351  };
352 
353 public:
356 
357  Consensus(Consensus&&) noexcept = default;
358 
365  Consensus(clock_type& clock, Adaptor& adaptor, beast::Journal j);
366 
380  void
381  startRound(
382  NetClock::time_point const& now,
383  typename Ledger_t::ID const& prevLedgerID,
384  Ledger_t prevLedger,
385  hash_set<NodeID_t> const& nowUntrusted,
386  bool proposing);
387 
394  bool
395  peerProposal(
396  NetClock::time_point const& now,
397  PeerPosition_t const& newProposal);
398 
403  void
404  timerEntry(NetClock::time_point const& now);
405 
411  void
412  gotTxSet(NetClock::time_point const& now, TxSet_t const& txSet);
413 
430  void
431  simulate(
432  NetClock::time_point const& now,
433  std::optional<std::chrono::milliseconds> consensusDelay);
434 
442  typename Ledger_t::ID
443  prevLedgerID() const
444  {
445  return prevLedgerID_;
446  }
447 
449  phase() const
450  {
451  return phase_;
452  }
453 
462  getJson(bool full) const;
463 
464 private:
465  void
467  NetClock::time_point const& now,
468  typename Ledger_t::ID const& prevLedgerID,
469  Ledger_t const& prevLedger,
470  ConsensusMode mode);
471 
472  // Change our view of the previous ledger
473  void
474  handleWrongLedger(typename Ledger_t::ID const& lgrId);
475 
481  void
482  checkLedger();
483 
487  void
489 
492  bool
494  NetClock::time_point const& now,
495  PeerPosition_t const& newProposal);
496 
503  void
504  phaseOpen();
505 
514  void
515  phaseEstablish();
516 
539  bool
540  shouldPause() const;
541 
542  // Close the open ledger and establish initial position.
543  void
544  closeLedger();
545 
546  // Adjust our positions to try to agree with other validators.
554  void
555  updateOurPositions(bool const share);
556 
557  bool
558  haveConsensus();
559 
560  // Create disputes between our position and the provided one.
561  void
562  createDisputes(TxSet_t const& o);
563 
564  // Update our disputes given that this node has adopted a new position.
565  // Will call createDisputes as needed.
566  void
567  updateDisputes(NodeID_t const& node, TxSet_t const& other);
568 
569  // Revoke our outstanding proposal, if any, and cease proposing
570  // until this round ends.
571  void
572  leaveConsensus();
573 
574  // The rounded or effective close time estimate from a proposer
577 
578  Adaptor& adaptor_;
579 
582  bool firstRound_ = true;
584 
586 
587  // How long the consensus convergence has taken, expressed as
588  // a percentage of the time that we expected it to take.
590 
591  // How long has this round been open
593 
595 
596  // Time it took for the last consensus round to converge
598 
599  //-------------------------------------------------------------------------
600  // Network time measurements of consensus progress
601 
602  // The current network adjusted time. This is the network time the
603  // ledger would close if it closed now
606 
607  //-------------------------------------------------------------------------
608  // Non-peer (self) consensus data
609 
610  // Last validated ledger ID provided to consensus
611  typename Ledger_t::ID prevLedgerID_;
612  // Last validated ledger seen by consensus
614 
615  // Transaction Sets, indexed by hash of transaction tree.
617  typename TxSet_t::ID,
618  const TxSet_t,
622 
623  // Tx sets that can be purged only once there is a new consensus round.
625 
628 
629  //-------------------------------------------------------------------------
630  // Peer related consensus data
631 
632  // Peer proposed positions for the current round
634 
635  // Recently received peer positions, available when transitioning between
636  // ledgers or rounds. Collected by ledger sequence. This allows us to
637  // know which positions are likely relevant to the ledger on which we are
638  // currently working. Also allows us to catch up faster if we fall behind
639  // the rest of the network since we won't need to re-aquire proposals
640  // and related transaction sets.
643 
644  // These are for peers not using code that adds a ledger sequence
645  // to the proposal message. TODO This should be removed eventually when
646  // the network fully upgrades.
648 
649  // The number of proposers who participated in the last consensus round
651 
652  // nodes that have bowed out of this consensus process
654 
655  // Journal for debugging
657 };
658 
659 template <class Adaptor>
661  clock_type& clock,
662  Adaptor& adaptor,
663  beast::Journal journal)
664  : adaptor_(adaptor), clock_(clock), acquired_(clock), j_{journal}
665 {
666  JLOG(j_.debug()) << "Creating consensus object";
667 }
668 
669 template <class Adaptor>
670 void
672  NetClock::time_point const& now,
673  typename Ledger_t::ID const& prevLedgerID,
674  Ledger_t prevLedger,
675  hash_set<NodeID_t> const& nowUntrusted,
676  bool proposing)
677 {
678  if (firstRound_)
679  {
680  // take our initial view of closeTime_ from the seed ledger
681  prevRoundTime_ = adaptor_.parms().ledgerIDLE_INTERVAL;
682  prevCloseTime_ = prevLedger.closeTime();
683  firstRound_ = false;
684  }
685  else
686  {
687  prevCloseTime_ = rawCloseTimes_.self;
688  }
689 
690  // Clear positions that we know will not ever be necessary again.
691  auto it = recentPeerPositions_.begin();
692  while (it != recentPeerPositions_.end() && it->first <= prevLedger.seq())
693  it = recentPeerPositions_.erase(it);
694  // Get rid of untrusted positions for the current working ledger.
695  auto currentPositions =
696  recentPeerPositions_.find(prevLedger.seq() + typename Ledger_t::Seq{1});
697  if (currentPositions != recentPeerPositions_.end())
698  {
699  for (NodeID_t const& n : nowUntrusted)
700  currentPositions->second.erase(n);
701  }
702 
703  for (NodeID_t const& n : nowUntrusted)
704  recentPeerPositionsLegacy_.erase(n);
705 
706  ConsensusMode startMode =
707  proposing ? ConsensusMode::proposing : ConsensusMode::observing;
708 
709  // We were handed the wrong ledger
710  if (prevLedger.id() != prevLedgerID)
711  {
712  // try to acquire the correct one
713  if (auto newLedger = adaptor_.acquireLedger(prevLedgerID))
714  {
715  prevLedger = *newLedger;
716  }
717  else // Unable to acquire the correct ledger
718  {
719  startMode = ConsensusMode::wrongLedger;
720  JLOG(j_.info())
721  << "Entering consensus with: " << previousLedger_.id();
722  JLOG(j_.info()) << "Correct LCL is: " << prevLedgerID;
723  }
724  }
725 
726  startRoundInternal(now, prevLedgerID, prevLedger, startMode);
727 }
728 template <class Adaptor>
729 void
731  NetClock::time_point const& now,
732  typename Ledger_t::ID const& prevLedgerID,
733  Ledger_t const& prevLedger,
734  ConsensusMode mode)
735 {
736  phase_ = ConsensusPhase::open;
737  JLOG(j_.debug()) << "transitioned to ConsensusPhase::open";
738  mode_.set(mode, adaptor_);
739  now_ = now;
740  prevLedgerID_ = prevLedgerID;
741  previousLedger_ = prevLedger;
742  result_.reset();
743  convergePercent_ = 0;
744  haveCloseTimeConsensus_ = false;
745  openTime_.reset(clock_.now());
746 
747  // beast::aged_unordered_map::erase by key is broken and
748  // is not used anywhere in the existing codebase.
749  while (!acquiredPurge_.empty())
750  {
751  auto found = acquired_.find(acquiredPurge_.top());
752  if (found != acquired_.end())
753  acquired_.erase(found);
754  acquiredPurge_.pop();
755  }
756  for (auto it = currPeerPositions_.begin(); it != currPeerPositions_.end();)
757  {
758  if (auto found = acquired_.find(it->second.proposal().position());
759  found != acquired_.end())
760  {
761  acquired_.erase(found);
762  }
763  it = currPeerPositions_.erase(it);
764  }
765 
766  // Hold up to 30 minutes worth of acquired tx sets. This to help
767  // catch up quickly from extended de-sync periods.
768  beast::expire(acquired_, std::chrono::minutes(30));
769  rawCloseTimes_.peers.clear();
770  rawCloseTimes_.self = {};
771  deadNodes_.clear();
772 
773  closeResolution_ = getNextLedgerTimeResolution(
774  previousLedger_.closeTimeResolution(),
775  previousLedger_.closeAgree(),
776  previousLedger_.seq() + typename Ledger_t::Seq{1});
777 
778  playbackProposals();
779  if (currPeerPositions_.size() > (prevProposers_ / 2))
780  {
781  // We may be falling behind, don't wait for the timer
782  // consider closing the ledger immediately
783  timerEntry(now_);
784  }
785 }
786 
787 template <class Adaptor>
788 bool
790  NetClock::time_point const& now,
791  PeerPosition_t const& newPeerPos)
792 {
793  auto const& peerID = newPeerPos.proposal().nodeID();
794 
795  // Always need to store recent positions
796  if (newPeerPos.proposal().ledgerSeq().has_value())
797  {
798  // Ignore proposals from prior ledgers.
799  typename Ledger_t::Seq const& propLedgerSeq =
800  *newPeerPos.proposal().ledgerSeq();
801  if (propLedgerSeq <= previousLedger_.seq())
802  return false;
803 
804  auto& bySeq = recentPeerPositions_[propLedgerSeq];
805  {
806  auto peerProp = bySeq.find(peerID);
807  if (peerProp == bySeq.end())
808  {
809  bySeq.emplace(peerID, newPeerPos);
810  }
811  else
812  {
813  // Only store if it's the latest proposal from this peer for the
814  // consensus round in the proposal.
815  if (newPeerPos.proposal().proposeSeq() <=
816  peerProp->second.proposal().proposeSeq())
817  {
818  return false;
819  }
820  peerProp->second = newPeerPos;
821  }
822  }
823  }
824  else
825  {
826  // legacy proposal with no ledger sequence
827  auto& props = recentPeerPositionsLegacy_[peerID];
828 
829  if (props.size() >= 10)
830  props.pop_front();
831 
832  props.push_back(newPeerPos);
833  }
834 
835  return peerProposalInternal(now, newPeerPos);
836 }
837 
838 template <class Adaptor>
839 bool
841  NetClock::time_point const& now,
842  PeerPosition_t const& newPeerPos)
843 {
844  now_ = now;
845 
846  auto const& newPeerProp = newPeerPos.proposal();
847 
848  if (newPeerProp.prevLedger() != prevLedgerID_)
849  {
850  JLOG(j_.debug()) << "Got proposal for " << newPeerProp.prevLedger()
851  << " but we are on " << prevLedgerID_;
852 
853  if (!acquired_.count(newPeerProp.position()))
854  {
855  // acquireTxSet will return the set if it is available, or
856  // spawn a request for it and return nullopt/nullptr. It will call
857  // gotTxSet once it arrives. If we're behind, this should save
858  // time when we catch up.
859  if (auto set = adaptor_.acquireTxSet(newPeerProp.position()))
860  gotTxSet(now_, *set);
861  else
862  JLOG(j_.debug()) << "Do not have tx set for peer";
863  }
864 
865  // There's nothing else to do with this proposal currently.
866  return false;
867  }
868 
869  auto const& peerID = newPeerProp.nodeID();
870 
871  if (deadNodes_.find(peerID) != deadNodes_.end())
872  {
873  JLOG(j_.info()) << "Position from dead node: " << peerID;
874  return false;
875  }
876 
877  {
878  // update current position
879  auto peerPosIt = currPeerPositions_.find(peerID);
880 
881  if (peerPosIt != currPeerPositions_.end())
882  {
883  if (newPeerProp.proposeSeq() <=
884  peerPosIt->second.proposal().proposeSeq())
885  {
886  return false;
887  }
888  }
889 
890  if (newPeerProp.isBowOut())
891  {
892  JLOG(j_.info()) << "Peer " << peerID << " bows out";
893  if (result_)
894  {
895  for (auto& it : result_->disputes)
896  it.second.unVote(peerID);
897  }
898  if (peerPosIt != currPeerPositions_.end())
899  {
900  // Remove from acquired_ or else it will consume space for
901  // awhile. beast::aged_unordered_map::erase by key is broken and
902  // is not used anywhere in the existing codebase.
903  if (auto found =
904  acquired_.find(peerPosIt->second.proposal().position());
905  found != acquired_.end())
906  {
907  acquiredPurge_.push(
908  peerPosIt->second.proposal().position());
909  }
910  currPeerPositions_.erase(peerID);
911  }
912  deadNodes_.insert(peerID);
913 
914  return true;
915  }
916 
917  if (peerPosIt != currPeerPositions_.end())
918  {
919  // Remove from acquired_ or else it will consume space for awhile.
920  // beast::aged_unordered_container::erase by key is broken and
921  // is not used anywhere in the existing codebase.
922  if (auto found = acquired_.find(newPeerPos.proposal().position());
923  found != acquired_.end())
924  {
925  acquiredPurge_.push(newPeerPos.proposal().position());
926  }
927  // The proposal's arrival time determines how long the network
928  // has been proposing, so new proposals from the same peer
929  // should reflect the original's arrival time.
930  newPeerPos.proposal().arrivalTime() =
931  peerPosIt->second.proposal().arrivalTime();
932  peerPosIt->second = newPeerPos;
933  }
934  else
935  {
936  currPeerPositions_.emplace(peerID, newPeerPos);
937  }
938  }
939 
940  if (newPeerProp.isInitial())
941  {
942  // Record the close time estimate
943  JLOG(j_.trace()) << "Peer reports close time as "
944  << newPeerProp.closeTime().time_since_epoch().count();
945  ++rawCloseTimes_.peers[newPeerProp.closeTime()];
946  }
947 
948  JLOG(j_.trace()) << "Processing peer proposal " << newPeerProp.proposeSeq()
949  << "/" << newPeerProp.position();
950 
951  {
952  auto const ait = acquired_.find(newPeerProp.position());
953  if (ait == acquired_.end())
954  {
955  // acquireTxSet will return the set if it is available, or
956  // spawn a request for it and return nullopt/nullptr. It will call
957  // gotTxSet once it arrives
958  if (auto set = adaptor_.acquireTxSet(newPeerProp.position()))
959  gotTxSet(now_, *set);
960  else
961  JLOG(j_.debug()) << "Don't have tx set for peer";
962  }
963  else if (result_)
964  {
965  updateDisputes(newPeerProp.nodeID(), ait->second);
966  }
967  }
968 
969  return true;
970 }
971 
972 template <class Adaptor>
973 void
975 {
976  // Nothing to do if we are currently working on a ledger
977  if (phase_ == ConsensusPhase::accepted)
978  return;
979 
980  now_ = now;
981 
982  // Check we are on the proper ledger (this may change phase_)
983  checkLedger();
984 
985  if (phase_ == ConsensusPhase::open)
986  phaseOpen();
987  else if (phase_ == ConsensusPhase::establish)
988  phaseEstablish();
989 }
990 
991 template <class Adaptor>
992 void
994  NetClock::time_point const& now,
995  TxSet_t const& txSet)
996 {
997  now_ = now;
998 
999  auto id = txSet.id();
1000 
1001  // If we've already processed this transaction set since requesting
1002  // it from the network, there is nothing to do now
1003  if (!acquired_.emplace(id, txSet).second)
1004  return;
1005 
1006  if (!result_)
1007  {
1008  JLOG(j_.debug()) << "Not creating disputes: no position yet.";
1009  }
1010  else
1011  {
1012  // Our position is added to acquired_ as soon as we create it,
1013  // so this txSet must differ
1014  assert(id != result_->position.position());
1015  bool any = false;
1016  for (auto const& [nodeId, peerPos] : currPeerPositions_)
1017  {
1018  if (peerPos.proposal().position() == id)
1019  {
1020  updateDisputes(nodeId, txSet);
1021  any = true;
1022  }
1023  }
1024 
1025  if (!any)
1026  {
1027  JLOG(j_.warn())
1028  << "By the time we got " << id << " no peers were proposing it";
1029  }
1030  }
1031 }
1032 
1033 template <class Adaptor>
1034 void
1036  NetClock::time_point const& now,
1038 {
1039  using namespace std::chrono_literals;
1040  JLOG(j_.info()) << "Simulating consensus";
1041  now_ = now;
1042  closeLedger();
1043  result_->roundTime.tick(consensusDelay.value_or(100ms));
1044  result_->proposers = prevProposers_ = currPeerPositions_.size();
1045  prevRoundTime_ = result_->roundTime.read();
1046  phase_ = ConsensusPhase::accepted;
1047  adaptor_.onForceAccept(
1048  *result_,
1049  previousLedger_,
1050  closeResolution_,
1051  rawCloseTimes_,
1052  mode_.get(),
1053  getJson(true));
1054  JLOG(j_.info()) << "Simulation complete";
1055 }
1056 
1057 template <class Adaptor>
1060 {
1061  using std::to_string;
1062  using Int = Json::Value::Int;
1063 
1065 
1066  ret["proposing"] = (mode_.get() == ConsensusMode::proposing);
1067  ret["proposers"] = static_cast<int>(currPeerPositions_.size());
1068 
1069  if (mode_.get() != ConsensusMode::wrongLedger)
1070  {
1071  ret["synched"] = true;
1072  ret["ledger_seq"] =
1073  static_cast<std::uint32_t>(previousLedger_.seq()) + 1;
1074  ret["close_granularity"] = static_cast<Int>(closeResolution_.count());
1075  }
1076  else
1077  ret["synched"] = false;
1078 
1079  ret["phase"] = to_string(phase_);
1080 
1081  if (result_ && !result_->disputes.empty() && !full)
1082  ret["disputes"] = static_cast<Int>(result_->disputes.size());
1083 
1084  if (result_)
1085  ret["our_position"] = result_->position.getJson();
1086 
1087  if (full)
1088  {
1089  if (result_)
1090  ret["current_ms"] =
1091  static_cast<Int>(result_->roundTime.read().count());
1092  ret["converge_percent"] = convergePercent_;
1093  ret["close_resolution"] = static_cast<Int>(closeResolution_.count());
1094  ret["have_time_consensus"] = haveCloseTimeConsensus_;
1095  ret["previous_proposers"] = static_cast<Int>(prevProposers_);
1096  ret["previous_mseconds"] = static_cast<Int>(prevRoundTime_.count());
1097 
1098  if (!currPeerPositions_.empty())
1099  {
1101 
1102  for (auto const& [nodeId, peerPos] : currPeerPositions_)
1103  {
1104  ppj[to_string(nodeId)] = peerPos.getJson();
1105  }
1106  ret["peer_positions"] = std::move(ppj);
1107  }
1108 
1109  if (!acquired_.empty())
1110  {
1112  for (auto const& at : acquired_)
1113  {
1114  acq.append(to_string(at.first));
1115  }
1116  ret["acquired"] = std::move(acq);
1117  }
1118 
1119  if (result_ && !result_->disputes.empty())
1120  {
1122  for (auto const& [txId, dispute] : result_->disputes)
1123  {
1124  dsj[to_string(txId)] = dispute.getJson();
1125  }
1126  ret["disputes"] = std::move(dsj);
1127  }
1128 
1129  if (!rawCloseTimes_.peers.empty())
1130  {
1132  for (auto const& ct : rawCloseTimes_.peers)
1133  {
1134  ctj[std::to_string(ct.first.time_since_epoch().count())] =
1135  ct.second;
1136  }
1137  ret["close_times"] = std::move(ctj);
1138  }
1139 
1140  if (!deadNodes_.empty())
1141  {
1143  for (auto const& dn : deadNodes_)
1144  {
1145  dnj.append(to_string(dn));
1146  }
1147  ret["dead_nodes"] = std::move(dnj);
1148  }
1149  }
1150 
1151  return ret;
1152 }
1153 
1154 // Handle a change in the prior ledger during a consensus round
1155 template <class Adaptor>
1156 void
1157 Consensus<Adaptor>::handleWrongLedger(typename Ledger_t::ID const& lgrId)
1158 {
1159  assert(lgrId != prevLedgerID_ || previousLedger_.id() != lgrId);
1160 
1161  // Stop proposing because we are out of sync
1162  leaveConsensus();
1163 
1164  // First time switching to this ledger
1165  if (prevLedgerID_ != lgrId)
1166  {
1167  prevLedgerID_ = lgrId;
1168 
1169  // Clear out state
1170  if (result_)
1171  {
1172  result_->disputes.clear();
1173  result_->compares.clear();
1174  }
1175 
1176  for (auto it = currPeerPositions_.begin();
1177  it != currPeerPositions_.end();)
1178  {
1179  // beast::aged_unordered_map::erase by key is broken and
1180  // is not used anywhere in the existing codebase.
1181  if (auto found = acquired_.find(it->second.proposal().position());
1182  found != acquired_.end())
1183  {
1184  acquiredPurge_.push(it->second.proposal().position());
1185  }
1186  it = currPeerPositions_.erase(it);
1187  }
1188  rawCloseTimes_.peers.clear();
1189  deadNodes_.clear();
1190 
1191  // Get back in sync, this will also recreate disputes
1192  playbackProposals();
1193  }
1194 
1195  if (previousLedger_.id() == prevLedgerID_)
1196  return;
1197 
1198  // we need to switch the ledger we're working from
1199  if (auto newLedger = adaptor_.acquireLedger(prevLedgerID_))
1200  {
1201  JLOG(j_.info()) << "Have the consensus ledger " << prevLedgerID_;
1202  startRoundInternal(
1203  now_, lgrId, *newLedger, ConsensusMode::switchedLedger);
1204  }
1205  else
1206  {
1207  mode_.set(ConsensusMode::wrongLedger, adaptor_);
1208  }
1209 }
1210 
1211 template <class Adaptor>
1212 void
1214 {
1215  auto netLgr =
1216  adaptor_.getPrevLedger(prevLedgerID_, previousLedger_, mode_.get());
1217 
1218  if (netLgr != prevLedgerID_)
1219  {
1220  JLOG(j_.warn()) << "View of consensus changed during "
1221  << to_string(phase_) << " status=" << to_string(phase_)
1222  << ", "
1223  << " mode=" << to_string(mode_.get());
1224  JLOG(j_.warn()) << prevLedgerID_ << " to " << netLgr;
1225  JLOG(j_.warn()) << Json::Compact{previousLedger_.getJson()};
1226  JLOG(j_.debug()) << "State on consensus change "
1227  << Json::Compact{getJson(true)};
1228  handleWrongLedger(netLgr);
1229  }
1230  else if (previousLedger_.id() != prevLedgerID_)
1231  handleWrongLedger(netLgr);
1232 }
1233 
1234 template <class Adaptor>
1235 void
1237 {
1238  // Only use proposals for the ledger sequence we're currently working on.
1239  auto const currentPositions = recentPeerPositions_.find(
1240  previousLedger_.seq() + typename Ledger_t::Seq{1});
1241  if (currentPositions != recentPeerPositions_.end())
1242  {
1243  for (auto const& [peerID, pos] : currentPositions->second)
1244  {
1245  if (pos.proposal().prevLedger() == prevLedgerID_ &&
1246  peerProposalInternal(now_, pos))
1247  {
1248  adaptor_.share(pos);
1249  }
1250  }
1251  }
1252 
1253  // It's safe to do this--if a proposal is based on the wrong ledger,
1254  // then peerProposalInternal() will not replace it in currPeerPositions_.
1255  // TODO Eventually, remove code to check for non-existent ledger sequence
1256  // in peer proposal messages and make that parameter required in
1257  // the protobuf definition. Do this only after the network is running on
1258  // rippled versions with that parameter set in peer proposals. This
1259  // can be done once an amendment for another feature forces that kind
1260  // of upgrade, but this particular feature does not require an amendment.
1261  for (auto const& it : recentPeerPositionsLegacy_)
1262  {
1263  for (auto const& pos : it.second)
1264  {
1265  if (pos.proposal().prevLedger() == prevLedgerID_)
1266  {
1267  if (peerProposalInternal(now_, pos))
1268  adaptor_.share(pos);
1269  }
1270  }
1271  }
1272 }
1273 
1274 template <class Adaptor>
1275 void
1277 {
1278  using namespace std::chrono;
1279 
1280  // it is shortly before ledger close time
1281  bool anyTransactions = adaptor_.hasOpenTransactions();
1282  auto proposersClosed = currPeerPositions_.size();
1283  auto proposersValidated = adaptor_.proposersValidated(prevLedgerID_);
1284 
1285  openTime_.tick(clock_.now());
1286 
1287  // This computes how long since last ledger's close time
1288  milliseconds sinceClose;
1289  {
1290  bool previousCloseCorrect =
1291  (mode_.get() != ConsensusMode::wrongLedger) &&
1292  previousLedger_.closeAgree() &&
1293  (previousLedger_.closeTime() !=
1294  (previousLedger_.parentCloseTime() + 1s));
1295 
1296  auto lastCloseTime = previousCloseCorrect
1297  ? previousLedger_.closeTime() // use consensus timing
1298  : prevCloseTime_; // use the time we saw internally
1299 
1300  if (now_ >= lastCloseTime)
1301  sinceClose = duration_cast<milliseconds>(now_ - lastCloseTime);
1302  else
1303  sinceClose = -duration_cast<milliseconds>(lastCloseTime - now_);
1304  }
1305 
1306  auto const idleInterval = std::max<milliseconds>(
1307  adaptor_.parms().ledgerIDLE_INTERVAL,
1308  2 * previousLedger_.closeTimeResolution());
1309 
1310  // Decide if we should close the ledger
1311  if (shouldCloseLedger(
1312  anyTransactions,
1313  prevProposers_,
1314  proposersClosed,
1315  proposersValidated,
1316  prevRoundTime_,
1317  sinceClose,
1318  openTime_.read(),
1319  adaptor_.getValidationDelay(),
1320  idleInterval,
1321  adaptor_.parms(),
1322  j_))
1323  {
1324  closeLedger();
1325  adaptor_.setValidationDelay();
1326  }
1327 }
1328 
1329 template <class Adaptor>
1330 bool
1332 {
1333  auto const& parms = adaptor_.parms();
1334  std::uint32_t const ahead(
1335  previousLedger_.seq() -
1336  std::min(adaptor_.getValidLedgerIndex(), previousLedger_.seq()));
1337  auto [quorum, trustedKeys] = adaptor_.getQuorumKeys();
1338  std::size_t const totalValidators = trustedKeys.size();
1339  std::size_t laggards =
1340  adaptor_.laggards(previousLedger_.seq(), trustedKeys);
1341  std::size_t const offline = trustedKeys.size();
1342 
1343  std::stringstream vars;
1344  vars << " (working seq: " << previousLedger_.seq() << ", "
1345  << "validated seq: " << adaptor_.getValidLedgerIndex() << ", "
1346  << "am validator: " << adaptor_.validator() << ", "
1347  << "have validated: " << adaptor_.haveValidated() << ", "
1348  << "roundTime: " << result_->roundTime.read().count() << ", "
1349  << "max consensus time: " << parms.ledgerMAX_CONSENSUS.count() << ", "
1350  << "validators: " << totalValidators << ", "
1351  << "laggards: " << laggards << ", "
1352  << "offline: " << offline << ", "
1353  << "quorum: " << quorum << ")";
1354 
1355  if (!ahead || !laggards || !totalValidators || !adaptor_.validator() ||
1356  !adaptor_.haveValidated() ||
1357  result_->roundTime.read() > parms.ledgerMAX_CONSENSUS)
1358  {
1359  j_.debug() << "not pausing (early)" << vars.str();
1360  return false;
1361  }
1362 
1363  bool willPause = false;
1364 
1378  constexpr static std::size_t maxPausePhase = 4;
1379 
1399  std::size_t const phase = (ahead - 1) % (maxPausePhase + 1);
1400 
1401  // validators that remain after the laggards() function are considered
1402  // offline, and should be considered as laggards for purposes of
1403  // evaluating whether the threshold for non-laggards has been reached.
1404  switch (phase)
1405  {
1406  case 0:
1407  // Laggards and offline shouldn't preclude consensus.
1408  if (laggards + offline > totalValidators - quorum)
1409  willPause = true;
1410  break;
1411  case maxPausePhase:
1412  // No tolerance.
1413  willPause = true;
1414  break;
1415  default:
1416  // Ensure that sufficient validators are known to be not lagging.
1417  // Their sufficiently most recent validation sequence was equal to
1418  // or greater than our own.
1419  //
1420  // The threshold is the amount required for quorum plus
1421  // the proportion of the remainder based on number of intermediate
1422  // phases between 0 and max.
1423  float const nonLaggards = totalValidators - (laggards + offline);
1424  float const quorumRatio =
1425  static_cast<float>(quorum) / totalValidators;
1426  float const allowedDissent = 1.0f - quorumRatio;
1427  float const phaseFactor = static_cast<float>(phase) / maxPausePhase;
1428 
1429  if (nonLaggards / totalValidators <
1430  quorumRatio + (allowedDissent * phaseFactor))
1431  {
1432  willPause = true;
1433  }
1434  }
1435 
1436  if (willPause)
1437  j_.warn() << "pausing" << vars.str();
1438  else
1439  j_.debug() << "not pausing" << vars.str();
1440  return willPause;
1441 }
1442 
1443 template <class Adaptor>
1444 void
1446 {
1447  // can only establish consensus if we already took a stance
1448  assert(result_);
1449 
1450  using namespace std::chrono;
1451  ConsensusParms const& parms = adaptor_.parms();
1452 
1453  result_->roundTime.tick(clock_.now());
1454  result_->proposers = currPeerPositions_.size();
1455 
1456  convergePercent_ = result_->roundTime.read() * 100 /
1457  std::max<milliseconds>(prevRoundTime_, parms.avMIN_CONSENSUS_TIME);
1458 
1459  {
1460  // Give everyone a chance to take an initial position unless enough
1461  // have already submitted theirs a long enough time ago
1462  // --because that means we're already
1463  // behind. Optimize pause duration if pausing. Pause until exactly
1464  // the number of ms after roundTime.read(), or the time since
1465  // receiving the earliest qualifying peer proposal. To protect
1466  // from faulty peers on the UNL, discard the earliest proposals
1467  // beyond the quorum threshold. For example, with a UNL of 20,
1468  // 80% quorum is 16. Assume the remaining 4 are Byzantine actors.
1469  // We therefore ignore the first 4 proposals received
1470  // for this calculation. We then take the earliest of either the
1471  // 5th proposal or our own proposal to determine whether enough
1472  // time has passed to possibly close. If not, then use that to
1473  // precisely determine how long to pause until checking again.
1474  std::size_t const q = adaptor_.quorum();
1475  std::size_t const discard =
1476  static_cast<std::size_t>(q / parms.minCONSENSUS_FACTOR) - q;
1477 
1478  std::chrono::milliseconds beginning;
1479  if (currPeerPositions_.size() > discard)
1480  {
1482  for (auto& pos : currPeerPositions_)
1483  {
1484  pos.second.proposal().arrivalTime().tick(clock_.now());
1485  arrivals.insert(pos.second.proposal().arrivalTime().read());
1486  }
1487  auto it = arrivals.rbegin();
1488  std::advance(it, discard);
1489  beginning = *it;
1490  }
1491  else
1492  {
1493  beginning = result_->roundTime.read();
1494  }
1495 
1496  // Give everyone a chance to take an initial position
1497  if (beginning < parms.ledgerMIN_CONSENSUS)
1498  {
1499  adaptor_.setTimerDelay(parms.ledgerMIN_CONSENSUS - beginning);
1500  return;
1501  }
1502  }
1503 
1504  updateOurPositions(true);
1505 
1506  // Nothing to do if too many laggards or we don't have consensus.
1507  if (shouldPause() || !haveConsensus())
1508  return;
1509 
1510  if (!haveCloseTimeConsensus_)
1511  {
1512  JLOG(j_.info()) << "We have TX consensus but not CT consensus";
1513  return;
1514  }
1515 
1516  JLOG(j_.info()) << "Converge cutoff (" << currPeerPositions_.size()
1517  << " participants)";
1518  adaptor_.updateOperatingMode(currPeerPositions_.size());
1519  prevProposers_ = currPeerPositions_.size();
1520  prevRoundTime_ = result_->roundTime.read();
1521  phase_ = ConsensusPhase::accepted;
1522  JLOG(j_.debug()) << "transitioned to ConsensusPhase::accepted";
1523 
1525  typename Adaptor::CanonicalTxSet_t,
1526  typename Adaptor::Ledger_t>>
1527  txsBuilt;
1528  // Track time spent retrying new ledger validation.
1530  startDelay;
1531  // Amount of time to pause checking for ledger to become validated.
1532  static auto const validationWait = std::chrono::milliseconds(100);
1533 
1534  // Make a copy of the result_ because it may be reset during the accept
1535  // phase if ledgers are switched and a new round is started.
1536  assert(result_.has_value());
1537  std::optional<Result const> result{result_};
1538  // Building the new ledger is time-consuming and safe to not lock, but
1539  // the rest of the logic below needs to be locked, until
1540  // finishing (onAccept).
1541  std::unique_lock<std::recursive_mutex> lock(adaptor_.peekMutex());
1542  do
1543  {
1544  if (!result_.has_value() ||
1545  result_->position.prevLedger() != result->position.prevLedger())
1546  {
1547  JLOG(j_.debug()) << "A new consensus round has started based on "
1548  "a different ledger.";
1549  return;
1550  }
1551  if (txsBuilt)
1552  {
1553  if (!startDelay)
1554  startDelay = std::chrono::steady_clock::now();
1555 
1556  // Only send a single validation per round.
1557  adaptor_.clearValidating();
1558  // Check if a better proposal has been shared by the network.
1559  auto prevProposal = result_->position;
1560  updateOurPositions(false);
1561 
1562  if (prevProposal == result_->position)
1563  {
1564  JLOG(j_.debug())
1565  << "old and new positions "
1566  "match: "
1567  << prevProposal.position() << " delay so far "
1568  << std::chrono::duration_cast<std::chrono::milliseconds>(
1569  std::chrono::steady_clock::now() - *startDelay)
1570  .count()
1571  << "ms. pausing";
1572  adaptor_.getLedgerMaster().waitForValidated(validationWait);
1573  continue;
1574  }
1575  JLOG(j_.debug()) << "retrying buildAndValidate with "
1576  "new position: "
1577  << result_->position.position();
1578  // Update the result used for the remainder of this Consensus round.
1579  assert(result_.has_value());
1580  result.emplace(*result_);
1581  }
1582  lock.unlock();
1583 
1584  // This is time-consuming and safe to not have under mutex.
1585  assert(result.has_value());
1586  txsBuilt = adaptor_.buildAndValidate(
1587  *result,
1588  previousLedger_,
1589  closeResolution_,
1590  mode_.get(),
1591  getJson(true));
1592  lock.lock();
1593  } while (adaptor_.retryAccept(txsBuilt->second, startDelay));
1594 
1595  if (startDelay)
1596  {
1597  auto const delay =
1598  std::chrono::duration_cast<std::chrono::milliseconds>(
1599  std::chrono::steady_clock::now() - *startDelay);
1600  JLOG(j_.debug()) << "validationDelay will be " << delay.count() << "ms";
1601  adaptor_.setValidationDelay(delay);
1602  }
1603 
1604  lock.unlock();
1605 
1606  assert(result.has_value());
1607  adaptor_.onAccept(
1608  *result,
1609  rawCloseTimes_,
1610  mode_.get(),
1611  getJson(true),
1612  std::move(*txsBuilt));
1613 }
1614 
1615 template <class Adaptor>
1616 void
1618 {
1619  // We should not be closing if we already have a position
1620  assert(!result_);
1621 
1622  phase_ = ConsensusPhase::establish;
1623  JLOG(j_.debug()) << "transitioned to ConsensusPhase::establish";
1624  rawCloseTimes_.self = now_;
1625 
1626  result_.emplace(
1627  adaptor_.onClose(previousLedger_, now_, mode_.get(), clock_));
1628  result_->roundTime.reset(clock_.now());
1629  // Share the newly created transaction set if we haven't already
1630  // received it from a peer
1631  if (acquired_.emplace(result_->txns.id(), result_->txns).second)
1632  adaptor_.share(result_->txns);
1633 
1634  if (mode_.get() == ConsensusMode::proposing)
1635  adaptor_.propose(result_->position);
1636 
1637  // Create disputes with any peer positions we have transactions for
1638  for (auto const& pit : currPeerPositions_)
1639  {
1640  auto const& pos = pit.second.proposal().position();
1641  auto const it = acquired_.find(pos);
1642  if (it != acquired_.end())
1643  createDisputes(it->second);
1644  }
1645  // There's no reason to pause, especially if we have fallen behind and
1646  // can possible agree to a consensus proposal already.
1647  timerEntry(now_);
1648 }
1649 
1662 inline int
1663 participantsNeeded(int participants, int percent)
1664 {
1665  int result = ((participants * percent) + (percent / 2)) / 100;
1666 
1667  return (result == 0) ? 1 : result;
1668 }
1669 
1670 template <class Adaptor>
1671 void
1673 {
1674  // We must have a position if we are updating it
1675  assert(result_);
1676  ConsensusParms const& parms = adaptor_.parms();
1677 
1678  // Compute a cutoff time
1679  auto const peerCutoff = now_ - parms.proposeFRESHNESS;
1680  auto const ourCutoff = now_ - parms.proposeINTERVAL;
1681 
1682  // Verify freshness of peer positions and compute close times
1683  std::map<NetClock::time_point, int> closeTimeVotes;
1684  {
1685  auto it = currPeerPositions_.begin();
1686  while (it != currPeerPositions_.end())
1687  {
1688  Proposal_t const& peerProp = it->second.proposal();
1689  if (peerProp.isStale(peerCutoff))
1690  {
1691  // peer's proposal is stale, so remove it
1692  NodeID_t const& peerID = peerProp.nodeID();
1693  JLOG(j_.warn()) << "Removing stale proposal from " << peerID;
1694  for (auto& dt : result_->disputes)
1695  dt.second.unVote(peerID);
1696  // Remove from acquired_ or else it will consume space for
1697  // awhile. beast::aged_unordered_map::erase by key is broken and
1698  // is not used anywhere in the existing codebase.
1699  if (auto found = acquired_.find(peerProp.position());
1700  found != acquired_.end())
1701  {
1702  acquiredPurge_.push(peerProp.position());
1703  }
1704  it = currPeerPositions_.erase(it);
1705  }
1706  else
1707  {
1708  // proposal is still fresh
1709  ++closeTimeVotes[asCloseTime(peerProp.closeTime())];
1710  ++it;
1711  }
1712  }
1713  }
1714 
1715  // This will stay unseated unless there are any changes
1716  std::optional<TxSet_t> ourNewSet;
1717 
1718  // Update votes on disputed transactions
1719  {
1721  for (auto& [txId, dispute] : result_->disputes)
1722  {
1723  // Because the threshold for inclusion increases,
1724  // time can change our position on a dispute
1725  if (dispute.updateVote(
1726  convergePercent_,
1727  mode_.get() == ConsensusMode::proposing,
1728  parms))
1729  {
1730  if (!mutableSet)
1731  mutableSet.emplace(result_->txns);
1732 
1733  if (dispute.getOurVote())
1734  {
1735  // now a yes
1736  mutableSet->insert(dispute.tx());
1737  }
1738  else
1739  {
1740  // now a no
1741  mutableSet->erase(txId);
1742  }
1743  }
1744  }
1745 
1746  if (mutableSet)
1747  ourNewSet.emplace(std::move(*mutableSet));
1748  }
1749 
1750  NetClock::time_point consensusCloseTime = {};
1751  haveCloseTimeConsensus_ = false;
1752 
1753  if (currPeerPositions_.empty())
1754  {
1755  // no other times
1756  haveCloseTimeConsensus_ = true;
1757  consensusCloseTime = asCloseTime(result_->position.closeTime());
1758  }
1759  else
1760  {
1761  int neededWeight;
1762 
1763  if (convergePercent_ < parms.avMID_CONSENSUS_TIME)
1764  neededWeight = parms.avINIT_CONSENSUS_PCT;
1765  else if (convergePercent_ < parms.avLATE_CONSENSUS_TIME)
1766  neededWeight = parms.avMID_CONSENSUS_PCT;
1767  else if (convergePercent_ < parms.avSTUCK_CONSENSUS_TIME)
1768  neededWeight = parms.avLATE_CONSENSUS_PCT;
1769  else
1770  neededWeight = parms.avSTUCK_CONSENSUS_PCT;
1771 
1772  int participants = currPeerPositions_.size();
1773  if (mode_.get() == ConsensusMode::proposing)
1774  {
1775  ++closeTimeVotes[asCloseTime(result_->position.closeTime())];
1776  ++participants;
1777  }
1778 
1779  // Threshold for non-zero vote
1780  int threshVote = participantsNeeded(participants, neededWeight);
1781 
1782  // Threshold to declare consensus
1783  int const threshConsensus =
1784  participantsNeeded(participants, parms.avCT_CONSENSUS_PCT);
1785 
1786  JLOG(j_.info()) << "Proposers:" << currPeerPositions_.size()
1787  << " nw:" << neededWeight << " thrV:" << threshVote
1788  << " thrC:" << threshConsensus;
1789 
1790  // An impasse is possible unless a validator pretends to change
1791  // its close time vote. Imagine 5 validators. 3 have positions
1792  // for close time t1, and 2 with t2. That's an impasse because
1793  // 75% will never be met. However, if one of the validators voting
1794  // for t2 switches to t1, then that will be 80% and sufficient
1795  // to break the impasse. It's also OK for those agreeing
1796  // with the 3 to pretend to vote for the one with 2, because
1797  // that will never exceed the threshold of 75%, even with as
1798  // few as 3 validators. The most it can achieve is 2/3.
1799  for (auto& [t, v] : closeTimeVotes)
1800  {
1801  if (adaptor_.validating() &&
1802  t != asCloseTime(result_->position.closeTime()))
1803  {
1804  JLOG(j_.debug()) << "Others have voted for a close time "
1805  "different than ours. Adding our vote "
1806  "to this one in case it is necessary "
1807  "to break an impasse.";
1808  ++v;
1809  }
1810  JLOG(j_.debug())
1811  << "CCTime: seq "
1812  << static_cast<std::uint32_t>(previousLedger_.seq()) + 1 << ": "
1813  << t.time_since_epoch().count() << " has " << v << ", "
1814  << threshVote << " required";
1815 
1816  if (v >= threshVote)
1817  {
1818  // A close time has enough votes for us to try to agree
1819  consensusCloseTime = t;
1820  threshVote = v;
1821 
1822  if (threshVote >= threshConsensus)
1823  {
1824  haveCloseTimeConsensus_ = true;
1825  // Make sure that the winning close time is the one
1826  // that propagates to the rest of the function.
1827  break;
1828  }
1829  }
1830  }
1831 
1832  if (!haveCloseTimeConsensus_)
1833  {
1834  JLOG(j_.debug())
1835  << "No CT consensus:"
1836  << " Proposers:" << currPeerPositions_.size()
1837  << " Mode:" << to_string(mode_.get())
1838  << " Thresh:" << threshConsensus
1839  << " Pos:" << consensusCloseTime.time_since_epoch().count();
1840  }
1841  }
1842 
1843  if (!ourNewSet &&
1844  ((consensusCloseTime != asCloseTime(result_->position.closeTime())) ||
1845  result_->position.isStale(ourCutoff)))
1846  {
1847  // close time changed or our position is stale
1848  ourNewSet.emplace(result_->txns);
1849  }
1850 
1851  if (ourNewSet)
1852  {
1853  auto newID = ourNewSet->id();
1854 
1855  result_->txns = std::move(*ourNewSet);
1856 
1857  JLOG(j_.info()) << "Position change: CTime "
1858  << consensusCloseTime.time_since_epoch().count()
1859  << ", tx " << newID;
1860 
1861  result_->position.changePosition(newID, consensusCloseTime, now_);
1862 
1863  // Share our new transaction set and update disputes
1864  // if we haven't already received it. Unless we have already
1865  // accepted a position, but are recalculating because it didn't
1866  // validate.
1867  if (acquired_.emplace(newID, result_->txns).second && share)
1868  {
1869  if (!result_->position.isBowOut())
1870  adaptor_.share(result_->txns);
1871 
1872  for (auto const& [nodeId, peerPos] : currPeerPositions_)
1873  {
1874  Proposal_t const& p = peerPos.proposal();
1875  if (p.position() == newID)
1876  updateDisputes(nodeId, result_->txns);
1877  }
1878  }
1879 
1880  // Share our new position if we are still participating this round,
1881  // unless we have already accepted a position but are recalculating
1882  // because it didn't validate.
1883  if (!result_->position.isBowOut() &&
1884  (mode_.get() == ConsensusMode::proposing) && share)
1885  adaptor_.propose(result_->position);
1886  }
1887 }
1888 
1889 template <class Adaptor>
1890 bool
1892 {
1893  // Must have a stance if we are checking for consensus
1894  assert(result_);
1895 
1896  // CHECKME: should possibly count unacquired TX sets as disagreeing
1897  int agree = 0, disagree = 0;
1898 
1899  auto ourPosition = result_->position.position();
1900 
1901  // Count number of agreements/disagreements with our position
1902  for (auto const& [nodeId, peerPos] : currPeerPositions_)
1903  {
1904  Proposal_t const& peerProp = peerPos.proposal();
1905  if (peerProp.position() == ourPosition)
1906  ++agree;
1907  else
1908  ++disagree;
1909  }
1910  auto currentFinished =
1911  adaptor_.proposersFinished(previousLedger_, prevLedgerID_);
1912 
1913  JLOG(j_.debug()) << "Checking for TX consensus: agree=" << agree
1914  << ", disagree=" << disagree;
1915 
1916  // Determine if we actually have consensus or not
1917  result_->state = checkConsensus(
1918  prevProposers_,
1919  agree + disagree,
1920  agree,
1921  currentFinished,
1922  prevRoundTime_,
1923  result_->roundTime.read(),
1924  adaptor_.parms(),
1925  mode_.get() == ConsensusMode::proposing,
1926  j_);
1927 
1928  if (result_->state == ConsensusState::No)
1929  return false;
1930 
1931  // There is consensus, but we need to track if the network moved on
1932  // without us.
1933  if (result_->state == ConsensusState::MovedOn)
1934  {
1935  JLOG(j_.error()) << "Unable to reach consensus MovedOn: "
1936  << Json::Compact{getJson(true)};
1937  }
1938 
1939  return true;
1940 }
1941 
1942 template <class Adaptor>
1943 void
1945 {
1946  if (mode_.get() == ConsensusMode::proposing)
1947  {
1948  if (result_ && !result_->position.isBowOut())
1949  {
1950  result_->position.bowOut(now_);
1951  adaptor_.propose(result_->position);
1952  }
1953 
1954  mode_.set(ConsensusMode::observing, adaptor_);
1955  JLOG(j_.info()) << "Bowing out of consensus";
1956  }
1957 }
1958 
1959 template <class Adaptor>
1960 void
1962 {
1963  // Cannot create disputes without our stance
1964  assert(result_);
1965 
1966  // Only create disputes if this is a new set
1967  if (!result_->compares.emplace(o.id()).second)
1968  return;
1969 
1970  // Nothing to dispute if we agree
1971  if (result_->txns.id() == o.id())
1972  return;
1973 
1974  JLOG(j_.debug()) << "createDisputes " << result_->txns.id() << " to "
1975  << o.id();
1976 
1977  auto differences = result_->txns.compare(o);
1978 
1979  int dc = 0;
1980 
1981  for (auto const& [txId, inThisSet] : differences)
1982  {
1983  ++dc;
1984  // create disputed transactions (from the ledger that has them)
1985  assert(
1986  (inThisSet && result_->txns.find(txId) && !o.find(txId)) ||
1987  (!inThisSet && !result_->txns.find(txId) && o.find(txId)));
1988 
1989  Tx_t tx = inThisSet ? result_->txns.find(txId) : o.find(txId);
1990  auto txID = tx.id();
1991 
1992  if (result_->disputes.find(txID) != result_->disputes.end())
1993  continue;
1994 
1995  JLOG(j_.trace()) << "Transaction " << txID << " is disputed";
1996 
1997  typename Result::Dispute_t dtx{
1998  tx,
1999  result_->txns.exists(txID),
2000  std::max(prevProposers_, currPeerPositions_.size()),
2001  j_};
2002 
2003  // Update all of the available peer's votes on the disputed transaction
2004  for (auto const& [nodeId, peerPos] : currPeerPositions_)
2005  {
2006  Proposal_t const& peerProp = peerPos.proposal();
2007  auto const cit = acquired_.find(peerProp.position());
2008  if (cit != acquired_.end())
2009  dtx.setVote(nodeId, cit->second.exists(txID));
2010  }
2011  adaptor_.share(dtx.tx());
2012 
2013  result_->disputes.emplace(txID, std::move(dtx));
2014  }
2015  JLOG(j_.trace()) << dc << " differences found";
2016 }
2017 
2018 template <class Adaptor>
2019 void
2021 {
2022  // Cannot updateDisputes without our stance
2023  assert(result_);
2024 
2025  // Ensure we have created disputes against this set if we haven't seen
2026  // it before
2027  if (result_->compares.find(other.id()) == result_->compares.end())
2028  createDisputes(other);
2029 
2030  for (auto& it : result_->disputes)
2031  {
2032  auto& d = it.second;
2033  d.setVote(node, other.exists(d.tx().id()));
2034  }
2035 }
2036 
2037 template <class Adaptor>
2040 {
2041  return roundCloseTime(raw, closeResolution_);
2042 }
2043 
2044 } // namespace ripple
2045 
2046 #endif
ripple::ConsensusParms::avCT_CONSENSUS_PCT
std::size_t avCT_CONSENSUS_PCT
Percentage of nodes required to reach agreement on ledger close time.
Definition: ConsensusParms.h:145
Json::Value::Int
Json::Int Int
Definition: json_value.h:154
ripple::Consensus::deadNodes_
hash_set< NodeID_t > deadNodes_
Definition: Consensus.h:653
ripple::checkConsensus
ConsensusState checkConsensus(std::size_t prevProposers, std::size_t currentProposers, std::size_t currentAgree, std::size_t currentFinished, std::chrono::milliseconds previousAgreeTime, std::chrono::milliseconds currentAgreeTime, ConsensusParms const &parms, bool proposing, beast::Journal j)
Determine whether the network reached consensus and whether we joined.
Definition: Consensus.cpp:115
sstream
ripple::Consensus::checkLedger
void checkLedger()
Check if our previous ledger matches the network's.
Definition: Consensus.h:1213
ripple::ConsensusState
ConsensusState
Whether we have or don't have a consensus.
Definition: ConsensusTypes.h:185
std::lock
T lock(T... args)
ripple::Consensus::playbackProposals
void playbackProposals()
If we radically changed our consensus context for some reason, we need to replay recent proposals so ...
Definition: Consensus.h:1236
ripple::Consensus::shouldPause
bool shouldPause() const
Evaluate whether pausing increases likelihood of validation.
Definition: Consensus.h:1331
ripple::Consensus::clock_
clock_type & clock_
Definition: Consensus.h:585
ripple::ConsensusMode::proposing
@ proposing
We are normal participant in consensus and propose our position.
ripple::Consensus::result_
std::optional< Result > result_
Definition: Consensus.h:626
ripple::ConsensusTimer
Measures the duration of phases of consensus.
Definition: ConsensusTypes.h:133
ripple::Consensus::asCloseTime
NetClock::time_point asCloseTime(NetClock::time_point raw) const
Definition: Consensus.h:2039
beast::Journal::trace
Stream trace() const
Severity stream access functions.
Definition: Journal.h:309
ripple::Consensus::createDisputes
void createDisputes(TxSet_t const &o)
Definition: Consensus.h:1961
ripple::Consensus::handleWrongLedger
void handleWrongLedger(typename Ledger_t::ID const &lgrId)
Definition: Consensus.h:1157
ripple::ConsensusParms::avMID_CONSENSUS_TIME
std::size_t avMID_CONSENSUS_TIME
Percentage of previous round duration before we advance.
Definition: ConsensusParms.h:127
ripple::Consensus::closeResolution_
NetClock::duration closeResolution_
Definition: Consensus.h:594
std::unordered_set
STL class.
ripple::Consensus< ripple::test::csf::Peer >::Tx_t
typename TxSet_t::Tx Tx_t
Definition: Consensus.h:319
Json::arrayValue
@ arrayValue
array value (ordered list)
Definition: json_value.h:42
std::pair
ripple::ConsensusParms::avLATE_CONSENSUS_PCT
std::size_t avLATE_CONSENSUS_PCT
Percentage of nodes that most vote yes after advancing.
Definition: ConsensusParms.h:136
ripple::Consensus::MonitoredMode::set
void set(ConsensusMode mode, Adaptor &a)
Definition: Consensus.h:346
ripple::Consensus::MonitoredMode::MonitoredMode
MonitoredMode(ConsensusMode m)
Definition: Consensus.h:336
Json::Compact
Decorator for streaming out compact json.
Definition: json_writer.h:316
ripple::Consensus::getJson
Json::Value getJson(bool full) const
Get the Json state of the consensus process.
Definition: Consensus.h:1059
ripple::ConsensusParms::avSTUCK_CONSENSUS_PCT
std::size_t avSTUCK_CONSENSUS_PCT
Percentage of nodes that must vote yes after we are stuck.
Definition: ConsensusParms.h:142
ripple::Consensus
Generic implementation of consensus algorithm.
Definition: Consensus.h:314
std::optional::value_or
T value_or(T... args)
std::stack< typename TxSet_t::ID >
std::chrono::milliseconds
ripple::ConsensusProposal::position
Position_t const & position() const
Get the proposed position.
Definition: ConsensusProposal.h:115
iterator
ripple::Consensus::acquired_
AcquiredType acquired_
Definition: Consensus.h:621
std::optional::emplace
T emplace(T... args)
std::stringstream
STL class.
beast::Journal::warn
Stream warn() const
Definition: Journal.h:327
ripple::Consensus::leaveConsensus
void leaveConsensus()
Definition: Consensus.h:1944
ripple::Consensus::recentPeerPositionsLegacy_
hash_map< NodeID_t, std::deque< PeerPosition_t > > recentPeerPositionsLegacy_
Definition: Consensus.h:647
ripple::Consensus::phase
ConsensusPhase phase() const
Definition: Consensus.h:449
ripple::Consensus::j_
const beast::Journal j_
Definition: Consensus.h:656
ripple::roundCloseTime
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:129
ripple::Consensus::currPeerPositions_
hash_map< NodeID_t, PeerPosition_t > currPeerPositions_
Definition: Consensus.h:633
ripple::ConsensusParms::proposeINTERVAL
std::chrono::seconds proposeINTERVAL
How often we force generating a new proposal to keep ours fresh.
Definition: ConsensusParms.h:67
ripple::Consensus::prevProposers_
std::size_t prevProposers_
Definition: Consensus.h:650
ripple::Consensus::now_
NetClock::time_point now_
Definition: Consensus.h:604
std::stringstream::read
T read(T... args)
ripple::ConsensusPhase::accepted
@ accepted
We have accepted a new last closed ledger and are waiting on a call to startRound to begin the next c...
ripple::Consensus::gotTxSet
void gotTxSet(NetClock::time_point const &now, TxSet_t const &txSet)
Process a transaction set acquired from the network.
Definition: Consensus.h:993
beast::abstract_clock< std::chrono::steady_clock >::clock_type
std::chrono::steady_clock clock_type
Definition: abstract_clock.h:64
ripple::ConsensusResult
Encapsulates the result of consensus.
Definition: ConsensusTypes.h:202
std::multiset
STL class.
ripple::Consensus::prevRoundTime_
std::chrono::milliseconds prevRoundTime_
Definition: Consensus.h:597
ripple::ConsensusMode::observing
@ observing
We are observing peer positions, but not proposing our position.
ripple::Consensus::updateOurPositions
void updateOurPositions(bool const share)
Adjust our positions to try to agree with other validators.
Definition: Consensus.h:1672
ripple::Consensus::adaptor_
Adaptor & adaptor_
Definition: Consensus.h:578
ripple::Consensus::phaseOpen
void phaseOpen()
Handle pre-close phase.
Definition: Consensus.h:1276
std::chrono::time_point::time_since_epoch
T time_since_epoch(T... args)
Json::Value::append
Value & append(const Value &value)
Append value to array at the end.
Definition: json_value.cpp:882
ripple::ConsensusProposal::nodeID
NodeID_t const & nodeID() const
Identifying which peer took this position.
Definition: ConsensusProposal.h:108
Json::objectValue
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:43
ripple::Consensus::timerEntry
void timerEntry(NetClock::time_point const &now)
Call periodically to drive consensus forward.
Definition: Consensus.h:974
ripple::Consensus::convergePercent_
int convergePercent_
Definition: Consensus.h:589
chrono
ripple::Consensus::phase_
ConsensusPhase phase_
Definition: Consensus.h:580
ripple::Consensus::startRoundInternal
void startRoundInternal(NetClock::time_point const &now, typename Ledger_t::ID const &prevLedgerID, Ledger_t const &prevLedger, ConsensusMode mode)
Definition: Consensus.h:730
ripple::Consensus::Consensus
Consensus(Consensus &&) noexcept=default
ripple::ConsensusParms::minCONSENSUS_FACTOR
float minCONSENSUS_FACTOR
Definition: ConsensusParms.h:76
std::unique_lock< std::recursive_mutex >
std::to_string
T to_string(T... args)
ripple::ConsensusParms::proposeFRESHNESS
std::chrono::seconds proposeFRESHNESS
How long we consider a proposal fresh.
Definition: ConsensusParms.h:64
ripple::Consensus::prevLedgerID
Ledger_t::ID prevLedgerID() const
Get the previous ledger ID.
Definition: Consensus.h:443
ripple::set
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:313
ripple::ConsensusProposal::closeTime
NetClock::time_point const & closeTime() const
The current position on the consensus close time.
Definition: ConsensusProposal.h:142
ripple::ConsensusPhase
ConsensusPhase
Phases of consensus for a single ledger round.
Definition: ConsensusTypes.h:102
beast::Journal::error
Stream error() const
Definition: Journal.h:333
beast::Journal::info
Stream info() const
Definition: Journal.h:321
std::chrono::time_point
ripple::Consensus::updateDisputes
void updateDisputes(NodeID_t const &node, TxSet_t const &other)
Definition: Consensus.h:2020
ripple::Consensus< ripple::test::csf::Peer >::NodeID_t
typename ripple::test::csf::Peer ::NodeID_t NodeID_t
Definition: Consensus.h:318
ripple::Consensus::haveCloseTimeConsensus_
bool haveCloseTimeConsensus_
Definition: Consensus.h:583
deque
ripple::ConsensusParms::avINIT_CONSENSUS_PCT
std::size_t avINIT_CONSENSUS_PCT
Percentage of nodes on our UNL that must vote yes.
Definition: ConsensusParms.h:124
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:58
beast::expire
std::enable_if< is_aged_container< AgedContainer >::value, std::size_t >::type expire(AgedContainer &c, std::chrono::duration< Rep, Period > const &age)
Expire aged container items past the specified age.
Definition: aged_container_utility.h:33
std::uint32_t
ripple::Consensus::firstRound_
bool firstRound_
Definition: Consensus.h:582
std::map
STL class.
ripple::Consensus::mode_
MonitoredMode mode_
Definition: Consensus.h:581
ripple::Consensus< ripple::test::csf::Peer >::Ledger_t
typename ripple::test::csf::Peer ::Ledger_t Ledger_t
Definition: Consensus.h:316
ripple::ConsensusParms::avMID_CONSENSUS_PCT
std::size_t avMID_CONSENSUS_PCT
Percentage of nodes that most vote yes after advancing.
Definition: ConsensusParms.h:130
ripple::Consensus< ripple::test::csf::Peer >::PeerPosition_t
typename ripple::test::csf::Peer ::PeerPosition_t PeerPosition_t
Definition: Consensus.h:320
beast::abstract_clock< std::chrono::steady_clock >
ripple::Consensus::phaseEstablish
void phaseEstablish()
Handle establish phase.
Definition: Consensus.h:1445
std::advance
T advance(T... args)
std::min
T min(T... args)
ripple::getJson
Json::Value getJson(LedgerFill const &fill)
Return a new Json::Value representing the ledger with given options.
Definition: LedgerToJson.cpp:291
ripple::Consensus::MonitoredMode::get
ConsensusMode get() const
Definition: Consensus.h:340
ripple::Consensus::prevCloseTime_
NetClock::time_point prevCloseTime_
Definition: Consensus.h:605
beast::detail::aged_unordered_container
Associative container where each element is also indexed by time.
Definition: aged_unordered_container.h:85
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::ConsensusParms::ledgerMIN_CONSENSUS
std::chrono::milliseconds ledgerMIN_CONSENSUS
The number of seconds we wait minimum to ensure participation.
Definition: ConsensusParms.h:88
ripple::ConsensusMode
ConsensusMode
Represents how a node currently participates in Consensus.
Definition: ConsensusTypes.h:54
ripple::Consensus::acquiredPurge_
std::stack< typename TxSet_t::ID > acquiredPurge_
Definition: Consensus.h:624
ripple::Consensus::peerProposalInternal
bool peerProposalInternal(NetClock::time_point const &now, PeerPosition_t const &newProposal)
Handle a replayed or a new peer proposal.
Definition: Consensus.h:840
ripple::ConsensusParms
Consensus algorithm parameters.
Definition: ConsensusParms.h:33
std::map::begin
T begin(T... args)
ripple::Consensus::MonitoredMode
Definition: Consensus.h:331
std
STL namespace.
std::multiset::insert
T insert(T... args)
ripple::ConsensusParms::avSTUCK_CONSENSUS_TIME
std::size_t avSTUCK_CONSENSUS_TIME
Percentage of previous round duration before we are stuck.
Definition: ConsensusParms.h:139
ripple::Consensus::prevLedgerID_
Ledger_t::ID prevLedgerID_
Definition: Consensus.h:611
ripple::Consensus::openTime_
ConsensusTimer openTime_
Definition: Consensus.h:592
ripple::Consensus::peerProposal
bool peerProposal(NetClock::time_point const &now, PeerPosition_t const &newProposal)
A peer has proposed a new position, adjust our tracking.
Definition: Consensus.h:789
optional
std::stringstream::str
T str(T... args)
ripple::DisputedTx
A transaction discovered to be in dispute during consensus.
Definition: DisputedTx.h:50
beast::Journal::debug
Stream debug() const
Definition: Journal.h:315
std::size_t
ripple::ConsensusParms::avLATE_CONSENSUS_TIME
std::size_t avLATE_CONSENSUS_TIME
Percentage of previous round duration before we advance.
Definition: ConsensusParms.h:133
ripple::Consensus< ripple::test::csf::Peer >::TxSet_t
typename ripple::test::csf::Peer ::TxSet_t TxSet_t
Definition: Consensus.h:317
beast::uhash<>
ripple::Consensus::haveConsensus
bool haveConsensus()
Definition: Consensus.h:1891
std::max
T max(T... args)
ripple::getNextLedgerTimeResolution
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
ripple::Consensus::closeLedger
void closeLedger()
Definition: Consensus.h:1617
ripple::Consensus::recentPeerPositions_
std::map< typename Ledger_t::Seq, hash_map< NodeID_t, PeerPosition_t > > recentPeerPositions_
Definition: Consensus.h:642
ripple::ledgerDefaultTimeResolution
constexpr auto ledgerDefaultTimeResolution
Initial resolution of ledger close time.
Definition: LedgerTiming.h:44
ripple::ConsensusProposal::isStale
bool isStale(NetClock::time_point cutoff) const
Get whether this position is stale relative to the provided cutoff.
Definition: ConsensusProposal.h:172
ripple::NetClock
Clock for measuring the network time.
Definition: chrono.h:48
ripple::Consensus::simulate
void simulate(NetClock::time_point const &now, std::optional< std::chrono::milliseconds > consensusDelay)
Simulate the consensus process without any network traffic.
Definition: Consensus.h:1035
ripple::Consensus::MonitoredMode::mode_
ConsensusMode mode_
Definition: Consensus.h:333
ripple::ConsensusParms::avMIN_CONSENSUS_TIME
std::chrono::milliseconds avMIN_CONSENSUS_TIME
The minimum amount of time to consider the previous round to have taken.
Definition: ConsensusParms.h:115
std::unordered_map
STL class.
ripple::shouldCloseLedger
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::optional< std::chrono::milliseconds > validationDelay, std::chrono::milliseconds idleInterval, ConsensusParms const &parms, beast::Journal j)
Determines whether the current ledger should close at this time.
Definition: Consensus.cpp:26
ripple::participantsNeeded
int participantsNeeded(int participants, int percent)
How many of the participants must agree to reach a given threshold?
Definition: Consensus.h:1663
ripple::Consensus::startRound
void startRound(NetClock::time_point const &now, typename Ledger_t::ID const &prevLedgerID, Ledger_t prevLedger, hash_set< NodeID_t > const &nowUntrusted, bool proposing)
Kick-off the next round of consensus.
Definition: Consensus.h:671
std::multiset::rbegin
T rbegin(T... args)
ripple::ConsensusCloseTimes
Stores the set of initial close times.
Definition: ConsensusTypes.h:173
Json::Value
Represents a JSON value.
Definition: json_value.h:145
ripple::Consensus::rawCloseTimes_
ConsensusCloseTimes rawCloseTimes_
Definition: Consensus.h:627
ripple::Consensus::previousLedger_
Ledger_t previousLedger_
Definition: Consensus.h:613
ripple::ConsensusProposal< NodeID_t, typename Ledger_t::ID, typename TxSet_t::ID, typename Ledger_t::Seq >
beast
Definition: base_uint.h:641
std::chrono
std::chrono::steady_clock::now
T now(T... args)