xrpld
Loading...
Searching...
No Matches
libxrpl/consensus/Consensus.cpp
1#include <xrpl/consensus/Consensus.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/beast/utility/Journal.h>
5#include <xrpl/consensus/ConsensusParms.h>
6#include <xrpl/consensus/ConsensusTypes.h>
7
8#include <algorithm>
9#include <chrono>
10#include <cstddef>
11#include <memory>
12#include <sstream>
13
14namespace xrpl {
15
16bool
18 bool anyTransactions,
19 std::size_t prevProposers,
20 std::size_t proposersClosed,
21 std::size_t proposersValidated,
22 std::chrono::milliseconds prevRoundTime,
23 std::chrono::milliseconds timeSincePrevClose, // Time since last ledger's close time
24 std::chrono::milliseconds openTime, // Time waiting to close this ledger
25 std::chrono::milliseconds idleInterval,
26 ConsensusParms const& parms,
29{
30 CLOG(clog) << "shouldCloseLedger params anyTransactions: " << anyTransactions
31 << ", prevProposers: " << prevProposers << ", proposersClosed: " << proposersClosed
32 << ", proposersValidated: " << proposersValidated
33 << ", prevRoundTime: " << prevRoundTime.count() << "ms"
34 << ", timeSincePrevClose: " << timeSincePrevClose.count() << "ms"
35 << ", openTime: " << openTime.count() << "ms"
36 << ", idleInterval: " << idleInterval.count() << "ms"
37 << ", ledgerMIN_CLOSE: " << parms.ledgerMinClose.count() << "ms"
38 << ". ";
39 using namespace std::chrono_literals;
40 if ((prevRoundTime < -1s) || (prevRoundTime > 10min) || (timeSincePrevClose > 10min))
41 {
42 // These are unexpected cases, we just close the ledger
44 ss << "shouldCloseLedger Trans=" << (anyTransactions ? "yes" : "no")
45 << " Prop: " << prevProposers << "/" << proposersClosed
46 << " Secs: " << timeSincePrevClose.count() << " (last: " << prevRoundTime.count() << ")";
47
48 JLOG(j.warn()) << ss.str();
49 CLOG(clog) << "closing ledger: " << ss.str() << ". ";
50 return true;
51 }
52
53 if ((proposersClosed + proposersValidated) > (prevProposers / 2))
54 {
55 // If more than half of the network has closed, we close
56 JLOG(j.trace()) << "Others have closed";
57 CLOG(clog) << "closing ledger because enough others have already. ";
58 return true;
59 }
60
61 if (!anyTransactions)
62 {
63 // Only close at the end of the idle interval
64 CLOG(clog) << "no transactions, returning. ";
65 return timeSincePrevClose >= idleInterval; // normal idle
66 }
67
68 // Preserve minimum ledger open time
69 if (openTime < parms.ledgerMinClose)
70 {
71 JLOG(j.debug()) << "Must wait minimum time before closing";
72 CLOG(clog) << "not closing because under ledgerMIN_CLOSE. ";
73 return false;
74 }
75
76 // Don't let this ledger close more than twice as fast as the previous
77 // ledger reached consensus so that slower validators can slow down
78 // the network
79 if (openTime < (prevRoundTime / 2))
80 {
81 JLOG(j.debug()) << "Ledger has not been open long enough";
82 CLOG(clog) << "not closing because not open long enough. ";
83 return false;
84 }
85
86 // Close the ledger
87 CLOG(clog) << "no reason to not close. ";
88 return true;
89}
90
91bool
93 std::size_t agreeing,
94 std::size_t total,
95 bool countSelf,
96 std::size_t minConsensusPct,
97 bool reachedMax,
98 bool stalled,
100{
101 CLOG(clog) << "checkConsensusReached params: agreeing: " << agreeing << ", total: " << total
102 << ", count_self: " << countSelf << ", minConsensusPct: " << minConsensusPct
103 << ", reachedMax: " << reachedMax << ". ";
104
105 // If we are alone for too long, we have consensus.
106 // Delaying consensus like this avoids a circumstance where a peer
107 // gets ahead of proposers insofar as it has not received any proposals.
108 // This could happen if there's a slowdown in receiving proposals. Reaching
109 // consensus prematurely in this way means that the peer will likely desync.
110 // The check for reachedMax should allow plenty of time for proposals to
111 // arrive, and there should be no downside. If a peer is truly not
112 // receiving any proposals, then there should be no hurry. There's
113 // really nowhere to go.
114 if (total == 0)
115 {
116 if (reachedMax)
117 {
118 CLOG(clog) << "Consensus reached because nobody shares our position and "
119 "maximum duration has passed.";
120 return true;
121 }
122 CLOG(clog) << "Consensus not reached and nobody shares our position. ";
123 return false;
124 }
125
126 // We only get stalled when there are disputed transactions and all of them
127 // unequivocally have 80% (minConsensusPct) agreement, either for or
128 // against. That is: either under 20% or over 80% consensus (respectively
129 // "nay" or "yay"). This prevents manipulation by a minority of byzantine
130 // peers of which transactions make the cut to get into the ledger.
131 if (stalled)
132 {
133 CLOG(clog) << "consensus stalled. ";
134 return true;
135 }
136
137 if (countSelf)
138 {
139 ++agreeing;
140 ++total;
141 CLOG(clog) << "agreeing and total adjusted: " << agreeing << ',' << total << ". ";
142 }
143
144 std::size_t const currentPercentage = (agreeing * 100) / total;
145
146 CLOG(clog) << "currentPercentage: " << currentPercentage;
147 bool const ret = currentPercentage >= minConsensusPct;
148 if (ret)
149 {
150 CLOG(clog) << ", consensus reached. ";
151 }
152 else
153 {
154 CLOG(clog) << ", consensus not reached. ";
155 }
156 return ret;
157}
158
161 std::size_t prevProposers,
162 std::size_t currentProposers,
163 std::size_t currentAgree,
164 std::size_t currentFinished,
165 std::chrono::milliseconds previousAgreeTime,
166 std::chrono::milliseconds currentAgreeTime,
167 bool stalled,
168 ConsensusParms const& parms,
169 bool proposing,
172{
173 CLOG(clog) << "checkConsensus: prop=" << currentProposers << "/" << prevProposers
174 << " agree=" << currentAgree << " validated=" << currentFinished
175 << " time=" << currentAgreeTime.count() << "/" << previousAgreeTime.count()
176 << " proposing? " << proposing
177 << " minimum duration to reach consensus: " << parms.ledgerMinConsensus.count()
178 << "ms"
179 << " max consensus time " << parms.ledgerMaxConsensus.count() << "ms"
180 << " minimum consensus percentage: " << parms.minConsensusPct << ". ";
181
182 if (currentAgreeTime <= parms.ledgerMinConsensus)
183 {
184 CLOG(clog) << "Not reached. ";
185 return ConsensusState::No;
186 }
187
188 if (currentProposers < (prevProposers * 3 / 4))
189 {
190 // Less than 3/4 of the last ledger's proposers are present; don't
191 // rush: we may need more time.
192 if (currentAgreeTime < (previousAgreeTime + parms.ledgerMinConsensus))
193 {
194 JLOG(j.trace()) << "too fast, not enough proposers";
195 CLOG(clog) << "Too fast, not enough proposers. Not reached. ";
196 return ConsensusState::No;
197 }
198 }
199
200 // Have we, together with the nodes on our UNL list, reached the threshold
201 // to declare consensus?
203 currentAgree,
204 currentProposers,
205 proposing,
206 parms.minConsensusPct,
207 currentAgreeTime > parms.ledgerMaxConsensus,
208 stalled,
209 clog))
210 {
211 JLOG((stalled ? j.warn() : j.debug()))
212 << "normal consensus" << (stalled ? ", but stalled" : "");
213 CLOG(clog) << "reached" << (stalled ? ", but stalled." : ".");
214 return ConsensusState::Yes;
215 }
216
217 // Have sufficient nodes on our UNL list moved on and reached the threshold
218 // to declare consensus?
220 currentFinished,
221 currentProposers,
222 false,
223 parms.minConsensusPct,
224 currentAgreeTime > parms.ledgerMaxConsensus,
225 false,
226 clog))
227 {
228 JLOG(j.warn()) << "We see no consensus, but 80% of nodes have moved on";
229 CLOG(clog) << "We see no consensus, but 80% of nodes have moved on";
231 }
232
233 std::chrono::milliseconds const maxAgreeTime =
234 previousAgreeTime * parms.ledgerAbandonConsensusFactor;
235 if (currentAgreeTime >
236 std::clamp(maxAgreeTime, parms.ledgerMaxConsensus, parms.ledgerAbandonConsensus))
237 {
238 JLOG(j.warn()) << "consensus taken too long";
239 CLOG(clog) << "Consensus taken too long. ";
240 // Note the Expired result may be overridden by the caller.
242 }
243
244 // no consensus yet
245 JLOG(j.trace()) << "no consensus";
246 CLOG(clog) << "No consensus. ";
247 return ConsensusState::No;
248}
249
250} // namespace xrpl
T clamp(T... args)
A generic endpoint for log messages.
Definition Journal.h:44
Stream debug() const
Definition Journal.h:344
Stream trace() const
Severity stream access functions.
Definition Journal.h:338
Stream warn() const
Definition Journal.h:356
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
ConsensusState
Whether we have or don't have a consensus.
@ Expired
Consensus time limit has hard-expired.
@ MovedOn
The network has consensus without us.
@ Yes
We have consensus along with the network.
@ No
We do not have consensus.
bool checkConsensusReached(std::size_t agreeing, std::size_t total, bool countSelf, std::size_t minConsensusPct, bool reachedMax, bool stalled, std::unique_ptr< std::stringstream > const &clog)
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, bool stalled, ConsensusParms const &parms, bool proposing, beast::Journal j, std::unique_ptr< std::stringstream > const &clog={})
Determine whether the network reached consensus and whether we joined.
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::chrono::milliseconds idleInterval, ConsensusParms const &parms, beast::Journal j, std::unique_ptr< std::stringstream > const &clog={})
Determines whether the current ledger should close at this time.
T str(T... args)
Consensus algorithm parameters.
std::chrono::milliseconds const ledgerMinClose
Minimum number of seconds to wait to ensure others have computed the LCL.
std::chrono::milliseconds const ledgerAbandonConsensus
Maximum amount of time to give a consensus round.
std::chrono::milliseconds const ledgerMinConsensus
The number of seconds we wait minimum to ensure participation.
std::chrono::milliseconds const ledgerMaxConsensus
The maximum amount of time to spend pausing for laggards.
std::size_t const minConsensusPct
The percentage threshold above which we can declare consensus.
std::size_t const ledgerAbandonConsensusFactor
How long to wait before completely abandoning consensus.