mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-23 23:30:54 +00:00
Compare commits
95 Commits
audit_wasm
...
pratik/std
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bacfb38233 | ||
|
|
bec82ee704 | ||
|
|
c6224c48ed | ||
|
|
5fb1429e3a | ||
|
|
da57183e0c | ||
|
|
f370289733 | ||
|
|
d1dc7a6ccf | ||
|
|
5639863715 | ||
|
|
1be4868875 | ||
|
|
368ff1afce | ||
|
|
3adf2d40b5 | ||
|
|
a6983f8bf3 | ||
|
|
4113b105a5 | ||
|
|
7442ff2dec | ||
|
|
666e77b22c | ||
|
|
8c12de6c56 | ||
|
|
b21fd86f6e | ||
|
|
f5f47f1cf5 | ||
|
|
ca39bff3c8 | ||
|
|
dd0edc19a0 | ||
|
|
820ca5b332 | ||
|
|
1b226c8b2e | ||
|
|
ca6121c5b3 | ||
|
|
c49789086a | ||
|
|
5337d028a2 | ||
|
|
43d842926a | ||
|
|
2adffaef72 | ||
|
|
bd87edfc75 | ||
|
|
d34aa37b3c | ||
|
|
a0074f83d3 | ||
|
|
028ccea7a1 | ||
|
|
df85d43d8a | ||
|
|
8e9b1791c5 | ||
|
|
946827b9bd | ||
|
|
91360c5126 | ||
|
|
af36890c11 | ||
|
|
1281c7a222 | ||
|
|
153b7839a7 | ||
|
|
26cc683ec1 | ||
|
|
6ca2fb84d4 | ||
|
|
a3147740f2 | ||
|
|
d43e5acaa7 | ||
|
|
c74724a719 | ||
|
|
0a572833ea | ||
|
|
639943123c | ||
|
|
909cc5bba9 | ||
|
|
6f5de9067a | ||
|
|
60291c3ed6 | ||
|
|
4173f7e499 | ||
|
|
9c292fbe4f | ||
|
|
b19c3c64f2 | ||
|
|
a0e1e578a0 | ||
|
|
07aa97fda4 | ||
|
|
4f8819565a | ||
|
|
6580b200db | ||
|
|
2967f1f0cc | ||
|
|
71e972cbed | ||
|
|
a24caaa6ea | ||
|
|
07b9c59b89 | ||
|
|
63d8772f69 | ||
|
|
b54c500bb2 | ||
|
|
b6526522fe | ||
|
|
61041155d2 | ||
|
|
5ea72b45f1 | ||
|
|
7b95a8ea8b | ||
|
|
7385004952 | ||
|
|
fce702d5c3 | ||
|
|
a6fea1227f | ||
|
|
968f9e1bf3 | ||
|
|
70813b336a | ||
|
|
47d84d2e21 | ||
|
|
ab8350b558 | ||
|
|
072e11ecd1 | ||
|
|
3ac48e297f | ||
|
|
f669b70451 | ||
|
|
62839b1531 | ||
|
|
9bddc54722 | ||
|
|
598bda0ebc | ||
|
|
25b9b4d27d | ||
|
|
22ed85c3ad | ||
|
|
8c7a27d721 | ||
|
|
9266c23ef6 | ||
|
|
5e9ce16de6 | ||
|
|
8d4ea00453 | ||
|
|
bdcf094171 | ||
|
|
a42e8174d9 | ||
|
|
791c061a98 | ||
|
|
91004209e9 | ||
|
|
4c18dd867c | ||
|
|
fb6ece56f2 | ||
|
|
b53df32334 | ||
|
|
42cced50fb | ||
|
|
0e815aa1ac | ||
|
|
21149a81e3 | ||
|
|
b78202a99a |
24
.codecov.yml
24
.codecov.yml
@@ -1,10 +1,32 @@
|
||||
codecov:
|
||||
require_ci_to_pass: true
|
||||
# The C++ and Rust uploads land minutes apart; without this gate Codecov
|
||||
# publishes a near-zero total from whichever one arrives first.
|
||||
notify:
|
||||
after_n_builds: 2
|
||||
wait_for_ci: true
|
||||
|
||||
comment:
|
||||
behavior: default
|
||||
layout: reach,diff,flags,tree,reach
|
||||
show_carryforward_flags: false
|
||||
show_carryforward_flags: true
|
||||
after_n_builds: 2
|
||||
|
||||
# C++ and Rust coverage upload from independent workflows under the `cpp` and
|
||||
# `rust` flags; carryforward keeps one language's total when only the other reran.
|
||||
flag_management:
|
||||
default_rules:
|
||||
carryforward: true
|
||||
individual_flags:
|
||||
- name: cpp
|
||||
carryforward: true
|
||||
paths:
|
||||
- include/
|
||||
- src/
|
||||
- name: rust
|
||||
carryforward: true
|
||||
paths:
|
||||
- crates/
|
||||
|
||||
coverage:
|
||||
range: "70..85"
|
||||
|
||||
@@ -7,6 +7,7 @@ ignorePaths:
|
||||
- cmake/**
|
||||
- LICENSE.md
|
||||
- .clang-tidy
|
||||
- nix/check-tools/*.txt # generated, and full of Nix store hashes
|
||||
language: en
|
||||
allowCompoundWords: true # TODO (#6334)
|
||||
ignoreRandomStrings: true
|
||||
@@ -68,6 +69,7 @@ words:
|
||||
- Buildx
|
||||
- canonicality
|
||||
- canonicalised
|
||||
- cctools
|
||||
- changespq
|
||||
- checkme
|
||||
- choco
|
||||
@@ -109,6 +111,7 @@ words:
|
||||
- disablerepo
|
||||
- distro
|
||||
- doxyfile
|
||||
- dsymutil
|
||||
- dxrpl
|
||||
- elgamal
|
||||
- enabled
|
||||
@@ -167,6 +170,7 @@ words:
|
||||
- LOCALGOOD
|
||||
- logwstream
|
||||
- Lombrozo
|
||||
- lresolv
|
||||
- lseq
|
||||
- lsmf
|
||||
- ltype
|
||||
@@ -220,6 +224,7 @@ words:
|
||||
- Nyffenegger
|
||||
- onlatest
|
||||
- ostr
|
||||
- otool
|
||||
- oxalica
|
||||
- pargs
|
||||
- partitioner
|
||||
@@ -249,11 +254,17 @@ words:
|
||||
- Raphson
|
||||
- rcflags
|
||||
- replayer
|
||||
- repodata
|
||||
- repomd
|
||||
- repost
|
||||
- reposts
|
||||
- rerandomize
|
||||
- rerandomization
|
||||
- rerandomized
|
||||
- rerandomizes
|
||||
- rerere
|
||||
- retargeted
|
||||
- retargets
|
||||
- retriable
|
||||
- RIPD
|
||||
- ripdtop
|
||||
@@ -289,10 +300,12 @@ words:
|
||||
- sles
|
||||
- soci
|
||||
- socidb
|
||||
- Sonatype
|
||||
- sponsee
|
||||
- sponsees
|
||||
- SRPMS
|
||||
- sslws
|
||||
- stackful
|
||||
- statsd
|
||||
- STATSDCOLLECTOR
|
||||
- stissue
|
||||
@@ -308,6 +321,7 @@ words:
|
||||
- summands
|
||||
- superpeer
|
||||
- superpeers
|
||||
- Swatinem
|
||||
- takergets
|
||||
- takerpays
|
||||
- ters
|
||||
@@ -363,12 +377,15 @@ words:
|
||||
- wthread
|
||||
- xbridge
|
||||
- xchain
|
||||
- xcrun
|
||||
- ximinez
|
||||
- XMACRO
|
||||
- xored
|
||||
- xrpkuwait
|
||||
- xrpl
|
||||
- xrpld
|
||||
- xrplf
|
||||
- xxhash
|
||||
- xxhasher
|
||||
- zstdio
|
||||
- CGNAT
|
||||
|
||||
4
.envrc
4
.envrc
@@ -1,3 +1,7 @@
|
||||
watch_file nix/*.nix
|
||||
|
||||
# The dev shell derivation includes all of conan/ (see nix/devshell.nix), so any
|
||||
# change in there has to invalidate direnv's cached environment.
|
||||
watch_dir conan
|
||||
|
||||
use flake
|
||||
|
||||
44
.github/actions/generate-version/action.yml
vendored
44
.github/actions/generate-version/action.yml
vendored
@@ -1,44 +0,0 @@
|
||||
name: Generate build version number
|
||||
description: "Generate build version number."
|
||||
|
||||
outputs:
|
||||
version:
|
||||
description: "The generated build version number."
|
||||
value: ${{ steps.version.outputs.version }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
# When a tag is pushed, the version is used as-is.
|
||||
- name: Generate version for tag event
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
shell: bash
|
||||
env:
|
||||
VERSION: ${{ github.ref_name }}
|
||||
run: echo "VERSION=${VERSION}" >>"${GITHUB_ENV}"
|
||||
|
||||
# When a tag is not pushed, then the version (e.g. 1.2.3-b0) is extracted
|
||||
# from the BuildInfo.cpp file and the shortened commit hash appended to it.
|
||||
# We use a plus sign instead of a hyphen because Conan recipe versions do
|
||||
# not support two hyphens.
|
||||
- name: Generate version for non-tag event
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'Extracting version from BuildInfo.cpp.'
|
||||
VERSION="$(cat src/libxrpl/protocol/BuildInfo.cpp | grep "versionString =" | awk -F '"' '{print $2}')"
|
||||
if [[ -z "${VERSION}" ]]; then
|
||||
echo 'Unable to extract version from BuildInfo.cpp.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo 'Appending shortened commit hash to version.'
|
||||
SHA='${{ github.sha }}'
|
||||
VERSION="${VERSION}+${SHA:0:7}"
|
||||
|
||||
echo "VERSION=${VERSION}" >>"${GITHUB_ENV}"
|
||||
|
||||
- name: Output version
|
||||
id: version
|
||||
shell: bash
|
||||
run: echo "version=${VERSION}" >>"${GITHUB_OUTPUT}"
|
||||
90
.github/actions/release-info/action.yml
vendored
Normal file
90
.github/actions/release-info/action.yml
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
name: Release info
|
||||
description: "Derive the version, release channel and package release number for this build."
|
||||
|
||||
outputs:
|
||||
version:
|
||||
description: "The build version number."
|
||||
value: ${{ steps.version.outputs.version }}
|
||||
channel:
|
||||
description: "The release channel this build belongs to."
|
||||
value: ${{ steps.channel.outputs.channel }}
|
||||
pkg_release:
|
||||
description: "The package release number: 1 for a tag, the run number otherwise."
|
||||
value: ${{ steps.pkg_release.outputs.pkg_release }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
# A tag names its own version. Anything else takes it from BuildInfo.cpp and
|
||||
# appends the commit hash as build metadata, joined with a plus sign because a
|
||||
# Conan version cannot contain two hyphens.
|
||||
- name: Determine version
|
||||
id: version
|
||||
shell: bash
|
||||
env:
|
||||
IS_TAG: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
SHA: ${{ github.sha }}
|
||||
run: |
|
||||
if [[ "${IS_TAG}" == "true" ]]; then
|
||||
version="${REF_NAME}"
|
||||
else
|
||||
version="$(awk -F'"' '/versionString =/ { print $2 }' src/libxrpl/protocol/BuildInfo.cpp)"
|
||||
if [[ -z "${version}" ]]; then
|
||||
echo "Unable to read versionString from BuildInfo.cpp." >&2
|
||||
exit 1
|
||||
fi
|
||||
version="${version}+${SHA:0:7}"
|
||||
fi
|
||||
|
||||
echo "version=${version}" | tee -a "${GITHUB_OUTPUT}"
|
||||
|
||||
# Only a tag says how mature a build is: a push is a develop build whatever
|
||||
# its version, and a non-public codebase keeps its packages to itself.
|
||||
- name: Determine release channel
|
||||
id: channel
|
||||
shell: bash
|
||||
env:
|
||||
IS_TAG: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
VISIBILITY: ${{ github.event.repository.visibility }}
|
||||
run: |
|
||||
pre_release=""
|
||||
if [[ "${REF_NAME}" == *-* ]]; then
|
||||
pre_release="${REF_NAME#*-}"
|
||||
fi
|
||||
|
||||
if [[ "${VISIBILITY}" != "public" ]]; then
|
||||
channel=private
|
||||
elif [[ "${IS_TAG}" != "true" ]]; then
|
||||
channel=develop
|
||||
elif [[ -z "${pre_release}" ]]; then
|
||||
channel=stable
|
||||
elif [[ "${pre_release}" =~ ^rc[0-9]+(\+.*)?$ ]]; then
|
||||
channel=unstable
|
||||
elif [[ "${pre_release}" =~ ^b(0|[1-9][0-9]*)(\+.*)?$ ]]; then
|
||||
channel=experimental
|
||||
else
|
||||
echo "Unsupported pre-release in tag '${REF_NAME}'. Use bN or rcN." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "channel=${channel}" | tee -a "${GITHUB_OUTPUT}"
|
||||
|
||||
# A tag is packaged once, so its release number is fixed at 1. Develop builds
|
||||
# repeat the same version, so the run number is what makes each push an
|
||||
# upgrade rather than a reinstall.
|
||||
- name: Determine package release
|
||||
id: pkg_release
|
||||
shell: bash
|
||||
env:
|
||||
IS_TAG: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
RUN_NUMBER: ${{ github.run_number }}
|
||||
run: |
|
||||
if [[ "${IS_TAG}" == "true" ]]; then
|
||||
pkg_release=1
|
||||
else
|
||||
pkg_release="${RUN_NUMBER}"
|
||||
fi
|
||||
|
||||
echo "pkg_release=${pkg_release}" | tee -a "${GITHUB_OUTPUT}"
|
||||
69
.github/actions/setup-nix-env/action.yml
vendored
Normal file
69
.github/actions/setup-nix-env/action.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
name: Setup Nix environment
|
||||
description: "Build the flake's CI environment and put its tools on PATH."
|
||||
|
||||
# The environment from nix/ci-env.nix, the same one the Linux CI images bake in
|
||||
# (see nix/docker). Exported onto PATH rather than entered with `nix develop`:
|
||||
# the composite actions below run plain `bash` and would escape a dev shell.
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
|
||||
steps:
|
||||
- name: Build the CI environment
|
||||
id: build
|
||||
shell: bash
|
||||
env:
|
||||
# --out-link doubles as a GC root for the length of the job.
|
||||
OUT_LINK: ${{ runner.temp }}/xrpld-ci-env
|
||||
run: |
|
||||
# --extra-experimental-features: flakes may not be on in the runner's nix.conf.
|
||||
nix --extra-experimental-features "nix-command flakes" \
|
||||
build .#default --out-link "${OUT_LINK}" --print-build-logs
|
||||
echo "path=$(readlink -f "${OUT_LINK}")" >>"${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Export the environment
|
||||
shell: bash
|
||||
env:
|
||||
ENV_PATH: ${{ steps.build.outputs.path }}
|
||||
run: |
|
||||
echo "${ENV_PATH}/bin" >>"${GITHUB_PATH}"
|
||||
|
||||
# Already KEY=VALUE per line. See `darwinEnv` in nix/ci-env.nix.
|
||||
ENV_FILE="${ENV_PATH}/share/xrpld-ci-env/env"
|
||||
if [ -f "${ENV_FILE}" ]; then
|
||||
cat "${ENV_FILE}" >>"${GITHUB_ENV}"
|
||||
fi
|
||||
|
||||
# XrplSanity.cmake otherwise rejects a Nix compiler as one that leaked.
|
||||
echo "XRPL_DEVSHELL=ci-env" >>"${GITHUB_ENV}"
|
||||
|
||||
# Unlike the Linux nix images, macOS needs no SSL_CERT_FILE: it has its
|
||||
# own trust store, and pinning would break TLS to hosts relying on it.
|
||||
|
||||
# Workspace-local, so `cleanup-workspace` clears it, but not the
|
||||
# `.conan2` prepare-runner hands the system toolchain: that Conan is a
|
||||
# different version, and the two would migrate each other's cache.
|
||||
echo "CONAN_HOME=${{ github.workspace }}/.conan2-nix" >>"${GITHUB_ENV}"
|
||||
|
||||
# Config, profiles and remote, exactly as the dev shell sets them up on
|
||||
# entry; the `setup-conan` action is skipped for this toolchain.
|
||||
- name: Setup Conan
|
||||
shell: bash
|
||||
run: ./conan/init.sh
|
||||
|
||||
# `Check tools` runs later but swallows failures; a bad export would just
|
||||
# build with the system toolchain.
|
||||
- name: Verify the toolchain resolves into the Nix store
|
||||
shell: bash
|
||||
run: |
|
||||
for tool in clang clang++ cmake ninja conan; do
|
||||
path="$(command -v "${tool}" || true)"
|
||||
echo "${tool} -> ${path:-<not found>}"
|
||||
case "${path}" in
|
||||
/nix/store/*) ;;
|
||||
*)
|
||||
echo "::error::${tool} does not resolve into the Nix store"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
18
.github/dependabot.yml
vendored
18
.github/dependabot.yml
vendored
@@ -4,7 +4,7 @@ updates:
|
||||
directories:
|
||||
- /
|
||||
- .github/actions/build-deps/
|
||||
- .github/actions/generate-version/
|
||||
- .github/actions/release-info/
|
||||
- .github/actions/set-compiler-env/
|
||||
- .github/actions/setup-conan/
|
||||
schedule:
|
||||
@@ -19,3 +19,19 @@ updates:
|
||||
github-actions:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
- package-ecosystem: cargo
|
||||
directory: /crates
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: monday
|
||||
time: "04:00"
|
||||
timezone: Etc/GMT
|
||||
commit-message:
|
||||
prefix: "chore: [DEPENDABOT] "
|
||||
target-branch: develop
|
||||
open-pull-requests-limit: 10
|
||||
groups:
|
||||
rust-dependencies:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
2
.github/scripts/rename/binary.sh
vendored
2
.github/scripts/rename/binary.sh
vendored
@@ -49,7 +49,7 @@ ${SED_COMMAND} -i -E 's@ripple/xrpld@XRPLF/rippled@g' BUILD.md
|
||||
${SED_COMMAND} -i -E 's@XRPLF/xrpld@XRPLF/rippled@g' BUILD.md
|
||||
${SED_COMMAND} -i -E 's@xrpld \(`xrpld`\)@xrpld@g' BUILD.md
|
||||
${SED_COMMAND} -i -E 's@XRPLF/xrpld@XRPLF/rippled@g' CONTRIBUTING.md
|
||||
${SED_COMMAND} -i -E 's@XRPLF/xrpld@XRPLF/rippled@g' docs/build/install.md
|
||||
${SED_COMMAND} -i -E 's@XRPLF/xrpld@XRPLF/rippled@g' docs/install.md
|
||||
|
||||
popd
|
||||
echo "Processing complete."
|
||||
|
||||
4
.github/scripts/rename/docs.sh
vendored
4
.github/scripts/rename/docs.sh
vendored
@@ -77,8 +77,8 @@ ${SED_COMMAND} -i 's/Ripple integrators/XRPL developers/' README.md
|
||||
${SED_COMMAND} -i 's/sanitizer-configuration-for-rippled/sanitizer-configuration-for-xrpld/' docs/build/sanitizers.md
|
||||
${SED_COMMAND} -i 's/rippled/xrpld/g' .github/scripts/levelization/README.md
|
||||
${SED_COMMAND} -i 's/rippled/xrpld/g' .github/scripts/strategy-matrix/generate.py
|
||||
${SED_COMMAND} -i 's@/rippled@/xrpld@g' docs/build/install.md
|
||||
${SED_COMMAND} -i 's@github.com/XRPLF/xrpld@github.com/XRPLF/rippled@g' docs/build/install.md
|
||||
${SED_COMMAND} -i 's@/rippled@/xrpld@g' docs/install.md
|
||||
${SED_COMMAND} -i 's@github.com/XRPLF/xrpld@github.com/XRPLF/rippled@g' docs/install.md
|
||||
${SED_COMMAND} -i 's/rippled/xrpld/g' docs/Doxyfile
|
||||
${SED_COMMAND} -i 's/ripple_basics/basics/' include/xrpl/basics/CountedObject.h
|
||||
${SED_COMMAND} -i 's/<ripple/<xrpl/' include/xrpl/protocol/AccountID.h
|
||||
|
||||
20
.github/scripts/strategy-matrix/generate.py
vendored
20
.github/scripts/strategy-matrix/generate.py
vendored
@@ -7,7 +7,13 @@ from pathlib import Path
|
||||
|
||||
THIS_DIR = Path(__file__).parent.resolve()
|
||||
|
||||
_BASE_CMAKE_ARGS = ["-Dtests=ON", "-Dwerr=ON", "-Dxrpld=ON", "-Dwextra=ON"]
|
||||
_BASE_CMAKE_ARGS = [
|
||||
"-Dtests=ON",
|
||||
"-Dwerr=ON",
|
||||
"-Dxrpld=ON",
|
||||
"-Dwextra=ON",
|
||||
"-Drust=ON",
|
||||
]
|
||||
|
||||
# Maps sanitizer names (as used in cmake) to short config-name suffixes.
|
||||
_SANITIZER_SUFFIX: dict[str, str] = {
|
||||
@@ -88,6 +94,9 @@ class PlatformConfig:
|
||||
build_only: bool = False # if true, skip tests (e.g. macos/Windows Debug)
|
||||
benchmark: bool = False # if true, smoke-run the benchmarks after testing
|
||||
extra_cmake_args: str = ""
|
||||
# "" is the runner's system compiler, "nix" the flake's CI environment.
|
||||
# macOS only: Linux always builds in a Nix image, Windows has no Nix.
|
||||
toolchain: str = ""
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if isinstance(self.build_type, str):
|
||||
@@ -137,6 +146,7 @@ class MatrixEntry:
|
||||
sanitizers: str
|
||||
image: str = "" # container image; empty for macOS/Windows (runs natively)
|
||||
compiler: str = "" # compiler name ("gcc" or "clang"); empty for macOS/Windows
|
||||
toolchain: str = "" # "nix" for the flake's CI environment; see PlatformConfig
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
@@ -215,7 +225,7 @@ def expand_linux_matrix(linux: LinuxFile, minimal: bool) -> list[MatrixEntry]:
|
||||
def expand_linux_packaging(linux: LinuxFile) -> list[PackagingEntry]:
|
||||
"""Generate the packaging matrix from a LinuxFile's package_configs section.
|
||||
|
||||
Packaging uses vanilla distro images (debian:bookworm, ubi9, …) instead of
|
||||
Packaging uses vanilla distro images (debian:bookworm, almalinux:9) 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'.
|
||||
@@ -253,9 +263,12 @@ def expand_platform_matrix(pf: PlatformFile, minimal: bool) -> list[MatrixEntry]
|
||||
if minimal and not cfg.minimal:
|
||||
continue
|
||||
for build_type in cfg.build_type:
|
||||
name = f"{platform_name}-{arch}-{build_type.lower()}"
|
||||
if cfg.toolchain:
|
||||
name += f"-{cfg.toolchain}"
|
||||
entries.append(
|
||||
MatrixEntry(
|
||||
config_name=f"{platform_name}-{arch}-{build_type.lower()}",
|
||||
config_name=name,
|
||||
cmake_args=get_cmake_args(build_type, cfg.extra_cmake_args),
|
||||
cmake_target="install" if is_windows else "all",
|
||||
build_only=cfg.build_only,
|
||||
@@ -263,6 +276,7 @@ def expand_platform_matrix(pf: PlatformFile, minimal: bool) -> list[MatrixEntry]
|
||||
build_type=build_type,
|
||||
architecture=Architecture(platform=pf.platform, runner=pf.runner),
|
||||
sanitizers="",
|
||||
toolchain=cfg.toolchain,
|
||||
)
|
||||
)
|
||||
return entries
|
||||
|
||||
6
.github/scripts/strategy-matrix/linux.json
vendored
6
.github/scripts/strategy-matrix/linux.json
vendored
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"image_tag": "sha-fecfc0c",
|
||||
"image_tag": "sha-a0074f8",
|
||||
"configs": {
|
||||
"ubuntu": [
|
||||
{
|
||||
@@ -92,7 +92,7 @@
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"],
|
||||
"minimal": false,
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-577d745"
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-a6983f8"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"],
|
||||
"minimal": false,
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-577d745"
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-a6983f8"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
13
.github/scripts/strategy-matrix/macos.json
vendored
13
.github/scripts/strategy-matrix/macos.json
vendored
@@ -12,6 +12,19 @@
|
||||
"extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
|
||||
"build_only": true,
|
||||
"minimal": false
|
||||
},
|
||||
{
|
||||
"build_type": "Release",
|
||||
"extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
|
||||
"toolchain": "nix",
|
||||
"minimal": false
|
||||
},
|
||||
{
|
||||
"build_type": "Debug",
|
||||
"extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
|
||||
"toolchain": "nix",
|
||||
"build_only": true,
|
||||
"minimal": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
3
.github/workflows/build-packaging-images.yml
vendored
3
.github/workflows/build-packaging-images.yml
vendored
@@ -36,8 +36,9 @@ jobs:
|
||||
distro:
|
||||
- name: debian
|
||||
base_image: debian:bookworm
|
||||
# AlmaLinux rather than UBI9, which does not ship rpm-sign.
|
||||
- name: rhel
|
||||
base_image: registry.access.redhat.com/ubi9/ubi:latest
|
||||
base_image: almalinux:9
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@9e7e4e80af9e684c116b38369add8eea64451f32
|
||||
with:
|
||||
image_name: xrpld/packaging-${{ matrix.distro.name }}
|
||||
|
||||
80
.github/workflows/cargo-audit.yml
vendored
Normal file
80
.github/workflows/cargo-audit.yml
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
name: Cargo audit
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# 06:32 UTC every Monday.
|
||||
- cron: "32 6 * * 1"
|
||||
push:
|
||||
branches:
|
||||
- "develop"
|
||||
- "release/*"
|
||||
paths:
|
||||
- "crates/**/Cargo.toml"
|
||||
- "crates/Cargo.lock"
|
||||
- ".github/workflows/cargo-audit.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "crates/**/Cargo.toml"
|
||||
- "crates/Cargo.lock"
|
||||
- ".github/workflows/cargo-audit.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: crates
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
audit:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-a0074f8
|
||||
permissions:
|
||||
contents: read
|
||||
# Needed to open an issue on scheduled failures.
|
||||
issues: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Run cargo audit
|
||||
id: audit
|
||||
continue-on-error: true
|
||||
run: |
|
||||
set -o pipefail
|
||||
cargo audit | tee /tmp/cargo-audit.txt
|
||||
|
||||
- name: Prepare issue body
|
||||
if: ${{ steps.audit.outcome != 'success' && github.event_name == 'schedule' }}
|
||||
run: |
|
||||
{
|
||||
echo "## \`cargo audit\` found advisories"
|
||||
echo
|
||||
echo '```'
|
||||
cat /tmp/cargo-audit.txt
|
||||
echo '```'
|
||||
echo
|
||||
echo "---"
|
||||
echo "*This issue was automatically created by the cargo-audit workflow.*"
|
||||
} >/tmp/cargo-audit-issue.md
|
||||
|
||||
- name: Create issue
|
||||
if: ${{ steps.audit.outcome != 'success' && github.event_name == 'schedule' }}
|
||||
uses: XRPLF/actions/create-issue@2b8bc36af85b88bca0dd7bfac2e2dc05f94ad712
|
||||
with:
|
||||
title: "cargo audit found vulnerabilities"
|
||||
body_file: /tmp/cargo-audit-issue.md
|
||||
labels: "Bug,Security"
|
||||
|
||||
- name: Fail if advisories were found
|
||||
if: ${{ steps.audit.outcome != 'success' }}
|
||||
run: |
|
||||
echo "cargo audit found advisories!"
|
||||
cat /tmp/cargo-audit.txt
|
||||
exit 1
|
||||
2
.github/workflows/check-tools.yml
vendored
2
.github/workflows/check-tools.yml
vendored
@@ -79,7 +79,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@c00c22ada3bd6bcda48fcb0d62fbbab49fec8a0f
|
||||
uses: XRPLF/actions/prepare-runner@51af40f99ea91a08c3528ddf16d98132dcc7e63c
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
|
||||
17
.github/workflows/on-pr.yml
vendored
17
.github/workflows/on-pr.yml
vendored
@@ -77,24 +77,28 @@ jobs:
|
||||
|
||||
# Keep the paths below in sync with those in `on-trigger.yml`.
|
||||
.github/actions/build-deps/**
|
||||
.github/actions/generate-version/**
|
||||
.github/actions/release-info/**
|
||||
.github/actions/setup-conan/**
|
||||
.github/actions/setup-nix-env/**
|
||||
.github/scripts/strategy-matrix/**
|
||||
.github/workflows/reusable-build-test-config.yml
|
||||
.github/workflows/reusable-build-test.yml
|
||||
.github/workflows/reusable-check-autogen.yml
|
||||
.github/workflows/reusable-clang-tidy.yml
|
||||
.github/workflows/reusable-package.yml
|
||||
.github/workflows/reusable-rust.yml
|
||||
.github/workflows/reusable-strategy-matrix.yml
|
||||
.github/workflows/reusable-test.yml
|
||||
.github/workflows/reusable-upload-recipe.yml
|
||||
.clang-tidy
|
||||
.codecov.yml
|
||||
bin/check-nix-store-refs.sh
|
||||
bin/check-tools.sh
|
||||
bin/default-loader-path.sh
|
||||
cfg/**
|
||||
cmake/**
|
||||
conan/**
|
||||
crates/**
|
||||
external/**
|
||||
include/**
|
||||
src/**
|
||||
@@ -102,6 +106,9 @@ jobs:
|
||||
CMakeLists.txt
|
||||
conanfile.py
|
||||
conan.lock
|
||||
flake.lock
|
||||
flake.nix
|
||||
nix/**
|
||||
LICENSE.md
|
||||
package/**
|
||||
README.md
|
||||
@@ -168,6 +175,13 @@ jobs:
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
rust:
|
||||
needs: should-run
|
||||
if: ${{ needs.should-run.outputs.go == 'true' }}
|
||||
uses: ./.github/workflows/reusable-rust.yml
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
package:
|
||||
needs: [should-run, build-test]
|
||||
# Packaging consumes the debian/rhel release binaries, which are only built
|
||||
@@ -211,6 +225,7 @@ jobs:
|
||||
- check-rename
|
||||
- clang-tidy
|
||||
- build-test
|
||||
- rust
|
||||
- package
|
||||
- upload-recipe
|
||||
- notify-clio
|
||||
|
||||
18
.github/workflows/on-tag.yml
vendored
18
.github/workflows/on-tag.yml
vendored
@@ -1,5 +1,9 @@
|
||||
# This workflow uploads the libxrpl recipe to the Conan remote and builds
|
||||
# release packages when a versioned tag is pushed.
|
||||
# When a versioned tag is pushed, this workflow:
|
||||
#
|
||||
# - uploads the libxrpl recipe to the Conan remote
|
||||
# - builds and tests the release binaries
|
||||
# - builds the DEB and RPM packages
|
||||
# - publishes those packages to the XRPLF package repositories
|
||||
name: Tag
|
||||
|
||||
on:
|
||||
@@ -24,7 +28,7 @@ jobs:
|
||||
remote_password: ${{ secrets.NEXUS_REMOTE_PASSWORD }}
|
||||
|
||||
build-test:
|
||||
if: ${{ github.repository == 'XRPLF/rippled' }}
|
||||
if: ${{ github.repository_owner == 'XRPLF' }}
|
||||
uses: ./.github/workflows/reusable-build-test.yml
|
||||
strategy:
|
||||
fail-fast: true
|
||||
@@ -37,6 +41,12 @@ jobs:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
package:
|
||||
if: ${{ github.repository == 'XRPLF/rippled' }}
|
||||
if: ${{ github.repository_owner == 'XRPLF' }}
|
||||
needs: build-test
|
||||
uses: ./.github/workflows/reusable-package.yml
|
||||
with:
|
||||
publish: true
|
||||
secrets:
|
||||
remote_username: ${{ secrets.NEXUS_REMOTE_USERNAME }}
|
||||
remote_password: ${{ secrets.NEXUS_REMOTE_PASSWORD }}
|
||||
signing_key: ${{ secrets.NEXUS_PACKAGES_PRIVATE_KEY }}
|
||||
|
||||
22
.github/workflows/on-trigger.yml
vendored
22
.github/workflows/on-trigger.yml
vendored
@@ -15,24 +15,28 @@ on:
|
||||
|
||||
# Keep the paths below in sync with those in `on-pr.yml`.
|
||||
- ".github/actions/build-deps/**"
|
||||
- ".github/actions/generate-version/**"
|
||||
- ".github/actions/release-info/**"
|
||||
- ".github/actions/setup-conan/**"
|
||||
- ".github/actions/setup-nix-env/**"
|
||||
- ".github/scripts/strategy-matrix/**"
|
||||
- ".github/workflows/reusable-build-test-config.yml"
|
||||
- ".github/workflows/reusable-build-test.yml"
|
||||
- ".github/workflows/reusable-check-autogen.yml"
|
||||
- ".github/workflows/reusable-clang-tidy.yml"
|
||||
- ".github/workflows/reusable-package.yml"
|
||||
- ".github/workflows/reusable-rust.yml"
|
||||
- ".github/workflows/reusable-strategy-matrix.yml"
|
||||
- ".github/workflows/reusable-test.yml"
|
||||
- ".github/workflows/reusable-upload-recipe.yml"
|
||||
- ".clang-tidy"
|
||||
- ".codecov.yml"
|
||||
- "bin/check-nix-store-refs.sh"
|
||||
- "bin/check-tools.sh"
|
||||
- "bin/default-loader-path.sh"
|
||||
- "cfg/**"
|
||||
- "cmake/**"
|
||||
- "conan/**"
|
||||
- "crates/**"
|
||||
- "external/**"
|
||||
- "include/**"
|
||||
- "src/**"
|
||||
@@ -40,6 +44,9 @@ on:
|
||||
- "CMakeLists.txt"
|
||||
- "conanfile.py"
|
||||
- "conan.lock"
|
||||
- "flake.lock"
|
||||
- "flake.nix"
|
||||
- "nix/**"
|
||||
- "LICENSE.md"
|
||||
- "package/**"
|
||||
- "README.md"
|
||||
@@ -96,6 +103,11 @@ jobs:
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
rust:
|
||||
uses: ./.github/workflows/reusable-rust.yml
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
upload-recipe:
|
||||
needs: build-test
|
||||
# Only run when pushing to the develop branch.
|
||||
@@ -108,3 +120,11 @@ jobs:
|
||||
package:
|
||||
needs: build-test
|
||||
uses: ./.github/workflows/reusable-package.yml
|
||||
with:
|
||||
# Packages are built on every trigger; only develop pushes in XRPLF/rippled
|
||||
# publish them, matching upload-recipe above.
|
||||
publish: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' && github.ref == 'refs/heads/develop' }}
|
||||
secrets:
|
||||
remote_username: ${{ secrets.NEXUS_REMOTE_USERNAME }}
|
||||
remote_password: ${{ secrets.NEXUS_REMOTE_PASSWORD }}
|
||||
signing_key: ${{ secrets.NEXUS_PACKAGES_PRIVATE_KEY }}
|
||||
|
||||
4
.github/workflows/publish-docs.yml
vendored
4
.github/workflows/publish-docs.yml
vendored
@@ -41,13 +41,13 @@ env:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-fecfc0c
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-a0074f8
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@c00c22ada3bd6bcda48fcb0d62fbbab49fec8a0f
|
||||
uses: XRPLF/actions/prepare-runner@51af40f99ea91a08c3528ddf16d98132dcc7e63c
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
|
||||
59
.github/workflows/reusable-build-test-config.yml
vendored
59
.github/workflows/reusable-build-test-config.yml
vendored
@@ -69,6 +69,12 @@ on:
|
||||
type: string
|
||||
default: ""
|
||||
|
||||
toolchain:
|
||||
description: 'Where the toolchain comes from ("nix" to build the flake CI environment on the runner, empty for the system one). macOS only: Linux always builds in a Nix image, and Nix has no Windows support.'
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
|
||||
secrets:
|
||||
CODECOV_TOKEN:
|
||||
description: "The Codecov token to use for uploading coverage reports."
|
||||
@@ -111,6 +117,9 @@ jobs:
|
||||
VOIDSTAR_ENABLED: ${{ contains(inputs.cmake_args, '-Dvoidstar=ON') }}
|
||||
VALIDATOR_KEYS_ENABLED: ${{ contains(inputs.cmake_args, '-Dvalidator_keys=ON') }}
|
||||
SANITIZERS_ENABLED: ${{ inputs.sanitizers != '' }}
|
||||
# The binaries reusable-package.yml consumes. A private repository skips
|
||||
# them except on a tag push, which is what produces its release packages.
|
||||
PACKAGING_ARTIFACTS_ENABLED: ${{ github.event.repository.visibility == 'public' || startsWith(github.ref, 'refs/tags/') }}
|
||||
steps:
|
||||
- name: Cleanup workspace (macOS and Windows)
|
||||
if: ${{ runner.os == 'macOS' || runner.os == 'Windows' }}
|
||||
@@ -120,10 +129,15 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@c00c22ada3bd6bcda48fcb0d62fbbab49fec8a0f
|
||||
uses: XRPLF/actions/prepare-runner@51af40f99ea91a08c3528ddf16d98132dcc7e63c
|
||||
with:
|
||||
enable_ccache: ${{ inputs.ccache_enabled }}
|
||||
|
||||
# Before any step that uses a build tool, composite actions included.
|
||||
- name: Setup Nix environment
|
||||
if: ${{ inputs.toolchain == 'nix' }}
|
||||
uses: ./.github/actions/setup-nix-env
|
||||
|
||||
- name: Set ccache log file
|
||||
if: ${{ inputs.ccache_enabled && runner.debug == '1' }}
|
||||
run: echo "CCACHE_LOGFILE=${{ runner.temp }}/ccache.log" >>"${GITHUB_ENV}"
|
||||
@@ -148,7 +162,22 @@ jobs:
|
||||
with:
|
||||
compiler: ${{ inputs.compiler }}
|
||||
|
||||
- name: Use cargo artifacts cache
|
||||
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
cache-directories: ${{ env.BUILD_DIR }}/corrosion
|
||||
key: ${{ inputs.config_name }}
|
||||
save-if: ${{ github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/heads/release') }}
|
||||
# two workspaces here because build artifacts are located in 2 places:
|
||||
# - crates/target when cargo is called directly
|
||||
# - build/cargo when cargo is called by cmake
|
||||
workspaces: |
|
||||
crates
|
||||
crates -> ${{ runner.os == 'Windows' && format('../{0}/x64/{1}/cargo', env.BUILD_DIR, inputs.build_type) || format('../{0}/cargo', env.BUILD_DIR) }}
|
||||
|
||||
# `setup-nix-env` already did this for the Nix toolchain.
|
||||
- name: Setup Conan
|
||||
if: ${{ inputs.toolchain != 'nix' }}
|
||||
env:
|
||||
SANITIZERS: ${{ inputs.sanitizers }}
|
||||
uses: ./.github/actions/setup-conan
|
||||
@@ -212,6 +241,24 @@ jobs:
|
||||
--target "${CMAKE_TARGET}" \
|
||||
2>&1 | tee "${GITHUB_WORKSPACE}/build.log"
|
||||
|
||||
# Nothing may reference the store, so whole trees are checked - the Conan
|
||||
# cache included, since what it holds is what gets uploaded and reused.
|
||||
- name: Check the build output for Nix store references (Nix toolchain)
|
||||
if: ${{ inputs.toolchain == 'nix' }}
|
||||
run: ./bin/check-nix-store-refs.sh "${BUILD_DIR}"
|
||||
|
||||
- name: Check the Conan cache for Nix store references (Nix toolchain)
|
||||
if: ${{ inputs.toolchain == 'nix' }}
|
||||
run: ./bin/check-nix-store-refs.sh "${CONAN_HOME}"
|
||||
|
||||
# Only what PatchNixBinary.cmake retargets: the toolchain in the Linux
|
||||
# images always references the store. Same condition it uses.
|
||||
- name: Check for Nix store references (Linux)
|
||||
if: ${{ runner.os == 'Linux' && env.SANITIZERS_ENABLED == 'false' }}
|
||||
run: |
|
||||
./bin/check-nix-store-refs.sh "${BUILD_DIR}/xrpld"
|
||||
./bin/check-nix-store-refs.sh "${BUILD_DIR}/xrpl_tests"
|
||||
|
||||
- name: Show ccache statistics
|
||||
if: ${{ inputs.ccache_enabled }}
|
||||
run: |
|
||||
@@ -222,7 +269,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Upload the binary (Linux)
|
||||
if: ${{ github.event.repository.visibility == 'public' && runner.os == 'Linux' }}
|
||||
if: ${{ env.PACKAGING_ARTIFACTS_ENABLED == 'true' && runner.os == 'Linux' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: xrpld-${{ inputs.config_name }}
|
||||
@@ -236,7 +283,7 @@ jobs:
|
||||
run: ./validator-keys --unittest
|
||||
|
||||
- name: Upload the validator-keys binary
|
||||
if: ${{ github.event.repository.visibility == 'public' && env.VALIDATOR_KEYS_ENABLED == 'true' }}
|
||||
if: ${{ env.PACKAGING_ARTIFACTS_ENABLED == 'true' && env.VALIDATOR_KEYS_ENABLED == 'true' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: validator-keys-${{ inputs.config_name }}
|
||||
@@ -323,6 +370,11 @@ jobs:
|
||||
|
||||
LD_PRELOAD="$PRELOAD" ./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee "${GITHUB_WORKSPACE}/unittest.log"
|
||||
|
||||
- name: Run Rust tests
|
||||
if: ${{ !inputs.build_only }}
|
||||
working-directory: crates
|
||||
run: cargo nextest run --workspace --all-features --locked --no-tests=warn
|
||||
|
||||
# Smoke-run every benchmark module with a single repetition to confirm the
|
||||
# benchmarks still build and execute. This is a correctness check, not a
|
||||
# performance measurement, so there is nothing to gain from repeating it
|
||||
@@ -394,6 +446,7 @@ jobs:
|
||||
disable_telem: true
|
||||
fail_ci_if_error: true
|
||||
files: ${{ env.BUILD_DIR }}/coverage.xml
|
||||
flags: cpp
|
||||
plugins: noop
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
verbose: true
|
||||
|
||||
1
.github/workflows/reusable-build-test.yml
vendored
1
.github/workflows/reusable-build-test.yml
vendored
@@ -51,5 +51,6 @@ jobs:
|
||||
config_name: ${{ matrix.config_name }}
|
||||
sanitizers: ${{ matrix.sanitizers }}
|
||||
compiler: ${{ matrix.compiler || '' }}
|
||||
toolchain: ${{ matrix.toolchain || '' }}
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
17
.github/workflows/reusable-clang-tidy.yml
vendored
17
.github/workflows/reusable-clang-tidy.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
needs: [determine-files]
|
||||
if: ${{ needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.need_full_run == 'true' }}
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-fecfc0c"
|
||||
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-a0074f8"
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@c00c22ada3bd6bcda48fcb0d62fbbab49fec8a0f
|
||||
uses: XRPLF/actions/prepare-runner@51af40f99ea91a08c3528ddf16d98132dcc7e63c
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
@@ -59,6 +59,13 @@ jobs:
|
||||
with:
|
||||
compiler: ${{ env.COMPILER }}
|
||||
|
||||
- name: Use cargo artifacts cache
|
||||
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
cache-directories: ${{ env.BUILD_DIR }}/corrosion
|
||||
save-if: ${{ github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/heads/release') }}
|
||||
workspaces: crates -> ../${{ env.BUILD_DIR }}/cargo
|
||||
|
||||
- name: Setup Conan
|
||||
uses: ./.github/actions/setup-conan
|
||||
|
||||
@@ -80,13 +87,13 @@ jobs:
|
||||
-Dwerr=ON \
|
||||
-Dxrpld=ON \
|
||||
-Dverify_headers=ON \
|
||||
-Drust=ON \
|
||||
..
|
||||
|
||||
# clang-tidy needs headers generated from proto files
|
||||
- name: Build libxrpl.libpb
|
||||
- name: Build clang-tidy prerequisites
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
run: |
|
||||
ninja -j ${{ steps.nproc.outputs.nproc }} xrpl.libpb
|
||||
ninja -j ${{ steps.nproc.outputs.nproc }} tidy_prerequisites
|
||||
|
||||
- name: Run clang tidy
|
||||
id: run_clang_tidy
|
||||
|
||||
60
.github/workflows/reusable-package.yml
vendored
60
.github/workflows/reusable-package.yml
vendored
@@ -1,17 +1,37 @@
|
||||
# Build Linux packages (DEB and RPM) from pre-built binary artifacts (xrpld and
|
||||
# validator-keys). 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.
|
||||
# Build Linux packages from the pre-built xrpld and validator-keys artifacts:
|
||||
#
|
||||
# - one job per distro, taken from "package_configs" in linux.json
|
||||
# - each job runs in that distro's container, which is what decides DEB or RPM
|
||||
# - with 'publish: true' a job also uploads what it built
|
||||
# (see package/publish_pkg.sh)
|
||||
#
|
||||
# Only linux/amd64 is supported; the runner is hardcoded in the job below.
|
||||
name: Package
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
pkg_release:
|
||||
description: "Package release number. Increment when repackaging the same executable."
|
||||
publish:
|
||||
description: "Whether to publish the packages after building them."
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
nexus_url:
|
||||
description: "The base URL of the Nexus instance hosting the deb and rpm repositories."
|
||||
required: false
|
||||
type: string
|
||||
default: "1"
|
||||
default: https://packages.xrplf.org
|
||||
|
||||
secrets:
|
||||
remote_username:
|
||||
description: "The username of a Nexus account with write access to the repositories."
|
||||
required: false
|
||||
remote_password:
|
||||
description: "The password or token for that Nexus account."
|
||||
required: false
|
||||
signing_key:
|
||||
description: "Armoured PGP private key used to sign the RPMs. Required when publishing."
|
||||
required: false
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -41,7 +61,7 @@ jobs:
|
||||
|
||||
package:
|
||||
needs: [generate-matrix]
|
||||
if: ${{ github.event.repository.visibility == 'public' }}
|
||||
if: ${{ github.event.repository.visibility == 'public' || startsWith(github.ref, 'refs/tags/') }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
|
||||
@@ -71,11 +91,24 @@ jobs:
|
||||
- name: Make binaries executable
|
||||
run: chmod +x "${BUILD_DIR}/xrpld" "${BUILD_DIR}/validator-keys"
|
||||
|
||||
- name: Determine release info
|
||||
id: release_info
|
||||
uses: ./.github/actions/release-info
|
||||
|
||||
- name: Build package
|
||||
env:
|
||||
PKG_RELEASE: ${{ inputs.pkg_release }}
|
||||
PKG_RELEASE: ${{ steps.release_info.outputs.pkg_release }}
|
||||
PKG_CHANNEL: ${{ steps.release_info.outputs.channel }}
|
||||
run: ./package/build_pkg.sh
|
||||
|
||||
# Before the upload, so the artifact and the published package are the
|
||||
# same bytes. DEBs are not signed, so the key is never set on that job.
|
||||
- name: Sign RPM
|
||||
if: ${{ inputs.publish && matrix.distro == 'rhel' }}
|
||||
env:
|
||||
PKG_SIGNING_KEY: ${{ secrets.signing_key }}
|
||||
run: ./package/sign_rpm.sh "${BUILD_DIR}"
|
||||
|
||||
- name: Upload package artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
@@ -85,3 +118,12 @@ jobs:
|
||||
${{ env.BUILD_DIR }}/debbuild/*.ddeb
|
||||
${{ env.BUILD_DIR }}/rpmbuild/RPMS/**/*.rpm
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Publish package
|
||||
if: ${{ inputs.publish }}
|
||||
env:
|
||||
CHANNEL: ${{ steps.release_info.outputs.channel }}
|
||||
NEXUS_URL: ${{ inputs.nexus_url }}
|
||||
NEXUS_USERNAME: ${{ secrets.remote_username }}
|
||||
NEXUS_PASSWORD: ${{ secrets.remote_password }}
|
||||
run: ./package/publish_pkg.sh "${CHANNEL}" "${BUILD_DIR}"
|
||||
|
||||
86
.github/workflows/reusable-rust.yml
vendored
Normal file
86
.github/workflows/reusable-rust.yml
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
# Clippy, coverage and documentation for the Rust crates in crates/. Each runs
|
||||
# as an independent job on a GitHub-hosted runner, but inside the same container
|
||||
# image used to build the crates in the C++/Corrosion path, so the toolchain
|
||||
# (and therefore the lints, coverage instrumentation and the cargo cache) matches
|
||||
# what production builds use.
|
||||
#
|
||||
# Rust unit tests are deliberately NOT run here. They run as part of the C++
|
||||
# build (reusable-build-test-config.yml), which already compiles the crates on a
|
||||
# self-hosted runner, so there is no need to provision a toolchain again.
|
||||
name: Rust
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
CODECOV_TOKEN:
|
||||
description: "The Codecov token to use for uploading coverage reports."
|
||||
required: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: crates
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-a0074f8
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Use cargo artifacts cache
|
||||
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
workspaces: crates
|
||||
|
||||
- name: Run clippy
|
||||
run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
|
||||
|
||||
coverage:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-a0074f8
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Use cargo artifacts cache
|
||||
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
workspaces: crates
|
||||
|
||||
- name: Generate coverage report
|
||||
run: cargo llvm-cov nextest --workspace --all-features --locked --no-tests=warn --lcov --output-path lcov.info
|
||||
|
||||
- name: Upload coverage report
|
||||
if: ${{ github.repository == 'XRPLF/rippled' }}
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
with:
|
||||
disable_search: true
|
||||
disable_telem: true
|
||||
fail_ci_if_error: true
|
||||
files: crates/lcov.info
|
||||
flags: rust
|
||||
plugins: noop
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
verbose: true
|
||||
|
||||
doc:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-a0074f8
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Use cargo artifacts cache
|
||||
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
workspaces: crates
|
||||
|
||||
- name: Build documentation
|
||||
env:
|
||||
RUSTDOCFLAGS: "-D warnings"
|
||||
run: cargo doc --workspace --no-deps --all-features --locked
|
||||
14
.github/workflows/reusable-upload-recipe.yml
vendored
14
.github/workflows/reusable-upload-recipe.yml
vendored
@@ -40,7 +40,7 @@ defaults:
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-fecfc0c
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-a0074f8
|
||||
env:
|
||||
REMOTE_NAME: ${{ inputs.remote_name }}
|
||||
CONAN_LOGIN_USERNAME_XRPLF: ${{ secrets.remote_username }}
|
||||
@@ -49,9 +49,9 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Generate build version number
|
||||
id: version
|
||||
uses: ./.github/actions/generate-version
|
||||
- name: Determine release info
|
||||
id: release_info
|
||||
uses: ./.github/actions/release-info
|
||||
|
||||
- name: Set up Conan
|
||||
uses: ./.github/actions/setup-conan
|
||||
@@ -64,8 +64,8 @@ jobs:
|
||||
|
||||
- name: Upload Conan recipe (version)
|
||||
run: |
|
||||
conan export . --version=${{ steps.version.outputs.version }}
|
||||
conan upload --confirm --check --remote="${REMOTE_NAME}" xrpl/${{ steps.version.outputs.version }}
|
||||
conan export . --version=${{ steps.release_info.outputs.version }}
|
||||
conan upload --confirm --check --remote="${REMOTE_NAME}" xrpl/${{ steps.release_info.outputs.version }}
|
||||
|
||||
# When this workflow is triggered by a push event, it will always be when merging into the
|
||||
# 'develop' branch, see on-trigger.yml.
|
||||
@@ -92,4 +92,4 @@ jobs:
|
||||
conan upload --confirm --check --remote="${REMOTE_NAME}" xrpl/release
|
||||
|
||||
outputs:
|
||||
ref: xrpl/${{ steps.version.outputs.version }}
|
||||
ref: xrpl/${{ steps.release_info.outputs.version }}
|
||||
|
||||
11
.github/workflows/upload-conan-deps.yml
vendored
11
.github/workflows/upload-conan-deps.yml
vendored
@@ -72,6 +72,11 @@ jobs:
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
# Before any step that uses a build tool, composite actions included.
|
||||
- name: Setup Nix environment
|
||||
if: ${{ matrix.toolchain == 'nix' }}
|
||||
uses: ./.github/actions/setup-nix-env
|
||||
|
||||
- name: Print build environment
|
||||
uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574
|
||||
|
||||
@@ -87,7 +92,9 @@ jobs:
|
||||
with:
|
||||
compiler: ${{ matrix.compiler }}
|
||||
|
||||
# `setup-nix-env` already did this for the Nix toolchain.
|
||||
- name: Setup Conan
|
||||
if: ${{ matrix.toolchain != 'nix' }}
|
||||
env:
|
||||
SANITIZERS: ${{ matrix.sanitizers }}
|
||||
uses: ./.github/actions/setup-conan
|
||||
@@ -106,6 +113,10 @@ jobs:
|
||||
log_verbosity: ${{ runner.os == 'Windows' && 'quiet' || 'verbose' }}
|
||||
sanitizers: ${{ matrix.sanitizers }}
|
||||
|
||||
- name: Check the Conan cache for Nix store references (Nix toolchain)
|
||||
if: ${{ matrix.toolchain == 'nix' }}
|
||||
run: ./bin/check-nix-store-refs.sh "${CONAN_HOME}"
|
||||
|
||||
- name: Log into Conan remote
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') }}
|
||||
run: conan remote login "${CONAN_REMOTE_NAME}" "${{ secrets.NEXUS_REMOTE_USERNAME }}" --password "${{ secrets.NEXUS_REMOTE_PASSWORD }}"
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -89,3 +89,6 @@ target/
|
||||
|
||||
# clangd cache
|
||||
/.cache
|
||||
|
||||
# Rust build directory
|
||||
crates/target
|
||||
|
||||
@@ -62,6 +62,15 @@ repos:
|
||||
types_or: [c++, c, proto]
|
||||
exclude: ^include/xrpl/protocol_autogen/(transactions|ledger_entries)/
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: cargo-fmt
|
||||
name: cargo fmt
|
||||
entry: cargo fmt --manifest-path crates/Cargo.toml --all
|
||||
language: system
|
||||
types: [rust]
|
||||
pass_filenames: false # rustfmt formats the whole workspace
|
||||
|
||||
- repo: https://github.com/BlankSpruce/gersemi-pre-commit
|
||||
rev: e98930bdc210d3387007f9252d8c1694ea7e410f # frozen: 0.27.7
|
||||
hooks:
|
||||
|
||||
@@ -54,6 +54,8 @@ This section contains changes targeting a future version.
|
||||
- `submit`: The `fail_hard` field now returns an error if the value is not a boolean. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
- `subscribe`: The `taker` field in the `books` array now returns `actMalformed` instead of `badIssuer` if the value is not a valid account. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
- Fixed a bug in `Forwarded` HTTP header parsing where the extracted IP address could be incorrect when no comma or semicolon delimiter follows the address. This could cause the server to misidentify a client's IP address when operating behind a reverse proxy. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
- `gateway_balances`: The `account` and `ident` fields now return an `invalidParams` error if the value is not a string, instead of an `internal` error. [#7655](https://github.com/XRPLF/rippled/pull/7655)
|
||||
- `account_lines`: The `peer` field now returns an error if the value is not a string. [#7728](https://github.com/XRPLF/rippled/pull/7728)
|
||||
|
||||
## XRP Ledger server version 3.1.0
|
||||
|
||||
|
||||
111
BUILD.md
111
BUILD.md
@@ -4,34 +4,14 @@
|
||||
|
||||
## Minimum Requirements
|
||||
|
||||
See [System Requirements](https://xrpl.org/system-requirements.html).
|
||||
For the hardware needed to run a node, see
|
||||
[System Requirements](https://xrpl.org/system-requirements.html).
|
||||
|
||||
Building xrpld generally requires Git, Python, Conan, CMake, and a C++
|
||||
compiler.
|
||||
|
||||
- [Python](https://www.python.org/downloads/)
|
||||
- [Conan](https://conan.io/downloads.html)
|
||||
- [CMake](https://cmake.org/download/)
|
||||
|
||||
You can verify that the required tools are installed and runnable with:
|
||||
|
||||
```bash
|
||||
./bin/check-tools.sh
|
||||
```
|
||||
|
||||
`xrpld` is written in the C++23 dialect. The [tested compiler versions][cpp23-support] are:
|
||||
|
||||
| Compiler | Version |
|
||||
| ----------- | --------------- |
|
||||
| GCC | 15.2 |
|
||||
| Clang | 22 |
|
||||
| Apple Clang | 21 |
|
||||
| MSVC | 19.44[^windows] |
|
||||
For the software needed to build xrpld, see the
|
||||
[environment setup guide](./docs/build/environment.md).
|
||||
|
||||
## Operating Systems
|
||||
|
||||
Please see the [environment setup guide](./docs/build/environment.md) for detailed instructions for all platforms.
|
||||
|
||||
### Linux
|
||||
|
||||
The Ubuntu Linux distribution has received the highest level of quality
|
||||
@@ -47,9 +27,8 @@ CI testing is done in macOS 26 (Tahoe), but the build defaults `CMAKE_OSX_DEPLOY
|
||||
|
||||
### Windows
|
||||
|
||||
Windows is used by some engineers for development only.
|
||||
|
||||
[^windows]: Windows is not recommended for production use.
|
||||
Windows is used by some engineers for development only, and is not recommended
|
||||
for production use.
|
||||
|
||||
## Steps
|
||||
|
||||
@@ -74,37 +53,25 @@ releases](https://github.com/XRPLF/rippled/releases).
|
||||
|
||||
### Set Up Conan
|
||||
|
||||
After you have a [C++ development environment](./docs/build/environment.md) ready with Git, Python,
|
||||
Conan, CMake, and a C++ compiler, you may need to set up your Conan profile.
|
||||
|
||||
These instructions assume a basic familiarity with Conan and CMake. If you are
|
||||
unfamiliar with Conan, then please read [this crash course](./docs/build/conan.md) or the official
|
||||
[Getting Started][conan-getting-started] walkthrough.
|
||||
|
||||
#### Profiles
|
||||
|
||||
We recommend that you install our Conan profiles:
|
||||
Once your [development environment](./docs/build/environment.md) is ready, set
|
||||
Conan up for this repository:
|
||||
|
||||
```bash
|
||||
conan config install conan/profiles/ -tf $(conan config home)/profiles/
|
||||
./conan/init.sh
|
||||
```
|
||||
|
||||
You can check your Conan profile by running:
|
||||
That installs our [`global.conf`](./conan/global.conf), our Conan
|
||||
[profiles](./conan/profiles), and the `xrplf` remote that hosts some of our
|
||||
dependencies. It honours `CONAN_HOME` and never deletes an existing Conan home,
|
||||
so it is safe to re-run — it only overwrites the files it manages.
|
||||
|
||||
```bash
|
||||
conan profile show
|
||||
```
|
||||
> [!TIP]
|
||||
> In the [Nix development shell](./docs/build/nix.md#conan-configuration) this is
|
||||
> already done for you: the script runs on entry.
|
||||
|
||||
If the default profile is not suitable for your environment, you can create a custom profile and pass it to Conan.
|
||||
More information on customizing Conan can be found in the [Advanced Conan configuration](./docs/build/advanced_conan.md).
|
||||
|
||||
#### Add xrplf remote
|
||||
|
||||
Run the following command to add the `xrplf` remote, which hosts some of our dependencies:
|
||||
|
||||
```bash
|
||||
conan remote add --index 0 --force xrplf https://conan.xrplf.org/repository/conan/
|
||||
```
|
||||
You can inspect the resulting profile with `conan profile show`. If it is not
|
||||
suitable for your environment, create a custom profile and pass it to Conan — see
|
||||
[Advanced Conan configuration](./docs/build/advanced_conan.md).
|
||||
|
||||
### Set Up Ccache
|
||||
|
||||
@@ -269,10 +236,14 @@ which is only enabled when the `coverage` option is set, e.g. with
|
||||
Prerequisites for the coverage report:
|
||||
|
||||
- [gcovr tool][gcovr] (can be installed e.g. with [pip][python-pip])
|
||||
- `gcov` for GCC (installed with the compiler by default) or
|
||||
- `llvm-cov` for Clang (installed with the compiler by default)
|
||||
- `gcov` for GCC or `llvm-cov` for Clang, usually installed with the compiler
|
||||
- `Debug` build type
|
||||
|
||||
> [!NOTE]
|
||||
> Clang coverage is not available in the [Nix development shell](./docs/build/nix.md#building-xrpld-in-the-nix-shell):
|
||||
> its `clang` shells do not ship `llvm-cov`. Use a `gcc` shell instead (`.#gcc`,
|
||||
> or `.#gcc-plain` on Linux), which provides a `gcov` matching its compiler.
|
||||
|
||||
A coverage report is created when the following steps are completed, in order:
|
||||
|
||||
1. `xrpld` binary built with instrumentation data, enabled by the `coverage`
|
||||
@@ -333,6 +304,7 @@ See [Sanitizers docs](./docs/build/sanitizers.md) for more details.
|
||||
| ---------------- | ------------- | ----------------------------------------------------------------------------- |
|
||||
| `assert` | OFF | Force enabling assertions. |
|
||||
| `coverage` | OFF | Prepare the coverage report. |
|
||||
| `rust` | OFF | Build the Rust crates and the C++ code that depends on them. |
|
||||
| `tests` | OFF | Build tests. |
|
||||
| `unity` | OFF | Configure a unity build. |
|
||||
| `verify_headers` | ON | Make the `verify-headers` target available to compile each header on its own. |
|
||||
@@ -345,6 +317,30 @@ memory) since they concatenate sources into fewer translation units. Non-unity
|
||||
builds may be faster for incremental builds, and can be helpful for detecting
|
||||
`#include` omissions.
|
||||
|
||||
### Rust crates
|
||||
|
||||
The Rust crates in `crates/` are only part of the build when `rust` is ON. With
|
||||
`-Drust=OFF` (the default) the `crates` directory is not added to the build, no
|
||||
cxxbridge bindings are generated, and the C++ tests that exercise the Rust
|
||||
interop are not compiled — so no Rust toolchain is needed. CI builds always pass
|
||||
`-Drust=ON`.
|
||||
|
||||
With `-Drust=ON` you need one extra dependency: a Rust toolchain (`cargo`,
|
||||
`rustc`) matching the channel pinned in
|
||||
[`rust-toolchain.toml`](./rust-toolchain.toml), which compiles the crates and
|
||||
generates the cxxbridge bindings. It is provided by the
|
||||
[Nix development shell](./docs/build/nix.md), so `-Drust=ON` works there without
|
||||
any extra setup; otherwise install it as described in
|
||||
[Rust](./docs/build/environment.md#rust).
|
||||
|
||||
The crates also have their own Rust unit tests. Those are run with `cargo` and
|
||||
need only the Rust toolchain, independently of CMake and of the `rust` option
|
||||
(CI runs them with `cargo nextest`):
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path crates/Cargo.toml --workspace
|
||||
```
|
||||
|
||||
### Verifying headers
|
||||
|
||||
The regular build only compiles `.cpp` files, so a header is only ever checked
|
||||
@@ -389,10 +385,14 @@ After any updates or changes to dependencies, you may need to do the following:
|
||||
4. [Regenerate lockfile](./docs/build/advanced_conan.md#conan-lockfile).
|
||||
5. Re-run [conan install](#build-and-test).
|
||||
|
||||
If you are using the Nix development shell, whether prebuilt Conan binaries apply
|
||||
depends on your platform — see
|
||||
[Prebuilt packages](./docs/build/nix.md#prebuilt-packages).
|
||||
|
||||
#### ERROR: Package not resolved
|
||||
|
||||
If you're seeing an error like `ERROR: Package 'snappy/1.1.10' not resolved: Unable to find 'snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1756234314.246' in remotes.`,
|
||||
please [add `xrplf` remote](#add-xrplf-remote) or re-run `conan export` for [patched recipes](./docs/build/advanced_conan.md#patched-recipes).
|
||||
please [set Conan up](#set-up-conan) so the `xrplf` remote is configured, or re-run `conan export` for [patched recipes](./docs/build/advanced_conan.md#patched-recipes).
|
||||
|
||||
### `protobuf/port_def.inc` file not found
|
||||
|
||||
@@ -412,7 +412,6 @@ For example, if you want to build Debug:
|
||||
1. For conan install, pass `--settings build_type=Debug`
|
||||
2. For cmake, pass `-DCMAKE_BUILD_TYPE=Debug`
|
||||
|
||||
[cpp23-support]: https://en.cppreference.com/w/cpp/compiler_support/23
|
||||
[conan-getting-started]: https://docs.conan.io/en/latest/getting_started.html
|
||||
[unity-build]: https://en.wikipedia.org/wiki/Unity_build
|
||||
[gcovr]: https://gcovr.com/en/stable/getting-started.html
|
||||
|
||||
@@ -158,7 +158,13 @@ if(coverage)
|
||||
include(XrplCov)
|
||||
endif()
|
||||
|
||||
add_custom_target(tidy_prerequisites)
|
||||
|
||||
if(rust)
|
||||
add_subdirectory(crates)
|
||||
endif()
|
||||
include(XrplCore)
|
||||
|
||||
include(XrplProtocolAutogen)
|
||||
include(XrplInstall)
|
||||
include(XrplValidatorKeys)
|
||||
|
||||
@@ -225,8 +225,9 @@ environment, so you don't need to install most of the individual tools
|
||||
yourself. The version of each hook sourced from an external repository
|
||||
(`clang-format`, `gersemi`, etc.) is pinned in that file, so running the hooks
|
||||
locally uses exactly the same versions as CI. A few `local` hooks — most notably
|
||||
`clang-tidy` — run tools from your own environment; see
|
||||
[Installing clang-tidy](#installing-clang-tidy) for how to get those.
|
||||
`clang-tidy` and `cargo fmt` — run tools from your own environment; see
|
||||
[Installing clang-tidy](#installing-clang-tidy) and
|
||||
[Rust](./docs/build/environment.md#rust) for how to get those.
|
||||
|
||||
To get started, install `pre-commit` and enable the git hook scripts:
|
||||
|
||||
@@ -255,6 +256,7 @@ The hooks configured in this repository include, among others:
|
||||
- `clang-tidy` — C++ static analysis (see [Clang-tidy](#clang-tidy)); opt in with `TIDY=1`
|
||||
- `fix-include-style`, `fix-pragma-once`, `check-doxygen-style` — C++ hygiene
|
||||
- `gersemi` — CMake formatting
|
||||
- `cargo fmt` — Rust formatting for the crates in `crates/`
|
||||
- `prettier`, `black`, `shfmt` — formatting for JavaScript/JSON/Markdown, Python, and shell
|
||||
- `cspell` — spell checking
|
||||
|
||||
@@ -319,7 +321,11 @@ See the [environment setup guide](./docs/build/environment.md#clang-tidy) for ho
|
||||
|
||||
### Running clang-tidy locally
|
||||
|
||||
Before running clang-tidy, you must build the project to generate required files (particularly protobuf headers). Refer to [`BUILD.md`](./BUILD.md) for build instructions.
|
||||
Before running clang-tidy, you must generate the files it depends on (protobuf headers, and, when the project is configured with `-Drust=ON`, the cxxbridge headers from the Rust crates). Configure the project as described in [`BUILD.md`](./BUILD.md), then build the `tidy_prerequisites` target, which generates all of them:
|
||||
|
||||
```bash
|
||||
cmake --build build --target tidy_prerequisites
|
||||
```
|
||||
|
||||
#### Via pre-commit (recommended)
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ Here are some good places to start learning the source code:
|
||||
| `./docs` | Source documentation files and doxygen config. |
|
||||
| `./cfg` | Example configuration files. |
|
||||
| `./src` | Source code. |
|
||||
| `./crates` | Rust source code. |
|
||||
|
||||
Some of the directories under `src` are external repositories included using
|
||||
git-subtree. See those directories' README files for more details.
|
||||
|
||||
111
bin/check-nix-store-refs.sh
Executable file
111
bin/check-nix-store-refs.sh
Executable file
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env bash
|
||||
# Fail if a binary under <path> records a /nix/store path it resolves at run
|
||||
# time. See docs/build/nix.md#prebuilt-packages for why that matters.
|
||||
#
|
||||
# <path> is a file or a directory. macOS: nothing may reference the store, so
|
||||
# point it at whole trees. Linux: the toolchain always writes the store into
|
||||
# PT_INTERP and RUNPATH, so only at what cmake/PatchNixBinary.cmake retargets.
|
||||
#
|
||||
# Only Mach-O / ELF is inspected. Static archives hold store paths in debug info
|
||||
# alone; the scripts in a Conan cache are all git hook samples and autotools
|
||||
# scratch, 36 false positives to 0 real.
|
||||
#
|
||||
# Usage: bin/check-nix-store-refs.sh <path>
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "usage: $0 <path>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [ ! -e "$1" ]; then
|
||||
echo "$0: no such path: $1" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
case "$(uname -s)" in
|
||||
Darwin)
|
||||
format=Mach-O
|
||||
recorded_paths=macho_recorded_paths
|
||||
tool=otool
|
||||
;;
|
||||
Linux)
|
||||
format=ELF
|
||||
recorded_paths=elf_recorded_paths
|
||||
tool=readelf
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported OS - skipping the Nix store reference check."
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# `pipefail` would catch this too, but only as a bare nonzero exit.
|
||||
if ! command -v "${tool}" >/dev/null; then
|
||||
echo "$0: ${tool} not found; cannot inspect binaries" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Both list what the file records. `ldd` would answer what this machine resolves
|
||||
# now, which is wrong both ways: store paths for a correctly patched binary,
|
||||
# silence for a store RUNPATH that resolves nowhere.
|
||||
|
||||
# `name` covers LC_ID_DYLIB and LC_LOAD*_DYLIB, `path` covers LC_RPATH.
|
||||
macho_recorded_paths() {
|
||||
otool -l "$1" | sed -nE 's#^ *(name|path) ([^ ]*).*#\2#p'
|
||||
}
|
||||
|
||||
# RPATH and RUNPATH are colon-separated.
|
||||
elf_recorded_paths() {
|
||||
readelf -ldW "$1" |
|
||||
sed -nE \
|
||||
-e 's#.*program interpreter: ([^]]*)\].*#\1#p' \
|
||||
-e 's#.*\((RPATH|RUNPATH|NEEDED)\).*\[([^]]*)\].*#\2#p' |
|
||||
tr ':' '\n'
|
||||
}
|
||||
|
||||
checked=0
|
||||
skipped=0
|
||||
leaked=0
|
||||
|
||||
while IFS= read -r file; do
|
||||
case "$(file -b "${file}" 2>/dev/null)" in
|
||||
*"${format}"*) ;;
|
||||
*)
|
||||
skipped=$((skipped + 1))
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
checked=$((checked + 1))
|
||||
|
||||
# Filter after extracting, or a search path starting elsewhere ($ORIGIN)
|
||||
# hides the rest. `sed` not `grep`: grep calls "no matches" a failure, and
|
||||
# the `|| true` that would need masks a broken pipeline too.
|
||||
refs="$("${recorded_paths}" "${file}" | sed -n '\#^/nix/store/#p' | sort -u)"
|
||||
if [ -n "${refs}" ]; then
|
||||
leaked=$((leaked + 1))
|
||||
echo "::error file=${file}::references the Nix store at run time"
|
||||
echo "${file}"
|
||||
echo "${refs}" | sed 's/^/ /'
|
||||
fi
|
||||
done < <(find "$1" -type f \( -perm -u+x -o -name '*.dylib' -o -name '*.so*' \))
|
||||
|
||||
echo "$1: checked ${checked}, skipped ${skipped}, ${leaked} with Nix store references."
|
||||
|
||||
if [ "${leaked}" -ne 0 ]; then
|
||||
cat >&2 <<'EOF'
|
||||
|
||||
Fixes, in order of preference:
|
||||
- A Conan package built before this check existed: drop it
|
||||
(`conan remove '<name>/*'`) and rebuild.
|
||||
- A binary that should have been retargeted to the system loader: check that
|
||||
cmake/PatchNixBinary.cmake ran for it.
|
||||
- Link the macOS system library instead of the Nix one - see
|
||||
libresolvSystemStub in nix/darwin.nix.
|
||||
- No system library exists (libstdc++): link it statically.
|
||||
- None of the above: pin the toolchain into the package ID, following
|
||||
`user.package:libc_version` in conan/profiles/ci.
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
@@ -15,10 +15,14 @@
|
||||
# - Windows: the core build tools only (CMake, Conan, Git, Python).
|
||||
# MSVC is expected to be provided separately and is not checked here.
|
||||
#
|
||||
# Some tools (clang-format, doxygen, gcovr, gh, git-cliff, gpg, pre-commit,
|
||||
# run-clang-tidy) are present in our Linux CI images and in local development
|
||||
# setups, but not in the macOS CI environment. They are checked everywhere
|
||||
# except when running in CI on macOS.
|
||||
# Some tools (clang-format, clang-tidy, doxygen, gcovr, gh, git-cliff, gpg,
|
||||
# pre-commit, run-clang-tidy) are present in our Linux CI images and in local
|
||||
# development setups, but not in the macOS CI environment. They are checked
|
||||
# everywhere except when running in CI on macOS.
|
||||
#
|
||||
# Tools that Nix also exposes under a version-suffixed name (`clang-tidy-22`,
|
||||
# `g++-15`, ...) are probed under both names: a suffixed name can break while
|
||||
# the plain one still works (see mkVersionedToolLinks in nix/packages.nix).
|
||||
#
|
||||
# Environment variables:
|
||||
# CI if set, skip the tools above when on macOS.
|
||||
@@ -26,14 +30,27 @@
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
# Version suffixes of the Nix tool links, tracking nix/packages.nix.
|
||||
gcc_version=15
|
||||
llvm_version=22
|
||||
|
||||
missing=()
|
||||
checked=0
|
||||
|
||||
# tool_path <name>
|
||||
# Fully resolved path of a tool, so the snapshots record which derivation
|
||||
# provides it. Prints nothing when it isn't on PATH.
|
||||
tool_path() {
|
||||
local path
|
||||
path="$(command -v "$1" 2>/dev/null)" || return 0
|
||||
readlink -f "${path}" 2>/dev/null || printf '%s' "${path}"
|
||||
}
|
||||
|
||||
# check <name> [probe-command...]
|
||||
# Runs the probe (default: "<name> --version"), capturing both stdout and
|
||||
# stderr, and prints one aligned line: the status, the name, and the first
|
||||
# non-blank line of the probe output (its version). Records <name> as missing
|
||||
# if the command is not found or exits non-zero.
|
||||
# stderr, and prints three lines: the status and name, the first non-blank line
|
||||
# of the probe output (its version, or the error when it failed), and the tool's
|
||||
# resolved path. Records <name> as missing if it is not found or exits non-zero.
|
||||
check() {
|
||||
local name="$1"
|
||||
shift
|
||||
@@ -43,14 +60,17 @@ check() {
|
||||
fi
|
||||
|
||||
checked=$((checked + 1))
|
||||
local output version
|
||||
local output version path
|
||||
path="$(tool_path "${name}")"
|
||||
if output="$("${probe[@]}" 2>&1)"; then
|
||||
version="$(printf '%s\n' "${output}" | grep -m1 '[^[:space:]]' || true)"
|
||||
printf ' [ ok ] %-20s %s\n' "${name}" "${version}"
|
||||
printf ' ✅ %s\n' "${name}"
|
||||
else
|
||||
printf ' [MISS] %s\n' "${name}"
|
||||
printf ' ❌ %s\n' "${name}"
|
||||
missing+=("${name}")
|
||||
fi
|
||||
version="$(printf '%s\n' "${output}" | grep -m1 '[^[:space:]]' || true)"
|
||||
printf ' %s\n' "${version:-(no output)}"
|
||||
printf ' %s\n' "${path:-(not found)}"
|
||||
}
|
||||
|
||||
case "$(uname -s)" in
|
||||
@@ -82,7 +102,9 @@ if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then
|
||||
echo "Development tooling:"
|
||||
check ccache
|
||||
check clang
|
||||
check "clang-${llvm_version}"
|
||||
check clang++
|
||||
check "clang++-${llvm_version}"
|
||||
check ClangBuildAnalyzer
|
||||
check curl
|
||||
check file
|
||||
@@ -101,7 +123,14 @@ if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then
|
||||
# setups, but not in the macOS CI environment. So check them everywhere
|
||||
# except when running in CI on macOS.
|
||||
if [ "${os}" = "linux" ] || [ -z "${CI:-}" ]; then
|
||||
check clang-apply-replacements
|
||||
check "clang-apply-replacements-${llvm_version}"
|
||||
check clang-format
|
||||
check "clang-format-${llvm_version}"
|
||||
# clang-tidy leads --version with the LLVM banner, not the version.
|
||||
tidy_probe="--version | grep -m1 -oE 'LLVM version [0-9.]+'"
|
||||
check clang-tidy sh -c "clang-tidy ${tidy_probe}"
|
||||
check "clang-tidy-${llvm_version}" sh -c "clang-tidy-${llvm_version} ${tidy_probe}"
|
||||
check dot
|
||||
check doxygen
|
||||
check gcovr
|
||||
@@ -112,6 +141,7 @@ if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then
|
||||
# pre-commit, or its alternative implementation prek
|
||||
check pre-commit sh -c 'pre-commit --version || prek --version'
|
||||
check run-clang-tidy run-clang-tidy --help
|
||||
check "run-clang-tidy-${llvm_version}" "run-clang-tidy-${llvm_version}" --help
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -126,7 +156,7 @@ if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then
|
||||
check cargo-audit cargo audit --version
|
||||
check cargo-llvm-cov cargo llvm-cov --version
|
||||
check cargo-nextest cargo nextest --version
|
||||
check clippy clippy-driver --version
|
||||
check clippy-driver
|
||||
check rust-analyzer
|
||||
check rustc
|
||||
check rustfmt
|
||||
@@ -138,7 +168,11 @@ if [ "${os}" = "linux" ]; then
|
||||
echo
|
||||
echo "GCC toolchain:"
|
||||
check gcc
|
||||
check "gcc-${gcc_version}"
|
||||
check g++
|
||||
check "g++-${gcc_version}"
|
||||
check cpp
|
||||
check "cpp-${gcc_version}"
|
||||
check gcov
|
||||
|
||||
echo
|
||||
@@ -163,9 +197,9 @@ else
|
||||
checked=$((checked + 1))
|
||||
tmp_clone="$(mktemp -d)"
|
||||
if git clone --depth 1 https://github.com/XRPLF/actions.git "${tmp_clone}/actions" >/dev/null 2>&1; then
|
||||
printf ' [ ok ] git clone over HTTPS\n'
|
||||
printf ' ✅ git clone over HTTPS\n'
|
||||
else
|
||||
printf ' [MISS] git clone over HTTPS\n'
|
||||
printf ' ❌ git clone over HTTPS\n'
|
||||
missing+=("git-https-clone")
|
||||
fi
|
||||
rm -rf "${tmp_clone}"
|
||||
@@ -173,9 +207,9 @@ fi
|
||||
|
||||
echo
|
||||
if [ "${#missing[@]}" -eq 0 ]; then
|
||||
echo "All ${checked} checked tools are present and runnable."
|
||||
echo "✅ All ${checked} checked tools are present and runnable."
|
||||
else
|
||||
echo "Missing or non-functional tools (${#missing[@]} of ${checked}):" >&2
|
||||
echo "❌ Missing or non-functional tools (${#missing[@]} of ${checked}):" >&2
|
||||
for tool in "${missing[@]}"; do
|
||||
echo " - ${tool}" >&2
|
||||
done
|
||||
|
||||
@@ -266,10 +266,50 @@ elseif(use_lld)
|
||||
)
|
||||
if("${LD_VERSION}" MATCHES "LLD")
|
||||
target_link_libraries(common INTERFACE -fuse-ld=lld)
|
||||
# remembered for the linker flag probe below
|
||||
set(fuse_ld_flag "-fuse-ld=lld")
|
||||
endif()
|
||||
unset(LD_VERSION)
|
||||
endif()
|
||||
|
||||
# Linker warnings are errors where we control the toolchain and the dependencies: CI and the Nix dev shell.
|
||||
# On non-Nix macOS we suppress the deployment target warning: an old Conan profile may not pin os.version.
|
||||
# Only the new Apple linker understands the flag, so probe the actual linker (lld may be selected above).
|
||||
if(is_macos OR is_linux)
|
||||
if(is_ci OR is_nix_compiler)
|
||||
if(is_macos)
|
||||
set(fatal_warnings_flag "-Wl,-fatal_warnings")
|
||||
else()
|
||||
set(fatal_warnings_flag "-Wl,--fatal-warnings")
|
||||
endif()
|
||||
message(
|
||||
STATUS
|
||||
"Treating all linker warnings as errors (${fatal_warnings_flag})"
|
||||
)
|
||||
target_link_options(common INTERFACE "${fatal_warnings_flag}")
|
||||
unset(fatal_warnings_flag)
|
||||
elseif(is_macos)
|
||||
set(silence_flag "-Wl,-deployment_target_mismatches,suppress")
|
||||
set(probe_flags ${fuse_ld_flag} "${silence_flag}")
|
||||
include(CheckLinkerFlag)
|
||||
check_linker_flag(
|
||||
CXX
|
||||
"${probe_flags}"
|
||||
have_deployment_target_mismatches
|
||||
)
|
||||
if(have_deployment_target_mismatches)
|
||||
message(
|
||||
STATUS
|
||||
"Silencing macOS deployment target mismatch warnings (${silence_flag})"
|
||||
)
|
||||
target_link_options(common INTERFACE "${silence_flag}")
|
||||
endif()
|
||||
unset(probe_flags)
|
||||
unset(silence_flag)
|
||||
endif()
|
||||
endif()
|
||||
unset(fuse_ld_flag)
|
||||
|
||||
if(assert)
|
||||
foreach(var_ CMAKE_C_FLAGS_RELEASE CMAKE_CXX_FLAGS_RELEASE)
|
||||
string(REGEX REPLACE "[-/]DNDEBUG" "" ${var_} "${${var_}}")
|
||||
|
||||
@@ -51,6 +51,8 @@ target_compile_options(
|
||||
|
||||
target_link_libraries(xrpl.libpb PUBLIC protobuf::libprotobuf gRPC::grpc++)
|
||||
|
||||
add_dependencies(tidy_prerequisites xrpl.libpb)
|
||||
|
||||
# TODO: Clean up the number of library targets later.
|
||||
add_library(xrpl.imports.main INTERFACE)
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ setup_target_for_coverage_gcovr(
|
||||
EXCLUDE
|
||||
"src/test"
|
||||
"src/tests"
|
||||
"src/benchmarks"
|
||||
"include/xrpl/beast/test"
|
||||
"include/xrpl/beast/unit_test"
|
||||
"${CMAKE_BINARY_DIR}/pb-xrpl.libpb"
|
||||
|
||||
@@ -32,6 +32,11 @@ endif()
|
||||
|
||||
option(benchmark "Build benchmarks" ON)
|
||||
|
||||
# When OFF, the crates directory is not added to the build at all: no Rust
|
||||
# toolchain is required, no cxxbridge bindings are generated, and the C++ tests
|
||||
# that consume those bindings are left out of the build tree.
|
||||
option(rust "Build the Rust crates and the C++ code that depends on them" OFF)
|
||||
|
||||
# Enabled by default so every header is compiled on its own as the main file of
|
||||
# its own compile_commands.json entry - this is what lets clang-tidy (and clangd
|
||||
# and IDEs) analyse a header's own includes directly. The per-header objects are
|
||||
|
||||
@@ -39,21 +39,12 @@ if(Boost_COMPILER)
|
||||
target_link_libraries(xrpl_boost INTERFACE Boost::disable_autolinking)
|
||||
endif()
|
||||
|
||||
# GCC 14+ has a false positive -Wuninitialized warning in Boost.Coroutine2's
|
||||
# state.hpp when compiled with -O3. This is due to GCC's intentional behavior
|
||||
# change (Bug #98871, #119388) where warnings from inlined system header code
|
||||
# are no longer suppressed by -isystem. The warning occurs in operator|= in
|
||||
# boost/coroutine2/detail/state.hpp when inlined from push_control_block::destroy().
|
||||
# See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=119388
|
||||
if(is_gcc AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 14)
|
||||
target_compile_options(xrpl_boost INTERFACE -Wno-uninitialized)
|
||||
endif()
|
||||
|
||||
# Boost.Context's ucontext backend has ASAN fiber-switching annotations
|
||||
# (start/finish_switch_fiber) that are compiled in when BOOST_USE_ASAN is defined.
|
||||
# This tells ASAN about coroutine stack switches, preventing false positive
|
||||
# stack-use-after-scope errors. BOOST_USE_UCONTEXT ensures the ucontext backend
|
||||
# is selected (fcontext does not support ASAN annotations).
|
||||
# This tells ASAN about fiber stack switches used by boost::asio::spawn,
|
||||
# preventing false positive stack-use-after-scope errors.
|
||||
# BOOST_USE_UCONTEXT ensures the ucontext backend is selected (fcontext does
|
||||
# not support ASAN annotations).
|
||||
# These defines must match what Boost was compiled with (see conan/profiles/sanitizers).
|
||||
if(enable_asan)
|
||||
target_compile_definitions(
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"fast_float/8.2.10#f6f28d6bb22112078e7dbda611caf681%1782494504.298",
|
||||
"ed25519/2015.03#ae761bdc52730a843f0809bdf6c1b1f6%1782307148.15562",
|
||||
"date/3.0.4#862e11e80030356b53c2c38599ceb32b%1782392402.538492",
|
||||
"corrosion/0.6.1#bfa292df0a957bc70a450ff316cd9435%1786119416.131296",
|
||||
"c-ares/1.34.6#545240bb1c40e2cacd4362d6b8967650%1782392402.681654",
|
||||
"bzip2/1.0.8#c470882369c2d95c5c77e970c0c7e321%1782392402.296732",
|
||||
"boost/1.91.0#ea540ca2133d831b560036aa24dece3c%1782392419.475605",
|
||||
|
||||
21
conan/init.sh
Executable file
21
conan/init.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install our Conan configuration, profiles and the xrplf remote into CONAN_HOME.
|
||||
# Safe to re-run; never deletes the Conan home.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CONAN_DIR="$(conan config home)"
|
||||
|
||||
echo "Installing Conan configuration into ${CONAN_DIR}"
|
||||
conan config install "${SCRIPT_DIR}/global.conf"
|
||||
conan config install "${SCRIPT_DIR}/profiles" -tf "${CONAN_DIR}/profiles"
|
||||
# This script manages these files, so make them read-only - Conan does not
|
||||
# preserve the source mode. Only the files: the directories must stay writable
|
||||
# for `conan config install` to replace them.
|
||||
chmod a-w "${CONAN_DIR}/global.conf"
|
||||
find "${CONAN_DIR}/profiles" -type f -exec chmod a-w {} +
|
||||
|
||||
echo "Adding the xrplf Conan remote"
|
||||
# --index 0: our patched recipes must win over Conan Center.
|
||||
conan remote add --index 0 --force xrplf https://conan.xrplf.org/repository/conan/
|
||||
@@ -1,10 +1,7 @@
|
||||
{% set os = detect_api.detect_os() %}
|
||||
{% set arch = detect_api.detect_arch() %}
|
||||
{% set compiler, version, compiler_exe = detect_api.detect_default_compiler() %}
|
||||
{% set compiler_version = version %}
|
||||
{% if os == "Linux" %}
|
||||
{% set compiler_version = detect_api.default_compiler_version(compiler, version) %}
|
||||
{% endif %}
|
||||
{% if os == "Macos" %}
|
||||
{# Minimum macOS the dependencies target. #}
|
||||
{# Without this, Conan builds each dependency against the (possibly newer) host SDK, so the #}
|
||||
|
||||
@@ -28,6 +28,7 @@ class Xrpl(ConanFile):
|
||||
}
|
||||
|
||||
requires = [
|
||||
"corrosion/0.6.1",
|
||||
"ed25519/2015.03",
|
||||
"fast_float/8.2.10",
|
||||
"grpc/1.81.1",
|
||||
@@ -61,7 +62,7 @@ class Xrpl(ConanFile):
|
||||
"boost/*:without_cobalt": True,
|
||||
"boost/*:without_context": False,
|
||||
"boost/*:without_coroutine": True,
|
||||
"boost/*:without_coroutine2": False,
|
||||
"boost/*:without_coroutine2": True,
|
||||
"date/*:header_only": True,
|
||||
"ed25519/*:shared": False,
|
||||
"grpc/*:shared": False,
|
||||
|
||||
17
crates/.cargo/config.toml
Normal file
17
crates/.cargo/config.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
# The Rust static libraries are linked into C++ targets, so the runtime linkage
|
||||
# here has to match what the C++ build uses (see cmake/XrplCompiler.cmake).
|
||||
#
|
||||
# macOS needs nothing: AppleClang cannot link libgcc/libc++ statically, so the
|
||||
# C++ build skips those flags on Apple as well.
|
||||
|
||||
# Both amd64 and arm64 Linux builds link libgcc statically. This only affects
|
||||
# links that rustc itself drives (`cargo test` binaries and the like) — the
|
||||
# `staticlib` crates consumed by CMake are archived, not linked, so rustc
|
||||
# silently ignores link args for them. Keeping libgcc_s.so.1 off the xrpld link
|
||||
# line is handled in crates/CMakeLists.txt instead.
|
||||
[target.'cfg(target_os = "linux")']
|
||||
rustflags = ["-C", "link-args=-static-libgcc"]
|
||||
|
||||
# Windows builds use the static MSVC runtime.
|
||||
[target.'cfg(windows)']
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
104
crates/CMakeLists.txt
Normal file
104
crates/CMakeLists.txt
Normal file
@@ -0,0 +1,104 @@
|
||||
find_package(Corrosion REQUIRED)
|
||||
|
||||
corrosion_import_crate(MANIFEST_PATH ${CMAKE_CURRENT_SOURCE_DIR}/Cargo.toml)
|
||||
|
||||
# The generated C++ lands in the build tree, so put a .clang-tidy next to it to
|
||||
# keep clang-tidy from analyzing code we don't own.
|
||||
configure_file(
|
||||
generated.clang-tidy
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/.clang-tidy"
|
||||
COPYONLY
|
||||
)
|
||||
|
||||
add_custom_target(xrpl_crates)
|
||||
add_dependencies(tidy_prerequisites xrpl_crates)
|
||||
|
||||
# On macOS, ld warns `ignoring duplicate libraries` when linking a crate.
|
||||
# Corrosion is the source of both duplicates it names:
|
||||
#
|
||||
# * The crate archive and its cxxbridge archive, because
|
||||
# `corrosion_add_cxxbridge` makes the two depend on each other, and CMake
|
||||
# repeats a static library cycle on the link line so single-pass linkers can
|
||||
# resolve it. (LINK_INTERFACE_MULTIPLICITY can only raise that count.)
|
||||
# * `-lSystem`, which Corrosion copies from rustc's `native-static-libs` even
|
||||
# though the compiler driver always links libSystem.
|
||||
#
|
||||
# ld needs neither: it resolves the cycle from one copy of each archive and
|
||||
# links libSystem once. So silence the warning rather than rewrite Corrosion's
|
||||
# link interface, which the cycle is also part of. The option itself is old —
|
||||
# Xcode 15 is only where the warning became the default — and the check below
|
||||
# leaves it out on a linker that does not know it.
|
||||
if(is_macos)
|
||||
include(CheckLinkerFlag)
|
||||
check_linker_flag(
|
||||
CXX
|
||||
-Wl,-no_warn_duplicate_libraries
|
||||
have_no_warn_duplicate_libraries
|
||||
)
|
||||
endif()
|
||||
|
||||
function(_unlink_libgcc_s crate)
|
||||
if(NOT (is_linux AND static))
|
||||
return()
|
||||
endif()
|
||||
|
||||
# Corrosion exposes a crate's staticlib as an imported `<crate>-static`
|
||||
# target and puts the native libs in its INTERFACE_LINK_LIBRARIES. If either
|
||||
# of those changes, warn instead of silently letting libgcc_s.so.1 return.
|
||||
set(imported "${crate}-static")
|
||||
if(NOT TARGET ${imported})
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Corrosion did not create the imported target '${imported}', so "
|
||||
"libgcc_s cannot be removed from the link interface of '${crate}'. "
|
||||
"xrpld will link libgcc_s.so.1 dynamically. Check where Corrosion "
|
||||
"${CORROSION_VERSION} now records `native-static-libs`."
|
||||
)
|
||||
return()
|
||||
endif()
|
||||
|
||||
get_target_property(libs ${imported} INTERFACE_LINK_LIBRARIES)
|
||||
if(NOT "gcc_s" IN_LIST libs)
|
||||
message(
|
||||
WARNING
|
||||
"'gcc_s' was not in the link interface of '${imported}' as "
|
||||
"expected. If the Rust toolchain stopped reporting it this "
|
||||
"workaround is obsolete and can be deleted; otherwise xrpld may "
|
||||
"link libgcc_s.so.1 dynamically. Verify with: "
|
||||
"objdump -p xrpld | grep NEEDED"
|
||||
)
|
||||
return()
|
||||
endif()
|
||||
|
||||
list(REMOVE_ITEM libs gcc_s)
|
||||
set_property(TARGET ${imported} PROPERTY INTERFACE_LINK_LIBRARIES ${libs})
|
||||
endfunction()
|
||||
|
||||
function(add_xrpl_crate name)
|
||||
cmake_parse_arguments(ARG "" "CRATE" "FILES" ${ARGN})
|
||||
_unlink_libgcc_s(${ARG_CRATE})
|
||||
# `cc` picks its runtime flag from `crt-static` alone, so it compiles a
|
||||
# crate's C++ with `-MT`; Debug needs `-MTd` (to match cmake/XrplCompiler.cmake).
|
||||
if(is_msvc)
|
||||
corrosion_set_env_vars(
|
||||
${ARG_CRATE}
|
||||
"$<$<CONFIG:Debug>:CXXFLAGS=-MTd>"
|
||||
)
|
||||
endif()
|
||||
corrosion_add_cxxbridge(${name}_cxxbridge CRATE ${ARG_CRATE} FILES
|
||||
${ARG_FILES}
|
||||
)
|
||||
# Generated cxxbridge headers don't exist at configure time; CMake 3.28+
|
||||
# validates INTERFACE_SOURCES on consuming targets. Clear it to skip the
|
||||
# existence check — build-time ordering is enforced by the custom commands.
|
||||
set_target_properties(${name}_cxxbridge PROPERTIES INTERFACE_SOURCES "")
|
||||
if(have_no_warn_duplicate_libraries)
|
||||
target_link_options(
|
||||
${name}_cxxbridge
|
||||
INTERFACE -Wl,-no_warn_duplicate_libraries
|
||||
)
|
||||
endif()
|
||||
add_dependencies(xrpl_crates ${name}_cxxbridge)
|
||||
endfunction()
|
||||
|
||||
add_xrpl_crate(rs_hello_world CRATE rs_hello_world FILES lib.rs)
|
||||
301
crates/Cargo.lock
generated
Normal file
301
crates/Cargo.lock
generated
Normal file
@@ -0,0 +1,301 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"termcolor",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.198"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fe442a792c7c736eea18b32a7f8a3b63cf8aafabda6760042dc2fdeda456291"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cxx-build",
|
||||
"cxxbridge-cmd",
|
||||
"cxxbridge-flags",
|
||||
"cxxbridge-macro",
|
||||
"foldhash",
|
||||
"link-cplusplus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx-build"
|
||||
version = "1.0.198"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3184a94384c663718698311a78a51ac00c484c10b4eeac06fb0a068c5f64fa2"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"codespan-reporting",
|
||||
"indexmap",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"scratch",
|
||||
"syn 3.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-cmd"
|
||||
version = "1.0.198"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0148d8fd1199329ddf1d157a5e134e51ceff37c6a7ddd38615c399d81cb05d8d"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"codespan-reporting",
|
||||
"indexmap",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-flags"
|
||||
version = "1.0.198"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52850339faed2eaadd24e286dc1d8268cc6f8a7bd9524d713adc9099566b4c89"
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-macro"
|
||||
version = "1.0.198"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c77c856545d886c9bd5215409ebb63b925e262135248b50c79e5a5f194ee47c"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "link-cplusplus"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f78c730aaa7d0b9336a299029ea49f9ee53b0ed06e9202e8cb7db9bae7b8c82"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rs-hello_world"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cxx",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scratch"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
15
crates/Cargo.toml
Normal file
15
crates/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[workspace]
|
||||
members = ["hello_world"]
|
||||
resolver = "3"
|
||||
|
||||
[workspace.dependencies]
|
||||
cxx = { version = "1.0.198", features = ["c++20"] }
|
||||
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
overflow-checks = true
|
||||
lto = true
|
||||
debug = true
|
||||
10
crates/generated.clang-tidy
Normal file
10
crates/generated.clang-tidy
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
# Neutralizes clang-tidy for the corrosion/cxxbridge-generated C++. Copied into
|
||||
# the crates build directory by crates/CMakeLists.txt, next to the generated
|
||||
# sources, so clang-tidy picks it up instead of the top-level configuration.
|
||||
#
|
||||
# One check is kept enabled to avoid clang-tidy's "no checks enabled" error.
|
||||
Checks: "-*,google-readability-todo"
|
||||
WarningsAsErrors: ""
|
||||
HeaderFilterRegex: ""
|
||||
InheritParentConfig: false
|
||||
10
crates/hello_world/Cargo.toml
Normal file
10
crates/hello_world/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "rs-hello_world"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib"]
|
||||
|
||||
[dependencies]
|
||||
cxx.workspace = true
|
||||
10
crates/hello_world/src/lib.rs
Normal file
10
crates/hello_world/src/lib.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
#[cxx::bridge(namespace = "rs::hello_world")]
|
||||
mod ffi {
|
||||
extern "Rust" {
|
||||
fn hello_world() -> String;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hello_world() -> String {
|
||||
"hello_world".to_string()
|
||||
}
|
||||
Binary file not shown.
101
docs/build/environment.md
vendored
101
docs/build/environment.md
vendored
@@ -6,22 +6,55 @@ This document explains how to set one up.
|
||||
|
||||
## Tested compiler versions
|
||||
|
||||
`xrpld` is built in the **C++23** dialect by default.
|
||||
Make sure your toolchain is recent enough — the compiler versions currently tested in CI are:
|
||||
`xrpld` is built in the **C++23** dialect by default, so your toolchain has to
|
||||
support it — see [compiler support for C++23][cpp23-support].
|
||||
The versions currently tested in CI are:
|
||||
|
||||
| Compiler | Version |
|
||||
| ----------- | ------- |
|
||||
| GCC | 15.2 |
|
||||
| Clang | 22 |
|
||||
| Apple Clang | 17 |
|
||||
| MSVC | 19.44 |
|
||||
| Compiler | Version |
|
||||
| ----------- | ------------------ |
|
||||
| GCC | 15.2 |
|
||||
| Clang | 22 |
|
||||
| Apple Clang | 21 |
|
||||
| MSVC | Visual Studio 2026 |
|
||||
|
||||
LLVM tools (`clang-tidy` and `clang-format`) are also pinned to version 22.
|
||||
|
||||
### Older compilers
|
||||
|
||||
Older compilers may fail to build the latest `develop` code: the codebase now
|
||||
relies on C++23 features and has been adjusted for `clang-tidy`.
|
||||
If the latest code doesn't build for you, update your build toolchain first.
|
||||
|
||||
If updating isn't an option for you, we do accept pull requests that fix builds
|
||||
on older compilers, as long as the change is small and doesn't make the code
|
||||
harder to read. What we can't promise is that older compilers will keep working:
|
||||
only the versions in the table above are tested in CI, and we won't hold back
|
||||
the use of C++23 features or add invasive workarounds to keep an untested
|
||||
compiler building. Treat support for anything outside the table as best-effort.
|
||||
|
||||
## Required tools
|
||||
|
||||
Besides a compiler, building `xrpld` requires:
|
||||
|
||||
| Tool | Minimum version |
|
||||
| ------------------------------------------- | --------------- |
|
||||
| [Git](https://git-scm.com/downloads) | any recent |
|
||||
| [Python](https://www.python.org/downloads/) | 3.11 |
|
||||
| [Conan](https://conan.io/downloads.html) | 2.17 |
|
||||
| [CMake](https://cmake.org/download/) | 3.16 |
|
||||
|
||||
On Linux and macOS, the [Nix development shell](./nix.md) provides all of them
|
||||
(see below). On Windows they have to be installed manually.
|
||||
|
||||
Building with `-Drust=ON` additionally requires a Rust toolchain, see
|
||||
[Rust](#rust). A default build does not, so it is not in the table above.
|
||||
|
||||
Once they are in place, verify that everything is installed and runnable with:
|
||||
|
||||
```bash
|
||||
./bin/check-tools.sh
|
||||
```
|
||||
|
||||
## Linux and macOS
|
||||
|
||||
The **recommended way** to get a development environment on Linux and macOS is
|
||||
@@ -39,20 +72,15 @@ Clang. If you instead opt to use your system-wide Apple Clang (via
|
||||
below).
|
||||
|
||||
See [Using the Nix development shell](./nix.md) for installation and usage
|
||||
details, including how to select a different compiler.
|
||||
|
||||
> [!NOTE]
|
||||
> Using Nix is not mandatory. Any custom environment (Homebrew packages or
|
||||
> anything else) will continue to work, but then it is up to you to keep it in
|
||||
> sync with the environment used in CI. Nix unifies the development environment
|
||||
> for everyone and synchronizes updates, which is why we recommend it.
|
||||
details, including how to select a different compiler and why we recommend Nix
|
||||
over a hand-maintained environment.
|
||||
|
||||
### macOS: managing the Apple Clang version
|
||||
|
||||
If you use your system-wide Apple Clang on macOS (via `nix develop .#apple-clang`),
|
||||
the compiler version is whatever your installed Xcode (or Command Line Tools)
|
||||
provides. The following command should return a version greater than or equal to
|
||||
the [minimum required](#tested-compiler-versions):
|
||||
the [tested one](#tested-compiler-versions):
|
||||
|
||||
```bash
|
||||
clang --version
|
||||
@@ -89,23 +117,42 @@ building xrpld. You may want to install and pin a specific version of Xcode:
|
||||
Nix is not available on Windows, so the required tools have to be installed
|
||||
manually:
|
||||
|
||||
- [Visual Studio 2022](https://visualstudio.microsoft.com/) with the
|
||||
- [Visual Studio 2026](https://visualstudio.microsoft.com/) with the
|
||||
**"Desktop development with C++"** workload — this provides MSVC and the
|
||||
"x64 Native Tools Command Prompt".
|
||||
"x64 Native Tools Command Prompt". CI configures CMake with the
|
||||
`Visual Studio 18 2026` generator.
|
||||
- [Git for Windows](https://git-scm.com/download/win)
|
||||
- [Python 3.11](https://www.python.org/downloads/), or higher
|
||||
- [Conan 2.17](https://conan.io/downloads.html), or higher
|
||||
- [CMake 3.22](https://cmake.org/download/), or higher
|
||||
- Python, Conan, and CMake, at the versions listed in
|
||||
[Required tools](#required-tools).
|
||||
- a [Rust toolchain](https://rustup.rs) — only needed to build with
|
||||
`-Drust=ON`, see [Rust](#rust)
|
||||
|
||||
> [!NOTE]
|
||||
> Windows is used for development only and is not recommended for production.
|
||||
## Rust
|
||||
|
||||
The repository contains a Rust workspace in [`crates/`](../../crates), whose
|
||||
crates are exposed to C++ through [cxx](https://cxx.rs) bindings. It is **not**
|
||||
part of a default build: the CMake `rust` option is OFF by default, and with it
|
||||
off no Rust toolchain is needed. It is only required when configuring with
|
||||
`-Drust=ON` (which is what CI does), see [Options](../../BUILD.md#options).
|
||||
|
||||
The toolchain (`cargo`, `rustc`) is pinned to the channel in
|
||||
[`rust-toolchain.toml`](../../rust-toolchain.toml) at the repository root. If
|
||||
you install Rust with [rustup](https://rustup.rs), that file is picked up
|
||||
automatically, and `cargo`/`rustc` in the repository will use the pinned
|
||||
version.
|
||||
|
||||
Everything else the Rust build needs on the CMake side comes from Conan along
|
||||
with the rest of the dependencies, so there is nothing further to install.
|
||||
|
||||
## Clang-tidy
|
||||
|
||||
`clang-tidy` is required to run static analysis checks locally (see
|
||||
[CONTRIBUTING.md](../../CONTRIBUTING.md)). It is not required to build the
|
||||
project. This project currently uses `clang-tidy` version 22.
|
||||
project. The version this project uses is listed in
|
||||
[Tested compiler versions](#tested-compiler-versions).
|
||||
|
||||
On Linux and macOS, the [Nix development shell](./nix.md) provides `clang-tidy`
|
||||
22 out of the box — run it via `run-clang-tidy`. No separate installation is
|
||||
needed.
|
||||
On Linux and macOS, the [Nix development shell](./nix.md) provides that exact
|
||||
version out of the box — run it via `run-clang-tidy`. No separate installation
|
||||
is needed.
|
||||
|
||||
[cpp23-support]: https://en.cppreference.com/w/cpp/compiler_support/23
|
||||
|
||||
108
docs/build/nix.md
vendored
108
docs/build/nix.md
vendored
@@ -7,7 +7,7 @@ This guide explains how to use Nix to set up a reproducible development environm
|
||||
## Benefits of Using Nix
|
||||
|
||||
- **Reproducible environment**: Everyone gets the same versions of tools and compilers
|
||||
- **Matches CI**: The Linux CI runs in Docker images built from this exact Nix environment
|
||||
- **Matches CI**: The Linux CI runs in Docker images built from this exact Nix environment, and CI builds some macOS configurations in it as well
|
||||
- **No system pollution**: Dependencies are isolated and don't affect your system packages
|
||||
- **Consistent compilers**: The GCC and Clang shells use the same versions as CI
|
||||
- **Quick setup**: Get started with a single command
|
||||
@@ -68,7 +68,7 @@ A compiler can be chosen by providing its name with the `.#` prefix, e.g. `nix d
|
||||
|
||||
On Linux, `.#gcc` and `.#clang` provide the exact toolchain CI uses:
|
||||
the compiler (pinned in [`nix/packages.nix`](../../nix/packages.nix))
|
||||
rebuilt against the pinned custom glibc (see [`nix/compilers.nix`](../../nix/compilers.nix)).
|
||||
rebuilt against the pinned custom glibc (see [`nix/linux.nix`](../../nix/linux.nix)).
|
||||
Building that toolchain the first time is slow unless it is fetched from a Nix binary cache.
|
||||
If you don't need the custom glibc, the Linux-only `.#gcc-plain` and `.#clang-plain`
|
||||
give you the stock nixpkgs compilers of the same versions.
|
||||
@@ -120,7 +120,7 @@ nix develop -c "$SHELL"
|
||||
>
|
||||
> If it doesn't, either adjust your shell configuration so it doesn't override `$PATH`, or use [direnv](#automatic-activation-with-direnv) (below), which loads the environment _after_ your shell config and so takes precedence regardless of the shell you use.
|
||||
|
||||
## Building xrpld with Nix
|
||||
## Building xrpld in the Nix shell
|
||||
|
||||
Once inside the Nix development shell, follow the standard [build instructions](../../BUILD.md#steps). The Nix shell provides all necessary tools (CMake, Ninja, Conan, etc.).
|
||||
|
||||
@@ -128,6 +128,100 @@ Coverage builds (`-Dcoverage=ON`) work in the `gcc` shell (and `gcc-plain` on Li
|
||||
each ships a `gcov` matching its compiler, since Nix's cc-wrapper does not expose one.
|
||||
The `clang` shells do not include `llvm-cov`, so use a `gcc` shell for coverage.
|
||||
|
||||
Builds of the Rust crates (`-Drust=ON`) also work out of the box: every shell
|
||||
provides the Rust toolchain pinned in
|
||||
[`rust-toolchain.toml`](../../rust-toolchain.toml) (see
|
||||
[Rust](./environment.md#rust)), plus the `cargo-audit`, `cargo-llvm-cov` and
|
||||
`cargo-nextest` plugins.
|
||||
|
||||
## Conan configuration
|
||||
|
||||
The shell runs [`conan/init.sh`](../../conan/init.sh) on entry, so
|
||||
[Set Up Conan](../../BUILD.md#set-up-conan) is already done for you. It installs
|
||||
into the shell's own Conan home: `CONAN_HOME=~/.conan2-nix`.
|
||||
|
||||
### Prebuilt packages
|
||||
|
||||
On **Linux**, the binaries on the `xrplf` remote are built in this same Nix
|
||||
environment — CI runs in Docker images that bundle the dev shell's toolchain (see
|
||||
[`nix/docker`](../../nix/docker)) — so `.#gcc` and `.#clang` can reuse them. The
|
||||
`-plain` shells do not match that toolchain's glibc, so binaries from the remote
|
||||
are not a reliable match there.
|
||||
|
||||
On **macOS**, CI also builds in this Nix environment, in Debug and Release (the
|
||||
`macos-arm64-*-nix` configurations — Debug because the profile defaults to it).
|
||||
The Nix build resolves to `compiler=clang`, so it gets its own package IDs,
|
||||
separate from the Apple Clang ones. The
|
||||
[dependency upload](../../.github/workflows/upload-conan-deps.yml) publishes them
|
||||
on pushes to `develop` and on manual runs — its nightly run rebuilds everything
|
||||
from source but uploads nothing — so once a set has been published `nix develop`
|
||||
can reuse it instead of compiling every dependency locally. These configurations
|
||||
run outside the reduced pull-request matrix, so label a PR `Full CI build` when it
|
||||
touches `flake.lock` or `nix/`.
|
||||
|
||||
To compile everything from source, add `--build '*'` to the `conan install`
|
||||
command.
|
||||
|
||||
### Why the nixpkgs revision is not part of the package ID
|
||||
|
||||
A Conan package ID records the compiler and its major version, but nothing about
|
||||
the nixpkgs revision the toolchain came from — and `flake.lock` moves far more
|
||||
often than the toolchain meaningfully changes, so folding it in would rebuild
|
||||
every dependency on every bump for nothing.
|
||||
|
||||
That is safe as long as no cached artifact resolves a `/nix/store` path at run
|
||||
time, because store paths change on every update and the old ones disappear with
|
||||
`nix-collect-garbage`. With the `clang` toolchain macOS CI and the dev shell use,
|
||||
they do not: it links against `/usr/lib/libc++` and `/usr/lib/libSystem`, and
|
||||
store paths reach the `.a` files only through debug info, which nothing resolves
|
||||
at link or run time.
|
||||
|
||||
> [!WARNING]
|
||||
> This does not hold for `nix develop .#gcc` on macOS. There is no system
|
||||
> libstdc++, so GCC links its own from the store and every binary keeps a
|
||||
> `/nix/store` reference. That shell is fine for tooling, but it is not a build
|
||||
> configuration CI covers, and no dependency binaries are published for it.
|
||||
|
||||
This is checked rather than assumed.
|
||||
[`bin/check-nix-store-refs.sh`](../../bin/check-nix-store-refs.sh) takes one file
|
||||
or directory and fails if a binary under it resolves a store path at run time.
|
||||
CI runs it over the build output and the Conan cache, and again in the upload job
|
||||
before anything is published. You can run it yourself:
|
||||
|
||||
```bash
|
||||
bin/check-nix-store-refs.sh build
|
||||
bin/check-nix-store-refs.sh ~/.conan2-nix
|
||||
```
|
||||
|
||||
It works on Linux too, but asserts something narrower there: the toolchain always
|
||||
writes the store into `PT_INTERP` and `RUNPATH`, and CI builds inside an image
|
||||
whose store is fixed for its lifetime, so that is fine. Only the binaries
|
||||
[`PatchNixBinary.cmake`](../../cmake/PatchNixBinary.cmake) retargets to the
|
||||
system loader have to be clean, and those are what CI checks:
|
||||
|
||||
```bash
|
||||
bin/check-nix-store-refs.sh build/xrpld
|
||||
```
|
||||
|
||||
### The libresolv stub
|
||||
|
||||
This is not hypothetical: `xrpld` used to be caught by it. The c-ares package
|
||||
tells the linker to pass `-lresolv`, and nixpkgs keeps `libresolv` out of the
|
||||
macOS SDK and ships it as an ordinary store dylib — so every Nix-built `xrpld`
|
||||
recorded a `/nix/store/…-libresolv-93/lib/libresolv.9.dylib` load command and
|
||||
stopped running once that path was collected. Nothing in the link uses a single
|
||||
symbol from it.
|
||||
|
||||
Both environments now put a stub on the linker search path
|
||||
(`libresolvSystemStub` in [`nix/darwin.nix`](../../nix/darwin.nix)): the
|
||||
same library with its install name set to `/usr/lib/libresolv.9.dylib`, which is
|
||||
exactly the load command the Apple Clang build records.
|
||||
|
||||
Package IDs did not change, so Conan keeps serving anything built before the
|
||||
stub landed. If a binary fails to start with `Library not loaded: /nix/store/…`,
|
||||
see [that entry](./nix_troubleshooting.md#library-not-loaded-nixstore-from-a-binary-that-used-to-work)
|
||||
in the troubleshooting guide.
|
||||
|
||||
## Automatic Activation with direnv
|
||||
|
||||
[direnv](https://direnv.net/) or [nix-direnv](https://github.com/nix-community/nix-direnv) can automatically activate the Nix development shell when you enter the repository directory.
|
||||
@@ -142,14 +236,6 @@ The repository already ships an `.envrc` at its root that activates the Nix flak
|
||||
> [!NOTE]
|
||||
> direnv only caches the `.direnv` directory (already listed in `.gitignore`); no other repository files are affected.
|
||||
|
||||
## Conan and Prebuilt Packages
|
||||
|
||||
Please note that there is no guarantee that binaries from conan cache will work when using nix. If you encounter any errors, please use `--build '*'` to force conan to compile everything from source:
|
||||
|
||||
```bash
|
||||
conan install .. --output-folder . --build '*' --settings build_type=Release
|
||||
```
|
||||
|
||||
## Updating `flake.lock` file
|
||||
|
||||
To update `flake.lock` to the latest revision use `nix flake update` command.
|
||||
|
||||
88
docs/build/nix_troubleshooting.md
vendored
88
docs/build/nix_troubleshooting.md
vendored
@@ -131,3 +131,91 @@ once it picks up that rebuild, then re-run the `grep libgit2` check above to
|
||||
confirm it reports `1.9.4` or newer.
|
||||
|
||||
Until then, prefer the workarounds above.
|
||||
|
||||
## `wint_t` / `uint32_t` errors from the Nix libc++ headers
|
||||
|
||||
A build that mixes the Nix toolchain with the system SDK fails in libc++ itself,
|
||||
with errors that look nothing like your code:
|
||||
|
||||
```
|
||||
/nix/store/...-libcxx-.../include/c++/v1/cwchar:136:9: error: target of using declaration conflicts with declaration already in scope
|
||||
136 | using ::wint_t _LIBCPP_USING_IF_EXISTS;
|
||||
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/_types/_wint_t.h:32:25: note: target of using declaration
|
||||
...
|
||||
error: use of undeclared identifier 'UINT32_C'
|
||||
```
|
||||
|
||||
The give-away is the second path: Nix's libc++ headers are being combined with
|
||||
the **Xcode Command Line Tools** SDK instead of the Nix one.
|
||||
|
||||
### Why it happens
|
||||
|
||||
`SDKROOT` and `DEVELOPER_DIR` are what point the toolchain at the Nix SDK, and
|
||||
they are not baked into the compiler — a dev shell gets them from the
|
||||
`apple-sdk` setup hook. CMake, finding neither, asks `xcrun`, which answers with
|
||||
the system SDK. Nix's `libc++` and Apple's headers then declare the same types
|
||||
twice.
|
||||
|
||||
### Fix
|
||||
|
||||
Run the build from inside the dev shell (`nix develop`), or from an environment
|
||||
that exports both variables. To confirm which SDK a configured build is using:
|
||||
|
||||
```bash
|
||||
grep -o '\-isysroot [^ ]*' build/compile_commands.json | sort -u
|
||||
```
|
||||
|
||||
It should print a `/nix/store/...-apple-sdk-*` path. If it prints
|
||||
`/Library/Developer/CommandLineTools/...`, re-configure from within the shell —
|
||||
CMake caches the sysroot, so an existing `build/` directory keeps the wrong one.
|
||||
|
||||
## `Library not loaded: /nix/store/…` from a binary that used to work
|
||||
|
||||
A binary stops starting after a `nix flake update`, or after
|
||||
`nix-collect-garbage` removes the paths the previous toolchain used:
|
||||
|
||||
```
|
||||
dyld[57271]: Library not loaded: /nix/store/…-libresolv-93/lib/libresolv.9.dylib
|
||||
```
|
||||
|
||||
[`bin/check-nix-store-refs.sh`](../../bin/check-nix-store-refs.sh) finds the same
|
||||
thing without having to run anything, and names the file:
|
||||
|
||||
```
|
||||
$ bin/check-nix-store-refs.sh ~/.conan2-nix
|
||||
::error file=/Users/you/.conan2-nix/p/b/c-area24ded30c388c/p/bin/adig::references the Nix store at run time
|
||||
/Users/you/.conan2-nix/p/b/c-area24ded30c388c/p/bin/adig
|
||||
/nix/store/p4lp3xq4imd1qzqh08x8vcq2zfhi7rca-libresolv-93/lib/libresolv.9.dylib
|
||||
/Users/you/.conan2-nix: checked 135, skipped 2495, 1 with Nix store references.
|
||||
```
|
||||
|
||||
Conan's cache folders are named after a truncated package name plus a hash, so
|
||||
ask Conan which package the offending one belongs to — pass the folder holding
|
||||
the hash, not the file itself:
|
||||
|
||||
```
|
||||
$ conan cache ref ~/.conan2-nix/p/b/c-area24ded30c388c
|
||||
c-ares/1.34.6#545240bb1c40e2cacd4362d6b8967650:dab5992496abe6d219defb7986ecbf367615a5e5#…
|
||||
```
|
||||
|
||||
### Why it happens
|
||||
|
||||
The binary records a store path that no longer exists. Nothing we build should:
|
||||
see [Prebuilt packages](./nix.md#prebuilt-packages) for why, and
|
||||
`libresolvSystemStub` in [`nix/darwin.nix`](../../nix/darwin.nix) for the one
|
||||
dependency that needed help to comply.
|
||||
|
||||
A Conan package ID does not encode the nixpkgs revision, so a package built
|
||||
before that stub existed stays in your local cache and keeps being reused. The
|
||||
dev shell is also what tends to produce one: it is a slightly _less_ isolated
|
||||
build environment than CI's, because `mkShell` puts every tool's headers and
|
||||
libraries on the compiler's search path — which is how c-ares found the Nix
|
||||
`libresolv` in the first place.
|
||||
|
||||
### Fix
|
||||
|
||||
Drop that package and let Conan refetch or rebuild it:
|
||||
|
||||
```bash
|
||||
conan remove 'c-ares/*'
|
||||
```
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
# Installing xrpld 3.3.0 and earlier
|
||||
|
||||
> [!IMPORTANT]
|
||||
> These instructions apply to xrpld 3.3.0 and earlier, published to
|
||||
> repos.ripple.com.
|
||||
> For later releases see [install.md](./install.md).
|
||||
|
||||
This document contains instructions for installing xrpld.
|
||||
The APT package manager is common on Debian-based Linux distributions like
|
||||
Ubuntu,
|
||||
@@ -52,7 +59,7 @@ The default [prefix][1] is typically `/usr/local` on Linux and macOS and
|
||||
|
||||
5. Add the appropriate XRPL repository for your operating system version:
|
||||
|
||||
echo "deb [signed-by=/usr/local/share/keyrings/ripple-key.gpg] https://repos.ripple.com/repos/xrpld-deb focal stable" | \
|
||||
echo "deb [signed-by=/usr/local/share/keyrings/ripple-key.gpg] https://repos.ripple.com/repos/rippled-deb focal stable" | \
|
||||
sudo tee -a /etc/apt/sources.list.d/ripple.list
|
||||
|
||||
The above example is appropriate for **Ubuntu 20.04 Focal Fossa**. For other operating systems, replace the word `focal` with one of the following:
|
||||
@@ -106,8 +113,8 @@ The default [prefix][1] is typically `/usr/local` on Linux and macOS and
|
||||
enabled=1
|
||||
gpgcheck=0
|
||||
repo_gpgcheck=1
|
||||
baseurl=https://repos.ripple.com/repos/xrpld-rpm/stable/
|
||||
gpgkey=https://repos.ripple.com/repos/xrpld-rpm/stable/repodata/repomd.xml.key
|
||||
baseurl=https://repos.ripple.com/repos/rippled-rpm/stable/
|
||||
gpgkey=https://repos.ripple.com/repos/rippled-rpm/stable/repodata/repomd.xml.key
|
||||
REPOFILE
|
||||
|
||||
_Unstable_
|
||||
@@ -118,8 +125,8 @@ The default [prefix][1] is typically `/usr/local` on Linux and macOS and
|
||||
enabled=1
|
||||
gpgcheck=0
|
||||
repo_gpgcheck=1
|
||||
baseurl=https://repos.ripple.com/repos/xrpld-rpm/unstable/
|
||||
gpgkey=https://repos.ripple.com/repos/xrpld-rpm/unstable/repodata/repomd.xml.key
|
||||
baseurl=https://repos.ripple.com/repos/rippled-rpm/unstable/
|
||||
gpgkey=https://repos.ripple.com/repos/rippled-rpm/unstable/repodata/repomd.xml.key
|
||||
REPOFILE
|
||||
|
||||
_Nightly_
|
||||
@@ -130,8 +137,8 @@ The default [prefix][1] is typically `/usr/local` on Linux and macOS and
|
||||
enabled=1
|
||||
gpgcheck=0
|
||||
repo_gpgcheck=1
|
||||
baseurl=https://repos.ripple.com/repos/xrpld-rpm/nightly/
|
||||
gpgkey=https://repos.ripple.com/repos/xrpld-rpm/nightly/repodata/repomd.xml.key
|
||||
baseurl=https://repos.ripple.com/repos/rippled-rpm/nightly/
|
||||
gpgkey=https://repos.ripple.com/repos/rippled-rpm/nightly/repodata/repomd.xml.key
|
||||
REPOFILE
|
||||
|
||||
2. Fetch the latest repo updates:
|
||||
144
docs/install.md
Normal file
144
docs/install.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# Installing xrpld
|
||||
|
||||
> [!NOTE]
|
||||
> These instructions apply to packages published from 2026-08-19 onwards.
|
||||
> For xrpld 3.3.0 and earlier see [install-legacy.md](./install-legacy.md).
|
||||
|
||||
`xrpld` is published as DEB and RPM packages for 64-bit x86 Linux.
|
||||
Use APT on Debian-based distributions such as Debian and Ubuntu,
|
||||
and YUM on Red Hat-based distributions such as RHEL, AlmaLinux, and Rocky Linux.
|
||||
To build from source instead, see [BUILD.md](../BUILD.md).
|
||||
|
||||
## Release channels
|
||||
|
||||
Packages are published to four channels:
|
||||
|
||||
- `stable` - the latest production release
|
||||
- `unstable` - release candidates
|
||||
- `experimental` - beta builds
|
||||
- `develop` - every push to the [`develop` branch](https://github.com/XRPLF/rippled/tree/develop)
|
||||
|
||||
See [Publishing packages](../package/README.md#publishing-packages) for how channels are produced.
|
||||
|
||||
The instructions below use `stable`.
|
||||
To follow another channel, replace `stable` with its name
|
||||
wherever it appears in the repository configuration.
|
||||
|
||||
> [!WARNING]
|
||||
> Channels other than `stable` may be broken at any time.
|
||||
> Do not use them for production servers.
|
||||
|
||||
## Install the xrpld package
|
||||
|
||||
### With the APT package manager
|
||||
|
||||
1. Install utilities:
|
||||
|
||||
```bash
|
||||
sudo apt update -y
|
||||
sudo apt install -y apt-transport-https ca-certificates curl gnupg
|
||||
```
|
||||
|
||||
2. Add the XRPL Foundation package-signing key to your list of trusted keys:
|
||||
|
||||
```bash
|
||||
sudo install -d -m 0755 /etc/apt/keyrings
|
||||
sudo curl -fsS https://packages.xrplf.org/xrplf.asc -o /etc/apt/keyrings/xrplf.asc
|
||||
```
|
||||
|
||||
3. Check the fingerprint of the newly-added key:
|
||||
|
||||
```bash
|
||||
gpg --show-keys /etc/apt/keyrings/xrplf.asc
|
||||
```
|
||||
|
||||
The output should be:
|
||||
|
||||
```text
|
||||
pub rsa4096 2026-08-18 [SC]
|
||||
B655416741221F780FBCFBC9AA84D41A11D29FA9
|
||||
uid XRPLF Packages <distribution@xrplf.org>
|
||||
```
|
||||
|
||||
In particular, make sure that the fingerprint matches.
|
||||
|
||||
4. Add the repository, using the channel you picked in [Release channels](#release-channels):
|
||||
|
||||
```bash
|
||||
echo "deb [signed-by=/etc/apt/keyrings/xrplf.asc] https://packages.xrplf.org/repository/deb-stable focal main" | \
|
||||
sudo tee /etc/apt/sources.list.d/xrplf.list
|
||||
```
|
||||
|
||||
5. Fetch the repository:
|
||||
|
||||
```bash
|
||||
sudo apt -y update
|
||||
```
|
||||
|
||||
6. Install the `xrpld` software package:
|
||||
|
||||
```bash
|
||||
sudo apt -y install xrpld
|
||||
```
|
||||
|
||||
### With the YUM package manager
|
||||
|
||||
1. Add the XRPL Foundation package-signing key:
|
||||
|
||||
```bash
|
||||
sudo rpm --import https://packages.xrplf.org/xrplf.asc
|
||||
```
|
||||
|
||||
2. Add the repository, using the channel you picked in [Release channels](#release-channels):
|
||||
|
||||
```bash
|
||||
cat << REPOFILE | sudo tee /etc/yum.repos.d/xrplf.repo
|
||||
[xrplf-stable]
|
||||
name=XRP Ledger Packages
|
||||
enabled=1
|
||||
baseurl=https://packages.xrplf.org/repository/rpm-stable/
|
||||
gpgcheck=1
|
||||
repo_gpgcheck=0
|
||||
gpgkey=https://packages.xrplf.org/xrplf.asc
|
||||
REPOFILE
|
||||
```
|
||||
|
||||
`gpgcheck=1` verifies each package against the key above.
|
||||
`repo_gpgcheck` is off because the repository metadata is generated by the server and is not signed.
|
||||
|
||||
3. Install the `xrpld` package:
|
||||
|
||||
```bash
|
||||
sudo yum install -y xrpld
|
||||
```
|
||||
|
||||
## The xrpld service
|
||||
|
||||
Both package managers install a systemd unit and enable it, so `xrpld` starts on boot.
|
||||
Check whether it is already running:
|
||||
|
||||
```bash
|
||||
systemctl status xrpld.service
|
||||
```
|
||||
|
||||
The APT packages start it immediately as well; the YUM packages do not, so start it yourself:
|
||||
|
||||
```bash
|
||||
sudo systemctl start xrpld.service
|
||||
```
|
||||
|
||||
### Optional: binding to privileged ports
|
||||
|
||||
To serve incoming API requests on port 80 or 443, grant the service the capability to bind them.
|
||||
You must also update the config file's port settings.
|
||||
|
||||
```bash
|
||||
sudo install -d -m 0755 /etc/systemd/system/xrpld.service.d
|
||||
sudo tee /etc/systemd/system/xrpld.service.d/privileged-ports.conf >/dev/null <<'EOF'
|
||||
[Service]
|
||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
EOF
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl restart xrpld.service
|
||||
```
|
||||
@@ -1,24 +0,0 @@
|
||||
/*!
|
||||
\page somestatechart Example state diagram
|
||||
|
||||
\startuml SomeState "my state diagram"
|
||||
scale 600 width
|
||||
|
||||
[*] -> State1
|
||||
State1 --> State2 : Succeeded
|
||||
State1 --> [*] : Aborted
|
||||
State2 --> State3 : Succeeded
|
||||
State2 --> [*] : Aborted
|
||||
state State3 {
|
||||
state "Accumulate Enough Data\nLong State Name" as long1
|
||||
long1 : Just a test
|
||||
[*] --> long1
|
||||
long1 --> long1 : New Data
|
||||
long1 --> ProcessData : Enough Data
|
||||
}
|
||||
State3 --> State3 : Failed
|
||||
State3 --> [*] : Succeeded / Save Result
|
||||
State3 --> [*] : Aborted
|
||||
|
||||
\enduml
|
||||
*/
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <filesystem>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -13,6 +13,6 @@ namespace xrpl {
|
||||
* @throws runtime_error
|
||||
*/
|
||||
void
|
||||
extractTarLz4(boost::filesystem::path const& src, boost::filesystem::path const& dst);
|
||||
extractTarLz4(std::filesystem::path const& src, std::filesystem::path const& dst);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
@@ -156,6 +157,19 @@ public:
|
||||
}
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Set every byte in the buffer to the given value.
|
||||
*
|
||||
* The size is unchanged, and this is a no-op on an empty buffer.
|
||||
*
|
||||
* @param value the byte to write to every position.
|
||||
*/
|
||||
void
|
||||
fill(std::uint8_t value) noexcept
|
||||
{
|
||||
std::fill_n(p_.get(), size_, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the buffer.
|
||||
* All memory is deallocated. The resulting size is 0.
|
||||
@@ -226,10 +240,4 @@ operator==(Buffer const& lhs, Buffer const& rhs) noexcept
|
||||
return std::memcmp(lhs.data(), rhs.data(), lhs.size()) == 0;
|
||||
}
|
||||
|
||||
inline bool
|
||||
operator!=(Buffer const& lhs, Buffer const& rhs) noexcept
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -1,24 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
std::string
|
||||
getFileContents(
|
||||
boost::system::error_code& ec,
|
||||
boost::filesystem::path const& sourcePath,
|
||||
std::error_code& ec,
|
||||
std::filesystem::path const& sourcePath,
|
||||
std::optional<std::size_t> maxSize = std::nullopt);
|
||||
|
||||
void
|
||||
writeFileContents(
|
||||
boost::system::error_code& ec,
|
||||
boost::filesystem::path const& destPath,
|
||||
std::error_code& ec,
|
||||
std::filesystem::path const& destPath,
|
||||
std::string const& contents);
|
||||
|
||||
/**
|
||||
* Generate a unique, non-existing path under @p base whose filename starts with
|
||||
* @p prefix and ends with a random hex suffix.
|
||||
*
|
||||
* Attempts up to @p maxAttempts paths. Throws `std::runtime_error` if a unique
|
||||
* path cannot be found or if the filesystem returns an error while checking for
|
||||
* existence.
|
||||
*/
|
||||
std::filesystem::path
|
||||
uniqueRandomPath(
|
||||
std::filesystem::path const& base,
|
||||
std::string const& prefix = "",
|
||||
std::size_t maxAttempts = 100);
|
||||
|
||||
/**
|
||||
* RAII temporary directory.
|
||||
*
|
||||
* The directory and all its contents are deleted when
|
||||
* the instance of `TempDir` is destroyed.
|
||||
*/
|
||||
class TempDir
|
||||
{
|
||||
std::filesystem::path path_;
|
||||
|
||||
public:
|
||||
#if !GENERATING_DOCS
|
||||
TempDir(TempDir const&) = delete;
|
||||
TempDir&
|
||||
operator=(TempDir const&) = delete;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Construct a temporary directory.
|
||||
*/
|
||||
TempDir();
|
||||
|
||||
/**
|
||||
* Destroy a temporary directory.
|
||||
*/
|
||||
~TempDir();
|
||||
|
||||
/**
|
||||
* Get the native path for the temporary directory.
|
||||
*/
|
||||
[[nodiscard]] std::string
|
||||
path() const;
|
||||
|
||||
/**
|
||||
* Get the native path for a file.
|
||||
*
|
||||
* The file does not need to exist.
|
||||
*/
|
||||
[[nodiscard]] std::string
|
||||
file(std::string const& name) const;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -96,9 +96,6 @@ public:
|
||||
SharedIntrusive&
|
||||
operator=(SharedIntrusive const& rhs);
|
||||
|
||||
bool
|
||||
operator!=(std::nullptr_t) const;
|
||||
|
||||
bool
|
||||
operator==(std::nullptr_t) const;
|
||||
|
||||
|
||||
@@ -111,13 +111,6 @@ SharedIntrusive<T>::operator=(SharedIntrusive<TT>&& rhs)
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool
|
||||
SharedIntrusive<T>::operator!=(std::nullptr_t) const
|
||||
{
|
||||
return this->get() != nullptr;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool
|
||||
SharedIntrusive<T>::operator==(std::nullptr_t) const
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
|
||||
#include <boost/beast/core/string.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
@@ -84,7 +84,7 @@ private:
|
||||
* @return `true` if the file was opened.
|
||||
*/
|
||||
bool
|
||||
open(boost::filesystem::path const& path);
|
||||
open(std::filesystem::path const& path);
|
||||
|
||||
/**
|
||||
* Close and re-open the system file associated with the log
|
||||
@@ -133,7 +133,7 @@ private:
|
||||
|
||||
private:
|
||||
std::unique_ptr<std::ofstream> stream_;
|
||||
boost::filesystem::path path_;
|
||||
std::filesystem::path path_;
|
||||
};
|
||||
|
||||
std::mutex mutable mutex_;
|
||||
@@ -152,7 +152,7 @@ public:
|
||||
virtual ~Logs() = default;
|
||||
|
||||
bool
|
||||
open(boost::filesystem::path const& pathToLogFile);
|
||||
open(std::filesystem::path const& pathToLogFile);
|
||||
|
||||
beast::Journal::Sink&
|
||||
get(std::string const& name);
|
||||
|
||||
@@ -304,7 +304,7 @@ concept Integral64 = std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::u
|
||||
* on-ledger are non-negative. This is due to implementation details of
|
||||
* several operations which use unsigned arithmetic internally. This is
|
||||
* sufficient to represent all valid XRP values (where the absolute value
|
||||
* can not exceed INITIAL_XRP: 10^17), and MPT values (where the absolute
|
||||
* can not exceed kInitialXRP: 10^17), and MPT values (where the absolute
|
||||
* value can not exceed maxMPTokenAmount: 2^63-1).
|
||||
*
|
||||
* ---- Mantissa Range Switching ----
|
||||
@@ -449,12 +449,6 @@ public:
|
||||
x.exponent_ == y.exponent_;
|
||||
}
|
||||
|
||||
friend constexpr bool
|
||||
operator!=(Number const& x, Number const& y) noexcept
|
||||
{
|
||||
return !(x == y);
|
||||
}
|
||||
|
||||
friend constexpr bool
|
||||
operator<(Number const& l, Number const& r) noexcept
|
||||
{
|
||||
|
||||
@@ -85,12 +85,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
inline bool
|
||||
operator!=(SHAMapHash const& x, SHAMapHash const& y)
|
||||
{
|
||||
return !(x == y);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline std::size_t
|
||||
extract(SHAMapHash const& key)
|
||||
|
||||
@@ -208,12 +208,6 @@ operator==(Slice const& lhs, Slice const& rhs) noexcept
|
||||
return std::memcmp(lhs.data(), rhs.data(), lhs.size()) == 0;
|
||||
}
|
||||
|
||||
inline bool
|
||||
operator!=(Slice const& lhs, Slice const& rhs) noexcept
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
inline bool
|
||||
operator<(Slice const& lhs, Slice const& rhs) noexcept
|
||||
{
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <xrpl/basics/Blob.h>
|
||||
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
|
||||
#include <array>
|
||||
@@ -125,9 +124,31 @@ struct ParsedUrl
|
||||
bool
|
||||
parseUrl(ParsedUrl& pUrl, std::string const& strUrl);
|
||||
|
||||
/**
|
||||
* Remove leading and trailing ASCII whitespace.
|
||||
*
|
||||
* Whitespace is the fixed set " \t\n\v\f\r"; the current locale is not
|
||||
* consulted, so the result depends only on the input.
|
||||
*
|
||||
* @param str The string to trim.
|
||||
* @return @p str without leading or trailing whitespace.
|
||||
*/
|
||||
std::string
|
||||
trimWhitespace(std::string str);
|
||||
|
||||
/**
|
||||
* Fold ASCII upper case letters to lower case.
|
||||
*
|
||||
* Only 'A' through 'Z' are remapped; every other byte is left alone and the
|
||||
* current locale is not consulted, so the result depends only on the input.
|
||||
*
|
||||
* @param str The string to fold.
|
||||
* @return @p str with each ASCII upper case letter replaced by its lower case
|
||||
* equivalent.
|
||||
*/
|
||||
std::string
|
||||
toLower(std::string str);
|
||||
|
||||
std::optional<std::uint64_t>
|
||||
toUInt64(std::string const& s);
|
||||
|
||||
|
||||
@@ -116,12 +116,6 @@ public:
|
||||
{
|
||||
return lhs.map == rhs.map && lhs.ait == rhs.ait && lhs.mit == rhs.mit;
|
||||
}
|
||||
|
||||
friend bool
|
||||
operator!=(Iterator const& lhs, Iterator const& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
};
|
||||
|
||||
struct ConstIterator
|
||||
@@ -189,12 +183,6 @@ public:
|
||||
{
|
||||
return lhs.map == rhs.map && lhs.ait == rhs.ait && lhs.mit == rhs.mit;
|
||||
}
|
||||
|
||||
friend bool
|
||||
operator!=(ConstIterator const& lhs, ConstIterator const& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
@@ -1038,25 +1038,6 @@ public:
|
||||
Compare,
|
||||
OtherAllocator> const& other) const;
|
||||
|
||||
template <
|
||||
bool OtherIsMulti,
|
||||
bool OtherIsMap,
|
||||
class OtherT,
|
||||
class OtherDuration,
|
||||
class OtherAllocator>
|
||||
bool
|
||||
operator!=(AgedOrderedContainer<
|
||||
OtherIsMulti,
|
||||
OtherIsMap,
|
||||
Key,
|
||||
OtherT,
|
||||
OtherDuration,
|
||||
Compare,
|
||||
OtherAllocator> const& other) const
|
||||
{
|
||||
return !(this->operator==(other));
|
||||
}
|
||||
|
||||
template <
|
||||
bool OtherIsMulti,
|
||||
bool OtherIsMap,
|
||||
|
||||
@@ -1340,28 +1340,6 @@ public:
|
||||
OtherAllocator> const& other) const
|
||||
requires MaybeMulti;
|
||||
|
||||
template <
|
||||
bool OtherIsMulti,
|
||||
bool OtherIsMap,
|
||||
class OtherKey,
|
||||
class OtherT,
|
||||
class OtherDuration,
|
||||
class OtherHash,
|
||||
class OtherAllocator>
|
||||
bool
|
||||
operator!=(AgedUnorderedContainer<
|
||||
OtherIsMulti,
|
||||
OtherIsMap,
|
||||
OtherKey,
|
||||
OtherT,
|
||||
OtherDuration,
|
||||
OtherHash,
|
||||
KeyEqual,
|
||||
OtherAllocator> const& other) const
|
||||
{
|
||||
return !(this->operator==(other));
|
||||
}
|
||||
|
||||
private:
|
||||
bool
|
||||
wouldExceed(size_type additional) const
|
||||
|
||||
@@ -82,13 +82,6 @@ public:
|
||||
return node_ == other.node_;
|
||||
}
|
||||
|
||||
template <typename M>
|
||||
bool
|
||||
operator!=(ListIterator<M> const& other) const noexcept
|
||||
{
|
||||
return !((*this) == other);
|
||||
}
|
||||
|
||||
reference
|
||||
operator*() const noexcept
|
||||
{
|
||||
|
||||
@@ -110,12 +110,6 @@ public:
|
||||
operator==(Endpoint const& lhs, Endpoint const& rhs);
|
||||
friend bool
|
||||
operator<(Endpoint const& lhs, Endpoint const& rhs);
|
||||
|
||||
friend bool
|
||||
operator!=(Endpoint const& lhs, Endpoint const& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
friend bool
|
||||
operator>(Endpoint const& lhs, Endpoint const& rhs)
|
||||
{
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace beast::rfc2616 {
|
||||
@@ -186,7 +187,7 @@ splitCommas(FwdIt first, FwdIt last)
|
||||
|
||||
template <class Result = std::vector<std::string>>
|
||||
Result
|
||||
splitCommas(boost::beast::string_view const& s)
|
||||
splitCommas(std::string_view s)
|
||||
{
|
||||
return splitCommas(s.begin(), s.end());
|
||||
}
|
||||
@@ -229,12 +230,6 @@ public:
|
||||
return other.it_ == it_ && other.end_ == end_ && other.value_.size() == value_.size();
|
||||
}
|
||||
|
||||
bool
|
||||
operator!=(ListIterator const& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
reference
|
||||
operator*() const
|
||||
{
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <xrpl/beast/unit_test/runner.h>
|
||||
#include <xrpl/beast/unit_test/suite_info.h>
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -188,7 +187,7 @@ Reporter<Unused>::fmtdur(clock_type::duration const& d)
|
||||
using namespace std::chrono;
|
||||
auto const ms = duration_cast<milliseconds>(d);
|
||||
if (ms < seconds{1})
|
||||
return boost::lexical_cast<std::string>(ms.count()) + "ms";
|
||||
return std::to_string(ms.count()) + "ms";
|
||||
std::stringstream ss;
|
||||
ss << std::fixed << std::setprecision(1) << (ms.count() / 1000.) << "s";
|
||||
return ss.str();
|
||||
|
||||
@@ -6,11 +6,10 @@
|
||||
|
||||
#include <xrpl/beast/unit_test/runner.h>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/throw_exception.hpp>
|
||||
|
||||
#include <exception>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
@@ -27,10 +26,10 @@ makeReason(String const& reason, char const* file, int line)
|
||||
std::string s(reason);
|
||||
if (!s.empty())
|
||||
s.append(": ");
|
||||
namespace fs = boost::filesystem;
|
||||
namespace fs = std::filesystem;
|
||||
s.append(fs::path{file}.filename().string());
|
||||
s.append("(");
|
||||
s.append(boost::lexical_cast<std::string>(line));
|
||||
s.append(std::to_string(line));
|
||||
s.append(")");
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace beast {
|
||||
|
||||
/**
|
||||
* RAII temporary directory.
|
||||
*
|
||||
* The directory and all its contents are deleted when
|
||||
* the instance of `temp_dir` is destroyed.
|
||||
*/
|
||||
class TempDir
|
||||
{
|
||||
boost::filesystem::path path_;
|
||||
|
||||
public:
|
||||
#if !GENERATING_DOCS
|
||||
TempDir(TempDir const&) = delete;
|
||||
TempDir&
|
||||
operator=(TempDir const&) = delete;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Construct a temporary directory.
|
||||
*/
|
||||
TempDir()
|
||||
{
|
||||
auto const dir = boost::filesystem::temp_directory_path();
|
||||
do
|
||||
{
|
||||
path_ = dir / boost::filesystem::unique_path();
|
||||
} while (boost::filesystem::exists(path_));
|
||||
boost::filesystem::create_directory(path_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy a temporary directory.
|
||||
*/
|
||||
~TempDir()
|
||||
{
|
||||
// use non-throwing calls in the destructor
|
||||
boost::system::error_code ec;
|
||||
boost::filesystem::remove_all(path_, ec);
|
||||
// TODO: warn/notify if ec set ?
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the native path for the temporary directory
|
||||
*/
|
||||
[[nodiscard]] std::string
|
||||
path() const
|
||||
{
|
||||
return path_.string();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the native path for the a file.
|
||||
*
|
||||
* The file does not need to exist.
|
||||
*/
|
||||
[[nodiscard]] std::string
|
||||
file(std::string const& name) const
|
||||
{
|
||||
return (path_ / name).string();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace beast
|
||||
@@ -92,10 +92,4 @@ operator==(Condition const& lhs, Condition const& rhs)
|
||||
lhs.fingerprint == rhs.fingerprint;
|
||||
}
|
||||
|
||||
inline bool
|
||||
operator!=(Condition const& lhs, Condition const& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
} // namespace xrpl::cryptoconditions
|
||||
|
||||
@@ -93,12 +93,6 @@ operator==(Fulfillment const& lhs, Fulfillment const& rhs)
|
||||
lhs.fingerprint() == rhs.fingerprint();
|
||||
}
|
||||
|
||||
inline bool
|
||||
operator!=(Fulfillment const& lhs, Fulfillment const& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given fulfillment and condition match
|
||||
*/
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <xrpl/basics/contract.h>
|
||||
|
||||
#include <boost/beast/core/string.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Coroutine stack size (1.5 MB). Increased from 1 MB because
|
||||
* ASAN-instrumented deep call stacks exceeded the original limit.
|
||||
*/
|
||||
constexpr std::size_t kCoroStackSize = 1536 * 1024;
|
||||
|
||||
template <class F>
|
||||
JobQueue::Coro::Coro(CoroCreateT, JobQueue& jq, JobType type, std::string name, F&& f)
|
||||
: jq_(jq)
|
||||
, type_(type)
|
||||
, name_(std::move(name))
|
||||
, coro_(
|
||||
boost::context::protected_fixedsize_stack(kCoroStackSize),
|
||||
[this, fn = std::forward<F>(f)](boost::coroutines2::coroutine<void>::push_type& doYield) {
|
||||
yield_ = &doYield;
|
||||
yield();
|
||||
fn(shared_from_this());
|
||||
#ifndef NDEBUG
|
||||
finished_ = true;
|
||||
#endif
|
||||
})
|
||||
{
|
||||
}
|
||||
|
||||
inline JobQueue::Coro::~Coro()
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
XRPL_ASSERT(finished_, "xrpl::JobQueue::Coro::~Coro : is finished");
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void
|
||||
JobQueue::Coro::yield() const
|
||||
{
|
||||
{
|
||||
std::scoped_lock const lock(jq_.mutex_);
|
||||
++jq_.nSuspend_;
|
||||
}
|
||||
(*yield_)();
|
||||
}
|
||||
|
||||
inline bool
|
||||
JobQueue::Coro::post()
|
||||
{
|
||||
{
|
||||
std::scoped_lock const lk(mutexRun_);
|
||||
running_ = true;
|
||||
}
|
||||
|
||||
// sp keeps 'this' alive
|
||||
if (jq_.addJob(type_, name_, [this, sp = shared_from_this()]() { resume(); }))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// The coroutine will not run. Clean up running_.
|
||||
std::scoped_lock const lk(mutexRun_);
|
||||
running_ = false;
|
||||
cv_.notify_all();
|
||||
return false;
|
||||
}
|
||||
|
||||
inline void
|
||||
JobQueue::Coro::resume()
|
||||
{
|
||||
{
|
||||
std::scoped_lock const lk(mutexRun_);
|
||||
running_ = true;
|
||||
}
|
||||
{
|
||||
std::scoped_lock const lk(jq_.mutex_);
|
||||
--jq_.nSuspend_;
|
||||
}
|
||||
auto saved = detail::getLocalValues().release();
|
||||
detail::getLocalValues().reset(&lvs_);
|
||||
std::scoped_lock const lock(mutex_);
|
||||
// A late resume() can arrive after the coroutine has already completed.
|
||||
// This is an expected (if rare) outcome of the race condition documented
|
||||
// in JobQueue.h:354-377 where post() schedules a resume job before the
|
||||
// coroutine yields — the mutex serializes access, but by the time this
|
||||
// resume() acquires the lock the coroutine may have already run to
|
||||
// completion. Calling operator() on a completed boost::coroutine2 is
|
||||
// undefined behavior, so we must check and skip invoking the coroutine
|
||||
// body if it has already completed.
|
||||
if (coro_)
|
||||
{
|
||||
coro_();
|
||||
}
|
||||
detail::getLocalValues().release();
|
||||
detail::getLocalValues().reset(saved);
|
||||
std::scoped_lock const lk(mutexRun_);
|
||||
running_ = false;
|
||||
cv_.notify_all();
|
||||
}
|
||||
|
||||
inline bool
|
||||
JobQueue::Coro::runnable() const
|
||||
{
|
||||
return static_cast<bool>(coro_);
|
||||
}
|
||||
|
||||
inline void
|
||||
JobQueue::Coro::expectEarlyExit()
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
if (!finished_)
|
||||
#endif
|
||||
{
|
||||
// expectEarlyExit() must only ever be called from outside the
|
||||
// Coro's stack. It you're inside the stack you can simply return
|
||||
// and be done.
|
||||
//
|
||||
// That said, since we're outside the Coro's stack, we need to
|
||||
// decrement the nSuspend that the Coro's call to yield caused.
|
||||
std::scoped_lock const lock(jq_.mutex_);
|
||||
--jq_.nSuspend_;
|
||||
#ifndef NDEBUG
|
||||
finished_ = true;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
inline void
|
||||
JobQueue::Coro::join()
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(mutexRun_);
|
||||
cv_.wait(lk, [this]() { return !running_; });
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
713
include/xrpl/core/CoroTask.h
Normal file
713
include/xrpl/core/CoroTask.h
Normal file
@@ -0,0 +1,713 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
|
||||
#include <coroutine>
|
||||
#include <exception>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
template <typename T = void>
|
||||
class CoroTask;
|
||||
|
||||
/**
|
||||
* CoroTask<void> -- coroutine return type for void-returning coroutines.
|
||||
*
|
||||
* Class / Dependency Diagram
|
||||
* ==========================
|
||||
*
|
||||
* CoroTask<void>
|
||||
* +-----------------------------------------------+
|
||||
* | - handle_ : Handle (coroutine_handle<promise>) |
|
||||
* +-----------------------------------------------+
|
||||
* | + handle(), done() |
|
||||
* | + await_ready/suspend/resume (Awaiter iface) |
|
||||
* +-----------------------------------------------+
|
||||
* | owns
|
||||
* v
|
||||
* promise_type
|
||||
* +-----------------------------------------------+
|
||||
* | - exception_ : std::exception_ptr |
|
||||
* | - continuation_ : std::coroutine_handle<> |
|
||||
* +-----------------------------------------------+
|
||||
* | + get_return_object() -> CoroTask |
|
||||
* | + initial_suspend() -> suspend_always (lazy) |
|
||||
* | + final_suspend() -> FinalAwaiter |
|
||||
* | + return_void() |
|
||||
* | + unhandled_exception() |
|
||||
* +-----------------------------------------------+
|
||||
* | returns at final_suspend
|
||||
* v
|
||||
* FinalAwaiter
|
||||
* +-----------------------------------------------+
|
||||
* | await_suspend(h): |
|
||||
* | if continuation_ set -> symmetric transfer |
|
||||
* | else -> noop_coroutine |
|
||||
* +-----------------------------------------------+
|
||||
*
|
||||
* Design Notes
|
||||
* ------------
|
||||
* - Lazy start: initial_suspend returns suspend_always, so the coroutine
|
||||
* body does not execute until the handle is explicitly resumed.
|
||||
* - Symmetric transfer: await_suspend returns a coroutine_handle instead
|
||||
* of void/bool, allowing the scheduler to jump directly to the next
|
||||
* coroutine without growing the call stack.
|
||||
* - Continuation chaining: when one CoroTask is co_await-ed inside
|
||||
* another, the caller's handle is stored as continuation_ so
|
||||
* FinalAwaiter can resume it when this task finishes.
|
||||
* - Move-only: the handle is exclusively owned; copy is deleted.
|
||||
*
|
||||
* Usage Examples
|
||||
* ==============
|
||||
*
|
||||
* 1. Basic void coroutine (the most common case in xrpld):
|
||||
*
|
||||
* CoroTask<void> doWork(std::shared_ptr<CoroTaskRunner> runner) {
|
||||
* // do something
|
||||
* co_await runner->suspend(); // yield control
|
||||
* // resumed later via runner->post() or runner->resume()
|
||||
* co_return;
|
||||
* }
|
||||
*
|
||||
* 2. co_await-ing one CoroTask<void> from another (chaining):
|
||||
*
|
||||
* CoroTask<void> inner() {
|
||||
* // ...
|
||||
* co_return;
|
||||
* }
|
||||
* CoroTask<void> outer() {
|
||||
* co_await inner(); // continuation_ links outer -> inner
|
||||
* co_return; // FinalAwaiter resumes outer
|
||||
* }
|
||||
*
|
||||
* 3. Exceptions propagate through co_await:
|
||||
*
|
||||
* CoroTask<void> failing() {
|
||||
* throw std::runtime_error("oops");
|
||||
* co_return;
|
||||
* }
|
||||
* CoroTask<void> caller() {
|
||||
* try { co_await failing(); }
|
||||
* catch (std::runtime_error const&) { // caught here }
|
||||
* }
|
||||
*
|
||||
* Caveats / Pitfalls
|
||||
* ==================
|
||||
*
|
||||
* BUG-RISK: Dangling references in coroutine parameters.
|
||||
* Coroutine parameters are copied into the frame, but references
|
||||
* are NOT -- they are stored as-is. If the referent goes out of scope
|
||||
* before the coroutine finishes, you get use-after-free.
|
||||
*
|
||||
* // BROKEN -- local dies before coroutine runs:
|
||||
* CoroTask<void> bad(int& ref) { co_return; }
|
||||
* void launch() {
|
||||
* int local = 42;
|
||||
* auto task = bad(local); // frame stores &local
|
||||
* } // local destroyed; frame holds dangling ref
|
||||
*
|
||||
* // FIX -- pass by value, or ensure lifetime via shared_ptr.
|
||||
*
|
||||
* BUG-RISK: GCC 14 corrupts reference captures in coroutine lambdas.
|
||||
* When a lambda that returns CoroTask captures by reference ([&]),
|
||||
* GCC 14 may generate a corrupted coroutine frame. Always capture
|
||||
* by explicit pointer-to-value instead:
|
||||
*
|
||||
* // BROKEN on GCC 14:
|
||||
* jq.postCoroTask(t, n, [&](auto) -> CoroTask<void> { ... });
|
||||
*
|
||||
* // FIX -- capture pointers explicitly:
|
||||
* jq.postCoroTask(t, n, [ptr = &val](auto) -> CoroTask<void> { ... });
|
||||
*
|
||||
* BUG-RISK: Resuming a destroyed or completed CoroTask.
|
||||
* Calling handle().resume() after the coroutine has already run to
|
||||
* completion (done() == true) is undefined behavior. The CoroTaskRunner
|
||||
* guards against this with an XRPL_ASSERT, but standalone usage of
|
||||
* CoroTask must check done() before resuming.
|
||||
*
|
||||
* BUG-RISK: Moving a CoroTask that is being awaited.
|
||||
* If task A is co_await-ed by task B (so A.continuation_ == B), moving
|
||||
* or destroying A will invalidate the continuation link. Never move
|
||||
* or reassign a CoroTask while it is mid-execution or being awaited.
|
||||
*
|
||||
* LIMITATION: CoroTask is fire-and-forget for the top-level owner.
|
||||
* There is no built-in notification when the coroutine finishes.
|
||||
* The caller must use external synchronization (e.g. CoroTaskRunner::join
|
||||
* or a gate/condition_variable) to know when it is done.
|
||||
*
|
||||
* LIMITATION: No cancellation token.
|
||||
* There is no way to cancel a suspended CoroTask from outside. The
|
||||
* coroutine body must cooperatively check a flag (e.g. jq_.isStopping())
|
||||
* after each co_await and co_return early if needed.
|
||||
*
|
||||
* LIMITATION: Stackless -- cannot suspend from nested non-coroutine calls.
|
||||
* If a coroutine calls a regular function that wants to "yield", it
|
||||
* cannot. Only the immediate coroutine body can use co_await.
|
||||
* This is acceptable for xrpld because all yield() sites are shallow.
|
||||
*/
|
||||
template <>
|
||||
class CoroTask<void>
|
||||
{
|
||||
public:
|
||||
// The C++ coroutine protocol mandates these names (promise_type,
|
||||
// initial_suspend, await_ready, ...) and instance-callable awaiter
|
||||
// methods, which conflict with the project naming/static conventions.
|
||||
// NOLINTBEGIN(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
struct promise_type;
|
||||
using Handle = std::coroutine_handle<promise_type>;
|
||||
|
||||
/**
|
||||
* Coroutine promise. Compiler uses this to manage coroutine state.
|
||||
* Stores the exception (if any) and the continuation handle for
|
||||
* symmetric transfer back to the awaiting coroutine.
|
||||
*/
|
||||
struct promise_type
|
||||
{
|
||||
// Captured exception from the coroutine body, rethrown in
|
||||
// await_resume() when this task is co_await-ed by a caller.
|
||||
std::exception_ptr exception_;
|
||||
|
||||
// Handle to the coroutine that is co_await-ing this task.
|
||||
// Set by await_suspend(). FinalAwaiter uses it for symmetric
|
||||
// transfer back to the caller. Null if this is a top-level task.
|
||||
std::coroutine_handle<> continuation_;
|
||||
|
||||
/**
|
||||
* Create the CoroTask return object.
|
||||
* Called by the compiler at coroutine creation.
|
||||
*/
|
||||
CoroTask
|
||||
get_return_object()
|
||||
{
|
||||
return CoroTask{Handle::from_promise(*this)};
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy start. The coroutine body does not execute until the
|
||||
* handle is explicitly resumed (e.g. by CoroTaskRunner::resume).
|
||||
*/
|
||||
std::suspend_always
|
||||
initial_suspend() noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Awaiter returned by final_suspend(). Uses symmetric transfer:
|
||||
* if a continuation exists, transfers control directly to it
|
||||
* (tail-call, no stack growth). Otherwise returns noop_coroutine
|
||||
* so the coroutine frame stays alive for the owner to destroy.
|
||||
*/
|
||||
struct FinalAwaiter
|
||||
{
|
||||
/**
|
||||
* Always false. We need await_suspend to run for
|
||||
* symmetric transfer.
|
||||
*/
|
||||
bool
|
||||
await_ready() noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Symmetric transfer: returns the continuation handle so
|
||||
* the compiler emits a tail-call instead of a nested resume.
|
||||
* If no continuation is set, returns noop_coroutine to
|
||||
* suspend at final_suspend without destroying the frame.
|
||||
*
|
||||
* @param h Handle to this completing coroutine
|
||||
*
|
||||
* @return Continuation handle, or noop_coroutine
|
||||
*/
|
||||
std::coroutine_handle<>
|
||||
await_suspend(Handle h) noexcept
|
||||
{
|
||||
if (auto cont = h.promise().continuation_)
|
||||
return cont;
|
||||
return std::noop_coroutine();
|
||||
}
|
||||
|
||||
void
|
||||
await_resume() noexcept
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns FinalAwaiter for symmetric transfer at coroutine end.
|
||||
*/
|
||||
FinalAwaiter
|
||||
final_suspend() noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the compiler for `co_return;` (void coroutine).
|
||||
*/
|
||||
void
|
||||
return_void()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the compiler when an exception escapes the coroutine
|
||||
* body. Captures it for later rethrowing in await_resume().
|
||||
*/
|
||||
void
|
||||
unhandled_exception()
|
||||
{
|
||||
exception_ = std::current_exception();
|
||||
}
|
||||
};
|
||||
// NOLINTEND(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
|
||||
/**
|
||||
* Default constructor. Creates an empty (null handle) task.
|
||||
*/
|
||||
CoroTask() = default;
|
||||
|
||||
/**
|
||||
* Takes ownership of a compiler-generated coroutine handle.
|
||||
*
|
||||
* @param h Coroutine handle to own
|
||||
*/
|
||||
explicit CoroTask(Handle h) : handle_(h)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the coroutine frame if this task owns one.
|
||||
*/
|
||||
~CoroTask()
|
||||
{
|
||||
if (handle_)
|
||||
handle_.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Move constructor. Transfers handle ownership, leaves other empty.
|
||||
*/
|
||||
CoroTask(CoroTask&& other) noexcept : handle_(std::exchange(other.handle_, {}))
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Move assignment. Destroys current frame (if any), takes other's.
|
||||
*/
|
||||
CoroTask&
|
||||
operator=(CoroTask&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
if (handle_)
|
||||
handle_.destroy();
|
||||
handle_ = std::exchange(other.handle_, {});
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
CoroTask(CoroTask const&) = delete;
|
||||
CoroTask&
|
||||
operator=(CoroTask const&) = delete;
|
||||
|
||||
/**
|
||||
* @return The underlying coroutine_handle
|
||||
*/
|
||||
[[nodiscard]] Handle
|
||||
handle() const
|
||||
{
|
||||
return handle_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the coroutine has run to completion (or thrown)
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
done() const
|
||||
{
|
||||
return handle_ && handle_.done();
|
||||
}
|
||||
|
||||
// -- Awaiter interface: allows `co_await someCoroTask;` --
|
||||
|
||||
/**
|
||||
* Always false. This task is lazy, so co_await always suspends
|
||||
* the caller to set up the continuation link.
|
||||
*/
|
||||
// NOLINTBEGIN(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
[[nodiscard]] bool
|
||||
await_ready() const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the caller's handle as our continuation, then returns
|
||||
* our handle for symmetric transfer (caller suspends, we resume).
|
||||
*
|
||||
* @param caller Handle of the coroutine doing co_await on us
|
||||
*
|
||||
* @return Our handle for symmetric transfer
|
||||
*/
|
||||
std::coroutine_handle<>
|
||||
await_suspend(std::coroutine_handle<> caller) noexcept
|
||||
{
|
||||
XRPL_ASSERT(handle_, "xrpl::CoroTask<void>::await_suspend : handle is valid");
|
||||
handle_.promise().continuation_ = caller;
|
||||
return handle_; // Symmetric transfer
|
||||
}
|
||||
|
||||
/**
|
||||
* Called in the awaiting coroutine's context after this task
|
||||
* completes. Rethrows any exception captured by
|
||||
* unhandled_exception().
|
||||
*/
|
||||
void
|
||||
await_resume()
|
||||
{
|
||||
XRPL_ASSERT(handle_, "xrpl::CoroTask<void>::await_resume : handle is valid");
|
||||
if (auto& ep = handle_.promise().exception_)
|
||||
std::rethrow_exception(ep);
|
||||
}
|
||||
// NOLINTEND(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
|
||||
private:
|
||||
// Exclusively-owned coroutine handle. Null after move or default
|
||||
// construction. Destroyed in the destructor.
|
||||
Handle handle_;
|
||||
};
|
||||
|
||||
/**
|
||||
* CoroTask<T> -- coroutine return type for value-returning coroutines.
|
||||
*
|
||||
* Class / Dependency Diagram
|
||||
* ==========================
|
||||
*
|
||||
* CoroTask<T>
|
||||
* +-----------------------------------------------+
|
||||
* | - handle_ : Handle (coroutine_handle<promise>) |
|
||||
* +-----------------------------------------------+
|
||||
* | + handle(), done() |
|
||||
* | + await_ready/suspend/resume (Awaiter iface) |
|
||||
* +-----------------------------------------------+
|
||||
* | owns
|
||||
* v
|
||||
* promise_type
|
||||
* +-----------------------------------------------+
|
||||
* | - result_ : variant<monostate, T, |
|
||||
* | exception_ptr> |
|
||||
* | - continuation_ : std::coroutine_handle<> |
|
||||
* +-----------------------------------------------+
|
||||
* | + get_return_object() -> CoroTask |
|
||||
* | + initial_suspend() -> suspend_always (lazy) |
|
||||
* | + final_suspend() -> FinalAwaiter |
|
||||
* | + return_value(T) -> stores in result_[1] |
|
||||
* | + unhandled_exception -> stores in result_[2] |
|
||||
* +-----------------------------------------------+
|
||||
* | returns at final_suspend
|
||||
* v
|
||||
* FinalAwaiter (same symmetric-transfer pattern as CoroTask<void>)
|
||||
*
|
||||
* Value Extraction
|
||||
* ----------------
|
||||
* await_resume() inspects the variant:
|
||||
* - index 2 (exception_ptr) -> rethrow
|
||||
* - index 1 (T) -> return value via move
|
||||
*
|
||||
* Usage Examples
|
||||
* ==============
|
||||
*
|
||||
* 1. Simple value return:
|
||||
*
|
||||
* CoroTask<int> computeAnswer() { co_return 42; }
|
||||
*
|
||||
* CoroTask<void> caller() {
|
||||
* int v = co_await computeAnswer(); // v == 42
|
||||
* }
|
||||
*
|
||||
* 2. Chaining value-returning coroutines:
|
||||
*
|
||||
* CoroTask<int> add(int a, int b) { co_return a + b; }
|
||||
* CoroTask<int> doubleSum(int a, int b) {
|
||||
* int s = co_await add(a, b);
|
||||
* co_return s * 2;
|
||||
* }
|
||||
*
|
||||
* 3. Exception propagation from inner to outer:
|
||||
*
|
||||
* CoroTask<int> failing() {
|
||||
* throw std::runtime_error("bad");
|
||||
* co_return 0; // never reached
|
||||
* }
|
||||
* CoroTask<void> caller() {
|
||||
* try {
|
||||
* int v = co_await failing(); // throws here
|
||||
* } catch (std::runtime_error const& e) {
|
||||
* // e.what() == "bad"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* Caveats / Pitfalls (in addition to CoroTask<void> caveats above)
|
||||
* ================================================================
|
||||
*
|
||||
* BUG-RISK: await_resume() moves the value out of the variant.
|
||||
* Calling co_await on the same CoroTask<T> instance twice is undefined
|
||||
* behavior -- the second call will see a moved-from T. CoroTask is
|
||||
* single-shot: one co_return, one co_await.
|
||||
*
|
||||
* BUG-RISK: T must be move-constructible.
|
||||
* return_value(T) takes by value and moves into the variant.
|
||||
* Types that are not movable cannot be used as T.
|
||||
*
|
||||
* LIMITATION: No co_yield support.
|
||||
* CoroTask<T> only supports a single co_return. It does not implement
|
||||
* yield_value(), so using co_yield inside a CoroTask<T> coroutine is a
|
||||
* compile error. For streaming values, a different return type
|
||||
* (e.g. Generator<T>) would be needed.
|
||||
*
|
||||
* LIMITATION: Result is only accessible via co_await.
|
||||
* There is no .get() or .result() method. The value can only be
|
||||
* extracted by co_await-ing the CoroTask<T> from inside another
|
||||
* coroutine. For extracting results in non-coroutine code, pass a
|
||||
* pointer to the caller and write through it (as the tests do).
|
||||
*/
|
||||
template <typename T>
|
||||
class CoroTask
|
||||
{
|
||||
static_assert(
|
||||
std::is_move_constructible_v<T>,
|
||||
"CoroTask<T> requires T to be move-constructible");
|
||||
|
||||
public:
|
||||
// The C++ coroutine protocol mandates these names (promise_type,
|
||||
// initial_suspend, await_ready, ...) and instance-callable awaiter
|
||||
// methods, which conflict with the project naming/static conventions.
|
||||
// NOLINTBEGIN(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
struct promise_type;
|
||||
using Handle = std::coroutine_handle<promise_type>;
|
||||
|
||||
/**
|
||||
* Coroutine promise for value-returning coroutines.
|
||||
* Stores the result as a variant: monostate (not yet set),
|
||||
* T (co_return value), or exception_ptr (unhandled exception).
|
||||
*/
|
||||
struct promise_type
|
||||
{
|
||||
// Tri-state result:
|
||||
// index 0 (monostate) -- coroutine has not yet completed
|
||||
// index 1 (T) -- co_return value stored here
|
||||
// index 2 (exception) -- unhandled exception captured here
|
||||
std::variant<std::monostate, T, std::exception_ptr> result_;
|
||||
|
||||
// Handle to the coroutine co_await-ing this task. Used by
|
||||
// FinalAwaiter for symmetric transfer. Null for top-level tasks.
|
||||
std::coroutine_handle<> continuation_;
|
||||
|
||||
/**
|
||||
* Create the CoroTask return object.
|
||||
* Called by the compiler at coroutine creation.
|
||||
*/
|
||||
CoroTask
|
||||
get_return_object()
|
||||
{
|
||||
return CoroTask{Handle::from_promise(*this)};
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy start. Coroutine body does not run until explicitly resumed.
|
||||
*/
|
||||
std::suspend_always
|
||||
initial_suspend() noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Symmetric-transfer awaiter at coroutine completion.
|
||||
* Same pattern as CoroTask<void>::FinalAwaiter.
|
||||
*/
|
||||
struct FinalAwaiter
|
||||
{
|
||||
bool
|
||||
await_ready() noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns continuation for symmetric transfer, or
|
||||
* noop_coroutine if this is a top-level task.
|
||||
*
|
||||
* @param h Handle to this completing coroutine
|
||||
*
|
||||
* @return Continuation handle, or noop_coroutine
|
||||
*/
|
||||
std::coroutine_handle<>
|
||||
await_suspend(Handle h) noexcept
|
||||
{
|
||||
if (auto cont = h.promise().continuation_)
|
||||
return cont;
|
||||
return std::noop_coroutine();
|
||||
}
|
||||
|
||||
void
|
||||
await_resume() noexcept
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
FinalAwaiter
|
||||
final_suspend() noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the compiler for `co_return value;`.
|
||||
* Moves the value into result_ at index 1.
|
||||
*
|
||||
* @param value The value to store
|
||||
*/
|
||||
void
|
||||
return_value(T value)
|
||||
{
|
||||
result_.template emplace<1>(std::move(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures unhandled exceptions at index 2 of result_.
|
||||
* Rethrown later in await_resume().
|
||||
*/
|
||||
void
|
||||
unhandled_exception()
|
||||
{
|
||||
result_.template emplace<2>(std::current_exception());
|
||||
}
|
||||
};
|
||||
// NOLINTEND(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
|
||||
/**
|
||||
* Default constructor. Creates an empty (null handle) task.
|
||||
*/
|
||||
CoroTask() = default;
|
||||
|
||||
/**
|
||||
* Takes ownership of a compiler-generated coroutine handle.
|
||||
*
|
||||
* @param h Coroutine handle to own
|
||||
*/
|
||||
explicit CoroTask(Handle h) : handle_(h)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the coroutine frame if this task owns one.
|
||||
*/
|
||||
~CoroTask()
|
||||
{
|
||||
if (handle_)
|
||||
handle_.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Move constructor. Transfers handle ownership, leaves other empty.
|
||||
*/
|
||||
CoroTask(CoroTask&& other) noexcept : handle_(std::exchange(other.handle_, {}))
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Move assignment. Destroys current frame (if any), takes other's.
|
||||
*/
|
||||
CoroTask&
|
||||
operator=(CoroTask&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
if (handle_)
|
||||
handle_.destroy();
|
||||
handle_ = std::exchange(other.handle_, {});
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
CoroTask(CoroTask const&) = delete;
|
||||
CoroTask&
|
||||
operator=(CoroTask const&) = delete;
|
||||
|
||||
/**
|
||||
* @return The underlying coroutine_handle
|
||||
*/
|
||||
[[nodiscard]] Handle
|
||||
handle() const
|
||||
{
|
||||
return handle_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the coroutine has run to completion (or thrown)
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
done() const
|
||||
{
|
||||
return handle_ && handle_.done();
|
||||
}
|
||||
|
||||
// -- Awaiter interface: allows `T val = co_await someCoroTask;` --
|
||||
|
||||
/**
|
||||
* Always false. co_await always suspends to set up continuation.
|
||||
*/
|
||||
// NOLINTBEGIN(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
[[nodiscard]] bool
|
||||
await_ready() const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores caller as continuation, returns our handle for
|
||||
* symmetric transfer.
|
||||
*
|
||||
* @param caller Handle of the coroutine doing co_await on us
|
||||
*
|
||||
* @return Our handle for symmetric transfer
|
||||
*/
|
||||
std::coroutine_handle<>
|
||||
await_suspend(std::coroutine_handle<> caller) noexcept
|
||||
{
|
||||
XRPL_ASSERT(handle_, "xrpl::CoroTask<T>::await_suspend : handle is valid");
|
||||
handle_.promise().continuation_ = caller;
|
||||
return handle_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the result: rethrows if exception, otherwise moves
|
||||
* the T value out of the variant. Single-shot: calling twice
|
||||
* on the same task is undefined (moved-from T).
|
||||
*
|
||||
* @return The co_return-ed value
|
||||
*/
|
||||
T
|
||||
await_resume()
|
||||
{
|
||||
XRPL_ASSERT(handle_, "xrpl::CoroTask<T>::await_resume : handle is valid");
|
||||
auto& result = handle_.promise().result_;
|
||||
if (auto* ep = std::get_if<2>(&result))
|
||||
std::rethrow_exception(*ep);
|
||||
return std::get<1>(std::move(result));
|
||||
}
|
||||
// NOLINTEND(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
|
||||
private:
|
||||
// Exclusively-owned coroutine handle. Null after move or default
|
||||
// construction. Destroyed in the destructor.
|
||||
Handle handle_;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
448
include/xrpl/core/CoroTaskRunner.ipp
Normal file
448
include/xrpl/core/CoroTaskRunner.ipp
Normal file
@@ -0,0 +1,448 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @file CoroTaskRunner.ipp
|
||||
*
|
||||
* CoroTaskRunner inline implementation.
|
||||
*
|
||||
* This file contains the business logic for managing C++20 coroutines
|
||||
* on the JobQueue. It is included at the bottom of JobQueue.h.
|
||||
*
|
||||
* Data Flow: suspend / post / resume cycle
|
||||
* =========================================
|
||||
*
|
||||
* coroutine body CoroTaskRunner JobQueue
|
||||
* -------------- -------------- --------
|
||||
* |
|
||||
* co_await runner->suspend()
|
||||
* |
|
||||
* +--- await_suspend ------> onSuspend()
|
||||
* | ++nSuspend_ ------------> nSuspend_
|
||||
* | [coroutine is now suspended]
|
||||
* |
|
||||
* . (externally or by yieldAndPost())
|
||||
* .
|
||||
* +--- (caller calls) -----> post()
|
||||
* | ++runCount_
|
||||
* | addJob(resume) ----------> job enqueued
|
||||
* | |
|
||||
* | [worker picks up]
|
||||
* | |
|
||||
* +--- <----- resume() <-----------------------------------+
|
||||
* | --nSuspend_ ------> nSuspend_
|
||||
* | swap in LocalValues (lvs_)
|
||||
* | task_.handle().resume()
|
||||
* | |
|
||||
* | [coroutine body continues here]
|
||||
* | |
|
||||
* | swap out LocalValues
|
||||
* | --runCount_
|
||||
* | cv_.notify_all()
|
||||
* v
|
||||
*
|
||||
* Thread Safety
|
||||
* =============
|
||||
* - mutex_ : guards task_.handle().resume() so that post()-before-suspend
|
||||
* races cannot resume the coroutine while it is still running.
|
||||
* (See the race condition discussion in JobQueue.h)
|
||||
* - mutexRun_ : guards runCount_ counter; used by join() to wait until
|
||||
* all in-flight resume operations complete.
|
||||
* - jq_.mutex_: guards nSuspend_ increments/decrements.
|
||||
*
|
||||
* Common Mistakes When Modifying This File
|
||||
* =========================================
|
||||
*
|
||||
* 1. Changing lock ordering.
|
||||
* resume() acquires locks sequentially (never held simultaneously):
|
||||
* jq_.mutex_ (released immediately), then mutex_ (held across resume),
|
||||
* then mutexRun_ (released after decrement). post() acquires only
|
||||
* mutexRun_. Any new code path must follow the same order.
|
||||
*
|
||||
* 2. Removing the shared_from_this() capture in post().
|
||||
* The lambda passed to addJob captures [this, sp = shared_from_this()].
|
||||
* If you remove sp, 'this' can be destroyed before the job runs,
|
||||
* causing use-after-free. The sp capture is load-bearing.
|
||||
*
|
||||
* 3. Forgetting to decrement nSuspend_ on a new code path.
|
||||
* Every ++nSuspend_ must have a matching --nSuspend_. If you add a new
|
||||
* suspension path (e.g. a new awaiter) and forget to decrement on resume
|
||||
* or on failure, JobQueue::stop() will hang.
|
||||
*
|
||||
* 4. Calling task_.handle().resume() without holding mutex_.
|
||||
* This allows a race where the coroutine runs on two threads
|
||||
* simultaneously. Always hold mutex_ around resume().
|
||||
*
|
||||
* 5. Swapping LocalValues outside of the mutex_ critical section.
|
||||
* The swap-in and swap-out of LocalValues must bracket the resume()
|
||||
* call. If you move the swap-out before the lock_guard(mutex_) is
|
||||
* released, you break LocalValue isolation for any code that runs
|
||||
* after the coroutine suspends but before the lock is dropped.
|
||||
*/
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Construct a CoroTaskRunner. Sets runCount_ to 0; does not
|
||||
* create the coroutine. Call init() afterwards.
|
||||
*
|
||||
* @param jq The JobQueue this coroutine will run on
|
||||
* @param type Job type for scheduling priority
|
||||
* @param name Human-readable name for logging
|
||||
*/
|
||||
inline JobQueue::CoroTaskRunner::CoroTaskRunner(
|
||||
CreateT,
|
||||
JobQueue& jq,
|
||||
JobType type,
|
||||
std::string name)
|
||||
: jq_(jq), type_(type), name_(std::move(name))
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize with a coroutine-returning callable.
|
||||
* Stores the callable on the heap (FuncStore) so it outlives the
|
||||
* coroutine frame. Coroutine frames store a reference to the
|
||||
* callable's implicit object parameter (the lambda). If the callable
|
||||
* is a temporary, that reference dangles after the caller returns.
|
||||
* Keeping the callable alive here ensures the coroutine's captures
|
||||
* remain valid.
|
||||
*
|
||||
* @param f Callable: CoroTask<void>(shared_ptr<CoroTaskRunner>)
|
||||
*/
|
||||
template <class F>
|
||||
void
|
||||
JobQueue::CoroTaskRunner::init(F&& f)
|
||||
{
|
||||
using Fn = std::decay_t<F>;
|
||||
auto store = std::make_unique<FuncStore<Fn>>(std::forward<F>(f));
|
||||
task_ = store->func(shared_from_this());
|
||||
storedFunc_ = std::move(store);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor. Waits for any in-flight resume() to complete, then
|
||||
* asserts (debug) that the coroutine has finished or
|
||||
* expectEarlyExit() was called.
|
||||
*
|
||||
* The join() call is necessary because with async dispatch the
|
||||
* coroutine runs on a worker thread. The gate signal (which wakes
|
||||
* the test thread) can arrive before resume() has set finished_.
|
||||
* join() synchronizes via mutexRun_, establishing a happens-before
|
||||
* edge: finished_ = true -> unlock(mutexRun_) in resume() ->
|
||||
* lock(mutexRun_) in join() -> read finished_.
|
||||
*/
|
||||
inline JobQueue::CoroTaskRunner::~CoroTaskRunner()
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
join();
|
||||
XRPL_ASSERT(finished_, "xrpl::JobQueue::CoroTaskRunner::~CoroTaskRunner : is finished");
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the JobQueue's suspended-coroutine count (nSuspend_).
|
||||
*/
|
||||
inline void
|
||||
JobQueue::CoroTaskRunner::onSuspend()
|
||||
{
|
||||
std::scoped_lock const lock(jq_.mutex_);
|
||||
++jq_.nSuspend_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrement nSuspend_ without resuming.
|
||||
*/
|
||||
inline void
|
||||
JobQueue::CoroTaskRunner::onUndoSuspend()
|
||||
{
|
||||
std::scoped_lock const lock(jq_.mutex_);
|
||||
--jq_.nSuspend_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a SuspendAwaiter whose await_suspend() increments nSuspend_
|
||||
* before the coroutine actually suspends. The caller must later call
|
||||
* post() or resume() to continue execution.
|
||||
*
|
||||
* @return Awaiter for use with `co_await runner->suspend()`
|
||||
*/
|
||||
inline auto
|
||||
JobQueue::CoroTaskRunner::suspend()
|
||||
{
|
||||
/**
|
||||
* Custom awaiter for suspend(). Always suspends (await_ready
|
||||
* returns false) and increments nSuspend_ in await_suspend().
|
||||
*/
|
||||
// The C++ coroutine protocol mandates these awaiter names and
|
||||
// instance-callable methods, which conflict with the project
|
||||
// naming/static conventions.
|
||||
// NOLINTBEGIN(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
struct SuspendAwaiter
|
||||
{
|
||||
CoroTaskRunner& runner_; // The runner that owns this coroutine.
|
||||
|
||||
/**
|
||||
* Always returns false so the coroutine suspends.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
await_ready() const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the coroutine suspends. Increments nSuspend_
|
||||
* so the JobQueue knows a coroutine is waiting.
|
||||
*/
|
||||
void
|
||||
await_suspend(std::coroutine_handle<>) const
|
||||
{
|
||||
runner_.onSuspend();
|
||||
}
|
||||
|
||||
void
|
||||
await_resume() const noexcept
|
||||
{
|
||||
}
|
||||
};
|
||||
// NOLINTEND(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
return SuspendAwaiter{*this};
|
||||
}
|
||||
|
||||
/**
|
||||
* Suspend and immediately repost on the JobQueue. Equivalent to
|
||||
* `co_await JobQueueAwaiter{runner}` but uses an inline struct
|
||||
* to work around a GCC-12 codegen bug (see declaration in JobQueue.h).
|
||||
*
|
||||
* If the JobQueue is stopping (post fails), the suspend count is
|
||||
* undone and the coroutine continues immediately via symmetric
|
||||
* transfer back to its own handle.
|
||||
*
|
||||
* @return An inline YieldPostAwaiter
|
||||
*/
|
||||
inline auto
|
||||
JobQueue::CoroTaskRunner::yieldAndPost()
|
||||
{
|
||||
// The C++ coroutine protocol mandates these awaiter names and
|
||||
// instance-callable methods, which conflict with the project
|
||||
// naming/static conventions.
|
||||
// NOLINTBEGIN(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
struct YieldPostAwaiter
|
||||
{
|
||||
CoroTaskRunner& runner_;
|
||||
|
||||
[[nodiscard]] bool
|
||||
await_ready() const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a coroutine_handle<> (symmetric transfer) rather than
|
||||
* void + h.resume(). Two reasons:
|
||||
*
|
||||
* 1. h.resume() runs the coroutine nested inside this frame. A
|
||||
* coroutine that yields in a loop against a stopping JobQueue
|
||||
* fails post() every iteration, so the stack grows without
|
||||
* bound. Symmetric transfer is a tail call and does not nest.
|
||||
*
|
||||
* 2. After h.resume() returns, the coroutine may have completed
|
||||
* and destroyed its frame -- the frame this awaiter lives in.
|
||||
* Returning from await_suspend would then touch freed memory.
|
||||
*
|
||||
* A bool return would also avoid nesting, but GCC-12 miscompiles
|
||||
* bool-returning await_suspend (see JobQueueAwaiter.h).
|
||||
*
|
||||
* @return noop_coroutine() to stay suspended (job posted);
|
||||
* the caller's handle to continue now (JQ stopping)
|
||||
*/
|
||||
std::coroutine_handle<>
|
||||
await_suspend(std::coroutine_handle<> h)
|
||||
{
|
||||
runner_.onSuspend();
|
||||
if (!runner_.post())
|
||||
{
|
||||
runner_.onUndoSuspend();
|
||||
return h;
|
||||
}
|
||||
return std::noop_coroutine();
|
||||
}
|
||||
|
||||
void
|
||||
await_resume() const noexcept
|
||||
{
|
||||
}
|
||||
};
|
||||
// NOLINTEND(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
return YieldPostAwaiter{*this};
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule coroutine resumption as a job on the JobQueue.
|
||||
* A shared_ptr capture (sp) prevents this CoroTaskRunner from being
|
||||
* destroyed while the job is queued but not yet executed.
|
||||
*
|
||||
* @return false if the JobQueue rejected the job (shutting down)
|
||||
*/
|
||||
inline bool
|
||||
JobQueue::CoroTaskRunner::post()
|
||||
{
|
||||
{
|
||||
std::scoped_lock const lk(mutexRun_);
|
||||
++runCount_;
|
||||
}
|
||||
|
||||
// sp prevents 'this' from being destroyed while the job is pending
|
||||
if (jq_.addJob(type_, name_, [this, sp = shared_from_this()]() { resume(); }))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// The coroutine will not run. Undo the runCount_ increment.
|
||||
std::scoped_lock const lk(mutexRun_);
|
||||
--runCount_;
|
||||
cv_.notify_all();
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume the coroutine on the current thread.
|
||||
*
|
||||
* Steps:
|
||||
* 1. Decrement nSuspend_ (under jq_.mutex_)
|
||||
* 2. Swap in this coroutine's LocalValues for thread-local isolation
|
||||
* 3. Resume the coroutine handle (under mutex_)
|
||||
* 4. Swap out LocalValues, restoring the thread's previous state
|
||||
* 5. Decrement runCount_ and notify join() waiters
|
||||
*
|
||||
* @pre post() must have been called before resume(). Direct calls
|
||||
* without a prior post() will corrupt runCount_ and break join().
|
||||
* Note: runCount_ is NOT incremented here — post() already did that.
|
||||
* This ensures join() stays blocked for the entire post->resume lifetime.
|
||||
*/
|
||||
inline void
|
||||
JobQueue::CoroTaskRunner::resume()
|
||||
{
|
||||
{
|
||||
std::scoped_lock const lock(jq_.mutex_);
|
||||
--jq_.nSuspend_;
|
||||
}
|
||||
auto saved = detail::getLocalValues().release();
|
||||
detail::getLocalValues().reset(&lvs_);
|
||||
std::scoped_lock const lock(mutex_);
|
||||
XRPL_ASSERT(
|
||||
task_.handle() && !task_.done(),
|
||||
"xrpl::JobQueue::CoroTaskRunner::resume : task handle is valid and not done");
|
||||
if (task_.handle() && !task_.done())
|
||||
{
|
||||
task_.handle().resume();
|
||||
}
|
||||
else
|
||||
{
|
||||
// A resume() with no coroutine to run (e.g. a duplicate external
|
||||
// post() after completion). Resuming a null or finished handle is
|
||||
// undefined behavior, so skip it -- this matches the old
|
||||
// Coro::resume() `if (coro_)` guard. The bookkeeping below still
|
||||
// runs to balance the ++runCount_ done by the post() that
|
||||
// scheduled this call.
|
||||
JLOG(jq_.journal_.warn())
|
||||
<< "CoroTaskRunner::resume called for coroutine '" << name_
|
||||
<< "' with no runnable coroutine (duplicate post or already completed)";
|
||||
}
|
||||
detail::getLocalValues().release();
|
||||
detail::getLocalValues().reset(saved);
|
||||
if (task_.done())
|
||||
{
|
||||
finished_ = true;
|
||||
// An exception that escapes a top-level coroutine body is captured
|
||||
// by promise_type::unhandled_exception() but has no awaiter to
|
||||
// rethrow it, so it would vanish with the frame. Surface it in the
|
||||
// log. (The old Boost path propagated it out of resume() instead.)
|
||||
if (auto const& ep = task_.handle().promise().exception_)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::rethrow_exception(ep);
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
JLOG(jq_.journal_.error())
|
||||
<< "Unhandled exception in coroutine '" << name_ << "': " << e.what();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
JLOG(jq_.journal_.error())
|
||||
<< "Unhandled non-standard exception in coroutine '" << name_ << "'";
|
||||
}
|
||||
}
|
||||
// Break the shared_ptr cycle: frame -> shared_ptr<runner> -> this.
|
||||
// Use std::move (not task_ = {}) so task_.handle_ is null BEFORE the
|
||||
// frame is destroyed. operator= would destroy the frame while handle_
|
||||
// still holds the old value -- a re-entrancy hazard on GCC-12 if
|
||||
// frame destruction triggers runner cleanup.
|
||||
[[maybe_unused]] auto completed = std::move(task_);
|
||||
}
|
||||
std::scoped_lock const lk(mutexRun_);
|
||||
--runCount_;
|
||||
cv_.notify_all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the coroutine has not yet run to completion
|
||||
*/
|
||||
inline bool
|
||||
JobQueue::CoroTaskRunner::runnable() const
|
||||
{
|
||||
// After normal completion, task_ is reset to break the shared_ptr cycle
|
||||
// (handle_ becomes null). A null handle means the coroutine is done.
|
||||
return task_.handle() && !task_.done();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle early termination when the coroutine never ran (e.g. JobQueue
|
||||
* is stopping). Decrements nSuspend_ and destroys the coroutine frame
|
||||
* to break the shared_ptr cycle: frame -> lambda -> runner -> frame.
|
||||
*/
|
||||
inline void
|
||||
JobQueue::CoroTaskRunner::expectEarlyExit()
|
||||
{
|
||||
if (!finished_)
|
||||
{
|
||||
std::scoped_lock const lock(jq_.mutex_);
|
||||
--jq_.nSuspend_;
|
||||
finished_ = true;
|
||||
}
|
||||
// Break the shared_ptr cycle: frame -> shared_ptr<runner> -> this.
|
||||
// The coroutine is at initial_suspend and never ran user code, so
|
||||
// destroying it is safe. Use std::move (not task_ = {}) so
|
||||
// task_.handle_ is null before the frame is destroyed.
|
||||
{
|
||||
[[maybe_unused]] auto completed = std::move(task_);
|
||||
}
|
||||
storedFunc_.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Block until all pending/active resume operations complete.
|
||||
* Uses cv_ + mutexRun_ to wait until runCount_ reaches 0 or
|
||||
* finished_ becomes true. The finished_ check handles the case
|
||||
* where resume() is called directly (without post()), which
|
||||
* decrements runCount_ below zero. In that scenario runCount_
|
||||
* never returns to 0, but finished_ becoming true guarantees
|
||||
* the coroutine is done and no more resumes will occur.
|
||||
*
|
||||
* Note: when join() returns via the finished_ disjunct, the final
|
||||
* resume() call may still be executing its post-completion
|
||||
* bookkeeping (the --runCount_ / notify after finished_ is set).
|
||||
* That is safe -- the coroutine body has fully completed and the
|
||||
* runner is kept alive by the resume job's shared_ptr -- but
|
||||
* callers must not assume resume() itself has returned.
|
||||
*/
|
||||
inline void
|
||||
JobQueue::CoroTaskRunner::join()
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(mutexRun_);
|
||||
cv_.wait(lk, [this]() { return runCount_ == 0 || finished_; });
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,25 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/LocalValue.h>
|
||||
#include <xrpl/core/ClosureCounter.h>
|
||||
#include <xrpl/core/JobTypeData.h>
|
||||
#include <xrpl/core/detail/Workers.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
// Include only the specific Boost.Coroutine2 headers actually used here.
|
||||
// Avoid `boost/coroutine2/all.hpp` because it transitively pulls in
|
||||
// `boost/context/pooled_fixedsize_stack.hpp`, whose `.malloc()` / `.free()`
|
||||
// member calls on `boost::pool` collide with MSVC's `_CRTDBG_MAP_ALLOC` macros
|
||||
// in Debug builds (see cmake/XrplCompiler.cmake).
|
||||
#include <xrpl/beast/insight/Collector.h>
|
||||
#include <xrpl/beast/insight/Gauge.h>
|
||||
#include <xrpl/beast/insight/Hook.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/core/ClosureCounter.h>
|
||||
#include <xrpl/core/CoroTask.h>
|
||||
#include <xrpl/core/Job.h>
|
||||
#include <xrpl/core/JobTypeData.h>
|
||||
#include <xrpl/core/LoadEvent.h>
|
||||
|
||||
#include <boost/context/protected_fixedsize_stack.hpp>
|
||||
#include <boost/coroutine2/coroutine.hpp>
|
||||
#include <xrpl/core/detail/Workers.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
@@ -40,10 +32,6 @@ class PerfLog;
|
||||
} // namespace perf
|
||||
|
||||
class Logs;
|
||||
struct CoroCreateT
|
||||
{
|
||||
explicit CoroCreateT() = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* A pool of threads to perform work.
|
||||
@@ -60,93 +48,414 @@ class JobQueue : private Workers::Callback
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Coroutines must run to completion.
|
||||
* C++20 coroutine lifecycle manager.
|
||||
*
|
||||
* Class / Inheritance / Dependency Diagram
|
||||
* =========================================
|
||||
*
|
||||
* std::enable_shared_from_this<CoroTaskRunner>
|
||||
* ^
|
||||
* | (public inheritance)
|
||||
* |
|
||||
* CoroTaskRunner
|
||||
* +---------------------------------------------------+
|
||||
* | - lvs_ : detail::LocalValues |
|
||||
* | - jq_ : JobQueue& |
|
||||
* | - type_ : JobType |
|
||||
* | - name_ : std::string |
|
||||
* | - runCount_ : int (in-flight resumes) |
|
||||
* | - mutex_ : std::mutex (coroutine guard) |
|
||||
* | - mutexRun_ : std::mutex (join guard) |
|
||||
* | - cv_ : condition_variable |
|
||||
* | - task_ : CoroTask<void> |
|
||||
* | - storedFunc_ : unique_ptr<FuncBase> (type-erased)|
|
||||
* +---------------------------------------------------+
|
||||
* | + init(F&&) : set up coroutine callable |
|
||||
* | + onSuspend() : ++jq_.nSuspend_ |
|
||||
* | + onUndoSuspend() : --jq_.nSuspend_ |
|
||||
* | + suspend() : returns SuspendAwaiter |
|
||||
* | + post() : schedule resume on JobQueue |
|
||||
* | + resume() : resume coroutine on caller |
|
||||
* | + runnable() : !task_.done() |
|
||||
* | + expectEarlyExit() : teardown for failed post |
|
||||
* | + join() : block until not running |
|
||||
* +---------------------------------------------------+
|
||||
* | |
|
||||
* | owns | references
|
||||
* v v
|
||||
* CoroTask<void> JobQueue
|
||||
* (coroutine frame) (thread pool + nSuspend_)
|
||||
*
|
||||
* FuncBase / FuncStore<F> (type-erased heap storage
|
||||
* for the coroutine lambda)
|
||||
*
|
||||
* Coroutine Lifecycle (Control Flow)
|
||||
* ===================================
|
||||
*
|
||||
* Caller thread JobQueue worker thread
|
||||
* ------------- ----------------------
|
||||
* postCoroTask(f)
|
||||
* |
|
||||
* +-- reserve a jobCounter_ slot (reject if JQ shutting down)
|
||||
* +-- ++nSuspend_ (lazy start counts as suspended)
|
||||
* +-- make_shared<CoroTaskRunner>
|
||||
* +-- init(f)
|
||||
* | +-- store lambda on heap (FuncStore)
|
||||
* | +-- task_ = f(shared_from_this())
|
||||
* | [coroutine created, suspended at initial_suspend]
|
||||
* +-- post()
|
||||
* | +-- ++runCount_
|
||||
* | +-- addJob(type_, [resume]{})
|
||||
* | resume()
|
||||
* | |
|
||||
* | +-- --nSuspend_
|
||||
* | +-- swap in LocalValues
|
||||
* | +-- task_.handle().resume()
|
||||
* | | [coroutine body runs]
|
||||
* | | ...
|
||||
* | | co_await suspend()
|
||||
* | | +-- ++nSuspend_
|
||||
* | | [coroutine suspends]
|
||||
* | +-- swap out LocalValues
|
||||
* | +-- --runCount_
|
||||
* | +-- cv_.notify_all()
|
||||
* |
|
||||
* post() <-- called externally or by yieldAndPost()
|
||||
* +-- ++runCount_
|
||||
* +-- addJob(type_, [resume]{})
|
||||
* resume()
|
||||
* |
|
||||
* +-- [coroutine body continues]
|
||||
* +-- co_return
|
||||
* +-- --runCount_
|
||||
* +-- cv_.notify_all()
|
||||
* join()
|
||||
* +-- cv_.wait([]{runCount_ == 0})
|
||||
* +-- [done]
|
||||
*
|
||||
* Usage Examples
|
||||
* ==============
|
||||
*
|
||||
* 1. Fire-and-forget coroutine (most common pattern):
|
||||
*
|
||||
* jq.postCoroTask(JtClient, "MyWork",
|
||||
* [](auto runner) -> CoroTask<void> {
|
||||
* doSomeWork();
|
||||
* co_await runner->suspend(); // yield to other jobs
|
||||
* doMoreWork();
|
||||
* co_return;
|
||||
* });
|
||||
*
|
||||
* 2. Manually controlling suspend / resume (external trigger):
|
||||
*
|
||||
* auto runner = jq.postCoroTask(JtClient, "ExtTrigger",
|
||||
* [&result](auto runner) -> CoroTask<void> {
|
||||
* startAsyncOperation(callback);
|
||||
* co_await runner->suspend();
|
||||
* // callback called runner->post() to get here
|
||||
* result = collectResult();
|
||||
* co_return;
|
||||
* });
|
||||
* // ... later, from the callback:
|
||||
* runner->post(); // reschedule the coroutine on the JobQueue
|
||||
*
|
||||
* 3. Using yieldAndPost() for automatic suspend + repost:
|
||||
*
|
||||
* jq.postCoroTask(JtClient, "AutoRepost",
|
||||
* [](auto runner) -> CoroTask<void> {
|
||||
* step1();
|
||||
* co_await runner->yieldAndPost(); // yield + auto-repost
|
||||
* step2();
|
||||
* co_await runner->yieldAndPost();
|
||||
* step3();
|
||||
* co_return;
|
||||
* });
|
||||
*
|
||||
* 4. Checking shutdown after co_await (cooperative cancellation):
|
||||
*
|
||||
* jq.postCoroTask(JtClient, "Cancellable",
|
||||
* [&jq](auto runner) -> CoroTask<void> {
|
||||
* while (moreWork()) {
|
||||
* co_await runner->yieldAndPost();
|
||||
* if (jq.isStopping())
|
||||
* co_return; // bail out cleanly
|
||||
* processNextItem();
|
||||
* }
|
||||
* co_return;
|
||||
* });
|
||||
*
|
||||
* Caveats / Pitfalls
|
||||
* ==================
|
||||
*
|
||||
* BUG-RISK: Calling suspend() without a matching post()/resume().
|
||||
* After co_await runner->suspend(), the coroutine is parked and
|
||||
* nSuspend_ is incremented. If nothing ever calls post() or
|
||||
* resume(), the coroutine is leaked and JobQueue::stop() will
|
||||
* hang forever waiting for nSuspend_ to reach zero.
|
||||
*
|
||||
* BUG-RISK: Calling post() on an already-running coroutine.
|
||||
* post() schedules a resume() job. If the coroutine has not
|
||||
* actually suspended yet (no co_await executed), the resume job
|
||||
* will try to call handle().resume() while the coroutine is still
|
||||
* running on another thread. This is UB. The mutex_ prevents
|
||||
* data corruption but the logic is wrong — always co_await
|
||||
* suspend() before calling post(). (The test incorrect_order()
|
||||
* shows this works only because mutex_ serializes the calls.)
|
||||
*
|
||||
* BUG-RISK: Dropping the shared_ptr<CoroTaskRunner> before join().
|
||||
* The CoroTaskRunner destructor asserts that finished_ is true
|
||||
* (the coroutine completed). If you let the last shared_ptr die
|
||||
* while the coroutine is still running or suspended, you get an
|
||||
* assertion failure in debug and UB in release. Always call
|
||||
* join() or expectEarlyExit() first.
|
||||
*
|
||||
* BUG-RISK: Lambda captures outliving the coroutine frame.
|
||||
* The lambda passed to postCoroTask is heap-allocated (FuncStore)
|
||||
* to prevent dangling. But objects captured by pointer still need
|
||||
* their own lifetime management. If you capture a raw pointer to
|
||||
* a stack variable, and the stack frame exits before the coroutine
|
||||
* finishes, the pointer dangles. Use shared_ptr or ensure the
|
||||
* pointed-to object outlives the coroutine.
|
||||
*
|
||||
* BUG-RISK: Forgetting co_return in a void coroutine.
|
||||
* If the coroutine body falls off the end without co_return,
|
||||
* the compiler may silently treat it as co_return (per standard),
|
||||
* but some compilers warn. Always write explicit co_return.
|
||||
*
|
||||
* LIMITATION: CoroTaskRunner only supports CoroTask<void>.
|
||||
* The task_ member is CoroTask<void>. To return values from
|
||||
* the top-level coroutine, write through a captured pointer
|
||||
* (as the tests demonstrate), or co_await inner CoroTask<T>
|
||||
* coroutines that return values.
|
||||
*
|
||||
* LIMITATION: One coroutine per CoroTaskRunner.
|
||||
* init() must be called exactly once. You cannot reuse a
|
||||
* CoroTaskRunner to run a second coroutine. Create a new one
|
||||
* via postCoroTask() instead.
|
||||
*
|
||||
* LIMITATION: No timeout on join().
|
||||
* join() blocks indefinitely. If the coroutine is suspended
|
||||
* and never posted, join() will deadlock. Use timed waits
|
||||
* on the gate pattern (condition_variable + wait_for) in tests.
|
||||
*/
|
||||
class Coro : public std::enable_shared_from_this<Coro>
|
||||
class CoroTaskRunner : public std::enable_shared_from_this<CoroTaskRunner>
|
||||
{
|
||||
private:
|
||||
// Per-coroutine thread-local storage. Swapped in before resume()
|
||||
// and swapped out after, so each coroutine sees its own LocalValue
|
||||
// state regardless of which worker thread executes it.
|
||||
detail::LocalValues lvs_;
|
||||
|
||||
// Back-reference to the owning JobQueue. Used to post jobs,
|
||||
// increment/decrement nSuspend_, and acquire jq_.mutex_.
|
||||
JobQueue& jq_;
|
||||
|
||||
// Job type passed to addJob() when posting this coroutine.
|
||||
JobType type_;
|
||||
|
||||
// Human-readable name for this coroutine job (for logging).
|
||||
std::string name_;
|
||||
bool running_{false};
|
||||
|
||||
// Number of in-flight resume operations (pending + active).
|
||||
// Incremented by post(), decremented when resume() finishes.
|
||||
// Guarded by mutexRun_. join() blocks until this reaches 0.
|
||||
//
|
||||
// A counter (not a bool) is needed because post() can be called
|
||||
// from within the coroutine body (e.g. via yieldAndPost()),
|
||||
// enqueuing a second resume while the first is still running.
|
||||
// A bool would be clobbered: R2.post() sets true, then R1's
|
||||
// cleanup sets false — losing the fact that R2 is still pending.
|
||||
int runCount_ = 0;
|
||||
|
||||
// Serializes all coroutine resume() calls, preventing concurrent
|
||||
// execution of the coroutine body on multiple threads. Handles the
|
||||
// race where post() enqueues a resume before the coroutine has
|
||||
// actually suspended (post-before-suspend pattern).
|
||||
std::mutex mutex_;
|
||||
|
||||
// Guards runCount_. Used with cv_ for join() to wait
|
||||
// until all pending/active resume operations complete.
|
||||
std::mutex mutexRun_;
|
||||
|
||||
// Notified when runCount_ reaches zero, allowing
|
||||
// join() waiters to wake up.
|
||||
std::condition_variable cv_;
|
||||
boost::coroutines2::coroutine<void>::push_type* yield_{};
|
||||
boost::coroutines2::coroutine<void>::pull_type coro_;
|
||||
#ifndef NDEBUG
|
||||
|
||||
// The coroutine handle wrapper. Owns the coroutine frame.
|
||||
// Set by init(). Reset to empty in resume() upon coroutine
|
||||
// completion (to break the shared_ptr cycle) or in
|
||||
// expectEarlyExit() on early termination.
|
||||
CoroTask<void> task_;
|
||||
|
||||
/**
|
||||
* Type-erased base for heap-stored callables.
|
||||
* Prevents the coroutine lambda from being destroyed before
|
||||
* the coroutine frame is done with it.
|
||||
*
|
||||
* @see FuncStore
|
||||
*/
|
||||
struct FuncBase
|
||||
{
|
||||
virtual ~FuncBase() = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Concrete type-erased storage for a callable of type F.
|
||||
* The coroutine frame stores a reference to the lambda's implicit
|
||||
* object parameter. If the lambda is a temporary, that reference
|
||||
* dangles after the call returns. FuncStore keeps it alive on
|
||||
* the heap for the lifetime of the CoroTaskRunner.
|
||||
*/
|
||||
template <class F>
|
||||
struct FuncStore : FuncBase
|
||||
{
|
||||
F func; // The stored callable (coroutine lambda).
|
||||
explicit FuncStore(F&& f) : func(std::move(f))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
// Heap-allocated callable storage. Set by init(), ensures the
|
||||
// lambda outlives the coroutine frame that references it.
|
||||
std::unique_ptr<FuncBase> storedFunc_;
|
||||
|
||||
// True once the coroutine has completed or expectEarlyExit() was
|
||||
// called. Asserted in the destructor (debug) to catch leaked
|
||||
// runners. Available in all builds to guard expectEarlyExit()
|
||||
// against double-decrementing nSuspend_.
|
||||
bool finished_ = false;
|
||||
#endif
|
||||
|
||||
public:
|
||||
template <class F>
|
||||
Coro(CoroCreateT, JobQueue&, JobType, std::string, F&&);
|
||||
|
||||
// Not copy-constructible or assignable
|
||||
Coro(Coro const&) = delete;
|
||||
Coro&
|
||||
operator=(Coro const&) = delete;
|
||||
|
||||
~Coro();
|
||||
/**
|
||||
* Tag type for private construction. Prevents external code
|
||||
* from constructing CoroTaskRunner directly. Use postCoroTask().
|
||||
*/
|
||||
struct CreateT
|
||||
{
|
||||
explicit CreateT() = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Suspend coroutine execution.
|
||||
* Effects:
|
||||
* The coroutine's stack is saved.
|
||||
* The associated Job thread is released.
|
||||
* Note:
|
||||
* The associated Job function returns.
|
||||
* Undefined behavior if called consecutively without a corresponding
|
||||
* post.
|
||||
* Construct a CoroTaskRunner. Private by convention (CreateT tag).
|
||||
*
|
||||
* @param jq The JobQueue this coroutine will run on
|
||||
* @param type Job type for scheduling priority
|
||||
* @param name Human-readable name for logging
|
||||
*/
|
||||
CoroTaskRunner(CreateT, JobQueue&, JobType, std::string);
|
||||
|
||||
CoroTaskRunner(CoroTaskRunner const&) = delete;
|
||||
CoroTaskRunner&
|
||||
operator=(CoroTaskRunner const&) = delete;
|
||||
|
||||
/**
|
||||
* Destructor. Asserts (debug) that the coroutine has finished
|
||||
* or expectEarlyExit() was called.
|
||||
*/
|
||||
~CoroTaskRunner();
|
||||
|
||||
/**
|
||||
* Initialize with a coroutine-returning callable.
|
||||
* Must be called exactly once, after the object is managed by
|
||||
* shared_ptr (because init uses shared_from_this internally).
|
||||
* This is handled automatically by postCoroTask().
|
||||
*
|
||||
* @param f Callable: CoroTask<void>(shared_ptr<CoroTaskRunner>)
|
||||
*/
|
||||
template <class F>
|
||||
void
|
||||
init(F&& f);
|
||||
|
||||
/**
|
||||
* Increment the JobQueue's suspended-coroutine count (nSuspend_).
|
||||
* Called when the coroutine is about to suspend. Every call
|
||||
* must be balanced by a corresponding decrement (via resume()
|
||||
* or onUndoSuspend()), or JobQueue::stop() will hang.
|
||||
*/
|
||||
void
|
||||
yield() const;
|
||||
onSuspend();
|
||||
|
||||
/**
|
||||
* Schedule coroutine execution.
|
||||
* Effects:
|
||||
* Returns immediately.
|
||||
* A new job is scheduled to resume the execution of the coroutine.
|
||||
* When the job runs, the coroutine's stack is restored and execution
|
||||
* continues at the beginning of coroutine function or the
|
||||
* statement after the previous call to yield. Undefined behavior if
|
||||
* called after the coroutine has completed with a return (as opposed to
|
||||
* a yield()). Undefined behavior if post() or resume() called
|
||||
* consecutively without a corresponding yield.
|
||||
* Decrement nSuspend_ without resuming.
|
||||
* Used to undo onSuspend() when a scheduled post() fails
|
||||
* (e.g. JobQueue is stopping).
|
||||
*/
|
||||
void
|
||||
onUndoSuspend();
|
||||
|
||||
/**
|
||||
* Suspend the coroutine.
|
||||
* The awaiter's await_suspend() increments nSuspend_ before the
|
||||
* coroutine actually suspends. The caller must later call post()
|
||||
* or resume() to continue execution.
|
||||
*
|
||||
* @return true if the Coro's job is added to the JobQueue.
|
||||
* @return An awaiter for use with `co_await runner->suspend()`
|
||||
*/
|
||||
auto
|
||||
suspend();
|
||||
|
||||
/**
|
||||
* Suspend the coroutine and immediately repost it on the
|
||||
* JobQueue. Combines suspend() + post() atomically inside
|
||||
* await_suspend, so there is no window where an external
|
||||
* event could race between the two.
|
||||
*
|
||||
* Equivalent to JobQueueAwaiter but defined as an inline
|
||||
* awaiter returned from a member function. This avoids a
|
||||
* GCC-12 coroutine codegen bug where an external awaiter
|
||||
* struct (JobQueueAwaiter) used at multiple co_await points
|
||||
* corrupts the coroutine state machine's resume index,
|
||||
* causing the coroutine to hang on the third resumption.
|
||||
*
|
||||
* @return An awaiter for use with `co_await runner->yieldAndPost()`
|
||||
*/
|
||||
auto
|
||||
yieldAndPost();
|
||||
|
||||
/**
|
||||
* Schedule coroutine resumption as a job on the JobQueue.
|
||||
* Captures shared_from_this() to prevent this runner from being
|
||||
* destroyed while the job is queued.
|
||||
*
|
||||
* @return true if the job was accepted; false if the JobQueue
|
||||
* is stopping (caller must handle cleanup)
|
||||
*/
|
||||
bool
|
||||
post();
|
||||
|
||||
/**
|
||||
* Resume coroutine execution.
|
||||
* Effects:
|
||||
* The coroutine continues execution from where it last left off
|
||||
* using this same thread.
|
||||
* If the coroutine has already completed, returns immediately
|
||||
* (handles the documented post-before-yield race condition).
|
||||
* Undefined behavior if resume() or post() called consecutively
|
||||
* without a corresponding yield.
|
||||
* Resume the coroutine on the current thread.
|
||||
* Decrements nSuspend_, swaps in LocalValues, resumes the
|
||||
* coroutine handle, swaps out LocalValues, and notifies join()
|
||||
* waiters. Lock ordering (sequential, non-overlapping):
|
||||
* jq_.mutex_ -> mutex_ -> mutexRun_.
|
||||
*
|
||||
* @pre post() must have been called before resume(). Direct
|
||||
* calls without a prior post() will corrupt runCount_
|
||||
* and break join().
|
||||
*/
|
||||
void
|
||||
resume();
|
||||
|
||||
/**
|
||||
* Returns true if the Coro is still runnable (has not returned).
|
||||
* @return true if the coroutine has not yet run to completion
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
bool
|
||||
runnable() const;
|
||||
|
||||
/**
|
||||
* Once called, the Coro allows early exit without an assert.
|
||||
* Handle early termination when the coroutine never ran.
|
||||
* Decrements nSuspend_ and destroys the coroutine frame to
|
||||
* break the shared_ptr cycle (frame -> lambda -> runner -> frame).
|
||||
* Called by postCoroTask() when post() fails.
|
||||
*/
|
||||
void
|
||||
expectEarlyExit();
|
||||
|
||||
/**
|
||||
* Waits until coroutine returns from the user function.
|
||||
* Block until all pending/active resume operations complete.
|
||||
* Uses cv_ + mutexRun_ to wait until runCount_ reaches 0.
|
||||
* Warning: deadlocks if the coroutine is suspended and never posted.
|
||||
*/
|
||||
void
|
||||
join();
|
||||
@@ -184,18 +493,18 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a coroutine and adds a job to the queue which will run it.
|
||||
* Creates a C++20 coroutine and adds a job to the queue to run it.
|
||||
*
|
||||
* @param t The type of job.
|
||||
* @param name Name of the job.
|
||||
* @param f Has a signature of void(std::shared_ptr<Coro>). Called when the
|
||||
* job executes.
|
||||
* @param f Callable with signature
|
||||
* CoroTask<void>(std::shared_ptr<CoroTaskRunner>).
|
||||
*
|
||||
* @return shared_ptr to posted Coro. nullptr if post was not successful.
|
||||
* @return shared_ptr to posted CoroTaskRunner. nullptr if not successful.
|
||||
*/
|
||||
template <class F>
|
||||
std::shared_ptr<Coro>
|
||||
postCoro(JobType t, std::string const& name, F&& f);
|
||||
std::shared_ptr<CoroTaskRunner>
|
||||
postCoroTask(JobType t, std::string const& name, F&& f);
|
||||
|
||||
/**
|
||||
* Jobs waiting at this priority.
|
||||
@@ -256,8 +565,6 @@ public:
|
||||
isStopped() const;
|
||||
|
||||
private:
|
||||
friend class Coro;
|
||||
|
||||
using JobDataMap = std::map<JobType, JobTypeData>;
|
||||
|
||||
beast::Journal journal_;
|
||||
@@ -358,88 +665,89 @@ private:
|
||||
getJobLimit(JobType type);
|
||||
};
|
||||
|
||||
/*
|
||||
An RPC command is received and is handled via ServerHandler(HTTP) or
|
||||
Handler(websocket), depending on the connection type. The handler then calls
|
||||
the JobQueue::postCoro() method to create a coroutine and run it at a later
|
||||
point. This frees up the handler thread and allows it to continue handling
|
||||
other requests while the RPC command completes its work asynchronously.
|
||||
|
||||
postCoro() creates a Coro object. When the Coro ctor is called, and its
|
||||
coro_ member is initialized (a boost::coroutines::pull_type), execution
|
||||
automatically passes to the coroutine, which we don't want at this point,
|
||||
since we are still in the handler thread context. It's important to note
|
||||
here that construction of a boost pull_type automatically passes execution to
|
||||
the coroutine. A pull_type object automatically generates a push_type that is
|
||||
passed as a parameter (do_yield) in the signature of the function the
|
||||
pull_type was created with. This function is immediately called during coro_
|
||||
construction and within it, Coro::yield_ is assigned the push_type
|
||||
parameter (do_yield) address and called (yield()) so we can return execution
|
||||
back to the caller's stack.
|
||||
|
||||
postCoro() then calls Coro::post(), which schedules a job on the job
|
||||
queue to continue execution of the coroutine in a JobQueue worker thread at
|
||||
some later time. When the job runs, we lock on the Coro::mutex_ and call
|
||||
coro_ which continues where we had left off. Since we the last thing we did
|
||||
in coro_ was call yield(), the next thing we continue with is calling the
|
||||
function param f, that was passed into Coro ctor. It is within this
|
||||
function body that the caller specifies what he would like to do while
|
||||
running in the coroutine and allow them to suspend and resume execution.
|
||||
A task that relies on other events to complete, such as path finding, calls
|
||||
Coro::yield() to suspend its execution while waiting on those events to
|
||||
complete and continue when signaled via the Coro::post() method.
|
||||
|
||||
There is a potential race condition that exists here where post() can get
|
||||
called before yield() after f is called. Technically the problem only occurs
|
||||
if the job that post() scheduled is executed before yield() is called.
|
||||
If the post() job were to be executed before yield(), undefined behavior
|
||||
would occur. The lock ensures that coro_ is not called again until we exit
|
||||
the coroutine. At which point a scheduled resume() job waiting on the lock
|
||||
would gain entry. resume() checks if the coroutine has already completed
|
||||
(coro_ converts to false) and, if so, skips invoking operator() since
|
||||
calling operator() on a completed boost::coroutine2 pull_type is undefined
|
||||
behavior.
|
||||
|
||||
The race condition occurs as follows:
|
||||
|
||||
1- The coroutine is running.
|
||||
2- The coroutine is about to suspend, but before it can do so, it must
|
||||
arrange for some event to wake it up.
|
||||
3- The coroutine arranges for some event to wake it up.
|
||||
4- Before the coroutine can suspend, that event occurs and the
|
||||
resumption of the coroutine is scheduled on the job queue. 5- Again, before
|
||||
the coroutine can suspend, the resumption of the coroutine is dispatched. 6-
|
||||
Again, before the coroutine can suspend, the resumption code runs the
|
||||
coroutine.
|
||||
The coroutine is now running in two threads.
|
||||
|
||||
The lock prevents this from happening as step 6 will block until the
|
||||
lock is released which only happens after the coroutine completes.
|
||||
*/
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
#include <xrpl/core/Coro.ipp> // IWYU pragma: keep
|
||||
#include <xrpl/core/CoroTaskRunner.ipp> // IWYU pragma: keep
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// postCoroTask — entry point for launching a C++20 coroutine on the JobQueue.
|
||||
//
|
||||
// Control Flow
|
||||
// ============
|
||||
//
|
||||
// postCoroTask(t, name, f)
|
||||
// |
|
||||
// +-- 1. Reserve a jobCounter_ slot — reject if JQ shutting down
|
||||
// |
|
||||
// +-- 2. ++nSuspend_ (the coroutine uses lazy-start, so it is
|
||||
// | "suspended" from the JQ's perspective before its first resume.
|
||||
// | This keeps the JQ shutdown logic correct — it waits for
|
||||
// | nSuspend_ to reach 0).
|
||||
// |
|
||||
// +-- 3. Create CoroTaskRunner (shared_ptr, ref-counted)
|
||||
// |
|
||||
// +-- 4. runner->init(f)
|
||||
// | +-- Heap-allocate the lambda (FuncStore) to prevent
|
||||
// | | dangling captures in the coroutine frame
|
||||
// | +-- task_ = f(shared_from_this())
|
||||
// | [coroutine created but NOT started — lazy initial_suspend]
|
||||
// |
|
||||
// +-- 5. runner->post()
|
||||
// | +-- addJob(type_, [resume]{}) → resume on worker thread
|
||||
// | +-- failure (JQ stopping):
|
||||
// | +-- runner->expectEarlyExit()
|
||||
// | | --nSuspend_, destroy coroutine frame
|
||||
// | +-- return nullptr
|
||||
// |
|
||||
// +-- 6. Release the jobCounter_ slot (on return)
|
||||
//
|
||||
// Why reserve a jobCounter_ slot?
|
||||
// ===============================
|
||||
// JobQueue::stop() joins jobCounter_ before it acquires mutex_ and asserts
|
||||
// nSuspend_ == 0. Without a reservation, stop() could run to completion in
|
||||
// the window between the ++nSuspend_ in step 2 and the balancing post() or
|
||||
// expectEarlyExit() in step 5, tripping that assert. Holding a slot blocks
|
||||
// stop()'s join() for the whole function, closing the window. wrap() also
|
||||
// returns nullopt once the counter is joined, so it doubles as the shutdown
|
||||
// check — a plain stopping_ read cannot do this, because the read and the
|
||||
// ++nSuspend_ are not a single atomic step.
|
||||
//
|
||||
// Why async post() instead of synchronous resume()?
|
||||
// ==================================================
|
||||
// The initial dispatch MUST use async post() so the coroutine body runs on
|
||||
// a JobQueue worker thread, not the caller's thread. resume() swaps the
|
||||
// caller's thread-local LocalValues with the coroutine's private copy.
|
||||
// If the coroutine mutates LocalValues (e.g. thread_specific_storage test),
|
||||
// those mutations bleed back into the caller's thread-local state after the
|
||||
// swap-out, corrupting subsequent tests that share the same thread pool.
|
||||
// Async post() avoids this by running the coroutine on a worker thread whose
|
||||
// LocalValues are managed by the thread pool, not by the caller.
|
||||
//
|
||||
template <class F>
|
||||
std::shared_ptr<JobQueue::Coro>
|
||||
JobQueue::postCoro(JobType t, std::string const& name, F&& f)
|
||||
std::shared_ptr<JobQueue::CoroTaskRunner>
|
||||
JobQueue::postCoroTask(JobType t, std::string const& name, F&& f)
|
||||
{
|
||||
/* First param is a detail type to make construction private.
|
||||
Last param is the function the coroutine runs. Signature of
|
||||
void(std::shared_ptr<Coro>).
|
||||
*/
|
||||
auto coro = std::make_shared<Coro>(CoroCreateT{}, *this, t, name, std::forward<F>(f));
|
||||
if (!coro->post())
|
||||
// Held until this function returns. Null once jobCounter_ is joined,
|
||||
// which is how a shutting-down JobQueue rejects new coroutines.
|
||||
auto const shutdownGuard = jobCounter_.wrap([]() {});
|
||||
if (!shutdownGuard)
|
||||
return nullptr;
|
||||
|
||||
// Account for the initial suspension (CoroTask uses lazy start).
|
||||
{
|
||||
// The Coro was not successfully posted. Disable it so it's destructor
|
||||
// can run with no negative side effects. Then destroy it.
|
||||
coro->expectEarlyExit();
|
||||
coro.reset();
|
||||
std::scoped_lock const lock(mutex_);
|
||||
++nSuspend_;
|
||||
}
|
||||
return coro;
|
||||
|
||||
auto runner = std::make_shared<CoroTaskRunner>(CoroTaskRunner::CreateT{}, *this, t, name);
|
||||
runner->init(std::forward<F>(f));
|
||||
if (!runner->post())
|
||||
{
|
||||
runner->expectEarlyExit();
|
||||
runner.reset();
|
||||
}
|
||||
return runner;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
212
include/xrpl/core/JobQueueAwaiter.h
Normal file
212
include/xrpl/core/JobQueueAwaiter.h
Normal file
@@ -0,0 +1,212 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/core/JobQueue.h>
|
||||
|
||||
#include <coroutine>
|
||||
#include <memory>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Awaiter that suspends and immediately reschedules on the JobQueue.
|
||||
* Equivalent to calling yield() followed by post() in the old Coro API.
|
||||
*
|
||||
* Usage:
|
||||
* co_await JobQueueAwaiter{runner};
|
||||
*
|
||||
* What it waits for: The coroutine is re-queued as a job and resumes
|
||||
* when a worker thread picks it up.
|
||||
*
|
||||
* Which thread resumes: A JobQueue worker thread.
|
||||
*
|
||||
* What await_resume() returns: void.
|
||||
*
|
||||
* Dependency Diagram
|
||||
* ==================
|
||||
*
|
||||
* JobQueueAwaiter
|
||||
* +----------------------------------------------+
|
||||
* | + runner : shared_ptr<CoroTaskRunner> |
|
||||
* +----------------------------------------------+
|
||||
* | + await_ready() -> false (always suspend) |
|
||||
* | + await_suspend() -> bool (suspend or cancel) |
|
||||
* | + await_resume() -> void |
|
||||
* +----------------------------------------------+
|
||||
* | |
|
||||
* | uses | uses
|
||||
* v v
|
||||
* CoroTaskRunner JobQueue
|
||||
* .onSuspend() (via runner->post() -> addJob)
|
||||
* .onUndoSuspend()
|
||||
* .post()
|
||||
*
|
||||
* Control Flow (await_suspend)
|
||||
* ============================
|
||||
*
|
||||
* co_await JobQueueAwaiter{runner}
|
||||
* |
|
||||
* +-- await_ready() -> false
|
||||
* +-- await_suspend(handle)
|
||||
* |
|
||||
* +-- runner->onSuspend() // ++nSuspend_
|
||||
* +-- runner->post() // addJob to JobQueue
|
||||
* | |
|
||||
* | +-- success? return noop_coroutine()
|
||||
* | | // coroutine stays suspended;
|
||||
* | | // worker thread will call resume()
|
||||
* | +-- failure? (JQ stopping)
|
||||
* | +-- runner->onUndoSuspend() // --nSuspend_
|
||||
* | +-- return handle // symmetric transfer back
|
||||
* | // coroutine continues immediately
|
||||
* | // so it can clean up and co_return
|
||||
*
|
||||
* DEPRECATED — prefer `co_await runner->yieldAndPost()`
|
||||
* =====================================================
|
||||
*
|
||||
* GCC-12 has a coroutine codegen bug where using this external awaiter
|
||||
* struct at multiple co_await points in the same coroutine corrupts the
|
||||
* state machine's resume index. After the second co_await, the third
|
||||
* resumption enters handle().resume() but never reaches await_resume()
|
||||
* or any subsequent user code — the coroutine hangs indefinitely.
|
||||
*
|
||||
* The fix is `co_await runner->yieldAndPost()`, which defines the
|
||||
* awaiter as an inline struct inside a CoroTaskRunner member function.
|
||||
* GCC-12 handles inline awaiters correctly at multiple co_await points.
|
||||
*
|
||||
* This struct is retained for single-use scenarios and documentation
|
||||
* purposes. For any code that may use co_await in a loop or at
|
||||
* multiple points, always use `runner->yieldAndPost()`.
|
||||
*
|
||||
* Usage Examples
|
||||
* ==============
|
||||
*
|
||||
* 1. Yield and auto-repost (preferred — works on all compilers):
|
||||
*
|
||||
* CoroTask<void> handler(auto runner) {
|
||||
* doPartA();
|
||||
* co_await runner->yieldAndPost(); // yield + repost
|
||||
* doPartB(); // runs on a worker thread
|
||||
* co_return;
|
||||
* }
|
||||
*
|
||||
* 2. Multiple yield points in a loop:
|
||||
*
|
||||
* CoroTask<void> batchProcessor(auto runner) {
|
||||
* for (auto& item : items) {
|
||||
* process(item);
|
||||
* co_await runner->yieldAndPost(); // let other jobs run
|
||||
* }
|
||||
* co_return;
|
||||
* }
|
||||
*
|
||||
* 3. Graceful shutdown — checking after resume:
|
||||
*
|
||||
* CoroTask<void> longTask(auto runner, JobQueue& jq) {
|
||||
* while (hasWork()) {
|
||||
* co_await runner->yieldAndPost();
|
||||
* // If JQ is stopping, await_suspend resumes the coroutine
|
||||
* // immediately without re-queuing. Always check
|
||||
* // isStopping() to decide whether to proceed:
|
||||
* if (jq.isStopping())
|
||||
* co_return;
|
||||
* doNextChunk();
|
||||
* }
|
||||
* co_return;
|
||||
* }
|
||||
*
|
||||
* Caveats / Pitfalls
|
||||
* ==================
|
||||
*
|
||||
* BUG-RISK: Using a stale or null runner.
|
||||
* The runner shared_ptr must be valid and point to the CoroTaskRunner
|
||||
* that owns the coroutine currently executing. Passing a runner from
|
||||
* a different coroutine, or a default-constructed shared_ptr, is UB.
|
||||
*
|
||||
* BUG-RISK: Assuming resume happens on the same thread.
|
||||
* After co_await, the coroutine resumes on whatever worker thread
|
||||
* picks up the job. Do not rely on thread-local state unless it is
|
||||
* managed through LocalValue (which CoroTaskRunner automatically
|
||||
* swaps in/out).
|
||||
*
|
||||
* BUG-RISK: Ignoring the shutdown path.
|
||||
* When the JobQueue is stopping, post() fails and await_suspend()
|
||||
* resumes the coroutine immediately (symmetric transfer back to h).
|
||||
* The coroutine body continues on the same thread. If your code
|
||||
* after co_await assumes it was re-queued and is running on a worker
|
||||
* thread, that assumption breaks during shutdown. Always handle the
|
||||
* "JQ is stopping" case, either by checking jq.isStopping() or by
|
||||
* letting the coroutine fall through to co_return naturally.
|
||||
*
|
||||
* DIFFERENCE from runner->suspend() + runner->post():
|
||||
* Both JobQueueAwaiter and yieldAndPost() combine suspend + post
|
||||
* in one atomic operation. With the manual suspend()/post() pattern,
|
||||
* there is a window between the two calls where an external event
|
||||
* could race. The atomic awaiters remove that window — onSuspend()
|
||||
* and post() happen within the same await_suspend() call while the
|
||||
* coroutine is guaranteed to be suspended. Use yieldAndPost() unless
|
||||
* you need an external party to decide *when* to call post().
|
||||
*/
|
||||
struct JobQueueAwaiter
|
||||
{
|
||||
// The CoroTaskRunner that owns the currently executing coroutine.
|
||||
std::shared_ptr<JobQueue::CoroTaskRunner> runner;
|
||||
|
||||
/**
|
||||
* Always returns false so the coroutine suspends.
|
||||
*/
|
||||
// The C++ coroutine protocol mandates these awaiter names and
|
||||
// instance-callable methods, which conflict with the project
|
||||
// naming/static conventions.
|
||||
// NOLINTBEGIN(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
[[nodiscard]] bool
|
||||
await_ready() const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment nSuspend (equivalent to yield()) and schedule resume
|
||||
* on the JobQueue (equivalent to post()). If the JobQueue is
|
||||
* stopping, undoes the suspend count and transfers back to the
|
||||
* coroutine so it can clean up and co_return.
|
||||
*
|
||||
* Returns a coroutine_handle<> (symmetric transfer) instead of
|
||||
* bool to work around a GCC-12 codegen bug where bool-returning
|
||||
* await_suspend leaves the coroutine in an invalid state —
|
||||
* neither properly suspended nor resumed — causing a hang.
|
||||
*
|
||||
* WARNING: GCC-12 has an additional codegen bug where using this
|
||||
* external awaiter struct at multiple co_await points in the same
|
||||
* coroutine corrupts the state machine's resume index, causing the
|
||||
* coroutine to hang on the third resumption. Prefer
|
||||
* `co_await runner->yieldAndPost()` which uses an inline awaiter
|
||||
* that GCC-12 handles correctly.
|
||||
*
|
||||
* @return noop_coroutine() to stay suspended (job posted);
|
||||
* the caller's handle to resume immediately (JQ stopping)
|
||||
*/
|
||||
std::coroutine_handle<>
|
||||
await_suspend(std::coroutine_handle<> h)
|
||||
{
|
||||
XRPL_ASSERT(runner, "xrpl::JobQueueAwaiter::await_suspend : runner is valid");
|
||||
runner->onSuspend();
|
||||
if (!runner->post())
|
||||
{
|
||||
// JobQueue is stopping. Undo the suspend count and
|
||||
// transfer back to the coroutine so it can clean up
|
||||
// and co_return.
|
||||
runner->onUndoSuspend();
|
||||
return h;
|
||||
}
|
||||
return std::noop_coroutine();
|
||||
}
|
||||
|
||||
void
|
||||
await_resume() const noexcept
|
||||
{
|
||||
}
|
||||
// NOLINTEND(readability-identifier-naming, readability-convert-member-functions-to-static)
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -4,10 +4,9 @@
|
||||
#include <xrpl/core/Job.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -44,7 +43,7 @@ public:
|
||||
*/
|
||||
struct Setup
|
||||
{
|
||||
boost::filesystem::path perfLog;
|
||||
std::filesystem::path perfLog;
|
||||
// log_interval is in milliseconds to support faster testing.
|
||||
milliseconds logInterval{seconds(1)};
|
||||
};
|
||||
@@ -149,7 +148,7 @@ public:
|
||||
};
|
||||
|
||||
PerfLog::Setup
|
||||
setupPerfLog(Section const& section, boost::filesystem::path const& configDir);
|
||||
setupPerfLog(Section const& section, std::filesystem::path const& configDir);
|
||||
|
||||
std::unique_ptr<PerfLog>
|
||||
makePerfLog(
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/beast/core/string.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace json {
|
||||
|
||||
class Value;
|
||||
|
||||
using Output = std::function<void(boost::beast::string_view const&)>;
|
||||
using Output = std::function<void(std::string_view)>;
|
||||
|
||||
inline Output
|
||||
stringOutput(std::string& s)
|
||||
{
|
||||
return [&](boost::beast::string_view const& b) { s.append(b.data(), b.size()); };
|
||||
return [&](std::string_view b) { s.append(b.data(), b.size()); };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <xrpl/json/json_forwards.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <string>
|
||||
@@ -72,36 +73,18 @@ operator==(StaticString x, StaticString y)
|
||||
return strcmp(x.cStr(), y.cStr()) == 0;
|
||||
}
|
||||
|
||||
inline bool
|
||||
operator!=(StaticString x, StaticString y)
|
||||
{
|
||||
return !(x == y);
|
||||
}
|
||||
|
||||
inline bool
|
||||
operator==(std::string const& x, StaticString y)
|
||||
{
|
||||
return strcmp(x.c_str(), y.cStr()) == 0;
|
||||
}
|
||||
|
||||
inline bool
|
||||
operator!=(std::string const& x, StaticString y)
|
||||
{
|
||||
return !(x == y);
|
||||
}
|
||||
|
||||
inline bool
|
||||
operator==(StaticString x, std::string const& y)
|
||||
{
|
||||
return y == x;
|
||||
}
|
||||
|
||||
inline bool
|
||||
operator!=(StaticString x, std::string const& y)
|
||||
{
|
||||
return !(y == x);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Represents a <a HREF="http://www.json.org">JSON</a> value.
|
||||
*
|
||||
@@ -489,12 +472,6 @@ toJson(xrpl::Number const& number)
|
||||
bool
|
||||
operator==(Value const&, Value const&);
|
||||
|
||||
inline bool
|
||||
operator!=(Value const& x, Value const& y)
|
||||
{
|
||||
return !(x == y);
|
||||
}
|
||||
|
||||
bool
|
||||
operator<(Value const&, Value const&);
|
||||
|
||||
@@ -548,6 +525,7 @@ public:
|
||||
class ValueIteratorBase
|
||||
{
|
||||
public:
|
||||
using iterator_category = std::bidirectional_iterator_tag;
|
||||
using size_t = unsigned int;
|
||||
using difference_type = int;
|
||||
using SelfType = ValueIteratorBase;
|
||||
@@ -562,12 +540,6 @@ public:
|
||||
return isEqual(other);
|
||||
}
|
||||
|
||||
bool
|
||||
operator!=(SelfType const& other) const
|
||||
{
|
||||
return !isEqual(other);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return either the index or the member name of the referenced value as a
|
||||
* Value.
|
||||
|
||||
@@ -49,12 +49,6 @@ public:
|
||||
bool
|
||||
operator==(const_iterator const& other) const;
|
||||
|
||||
bool
|
||||
operator!=(const_iterator const& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
reference
|
||||
operator*() const;
|
||||
|
||||
|
||||
@@ -59,12 +59,6 @@ private:
|
||||
return lhs.txId_ == rhs.txId_;
|
||||
}
|
||||
|
||||
friend bool
|
||||
operator!=(Key const& lhs, Key const& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
[[nodiscard]] uint256 const&
|
||||
getAccount() const
|
||||
{
|
||||
|
||||
@@ -59,12 +59,6 @@ public:
|
||||
bool
|
||||
operator==(ConstIterator const& other) const;
|
||||
|
||||
bool
|
||||
operator!=(ConstIterator const& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
reference
|
||||
operator*() const;
|
||||
|
||||
|
||||
@@ -35,6 +35,11 @@ enum class SkipEntry : bool { No = false, Yes };
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Whether an expiration check should be inclusive or exclusive.
|
||||
*/
|
||||
enum class ExpiryComparison { Inclusive, Exclusive };
|
||||
|
||||
/**
|
||||
* Determines whether the given expiration time has passed.
|
||||
*
|
||||
@@ -54,11 +59,16 @@ enum class SkipEntry : bool { No = false, Yes };
|
||||
*
|
||||
* @param view The ledger whose parent time is used as the clock.
|
||||
* @param exp The optional expiration time we want to check.
|
||||
* @param comparison Whether the boundary is inclusive (`now >= exp`, the
|
||||
* default) or exclusive (`now > exp`).
|
||||
*
|
||||
* @return `true` if `exp` is in the past; `false` otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
hasExpired(ReadView const& view, std::optional<std::uint32_t> const& exp);
|
||||
hasExpired(
|
||||
ReadView const& view,
|
||||
std::optional<std::uint32_t> const& exp,
|
||||
ExpiryComparison comparison = ExpiryComparison::Inclusive);
|
||||
|
||||
// Note, depth parameter is used to limit the recursion depth
|
||||
[[nodiscard]] bool
|
||||
@@ -68,6 +78,13 @@ isVaultPseudoAccountFrozen(
|
||||
MPTIssue const& mptShare,
|
||||
std::uint8_t depth);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isVaultPseudoAccountFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
SLE const& issuanceSle,
|
||||
std::uint8_t depth);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isLPTokenFrozen(
|
||||
ReadView const& view,
|
||||
@@ -75,6 +92,26 @@ isLPTokenFrozen(
|
||||
Asset const& asset,
|
||||
Asset const& asset2);
|
||||
|
||||
/**
|
||||
* Check whether an AMM LPToken may be transferred between @p from and @p to.
|
||||
*
|
||||
* @p lpTokenIssuer is the issuer of the LPToken being moved. If it is not an
|
||||
* AMM account the token is not an LPToken and the transfer is unconditionally
|
||||
* permitted. Otherwise, for each MPT pool asset of that AMM, canTransfer() must
|
||||
* permit the transfer (which exempts the MPT issuer). Non-MPT pool assets are
|
||||
* always transferable by this check, so it is implicitly gated by
|
||||
* featureMPTokensV2 (MPTs can only be AMM pool assets once V2 is enabled).
|
||||
*
|
||||
* @return tesSUCCESS if permitted, otherwise the canTransfer() failure code
|
||||
* (e.g. tecNO_AUTH) of the first MPT pool asset that disallows it.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
canTransferLPToken(
|
||||
ReadView const& view,
|
||||
AccountID const& from,
|
||||
AccountID const& to,
|
||||
AccountID const& lpTokenIssuer);
|
||||
|
||||
// Return the list of enabled amendments
|
||||
[[nodiscard]] std::set<uint256>
|
||||
getEnabledAmendments(ReadView const& view);
|
||||
|
||||
@@ -85,9 +85,6 @@ public:
|
||||
bool
|
||||
operator==(Iterator const& other) const;
|
||||
|
||||
bool
|
||||
operator!=(Iterator const& other) const;
|
||||
|
||||
// Can throw
|
||||
reference
|
||||
operator*() const;
|
||||
|
||||
@@ -64,13 +64,6 @@ ReadViewFwdRange<ValueType>::Iterator::operator==(Iterator const& other) const
|
||||
return impl_ == other.impl_;
|
||||
}
|
||||
|
||||
template <class ValueType>
|
||||
bool
|
||||
ReadViewFwdRange<ValueType>::Iterator::operator!=(Iterator const& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
template <class ValueType>
|
||||
auto
|
||||
ReadViewFwdRange<ValueType>::Iterator::operator*() const -> reference
|
||||
|
||||
@@ -226,7 +226,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};
|
||||
@@ -294,7 +294,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)};
|
||||
@@ -313,11 +313,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
|
||||
@@ -396,10 +396,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);
|
||||
}();
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/STArray.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
@@ -34,7 +35,7 @@ deleteSLE(ApplyView& view, SLE::ref sleCredential, beast::Journal j);
|
||||
|
||||
// Amendment and parameters checks for sfCredentialIDs field
|
||||
NotTEC
|
||||
checkFields(STTx const& tx, beast::Journal j);
|
||||
checkFields(STTx const& tx, Rules const& rules, beast::Journal j);
|
||||
|
||||
// Accessing the ledger to check if provided credentials are valid. Do not use
|
||||
// in doApply (only in preclaim) since it does not remove expired credentials.
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h> // IWYU pragma: keep
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
@@ -21,6 +22,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
@@ -58,6 +60,42 @@ canApplyToBrokerCover(
|
||||
bool
|
||||
checkLendingProtocolDependencies(Rules const& rules, STTx const& tx);
|
||||
|
||||
/**
|
||||
* The accounts and asset that LoanManage::defaultLoan's fixCleanup3_4_0
|
||||
* freeze/lock exemption applies to.
|
||||
*
|
||||
* `defaultLoan` moves funds from the LoanBroker pseudo-account to the Vault
|
||||
* pseudo-account via `accountSend`. Since neither is the vault asset's
|
||||
* issuer, this is a third-party transfer that transits through the issuer in
|
||||
* two hops (broker -> issuer, issuer -> vault; see
|
||||
* `directSendNoLimitIOU`/`directSendNoLimitMPT`), so the exemption must cover
|
||||
* both the issuer/broker and issuer/vault pairs, not a direct broker/vault
|
||||
* pair. `asset` scopes it further to the vault's own currency/MPT issuance,
|
||||
* so an unrelated one the same accounts happen to hold is still protected.
|
||||
*/
|
||||
struct LoanDefaultFreezeExemptAccounts
|
||||
{
|
||||
AccountID issuer;
|
||||
AccountID broker;
|
||||
AccountID vault;
|
||||
Asset asset;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolves the accounts and asset a LoanManage default transaction is
|
||||
* exempt from freeze/lock for.
|
||||
*
|
||||
* @param view Ledger view used to resolve the Loan -> LoanBroker -> Vault
|
||||
* chain.
|
||||
* @param tx The transaction under invariant review.
|
||||
* @return The exempt accounts and asset if `tx` is a `ttLOAN_MANAGE`
|
||||
* transaction with the `tfLoanDefault` flag set, `fixCleanup3_4_0` is
|
||||
* enabled, and the loan/broker/vault objects it references can all be
|
||||
* resolved; `std::nullopt` otherwise.
|
||||
*/
|
||||
[[nodiscard]] std::optional<LoanDefaultFreezeExemptAccounts>
|
||||
getLoanDefaultFreezeExemptAccounts(ReadView const& view, STTx const& tx);
|
||||
|
||||
static constexpr std::uint32_t kSecondsInYear = 365 * 24 * 60 * 60;
|
||||
|
||||
Number
|
||||
|
||||
@@ -29,6 +29,9 @@ namespace xrpl {
|
||||
[[nodiscard]] bool
|
||||
isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isGlobalFrozen(SLE const& issuanceSle);
|
||||
|
||||
/**
|
||||
* Returns true if @p account's MPToken for @p mptIssue carries the
|
||||
* individual-lock flag (lsfMPTLocked).
|
||||
@@ -40,9 +43,29 @@ isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue);
|
||||
* receive tokens — it combines isIndividualFrozen, isGlobalFrozen, and
|
||||
* isVaultPseudoAccountFrozen into a single complete check.
|
||||
*/
|
||||
|
||||
[[nodiscard]] bool
|
||||
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isIndividualFrozen(SLE const& mptSle);
|
||||
|
||||
/**
|
||||
* Returns true if @p account cannot send or receive tokens of @p mptIssue
|
||||
* because a freeze applies. This is the complete check callers should use
|
||||
* before moving MPT value: it combines @ref isGlobalFrozen (issuance-level
|
||||
* lock), @ref isIndividualFrozen (per-holder lock bit), and the transitive
|
||||
* vault pseudo-account check (if @p mptIssue is a vault share, the underlying
|
||||
* asset is checked, and so on recursively up to @c maxAssetCheckDepth).
|
||||
*
|
||||
* The @c SLE overload takes an already-loaded ltMPTOKEN or ltMPTOKEN_ISSUANCE
|
||||
* ledger entry; for ltMPTOKEN it can skip the per-holder individual-lock lookup.
|
||||
* @ref isAnyFrozen answers the same question for a set of accounts and returns true
|
||||
* if the freeze applies to any of them.
|
||||
*
|
||||
* @param depth Current recursion depth for the vault-share walk. Callers
|
||||
* outside this module should leave it at the default.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isFrozen(
|
||||
ReadView const& view,
|
||||
@@ -50,6 +73,18 @@ isFrozen(
|
||||
MPTIssue const& mptIssue,
|
||||
std::uint8_t depth = 0);
|
||||
|
||||
/**
|
||||
* SLE overload: pass an already-loaded ltMPTOKEN (holder row) or
|
||||
* ltMPTOKEN_ISSUANCE to reuse it for the freeze checks and avoid re-reading
|
||||
* the same object. For an ltMPTOKEN, @p sle is used directly for the
|
||||
* individual-lock check and the issuance is read once for global-freeze and
|
||||
* vault-pseudo-account. For an ltMPTOKEN_ISSUANCE, @p sle is used directly
|
||||
* for global-freeze and vault-pseudo-account, and the caller's holder row is
|
||||
* read for the individual-lock check.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, SLE const& sle, std::uint8_t depth = 0);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isAnyFrozen(
|
||||
ReadView const& view,
|
||||
@@ -261,6 +296,14 @@ checkCreateMPT(
|
||||
xrpl::MPTIssue const& mptIssue,
|
||||
xrpl::AccountID const& holder,
|
||||
SLE::ref sponsorSle,
|
||||
std::uint32_t flags,
|
||||
beast::Journal j);
|
||||
|
||||
TER
|
||||
checkCreateMPT(
|
||||
xrpl::ApplyView& view,
|
||||
xrpl::MPTIssue const& mptIssue,
|
||||
xrpl::AccountID const& holder,
|
||||
beast::Journal j);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class STTx;
|
||||
|
||||
/**
|
||||
* From the perspective of a vault, return the number of shares to give
|
||||
* depositor when they offer a fixed amount of assets. Note, since shares are
|
||||
@@ -52,6 +57,38 @@ enum class TruncateShares : bool { No = false, Yes = true };
|
||||
*/
|
||||
enum class WaiveUnrealizedLoss : bool { No = false, Yes = true };
|
||||
|
||||
/**
|
||||
* Returns the effective total of assets backing outstanding shares for the
|
||||
* purposes of a withdrawal, i.e. sfAssetsTotal, discounted by sfLossUnrealized
|
||||
* unless waived. This is the numerator used by both withdraw conversion
|
||||
* helpers (assetsToSharesWithdraw and sharesToAssetsWithdraw) to compute the
|
||||
* share/asset exchange rate.
|
||||
*
|
||||
* @param vault The vault SLE.
|
||||
* @param waive Whether to waive (i.e. not subtract) the vault's unrealized
|
||||
* loss.
|
||||
*/
|
||||
[[nodiscard]] Number
|
||||
assetsTotalForWithdrawal(SLE::const_ref vault, WaiveUnrealizedLoss waive);
|
||||
|
||||
/**
|
||||
* Returns whether debiting `amount` from `total` — the current value of a
|
||||
* vault's sfAssetsTotal or sfAssetsAvailable field — would canonicalize back
|
||||
* to the exact same STAmount value it started at. This happens when a
|
||||
* genuinely non-zero debit is dust relative to a `total` large enough to
|
||||
* exceed STAmount's significant-digit precision: the shares still move, but
|
||||
* the stored total doesn't change, which otherwise trips the ValidVault
|
||||
* invariant after the fact instead of failing cleanly upfront.
|
||||
*
|
||||
* @param asset The vault's underlying asset, used to canonicalize both sides
|
||||
* the same way the ledger will when the field is stored.
|
||||
* @param total The field's current value.
|
||||
* @param amount The amount to debit. A value of zero always returns false;
|
||||
* that case is rejected separately and unconditionally.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
debitIsNonZeroDust(Asset const& asset, Number const& total, Number const& amount);
|
||||
|
||||
/**
|
||||
* From the perspective of a vault, return the number of shares to demand from
|
||||
* the depositor when they ask to withdraw a fixed amount of assets. Since
|
||||
@@ -123,4 +160,82 @@ isSoleShareholder(ReadView const& view, AccountID const& account, SLE::const_ref
|
||||
[[nodiscard]] VaultVersion
|
||||
getVaultVersion(SLE::const_ref vault);
|
||||
|
||||
/**
|
||||
* Resolves the VaultKind of a vault SLE. Returns VaultKind::ClosedEnded when
|
||||
* sfVaultKind is present and equal to that value; anything else (including an
|
||||
* absent field or an unrecognised value) is treated as VaultKind::OpenEnded.
|
||||
*
|
||||
* @param vault The vault SLE.
|
||||
*/
|
||||
[[nodiscard]] VaultKind
|
||||
getVaultKind(SLE::const_ref vault);
|
||||
|
||||
/**
|
||||
* Reads sfVaultKind from a transaction. An absent field resolves to
|
||||
* VaultKind::OpenEnded (matching the on-ledger default); any unrecognised
|
||||
* value is also treated as VaultKind::OpenEnded, mirroring the SLE overload.
|
||||
* Callers that need to reject out-of-range values (e.g. preflight) should
|
||||
* gate on isValidVaultKind() first.
|
||||
*
|
||||
* @param tx The transaction.
|
||||
*/
|
||||
[[nodiscard]] VaultKind
|
||||
getVaultKind(STTx const& tx);
|
||||
|
||||
/**
|
||||
* Returns true iff sfVaultKind is either absent from @p tx or is present and
|
||||
* equal to a recognised VaultKind enumerator. Intended for use in preflight
|
||||
* to reject malformed transactions before decoding with getVaultKind().
|
||||
*
|
||||
* @param tx The transaction.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isValidVaultKind(STTx const& tx);
|
||||
|
||||
/**
|
||||
* Returns true iff the (SubscriptionDate, RedemptionDate) gap of a
|
||||
* closed-ended vault satisfies
|
||||
* kMinInvestmentPeriod <= (red - sub) < kMaxInvestmentPeriod. The arithmetic
|
||||
* is performed in std::int64_t so that @p sub near UINT32_MAX does not
|
||||
* overflow. Shared by VaultCreate::preflight and the ValidVault invariant.
|
||||
*
|
||||
* @param sub The value of sfSubscriptionDate.
|
||||
* @param red The value of sfRedemptionDate.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isValidClosedEndedGap(std::uint32_t sub, std::uint32_t red);
|
||||
|
||||
/**
|
||||
* Returns the current lifecycle phase of a vault. Open-ended
|
||||
* vaults are always NoPhase. For closed-ended vaults the phase is derived
|
||||
* from the parent ledger close time and the vault's immutable
|
||||
* SubscriptionDate and RedemptionDate.
|
||||
*
|
||||
* @param view The ledger view whose parent close time is used as the clock.
|
||||
* @param vault The vault SLE.
|
||||
*/
|
||||
[[nodiscard]] VaultPhase
|
||||
getVaultPhase(ReadView const& view, SLE::const_ref vault);
|
||||
|
||||
/**
|
||||
* Raw-fields overload of getVaultPhase. Derives the phase from an already
|
||||
* decomposed vault snapshot: an absent or non-ClosedEnded @p vaultKind
|
||||
* resolves to VaultPhase::NoPhase; otherwise the phase is computed from
|
||||
* @p subscriptionDate and @p redemptionDate against the view's parent
|
||||
* close time using the same boundary semantics as the SLE overload
|
||||
* (Subscription is inclusive of now == SubscriptionDate; Investment starts
|
||||
* strictly after).
|
||||
*
|
||||
* @param view The ledger view whose parent close time is used as the clock.
|
||||
* @param vaultKind The value of sfVaultKind, or nullopt if absent.
|
||||
* @param subscriptionDate The value of sfSubscriptionDate, or nullopt if absent.
|
||||
* @param redemptionDate The value of sfRedemptionDate, or nullopt if absent.
|
||||
*/
|
||||
[[nodiscard]] VaultPhase
|
||||
getVaultPhase(
|
||||
ReadView const& view,
|
||||
std::optional<std::uint8_t> vaultKind,
|
||||
std::optional<std::uint32_t> subscriptionDate,
|
||||
std::optional<std::uint32_t> redemptionDate);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/tls1.h>
|
||||
|
||||
#include <format>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
@@ -38,8 +38,8 @@ public:
|
||||
|
||||
if (ec && sslVerifyDir.empty())
|
||||
{
|
||||
Throw<std::runtime_error>(boost::str(
|
||||
boost::format("Failed to set_default_verify_paths: %s") % ec.message()));
|
||||
Throw<std::runtime_error>(
|
||||
std::format("Failed to set_default_verify_paths: {}", ec.message()));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -54,7 +54,7 @@ public:
|
||||
if (ec)
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
boost::str(boost::format("Failed to add verify path: %s") % ec.message()));
|
||||
std::format("Failed to add verify path: {}", ec.message()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user