rippled
Loading...
Searching...
No Matches
LedgerCleaner.cpp
1#include <xrpld/app/ledger/InboundLedgers.h>
2#include <xrpld/app/ledger/LedgerCleaner.h>
3#include <xrpld/app/ledger/LedgerMaster.h>
4#include <xrpld/app/misc/LoadFeeTrack.h>
5
6#include <xrpl/beast/core/CurrentThreadName.h>
7#include <xrpl/protocol/jss.h>
8
9namespace ripple {
10
11/*
12
13LedgerCleaner
14
15Cleans up the ledger. Specifically, resolves these issues:
16
171. Older versions could leave the SQLite account and transaction databases in
18 an inconsistent state. The cleaner identifies these inconsistencies and
19 resolves them.
20
212. Upon request, checks for missing nodes in a ledger and triggers a fetch.
22
23*/
24
26{
30
32
34
35 enum class State : char { notCleaning = 0, cleaning };
37 bool shouldExit_ = false;
38
39 // The lowest ledger in the range we're checking.
41
42 // The highest ledger in the range we're checking
44
45 // Check all state/transaction nodes
46 bool checkNodes_ = false;
47
48 // Rewrite SQL databases
49 bool fixTxns_ = false;
50
51 // Number of errors encountered since last success
52 int failures_ = 0;
53
54 //--------------------------------------------------------------------------
55public:
57 : app_(app), j_(journal)
58 {
59 }
60
62 {
63 if (thread_.joinable())
64 LogicError("LedgerCleanerImp::stop not called.");
65 }
66
67 void
68 start() override
69 {
71 }
72
73 void
74 stop() override
75 {
76 JLOG(j_.info()) << "Stopping";
77 {
79 shouldExit_ = true;
81 }
82 thread_.join();
83 }
84
85 //--------------------------------------------------------------------------
86 //
87 // PropertyStream
88 //
89 //--------------------------------------------------------------------------
90
91 void
93 {
95
96 if (maxRange_ == 0)
97 map["status"] = "idle";
98 else
99 {
100 map["status"] = "running";
101 map["min_ledger"] = minRange_;
102 map["max_ledger"] = maxRange_;
103 map["check_nodes"] = checkNodes_ ? "true" : "false";
104 map["fix_txns"] = fixTxns_ ? "true" : "false";
105 if (failures_ > 0)
106 map["fail_counts"] = failures_;
107 }
108 }
109
110 //--------------------------------------------------------------------------
111 //
112 // LedgerCleaner
113 //
114 //--------------------------------------------------------------------------
115
116 void
117 clean(Json::Value const& params) override
118 {
119 LedgerIndex minRange = 0;
120 LedgerIndex maxRange = 0;
121 app_.getLedgerMaster().getFullValidatedRange(minRange, maxRange);
122
123 {
125
126 maxRange_ = maxRange;
127 minRange_ = minRange;
128 checkNodes_ = false;
129 fixTxns_ = false;
130 failures_ = 0;
131
132 /*
133 JSON Parameters:
134
135 All parameters are optional. By default the cleaner cleans
136 things it thinks are necessary. This behavior can be modified
137 using the following options supplied via JSON RPC:
138
139 "ledger"
140 A single unsigned integer representing an individual
141 ledger to clean.
142
143 "min_ledger", "max_ledger"
144 Unsigned integers representing the starting and ending
145 ledger numbers to clean. If unspecified, clean all ledgers.
146
147 "full"
148 A boolean. When true, means clean everything possible.
149
150 "fix_txns"
151 A boolean value indicating whether or not to fix the
152 transactions in the database as well.
153
154 "check_nodes"
155 A boolean, when set to true means check the nodes.
156
157 "stop"
158 A boolean, when true informs the cleaner to gracefully
159 stop its current activities if any cleaning is taking place.
160 */
161
162 // Quick way to fix a single ledger
163 if (params.isMember(jss::ledger))
164 {
165 maxRange_ = params[jss::ledger].asUInt();
166 minRange_ = params[jss::ledger].asUInt();
167 fixTxns_ = true;
168 checkNodes_ = true;
169 }
170
171 if (params.isMember(jss::max_ledger))
172 maxRange_ = params[jss::max_ledger].asUInt();
173
174 if (params.isMember(jss::min_ledger))
175 minRange_ = params[jss::min_ledger].asUInt();
176
177 if (params.isMember(jss::full))
178 fixTxns_ = checkNodes_ = params[jss::full].asBool();
179
180 if (params.isMember(jss::fix_txns))
181 fixTxns_ = params[jss::fix_txns].asBool();
182
183 if (params.isMember(jss::check_nodes))
184 checkNodes_ = params[jss::check_nodes].asBool();
185
186 if (params.isMember(jss::stop) && params[jss::stop].asBool())
187 minRange_ = maxRange_ = 0;
188
191 }
192 }
193
194 //--------------------------------------------------------------------------
195 //
196 // LedgerCleanerImp
197 //
198 //--------------------------------------------------------------------------
199private:
200 void
202 {
203 beast::setCurrentThreadName("LedgerCleaner");
204 JLOG(j_.debug()) << "Started";
205
206 while (true)
207 {
208 {
211 wakeup_.wait(lock, [this]() {
212 return (shouldExit_ || state_ == State::cleaning);
213 });
214 if (shouldExit_)
215 break;
216 XRPL_ASSERT(
218 "ripple::LedgerCleanerImp::run : is cleaning");
219 }
221 }
222 }
223
224 // VFALCO TODO This should return std::optional<uint256>
227 {
229 try
230 {
231 hash = hashOfSeq(*ledger, index, j_);
232 }
233 catch (SHAMapMissingNode const& mn)
234 {
235 JLOG(j_.warn())
236 << "Ledger #" << ledger->info().seq << ": " << mn.what();
238 ledger->info().hash,
239 ledger->info().seq,
241 }
242 return hash ? *hash : beast::zero; // kludge
243 }
244
252 bool
254 LedgerIndex const& ledgerIndex,
255 LedgerHash const& ledgerHash,
256 bool doNodes,
257 bool doTxns)
258 {
259 auto nodeLedger = app_.getInboundLedgers().acquire(
260 ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
261 if (!nodeLedger)
262 {
263 JLOG(j_.debug()) << "Ledger " << ledgerIndex << " not available";
264 app_.getLedgerMaster().clearLedger(ledgerIndex);
266 ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
267 return false;
268 }
269
270 auto dbLedger = loadByIndex(ledgerIndex, app_);
271 if (!dbLedger || (dbLedger->info().hash != ledgerHash) ||
272 (dbLedger->info().parentHash != nodeLedger->info().parentHash))
273 {
274 // Ideally we'd also check for more than one ledger with that index
275 JLOG(j_.debug())
276 << "Ledger " << ledgerIndex << " mismatches SQL DB";
277 doTxns = true;
278 }
279
280 if (!app_.getLedgerMaster().fixIndex(ledgerIndex, ledgerHash))
281 {
282 JLOG(j_.debug())
283 << "ledger " << ledgerIndex << " had wrong entry in history";
284 doTxns = true;
285 }
286
287 if (doNodes && !nodeLedger->walkLedger(app_.journal("Ledger")))
288 {
289 JLOG(j_.debug()) << "Ledger " << ledgerIndex << " is missing nodes";
290 app_.getLedgerMaster().clearLedger(ledgerIndex);
292 ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
293 return false;
294 }
295
296 if (doTxns && !pendSaveValidated(app_, nodeLedger, true, false))
297 {
298 JLOG(j_.debug()) << "Failed to save ledger " << ledgerIndex;
299 return false;
300 }
301
302 return true;
303 }
304
312 LedgerIndex const& ledgerIndex,
313 std::shared_ptr<ReadView const>& referenceLedger)
314 {
315 LedgerHash ledgerHash;
316
317 if (!referenceLedger || (referenceLedger->info().seq < ledgerIndex))
318 {
319 referenceLedger = app_.getLedgerMaster().getValidatedLedger();
320 if (!referenceLedger)
321 {
322 JLOG(j_.warn()) << "No validated ledger";
323 return ledgerHash; // Nothing we can do. No validated ledger.
324 }
325 }
326
327 if (referenceLedger->info().seq >= ledgerIndex)
328 {
329 // See if the hash for the ledger we need is in the reference ledger
330 ledgerHash = getLedgerHash(referenceLedger, ledgerIndex);
331 if (ledgerHash.isZero())
332 {
333 // No. Try to get another ledger that might have the hash we
334 // need: compute the index and hash of a ledger that will have
335 // the hash we need.
336 LedgerIndex refIndex = getCandidateLedger(ledgerIndex);
337 LedgerHash refHash = getLedgerHash(referenceLedger, refIndex);
338
339 bool const nonzero(refHash.isNonZero());
340 XRPL_ASSERT(
341 nonzero,
342 "ripple::LedgerCleanerImp::getHash : nonzero hash");
343 if (nonzero)
344 {
345 // We found the hash and sequence of a better reference
346 // ledger.
347 referenceLedger = app_.getInboundLedgers().acquire(
348 refHash, refIndex, InboundLedger::Reason::GENERIC);
349 if (referenceLedger)
350 ledgerHash =
351 getLedgerHash(referenceLedger, ledgerIndex);
352 }
353 }
354 }
355 else
356 JLOG(j_.warn()) << "Validated ledger is prior to target ledger";
357
358 return ledgerHash;
359 }
360
362 void
364 {
365 auto shouldExit = [this] {
367 return shouldExit_;
368 };
369
371
372 while (!shouldExit())
373 {
374 LedgerIndex ledgerIndex;
375 LedgerHash ledgerHash;
376 bool doNodes;
377 bool doTxns;
378
380 {
381 JLOG(j_.debug()) << "Waiting for load to subside";
383 continue;
384 }
385
386 {
388 if ((minRange_ > maxRange_) || (maxRange_ == 0) ||
389 (minRange_ == 0))
390 {
391 minRange_ = maxRange_ = 0;
392 return;
393 }
394 ledgerIndex = maxRange_;
395 doNodes = checkNodes_;
396 doTxns = fixTxns_;
397 }
398
399 ledgerHash = getHash(ledgerIndex, goodLedger);
400
401 bool fail = false;
402 if (ledgerHash.isZero())
403 {
404 JLOG(j_.info())
405 << "Unable to get hash for ledger " << ledgerIndex;
406 fail = true;
407 }
408 else if (!doLedger(ledgerIndex, ledgerHash, doNodes, doTxns))
409 {
410 JLOG(j_.info()) << "Failed to process ledger " << ledgerIndex;
411 fail = true;
412 }
413
414 if (fail)
415 {
416 {
418 ++failures_;
419 }
420 // Wait for acquiring to catch up to us
422 }
423 else
424 {
425 {
427 if (ledgerIndex == minRange_)
428 ++minRange_;
429 if (ledgerIndex == maxRange_)
430 --maxRange_;
431 failures_ = 0;
432 }
433 // Reduce I/O pressure and wait for acquiring to catch up to us
435 }
436 }
437 }
438};
439
442{
443 return std::make_unique<LedgerCleanerImp>(app, journal);
444}
445
446} // namespace ripple
Represents a JSON value.
Definition json_value.h:131
UInt asUInt() const
bool asBool() const
bool isMember(char const *key) const
Return true if the object has a member named key.
A generic endpoint for log messages.
Definition Journal.h:41
Stream debug() const
Definition Journal.h:309
Stream info() const
Definition Journal.h:315
Stream warn() const
Definition Journal.h:321
virtual LoadFeeTrack & getFeeTrack()=0
virtual beast::Journal journal(std::string const &name)=0
virtual InboundLedgers & getInboundLedgers()=0
virtual LedgerMaster & getLedgerMaster()=0
virtual std::shared_ptr< Ledger const > acquire(uint256 const &hash, std::uint32_t seq, InboundLedger::Reason)=0
LedgerHash getLedgerHash(std::shared_ptr< ReadView const > &ledger, LedgerIndex index)
void clean(Json::Value const &params) override
Start a long running task to clean the ledger.
LedgerCleanerImp(Application &app, beast::Journal journal)
void doLedgerCleaner()
Run the ledger cleaner.
std::condition_variable wakeup_
bool doLedger(LedgerIndex const &ledgerIndex, LedgerHash const &ledgerHash, bool doNodes, bool doTxns)
Process a single ledger.
void onWrite(beast::PropertyStream::Map &map) override
Subclass override.
beast::Journal const j_
LedgerHash getHash(LedgerIndex const &ledgerIndex, std::shared_ptr< ReadView const > &referenceLedger)
Returns the hash of the specified ledger.
Check the ledger/transaction databases to make sure they have continuity.
std::shared_ptr< Ledger const > getValidatedLedger()
bool fixIndex(LedgerIndex ledgerIndex, LedgerHash const &ledgerHash)
void clearLedger(std::uint32_t seq)
bool getFullValidatedRange(std::uint32_t &minVal, std::uint32_t &maxVal)
bool isLoadedLocal() const
bool isZero() const
Definition base_uint.h:521
bool isNonZero() const
Definition base_uint.h:526
T is_same_v
T join(T... args)
T joinable(T... args)
void setCurrentThreadName(std::string_view newThreadName)
Changes the name of the caller thread.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
std::unique_ptr< LedgerCleaner > make_LedgerCleaner(Application &app, beast::Journal journal)
std::shared_ptr< Ledger > loadByIndex(std::uint32_t ledgerIndex, Application &app, bool acquire)
Definition Ledger.cpp:1099
LedgerIndex getCandidateLedger(LedgerIndex requested)
Find a ledger index from which we could easily get the requested ledger.
Definition View.h:410
std::optional< uint256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition View.cpp:942
void LogicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
bool pendSaveValidated(Application &app, std::shared_ptr< Ledger const > const &ledger, bool isSynchronous, bool isCurrent)
Save, or arrange to save, a fully-validated ledger Returns false on error.
Definition Ledger.cpp:978
T sleep_for(T... args)
T what(T... args)