20 #ifndef RIPPLE_CONSENSUS_CONSENSUS_H_INCLUDED
21 #define RIPPLE_CONSENSUS_CONSENSUS_H_INCLUDED
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>
74 ConsensusParms
const& parms,
99 ConsensusParms
const& parms,
313 template <
class Adaptor>
319 using Tx_t =
typename TxSet_t::Tx;
323 typename Ledger_t::ID,
324 typename TxSet_t::ID,
325 typename Ledger_t::Seq>;
348 a.onModeChange(
mode_, mode);
433 std::optional<
std::chrono::milliseconds> consensusDelay);
617 typename TxSet_t::ID,
659 template <
class Adaptor>
664 : adaptor_(adaptor), clock_(clock), acquired_(clock), j_{journal}
666 JLOG(j_.
debug()) <<
"Creating consensus object";
669 template <
class Adaptor>
673 typename Ledger_t::ID
const& prevLedgerID,
681 prevRoundTime_ = adaptor_.parms().ledgerIDLE_INTERVAL;
682 prevCloseTime_ = prevLedger.closeTime();
687 prevCloseTime_ = rawCloseTimes_.self;
691 auto it = recentPeerPositions_.begin();
692 while (it != recentPeerPositions_.end() && it->first <= prevLedger.seq())
693 it = recentPeerPositions_.erase(it);
695 auto currentPositions =
696 recentPeerPositions_.find(prevLedger.seq() +
typename Ledger_t::Seq{1});
697 if (currentPositions != recentPeerPositions_.end())
699 for (
NodeID_t const& n : nowUntrusted)
700 currentPositions->second.erase(n);
703 for (
NodeID_t const& n : nowUntrusted)
704 recentPeerPositionsLegacy_.erase(n);
707 proposing ? ConsensusMode::proposing : ConsensusMode::observing;
710 if (prevLedger.id() != prevLedgerID)
713 if (
auto newLedger = adaptor_.acquireLedger(prevLedgerID))
715 prevLedger = *newLedger;
719 startMode = ConsensusMode::wrongLedger;
721 <<
"Entering consensus with: " << previousLedger_.id();
722 JLOG(j_.
info()) <<
"Correct LCL is: " << prevLedgerID;
726 startRoundInternal(now, prevLedgerID, prevLedger, startMode);
728 template <
class Adaptor>
732 typename Ledger_t::ID
const& prevLedgerID,
736 phase_ = ConsensusPhase::open;
737 JLOG(j_.
debug()) <<
"transitioned to ConsensusPhase::open";
738 mode_.set(mode, adaptor_);
740 prevLedgerID_ = prevLedgerID;
741 previousLedger_ = prevLedger;
743 convergePercent_ = 0;
744 haveCloseTimeConsensus_ =
false;
745 openTime_.reset(clock_.now());
749 while (!acquiredPurge_.empty())
751 auto found = acquired_.find(acquiredPurge_.top());
752 if (found != acquired_.end())
753 acquired_.erase(found);
754 acquiredPurge_.pop();
756 for (
auto it = currPeerPositions_.begin(); it != currPeerPositions_.end();)
758 if (
auto found = acquired_.find(it->second.proposal().position());
759 found != acquired_.end())
761 acquired_.erase(found);
763 it = currPeerPositions_.erase(it);
769 rawCloseTimes_.peers.clear();
770 rawCloseTimes_.self = {};
774 previousLedger_.closeTimeResolution(),
775 previousLedger_.closeAgree(),
776 previousLedger_.seq() +
typename Ledger_t::Seq{1});
779 if (currPeerPositions_.size() > (prevProposers_ / 2))
787 template <
class Adaptor>
793 auto const& peerID = newPeerPos.proposal().nodeID();
796 if (newPeerPos.proposal().ledgerSeq().has_value())
799 typename Ledger_t::Seq
const& propLedgerSeq =
800 *newPeerPos.proposal().ledgerSeq();
801 if (propLedgerSeq <= previousLedger_.seq())
804 auto& bySeq = recentPeerPositions_[propLedgerSeq];
806 auto peerProp = bySeq.find(peerID);
807 if (peerProp == bySeq.end())
809 bySeq.emplace(peerID, newPeerPos);
815 if (newPeerPos.proposal().proposeSeq() <=
816 peerProp->second.proposal().proposeSeq())
820 peerProp->second = newPeerPos;
827 auto& props = recentPeerPositionsLegacy_[peerID];
829 if (props.size() >= 10)
832 props.push_back(newPeerPos);
835 return peerProposalInternal(now, newPeerPos);
838 template <
class Adaptor>
846 auto const& newPeerProp = newPeerPos.proposal();
848 if (newPeerProp.prevLedger() != prevLedgerID_)
850 JLOG(j_.
debug()) <<
"Got proposal for " << newPeerProp.prevLedger()
851 <<
" but we are on " << prevLedgerID_;
853 if (!acquired_.count(newPeerProp.position()))
859 if (
auto set = adaptor_.acquireTxSet(newPeerProp.position()))
860 gotTxSet(now_, *
set);
862 JLOG(j_.
debug()) <<
"Do not have tx set for peer";
869 auto const& peerID = newPeerProp.nodeID();
871 if (deadNodes_.find(peerID) != deadNodes_.end())
873 JLOG(j_.
info()) <<
"Position from dead node: " << peerID;
879 auto peerPosIt = currPeerPositions_.find(peerID);
881 if (peerPosIt != currPeerPositions_.end())
883 if (newPeerProp.proposeSeq() <=
884 peerPosIt->second.proposal().proposeSeq())
890 if (newPeerProp.isBowOut())
892 JLOG(j_.
info()) <<
"Peer " << peerID <<
" bows out";
895 for (
auto& it : result_->disputes)
896 it.second.unVote(peerID);
898 if (peerPosIt != currPeerPositions_.end())
904 acquired_.find(peerPosIt->second.proposal().position());
905 found != acquired_.end())
908 peerPosIt->second.proposal().position());
910 currPeerPositions_.erase(peerID);
912 deadNodes_.insert(peerID);
917 if (peerPosIt != currPeerPositions_.end())
922 if (
auto found = acquired_.find(newPeerPos.proposal().position());
923 found != acquired_.end())
925 acquiredPurge_.push(newPeerPos.proposal().position());
930 newPeerPos.proposal().arrivalTime() =
931 peerPosIt->second.proposal().arrivalTime();
932 peerPosIt->second = newPeerPos;
936 currPeerPositions_.emplace(peerID, newPeerPos);
940 if (newPeerProp.isInitial())
943 JLOG(j_.
trace()) <<
"Peer reports close time as "
944 << newPeerProp.closeTime().time_since_epoch().count();
945 ++rawCloseTimes_.peers[newPeerProp.closeTime()];
948 JLOG(j_.
trace()) <<
"Processing peer proposal " << newPeerProp.proposeSeq()
949 <<
"/" << newPeerProp.position();
952 auto const ait = acquired_.find(newPeerProp.position());
953 if (ait == acquired_.end())
958 if (
auto set = adaptor_.acquireTxSet(newPeerProp.position()))
959 gotTxSet(now_, *
set);
961 JLOG(j_.
debug()) <<
"Don't have tx set for peer";
965 updateDisputes(newPeerProp.nodeID(), ait->second);
972 template <
class Adaptor>
977 if (phase_ == ConsensusPhase::accepted)
985 if (phase_ == ConsensusPhase::open)
987 else if (phase_ == ConsensusPhase::establish)
991 template <
class Adaptor>
999 auto id = txSet.id();
1003 if (!acquired_.emplace(
id, txSet).second)
1008 JLOG(j_.
debug()) <<
"Not creating disputes: no position yet.";
1014 assert(
id != result_->position.position());
1016 for (
auto const& [nodeId, peerPos] : currPeerPositions_)
1018 if (peerPos.proposal().position() ==
id)
1020 updateDisputes(nodeId, txSet);
1028 <<
"By the time we got " <<
id <<
" no peers were proposing it";
1033 template <
class Adaptor>
1039 using namespace std::chrono_literals;
1040 JLOG(j_.
info()) <<
"Simulating consensus";
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(
1054 JLOG(j_.
info()) <<
"Simulation complete";
1057 template <
class Adaptor>
1066 ret[
"proposing"] = (mode_.get() == ConsensusMode::proposing);
1067 ret[
"proposers"] =
static_cast<int>(currPeerPositions_.size());
1069 if (mode_.get() != ConsensusMode::wrongLedger)
1071 ret[
"synched"] =
true;
1074 ret[
"close_granularity"] =
static_cast<Int
>(closeResolution_.count());
1077 ret[
"synched"] =
false;
1079 ret[
"phase"] = to_string(phase_);
1081 if (result_ && !result_->disputes.empty() && !full)
1082 ret[
"disputes"] =
static_cast<Int
>(result_->disputes.size());
1085 ret[
"our_position"] = result_->position.getJson();
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());
1098 if (!currPeerPositions_.empty())
1102 for (
auto const& [nodeId, peerPos] : currPeerPositions_)
1104 ppj[to_string(nodeId)] = peerPos.getJson();
1106 ret[
"peer_positions"] = std::move(ppj);
1109 if (!acquired_.empty())
1112 for (
auto const& at : acquired_)
1114 acq.
append(to_string(at.first));
1116 ret[
"acquired"] = std::move(acq);
1119 if (result_ && !result_->disputes.empty())
1122 for (
auto const& [txId, dispute] : result_->disputes)
1124 dsj[to_string(txId)] = dispute.getJson();
1126 ret[
"disputes"] = std::move(dsj);
1129 if (!rawCloseTimes_.peers.empty())
1132 for (
auto const& ct : rawCloseTimes_.peers)
1137 ret[
"close_times"] = std::move(ctj);
1140 if (!deadNodes_.empty())
1143 for (
auto const& dn : deadNodes_)
1145 dnj.
append(to_string(dn));
1147 ret[
"dead_nodes"] = std::move(dnj);
1155 template <
class Adaptor>
1159 assert(lgrId != prevLedgerID_ || previousLedger_.id() != lgrId);
1165 if (prevLedgerID_ != lgrId)
1167 prevLedgerID_ = lgrId;
1172 result_->disputes.clear();
1173 result_->compares.clear();
1176 for (
auto it = currPeerPositions_.begin();
1177 it != currPeerPositions_.end();)
1181 if (
auto found = acquired_.find(it->second.proposal().position());
1182 found != acquired_.end())
1184 acquiredPurge_.push(it->second.proposal().position());
1186 it = currPeerPositions_.erase(it);
1188 rawCloseTimes_.peers.clear();
1192 playbackProposals();
1195 if (previousLedger_.id() == prevLedgerID_)
1199 if (
auto newLedger = adaptor_.acquireLedger(prevLedgerID_))
1201 JLOG(j_.
info()) <<
"Have the consensus ledger " << prevLedgerID_;
1203 now_, lgrId, *newLedger, ConsensusMode::switchedLedger);
1207 mode_.set(ConsensusMode::wrongLedger, adaptor_);
1211 template <
class Adaptor>
1216 adaptor_.getPrevLedger(prevLedgerID_, previousLedger_, mode_.get());
1218 if (netLgr != prevLedgerID_)
1220 JLOG(j_.
warn()) <<
"View of consensus changed during "
1221 << to_string(phase_) <<
" status=" << to_string(phase_)
1223 <<
" mode=" << to_string(mode_.get());
1224 JLOG(j_.
warn()) << prevLedgerID_ <<
" to " << netLgr;
1226 JLOG(j_.
debug()) <<
"State on consensus change "
1228 handleWrongLedger(netLgr);
1230 else if (previousLedger_.id() != prevLedgerID_)
1231 handleWrongLedger(netLgr);
1234 template <
class Adaptor>
1239 auto const currentPositions = recentPeerPositions_.find(
1240 previousLedger_.seq() +
typename Ledger_t::Seq{1});
1241 if (currentPositions != recentPeerPositions_.end())
1243 for (
auto const& [peerID, pos] : currentPositions->second)
1245 if (pos.proposal().prevLedger() == prevLedgerID_ &&
1246 peerProposalInternal(now_, pos))
1248 adaptor_.share(pos);
1261 for (
auto const& it : recentPeerPositionsLegacy_)
1263 for (
auto const& pos : it.second)
1265 if (pos.proposal().prevLedger() == prevLedgerID_)
1267 if (peerProposalInternal(now_, pos))
1268 adaptor_.share(pos);
1274 template <
class Adaptor>
1281 bool anyTransactions = adaptor_.hasOpenTransactions();
1282 auto proposersClosed = currPeerPositions_.size();
1283 auto proposersValidated = adaptor_.proposersValidated(prevLedgerID_);
1285 openTime_.tick(clock_.now());
1290 bool previousCloseCorrect =
1291 (mode_.get() != ConsensusMode::wrongLedger) &&
1292 previousLedger_.closeAgree() &&
1293 (previousLedger_.closeTime() !=
1294 (previousLedger_.parentCloseTime() + 1s));
1296 auto lastCloseTime = previousCloseCorrect
1297 ? previousLedger_.closeTime()
1300 if (now_ >= lastCloseTime)
1301 sinceClose = duration_cast<milliseconds>(now_ - lastCloseTime);
1303 sinceClose = -duration_cast<milliseconds>(lastCloseTime - now_);
1306 auto const idleInterval = std::max<milliseconds>(
1307 adaptor_.parms().ledgerIDLE_INTERVAL,
1308 2 * previousLedger_.closeTimeResolution());
1319 adaptor_.getValidationDelay(),
1325 adaptor_.setValidationDelay();
1329 template <
class Adaptor>
1333 auto const& parms = adaptor_.parms();
1335 previousLedger_.seq() -
1336 std::min(adaptor_.getValidLedgerIndex(), previousLedger_.seq()));
1337 auto [quorum, trustedKeys] = adaptor_.getQuorumKeys();
1338 std::size_t const totalValidators = trustedKeys.size();
1340 adaptor_.laggards(previousLedger_.seq(), trustedKeys);
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 <<
")";
1355 if (!ahead || !laggards || !totalValidators || !adaptor_.validator() ||
1356 !adaptor_.haveValidated() ||
1357 result_->roundTime.read() > parms.ledgerMAX_CONSENSUS)
1359 j_.
debug() <<
"not pausing (early)" << vars.
str();
1363 bool willPause =
false;
1399 std::size_t const phase = (ahead - 1) % (maxPausePhase + 1);
1408 if (laggards + offline > totalValidators - quorum)
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;
1429 if (nonLaggards / totalValidators <
1430 quorumRatio + (allowedDissent * phaseFactor))
1437 j_.
warn() <<
"pausing" << vars.
str();
1439 j_.
debug() <<
"not pausing" << vars.
str();
1443 template <
class Adaptor>
1453 result_->roundTime.tick(clock_.now());
1454 result_->proposers = currPeerPositions_.size();
1456 convergePercent_ = result_->roundTime.read() * 100 /
1479 if (currPeerPositions_.size() > discard)
1482 for (
auto& pos : currPeerPositions_)
1484 pos.second.proposal().arrivalTime().tick(clock_.now());
1485 arrivals.
insert(pos.second.proposal().arrivalTime().read());
1487 auto it = arrivals.
rbegin();
1493 beginning = result_->roundTime.read();
1504 updateOurPositions(
true);
1507 if (shouldPause() || !haveConsensus())
1510 if (!haveCloseTimeConsensus_)
1512 JLOG(j_.
info()) <<
"We have TX consensus but not CT consensus";
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";
1525 typename Adaptor::CanonicalTxSet_t,
1526 typename Adaptor::Ledger_t>>
1536 assert(result_.has_value());
1544 if (!result_.has_value() ||
1545 result_->position.prevLedger() != result->position.prevLedger())
1547 JLOG(j_.
debug()) <<
"A new consensus round has started based on "
1548 "a different ledger.";
1557 adaptor_.clearValidating();
1559 auto prevProposal = result_->position;
1560 updateOurPositions(
false);
1562 if (prevProposal == result_->position)
1565 <<
"old and new positions "
1567 << prevProposal.position() <<
" delay so far "
1568 << std::chrono::duration_cast<std::chrono::milliseconds>(
1572 adaptor_.getLedgerMaster().waitForValidated(validationWait);
1575 JLOG(j_.
debug()) <<
"retrying buildAndValidate with "
1577 << result_->position.position();
1579 assert(result_.has_value());
1580 result.emplace(*result_);
1585 assert(result.has_value());
1586 txsBuilt = adaptor_.buildAndValidate(
1593 }
while (adaptor_.retryAccept(txsBuilt->second, startDelay));
1598 std::chrono::duration_cast<std::chrono::milliseconds>(
1600 JLOG(j_.
debug()) <<
"validationDelay will be " << delay.count() <<
"ms";
1601 adaptor_.setValidationDelay(delay);
1606 assert(result.has_value());
1612 std::move(*txsBuilt));
1615 template <
class Adaptor>
1622 phase_ = ConsensusPhase::establish;
1623 JLOG(j_.
debug()) <<
"transitioned to ConsensusPhase::establish";
1624 rawCloseTimes_.self = now_;
1627 adaptor_.onClose(previousLedger_, now_, mode_.get(), clock_));
1628 result_->roundTime.reset(clock_.now());
1631 if (acquired_.emplace(result_->txns.id(), result_->txns).second)
1632 adaptor_.share(result_->txns);
1634 if (mode_.get() == ConsensusMode::proposing)
1635 adaptor_.propose(result_->position);
1638 for (
auto const& pit : currPeerPositions_)
1640 auto const& pos = pit.second.proposal().position();
1641 auto const it = acquired_.find(pos);
1642 if (it != acquired_.end())
1643 createDisputes(it->second);
1665 int result = ((participants * percent) + (percent / 2)) / 100;
1667 return (result == 0) ? 1 : result;
1670 template <
class Adaptor>
1685 auto it = currPeerPositions_.
begin();
1686 while (it != currPeerPositions_.end())
1688 Proposal_t const& peerProp = it->second.proposal();
1689 if (peerProp.
isStale(peerCutoff))
1693 JLOG(j_.
warn()) <<
"Removing stale proposal from " << peerID;
1694 for (
auto& dt : result_->disputes)
1695 dt.second.unVote(peerID);
1699 if (
auto found = acquired_.find(peerProp.
position());
1700 found != acquired_.end())
1702 acquiredPurge_.push(peerProp.
position());
1704 it = currPeerPositions_.erase(it);
1709 ++closeTimeVotes[asCloseTime(peerProp.
closeTime())];
1721 for (
auto& [txId, dispute] : result_->disputes)
1725 if (dispute.updateVote(
1727 mode_.get() == ConsensusMode::proposing,
1731 mutableSet.
emplace(result_->txns);
1733 if (dispute.getOurVote())
1736 mutableSet->insert(dispute.tx());
1741 mutableSet->erase(txId);
1747 ourNewSet.
emplace(std::move(*mutableSet));
1751 haveCloseTimeConsensus_ =
false;
1753 if (currPeerPositions_.empty())
1756 haveCloseTimeConsensus_ =
true;
1757 consensusCloseTime = asCloseTime(result_->position.closeTime());
1772 int participants = currPeerPositions_.size();
1773 if (mode_.get() == ConsensusMode::proposing)
1775 ++closeTimeVotes[asCloseTime(result_->position.closeTime())];
1783 int const threshConsensus =
1786 JLOG(j_.
info()) <<
"Proposers:" << currPeerPositions_.size()
1787 <<
" nw:" << neededWeight <<
" thrV:" << threshVote
1788 <<
" thrC:" << threshConsensus;
1799 for (
auto& [t, v] : closeTimeVotes)
1801 if (adaptor_.validating() &&
1802 t != asCloseTime(result_->position.closeTime()))
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.";
1812 <<
static_cast<std::uint32_t>(previousLedger_.seq()) + 1 <<
": "
1813 << t.time_since_epoch().count() <<
" has " << v <<
", "
1814 << threshVote <<
" required";
1816 if (v >= threshVote)
1819 consensusCloseTime = t;
1822 if (threshVote >= threshConsensus)
1824 haveCloseTimeConsensus_ =
true;
1832 if (!haveCloseTimeConsensus_)
1835 <<
"No CT consensus:"
1836 <<
" Proposers:" << currPeerPositions_.size()
1837 <<
" Mode:" << to_string(mode_.get())
1838 <<
" Thresh:" << threshConsensus
1844 ((consensusCloseTime != asCloseTime(result_->position.closeTime())) ||
1845 result_->position.isStale(ourCutoff)))
1848 ourNewSet.
emplace(result_->txns);
1853 auto newID = ourNewSet->id();
1855 result_->txns = std::move(*ourNewSet);
1857 JLOG(j_.
info()) <<
"Position change: CTime "
1859 <<
", tx " << newID;
1861 result_->position.changePosition(newID, consensusCloseTime, now_);
1867 if (acquired_.emplace(newID, result_->txns).second && share)
1869 if (!result_->position.isBowOut())
1870 adaptor_.share(result_->txns);
1872 for (
auto const& [nodeId, peerPos] : currPeerPositions_)
1876 updateDisputes(nodeId, result_->txns);
1883 if (!result_->position.isBowOut() &&
1884 (mode_.get() == ConsensusMode::proposing) && share)
1885 adaptor_.propose(result_->position);
1889 template <
class Adaptor>
1897 int agree = 0, disagree = 0;
1899 auto ourPosition = result_->position.position();
1902 for (
auto const& [nodeId, peerPos] : currPeerPositions_)
1904 Proposal_t const& peerProp = peerPos.proposal();
1905 if (peerProp.
position() == ourPosition)
1910 auto currentFinished =
1911 adaptor_.proposersFinished(previousLedger_, prevLedgerID_);
1913 JLOG(j_.
debug()) <<
"Checking for TX consensus: agree=" << agree
1914 <<
", disagree=" << disagree;
1923 result_->roundTime.read(),
1925 mode_.get() == ConsensusMode::proposing,
1928 if (result_->state == ConsensusState::No)
1933 if (result_->state == ConsensusState::MovedOn)
1935 JLOG(j_.
error()) <<
"Unable to reach consensus MovedOn: "
1942 template <
class Adaptor>
1946 if (mode_.get() == ConsensusMode::proposing)
1948 if (result_ && !result_->position.isBowOut())
1950 result_->position.bowOut(now_);
1951 adaptor_.propose(result_->position);
1954 mode_.set(ConsensusMode::observing, adaptor_);
1955 JLOG(j_.
info()) <<
"Bowing out of consensus";
1959 template <
class Adaptor>
1967 if (!result_->compares.emplace(o.id()).second)
1971 if (result_->txns.id() == o.id())
1974 JLOG(j_.
debug()) <<
"createDisputes " << result_->txns.id() <<
" to "
1977 auto differences = result_->txns.compare(o);
1981 for (
auto const& [txId, inThisSet] : differences)
1986 (inThisSet && result_->txns.find(txId) && !o.find(txId)) ||
1987 (!inThisSet && !result_->txns.find(txId) && o.find(txId)));
1989 Tx_t tx = inThisSet ? result_->txns.find(txId) : o.find(txId);
1990 auto txID = tx.id();
1992 if (result_->disputes.find(txID) != result_->disputes.end())
1995 JLOG(j_.
trace()) <<
"Transaction " << txID <<
" is disputed";
1999 result_->txns.exists(txID),
2000 std::max(prevProposers_, currPeerPositions_.size()),
2004 for (
auto const& [nodeId, peerPos] : currPeerPositions_)
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));
2011 adaptor_.share(dtx.tx());
2013 result_->disputes.emplace(txID, std::move(dtx));
2015 JLOG(j_.
trace()) << dc <<
" differences found";
2018 template <
class Adaptor>
2027 if (result_->compares.find(other.id()) == result_->compares.end())
2028 createDisputes(other);
2030 for (
auto& it : result_->disputes)
2032 auto& d = it.second;
2033 d.setVote(node, other.exists(d.tx().id()));
2037 template <
class Adaptor>