mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-07 18:56:47 +00:00
Compare commits
21 Commits
bthomee/mo
...
gregtatcam
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81c4a66c63 | ||
|
|
e305416c80 | ||
|
|
93fa1e31e7 | ||
|
|
b325a4fec5 | ||
|
|
caea67ce11 | ||
|
|
8308c5cebe | ||
|
|
27ef4f87e9 | ||
|
|
2cb2fdb97b | ||
|
|
5b9d599007 | ||
|
|
775b6bcbd3 | ||
|
|
20ce845ed2 | ||
|
|
05c4357242 | ||
|
|
5cd0df92d4 | ||
|
|
22fbf4d060 | ||
|
|
a27596f988 | ||
|
|
c6918f5915 | ||
|
|
01164a3ec0 | ||
|
|
21cfba66fd | ||
|
|
210b6e08ba | ||
|
|
305d784b26 | ||
|
|
e568175524 |
@@ -154,7 +154,7 @@ Checks: "-*,
|
||||
"
|
||||
# ---
|
||||
# readability-inconsistent-declaration-parameter-name, # in this codebase this check will break a lot of arg names
|
||||
# readability-static-accessed-through-instance, # this check is probably unnecessary. It makes the code less readable
|
||||
# readability-static-accessed-through-instance, # this check is probably unnecessary. it makes the code less readable
|
||||
# ---
|
||||
|
||||
CheckOptions:
|
||||
|
||||
@@ -11,6 +11,9 @@ endfunction()
|
||||
function(create_symbolic_link target link)
|
||||
endfunction()
|
||||
|
||||
function(xrpl_add_test name)
|
||||
endfunction()
|
||||
|
||||
macro(exclude_from_default target_)
|
||||
endmacro()
|
||||
|
||||
|
||||
3
.github/actions/build-deps/action.yml
vendored
3
.github/actions/build-deps/action.yml
vendored
@@ -35,8 +35,9 @@ runs:
|
||||
LOG_VERBOSITY: ${{ inputs.log_verbosity }}
|
||||
SANITIZERS: ${{ inputs.sanitizers }}
|
||||
run: |
|
||||
echo 'Installing dependencies.'
|
||||
conan install \
|
||||
--profile:all ci \
|
||||
--profile ci \
|
||||
--build="${BUILD_OPTION}" \
|
||||
--options:host='&:tests=True' \
|
||||
--options:host='&:xrpld=True' \
|
||||
|
||||
34
.github/actions/set-compiler-env/action.yml
vendored
34
.github/actions/set-compiler-env/action.yml
vendored
@@ -1,34 +0,0 @@
|
||||
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
|
||||
21
.github/actions/setup-conan/action.yml
vendored
21
.github/actions/setup-conan/action.yml
vendored
@@ -15,35 +15,32 @@ runs:
|
||||
using: composite
|
||||
|
||||
steps:
|
||||
- name: Apply custom configuration to global.conf
|
||||
- name: Set up Conan configuration
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'Installing configuration.'
|
||||
cat conan/global.conf ${{ runner.os == 'Linux' && '>>' || '>' }} $(conan config home)/global.conf
|
||||
|
||||
- name: Show global configuration
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'Conan configuration:'
|
||||
conan config show '*'
|
||||
|
||||
- name: Install profiles
|
||||
- name: Set up Conan profile
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'Installing profile.'
|
||||
conan config install conan/profiles/ -tf $(conan config home)/profiles/
|
||||
|
||||
- name: Show CI profile
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'Conan profile:'
|
||||
conan profile show --profile ci
|
||||
|
||||
- name: Add a remote
|
||||
- name: Set up Conan 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}"
|
||||
|
||||
- name: List remotes
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'Listing Conan remotes.'
|
||||
conan remote list
|
||||
|
||||
40
.github/dependabot.yml
vendored
40
.github/dependabot.yml
vendored
@@ -1,12 +1,40 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directories:
|
||||
- /
|
||||
- .github/actions/build-deps/
|
||||
- .github/actions/generate-version/
|
||||
- .github/actions/set-compiler-env/
|
||||
- .github/actions/setup-conan/
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: "04:00"
|
||||
timezone: Etc/GMT
|
||||
commit-message:
|
||||
prefix: "ci: [DEPENDABOT] "
|
||||
target-branch: develop
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: .github/actions/build-deps/
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: "04:00"
|
||||
timezone: Etc/GMT
|
||||
commit-message:
|
||||
prefix: "ci: [DEPENDABOT] "
|
||||
target-branch: develop
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: .github/actions/generate-version/
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: "04:00"
|
||||
timezone: Etc/GMT
|
||||
commit-message:
|
||||
prefix: "ci: [DEPENDABOT] "
|
||||
target-branch: develop
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: .github/actions/setup-conan/
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
|
||||
4
.github/scripts/rename/cmake.sh
vendored
4
.github/scripts/rename/cmake.sh
vendored
@@ -43,6 +43,9 @@ pushd "${DIRECTORY}"
|
||||
# Rename the files.
|
||||
find cmake -type f -name 'Rippled*.cmake' -exec bash -c 'mv "${1}" "${1/Rippled/Xrpl}"' - {} \;
|
||||
find cmake -type f -name 'Ripple*.cmake' -exec bash -c 'mv "${1}" "${1/Ripple/Xrpl}"' - {} \;
|
||||
if [ -e cmake/xrpl_add_test.cmake ]; then
|
||||
mv cmake/xrpl_add_test.cmake cmake/XrplAddTest.cmake
|
||||
fi
|
||||
if [ -e include/xrpl/proto/ripple.proto ]; then
|
||||
mv include/xrpl/proto/ripple.proto include/xrpl/proto/xrpl.proto
|
||||
fi
|
||||
@@ -57,6 +60,7 @@ find cmake -type f -name '*.cmake' | while read -r FILE; do
|
||||
done
|
||||
${SED_COMMAND} -i -E 's/Rippled?/Xrpl/g' CMakeLists.txt
|
||||
${SED_COMMAND} -i 's/ripple/xrpl/g' CMakeLists.txt
|
||||
${SED_COMMAND} -i 's/include(xrpl_add_test)/include(XrplAddTest)/' src/tests/libxrpl/CMakeLists.txt
|
||||
${SED_COMMAND} -i 's/ripple.pb.h/xrpl.pb.h/' include/xrpl/protocol/messages.h
|
||||
${SED_COMMAND} -i 's/ripple.pb.h/xrpl.pb.h/' BUILD.md
|
||||
${SED_COMMAND} -i 's/ripple.pb.h/xrpl.pb.h/' BUILD.md
|
||||
|
||||
563
.github/scripts/strategy-matrix/generate.py
vendored
563
.github/scripts/strategy-matrix/generate.py
vendored
@@ -1,281 +1,384 @@
|
||||
#!/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"]
|
||||
|
||||
# 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]
|
||||
@dataclass
|
||||
class Config:
|
||||
architecture: list[dict]
|
||||
os: list[dict]
|
||||
build_type: 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
|
||||
cmake_args: list[str]
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class LinuxFile:
|
||||
"""Shape of linux.json."""
|
||||
"""
|
||||
Generate a strategy matrix for GitHub Actions CI.
|
||||
|
||||
image_tag: str
|
||||
configs: dict[str, list[LinuxConfig]] # distro → configs
|
||||
package_configs: dict[str, list[LinuxConfig]] # distro → packaging configs
|
||||
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.
|
||||
|
||||
@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", {})),
|
||||
)
|
||||
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.
|
||||
"""
|
||||
|
||||
|
||||
@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 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 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.
|
||||
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.
|
||||
"""
|
||||
entries: list[MatrixEntry] = []
|
||||
return [
|
||||
{
|
||||
"artifact_name": f"xrpld-{build_config_name(os, 'linux/amd64', 'Release')}",
|
||||
"os": os,
|
||||
}
|
||||
for os in config.os
|
||||
if os.get("package", False)
|
||||
]
|
||||
|
||||
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}
|
||||
|
||||
for compiler, build_type, sanitizer, (arch, arch_info) in itertools.product(
|
||||
cfg.compiler,
|
||||
cfg.build_type,
|
||||
effective_sanitizers,
|
||||
effective_archs.items(),
|
||||
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"
|
||||
):
|
||||
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)
|
||||
continue
|
||||
|
||||
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,
|
||||
)
|
||||
)
|
||||
# Windows:
|
||||
# - Release on windows/amd64.
|
||||
if os["distro_name"] == "windows" and not (
|
||||
build_type == "Release" and architecture["platform"] == "windows/amd64"
|
||||
):
|
||||
continue
|
||||
|
||||
return entries
|
||||
# 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
|
||||
|
||||
def expand_linux_packaging(linux: LinuxFile) -> list[PackagingEntry]:
|
||||
"""Generate the packaging matrix from a LinuxFile's package_configs section.
|
||||
# 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
|
||||
|
||||
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,
|
||||
)
|
||||
)
|
||||
# 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"
|
||||
|
||||
return entries
|
||||
# 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"
|
||||
|
||||
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="",
|
||||
)
|
||||
# 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",
|
||||
}
|
||||
)
|
||||
return entries
|
||||
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",
|
||||
}
|
||||
)
|
||||
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
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Entry point
|
||||
# ---------------------------------------------------------------------------
|
||||
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)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate a CI strategy matrix for all platforms or a specific one."
|
||||
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.add_argument(
|
||||
"-c",
|
||||
"--config",
|
||||
help="Platform to generate for ('linux', 'macos', or 'windows'). Defaults to all platforms.",
|
||||
choices=["linux", "macos", "windows"],
|
||||
default=None,
|
||||
help="Path to the JSON file containing the strategy matrix configurations.",
|
||||
required=False,
|
||||
type=Path,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--packaging",
|
||||
help="Emit the Linux packaging matrix instead of the build/test matrix.",
|
||||
help="Emit the packaging matrix (derived from the 'package' field on os entries) instead of the build/test matrix.",
|
||||
action="store_true",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
matrix: list[MatrixEntry] | list[PackagingEntry] = []
|
||||
|
||||
matrix = []
|
||||
if args.packaging:
|
||||
matrix = expand_linux_packaging(LinuxFile.load(THIS_DIR / "linux.json"))
|
||||
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:
|
||||
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")
|
||||
)
|
||||
matrix += generate_strategy_matrix(args.all, read_config(args.config))
|
||||
|
||||
print(f"matrix={json.dumps({'include': [dataclasses.asdict(e) for e in matrix]})}")
|
||||
# Generate the strategy matrix.
|
||||
print(f"matrix={json.dumps({'include': matrix})}")
|
||||
|
||||
300
.github/scripts/strategy-matrix/linux.json
vendored
300
.github/scripts/strategy-matrix/linux.json
vendored
@@ -1,83 +1,221 @@
|
||||
{
|
||||
"image_tag": "sha-8abe82e",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
"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": [""]
|
||||
}
|
||||
|
||||
26
.github/scripts/strategy-matrix/macos.json
vendored
26
.github/scripts/strategy-matrix/macos.json
vendored
@@ -1,15 +1,19 @@
|
||||
{
|
||||
"platform": "macos/arm64",
|
||||
"runner": ["self-hosted", "macOS", "ARM64", "mac-runner-m1"],
|
||||
"configs": [
|
||||
"architecture": [
|
||||
{
|
||||
"build_type": "Release",
|
||||
"extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5"
|
||||
},
|
||||
{
|
||||
"build_type": "Debug",
|
||||
"extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
|
||||
"build_only": true
|
||||
"platform": "macos/arm64",
|
||||
"runner": ["self-hosted", "macOS", "ARM64", "mac-runner-m1"]
|
||||
}
|
||||
]
|
||||
],
|
||||
"os": [
|
||||
{
|
||||
"distro_name": "macos",
|
||||
"distro_version": "",
|
||||
"compiler_name": "",
|
||||
"compiler_version": "",
|
||||
"image_sha": ""
|
||||
}
|
||||
],
|
||||
"build_type": ["Debug", "Release"],
|
||||
"cmake_args": ["-DCMAKE_POLICY_VERSION_MINIMUM=3.5"]
|
||||
}
|
||||
|
||||
23
.github/scripts/strategy-matrix/windows.json
vendored
23
.github/scripts/strategy-matrix/windows.json
vendored
@@ -1,8 +1,19 @@
|
||||
{
|
||||
"platform": "windows/amd64",
|
||||
"runner": ["self-hosted", "Windows", "devbox"],
|
||||
"configs": [
|
||||
{ "build_type": "Release" },
|
||||
{ "build_type": "Debug", "build_only": true }
|
||||
]
|
||||
"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": [""]
|
||||
}
|
||||
|
||||
109
.github/workflows/build-nix-image.yml
vendored
Normal file
109
.github/workflows/build-nix-image.yml
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
name: Build Nix Docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
paths:
|
||||
- ".github/workflows/build-nix-image.yml"
|
||||
- ".github/workflows/reusable-build-docker-image.yml"
|
||||
- "docker/**"
|
||||
- "flake.nix"
|
||||
- "flake.lock"
|
||||
- "nix/**"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/build-nix-image.yml"
|
||||
- ".github/workflows/reusable-build-docker-image.yml"
|
||||
- "docker/**"
|
||||
- "flake.nix"
|
||||
- "flake.lock"
|
||||
- "nix/**"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build ${{ matrix.distro.name }} (${{ matrix.target.platform }})
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# The base images are the oldest supported version of each distro
|
||||
# that we want to build images for.
|
||||
distro:
|
||||
- name: nixos
|
||||
base_image: nixos/nix:latest
|
||||
- name: ubuntu
|
||||
base_image: ubuntu:20.04
|
||||
- name: rhel
|
||||
base_image: registry.access.redhat.com/ubi9/ubi:latest
|
||||
- name: debian
|
||||
base_image: debian:bookworm
|
||||
target:
|
||||
- platform: linux/amd64
|
||||
runner: ubuntu-latest
|
||||
- platform: linux/arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
uses: ./.github/workflows/reusable-build-docker-image.yml
|
||||
with:
|
||||
image_name: ghcr.io/xrplf/xrpld/nix-${{ matrix.distro.name }}
|
||||
dockerfile: docker/nix.Dockerfile
|
||||
base_image: ${{ matrix.distro.base_image }}
|
||||
platform: ${{ matrix.target.platform }}
|
||||
runner: ${{ matrix.target.runner }}
|
||||
push: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }}
|
||||
|
||||
merge:
|
||||
name: Merge ${{ matrix.distro }} manifest
|
||||
needs: build
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
distro: [nixos, ubuntu, rhel, debian]
|
||||
env:
|
||||
IMAGE_NAME: ghcr.io/xrplf/xrpld/nix-${{ matrix.distro }}
|
||||
|
||||
steps:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
|
||||
- name: Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
|
||||
with:
|
||||
images: ${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=sha,prefix=sha-,format=short
|
||||
type=raw,value=latest
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create multi-arch manifests
|
||||
run: |
|
||||
for tag in $(jq -cr '.tags[]' <<<"$DOCKER_METADATA_OUTPUT_JSON"); do
|
||||
docker buildx imagetools create -t "$tag" "${tag}-amd64" "${tag}-arm64"
|
||||
done
|
||||
|
||||
- name: Inspect image
|
||||
run: |
|
||||
docker buildx imagetools inspect "${IMAGE_NAME}:${{ steps.meta.outputs.version }}"
|
||||
56
.github/workflows/build-nix-images.yml
vendored
56
.github/workflows/build-nix-images.yml
vendored
@@ -1,56 +0,0 @@
|
||||
name: Build Nix Docker images
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
paths:
|
||||
- ".github/workflows/build-nix-images.yml"
|
||||
- ".github/workflows/reusable-build-docker-image.yml"
|
||||
- ".github/workflows/reusable-build-merge-docker-images.yml"
|
||||
- "flake.nix"
|
||||
- "flake.lock"
|
||||
- "nix/**"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/build-nix-images.yml"
|
||||
- ".github/workflows/reusable-build-docker-image.yml"
|
||||
- ".github/workflows/reusable-build-merge-docker-images.yml"
|
||||
- "flake.nix"
|
||||
- "flake.lock"
|
||||
- "nix/**"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
build-merge:
|
||||
name: Build and push nix-${{ matrix.distro.name }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# The base images are the oldest supported version of each distro
|
||||
# that we want to build images for.
|
||||
distro:
|
||||
- name: nixos
|
||||
base_image: nixos/nix:latest
|
||||
- name: ubuntu
|
||||
base_image: ubuntu:20.04
|
||||
- name: debian
|
||||
base_image: debian:bookworm
|
||||
- name: rhel
|
||||
base_image: registry.access.redhat.com/ubi9/ubi:latest
|
||||
uses: ./.github/workflows/reusable-build-merge-docker-images.yml
|
||||
with:
|
||||
image_name: ghcr.io/xrplf/xrpld/nix-${{ matrix.distro.name }}
|
||||
dockerfile: nix/docker/Dockerfile
|
||||
base_image: ${{ matrix.distro.base_image }}
|
||||
48
.github/workflows/build-packaging-images.yml
vendored
48
.github/workflows/build-packaging-images.yml
vendored
@@ -1,48 +0,0 @@
|
||||
name: Build packaging Docker images
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
paths:
|
||||
- ".github/workflows/build-packaging-images.yml"
|
||||
- ".github/workflows/reusable-build-docker-image.yml"
|
||||
- ".github/workflows/reusable-build-merge-docker-images.yml"
|
||||
- "package/Dockerfile"
|
||||
- "package/install-packaging-tools.sh"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/build-packaging-images.yml"
|
||||
- ".github/workflows/reusable-build-docker-image.yml"
|
||||
- ".github/workflows/reusable-build-merge-docker-images.yml"
|
||||
- "package/Dockerfile"
|
||||
- "package/install-packaging-tools.sh"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
build-merge:
|
||||
name: Build and push packaging-${{ matrix.distro.name }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
distro:
|
||||
- name: debian
|
||||
base_image: debian:bookworm
|
||||
- name: rhel
|
||||
base_image: registry.access.redhat.com/ubi9/ubi:latest
|
||||
uses: ./.github/workflows/reusable-build-merge-docker-images.yml
|
||||
with:
|
||||
image_name: ghcr.io/xrplf/xrpld/packaging-${{ matrix.distro.name }}
|
||||
dockerfile: package/Dockerfile
|
||||
base_image: ${{ matrix.distro.base_image }}
|
||||
2
.github/workflows/check-pr-description.yml
vendored
2
.github/workflows/check-pr-description.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Write PR body to file
|
||||
env:
|
||||
|
||||
2
.github/workflows/on-pr.yml
vendored
2
.github/workflows/on-pr.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Determine changed files
|
||||
# This step checks whether any files have changed that should
|
||||
# cause the next jobs to run. We do it this way rather than
|
||||
|
||||
1
.github/workflows/on-tag.yml
vendored
1
.github/workflows/on-tag.yml
vendored
@@ -33,6 +33,7 @@ jobs:
|
||||
with:
|
||||
ccache_enabled: false
|
||||
os: ${{ matrix.os }}
|
||||
strategy_matrix: minimal
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
|
||||
1
.github/workflows/on-trigger.yml
vendored
1
.github/workflows/on-trigger.yml
vendored
@@ -88,6 +88,7 @@ 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 }}
|
||||
|
||||
|
||||
20
.github/workflows/publish-docs.yml
vendored
20
.github/workflows/publish-docs.yml
vendored
@@ -41,10 +41,10 @@ env:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-8abe82e
|
||||
container: ghcr.io/xrplf/ci/tools-rippled-documentation:sha-a8c7be1
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab
|
||||
@@ -57,11 +57,19 @@ jobs:
|
||||
with:
|
||||
subtract: ${{ env.NPROC_SUBTRACT }}
|
||||
|
||||
- name: Print build environment
|
||||
uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574
|
||||
- name: Check configuration
|
||||
run: |
|
||||
echo 'Checking path.'
|
||||
echo ${PATH} | tr ':' '\n'
|
||||
|
||||
- name: Check Doxygen version
|
||||
run: doxygen --version
|
||||
echo 'Checking environment variables.'
|
||||
env | sort
|
||||
|
||||
echo 'Checking CMake version.'
|
||||
cmake --version
|
||||
|
||||
echo 'Checking Doxygen version.'
|
||||
doxygen --version
|
||||
|
||||
- name: Build documentation
|
||||
env:
|
||||
|
||||
@@ -38,7 +38,7 @@ defaults:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build ${{ inputs.platform }}
|
||||
name: Build (${{ inputs.platform }})
|
||||
runs-on: ${{ inputs.runner }}
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Determine arch
|
||||
id: vars
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
name: Reusable build and merge Docker image (multi-arch)
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
image_name:
|
||||
description: "Full image name without tag (e.g. 'ghcr.io/xrplf/xrpld/nix-ubuntu')"
|
||||
required: true
|
||||
type: string
|
||||
dockerfile:
|
||||
description: "Path to the Dockerfile, relative to the repository root"
|
||||
required: true
|
||||
type: string
|
||||
base_image:
|
||||
description: "Value passed to the Dockerfile as the BASE_IMAGE build arg"
|
||||
required: true
|
||||
type: string
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build ${{ inputs.image_name }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- platform: linux/amd64
|
||||
runner: ubuntu-latest
|
||||
- platform: linux/arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
|
||||
uses: ./.github/workflows/reusable-build-docker-image.yml
|
||||
with:
|
||||
image_name: ${{ inputs.image_name }}
|
||||
dockerfile: ${{ inputs.dockerfile }}
|
||||
base_image: ${{ inputs.base_image }}
|
||||
platform: ${{ matrix.target.platform }}
|
||||
runner: ${{ matrix.target.runner }}
|
||||
push: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }}
|
||||
|
||||
merge:
|
||||
name: Merge ${{ inputs.image_name }}
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
|
||||
- name: Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
|
||||
with:
|
||||
images: ${{ inputs.image_name }}
|
||||
tags: |
|
||||
type=sha,prefix=sha-,format=short
|
||||
type=raw,value=latest
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create multi-arch manifests
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }}
|
||||
run: |
|
||||
for tag in $(jq -cr '.tags[]' <<<"$DOCKER_METADATA_OUTPUT_JSON"); do
|
||||
docker buildx imagetools create -t "$tag" "${tag}-amd64" "${tag}-arm64"
|
||||
done
|
||||
|
||||
- name: Inspect image
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }}
|
||||
env:
|
||||
IMAGE_NAME: ${{ inputs.image_name }}
|
||||
IMAGE_VERSION: ${{ steps.meta.outputs.version }}
|
||||
run: |
|
||||
docker buildx imagetools inspect "${IMAGE_NAME}:${IMAGE_VERSION}"
|
||||
72
.github/workflows/reusable-build-test-config.yml
vendored
72
.github/workflows/reusable-build-test-config.yml
vendored
@@ -57,12 +57,6 @@ 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."
|
||||
@@ -110,7 +104,7 @@ jobs:
|
||||
uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab
|
||||
@@ -130,12 +124,6 @@ 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 }}
|
||||
@@ -203,21 +191,6 @@ 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: |
|
||||
@@ -236,15 +209,6 @@ jobs:
|
||||
retention-days: 3
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload the test binary (Linux)
|
||||
if: ${{ github.event.repository.visibility == 'public' && runner.os == 'Linux' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: xrpl_tests-${{ inputs.config_name }}
|
||||
path: ${{ env.BUILD_DIR }}/xrpl_tests
|
||||
retention-days: 3
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Export server definitions
|
||||
if: ${{ runner.os != 'Windows' && !inputs.build_only && env.VOIDSTAR_ENABLED != 'true' }}
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
@@ -253,7 +217,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-gcc-release-amd64' }}
|
||||
if: ${{ github.event.repository.visibility == 'public' && inputs.config_name == 'debian-bookworm-gcc-13-amd64-release' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: server-definitions
|
||||
@@ -295,8 +259,16 @@ jobs:
|
||||
|
||||
- name: Run the separate tests
|
||||
if: ${{ !inputs.build_only }}
|
||||
working-directory: ${{ runner.os == 'Windows' && format('{0}/{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }}
|
||||
run: ./xrpl_tests
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
# Windows locks some of the build files while running tests, and parallel jobs can collide
|
||||
env:
|
||||
BUILD_TYPE: ${{ inputs.build_type }}
|
||||
PARALLELISM: ${{ runner.os == 'Windows' && '1' || steps.nproc.outputs.nproc }}
|
||||
run: |
|
||||
ctest \
|
||||
--output-on-failure \
|
||||
-C "${BUILD_TYPE}" \
|
||||
-j "${PARALLELISM}"
|
||||
|
||||
- name: Run the embedded tests
|
||||
if: ${{ !inputs.build_only }}
|
||||
@@ -307,25 +279,7 @@ 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))
|
||||
|
||||
# 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
|
||||
./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee unittest.log
|
||||
|
||||
- name: Show test failure summary
|
||||
if: ${{ failure() && !inputs.build_only }}
|
||||
|
||||
12
.github/workflows/reusable-build-test.yml
vendored
12
.github/workflows/reusable-build-test.yml
vendored
@@ -19,6 +19,13 @@ 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."
|
||||
@@ -30,6 +37,7 @@ 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:
|
||||
@@ -39,6 +47,7 @@ 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 }}
|
||||
@@ -46,9 +55,8 @@ jobs:
|
||||
cmake_args: ${{ matrix.cmake_args }}
|
||||
cmake_target: ${{ matrix.cmake_target }}
|
||||
runs_on: ${{ toJSON(matrix.architecture.runner) }}
|
||||
image: ${{ matrix.image || '' }}
|
||||
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) || '' }}
|
||||
config_name: ${{ matrix.config_name }}
|
||||
sanitizers: ${{ matrix.sanitizers }}
|
||||
compiler: ${{ matrix.compiler || '' }}
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Check levelization
|
||||
run: python .github/scripts/levelization/generate.py
|
||||
- name: Check for differences
|
||||
|
||||
2
.github/workflows/reusable-check-rename.yml
vendored
2
.github/workflows/reusable-check-rename.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Check definitions
|
||||
run: .github/scripts/rename/definitions.sh .
|
||||
- name: Check copyright notices
|
||||
|
||||
9
.github/workflows/reusable-clang-tidy.yml
vendored
9
.github/workflows/reusable-clang-tidy.yml
vendored
@@ -36,13 +36,13 @@ jobs:
|
||||
needs: [determine-files]
|
||||
if: ${{ always() && !cancelled() && (!inputs.check_only_changed || needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.clang_tidy_config_changed == 'true') }}
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-8abe82e"
|
||||
container: "ghcr.io/xrplf/ci/debian-trixie:clang-21-sha-53033a2"
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab
|
||||
@@ -56,11 +56,6 @@ jobs:
|
||||
uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf
|
||||
id: nproc
|
||||
|
||||
- name: Set compiler environment
|
||||
uses: ./.github/actions/set-compiler-env
|
||||
with:
|
||||
compiler: clang
|
||||
|
||||
- name: Setup Conan
|
||||
uses: ./.github/actions/setup-conan
|
||||
|
||||
|
||||
45
.github/workflows/reusable-package.yml
vendored
45
.github/workflows/reusable-package.yml
vendored
@@ -1,7 +1,8 @@
|
||||
# Build Linux packages (DEB and RPM) from pre-built binary artifacts.
|
||||
# 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.
|
||||
# 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.
|
||||
name: Package
|
||||
|
||||
on:
|
||||
@@ -27,17 +28,18 @@ jobs:
|
||||
matrix: ${{ steps.generate.outputs.matrix }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- 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 >>"${GITHUB_OUTPUT}"
|
||||
run: |
|
||||
./generate.py --packaging --config=linux.json >>"${GITHUB_OUTPUT}"
|
||||
|
||||
generate-version:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -45,7 +47,7 @@ jobs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
sparse-checkout: |
|
||||
.github/actions/generate-version
|
||||
@@ -64,37 +66,12 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: ${{ matrix.image }}
|
||||
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) }}
|
||||
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Download pre-built binary
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
|
||||
17
.github/workflows/reusable-strategy-matrix.yml
vendored
17
.github/workflows/reusable-strategy-matrix.yml
vendored
@@ -4,9 +4,15 @@ on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
os:
|
||||
description: 'The operating system to use for the build ("linux", "macos", "windows", or empty for all).'
|
||||
description: 'The operating system to use for the build ("linux", "macos", "windows").'
|
||||
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."
|
||||
@@ -23,16 +29,17 @@ jobs:
|
||||
matrix: ${{ steps.generate.outputs.matrix }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- 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}', inputs.os) || '' }}
|
||||
run: ./generate.py ${GENERATE_CONFIG} >>"${GITHUB_OUTPUT}"
|
||||
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}"
|
||||
|
||||
4
.github/workflows/reusable-upload-recipe.yml
vendored
4
.github/workflows/reusable-upload-recipe.yml
vendored
@@ -40,10 +40,10 @@ defaults:
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-8abe82e
|
||||
container: ghcr.io/xrplf/ci/ubuntu-noble:gcc-13-sha-5dd7158
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Generate build version number
|
||||
id: version
|
||||
|
||||
13
.github/workflows/upload-conan-deps.yml
vendored
13
.github/workflows/upload-conan-deps.yml
vendored
@@ -48,6 +48,8 @@ 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:
|
||||
@@ -56,15 +58,16 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
|
||||
max-parallel: 10
|
||||
runs-on: ${{ matrix.architecture.runner }}
|
||||
container: ${{ matrix.image || null }}
|
||||
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 }}
|
||||
steps:
|
||||
- name: Cleanup workspace (macOS and Windows)
|
||||
if: ${{ runner.os == 'macOS' || runner.os == 'Windows' }}
|
||||
uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab
|
||||
@@ -80,12 +83,6 @@ 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 }}
|
||||
|
||||
22
cmake/XrplAddTest.cmake
Normal file
22
cmake/XrplAddTest.cmake
Normal file
@@ -0,0 +1,22 @@
|
||||
include(isolate_headers)
|
||||
|
||||
function(xrpl_add_test name)
|
||||
set(target ${PROJECT_NAME}.test.${name})
|
||||
|
||||
file(
|
||||
GLOB_RECURSE sources
|
||||
CONFIGURE_DEPENDS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/${name}/*.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/${name}.cpp"
|
||||
)
|
||||
add_executable(${target} ${ARGN} ${sources})
|
||||
|
||||
isolate_headers(
|
||||
${target}
|
||||
"${CMAKE_SOURCE_DIR}"
|
||||
"${CMAKE_SOURCE_DIR}/tests/${name}"
|
||||
PRIVATE
|
||||
)
|
||||
|
||||
add_test(NAME ${target} COMMAND ${target})
|
||||
endfunction()
|
||||
@@ -145,39 +145,13 @@ else()
|
||||
INTERFACE
|
||||
-rdynamic
|
||||
$<$<BOOL:${is_linux}>:-Wl,-z,relro,-z,now,--build-id>
|
||||
# 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}>>>:
|
||||
# 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}>>>:
|
||||
-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.
|
||||
|
||||
@@ -47,7 +47,7 @@ setup_target_for_coverage_gcovr(
|
||||
"include/xrpl/beast/test"
|
||||
"include/xrpl/beast/unit_test"
|
||||
"${CMAKE_BINARY_DIR}/pb-xrpl.libpb"
|
||||
DEPENDENCIES xrpld xrpl_tests
|
||||
DEPENDENCIES xrpld xrpl.tests
|
||||
)
|
||||
|
||||
add_code_coverage_to_target(opts INTERFACE)
|
||||
|
||||
@@ -1,8 +1 @@
|
||||
{% 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 %}
|
||||
|
||||
@@ -50,7 +50,6 @@ words:
|
||||
- AMMXRP
|
||||
- amt
|
||||
- amts
|
||||
- archs
|
||||
- asnode
|
||||
- asynchrony
|
||||
- attestation
|
||||
@@ -135,7 +134,6 @@ words:
|
||||
- iou
|
||||
- ious
|
||||
- isrdc
|
||||
- isystem
|
||||
- itype
|
||||
- jemalloc
|
||||
- jlog
|
||||
|
||||
@@ -32,7 +32,7 @@ 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 echo "${BASE_IMAGE}" | grep -qiE 'nixos'; then \
|
||||
RUN if [ -d /nix ]; then \
|
||||
ln -s /root/.nix-profile/bin/bash /bin/bash; \
|
||||
fi
|
||||
|
||||
@@ -47,16 +47,10 @@ COPY --from=builder /tmp/build/result /nix/ci-env
|
||||
|
||||
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. Install it
|
||||
# from the Nix store when the base image doesn't already provide one.
|
||||
COPY nix/docker/loader-path.sh /tmp/loader-path.sh
|
||||
COPY docker/loader-path.sh /tmp/loader-path.sh
|
||||
|
||||
RUN <<EOF
|
||||
target="$(/tmp/loader-path.sh)"
|
||||
@@ -71,44 +65,38 @@ if [ ! -e "${target}" ]; then
|
||||
fi
|
||||
EOF
|
||||
|
||||
COPY nix/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 nix/docker/test_files/cpp_sources/ /tmp/cpp_sources/
|
||||
COPY nix/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 nix/docker/install-sanitizer-libs.sh /tmp/install-sanitizer-libs.sh
|
||||
COPY nix/docker/test_files/run-test-binaries.sh /tmp/run-test-binaries.sh
|
||||
COPY --from=final /tmp/bins /tmp/bins
|
||||
|
||||
RUN <<EOF
|
||||
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
|
||||
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
|
||||
EOF
|
||||
|
||||
# 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
|
||||
# Sanity-check that the sanitizer runtimes shipped with g++/clang++ are able to build binaries
|
||||
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
|
||||
|
||||
# Sanity-check that the built binaries are able to run.
|
||||
# We only support running the test binaries on Ubuntu and NixOS right now (will be fixed in the future)
|
||||
#
|
||||
# When build and test images will be separate, we will be to run on vanilla images.
|
||||
COPY docker/test_files/run-test-binaries.sh /tmp/run-test-binaries.sh
|
||||
RUN if echo "${BASE_IMAGE}" | grep -qiE '(ubuntu|nixos)'; then \
|
||||
/tmp/run-test-binaries.sh /tmp/bins; \
|
||||
fi
|
||||
@@ -20,21 +20,14 @@ function compile() {
|
||||
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 \
|
||||
echo "=== Compile ${name} with ${compiler} ==="
|
||||
cmd="${compiler} -std=c++23 -O1 -g \
|
||||
-pthread \
|
||||
-static-libstdc++ \
|
||||
-Wl,--dynamic-linker=${loader} \
|
||||
${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}"
|
||||
echo "Command: ${cmd}"
|
||||
eval "${cmd}"
|
||||
}
|
||||
|
||||
declare -A sanitize=(
|
||||
@@ -2,13 +2,6 @@
|
||||
#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)
|
||||
@@ -7,8 +7,6 @@ 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() {
|
||||
@@ -20,34 +18,27 @@ function run() {
|
||||
out_file="$(mktemp)"
|
||||
|
||||
echo "=== Run ${binary} ==="
|
||||
set +e
|
||||
"${binary}" >"${out_file}" 2>&1
|
||||
local rc=$?
|
||||
set -e
|
||||
local rc=0
|
||||
"${binary}" >"${out_file}" 2>&1 || rc=$?
|
||||
|
||||
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
|
||||
exit 1
|
||||
fi
|
||||
elif [ "${rc}" -ne "${expected_rc}" ]; then
|
||||
echo "ERROR: expected exit code ${expected_rc} from ${binary}, got ${rc}" >&2
|
||||
failed=1
|
||||
exit 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
|
||||
grep -q "${expected_output}" "${out_file}" ||
|
||||
{
|
||||
echo "ERROR: expected '${expected_output}' from ${binary}" >&2
|
||||
exit 1
|
||||
}
|
||||
echo "OK: '${expected_output}' detected"
|
||||
}
|
||||
|
||||
declare -A expect=(
|
||||
@@ -61,15 +52,6 @@ declare -A expect=(
|
||||
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
|
||||
@@ -78,9 +60,3 @@ for compiler in g++ clang++; do
|
||||
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
6
flake.lock
generated
@@ -2,11 +2,11 @@
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1780243769,
|
||||
"narHash": "sha256-x5UQuRsH3MqI0U9afaXSNqzTPSeZlRLvFAav2Ux1pNw=",
|
||||
"lastModified": 1777954456,
|
||||
"narHash": "sha256-hGdgeU2Nk87RAuZyYjyDjFL6LK7dAZN5RE9+hrDTkDU=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "331800de5053fcebacf6813adb5db9c9dca22a0c",
|
||||
"rev": "549bd84d6279f9852cae6225e372cc67fb91a4c1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -95,7 +95,13 @@ strUnHex(std::size_t strSize, Iterator begin, Iterator end)
|
||||
}
|
||||
|
||||
inline std::optional<Blob>
|
||||
strUnHex(std::string_view strSrc)
|
||||
strUnHex(std::string const& strSrc)
|
||||
{
|
||||
return strUnHex(strSrc.size(), strSrc.cbegin(), strSrc.cend());
|
||||
}
|
||||
|
||||
inline std::optional<Blob>
|
||||
strViewUnHex(std::string_view strSrc)
|
||||
{
|
||||
return strUnHex(strSrc.size(), strSrc.cbegin(), strSrc.cend());
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ std::string
|
||||
base64Encode(std::uint8_t const* data, std::size_t len);
|
||||
|
||||
inline std::string
|
||||
base64Encode(std::string_view s)
|
||||
base64Encode(std::string const& s)
|
||||
{
|
||||
return base64Encode(reinterpret_cast<std::uint8_t const*>(s.data()), s.size());
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
template <class Stream, class Iter>
|
||||
Stream&
|
||||
join(Stream& s, Iter iter, Iter end, std::string_view delimiter)
|
||||
join(Stream& s, Iter iter, Iter end, std::string const& delimiter)
|
||||
{
|
||||
if (iter == end)
|
||||
return s;
|
||||
|
||||
@@ -29,7 +29,7 @@ public:
|
||||
factory function in the Collector interface.
|
||||
@see Collector.
|
||||
*/
|
||||
explicit Counter(std::shared_ptr<CounterImpl> impl) : impl_(std::move(impl))
|
||||
explicit Counter(std::shared_ptr<CounterImpl> const& impl) : impl_(impl)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ public:
|
||||
factory function in the Collector interface.
|
||||
@see Collector.
|
||||
*/
|
||||
explicit Event(std::shared_ptr<EventImpl> impl) : impl_(std::move(impl))
|
||||
explicit Event(std::shared_ptr<EventImpl> const& impl) : impl_(impl)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ public:
|
||||
factory function in the Collector interface.
|
||||
@see Collector.
|
||||
*/
|
||||
explicit Gauge(std::shared_ptr<GaugeImpl> impl) : impl_(std::move(impl))
|
||||
explicit Gauge(std::shared_ptr<GaugeImpl> const& impl) : impl_(impl)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ public:
|
||||
factory function in the Collector interface.
|
||||
@see Collector.
|
||||
*/
|
||||
explicit Hook(std::shared_ptr<HookImpl> impl) : impl_(std::move(impl))
|
||||
explicit Hook(std::shared_ptr<HookImpl> const& impl) : impl_(impl)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ public:
|
||||
factory function in the Collector interface.
|
||||
@see Collector.
|
||||
*/
|
||||
explicit Meter(std::shared_ptr<MeterImpl> impl) : impl_(std::move(impl))
|
||||
explicit Meter(std::shared_ptr<MeterImpl> const& impl) : impl_(impl)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ private:
|
||||
|
||||
public:
|
||||
template <class = void>
|
||||
explicit Selector(ModeT mode, std::string pattern = "");
|
||||
explicit Selector(ModeT mode, std::string const& pattern = "");
|
||||
|
||||
template <class = void>
|
||||
bool
|
||||
@@ -51,7 +51,7 @@ public:
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <class>
|
||||
Selector::Selector(ModeT mode, std::string pattern) : mode_(mode), pat_(std::move(pattern))
|
||||
Selector::Selector(ModeT mode, std::string const& pattern) : mode_(mode), pat_(pattern)
|
||||
{
|
||||
if (mode_ == ModeT::Automatch && pattern.empty())
|
||||
mode_ = ModeT::All;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -35,7 +34,7 @@ private:
|
||||
static void
|
||||
standard(std::string& strWord);
|
||||
static int
|
||||
wsrch(std::string_view strWord, int iMin, int iMax);
|
||||
wsrch(std::string const& strWord, int iMin, int iMax);
|
||||
static int
|
||||
etob(std::string& strData, std::vector<std::string> vsHuman);
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
insert(std::shared_ptr<STTx const> txn);
|
||||
insert(std::shared_ptr<STTx const> const& txn);
|
||||
|
||||
// Pops the next transaction on account that follows seqProx in the
|
||||
// sort order. Normally called when a transaction is successfully
|
||||
|
||||
@@ -200,7 +200,7 @@ getAMMOfferStartWithTakerGets(
|
||||
|
||||
auto getAmounts = [&pool, &tfee](Number const& nTakerGetsProposed) {
|
||||
// Round downward to minimize the offer and to maximize the quality.
|
||||
// This has the most impact when takerGets is XRP.
|
||||
// This has the most impact when takerGets is integral.
|
||||
auto const takerGets =
|
||||
toAmount<TOut>(getAsset(pool.out), nTakerGetsProposed, Number::RoundingMode::Downward);
|
||||
return TAmounts<TIn, TOut>{swapAssetOut(pool, takerGets, tfee), takerGets};
|
||||
@@ -215,7 +215,7 @@ getAMMOfferStartWithTakerGets(
|
||||
}
|
||||
|
||||
/** Generate AMM offer starting with takerPays when AMM pool
|
||||
* from the payment perspective is XRP(in)/IOU(out) or IOU(in)/IOU(out).
|
||||
* from the payment perspective has a fractional output asset.
|
||||
* Equations:
|
||||
* Spot Price Quality after the offer is consumed:
|
||||
* Qsp = (O - o) / (I + i) -- equation (1)
|
||||
@@ -267,7 +267,7 @@ getAMMOfferStartWithTakerPays(
|
||||
|
||||
auto getAmounts = [&pool, &tfee](Number const& nTakerPaysProposed) {
|
||||
// Round downward to minimize the offer and to maximize the quality.
|
||||
// This has the most impact when takerPays is XRP.
|
||||
// This has the most impact when takerPays is integral.
|
||||
auto const takerPays =
|
||||
toAmount<TIn>(getAsset(pool.in), nTakerPaysProposed, Number::RoundingMode::Downward);
|
||||
return TAmounts<TIn, TOut>{takerPays, swapAssetIn(pool, takerPays, tfee)};
|
||||
@@ -285,11 +285,11 @@ getAMMOfferStartWithTakerPays(
|
||||
* is equal to LOB quality (in this case AMM offer quality is
|
||||
* better than LOB quality) or AMM offer is equal to LOB quality
|
||||
* (in this case SPQ is better than LOB quality).
|
||||
* Pre-amendment code calculates takerPays first. If takerGets is XRP,
|
||||
* it is rounded down, which results in worse offer quality than
|
||||
* LOB quality, and the offer might fail to generate.
|
||||
* Post-amendment code calculates the XRP offer side first. The result
|
||||
* is rounded down, which makes the offer quality better.
|
||||
* Pre-amendment code calculates takerPays first. If takerGets is the
|
||||
* economically coarser integral side, it is rounded down, which results in
|
||||
* worse offer quality than LOB quality, and the offer might fail to generate.
|
||||
* Post-amendment code calculates the economically coarser integral offer side
|
||||
* first. The result is rounded down, which makes the offer quality better.
|
||||
* It might not be possible to match either SPQ or AMM offer to LOB
|
||||
* quality. This generally happens at higher fees.
|
||||
* @param pool AMM pool balances
|
||||
@@ -368,10 +368,18 @@ changeSpotPriceQuality(
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Generate the offer starting with XRP side. Return seated offer amounts
|
||||
// if the offer can be generated, otherwise nullopt.
|
||||
auto amounts = [&]() {
|
||||
if (isXRP(getAsset(pool.out)))
|
||||
bool const inIntegral = getAsset(pool.in).integral();
|
||||
bool const outIntegral = getAsset(pool.out).integral();
|
||||
|
||||
// Preserve historical behavior for fractional pairs and XRP/IOU-style
|
||||
// one-integral-side pairs. For two integral assets, pick the side whose
|
||||
// minimum unit is economically coarser at this quality.
|
||||
//
|
||||
// Quality::rate() is input units per output unit, so one output unit is
|
||||
// coarser when it costs at least one input unit. Ties use takerGets,
|
||||
// matching the historical XRP-output behavior.
|
||||
if (outIntegral && (!inIntegral || Number(quality.rate()) >= 1))
|
||||
return getAMMOfferStartWithTakerGets(pool, quality, tfee);
|
||||
return getAMMOfferStartWithTakerPays(pool, quality, tfee);
|
||||
}();
|
||||
|
||||
@@ -230,6 +230,14 @@ createMPToken(
|
||||
AccountID const& account,
|
||||
std::uint32_t const flags);
|
||||
|
||||
TER
|
||||
checkCreateMPT(
|
||||
xrpl::ApplyView& view,
|
||||
xrpl::MPTIssue const& mptIssue,
|
||||
xrpl::AccountID const& holder,
|
||||
std::uint32_t flags,
|
||||
beast::Journal j);
|
||||
|
||||
TER
|
||||
checkCreateMPT(
|
||||
xrpl::ApplyView& view,
|
||||
|
||||
@@ -53,7 +53,7 @@ public:
|
||||
boost::system::error_code const& ecResult,
|
||||
int iStatus,
|
||||
std::string const& strData)> complete,
|
||||
beast::Journal const& j);
|
||||
beast::Journal& j);
|
||||
|
||||
static void
|
||||
get(bool bSSL,
|
||||
@@ -67,7 +67,7 @@ public:
|
||||
boost::system::error_code const& ecResult,
|
||||
int iStatus,
|
||||
std::string const& strData)> complete,
|
||||
beast::Journal const& j);
|
||||
beast::Journal& j);
|
||||
|
||||
static void
|
||||
request(
|
||||
@@ -82,7 +82,7 @@ public:
|
||||
boost::system::error_code const& ecResult,
|
||||
int iStatus,
|
||||
std::string const& strData)> complete,
|
||||
beast::Journal const& j);
|
||||
beast::Journal& j);
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -25,7 +25,7 @@ struct varint_traits<T, true>
|
||||
{
|
||||
explicit varint_traits() = default;
|
||||
|
||||
static constexpr std::size_t kMax = ((8 * sizeof(T)) + 6) / 7;
|
||||
static constexpr std::size_t kMax = (8 * sizeof(T) + 6) / 7;
|
||||
};
|
||||
|
||||
// Returns: Number of bytes consumed or 0 on error,
|
||||
|
||||
@@ -144,7 +144,7 @@ T
|
||||
toAmount(Asset const& asset, Number const& n, Number::RoundingMode mode = Number::getround())
|
||||
{
|
||||
SaveNumberRoundMode const rm(Number::getround());
|
||||
if (isXRP(asset))
|
||||
if (asset.integral())
|
||||
Number::setround(mode);
|
||||
|
||||
if constexpr (std::is_same_v<IOUAmount, T>)
|
||||
|
||||
@@ -51,6 +51,14 @@ public:
|
||||
std::optional<Number>
|
||||
outFromAvgQ(Quality const& quality);
|
||||
|
||||
/** Return whether `out` produces at least the requested
|
||||
* average quality.
|
||||
* @param quality requested average quality (quality limit)
|
||||
* @param out output amount to test
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
satisfiesAvgQ(Quality const& quality, Number const& out) const;
|
||||
|
||||
/** Return true if the quality function is constant
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
|
||||
@@ -17,8 +17,8 @@ public:
|
||||
STVector256() = default;
|
||||
|
||||
explicit STVector256(SField const& n);
|
||||
explicit STVector256(std::vector<uint256> vector);
|
||||
STVector256(SField const& n, std::vector<uint256> vector);
|
||||
explicit STVector256(std::vector<uint256> const& vector);
|
||||
STVector256(SField const& n, std::vector<uint256> const& vector);
|
||||
STVector256(SerialIter& sit, SField const& name);
|
||||
|
||||
[[nodiscard]] SerializedTypeID
|
||||
@@ -103,12 +103,12 @@ inline STVector256::STVector256(SField const& n) : STBase(n)
|
||||
{
|
||||
}
|
||||
|
||||
inline STVector256::STVector256(std::vector<uint256> vector) : value_(std::move(vector))
|
||||
inline STVector256::STVector256(std::vector<uint256> const& vector) : value_(vector)
|
||||
{
|
||||
}
|
||||
|
||||
inline STVector256::STVector256(SField const& n, std::vector<uint256> vector)
|
||||
: STBase(n), value_(std::move(vector))
|
||||
inline STVector256::STVector256(SField const& n, std::vector<uint256> const& vector)
|
||||
: STBase(n), value_(vector)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -54,10 +53,10 @@ public:
|
||||
/** Send a copy of data asynchronously. */
|
||||
/** @{ */
|
||||
void
|
||||
write(std::string_view s)
|
||||
write(std::string const& s)
|
||||
{
|
||||
if (!s.empty())
|
||||
write(s.data(), s.size());
|
||||
write(&s[0], std::distance(s.begin(), s.end()));
|
||||
}
|
||||
|
||||
template <typename BufferSequence>
|
||||
|
||||
@@ -85,7 +85,7 @@ private:
|
||||
/** The sequence of the ledger that this map references, if any. */
|
||||
std::uint32_t ledgerSeq_ = 0;
|
||||
|
||||
SHAMapTreeNodePtr root_;
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode> root_;
|
||||
mutable SHAMapState state_;
|
||||
SHAMapType const type_;
|
||||
bool backed_ = true; // Map is backed by the database
|
||||
@@ -161,7 +161,7 @@ public:
|
||||
setLedgerSeq(std::uint32_t lseq);
|
||||
|
||||
bool
|
||||
fetchRoot(SHAMapHash const& hash, SHAMapSyncFilter const* filter);
|
||||
fetchRoot(SHAMapHash const& hash, SHAMapSyncFilter* filter);
|
||||
|
||||
// normal hash access functions
|
||||
|
||||
@@ -248,7 +248,7 @@ public:
|
||||
@param return The nodes known to be missing
|
||||
*/
|
||||
std::vector<std::pair<SHAMapNodeID, uint256>>
|
||||
getMissingNodes(int maxNodes, SHAMapSyncFilter const* filter);
|
||||
getMissingNodes(int maxNodes, SHAMapSyncFilter* filter);
|
||||
|
||||
bool
|
||||
getNodeFat(
|
||||
@@ -281,9 +281,9 @@ public:
|
||||
serializeRoot(Serializer& s) const;
|
||||
|
||||
SHAMapAddNode
|
||||
addRootNode(SHAMapHash const& hash, Slice const& rootNode, SHAMapSyncFilter const* filter);
|
||||
addRootNode(SHAMapHash const& hash, Slice const& rootNode, SHAMapSyncFilter* filter);
|
||||
SHAMapAddNode
|
||||
addKnownNode(SHAMapNodeID const& nodeID, Slice const& rawNode, SHAMapSyncFilter const* filter);
|
||||
addKnownNode(SHAMapNodeID const& nodeID, Slice const& rawNode, SHAMapSyncFilter* filter);
|
||||
|
||||
// status functions
|
||||
void
|
||||
@@ -326,32 +326,36 @@ public:
|
||||
invariants() const;
|
||||
|
||||
private:
|
||||
using SharedPtrNodeStack = std::stack<std::pair<SHAMapTreeNodePtr, SHAMapNodeID>>;
|
||||
using SharedPtrNodeStack =
|
||||
std::stack<std::pair<intr_ptr::SharedPtr<SHAMapTreeNode>, SHAMapNodeID>>;
|
||||
using DeltaRef =
|
||||
std::pair<boost::intrusive_ptr<SHAMapItem const>, boost::intrusive_ptr<SHAMapItem const>>;
|
||||
|
||||
// tree node cache operations
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
cacheLookup(SHAMapHash const& hash) const;
|
||||
|
||||
void
|
||||
canonicalize(SHAMapHash const& hash, SHAMapTreeNodePtr&) const;
|
||||
canonicalize(SHAMapHash const& hash, intr_ptr::SharedPtr<SHAMapTreeNode>&) const;
|
||||
|
||||
// database operations
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
fetchNodeFromDB(SHAMapHash const& hash) const;
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
fetchNodeNT(SHAMapHash const& hash) const;
|
||||
SHAMapTreeNodePtr
|
||||
fetchNodeNT(SHAMapHash const& hash, SHAMapSyncFilter const* filter) const;
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
fetchNodeNT(SHAMapHash const& hash, SHAMapSyncFilter* filter) const;
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
fetchNode(SHAMapHash const& hash) const;
|
||||
SHAMapTreeNodePtr
|
||||
checkFilter(SHAMapHash const& hash, SHAMapSyncFilter const* filter) const;
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
checkFilter(SHAMapHash const& hash, SHAMapSyncFilter* filter) const;
|
||||
|
||||
/** Update hashes up to the root */
|
||||
void
|
||||
dirtyUp(SharedPtrNodeStack& stack, uint256 const& target, SHAMapTreeNodePtr terminal);
|
||||
dirtyUp(
|
||||
SharedPtrNodeStack& stack,
|
||||
uint256 const& target,
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode> 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() ==
|
||||
@@ -373,21 +377,25 @@ private:
|
||||
preFlushNode(intr_ptr::SharedPtr<Node> node) const;
|
||||
|
||||
/** write and canonicalize modified node */
|
||||
SHAMapTreeNodePtr
|
||||
writeNode(NodeObjectType t, SHAMapTreeNodePtr node) const;
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
writeNode(NodeObjectType t, intr_ptr::SharedPtr<SHAMapTreeNode> node) const;
|
||||
|
||||
// returns the first item at or below this node
|
||||
SHAMapLeafNode*
|
||||
firstBelow(SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, int branch = 0) const;
|
||||
firstBelow(intr_ptr::SharedPtr<SHAMapTreeNode>, SharedPtrNodeStack& stack, int branch = 0)
|
||||
const;
|
||||
|
||||
// returns the last item at or below this node
|
||||
SHAMapLeafNode*
|
||||
lastBelow(SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, int branch = kBranchFactor) const;
|
||||
lastBelow(
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode> node,
|
||||
SharedPtrNodeStack& stack,
|
||||
int branch = kBranchFactor) const;
|
||||
|
||||
// helper function for firstBelow and lastBelow
|
||||
SHAMapLeafNode*
|
||||
belowHelper(
|
||||
SHAMapTreeNodePtr node,
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode> node,
|
||||
SharedPtrNodeStack& stack,
|
||||
int branch,
|
||||
std::tuple<int, std::function<bool(int)>, std::function<void(int&)>> const& loopParams)
|
||||
@@ -399,19 +407,20 @@ private:
|
||||
descend(SHAMapInnerNode*, int branch) const;
|
||||
SHAMapTreeNode*
|
||||
descendThrow(SHAMapInnerNode*, int branch) const;
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
descend(SHAMapInnerNode&, int branch) const;
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
descendThrow(SHAMapInnerNode&, int branch) const;
|
||||
|
||||
// Descend with filter
|
||||
// If pending, callback is called as if it called fetchNodeNT
|
||||
using descendCallback = std::function<void(SHAMapTreeNodePtr, SHAMapHash const&)>;
|
||||
using descendCallback =
|
||||
std::function<void(intr_ptr::SharedPtr<SHAMapTreeNode>, SHAMapHash const&)>;
|
||||
SHAMapTreeNode*
|
||||
descendAsync(
|
||||
SHAMapInnerNode* parent,
|
||||
int branch,
|
||||
SHAMapSyncFilter const* filter,
|
||||
SHAMapSyncFilter* filter,
|
||||
bool& pending,
|
||||
descendCallback&&) const;
|
||||
|
||||
@@ -420,11 +429,11 @@ private:
|
||||
SHAMapInnerNode* parent,
|
||||
SHAMapNodeID const& parentID,
|
||||
int branch,
|
||||
SHAMapSyncFilter const* filter) const;
|
||||
SHAMapSyncFilter* filter) const;
|
||||
|
||||
// Non-storing
|
||||
// Does not hook the returned node to its parent
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
descendNoStore(SHAMapInnerNode&, int branch) const;
|
||||
|
||||
/** If there is only one leaf below this node, get its contents */
|
||||
@@ -461,7 +470,7 @@ private:
|
||||
|
||||
// basic parameters
|
||||
int max;
|
||||
SHAMapSyncFilter const* filter;
|
||||
SHAMapSyncFilter* filter;
|
||||
int const maxDefer;
|
||||
std::uint32_t generation;
|
||||
|
||||
@@ -486,10 +495,10 @@ private:
|
||||
|
||||
// nodes we may have acquired from deferred reads
|
||||
using DeferredNode = std::tuple<
|
||||
SHAMapInnerNode*, // parent node
|
||||
SHAMapNodeID, // parent node ID
|
||||
int, // branch
|
||||
SHAMapTreeNodePtr>; // node
|
||||
SHAMapInnerNode*, // parent node
|
||||
SHAMapNodeID, // parent node ID
|
||||
int, // branch
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>>; // node
|
||||
|
||||
int deferred;
|
||||
std::mutex deferLock;
|
||||
@@ -500,11 +509,7 @@ private:
|
||||
// reads
|
||||
std::map<SHAMapInnerNode*, SHAMapNodeID> resumes;
|
||||
|
||||
MissingNodes(
|
||||
int max,
|
||||
SHAMapSyncFilter const* filter,
|
||||
int maxDefer,
|
||||
std::uint32_t generation)
|
||||
MissingNodes(int max, SHAMapSyncFilter* filter, int maxDefer, std::uint32_t generation)
|
||||
: max(max), filter(filter), maxDefer(maxDefer), generation(generation), deferred(0)
|
||||
{
|
||||
missingNodes.reserve(max);
|
||||
@@ -519,7 +524,7 @@ private:
|
||||
gmnProcessDeferredReads(MissingNodes&);
|
||||
|
||||
// fetch from DB helper function
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
finishFetch(SHAMapHash const& hash, std::shared_ptr<NodeObject> const& object) const;
|
||||
};
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
clone(std::uint32_t cowid) const final
|
||||
{
|
||||
return intr_ptr::makeShared<SHAMapAccountStateLeafNode>(item_, cowid, hash_);
|
||||
|
||||
@@ -87,7 +87,7 @@ public:
|
||||
void
|
||||
partialDestructor() override;
|
||||
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
clone(std::uint32_t cowid) const override;
|
||||
|
||||
SHAMapNodeType
|
||||
@@ -121,19 +121,19 @@ public:
|
||||
getChildHash(int m) const;
|
||||
|
||||
void
|
||||
setChild(int m, SHAMapTreeNodePtr child);
|
||||
setChild(int m, intr_ptr::SharedPtr<SHAMapTreeNode> child);
|
||||
|
||||
void
|
||||
shareChild(int m, SHAMapTreeNodePtr const& child);
|
||||
shareChild(int m, intr_ptr::SharedPtr<SHAMapTreeNode> const& child);
|
||||
|
||||
SHAMapTreeNode*
|
||||
getChildPointer(int branch);
|
||||
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
getChild(int branch);
|
||||
|
||||
SHAMapTreeNodePtr
|
||||
canonicalizeChild(int branch, SHAMapTreeNodePtr node);
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
canonicalizeChild(int branch, intr_ptr::SharedPtr<SHAMapTreeNode> node);
|
||||
|
||||
// sync functions
|
||||
bool
|
||||
@@ -161,10 +161,10 @@ public:
|
||||
void
|
||||
invariants(bool isRoot = false) const override;
|
||||
|
||||
static SHAMapTreeNodePtr
|
||||
static intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
makeFullInner(Slice data, SHAMapHash const& hash, bool hashValid);
|
||||
|
||||
static SHAMapTreeNodePtr
|
||||
static intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
makeCompressedInner(Slice data);
|
||||
};
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ operator<<(std::ostream& out, SHAMapNodeID const& node)
|
||||
deserializeSHAMapNodeID(void const* data, std::size_t size);
|
||||
|
||||
[[nodiscard]] inline std::optional<SHAMapNodeID>
|
||||
deserializeSHAMapNodeID(std::string_view s)
|
||||
deserializeSHAMapNodeID(std::string const& s)
|
||||
{
|
||||
return deserializeSHAMapNodeID(s.data(), s.size());
|
||||
}
|
||||
|
||||
@@ -13,9 +13,6 @@
|
||||
|
||||
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;
|
||||
@@ -115,7 +112,7 @@ public:
|
||||
}
|
||||
|
||||
/** Make a copy of this node, setting the owner. */
|
||||
virtual SHAMapTreeNodePtr
|
||||
virtual intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
clone(std::uint32_t cowid) const = 0;
|
||||
/** @} */
|
||||
|
||||
@@ -156,20 +153,20 @@ public:
|
||||
virtual void
|
||||
invariants(bool isRoot = false) const = 0;
|
||||
|
||||
static SHAMapTreeNodePtr
|
||||
static intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
makeFromPrefix(Slice rawNode, SHAMapHash const& hash);
|
||||
|
||||
static SHAMapTreeNodePtr
|
||||
static intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
makeFromWire(Slice rawNode);
|
||||
|
||||
private:
|
||||
static SHAMapTreeNodePtr
|
||||
static intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
makeTransaction(Slice data, SHAMapHash const& hash, bool hashValid);
|
||||
|
||||
static SHAMapTreeNodePtr
|
||||
static intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
makeAccountState(Slice data, SHAMapHash const& hash, bool hashValid);
|
||||
|
||||
static SHAMapTreeNodePtr
|
||||
static intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
makeTransactionWithMeta(Slice data, SHAMapHash const& hash, bool hashValid);
|
||||
};
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
clone(std::uint32_t cowid) const final
|
||||
{
|
||||
return intr_ptr::makeShared<SHAMapTxLeafNode>(item_, cowid, hash_);
|
||||
|
||||
@@ -27,7 +27,7 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
clone(std::uint32_t cowid) const override
|
||||
{
|
||||
return intr_ptr::makeShared<SHAMapTxPlusMetaLeafNode>(item_, cowid, hash_);
|
||||
|
||||
@@ -11,5 +11,5 @@ using TreeNodeCache = TaggedCache<
|
||||
SHAMapTreeNode,
|
||||
/*IsKeyCache*/ false,
|
||||
intr_ptr::SharedWeakUnionPtr<SHAMapTreeNode>,
|
||||
SHAMapTreeNodePtr>;
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>>;
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -148,7 +148,7 @@ public:
|
||||
/** Get the number of elements in each array and a pointer to the start
|
||||
of each array.
|
||||
*/
|
||||
[[nodiscard]] std::tuple<std::uint8_t, SHAMapHash*, SHAMapTreeNodePtr*>
|
||||
[[nodiscard]] std::tuple<std::uint8_t, SHAMapHash*, intr_ptr::SharedPtr<SHAMapTreeNode>*>
|
||||
getHashesAndChildren() const;
|
||||
|
||||
/** Get the `hashes` array */
|
||||
@@ -156,7 +156,7 @@ public:
|
||||
getHashes() const;
|
||||
|
||||
/** Get the `children` array */
|
||||
[[nodiscard]] SHAMapTreeNodePtr*
|
||||
[[nodiscard]] intr_ptr::SharedPtr<SHAMapTreeNode>*
|
||||
getChildren() const;
|
||||
|
||||
/** Call the `f` callback for all 16 (branchFactor) branches - even if
|
||||
|
||||
@@ -26,7 +26,8 @@ static_assert(
|
||||
// Terminology: A chunk is the memory being allocated from a block. A block
|
||||
// contains multiple chunks. This is the terminology the boost documentation
|
||||
// uses. Pools use "Simple Segregated Storage" as their storage format.
|
||||
constexpr size_t kElementSizeBytes = sizeof(SHAMapHash) + sizeof(SHAMapTreeNodePtr);
|
||||
constexpr size_t kElementSizeBytes =
|
||||
(sizeof(SHAMapHash) + sizeof(intr_ptr::SharedPtr<SHAMapTreeNode>));
|
||||
|
||||
constexpr size_t kBlockSizeBytes = kilobytes(512);
|
||||
|
||||
@@ -363,7 +364,8 @@ inline TaggedPointer::TaggedPointer(
|
||||
// keep
|
||||
new (&dstHashes[dstIndex]) SHAMapHash{srcHashes[srcIndex]};
|
||||
|
||||
new (&dstChildren[dstIndex]) SHAMapTreeNodePtr{std::move(srcChildren[srcIndex])};
|
||||
new (&dstChildren[dstIndex])
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>{std::move(srcChildren[srcIndex])};
|
||||
++dstIndex;
|
||||
++srcIndex;
|
||||
}
|
||||
@@ -374,7 +376,7 @@ inline TaggedPointer::TaggedPointer(
|
||||
if (dstIsDense)
|
||||
{
|
||||
new (&dstHashes[dstIndex]) SHAMapHash{};
|
||||
new (&dstChildren[dstIndex]) SHAMapTreeNodePtr{};
|
||||
new (&dstChildren[dstIndex]) intr_ptr::SharedPtr<SHAMapTreeNode>{};
|
||||
++dstIndex;
|
||||
}
|
||||
}
|
||||
@@ -382,7 +384,7 @@ inline TaggedPointer::TaggedPointer(
|
||||
{
|
||||
// add
|
||||
new (&dstHashes[dstIndex]) SHAMapHash{};
|
||||
new (&dstChildren[dstIndex]) SHAMapTreeNodePtr{};
|
||||
new (&dstChildren[dstIndex]) intr_ptr::SharedPtr<SHAMapTreeNode>{};
|
||||
++dstIndex;
|
||||
if (srcIsDense)
|
||||
{
|
||||
@@ -395,7 +397,7 @@ inline TaggedPointer::TaggedPointer(
|
||||
if (dstIsDense)
|
||||
{
|
||||
new (&dstHashes[dstIndex]) SHAMapHash{};
|
||||
new (&dstChildren[dstIndex]) SHAMapTreeNodePtr{};
|
||||
new (&dstChildren[dstIndex]) intr_ptr::SharedPtr<SHAMapTreeNode>{};
|
||||
++dstIndex;
|
||||
}
|
||||
if (srcIsDense)
|
||||
@@ -412,7 +414,7 @@ inline TaggedPointer::TaggedPointer(
|
||||
for (int i = dstIndex; i < dstNumAllocated; ++i)
|
||||
{
|
||||
new (&dstHashes[i]) SHAMapHash{};
|
||||
new (&dstChildren[i]) SHAMapTreeNodePtr{};
|
||||
new (&dstChildren[i]) intr_ptr::SharedPtr<SHAMapTreeNode>{};
|
||||
}
|
||||
*this = std::move(dst);
|
||||
}
|
||||
@@ -431,10 +433,8 @@ inline TaggedPointer::TaggedPointer(
|
||||
|
||||
// allocate hashes and children, but do not run constructors
|
||||
TaggedPointer newHashesAndChildren{RawAllocateTag{}, toAllocate};
|
||||
SHAMapHash* newHashes = nullptr;
|
||||
SHAMapHash* oldHashes = nullptr;
|
||||
SHAMapTreeNodePtr* newChildren = nullptr;
|
||||
SHAMapTreeNodePtr* oldChildren = nullptr;
|
||||
SHAMapHash *newHashes = nullptr, *oldHashes = nullptr;
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>*newChildren = nullptr, *oldChildren = nullptr;
|
||||
std::uint8_t newNumAllocated = 0;
|
||||
// structured bindings can't be captured in c++ 17; use tie instead
|
||||
std::tie(newNumAllocated, newHashes, newChildren) = newHashesAndChildren.getHashesAndChildren();
|
||||
@@ -445,7 +445,8 @@ inline TaggedPointer::TaggedPointer(
|
||||
// new arrays are dense, old arrays are sparse
|
||||
iterNonEmptyChildIndexes(isBranch, [&](auto branchNum, auto indexNum) {
|
||||
new (&newHashes[branchNum]) SHAMapHash{oldHashes[indexNum]};
|
||||
new (&newChildren[branchNum]) SHAMapTreeNodePtr{std::move(oldChildren[indexNum])};
|
||||
new (&newChildren[branchNum])
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>{std::move(oldChildren[indexNum])};
|
||||
});
|
||||
// Run the constructors for the remaining elements
|
||||
for (int i = 0; i < SHAMapInnerNode::kBranchFactor; ++i)
|
||||
@@ -453,7 +454,7 @@ inline TaggedPointer::TaggedPointer(
|
||||
if (((1 << i) & isBranch) != 0)
|
||||
continue;
|
||||
new (&newHashes[i]) SHAMapHash{};
|
||||
new (&newChildren[i]) SHAMapTreeNodePtr{};
|
||||
new (&newChildren[i]) intr_ptr::SharedPtr<SHAMapTreeNode>{};
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -463,14 +464,14 @@ inline TaggedPointer::TaggedPointer(
|
||||
iterNonEmptyChildIndexes(isBranch, [&](auto branchNum, auto indexNum) {
|
||||
new (&newHashes[curCompressedIndex]) SHAMapHash{oldHashes[indexNum]};
|
||||
new (&newChildren[curCompressedIndex])
|
||||
SHAMapTreeNodePtr{std::move(oldChildren[indexNum])};
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>{std::move(oldChildren[indexNum])};
|
||||
++curCompressedIndex;
|
||||
});
|
||||
// Run the constructors for the remaining elements
|
||||
for (int i = curCompressedIndex; i < newNumAllocated; ++i)
|
||||
{
|
||||
new (&newHashes[i]) SHAMapHash{};
|
||||
new (&newChildren[i]) SHAMapTreeNodePtr{};
|
||||
new (&newChildren[i]) intr_ptr::SharedPtr<SHAMapTreeNode>{};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,7 +485,7 @@ inline TaggedPointer::TaggedPointer(std::uint8_t numChildren)
|
||||
for (std::size_t i = 0; i < numAllocated; ++i)
|
||||
{
|
||||
new (&hashes[i]) SHAMapHash{};
|
||||
new (&children[i]) SHAMapTreeNodePtr{};
|
||||
new (&children[i]) intr_ptr::SharedPtr<SHAMapTreeNode>{};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,13 +523,14 @@ TaggedPointer::isDense() const
|
||||
return (tp_ & kTagMask) == kBoundaries.size() - 1;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline std::tuple<std::uint8_t, SHAMapHash*, SHAMapTreeNodePtr*>
|
||||
[[nodiscard]] inline std::tuple<std::uint8_t, SHAMapHash*, intr_ptr::SharedPtr<SHAMapTreeNode>*>
|
||||
TaggedPointer::getHashesAndChildren() const
|
||||
{
|
||||
auto const [tag, ptr] = decode();
|
||||
auto const hashes = reinterpret_cast<SHAMapHash*>(ptr);
|
||||
std::uint8_t const numAllocated = kBoundaries[tag];
|
||||
auto const children = reinterpret_cast<SHAMapTreeNodePtr*>(hashes + numAllocated);
|
||||
auto const children =
|
||||
reinterpret_cast<intr_ptr::SharedPtr<SHAMapTreeNode>*>(hashes + numAllocated);
|
||||
return {numAllocated, hashes, children};
|
||||
};
|
||||
|
||||
@@ -538,7 +540,7 @@ TaggedPointer::getHashes() const
|
||||
return reinterpret_cast<SHAMapHash*>(tp_ & kPtrMask);
|
||||
};
|
||||
|
||||
[[nodiscard]] inline SHAMapTreeNodePtr*
|
||||
[[nodiscard]] inline intr_ptr::SharedPtr<SHAMapTreeNode>*
|
||||
TaggedPointer::getChildren() const
|
||||
{
|
||||
auto [unused1, unused2, result] = getHashesAndChildren();
|
||||
|
||||
@@ -352,7 +352,7 @@ qualityUpperBound(ReadView const& v, Strand const& strand)
|
||||
* increases quality of AMM steps, increasing the strand's composite
|
||||
* quality as the result.
|
||||
*/
|
||||
template <typename TOutAmt>
|
||||
template <StepAmount TOutAmt>
|
||||
inline TOutAmt
|
||||
limitOut(
|
||||
ReadView const& v,
|
||||
@@ -390,21 +390,29 @@ limitOut(
|
||||
auto const out = qf->outFromAvgQ(limitQuality);
|
||||
if (!out)
|
||||
return remainingOut;
|
||||
if constexpr (std::is_same_v<TOutAmt, XRPAmount>)
|
||||
if constexpr (std::is_same_v<TOutAmt, XRPAmount> || std::is_same_v<TOutAmt, MPTAmount>)
|
||||
{
|
||||
return XRPAmount{*out};
|
||||
auto const roundedOut = TOutAmt{*out};
|
||||
// Integral outputs that round above the continuous target can
|
||||
// realize worse average quality than the requested limit. Keep the
|
||||
// default rounded value when it still satisfies the limit, since it
|
||||
// is the largest matching offer; otherwise round down.
|
||||
if (v.rules().enabled(featureMPTokensV2) && roundedOut > *out &&
|
||||
!qf->satisfiesAvgQ(limitQuality, roundedOut))
|
||||
{
|
||||
NumberRoundModeGuard const g(Number::RoundingMode::Downward);
|
||||
return TOutAmt{*out};
|
||||
}
|
||||
return roundedOut;
|
||||
}
|
||||
else if constexpr (std::is_same_v<TOutAmt, IOUAmount>)
|
||||
{
|
||||
return IOUAmount{*out};
|
||||
}
|
||||
else if constexpr (std::is_same_v<TOutAmt, MPTAmount>)
|
||||
{
|
||||
return MPTAmount{*out};
|
||||
}
|
||||
else
|
||||
{
|
||||
return STAmount{remainingOut.asset(), out->mantissa(), out->exponent()};
|
||||
static constexpr bool kAlwaysFalse = !std::is_same_v<TOutAmt, TOutAmt>;
|
||||
static_assert(kAlwaysFalse, "Unhandled StepAmount type");
|
||||
}
|
||||
}();
|
||||
// A tiny difference could be due to the round off
|
||||
|
||||
@@ -43,15 +43,6 @@ let
|
||||
bintools = customBinutils;
|
||||
};
|
||||
|
||||
# gcov ships in gcc's `cc` output, but the cc-wrapper doesn't expose it.
|
||||
# Surface the gcov from our rebuilt gcc (linked against the custom glibc, so
|
||||
# it runs under the loader installed in the image) and matching the exact
|
||||
# compiler version, so gcovr can produce coverage reports in the CI env.
|
||||
customGcov = pkgs.runCommand "gcov-custom-for-ci-env" { } ''
|
||||
mkdir -p "$out/bin"
|
||||
ln -s "${customGccCc}/bin/gcov" "$out/bin/gcov"
|
||||
'';
|
||||
|
||||
# stdenv built around the rebuilt gcc / custom glibc. Used to rebuild
|
||||
# compiler-rt below so its sanitizer runtimes see the custom glibc
|
||||
# headers.
|
||||
@@ -94,14 +85,6 @@ let
|
||||
ln -s "${customCompilerRt.out}/lib" "$rsrc/lib"
|
||||
ln -s "${customCompilerRt.out}/share" "$rsrc/share" || true
|
||||
echo "-resource-dir=$rsrc" >> $out/nix-support/cc-cflags
|
||||
# compiler-rt ships the sanitizer/profile/xray interface headers (e.g.
|
||||
# <sanitizer/lsan_interface.h>) in its `dev` output. In a normal Nix
|
||||
# build these reach the include path because compiler-rt is propagated
|
||||
# via depsTargetTargetPropagated and stdenv's setup hooks add its
|
||||
# dev/include. The CI image runs clang outside a Nix stdenv (binaries
|
||||
# on PATH, no setup hooks), so that never happens; add the headers
|
||||
# explicitly. gcc ships its own copy, which is why this is clang-only.
|
||||
echo "-isystem ${customCompilerRt.dev}/include" >> $out/nix-support/cc-cflags
|
||||
'';
|
||||
};
|
||||
|
||||
@@ -122,16 +105,11 @@ in
|
||||
name = "xrpld-ci-env";
|
||||
paths = commonPackages ++ [
|
||||
customGcc
|
||||
customGcov
|
||||
customClangForCiEnv
|
||||
customBinutils
|
||||
# CA certificate bundle so HTTPS clients (git, curl, conan) can verify
|
||||
# TLS connections without ca-certificates being installed in the system.
|
||||
pkgs.cacert
|
||||
];
|
||||
pathsToLink = [
|
||||
"/bin"
|
||||
"/etc/ssl/certs"
|
||||
"/lib"
|
||||
"/include"
|
||||
"/share"
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/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}"
|
||||
@@ -1,113 +0,0 @@
|
||||
#!/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."
|
||||
@@ -11,16 +11,11 @@ in
|
||||
ccache
|
||||
cmake
|
||||
conan
|
||||
curlMinimal # needed for codecov/codecov-action
|
||||
doxygen
|
||||
gcovr
|
||||
git
|
||||
gnumake
|
||||
gnupg # needed for signing commits & codecov/codecov-action
|
||||
llvmPackages_22.clang-tools
|
||||
less # needed for git diff
|
||||
mold
|
||||
nettools # provides netstat, used to debug failures in CI
|
||||
ninja
|
||||
patchelf
|
||||
perl # needed for openssl
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
ARG BASE_IMAGE=debian:bookworm
|
||||
|
||||
FROM ${BASE_IMAGE}
|
||||
|
||||
# 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.
|
||||
# The container also uses 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.
|
||||
|
||||
COPY package/install-packaging-tools.sh /tmp/install-packaging-tools.sh
|
||||
|
||||
RUN /tmp/install-packaging-tools.sh
|
||||
@@ -22,24 +22,25 @@ package/
|
||||
|
||||
Packaging targets and their container images are declared in
|
||||
[`.github/scripts/strategy-matrix/linux.json`](../.github/scripts/strategy-matrix/linux.json)
|
||||
inside `package_configs` configurations. Today only
|
||||
`linux/amd64` is emitted. The package format
|
||||
via a `"package": true` field on specific os entries. Today only
|
||||
`linux/amd64` is emitted; the architecture is hardcoded in `generate.py`
|
||||
and the workflow runner. The package format
|
||||
(deb or rpm) is inferred at build time from the container's package manager
|
||||
(`apt-get` -> deb, `dnf`/`yum` -> rpm). The image tag is composed as
|
||||
`ghcr.io/xrplf/xrpld/packaging-<distro>:sha-<git_sha>` —
|
||||
`ghcr.io/xrplf/ci/{distro}-{version}:{compiler}-{cver}-sha-{image_sha}` —
|
||||
the same scheme used by `reusable-build-test.yml`. Bump `image_sha` in
|
||||
`linux.json` and both CI and local builds pick up the new image with no
|
||||
workflow edits.
|
||||
|
||||
| Package type | Image (derived from `linux.json`) | Tool required |
|
||||
| ------------ | ---------------------------------------------------- | --------------------------------------------------------------- |
|
||||
| RPM | `ghcr.io/xrplf/xrpld/packaging-rhel:sha-<git_sha>` | `rpmbuild` |
|
||||
| DEB | `ghcr.io/xrplf/xrpld/packaging-debian:sha-<git_sha>` | `dpkg-buildpackage`, `debhelper (>= 13)`, `dh-sequence-systemd` |
|
||||
| RPM | `ghcr.io/xrplf/ci/rhel-9:gcc-12-sha-<git_sha>` | `rpmbuild` |
|
||||
| DEB | `ghcr.io/xrplf/ci/ubuntu-jammy:gcc-12-sha-<git_sha>` | `dpkg-buildpackage`, `debhelper (>= 13)`, `dh-sequence-systemd` |
|
||||
|
||||
To print the exact image tags for the current `linux.json`:
|
||||
|
||||
```bash
|
||||
./.github/scripts/strategy-matrix/generate.py --packaging
|
||||
./.github/scripts/strategy-matrix/generate.py --packaging --config=.github/scripts/strategy-matrix/linux.json
|
||||
```
|
||||
|
||||
## Building packages
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
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 install() {
|
||||
case "${ID}" in
|
||||
debian | ubuntu)
|
||||
apt-get update -y
|
||||
apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
debhelper \
|
||||
debhelper-compat \
|
||||
dpkg-dev \
|
||||
git
|
||||
;;
|
||||
|
||||
rhel | centos | rocky | almalinux)
|
||||
dnf install -y --setopt=install_weak_deps=False \
|
||||
git \
|
||||
rpm-build \
|
||||
redhat-rpm-config \
|
||||
systemd-rpm-macros
|
||||
;;
|
||||
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
|
||||
}
|
||||
|
||||
install
|
||||
postinstall
|
||||
@@ -61,8 +61,7 @@ Logs::File::open(boost::filesystem::path const& path)
|
||||
bool wasOpened = false;
|
||||
|
||||
// VFALCO TODO Make this work with Unicode file paths
|
||||
std::unique_ptr<std::ofstream> stream =
|
||||
std::make_unique<std::ofstream>(path.c_str(), std::fstream::app);
|
||||
std::unique_ptr<std::ofstream> stream(new std::ofstream(path.c_str(), std::fstream::app));
|
||||
|
||||
if (stream->good())
|
||||
{
|
||||
|
||||
@@ -1241,11 +1241,9 @@ root(Number f, unsigned d)
|
||||
}
|
||||
|
||||
// Quadratic least squares curve fit of f^(1/d) in the range [0, 1]
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
auto const D = (((((6 * di) + 11) * di) + 6) * di) + 1;
|
||||
auto const a0 = 3 * di * ((((2 * di) - 3) * di) + 1);
|
||||
auto const a1 = 24 * di * ((2 * di) - 1);
|
||||
auto const D = (((6 * di + 11) * di + 6) * di) + 1; // NOLINT(readability-identifier-naming)
|
||||
auto const a0 = 3 * di * ((2 * di - 3) * di + 1);
|
||||
auto const a1 = 24 * di * (2 * di - 1);
|
||||
auto const a2 = -30 * (di - 1) * di;
|
||||
Number r = ((Number{a2} * f + Number{a1}) * f + Number{a0}) / Number{D};
|
||||
if (neg)
|
||||
|
||||
@@ -71,6 +71,7 @@ setCurrentThreadNameImpl(std::string_view name)
|
||||
#if BOOST_OS_LINUX
|
||||
#include <pthread.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <iostream> // IWYU pragma: keep
|
||||
|
||||
namespace beast::detail {
|
||||
|
||||
@@ -66,7 +66,7 @@ public:
|
||||
class StatsDHookImpl : public HookImpl, public StatsDMetricBase
|
||||
{
|
||||
public:
|
||||
StatsDHookImpl(HandlerType handler, std::shared_ptr<StatsDCollectorImp> impl);
|
||||
StatsDHookImpl(HandlerType handler, std::shared_ptr<StatsDCollectorImp> const& impl);
|
||||
|
||||
~StatsDHookImpl() override;
|
||||
|
||||
@@ -86,7 +86,7 @@ private:
|
||||
class StatsDCounterImpl : public CounterImpl, public StatsDMetricBase
|
||||
{
|
||||
public:
|
||||
StatsDCounterImpl(std::string name, std::shared_ptr<StatsDCollectorImp> impl);
|
||||
StatsDCounterImpl(std::string name, std::shared_ptr<StatsDCollectorImp> const& impl);
|
||||
|
||||
~StatsDCounterImpl() override;
|
||||
|
||||
@@ -115,7 +115,7 @@ private:
|
||||
class StatsDEventImpl : public EventImpl
|
||||
{
|
||||
public:
|
||||
StatsDEventImpl(std::string name, std::shared_ptr<StatsDCollectorImp> impl);
|
||||
StatsDEventImpl(std::string name, std::shared_ptr<StatsDCollectorImp> const& impl);
|
||||
|
||||
~StatsDEventImpl() override = default;
|
||||
|
||||
@@ -140,7 +140,7 @@ private:
|
||||
class StatsDGaugeImpl : public GaugeImpl, public StatsDMetricBase
|
||||
{
|
||||
public:
|
||||
StatsDGaugeImpl(std::string name, std::shared_ptr<StatsDCollectorImp> impl);
|
||||
StatsDGaugeImpl(std::string name, std::shared_ptr<StatsDCollectorImp> const& impl);
|
||||
|
||||
~StatsDGaugeImpl() override;
|
||||
|
||||
@@ -174,7 +174,7 @@ private:
|
||||
class StatsDMeterImpl : public MeterImpl, public StatsDMetricBase
|
||||
{
|
||||
public:
|
||||
explicit StatsDMeterImpl(std::string name, std::shared_ptr<StatsDCollectorImp> impl);
|
||||
explicit StatsDMeterImpl(std::string name, std::shared_ptr<StatsDCollectorImp> const& impl);
|
||||
|
||||
~StatsDMeterImpl() override;
|
||||
|
||||
@@ -478,8 +478,8 @@ public:
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
StatsDHookImpl::StatsDHookImpl(HandlerType handler, std::shared_ptr<StatsDCollectorImp> impl)
|
||||
: impl_(std::move(impl)), handler_(std::move(handler))
|
||||
StatsDHookImpl::StatsDHookImpl(HandlerType handler, std::shared_ptr<StatsDCollectorImp> const& impl)
|
||||
: impl_(impl), handler_(std::move(handler))
|
||||
{
|
||||
impl_->add(*this);
|
||||
}
|
||||
@@ -497,8 +497,10 @@ StatsDHookImpl::doProcess()
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
StatsDCounterImpl::StatsDCounterImpl(std::string name, std::shared_ptr<StatsDCollectorImp> impl)
|
||||
: impl_(std::move(impl)), name_(std::move(name))
|
||||
StatsDCounterImpl::StatsDCounterImpl(
|
||||
std::string name,
|
||||
std::shared_ptr<StatsDCollectorImp> const& impl)
|
||||
: impl_(impl), name_(std::move(name))
|
||||
{
|
||||
impl_->add(*this);
|
||||
}
|
||||
@@ -548,8 +550,8 @@ StatsDCounterImpl::doProcess()
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
StatsDEventImpl::StatsDEventImpl(std::string name, std::shared_ptr<StatsDCollectorImp> impl)
|
||||
: impl_(std::move(impl)), name_(std::move(name))
|
||||
StatsDEventImpl::StatsDEventImpl(std::string name, std::shared_ptr<StatsDCollectorImp> const& impl)
|
||||
: impl_(impl), name_(std::move(name))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -575,8 +577,8 @@ StatsDEventImpl::doNotify(EventImpl::value_type const& value)
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
StatsDGaugeImpl::StatsDGaugeImpl(std::string name, std::shared_ptr<StatsDCollectorImp> impl)
|
||||
: impl_(std::move(impl)), name_(std::move(name))
|
||||
StatsDGaugeImpl::StatsDGaugeImpl(std::string name, std::shared_ptr<StatsDCollectorImp> const& impl)
|
||||
: impl_(impl), name_(std::move(name))
|
||||
{
|
||||
impl_->add(*this);
|
||||
}
|
||||
@@ -662,8 +664,8 @@ StatsDGaugeImpl::doProcess()
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
StatsDMeterImpl::StatsDMeterImpl(std::string name, std::shared_ptr<StatsDCollectorImp> impl)
|
||||
: impl_(std::move(impl)), name_(std::move(name))
|
||||
StatsDMeterImpl::StatsDMeterImpl(std::string name, std::shared_ptr<StatsDCollectorImp> const& impl)
|
||||
: impl_(impl), name_(std::move(name))
|
||||
{
|
||||
impl_->add(*this);
|
||||
}
|
||||
|
||||
@@ -306,7 +306,7 @@ RFC1751::standard(std::string& strWord)
|
||||
|
||||
// Binary search of dictionary.
|
||||
int
|
||||
RFC1751::wsrch(std::string_view strWord, int iMin, int iMax)
|
||||
RFC1751::wsrch(std::string const& strWord, int iMin, int iMax)
|
||||
{
|
||||
int iResult = -1;
|
||||
|
||||
|
||||
@@ -40,11 +40,14 @@ CanonicalTXSet::accountKey(AccountID const& account)
|
||||
}
|
||||
|
||||
void
|
||||
CanonicalTXSet::insert(std::shared_ptr<STTx const> txn)
|
||||
CanonicalTXSet::insert(std::shared_ptr<STTx const> const& txn)
|
||||
{
|
||||
Key const key(
|
||||
accountKey(txn->getAccountID(sfAccount)), txn->getSeqProxy(), txn->getTransactionID());
|
||||
map_.insert(std::make_pair(key, std::move(txn)));
|
||||
map_.insert(
|
||||
std::make_pair(
|
||||
Key(accountKey(txn->getAccountID(sfAccount)),
|
||||
txn->getSeqProxy(),
|
||||
txn->getTransactionID()),
|
||||
txn));
|
||||
}
|
||||
|
||||
std::shared_ptr<STTx const>
|
||||
|
||||
@@ -922,6 +922,7 @@ checkCreateMPT(
|
||||
xrpl::ApplyView& view,
|
||||
xrpl::MPTIssue const& mptIssue,
|
||||
xrpl::AccountID const& holder,
|
||||
std::uint32_t flags,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (mptIssue.getIssuer() == holder)
|
||||
@@ -931,7 +932,7 @@ checkCreateMPT(
|
||||
auto const mptokenID = keylet::mptoken(mptIssuanceID.key, holder);
|
||||
if (!view.exists(mptokenID))
|
||||
{
|
||||
if (auto const err = createMPToken(view, mptIssue.getMptID(), holder, 0);
|
||||
if (auto const err = createMPToken(view, mptIssue.getMptID(), holder, flags);
|
||||
!isTesSuccess(err))
|
||||
{
|
||||
return err;
|
||||
@@ -946,6 +947,16 @@ checkCreateMPT(
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
checkCreateMPT(
|
||||
xrpl::ApplyView& view,
|
||||
xrpl::MPTIssue const& mptIssue,
|
||||
xrpl::AccountID const& holder,
|
||||
beast::Journal j)
|
||||
{
|
||||
return checkCreateMPT(view, mptIssue, holder, 0, j);
|
||||
}
|
||||
|
||||
std::int64_t
|
||||
maxMPTAmount(SLE const& sleIssuance)
|
||||
{
|
||||
|
||||
@@ -64,7 +64,7 @@ public:
|
||||
boost::asio::io_context& ioContext,
|
||||
unsigned short const port,
|
||||
std::size_t maxResponseSize,
|
||||
beast::Journal const& j)
|
||||
beast::Journal& j)
|
||||
: socket_(
|
||||
ioContext,
|
||||
gHttpClientSslContext->context()) // NOLINT(bugprone-unchecked-optional-access)
|
||||
@@ -552,7 +552,7 @@ HTTPClient::get(
|
||||
std::function<
|
||||
bool(boost::system::error_code const& ecResult, int iStatus, std::string const& strData)>
|
||||
complete,
|
||||
beast::Journal const& j)
|
||||
beast::Journal& j)
|
||||
{
|
||||
auto client = std::make_shared<HTTPClientImp>(ioContext, port, responseMax, j);
|
||||
client->get(bSSL, deqSites, strPath, timeout, complete);
|
||||
@@ -570,7 +570,7 @@ HTTPClient::get(
|
||||
std::function<
|
||||
bool(boost::system::error_code const& ecResult, int iStatus, std::string const& strData)>
|
||||
complete,
|
||||
beast::Journal const& j)
|
||||
beast::Journal& j)
|
||||
{
|
||||
std::deque<std::string> const deqSites(1, strSite);
|
||||
|
||||
@@ -590,7 +590,7 @@ HTTPClient::request(
|
||||
std::function<
|
||||
bool(boost::system::error_code const& ecResult, int iStatus, std::string const& strData)>
|
||||
complete,
|
||||
beast::Journal const& j)
|
||||
beast::Journal& j)
|
||||
{
|
||||
std::deque<std::string> const deqSites(1, strSite);
|
||||
|
||||
|
||||
@@ -41,4 +41,12 @@ QualityFunction::outFromAvgQ(Quality const& quality)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool
|
||||
QualityFunction::satisfiesAvgQ(Quality const& quality, Number const& out) const
|
||||
{
|
||||
if (quality.rate() == beast::kZero)
|
||||
return false;
|
||||
return m_ * out + b_ >= 1 / quality.rate();
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -1575,6 +1575,45 @@ public:
|
||||
operator=(DontAffectNumberRoundMode const&) = delete;
|
||||
};
|
||||
|
||||
Number::RoundingMode
|
||||
roundMode(bool const resultNegative, bool const roundUp)
|
||||
{
|
||||
using enum Number::RoundingMode;
|
||||
// STAmount roundUp means "away from zero". The legacy scaled-mantissa
|
||||
// multiply and divide paths reach that result with slightly different
|
||||
// mechanics, including a final TowardsZero materialization in multiply.
|
||||
//
|
||||
// The MPT/V2 Number path already performs the operation under the directed
|
||||
// mode below. Use the same mode again when converting back to STAmount so a
|
||||
// fractional integral result stays consistently rounded after Number
|
||||
// arithmetic, independent of whether the operation was multiply or divide.
|
||||
return roundUp ^ resultNegative ? Upward : Downward;
|
||||
}
|
||||
|
||||
STAmount
|
||||
roundNumberResult(
|
||||
Asset const& asset,
|
||||
bool const resultNegative,
|
||||
bool const roundUp,
|
||||
Number const& number)
|
||||
{
|
||||
// MPT/V2 Number arithmetic uses directed rounding both for the operation
|
||||
// and for materializing the final integral amount.
|
||||
NumberRoundModeGuard const finalRound(roundMode(resultNegative, roundUp));
|
||||
auto result = STAmount{asset, number};
|
||||
|
||||
if (roundUp && !resultNegative && !result)
|
||||
{
|
||||
// Preserve existing mulRound/divRound behavior for a positive result
|
||||
// too small to represent in the target asset.
|
||||
if (asset.integral())
|
||||
return STAmount{asset, 1};
|
||||
return STAmount{asset, STAmount::kMinValue, STAmount::kMinOffset, false};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// Pass the canonicalizeRound function pointer as a template parameter.
|
||||
@@ -1616,6 +1655,23 @@ mulRoundImpl(STAmount const& v1, STAmount const& v2, Asset const& asset, bool ro
|
||||
return STAmount(asset, minV * maxV);
|
||||
}
|
||||
|
||||
bool const resultNegative = v1.negative() != v2.negative();
|
||||
|
||||
if (asset.holds<MPTIssue>() && isFeatureEnabled(featureMPTokensV2, false) &&
|
||||
getSTNumberSwitchover())
|
||||
{
|
||||
// MPT DEX can combine 63-bit MPT amounts with IOU-shaped transfer
|
||||
// rates. Use Number arithmetic under MPTokensV2 so the rounded
|
||||
// operation is not limited by the legacy uint64_t scaled mantissa.
|
||||
Number result;
|
||||
{
|
||||
NumberRoundModeGuard const operationRound(roundMode(resultNegative, roundUp));
|
||||
result = Number{v1} * Number{v2};
|
||||
}
|
||||
|
||||
return roundNumberResult(asset, resultNegative, roundUp, result);
|
||||
}
|
||||
|
||||
std::uint64_t value1 = v1.mantissa(), value2 = v2.mantissa();
|
||||
int offset1 = v1.exponent(), offset2 = v2.exponent();
|
||||
|
||||
@@ -1636,9 +1692,6 @@ mulRoundImpl(STAmount const& v1, STAmount const& v2, Asset const& asset, bool ro
|
||||
--offset2;
|
||||
}
|
||||
}
|
||||
|
||||
bool const resultNegative = v1.negative() != v2.negative();
|
||||
|
||||
// We multiply the two mantissas (each is between 10^15
|
||||
// and 10^16), so their product is in the 10^30 to 10^32
|
||||
// range. Dividing their product by 10^14 maintains the
|
||||
@@ -1705,6 +1758,23 @@ divRoundImpl(STAmount const& num, STAmount const& den, Asset const& asset, bool
|
||||
if (num == beast::kZero)
|
||||
return {asset};
|
||||
|
||||
bool const resultNegative = (num.negative() != den.negative());
|
||||
|
||||
if (asset.holds<MPTIssue>() && isFeatureEnabled(featureMPTokensV2, false) &&
|
||||
getSTNumberSwitchover())
|
||||
{
|
||||
// Match the multiply path above: Number performs the rounded
|
||||
// operation, then STAmount materializes the final MPT amount using the
|
||||
// same final rounding mode as the legacy path below.
|
||||
Number result;
|
||||
{
|
||||
NumberRoundModeGuard const operationRound(roundMode(resultNegative, roundUp));
|
||||
result = Number{num} / Number{den};
|
||||
}
|
||||
|
||||
return roundNumberResult(asset, resultNegative, roundUp, result);
|
||||
}
|
||||
|
||||
std::uint64_t numVal = num.mantissa(), denVal = den.mantissa();
|
||||
int numOffset = num.exponent(), denOffset = den.exponent();
|
||||
|
||||
@@ -1726,8 +1796,6 @@ divRoundImpl(STAmount const& num, STAmount const& den, Asset const& asset, bool
|
||||
}
|
||||
}
|
||||
|
||||
bool const resultNegative = (num.negative() != den.negative());
|
||||
|
||||
// We divide the two mantissas (each is between 10^15
|
||||
// and 10^16). To maintain precision, we multiply the
|
||||
// numerator by 10^17 (the product is in the range of
|
||||
|
||||
@@ -26,8 +26,8 @@ namespace xrpl {
|
||||
bool
|
||||
Port::secure() const
|
||||
{
|
||||
return protocol.contains("peer") || protocol.contains("https") || protocol.contains("wss") ||
|
||||
protocol.contains("wss2");
|
||||
return protocol.count("peer") > 0 || protocol.count("https") > 0 || protocol.count("wss") > 0 ||
|
||||
protocol.count("wss2") > 0;
|
||||
}
|
||||
|
||||
std::string
|
||||
|
||||
@@ -97,7 +97,10 @@ SHAMap::snapShot(bool isMutable) const
|
||||
}
|
||||
|
||||
void
|
||||
SHAMap::dirtyUp(SharedPtrNodeStack& stack, uint256 const& target, SHAMapTreeNodePtr child)
|
||||
SHAMap::dirtyUp(
|
||||
SharedPtrNodeStack& stack,
|
||||
uint256 const& target,
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode> child)
|
||||
{
|
||||
// walk the tree up from through the inner nodes to the root_
|
||||
// update hashes and links
|
||||
@@ -162,7 +165,7 @@ SHAMap::findKey(uint256 const& id) const
|
||||
return leaf;
|
||||
}
|
||||
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMap::fetchNodeFromDB(SHAMapHash const& hash) const
|
||||
{
|
||||
XRPL_ASSERT(backed_, "xrpl::SHAMap::fetchNodeFromDB : is backed");
|
||||
@@ -170,7 +173,7 @@ SHAMap::fetchNodeFromDB(SHAMapHash const& hash) const
|
||||
return finishFetch(hash, obj);
|
||||
}
|
||||
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMap::finishFetch(SHAMapHash const& hash, std::shared_ptr<NodeObject> const& object) const
|
||||
{
|
||||
XRPL_ASSERT(backed_, "xrpl::SHAMap::finishFetch : is backed");
|
||||
@@ -205,8 +208,8 @@ SHAMap::finishFetch(SHAMapHash const& hash, std::shared_ptr<NodeObject> const& o
|
||||
}
|
||||
|
||||
// See if a sync filter has a node
|
||||
SHAMapTreeNodePtr
|
||||
SHAMap::checkFilter(SHAMapHash const& hash, SHAMapSyncFilter const* filter) const
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMap::checkFilter(SHAMapHash const& hash, SHAMapSyncFilter* filter) const
|
||||
{
|
||||
if (auto nodeData = filter->getNode(hash))
|
||||
{
|
||||
@@ -231,8 +234,8 @@ SHAMap::checkFilter(SHAMapHash const& hash, SHAMapSyncFilter const* filter) cons
|
||||
|
||||
// Get a node without throwing
|
||||
// Used on maps where missing nodes are expected
|
||||
SHAMapTreeNodePtr
|
||||
SHAMap::fetchNodeNT(SHAMapHash const& hash, SHAMapSyncFilter const* filter) const
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMap::fetchNodeNT(SHAMapHash const& hash, SHAMapSyncFilter* filter) const
|
||||
{
|
||||
auto node = cacheLookup(hash);
|
||||
if (node)
|
||||
@@ -254,7 +257,7 @@ SHAMap::fetchNodeNT(SHAMapHash const& hash, SHAMapSyncFilter const* filter) cons
|
||||
return node;
|
||||
}
|
||||
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMap::fetchNodeNT(SHAMapHash const& hash) const
|
||||
{
|
||||
auto node = cacheLookup(hash);
|
||||
@@ -266,7 +269,7 @@ SHAMap::fetchNodeNT(SHAMapHash const& hash) const
|
||||
}
|
||||
|
||||
// Throw if the node is missing
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMap::fetchNode(SHAMapHash const& hash) const
|
||||
{
|
||||
auto node = fetchNodeNT(hash);
|
||||
@@ -288,10 +291,10 @@ SHAMap::descendThrow(SHAMapInnerNode* parent, int branch) const
|
||||
return ret;
|
||||
}
|
||||
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMap::descendThrow(SHAMapInnerNode& parent, int branch) const
|
||||
{
|
||||
SHAMapTreeNodePtr ret = descend(parent, branch);
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode> ret = descend(parent, branch);
|
||||
|
||||
if (!ret && !parent.isEmptyBranch(branch))
|
||||
Throw<SHAMapMissingNode>(type_, parent.getChildHash(branch));
|
||||
@@ -306,7 +309,7 @@ SHAMap::descend(SHAMapInnerNode* parent, int branch) const
|
||||
if ((ret != nullptr) || !backed_)
|
||||
return ret;
|
||||
|
||||
SHAMapTreeNodePtr node = fetchNodeNT(parent->getChildHash(branch));
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode> node = fetchNodeNT(parent->getChildHash(branch));
|
||||
if (!node)
|
||||
return nullptr;
|
||||
|
||||
@@ -314,10 +317,10 @@ SHAMap::descend(SHAMapInnerNode* parent, int branch) const
|
||||
return node.get();
|
||||
}
|
||||
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMap::descend(SHAMapInnerNode& parent, int branch) const
|
||||
{
|
||||
SHAMapTreeNodePtr node = parent.getChild(branch);
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode> node = parent.getChild(branch);
|
||||
if (node || !backed_)
|
||||
return node;
|
||||
|
||||
@@ -331,10 +334,10 @@ SHAMap::descend(SHAMapInnerNode& parent, int branch) const
|
||||
|
||||
// Gets the node that would be hooked to this branch,
|
||||
// but doesn't hook it up.
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMap::descendNoStore(SHAMapInnerNode& parent, int branch) const
|
||||
{
|
||||
SHAMapTreeNodePtr ret = parent.getChild(branch);
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode> ret = parent.getChild(branch);
|
||||
if (!ret && backed_)
|
||||
ret = fetchNode(parent.getChildHash(branch));
|
||||
return ret;
|
||||
@@ -345,7 +348,7 @@ SHAMap::descend(
|
||||
SHAMapInnerNode* parent,
|
||||
SHAMapNodeID const& parentID,
|
||||
int branch,
|
||||
SHAMapSyncFilter const* filter) const
|
||||
SHAMapSyncFilter* filter) const
|
||||
{
|
||||
XRPL_ASSERT(parent->isInner(), "xrpl::SHAMap::descend : valid parent input");
|
||||
XRPL_ASSERT(
|
||||
@@ -358,7 +361,7 @@ SHAMap::descend(
|
||||
if (child == nullptr)
|
||||
{
|
||||
auto const& childHash = parent->getChildHash(branch);
|
||||
SHAMapTreeNodePtr childNode = fetchNodeNT(childHash, filter);
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode> childNode = fetchNodeNT(childHash, filter);
|
||||
|
||||
if (childNode)
|
||||
{
|
||||
@@ -374,7 +377,7 @@ SHAMapTreeNode*
|
||||
SHAMap::descendAsync(
|
||||
SHAMapInnerNode* parent,
|
||||
int branch,
|
||||
SHAMapSyncFilter const* filter,
|
||||
SHAMapSyncFilter* filter,
|
||||
bool& pending,
|
||||
descendCallback&& callback) const
|
||||
{
|
||||
@@ -431,7 +434,7 @@ SHAMap::unshareNode(intr_ptr::SharedPtr<Node> node, SHAMapNodeID const& nodeID)
|
||||
|
||||
SHAMapLeafNode*
|
||||
SHAMap::belowHelper(
|
||||
SHAMapTreeNodePtr node,
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode> node,
|
||||
SharedPtrNodeStack& stack,
|
||||
int branch,
|
||||
std::tuple<int, std::function<bool(int)>, std::function<void(int&)>> const& loopParams) const
|
||||
@@ -476,7 +479,8 @@ SHAMap::belowHelper(
|
||||
return nullptr;
|
||||
}
|
||||
SHAMapLeafNode*
|
||||
SHAMap::lastBelow(SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, int branch) const
|
||||
SHAMap::lastBelow(intr_ptr::SharedPtr<SHAMapTreeNode> node, SharedPtrNodeStack& stack, int branch)
|
||||
const
|
||||
{
|
||||
auto init = kBranchFactor - 1;
|
||||
auto cmp = [](int i) { return i >= 0; };
|
||||
@@ -485,7 +489,8 @@ SHAMap::lastBelow(SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, int branch)
|
||||
return belowHelper(node, stack, branch, {init, cmp, incr});
|
||||
}
|
||||
SHAMapLeafNode*
|
||||
SHAMap::firstBelow(SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, int branch) const
|
||||
SHAMap::firstBelow(intr_ptr::SharedPtr<SHAMapTreeNode> node, SharedPtrNodeStack& stack, int branch)
|
||||
const
|
||||
{
|
||||
auto init = 0;
|
||||
auto cmp = [](int i) { return i <= kBranchFactor; };
|
||||
@@ -694,8 +699,10 @@ SHAMap::delItem(uint256 const& id)
|
||||
|
||||
SHAMapNodeType const type = leaf->getType();
|
||||
|
||||
using TreeNodeType = intr_ptr::SharedPtr<SHAMapTreeNode>;
|
||||
|
||||
// What gets attached to the end of the chain (For now, nothing, since we deleted the leaf)
|
||||
SHAMapTreeNodePtr prevNode;
|
||||
TreeNodeType prevNode;
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
@@ -721,7 +728,7 @@ SHAMap::delItem(uint256 const& id)
|
||||
// no children below this branch
|
||||
//
|
||||
// Note: This is unnecessary due to the std::move above but left here for safety
|
||||
prevNode = SHAMapTreeNodePtr{};
|
||||
prevNode = TreeNodeType{};
|
||||
}
|
||||
else if (bc == 1)
|
||||
{
|
||||
@@ -734,7 +741,7 @@ SHAMap::delItem(uint256 const& id)
|
||||
{
|
||||
if (!node->isEmptyBranch(i))
|
||||
{
|
||||
node->setChild(i, SHAMapTreeNodePtr{});
|
||||
node->setChild(i, TreeNodeType{});
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -885,7 +892,7 @@ SHAMap::updateGiveItem(SHAMapNodeType type, boost::intrusive_ptr<SHAMapItem cons
|
||||
}
|
||||
|
||||
bool
|
||||
SHAMap::fetchRoot(SHAMapHash const& hash, SHAMapSyncFilter const* filter)
|
||||
SHAMap::fetchRoot(SHAMapHash const& hash, SHAMapSyncFilter* filter)
|
||||
{
|
||||
if (hash == root_->getHash())
|
||||
return true;
|
||||
@@ -930,8 +937,8 @@ SHAMap::fetchRoot(SHAMapHash const& hash, SHAMapSyncFilter const* filter)
|
||||
@note The node must have already been unshared by having the caller
|
||||
first call SHAMapTreeNode::unshare().
|
||||
*/
|
||||
SHAMapTreeNodePtr
|
||||
SHAMap::writeNode(NodeObjectType t, SHAMapTreeNodePtr node) const
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMap::writeNode(NodeObjectType t, intr_ptr::SharedPtr<SHAMapTreeNode> node) const
|
||||
{
|
||||
XRPL_ASSERT(node->cowid() == 0, "xrpl::SHAMap::writeNode : valid input node");
|
||||
XRPL_ASSERT(backed_, "xrpl::SHAMap::writeNode : is backed");
|
||||
@@ -1148,7 +1155,7 @@ SHAMap::dump(bool hash) const
|
||||
JLOG(journal_.info()) << leafCount << " resident leaves";
|
||||
}
|
||||
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMap::cacheLookup(SHAMapHash const& hash) const
|
||||
{
|
||||
auto ret = f_.getTreeNodeCache()->fetch(hash.asUInt256());
|
||||
@@ -1157,7 +1164,7 @@ SHAMap::cacheLookup(SHAMapHash const& hash) const
|
||||
}
|
||||
|
||||
void
|
||||
SHAMap::canonicalize(SHAMapHash const& hash, SHAMapTreeNodePtr& node) const
|
||||
SHAMap::canonicalize(SHAMapHash const& hash, intr_ptr::SharedPtr<SHAMapTreeNode>& node) const
|
||||
{
|
||||
XRPL_ASSERT(backed_, "xrpl::SHAMap::canonicalize : is backed");
|
||||
XRPL_ASSERT(node->cowid() == 0, "xrpl::SHAMap::canonicalize : valid node input");
|
||||
|
||||
@@ -261,7 +261,7 @@ SHAMap::walkMap(std::vector<SHAMapMissingNode>& missingNodes, int maxMissing) co
|
||||
{
|
||||
if (!node->isEmptyBranch(i))
|
||||
{
|
||||
SHAMapTreeNodePtr const nextNode = descendNoStore(*node, i);
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode> const nextNode = descendNoStore(*node, i);
|
||||
|
||||
if (nextNode)
|
||||
{
|
||||
@@ -286,7 +286,7 @@ SHAMap::walkMapParallel(std::vector<SHAMapMissingNode>& missingNodes, int maxMis
|
||||
return false;
|
||||
|
||||
using StackEntry = intr_ptr::SharedPtr<SHAMapInnerNode>;
|
||||
std::array<SHAMapTreeNodePtr, 16> topChildren;
|
||||
std::array<intr_ptr::SharedPtr<SHAMapTreeNode>, 16> topChildren;
|
||||
{
|
||||
auto const& innerRoot = intr_ptr::staticPointerCast<SHAMapInnerNode>(root_);
|
||||
for (int i = 0; i < 16; ++i)
|
||||
@@ -331,7 +331,8 @@ SHAMap::walkMapParallel(std::vector<SHAMapMissingNode>& missingNodes, int maxMis
|
||||
{
|
||||
if (node->isEmptyBranch(i))
|
||||
continue;
|
||||
SHAMapTreeNodePtr const nextNode = descendNoStore(*node, i);
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode> const nextNode =
|
||||
descendNoStore(*node, i);
|
||||
|
||||
if (nextNode)
|
||||
{
|
||||
|
||||
@@ -37,7 +37,7 @@ SHAMapInnerNode::~SHAMapInnerNode() = default;
|
||||
void
|
||||
SHAMapInnerNode::partialDestructor()
|
||||
{
|
||||
SHAMapTreeNodePtr* children = nullptr;
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>* children = nullptr;
|
||||
// structured bindings can't be captured in c++ 17; use tie instead
|
||||
std::tie(std::ignore, std::ignore, children) = hashesAndChildren_.getHashesAndChildren();
|
||||
iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) { children[indexNum].reset(); });
|
||||
@@ -69,7 +69,7 @@ SHAMapInnerNode::getChildIndex(int i) const
|
||||
return hashesAndChildren_.getChildIndex(isBranch_, i);
|
||||
}
|
||||
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapInnerNode::clone(std::uint32_t cowid) const
|
||||
{
|
||||
auto const branchCount = getBranchCount();
|
||||
@@ -78,10 +78,8 @@ SHAMapInnerNode::clone(std::uint32_t cowid) const
|
||||
p->hash_ = hash_;
|
||||
p->isBranch_ = isBranch_;
|
||||
p->fullBelowGen_ = fullBelowGen_;
|
||||
SHAMapHash* cloneHashes = nullptr;
|
||||
SHAMapHash* thisHashes = nullptr;
|
||||
SHAMapTreeNodePtr* cloneChildren = nullptr;
|
||||
SHAMapTreeNodePtr* thisChildren = nullptr;
|
||||
SHAMapHash *cloneHashes = nullptr, *thisHashes = nullptr;
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>*cloneChildren = nullptr, *thisChildren = nullptr;
|
||||
// structured bindings can't be captured in c++ 17; use tie instead
|
||||
std::tie(std::ignore, cloneHashes, cloneChildren) =
|
||||
p->hashesAndChildren_.getHashesAndChildren();
|
||||
@@ -120,7 +118,7 @@ SHAMapInnerNode::clone(std::uint32_t cowid) const
|
||||
return p;
|
||||
}
|
||||
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapInnerNode::makeFullInner(Slice data, SHAMapHash const& hash, bool hashValid)
|
||||
{
|
||||
// A full inner node is serialized as 16 256-bit hashes, back to back:
|
||||
@@ -155,7 +153,7 @@ SHAMapInnerNode::makeFullInner(Slice data, SHAMapHash const& hash, bool hashVali
|
||||
return ret;
|
||||
}
|
||||
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapInnerNode::makeCompressedInner(Slice data)
|
||||
{
|
||||
// A compressed inner node is serialized as a series of 33 byte chunks,
|
||||
@@ -209,7 +207,7 @@ void
|
||||
SHAMapInnerNode::updateHashDeep()
|
||||
{
|
||||
SHAMapHash* hashes = nullptr;
|
||||
SHAMapTreeNodePtr* children = nullptr;
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>* children = nullptr;
|
||||
// structured bindings can't be captured in c++ 17; use tie instead
|
||||
std::tie(std::ignore, hashes, children) = hashesAndChildren_.getHashesAndChildren();
|
||||
iterNonEmptyChildIndexes([&](auto branchNum, auto indexNum) {
|
||||
@@ -267,7 +265,7 @@ SHAMapInnerNode::getString(SHAMapNodeID const& id) const
|
||||
|
||||
// We are modifying an inner node
|
||||
void
|
||||
SHAMapInnerNode::setChild(int m, SHAMapTreeNodePtr child)
|
||||
SHAMapInnerNode::setChild(int m, intr_ptr::SharedPtr<SHAMapTreeNode> child)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
(m >= 0) && (m < kBranchFactor), "xrpl::SHAMapInnerNode::setChild : valid branch input");
|
||||
@@ -309,7 +307,7 @@ SHAMapInnerNode::setChild(int m, SHAMapTreeNodePtr child)
|
||||
|
||||
// finished modifying, now make shareable
|
||||
void
|
||||
SHAMapInnerNode::shareChild(int m, SHAMapTreeNodePtr const& child)
|
||||
SHAMapInnerNode::shareChild(int m, intr_ptr::SharedPtr<SHAMapTreeNode> const& child)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
(m >= 0) && (m < kBranchFactor), "xrpl::SHAMapInnerNode::shareChild : valid branch input");
|
||||
@@ -339,7 +337,7 @@ SHAMapInnerNode::getChildPointer(int branch)
|
||||
return hashesAndChildren_.getChildren()[index].get();
|
||||
}
|
||||
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapInnerNode::getChild(int branch)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
@@ -367,8 +365,8 @@ SHAMapInnerNode::getChildHash(int m) const
|
||||
return kZeroShaMapHash;
|
||||
}
|
||||
|
||||
SHAMapTreeNodePtr
|
||||
SHAMapInnerNode::canonicalizeChild(int branch, SHAMapTreeNodePtr node)
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapInnerNode::canonicalizeChild(int branch, intr_ptr::SharedPtr<SHAMapTreeNode> node)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
branch >= 0 && branch < kBranchFactor,
|
||||
|
||||
@@ -66,7 +66,7 @@ SHAMap::visitNodes(std::function<bool(SHAMapTreeNode&)> const& function) const
|
||||
{
|
||||
if (!node->isEmptyBranch(pos))
|
||||
{
|
||||
SHAMapTreeNodePtr const child = descendNoStore(*node, pos);
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode> const child = descendNoStore(*node, pos);
|
||||
if (!function(*child))
|
||||
return;
|
||||
|
||||
@@ -204,7 +204,8 @@ SHAMap::gmnProcessNodes(MissingNodes& mn, MissingNodes::StackEntry& se)
|
||||
branch,
|
||||
mn.filter,
|
||||
pending,
|
||||
[node, nodeID, branch, &mn](SHAMapTreeNodePtr found, SHAMapHash const&) {
|
||||
[node, nodeID, branch, &mn](
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode> found, SHAMapHash const&) {
|
||||
// a read completed asynchronously
|
||||
std::unique_lock<std::mutex> const lock{mn.deferLock};
|
||||
mn.finishedReads.emplace_back(node, nodeID, branch, std::move(found));
|
||||
@@ -265,7 +266,8 @@ SHAMap::gmnProcessDeferredReads(MissingNodes& mn)
|
||||
int complete = 0;
|
||||
while (complete != mn.deferred)
|
||||
{
|
||||
std::tuple<SHAMapInnerNode*, SHAMapNodeID, int, SHAMapTreeNodePtr> deferredNode;
|
||||
std::tuple<SHAMapInnerNode*, SHAMapNodeID, int, intr_ptr::SharedPtr<SHAMapTreeNode>>
|
||||
deferredNode;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock{mn.deferLock};
|
||||
|
||||
@@ -305,7 +307,7 @@ SHAMap::gmnProcessDeferredReads(MissingNodes& mn)
|
||||
nodes that are not permanently stored locally
|
||||
*/
|
||||
std::vector<std::pair<SHAMapNodeID, uint256>>
|
||||
SHAMap::getMissingNodes(int max, SHAMapSyncFilter const* filter)
|
||||
SHAMap::getMissingNodes(int max, SHAMapSyncFilter* filter)
|
||||
{
|
||||
XRPL_ASSERT(root_->getHash().isNonZero(), "xrpl::SHAMap::getMissingNodes : nonzero root hash");
|
||||
XRPL_ASSERT(max > 0, "xrpl::SHAMap::getMissingNodes : valid max input");
|
||||
@@ -507,7 +509,7 @@ SHAMap::serializeRoot(Serializer& s) const
|
||||
}
|
||||
|
||||
SHAMapAddNode
|
||||
SHAMap::addRootNode(SHAMapHash const& hash, Slice const& rootNode, SHAMapSyncFilter const* filter)
|
||||
SHAMap::addRootNode(SHAMapHash const& hash, Slice const& rootNode, SHAMapSyncFilter* filter)
|
||||
{
|
||||
// we already have a root_ node
|
||||
if (root_->getHash().isNonZero())
|
||||
@@ -542,7 +544,7 @@ SHAMap::addRootNode(SHAMapHash const& hash, Slice const& rootNode, SHAMapSyncFil
|
||||
}
|
||||
|
||||
SHAMapAddNode
|
||||
SHAMap::addKnownNode(SHAMapNodeID const& node, Slice const& rawNode, SHAMapSyncFilter const* filter)
|
||||
SHAMap::addKnownNode(SHAMapNodeID const& node, Slice const& rawNode, SHAMapSyncFilter* filter)
|
||||
{
|
||||
XRPL_ASSERT(!node.isRoot(), "xrpl::SHAMap::addKnownNode : valid node input");
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapTreeNode::makeTransaction(Slice data, SHAMapHash const& hash, bool hashValid)
|
||||
{
|
||||
if (data.size() < kMinShaMapItemBytes)
|
||||
@@ -43,7 +43,7 @@ SHAMapTreeNode::makeTransaction(Slice data, SHAMapHash const& hash, bool hashVal
|
||||
return intr_ptr::makeShared<SHAMapTxLeafNode>(std::move(item), 0);
|
||||
}
|
||||
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapTreeNode::makeTransactionWithMeta(Slice data, SHAMapHash const& hash, bool hashValid)
|
||||
{
|
||||
Serializer s(data.data(), data.size());
|
||||
@@ -83,7 +83,7 @@ SHAMapTreeNode::makeTransactionWithMeta(Slice data, SHAMapHash const& hash, bool
|
||||
return intr_ptr::makeShared<SHAMapTxPlusMetaLeafNode>(std::move(item), 0);
|
||||
}
|
||||
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapTreeNode::makeAccountState(Slice data, SHAMapHash const& hash, bool hashValid)
|
||||
{
|
||||
Serializer s(data.data(), data.size());
|
||||
@@ -124,7 +124,7 @@ SHAMapTreeNode::makeAccountState(Slice data, SHAMapHash const& hash, bool hashVa
|
||||
return intr_ptr::makeShared<SHAMapAccountStateLeafNode>(std::move(item), 0);
|
||||
}
|
||||
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapTreeNode::makeFromWire(Slice rawNode)
|
||||
{
|
||||
if (rawNode.empty())
|
||||
@@ -155,7 +155,7 @@ SHAMapTreeNode::makeFromWire(Slice rawNode)
|
||||
Throw<std::runtime_error>("wire: Unknown type (" + std::to_string(type) + ")");
|
||||
}
|
||||
|
||||
SHAMapTreeNodePtr
|
||||
intr_ptr::SharedPtr<SHAMapTreeNode>
|
||||
SHAMapTreeNode::makeFromPrefix(Slice rawNode, SHAMapHash const& hash)
|
||||
{
|
||||
if (rawNode.size() < 4)
|
||||
|
||||
@@ -244,12 +244,12 @@ ValidMPTIssuance::finalize(
|
||||
"but created bad number of mptokens";
|
||||
return false;
|
||||
}
|
||||
// At most one MPToken may be created on withdraw/clawback since:
|
||||
// At most two MPToken may be created on withdraw/clawback since:
|
||||
// - Liquidity Provider must have at least one token in order
|
||||
// participate in AMM pool liquidity.
|
||||
// participate in AMM pool liquidity or have LPTokens only.
|
||||
// - At most two MPTokens may be deleted if AMM pool, which has exactly
|
||||
// two tokens, is empty after withdraw/clawback.
|
||||
if (mptokensCreated_ > 1 || mptokensDeleted_ > 2)
|
||||
if (mptokensCreated_ > 2 || mptokensDeleted_ > 2)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: MPT authorize succeeded "
|
||||
"but created/deleted bad number of mptokens";
|
||||
|
||||
@@ -44,7 +44,9 @@
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
@@ -652,7 +654,15 @@ limitStepIn(
|
||||
// under an amendment.
|
||||
ofrAmt = offer.limitIn(ofrAmt, inLmt, /* roundUp */ false);
|
||||
stpAmt.out = ofrAmt.out;
|
||||
ownerGives = mulRatio(ofrAmt.out, transferRateOut, QUALITY_ONE, /*roundUp*/ false);
|
||||
// Round up for MPT output so the offer owner pays the full
|
||||
// ceil(amount × rate) fee, matching direct Payment semantics. IOU uses
|
||||
// floating-point arithmetic so the floor/ceil distinction is sub-epsilon
|
||||
// there; preserve the historical false to avoid changing IOU behavior.
|
||||
ownerGives = mulRatio(
|
||||
ofrAmt.out,
|
||||
transferRateOut,
|
||||
QUALITY_ONE,
|
||||
/*roundUp*/ std::is_same_v<TOut, MPTAmount>);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -671,7 +681,11 @@ limitStepOut(
|
||||
if (limit < stpAmt.out)
|
||||
{
|
||||
stpAmt.out = limit;
|
||||
ownerGives = mulRatio(stpAmt.out, transferRateOut, QUALITY_ONE, /*roundUp*/ false);
|
||||
ownerGives = mulRatio(
|
||||
stpAmt.out,
|
||||
transferRateOut,
|
||||
QUALITY_ONE,
|
||||
/*roundUp*/ std::is_same_v<TOut, MPTAmount>);
|
||||
ofrAmt = offer.limitOut(
|
||||
ofrAmt,
|
||||
stpAmt.out,
|
||||
@@ -726,17 +740,20 @@ BookStep<TIn, TOut, TDerived>::forEachOffer(
|
||||
bool const isAssetInMPT = assetIn.holds<MPTIssue>();
|
||||
auto const& owner = offer.owner();
|
||||
|
||||
if (isAssetInMPT)
|
||||
{
|
||||
// Create MPToken for the offer's owner. No need to check
|
||||
// for the reserve since the offer is removed if it is consumed.
|
||||
// Therefore, the owner count remains the same.
|
||||
if (auto const err = checkCreateMPT(sb, assetIn.get<MPTIssue>(), owner, j_);
|
||||
!isTesSuccess(err))
|
||||
auto removeOffer = [&](std::string_view logMessage = {}) {
|
||||
auto const key = offer.key();
|
||||
if (!logMessage.empty())
|
||||
{
|
||||
return true;
|
||||
JLOG(j_.trace()) << logMessage << (key ? " " + to_string(*key) : "");
|
||||
}
|
||||
}
|
||||
if (key)
|
||||
offers.permRmOffer(*key);
|
||||
if (!offerAttempted)
|
||||
{
|
||||
// Change quality only if no previous offers were tried.
|
||||
ofrQ = std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
// It shouldn't matter from auth point of view whether it's sb
|
||||
// or afView. Amendment guard this change just in case.
|
||||
@@ -744,17 +761,15 @@ BookStep<TIn, TOut, TDerived>::forEachOffer(
|
||||
// Make sure offer owner has authorization to own Assets from issuer
|
||||
// and MPT assets can be traded/transferred.
|
||||
// An account can always own XRP or their own Assets.
|
||||
if (!isTesSuccess(requireAuth(applyView, assetIn, owner)) || !checkMPTDEX(sb, owner))
|
||||
// Missing MPTokens are allowed during offer discovery; they are
|
||||
// created later if the offer is actually consumed.
|
||||
auto const authType = isAssetInMPT ? AuthType::WeakAuth : AuthType::Legacy;
|
||||
if (!isTesSuccess(requireAuth(applyView, assetIn, owner, authType)) ||
|
||||
!checkMPTDEX(sb, owner))
|
||||
{
|
||||
// Offer owner not authorized to hold IOU/MPT from issuer.
|
||||
// Remove this offer even if no crossing occurs.
|
||||
if (auto const key = offer.key())
|
||||
offers.permRmOffer(*key);
|
||||
if (!offerAttempted)
|
||||
{
|
||||
// Change quality only if no previous offers were tried.
|
||||
ofrQ = std::nullopt;
|
||||
}
|
||||
removeOffer();
|
||||
// Returning true causes offers.step() to delete the offer.
|
||||
return true;
|
||||
}
|
||||
@@ -767,52 +782,75 @@ BookStep<TIn, TOut, TDerived>::forEachOffer(
|
||||
static_cast<TDerived const*>(this)->getOfrOutRate(prevStep_, owner, strandDst_, trOut));
|
||||
|
||||
auto ofrAmt = offer.amount();
|
||||
TAmounts stpAmt{mulRatio(ofrAmt.in, ofrInRate, QUALITY_ONE, /*roundUp*/ true), ofrAmt.out};
|
||||
|
||||
// owner pays the transfer fee.
|
||||
auto ownerGives = mulRatio(ofrAmt.out, ofrOutRate, QUALITY_ONE, /*roundUp*/ false);
|
||||
|
||||
auto const funds = offer.isFunded()
|
||||
? ownerGives // Offer owner is issuer; they have unlimited funds
|
||||
: offers.ownerFunds();
|
||||
|
||||
// Only if CLOB offer
|
||||
if (funds < ownerGives)
|
||||
TAmounts stpAmt{ofrAmt.in, ofrAmt.out};
|
||||
auto ownerGives = ofrAmt.out;
|
||||
try
|
||||
{
|
||||
// We already know offer.owner()!=offer.issueOut().account
|
||||
ownerGives = funds;
|
||||
stpAmt.out = mulRatio(ownerGives, QUALITY_ONE, ofrOutRate, /*roundUp*/ false);
|
||||
|
||||
// It turns out we can prevent order book blocking by (strictly)
|
||||
// rounding down the ceil_out() result. This adjustment changes
|
||||
// transaction outcomes, so it must be made under an amendment.
|
||||
ofrAmt = offer.limitOut(ofrAmt, stpAmt.out, /*roundUp*/ false);
|
||||
|
||||
// All arithmetic in this block runs before the offer is consumed.
|
||||
// A crafted MPTokensV2 offer can overflow while transfer rates or
|
||||
// crossing limits are applied; remove that unusable offer instead
|
||||
// of letting it persist as a tecINTERNAL source.
|
||||
stpAmt.in = mulRatio(ofrAmt.in, ofrInRate, QUALITY_ONE, /*roundUp*/ true);
|
||||
}
|
||||
|
||||
// Limit offer's input if MPT, BookStep is the first step (an issuer
|
||||
// is making a cross-currency payment), and this offer is not owned
|
||||
// by the issuer. Otherwise, OutstandingAmount may overflow.
|
||||
auto const& issuer = assetIn.getIssuer();
|
||||
if (isAssetInMPT && !prevStep_ && offer.owner() != issuer)
|
||||
{
|
||||
// Funds available to issue
|
||||
auto const available = toAmount<TIn>(accountFunds(
|
||||
sb,
|
||||
issuer,
|
||||
assetIn, // STAmount{0}, but the default is not used
|
||||
FreezeHandling::IgnoreFreeze,
|
||||
AuthHandling::IgnoreAuth,
|
||||
j_));
|
||||
if (stpAmt.in > available)
|
||||
// owner pays the transfer fee.
|
||||
ownerGives = mulRatio(
|
||||
ofrAmt.out,
|
||||
ofrOutRate,
|
||||
QUALITY_ONE,
|
||||
/*roundUp*/ std::is_same_v<TOut, MPTAmount>);
|
||||
|
||||
auto const funds = offer.isFunded()
|
||||
? ownerGives // Offer owner is issuer; they have unlimited funds
|
||||
: offers.ownerFunds();
|
||||
|
||||
// Only if CLOB offer
|
||||
if (funds < ownerGives)
|
||||
{
|
||||
limitStepIn(offer, ofrAmt, stpAmt, ownerGives, ofrInRate, ofrOutRate, available);
|
||||
}
|
||||
}
|
||||
// We already know offer.owner()!=offer.issueOut().account
|
||||
ownerGives = funds;
|
||||
stpAmt.out = mulRatio(ownerGives, QUALITY_ONE, ofrOutRate, /*roundUp*/ false);
|
||||
|
||||
offerAttempted = true;
|
||||
return callback(offer, ofrAmt, stpAmt, ownerGives, ofrInRate, ofrOutRate);
|
||||
// It turns out we can prevent order book blocking by (strictly)
|
||||
// rounding down the ceil_out() result. This adjustment changes
|
||||
// transaction outcomes, so it must be made under an amendment.
|
||||
ofrAmt = offer.limitOut(ofrAmt, stpAmt.out, /*roundUp*/ false);
|
||||
|
||||
stpAmt.in = mulRatio(ofrAmt.in, ofrInRate, QUALITY_ONE, /*roundUp*/ true);
|
||||
}
|
||||
|
||||
// Limit offer's input if MPT, BookStep is the first step (an issuer
|
||||
// is making a cross-currency payment), and this offer is not owned
|
||||
// by the issuer. Otherwise, OutstandingAmount may overflow.
|
||||
auto const& issuer = assetIn.getIssuer();
|
||||
if (isAssetInMPT && !prevStep_ && offer.owner() != issuer)
|
||||
{
|
||||
// Funds available to issue
|
||||
auto const available = toAmount<TIn>(accountFunds(
|
||||
sb,
|
||||
issuer,
|
||||
assetIn, // STAmount{0}, but the default is not used
|
||||
FreezeHandling::IgnoreFreeze,
|
||||
AuthHandling::IgnoreAuth,
|
||||
j_));
|
||||
if (stpAmt.in > available)
|
||||
{
|
||||
limitStepIn(
|
||||
offer, ofrAmt, stpAmt, ownerGives, ofrInRate, ofrOutRate, available);
|
||||
}
|
||||
}
|
||||
|
||||
offerAttempted = true;
|
||||
return callback(offer, ofrAmt, stpAmt, ownerGives, ofrInRate, ofrOutRate);
|
||||
}
|
||||
catch (std::overflow_error const&)
|
||||
{
|
||||
if (sb.rules().enabled(featureMPTokensV2))
|
||||
{
|
||||
removeOffer("Removing offer with overflowing amount calculation");
|
||||
return true;
|
||||
}
|
||||
throw;
|
||||
}
|
||||
};
|
||||
|
||||
// At any payment engine iteration, AMM offer can only be consumed once.
|
||||
@@ -875,6 +913,18 @@ BookStep<TIn, TOut, TDerived>::consumeOffer(
|
||||
// The offer owner gets the ofrAmt. The difference between ofrAmt and
|
||||
// stepAmt is a transfer fee that goes to book_.in.account
|
||||
{
|
||||
if constexpr (std::is_same_v<TIn, MPTAmount>)
|
||||
{
|
||||
// If the offer's TakerPays asset is an MPT, the offer owner must
|
||||
// hold an MPToken to receive it. Create one here, after the
|
||||
// consumption decision has been made, so the +1 to ownerCount is
|
||||
// paired atomically with the -1 that follows when the offer SLE is
|
||||
// deleted by BookTip::step().
|
||||
if (auto const err = checkCreateMPT(sb, book_.in.get<MPTIssue>(), offer.owner(), j_);
|
||||
!isTesSuccess(err))
|
||||
Throw<FlowException>(err);
|
||||
}
|
||||
|
||||
auto const dr = offer.send(
|
||||
sb, book_.in.getIssuer(), offer.owner(), toSTAmount(ofrAmt.in, book_.in), j_);
|
||||
if (!isTesSuccess(dr))
|
||||
@@ -1040,6 +1090,9 @@ BookStep<TIn, TOut, TDerived>::revImp(
|
||||
auto ofrAdjAmt = ofrAmt;
|
||||
auto stpAdjAmt = stpAmt;
|
||||
auto ownerGivesAdj = ownerGives;
|
||||
// This reduction can overflow, but savedIns/savedOuts are not updated
|
||||
// until after it succeeds. The outer execOffer() catch can therefore
|
||||
// remove the offer without rolling back local state.
|
||||
limitStepOut(
|
||||
offer,
|
||||
ofrAdjAmt,
|
||||
@@ -1141,12 +1194,21 @@ BookStep<TIn, TOut, TDerived>::fwdImp(
|
||||
auto stpAdjAmt = stpAmt;
|
||||
auto ownerGivesAdj = ownerGives;
|
||||
|
||||
// limitStepIn()/limitStepOut() can throw std::overflow_error, which is
|
||||
// handled by execOffer() by removing the offer. Keep candidate
|
||||
// accumulator changes local until those calls succeed so the catch
|
||||
// path does not observe partially updated state. Re-sum the staged
|
||||
// sets to preserve historical flat_multiset summing behavior.
|
||||
auto savedInsAdj = savedIns;
|
||||
auto savedOutsAdj = savedOuts;
|
||||
auto resultAdj = result;
|
||||
typename boost::container::flat_multiset<TOut>::const_iterator lastOut;
|
||||
|
||||
if (stpAmt.in <= remainingIn)
|
||||
{
|
||||
savedIns.insert(stpAmt.in);
|
||||
lastOut = savedOuts.insert(stpAmt.out);
|
||||
result = TAmounts<TIn, TOut>(sum(savedIns), sum(savedOuts));
|
||||
savedInsAdj.insert(stpAmt.in);
|
||||
lastOut = savedOutsAdj.insert(stpAmt.out);
|
||||
resultAdj = TAmounts<TIn, TOut>(sum(savedInsAdj), sum(savedOutsAdj));
|
||||
// consume the offer even if stepAmt.in == remainingIn
|
||||
processMore = true;
|
||||
}
|
||||
@@ -1160,15 +1222,15 @@ BookStep<TIn, TOut, TDerived>::fwdImp(
|
||||
transferRateIn,
|
||||
transferRateOut,
|
||||
remainingIn);
|
||||
savedIns.insert(remainingIn);
|
||||
lastOut = savedOuts.insert(stpAdjAmt.out);
|
||||
result.out = sum(savedOuts);
|
||||
result.in = in;
|
||||
savedInsAdj.insert(remainingIn);
|
||||
lastOut = savedOutsAdj.insert(stpAdjAmt.out);
|
||||
resultAdj.out = sum(savedOutsAdj);
|
||||
resultAdj.in = in;
|
||||
|
||||
processMore = false;
|
||||
}
|
||||
|
||||
if (result.out > cache_->out && result.in <= cache_->in)
|
||||
if (resultAdj.out > cache_->out && resultAdj.in <= cache_->in)
|
||||
{
|
||||
// The step produced more output in the forward pass than the
|
||||
// reverse pass while consuming the same input (or less). If we
|
||||
@@ -1178,8 +1240,8 @@ BookStep<TIn, TOut, TDerived>::fwdImp(
|
||||
// input provided in the forward step and produce the output
|
||||
// requested from the reverse step.
|
||||
auto const lastOutAmt = *lastOut;
|
||||
savedOuts.erase(lastOut);
|
||||
auto const remainingOut = cache_->out - sum(savedOuts);
|
||||
savedOutsAdj.erase(lastOut);
|
||||
auto const remainingOut = cache_->out - sum(savedOutsAdj);
|
||||
auto ofrAdjAmtRev = ofrAmt;
|
||||
auto stpAdjAmtRev = stpAmt;
|
||||
auto ownerGivesAdjRev = ownerGives;
|
||||
@@ -1194,13 +1256,13 @@ BookStep<TIn, TOut, TDerived>::fwdImp(
|
||||
|
||||
if (stpAdjAmtRev.in == remainingIn)
|
||||
{
|
||||
result.in = in;
|
||||
result.out = cache_->out;
|
||||
resultAdj.in = in;
|
||||
resultAdj.out = cache_->out;
|
||||
|
||||
savedIns.clear();
|
||||
savedIns.insert(result.in);
|
||||
savedOuts.clear();
|
||||
savedOuts.insert(result.out);
|
||||
savedInsAdj.clear();
|
||||
savedInsAdj.insert(resultAdj.in);
|
||||
savedOutsAdj.clear();
|
||||
savedOutsAdj.insert(resultAdj.out);
|
||||
|
||||
ofrAdjAmt = ofrAdjAmtRev;
|
||||
stpAdjAmt.in = remainingIn;
|
||||
@@ -1211,10 +1273,15 @@ BookStep<TIn, TOut, TDerived>::fwdImp(
|
||||
{
|
||||
// This is (likely) a problem case, and will be caught
|
||||
// with later checks
|
||||
savedOuts.insert(lastOutAmt);
|
||||
savedOutsAdj.insert(lastOutAmt);
|
||||
}
|
||||
}
|
||||
|
||||
// Commit the staged accounting only after limitStepIn()/limitStepOut()
|
||||
// have succeeded.
|
||||
savedIns = std::move(savedInsAdj);
|
||||
savedOuts = std::move(savedOutsAdj);
|
||||
result = resultAdj;
|
||||
remainingIn = in - result.in;
|
||||
this->consumeOffer(sb, offer, ofrAdjAmt, stpAdjAmt, ownerGivesAdj);
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Book.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/MPTAmount.h>
|
||||
@@ -28,6 +29,8 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -135,17 +138,17 @@ template <class TTakerPays, class TTakerGets>
|
||||
TOfferStreamBase<TIn, TOut>::shouldRmSmallIncreasedQOffer() const
|
||||
{
|
||||
// Consider removing the offer if:
|
||||
// o `TakerPays` is XRP (because of XRP drops granularity) or
|
||||
// o `TakerPays` is integral (because XRP/MPT have indivisible units) or
|
||||
// o `TakerPays` and `TakerGets` are both IOU and `TakerPays`<`TakerGets`
|
||||
static constexpr bool kInIsXrp = std::is_same_v<TTakerPays, XRPAmount>;
|
||||
static constexpr bool kOutIsXrp = std::is_same_v<TTakerGets, XRPAmount>;
|
||||
constexpr bool const kInIsIntegral = !std::is_same_v<TTakerPays, IOUAmount>;
|
||||
constexpr bool const kOutIsIntegral = !std::is_same_v<TTakerGets, IOUAmount>;
|
||||
|
||||
if constexpr (kOutIsXrp)
|
||||
if constexpr (!kInIsIntegral && kOutIsIntegral)
|
||||
{
|
||||
// If `TakerGets` is XRP, the worst this offer's quality can change is
|
||||
// to about 10^-81 `TakerPays` and 1 drop `TakerGets`. This will be
|
||||
// remarkably good quality for any realistic asset, so these offers
|
||||
// don't need this extra check.
|
||||
// If only `TakerGets` is integral, the worst this offer's quality can
|
||||
// change is to about 10^-81 `TakerPays` and 1 unit `TakerGets`. This
|
||||
// will be perfect quality for any realistic asset, so these
|
||||
// offers don't need this extra check.
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -155,7 +158,7 @@ TOfferStreamBase<TIn, TOut>::shouldRmSmallIncreasedQOffer() const
|
||||
TAmounts<TTakerPays, TTakerGets> const ofrAmts{
|
||||
toAmount<TTakerPays>(offer_.amount().in), toAmount<TTakerGets>(offer_.amount().out)};
|
||||
|
||||
if constexpr (!kInIsXrp && !kOutIsXrp)
|
||||
if constexpr (!kInIsIntegral && !kOutIsIntegral)
|
||||
{
|
||||
if (Number(ofrAmts.in) >= Number(ofrAmts.out))
|
||||
return false;
|
||||
@@ -164,7 +167,12 @@ TOfferStreamBase<TIn, TOut>::shouldRmSmallIncreasedQOffer() const
|
||||
TTakerGets const ownerFunds = toAmount<TTakerGets>(*ownerFunds_);
|
||||
|
||||
auto const effectiveAmounts = [&] {
|
||||
if (offer_.owner() != offer_.assetOut().getIssuer() && ownerFunds < ofrAmts.out)
|
||||
// Issuer-owned IOU offers are self-funded without a limit. MPT issuer
|
||||
// offers are bounded by remaining issuance capacity, so they still need
|
||||
// to be clipped by ownerFunds.
|
||||
bool const issuerHasUnlimitedFunds = offer_.owner() == offer_.assetOut().getIssuer() &&
|
||||
offer_.assetOut().template holds<Issue>();
|
||||
if (!issuerHasUnlimitedFunds && ownerFunds < ofrAmts.out)
|
||||
{
|
||||
// adjust the amounts by owner funds.
|
||||
//
|
||||
@@ -298,7 +306,28 @@ TOfferStreamBase<TIn, TOut>::step()
|
||||
continue;
|
||||
}
|
||||
|
||||
if (shouldRmSmallIncreasedQOffer<TIn, TOut>())
|
||||
// Partially funded offers can be reduced before BookStep sees them.
|
||||
// If that strict reduction overflows under MPTokensV2, remove the
|
||||
// unusable offer instead of leaving it at the book tip.
|
||||
bool shouldRemoveSmallIncreasedQOffer = false;
|
||||
try
|
||||
{
|
||||
shouldRemoveSmallIncreasedQOffer = shouldRmSmallIncreasedQOffer<TIn, TOut>();
|
||||
}
|
||||
catch (std::overflow_error const&)
|
||||
{
|
||||
if (view_.rules().enabled(featureMPTokensV2))
|
||||
{
|
||||
permRmOffer(entry->key());
|
||||
JLOG(j_.trace()) << "Removing offer with overflowing reduced quality "
|
||||
<< entry->key();
|
||||
offer_ = TOffer<TIn, TOut>{};
|
||||
continue;
|
||||
}
|
||||
throw;
|
||||
}
|
||||
|
||||
if (shouldRemoveSmallIncreasedQOffer)
|
||||
{
|
||||
auto const originalFunds = accountFundsHelper(
|
||||
cancelView_,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user