mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-18 16:16:55 +00:00
Compare commits
35 Commits
ximinez/fi
...
ximinez/di
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff3708a757 | ||
|
|
391a1e442c | ||
|
|
9e77212900 | ||
|
|
93f5a0e217 | ||
|
|
71367f361c | ||
|
|
931d21b2a7 | ||
|
|
c99feb82e7 | ||
|
|
7b53a5e0c5 | ||
|
|
4a02518497 | ||
|
|
0b6c3630cc | ||
|
|
4d04ba5be5 | ||
|
|
ed53557d41 | ||
|
|
e4d10393f3 | ||
|
|
fcc7f57e82 | ||
|
|
a25229f154 | ||
|
|
eb6eaf2532 | ||
|
|
ff987fc7c6 | ||
|
|
d21137c4c1 | ||
|
|
ac7db2e621 | ||
|
|
42005a8080 | ||
|
|
1f7b1b3a78 | ||
|
|
80b90544c5 | ||
|
|
00b9a8cd67 | ||
|
|
3be49f814a | ||
|
|
1674fabe81 | ||
|
|
6dfa47ce7a | ||
|
|
bef095be65 | ||
|
|
8e5d774c36 | ||
|
|
fb8fb30f6c | ||
|
|
a553001125 | ||
|
|
57782e84ee | ||
|
|
9d5076c8a9 | ||
|
|
1af379e09f | ||
|
|
1ced0875ae | ||
|
|
53e6d7580a |
@@ -153,7 +153,6 @@ Checks: "-*,
|
||||
readability-use-std-min-max
|
||||
"
|
||||
# ---
|
||||
# bugprone-narrowing-conversions, # this will break a lot of code but we should enable it in the future because it can eliminate a lot of bugs
|
||||
# readability-inconsistent-declaration-parameter-name, # In this codebase this check will break a lot of arg names
|
||||
# readability-static-accessed-through-instance, # this check is probably unnecessary. It makes the code less readable
|
||||
# ---
|
||||
|
||||
@@ -14,6 +14,7 @@ libxrpl.ledger > xrpl.json
|
||||
libxrpl.ledger > xrpl.ledger
|
||||
libxrpl.ledger > xrpl.nodestore
|
||||
libxrpl.ledger > xrpl.protocol
|
||||
libxrpl.ledger > xrpl.server
|
||||
libxrpl.ledger > xrpl.shamap
|
||||
libxrpl.net > xrpl.basics
|
||||
libxrpl.net > xrpl.net
|
||||
@@ -220,6 +221,7 @@ xrpl.core > xrpl.protocol
|
||||
xrpl.json > xrpl.basics
|
||||
xrpl.ledger > xrpl.basics
|
||||
xrpl.ledger > xrpl.protocol
|
||||
xrpl.ledger > xrpl.server
|
||||
xrpl.ledger > xrpl.shamap
|
||||
xrpl.net > xrpl.basics
|
||||
xrpl.nodestore > xrpl.basics
|
||||
|
||||
2
.github/scripts/strategy-matrix/generate.py
vendored
2
.github/scripts/strategy-matrix/generate.py
vendored
@@ -20,6 +20,8 @@ _SANITIZER_SUFFIX: dict[str, str] = {
|
||||
def get_cmake_args(build_type: str, extra_args: str) -> str:
|
||||
"""Get the full list of CMake arguments for a config."""
|
||||
args = _BASE_CMAKE_ARGS.copy()
|
||||
if build_type == "Release":
|
||||
args.append("-Dassert=ON")
|
||||
if extra_args:
|
||||
args.extend(extra_args.split())
|
||||
return " ".join(args)
|
||||
|
||||
6
.github/scripts/strategy-matrix/linux.json
vendored
6
.github/scripts/strategy-matrix/linux.json
vendored
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"image_tag": "sha-fe4c8ae",
|
||||
"image_tag": "sha-63ffdc3",
|
||||
"configs": {
|
||||
"ubuntu": [
|
||||
{
|
||||
@@ -68,7 +68,7 @@
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"],
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-577d745"
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-63ffdc3"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"],
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-577d745"
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-63ffdc3"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
8
.github/workflows/build-nix-images.yml
vendored
8
.github/workflows/build-nix-images.yml
vendored
@@ -9,20 +9,12 @@ on:
|
||||
- "flake.nix"
|
||||
- "flake.lock"
|
||||
- "nix/**"
|
||||
- "!nix/docker/README.md"
|
||||
- "!nix/devshell.nix"
|
||||
- "bin/check-tools.sh"
|
||||
- "bin/install-sanitizer-libs.sh"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/build-nix-images.yml"
|
||||
- "flake.nix"
|
||||
- "flake.lock"
|
||||
- "nix/**"
|
||||
- "!nix/docker/README.md"
|
||||
- "!nix/devshell.nix"
|
||||
- "bin/check-tools.sh"
|
||||
- "bin/install-sanitizer-libs.sh"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
|
||||
2
.github/workflows/publish-docs.yml
vendored
2
.github/workflows/publish-docs.yml
vendored
@@ -41,7 +41,7 @@ env:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-fe4c8ae
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-63ffdc3
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
40
.github/workflows/reusable-build-test-config.yml
vendored
40
.github/workflows/reusable-build-test-config.yml
vendored
@@ -121,11 +121,6 @@ jobs:
|
||||
if: ${{ inputs.ccache_enabled && runner.debug == '1' }}
|
||||
run: echo "CCACHE_LOGFILE=${{ runner.temp }}/ccache.log" >>"${GITHUB_ENV}"
|
||||
|
||||
- name: Check tools
|
||||
env:
|
||||
CHECK_TOOLS_SKIP_CLONE: "1"
|
||||
run: ./bin/check-tools.sh
|
||||
|
||||
- name: Print build environment
|
||||
uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574
|
||||
|
||||
@@ -169,27 +164,6 @@ jobs:
|
||||
${CMAKE_ARGS} \
|
||||
..
|
||||
|
||||
# Export the sanitizer options before any instrumented binary runs. The
|
||||
# protocol code-gen and build steps below invoke instrumented dependency
|
||||
# tools (protoc, grpc), so setting UBSAN_OPTIONS here lets the UBSan
|
||||
# suppression list silence their diagnostics too, not just at test time.
|
||||
# GITHUB_WORKSPACE (not the github.workspace context) is used so the path
|
||||
# resolves correctly inside the container job.
|
||||
- name: Set sanitizer options
|
||||
if: ${{ !inputs.build_only && env.SANITIZERS_ENABLED == 'true' }}
|
||||
env:
|
||||
CONFIG_NAME: ${{ inputs.config_name }}
|
||||
run: |
|
||||
SUPP="${GITHUB_WORKSPACE}/sanitizers/suppressions"
|
||||
ASAN_OPTS="include=${SUPP}/runtime-asan-options.txt:suppressions=${SUPP}/asan.supp"
|
||||
if [[ "${CONFIG_NAME}" == *gcc* ]]; then
|
||||
ASAN_OPTS="${ASAN_OPTS}:alloc_dealloc_mismatch=0"
|
||||
fi
|
||||
echo "ASAN_OPTIONS=${ASAN_OPTS}" >>${GITHUB_ENV}
|
||||
echo "TSAN_OPTIONS=include=${SUPP}/runtime-tsan-options.txt:suppressions=${SUPP}/tsan.supp" >>${GITHUB_ENV}
|
||||
echo "UBSAN_OPTIONS=include=${SUPP}/runtime-ubsan-options.txt:suppressions=${SUPP}/ubsan.supp" >>${GITHUB_ENV}
|
||||
echo "LSAN_OPTIONS=include=${SUPP}/runtime-lsan-options.txt:suppressions=${SUPP}/lsan.supp" >>${GITHUB_ENV}
|
||||
|
||||
- name: Check protocol autogen files are up-to-date
|
||||
working-directory: ${{ env.BUILD_DIR }}
|
||||
env:
|
||||
@@ -305,6 +279,20 @@ jobs:
|
||||
run: |
|
||||
./xrpld --version | grep libvoidstar
|
||||
|
||||
- name: Set sanitizer options
|
||||
if: ${{ !inputs.build_only && env.SANITIZERS_ENABLED == 'true' }}
|
||||
env:
|
||||
CONFIG_NAME: ${{ inputs.config_name }}
|
||||
run: |
|
||||
ASAN_OPTS="include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-asan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/asan.supp"
|
||||
if [[ "${CONFIG_NAME}" == *gcc* ]]; then
|
||||
ASAN_OPTS="${ASAN_OPTS}:alloc_dealloc_mismatch=0"
|
||||
fi
|
||||
echo "ASAN_OPTIONS=${ASAN_OPTS}" >>${GITHUB_ENV}
|
||||
echo "TSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-tsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/tsan.supp" >>${GITHUB_ENV}
|
||||
echo "UBSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-ubsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/ubsan.supp" >>${GITHUB_ENV}
|
||||
echo "LSAN_OPTIONS=include=${GITHUB_WORKSPACE}/sanitizers/suppressions/runtime-lsan-options.txt:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/lsan.supp" >>${GITHUB_ENV}
|
||||
|
||||
- name: Run the separate tests
|
||||
if: ${{ !inputs.build_only }}
|
||||
working-directory: ${{ runner.os == 'Windows' && format('{0}/{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }}
|
||||
|
||||
25
.github/workflows/reusable-clang-tidy.yml
vendored
25
.github/workflows/reusable-clang-tidy.yml
vendored
@@ -20,12 +20,9 @@ env:
|
||||
BUILD_DIR: build
|
||||
BUILD_TYPE: Debug # Debug so that ASSERTS and such participate in clang-tidy check
|
||||
|
||||
OUTPUT_FILE: /tmp/clang-tidy-output.txt
|
||||
FILTERED_OUTPUT_FILE: /tmp/clang-tidy-filtered-output.txt
|
||||
DIFF_FILE: /tmp/clang-tidy-git-diff.txt
|
||||
ISSUE_FILE: /tmp/clang-tidy-issue.md
|
||||
|
||||
COMPILER: clang
|
||||
OUTPUT_FILE: clang-tidy-output.txt
|
||||
DIFF_FILE: clang-tidy-git-diff.txt
|
||||
ISSUE_FILE: clang-tidy-issue.md
|
||||
|
||||
jobs:
|
||||
determine-files:
|
||||
@@ -39,7 +36,7 @@ jobs:
|
||||
needs: [determine-files]
|
||||
if: ${{ always() && !cancelled() && (!inputs.check_only_changed || needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.clang_tidy_config_changed == 'true') }}
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-fe4c8ae"
|
||||
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-63ffdc3"
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
@@ -62,7 +59,7 @@ jobs:
|
||||
- name: Set compiler environment
|
||||
uses: ./.github/actions/set-compiler-env
|
||||
with:
|
||||
compiler: ${{ env.COMPILER }}
|
||||
compiler: clang
|
||||
|
||||
- name: Setup Conan
|
||||
uses: ./.github/actions/setup-conan
|
||||
@@ -153,21 +150,21 @@ jobs:
|
||||
run: |
|
||||
if [ -f "${OUTPUT_FILE}" ]; then
|
||||
# Extract lines containing 'error:', 'warning:', or 'note:'
|
||||
grep -E '(error:|warning:|note:)' "${OUTPUT_FILE}" >"${FILTERED_OUTPUT_FILE}" || true
|
||||
grep -E '(error:|warning:|note:)' "${OUTPUT_FILE}" >filtered-output.txt || true
|
||||
|
||||
# If filtered output is empty, use original (might be a different error format)
|
||||
if [ ! -s "${FILTERED_OUTPUT_FILE}" ]; then
|
||||
cp "${OUTPUT_FILE}" "${FILTERED_OUTPUT_FILE}"
|
||||
if [ ! -s filtered-output.txt ]; then
|
||||
cp "${OUTPUT_FILE}" filtered-output.txt
|
||||
fi
|
||||
|
||||
# Truncate if too large
|
||||
head -c 60000 "${FILTERED_OUTPUT_FILE}" >>"${ISSUE_FILE}"
|
||||
if [ "$(wc -c <"${FILTERED_OUTPUT_FILE}")" -gt 60000 ]; then
|
||||
head -c 60000 filtered-output.txt >>"${ISSUE_FILE}"
|
||||
if [ "$(wc -c <filtered-output.txt)" -gt 60000 ]; then
|
||||
echo "" >>"${ISSUE_FILE}"
|
||||
echo "... (output truncated, see artifacts for full output)" >>"${ISSUE_FILE}"
|
||||
fi
|
||||
|
||||
rm "${FILTERED_OUTPUT_FILE}"
|
||||
rm filtered-output.txt
|
||||
else
|
||||
echo "No output file found" >>"${ISSUE_FILE}"
|
||||
fi
|
||||
|
||||
2
.github/workflows/reusable-upload-recipe.yml
vendored
2
.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-fe4c8ae
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-63ffdc3
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
391
BUILD.md
391
BUILD.md
@@ -1,57 +1,26 @@
|
||||
| :warning: **WARNING** :warning: |
|
||||
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| These instructions assume you have a C++ development environment ready with Git, Python, Conan, CMake, and a C++ compiler. For help setting one up on Linux, macOS, or Windows, [see this guide](./docs/build/environment.md).<br><br>These instructions also assume a basic familiarity with Conan and CMake. If you are unfamiliar with Conan, you can read our [crash course](./docs/build/conan.md) or the official [Getting Started][conan-getting-started] walkthrough. |
|
||||
| :warning: **WARNING** :warning: |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| These instructions assume you have a C++ development environment ready with Git, Python, Conan, CMake, and a C++ compiler. For help setting one up on Linux, macOS, or Windows, [see this guide](./docs/build/environment.md). |
|
||||
|
||||
## Minimum Requirements
|
||||
> These instructions also assume a basic familiarity with Conan and CMake.
|
||||
> If you are unfamiliar with Conan, you can read our
|
||||
> [crash course](./docs/build/conan.md) or the official [Getting Started][3]
|
||||
> walkthrough.
|
||||
|
||||
See [System Requirements](https://xrpl.org/system-requirements.html).
|
||||
## Branches
|
||||
|
||||
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:
|
||||
For a stable release, choose the `master` branch or one of the [tagged
|
||||
releases](https://github.com/XRPLF/rippled/releases).
|
||||
|
||||
```bash
|
||||
./bin/check-tools.sh
|
||||
git checkout master
|
||||
```
|
||||
|
||||
`xrpld` is written in the C++23 dialect. The [tested compiler versions][cpp23-support] are:
|
||||
For the latest release candidate, choose the `release` branch.
|
||||
|
||||
| Compiler | Version |
|
||||
| ----------- | --------------- |
|
||||
| GCC | 15.2 |
|
||||
| Clang | 22 |
|
||||
| Apple Clang | 17 |
|
||||
| MSVC | 19.44[^windows] |
|
||||
|
||||
## 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
|
||||
assurance, testing, and support. We also support Red Hat and use Debian
|
||||
internally.
|
||||
Our Linux CI tooling is distro-independent and uses a Nix-based environment, so it should be possible to build on other Linux distributions as well, although we have not tested them.
|
||||
|
||||
### macOS
|
||||
|
||||
Many `xrpld` engineers use macOS for development.
|
||||
|
||||
### Windows
|
||||
|
||||
Windows is used by some engineers for development only.
|
||||
|
||||
[^windows]: Windows is not recommended for production use.
|
||||
|
||||
## Steps
|
||||
|
||||
### Branches
|
||||
```bash
|
||||
git checkout release
|
||||
```
|
||||
|
||||
For the latest set of untested features, or to contribute, choose the `develop`
|
||||
branch.
|
||||
@@ -60,15 +29,55 @@ branch.
|
||||
git checkout develop
|
||||
```
|
||||
|
||||
For a release candidate, choose the relevant release branch, e.g.
|
||||
`release/3.2.x`.
|
||||
## Minimum Requirements
|
||||
|
||||
```bash
|
||||
git checkout release/3.2.x
|
||||
```
|
||||
See [System Requirements](https://xrpl.org/system-requirements.html).
|
||||
|
||||
For a stable release, choose one of the [tagged
|
||||
releases](https://github.com/XRPLF/rippled/releases).
|
||||
Building xrpld generally requires git, Python, Conan, CMake, and a C++
|
||||
compiler. Some guidance on setting up such a [C++ development environment can be
|
||||
found here](./docs/build/environment.md).
|
||||
|
||||
- [Python 3.11](https://www.python.org/downloads/), or higher
|
||||
- [Conan 2.17](https://conan.io/downloads.html)[^1], or higher
|
||||
- [CMake 3.22](https://cmake.org/download/), or higher
|
||||
|
||||
[^1]:
|
||||
It is possible to build with Conan 1.60+, but the instructions are
|
||||
significantly different, which is why we are not recommending it.
|
||||
|
||||
`xrpld` is written in the C++23 dialect and includes the `<concepts>` header.
|
||||
The [tested compiler versions][2] are:
|
||||
|
||||
| Compiler | Version |
|
||||
| ----------- | --------- |
|
||||
| GCC | 15 |
|
||||
| Clang | 22 |
|
||||
| Apple Clang | 17 |
|
||||
| MSVC | 19.44[^3] |
|
||||
|
||||
### Linux
|
||||
|
||||
The Ubuntu Linux distribution has received the highest level of quality
|
||||
assurance, testing, and support. We also support Red Hat and use Debian
|
||||
internally.
|
||||
|
||||
Here are [sample instructions for setting up a C++ development environment on
|
||||
Linux](./docs/build/environment.md#linux).
|
||||
|
||||
### Mac
|
||||
|
||||
Many xrpld engineers use macOS for development.
|
||||
|
||||
Here are [sample instructions for setting up a C++ development environment on
|
||||
macOS](./docs/build/environment.md#macos).
|
||||
|
||||
### Windows
|
||||
|
||||
Windows is used by some engineers for development only.
|
||||
|
||||
[^3]: Windows is not recommended for production use.
|
||||
|
||||
## Steps
|
||||
|
||||
### Set Up Conan
|
||||
|
||||
@@ -77,11 +86,18 @@ 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.
|
||||
[Getting Started][3] walkthrough.
|
||||
|
||||
#### Profiles
|
||||
#### Conan lockfile
|
||||
|
||||
We recommend that you install our Conan profiles:
|
||||
To achieve reproducible dependencies, we use a [Conan lockfile](https://docs.conan.io/2/tutorial/versioning/lockfiles.html),
|
||||
which has to be updated every time dependencies change.
|
||||
|
||||
Please see the [instructions on how to regenerate the lockfile](conan/lockfile/README.md).
|
||||
|
||||
#### Default profile
|
||||
|
||||
We recommend that you import the provided `conan/profiles/default` profile:
|
||||
|
||||
```bash
|
||||
conan config install conan/profiles/ -tf $(conan config home)/profiles/
|
||||
@@ -93,15 +109,222 @@ You can check your Conan profile by running:
|
||||
conan profile show
|
||||
```
|
||||
|
||||
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).
|
||||
#### Custom profile
|
||||
|
||||
#### Add xrplf remote
|
||||
|
||||
Run the following command to add the `xrplf` remote, which hosts some of our dependencies:
|
||||
If the default profile does not work for you and you do not yet have a Conan
|
||||
profile, you can create one by running:
|
||||
|
||||
```bash
|
||||
conan remote add --index 0 --force xrplf https://conan.ripplex.io
|
||||
conan profile detect
|
||||
```
|
||||
|
||||
You may need to make changes to the profile to suit your environment. You can
|
||||
refer to the provided `conan/profiles/default` profile for inspiration, and you
|
||||
may also need to apply the required [tweaks](#conan-profile-tweaks) to this
|
||||
default profile.
|
||||
|
||||
### Patched recipes
|
||||
|
||||
Occasionally, we need patched recipes or recipes not present in Conan Center.
|
||||
We maintain a fork of the Conan Center Index
|
||||
[here](https://github.com/XRPLF/conan-center-index/) containing the modified and newly added recipes.
|
||||
|
||||
To ensure our patched recipes are used, you must add our Conan remote at a
|
||||
higher index than the default Conan Center remote, so it is consulted first. You
|
||||
can do this by running:
|
||||
|
||||
```bash
|
||||
conan remote add --index 0 xrplf https://conan.ripplex.io
|
||||
```
|
||||
|
||||
Alternatively, you can pull our recipes from the repository and export them locally:
|
||||
|
||||
```bash
|
||||
# Define which recipes to export.
|
||||
recipes=('abseil' 'ed25519' 'mpt-crypto' 'openssl' 'secp256k1' 'snappy' 'soci' 'wasm-xrplf' 'wasmi')
|
||||
|
||||
# Selectively check out the recipes from our CCI fork.
|
||||
cd external
|
||||
mkdir -p conan-center-index
|
||||
cd conan-center-index
|
||||
git init
|
||||
git remote add origin git@github.com:XRPLF/conan-center-index.git
|
||||
git sparse-checkout init
|
||||
for recipe in "${recipes[@]}"; do
|
||||
echo "Checking out recipe '${recipe}'..."
|
||||
git sparse-checkout add recipes/${recipe}
|
||||
done
|
||||
git fetch origin master
|
||||
git checkout master
|
||||
|
||||
./export_all.sh
|
||||
cd ../../
|
||||
```
|
||||
|
||||
In the case we switch to a newer version of a dependency that still requires a
|
||||
patch or add a new dependency, it will be necessary for you to pull in the changes and re-export the
|
||||
updated dependencies with the newer version. However, if we switch to a newer
|
||||
version that no longer requires a patch, no action is required on your part, as
|
||||
the new recipe will be automatically pulled from the official Conan Center.
|
||||
|
||||
> [!NOTE]
|
||||
> You might need to add `--lockfile=""` to your `conan install` command
|
||||
> to avoid automatic use of the existing `conan.lock` file when you run
|
||||
> `conan export` manually on your machine
|
||||
>
|
||||
> This is not recommended though, as you might end up using different revisions of recipes.
|
||||
|
||||
### Conan profile tweaks
|
||||
|
||||
#### Missing compiler version
|
||||
|
||||
If you see an error similar to the following after running `conan profile show`:
|
||||
|
||||
```text
|
||||
ERROR: Invalid setting '17' is not a valid 'settings.compiler.version' value.
|
||||
Possible values are ['5.0', '5.1', '6.0', '6.1', '7.0', '7.3', '8.0', '8.1',
|
||||
'9.0', '9.1', '10.0', '11.0', '12.0', '13', '13.0', '13.1', '14', '14.0', '15',
|
||||
'15.0', '16', '16.0']
|
||||
Read "http://docs.conan.io/2/knowledge/faq.html#error-invalid-setting"
|
||||
```
|
||||
|
||||
you need to add your compiler to the list of compiler versions in
|
||||
`$(conan config home)/settings_user.yml`, by adding the required version number(s)
|
||||
to the `version` array specific for your compiler. For example:
|
||||
|
||||
```yaml
|
||||
compiler:
|
||||
apple-clang:
|
||||
version: ["17.0"]
|
||||
```
|
||||
|
||||
#### Multiple compilers
|
||||
|
||||
If you have multiple compilers installed, make sure to select the one to use in
|
||||
your default Conan configuration **before** running `conan profile detect`, by
|
||||
setting the `CC` and `CXX` environment variables.
|
||||
|
||||
For example, if you are running MacOS and have [homebrew
|
||||
LLVM@18](https://formulae.brew.sh/formula/llvm@18), and want to use it as a
|
||||
compiler in the new Conan profile:
|
||||
|
||||
```bash
|
||||
export CC=$(brew --prefix llvm@18)/bin/clang
|
||||
export CXX=$(brew --prefix llvm@18)/bin/clang++
|
||||
conan profile detect
|
||||
```
|
||||
|
||||
You should also explicitly set the path to the compiler in the profile file,
|
||||
which helps to avoid errors when `CC` and/or `CXX` are set and disagree with the
|
||||
selected Conan profile. For example:
|
||||
|
||||
```text
|
||||
[conf]
|
||||
tools.build:compiler_executables={'c':'/usr/bin/gcc','cpp':'/usr/bin/g++'}
|
||||
```
|
||||
|
||||
#### Multiple profiles
|
||||
|
||||
You can manage multiple Conan profiles in the directory
|
||||
`$(conan config home)/profiles`, for example renaming `default` to a different
|
||||
name and then creating a new `default` profile for a different compiler.
|
||||
|
||||
#### Select language
|
||||
|
||||
The default profile created by Conan will typically select different C++ dialect
|
||||
than C++23 used by this project. You should set `23` in the profile line
|
||||
starting with `compiler.cppstd=`. For example:
|
||||
|
||||
```bash
|
||||
sed -i.bak -e 's|^compiler\.cppstd=.*$|compiler.cppstd=23|' $(conan config home)/profiles/default
|
||||
```
|
||||
|
||||
#### Select standard library in Linux
|
||||
|
||||
**Linux** developers will commonly have a default Conan [profile][] that
|
||||
compiles with GCC and links with libstdc++. If you are linking with libstdc++
|
||||
(see profile setting `compiler.libcxx`), then you will need to choose the
|
||||
`libstdc++11` ABI:
|
||||
|
||||
```bash
|
||||
sed -i.bak -e 's|^compiler\.libcxx=.*$|compiler.libcxx=libstdc++11|' $(conan config home)/profiles/default
|
||||
```
|
||||
|
||||
#### Select architecture and runtime in Windows
|
||||
|
||||
**Windows** developers may need to use the x64 native build tools. An easy way
|
||||
to do that is to run the shortcut "x64 Native Tools Command Prompt" for the
|
||||
version of Visual Studio that you have installed.
|
||||
|
||||
Windows developers must also build `xrpld` and its dependencies for the x64
|
||||
architecture:
|
||||
|
||||
```bash
|
||||
sed -i.bak -e 's|^arch=.*$|arch=x86_64|' $(conan config home)/profiles/default
|
||||
```
|
||||
|
||||
**Windows** developers also must select static runtime:
|
||||
|
||||
```bash
|
||||
sed -i.bak -e 's|^compiler\.runtime=.*$|compiler.runtime=static|' $(conan config home)/profiles/default
|
||||
```
|
||||
|
||||
#### Clang workaround for grpc
|
||||
|
||||
If your compiler is clang, version 19 or later, or apple-clang, version 17 or
|
||||
later, you may encounter a compilation error while building the `grpc`
|
||||
dependency:
|
||||
|
||||
```text
|
||||
In file included from .../lib/promise/try_seq.h:26:
|
||||
.../lib/promise/detail/basic_seq.h:499:38: error: a template argument list is expected after a name prefixed by the template keyword [-Wmissing-template-arg-list-after-template-kw]
|
||||
499 | Traits::template CallSeqFactory(f_, *cur_, std::move(arg)));
|
||||
| ^
|
||||
```
|
||||
|
||||
The workaround for this error is to add two lines to profile:
|
||||
|
||||
```text
|
||||
[conf]
|
||||
tools.build:cxxflags=['-Wno-missing-template-arg-list-after-template-kw']
|
||||
```
|
||||
|
||||
#### Workaround for gcc 12
|
||||
|
||||
If your compiler is gcc, version 12, and you have enabled `werr` option, you may
|
||||
encounter a compilation error such as:
|
||||
|
||||
```text
|
||||
/usr/include/c++/12/bits/char_traits.h:435:56: error: 'void* __builtin_memcpy(void*, const void*, long unsigned int)' accessing 9223372036854775810 or more bytes at offsets [2, 9223372036854775807] and 1 may overlap up to 9223372036854775813 bytes at offset -3 [-Werror=restrict]
|
||||
435 | return static_cast<char_type*>(__builtin_memcpy(__s1, __s2, __n));
|
||||
| ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~
|
||||
cc1plus: all warnings being treated as errors
|
||||
```
|
||||
|
||||
The workaround for this error is to add two lines to your profile:
|
||||
|
||||
```text
|
||||
[conf]
|
||||
tools.build:cxxflags=['-Wno-restrict']
|
||||
```
|
||||
|
||||
#### Workaround for clang 16
|
||||
|
||||
If your compiler is clang, version 16, you may encounter compilation error such
|
||||
as:
|
||||
|
||||
```text
|
||||
In file included from .../boost/beast/websocket/stream.hpp:2857:
|
||||
.../boost/beast/websocket/impl/read.hpp:695:17: error: call to 'async_teardown' is ambiguous
|
||||
async_teardown(impl.role, impl.stream(),
|
||||
^~~~~~~~~~~~~~
|
||||
```
|
||||
|
||||
The workaround for this error is to add two lines to your profile:
|
||||
|
||||
```text
|
||||
[conf]
|
||||
tools.build:cxxflags=['-DBOOST_ASIO_DISABLE_CONCEPTS']
|
||||
```
|
||||
|
||||
### Set Up Ccache
|
||||
@@ -110,7 +333,14 @@ To speed up repeated compilations, we recommend that you install
|
||||
[ccache](https://ccache.dev), a tool that wraps your compiler so that it can
|
||||
cache build objects locally.
|
||||
|
||||
On Linux and macOS, `ccache` is included in the [Nix development shell](./docs/build/nix.md).
|
||||
#### Linux
|
||||
|
||||
You can install it using the package manager, e.g. `sudo apt install ccache`
|
||||
(Ubuntu) or `sudo dnf install ccache` (RHEL).
|
||||
|
||||
#### macOS
|
||||
|
||||
You can install it using Homebrew, i.e. `brew install ccache`.
|
||||
|
||||
#### Windows
|
||||
|
||||
@@ -319,7 +549,7 @@ See [Sanitizers docs](./docs/build/sanitizers.md) for more details.
|
||||
|
||||
| Option | Default Value | Description |
|
||||
| ---------- | ------------- | -------------------------------------------------------------- |
|
||||
| `assert` | OFF | Force enabling assertions. |
|
||||
| `assert` | OFF | Enable assertions. |
|
||||
| `coverage` | OFF | Prepare the coverage report. |
|
||||
| `tests` | OFF | Build tests. |
|
||||
| `unity` | OFF | Configure a unity build. |
|
||||
@@ -327,7 +557,7 @@ See [Sanitizers docs](./docs/build/sanitizers.md) for more details.
|
||||
| `werr` | OFF | Treat compilation warnings as errors |
|
||||
| `wextra` | OFF | Enable additional compilation warnings |
|
||||
|
||||
[Unity builds][unity-build] may be faster for the first build (at the cost of much more
|
||||
[Unity builds][5] may be faster for the first build (at the cost of much more
|
||||
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.
|
||||
@@ -353,14 +583,14 @@ After any updates or changes to dependencies, you may need to do the following:
|
||||
conan remove '*'
|
||||
```
|
||||
|
||||
3. Re-run [conan export](./docs/build/advanced_conan.md#patched-recipes) if needed.
|
||||
4. [Regenerate lockfile](./docs/build/advanced_conan.md#conan-lockfile).
|
||||
3. Re-run [conan export](#patched-recipes) if needed.
|
||||
4. [Regenerate lockfile](#conan-lockfile).
|
||||
5. Re-run [conan install](#build-and-test).
|
||||
|
||||
#### 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 add `xrplf` remote or re-run `conan export` for [patched recipes](#patched-recipes).
|
||||
|
||||
### `protobuf/port_def.inc` file not found
|
||||
|
||||
@@ -380,9 +610,28 @@ 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
|
||||
## Add a Dependency
|
||||
|
||||
If you want to experiment with a new package, follow these steps:
|
||||
|
||||
1. Search for the package on [Conan Center](https://conan.io/center/).
|
||||
2. Modify [`conanfile.py`](./conanfile.py):
|
||||
- Add a version of the package to the `requires` property.
|
||||
- Change any default options for the package by adding them to the
|
||||
`default_options` property (with syntax `'$package:$option': $value`).
|
||||
3. Modify [`CMakeLists.txt`](./CMakeLists.txt):
|
||||
- Add a call to `find_package($package REQUIRED)`.
|
||||
- Link a library from the package to the target `xrpl_libs`
|
||||
(search for the existing call to `target_link_libraries(xrpl_libs INTERFACE ...)`).
|
||||
4. Start coding! Don't forget to include whatever headers you need from the package.
|
||||
|
||||
[1]: https://github.com/conan-io/conan-center-index/issues/13168
|
||||
[2]: https://en.cppreference.com/w/cpp/compiler_support/20
|
||||
[3]: https://docs.conan.io/en/latest/getting_started.html
|
||||
[5]: https://en.wikipedia.org/wiki/Unity_build
|
||||
[6]: https://github.com/boostorg/beast/issues/2648
|
||||
[7]: https://github.com/boostorg/beast/issues/2661
|
||||
[gcovr]: https://gcovr.com/en/stable/getting-started.html
|
||||
[python-pip]: https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/
|
||||
[build_type]: https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html
|
||||
[profile]: https://docs.conan.io/en/latest/reference/profiles.html
|
||||
|
||||
@@ -14,9 +14,9 @@ The following branches exist in the main project repository:
|
||||
|
||||
- `develop`: The latest set of unreleased features, and the most common
|
||||
starting point for contributions.
|
||||
- `release/*` (e.g. `release/3.2.x`): Release branches, one per release line,
|
||||
holding the latest release candidate, or stable release for that line.
|
||||
Stable releases are published as [tagged releases](https://github.com/XRPLF/rippled/releases).
|
||||
- `release`: The latest beta release or release candidate.
|
||||
- `master`: The latest stable release.
|
||||
- `gh-pages`: The documentation for this project, built by Doxygen.
|
||||
|
||||
The tip of each branch must be signed. In order for GitHub to sign a
|
||||
squashed commit that it builds from your pull request, GitHub must know
|
||||
@@ -130,9 +130,11 @@ tl;dr
|
||||
## Pull requests
|
||||
|
||||
In general, pull requests use `develop` as the base branch.
|
||||
The exceptions are
|
||||
|
||||
The exceptions are fixes, improvements, and hotfixes for an existing release,
|
||||
which use that release's branch (e.g. `release/3.2.x`) as the base.
|
||||
- Fixes and improvements to a release candidate use `release` as the
|
||||
base.
|
||||
- Hotfixes use `master` as the base.
|
||||
|
||||
If your changes are not quite ready, but you want to make it easily available
|
||||
for preliminary examination or review, you can create a "Draft" pull request.
|
||||
@@ -214,7 +216,7 @@ coherent rather than a set of _thou shalt not_ commandments.
|
||||
|
||||
## Formatting
|
||||
|
||||
All code must conform to `clang-format` version 22,
|
||||
All code must conform to `clang-format` version 21,
|
||||
according to the settings in [`.clang-format`](./.clang-format),
|
||||
unless the result would be unreasonably difficult to read or maintain.
|
||||
To demarcate lines that should be left as-is, surround them with comments like
|
||||
@@ -259,7 +261,7 @@ This ensures that configuration changes don't introduce new warnings across the
|
||||
|
||||
### Installing clang-tidy
|
||||
|
||||
See the [environment setup guide](./docs/build/environment.md#clang-tidy) for how to get clang-tidy.
|
||||
See the [environment setup guide](./docs/build/environment.md#clang-tidy) for platform-specific installation instructions.
|
||||
|
||||
### Running clang-tidy locally
|
||||
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# check-tools.sh — verify the xrpld development tooling is present and runnable.
|
||||
#
|
||||
# Works on Linux, macOS, and Windows (Git Bash / MSYS). For every expected tool
|
||||
# it runs a version probe, collecting anything that is missing or fails to run,
|
||||
# and prints a summary at the end (exiting non-zero if anything is missing).
|
||||
#
|
||||
# The tool set is platform-aware:
|
||||
# - Linux: the full Nix CI environment (see nix/packages.nix, nix/ci-env.nix),
|
||||
# with GCC, Clang and the sanitizer/coverage tooling. This script is
|
||||
# run during the Nix Docker image build (nix/docker/Dockerfile), so
|
||||
# the Linux list is kept in sync with that environment.
|
||||
# - macOS: the same tooling, minus GCC/g++/gcov/mold
|
||||
# - 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.
|
||||
#
|
||||
# Environment variables:
|
||||
# CI if set, skip the tools above when on macOS.
|
||||
# CHECK_TOOLS_SKIP_CLONE if set, skip the git-over-HTTPS connectivity check.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
missing=()
|
||||
checked=0
|
||||
|
||||
# check <name> [probe-command...]
|
||||
# Runs the probe (default: "<name> --version") quietly. Records <name> as
|
||||
# missing if the command is not found or exits non-zero.
|
||||
check() {
|
||||
local name="$1"
|
||||
shift
|
||||
local -a probe=("$@")
|
||||
if [ "${#probe[@]}" -eq 0 ]; then
|
||||
probe=("${name}" --version)
|
||||
fi
|
||||
|
||||
echo "Checking ${name}..."
|
||||
checked=$((checked + 1))
|
||||
if "${probe[@]}" | head -n 1; then
|
||||
printf ' [ ok ] %s\n' "${name}"
|
||||
else
|
||||
printf ' [MISS] %s\n' "${name}"
|
||||
missing+=("${name}")
|
||||
fi
|
||||
}
|
||||
|
||||
case "$(uname -s)" in
|
||||
Linux*) os=linux ;;
|
||||
Darwin*) os=macos ;;
|
||||
MINGW* | MSYS* | CYGWIN*) os=windows ;;
|
||||
*)
|
||||
echo "Unknown OS: $(uname -s)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "Detected OS: ${os} ($(uname -s) $(uname -m))"
|
||||
echo
|
||||
echo "Core build tools:"
|
||||
check cmake
|
||||
check conan
|
||||
check git
|
||||
if [ "${os}" = "windows" ]; then
|
||||
check python python --version
|
||||
else
|
||||
check python3
|
||||
fi
|
||||
|
||||
# The full development toolchain. Available from Nix on Linux and macOS; on
|
||||
# Windows these are typically not installed, so they are skipped.
|
||||
if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then
|
||||
echo
|
||||
echo "Development tooling:"
|
||||
check ccache
|
||||
check clang
|
||||
check clang++
|
||||
check ClangBuildAnalyzer
|
||||
check curl
|
||||
check file
|
||||
check less
|
||||
check make
|
||||
check netstat which netstat
|
||||
check ninja
|
||||
check perl
|
||||
check pkg-config
|
||||
check vim
|
||||
|
||||
# These tools are present in our Linux CI images and in local development
|
||||
# setups, but not in the macOS CI environment. So check them everywhere
|
||||
# except when running in CI on macOS.
|
||||
if [ "${os}" = "linux" ] || [ -z "${CI:-}" ]; then
|
||||
check clang-format
|
||||
check doxygen
|
||||
check gcovr
|
||||
check gh
|
||||
check git-cliff
|
||||
check gpg
|
||||
# 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
|
||||
fi
|
||||
fi
|
||||
|
||||
# GCC is the default compiler on Linux. macOS uses the system Apple Clang
|
||||
# instead, so GCC/g++/gcov are not expected there.
|
||||
if [ "${os}" = "linux" ]; then
|
||||
echo
|
||||
echo "GCC toolchain:"
|
||||
check gcc
|
||||
check g++
|
||||
check gcov
|
||||
|
||||
echo
|
||||
echo "Mold:"
|
||||
check mold
|
||||
fi
|
||||
|
||||
if [ "${os}" = "windows" ]; then
|
||||
echo
|
||||
echo "Note: on Windows the C++ compiler is MSVC, which is provided"
|
||||
echo " separately (e.g. via Visual Studio) and is not checked here."
|
||||
fi
|
||||
|
||||
# A simple test to verify that git can clone a repository over HTTPS
|
||||
# (i.e. the CA bundle is wired up). Clone to a temp dir and clean up.
|
||||
if [ -n "${CHECK_TOOLS_SKIP_CLONE:-}" ]; then
|
||||
echo
|
||||
echo "Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set)."
|
||||
else
|
||||
echo
|
||||
echo "Connectivity check:"
|
||||
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'
|
||||
else
|
||||
printf ' [MISS] git clone over HTTPS\n'
|
||||
missing+=("git-https-clone")
|
||||
fi
|
||||
rm -rf "${tmp_clone}"
|
||||
fi
|
||||
|
||||
echo
|
||||
if [ "${#missing[@]}" -eq 0 ]; then
|
||||
echo "All ${checked} checked tools are present and runnable."
|
||||
else
|
||||
echo "Missing or non-functional tools (${#missing[@]} of ${checked}):" >&2
|
||||
for tool in "${missing[@]}"; do
|
||||
echo " - ${tool}" >&2
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
@@ -109,7 +109,6 @@ words:
|
||||
- enabled
|
||||
- enablerepo
|
||||
- endmacro
|
||||
- envrc
|
||||
- exceptioned
|
||||
- EXPECT_STREQ
|
||||
- Falco
|
||||
|
||||
193
docs/build/advanced_conan.md
vendored
193
docs/build/advanced_conan.md
vendored
@@ -1,193 +0,0 @@
|
||||
# Advanced Conan configuration
|
||||
|
||||
This document provides advanced instructions for setting up and configuring Conan for `xrpld` development: custom profiles, the lockfile, patched recipes, and profile tweaks.
|
||||
|
||||
## Custom profile
|
||||
|
||||
If the default profile does not work for you and you do not yet have a Conan
|
||||
profile, you can create one by running:
|
||||
|
||||
```bash
|
||||
conan profile detect
|
||||
```
|
||||
|
||||
You may need to make changes to the profile to suit your environment. You can
|
||||
refer to the provided `conan/profiles/default` profile for inspiration, and you
|
||||
may also need to apply the required [tweaks](#conan-profile-tweaks) to this
|
||||
default profile.
|
||||
|
||||
## Conan lockfile
|
||||
|
||||
To achieve reproducible dependencies, we use a [Conan lockfile](https://docs.conan.io/2/tutorial/versioning/lockfiles.html),
|
||||
which has to be updated every time dependencies change.
|
||||
|
||||
Please see the [instructions on how to regenerate the lockfile](../../conan/lockfile/README.md).
|
||||
|
||||
## Patched recipes
|
||||
|
||||
Occasionally, we need patched recipes or recipes not present in Conan Center.
|
||||
We maintain a fork of the Conan Center Index
|
||||
[here](https://github.com/XRPLF/conan-center-index/) containing the modified and newly added recipes.
|
||||
|
||||
To ensure our patched recipes are used, you must add our Conan remote at a
|
||||
higher index than the default Conan Center remote, so it is consulted first. You
|
||||
can do this by running:
|
||||
|
||||
```bash
|
||||
conan remote add --index 0 --force xrplf https://conan.ripplex.io
|
||||
```
|
||||
|
||||
Alternatively, you can pull our recipes from the repository and export them locally:
|
||||
|
||||
```bash
|
||||
# Define which recipes to export.
|
||||
recipes=('abseil' 'ed25519' 'mpt-crypto' 'openssl' 'secp256k1' 'snappy' 'soci' 'wasm-xrplf' 'wasmi')
|
||||
|
||||
# Selectively check out the recipes from our CCI fork.
|
||||
cd external
|
||||
mkdir -p conan-center-index
|
||||
cd conan-center-index
|
||||
git init
|
||||
git remote add origin git@github.com:XRPLF/conan-center-index.git
|
||||
git sparse-checkout init
|
||||
for recipe in "${recipes[@]}"; do
|
||||
echo "Checking out recipe '${recipe}'..."
|
||||
git sparse-checkout add recipes/${recipe}
|
||||
done
|
||||
git fetch origin master
|
||||
git checkout master
|
||||
|
||||
./export_all.sh
|
||||
cd ../../
|
||||
```
|
||||
|
||||
In the case we switch to a newer version of a dependency that still requires a
|
||||
patch or add a new dependency, it will be necessary for you to pull in the changes and re-export the
|
||||
updated dependencies with the newer version. However, if we switch to a newer
|
||||
version that no longer requires a patch, no action is required on your part, as
|
||||
the new recipe will be automatically pulled from the official Conan Center.
|
||||
|
||||
> [!NOTE]
|
||||
> You might need to add `--lockfile=""` to your `conan install` command
|
||||
> to avoid automatic use of the existing `conan.lock` file when you run
|
||||
> `conan export` manually on your machine
|
||||
>
|
||||
> This is not recommended though, as you might end up using different revisions of recipes.
|
||||
|
||||
## Conan profile tweaks
|
||||
|
||||
### Missing compiler version
|
||||
|
||||
If you see an error similar to the following after running `conan profile show`:
|
||||
|
||||
```text
|
||||
ERROR: Invalid setting '17' is not a valid 'settings.compiler.version' value.
|
||||
Possible values are ['5.0', '5.1', '6.0', '6.1', '7.0', '7.3', '8.0', '8.1',
|
||||
'9.0', '9.1', '10.0', '11.0', '12.0', '13', '13.0', '13.1', '14', '14.0', '15',
|
||||
'15.0', '16', '16.0']
|
||||
Read "http://docs.conan.io/2/knowledge/faq.html#error-invalid-setting"
|
||||
```
|
||||
|
||||
you need to create `$(conan config home)/settings_user.yml` file if it doesn't exist and add the required version number(s)
|
||||
to the `version` array specific for your compiler. For example:
|
||||
|
||||
```yaml
|
||||
compiler:
|
||||
apple-clang:
|
||||
version: ["17.0"]
|
||||
```
|
||||
|
||||
### Multiple compilers
|
||||
|
||||
If you have multiple compilers installed, make sure to select the one to use in
|
||||
your default Conan configuration **before** running `conan profile detect`, by
|
||||
setting the `CC` and `CXX` environment variables.
|
||||
|
||||
For example, if you are running MacOS and have [homebrew
|
||||
LLVM@18](https://formulae.brew.sh/formula/llvm@18), and want to use it as a
|
||||
compiler in the new Conan profile:
|
||||
|
||||
```bash
|
||||
export CC=$(brew --prefix llvm@18)/bin/clang
|
||||
export CXX=$(brew --prefix llvm@18)/bin/clang++
|
||||
conan profile detect
|
||||
```
|
||||
|
||||
You should also explicitly set the path to the compiler in the profile file,
|
||||
which helps to avoid errors when `CC` and/or `CXX` are set and disagree with the
|
||||
selected Conan profile. For example:
|
||||
|
||||
```text
|
||||
[conf]
|
||||
tools.build:compiler_executables={'c':'/usr/bin/gcc','cpp':'/usr/bin/g++'}
|
||||
```
|
||||
|
||||
### Multiple profiles
|
||||
|
||||
You can manage multiple Conan profiles in the directory
|
||||
`$(conan config home)/profiles`, for example renaming `default` to a different
|
||||
name and then creating a new `default` profile for a different compiler.
|
||||
|
||||
### Select language
|
||||
|
||||
The default profile created by Conan will typically select different C++ dialect
|
||||
than C++23 used by this project. You should set `23` in the profile line
|
||||
starting with `compiler.cppstd=`. For example:
|
||||
|
||||
```bash
|
||||
sed -i.bak -e 's|^compiler\.cppstd=.*$|compiler.cppstd=23|' $(conan config home)/profiles/default
|
||||
```
|
||||
|
||||
### Select standard library in Linux
|
||||
|
||||
**Linux** developers will commonly have a default Conan [profile][] that
|
||||
compiles with GCC and links with libstdc++. If you are linking with libstdc++
|
||||
(see profile setting `compiler.libcxx`), then you will need to choose the
|
||||
`libstdc++11` ABI:
|
||||
|
||||
```bash
|
||||
sed -i.bak -e 's|^compiler\.libcxx=.*$|compiler.libcxx=libstdc++11|' $(conan config home)/profiles/default
|
||||
```
|
||||
|
||||
### Select architecture and runtime in Windows
|
||||
|
||||
**Windows** developers may need to use the x64 native build tools. An easy way
|
||||
to do that is to run the shortcut "x64 Native Tools Command Prompt" for the
|
||||
version of Visual Studio that you have installed.
|
||||
|
||||
Windows developers must also build `xrpld` and its dependencies for the x64
|
||||
architecture:
|
||||
|
||||
```bash
|
||||
sed -i.bak -e 's|^arch=.*$|arch=x86_64|' $(conan config home)/profiles/default
|
||||
```
|
||||
|
||||
**Windows** developers also must select static runtime:
|
||||
|
||||
```bash
|
||||
sed -i.bak -e 's|^compiler\.runtime=.*$|compiler.runtime=static|' $(conan config home)/profiles/default
|
||||
```
|
||||
|
||||
## Add a Dependency
|
||||
|
||||
If you want to experiment with a new package, follow these steps:
|
||||
|
||||
1. Search for the package on [Conan Center](https://conan.io/center/).
|
||||
2. Modify [`conanfile.py`](../../conanfile.py):
|
||||
- Add a version of the package to the `requires` property.
|
||||
- Change any default options for the package by adding them to the
|
||||
`default_options` property (with syntax `'$package:$option': $value`).
|
||||
3. Regenerate the [Conan lockfile](../../conan/lockfile/README.md) so the new
|
||||
dependency is captured:
|
||||
|
||||
```bash
|
||||
./conan/lockfile/regenerate.sh
|
||||
```
|
||||
|
||||
4. Modify [`CMakeLists.txt`](../../CMakeLists.txt):
|
||||
- Add a call to `find_package($package REQUIRED)`.
|
||||
- Link a library from the package to the target `xrpl_libs`
|
||||
(search for the existing call to `target_link_libraries(xrpl_libs INTERFACE ...)`).
|
||||
5. Start coding! Don't forget to include whatever headers you need from the package.
|
||||
|
||||
[profile]: https://docs.conan.io/2/reference/config_files/profiles.html
|
||||
2
docs/build/conan.md
vendored
2
docs/build/conan.md
vendored
@@ -115,7 +115,7 @@ By default, Conan will use the profile named "default".
|
||||
[find_package]: https://cmake.org/cmake/help/latest/command/find_package.html
|
||||
[pcf]: https://cmake.org/cmake/help/latest/manual/cmake-packages.7.html#package-configuration-file
|
||||
[prefix_path]: https://cmake.org/cmake/help/latest/variable/CMAKE_PREFIX_PATH.html
|
||||
[profile]: https://docs.conan.io/2/reference/config_files/profiles.html
|
||||
[profile]: https://docs.conan.io/en/latest/reference/profiles.html
|
||||
[pvf]: https://cmake.org/cmake/help/latest/manual/cmake-packages.7.html#package-version-file
|
||||
[runtime]: https://cmake.org/cmake/help/latest/variable/CMAKE_MSVC_RUNTIME_LIBRARY.html
|
||||
[search]: https://cmake.org/cmake/help/latest/command/find_package.html#search-procedure
|
||||
|
||||
162
docs/build/environment.md
vendored
162
docs/build/environment.md
vendored
@@ -1,73 +1,69 @@
|
||||
Our [build instructions][BUILD.md] assume you have a C++ development
|
||||
environment complete with Git, Python, Conan, CMake, and a C++ compiler.
|
||||
This document explains how to set one up.
|
||||
This document exists to help readers set one up on any of the Big Three
|
||||
platforms: Linux, macOS, or Windows.
|
||||
|
||||
As an alternative to system packages, the Nix development shell can be used to provide a development environment. See [using nix development shell](./nix.md) for more details.
|
||||
|
||||
[BUILD.md]: ../../BUILD.md
|
||||
|
||||
## Tested compiler versions
|
||||
## Linux
|
||||
|
||||
`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:
|
||||
Package ecosystems vary across Linux distributions,
|
||||
so there is no one set of instructions that will work for every Linux user.
|
||||
The instructions below are written for Debian 12 (Bookworm).
|
||||
|
||||
| Compiler | Version |
|
||||
| ----------- | ------- |
|
||||
| GCC | 15.2 |
|
||||
| Clang | 22 |
|
||||
| Apple Clang | 17 |
|
||||
| MSVC | 19.44 |
|
||||
```
|
||||
export GCC_RELEASE=12
|
||||
sudo apt update
|
||||
sudo apt install --yes gcc-${GCC_RELEASE} g++-${GCC_RELEASE} python3-pip \
|
||||
python-is-python3 python3-venv python3-dev curl wget ca-certificates \
|
||||
git build-essential cmake ninja-build libc6-dev
|
||||
sudo pip install --break-system-packages conan
|
||||
|
||||
LLVM tools (`clang-tidy` and `clang-format`) are also pinned to version 22.
|
||||
|
||||
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.
|
||||
|
||||
## Linux and macOS
|
||||
|
||||
The **recommended way** to get a development environment on Linux and macOS is
|
||||
the Nix development shell. It provides the exact tooling used in CI — `git`,
|
||||
`python`, `conan`, `cmake`, `clang-tidy`, `clang-format`, and everything else —
|
||||
with a single command and without installing anything system-wide:
|
||||
|
||||
```bash
|
||||
nix --experimental-features 'nix-command flakes' develop
|
||||
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/gcc-${GCC_RELEASE} 999
|
||||
sudo update-alternatives --install \
|
||||
/usr/bin/gcc gcc /usr/bin/gcc-${GCC_RELEASE} 100 \
|
||||
--slave /usr/bin/g++ g++ /usr/bin/g++-${GCC_RELEASE} \
|
||||
--slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-${GCC_RELEASE} \
|
||||
--slave /usr/bin/gcc-nm gcc-nm /usr/bin/gcc-nm-${GCC_RELEASE} \
|
||||
--slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-${GCC_RELEASE} \
|
||||
--slave /usr/bin/gcov gcov /usr/bin/gcov-${GCC_RELEASE} \
|
||||
--slave /usr/bin/gcov-tool gcov-tool /usr/bin/gcov-tool-${GCC_RELEASE} \
|
||||
--slave /usr/bin/gcov-dump gcov-dump /usr/bin/gcov-dump-${GCC_RELEASE} \
|
||||
--slave /usr/bin/lto-dump lto-dump /usr/bin/lto-dump-${GCC_RELEASE}
|
||||
sudo update-alternatives --auto cc
|
||||
sudo update-alternatives --auto gcc
|
||||
```
|
||||
|
||||
On **Linux**, Nix also provides the compiler (GCC). On **macOS**, the shell uses
|
||||
your **system-wide Apple Clang** as the compiler, so you still need to manage
|
||||
its version (see below).
|
||||
If you use different Linux distribution, hope the instruction above can guide
|
||||
you in the right direction. We try to maintain compatibility with all recent
|
||||
compiler releases, so if you use a rolling distribution like e.g. Arch or CentOS
|
||||
then there is a chance that everything will "just work".
|
||||
|
||||
See [Using the Nix development shell](./nix.md) for installation and usage
|
||||
details, including how to select a different compiler.
|
||||
## macOS
|
||||
|
||||
> [!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.
|
||||
Open a Terminal and enter the below command to bring up a dialog to install
|
||||
the command line developer tools.
|
||||
Once it is finished, this command should return a version greater than the
|
||||
minimum required (see [BUILD.md][]).
|
||||
|
||||
### macOS: managing the Apple Clang version
|
||||
|
||||
Because the Nix shell uses the system-wide Apple Clang on macOS, 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):
|
||||
|
||||
```bash
|
||||
```
|
||||
clang --version
|
||||
```
|
||||
|
||||
If you develop other applications using Xcode, you might be consistently
|
||||
updating to the newest version of Apple Clang, which will likely cause issues
|
||||
building xrpld. You may want to install and pin a specific version of Xcode:
|
||||
### Install Xcode Specific Version (Optional)
|
||||
|
||||
If you develop other applications using XCode you might be consistently updating to the newest version of Apple Clang.
|
||||
This will likely cause issues building xrpld. You may want to install a specific version of Xcode:
|
||||
|
||||
1. **Download Xcode**
|
||||
- Visit [Apple Developer Downloads](https://developer.apple.com/download/more/)
|
||||
- Sign in with your Apple Developer account
|
||||
- Search for an Xcode version that includes the expected Apple Clang version
|
||||
- Search for an Xcode version that includes **Apple Clang (Expected Version)**
|
||||
- Download the `.xip` file
|
||||
|
||||
2. **Install and configure Xcode**
|
||||
2. **Install and Configure Xcode**
|
||||
|
||||
```bash
|
||||
# Extract the .xip file and rename for version management
|
||||
@@ -83,28 +79,62 @@ building xrpld. You may want to install and pin a specific version of Xcode:
|
||||
export DEVELOPER_DIR=/Applications/Xcode_16.2.app/Contents/Developer
|
||||
```
|
||||
|
||||
## Windows
|
||||
The command line developer tools should include Git too:
|
||||
|
||||
Nix is not available on Windows, so the required tools have to be installed
|
||||
manually:
|
||||
```
|
||||
git --version
|
||||
```
|
||||
|
||||
- [Visual Studio 2022](https://visualstudio.microsoft.com/) with the
|
||||
**"Desktop development with C++"** workload — this provides MSVC and the
|
||||
"x64 Native Tools Command Prompt".
|
||||
- [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
|
||||
Install [Homebrew][],
|
||||
use it to install [pyenv][],
|
||||
use it to install Python,
|
||||
and use it to install Conan:
|
||||
|
||||
> [!NOTE]
|
||||
> Windows is used for development only and is not recommended for production.
|
||||
[Homebrew]: https://brew.sh/
|
||||
[pyenv]: https://github.com/pyenv/pyenv
|
||||
|
||||
```
|
||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
brew update
|
||||
brew install xz
|
||||
brew install pyenv
|
||||
pyenv install 3.11
|
||||
pyenv global 3.11
|
||||
eval "$(pyenv init -)"
|
||||
pip install 'conan'
|
||||
```
|
||||
|
||||
Install CMake with Homebrew too:
|
||||
|
||||
```
|
||||
brew install cmake
|
||||
```
|
||||
|
||||
## 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.
|
||||
Clang-tidy is required to run static analysis checks locally (see [CONTRIBUTING.md](../../CONTRIBUTING.md)).
|
||||
It is not required to build the project. Currently this project uses clang-tidy version 21.
|
||||
|
||||
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.
|
||||
### Linux
|
||||
|
||||
LLVM 21 is not available in the default Debian 12 (Bookworm) repositories.
|
||||
Install it using the official LLVM apt installer:
|
||||
|
||||
```
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 21
|
||||
sudo apt install --yes clang-tidy-21
|
||||
```
|
||||
|
||||
Then use `run-clang-tidy-21` when running clang-tidy locally.
|
||||
|
||||
### macOS
|
||||
|
||||
Install LLVM 21 via Homebrew:
|
||||
|
||||
```
|
||||
brew install llvm@21
|
||||
```
|
||||
|
||||
Then use `run-clang-tidy` from the LLVM 21 Homebrew prefix when running clang-tidy locally.
|
||||
|
||||
45
docs/build/nix.md
vendored
45
docs/build/nix.md
vendored
@@ -2,12 +2,9 @@
|
||||
|
||||
This guide explains how to use Nix to set up a reproducible development environment for xrpld. Using Nix eliminates the need to manually install utilities and ensures consistent tooling across different machines.
|
||||
|
||||
**The Nix development shell is the recommended way to develop xrpld.** It unifies the development environment for everyone and synchronizes updates: the same tooling and compiler versions are used both here and in CI. 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.
|
||||
|
||||
## 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
|
||||
- **No system pollution**: Dependencies are isolated and don't affect your system packages
|
||||
- **Multiple compiler versions**: Easily switch between different GCC and Clang versions
|
||||
- **Quick setup**: Get started with a single command
|
||||
@@ -31,22 +28,11 @@ This will:
|
||||
|
||||
- Download and set up all required development tools (CMake, Ninja, Conan, etc.)
|
||||
- Configure the appropriate compiler for your platform:
|
||||
- **Linux**: GCC 15.2 (provided by Nix)
|
||||
- **macOS**: Apple Clang (your system compiler)
|
||||
- **macOS**: Apple Clang (default system compiler)
|
||||
- **Linux**: GCC 15
|
||||
|
||||
The first time you run this command, it will take a few minutes to download and build the environment. Subsequent runs will be much faster.
|
||||
|
||||
### Platform notes
|
||||
|
||||
- **Linux**: `nix develop` gives you a shell with all the tooling necessary to
|
||||
develop xrpld and with GCC 15.2 (also provided by Nix). There are no caveats.
|
||||
- **macOS**: `nix develop` gives you a full environment too. The compiler is
|
||||
your system-wide Apple Clang, while every other tool — including Conan — is
|
||||
provided by Nix. Conan has no binary in the Nix cache for macOS, so it is
|
||||
built from source the first time you enter the shell, which makes the initial
|
||||
setup slower (this is handled automatically; see
|
||||
[`nix/devshell.nix`](../../nix/devshell.nix)).
|
||||
|
||||
> [!TIP]
|
||||
> To avoid typing `--experimental-features 'nix-command flakes'` every time, you can permanently enable flakes by creating `~/.config/nix/nix.conf`:
|
||||
>
|
||||
@@ -65,7 +51,7 @@ The first time you run this command, it will take a few minutes to download and
|
||||
A compiler can be chosen by providing its name with the `.#` prefix, e.g. `nix develop .#gcc15`.
|
||||
Use `nix flake show` to see all the available development shells.
|
||||
|
||||
Use `nix develop .#no-compiler` to use the compiler from your system.
|
||||
Use `nix develop .#no_compiler` to use the compiler from your system.
|
||||
|
||||
### Example Usage
|
||||
|
||||
@@ -82,28 +68,12 @@ nix develop
|
||||
|
||||
### Using a different shell
|
||||
|
||||
`nix develop` opens bash by default. To use another shell, pass it with the `-c` flag — this works with any shell, e.g. `zsh` or `fish`:
|
||||
`nix develop` opens bash by default. If you want to use another shell this could be done by adding `-c` flag. For example:
|
||||
|
||||
```bash
|
||||
# Use zsh
|
||||
nix develop -c zsh
|
||||
|
||||
# Use fish
|
||||
nix develop -c fish
|
||||
|
||||
# Use your login shell
|
||||
nix develop -c "$SHELL"
|
||||
```
|
||||
|
||||
> [!WARNING]
|
||||
> Your shell's interactive startup files (e.g. `config.fish`, `.zshrc`) may prepend other directories — most commonly Homebrew — to `$PATH`, which can shadow the tools provided by the Nix shell. After entering, verify that tools resolve into the Nix store:
|
||||
>
|
||||
> ```bash
|
||||
> command -v cmake # should print a /nix/store/... path
|
||||
> ```
|
||||
>
|
||||
> 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
|
||||
|
||||
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.).
|
||||
@@ -112,8 +82,6 @@ Once inside the Nix development shell, follow the standard [build instructions](
|
||||
|
||||
[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.
|
||||
|
||||
This is also the most robust way to use the environment from **any shell** (bash, zsh, fish, …): direnv stays in your current shell and loads the environment _after_ your shell's startup files have run, so the Nix-provided tools take precedence over anything your shell configuration adds to `$PATH`. To use it, install direnv for your shell, then add an `.envrc` containing `use flake` at the repository root and run `direnv allow`.
|
||||
|
||||
## 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:
|
||||
@@ -125,8 +93,3 @@ 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.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
See [Troubleshooting Nix problems](./nix_troubleshooting.md) for common issues,
|
||||
such as `nix develop` failing inside Git worktrees.
|
||||
|
||||
61
docs/build/nix_troubleshooting.md
vendored
61
docs/build/nix_troubleshooting.md
vendored
@@ -1,61 +0,0 @@
|
||||
# Troubleshooting Nix problems
|
||||
|
||||
Common issues encountered when using the [Nix development shell](./nix.md), and
|
||||
how to resolve them.
|
||||
|
||||
## Git worktrees
|
||||
|
||||
If `nix develop` fails with an error like:
|
||||
|
||||
```
|
||||
error:
|
||||
… while fetching the input 'git+file:///path/to/rippled'
|
||||
|
||||
error: opening Git repository "/path/to/rippled": unsupported extension name extensions.relativeworktrees (libgit2 error code = 6)
|
||||
```
|
||||
|
||||
then your Nix is linked against a libgit2 older than **1.9.4**. Git 2.48+ writes
|
||||
the `extensions.relativeWorktrees` config entry when a worktree is created with
|
||||
relative paths (`git worktree add --relative-paths`, or with
|
||||
`worktree.useRelativePaths=true`), and older libgit2 versions refuse to open a
|
||||
repository that uses it. Nix uses libgit2 to read the flake, so evaluation
|
||||
fails.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This entry is written to the **shared** repository config, so once any
|
||||
> relative worktree exists, `nix develop` fails in the main checkout too — not
|
||||
> just inside the worktree.
|
||||
|
||||
### Workarounds
|
||||
|
||||
These work today, with any Nix version:
|
||||
|
||||
- bypass libgit2 with a `path:` flakeref: `nix develop "path:$PWD"`
|
||||
(note: this copies the working tree to the store and ignores `.gitignore`); or
|
||||
- create worktrees with absolute paths (omit `--relative-paths`); or
|
||||
- clear the extension if you don't need relative worktrees:
|
||||
`git config --unset extensions.relativeWorktrees`.
|
||||
|
||||
### Permanent fix
|
||||
|
||||
The fix is in [libgit2 1.9.4](https://github.com/libgit2/libgit2/releases/tag/v1.9.4),
|
||||
so the real solution is a Nix that links against libgit2 `1.9.4` or newer. Check
|
||||
which version yours links against:
|
||||
|
||||
```bash
|
||||
nix-store -qR "$(readlink -f "$(command -v nix)")" | grep libgit2
|
||||
```
|
||||
|
||||
> [!WARNING]
|
||||
> `nix upgrade-nix` does **not** help yet. It installs the build from the
|
||||
> official [`nix-fallback-paths`](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/installer/tools/nix-fallback-paths.nix),
|
||||
> which is still linked against libgit2 `1.9.2` — there is no new upstream Nix
|
||||
> release with the fix. (On some systems that build is even the exact store path
|
||||
> you already have, making the upgrade a no-op.)
|
||||
|
||||
nixpkgs has already rebuilt Nix against the fixed libgit2 (e.g. `nix-2.34.7+1`),
|
||||
so the cleanest path is to reinstall Nix using your usual installation method
|
||||
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.
|
||||
13
flake.lock
generated
13
flake.lock
generated
@@ -2,18 +2,17 @@
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1781173989,
|
||||
"narHash": "sha256-fnzKKPvS+oieI/pTzotA5tkoM47EB1NpaBcgk4R97hE=",
|
||||
"lastModified": 1780749050,
|
||||
"narHash": "sha256-3av0pIjlOWQ6rDbNOmpUSvbNnJkGORQKKjb4LtCZsIY=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "8c91a71d13451abc40eb9dae8910f972f979852f",
|
||||
"rev": "a799d3e3886da994fa307f817a6bc705ae538eeb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixos-unstable",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"nixpkgs-custom-glibc": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
description = "Nix related things for xrpld";
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
nixpkgs.url = "nixpkgs/nixos-unstable";
|
||||
# nixpkgs snapshot (2020-06-30) that shipped glibc 2.31 as the primary
|
||||
# version — matches the system libc on Ubuntu 20.04 LTS. Imported
|
||||
# manually (flake = false) because this revision predates nixpkgs'
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <xrpl/basics/IntrusivePointer.ipp>
|
||||
#include <xrpl/basics/TaggedCache.h>
|
||||
#include <xrpl/basics/scope.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -537,15 +536,8 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
|
||||
std::vector<key_type> v;
|
||||
|
||||
{
|
||||
std::unique_lock lock(mutex_);
|
||||
for (auto size = cache_.size(); v.capacity() < size; size = cache_.size())
|
||||
{
|
||||
ScopeUnlock const unlock(lock);
|
||||
v.reserve(size);
|
||||
}
|
||||
XRPL_ASSERT(lock.owns_lock(), "xrpl::TaggedCache::getKeys(): owns lock");
|
||||
XRPL_ASSERT(
|
||||
v.capacity() >= cache_.size(), "xrpl::TaggedCache::getKeys(): sufficient capacity");
|
||||
std::scoped_lock const lock(mutex_);
|
||||
v.reserve(cache_.size());
|
||||
for (auto const& _ : cache_)
|
||||
v.push_back(_.first);
|
||||
}
|
||||
|
||||
29
include/xrpl/basics/rocksdb.h
Normal file
29
include/xrpl/basics/rocksdb.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#if XRPL_ROCKSDB_AVAILABLE
|
||||
// #include <rocksdb2/port/port_posix.h>
|
||||
#include <rocksdb/cache.h>
|
||||
#include <rocksdb/compaction_filter.h>
|
||||
#include <rocksdb/comparator.h>
|
||||
#include <rocksdb/convenience.h>
|
||||
#include <rocksdb/db.h>
|
||||
#include <rocksdb/env.h>
|
||||
#include <rocksdb/filter_policy.h>
|
||||
#include <rocksdb/flush_block_policy.h>
|
||||
#include <rocksdb/iterator.h>
|
||||
#include <rocksdb/memtablerep.h>
|
||||
#include <rocksdb/merge_operator.h>
|
||||
#include <rocksdb/options.h>
|
||||
#include <rocksdb/perf_context.h>
|
||||
#include <rocksdb/slice.h>
|
||||
#include <rocksdb/slice_transform.h>
|
||||
#include <rocksdb/statistics.h>
|
||||
#include <rocksdb/status.h>
|
||||
#include <rocksdb/table.h>
|
||||
#include <rocksdb/table_properties.h>
|
||||
#include <rocksdb/transaction_log.h>
|
||||
#include <rocksdb/types.h>
|
||||
#include <rocksdb/universal_compaction.h>
|
||||
#include <rocksdb/write_batch.h>
|
||||
|
||||
#endif
|
||||
@@ -4,7 +4,7 @@
|
||||
/*
|
||||
ASAN flags some false positives with sudden jumps in control flow, like
|
||||
exceptions, or when encountering coroutine stack switches. This macro can be used to disable ASAN
|
||||
instrumentation for specific functions.
|
||||
intrumentation for specific functions.
|
||||
*/
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define XRPL_NO_SANITIZE_ADDRESS __attribute__((no_sanitize("address", "hwaddress")))
|
||||
|
||||
49
include/xrpl/ledger/BookListeners.h
Normal file
49
include/xrpl/ledger/BookListeners.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/MultiApiJson.h>
|
||||
#include <xrpl/server/InfoSub.h>
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/** Listen to public/subscribe messages from a book. */
|
||||
class BookListeners
|
||||
{
|
||||
public:
|
||||
using pointer = std::shared_ptr<BookListeners>;
|
||||
|
||||
BookListeners() = default;
|
||||
|
||||
/** Add a new subscription for this book
|
||||
*/
|
||||
void
|
||||
addSubscriber(InfoSub::ref sub);
|
||||
|
||||
/** Stop publishing to a subscriber
|
||||
*/
|
||||
void
|
||||
removeSubscriber(std::uint64_t sub);
|
||||
|
||||
/** Publish a transaction to subscribers
|
||||
|
||||
Publish a transaction to clients subscribed to changes on this book.
|
||||
Uses havePublished to prevent sending duplicate transactions to clients
|
||||
that have subscribed to multiple books.
|
||||
|
||||
@param jvObj JSON transaction data to publish
|
||||
@param havePublished InfoSub sequence numbers that have already
|
||||
published this transaction.
|
||||
|
||||
*/
|
||||
void
|
||||
publish(MultiApiJson const& jvObj, hash_set<std::uint64_t>& havePublished);
|
||||
|
||||
private:
|
||||
std::recursive_mutex lock_;
|
||||
|
||||
hash_map<std::uint64_t, InfoSub::wptr> listeners_;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,11 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/UnorderedContainers.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/AcceptedLedgerTx.h>
|
||||
#include <xrpl/ledger/BookListeners.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Book.h>
|
||||
#include <xrpl/protocol/MultiApiJson.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
|
||||
#include <memory>
|
||||
@@ -77,24 +77,34 @@ public:
|
||||
*/
|
||||
virtual bool
|
||||
isBookToXRP(Asset const& asset, std::optional<Domain> const& domain = std::nullopt) = 0;
|
||||
|
||||
/**
|
||||
* Process a transaction for order book tracking.
|
||||
* @param ledger The ledger the transaction was applied to
|
||||
* @param alTx The transaction to process
|
||||
* @param jvObj The JSON object of the transaction
|
||||
*/
|
||||
virtual void
|
||||
processTxn(
|
||||
std::shared_ptr<ReadView const> const& ledger,
|
||||
AcceptedLedgerTx const& alTx,
|
||||
MultiApiJson const& jvObj) = 0;
|
||||
|
||||
/**
|
||||
* Get the book listeners for a book.
|
||||
* @param book The book to get the listeners for
|
||||
* @return The book listeners for the book
|
||||
*/
|
||||
virtual BookListeners::pointer
|
||||
getBookListeners(Book const&) = 0;
|
||||
|
||||
/**
|
||||
* Create a new book listeners for a book.
|
||||
* @param book The book to create the listeners for
|
||||
* @return The new book listeners for the book
|
||||
*/
|
||||
virtual BookListeners::pointer
|
||||
makeBookListeners(Book const&) = 0;
|
||||
};
|
||||
|
||||
/** Extract the set of books affected by a transaction.
|
||||
*
|
||||
* Walks the transaction's metadata nodes and collects every order book
|
||||
* whose offers were created, modified, or deleted. Used by NetworkOPs to
|
||||
* fan transaction notifications out to book subscribers.
|
||||
*
|
||||
* @param alTx The accepted ledger transaction to inspect.
|
||||
* @param j Journal used to log per-node parsing failures. Inspecting an
|
||||
* offer node can throw if a required field is missing; in that
|
||||
* case the bad node is skipped and a warn-level message is
|
||||
* emitted via @p j. Other affected books in the same transaction
|
||||
* are still returned.
|
||||
* @return The set of books whose offers were created, modified, or
|
||||
* deleted. May be empty for non-offer transactions.
|
||||
*/
|
||||
hash_set<Book>
|
||||
affectedBooks(AcceptedLedgerTx const& alTx, beast::Journal const& j);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -36,13 +36,13 @@ checkFields(STTx const& tx, beast::Journal j);
|
||||
TER
|
||||
valid(STTx const& tx, ReadView const& view, AccountID const& src, beast::Journal j);
|
||||
|
||||
// Check if subject has any credential matching the given domain. If you call it
|
||||
// Check if subject has any credential maching the given domain. If you call it
|
||||
// in preclaim and it returns tecEXPIRED, you should call verifyValidDomain in
|
||||
// doApply. This will ensure that expired credentials are deleted.
|
||||
TER
|
||||
validDomain(ReadView const& view, uint256 domainID, AccountID const& subject);
|
||||
|
||||
// This function is only called when we are about to return tecNO_PERMISSION
|
||||
// This function is only called when we about to return tecNO_PERMISSION
|
||||
// because all the checks for the DepositPreauth authorization failed.
|
||||
TER
|
||||
authorizedDepositPreauth(ReadView const& view, STVector256 const& ctx, AccountID const& dst);
|
||||
@@ -58,7 +58,7 @@ checkArray(STArray const& credentials, unsigned maxSize, beast::Journal j);
|
||||
|
||||
} // namespace credentials
|
||||
|
||||
// Check expired credentials and for credentials matching DomainID of the ledger
|
||||
// Check expired credentials and for credentials maching DomainID of the ledger
|
||||
// object
|
||||
TER
|
||||
verifyValidDomain(ApplyView& view, AccountID const& account, uint256 domainID, beast::Journal j);
|
||||
|
||||
@@ -5,45 +5,9 @@
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/** Close a payment channel and return its remaining funds to the channel owner.
|
||||
*
|
||||
* @param slep The SLE for the PayChannel object to close.
|
||||
* @param view The apply view in which ledger state modifications are made.
|
||||
* @param key The ledger key identifying the PayChannel entry.
|
||||
* @param j Journal used for fatal-level diagnostic messages.
|
||||
* @return tesSUCCESS on success; tefBAD_LEDGER if a directory removal
|
||||
* fails; tefINTERNAL if the source account SLE cannot be found.
|
||||
*/
|
||||
TER
|
||||
closeChannel(SLE::ref slep, ApplyView& view, uint256 const& key, beast::Journal j);
|
||||
|
||||
/** Add two uint32_t values with saturation at UINT32_MAX.
|
||||
*
|
||||
* @param rules The current ledger rules used to check amendment status.
|
||||
* @param lhs Left-hand operand.
|
||||
* @param rhs Right-hand operand.
|
||||
* @return @p lhs + @p rhs, saturated at UINT32_MAX when the amendment
|
||||
* is active.
|
||||
*/
|
||||
uint32_t
|
||||
saturatingAdd(Rules const& rules, uint32_t const lhs, uint32_t const rhs);
|
||||
|
||||
/** Determine whether a payment channel time field represents an expired time.
|
||||
*
|
||||
* @param view The apply view providing the parent close time and rules.
|
||||
* @param timeField The optional expiry timestamp (seconds since the XRP
|
||||
* Ledger epoch). If empty, the function returns false.
|
||||
* @return @c true if @p timeField is set and the indicated time is
|
||||
* in the past relative to the view's parent close time;
|
||||
* @c false otherwise.
|
||||
*/
|
||||
bool
|
||||
isChannelExpired(ApplyView const& view, std::optional<std::uint32_t> timeField);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -102,32 +102,25 @@ getAPIVersionNumber(json::Value const& jv, bool betaEnabled)
|
||||
json::Value const maxVersion(
|
||||
betaEnabled ? RPC::kApiBetaVersion : RPC::kApiMaximumSupportedVersion);
|
||||
|
||||
if (!jv.isObject() || !jv.isMember(jss::api_version))
|
||||
return RPC::kApiVersionIfUnspecified;
|
||||
|
||||
try
|
||||
if (jv.isObject())
|
||||
{
|
||||
auto const& rawVersion = jv[jss::api_version];
|
||||
switch (rawVersion.type())
|
||||
if (jv.isMember(jss::api_version))
|
||||
{
|
||||
case json::ValueType::Int:
|
||||
if (rawVersion.asInt() < 0)
|
||||
return RPC::kApiInvalidVersion;
|
||||
[[fallthrough]];
|
||||
case json::ValueType::UInt: {
|
||||
auto const apiVersion = rawVersion.asUInt();
|
||||
if (apiVersion < kMinVersion || apiVersion > maxVersion)
|
||||
return RPC::kApiInvalidVersion;
|
||||
return apiVersion;
|
||||
}
|
||||
default:
|
||||
auto const specifiedVersion = jv[jss::api_version];
|
||||
if (!specifiedVersion.isInt() && !specifiedVersion.isUInt())
|
||||
{
|
||||
return RPC::kApiInvalidVersion;
|
||||
}
|
||||
auto const specifiedVersionInt = specifiedVersion.asInt();
|
||||
if (specifiedVersionInt < kMinVersion || specifiedVersionInt > maxVersion)
|
||||
{
|
||||
return RPC::kApiInvalidVersion;
|
||||
}
|
||||
return specifiedVersionInt;
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return RPC::kApiInvalidVersion;
|
||||
}
|
||||
|
||||
return RPC::kApiVersionIfUnspecified;
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// Add new amendments to the top of this list.
|
||||
// Keep it sorted in reverse chronological order.
|
||||
|
||||
XRPL_FEATURE(DefragDirectories, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (Cleanup3_3_0, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (Cleanup3_2_0, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(MPTokensV2, Supported::No, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/CountedObject.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/Book.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
@@ -27,19 +26,6 @@ public:
|
||||
};
|
||||
|
||||
/** Manages a client's subscription to data feeds.
|
||||
*
|
||||
* An InfoSub holds a non-owning reference to its `Source` (typically the
|
||||
* process-wide `NetworkOPsImp`). The destructor reaches back into the
|
||||
* `Source` to remove this subscriber from every server-side subscription
|
||||
* map.
|
||||
*
|
||||
* @note Lifetime contract: every `InfoSub` instance MUST be destroyed
|
||||
* before the backing `Source`. NetworkOPsImp shutdown drops all
|
||||
* subscriber strong refs before its own teardown to satisfy this.
|
||||
* @note Thread-safety: per-instance state is guarded by `lock_`. The
|
||||
* destructor reads tracking sets without taking `lock_` because
|
||||
* the strong-pointer ref-count is zero at destruction time, so
|
||||
* no other thread can be calling the public mutators.
|
||||
*/
|
||||
class InfoSub : public CountedObject<InfoSub>
|
||||
{
|
||||
@@ -131,43 +117,8 @@ public:
|
||||
|
||||
virtual bool
|
||||
subBook(ref ispListener, Book const&) = 0;
|
||||
|
||||
/**
|
||||
* Remove a book subscription for a live subscriber.
|
||||
*
|
||||
* Clears the book from the subscriber's own tracking set
|
||||
* (InfoSub::bookSubscriptions_) and then removes the server-side
|
||||
* entry from subBook_. Call this from RPC unsubscribe handlers.
|
||||
*
|
||||
* @param ispListener The subscriber requesting removal.
|
||||
* @param book The order book to unsubscribe from.
|
||||
* @return true if the entry was present and removed, false if the
|
||||
* subscriber was not subscribed to @p book.
|
||||
*
|
||||
* @note Thread-safety: acquires subLock_ internally.
|
||||
* @note Do NOT call from ~InfoSub(). Use unsubBookInternal instead
|
||||
* to avoid a redundant write-back to bookSubscriptions_ on a
|
||||
* partially-destroyed object.
|
||||
*/
|
||||
virtual bool
|
||||
unsubBook(ref ispListener, Book const&) = 0;
|
||||
|
||||
/**
|
||||
* Remove a book subscription during InfoSub teardown.
|
||||
*
|
||||
* Removes only the server-side entry from subBook_. Does NOT touch
|
||||
* InfoSub::bookSubscriptions_ because the InfoSub is being destroyed.
|
||||
* Called by ~InfoSub() for each book in bookSubscriptions_.
|
||||
*
|
||||
* @param uListener The sequence number of the subscriber being torn down.
|
||||
* @param book The order book entry to remove.
|
||||
* @return true if the entry was present and removed, false otherwise
|
||||
* (e.g., already removed by a concurrent RPC unsubscribe).
|
||||
*
|
||||
* @note Thread-safety: acquires subLock_ internally.
|
||||
*/
|
||||
virtual bool
|
||||
unsubBookInternal(std::uint64_t uListener, Book const&) = 0;
|
||||
unsubBook(std::uint64_t uListener, Book const&) = 0;
|
||||
|
||||
virtual bool
|
||||
subTransactions(ref ispListener) = 0;
|
||||
@@ -207,13 +158,6 @@ public:
|
||||
addRpcSub(std::string const& strUrl, ref rspEntry) = 0;
|
||||
virtual bool
|
||||
tryRemoveRpcSub(std::string const& strUrl) = 0;
|
||||
|
||||
/** Journal used by InfoSub for diagnostics that occur after the
|
||||
* owning subsystem (e.g. application-level Logs) is the only
|
||||
* surviving sink — primarily destructor-time cleanup failures.
|
||||
*/
|
||||
[[nodiscard]] virtual beast::Journal const&
|
||||
journal() const = 0;
|
||||
};
|
||||
|
||||
public:
|
||||
@@ -240,31 +184,6 @@ public:
|
||||
void
|
||||
deleteSubAccountInfo(AccountID const& account, bool rt);
|
||||
|
||||
/** Record that this subscriber is following @p book.
|
||||
*
|
||||
* Called by NetworkOPsImp::subBook so that ~InfoSub() can issue a
|
||||
* matching unsubBook for every book this subscriber is tracking,
|
||||
* keeping per-subscriber state symmetric with the server-side map.
|
||||
*
|
||||
* @param book The order book this subscriber has just subscribed to.
|
||||
* @note Idempotent: re-inserting an already-tracked book is a no-op.
|
||||
* @note Thread-safe: takes InfoSub::lock_.
|
||||
*/
|
||||
void
|
||||
insertBookSubscription(Book const& book);
|
||||
|
||||
/** Stop tracking @p book for this subscriber.
|
||||
*
|
||||
* Called by the unsubscribe RPC handler so that the book is not
|
||||
* re-unsubscribed by ~InfoSub(). Pairs with insertBookSubscription.
|
||||
*
|
||||
* @param book The order book to forget.
|
||||
* @note No-op if @p book was not previously inserted.
|
||||
* @note Thread-safe: takes InfoSub::lock_.
|
||||
*/
|
||||
void
|
||||
deleteBookSubscription(Book const& book);
|
||||
|
||||
// return false if already subscribed to this account
|
||||
bool
|
||||
insertSubAccountHistory(AccountID const& account);
|
||||
@@ -298,7 +217,6 @@ private:
|
||||
std::shared_ptr<InfoSubRequest> request_;
|
||||
std::uint64_t seq_;
|
||||
hash_set<AccountID> accountHistorySubscriptions_;
|
||||
hash_set<Book> bookSubscriptions_;
|
||||
unsigned int apiVersion_ = 0;
|
||||
|
||||
static int
|
||||
|
||||
@@ -249,19 +249,6 @@ public:
|
||||
|
||||
virtual void
|
||||
stateAccounting(json::Value& obj) = 0;
|
||||
|
||||
/** Total number of (book, subscriber) entries currently tracked.
|
||||
*
|
||||
* Counts every weak_ptr stored across every book in subBook_, NOT the
|
||||
* number of distinct subscribers and NOT the number of distinct
|
||||
* books: a single subscriber following N books contributes N entries.
|
||||
*
|
||||
* @note Diagnostic accessor; intended for tests and operator visibility
|
||||
* into per-book subscription state. The returned value is a
|
||||
* snapshot under the subscription lock.
|
||||
*/
|
||||
virtual std::size_t
|
||||
getBookSubscribersCount() = 0;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -1,6 +1,26 @@
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
inherit (import ./packages.nix { inherit pkgs; }) commonPackages;
|
||||
# conan is in the binary cache for Linux but not for Darwin, so on Darwin
|
||||
# it is always built from source — and its bundled test suite is unreliable
|
||||
# in the sandbox: `test_qbsprofile_rcflags` needs gcc (absent on Darwin, see
|
||||
# https://github.com/NixOS/nixpkgs/pull/528995) and the patch tests are
|
||||
# flaky from source. We only use conan as a build tool, so skip its tests on
|
||||
# Darwin. Scoped to the dev shell (not the CI env, which builds conan on
|
||||
# Linux from the cache). Drop once the fix reaches nixos-unstable and the
|
||||
# lock is bumped.
|
||||
pkgs_patched =
|
||||
if pkgs.stdenv.isDarwin then
|
||||
pkgs.extend (
|
||||
final: prev: {
|
||||
conan = prev.conan.overridePythonAttrs (_: {
|
||||
doCheck = false;
|
||||
});
|
||||
}
|
||||
)
|
||||
else
|
||||
pkgs;
|
||||
|
||||
inherit (import ./packages.nix { pkgs = pkgs_patched; }) commonPackages;
|
||||
|
||||
# Supported compiler versions
|
||||
gccVersion = pkgs.lib.range 13 15;
|
||||
|
||||
@@ -71,7 +71,7 @@ if [ ! -e "${target}" ]; then
|
||||
fi
|
||||
EOF
|
||||
|
||||
COPY bin/check-tools.sh /tmp/check-tools.sh
|
||||
COPY nix/docker/check-tools.sh /tmp/check-tools.sh
|
||||
RUN /tmp/check-tools.sh
|
||||
|
||||
# Sanity-check that the g++/clang++ are able to build binaries, including sanitizer-instrumented ones.
|
||||
@@ -93,7 +93,7 @@ RUN if echo "${BASE_IMAGE}" | grep -qiE 'nixos'; then \
|
||||
SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
|
||||
|
||||
# Sanity-check that the built binaries run correctly in the vanilla base image, with the necessary sanitizer runtime libraries installed.
|
||||
COPY bin/install-sanitizer-libs.sh /tmp/install-sanitizer-libs.sh
|
||||
COPY nix/docker/install-sanitizer-libs.sh /tmp/install-sanitizer-libs.sh
|
||||
COPY nix/docker/test_files/run-test-binaries.sh /tmp/run-test-binaries.sh
|
||||
COPY --from=final /tmp/bins /tmp/bins
|
||||
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
# Nix CI Docker images
|
||||
|
||||
This directory builds the Docker images used by xrpld's Linux CI. Each image
|
||||
bundles the **exact same toolchain that the Nix development shell provides**
|
||||
(see [`docs/build/nix.md`](../../docs/build/nix.md)), so what runs in CI matches
|
||||
what developers get locally from `nix develop`.
|
||||
|
||||
The toolchain (CMake, Ninja, Conan, GCC, Clang, clang-tidy, the
|
||||
sanitizer/coverage tools, …) is defined in [`nix/packages.nix`](../packages.nix)
|
||||
and assembled for CI by [`nix/ci-env.nix`](../ci-env.nix). The Docker build
|
||||
turns that Nix environment into an ordinary container image layered on top of a
|
||||
conventional base image (Ubuntu, Debian, RHEL, or `nixos/nix`).
|
||||
|
||||
## Images
|
||||
|
||||
The images are built by the [`build-nix-images.yml`](../../.github/workflows/build-nix-images.yml)
|
||||
workflow and pushed to `ghcr.io/xrplf/xrpld/nix-<distro>`. The `<distro>` is
|
||||
selected through the `BASE_IMAGE` build argument; the base images are the
|
||||
**oldest supported version** of each distribution we target:
|
||||
|
||||
| Image | `BASE_IMAGE` | Notes |
|
||||
| ------------ | -------------------------------------------- | -------------------------------------------------- |
|
||||
| `nix-nixos` | `nixos/nix:latest` | Build/lint only; binaries are not run (see below). |
|
||||
| `nix-ubuntu` | `ubuntu:20.04` | Oldest supported Ubuntu (glibc 2.31). |
|
||||
| `nix-debian` | `debian:bookworm` | |
|
||||
| `nix-rhel` | `registry.access.redhat.com/ubi9/ubi:latest` | |
|
||||
|
||||
All images carry the full toolchain on `PATH` (via `/nix/ci-env/bin`) plus the
|
||||
CA bundle shipped in the Nix environment, so HTTPS clients (git, curl, Conan)
|
||||
work without `ca-certificates` being installed in the base image.
|
||||
|
||||
## Build stages
|
||||
|
||||
[`Dockerfile`](./Dockerfile) is a multi-stage build:
|
||||
|
||||
1. **`builder`** — On a `nixos/nix` builder, evaluate the flake and build the
|
||||
CI environment (`nix/ci-env.nix`). The resulting Nix store closure (the
|
||||
complete set of store paths the toolchain depends on) is copied into a
|
||||
staging directory.
|
||||
2. **`final`** — Start from `BASE_IMAGE`, copy in the Nix store closure and the
|
||||
`ci-env` symlink tree, and wire up `PATH` and the CA bundle. It then:
|
||||
- installs the dynamic linker if the base image lacks one (see
|
||||
[How libc is handled](#how-libc-is-handled)),
|
||||
- runs [`bin/check-tools.sh`](../../bin/check-tools.sh) to verify every
|
||||
expected tool is present and runnable, and
|
||||
- compiles the C++ test programs in
|
||||
[`test_files/`](./test_files) with both `g++` and `clang++`, and sanitizers.
|
||||
3. **`tester`** — Start again from a clean `BASE_IMAGE` (no Nix toolchain),
|
||||
install only the sanitizer runtime libraries
|
||||
([`install-sanitizer-libs.sh`](./install-sanitizer-libs.sh)), and run the
|
||||
binaries compiled in `final`. This proves the binaries built with the Nix
|
||||
toolchain actually run on a vanilla base image. On `nixos/nix` this step is
|
||||
skipped (the binaries are patched for a conventional FHS loader).
|
||||
4. **Output** — The final image is gated on the tester succeeding: it copies a
|
||||
sentinel file out of `tester`, so a failed test run fails the whole build.
|
||||
|
||||
## How libc is handled
|
||||
|
||||
The goal is for binaries built in these images to run on the **oldest supported
|
||||
base image** (Ubuntu 20.04, glibc 2.31) and newer — without the developer's Nix
|
||||
toolchain being present at runtime. Two pieces make that work:
|
||||
|
||||
- **Compilers linked against an old glibc.** The Nix CI environment does not use
|
||||
nixpkgs' current glibc. Instead it pins a 2020 nixpkgs snapshot whose primary
|
||||
glibc is **2.31** (matching Ubuntu 20.04), via the `nixpkgs-custom-glibc`
|
||||
flake input. GCC, Clang, binutils and compiler-rt are all rebuilt/wrapped
|
||||
against this custom glibc (see [`nix/ci-env.nix`](../ci-env.nix)). As a result
|
||||
the libraries they emit (`libstdc++`, `libgcc_s`, the sanitizer runtimes)
|
||||
reference only symbols available in glibc 2.31.
|
||||
|
||||
- **An expected dynamic linker in the image.**
|
||||
Binaries built in Nix environments reference a dynamic linker from Nix store paths, which won't be present in the base image. However,
|
||||
[`loader-path.sh`](./loader-path.sh) reports the expected loader path for the
|
||||
current architecture, so we can patch the binaries to use the correct loader.
|
||||
|
||||
The build then verifies all of this end to end: the test programs in
|
||||
`test_files/` (a regular binary plus ASan/TSan/UBSan variants) are compiled in
|
||||
`final`, their `PT_INTERP` is patched to the target loader, and they are run in
|
||||
the clean `tester` stage to confirm each emits the expected sanitizer
|
||||
diagnostic on a stock base image.
|
||||
|
||||
## Files
|
||||
|
||||
| File | Purpose |
|
||||
| ----------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
|
||||
| [`./Dockerfile`](./Dockerfile) | Multi-stage build described above. |
|
||||
| [`./loader-path.sh`](./loader-path.sh) | Print the dynamic-linker (`PT_INTERP`) path for the current architecture. |
|
||||
| [`./test_files/`](./test_files) | C++ sources and scripts to compile and run the sanitizer smoke tests. |
|
||||
| [`/bin/check-tools.sh`](../../bin/check-tools.sh) | Verify every expected tools are present and runnable. |
|
||||
| [`/bin/install-sanitizer-libs.sh`](../../bin/install-sanitizer-libs.sh) | Install `libasan`/`libtsan`/`libubsan` runtimes on the supported base images. |
|
||||
38
nix/docker/check-tools.sh
Executable file
38
nix/docker/check-tools.sh
Executable file
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
# Verify that every tool expected in the Nix CI env is present and runnable.
|
||||
set -euo pipefail
|
||||
|
||||
ccache --version
|
||||
clang --version
|
||||
clang++ --version
|
||||
clang-format --version
|
||||
cmake --version
|
||||
conan --version
|
||||
curl --version
|
||||
doxygen --version
|
||||
file --version
|
||||
g++ --version
|
||||
gcc --version
|
||||
gcov --version
|
||||
gcovr --version
|
||||
gh --version
|
||||
git --version
|
||||
git-cliff --version
|
||||
gpg --version
|
||||
less --version
|
||||
make --version
|
||||
mold --version
|
||||
netstat --version
|
||||
ninja --version
|
||||
perl --version
|
||||
pkg-config --version
|
||||
pre-commit --version
|
||||
python3 --version
|
||||
run-clang-tidy --help
|
||||
vim --version
|
||||
|
||||
# A simple test to verify that git can clone a repository over HTTPS
|
||||
# (i.e. the CA bundle is wired up). Clone to a temp dir and clean up.
|
||||
tmp_clone="$(mktemp -d)"
|
||||
git clone --depth 1 https://github.com/XRPLF/actions.git "${tmp_clone}/actions"
|
||||
rm -rf "${tmp_clone}"
|
||||
@@ -9,7 +9,6 @@ in
|
||||
{
|
||||
commonPackages = with pkgs; [
|
||||
ccache
|
||||
clangbuildanalyzer
|
||||
cmake
|
||||
conan
|
||||
curlMinimal # needed for codecov/codecov-action
|
||||
@@ -19,10 +18,8 @@ in
|
||||
gh
|
||||
git
|
||||
git-cliff
|
||||
git-lfs
|
||||
gnumake
|
||||
gnupg # needed for signing commits & codecov/codecov-action
|
||||
graphviz
|
||||
llvmPackages_22.clang-tools
|
||||
less # needed for git diff
|
||||
mold
|
||||
@@ -35,6 +32,5 @@ in
|
||||
python3
|
||||
runClangTidy
|
||||
vim
|
||||
zip
|
||||
];
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ package/
|
||||
xrpld.sysusers sysusers.d config (used by both RPM and DEB)
|
||||
xrpld.tmpfiles tmpfiles.d config (used by both RPM and DEB)
|
||||
xrpld.logrotate logrotate config (installed to /etc/logrotate.d/xrpld)
|
||||
update-xrpld auto-update script (installed to /usr/libexec/xrpld/, run by update-xrpld.timer)
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -114,11 +114,10 @@ VER_BASE="${VERSION%%-*}"
|
||||
VER_SUFFIX="${VERSION#*-}"
|
||||
[[ "${VER_SUFFIX}" == "${VERSION}" ]] && VER_SUFFIX=""
|
||||
|
||||
# Reject multi-segment suffixes (e.g. "beta-1", "rc1-15-gabc123"). Neither an
|
||||
# RPM Version nor a Debian upstream version may contain '-' (it's the NVR /
|
||||
# version-revision separator), and the convention here is single-token
|
||||
# suffixes like b1 or rc2. Fail early with a clear message rather than letting
|
||||
# the package tooling blow up or silently mangle dashes.
|
||||
# Reject multi-segment suffixes (e.g. "beta-1", "rc1-15-gabc123"). The RPM
|
||||
# Release field forbids '-', and the convention here is single-token suffixes
|
||||
# like b1 or rc2. Fail early with a clear message rather than letting either
|
||||
# rpmbuild blow up or silently mangling dashes into dots.
|
||||
if [[ "${VER_SUFFIX}" == *-* ]]; then
|
||||
echo "build_pkg.sh: multi-segment pre-release in VERSION='${VERSION}' (suffix '${VER_SUFFIX}')." >&2
|
||||
echo "Use single-token suffixes like 3.2.0-b1 or 3.2.0-rc2." >&2
|
||||
@@ -143,6 +142,9 @@ stage_common() {
|
||||
cp "${SHARED}/xrpld.sysusers" "${dest}/xrpld.sysusers"
|
||||
cp "${SHARED}/xrpld.tmpfiles" "${dest}/xrpld.tmpfiles"
|
||||
cp "${SHARED}/xrpld.logrotate" "${dest}/xrpld.logrotate"
|
||||
cp "${SHARED}/update-xrpld" "${dest}/update-xrpld"
|
||||
cp "${SHARED}/update-xrpld.service" "${dest}/update-xrpld.service"
|
||||
cp "${SHARED}/update-xrpld.timer" "${dest}/update-xrpld.timer"
|
||||
cp "${SHARED}/50-xrpld.preset" "${dest}/50-xrpld.preset"
|
||||
}
|
||||
|
||||
@@ -154,18 +156,20 @@ build_rpm() {
|
||||
cp "${SRC_DIR}/package/rpm/xrpld.spec" "${topdir}/SPECS/xrpld.spec"
|
||||
stage_common "${topdir}/SOURCES"
|
||||
|
||||
# Pre-releases use the modern rpm '~' convention (rpm >= 4.10): the suffix
|
||||
# goes in Version (e.g. 3.2.0~b1), which rpmvercmp sorts *before* the final
|
||||
# 3.2.0 — identical semantics to Debian's '~'. Release is just the package
|
||||
# release number. This replaces the older "0.<release>.<suffix>" Release
|
||||
# hack and keeps the RPM and DEB version strings symmetric.
|
||||
local rpm_version="${VER_BASE}${VER_SUFFIX:+~${VER_SUFFIX}}"
|
||||
# RPM Version can't contain '-'. A pre-release goes in Release with a
|
||||
# leading "0." so 3.2.0-b1 sorts before the final 3.2.0-<pkg_release>.
|
||||
# The order is "0.<pkg_release>.<suffix>" (e.g. 0.1.b6) — the Fedora/EPEL
|
||||
# convention. Reversing to "0.<suffix>.<pkg_release>" (e.g. 0.b6.1) breaks
|
||||
# rpmvercmp against the former because numeric segments outrank alphabetic
|
||||
# ones, so "0.1.b5" would sort newer than "0.b6.1".
|
||||
local rpm_release="${PKG_RELEASE}"
|
||||
[[ -n "${VER_SUFFIX}" ]] && rpm_release="0.${PKG_RELEASE}.${VER_SUFFIX}"
|
||||
|
||||
set -x
|
||||
rpmbuild -bb \
|
||||
--define "_topdir ${topdir}" \
|
||||
--define "xrpld_version ${rpm_version}" \
|
||||
--define "xrpld_release ${PKG_RELEASE}" \
|
||||
--define "xrpld_version ${VER_BASE}" \
|
||||
--define "xrpld_release ${rpm_release}" \
|
||||
"${topdir}/SPECS/xrpld.spec"
|
||||
}
|
||||
|
||||
@@ -177,10 +181,13 @@ build_deb() {
|
||||
stage_common "${staging}"
|
||||
cp -r "${DEBIAN_DIR}" "${staging}/debian"
|
||||
|
||||
# Debhelper auto-discovers these only from debian/.
|
||||
cp "${staging}/xrpld.service" "${staging}/debian/xrpld.service"
|
||||
cp "${staging}/xrpld.sysusers" "${staging}/debian/xrpld.sysusers"
|
||||
cp "${staging}/xrpld.tmpfiles" "${staging}/debian/xrpld.tmpfiles"
|
||||
cp "${staging}/xrpld.logrotate" "${staging}/debian/xrpld.logrotate"
|
||||
cp "${staging}/update-xrpld.service" "${staging}/debian/xrpld.update-xrpld.service"
|
||||
cp "${staging}/update-xrpld.timer" "${staging}/debian/xrpld.update-xrpld.timer"
|
||||
|
||||
# Debian '~' marks a pre-release; 3.2.0~b1 sorts before 3.2.0.
|
||||
local deb_full_version="${VER_BASE}${VER_SUFFIX:+~${VER_SUFFIX}}-${PKG_RELEASE}"
|
||||
|
||||
@@ -10,6 +10,7 @@ override_dh_auto_configure override_dh_auto_build override_dh_auto_test:
|
||||
|
||||
override_dh_installsystemd:
|
||||
dh_installsystemd --no-stop-on-upgrade xrpld.service
|
||||
dh_installsystemd --name=update-xrpld --no-enable --no-start update-xrpld.service update-xrpld.timer
|
||||
|
||||
execute_before_dh_installtmpfiles:
|
||||
dh_installsysusers
|
||||
@@ -20,6 +21,7 @@ override_dh_install:
|
||||
install -D -m 0755 xrpld debian/xrpld/usr/bin/xrpld
|
||||
install -D -m 0644 xrpld.cfg debian/xrpld/etc/xrpld/xrpld.cfg
|
||||
install -D -m 0644 validators.txt debian/xrpld/etc/xrpld/validators.txt
|
||||
install -D -m 0755 update-xrpld debian/xrpld/usr/libexec/xrpld/update-xrpld
|
||||
|
||||
override_dh_dwz:
|
||||
@:
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
README.md
|
||||
LICENSE.md
|
||||
|
||||
@@ -35,6 +35,8 @@ install -Dm0644 %{_sourcedir}/validators.txt %{buildroot}%{_sysconfdir}/%{
|
||||
|
||||
# systemd units, sysusers, tmpfiles, preset
|
||||
install -Dm0644 %{_sourcedir}/xrpld.service %{buildroot}%{_unitdir}/xrpld.service
|
||||
install -Dm0644 %{_sourcedir}/update-xrpld.service %{buildroot}%{_unitdir}/update-xrpld.service
|
||||
install -Dm0644 %{_sourcedir}/update-xrpld.timer %{buildroot}%{_unitdir}/update-xrpld.timer
|
||||
install -Dm0644 %{_sourcedir}/xrpld.sysusers %{buildroot}%{_sysusersdir}/xrpld.conf
|
||||
install -Dm0644 %{_sourcedir}/xrpld.tmpfiles %{buildroot}%{_tmpfilesdir}/xrpld.conf
|
||||
install -Dm0644 %{_sourcedir}/50-xrpld.preset %{buildroot}%{_presetdir}/50-xrpld.preset
|
||||
@@ -42,6 +44,9 @@ install -Dm0644 %{_sourcedir}/50-xrpld.preset %{buildroot}%{_presetdir}/50-
|
||||
# Logrotate config
|
||||
install -Dm0644 %{_sourcedir}/xrpld.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/%{name}
|
||||
|
||||
# Update helper
|
||||
install -Dm0755 %{_sourcedir}/update-xrpld %{buildroot}%{_libexecdir}/%{name}/update-xrpld
|
||||
|
||||
# Docs
|
||||
install -Dm0644 %{_sourcedir}/LICENSE.md %{buildroot}%{_docdir}/%{name}/LICENSE.md
|
||||
install -Dm0644 %{_sourcedir}/README.md %{buildroot}%{_docdir}/%{name}/README.md
|
||||
@@ -56,10 +61,10 @@ ln -s %{_bindir}/%{name} %{buildroot}/usr/local/bin/rippled
|
||||
|
||||
%post
|
||||
systemd-tmpfiles --create %{_tmpfilesdir}/xrpld.conf || :
|
||||
%systemd_post xrpld.service
|
||||
%systemd_post xrpld.service update-xrpld.timer
|
||||
|
||||
%preun
|
||||
%systemd_preun xrpld.service
|
||||
%systemd_preun xrpld.service update-xrpld.timer
|
||||
|
||||
%postun
|
||||
%systemd_postun_with_restart xrpld.service
|
||||
@@ -69,6 +74,7 @@ systemd-tmpfiles --create %{_tmpfilesdir}/xrpld.conf || :
|
||||
%doc %{_docdir}/%{name}/README.md
|
||||
|
||||
%dir %{_sysconfdir}/%{name}
|
||||
%dir %{_libexecdir}/%{name}
|
||||
|
||||
%{_bindir}/%{name}
|
||||
|
||||
@@ -76,13 +82,18 @@ systemd-tmpfiles --create %{_tmpfilesdir}/xrpld.conf || :
|
||||
%config(noreplace) %{_sysconfdir}/%{name}/validators.txt
|
||||
%config(noreplace) %{_sysconfdir}/logrotate.d/%{name}
|
||||
|
||||
%{_libexecdir}/%{name}/update-xrpld
|
||||
|
||||
%{_unitdir}/xrpld.service
|
||||
%{_unitdir}/update-xrpld.service
|
||||
%{_unitdir}/update-xrpld.timer
|
||||
%{_presetdir}/50-xrpld.preset
|
||||
%{_sysusersdir}/xrpld.conf
|
||||
%{_tmpfilesdir}/xrpld.conf
|
||||
%ghost %dir /var/lib/xrpld
|
||||
%ghost %dir /var/log/xrpld
|
||||
|
||||
%ghost %dir /var/lib/%{name}
|
||||
%ghost %dir /var/log/%{name}
|
||||
|
||||
|
||||
# Legacy compatibility for pre-FHS package layouts.
|
||||
# TODO: remove after rippled fully deprecated.
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
# /usr/lib/systemd/system-preset/50-xrpld.preset
|
||||
enable xrpld.service
|
||||
# Don't enable automatic updates
|
||||
disable update-xrpld.timer
|
||||
|
||||
152
package/shared/update-xrpld
Executable file
152
package/shared/update-xrpld
Executable file
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Optional: also write logs to a legacy file in addition to journald.
|
||||
# By default, this script logs to systemd/journald, viewable via:
|
||||
# journalctl -t update-xrpld
|
||||
#
|
||||
# Uncomment the line below if you need a flat file for compatibility with
|
||||
# external tooling, manual inspection, or environments where journald logs
|
||||
# are not persisted or easily accessible.
|
||||
#
|
||||
# Note: This duplicates all output (stdout/stderr) to both journald and the file.
|
||||
# It is generally not needed on modern systems and may cause log file growth
|
||||
# if left enabled long-term.
|
||||
#
|
||||
# Requires /var/log/xrpld/ to exist and be writable by the service (root).
|
||||
#
|
||||
# exec > >(tee -a /var/log/xrpld/update.log) 2>&1
|
||||
|
||||
PATH=/usr/sbin:/usr/bin:/sbin:/bin
|
||||
|
||||
PKG_NAME=${PKG_NAME:-xrpld}
|
||||
|
||||
log() {
|
||||
# If running under systemd/journald, let it handle timestamps.
|
||||
if [[ -n "${JOURNAL_STREAM:-}" ]]; then
|
||||
printf '%s\n' "$*"
|
||||
else
|
||||
printf '%s %s\n' "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" "$*"
|
||||
fi
|
||||
}
|
||||
|
||||
require_root() {
|
||||
if [[ ${EUID:-$(id -u)} -ne 0 ]]; then
|
||||
log "RESULT: failed reason=not-root"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
get_installed_version() {
|
||||
if command -v dpkg-query >/dev/null 2>&1; then
|
||||
dpkg-query -W -f='${Version}' "$PKG_NAME" 2>/dev/null || printf 'unknown'
|
||||
elif command -v rpm >/dev/null 2>&1; then
|
||||
rpm -q --qf '%{VERSION}-%{RELEASE}' "$PKG_NAME" 2>/dev/null || printf 'unknown'
|
||||
else
|
||||
printf 'unknown'
|
||||
fi
|
||||
}
|
||||
|
||||
trap 'log "RESULT: failed reason=script-error exit_code=$?"' ERR
|
||||
|
||||
apt_can_update() {
|
||||
apt-get update -qq
|
||||
apt-get -s --only-upgrade install "$PKG_NAME" 2>/dev/null | grep -q "^Inst ${PKG_NAME}\b"
|
||||
}
|
||||
|
||||
apt_apply_update() {
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
|
||||
-o Dpkg::Options::="--force-confdef" \
|
||||
-o Dpkg::Options::="--force-confold" \
|
||||
"$PKG_NAME"
|
||||
}
|
||||
|
||||
get_rpm_pm() {
|
||||
if command -v dnf >/dev/null 2>&1; then
|
||||
printf 'dnf\n'
|
||||
elif command -v yum >/dev/null 2>&1; then
|
||||
printf 'yum\n'
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
rpm_refresh_metadata() {
|
||||
local pm=$1
|
||||
if [[ "$pm" == "dnf" ]]; then
|
||||
dnf makecache --refresh -q >/dev/null
|
||||
else
|
||||
yum clean expire-cache -q >/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
rpm_can_update() {
|
||||
local pm=$1
|
||||
|
||||
rpm_refresh_metadata "$pm"
|
||||
local rc=0
|
||||
set +e
|
||||
"$pm" check-update -q "$PKG_NAME" >/dev/null 2>&1
|
||||
rc=$?
|
||||
set -e
|
||||
|
||||
if [[ $rc -eq 100 ]]; then
|
||||
return 0
|
||||
elif [[ $rc -eq 0 ]]; then
|
||||
return 1
|
||||
else
|
||||
log "$pm check-update failed with exit code ${rc}."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
rpm_apply_update() {
|
||||
local pm=$1
|
||||
"$pm" update -y "$PKG_NAME"
|
||||
}
|
||||
|
||||
restart_service() {
|
||||
# Preserve the operator's prior service state: if xrpld was intentionally
|
||||
# stopped before the update, don't bring it back up just because the
|
||||
# auto-update timer fired.
|
||||
if systemctl is-active --quiet "${PKG_NAME}.service"; then
|
||||
systemctl restart "${PKG_NAME}.service"
|
||||
log "${PKG_NAME} service restarted successfully."
|
||||
else
|
||||
log "${PKG_NAME} service was not running; skipping restart to preserve prior state."
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
require_root
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
log "Checking for ${PKG_NAME} updates via apt"
|
||||
if apt_can_update; then
|
||||
log "Update available; installing."
|
||||
apt_apply_update
|
||||
restart_service
|
||||
log "RESULT: updated ${PKG_NAME}=$(get_installed_version)"
|
||||
else
|
||||
log "RESULT: no-update ${PKG_NAME}=$(get_installed_version)"
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
local rpm_pm=""
|
||||
if rpm_pm="$(get_rpm_pm)"; then
|
||||
log "Checking for ${PKG_NAME} updates via ${rpm_pm}"
|
||||
if rpm_can_update "$rpm_pm"; then
|
||||
log "Update available; installing"
|
||||
rpm_apply_update "$rpm_pm"
|
||||
restart_service
|
||||
log "RESULT: updated ${PKG_NAME}=$(get_installed_version)"
|
||||
else
|
||||
log "RESULT: no-update ${PKG_NAME}=$(get_installed_version)"
|
||||
fi
|
||||
return
|
||||
fi
|
||||
log "RESULT: failed reason=no-package-manager"
|
||||
exit 1
|
||||
}
|
||||
|
||||
main "$@"
|
||||
16
package/shared/update-xrpld.service
Normal file
16
package/shared/update-xrpld.service
Normal file
@@ -0,0 +1,16 @@
|
||||
[Unit]
|
||||
Description=Check for and install xrpld package updates
|
||||
Documentation=man:systemd.service(5)
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
ConditionPathExists=/usr/libexec/xrpld/update-xrpld
|
||||
ConditionPathExists=/usr/bin/xrpld
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/bin/flock -n /run/lock/xrpld-update.lock /usr/libexec/xrpld/update-xrpld
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=update-xrpld
|
||||
TimeoutStartSec=30min
|
||||
PrivateTmp=true
|
||||
10
package/shared/update-xrpld.timer
Normal file
10
package/shared/update-xrpld.timer
Normal file
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=Daily xrpld update check
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 00:00:00
|
||||
RandomizedDelaySec=4h
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@@ -18,11 +18,6 @@ PrivateTmp=true
|
||||
User=xrpld
|
||||
Group=xrpld
|
||||
LimitNOFILE=65536
|
||||
SystemCallArchitectures=native
|
||||
|
||||
# Uncomment both lines to allow xrpld to bind to privileged ports (<1024)
|
||||
#CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||
#AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
@@ -1 +1 @@
|
||||
halt_on_error=true
|
||||
halt_on_error=false
|
||||
|
||||
@@ -72,7 +72,7 @@ vptr:boost
|
||||
|
||||
# Google protobuf - intentional overflows in hash functions
|
||||
undefined:protobuf
|
||||
unsigned-integer-overflow:protobuf
|
||||
unsigned-integer-overflow:google/protobuf/stubs/stringpiece.h
|
||||
|
||||
# gRPC intentional overflows in timer calculations
|
||||
unsigned-integer-overflow:grpc
|
||||
@@ -102,103 +102,47 @@ undefined:nudb
|
||||
# Snappy compression library intentional overflows
|
||||
unsigned-integer-overflow:snappy.cc
|
||||
|
||||
# Abseil intentional overflows in hashing, RNG and time arithmetic.
|
||||
# Matched at library scope (like boost above): the wraparound is by design
|
||||
# across many absl files (hash mixing, raw_hash_set probing, duration math,
|
||||
# int128, uniform_int_distribution), so listing individual files just churns.
|
||||
unsigned-integer-overflow:absl
|
||||
# Abseil intentional overflows
|
||||
unsigned-integer-overflow:absl/strings/numbers.cc
|
||||
unsigned-integer-overflow:absl/strings/internal/cord_rep_flat.h
|
||||
unsigned-integer-overflow:absl/base/internal/low_level_alloc.cc
|
||||
unsigned-integer-overflow:absl/hash/internal/hash.h
|
||||
unsigned-integer-overflow:absl/container/internal/raw_hash_set.h
|
||||
|
||||
# Standard library intentional overflows
|
||||
unsigned-integer-overflow:basic_string.h
|
||||
unsigned-integer-overflow:bits/align.h
|
||||
unsigned-integer-overflow:bits/basic_string.tcc
|
||||
unsigned-integer-overflow:bits/chrono.h
|
||||
unsigned-integer-overflow:bits/random.h
|
||||
unsigned-integer-overflow:bits/random.tcc
|
||||
unsigned-integer-overflow:bits/stl_algobase.h
|
||||
unsigned-integer-overflow:bits/string_view.tcc
|
||||
unsigned-integer-overflow:bits/uniform_int_dist.h
|
||||
unsigned-integer-overflow:string_view
|
||||
unsigned-integer-overflow:__random/seed_seq.h
|
||||
unsigned-integer-overflow:__charconv/traits.h
|
||||
unsigned-integer-overflow:__chrono/duration.h
|
||||
# libstdc++ <bit> (std::__bit_ceil etc.) negates an unsigned width; <bit> is a
|
||||
# distinct header from the bits/ directory so it needs its own entry.
|
||||
unsigned-integer-overflow:include/c++/*/bit
|
||||
|
||||
# =============================================================================
|
||||
# Rippled code suppressions
|
||||
# =============================================================================
|
||||
|
||||
# These suppressions are keyed by SOURCE FILE, not function name. This UBSan
|
||||
# build runs without symbol information, so the runtime only knows the
|
||||
# file:line of each report, never the enclosing function — function-name
|
||||
# patterns silently never match. Each entry below is therefore scoped to the
|
||||
# file whose arithmetic is intentional; the comment names the specific
|
||||
# construct.
|
||||
# Signed integer negation (-value) in amount types.
|
||||
# INT64_MIN cannot occur in practice due to domain invariants (mantissa ranges
|
||||
# are well within int64_t bounds), but UBSan flags the pattern as potential
|
||||
# signed overflow. Narrowed to operator- to avoid suppressing unrelated
|
||||
# overflows anywhere in a stack trace containing these type names.
|
||||
signed-integer-overflow:operator-*IOUAmount*
|
||||
signed-integer-overflow:operator-*XRPAmount*
|
||||
signed-integer-overflow:operator-*MPTAmount*
|
||||
signed-integer-overflow:operator-*STAmount*
|
||||
|
||||
# STAmount amount-type arithmetic. Unary negation of the mantissa in xrp()/
|
||||
# iou()/mpt()/canonicalize() and getInt64Value, plus bounded +/- on amounts:
|
||||
# INT64_MIN cannot occur because canonicalize() keeps the mantissa well within
|
||||
# int64_t, and operands are bounded by total supply (~10^17 XRP, ~10^18 MPT).
|
||||
signed-integer-overflow:protocol/STAmount.cpp
|
||||
# STAmount::operator+ signed addition — operands are bounded by total supply
|
||||
# (~10^17 for XRP, ~10^18 for MPT) so overflow cannot occur in practice.
|
||||
signed-integer-overflow:operator+*STAmount*
|
||||
|
||||
# nft::cipheredTaxon uses intentional uint32 wraparound (LCG permutation);
|
||||
# the helper lives in the generated protocol header nft.h.
|
||||
unsigned-integer-overflow:protocol/nft.h
|
||||
# STAmount::getRate uses unsigned shift and addition
|
||||
unsigned-integer-overflow:*STAmount*getRate*
|
||||
# STAmount::serialize uses unsigned bitwise operations
|
||||
unsigned-integer-overflow:*STAmount*serialize*
|
||||
|
||||
# STPathElement::getHash multiplies/adds accumulators (non-secure, speed-first).
|
||||
unsigned-integer-overflow:protocol/STPathSet.cpp
|
||||
|
||||
# beast XorShiftEngine PRNG and murmurhash3 mixing wrap by design.
|
||||
unsigned-integer-overflow:beast/xor_shift_engine.h
|
||||
|
||||
# Number::normalizeToRange multiplies the mantissa by powers of ten; the result
|
||||
# is intentionally allowed to wrap while searching for the in-range value.
|
||||
unsigned-integer-overflow:basics/Number.h
|
||||
|
||||
# Counter / sequence arithmetic with intentional unsigned wraparound, each
|
||||
# guarded by an explicit overflow or domain check at the call site:
|
||||
# base_uint operator++/-- wrap by definition;
|
||||
# ApplyView::insertPage ++page is asserted to wrap to 0 (page exhaustion);
|
||||
# confineOwnerCount documents "overflow is well defined on unsigned";
|
||||
# NFTokenMint checks tokenSeq + 1u == 0u; AmendmentTable does (seq - 1) / 256.
|
||||
unsigned-integer-overflow:basics/base_uint.h
|
||||
unsigned-integer-overflow:ledger/ApplyView.cpp
|
||||
unsigned-integer-overflow:ledger/helpers/AccountRootHelpers.cpp
|
||||
unsigned-integer-overflow:tx/transactors/nft/NFTokenMint.cpp
|
||||
unsigned-integer-overflow:app/misc/detail/AmendmentTable.cpp
|
||||
|
||||
# Sentinel / bounded subtractions that wrap by design (loop counters, reverse
|
||||
# iteration, "not found" sentinels, balance math bounded by issuance invariants,
|
||||
# base58/base64 codec index math, hash-router and role bit math).
|
||||
unsigned-integer-overflow:shamap/SHAMap.cpp
|
||||
unsigned-integer-overflow:protocol/Permissions.cpp
|
||||
unsigned-integer-overflow:protocol/tokens.cpp
|
||||
unsigned-integer-overflow:basics/base64.cpp
|
||||
unsigned-integer-overflow:json/json_value.cpp
|
||||
unsigned-integer-overflow:app/misc/NetworkOPs.cpp
|
||||
unsigned-integer-overflow:rpc/detail/Role.cpp
|
||||
unsigned-integer-overflow:tx/transactors/oracle/OracleSet.cpp
|
||||
unsigned-integer-overflow:ledger/helpers/MPTokenHelpers.cpp
|
||||
unsigned-integer-overflow:crypto/RFC1751.cpp
|
||||
unsigned-integer-overflow:tx/paths/detail/StrandFlow.h
|
||||
unsigned-integer-overflow:protocol/STObject.h
|
||||
|
||||
# GetAggregatePrice negates an unsigned trim count to step a reverse iterator;
|
||||
# trimCount is bounded by the price set size.
|
||||
unsigned-integer-overflow:rpc/handlers/orderbook/GetAggregatePrice.cpp
|
||||
|
||||
# Test-only intentional overflow/underflow in fixture and unit-test arithmetic.
|
||||
unsigned-integer-overflow:tests/libxrpl/basics/RangeSet.cpp
|
||||
unsigned-integer-overflow:test/app/Batch_test.cpp
|
||||
unsigned-integer-overflow:test/app/Invariants_test.cpp
|
||||
unsigned-integer-overflow:test/app/Loan_test.cpp
|
||||
unsigned-integer-overflow:test/app/NFToken_test.cpp
|
||||
unsigned-integer-overflow:test/app/OfferMPT_test.cpp
|
||||
unsigned-integer-overflow:test/app/Offer_test.cpp
|
||||
unsigned-integer-overflow:test/app/Path_test.cpp
|
||||
unsigned-integer-overflow:test/jtx/impl/acctdelete.cpp
|
||||
unsigned-integer-overflow:test/ledger/SkipList_test.cpp
|
||||
unsigned-integer-overflow:test/rpc/Subscribe_test.cpp
|
||||
signed-integer-overflow:test/basics/XRPAmount_test.cpp
|
||||
# nft::cipheredTaxon uses intentional uint32 wraparound (LCG permutation)
|
||||
unsigned-integer-overflow:cipheredTaxon
|
||||
|
||||
@@ -26,6 +26,14 @@ namespace xrpl {
|
||||
|
||||
namespace directory {
|
||||
|
||||
struct Gap
|
||||
{
|
||||
uint64_t const page;
|
||||
SLE::pointer node;
|
||||
uint64_t const nextPage;
|
||||
SLE::pointer next;
|
||||
};
|
||||
|
||||
std::uint64_t
|
||||
createRoot(
|
||||
ApplyView& view,
|
||||
@@ -126,7 +134,9 @@ insertPage(
|
||||
if (page == 0)
|
||||
return std::nullopt;
|
||||
if (!view.rules().enabled(fixDirectoryLimit) && page >= kDirNodeMaxPages) // Old pages limit
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// We are about to create a new node; we'll link it to
|
||||
// the chain first:
|
||||
@@ -147,12 +157,8 @@ insertPage(
|
||||
// Save some space by not specifying the value 0 since it's the default.
|
||||
if (page != 1)
|
||||
node->setFieldU64(sfIndexPrevious, page - 1);
|
||||
XRPL_ASSERT_PARTS(!nextPage, "xrpl::directory::insertPage", "nextPage has default value");
|
||||
/* Reserved for future use when directory pages may be inserted in
|
||||
* between two other pages instead of only at the end of the chain.
|
||||
if (nextPage)
|
||||
node->setFieldU64(sfIndexNext, nextPage);
|
||||
*/
|
||||
describe(node);
|
||||
view.insert(node);
|
||||
|
||||
@@ -168,7 +174,7 @@ ApplyView::dirAdd(
|
||||
uint256 const& key,
|
||||
std::function<void(SLE::ref)> const& describe)
|
||||
{
|
||||
auto root = peek(directory);
|
||||
auto const root = peek(directory);
|
||||
|
||||
if (!root)
|
||||
{
|
||||
@@ -178,6 +184,43 @@ ApplyView::dirAdd(
|
||||
|
||||
auto [page, node, indexes] = directory::findPreviousPage(*this, directory, root);
|
||||
|
||||
if (rules().enabled(featureDefragDirectories))
|
||||
{
|
||||
// If there are more nodes than just the root, and there's no space in
|
||||
// the last one, walk backwards to find one with space, or to find one
|
||||
// missing.
|
||||
std::optional<directory::Gap> gapPages;
|
||||
while (page && indexes.size() >= kDIR_NODE_MAX_PAGES)
|
||||
{
|
||||
// Find a page with space, or a gap in pages.
|
||||
auto [prevPage, prevNode, prevIndexes] =
|
||||
directory::findPreviousPage(*this, directory, node);
|
||||
if (!gapPages && prevPage != page - 1)
|
||||
gapPages.emplace(prevPage, prevNode, page, node);
|
||||
page = prevPage;
|
||||
node = prevNode;
|
||||
indexes = prevIndexes;
|
||||
}
|
||||
// We looped through all the pages back to the root.
|
||||
if (!page)
|
||||
{
|
||||
// If we found a gap, use it.
|
||||
if (gapPages)
|
||||
{
|
||||
return directory::insertPage(
|
||||
*this,
|
||||
gapPages->page,
|
||||
gapPages->node,
|
||||
gapPages->nextPage,
|
||||
gapPages->next,
|
||||
key,
|
||||
directory,
|
||||
describe);
|
||||
}
|
||||
std::tie(page, node, indexes) = directory::findPreviousPage(*this, directory, root);
|
||||
}
|
||||
}
|
||||
|
||||
// If there's space, we use it:
|
||||
if (indexes.size() < kDirNodeMaxEntries)
|
||||
{
|
||||
|
||||
55
src/libxrpl/ledger/BookListeners.cpp
Normal file
55
src/libxrpl/ledger/BookListeners.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#include <xrpl/ledger/BookListeners.h>
|
||||
|
||||
#include <xrpl/basics/UnorderedContainers.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/MultiApiJson.h>
|
||||
#include <xrpl/server/InfoSub.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
void
|
||||
BookListeners::addSubscriber(InfoSub::ref sub)
|
||||
{
|
||||
std::scoped_lock const sl(lock_);
|
||||
listeners_[sub->getSeq()] = sub;
|
||||
}
|
||||
|
||||
void
|
||||
BookListeners::removeSubscriber(std::uint64_t seq)
|
||||
{
|
||||
std::scoped_lock const sl(lock_);
|
||||
listeners_.erase(seq);
|
||||
}
|
||||
|
||||
void
|
||||
BookListeners::publish(MultiApiJson const& jvObj, hash_set<std::uint64_t>& havePublished)
|
||||
{
|
||||
std::scoped_lock const sl(lock_);
|
||||
auto it = listeners_.cbegin();
|
||||
|
||||
while (it != listeners_.cend())
|
||||
{
|
||||
InfoSub::pointer p = it->second.lock();
|
||||
|
||||
if (p)
|
||||
{
|
||||
// Only publish jvObj if this is the first occurrence
|
||||
if (havePublished.emplace(p->getSeq()).second)
|
||||
{
|
||||
jvObj.visit(
|
||||
p->getApiVersion(), //
|
||||
[&](json::Value const& jv) { p->send(jv, true); });
|
||||
}
|
||||
++it;
|
||||
}
|
||||
else
|
||||
{
|
||||
it = listeners_.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -5,20 +5,13 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
TER
|
||||
@@ -66,28 +59,4 @@ closeChannel(SLE::ref slep, ApplyView& view, uint256 const& key, beast::Journal
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
saturatingAdd(Rules const& rules, uint32_t const lhs, uint32_t const rhs)
|
||||
{
|
||||
if (rules.enabled(fixCleanup3_2_0))
|
||||
{
|
||||
static constexpr auto kUint32Max =
|
||||
static_cast<uint64_t>(std::numeric_limits<uint32_t>::max());
|
||||
uint64_t const saturatedResult = std::min(uint64_t{lhs} + rhs, kUint32Max);
|
||||
return static_cast<uint32_t>(saturatedResult);
|
||||
}
|
||||
|
||||
return lhs + rhs;
|
||||
}
|
||||
|
||||
bool
|
||||
isChannelExpired(ApplyView const& view, std::optional<uint32_t> timeField)
|
||||
{
|
||||
if (!timeField)
|
||||
return false;
|
||||
if (view.rules().enabled(fixCleanup3_2_0))
|
||||
return after(view.header().parentCloseTime, *timeField);
|
||||
return view.header().parentCloseTime.time_since_epoch().count() >= *timeField;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -45,10 +45,8 @@ ManagerImp::missingBackend()
|
||||
// the Factory classes is an undefined behaviour.
|
||||
void
|
||||
registerNuDBFactory(Manager& manager);
|
||||
#if XRPL_ROCKSDB_AVAILABLE
|
||||
void
|
||||
registerRocksDBFactory(Manager& manager);
|
||||
#endif
|
||||
void
|
||||
registerNullFactory(Manager& manager);
|
||||
void
|
||||
@@ -57,9 +55,7 @@ registerMemoryFactory(Manager& manager);
|
||||
ManagerImp::ManagerImp()
|
||||
{
|
||||
registerNuDBFactory(*this);
|
||||
#if XRPL_ROCKSDB_AVAILABLE
|
||||
registerRocksDBFactory(*this);
|
||||
#endif
|
||||
registerNullFactory(*this);
|
||||
registerMemoryFactory(*this);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
#if XRPL_ROCKSDB_AVAILABLE
|
||||
#include <xrpl/basics/ByteUtilities.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
#include <xrpl/beast/core/CurrentThreadName.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/config/BasicConfig.h>
|
||||
#include <xrpl/config/Constants.h>
|
||||
#include <xrpl/nodestore/Backend.h>
|
||||
#include <xrpl/nodestore/Factory.h>
|
||||
#include <xrpl/nodestore/Manager.h>
|
||||
#include <xrpl/nodestore/NodeObject.h>
|
||||
#include <xrpl/nodestore/Scheduler.h>
|
||||
#include <xrpl/nodestore/Types.h>
|
||||
#include <xrpl/nodestore/detail/BatchWriter.h>
|
||||
#include <xrpl/nodestore/detail/DecodedBlob.h>
|
||||
#include <xrpl/nodestore/detail/EncodedBlob.h>
|
||||
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
@@ -35,14 +25,26 @@
|
||||
#include <rocksdb/table.h>
|
||||
#include <rocksdb/write_batch.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#if XRPL_ROCKSDB_AVAILABLE
|
||||
#include <xrpl/basics/ByteUtilities.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
#include <xrpl/beast/core/CurrentThreadName.h>
|
||||
#include <xrpl/nodestore/Factory.h>
|
||||
#include <xrpl/nodestore/Manager.h>
|
||||
#include <xrpl/nodestore/detail/BatchWriter.h>
|
||||
#include <xrpl/nodestore/detail/DecodedBlob.h>
|
||||
#include <xrpl/nodestore/detail/EncodedBlob.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
namespace xrpl::NodeStore {
|
||||
|
||||
class RocksDBEnv : public rocksdb::EnvWrapper
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace {
|
||||
//------------------------------------------------------------------------------
|
||||
// clang-format off
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
char const* const versionString = "3.3.0-b0"
|
||||
char const* const versionString = "3.2.0-rc3"
|
||||
// clang-format on
|
||||
;
|
||||
|
||||
|
||||
@@ -1,47 +1,15 @@
|
||||
#include <xrpl/server/InfoSub.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Book.h>
|
||||
#include <xrpl/resource/Consumer.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
namespace {
|
||||
|
||||
// Wraps a Source teardown call so that an exception from one cleanup
|
||||
// step does not prevent the subsequent steps from running. Source methods
|
||||
// acquire a lock and can throw std::system_error; a throw out of ~InfoSub
|
||||
// during stack unwinding would terminate the process. Failures are
|
||||
// reported through the Source's Journal so they reach the configured log
|
||||
// sinks; JLOG itself cannot throw, so the noexcept guarantee holds.
|
||||
template <typename F>
|
||||
void
|
||||
safeUnsub(std::uint64_t seq, F&& f, beast::Journal j) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
f();
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
JLOG(j.warn()) << "~InfoSub[seq=" << seq << "]: cleanup step failed: " << e.what();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
JLOG(j.warn()) << "~InfoSub[seq=" << seq << "]: cleanup step failed: unknown exception";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// This is the primary interface into the "client" portion of the program.
|
||||
// Code that wants to do normal operations on the network such as
|
||||
// creating and monitoring accounts, creating transactions, and so on
|
||||
@@ -64,44 +32,25 @@ InfoSub::InfoSub(Source& source, Consumer consumer)
|
||||
|
||||
InfoSub::~InfoSub()
|
||||
{
|
||||
// Each Source teardown call below acquires a server-side lock and
|
||||
// can throw. Wrap each independent call so partial failure does not
|
||||
// skip the remaining teardown steps.
|
||||
|
||||
auto const& j = source_.journal();
|
||||
|
||||
safeUnsub(seq_, [&] { source_.unsubTransactions(seq_); }, j);
|
||||
safeUnsub(seq_, [&] { source_.unsubRTTransactions(seq_); }, j);
|
||||
safeUnsub(seq_, [&] { source_.unsubLedger(seq_); }, j);
|
||||
safeUnsub(seq_, [&] { source_.unsubManifests(seq_); }, j);
|
||||
safeUnsub(seq_, [&] { source_.unsubServer(seq_); }, j);
|
||||
safeUnsub(seq_, [&] { source_.unsubValidations(seq_); }, j);
|
||||
safeUnsub(seq_, [&] { source_.unsubPeerStatus(seq_); }, j);
|
||||
safeUnsub(seq_, [&] { source_.unsubConsensus(seq_); }, j);
|
||||
source_.unsubTransactions(seq_);
|
||||
source_.unsubRTTransactions(seq_);
|
||||
source_.unsubLedger(seq_);
|
||||
source_.unsubManifests(seq_);
|
||||
source_.unsubServer(seq_);
|
||||
source_.unsubValidations(seq_);
|
||||
source_.unsubPeerStatus(seq_);
|
||||
source_.unsubConsensus(seq_);
|
||||
|
||||
// Use the internal unsubscribe so that it won't call
|
||||
// back to us and modify its own parameter
|
||||
if (!realTimeSubscriptions_.empty())
|
||||
{
|
||||
safeUnsub(
|
||||
seq_, [&] { source_.unsubAccountInternal(seq_, realTimeSubscriptions_, true); }, j);
|
||||
}
|
||||
source_.unsubAccountInternal(seq_, realTimeSubscriptions_, true);
|
||||
|
||||
if (!normalSubscriptions_.empty())
|
||||
{
|
||||
safeUnsub(
|
||||
seq_, [&] { source_.unsubAccountInternal(seq_, normalSubscriptions_, false); }, j);
|
||||
}
|
||||
source_.unsubAccountInternal(seq_, normalSubscriptions_, false);
|
||||
|
||||
for (auto const& account : accountHistorySubscriptions_)
|
||||
{
|
||||
safeUnsub(seq_, [&] { source_.unsubAccountHistoryInternal(seq_, account, false); }, j);
|
||||
}
|
||||
|
||||
for (auto const& book : bookSubscriptions_)
|
||||
{
|
||||
safeUnsub(seq_, [&] { source_.unsubBookInternal(seq_, book); }, j);
|
||||
}
|
||||
source_.unsubAccountHistoryInternal(seq_, account, false);
|
||||
}
|
||||
|
||||
Resource::Consumer&
|
||||
@@ -165,20 +114,6 @@ InfoSub::deleteSubAccountHistory(AccountID const& account)
|
||||
accountHistorySubscriptions_.erase(account);
|
||||
}
|
||||
|
||||
void
|
||||
InfoSub::insertBookSubscription(Book const& book)
|
||||
{
|
||||
std::scoped_lock const sl(lock_);
|
||||
bookSubscriptions_.insert(book);
|
||||
}
|
||||
|
||||
void
|
||||
InfoSub::deleteBookSubscription(Book const& book)
|
||||
{
|
||||
std::scoped_lock const sl(lock_);
|
||||
bookSubscriptions_.erase(book);
|
||||
}
|
||||
|
||||
void
|
||||
InfoSub::clearRequest()
|
||||
{
|
||||
|
||||
@@ -1091,13 +1091,10 @@ AMMWithdraw::singleWithdrawEPrice(
|
||||
// t = T*(T + A*E*(f - 2))/(T*f - A*E)
|
||||
Number const ae = amountBalance * ePrice;
|
||||
auto const f = getFee(tfee);
|
||||
auto const denom = lptAMMBalance * f - ae;
|
||||
// fixCleanup3_3_0: guard against division by zero
|
||||
// when ePrice == lptAMMBalance*f/amountBalance
|
||||
if (view.rules().enabled(fixCleanup3_3_0) && denom == beast::kZero)
|
||||
return {tecAMM_FAILED, STAmount{}};
|
||||
auto tokNoRoundCb = [&] { return lptAMMBalance * (lptAMMBalance + ae * (f - 2)) / denom; };
|
||||
auto tokProdCb = [&] { return (lptAMMBalance + ae * (f - 2)) / denom; };
|
||||
auto tokNoRoundCb = [&] {
|
||||
return lptAMMBalance * (lptAMMBalance + ae * (f - 2)) / (lptAMMBalance * f - ae);
|
||||
};
|
||||
auto tokProdCb = [&] { return (lptAMMBalance + ae * (f - 2)) / (lptAMMBalance * f - ae); };
|
||||
auto const tokensAdj =
|
||||
getRoundedLPTokens(view.rules(), tokNoRoundCb, lptAMMBalance, tokProdCb, IsDeposit::No);
|
||||
if (tokensAdj <= beast::kZero)
|
||||
|
||||
@@ -42,9 +42,6 @@ PaymentChannelClaim::getFlagsMask(PreflightContext const&)
|
||||
NotTEC
|
||||
PaymentChannelClaim::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (ctx.rules.enabled(fixCleanup3_2_0) && ctx.tx[sfChannel] == beast::kZero)
|
||||
return temMALFORMED;
|
||||
|
||||
auto const bal = ctx.tx[~sfBalance];
|
||||
if (bal && (!isXRP(*bal) || *bal <= beast::kZero))
|
||||
return temBAD_AMOUNT;
|
||||
@@ -119,10 +116,12 @@ PaymentChannelClaim::doApply()
|
||||
AccountID const txAccount = ctx_.tx[sfAccount];
|
||||
|
||||
auto const curExpiration = (*slep)[~sfExpiration];
|
||||
if (isChannelExpired(ctx_.view(), (*slep)[~sfCancelAfter]) ||
|
||||
isChannelExpired(ctx_.view(), curExpiration))
|
||||
{
|
||||
return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.get().getJournal("View"));
|
||||
auto const cancelAfter = (*slep)[~sfCancelAfter];
|
||||
auto const closeTime = ctx_.view().header().parentCloseTime.time_since_epoch().count();
|
||||
if ((cancelAfter && closeTime >= *cancelAfter) ||
|
||||
(curExpiration && closeTime >= *curExpiration))
|
||||
return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.get().getJournal("View"));
|
||||
}
|
||||
|
||||
if (txAccount != src && txAccount != dst)
|
||||
@@ -135,19 +134,13 @@ PaymentChannelClaim::doApply()
|
||||
auto const reqBalance = ctx_.tx[sfBalance].xrp();
|
||||
|
||||
if (txAccount == dst && !ctx_.tx[~sfSignature])
|
||||
{
|
||||
return ctx_.view().rules().enabled(fixCleanup3_2_0) ? TER{tecNO_PERMISSION}
|
||||
: TER{temBAD_SIGNATURE};
|
||||
}
|
||||
return temBAD_SIGNATURE;
|
||||
|
||||
if (ctx_.tx[~sfSignature])
|
||||
{
|
||||
PublicKey const pk((*slep)[sfPublicKey]);
|
||||
if (ctx_.tx[sfPublicKey] != pk)
|
||||
{
|
||||
return ctx_.view().rules().enabled(fixCleanup3_2_0) ? TER{tecNO_PERMISSION}
|
||||
: TER{temBAD_SIGNER};
|
||||
}
|
||||
return temBAD_SIGNER;
|
||||
}
|
||||
|
||||
if (reqBalance > chanFunds)
|
||||
@@ -191,10 +184,9 @@ PaymentChannelClaim::doApply()
|
||||
if (dst == txAccount || (*slep)[sfBalance] == (*slep)[sfAmount])
|
||||
return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.get().getJournal("View"));
|
||||
|
||||
auto const settleExpiration = saturatingAdd(
|
||||
ctx_.view().rules(),
|
||||
ctx_.view().header().parentCloseTime.time_since_epoch().count(),
|
||||
(*slep)[sfSettleDelay]);
|
||||
auto const settleExpiration =
|
||||
ctx_.view().header().parentCloseTime.time_since_epoch().count() +
|
||||
(*slep)[sfSettleDelay];
|
||||
|
||||
if (!curExpiration || *curExpiration > settleExpiration)
|
||||
{
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/PaymentChannelHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
@@ -30,9 +29,6 @@ PaymentChannelFund::makeTxConsequences(PreflightContext const& ctx)
|
||||
NotTEC
|
||||
PaymentChannelFund::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (ctx.rules.enabled(fixCleanup3_2_0) && ctx.tx[sfChannel] == beast::kZero)
|
||||
return temMALFORMED;
|
||||
|
||||
if (!isXRP(ctx.tx[sfAmount]) || (ctx.tx[sfAmount] <= beast::kZero))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
@@ -49,12 +45,13 @@ PaymentChannelFund::doApply()
|
||||
|
||||
AccountID const src = (*slep)[sfAccount];
|
||||
auto const txAccount = ctx_.tx[sfAccount];
|
||||
auto const curExpiration = (*slep)[~sfExpiration];
|
||||
auto const expiration = (*slep)[~sfExpiration];
|
||||
|
||||
if (isChannelExpired(ctx_.view(), (*slep)[~sfCancelAfter]) ||
|
||||
isChannelExpired(ctx_.view(), curExpiration))
|
||||
{
|
||||
return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.get().getJournal("View"));
|
||||
auto const cancelAfter = (*slep)[~sfCancelAfter];
|
||||
auto const closeTime = ctx_.view().header().parentCloseTime.time_since_epoch().count();
|
||||
if ((cancelAfter && closeTime >= *cancelAfter) || (expiration && closeTime >= *expiration))
|
||||
return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.get().getJournal("View"));
|
||||
}
|
||||
|
||||
if (src != txAccount)
|
||||
@@ -63,21 +60,16 @@ PaymentChannelFund::doApply()
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
if (auto newExpiration = ctx_.tx[~sfExpiration])
|
||||
if (auto extend = ctx_.tx[~sfExpiration])
|
||||
{
|
||||
auto minExpiration = saturatingAdd(
|
||||
ctx_.view().rules(),
|
||||
ctx_.view().header().parentCloseTime.time_since_epoch().count(),
|
||||
(*slep)[sfSettleDelay]);
|
||||
if (curExpiration && *curExpiration < minExpiration)
|
||||
minExpiration = *curExpiration;
|
||||
auto minExpiration = ctx_.view().header().parentCloseTime.time_since_epoch().count() +
|
||||
(*slep)[sfSettleDelay];
|
||||
if (expiration && *expiration < minExpiration)
|
||||
minExpiration = *expiration;
|
||||
|
||||
if (*newExpiration < minExpiration)
|
||||
{
|
||||
return ctx_.view().rules().enabled(fixCleanup3_2_0) ? TER{tecNO_PERMISSION}
|
||||
: TER{temBAD_EXPIRATION};
|
||||
}
|
||||
(*slep)[~sfExpiration] = *newExpiration;
|
||||
if (*extend < minExpiration)
|
||||
return temBAD_EXPIRATION;
|
||||
(*slep)[~sfExpiration] = *extend;
|
||||
ctx_.view().update(slep);
|
||||
}
|
||||
|
||||
|
||||
@@ -2229,31 +2229,6 @@ private:
|
||||
ammAlice.withdraw(alice_, XRPAmount{9'999'999'999});
|
||||
BEAST_EXPECT(ammAlice.expectBalances(XRPAmount{1}, USD(10'000), IOUAmount{100}));
|
||||
});
|
||||
|
||||
// singleWithdrawEPrice: crafted ePrice = lptAMMBalance*f/amountBalance
|
||||
// makes the denominator (T*f - A*E) exactly zero.
|
||||
// Pre-fixCleanup3_3_0: std::overflow_error escapes to the
|
||||
// transactor backstop and is returned as tefEXCEPTION.
|
||||
// Post-fixCleanup3_3_0: denominator check returns tecAMM_FAILED.
|
||||
//
|
||||
// Pool: USD(100)/EUR(100), baseFee=1000 (1%).
|
||||
// Alice is the creator so her discounted fee is 100 (0.1%), f=0.001.
|
||||
// ePrice = lptAMMBalance(100) * f(0.001) / amountBalance(100) = 0.001
|
||||
testAMM(
|
||||
[&](AMM& ammAlice, Env& env) {
|
||||
auto const err =
|
||||
env.enabled(fixCleanup3_3_0) ? Ter(tecAMM_FAILED) : Ter(tefEXCEPTION);
|
||||
ammAlice.withdraw(
|
||||
WithdrawArg{
|
||||
.account = alice_,
|
||||
.asset1Out = USD(0),
|
||||
.maxEP = IOUAmount{1, -3}, // ePrice=0.001 → denom=0
|
||||
.err = err});
|
||||
},
|
||||
{{USD(100), EUR(100)}},
|
||||
1000,
|
||||
std::nullopt,
|
||||
{all - fixCleanup3_3_0, all});
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -1990,10 +1990,7 @@ public:
|
||||
run() override
|
||||
{
|
||||
using namespace test::jtx;
|
||||
// fixCleanup3_2_0 changes payment-channel error codes (tem* -> tec*)
|
||||
// and channel-closing semantics. This suite asserts the
|
||||
// pre-amendment behavior, so run it with the amendment disabled.
|
||||
FeatureBitset const all{testableAmendments() - fixCleanup3_2_0};
|
||||
FeatureBitset const all{testableAmendments()};
|
||||
testWithFeats(all);
|
||||
testDepositAuthCreds();
|
||||
testMetaAndOwnership(all - fixIncludeKeyletFields);
|
||||
|
||||
@@ -21,16 +21,11 @@
|
||||
#include <nudb/file.hpp>
|
||||
#include <nudb/native_file.hpp>
|
||||
#include <nudb/xxhasher.hpp>
|
||||
|
||||
#if XRPL_ROCKSDB_AVAILABLE
|
||||
|
||||
#include <rocksdb/db.h>
|
||||
#include <rocksdb/iterator.h>
|
||||
#include <rocksdb/options.h>
|
||||
#include <rocksdb/status.h>
|
||||
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
|
||||
@@ -100,17 +100,6 @@ class TMGetObjectByHash_test : public beast::unit_test::Suite
|
||||
return lastSentMessage_;
|
||||
}
|
||||
|
||||
// Synchronous test access to the JobQueue-dispatched processor.
|
||||
// The production path runs this on JtLedgerReq; tests need a
|
||||
// synchronous entry point to inspect the reply via send().
|
||||
// PeerImp::processGetObjectByHash is `protected` so the derived
|
||||
// test subclass can call it directly.
|
||||
void
|
||||
runProcessGetObjectByHash(std::shared_ptr<protocol::TMGetObjectByHash> const& m)
|
||||
{
|
||||
processGetObjectByHash(m);
|
||||
}
|
||||
|
||||
static void
|
||||
resetId()
|
||||
{
|
||||
@@ -190,10 +179,6 @@ class TMGetObjectByHash_test : public beast::unit_test::Suite
|
||||
/**
|
||||
* Test that reply is limited to hardMaxReplyNodes when more objects
|
||||
* are requested than the limit allows.
|
||||
*
|
||||
* `onMessage(TMGetObjectByHash)` dispatches the generic-query path
|
||||
* to the JobQueue, so tests invoke the synchronous processor
|
||||
* directly via `runProcessGetObjectByHash`.
|
||||
*/
|
||||
void
|
||||
testReplyLimit(size_t const numObjects, int const expectedReplySize)
|
||||
@@ -206,7 +191,8 @@ class TMGetObjectByHash_test : public beast::unit_test::Suite
|
||||
auto peer = createPeer(env);
|
||||
|
||||
auto request = createRequest(numObjects, env);
|
||||
peer->runProcessGetObjectByHash(request);
|
||||
// Call the onMessage handler
|
||||
peer->onMessage(request);
|
||||
|
||||
// Verify that a reply was sent
|
||||
auto sentMessage = peer->getLastSentMessage();
|
||||
|
||||
@@ -9,17 +9,19 @@
|
||||
#include <xrpl/core/JobQueue.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/AcceptedLedgerTx.h>
|
||||
#include <xrpl/ledger/BookListeners.h>
|
||||
#include <xrpl/ledger/OrderBookDB.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Book.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/MultiApiJson.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/server/NetworkOPs.h>
|
||||
#include <xrpl/shamap/SHAMapMissingNode.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@@ -305,10 +307,55 @@ OrderBookDBImpl::isBookToXRP(Asset const& asset, std::optional<Domain> const& do
|
||||
return xrpBooks_.contains(asset);
|
||||
}
|
||||
|
||||
hash_set<Book>
|
||||
affectedBooks(AcceptedLedgerTx const& alTx, beast::Journal const& j)
|
||||
BookListeners::pointer
|
||||
OrderBookDBImpl::makeBookListeners(Book const& book)
|
||||
{
|
||||
hash_set<Book> result;
|
||||
std::scoped_lock const sl(lock_);
|
||||
auto ret = getBookListeners(book);
|
||||
|
||||
if (!ret)
|
||||
{
|
||||
ret = std::make_shared<BookListeners>();
|
||||
|
||||
listeners_[book] = ret;
|
||||
XRPL_ASSERT(
|
||||
getBookListeners(book) == ret,
|
||||
"xrpl::OrderBookDB::makeBookListeners : result roundtrip "
|
||||
"lookup");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
BookListeners::pointer
|
||||
OrderBookDBImpl::getBookListeners(Book const& book)
|
||||
{
|
||||
BookListeners::pointer ret;
|
||||
std::scoped_lock const sl(lock_);
|
||||
|
||||
auto it0 = listeners_.find(book);
|
||||
if (it0 != listeners_.end())
|
||||
ret = it0->second;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Based on the meta, send the meta to the streams that are listening.
|
||||
// We need to determine which streams a given meta effects.
|
||||
void
|
||||
OrderBookDBImpl::processTxn(
|
||||
std::shared_ptr<ReadView const> const& ledger,
|
||||
AcceptedLedgerTx const& alTx,
|
||||
MultiApiJson const& jvObj)
|
||||
{
|
||||
std::scoped_lock const sl(lock_);
|
||||
|
||||
// For this particular transaction, maintain the set of unique
|
||||
// subscriptions that have already published it. This prevents sending
|
||||
// the transaction multiple times if it touches multiple ltOFFER
|
||||
// entries for the same book, or if it touches multiple books and a
|
||||
// single client has subscribed to those books.
|
||||
hash_set<std::uint64_t> havePublished;
|
||||
|
||||
for (auto const& node : alTx.getMeta().getNodes())
|
||||
{
|
||||
@@ -316,41 +363,40 @@ affectedBooks(AcceptedLedgerTx const& alTx, beast::Journal const& j)
|
||||
{
|
||||
if (node.getFieldU16(sfLedgerEntryType) == ltOFFER)
|
||||
{
|
||||
auto extract = [&](SField const& field) {
|
||||
auto process = [&, this](SField const& field) {
|
||||
if (auto data = dynamic_cast<STObject const*>(node.peekAtPField(field)); data &&
|
||||
data->isFieldPresent(sfTakerPays) && data->isFieldPresent(sfTakerGets))
|
||||
{
|
||||
result.emplace(
|
||||
data->getFieldAmount(sfTakerGets).asset(),
|
||||
data->getFieldAmount(sfTakerPays).asset(),
|
||||
(*data)[~sfDomainID]);
|
||||
auto listeners = getBookListeners(
|
||||
{data->getFieldAmount(sfTakerGets).asset(),
|
||||
data->getFieldAmount(sfTakerPays).asset(),
|
||||
(*data)[~sfDomainID]});
|
||||
if (listeners)
|
||||
listeners->publish(jvObj, havePublished);
|
||||
}
|
||||
};
|
||||
|
||||
// We need a field that contains the TakerGets and TakerPays
|
||||
// parameters.
|
||||
if (node.getFName() == sfModifiedNode)
|
||||
{
|
||||
extract(sfPreviousFields);
|
||||
process(sfPreviousFields);
|
||||
}
|
||||
else if (node.getFName() == sfCreatedNode)
|
||||
{
|
||||
extract(sfNewFields);
|
||||
process(sfNewFields);
|
||||
}
|
||||
else if (node.getFName() == sfDeletedNode)
|
||||
{
|
||||
extract(sfFinalFields);
|
||||
process(sfFinalFields);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (std::exception const& ex)
|
||||
{
|
||||
// The bad node is skipped; other affected books in the same
|
||||
// transaction are still returned. Logged at warn so a malformed
|
||||
// offer node is visible to operators.
|
||||
JLOG(j.warn()) << "affectedBooks: skipping malformed node (" << ex.what() << ")";
|
||||
JLOG(j_.info()) << "processTxn: field not found (" << ex.what() << ")";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/AcceptedLedgerTx.h>
|
||||
#include <xrpl/ledger/BookListeners.h>
|
||||
#include <xrpl/ledger/OrderBookDB.h>
|
||||
#include <xrpl/protocol/MultiApiJson.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
|
||||
#include <mutex>
|
||||
@@ -51,6 +54,18 @@ public:
|
||||
void
|
||||
update(std::shared_ptr<ReadView const> const& ledger);
|
||||
|
||||
// see if this txn effects any orderbook
|
||||
void
|
||||
processTxn(
|
||||
std::shared_ptr<ReadView const> const& ledger,
|
||||
AcceptedLedgerTx const& alTx,
|
||||
MultiApiJson const& jvObj) override;
|
||||
|
||||
BookListeners::pointer
|
||||
getBookListeners(Book const&) override;
|
||||
BookListeners::pointer
|
||||
makeBookListeners(Book const&) override;
|
||||
|
||||
private:
|
||||
std::reference_wrapper<ServiceRegistry> registry_;
|
||||
int const pathSearchMax_;
|
||||
@@ -69,6 +84,10 @@ private:
|
||||
|
||||
std::recursive_mutex lock_;
|
||||
|
||||
using BookToListenersMap = hash_map<Book, BookListeners::pointer>;
|
||||
|
||||
BookToListenersMap listeners_;
|
||||
|
||||
std::atomic<std::uint32_t> seq_;
|
||||
|
||||
beast::Journal const j_;
|
||||
|
||||
@@ -527,8 +527,6 @@ public:
|
||||
updateLocalTx(ReadView const& view) override;
|
||||
std::size_t
|
||||
getLocalTxCount() override;
|
||||
std::size_t
|
||||
getBookSubscribersCount() override;
|
||||
|
||||
//
|
||||
// Monitoring: publisher side.
|
||||
@@ -588,9 +586,7 @@ public:
|
||||
bool
|
||||
subBook(InfoSub::ref ispListener, Book const&) override;
|
||||
bool
|
||||
unsubBook(InfoSub::ref ispListener, Book const&) override;
|
||||
bool
|
||||
unsubBookInternal(std::uint64_t uListener, Book const&) override;
|
||||
unsubBook(std::uint64_t uListener, Book const&) override;
|
||||
|
||||
bool
|
||||
subManifests(InfoSub::ref ispListener) override;
|
||||
@@ -633,12 +629,6 @@ public:
|
||||
bool
|
||||
tryRemoveRpcSub(std::string const& strUrl) override;
|
||||
|
||||
beast::Journal const&
|
||||
journal() const override
|
||||
{
|
||||
return journal_;
|
||||
}
|
||||
|
||||
void
|
||||
stop() override
|
||||
{
|
||||
@@ -715,32 +705,6 @@ private:
|
||||
AcceptedLedgerTx const& transaction,
|
||||
bool last);
|
||||
|
||||
/**
|
||||
* Fan transaction notifications out to all book subscribers.
|
||||
*
|
||||
* Extracts the set of order books affected by @p transaction, then
|
||||
* delivers @p jvObj to every live subscriber of those books.
|
||||
*
|
||||
* Uses a two-pass design to keep subLock_ hold time short:
|
||||
* 1. Under subLock_, collect strong InfoSub pointers for all live
|
||||
* subscribers and prune any expired weak_ptrs encountered.
|
||||
* 2. Release subLock_, then call send() on each collected pointer.
|
||||
*
|
||||
* @param transaction The accepted ledger transaction to inspect.
|
||||
* @param jvObj JSON representation of the transaction to deliver.
|
||||
*
|
||||
* @note Thread-safety: acquires subLock_ for the collection pass only.
|
||||
* send() is intentionally called outside the lock to avoid blocking
|
||||
* all other sub/unsub/publish paths while I/O is in progress.
|
||||
* @note Contention: subLock_ is shared with all other subscription types.
|
||||
* On high-throughput nodes processing multi-hop payments that touch
|
||||
* many offer nodes, this pass holds subLock_ longer than the old
|
||||
* per-book BookListeners locks did. This is an accepted trade-off
|
||||
* for lock-domain simplicity.
|
||||
*/
|
||||
void
|
||||
pubBookTransaction(AcceptedLedgerTx const& transaction, MultiApiJson const& jvObj);
|
||||
|
||||
void
|
||||
pubProposedAccountTransaction(
|
||||
std::shared_ptr<ReadView const> const& ledger,
|
||||
@@ -838,19 +802,8 @@ private:
|
||||
|
||||
LedgerMaster& ledgerMaster_;
|
||||
|
||||
/** Maps each order book to its current set of subscribers.
|
||||
* Outer key: the Book (currency pair + optional domain).
|
||||
* Inner key: InfoSub::seq (unique per connection).
|
||||
* Inner value: weak_ptr so that a dropped connection does not prevent
|
||||
* the InfoSub from being destroyed; expired entries are pruned lazily
|
||||
* by pubBookTransaction and eagerly by unsubBookInternal (~InfoSub path).
|
||||
* Guarded by subLock_.
|
||||
*/
|
||||
using SubBookMapType = hash_map<Book, SubMapType>;
|
||||
|
||||
SubInfoMapType subAccount_;
|
||||
SubInfoMapType subRTAccount_;
|
||||
SubBookMapType subBook_; ///< Guarded by subLock_.
|
||||
|
||||
subRpcMapType rpcSubMap_;
|
||||
|
||||
@@ -3239,16 +3192,6 @@ NetworkOPsImp::getLocalTxCount()
|
||||
return localTX_->size();
|
||||
}
|
||||
|
||||
std::size_t
|
||||
NetworkOPsImp::getBookSubscribersCount()
|
||||
{
|
||||
std::scoped_lock const sl(subLock_);
|
||||
std::size_t total = 0;
|
||||
for (auto const& [_, subs] : subBook_)
|
||||
total += subs.size();
|
||||
return total;
|
||||
}
|
||||
|
||||
// This routine should only be used to publish accepted or validated
|
||||
// transactions.
|
||||
MultiApiJson
|
||||
@@ -3410,89 +3353,11 @@ NetworkOPsImp::pubValidatedTransaction(
|
||||
}
|
||||
|
||||
if (transaction.getResult() == tesSUCCESS)
|
||||
pubBookTransaction(transaction, jvObj);
|
||||
registry_.get().getOrderBookDB().processTxn(ledger, transaction, jvObj);
|
||||
|
||||
pubAccountTransaction(ledger, transaction, last);
|
||||
}
|
||||
|
||||
void
|
||||
NetworkOPsImp::pubBookTransaction(AcceptedLedgerTx const& alTx, MultiApiJson const& jvObj)
|
||||
{
|
||||
auto const books = affectedBooks(alTx, journal_);
|
||||
if (books.empty())
|
||||
return;
|
||||
|
||||
// Two-pass design:
|
||||
//
|
||||
// 1. Under subLock_, walk subBook_, collect a strong pointer for each
|
||||
// unique listener (and prune any expired weak_ptrs we encounter).
|
||||
// 2. Release subLock_, then send to each collected listener.
|
||||
//
|
||||
// Reasoning:
|
||||
// * send() can be slow / blocking, so holding subLock_ across it would
|
||||
// stall every other sub/unsub/pub path on this server (see the matching
|
||||
// TODO above pubServer at line ~2275).
|
||||
// * A strong pointer destructed while subLock_ is held risks running
|
||||
// ~InfoSub() in-line, which re-enters unsubBook() and mutates the very
|
||||
// subBook_/SubMapType being iterated -> dangling iterator UB.
|
||||
//
|
||||
// Releasing subLock_ before any InfoSub::pointer can decay solves both.
|
||||
// ~InfoSub() reacquires subLock_ via unsubBook() on its own and serializes
|
||||
// safely with concurrent traffic.
|
||||
|
||||
std::vector<InfoSub::pointer> listeners;
|
||||
hash_set<std::uint64_t> seen;
|
||||
|
||||
// Sized for the common case where every affected book has at most
|
||||
// one subscriber. Multi-subscriber books trigger reallocation, but
|
||||
// that is rare and the upper-bound estimate (sum of per-book sizes)
|
||||
// would itself require walking subBook_ twice.
|
||||
listeners.reserve(books.size());
|
||||
seen.reserve(books.size());
|
||||
|
||||
{
|
||||
std::scoped_lock const sl(subLock_);
|
||||
|
||||
for (auto const& book : books)
|
||||
{
|
||||
auto it = subBook_.find(book);
|
||||
if (it == subBook_.end())
|
||||
continue;
|
||||
|
||||
for (auto sit = it->second.begin(); sit != it->second.end();)
|
||||
{
|
||||
if (auto p = sit->second.lock())
|
||||
{
|
||||
// Defensive: subBook_ entries are normally cleared by
|
||||
// ~InfoSub() -> unsubBook(), so we rarely see expired
|
||||
// weak_ptrs here. The else branch covers the narrow race
|
||||
// where the last strong ref is dropped between insertion
|
||||
// and our lock() call.
|
||||
if (seen.emplace(p->getSeq()).second)
|
||||
listeners.emplace_back(std::move(p));
|
||||
++sit;
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(journal_.debug())
|
||||
<< "pubBookTransaction: pruning expired weak_ptr for seq=" << sit->first;
|
||||
sit = it->second.erase(sit);
|
||||
}
|
||||
}
|
||||
|
||||
if (it->second.empty())
|
||||
subBook_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& p : listeners)
|
||||
{
|
||||
jvObj.visit(p->getApiVersion(), [&](json::Value const& jv) { p->send(jv, true); });
|
||||
}
|
||||
// listeners destructs here, outside subLock_; ~InfoSub (if any fires)
|
||||
// will reacquire subLock_ via unsubBook with no iterator hazard.
|
||||
}
|
||||
|
||||
void
|
||||
NetworkOPsImp::pubAccountTransaction(
|
||||
std::shared_ptr<ReadView const> const& ledger,
|
||||
@@ -4146,39 +4011,26 @@ NetworkOPsImp::unsubAccountHistoryInternal(
|
||||
bool
|
||||
NetworkOPsImp::subBook(InfoSub::ref isrListener, Book const& book)
|
||||
{
|
||||
// Server-side insert first, then InfoSub bookkeeping. If the InfoSub-side
|
||||
// insert throws, the orphan in subBook_ is cleared by the expired-weak_ptr
|
||||
// prune in pubBookTransaction. With the reverse ordering, ~InfoSub would
|
||||
// call unsubBookInternal for a key that was never inserted server-side.
|
||||
if (auto listeners = registry_.get().getOrderBookDB().makeBookListeners(book))
|
||||
{
|
||||
std::scoped_lock const sl(subLock_);
|
||||
subBook_[book].try_emplace(isrListener->getSeq(), isrListener);
|
||||
listeners->addSubscriber(isrListener);
|
||||
}
|
||||
else
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::NetworkOPsImp::subBook : null book listeners");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
isrListener->insertBookSubscription(book);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
NetworkOPsImp::unsubBook(InfoSub::ref isrListener, Book const& book)
|
||||
NetworkOPsImp::unsubBook(std::uint64_t uSeq, Book const& book)
|
||||
{
|
||||
// Mirrors unsubAccount: clear the per-subscriber tracking set first so
|
||||
// ~InfoSub does not re-issue an unsubBookInternal for a book the caller
|
||||
// already removed, then erase the server-side entry.
|
||||
isrListener->deleteBookSubscription(book);
|
||||
return unsubBookInternal(isrListener->getSeq(), book);
|
||||
}
|
||||
if (auto listeners = registry_.get().getOrderBookDB().getBookListeners(book))
|
||||
listeners->removeSubscriber(uSeq);
|
||||
|
||||
bool
|
||||
NetworkOPsImp::unsubBookInternal(std::uint64_t uSeq, Book const& book)
|
||||
{
|
||||
std::scoped_lock const sl(subLock_);
|
||||
auto it = subBook_.find(book);
|
||||
if (it == subBook_.end())
|
||||
return false;
|
||||
bool const erased = it->second.erase(uSeq) != 0u;
|
||||
if (it->second.empty())
|
||||
subBook_.erase(it);
|
||||
return erased;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#include <xrpld/peerfinder/PeerfinderManager.h>
|
||||
#include <xrpld/peerfinder/Slot.h>
|
||||
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/SHAMapHash.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
@@ -59,6 +58,7 @@
|
||||
#include <xrpl/resource/Disposition.h>
|
||||
#include <xrpl/resource/Fees.h>
|
||||
#include <xrpl/resource/Gossip.h>
|
||||
#include <xrpl/server/Handoff.h>
|
||||
#include <xrpl/server/LoadFeeTrack.h>
|
||||
#include <xrpl/server/NetworkOPs.h>
|
||||
#include <xrpl/shamap/SHAMapNodeID.h>
|
||||
@@ -68,7 +68,6 @@
|
||||
#include <boost/asio/bind_executor.hpp>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/completion_condition.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
@@ -82,7 +81,6 @@
|
||||
#include <xrpl.pb.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
@@ -394,25 +392,13 @@ PeerImp::removeTxQueue(uint256 const& hash)
|
||||
void
|
||||
PeerImp::charge(Resource::Charge const& fee, std::string const& context)
|
||||
{
|
||||
dispatch(strand_, [this, self = shared_from_this(), fee, context]() {
|
||||
if ((usage_.charge(fee, context) == Resource::Disposition::Drop) &&
|
||||
usage_.disconnect(pJournal_))
|
||||
{
|
||||
// Idempotent: only the first worker to observe Drop counts the
|
||||
// metric and posts fail(). Without the guard, several queued
|
||||
// workers can all see Drop before fail() lands on the strand,
|
||||
// overcounting peerDisconnectsCharges_ and posting duplicate
|
||||
// shutdowns. fail(std::string const&) self-posts to strand_
|
||||
// when invoked off-strand.
|
||||
bool expected = false;
|
||||
if (chargeDisconnectFired_.compare_exchange_strong(
|
||||
expected, true, std::memory_order_acq_rel))
|
||||
{
|
||||
overlay_.incPeerDisconnectCharges();
|
||||
fail("charge: Resources");
|
||||
}
|
||||
}
|
||||
});
|
||||
if ((usage_.charge(fee, context) == Resource::Disposition::Drop) &&
|
||||
usage_.disconnect(pJournal_) && strand_.running_in_this_thread())
|
||||
{
|
||||
// Sever the connection
|
||||
overlay_.incPeerDisconnectCharges();
|
||||
fail("charge: Resources");
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -2046,7 +2032,7 @@ PeerImp::checkTracking(std::uint32_t validationSeq)
|
||||
void
|
||||
PeerImp::checkTracking(std::uint32_t seq1, std::uint32_t seq2)
|
||||
{
|
||||
std::uint32_t const diff = std::max(seq1, seq2) - std::min(seq1, seq2);
|
||||
int const diff = std::max(seq1, seq2) - std::min(seq1, seq2);
|
||||
|
||||
if (diff < Tuning::kConvergedLedgerLimit)
|
||||
{
|
||||
@@ -2487,63 +2473,63 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMGetObjectByHash> const& m)
|
||||
return;
|
||||
}
|
||||
|
||||
protocol::TMGetObjectByHash reply;
|
||||
|
||||
reply.set_query(false);
|
||||
|
||||
reply.set_type(packet.type());
|
||||
|
||||
if (packet.has_ledgerhash())
|
||||
{
|
||||
if (!stringIsUInt256Sized(packet.ledgerhash()))
|
||||
{
|
||||
JLOG(pJournal_.debug()) << "GetObj: malformed ledgerhash from peer " << id_;
|
||||
fee_.update(Resource::kFeeMalformedRequest, "get object ledger hash");
|
||||
fee_.update(Resource::kFeeMalformedRequest, "ledger hash");
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Reject oversized requests before touching the NodeStore.
|
||||
// The legitimate upper bound (InboundLedger::getNeededHashes())
|
||||
// is 8 hashes; anything beyond kHardMaxReplyNodes is non-conforming.
|
||||
if (packet.objects_size() > Tuning::kHardMaxReplyNodes)
|
||||
{
|
||||
JLOG(pJournal_.warn())
|
||||
<< "GetObj: oversized request from peer " << id_ << " (" << packet.objects_size()
|
||||
<< " > " << Tuning::kHardMaxReplyNodes << ")";
|
||||
fee_.update(Resource::kFeeInvalidData, "oversized get object request");
|
||||
return;
|
||||
|
||||
reply.set_ledgerhash(packet.ledgerhash());
|
||||
}
|
||||
|
||||
// Dispatch heavy synchronous NodeStore lookups off the peer's
|
||||
// I/O strand and onto the bounded job queue, mirroring the pattern
|
||||
// used by processLedgerRequest.
|
||||
std::weak_ptr<PeerImp> const weak = shared_from_this();
|
||||
bool const queued = app_.getJobQueue().addJob(JtLedgerReq, "RcvGetObjByHash", [weak, m]() {
|
||||
auto peer = weak.lock();
|
||||
if (!peer)
|
||||
return;
|
||||
try
|
||||
{
|
||||
peer->processGetObjectByHash(m);
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
// Surface backend failures (NodeStore I/O, allocation)
|
||||
// back through the resource model so a misbehaving peer
|
||||
// is still accountable rather than silently dropped.
|
||||
JLOG(peer->pJournal_.warn()) << "GetObj: handler threw: " << e.what();
|
||||
peer->charge(Resource::kFeeRequestNoReply, "get object handler exception");
|
||||
}
|
||||
});
|
||||
if (!queued)
|
||||
fee_.update(Resource::kFeeModerateBurdenPeer, " received a get object by hash request");
|
||||
|
||||
// This is a very minimal implementation
|
||||
for (int i = 0; i < packet.objects_size(); ++i)
|
||||
{
|
||||
// The JobQueue is no longer accepting new work (typically
|
||||
// because it is shutting down / has been joined).
|
||||
JLOG(pJournal_.warn()) << "GetObj: job queue refused request from peer " << id_;
|
||||
return;
|
||||
auto const& obj = packet.objects(i);
|
||||
if (obj.has_hash() && stringIsUInt256Sized(obj.hash()))
|
||||
{
|
||||
uint256 const hash = uint256::fromRaw(obj.hash());
|
||||
// VFALCO TODO Move this someplace more sensible so we dont
|
||||
// need to inject the NodeStore interfaces.
|
||||
std::uint32_t const seq{obj.has_ledgerseq() ? obj.ledgerseq() : 0};
|
||||
auto nodeObject{app_.getNodeStore().fetchNodeObject(hash, seq)};
|
||||
if (nodeObject)
|
||||
{
|
||||
protocol::TMIndexedObject& newObj = *reply.add_objects();
|
||||
newObj.set_hash(hash.begin(), hash.size());
|
||||
newObj.set_data(&nodeObject->getData().front(), nodeObject->getData().size());
|
||||
|
||||
if (obj.has_nodeid())
|
||||
newObj.set_index(obj.nodeid());
|
||||
if (obj.has_ledgerseq())
|
||||
newObj.set_ledgerseq(obj.ledgerseq());
|
||||
|
||||
// Check if by adding this object, reply has reached its
|
||||
// limit
|
||||
if (reply.objects_size() >= Tuning::kHardMaxReplyNodes)
|
||||
{
|
||||
fee_.update(
|
||||
Resource::kFeeModerateBurdenPeer,
|
||||
"Reply limit reached. Truncating reply.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Admission-time charge: a peer that floods enqueues would
|
||||
// otherwise be billed only the trivial onMessageEnd fee per
|
||||
// message until the JobQueue catches up, re-creating an
|
||||
// uncharged DoS window. Charge the base burden up-front (after
|
||||
// a successful enqueue); the per-lookup differential is added
|
||||
// in the worker.
|
||||
fee_.update(Resource::kFeeModerateBurdenPeer, "received a get object by hash request");
|
||||
JLOG(pJournal_.trace()) << "GetObj: " << reply.objects_size() << " of "
|
||||
<< packet.objects_size();
|
||||
send(std::make_shared<Message>(reply, protocol::mtGET_OBJECTS));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2599,69 +2585,6 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMGetObjectByHash> const& m)
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PeerImp::processGetObjectByHash(std::shared_ptr<protocol::TMGetObjectByHash> const& m)
|
||||
{
|
||||
protocol::TMGetObjectByHash const& packet = *m;
|
||||
|
||||
protocol::TMGetObjectByHash reply;
|
||||
reply.set_query(false);
|
||||
reply.set_type(packet.type());
|
||||
|
||||
if (packet.has_ledgerhash())
|
||||
{
|
||||
reply.set_ledgerhash(packet.ledgerhash());
|
||||
}
|
||||
|
||||
// Defense in depth: caller (onMessage) already validates cheap
|
||||
// structural properties of the request before dispatching here:
|
||||
// - objects_size() <= kHardMaxReplyNodes (oversize gate)
|
||||
// - if has_ledgerhash() then ledgerhash is uint256-sized
|
||||
// The iteration cap below mirrors the oversize gate so this method
|
||||
// remains safe if invoked directly by tests or future callers, and
|
||||
// a peer cannot drive unbounded NodeStore lookups by sending
|
||||
// non-existent hashes.
|
||||
int const requested = packet.objects_size();
|
||||
int const iterLimit = std::min(requested, Tuning::kHardMaxReplyNodes);
|
||||
|
||||
for (int i = 0; i < iterLimit; ++i)
|
||||
{
|
||||
auto const& obj = packet.objects(i);
|
||||
if (!obj.has_hash() || !stringIsUInt256Sized(obj.hash()))
|
||||
continue;
|
||||
|
||||
uint256 const hash = uint256::fromRaw(obj.hash());
|
||||
// VFALCO TODO Move this someplace more sensible so we don't
|
||||
// need to inject the NodeStore interfaces.
|
||||
std::uint32_t const seq{obj.has_ledgerseq() ? obj.ledgerseq() : 0};
|
||||
auto const nodeObject = app_.getNodeStore().fetchNodeObject(hash, seq);
|
||||
if (!nodeObject)
|
||||
continue;
|
||||
|
||||
protocol::TMIndexedObject& newObj = *reply.add_objects();
|
||||
newObj.set_hash(hash.begin(), hash.size());
|
||||
auto const& data = nodeObject->getData();
|
||||
newObj.set_data(data.data(), data.size());
|
||||
if (obj.has_nodeid())
|
||||
newObj.set_index(obj.nodeid());
|
||||
if (obj.has_ledgerseq())
|
||||
newObj.set_ledgerseq(obj.ledgerseq());
|
||||
}
|
||||
|
||||
// Apply work-proportional charge. `charge()` posts the disconnect
|
||||
// step (if any) back to strand_, so it is safe to call from this
|
||||
// JobQueue worker thread.
|
||||
charge(
|
||||
// We pass `requested` directly here, instead of actual lookups done. Which could be
|
||||
// std::min(packet.objects_size(), static_cast<int>(Tuning::kHardMaxReplyNodes));
|
||||
// Because we want to charge as per the request size, to discourage large requests.
|
||||
computeGetObjectByHashFee(requested, reply.objects_size()),
|
||||
"processed get object by hash request");
|
||||
|
||||
JLOG(pJournal_.trace()) << "GetObj: " << reply.objects_size() << " of " << requested;
|
||||
send(std::make_shared<Message>(reply, protocol::mtGET_OBJECTS));
|
||||
}
|
||||
|
||||
void
|
||||
PeerImp::onMessage(std::shared_ptr<protocol::TMHaveTransactions> const& m)
|
||||
{
|
||||
@@ -3489,53 +3412,6 @@ PeerImp::processLedgerRequest(std::shared_ptr<protocol::TMGetLedger> const& m)
|
||||
send(std::make_shared<Message>(ledgerData, protocol::mtLEDGER_DATA));
|
||||
}
|
||||
|
||||
// Differential pricing helper. Returns only the *dynamic* component
|
||||
// of the per-message charge — the base `kFeeModerateBurdenPeer` is
|
||||
// applied at admission time in `onMessage(TMGetObjectByHash)` so a
|
||||
// high traffic client pays for the message regardless of when (or
|
||||
// whether) the worker runs.
|
||||
//
|
||||
// Dynamic charge model:
|
||||
//
|
||||
// billable = max(0, requested - kFreeObjectsPerRequest)
|
||||
// missed = max(0, requested - found)
|
||||
// billableMisses = min(missed, billable) // misses billed first
|
||||
// billableHits = billable - billableMisses
|
||||
// sizeBand = (requested > kBandMediumMax) ? kCostBandLarge
|
||||
// : (requested > kBandSmallMax) ? kCostBandMedium
|
||||
// : kCostBandSmall
|
||||
// dynamic = billableHits * kCostPerLookupHit
|
||||
// + billableMisses * kCostPerLookupMiss
|
||||
// + sizeBand
|
||||
//
|
||||
// Misses are billed first against the billable budget because a node store
|
||||
// seek dominates a cache hit and because invalid hashes are ~100% miss by construction.
|
||||
Resource::Charge
|
||||
PeerImp::computeGetObjectByHashFee(int const requested, int const found)
|
||||
{
|
||||
int const billable = std::max(0, requested - static_cast<int>(Tuning::kFreeObjectsPerRequest));
|
||||
// Clamp `missed` so a future caller passing found > requested cannot
|
||||
// produce a negative value that flips the hits/misses split.
|
||||
int const missed = std::max(0, requested - found);
|
||||
int const billableMisses = std::min(missed, billable);
|
||||
int const billableHits = billable - billableMisses;
|
||||
|
||||
int sizeBand = Tuning::kCostBandSmall;
|
||||
if (requested > Tuning::kBandMediumMax)
|
||||
{
|
||||
sizeBand = Tuning::kCostBandLarge;
|
||||
}
|
||||
else if (requested > Tuning::kBandSmallMax)
|
||||
{
|
||||
sizeBand = Tuning::kCostBandMedium;
|
||||
}
|
||||
|
||||
int const dynamic = (billableHits * Tuning::kCostPerLookupHit) +
|
||||
(billableMisses * Tuning::kCostPerLookupMiss) + sizeBand;
|
||||
|
||||
return Resource::Charge(dynamic, "GetObject differential");
|
||||
}
|
||||
|
||||
int
|
||||
PeerImp::getScore(bool haveItem) const
|
||||
{
|
||||
|
||||
@@ -147,12 +147,6 @@ private:
|
||||
protocol::TMStatusChange lastStatus_;
|
||||
Resource::Consumer usage_;
|
||||
ChargeWithContext fee_;
|
||||
|
||||
// One-shot guard so concurrent JobQueue workers cannot double-count
|
||||
// the per-connection peer-disconnect-by-charge metric (and cannot
|
||||
// post duplicate fail() calls) when several queued requests cross
|
||||
// kDropThreshold before the first fail() lands on the strand.
|
||||
std::atomic<bool> chargeDisconnectFired_{false};
|
||||
std::shared_ptr<PeerFinder::Slot> const slot_;
|
||||
boost::beast::multi_buffer readBuffer_;
|
||||
http_request_type request_;
|
||||
@@ -630,67 +624,6 @@ private:
|
||||
|
||||
void
|
||||
processLedgerRequest(std::shared_ptr<protocol::TMGetLedger> const& m);
|
||||
|
||||
protected:
|
||||
// Kept `protected` so test subclasses (see
|
||||
// TMGetObjectByHash_test) can drive the
|
||||
// synchronous processor and the differential-pricing helper without
|
||||
// routing through the JobQueue or going through `friend` plumbing.
|
||||
// Production callers reach these members only via
|
||||
// `onMessage(TMGetObjectByHash)` → JobQueue → `processGetObjectByHash`.
|
||||
|
||||
/** Process a generic-query TMGetObjectByHash message.
|
||||
|
||||
Dispatched from `onMessage(TMGetObjectByHash)` to the JobQueue
|
||||
(`JtLedgerReq`) so synchronous NodeStore lookups do not block the
|
||||
peer's I/O strand. Caps iteration at `Tuning::kHardMaxReplyNodes`
|
||||
regardless of hit/miss outcome and applies differential pricing
|
||||
via `computeGetObjectByHashFee()` after the fetch loop completes.
|
||||
|
||||
@param m The protocol message containing requested object hashes.
|
||||
*/
|
||||
void
|
||||
processGetObjectByHash(std::shared_ptr<protocol::TMGetObjectByHash> const& m);
|
||||
|
||||
/** Compute the per-message resource charge for a TMGetObjectByHash
|
||||
request based on how much work was actually performed.
|
||||
|
||||
The charge has three components on top of the base
|
||||
`Resource::kFeeModerateBurdenPeer`:
|
||||
- per-hit lookup cost (cheap; usually served from cache)
|
||||
- per-miss lookup cost (expensive node store seeks)
|
||||
- request-size band surcharge (escalates abusive batch sizes)
|
||||
|
||||
The first `Tuning::kFreeObjectsPerRequest` objects are free so
|
||||
that legitimate `InboundLedger::getNeededHashes()` traffic
|
||||
(at most 8 objects) is unaffected.
|
||||
|
||||
@param requested Number of objects requested by the message. This
|
||||
value is used for request-size pricing and may
|
||||
exceed `Tuning::kHardMaxReplyNodes` when this
|
||||
helper is called directly, even though processing
|
||||
caps the iterations to `Tuning::kHardMaxReplyNodes`.
|
||||
@param found Number of objects successfully returned in the
|
||||
reply.
|
||||
@return A `Resource::Charge` whose cost reflects the work performed.
|
||||
*/
|
||||
static Resource::Charge
|
||||
computeGetObjectByHashFee(int const requested, int const found);
|
||||
|
||||
/** Read-only accessor for the accumulated peer-message charge.
|
||||
|
||||
Exposed at `protected` scope so test subclasses can verify the
|
||||
oversized-request rejection path (Layer 1) without invoking the
|
||||
full JobQueue handler. Production callers should never read this back —
|
||||
the value is consumed by `charge()`/`disconnect()` internally.
|
||||
|
||||
@return The current `Resource::Charge` accumulated on `fee_`.
|
||||
*/
|
||||
Resource::Charge
|
||||
currentFeeCharge() const
|
||||
{
|
||||
return fee_.fee;
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
#pragma once
|
||||
#include <xrpl/shamap/SHAMapInnerNode.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl::Tuning {
|
||||
|
||||
/** How many ledgers off a server can be and we will
|
||||
still consider it converged */
|
||||
static constexpr std::uint32_t kConvergedLedgerLimit = 24;
|
||||
static constexpr auto kConvergedLedgerLimit = 24;
|
||||
|
||||
/** How many ledgers off a server has to be before we
|
||||
consider it diverged */
|
||||
static constexpr std::uint32_t kDivergedLedgerLimit = 128;
|
||||
static constexpr auto kDivergedLedgerLimit = 128;
|
||||
|
||||
/** The soft cap on the number of ledger entries in a single reply. */
|
||||
static constexpr auto kSoftMaxReplyNodes = 8192;
|
||||
@@ -41,92 +37,4 @@ static constexpr auto kMaxQueryDepth = 3;
|
||||
/** Size of buffer used to read from the socket. */
|
||||
constexpr std::size_t kReadBufferBytes = 16384;
|
||||
|
||||
/** TMGetObjectByHash differential pricing.
|
||||
|
||||
Honest peers ask for at most 8 hashes per call (the header, or up to
|
||||
4 state + 4 tx hashes from `InboundLedger::getNeededHashes()`). The
|
||||
free tier covers them at zero cost. Beyond that, each lookup is billed:
|
||||
'misses' cost much more than 'hits' because a miss does a node store seek
|
||||
while a hit is usually served from cache. On top of that, a size-band
|
||||
surcharge kicks in for larger requests so an attacker who crams a
|
||||
single message with thousands of hashes blows past
|
||||
`Resource::kDropThreshold` and gets disconnected.
|
||||
|
||||
The numbers below are picked to keep three things true given
|
||||
`kDropThreshold = 25000`:
|
||||
|
||||
- Honest traffic (<= 8 objects per request) is free.
|
||||
- A single all-miss request at `kHardMaxReplyNodes` (12288) costs
|
||||
more than the drop threshold, so an attacker gets dropped in one
|
||||
message.
|
||||
- A peer spamming 1024-object hit-only requests gets dropped in
|
||||
~19 messages — fast enough to be useful, slow enough that an
|
||||
honest peer momentarily sending oversized requests has time to
|
||||
back off. */
|
||||
|
||||
/** How many objects a request can ask for before per-lookup billing
|
||||
begins?
|
||||
Twice the honest peak (8) so a peer that occasionally retries a hash
|
||||
never trips pricing. Same value as `SHAMapInnerNode::kBranchFactor`;
|
||||
that's a coincidence, not a requirement. */
|
||||
static constexpr auto kFreeObjectsPerRequest = 16;
|
||||
|
||||
/** Cost of one cache-hit lookup. The unit; everything else is a
|
||||
multiple of this. */
|
||||
static constexpr auto kCostPerLookupHit = 1;
|
||||
|
||||
/** Cost of one node-store miss, in units of `kCostPerLookupHit`.
|
||||
|
||||
A miss does a node store disk seek; a hit usually comes from cache.
|
||||
The 8x ratio is an order-of-magnitude guess at the latency gap on
|
||||
SSD-backed nodes, not a measured number. The math only requires this
|
||||
to be at least 2 — any smaller and a full-miss request at the hard
|
||||
cap wouldn't trip the drop threshold. 8 leaves headroom: if
|
||||
`kDropThreshold` goes up or `kHardMaxReplyNodes` comes down, the
|
||||
drop-on-attack property still holds without a code change. */
|
||||
static constexpr auto kCostPerLookupMiss = 8;
|
||||
|
||||
/** Size-band surcharges. Whichever band a request's size falls into,
|
||||
its surcharge is added once on top of the per-lookup cost.
|
||||
|
||||
The job of the surcharge is to make crossing a band edge feel like
|
||||
a step, not a slope. With these values, the cost roughly doubles or triples at each cliff:
|
||||
|
||||
n=64: costs 48 => n=65 costs 149 (~3x jump)
|
||||
n=1024: costs 1108 => n=1025 costs 2009 (~2x jump)
|
||||
|
||||
The 10x step between medium and large mirrors the ~16x step
|
||||
between the band edges (64 -> 1024) so the cliff feels comparable
|
||||
at both scales.
|
||||
*/
|
||||
static constexpr auto kCostBandSmall = 0;
|
||||
static constexpr auto kCostBandMedium = 100;
|
||||
static constexpr auto kCostBandLarge = 1000;
|
||||
|
||||
/** How many hashes per type an honest peer asks for at a time.
|
||||
|
||||
Matches the `4` passed to `neededStateHashes(4)` and
|
||||
`neededTxHashes(4)` in `InboundLedger::getNeededHashes()`. Kept here
|
||||
instead of imported from the ledger module so overlay stays
|
||||
self-contained; if that `4` ever changes, update this in lockstep or
|
||||
the band thresholds below will start charging honest peers. */
|
||||
static constexpr auto kLegitHashesPerType = 4;
|
||||
|
||||
/** Cutoffs that decide which size band a request falls into.
|
||||
|
||||
A SHAMap inner node has 16 children; an honest peer asks for 4
|
||||
hashes per type. So:
|
||||
|
||||
kBandSmallMax = 4 * 16 = 64 // one inner node's worth
|
||||
kBandMediumMax = 4 * 16^2 = 1024 // a depth-2 subtree's worth
|
||||
|
||||
A request up to 64 objects is small (no surcharge); up to 1024 is
|
||||
medium; anything larger is large. The bounds are inclusive: a
|
||||
request of exactly 64 is small, 65 is medium. Anything past 1024 is
|
||||
well beyond what the honest sync path produces, so it's billed at
|
||||
the large rate to drive attack-shaped traffic over the drop
|
||||
threshold quickly. */
|
||||
static constexpr auto kBandSmallMax = kLegitHashesPerType * SHAMapInnerNode::kBranchFactor;
|
||||
static constexpr auto kBandMediumMax = kBandSmallMax * SHAMapInnerNode::kBranchFactor;
|
||||
|
||||
} // namespace xrpl::Tuning
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
#include <expected>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -350,15 +349,13 @@ doLedgerGrpc(RPC::GRPCContext<org::xrpl::rpc::v1::GetLedgerRequest>& context)
|
||||
auto end = std::chrono::system_clock::now();
|
||||
auto duration =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count() * 1.0;
|
||||
// Guard the per-item rates: an empty ledger has zero objects and/or zero
|
||||
// transactions, and dividing by zero is undefined for these doubles.
|
||||
auto const numObjects = response.ledger_objects().objects_size();
|
||||
auto const numTxns = response.transactions_list().transactions_size();
|
||||
std::string const msPerObj = numObjects > 0 ? std::to_string(duration / numObjects) : "n/a";
|
||||
std::string const msPerTxn = numTxns > 0 ? std::to_string(duration / numTxns) : "n/a";
|
||||
JLOG(context.j.warn()) << __func__ << " - Extract time = " << duration
|
||||
<< " - num objects = " << numObjects << " - num txns = " << numTxns
|
||||
<< " - ms per obj " << msPerObj << " - ms per txn " << msPerTxn;
|
||||
<< " - num objects = " << response.ledger_objects().objects_size()
|
||||
<< " - num txns = " << response.transactions_list().transactions_size()
|
||||
<< " - ms per obj "
|
||||
<< duration / response.ledger_objects().objects_size()
|
||||
<< " - ms per txn "
|
||||
<< duration / response.transactions_list().transactions_size();
|
||||
|
||||
return {response, status};
|
||||
}
|
||||
|
||||
@@ -186,23 +186,13 @@ doUnsubscribe(RPC::JsonContext& context)
|
||||
book.domain = domain;
|
||||
}
|
||||
|
||||
if (!context.netOps.unsubBook(ispSub, book))
|
||||
{
|
||||
JLOG(context.j.debug())
|
||||
<< "doUnsubscribe: book not subscribed (no-op for seq=" << ispSub->getSeq()
|
||||
<< ")";
|
||||
}
|
||||
context.netOps.unsubBook(ispSub->getSeq(), book);
|
||||
|
||||
// both_sides is deprecated.
|
||||
if ((jv.isMember(jss::both) && jv[jss::both].asBool()) ||
|
||||
(jv.isMember(jss::both_sides) && jv[jss::both_sides].asBool()))
|
||||
{
|
||||
if (!context.netOps.unsubBook(ispSub, reversed(book)))
|
||||
{
|
||||
JLOG(context.j.debug())
|
||||
<< "doUnsubscribe: reversed book not subscribed (no-op for seq="
|
||||
<< ispSub->getSeq() << ")";
|
||||
}
|
||||
context.netOps.unsubBook(ispSub->getSeq(), reversed(book));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user