Skip to content

Commit

Permalink
[WIP] Checkpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
ximinez committed Feb 11, 2025
1 parent 7c16494 commit 6e513d9
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 111 deletions.
48 changes: 30 additions & 18 deletions src/xrpld/consensus/Consensus.h
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,8 @@ class Consensus

std::optional<Result> result_;
ConsensusCloseTimes rawCloseTimes_;
ConsensusParms::AvalancheState closeTimeAvalancheState_ =
ConsensusParms::init;

//-------------------------------------------------------------------------
// Peer related consensus data
Expand Down Expand Up @@ -834,9 +836,6 @@ Consensus<Adaptor>::timerEntry(NetClock::time_point const& now)
else if (phase_ == ConsensusPhase::establish)
{
phaseEstablish();

if (result_)
++result_->roundCounter;
}
}

Expand Down Expand Up @@ -1413,7 +1412,6 @@ Consensus<Adaptor>::updateOurPositions()
// time can change our position on a dispute
if (dispute.updateVote(
convergePercent_,
result_->roundCounter,
mode_.get() == ConsensusMode::proposing,
parms))
{
Expand Down Expand Up @@ -1448,16 +1446,33 @@ Consensus<Adaptor>::updateOurPositions()
}
else
{
int neededWeight;

if (convergePercent_ < parms.avMID_CONSENSUS_TIME)
neededWeight = parms.avINIT_CONSENSUS_PCT;
else if (convergePercent_ < parms.avLATE_CONSENSUS_TIME)
neededWeight = parms.avMID_CONSENSUS_PCT;
else if (convergePercent_ < parms.avSTUCK_CONSENSUS_TIME)
neededWeight = parms.avLATE_CONSENSUS_PCT;
else
neededWeight = parms.avSTUCK_CONSENSUS_PCT;
int const neededWeight = [&]() {
// To prevent avalanche stalls, we increase the needed weight
// slightly over time.
// at() can throw, but the map is built by hand to ensure all valid
// values are available.
auto const& currentState =
parms.avalancheCutoffs.at(closeTimeAvalancheState_);
// Unlike disputes, the close time percentage can never get past
// "stuck"
if (closeTimeAvalancheState_ != ConsensusParms::stuck)
{
// See if enough time has passed to move on to the next state.
// at() can throw, but the map is built by hand to ensure all
// valid values are available.
auto const& nextState =
parms.avalancheCutoffs.at(currentState.next);
XRPL_ASSERT(
nextState.consensusTime,
"ripple::DisputedTx::updateVote next state valid");
if (convergePercent_ >= nextState.consensusTime)
{
closeTimeAvalancheState_ = currentState.next;
return nextState.consensusPct;
}
}
return currentState.consensusPct;
}();

int participants = currPeerPositions_.size();
if (mode_.get() == ConsensusMode::proposing)
Expand Down Expand Up @@ -1586,10 +1601,7 @@ Consensus<Adaptor>::haveConsensus()
bool noMoreYesDisputes = true;
for (auto const& [txid, dt] : result_->disputes)
{
auto const reqRound = dt.getRequiredRound();
if (!reqRound ||
result_->roundCounter < (*reqRound + parms.avMIN_ROUNDS) ||
dt.getOurVote())
if (dt.canChangeVote(parms))
{
// We haven't reached the point where unanimity is required, or we
// are still voting for this tx
Expand Down
56 changes: 29 additions & 27 deletions src/xrpld/consensus/ConsensusParms.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#include <chrono>
#include <cstddef>
#include <map>

namespace ripple {

Expand Down Expand Up @@ -124,38 +125,39 @@ struct ConsensusParms
// we increase the threshold for yes votes to add a transaction to our
// position.

//! Percentage of nodes on our UNL that must vote yes
std::size_t avINIT_CONSENSUS_PCT = 50;

//! Percentage of previous round duration before we advance
std::size_t avMID_CONSENSUS_TIME = 50;

//! Percentage of nodes that most vote yes after advancing
std::size_t avMID_CONSENSUS_PCT = 65;

//! Percentage of previous round duration before we advance
std::size_t avLATE_CONSENSUS_TIME = 85;

//! Percentage of nodes that most vote yes after advancing
std::size_t avLATE_CONSENSUS_PCT = 70;

//! Percentage of previous round duration before we are stuck
std::size_t avSTUCK_CONSENSUS_TIME = 200;

//! Percentage of nodes that must vote yes after we are stuck
std::size_t avSTUCK_CONSENSUS_PCT = 95;

//! Percentage of previous round duration before we require 100% approval
std::size_t avREQUIRED_CONSENSUS_TIME = 400;

//! Percentage of previous round duration before all votes change to "no"
std::size_t avABORT_CONSENSUS_TIME = 500;

//! Percentage of nodes required to reach agreement on ledger close time
std::size_t avCT_CONSENSUS_PCT = 75;

//! Number of rounds at each avalanche level before we can move to the next
std::size_t avMIN_ROUNDS = 2;

enum AvalancheState { init, mid, late, stuck, limbo, abort };
struct AvalancheCutoff
{
// Point to the members above so they can be modified without worrying
// about the table
std::size_t const consensusTime;
std::size_t const consensusPct;
AvalancheState const next;
};
std::map<AvalancheState, AvalancheCutoff> avalancheCutoffs = {
// {state, {time, percent, nextState}},
// Initial state: 50% of nodes must vote yes
{init, {0, 50, mid}},
// mid-consensus starts after 50% of the previous round time, and
// requires 65% yes
{mid, {50, 65, late}},
// late consensus starts after 85% time, and requires 70% yes
{late, {85, 70, stuck}},
// we're stuck after 2x time, and requires 95% yes
{stuck, {200, 95, limbo}},
// we're in limbo if the transaction is still in dispute after 4x time,
// and the only way out is complete agreement
{limbo, {400, 100, abort}},
// We abort after 5x time. Vote no regardless of agreement (signified by
// the magic "0")
{abort, {500, 0, abort}},
};
};

} // namespace ripple
Expand Down
3 changes: 0 additions & 3 deletions src/xrpld/consensus/ConsensusTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,6 @@ struct ConsensusResult
// Measures the duration of the establish phase for this consensus round
ConsensusTimer roundTime;

// Counts the number of attempts to finish the establish phase
std::size_t roundCounter = 0;

// Indicates state in which consensus ended. Once in the accept phase
// will be either Yes or MovedOn or Expired
ConsensusState state = ConsensusState::No;
Expand Down
112 changes: 49 additions & 63 deletions src/xrpld/consensus/DisputedTx.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,20 @@ class DisputedTx
return ourVote_;
}

//! The round when the transaction became required
std::optional<std::size_t>
getRequiredRound() const
//! Will we ever consider changing our vote?
// We can change our vote if we are currently voting yes or the required
// agreement is < 100.
bool
canChangeVote(ConsensusParms const& p) const
{
return requiredRound_;
if (ourVote_)
return true;
// at() can throw, but the map is built by hand to ensure all valid
// values are available.
auto const& currentState = p.avalancheCutoffs.at(avalancheState_);
// If the currentState has no consensusPct, then we are at the final
// state, and will vote no.
return (currentState.consensusPct && currentState.consensusPct < 100);
}

//! The disputed transaction.
Expand Down Expand Up @@ -127,17 +136,12 @@ class DisputedTx
@param percentTime Percentage progress through consensus, e.g. 50%
through or 90%.
@param roundCounter Number of attempts to establish consensus
@param proposing Whether we are proposing to our peers in this round.
@param p Consensus parameters controlling thresholds for voting
@return Whether our vote changed
*/
bool
updateVote(
int percentTime,
std::size_t roundCounter,
bool proposing,
ConsensusParms const& p);
updateVote(int percentTime, bool proposing, ConsensusParms const& p);

//! JSON representation of dispute, used for debugging
Json::Value
Expand All @@ -147,16 +151,8 @@ class DisputedTx
int yays_; //< Number of yes votes
int nays_; //< Number of no votes
bool ourVote_; //< Our vote (true is yes)
/// The value of roundCounter when we first cross the "init" threshold
std::optional<std::size_t> initRound_;
/// The value of roundCounter when we first cross the "mid" threshold
std::optional<std::size_t> midRound_;
/// The value of roundCounter when we first cross the "late" threshold
std::optional<std::size_t> lateRound_;
/// The value of roundCounter when we first cross the "stuck" threshold
std::optional<std::size_t> stuckRound_;
/// The value of roundCounter when we first cross the "required" threshold
std::optional<std::size_t> requiredRound_;
ConsensusParms::AvalancheState avalancheState_ = ConsensusParms::init;
int avalancheCounter_ = 0;
Tx_t tx_; //< Transaction under dispute
Map_t votes_; //< Map from NodeID to vote
beast::Journal const j_;
Expand Down Expand Up @@ -223,7 +219,6 @@ template <class Tx_t, class NodeID_t>
bool
DisputedTx<Tx_t, NodeID_t>::updateVote(
int percentTime,
std::size_t roundCounter,
bool proposing,
ConsensusParms const& p)
{
Expand All @@ -241,48 +236,39 @@ DisputedTx<Tx_t, NodeID_t>::updateVote(
// This is basically the percentage of nodes voting 'yes' (including us)
weight = (yays_ * 100 + (ourVote_ ? 100 : 0)) / (nays_ + yays_ + 1);

// To prevent avalanche stalls, we increase the needed weight slightly
// over time.
if (percentTime < p.avMID_CONSENSUS_TIME || !initRound_ ||
roundCounter < (*initRound_ + p.avMIN_ROUNDS))
{
if (!initRound_)
initRound_ = roundCounter;
newPosition = weight > p.avINIT_CONSENSUS_PCT;
}
else if (
percentTime < p.avLATE_CONSENSUS_TIME || !midRound_ ||
roundCounter < (*midRound_ + p.avMIN_ROUNDS))
{
if (!midRound_)
midRound_ = roundCounter;
newPosition = weight > p.avMID_CONSENSUS_PCT;
}
else if (
percentTime < p.avSTUCK_CONSENSUS_TIME || !lateRound_ ||
roundCounter < (*lateRound_ + p.avMIN_ROUNDS))
{
if (!lateRound_)
lateRound_ = roundCounter;
newPosition = weight > p.avLATE_CONSENSUS_PCT;
}
else if (
percentTime < p.avREQUIRED_CONSENSUS_TIME || !stuckRound_ ||
roundCounter < (*stuckRound_ + p.avMIN_ROUNDS))
{
if (!stuckRound_)
stuckRound_ = roundCounter;
newPosition = weight > p.avSTUCK_CONSENSUS_PCT;
}
else if (
percentTime < p.avABORT_CONSENSUS_TIME || !requiredRound_ ||
roundCounter < (*requiredRound_ + p.avMIN_ROUNDS))
{
if (!requiredRound_)
requiredRound_ = roundCounter;
// Only keep this tx if every node agrees on it
newPosition = weight >= 100;
}
auto const requiredPct = [&]() {
// To prevent avalanche stalls, we increase the needed weight
// slightly over time. We also need to ensure that the consensus has
// made a minimum number of attempts at each "state" before moving
// to the next.
// at() can throw, but the map is built by hand to ensure all valid
// values are available.
auto const& currentState = p.avalancheCutoffs.at(avalancheState_);
// If the currentState has no consensusPct, then we are at the final
// state, and will vote no.
if (++avalancheCounter_ >= p.avMIN_ROUNDS &&
currentState.consensusPct)
{
// We've spent suffienct rounds at this step. See if enough time
// has passed to move on to the next.
// at() can throw, but the map is built by hand to ensure all
// valid values are available.
auto const& nextState =
p.avalancheCutoffs.at(currentState.next);
XRPL_ASSERT(
nextState.consensusTime,
"ripple::DisputedTx::updateVote next state valid");
if (percentTime >= nextState.consensusTime)
{
avalancheState_ = currentState.next;
avalancheCounter_ = 0;
return nextState.consensusPct;
}
}
return currentState.consensusPct;
}();
if (requiredPct)
newPosition = weight > requiredPct;
else
// Give up
newPosition = false;
Expand Down

0 comments on commit 6e513d9

Please sign in to comment.