Compare commits

..

19 Commits

Author SHA1 Message Date
Ayaz Salikhov
8abe82eefa ci: Redesign matrix configuration based on Nix images (#7385)
Co-authored-by: semgrep-companion-app[bot] <218312740+semgrep-companion-app[bot]@users.noreply.github.com>
2026-06-04 20:02:59 +00:00
Ayaz Salikhov
5b8e6cd1dd test: Fix LCOV_EXCL_END -> LCOV_EXCL_STOP (#7407) 2026-06-04 19:35:36 +00:00
Ayaz Salikhov
12e81abef3 ci: Improve sanitizer-libs, add doxygen, dpkg, rpm in nix (#7403) 2026-06-04 14:52:42 +00:00
Ayaz Salikhov
6c543426c3 ci: Fix clang asan include dirs in nix images, add curl & gnupg (#7400) 2026-06-03 22:19:15 +00:00
yinyiqian1
e5cf1a0985 fix: Add zero NFT Offer ID check for NFTokenCancelOffer (#7391) 2026-06-03 19:30:20 +00:00
Ayaz Salikhov
023bdaeeed ci: Install gcov, nettools, cacert in nix images (#7398) 2026-06-03 19:14:17 +00:00
Bart
96b2c0964f refactor: Replace intr_ptr::SharedPtr<SHAMapTreeNode> by SHAMapTreeNodePtr (#7396)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-06-03 15:34:19 +00:00
Ayaz Salikhov
1441d4690d chore: Update flake.lock to allow conan with clang-22 support (#7390) 2026-06-03 00:16:02 +00:00
Vito Tumas
225ed204ad test: Suppress invariant-failure logs in Vault and LoanBroker bug-regression tests (#7379) 2026-06-02 17:12:09 +00:00
Ayaz Salikhov
ad111bcc22 ci: Patch binaries in nix-based images and test in every distro (#7376)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-02 13:51:20 +00:00
Ayaz Salikhov
d4cb68d5a1 ci: Check binaries separately from building them (#7355)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-01 16:47:01 +00:00
dependabot[bot]
e209ee5371 ci: [DEPENDABOT] bump eps1lon/actions-label-merge-conflict from 3.0.3 to 3.1.0 (#7375)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-01 15:29:12 +00:00
Vito Tumas
109b649106 refactor: Use STLedgerEntry type aliases instead of std::shared_ptr (#7282) 2026-06-01 15:27:13 +00:00
Michael Legleux
0fffe23abc fix: Adjust xrpld systemd service and update timer (#7374) 2026-06-01 03:33:19 +00:00
Bart
7e15621e7b release: Bump version to 3.2.0-rc3 (#7371)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-05-31 22:55:18 +00:00
Vito Tumas
99431d7833 fix: Pin overpayment principal reduction to exact on-grid value (#7360) 2026-05-31 22:54:23 +00:00
Ed Hennis
47365f4220 fix: Improve upward rounding edge cases for Number::operator/= (#7328)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Vito Tumas <5780819+Tapanito@users.noreply.github.com>
2026-05-31 00:23:29 +00:00
Bart
1599c1a672 refactor: Revert "perf: Remove unnecessary caches (#5439)" (#7359)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-05-30 18:48:59 +00:00
yinyiqian1
763dd503be fix: Add zero domainID check for permissionedDomain (#7362) 2026-05-30 00:16:25 +00:00
357 changed files with 3130 additions and 7041 deletions

View File

@@ -35,9 +35,8 @@ runs:
LOG_VERBOSITY: ${{ inputs.log_verbosity }}
SANITIZERS: ${{ inputs.sanitizers }}
run: |
echo 'Installing dependencies.'
conan install \
--profile ci \
--profile:all ci \
--build="${BUILD_OPTION}" \
--options:host='&:tests=True' \
--options:host='&:xrpld=True' \

View File

@@ -0,0 +1,34 @@
name: Set compiler environment
description: "Set CC and CXX environment variables for the given compiler."
inputs:
compiler:
description: 'The compiler to use ("gcc" or "clang").'
required: true
runs:
using: composite
steps:
- name: Set CC and CXX for gcc
if: ${{ inputs.compiler == 'gcc' }}
shell: bash
run: |
echo "CC=gcc" >>"${GITHUB_ENV}"
echo "CXX=g++" >>"${GITHUB_ENV}"
- name: Set CC and CXX for clang
if: ${{ inputs.compiler == 'clang' }}
shell: bash
run: |
echo "CC=clang" >>"${GITHUB_ENV}"
echo "CXX=clang++" >>"${GITHUB_ENV}"
- name: Fail on unknown compiler
if: ${{ inputs.compiler != 'gcc' && inputs.compiler != 'clang' }}
shell: bash
env:
COMPILER: ${{ inputs.compiler }}
run: |
echo "Unknown compiler: $COMPILER" >&2
exit 1

View File

@@ -15,32 +15,35 @@ runs:
using: composite
steps:
- name: Set up Conan configuration
- name: Apply custom configuration to global.conf
shell: bash
run: |
echo 'Installing configuration.'
cat conan/global.conf ${{ runner.os == 'Linux' && '>>' || '>' }} $(conan config home)/global.conf
echo 'Conan configuration:'
conan config show '*'
- name: Set up Conan profile
- name: Show global configuration
shell: bash
run: |
conan config show '*'
- name: Install profiles
shell: bash
run: |
echo 'Installing profile.'
conan config install conan/profiles/ -tf $(conan config home)/profiles/
echo 'Conan profile:'
- name: Show CI profile
shell: bash
run: |
conan profile show --profile ci
- name: Set up Conan remote
- name: Add a remote
shell: bash
env:
REMOTE_NAME: ${{ inputs.remote_name }}
REMOTE_URL: ${{ inputs.remote_url }}
run: |
echo "Adding Conan remote '${REMOTE_NAME}' at '${REMOTE_URL}'."
conan remote add --index 0 --force "${REMOTE_NAME}" "${REMOTE_URL}"
echo 'Listing Conan remotes.'
- name: List remotes
shell: bash
run: |
conan remote list

View File

@@ -1,384 +1,281 @@
#!/usr/bin/env python3
import argparse
import dataclasses
import itertools
import json
from dataclasses import dataclass
from pathlib import Path
THIS_DIR = Path(__file__).parent.resolve()
_BASE_CMAKE_ARGS = ["-Dtests=ON", "-Dwerr=ON", "-Dxrpld=ON", "-Dwextra=ON"]
@dataclass
class Config:
architecture: list[dict]
os: list[dict]
# Maps sanitizer names (as used in cmake) to short config-name suffixes.
_SANITIZER_SUFFIX: dict[str, str] = {
"address": "asan",
"undefinedbehavior": "ubsan",
"thread": "tsan",
}
def get_cmake_args(build_type: str, extra_args: str) -> str:
"""Get the full list of CMake arguments for a config."""
args = _BASE_CMAKE_ARGS.copy()
if build_type == "Release":
args.append("-Dassert=ON")
if extra_args:
args.extend(extra_args.split())
return " ".join(args)
# ---------------------------------------------------------------------------
# Input types — shapes of the JSON config files
# ---------------------------------------------------------------------------
@dataclasses.dataclass
class LinuxConfig:
"""One entry in linux.json's 'configs' or 'package_configs' arrays."""
compiler: list[str]
build_type: list[str]
cmake_args: list[str]
arch: list[str]
sanitizers: list[str] = dataclasses.field(default_factory=list)
suffix: str = ""
extra_cmake_args: str = ""
image: str = "" # only used by package_configs entries
"""
Generate a strategy matrix for GitHub Actions CI.
@dataclasses.dataclass
class LinuxFile:
"""Shape of linux.json."""
On each PR commit we will build a selection of Debian, RHEL, Ubuntu, MacOS, and
Windows configurations, while upon merge into the develop or release branches,
we will build all configurations, and test most of them.
image_tag: str
configs: dict[str, list[LinuxConfig]] # distro → configs
package_configs: dict[str, list[LinuxConfig]] # distro → packaging configs
We will further set additional CMake arguments as follows:
- All builds will have the `tests`, `werr`, and `xrpld` options.
- All builds will have the `wextra` option except for GCC 12 and Clang 16.
- All release builds will have the `assert` option.
- Certain Debian Bookworm configurations will change the reference fee, enable
codecov, and enable voidstar in PRs.
"""
@classmethod
def load(cls, path: Path) -> "LinuxFile":
data = json.loads(path.read_text())
def parse(section: dict) -> dict[str, list[LinuxConfig]]:
return {
distro: [LinuxConfig(**c) for c in cfgs]
for distro, cfgs in section.items()
}
return cls(
image_tag=data["image_tag"],
configs=parse(data["configs"]),
package_configs=parse(data.get("package_configs", {})),
)
def build_config_name(os_entry: dict[str, str], platform: str, build_type: str) -> str:
parts = [os_entry["distro_name"]]
for key in ("distro_version", "compiler_name", "compiler_version"):
if value := os_entry[key]:
parts.append(value)
parts.append("arm64" if "arm64" in platform else "amd64")
parts.append(build_type.lower())
return "-".join(parts)
@dataclasses.dataclass
class PlatformConfig:
"""One entry in macos.json's or windows.json's 'configs' array."""
build_type: list[str]
build_only: bool = False # if true, skip tests (e.g. macos/Windows Debug)
extra_cmake_args: str = ""
def __post_init__(self) -> None:
if isinstance(self.build_type, str):
self.build_type = [self.build_type]
def generate_packaging_matrix(config: Config) -> list[dict]:
"""Emit one entry per os entry with `package: true`. Architecture is
hardcoded to linux/amd64 here (and the runner is hardcoded at the
workflow level) until arm64 packaging is ready.
@dataclasses.dataclass
class PlatformFile:
"""Shape of macos.json and windows.json."""
platform: str # e.g. "macos/arm64" or "windows/amd64"
runner: list[str] # GitHub Actions runner labels
configs: list[PlatformConfig]
@classmethod
def load(cls, path: Path) -> "PlatformFile":
data = json.loads(path.read_text())
return cls(
platform=data["platform"],
runner=data["runner"],
configs=[PlatformConfig(**c) for c in data["configs"]],
)
# ---------------------------------------------------------------------------
# Output types — shapes of the generated GitHub Actions matrix entries
# ---------------------------------------------------------------------------
@dataclasses.dataclass
class Architecture:
platform: str
runner: list[str]
@dataclasses.dataclass
class MatrixEntry:
"""One entry in the generated build/test strategy matrix."""
config_name: str
cmake_args: str
cmake_target: str
build_only: bool
build_type: str
architecture: Architecture
sanitizers: str
image: str = "" # container image; empty for macOS/Windows (runs natively)
compiler: str = "" # compiler name ("gcc" or "clang"); empty for macOS/Windows
@dataclasses.dataclass
class PackagingEntry:
"""One entry in the generated packaging strategy matrix."""
artifact_name: str
image: str
distro: str # e.g. "debian" or "rhel"; drives package-format-specific steps
# ---------------------------------------------------------------------------
# Matrix expansion
# ---------------------------------------------------------------------------
_ARCHS: dict[str, Architecture] = {
"amd64": Architecture(
platform="linux/amd64", runner=["self-hosted", "Linux", "X64", "heavy"]
),
"arm64": Architecture(
platform="linux/arm64",
runner=["self-hosted", "Linux", "ARM64", "heavy-arm64"],
),
}
def expand_linux_matrix(linux: LinuxFile) -> list[MatrixEntry]:
"""Expand a LinuxFile into a flat list of matrix entries.
Each config entry is expanded over the cross-product of its
compiler, build_type, sanitizers, and architecture lists.
"""
return [
{
"artifact_name": f"xrpld-{build_config_name(os, 'linux/amd64', 'Release')}",
"os": os,
}
for os in config.os
if os.get("package", False)
]
entries: list[MatrixEntry] = []
for distro, configs in linux.configs.items():
for cfg in configs:
# An empty sanitizers list means "one entry with no sanitizer".
effective_sanitizers = cfg.sanitizers or [""]
effective_archs = {arch: _ARCHS[arch] for arch in cfg.arch}
def generate_strategy_matrix(all: bool, config: Config) -> list[dict]:
configurations = []
for architecture, os, build_type, cmake_args in itertools.product(
config.architecture, config.os, config.build_type, config.cmake_args
):
# The default CMake target is 'all' for Linux and MacOS and 'install'
# for Windows, but it can get overridden for certain configurations.
cmake_target = "install" if os["distro_name"] == "windows" else "all"
# We build and test all configurations by default, except for Windows in
# Debug, because it is too slow, as well as when code coverage is
# enabled as that mode already runs the tests.
build_only = False
if os["distro_name"] == "windows" and build_type == "Debug":
build_only = True
# Only generate a subset of configurations in PRs.
if not all:
# Debian:
# - Bookworm using GCC 13: Debug on linux/amd64, set the reference
# fee to 500 and enable code coverage (which will be done below).
# - Bookworm using GCC 15: Debug on linux/amd64, enable Address and
# UB sanitizers (which will be done below).
# - Bookworm using Clang 16: Debug on linux/amd64, enable voidstar.
# - Bookworm using Clang 17: Release on linux/amd64, set the
# reference fee to 1000.
# - Bookworm using Clang 20: Debug on linux/amd64, enable Address
# and UB sanitizers (which will be done below).
if os["distro_name"] == "debian":
skip = True
if os["distro_version"] == "bookworm":
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-13"
and build_type == "Debug"
and architecture["platform"] == "linux/amd64"
):
cmake_args = f"-DUNIT_TEST_REFERENCE_FEE=500 {cmake_args}"
skip = False
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-15"
and build_type == "Release"
and architecture["platform"] == "linux/amd64"
):
skip = False
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-16"
and build_type == "Debug"
and architecture["platform"] == "linux/amd64"
):
cmake_args = f"-Dvoidstar=ON {cmake_args}"
skip = False
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-17"
and build_type == "Release"
and architecture["platform"] == "linux/amd64"
):
cmake_args = f"-DUNIT_TEST_REFERENCE_FEE=1000 {cmake_args}"
skip = False
elif os["distro_version"] == "trixie":
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-22"
and build_type == "Debug"
and architecture["platform"] == "linux/amd64"
):
skip = False
if skip:
continue
# RHEL:
# - 9 using GCC 12: Debug and Release on linux/amd64
# (Release is required for RPM packaging).
# - 10 using Clang: Release on linux/amd64.
if os["distro_name"] == "rhel":
skip = True
if os["distro_version"] == "9":
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12"
and build_type in ["Debug", "Release"]
and architecture["platform"] == "linux/amd64"
):
skip = False
elif os["distro_version"] == "10":
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-any"
and build_type == "Release"
and architecture["platform"] == "linux/amd64"
):
skip = False
if skip:
continue
# Ubuntu:
# - Jammy using GCC 12: Debug on linux/arm64, Release on
# linux/amd64 (Release is required for DEB packaging).
# - Noble using GCC 14: Release on linux/amd64.
# - Noble using Clang 18: Debug on linux/amd64.
# - Noble using Clang 19: Release on linux/arm64.
if os["distro_name"] == "ubuntu":
skip = True
if os["distro_version"] == "jammy":
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12"
and build_type == "Debug"
and architecture["platform"] == "linux/arm64"
):
skip = False
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12"
and build_type == "Release"
and architecture["platform"] == "linux/amd64"
):
skip = False
elif os["distro_version"] == "noble":
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-14"
and build_type == "Release"
and architecture["platform"] == "linux/amd64"
):
skip = False
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-18"
and build_type == "Debug"
and architecture["platform"] == "linux/amd64"
):
skip = False
if (
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-19"
and build_type == "Release"
and architecture["platform"] == "linux/arm64"
):
skip = False
if skip:
continue
# MacOS:
# - Debug on macos/arm64.
if os["distro_name"] == "macos" and not (
build_type == "Debug" and architecture["platform"] == "macos/arm64"
for compiler, build_type, sanitizer, (arch, arch_info) in itertools.product(
cfg.compiler,
cfg.build_type,
effective_sanitizers,
effective_archs.items(),
):
continue
name = f"{distro}-{compiler}-{build_type.lower()}-{arch}"
suffix_parts = [
s for s in [cfg.suffix, _SANITIZER_SUFFIX.get(sanitizer, "")] if s
]
if suffix_parts:
name += "-" + "-".join(suffix_parts)
# Windows:
# - Release on windows/amd64.
if os["distro_name"] == "windows" and not (
build_type == "Release" and architecture["platform"] == "windows/amd64"
):
continue
# Additional CMake arguments.
cmake_args = f"{cmake_args} -Dtests=ON -Dwerr=ON -Dxrpld=ON"
if not f"{os['compiler_name']}-{os['compiler_version']}" in [
"gcc-12",
"clang-16",
]:
cmake_args = f"{cmake_args} -Dwextra=ON"
if build_type == "Release":
cmake_args = f"{cmake_args} -Dassert=ON"
# We skip all RHEL on arm64 due to a build failure that needs further
# investigation.
if os["distro_name"] == "rhel" and architecture["platform"] == "linux/arm64":
continue
# We skip all clang 20+ on arm64 due to Boost build error.
if (
os["compiler_name"] == "clang"
and os["compiler_version"].isdigit()
and int(os["compiler_version"]) >= 20
and architecture["platform"] == "linux/arm64"
):
continue
# Enable code coverage for Debian Bookworm using GCC 13 in Debug on
# linux/amd64.
if (
f"{os['distro_name']}-{os['distro_version']}" == "debian-bookworm"
and f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-13"
and build_type == "Debug"
and architecture["platform"] == "linux/amd64"
):
cmake_args = f"{cmake_args} -Dcoverage=ON -Dcoverage_format=xml -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_C_FLAGS=-O0 -DCMAKE_CXX_FLAGS=-O0"
# Enable unity build for Ubuntu Jammy using GCC 12 in Debug on
# linux/amd64.
if (
f"{os['distro_name']}-{os['distro_version']}" == "ubuntu-jammy"
and f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12"
and build_type == "Debug"
and architecture["platform"] == "linux/amd64"
):
cmake_args = f"{cmake_args} -Dunity=ON"
# Generate a unique name for the configuration, e.g. macos-arm64-debug
# or debian-bookworm-gcc-12-amd64-release.
config_name = build_config_name(os, architecture["platform"], build_type)
if "-Dcoverage=ON" in cmake_args:
config_name += "-coverage"
if "-Dunity=ON" in cmake_args:
config_name += "-unity"
# Add the configuration to the list, with the most unique fields first,
# so that they are easier to identify in the GitHub Actions UI, as long
# names get truncated.
# Add Address and UB sanitizers as separate configurations for specific
# bookworm distros. Thread sanitizer is currently disabled (see below).
# GCC-Asan xrpld-embedded tests are failing because of https://github.com/google/sanitizers/issues/856
if (
os["distro_version"] == "bookworm"
and f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-15"
) or (
os["distro_version"] == "trixie"
and f"{os['compiler_name']}-{os['compiler_version']}" == "clang-22"
):
# Add ASAN and UBSAN configurations for both gcc-15 and clang-22
configurations.append(
{
"config_name": config_name + "-asan",
"cmake_args": cmake_args,
"cmake_target": cmake_target,
"build_only": build_only,
"build_type": build_type,
"os": os,
"architecture": architecture,
"sanitizers": "address",
}
)
configurations.append(
{
"config_name": config_name + "-ubsan",
"cmake_args": cmake_args,
"cmake_target": cmake_target,
"build_only": build_only,
"build_type": build_type,
"os": os,
"architecture": architecture,
"sanitizers": "undefinedbehavior",
}
)
# TSAN is deactivated due to seg faults with latest compilers.
activate_tsan = False
if activate_tsan:
configurations.append(
{
"config_name": config_name + "-tsan-ubsan",
"cmake_args": cmake_args,
"cmake_target": cmake_target,
"build_only": build_only,
"build_type": build_type,
"os": os,
"architecture": architecture,
"sanitizers": "thread,undefinedbehavior",
}
entries.append(
MatrixEntry(
config_name=name,
image=f"ghcr.io/xrplf/xrpld/nix-{distro}:{linux.image_tag}",
cmake_args=get_cmake_args(build_type, cfg.extra_cmake_args),
cmake_target="all",
build_only=False,
build_type=build_type,
architecture=arch_info,
sanitizers=sanitizer,
compiler=compiler,
)
)
return entries
def expand_linux_packaging(linux: LinuxFile) -> list[PackagingEntry]:
"""Generate the packaging matrix from a LinuxFile's package_configs section.
Packaging uses vanilla distro images (debian:bookworm, ubi9, …) instead of
the nix-based build images, because deb/rpm tooling (debhelper, rpm-build)
is taken from the distro's archive rather than from nixpkgs. Each config
entry carries its own 'image'.
"""
entries = []
for distro, configs in linux.package_configs.items():
for cfg in configs:
for compiler, build_type in itertools.product(cfg.compiler, cfg.build_type):
entries.append(
PackagingEntry(
artifact_name=f"xrpld-{distro}-{compiler}-{build_type.lower()}-amd64",
image=cfg.image,
distro=distro,
)
)
return entries
def expand_platform_matrix(pf: PlatformFile) -> list[MatrixEntry]:
"""Expand a PlatformFile (macOS or Windows) into matrix entries."""
platform_name, arch = pf.platform.split("/")
is_windows = platform_name == "windows"
entries: list[MatrixEntry] = []
for cfg in pf.configs:
for build_type in cfg.build_type:
entries.append(
MatrixEntry(
config_name=f"{platform_name}-{arch}-{build_type.lower()}",
cmake_args=get_cmake_args(build_type, cfg.extra_cmake_args),
cmake_target="install" if is_windows else "all",
build_only=cfg.build_only,
build_type=build_type,
architecture=Architecture(platform=pf.platform, runner=pf.runner),
sanitizers="",
)
else:
configurations.append(
{
"config_name": config_name,
"cmake_args": cmake_args,
"cmake_target": cmake_target,
"build_only": build_only,
"build_type": build_type,
"os": os,
"architecture": architecture,
"sanitizers": "",
}
)
return configurations
return entries
def read_config(file: Path) -> Config:
config = json.loads(file.read_text())
if (
config["architecture"] is None
or config["os"] is None
or config["build_type"] is None
or config["cmake_args"] is None
):
raise Exception("Invalid configuration file.")
return Config(**config)
# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"-a",
"--all",
help="Set to generate all configurations (generally used when merging a PR) or leave unset to generate a subset of configurations (generally used when committing to a PR).",
action="store_true",
parser = argparse.ArgumentParser(
description="Generate a CI strategy matrix for all platforms or a specific one."
)
parser.add_argument(
"-c",
"--config",
help="Path to the JSON file containing the strategy matrix configurations.",
required=False,
type=Path,
help="Platform to generate for ('linux', 'macos', or 'windows'). Defaults to all platforms.",
choices=["linux", "macos", "windows"],
default=None,
)
parser.add_argument(
"-p",
"--packaging",
help="Emit the packaging matrix (derived from the 'package' field on os entries) instead of the build/test matrix.",
help="Emit the Linux packaging matrix instead of the build/test matrix.",
action="store_true",
)
args = parser.parse_args()
matrix = []
if args.packaging:
config_path = args.config if args.config else THIS_DIR / "linux.json"
matrix += generate_packaging_matrix(read_config(config_path))
elif args.config is None or args.config == "":
matrix += generate_strategy_matrix(
args.all, read_config(THIS_DIR / "linux.json")
)
matrix += generate_strategy_matrix(
args.all, read_config(THIS_DIR / "macos.json")
)
matrix += generate_strategy_matrix(
args.all, read_config(THIS_DIR / "windows.json")
)
else:
matrix += generate_strategy_matrix(args.all, read_config(args.config))
matrix: list[MatrixEntry] | list[PackagingEntry] = []
# Generate the strategy matrix.
print(f"matrix={json.dumps({'include': matrix})}")
if args.packaging:
matrix = expand_linux_packaging(LinuxFile.load(THIS_DIR / "linux.json"))
else:
if args.config in ("linux", None):
matrix += expand_linux_matrix(LinuxFile.load(THIS_DIR / "linux.json"))
if args.config in ("macos", None):
matrix += expand_platform_matrix(PlatformFile.load(THIS_DIR / "macos.json"))
if args.config in ("windows", None):
matrix += expand_platform_matrix(
PlatformFile.load(THIS_DIR / "windows.json")
)
print(f"matrix={json.dumps({'include': [dataclasses.asdict(e) for e in matrix]})}")

View File

@@ -1,221 +1,83 @@
{
"architecture": [
{
"platform": "linux/amd64",
"runner": ["self-hosted", "Linux", "X64", "heavy"]
},
{
"platform": "linux/arm64",
"runner": ["self-hosted", "Linux", "ARM64", "heavy-arm64"]
}
],
"os": [
{
"distro_name": "debian",
"distro_version": "bookworm",
"compiler_name": "gcc",
"compiler_version": "12",
"image_sha": "4c086b9"
},
{
"distro_name": "debian",
"distro_version": "bookworm",
"compiler_name": "gcc",
"compiler_version": "13",
"image_sha": "4c086b9"
},
{
"distro_name": "debian",
"distro_version": "bookworm",
"compiler_name": "gcc",
"compiler_version": "14",
"image_sha": "4c086b9"
},
{
"distro_name": "debian",
"distro_version": "bookworm",
"compiler_name": "gcc",
"compiler_version": "15",
"image_sha": "4c086b9"
},
{
"distro_name": "debian",
"distro_version": "bookworm",
"compiler_name": "clang",
"compiler_version": "16",
"image_sha": "4c086b9"
},
{
"distro_name": "debian",
"distro_version": "bookworm",
"compiler_name": "clang",
"compiler_version": "17",
"image_sha": "4c086b9"
},
{
"distro_name": "debian",
"distro_version": "bookworm",
"compiler_name": "clang",
"compiler_version": "18",
"image_sha": "4c086b9"
},
{
"distro_name": "debian",
"distro_version": "bookworm",
"compiler_name": "clang",
"compiler_version": "19",
"image_sha": "4c086b9"
},
{
"distro_name": "debian",
"distro_version": "bookworm",
"compiler_name": "clang",
"compiler_version": "20",
"image_sha": "4c086b9"
},
{
"distro_name": "debian",
"distro_version": "trixie",
"compiler_name": "gcc",
"compiler_version": "14",
"image_sha": "4c086b9"
},
{
"distro_name": "debian",
"distro_version": "trixie",
"compiler_name": "gcc",
"compiler_version": "15",
"image_sha": "4c086b9"
},
{
"distro_name": "debian",
"distro_version": "trixie",
"compiler_name": "clang",
"compiler_version": "20",
"image_sha": "4c086b9"
},
{
"distro_name": "debian",
"distro_version": "trixie",
"compiler_name": "clang",
"compiler_version": "21",
"image_sha": "4c086b9"
},
{
"distro_name": "debian",
"distro_version": "trixie",
"compiler_name": "clang",
"compiler_version": "22",
"image_sha": "4c086b9"
},
{
"distro_name": "rhel",
"distro_version": "8",
"compiler_name": "gcc",
"compiler_version": "14",
"image_sha": "4c086b9"
},
{
"distro_name": "rhel",
"distro_version": "8",
"compiler_name": "clang",
"compiler_version": "any",
"image_sha": "4c086b9"
},
{
"distro_name": "rhel",
"distro_version": "9",
"compiler_name": "gcc",
"compiler_version": "12",
"image_sha": "4c086b9",
"package": true
},
{
"distro_name": "rhel",
"distro_version": "9",
"compiler_name": "gcc",
"compiler_version": "13",
"image_sha": "4c086b9"
},
{
"distro_name": "rhel",
"distro_version": "9",
"compiler_name": "gcc",
"compiler_version": "14",
"image_sha": "4c086b9"
},
{
"distro_name": "rhel",
"distro_version": "9",
"compiler_name": "clang",
"compiler_version": "any",
"image_sha": "4c086b9"
},
{
"distro_name": "rhel",
"distro_version": "10",
"compiler_name": "gcc",
"compiler_version": "14",
"image_sha": "4c086b9"
},
{
"distro_name": "rhel",
"distro_version": "10",
"compiler_name": "clang",
"compiler_version": "any",
"image_sha": "4c086b9"
},
{
"distro_name": "ubuntu",
"distro_version": "jammy",
"compiler_name": "gcc",
"compiler_version": "12",
"image_sha": "4c086b9",
"package": true
},
{
"distro_name": "ubuntu",
"distro_version": "noble",
"compiler_name": "gcc",
"compiler_version": "13",
"image_sha": "4c086b9"
},
{
"distro_name": "ubuntu",
"distro_version": "noble",
"compiler_name": "gcc",
"compiler_version": "14",
"image_sha": "4c086b9"
},
{
"distro_name": "ubuntu",
"distro_version": "noble",
"compiler_name": "clang",
"compiler_version": "16",
"image_sha": "4c086b9"
},
{
"distro_name": "ubuntu",
"distro_version": "noble",
"compiler_name": "clang",
"compiler_version": "17",
"image_sha": "4c086b9"
},
{
"distro_name": "ubuntu",
"distro_version": "noble",
"compiler_name": "clang",
"compiler_version": "18",
"image_sha": "4c086b9"
},
{
"distro_name": "ubuntu",
"distro_version": "noble",
"compiler_name": "clang",
"compiler_version": "19",
"image_sha": "4c086b9"
}
],
"build_type": ["Debug", "Release"],
"cmake_args": [""]
"image_tag": "sha-6c54342",
"configs": {
"ubuntu": [
{
"compiler": ["gcc", "clang"],
"build_type": ["Debug", "Release"],
"arch": ["amd64", "arm64"]
},
{
"compiler": ["gcc", "clang"],
"build_type": ["Debug"],
"arch": ["amd64"],
"sanitizers": ["address", "undefinedbehavior"]
},
{
"compiler": ["gcc"],
"build_type": ["Debug"],
"arch": ["amd64"],
"suffix": "coverage",
"extra_cmake_args": "-DUNIT_TEST_REFERENCE_FEE=500 -Dcoverage=ON -Dcoverage_format=xml -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_C_FLAGS=-O0 -DCMAKE_CXX_FLAGS=-O0"
},
{
"compiler": ["clang"],
"build_type": ["Debug"],
"arch": ["amd64"],
"suffix": "voidstar",
"extra_cmake_args": "-Dvoidstar=ON"
},
{
"compiler": ["clang"],
"build_type": ["Release"],
"arch": ["amd64"],
"suffix": "reffee",
"extra_cmake_args": "-DUNIT_TEST_REFERENCE_FEE=1000"
},
{
"compiler": ["gcc"],
"build_type": ["Debug"],
"arch": ["amd64"],
"suffix": "unity",
"extra_cmake_args": "-Dunity=ON"
}
],
"debian": [
{
"compiler": ["gcc"],
"build_type": ["Release"],
"arch": ["amd64"]
}
],
"rhel": [
{
"compiler": ["gcc"],
"build_type": ["Release"],
"arch": ["amd64"]
}
]
},
"package_configs": {
"debian": [
{
"compiler": ["gcc"],
"build_type": ["Release"],
"arch": ["amd64"],
"image": "debian:bookworm"
}
],
"rhel": [
{
"compiler": ["gcc"],
"build_type": ["Release"],
"arch": ["amd64"],
"image": "registry.access.redhat.com/ubi9/ubi:latest"
}
]
}
}

View File

@@ -1,19 +1,15 @@
{
"architecture": [
"platform": "macos/arm64",
"runner": ["self-hosted", "macOS", "ARM64", "mac-runner-m1"],
"configs": [
{
"platform": "macos/arm64",
"runner": ["self-hosted", "macOS", "ARM64", "mac-runner-m1"]
}
],
"os": [
"build_type": "Release",
"extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5"
},
{
"distro_name": "macos",
"distro_version": "",
"compiler_name": "",
"compiler_version": "",
"image_sha": ""
"build_type": "Debug",
"extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
"build_only": true
}
],
"build_type": ["Debug", "Release"],
"cmake_args": ["-DCMAKE_POLICY_VERSION_MINIMUM=3.5"]
]
}

View File

@@ -1,19 +1,8 @@
{
"architecture": [
{
"platform": "windows/amd64",
"runner": ["self-hosted", "Windows", "devbox"]
}
],
"os": [
{
"distro_name": "windows",
"distro_version": "",
"compiler_name": "",
"compiler_version": "",
"image_sha": ""
}
],
"build_type": ["Debug", "Release"],
"cmake_args": [""]
"platform": "windows/amd64",
"runner": ["self-hosted", "Windows", "devbox"],
"configs": [
{ "build_type": "Release" },
{ "build_type": "Debug", "build_only": true }
]
}

View File

@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check if PRs are dirty
uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3.0.3
uses: eps1lon/actions-label-merge-conflict@0273be72a0bbd58fcd71d0d6c02c209b50d1e5e1 # v3.1.0
with:
dirtyLabel: "PR: has conflicts"
repoToken: "${{ secrets.GITHUB_TOKEN }}"

View File

@@ -33,7 +33,6 @@ jobs:
with:
ccache_enabled: false
os: ${{ matrix.os }}
strategy_matrix: minimal
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -88,7 +88,6 @@ jobs:
# not identical to a regular compilation.
ccache_enabled: ${{ github.repository_owner == 'XRPLF' && !startsWith(github.ref, 'refs/heads/release') }}
os: ${{ matrix.os }}
strategy_matrix: ${{ github.event_name == 'schedule' && 'all' || 'minimal' }}
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -57,6 +57,12 @@ on:
type: string
default: ""
compiler:
description: 'The compiler to use ("gcc" or "clang"). Leave empty for macOS/Windows (uses system default).'
required: false
type: string
default: ""
secrets:
CODECOV_TOKEN:
description: "The Codecov token to use for uploading coverage reports."
@@ -124,6 +130,12 @@ jobs:
with:
subtract: ${{ inputs.nproc_subtract }}
- name: Set compiler environment (Linux)
if: ${{ runner.os == 'Linux' }}
uses: ./.github/actions/set-compiler-env
with:
compiler: ${{ inputs.compiler }}
- name: Setup Conan
env:
SANITIZERS: ${{ inputs.sanitizers }}
@@ -191,6 +203,21 @@ jobs:
--parallel "${BUILD_NPROC}" \
--target "${CMAKE_TARGET}"
# This step is needed to allow running in non-Nix environments
- name: Patch binary to use default loader and remove rpath (Linux)
if: ${{ runner.os == 'Linux' && env.SANITIZERS_ENABLED == 'false' }}
run: |
loader="$(/tmp/loader-path.sh)"
patchelf --set-interpreter "${loader}" --remove-rpath "${{ env.BUILD_DIR }}/xrpld"
# We're only running aarch64 Linux builds in Ubuntu-based images, so this is kept simple
- name: Install libatomic (Linux aarch64)
if: ${{ runner.os == 'Linux' && runner.arch == 'ARM64' }}
run: |
apt update --yes
apt install -y --no-install-recommends \
libatomic1
- name: Show ccache statistics
if: ${{ inputs.ccache_enabled }}
run: |
@@ -217,7 +244,7 @@ jobs:
./xrpld --definitions | python3 -m json.tool >server_definitions.json
- name: Upload server definitions
if: ${{ github.event.repository.visibility == 'public' && inputs.config_name == 'debian-bookworm-gcc-13-amd64-release' }}
if: ${{ github.event.repository.visibility == 'public' && inputs.config_name == 'debian-gcc-release-amd64' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: server-definitions
@@ -279,7 +306,25 @@ jobs:
set -o pipefail
# Coverage builds are slower due to instrumentation; use fewer parallel jobs to avoid flakiness
[ "$COVERAGE_ENABLED" = "true" ] && BUILD_NPROC=$((BUILD_NPROC - 2))
./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee unittest.log
# The resolver/preload workaround is only correct for the ASan build:
# a regular build doesn't hit the __dn_expand interceptor bug, and must
# NOT have libasan injected. So only preload when xrpld is ASan-built.
#
# libresolv hosts getaddrinfo's resolver helpers (dn_expand, res_*). Under ASan
# these are intercepted via dlsym(RTLD_NEXT, ...), which yields a NULL pointer
# and crashes DNS resolution if libresolv isn't loaded. Linking it guarantees
# the symbols are present; it's a harmless no-op on glibc >= 2.34 (merged into
# libc) and is what the compiler driver already does for sanitizer builds.
# https://github.com/llvm/llvm-project/issues/59007
# https://github.com/google/sanitizers/issues/1592
if ldd ./xrpld | grep -q libasan; then
PRELOAD="$(gcc -print-file-name=libasan.so):/usr/lib/x86_64-linux-gnu/libresolv.so.2"
else
PRELOAD=""
fi
LD_PRELOAD="$PRELOAD" ./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee unittest.log
- name: Show test failure summary
if: ${{ failure() && !inputs.build_only }}

View File

@@ -19,13 +19,6 @@ on:
required: true
type: string
strategy_matrix:
# TODO: Support additional strategies, e.g. "ubuntu" for generating all Ubuntu configurations.
description: 'The strategy matrix to use for generating the configurations ("minimal", "all").'
required: false
type: string
default: "minimal"
secrets:
CODECOV_TOKEN:
description: "The Codecov token to use for uploading coverage reports."
@@ -37,7 +30,6 @@ jobs:
uses: ./.github/workflows/reusable-strategy-matrix.yml
with:
os: ${{ inputs.os }}
strategy_matrix: ${{ inputs.strategy_matrix }}
# Build and test the binary for each configuration.
build-test-config:
@@ -47,7 +39,6 @@ jobs:
strategy:
fail-fast: ${{ github.event_name == 'merge_group' }}
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
max-parallel: 10
with:
build_only: ${{ matrix.build_only }}
build_type: ${{ matrix.build_type }}
@@ -55,8 +46,9 @@ jobs:
cmake_args: ${{ matrix.cmake_args }}
cmake_target: ${{ matrix.cmake_target }}
runs_on: ${{ toJSON(matrix.architecture.runner) }}
image: ${{ contains(matrix.architecture.platform, 'linux') && format('ghcr.io/xrplf/ci/{0}-{1}:{2}-{3}-sha-{4}', matrix.os.distro_name, matrix.os.distro_version, matrix.os.compiler_name, matrix.os.compiler_version, matrix.os.image_sha) || '' }}
image: ${{ matrix.image || '' }}
config_name: ${{ matrix.config_name }}
sanitizers: ${{ matrix.sanitizers }}
compiler: ${{ matrix.compiler || '' }}
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -1,8 +1,7 @@
# Build Linux packages (DEB and RPM) from pre-built binary artifacts.
# Discovers which configurations to package from linux.json (os entries
# with "package": true) and fans out one job per entry. Today only
# linux/amd64 is emitted; the architecture is hardcoded both here
# (runner) and in generate.py.
# Discovers which configurations to package from linux.json (configs in
# "package_configs") and fans out one job per distro. Only linux/amd64 is
# supported; the runner is hardcoded in the job below.
name: Package
on:
@@ -33,13 +32,12 @@ jobs:
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: 3.13
python-version: "3.13"
- name: Generate packaging matrix
id: generate
working-directory: .github/scripts/strategy-matrix
run: |
./generate.py --packaging --config=linux.json >>"${GITHUB_OUTPUT}"
run: ./generate.py --packaging >>"${GITHUB_OUTPUT}"
generate-version:
runs-on: ubuntu-latest
@@ -66,10 +64,35 @@ jobs:
permissions:
contents: read
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
container: ${{ format('ghcr.io/xrplf/ci/{0}-{1}:{2}-{3}-sha-{4}', matrix.os.distro_name, matrix.os.distro_version, matrix.os.compiler_name, matrix.os.compiler_version, matrix.os.image_sha) }}
container: ${{ matrix.image }}
timeout-minutes: 30
steps:
# Packaging runs in a vanilla distro image, so the tooling has to come
# from the distro's archive: debhelper for deb, rpm-build (and the
# systemd / find-debuginfo macros it depends on) for rpm. Run this
# before actions/checkout so the latter can use git (real history) for
# build_pkg.sh's SOURCE_DATE_EPOCH; otherwise it falls back to a tarball
# download and the timestamp comes from wall-clock time.
- name: Install packaging tooling (deb)
if: ${{ matrix.distro == 'debian' }}
run: |
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y --no-install-recommends \
ca-certificates \
debhelper \
git
- name: Install packaging tooling (rpm)
if: ${{ matrix.distro == 'rhel' }}
run: |
dnf install -y --setopt=install_weak_deps=False \
git \
rpm-build \
redhat-rpm-config \
systemd-rpm-macros
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

View File

@@ -4,15 +4,9 @@ on:
workflow_call:
inputs:
os:
description: 'The operating system to use for the build ("linux", "macos", "windows").'
description: 'The operating system to use for the build ("linux", "macos", "windows", or empty for all).'
required: false
type: string
strategy_matrix:
# TODO: Support additional strategies, e.g. "ubuntu" for generating all Ubuntu configurations.
description: 'The strategy matrix to use for generating the configurations ("minimal", "all").'
required: false
type: string
default: "minimal"
outputs:
matrix:
description: "The generated strategy matrix."
@@ -34,12 +28,11 @@ jobs:
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: 3.13
python-version: "3.13"
- name: Generate strategy matrix
working-directory: .github/scripts/strategy-matrix
id: generate
env:
GENERATE_CONFIG: ${{ inputs.os != '' && format('--config={0}.json', inputs.os) || '' }}
GENERATE_OPTION: ${{ inputs.strategy_matrix == 'all' && '--all' || '' }}
run: ./generate.py ${GENERATE_OPTION} ${GENERATE_CONFIG} >>"${GITHUB_OUTPUT}"
GENERATE_CONFIG: ${{ inputs.os != '' && format('--config={0}', inputs.os) || '' }}
run: ./generate.py ${GENERATE_CONFIG} >>"${GITHUB_OUTPUT}"

View File

@@ -48,8 +48,6 @@ jobs:
# Generate the strategy matrix to be used by the following job.
generate-matrix:
uses: ./.github/workflows/reusable-strategy-matrix.yml
with:
strategy_matrix: ${{ github.event_name == 'pull_request' && 'minimal' || 'all' }}
# Build and upload the dependencies for each configuration.
run-upload-conan-deps:
@@ -58,9 +56,8 @@ jobs:
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
max-parallel: 10
runs-on: ${{ matrix.architecture.runner }}
container: ${{ contains(matrix.architecture.platform, 'linux') && format('ghcr.io/xrplf/ci/{0}-{1}:{2}-{3}-sha-{4}', matrix.os.distro_name, matrix.os.distro_version, matrix.os.compiler_name, matrix.os.compiler_version, matrix.os.image_sha) || null }}
container: ${{ matrix.image || null }}
steps:
- name: Cleanup workspace (macOS and Windows)
if: ${{ runner.os == 'macOS' || runner.os == 'Windows' }}
@@ -83,6 +80,12 @@ jobs:
with:
subtract: ${{ env.NPROC_SUBTRACT }}
- name: Set compiler environment (Linux)
if: ${{ runner.os == 'Linux' }}
uses: ./.github/actions/set-compiler-env
with:
compiler: ${{ matrix.compiler }}
- name: Setup Conan
env:
SANITIZERS: ${{ matrix.sanitizers }}

View File

@@ -953,6 +953,21 @@
#
# Optional keys for NuDB and RocksDB:
#
# cache_size Size of cache for database records. Default is 16384.
# Setting this value to 0 will use the default value.
#
# cache_age Length of time in minutes to keep database records
# cached. Default is 5 minutes. Setting this value to
# 0 will use the default value.
#
# Note: if cache_size or cache_age is not specified,
# default values will be used for the unspecified
# parameter.
#
# Note: the cache will not be created if online_delete
# is specified, because the rotating NodeStore does
# not use this cache).
#
# fast_load Boolean. If set, load the last persisted ledger
# from disk upon process start before syncing to
# the network. This is likely to improve performance

View File

@@ -145,13 +145,39 @@ else()
INTERFACE
-rdynamic
$<$<BOOL:${is_linux}>:-Wl,-z,relro,-z,now,--build-id>
# link to static libc/c++ iff: * static option set and * NOT APPLE (AppleClang does not support static
# libc/c++) and * NOT SANITIZERS (sanitizers typically don't work with static libc/c++)
$<$<AND:$<BOOL:${static}>,$<NOT:$<BOOL:${APPLE}>>,$<NOT:$<BOOL:${SANITIZERS_ENABLED}>>>:
# link to static libc/c++ if:
# * static option set and
# * NOT APPLE (AppleClang does not support static libc/c++)
$<$<AND:$<BOOL:${static}>,$<NOT:$<BOOL:${APPLE}>>>:
-static-libstdc++
-static-libgcc
>
)
# Keep -stdlib=libstdc++ off the compile commands, but preserve it for linking.
#
# Conan turns `compiler.libcxx=libstdc++` into `-stdlib=libstdc++` and puts it in
# CMAKE_CXX_FLAGS, which CMake passes to BOTH compile and link steps. On a normal Clang
# the compile step consumes it while choosing the C++ stdlib include paths. The Nixpkgs
# 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/"
AND is_linux
AND is_clang
AND CMAKE_CXX_FLAGS MATCHES "stdlib=libstdc"
)
string(
REPLACE "-stdlib=libstdc++"
""
CMAKE_CXX_FLAGS
"${CMAKE_CXX_FLAGS}"
)
string(STRIP "${CMAKE_CXX_FLAGS}" CMAKE_CXX_FLAGS)
add_link_options($<$<LINK_LANGUAGE:CXX>:-stdlib=libstdc++>)
endif()
endif()
# Antithesis instrumentation will only be built and deployed using machines running Linux.

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a ${name} ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit ${name}(std::shared_ptr<SLE const> sle)
explicit ${name}(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -168,7 +168,7 @@ ${field['typeData']['setter_type']} ${field['paramName']}${',' if i < len(requir
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
${name}Builder(std::shared_ptr<SLE const> sle)
${name}Builder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ${tag})
{

View File

@@ -1 +1,8 @@
{% set os = detect_api.detect_os() %}
include(sanitizers)
[conf]
{% if os == "Linux" %}
user.package:libc_version=2.31
tools.info.package_id:confs+=["user.package:libc_version"]
{% endif %}

View File

@@ -50,6 +50,7 @@ words:
- AMMXRP
- amt
- amts
- archs
- asnode
- asynchrony
- attestation
@@ -134,6 +135,7 @@ words:
- iou
- ious
- isrdc
- isystem
- itype
- jemalloc
- jlog

View File

@@ -1,48 +0,0 @@
#!/bin/bash
# Sanity-check that the sanitizer runtimes shipped with g++/clang++ work
# end-to-end against the system loader: compile each example with both
# compilers, run it, and confirm the expected diagnostic is emitted.
set -eo pipefail
cpp_files_dir="${1:?usage: $0 <cpp_files_dir>}"
case "$(uname -m)" in
x86_64) loader=/lib64/ld-linux-x86-64.so.2 ;;
aarch64) loader=/lib/ld-linux-aarch64.so.1 ;;
*)
echo "Unsupported arch: $(uname -m)" >&2
exit 1
;;
esac
declare -A sanitize=(
[asan]="-fsanitize=address"
[tsan]="-fsanitize=thread"
[ubsan]="-fsanitize=undefined"
)
declare -A expect=(
[asan]="heap-use-after-free"
[tsan]="data race"
[ubsan]="signed integer overflow"
)
for compiler in g++ clang++; do
for name in asan tsan ubsan; do
bin="/tmp/${name}-${compiler}"
echo "=== Build ${name} with ${compiler} ==="
"$compiler" -std=c++20 -O1 -g ${sanitize[$name]} \
-Wl,--dynamic-linker=$loader \
"${cpp_files_dir}/${name}.cpp" -o "$bin"
echo "=== Run ${name}-${compiler} ==="
output=$("$bin" 2>&1) || true
echo "$output"
echo "$output" | grep -q "${expect[$name]}" ||
{
echo "expected '${expect[$name]}' from $bin"
exit 1
}
rm -f "$bin"
done
done

35
docker/check-tools.sh Executable file
View File

@@ -0,0 +1,35 @@
#!/bin/bash
# Verify that every tool expected in the Nix CI env is present and runnable.
set -euo pipefail
ccache --version
clang --version
clang++ --version
clang-format --version
cmake --version
conan --version
curl --version
doxygen --version
g++ --version
gcc --version
gcov --version
gcovr --version
git --version
gpg --version
less --version
make --version
mold --version
netstat --version
ninja --version
perl --version
pkg-config --version
pre-commit --version
python3 --version
run-clang-tidy --help
vim --version
# A simple test to verify that git can clone a repository over HTTPS
# (i.e. the CA bundle is wired up). Clone to a temp dir and clean up.
tmp_clone="$(mktemp -d)"
git clone --depth 1 https://github.com/XRPLF/actions.git "${tmp_clone}/actions"
rm -rf "${tmp_clone}"

113
docker/install-sanitizer-libs.sh Executable file
View File

@@ -0,0 +1,113 @@
#!/bin/bash
# Install sanitizer runtime libraries required to run binaries compiled with:
# -fsanitize=address → libasan.so.8
# -fsanitize=thread → libtsan.so.2
# -fsanitize=undefined → libubsan.so.1
#
# The exact SONAMEs required depend on the compiler toolchain used to build the
# test binaries (see nix/ci-env.nix). If the toolchain is bumped and SONAMEs
# change, update the list below (or detect them from the binaries).
#
# Supported base images:
# debian:bookworm
# ubuntu:20.04
# rhel:9
# nixos/nix — tests are skipped; this script is not called
set -euo pipefail
if [ ! -f /etc/os-release ]; then
echo "ERROR: /etc/os-release not found; cannot detect OS" >&2
exit 1
fi
# shellcheck source=/dev/null
. /etc/os-release
echo "Detected OS: ${ID} ${VERSION_ID:-}"
case "${ID}" in
ubuntu | debian | rhel | centos | rocky | almalinux)
echo "Supported OS detected: ${ID}"
;;
*)
echo "ERROR: unsupported OS '${ID}'. Supported: debian, ubuntu, rhel-family" >&2
exit 1
;;
esac
function preinstall() {
case "${ID}" in
ubuntu)
apt-get update -y
apt-get install -y --no-install-recommends \
gnupg \
software-properties-common
add-apt-repository -y ppa:ubuntu-toolchain-r/test
;;
esac
}
function install() {
case "${ID}" in
debian | ubuntu)
apt-get update -y
apt-get install -y --no-install-recommends \
libasan8 \
libtsan2 \
libubsan1
;;
rhel | centos | rocky | almalinux)
dnf install -y \
libasan8 \
libtsan2 \
libubsan
;;
esac
}
function postinstall() {
# Don't clear cache in non-CI environments
if [ -z "${CI:-}" ]; then
echo "Not running in CI environment; skipping cache cleanup"
return
fi
case "${ID}" in
debian | ubuntu)
apt-get clean
rm -rf /var/lib/apt/lists/*
;;
rhel | centos | rocky | almalinux)
dnf clean -y all
rm -rf /var/cache/dnf/*
;;
esac
}
function verify() {
# Verify that every expected library is now resolvable by the dynamic linker.
missing=0
for lib in libasan.so.8 libtsan.so.2 libubsan.so.1; do
if ldconfig -p | grep -q "${lib}"; then
echo "OK: ${lib} found"
else
echo "ERROR: ${lib} not found after installation" >&2
missing=$((missing + 1))
fi
done
if [ "${missing}" -ne 0 ]; then
echo "ERROR: ${missing} library/libraries missing" >&2
exit 1
fi
}
preinstall
install
postinstall
verify
echo "All sanitizer runtime libraries installed successfully."

12
docker/loader-path.sh Executable file
View File

@@ -0,0 +1,12 @@
#!/bin/bash
case "$(uname -m)" in
x86_64) LOADER=/lib64/ld-linux-x86-64.so.2 ;;
aarch64) LOADER=/lib/ld-linux-aarch64.so.1 ;;
*)
echo "Unsupported arch: $(uname -m)" >&2
exit 1
;;
esac
echo "${LOADER}"

View File

@@ -27,10 +27,12 @@ RUN mkdir /tmp/nix-store-closure && \
cp -R $(nix-store -qR result/) /tmp/nix-store-closure
# Final image
FROM ${BASE_IMAGE}
FROM ${BASE_IMAGE} AS final
ARG BASE_IMAGE
# bash is not located at /bin/bash in nixos/nix, so we need to create a symlink to it.
RUN if [ -d /nix ]; then \
RUN if echo "${BASE_IMAGE}" | grep -qiE 'nixos'; then \
ln -s /root/.nix-profile/bin/bash /bin/bash; \
fi
@@ -43,53 +45,70 @@ ENTRYPOINT ["/bin/bash"]
COPY --from=builder /tmp/nix-store-closure /nix/store
COPY --from=builder /tmp/build/result /nix/ci-env
ENV PATH="/nix/ci-env/bin:$PATH"
ENV PATH="/nix/ci-env/bin:${PATH}"
# Point HTTPS clients (git, curl, conan, ...) at the CA bundle shipped in the
# Nix CI environment, so TLS verification works without ca-certificates being
# installed in the system.
ENV SSL_CERT_FILE="/nix/ci-env/etc/ssl/certs/ca-bundle.crt"
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. Copy the
# loader from the Nix store to that path when the base image doesn't already
# provide one (i.e. on nixos/nix).
# (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 docker/loader-path.sh /tmp/loader-path.sh
RUN <<EOF
case "$(uname -m)" in
x86_64) target=/lib64/ld-linux-x86-64.so.2 ;;
aarch64) target=/lib/ld-linux-aarch64.so.1 ;;
*) echo "Unsupported arch: $(uname -m)" >&2; exit 1 ;;
esac
if [ ! -e "$target" ]; then
target="$(/tmp/loader-path.sh)"
if [ ! -e "${target}" ]; then
# Use the loader from the same glibc that gcc links libc against, so
# ld-linux and libc/libpthread share GLIBC_PRIVATE symbols at runtime.
src="$(dirname "$(gcc -print-file-name=libc.so.6)")/$(basename "$target")"
[ -e "$src" ] || { echo "ld-linux not found at $src" >&2; exit 1; }
mkdir -p "$(dirname "$target")"
cp "$src" "$target"
src="$(dirname "$(gcc -print-file-name=libc.so.6)")/$(basename "${target}")"
[ -e "${src}" ] || { echo "ld-linux not found at ${src}" >&2; exit 1; }
mkdir -p "$(dirname "${target}")"
cp "${src}" "${target}"
fi
EOF
COPY docker/check-tools.sh /tmp/check-tools.sh
RUN /tmp/check-tools.sh
# Sanity-check that the g++/clang++ are able to build binaries, including sanitizer-instrumented ones.
COPY docker/test_files/cpp_sources/ /tmp/cpp_sources/
COPY docker/test_files/compile-cpp-sources.sh /tmp/compile-cpp-sources.sh
RUN /tmp/compile-cpp-sources.sh /tmp/cpp_sources /tmp/bins
# Tester: start from a clean BASE_IMAGE, install sanitizer runtime libraries,
# and run the compiled test binaries to verify they execute correctly.
FROM ${BASE_IMAGE} AS tester
ARG BASE_IMAGE
# bash is not located at /bin/bash in nixos/nix, so we need to create a symlink to it.
RUN if echo "${BASE_IMAGE}" | grep -qiE 'nixos'; then \
ln -s /root/.nix-profile/bin/bash /bin/bash; \
fi
SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
# Sanity-check that the built binaries run correctly in the vanilla base image, with the necessary sanitizer runtime libraries installed.
COPY docker/install-sanitizer-libs.sh /tmp/install-sanitizer-libs.sh
COPY docker/test_files/run-test-binaries.sh /tmp/run-test-binaries.sh
COPY --from=final /tmp/bins /tmp/bins
RUN <<EOF
ccache --version
clang --version
clang++ --version
clang-format --version
cmake --version
conan --version
g++ --version
gcc --version
gcovr --version
git --version
make --version
mold --version
ninja --version
perl --version
pkg-config --version
pre-commit --version
python3 --version
run-clang-tidy --help
vim --version
if echo "${BASE_IMAGE}" | grep -qiE 'nixos'; then
echo "Skipping runnning binaries on NixOS."
else
/tmp/install-sanitizer-libs.sh
/tmp/run-test-binaries.sh /tmp/bins
fi
touch /tmp/tests-passed
EOF
# Sanity-check that the sanitizer runtimes shipped with g++/clang++ work
# end-to-end against the system loader.
COPY docker/cpp_files/ /tmp/cpp_files/
COPY docker/check-sanitizers.sh /tmp/check-sanitizers.sh
RUN grep -qi ubuntu /etc/os-release 2>/dev/null && /tmp/check-sanitizers.sh /tmp/cpp_files || true
# Output: the final image, gated on a successful test run in the tester stage.
# Copying the sentinel from tester creates a hard build dependency: if the test
# run above failed, this stage — and the overall build — fails too.
FROM final
COPY --from=tester /tmp/tests-passed /tmp/tests-passed

View File

@@ -0,0 +1,57 @@
#!/bin/bash
# Compile all C++ test binaries during the Docker image build.
# Each binary has the target system's ELF PT_INTERP (dynamic-linker path)
# baked in so it can run on the (potentially minimal) final BASE_IMAGE.
set -eo pipefail
src_dir="${1:?usage: $0 <src_dir> <dst_dir>}"
dst_dir="${2:?usage: $0 <src_dir> <dst_dir>}"
loader="$(/tmp/loader-path.sh)"
mkdir -p "${dst_dir}"
function compile() {
local compiler="${1}"
local name="${2}"
local san_flag="${3:-}"
local src="${src_dir}/${name}.cpp"
local binary="${dst_dir}/${name}-${compiler}"
echo "=== Compiling ${name} with ${compiler} ==="
# Always statically link libstdc++ so the test binary does not depend on
# the host's libstdc++.so.6 version.
local compile_cmd="${compiler} -std=c++23 -O1 -g \
-pthread \
-static-libstdc++ \
${san_flag} \
${src} -o ${binary}"
echo "Compile cmd: ${compile_cmd}"
eval "${compile_cmd}"
echo "=== Patching ${binary} to use ${loader} as PT_INTERP ==="
local patch_cmd="patchelf --set-interpreter ${loader} --remove-rpath ${binary}"
echo "Patch cmd: ${patch_cmd}"
eval "${patch_cmd}"
}
declare -A sanitize=(
[regular]=""
[asan]="-fsanitize=address"
[tsan]="-fsanitize=thread"
[ubsan]="-fsanitize=undefined -fno-sanitize-recover=all"
)
for name in regular asan tsan ubsan; do
san_flag="${sanitize[${name}]}"
for compiler in g++ clang++; do
compile "${compiler}" "${name}" "${san_flag}"
done
done
echo "=== All binaries compiled ==="
ls -la "${dst_dir}"

View File

@@ -2,6 +2,13 @@
#include <cstddef>
#include <iostream>
// Regression test: the compiler-rt sanitizer interface headers must be on the
// include path. A bare on-PATH clang in the Nix CI env doesn't get them
// propagated automatically, so this include would fail to compile with clang++
// if the env isn't wired up correctly. abseil hits the same include during
// sanitizer builds. LeakSanitizer ships with AddressSanitizer.
#include <sanitizer/lsan_interface.h>
#if defined(__clang__) || defined(__GNUC__)
__attribute__((noinline))
#elif defined(_MSC_VER)

View File

@@ -0,0 +1,28 @@
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
static std::mutex gMutex;
void
worker(int id)
{
std::lock_guard<std::mutex> lock(gMutex);
std::cout << "Hello from thread " << id << "\n";
}
int
main()
{
constexpr int kNumThreads = 10;
std::vector<std::thread> threads;
threads.reserve(kNumThreads);
for (int i = 0; i < kNumThreads; ++i)
threads.emplace_back(worker, i);
for (auto& t : threads)
t.join();
std::cout << "Hello from main thread\n";
return 0;
}

View File

@@ -0,0 +1,86 @@
#!/bin/bash
# Run pre-compiled sanitizer binaries and confirm each emits its expected diagnostic.
# Binaries must already exist in <bins_dir> with the layout:
# <name>-g++ and <name>-clang++ for name in {regular,asan,tsan,ubsan}
set -eo pipefail
bins_dir="${1:?usage: $0 <bins_dir>}"
failed_binaries=()
# Run a binary and verify its exit code and output.
# Usage: run <binary> <expected_output> <expected_rc>
function run() {
local binary="${1}"
local expected_output="${2}"
local expected_rc="${3}"
local out_file
out_file="$(mktemp)"
echo "=== Run ${binary} ==="
set +e
"${binary}" >"${out_file}" 2>&1
local rc=$?
set -e
cat "${out_file}"
local failed=0
if [ "${expected_rc}" = "nonzero" ]; then
if [ "${rc}" -eq 0 ]; then
echo "ERROR: expected non-zero exit code from ${binary}, got ${rc}" >&2
failed=1
fi
elif [ "${rc}" -ne "${expected_rc}" ]; then
echo "ERROR: expected exit code ${expected_rc} from ${binary}, got ${rc}" >&2
failed=1
fi
if ! grep -q "${expected_output}" "${out_file}"; then
echo "ERROR: expected '${expected_output}' from ${binary}" >&2
failed=1
fi
if [ "${failed}" -eq 0 ]; then
echo "OK: '${expected_output}' detected"
else
failed_binaries+=("${binary}")
fi
}
declare -A expect=(
[regular]="Hello from main thread"
[asan]="heap-use-after-free"
[tsan]="data race"
[ubsan]="signed integer overflow"
)
for compiler in g++ clang++; do
for name in regular asan tsan ubsan; do
binary="${bins_dir}/${name}-${compiler}"
if [ "${name}" = "tsan" ] && [ "${compiler}" = "g++" ] &&
grep -qi 'debian' /etc/os-release 2>/dev/null &&
[ "$(uname -m)" = "aarch64" ]; then
echo "=== Skipping ${binary} (tsan-g++ unsupported on Debian ARM64) ==="
echo " NOTE: to enable it, add --security-opt seccomp=unconfined to your docker run command"
continue
fi
if [ "${name}" = "regular" ]; then
expected_rc=0
else
expected_rc=nonzero
fi
run "${binary}" "${expect[$name]}" "${expected_rc}"
done
done
if [ "${#failed_binaries[@]}" -gt 0 ]; then
echo "ERROR: the following binaries failed:" >&2
printf ' %s\n' "${failed_binaries[@]}" >&2
exit 1
fi

6
flake.lock generated
View File

@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1777954456,
"narHash": "sha256-hGdgeU2Nk87RAuZyYjyDjFL6LK7dAZN5RE9+hrDTkDU=",
"lastModified": 1780243769,
"narHash": "sha256-x5UQuRsH3MqI0U9afaXSNqzTPSeZlRLvFAav2Ux1pNw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "549bd84d6279f9852cae6225e372cc67fb91a4c1",
"rev": "331800de5053fcebacf6813adb5db9c9dca22a0c",
"type": "github"
},
"original": {

View File

@@ -2,12 +2,14 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <array>
#include <cstdint>
#include <functional>
#include <limits>
#include <optional>
#include <ostream>
#include <set>
#include <stdexcept>
#include <string>
#include <unordered_map>
@@ -40,6 +42,47 @@ isPowerOfTen(T value)
return logTen(value).has_value();
}
namespace detail {
/** Builds a table of the powers of 10
*
* This function is marked consteval, so it can only be run in
* a constexpr context. This assures that it is and can only be run at
* compile time. Doing it at runtime would be pretty wasteful and
* inefficient.
*/
constexpr std::size_t kInt64Digits = 20;
consteval std::array<std::uint64_t, kInt64Digits>
buildPowersOfTen()
{
std::array<std::uint64_t, kInt64Digits> result{};
std::uint64_t power = 1;
std::size_t exponent = 0;
// end the loop early so it doesn't overflow;
for (; exponent < result.size() - 1; ++exponent, power *= 10)
{
result[exponent] = power;
if (power > std::numeric_limits<std::uint64_t>::max() / 10)
throw std::logic_error("Power of 10 table is too big");
}
result[exponent] = power;
if (power < std::numeric_limits<std::uint64_t>::max() / 10)
throw std::logic_error("Power of 10 table is not big enough for the uint64_t type");
return result;
}
} // namespace detail
constexpr std::array<std::uint64_t, detail::kInt64Digits> kPowerOfTen = detail::buildPowersOfTen();
static_assert(kPowerOfTen[0] == 1);
static_assert(kPowerOfTen[1] == 10);
static_assert(kPowerOfTen[10] == 10'000'000'000);
static_assert(
isPowerOfTen(kPowerOfTen.back()) && *logTen(kPowerOfTen.back()) == detail::kInt64Digits - 1);
/** MantissaRange defines a range for the mantissa of a normalized Number.
*
* The mantissa is in the range [min, max], where
@@ -76,6 +119,7 @@ isPowerOfTen(T value)
struct MantissaRange final
{
using rep = std::uint64_t;
enum class MantissaScale {
Small,
// LargeLegacy can be removed when fixCleanup3_2_0 is retired
@@ -89,19 +133,15 @@ struct MantissaRange final
Enabled = true,
};
explicit constexpr MantissaRange(MantissaScale scale)
: min(getMin(scale))
, cuspRoundingFixEnabled(isCuspFixEnabled(scale))
, log(logTen(min).value_or(-1))
, scale(scale)
explicit constexpr MantissaRange(MantissaScale sc) : scale(sc)
{
}
rep min;
rep max{(min * 10) - 1};
CuspRoundingFix cuspRoundingFixEnabled;
int log;
MantissaScale scale;
MantissaScale const scale;
int const log{getExponent(scale)};
rep const min{getMin(scale, log)};
rep const max{(min * 10) - 1};
CuspRoundingFix const cuspRoundingFixEnabled{isCuspFixEnabled(scale)};
static MantissaRange const&
getMantissaRange(MantissaScale scale);
@@ -110,23 +150,35 @@ struct MantissaRange final
getAllScales();
private:
static constexpr rep
getMin(MantissaScale scale)
static constexpr int
getExponent(MantissaScale scale)
{
switch (scale)
{
case MantissaScale::Small:
return 1'000'000'000'000'000ULL;
return 15;
case MantissaScale::LargeLegacy:
case MantissaScale::Large:
return 1'000'000'000'000'000'000ULL;
return 18;
// LCOV_EXCL_START
default:
// If called in a constexpr context, this throw assures that the build fails if an
// invalid scale is used.
throw std::runtime_error("Unknown mantissa scale"); // LCOV_EXCL_LINE
throw std::runtime_error("Unknown mantissa scale");
// LCOV_EXCL_STOP
}
}
// Keep this function for future use with different ways to compute
// the ranges.
static constexpr rep
getMin(MantissaScale scale, int exponent)
{
if (exponent < 0 || exponent >= kPowerOfTen.size())
throw std::runtime_error("Invalid exponent"); // LCOV_EXCL_LINE
return kPowerOfTen[exponent];
}
static constexpr CuspRoundingFix
isCuspFixEnabled(MantissaScale scale)
{
@@ -477,8 +529,7 @@ public:
template <
auto MinMantissa,
auto MaxMantissa,
Integral64 T = std::decay_t<decltype(MinMantissa)>,
Integral64 TMax = std::decay_t<decltype(MaxMantissa)>>
Integral64 T = std::decay_t<decltype(MinMantissa)>>
[[nodiscard]]
std::pair<T, int>
normalizeToRange() const;
@@ -519,7 +570,8 @@ private:
int& exponent,
MantissaRange::rep const& minMantissa,
MantissaRange::rep const& maxMantissa,
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled);
MantissaRange::CuspRoundingFix cuspRoundingFixEnabled,
bool dropped);
[[nodiscard]] bool
isnormal() const noexcept;
@@ -725,16 +777,18 @@ Number::isnormal() const noexcept
kMinExponent <= exponent_ && exponent_ <= kMaxExponent);
}
template <auto MinMantissa, auto MaxMantissa, Integral64 T, Integral64 TMax>
template <auto MinMantissa, auto MaxMantissa, Integral64 T>
std::pair<T, int>
Number::normalizeToRange() const
{
static_assert(std::is_same_v<T, std::uint64_t> || std::is_same_v<T, std::int64_t>);
static_assert(std::is_same_v<T, TMax>);
static_assert(std::is_same_v<T, std::decay_t<decltype(MinMantissa)>>);
static_assert(std::is_same_v<T, std::decay_t<decltype(MaxMantissa)>>);
auto constexpr kMIN = static_cast<T>(MinMantissa);
auto constexpr kMAX = static_cast<T>(MaxMantissa);
static_assert(kMIN > 0);
static_assert(kMIN % 10 == 0);
static_assert(isPowerOfTen(kMIN));
static_assert(kMAX % 10 == 9);
static_assert((kMAX + 1) / 10 == kMIN);

View File

@@ -157,7 +157,7 @@ public:
/** Fetch an item from the cache.
If the digest was not found, Handler
will be called with this signature:
std::shared_ptr<SLE const>(void)
SLE::const_pointer(void)
*/
template <class Handler>
SharedPointerType

View File

@@ -123,7 +123,7 @@ private:
bool preserveOrder,
Keylet const& directory,
uint256 const& key,
std::function<void(std::shared_ptr<SLE> const&)> const& describe);
std::function<void(SLE::ref)> const& describe);
public:
ApplyView() = default;
@@ -153,7 +153,7 @@ public:
@return `nullptr` if the key is not present
*/
virtual std::shared_ptr<SLE>
virtual SLE::pointer
peek(Keylet const& k) = 0;
/** Remove a peeked SLE.
@@ -168,7 +168,7 @@ public:
The key is no longer associated with the SLE.
*/
virtual void
erase(std::shared_ptr<SLE> const& sle) = 0;
erase(SLE::ref sle) = 0;
/** Insert a new state SLE
@@ -189,7 +189,7 @@ public:
@note The key is taken from the SLE
*/
virtual void
insert(std::shared_ptr<SLE> const& sle) = 0;
insert(SLE::ref sle) = 0;
/** Indicate changes to a peeked SLE
@@ -208,7 +208,7 @@ public:
*/
/** @{ */
virtual void
update(std::shared_ptr<SLE> const& sle) = 0;
update(SLE::ref sle) = 0;
//--------------------------------------------------------------------------
@@ -301,7 +301,7 @@ public:
dirAppend(
Keylet const& directory,
Keylet const& key,
std::function<void(std::shared_ptr<SLE> const&)> const& describe)
std::function<void(SLE::ref)> const& describe)
{
if (key.type != ltOFFER)
{
@@ -340,7 +340,7 @@ public:
dirInsert(
Keylet const& directory,
uint256 const& key,
std::function<void(std::shared_ptr<SLE> const&)> const& describe)
std::function<void(SLE::ref)> const& describe)
{
return dirAdd(false, directory, key, describe);
}
@@ -349,7 +349,7 @@ public:
dirInsert(
Keylet const& directory,
Keylet const& key,
std::function<void(std::shared_ptr<SLE> const&)> const& describe)
std::function<void(SLE::ref)> const& describe)
{
return dirAdd(false, directory, key.key, describe);
}
@@ -411,7 +411,7 @@ createRoot(
ApplyView& view,
Keylet const& directory,
uint256 const& key,
std::function<void(std::shared_ptr<SLE> const&)> const& describe);
std::function<void(SLE::ref)> const& describe);
auto
findPreviousPage(ApplyView& view, Keylet const& directory, SLE::ref start);
@@ -434,7 +434,7 @@ insertPage(
SLE::ref next,
uint256 const& key,
Keylet const& directory,
std::function<void(std::shared_ptr<SLE> const&)> const& describe);
std::function<void(SLE::ref)> const& describe);
} // namespace directory
} // namespace xrpl

View File

@@ -67,8 +67,8 @@ public:
std::function<void(
uint256 const& key,
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after)> const& func);
SLE::const_ref before,
SLE::const_ref after)> const& func);
private:
std::optional<STAmount> deliver_;

View File

@@ -11,13 +11,13 @@ private:
uint256 const root_;
uint256 const nextQuality_;
uint256 const key_;
std::shared_ptr<SLE const> sle_ = nullptr;
SLE::const_pointer sle_ = nullptr;
unsigned int entry_ = 0;
uint256 index_;
public:
class const_iterator; // NOLINT(readability-identifier-naming)
using value_type = std::shared_ptr<SLE const>;
using value_type = SLE::const_pointer;
BookDirs(ReadView const&, Book const&);
@@ -76,7 +76,7 @@ private:
uint256 nextQuality_;
uint256 key_;
uint256 curKey_;
std::shared_ptr<SLE const> sle_;
SLE::const_pointer sle_;
unsigned int entry_ = 0;
uint256 index_;
std::optional<value_type> mutable cache_;

View File

@@ -36,7 +36,7 @@ public:
bool
exists(Keylet const& k) const override;
std::shared_ptr<SLE const>
SLE::const_pointer
read(Keylet const& k) const override;
bool

View File

@@ -1,229 +0,0 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/hardened_hash.h>
#include <array>
#include <cstdint>
#include <functional>
#include <unordered_map>
#include <vector>
namespace xrpl {
/** An inner-node position in a SHAMap that needs hash recomputation.
Plan 7's deferred-rebuild algorithm walks bottom-up, recomputing
each affected inner node's hash from its children. `AffectedNode`
identifies one such node by:
* depth: 0 = root, 1..63 = inner nodes, 64 = leaf (not included
in the plan output — only inner nodes are rebuilt)
* prefix: the leaf-key's first `depth*4` bits, with the remainder
zeroed. Two nodes at the same depth with the same
prefix are the same node.
*/
struct AffectedNode
{
int depth;
uint256 prefix;
[[nodiscard]] bool
operator==(AffectedNode const& other) const noexcept
{
return depth == other.depth && prefix == other.prefix;
}
};
/** Plan which inner nodes need rebuild for a given set of leaf changes.
Returns the union of ancestor paths of all `modifiedKeys`, sorted
by depth descending so a bottom-up rebuild can iterate the result
and find each level's nodes before the level above.
The returned plan contains only INNER nodes (depths 0..63). The
leaves themselves are at depth 64 and are not in the plan — they
are the modifications, not nodes to be rebuilt.
Complexity: O(K * 64) where K is `modifiedKeys.size()`. For real
workloads (~thousands of modifications), this is microseconds.
*/
[[nodiscard]] std::vector<AffectedNode>
planDeferredRebuild(std::vector<uint256> const& modifiedKeys);
/** Compute the hash of a SHAMap inner node from its 16 children.
Byte-identical to `SHAMapInnerNode::updateHash()`:
sha512_half(
HashPrefix::InnerNode (4 bytes, big-endian) ||
child[0] (32 bytes) ||
child[1] (32 bytes) ||
...
child[15] (32 bytes))
This is the elementary operation of plan-7's bottom-up rebuild:
given the (already-computed) 16 child hashes of an inner node,
produce that node's hash. Empty branches are passed as zero
uint256 — the same convention SHAMap uses.
Pure function; no SHAMap state, no allocation beyond a stack buffer.
*/
[[nodiscard]] uint256
computeInnerNodeHash(std::array<uint256, 16> const& childHashes);
/** Hash combiner for AffectedNode keys in unordered_map. */
struct AffectedNodeHash
{
[[nodiscard]] std::size_t
operator()(AffectedNode const& n) const noexcept
{
// Mix depth into the high bits of a hash of prefix.
std::size_t h = 0;
for (auto b : n.prefix)
h = h * 31 + b;
return h ^ (static_cast<std::size_t>(n.depth) << 56);
}
};
/** Map of recomputed inner-node hashes keyed by (depth, prefix). */
using RebuildResult =
std::unordered_map<AffectedNode, uint256, AffectedNodeHash>;
/** Walk a depth-descending plan and compute each affected node's new hash.
For each AffectedNode in the plan (deepest first), collect its 16
child hashes:
* If a child position is itself in the plan (and already
computed, since we walk deepest-first), use the computed hash.
* Otherwise, fall back to `getOriginalChildHash` — the callback
is expected to walk the parent SHAMap to find the hash at that
position.
Then `computeInnerNodeHash` over the 16 children yields the
affected node's new hash. The result map contains every entry in
the plan keyed by its (depth, prefix).
@param plan
Depth-descending plan from `planDeferredRebuild`.
@param getOriginalChildHash
Callable `uint256(int depth, uint256 const& prefix)` returning
the hash at the given position in the parent SHAMap. May
return uint256{} for absent positions.
@note Pure function; safe to call concurrently with disjoint plans.
*/
template <typename GetChildHashFn>
[[nodiscard]] RebuildResult
executeRebuildPlan(
std::vector<AffectedNode> const& plan,
GetChildHashFn getOriginalChildHash);
namespace detail {
// Forwarder for template instantiation; declared here, defined in .cpp.
[[nodiscard]] RebuildResult
executeRebuildPlanImpl(
std::vector<AffectedNode> const& plan,
std::function<uint256(int, uint256 const&)> getOriginalChildHash);
} // namespace detail
template <typename GetChildHashFn>
[[nodiscard]] RebuildResult
executeRebuildPlan(
std::vector<AffectedNode> const& plan,
GetChildHashFn getOriginalChildHash)
{
return detail::executeRebuildPlanImpl(
plan,
std::function<uint256(int, uint256 const&)>(
std::move(getOriginalChildHash)));
}
/** End-to-end deferred rebuild: produce the new SHAMap root hash.
Combines `planDeferredRebuild` + `executeRebuildPlan` into one call.
This is the consumer-facing API — the integration site only needs
to supply the set of modified keys and a callback that reads the
parent SHAMap. No AffectedNode plumbing is exposed.
@param modifiedKeys
Keys whose leaves have been added/replaced/deleted in this
ledger close. Empty → no rebuild; returns the existing root
via the callback at (depth=0, prefix=zero).
@param getOriginalChildHash
Callable `uint256(int depth, uint256 const& prefix)` returning
the hash at the given position in the parent SHAMap. May return
uint256{} for absent positions.
@return The new SHAMap root hash.
*/
template <typename GetChildHashFn>
[[nodiscard]] uint256
deferredRebuildRoot(
std::vector<uint256> const& modifiedKeys,
GetChildHashFn getOriginalChildHash)
{
if (modifiedKeys.empty())
return getOriginalChildHash(0, uint256{});
auto const plan = planDeferredRebuild(modifiedKeys);
auto const result = executeRebuildPlan(plan, std::move(getOriginalChildHash));
AffectedNode const rootKey{0, uint256{}};
auto const it = result.find(rootKey);
if (it == result.end())
return uint256{};
return it->second;
}
/** Partition modified keys by their first nibble (0..15).
At depth 1 the root has 16 child subtrees, one per first-nibble
value. Keys in different subtrees rebuild independently, so this
partition is the basis for plan-7 P7.3's parallel-by-subtree
rebuild.
Returns 16 buckets, one per first-nibble value, each containing
only the keys whose first nibble matches the bucket index.
*/
[[nodiscard]] std::array<std::vector<uint256>, 16>
partitionByFirstNibble(std::vector<uint256> const& modifiedKeys);
namespace detail {
[[nodiscard]] uint256
deferredRebuildRootParallelImpl(
std::vector<uint256> const& modifiedKeys,
std::function<uint256(int, uint256 const&)> getOriginalChildHash);
} // namespace detail
/** Parallel deferred rebuild: partition by first nibble, rebuild each
of the 16 subtrees in parallel, combine into the root.
Equivalent to `deferredRebuildRoot` in output; differs only in
execution strategy. Useful when the parent SHAMap is large enough
that the rebuild cost matters per-close. For small workloads
(handful of modifications), the serial path is faster — threading
overhead exceeds the work saved.
@param modifiedKeys
Keys whose leaves have been added/replaced/deleted.
@param getOriginalChildHash
Thread-safe callable; will be invoked concurrently from
multiple subtree workers.
@return The new SHAMap root hash, byte-identical to
`deferredRebuildRoot` over the same inputs.
*/
template <typename GetChildHashFn>
[[nodiscard]] uint256
deferredRebuildRootParallel(
std::vector<uint256> const& modifiedKeys,
GetChildHashFn getOriginalChildHash)
{
return detail::deferredRebuildRootParallelImpl(
modifiedKeys,
std::function<uint256(int, uint256 const&)>(
std::move(getOriginalChildHash)));
}
} // namespace xrpl

View File

@@ -22,12 +22,12 @@ class Dir
private:
ReadView const* view_ = nullptr;
Keylet root_;
std::shared_ptr<SLE const> sle_;
SLE::const_pointer sle_;
STVector256 const* indexes_ = nullptr;
public:
class ConstIterator;
using value_type = std::shared_ptr<SLE const>;
using value_type = SLE::const_pointer;
Dir(ReadView const&, Keylet const&);
@@ -102,7 +102,7 @@ private:
Keylet page_;
uint256 index_;
std::optional<value_type> mutable cache_;
std::shared_ptr<SLE const> sle_;
SLE::const_pointer sle_;
STVector256 const* indexes_ = nullptr;
std::vector<uint256>::const_iterator it_;
};

View File

@@ -1,347 +0,0 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/hardened_hash.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <cstddef>
#include <iterator>
#include <memory>
#include <shared_mutex>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace xrpl {
/** Flat keylet-indexed materialization of XRPL state.
The SHAMap is XRPL's authoritative state structure — it produces
the state root that consensus agrees on. But on its own, it forces
every state read to walk the trie: ~610 spinlocked inner-node
fetches per `read(Keylet)`.
`FlatStateMap` materializes the same keylet → SLE mapping into a
flat hash table. Once populated, lookups are a single
`unordered_map::find()` — nanoseconds, not microseconds.
This is the 2-writes-for-1-read pattern (Plan 6):
* On apply, the apply path dual-writes to SHAMap and FlatStateMap.
* On read, only FlatStateMap is consulted.
The flat map is purely auxiliary. The SHAMap remains authoritative,
can always be rebuilt from the underlying NodeStore, and is what the
network's state-root commitment is computed from. If FlatStateMap is
wrong, the differential invariant check at ledger close (Plan 6 P6.5)
catches it; there is **no runtime fallback to SHAMap descent on
miss** — a miss is a bug.
Thread-safe via a shared_mutex. Concurrent reads do not block each
other; writes are exclusive. Future phases may replace this with
a lock-free atomic-pointer-swap design.
*/
class FlatStateMap
{
public:
using key_type = uint256;
using value_type = std::shared_ptr<STLedgerEntry const>;
FlatStateMap() = default;
~FlatStateMap() = default;
// Non-copyable, non-movable. The map owns a shared_mutex (not movable)
// and a potentially large hash table; callers that need ownership
// transfer should wrap in std::unique_ptr<FlatStateMap>.
FlatStateMap(FlatStateMap const&) = delete;
FlatStateMap&
operator=(FlatStateMap const&) = delete;
FlatStateMap(FlatStateMap&&) = delete;
FlatStateMap&
operator=(FlatStateMap&&) = delete;
/** Look up an SLE by its SHAMap key.
Returns nullptr if the key is not in the map. In Plan 6's pure
2w/1r model, callers reading state that *should* exist treat
nullptr as a precondition violation — there is no fallback path
that would recover from a missed entry.
*/
[[nodiscard]] value_type
read(key_type const& key) const;
/** Test whether an SLE is present. O(1). */
[[nodiscard]] bool
exists(key_type const& key) const;
/** Insert a new SLE. Replaces any prior entry under the same key.
Used by both:
* the apply path's `view.insert()` (new ledger object), and
* the apply path's `view.update()` (mutating an existing SLE
produces a new shared_ptr value).
*/
void
insert(key_type const& key, value_type sle);
/** Remove an SLE. No-op if the key is absent. */
void
erase(key_type const& key);
/** Number of SLEs currently materialized. */
[[nodiscard]] std::size_t
size() const;
/** Test whether the map is empty. O(1). */
[[nodiscard]] bool
empty() const;
/** Remove every entry. Used by tests and by snapshot reset. */
void
clear();
/** Deep-copy snapshot of the current state.
The snapshot is a frozen FlatStateMap (returned by value-like
unique_ptr) that shares the underlying SLE objects via
shared_ptr but has its own hash table. Subsequent writes to the
source FlatStateMap do not affect the snapshot.
Snapshot cost is O(N) in entry count — ~one pointer copy per
entry plus bucket allocation. For current mainnet (~10M SLEs)
this is ~100200 ms; expensive enough that snapshots should be
per-ledger-close, not per-transaction.
A future phase will replace this with a persistent / HAMT
structure that gives O(log N) snapshot and structural sharing
across versions.
*/
[[nodiscard]] std::unique_ptr<FlatStateMap>
snapshot() const;
/** Visit every (key, SLE) pair under a single shared lock.
@param visitor Called as `void(key_type const&, value_type const&)`.
The lock is held for the duration of the iteration; visitors
must not call back into the same FlatStateMap (deadlock /
recursive shared_lock UB). Visitors that want to mutate state
should collect keys first and apply mutations after iteration
returns.
*/
template <typename F>
void
forEach(F&& visitor) const
{
std::shared_lock<std::shared_mutex> lock(mutex_);
for (auto const& [key, sle] : map_)
visitor(key, sle);
}
private:
using HashFn = HardenedHash<>;
using MapType = std::unordered_map<key_type, value_type, HashFn>;
mutable std::shared_mutex mutex_;
MapType map_;
};
// Forward declarations to avoid pulling heavy headers into this file.
class ReadView;
class Ledger;
/** Populate a FlatStateMap from every SLE in a ReadView.
Used at node startup to build the flat materialization from a
SHAMap-backed authoritative ledger. Cost is O(N) iterations of
`view.sles`, each of which descends the SHAMap, so this is
expected to take seconds-to-minutes for a current mainnet-sized
ledger (~10M SLEs). Run once on startup; subsequent ledgers are
maintained incrementally via dual-write at apply time (P6.3).
@pre `target` is empty. (Not enforced — replacing existing entries
is well-defined, but mixing populated state with an externally-
provided ReadView is a bug-shaped pattern; assert in DEBUG.)
*/
void
populateFromReadView(FlatStateMap& target, ReadView const& source);
/** Populate a FlatStateMap from any forward range of `shared_ptr<SLE const>`.
Lower-level building block underlying `populateFromReadView`. Useful
in tests (the range can be a `std::vector<shared_ptr<SLE const>>`)
and in non-ReadView contexts (e.g., reloading a persisted flat-map
sidecar at startup).
The range element type must be `shared_ptr<SLE const>` (or
implicitly convertible). Each element's `.key()` becomes the
FlatStateMap key.
*/
template <typename Range>
void
populateFromRange(FlatStateMap& target, Range const& sles)
{
for (auto const& sle : sles)
target.insert(sle->key(), sle);
}
// ---------------------------------------------------------------------------
// Mirror helpers (P6.3).
//
// The xrpld `RawView` interface defines three pure-virtual methods that
// every state mutation flows through: `rawInsert`, `rawReplace`, and
// `rawErase`. In plan-6's 2-writes-for-1-read pattern, every such call
// must also update the flat map. These helpers perform the flat-map side
// of that dual-write — they exist as standalone functions (rather than
// methods on FlatStateMap) so the Ledger integration is a one-line
// addition at each `raw*` override site, with no FlatStateMap class
// surface added for purely Ledger-specific semantics.
//
// Permissive semantics: `mirrorRawReplace` on an absent key inserts;
// `mirrorRawErase` on an absent key is a no-op. The SHAMap side enforces
// the precondition (replace requires existence); the flat mirror ensures
// the post-state matches whatever the SHAMap committed. If the caller
// gets it wrong, the differential invariant check at close (P6.5) is the
// stop-the-line gate.
// ---------------------------------------------------------------------------
void
mirrorRawInsert(FlatStateMap& map, std::shared_ptr<STLedgerEntry const> sle);
void
mirrorRawReplace(FlatStateMap& map, std::shared_ptr<STLedgerEntry const> sle);
void
mirrorRawErase(FlatStateMap& map, std::shared_ptr<STLedgerEntry const> const& sle);
void
mirrorRawErase(FlatStateMap& map, uint256 const& key);
// ---------------------------------------------------------------------------
// Keylet-aware read (P6.4).
//
// This is the read-side counterpart to `mirrorRaw*` — the testable unit
// underlying `Ledger::read(Keylet)`'s flat-map path. It looks up the
// SLE by `k.key` and verifies the SLE matches the keylet's expected
// type via `Keylet::check`. On either a miss or a type mismatch, it
// returns nullptr — matching the contract of `Ledger::read`.
//
// Plan 6 v2 semantics: this function does not consult a SHAMap or any
// other source on miss. When a FlatStateMap is the read source of
// truth, a miss IS the answer. The differential invariant check at
// close (P6.5) is what makes that safe.
// ---------------------------------------------------------------------------
std::shared_ptr<STLedgerEntry const>
readFromFlatStateMap(FlatStateMap const& map, Keylet const& k);
// ---------------------------------------------------------------------------
// Differential invariant (P6.5).
//
// At every ledger close, the flat map's key-set must match the
// SHAMap's key-set. `diffFlatStateKeys` produces both sides of the
// disagreement; `flatStateMapMatches` is the boolean predicate the
// hot-path integration calls (and fails the close if it returns false).
//
// Content drift (right keys, wrong SLE bodies) is a separate, stronger
// invariant. It's prevented by construction: the mirror helpers write
// exactly the SLE the caller passed to raw*. If mirror helpers and
// wiring are both correct, the membership check above is sufficient.
// ---------------------------------------------------------------------------
struct FlatStateKeyDiff
{
std::vector<uint256> missingFromFlat;
std::vector<uint256> extraInFlat;
};
template <typename SourceRange>
[[nodiscard]] FlatStateKeyDiff
diffFlatStateKeys(FlatStateMap const& flat, SourceRange const& sourceKeys)
{
FlatStateKeyDiff diff;
// Pass 1: walk source keys; collect any absent from flat. Record
// which keys we've seen so pass 2 can spot phantoms.
std::unordered_set<uint256, HardenedHash<>> seen;
seen.reserve(static_cast<std::size_t>(std::distance(
std::begin(sourceKeys), std::end(sourceKeys))));
for (auto const& key : sourceKeys)
{
seen.insert(key);
if (!flat.exists(key))
diff.missingFromFlat.push_back(key);
}
// Pass 2: walk flat; anything not in `seen` is a phantom.
flat.forEach(
[&seen, &diff](uint256 const& key, auto const& /*sle*/) {
if (!seen.contains(key))
diff.extraInFlat.push_back(key);
});
return diff;
}
template <typename SourceRange>
[[nodiscard]] bool
flatStateMapMatches(FlatStateMap const& flat, SourceRange const& sourceKeys)
{
auto const diff = diffFlatStateKeys(flat, sourceKeys);
return diff.missingFromFlat.empty() && diff.extraInFlat.empty();
}
/** Compare a FlatStateMap against a SHAMap-like source.
`ShaMapLike` is any range whose elements expose a `key()` accessor
returning a `uint256`. The real `SHAMap` satisfies this contract
(its iterators yield `SHAMapItem`s with `.key()`), as does the
`MockShaMapItem` used in tests.
This is the helper the Ledger integration calls to run the P6.5
differential invariant. It extracts keys into a transient buffer
(O(N) allocation, ~N pointers' worth of memory) and forwards to
`flatStateMapMatches`. The transient buffer is acceptable at close
cadence; the integration can later optimize by walking the SHAMap
in-place once profiling shows the allocation matters.
*/
template <typename ShaMapLike>
[[nodiscard]] bool
flatStateMapMatchesShaMap(FlatStateMap const& flat, ShaMapLike const& shaMap)
{
std::vector<uint256> keys;
for (auto const& item : shaMap)
keys.push_back(item.key());
return flatStateMapMatches(flat, keys);
}
// ---------------------------------------------------------------------------
// A-phase integration: attach a populated FlatStateMap to a Ledger.
//
// This is the public entry point a node or test uses to "turn on" the
// flat-map read path for a Ledger. The function:
// 1. Allocates a new FlatStateMap.
// 2. Eagerly populates it by walking every SLE in the Ledger.
// 3. Attaches it via `Ledger::setFlatStateMap`.
//
// After this call:
// * `Ledger::flatStateMap()` returns the populated map
// * `Ledger::read(keylet)` routes through the flat map (no SHAMap
// descent on the hot path; see P6.4 wiring)
// * `Ledger::raw{Insert,Replace,Erase}` mirror writes to the flat
// map alongside the SHAMap (see P6.3 wiring)
// * `Ledger::validateFlatStateMapMatchesShaMap()` returns true
//
// Repeat calls discard the prior map and produce a fresh one.
//
// Cost: O(N) walk over the Ledger's state SHAMap to populate the flat
// map. For mainnet-scale state, this is bounded by SHAMap traversal
// speed — typically minutes once. Run at node startup or whenever a
// Ledger first becomes "live" (the one apply writes to).
// ---------------------------------------------------------------------------
void
attachFlatStateMapTo(Ledger& ledger);
} // namespace xrpl

View File

@@ -14,7 +14,6 @@
namespace xrpl {
class FlatStateMap;
class ServiceRegistry;
class Job;
class TransactionMaster;
@@ -167,7 +166,7 @@ public:
std::optional<uint256>
succ(uint256 const& key, std::optional<uint256> const& last = std::nullopt) const override;
std::shared_ptr<SLE const>
SLE::const_pointer
read(Keylet const& k) const override;
std::unique_ptr<SlesType::iter_base>
@@ -203,16 +202,16 @@ public:
//
void
rawErase(std::shared_ptr<SLE> const& sle) override;
rawErase(SLE::ref sle) override;
void
rawInsert(std::shared_ptr<SLE> const& sle) override;
rawInsert(SLE::ref sle) override;
void
rawErase(uint256 const& key);
void
rawReplace(std::shared_ptr<SLE> const& sle) override;
rawReplace(SLE::ref sle) override;
void
rawDestroyXRP(XRPAmount const& fee) override
@@ -362,41 +361,9 @@ public:
bool
isVotingLedger() const;
std::shared_ptr<SLE>
SLE::pointer
peek(Keylet const& k) const;
//
// Flat-state mirror (Plan 6 P6.3).
//
// When a FlatStateMap is attached, every successful `raw*` call below
// mirrors the operation into the map via the matching `mirrorRaw*`
// helper. With no map attached (the default), the ledger behaves
// exactly as before — no allocation, no mutex, no observable change.
// The map's lifetime is managed by the caller (typically the
// Application owns the live ledger's map).
//
void
setFlatStateMap(std::shared_ptr<FlatStateMap> map);
[[nodiscard]] std::shared_ptr<FlatStateMap>
flatStateMap() const;
/** Run the Plan 6 P6.5 differential invariant.
Returns true iff (a) no FlatStateMap is attached (vacuously
true — there is no second source of truth to disagree), or
(b) the attached FlatStateMap's key-set matches the SHAMap's
key-set exactly.
Intended to be called at ledger close, before the new state
root is published. A false return is a stop-the-line bug —
the integrator should crash rather than publish a state root
that disagrees with the read path.
*/
[[nodiscard]] bool
validateFlatStateMapMatchesShaMap() const;
private:
class SlesIterImpl;
class TxsIterImpl;
@@ -433,11 +400,6 @@ private:
// A SHAMap containing the state objects for this ledger.
SHAMap mutable stateMap_;
// Optional flat keylet→SLE mirror. When non-null, every successful
// raw* state mutation is mirrored into this map. See FlatStateMap.h
// and the Plan 6 docs in tasks/.
std::shared_ptr<FlatStateMap> mutable flatStateMap_;
// Protects fee variables
std::mutex mutable mutex_;

View File

@@ -197,7 +197,7 @@ public:
std::optional<key_type>
succ(key_type const& key, std::optional<key_type> const& last = std::nullopt) const override;
std::shared_ptr<SLE const>
SLE::const_pointer
read(Keylet const& k) const override;
std::unique_ptr<SlesType::iter_base>
@@ -224,13 +224,13 @@ public:
// RawView
void
rawErase(std::shared_ptr<SLE> const& sle) override;
rawErase(SLE::ref sle) override;
void
rawInsert(std::shared_ptr<SLE> const& sle) override;
rawInsert(SLE::ref sle) override;
void
rawReplace(std::shared_ptr<SLE> const& sle) override;
rawReplace(SLE::ref sle) override;
void
rawDestroyXRP(XRPAmount const& fee) override;

View File

@@ -25,7 +25,7 @@ public:
can calculate metadata.
*/
virtual void
rawErase(std::shared_ptr<SLE> const& sle) = 0;
rawErase(SLE::ref sle) = 0;
/** Unconditionally insert a state item.
@@ -39,7 +39,7 @@ public:
@note The key is taken from the SLE
*/
virtual void
rawInsert(std::shared_ptr<SLE> const& sle) = 0;
rawInsert(SLE::ref sle) = 0;
/** Unconditionally replace a state item.
@@ -54,7 +54,7 @@ public:
@note The key is taken from the SLE
*/
virtual void
rawReplace(std::shared_ptr<SLE> const& sle) = 0;
rawReplace(SLE::ref sle) = 0;
/** Destroy XRP.

View File

@@ -34,9 +34,9 @@ public:
using key_type = uint256;
using mapped_type = std::shared_ptr<SLE const>;
using mapped_type = SLE::const_pointer;
struct SlesType : detail::ReadViewFwdRange<std::shared_ptr<SLE const>>
struct SlesType : detail::ReadViewFwdRange<SLE::const_pointer>
{
explicit SlesType(ReadView const& view);
[[nodiscard]] Iterator
@@ -143,7 +143,7 @@ public:
@return `nullptr` if the key is not present or
if the type does not match.
*/
[[nodiscard]] virtual std::shared_ptr<SLE const>
[[nodiscard]] virtual SLE::const_pointer
read(Keylet const& k) const = 0;
// Accounts in a payment are not allowed to use assets acquired during that

View File

@@ -12,7 +12,6 @@
#include <cstdint>
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <utility>
@@ -135,7 +134,7 @@ areCompatible(
dirLink(
ApplyView& view,
AccountID const& owner,
std::shared_ptr<SLE>& object,
SLE::pointer& object,
SF_UINT64 const& node = sfOwnerNode);
/** Checks that can withdraw funds from an object to itself or a destination.
@@ -215,8 +214,8 @@ doWithdraw(
* (if should not be skipped) and if the entry should be skipped. The status
* is always tesSUCCESS if the entry should be skipped.
*/
using EntryDeleter = std::function<
std::pair<TER, SkipEntry>(LedgerEntryType, uint256 const&, std::shared_ptr<SLE>&)>;
using EntryDeleter =
std::function<std::pair<TER, SkipEntry>(LedgerEntryType, uint256 const&, SLE::pointer&)>;
/** Cleanup owner directory entries on account delete.
* Used for a regular and AMM accounts deletion. The caller
* has to provide the deleter function, which handles details of

View File

@@ -8,8 +8,6 @@
#include <xrpl/protocol/TxMeta.h>
#include <xrpl/protocol/XRPAmount.h>
#include <memory>
namespace xrpl::detail {
// Helper class that buffers modifications
@@ -26,7 +24,7 @@ private:
Modify,
};
using items_t = std::map<key_type, std::pair<Action, std::shared_ptr<SLE>>>;
using items_t = std::map<key_type, std::pair<Action, SLE::pointer>>;
items_t items_;
XRPAmount dropsDestroyed_{0};
@@ -60,10 +58,10 @@ public:
[[nodiscard]] std::optional<key_type>
succ(ReadView const& base, key_type const& key, std::optional<key_type> const& last) const;
[[nodiscard]] std::shared_ptr<SLE const>
[[nodiscard]] SLE::const_pointer
read(ReadView const& base, Keylet const& k) const;
std::shared_ptr<SLE>
SLE::pointer
peek(ReadView const& base, Keylet const& k);
[[nodiscard]] std::size_t
@@ -75,23 +73,23 @@ public:
std::function<void(
uint256 const& key,
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after)> const& func) const;
SLE::const_ref before,
SLE::const_ref after)> const& func) const;
void
erase(ReadView const& base, std::shared_ptr<SLE> const& sle);
erase(ReadView const& base, SLE::ref sle);
void
rawErase(ReadView const& base, std::shared_ptr<SLE> const& sle);
rawErase(ReadView const& base, SLE::ref sle);
void
insert(ReadView const& base, std::shared_ptr<SLE> const& sle);
insert(ReadView const& base, SLE::ref sle);
void
update(ReadView const& base, std::shared_ptr<SLE> const& sle);
update(ReadView const& base, SLE::ref sle);
void
replace(ReadView const& base, std::shared_ptr<SLE> const& sle);
replace(ReadView const& base, SLE::ref sle);
void
destroyXRP(XRPAmount const& fee);
@@ -104,12 +102,12 @@ public:
}
private:
using Mods = hash_map<key_type, std::shared_ptr<SLE>>;
using Mods = hash_map<key_type, SLE::pointer>;
static void
threadItem(TxMeta& meta, std::shared_ptr<SLE> const& to);
threadItem(TxMeta& meta, SLE::ref to);
std::shared_ptr<SLE>
SLE::pointer
getForMod(ReadView const& base, key_type const& key, Mods& mods, beast::Journal j);
void
@@ -119,7 +117,7 @@ private:
threadOwners(
ReadView const& base,
TxMeta& meta,
std::shared_ptr<SLE const> const& sle,
SLE::const_ref sle,
Mods& mods,
beast::Journal j);
};

View File

@@ -40,7 +40,7 @@ public:
[[nodiscard]] std::optional<key_type>
succ(key_type const& key, std::optional<key_type> const& last = std::nullopt) const override;
[[nodiscard]] std::shared_ptr<SLE const>
[[nodiscard]] SLE::const_pointer
read(Keylet const& k) const override;
[[nodiscard]] std::unique_ptr<SlesType::iter_base>
@@ -69,28 +69,28 @@ public:
[[nodiscard]] ApplyFlags
flags() const override;
std::shared_ptr<SLE>
SLE::pointer
peek(Keylet const& k) override;
void
erase(std::shared_ptr<SLE> const& sle) override;
erase(SLE::ref sle) override;
void
insert(std::shared_ptr<SLE> const& sle) override;
insert(SLE::ref sle) override;
void
update(std::shared_ptr<SLE> const& sle) override;
update(SLE::ref sle) override;
// RawView
void
rawErase(std::shared_ptr<SLE> const& sle) override;
rawErase(SLE::ref sle) override;
void
rawInsert(std::shared_ptr<SLE> const& sle) override;
rawInsert(SLE::ref sle) override;
void
rawReplace(std::shared_ptr<SLE> const& sle) override;
rawReplace(SLE::ref sle) override;
void
rawDestroyXRP(XRPAmount const& feeDrops) override;

View File

@@ -49,15 +49,15 @@ public:
succ(ReadView const& base, key_type const& key, std::optional<key_type> const& last) const;
void
erase(std::shared_ptr<SLE> const& sle);
erase(SLE::ref sle);
void
insert(std::shared_ptr<SLE> const& sle);
insert(SLE::ref sle);
void
replace(std::shared_ptr<SLE> const& sle);
replace(SLE::ref sle);
[[nodiscard]] std::shared_ptr<SLE const>
[[nodiscard]] SLE::const_pointer
read(ReadView const& base, Keylet const& k) const;
void
@@ -84,10 +84,10 @@ private:
struct SleAction
{
Action action;
std::shared_ptr<SLE> sle;
SLE::pointer sle;
// Constructor needed for emplacement in std::map
SleAction(Action action, std::shared_ptr<SLE> const& sle) : action(action), sle(sle)
SleAction(Action action, SLE::pointer sle) : action(action), sle(std::move(sle))
{
}
};

View File

@@ -792,7 +792,7 @@ deleteAMMAccount(Sandbox& view, Asset const& asset, Asset const& asset2, beast::
void
initializeFeeAuctionVote(
ApplyView& view,
std::shared_ptr<SLE>& ammSle,
SLE::pointer& ammSle,
AccountID const& account,
Asset const& lptAsset,
std::uint16_t tfee);
@@ -812,7 +812,7 @@ Expected<bool, TER>
verifyAndAdjustLPTokenBalance(
Sandbox& sb,
STAmount const& lpTokens,
std::shared_ptr<SLE>& ammSle,
SLE::pointer& ammSle,
AccountID const& account);
} // namespace xrpl

View File

@@ -9,7 +9,6 @@
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <memory>
#include <set>
#include <vector>
@@ -36,11 +35,7 @@ xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj,
/** Adjust the owner count up or down. */
void
adjustOwnerCount(
ApplyView& view,
std::shared_ptr<SLE> const& sle,
std::int32_t amount,
beast::Journal j);
adjustOwnerCount(ApplyView& view, SLE::ref sle, std::int32_t amount, beast::Journal j);
/** Returns IOU issuer transfer fee as Rate. Rate specifies
* the fee as fractions of 1 billion. For example, 1% transfer rate
@@ -76,9 +71,7 @@ getPseudoAccountFields();
- null pointer
*/
[[nodiscard]] bool
isPseudoAccount(
std::shared_ptr<SLE const> sleAcct,
std::set<SField const*> const& pseudoFieldFilter = {});
isPseudoAccount(SLE::const_pointer sleAcct, std::set<SField const*> const& pseudoFieldFilter = {});
/** Convenience overload that reads the account from the view. */
[[nodiscard]] inline bool
@@ -98,7 +91,7 @@ isPseudoAccount(
* before using a field. The amendment check is **not** performed in
* createPseudoAccount.
*/
[[nodiscard]] Expected<std::shared_ptr<SLE>, TER>
[[nodiscard]] Expected<SLE::pointer, TER>
createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey, SField const& ownerField);
/** Checks the destination and tag.

View File

@@ -23,7 +23,7 @@ checkExpired(SLE const& sleCredential, NetClock::time_point const& closed);
// Actually remove a credentials object from the ledger
[[nodiscard]] TER
deleteSLE(ApplyView& view, std::shared_ptr<SLE> const& sleCredential, beast::Journal j);
deleteSLE(ApplyView& view, SLE::ref sleCredential, beast::Journal j);
// Amendment and parameters checks for sfCredentialIDs field
NotTEC
@@ -70,7 +70,7 @@ verifyDepositPreauth(
ApplyView& view,
AccountID const& src,
AccountID const& dst,
std::shared_ptr<SLE const> const& sleDst,
SLE::const_ref sleDst,
beast::Journal j);
} // namespace xrpl

View File

@@ -15,7 +15,7 @@ namespace xrpl {
* if not.
*/
NotTEC
checkTxPermission(std::shared_ptr<SLE const> const& delegate, STTx const& tx);
checkTxPermission(SLE::const_ref delegate, STTx const& tx);
/**
* Load the granular permissions granted to the delegate account for the
@@ -28,7 +28,7 @@ checkTxPermission(std::shared_ptr<SLE const> const& delegate, STTx const& tx);
*/
void
loadGranularPermission(
std::shared_ptr<SLE const> const& delegate,
SLE::const_ref delegate,
TxType const& type,
std::unordered_set<GranularPermissionType>& granularPermissions);

View File

@@ -115,7 +115,7 @@ bool
cdirFirst(
ReadView const& view,
uint256 const& root,
std::shared_ptr<SLE const>& page,
SLE::const_pointer& page,
unsigned int& index,
uint256& entry);
@@ -123,7 +123,7 @@ bool
dirFirst(
ApplyView& view,
uint256 const& root,
std::shared_ptr<SLE>& page,
SLE::pointer& page,
unsigned int& index,
uint256& entry);
/** @} */
@@ -147,7 +147,7 @@ bool
cdirNext(
ReadView const& view,
uint256 const& root,
std::shared_ptr<SLE const>& page,
SLE::const_pointer& page,
unsigned int& index,
uint256& entry);
@@ -155,17 +155,14 @@ bool
dirNext(
ApplyView& view,
uint256 const& root,
std::shared_ptr<SLE>& page,
SLE::pointer& page,
unsigned int& index,
uint256& entry);
/** @} */
/** Iterate all items in the given directory. */
void
forEachItem(
ReadView const& view,
Keylet const& root,
std::function<void(std::shared_ptr<SLE const> const&)> const& f);
forEachItem(ReadView const& view, Keylet const& root, std::function<void(SLE::const_ref)> const& f);
/** Iterate all items after an item in the given directory.
@param after The key of the item to start after
@@ -180,14 +177,11 @@ forEachItemAfter(
uint256 const& after,
std::uint64_t const hint,
unsigned int limit,
std::function<bool(std::shared_ptr<SLE const> const&)> const& f);
std::function<bool(SLE::const_ref)> const& f);
/** Iterate all items in an account's owner directory. */
inline void
forEachItem(
ReadView const& view,
AccountID const& id,
std::function<void(std::shared_ptr<SLE const> const&)> const& f)
forEachItem(ReadView const& view, AccountID const& id, std::function<void(SLE::const_ref)> const& f)
{
forEachItem(view, keylet::ownerDir(id), f);
}
@@ -205,7 +199,7 @@ forEachItemAfter(
uint256 const& after,
std::uint64_t const hint,
unsigned int limit,
std::function<bool(std::shared_ptr<SLE const> const&)> const& f)
std::function<bool(SLE::const_ref)> const& f)
{
return forEachItemAfter(view, keylet::ownerDir(id), after, hint, limit, f);
}

View File

@@ -18,7 +18,7 @@ TER
escrowUnlockApplyHelper(
ApplyView& view,
Rate lockedRate,
std::shared_ptr<SLE> const& sleDest,
SLE::ref sleDest,
STAmount const& xrpBalance,
STAmount const& amount,
AccountID const& issuer,
@@ -32,7 +32,7 @@ inline TER
escrowUnlockApplyHelper<Issue>(
ApplyView& view,
Rate lockedRate,
std::shared_ptr<SLE> const& sleDest,
SLE::ref sleDest,
STAmount const& xrpBalance,
STAmount const& amount,
AccountID const& issuer,
@@ -162,7 +162,7 @@ inline TER
escrowUnlockApplyHelper<MPTIssue>(
ApplyView& view,
Rate lockedRate,
std::shared_ptr<SLE> const& sleDest,
SLE::ref sleDest,
STAmount const& xrpBalance,
STAmount const& amount,
AccountID const& issuer,

View File

@@ -461,6 +461,7 @@ loanAccruedInterest(
ExtendedPaymentComponents
computeOverpaymentComponents(
Rules const& rules,
Asset const& asset,
int32_t const loanScale,
Number const& overpayment,

View File

@@ -28,10 +28,9 @@ findToken(ReadView const& view, AccountID const& owner, uint256 const& nftokenID
struct TokenAndPage
{
STObject token;
std::shared_ptr<SLE> page;
SLE::pointer page;
TokenAndPage(STObject token, std::shared_ptr<SLE> page)
: token(std::move(token)), page(std::move(page))
TokenAndPage(STObject token, SLE::pointer page) : token(std::move(token)), page(std::move(page))
{
}
};
@@ -47,11 +46,7 @@ TER
removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID);
TER
removeToken(
ApplyView& view,
AccountID const& owner,
uint256 const& nftokenID,
std::shared_ptr<SLE> const& page);
removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, SLE::ref page);
/** Deletes the given token offer.
@@ -63,7 +58,7 @@ removeToken(
The offer also consumes one incremental reserve.
*/
bool
deleteTokenOffer(ApplyView& view, std::shared_ptr<SLE> const& offer);
deleteTokenOffer(ApplyView& view, SLE::ref offer);
/** Repairs the links in an NFTokenPage directory.

View File

@@ -5,8 +5,6 @@
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <memory>
namespace xrpl {
/** Delete an offer.
@@ -23,6 +21,6 @@ namespace xrpl {
*/
// [[nodiscard]] // nodiscard commented out so Flow, BookTip and others compile.
TER
offerDelete(ApplyView& view, std::shared_ptr<SLE> const& sle, beast::Journal j);
offerDelete(ApplyView& view, SLE::ref sle, beast::Journal j);
} // namespace xrpl

View File

@@ -8,10 +8,6 @@
namespace xrpl {
TER
closeChannel(
std::shared_ptr<SLE> const& slep,
ApplyView& view,
uint256 const& key,
beast::Journal j);
closeChannel(SLE::ref slep, ApplyView& view, uint256 const& key, beast::Journal j);
} // namespace xrpl

View File

@@ -154,7 +154,7 @@ trustCreate(
[[nodiscard]] TER
trustDelete(
ApplyView& view,
std::shared_ptr<SLE> const& sleRippleState,
SLE::ref sleRippleState,
AccountID const& uLowAccountID,
AccountID const& uHighAccountID,
beast::Journal j);
@@ -248,7 +248,7 @@ removeEmptyHolding(
[[nodiscard]] TER
deleteAMMTrustLine(
ApplyView& view,
std::shared_ptr<SLE> sleState,
SLE::pointer sleState,
std::optional<AccountID> const& ammAccountID,
beast::Journal j);
@@ -258,7 +258,7 @@ deleteAMMTrustLine(
[[nodiscard]] TER
deleteAMMMPToken(
ApplyView& view,
std::shared_ptr<SLE> sleMPT,
SLE::pointer sleMPT,
AccountID const& ammAccountID,
beast::Journal j);

View File

@@ -5,7 +5,6 @@
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <memory>
#include <optional>
namespace xrpl {
@@ -21,10 +20,7 @@ namespace xrpl {
@return The number of shares, or nullopt on error.
*/
[[nodiscard]] std::optional<STAmount>
assetsToSharesDeposit(
std::shared_ptr<SLE const> const& vault,
std::shared_ptr<SLE const> const& issuance,
STAmount const& assets);
assetsToSharesDeposit(SLE::const_ref vault, SLE::const_ref issuance, STAmount const& assets);
/** From the perspective of a vault, return the number of assets to take from
depositor when they receive a fixed amount of shares. Note, since shares are
@@ -37,10 +33,7 @@ assetsToSharesDeposit(
@return The number of assets, or nullopt on error.
*/
[[nodiscard]] std::optional<STAmount>
sharesToAssetsDeposit(
std::shared_ptr<SLE const> const& vault,
std::shared_ptr<SLE const> const& issuance,
STAmount const& shares);
sharesToAssetsDeposit(SLE::const_ref vault, SLE::const_ref issuance, STAmount const& shares);
/** Controls whether to truncate shares instead of rounding. */
enum class TruncateShares : bool { No = false, Yes = true };
@@ -69,8 +62,8 @@ enum class WaiveUnrealizedLoss : bool { No = false, Yes = true };
*/
[[nodiscard]] std::optional<STAmount>
assetsToSharesWithdraw(
std::shared_ptr<SLE const> const& vault,
std::shared_ptr<SLE const> const& issuance,
SLE::const_ref vault,
SLE::const_ref issuance,
STAmount const& assets,
TruncateShares truncate = TruncateShares::No,
WaiveUnrealizedLoss waive = WaiveUnrealizedLoss::No);
@@ -89,8 +82,8 @@ assetsToSharesWithdraw(
*/
[[nodiscard]] std::optional<STAmount>
sharesToAssetsWithdraw(
std::shared_ptr<SLE const> const& vault,
std::shared_ptr<SLE const> const& issuance,
SLE::const_ref vault,
SLE::const_ref issuance,
STAmount const& shares,
WaiveUnrealizedLoss waive = WaiveUnrealizedLoss::No);
@@ -104,9 +97,6 @@ sharesToAssetsWithdraw(
both the share MPTID and the outstanding-amount total.
*/
[[nodiscard]] bool
isSoleShareholder(
ReadView const& view,
AccountID const& account,
std::shared_ptr<SLE const> const& issuance);
isSoleShareholder(ReadView const& view, AccountID const& account, SLE::const_ref issuance);
} // namespace xrpl

View File

@@ -131,6 +131,10 @@ public:
std::uint32_t ledgerSeq,
std::function<void(std::shared_ptr<NodeObject> const&)>&& callback);
/** Remove expired entries from the positive and negative caches. */
virtual void
sweep() = 0;
/** Gather statistics pertaining to read and write activities.
*
* @param obj Json object reference into which to place counters.

View File

@@ -22,6 +22,32 @@ public:
beast::Journal j)
: Database(scheduler, readThreads, config, j), backend_(std::move(backend))
{
std::optional<int> cacheSize, cacheAge;
if (config.exists("cache_size"))
{
cacheSize = get<int>(config, "cache_size");
if (cacheSize.value() < 0)
Throw<std::runtime_error>("Specified negative value for cache_size");
}
if (config.exists("cache_age"))
{
cacheAge = get<int>(config, "cache_age");
if (cacheAge.value() < 0)
Throw<std::runtime_error>("Specified negative value for cache_age");
}
if (cacheSize.has_value() || cacheAge.has_value())
{
cache_ = std::make_shared<TaggedCache<uint256, NodeObject>>(
"DatabaseNodeImp",
cacheSize.value_or(0),
std::chrono::minutes(cacheAge.value_or(0)),
stopwatch(),
j);
}
XRPL_ASSERT(
backend_,
"xrpl::NodeStore::DatabaseNodeImp::DatabaseNodeImp : non-null "
@@ -73,7 +99,13 @@ public:
std::uint32_t ledgerSeq,
std::function<void(std::shared_ptr<NodeObject> const&)>&& callback) override;
void
sweep() override;
private:
// Cache for database objects. This cache is not always initialized. Check
// for null before using.
std::shared_ptr<TaggedCache<uint256, NodeObject>> cache_;
// Persistent key/value storage
std::shared_ptr<Backend> backend_;

View File

@@ -55,6 +55,9 @@ public:
void
sync() override;
void
sweep() override;
private:
std::shared_ptr<Backend> writableBackend_;
std::shared_ptr<Backend> archiveBackend_;

View File

@@ -27,7 +27,7 @@ public:
* @brief Construct a ledger entry wrapper from an existing SLE object.
* @param sle The underlying serialized ledger entry to wrap
*/
explicit LedgerEntryBase(std::shared_ptr<SLE const> sle) : sle_(std::move(sle))
explicit LedgerEntryBase(SLE::const_pointer sle) : sle_(std::move(sle))
{
}
@@ -151,7 +151,7 @@ public:
* @return A constant reference to the underlying SLE object
*/
[[nodiscard]]
std::shared_ptr<SLE const>
SLE::const_pointer
getSle() const
{
return sle_;
@@ -159,7 +159,7 @@ public:
protected:
/** @brief The underlying serialized ledger entry being wrapped. */
std::shared_ptr<SLE const> sle_;
SLE::const_pointer sle_;
};
} // namespace xrpl::ledger_entries

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a AMM ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit AMM(std::shared_ptr<SLE const> sle)
explicit AMM(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -256,7 +256,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
AMMBuilder(std::shared_ptr<SLE const> sle)
AMMBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltAMM)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a AccountRoot ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit AccountRoot(std::shared_ptr<SLE const> sle)
explicit AccountRoot(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -555,7 +555,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
AccountRootBuilder(std::shared_ptr<SLE const> sle)
AccountRootBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltACCOUNT_ROOT)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a Amendments ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit Amendments(std::shared_ptr<SLE const> sle)
explicit Amendments(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -166,7 +166,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
AmendmentsBuilder(std::shared_ptr<SLE const> sle)
AmendmentsBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltAMENDMENTS)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a Bridge ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit Bridge(std::shared_ptr<SLE const> sle)
explicit Bridge(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -210,7 +210,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
BridgeBuilder(std::shared_ptr<SLE const> sle)
BridgeBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltBRIDGE)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a Check ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit Check(std::shared_ptr<SLE const> sle)
explicit Check(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -269,7 +269,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
CheckBuilder(std::shared_ptr<SLE const> sle)
CheckBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltCHECK)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a Credential ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit Credential(std::shared_ptr<SLE const> sle)
explicit Credential(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -219,7 +219,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
CredentialBuilder(std::shared_ptr<SLE const> sle)
CredentialBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltCREDENTIAL)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a DID ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit DID(std::shared_ptr<SLE const> sle)
explicit DID(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -193,7 +193,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
DIDBuilder(std::shared_ptr<SLE const> sle)
DIDBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltDID)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a Delegate ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit Delegate(std::shared_ptr<SLE const> sle)
explicit Delegate(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -172,7 +172,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
DelegateBuilder(std::shared_ptr<SLE const> sle)
DelegateBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltDELEGATE)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a DepositPreauth ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit DepositPreauth(std::shared_ptr<SLE const> sle)
explicit DepositPreauth(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -170,7 +170,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
DepositPreauthBuilder(std::shared_ptr<SLE const> sle)
DepositPreauthBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltDEPOSIT_PREAUTH)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a DirectoryNode ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit DirectoryNode(std::shared_ptr<SLE const> sle)
explicit DirectoryNode(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -431,7 +431,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
DirectoryNodeBuilder(std::shared_ptr<SLE const> sle)
DirectoryNodeBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltDIR_NODE)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a Escrow ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit Escrow(std::shared_ptr<SLE const> sle)
explicit Escrow(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -363,7 +363,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
EscrowBuilder(std::shared_ptr<SLE const> sle)
EscrowBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltESCROW)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a FeeSettings ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit FeeSettings(std::shared_ptr<SLE const> sle)
explicit FeeSettings(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -285,7 +285,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
FeeSettingsBuilder(std::shared_ptr<SLE const> sle)
FeeSettingsBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltFEE_SETTINGS)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a LedgerHashes ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit LedgerHashes(std::shared_ptr<SLE const> sle)
explicit LedgerHashes(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -130,7 +130,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
LedgerHashesBuilder(std::shared_ptr<SLE const> sle)
LedgerHashesBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltLEDGER_HASHES)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a Loan ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit Loan(std::shared_ptr<SLE const> sle)
explicit Loan(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -607,7 +607,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
LoanBuilder(std::shared_ptr<SLE const> sle)
LoanBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltLOAN)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a LoanBroker ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit LoanBroker(std::shared_ptr<SLE const> sle)
explicit LoanBroker(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -378,7 +378,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
LoanBrokerBuilder(std::shared_ptr<SLE const> sle)
LoanBrokerBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltLOAN_BROKER)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a MPToken ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit MPToken(std::shared_ptr<SLE const> sle)
explicit MPToken(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -182,7 +182,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
MPTokenBuilder(std::shared_ptr<SLE const> sle)
MPTokenBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltMPTOKEN)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a MPTokenIssuance ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit MPTokenIssuance(std::shared_ptr<SLE const> sle)
explicit MPTokenIssuance(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -339,7 +339,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
MPTokenIssuanceBuilder(std::shared_ptr<SLE const> sle)
MPTokenIssuanceBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltMPTOKEN_ISSUANCE)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a NFTokenOffer ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit NFTokenOffer(std::shared_ptr<SLE const> sle)
explicit NFTokenOffer(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -208,7 +208,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
NFTokenOfferBuilder(std::shared_ptr<SLE const> sle)
NFTokenOfferBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltNFTOKEN_OFFER)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a NFTokenPage ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit NFTokenPage(std::shared_ptr<SLE const> sle)
explicit NFTokenPage(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -157,7 +157,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
NFTokenPageBuilder(std::shared_ptr<SLE const> sle)
NFTokenPageBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltNFTOKEN_PAGE)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a NegativeUNL ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit NegativeUNL(std::shared_ptr<SLE const> sle)
explicit NegativeUNL(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -190,7 +190,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
NegativeUNLBuilder(std::shared_ptr<SLE const> sle)
NegativeUNLBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltNEGATIVE_UNL)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a Offer ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit Offer(std::shared_ptr<SLE const> sle)
explicit Offer(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -259,7 +259,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
OfferBuilder(std::shared_ptr<SLE const> sle)
OfferBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltOFFER)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a Oracle ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit Oracle(std::shared_ptr<SLE const> sle)
explicit Oracle(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -222,7 +222,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
OracleBuilder(std::shared_ptr<SLE const> sle)
OracleBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltORACLE)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a PayChannel ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit PayChannel(std::shared_ptr<SLE const> sle)
explicit PayChannel(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -330,7 +330,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
PayChannelBuilder(std::shared_ptr<SLE const> sle)
PayChannelBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltPAYCHAN)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a PermissionedDomain ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit PermissionedDomain(std::shared_ptr<SLE const> sle)
explicit PermissionedDomain(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -148,7 +148,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
PermissionedDomainBuilder(std::shared_ptr<SLE const> sle)
PermissionedDomainBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltPERMISSIONED_DOMAIN)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a RippleState ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit RippleState(std::shared_ptr<SLE const> sle)
explicit RippleState(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -278,7 +278,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
RippleStateBuilder(std::shared_ptr<SLE const> sle)
RippleStateBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltRIPPLE_STATE)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a SignerList ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit SignerList(std::shared_ptr<SLE const> sle)
explicit SignerList(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -172,7 +172,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
SignerListBuilder(std::shared_ptr<SLE const> sle)
SignerListBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltSIGNER_LIST)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a Ticket ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit Ticket(std::shared_ptr<SLE const> sle)
explicit Ticket(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -134,7 +134,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
TicketBuilder(std::shared_ptr<SLE const> sle)
TicketBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltTICKET)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a Vault ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit Vault(std::shared_ptr<SLE const> sle)
explicit Vault(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -330,7 +330,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
VaultBuilder(std::shared_ptr<SLE const> sle)
VaultBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltVAULT)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a XChainOwnedClaimID ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit XChainOwnedClaimID(std::shared_ptr<SLE const> sle)
explicit XChainOwnedClaimID(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -187,7 +187,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
XChainOwnedClaimIDBuilder(std::shared_ptr<SLE const> sle)
XChainOwnedClaimIDBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltXCHAIN_OWNED_CLAIM_ID)
{

View File

@@ -33,7 +33,7 @@ public:
* @brief Construct a XChainOwnedCreateAccountClaimID ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit XChainOwnedCreateAccountClaimID(std::shared_ptr<SLE const> sle)
explicit XChainOwnedCreateAccountClaimID(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
@@ -161,7 +161,7 @@ public:
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
XChainOwnedCreateAccountClaimIDBuilder(std::shared_ptr<SLE const> sle)
XChainOwnedCreateAccountClaimIDBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID)
{

View File

@@ -85,7 +85,7 @@ private:
/** The sequence of the ledger that this map references, if any. */
std::uint32_t ledgerSeq_ = 0;
intr_ptr::SharedPtr<SHAMapTreeNode> root_;
SHAMapTreeNodePtr root_;
mutable SHAMapState state_;
SHAMapType const type_;
bool backed_ = true; // Map is backed by the database
@@ -178,31 +178,6 @@ public:
SHAMapHash
getHash() const;
/** Recompute dirty node hashes in parallel and return the root hash.
Plan 7 Phase 2. Equivalent in output to `getHash()` on a freshly
mutated map: it recomputes the hash of every node dirtied since the
last hash settle, bottom-up, then returns the root hash. The work is
fanned out by top-level subtree — the root's up-to-16 children are
independent (a dirty node under one branch is never shared with
another), so their subtree recomputations run concurrently with no
synchronization, and only the root hash is computed serially after.
Byte-identical to the serial path by construction: a node's hash is a
pure function of its children's hashes, so computation order is
irrelevant as long as children precede parents — which the bottom-up
walk guarantees.
Unlike `getHash()`/`unshare()`, this does NOT convert nodes to shared
(cowid stays as-is) or flush to the nodestore; it only refreshes hash
caches. A subsequent `getHash()` therefore returns immediately.
@param workers Maximum concurrent subtree recomputations. <= 1 runs
serially. Defaults to the hardware concurrency.
*/
SHAMapHash
updateHashesParallel(int workers = 0);
// save a copy if you have a temporary anyway
bool
updateGiveItem(SHAMapNodeType type, boost::intrusive_ptr<SHAMapItem const> item);
@@ -351,36 +326,32 @@ public:
invariants() const;
private:
using SharedPtrNodeStack =
std::stack<std::pair<intr_ptr::SharedPtr<SHAMapTreeNode>, SHAMapNodeID>>;
using SharedPtrNodeStack = std::stack<std::pair<SHAMapTreeNodePtr, SHAMapNodeID>>;
using DeltaRef =
std::pair<boost::intrusive_ptr<SHAMapItem const>, boost::intrusive_ptr<SHAMapItem const>>;
// tree node cache operations
intr_ptr::SharedPtr<SHAMapTreeNode>
SHAMapTreeNodePtr
cacheLookup(SHAMapHash const& hash) const;
void
canonicalize(SHAMapHash const& hash, intr_ptr::SharedPtr<SHAMapTreeNode>&) const;
canonicalize(SHAMapHash const& hash, SHAMapTreeNodePtr&) const;
// database operations
intr_ptr::SharedPtr<SHAMapTreeNode>
SHAMapTreeNodePtr
fetchNodeFromDB(SHAMapHash const& hash) const;
intr_ptr::SharedPtr<SHAMapTreeNode>
SHAMapTreeNodePtr
fetchNodeNT(SHAMapHash const& hash) const;
intr_ptr::SharedPtr<SHAMapTreeNode>
SHAMapTreeNodePtr
fetchNodeNT(SHAMapHash const& hash, SHAMapSyncFilter* filter) const;
intr_ptr::SharedPtr<SHAMapTreeNode>
SHAMapTreeNodePtr
fetchNode(SHAMapHash const& hash) const;
intr_ptr::SharedPtr<SHAMapTreeNode>
SHAMapTreeNodePtr
checkFilter(SHAMapHash const& hash, SHAMapSyncFilter* filter) const;
/** Update hashes up to the root */
void
dirtyUp(
SharedPtrNodeStack& stack,
uint256 const& target,
intr_ptr::SharedPtr<SHAMapTreeNode> terminal);
dirtyUp(SharedPtrNodeStack& stack, uint256 const& target, SHAMapTreeNodePtr terminal);
/** Walk towards the specified id, returning the node. Caller must check
if the return is nullptr, and if not, if the node->peekItem()->key() ==
@@ -402,25 +373,21 @@ private:
preFlushNode(intr_ptr::SharedPtr<Node> node) const;
/** write and canonicalize modified node */
intr_ptr::SharedPtr<SHAMapTreeNode>
writeNode(NodeObjectType t, intr_ptr::SharedPtr<SHAMapTreeNode> node) const;
SHAMapTreeNodePtr
writeNode(NodeObjectType t, SHAMapTreeNodePtr node) const;
// returns the first item at or below this node
SHAMapLeafNode*
firstBelow(intr_ptr::SharedPtr<SHAMapTreeNode>, SharedPtrNodeStack& stack, int branch = 0)
const;
firstBelow(SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, int branch = 0) const;
// returns the last item at or below this node
SHAMapLeafNode*
lastBelow(
intr_ptr::SharedPtr<SHAMapTreeNode> node,
SharedPtrNodeStack& stack,
int branch = kBranchFactor) const;
lastBelow(SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, int branch = kBranchFactor) const;
// helper function for firstBelow and lastBelow
SHAMapLeafNode*
belowHelper(
intr_ptr::SharedPtr<SHAMapTreeNode> node,
SHAMapTreeNodePtr node,
SharedPtrNodeStack& stack,
int branch,
std::tuple<int, std::function<bool(int)>, std::function<void(int&)>> const& loopParams)
@@ -432,15 +399,14 @@ private:
descend(SHAMapInnerNode*, int branch) const;
SHAMapTreeNode*
descendThrow(SHAMapInnerNode*, int branch) const;
intr_ptr::SharedPtr<SHAMapTreeNode>
SHAMapTreeNodePtr
descend(SHAMapInnerNode&, int branch) const;
intr_ptr::SharedPtr<SHAMapTreeNode>
SHAMapTreeNodePtr
descendThrow(SHAMapInnerNode&, int branch) const;
// Descend with filter
// If pending, callback is called as if it called fetchNodeNT
using descendCallback =
std::function<void(intr_ptr::SharedPtr<SHAMapTreeNode>, SHAMapHash const&)>;
using descendCallback = std::function<void(SHAMapTreeNodePtr, SHAMapHash const&)>;
SHAMapTreeNode*
descendAsync(
SHAMapInnerNode* parent,
@@ -458,7 +424,7 @@ private:
// Non-storing
// Does not hook the returned node to its parent
intr_ptr::SharedPtr<SHAMapTreeNode>
SHAMapTreeNodePtr
descendNoStore(SHAMapInnerNode&, int branch) const;
/** If there is only one leaf below this node, get its contents */
@@ -520,10 +486,10 @@ private:
// nodes we may have acquired from deferred reads
using DeferredNode = std::tuple<
SHAMapInnerNode*, // parent node
SHAMapNodeID, // parent node ID
int, // branch
intr_ptr::SharedPtr<SHAMapTreeNode>>; // node
SHAMapInnerNode*, // parent node
SHAMapNodeID, // parent node ID
int, // branch
SHAMapTreeNodePtr>; // node
int deferred;
std::mutex deferLock;
@@ -549,7 +515,7 @@ private:
gmnProcessDeferredReads(MissingNodes&);
// fetch from DB helper function
intr_ptr::SharedPtr<SHAMapTreeNode>
SHAMapTreeNodePtr
finishFetch(SHAMapHash const& hash, std::shared_ptr<NodeObject> const& object) const;
};

View File

@@ -27,7 +27,7 @@ public:
{
}
intr_ptr::SharedPtr<SHAMapTreeNode>
SHAMapTreeNodePtr
clone(std::uint32_t cowid) const final
{
return intr_ptr::makeShared<SHAMapAccountStateLeafNode>(item_, cowid, hash_);

View File

@@ -87,7 +87,7 @@ public:
void
partialDestructor() override;
intr_ptr::SharedPtr<SHAMapTreeNode>
SHAMapTreeNodePtr
clone(std::uint32_t cowid) const override;
SHAMapNodeType
@@ -121,19 +121,19 @@ public:
getChildHash(int m) const;
void
setChild(int m, intr_ptr::SharedPtr<SHAMapTreeNode> child);
setChild(int m, SHAMapTreeNodePtr child);
void
shareChild(int m, intr_ptr::SharedPtr<SHAMapTreeNode> const& child);
shareChild(int m, SHAMapTreeNodePtr const& child);
SHAMapTreeNode*
getChildPointer(int branch);
intr_ptr::SharedPtr<SHAMapTreeNode>
SHAMapTreeNodePtr
getChild(int branch);
intr_ptr::SharedPtr<SHAMapTreeNode>
canonicalizeChild(int branch, intr_ptr::SharedPtr<SHAMapTreeNode> node);
SHAMapTreeNodePtr
canonicalizeChild(int branch, SHAMapTreeNodePtr node);
// sync functions
bool
@@ -161,10 +161,10 @@ public:
void
invariants(bool isRoot = false) const override;
static intr_ptr::SharedPtr<SHAMapTreeNode>
static SHAMapTreeNodePtr
makeFullInner(Slice data, SHAMapHash const& hash, bool hashValid);
static intr_ptr::SharedPtr<SHAMapTreeNode>
static SHAMapTreeNodePtr
makeCompressedInner(Slice data);
};

View File

@@ -13,6 +13,9 @@
namespace xrpl {
class SHAMapTreeNode;
using SHAMapTreeNodePtr = intr_ptr::SharedPtr<SHAMapTreeNode>;
// These are wire-protocol identifiers used during serialization to encode the
// type of a node. They should not be arbitrarily be changed.
static constexpr unsigned char const kWireTypeTransaction = 0;
@@ -112,7 +115,7 @@ public:
}
/** Make a copy of this node, setting the owner. */
virtual intr_ptr::SharedPtr<SHAMapTreeNode>
virtual SHAMapTreeNodePtr
clone(std::uint32_t cowid) const = 0;
/** @} */
@@ -153,20 +156,20 @@ public:
virtual void
invariants(bool isRoot = false) const = 0;
static intr_ptr::SharedPtr<SHAMapTreeNode>
static SHAMapTreeNodePtr
makeFromPrefix(Slice rawNode, SHAMapHash const& hash);
static intr_ptr::SharedPtr<SHAMapTreeNode>
static SHAMapTreeNodePtr
makeFromWire(Slice rawNode);
private:
static intr_ptr::SharedPtr<SHAMapTreeNode>
static SHAMapTreeNodePtr
makeTransaction(Slice data, SHAMapHash const& hash, bool hashValid);
static intr_ptr::SharedPtr<SHAMapTreeNode>
static SHAMapTreeNodePtr
makeAccountState(Slice data, SHAMapHash const& hash, bool hashValid);
static intr_ptr::SharedPtr<SHAMapTreeNode>
static SHAMapTreeNodePtr
makeTransactionWithMeta(Slice data, SHAMapHash const& hash, bool hashValid);
};

View File

@@ -26,7 +26,7 @@ public:
{
}
intr_ptr::SharedPtr<SHAMapTreeNode>
SHAMapTreeNodePtr
clone(std::uint32_t cowid) const final
{
return intr_ptr::makeShared<SHAMapTxLeafNode>(item_, cowid, hash_);

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