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
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);
284 app_.getInboundLedgers().acquire(
285 ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
286 return false;
287 }
288
289 Rules const rules{app_.config().features};
290 Fees const fees = app_.config().fees.toFees();
291 auto const dbLedger = loadByIndex(ledgerIndex, rules, fees, app_);
292 if (!dbLedger || (dbLedger->header().hash != ledgerHash) ||
293 (dbLedger->header().parentHash != nodeLedger->header().parentHash))
294 {
295 // Ideally we'd also check for more than one ledger with that index
296 JLOG(j_.debug()) << "Ledger " << ledgerIndex << " mismatches SQL DB";
297 doTxns = true;
298 }
299
300 if (!app_.getLedgerMaster().fixIndex(ledgerIndex, ledgerHash))
301 {
302 JLOG(j_.debug()) << "ledger " << ledgerIndex << " had wrong entry in history";
303 doTxns = true;
304 }
305
306 if (doNodes && !nodeLedger->walkLedger(app_.getJournal("Ledger")))
307 {
308 JLOG(j_.debug()) << "Ledger " << ledgerIndex << " is missing nodes";
309 app_.getLedgerMaster().clearLedger(ledgerIndex);
310 app_.getInboundLedgers().acquire(
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 getHash(LedgerIndex const& ledgerIndex, std::shared_ptr<ReadView const>& referenceLedger)
332 {
333 LedgerHash ledgerHash;
334
335 if (!referenceLedger || (referenceLedger->header().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->header().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 const refIndex = getCandidateLedger(ledgerIndex);
355 LedgerHash const refHash = getLedgerHash(referenceLedger, refIndex);
356
357 bool const nonzero(refHash.isNonZero());
358 XRPL_ASSERT(nonzero, "xrpl::LedgerCleanerImp::getHash : nonzero hash");
359 if (nonzero)
360 {
361 // We found the hash and sequence of a better reference
362 // ledger.
363 referenceLedger = app_.getInboundLedgers().acquire(
364 refHash, refIndex, InboundLedger::Reason::GENERIC);
365 if (referenceLedger)
366 ledgerHash = getLedgerHash(referenceLedger, ledgerIndex);
367 }
368 }
369 }
370 else
371 {
372 JLOG(j_.warn()) << "Validated ledger is prior to target ledger";
373 }
374
375 return ledgerHash;
376 }
377
381 void
383 {
384 auto shouldExit = [this] {
385 std::scoped_lock const lock(mutex_);
386 return shouldExit_;
387 };
388
390
391 while (!shouldExit())
392 {
393 LedgerIndex ledgerIndex = 0;
394 LedgerHash ledgerHash;
395 bool doNodes = false;
396 bool doTxns = false;
397
398 if (app_.getFeeTrack().isLoadedLocal())
399 {
400 JLOG(j_.debug()) << "Waiting for load to subside";
402 continue;
403 }
404
405 {
406 std::scoped_lock const lock(mutex_);
407 if ((minRange_ > maxRange_) || (maxRange_ == 0) || (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()) << "Unable to get hash for ledger " << ledgerIndex;
423 fail = true;
424 }
425 else if (!doLedger(ledgerIndex, ledgerHash, doNodes, doTxns))
426 {
427 JLOG(j_.info()) << "Failed to process ledger " << ledgerIndex;
428 fail = true;
429 }
430
431 if (fail)
432 {
433 {
434 std::scoped_lock const lock(mutex_);
435 ++failures_;
436 }
437 // Wait for acquiring to catch up to us
439 }
440 else
441 {
442 {
443 std::scoped_lock const lock(mutex_);
444 if (ledgerIndex == minRange_)
445 ++minRange_;
446 if (ledgerIndex == maxRange_)
447 --maxRange_;
448 failures_ = 0;
449 }
450 // Reduce I/O pressure and wait for acquiring to catch up to us
452 }
453 }
454 }
455};
456
459{
460 return std::make_unique<LedgerCleanerImp>(app, journal);
461}
462
463} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:44
Represents a JSON value.
Definition json_value.h:117
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:562
bool isNonZero() const
Definition base_uint.h:567
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:40
T make_unique(T... args)
void setCurrentThreadName(std::string_view newThreadName)
Changes the name of the caller thread.
constexpr Zero kZero
Definition Zero.h:30
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:370
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:124
std::optional< uint256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition View.cpp:279
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)