Merge branch 'develop' into Rust_integration

This commit is contained in:
Sergey Kuznetsov
2026-07-27 15:58:54 +01:00
committed by GitHub
103 changed files with 2013 additions and 1424 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

@@ -1,5 +1,5 @@
{
"image_tag": "sha-3122de8",
"image_tag": "sha-fecfc0c",
"configs": {
"ubuntu": [
{

View File

@@ -13,6 +13,7 @@ on:
- "!nix/docker/README.md"
- "!nix/devshell.nix"
- "bin/check-tools.sh"
- "bin/default-loader-path.sh"
- "bin/install-sanitizer-libs.sh"
pull_request:
paths:
@@ -24,6 +25,7 @@ on:
- "!nix/docker/README.md"
- "!nix/devshell.nix"
- "bin/check-tools.sh"
- "bin/default-loader-path.sh"
- "bin/install-sanitizer-libs.sh"
workflow_dispatch:

View File

@@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Write PR body to file
env:

114
.github/workflows/check-tools.yml vendored Normal file
View File

@@ -0,0 +1,114 @@
# Verifies the committed snapshots of `bin/check-tools.sh` output for each Nix
# environment (see nix/check-tools/). If the environment changes — a new image
# tag, an updated flake.lock, a different tool list — without the matching
# snapshot being regenerated and committed, this workflow fails so the drift is
# caught in review.
#
# To regenerate the snapshots, see nix/check-tools/README.md.
name: Check tools
on:
pull_request:
paths:
- ".github/workflows/check-tools.yml"
- ".github/scripts/strategy-matrix/linux.json"
- "bin/check-tools.sh"
- "nix/**"
- "flake.nix"
- "flake.lock"
- "rust-toolchain.toml"
push:
branches:
- "develop"
paths:
- ".github/workflows/check-tools.yml"
- ".github/scripts/strategy-matrix/linux.json"
- "bin/check-tools.sh"
- "nix/**"
- "flake.nix"
- "flake.lock"
- "rust-toolchain.toml"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
defaults:
run:
shell: bash
jobs:
# The nix-nixos image tag is pinned alongside the build matrix in linux.json,
# so snapshots are checked against the exact image CI builds against.
linux-image-tag:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.tag.outputs.tag }}
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Read nix image tag
id: tag
run: echo "tag=$(jq -r .image_tag .github/scripts/strategy-matrix/linux.json)" >>"${GITHUB_OUTPUT}"
# One job for all environments; they differ only in whether the tools come
# from the nix-nixos container (Linux) or `nix develop` (macOS).
check-tools:
needs: linux-image-tag
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-latest
snapshot: nix/check-tools/nix-ubuntu-amd64.txt
nix_develop: false
- runner: ubuntu-24.04-arm
snapshot: nix/check-tools/nix-ubuntu-arm64.txt
nix_develop: false
- runner: macos-26-apple-clang-21
snapshot: nix/check-tools/macos.txt
nix_develop: true
runs-on: ${{ matrix.runner }}
# Linux runs inside the pinned nix-nixos image; macOS runs natively and uses
# the flake's dev shell instead (see the run step below).
container: ${{ !matrix.nix_develop && format('ghcr.io/xrplf/xrpld/nix-ubuntu:{0}', needs.linux-image-tag.outputs.tag) || null }}
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@c00c22ada3bd6bcda48fcb0d62fbbab49fec8a0f
with:
enable_ccache: false
- name: Regenerate snapshot
env:
CHECK_TOOLS_SKIP_CLONE: "1"
# check-tools.sh skips some macOS tools when CI is set; the snapshots
# capture the full `nix develop` environment, so unset it here.
CI: ""
run: |
if [ "${{ matrix.nix_develop }}" = "true" ]; then
# `nix develop` prints the dev-shell greeting first; keep only the
# check-tools.sh output (from the "Detected OS:" line onward).
nix --extra-experimental-features "nix-command flakes" develop \
-c bash bin/check-tools.sh | sed -n '/^Detected OS:/,$p' >"${{ matrix.snapshot }}"
else
bash bin/check-tools.sh >"${{ matrix.snapshot }}"
fi
- name: Verify snapshot is up to date
run: |
if ! git diff --exit-code -- "${{ matrix.snapshot }}"; then
echo "::error::${{ matrix.snapshot }} is out of date. Regenerate it (see nix/check-tools/README.md) and commit the result."
exit 1
fi
- name: Upload regenerated snapshot
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: check-tools-${{ runner.os }}-${{ runner.arch }}
path: ${{ matrix.snapshot }}

View File

@@ -52,7 +52,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Determine changed files
# This step checks whether any files have changed that should
# cause the next jobs to run. We do it this way rather than
@@ -91,6 +91,7 @@ jobs:
.clang-tidy
.codecov.yml
bin/check-tools.sh
bin/default-loader-path.sh
cfg/**
cmake/**
conan/**

View File

@@ -29,6 +29,7 @@ on:
- ".clang-tidy"
- ".codecov.yml"
- "bin/check-tools.sh"
- "bin/default-loader-path.sh"
- "cfg/**"
- "cmake/**"
- "conan/**"

View File

@@ -41,13 +41,13 @@ env:
jobs:
build:
runs-on: ubuntu-latest
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-3122de8
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-fecfc0c
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@e4b6449d55a61c002d7c3fdfa6c20f721ede0606
uses: XRPLF/actions/prepare-runner@c00c22ada3bd6bcda48fcb0d62fbbab49fec8a0f
with:
enable_ccache: false

View File

@@ -110,10 +110,10 @@ jobs:
uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@e4b6449d55a61c002d7c3fdfa6c20f721ede0606
uses: XRPLF/actions/prepare-runner@c00c22ada3bd6bcda48fcb0d62fbbab49fec8a0f
with:
enable_ccache: ${{ inputs.ccache_enabled }}

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Check levelization
run: python .github/scripts/levelization/generate.py
- name: Check for differences

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Check definitions
run: .github/scripts/rename/definitions.sh .
- name: Check copyright notices

View File

@@ -34,16 +34,16 @@ jobs:
needs: [determine-files]
if: ${{ needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.need_full_run == 'true' }}
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-3122de8"
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-fecfc0c"
permissions:
contents: read
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@e4b6449d55a61c002d7c3fdfa6c20f721ede0606
uses: XRPLF/actions/prepare-runner@c00c22ada3bd6bcda48fcb0d62fbbab49fec8a0f
with:
enable_ccache: false

View File

@@ -27,7 +27,7 @@ jobs:
matrix: ${{ steps.generate.outputs.matrix }}
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
@@ -54,7 +54,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Download pre-built binary
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1

View File

@@ -23,7 +23,7 @@ jobs:
matrix: ${{ steps.generate.outputs.matrix }}
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0

View File

@@ -40,14 +40,14 @@ defaults:
jobs:
upload:
runs-on: ubuntu-latest
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-3122de8
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-fecfc0c
env:
REMOTE_NAME: ${{ inputs.remote_name }}
CONAN_LOGIN_USERNAME_XRPLF: ${{ secrets.remote_username }}
CONAN_PASSWORD_XRPLF: ${{ secrets.remote_password }}
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Generate build version number
id: version

View File

@@ -65,10 +65,10 @@ jobs:
uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@e4b6449d55a61c002d7c3fdfa6c20f721ede0606
uses: XRPLF/actions/prepare-runner@c00c22ada3bd6bcda48fcb0d62fbbab49fec8a0f
with:
enable_ccache: false

View File

@@ -30,8 +30,10 @@ missing=()
checked=0
# check <name> [probe-command...]
# Runs the probe (default: "<name> --version") quietly. Records <name> as
# missing if the command is not found or exits non-zero.
# Runs the probe (default: "<name> --version"), capturing both stdout and
# stderr, and prints one aligned line: the status, the name, and the first
# non-blank line of the probe output (its version). Records <name> as missing
# if the command is not found or exits non-zero.
check() {
local name="$1"
shift
@@ -40,10 +42,11 @@ check() {
probe=("${name}" --version)
fi
echo "Checking ${name}..."
checked=$((checked + 1))
if "${probe[@]}" | head -n 1; then
printf ' [ ok ] %s\n' "${name}"
local output version
if output="$("${probe[@]}" 2>&1)"; then
version="$(printf '%s\n' "${output}" | grep -m1 '[^[:space:]]' || true)"
printf ' [ ok ] %-20s %s\n' "${name}" "${version}"
else
printf ' [MISS] %s\n' "${name}"
missing+=("${name}")
@@ -85,12 +88,14 @@ if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then
check file
check less
check make
check netstat which netstat
# net-tools netstat reports "net-tools X.Y"; macOS ships BSD netstat with no
# version flag, so fall back to a presence marker there.
check netstat sh -c 'command -v netstat >/dev/null && { netstat --version 2>&1 | grep -m1 -oE "net-tools [0-9.]+" || echo present; }'
check ninja
check perl
check perl perl -e 'print "$^V\n"'
check pkg-config
check vim
check zip
check zip bash -c 'zip --version 2>&1 | grep -m1 -oE "Zip [0-9.]+"'
# These tools are present in our Linux CI images and in local development
# setups, but not in the macOS CI environment. So check them everywhere

View File

@@ -29,6 +29,27 @@ if(CMAKE_GENERATOR STREQUAL "Xcode")
set(is_xcode TRUE)
endif()
# --------------------------------------------------------------------
# Nix toolchain detection
# --------------------------------------------------------------------
# True when the C++ compiler resolves into the Nix store. CMAKE_CXX_COMPILER may
# be referenced through a symlink outside the store (a Nix profile, a /usr/bin
# alternative, ...), so resolve the real path before matching.
set(is_nix_compiler FALSE)
get_filename_component(_cxx_real "${CMAKE_CXX_COMPILER}" REALPATH)
if(_cxx_real MATCHES "^/nix/store/")
set(is_nix_compiler TRUE)
endif()
unset(_cxx_real)
# True inside the Nix CI Docker image, identified by the /nix/ci-env tree it
# ships (see nix/docker/Dockerfile). The dev shell and bare systems don't have
# it, so it distinguishes the CI image from other Nix-compiler environments.
set(is_ci_image FALSE)
if(EXISTS "/nix/ci-env/bin")
set(is_ci_image TRUE)
endif()
# --------------------------------------------------------------------
# Operating system detection
# --------------------------------------------------------------------

View File

@@ -1,26 +1,37 @@
#[===================================================================[
Patch executables to run in non-Nix environments.
The Nix-based CI image links binaries against an ELF interpreter (loader)
that lives in the Nix store, so the resulting binaries don't run elsewhere
(including once installed from the .deb package). `patch_nix_binary` adds a
POST_BUILD step that resets the interpreter to the system default loader and
drops the rpath.
The Nix toolchain links binaries against an ELF interpreter (loader)
that lives in the Nix store, so the resulting binaries don't run elsewhere.
`patch_nix_binary` adds a POST_BUILD step that resets the interpreter
to the system default loader and drops the rpath.
This is only active inside the Nix-based image, detected by the presence of
/tmp/loader-path.sh (shipped by that image, resolves the default loader). It
is skipped for sanitizer builds, whose runtime libraries are resolved through
the rpath. Everywhere else `patch_nix_binary` is a no-op.
This runs by default for Nix-toolchain builds (determined by whether the compiler resolves under /nix/store/).
Those builds are where binaries get a Nix-store loader.
It is opted out of by setting the XRPLD_NO_PATCH_NIX_BINARY environment variable
the plain Nix dev shells set it, since their binaries link a newer glibc
and must not be retargeted to the system loader.
Non-Nix builds (a system compiler, already using the system loader) and sanitizer builds
(runtime libraries resolved through the rpath) are skipped too.
Everywhere else `patch_nix_binary` is a no-op.
The default loader is resolved by bin/default-loader-path.sh.
#]===================================================================]
include_guard(GLOBAL)
include(CompilationEnv)
# Provided by the Nix-based CI image; prints the system default ELF loader path.
set(_loader_path_script "/tmp/loader-path.sh")
# Resolves the system default ELF loader path for the current architecture.
set(_loader_path_script "${CMAKE_SOURCE_DIR}/bin/default-loader-path.sh")
if(is_linux AND NOT SANITIZERS_ENABLED AND EXISTS "${_loader_path_script}")
if(
is_linux
AND NOT SANITIZERS_ENABLED
AND is_nix_compiler
AND NOT DEFINED ENV{XRPLD_NO_PATCH_NIX_BINARY}
)
execute_process(
COMMAND "${_loader_path_script}"
OUTPUT_VARIABLE DEFAULT_LOADER_PATH

View File

@@ -171,9 +171,8 @@ else()
# Clang wrapper supplies those paths itself (via -nostdinc++), so at compile time the
# flag is unused -> Clang errors under our -Werror. At link time the flag IS consumed
# (it selects the C++ runtime), so we move it there instead of dropping it entirely.
get_filename_component(_cxx_real "${CMAKE_CXX_COMPILER}" REALPATH)
if(
_cxx_real MATCHES "^/nix/store/"
is_nix_compiler
AND is_linux
AND is_clang
AND CMAKE_CXX_FLAGS MATCHES "stdlib=libstdc"

View File

@@ -36,6 +36,19 @@ elseif(is_gcc)
endif()
endif()
# A Nix compiler is only meant to be used from a managed environment: the xrpld
# dev shell (which exports XRPL_DEVSHELL) or the CI image. Using one from a bare
# shell usually means a leaked toolchain (picked up via PATH or a Conan profile)
# and leads to confusing breakage, so fail early with guidance.
if(is_nix_compiler AND NOT is_ci_image AND NOT DEFINED ENV{XRPL_DEVSHELL})
message(
FATAL_ERROR
"A Nix compiler (${CMAKE_CXX_COMPILER}) is being used outside the xrpld "
"dev shell. Enter it with `nix develop` (see docs/build/nix.md) before "
"configuring the build."
)
endif()
# check for in-source build and fail
if("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}")
message(

8
docs/build/nix.md vendored
View File

@@ -154,6 +154,14 @@ conan install .. --output-folder . --build '*' --settings build_type=Release
To update `flake.lock` to the latest revision use `nix flake update` command.
## Tooling snapshots
The tool versions in each Nix environment are recorded in
[`nix/check-tools/`](../../nix/check-tools) and verified by CI. If you change the
environment (bump the CI image tag, update `flake.lock`, or edit the tool list in
`bin/check-tools.sh`), CI fails until you regenerate and commit the affected
snapshot — see [`nix/check-tools/README.md`](../../nix/check-tools/README.md).
## Troubleshooting
See [Troubleshooting Nix problems](./nix_troubleshooting.md) for common issues,

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&

49
nix/check-tools/README.md Normal file
View File

@@ -0,0 +1,49 @@
# check-tools snapshots
These files capture the output of [`bin/check-tools.sh`](../../bin/check-tools.sh)
— the versions of the development tooling — in each Nix environment:
| File | Environment |
| ---------------------- | ------------------------------------ |
| `nix-ubuntu-amd64.txt` | `nix-ubuntu` CI image, `linux/amd64` |
| `nix-ubuntu-arm64.txt` | `nix-ubuntu` CI image, `linux/arm64` |
| `macos.txt` | macOS, inside `nix develop` |
The [`check-tools`](../../.github/workflows/check-tools.yml) workflow regenerates
each snapshot in its environment and fails if it differs from the committed file.
So if you change the environment (bump the image tag in
[`linux.json`](../../.github/scripts/strategy-matrix/linux.json), update
`flake.lock`, change the tool list in `check-tools.sh`, …) you must regenerate
and commit the affected snapshots.
Each snapshot is `check-tools.sh` stdout with the git-clone connectivity check
skipped (`CHECK_TOOLS_SKIP_CLONE=1`), so it contains only deterministic version
data. On macOS the dev-shell greeting that `nix develop` prints first is dropped
with `sed -n '/^Detected OS:/,$p'`.
## Regenerating
The two Linux snapshots come from the `nix-ubuntu` image (Docker or a compatible
runtime such as Apple `container`). The image tag is pinned in `linux.json`:
```bash
img="ghcr.io/xrplf/xrpld/nix-ubuntu:$(jq -r .image_tag .github/scripts/strategy-matrix/linux.json)"
for arch in amd64 arm64; do
container run --rm -i -e CHECK_TOOLS_SKIP_CLONE=1 -a "${arch}" --entrypoint bash "${img}" -s \
<bin/check-tools.sh >"nix/check-tools/nix-ubuntu-${arch}.txt"
done
```
(With Docker, replace `container run … -a "${arch}"` with
`docker run … --platform "linux/${arch}"`.)
The macOS snapshot is generated locally. `CI=` is unset so `check-tools.sh`
checks the full dev-shell tool set (it otherwise skips some tools when `CI` is
set):
```bash
CI= nix develop -c bash -c 'CHECK_TOOLS_SKIP_CLONE=1 bash bin/check-tools.sh' |
sed -n '/^Detected OS:/,$p' \
>nix/check-tools/macos.txt
```

47
nix/check-tools/macos.txt Normal file
View File

@@ -0,0 +1,47 @@
Detected OS: macos (Darwin arm64)
Core build tools:
[ ok ] cmake cmake version 4.1.2
[ ok ] conan Conan version 2.28.1
[ ok ] git git version 2.54.0
[ ok ] python3 Python 3.13.13
Development tooling:
[ ok ] ccache ccache version 4.13.6
[ ok ] clang clang version 22.1.7
[ ok ] clang++ clang version 22.1.7
[ ok ] ClangBuildAnalyzer ClangBuildAnalyzer 1.6.0
[ ok ] curl curl 8.20.0 (aarch64-apple-darwin25.3.0) libcurl/8.20.0 OpenSSL/3.6.2 zlib/1.3.2 libssh2/1.11.1 nghttp2/1.69.0 mit-krb5/1.22.1
[ ok ] file file-5.47
[ ok ] less less 692 (PCRE2 regular expressions)
[ ok ] make GNU Make 4.4.1
[ ok ] netstat present
[ ok ] ninja 1.13.2
[ ok ] perl v5.42.0
[ ok ] pkg-config 0.29.2
[ ok ] vim VIM - Vi IMproved 9.2 (2026 Feb 14, compiled Jan 01 1980 00:00:00)
[ ok ] zip Zip 3.0
[ ok ] clang-format clang-format version 22.1.7
[ ok ] dot dot - graphviz version 12.2.1 (0)
[ ok ] doxygen 1.16.1
[ ok ] gcovr gcovr 8.4
[ ok ] gh gh version 2.94.0 (nixpkgs)
[ ok ] git-cliff git-cliff 2.13.1
[ ok ] git-lfs git-lfs/3.7.1 (3.7.1; darwin arm64; go 1.26.3)
[ ok ] gpg gpg (GnuPG) 2.4.9
[ ok ] pre-commit pre-commit 4.5.1
[ ok ] run-clang-tidy usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
Rust toolchain:
[ ok ] cargo cargo 1.95.0 (f2d3ce0bd 2026-03-21)
[ ok ] cargo-audit cargo-audit-audit 0.22.1
[ ok ] cargo-llvm-cov cargo-llvm-cov 0.8.5
[ ok ] cargo-nextest cargo-nextest 0.9.137
[ ok ] clippy clippy 0.1.95 (59807616e1 2026-04-14)
[ ok ] rust-analyzer rust-analyzer 1.95.0 (59807616 2026-04-14)
[ ok ] rustc rustc 1.95.0 (59807616e 2026-04-14)
[ ok ] rustfmt rustfmt 1.9.0-stable (59807616e1 2026-04-14)
Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set).
All 36 checked tools are present and runnable.

View File

@@ -0,0 +1,55 @@
Detected OS: linux (Linux x86_64)
Core build tools:
[ ok ] cmake cmake version 4.1.2
[ ok ] conan Conan version 2.28.1
[ ok ] git git version 2.54.0
[ ok ] python3 Python 3.13.13
Development tooling:
[ ok ] ccache ccache version 4.13.6
[ ok ] clang clang version 22.1.7
[ ok ] clang++ clang version 22.1.7
[ ok ] ClangBuildAnalyzer ClangBuildAnalyzer 1.6.0
[ ok ] curl curl 8.20.0 (x86_64-pc-linux-gnu) libcurl/8.20.0 OpenSSL/3.6.2 zlib/1.3.2 libssh2/1.11.1 nghttp2/1.69.0 mit-krb5/1.22.1
[ ok ] file file-5.47
[ ok ] less less 692 (PCRE2 regular expressions)
[ ok ] make GNU Make 4.4.1
[ ok ] netstat net-tools 2.10
[ ok ] ninja 1.13.2
[ ok ] perl v5.42.0
[ ok ] pkg-config 0.29.2
[ ok ] vim VIM - Vi IMproved 9.2 (2026 Feb 14, compiled Jan 01 1980 00:00:00)
[ ok ] zip Zip 3.0
[ ok ] clang-format clang-format version 22.1.7
[ ok ] dot dot - graphviz version 12.2.1 (0)
[ ok ] doxygen 1.16.1
[ ok ] gcovr gcovr 8.4
[ ok ] gh gh version 2.94.0 (nixpkgs)
[ ok ] git-cliff git-cliff 2.13.1
[ ok ] git-lfs git-lfs/3.7.1 (3.7.1; linux amd64; go 1.26.3)
[ ok ] gpg gpg (GnuPG) 2.4.9
[ ok ] pre-commit pre-commit 4.5.1
[ ok ] run-clang-tidy usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
Rust toolchain:
[ ok ] cargo cargo 1.95.0 (f2d3ce0bd 2026-03-21)
[ ok ] cargo-audit cargo-audit-audit 0.22.1
[ ok ] cargo-llvm-cov cargo-llvm-cov 0.8.5
[ ok ] cargo-nextest cargo-nextest 0.9.137
[ ok ] clippy clippy 0.1.95 (59807616e1 2026-04-14)
[ ok ] rust-analyzer rust-analyzer 1.95.0 (5980761 2026-04-14)
[ ok ] rustc rustc 1.95.0 (59807616e 2026-04-14)
[ ok ] rustfmt rustfmt 1.9.0-stable (59807616e1 2026-04-14)
GCC toolchain:
[ ok ] gcc gcc (GCC) 15.2.0
[ ok ] g++ g++ (GCC) 15.2.0
[ ok ] gcov gcov (GCC) 15.2.0
Mold:
[ ok ] mold mold 2.41.0 (compatible with GNU ld)
Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set).
All 40 checked tools are present and runnable.

View File

@@ -0,0 +1,55 @@
Detected OS: linux (Linux aarch64)
Core build tools:
[ ok ] cmake cmake version 4.1.2
[ ok ] conan Conan version 2.28.1
[ ok ] git git version 2.54.0
[ ok ] python3 Python 3.13.13
Development tooling:
[ ok ] ccache ccache version 4.13.6
[ ok ] clang clang version 22.1.7
[ ok ] clang++ clang version 22.1.7
[ ok ] ClangBuildAnalyzer ClangBuildAnalyzer 1.6.0
[ ok ] curl curl 8.20.0 (aarch64-unknown-linux-gnu) libcurl/8.20.0 OpenSSL/3.6.2 zlib/1.3.2 libssh2/1.11.1 nghttp2/1.69.0 mit-krb5/1.22.1
[ ok ] file file-5.47
[ ok ] less less 692 (PCRE2 regular expressions)
[ ok ] make GNU Make 4.4.1
[ ok ] netstat net-tools 2.10
[ ok ] ninja 1.13.2
[ ok ] perl v5.42.0
[ ok ] pkg-config 0.29.2
[ ok ] vim VIM - Vi IMproved 9.2 (2026 Feb 14, compiled Jan 01 1980 00:00:00)
[ ok ] zip Zip 3.0
[ ok ] clang-format clang-format version 22.1.7
[ ok ] dot dot - graphviz version 12.2.1 (0)
[ ok ] doxygen 1.16.1
[ ok ] gcovr gcovr 8.4
[ ok ] gh gh version 2.94.0 (nixpkgs)
[ ok ] git-cliff git-cliff 2.13.1
[ ok ] git-lfs git-lfs/3.7.1 (3.7.1; linux arm64; go 1.26.3)
[ ok ] gpg gpg (GnuPG) 2.4.9
[ ok ] pre-commit pre-commit 4.5.1
[ ok ] run-clang-tidy usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
Rust toolchain:
[ ok ] cargo cargo 1.95.0 (f2d3ce0bd 2026-03-21)
[ ok ] cargo-audit cargo-audit-audit 0.22.1
[ ok ] cargo-llvm-cov cargo-llvm-cov 0.8.5
[ ok ] cargo-nextest cargo-nextest 0.9.137
[ ok ] clippy clippy 0.1.95 (59807616e1 2026-04-14)
[ ok ] rust-analyzer rust-analyzer 1.95.0 (5980761 2026-04-14)
[ ok ] rustc rustc 1.95.0 (59807616e 2026-04-14)
[ ok ] rustfmt rustfmt 1.9.0-stable (59807616e1 2026-04-14)
GCC toolchain:
[ ok ] gcc gcc (GCC) 15.2.0
[ ok ] g++ g++ (GCC) 15.2.0
[ ok ] gcov gcov (GCC) 15.2.0
Mold:
[ ok ] mold mold 2.41.0 (compatible with GNU ld)
Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set).
All 40 checked tools are present and runnable.

View File

@@ -50,12 +50,17 @@ let
# compilerName is the command used to print the version, or null for none.
makeShell =
{
shellName,
stdenv,
compilerName,
version ? null,
versionedTools ? [ ],
extraPackages ? [ ],
warningHook ? "",
# Opt out of PatchNixBinary.cmake retargeting binaries to the system
# loader. The plain toolchain links a newer glibc, so it must not be
# patched; the custom toolchain patches by default.
noPatchNixBinary ? false,
}:
let
compilerVersionHook =
@@ -73,14 +78,20 @@ let
tools = versionedTools;
});
in
(pkgs.mkShell.override { inherit stdenv; }) {
packages = commonPackages ++ versionedLinks ++ extraPackages;
shellHook = ''
echo "Welcome to xrpld development shell";
${compilerVersionHook}
${warningHook}
'';
};
(pkgs.mkShell.override { inherit stdenv; }) (
{
packages = commonPackages ++ versionedLinks ++ extraPackages;
# Marks a managed dev shell, so the build (XrplSanity.cmake) can tell an
# intentional Nix toolchain from one leaked into a bare shell.
XRPL_DEVSHELL = shellName;
shellHook = ''
echo "Welcome to xrpld development shell";
${compilerVersionHook}
${warningHook}
'';
}
// pkgs.lib.optionalAttrs noPatchNixBinary { XRPLD_NO_PATCH_NIX_BINARY = "1"; }
);
in
rec {
# macOS: Nix Clang. Linux: Nix GCC.
@@ -89,6 +100,7 @@ rec {
# gcc/clang use the custom-glibc toolchain, matching CI. On darwin there is no
# custom glibc, so they fall back to the plain nixpkgs toolchain.
gcc = makeShell {
shellName = "gcc";
stdenv = customGccStdenv;
compilerName = "gcc";
version = gccVersion;
@@ -97,6 +109,7 @@ rec {
};
clang = makeShell {
shellName = "clang";
stdenv = customClangStdenv;
compilerName = "clang";
version = llvmVersion;
@@ -105,6 +118,7 @@ rec {
# Nix provides no compiler; use the one from your system (e.g. Apple Clang).
no-compiler = makeShell {
shellName = "no-compiler";
stdenv = pkgs.stdenvNoCC;
compilerName = null;
};
@@ -115,19 +129,23 @@ rec {
# makes `nix develop .#gcc-plain` fail there rather than silently aliasing gcc.
// pkgs.lib.optionalAttrs pkgs.stdenv.isLinux {
gcc-plain = makeShell {
shellName = "gcc-plain";
stdenv = plainGccStdenv;
compilerName = "gcc";
version = gccVersion;
versionedTools = gccVersionedTools;
extraPackages = [ plainGcov ];
warningHook = plainWarningHook;
noPatchNixBinary = true;
};
clang-plain = makeShell {
shellName = "clang-plain";
stdenv = plainClangStdenv;
compilerName = "clang";
version = llvmVersion;
versionedTools = clangVersionedTools;
warningHook = plainWarningHook;
noPatchNixBinary = true;
};
}

View File

@@ -58,7 +58,7 @@ ENV GIT_SSL_CAINFO="/nix/ci-env/etc/ssl/certs/ca-bundle.crt"
# Externally-built dynamically-linked ELF binaries hard-code the loader path
# (e.g. /lib64/ld-linux-x86-64.so.2) in their PT_INTERP header. Install it
# from the Nix store when the base image doesn't already provide one.
COPY nix/docker/loader-path.sh /tmp/loader-path.sh
COPY bin/default-loader-path.sh /tmp/loader-path.sh
RUN <<EOF
target="$(/tmp/loader-path.sh)"

View File

@@ -75,8 +75,9 @@ toolchain being present at runtime. Two pieces make that work:
- **An expected dynamic linker in the image.**
Binaries built in Nix environments reference a dynamic linker from Nix store paths, which won't be present in the base image. However,
[`loader-path.sh`](./loader-path.sh) reports the expected loader path for the
current architecture, so we can patch the binaries to use the correct loader.
[`bin/default-loader-path.sh`](../../bin/default-loader-path.sh) reports the
expected loader path for the current architecture, so we can patch the binaries
to use the correct loader.
The build then verifies all of this end to end, and the C++ and Rust programs
go through the same pipeline: each is compiled in `final`, has its `PT_INTERP`
@@ -93,8 +94,8 @@ whose resulting binary is patched and run like the others.
| File | Purpose |
| ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| [`./Dockerfile`](./Dockerfile) | Multi-stage build described above. |
| [`./loader-path.sh`](./loader-path.sh) | Print the dynamic-linker (`PT_INTERP`) path for the current architecture. |
| [`./test_files/cpp/`](./test_files/cpp) | C++ sanitizer smoke test: sources + compile/run scripts. |
| [`./test_files/rust/`](./test_files/rust) | Rust smoke test: rustc sources + a cargo proc-macro workspace + compile/run scripts. |
| [`/bin/check-tools.sh`](../../bin/check-tools.sh) | Verify every expected tools are present and runnable. |
| [`/bin/default-loader-path.sh`](../../bin/default-loader-path.sh) | Print the dynamic-linker (`PT_INTERP`) path for the current architecture. |
| [`/bin/install-sanitizer-libs.sh`](../../bin/install-sanitizer-libs.sh) | Install `libasan`/`libtsan`/`libubsan` runtimes on the supported base images. |

View File

@@ -16,7 +16,23 @@ let
exec ${pkgs.python3}/bin/python3 ${llvmPackages.clang-unwrapped}/bin/run-clang-tidy "$@"
'';
rustToolchain = pkgs.rust-bin.fromRustupToolchainFile ../rust-toolchain.toml;
# rust-overlay's toolchain propagates the *default* stdenv.cc onto the PATH (so
# cargo has a linker). That default may be different from the clang we pin here,
# so it shadows our clang and the build can silently use a different compiler
# version. Drop that cc from every propagation channel instead of pinning a
# replacement: the toolchain then carries no compiler and cargo just uses the
# active shell's stdenv cc. Must cover all channels — rust-overlay uses both
# propagatedBuildInputs and depsHostHostPropagated.
rustToolchainBase = pkgs.rust-bin.fromRustupToolchainFile ../rust-toolchain.toml;
rustToolchain =
let
defaultCc = pkgs.stdenv.cc; # default compiler from nixpkgs stdenv
withoutDefaultCc = builtins.filter (dep: (dep.outPath or "") != defaultCc.outPath);
in
rustToolchainBase.overrideAttrs (old: {
propagatedBuildInputs = withoutDefaultCc (old.propagatedBuildInputs or [ ]);
depsHostHostPropagated = withoutDefaultCc (old.depsHostHostPropagated or [ ]);
});
# Nix wraps its toolchain so that binaries are exposed only under unsuffixed
# names (gcc, g++, clang-tidy, ...). Several tools probe for a

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

@@ -40,6 +40,17 @@ public:
*/
[[nodiscard]] virtual unsigned
version() const = 0;
/**
* Close the client's connection to the server.
*
* Releases the connection the client holds against the server's per-port
* connection limit. After this call the client must not be used to
* invoke() again. Tests use this to deterministically free the slot
* rather than waiting for the server's idle timeout to drop it.
*/
virtual void
disconnect() = 0;
};
} // namespace xrpl::test

View File

@@ -482,11 +482,52 @@ public:
app().getNumberOfThreads() == 1,
"syncClose() is only useful on an application with a single thread");
auto const result = close();
auto serverBarrier = std::make_shared<std::promise<void>>();
auto future = serverBarrier->get_future();
boost::asio::post(app().getIOContext(), [serverBarrier]() { serverBarrier->set_value(); });
auto const status = future.wait_for(timeout);
return result && status == std::future_status::ready;
return result && drainServerIo(timeout);
}
/**
* Disconnect the Env's built-in client and wait for the server to
* register the dropped connection.
*
* Env holds one persistent client connection to the server's RPC port for
* its whole lifetime (see client()), and that connection counts against
* the port's connection limit. Tests that need a known starting occupancy
* can call this to deterministically release that slot instead of waiting
* out the server's localhost idle timeout.
*
* The server decrements its per-port connection count in the peer's
* destructor, which runs when the io_context processes the end-of-stream
* on the closed socket. After closing the client this drains the server's
* io_context twice: the first barrier guarantees the reactor has reaped
* the closed socket and queued the peer's teardown, and the second
* guarantees that teardown (and therefore the count decrement) has run.
*
* This is only sound when the server uses a single io_context thread, so
* that draining establishes ordering against the teardown - configure the
* Env with singleThreadIo() (as syncClose() also requires). Like
* syncClose(), it relies on loopback teardown latency being negligible.
*
* @param timeout Maximum time to wait for each barrier task to execute
* @return true if both barriers executed within timeout, false otherwise
*/
[[nodiscard]] bool
disconnectClient(std::chrono::steady_clock::duration timeout = std::chrono::seconds{1})
{
XRPL_ASSERT(
app().getNumberOfThreads() == 1,
"disconnectClient() is only useful on an application with a single "
"thread");
bundle_.client->disconnect();
// Drain the server's single io thread twice: the first barrier flushes
// the reactor's reap of the closed socket (queuing the peer teardown),
// the second flushes that teardown - and therefore the connection-count
// decrement. Both run unconditionally so a timed-out first drain does
// not short-circuit the second.
bool const reaped = drainServerIo(timeout);
bool const toreDown = drainServerIo(timeout);
return reaped && toreDown;
}
/**
@@ -846,6 +887,25 @@ public:
}
private:
/**
* Drain the (single) server io_context thread once.
*
* Posts a barrier task to the server's io_context and blocks until it
* runs, so every task queued before it has been processed. Only meaningful
* with a single io thread (see syncClose()/disconnectClient()).
*
* @param timeout Maximum time to wait for the barrier task to execute
* @return true if the barrier ran within timeout, false otherwise
*/
[[nodiscard]] bool
drainServerIo(std::chrono::steady_clock::duration timeout)
{
auto barrier = std::make_shared<std::promise<void>>();
auto future = barrier->get_future();
boost::asio::post(app().getIOContext(), [barrier]() { barrier->set_value(); });
return future.wait_for(timeout) == std::future_status::ready;
}
void
fund(bool setDefaultRipple, STAmount const& amount, Account const& account);

View File

@@ -13,8 +13,9 @@ class WSClient_test : public beast::unit_test::Suite
{
public:
void
run() override
testSmoke()
{
testcase("smoke");
using namespace jtx;
Env env(*this);
auto wsc = makeWSClient(env.app().config());
@@ -28,6 +29,47 @@ public:
auto jv = wsc->getMsg(std::chrono::seconds(1));
pass();
}
void
testGracefulDisconnect()
{
testcase("graceful disconnect");
using namespace jtx;
using namespace std::chrono;
Env env(*this);
auto wsc = makeWSClient(env.app().config());
// Put real traffic on the connection before closing it.
json::Value stream;
stream["streams"] = json::ValueType::Array;
stream["streams"].append("ledger");
auto const sub = wsc->invoke("subscribe", stream);
BEAST_EXPECT(sub.isMember("result") || sub.isMember("status"));
// disconnect() performs a graceful WebSocket closing handshake and
// blocks until the server acknowledges. On loopback that completes in
// well under its internal 1s timeout; only a broken async_close/ack
// coordination would fall through to the force-close path at ~1s. A
// generous bound keeps this from flaking under load while still
// catching that regression.
auto const start = steady_clock::now();
wsc->disconnect();
auto const elapsed = duration_cast<milliseconds>(steady_clock::now() - start);
BEAST_EXPECT(elapsed < milliseconds{750});
// disconnect() must be idempotent: a second call (and the subsequent
// destructor) must not hang, double-close, or crash.
wsc->disconnect();
pass();
}
void
run() override
{
testSmoke();
testGracefulDisconnect();
}
};
BEAST_DEFINE_TESTSUITE(WSClient, jtx, xrpl);

View File

@@ -14,18 +14,23 @@
#include <xrpl/server/Port.h>
#include <boost/asio/buffer.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/address_v4.hpp>
#include <boost/asio/ip/address_v6.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast/core/multi_buffer.hpp>
#include <boost/beast/http/dynamic_body.hpp>
#include <boost/beast/http/error.hpp>
#include <boost/beast/http/message.hpp>
#include <boost/beast/http/read.hpp>
#include <boost/beast/http/string_body.hpp>
#include <boost/beast/http/verb.hpp>
#include <boost/beast/http/write.hpp>
#include <boost/system/system_error.hpp>
#include <algorithm>
#include <array>
#include <iostream>
#include <memory>
#include <sstream>
@@ -84,6 +89,40 @@ class JSONRPCClient : public AbstractClient
boost::beast::multi_buffer bout_;
unsigned rpcVersion_;
bool disconnected_ = false;
// Errors that mean the persistent keep-alive connection was dropped by the
// server (rather than a genuine protocol failure), so the request can be
// safely retried on a fresh connection.
static bool
droppedConnection(boost::system::error_code const& ec)
{
namespace error = boost::asio::error;
static auto const kDroppedConnectionErrors = std::to_array<boost::system::error_code>({
boost::beast::http::error::end_of_stream,
error::eof,
error::connection_reset,
error::connection_aborted,
error::broken_pipe,
error::not_connected,
});
return std::ranges::any_of(
kDroppedConnectionErrors,
[&ec](boost::system::error_code const& e) { return ec == e; });
}
// Tear down and re-establish the socket to ep_, discarding any buffered
// bytes left over from the dropped connection.
void
reconnect()
{
boost::system::error_code ec;
stream_.close(ec);
bin_.clear();
stream_.connect(ep_);
}
public:
explicit JSONRPCClient(Config const& cfg, unsigned rpcVersion)
: ep_(getEndpoint(cfg)), stream_(ios_), rpcVersion_(rpcVersion)
@@ -91,12 +130,10 @@ public:
stream_.connect(ep_);
}
/*
Return value is an Object type with up to three keys:
status
error
result
*/
// Return value is an Object type with up to three keys:
// status
// error
// result
json::Value
invoke(std::string const& cmd, json::Value const& params) override
{
@@ -104,6 +141,13 @@ public:
using namespace boost::asio;
using namespace std::string_literals;
// Once disconnect() has released the slot, the client must not be
// reused (see AbstractClient::disconnect). Refuse rather than let the
// failed write/read below trip the reconnect path and silently
// re-consume a connection slot, which would defeat disconnectClient().
if (disconnected_)
Throw<std::logic_error>("JSONRPCClient::invoke called after disconnect()");
request<string_body> req;
req.method(boost::beast::http::verb::post);
req.target("/");
@@ -131,10 +175,29 @@ public:
req.body() = to_string(jr);
}
req.prepare_payload();
write(stream_, req);
// The client keeps a single keep-alive connection for its whole
// lifetime, but the server drops idle localhost connections after a few
// seconds (BaseHTTPPeer::kTimeoutSecondsLocal). If a slow gap between
// requests let the server close the socket, the write/read here fails
// with end_of_stream; reconnect and retry the request exactly once.
response<dynamic_body> res;
read(stream_, bin_, res);
auto writeAndRead = [&] {
write(stream_, req);
read(stream_, bin_, res);
};
try
{
writeAndRead();
}
catch (boost::system::system_error const& e)
{
if (!droppedConnection(e.code()))
throw;
reconnect();
res = {};
writeAndRead();
}
json::Reader jr;
json::Value jv;
@@ -151,6 +214,19 @@ public:
{
return rpcVersion_;
}
void
disconnect() override
{
if (disconnected_)
return;
disconnected_ = true;
boost::system::error_code ec;
stream_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
stream_.close(ec);
}
};
std::unique_ptr<AbstractClient>

View File

@@ -2,6 +2,7 @@
#include <xrpld/core/Config.h>
#include <xrpl/basics/Mutex.hpp>
#include <xrpl/basics/contract.h>
#include <xrpl/config/BasicConfig.h>
#include <xrpl/config/Constants.h>
@@ -112,10 +113,11 @@ class WSClientImpl : public WSClient
bool peerClosed_ = false;
// synchronize destructor
bool b0_ = false;
std::mutex m0_;
std::condition_variable cv0_;
// disconnect() waits on this until the read loop ends (for any reason:
// the server acknowledged our close, or a timeout force-closed the socket).
static constexpr auto kDisconnectTimeout = std::chrono::seconds{1};
xrpl::Mutex<bool> readEnded_;
std::condition_variable readEndCv_;
// synchronize message queue
std::mutex m_;
@@ -127,23 +129,26 @@ class WSClientImpl : public WSClient
void
cleanup()
{
boost::asio::post(ios_, boost::asio::bind_executor(strand_, [this] {
if (!peerClosed_)
{
ws_.async_close(
{}, boost::asio::bind_executor(strand_, [&](error_code) {
try
{
stream_.cancel();
}
// NOLINTNEXTLINE(bugprone-empty-catch)
catch (boost::system::system_error const&)
{
// ignored
}
}));
}
}));
boost::asio::post(
ios_, //
boost::asio::bind_executor(strand_, [this] {
if (!peerClosed_)
{
ws_.async_close(
{}, //
boost::asio::bind_executor(strand_, [&](error_code) {
try
{
stream_.cancel();
}
// NOLINTNEXTLINE(bugprone-empty-catch)
catch (boost::system::system_error const&)
{
// ignored
}
}));
}
}));
work_ = std::nullopt;
thread_.join();
}
@@ -289,6 +294,44 @@ public:
return rpcVersion_;
}
void
disconnect() override
{
// Perform a graceful WebSocket closing handshake and block until the
// read loop ends, so the server observes a clean close (not a RST) and
// has finished tearing the connection down by the time we return.
// If the server already closed, the wait below returns immediately.
boost::asio::post(
ios_,
boost::asio::bind_executor(
strand_, //
[this] {
if (!peerClosed_)
{
ws_.async_close(
boost::beast::websocket::close_code::normal,
boost::asio::bind_executor(strand_, [](error_code) {}));
}
}));
auto lock = readEnded_.lock<std::unique_lock>();
readEndCv_.wait_for(lock, kDisconnectTimeout, [&lock] { return *lock; });
// On timeout (server gone or not replying) force the socket closed so
// the outstanding read ends and the worker thread can later be joined.
if (!*lock)
{
boost::asio::post(
ios_,
boost::asio::bind_executor(
strand_, //
[this] {
boost::system::error_code ec;
stream_.close(ec);
}));
}
}
private:
void
onReadMsg(error_code const& ec)
@@ -297,33 +340,31 @@ private:
{
if (ec == boost::beast::websocket::error::closed)
peerClosed_ = true;
*readEnded_.lock() = true;
readEndCv_.notify_all();
return;
}
json::Value jv;
json::Reader jr;
jr.parse(bufferString(rb_.data()), jv);
rb_.consume(rb_.size());
auto m = std::make_shared<Msg>(std::move(jv));
{
std::scoped_lock const lock(m_);
msgs_.push_front(m);
cv_.notify_all();
}
ws_.async_read(
rb_, boost::asio::bind_executor(strand_, [this](error_code const& ec, std::size_t) {
onReadMsg(ec);
}));
}
// Called when the read op terminates
void
onReadDone()
{
std::scoped_lock const lock(m0_);
b0_ = true;
cv0_.notify_all();
}
};
std::unique_ptr<WSClient>

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

@@ -558,10 +558,12 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En
using namespace test::jtx;
using namespace boost::asio;
using namespace boost::beast::http;
Env env{*this, envconfig([&](std::unique_ptr<Config> cfg) {
// Run the server with a single io thread so disconnectClient() below
// can deterministically drain the server's io_context (see its docs).
Env env{*this, singleThreadIo(envconfig([&](std::unique_ptr<Config> cfg) {
(*cfg)[Sections::kPortRpc].set(Keys::kLimit, std::to_string(limit));
return cfg;
})};
}))};
auto const section = env.app().config().section(Sections::kPortRpc);
// NOLINTBEGIN(bugprone-unchecked-optional-access)
@@ -580,16 +582,27 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En
BEAST_EXPECT(!ec);
std::vector<std::pair<ip::tcp::socket, boost::beast::multi_buffer>> clients;
int connectionCount{1}; // starts at 1 because the Env already has one
// for JSONRPCCLient
// for nonzero limits, go one past the limit, although failures happen
// at the limit, so this really leads to the last two clients failing.
// for zero limit, pick an arbitrary nonzero number of clients - all
// should connect fine.
// Env owns a persistent JSON-RPC HTTP client connection to port_rpc as
// part of startup, which counts against this port's connection limit.
// This test wants a known starting occupancy of zero, so for nonzero
// limits it deterministically drops that hidden client and waits for
// the server to register the disconnect before opening its own clients.
//
// Starting from zero is important because the port limit rejects once
// the incremented connection count reaches the configured limit. With a
// zero baseline and N = limit + 1 test-owned clients, exactly the last
// two requests should be rejected.
if (limit != 0)
BEAST_EXPECT(env.disconnectClient());
// For nonzero limits, go one past the limit. The port rejects at the
// limit, not only above it, so this yields the last two clients
// failing. For zero limit, pick an arbitrary nonzero number of clients
// and expect them all to succeed.
int const testTo = (limit == 0) ? 50 : limit + 1;
while (connectionCount < testTo)
while (static_cast<int>(clients.size()) < testTo)
{
clients.emplace_back(ip::tcp::socket{ios}, boost::beast::multi_buffer{});
async_connect(clients.back().first, it, yield[ec]);
@@ -597,19 +610,24 @@ class ServerStatus_test : public beast::unit_test::Suite, public beast::test::En
auto req = makeHTTPRequest(ip, port, to_string(jr), {});
async_write(clients.back().first, req, yield[ec]);
BEAST_EXPECT(!ec);
++connectionCount;
}
int readCount = 0;
int successfulReads = 0;
for (auto& [soc, buf] : clients)
{
boost::beast::http::response<boost::beast::http::string_body> resp;
async_read(soc, buf, resp, yield[ec]);
++readCount;
// expect the reads to fail for the clients that connected at or
// above the limit. If limit is 0, all reads should succeed
BEAST_EXPECT((limit == 0 || readCount < limit - 1) ? (!ec) : bool(ec));
if (!ec)
++successfulReads;
}
// This test cares about the exact number of accepted requests, not which
// specific client observed the rejection. With a zero baseline (the
// hidden Env client dropped above), the server accepts until the
// connection count reaches the limit: all clients for limit 0, else
// limit - 1 of the limit + 1 clients (the last two are rejected).
int const expectedReads = (limit == 0) ? static_cast<int>(clients.size()) : limit - 1;
BEAST_EXPECT(successfulReads == expectedReads);
}
void

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

Some files were not shown because too many files have changed in this diff Show More