rippled
Loading...
Searching...
No Matches
app/ledger/detail/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/ledger/LedgerPersistence.h>
5#include <xrpld/app/main/Application.h>
6
7#include <xrpl/beast/core/CurrentThreadName.h>
8#include <xrpl/protocol/jss.h>
9#include <xrpl/server/LoadFeeTrack.h>
10
11namespace xrpl {
12
13/*
14
15LedgerCleaner
16
17Cleans up the ledger. Specifically, resolves these issues:
18
191. Older versions could leave the SQLite account and transaction databases in
20 an inconsistent state. The cleaner identifies these inconsistencies and
21 resolves them.
22
232. Upon request, checks for missing nodes in a ledger and triggers a fetch.
24
25*/
26
28{
32
34
36
37 enum class State : char { notCleaning = 0, cleaning };
39 bool shouldExit_ = false;
40
41 // The lowest ledger in the range we're checking.
43
44 // The highest ledger in the range we're checking
46
47 // Check all state/transaction nodes
48 bool checkNodes_ = false;
49
50 // Rewrite SQL databases
51 bool fixTxns_ = false;
52
53 // Number of errors encountered since last success
54 int failures_ = 0;
55
56 //--------------------------------------------------------------------------
57public:
58 LedgerCleanerImp(Application& app, beast::Journal journal) : app_(app), j_(journal)
59 {
60 }
61
63 {
64 if (thread_.joinable())
65 LogicError("LedgerCleanerImp::stop not called.");
66 }
67
68 void
69 start() override
70 {
72 }
73
74 void
75 stop() override
76 {
77 JLOG(j_.info()) << "Stopping";
78 {
79 std::lock_guard const lock(mutex_);
80 shouldExit_ = true;
82 }
83 thread_.join();
84 }
85
86 //--------------------------------------------------------------------------
87 //
88 // PropertyStream
89 //
90 //--------------------------------------------------------------------------
91
92 void
94 {
95 std::lock_guard const lock(mutex_);
96
97 if (maxRange_ == 0)
98 {
99 map["status"] = "idle";
100 }
101 else
102 {
103 map["status"] = "running";
104 map["min_ledger"] = minRange_;
105 map["max_ledger"] = maxRange_;
106 map["check_nodes"] = checkNodes_ ? "true" : "false";
107 map["fix_txns"] = fixTxns_ ? "true" : "false";
108 if (failures_ > 0)
109 map["fail_counts"] = failures_;
110 }
111 }
112
113 //--------------------------------------------------------------------------
114 //
115 // LedgerCleaner
116 //
117 //--------------------------------------------------------------------------
118
119 void
120 clean(Json::Value const& params) override
121 {
122 LedgerIndex minRange = 0;
123 LedgerIndex maxRange = 0;
124 app_.getLedgerMaster().getFullValidatedRange(minRange, maxRange);
125
126 {
127 std::lock_guard const lock(mutex_);
128
129 maxRange_ = maxRange;
130 minRange_ = minRange;
131 checkNodes_ = false;
132 fixTxns_ = false;
133 failures_ = 0;
134
135 /*
136 JSON Parameters:
137
138 All parameters are optional. By default the cleaner cleans
139 things it thinks are necessary. This behavior can be modified
140 using the following options supplied via JSON RPC:
141
142 "ledger"
143 A single unsigned integer representing an individual
144 ledger to clean.
145
146 "min_ledger", "max_ledger"
147 Unsigned integers representing the starting and ending
148 ledger numbers to clean. If unspecified, clean all ledgers.
149
150 "full"
151 A boolean. When true, means clean everything possible.
152
153 "fix_txns"
154 A boolean value indicating whether or not to fix the
155 transactions in the database as well.
156
157 "check_nodes"
158 A boolean, when set to true means check the nodes.
159
160 "stop"
161 A boolean, when true informs the cleaner to gracefully
162 stop its current activities if any cleaning is taking place.
163 */
164
165 // Quick way to fix a single ledger
166 if (params.isMember(jss::ledger))
167 {
168 maxRange_ = params[jss::ledger].asUInt();
169 minRange_ = params[jss::ledger].asUInt();
170 fixTxns_ = true;
171 checkNodes_ = true;
172 }
173
174 if (params.isMember(jss::max_ledger))
175 maxRange_ = params[jss::max_ledger].asUInt();
176
177 if (params.isMember(jss::min_ledger))
178 minRange_ = params[jss::min_ledger].asUInt();
179
180 if (params.isMember(jss::full))
181 fixTxns_ = checkNodes_ = params[jss::full].asBool();
182
183 if (params.isMember(jss::fix_txns))
184 fixTxns_ = params[jss::fix_txns].asBool();
185
186 if (params.isMember(jss::check_nodes))
187 checkNodes_ = params[jss::check_nodes].asBool();
188
189 if (params.isMember(jss::stop) && params[jss::stop].asBool())
190 minRange_ = maxRange_ = 0;
191
194 }
195 }
196
197 //--------------------------------------------------------------------------
198 //
199 // LedgerCleanerImp
200 //
201 //--------------------------------------------------------------------------
202private:
203 void
205 {
206 beast::setCurrentThreadName("LedgerCleaner");
207 JLOG(j_.debug()) << "Started";
208
209 while (true)
210 {
211 {
214 wakeup_.wait(lock, [this]() { return (shouldExit_ || state_ == State::cleaning); });
215 if (shouldExit_)
216 break;
217 XRPL_ASSERT(state_ == State::cleaning, "xrpl::LedgerCleanerImp::run : is cleaning");
218 }
220 }
221 }
222
223 // VFALCO TODO This should return std::optional<uint256>
226 {
228 try
229 {
230 hash = hashOfSeq(*ledger, index, j_);
231 }
232 catch (SHAMapMissingNode const& mn)
233 {
234 JLOG(j_.warn()) << "Ledger #" << ledger->header().seq << ": " << mn.what();
236 ledger->header().hash, ledger->header().seq, InboundLedger::Reason::GENERIC);
237 }
238 return hash ? *hash : beast::zero; // kludge
239 }
240
248 bool
250 LedgerIndex const& ledgerIndex,
251 LedgerHash const& ledgerHash,
252 bool doNodes,
253 bool doTxns)
254 {
255 auto nodeLedger = app_.getInboundLedgers().acquire(
256 ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
257 if (!nodeLedger)
258 {
259 JLOG(j_.debug()) << "Ledger " << ledgerIndex << " not available";
260 app_.getLedgerMaster().clearLedger(ledgerIndex);
262 ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
263 return false;
264 }
265
266 Rules const rules{app_.config().features};
267 Fees const fees = app_.config().FEES.toFees();
268 auto const dbLedger = loadByIndex(ledgerIndex, rules, fees, app_);
269 if (!dbLedger || (dbLedger->header().hash != ledgerHash) ||
270 (dbLedger->header().parentHash != nodeLedger->header().parentHash))
271 {
272 // Ideally we'd also check for more than one ledger with that index
273 JLOG(j_.debug()) << "Ledger " << ledgerIndex << " mismatches SQL DB";
274 doTxns = true;
275 }
276
277 if (!app_.getLedgerMaster().fixIndex(ledgerIndex, ledgerHash))
278 {
279 JLOG(j_.debug()) << "ledger " << ledgerIndex << " had wrong entry in history";
280 doTxns = true;
281 }
282
283 if (doNodes && !nodeLedger->walkLedger(app_.getJournal("Ledger")))
284 {
285 JLOG(j_.debug()) << "Ledger " << ledgerIndex << " is missing nodes";
286 app_.getLedgerMaster().clearLedger(ledgerIndex);
288 ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC);
289 return false;
290 }
291
292 if (doTxns && !pendSaveValidated(app_, nodeLedger, true, false))
293 {
294 JLOG(j_.debug()) << "Failed to save ledger " << ledgerIndex;
295 return false;
296 }
297
298 return true;
299 }
300
307 getHash(LedgerIndex const& ledgerIndex, std::shared_ptr<ReadView const>& referenceLedger)
308 {
309 LedgerHash ledgerHash;
310
311 if (!referenceLedger || (referenceLedger->header().seq < ledgerIndex))
312 {
313 referenceLedger = app_.getLedgerMaster().getValidatedLedger();
314 if (!referenceLedger)
315 {
316 JLOG(j_.warn()) << "No validated ledger";
317 return ledgerHash; // Nothing we can do. No validated ledger.
318 }
319 }
320
321 if (referenceLedger->header().seq >= ledgerIndex)
322 {
323 // See if the hash for the ledger we need is in the reference ledger
324 ledgerHash = getLedgerHash(referenceLedger, ledgerIndex);
325 if (ledgerHash.isZero())
326 {
327 // No. Try to get another ledger that might have the hash we
328 // need: compute the index and hash of a ledger that will have
329 // the hash we need.
330 LedgerIndex const refIndex = getCandidateLedger(ledgerIndex);
331 LedgerHash const refHash = getLedgerHash(referenceLedger, refIndex);
332
333 bool const nonzero(refHash.isNonZero());
334 XRPL_ASSERT(nonzero, "xrpl::LedgerCleanerImp::getHash : nonzero hash");
335 if (nonzero)
336 {
337 // We found the hash and sequence of a better reference
338 // ledger.
339 referenceLedger = app_.getInboundLedgers().acquire(
340 refHash, refIndex, InboundLedger::Reason::GENERIC);
341 if (referenceLedger)
342 ledgerHash = getLedgerHash(referenceLedger, ledgerIndex);
343 }
344 }
345 }
346 else
347 JLOG(j_.warn()) << "Validated ledger is prior to target ledger";
348
349 return ledgerHash;
350 }
351
353 void
355 {
356 auto shouldExit = [this] {
357 std::lock_guard const lock(mutex_);
358 return shouldExit_;
359 };
360
362
363 while (!shouldExit())
364 {
365 LedgerIndex ledgerIndex = 0;
366 LedgerHash ledgerHash;
367 bool doNodes = false;
368 bool doTxns = false;
369
371 {
372 JLOG(j_.debug()) << "Waiting for load to subside";
374 continue;
375 }
376
377 {
378 std::lock_guard const lock(mutex_);
379 if ((minRange_ > maxRange_) || (maxRange_ == 0) || (minRange_ == 0))
380 {
381 minRange_ = maxRange_ = 0;
382 return;
383 }
384 ledgerIndex = maxRange_;
385 doNodes = checkNodes_;
386 doTxns = fixTxns_;
387 }
388
389 ledgerHash = getHash(ledgerIndex, goodLedger);
390
391 bool fail = false;
392 if (ledgerHash.isZero())
393 {
394 JLOG(j_.info()) << "Unable to get hash for ledger " << ledgerIndex;
395 fail = true;
396 }
397 else if (!doLedger(ledgerIndex, ledgerHash, doNodes, doTxns))
398 {
399 JLOG(j_.info()) << "Failed to process ledger " << ledgerIndex;
400 fail = true;
401 }
402
403 if (fail)
404 {
405 {
406 std::lock_guard const lock(mutex_);
407 ++failures_;
408 }
409 // Wait for acquiring to catch up to us
411 }
412 else
413 {
414 {
415 std::lock_guard const lock(mutex_);
416 if (ledgerIndex == minRange_)
417 ++minRange_;
418 if (ledgerIndex == maxRange_)
419 --maxRange_;
420 failures_ = 0;
421 }
422 // Reduce I/O pressure and wait for acquiring to catch up to us
424 }
425 }
426 }
427};
428
431{
432 return std::make_unique<LedgerCleanerImp>(app, journal);
433}
434
435} // namespace xrpl
Represents a JSON value.
Definition json_value.h:130
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:40
Stream debug() const
Definition Journal.h:301
Stream info() const
Definition Journal.h:307
Stream warn() const
Definition Journal.h:313
virtual Config & config()=0
std::unordered_set< uint256, beast::uhash<> > features
Definition Config.h:261
FeeSetup FEES
Definition Config.h:189
virtual std::shared_ptr< Ledger const > acquire(uint256 const &hash, std::uint32_t seq, InboundLedger::Reason)=0
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)
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
Rules controlling protocol behavior.
Definition Rules.h:18
virtual beast::Journal getJournal(std::string const &name)=0
virtual LoadFeeTrack & getFeeTrack()=0
virtual InboundLedgers & getInboundLedgers()=0
virtual LedgerMaster & getLedgerMaster()=0
bool isZero() const
Definition base_uint.h:513
bool isNonZero() const
Definition base_uint.h:518
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:5
void LogicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
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::unique_ptr< LedgerCleaner > make_LedgerCleaner(Application &app, beast::Journal journal)
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.
LedgerIndex getCandidateLedger(LedgerIndex requested)
Find a ledger index from which we could easily get the requested ledger.
Definition View.h:103
std::optional< uint256 > hashOfSeq(ReadView const &ledger, LedgerIndex seq, beast::Journal journal)
Return the hash of a ledger by sequence.
Definition View.cpp:232
T sleep_for(T... args)
Fees toFees() const
Convert to a Fees object for use with Ledger construction.
Definition Config.h:64
Reflects the fee settings for a particular ledger.
T what(T... args)