1#include <xrpld/app/misc/SHAMapStoreImp.h>
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>
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>
31#include <boost/algorithm/string/predicate.hpp>
113 Throw<std::runtime_error>(
114 std::format(
"Missing [{}] entry in configuration file", Sections::kNodeDatabase));
134 auto const minInterval =
139 std::format(
"online_delete must be at least {}", minInterval));
145 "online_delete must not be less than ledger_history (currently {})",
176 auto const minWaiting = minInterval / 4;
180 std::format(
"max_waiting_ledgers must be at least {}", minWaiting));
217 state.
writableDb = writableBackend->getName();
218 state.
archiveDb = archiveBackend->getName();
227 std::move(writableBackend),
228 std::move(archiveBackend),
266 auto notWorking = [&] {
return !
working_; };
271 return rendezvous_.wait_for(lock, *timeout, notWorking);
291 XRPL_ASSERT(node.
cowid() == 0,
"SHAMapStoreImp::copyNode : rescued node must be clean");
302 JLOG(
journal_.warn()) <<
"copyNode: re-stored node missing from both backends, hash="
303 << hash <<
" type=" <<
static_cast<int>(node.
getType());
351 LedgerIndex const validatedSeq = validatedLedger->header().seq;
352 if (lastRotated == 0u)
354 lastRotated = validatedSeq;
355 stateDb_.setLastRotated(lastRotated);
361 bool const readyToRotate = validatedSeq >= lastRotated +
deleteInterval_ &&
384 JLOG(
journal_.trace()) <<
"run: Set lastGoodValidatedLedger_ to " << l;
390 JLOG(
journal_.warn()) <<
"rotating validatedSeq " << validatedSeq <<
" lastRotated "
393 <<
app_.getOPs().strOperatingMode(
false) <<
" age "
395 <<
"s. Complete ledgers: " <<
ledgerMaster_->getCompleteLedgers();
408 JLOG(
journal_.debug()) <<
"copying ledger " << validatedSeq;
413 validatedLedger->stateMap().snapShot(
false)->visitNodes(
421 <<
"Missing node while copying ledger before rotate: " << e.
what();
436 <<
"copied ledger " << validatedSeq <<
" nodecount " << nodeCount;
444 struct RotationExposureGuard
447 ~RotationExposureGuard()
452 RotationExposureGuard
const rotationExposureGuard{*
dbRotating_};
455 JLOG(
journal_.debug()) <<
"freshening caches";
467 JLOG(
journal_.debug()) << validatedSeq <<
" freshened caches";
469 JLOG(
journal_.trace()) <<
"Making a new backend";
471 JLOG(
journal_.debug()) << validatedSeq <<
" new backend " << newBackend->getName();
484 lastRotated = validatedSeq;
487 std::move(newBackend),
498 JLOG(
journal_.warn()) <<
"finished rotation. validatedSeq: " << validatedSeq
499 <<
", lastRotated: " << lastRotated
500 <<
". Complete ledgers: " <<
ledgerMaster_->getCompleteLedgers();
519 journal_.error() <<
"node db path must be a directory. " << dbPath.
string();
538 if (stored.parent_path() == dbPath)
541 sPath = (dbPath / stored.
filename()).string();
552 bool writableDbExists =
false;
553 bool archiveDbExists =
false;
562 writableDbExists =
true;
566 archiveDbExists =
true;
568 else if (
dbPrefix_ == it->path().stem().string())
575 (!archiveDbExists && !state.
archiveDb.
empty()) || (writableDbExists != archiveDbExists) ||
580 stateDbPathName +=
"*";
582 journal_.error() <<
"state db error:\n"
583 <<
" writableDbExists " << writableDbExists <<
" archiveDbExists "
584 << archiveDbExists <<
'\n'
585 <<
" writableDb '" << state.
writableDb <<
"' archiveDb '"
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 "
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.";
636 XRPL_ASSERT(
deleteInterval_,
"xrpl::SHAMapStoreImp::clearSql : nonzero delete interval");
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;
650 if (
min == lastRotated)
653 JLOG(
journal_.trace()) <<
"Nothing to delete from " << tableName;
657 JLOG(
journal_.debug()) <<
"start deleting in: " << tableName <<
" from " <<
min <<
" to "
659 while (
min < lastRotated)
670 <<
" rows with LedgerSeq < " <<
min <<
" from: " << tableName;
671 deleteBeforeSeq(
min);
673 <<
min <<
" from: " << tableName;
675 JLOG(
journal_.debug()) <<
"finished deleting from: " << tableName;
703 JLOG(
journal_.trace()) <<
"Begin: Clear internal ledgers up to " << lastRotated;
705 JLOG(
journal_.trace()) <<
"End: Clear internal ledgers up to " << lastRotated;
709 auto& db =
app_.getRelationalDatabase();
719 if (!
app_.config().useTxTables())
732 "AccountTransactions",
734 [&db](
LedgerIndex min) ->
void { db.deleteAccountTransactionsBeforeLedgerSeq(
min); });
750 auto readServerStatus = [
this](
761 mode =
netOPs_->getOperatingMode();
764 lowerBound == 0 ? 0 :
ledgerMaster_->missingFromCompleteLedgerRange(lowerBound, index);
766 buildingIndex = (numMissing == 1 && !haveIndex);
771 bool buildingIndex =
false;
785 readServerStatus(index, buildingIndex, age, mode, numMissing, lowerBound, unlock);
800 if (age > ageThreshold)
809 while (!
stop_ && !healthy() && index < circuitBreaker)
818 [mode, age, ageThreshold, buildingIndex, waitTime, index, lastSuccess,
this]
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();
841 readServerStatus(index, buildingIndex, age, mode, numMissing, lowerBound, unlock);
843 index > lastLedger,
"SHAMapStoreImp::healthWait : validated ledger index changed");
849 if (index < circuitBreaker)
851 JLOG(
journal_.error()) <<
"online_delete rotation has been unable to make progress for "
853 <<
"validated ledger index: " << index
854 <<
", last successful health check index: "
856 <<
", circuit breaker index: " << circuitBreaker;
860 XRPL_ASSERT(
lock.owns_lock(),
"SHAMapStoreImp::healthWait : lock held");
888 return app_.getLedgerMaster().minSqlSeq();
A generic endpoint for log messages.
virtual Config & config()=0
Holds unparsed configuration information.
Section & section(std::string const &name)
Returns the section with the given name.
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
void setState(SavedState const &state)
void init(BasicConfig const &config, std::string const &dbName)
LedgerIndex setCanDelete(LedgerIndex canDelete)
void setLastRotated(LedgerIndex seq)
LedgerIndex getCanDelete()
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.
Holds a collection of configuration values.
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.
virtual std::unique_ptr< Backend > makeBackend(Section const ¶meters, 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)
std::uint32_t cowid() const
Returns the SHAMap that owns this node.
T is_directory(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.
void initStateDB(soci::session &session, BasicConfig const &config, std::string const &dbName)
initStateDB Opens a session with the State database.
void setSavedState(soci::session &session, SavedState const &state)
setSavedState Saves the given state.
std::uint32_t LedgerIndex
A ledger index.
T get(Section const §ion, 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.
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 §ion, std::string const &name, T &v)
SavedState getSavedState(soci::session &session)
getSavedState Returns the saved state.
constexpr auto megabytes(T value) noexcept
LedgerIndex setCanDelete(soci::session &session, LedgerIndex canDelete)
setCanDelete Updates the ledger sequence which can be deleted.
OperatingMode
Specifies the mode under which the server believes it's operating.
@ DISCONNECTED
not ready to process requests
@ FULL
we have the ledger and can even validate
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
LedgerIndex getCanDelete(soci::session &session)
getCanDelete Returns the ledger sequence which can be deleted.
static constexpr auto kMaxWaitingLedgers
static constexpr auto kBackOffMilliseconds
static constexpr auto kAdvisoryDelete
static constexpr auto kCacheMb
static constexpr auto kCacheSize
static constexpr auto kBackOff
static constexpr auto kAgeThresholdSeconds
static constexpr auto kDeleteBatch
static constexpr auto kFilterBits
static constexpr auto kRecoveryWaitSeconds
static constexpr auto kType
static constexpr auto kPath
static constexpr auto kCacheAge
static constexpr auto kOnlineDelete
static constexpr auto kNodeDatabase
static constexpr auto kDatabasePath