Skip to content

Commit a6d3cea

Browse files
committed
Declare consensus if peers enter into a stable state
- Stable state means that neither we, nor any of our peers has changed a vote on a disputed transaction in a while. This is undesirable if an 80% consensus has not otherwise been reached. It will cause a validation to be sent, which will help get other (trusting) validators back on track using preferred ledger logic.
1 parent a201bfe commit a6d3cea

File tree

7 files changed

+271
-64
lines changed

7 files changed

+271
-64
lines changed

src/test/consensus/Consensus_test.cpp

+93-8
Original file line numberDiff line numberDiff line change
@@ -82,47 +82,96 @@ class Consensus_test : public beast::unit_test::suite
8282
// Use default parameterss
8383
ConsensusParms const p{};
8484

85+
///////////////
86+
// Disputes still in doubt
87+
//
8588
// Not enough time has elapsed
8689
BEAST_EXPECT(
8790
ConsensusState::No ==
88-
checkConsensus(10, 2, 2, 0, 3s, 2s, p, true, journal_));
91+
checkConsensus(10, 2, 2, 0, 3s, 2s, false, p, true, journal_));
8992

9093
// If not enough peers have propsed, ensure
9194
// more time for proposals
9295
BEAST_EXPECT(
9396
ConsensusState::No ==
94-
checkConsensus(10, 2, 2, 0, 3s, 4s, p, true, journal_));
97+
checkConsensus(10, 2, 2, 0, 3s, 4s, false, p, true, journal_));
9598

9699
// Enough time has elapsed and we all agree
97100
BEAST_EXPECT(
98101
ConsensusState::Yes ==
99-
checkConsensus(10, 2, 2, 0, 3s, 10s, p, true, journal_));
102+
checkConsensus(10, 2, 2, 0, 3s, 10s, false, p, true, journal_));
100103

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

106109
// Our peers have moved on
107110
// Enough time has elapsed and we all agree
108111
BEAST_EXPECT(
109112
ConsensusState::MovedOn ==
110-
checkConsensus(10, 2, 1, 8, 3s, 10s, p, true, journal_));
113+
checkConsensus(10, 2, 1, 8, 3s, 10s, false, p, true, journal_));
111114

112115
// If no peers, don't agree until time has passed.
113116
BEAST_EXPECT(
114117
ConsensusState::No ==
115-
checkConsensus(0, 0, 0, 0, 3s, 10s, p, true, journal_));
118+
checkConsensus(0, 0, 0, 0, 3s, 10s, false, p, true, journal_));
116119

117120
// Agree if no peers and enough time has passed.
118121
BEAST_EXPECT(
119122
ConsensusState::Yes ==
120-
checkConsensus(0, 0, 0, 0, 3s, 16s, p, true, journal_));
123+
checkConsensus(0, 0, 0, 0, 3s, 16s, false, p, true, journal_));
121124

122125
// Expire if too much time has passed without agreement
123126
BEAST_EXPECT(
124127
ConsensusState::Expired ==
125-
checkConsensus(10, 8, 1, 0, 1s, 19s, p, true, journal_));
128+
checkConsensus(10, 8, 1, 0, 1s, 19s, false, p, true, journal_));
129+
///////////////
130+
// Stable state
131+
//
132+
// Not enough time has elapsed
133+
BEAST_EXPECT(
134+
ConsensusState::No ==
135+
checkConsensus(10, 2, 2, 0, 3s, 2s, true, p, true, journal_));
136+
137+
// If not enough peers have propsed, ensure
138+
// more time for proposals
139+
BEAST_EXPECT(
140+
ConsensusState::No ==
141+
checkConsensus(10, 2, 2, 0, 3s, 4s, true, p, true, journal_));
142+
143+
// Enough time has elapsed and we all agree
144+
BEAST_EXPECT(
145+
ConsensusState::Yes ==
146+
checkConsensus(10, 2, 2, 0, 3s, 10s, true, p, true, journal_));
147+
148+
// Enough time has elapsed and we don't yet agree, but there's nothing
149+
// left to dispute
150+
BEAST_EXPECT(
151+
ConsensusState::Yes ==
152+
checkConsensus(10, 2, 1, 0, 3s, 10s, true, p, true, journal_));
153+
154+
// Our peers have moved on
155+
// Enough time has elapsed and we all agree, nothing left to dispute
156+
BEAST_EXPECT(
157+
ConsensusState::Yes ==
158+
checkConsensus(10, 2, 1, 8, 3s, 10s, true, p, true, journal_));
159+
160+
// If no peers, don't agree until time has passed.
161+
BEAST_EXPECT(
162+
ConsensusState::No ==
163+
checkConsensus(0, 0, 0, 0, 3s, 10s, true, p, true, journal_));
164+
165+
// Agree if no peers and enough time has passed.
166+
BEAST_EXPECT(
167+
ConsensusState::Yes ==
168+
checkConsensus(0, 0, 0, 0, 3s, 16s, true, p, true, journal_));
169+
170+
// We are done if there's nothing left to dispute, no matter how much
171+
// time has passed
172+
BEAST_EXPECT(
173+
ConsensusState::Yes ==
174+
checkConsensus(10, 8, 1, 0, 1s, 19s, true, p, true, journal_));
126175
}
127176

128177
void
@@ -1057,6 +1106,41 @@ class Consensus_test : public beast::unit_test::suite
10571106
BEAST_EXPECT(sim.synchronized());
10581107
}
10591108

1109+
void
1110+
testDisputes()
1111+
{
1112+
using namespace csf;
1113+
using namespace std::chrono;
1114+
1115+
std::uint32_t numPeers = 35;
1116+
1117+
ConsensusParms const parms{};
1118+
Sim sim;
1119+
1120+
PeerGroup peers = sim.createGroup(numPeers);
1121+
1122+
SimDuration delay = round<milliseconds>(0.2 * parms.ledgerGRANULARITY);
1123+
peers.trustAndConnect(peers, delay);
1124+
1125+
// Initial round to set prior state
1126+
sim.run(1);
1127+
for (Peer* peer : peers)
1128+
{
1129+
// Every transaction will be seen by every node but two.
1130+
// To accomplish that, every peer will add the ids of every peer
1131+
// except itself, and the one following.
1132+
auto const myId = peer->id;
1133+
auto const nextId = (peer->id + PeerID(1)) % PeerID(numPeers);
1134+
for (Peer* to : sim.trustGraph.trustedPeers(peer))
1135+
{
1136+
if (to->id == myId || to->id == nextId)
1137+
continue;
1138+
peer->openTxs.insert(Tx{static_cast<std::uint32_t>(to->id)});
1139+
}
1140+
}
1141+
sim.run(1);
1142+
}
1143+
10601144
void
10611145
run() override
10621146
{
@@ -1073,6 +1157,7 @@ class Consensus_test : public beast::unit_test::suite
10731157
testHubNetwork();
10741158
testPreferredByBranch();
10751159
testPauseForLaggards();
1160+
testDisputes();
10761161
}
10771162
};
10781163

src/xrpld/app/consensus/RCLValidations.cpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,9 @@ handleNewValidation(
189189
auto& validations = app.getValidations();
190190

191191
// masterKey is seated only if validator is trusted or listed
192-
auto const outcome =
193-
validations.add(calcNodeID(masterKey.value_or(signingKey)), val);
192+
auto const nodeKey = masterKey.value_or(signingKey);
193+
assert(nodeKey != app.getValidationPublicKey());
194+
auto const outcome = validations.add(calcNodeID(nodeKey), val);
194195

195196
if (outcome == ValStatus::current)
196197
{

src/xrpld/consensus/Consensus.cpp

+10-5
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ checkConsensusReached(
8888
std::size_t total,
8989
bool count_self,
9090
std::size_t minConsensusPct,
91-
bool reachedMax)
91+
bool reachedMax,
92+
bool stableState)
9293
{
9394
// If we are alone for too long, we have consensus.
9495
// Delaying consensus like this avoids a circumstance where a peer
@@ -114,7 +115,7 @@ checkConsensusReached(
114115

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

117-
return currentPercentage >= minConsensusPct;
118+
return currentPercentage >= minConsensusPct || stableState;
118119
}
119120

120121
ConsensusState
@@ -125,6 +126,7 @@ checkConsensus(
125126
std::size_t currentFinished,
126127
std::chrono::milliseconds previousAgreeTime,
127128
std::chrono::milliseconds currentAgreeTime,
129+
bool stableState,
128130
ConsensusParms const& parms,
129131
bool proposing,
130132
beast::Journal j)
@@ -162,9 +164,11 @@ checkConsensus(
162164
currentProposers,
163165
proposing,
164166
parms.minCONSENSUS_PCT,
165-
currentAgreeTime > parms.ledgerMAX_CONSENSUS))
167+
currentAgreeTime > parms.ledgerMAX_CONSENSUS,
168+
stableState))
166169
{
167-
JLOG(j.debug()) << "normal consensus";
170+
JLOG(j.debug()) << "normal consensus with " << (stableState ? "" : "un")
171+
<< "stable state";
168172
return ConsensusState::Yes;
169173
}
170174

@@ -175,7 +179,8 @@ checkConsensus(
175179
currentProposers,
176180
false,
177181
parms.minCONSENSUS_PCT,
178-
currentAgreeTime > parms.ledgerMAX_CONSENSUS))
182+
currentAgreeTime > parms.ledgerMAX_CONSENSUS,
183+
false))
179184
{
180185
JLOG(j.warn()) << "We see no consensus, but 80% of nodes have moved on";
181186
return ConsensusState::MovedOn;

src/xrpld/consensus/Consensus.h

+36-15
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ shouldCloseLedger(
7979
last ledger
8080
@param currentAgreeTime how long, in milliseconds, we've been trying to
8181
agree
82+
@param stableState the network appears to be in a stable state, where
83+
neither we nor our peers have changed their vote on any disputes in a
84+
while. This is undesirable, and will cause us to end consensus
85+
without 80% agreement.
8286
@param parms Consensus constant parameters
8387
@param proposing whether we should count ourselves
8488
@param j journal for logging
@@ -91,6 +95,7 @@ checkConsensus(
9195
std::size_t currentFinished,
9296
std::chrono::milliseconds previousAgreeTime,
9397
std::chrono::milliseconds currentAgreeTime,
98+
bool stableState,
9499
ConsensusParms const& parms,
95100
bool proposing,
96101
beast::Journal j);
@@ -559,6 +564,9 @@ class Consensus
559564

560565
NetClock::duration closeResolution_ = ledgerDefaultTimeResolution;
561566

567+
ConsensusParms::AvalancheState closeTimeAvalancheState_ =
568+
ConsensusParms::init;
569+
562570
// Time it took for the last consensus round to converge
563571
std::chrono::milliseconds prevRoundTime_;
564572

@@ -676,6 +684,7 @@ Consensus<Adaptor>::startRoundInternal(
676684
previousLedger_ = prevLedger;
677685
result_.reset();
678686
convergePercent_ = 0;
687+
closeTimeAvalancheState_ = ConsensusParms::init;
679688
haveCloseTimeConsensus_ = false;
680689
openTime_.reset(clock_.now());
681690
currPeerPositions_.clear();
@@ -832,6 +841,8 @@ Consensus<Adaptor>::timerEntry(NetClock::time_point const& now)
832841
}
833842
else if (phase_ == ConsensusPhase::establish)
834843
{
844+
if (result_)
845+
++result_->peerUnchangedCounter;
835846
phaseEstablish();
836847
}
837848
}
@@ -1443,16 +1454,10 @@ Consensus<Adaptor>::updateOurPositions()
14431454
}
14441455
else
14451456
{
1446-
int neededWeight;
1447-
1448-
if (convergePercent_ < parms.avMID_CONSENSUS_TIME)
1449-
neededWeight = parms.avINIT_CONSENSUS_PCT;
1450-
else if (convergePercent_ < parms.avLATE_CONSENSUS_TIME)
1451-
neededWeight = parms.avMID_CONSENSUS_PCT;
1452-
else if (convergePercent_ < parms.avSTUCK_CONSENSUS_TIME)
1453-
neededWeight = parms.avLATE_CONSENSUS_PCT;
1454-
else
1455-
neededWeight = parms.avSTUCK_CONSENSUS_PCT;
1457+
auto const [neededWeight, newState] = getNeededWeight(
1458+
parms, closeTimeAvalancheState_, convergePercent_, {});
1459+
if (newState)
1460+
closeTimeAvalancheState_ = *newState;
14561461

14571462
int participants = currPeerPositions_.size();
14581463
if (mode_.get() == ConsensusMode::proposing)
@@ -1566,7 +1571,8 @@ Consensus<Adaptor>::haveConsensus()
15661571
}
15671572
else
15681573
{
1569-
JLOG(j_.debug()) << nodeId << " has " << peerProp.position();
1574+
JLOG(j_.debug()) << "Proposal disagreement: Peer " << nodeId
1575+
<< " has " << peerProp.position();
15701576
++disagree;
15711577
}
15721578
}
@@ -1576,6 +1582,18 @@ Consensus<Adaptor>::haveConsensus()
15761582
JLOG(j_.debug()) << "Checking for TX consensus: agree=" << agree
15771583
<< ", disagree=" << disagree;
15781584

1585+
ConsensusParms const& parms = adaptor_.parms();
1586+
// stable state is NOT desirable if we don't have 80% agreement
1587+
bool stableState = true;
1588+
for (auto const& [txid, dt] : result_->disputes)
1589+
{
1590+
if (!dt.stableState(parms, result_->peerUnchangedCounter))
1591+
{
1592+
stableState = false;
1593+
break;
1594+
}
1595+
}
1596+
15791597
// Determine if we actually have consensus or not
15801598
result_->state = checkConsensus(
15811599
prevProposers_,
@@ -1584,7 +1602,8 @@ Consensus<Adaptor>::haveConsensus()
15841602
currentFinished,
15851603
prevRoundTime_,
15861604
result_->roundTime.read(),
1587-
adaptor_.parms(),
1605+
stableState,
1606+
parms,
15881607
mode_.get() == ConsensusMode::proposing,
15891608
j_);
15901609

@@ -1676,8 +1695,9 @@ Consensus<Adaptor>::createDisputes(TxSet_t const& o)
16761695
{
16771696
Proposal_t const& peerProp = peerPos.proposal();
16781697
auto const cit = acquired_.find(peerProp.position());
1679-
if (cit != acquired_.end())
1680-
dtx.setVote(nodeId, cit->second.exists(txID));
1698+
if (cit != acquired_.end() &&
1699+
dtx.setVote(nodeId, cit->second.exists(txID)))
1700+
result_->peerUnchangedCounter = 0;
16811701
}
16821702
adaptor_.share(dtx.tx());
16831703

@@ -1701,7 +1721,8 @@ Consensus<Adaptor>::updateDisputes(NodeID_t const& node, TxSet_t const& other)
17011721
for (auto& it : result_->disputes)
17021722
{
17031723
auto& d = it.second;
1704-
d.setVote(node, other.exists(d.tx().id()));
1724+
if (d.setVote(node, other.exists(d.tx().id())))
1725+
result_->peerUnchangedCounter = 0;
17051726
}
17061727
}
17071728

0 commit comments

Comments
 (0)