xrpld
Loading...
Searching...
No Matches
app/ledger/detail/LedgerCleaner.cpp
1#include <xrpld/app/ledger/LedgerCleaner.h>
2
3#include <xrpld/app/ledger/InboundLedger.h>
4#include <xrpld/app/ledger/InboundLedgers.h>
5#include <xrpld/app/ledger/LedgerMaster.h>
6#include <xrpld/app/ledger/LedgerPersistence.h>
7#include <xrpld/app/main/Application.h>
8
9#include <xrpl/basics/Log.h>
10#include <xrpl/basics/contract.h>
11#include <xrpl/beast/core/CurrentThreadName.h>
12#include <xrpl/beast/utility/Journal.h>
13#include <xrpl/beast/utility/PropertyStream.h>
14#include <xrpl/beast/utility/Zero.h>
15#include <xrpl/beast/utility/instrumentation.h>
16#include <xrpl/json/json_value.h>
17#include <xrpl/ledger/ReadView.h>
18#include <xrpl/ledger/View.h>
19#include <xrpl/protocol/Protocol.h>
20#include <xrpl/protocol/RippleLedgerHash.h>
21#include <xrpl/protocol/Rules.h>
22#include <xrpl/protocol/jss.h>
23#include <xrpl/server/LoadFeeTrack.h>
24#include <xrpl/shamap/SHAMapMissingNode.h>
25
26#include <chrono>
27#include <condition_variable>
28#include <memory>
29#include <mutex>
30#include <optional>
31#include <thread>
32
33namespace xrpl {
34
35/*
36
37LedgerCleaner
38
39Cleans up the ledger. Specifically, resolves these issues:
40
411. Older versions could leave the SQLite account and transaction databases in
42 an inconsistent state. The cleaner identifies these inconsistencies and
43 resolves them.
44
452. Upon request, checks for missing nodes in a ledger and triggers a fetch.
46
47*/
48
50{
54
56
58
59 enum class State : char { NotCleaning = 0, Cleaning };
61 bool shouldExit_ = false;
62
63 // The lowest ledger in the range we're checking.
65
66 // The highest ledger in the range we're checking
68
69 // Check all state/transaction nodes
70 bool checkNodes_ = false;
71
72 // Rewrite SQL databases
73 bool fixTxns_ = false;
74
75 // Number of errors encountered since last success
76 int failures_ = 0;
77
78 //--------------------------------------------------------------------------
79public:
80 LedgerCleanerImp(Application& app, beast::Journal journal) : app_(app), j_(journal)
81 {
82 }
83
85 {
86 if (thread_.joinable())
87 logicError("LedgerCleanerImp::stop not called.");
88 }
89
90 void
91 start() override
92 {
94 }
95
96 void
97 stop() override
98 {
99 JLOG(j_.info()) << "Stopping";
100 {
101 std::scoped_lock const lock(mutex_);
102 shouldExit_ = true;
103 wakeup_.notify_one();
104 }
105 thread_.join();
106 }
107
108 //--------------------------------------------------------------------------
109 //
110 // PropertyStream
111 //
112 //--------------------------------------------------------------------------
113
114 void
116 {
117 std::scoped_lock const lock(mutex_);
118
119 if (maxRange_ == 0)
120 {
121 map["status"] = "idle";
122 }
123 else
124 {
125 map["status"] = "running";
126 map["min_ledger"] = minRange_;
127 map["max_ledger"] = maxRange_;
128 map["check_nodes"] = checkNodes_ ? "true" : "false";
129 map["fix_txns"] = fixTxns_ ? "true" : "false";
130 if (failures_ > 0)
131 map["fail_counts"] = failures_;
132 }
133 }
134
135 //--------------------------------------------------------------------------
136 //
137 // LedgerCleaner
138 //
139 //--------------------------------------------------------------------------
140
141 void
142 clean(json::Value const& params) override
143 {
144 LedgerIndex minRange = 0;
145 LedgerIndex maxRange = 0;
146 app_.getLedgerMaster().getFullValidatedRange(minRange, maxRange);
147
148 {
149 std::scoped_lock const lock(mutex_);
150
151 maxRange_ = maxRange;
152 minRange_ = minRange;
153 checkNodes_ = false;
154 fixTxns_ = false;
155 failures_ = 0;
156
157 /*
158 JSON Parameters:
159
160 All parameters are optional. By default the cleaner cleans
161 things it thinks are necessary. This behavior can be modified
162 using the following options supplied via JSON RPC:
163
164 "ledger"
165 A single unsigned integer representing an individual
166 ledger to clean.
167
168 "min_ledger", "max_ledger"
169 Unsigned integers representing the starting and ending
170 ledger numbers to clean. If unspecified, clean all ledgers.
171
172 "full"
173 A boolean. When true, means clean everything possible.
174
175 "fix_txns"
176 A boolean value indicating whether or not to fix the
177 transactions in the database as well.
178
179 "check_nodes"
180 A boolean, when set to true means check the nodes.
181
182 "stop"
183 A boolean, when true informs the cleaner to gracefully
184 stop its current activities if any cleaning is taking place.
185 */
186
187 // Quick way to fix a single ledger
188 if (params.isMember(jss::ledger))
189 {
190 maxRange_ = params[jss::ledger].asUInt();
191 minRange_ = params[jss::ledger].asUInt();
192 fixTxns_ = true;
193 checkNodes_ = true;
194 }
195
196 if (params.isMember(jss::max_ledger))
197 maxRange_ = params[jss::max_ledger].asUInt();
198
199 if (params.isMember(jss::min_ledger))
200 minRange_ = params[jss::min_ledger].asUInt();
201
202 if (params.isMember(jss::full))
203 fixTxns_ = checkNodes_ = params[jss::full].asBool();
204
205 if (params.isMember(jss::fix_txns))
206 fixTxns_ = params[jss::fix_txns].asBool();
207
208 if (params.isMember(jss::check_nodes))
209 checkNodes_ = params[jss::check_nodes].asBool();
210
211 if (params.isMember(jss::stop) && params[jss::stop].asBool())
212 minRange_ = maxRange_ = 0;
213
215 wakeup_.notify_one();
216 }
217 }
218
219 //--------------------------------------------------------------------------
220 //
221 // LedgerCleanerImp
222 //
223 //--------------------------------------------------------------------------
224private:
225 void
227 {
228 beast::setCurrentThreadName("LedgerCleaner");
229 JLOG(j_.debug()) << "Started";
230
231 while (true)
232 {
233 {
236 wakeup_.wait(lock, [this]() { return (shouldExit_ || state_ == State::Cleaning); });
237 if (shouldExit_)
238 break;
239 XRPL_ASSERT(state_ == State::Cleaning, "xrpl::LedgerCleanerImp::run : is cleaning");
240 }
242 }
243 }
244
245 // VFALCO TODO This should return std::optional<uint256>
248 {
250 try
251 {
252 hash = hashOfSeq(*ledger, index, j_);
253 }
254 catch (SHAMapMissingNode const& mn)
255 {
256 JLOG(j_.warn()) << "Ledger #" << ledger->header().seq << ": " << mn.what();
257 app_.getInboundLedgers().acquire(
258 ledger->header().hash, ledger->header().seq, InboundLedger::Reason::GENERIC);
259 }
260 return hash ? *hash : beast::kZero; // 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);
283 app_.getInboundLedgers().acquire(
284 ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
285 return false;
286 }
287
288 Rules const rules{app_.config().features};
289 Fees const fees = app_.config().fees.toFees();
290 auto const dbLedger = loadByIndex(ledgerIndex, rules, fees, app_);
291 if (!dbLedger || (dbLedger->header().hash != ledgerHash) ||
292 (dbLedger->header().parentHash != nodeLedger->header().parentHash))
293 {
294 // Ideally we'd also check for more than one ledger with that index
295 JLOG(j_.debug()) << "Ledger " << ledgerIndex << " mismatches SQL DB";
296 doTxns = true;
297 }
298
299 if (!app_.getLedgerMaster().fixIndex(ledgerIndex, ledgerHash))
300 {
301 JLOG(j_.debug()) << "ledger " << ledgerIndex << " had wrong entry in history";
302 doTxns = true;
303 }
304
305 if (doNodes && !nodeLedger->walkLedger(app_.getJournal("Ledger")))
306 {
307 JLOG(j_.debug()) << "Ledger " << ledgerIndex << " is missing nodes";
308 app_.getLedgerMaster().clearLedger(ledgerIndex);
309 app_.getInboundLedgers().acquire(
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
329 getHash(LedgerIndex const& ledgerIndex, std::shared_ptr<ReadView const>& referenceLedger)
330 {
331 LedgerHash ledgerHash;
332
333 if (!referenceLedger || (referenceLedger->header().seq < ledgerIndex))
334 {
335 referenceLedger = app_.getLedgerMaster().getValidatedLedger();
336 if (!referenceLedger)
337 {
338 JLOG(j_.warn()) << "No validated ledger";
339 return ledgerHash; // Nothing we can do. No validated ledger.
340 }
341 }
342
343 if (referenceLedger->header().seq >= ledgerIndex)
344 {
345 // See if the hash for the ledger we need is in the reference ledger
346 ledgerHash = getLedgerHash(referenceLedger, ledgerIndex);
347 if (ledgerHash.isZero())
348 {
349 // No. Try to get another ledger that might have the hash we
350 // need: compute the index and hash of a ledger that will have
351 // the hash we need.
352 LedgerIndex const refIndex = getCandidateLedger(ledgerIndex);
353 LedgerHash const refHash = getLedgerHash(referenceLedger, refIndex);
354
355 bool const nonzero(refHash.isNonZero());
356 XRPL_ASSERT(nonzero, "xrpl::LedgerCleanerImp::getHash : nonzero hash");
357 if (nonzero)
358 {
359 // We found the hash and sequence of a better reference
360 // ledger.
361 referenceLedger = app_.getInboundLedgers().acquire(
362 refHash, refIndex, InboundLedger::Reason::GENERIC);
363 if (referenceLedger)
364 ledgerHash = getLedgerHash(referenceLedger, ledgerIndex);
365 }
366 }
367 }
368 else
369 {
370 JLOG(j_.warn()) << "Validated ledger is prior to target ledger";
371 }
372
373 return ledgerHash;
374 }
375
377 void
379 {
380 auto shouldExit = [this] {
381 std::scoped_lock const lock(mutex_);
382 return shouldExit_;
383 };
384
386
387 while (!shouldExit())
388 {
389 LedgerIndex ledgerIndex = 0;
390 LedgerHash ledgerHash;
391 bool doNodes = false;
392 bool doTxns = false;
393
394 if (app_.getFeeTrack().isLoadedLocal())
395 {
396 JLOG(j_.debug()) << "Waiting for load to subside";
398 continue;
399 }
400
401 {
402 std::scoped_lock const lock(mutex_);
403 if ((minRange_ > maxRange_) || (maxRange_ == 0) || (minRange_ == 0))
404 {
405 minRange_ = maxRange_ = 0;
406 return;
407 }
408 ledgerIndex = maxRange_;
409 doNodes = checkNodes_;
410 doTxns = fixTxns_;
411 }
412
413 ledgerHash = getHash(ledgerIndex, goodLedger);
414
415 bool fail = false;
416 if (ledgerHash.isZero())
417 {
418 JLOG(j_.info()) << "Unable to get hash for ledger " << ledgerIndex;
419 fail = true;
420 }
421 else if (!doLedger(ledgerIndex, ledgerHash, doNodes, doTxns))
422 {
423 JLOG(j_.info()) << "Failed to process ledger " << ledgerIndex;
424 fail = true;
425 }
426
427 if (fail)
428 {
429 {
430 std::scoped_lock const lock(mutex_);
431 ++failures_;
432 }
433 // Wait for acquiring to catch up to us
435 }
436 else
437 {
438 {
439 std::scoped_lock const lock(mutex_);
440 if (ledgerIndex == minRange_)
441 ++minRange_;
442 if (ledgerIndex == maxRange_)
443 --maxRange_;
444 failures_ = 0;
445 }
446 // Reduce I/O pressure and wait for acquiring to catch up to us
448 }
449 }
450 }
451};
452
455{
456 return std::make_unique<LedgerCleanerImp>(app, journal);
457}
458
459} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:38
Represents a JSON value.
Definition json_value.h:130
bool asBool() const
UInt asUInt() const
bool isMember(char const *key) const
Return true if the object has a member named key.
bool isZero() const
Definition base_uint.h:544
bool isNonZero() const
Definition base_uint.h:549
LedgerHash getHash(LedgerIndex const &ledgerIndex, std::shared_ptr< ReadView const > &referenceLedger)
Returns the hash of the specified ledger.
void clean(json::Value const &params) override
Start a long running task to clean the ledger.
void doLedgerCleaner()
Run the ledger cleaner.
void onWrite(beast::PropertyStream::Map &map) override
Subclass override.
bool doLedger(LedgerIndex const &ledgerIndex, LedgerHash const &ledgerHash, bool doNodes, bool doTxns)
Process a single ledger.
LedgerCleanerImp(Application &app, beast::Journal journal)
LedgerHash getLedgerHash(std::shared_ptr< ReadView const > &ledger, LedgerIndex index)
Rules controlling protocol behavior.
Definition Rules.h:33
T make_unique(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:5
bool pendSaveValidated(ServiceRegistry &registry, std::shared_ptr< Ledger const > const &ledger, bool isSynchronous, bool isCurrent)
Save, or arrange to save, a fully-validated ledger.
std::uint32_t LedgerIndex
A ledger index.
Definition Protocol.h:259
std::shared_ptr< Ledger > loadByIndex(std::uint32_t ledgerIndex, Rules const &rules, Fees const &fees, ServiceRegistry &registry, bool acquire)
Load a ledger by its sequence number.
void logicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
LedgerIndex getCandidateLedger(LedgerIndex requested)
Find a ledger index from which we could easily get the requested ledger.
Definition View.h:102
std::optional< uint256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition View.cpp:270
std::unique_ptr< LedgerCleaner > makeLedgerCleaner(Application &app, beast::Journal journal)
uint256 LedgerHash
T sleep_for(T... args)
Reflects the fee settings for a particular ledger.
T what(T... args)