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 xrpl {
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 "xrpl::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->header().seq << ": " << mn.what();
238 ledger->header().hash,
239 ledger->header().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->header().hash != ledgerHash) ||
272 (dbLedger->header().parentHash != nodeLedger->header().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->header().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->header().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, "xrpl::LedgerCleanerImp::getHash : nonzero hash");
342 if (nonzero)
343 {
344 // We found the hash and sequence of a better reference
345 // ledger.
346 referenceLedger = app_.getInboundLedgers().acquire(
347 refHash, refIndex, InboundLedger::Reason::GENERIC);
348 if (referenceLedger)
349 ledgerHash =
350 getLedgerHash(referenceLedger, ledgerIndex);
351 }
352 }
353 }
354 else
355 JLOG(j_.warn()) << "Validated ledger is prior to target ledger";
356
357 return ledgerHash;
358 }
359
361 void
363 {
364 auto shouldExit = [this] {
366 return shouldExit_;
367 };
368
370
371 while (!shouldExit())
372 {
373 LedgerIndex ledgerIndex;
374 LedgerHash ledgerHash;
375 bool doNodes;
376 bool doTxns;
377
379 {
380 JLOG(j_.debug()) << "Waiting for load to subside";
382 continue;
383 }
384
385 {
387 if ((minRange_ > maxRange_) || (maxRange_ == 0) ||
388 (minRange_ == 0))
389 {
390 minRange_ = maxRange_ = 0;
391 return;
392 }
393 ledgerIndex = maxRange_;
394 doNodes = checkNodes_;
395 doTxns = fixTxns_;
396 }
397
398 ledgerHash = getHash(ledgerIndex, goodLedger);
399
400 bool fail = false;
401 if (ledgerHash.isZero())
402 {
403 JLOG(j_.info())
404 << "Unable to get hash for ledger " << ledgerIndex;
405 fail = true;
406 }
407 else if (!doLedger(ledgerIndex, ledgerHash, doNodes, doTxns))
408 {
409 JLOG(j_.info()) << "Failed to process ledger " << ledgerIndex;
410 fail = true;
411 }
412
413 if (fail)
414 {
415 {
417 ++failures_;
418 }
419 // Wait for acquiring to catch up to us
421 }
422 else
423 {
424 {
426 if (ledgerIndex == minRange_)
427 ++minRange_;
428 if (ledgerIndex == maxRange_)
429 --maxRange_;
430 failures_ = 0;
431 }
432 // Reduce I/O pressure and wait for acquiring to catch up to us
434 }
435 }
436 }
437};
438
441{
442 return std::make_unique<LedgerCleanerImp>(app, journal);
443}
444
445} // namespace xrpl
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 InboundLedgers & getInboundLedgers()=0
virtual LoadFeeTrack & getFeeTrack()=0
virtual LedgerMaster & getLedgerMaster()=0
virtual beast::Journal journal(std::string const &name)=0
virtual std::shared_ptr< Ledger const > acquire(uint256 const &hash, std::uint32_t seq, InboundLedger::Reason)=0
std::condition_variable wakeup_
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.
beast::Journal const j_
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)
Check the ledger/transaction databases to make sure they have continuity.
bool fixIndex(LedgerIndex ledgerIndex, LedgerHash const &ledgerHash)
void clearLedger(std::uint32_t seq)
std::shared_ptr< Ledger const > getValidatedLedger()
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
void LogicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
std::unique_ptr< LedgerCleaner > make_LedgerCleaner(Application &app, beast::Journal journal)
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:981
LedgerIndex getCandidateLedger(LedgerIndex requested)
Find a ledger index from which we could easily get the requested ledger.
Definition View.h:528
std::shared_ptr< Ledger > loadByIndex(std::uint32_t ledgerIndex, Application &app, bool acquire)
Definition Ledger.cpp:1102
std::optional< uint256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition View.cpp:1063
T sleep_for(T... args)
T what(T... args)