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
25#include <xrpl/beast/core/CurrentThreadName.h>
26#include <xrpl/protocol/jss.h>
27
28namespace ripple {
29
30/*
31
32LedgerCleaner
33
34Cleans up the ledger. Specifically, resolves these issues:
35
361. Older versions could leave the SQLite account and transaction databases in
37 an inconsistent state. The cleaner identifies these inconsistencies and
38 resolves them.
39
402. Upon request, checks for missing nodes in a ledger and triggers a fetch.
41
42*/
43
45{
49
51
53
54 enum class State : char { notCleaning = 0, cleaning };
56 bool shouldExit_ = false;
57
58 // The lowest ledger in the range we're checking.
60
61 // The highest ledger in the range we're checking
63
64 // Check all state/transaction nodes
65 bool checkNodes_ = false;
66
67 // Rewrite SQL databases
68 bool fixTxns_ = false;
69
70 // Number of errors encountered since last success
71 int failures_ = 0;
72
73 //--------------------------------------------------------------------------
74public:
76 : app_(app), j_(journal)
77 {
78 }
79
81 {
82 if (thread_.joinable())
83 LogicError("LedgerCleanerImp::stop not called.");
84 }
85
86 void
87 start() override
88 {
90 }
91
92 void
93 stop() override
94 {
95 JLOG(j_.info()) << "Stopping";
96 {
98 shouldExit_ = true;
100 }
101 thread_.join();
102 }
103
104 //--------------------------------------------------------------------------
105 //
106 // PropertyStream
107 //
108 //--------------------------------------------------------------------------
109
110 void
112 {
114
115 if (maxRange_ == 0)
116 map["status"] = "idle";
117 else
118 {
119 map["status"] = "running";
120 map["min_ledger"] = minRange_;
121 map["max_ledger"] = maxRange_;
122 map["check_nodes"] = checkNodes_ ? "true" : "false";
123 map["fix_txns"] = fixTxns_ ? "true" : "false";
124 if (failures_ > 0)
125 map["fail_counts"] = failures_;
126 }
127 }
128
129 //--------------------------------------------------------------------------
130 //
131 // LedgerCleaner
132 //
133 //--------------------------------------------------------------------------
134
135 void
136 clean(Json::Value const& params) override
137 {
138 LedgerIndex minRange = 0;
139 LedgerIndex maxRange = 0;
140 app_.getLedgerMaster().getFullValidatedRange(minRange, maxRange);
141
142 {
144
145 maxRange_ = maxRange;
146 minRange_ = minRange;
147 checkNodes_ = false;
148 fixTxns_ = false;
149 failures_ = 0;
150
151 /*
152 JSON Parameters:
153
154 All parameters are optional. By default the cleaner cleans
155 things it thinks are necessary. This behavior can be modified
156 using the following options supplied via JSON RPC:
157
158 "ledger"
159 A single unsigned integer representing an individual
160 ledger to clean.
161
162 "min_ledger", "max_ledger"
163 Unsigned integers representing the starting and ending
164 ledger numbers to clean. If unspecified, clean all ledgers.
165
166 "full"
167 A boolean. When true, means clean everything possible.
168
169 "fix_txns"
170 A boolean value indicating whether or not to fix the
171 transactions in the database as well.
172
173 "check_nodes"
174 A boolean, when set to true means check the nodes.
175
176 "stop"
177 A boolean, when true informs the cleaner to gracefully
178 stop its current activities if any cleaning is taking place.
179 */
180
181 // Quick way to fix a single ledger
182 if (params.isMember(jss::ledger))
183 {
184 maxRange_ = params[jss::ledger].asUInt();
185 minRange_ = params[jss::ledger].asUInt();
186 fixTxns_ = true;
187 checkNodes_ = true;
188 }
189
190 if (params.isMember(jss::max_ledger))
191 maxRange_ = params[jss::max_ledger].asUInt();
192
193 if (params.isMember(jss::min_ledger))
194 minRange_ = params[jss::min_ledger].asUInt();
195
196 if (params.isMember(jss::full))
197 fixTxns_ = checkNodes_ = params[jss::full].asBool();
198
199 if (params.isMember(jss::fix_txns))
200 fixTxns_ = params[jss::fix_txns].asBool();
201
202 if (params.isMember(jss::check_nodes))
203 checkNodes_ = params[jss::check_nodes].asBool();
204
205 if (params.isMember(jss::stop) && params[jss::stop].asBool())
206 minRange_ = maxRange_ = 0;
207
210 }
211 }
212
213 //--------------------------------------------------------------------------
214 //
215 // LedgerCleanerImp
216 //
217 //--------------------------------------------------------------------------
218private:
219 void
221 {
222 beast::setCurrentThreadName("LedgerCleaner");
223 JLOG(j_.debug()) << "Started";
224
225 while (true)
226 {
227 {
230 wakeup_.wait(lock, [this]() {
231 return (shouldExit_ || state_ == State::cleaning);
232 });
233 if (shouldExit_)
234 break;
235 XRPL_ASSERT(
237 "ripple::LedgerCleanerImp::run : is cleaning");
238 }
240 }
241 }
242
243 // VFALCO TODO This should return std::optional<uint256>
246 {
248 try
249 {
250 hash = hashOfSeq(*ledger, index, j_);
251 }
252 catch (SHAMapMissingNode const& mn)
253 {
254 JLOG(j_.warn())
255 << "Ledger #" << ledger->info().seq << ": " << mn.what();
257 ledger->info().hash,
258 ledger->info().seq,
260 }
261 return hash ? *hash : beast::zero; // kludge
262 }
263
271 bool
273 LedgerIndex const& ledgerIndex,
274 LedgerHash const& ledgerHash,
275 bool doNodes,
276 bool doTxns)
277 {
278 auto nodeLedger = app_.getInboundLedgers().acquire(
279 ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
280 if (!nodeLedger)
281 {
282 JLOG(j_.debug()) << "Ledger " << ledgerIndex << " not available";
283 app_.getLedgerMaster().clearLedger(ledgerIndex);
285 ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
286 return false;
287 }
288
289 auto dbLedger = loadByIndex(ledgerIndex, app_);
290 if (!dbLedger || (dbLedger->info().hash != ledgerHash) ||
291 (dbLedger->info().parentHash != nodeLedger->info().parentHash))
292 {
293 // Ideally we'd also check for more than one ledger with that index
294 JLOG(j_.debug())
295 << "Ledger " << ledgerIndex << " mismatches SQL DB";
296 doTxns = true;
297 }
298
299 if (!app_.getLedgerMaster().fixIndex(ledgerIndex, ledgerHash))
300 {
301 JLOG(j_.debug())
302 << "ledger " << ledgerIndex << " had wrong entry in history";
303 doTxns = true;
304 }
305
306 if (doNodes && !nodeLedger->walkLedger(app_.journal("Ledger")))
307 {
308 JLOG(j_.debug()) << "Ledger " << ledgerIndex << " is missing nodes";
309 app_.getLedgerMaster().clearLedger(ledgerIndex);
311 ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
312 return false;
313 }
314
315 if (doTxns && !pendSaveValidated(app_, nodeLedger, true, false))
316 {
317 JLOG(j_.debug()) << "Failed to save ledger " << ledgerIndex;
318 return false;
319 }
320
321 return true;
322 }
323
331 LedgerIndex const& ledgerIndex,
332 std::shared_ptr<ReadView const>& referenceLedger)
333 {
334 LedgerHash ledgerHash;
335
336 if (!referenceLedger || (referenceLedger->info().seq < ledgerIndex))
337 {
338 referenceLedger = app_.getLedgerMaster().getValidatedLedger();
339 if (!referenceLedger)
340 {
341 JLOG(j_.warn()) << "No validated ledger";
342 return ledgerHash; // Nothing we can do. No validated ledger.
343 }
344 }
345
346 if (referenceLedger->info().seq >= ledgerIndex)
347 {
348 // See if the hash for the ledger we need is in the reference ledger
349 ledgerHash = getLedgerHash(referenceLedger, ledgerIndex);
350 if (ledgerHash.isZero())
351 {
352 // No. Try to get another ledger that might have the hash we
353 // need: compute the index and hash of a ledger that will have
354 // the hash we need.
355 LedgerIndex refIndex = getCandidateLedger(ledgerIndex);
356 LedgerHash refHash = getLedgerHash(referenceLedger, refIndex);
357
358 bool const nonzero(refHash.isNonZero());
359 XRPL_ASSERT(
360 nonzero,
361 "ripple::LedgerCleanerImp::getHash : nonzero hash");
362 if (nonzero)
363 {
364 // We found the hash and sequence of a better reference
365 // ledger.
366 referenceLedger = app_.getInboundLedgers().acquire(
367 refHash, refIndex, InboundLedger::Reason::GENERIC);
368 if (referenceLedger)
369 ledgerHash =
370 getLedgerHash(referenceLedger, ledgerIndex);
371 }
372 }
373 }
374 else
375 JLOG(j_.warn()) << "Validated ledger is prior to target ledger";
376
377 return ledgerHash;
378 }
379
381 void
383 {
384 auto shouldExit = [this] {
386 return shouldExit_;
387 };
388
390
391 while (!shouldExit())
392 {
393 LedgerIndex ledgerIndex;
394 LedgerHash ledgerHash;
395 bool doNodes;
396 bool doTxns;
397
399 {
400 JLOG(j_.debug()) << "Waiting for load to subside";
402 continue;
403 }
404
405 {
407 if ((minRange_ > maxRange_) || (maxRange_ == 0) ||
408 (minRange_ == 0))
409 {
410 minRange_ = maxRange_ = 0;
411 return;
412 }
413 ledgerIndex = maxRange_;
414 doNodes = checkNodes_;
415 doTxns = fixTxns_;
416 }
417
418 ledgerHash = getHash(ledgerIndex, goodLedger);
419
420 bool fail = false;
421 if (ledgerHash.isZero())
422 {
423 JLOG(j_.info())
424 << "Unable to get hash for ledger " << ledgerIndex;
425 fail = true;
426 }
427 else if (!doLedger(ledgerIndex, ledgerHash, doNodes, doTxns))
428 {
429 JLOG(j_.info()) << "Failed to process ledger " << ledgerIndex;
430 fail = true;
431 }
432
433 if (fail)
434 {
435 {
437 ++failures_;
438 }
439 // Wait for acquiring to catch up to us
441 }
442 else
443 {
444 {
446 if (ledgerIndex == minRange_)
447 ++minRange_;
448 if (ledgerIndex == maxRange_)
449 --maxRange_;
450 failures_ = 0;
451 }
452 // Reduce I/O pressure and wait for acquiring to catch up to us
454 }
455 }
456 }
457};
458
461{
462 return std::make_unique<LedgerCleanerImp>(app, journal);
463}
464
465} // namespace ripple
Represents a JSON value.
Definition json_value.h:149
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: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.
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:540
bool isNonZero() const
Definition base_uint.h:545
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:25
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:1118
LedgerIndex getCandidateLedger(LedgerIndex requested)
Find a ledger index from which we could easily get the requested ledger.
Definition View.h:429
std::optional< uint256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition View.cpp:961
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:997
T sleep_for(T... args)
T what(T... args)