Skip to content

Commit

Permalink
[WIP] Checkpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
ximinez committed Feb 8, 2025
1 parent 17b1c53 commit 61e12a2
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 22 deletions.
65 changes: 57 additions & 8 deletions src/test/consensus/Consensus_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,47 +82,96 @@ class Consensus_test : public beast::unit_test::suite
// Use default parameterss
ConsensusParms const p{};

///////////////
// Disputes remaining
//
// Not enough time has elapsed
BEAST_EXPECT(
ConsensusState::No ==
checkConsensus(10, 2, 2, 0, 3s, 2s, p, true, journal_));
checkConsensus(10, 2, 2, 0, 3s, 2s, false, p, true, journal_));

// If not enough peers have propsed, ensure
// more time for proposals
BEAST_EXPECT(
ConsensusState::No ==
checkConsensus(10, 2, 2, 0, 3s, 4s, p, true, journal_));
checkConsensus(10, 2, 2, 0, 3s, 4s, false, p, true, journal_));

// Enough time has elapsed and we all agree
BEAST_EXPECT(
ConsensusState::Yes ==
checkConsensus(10, 2, 2, 0, 3s, 10s, p, true, journal_));
checkConsensus(10, 2, 2, 0, 3s, 10s, false, p, true, journal_));

// Enough time has elapsed and we don't yet agree
BEAST_EXPECT(
ConsensusState::No ==
checkConsensus(10, 2, 1, 0, 3s, 10s, p, true, journal_));
checkConsensus(10, 2, 1, 0, 3s, 10s, false, p, true, journal_));

// Our peers have moved on
// Enough time has elapsed and we all agree
BEAST_EXPECT(
ConsensusState::MovedOn ==
checkConsensus(10, 2, 1, 8, 3s, 10s, p, true, journal_));
checkConsensus(10, 2, 1, 8, 3s, 10s, false, p, true, journal_));

// If no peers, don't agree until time has passed.
BEAST_EXPECT(
ConsensusState::No ==
checkConsensus(0, 0, 0, 0, 3s, 10s, p, true, journal_));
checkConsensus(0, 0, 0, 0, 3s, 10s, false, p, true, journal_));

// Agree if no peers and enough time has passed.
BEAST_EXPECT(
ConsensusState::Yes ==
checkConsensus(0, 0, 0, 0, 3s, 16s, p, true, journal_));
checkConsensus(0, 0, 0, 0, 3s, 16s, false, p, true, journal_));

// Expire if too much time has passed without agreement
BEAST_EXPECT(
ConsensusState::Expired ==
checkConsensus(10, 8, 1, 0, 1s, 19s, p, true, journal_));
checkConsensus(10, 8, 1, 0, 1s, 19s, false, p, true, journal_));
///////////////
// No disputes remaining
//
// Not enough time has elapsed
BEAST_EXPECT(
ConsensusState::No ==
checkConsensus(10, 2, 2, 0, 3s, 2s, true, p, true, journal_));

// If not enough peers have propsed, ensure
// more time for proposals
BEAST_EXPECT(
ConsensusState::No ==
checkConsensus(10, 2, 2, 0, 3s, 4s, true, p, true, journal_));

// Enough time has elapsed and we all agree
BEAST_EXPECT(
ConsensusState::Yes ==
checkConsensus(10, 2, 2, 0, 3s, 10s, true, p, true, journal_));

// Enough time has elapsed and we don't yet agree, but there's nothing
// left to dispute
BEAST_EXPECT(
ConsensusState::Yes ==
checkConsensus(10, 2, 1, 0, 3s, 10s, true, p, true, journal_));

// Our peers have moved on
// Enough time has elapsed and we all agree, nothing left to dispute
BEAST_EXPECT(
ConsensusState::Yes ==
checkConsensus(10, 2, 1, 8, 3s, 10s, true, p, true, journal_));

// If no peers, don't agree until time has passed.
BEAST_EXPECT(
ConsensusState::No ==
checkConsensus(0, 0, 0, 0, 3s, 10s, true, p, true, journal_));

// Agree if no peers and enough time has passed.
BEAST_EXPECT(
ConsensusState::Yes ==
checkConsensus(0, 0, 0, 0, 3s, 16s, true, p, true, journal_));

// We are done if there's nothing left to dispute, no matter how much
// time has passed
BEAST_EXPECT(
ConsensusState::Yes ==
checkConsensus(10, 8, 1, 0, 1s, 19s, true, p, true, journal_));
}

void
Expand Down
15 changes: 10 additions & 5 deletions src/xrpld/consensus/Consensus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ checkConsensusReached(
std::size_t total,
bool count_self,
std::size_t minConsensusPct,
bool reachedMax)
bool reachedMax,
bool noDisputes)
{
// If we are alone for too long, we have consensus.
// Delaying consensus like this avoids a circumstance where a peer
Expand All @@ -114,7 +115,7 @@ checkConsensusReached(

std::size_t currentPercentage = (agreeing * 100) / total;

return currentPercentage >= minConsensusPct;
return currentPercentage >= minConsensusPct || noDisputes;
}

ConsensusState
Expand All @@ -125,6 +126,7 @@ checkConsensus(
std::size_t currentFinished,
std::chrono::milliseconds previousAgreeTime,
std::chrono::milliseconds currentAgreeTime,
bool noDisputes,
ConsensusParms const& parms,
bool proposing,
beast::Journal j)
Expand Down Expand Up @@ -162,9 +164,11 @@ checkConsensus(
currentProposers,
proposing,
parms.minCONSENSUS_PCT,
currentAgreeTime > parms.ledgerMAX_CONSENSUS))
currentAgreeTime > parms.ledgerMAX_CONSENSUS,
noDisputes))
{
JLOG(j.debug()) << "normal consensus";
JLOG(j.debug()) << "normal consensus with " << (noDisputes ? "no " : "")
<< "disputes";
return ConsensusState::Yes;
}

Expand All @@ -175,7 +179,8 @@ checkConsensus(
currentProposers,
false,
parms.minCONSENSUS_PCT,
currentAgreeTime > parms.ledgerMAX_CONSENSUS))
currentAgreeTime > parms.ledgerMAX_CONSENSUS,
false))
{
JLOG(j.warn()) << "We see no consensus, but 80% of nodes have moved on";
return ConsensusState::MovedOn;
Expand Down
24 changes: 23 additions & 1 deletion src/xrpld/consensus/Consensus.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ checkConsensus(
std::size_t currentFinished,
std::chrono::milliseconds previousAgreeTime,
std::chrono::milliseconds currentAgreeTime,
bool noDisputes,
ConsensusParms const& parms,
bool proposing,
beast::Journal j);
Expand Down Expand Up @@ -833,6 +834,9 @@ Consensus<Adaptor>::timerEntry(NetClock::time_point const& now)
else if (phase_ == ConsensusPhase::establish)
{
phaseEstablish();

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

Expand Down Expand Up @@ -1409,6 +1413,7 @@ 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 @@ -1577,6 +1582,22 @@ Consensus<Adaptor>::haveConsensus()
JLOG(j_.debug()) << "Checking for TX consensus: agree=" << agree
<< ", disagree=" << disagree;

ConsensusParms const& parms = adaptor_.parms();
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())
{
// We haven't reached the point where unanimity is required, or we
// are still voting for this tx
noMoreYesDisputes = false;
break;
}
}

// Determine if we actually have consensus or not
result_->state = checkConsensus(
prevProposers_,
Expand All @@ -1585,7 +1606,8 @@ Consensus<Adaptor>::haveConsensus()
currentFinished,
prevRoundTime_,
result_->roundTime.read(),
adaptor_.parms(),
noMoreYesDisputes,
parms,
mode_.get() == ConsensusMode::proposing,
j_);

Expand Down
3 changes: 3 additions & 0 deletions src/xrpld/consensus/ConsensusParms.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ struct ConsensusParms

//! 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;
};

} // namespace ripple
Expand Down
3 changes: 3 additions & 0 deletions src/xrpld/consensus/ConsensusTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,9 @@ 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
68 changes: 60 additions & 8 deletions src/xrpld/consensus/DisputedTx.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ class DisputedTx
return ourVote_;
}

//! The round when the transaction became required
std::optional<std::size_t>
getRequiredRound() const
{
return requiredRound_;
}

//! The disputed transaction.
Tx_t const&
tx() const
Expand Down Expand Up @@ -120,12 +127,17 @@ 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, bool proposing, ConsensusParms const& p);
updateVote(
int percentTime,
std::size_t roundCounter,
bool proposing,
ConsensusParms const& p);

//! JSON representation of dispute, used for debugging
Json::Value
Expand All @@ -135,8 +147,18 @@ class DisputedTx
int yays_; //< Number of yes votes
int nays_; //< Number of no votes
bool ourVote_; //< Our vote (true is yes)
Tx_t tx_; //< Transaction under dispute
Map_t votes_; //< Map from NodeID to vote
/// 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_;
Tx_t tx_; //< Transaction under dispute
Map_t votes_; //< Map from NodeID to vote
beast::Journal const j_;
};

Expand Down Expand Up @@ -201,6 +223,7 @@ 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 @@ -220,17 +243,46 @@ DisputedTx<Tx_t, NodeID_t>::updateVote(

// To prevent avalanche stalls, we increase the needed weight slightly
// over time.
if (percentTime < p.avMID_CONSENSUS_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)
}
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)
}
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)
}
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)
}
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;
}
else
// Give up
newPosition = false;
Expand Down

0 comments on commit 61e12a2

Please sign in to comment.