test: Migrate nodestore tests from Beast to GTest (#7292)

Co-authored-by: Marek Foss <marek.foss@neti-soft.com>
Co-authored-by: Alex Kremer <akremer@ripple.com>
This commit is contained in:
Andrzej Budzanowski
2026-07-27 15:00:14 +02:00
committed by GitHub
parent 20801d98ac
commit 29120dfcbd
67 changed files with 1235 additions and 1310 deletions

View File

@@ -130,12 +130,9 @@ test.ledger > xrpl.json
test.ledger > xrpl.ledger
test.ledger > xrpl.protocol
test.nodestore > test.jtx
test.nodestore > test.unit_test
test.nodestore > xrpl.basics
test.nodestore > xrpl.config
test.nodestore > xrpld.core
test.nodestore > xrpl.nodestore
test.nodestore > xrpl.protocol
test.nodestore > xrpl.rdb
test.overlay > test.jtx
test.overlay > test.unit_test

View File

@@ -46,9 +46,6 @@ for DIRECTORY in "${DIRECTORIES[@]}"; do
done
done
# Special case for NuDBFactory that has ripple twice in the test suite name.
${SED_COMMAND} -i -E 's/(BEAST_DEFINE_TESTSUITE.+)ripple(.+)/\1xrpl\2/g' src/test/nodestore/NuDBFactory_test.cpp
DIRECTORY=$1
find "${DIRECTORY}" -type f -name "*.md" | while read -r FILE; do
echo "Processing file: ${FILE}"

View File

@@ -15,9 +15,9 @@
namespace xrpl {
// Forward declarations
namespace NodeStore {
namespace node_store {
class Database;
} // namespace NodeStore
} // namespace node_store
namespace Resource {
class Manager;
} // namespace Resource
@@ -164,7 +164,7 @@ public:
getResourceManager() = 0;
// Storage services
virtual NodeStore::Database&
virtual node_store::Database&
getNodeStore() = 0;
virtual SHAMapStore&

View File

@@ -13,7 +13,7 @@
#include <stdexcept>
#include <string>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
/**
* A backend used for the NodeStore.
@@ -163,4 +163,4 @@ public:
fdRequired() const = 0;
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -25,7 +25,7 @@ namespace xrpl {
class Section;
} // namespace xrpl
namespace xrpl::NodeStore {
namespace xrpl::node_store {
/**
* Persistency layer for NodeObject
@@ -248,7 +248,7 @@ protected:
void
storeStats(std::uint64_t count, std::uint64_t sz)
{
XRPL_ASSERT(count <= sz, "xrpl::NodeStore::Database::storeStats : valid inputs");
XRPL_ASSERT(count <= sz, "xrpl::node_store::Database::storeStats : valid inputs");
storeCount_ += count;
storeSz_ += sz;
}
@@ -308,4 +308,4 @@ private:
threadEntry();
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -9,7 +9,7 @@
#include <memory>
#include <string>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
/* This class has two key-value store Backend objects for persisting SHAMap
* records. This facilitates online deletion of data. New backends are
@@ -38,7 +38,7 @@ public:
*/
virtual void
rotate(
std::unique_ptr<NodeStore::Backend>&& newBackend,
std::unique_ptr<node_store::Backend>&& newBackend,
std::function<void(std::string const& writableName, std::string const& archiveName)> const&
f) = 0;
@@ -56,4 +56,4 @@ public:
setRotationInFlight(bool inFlight) = 0;
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -3,7 +3,7 @@
#include <xrpl/nodestore/Scheduler.h>
#include <xrpl/nodestore/Task.h>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
/**
* Simple NodeStore Scheduler that just performs the tasks synchronously.
@@ -21,4 +21,4 @@ public:
onBatchWrite(BatchWriteReport const& report) override;
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -14,7 +14,7 @@ namespace xrpl {
class Section;
} // namespace xrpl
namespace xrpl::NodeStore {
namespace xrpl::node_store {
/**
* Base class for backend factories.
@@ -70,4 +70,4 @@ public:
}
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -10,7 +10,7 @@
#include <memory>
#include <string>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
/**
* Singleton for managing NodeStore factories and back ends.
@@ -98,4 +98,4 @@ public:
beast::Journal journal) = 0;
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -8,7 +8,7 @@
#include <cstdint>
#include <memory>
// VFALCO NOTE Intentionally not in the NodeStore namespace
// VFALCO NOTE Intentionally not in the node_store namespace
namespace xrpl {

View File

@@ -4,7 +4,7 @@
#include <chrono>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
enum class FetchType { Synchronous, Async };
@@ -71,4 +71,4 @@ public:
onBatchWrite(BatchWriteReport const& report) = 0;
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -1,6 +1,6 @@
#pragma once
namespace xrpl::NodeStore {
namespace xrpl::node_store {
/**
* Derived classes perform scheduled tasks.
@@ -17,4 +17,4 @@ struct Task
performScheduledTask() = 0;
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -5,7 +5,7 @@
#include <memory>
#include <vector>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
// This is only used to pre-allocate the array for
// batch objects and does not affect the amount written.
@@ -36,4 +36,4 @@ enum class Status {
*/
using Batch = std::vector<std::shared_ptr<NodeObject>>;
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -9,7 +9,7 @@
#include <memory>
#include <mutex>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
/**
* Batch-writing assist logic.
@@ -86,4 +86,4 @@ private:
Batch writeSet_;
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -22,7 +22,7 @@
#include <stdexcept>
#include <utility>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
class DatabaseNodeImp : public Database
{
@@ -68,7 +68,7 @@ public:
XRPL_ASSERT(
backend_,
"xrpl::NodeStore::DatabaseNodeImp::DatabaseNodeImp : non-null "
"xrpl::node_store::DatabaseNodeImp::DatabaseNodeImp : non-null "
"backend");
}
@@ -138,4 +138,4 @@ private:
}
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -16,7 +16,7 @@
#include <mutex>
#include <string>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
class DatabaseRotatingImp : public DatabaseRotating
{
@@ -41,7 +41,7 @@ public:
void
rotate(
std::unique_ptr<NodeStore::Backend>&& newBackend,
std::unique_ptr<node_store::Backend>&& newBackend,
std::function<void(std::string const& writableName, std::string const& archiveName)> const&
f) override;
@@ -94,4 +94,4 @@ private:
forEach(std::function<void(std::shared_ptr<NodeObject>)> f) override;
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -4,7 +4,7 @@
#include <memory>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
/**
* Parsed key/value blob into NodeObject components.
@@ -49,4 +49,4 @@ private:
int dataBytes_;
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -12,7 +12,7 @@
#include <memory>
#include <stdexcept>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
/**
* Convert a NodeObject from in-memory to database format.
@@ -68,7 +68,7 @@ class EncodedBlob
public:
explicit EncodedBlob(std::shared_ptr<NodeObject> const& obj)
: size_([&obj]() {
XRPL_ASSERT(obj, "xrpl::NodeStore::EncodedBlob::EncodedBlob : non-null input");
XRPL_ASSERT(obj, "xrpl::node_store::EncodedBlob::EncodedBlob : non-null input");
if (!obj)
throw std::runtime_error("EncodedBlob: unseated std::shared_ptr used.");
@@ -88,7 +88,7 @@ public:
XRPL_ASSERT(
((ptr_ == payload_.data()) && (size_ <= payload_.size())) ||
((ptr_ != payload_.data()) && (size_ > payload_.size())),
"xrpl::NodeStore::EncodedBlob::~EncodedBlob : valid payload "
"xrpl::node_store::EncodedBlob::~EncodedBlob : valid payload "
"pointer");
if (ptr_ != payload_.data())
@@ -114,4 +114,4 @@ public:
}
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -13,7 +13,7 @@
#include <string>
#include <vector>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
class ManagerImp : public Manager
{
@@ -57,4 +57,4 @@ public:
beast::Journal journal) override;
};
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -21,7 +21,7 @@
#include <cstring>
#include <string>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
template <class BufferFactory>
std::pair<void const*, std::size_t>
@@ -269,7 +269,7 @@ nodeobjectCompress(void const* in, std::size_t inSize, BufferFactory&& bf)
case 1: // lz4
{
std::uint8_t* p = nullptr;
auto const lzr = NodeStore::lz4Compress(in, inSize, [&p, &vn, &bf](std::size_t n) {
auto const lzr = node_store::lz4Compress(in, inSize, [&p, &vn, &bf](std::size_t n) {
p = reinterpret_cast<std::uint8_t*>(bf(vn + n));
return p + vn;
});
@@ -316,4 +316,4 @@ filterInner(void* in, std::size_t inSize)
}
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -6,7 +6,7 @@
#include <cstdint>
#include <type_traits>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
// This is a variant of the base128 varint format from
// google protocol buffers:
@@ -123,4 +123,4 @@ write(nudb::detail::ostream& os, std::size_t t)
writeVarint(os.data(sizeVarint(t)), t);
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -26,10 +26,10 @@ public:
explicit Family() = default;
virtual ~Family() = default;
virtual NodeStore::Database&
virtual node_store::Database&
db() = 0;
[[nodiscard]] virtual NodeStore::Database const&
[[nodiscard]] virtual node_store::Database const&
db() const = 0;
virtual beast::Journal const&

View File

@@ -17,7 +17,7 @@
#include <utility>
#include <vector>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
namespace {
constexpr std::size_t kPoolSizes[] = {1000, 10000, 100000};
@@ -326,4 +326,4 @@ registerStoreBatch(BackendConfig const& bc)
}();
} // namespace
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -18,7 +18,7 @@
#include <utility>
#include <vector>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
namespace {
// Number of distinct objects pre-generated per run.
@@ -240,4 +240,4 @@ registerWorkload(BackendConfig const& bc, Workload const& w)
}();
} // namespace
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -32,7 +32,7 @@
// Shared helpers for the NodeStore benchmarks.
//
namespace xrpl::NodeStore {
namespace xrpl::node_store {
// Fill `bytes` of memory at `buffer` with random bits drawn from `g`.
template <class Generator>
@@ -315,4 +315,4 @@ backendConfigs()
return kConfigs;
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -11,7 +11,7 @@
#include <mutex>
#include <vector>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
BatchWriter::BatchWriter(Callback& callback, Scheduler& scheduler)
: callback_(callback), scheduler_(scheduler)
@@ -72,7 +72,7 @@ BatchWriter::writeBatch()
writeSet_.swap(set);
XRPL_ASSERT(
writeSet_.empty(), "xrpl::NodeStore::BatchWriter::writeBatch : writes not set");
writeSet_.empty(), "xrpl::node_store::BatchWriter::writeBatch : writes not set");
writeLoad_ = set.size();
if (set.empty())
@@ -107,4 +107,4 @@ BatchWriter::waitForWriting()
writeCondition_.wait(sl);
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -30,7 +30,7 @@
#include <thread>
#include <utility>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
Database::Database(
Scheduler& scheduler,
@@ -43,7 +43,7 @@ Database::Database(
, requestBundle_(get<int>(config, Keys::kRqBundle, 4))
, readThreads_(std::max(1, readThreads))
{
XRPL_ASSERT(readThreads, "xrpl::NodeStore::Database::Database : nonzero threads input");
XRPL_ASSERT(readThreads, "xrpl::node_store::Database::Database : nonzero threads input");
if (earliestLedgerSeq_ < 1)
Throw<std::runtime_error>("Invalid earliest_seq");
@@ -89,7 +89,7 @@ Database::Database(
{
XRPL_ASSERT(
!it->second.empty(),
"xrpl::NodeStore::Database::Database : non-empty "
"xrpl::node_store::Database::Database : non-empty "
"data");
auto const& hash = it->first;
@@ -164,7 +164,7 @@ Database::stop()
{
XRPL_ASSERT(
steady_clock::now() - start < 30s,
"xrpl::NodeStore::Database::stop : maximum stop duration");
"xrpl::node_store::Database::stop : maximum stop duration");
std::this_thread::yield();
}
@@ -213,7 +213,7 @@ Database::importInternal(Backend& dstBackend, Database& srcDB)
};
srcDB.forEach([&](std::shared_ptr<NodeObject> nodeObject) {
XRPL_ASSERT(nodeObject, "xrpl::NodeStore::Database::importInternal : non-null node");
XRPL_ASSERT(nodeObject, "xrpl::node_store::Database::importInternal : non-null node");
if (!nodeObject) // This should never happen
return;
@@ -257,7 +257,7 @@ Database::fetchNodeObject(
void
Database::getCountsJson(json::Value& obj)
{
XRPL_ASSERT(obj.isObject(), "xrpl::NodeStore::Database::getCountsJson : valid input type");
XRPL_ASSERT(obj.isObject(), "xrpl::node_store::Database::getCountsJson : valid input type");
{
std::unique_lock<std::mutex> const lock(readLock_);
@@ -276,4 +276,4 @@ Database::getCountsJson(json::Value& obj)
obj[jss::node_reads_duration_us] = std::to_string(fetchDurationUs_);
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -15,7 +15,7 @@
#include <memory>
#include <utility>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
void
DatabaseNodeImp::store(NodeObjectType type, Blob&& data, uint256 const& hash, std::uint32_t)
@@ -125,4 +125,4 @@ DatabaseNodeImp::fetchNodeObject(
return nodeObject;
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -22,7 +22,7 @@
#include <string>
#include <utility>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
DatabaseRotatingImp::DatabaseRotatingImp(
Scheduler& scheduler,
@@ -43,7 +43,7 @@ DatabaseRotatingImp::DatabaseRotatingImp(
void
DatabaseRotatingImp::rotate(
std::unique_ptr<NodeStore::Backend>&& newBackend,
std::unique_ptr<node_store::Backend>&& newBackend,
std::function<void(std::string const& writableName, std::string const& archiveName)> const& f)
{
// Pass these two names to the callback function
@@ -52,7 +52,7 @@ DatabaseRotatingImp::rotate(
// Hold on to current archive backend pointer until after the
// callback finishes. Only then will the archive directory be
// deleted.
std::shared_ptr<NodeStore::Backend> oldArchiveBackend;
std::shared_ptr<node_store::Backend> oldArchiveBackend;
std::uint64_t copyForwards = 0;
{
std::scoped_lock const lock(mutex_);
@@ -232,4 +232,4 @@ DatabaseRotatingImp::forEach(std::function<void(std::shared_ptr<NodeObject>)> f)
archive->forEach(f);
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -10,7 +10,7 @@
#include <memory>
#include <utility>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
DecodedBlob::DecodedBlob(void const* key, void const* value, int valueBytes) : key_(key)
{
@@ -55,7 +55,7 @@ DecodedBlob::DecodedBlob(void const* key, void const* value, int valueBytes) : k
std::shared_ptr<NodeObject>
DecodedBlob::createObject()
{
XRPL_ASSERT(success_, "xrpl::NodeStore::DecodedBlob::createObject : valid object type");
XRPL_ASSERT(success_, "xrpl::node_store::DecodedBlob::createObject : valid object type");
std::shared_ptr<NodeObject> object;
@@ -69,4 +69,4 @@ DecodedBlob::createObject()
return object;
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -3,7 +3,7 @@
#include <xrpl/nodestore/Scheduler.h>
#include <xrpl/nodestore/Task.h>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
void
DummyScheduler::scheduleTask(Task& task)
@@ -22,4 +22,4 @@ DummyScheduler::onBatchWrite(BatchWriteReport const& report)
{
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -22,7 +22,7 @@
#include <string>
#include <utility>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
ManagerImp&
ManagerImp::instance()
@@ -112,7 +112,7 @@ ManagerImp::erase(Factory& factory)
std::scoped_lock const _(mutex_);
auto const iter =
std::ranges::find_if(list_, [&factory](Factory* other) { return other == &factory; });
XRPL_ASSERT(iter != list_.end(), "xrpl::NodeStore::ManagerImp::erase : valid input");
XRPL_ASSERT(iter != list_.end(), "xrpl::node_store::ManagerImp::erase : valid input");
list_.erase(iter);
}
@@ -135,4 +135,4 @@ Manager::instance()
return ManagerImp::instance();
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -24,7 +24,7 @@
#include <tuple>
#include <utility>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
struct MemoryDB
{
@@ -132,7 +132,7 @@ public:
Status
fetch(uint256 const& hash, std::shared_ptr<NodeObject>* pObject) override
{
XRPL_ASSERT(db_, "xrpl::NodeStore::MemoryBackend::fetch : non-null database");
XRPL_ASSERT(db_, "xrpl::node_store::MemoryBackend::fetch : non-null database");
std::scoped_lock const _(db_->mutex);
@@ -149,7 +149,7 @@ public:
void
store(std::shared_ptr<NodeObject> const& object) override
{
XRPL_ASSERT(db_, "xrpl::NodeStore::MemoryBackend::store : non-null database");
XRPL_ASSERT(db_, "xrpl::node_store::MemoryBackend::store : non-null database");
std::scoped_lock const _(db_->mutex);
db_->table.emplace(object->getHash(), object);
}
@@ -169,7 +169,7 @@ public:
void
forEach(std::function<void(std::shared_ptr<NodeObject>)> f) override
{
XRPL_ASSERT(db_, "xrpl::NodeStore::MemoryBackend::forEach : non-null database");
XRPL_ASSERT(db_, "xrpl::node_store::MemoryBackend::forEach : non-null database");
for (auto const& e : db_->table)
f(e.second);
}
@@ -216,4 +216,4 @@ MemoryFactory::createInstance(
return std::make_unique<MemoryBackend>(keyBytes, keyValues, journal);
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -44,7 +44,7 @@
#include <string>
#include <utility>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
class NuDBBackend : public Backend
{
@@ -136,7 +136,7 @@ public:
{
// LCOV_EXCL_START
UNREACHABLE(
"xrpl::NodeStore::NuDBBackend::open : database is already "
"xrpl::node_store::NuDBBackend::open : database is already "
"open");
JLOG(j.error()) << "database is already open";
return;
@@ -441,4 +441,4 @@ registerNuDBFactory(Manager& manager)
static NuDBFactory const kInstance{manager};
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -13,7 +13,7 @@
#include <memory>
#include <string>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
class NullBackend : public Backend
{
@@ -125,4 +125,4 @@ registerNullFactory(Manager& manager)
static NullFactory const kInstance{manager};
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -42,7 +42,7 @@
#include <stdexcept>
#include <string>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
class RocksDBEnv : public rocksdb::EnvWrapper
{
@@ -231,7 +231,7 @@ public:
{
// LCOV_EXCL_START
UNREACHABLE(
"xrpl::NodeStore::RocksDBBackend::open : database is already "
"xrpl::node_store::RocksDBBackend::open : database is already "
"open");
JLOG(journal.error()) << "database is already open";
return;
@@ -279,7 +279,7 @@ public:
Status
fetch(uint256 const& hash, std::shared_ptr<NodeObject>* pObject) override
{
XRPL_ASSERT(db, "xrpl::NodeStore::RocksDBBackend::fetch : non-null database");
XRPL_ASSERT(db, "xrpl::node_store::RocksDBBackend::fetch : non-null database");
pObject->reset();
Status status = Status::Ok;
@@ -339,7 +339,7 @@ public:
{
XRPL_ASSERT(
db,
"xrpl::NodeStore::RocksDBBackend::storeBatch : non-null "
"xrpl::node_store::RocksDBBackend::storeBatch : non-null "
"database");
rocksdb::WriteBatch wb;
@@ -369,7 +369,7 @@ public:
void
forEach(std::function<void(std::shared_ptr<NodeObject>)> f) override
{
XRPL_ASSERT(db, "xrpl::NodeStore::RocksDBBackend::forEach : non-null database");
XRPL_ASSERT(db, "xrpl::node_store::RocksDBBackend::forEach : non-null database");
rocksdb::ReadOptions const options;
std::unique_ptr<rocksdb::Iterator> it(db->NewIterator(options));
@@ -468,6 +468,6 @@ registerRocksDBFactory(Manager& manager)
static RocksDBFactory const kInstance{manager};
}
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store
#endif

View File

@@ -489,7 +489,7 @@ public:
lastRotated = ledgerSeq - 1;
}
std::unique_ptr<NodeStore::Backend>
std::unique_ptr<node_store::Backend>
makeBackendRotating(jtx::Env& env, NodeStoreScheduler& scheduler, std::string path)
{
Section section{env.app().config().section(Sections::kNodeDatabase)};
@@ -500,7 +500,7 @@ public:
newPath = path;
section.set(Keys::kPath, newPath.string());
auto backend{NodeStore::Manager::instance().makeBackend(
auto backend{node_store::Manager::instance().makeBackend(
section,
megabytes(env.app().config().getValueFor(SizedItem::BurstSize, std::nullopt)),
scheduler,
@@ -549,7 +549,7 @@ public:
auto archiveBackend = makeBackendRotating(env, scheduler, archiveDb);
static constexpr int kReadThreads = 4;
auto dbr = std::make_unique<NodeStore::DatabaseRotatingImp>(
auto dbr = std::make_unique<node_store::DatabaseRotatingImp>(
scheduler,
kReadThreads,
std::move(writableBackend),

View File

@@ -1,110 +0,0 @@
#include <test/nodestore/TestBase.h>
#include <test/unit_test/SuiteJournal.h>
#include <xrpl/basics/ByteUtilities.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/temp_dir.h>
#include <xrpl/beast/xor_shift_engine.h>
#include <xrpl/config/BasicConfig.h>
#include <xrpl/config/Constants.h>
#include <xrpl/nodestore/Backend.h>
#include <xrpl/nodestore/DummyScheduler.h>
#include <xrpl/nodestore/Manager.h>
#include <xrpl/nodestore/Types.h>
#include <algorithm>
#include <cstdint>
#include <memory>
#include <string>
namespace xrpl::NodeStore {
// Tests the Backend interface
//
class Backend_test : public TestBase
{
public:
void
testBackend(std::string const& type, std::uint64_t const seedValue, int numObjsToTest = 2000)
{
DummyScheduler scheduler;
testcase("Backend type=" + type);
Section params;
beast::TempDir const tempDir;
params.set(Keys::kType, type);
params.set(Keys::kPath, tempDir.path());
beast::xor_shift_engine rng(seedValue);
// Create a batch
auto batch = createPredictableBatch(numObjsToTest, rng());
using beast::Severity;
test::SuiteJournal journal("Backend_test", *this);
{
// Open the backend
std::unique_ptr<Backend> backend =
Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
backend->open();
// Write the batch
storeBatch(*backend, batch);
{
// Read it back in
Batch copy;
fetchCopyOfBatch(*backend, &copy, batch);
BEAST_EXPECT(areBatchesEqual(batch, copy));
}
{
// Reorder and read the copy again
std::shuffle(batch.begin(), batch.end(), rng);
Batch copy;
fetchCopyOfBatch(*backend, &copy, batch);
BEAST_EXPECT(areBatchesEqual(batch, copy));
}
}
{
// Re-open the backend
std::unique_ptr<Backend> backend =
Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
backend->open();
// Read it back in
Batch copy;
fetchCopyOfBatch(*backend, &copy, batch);
// Canonicalize the source and destination batches
std::ranges::sort(batch, LessThan{});
std::ranges::sort(copy, LessThan{});
BEAST_EXPECT(areBatchesEqual(batch, copy));
}
}
//--------------------------------------------------------------------------
void
run() override
{
std::uint64_t const seedValue = 50;
testBackend("nudb", seedValue);
#if XRPL_ROCKSDB_AVAILABLE
testBackend("rocksdb", seedValue);
#endif
#ifdef XRPL_ENABLE_SQLITE_BACKEND_TESTS
testBackend("sqlite", seedValue);
#endif
}
};
BEAST_DEFINE_TESTSUITE(Backend, nodestore, xrpl);
} // namespace xrpl::NodeStore

View File

@@ -1,73 +0,0 @@
#include <test/nodestore/TestBase.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/nodestore/NodeObject.h>
#include <xrpl/nodestore/detail/DecodedBlob.h>
#include <xrpl/nodestore/detail/EncodedBlob.h>
#include <cstdint>
#include <memory>
namespace xrpl::NodeStore {
// Tests predictable batches, and NodeObject blob encoding
//
class NodeStoreBasic_test : public TestBase
{
public:
// Make sure predictable object generation works!
void
testBatches(std::uint64_t const seedValue)
{
testcase("batch");
auto batch1 = createPredictableBatch(kNumObjectsToTest, seedValue);
auto batch2 = createPredictableBatch(kNumObjectsToTest, seedValue);
BEAST_EXPECT(areBatchesEqual(batch1, batch2));
auto batch3 = createPredictableBatch(kNumObjectsToTest, seedValue + 1);
BEAST_EXPECT(!areBatchesEqual(batch1, batch3));
}
// Checks encoding/decoding blobs
void
testBlobs(std::uint64_t const seedValue)
{
testcase("encoding");
auto batch = createPredictableBatch(kNumObjectsToTest, seedValue);
for (auto const& expected : batch)
{
EncodedBlob const encoded(expected);
DecodedBlob decoded(encoded.getKey(), encoded.getData(), encoded.getSize());
BEAST_EXPECT(decoded.wasOk());
if (decoded.wasOk())
{
std::shared_ptr<NodeObject> const object(decoded.createObject());
BEAST_EXPECT(isSame(expected, object));
}
}
}
void
run() override
{
std::uint64_t const seedValue = 50;
testBatches(seedValue);
testBlobs(seedValue);
}
};
BEAST_DEFINE_TESTSUITE(NodeStoreBasic, nodestore, xrpl);
} // namespace xrpl::NodeStore

View File

@@ -1,44 +1,21 @@
#include <test/jtx/CheckMessageLogs.h>
#include <test/jtx/Env.h>
#include <test/jtx/envconfig.h>
#include <test/nodestore/TestBase.h>
#include <test/unit_test/SuiteJournal.h>
#include <xrpld/core/Config.h>
#include <xrpl/basics/ByteUtilities.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/temp_dir.h>
#include <xrpl/beast/xor_shift_engine.h>
#include <xrpl/config/BasicConfig.h>
#include <xrpl/config/Constants.h>
#include <xrpl/nodestore/Database.h>
#include <xrpl/nodestore/DummyScheduler.h>
#include <xrpl/nodestore/Manager.h>
#include <xrpl/nodestore/Types.h>
#include <xrpl/protocol/SystemParameters.h>
#include <xrpl/rdb/DatabaseCon.h>
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <memory>
#include <stdexcept>
#include <string>
#include <utility>
namespace xrpl::NodeStore {
namespace xrpl::node_store {
class Database_test : public TestBase
class DatabaseConfig_test : public beast::unit_test::Suite
{
test::SuiteJournal journal_;
public:
Database_test() : journal_("Database_test", *this)
{
}
void
testConfig()
{
@@ -73,8 +50,8 @@ public:
Env env = [&]() {
auto p = test::jtx::envconfig();
{
auto& section = p->section(Sections::kSqlite);
section.set(Keys::kSafetyLevel, "high");
auto& section = p->section("sqlite");
section.set("safety_level", "high");
}
p->ledgerHistory = 100'000'000;
@@ -102,8 +79,8 @@ public:
Env env = [&]() {
auto p = test::jtx::envconfig();
{
auto& section = p->section(Sections::kSqlite);
section.set(Keys::kSafetyLevel, "low");
auto& section = p->section("sqlite");
section.set("safety_level", "low");
}
p->ledgerHistory = 100'000'000;
@@ -131,10 +108,10 @@ public:
Env env = [&]() {
auto p = test::jtx::envconfig();
{
auto& section = p->section(Sections::kSqlite);
section.set(Keys::kJournalMode, "off");
section.set(Keys::kSynchronous, "extra");
section.set(Keys::kTempStore, "default");
auto& section = p->section("sqlite");
section.set("journal_mode", "off");
section.set("synchronous", "extra");
section.set("temp_store", "default");
}
return Env(
@@ -145,7 +122,7 @@ public:
}();
// No warning, even though higher risk settings were used because
// LEDGER_HISTORY is small
// ledgerHistory is small
BEAST_EXPECT(!found);
auto const s = setupDatabaseCon(env.app().config());
if (BEAST_EXPECT(s.globalPragma->size() == 3))
@@ -163,10 +140,10 @@ public:
Env env = [&]() {
auto p = test::jtx::envconfig();
{
auto& section = p->section(Sections::kSqlite);
section.set(Keys::kJournalMode, "off");
section.set(Keys::kSynchronous, "extra");
section.set(Keys::kTempStore, "default");
auto& section = p->section("sqlite");
section.set("journal_mode", "off");
section.set("synchronous", "extra");
section.set("temp_store", "default");
}
p->ledgerHistory = 50'000'000;
@@ -178,7 +155,7 @@ public:
}();
// No warning, even though higher risk settings were used because
// LEDGER_HISTORY is small
// ledgerHistory is small
BEAST_EXPECT(found);
auto const s = setupDatabaseCon(env.app().config());
if (BEAST_EXPECT(s.globalPragma->size() == 3))
@@ -199,11 +176,11 @@ public:
auto p = test::jtx::envconfig();
{
auto& section = p->section(Sections::kSqlite);
section.set(Keys::kSafetyLevel, "low");
section.set(Keys::kJournalMode, "off");
section.set(Keys::kSynchronous, "extra");
section.set(Keys::kTempStore, "default");
auto& section = p->section("sqlite");
section.set("safety_level", "low");
section.set("journal_mode", "off");
section.set("synchronous", "extra");
section.set("temp_store", "default");
}
try
@@ -230,9 +207,9 @@ public:
auto p = test::jtx::envconfig();
{
auto& section = p->section(Sections::kSqlite);
section.set(Keys::kSafetyLevel, "high");
section.set(Keys::kJournalMode, "off");
auto& section = p->section("sqlite");
section.set("safety_level", "high");
section.set("journal_mode", "off");
}
try
@@ -259,9 +236,9 @@ public:
auto p = test::jtx::envconfig();
{
auto& section = p->section(Sections::kSqlite);
section.set(Keys::kSafetyLevel, "low");
section.set(Keys::kSynchronous, "extra");
auto& section = p->section("sqlite");
section.set("safety_level", "low");
section.set("synchronous", "extra");
}
try
@@ -288,9 +265,9 @@ public:
auto p = test::jtx::envconfig();
{
auto& section = p->section(Sections::kSqlite);
section.set(Keys::kSafetyLevel, "high");
section.set(Keys::kTempStore, "default");
auto& section = p->section("sqlite");
section.set("safety_level", "high");
section.set("temp_store", "default");
}
try
@@ -317,8 +294,8 @@ public:
auto p = test::jtx::envconfig();
{
auto& section = p->section(Sections::kSqlite);
section.set(Keys::kSafetyLevel, "slow");
auto& section = p->section("sqlite");
section.set("safety_level", "slow");
}
try
@@ -345,8 +322,8 @@ public:
auto p = test::jtx::envconfig();
{
auto& section = p->section(Sections::kSqlite);
section.set(Keys::kJournalMode, "fast");
auto& section = p->section("sqlite");
section.set("journal_mode", "fast");
}
try
@@ -373,8 +350,8 @@ public:
auto p = test::jtx::envconfig();
{
auto& section = p->section(Sections::kSqlite);
section.set(Keys::kSynchronous, "instant");
auto& section = p->section("sqlite");
section.set("synchronous", "instant");
}
try
@@ -401,8 +378,8 @@ public:
auto p = test::jtx::envconfig();
{
auto& section = p->section(Sections::kSqlite);
section.set(Keys::kTempStore, "network");
auto& section = p->section("sqlite");
section.set("temp_store", "network");
}
try
@@ -436,9 +413,9 @@ public:
Env env = [&]() {
auto p = test::jtx::envconfig();
{
auto& section = p->section(Sections::kSqlite);
section.set(Keys::kPageSize, "512");
section.set(Keys::kJournalSizeLimit, "2582080");
auto& section = p->section("sqlite");
section.set("page_size", "512");
section.set("journal_size_limit", "2582080");
}
return Env(*this, std::move(p));
}();
@@ -457,8 +434,8 @@ public:
bool found = false;
auto p = test::jtx::envconfig();
{
auto& section = p->section(Sections::kSqlite);
section.set(Keys::kPageSize, "256");
auto& section = p->section("sqlite");
section.set("page_size", "256");
}
try
{
@@ -480,8 +457,8 @@ public:
bool found = false;
auto p = test::jtx::envconfig();
{
auto& section = p->section(Sections::kSqlite);
section.set(Keys::kPageSize, "131072");
auto& section = p->section("sqlite");
section.set("page_size", "131072");
}
try
{
@@ -503,8 +480,8 @@ public:
bool found = false;
auto p = test::jtx::envconfig();
{
auto& section = p->section(Sections::kSqlite);
section.set(Keys::kPageSize, "513");
auto& section = p->section("sqlite");
section.set("page_size", "513");
}
try
{
@@ -522,208 +499,13 @@ public:
}
}
//--------------------------------------------------------------------------
void
testImport(
std::string const& destBackendType,
std::string const& srcBackendType,
std::int64_t seedValue)
{
DummyScheduler scheduler;
beast::TempDir const nodeDb;
Section srcParams;
srcParams.set(Keys::kType, srcBackendType);
srcParams.set(Keys::kPath, nodeDb.path());
// Create a batch
auto batch = createPredictableBatch(kNumObjectsToTest, seedValue);
// Write to source db
{
std::unique_ptr<Database> src =
Manager::instance().makeDatabase(megabytes(4), scheduler, 2, srcParams, journal_);
storeBatch(*src, batch);
}
Batch copy;
{
// Re-open the db
std::unique_ptr<Database> src =
Manager::instance().makeDatabase(megabytes(4), scheduler, 2, srcParams, journal_);
// Set up the destination database
beast::TempDir const destDb;
Section destParams;
destParams.set(Keys::kType, destBackendType);
destParams.set(Keys::kPath, destDb.path());
std::unique_ptr<Database> dest =
Manager::instance().makeDatabase(megabytes(4), scheduler, 2, destParams, journal_);
testcase("import into '" + destBackendType + "' from '" + srcBackendType + "'");
// Do the import
dest->importDatabase(*src);
// Get the results of the import
fetchCopyOfBatch(*dest, &copy, batch);
}
// Canonicalize the source and destination batches
std::ranges::sort(batch, LessThan{});
std::ranges::sort(copy, LessThan{});
BEAST_EXPECT(areBatchesEqual(batch, copy));
}
//--------------------------------------------------------------------------
void
testNodeStore(
std::string const& type,
bool const testPersistence,
std::int64_t const seedValue,
int numObjsToTest = 2000)
{
DummyScheduler scheduler;
std::string const s = "NodeStore backend '" + type + "'";
testcase(s);
beast::TempDir const nodeDb;
Section nodeParams;
nodeParams.set(Keys::kType, type);
nodeParams.set(Keys::kPath, nodeDb.path());
beast::xor_shift_engine rng(seedValue);
// Create a batch
auto batch = createPredictableBatch(numObjsToTest, rng());
{
// Open the database
std::unique_ptr<Database> db =
Manager::instance().makeDatabase(megabytes(4), scheduler, 2, nodeParams, journal_);
// Write the batch
storeBatch(*db, batch);
{
// Read it back in
Batch copy;
fetchCopyOfBatch(*db, &copy, batch);
BEAST_EXPECT(areBatchesEqual(batch, copy));
}
{
// Reorder and read the copy again
std::shuffle(batch.begin(), batch.end(), rng);
Batch copy;
fetchCopyOfBatch(*db, &copy, batch);
BEAST_EXPECT(areBatchesEqual(batch, copy));
}
}
if (testPersistence)
{
// Re-open the database without the ephemeral DB
std::unique_ptr<Database> db =
Manager::instance().makeDatabase(megabytes(4), scheduler, 2, nodeParams, journal_);
// Read it back in
Batch copy;
fetchCopyOfBatch(*db, &copy, batch);
// Canonicalize the source and destination batches
std::ranges::sort(batch, LessThan{});
std::ranges::sort(copy, LessThan{});
BEAST_EXPECT(areBatchesEqual(batch, copy));
}
if (type == "memory")
{
// Verify default earliest ledger sequence
{
std::unique_ptr<Database> db = Manager::instance().makeDatabase(
megabytes(4), scheduler, 2, nodeParams, journal_);
BEAST_EXPECT(db->earliestLedgerSeq() == kXrpLedgerEarliestSeq);
}
// Set an invalid earliest ledger sequence
try
{
nodeParams.set(Keys::kEarliestSeq, "0");
std::unique_ptr<Database> const db = Manager::instance().makeDatabase(
megabytes(4), scheduler, 2, nodeParams, journal_);
}
catch (std::runtime_error const& e)
{
BEAST_EXPECT(std::strcmp(e.what(), "Invalid earliest_seq") == 0);
}
{
// Set a valid earliest ledger sequence
nodeParams.set(Keys::kEarliestSeq, "1");
std::unique_ptr<Database> db = Manager::instance().makeDatabase(
megabytes(4), scheduler, 2, nodeParams, journal_);
// Verify database uses the earliest ledger sequence setting
BEAST_EXPECT(db->earliestLedgerSeq() == 1);
}
// Create another database that attempts to set the value again
try
{
// Set to default earliest ledger sequence
nodeParams.set(Keys::kEarliestSeq, std::to_string(kXrpLedgerEarliestSeq));
std::unique_ptr<Database> const db2 = Manager::instance().makeDatabase(
megabytes(4), scheduler, 2, nodeParams, journal_);
}
catch (std::runtime_error const& e)
{
BEAST_EXPECT(std::strcmp(e.what(), "earliest_seq set more than once") == 0);
}
}
}
//--------------------------------------------------------------------------
void
run() override
{
std::int64_t const seedValue = 50;
testConfig();
testNodeStore("memory", false, seedValue);
// Persistent backend tests
{
testNodeStore("nudb", true, seedValue);
#if XRPL_ROCKSDB_AVAILABLE
testNodeStore("rocksdb", true, seedValue);
#endif
}
// Import tests
{
testImport("nudb", "nudb", seedValue);
#if XRPL_ROCKSDB_AVAILABLE
testImport("rocksdb", "rocksdb", seedValue);
#endif
#if XRPL_ENABLE_SQLITE_BACKEND_TESTS
testImport("sqlite", "sqlite", seedValue);
#endif
}
}
};
BEAST_DEFINE_TESTSUITE(Database, nodestore, xrpl);
BEAST_DEFINE_TESTSUITE(DatabaseConfig, nodestore, xrpl);
} // namespace xrpl::NodeStore
} // namespace xrpl::node_store

View File

@@ -1,443 +0,0 @@
#include <test/nodestore/TestBase.h>
#include <test/unit_test/SuiteJournal.h>
#include <xrpl/basics/ByteUtilities.h>
#include <xrpl/basics/Number.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/temp_dir.h>
#include <xrpl/config/BasicConfig.h>
#include <xrpl/config/Constants.h>
#include <xrpl/nodestore/DummyScheduler.h>
#include <xrpl/nodestore/Manager.h>
#include <xrpl/nodestore/Types.h>
#include <cstddef>
#include <exception>
#include <memory>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
namespace xrpl::NodeStore {
class NuDBFactory_test : public TestBase
{
private:
// Helper function to create a Section with specified parameters
static Section
createSection(std::string const& path, std::string const& blockSize = "")
{
Section params;
params.set(Keys::kType, "nudb");
params.set(Keys::kPath, path);
if (!blockSize.empty())
params.set(Keys::kNudbBlockSize, blockSize);
return params;
}
// Helper function to create a backend and test basic functionality
bool
testBackendFunctionality(Section const& params, std::size_t expectedBlocksize)
{
try
{
DummyScheduler scheduler;
test::SuiteJournal journal("NuDBFactory_test", *this);
auto backend =
Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
if (!BEAST_EXPECT(backend))
return false;
if (!BEAST_EXPECT(backend->getBlockSize() == expectedBlocksize))
return false;
backend->open();
if (!BEAST_EXPECT(backend->isOpen()))
return false;
// Test basic store/fetch functionality
auto batch = createPredictableBatch(10, 12345);
storeBatch(*backend, batch);
Batch copy;
fetchCopyOfBatch(*backend, &copy, batch);
backend->close();
return areBatchesEqual(batch, copy);
}
catch (...)
{
return false;
}
}
// Helper function to test log messages
void
testLogMessage(Section const& params, beast::Severity level, std::string const& expectedMessage)
{
test::StreamSink sink(level);
beast::Journal const journal(sink);
DummyScheduler scheduler;
auto backend = Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
std::string const logOutput = sink.messages().str();
BEAST_EXPECT(logOutput.contains(expectedMessage));
}
// Helper function to test power of two validation
void
testPowerOfTwoValidation(std::string const& size, bool shouldWork)
{
beast::TempDir const tempDir;
auto params = createSection(tempDir.path(), size);
test::StreamSink sink(beast::Severity::Warning);
beast::Journal const journal(sink);
DummyScheduler scheduler;
auto backend = Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
std::string const logOutput = sink.messages().str();
bool const hasWarning = logOutput.contains("Invalid nudb_block_size");
BEAST_EXPECT(hasWarning == !shouldWork);
}
public:
void
testDefaultBlockSize()
{
testcase("Default block size (no nudb_block_size specified)");
beast::TempDir const tempDir;
auto params = createSection(tempDir.path());
// Should work with default 4096 block size
BEAST_EXPECT(testBackendFunctionality(params, 4096));
}
void
testValidBlockSizes()
{
testcase("Valid block sizes");
std::vector<std::size_t> const validSizes = {4096, 8192, 16384, 32768};
for (auto const& size : validSizes)
{
beast::TempDir const tempDir;
auto params = createSection(tempDir.path(), to_string(size));
BEAST_EXPECT(testBackendFunctionality(params, size));
}
// Empty value is ignored by the config parser, so uses the
// default
beast::TempDir const tempDir;
auto params = createSection(tempDir.path(), "");
BEAST_EXPECT(testBackendFunctionality(params, 4096));
}
void
testInvalidBlockSizes()
{
testcase("Invalid block sizes");
std::vector<std::string> const invalidSizes = {
"2048", // Too small
"1024", // Too small
"65536", // Too large
"131072", // Too large
"5000", // Not power of 2
"6000", // Not power of 2
"10000", // Not power of 2
"0", // Zero
"-1", // Negative
"abc", // Non-numeric
"4k", // Invalid format
"4096.5" // Decimal
};
for (auto const& size : invalidSizes)
{
beast::TempDir const tempDir;
auto params = createSection(tempDir.path(), size);
// Fails
BEAST_EXPECT(!testBackendFunctionality(params, 4096));
}
// Test whitespace cases separately since lexical_cast may handle them
std::vector<std::string> const whitespaceInvalidSizes = {
"4096 ", // Trailing space - might be handled by lexical_cast
" 4096" // Leading space - might be handled by lexical_cast
};
for (auto const& size : whitespaceInvalidSizes)
{
beast::TempDir const tempDir;
auto params = createSection(tempDir.path(), size);
// Fails
BEAST_EXPECT(!testBackendFunctionality(params, 4096));
}
}
void
testLogMessages()
{
testcase("Log message verification");
// Test valid custom block size logging
{
beast::TempDir const tempDir;
auto params = createSection(tempDir.path(), "8192");
testLogMessage(params, beast::Severity::Info, "Using custom NuDB block size: 8192");
}
// Test invalid block size failure
{
beast::TempDir const tempDir;
auto params = createSection(tempDir.path(), "5000");
test::StreamSink sink(beast::Severity::Warning);
beast::Journal const journal(sink);
DummyScheduler scheduler;
try
{
auto backend =
Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
fail();
}
catch (std::exception const& e)
{
std::string const logOutput{e.what()};
BEAST_EXPECT(logOutput.contains("Invalid nudb_block_size: 5000"));
BEAST_EXPECT(logOutput.contains("Must be power of 2 between 4096 and 32768"));
}
}
// Test non-numeric value failure
{
beast::TempDir const tempDir;
auto params = createSection(tempDir.path(), "invalid");
test::StreamSink sink(beast::Severity::Warning);
beast::Journal const journal(sink);
DummyScheduler scheduler;
try
{
auto backend =
Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
fail();
}
catch (std::exception const& e)
{
std::string const logOutput{e.what()};
BEAST_EXPECT(logOutput.contains("Invalid nudb_block_size value: invalid"));
}
}
}
void
testPowerOfTwoValidation()
{
testcase("Power of 2 validation logic");
// Test edge cases around valid range
std::vector<std::pair<std::string, bool>> const testCases = {
{"4095", false}, // Just below minimum
{"4096", true}, // Minimum valid
{"4097", false}, // Just above minimum, not power of 2
{"8192", true}, // Valid power of 2
{"8193", false}, // Just above valid power of 2
{"16384", true}, // Valid power of 2
{"32768", true}, // Maximum valid
{"32769", false}, // Just above maximum
{"65536", false} // Power of 2 but too large
};
for (auto const& [size, shouldWork] : testCases)
{
beast::TempDir const tempDir;
auto params = createSection(tempDir.path(), size);
// We test the validation logic by catching exceptions for invalid
// values
test::StreamSink sink(beast::Severity::Warning);
beast::Journal const journal(sink);
DummyScheduler scheduler;
try
{
auto backend =
Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
BEAST_EXPECT(shouldWork);
}
catch (std::exception const& e)
{
std::string const logOutput{e.what()};
BEAST_EXPECT(logOutput.contains("Invalid nudb_block_size"));
}
}
}
void
testBothConstructorVariants()
{
testcase("Both constructor variants work with custom block size");
beast::TempDir const tempDir;
auto params = createSection(tempDir.path(), "16384");
DummyScheduler scheduler;
test::SuiteJournal journal("NuDBFactory_test", *this);
// Test first constructor (without nudb::context)
{
auto backend1 =
Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
BEAST_EXPECT(backend1 != nullptr);
BEAST_EXPECT(testBackendFunctionality(params, 16384));
}
// Test second constructor (with nudb::context)
// Note: This would require access to nudb::context, which might not be
// easily testable without more complex setup. For now, we test that
// the factory can create backends with the first constructor.
}
void
testConfigurationParsing()
{
testcase("Configuration parsing edge cases");
// Test that whitespace is handled correctly
std::vector<std::string> const validFormats = {
"8192" // Basic valid format
};
// Test whitespace handling separately since lexical_cast behavior may
// vary
std::vector<std::string> const whitespaceFormats = {
" 8192", // Leading space - may or may not be handled by
// lexical_cast
"8192 " // Trailing space - may or may not be handled by
// lexical_cast
};
// Test basic valid format
for (auto const& format : validFormats)
{
beast::TempDir const tempDir;
auto params = createSection(tempDir.path(), format);
test::StreamSink sink(beast::Severity::Info);
beast::Journal const journal(sink);
DummyScheduler scheduler;
auto backend =
Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
// Should log success message for valid values
std::string const logOutput = sink.messages().str();
bool const hasSuccessMessage = logOutput.contains("Using custom NuDB block size");
BEAST_EXPECT(hasSuccessMessage);
}
// Test whitespace formats - these should work if lexical_cast handles
// them
for (auto const& format : whitespaceFormats)
{
beast::TempDir const tempDir;
auto params = createSection(tempDir.path(), format);
// Use a lower threshold to capture both info and warning messages
test::StreamSink sink(beast::Severity::Debug);
beast::Journal const journal(sink);
DummyScheduler scheduler;
try
{
auto backend =
Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
fail();
}
catch (...)
{
// Fails
BEAST_EXPECT(!testBackendFunctionality(params, 8192));
}
}
}
void
testDataPersistence()
{
testcase("Data persistence with different block sizes");
std::vector<std::string> const blockSizes = {"4096", "8192", "16384", "32768"};
for (auto const& size : blockSizes)
{
beast::TempDir const tempDir;
auto params = createSection(tempDir.path(), size);
DummyScheduler scheduler;
test::SuiteJournal journal("NuDBFactory_test", *this);
// Create test data
auto batch = createPredictableBatch(50, 54321);
// Store data
{
auto backend =
Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
backend->open();
storeBatch(*backend, batch);
backend->close();
}
// Retrieve data in new backend instance
{
auto backend =
Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
backend->open();
Batch copy;
fetchCopyOfBatch(*backend, &copy, batch);
BEAST_EXPECT(areBatchesEqual(batch, copy));
backend->close();
}
}
}
void
run() override
{
testDefaultBlockSize();
testValidBlockSizes();
testInvalidBlockSizes();
testLogMessages();
testPowerOfTwoValidation();
testBothConstructorVariants();
testConfigurationParsing();
testDataPersistence();
}
};
BEAST_DEFINE_TESTSUITE(NuDBFactory, xrpl_core, xrpl);
} // namespace xrpl::NodeStore

View File

@@ -1,202 +0,0 @@
#pragma once
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/random.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/beast/utility/rngfill.h>
#include <xrpl/beast/xor_shift_engine.h>
#include <xrpl/nodestore/Backend.h>
#include <xrpl/nodestore/Database.h>
#include <xrpl/nodestore/NodeObject.h>
#include <xrpl/nodestore/Types.h>
#include <boost/algorithm/string.hpp>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <utility>
namespace xrpl::NodeStore {
/**
* Binary function that satisfies the strict-weak-ordering requirement.
*
* This compares the hashes of both objects and returns true if
* the first hash is considered to go before the second.
*
* @see std::sort
*/
struct LessThan
{
bool
operator()(std::shared_ptr<NodeObject> const& lhs, std::shared_ptr<NodeObject> const& rhs)
const noexcept
{
return lhs->getHash() < rhs->getHash();
}
};
/**
* Returns `true` if objects are identical.
*/
inline bool
isSame(std::shared_ptr<NodeObject> const& lhs, std::shared_ptr<NodeObject> const& rhs)
{
return (lhs->getType() == rhs->getType()) && (lhs->getHash() == rhs->getHash()) &&
(lhs->getData() == rhs->getData());
}
// Some common code for the unit tests
//
class TestBase : public beast::unit_test::Suite
{
public:
// Tunable parameters
//
static std::size_t const kMinPayloadBytes = 1;
static std::size_t const kMaxPayloadBytes = 2000;
static int const kNumObjectsToTest = 2000;
public:
// Create a predictable batch of objects
static Batch
createPredictableBatch(int numObjects, std::uint64_t seed)
{
Batch batch;
batch.reserve(numObjects);
beast::xor_shift_engine rng(seed);
for (int i = 0; i < numObjects; ++i)
{
NodeObjectType const type = [&] {
switch (randInt(rng, 3))
{
case 0:
return NodeObjectType::Ledger;
case 1:
return NodeObjectType::AccountNode;
case 2:
return NodeObjectType::TransactionNode;
case 3:
default:
return NodeObjectType::Unknown;
}
}();
uint256 hash;
beast::rngfill(hash.begin(), hash.size(), rng);
Blob blob(randInt(rng, kMinPayloadBytes, kMaxPayloadBytes));
beast::rngfill(blob.data(), blob.size(), rng);
batch.push_back(NodeObject::createObject(type, std::move(blob), hash));
}
return batch;
}
// Compare two batches for equality
static bool
areBatchesEqual(Batch const& lhs, Batch const& rhs)
{
bool result = true;
if (lhs.size() == rhs.size())
{
for (int i = 0; i < lhs.size(); ++i)
{
if (!isSame(lhs[i], rhs[i]))
{
result = false;
break;
}
}
}
else
{
result = false;
}
return result;
}
// Store a batch in a backend
static void
storeBatch(Backend& backend, Batch const& batch)
{
for (auto const& object : batch)
{
backend.store(object);
}
}
// Get a copy of a batch in a backend
void
fetchCopyOfBatch(Backend& backend, Batch* pCopy, Batch const& batch)
{
pCopy->clear();
pCopy->reserve(batch.size());
for (auto const& expected : batch)
{
std::shared_ptr<NodeObject> object;
Status const status = backend.fetch(expected->getHash(), &object);
BEAST_EXPECT(status == Status::Ok);
if (status == Status::Ok)
{
BEAST_EXPECT(object != nullptr);
pCopy->push_back(object);
}
}
}
void
fetchMissing(Backend& backend, Batch const& batch)
{
for (auto const& expected : batch)
{
std::shared_ptr<NodeObject> object;
Status const status = backend.fetch(expected->getHash(), &object);
BEAST_EXPECT(status == Status::NotFound);
}
}
// Store all objects in a batch
static void
storeBatch(Database& db, Batch const& batch)
{
for (auto const& object : batch)
{
Blob data(object->getData());
db.store(object->getType(), std::move(data), object->getHash(), db.earliestLedgerSeq());
}
}
// Fetch all the hashes in one batch, into another batch.
static void
fetchCopyOfBatch(Database& db, Batch* pCopy, Batch const& batch)
{
pCopy->clear();
pCopy->reserve(batch.size());
for (auto const& expected : batch)
{
std::shared_ptr<NodeObject> const object = db.fetchNodeObject(expected->getHash(), 0);
if (object != nullptr)
pCopy->push_back(object);
}
}
};
} // namespace xrpl::NodeStore

View File

@@ -195,7 +195,7 @@ fmtdur(std::chrono::duration<Period, Rep> const& d)
} // namespace detail
namespace NodeStore {
namespace node_store {
//------------------------------------------------------------------------------
@@ -552,5 +552,5 @@ BEAST_DEFINE_TESTSUITE_MANUAL(import, nodestore, xrpl);
//------------------------------------------------------------------------------
} // namespace NodeStore
} // namespace node_store
} // namespace xrpl

View File

@@ -1,57 +0,0 @@
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/nodestore/detail/varint.h>
#include <array>
#include <cstddef>
#include <cstdint>
#include <vector>
namespace xrpl::NodeStore::tests {
class varint_test : public beast::unit_test::Suite
{
public:
void
testVarints(std::vector<std::size_t> vv)
{
testcase("encode, decode");
for (auto const v : vv)
{
std::array<std::uint8_t, varint_traits<std::size_t>::kMax> vi{};
auto const n0 = writeVarint(vi.data(), v);
expect(n0 > 0, "write error");
expect(n0 == sizeVarint(v), "size error");
std::size_t v1 = 0;
auto const n1 = readVarint(vi.data(), n0, v1);
expect(n1 == n0, "read error");
expect(v == v1, "wrong value");
}
}
void
run() override
{
testVarints(
{0,
1,
2,
126,
127,
128,
253,
254,
255,
16127,
16128,
16129,
0xff,
0xffff,
0xffffffff,
0xffffffffffffUL,
0xffffffffffffffffUL});
}
};
BEAST_DEFINE_TESTSUITE(varint, nodestore, xrpl);
} // namespace xrpl::NodeStore::tests

View File

@@ -35,6 +35,7 @@ set(test_modules
shamap
tx
protocol_autogen
nodestore
)
if(NOT WIN32)
list(APPEND test_modules net)

View File

@@ -0,0 +1,50 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <mutex>
#include <sstream>
#include <string>
namespace xrpl::test {
class CaptureSink : public beast::Journal::Sink
{
mutable std::mutex mutex_;
std::stringstream strm_;
public:
explicit CaptureSink(beast::Severity threshold = beast::Severity::Debug)
: Sink{threshold, false}
{
}
void
write(beast::Severity level, std::string const& text) override
{
if (level < threshold())
return;
writeAlways(level, text);
}
void
writeAlways(beast::Severity /*level*/, std::string const& text) override
{
// Journal sinks may be written to concurrently (e.g. from a backend's background workers),
// so serialize access to strm_. write() funnels into writeAlways(), so the lock lives here
// only: locking in both would self-deadlock on this non-recursive mutex.
std::scoped_lock const lock(mutex_);
strm_ << text << '\n';
}
[[nodiscard]] std::string
messages() const
{
// Returns a snapshot of the captured output. Takes the lock so the read is safe even if a
// writer is still active.
std::scoped_lock const lock(mutex_);
return strm_.str();
}
};
} // namespace xrpl::test

View File

@@ -29,11 +29,11 @@ namespace xrpl::test {
class TestFamily : public Family
{
private:
std::unique_ptr<NodeStore::Database> db_;
std::unique_ptr<node_store::Database> db_;
TestStopwatch clock_;
std::shared_ptr<FullBelowCache> fbCache_;
std::shared_ptr<TreeNodeCache> tnCache_;
NodeStore::DummyScheduler scheduler_;
node_store::DummyScheduler scheduler_;
beast::Journal j_;
public:
@@ -51,16 +51,16 @@ public:
Section config;
config.set(Keys::kType, "memory");
config.set(Keys::kPath, "TestFamily");
db_ = NodeStore::Manager::instance().makeDatabase(megabytes(4), scheduler_, 1, config, j);
db_ = node_store::Manager::instance().makeDatabase(megabytes(4), scheduler_, 1, config, j);
}
NodeStore::Database&
node_store::Database&
db() override
{
return *db_;
}
[[nodiscard]] NodeStore::Database const&
[[nodiscard]] node_store::Database const&
db() const override
{
return *db_;

View File

@@ -220,7 +220,7 @@ public:
}
// Storage services
NodeStore::Database&
node_store::Database&
getNodeStore() override
{
throw std::logic_error("TestServiceRegistry::getNodeStore() not implemented");

View File

@@ -0,0 +1,182 @@
#include <xrpl/nodestore/Backend.h>
#include <xrpl/basics/ByteUtilities.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/temp_dir.h>
#include <xrpl/beast/xor_shift_engine.h>
#include <xrpl/config/BasicConfig.h>
#include <xrpl/nodestore/DummyScheduler.h>
#include <xrpl/nodestore/Manager.h>
#include <xrpl/nodestore/NodeObject.h>
#include <xrpl/nodestore/Types.h>
#include <gtest/gtest.h>
#include <helpers/TestSink.h>
#include <nodestore/TestBase.h>
#include <algorithm>
#include <atomic>
#include <cstddef>
#include <memory>
#include <ranges>
#include <string>
#include <thread>
#include <vector>
namespace xrpl::node_store {
namespace {
std::vector<std::string>
backendTypes()
{
std::vector<std::string> types{"nudb"};
#if XRPL_ROCKSDB_AVAILABLE
types.emplace_back("rocksdb");
#endif
#ifdef XRPL_ENABLE_SQLITE_BACKEND_TESTS
types.emplace_back("sqlite");
#endif
return types;
}
// Run work(i) for every i in [0, n) spread across numThreads threads, handing
// out indices via a shared atomic counter (mirrors the old Timing_test
// parallel-for so the N items are partitioned, not duplicated).
template <class Work>
void
parallelFor(std::size_t n, std::size_t numThreads, Work work)
{
std::atomic<std::size_t> next{0};
auto const runner = [&] {
for (std::size_t i = next++; i < n; i = next++)
work(i);
};
auto threads = std::views::iota(std::size_t{0}, numThreads) |
std::views::transform([&](std::size_t) { return std::thread{runner}; }) |
std::ranges::to<std::vector>();
std::ranges::for_each(threads, &std::thread::join);
}
} // namespace
class BackendTypeTest : public ::testing::TestWithParam<std::string>
{
protected:
void
SetUp() override
{
params_.set("type", GetParam());
params_.set("path", tempDir_.path());
beast::xor_shift_engine rng(kSeedValue);
batch_ = createPredictableBatch(kNumObjects, rng());
}
std::unique_ptr<Backend>
makeOpenBackend()
{
auto backend = Manager::instance().makeBackend(params_, megabytes(4), scheduler_, journal_);
backend->open();
return backend;
}
DummyScheduler scheduler_;
beast::TempDir const tempDir_;
beast::Journal const journal_{TestSink::instance()};
Section params_;
Batch batch_;
};
TEST_P(BackendTypeTest, store_and_fetch)
{
auto backend = makeOpenBackend();
storeBatch(*backend, batch_);
{
SCOPED_TRACE("read in original order");
auto const copy = fetchCopyOfBatch(*backend, batch_);
EXPECT_EQ(batch_, copy);
}
{
SCOPED_TRACE("read in shuffled order");
beast::xor_shift_engine rng(kSeedValue);
std::shuffle(batch_.begin(), batch_.end(), rng);
auto const copy = fetchCopyOfBatch(*backend, batch_);
EXPECT_EQ(batch_, copy);
}
}
TEST_P(BackendTypeTest, persists_after_reopen)
{
{
auto backend = makeOpenBackend();
storeBatch(*backend, batch_);
}
// re-open a fresh backend instance over the same path
auto backend = makeOpenBackend();
auto copy = fetchCopyOfBatch(*backend, batch_);
std::ranges::sort(batch_, LessThan{});
std::ranges::sort(copy, LessThan{});
EXPECT_EQ(batch_, copy);
}
// missing-key path. Replaces the correctness half of Timing_test::doMissing
// (and the missing branch of doMixed): every fetch on an empty backend must
// report Status::NotFound.
TEST_P(BackendTypeTest, fetch_missing)
{
auto backend = makeOpenBackend();
// deliberately do NOT store batch_ — every key must be absent
fetchMissing(*backend, batch_);
}
// concurrent store/fetch correctness. Replaces the correctness half of the
// multi-threaded Timing_test workloads (which only ran manually, never in CI):
// many threads store disjoint objects, then many threads fetch and verify each
// round-trips. Doubles as a thread-safety smoke test for the backend.
TEST_P(BackendTypeTest, concurrent_store_and_fetch)
{
// The SQLite backend is not designed for concurrent writers (and the old
// Timing_test only exercised nudb/rocksdb under threads).
if (GetParam() == "sqlite")
GTEST_SKIP() << "sqlite backend is not exercised under concurrency";
for (auto const numThreads : {4uz, 8uz})
{
SCOPED_TRACE("threads=" + std::to_string(numThreads));
auto backend = makeOpenBackend();
// concurrent stores of disjoint objects
parallelFor(batch_.size(), numThreads, [&](std::size_t i) { backend->store(batch_[i]); });
// concurrent fetches, each verifying its object round-trips. Worker
// threads only touch an atomic counter; the EXPECT runs on the main
// thread after join to avoid relying on cross-thread assertion support.
std::atomic<std::size_t> mismatches{0};
parallelFor(batch_.size(), numThreads, [&](std::size_t i) {
std::shared_ptr<NodeObject> result;
if (backend->fetch(batch_[i]->getHash(), &result) != Status::Ok || !result ||
!isSame(result, batch_[i]))
{
++mismatches;
}
});
EXPECT_EQ(mismatches.load(), 0u);
backend->close();
}
}
INSTANTIATE_TEST_SUITE_P(
BackendTypes,
BackendTypeTest,
::testing::ValuesIn(backendTypes()),
[](::testing::TestParamInfo<std::string> const& info) { return info.param; });
} // namespace xrpl::node_store

View File

@@ -0,0 +1,41 @@
#include <xrpl/nodestore/NodeObject.h>
#include <xrpl/nodestore/detail/DecodedBlob.h>
#include <xrpl/nodestore/detail/EncodedBlob.h>
#include <gtest/gtest.h>
#include <nodestore/TestBase.h>
#include <cstddef>
#include <memory>
#include <string>
namespace xrpl::node_store {
TEST(NodeStoreBasics, predictable_batches)
{
auto const batch1 = createPredictableBatch(kNumObjectsToTest, kSeedValue);
auto const batch2 = createPredictableBatch(kNumObjectsToTest, kSeedValue);
EXPECT_EQ(batch1, batch2);
auto const batch3 = createPredictableBatch(kNumObjectsToTest, kSeedValue + 1);
EXPECT_NE(batch1, batch3);
}
TEST(NodeStoreBasics, blob_encoding)
{
auto const batch = createPredictableBatch(kNumObjectsToTest, kSeedValue);
for (std::size_t i = 0; i < batch.size(); ++i)
{
SCOPED_TRACE("blob index=" + std::to_string(i));
EncodedBlob const encoded(batch[i]);
DecodedBlob decoded(encoded.getKey(), encoded.getData(), encoded.getSize());
EXPECT_TRUE(decoded.wasOk());
if (decoded.wasOk())
{
std::shared_ptr<NodeObject> const object(decoded.createObject());
EXPECT_TRUE(isSame(batch[i], object));
}
}
}
} // namespace xrpl::node_store

View File

@@ -0,0 +1,248 @@
#include <xrpl/nodestore/Database.h>
#include <xrpl/basics/ByteUtilities.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/temp_dir.h>
#include <xrpl/beast/xor_shift_engine.h>
#include <xrpl/config/BasicConfig.h>
#include <xrpl/nodestore/DummyScheduler.h>
#include <xrpl/nodestore/Manager.h>
#include <xrpl/nodestore/Types.h>
#include <xrpl/protocol/SystemParameters.h>
#include <gtest/gtest.h>
#include <helpers/TestSink.h>
#include <nodestore/TestBase.h>
#include <algorithm>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
namespace xrpl::node_store {
namespace {
std::vector<std::string>
allBackends()
{
std::vector<std::string> types{"memory", "nudb"};
#if XRPL_ROCKSDB_AVAILABLE
types.emplace_back("rocksdb");
#endif
return types;
}
std::vector<std::string>
persistentBackends()
{
std::vector<std::string> types{"nudb"};
#if XRPL_ROCKSDB_AVAILABLE
types.emplace_back("rocksdb");
#endif
return types;
}
std::vector<std::string>
importBackends()
{
std::vector<std::string> types{"nudb"};
#if XRPL_ROCKSDB_AVAILABLE
types.emplace_back("rocksdb");
#endif
#ifdef XRPL_ENABLE_SQLITE_BACKEND_TESTS
types.emplace_back("sqlite");
#endif
return types;
}
} // namespace
// Shared setup for the parameterized Database tests: builds the node params,
// journal and a predictable batch per test, mirroring Backend.cpp's fixture.
class NodeStoreDatabaseTestBase : public ::testing::TestWithParam<std::string>
{
protected:
void
SetUp() override
{
nodeParams_.set("type", GetParam());
nodeParams_.set("path", nodeDb_.path());
beast::xor_shift_engine rng(kSeedValue);
batch_ = createPredictableBatch(kNumObjects, rng());
}
std::unique_ptr<Database>
makeDatabase()
{
return Manager::instance().makeDatabase(megabytes(4), scheduler_, 2, nodeParams_, journal_);
}
DummyScheduler scheduler_;
beast::TempDir const nodeDb_;
beast::Journal const journal_{TestSink::instance()};
Section nodeParams_;
Batch batch_;
};
class NodeStoreDatabaseTest : public NodeStoreDatabaseTestBase
{
};
class NodeStoreDatabasePersistenceTest : public NodeStoreDatabaseTestBase
{
};
TEST_P(NodeStoreDatabaseTest, store_and_fetch)
{
auto db = makeDatabase();
storeBatch(*db, batch_);
{
SCOPED_TRACE("read in original order");
auto const copy = fetchCopyOfBatch(*db, batch_);
EXPECT_EQ(batch_, copy);
}
{
SCOPED_TRACE("read in shuffled order");
beast::xor_shift_engine rng(kSeedValue);
std::shuffle(batch_.begin(), batch_.end(), rng);
auto const copy = fetchCopyOfBatch(*db, batch_);
EXPECT_EQ(batch_, copy);
}
}
TEST_P(NodeStoreDatabasePersistenceTest, round_trip)
{
{
auto db = makeDatabase();
storeBatch(*db, batch_);
}
// re-open without the ephemeral db
auto db = makeDatabase();
auto copy = fetchCopyOfBatch(*db, batch_);
std::ranges::sort(batch_, LessThan{});
std::ranges::sort(copy, LessThan{});
EXPECT_EQ(batch_, copy);
}
// missing-key path at the Database layer. Mirrors Backend's fetch_missing —
// fetching keys that were never stored must return nullptr (NotFound).
TEST_P(NodeStoreDatabaseTest, fetch_missing)
{
auto db = makeDatabase();
// never store: every key must be absent
fetchMissing(*db, batch_);
}
INSTANTIATE_TEST_SUITE_P(
NodeStoreBackends,
NodeStoreDatabaseTest,
::testing::ValuesIn(allBackends()),
[](::testing::TestParamInfo<std::string> const& info) { return info.param; });
INSTANTIATE_TEST_SUITE_P(
PersistentBackends,
NodeStoreDatabasePersistenceTest,
::testing::ValuesIn(persistentBackends()),
[](::testing::TestParamInfo<std::string> const& info) { return info.param; });
TEST(NodeStoreDatabase, memory_earliest_seq)
{
DummyScheduler scheduler;
beast::TempDir const nodeDb;
Section nodeParams;
nodeParams.set("type", "memory");
nodeParams.set("path", nodeDb.path());
beast::Journal const journal(TestSink::instance());
// default earliest ledger sequence
{
auto db = Manager::instance().makeDatabase(megabytes(4), scheduler, 2, nodeParams, journal);
EXPECT_EQ(db->earliestLedgerSeq(), kXrpLedgerEarliestSeq);
}
// invalid earliest_seq value
{
nodeParams.set("earliest_seq", "0");
try
{
auto db =
Manager::instance().makeDatabase(megabytes(4), scheduler, 2, nodeParams, journal);
FAIL() << "expected runtime_error for earliest_seq=0";
}
catch (std::runtime_error const& e)
{
EXPECT_STREQ(e.what(), "Invalid earliest_seq");
}
}
// valid earliest_seq value
{
nodeParams.set("earliest_seq", "1");
auto db = Manager::instance().makeDatabase(megabytes(4), scheduler, 2, nodeParams, journal);
EXPECT_EQ(db->earliestLedgerSeq(), 1u);
}
}
class DatabaseImportTest : public ::testing::TestWithParam<std::string>
{
};
TEST_P(DatabaseImportTest, same_backend)
{
auto const type = GetParam();
DummyScheduler scheduler;
beast::Journal const journal(TestSink::instance());
beast::TempDir const srcDir;
Section srcParams;
srcParams.set("type", type);
srcParams.set("path", srcDir.path());
auto batch = createPredictableBatch(kNumObjects, kSeedValue);
// write to source db
{
auto src = Manager::instance().makeDatabase(megabytes(4), scheduler, 2, srcParams, journal);
storeBatch(*src, batch);
}
Batch copy;
{
// re-open source and import into a fresh destination
auto src = Manager::instance().makeDatabase(megabytes(4), scheduler, 2, srcParams, journal);
beast::TempDir const destDir;
Section destParams;
destParams.set("type", type);
destParams.set("path", destDir.path());
auto dest =
Manager::instance().makeDatabase(megabytes(4), scheduler, 2, destParams, journal);
dest->importDatabase(*src);
copy = fetchCopyOfBatch(*dest, batch);
}
std::ranges::sort(batch, LessThan{});
std::ranges::sort(copy, LessThan{});
EXPECT_EQ(batch, copy);
}
INSTANTIATE_TEST_SUITE_P(
ImportBackends,
DatabaseImportTest,
::testing::ValuesIn(importBackends()),
[](::testing::TestParamInfo<std::string> const& info) { return info.param; });
} // namespace xrpl::node_store

View File

@@ -0,0 +1,297 @@
#include <xrpl/basics/ByteUtilities.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/temp_dir.h>
#include <xrpl/config/BasicConfig.h>
#include <xrpl/nodestore/DummyScheduler.h>
#include <xrpl/nodestore/Manager.h>
#include <gtest/gtest.h>
#include <helpers/CaptureSink.h>
#include <helpers/TestSink.h>
#include <nodestore/TestBase.h>
#include <array>
#include <cstddef>
#include <exception>
#include <memory>
#include <string>
#include <utility>
#include <vector>
namespace xrpl::node_store {
namespace {
Section
makeSection(std::string const& path, std::string const& blockSize = "")
{
Section params;
params.set("type", "nudb");
params.set("path", path);
if (!blockSize.empty())
params.set("nudb_block_size", blockSize);
return params;
}
void
runRoundTrip(Section const& params, std::size_t expectedBlocksize)
{
DummyScheduler scheduler;
beast::Journal const journal(TestSink::instance());
auto backend = Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
ASSERT_TRUE(backend);
ASSERT_EQ(backend->getBlockSize(), expectedBlocksize);
backend->open();
ASSERT_TRUE(backend->isOpen());
auto const batch = createPredictableBatch(10, 12345);
storeBatch(*backend, batch);
auto const copy = fetchCopyOfBatch(*backend, batch);
backend->close();
EXPECT_EQ(batch, copy);
}
} // namespace
TEST(NuDBFactory, default_block_size)
{
beast::TempDir const tempDir;
auto const params = makeSection(tempDir.path());
ASSERT_NO_FATAL_FAILURE(runRoundTrip(params, 4096));
}
TEST(NuDBFactory, valid_block_sizes)
{
auto const kValidSizes = std::to_array<std::size_t>({4096, 8192, 16384, 32768});
for (auto const size : kValidSizes)
{
SCOPED_TRACE("size=" + std::to_string(size));
beast::TempDir const tempDir;
auto const params = makeSection(tempDir.path(), std::to_string(size));
ASSERT_NO_FATAL_FAILURE(runRoundTrip(params, size));
}
// empty value is ignored by config parser; default (4096) is used
{
beast::TempDir const tempDir;
auto const params = makeSection(tempDir.path(), "");
ASSERT_NO_FATAL_FAILURE(runRoundTrip(params, 4096));
}
}
TEST(NuDBFactory, invalid_block_sizes)
{
std::vector<std::string> const kInvalidSizes = {
"2048", // too small
"1024", // too small
"65536", // too large
"131072", // too large
"5000", // not power of 2
"6000", // not power of 2
"10000", // not power of 2
"0", // zero
"-1", // negative
"abc", // non-numeric
"4k", // invalid format
"4096.5"}; // decimal
for (auto const& size : kInvalidSizes)
{
SCOPED_TRACE("size='" + size + "'");
beast::TempDir const tempDir;
auto const params = makeSection(tempDir.path(), size);
EXPECT_THROW(runRoundTrip(params, 4096), std::exception);
}
// whitespace handling — lexical_cast may or may not strip; treat as invalid
std::vector<std::string> const kWhitespaceSizes = {"4096 ", " 4096"};
for (auto const& size : kWhitespaceSizes)
{
SCOPED_TRACE("size='" + size + "'");
beast::TempDir const tempDir;
auto const params = makeSection(tempDir.path(), size);
EXPECT_THROW(runRoundTrip(params, 4096), std::exception);
}
}
TEST(NuDBFactory, log_messages)
{
// valid custom block size emits info log
{
beast::TempDir const tempDir;
auto const params = makeSection(tempDir.path(), "8192");
test::CaptureSink sink(beast::Severity::Info);
beast::Journal const journal(sink);
DummyScheduler scheduler;
[[maybe_unused]] auto backend =
Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
EXPECT_TRUE(sink.messages().contains("Using custom NuDB block size: 8192"));
}
// invalid block size throws with informative message
{
beast::TempDir const tempDir;
auto const params = makeSection(tempDir.path(), "5000");
test::CaptureSink sink(beast::Severity::Warning);
beast::Journal const journal(sink);
DummyScheduler scheduler;
try
{
auto backend =
Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
FAIL() << "expected exception for invalid block size 5000";
}
catch (std::exception const& e)
{
std::string const what{e.what()};
EXPECT_TRUE(what.contains("Invalid nudb_block_size: 5000"));
EXPECT_TRUE(what.contains("Must be power of 2 between 4096 and 32768"));
}
}
// non-numeric value throws
{
beast::TempDir const tempDir;
auto const params = makeSection(tempDir.path(), "invalid");
test::CaptureSink sink(beast::Severity::Warning);
beast::Journal const journal(sink);
DummyScheduler scheduler;
try
{
auto backend =
Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
FAIL() << "expected exception for non-numeric block size";
}
catch (std::exception const& e)
{
std::string const what{e.what()};
EXPECT_TRUE(what.contains("Invalid nudb_block_size value: invalid"));
}
}
}
TEST(NuDBFactory, power_of_two_validation)
{
std::vector<std::pair<std::string, bool>> const kCASES = {
{"4095", false}, // just below minimum
{"4096", true}, // minimum valid
{"4097", false}, // not power of 2
{"8192", true}, // valid power of 2
{"8193", false}, // not power of 2
{"16384", true}, // valid power of 2
{"32768", true}, // maximum valid
{"32769", false}, // just above maximum
{"65536", false}}; // power of 2 but too large
for (auto const& [size, shouldWork] : kCASES)
{
SCOPED_TRACE("size=" + size + " shouldWork=" + (shouldWork ? "true" : "false"));
beast::TempDir const tempDir;
auto const params = makeSection(tempDir.path(), size);
test::CaptureSink sink(beast::Severity::Warning);
beast::Journal const journal(sink);
DummyScheduler scheduler;
try
{
auto backend =
Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
EXPECT_TRUE(shouldWork);
}
catch (std::exception const& e)
{
// A throw is only expected for sizes that should NOT work; if a
// valid size throws, fail here instead of silently matching the
// message below (which would mask the regression).
EXPECT_FALSE(shouldWork);
std::string const what{e.what()};
EXPECT_TRUE(what.contains("Invalid nudb_block_size"));
}
}
}
TEST(NuDBFactory, both_constructor_variants)
{
beast::TempDir const tempDir;
auto const params = makeSection(tempDir.path(), "16384");
DummyScheduler scheduler;
beast::Journal const journal(TestSink::instance());
auto backend1 = Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
EXPECT_NE(backend1, nullptr);
ASSERT_NO_FATAL_FAILURE(runRoundTrip(params, 16384));
// Test second constructor (with nudb::context)
// Note: This would require access to nudb::context, which might not be
// easily testable without more complex setup. For now, we test that
// the factory can create backends with the first constructor.
}
TEST(NuDBFactory, configuration_parsing)
{
// basic valid format emits success log
{
beast::TempDir const tempDir;
auto const params = makeSection(tempDir.path(), "8192");
test::CaptureSink sink(beast::Severity::Info);
beast::Journal const journal(sink);
DummyScheduler scheduler;
[[maybe_unused]] auto backend =
Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
EXPECT_TRUE(sink.messages().contains("Using custom NuDB block size"));
}
// Test whitespace handling separately since lexical_cast behavior may vary
std::vector<std::string> const kWhitespaceFormats = {" 8192", "8192 "};
for (auto const& format : kWhitespaceFormats)
{
SCOPED_TRACE("format='" + format + "'");
beast::TempDir const tempDir;
auto const params = makeSection(tempDir.path(), format);
test::CaptureSink sink(beast::Severity::Debug);
beast::Journal const journal(sink);
DummyScheduler scheduler;
EXPECT_ANY_THROW(Manager::instance().makeBackend(params, megabytes(4), scheduler, journal));
}
}
TEST(NuDBFactory, data_persistence)
{
std::vector<std::string> const kBlockSizes = {"4096", "8192", "16384", "32768"};
for (auto const& size : kBlockSizes)
{
SCOPED_TRACE("size=" + size);
beast::TempDir const tempDir;
auto const params = makeSection(tempDir.path(), size);
DummyScheduler scheduler;
beast::Journal const journal(TestSink::instance());
// Create test data
auto const batch = createPredictableBatch(50, 54321);
// Store data
{
auto backend =
Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
backend->open();
storeBatch(*backend, batch);
backend->close();
}
// Retrieve data in new backend instance
{
auto backend =
Manager::instance().makeBackend(params, megabytes(4), scheduler, journal);
backend->open();
auto const copy = fetchCopyOfBatch(*backend, batch);
EXPECT_EQ(batch, copy);
backend->close();
}
}
}
} // namespace xrpl::node_store

View File

@@ -0,0 +1,169 @@
#pragma once
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/random.h>
#include <xrpl/beast/utility/rngfill.h>
#include <xrpl/beast/xor_shift_engine.h>
#include <xrpl/nodestore/Backend.h>
#include <xrpl/nodestore/Database.h>
#include <xrpl/nodestore/NodeObject.h>
#include <xrpl/nodestore/Types.h>
#include <gtest/gtest.h>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
namespace xrpl::node_store {
constexpr std::size_t kMinPayloadBytes = 1;
constexpr std::size_t kMaxPayloadBytes = 2000;
constexpr int kNumObjectsToTest = 2000;
constexpr int kNumObjects = 2000;
constexpr std::uint64_t kSeedValue = 50;
struct LessThan
{
bool
operator()(std::shared_ptr<NodeObject> const& lhs, std::shared_ptr<NodeObject> const& rhs)
const noexcept
{
return lhs->getHash() < rhs->getHash();
}
};
[[nodiscard]] inline bool
isSame(std::shared_ptr<NodeObject> const& lhs, std::shared_ptr<NodeObject> const& rhs)
{
return (lhs->getType() == rhs->getType()) && (lhs->getHash() == rhs->getHash()) &&
(lhs->getData() == rhs->getData());
}
[[nodiscard]] inline Batch
createPredictableBatch(std::size_t numObjects, std::uint64_t seed)
{
Batch batch;
batch.reserve(numObjects);
beast::xor_shift_engine rng(seed);
for (auto i = 0uz; i < numObjects; ++i)
{
NodeObjectType const type = [&] {
switch (randInt(rng, 3))
{
case 0:
return NodeObjectType::Ledger;
case 1:
return NodeObjectType::AccountNode;
case 2:
return NodeObjectType::TransactionNode;
case 3:
default:
return NodeObjectType::Unknown;
}
}();
uint256 hash;
beast::rngfill(hash.begin(), hash.size(), rng);
Blob blob(randInt(rng, kMinPayloadBytes, kMaxPayloadBytes));
beast::rngfill(blob.data(), blob.size(), rng);
batch.emplace_back(NodeObject::createObject(type, std::move(blob), hash));
}
return batch;
}
inline void
storeBatch(Backend& backend, Batch const& batch)
{
for (auto const& obj : batch)
backend.store(obj);
}
[[nodiscard]] inline Batch
fetchCopyOfBatch(Backend& backend, Batch const& batch)
{
Batch copy;
copy.reserve(batch.size());
for (auto i = 0uz; i < batch.size(); ++i)
{
SCOPED_TRACE("fetchCopyOfBatch index=" + std::to_string(i));
std::shared_ptr<NodeObject> object;
Status const status = backend.fetch(batch[i]->getHash(), &object);
EXPECT_EQ(status, Status::Ok);
if (status == Status::Ok)
{
EXPECT_NE(object, nullptr);
copy.emplace_back(object);
}
}
return copy;
}
inline void
fetchMissing(Backend& backend, Batch const& batch)
{
for (auto i = 0uz; i < batch.size(); ++i)
{
SCOPED_TRACE("fetchMissing index=" + std::to_string(i));
std::shared_ptr<NodeObject> object;
Status const status = backend.fetch(batch[i]->getHash(), &object);
EXPECT_EQ(status, Status::NotFound);
}
}
inline void
storeBatch(Database& db, Batch const& batch)
{
for (auto const& obj : batch)
{
Blob data(obj->getData());
db.store(obj->getType(), std::move(data), obj->getHash(), db.earliestLedgerSeq());
}
}
[[nodiscard]] inline Batch
fetchCopyOfBatch(Database& db, Batch const& batch)
{
Batch copy;
copy.reserve(batch.size());
for (auto const& obj : batch)
{
std::shared_ptr<NodeObject> const result = db.fetchNodeObject(obj->getHash(), 0);
if (result != nullptr)
copy.emplace_back(result);
}
return copy;
}
inline void
fetchMissing(Database& db, Batch const& batch)
{
for (auto i = 0uz; i < batch.size(); ++i)
{
SCOPED_TRACE("fetchMissing(Database) index=" + std::to_string(i));
EXPECT_EQ(db.fetchNodeObject(batch[i]->getHash(), 0), nullptr);
}
}
} // namespace xrpl::node_store
namespace xrpl {
[[nodiscard]] inline bool
operator==(node_store::Batch const& lhs, node_store::Batch const& rhs)
{
return std::ranges::equal(lhs, rhs, node_store::isSame);
}
} // namespace xrpl

View File

@@ -0,0 +1,46 @@
#include <xrpl/nodestore/detail/varint.h>
#include <gtest/gtest.h>
#include <array>
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
using namespace xrpl::node_store;
TEST(varint, encode_decode)
{
std::vector<std::size_t> const kVALUES = {
0,
1,
2,
126,
127,
128,
253,
254,
255,
16127,
16128,
16129,
0xff,
0xffff,
0xffffffff,
0xffffffffffffUL,
0xffffffffffffffffUL};
for (auto const v : kVALUES)
{
SCOPED_TRACE("value=" + std::to_string(v));
std::array<std::uint8_t, varint_traits<std::size_t>::kMax> vi{};
auto const n0 = writeVarint(vi.data(), v);
EXPECT_GT(n0, 0u) << "write error";
EXPECT_EQ(n0, sizeVarint(v)) << "size error";
std::size_t v1 = 0;
auto const n1 = readVarint(vi.data(), n0, v1);
EXPECT_EQ(n1, n0) << "read error";
EXPECT_EQ(v1, v) << "wrong value";
}
}

View File

@@ -24,13 +24,13 @@ namespace xrpl::tests {
class TestNodeFamily : public Family
{
private:
std::unique_ptr<NodeStore::Database> db_;
std::unique_ptr<node_store::Database> db_;
std::shared_ptr<FullBelowCache> fbCache_;
std::shared_ptr<TreeNodeCache> tnCache_;
TestStopwatch clock_;
NodeStore::DummyScheduler scheduler_;
node_store::DummyScheduler scheduler_;
beast::Journal const j_;
@@ -49,17 +49,17 @@ public:
Section testSection;
testSection.set(Keys::kType, "memory");
testSection.set(Keys::kPath, "SHAMap_test");
db_ = NodeStore::Manager::instance().makeDatabase(
db_ = node_store::Manager::instance().makeDatabase(
megabytes(4), scheduler_, 1, testSection, j);
}
NodeStore::Database&
node_store::Database&
db() override
{
return *db_;
}
[[nodiscard]] NodeStore::Database const&
[[nodiscard]] node_store::Database const&
db() const override
{
return *db_;

View File

@@ -18,7 +18,7 @@ namespace xrpl {
class AccountStateSF : public SHAMapSyncFilter
{
public:
AccountStateSF(NodeStore::Database& db, AbstractFetchPackContainer& fp) : db_(db), fp_(fp)
AccountStateSF(node_store::Database& db, AbstractFetchPackContainer& fp) : db_(db), fp_(fp)
{
}
@@ -34,7 +34,7 @@ public:
getNode(SHAMapHash const& nodeHash) const override;
private:
NodeStore::Database& db_;
node_store::Database& db_;
AbstractFetchPackContainer& fp_;
};

View File

@@ -136,7 +136,7 @@ private:
addPeers();
void
tryDB(NodeStore::Database& srcDB);
tryDB(node_store::Database& srcDB);
void
done();

View File

@@ -18,7 +18,7 @@ namespace xrpl {
class TransactionStateSF : public SHAMapSyncFilter
{
public:
TransactionStateSF(NodeStore::Database& db, AbstractFetchPackContainer& fp) : db_(db), fp_(fp)
TransactionStateSF(node_store::Database& db, AbstractFetchPackContainer& fp) : db_(db), fp_(fp)
{
}
@@ -34,7 +34,7 @@ public:
getNode(SHAMapHash const& nodeHash) const override;
private:
NodeStore::Database& db_;
node_store::Database& db_;
AbstractFetchPackContainer& fp_;
};

View File

@@ -224,7 +224,7 @@ InboundLedger::neededStateHashes(int max, SHAMapSyncFilter const* filter) const
// See how much of the ledger data is stored locally
// Data found in a fetch pack will be stored
void
InboundLedger::tryDB(NodeStore::Database& srcDB)
InboundLedger::tryDB(node_store::Database& srcDB)
{
if (!haveHeader_)
{

View File

@@ -653,7 +653,7 @@ LedgerMaster::tryFill(std::shared_ptr<Ledger const> ledger)
std::uint32_t minHas = seq;
std::uint32_t maxHas = seq;
NodeStore::Database& nodeStore{app_.getNodeStore()};
node_store::Database& nodeStore{app_.getNodeStore()};
while (!app_.getJobQueue().isStopping() && seq > 0)
{
{

View File

@@ -232,7 +232,7 @@ public:
std::unique_ptr<Resource::Manager> resourceManager_;
std::unique_ptr<NodeStore::Database> nodeStore_;
std::unique_ptr<node_store::Database> nodeStore_;
NodeFamily nodeFamily_;
std::unique_ptr<OrderBookDB> orderBookDB_;
std::unique_ptr<PathRequestManager> pathRequestManager_;
@@ -655,7 +655,7 @@ public:
return tempNodeCache_;
}
NodeStore::Database&
node_store::Database&
getNodeStore() override
{
return *nodeStore_;
@@ -860,9 +860,9 @@ public:
if (config_->doImport)
{
auto j = logs_->journal("NodeObject");
NodeStore::DummyScheduler dummyScheduler;
std::unique_ptr<NodeStore::Database> source =
NodeStore::Manager::instance().makeDatabase(
node_store::DummyScheduler dummyScheduler;
std::unique_ptr<node_store::Database> source =
node_store::Manager::instance().makeDatabase(
megabytes(config_->getValueFor(SizedItem::BurstSize, std::nullopt)),
dummyScheduler,
0,

View File

@@ -12,7 +12,7 @@ NodeStoreScheduler::NodeStoreScheduler(JobQueue& jobQueue) : jobQueue_(jobQueue)
}
void
NodeStoreScheduler::scheduleTask(NodeStore::Task& task)
NodeStoreScheduler::scheduleTask(node_store::Task& task)
{
if (jobQueue_.isStopped())
return;
@@ -26,19 +26,19 @@ NodeStoreScheduler::scheduleTask(NodeStore::Task& task)
}
void
NodeStoreScheduler::onFetch(NodeStore::FetchReport const& report)
NodeStoreScheduler::onFetch(node_store::FetchReport const& report)
{
if (jobQueue_.isStopped())
return;
jobQueue_.addLoadEvents(
report.fetchType == NodeStore::FetchType::Async ? JtNsAsyncRead : JtNsSyncRead,
report.fetchType == node_store::FetchType::Async ? JtNsAsyncRead : JtNsSyncRead,
1,
report.elapsed);
}
void
NodeStoreScheduler::onBatchWrite(NodeStore::BatchWriteReport const& report)
NodeStoreScheduler::onBatchWrite(node_store::BatchWriteReport const& report)
{
if (jobQueue_.isStopped())
return;

View File

@@ -7,19 +7,19 @@
namespace xrpl {
/**
* A NodeStore::Scheduler which uses the JobQueue.
* A node_store::Scheduler which uses the JobQueue.
*/
class NodeStoreScheduler : public NodeStore::Scheduler
class NodeStoreScheduler : public node_store::Scheduler
{
public:
explicit NodeStoreScheduler(JobQueue& jobQueue);
void
scheduleTask(NodeStore::Task& task) override;
scheduleTask(node_store::Task& task) override;
void
onFetch(NodeStore::FetchReport const& report) override;
onFetch(node_store::FetchReport const& report) override;
void
onBatchWrite(NodeStore::BatchWriteReport const& report) override;
onBatchWrite(node_store::BatchWriteReport const& report) override;
private:
JobQueue& jobQueue_;

View File

@@ -43,7 +43,7 @@ public:
[[nodiscard]] virtual std::uint32_t
clampFetchDepth(std::uint32_t fetchDepth) const = 0;
virtual std::unique_ptr<NodeStore::Database>
virtual std::unique_ptr<node_store::Database>
makeNodeStore(int readThreads) = 0;
/**
@@ -101,5 +101,5 @@ public:
//------------------------------------------------------------------------------
std::unique_ptr<SHAMapStore>
makeSHAMapStore(Application& app, NodeStore::Scheduler& scheduler, beast::Journal journal);
makeSHAMapStore(Application& app, node_store::Scheduler& scheduler, beast::Journal journal);
} // namespace xrpl

View File

@@ -94,7 +94,7 @@ SHAMapStoreImp::SavedStateDB::setLastRotated(LedgerIndex seq)
SHAMapStoreImp::SHAMapStoreImp(
Application& app,
NodeStore::Scheduler& scheduler,
node_store::Scheduler& scheduler,
beast::Journal journal)
: app_(app)
, scheduler_(scheduler)
@@ -165,7 +165,7 @@ SHAMapStoreImp::SHAMapStoreImp(
}
}
std::unique_ptr<NodeStore::Database>
std::unique_ptr<node_store::Database>
SHAMapStoreImp::makeNodeStore(int readThreads)
{
auto nscfg = app_.config().section(Sections::kNodeDatabase);
@@ -185,7 +185,7 @@ SHAMapStoreImp::makeNodeStore(int readThreads)
std::to_string(app_.config().getValueFor(SizedItem::TreeCacheAge, std::nullopt)));
}
std::unique_ptr<NodeStore::Database> db;
std::unique_ptr<node_store::Database> db;
if (deleteInterval_ != 0u)
{
@@ -201,7 +201,7 @@ SHAMapStoreImp::makeNodeStore(int readThreads)
// Create NodeStore with two backends to allow online deletion of
// data
auto dbr = std::make_unique<NodeStore::DatabaseRotatingImp>(
auto dbr = std::make_unique<node_store::DatabaseRotatingImp>(
scheduler_,
readThreads,
std::move(writableBackend),
@@ -210,11 +210,11 @@ SHAMapStoreImp::makeNodeStore(int readThreads)
app_.getJournal(kNodeStoreName));
fdRequired_ += dbr->fdRequired();
dbRotating_ = dbr.get();
db.reset(dynamic_cast<NodeStore::Database*>(dbr.release()));
db.reset(dynamic_cast<node_store::Database*>(dbr.release()));
}
else
{
db = NodeStore::Manager::instance().makeDatabase(
db = node_store::Manager::instance().makeDatabase(
megabytes(app_.config().getValueFor(SizedItem::BurstSize, std::nullopt)),
scheduler_,
readThreads,
@@ -257,7 +257,7 @@ SHAMapStoreImp::copyNode(std::uint64_t& nodeCount, SHAMapTreeNode const& node)
{
// Copy a single record from node to dbRotating_
auto obj = dbRotating_->fetchNodeObject(
node.getHash().asUInt256(), 0, NodeStore::FetchType::Synchronous, true);
node.getHash().asUInt256(), 0, node_store::FetchType::Synchronous, true);
if (!obj)
{
XRPL_ASSERT(node.cowid() == 0, "SHAMapStoreImp::copyNode : rescued node must be clean");
@@ -374,7 +374,7 @@ SHAMapStoreImp::run()
// exception) also clear the flag.
struct RotationExposureGuard
{
NodeStore::DatabaseRotating& db;
node_store::DatabaseRotating& db;
~RotationExposureGuard()
{
db.setRotationInFlight(false);
@@ -516,7 +516,7 @@ SHAMapStoreImp::dbPaths()
boost::filesystem::remove_all(p);
}
std::unique_ptr<NodeStore::Backend>
std::unique_ptr<node_store::Backend>
SHAMapStoreImp::makeBackendRotating(std::string path)
{
Section section{app_.config().section(Sections::kNodeDatabase)};
@@ -535,7 +535,7 @@ SHAMapStoreImp::makeBackendRotating(std::string path)
}
section.set(Keys::kPath, newPath.string());
auto backend{NodeStore::Manager::instance().makeBackend(
auto backend{node_store::Manager::instance().makeBackend(
section,
megabytes(app_.config().getValueFor(SizedItem::BurstSize, std::nullopt)),
scheduler_,
@@ -702,7 +702,7 @@ SHAMapStoreImp::minimumOnline() const
//------------------------------------------------------------------------------
std::unique_ptr<SHAMapStore>
makeSHAMapStore(Application& app, NodeStore::Scheduler& scheduler, beast::Journal journal)
makeSHAMapStore(Application& app, node_store::Scheduler& scheduler, beast::Journal journal)
{
return std::make_unique<SHAMapStoreImp>(app, scheduler, journal);
}

View File

@@ -81,9 +81,9 @@ private:
// minimum ledger to maintain online.
std::atomic<LedgerIndex> minimumOnline_;
NodeStore::Scheduler& scheduler_;
node_store::Scheduler& scheduler_;
beast::Journal const journal_;
NodeStore::DatabaseRotating* dbRotating_ = nullptr;
node_store::DatabaseRotating* dbRotating_ = nullptr;
SavedStateDB stateDb_;
std::thread thread_;
bool stop_ = false;
@@ -119,7 +119,7 @@ private:
static constexpr auto kNodeStoreName = "NodeStore";
public:
SHAMapStoreImp(Application& app, NodeStore::Scheduler& scheduler, beast::Journal journal);
SHAMapStoreImp(Application& app, node_store::Scheduler& scheduler, beast::Journal journal);
std::uint32_t
clampFetchDepth(std::uint32_t fetchDepth) const override
@@ -127,7 +127,7 @@ public:
return (deleteInterval_ != 0u) ? std::min(fetchDepth, deleteInterval_) : fetchDepth;
}
std::unique_ptr<NodeStore::Database>
std::unique_ptr<node_store::Database>
makeNodeStore(int readThreads) override;
LedgerIndex
@@ -180,7 +180,7 @@ private:
void
dbPaths();
std::unique_ptr<NodeStore::Backend>
std::unique_ptr<node_store::Backend>
makeBackendRotating(std::string path = std::string());
template <class CacheInstance>
@@ -191,7 +191,7 @@ private:
for (auto const& key : cache.getKeys())
{
dbRotating_->fetchNodeObject(key, 0, NodeStore::FetchType::Synchronous, true);
dbRotating_->fetchNodeObject(key, 0, node_store::FetchType::Synchronous, true);
if (!(++check % checkHealthInterval_) && healthWait() == HealthResult::Stopping)
return true;
}

View File

@@ -33,13 +33,13 @@ public:
NodeFamily(Application& app, CollectorManager& cm);
NodeStore::Database&
node_store::Database&
db() override
{
return db_;
}
[[nodiscard]] NodeStore::Database const&
[[nodiscard]] node_store::Database const&
db() const override
{
return db_;
@@ -80,7 +80,7 @@ public:
private:
Application& app_;
NodeStore::Database& db_;
node_store::Database& db_;
beast::Journal const j_;
std::shared_ptr<FullBelowCache> fbCache_;