xrpld
Loading...
Searching...
No Matches
SHAMapStoreImp.cpp
1#include <xrpld/app/misc/SHAMapStoreImp.h>
2
3#include <xrpld/app/ledger/TransactionMaster.h>
4#include <xrpld/app/misc/SHAMapStore.h>
5#include <xrpld/app/rdb/backend/SQLiteDatabase.h>
6#include <xrpld/core/Config.h>
7
8#include <xrpl/basics/ByteUtilities.h>
9#include <xrpl/basics/FileUtilities.h>
10#include <xrpl/basics/Log.h>
11#include <xrpl/basics/contract.h>
12#include <xrpl/basics/scope.h>
13#include <xrpl/beast/core/CurrentThreadName.h>
14#include <xrpl/beast/utility/Journal.h>
15#include <xrpl/beast/utility/instrumentation.h>
16#include <xrpl/config/BasicConfig.h>
17#include <xrpl/config/Constants.h>
18#include <xrpl/ledger/Ledger.h>
19#include <xrpl/nodestore/Database.h>
20#include <xrpl/nodestore/Manager.h>
21#include <xrpl/nodestore/NodeObject.h>
22#include <xrpl/nodestore/Scheduler.h>
23#include <xrpl/nodestore/detail/DatabaseRotatingImp.h>
24#include <xrpl/protocol/Protocol.h>
25#include <xrpl/protocol/Serializer.h>
26#include <xrpl/server/NetworkOPs.h>
27#include <xrpl/server/State.h>
28#include <xrpl/shamap/SHAMapMissingNode.h>
29#include <xrpl/shamap/SHAMapTreeNode.h>
30
31#include <boost/algorithm/string/predicate.hpp>
32
33#include <algorithm>
34#include <chrono>
35#include <cstddef>
36#include <cstdint>
37#include <filesystem>
38#include <format>
39#include <functional>
40#include <limits>
41#include <memory>
42#include <mutex>
43#include <optional>
44#include <stdexcept>
45#include <string>
46#include <thread>
47#include <utility>
48#include <vector>
49
50namespace xrpl {
51void
53{
54 std::scoped_lock const lock(mutex);
55 initStateDB(sqlDb, config, dbName);
56}
57
65
68{
69 std::scoped_lock const lock(mutex);
70
71 return xrpl::setCanDelete(sqlDb, canDelete);
72}
73
81
82void
88
89void
95
96//------------------------------------------------------------------------------
97
99 Application& app,
100 node_store::Scheduler& scheduler,
101 beast::Journal journal)
102 : app_(app)
103 , scheduler_(scheduler)
104 , journal_(journal)
105 , working_(true)
106 , canDelete_(std::numeric_limits<LedgerIndex>::max())
107{
108 Config& config{app.config()};
109
110 Section& section{config.section(Sections::kNodeDatabase)};
111 if (section.empty())
112 {
113 Throw<std::runtime_error>(
114 std::format("Missing [{}] entry in configuration file", Sections::kNodeDatabase));
115 }
116
117 // RocksDB only. Use sensible defaults if no values specified.
118 if (boost::iequals(get(section, Keys::kType), "RocksDB"))
119 {
120 if (!section.exists(Keys::kCacheMb))
121 {
122 section.set(
124 }
125
126 if (!section.exists(Keys::kFilterBits) && (config.nodeSize >= 2))
127 section.set(Keys::kFilterBits, "10");
128 }
129
131
132 if (deleteInterval_ != 0u)
133 {
134 auto const minInterval =
136 if (deleteInterval_ < minInterval)
137 {
139 std::format("online_delete must be at least {}", minInterval));
140 }
141
142 if (config.ledgerHistory > deleteInterval_)
143 {
145 "online_delete must not be less than ledger_history (currently {})",
146 config.ledgerHistory));
147 }
148
149 // Configuration that affects the behavior of online delete
151 std::uint32_t temp = 0;
152 if (getIfExists(section, Keys::kBackOffMilliseconds, temp) ||
153 // Included for backward compatibility with an undocumented setting
154 getIfExists(section, Keys::kBackOff, temp))
155 {
157 }
158 if (getIfExists(section, Keys::kAgeThresholdSeconds, temp))
160 if (getIfExists(section, Keys::kRecoveryWaitSeconds, temp))
163 Throw<std::runtime_error>("recovery_wait_seconds must be at least 1 second");
164
166
167 if (getIfExists(section, Keys::kMaxWaitingLedgers, temp))
168 {
169 maxWaitingLedgers_ = temp;
170 }
171 else
172 {
174 }
175
176 auto const minWaiting = minInterval / 4;
177 if (maxWaitingLedgers_ < minWaiting)
178 {
180 std::format("max_waiting_ledgers must be at least {}", minWaiting));
181 }
182
183 stateDb_.init(config, dbName_);
184 dbPaths();
185 }
186}
187
190{
191 auto nscfg = app_.config().section(Sections::kNodeDatabase);
192
193 // Provide default values.
194 if (!nscfg.exists(Keys::kCacheSize))
195 {
196 nscfg.set(
198 std::to_string(app_.config().getValueFor(SizedItem::TreeCacheSize, std::nullopt)));
199 }
200
201 if (!nscfg.exists(Keys::kCacheAge))
202 {
203 nscfg.set(
205 std::to_string(app_.config().getValueFor(SizedItem::TreeCacheAge, std::nullopt)));
206 }
207
209
210 if (deleteInterval_ != 0u)
211 {
212 SavedState state = stateDb_.getState();
213 auto writableBackend = makeBackendRotating(state.writableDb);
214 auto archiveBackend = makeBackendRotating(state.archiveDb);
215 if (state.writableDb.empty())
216 {
217 state.writableDb = writableBackend->getName();
218 state.archiveDb = archiveBackend->getName();
219 stateDb_.setState(state);
220 }
221
222 // Create NodeStore with two backends to allow online deletion of
223 // data
226 readThreads,
227 std::move(writableBackend),
228 std::move(archiveBackend),
229 nscfg,
230 app_.getJournal(kNodeStoreName));
231 fdRequired_ += dbr->fdRequired();
232 dbRotating_ = dbr.get();
233 db.reset(dynamic_cast<node_store::Database*>(dbr.release()));
234 }
235 else
236 {
238 megabytes(app_.config().getValueFor(SizedItem::BurstSize, std::nullopt)),
240 readThreads,
241 nscfg,
242 app_.getJournal(kNodeStoreName));
243 fdRequired_ += db->fdRequired();
244 }
245 return db;
246}
247
248void
250{
251 {
252 std::scoped_lock const lock(mutex_);
253 newLedger_ = ledger;
254 working_ = true;
255 }
256 cond_.notify_one();
257}
258
259[[nodiscard]]
260bool
262{
263 if (!working_)
264 return true;
265
266 auto notWorking = [&] { return !working_; };
267
269 if (timeout)
270 {
271 return rendezvous_.wait_for(lock, *timeout, notWorking);
272 }
273 rendezvous_.wait(lock, notWorking);
274 return true;
275}
276
277int
279{
280 return fdRequired_;
281}
282
283bool
285{
286 // Copy a single record from node to dbRotating_
287 auto obj = dbRotating_->fetchNodeObject(
289 if (!obj)
290 {
291 XRPL_ASSERT(node.cowid() == 0, "SHAMapStoreImp::copyNode : rescued node must be clean");
292 // Reachable from the validated state map in memory, but present in
293 // neither backend: its only on-disk copy lived in a backend removed by
294 // an earlier rotation, and it was never rewritten because it is clean
295 // (cowid == 0, so flushDirty skips it). Persist the in-memory body
296 // directly into the writable backend so it survives this rotation
297 // instead of later surfacing as an unresolvable SHAMapMissingNode.
298 auto const hash = node.getHash().asUInt256();
299 Serializer s;
300 node.serializeWithPrefix(s);
301 dbRotating_->store(NodeObjectType::AccountNode, std::move(s.modData()), hash, 0);
302 JLOG(journal_.warn()) << "copyNode: re-stored node missing from both backends, hash="
303 << hash << " type=" << static_cast<int>(node.getType());
304 }
305 if ((++nodeCount % checkHealthInterval_) == 0u)
306 {
308 return false;
309 }
310
311 return true;
312}
313
314void
316{
317 beast::setCurrentThreadName("SHAMapStore");
318 LedgerIndex lastRotated = stateDb_.getState().lastRotated;
319 netOPs_ = &app_.getOPs();
320 ledgerMaster_ = &app_.getLedgerMaster();
321 fullBelowCache_ = &(*app_.getNodeFamily().getFullBelowCache());
322 treeNodeCache_ = &(*app_.getNodeFamily().getTreeNodeCache());
323
324 if (advisoryDelete_)
325 canDelete_ = stateDb_.getCanDelete();
326
327 while (true)
328 {
329 healthy_ = true;
330 std::shared_ptr<Ledger const> validatedLedger;
331
332 {
334 working_ = false;
335 rendezvous_.notify_all();
336 if (stop_)
337 {
338 return;
339 }
340 cond_.wait(lock);
341 if (newLedger_)
342 {
343 validatedLedger = std::move(newLedger_);
344 }
345 else
346 {
347 continue;
348 }
349 }
350
351 LedgerIndex const validatedSeq = validatedLedger->header().seq;
352 if (lastRotated == 0u)
353 {
354 lastRotated = validatedSeq;
355 stateDb_.setLastRotated(lastRotated);
356 }
357
358 // We're starting a new cycle, so reset back to the default.
360
361 bool const readyToRotate = validatedSeq >= lastRotated + deleteInterval_ &&
362 canDelete_ >= lastRotated - 1 && healthWait() == HealthResult::KeepGoing;
363
364 {
365 // Note that this is set after the healthWait() check, so that we
366 // don't start the rotation until the validated ledger is fully
367 // processed. It is not guaranteed to be done at this point. It also
368 // allows the testLedgerGaps unit test to work.
370 if (newLedger_)
371 {
372 // It is possible, though very unlikely outside of tests which manipulate internals,
373 // that healthWait() took so long that the validated ledger (newLedger_) has moved
374 // on from where we started. If that's the case, update lastGoodValidatedLedger_
375 // to that ledger's sequence number.
376 lastGoodValidatedLedger_ = newLedger_->header().seq;
377 }
378 else
379 {
380 lastGoodValidatedLedger_ = validatedSeq;
381 }
382 auto const l = lastGoodValidatedLedger_;
383 lock.unlock();
384 JLOG(journal_.trace()) << "run: Set lastGoodValidatedLedger_ to " << l;
385 }
386
387 // will delete up to (not including) lastRotated
388 if (readyToRotate)
389 {
390 JLOG(journal_.warn()) << "rotating validatedSeq " << validatedSeq << " lastRotated "
391 << lastRotated << " deleteInterval " << deleteInterval_
392 << " canDelete_ " << canDelete_ << " state "
393 << app_.getOPs().strOperatingMode(false) << " age "
394 << ledgerMaster_->getValidatedLedgerAge().count()
395 << "s. Complete ledgers: " << ledgerMaster_->getCompleteLedgers();
396
397 clearPrior(lastRotated);
398 switch (healthWait())
399 {
401 return;
403 continue;
405 break;
406 }
407
408 JLOG(journal_.debug()) << "copying ledger " << validatedSeq;
409 std::uint64_t nodeCount = 0;
410
411 try
412 {
413 validatedLedger->stateMap().snapShot(false)->visitNodes(
414 [this, &nodeCount](SHAMapTreeNode const& node) {
415 return copyNode(nodeCount, node);
416 });
417 }
418 catch (SHAMapMissingNode const& e)
419 {
420 JLOG(journal_.error())
421 << "Missing node while copying ledger before rotate: " << e.what();
422 continue;
423 }
424
425 switch (healthWait())
426 {
428 return;
430 continue;
432 break;
433 }
434 // Only log if we completed without a "health" abort
435 JLOG(journal_.debug())
436 << "copied ledger " << validatedSeq << " nodecount " << nodeCount;
437
438 // Close the getKeys()->swap exposure window: from here until
439 // rotate() completes, an ordinary read served by the archive is
440 // copied forward into the writable backend, so a node fetched
441 // from the doomed archive cannot be left RAM-only when the
442 // archive is deleted. RAII so the early returns below (and any
443 // exception) also clear the flag.
444 struct RotationExposureGuard
445 {
447 ~RotationExposureGuard()
448 {
449 db.setRotationInFlight(false);
450 }
451 };
452 RotationExposureGuard const rotationExposureGuard{*dbRotating_};
453 dbRotating_->setRotationInFlight(true);
454
455 JLOG(journal_.debug()) << "freshening caches";
457 switch (healthWait())
458 {
460 return;
462 continue;
464 break;
465 }
466 // Only log if we completed without a "health" abort
467 JLOG(journal_.debug()) << validatedSeq << " freshened caches";
468
469 JLOG(journal_.trace()) << "Making a new backend";
470 auto newBackend = makeBackendRotating();
471 JLOG(journal_.debug()) << validatedSeq << " new backend " << newBackend->getName();
472
473 clearCaches(validatedSeq);
474 switch (healthWait())
475 {
477 return;
479 continue;
481 break;
482 }
483
484 lastRotated = validatedSeq;
485
486 dbRotating_->rotate(
487 std::move(newBackend),
488 [&](std::string const& writableName, std::string const& archiveName) {
489 SavedState savedState;
490 savedState.writableDb = writableName;
491 savedState.archiveDb = archiveName;
492 savedState.lastRotated = lastRotated;
493 stateDb_.setState(savedState);
494
495 clearCaches(validatedSeq);
496 });
497
498 JLOG(journal_.warn()) << "finished rotation. validatedSeq: " << validatedSeq
499 << ", lastRotated: " << lastRotated
500 << ". Complete ledgers: " << ledgerMaster_->getCompleteLedgers();
501 }
502 }
503}
504
505void
507{
508 Section const section{app_.config().section(Sections::kNodeDatabase)};
509
510 // Skip creating the directory when an in-memory database is used.
511 if (boost::iequals(get(section, Keys::kType), "memory"))
512 return;
513
514 std::filesystem::path dbPath = get(section, Keys::kPath);
515 if (std::filesystem::exists(dbPath))
516 {
518 {
519 journal_.error() << "node db path must be a directory. " << dbPath.string();
520 Throw<std::runtime_error>("node db path must be a directory.");
521 }
522 }
523 else
524 {
526 }
527
528 SavedState state = stateDb_.getState();
529
530 {
531 auto update = [&dbPath](std::string& sPath) {
532 if (sPath.empty())
533 return false;
534
535 // Check if configured "path" matches stored directory path
536 using namespace std::filesystem;
537 auto const stored{std::filesystem::path(sPath)};
538 if (stored.parent_path() == dbPath)
539 return false;
540
541 sPath = (dbPath / stored.filename()).string();
542 return true;
543 };
544
545 if (update(state.writableDb))
546 {
547 update(state.archiveDb);
548 stateDb_.setState(state);
549 }
550 }
551
552 bool writableDbExists = false;
553 bool archiveDbExists = false;
554
558 ++it)
559 {
560 if (state.writableDb == it->path().string())
561 {
562 writableDbExists = true;
563 }
564 else if (state.archiveDb == it->path().string())
565 {
566 archiveDbExists = true;
567 }
568 else if (dbPrefix_ == it->path().stem().string())
569 {
570 pathsToDelete.push_back(it->path());
571 }
572 }
573
574 if ((!writableDbExists && !state.writableDb.empty()) ||
575 (!archiveDbExists && !state.archiveDb.empty()) || (writableDbExists != archiveDbExists) ||
576 state.writableDb.empty() != state.archiveDb.empty())
577 {
578 std::filesystem::path stateDbPathName = app_.config().legacy(Sections::kDatabasePath);
579 stateDbPathName /= dbName_;
580 stateDbPathName += "*";
581
582 journal_.error() << "state db error:\n"
583 << " writableDbExists " << writableDbExists << " archiveDbExists "
584 << archiveDbExists << '\n'
585 << " writableDb '" << state.writableDb << "' archiveDb '"
586 << state.archiveDb << "\n\n"
587 << "The existing data is in a corrupted state.\n"
588 << "To resume operation, remove the files matching "
589 << stateDbPathName.string() << " and contents of the directory "
590 << get(section, Keys::kPath) << '\n'
591 << "Optionally, you can move those files to another\n"
592 << "location if you wish to analyze or back up the data.\n"
593 << "However, there is no guarantee that the data in its\n"
594 << "existing form is usable.";
595
596 Throw<std::runtime_error>("state db error");
597 }
598
599 // The necessary directories exist. Now, remove any others.
600 for (std::filesystem::path const& p : pathsToDelete)
602}
603
606{
607 Section section{app_.config().section(Sections::kNodeDatabase)};
608 std::filesystem::path newPath;
609
610 if (!path.empty())
611 {
612 newPath = path;
613 }
614 else
615 {
616 newPath = uniqueRandomPath(get(section, Keys::kPath), dbPrefix_ + ".");
617 }
618 section.set(Keys::kPath, newPath.string());
619
621 section,
622 megabytes(app_.config().getValueFor(SizedItem::BurstSize, std::nullopt)),
624 app_.getJournal(kNodeStoreName))};
625 backend->open();
626 return backend;
627}
628
629void
631 LedgerIndex lastRotated,
632 std::string const& tableName,
633 std::function<std::optional<LedgerIndex>()> const& getMinSeq,
634 std::function<void(LedgerIndex)> const& deleteBeforeSeq)
635{
636 XRPL_ASSERT(deleteInterval_, "xrpl::SHAMapStoreImp::clearSql : nonzero delete interval");
638
639 {
640 JLOG(journal_.trace()) << "Begin: Look up lowest value of: " << tableName;
641 auto m = getMinSeq();
642 JLOG(journal_.trace()) << "End: Look up lowest value of: " << tableName;
643 if (!m)
644 return;
645 min = *m;
646 }
647
648 if (min > lastRotated || healthWait() != HealthResult::KeepGoing)
649 return;
650 if (min == lastRotated)
651 {
652 // Micro-optimization mainly to clarify logs
653 JLOG(journal_.trace()) << "Nothing to delete from " << tableName;
654 return;
655 }
656
657 JLOG(journal_.debug()) << "start deleting in: " << tableName << " from " << min << " to "
658 << lastRotated;
659 while (min < lastRotated)
660 {
661 // The very first sleep is, arguably wasted, but clearSql is called multiple times for
662 // different tables, so the time is amortized among all the operations. This results in
663 // a backoff in between each set of tables, too.
666 return;
667
668 min = std::min(lastRotated, min + deleteBatch_);
669 JLOG(journal_.trace()) << "Begin: Delete up to " << deleteBatch_
670 << " rows with LedgerSeq < " << min << " from: " << tableName;
671 deleteBeforeSeq(min);
672 JLOG(journal_.trace()) << "End: Delete up to " << deleteBatch_ << " rows with LedgerSeq < "
673 << min << " from: " << tableName;
674 }
675 JLOG(journal_.debug()) << "finished deleting from: " << tableName;
676}
677
678void
680{
681 ledgerMaster_->clearLedgerCachePrior(validatedSeq);
682 // Also clear the FullBelowCache so its generation counter is bumped.
683 // This prevents stale "full below" markers from persisting across
684 // backend rotation/online deletion and interfering with SHAMap sync.
685 fullBelowCache_->clear();
686}
687
688void
690{
692 return;
693 if (freshenCache(app_.getMasterTransaction().getCache()))
694 return;
695}
696
697void
699{
700 // Do not allow ledgers to be acquired from the network
701 // that are about to be deleted.
702 minimumOnline_ = lastRotated + 1;
703 JLOG(journal_.trace()) << "Begin: Clear internal ledgers up to " << lastRotated;
704 ledgerMaster_->clearPriorLedgers(lastRotated);
705 JLOG(journal_.trace()) << "End: Clear internal ledgers up to " << lastRotated;
707 return;
708
709 auto& db = app_.getRelationalDatabase();
710
711 clearSql(
712 lastRotated,
713 "Ledgers",
714 [&db]() -> std::optional<LedgerIndex> { return db.getMinLedgerSeq(); },
715 [&db](LedgerIndex min) -> void { db.deleteBeforeLedgerSeq(min); });
717 return;
718
719 if (!app_.config().useTxTables())
720 return;
721
722 clearSql(
723 lastRotated,
724 "Transactions",
725 [&db]() -> std::optional<LedgerIndex> { return db.getTransactionsMinLedgerSeq(); },
726 [&db](LedgerIndex min) -> void { db.deleteTransactionsBeforeLedgerSeq(min); });
728 return;
729
730 clearSql(
731 lastRotated,
732 "AccountTransactions",
733 [&db]() -> std::optional<LedgerIndex> { return db.getAccountTransactionsMinLedgerSeq(); },
734 [&db](LedgerIndex min) -> void { db.deleteAccountTransactionsBeforeLedgerSeq(min); });
736 return;
737}
738
741{
742 // Gets the current status of the server from ledgerMaster_ and netOPs_. Must be called
743 // while mutex_ is unlocked to avoid unlikely, but possible, deadlock with ledgerMaster_'s
744 // completeLock_.
745 // Releasing the lock may mean that status will be slightly out of date when the lock is
746 // reacquired, but it's close enough. In a normal rotation, healthWait() is called frequently,
747 // so a false positive will be detected on the next call, and a false negative will be detected
748 // in the next loop iteration. Database rotation is important, but not timely, so an extra
749 // delay is fine.
750 auto readServerStatus = [this](
751 LedgerIndex& index,
752 bool& buildingIndex,
754 OperatingMode& mode,
755 std::size_t& numMissing,
756 LedgerIndex const lowerBound,
757 ScopeUnlock<decltype(mutex_)> const&) {
758 index = ledgerMaster_->getValidLedgerIndex();
759 bool const haveIndex = ledgerMaster_->haveLedger(index);
760 age = ledgerMaster_->getValidatedLedgerAge();
761 mode = netOPs_->getOperatingMode();
762
763 numMissing =
764 lowerBound == 0 ? 0 : ledgerMaster_->missingFromCompleteLedgerRange(lowerBound, index);
765
766 buildingIndex = (numMissing == 1 && !haveIndex);
767 };
768
769 // Tracked server status properties
770 LedgerIndex index = 0;
771 bool buildingIndex = false;
774 std::size_t numMissing = 0;
775
777
778 auto const waitTime = recoveryWaitTime_;
779 auto const ageThreshold = ageThreshold_;
780 {
781 auto const lowerBound = lastGoodValidatedLedger_;
782
783 ScopeUnlock const unlock(lock);
784
785 readServerStatus(index, buildingIndex, age, mode, numMissing, lowerBound, unlock);
786 }
787 // If index gets past this point without the health check succeeding, return
788 // HealthWait::Expired. This depends on index being initialized, so it must be after
789 // readServerStatus().
790 auto const lastSuccess = lastSuccessfulHealthCheck_ == 0 ? index : lastSuccessfulHealthCheck_;
791 auto const circuitBreaker = lastSuccess + maxWaitingLedgers_;
792
793 auto healthy = [&] {
794 // Special case: If the server is disconnected, it's not doing any ledger I/O, because
795 // it's focused on trying to get peers. A disconnected state is should never be caused by
796 // the activity of the server. It's usually limited to hardware or connectivity issues. Take
797 // advantage of that to run as much rotation I/O as possible before it comes back online.
798 if (mode == OperatingMode::DISCONNECTED)
799 return true;
800 if (age > ageThreshold)
801 return false;
802 if (numMissing > 0)
803 return false;
804 if (mode != OperatingMode::FULL)
805 return false;
806 return true;
807 };
808
809 while (!stop_ && !healthy() && index < circuitBreaker)
810 {
811 // Future-proofing: this value shouldn't change while we are sleeping, but grab it while we
812 // have the lock in case it does.
813 auto const lowerBound = lastGoodValidatedLedger_;
814
815 ScopeUnlock const unlock(lock);
816
817 auto const [stream, waitMs] = std::invoke(
818 [mode, age, ageThreshold, buildingIndex, waitTime, index, lastSuccess, this]
820 if (mode != OperatingMode::FULL || age > ageThreshold ||
821 (index - lastSuccess > maxWaitingLedgers_ / 4))
822 return {journal_.warn(), waitTime};
823 if (buildingIndex)
824 {
825 // We expect this ledger to be built soon, so log at a lower level, and don't
826 // wait as long.
827 return {
828 journal_.trace(),
830 }
831 return {journal_.info(), waitTime};
832 });
833 JLOG(stream) << "Waiting " << waitMs.count() << "ms for node to stabilize. state: "
834 << app_.getOPs().strOperatingMode(mode, false) << ". age " << age.count()
835 << "s. Missing ledgers: " << numMissing << ". Expect: " << lowerBound << "-"
836 << index << ". Complete ledgers: " << ledgerMaster_->getCompleteLedgers();
838
839 [[maybe_unused]]
840 LedgerIndex const lastLedger = index;
841 readServerStatus(index, buildingIndex, age, mode, numMissing, lowerBound, unlock);
842 SOMETIMES(
843 index > lastLedger, "SHAMapStoreImp::healthWait : validated ledger index changed");
844 }
845
846 auto const result = std::invoke([index, circuitBreaker, this]() -> HealthResult {
847 if (stop_)
849 if (index < circuitBreaker)
851 JLOG(journal_.error()) << "online_delete rotation has been unable to make progress for "
852 << maxWaitingLedgers_ << " ledgers. "
853 << "validated ledger index: " << index
854 << ", last successful health check index: "
856 << ", circuit breaker index: " << circuitBreaker;
858 });
859
860 XRPL_ASSERT(lock.owns_lock(), "SHAMapStoreImp::healthWait : lock held");
861 if (result == HealthResult::KeepGoing)
863
864 return result;
865}
866
867void
869{
870 if (thread_.joinable())
871 {
872 {
874 stop_ = true;
875 cond_.notify_one();
876 }
877 thread_.join();
878 }
879}
880
883{
884 // minimumOnline_ with 0 value is equivalent to unknown/not set.
885 // Don't attempt to acquire ledgers if that value is unknown.
886 if ((deleteInterval_ != 0u) && (minimumOnline_ != 0u))
887 return minimumOnline_.load();
888 return app_.getLedgerMaster().minSqlSeq();
889}
890
891//------------------------------------------------------------------------------
892
895{
896 return std::make_unique<SHAMapStoreImp>(app, scheduler, journal);
897}
898
899} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:44
virtual Config & config()=0
Holds unparsed configuration information.
Section & section(std::string const &name)
Returns the section with the given name.
bool standalone() const
std::uint32_t ledgerHistory
int getValueFor(SizedItem item, std::optional< std::size_t > node=std::nullopt) const
Retrieve the default value for the item at the specified node size.
UInt256 const & asUInt256() const
Definition SHAMapHash.h:26
void setState(SavedState const &state)
void init(BasicConfig const &config, std::string const &dbName)
LedgerIndex setCanDelete(LedgerIndex canDelete)
bool copyNode(std::uint64_t &nodeCount, SHAMapTreeNode const &node)
std::atomic< bool > working_
std::condition_variable cond_
std::uint32_t deleteBatch_
std::atomic< LedgerIndex > minimumOnline_
std::chrono::seconds recoveryWaitTime_
If the node is out of sync, or any recent ledgers are not available during an online_delete healthWai...
FullBelowCache * fullBelowCache_
std::optional< LedgerIndex > minimumOnline() const override
The minimum ledger to try and maintain in our database.
TreeNodeCache * treeNodeCache_
LedgerIndex lastSuccessfulHealthCheck_
std::uint32_t deleteInterval_
std::unique_ptr< node_store::Database > makeNodeStore(int readThreads) override
static std::uint32_t const kMinimumDeletionIntervalSa
int fdRequired() const override
Returns the number of file descriptors that are needed.
std::chrono::milliseconds backOff_
std::atomic< LedgerIndex > canDelete_
std::shared_ptr< Ledger const > newLedger_
std::string const dbName_
void clearSql(LedgerIndex lastRotated, std::string const &tableName, std::function< std::optional< LedgerIndex >()> const &getMinSeq, std::function< void(LedgerIndex)> const &deleteBeforeSeq)
delete from sqlite table in batches to not lock the db excessively.
std::condition_variable rendezvous_
std::uint64_t const checkHealthInterval_
LedgerIndex lastGoodValidatedLedger_
HealthResult healthWait()
void clearCaches(LedgerIndex validatedSeq)
std::chrono::seconds ageThreshold_
LedgerMaster * ledgerMaster_
beast::Journal const journal_
bool rendezvous(std::optional< std::chrono::milliseconds > const &timeout={}) const override
static constexpr auto kNodeStoreName
std::string const dbPrefix_
node_store::Scheduler & scheduler_
std::uint32_t maxWaitingLedgers_
If the rotation stays "unhealthy" for a very long time, the process is aborted, and tried again later...
HealthResult
This is a health check for online deletion that waits until xrpld is stable before returning.
void onLedgerClosed(std::shared_ptr< Ledger const > const &ledger) override
Called by LedgerMaster every time a ledger validates.
node_store::DatabaseRotating * dbRotating_
std::unique_ptr< node_store::Backend > makeBackendRotating(std::string path=std::string())
static std::uint32_t const kMinimumDeletionInterval
SHAMapStoreImp(Application &app, node_store::Scheduler &scheduler, beast::Journal journal)
bool freshenCache(CacheInstance &cache)
void clearPrior(LedgerIndex lastRotated)
SHAMapHash const & getHash() const
Return the hash of this node.
virtual void serializeWithPrefix(Serializer &) const =0
Serialize the node in a format appropriate for hashing.
virtual SHAMapNodeType getType() const =0
Determines the type of node.
Automatically unlocks and re-locks a unique_lock object.
Definition scope.h:197
Holds a collection of configuration values.
Definition BasicConfig.h:28
bool empty() const
void set(std::string const &key, std::string const &value)
Set a key/value pair.
bool exists(std::string const &name) const
Returns true if a key with the given name exists.
virtual void setRotationInFlight(bool inFlight)=0
Marks an online-delete rotation as in progress (or completed).
Persistency layer for NodeObject.
Definition Database.h:45
virtual std::unique_ptr< Backend > makeBackend(Section const &parameters, std::size_t burstSize, Scheduler &scheduler, beast::Journal journal)=0
Create a backend.
virtual std::unique_ptr< Database > makeDatabase(std::size_t burstSize, Scheduler &scheduler, int readThreads, Section const &backendParameters, beast::Journal journal)=0
Construct a NodeStore database.
static Manager & instance()
Returns the instance of the manager singleton.
Scheduling for asynchronous backend activity.
T create_directories(T... args)
T duration_cast(T... args)
T empty(T... args)
T exists(T... args)
T filename(T... args)
T format(T... args)
std::uint32_t cowid() const
Returns the SHAMap that owns this node.
T invoke(T... args)
T is_directory(T... args)
T lock(T... args)
T make_unique(T... args)
T max(T... args)
T min(T... args)
void setCurrentThreadName(std::string_view newThreadName)
Changes the name of the caller thread.
STL namespace.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
void initStateDB(soci::session &session, BasicConfig const &config, std::string const &dbName)
initStateDB Opens a session with the State database.
Definition State.cpp:22
void setSavedState(soci::session &session, SavedState const &state)
setSavedState Saves the given state.
Definition State.cpp:99
std::uint32_t LedgerIndex
A ledger index.
Definition Protocol.h:382
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
void setLastRotated(soci::session &session, LedgerIndex seq)
setLastRotated Updates the last rotated ledger sequence.
Definition State.cpp:110
std::unique_ptr< SHAMapStore > makeSHAMapStore(Application &app, node_store::Scheduler &scheduler, beast::Journal journal)
std::filesystem::path uniqueRandomPath(std::filesystem::path const &base, std::string const &prefix="", std::size_t maxAttempts=100)
Generate a unique, non-existing path under base whose filename starts with prefix and ends with a ran...
bool getIfExists(Section const &section, std::string const &name, T &v)
SavedState getSavedState(soci::session &session)
getSavedState Returns the saved state.
Definition State.cpp:88
constexpr auto megabytes(T value) noexcept
LedgerIndex setCanDelete(soci::session &session, LedgerIndex canDelete)
setCanDelete Updates the ledger sequence which can be deleted.
Definition State.cpp:80
OperatingMode
Specifies the mode under which the server believes it's operating.
Definition NetworkOPs.h:60
@ DISCONNECTED
not ready to process requests
Definition NetworkOPs.h:61
@ FULL
we have the ledger and can even validate
Definition NetworkOPs.h:65
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:52
LedgerIndex getCanDelete(soci::session &session)
getCanDelete Returns the ledger sequence which can be deleted.
Definition State.cpp:71
T push_back(T... args)
T remove_all(T... args)
T reset(T... args)
T sleep_for(T... args)
static constexpr auto kMaxWaitingLedgers
Definition Constants.h:128
static constexpr auto kBackOffMilliseconds
Definition Constants.h:92
static constexpr auto kAdvisoryDelete
Definition Constants.h:89
static constexpr auto kCacheMb
Definition Constants.h:98
static constexpr auto kCacheSize
Definition Constants.h:99
static constexpr auto kBackOff
Definition Constants.h:91
static constexpr auto kAgeThresholdSeconds
Definition Constants.h:90
static constexpr auto kDeleteBatch
Definition Constants.h:104
static constexpr auto kFilterBits
Definition Constants.h:109
static constexpr auto kRecoveryWaitSeconds
Definition Constants.h:149
static constexpr auto kType
Definition Constants.h:174
static constexpr auto kPath
Definition Constants.h:144
static constexpr auto kCacheAge
Definition Constants.h:97
static constexpr auto kOnlineDelete
Definition Constants.h:137
std::string writableDb
Definition State.h:13
LedgerIndex lastRotated
Definition State.h:15
std::string archiveDb
Definition State.h:14
static constexpr auto kNodeDatabase
Definition Constants.h:32
static constexpr auto kDatabasePath
Definition Constants.h:13
T to_string(T... args)
T what(T... args)