rippled
Loading...
Searching...
No Matches
LedgerCleaner.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012, 2013 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpld/app/ledger/InboundLedgers.h>
21#include <xrpld/app/ledger/LedgerCleaner.h>
22#include <xrpld/app/ledger/LedgerMaster.h>
23#include <xrpld/app/misc/LoadFeeTrack.h>
24#include <xrpl/beast/core/CurrentThreadName.h>
25#include <xrpl/protocol/jss.h>
26
27namespace ripple {
28
29/*
30
31LedgerCleaner
32
33Cleans up the ledger. Specifically, resolves these issues:
34
351. Older versions could leave the SQLite account and transaction databases in
36 an inconsistent state. The cleaner identifies these inconsistencies and
37 resolves them.
38
392. Upon request, checks for missing nodes in a ledger and triggers a fetch.
40
41*/
42
44{
48
50
52
53 enum class State : char { notCleaning = 0, cleaning };
55 bool shouldExit_ = false;
56
57 // The lowest ledger in the range we're checking.
59
60 // The highest ledger in the range we're checking
62
63 // Check all state/transaction nodes
64 bool checkNodes_ = false;
65
66 // Rewrite SQL databases
67 bool fixTxns_ = false;
68
69 // Number of errors encountered since last success
70 int failures_ = 0;
71
72 //--------------------------------------------------------------------------
73public:
75 : app_(app), j_(journal)
76 {
77 }
78
80 {
81 if (thread_.joinable())
82 LogicError("LedgerCleanerImp::stop not called.");
83 }
84
85 void
86 start() override
87 {
89 }
90
91 void
92 stop() override
93 {
94 JLOG(j_.info()) << "Stopping";
95 {
97 shouldExit_ = true;
99 }
100 thread_.join();
101 }
102
103 //--------------------------------------------------------------------------
104 //
105 // PropertyStream
106 //
107 //--------------------------------------------------------------------------
108
109 void
111 {
113
114 if (maxRange_ == 0)
115 map["status"] = "idle";
116 else
117 {
118 map["status"] = "running";
119 map["min_ledger"] = minRange_;
120 map["max_ledger"] = maxRange_;
121 map["check_nodes"] = checkNodes_ ? "true" : "false";
122 map["fix_txns"] = fixTxns_ ? "true" : "false";
123 if (failures_ > 0)
124 map["fail_counts"] = failures_;
125 }
126 }
127
128 //--------------------------------------------------------------------------
129 //
130 // LedgerCleaner
131 //
132 //--------------------------------------------------------------------------
133
134 void
135 clean(Json::Value const& params) override
136 {
137 LedgerIndex minRange = 0;
138 LedgerIndex maxRange = 0;
139 app_.getLedgerMaster().getFullValidatedRange(minRange, maxRange);
140
141 {
143
144 maxRange_ = maxRange;
145 minRange_ = minRange;
146 checkNodes_ = false;
147 fixTxns_ = false;
148 failures_ = 0;
149
150 /*
151 JSON Parameters:
152
153 All parameters are optional. By default the cleaner cleans
154 things it thinks are necessary. This behavior can be modified
155 using the following options supplied via JSON RPC:
156
157 "ledger"
158 A single unsigned integer representing an individual
159 ledger to clean.
160
161 "min_ledger", "max_ledger"
162 Unsigned integers representing the starting and ending
163 ledger numbers to clean. If unspecified, clean all ledgers.
164
165 "full"
166 A boolean. When true, means clean everything possible.
167
168 "fix_txns"
169 A boolean value indicating whether or not to fix the
170 transactions in the database as well.
171
172 "check_nodes"
173 A boolean, when set to true means check the nodes.
174
175 "stop"
176 A boolean, when true informs the cleaner to gracefully
177 stop its current activities if any cleaning is taking place.
178 */
179
180 // Quick way to fix a single ledger
181 if (params.isMember(jss::ledger))
182 {
183 maxRange_ = params[jss::ledger].asUInt();
184 minRange_ = params[jss::ledger].asUInt();
185 fixTxns_ = true;
186 checkNodes_ = true;
187 }
188
189 if (params.isMember(jss::max_ledger))
190 maxRange_ = params[jss::max_ledger].asUInt();
191
192 if (params.isMember(jss::min_ledger))
193 minRange_ = params[jss::min_ledger].asUInt();
194
195 if (params.isMember(jss::full))
196 fixTxns_ = checkNodes_ = params[jss::full].asBool();
197
198 if (params.isMember(jss::fix_txns))
199 fixTxns_ = params[jss::fix_txns].asBool();
200
201 if (params.isMember(jss::check_nodes))
202 checkNodes_ = params[jss::check_nodes].asBool();
203
204 if (params.isMember(jss::stop) && params[jss::stop].asBool())
205 minRange_ = maxRange_ = 0;
206
209 }
210 }
211
212 //--------------------------------------------------------------------------
213 //
214 // LedgerCleanerImp
215 //
216 //--------------------------------------------------------------------------
217private:
218 void
220 {
221 beast::setCurrentThreadName("LedgerCleaner");
222 JLOG(j_.debug()) << "Started";
223
224 while (true)
225 {
226 {
229 wakeup_.wait(lock, [this]() {
230 return (shouldExit_ || state_ == State::cleaning);
231 });
232 if (shouldExit_)
233 break;
234 XRPL_ASSERT(
236 "ripple::LedgerCleanerImp::run : is cleaning");
237 }
239 }
240 }
241
242 // VFALCO TODO This should return std::optional<uint256>
245 {
247 try
248 {
249 hash = hashOfSeq(*ledger, index, j_);
250 }
251 catch (SHAMapMissingNode const& mn)
252 {
253 JLOG(j_.warn())
254 << "Ledger #" << ledger->info().seq << ": " << mn.what();
256 ledger->info().hash,
257 ledger->info().seq,
259 }
260 return hash ? *hash : beast::zero; // kludge
261 }
262
270 bool
272 LedgerIndex const& ledgerIndex,
273 LedgerHash const& ledgerHash,
274 bool doNodes,
275 bool doTxns)
276 {
277 auto nodeLedger = app_.getInboundLedgers().acquire(
278 ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
279 if (!nodeLedger)
280 {
281 JLOG(j_.debug()) << "Ledger " << ledgerIndex << " not available";
282 app_.getLedgerMaster().clearLedger(ledgerIndex);
284 ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
285 return false;
286 }
287
288 auto dbLedger = loadByIndex(ledgerIndex, app_);
289 if (!dbLedger || (dbLedger->info().hash != ledgerHash) ||
290 (dbLedger->info().parentHash != nodeLedger->info().parentHash))
291 {
292 // Ideally we'd also check for more than one ledger with that index
293 JLOG(j_.debug())
294 << "Ledger " << ledgerIndex << " mismatches SQL DB";
295 doTxns = true;
296 }
297
298 if (!app_.getLedgerMaster().fixIndex(ledgerIndex, ledgerHash))
299 {
300 JLOG(j_.debug())
301 << "ledger " << ledgerIndex << " had wrong entry in history";
302 doTxns = true;
303 }
304
305 if (doNodes && !nodeLedger->walkLedger(app_.journal("Ledger")))
306 {
307 JLOG(j_.debug()) << "Ledger " << ledgerIndex << " is missing nodes";
308 app_.getLedgerMaster().clearLedger(ledgerIndex);
310 ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
311 return false;
312 }
313
314 if (doTxns && !pendSaveValidated(app_, nodeLedger, true, false))
315 {
316 JLOG(j_.debug()) << "Failed to save ledger " << ledgerIndex;
317 return false;
318 }
319
320 return true;
321 }
322
330 LedgerIndex const& ledgerIndex,
331 std::shared_ptr<ReadView const>& referenceLedger)
332 {
333 LedgerHash ledgerHash;
334
335 if (!referenceLedger || (referenceLedger->info().seq < ledgerIndex))
336 {
337 referenceLedger = app_.getLedgerMaster().getValidatedLedger();
338 if (!referenceLedger)
339 {
340 JLOG(j_.warn()) << "No validated ledger";
341 return ledgerHash; // Nothing we can do. No validated ledger.
342 }
343 }
344
345 if (referenceLedger->info().seq >= ledgerIndex)
346 {
347 // See if the hash for the ledger we need is in the reference ledger
348 ledgerHash = getLedgerHash(referenceLedger, ledgerIndex);
349 if (ledgerHash.isZero())
350 {
351 // No. Try to get another ledger that might have the hash we
352 // need: compute the index and hash of a ledger that will have
353 // the hash we need.
354 LedgerIndex refIndex = getCandidateLedger(ledgerIndex);
355 LedgerHash refHash = getLedgerHash(referenceLedger, refIndex);
356
357 bool const nonzero(refHash.isNonZero());
358 XRPL_ASSERT(
359 nonzero,
360 "ripple::LedgerCleanerImp::getHash : nonzero hash");
361 if (nonzero)
362 {
363 // We found the hash and sequence of a better reference
364 // ledger.
365 referenceLedger = app_.getInboundLedgers().acquire(
366 refHash, refIndex, InboundLedger::Reason::GENERIC);
367 if (referenceLedger)
368 ledgerHash =
369 getLedgerHash(referenceLedger, ledgerIndex);
370 }
371 }
372 }
373 else
374 JLOG(j_.warn()) << "Validated ledger is prior to target ledger";
375
376 return ledgerHash;
377 }
378
380 void
382 {
383 auto shouldExit = [this] {
385 return shouldExit_;
386 };
387
389
390 while (!shouldExit())
391 {
392 LedgerIndex ledgerIndex;
393 LedgerHash ledgerHash;
394 bool doNodes;
395 bool doTxns;
396
398 {
399 JLOG(j_.debug()) << "Waiting for load to subside";
401 continue;
402 }
403
404 {
406 if ((minRange_ > maxRange_) || (maxRange_ == 0) ||
407 (minRange_ == 0))
408 {
409 minRange_ = maxRange_ = 0;
410 return;
411 }
412 ledgerIndex = maxRange_;
413 doNodes = checkNodes_;
414 doTxns = fixTxns_;
415 }
416
417 ledgerHash = getHash(ledgerIndex, goodLedger);
418
419 bool fail = false;
420 if (ledgerHash.isZero())
421 {
422 JLOG(j_.info())
423 << "Unable to get hash for ledger " << ledgerIndex;
424 fail = true;
425 }
426 else if (!doLedger(ledgerIndex, ledgerHash, doNodes, doTxns))
427 {
428 JLOG(j_.info()) << "Failed to process ledger " << ledgerIndex;
429 fail = true;
430 }
431
432 if (fail)
433 {
434 {
436 ++failures_;
437 }
438 // Wait for acquiring to catch up to us
440 }
441 else
442 {
443 {
445 if (ledgerIndex == minRange_)
446 ++minRange_;
447 if (ledgerIndex == maxRange_)
448 --maxRange_;
449 failures_ = 0;
450 }
451 // Reduce I/O pressure and wait for acquiring to catch up to us
453 }
454 }
455 }
456};
457
460{
461 return std::make_unique<LedgerCleanerImp>(app, journal);
462}
463
464} // namespace ripple
Represents a JSON value.
Definition: json_value.h:148
UInt asUInt() const
Definition: json_value.cpp:551
bool asBool() const
Definition: json_value.cpp:625
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:949
A generic endpoint for log messages.
Definition: Journal.h:60
Stream debug() const
Definition: Journal.h:328
Stream info() const
Definition: Journal.h:334
Stream warn() const
Definition: Journal.h:340
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.
Definition: LedgerCleaner.h:32
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
Definition: LoadFeeTrack.h:126
bool isZero() const
Definition: base_uint.h:540
bool isNonZero() const
Definition: base_uint.h:545
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:26
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:1112
LedgerIndex getCandidateLedger(LedgerIndex requested)
Find a ledger index from which we could easily get the requested ledger.
Definition: View.h:326
std::optional< uint256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition: View.cpp:835
void LogicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
Definition: contract.cpp:50
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:992
T sleep_for(T... args)
T what(T... args)