mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-16 12:28:34 +00:00
Compare commits
2 Commits
copilot/ad
...
mvadari/pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
744355c1d9 | ||
|
|
9dde6c907f |
@@ -366,6 +366,7 @@ words:
|
||||
- venv
|
||||
- vfalco
|
||||
- vinnie
|
||||
- vkeylet
|
||||
- wasmi
|
||||
- wextra
|
||||
- wptr
|
||||
|
||||
9
.github/actions/setup-nix-env/action.yml
vendored
9
.github/actions/setup-nix-env/action.yml
vendored
@@ -40,11 +40,10 @@ runs:
|
||||
# Unlike the Linux nix images, macOS needs no SSL_CERT_FILE: it has its
|
||||
# own trust store, and pinning would break TLS to hosts relying on it.
|
||||
|
||||
# In RUNNER_TEMP, which the runner empties per job, like the `.conan2`
|
||||
# prepare-runner hands the system toolchain - but under its own name:
|
||||
# that Conan is a different version, and the two would migrate each
|
||||
# other's cache.
|
||||
echo "CONAN_HOME=${RUNNER_TEMP}/.conan2-nix" >>"${GITHUB_ENV}"
|
||||
# Workspace-local, so `cleanup-workspace` clears it, but not the
|
||||
# `.conan2` prepare-runner hands the system toolchain: that Conan is a
|
||||
# different version, and the two would migrate each other's cache.
|
||||
echo "CONAN_HOME=${{ github.workspace }}/.conan2-nix" >>"${GITHUB_ENV}"
|
||||
|
||||
# Config, profiles and remote, exactly as the dev shell sets them up on
|
||||
# entry; the `setup-conan` action is skipped for this toolchain.
|
||||
|
||||
4
.github/pull_request_template.md
vendored
4
.github/pull_request_template.md
vendored
@@ -18,7 +18,7 @@ If too broad, please consider splitting into multiple PRs.
|
||||
If there is a relevant task or issue, please link it here.
|
||||
-->
|
||||
|
||||
## Context of Change
|
||||
### Context of Change
|
||||
|
||||
<!--
|
||||
Please include the context of a change.
|
||||
@@ -29,7 +29,7 @@ If a refactor, how is this better than the previous implementation?
|
||||
If there is a spec or design document for this feature, please link it here.
|
||||
-->
|
||||
|
||||
## API Impact
|
||||
### API Impact
|
||||
|
||||
<!--
|
||||
Please check [x] relevant options, delete irrelevant ones.
|
||||
|
||||
112
.github/scripts/levelization/README.md
vendored
112
.github/scripts/levelization/README.md
vendored
@@ -1,34 +1,19 @@
|
||||
# Levelization
|
||||
|
||||
Levelization is the term used to describe efforts to prevent xrpld from
|
||||
having or creating cyclic dependencies.
|
||||
Levelization is the term used to describe efforts to prevent xrpld from having or creating cyclic dependencies.
|
||||
|
||||
xrpld code is organized into directories under `src/xrpld`, `src/libxrpl` (and
|
||||
`src/test`) representing modules. The modules are intended to be
|
||||
organized into "tiers" or "levels" such that a module from one level can
|
||||
only include code from lower levels. Additionally, a module
|
||||
in one level should never include code in an `impl` or `detail` folder of any level
|
||||
other than its own.
|
||||
xrpld code is organized into directories under `src/xrpld`, `src/libxrpl` (and `src/test`) representing modules. The modules are intended to be organized into "tiers" or "levels" such that a module from one level can only include code from lower levels. Additionally, a module in one level should never include code in an `impl` or `detail` folder of any level other than its own.
|
||||
|
||||
The codebase is split into two main areas:
|
||||
|
||||
- **libxrpl** (`src/libxrpl`, `include/xrpl`): Reusable library modules with public interfaces
|
||||
- **xrpld** (`src/xrpld`): Application-specific implementation code
|
||||
|
||||
Unfortunately, over time, enforcement of levelization has been
|
||||
inconsistent, so the current state of the code doesn't necessarily
|
||||
reflect these rules. Whenever possible, developers should refactor any
|
||||
levelization violations they find (by moving files or individual
|
||||
classes). At the very least, don't make things worse.
|
||||
Unfortunately, over time, enforcement of levelization has been inconsistent, so the current state of the code doesn't necessarily reflect these rules. Whenever possible, developers should refactor any levelization violations they find (by moving files or individual classes). At the very least, don't make things worse.
|
||||
|
||||
The table below summarizes the _desired_ division of modules, based on the current
|
||||
state of the xrpld code. The levels are numbered from
|
||||
the bottom up with the lower level, lower numbered, more independent
|
||||
modules listed first, and the higher level, higher numbered modules with
|
||||
more dependencies listed later.
|
||||
The table below summarizes the _desired_ division of modules, based on the current state of the xrpld code. The levels are numbered from the bottom up with the lower level, lower numbered, more independent modules listed first, and the higher level, higher numbered modules with more dependencies listed later.
|
||||
|
||||
**tl;dr:** The modules listed first are more independent than the modules
|
||||
listed later.
|
||||
**tl;dr:** The modules listed first are more independent than the modules listed later.
|
||||
|
||||
## libxrpl Modules (Reusable Libraries)
|
||||
|
||||
@@ -55,80 +40,37 @@ listed later.
|
||||
|
||||
## Test Modules
|
||||
|
||||
| Level / Tier | Module(s) |
|
||||
| ------------ | -------------------------------------------------------------------------------------------------------- |
|
||||
| 11 | test/jtx test/beast test/csf |
|
||||
| 12 | test/unit_test |
|
||||
| 13 | test/crypto test/conditions test/json test/resource test/shamap test/peerfinder test/basics test/overlay |
|
||||
| 14 | test |
|
||||
| 15 | test/net test/protocol test/ledger test/consensus test/core test/server test/nodestore |
|
||||
| 16 | test/rpc test/app |
|
||||
| Level / Tier | Module(s) |
|
||||
| --- | --- |
|
||||
| 11 | test/jtx test/beast test/csf |
|
||||
| 12 | test/unit_test |
|
||||
| 13 | test/crypto test/conditions test/json test/resource test/shamap test/peerfinder test/basics test/overlay |
|
||||
| 14 | test |
|
||||
| 15 | test/net test/protocol test/ledger test/consensus test/core test/server test/nodestore |
|
||||
| 16 | test/rpc test/app |
|
||||
|
||||
(Note that `test` levelization is _much_ less important and _much_ less
|
||||
strictly enforced than `xrpl`/`xrpld` levelization, other than the requirement
|
||||
that `test` code should _never_ be included in `xrpl` or `xrpld` code.)
|
||||
(Note that `test` levelization is _much_ less important and _much_ less strictly enforced than `xrpl`/`xrpld` levelization, other than the requirement that `test` code should _never_ be included in `xrpl` or `xrpld` code.)
|
||||
|
||||
## Validation
|
||||
|
||||
The [levelization](generate.py) script takes no parameters,
|
||||
reads no environment variables, and can be run from any directory,
|
||||
as long as it is in the expected location in the xrpld repo.
|
||||
It can be run at any time from within a checked out repo, and will
|
||||
do an analysis of all the `#include`s in
|
||||
the xrpld source. The only caveat is that it runs much slower
|
||||
under Windows than in Linux. It hasn't yet been tested under MacOS.
|
||||
It generates many files of [results](results):
|
||||
The [levelization](generate.py) script takes no parameters, reads no environment variables, and can be run from any directory, as long as it is in the expected location in the xrpld repo. It can be run at any time from within a checked out repo, and will do an analysis of all the `#include`s in the xrpld source. The only caveat is that it runs much slower under Windows than in Linux. It hasn't yet been tested under MacOS. It generates many files of [results](results):
|
||||
|
||||
- `rawincludes.txt`: The raw dump of the `#includes`
|
||||
- `paths.txt`: A second dump grouping the source module
|
||||
to the destination module, de-duped, and with frequency counts.
|
||||
- `includes/`: A directory where each file represents a module and
|
||||
contains a list of modules and counts that the module _includes_.
|
||||
- `included_by/`: Similar to `includes/`, but the other way around. Each
|
||||
file represents a module and contains a list of modules and counts
|
||||
that _include_ the module.
|
||||
- [`loops.txt`](results/loops.txt): A list of direct loops detected
|
||||
between modules as they actually exist, as opposed to how they are
|
||||
desired as described above. In a perfect repo, this file will be
|
||||
empty.
|
||||
This file is committed to the repo, and is used by the [levelization
|
||||
Github workflow](../../workflows/reusable-check-levelization.yml) to validate
|
||||
that nothing changed.
|
||||
- [`ordering.txt`](results/ordering.txt): A list showing relationships
|
||||
between modules where there are no loops as they actually exist, as
|
||||
opposed to how they are desired as described above.
|
||||
This file is committed to the repo, and is used by the [levelization
|
||||
Github workflow](../../workflows/reusable-check-levelization.yml) to validate
|
||||
that nothing changed.
|
||||
- [`levelization.yml`](../../workflows/reusable-check-levelization.yml)
|
||||
Github Actions workflow to test that levelization loops haven't
|
||||
changed. Unfortunately, if changes are detected, it can't tell if
|
||||
they are improvements or not, so if you have resolved any issues or
|
||||
done anything else to improve levelization, run `generate.py`,
|
||||
and commit the updated results.
|
||||
- `paths.txt`: A second dump grouping the source module to the destination module, de-duped, and with frequency counts.
|
||||
- `includes/`: A directory where each file represents a module and contains a list of modules and counts that the module _includes_.
|
||||
- `included_by/`: Similar to `includes/`, but the other way around. Each file represents a module and contains a list of modules and counts that _include_ the module.
|
||||
- [`loops.txt`](results/loops.txt): A list of direct loops detected between modules as they actually exist, as opposed to how they are desired as described above. In a perfect repo, this file will be empty. This file is committed to the repo, and is used by the [levelization Github workflow](../../workflows/reusable-check-levelization.yml) to validate that nothing changed.
|
||||
- [`ordering.txt`](results/ordering.txt): A list showing relationships between modules where there are no loops as they actually exist, as opposed to how they are desired as described above. This file is committed to the repo, and is used by the [levelization Github workflow](../../workflows/reusable-check-levelization.yml) to validate that nothing changed.
|
||||
- [`levelization.yml`](../../workflows/reusable-check-levelization.yml) Github Actions workflow to test that levelization loops haven't changed. Unfortunately, if changes are detected, it can't tell if they are improvements or not, so if you have resolved any issues or done anything else to improve levelization, run `generate.py`, and commit the updated results.
|
||||
|
||||
The `loops.txt` and `ordering.txt` files relate the modules
|
||||
using comparison signs, which indicate the number of times each
|
||||
module is included in the other.
|
||||
The `loops.txt` and `ordering.txt` files relate the modules using comparison signs, which indicate the number of times each module is included in the other.
|
||||
|
||||
- `A > B` means that A should probably be at a higher level than B,
|
||||
because B is included in A significantly more than A is included in B.
|
||||
These results can be included in both `loops.txt` and `ordering.txt`.
|
||||
Because `ordering.txt`only includes relationships where B is not
|
||||
included in A at all, it will only include these types of results.
|
||||
- `A ~= B` means that A and B are included in each other a different
|
||||
number of times, but the values are so close that the script can't
|
||||
definitively say that one should be above the other. These results
|
||||
will only be included in `loops.txt`.
|
||||
- `A == B` means that A and B include each other the same number of
|
||||
times, so the script has no clue which should be higher. These results
|
||||
will only be included in `loops.txt`.
|
||||
- `A > B` means that A should probably be at a higher level than B, because B is included in A significantly more than A is included in B. These results can be included in both `loops.txt` and `ordering.txt`. Because `ordering.txt`only includes relationships where B is not included in A at all, it will only include these types of results.
|
||||
- `A ~= B` means that A and B are included in each other a different number of times, but the values are so close that the script can't definitively say that one should be above the other. These results will only be included in `loops.txt`.
|
||||
- `A == B` means that A and B include each other the same number of times, so the script has no clue which should be higher. These results will only be included in `loops.txt`.
|
||||
|
||||
The committed files hide the detailed values intentionally, to
|
||||
prevent false alarms and merging issues, and because it's easy to
|
||||
get those details locally.
|
||||
The committed files hide the detailed values intentionally, to prevent false alarms and merging issues, and because it's easy to get those details locally.
|
||||
|
||||
1. Run `generate.py`
|
||||
2. Grep the modules in `paths.txt`.
|
||||
- For example, if a cycle is found `A ~= B`, simply `grep -w
|
||||
A .github/scripts/levelization/results/paths.txt | grep -w B`
|
||||
- For example, if a cycle is found `A ~= B`, simply `grep -w A .github/scripts/levelization/results/paths.txt | grep -w B`
|
||||
|
||||
41
.github/scripts/rename/README.md
vendored
41
.github/scripts/rename/README.md
vendored
@@ -1,41 +1,20 @@
|
||||
## Renaming ripple(d) to xrpl(d)
|
||||
|
||||
In the initial phases of development of the XRPL, the open source codebase was
|
||||
called "rippled" and it remains with that name even today. Today, over 1000
|
||||
nodes run the application, and code contributions have been submitted by
|
||||
developers located around the world. The XRPL community is larger than ever.
|
||||
In light of the decentralized and diversified nature of XRPL, we will rename any
|
||||
references to `ripple` and `rippled` to `xrpl` and `xrpld`, when appropriate.
|
||||
In the initial phases of development of the XRPL, the open source codebase was called "rippled" and it remains with that name even today. Today, over 1000 nodes run the application, and code contributions have been submitted by developers located around the world. The XRPL community is larger than ever. In light of the decentralized and diversified nature of XRPL, we will rename any references to `ripple` and `rippled` to `xrpl` and `xrpld`, when appropriate.
|
||||
|
||||
See [here](https://xls.xrpl.org/xls/XLS-0095-rename-rippled-to-xrpld.html) for
|
||||
more information.
|
||||
See [here](https://xls.xrpl.org/xls/XLS-0095-rename-rippled-to-xrpld.html) for more information.
|
||||
|
||||
### Scripts
|
||||
|
||||
To facilitate this transition, there will be multiple scripts that developers
|
||||
can run on their own PRs and forks to minimize conflicts. Each script should be
|
||||
run from the repository root.
|
||||
To facilitate this transition, there will be multiple scripts that developers can run on their own PRs and forks to minimize conflicts. Each script should be run from the repository root.
|
||||
|
||||
1. `.github/scripts/rename/definitions.sh`: This script will rename all
|
||||
definitions, such as include guards, from `RIPPLE_XXX` and `RIPPLED_XXX` to
|
||||
`XRPL_XXX`.
|
||||
2. `.github/scripts/rename/copyright.sh`: This script will remove superfluous
|
||||
copyright notices.
|
||||
3. `.github/scripts/rename/cmake.sh`: This script will rename all CMake files
|
||||
from `RippleXXX.cmake` or `RippledXXX.cmake` to `XrplXXX.cmake`, and any
|
||||
references to `ripple` and `rippled` (with or without capital letters) to
|
||||
`xrpl` and `xrpld`, respectively. The name of the binary will remain as-is,
|
||||
and will only be renamed to `xrpld` by a later script.
|
||||
4. `.github/scripts/rename/binary.sh`: This script will rename the binary from
|
||||
`rippled` to `xrpld`, and reverses the symlink so that `rippled` points to
|
||||
the `xrpld` binary.
|
||||
5. `.github/scripts/rename/namespace.sh`: This script will rename the C++
|
||||
namespaces from `ripple` to `xrpl`.
|
||||
6. `.github/scripts/rename/config.sh`: This script will rename the config from
|
||||
`rippled.cfg` to `xrpld.cfg`, and updating the code accordingly. The old
|
||||
filename will still be accepted.
|
||||
7. `.github/scripts/rename/docs.sh`: This script will rename any lingering
|
||||
references of `ripple(d)` to `xrpl(d)` in code, comments, and documentation.
|
||||
1. `.github/scripts/rename/definitions.sh`: This script will rename all definitions, such as include guards, from `RIPPLE_XXX` and `RIPPLED_XXX` to `XRPL_XXX`.
|
||||
2. `.github/scripts/rename/copyright.sh`: This script will remove superfluous copyright notices.
|
||||
3. `.github/scripts/rename/cmake.sh`: This script will rename all CMake files from `RippleXXX.cmake` or `RippledXXX.cmake` to `XrplXXX.cmake`, and any references to `ripple` and `rippled` (with or without capital letters) to `xrpl` and `xrpld`, respectively. The name of the binary will remain as-is, and will only be renamed to `xrpld` by a later script.
|
||||
4. `.github/scripts/rename/binary.sh`: This script will rename the binary from `rippled` to `xrpld`, and reverses the symlink so that `rippled` points to the `xrpld` binary.
|
||||
5. `.github/scripts/rename/namespace.sh`: This script will rename the C++ namespaces from `ripple` to `xrpl`.
|
||||
6. `.github/scripts/rename/config.sh`: This script will rename the config from `rippled.cfg` to `xrpld.cfg`, and updating the code accordingly. The old filename will still be accepted.
|
||||
7. `.github/scripts/rename/docs.sh`: This script will rename any lingering references of `ripple(d)` to `xrpl(d)` in code, comments, and documentation.
|
||||
|
||||
You can run all these scripts from the repository root as follows:
|
||||
|
||||
|
||||
6
.github/scripts/strategy-matrix/linux.json
vendored
6
.github/scripts/strategy-matrix/linux.json
vendored
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"image_tag": "sha-060957e",
|
||||
"image_tag": "sha-473fe44",
|
||||
"configs": {
|
||||
"ubuntu": [
|
||||
{
|
||||
@@ -74,7 +74,7 @@
|
||||
"extra_cmake_args": "-Dvalidator_keys=ON",
|
||||
"package": {
|
||||
"type": "deb",
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-49cdc10"
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-b6a8995"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -88,7 +88,7 @@
|
||||
"extra_cmake_args": "-Dvalidator_keys=ON",
|
||||
"package": {
|
||||
"type": "rpm",
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-49cdc10"
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-b6a8995"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
8
.github/workflows/build-nix-images.yml
vendored
8
.github/workflows/build-nix-images.yml
vendored
@@ -5,13 +5,15 @@ on:
|
||||
branches:
|
||||
- develop
|
||||
paths:
|
||||
- ".github/workflows/build-nix-images.yml"
|
||||
- "flake.nix"
|
||||
- "flake.lock"
|
||||
- "rust-toolchain.toml"
|
||||
- "nix/**"
|
||||
- "!nix/docker/README.md"
|
||||
- "!nix/devshell.nix"
|
||||
- "!nix/check-tools/**"
|
||||
- "!nix/check-tools/*.txt"
|
||||
- "bin/check-tools.sh"
|
||||
- "bin/default-loader-path.sh"
|
||||
- "bin/install-sanitizer-libs.sh"
|
||||
pull_request:
|
||||
@@ -23,7 +25,7 @@ on:
|
||||
- "nix/**"
|
||||
- "!nix/docker/README.md"
|
||||
- "!nix/devshell.nix"
|
||||
- "!nix/check-tools/**"
|
||||
- "!nix/check-tools/*.txt"
|
||||
- "bin/check-tools.sh"
|
||||
- "bin/default-loader-path.sh"
|
||||
- "bin/install-sanitizer-libs.sh"
|
||||
@@ -58,7 +60,7 @@ jobs:
|
||||
base_image: debian:bookworm
|
||||
- name: rhel
|
||||
base_image: registry.access.redhat.com/ubi9/ubi:latest
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@034e87065fcd0100214cf0672923bd38d193cf78
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@65d5a0bd72be4ecea95cff0673a6e0672ab5243a
|
||||
with:
|
||||
image_name: xrpld/nix-${{ matrix.distro.name }}
|
||||
dockerfile: nix/docker/Dockerfile
|
||||
|
||||
2
.github/workflows/build-packaging-images.yml
vendored
2
.github/workflows/build-packaging-images.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
# AlmaLinux rather than UBI, which does not ship rpm-sign.
|
||||
- name: rhel
|
||||
base_image: almalinux:10
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@034e87065fcd0100214cf0672923bd38d193cf78
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@65d5a0bd72be4ecea95cff0673a6e0672ab5243a
|
||||
with:
|
||||
image_name: xrpld/packaging-${{ matrix.distro.name }}
|
||||
dockerfile: package/docker/Dockerfile
|
||||
|
||||
2
.github/workflows/build-pre-commit-image.yml
vendored
2
.github/workflows/build-pre-commit-image.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@034e87065fcd0100214cf0672923bd38d193cf78
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@65d5a0bd72be4ecea95cff0673a6e0672ab5243a
|
||||
with:
|
||||
image_name: xrpld/pre-commit
|
||||
dockerfile: bin/pre-commit/Dockerfile
|
||||
|
||||
2
.github/workflows/cargo-audit.yml
vendored
2
.github/workflows/cargo-audit.yml
vendored
@@ -34,7 +34,7 @@ permissions:
|
||||
jobs:
|
||||
audit:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-473fe44
|
||||
permissions:
|
||||
contents: read
|
||||
# Needed to open an issue on scheduled failures.
|
||||
|
||||
2
.github/workflows/check-tools.yml
vendored
2
.github/workflows/check-tools.yml
vendored
@@ -79,7 +79,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
|
||||
uses: XRPLF/actions/prepare-runner@7bf7ceca5932114abdd0d43493c3c30c5a654e13
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
|
||||
2
.github/workflows/pre-commit.yml
vendored
2
.github/workflows/pre-commit.yml
vendored
@@ -14,7 +14,7 @@ on:
|
||||
jobs:
|
||||
# Call the workflow in the XRPLF/actions repo that runs the pre-commit hooks.
|
||||
run-hooks:
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@279ec358f4a1be4088be3e024b07916fa97c75b6
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@f1952595d212e86169935135efc66294b4574131
|
||||
with:
|
||||
runs_on: ubuntu-latest
|
||||
container: '{ "image": "ghcr.io/xrplf/xrpld/pre-commit:sha-473fe44" }'
|
||||
|
||||
6
.github/workflows/publish-docs.yml
vendored
6
.github/workflows/publish-docs.yml
vendored
@@ -41,13 +41,13 @@ env:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-473fe44
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
|
||||
uses: XRPLF/actions/prepare-runner@7bf7ceca5932114abdd0d43493c3c30c5a654e13
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
@@ -91,4 +91,4 @@ jobs:
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deploy
|
||||
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
|
||||
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
|
||||
|
||||
@@ -129,7 +129,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
|
||||
uses: XRPLF/actions/prepare-runner@7bf7ceca5932114abdd0d43493c3c30c5a654e13
|
||||
with:
|
||||
enable_ccache: ${{ inputs.ccache_enabled }}
|
||||
|
||||
|
||||
4
.github/workflows/reusable-clang-tidy.yml
vendored
4
.github/workflows/reusable-clang-tidy.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
needs: [determine-files]
|
||||
if: ${{ needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.need_full_run == 'true' }}
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-060957e"
|
||||
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-473fe44"
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
|
||||
uses: XRPLF/actions/prepare-runner@7bf7ceca5932114abdd0d43493c3c30c5a654e13
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
|
||||
192
.github/workflows/reusable-package.yml
vendored
192
.github/workflows/reusable-package.yml
vendored
@@ -1,14 +1,11 @@
|
||||
# Build, verify and publish Linux packages from the pre-built xrpld and
|
||||
# validator-keys artifacts, in three stages:
|
||||
# Build Linux packages from the pre-built xrpld and validator-keys artifacts:
|
||||
#
|
||||
# - 'package' builds and signs one format per config that carries a "package"
|
||||
# map in linux.json; that map names the container image and the format
|
||||
# - 'test-install' installs what was built on a range of distros and runs the
|
||||
# binaries there, so a package that cannot be installed never reaches Nexus
|
||||
# - 'publish' uploads with the image's publish_pkg.py, doing a --dry-run
|
||||
# unless 'publish: true'
|
||||
# - one job per config that carries a "package" map in linux.json
|
||||
# - that map names the container image and the format it builds there
|
||||
# - every job ends with the image's publish_pkg.py, uploading what it built
|
||||
# with 'publish: true' and doing a --dry-run otherwise
|
||||
#
|
||||
# Only linux/amd64 is supported; the runner is hardcoded in the jobs below.
|
||||
# Only linux/amd64 is supported; the runner is hardcoded in the job below.
|
||||
name: Package
|
||||
|
||||
on:
|
||||
@@ -42,7 +39,6 @@ defaults:
|
||||
|
||||
env:
|
||||
BUILD_DIR: build
|
||||
PACKAGE_DIR: packages
|
||||
|
||||
jobs:
|
||||
generate-matrix:
|
||||
@@ -74,14 +70,14 @@ jobs:
|
||||
contents: read
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: ${{ matrix.image }}
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
|
||||
uses: XRPLF/actions/prepare-runner@7bf7ceca5932114abdd0d43493c3c30c5a654e13
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
@@ -116,184 +112,24 @@ jobs:
|
||||
--pkg-release "${PKG_RELEASE}" \
|
||||
--channel "${CHANNEL}"
|
||||
|
||||
# Before the upload, so the artifact, the tested package and the published
|
||||
# package are the same bytes.
|
||||
# Before the upload, so the artifact and the published package are the
|
||||
# same bytes. DEBs are not signed, so the key is never set on that job.
|
||||
- name: Sign RPM
|
||||
if: ${{ inputs.publish && matrix.package_type == 'rpm' }}
|
||||
env:
|
||||
PKG_SIGNING_KEY: ${{ secrets.signing_key }}
|
||||
run: ./package/sign_rpm.py --package-dir "${BUILD_DIR}"
|
||||
|
||||
# Split from the debug symbols, which are an order of magnitude larger, so
|
||||
# that test-install downloads only what it installs.
|
||||
- name: Upload package artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ matrix.xrpld_artifact_name }}-pkg
|
||||
path: |
|
||||
${{ env.BUILD_DIR }}/debbuild/xrpld_*.deb
|
||||
${{ env.BUILD_DIR }}/rpmbuild/RPMS/**/xrpld-[0-9]*.rpm
|
||||
${{ env.BUILD_DIR }}/debbuild/*.deb
|
||||
${{ env.BUILD_DIR }}/debbuild/*.ddeb
|
||||
${{ env.BUILD_DIR }}/rpmbuild/RPMS/**/*.rpm
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload debug symbol artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ matrix.xrpld_artifact_name }}-pkg-debug
|
||||
path: |
|
||||
${{ env.BUILD_DIR }}/debbuild/xrpld-dbgsym_*.deb
|
||||
${{ env.BUILD_DIR }}/debbuild/xrpld-dbgsym_*.ddeb
|
||||
${{ env.BUILD_DIR }}/rpmbuild/RPMS/**/xrpld-debuginfo-*.rpm
|
||||
if-no-files-found: error
|
||||
|
||||
# Every distro family the packages target, oldest release first, so both ends
|
||||
# of the dependency range they declare are exercised.
|
||||
test-install:
|
||||
needs: [package]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- package_type: deb
|
||||
image: debian:11
|
||||
- package_type: deb
|
||||
image: debian:12
|
||||
- package_type: deb
|
||||
image: debian:13
|
||||
- package_type: deb
|
||||
image: ubuntu:20.04
|
||||
- package_type: deb
|
||||
image: ubuntu:22.04
|
||||
- package_type: deb
|
||||
image: ubuntu:24.04
|
||||
- package_type: deb
|
||||
image: ubuntu:26.04
|
||||
|
||||
- package_type: rpm
|
||||
image: almalinux:9
|
||||
- package_type: rpm
|
||||
image: almalinux:10
|
||||
- package_type: rpm
|
||||
image: rockylinux/rockylinux:9
|
||||
- package_type: rpm
|
||||
image: rockylinux/rockylinux:10
|
||||
- package_type: rpm
|
||||
image: registry.access.redhat.com/ubi9/ubi
|
||||
- package_type: rpm
|
||||
image: registry.access.redhat.com/ubi10/ubi
|
||||
name: "install ${{ matrix.package_type }} on ${{ matrix.image }}"
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-latest
|
||||
container: ${{ matrix.image }}
|
||||
timeout-minutes: 5
|
||||
|
||||
steps:
|
||||
# Both formats land in one directory; the step below picks its own by
|
||||
# extension, so this stays independent of the artifact names.
|
||||
- name: Download package artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: "*-pkg"
|
||||
merge-multiple: true
|
||||
path: ${{ env.PACKAGE_DIR }}
|
||||
|
||||
- name: Find the package
|
||||
id: find
|
||||
env:
|
||||
PACKAGE_TYPE: ${{ matrix.package_type }}
|
||||
run: |
|
||||
package="$(find "${PACKAGE_DIR}" -type f -name "*.${PACKAGE_TYPE}" -print -quit)"
|
||||
test -n "${package}" || {
|
||||
echo "no .${PACKAGE_TYPE} found in ${PACKAGE_DIR}" >&2
|
||||
exit 1
|
||||
}
|
||||
echo "package=${package}" >>"${GITHUB_OUTPUT}"
|
||||
|
||||
# Debian 11 went end-of-life on 2026-08-31
|
||||
# (https://www.debian.org/News/2026/20260831) and its packages are
|
||||
# already partly gone from deb.debian.org, so switch to the
|
||||
# snapshot.debian.org entries the image ships commented out in its
|
||||
# sources.list: they are pinned to the snapshot the image was built
|
||||
# from, so they serve every version it needs and never go away.
|
||||
# Snapshots keep their original, long-passed Valid-Until, hence the
|
||||
# disabled check; the retries absorb snapshot.debian.org's throttling.
|
||||
- name: Switch Debian 11 to snapshot.debian.org
|
||||
if: ${{ matrix.image == 'debian:11' }}
|
||||
run: |
|
||||
sed -i 's|^deb |# deb |; s|^# deb http://snapshot|deb http://snapshot|' /etc/apt/sources.list
|
||||
printf '%s\n' \
|
||||
'Acquire::Check-Valid-Until "false";' \
|
||||
'Acquire::Retries "3";' \
|
||||
>/etc/apt/apt.conf.d/99snapshot
|
||||
|
||||
- name: Install the DEB
|
||||
if: ${{ matrix.package_type == 'deb' }}
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
PACKAGE: ${{ steps.find.outputs.package }}
|
||||
run: |
|
||||
# Stock Debian and Ubuntu images carry no package lists, so apt has
|
||||
# nothing to resolve the systemd dependency from until it fetches them.
|
||||
apt-get update -qq
|
||||
apt-get install -y "./${PACKAGE}"
|
||||
|
||||
- name: Install the RPM
|
||||
if: ${{ matrix.package_type == 'rpm' }}
|
||||
env:
|
||||
PACKAGE: ${{ steps.find.outputs.package }}
|
||||
run: dnf install -y "./${PACKAGE}"
|
||||
|
||||
- name: Run xrpld
|
||||
run: xrpld --version
|
||||
|
||||
- name: Run validator-keys
|
||||
run: validator-keys --version
|
||||
|
||||
- name: Run rippled, the legacy compatibility symlink
|
||||
run: rippled --version
|
||||
|
||||
- name: Check the service account
|
||||
run: id xrpld
|
||||
|
||||
- name: Check the state directory
|
||||
run: test -d /var/lib/xrpld
|
||||
|
||||
- name: Check the log directory
|
||||
run: test -d /var/log/xrpld
|
||||
|
||||
publish:
|
||||
needs: [generate-matrix, package, test-install]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
|
||||
name: "publish ${{ matrix.xrpld_artifact_name }}"
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: ${{ matrix.image }}
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
# Both artifacts, so the debug symbols are published alongside the package.
|
||||
- name: Download package artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: ${{ matrix.xrpld_artifact_name }}-pkg*
|
||||
merge-multiple: true
|
||||
path: ${{ env.PACKAGE_DIR }}
|
||||
|
||||
- name: Determine release info
|
||||
id: release_info
|
||||
uses: ./.github/actions/release-info
|
||||
|
||||
- name: Publish package
|
||||
env:
|
||||
CHANNEL: ${{ steps.release_info.outputs.channel }}
|
||||
@@ -304,6 +140,6 @@ jobs:
|
||||
run: |
|
||||
publish_pkg.py \
|
||||
--channel "${CHANNEL}" \
|
||||
--package-dir "${PACKAGE_DIR}" \
|
||||
--package-dir "${BUILD_DIR}" \
|
||||
--nexus-url "${NEXUS_URL}" \
|
||||
${DRY_RUN_OPTION}
|
||||
|
||||
14
.github/workflows/reusable-rust.yml
vendored
14
.github/workflows/reusable-rust.yml
vendored
@@ -1,9 +1,8 @@
|
||||
# Clippy, coverage and documentation for the Rust crates in crates/. Each runs
|
||||
# as an independent job on a GitHub-hosted runner, but inside the same container
|
||||
# image used to build the crates in the C++/Corrosion path, so the toolchain
|
||||
# (and therefore the lints and the cargo cache) matches what production builds
|
||||
# use. Coverage is the exception: it needs the nightly rustc that honours
|
||||
# #[coverage(off)], which the image carries alongside the pinned stable.
|
||||
# (and therefore the lints, coverage instrumentation and the cargo cache) matches
|
||||
# what production builds use.
|
||||
#
|
||||
# Rust unit tests are deliberately NOT run here. They run as part of the C++
|
||||
# build (reusable-build-test-config.yml), which already compiles the crates on a
|
||||
@@ -28,7 +27,7 @@ permissions:
|
||||
jobs:
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-473fe44
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
@@ -41,14 +40,11 @@ jobs:
|
||||
|
||||
coverage:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-473fe44
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Use the nightly Rust toolchain
|
||||
run: rust-nightly path >>"${GITHUB_PATH}"
|
||||
|
||||
- name: Use cargo artifacts cache
|
||||
uses: ./.github/actions/cargo-cache
|
||||
|
||||
@@ -70,7 +66,7 @@ jobs:
|
||||
|
||||
doc:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-473fe44
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
4
.github/workflows/reusable-upload-recipe.yml
vendored
4
.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-060957e
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-473fe44
|
||||
env:
|
||||
REMOTE_NAME: ${{ inputs.remote_name }}
|
||||
CONAN_LOGIN_USERNAME_XRPLF: ${{ secrets.remote_username }}
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
|
||||
uses: XRPLF/actions/prepare-runner@7bf7ceca5932114abdd0d43493c3c30c5a654e13
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
|
||||
2
.github/workflows/upload-conan-deps.yml
vendored
2
.github/workflows/upload-conan-deps.yml
vendored
@@ -68,7 +68,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@b3e255d74d785d053e4903da8ac90983cd7d9e82
|
||||
uses: XRPLF/actions/prepare-runner@7bf7ceca5932114abdd0d43493c3c30c5a654e13
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -72,16 +72,11 @@ DerivedData
|
||||
/.zed/
|
||||
|
||||
# AI tools.
|
||||
# Shared/committable AI agent config (AGENTS.md, CLAUDE.md, GEMINI.md, .claude/settings.json,
|
||||
# tool-specific rules files, etc.) should be checked in — see CONTRIBUTING.md. Only the
|
||||
# personal/local variants below are ignored.
|
||||
/.agent
|
||||
/.agents
|
||||
/.augment
|
||||
/.claude/settings.local.json
|
||||
AGENTS.override.md
|
||||
CLAUDE.local.md
|
||||
GEMINI.local.md
|
||||
/.claude
|
||||
/CLAUDE.md
|
||||
|
||||
# Python
|
||||
__pycache__
|
||||
|
||||
@@ -70,11 +70,6 @@ repos:
|
||||
language: system
|
||||
types: [rust]
|
||||
pass_filenames: false # rustfmt formats the whole workspace
|
||||
- id: check-coverage-attrs
|
||||
name: check Rust coverage attributes
|
||||
entry: ./bin/pre-commit/check_rust_coverage_attrs.py
|
||||
language: python
|
||||
files: ^crates/.*\.rs$
|
||||
|
||||
- repo: https://github.com/BlankSpruce/gersemi-pre-commit
|
||||
rev: e98930bdc210d3387007f9252d8c1694ea7e410f # frozen: 0.27.7
|
||||
|
||||
7
.prettierrc.yaml
Normal file
7
.prettierrc.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
# Collapse hard-wrapped Markdown prose to one line per paragraph instead of
|
||||
# preserving manual line breaks (the prettier default). Scoped to Markdown
|
||||
# only, since proseWrap also reflows YAML block scalars.
|
||||
overrides:
|
||||
- files: "*.md"
|
||||
options:
|
||||
proseWrap: never
|
||||
42
AGENTS.md
42
AGENTS.md
@@ -1,42 +0,0 @@
|
||||
# AGENTS.md
|
||||
|
||||
This file provides guidance to AI coding agents (Claude Code, and other AGENTS.md-compatible tools) when working with code in this repository.
|
||||
|
||||
## Build
|
||||
|
||||
Required on Linux/macOS: use the Nix devshell, which sets up the compiler, Conan, ccache, and (optionally) Rust automatically.
|
||||
|
||||
```bash
|
||||
nix develop
|
||||
```
|
||||
|
||||
For alternate devshell variants (specific compiler, no-compiler, coverage), see [docs/build/nix.md](./docs/build/nix.md). For the manual build steps, CMake options, and protocol codegen commands, see [BUILD.md](./BUILD.md) (`## Steps`, `## Options`, `## Code generation`).
|
||||
|
||||
Rust crate tests (independent of the CMake build): `cargo test --manifest-path crates/Cargo.toml --workspace` (CI uses `cargo nextest`).
|
||||
|
||||
## Testing
|
||||
|
||||
Unit tests are a custom framework built into the `xrpld` binary itself (not Boost.Test/GTest/Catch); see [CONTRIBUTING.md](./CONTRIBUTING.md#unit-tests) for the basic invocation. Notes not covered there:
|
||||
|
||||
- A suite's `--unittest` name is built from the arguments to its `BEAST_DEFINE_TESTSUITE`/`BEAST_DEFINE_TESTSUITE_PRIO` macro (usually at the bottom of the test file), in reverse order and joined with `.`: `BEAST_DEFINE_TESTSUITE(Credentials, app, xrpl)` → `xrpl.app.Credentials`.
|
||||
- `--unittest-arg` does nothing — don't use it.
|
||||
- Tests that run offline in under a minute should be automatic `--unittest` suites; anything else is a manual/integration test.
|
||||
- New tests should be written using `gtest` under `src/tests/` unless that isn't possible, in which case fall back to the legacy Beast framework under `src/test/`. `tests/` (top-level) holds integration tests exercised against `libxrpl`/`xrpld`.
|
||||
|
||||
## Lint/Format
|
||||
|
||||
See [CONTRIBUTING.md](./CONTRIBUTING.md#pre-commit-hooks) for `pre-commit` setup and [CONTRIBUTING.md](./CONTRIBUTING.md#clang-tidy) for `clang-tidy` (opt-in, needs local `clang-tidy` and generated headers).
|
||||
|
||||
## Code Style
|
||||
|
||||
New file placement and header levelization: see [CONTRIBUTING.md](./CONTRIBUTING.md#before-making-a-pull-request). Braces, whitespace, member order, and other conventions: see [docs/CodingStyle.md](./docs/CodingStyle.md). `XRPL_ASSERT`/`UNREACHABLE` contracts: see [CONTRIBUTING.md](./CONTRIBUTING.md#contracts-and-instrumentation). Commit messages: see [CONTRIBUTING.md](./CONTRIBUTING.md#good-commit-messages).
|
||||
|
||||
## Architecture
|
||||
|
||||
Paths below reflect the current layout; update this section if modularization moves a subsystem to a different directory.
|
||||
|
||||
- `include/xrpl/` + `src/libxrpl/` — the core protocol library: ledger, shamap, consensus, crypto, json, resource, nodestore, rdb, peerfinder, and `tx/` (transaction application: `Transactor.cpp`, `applySteps.cpp`, invariants, payment paths). `tx/transactors/` has one file per transaction type, grouped by subsystem: `escrow/`, `vault/`, `lending/`, `sponsor/`, `nft/`, `token/` (MPT), `payment_channel/`, `permissioned_domain/`, `dex/`, `oracle/`, `did/`, `credentials/`, `bridge/`, `check/`, `delegate/`, `account/`, `system/`. Any change to transaction-processing behavior must be gated behind an Amendment.
|
||||
- `src/xrpld/` — the server application built on top of `libxrpl`: `app`, `core`, `overlay` (P2P networking), `peerfinder`, `perflog`, `rpc`, `shamap`. `main` builds an `ApplicationImp` implementing `Application`; most components hold a reference to it (`app_`), giving broad cross-component access — expect to trace call chains through `Application&`.
|
||||
- `src/test/` — unit tests mirroring the subsystems above, plus `jtx/` (the transaction-building test DSL — e.g. `jtx/escrow.h`, `jtx/vault.h`, `jtx/sponsor.h`, `jtx/permissioned_dex.h`) and `unit_test/` (the custom test framework itself, derived from Beast).
|
||||
- `src/tests/` — unit tests for `libxrpl` written in `gtest`, gradually replacing the `src/test` equivalents.
|
||||
- `crates/` — a Rust workspace (only built with `-Dxrpld -Drust=ON`) bridged into C++ via `cxxbridge`/the `cxx` crate; currently just a `hello_world` interop scaffold. Requires the Rust toolchain pinned in `rust-toolchain.toml` (the Nix devshell provides it automatically).
|
||||
@@ -22,47 +22,13 @@ API version 2 is available in `xrpld` version 2.0.0 and later. See [API-VERSION-
|
||||
|
||||
This version is supported by all `xrpld` versions. For WebSocket and HTTP JSON-RPC requests, it is currently the default API version used when no `api_version` is specified.
|
||||
|
||||
## XRP Ledger server version 3.4.0
|
||||
## Unreleased
|
||||
|
||||
Version 3.4.0 is not yet released. These changes are available in the 3.4.0 beta releases.
|
||||
This section contains changes targeting a future version.
|
||||
|
||||
### Additions in 3.4.0
|
||||
|
||||
- `ledger`: `nftoken_id`, `nftoken_ids`, and `offer_id` are now included in transaction metadata when transactions are expanded (`expand`, or admin-only `full`), matching the `tx`, `account_tx`, and `subscribe` (`transactions` stream) responses. ([#5706](https://github.com/XRPLF/rippled/pull/5706))
|
||||
|
||||
### Bugfixes in 3.4.0
|
||||
|
||||
- `sign`, `sign_for`, `submit`: `signature_target` now returns `invalidParams` unless it names `CounterpartySignature` or `SponsorSignature`. It previously accepted any inner object field, such as `Book` or `NFToken`, and signed into it.
|
||||
- `sign`, `sign_for`, `submit`, `submit_multisigned`: With `fixCleanup3_4_0` enabled, a signature in `CounterpartySignature` or `SponsorSignature` covers a different prefix than the transaction's own signature, so a signature can no longer be moved from one of those roles into another. Clients that build these signatures themselves must use the new prefixes: `CPT` and `CPM` (single- and multi-signing) for `CounterpartySignature`, and `SPN` and `SPM` for `SponsorSignature`.
|
||||
- `get_aggregate_price`: Duplicate entries in the `oracles` request array are now ignored. [#6586](https://github.com/XRPLF/rippled/pull/6586)
|
||||
- `vault_info`: Errors now identify what the request got wrong instead of reporting every failure as the unregistered token `malformedRequest`, and the `error`, `error_code` and `error_message` fields now agree with each other. An invalid `vault_id` or `seq` returns `invalidParams`, an invalid `owner` returns `actMalformed`, and a request that mixes `vault_id` with `owner`/`seq` or supplies neither returns `invalidParams` with a message naming the accepted combinations. [#8015](https://github.com/XRPLF/rippled/pull/8015)
|
||||
- `vault_info`: A well-formed all-zero `vault_id` now returns `entryNotFound` instead of being rejected as malformed, and `entryNotFound` responses now include `error_code` and `error_message`. Clients that request `ripplerpc` 3.0 or above therefore receive HTTP 400 with that error rather than HTTP 200. [#8015](https://github.com/XRPLF/rippled/pull/8015)
|
||||
- `vault_info`: `vault_id` and `owner` must now be strings, matching how `ledger_entry` reads the same fields. An object or an array in either field previously produced an internal error, and a number was silently converted to its decimal text; `vault_id` now returns `invalidParams` and `owner` returns `actMalformed`. [#8015](https://github.com/XRPLF/rippled/pull/8015)
|
||||
- `gateway_balances`: The `account` and `ident` fields now return an `invalidParams` error if the value is not a string, instead of an `internal` error. [#7655](https://github.com/XRPLF/rippled/pull/7655)
|
||||
- `account_lines`: The `peer` field now returns an error if the value is not a string. [#7728](https://github.com/XRPLF/rippled/pull/7728)
|
||||
- `ledger`: `delivered_amount` is now included in the metadata of successful `AccountDelete` transactions when transactions are expanded (`expand`, or admin-only `full`). Previously it was only added for `Payment` and `CheckCash`, which made `ledger` inconsistent with `tx` and `account_tx`. [#5706](https://github.com/XRPLF/rippled/pull/5706)
|
||||
- `submit`: Augmented response fields (`accepted`, `applied`, `broadcast`, `queued`, `kept`, `account_sequence_next`, `account_sequence_available`, `open_ledger_cost`, `validated_ledger_index`) are now included in sign-and-submit mode. Previously, these fields were only returned when submitting a binary transaction blob. ([#6304](https://github.com/XRPLF/rippled/pull/6304))
|
||||
|
||||
## XRP Ledger server version 3.3.0
|
||||
|
||||
[Version 3.3.0](https://github.com/XRPLF/rippled/releases/tag/3.3.0) was released on Aug 6, 2026.
|
||||
|
||||
### Additions in 3.3.0
|
||||
|
||||
- `account_tx`: Added an optional `delegate` request object to filter delegated transactions. The object requires `delegate_filter`, which must be either `actor` for transactions owned by the requested account but signed by another account, or `authorizer` for transactions signed by the requested account on behalf of another account. The optional `counter_party` account narrows the results to a specific signer/delegate for `actor` or a specific owner/delegator for `authorizer`. Malformed `delegate`, `delegate_filter`, and `counter_party` values return standard invalid field errors, and invalid account IDs return `actMalformed`. When paginating delegate-filtered queries, a marker from a delegate-filtered query includes a `delegate` flag and is only valid for follow-up requests that also supply `delegate` (mixing marker conventions returns `invalidParams`). Because filtering is applied after the ledger scan, a page may contain fewer results than `limit` (possibly zero) while still returning a marker, so callers must continue until no marker is present. ([#6126](https://github.com/XRPLF/rippled/pull/6126))
|
||||
|
||||
## XRP Ledger server version 3.2.1
|
||||
|
||||
[Version 3.2.1](https://github.com/XRPLF/rippled/releases/tag/3.2.1) was released on Aug 1, 2026.
|
||||
|
||||
This release contains bug fixes only and no API changes.
|
||||
|
||||
## XRP Ledger server version 3.2.0
|
||||
|
||||
[Version 3.2.0](https://github.com/XRPLF/rippled/releases/tag/3.2.0) was released on Jun 16, 2026.
|
||||
|
||||
### Additions in 3.2.0
|
||||
### Additions
|
||||
|
||||
- `account_tx`: Added an optional `delegate` request object to filter delegated transactions. The object requires `delegate_filter`, which must be either `actor` for transactions owned by the requested account but signed by another account, or `authorizer` for transactions signed by the requested account on behalf of another account. The optional `counter_party` account narrows the results to a specific signer/delegate for `actor` or a specific owner/delegator for `authorizer`. Malformed `delegate`, `delegate_filter`, and `counter_party` values return standard invalid field errors, and invalid account IDs return `actMalformed`. When paginating delegate-filtered queries, a marker from a delegate-filtered query includes a `delegate` flag and is only valid for follow-up requests that also supply `delegate` (mixing marker conventions returns `invalidParams`). Because filtering is applied after the ledger scan, a page may contain fewer results than `limit` (possibly zero) while still returning a marker, so callers must continue until no marker is present.
|
||||
- `ledger_entry`, `account_objects`: The `Delegate` ledger entry now includes an optional `DestinationNode` field, which stores the index into the authorized account's owner directory. This field is present on entries created after bidirectional directory tracking was introduced and may appear in RPC responses for those entries. ([#6681](https://github.com/XRPLF/rippled/pull/6681))
|
||||
- `server_definitions`: Added the following new sections to the response ([#6321](https://github.com/XRPLF/rippled/pull/6321)):
|
||||
- `TRANSACTION_FORMATS`: Describes the fields and their optionality for each transaction type, including common fields shared across all transactions.
|
||||
@@ -70,9 +36,11 @@ This release contains bug fixes only and no API changes.
|
||||
- `TRANSACTION_FLAGS`: Maps transaction type names to their supported flags and flag values.
|
||||
- `LEDGER_ENTRY_FLAGS`: Maps ledger entry type names to their flags and flag values.
|
||||
- `ACCOUNT_SET_FLAGS`: Maps AccountSet flag names (asf flags) to their numeric values.
|
||||
- `ledger`: `nftoken_id`, `nftoken_ids`, and `offer_id` are now included in transaction metadata when transactions are expanded (`expand`, or admin-only `full`), matching the `tx`, `account_tx`, and `subscribe` (`transactions` stream) responses. ([#5706](https://github.com/XRPLF/rippled/pull/5706))
|
||||
|
||||
### Bugfixes in 3.2.0
|
||||
### Bugfixes
|
||||
|
||||
- `get_aggregate_price`: Duplicate entries in the `oracles` request array are now ignored. [#6586](https://github.com/XRPLF/rippled/pull/6586)
|
||||
- Peer Crawler: The `port` field in `overlay.active[]` now consistently returns an integer instead of a string for outbound peers. [#6318](https://github.com/XRPLF/rippled/pull/6318)
|
||||
- `ping`: The `ip` field is no longer returned as an empty string for proxied connections without a forwarded-for header. It is now omitted, consistent with the behavior for identified connections. [#6730](https://github.com/XRPLF/rippled/pull/6730)
|
||||
- gRPC `GetLedgerDiff`: Fixed error message that incorrectly said "base ledger not validated" when the desired ledger was not validated. [#6730](https://github.com/XRPLF/rippled/pull/6730)
|
||||
@@ -84,24 +52,12 @@ This release contains bug fixes only and no API changes.
|
||||
- `submit`: The `fail_hard` field now returns an error if the value is not a boolean. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
- `subscribe`: The `taker` field in the `books` array now returns `actMalformed` instead of `badIssuer` if the value is not a valid account. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
- Fixed a bug in `Forwarded` HTTP header parsing where the extracted IP address could be incorrect when no comma or semicolon delimiter follows the address. This could cause the server to misidentify a client's IP address when operating behind a reverse proxy. [#6529](https://github.com/XRPLF/rippled/pull/6529)
|
||||
|
||||
## XRP Ledger server version 3.1.3
|
||||
|
||||
[Version 3.1.3](https://github.com/XRPLF/rippled/releases/tag/3.1.3) was released on May 8, 2026.
|
||||
|
||||
This release contains bug fixes only and no API changes.
|
||||
|
||||
## XRP Ledger server version 3.1.2
|
||||
|
||||
[Version 3.1.2](https://github.com/XRPLF/rippled/releases/tag/3.1.2) was released on Mar 12, 2026.
|
||||
|
||||
This release contains bug fixes only and no API changes.
|
||||
|
||||
## XRP Ledger server version 3.1.1
|
||||
|
||||
[Version 3.1.1](https://github.com/XRPLF/rippled/releases/tag/3.1.1) was released on Feb 23, 2026.
|
||||
|
||||
This release contains bug fixes only and no API changes.
|
||||
- `vault_info`: Errors now identify what the request got wrong instead of reporting every failure as the unregistered token `malformedRequest`, and the `error`, `error_code` and `error_message` fields now agree with each other. An invalid `vault_id` or `seq` returns `invalidParams`, an invalid `owner` returns `actMalformed`, and a request that mixes `vault_id` with `owner`/`seq` or supplies neither returns `invalidParams` with a message naming the accepted combinations. [#8015](https://github.com/XRPLF/rippled/pull/8015)
|
||||
- `vault_info`: A well-formed all-zero `vault_id` now returns `entryNotFound` instead of being rejected as malformed, and `entryNotFound` responses now include `error_code` and `error_message`. Clients that request `ripplerpc` 3.0 or above therefore receive HTTP 400 with that error rather than HTTP 200. [#8015](https://github.com/XRPLF/rippled/pull/8015)
|
||||
- `vault_info`: `vault_id` and `owner` must now be strings, matching how `ledger_entry` reads the same fields. An object or an array in either field previously produced an internal error, and a number was silently converted to its decimal text; `vault_id` now returns `invalidParams` and `owner` returns `actMalformed`. [#8015](https://github.com/XRPLF/rippled/pull/8015)
|
||||
- `gateway_balances`: The `account` and `ident` fields now return an `invalidParams` error if the value is not a string, instead of an `internal` error. [#7655](https://github.com/XRPLF/rippled/pull/7655)
|
||||
- `account_lines`: The `peer` field now returns an error if the value is not a string. [#7728](https://github.com/XRPLF/rippled/pull/7728)
|
||||
- `ledger`: `delivered_amount` is now included in the metadata of successful `AccountDelete` transactions when transactions are expanded (`expand`, or admin-only `full`). Previously it was only added for `Payment` and `CheckCash`, which made `ledger` inconsistent with `tx` and `account_tx`. [#5706](https://github.com/XRPLF/rippled/pull/5706)
|
||||
|
||||
## XRP Ledger server version 3.1.0
|
||||
|
||||
@@ -251,9 +207,7 @@ This release contains bug fixes only and no API changes.
|
||||
- Adds `AMMDelete` transaction type to delete `AMM` instance.
|
||||
- Adds `sfAMMID` to `AccountRoot` to indicate that the account is `AMM`'s account. `AMMID` is used to fetch `ltAMM`.
|
||||
- Adds `lsfAMMNode` `TrustLine` flag to indicate that one side of the `TrustLine` is `AMM` account.
|
||||
- Adds `tfLPToken`, `tfSingleAsset`, `tfTwoAsset`, `tfOneAssetLPToken`, `tfLimitLPToken`, `tfTwoAssetIfEmpty`,
|
||||
`tfWithdrawAll`, `tfOneAssetWithdrawAll` which allow a trader to specify different fields combination
|
||||
for `AMMDeposit` and `AMMWithdraw` transactions.
|
||||
- Adds `tfLPToken`, `tfSingleAsset`, `tfTwoAsset`, `tfOneAssetLPToken`, `tfLimitLPToken`, `tfTwoAssetIfEmpty`, `tfWithdrawAll`, `tfOneAssetWithdrawAll` which allow a trader to specify different fields combination for `AMMDeposit` and `AMMWithdraw` transactions.
|
||||
- Adds new transaction result codes:
|
||||
- tecUNFUNDED_AMM: insufficient balance to fund AMM. The account does not have funds for liquidity provision.
|
||||
- tecAMM_BALANCE: AMM has invalid balance. Calculated balances greater than the current pool balances.
|
||||
@@ -296,14 +250,11 @@ This release contains bug fixes only and no API changes.
|
||||
|
||||
## XRP Ledger server version 1.10.0
|
||||
|
||||
[Version 1.10.0](https://github.com/XRPLF/rippled/releases/tag/1.10.0)
|
||||
was released on Mar 14, 2023.
|
||||
[Version 1.10.0](https://github.com/XRPLF/rippled/releases/tag/1.10.0) was released on Mar 14, 2023.
|
||||
|
||||
### Breaking changes in 1.10
|
||||
|
||||
- If the `XRPFees` feature is enabled, the `fee_ref` field will be
|
||||
removed from the [ledger subscription stream](https://xrpl.org/subscribe.html#ledger-stream), because it will no longer
|
||||
have any meaning.
|
||||
- If the `XRPFees` feature is enabled, the `fee_ref` field will be removed from the [ledger subscription stream](https://xrpl.org/subscribe.html#ledger-stream), because it will no longer have any meaning.
|
||||
|
||||
# Unit tests for API changes
|
||||
|
||||
|
||||
@@ -13,8 +13,7 @@ In API version 2, the following deprecated methods are no longer available: ([#4
|
||||
|
||||
## Modifications to JSON transaction element in API version 2
|
||||
|
||||
In API version 2, JSON elements for transaction output have been changed and made consistent for all methods which output transactions. ([#4775](https://github.com/XRPLF/rippled/pull/4775))
|
||||
This helps to unify the JSON serialization format of transactions. ([clio#722](https://github.com/XRPLF/clio/issues/722), [#4727](https://github.com/XRPLF/rippled/issues/4727))
|
||||
In API version 2, JSON elements for transaction output have been changed and made consistent for all methods which output transactions. ([#4775](https://github.com/XRPLF/rippled/pull/4775)) This helps to unify the JSON serialization format of transactions. ([clio#722](https://github.com/XRPLF/clio/issues/722), [#4727](https://github.com/XRPLF/rippled/issues/4727))
|
||||
|
||||
- JSON transaction element is named `tx_json`
|
||||
- Binary transaction element is named `tx_blob`
|
||||
|
||||
218
BUILD.md
218
BUILD.md
@@ -1,91 +1,70 @@
|
||||
| :warning: **WARNING** :warning: |
|
||||
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| :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. |
|
||||
|
||||
## Minimum Requirements
|
||||
|
||||
For the hardware needed to run a node, see
|
||||
[System Requirements](https://xrpl.org/system-requirements.html).
|
||||
For the hardware needed to run a node, see [System Requirements](https://xrpl.org/system-requirements.html).
|
||||
|
||||
For the software needed to build xrpld, see the
|
||||
[environment setup guide](./docs/build/environment.md).
|
||||
For the software needed to build xrpld, see the [environment setup guide](./docs/build/environment.md).
|
||||
|
||||
## Operating Systems
|
||||
|
||||
### 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.
|
||||
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.
|
||||
The minimum supported version is macOS 15 (Sequoia).
|
||||
CI testing is done in macOS 26 (Tahoe), but the build defaults `CMAKE_OSX_DEPLOYMENT_TARGET` to 15.
|
||||
Many `xrpld` engineers use macOS for development. The minimum supported version is macOS 15 (Sequoia). CI testing is done in macOS 26 (Tahoe), but the build defaults `CMAKE_OSX_DEPLOYMENT_TARGET` to 15.
|
||||
|
||||
### Windows
|
||||
|
||||
Windows is used by some engineers for development only, and is not recommended
|
||||
for production use.
|
||||
Windows is used by some engineers for development only, and is not recommended for production use.
|
||||
|
||||
## Steps
|
||||
|
||||
### Branches
|
||||
|
||||
For the latest set of untested features, or to contribute, choose the `develop`
|
||||
branch.
|
||||
For the latest set of untested features, or to contribute, choose the `develop` branch.
|
||||
|
||||
```bash
|
||||
git checkout develop
|
||||
```
|
||||
|
||||
For a release candidate, choose the relevant release branch, e.g.
|
||||
`release/3.2.x`.
|
||||
For a release candidate, choose the relevant release branch, e.g. `release/3.2.x`.
|
||||
|
||||
```bash
|
||||
git checkout release/3.2.x
|
||||
```
|
||||
|
||||
For a stable release, choose one of the [tagged
|
||||
releases](https://github.com/XRPLF/rippled/releases).
|
||||
For a stable release, choose one of the [tagged releases](https://github.com/XRPLF/rippled/releases).
|
||||
|
||||
### Set Up Conan
|
||||
|
||||
Once your [development environment](./docs/build/environment.md) is ready, set
|
||||
Conan up for this repository:
|
||||
Once your [development environment](./docs/build/environment.md) is ready, set Conan up for this repository:
|
||||
|
||||
```bash
|
||||
./conan/init.sh
|
||||
```
|
||||
|
||||
That installs our [`global.conf`](./conan/global.conf), our Conan
|
||||
[profiles](./conan/profiles), and the `xrplf` remote that hosts some of our
|
||||
dependencies. It honours `CONAN_HOME` and never deletes an existing Conan home,
|
||||
so it is safe to re-run — it only overwrites the files it manages.
|
||||
That installs our [`global.conf`](./conan/global.conf), our Conan [profiles](./conan/profiles), and the `xrplf` remote that hosts some of our dependencies. It honours `CONAN_HOME` and never deletes an existing Conan home, so it is safe to re-run — it only overwrites the files it manages.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
> [!TIP]
|
||||
> In the [Nix development shell](./docs/build/nix.md#conan-configuration) this is
|
||||
> already done for you: the script runs on entry.
|
||||
> In the [Nix development shell](./docs/build/nix.md#conan-configuration) this is already done for you: the script runs on entry.
|
||||
|
||||
You can inspect the resulting profile with `conan profile show`. If it is not
|
||||
suitable for your environment, create a custom profile and pass it to Conan — see
|
||||
[Advanced Conan configuration](./docs/build/advanced_conan.md).
|
||||
You can inspect the resulting profile with `conan profile show`. If it is not suitable for your environment, create a custom profile and pass it to Conan — see [Advanced Conan configuration](./docs/build/advanced_conan.md).
|
||||
|
||||
### Set Up Ccache
|
||||
|
||||
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.
|
||||
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).
|
||||
|
||||
#### Windows
|
||||
|
||||
You can install it using Chocolatey, i.e. `choco install ccache`. If you already
|
||||
have Ccache installed, then `choco upgrade ccache` will update it to the latest
|
||||
version. However, if you see an error such as:
|
||||
You can install it using Chocolatey, i.e. `choco install ccache`. If you already have Ccache installed, then `choco upgrade ccache` will update it to the latest version. However, if you see an error such as:
|
||||
|
||||
```
|
||||
terminate called after throwing an instance of 'std::bad_alloc'
|
||||
@@ -93,8 +72,7 @@ terminate called after throwing an instance of 'std::bad_alloc'
|
||||
C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Microsoft\VC\v170\Microsoft.CppCommon.targets(617,5): error MSB6006: "cl.exe" exited with code 3.
|
||||
```
|
||||
|
||||
then please install a specific version of Ccache that we know works, via: `choco
|
||||
install ccache --version 4.11.3 --allow-downgrade`.
|
||||
then please install a specific version of Ccache that we know works, via: `choco install ccache --version 4.11.3 --allow-downgrade`.
|
||||
|
||||
### Build and Test
|
||||
|
||||
@@ -105,14 +83,9 @@ install ccache --version 4.11.3 --allow-downgrade`.
|
||||
cd .build
|
||||
```
|
||||
|
||||
You can use any directory name. Conan treats your working directory as an
|
||||
install folder and generates files with implementation details.
|
||||
You don't need to worry about these files, but make sure to change
|
||||
your working directory to your build directory before calling Conan.
|
||||
You can use any directory name. Conan treats your working directory as an install folder and generates files with implementation details. You don't need to worry about these files, but make sure to change your working directory to your build directory before calling Conan.
|
||||
|
||||
**Note:** You can specify a directory for the installation files by adding
|
||||
the `install-folder` or `-if` option to every `conan install` command
|
||||
in the next step.
|
||||
**Note:** You can specify a directory for the installation files by adding the `install-folder` or `-if` option to every `conan install` command in the next step.
|
||||
|
||||
2. Use conan to generate CMake files for every configuration you want to build:
|
||||
|
||||
@@ -123,25 +96,15 @@ install ccache --version 4.11.3 --allow-downgrade`.
|
||||
|
||||
To build Debug, in the next step, be sure to set `-DCMAKE_BUILD_TYPE=Debug`
|
||||
|
||||
For a single-configuration generator, e.g. `Unix Makefiles` or `Ninja`,
|
||||
you only need to run this command once.
|
||||
For a multi-configuration generator, e.g. `Visual Studio`, you may want to
|
||||
run it more than once.
|
||||
For a single-configuration generator, e.g. `Unix Makefiles` or `Ninja`, you only need to run this command once. For a multi-configuration generator, e.g. `Visual Studio`, you may want to run it more than once.
|
||||
|
||||
Each of these commands should also have a different `build_type` setting.
|
||||
A second command with the same `build_type` setting will overwrite the files
|
||||
generated by the first. You can pass the build type on the command line with
|
||||
`--settings build_type=$BUILD_TYPE` or in the profile itself,
|
||||
under the section `[settings]` with the key `build_type`.
|
||||
Each of these commands should also have a different `build_type` setting. A second command with the same `build_type` setting will overwrite the files generated by the first. You can pass the build type on the command line with `--settings build_type=$BUILD_TYPE` or in the profile itself, under the section `[settings]` with the key `build_type`.
|
||||
|
||||
3. Configure CMake and pass the toolchain file generated by Conan, located at
|
||||
`$OUTPUT_FOLDER/build/generators/conan_toolchain.cmake`.
|
||||
3. Configure CMake and pass the toolchain file generated by Conan, located at `$OUTPUT_FOLDER/build/generators/conan_toolchain.cmake`.
|
||||
|
||||
Single-config generators:
|
||||
|
||||
Pass the CMake variable [`CMAKE_BUILD_TYPE`][build_type]
|
||||
and make sure it matches the one of the `build_type` settings
|
||||
you chose in the previous step.
|
||||
Pass the CMake variable [`CMAKE_BUILD_TYPE`][build_type] and make sure it matches the one of the `build_type` settings you chose in the previous step.
|
||||
|
||||
For example, to build Debug, in the next command, replace "Release" with "Debug"
|
||||
|
||||
@@ -159,9 +122,7 @@ install ccache --version 4.11.3 --allow-downgrade`.
|
||||
|
||||
4. Build `xrpld`.
|
||||
|
||||
For a single-configuration generator, it will build whatever configuration
|
||||
you passed for `CMAKE_BUILD_TYPE`. For a multi-configuration generator, you
|
||||
must pass the option `--config` to select the build configuration.
|
||||
For a single-configuration generator, it will build whatever configuration you passed for `CMAKE_BUILD_TYPE`. For a multi-configuration generator, you must pass the option `--config` to select the build configuration.
|
||||
|
||||
Single-config generators:
|
||||
|
||||
@@ -176,8 +137,7 @@ install ccache --version 4.11.3 --allow-downgrade`.
|
||||
cmake --build . --config Debug --parallel N
|
||||
```
|
||||
|
||||
Replace the `--parallel` parameter N with the desired number of parallel jobs. A common starting point is half of the number of available CPU
|
||||
cores.
|
||||
Replace the `--parallel` parameter N with the desired number of parallel jobs. A common starting point is half of the number of available CPU cores.
|
||||
|
||||
5. Test xrpld.
|
||||
|
||||
@@ -194,28 +154,20 @@ install ccache --version 4.11.3 --allow-downgrade`.
|
||||
./Debug/xrpld --unittest --unittest-jobs N
|
||||
```
|
||||
|
||||
Replace the `--unittest-jobs` parameter N with the desired unit tests
|
||||
concurrency. Recommended setting is half of the number of available CPU
|
||||
cores.
|
||||
Replace the `--unittest-jobs` parameter N with the desired unit tests concurrency. Recommended setting is half of the number of available CPU cores.
|
||||
|
||||
The location of `xrpld` binary in your build directory depends on your
|
||||
CMake generator. Pass `--help` to see the rest of the command line options.
|
||||
The location of `xrpld` binary in your build directory depends on your CMake generator. Pass `--help` to see the rest of the command line options.
|
||||
|
||||
## Code generation
|
||||
|
||||
The protocol wrapper classes in `include/xrpl/protocol_autogen/` are generated
|
||||
from macro definition files in `include/xrpl/protocol/detail/`. If you modify
|
||||
the macro files (e.g. `transactions.macro`, `ledger_entries.macro`) or the
|
||||
generation scripts/templates in `cmake/scripts/codegen/`, you need to regenerate the
|
||||
files:
|
||||
The protocol wrapper classes in `include/xrpl/protocol_autogen/` are generated from macro definition files in `include/xrpl/protocol/detail/`. If you modify the macro files (e.g. `transactions.macro`, `ledger_entries.macro`) or the generation scripts/templates in `cmake/scripts/codegen/`, you need to regenerate the files:
|
||||
|
||||
```
|
||||
cmake --build . --target setup_code_gen # create venv and install dependencies (once)
|
||||
cmake --build . --target code_gen # regenerate code
|
||||
```
|
||||
|
||||
The same targets are also available as a standalone project, which does not
|
||||
need the dependencies to be configured first:
|
||||
The same targets are also available as a standalone project, which does not need the dependencies to be configured first:
|
||||
|
||||
```
|
||||
cmake -S cmake/codegen -B build/codegen
|
||||
@@ -223,15 +175,11 @@ cmake --build build/codegen --target setup_code_gen
|
||||
cmake --build build/codegen --target code_gen
|
||||
```
|
||||
|
||||
The regenerated files should be committed alongside your changes. CI verifies
|
||||
that they are up-to-date.
|
||||
The regenerated files should be committed alongside your changes. CI verifies that they are up-to-date.
|
||||
|
||||
## Coverage report
|
||||
|
||||
The coverage report is intended for developers using compilers GCC
|
||||
or Clang (including Apple Clang). It is generated by the build target `coverage`,
|
||||
which is only enabled when the `coverage` option is set, e.g. with
|
||||
`--options coverage=True` in `conan` or `-Dcoverage=ON` variable in `cmake`
|
||||
The coverage report is intended for developers using compilers GCC or Clang (including Apple Clang). It is generated by the build target `coverage`, which is only enabled when the `coverage` option is set, e.g. with `--options coverage=True` in `conan` or `-Dcoverage=ON` variable in `cmake`
|
||||
|
||||
Prerequisites for the coverage report:
|
||||
|
||||
@@ -239,34 +187,19 @@ Prerequisites for the coverage report:
|
||||
- `gcov` for GCC or `llvm-cov` for Clang, usually installed with the compiler
|
||||
- `Debug` build type
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> Clang coverage is not available in the [Nix development shell](./docs/build/nix.md#building-xrpld-in-the-nix-shell):
|
||||
> its `clang` shells do not ship `llvm-cov`. Use a `gcc` shell instead (`.#gcc`,
|
||||
> or `.#gcc-plain` on Linux), which provides a `gcov` matching its compiler.
|
||||
> Clang coverage is not available in the [Nix development shell](./docs/build/nix.md#building-xrpld-in-the-nix-shell): its `clang` shells do not ship `llvm-cov`. Use a `gcc` shell instead (`.#gcc`, or `.#gcc-plain` on Linux), which provides a `gcov` matching its compiler.
|
||||
|
||||
A coverage report is created when the following steps are completed, in order:
|
||||
|
||||
1. `xrpld` binary built with instrumentation data, enabled by the `coverage`
|
||||
option mentioned above
|
||||
1. `xrpld` binary built with instrumentation data, enabled by the `coverage` option mentioned above
|
||||
2. completed one or more run of the unit tests, which populates coverage capture data
|
||||
3. completed run of the `gcovr` tool (which internally invokes either `gcov` or `llvm-cov`)
|
||||
to assemble both instrumentation data and the coverage capture data into a coverage report
|
||||
3. completed run of the `gcovr` tool (which internally invokes either `gcov` or `llvm-cov`) to assemble both instrumentation data and the coverage capture data into a coverage report
|
||||
|
||||
The last step of the above is automated into a single target `coverage`. The instrumented
|
||||
`xrpld` binary can also be used for regular development or testing work, at
|
||||
the cost of extra disk space utilization and a small performance hit
|
||||
(to store coverage capture data). Since `xrpld` binary is simply a dependency of the
|
||||
coverage report target, it is possible to re-run the `coverage` target without
|
||||
rebuilding the `xrpld` binary. Note, running of the unit tests before the `coverage`
|
||||
target is left to the developer. Each such run will append to the coverage data
|
||||
collected in the build directory.
|
||||
The last step of the above is automated into a single target `coverage`. The instrumented `xrpld` binary can also be used for regular development or testing work, at the cost of extra disk space utilization and a small performance hit (to store coverage capture data). Since `xrpld` binary is simply a dependency of the coverage report target, it is possible to re-run the `coverage` target without rebuilding the `xrpld` binary. Note, running of the unit tests before the `coverage` target is left to the developer. Each such run will append to the coverage data collected in the build directory.
|
||||
|
||||
The default coverage report format is `html-details`, but the user
|
||||
can override it to any of the formats listed in `Builds/CMake/CodeCoverage.cmake`
|
||||
by setting the `coverage_format` variable in `cmake`. It is also possible
|
||||
to generate more than one format at a time by setting the `coverage_extra_args`
|
||||
variable in `cmake`. The specific command line used to run the `gcovr` tool will be
|
||||
displayed if the `CODE_COVERAGE_VERBOSE` variable is set.
|
||||
The default coverage report format is `html-details`, but the user can override it to any of the formats listed in `Builds/CMake/CodeCoverage.cmake` by setting the `coverage_format` variable in `cmake`. It is also possible to generate more than one format at a time by setting the `coverage_extra_args` variable in `cmake`. The specific command line used to run the `gcovr` tool will be displayed if the `CODE_COVERAGE_VERBOSE` variable is set.
|
||||
|
||||
Example use with some cmake variables set:
|
||||
|
||||
@@ -277,16 +210,14 @@ cmake -DCMAKE_BUILD_TYPE=Debug -Dcoverage=ON -Dxrpld=ON -Dtests=ON -Dcoverage_te
|
||||
cmake --build . --target coverage
|
||||
```
|
||||
|
||||
After the `coverage` target is completed, the generated coverage report will be
|
||||
stored inside the build directory, as either of:
|
||||
After the `coverage` target is completed, the generated coverage report will be stored inside the build directory, as either of:
|
||||
|
||||
- file named `coverage.`_extension_, with a suitable extension for the report format, or
|
||||
- directory named `coverage`, with the `index.html` and other files inside, for the `html-details` or `html-nested` report formats.
|
||||
|
||||
## Sanitizers
|
||||
|
||||
To build dependencies and xrpld with sanitizer instrumentation, set the
|
||||
`SANITIZERS` environment variable when running `conan install` and use the `sanitizers` profile:
|
||||
To build dependencies and xrpld with sanitizer instrumentation, set the `SANITIZERS` environment variable when running `conan install` and use the `sanitizers` profile:
|
||||
|
||||
```bash
|
||||
export SANITIZERS=address,undefinedbehavior
|
||||
@@ -300,42 +231,27 @@ See [Sanitizers docs](./docs/build/sanitizers.md) for more details.
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Default Value | Description |
|
||||
| ---------------- | ------------- | ----------------------------------------------------------------------------- |
|
||||
| `assert` | OFF | Force enabling assertions. |
|
||||
| `coverage` | OFF | Prepare the coverage report. |
|
||||
| `rust` | OFF | Build the Rust crates and the C++ code that depends on them. |
|
||||
| `tests` | OFF | Build tests. |
|
||||
| `unity` | OFF | Configure a unity build. |
|
||||
| `verify_headers` | ON | Make the `verify-headers` target available to compile each header on its own. |
|
||||
| `xrpld` | OFF | Build the xrpld application, and not just the libxrpl library. |
|
||||
| `werr` | OFF | Treat compilation warnings as errors |
|
||||
| `wextra` | OFF | Enable additional compilation warnings |
|
||||
| Option | Default Value | Description |
|
||||
| --- | --- | --- |
|
||||
| `assert` | OFF | Force enabling assertions. |
|
||||
| `coverage` | OFF | Prepare the coverage report. |
|
||||
| `rust` | OFF | Build the Rust crates and the C++ code that depends on them. |
|
||||
| `tests` | OFF | Build tests. |
|
||||
| `unity` | OFF | Configure a unity build. |
|
||||
| `verify_headers` | ON | Make the `verify-headers` target available to compile each header on its own. |
|
||||
| `xrpld` | OFF | Build the xrpld application, and not just the libxrpl library. |
|
||||
| `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
|
||||
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.
|
||||
[Unity builds][unity-build] 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.
|
||||
|
||||
### Rust crates
|
||||
|
||||
The Rust crates in `crates/` are only part of the build when `rust` is ON. With
|
||||
`-Drust=OFF` (the default) the `crates` directory is not added to the build, no
|
||||
cxxbridge bindings are generated, and the C++ tests that exercise the Rust
|
||||
interop are not compiled — so no Rust toolchain is needed. CI builds always pass
|
||||
`-Drust=ON`.
|
||||
The Rust crates in `crates/` are only part of the build when `rust` is ON. With `-Drust=OFF` (the default) the `crates` directory is not added to the build, no cxxbridge bindings are generated, and the C++ tests that exercise the Rust interop are not compiled — so no Rust toolchain is needed. CI builds always pass `-Drust=ON`.
|
||||
|
||||
With `-Drust=ON` you need one extra dependency: a Rust toolchain (`cargo`,
|
||||
`rustc`) matching the channel pinned in
|
||||
[`rust-toolchain.toml`](./rust-toolchain.toml), which compiles the crates and
|
||||
generates the cxxbridge bindings. It is provided by the
|
||||
[Nix development shell](./docs/build/nix.md), so `-Drust=ON` works there without
|
||||
any extra setup; otherwise install it as described in
|
||||
[Rust](./docs/build/environment.md#rust).
|
||||
With `-Drust=ON` you need one extra dependency: a Rust toolchain (`cargo`, `rustc`) matching the channel pinned in [`rust-toolchain.toml`](./rust-toolchain.toml), which compiles the crates and generates the cxxbridge bindings. It is provided by the [Nix development shell](./docs/build/nix.md), so `-Drust=ON` works there without any extra setup; otherwise install it as described in [Rust](./docs/build/environment.md#rust).
|
||||
|
||||
The crates also have their own Rust unit tests. Those are run with `cargo` and
|
||||
need only the Rust toolchain, independently of CMake and of the `rust` option
|
||||
(CI runs them with `cargo nextest`):
|
||||
The crates also have their own Rust unit tests. Those are run with `cargo` and need only the Rust toolchain, independently of CMake and of the `rust` option (CI runs them with `cargo nextest`):
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path crates/Cargo.toml --workspace
|
||||
@@ -343,22 +259,13 @@ cargo test --manifest-path crates/Cargo.toml --workspace
|
||||
|
||||
### Verifying headers
|
||||
|
||||
The regular build only compiles `.cpp` files, so a header is only ever checked
|
||||
through whatever translation unit happens to include it. A header that forgets
|
||||
an `#include` is not caught as long as every `.cpp` that uses it includes its
|
||||
missing dependency first. The `verify_headers` option (ON by default) adds a
|
||||
`verify-headers` target that compiles every header on its own, which fails if a
|
||||
header is not self-contained:
|
||||
The regular build only compiles `.cpp` files, so a header is only ever checked through whatever translation unit happens to include it. A header that forgets an `#include` is not caught as long as every `.cpp` that uses it includes its missing dependency first. The `verify_headers` option (ON by default) adds a `verify-headers` target that compiles every header on its own, which fails if a header is not self-contained:
|
||||
|
||||
```bash
|
||||
cmake --build . --target verify-headers
|
||||
```
|
||||
|
||||
The per-header objects are excluded from the `all` target, so a normal build
|
||||
never compiles them; they are built only through `verify-headers`. The generated
|
||||
translation units do appear in `compile_commands.json`, so clang-tidy (and
|
||||
clangd and IDEs) can lint each header on its own. Pass `-Dverify_headers=OFF` to
|
||||
omit them entirely.
|
||||
The per-header objects are excluded from the `all` target, so a normal build never compiles them; they are built only through `verify-headers`. The generated translation units do appear in `compile_commands.json`, so clang-tidy (and clangd and IDEs) can lint each header on its own. Pass `-Dverify_headers=OFF` to omit them entirely.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -385,20 +292,15 @@ After any updates or changes to dependencies, you may need to do the following:
|
||||
4. [Regenerate lockfile](./docs/build/advanced_conan.md#conan-lockfile).
|
||||
5. Re-run [conan install](#build-and-test).
|
||||
|
||||
If you are using the Nix development shell, whether prebuilt Conan binaries apply
|
||||
depends on your platform — see
|
||||
[Prebuilt packages](./docs/build/nix.md#prebuilt-packages).
|
||||
If you are using the Nix development shell, whether prebuilt Conan binaries apply depends on your platform — see [Prebuilt packages](./docs/build/nix.md#prebuilt-packages).
|
||||
|
||||
#### ERROR: Package not resolved
|
||||
|
||||
If you're seeing an error like `ERROR: Package 'snappy/1.1.10' not resolved: Unable to find 'snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1756234314.246' in remotes.`,
|
||||
please [set Conan up](#set-up-conan) so the `xrplf` remote is configured, or re-run `conan export` for [patched recipes](./docs/build/advanced_conan.md#patched-recipes).
|
||||
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 [set Conan up](#set-up-conan) so the `xrplf` remote is configured, or re-run `conan export` for [patched recipes](./docs/build/advanced_conan.md#patched-recipes).
|
||||
|
||||
### `protobuf/port_def.inc` file not found
|
||||
|
||||
If `cmake --build .` results in an error due to a missing a protobuf file, then
|
||||
you might have generated CMake files for a different `build_type` than the
|
||||
`CMAKE_BUILD_TYPE` you passed to Conan.
|
||||
If `cmake --build .` results in an error due to a missing a protobuf file, then you might have generated CMake files for a different `build_type` than the `CMAKE_BUILD_TYPE` you passed to Conan.
|
||||
|
||||
```
|
||||
/xrpld/.build/pb-xrpl.libpb/xrpl/proto/xrpl.pb.h:10:10: fatal error: 'google/protobuf/port_def.inc' file not found
|
||||
|
||||
650
CONTRIBUTING.md
650
CONTRIBUTING.md
@@ -1,31 +1,19 @@
|
||||
The XRP Ledger has many and diverse stakeholders, and everyone deserves
|
||||
a chance to contribute meaningful changes to the code that runs the
|
||||
XRPL.
|
||||
The XRP Ledger has many and diverse stakeholders, and everyone deserves a chance to contribute meaningful changes to the code that runs the XRPL.
|
||||
|
||||
# Contributing
|
||||
|
||||
We assume you are familiar with the general practice of [making
|
||||
contributions on GitHub][contrib]. This file includes only special
|
||||
instructions specific to this project.
|
||||
We assume you are familiar with the general practice of [making contributions on GitHub][contrib]. This file includes only special instructions specific to this project.
|
||||
|
||||
## Before you start
|
||||
|
||||
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).
|
||||
- `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).
|
||||
|
||||
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
|
||||
your verifying key. Please set up [signature verification][signing].
|
||||
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 your verifying key. Please set up [signature verification][signing].
|
||||
|
||||
In general, external contributions should be developed in your personal
|
||||
[fork][forking]. Contributions from developers with write permissions
|
||||
should be done in [the main repository][xrpld] in a branch with
|
||||
a permitted prefix. Permitted prefixes are:
|
||||
In general, external contributions should be developed in your personal [fork][forking]. Contributions from developers with write permissions should be done in [the main repository][xrpld] in a branch with a permitted prefix. Permitted prefixes are:
|
||||
|
||||
- XLS-[a-zA-Z0-9]+/.+
|
||||
- e.g. XLS-0033d/mpt-clarify-STEitherAmount
|
||||
@@ -34,98 +22,47 @@ a permitted prefix. Permitted prefixes are:
|
||||
- [Organization name]/.+
|
||||
- e.g. ripple/antithesis
|
||||
|
||||
Regardless of where the branch is created, please open a _draft_ pull
|
||||
request as soon as possible after pushing the branch to Github, to
|
||||
increase visibility, and ease feedback during the development process.
|
||||
Regardless of where the branch is created, please open a _draft_ pull request as soon as possible after pushing the branch to Github, to increase visibility, and ease feedback during the development process.
|
||||
|
||||
## Major contributions
|
||||
|
||||
If your contribution is a major feature or breaking change, then you
|
||||
must first write an XRP Ledger Standard (XLS) describing it. Go to
|
||||
[XRPL-Standards](https://github.com/XRPLF/XRPL-Standards/discussions),
|
||||
choose the next available standard number, and open a discussion with an
|
||||
appropriate title to propose your draft standard.
|
||||
If your contribution is a major feature or breaking change, then you must first write an XRP Ledger Standard (XLS) describing it. Go to [XRPL-Standards](https://github.com/XRPLF/XRPL-Standards/discussions), choose the next available standard number, and open a discussion with an appropriate title to propose your draft standard.
|
||||
|
||||
When you submit a pull request, please link the corresponding XLS in the
|
||||
description. An XLS still in `Draft` status is considered a
|
||||
work-in-progress and open for discussion. Please allow time for
|
||||
questions, suggestions, and changes to the XLS draft. It is the
|
||||
responsibility of the XLS author to update the draft to match the final
|
||||
implementation when its corresponding pull request is merged, unless the
|
||||
author delegates that responsibility to others.
|
||||
When you submit a pull request, please link the corresponding XLS in the description. An XLS still in `Draft` status is considered a work-in-progress and open for discussion. Please allow time for questions, suggestions, and changes to the XLS draft. It is the responsibility of the XLS author to update the draft to match the final implementation when its corresponding pull request is merged, unless the author delegates that responsibility to others.
|
||||
|
||||
Any amendment or major RPC change requires either a new XLS or an update
|
||||
to an existing XLS. Neither change will be released (in an amendment's
|
||||
case, marked as `Supported::yes`) until the corresponding XLS's status
|
||||
is `Final`.
|
||||
|
||||
## AI coding agents
|
||||
|
||||
[`AGENTS.md`](./AGENTS.md) (and its `CLAUDE.md` symlink, for Claude Code) holds shared, checked-in guidance for AI coding agents working in this repository — build/test/lint commands and architecture notes. Additional `AGENTS.md` files may exist in subdirectories to give agents context specific to that part of the codebase; whenever you add one, also add a `CLAUDE.md` symlink pointing to it (`ln -s AGENTS.md CLAUDE.md`) so Claude Code picks it up too.
|
||||
|
||||
If you want to give an agent personal instructions that shouldn't be shared with other contributors (e.g. your own workflow preferences), those are gitignored, not checked in:
|
||||
|
||||
- `CLAUDE.local.md` — read by Claude Code alongside `CLAUDE.md`.
|
||||
- `AGENTS.override.md` — read by AGENTS.md-compatible tools that support a personal override file layered on top of `AGENTS.md`.
|
||||
|
||||
Likewise, `.claude/settings.local.json` is for personal, untracked Claude Code settings, while `.claude/settings.json` is shared.
|
||||
Any amendment or major RPC change requires either a new XLS or an update to an existing XLS. Neither change will be released (in an amendment's case, marked as `Supported::yes`) until the corresponding XLS's status is `Final`.
|
||||
|
||||
## Before making a pull request
|
||||
|
||||
(Or marking a draft pull request as ready.)
|
||||
|
||||
Changes that alter transaction processing must be guarded by an
|
||||
[Amendment](https://xrpl.org/amendments.html).
|
||||
All other changes that maintain the existing behavior do not need an
|
||||
Amendment.
|
||||
Changes that alter transaction processing must be guarded by an [Amendment](https://xrpl.org/amendments.html). All other changes that maintain the existing behavior do not need an Amendment.
|
||||
|
||||
Ensure that your code compiles according to the build instructions in
|
||||
[`BUILD.md`](./BUILD.md).
|
||||
Ensure that your code compiles according to the build instructions in [`BUILD.md`](./BUILD.md).
|
||||
|
||||
Please write tests for your code.
|
||||
If your test can be run offline, in under 60 seconds, then it can be an
|
||||
automatic test run by `xrpld --unittest`.
|
||||
Otherwise, it must be a manual test.
|
||||
Please write tests for your code. If your test can be run offline, in under 60 seconds, then it can be an automatic test run by `xrpld --unittest`. Otherwise, it must be a manual test.
|
||||
|
||||
If you create new source files, they must be organized as follows:
|
||||
|
||||
- If the files are in any of the `libxrpl` modules, the headers (`.h`) must go
|
||||
under `include/xrpl`, and source (`.cpp`) files must go under
|
||||
`src/libxrpl`.
|
||||
- If the files are in any of the `libxrpl` modules, the headers (`.h`) must go under `include/xrpl`, and source (`.cpp`) files must go under `src/libxrpl`.
|
||||
- All other non-test files must go under `src/xrpld`.
|
||||
- New test source files should use `gtest` and go under `src/tests`, unless that isn't possible, in which case they should use our legacy test framework and go under `src/test`.
|
||||
- All test source files must go under `src/test`.
|
||||
- All benchmark source files must go under `src/benchmarks`.
|
||||
|
||||
The source must be formatted according to the style guide below. The easiest
|
||||
way to satisfy this is to install the [`pre-commit`](#pre-commit-hooks) hooks,
|
||||
which format and lint your changes automatically on every commit.
|
||||
The source must be formatted according to the style guide below. The easiest way to satisfy this is to install the [`pre-commit`](#pre-commit-hooks) hooks, which format and lint your changes automatically on every commit.
|
||||
|
||||
Header includes must be [levelized](.github/scripts/levelization).
|
||||
|
||||
Changes should be usually squashed down into a single commit.
|
||||
Some larger or more complicated change sets make more sense,
|
||||
and are easier to review if organized into multiple logical commits.
|
||||
Either way, all commits should fit the following criteria:
|
||||
Changes should be usually squashed down into a single commit. Some larger or more complicated change sets make more sense, and are easier to review if organized into multiple logical commits. Either way, all commits should fit the following criteria:
|
||||
|
||||
- Changes should be presented in a single commit or a logical
|
||||
sequence of commits.
|
||||
Specifically, chronological commits that simply
|
||||
reflect the history of how the author implemented
|
||||
the change, "warts and all", are not useful to
|
||||
reviewers.
|
||||
- Every commit should have a [good message](#good-commit-messages).
|
||||
to explain a specific aspects of the change.
|
||||
- Changes should be presented in a single commit or a logical sequence of commits. Specifically, chronological commits that simply reflect the history of how the author implemented the change, "warts and all", are not useful to reviewers.
|
||||
- Every commit should have a [good message](#good-commit-messages). to explain a specific aspects of the change.
|
||||
- Every commit should be signed.
|
||||
- Every commit should be well-formed (builds successfully,
|
||||
unit tests passing), as this helps to resolve merge
|
||||
conflicts, and makes it easier to use `git bisect`
|
||||
to find bugs.
|
||||
- Every commit should be well-formed (builds successfully, unit tests passing), as this helps to resolve merge conflicts, and makes it easier to use `git bisect` to find bugs.
|
||||
|
||||
### Good commit messages
|
||||
|
||||
Refer to
|
||||
["How to Write a Git Commit Message"](https://cbea.ms/git-commit/)
|
||||
for general rules on writing a good commit message.
|
||||
Refer to ["How to Write a Git Commit Message"](https://cbea.ms/git-commit/) for general rules on writing a good commit message.
|
||||
|
||||
tl;dr
|
||||
|
||||
@@ -135,9 +72,7 @@ tl;dr
|
||||
> 3. Capitalize the subject line.
|
||||
> 4. Do not end the subject line with a period.
|
||||
> 5. Use the imperative mood in the subject line.
|
||||
> - A properly formed Git commit subject line should always be able
|
||||
> to complete the following sentence: "If applied, this commit will
|
||||
> _your subject line here_".
|
||||
> - A properly formed Git commit subject line should always be able to complete the following sentence: "If applied, this commit will _your subject line here_".
|
||||
> 6. Wrap the body at 72 characters.
|
||||
> 7. Use the body to explain what and why vs. how.
|
||||
|
||||
@@ -145,32 +80,15 @@ tl;dr
|
||||
|
||||
In general, pull requests use `develop` as the base branch.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
While a pull request is marked as a "Draft", you can rebase or reorganize the
|
||||
commits in the pull request as desired.
|
||||
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. While a pull request is marked as a "Draft", you can rebase or reorganize the commits in the pull request as desired.
|
||||
|
||||
Github pull requests are created as "Ready" by default, or you can mark
|
||||
a "Draft" pull request as "Ready".
|
||||
Once a pull request is marked as "Ready",
|
||||
any changes must be added as new commits. Do not
|
||||
force-push to a branch in a pull request under review.
|
||||
(This includes rebasing your branch onto the updated base branch.
|
||||
Use a merge operation, instead or hit the "Update branch" button
|
||||
at the bottom of the Github PR page.)
|
||||
This preserves the ability for reviewers to filter changes since their last
|
||||
review.
|
||||
Github pull requests are created as "Ready" by default, or you can mark a "Draft" pull request as "Ready". Once a pull request is marked as "Ready", any changes must be added as new commits. Do not force-push to a branch in a pull request under review. (This includes rebasing your branch onto the updated base branch. Use a merge operation, instead or hit the "Update branch" button at the bottom of the Github PR page.) This preserves the ability for reviewers to filter changes since their last review.
|
||||
|
||||
A pull request must obtain **approvals from at least two reviewers**
|
||||
before it can be considered for merge by a Maintainer.
|
||||
Maintainers retain discretion to require more approvals if they feel the
|
||||
credibility of the existing approvals is insufficient.
|
||||
A pull request must obtain **approvals from at least two reviewers** before it can be considered for merge by a Maintainer. Maintainers retain discretion to require more approvals if they feel the credibility of the existing approvals is insufficient.
|
||||
|
||||
Pull requests must be merged by [squash-and-merge][squash]
|
||||
to preserve a linear history for the `develop` branch.
|
||||
Pull requests must be merged by [squash-and-merge][squash] to preserve a linear history for the `develop` branch.
|
||||
|
||||
### Type of Change
|
||||
|
||||
@@ -191,54 +109,26 @@ First letter after the type prefix should be capitalized, and the type prefix sh
|
||||
|
||||
### "Ready to merge"
|
||||
|
||||
A pull request should only have the "Ready to merge" label added when it
|
||||
meets a few criteria:
|
||||
A pull request should only have the "Ready to merge" label added when it meets a few criteria:
|
||||
|
||||
1. It must have two approving reviews [as described
|
||||
above](#pull-requests). (Exception: PRs that are deemed "trivial"
|
||||
only need one approval.)
|
||||
2. All CI checks must be complete and passed. (One-off failures may
|
||||
be acceptable if they are related to a known issue.)
|
||||
1. It must have two approving reviews [as described above](#pull-requests). (Exception: PRs that are deemed "trivial" only need one approval.)
|
||||
2. All CI checks must be complete and passed. (One-off failures may be acceptable if they are related to a known issue.)
|
||||
3. The PR must have a [good commit message](#good-commit-messages).
|
||||
- If the PR started with a good commit message, and it doesn't
|
||||
need to be updated, the author can indicate that in a comment.
|
||||
- Any contributor, preferably the author, can leave a comment
|
||||
suggesting a commit message.
|
||||
- If the author squashes and rebases the code in preparation for
|
||||
merge, they should also ensure the commit message(s) are updated
|
||||
as well.
|
||||
4. The PR branch must be up to date with the base branch (usually
|
||||
`develop`). This is usually accomplished by merging the base branch
|
||||
into the feature branch, but if the other criteria are met, the
|
||||
changes can be squashed and rebased on top of the base branch.
|
||||
5. Finally, and most importantly, the author of the PR must
|
||||
positively indicate that the PR is ready to merge. That can be
|
||||
accomplished by adding the "Ready to merge" label if their role
|
||||
allows, or by leaving a comment to the effect that the PR is ready to
|
||||
merge.
|
||||
- If the PR started with a good commit message, and it doesn't need to be updated, the author can indicate that in a comment.
|
||||
- Any contributor, preferably the author, can leave a comment suggesting a commit message.
|
||||
- If the author squashes and rebases the code in preparation for merge, they should also ensure the commit message(s) are updated as well.
|
||||
4. The PR branch must be up to date with the base branch (usually `develop`). This is usually accomplished by merging the base branch into the feature branch, but if the other criteria are met, the changes can be squashed and rebased on top of the base branch.
|
||||
5. Finally, and most importantly, the author of the PR must positively indicate that the PR is ready to merge. That can be accomplished by adding the "Ready to merge" label if their role allows, or by leaving a comment to the effect that the PR is ready to merge.
|
||||
|
||||
Once the "Ready to merge" label is added, a maintainer may merge the PR
|
||||
at any time, so don't use it lightly.
|
||||
Once the "Ready to merge" label is added, a maintainer may merge the PR at any time, so don't use it lightly.
|
||||
|
||||
# Style guide
|
||||
|
||||
This is a non-exhaustive list of recommended style guidelines. These are
|
||||
not always strictly enforced and serve as a way to keep the codebase
|
||||
coherent rather than a set of _thou shalt not_ commandments.
|
||||
This is a non-exhaustive list of recommended style guidelines. These are not always strictly enforced and serve as a way to keep the codebase coherent rather than a set of _thou shalt not_ commandments.
|
||||
|
||||
## Pre-commit hooks
|
||||
|
||||
We use the [`pre-commit`](https://pre-commit.com/) framework to run the
|
||||
formatting and linting tools that keep the codebase consistent. `pre-commit`
|
||||
runs each tool configured in
|
||||
[`.pre-commit-config.yaml`](./.pre-commit-config.yaml) in its own isolated
|
||||
environment, so you don't need to install most of the individual tools
|
||||
yourself. The version of each hook sourced from an external repository
|
||||
(`clang-format`, `gersemi`, etc.) is pinned in that file, so running the hooks
|
||||
locally uses exactly the same versions as CI. A few `local` hooks — most notably
|
||||
`clang-tidy` and `cargo fmt` — run tools from your own environment; see
|
||||
[Installing clang-tidy](#installing-clang-tidy) and
|
||||
[Rust](./docs/build/environment.md#rust) for how to get those.
|
||||
We use the [`pre-commit`](https://pre-commit.com/) framework to run the formatting and linting tools that keep the codebase consistent. `pre-commit` runs each tool configured in [`.pre-commit-config.yaml`](./.pre-commit-config.yaml) in its own isolated environment, so you don't need to install most of the individual tools yourself. The version of each hook sourced from an external repository (`clang-format`, `gersemi`, etc.) is pinned in that file, so running the hooks locally uses exactly the same versions as CI. A few `local` hooks — most notably `clang-tidy` and `cargo fmt` — run tools from your own environment; see [Installing clang-tidy](#installing-clang-tidy) and [Rust](./docs/build/environment.md#rust) for how to get those.
|
||||
|
||||
To get started, install `pre-commit` and enable the git hook scripts:
|
||||
|
||||
@@ -247,8 +137,7 @@ pip install pre-commit
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
Once installed, the hooks run automatically on your staged files every time you
|
||||
`git commit`. You can also run them on demand:
|
||||
Once installed, the hooks run automatically on your staged files every time you `git commit`. You can also run them on demand:
|
||||
|
||||
```bash
|
||||
# Run all hooks against only the staged files
|
||||
@@ -271,18 +160,11 @@ The hooks configured in this repository include, among others:
|
||||
- `prettier`, `black`, `shfmt` — formatting for JavaScript/JSON/Markdown, Python, and shell
|
||||
- `cspell` — spell checking
|
||||
|
||||
The same hooks run in CI on every pull request, so running them locally before
|
||||
you push helps you avoid CI failures.
|
||||
The same hooks run in CI on every pull request, so running them locally before you push helps you avoid CI failures.
|
||||
|
||||
## Formatting
|
||||
|
||||
All code must conform to `clang-format`, according to the settings in
|
||||
[`.clang-format`](./.clang-format), unless the result would be unreasonably
|
||||
difficult to read or maintain. The `clang-format` version is pinned in
|
||||
[`.pre-commit-config.yaml`](./.pre-commit-config.yaml), so the
|
||||
[`pre-commit`](#pre-commit-hooks) hook always formats with the same version as
|
||||
CI. To demarcate lines that should be left as-is, surround them with comments
|
||||
like this:
|
||||
All code must conform to `clang-format`, according to the settings in [`.clang-format`](./.clang-format), unless the result would be unreasonably difficult to read or maintain. The `clang-format` version is pinned in [`.pre-commit-config.yaml`](./.pre-commit-config.yaml), so the [`pre-commit`](#pre-commit-hooks) hook always formats with the same version as CI. To demarcate lines that should be left as-is, surround them with comments like this:
|
||||
|
||||
```
|
||||
// clang-format off
|
||||
@@ -290,20 +172,17 @@ like this:
|
||||
// clang-format on
|
||||
```
|
||||
|
||||
The easiest way to format your changes is to let the `pre-commit` hook run
|
||||
automatically on commit, or to run it manually:
|
||||
The easiest way to format your changes is to let the `pre-commit` hook run automatically on commit, or to run it manually:
|
||||
|
||||
```bash
|
||||
pre-commit run clang-format --all-files
|
||||
```
|
||||
|
||||
You can also format individual files in place by running `clang-format -i <file>...`
|
||||
from any directory within this project.
|
||||
You can also format individual files in place by running `clang-format -i <file>...` from any directory within this project.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> This uses whatever `clang-format` version is installed locally, which may
|
||||
> differ from the pinned version used by `pre-commit` and CI, so the results
|
||||
> can vary.
|
||||
> This uses whatever `clang-format` version is installed locally, which may differ from the pinned version used by `pre-commit` and CI, so the results can vary.
|
||||
|
||||
There is a Continuous Integration job that runs clang-format on pull requests. If the code doesn't comply, a patch file that corrects auto-fixable formatting issues is generated.
|
||||
|
||||
@@ -364,8 +243,7 @@ Then run clang-tidy on your local changes:
|
||||
run-clang-tidy -p build -allow-no-checks src tests
|
||||
```
|
||||
|
||||
This will check all source files in the `src`, `include` and `tests` directories using the compile commands from your `build` directory.
|
||||
If you wish to automatically fix whatever clang-tidy finds _and_ is capable of fixing, add `-fix -format` to the above command:
|
||||
This will check all source files in the `src`, `include` and `tests` directories using the compile commands from your `build` directory. If you wish to automatically fix whatever clang-tidy finds _and_ is capable of fixing, add `-fix -format` to the above command:
|
||||
|
||||
```
|
||||
run-clang-tidy -p build -quiet -fix -format -allow-no-checks src tests
|
||||
@@ -375,27 +253,11 @@ run-clang-tidy -p build -quiet -fix -format -allow-no-checks src tests
|
||||
|
||||
## Contracts and instrumentation
|
||||
|
||||
We are using [Antithesis](https://antithesis.com/) for continuous fuzzing,
|
||||
and keep a copy of [Antithesis C++ SDK](https://github.com/antithesishq/antithesis-sdk-cpp/)
|
||||
in `external/antithesis-sdk`. One of the aims of fuzzing is to identify bugs
|
||||
by finding external conditions which cause contracts violations inside `xrpld`.
|
||||
The contracts are expressed as `XRPL_ASSERT` or `UNREACHABLE` (defined in
|
||||
`include/xrpl/beast/utility/instrumentation.h`), which are effectively (outside
|
||||
of Antithesis) wrappers for `assert(...)` with added name. The purpose of name
|
||||
is to provide contracts with stable identity which does not rely on line numbers.
|
||||
We are using [Antithesis](https://antithesis.com/) for continuous fuzzing, and keep a copy of [Antithesis C++ SDK](https://github.com/antithesishq/antithesis-sdk-cpp/) in `external/antithesis-sdk`. One of the aims of fuzzing is to identify bugs by finding external conditions which cause contracts violations inside `xrpld`. The contracts are expressed as `XRPL_ASSERT` or `UNREACHABLE` (defined in `include/xrpl/beast/utility/instrumentation.h`), which are effectively (outside of Antithesis) wrappers for `assert(...)` with added name. The purpose of name is to provide contracts with stable identity which does not rely on line numbers.
|
||||
|
||||
When `xrpld` is built with the Antithesis instrumentation enabled
|
||||
(using `voidstar` CMake option) and ran on the Antithesis platform, the
|
||||
contracts become
|
||||
[test properties](https://antithesis.com/docs/using_antithesis/properties.html);
|
||||
otherwise they are just like a regular `assert`.
|
||||
To learn more about Antithesis, see
|
||||
[How Antithesis Works](https://antithesis.com/docs/introduction/how_antithesis_works.html)
|
||||
and [C++ SDK](https://antithesis.com/docs/using_antithesis/sdk/cpp/overview.html#)
|
||||
When `xrpld` is built with the Antithesis instrumentation enabled (using `voidstar` CMake option) and ran on the Antithesis platform, the contracts become [test properties](https://antithesis.com/docs/using_antithesis/properties.html); otherwise they are just like a regular `assert`. To learn more about Antithesis, see [How Antithesis Works](https://antithesis.com/docs/introduction/how_antithesis_works.html) and [C++ SDK](https://antithesis.com/docs/using_antithesis/sdk/cpp/overview.html#)
|
||||
|
||||
We continue to use the old style `assert` or `assert(false)` in certain
|
||||
locations, where the reporting of contract violations on the Antithesis
|
||||
platform is either not possible or not useful.
|
||||
We continue to use the old style `assert` or `assert(false)` in certain locations, where the reporting of contract violations on the Antithesis platform is either not possible or not useful.
|
||||
|
||||
For this reason:
|
||||
|
||||
@@ -403,38 +265,17 @@ For this reason:
|
||||
- `constexpr` functions
|
||||
- unit tests i.e. files under `src/test`
|
||||
- unit tests-related modules (files under `beast/test` and `beast/unit_test`)
|
||||
- Outside of the listed locations, do not use `assert`; use `XRPL_ASSERT` instead,
|
||||
giving it unique name, with the short description of the contract.
|
||||
- Outside of the listed locations, do not use `assert(false)`; use
|
||||
`UNREACHABLE` instead, giving it unique name, with the description of the
|
||||
condition being violated
|
||||
- The contract name should start with a full name (including scope) of the
|
||||
function, optionally a named lambda, followed by a colon `:` and a brief
|
||||
(typically at most five words) description. `UNREACHABLE` contracts
|
||||
can use slightly longer descriptions. If there are multiple overloads of the
|
||||
function, use common sense to balance both brevity and unambiguity of the
|
||||
function name. NOTE: the purpose of name is to provide stable means of
|
||||
unique identification of every contract; for this reason try to avoid elements
|
||||
which can change in some obvious refactors or when reinforcing the condition.
|
||||
- Contract description typically (except for `UNREACHABLE`) should describe the
|
||||
_expected_ condition, as in "I assert that _expected_ is true".
|
||||
- Contract description for `UNREACHABLE` should describe the _unexpected_
|
||||
situation which caused the line to have been reached.
|
||||
- Example good name for an
|
||||
`UNREACHABLE` macro `"json::operator==(Value, Value) : invalid type"`; example
|
||||
good name for an `XRPL_ASSERT` macro `"json::Value::asCString : valid type"`.
|
||||
- Example **bad** name
|
||||
`"RFC1751::insert(char* s, int x, int start, int length) : length is greater than or equal zero"`
|
||||
(missing namespace, unnecessary full function signature, description too verbose).
|
||||
Good name: `"xrpl::RFC1751::insert : minimum length"`.
|
||||
- In **few** well-justified cases a non-standard name can be used, in which case a
|
||||
comment should be placed to explain the rationale (example in `contract.cpp`)
|
||||
- Do **not** rename a contract without a good reason (e.g. the name no longer
|
||||
reflects the location or the condition being checked)
|
||||
- Outside of the listed locations, do not use `assert`; use `XRPL_ASSERT` instead, giving it unique name, with the short description of the contract.
|
||||
- Outside of the listed locations, do not use `assert(false)`; use `UNREACHABLE` instead, giving it unique name, with the description of the condition being violated
|
||||
- The contract name should start with a full name (including scope) of the function, optionally a named lambda, followed by a colon `:` and a brief (typically at most five words) description. `UNREACHABLE` contracts can use slightly longer descriptions. If there are multiple overloads of the function, use common sense to balance both brevity and unambiguity of the function name. NOTE: the purpose of name is to provide stable means of unique identification of every contract; for this reason try to avoid elements which can change in some obvious refactors or when reinforcing the condition.
|
||||
- Contract description typically (except for `UNREACHABLE`) should describe the _expected_ condition, as in "I assert that _expected_ is true".
|
||||
- Contract description for `UNREACHABLE` should describe the _unexpected_ situation which caused the line to have been reached.
|
||||
- Example good name for an `UNREACHABLE` macro `"json::operator==(Value, Value) : invalid type"`; example good name for an `XRPL_ASSERT` macro `"json::Value::asCString : valid type"`.
|
||||
- Example **bad** name `"RFC1751::insert(char* s, int x, int start, int length) : length is greater than or equal zero"` (missing namespace, unnecessary full function signature, description too verbose). Good name: `"xrpl::RFC1751::insert : minimum length"`.
|
||||
- In **few** well-justified cases a non-standard name can be used, in which case a comment should be placed to explain the rationale (example in `contract.cpp`)
|
||||
- Do **not** rename a contract without a good reason (e.g. the name no longer reflects the location or the condition being checked)
|
||||
- Do not use `std::unreachable`
|
||||
- Do not put contracts where they can be violated by an external condition
|
||||
(e.g. timing, data payload before mandatory validation etc.) as this creates
|
||||
bogus bug reports (and causes crashes of Debug builds)
|
||||
- Do not put contracts where they can be violated by an external condition (e.g. timing, data payload before mandatory validation etc.) as this creates bogus bug reports (and causes crashes of Debug builds)
|
||||
|
||||
## Unit Tests
|
||||
|
||||
@@ -442,8 +283,7 @@ To execute all unit tests:
|
||||
|
||||
`xrpld --unittest --unittest-jobs=<number of cores>`
|
||||
|
||||
(Note: Using multiple cores on a Mac M1 can cause spurious test failures. The
|
||||
cause is still under investigation. If you observe this problem, try specifying fewer jobs.)
|
||||
(Note: Using multiple cores on a Mac M1 can cause spurious test failures. The cause is still under investigation. If you observe this problem, try specifying fewer jobs.)
|
||||
|
||||
To run a specific set of test suites:
|
||||
|
||||
@@ -451,11 +291,7 @@ To run a specific set of test suites:
|
||||
xrpld --unittest TestSuiteName
|
||||
```
|
||||
|
||||
Note: In this example, all tests with prefix `TestSuiteName` will be run, so if
|
||||
`TestSuiteName1` and `TestSuiteName2` both exist, then both tests will run.
|
||||
Alternatively, if the unit test name finds an exact match, it will stop
|
||||
doing partial matches, i.e. if a unit test with a title of `TestSuiteName`
|
||||
exists, then no other unit test will be executed, apart from `TestSuiteName`.
|
||||
Note: In this example, all tests with prefix `TestSuiteName` will be run, so if `TestSuiteName1` and `TestSuiteName2` both exist, then both tests will run. Alternatively, if the unit test name finds an exact match, it will stop doing partial matches, i.e. if a unit test with a title of `TestSuiteName` exists, then no other unit test will be executed, apart from `TestSuiteName`.
|
||||
|
||||
## Avoid
|
||||
|
||||
@@ -465,44 +301,27 @@ exists, then no other unit test will be executed, apart from `TestSuiteName`.
|
||||
4. Unmanaged memory allocation and raw pointers.
|
||||
5. Macros and non-trivial templates (unless they add significant value).
|
||||
6. Lambda patterns (unless these add significant value).
|
||||
7. CPU or architecture-specific code unless there is a good reason to
|
||||
include it, and where it is used, guard it with macros and provide
|
||||
explanatory comments.
|
||||
7. CPU or architecture-specific code unless there is a good reason to include it, and where it is used, guard it with macros and provide explanatory comments.
|
||||
8. Importing new libraries unless there is a very good reason to do so.
|
||||
|
||||
## Seek to
|
||||
|
||||
9. Extend functionality of existing code rather than creating new code.
|
||||
10. Prefer readability over terseness where important logic is
|
||||
concerned.
|
||||
11. Inline functions that are not used or are not likely to be used
|
||||
elsewhere in the codebase.
|
||||
12. Use clear and self-explanatory names for functions, variables,
|
||||
structs and classes.
|
||||
13. Use TitleCase for classes, structs and filenames, camelCase for
|
||||
function and variable names, lower case for namespaces and folders.
|
||||
14. Provide as many comments as you feel that a competent programmer
|
||||
would need to understand what your code does.
|
||||
10. Prefer readability over terseness where important logic is concerned.
|
||||
11. Inline functions that are not used or are not likely to be used elsewhere in the codebase.
|
||||
12. Use clear and self-explanatory names for functions, variables, structs and classes.
|
||||
13. Use TitleCase for classes, structs and filenames, camelCase for function and variable names, lower case for namespaces and folders.
|
||||
14. Provide as many comments as you feel that a competent programmer would need to understand what your code does.
|
||||
|
||||
# Maintainers
|
||||
|
||||
Maintainers are ecosystem participants with elevated access to the repository.
|
||||
They are able to push new code, make decisions on when a release should be
|
||||
made, etc.
|
||||
Maintainers are ecosystem participants with elevated access to the repository. They are able to push new code, make decisions on when a release should be made, etc.
|
||||
|
||||
## Adding and removing
|
||||
|
||||
New maintainers can be proposed by two existing maintainers, subject to a vote
|
||||
by a quorum of the existing maintainers.
|
||||
A minimum of 50% support and a 50% participation is required.
|
||||
In the event of a tie vote, the addition of the new maintainer will be
|
||||
rejected.
|
||||
New maintainers can be proposed by two existing maintainers, subject to a vote by a quorum of the existing maintainers. A minimum of 50% support and a 50% participation is required. In the event of a tie vote, the addition of the new maintainer will be rejected.
|
||||
|
||||
Existing maintainers can resign, or be subject to a vote for removal at the
|
||||
behest of two existing maintainers.
|
||||
A minimum of 60% agreement and 50% participation are required.
|
||||
The XRP Ledger Foundation will have the ability, for cause, to remove an
|
||||
existing maintainer without a vote.
|
||||
Existing maintainers can resign, or be subject to a vote for removal at the behest of two existing maintainers. A minimum of 60% agreement and 50% participation are required. The XRP Ledger Foundation will have the ability, for cause, to remove an existing maintainer without a vote.
|
||||
|
||||
## Current Maintainers
|
||||
|
||||
@@ -518,8 +337,7 @@ Maintainers are users with maintain or admin access to the repo.
|
||||
|
||||
## Current Code Reviewers
|
||||
|
||||
Code Reviewers are developers who have the ability to review, approve, and
|
||||
in some cases merge source code changes.
|
||||
Code Reviewers are developers who have the ability to review, approve, and in some cases merge source code changes.
|
||||
|
||||
- [a1q123456](https://github.com/a1q123456) (Ripple)
|
||||
- [Bronek](https://github.com/Bronek) (Ripple)
|
||||
@@ -540,14 +358,11 @@ in some cases merge source code changes.
|
||||
- [Tapanito](https://github.com/Tapanito) (Ripple)
|
||||
- [ximinez](https://github.com/ximinez) (Ripple)
|
||||
|
||||
Developers not on this list are able and encouraged to submit feedback
|
||||
on pending code changes (open pull requests).
|
||||
Developers not on this list are able and encouraged to submit feedback on pending code changes (open pull requests).
|
||||
|
||||
## Instructions for maintainers
|
||||
|
||||
These instructions assume you have your git upstream remotes configured
|
||||
to avoid accidental pushes to the main repo, and a remote group
|
||||
specifying both of them. e.g.
|
||||
These instructions assume you have your git upstream remotes configured to avoid accidental pushes to the main repo, and a remote group specifying both of them. e.g.
|
||||
|
||||
```
|
||||
$ git remote -v | grep upstream
|
||||
@@ -572,104 +387,58 @@ $ git config user.signingkey
|
||||
|
||||
### When and how to merge pull requests
|
||||
|
||||
The maintainer should double-check that the PR has met all the
|
||||
necessary criteria, and can request additional information from the
|
||||
owner, or additional reviews, and can always feel free to remove the
|
||||
"Ready to merge" label if appropriate. The maintainer has final say on
|
||||
whether a PR gets merged, and are encouraged to communicate and issues
|
||||
or concerns to other maintainers.
|
||||
The maintainer should double-check that the PR has met all the necessary criteria, and can request additional information from the owner, or additional reviews, and can always feel free to remove the "Ready to merge" label if appropriate. The maintainer has final say on whether a PR gets merged, and are encouraged to communicate and issues or concerns to other maintainers.
|
||||
|
||||
#### Most pull requests: "Squash and merge"
|
||||
|
||||
Most pull requests don't need special handling, and can simply be
|
||||
merged using the "Squash and merge" button on the Github UI. Update
|
||||
the suggested commit message, or modify it as needed.
|
||||
Most pull requests don't need special handling, and can simply be merged using the "Squash and merge" button on the Github UI. Update the suggested commit message, or modify it as needed.
|
||||
|
||||
#### Slightly more complicated pull requests
|
||||
|
||||
Some pull requests need to be pushed to `develop` as more than one
|
||||
commit. A PR author may _request_ to merge as separate commits. They
|
||||
must _justify_ why separate commits are needed, and _specify_ how they
|
||||
would like the commits to be merged. If you disagree with the author,
|
||||
discuss it with them directly.
|
||||
Some pull requests need to be pushed to `develop` as more than one commit. A PR author may _request_ to merge as separate commits. They must _justify_ why separate commits are needed, and _specify_ how they would like the commits to be merged. If you disagree with the author, discuss it with them directly.
|
||||
|
||||
If the process is reasonable, follow it. The simplest option is to do a
|
||||
fast forward only merge (`--ff-only`) on the command line and push to
|
||||
`develop`.
|
||||
If the process is reasonable, follow it. The simplest option is to do a fast forward only merge (`--ff-only`) on the command line and push to `develop`.
|
||||
|
||||
Some examples of when separate commits are worthwhile are:
|
||||
|
||||
1. PRs where source files are reorganized in multiple steps.
|
||||
2. PRs where the commits are mostly independent and _could_ be separate
|
||||
PRs, but are pulled together into one PR under a commit theme or
|
||||
issue.
|
||||
3. PRs that are complicated enough that `git bisect` would not be much
|
||||
help if it determined this PR introduced a problem.
|
||||
2. PRs where the commits are mostly independent and _could_ be separate PRs, but are pulled together into one PR under a commit theme or issue.
|
||||
3. PRs that are complicated enough that `git bisect` would not be much help if it determined this PR introduced a problem.
|
||||
|
||||
Either way, check that:
|
||||
|
||||
- The commits are based on the current tip of `develop`.
|
||||
- The commits are clean: No merge commits (except when reverse
|
||||
merging), no "[FOLD]" or "fixup!" messages.
|
||||
- All commits are signed. If the commits are not signed by the author, use
|
||||
`git commit --amend -S` to sign them yourself.
|
||||
- At least one (but preferably all) of the commits has the PR number
|
||||
in the commit message.
|
||||
- The commits are clean: No merge commits (except when reverse merging), no "[FOLD]" or "fixup!" messages.
|
||||
- All commits are signed. If the commits are not signed by the author, use `git commit --amend -S` to sign them yourself.
|
||||
- At least one (but preferably all) of the commits has the PR number in the commit message.
|
||||
|
||||
The "Create a merge commit" and "Rebase and merge" options should be
|
||||
disabled in the Github UI, but if you ever find them available **Do not
|
||||
use them!**
|
||||
The "Create a merge commit" and "Rebase and merge" options should be disabled in the Github UI, but if you ever find them available **Do not use them!**
|
||||
|
||||
### Releases
|
||||
|
||||
All releases, including release candidates and betas, are handled
|
||||
differently from typical PRs. Most importantly, never use
|
||||
the Github UI to merge a release.
|
||||
All releases, including release candidates and betas, are handled differently from typical PRs. Most importantly, never use the Github UI to merge a release.
|
||||
|
||||
Xrpld uses a linear workflow model that can be summarized as:
|
||||
|
||||
1. In between releases, developers work against the `develop` branch.
|
||||
2. Periodically, a maintainer will build and tag a beta version from
|
||||
`develop`, which is pushed to `release`.
|
||||
- Betas are usually released every two to three weeks, though that
|
||||
schedule can vary depending on progress, availability, and other
|
||||
factors.
|
||||
3. When the changes in `develop` are considered stable and mature enough
|
||||
to be ready to release, a release candidate (RC) is built and tagged
|
||||
from `develop`, and merged to `release`.
|
||||
- Further development for that release (primarily fixes) then
|
||||
continues against `release`, while other development continues on
|
||||
`develop`. Effectively, `release` is forked from `develop`. Changes
|
||||
to `release` must be reverse merged to `develop`.
|
||||
4. When the candidate has passed testing and is ready for release, the
|
||||
final release is merged to `master`.
|
||||
5. If any issues are found post-release, a hotfix / point release may be
|
||||
created, which is merged to `master`, and then reverse merged to
|
||||
`develop`.
|
||||
2. Periodically, a maintainer will build and tag a beta version from `develop`, which is pushed to `release`.
|
||||
- Betas are usually released every two to three weeks, though that schedule can vary depending on progress, availability, and other factors.
|
||||
3. When the changes in `develop` are considered stable and mature enough to be ready to release, a release candidate (RC) is built and tagged from `develop`, and merged to `release`.
|
||||
- Further development for that release (primarily fixes) then continues against `release`, while other development continues on `develop`. Effectively, `release` is forked from `develop`. Changes to `release` must be reverse merged to `develop`.
|
||||
4. When the candidate has passed testing and is ready for release, the final release is merged to `master`.
|
||||
5. If any issues are found post-release, a hotfix / point release may be created, which is merged to `master`, and then reverse merged to `develop`.
|
||||
|
||||
#### Betas, and the first release candidate
|
||||
|
||||
##### Preparing the `develop` branch
|
||||
|
||||
1. Optimally, the `develop` branch will be ready to go, with all
|
||||
relevant PRs already merged.
|
||||
1. Optimally, the `develop` branch will be ready to go, with all relevant PRs already merged.
|
||||
2. If there are any PRs pending, merge them **BEFORE** preparing the beta.
|
||||
1. If only one or two PRs need to be merged, merge those PRs [as
|
||||
normal](#when-and-how-to-merge-pull-requests), updating the second
|
||||
one, and waiting for CI to finish in between.
|
||||
2. If there are several pending PRs, do not use the Github UI,
|
||||
because the delays waiting for CI in between each merge will be
|
||||
unnecessarily onerous. (Incidentally, this process can also be
|
||||
used to merge if the Github UI has issues.) Merge each PR branch
|
||||
directly to a `release-next` on your local machine and create a single
|
||||
PR, then push your branch to `develop`.
|
||||
1. Squash the changes from each PR, one commit each (unless more
|
||||
are needed), being sure to sign each commit and update the
|
||||
commit message to include the PR number. You may be able to use
|
||||
a fast-forward merge for the first PR.
|
||||
1. If only one or two PRs need to be merged, merge those PRs [as normal](#when-and-how-to-merge-pull-requests), updating the second one, and waiting for CI to finish in between.
|
||||
2. If there are several pending PRs, do not use the Github UI, because the delays waiting for CI in between each merge will be unnecessarily onerous. (Incidentally, this process can also be used to merge if the Github UI has issues.) Merge each PR branch directly to a `release-next` on your local machine and create a single PR, then push your branch to `develop`.
|
||||
1. Squash the changes from each PR, one commit each (unless more are needed), being sure to sign each commit and update the commit message to include the PR number. You may be able to use a fast-forward merge for the first PR.
|
||||
2. Push your branch.
|
||||
3. Continue to [Making the release](#making-the-release) to update
|
||||
the version number, etc.
|
||||
3. Continue to [Making the release](#making-the-release) to update the version number, etc.
|
||||
|
||||
The workflow may look something like:
|
||||
|
||||
@@ -702,17 +471,13 @@ git push --set-upstream origin
|
||||
|
||||
You can also use the [squash-branches] script.
|
||||
|
||||
You may also need to manually close the open PRs after the changes are
|
||||
merged to `develop`. Be sure to include the commit ID.
|
||||
You may also need to manually close the open PRs after the changes are merged to `develop`. Be sure to include the commit ID.
|
||||
|
||||
##### Making the release
|
||||
|
||||
This includes, betas, and the first release candidate (RC).
|
||||
|
||||
1. If you didn't create one [preparing the `develop`
|
||||
branch](#preparing-the-develop-branch), Ensure there is no old
|
||||
`release-next` branch hanging around. Then make a `release-next`
|
||||
branch that only changes the version number. e.g.
|
||||
1. If you didn't create one [preparing the `develop` branch](#preparing-the-develop-branch), Ensure there is no old `release-next` branch hanging around. Then make a `release-next` branch that only changes the version number. e.g.
|
||||
|
||||
```
|
||||
git fetch upstreams
|
||||
@@ -735,8 +500,7 @@ git fetch upstreams
|
||||
git branch --set-upstream-to=upstream/release-next
|
||||
```
|
||||
|
||||
You can also use the [update-version] script. 2. Create a Pull Request for `release-next` with **`develop`** as
|
||||
the base branch.
|
||||
You can also use the [update-version] script. 2. Create a Pull Request for `release-next` with **`develop`** as the base branch.
|
||||
|
||||
1. Use the title "[TRIVIAL] Set version to X.X.X-bX".
|
||||
2. Instead of the default description template, use the following:
|
||||
@@ -748,27 +512,17 @@ This PR only changes the version number. It will be merged as
|
||||
soon as Github CI actions successfully complete.
|
||||
```
|
||||
|
||||
3. Wait for CI to successfully complete, and get someone to approve
|
||||
the PR. (It is safe to ignore known CI issues.)
|
||||
4. Push the updated `develop` branch using your `release-next`
|
||||
branch. **Do not use the Github UI. It's important to preserve
|
||||
commit IDs.**
|
||||
3. Wait for CI to successfully complete, and get someone to approve the PR. (It is safe to ignore known CI issues.)
|
||||
4. Push the updated `develop` branch using your `release-next` branch. **Do not use the Github UI. It's important to preserve commit IDs.**
|
||||
|
||||
```
|
||||
git push upstream-push release-next:develop
|
||||
```
|
||||
|
||||
5. In the unlikely event that the push fails because someone has merged
|
||||
something else in the meantime, rebase your branch onto the updated
|
||||
`develop` branch, push again, and go back to step 3.
|
||||
6. Ensure that your PR against `develop` is closed. Github should do it
|
||||
automatically.
|
||||
7. Once this is done, forward progress on `develop` can continue
|
||||
(other PRs may be merged).
|
||||
8. Now create a Pull Request for `release-next` with **`release`** as
|
||||
the base branch. Instead of the default template, reuse and update
|
||||
the message from the previous release. Include the following verbiage
|
||||
somewhere in the description:
|
||||
5. In the unlikely event that the push fails because someone has merged something else in the meantime, rebase your branch onto the updated `develop` branch, push again, and go back to step 3.
|
||||
6. Ensure that your PR against `develop` is closed. Github should do it automatically.
|
||||
7. Once this is done, forward progress on `develop` can continue (other PRs may be merged).
|
||||
8. Now create a Pull Request for `release-next` with **`release`** as the base branch. Instead of the default template, reuse and update the message from the previous release. Include the following verbiage somewhere in the description:
|
||||
|
||||
```
|
||||
The base branch is `release`. [All releases (including
|
||||
@@ -777,12 +531,8 @@ go in `release`. This PR branch will be pushed directly to `release` (not
|
||||
squashed or rebased, and not using the GitHub UI).
|
||||
```
|
||||
|
||||
7. Sign-offs for the three platforms (Linux, Mac, Windows) usually occur
|
||||
offline, but at least one approval will be needed on the PR.
|
||||
- If issues are discovered during testing, simply abandon the
|
||||
release. It's easy to start a new release, it should be easy to
|
||||
abandon one. **DO NOT REUSE THE VERSION NUMBER.** e.g. If you
|
||||
abandon 2.4.0-b1, the next attempt will be 2.4.0-b2.
|
||||
7. Sign-offs for the three platforms (Linux, Mac, Windows) usually occur offline, but at least one approval will be needed on the PR.
|
||||
- If issues are discovered during testing, simply abandon the release. It's easy to start a new release, it should be easy to abandon one. **DO NOT REUSE THE VERSION NUMBER.** e.g. If you abandon 2.4.0-b1, the next attempt will be 2.4.0-b2.
|
||||
8. Once everything is ready to go, push to `release`.
|
||||
|
||||
```
|
||||
@@ -819,44 +569,25 @@ git push upstream-push <version number>
|
||||
git push --delete upstream-push release-next
|
||||
```
|
||||
|
||||
11. Finally [create a new release on
|
||||
Github](https://github.com/XRPLF/rippled/releases).
|
||||
11. Finally [create a new release on Github](https://github.com/XRPLF/rippled/releases).
|
||||
|
||||
#### Release candidates after the first
|
||||
|
||||
Once the first release candidate is [merged into
|
||||
release](#making-the-release), then `release` and `develop` _are allowed
|
||||
to diverge_.
|
||||
Once the first release candidate is [merged into release](#making-the-release), then `release` and `develop` _are allowed to diverge_.
|
||||
|
||||
If a bug or issue is discovered in a version that has a release
|
||||
candidate being tested, any fix and new version will need to be applied
|
||||
against `release`, then reverse-merged to `develop`. This helps keep git
|
||||
history as linear as possible.
|
||||
If a bug or issue is discovered in a version that has a release candidate being tested, any fix and new version will need to be applied against `release`, then reverse-merged to `develop`. This helps keep git history as linear as possible.
|
||||
|
||||
A `release-next` branch will be created from `release`, and any further
|
||||
work for that release must be based on `release-next`. Specifically,
|
||||
PRs must use `release-next` as the base, and those PRs will be merged
|
||||
directly to `release-next` when approved. Changes should be restricted
|
||||
to bug fixes, but other changes may be necessary from time to time.
|
||||
A `release-next` branch will be created from `release`, and any further work for that release must be based on `release-next`. Specifically, PRs must use `release-next` as the base, and those PRs will be merged directly to `release-next` when approved. Changes should be restricted to bug fixes, but other changes may be necessary from time to time.
|
||||
|
||||
1. Open any PRs for the pending release using `release-next` as the base,
|
||||
so they can be merged directly in to it. Unlike `develop`, though,
|
||||
`release-next` can be thrown away and recreated if necessary.
|
||||
2. Once a new release candidate is ready, create a version commit as in
|
||||
step 1 [above](#making-the-release) on `release-next`. You can use
|
||||
the [update-version] script for this, too.
|
||||
3. Jump to step 8 ("Now create a Pull Request for `release-next` with
|
||||
**`release`** as the base") from the process
|
||||
[above](#making-the-release) to merge `release-next` into `release`.
|
||||
1. Open any PRs for the pending release using `release-next` as the base, so they can be merged directly in to it. Unlike `develop`, though, `release-next` can be thrown away and recreated if necessary.
|
||||
2. Once a new release candidate is ready, create a version commit as in step 1 [above](#making-the-release) on `release-next`. You can use the [update-version] script for this, too.
|
||||
3. Jump to step 8 ("Now create a Pull Request for `release-next` with **`release`** as the base") from the process [above](#making-the-release) to merge `release-next` into `release`.
|
||||
|
||||
##### Follow up: reverse merge
|
||||
|
||||
Once the RC is merged and tagged, it needs to be reverse merged into
|
||||
`develop` as soon as possible.
|
||||
Once the RC is merged and tagged, it needs to be reverse merged into `develop` as soon as possible.
|
||||
|
||||
1. Create a branch, based on `upstream/develop`.
|
||||
The branch name is not important, but could include "mergeNNNrcN".
|
||||
E.g. For release A.B.C-rcD, use `mergeABCrcD`.
|
||||
1. Create a branch, based on `upstream/develop`. The branch name is not important, but could include "mergeNNNrcN". E.g. For release A.B.C-rcD, use `mergeABCrcD`.
|
||||
|
||||
```
|
||||
git fetch upstreams
|
||||
@@ -872,24 +603,15 @@ git checkout --no-track -b mergeABCrcD upstream/develop
|
||||
git merge upstream/release
|
||||
```
|
||||
|
||||
3. `BuildInfo.cpp` will have a conflict with the version number.
|
||||
Resolve it with the version from `develop` - the higher version.
|
||||
4. Push your branch to your repo (or `upstream` if you have permission),
|
||||
and open a normal PR against `develop`. The "High level overview" can
|
||||
simply indicate that this is a merge of the RC. The "Context" should
|
||||
summarize the changes from the RC. Include the following text
|
||||
prominently:
|
||||
3. `BuildInfo.cpp` will have a conflict with the version number. Resolve it with the version from `develop` - the higher version.
|
||||
4. Push your branch to your repo (or `upstream` if you have permission), and open a normal PR against `develop`. The "High level overview" can simply indicate that this is a merge of the RC. The "Context" should summarize the changes from the RC. Include the following text prominently:
|
||||
|
||||
```
|
||||
This PR must be merged manually using a push. Do not use the Github UI.
|
||||
```
|
||||
|
||||
5. Depending on the complexity of the changes, and/or merge conflicts,
|
||||
the PR may need a thorough review, or just a sign-off that the
|
||||
merge was done correctly.
|
||||
6. If `develop` is updated before this PR is merged, do not merge
|
||||
`develop` back into your branch. Instead rebase preserving merges,
|
||||
or do the merge again. (See also the `rerere` git config setting.)
|
||||
5. Depending on the complexity of the changes, and/or merge conflicts, the PR may need a thorough review, or just a sign-off that the merge was done correctly.
|
||||
6. If `develop` is updated before this PR is merged, do not merge `develop` back into your branch. Instead rebase preserving merges, or do the merge again. (See also the `rerere` git config setting.)
|
||||
|
||||
```
|
||||
git rebase --rebase-merges upstream/develop
|
||||
@@ -917,30 +639,14 @@ Development on `develop` can proceed as normal.
|
||||
|
||||
A final release is any release that is not a beta or RC, such as 2.2.0.
|
||||
|
||||
Only code that has already been tested and vetted across all three
|
||||
platforms should be included in a final release. Most of the time, that
|
||||
means that the commit immediately preceding the commit setting the
|
||||
version number will be an RC. Occasionally, there may be last-minute bug
|
||||
fixes included as well. If so, those bug fixes must have been tested
|
||||
internally as if they were RCs (at minimum, ensuring unit tests pass,
|
||||
and the app starts, syncs, and stops cleanly across all three
|
||||
platforms.)
|
||||
Only code that has already been tested and vetted across all three platforms should be included in a final release. Most of the time, that means that the commit immediately preceding the commit setting the version number will be an RC. Occasionally, there may be last-minute bug fixes included as well. If so, those bug fixes must have been tested internally as if they were RCs (at minimum, ensuring unit tests pass, and the app starts, syncs, and stops cleanly across all three platforms.)
|
||||
|
||||
_If in doubt, make an RC first._
|
||||
|
||||
The process for building a final release is very similar to [the process
|
||||
for building a beta](#making-the-release), except the code will be
|
||||
moving from `release` to `master` instead of from `develop` to
|
||||
`release`, and both branches will be pushed at the same time.
|
||||
The process for building a final release is very similar to [the process for building a beta](#making-the-release), except the code will be moving from `release` to `master` instead of from `develop` to `release`, and both branches will be pushed at the same time.
|
||||
|
||||
1. Ensure there is no old `master-next` branch hanging around.
|
||||
Then make a `master-next` branch that only changes the version
|
||||
number. As above, or using the
|
||||
[update-version] script.
|
||||
2. Create a Pull Request for `master-next` with **`master`** as
|
||||
the base branch. Instead of the default template, reuse and update
|
||||
the message from the previous final release. Include the following verbiage
|
||||
somewhere in the description:
|
||||
1. Ensure there is no old `master-next` branch hanging around. Then make a `master-next` branch that only changes the version number. As above, or using the [update-version] script.
|
||||
2. Create a Pull Request for `master-next` with **`master`** as the base branch. Instead of the default template, reuse and update the message from the previous final release. Include the following verbiage somewhere in the description:
|
||||
|
||||
```
|
||||
The base branch is `master`. This PR branch will be pushed directly to
|
||||
@@ -948,11 +654,8 @@ The base branch is `master`. This PR branch will be pushed directly to
|
||||
GitHub UI).
|
||||
```
|
||||
|
||||
7. Sign-offs for the three platforms (Linux, Mac, Windows) usually occur
|
||||
offline, but at least one approval will be needed on the PR.
|
||||
- If issues are discovered during testing, close the PR, delete
|
||||
`master-next`, and move development back to `release`, [issuing
|
||||
more RCs as necessary](#release-candidates-after-the-first)
|
||||
7. Sign-offs for the three platforms (Linux, Mac, Windows) usually occur offline, but at least one approval will be needed on the PR.
|
||||
- If issues are discovered during testing, close the PR, delete `master-next`, and move development back to `release`, [issuing more RCs as necessary](#release-candidates-after-the-first)
|
||||
8. Once everything is ready to go, push to `release` and `master`.
|
||||
|
||||
```
|
||||
@@ -991,31 +694,19 @@ git push upstream-push <version number>
|
||||
git push --delete upstream-push master-next
|
||||
```
|
||||
|
||||
11. [Create a new release on
|
||||
Github](https://github.com/XRPLF/rippled/releases). Be sure that
|
||||
"Set as the latest release" is checked.
|
||||
11. [Create a new release on Github](https://github.com/XRPLF/rippled/releases). Be sure that "Set as the latest release" is checked.
|
||||
12. Open a PR to update the [API-CHANGELOG](API-CHANGELOG.md) and `API-VERSION-[n].md` with the changes for this release (if any are missing).
|
||||
13. Finally, [reverse merge the release into `develop`](#follow-up-reverse-merge).
|
||||
|
||||
#### Special cases: point releases, hotfixes, etc.
|
||||
|
||||
On occasion, a bug or issue is discovered in a version that already
|
||||
had a final release. Most of the time, development will have started
|
||||
on the next version, and will usually have changes in `develop`
|
||||
and often in `release`.
|
||||
On occasion, a bug or issue is discovered in a version that already had a final release. Most of the time, development will have started on the next version, and will usually have changes in `develop` and often in `release`.
|
||||
|
||||
Because git history is kept as linear as possible, any fix and new
|
||||
version will need to be applied against `master`.
|
||||
Because git history is kept as linear as possible, any fix and new version will need to be applied against `master`.
|
||||
|
||||
The process for building a hotfix release is very similar to [the
|
||||
process for building release candidates after the
|
||||
first](#release-candidates-after-the-first) and [for building a final
|
||||
release](#final-releases), except the changes will be done against
|
||||
`master` instead of `release`.
|
||||
The process for building a hotfix release is very similar to [the process for building release candidates after the first](#release-candidates-after-the-first) and [for building a final release](#final-releases), except the changes will be done against `master` instead of `release`.
|
||||
|
||||
If there is only a single issue for the hotfix, the work can be done in
|
||||
any branch. When it's ready to merge, jump to step 3 using your branch
|
||||
instead of `master-next`.
|
||||
If there is only a single issue for the hotfix, the work can be done in any branch. When it's ready to merge, jump to step 3 using your branch instead of `master-next`.
|
||||
|
||||
1. Create a `master-next` branch from `master`.
|
||||
|
||||
@@ -1025,27 +716,17 @@ git push upstream-push
|
||||
git fetch upstreams
|
||||
```
|
||||
|
||||
2. Open any PRs for the pending hotfix using `master-next` as the base,
|
||||
so they can be merged directly in to it. Unlike `develop`, though,
|
||||
`master-next` can be thrown away and recreated if necessary.
|
||||
3. Once the hotfix is ready, create a version commit using the same
|
||||
steps as above, or use the
|
||||
[update-version] script.
|
||||
4. Create a Pull Request for `master-next` with **`master`** as
|
||||
the base branch. Instead of the default template, reuse and update
|
||||
the message from the previous final release. Include the following verbiage
|
||||
somewhere in the description:
|
||||
2. Open any PRs for the pending hotfix using `master-next` as the base, so they can be merged directly in to it. Unlike `develop`, though, `master-next` can be thrown away and recreated if necessary.
|
||||
3. Once the hotfix is ready, create a version commit using the same steps as above, or use the [update-version] script.
|
||||
4. Create a Pull Request for `master-next` with **`master`** as the base branch. Instead of the default template, reuse and update the message from the previous final release. Include the following verbiage somewhere in the description:
|
||||
|
||||
```
|
||||
The base branch is `master`. This PR branch will be pushed directly to
|
||||
`master` (not squashed or rebased, and not using the GitHub UI).
|
||||
```
|
||||
|
||||
7. Sign-offs for the three platforms (Linux, Mac, Windows) usually occur
|
||||
offline, but at least one approval will be needed on the PR.
|
||||
- If issues are discovered during testing, update `master-next` as
|
||||
needed, but ensure that the changes are properly squashed, and the
|
||||
version setting commit remains last
|
||||
7. Sign-offs for the three platforms (Linux, Mac, Windows) usually occur offline, but at least one approval will be needed on the PR.
|
||||
- If issues are discovered during testing, update `master-next` as needed, but ensure that the changes are properly squashed, and the version setting commit remains last
|
||||
8. Once everything is ready to go, push to `master` **only**.
|
||||
|
||||
```
|
||||
@@ -1082,17 +763,11 @@ git push upstream-push <version number>
|
||||
git push --delete upstream-push master-next
|
||||
```
|
||||
|
||||
10. [Create a new release on
|
||||
Github](https://github.com/XRPLF/rippled/releases). Be sure that
|
||||
"Set as the latest release" is checked.
|
||||
10. [Create a new release on Github](https://github.com/XRPLF/rippled/releases). Be sure that "Set as the latest release" is checked.
|
||||
|
||||
Once the hotfix is released, it needs to be reverse merged into
|
||||
`develop` as soon as possible. It may also need to be merged into
|
||||
`release` if a release candidate is under development.
|
||||
Once the hotfix is released, it needs to be reverse merged into `develop` as soon as possible. It may also need to be merged into `release` if a release candidate is under development.
|
||||
|
||||
1. Create a branch in your own repo, based on `upstream/develop`.
|
||||
The branch name is not important, but could include "mergeNNN".
|
||||
E.g. For release 2.2.3, use `merge223`.
|
||||
1. Create a branch in your own repo, based on `upstream/develop`. The branch name is not important, but could include "mergeNNN". E.g. For release 2.2.3, use `merge223`.
|
||||
|
||||
```
|
||||
git fetch upstreams
|
||||
@@ -1108,24 +783,15 @@ git checkout --no-track -b merge223 upstream/develop
|
||||
git merge upstream/master
|
||||
```
|
||||
|
||||
3. `BuildInfo.cpp` will have a conflict with the version number.
|
||||
Resolve it with the version from `develop` - the higher version.
|
||||
4. Push your branch to your repo, and open a normal PR against
|
||||
`develop`. The "High level overview" can simply indicate that this
|
||||
is a merge of the hotfix version. The "Context" should summarize
|
||||
the changes from the hotfix. Include the following text
|
||||
prominently:
|
||||
3. `BuildInfo.cpp` will have a conflict with the version number. Resolve it with the version from `develop` - the higher version.
|
||||
4. Push your branch to your repo, and open a normal PR against `develop`. The "High level overview" can simply indicate that this is a merge of the hotfix version. The "Context" should summarize the changes from the hotfix. Include the following text prominently:
|
||||
|
||||
```
|
||||
This PR must be merged manually using a --ff-only merge. Do not use the Github UI.
|
||||
```
|
||||
|
||||
5. Depending on the complexity of the hotfix, and/or merge conflicts,
|
||||
the PR may need a thorough review, or just a sign-off that the
|
||||
merge was done correctly.
|
||||
6. If `develop` is updated before this PR is merged, do not merge
|
||||
`develop` back into your branch. Instead rebase preserving merges,
|
||||
or do the merge again. (See also the `rerere` git config setting.)
|
||||
5. Depending on the complexity of the hotfix, and/or merge conflicts, the PR may need a thorough review, or just a sign-off that the merge was done correctly.
|
||||
6. If `develop` is updated before this PR is merged, do not merge `develop` back into your branch. Instead rebase preserving merges, or do the merge again. (See also the `rerere` git config setting.)
|
||||
|
||||
```
|
||||
git rebase --rebase-merges upstream/develop
|
||||
@@ -1145,22 +811,13 @@ git log --show-signature "upstream/develop..HEAD"
|
||||
git push upstream-push HEAD:develop
|
||||
```
|
||||
|
||||
Development on `develop` can proceed as normal. It is recommended to
|
||||
create a beta (or RC) immediately to ensure that everything worked as
|
||||
expected.
|
||||
Development on `develop` can proceed as normal. It is recommended to create a beta (or RC) immediately to ensure that everything worked as expected.
|
||||
|
||||
##### An even rarer scenario: A hotfix on an old release
|
||||
|
||||
Historically, once a final release is tagged and packages are released,
|
||||
versions older than the latest final release are no longer supported.
|
||||
However, there is a possibility that a very high severity bug may occur
|
||||
in a non-amendment blocked version that is still being run by
|
||||
a significant fraction of users, which would necessitate a hotfix / point
|
||||
release to that version as well as any later versions.
|
||||
Historically, once a final release is tagged and packages are released, versions older than the latest final release are no longer supported. However, there is a possibility that a very high severity bug may occur in a non-amendment blocked version that is still being run by a significant fraction of users, which would necessitate a hotfix / point release to that version as well as any later versions.
|
||||
|
||||
This scenario would follow the same basic procedure as above,
|
||||
except that _none_ of `develop`, `release`, or `master`
|
||||
would be touched during the release process.
|
||||
This scenario would follow the same basic procedure as above, except that _none_ of `develop`, `release`, or `master` would be touched during the release process.
|
||||
|
||||
In this example, consider if version 2.1.1 needed to be patched.
|
||||
|
||||
@@ -1180,20 +837,9 @@ git push upstream-push
|
||||
git fetch upstreams
|
||||
```
|
||||
|
||||
2. Work continues as above, except using `master-2.1.2`as
|
||||
the base branch for any merging, packaging, etc.
|
||||
3. After the release is tagged and packages are built, you could
|
||||
potentially delete both branches, e.g. `master-2.1.2` and
|
||||
`master212-next`. However, it may be useful to keep `master-2.1.2`
|
||||
around indefinitely for reference.
|
||||
4. Assuming that a hotfix is also released for the latest
|
||||
version in parallel with this one, or if the issue is
|
||||
already fixed in the latest version, do no do any
|
||||
reverse merges. However, if it is not, it probably makes
|
||||
sense to reverse merge `master-2.1.2` into `master`,
|
||||
release a hotfix for _that_ version, then reverse merge
|
||||
from `master` to `develop`. (Please don't do this unless absolutely
|
||||
necessary.)
|
||||
2. Work continues as above, except using `master-2.1.2`as the base branch for any merging, packaging, etc.
|
||||
3. After the release is tagged and packages are built, you could potentially delete both branches, e.g. `master-2.1.2` and `master212-next`. However, it may be useful to keep `master-2.1.2` around indefinitely for reference.
|
||||
4. Assuming that a hotfix is also released for the latest version in parallel with this one, or if the issue is already fixed in the latest version, do no do any reverse merges. However, if it is not, it probably makes sense to reverse merge `master-2.1.2` into `master`, release a hotfix for _that_ version, then reverse merge from `master` to `develop`. (Please don't do this unless absolutely necessary.)
|
||||
|
||||
[contrib]: https://docs.github.com/en/get-started/quickstart/contributing-to-projects
|
||||
[squash]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/about-pull-request-merges#squash-and-merge-your-commits
|
||||
|
||||
15
LICENSE.md
15
LICENSE.md
@@ -1,16 +1,7 @@
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2011, Arthur Britto, David Schwartz, Jed McCaleb, Vinnie Falco, Bob Way, Eric Lombrozo, Nikolaos D. Bougalis, Howard Hinnant.
|
||||
Copyright (c) 2012-present, the XRP Ledger developers.
|
||||
Copyright (c) 2011, Arthur Britto, David Schwartz, Jed McCaleb, Vinnie Falco, Bob Way, Eric Lombrozo, Nikolaos D. Bougalis, Howard Hinnant. Copyright (c) 2012-present, the XRP Ledger developers.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
@@ -56,8 +56,7 @@ Here are some good places to start learning the source code:
|
||||
| `./src` | Source code. |
|
||||
| `./crates` | Rust source code. |
|
||||
|
||||
Some of the directories under `src` are external repositories included using
|
||||
git-subtree. See those directories' README files for more details.
|
||||
Some of the directories under `src` are external repositories included using git-subtree. See those directories' README files for more details.
|
||||
|
||||
## Additional Documentation
|
||||
|
||||
|
||||
@@ -158,7 +158,6 @@ if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then
|
||||
check cargo-nextest cargo nextest --version
|
||||
check clippy-driver
|
||||
check rust-analyzer
|
||||
check rust-nightly rust-nightly run rustc --version
|
||||
check rustc
|
||||
check rustfmt
|
||||
fi
|
||||
|
||||
@@ -25,9 +25,7 @@ esac
|
||||
# Packaging runs in a vanilla distro image, so the tooling comes from the distro's
|
||||
# archive rather than from nixpkgs:
|
||||
#
|
||||
# - debhelper and dpkg-dev build the DEB, and lintian checks it
|
||||
# - binutils gives debian/rules the readelf its glibc-floor check runs; it
|
||||
# already arrives via dpkg-dev, but that tool is called directly
|
||||
# - debhelper and dpkg-dev build the DEB
|
||||
# - rpm-build builds the RPM, with systemd-rpm-macros and redhat-rpm-config
|
||||
# supplying the systemd and find-debuginfo macros the spec uses
|
||||
# - rpm-sign and gnupg2 sign the built RPM
|
||||
@@ -39,13 +37,11 @@ function install() {
|
||||
debian | ubuntu)
|
||||
apt-get update -y
|
||||
apt-get install -y --no-install-recommends \
|
||||
binutils \
|
||||
ca-certificates \
|
||||
debhelper \
|
||||
debhelper-compat \
|
||||
dpkg-dev \
|
||||
git \
|
||||
lintian \
|
||||
python3
|
||||
;;
|
||||
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Check that Rust unit tests stay out of the coverage report.
|
||||
|
||||
cargo-llvm-cov instruments the test code along with everything else, so a test
|
||||
module that is not excluded counts its own body as covered and inflates the
|
||||
reported number. Excluding it takes two attributes:
|
||||
|
||||
* every `#[cfg(test)]` module carries
|
||||
`#[cfg_attr(coverage_nightly, coverage(off))]`;
|
||||
* every crate root (lib.rs, main.rs) carries
|
||||
`#![cfg_attr(coverage_nightly, feature(coverage_attribute))]`, which the
|
||||
attribute above needs in order to compile.
|
||||
|
||||
Both are inert outside the coverage job: cargo-llvm-cov defines
|
||||
`coverage_nightly` only when it runs on a nightly toolchain.
|
||||
|
||||
The crate-root gate is checked even in a crate that has no tests yet, because
|
||||
that is what lets the first test module added later carry the attribute without
|
||||
a build failure. Missing it is a hard error, so it cannot go unnoticed; a
|
||||
missing `coverage(off)` fails open, which is why this check exists.
|
||||
|
||||
Matching is on exact attribute text, which works because `cargo fmt` runs over
|
||||
the whole workspace in the hook ahead of this one: rustfmt puts every attribute
|
||||
on its own line and normalizes what is inside it, turning `#[cfg( test )]`
|
||||
and `#[cfg(test,)]` alike into `#[cfg(test)]`. So there is nothing here that
|
||||
parses Rust. The price is that a cfg this file does not spell out literally --
|
||||
`all(test, ...)`, `any(test, ...)`, `not(test)` -- is reported rather than
|
||||
classified, on the grounds that guessing at coverage semantics is how a check
|
||||
like this ends up quietly wrong.
|
||||
|
||||
Usage: ./bin/pre-commit/check_rust_coverage_attrs.py <file1> <file2> ...
|
||||
|
||||
Exit status is non-zero if any violation is found.
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
CRATE_ROOTS = {"lib.rs", "main.rs"}
|
||||
|
||||
FEATURE_ATTR = "#![cfg_attr(coverage_nightly, feature(coverage_attribute))]"
|
||||
COVERAGE_OFF_ATTR = "#[cfg_attr(coverage_nightly, coverage(off))]"
|
||||
CFG_TEST_ATTR = "#[cfg(test)]"
|
||||
|
||||
# Any other cfg that mentions `test`. String literals are blanked before this
|
||||
# runs, so `feature = "test"` does not read as the `test` cfg.
|
||||
RE_CFG_MENTIONS_TEST = re.compile(r"^#\[cfg\(.*\btest\b.*\)\]$")
|
||||
RE_STRING = re.compile(r'"(?:[^"\\]|\\.)*"')
|
||||
RE_MOD = re.compile(r"^(?:pub(?:\([^)]*\))?\s+)?mod\s+([A-Za-z_]\w*)")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Finding:
|
||||
line: int
|
||||
label: str
|
||||
message: str
|
||||
|
||||
|
||||
def _check_module(attrs: list[str], line: int, name: str) -> list[Finding]:
|
||||
"""Findings for one module, given the attributes attached to it."""
|
||||
if COVERAGE_OFF_ATTR in attrs:
|
||||
return [] # excluded from coverage; which cfg gates it does not matter
|
||||
if CFG_TEST_ATTR in attrs:
|
||||
return [
|
||||
Finding(
|
||||
line,
|
||||
"missing-coverage-off",
|
||||
f"`mod {name}` is #[cfg(test)] but not excluded from coverage; "
|
||||
f"add {COVERAGE_OFF_ATTR}",
|
||||
)
|
||||
]
|
||||
unclassified = [
|
||||
attr for attr in attrs if RE_CFG_MENTIONS_TEST.match(RE_STRING.sub('""', attr))
|
||||
]
|
||||
if unclassified:
|
||||
return [
|
||||
Finding(
|
||||
line,
|
||||
"unclassified-cfg",
|
||||
f"`mod {name}` is gated on {unclassified[0]}, which this check "
|
||||
f"cannot tell apart from a module that ships in the library; "
|
||||
f"add {COVERAGE_OFF_ATTR} if it is test-only, or teach this "
|
||||
f"check the cfg if it is not",
|
||||
)
|
||||
]
|
||||
return []
|
||||
|
||||
|
||||
def _check_test_modules(lines: list[str]) -> list[Finding]:
|
||||
"""Findings for every test module that is not excluded from coverage."""
|
||||
findings: list[Finding] = []
|
||||
attrs: list[str] = []
|
||||
attrs_line = 0
|
||||
for number, raw in enumerate(lines, start=1):
|
||||
stripped = raw.strip()
|
||||
# Blank lines and comments are allowed between an attribute and its item.
|
||||
if not stripped or stripped.startswith("//"):
|
||||
continue
|
||||
if stripped.startswith("#["):
|
||||
if not attrs:
|
||||
attrs_line = number
|
||||
attrs.append(stripped)
|
||||
continue
|
||||
module = RE_MOD.match(stripped)
|
||||
if module is not None and attrs:
|
||||
findings += _check_module(attrs, attrs_line, module.group(1))
|
||||
attrs = []
|
||||
return findings
|
||||
|
||||
|
||||
def _check_crate_root(name: str, lines: list[str]) -> list[Finding]:
|
||||
"""A finding if a crate root is missing the coverage_attribute feature gate."""
|
||||
if name not in CRATE_ROOTS:
|
||||
return []
|
||||
if any(line.strip() == FEATURE_ATTR for line in lines):
|
||||
return []
|
||||
return [
|
||||
Finding(
|
||||
1,
|
||||
"missing-feature-gate",
|
||||
f"crate root is missing {FEATURE_ATTR}",
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def check_source(name: str, text: str) -> list[Finding]:
|
||||
"""Findings for one file's contents; `name` is its base name (lib.rs, ...)."""
|
||||
lines = text.splitlines()
|
||||
return _check_crate_root(name, lines) + _check_test_modules(lines)
|
||||
|
||||
|
||||
def check_file(path: Path) -> list[Finding]:
|
||||
return check_source(path.name, path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def main() -> int:
|
||||
total = 0
|
||||
for path in (Path(name) for name in sys.argv[1:]):
|
||||
for finding in check_file(path):
|
||||
total += 1
|
||||
print(f"{path}:{finding.line}: {finding.label}: {finding.message}")
|
||||
return 1 if total else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -2,11 +2,8 @@
|
||||
|
||||
To achieve reproducible dependencies, we use a [Conan lockfile](https://docs.conan.io/2/tutorial/versioning/lockfiles.html).
|
||||
|
||||
The `conan.lock` file in the repository contains a "snapshot" of the current
|
||||
dependencies. It is implicitly used when running `conan` commands, so you don't
|
||||
need to specify it.
|
||||
The `conan.lock` file in the repository contains a "snapshot" of the current dependencies. It is implicitly used when running `conan` commands, so you don't need to specify it.
|
||||
|
||||
You have to update this file every time you add a new dependency or change a
|
||||
revision or version of an existing dependency.
|
||||
You have to update this file every time you add a new dependency or change a revision or version of an existing dependency.
|
||||
|
||||
To update a lockfile, run from the repository root: `./conan/lockfile/regenerate.sh`
|
||||
|
||||
@@ -8,9 +8,6 @@ cxx = { version = "1.0.198", features = ["c++20"] }
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
|
||||
[workspace.lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = [ 'cfg(coverage)', 'cfg(coverage_nightly)' ] }
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
overflow-checks = true
|
||||
|
||||
@@ -8,6 +8,3 @@ crate-type = ["staticlib"]
|
||||
|
||||
[dependencies]
|
||||
cxx.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
|
||||
|
||||
#[cxx::bridge(namespace = "rs::hello_world")]
|
||||
mod ffi {
|
||||
extern "Rust" {
|
||||
@@ -10,14 +8,3 @@ mod ffi {
|
||||
pub fn hello_world() -> String {
|
||||
"hello_world".to_string()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg_attr(coverage_nightly, coverage(off))]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn hello_world_returns_hello_world() {
|
||||
assert_eq!(hello_world(), "hello_world")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,61 +2,21 @@
|
||||
|
||||
## The Problem Statement
|
||||
|
||||
The moment-to-moment health of the XRP Ledger network depends on the health and
|
||||
connectivity of a small number of computers (nodes). The most important nodes
|
||||
are validators, specifically ones listed on the unique node list
|
||||
([UNL](#Question-What-are-UNLs)). Ripple publishes a recommended UNL that most
|
||||
network nodes use to determine which peers in the network are trusted. Although
|
||||
most validators use the same list, they are not required to. The XRP Ledger
|
||||
network progresses to the next ledger when enough validators reach agreement
|
||||
(above the minimum quorum of 80%) about what transactions to include in the next
|
||||
ledger.
|
||||
The moment-to-moment health of the XRP Ledger network depends on the health and connectivity of a small number of computers (nodes). The most important nodes are validators, specifically ones listed on the unique node list ([UNL](#Question-What-are-UNLs)). Ripple publishes a recommended UNL that most network nodes use to determine which peers in the network are trusted. Although most validators use the same list, they are not required to. The XRP Ledger network progresses to the next ledger when enough validators reach agreement (above the minimum quorum of 80%) about what transactions to include in the next ledger.
|
||||
|
||||
As an example, if there are 10 validators on the UNL, at least 8 validators have
|
||||
to agree with the latest ledger for it to become validated. But what if enough
|
||||
of those validators are offline to drop the network below the 80% quorum? The
|
||||
XRP Ledger network favors safety/correctness over advancing the ledger. Which
|
||||
means if enough validators are offline, the network will not be able to validate
|
||||
ledgers.
|
||||
As an example, if there are 10 validators on the UNL, at least 8 validators have to agree with the latest ledger for it to become validated. But what if enough of those validators are offline to drop the network below the 80% quorum? The XRP Ledger network favors safety/correctness over advancing the ledger. Which means if enough validators are offline, the network will not be able to validate ledgers.
|
||||
|
||||
Unfortunately validators can go offline at any time for many different reasons.
|
||||
Power outages, network connectivity issues, and hardware failures are just a few
|
||||
scenarios where a validator would appear "offline". Given that most of these
|
||||
events are temporary, it would make sense to temporarily remove that validator
|
||||
from the UNL. But the UNL is updated infrequently and not every node uses the
|
||||
same UNL. So instead of removing the unreliable validator from the Ripple
|
||||
recommended UNL, we can create a second negative UNL which is stored directly on
|
||||
the ledger (so the entire network has the same view). This will help the network
|
||||
see which validators are **currently** unreliable, and adjust their quorum
|
||||
calculation accordingly.
|
||||
Unfortunately validators can go offline at any time for many different reasons. Power outages, network connectivity issues, and hardware failures are just a few scenarios where a validator would appear "offline". Given that most of these events are temporary, it would make sense to temporarily remove that validator from the UNL. But the UNL is updated infrequently and not every node uses the same UNL. So instead of removing the unreliable validator from the Ripple recommended UNL, we can create a second negative UNL which is stored directly on the ledger (so the entire network has the same view). This will help the network see which validators are **currently** unreliable, and adjust their quorum calculation accordingly.
|
||||
|
||||
_Improving the liveness of the network is the main motivation for the negative UNL._
|
||||
|
||||
### Targeted Faults
|
||||
|
||||
In order to determine which validators are unreliable, we need clearly define
|
||||
what kind of faults to measure and analyze. We want to deal with the faults we
|
||||
frequently observe in the production network. Hence we will only monitor for
|
||||
validators that do not reliably respond to network messages or send out
|
||||
validations disagreeing with the locally generated validations. We will not
|
||||
target other byzantine faults.
|
||||
In order to determine which validators are unreliable, we need clearly define what kind of faults to measure and analyze. We want to deal with the faults we frequently observe in the production network. Hence we will only monitor for validators that do not reliably respond to network messages or send out validations disagreeing with the locally generated validations. We will not target other byzantine faults.
|
||||
|
||||
To track whether or not a validator is responding to the network, we could
|
||||
monitor them with a “heartbeat” protocol. Instead of creating a new heartbeat
|
||||
protocol, we can leverage some existing protocol messages to mimic the
|
||||
heartbeat. We picked validation messages because validators should send one and
|
||||
only one validation message per ledger. In addition, we only count the
|
||||
validation messages that agree with the local node's validations.
|
||||
To track whether or not a validator is responding to the network, we could monitor them with a “heartbeat” protocol. Instead of creating a new heartbeat protocol, we can leverage some existing protocol messages to mimic the heartbeat. We picked validation messages because validators should send one and only one validation message per ledger. In addition, we only count the validation messages that agree with the local node's validations.
|
||||
|
||||
With the negative UNL, the network could keep making forward progress safely
|
||||
even if the number of remaining validators gets to 60%. Say we have a network
|
||||
with 10 validators on the UNL and everything is operating correctly. The quorum
|
||||
required for this network would be 8 (80% of 10). When validators fail, the
|
||||
quorum required would be as low as 6 (60% of 10), which is the absolute
|
||||
**_minimum quorum_**. We need the absolute minimum quorum to be strictly greater
|
||||
than 50% of the original UNL so that there cannot be two partitions of
|
||||
well-behaved nodes headed in different directions. We arbitrarily choose 60% as
|
||||
the minimum quorum to give a margin of safety.
|
||||
With the negative UNL, the network could keep making forward progress safely even if the number of remaining validators gets to 60%. Say we have a network with 10 validators on the UNL and everything is operating correctly. The quorum required for this network would be 8 (80% of 10). When validators fail, the quorum required would be as low as 6 (60% of 10), which is the absolute **_minimum quorum_**. We need the absolute minimum quorum to be strictly greater than 50% of the original UNL so that there cannot be two partitions of well-behaved nodes headed in different directions. We arbitrarily choose 60% as the minimum quorum to give a margin of safety.
|
||||
|
||||
Consider these events in the absence of negative UNL:
|
||||
|
||||
@@ -65,10 +25,7 @@ Consider these events in the absence of negative UNL:
|
||||
1. 5:00pm - validator3 fails, votes vs. quorum: 7 < 8, we don’t have quorum
|
||||
- **network cannot validate new ledgers with 3 failed validators**
|
||||
|
||||
We're below 80% agreement, so new ledgers cannot be validated. This is how the
|
||||
XRP Ledger operates today, but if the negative UNL was enabled, the events would
|
||||
happen as follows. (Please note that the events below are from a simplified
|
||||
version of our protocol.)
|
||||
We're below 80% agreement, so new ledgers cannot be validated. This is how the XRP Ledger operates today, but if the negative UNL was enabled, the events would happen as follows. (Please note that the events below are from a simplified version of our protocol.)
|
||||
|
||||
1. 1:00pm - validator1 fails, votes vs. quorum: 9 >= 8, we have quorum
|
||||
1. 1:40pm - network adds validator1 to negative UNL, quorum changes to ceil(9 \* 0.8), or 8
|
||||
@@ -88,8 +45,7 @@ This proposal will:
|
||||
1. add a new pseudo-transaction type
|
||||
1. add the negative UNL to the ledger data structure.
|
||||
|
||||
Any tools or systems that rely on the format of this data will have to be
|
||||
updated.
|
||||
Any tools or systems that rely on the format of this data will have to be updated.
|
||||
|
||||
### Amendment
|
||||
|
||||
@@ -105,122 +61,70 @@ This section discusses the following topics about the Negative UNL design:
|
||||
- [Negative UNL maintenance](#Negative-UNL-Maintenance)
|
||||
- [Quorum size calculation](#Quorum-Size-Calculation)
|
||||
- [Filter validation messages](#Filter-Validation-Messages)
|
||||
- [High level sequence diagram of code
|
||||
changes](#High-Level-Sequence-Diagram-of-Code-Changes)
|
||||
- [High level sequence diagram of code changes](#High-Level-Sequence-Diagram-of-Code-Changes)
|
||||
|
||||
### Negative UNL Protocol Overview
|
||||
|
||||
Every ledger stores a list of zero or more unreliable validators. Updates to the
|
||||
list must be approved by the validators using the consensus mechanism that
|
||||
validators use to agree on the set of transactions. The list is used only when
|
||||
checking if a ledger is fully validated. If a validator V is in the list, nodes
|
||||
with V in their UNL adjust the quorum and V’s validation message is not counted
|
||||
when verifying if a ledger is fully validated. V’s flow of messages and network
|
||||
interactions, however, will remain the same.
|
||||
Every ledger stores a list of zero or more unreliable validators. Updates to the list must be approved by the validators using the consensus mechanism that validators use to agree on the set of transactions. The list is used only when checking if a ledger is fully validated. If a validator V is in the list, nodes with V in their UNL adjust the quorum and V’s validation message is not counted when verifying if a ledger is fully validated. V’s flow of messages and network interactions, however, will remain the same.
|
||||
|
||||
We define the **\*effective UNL** = original UNL - negative UNL\*, and the
|
||||
**_effective quorum_** as the quorum of the _effective UNL_. And we set
|
||||
_effective quorum = Ceiling(80% _ effective UNL)\*.
|
||||
We define the **\*effective UNL** = original UNL - negative UNL\*, and the **_effective quorum_** as the quorum of the _effective UNL_. And we set _effective quorum = Ceiling(80% _ effective UNL)\*.
|
||||
|
||||
### Validator Reliability Measurement
|
||||
|
||||
A node only measures the reliability of validators on its own UNL, and only
|
||||
proposes based on local observations. There are many metrics that a node can
|
||||
measure about its validators, but we have chosen ledger validation messages.
|
||||
This is because every validator shall send one and only one signed validation
|
||||
message per ledger. This keeps the measurement simple and removes
|
||||
timing/clock-sync issues. A node will measure the percentage of agreeing
|
||||
validation messages (_PAV_) received from each validator on the node's UNL. Note
|
||||
that the node will only count the validation messages that agree with its own
|
||||
validations.
|
||||
A node only measures the reliability of validators on its own UNL, and only proposes based on local observations. There are many metrics that a node can measure about its validators, but we have chosen ledger validation messages. This is because every validator shall send one and only one signed validation message per ledger. This keeps the measurement simple and removes timing/clock-sync issues. A node will measure the percentage of agreeing validation messages (_PAV_) received from each validator on the node's UNL. Note that the node will only count the validation messages that agree with its own validations.
|
||||
|
||||
We define the **PAV** as the Percentage of Agreed Validation
|
||||
messages received for the last N ledgers, where N = 256 by default.
|
||||
We define the **PAV** as the Percentage of Agreed Validation messages received for the last N ledgers, where N = 256 by default.
|
||||
|
||||
When the PAV drops below the **_low-water mark_**, the validator is considered
|
||||
unreliable, and is a candidate to be disabled by being added to the negative
|
||||
UNL. A validator must have a PAV higher than the **_high-water mark_** to be
|
||||
re-enabled. The validator is re-enabled by removing it from the negative UNL. In
|
||||
the implementation, we plan to set the low-water mark as 50% and the high-water
|
||||
mark as 80%.
|
||||
When the PAV drops below the **_low-water mark_**, the validator is considered unreliable, and is a candidate to be disabled by being added to the negative UNL. A validator must have a PAV higher than the **_high-water mark_** to be re-enabled. The validator is re-enabled by removing it from the negative UNL. In the implementation, we plan to set the low-water mark as 50% and the high-water mark as 80%.
|
||||
|
||||
### Format Changes
|
||||
|
||||
The negative UNL component in a ledger contains three fields.
|
||||
|
||||
- **_NegativeUNL_**: The current negative UNL, a list of unreliable validators.
|
||||
- **_ToDisable_**: The validator to be added to the negative UNL on the next
|
||||
flag ledger.
|
||||
- **_ToReEnable_**: The validator to be removed from the negative UNL on the
|
||||
next flag ledger.
|
||||
- **_ToDisable_**: The validator to be added to the negative UNL on the next flag ledger.
|
||||
- **_ToReEnable_**: The validator to be removed from the negative UNL on the next flag ledger.
|
||||
|
||||
All three fields are optional. When the _ToReEnable_ field exists, the
|
||||
_NegativeUNL_ field cannot be empty.
|
||||
All three fields are optional. When the _ToReEnable_ field exists, the _NegativeUNL_ field cannot be empty.
|
||||
|
||||
A new pseudo-transaction, **_UNLModify_**, is added. It has three fields
|
||||
|
||||
- **_Disabling_**: A flag indicating whether the modification is to disable or
|
||||
to re-enable a validator.
|
||||
- **_Disabling_**: A flag indicating whether the modification is to disable or to re-enable a validator.
|
||||
- **_Seq_**: The ledger sequence number.
|
||||
- **_Validator_**: The validator to be disabled or re-enabled.
|
||||
|
||||
There would be at most one _disable_ `UNLModify` and one _re-enable_ `UNLModify`
|
||||
transaction per flag ledger. The full machinery is described further on.
|
||||
There would be at most one _disable_ `UNLModify` and one _re-enable_ `UNLModify` transaction per flag ledger. The full machinery is described further on.
|
||||
|
||||
### Negative UNL Maintenance
|
||||
|
||||
The negative UNL can only be modified on the flag ledgers. If a validator's
|
||||
reliability status changes, it takes two flag ledgers to modify the negative
|
||||
UNL. Let's see an example of the algorithm:
|
||||
The negative UNL can only be modified on the flag ledgers. If a validator's reliability status changes, it takes two flag ledgers to modify the negative UNL. Let's see an example of the algorithm:
|
||||
|
||||
- Ledger seq = 100: A validator V goes offline.
|
||||
- Ledger seq = 256: This is a flag ledger, and V's reliability measurement _PAV_
|
||||
is lower than the low-water mark. Other validators add `UNLModify`
|
||||
pseudo-transactions `{true, 256, V}` to the transaction set which goes through
|
||||
the consensus. Then the pseudo-transaction is applied to the negative UNL
|
||||
ledger component by setting `ToDisable = V`.
|
||||
- Ledger seq = 257 ~ 511: The negative UNL ledger component is copied from the
|
||||
parent ledger.
|
||||
- Ledger seq=512: This is a flag ledger, and the negative UNL is updated
|
||||
`NegativeUNL = NegativeUNL + ToDisable`.
|
||||
- Ledger seq = 256: This is a flag ledger, and V's reliability measurement _PAV_ is lower than the low-water mark. Other validators add `UNLModify` pseudo-transactions `{true, 256, V}` to the transaction set which goes through the consensus. Then the pseudo-transaction is applied to the negative UNL ledger component by setting `ToDisable = V`.
|
||||
- Ledger seq = 257 ~ 511: The negative UNL ledger component is copied from the parent ledger.
|
||||
- Ledger seq=512: This is a flag ledger, and the negative UNL is updated `NegativeUNL = NegativeUNL + ToDisable`.
|
||||
|
||||
The negative UNL may have up to `MaxNegativeListed = floor(original UNL * 25%)`
|
||||
validators. The 25% is because of 75% \* 80% = 60%, where 75% = 100% - 25%, 80%
|
||||
is the quorum of the effective UNL, and 60% is the absolute minimum quorum of
|
||||
the original UNL. Adding more than 25% validators to the negative UNL does not
|
||||
improve the liveness of the network, because adding more validators to the
|
||||
negative UNL cannot lower the effective quorum.
|
||||
The negative UNL may have up to `MaxNegativeListed = floor(original UNL * 25%)` validators. The 25% is because of 75% \* 80% = 60%, where 75% = 100% - 25%, 80% is the quorum of the effective UNL, and 60% is the absolute minimum quorum of the original UNL. Adding more than 25% validators to the negative UNL does not improve the liveness of the network, because adding more validators to the negative UNL cannot lower the effective quorum.
|
||||
|
||||
The following is the detailed algorithm:
|
||||
|
||||
- **If** the ledger seq = x is a flag ledger
|
||||
1. Compute `NegativeUNL = NegativeUNL + ToDisable - ToReEnable` if they
|
||||
exist in the parent ledger
|
||||
1. Compute `NegativeUNL = NegativeUNL + ToDisable - ToReEnable` if they exist in the parent ledger
|
||||
|
||||
1. Try to find a candidate to disable if `sizeof NegativeUNL < MaxNegativeListed`
|
||||
|
||||
1. Find a validator V that has a _PAV_ lower than the low-water
|
||||
mark, but is not in `NegativeUNL`.
|
||||
1. Find a validator V that has a _PAV_ lower than the low-water mark, but is not in `NegativeUNL`.
|
||||
|
||||
1. If two or more are found, their public keys are XORed with the hash
|
||||
of the parent ledger and the one with the lowest XOR result is chosen.
|
||||
1. If V is found, create a `UNLModify` pseudo-transaction
|
||||
`TxDisableValidator = {true, x, V}`
|
||||
1. If two or more are found, their public keys are XORed with the hash of the parent ledger and the one with the lowest XOR result is chosen.
|
||||
1. If V is found, create a `UNLModify` pseudo-transaction `TxDisableValidator = {true, x, V}`
|
||||
1. Try to find a candidate to re-enable if `sizeof NegativeUNL > 0`:
|
||||
1. Find a validator U that is in `NegativeUNL` and has a _PAV_ higher
|
||||
than the high-water mark.
|
||||
1. If U is not found, try to find one in `NegativeUNL` but not in the
|
||||
local _UNL_.
|
||||
1. If two or more are found, their public keys are XORed with the hash
|
||||
of the parent ledger and the one with the lowest XOR result is chosen.
|
||||
1. If U is found, create a `UNLModify` pseudo-transaction
|
||||
`TxReEnableValidator = {false, x, U}`
|
||||
1. Find a validator U that is in `NegativeUNL` and has a _PAV_ higher than the high-water mark.
|
||||
1. If U is not found, try to find one in `NegativeUNL` but not in the local _UNL_.
|
||||
1. If two or more are found, their public keys are XORed with the hash of the parent ledger and the one with the lowest XOR result is chosen.
|
||||
1. If U is found, create a `UNLModify` pseudo-transaction `TxReEnableValidator = {false, x, U}`
|
||||
|
||||
1. If any `UNLModify` pseudo-transactions are created, add them to the
|
||||
transaction set. The transaction set goes through the consensus algorithm.
|
||||
1. If have enough support, the `UNLModify` pseudo-transactions remain in the
|
||||
transaction set agreed by the validators. Then the pseudo-transactions are
|
||||
applied to the ledger:
|
||||
1. If any `UNLModify` pseudo-transactions are created, add them to the transaction set. The transaction set goes through the consensus algorithm.
|
||||
1. If have enough support, the `UNLModify` pseudo-transactions remain in the transaction set agreed by the validators. Then the pseudo-transactions are applied to the ledger:
|
||||
|
||||
1. If have `TxDisableValidator`, set `ToDisable=TxDisableValidator.V`.
|
||||
Else clear `ToDisable`.
|
||||
@@ -231,61 +135,33 @@ The following is the detailed algorithm:
|
||||
- **Else** (not a flag ledger)
|
||||
1. Copy the negative UNL ledger component from the parent ledger
|
||||
|
||||
The negative UNL is stored on each ledger because we don't know when a validator
|
||||
may reconnect to the network. If the negative UNL was stored only on every flag
|
||||
ledger, then a new validator would have to wait until it acquires the latest
|
||||
flag ledger to know the negative UNL. So any new ledgers created that are not
|
||||
flag ledgers copy the negative UNL from the parent ledger.
|
||||
The negative UNL is stored on each ledger because we don't know when a validator may reconnect to the network. If the negative UNL was stored only on every flag ledger, then a new validator would have to wait until it acquires the latest flag ledger to know the negative UNL. So any new ledgers created that are not flag ledgers copy the negative UNL from the parent ledger.
|
||||
|
||||
Note that when we have a validator to disable and a validator to re-enable at
|
||||
the same flag ledger, we create two separate `UNLModify` pseudo-transactions. We
|
||||
want either one or the other or both to make it into the ledger on their own
|
||||
merits.
|
||||
Note that when we have a validator to disable and a validator to re-enable at the same flag ledger, we create two separate `UNLModify` pseudo-transactions. We want either one or the other or both to make it into the ledger on their own merits.
|
||||
|
||||
Readers may have noticed that we defined several rules of creating the
|
||||
`UNLModify` pseudo-transactions but did not describe how to enforce the rules.
|
||||
The rules are actually enforced by the existing consensus algorithm. Unless
|
||||
enough validators propose the same pseudo-transaction it will not be included in
|
||||
the transaction set of the ledger.
|
||||
Readers may have noticed that we defined several rules of creating the `UNLModify` pseudo-transactions but did not describe how to enforce the rules. The rules are actually enforced by the existing consensus algorithm. Unless enough validators propose the same pseudo-transaction it will not be included in the transaction set of the ledger.
|
||||
|
||||
### Quorum Size Calculation
|
||||
|
||||
The effective quorum is 80% of the effective UNL. Note that because at most 25%
|
||||
of the original UNL can be on the negative UNL, the quorum should not be lower
|
||||
than the absolute minimum quorum (i.e. 60%) of the original UNL. However,
|
||||
considering that different nodes may have different UNLs, to be safe we compute
|
||||
`quorum = Ceiling(max(60% * original UNL, 80% * effective UNL))`.
|
||||
The effective quorum is 80% of the effective UNL. Note that because at most 25% of the original UNL can be on the negative UNL, the quorum should not be lower than the absolute minimum quorum (i.e. 60%) of the original UNL. However, considering that different nodes may have different UNLs, to be safe we compute `quorum = Ceiling(max(60% * original UNL, 80% * effective UNL))`.
|
||||
|
||||
### Filter Validation Messages
|
||||
|
||||
If a validator V is in the negative UNL, it still participates in consensus
|
||||
sessions in the same way, i.e. V still follows the protocol and publishes
|
||||
proposal and validation messages. The messages from V are still stored the same
|
||||
way by everyone, used to calculate the new PAV for V, and could be used in
|
||||
future consensus sessions if needed. However V's ledger validation message is
|
||||
not counted when checking if the ledger is fully validated.
|
||||
If a validator V is in the negative UNL, it still participates in consensus sessions in the same way, i.e. V still follows the protocol and publishes proposal and validation messages. The messages from V are still stored the same way by everyone, used to calculate the new PAV for V, and could be used in future consensus sessions if needed. However V's ledger validation message is not counted when checking if the ledger is fully validated.
|
||||
|
||||
### High Level Sequence Diagram of Code Changes
|
||||
|
||||
The diagram below is the sequence of one round of consensus. Classes and
|
||||
components with non-trivial changes are colored green.
|
||||
The diagram below is the sequence of one round of consensus. Classes and components with non-trivial changes are colored green.
|
||||
|
||||
- The `ValidatorList` class is modified to compute the quorum of the effective
|
||||
UNL.
|
||||
- The `ValidatorList` class is modified to compute the quorum of the effective UNL.
|
||||
|
||||
- The `Validations` class provides an interface for querying the validation
|
||||
messages from trusted validators.
|
||||
- The `Validations` class provides an interface for querying the validation messages from trusted validators.
|
||||
|
||||
- The `ConsensusAdaptor` component:
|
||||
- The `RCLConsensus::Adaptor` class is modified for creating `UNLModify`
|
||||
Pseudo-Transactions.
|
||||
- The `Change` class is modified for applying `UNLModify`
|
||||
Pseudo-Transactions.
|
||||
- The `Ledger` class is modified for creating and adjusting the negative UNL
|
||||
ledger component.
|
||||
- The `LedgerMaster` class is modified for filtering out validation messages
|
||||
from negative UNL validators when verifying if a ledger is fully
|
||||
validated.
|
||||
- The `RCLConsensus::Adaptor` class is modified for creating `UNLModify` Pseudo-Transactions.
|
||||
- The `Change` class is modified for applying `UNLModify` Pseudo-Transactions.
|
||||
- The `Ledger` class is modified for creating and adjusting the negative UNL ledger component.
|
||||
- The `LedgerMaster` class is modified for filtering out validation messages from negative UNL validators when verifying if a ledger is fully validated.
|
||||
|
||||

|
||||
@@ -294,63 +170,29 @@ Changes")
|
||||
|
||||
### Use a Mechanism Like Fee Voting to Process UNLModify Pseudo-Transactions
|
||||
|
||||
The previous version of the negative UNL specification used the same mechanism
|
||||
as the [fee voting](https://xrpl.org/fee-voting.html#voting-process.) for
|
||||
creating the negative UNL, and used the negative UNL as soon as the ledger was
|
||||
fully validated. However the timing of fully validation can differ among nodes,
|
||||
so different negative UNLs could be used, resulting in different effective UNLs
|
||||
and different quorums for the same ledger. As a result, the network's safety is
|
||||
impacted.
|
||||
The previous version of the negative UNL specification used the same mechanism as the [fee voting](https://xrpl.org/fee-voting.html#voting-process.) for creating the negative UNL, and used the negative UNL as soon as the ledger was fully validated. However the timing of fully validation can differ among nodes, so different negative UNLs could be used, resulting in different effective UNLs and different quorums for the same ledger. As a result, the network's safety is impacted.
|
||||
|
||||
This updated version does not impact safety though operates a bit more slowly.
|
||||
The negative UNL modifications in the _UNLModify_ pseudo-transaction approved by
|
||||
the consensus will take effect at the next flag ledger. The extra time of the
|
||||
256 ledgers should be enough for nodes to be in sync of the negative UNL
|
||||
modifications.
|
||||
This updated version does not impact safety though operates a bit more slowly. The negative UNL modifications in the _UNLModify_ pseudo-transaction approved by the consensus will take effect at the next flag ledger. The extra time of the 256 ledgers should be enough for nodes to be in sync of the negative UNL modifications.
|
||||
|
||||
### Use an Expiration Approach to Re-enable Validators
|
||||
|
||||
After a validator disabled by the negative UNL becomes reliable, other
|
||||
validators explicitly vote for re-enabling it. An alternative approach to
|
||||
re-enable a validator is the expiration approach, which was considered in the
|
||||
previous version of the specification. In the expiration approach, every entry
|
||||
in the negative UNL has a fixed expiration time. One flag ledger interval was
|
||||
chosen as the expiration interval. Once expired, the other validators must
|
||||
continue voting to keep the unreliable validator on the negative UNL. The
|
||||
advantage of this approach is its simplicity. But it has a requirement. The
|
||||
negative UNL protocol must be able to vote multiple unreliable validators to be
|
||||
disabled at the same flag ledger. In this version of the specification, however,
|
||||
only one unreliable validator can be disabled at a flag ledger. So the
|
||||
expiration approach cannot be simply applied.
|
||||
After a validator disabled by the negative UNL becomes reliable, other validators explicitly vote for re-enabling it. An alternative approach to re-enable a validator is the expiration approach, which was considered in the previous version of the specification. In the expiration approach, every entry in the negative UNL has a fixed expiration time. One flag ledger interval was chosen as the expiration interval. Once expired, the other validators must continue voting to keep the unreliable validator on the negative UNL. The advantage of this approach is its simplicity. But it has a requirement. The negative UNL protocol must be able to vote multiple unreliable validators to be disabled at the same flag ledger. In this version of the specification, however, only one unreliable validator can be disabled at a flag ledger. So the expiration approach cannot be simply applied.
|
||||
|
||||
### Validator Reliability Measurement and Flag Ledger Frequency
|
||||
|
||||
If the ledger time is about 4.5 seconds and the low-water mark is 50%, then in
|
||||
the worst case, it takes 48 minutes _((0.5 _ 256 + 256 + 256) _ 4.5 / 60 = 48)_
|
||||
to put an offline validator on the negative UNL. We considered lowering the flag
|
||||
ledger frequency so that the negative UNL can be more responsive. We also
|
||||
considered decoupling the reliability measurement and flag ledger frequency to
|
||||
be more flexible. In practice, however, their benefits are not clear.
|
||||
If the ledger time is about 4.5 seconds and the low-water mark is 50%, then in the worst case, it takes 48 minutes _((0.5 _ 256 + 256 + 256) _ 4.5 / 60 = 48)_ to put an offline validator on the negative UNL. We considered lowering the flag ledger frequency so that the negative UNL can be more responsive. We also considered decoupling the reliability measurement and flag ledger frequency to be more flexible. In practice, however, their benefits are not clear.
|
||||
|
||||
## New Attack Vectors
|
||||
|
||||
A group of malicious validators may try to frame a reliable validator and put it
|
||||
on the negative UNL. But they cannot succeed. Because:
|
||||
A group of malicious validators may try to frame a reliable validator and put it on the negative UNL. But they cannot succeed. Because:
|
||||
|
||||
1. A reliable validator sends a signed validation message every ledger. A
|
||||
sufficient peer-to-peer network will propagate the validation messages to other
|
||||
validators. The validators will decide if another validator is reliable or not
|
||||
only by its local observation of the validation messages received. So an honest
|
||||
validator’s vote on another validator’s reliability is accurate.
|
||||
1. A reliable validator sends a signed validation message every ledger. A sufficient peer-to-peer network will propagate the validation messages to other validators. The validators will decide if another validator is reliable or not only by its local observation of the validation messages received. So an honest validator’s vote on another validator’s reliability is accurate.
|
||||
|
||||
1. Given the votes are accurate, and one vote per validator, an honest validator
|
||||
will not create a UNLModify transaction of a reliable validator.
|
||||
1. Given the votes are accurate, and one vote per validator, an honest validator will not create a UNLModify transaction of a reliable validator.
|
||||
|
||||
1. A validator can be added to a negative UNL only through a UNLModify
|
||||
transaction.
|
||||
1. A validator can be added to a negative UNL only through a UNLModify transaction.
|
||||
|
||||
Assuming the group of malicious validators is less than the quorum, they cannot
|
||||
frame a reliable validator.
|
||||
Assuming the group of malicious validators is less than the quorum, they cannot frame a reliable validator.
|
||||
|
||||
## Summary
|
||||
|
||||
@@ -358,16 +200,13 @@ The bullet points below briefly summarize the current proposal:
|
||||
|
||||
- The motivation of the negative UNL is to improve the liveness of the network.
|
||||
|
||||
- The targeted faults are the ones frequently observed in the production
|
||||
network.
|
||||
- The targeted faults are the ones frequently observed in the production network.
|
||||
|
||||
- Validators propose negative UNL candidates based on their local measurements.
|
||||
|
||||
- The absolute minimum quorum is 60% of the original UNL.
|
||||
|
||||
- The format of the ledger is changed, and a new _UNLModify_ pseudo-transaction
|
||||
is added. Any tools or systems that rely on the format of these data will have
|
||||
to be updated.
|
||||
- The format of the ledger is changed, and a new _UNLModify_ pseudo-transaction is added. Any tools or systems that rely on the format of these data will have to be updated.
|
||||
|
||||
- The negative UNL can only be modified on the flag ledgers.
|
||||
|
||||
@@ -375,59 +214,39 @@ The bullet points below briefly summarize the current proposal:
|
||||
|
||||
- At most one validator can be removed from the negative UNL at a flag ledger.
|
||||
|
||||
- If a validator's reliability status changes, it takes two flag ledgers to
|
||||
modify the negative UNL.
|
||||
- If a validator's reliability status changes, it takes two flag ledgers to modify the negative UNL.
|
||||
|
||||
- The quorum is the larger of 80% of the effective UNL and 60% of the original
|
||||
UNL.
|
||||
- The quorum is the larger of 80% of the effective UNL and 60% of the original UNL.
|
||||
|
||||
- If a validator is on the negative UNL, its validation messages are ignored
|
||||
when the local node verifies if a ledger is fully validated.
|
||||
- If a validator is on the negative UNL, its validation messages are ignored when the local node verifies if a ledger is fully validated.
|
||||
|
||||
## FAQ
|
||||
|
||||
### Question: What are UNLs?
|
||||
|
||||
Quote from the [Technical FAQ](https://xrpl.org/technical-faq.html): "They are
|
||||
the lists of transaction validators a given participant believes will not
|
||||
conspire to defraud them."
|
||||
Quote from the [Technical FAQ](https://xrpl.org/technical-faq.html): "They are the lists of transaction validators a given participant believes will not conspire to defraud them."
|
||||
|
||||
### Question: How does the negative UNL proposal affect network liveness?
|
||||
|
||||
The network can make forward progress when more than a quorum of the trusted
|
||||
validators agree with the progress. The lower the quorum size is, the easier for
|
||||
the network to progress. If the quorum is too low, however, the network is not
|
||||
safe because nodes may have different results. So the quorum size used in the
|
||||
consensus protocol is a balance between the safety and the liveness of the
|
||||
network. The negative UNL reduces the size of the effective UNL, resulting in a
|
||||
lower quorum size while keeping the network safe.
|
||||
The network can make forward progress when more than a quorum of the trusted validators agree with the progress. The lower the quorum size is, the easier for the network to progress. If the quorum is too low, however, the network is not safe because nodes may have different results. So the quorum size used in the consensus protocol is a balance between the safety and the liveness of the network. The negative UNL reduces the size of the effective UNL, resulting in a lower quorum size while keeping the network safe.
|
||||
|
||||
<h3> Question: How does a validator get into the negative UNL? How is a
|
||||
validator removed from the negative UNL? </h3>
|
||||
|
||||
A validator’s reliability is measured by other validators. If a validator
|
||||
becomes unreliable, at a flag ledger, other validators propose _UNLModify_
|
||||
pseudo-transactions which vote the validator to add to the negative UNL during
|
||||
the consensus session. If agreed, the validator is added to the negative UNL at
|
||||
the next flag ledger. The mechanism of removing a validator from the negative
|
||||
UNL is the same.
|
||||
A validator’s reliability is measured by other validators. If a validator becomes unreliable, at a flag ledger, other validators propose _UNLModify_ pseudo-transactions which vote the validator to add to the negative UNL during the consensus session. If agreed, the validator is added to the negative UNL at the next flag ledger. The mechanism of removing a validator from the negative UNL is the same.
|
||||
|
||||
### Question: Given a negative UNL, what happens if the UNL changes?
|
||||
|
||||
Answer: Let’s consider the cases:
|
||||
|
||||
1. A validator is added to the UNL, and it is already in the negative UNL. This
|
||||
case could happen when not all the nodes have the same UNL. Note that the
|
||||
negative UNL on the ledger lists unreliable nodes that are not necessarily the
|
||||
validators for everyone.
|
||||
1. A validator is added to the UNL, and it is already in the negative UNL. This case could happen when not all the nodes have the same UNL. Note that the negative UNL on the ledger lists unreliable nodes that are not necessarily the validators for everyone.
|
||||
|
||||
In this case, the liveness is affected negatively. Because the minimum
|
||||
quorum could be larger but the usable validators are not increased.
|
||||
|
||||
1. A validator is removed from the UNL, and it is in the negative UNL.
|
||||
|
||||
In this case, the liveness is affected positively. Because the quorum could
|
||||
be smaller but the usable validators are not reduced.
|
||||
In this case, the liveness is affected positively. Because the quorum could be smaller but the usable validators are not reduced.
|
||||
|
||||
1. A validator is added to the UNL, and it is not in the negative UNL.
|
||||
1. A validator is removed from the UNL, and it is not in the negative UNL.
|
||||
@@ -438,59 +257,23 @@ Answer: Let’s consider the cases:
|
||||
|
||||
Answer: No, because the negative UNL approach is safer.
|
||||
|
||||
First let’s compare the two approaches intuitively, (1) the _negative UNL_
|
||||
approach, and (2) _lower quorum_: simply lowering the quorum from 80% to 60%
|
||||
without the negative UNL. The negative UNL approach uses consensus to come up
|
||||
with a list of unreliable validators, which are then removed from the effective
|
||||
UNL temporarily. With this approach, the list of unreliable validators is agreed
|
||||
to by a quorum of validators and will be used by every node in the network to
|
||||
adjust its UNL. The quorum is always 80% of the effective UNL. The lower quorum
|
||||
approach is a tradeoff between safety and liveness and against our principle of
|
||||
preferring safety over liveness. Note that different validators don't have to
|
||||
agree on which validation sources they are ignoring.
|
||||
First let’s compare the two approaches intuitively, (1) the _negative UNL_ approach, and (2) _lower quorum_: simply lowering the quorum from 80% to 60% without the negative UNL. The negative UNL approach uses consensus to come up with a list of unreliable validators, which are then removed from the effective UNL temporarily. With this approach, the list of unreliable validators is agreed to by a quorum of validators and will be used by every node in the network to adjust its UNL. The quorum is always 80% of the effective UNL. The lower quorum approach is a tradeoff between safety and liveness and against our principle of preferring safety over liveness. Note that different validators don't have to agree on which validation sources they are ignoring.
|
||||
|
||||
Next we compare the two approaches quantitatively with examples, and apply
|
||||
Theorem 8 of [Analysis of the XRP Ledger Consensus
|
||||
Protocol](https://arxiv.org/abs/1802.07242) paper:
|
||||
Next we compare the two approaches quantitatively with examples, and apply Theorem 8 of [Analysis of the XRP Ledger Consensus Protocol](https://arxiv.org/abs/1802.07242) paper:
|
||||
|
||||
_XRP LCP guarantees fork safety if **O<sub>i,j</sub> > n<sub>j</sub> / 2 +
|
||||
n<sub>i</sub> − q<sub>i</sub> + t<sub>i,j</sub>** for every pair of nodes
|
||||
P<sub>i</sub>, P<sub>j</sub>,_
|
||||
_XRP LCP guarantees fork safety if **O<sub>i,j</sub> > n<sub>j</sub> / 2 + n<sub>i</sub> − q<sub>i</sub> + t<sub>i,j</sub>** for every pair of nodes P<sub>i</sub>, P<sub>j</sub>,_
|
||||
|
||||
where _O<sub>i,j</sub>_ is the overlapping requirement, n<sub>j</sub> and
|
||||
n<sub>i</sub> are UNL sizes, q<sub>i</sub> is the quorum size of P<sub>i</sub>,
|
||||
_t<sub>i,j</sub> = min(t<sub>i</sub>, t<sub>j</sub>, O<sub>i,j</sub>)_, and
|
||||
t<sub>i</sub> and t<sub>j</sub> are the number of faults can be tolerated by
|
||||
P<sub>i</sub> and P<sub>j</sub>.
|
||||
where _O<sub>i,j</sub>_ is the overlapping requirement, n<sub>j</sub> and n<sub>i</sub> are UNL sizes, q<sub>i</sub> is the quorum size of P<sub>i</sub>, _t<sub>i,j</sub> = min(t<sub>i</sub>, t<sub>j</sub>, O<sub>i,j</sub>)_, and t<sub>i</sub> and t<sub>j</sub> are the number of faults can be tolerated by P<sub>i</sub> and P<sub>j</sub>.
|
||||
|
||||
We denote _UNL<sub>i</sub>_ as _P<sub>i</sub>'s UNL_, and _|UNL<sub>i</sub>|_ as
|
||||
the size of _P<sub>i</sub>'s UNL_.
|
||||
We denote _UNL<sub>i</sub>_ as _P<sub>i</sub>'s UNL_, and _|UNL<sub>i</sub>|_ as the size of _P<sub>i</sub>'s UNL_.
|
||||
|
||||
Assuming _|UNL<sub>i</sub>| = |UNL<sub>j</sub>|_, let's consider the following
|
||||
three cases:
|
||||
Assuming _|UNL<sub>i</sub>| = |UNL<sub>j</sub>|_, let's consider the following three cases:
|
||||
|
||||
1. With 80% quorum and 20% faults, _O<sub>i,j</sub> > 100% / 2 + 100% - 80% +
|
||||
20% = 90%_. I.e. fork safety requires > 90% UNL overlaps. This is one of the
|
||||
results in the analysis paper.
|
||||
1. With 80% quorum and 20% faults, _O<sub>i,j</sub> > 100% / 2 + 100% - 80% + 20% = 90%_. I.e. fork safety requires > 90% UNL overlaps. This is one of the results in the analysis paper.
|
||||
|
||||
1. If the quorum is 60%, the relationship between the overlapping requirement
|
||||
and the faults that can be tolerated is _O<sub>i,j</sub> > 90% +
|
||||
t<sub>i,j</sub>_. Under the same overlapping condition (i.e. 90%), to guarantee
|
||||
the fork safety, the network cannot tolerate any faults. So under the same
|
||||
overlapping condition, if the quorum is simply lowered, the network can tolerate
|
||||
fewer faults.
|
||||
1. If the quorum is 60%, the relationship between the overlapping requirement and the faults that can be tolerated is _O<sub>i,j</sub> > 90% + t<sub>i,j</sub>_. Under the same overlapping condition (i.e. 90%), to guarantee the fork safety, the network cannot tolerate any faults. So under the same overlapping condition, if the quorum is simply lowered, the network can tolerate fewer faults.
|
||||
|
||||
1. With the negative UNL approach, we want to argue that the inequation
|
||||
_O<sub>i,j</sub> > n<sub>j</sub> / 2 + n<sub>i</sub> − q<sub>i</sub> +
|
||||
t<sub>i,j</sub>_ is always true to guarantee fork safety, while the negative UNL
|
||||
protocol runs, i.e. the effective quorum is lowered without weakening the
|
||||
network's fault tolerance. To make the discussion easier, we rewrite the
|
||||
inequation as _O<sub>i,j</sub> > n<sub>j</sub> / 2 + (n<sub>i</sub> −
|
||||
q<sub>i</sub>) + min(t<sub>i</sub>, t<sub>j</sub>)_, where O<sub>i,j</sub> is
|
||||
dropped from the definition of t<sub>i,j</sub> because _O<sub>i,j</sub> >
|
||||
min(t<sub>i</sub>, t<sub>j</sub>)_ always holds under the parameters we will
|
||||
use. Assuming a validator V is added to the negative UNL, now let's consider the
|
||||
4 cases:
|
||||
1. With the negative UNL approach, we want to argue that the inequation _O<sub>i,j</sub> > n<sub>j</sub> / 2 + n<sub>i</sub> − q<sub>i</sub> + t<sub>i,j</sub>_ is always true to guarantee fork safety, while the negative UNL protocol runs, i.e. the effective quorum is lowered without weakening the network's fault tolerance. To make the discussion easier, we rewrite the inequation as _O<sub>i,j</sub> > n<sub>j</sub> / 2 + (n<sub>i</sub> − q<sub>i</sub>) + min(t<sub>i</sub>, t<sub>j</sub>)_, where O<sub>i,j</sub> is dropped from the definition of t<sub>i,j</sub> because _O<sub>i,j</sub> > min(t<sub>i</sub>, t<sub>j</sub>)_ always holds under the parameters we will use. Assuming a validator V is added to the negative UNL, now let's consider the 4 cases:
|
||||
|
||||
1. V is not on UNL<sub>i</sub> nor UNL<sub>j</sub>
|
||||
|
||||
@@ -526,64 +309,35 @@ three cases:
|
||||
<h3> Question: We have observed that occasionally a validator wanders off on its
|
||||
own chain. How is this case handled by the negative UNL algorithm? </h3>
|
||||
|
||||
Answer: The case that a validator wanders off on its own chain can be measured
|
||||
with the validations agreement. Because the validations by this validator must
|
||||
be different from other validators' validations of the same sequence numbers.
|
||||
When there are enough disagreed validations, other validators will vote this
|
||||
validator onto the negative UNL.
|
||||
Answer: The case that a validator wanders off on its own chain can be measured with the validations agreement. Because the validations by this validator must be different from other validators' validations of the same sequence numbers. When there are enough disagreed validations, other validators will vote this validator onto the negative UNL.
|
||||
|
||||
In general by measuring the agreement of validations, we also measured the
|
||||
"sanity". If two validators have too many disagreements, one of them could be
|
||||
insane. When enough validators think a validator is insane, that validator is
|
||||
put on the negative UNL.
|
||||
In general by measuring the agreement of validations, we also measured the "sanity". If two validators have too many disagreements, one of them could be insane. When enough validators think a validator is insane, that validator is put on the negative UNL.
|
||||
|
||||
<h3> Question: Why would there be at most one disable UNLModify and one
|
||||
re-enable UNLModify transaction per flag ledger? </h3>
|
||||
|
||||
Answer: It is a design choice so that the effective UNL does not change too
|
||||
quickly. A typical targeted scenario is several validators go offline slowly
|
||||
during a long weekend. The current design can handle this kind of cases well
|
||||
without changing the effective UNL too quickly.
|
||||
Answer: It is a design choice so that the effective UNL does not change too quickly. A typical targeted scenario is several validators go offline slowly during a long weekend. The current design can handle this kind of cases well without changing the effective UNL too quickly.
|
||||
|
||||
## Appendix
|
||||
|
||||
### Confidence Test
|
||||
|
||||
We will use two test networks, a single machine test network with multiple IP
|
||||
addresses and the QE test network with multiple machines. The single machine
|
||||
network will be used to test all the test cases and to debug. The QE network
|
||||
will be used after that. We want to see the test cases still pass with real
|
||||
network delay. A test case specifies:
|
||||
We will use two test networks, a single machine test network with multiple IP addresses and the QE test network with multiple machines. The single machine network will be used to test all the test cases and to debug. The QE network will be used after that. We want to see the test cases still pass with real network delay. A test case specifies:
|
||||
|
||||
1. a UNL with different number of validators for different test cases,
|
||||
1. a network with zero or more non-validator nodes,
|
||||
1. a sequence of validator reliability change events (by killing/restarting
|
||||
nodes, or by running modified xrpld that does not send all validation
|
||||
messages),
|
||||
1. a sequence of validator reliability change events (by killing/restarting nodes, or by running modified xrpld that does not send all validation messages),
|
||||
1. the correct outcomes.
|
||||
|
||||
For all the test cases, the correct outcomes are verified by examining logs. We
|
||||
will grep the log to see if the correct negative UNLs are generated, and whether
|
||||
or not the network is making progress when it should be. The ripdtop tool will
|
||||
be helpful for monitoring validators' states and ledger progress. Some of the
|
||||
timing parameters of xrpld will be changed to have faster ledger time. Most if
|
||||
not all test cases do not need client transactions.
|
||||
For all the test cases, the correct outcomes are verified by examining logs. We will grep the log to see if the correct negative UNLs are generated, and whether or not the network is making progress when it should be. The ripdtop tool will be helpful for monitoring validators' states and ledger progress. Some of the timing parameters of xrpld will be changed to have faster ledger time. Most if not all test cases do not need client transactions.
|
||||
|
||||
For example, the test cases for the prototype:
|
||||
|
||||
1. A 10-validator UNL.
|
||||
1. The network does not have other nodes.
|
||||
1. The validators will be started from the genesis. Once they start to produce
|
||||
ledgers, we kill five validators, one every flag ledger interval. Then we
|
||||
will restart them one by one.
|
||||
1. A sequence of events (or the lack of events) such as a killed validator is
|
||||
added to the negative UNL.
|
||||
1. The validators will be started from the genesis. Once they start to produce ledgers, we kill five validators, one every flag ledger interval. Then we will restart them one by one.
|
||||
1. A sequence of events (or the lack of events) such as a killed validator is added to the negative UNL.
|
||||
|
||||
#### Roads Not Taken: Test with Extended CSF
|
||||
|
||||
We considered testing with the current unit test framework, specifically the
|
||||
[Consensus Simulation
|
||||
Framework](https://github.com/XRPLF/rippled/blob/develop/src/test/csf/README.md)
|
||||
(CSF). However, the CSF currently can only test the generic consensus algorithm
|
||||
as in the paper: [Analysis of the XRP Ledger Consensus
|
||||
Protocol](https://arxiv.org/abs/1802.07242).
|
||||
We considered testing with the current unit test framework, specifically the [Consensus Simulation Framework](https://github.com/XRPLF/rippled/blob/develop/src/test/csf/README.md) (CSF). However, the CSF currently can only test the generic consensus algorithm as in the paper: [Analysis of the XRP Ledger Consensus Protocol](https://arxiv.org/abs/1802.07242).
|
||||
|
||||
@@ -1,63 +1,20 @@
|
||||
# Ledger Replay
|
||||
|
||||
`LedgerReplayer` is a new `Stoppable` for replaying ledgers.
|
||||
Patterned after two other `Stoppable`s under `JobQueue`---`InboundLedgers`
|
||||
and `InboundTransactions`---it acts like a factory for creating
|
||||
state-machine workers, and a network message demultiplexer for those workers.
|
||||
Think of these workers like asynchronous functions.
|
||||
Like functions, they each take a set of parameters.
|
||||
The `Stoppable` memoizes these functions. It maintains a table for each
|
||||
worker type, mapping sets of arguments to the worker currently working
|
||||
on that argument set.
|
||||
Whenever the `Stoppable` is asked to construct a worker, it first searches its
|
||||
table to see if there is an existing worker with the same or overlapping
|
||||
argument set.
|
||||
If one exists, then it is used. If not, then a new one is created,
|
||||
initialized, and added to the table.
|
||||
`LedgerReplayer` is a new `Stoppable` for replaying ledgers. Patterned after two other `Stoppable`s under `JobQueue`---`InboundLedgers` and `InboundTransactions`---it acts like a factory for creating state-machine workers, and a network message demultiplexer for those workers. Think of these workers like asynchronous functions. Like functions, they each take a set of parameters. The `Stoppable` memoizes these functions. It maintains a table for each worker type, mapping sets of arguments to the worker currently working on that argument set. Whenever the `Stoppable` is asked to construct a worker, it first searches its table to see if there is an existing worker with the same or overlapping argument set. If one exists, then it is used. If not, then a new one is created, initialized, and added to the table.
|
||||
|
||||
For `LedgerReplayer`, there are three worker types: `LedgerReplayTask`,
|
||||
`SkipListAcquire`, and `LedgerDeltaAcquire`.
|
||||
Each is derived from `TimeoutCounter` to give it a timeout.
|
||||
For `LedgerReplayTask`, the parameter set
|
||||
is {reason, finish ledger ID, number of ledgers}. For `SkipListAcquire` and
|
||||
`LedgerDeltaAcquire`, there is just one parameter: a ledger ID.
|
||||
For `LedgerReplayer`, there are three worker types: `LedgerReplayTask`, `SkipListAcquire`, and `LedgerDeltaAcquire`. Each is derived from `TimeoutCounter` to give it a timeout. For `LedgerReplayTask`, the parameter set is {reason, finish ledger ID, number of ledgers}. For `SkipListAcquire` and `LedgerDeltaAcquire`, there is just one parameter: a ledger ID.
|
||||
|
||||
Each `Stoppable` has an entry point. For `LedgerReplayer`, it is `replay`.
|
||||
`replay` creates two workers: a `LedgerReplayTask` and a `SkipListAcquire`.
|
||||
`LedgerDeltaAcquire`s are created in the callback for when the skip list
|
||||
returns.
|
||||
Each `Stoppable` has an entry point. For `LedgerReplayer`, it is `replay`. `replay` creates two workers: a `LedgerReplayTask` and a `SkipListAcquire`. `LedgerDeltaAcquire`s are created in the callback for when the skip list returns.
|
||||
|
||||
For `SkipListAcquire` and `LedgerDeltaAcquire`, initialization fires off the
|
||||
underlying asynchronous network request and starts the timeout. The argument
|
||||
set identifying the worker is included in the network request, and copied to
|
||||
the network response. `SkipListAcquire` sends a request for a proof path for
|
||||
the skip list of the desired ledger. `LedgerDeltaAcquire` sends a request for
|
||||
the transaction set of the desired ledger.
|
||||
For `SkipListAcquire` and `LedgerDeltaAcquire`, initialization fires off the underlying asynchronous network request and starts the timeout. The argument set identifying the worker is included in the network request, and copied to the network response. `SkipListAcquire` sends a request for a proof path for the skip list of the desired ledger. `LedgerDeltaAcquire` sends a request for the transaction set of the desired ledger.
|
||||
|
||||
`LedgerReplayer` is also a network message demultiplexer.
|
||||
When a response arrives for a request that was sent by a `SkipListAcquire` or
|
||||
`LedgerDeltaAcquire` worker, the `Peer` object knows to send it to the
|
||||
`LedgerReplayer`, which looks up the worker waiting for that response based on
|
||||
the identifying argument set included in the response.
|
||||
`LedgerReplayer` is also a network message demultiplexer. When a response arrives for a request that was sent by a `SkipListAcquire` or `LedgerDeltaAcquire` worker, the `Peer` object knows to send it to the `LedgerReplayer`, which looks up the worker waiting for that response based on the identifying argument set included in the response.
|
||||
|
||||
`LedgerReplayTask` may ask `InboundLedgers` to send requests to acquire
|
||||
the start ledger, but there is no way to attach a callback or be notified when
|
||||
the `InboundLedger` worker completes. All the responses for its messages will
|
||||
be directed to `InboundLedgers`, not `LedgerReplayer`. Instead,
|
||||
`LedgerReplayTask` checks whether the start ledger has arrived every time its
|
||||
timeout expires.
|
||||
`LedgerReplayTask` may ask `InboundLedgers` to send requests to acquire the start ledger, but there is no way to attach a callback or be notified when the `InboundLedger` worker completes. All the responses for its messages will be directed to `InboundLedgers`, not `LedgerReplayer`. Instead, `LedgerReplayTask` checks whether the start ledger has arrived every time its timeout expires.
|
||||
|
||||
Like a promise, each worker keeps track of whether it is pending (`!isDone()`)
|
||||
or whether it has resolved successfully (`complete_ == true`) or unsuccessfully
|
||||
(`failed_ == true`). It will never exist in both resolved states at once, nor
|
||||
will it return to a pending state after reaching a resolved state.
|
||||
Like a promise, each worker keeps track of whether it is pending (`!isDone()`) or whether it has resolved successfully (`complete_ == true`) or unsuccessfully (`failed_ == true`). It will never exist in both resolved states at once, nor will it return to a pending state after reaching a resolved state.
|
||||
|
||||
Like promises, some workers can accept continuations to be called when they
|
||||
reach a resolved state, or immediately if they are already resolved.
|
||||
`SkipListAcquire` and `LedgerDeltaAcquire` both accept continuations of a type
|
||||
specific to their payload, both via a method named `addDataCallback()`. Continuations
|
||||
cannot be removed explicitly, but they are held by `std::weak_ptr` so they can
|
||||
be removed implicitly.
|
||||
Like promises, some workers can accept continuations to be called when they reach a resolved state, or immediately if they are already resolved. `SkipListAcquire` and `LedgerDeltaAcquire` both accept continuations of a type specific to their payload, both via a method named `addDataCallback()`. Continuations cannot be removed explicitly, but they are held by `std::weak_ptr` so they can be removed implicitly.
|
||||
|
||||
`LedgerReplayTask` is simultaneously:
|
||||
|
||||
@@ -73,13 +30,7 @@ Each of these roles corresponds to different entry points:
|
||||
1. the callback added to `LedgerDeltaAcquire`, which calls `deltaReady(...)` or `cancel()`
|
||||
1. `onTimer()`
|
||||
|
||||
Each of these entry points does something unique to that entry point. They
|
||||
either (a) transition `LedgerReplayTask` to a terminal failed resolved state
|
||||
(`cancel()` and `onTimer()`) or (b) try to make progress toward the successful
|
||||
resolved state. `init()` and `updateSkipList(...)` call `trigger()` while
|
||||
`deltaReady(...)` calls `tryAdvance()`. There's a similarity between this
|
||||
pattern and the way coroutines are implemented, where every yield saves the spot
|
||||
in the code where it left off and every resume jumps back to that spot.
|
||||
Each of these entry points does something unique to that entry point. They either (a) transition `LedgerReplayTask` to a terminal failed resolved state (`cancel()` and `onTimer()`) or (b) try to make progress toward the successful resolved state. `init()` and `updateSkipList(...)` call `trigger()` while `deltaReady(...)` calls `tryAdvance()`. There's a similarity between this pattern and the way coroutines are implemented, where every yield saves the spot in the code where it left off and every resume jumps back to that spot.
|
||||
|
||||
### Sequence Diagram
|
||||
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
# Coding Standards
|
||||
|
||||
Coding standards used here gradually evolve and propagate through
|
||||
code reviews. Some aspects are enforced more strictly than others.
|
||||
Coding standards used here gradually evolve and propagate through code reviews. Some aspects are enforced more strictly than others.
|
||||
|
||||
## Rules
|
||||
|
||||
These rules only apply to our own code. We can't enforce any sort of
|
||||
style on the external repositories and libraries we include. The best
|
||||
guideline is to maintain the standards that are used in those libraries.
|
||||
These rules only apply to our own code. We can't enforce any sort of style on the external repositories and libraries we include. The best guideline is to maintain the standards that are used in those libraries.
|
||||
|
||||
- Tab inserts 4 spaces. No tab characters.
|
||||
- Braces are indented in the [Allman style][1].
|
||||
@@ -16,67 +13,37 @@ guideline is to maintain the standards that are used in those libraries.
|
||||
|
||||
## Guidelines
|
||||
|
||||
If you want to do something contrary to these guidelines, understand
|
||||
why you're doing it. Think, use common sense, and consider that these
|
||||
changes will probably need to be maintained long after you've
|
||||
moved on to other projects.
|
||||
If you want to do something contrary to these guidelines, understand why you're doing it. Think, use common sense, and consider that these changes will probably need to be maintained long after you've moved on to other projects.
|
||||
|
||||
- Use white space and blank lines to guide the eye and keep your intent clear.
|
||||
- Put private data members at the top of a class, and the 6 public special
|
||||
members immediately after, in the following order:
|
||||
- Put private data members at the top of a class, and the 6 public special members immediately after, in the following order:
|
||||
- Destructor
|
||||
- Default constructor
|
||||
- Copy constructor
|
||||
- Copy assignment
|
||||
- Move constructor
|
||||
- Move assignment
|
||||
- Don't over-inline by defining large functions within the class
|
||||
declaration, not even for template classes.
|
||||
- Don't over-inline by defining large functions within the class declaration, not even for template classes.
|
||||
|
||||
## Formatting
|
||||
|
||||
The goal of source code formatting should always be to make things as easy to
|
||||
read as possible. White space is used to guide the eye so that details are not
|
||||
overlooked. Blank lines are used to separate code into "paragraphs."
|
||||
The goal of source code formatting should always be to make things as easy to read as possible. White space is used to guide the eye so that details are not overlooked. Blank lines are used to separate code into "paragraphs."
|
||||
|
||||
- Always place a space before and after all binary operators,
|
||||
especially assignments (`operator=`).
|
||||
- Always place a space before and after all binary operators, especially assignments (`operator=`).
|
||||
- The `!` operator should be preceded by a space, but not followed by one.
|
||||
- The `~` operator should be preceded by a space, but not followed by one.
|
||||
- The `++` and `--` operators should have no spaces between the operator and
|
||||
the operand.
|
||||
- The `++` and `--` operators should have no spaces between the operator and the operand.
|
||||
- A space never appears before a comma, and always appears after a comma.
|
||||
- Don't put spaces after a parenthesis. A typical member function call might
|
||||
look like this: `foobar (1, 2, 3);`
|
||||
- Don't put spaces after a parenthesis. A typical member function call might look like this: `foobar (1, 2, 3);`
|
||||
- In general, leave a blank line before an `if` statement.
|
||||
- In general, leave a blank line after a closing brace `}`.
|
||||
- Do not place code on the same line as any opening or
|
||||
closing brace.
|
||||
- Do not write `if` statements all-on-one-line. The exception to this is when
|
||||
you've got a sequence of similar `if` statements, and are aligning them all
|
||||
vertically to highlight their similarities.
|
||||
- In an `if-else` statement, if you surround one half of the statement with
|
||||
braces, you also need to put braces around the other half, to match.
|
||||
- When writing a pointer type, use this spacing: `SomeObject* myObject`.
|
||||
Technically, a more correct spacing would be `SomeObject *myObject`, but
|
||||
it makes more sense for the asterisk to be grouped with the type name,
|
||||
since being a pointer is part of the type, not the variable name. The only
|
||||
time that this can lead to any problems is when you're declaring multiple
|
||||
pointers of the same type in the same statement - which leads on to the next
|
||||
rule:
|
||||
- When declaring multiple pointers, never do so in a single statement, e.g.
|
||||
`SomeObject* p1, *p2;` - instead, always split them out onto separate lines
|
||||
and write the type name again, to make it quite clear what's going on, and
|
||||
avoid the danger of missing out any vital asterisks.
|
||||
- The previous point also applies to references, so always put the `&` next to
|
||||
the type rather than the variable, e.g. `void foo (Thing const& thing)`. And
|
||||
don't put a space on both sides of the `*` or `&` - always put a space after
|
||||
it, but never before it.
|
||||
- The word `const` should be placed to the right of the thing that it modifies,
|
||||
for consistency. For example `int const` refers to an int which is const.
|
||||
`int const*` is a pointer to an int which is const. `int *const` is a const
|
||||
pointer to an int.
|
||||
- Always place a space in between the template angle brackets and the type
|
||||
name. Template code is already hard enough to read!
|
||||
- Do not place code on the same line as any opening or closing brace.
|
||||
- Do not write `if` statements all-on-one-line. The exception to this is when you've got a sequence of similar `if` statements, and are aligning them all vertically to highlight their similarities.
|
||||
- In an `if-else` statement, if you surround one half of the statement with braces, you also need to put braces around the other half, to match.
|
||||
- When writing a pointer type, use this spacing: `SomeObject* myObject`. Technically, a more correct spacing would be `SomeObject *myObject`, but it makes more sense for the asterisk to be grouped with the type name, since being a pointer is part of the type, not the variable name. The only time that this can lead to any problems is when you're declaring multiple pointers of the same type in the same statement - which leads on to the next rule:
|
||||
- When declaring multiple pointers, never do so in a single statement, e.g. `SomeObject* p1, *p2;` - instead, always split them out onto separate lines and write the type name again, to make it quite clear what's going on, and avoid the danger of missing out any vital asterisks.
|
||||
- The previous point also applies to references, so always put the `&` next to the type rather than the variable, e.g. `void foo (Thing const& thing)`. And don't put a space on both sides of the `*` or `&` - always put a space after it, but never before it.
|
||||
- The word `const` should be placed to the right of the thing that it modifies, for consistency. For example `int const` refers to an int which is const. `int const*` is a pointer to an int which is const. `int *const` is a const pointer to an int.
|
||||
- Always place a space in between the template angle brackets and the type name. Template code is already hard enough to read!
|
||||
|
||||
[1]: http://en.wikipedia.org/wiki/Indent_style#Allman_style
|
||||
|
||||
@@ -1,33 +1,18 @@
|
||||
## Heap profiling of xrpld with jemalloc
|
||||
|
||||
The jemalloc library provides a good API for doing heap analysis,
|
||||
including a mechanism to dump a description of the heap from within the
|
||||
running application via a function call. Details on how to perform this
|
||||
activity in general, as well as how to acquire the software, are available on
|
||||
the jemalloc site:
|
||||
[https://github.com/jemalloc/jemalloc/wiki/Use-Case:-Heap-Profiling](https://github.com/jemalloc/jemalloc/wiki/Use-Case:-Heap-Profiling)
|
||||
The jemalloc library provides a good API for doing heap analysis, including a mechanism to dump a description of the heap from within the running application via a function call. Details on how to perform this activity in general, as well as how to acquire the software, are available on the jemalloc site: [https://github.com/jemalloc/jemalloc/wiki/Use-Case:-Heap-Profiling](https://github.com/jemalloc/jemalloc/wiki/Use-Case:-Heap-Profiling)
|
||||
|
||||
jemalloc is acquired separately from xrpld, and is not affiliated
|
||||
with Ripple Labs. If you compile and install jemalloc from the
|
||||
source release with default options, it will install the library and header
|
||||
under `/usr/local/lib` and `/usr/local/include`, respectively. Heap
|
||||
profiling has been tested with xrpld on a Linux platform. It should
|
||||
work on platforms on which both xrpld and jemalloc are available.
|
||||
jemalloc is acquired separately from xrpld, and is not affiliated with Ripple Labs. If you compile and install jemalloc from the source release with default options, it will install the library and header under `/usr/local/lib` and `/usr/local/include`, respectively. Heap profiling has been tested with xrpld on a Linux platform. It should work on platforms on which both xrpld and jemalloc are available.
|
||||
|
||||
To link xrpld with jemalloc, the argument
|
||||
`profile-jemalloc=<jemalloc_dir>` is provided after the optional target.
|
||||
The `<jemalloc_dir>` argument should be the same as that of the
|
||||
`--prefix` parameter passed to the jemalloc configure script when building.
|
||||
To link xrpld with jemalloc, the argument `profile-jemalloc=<jemalloc_dir>` is provided after the optional target. The `<jemalloc_dir>` argument should be the same as that of the `--prefix` parameter passed to the jemalloc configure script when building.
|
||||
|
||||
## Examples:
|
||||
|
||||
Build xrpld with jemalloc library under /usr/local/lib and
|
||||
header under /usr/local/include:
|
||||
Build xrpld with jemalloc library under /usr/local/lib and header under /usr/local/include:
|
||||
|
||||
$ scons profile-jemalloc=/usr/local
|
||||
|
||||
Build xrpld using clang with the jemalloc library under /opt/local/lib
|
||||
and header under /opt/local/include:
|
||||
Build xrpld using clang with the jemalloc library under /opt/local/lib and header under /opt/local/include:
|
||||
|
||||
$ scons clang profile-jemalloc=/opt/local
|
||||
|
||||
@@ -35,10 +20,7 @@ and header under /opt/local/include:
|
||||
|
||||
## Using the jemalloc library from within the code
|
||||
|
||||
The `profile-jemalloc` parameter enables a macro definition called
|
||||
`PROFILE_JEMALLOC`. Include the jemalloc header file as
|
||||
well as the api call(s) that you wish to make within preprocessor
|
||||
conditional groups, such as:
|
||||
The `profile-jemalloc` parameter enables a macro definition called `PROFILE_JEMALLOC`. Include the jemalloc header file as well as the api call(s) that you wish to make within preprocessor conditional groups, such as:
|
||||
|
||||
In global scope:
|
||||
|
||||
@@ -52,11 +34,6 @@ And later, within a function scope:
|
||||
mallctl("prof.dump", NULL, NULL, NULL, 0);
|
||||
#endif
|
||||
|
||||
Fuller descriptions of how to acquire and use jemalloc's api to do memory
|
||||
analysis are available at the [jemalloc
|
||||
site.](http://www.canonware.com/jemalloc/)
|
||||
Fuller descriptions of how to acquire and use jemalloc's api to do memory analysis are available at the [jemalloc site.](http://www.canonware.com/jemalloc/)
|
||||
|
||||
Linking against the jemalloc library will override
|
||||
the system's default `malloc()` and related functions with jemalloc's
|
||||
implementation. This is the case even if the code is not instrumented
|
||||
to use jemalloc's specific API.
|
||||
Linking against the jemalloc library will override the system's default `malloc()` and related functions with jemalloc's implementation. This is the case even if the code is not instrumented to use jemalloc's specific API.
|
||||
|
||||
74
docs/build/advanced_conan.md
vendored
74
docs/build/advanced_conan.md
vendored
@@ -4,34 +4,25 @@ This document provides advanced instructions for setting up and configuring Cona
|
||||
|
||||
## 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:
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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:
|
||||
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.xrplf.org/repository/conan/
|
||||
@@ -61,16 +52,11 @@ git checkout master
|
||||
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.
|
||||
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.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
> [!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
|
||||
> 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.
|
||||
|
||||
@@ -88,8 +74,7 @@ Possible values are ['5.0', '5.1', '6.0', '6.1', '7.0', '7.3', '8.0', '8.1',
|
||||
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:
|
||||
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:
|
||||
@@ -99,13 +84,9 @@ compiler:
|
||||
|
||||
### 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.
|
||||
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:
|
||||
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
|
||||
@@ -113,9 +94,7 @@ 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:
|
||||
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]
|
||||
@@ -124,15 +103,11 @@ 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.
|
||||
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:
|
||||
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
|
||||
@@ -140,10 +115,7 @@ sed -i.bak -e 's|^compiler\.cppstd=.*$|compiler.cppstd=23|' $(conan config home)
|
||||
|
||||
### 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:
|
||||
**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
|
||||
@@ -151,12 +123,9 @@ sed -i.bak -e 's|^compiler\.libcxx=.*$|compiler.libcxx=libstdc++11|' $(conan con
|
||||
|
||||
### 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 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:
|
||||
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
|
||||
@@ -175,10 +144,8 @@ 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:
|
||||
- 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
|
||||
@@ -186,8 +153,7 @@ If you want to experiment with a new package, follow these steps:
|
||||
|
||||
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 ...)`).
|
||||
- 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
|
||||
|
||||
103
docs/build/conan.md
vendored
103
docs/build/conan.md
vendored
@@ -1,115 +1,44 @@
|
||||
## A crash course in CMake and Conan
|
||||
|
||||
To better understand how to use Conan,
|
||||
we should first understand _why_ we use Conan,
|
||||
and to understand that,
|
||||
we need to understand how we use CMake.
|
||||
To better understand how to use Conan, we should first understand _why_ we use Conan, and to understand that, we need to understand how we use CMake.
|
||||
|
||||
### CMake
|
||||
|
||||
Technically, you don't need CMake to build this project.
|
||||
You could manually compile every translation unit into an object file,
|
||||
using the right compiler options,
|
||||
and then manually link all those objects together,
|
||||
using the right linker options.
|
||||
However, that is very tedious and error-prone,
|
||||
which is why we lean on tools like CMake.
|
||||
Technically, you don't need CMake to build this project. You could manually compile every translation unit into an object file, using the right compiler options, and then manually link all those objects together, using the right linker options. However, that is very tedious and error-prone, which is why we lean on tools like CMake.
|
||||
|
||||
We have written CMake configuration files
|
||||
([`CMakeLists.txt`](./CMakeLists.txt) and friends)
|
||||
for this project so that CMake can be used to correctly compile and link
|
||||
all of the translation units in it.
|
||||
Or rather, CMake will generate files for a separate build system
|
||||
(e.g. Make, Ninja, Visual Studio, Xcode, etc.)
|
||||
that compile and link all of the translation units.
|
||||
Even then, CMake has parameters, some of which are platform-specific.
|
||||
In CMake's parlance, parameters are specially-named **variables** like
|
||||
[`CMAKE_BUILD_TYPE`][build_type] or
|
||||
[`CMAKE_MSVC_RUNTIME_LIBRARY`][runtime].
|
||||
Parameters include:
|
||||
We have written CMake configuration files ([`CMakeLists.txt`](./CMakeLists.txt) and friends) for this project so that CMake can be used to correctly compile and link all of the translation units in it. Or rather, CMake will generate files for a separate build system (e.g. Make, Ninja, Visual Studio, Xcode, etc.) that compile and link all of the translation units. Even then, CMake has parameters, some of which are platform-specific. In CMake's parlance, parameters are specially-named **variables** like [`CMAKE_BUILD_TYPE`][build_type] or [`CMAKE_MSVC_RUNTIME_LIBRARY`][runtime]. Parameters include:
|
||||
|
||||
- what build system to generate files for
|
||||
- where to find the compiler and linker
|
||||
- where to find dependencies, e.g. libraries and headers
|
||||
- how to link dependencies, e.g. any special compiler or linker flags that
|
||||
need to be used with them, including preprocessor definitions
|
||||
- how to compile translation units, e.g. with optimizations, debug symbols,
|
||||
position-independent code, etc.
|
||||
- how to link dependencies, e.g. any special compiler or linker flags that need to be used with them, including preprocessor definitions
|
||||
- how to compile translation units, e.g. with optimizations, debug symbols, position-independent code, etc.
|
||||
- on Windows, which runtime library to link with
|
||||
|
||||
For some of these parameters, like the build system and compiler,
|
||||
CMake goes through a complicated search process to choose default values.
|
||||
For others, like the dependencies,
|
||||
_we_ had written in the CMake configuration files of this project
|
||||
our own complicated process to choose defaults.
|
||||
For most developers, things "just worked"... until they didn't, and then
|
||||
you were left trying to debug one of these complicated processes, instead of
|
||||
choosing and manually passing the parameter values yourself.
|
||||
For some of these parameters, like the build system and compiler, CMake goes through a complicated search process to choose default values. For others, like the dependencies, _we_ had written in the CMake configuration files of this project our own complicated process to choose defaults. For most developers, things "just worked"... until they didn't, and then you were left trying to debug one of these complicated processes, instead of choosing and manually passing the parameter values yourself.
|
||||
|
||||
You can pass every parameter to CMake on the command line,
|
||||
but writing out these parameters every time we want to configure CMake is
|
||||
a pain.
|
||||
Most humans prefer to put them into a configuration file, once, that
|
||||
CMake can read every time it is configured.
|
||||
For CMake, that file is a [toolchain file][toolchain].
|
||||
You can pass every parameter to CMake on the command line, but writing out these parameters every time we want to configure CMake is a pain. Most humans prefer to put them into a configuration file, once, that CMake can read every time it is configured. For CMake, that file is a [toolchain file][toolchain].
|
||||
|
||||
### Conan
|
||||
|
||||
These next few paragraphs on Conan are going to read much like the ones above
|
||||
for CMake.
|
||||
These next few paragraphs on Conan are going to read much like the ones above for CMake.
|
||||
|
||||
Technically, you don't need Conan to build this project.
|
||||
You could manually download, configure, build, and install all of the
|
||||
dependencies yourself, and then pass all of the parameters necessary for
|
||||
CMake to link to those dependencies.
|
||||
To guarantee ABI compatibility, you must be sure to use the same set of
|
||||
compiler and linker options for all dependencies _and_ this project.
|
||||
However, that is very tedious and error-prone, which is why we lean on tools
|
||||
like Conan.
|
||||
Technically, you don't need Conan to build this project. You could manually download, configure, build, and install all of the dependencies yourself, and then pass all of the parameters necessary for CMake to link to those dependencies. To guarantee ABI compatibility, you must be sure to use the same set of compiler and linker options for all dependencies _and_ this project. However, that is very tedious and error-prone, which is why we lean on tools like Conan.
|
||||
|
||||
We have written a Conan configuration file ([`conanfile.py`](../../conanfile.py))
|
||||
so that Conan can be used to correctly download, configure, build, and install
|
||||
all of the dependencies for this project,
|
||||
using a single set of compiler and linker options for all of them.
|
||||
It generates files that contain almost all of the parameters that CMake
|
||||
expects.
|
||||
Those files include:
|
||||
We have written a Conan configuration file ([`conanfile.py`](../../conanfile.py)) so that Conan can be used to correctly download, configure, build, and install all of the dependencies for this project, using a single set of compiler and linker options for all of them. It generates files that contain almost all of the parameters that CMake expects. Those files include:
|
||||
|
||||
- A single toolchain file.
|
||||
- For every dependency, a CMake [package configuration file][pcf],
|
||||
[package version file][pvf], and for every build type, a package
|
||||
targets file.
|
||||
Together, these files implement version checking and define `IMPORTED`
|
||||
targets for the dependencies.
|
||||
- For every dependency, a CMake [package configuration file][pcf], [package version file][pvf], and for every build type, a package targets file. Together, these files implement version checking and define `IMPORTED` targets for the dependencies.
|
||||
|
||||
The toolchain file itself amends the search path
|
||||
([`CMAKE_PREFIX_PATH`][prefix_path]) so that [`find_package()`][find_package]
|
||||
will [discover][search] the generated package configuration files.
|
||||
The toolchain file itself amends the search path ([`CMAKE_PREFIX_PATH`][prefix_path]) so that [`find_package()`][find_package] will [discover][search] the generated package configuration files.
|
||||
|
||||
**Nearly all we must do to properly configure CMake is pass the toolchain
|
||||
file.**
|
||||
What CMake parameters are left out?
|
||||
You'll still need to pick a build system generator,
|
||||
and if you choose a single-configuration generator,
|
||||
you'll need to pass the `CMAKE_BUILD_TYPE`,
|
||||
which should match the `build_type` setting you gave to Conan.
|
||||
**Nearly all we must do to properly configure CMake is pass the toolchain file.** What CMake parameters are left out? You'll still need to pick a build system generator, and if you choose a single-configuration generator, you'll need to pass the `CMAKE_BUILD_TYPE`, which should match the `build_type` setting you gave to Conan.
|
||||
|
||||
Even then, Conan has parameters, some of which are platform-specific.
|
||||
In Conan's parlance, parameters are either settings or options.
|
||||
**Settings** are shared by all packages, e.g. the build type.
|
||||
**Options** are specific to a given package, e.g. whether to build and link
|
||||
OpenSSL as a shared library.
|
||||
Even then, Conan has parameters, some of which are platform-specific. In Conan's parlance, parameters are either settings or options. **Settings** are shared by all packages, e.g. the build type. **Options** are specific to a given package, e.g. whether to build and link OpenSSL as a shared library.
|
||||
|
||||
For settings, Conan goes through a complicated search process to choose
|
||||
defaults.
|
||||
For options, each package recipe defines its own defaults.
|
||||
For settings, Conan goes through a complicated search process to choose defaults. For options, each package recipe defines its own defaults.
|
||||
|
||||
You can pass every parameter to Conan on the command line,
|
||||
but it is more convenient to put them in a configuration file, once, that
|
||||
Conan can read every time it is configured.
|
||||
For Conan, that file is a [profile][].
|
||||
**All we must do to properly configure Conan is edit and pass the profile.**
|
||||
By default, Conan will use the profile named "default".
|
||||
You can pass every parameter to Conan on the command line, but it is more convenient to put them in a configuration file, once, that Conan can read every time it is configured. For Conan, that file is a [profile][]. **All we must do to properly configure Conan is edit and pass the profile.** By default, Conan will use the profile named "default".
|
||||
|
||||
[build_type]: https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html
|
||||
[find_package]: https://cmake.org/cmake/help/latest/command/find_package.html
|
||||
|
||||
19
docs/build/depend.md
vendored
19
docs/build/depend.md
vendored
@@ -1,12 +1,8 @@
|
||||
We recommend two different methods to depend on libxrpl in your own [CMake][]
|
||||
project.
|
||||
Both methods add a CMake library target named `xrpl::libxrpl`.
|
||||
We recommend two different methods to depend on libxrpl in your own [CMake][] project. Both methods add a CMake library target named `xrpl::libxrpl`.
|
||||
|
||||
## Conan requirement
|
||||
|
||||
The first method adds libxrpl as a [Conan][] requirement.
|
||||
With this method, there is no need for a Git [submodule][].
|
||||
It is good for when you just need a dependency on libxrpl as-is.
|
||||
The first method adds libxrpl as a [Conan][] requirement. With this method, there is no need for a Git [submodule][]. It is good for when you just need a dependency on libxrpl as-is.
|
||||
|
||||
```
|
||||
# This conanfile.txt is just an example.
|
||||
@@ -49,16 +45,7 @@ cmake --build . --parallel
|
||||
|
||||
## CMake subdirectory
|
||||
|
||||
The second method adds the [xrpld][] project as a CMake
|
||||
[subdirectory][add_subdirectory].
|
||||
This method works well when you keep the xrpld project as a Git
|
||||
[submodule][].
|
||||
It's good for when you want to make changes to libxrpl as part of your own
|
||||
project.
|
||||
Be careful, though.
|
||||
Your project will inherit all of the same CMake options,
|
||||
so watch out for name collisions.
|
||||
We still recommend using [Conan][] to download, build, and connect dependencies.
|
||||
The second method adds the [xrpld][] project as a CMake [subdirectory][add_subdirectory]. This method works well when you keep the xrpld project as a Git [submodule][]. It's good for when you want to make changes to libxrpl as part of your own project. Be careful, though. Your project will inherit all of the same CMake options, so watch out for name collisions. We still recommend using [Conan][] to download, build, and connect dependencies.
|
||||
|
||||
```
|
||||
# Add the project as a Git submodule.
|
||||
|
||||
86
docs/build/environment.md
vendored
86
docs/build/environment.md
vendored
@@ -1,14 +1,10 @@
|
||||
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.
|
||||
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.
|
||||
|
||||
[BUILD.md]: ../../BUILD.md
|
||||
|
||||
## Tested compiler versions
|
||||
|
||||
`xrpld` is built in the **C++23** dialect by default, so your toolchain has to
|
||||
support it — see [compiler support for C++23][cpp23-support].
|
||||
The versions currently tested in CI are:
|
||||
`xrpld` is built in the **C++23** dialect by default, so your toolchain has to support it — see [compiler support for C++23][cpp23-support]. The versions currently tested in CI are:
|
||||
|
||||
| Compiler | Version |
|
||||
| ----------- | ------------------ |
|
||||
@@ -21,16 +17,9 @@ LLVM tools (`clang-tidy` and `clang-format`) are also pinned to version 22.
|
||||
|
||||
### Older compilers
|
||||
|
||||
Older compilers may fail to build the latest `develop` code: the codebase now
|
||||
relies on C++23 features and has been adjusted for `clang-tidy`.
|
||||
If the latest code doesn't build for you, update your build toolchain first.
|
||||
Older compilers may fail to build the latest `develop` code: the codebase now relies on C++23 features and has been adjusted for `clang-tidy`. If the latest code doesn't build for you, update your build toolchain first.
|
||||
|
||||
If updating isn't an option for you, we do accept pull requests that fix builds
|
||||
on older compilers, as long as the change is small and doesn't make the code
|
||||
harder to read. What we can't promise is that older compilers will keep working:
|
||||
only the versions in the table above are tested in CI, and we won't hold back
|
||||
the use of C++23 features or add invasive workarounds to keep an untested
|
||||
compiler building. Treat support for anything outside the table as best-effort.
|
||||
If updating isn't an option for you, we do accept pull requests that fix builds on older compilers, as long as the change is small and doesn't make the code harder to read. What we can't promise is that older compilers will keep working: only the versions in the table above are tested in CI, and we won't hold back the use of C++23 features or add invasive workarounds to keep an untested compiler building. Treat support for anything outside the table as best-effort.
|
||||
|
||||
## Required tools
|
||||
|
||||
@@ -43,11 +32,9 @@ Besides a compiler, building `xrpld` requires:
|
||||
| [Conan](https://conan.io/downloads.html) | 2.17 |
|
||||
| [CMake](https://cmake.org/download/) | 3.16 |
|
||||
|
||||
On Linux and macOS, the [Nix development shell](./nix.md) provides all of them
|
||||
(see below). On Windows they have to be installed manually.
|
||||
On Linux and macOS, the [Nix development shell](./nix.md) provides all of them (see below). On Windows they have to be installed manually.
|
||||
|
||||
Building with `-Drust=ON` additionally requires a Rust toolchain, see
|
||||
[Rust](#rust). A default build does not, so it is not in the table above.
|
||||
Building with `-Drust=ON` additionally requires a Rust toolchain, see [Rust](#rust). A default build does not, so it is not in the table above.
|
||||
|
||||
Once they are in place, verify that everything is installed and runnable with:
|
||||
|
||||
@@ -57,38 +44,25 @@ Once they are in place, verify that everything is installed and runnable with:
|
||||
|
||||
## 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:
|
||||
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
|
||||
```
|
||||
|
||||
On **Linux**, Nix also provides the compiler (GCC); on **macOS**, it provides
|
||||
Clang. If you instead opt to use your system-wide Apple Clang (via
|
||||
`nix develop .#apple-clang`), you need to manage its version yourself (see
|
||||
below).
|
||||
On **Linux**, Nix also provides the compiler (GCC); on **macOS**, it provides Clang. If you instead opt to use your system-wide Apple Clang (via `nix develop .#apple-clang`), you need to manage its version yourself (see below).
|
||||
|
||||
See [Using the Nix development shell](./nix.md) for installation and usage
|
||||
details, including how to select a different compiler and why we recommend Nix
|
||||
over a hand-maintained environment.
|
||||
See [Using the Nix development shell](./nix.md) for installation and usage details, including how to select a different compiler and why we recommend Nix over a hand-maintained environment.
|
||||
|
||||
### macOS: managing the Apple Clang version
|
||||
|
||||
If you use your system-wide Apple Clang on macOS (via `nix develop .#apple-clang`),
|
||||
the compiler version is whatever your installed Xcode (or Command Line Tools)
|
||||
provides. The following command should return a version greater than or equal to
|
||||
the [tested one](#tested-compiler-versions):
|
||||
If you use your system-wide Apple Clang on macOS (via `nix develop .#apple-clang`), the compiler version is whatever your installed Xcode (or Command Line Tools) provides. The following command should return a version greater than or equal to the [tested one](#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:
|
||||
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:
|
||||
|
||||
1. **Download Xcode**
|
||||
- Visit [Apple Developer Downloads](https://developer.apple.com/download/more/)
|
||||
@@ -114,45 +88,25 @@ building xrpld. You may want to install and pin a specific version of Xcode:
|
||||
|
||||
## Windows
|
||||
|
||||
Nix is not available on Windows, so the required tools have to be installed
|
||||
manually:
|
||||
Nix is not available on Windows, so the required tools have to be installed manually:
|
||||
|
||||
- [Visual Studio 2026](https://visualstudio.microsoft.com/) with the
|
||||
**"Desktop development with C++"** workload — this provides MSVC and the
|
||||
"x64 Native Tools Command Prompt". CI configures CMake with the
|
||||
`Visual Studio 18 2026` generator.
|
||||
- [Visual Studio 2026](https://visualstudio.microsoft.com/) with the **"Desktop development with C++"** workload — this provides MSVC and the "x64 Native Tools Command Prompt". CI configures CMake with the `Visual Studio 18 2026` generator.
|
||||
- [Git for Windows](https://git-scm.com/download/win)
|
||||
- Python, Conan, and CMake, at the versions listed in
|
||||
[Required tools](#required-tools).
|
||||
- a [Rust toolchain](https://rustup.rs) — only needed to build with
|
||||
`-Drust=ON`, see [Rust](#rust)
|
||||
- Python, Conan, and CMake, at the versions listed in [Required tools](#required-tools).
|
||||
- a [Rust toolchain](https://rustup.rs) — only needed to build with `-Drust=ON`, see [Rust](#rust)
|
||||
|
||||
## Rust
|
||||
|
||||
The repository contains a Rust workspace in [`crates/`](../../crates), whose
|
||||
crates are exposed to C++ through [cxx](https://cxx.rs) bindings. It is **not**
|
||||
part of a default build: the CMake `rust` option is OFF by default, and with it
|
||||
off no Rust toolchain is needed. It is only required when configuring with
|
||||
`-Drust=ON` (which is what CI does), see [Options](../../BUILD.md#options).
|
||||
The repository contains a Rust workspace in [`crates/`](../../crates), whose crates are exposed to C++ through [cxx](https://cxx.rs) bindings. It is **not** part of a default build: the CMake `rust` option is OFF by default, and with it off no Rust toolchain is needed. It is only required when configuring with `-Drust=ON` (which is what CI does), see [Options](../../BUILD.md#options).
|
||||
|
||||
The toolchain (`cargo`, `rustc`) is pinned to the channel in
|
||||
[`rust-toolchain.toml`](../../rust-toolchain.toml) at the repository root. If
|
||||
you install Rust with [rustup](https://rustup.rs), that file is picked up
|
||||
automatically, and `cargo`/`rustc` in the repository will use the pinned
|
||||
version.
|
||||
The toolchain (`cargo`, `rustc`) is pinned to the channel in [`rust-toolchain.toml`](../../rust-toolchain.toml) at the repository root. If you install Rust with [rustup](https://rustup.rs), that file is picked up automatically, and `cargo`/`rustc` in the repository will use the pinned version.
|
||||
|
||||
Everything else the Rust build needs on the CMake side comes from Conan along
|
||||
with the rest of the dependencies, so there is nothing further to install.
|
||||
Everything else the Rust build needs on the CMake side comes from Conan along with the rest of the dependencies, so there is nothing further to install.
|
||||
|
||||
## Clang-tidy
|
||||
|
||||
`clang-tidy` is required to run static analysis checks locally (see
|
||||
[CONTRIBUTING.md](../../CONTRIBUTING.md)). It is not required to build the
|
||||
project. The version this project uses is listed in
|
||||
[Tested compiler versions](#tested-compiler-versions).
|
||||
`clang-tidy` is required to run static analysis checks locally (see [CONTRIBUTING.md](../../CONTRIBUTING.md)). It is not required to build the project. The version this project uses is listed in [Tested compiler versions](#tested-compiler-versions).
|
||||
|
||||
On Linux and macOS, the [Nix development shell](./nix.md) provides that exact
|
||||
version out of the box — run it via `run-clang-tidy`. No separate installation
|
||||
is needed.
|
||||
On Linux and macOS, the [Nix development shell](./nix.md) provides that exact version out of the box — run it via `run-clang-tidy`. No separate installation is needed.
|
||||
|
||||
[cpp23-support]: https://en.cppreference.com/w/cpp/compiler_support/23
|
||||
|
||||
115
docs/build/nix.md
vendored
115
docs/build/nix.md
vendored
@@ -38,17 +38,10 @@ The first time you run this command, it will take a few minutes to download and
|
||||
|
||||
### Platform notes
|
||||
|
||||
- **Linux**: `nix develop` gives you a shell with all the tooling necessary to develop xrpld
|
||||
and with the same GCC/glibc toolchain that Nix builds for CI.
|
||||
See [Choosing a different compiler](#choosing-a-different-compiler)
|
||||
for the custom-vs-plain toolchain trade-off.
|
||||
- **macOS**: `nix develop` gives you a full environment too, with Clang (and
|
||||
every other tool, including Conan) provided by Nix. To use your system-wide
|
||||
Apple Clang instead, enter `nix develop .#apple-clang`. 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)).
|
||||
- **Linux**: `nix develop` gives you a shell with all the tooling necessary to develop xrpld and with the same GCC/glibc toolchain that Nix builds for CI. See [Choosing a different compiler](#choosing-a-different-compiler) for the custom-vs-plain toolchain trade-off.
|
||||
- **macOS**: `nix develop` gives you a full environment too, with Clang (and every other tool, including Conan) provided by Nix. To use your system-wide Apple Clang instead, enter `nix develop .#apple-clang`. 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)).
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
> [!TIP]
|
||||
> To avoid typing `--experimental-features 'nix-command flakes'` every time, you can permanently enable flakes by creating `~/.config/nix/nix.conf`:
|
||||
>
|
||||
@@ -59,6 +52,7 @@ The first time you run this command, it will take a few minutes to download and
|
||||
>
|
||||
> After this, you can simply use `nix develop` instead.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> The examples below assume you've enabled flakes in your config. If you haven't, add `--experimental-features 'nix-command flakes'` after each `nix` command.
|
||||
|
||||
@@ -66,14 +60,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 .#clang`.
|
||||
|
||||
On Linux, `.#gcc` and `.#clang` provide the exact toolchain CI uses:
|
||||
the compiler (pinned in [`nix/packages.nix`](../../nix/packages.nix))
|
||||
rebuilt against the pinned custom glibc (see [`nix/linux.nix`](../../nix/linux.nix)).
|
||||
Building that toolchain the first time is slow unless it is fetched from a Nix binary cache.
|
||||
If you don't need the custom glibc, the Linux-only `.#gcc-plain` and `.#clang-plain`
|
||||
give you the stock nixpkgs compilers of the same versions.
|
||||
On macOS there is no custom glibc, so `.#gcc` and `.#clang` are already the plain nixpkgs toolchain,
|
||||
and the `-plain` variants do not exist.
|
||||
On Linux, `.#gcc` and `.#clang` provide the exact toolchain CI uses: the compiler (pinned in [`nix/packages.nix`](../../nix/packages.nix)) rebuilt against the pinned custom glibc (see [`nix/linux.nix`](../../nix/linux.nix)). Building that toolchain the first time is slow unless it is fetched from a Nix binary cache. If you don't need the custom glibc, the Linux-only `.#gcc-plain` and `.#clang-plain` give you the stock nixpkgs compilers of the same versions. On macOS there is no custom glibc, so `.#gcc` and `.#clang` are already the plain nixpkgs toolchain, and the `-plain` variants do not exist.
|
||||
|
||||
Use `nix flake show` to see all the available development shells.
|
||||
|
||||
@@ -111,6 +98,7 @@ nix develop -c fish
|
||||
nix develop -c "$SHELL"
|
||||
```
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
> [!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:
|
||||
>
|
||||
@@ -124,80 +112,40 @@ nix develop -c "$SHELL"
|
||||
|
||||
Once inside the Nix development shell, follow the standard [build instructions](../../BUILD.md#steps). The Nix shell provides all necessary tools (CMake, Ninja, Conan, etc.).
|
||||
|
||||
Coverage builds (`-Dcoverage=ON`) work in the `gcc` shell (and `gcc-plain` on Linux):
|
||||
each ships a `gcov` matching its compiler, since Nix's cc-wrapper does not expose one.
|
||||
The `clang` shells do not include `llvm-cov`, so use a `gcc` shell for coverage.
|
||||
Coverage builds (`-Dcoverage=ON`) work in the `gcc` shell (and `gcc-plain` on Linux): each ships a `gcov` matching its compiler, since Nix's cc-wrapper does not expose one. The `clang` shells do not include `llvm-cov`, so use a `gcc` shell for coverage.
|
||||
|
||||
Builds of the Rust crates (`-Drust=ON`) also work out of the box: every shell
|
||||
provides the Rust toolchain pinned in
|
||||
[`rust-toolchain.toml`](../../rust-toolchain.toml) (see
|
||||
[Rust](./environment.md#rust)), plus the `cargo-audit`, `cargo-llvm-cov` and
|
||||
`cargo-nextest` plugins.
|
||||
Builds of the Rust crates (`-Drust=ON`) also work out of the box: every shell provides the Rust toolchain pinned in [`rust-toolchain.toml`](../../rust-toolchain.toml) (see [Rust](./environment.md#rust)), plus the `cargo-audit`, `cargo-llvm-cov` and `cargo-nextest` plugins.
|
||||
|
||||
## Conan configuration
|
||||
|
||||
The shell runs [`conan/init.sh`](../../conan/init.sh) on entry, so
|
||||
[Set Up Conan](../../BUILD.md#set-up-conan) is already done for you. It installs
|
||||
into the shell's own Conan home: `CONAN_HOME=~/.conan2-nix`.
|
||||
The shell runs [`conan/init.sh`](../../conan/init.sh) on entry, so [Set Up Conan](../../BUILD.md#set-up-conan) is already done for you. It installs into the shell's own Conan home: `CONAN_HOME=~/.conan2-nix`.
|
||||
|
||||
### Prebuilt packages
|
||||
|
||||
On **Linux**, the binaries on the `xrplf` remote are built in this same Nix
|
||||
environment — CI runs in Docker images that bundle the dev shell's toolchain (see
|
||||
[`nix/docker`](../../nix/docker)) — so `.#gcc` and `.#clang` can reuse them. The
|
||||
`-plain` shells do not match that toolchain's glibc, so binaries from the remote
|
||||
are not a reliable match there.
|
||||
On **Linux**, the binaries on the `xrplf` remote are built in this same Nix environment — CI runs in Docker images that bundle the dev shell's toolchain (see [`nix/docker`](../../nix/docker)) — so `.#gcc` and `.#clang` can reuse them. The `-plain` shells do not match that toolchain's glibc, so binaries from the remote are not a reliable match there.
|
||||
|
||||
On **macOS**, CI also builds in this Nix environment, in Debug and Release (the
|
||||
`macos-arm64-*-nix` configurations — Debug because the profile defaults to it).
|
||||
The Nix build resolves to `compiler=clang`, so it gets its own package IDs,
|
||||
separate from the Apple Clang ones. The
|
||||
[dependency upload](../../.github/workflows/upload-conan-deps.yml) publishes them
|
||||
on pushes to `develop` and on manual runs — its nightly run rebuilds everything
|
||||
from source but uploads nothing — so once a set has been published `nix develop`
|
||||
can reuse it instead of compiling every dependency locally. These configurations
|
||||
run outside the reduced pull-request matrix, so label a PR `Full CI build` when it
|
||||
touches `flake.lock` or `nix/`.
|
||||
On **macOS**, CI also builds in this Nix environment, in Debug and Release (the `macos-arm64-*-nix` configurations — Debug because the profile defaults to it). The Nix build resolves to `compiler=clang`, so it gets its own package IDs, separate from the Apple Clang ones. The [dependency upload](../../.github/workflows/upload-conan-deps.yml) publishes them on pushes to `develop` and on manual runs — its nightly run rebuilds everything from source but uploads nothing — so once a set has been published `nix develop` can reuse it instead of compiling every dependency locally. These configurations run outside the reduced pull-request matrix, so label a PR `Full CI build` when it touches `flake.lock` or `nix/`.
|
||||
|
||||
To compile everything from source, add `--build '*'` to the `conan install`
|
||||
command.
|
||||
To compile everything from source, add `--build '*'` to the `conan install` command.
|
||||
|
||||
### Why the nixpkgs revision is not part of the package ID
|
||||
|
||||
A Conan package ID records the compiler and its major version, but nothing about
|
||||
the nixpkgs revision the toolchain came from — and `flake.lock` moves far more
|
||||
often than the toolchain meaningfully changes, so folding it in would rebuild
|
||||
every dependency on every bump for nothing.
|
||||
A Conan package ID records the compiler and its major version, but nothing about the nixpkgs revision the toolchain came from — and `flake.lock` moves far more often than the toolchain meaningfully changes, so folding it in would rebuild every dependency on every bump for nothing.
|
||||
|
||||
That is safe as long as no cached artifact resolves a `/nix/store` path at run
|
||||
time, because store paths change on every update and the old ones disappear with
|
||||
`nix-collect-garbage`. With the `clang` toolchain macOS CI and the dev shell use,
|
||||
they do not: it links against `/usr/lib/libc++` and `/usr/lib/libSystem`, and
|
||||
store paths reach the `.a` files only through debug info, which nothing resolves
|
||||
at link or run time.
|
||||
That is safe as long as no cached artifact resolves a `/nix/store` path at run time, because store paths change on every update and the old ones disappear with `nix-collect-garbage`. With the `clang` toolchain macOS CI and the dev shell use, they do not: it links against `/usr/lib/libc++` and `/usr/lib/libSystem`, and store paths reach the `.a` files only through debug info, which nothing resolves at link or run time.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
> [!WARNING]
|
||||
> This does not hold for `nix develop .#gcc` on macOS. There is no system
|
||||
> libstdc++, so GCC links its own from the store and every binary keeps a
|
||||
> `/nix/store` reference. That shell is fine for tooling, but it is not a build
|
||||
> configuration CI covers, and no dependency binaries are published for it.
|
||||
> This does not hold for `nix develop .#gcc` on macOS. There is no system libstdc++, so GCC links its own from the store and every binary keeps a `/nix/store` reference. That shell is fine for tooling, but it is not a build configuration CI covers, and no dependency binaries are published for it.
|
||||
|
||||
This is checked rather than assumed.
|
||||
[`bin/check-nix-store-refs.sh`](../../bin/check-nix-store-refs.sh) takes one file
|
||||
or directory and fails if a binary under it resolves a store path at run time.
|
||||
CI runs it over the build output and the Conan cache, and again in the upload job
|
||||
before anything is published. You can run it yourself:
|
||||
This is checked rather than assumed. [`bin/check-nix-store-refs.sh`](../../bin/check-nix-store-refs.sh) takes one file or directory and fails if a binary under it resolves a store path at run time. CI runs it over the build output and the Conan cache, and again in the upload job before anything is published. You can run it yourself:
|
||||
|
||||
```bash
|
||||
bin/check-nix-store-refs.sh build
|
||||
bin/check-nix-store-refs.sh ~/.conan2-nix
|
||||
```
|
||||
|
||||
It works on Linux too, but asserts something narrower there: the toolchain always
|
||||
writes the store into `PT_INTERP` and `RUNPATH`, and CI builds inside an image
|
||||
whose store is fixed for its lifetime, so that is fine. Only the binaries
|
||||
[`PatchNixBinary.cmake`](../../cmake/PatchNixBinary.cmake) retargets to the
|
||||
system loader have to be clean, and those are what CI checks:
|
||||
It works on Linux too, but asserts something narrower there: the toolchain always writes the store into `PT_INTERP` and `RUNPATH`, and CI builds inside an image whose store is fixed for its lifetime, so that is fine. Only the binaries [`PatchNixBinary.cmake`](../../cmake/PatchNixBinary.cmake) retargets to the system loader have to be clean, and those are what CI checks:
|
||||
|
||||
```bash
|
||||
bin/check-nix-store-refs.sh build/xrpld
|
||||
@@ -205,22 +153,11 @@ bin/check-nix-store-refs.sh build/xrpld
|
||||
|
||||
### The libresolv stub
|
||||
|
||||
This is not hypothetical: `xrpld` used to be caught by it. The c-ares package
|
||||
tells the linker to pass `-lresolv`, and nixpkgs keeps `libresolv` out of the
|
||||
macOS SDK and ships it as an ordinary store dylib — so every Nix-built `xrpld`
|
||||
recorded a `/nix/store/…-libresolv-93/lib/libresolv.9.dylib` load command and
|
||||
stopped running once that path was collected. Nothing in the link uses a single
|
||||
symbol from it.
|
||||
This is not hypothetical: `xrpld` used to be caught by it. The c-ares package tells the linker to pass `-lresolv`, and nixpkgs keeps `libresolv` out of the macOS SDK and ships it as an ordinary store dylib — so every Nix-built `xrpld` recorded a `/nix/store/…-libresolv-93/lib/libresolv.9.dylib` load command and stopped running once that path was collected. Nothing in the link uses a single symbol from it.
|
||||
|
||||
Both environments now put a stub on the linker search path
|
||||
(`libresolvSystemStub` in [`nix/darwin.nix`](../../nix/darwin.nix)): the
|
||||
same library with its install name set to `/usr/lib/libresolv.9.dylib`, which is
|
||||
exactly the load command the Apple Clang build records.
|
||||
Both environments now put a stub on the linker search path (`libresolvSystemStub` in [`nix/darwin.nix`](../../nix/darwin.nix)): the same library with its install name set to `/usr/lib/libresolv.9.dylib`, which is exactly the load command the Apple Clang build records.
|
||||
|
||||
Package IDs did not change, so Conan keeps serving anything built before the
|
||||
stub landed. If a binary fails to start with `Library not loaded: /nix/store/…`,
|
||||
see [that entry](./nix_troubleshooting.md#library-not-loaded-nixstore-from-a-binary-that-used-to-work)
|
||||
in the troubleshooting guide.
|
||||
Package IDs did not change, so Conan keeps serving anything built before the stub landed. If a binary fails to start with `Library not loaded: /nix/store/…`, see [that entry](./nix_troubleshooting.md#library-not-loaded-nixstore-from-a-binary-that-used-to-work) in the troubleshooting guide.
|
||||
|
||||
## Automatic Activation with direnv
|
||||
|
||||
@@ -233,6 +170,7 @@ The repository already ships an `.envrc` at its root that activates the Nix flak
|
||||
1. [Install direnv](https://direnv.net/docs/installation.html) and [hook it into your shell](https://direnv.net/docs/hook.html) (bash, zsh, fish, …). Installing [nix-direnv](https://github.com/nix-community/nix-direnv) as well is recommended: it caches the shell so that activation is near-instant after the first run.
|
||||
2. Run `direnv allow` once in the repository root. direnv will then load (and reload) the Nix development shell automatically whenever you enter the directory.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> direnv only caches the `.direnv` directory (already listed in `.gitignore`); no other repository files are affected.
|
||||
|
||||
@@ -242,13 +180,8 @@ To update `flake.lock` to the latest revision use `nix flake update` command.
|
||||
|
||||
## Tooling snapshots
|
||||
|
||||
The tool versions in each Nix environment are recorded in
|
||||
[`nix/check-tools/`](../../nix/check-tools) and verified by CI. If you change the
|
||||
environment (bump the CI image tag, update `flake.lock`, or edit the tool list in
|
||||
`bin/check-tools.sh`), CI fails until you regenerate and commit the affected
|
||||
snapshot — see [`nix/check-tools/README.md`](../../nix/check-tools/README.md).
|
||||
The tool versions in each Nix environment are recorded in [`nix/check-tools/`](../../nix/check-tools) and verified by CI. If you change the environment (bump the CI image tag, update `flake.lock`, or edit the tool list in `bin/check-tools.sh`), CI fails until you regenerate and commit the affected snapshot — see [`nix/check-tools/README.md`](../../nix/check-tools/README.md).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
See [Troubleshooting Nix problems](./nix_troubleshooting.md) for common issues,
|
||||
such as `nix develop` failing inside Git worktrees.
|
||||
See [Troubleshooting Nix problems](./nix_troubleshooting.md) for common issues, such as `nix develop` failing inside Git worktrees.
|
||||
|
||||
104
docs/build/nix_troubleshooting.md
vendored
104
docs/build/nix_troubleshooting.md
vendored
@@ -1,7 +1,6 @@
|
||||
# Troubleshooting Nix problems
|
||||
|
||||
Common issues encountered when using the [Nix development shell](./nix.md), and
|
||||
how to resolve them.
|
||||
Common issues encountered when using the [Nix development shell](./nix.md), and how to resolve them.
|
||||
|
||||
## `command not found: nix` after a macOS update
|
||||
|
||||
@@ -12,8 +11,7 @@ $ nix develop
|
||||
zsh: command not found: nix
|
||||
```
|
||||
|
||||
then Nix is almost certainly still installed — only the shell hook that puts it
|
||||
on your `PATH` is gone. Confirm that first:
|
||||
then Nix is almost certainly still installed — only the shell hook that puts it on your `PATH` is gone. Confirm that first:
|
||||
|
||||
```bash
|
||||
ls -l /nix/var/nix/profiles/default/bin/nix
|
||||
@@ -23,8 +21,7 @@ If that exists, the installation is fine and this is purely a `PATH` problem.
|
||||
|
||||
### Why it happens
|
||||
|
||||
The installer does not touch your dotfiles. Instead it sources a setup script
|
||||
from the Nix store by editing **system-wide** rc files:
|
||||
The installer does not touch your dotfiles. Instead it sources a setup script from the Nix store by editing **system-wide** rc files:
|
||||
|
||||
| Shell | File the installer edits |
|
||||
| ----- | ------------------------------------- |
|
||||
@@ -32,17 +29,13 @@ from the Nix store by editing **system-wide** rc files:
|
||||
| zsh | `/etc/zshrc` |
|
||||
| fish | `$__fish_sysconf_dir/conf.d/nix.fish` |
|
||||
|
||||
macOS manages `/etc/zshrc`, so an OS update can replace it with the vendor copy
|
||||
and silently drop the Nix block. `/etc/bashrc` and the fish file usually survive,
|
||||
which is why the breakage often shows up in zsh only. You can verify this by
|
||||
diffing against the backup the installer left behind:
|
||||
macOS manages `/etc/zshrc`, so an OS update can replace it with the vendor copy and silently drop the Nix block. `/etc/bashrc` and the fish file usually survive, which is why the breakage often shows up in zsh only. You can verify this by diffing against the backup the installer left behind:
|
||||
|
||||
```bash
|
||||
diff /etc/zshrc /etc/zshrc.backup-before-nix
|
||||
```
|
||||
|
||||
If they are identical, the Nix snippet was wiped. This is upstream issue
|
||||
[NixOS/nix#3616](https://github.com/NixOS/nix/issues/3616).
|
||||
If they are identical, the Nix snippet was wiped. This is upstream issue [NixOS/nix#3616](https://github.com/NixOS/nix/issues/3616).
|
||||
|
||||
### Fix
|
||||
|
||||
@@ -52,8 +45,7 @@ To unblock the current shell:
|
||||
. /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
|
||||
```
|
||||
|
||||
For a permanent fix, add the snippet to your **user** rc file rather than
|
||||
restoring `/etc/zshrc` — user dotfiles are not clobbered by OS updates:
|
||||
For a permanent fix, add the snippet to your **user** rc file rather than restoring `/etc/zshrc` — user dotfiles are not clobbered by OS updates:
|
||||
|
||||
```bash
|
||||
cat >>~/.zshrc <<'EOF'
|
||||
@@ -66,14 +58,11 @@ fi
|
||||
EOF
|
||||
```
|
||||
|
||||
The scripts guard against double-sourcing via `__ETC_PROFILE_NIX_SOURCED`, so
|
||||
this is safe even if a system-wide hook is later restored.
|
||||
The scripts guard against double-sourcing via `__ETC_PROFILE_NIX_SOURCED`, so this is safe even if a system-wide hook is later restored.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> `/etc/zshrc` and `~/.zshrc` are only read by **interactive** zsh. If the
|
||||
> snippet is present but `zsh -c '…'`, a script, or an IDE terminal still can't
|
||||
> find `nix`, that shell is non-interactive — put the snippet in `~/.zshenv`
|
||||
> instead.
|
||||
> `/etc/zshrc` and `~/.zshrc` are only read by **interactive** zsh. If the snippet is present but `zsh -c '…'`, a script, or an IDE terminal still can't find `nix`, that shell is non-interactive — put the snippet in `~/.zshenv` instead.
|
||||
|
||||
## Git worktrees
|
||||
|
||||
@@ -86,56 +75,39 @@ error:
|
||||
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.
|
||||
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.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
> [!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.
|
||||
> 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
|
||||
- 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`.
|
||||
- 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:
|
||||
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
|
||||
```
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
> [!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.)
|
||||
> `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.
|
||||
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.
|
||||
|
||||
## `wint_t` / `uint32_t` errors from the Nix libc++ headers
|
||||
|
||||
A build that mixes the Nix toolchain with the system SDK fails in libc++ itself,
|
||||
with errors that look nothing like your code:
|
||||
A build that mixes the Nix toolchain with the system SDK fails in libc++ itself, with errors that look nothing like your code:
|
||||
|
||||
```
|
||||
/nix/store/...-libcxx-.../include/c++/v1/cwchar:136:9: error: target of using declaration conflicts with declaration already in scope
|
||||
@@ -145,41 +117,31 @@ with errors that look nothing like your code:
|
||||
error: use of undeclared identifier 'UINT32_C'
|
||||
```
|
||||
|
||||
The give-away is the second path: Nix's libc++ headers are being combined with
|
||||
the **Xcode Command Line Tools** SDK instead of the Nix one.
|
||||
The give-away is the second path: Nix's libc++ headers are being combined with the **Xcode Command Line Tools** SDK instead of the Nix one.
|
||||
|
||||
### Why it happens
|
||||
|
||||
`SDKROOT` and `DEVELOPER_DIR` are what point the toolchain at the Nix SDK, and
|
||||
they are not baked into the compiler — a dev shell gets them from the
|
||||
`apple-sdk` setup hook. CMake, finding neither, asks `xcrun`, which answers with
|
||||
the system SDK. Nix's `libc++` and Apple's headers then declare the same types
|
||||
twice.
|
||||
`SDKROOT` and `DEVELOPER_DIR` are what point the toolchain at the Nix SDK, and they are not baked into the compiler — a dev shell gets them from the `apple-sdk` setup hook. CMake, finding neither, asks `xcrun`, which answers with the system SDK. Nix's `libc++` and Apple's headers then declare the same types twice.
|
||||
|
||||
### Fix
|
||||
|
||||
Run the build from inside the dev shell (`nix develop`), or from an environment
|
||||
that exports both variables. To confirm which SDK a configured build is using:
|
||||
Run the build from inside the dev shell (`nix develop`), or from an environment that exports both variables. To confirm which SDK a configured build is using:
|
||||
|
||||
```bash
|
||||
grep -o '\-isysroot [^ ]*' build/compile_commands.json | sort -u
|
||||
```
|
||||
|
||||
It should print a `/nix/store/...-apple-sdk-*` path. If it prints
|
||||
`/Library/Developer/CommandLineTools/...`, re-configure from within the shell —
|
||||
CMake caches the sysroot, so an existing `build/` directory keeps the wrong one.
|
||||
It should print a `/nix/store/...-apple-sdk-*` path. If it prints `/Library/Developer/CommandLineTools/...`, re-configure from within the shell — CMake caches the sysroot, so an existing `build/` directory keeps the wrong one.
|
||||
|
||||
## `Library not loaded: /nix/store/…` from a binary that used to work
|
||||
|
||||
A binary stops starting after a `nix flake update`, or after
|
||||
`nix-collect-garbage` removes the paths the previous toolchain used:
|
||||
A binary stops starting after a `nix flake update`, or after `nix-collect-garbage` removes the paths the previous toolchain used:
|
||||
|
||||
```
|
||||
dyld[57271]: Library not loaded: /nix/store/…-libresolv-93/lib/libresolv.9.dylib
|
||||
```
|
||||
|
||||
[`bin/check-nix-store-refs.sh`](../../bin/check-nix-store-refs.sh) finds the same
|
||||
thing without having to run anything, and names the file:
|
||||
[`bin/check-nix-store-refs.sh`](../../bin/check-nix-store-refs.sh) finds the same thing without having to run anything, and names the file:
|
||||
|
||||
```
|
||||
$ bin/check-nix-store-refs.sh ~/.conan2-nix
|
||||
@@ -189,9 +151,7 @@ $ bin/check-nix-store-refs.sh ~/.conan2-nix
|
||||
/Users/you/.conan2-nix: checked 135, skipped 2495, 1 with Nix store references.
|
||||
```
|
||||
|
||||
Conan's cache folders are named after a truncated package name plus a hash, so
|
||||
ask Conan which package the offending one belongs to — pass the folder holding
|
||||
the hash, not the file itself:
|
||||
Conan's cache folders are named after a truncated package name plus a hash, so ask Conan which package the offending one belongs to — pass the folder holding the hash, not the file itself:
|
||||
|
||||
```
|
||||
$ conan cache ref ~/.conan2-nix/p/b/c-area24ded30c388c
|
||||
@@ -200,17 +160,9 @@ c-ares/1.34.6#545240bb1c40e2cacd4362d6b8967650:dab5992496abe6d219defb7986ecbf367
|
||||
|
||||
### Why it happens
|
||||
|
||||
The binary records a store path that no longer exists. Nothing we build should:
|
||||
see [Prebuilt packages](./nix.md#prebuilt-packages) for why, and
|
||||
`libresolvSystemStub` in [`nix/darwin.nix`](../../nix/darwin.nix) for the one
|
||||
dependency that needed help to comply.
|
||||
The binary records a store path that no longer exists. Nothing we build should: see [Prebuilt packages](./nix.md#prebuilt-packages) for why, and `libresolvSystemStub` in [`nix/darwin.nix`](../../nix/darwin.nix) for the one dependency that needed help to comply.
|
||||
|
||||
A Conan package ID does not encode the nixpkgs revision, so a package built
|
||||
before that stub existed stays in your local cache and keeps being reused. The
|
||||
dev shell is also what tends to produce one: it is a slightly _less_ isolated
|
||||
build environment than CI's, because `mkShell` puts every tool's headers and
|
||||
libraries on the compiler's search path — which is how c-ares found the Nix
|
||||
`libresolv` in the first place.
|
||||
A Conan package ID does not encode the nixpkgs revision, so a package built before that stub existed stays in your local cache and keeps being reused. The dev shell is also what tends to produce one: it is a slightly _less_ isolated build environment than CI's, because `mkShell` puts every tool's headers and libraries on the compiler's search path — which is how c-ares found the Nix `libresolv` in the first place.
|
||||
|
||||
### Fix
|
||||
|
||||
|
||||
11
docs/build/sanitizers.md
vendored
11
docs/build/sanitizers.md
vendored
@@ -1,11 +1,10 @@
|
||||
# Sanitizer Configuration for Xrpld
|
||||
|
||||
This document explains how to properly configure and run sanitizers (`AddressSanitizer`, `UndefinedBehaviorSanitizer`, `ThreadSanitizer`) with the xrpld project.
|
||||
Corresponding suppression files are located in the `sanitizers/suppressions` directory.
|
||||
This document explains how to properly configure and run sanitizers (`AddressSanitizer`, `UndefinedBehaviorSanitizer`, `ThreadSanitizer`) with the xrpld project. Corresponding suppression files are located in the `sanitizers/suppressions` directory.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
> [!CAUTION]
|
||||
> Do not mix Address and Thread sanitizers - they are incompatible.
|
||||
> Also, we don't yet support MSVC sanitizers, so this is only for Clang/GCC builds.
|
||||
> Do not mix Address and Thread sanitizers - they are incompatible. Also, we don't yet support MSVC sanitizers, so this is only for Clang/GCC builds.
|
||||
|
||||
- [Sanitizer Configuration for Xrpld](#sanitizer-configuration-for-xrpld)
|
||||
- [Building with Sanitizers](#building-with-sanitizers)
|
||||
@@ -35,10 +34,10 @@ Corresponding suppression files are located in the `sanitizers/suppressions` dir
|
||||
Follow the same instructions as mentioned in [BUILD.md](../../BUILD.md) but with the following changes:
|
||||
|
||||
1. Make sure you have a clean build directory.
|
||||
2. Set the `SANITIZERS` environment variable before calling `conan install`. Only set it once.
|
||||
Example: `export SANITIZERS=address,undefinedbehavior`
|
||||
2. Set the `SANITIZERS` environment variable before calling `conan install`. Only set it once. Example: `export SANITIZERS=address,undefinedbehavior`
|
||||
3. Use `--profile:all sanitizers` with Conan to build dependencies with sanitizer instrumentation.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> Building with sanitizer-instrumented dependencies is slower but produces fewer false positives.
|
||||
|
||||
|
||||
@@ -2,277 +2,116 @@
|
||||
|
||||
**This section is a work in progress!!**
|
||||
|
||||
Consensus is the task of reaching agreement within a distributed system in the
|
||||
presence of faulty or even malicious participants. This document outlines the
|
||||
[XRP Ledger Consensus Algorithm](https://arxiv.org/abs/1802.07242)
|
||||
as implemented in [xrpld](https://github.com/XRPLF/rippled), but
|
||||
focuses on its utility as a generic consensus algorithm independent of the
|
||||
detailed mechanics of the XRPL consensus Ledger. Most notably, the algorithm
|
||||
does not require fully synchronous communication between all nodes in the
|
||||
network, or even a fixed network topology, but instead achieves consensus via
|
||||
collectively trusted subnetworks.
|
||||
Consensus is the task of reaching agreement within a distributed system in the presence of faulty or even malicious participants. This document outlines the [XRP Ledger Consensus Algorithm](https://arxiv.org/abs/1802.07242) as implemented in [xrpld](https://github.com/XRPLF/rippled), but focuses on its utility as a generic consensus algorithm independent of the detailed mechanics of the XRPL consensus Ledger. Most notably, the algorithm does not require fully synchronous communication between all nodes in the network, or even a fixed network topology, but instead achieves consensus via collectively trusted subnetworks.
|
||||
|
||||
## Distributed Agreement
|
||||
|
||||
A challenge for distributed systems is reaching agreement on changes in shared
|
||||
state. For the XRPL network, the shared state is the current ledger--account
|
||||
information, account balances, order books and other financial data. We will
|
||||
refer to shared distributed state as a /ledger/ throughout the remainder of this
|
||||
document.
|
||||
A challenge for distributed systems is reaching agreement on changes in shared state. For the XRPL network, the shared state is the current ledger--account information, account balances, order books and other financial data. We will refer to shared distributed state as a /ledger/ throughout the remainder of this document.
|
||||
|
||||

|
||||
|
||||
As shown above, new ledgers are made by applying a set of transactions to the
|
||||
prior ledger. For the XRPL network, transactions include payments,
|
||||
modification of account settings, updates to offers and more.
|
||||
As shown above, new ledgers are made by applying a set of transactions to the prior ledger. For the XRPL network, transactions include payments, modification of account settings, updates to offers and more.
|
||||
|
||||
In a centralized system, generating the next ledger is trivial since there is a
|
||||
single unique arbiter of which transactions to include and how to apply them to
|
||||
a ledger. For decentralized systems, participants must resolve disagreements on
|
||||
the set of transactions to include, the order to apply those transactions, and
|
||||
even the resulting ledger after applying the transactions. This is even more
|
||||
difficult when some participants are faulty or malicious.
|
||||
In a centralized system, generating the next ledger is trivial since there is a single unique arbiter of which transactions to include and how to apply them to a ledger. For decentralized systems, participants must resolve disagreements on the set of transactions to include, the order to apply those transactions, and even the resulting ledger after applying the transactions. This is even more difficult when some participants are faulty or malicious.
|
||||
|
||||
The XRPL network is a decentralized and **trust-full** network. Anyone is free
|
||||
to join and participants are free to choose a subset of peers that are
|
||||
collectively trusted to not collude in an attempt to defraud the participant.
|
||||
Leveraging this network of trust, the XRPL algorithm has two main components.
|
||||
The XRPL network is a decentralized and **trust-full** network. Anyone is free to join and participants are free to choose a subset of peers that are collectively trusted to not collude in an attempt to defraud the participant. Leveraging this network of trust, the XRPL algorithm has two main components.
|
||||
|
||||
- _Consensus_ in which network participants agree on the transactions to apply
|
||||
to a prior ledger, based on the positions of their chosen peers.
|
||||
- _Validation_ in which network participants agree on what ledger was
|
||||
generated, based on the ledgers generated by chosen peers.
|
||||
- _Consensus_ in which network participants agree on the transactions to apply to a prior ledger, based on the positions of their chosen peers.
|
||||
- _Validation_ in which network participants agree on what ledger was generated, based on the ledgers generated by chosen peers.
|
||||
|
||||
These phases are continually repeated to process transactions submitted to the
|
||||
network, generating successive ledgers and giving rise to the blockchain ledger
|
||||
history depicted below. In this diagram, time is flowing to the right, but
|
||||
links between ledgers point backward to the parent. Also note the alternate
|
||||
Ledger 2 that was generated by some participants, but which failed validation
|
||||
and was abandoned.
|
||||
These phases are continually repeated to process transactions submitted to the network, generating successive ledgers and giving rise to the blockchain ledger history depicted below. In this diagram, time is flowing to the right, but links between ledgers point backward to the parent. Also note the alternate Ledger 2 that was generated by some participants, but which failed validation and was abandoned.
|
||||
|
||||

|
||||
|
||||
The remainder of this section describes the Consensus and Validation algorithms
|
||||
in more detail and is meant as a companion guide to understanding the generic
|
||||
implementation in `xrpld`. The document **does not** discuss correctness,
|
||||
fault-tolerance or liveness properties of the algorithms or the full details of
|
||||
how they integrate within `xrpld` to support the XRPL consensus Ledger.
|
||||
The remainder of this section describes the Consensus and Validation algorithms in more detail and is meant as a companion guide to understanding the generic implementation in `xrpld`. The document **does not** discuss correctness, fault-tolerance or liveness properties of the algorithms or the full details of how they integrate within `xrpld` to support the XRPL consensus Ledger.
|
||||
|
||||
## Consensus Overview
|
||||
|
||||
### Definitions
|
||||
|
||||
- The _ledger_ is the shared distributed state. Each ledger has a unique ID to
|
||||
distinguish it from all other ledgers. During consensus, the _previous_,
|
||||
_prior_ or _last-closed_ ledger is the most recent ledger seen by consensus
|
||||
and is the basis upon which it will build the next ledger.
|
||||
- A _transaction_ is an instruction for an atomic change in the ledger state. A
|
||||
unique ID distinguishes a transaction from other transactions.
|
||||
- A _transaction set_ is a set of transactions under consideration by consensus.
|
||||
The goal of consensus is to reach agreement on this set. The generic
|
||||
consensus algorithm does not rely on an ordering of transactions within the
|
||||
set, nor does it specify how to apply a transaction set to a ledger to
|
||||
generate a new ledger. A unique ID distinguishes a set of transactions from
|
||||
all other sets of transactions.
|
||||
- A _node_ is one of the distributed actors running the consensus algorithm. It
|
||||
has a unique ID to distinguish it from all other nodes.
|
||||
- A _peer_ of a node is another node that it has chosen to follow and which it
|
||||
believes will not collude with other chosen peers. The choice of peers is not
|
||||
symmetric, since participants can decide on their chosen sets independently.
|
||||
- A /position/ is the current belief of the next ledger's transaction set and
|
||||
close time. Position can refer to the node's own position or the position of a
|
||||
peer.
|
||||
- A _proposal_ is one of a sequence of positions a node shares during consensus.
|
||||
An initial proposal contains the starting position taken by a node before it
|
||||
considers any peer positions. If a node subsequently updates its position in
|
||||
response to its peers, it will issue an updated proposal. A proposal is
|
||||
uniquely identified by the ID of the proposing node, the ID of the position
|
||||
taken, the ID of the prior ledger the proposal is for, and the sequence number
|
||||
of the proposal.
|
||||
- A _dispute_ is a transaction that is either not part of a node's position or
|
||||
not in a peer's position. During consensus, the node will add or remove
|
||||
disputed transactions from its position based on that transaction's support
|
||||
amongst its peers.
|
||||
- The _ledger_ is the shared distributed state. Each ledger has a unique ID to distinguish it from all other ledgers. During consensus, the _previous_, _prior_ or _last-closed_ ledger is the most recent ledger seen by consensus and is the basis upon which it will build the next ledger.
|
||||
- A _transaction_ is an instruction for an atomic change in the ledger state. A unique ID distinguishes a transaction from other transactions.
|
||||
- A _transaction set_ is a set of transactions under consideration by consensus. The goal of consensus is to reach agreement on this set. The generic consensus algorithm does not rely on an ordering of transactions within the set, nor does it specify how to apply a transaction set to a ledger to generate a new ledger. A unique ID distinguishes a set of transactions from all other sets of transactions.
|
||||
- A _node_ is one of the distributed actors running the consensus algorithm. It has a unique ID to distinguish it from all other nodes.
|
||||
- A _peer_ of a node is another node that it has chosen to follow and which it believes will not collude with other chosen peers. The choice of peers is not symmetric, since participants can decide on their chosen sets independently.
|
||||
- A /position/ is the current belief of the next ledger's transaction set and close time. Position can refer to the node's own position or the position of a peer.
|
||||
- A _proposal_ is one of a sequence of positions a node shares during consensus. An initial proposal contains the starting position taken by a node before it considers any peer positions. If a node subsequently updates its position in response to its peers, it will issue an updated proposal. A proposal is uniquely identified by the ID of the proposing node, the ID of the position taken, the ID of the prior ledger the proposal is for, and the sequence number of the proposal.
|
||||
- A _dispute_ is a transaction that is either not part of a node's position or not in a peer's position. During consensus, the node will add or remove disputed transactions from its position based on that transaction's support amongst its peers.
|
||||
|
||||
Note that most types have an ID as a lightweight identifier of instances of that
|
||||
type. Consensus often operates on the IDs directly since the underlying type is
|
||||
potentially expensive to share over the network. For example, proposal's only
|
||||
contain the ID of the position of a peer. Since many peers likely have the same
|
||||
position, this reduces the need to send the full transaction set multiple times.
|
||||
Instead, a node can request the transaction set from the network if necessary.
|
||||
Note that most types have an ID as a lightweight identifier of instances of that type. Consensus often operates on the IDs directly since the underlying type is potentially expensive to share over the network. For example, proposal's only contain the ID of the position of a peer. Since many peers likely have the same position, this reduces the need to send the full transaction set multiple times. Instead, a node can request the transaction set from the network if necessary.
|
||||
|
||||
### Overview
|
||||
|
||||

|
||||
|
||||
The diagram above is an overview of the consensus process from the perspective
|
||||
of a single participant. Recall that during a single consensus round, a node is
|
||||
trying to agree with its peers on which transactions to apply to its prior
|
||||
ledger when generating the next ledger. It also attempts to agree on the
|
||||
[network time when the ledger closed](#effective_close_time). There are
|
||||
3 main phases to a consensus round:
|
||||
The diagram above is an overview of the consensus process from the perspective of a single participant. Recall that during a single consensus round, a node is trying to agree with its peers on which transactions to apply to its prior ledger when generating the next ledger. It also attempts to agree on the [network time when the ledger closed](#effective_close_time). There are 3 main phases to a consensus round:
|
||||
|
||||
- A call to `startRound` places the node in the `Open` phase. In this phase,
|
||||
the node is waiting for transactions to include in its open ledger.
|
||||
- At some point, the node will `Close` the open ledger and transition to the
|
||||
`Establish` phase. In this phase, the node shares/receives peer proposals on
|
||||
which transactions should be accepted in the closed ledger.
|
||||
- At some point, the node determines it has reached consensus with its peers on
|
||||
which transactions to include. It transitions to the `Accept` phase. In this
|
||||
phase, the node works on applying the transactions to the prior ledger to
|
||||
generate a new closed ledger. Once the new ledger is completed, the node shares
|
||||
the validated ledger hash with the network and makes a call to `startRound` to
|
||||
start the cycle again for the next ledger.
|
||||
- A call to `startRound` places the node in the `Open` phase. In this phase, the node is waiting for transactions to include in its open ledger.
|
||||
- At some point, the node will `Close` the open ledger and transition to the `Establish` phase. In this phase, the node shares/receives peer proposals on which transactions should be accepted in the closed ledger.
|
||||
- At some point, the node determines it has reached consensus with its peers on which transactions to include. It transitions to the `Accept` phase. In this phase, the node works on applying the transactions to the prior ledger to generate a new closed ledger. Once the new ledger is completed, the node shares the validated ledger hash with the network and makes a call to `startRound` to start the cycle again for the next ledger.
|
||||
|
||||
Throughout, a heartbeat timer calls `timerEntry` at a regular frequency to drive
|
||||
the process forward. Although the `startRound` call occurs at arbitrary times
|
||||
based on when the initial round began and the time it takes to apply
|
||||
transactions, the transitions from `Open` to `Establish` and `Establish` to
|
||||
`Accept` only occur during calls to `timerEntry`. Similarly, transactions can
|
||||
arrive at arbitrary times, independent of the heartbeat timer. Transactions
|
||||
received after the `Open` to `Close` transition and not part of peer proposals
|
||||
won't be considered until the next consensus round. They are represented above
|
||||
by the light green triangles.
|
||||
Throughout, a heartbeat timer calls `timerEntry` at a regular frequency to drive the process forward. Although the `startRound` call occurs at arbitrary times based on when the initial round began and the time it takes to apply transactions, the transitions from `Open` to `Establish` and `Establish` to `Accept` only occur during calls to `timerEntry`. Similarly, transactions can arrive at arbitrary times, independent of the heartbeat timer. Transactions received after the `Open` to `Close` transition and not part of peer proposals won't be considered until the next consensus round. They are represented above by the light green triangles.
|
||||
|
||||
Peer proposals are issued by a node during a `timerEntry` call, but since peers
|
||||
do not synchronize `timerEntry` calls, they are received by other peers at
|
||||
arbitrary times. Peer proposals are only considered if received prior to the
|
||||
`Establish` to `Accept` transition, and only if the peer is working on the same
|
||||
prior ledger. Peer proposals received after consensus is reached will not be
|
||||
meaningful and are represented above by the circle with the X in it. Only
|
||||
proposals from chosen peers are considered.
|
||||
Peer proposals are issued by a node during a `timerEntry` call, but since peers do not synchronize `timerEntry` calls, they are received by other peers at arbitrary times. Peer proposals are only considered if received prior to the `Establish` to `Accept` transition, and only if the peer is working on the same prior ledger. Peer proposals received after consensus is reached will not be meaningful and are represented above by the circle with the X in it. Only proposals from chosen peers are considered.
|
||||
|
||||
### Effective Close Time ### {#effective_close_time}
|
||||
|
||||
In addition to agreeing on a transaction set, each consensus round tries to
|
||||
agree on the time the ledger closed. Each node calculates its own close time
|
||||
when it closes the open ledger. This exact close time is rounded to the nearest
|
||||
multiple of the current _effective close time resolution_. It is this
|
||||
_effective close time_ that nodes seek to agree on. This allows servers to
|
||||
derive a common time for a ledger without the need for perfectly synchronized
|
||||
clocks. As depicted below, the 3 pink arrows represent exact close times from 3
|
||||
consensus nodes that round to the same effective close time given the current
|
||||
resolution. The purple arrow represents a peer whose estimate rounds to a
|
||||
different effective close time given the current resolution.
|
||||
In addition to agreeing on a transaction set, each consensus round tries to agree on the time the ledger closed. Each node calculates its own close time when it closes the open ledger. This exact close time is rounded to the nearest multiple of the current _effective close time resolution_. It is this _effective close time_ that nodes seek to agree on. This allows servers to derive a common time for a ledger without the need for perfectly synchronized clocks. As depicted below, the 3 pink arrows represent exact close times from 3 consensus nodes that round to the same effective close time given the current resolution. The purple arrow represents a peer whose estimate rounds to a different effective close time given the current resolution.
|
||||
|
||||

|
||||
|
||||
The effective close time is part of the node's position and is shared with peers
|
||||
in its proposals. Just like the position on the consensus transaction set, a
|
||||
node will update its close time position in response to its peers' effective
|
||||
close time positions. Peers can agree to disagree on the close time, in which
|
||||
case the effective close time is taken as 1 second past the prior close.
|
||||
The effective close time is part of the node's position and is shared with peers in its proposals. Just like the position on the consensus transaction set, a node will update its close time position in response to its peers' effective close time positions. Peers can agree to disagree on the close time, in which case the effective close time is taken as 1 second past the prior close.
|
||||
|
||||
The close time resolution is itself dynamic, decreasing (coarser) resolution in
|
||||
subsequent consensus rounds if nodes are unable to reach consensus on an
|
||||
effective close time and increasing (finer) resolution if nodes consistently
|
||||
reach close time consensus.
|
||||
The close time resolution is itself dynamic, decreasing (coarser) resolution in subsequent consensus rounds if nodes are unable to reach consensus on an effective close time and increasing (finer) resolution if nodes consistently reach close time consensus.
|
||||
|
||||
### Modes
|
||||
|
||||
Internally, a node operates under one of the following consensus modes. Either
|
||||
of the first two modes may be chosen when a consensus round starts.
|
||||
Internally, a node operates under one of the following consensus modes. Either of the first two modes may be chosen when a consensus round starts.
|
||||
|
||||
- _Proposing_ indicates the node is a full-fledged consensus participant. It
|
||||
takes on positions and sends proposals to its peers.
|
||||
- _Observing_ indicates the node is a passive consensus participant. It
|
||||
maintains a position internally, but does not propose that position to its
|
||||
peers. Instead, it receives peer proposals and updates its position
|
||||
to track the majority of its peers. This may be preferred if the node is only
|
||||
being used to track the state of the network or during a start-up phase while
|
||||
it is still synchronizing with the network.
|
||||
- _Proposing_ indicates the node is a full-fledged consensus participant. It takes on positions and sends proposals to its peers.
|
||||
- _Observing_ indicates the node is a passive consensus participant. It maintains a position internally, but does not propose that position to its peers. Instead, it receives peer proposals and updates its position to track the majority of its peers. This may be preferred if the node is only being used to track the state of the network or during a start-up phase while it is still synchronizing with the network.
|
||||
|
||||
The other two modes are set internally during the consensus round when the node
|
||||
believes it is no longer working on the dominant ledger chain based on peer
|
||||
validations. It checks this on every call to `timerEntry`.
|
||||
The other two modes are set internally during the consensus round when the node believes it is no longer working on the dominant ledger chain based on peer validations. It checks this on every call to `timerEntry`.
|
||||
|
||||
- _Wrong Ledger_ indicates the node is not working on the correct prior ledger
|
||||
and does not have it available. It requests that ledger from the network, but
|
||||
continues to work towards consensus this round while waiting. If it had been
|
||||
_proposing_, it will send a special "bow-out" proposal to its peers to indicate
|
||||
its change in mode for the rest of this round. For the duration of the round,
|
||||
it defers to peer positions for determining the consensus outcome as if it
|
||||
were just _observing_.
|
||||
- _Switch Ledger_ indicates that the node has acquired the correct prior ledger
|
||||
from the network. Although it now has the correct prior ledger, the fact that
|
||||
it had the wrong one at some point during this round means it is likely behind
|
||||
and should defer to peer positions for determining the consensus outcome.
|
||||
- _Wrong Ledger_ indicates the node is not working on the correct prior ledger and does not have it available. It requests that ledger from the network, but continues to work towards consensus this round while waiting. If it had been _proposing_, it will send a special "bow-out" proposal to its peers to indicate its change in mode for the rest of this round. For the duration of the round, it defers to peer positions for determining the consensus outcome as if it were just _observing_.
|
||||
- _Switch Ledger_ indicates that the node has acquired the correct prior ledger from the network. Although it now has the correct prior ledger, the fact that it had the wrong one at some point during this round means it is likely behind and should defer to peer positions for determining the consensus outcome.
|
||||
|
||||

|
||||
|
||||
Once either wrong ledger or switch ledger are reached, the node cannot
|
||||
return to proposing or observing until the next consensus round. However,
|
||||
the node could change its view of the correct prior ledger, so going from
|
||||
switch ledger to wrong ledger and back again is possible.
|
||||
Once either wrong ledger or switch ledger are reached, the node cannot return to proposing or observing until the next consensus round. However, the node could change its view of the correct prior ledger, so going from switch ledger to wrong ledger and back again is possible.
|
||||
|
||||
The distinction between the wrong and switched ledger modes arises because a
|
||||
ledger's unique identifier may be known by a node before the ledger itself. This
|
||||
reflects that fact that the data corresponding to a ledger may be large and take
|
||||
time to share over the network, whereas the smaller ID could be shared in a peer
|
||||
validation much more quickly. Distinguishing the two states allows the node to
|
||||
decide how best to generate the next ledger once it declares consensus.
|
||||
The distinction between the wrong and switched ledger modes arises because a ledger's unique identifier may be known by a node before the ledger itself. This reflects that fact that the data corresponding to a ledger may be large and take time to share over the network, whereas the smaller ID could be shared in a peer validation much more quickly. Distinguishing the two states allows the node to decide how best to generate the next ledger once it declares consensus.
|
||||
|
||||
### Phases
|
||||
|
||||
As depicted in the overview diagram, consensus is best viewed as a progression
|
||||
through 3 phases. There are 4 public methods of the generic consensus algorithm
|
||||
that determine this progression
|
||||
As depicted in the overview diagram, consensus is best viewed as a progression through 3 phases. There are 4 public methods of the generic consensus algorithm that determine this progression
|
||||
|
||||
- `startRound` begins a consensus round.
|
||||
- `timerEntry` is called at a regular frequency (`LEDGER_MIN_CLOSE`) and is the
|
||||
only call to consensus that can change the phase from `Open` to `Establish`
|
||||
or `Accept`.
|
||||
- `peerProposal` is called whenever a peer proposal is received and is what
|
||||
allows a node to update its position in a subsequent `timerEntry` call.
|
||||
- `gotTxSet` is called when a transaction set is received from the network. This
|
||||
is typically in response to a prior request from the node to acquire the
|
||||
transaction set corresponding to a disagreeing peer's position.
|
||||
- `timerEntry` is called at a regular frequency (`LEDGER_MIN_CLOSE`) and is the only call to consensus that can change the phase from `Open` to `Establish` or `Accept`.
|
||||
- `peerProposal` is called whenever a peer proposal is received and is what allows a node to update its position in a subsequent `timerEntry` call.
|
||||
- `gotTxSet` is called when a transaction set is received from the network. This is typically in response to a prior request from the node to acquire the transaction set corresponding to a disagreeing peer's position.
|
||||
|
||||
The following subsections describe each consensus phase in more detail and what
|
||||
actions are taken in response to these calls.
|
||||
The following subsections describe each consensus phase in more detail and what actions are taken in response to these calls.
|
||||
|
||||
#### Open
|
||||
|
||||
The `Open` phase is a quiescent period to allow transactions to build up in the
|
||||
node's open ledger. The duration is a trade-off between latency and throughput.
|
||||
A shorter window reduces the latency to generating the next ledger, but also
|
||||
reduces transaction throughput due to fewer transactions accepted into the
|
||||
ledger.
|
||||
The `Open` phase is a quiescent period to allow transactions to build up in the node's open ledger. The duration is a trade-off between latency and throughput. A shorter window reduces the latency to generating the next ledger, but also reduces transaction throughput due to fewer transactions accepted into the ledger.
|
||||
|
||||
A call to `startRound` would forcibly begin the next consensus round, skipping
|
||||
completion of the current round. This is not expected during normal operation.
|
||||
Calls to `peerProposal` or `gotTxSet` simply store the proposal or transaction
|
||||
set for use in the coming `Establish` phase.
|
||||
A call to `startRound` would forcibly begin the next consensus round, skipping completion of the current round. This is not expected during normal operation. Calls to `peerProposal` or `gotTxSet` simply store the proposal or transaction set for use in the coming `Establish` phase.
|
||||
|
||||
A call to `timerEntry` first checks that the node is working on the correct
|
||||
prior ledger. If not, it will update the mode and request the correct ledger.
|
||||
Otherwise, the node checks whether to switch to the `Establish` phase and close
|
||||
the ledger.
|
||||
A call to `timerEntry` first checks that the node is working on the correct prior ledger. If not, it will update the mode and request the correct ledger. Otherwise, the node checks whether to switch to the `Establish` phase and close the ledger.
|
||||
|
||||
##### Ledger Close
|
||||
|
||||
Under normal circumstances, the open ledger period ends when one of the following
|
||||
is true
|
||||
Under normal circumstances, the open ledger period ends when one of the following is true
|
||||
|
||||
- if there are transactions in the open ledger and more than `LEDGER_MIN_CLOSE`
|
||||
have elapsed. This is the typical behavior.
|
||||
- if there are no open transactions and a suitably longer idle interval has
|
||||
elapsed. This increases the opportunity to get some transaction into
|
||||
the next ledger and avoids doing useless work closing an empty ledger.
|
||||
- if more than half the number of prior round peers have already closed or finished
|
||||
this round. This indicates the node is falling behind and needs to catch up.
|
||||
- if there are transactions in the open ledger and more than `LEDGER_MIN_CLOSE` have elapsed. This is the typical behavior.
|
||||
- if there are no open transactions and a suitably longer idle interval has elapsed. This increases the opportunity to get some transaction into the next ledger and avoids doing useless work closing an empty ledger.
|
||||
- if more than half the number of prior round peers have already closed or finished this round. This indicates the node is falling behind and needs to catch up.
|
||||
|
||||
When closing the ledger, the node takes its initial position based on the
|
||||
transactions in the open ledger and uses the current time as
|
||||
its initial close time estimate. If in the proposing mode, the node shares its
|
||||
initial position with peers. Now that the node has taken a position, it will
|
||||
consider any peer positions for this round that arrived earlier. The node
|
||||
generates disputed transactions for each transaction not in common with a peer's
|
||||
position. The node also records the vote of each peer for each disputed
|
||||
transaction.
|
||||
When closing the ledger, the node takes its initial position based on the transactions in the open ledger and uses the current time as its initial close time estimate. If in the proposing mode, the node shares its initial position with peers. Now that the node has taken a position, it will consider any peer positions for this round that arrived earlier. The node generates disputed transactions for each transaction not in common with a peer's position. The node also records the vote of each peer for each disputed transaction.
|
||||
|
||||
In the example below, we suppose our node has closed with transactions 1,2 and 3. It creates disputes
|
||||
for transactions 2,3 and 4, since at least one peer position differs on each.
|
||||
In the example below, we suppose our node has closed with transactions 1,2 and 3. It creates disputes for transactions 2,3 and 4, since at least one peer position differs on each.
|
||||
|
||||
##### disputes ##### {#disputes_image}
|
||||
|
||||
@@ -280,119 +119,55 @@ for transactions 2,3 and 4, since at least one peer position differs on each.
|
||||
|
||||
#### Establish
|
||||
|
||||
The establish phase is the active period of consensus in which the node
|
||||
exchanges proposals with peers in an attempt to reach agreement on the consensus
|
||||
transactions and effective close time.
|
||||
The establish phase is the active period of consensus in which the node exchanges proposals with peers in an attempt to reach agreement on the consensus transactions and effective close time.
|
||||
|
||||
A call to `startRound` would forcibly begin the next consensus round, skipping
|
||||
completion of the current round. This is not expected during normal operation.
|
||||
Calls to `peerProposal` or `gotTxSet` that reflect new positions will generate
|
||||
disputed transactions for any new disagreements and will update the peer's vote
|
||||
for all disputed transactions.
|
||||
A call to `startRound` would forcibly begin the next consensus round, skipping completion of the current round. This is not expected during normal operation. Calls to `peerProposal` or `gotTxSet` that reflect new positions will generate disputed transactions for any new disagreements and will update the peer's vote for all disputed transactions.
|
||||
|
||||
A call to `timerEntry` first checks that the node is working from the correct
|
||||
prior ledger. If not, the node will update the mode and request the correct
|
||||
ledger. Otherwise, the node updates the node's position and considers whether
|
||||
to switch to the `Accepted` phase and declare consensus reached. However, at
|
||||
least `LEDGER_MIN_CONSENSUS` time must have elapsed before doing either. This
|
||||
allows peers an opportunity to take an initial position and share it.
|
||||
A call to `timerEntry` first checks that the node is working from the correct prior ledger. If not, the node will update the mode and request the correct ledger. Otherwise, the node updates the node's position and considers whether to switch to the `Accepted` phase and declare consensus reached. However, at least `LEDGER_MIN_CONSENSUS` time must have elapsed before doing either. This allows peers an opportunity to take an initial position and share it.
|
||||
|
||||
##### Update Position
|
||||
|
||||
In order to achieve consensus, the node is looking for a transaction set that is
|
||||
supported by a super-majority of peers. The node works towards this set by
|
||||
adding or removing disputed transactions from its position based on an
|
||||
increasing threshold for inclusion.
|
||||
In order to achieve consensus, the node is looking for a transaction set that is supported by a super-majority of peers. The node works towards this set by adding or removing disputed transactions from its position based on an increasing threshold for inclusion.
|
||||
|
||||

|
||||
|
||||
By starting with a lower threshold, a node initially allows a wide set of
|
||||
transactions into its position. If the establish round continues and the node is
|
||||
"stuck", a higher threshold can focus on accepting transactions with the most
|
||||
support. The constants that define the thresholds and durations at which the
|
||||
thresholds change are given by `AV_XXX_CONSENSUS_PCT` and
|
||||
`AV_XXX_CONSENSUS_TIME` respectively, where `XXX` is `INIT`,`MID`,`LATE` and
|
||||
`STUCK`. The effective close time position is updated using the same
|
||||
thresholds.
|
||||
By starting with a lower threshold, a node initially allows a wide set of transactions into its position. If the establish round continues and the node is "stuck", a higher threshold can focus on accepting transactions with the most support. The constants that define the thresholds and durations at which the thresholds change are given by `AV_XXX_CONSENSUS_PCT` and `AV_XXX_CONSENSUS_TIME` respectively, where `XXX` is `INIT`,`MID`,`LATE` and `STUCK`. The effective close time position is updated using the same thresholds.
|
||||
|
||||
Given the [example disputes above](#disputes_image) and an initial threshold
|
||||
of 50%, our node would retain its position since transaction 1 was not in
|
||||
dispute and transactions 2 and 3 have 75% support. Since its position did not
|
||||
change, it would not need to send a new proposal to peers. Peer C would not
|
||||
change either. Peer A would add transaction 3 to its position and Peer B would
|
||||
remove transaction 4 from its position; both would then send an updated
|
||||
position.
|
||||
Given the [example disputes above](#disputes_image) and an initial threshold of 50%, our node would retain its position since transaction 1 was not in dispute and transactions 2 and 3 have 75% support. Since its position did not change, it would not need to send a new proposal to peers. Peer C would not change either. Peer A would add transaction 3 to its position and Peer B would remove transaction 4 from its position; both would then send an updated position.
|
||||
|
||||
Conversely, if the diagram reflected a later call to =timerEntry= that occurs in
|
||||
the stuck region with a threshold of say 95%, our node would remove transactions
|
||||
2 and 3 from its candidate set and send an updated position. Likewise, all the
|
||||
other peers would end up with only transaction 1 in their position.
|
||||
Conversely, if the diagram reflected a later call to =timerEntry= that occurs in the stuck region with a threshold of say 95%, our node would remove transactions 2 and 3 from its candidate set and send an updated position. Likewise, all the other peers would end up with only transaction 1 in their position.
|
||||
|
||||
Lastly, if our node were not in the proposing mode, it would not include its own
|
||||
vote and just take the majority (>50%) position of its peers. In this example,
|
||||
our node would maintain its position of transactions 1, 2 and 3.
|
||||
Lastly, if our node were not in the proposing mode, it would not include its own vote and just take the majority (>50%) position of its peers. In this example, our node would maintain its position of transactions 1, 2 and 3.
|
||||
|
||||
##### Checking Consensus
|
||||
|
||||
After updating its position, the node checks for supermajority agreement with
|
||||
its peers on its current position. This agreement is of the exact transaction
|
||||
set, not just the support of individual transactions. That is, if our position
|
||||
is a subset of a peer's position, that counts as a disagreement. Also recall
|
||||
that effective close time agreement allows a supermajority of participants
|
||||
agreeing to disagree.
|
||||
After updating its position, the node checks for supermajority agreement with its peers on its current position. This agreement is of the exact transaction set, not just the support of individual transactions. That is, if our position is a subset of a peer's position, that counts as a disagreement. Also recall that effective close time agreement allows a supermajority of participants agreeing to disagree.
|
||||
|
||||
Consensus is declared when the following 3 clauses are true:
|
||||
|
||||
- `LEDGER_MIN_CONSENSUS` time has elapsed in the establish phase
|
||||
- At least 75% of the prior round proposers have proposed OR this establish
|
||||
phase is `LEDGER_MIN_CONSENSUS` longer than the last round's establish phase
|
||||
- At least 75% of the prior round proposers have proposed OR this establish phase is `LEDGER_MIN_CONSENSUS` longer than the last round's establish phase
|
||||
- `minimumConsensusPercentage` of ourself and our peers share the same position
|
||||
|
||||
The middle condition ensures slower peers have a chance to share positions, but
|
||||
prevents waiting too long on peers that have disconnected. Additionally, a node
|
||||
can declare that consensus has moved on if `minimumConsensusPercentage` peers
|
||||
have sent validations and moved on to the next ledger. This outcome indicates
|
||||
the node has fallen behind its peers and needs to catch up.
|
||||
The middle condition ensures slower peers have a chance to share positions, but prevents waiting too long on peers that have disconnected. Additionally, a node can declare that consensus has moved on if `minimumConsensusPercentage` peers have sent validations and moved on to the next ledger. This outcome indicates the node has fallen behind its peers and needs to catch up.
|
||||
|
||||
If a node is not proposing, it does not include its own position when
|
||||
calculating the percent of agreeing participants but otherwise follows the above
|
||||
logic.
|
||||
If a node is not proposing, it does not include its own position when calculating the percent of agreeing participants but otherwise follows the above logic.
|
||||
|
||||
##### Accepting Consensus
|
||||
|
||||
Once consensus is reached (or moved on), the node switches to the `Accept` phase
|
||||
and signals to the implementing code that the round is complete. That code is
|
||||
responsible for using the consensus transaction set to generate the next ledger
|
||||
and calling `startRound` to begin the next round. The implementation has total
|
||||
freedom on ordering transactions, deciding what to do if consensus moved on,
|
||||
determining whether to retry or abandon local transactions that did not make the
|
||||
consensus set and updating any internal state based on the consensus progress.
|
||||
Once consensus is reached (or moved on), the node switches to the `Accept` phase and signals to the implementing code that the round is complete. That code is responsible for using the consensus transaction set to generate the next ledger and calling `startRound` to begin the next round. The implementation has total freedom on ordering transactions, deciding what to do if consensus moved on, determining whether to retry or abandon local transactions that did not make the consensus set and updating any internal state based on the consensus progress.
|
||||
|
||||
#### Accept
|
||||
|
||||
The `Accept` phase is the terminal phase of the consensus algorithm. Calls to
|
||||
`timerEntry`, `peerProposal` and `gotTxSet` will not change the internal
|
||||
consensus state while in the accept phase. The expectation is that the
|
||||
application specific code is working to generate the new ledger based on the
|
||||
consensus outcome. Once complete, that code should make a call to `startRound`
|
||||
to kick off the next consensus round. The `startRound` call includes the new
|
||||
prior ledger, prior ledger ID and whether the round should begin in the
|
||||
proposing or observing mode. After setting some initial state, the phase
|
||||
transitions to `Open`. The node will also check if the provided prior ledger
|
||||
and ID are correct, updating the mode and requesting the proper ledger from the
|
||||
network if necessary.
|
||||
The `Accept` phase is the terminal phase of the consensus algorithm. Calls to `timerEntry`, `peerProposal` and `gotTxSet` will not change the internal consensus state while in the accept phase. The expectation is that the application specific code is working to generate the new ledger based on the consensus outcome. Once complete, that code should make a call to `startRound` to kick off the next consensus round. The `startRound` call includes the new prior ledger, prior ledger ID and whether the round should begin in the proposing or observing mode. After setting some initial state, the phase transitions to `Open`. The node will also check if the provided prior ledger and ID are correct, updating the mode and requesting the proper ledger from the network if necessary.
|
||||
|
||||
## Consensus Type Requirements
|
||||
|
||||
The consensus type requirements are given below as minimal implementation stubs.
|
||||
Actual implementations would augment these stubs with members appropriate for
|
||||
managing the details of transactions and ledgers within the larger application
|
||||
framework.
|
||||
The consensus type requirements are given below as minimal implementation stubs. Actual implementations would augment these stubs with members appropriate for managing the details of transactions and ledgers within the larger application framework.
|
||||
|
||||
### Transaction
|
||||
|
||||
The transaction type `Tx` encapsulates a single transaction under consideration
|
||||
by consensus.
|
||||
The transaction type `Tx` encapsulates a single transaction under consideration by consensus.
|
||||
|
||||
```{.cpp}
|
||||
struct Tx
|
||||
@@ -406,10 +181,7 @@ struct Tx
|
||||
|
||||
### Transaction Set
|
||||
|
||||
The transaction set type `TxSet` represents a set of `Tx`s that are collectively
|
||||
under consideration by consensus. A `TxSet` can be compared against other `TxSet`s
|
||||
(typically from peers) and can be modified to add or remove transactions via
|
||||
the mutable subtype.
|
||||
The transaction set type `TxSet` represents a set of `Tx`s that are collectively under consideration by consensus. A `TxSet` can be compared against other `TxSet`s (typically from peers) and can be modified to add or remove transactions via the mutable subtype.
|
||||
|
||||
```{.cpp}
|
||||
struct TxSet
|
||||
@@ -446,12 +218,7 @@ struct TxSet
|
||||
|
||||
### Ledger
|
||||
|
||||
The `Ledger` type represents the state shared amongst the
|
||||
distributed participants. Notice that the details of how the next ledger is
|
||||
generated from the prior ledger and the consensus accepted transaction set is
|
||||
not part of the interface. Within the generic code, this type is primarily used
|
||||
to know that peers are working on the same tip of the ledger chain and to
|
||||
provide some basic timing data for consensus.
|
||||
The `Ledger` type represents the state shared amongst the distributed participants. Notice that the details of how the next ledger is generated from the prior ledger and the consensus accepted transaction set is not part of the interface. Within the generic code, this type is primarily used to know that peers are working on the same tip of the ledger chain and to provide some basic timing data for consensus.
|
||||
|
||||
```{.cpp}
|
||||
struct Ledger
|
||||
@@ -485,9 +252,7 @@ struct Ledger
|
||||
|
||||
### PeerProposal
|
||||
|
||||
The `PeerProposal` type represents the signed position taken
|
||||
by a peer during consensus. The only type requirement is owning an instance of a
|
||||
generic `ConsensusProposal`.
|
||||
The `PeerProposal` type represents the signed position taken by a peer during consensus. The only type requirement is owning an instance of a generic `ConsensusProposal`.
|
||||
|
||||
```{.cpp}
|
||||
// Represents our proposed position or a peer's proposed position
|
||||
@@ -508,11 +273,7 @@ struct PeerPosition
|
||||
|
||||
### Generic Consensus Interface
|
||||
|
||||
The generic `Consensus` relies on `Adaptor` template class to implement a set
|
||||
of helper functions that plug the consensus algorithm into a specific application.
|
||||
The `Adaptor` class also defines the types above needed by the algorithm. Below
|
||||
are excerpts of the generic consensus implementation and of helper types that will
|
||||
interact with the concrete implementing class.
|
||||
The generic `Consensus` relies on `Adaptor` template class to implement a set of helper functions that plug the consensus algorithm into a specific application. The `Adaptor` class also defines the types above needed by the algorithm. Below are excerpts of the generic consensus implementation and of helper types that will interact with the concrete implementing class.
|
||||
|
||||
```{.cpp}
|
||||
// Represents a transaction under dispute this round
|
||||
@@ -653,26 +414,11 @@ struct Adaptor
|
||||
};
|
||||
```
|
||||
|
||||
The implementing class hides many details of the peer communication
|
||||
model from the generic code.
|
||||
The implementing class hides many details of the peer communication model from the generic code.
|
||||
|
||||
- The `share` member functions are responsible for sharing the given type with a
|
||||
node's peers, but are agnostic to the mechanism. Ideally, messages are delivered
|
||||
faster than `LEDGER_GRANULARITY`.
|
||||
- The generic code does not specify how transactions are submitted by clients,
|
||||
propagated through the network or stored in the open ledger. Indeed, the open
|
||||
ledger is only conceptual from the perspective of the generic code---the
|
||||
initial position and transaction set are opaquely generated in a
|
||||
`Consensus::Result` instance returned from the `onClose` callback.
|
||||
- The calls to `acquireLedger` and `acquireTxSet` only have non-trivial return
|
||||
if the ledger or transaction set of interest is available. The implementing
|
||||
class is free to block while acquiring, or return the empty option while
|
||||
servicing the request asynchronously. Due to legacy reasons, the two calls
|
||||
are not symmetric. `acquireTxSet` requires the host application to call
|
||||
`gotTxSet` when an asynchronous `acquire` completes. Conversely,
|
||||
`acquireLedger` will be called again later by the consensus code if it still
|
||||
desires the ledger with the hope that the asynchronous acquisition is
|
||||
complete.
|
||||
- The `share` member functions are responsible for sharing the given type with a node's peers, but are agnostic to the mechanism. Ideally, messages are delivered faster than `LEDGER_GRANULARITY`.
|
||||
- The generic code does not specify how transactions are submitted by clients, propagated through the network or stored in the open ledger. Indeed, the open ledger is only conceptual from the perspective of the generic code---the initial position and transaction set are opaquely generated in a `Consensus::Result` instance returned from the `onClose` callback.
|
||||
- The calls to `acquireLedger` and `acquireTxSet` only have non-trivial return if the ledger or transaction set of interest is available. The implementing class is free to block while acquiring, or return the empty option while servicing the request asynchronously. Due to legacy reasons, the two calls are not symmetric. `acquireTxSet` requires the host application to call `gotTxSet` when an asynchronous `acquire` completes. Conversely, `acquireLedger` will be called again later by the consensus code if it still desires the ledger with the hope that the asynchronous acquisition is complete.
|
||||
|
||||
## Validation
|
||||
|
||||
|
||||
@@ -1,29 +1,20 @@
|
||||
# Installing xrpld 3.3.0 and earlier
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
> [!IMPORTANT]
|
||||
> These instructions apply to xrpld 3.3.0 and earlier, published to
|
||||
> repos.ripple.com.
|
||||
> For later releases see [install.md](./install.md).
|
||||
> These instructions apply to xrpld 3.3.0 and earlier, published to repos.ripple.com. For later releases see [install.md](./install.md).
|
||||
|
||||
This document contains instructions for installing xrpld.
|
||||
The APT package manager is common on Debian-based Linux distributions like
|
||||
Ubuntu,
|
||||
while the YUM package manager is common on Red Hat-based Linux distributions
|
||||
like CentOS.
|
||||
Installing from source is an option for all platforms,
|
||||
and the only supported option for installing custom builds.
|
||||
This document contains instructions for installing xrpld. The APT package manager is common on Debian-based Linux distributions like Ubuntu, while the YUM package manager is common on Red Hat-based Linux distributions like CentOS. Installing from source is an option for all platforms, and the only supported option for installing custom builds.
|
||||
|
||||
## From source
|
||||
|
||||
From a source build, you can install xrpld and libxrpl using CMake's
|
||||
`--install` mode:
|
||||
From a source build, you can install xrpld and libxrpl using CMake's `--install` mode:
|
||||
|
||||
```
|
||||
cmake --install . --prefix /opt/local
|
||||
```
|
||||
|
||||
The default [prefix][1] is typically `/usr/local` on Linux and macOS and
|
||||
`C:/Program Files/xrpld` on Windows.
|
||||
The default [prefix][1] is typically `/usr/local` on Linux and macOS and `C:/Program Files/xrpld` on Windows.
|
||||
|
||||
[1]: https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_PREFIX.html
|
||||
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
# Installing xrpld
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> These instructions apply to packages published from 2026-08-19 onwards.
|
||||
> For xrpld 3.3.0 and earlier see [install-legacy.md](./install-legacy.md).
|
||||
> These instructions apply to packages published from 2026-08-19 onwards. For xrpld 3.3.0 and earlier see [install-legacy.md](./install-legacy.md).
|
||||
|
||||
`xrpld` is published as DEB and RPM packages for 64-bit x86 Linux.
|
||||
Use APT on Debian-based distributions such as Debian and Ubuntu,
|
||||
and YUM on Red Hat-based distributions such as RHEL, AlmaLinux, and Rocky Linux.
|
||||
To build from source instead, see [BUILD.md](../BUILD.md).
|
||||
`xrpld` is published as DEB and RPM packages for 64-bit x86 Linux. Use APT on Debian-based distributions such as Debian and Ubuntu, and YUM on Red Hat-based distributions such as RHEL, AlmaLinux, and Rocky Linux. To build from source instead, see [BUILD.md](../BUILD.md).
|
||||
|
||||
## Release channels
|
||||
|
||||
@@ -20,13 +17,11 @@ Packages are published to four channels:
|
||||
|
||||
See [Publishing packages](../package/README.md#publishing-packages) for how channels are produced.
|
||||
|
||||
The instructions below use `stable`.
|
||||
To follow another channel, replace `stable` with its name
|
||||
wherever it appears in the repository configuration.
|
||||
The instructions below use `stable`. To follow another channel, replace `stable` with its name wherever it appears in the repository configuration.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
> [!WARNING]
|
||||
> Channels other than `stable` may be broken at any time.
|
||||
> Do not use them for production servers.
|
||||
> Channels other than `stable` may be broken at any time. Do not use them for production servers.
|
||||
|
||||
## Install the xrpld package
|
||||
|
||||
@@ -103,8 +98,7 @@ wherever it appears in the repository configuration.
|
||||
REPOFILE
|
||||
```
|
||||
|
||||
`gpgcheck=1` verifies each package against the key above.
|
||||
`repo_gpgcheck=1` verifies the repository metadata, which the server signs with the same key.
|
||||
`gpgcheck=1` verifies each package against the key above. `repo_gpgcheck=1` verifies the repository metadata, which the server signs with the same key.
|
||||
|
||||
3. Install the `xrpld` package:
|
||||
|
||||
@@ -114,8 +108,7 @@ wherever it appears in the repository configuration.
|
||||
|
||||
## The xrpld service
|
||||
|
||||
Both package managers install a systemd unit and enable it, so `xrpld` starts on boot.
|
||||
Check whether it is already running:
|
||||
Both package managers install a systemd unit and enable it, so `xrpld` starts on boot. Check whether it is already running:
|
||||
|
||||
```bash
|
||||
systemctl status xrpld.service
|
||||
@@ -129,8 +122,7 @@ sudo systemctl start xrpld.service
|
||||
|
||||
### Optional: binding to privileged ports
|
||||
|
||||
To serve incoming API requests on port 80 or 443, grant the service the capability to bind them.
|
||||
You must also update the config file's port settings.
|
||||
To serve incoming API requests on port 80 or 443, grant the service the capability to bind them. You must also update the config file's port settings.
|
||||
|
||||
```bash
|
||||
sudo install -d -m 0755 /etc/systemd/system/xrpld.service.d
|
||||
|
||||
@@ -15,8 +15,7 @@ The module xrpl/basics should contain no dependencies on other modules.
|
||||
- `std::list`
|
||||
- For ordered containers with inserts and erases to the middle.
|
||||
- For containers with iterators stable over insert and erase.
|
||||
- Generally slower and bigger than `std::vector` or `std::deque` except for
|
||||
those cases.
|
||||
- Generally slower and bigger than `std::vector` or `std::deque` except for those cases.
|
||||
|
||||
- `std::set`
|
||||
- For sorted containers.
|
||||
@@ -26,9 +25,7 @@ The module xrpl/basics should contain no dependencies on other modules.
|
||||
- For "small" sets, `std::set` might be faster and smaller.
|
||||
|
||||
- `xrpl::hardened_hash_set`
|
||||
- For data sets where the key could be manipulated by an attacker
|
||||
in an attempt to mount an algorithmic complexity attack: see
|
||||
http://en.wikipedia.org/wiki/Algorithmic_complexity_attack
|
||||
- For data sets where the key could be manipulated by an attacker in an attempt to mount an algorithmic complexity attack: see http://en.wikipedia.org/wiki/Algorithmic_complexity_attack
|
||||
|
||||
The following container is deprecated
|
||||
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
# Consensus
|
||||
|
||||
This directory contains the implementation of a
|
||||
generic consensus algorithm. The implementation
|
||||
follows a CRTP design, requiring client code to implement
|
||||
specific functions and types to use consensus in their
|
||||
application. The interface is undergoing refactoring and
|
||||
is not yet finalized.
|
||||
This directory contains the implementation of a generic consensus algorithm. The implementation follows a CRTP design, requiring client code to implement specific functions and types to use consensus in their application. The interface is undergoing refactoring and is not yet finalized.
|
||||
|
||||
@@ -34,10 +34,7 @@ enum class HashRouterFlags : std::uint16_t {
|
||||
PRIVATE4 = 0x0800,
|
||||
// Used in EscrowFinish.cpp
|
||||
PRIVATE5 = 0x1000,
|
||||
PRIVATE6 = 0x2000,
|
||||
// Used in apply.cpp
|
||||
PRIVATE7 = 0x4000,
|
||||
PRIVATE8 = 0x8000
|
||||
PRIVATE6 = 0x2000
|
||||
};
|
||||
|
||||
constexpr HashRouterFlags
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# SSLUtil
|
||||
|
||||
This module exposes the OpenSSL headers and provides utilities to
|
||||
operate with OpenSSL / BIGNUM objects.
|
||||
This module exposes the OpenSSL headers and provides utilities to operate with OpenSSL / BIGNUM objects.
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <xrpl/protocol/STVector256.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
@@ -33,6 +34,32 @@ checkExpired(SLE const& sleCredential, NetClock::time_point const& closed);
|
||||
[[nodiscard]] TER
|
||||
deleteSLE(ApplyView& view, SLE::ref sleCredential, beast::Journal j);
|
||||
|
||||
/**
|
||||
* @brief Remove credentials pinned to a pseudo-account's owner directory.
|
||||
*
|
||||
* Cleans up credentials that were linked to a pseudo-account (Vault, LoanBroker,
|
||||
* AMM), which such an account can neither accept nor delete. Only credentials
|
||||
* are removed; every other object is left in place. The walk visits at most
|
||||
* @p maxNodesToDelete directory entries and charges the ones it leaves alone
|
||||
* against that budget too, so a directory holding other objects yields fewer
|
||||
* than @p maxNodesToDelete deletions. On reaching the bound the result is
|
||||
* `tecINCOMPLETE` and the caller must propagate it so a later transaction
|
||||
* resumes.
|
||||
*
|
||||
* @param view Mutable ledger view.
|
||||
* @param pseudoAcct The pseudo-account whose directory is cleaned.
|
||||
* @param maxNodesToDelete Upper bound on directory entries processed in one call.
|
||||
* @param j Journal for diagnostics.
|
||||
* @return tesSUCCESS once no credentials remain, tecINCOMPLETE if the bound was
|
||||
* reached, or a deletion error.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
deletePseudoAccountCredentials(
|
||||
ApplyView& view,
|
||||
AccountID const& pseudoAcct,
|
||||
std::uint16_t maxNodesToDelete,
|
||||
beast::Journal j);
|
||||
|
||||
// Amendment and parameters checks for sfCredentialIDs field
|
||||
NotTEC
|
||||
checkFields(STTx const& tx, Rules const& rules, beast::Journal j);
|
||||
|
||||
@@ -239,13 +239,8 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* XRP and the issuer itself are always tesSUCCESS. Otherwise, after
|
||||
* fixCleanup3_4_0, an existing trust line returns tecDUPLICATE without
|
||||
* consulting issuer freeze or DefaultRipple; both still apply on the create
|
||||
* path (DefaultRipple off is terNO_RIPPLE). canAddHolding() ignores existing
|
||||
* holdings, so transactors that may create a holding in doApply should gate
|
||||
* their preclaim call on it: after the amendment only when no holding
|
||||
* exists, before it always.
|
||||
* Any transactors that call addEmptyHolding() in doApply must call
|
||||
* canAddHolding() in preflight with the same View and Asset
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
addEmptyHolding(
|
||||
|
||||
@@ -38,12 +38,6 @@ enum class FreezeHandling { IgnoreFreeze, ZeroIfFrozen };
|
||||
*/
|
||||
enum class AuthHandling { IgnoreAuth, ZeroIfUnauthorized };
|
||||
|
||||
/**
|
||||
* Controls whether the recipient owner-reserve check is enforced when
|
||||
* auto-creating a trustline or MPToken during AMMWithdraw or AMMClawback.
|
||||
*/
|
||||
enum class ReserveHandling : bool { EnforceReserve, IgnoreReserve };
|
||||
|
||||
/**
|
||||
* Controls whether to include the account's full spendable balance
|
||||
*/
|
||||
@@ -325,12 +319,6 @@ transferRate(ReadView const& view, STAmount const& amount);
|
||||
[[nodiscard]] TER
|
||||
canAddHolding(ReadView const& view, Asset const& asset);
|
||||
|
||||
/**
|
||||
* True if the account already holds this asset (or is the issuer / XRP).
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
holdingExists(ReadView const& view, AccountID const& account, Asset const& asset);
|
||||
|
||||
[[nodiscard]] TER
|
||||
addEmptyHolding(
|
||||
ApplyViewContext ctx,
|
||||
|
||||
@@ -7,16 +7,11 @@
|
||||
|
||||
## Introduction
|
||||
|
||||
A `NodeObject` is a simple object that the Ledger uses to store entries. It is
|
||||
comprised of a type, a hash and a blob. It can be uniquely
|
||||
identified by the hash, which is a 256 bit hash of the blob. The blob is a
|
||||
variable length block of serialized data. The type identifies what the blob
|
||||
contains. The fields are as follows:
|
||||
A `NodeObject` is a simple object that the Ledger uses to store entries. It is comprised of a type, a hash and a blob. It can be uniquely identified by the hash, which is a 256 bit hash of the blob. The blob is a variable length block of serialized data. The type identifies what the blob contains. The fields are as follows:
|
||||
|
||||
- `mType`
|
||||
|
||||
An enumeration that determines what the blob holds. There are four
|
||||
different types of objects stored.
|
||||
An enumeration that determines what the blob holds. There are four different types of objects stored.
|
||||
|
||||
- **ledger**
|
||||
|
||||
@@ -50,19 +45,11 @@ A blob containing the payload. Stored in the following format.
|
||||
|
||||
---
|
||||
|
||||
The `NodeStore` provides an interface that stores, in a persistent database, a
|
||||
collection of NodeObjects that xrpld uses as its primary representation of
|
||||
ledger entries. All ledger entries are stored as NodeObjects and as such, need
|
||||
to be persisted between launches. If a NodeObject is accessed and is not in
|
||||
memory, it will be retrieved from the database.
|
||||
The `NodeStore` provides an interface that stores, in a persistent database, a collection of NodeObjects that xrpld uses as its primary representation of ledger entries. All ledger entries are stored as NodeObjects and as such, need to be persisted between launches. If a NodeObject is accessed and is not in memory, it will be retrieved from the database.
|
||||
|
||||
## Backend
|
||||
|
||||
The `NodeStore` implementation provides the `Backend` abstract interface,
|
||||
which lets different key/value databases to be chosen at run-time. This allows
|
||||
experimentation with different engines. Improvements in the performance of the
|
||||
NodeStore are a constant area of research. The database can be specified in
|
||||
the configuration file [node_db] section as follows.
|
||||
The `NodeStore` implementation provides the `Backend` abstract interface, which lets different key/value databases to be chosen at run-time. This allows experimentation with different engines. Improvements in the performance of the NodeStore are a constant area of research. The database can be specified in the configuration file [node_db] section as follows.
|
||||
|
||||
One or more lines of key / value pairs
|
||||
|
||||
@@ -106,75 +93,30 @@ Choices for 'compression'
|
||||
|
||||
# Benchmarks
|
||||
|
||||
The `NodeStore.Timing` test is used to execute a set of read/write workloads to
|
||||
compare current available nodestore backends. It can be executed with:
|
||||
The `NodeStore.Timing` test is used to execute a set of read/write workloads to compare current available nodestore backends. It can be executed with:
|
||||
|
||||
```
|
||||
$xrpld --unittest=NodeStoreTiming
|
||||
```
|
||||
|
||||
It is also possible to use alternate DB config params by passing config strings
|
||||
as `--unittest-arg`.
|
||||
It is also possible to use alternate DB config params by passing config strings as `--unittest-arg`.
|
||||
|
||||
## Addendum
|
||||
|
||||
The discussion below refers to a `RocksDBQuick` backend that has since been
|
||||
removed from the code as it was not working and not maintained. That backend
|
||||
primarily used one of the several rocks `Optimize*` methods to setup the
|
||||
majority of the DB options/params, whereas the primary RocksDB backend exposes
|
||||
many of the available config options directly. The code for RocksDBQuick can be
|
||||
found in versions of this repo 1.2 and earlier if you need to refer back to it.
|
||||
The conclusions below date from about 2014 and may need revisiting based on
|
||||
newer versions of RocksDB (TBD).
|
||||
The discussion below refers to a `RocksDBQuick` backend that has since been removed from the code as it was not working and not maintained. That backend primarily used one of the several rocks `Optimize*` methods to setup the majority of the DB options/params, whereas the primary RocksDB backend exposes many of the available config options directly. The code for RocksDBQuick can be found in versions of this repo 1.2 and earlier if you need to refer back to it. The conclusions below date from about 2014 and may need revisiting based on newer versions of RocksDB (TBD).
|
||||
|
||||
## Discussion
|
||||
|
||||
RocksDBQuickFactory is intended to provide a testbed for comparing potential
|
||||
rocksdb performance with the existing recommended configuration in xrpld.cfg.
|
||||
Through various executions and profiling some conclusions are presented below.
|
||||
RocksDBQuickFactory is intended to provide a testbed for comparing potential rocksdb performance with the existing recommended configuration in xrpld.cfg. Through various executions and profiling some conclusions are presented below.
|
||||
|
||||
- If the write ahead log is enabled, insert speed soon clogs up under load. The
|
||||
BatchWriter class intends to stop this from blocking the main threads by queuing
|
||||
up writes and running them in a separate thread. However, rocksdb already has
|
||||
separate threads dedicated to flushing the memtable to disk and the memtable is
|
||||
itself an in-memory queue. The result is two queues with a guarantee of
|
||||
durability in between. However if the memtable was used as the sole queue and
|
||||
the rocksdb::Flush() call was manually triggered at opportune moments, possibly
|
||||
just after ledger close, then that would provide similar, but more predictable
|
||||
guarantees. It would also remove an unneeded thread and unnecessary memory
|
||||
usage. An alternative point of view is that because there will always be many
|
||||
other xrpld instances running there is no need for such guarantees. The nodes
|
||||
will always be available from another peer.
|
||||
- If the write ahead log is enabled, insert speed soon clogs up under load. The BatchWriter class intends to stop this from blocking the main threads by queuing up writes and running them in a separate thread. However, rocksdb already has separate threads dedicated to flushing the memtable to disk and the memtable is itself an in-memory queue. The result is two queues with a guarantee of durability in between. However if the memtable was used as the sole queue and the rocksdb::Flush() call was manually triggered at opportune moments, possibly just after ledger close, then that would provide similar, but more predictable guarantees. It would also remove an unneeded thread and unnecessary memory usage. An alternative point of view is that because there will always be many other xrpld instances running there is no need for such guarantees. The nodes will always be available from another peer.
|
||||
|
||||
- Lookup in a block was previously using binary search. With xrpld's use case
|
||||
it is highly unlikely that two adjacent key/values will ever be requested one
|
||||
after the other. Therefore hash indexing of blocks makes much more sense.
|
||||
Rocksdb has a number of options for hash indexing both memtables and blocks and
|
||||
these need more testing to find the best choice.
|
||||
- Lookup in a block was previously using binary search. With xrpld's use case it is highly unlikely that two adjacent key/values will ever be requested one after the other. Therefore hash indexing of blocks makes much more sense. Rocksdb has a number of options for hash indexing both memtables and blocks and these need more testing to find the best choice.
|
||||
|
||||
- The current Database implementation has two forms of caching, so the LRU cache
|
||||
of blocks at Factory level does not make any sense. However, if the hash
|
||||
indexing and potentially the new [bloom
|
||||
filter](http://rocksdb.org/blog/1427/new-bloom-filter-format/) can provide
|
||||
faster lookup for non-existent keys, then potentially the caching could exist at
|
||||
Factory level.
|
||||
- The current Database implementation has two forms of caching, so the LRU cache of blocks at Factory level does not make any sense. However, if the hash indexing and potentially the new [bloom filter](http://rocksdb.org/blog/1427/new-bloom-filter-format/) can provide faster lookup for non-existent keys, then potentially the caching could exist at Factory level.
|
||||
|
||||
- Multiple runs of the benchmarks can yield surprisingly different results. This
|
||||
can perhaps be attributed to the asynchronous nature of rocksdb's compaction
|
||||
process. The benchmarks are artificial and create highly unlikely write load to
|
||||
create the dataset to measure different read access patterns. Therefore multiple
|
||||
runs of the benchmarks are required to get a feel for the effectiveness of the
|
||||
changes. This contrasts sharply with the keyvadb benchmarking were highly
|
||||
repeatable timings were discovered. Also realistically sized datasets are
|
||||
required to get a correct insight. The number of 2,000,000 key/values (actually
|
||||
4,000,000 after the two insert benchmarks complete) is too low to get a full
|
||||
picture.
|
||||
- Multiple runs of the benchmarks can yield surprisingly different results. This can perhaps be attributed to the asynchronous nature of rocksdb's compaction process. The benchmarks are artificial and create highly unlikely write load to create the dataset to measure different read access patterns. Therefore multiple runs of the benchmarks are required to get a feel for the effectiveness of the changes. This contrasts sharply with the keyvadb benchmarking were highly repeatable timings were discovered. Also realistically sized datasets are required to get a correct insight. The number of 2,000,000 key/values (actually 4,000,000 after the two insert benchmarks complete) is too low to get a full picture.
|
||||
|
||||
- An interesting side effect of running the benchmarks in a profiler was that a
|
||||
clear pattern of what RocksDB does under the hood was observable. This led to
|
||||
the decision to trial hash indexing and also the discovery of the native CRC32
|
||||
instruction not being used.
|
||||
- An interesting side effect of running the benchmarks in a profiler was that a clear pattern of what RocksDB does under the hood was observable. This led to the decision to trial hash indexing and also the discovery of the native CRC32 instruction not being used.
|
||||
|
||||
- Important point to note that is if this factory is tested with an existing set
|
||||
of sst files none of the old sst files will benefit from indexing changes until
|
||||
they are compacted at a future point in time.
|
||||
- Important point to note that is if this factory is tested with an existing set of sst files none of the old sst files will benefit from indexing changes until they are compacted at a future point in time.
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# Proto
|
||||
|
||||
This holds protocol buffers source code. The protoc tool stores the output
|
||||
of the .proto files in the build directory.
|
||||
This holds protocol buffers source code. The protoc tool stores the output of the .proto files in the build directory.
|
||||
|
||||
@@ -1,81 +1,35 @@
|
||||
# Protocol buffer definitions for gRPC
|
||||
|
||||
This folder contains the protocol buffer definitions used by the xrpld gRPC API.
|
||||
The gRPC API attempts to mimic the JSON/Websocket API as much as possible.
|
||||
As of April 2020, the gRPC API supports a subset of the full xrpld API:
|
||||
tx, account_tx, account_info, fee and submit.
|
||||
This folder contains the protocol buffer definitions used by the xrpld gRPC API. The gRPC API attempts to mimic the JSON/Websocket API as much as possible. As of April 2020, the gRPC API supports a subset of the full xrpld API: tx, account_tx, account_info, fee and submit.
|
||||
|
||||
### Making Changes
|
||||
|
||||
#### Wire Format and Backwards Compatibility
|
||||
|
||||
When making changes to the protocol buffer definitions in this folder, care must
|
||||
be taken to ensure the changes do not break the wire format, which would break
|
||||
backwards compatibility. At a high level, do not change any existing fields.
|
||||
This includes the field's name, type and field number. Do not remove any
|
||||
existing fields. It is always safe to add fields; just remember to give each of
|
||||
the new fields a unique field number. The field numbers don't have to be in any
|
||||
particular order and there can be gaps. More info about what changes break the
|
||||
wire format can be found
|
||||
[here](https://developers.google.com/protocol-buffers/docs/proto3#updating).
|
||||
When making changes to the protocol buffer definitions in this folder, care must be taken to ensure the changes do not break the wire format, which would break backwards compatibility. At a high level, do not change any existing fields. This includes the field's name, type and field number. Do not remove any existing fields. It is always safe to add fields; just remember to give each of the new fields a unique field number. The field numbers don't have to be in any particular order and there can be gaps. More info about what changes break the wire format can be found [here](https://developers.google.com/protocol-buffers/docs/proto3#updating).
|
||||
|
||||
#### Conventions
|
||||
|
||||
For fields that are reused across different message types, we define the field as a unique
|
||||
message type in common.proto. The name of the message type is the same as the
|
||||
field name, with the exception that the field name itself is snake case, whereas
|
||||
the message type is in Pascal case. The message type has one field, called
|
||||
`value`. This pattern does not need to be strictly followed across the entire API,
|
||||
but should be followed for transactions and ledger objects, since there is a high rate
|
||||
of field reuse across different transactions and ledger objects.
|
||||
The motivation for this pattern is two-fold. First, we ensure the field has the
|
||||
same type everywhere that the field is used. Second, wrapping primitive types in
|
||||
their own message type prevents default initialization of those primitive types.
|
||||
For example, `uint32` is initialized to `0` if not explicitly set;
|
||||
there is no way to tell if the client or server set the field to `0` (which may be
|
||||
a valid value for the field) or the field was default initialized.
|
||||
For fields that are reused across different message types, we define the field as a unique message type in common.proto. The name of the message type is the same as the field name, with the exception that the field name itself is snake case, whereas the message type is in Pascal case. The message type has one field, called `value`. This pattern does not need to be strictly followed across the entire API, but should be followed for transactions and ledger objects, since there is a high rate of field reuse across different transactions and ledger objects. The motivation for this pattern is two-fold. First, we ensure the field has the same type everywhere that the field is used. Second, wrapping primitive types in their own message type prevents default initialization of those primitive types. For example, `uint32` is initialized to `0` if not explicitly set; there is no way to tell if the client or server set the field to `0` (which may be a valid value for the field) or the field was default initialized.
|
||||
|
||||
#### Name Collisions
|
||||
|
||||
Each message type must have a unique name. To resolve collisions, add a suffix
|
||||
to one or more message types. For instance, ledger objects and transaction types
|
||||
often have the same name (`DepositPreauth` for example). To resolve this, the
|
||||
`DepositPreauth` ledger object is named `DepositPreauthObject`.
|
||||
Each message type must have a unique name. To resolve collisions, add a suffix to one or more message types. For instance, ledger objects and transaction types often have the same name (`DepositPreauth` for example). To resolve this, the `DepositPreauth` ledger object is named `DepositPreauthObject`.
|
||||
|
||||
#### To add a field or message type
|
||||
|
||||
To add a field to a message, define the fields type, name and unique index.
|
||||
To add a new message type, give the message type a unique name.
|
||||
Then, add the appropriate C++ code in GRPCHelpers.cpp, or in the handler itself,
|
||||
to serialize/deserialize the new field or message type.
|
||||
To add a field to a message, define the fields type, name and unique index. To add a new message type, give the message type a unique name. Then, add the appropriate C++ code in GRPCHelpers.cpp, or in the handler itself, to serialize/deserialize the new field or message type.
|
||||
|
||||
#### To add a new gRPC method
|
||||
|
||||
To add a new gRPC method, add the gRPC method in xrp_ledger.proto. The method name
|
||||
should begin with a verb. Define the request and response types in their own
|
||||
file. The name of the request type should be the method name suffixed with `Request`, and
|
||||
the response type name should be the method name suffixed with `Response`. For
|
||||
example, the `GetAccountInfo` method has request type `GetAccountInfoRequest` and
|
||||
response type `GetAccountInfoResponse`.
|
||||
To add a new gRPC method, add the gRPC method in xrp_ledger.proto. The method name should begin with a verb. Define the request and response types in their own file. The name of the request type should be the method name suffixed with `Request`, and the response type name should be the method name suffixed with `Response`. For example, the `GetAccountInfo` method has request type `GetAccountInfoRequest` and response type `GetAccountInfoResponse`.
|
||||
|
||||
After defining the protobuf messages for the new method, add an instantiation of the
|
||||
templated `CallData` class in GRPCServerImpl::setupListeners(). The template
|
||||
parameters should be the request type and the response type.
|
||||
After defining the protobuf messages for the new method, add an instantiation of the templated `CallData` class in GRPCServerImpl::setupListeners(). The template parameters should be the request type and the response type.
|
||||
|
||||
Finally, define the handler itself in the appropriate file under the
|
||||
src/xrpld/rpc/handlers folder. If the method already has a JSON/Websocket
|
||||
equivalent, write the gRPC handler in the same file, and abstract common logic
|
||||
into helper functions (see Tx.cpp or AccountTx.cpp for an example).
|
||||
Finally, define the handler itself in the appropriate file under the src/xrpld/rpc/handlers folder. If the method already has a JSON/Websocket equivalent, write the gRPC handler in the same file, and abstract common logic into helper functions (see Tx.cpp or AccountTx.cpp for an example).
|
||||
|
||||
#### Testing
|
||||
|
||||
When modifying an existing gRPC method, be sure to test that modification in the
|
||||
corresponding, existing unit test. When creating a new gRPC method, create a
|
||||
client stub with `XRPLedgerAPIService::NewStub` and `grpc::CreateChannel`, and
|
||||
use it to call the new method. See `GRPCServerTLS_test.cpp` for an example.
|
||||
The gRPC tests are paired with their JSON counterpart, and the tests should
|
||||
mirror the JSON test as much as possible.
|
||||
When modifying an existing gRPC method, be sure to test that modification in the corresponding, existing unit test. When creating a new gRPC method, create a client stub with `XRPLedgerAPIService::NewStub` and `grpc::CreateChannel`, and use it to call the new method. See `GRPCServerTLS_test.cpp` for an example. The gRPC tests are paired with their JSON counterpart, and the tests should mirror the JSON test as much as possible.
|
||||
|
||||
Refer to the Protocol Buffers [language
|
||||
guide](https://developers.google.com/protocol-buffers/docs/proto3)
|
||||
for more detailed information about Protocol Buffers.
|
||||
Refer to the Protocol Buffers [language guide](https://developers.google.com/protocol-buffers/docs/proto3) for more detailed information about Protocol Buffers.
|
||||
|
||||
@@ -92,26 +92,6 @@ enum class HashPrefix : std::uint32_t {
|
||||
* Batch
|
||||
*/
|
||||
Batch = detail::makeHashPrefix('B', 'C', 'H'),
|
||||
|
||||
/**
|
||||
* inner transaction to sign as the counterparty
|
||||
*/
|
||||
CounterpartyTxSign = detail::makeHashPrefix('C', 'P', 'T'),
|
||||
|
||||
/**
|
||||
* inner transaction to multi-sign as the counterparty
|
||||
*/
|
||||
CounterpartyTxMultiSign = detail::makeHashPrefix('C', 'P', 'M'),
|
||||
|
||||
/**
|
||||
* inner transaction to sign as the sponsor
|
||||
*/
|
||||
SponsorTxSign = detail::makeHashPrefix('S', 'P', 'N'),
|
||||
|
||||
/**
|
||||
* inner transaction to multi-sign as the sponsor
|
||||
*/
|
||||
SponsorTxMultiSign = detail::makeHashPrefix('S', 'P', 'M'),
|
||||
};
|
||||
|
||||
template <class Hasher>
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -408,6 +407,16 @@ using TxID = uint256;
|
||||
*/
|
||||
constexpr std::uint16_t kMaxDeletableAmmTrustLines = 512;
|
||||
|
||||
/**
|
||||
* The maximum number of owner-directory entries to walk when clearing
|
||||
* credentials pinned to a pseudo-account, in a single transaction.
|
||||
*
|
||||
* The walk stops after this many entries whether or not each one turns out to
|
||||
* be a credential, so a directory that also holds other objects yields fewer
|
||||
* deletions per transaction.
|
||||
*/
|
||||
constexpr std::uint16_t kMaxDeletablePseudoAccountCredentials = 512;
|
||||
|
||||
/**
|
||||
* The maximum length of a URI inside an Oracle
|
||||
*/
|
||||
@@ -545,11 +554,6 @@ constexpr std::size_t kEcClawbackProofLength = SECP256K1_COMPACT_CLAWBACK_PROOF_
|
||||
*/
|
||||
constexpr std::uint32_t kConfidentialFeeMultiplier = 9;
|
||||
|
||||
/**
|
||||
* Maximum value a confidential MPT key epoch may reach.
|
||||
*/
|
||||
constexpr std::uint32_t kMaxKeyEpoch = std::numeric_limits<std::uint32_t>::max();
|
||||
|
||||
/**
|
||||
* Compressed EC point prefix for even y-coordinate
|
||||
*/
|
||||
|
||||
@@ -1,42 +1,24 @@
|
||||
# protocol
|
||||
|
||||
Classes and functions for handling data and
|
||||
values associated with the XRP Ledger protocol.
|
||||
Classes and functions for handling data and values associated with the XRP Ledger protocol.
|
||||
|
||||
## Serialized Objects
|
||||
|
||||
Objects transmitted over the network must be
|
||||
serialized into a canonical format. The prefix "ST" refers
|
||||
to classes that deal with the serialized format.
|
||||
Objects transmitted over the network must be serialized into a canonical format. The prefix "ST" refers to classes that deal with the serialized format.
|
||||
|
||||
The term "Tx" or "tx" is an abbreviation for "Transaction",
|
||||
a commonly occurring object type.
|
||||
The term "Tx" or "tx" is an abbreviation for "Transaction", a commonly occurring object type.
|
||||
|
||||
### Optional Fields
|
||||
|
||||
Our serialized fields have some "type magic" to make
|
||||
optional fields easier to read:
|
||||
Our serialized fields have some "type magic" to make optional fields easier to read:
|
||||
|
||||
- The operation `x[sfFoo]` means "return the value of 'Foo'
|
||||
if it exists, or the default value if it doesn't."
|
||||
- The operation `x[~sfFoo]` means "return the value of 'Foo'
|
||||
if it exists, or nothing if it doesn't." This usage of the
|
||||
tilde/bitwise NOT operator is not standard outside of the
|
||||
`xrpld` codebase.
|
||||
- As a consequence of this, `x[~sfFoo] = y[~sfFoo]`
|
||||
assigns the value of Foo from y to x, including omitting
|
||||
Foo from x if it doesn't exist in y.
|
||||
- The operation `x[sfFoo]` means "return the value of 'Foo' if it exists, or the default value if it doesn't."
|
||||
- The operation `x[~sfFoo]` means "return the value of 'Foo' if it exists, or nothing if it doesn't." This usage of the tilde/bitwise NOT operator is not standard outside of the `xrpld` codebase.
|
||||
- As a consequence of this, `x[~sfFoo] = y[~sfFoo]` assigns the value of Foo from y to x, including omitting Foo from x if it doesn't exist in y.
|
||||
|
||||
Typically, for things that are guaranteed to exist, you use
|
||||
`x[sfFoo]` and avoid having to deal with a container that may
|
||||
or may not hold a value. For things not guaranteed to exist,
|
||||
you use `x[~sfFoo]` because you want such a container. It
|
||||
avoids having to look something up twice, once just to see if
|
||||
it exists and a second time to get/set its value.
|
||||
([Real example](https://github.com/XRPLF/rippled/blob/35f4698aed5dce02f771b34cfbb690495cb5efcc/src/ripple/app/tx/impl/PayChan.cpp#L229-L236))
|
||||
Typically, for things that are guaranteed to exist, you use `x[sfFoo]` and avoid having to deal with a container that may or may not hold a value. For things not guaranteed to exist, you use `x[~sfFoo]` because you want such a container. It avoids having to look something up twice, once just to see if it exists and a second time to get/set its value. ([Real example](https://github.com/XRPLF/rippled/blob/35f4698aed5dce02f771b34cfbb690495cb5efcc/src/ripple/app/tx/impl/PayChan.cpp#L229-L236))
|
||||
|
||||
The source of this "type magic" is in
|
||||
[SField.h](./SField.h#L296-L302).
|
||||
The source of this "type magic" is in [SField.h](./SField.h#L296-L302).
|
||||
|
||||
### Related Resources
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
@@ -14,7 +13,6 @@
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/SeqProxy.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/Sign.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
|
||||
#include <boost/container/flat_set.hpp>
|
||||
@@ -107,36 +105,14 @@ public:
|
||||
[[nodiscard]] json::Value
|
||||
getJson(JsonOptions options, bool binary) const;
|
||||
|
||||
/**
|
||||
* Sign the transaction as its account.
|
||||
*
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
*/
|
||||
void
|
||||
sign(PublicKey const& publicKey, SecretKey const& secretKey);
|
||||
|
||||
/**
|
||||
* Sign the transaction in one of its signature fields.
|
||||
*
|
||||
* The signature is bound to the role that made it, so it cannot be moved
|
||||
* into another role.
|
||||
*
|
||||
* @param publicKey The public key for signing.
|
||||
* @param secretKey The secret key for signing.
|
||||
* @param role The role signing the transaction.
|
||||
* @param rules The current ledger rules.
|
||||
*/
|
||||
void
|
||||
sign(
|
||||
PublicKey const& publicKey,
|
||||
SecretKey const& secretKey,
|
||||
SignatureRole role,
|
||||
Rules const& rules);
|
||||
std::optional<std::reference_wrapper<SField const>> signatureTarget = {});
|
||||
|
||||
/**
|
||||
* Check the signature.
|
||||
*
|
||||
* @param rules The current ledger rules.
|
||||
* @return `true` if valid signature. If invalid, the error message string.
|
||||
*/
|
||||
@@ -144,7 +120,7 @@ public:
|
||||
checkSign(Rules const& rules) const;
|
||||
|
||||
[[nodiscard]] std::expected<void, std::string>
|
||||
checkBatchSign() const;
|
||||
checkBatchSign(Rules const& rules) const;
|
||||
|
||||
// SQL Functions with metadata.
|
||||
static std::string const&
|
||||
@@ -186,28 +162,28 @@ public:
|
||||
private:
|
||||
/**
|
||||
* Check the signature.
|
||||
*
|
||||
* @param rules The current ledger rules.
|
||||
* @param sigObject Reference to object that contains the signature fields.
|
||||
* Will be *this more often than not.
|
||||
* @param role The role that made the signature in sigObject. Determines
|
||||
* the signing prefix, which binds the signature to that role.
|
||||
* @return `true` if valid signature. If invalid, the error message string.
|
||||
*/
|
||||
[[nodiscard]] std::expected<void, std::string>
|
||||
checkSign(Rules const& rules, STObject const& sigObject, SignatureRole role) const;
|
||||
checkSign(Rules const& rules, STObject const& sigObject) const;
|
||||
|
||||
[[nodiscard]] std::expected<void, std::string>
|
||||
checkSingleSign(STObject const& sigObject, HashPrefix prefix) const;
|
||||
checkSingleSign(STObject const& sigObject) const;
|
||||
|
||||
[[nodiscard]] std::expected<void, std::string>
|
||||
checkMultiSign(STObject const& sigObject, HashPrefix prefix) const;
|
||||
checkMultiSign(Rules const& rules, STObject const& sigObject) const;
|
||||
|
||||
[[nodiscard]] std::expected<void, std::string>
|
||||
checkBatchSingleSign(STObject const& batchSigner, std::vector<uint256> const& txIds) const;
|
||||
|
||||
[[nodiscard]] std::expected<void, std::string>
|
||||
checkBatchMultiSign(STObject const& batchSigner, std::vector<uint256> const& txIds) const;
|
||||
checkBatchMultiSign(
|
||||
STObject const& batchSigner,
|
||||
Rules const& rules,
|
||||
std::vector<uint256> const& txIds) const;
|
||||
|
||||
void
|
||||
buildBatchTxns();
|
||||
|
||||
@@ -4,65 +4,13 @@
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
#include <xrpl/protocol/KeyType.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* The signature slots on a transaction.
|
||||
*
|
||||
* Each role signs different bytes, so a signature cannot be moved from the
|
||||
* role that made it into another role. See signingPrefix.
|
||||
*/
|
||||
enum class SignatureRole {
|
||||
/**
|
||||
* The transaction's own signature, in sfTxnSignature or sfSigners.
|
||||
*/
|
||||
Transaction,
|
||||
/**
|
||||
* The counterparty's signature, in sfCounterpartySignature.
|
||||
*/
|
||||
Counterparty,
|
||||
/**
|
||||
* The sponsor's signature, in sfSponsorSignature.
|
||||
*/
|
||||
Sponsor
|
||||
};
|
||||
|
||||
/**
|
||||
* The field that holds this role's signature.
|
||||
*
|
||||
* @return The signature field, or nullptr for SignatureRole::Transaction,
|
||||
* whose signature lives at the top level of the transaction.
|
||||
*/
|
||||
[[nodiscard]] SField const*
|
||||
signatureField(SignatureRole role);
|
||||
|
||||
/**
|
||||
* The role that signs into the given field.
|
||||
*
|
||||
* @return The role, or an unseated optional if the field does not hold a
|
||||
* transaction signature.
|
||||
*/
|
||||
[[nodiscard]] std::optional<SignatureRole>
|
||||
signatureRole(SField const& sigField);
|
||||
|
||||
/**
|
||||
* The hash prefix that binds a transaction signature to the role that made it.
|
||||
*
|
||||
* @param role The role making the signature.
|
||||
* @param multiSigning Whether the signature is a multi-signature.
|
||||
* @param rules The current ledger rules.
|
||||
*/
|
||||
[[nodiscard]] HashPrefix
|
||||
signingPrefix(SignatureRole role, bool multiSigning, Rules const& rules);
|
||||
|
||||
/**
|
||||
* Sign an STObject
|
||||
*
|
||||
@@ -101,12 +49,9 @@ verify(
|
||||
|
||||
/**
|
||||
* Return a Serializer suitable for computing a multisigning TxnSignature.
|
||||
*
|
||||
* @param prefix Prefix to insert before the serialized object. Get it from
|
||||
* signingPrefix, so that the signature is bound to the role making it.
|
||||
*/
|
||||
Serializer
|
||||
buildMultiSigningData(STObject const& obj, AccountID const& signingID, HashPrefix prefix);
|
||||
buildMultiSigningData(STObject const& obj, AccountID const& signingID);
|
||||
|
||||
/**
|
||||
* Break the multi-signing hash computation into 2 parts for optimization.
|
||||
@@ -122,7 +67,7 @@ buildMultiSigningData(STObject const& obj, AccountID const& signingID, HashPrefi
|
||||
* signer's unique data.
|
||||
*/
|
||||
Serializer
|
||||
startMultiSigningData(STObject const& obj, HashPrefix prefix);
|
||||
startMultiSigningData(STObject const& obj);
|
||||
|
||||
inline void
|
||||
finishMultiSigningData(AccountID const& signingID, Serializer& s)
|
||||
|
||||
@@ -129,11 +129,8 @@ enum TEMcodes : TERUnderlyingType {
|
||||
temARRAY_TOO_LARGE,
|
||||
temBAD_TRANSFER_FEE,
|
||||
temINVALID_INNER_BATCH,
|
||||
|
||||
temBAD_MPT,
|
||||
temBAD_CIPHERTEXT,
|
||||
temINVALID_BYTECODE,
|
||||
temTEMP_DISABLED,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -182,8 +179,6 @@ enum TEFcodes : TERUnderlyingType {
|
||||
tefINVALID_LEDGER_FIX_TYPE,
|
||||
tefNO_DST_PARTIAL,
|
||||
tefBAD_PATH_COUNT,
|
||||
tefNO_BYTECODE,
|
||||
tefBYTECODE_NOT_INCLUDED,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -375,8 +370,6 @@ enum TECcodes : TERUnderlyingType {
|
||||
tecNO_DELEGATE_PERMISSION = 198,
|
||||
tecBAD_PROOF = 199,
|
||||
tecNO_SPONSOR_PERMISSION = 200,
|
||||
tecOUT_OF_GAS = 201,
|
||||
tecBYTECODE_REJECTED = 202,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -15,10 +15,6 @@
|
||||
// Add new amendments to the top of this list.
|
||||
// Keep it sorted in reverse chronological order.
|
||||
|
||||
XRPL_FEATURE(SmartEscrow, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(LendingProtocolV1_2, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (Cleanup3_5_0, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(ConfidentialMPTKeyRotation, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (Cleanup3_4_0, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(Sponsor, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(BatchV1_1, Supported::Yes, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -309,11 +309,6 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({
|
||||
{sfBaseFeeDrops, SoeOptional},
|
||||
{sfReserveBaseDrops, SoeOptional},
|
||||
{sfReserveIncrementDrops, SoeOptional},
|
||||
// Smart Escrow fields
|
||||
{sfGasLimit, SoeOptional},
|
||||
{sfBytecodeSizeLimit, SoeOptional},
|
||||
{sfGasPrice, SoeOptional},
|
||||
|
||||
{sfPreviousTxnID, SoeOptional},
|
||||
{sfPreviousTxnLgrSeq, SoeOptional},
|
||||
}))
|
||||
@@ -344,8 +339,6 @@ LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({
|
||||
{sfCondition, SoeOptional},
|
||||
{sfCancelAfter, SoeOptional},
|
||||
{sfFinishAfter, SoeOptional},
|
||||
{sfBytecode, SoeOptional},
|
||||
{sfData, SoeOptional},
|
||||
{sfSourceTag, SoeOptional},
|
||||
{sfDestinationTag, SoeOptional},
|
||||
{sfOwnerNode, SoeRequired},
|
||||
@@ -415,8 +408,6 @@ LEDGER_ENTRY(ltMPTOKEN_ISSUANCE, 0x007e, MPTokenIssuance, mpt_issuance, ({
|
||||
{sfReferenceHolding, SoeOptional},
|
||||
{sfIssuerEncryptionKey, SoeOptional},
|
||||
{sfAuditorEncryptionKey, SoeOptional},
|
||||
{sfIssuerKeyEpoch, SoeOptional},
|
||||
{sfAuditorKeyEpoch, SoeOptional},
|
||||
{sfConfidentialOutstandingAmount, SoeDefault},
|
||||
}))
|
||||
|
||||
|
||||
@@ -119,15 +119,6 @@ TYPED_SFIELD(sfRemainingOwnerCount, UINT32, 73)
|
||||
TYPED_SFIELD(sfSponsorFlags, UINT32, 74)
|
||||
TYPED_SFIELD(sfSubscriptionDate, UINT32, 75)
|
||||
TYPED_SFIELD(sfRedemptionDate, UINT32, 76)
|
||||
TYPED_SFIELD(sfIssuerKeyEpoch, UINT32, 77)
|
||||
TYPED_SFIELD(sfAuditorKeyEpoch, UINT32, 78)
|
||||
TYPED_SFIELD(sfIssuerKeyMirrorEpoch, UINT32, 79)
|
||||
TYPED_SFIELD(sfAuditorKeyMirrorEpoch, UINT32, 80)
|
||||
TYPED_SFIELD(sfGasLimit, UINT32, 81)
|
||||
TYPED_SFIELD(sfBytecodeSizeLimit, UINT32, 82)
|
||||
TYPED_SFIELD(sfGasPrice, UINT32, 83)
|
||||
TYPED_SFIELD(sfGas, UINT32, 84)
|
||||
TYPED_SFIELD(sfGasUsed, UINT32, 85)
|
||||
|
||||
// 64-bit integers (common)
|
||||
TYPED_SFIELD(sfIndexNext, UINT64, 1)
|
||||
@@ -243,7 +234,6 @@ TYPED_SFIELD(sfManagementFeeOutstanding, NUMBER, 17, SField::kSmdNeedsAsset
|
||||
// 32-bit signed (common)
|
||||
TYPED_SFIELD(sfLoanScale, INT32, 1)
|
||||
TYPED_SFIELD(sfRemainingOwnerCountDelta, INT32, 2)
|
||||
TYPED_SFIELD(sfVMReturnCode, INT32, 3)
|
||||
|
||||
// currency amount (common)
|
||||
TYPED_SFIELD(sfAmount, AMOUNT, 1)
|
||||
@@ -328,7 +318,6 @@ TYPED_SFIELD(sfAuditorEncryptedAmount, VL, 43)
|
||||
TYPED_SFIELD(sfAuditorEncryptionKey, VL, 44)
|
||||
TYPED_SFIELD(sfAmountCommitment, VL, 45)
|
||||
TYPED_SFIELD(sfBalanceCommitment, VL, 46)
|
||||
TYPED_SFIELD(sfBytecode, VL, 47)
|
||||
|
||||
// account (common)
|
||||
TYPED_SFIELD(sfAccount, ACCOUNT, 1)
|
||||
|
||||
@@ -66,13 +66,11 @@ TRANSACTION(ttPAYMENT, 0, Payment,
|
||||
#endif
|
||||
TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, ({.delegable = Delegation::Delegable}), ({
|
||||
{sfDestination, SoeRequired},
|
||||
{sfDestinationTag, SoeOptional},
|
||||
{sfAmount, SoeRequired, SoeMptSupported},
|
||||
{sfCondition, SoeOptional},
|
||||
{sfCancelAfter, SoeOptional},
|
||||
{sfFinishAfter, SoeOptional},
|
||||
{sfBytecode, SoeOptional},
|
||||
{sfData, SoeOptional},
|
||||
{sfDestinationTag, SoeOptional},
|
||||
}))
|
||||
|
||||
/** This transaction type completes an existing escrow. */
|
||||
@@ -85,7 +83,6 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, ({.delegable = Delegation::Delegab
|
||||
{sfFulfillment, SoeOptional},
|
||||
{sfCondition, SoeOptional},
|
||||
{sfCredentialIDs, SoeOptional},
|
||||
{sfGas, SoeOptional},
|
||||
}))
|
||||
|
||||
|
||||
@@ -1164,10 +1161,6 @@ TRANSACTION(ttFEE, 101, SetFee,
|
||||
{sfBaseFeeDrops, SoeOptional},
|
||||
{sfReserveBaseDrops, SoeOptional},
|
||||
{sfReserveIncrementDrops, SoeOptional},
|
||||
// Smart Escrow fields
|
||||
{sfGasLimit, SoeOptional},
|
||||
{sfBytecodeSizeLimit, SoeOptional},
|
||||
{sfGasPrice, SoeOptional},
|
||||
}))
|
||||
|
||||
/** This system-generated transaction type is used to update the network's negative UNL
|
||||
|
||||
@@ -11,21 +11,16 @@ The files in this directory are generated from macro definition files:
|
||||
|
||||
## Generation Process
|
||||
|
||||
Generation requires a one-time setup step to create a virtual environment
|
||||
and install Python dependencies, followed by running the generation target:
|
||||
Generation requires a one-time setup step to create a virtual environment and install Python dependencies, followed by running the generation target:
|
||||
|
||||
```bash
|
||||
cmake --build . --target setup_code_gen # create venv and install dependencies (once)
|
||||
cmake --build . --target code_gen # generate code
|
||||
```
|
||||
|
||||
By default, `CODEGEN_VENV_DIR` points to `.venv` in the project root. The
|
||||
`setup_code_gen` target creates a venv there and installs the required packages.
|
||||
The `code_gen` target then uses the venv's Python interpreter to run generation.
|
||||
By default, `CODEGEN_VENV_DIR` points to `.venv` in the project root. The `setup_code_gen` target creates a venv there and installs the required packages. The `code_gen` target then uses the venv's Python interpreter to run generation.
|
||||
|
||||
Generation is pure Python, so the same targets are also available as a
|
||||
standalone project that needs neither the dependencies nor a compiler. This is
|
||||
what CI uses, and it is handy if you only want to regenerate these files:
|
||||
Generation is pure Python, so the same targets are also available as a standalone project that needs neither the dependencies nor a compiler. This is what CI uses, and it is handy if you only want to regenerate these files:
|
||||
|
||||
```bash
|
||||
cmake -S cmake/codegen -B build/codegen
|
||||
|
||||
@@ -174,54 +174,6 @@ public:
|
||||
return this->sle_->isFieldPresent(sfFinishAfter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfBytecode (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getBytecode() const
|
||||
{
|
||||
if (hasBytecode())
|
||||
return this->sle_->at(sfBytecode);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfBytecode is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasBytecode() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfBytecode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfData (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getData() const
|
||||
{
|
||||
if (hasData())
|
||||
return this->sle_->at(sfData);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfData is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasData() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfSourceTag (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
@@ -501,28 +453,6 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfBytecode (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
EscrowBuilder&
|
||||
setBytecode(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfBytecode] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfData (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
EscrowBuilder&
|
||||
setData(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfData] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfSourceTag (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
|
||||
@@ -213,78 +213,6 @@ public:
|
||||
return this->sle_->isFieldPresent(sfReserveIncrementDrops);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfGasLimit (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getGasLimit() const
|
||||
{
|
||||
if (hasGasLimit())
|
||||
return this->sle_->at(sfGasLimit);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfGasLimit is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasGasLimit() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfGasLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfBytecodeSizeLimit (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getBytecodeSizeLimit() const
|
||||
{
|
||||
if (hasBytecodeSizeLimit())
|
||||
return this->sle_->at(sfBytecodeSizeLimit);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfBytecodeSizeLimit is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasBytecodeSizeLimit() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfBytecodeSizeLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfGasPrice (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getGasPrice() const
|
||||
{
|
||||
if (hasGasPrice())
|
||||
return this->sle_->at(sfGasPrice);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfGasPrice is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasGasPrice() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfGasPrice);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfPreviousTxnID (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
@@ -447,39 +375,6 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfGasLimit (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
FeeSettingsBuilder&
|
||||
setGasLimit(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfGasLimit] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfBytecodeSizeLimit (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
FeeSettingsBuilder&
|
||||
setBytecodeSizeLimit(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfBytecodeSizeLimit] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfGasPrice (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
FeeSettingsBuilder&
|
||||
setGasPrice(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfGasPrice] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfPreviousTxnID (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
|
||||
@@ -351,54 +351,6 @@ public:
|
||||
return this->sle_->isFieldPresent(sfAuditorEncryptionKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfIssuerKeyEpoch (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getIssuerKeyEpoch() const
|
||||
{
|
||||
if (hasIssuerKeyEpoch())
|
||||
return this->sle_->at(sfIssuerKeyEpoch);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfIssuerKeyEpoch is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasIssuerKeyEpoch() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfIssuerKeyEpoch);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfAuditorKeyEpoch (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getAuditorKeyEpoch() const
|
||||
{
|
||||
if (hasAuditorKeyEpoch())
|
||||
return this->sle_->at(sfAuditorKeyEpoch);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfAuditorKeyEpoch is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasAuditorKeyEpoch() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfAuditorKeyEpoch);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfConfidentialOutstandingAmount (SoeDefault)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
@@ -648,28 +600,6 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfIssuerKeyEpoch (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
MPTokenIssuanceBuilder&
|
||||
setIssuerKeyEpoch(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfIssuerKeyEpoch] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfAuditorKeyEpoch (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
MPTokenIssuanceBuilder&
|
||||
setAuditorKeyEpoch(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfAuditorKeyEpoch] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfConfidentialOutstandingAmount (SoeDefault)
|
||||
* @return Reference to this builder for method chaining.
|
||||
|
||||
@@ -58,32 +58,6 @@ public:
|
||||
return this->tx_->at(sfDestination);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfDestinationTag (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getDestinationTag() const
|
||||
{
|
||||
if (hasDestinationTag())
|
||||
{
|
||||
return this->tx_->at(sfDestinationTag);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfDestinationTag is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasDestinationTag() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfDestinationTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfAmount (SoeRequired)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
@@ -175,55 +149,29 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfBytecode (SoeOptional)
|
||||
* @brief Get sfDestinationTag (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getBytecode() const
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getDestinationTag() const
|
||||
{
|
||||
if (hasBytecode())
|
||||
if (hasDestinationTag())
|
||||
{
|
||||
return this->tx_->at(sfBytecode);
|
||||
return this->tx_->at(sfDestinationTag);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfBytecode is present.
|
||||
* @brief Check if sfDestinationTag is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasBytecode() const
|
||||
hasDestinationTag() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfBytecode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfData (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getData() const
|
||||
{
|
||||
if (hasData())
|
||||
{
|
||||
return this->tx_->at(sfData);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfData is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasData() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfData);
|
||||
return this->tx_->isFieldPresent(sfDestinationTag);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -284,17 +232,6 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfDestinationTag (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
EscrowCreateBuilder&
|
||||
setDestinationTag(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfDestinationTag] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfAmount (SoeRequired)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
@@ -341,24 +278,13 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfBytecode (SoeOptional)
|
||||
* @brief Set sfDestinationTag (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
EscrowCreateBuilder&
|
||||
setBytecode(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
setDestinationTag(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfBytecode] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfData (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
EscrowCreateBuilder&
|
||||
setData(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfData] = value;
|
||||
object_[sfDestinationTag] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
@@ -146,32 +146,6 @@ public:
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfCredentialIDs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfGas (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getGas() const
|
||||
{
|
||||
if (hasGas())
|
||||
{
|
||||
return this->tx_->at(sfGas);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfGas is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasGas() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfGas);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -275,17 +249,6 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfGas (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
EscrowFinishBuilder&
|
||||
setGas(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfGas] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the EscrowFinish wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
|
||||
@@ -254,84 +254,6 @@ public:
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfReserveIncrementDrops);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfGasLimit (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getGasLimit() const
|
||||
{
|
||||
if (hasGasLimit())
|
||||
{
|
||||
return this->tx_->at(sfGasLimit);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfGasLimit is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasGasLimit() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfGasLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfBytecodeSizeLimit (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getBytecodeSizeLimit() const
|
||||
{
|
||||
if (hasBytecodeSizeLimit())
|
||||
{
|
||||
return this->tx_->at(sfBytecodeSizeLimit);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfBytecodeSizeLimit is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasBytecodeSizeLimit() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfBytecodeSizeLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfGasPrice (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT32::type::value_type>
|
||||
getGasPrice() const
|
||||
{
|
||||
if (hasGasPrice())
|
||||
{
|
||||
return this->tx_->at(sfGasPrice);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfGasPrice is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasGasPrice() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfGasPrice);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -464,39 +386,6 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfGasLimit (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SetFeeBuilder&
|
||||
setGasLimit(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfGasLimit] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfBytecodeSizeLimit (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SetFeeBuilder&
|
||||
setBytecodeSizeLimit(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfBytecodeSizeLimit] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfGasPrice (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
SetFeeBuilder&
|
||||
setGasPrice(std::decay_t<typename SF_UINT32::type::value_type> const& value)
|
||||
{
|
||||
object_[sfGasPrice] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the SetFee wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
|
||||
@@ -9,69 +9,38 @@ The ResourceManager module has these responsibilities:
|
||||
|
||||
## Description
|
||||
|
||||
To prevent monopolization of server resources or attacks on servers,
|
||||
resource consumption is monitored at each endpoint. When consumption
|
||||
exceeds certain thresholds, costs are imposed. Costs could include charging
|
||||
additional XRP for transactions, requiring a proof of work to be
|
||||
performed, or simply disconnecting the endpoint.
|
||||
To prevent monopolization of server resources or attacks on servers, resource consumption is monitored at each endpoint. When consumption exceeds certain thresholds, costs are imposed. Costs could include charging additional XRP for transactions, requiring a proof of work to be performed, or simply disconnecting the endpoint.
|
||||
|
||||
Currently, consumption endpoints include websocket connections used to
|
||||
service clients, and peer connections used to create the peer to peer
|
||||
overlay network implementing the XRPL protocol.
|
||||
Currently, consumption endpoints include websocket connections used to service clients, and peer connections used to create the peer to peer overlay network implementing the XRPL protocol.
|
||||
|
||||
The current "balance" of a Consumer represents resource consumption
|
||||
debt or credit. Debt is accrued when bad loads are imposed. Credit is
|
||||
granted when good loads are imposed. When the balance crosses heuristic
|
||||
thresholds, costs are increased on the endpoint. The balance is
|
||||
represented as a unitless relative quantity. This balance is currently
|
||||
held by the Entry struct in the impl/Entry.h file.
|
||||
The current "balance" of a Consumer represents resource consumption debt or credit. Debt is accrued when bad loads are imposed. Credit is granted when good loads are imposed. When the balance crosses heuristic thresholds, costs are increased on the endpoint. The balance is represented as a unitless relative quantity. This balance is currently held by the Entry struct in the impl/Entry.h file.
|
||||
|
||||
Costs associated with specific transactions are defined in the
|
||||
impl/Fees files.
|
||||
Costs associated with specific transactions are defined in the impl/Fees files.
|
||||
|
||||
Although RPC connections consume resources, they are transient and
|
||||
cannot be rate limited. It is advised not to expose RPC interfaces
|
||||
to the general public.
|
||||
Although RPC connections consume resources, they are transient and cannot be rate limited. It is advised not to expose RPC interfaces to the general public.
|
||||
|
||||
## Consumer Types
|
||||
|
||||
Consumers are placed into three classifications (as identified by the
|
||||
resource::Kind enumeration):
|
||||
Consumers are placed into three classifications (as identified by the resource::Kind enumeration):
|
||||
|
||||
- InBound,
|
||||
- OutBound, and
|
||||
- Admin
|
||||
|
||||
Each caller determines for itself the classification of the Consumer it is
|
||||
creating.
|
||||
Each caller determines for itself the classification of the Consumer it is creating.
|
||||
|
||||
## Resource Loading
|
||||
|
||||
It is expected that a client will impose a higher load on the server
|
||||
when it first connects: the client may need to catch up on transactions
|
||||
it has missed, or get trust lines, or transfer fees. The Manager must
|
||||
expect this initial peak load, but not allow that high load to continue
|
||||
because over the long term that would unduly stress the server.
|
||||
It is expected that a client will impose a higher load on the server when it first connects: the client may need to catch up on transactions it has missed, or get trust lines, or transfer fees. The Manager must expect this initial peak load, but not allow that high load to continue because over the long term that would unduly stress the server.
|
||||
|
||||
If a client places a sustained high load on the server, that client
|
||||
is initially given a warning message. If that high load continues
|
||||
the Manager may tell the heavily loaded server to drop the connection
|
||||
entirely and not allow re-connection for some amount of time.
|
||||
If a client places a sustained high load on the server, that client is initially given a warning message. If that high load continues the Manager may tell the heavily loaded server to drop the connection entirely and not allow re-connection for some amount of time.
|
||||
|
||||
Each load is monitored by capturing peaks and then decaying those peak
|
||||
values over time: this is implemented by the DecayingSample class.
|
||||
Each load is monitored by capturing peaks and then decaying those peak values over time: this is implemented by the DecayingSample class.
|
||||
|
||||
## Gossip
|
||||
|
||||
Each server in a cluster creates a list of IP addresses of end points
|
||||
that are imposing a significant load. This list is called Gossip, which
|
||||
is passed to other nodes in that cluster. Gossip helps individual
|
||||
servers in the cluster identify IP addresses that might be unduly loading
|
||||
the entire cluster. Again the recourse of the individual servers is to
|
||||
drop connections to those IP addresses that occur commonly in the gossip.
|
||||
Each server in a cluster creates a list of IP addresses of end points that are imposing a significant load. This list is called Gossip, which is passed to other nodes in that cluster. Gossip helps individual servers in the cluster identify IP addresses that might be unduly loading the entire cluster. Again the recourse of the individual servers is to drop connections to those IP addresses that occur commonly in the gossip.
|
||||
|
||||
## Access
|
||||
|
||||
In xrpld, the Application holds a unique instance of resource::Manager,
|
||||
which may be retrieved by calling the method
|
||||
`Application::getResourceManager()`.
|
||||
In xrpld, the Application holds a unique instance of resource::Manager, which may be retrieved by calling the method `Application::getResourceManager()`.
|
||||
|
||||
@@ -2,25 +2,18 @@
|
||||
|
||||
March 2020
|
||||
|
||||
The `SHAMap` is a Merkle tree (http://en.wikipedia.org/wiki/Merkle_tree).
|
||||
The `SHAMap` is also a radix trie of radix 16
|
||||
(http://en.wikipedia.org/wiki/Radix_tree).
|
||||
The `SHAMap` is a Merkle tree (http://en.wikipedia.org/wiki/Merkle_tree). The `SHAMap` is also a radix trie of radix 16 (http://en.wikipedia.org/wiki/Radix_tree).
|
||||
|
||||
The Merkle trie data structure is important because subtrees and even the entire
|
||||
tree can be compared with other trees in O(1) time by simply comparing the hashes.
|
||||
This makes it very efficient to determine if two `SHAMap`s contain the same set of
|
||||
transactions or account state modifications.
|
||||
The Merkle trie data structure is important because subtrees and even the entire tree can be compared with other trees in O(1) time by simply comparing the hashes. This makes it very efficient to determine if two `SHAMap`s contain the same set of transactions or account state modifications.
|
||||
|
||||
The radix trie property is helpful in that a key (hash) of a transaction
|
||||
or account state can be used to navigate the trie.
|
||||
The radix trie property is helpful in that a key (hash) of a transaction or account state can be used to navigate the trie.
|
||||
|
||||
A `SHAMap` is a trie with two node types:
|
||||
|
||||
1. SHAMapInnerNode
|
||||
2. SHAMapLeafNode
|
||||
|
||||
Both of these nodes directly inherit from SHAMapTreeNode which holds data
|
||||
common to both of the node types.
|
||||
Both of these nodes directly inherit from SHAMapTreeNode which holds data common to both of the node types.
|
||||
|
||||
All non-leaf nodes have type SHAMapInnerNode.
|
||||
|
||||
@@ -34,20 +27,13 @@ A given `SHAMap` always stores only one of three kinds of data:
|
||||
- Transactions without metadata, or
|
||||
- Account states.
|
||||
|
||||
So all of the leaf nodes of a particular `SHAMap` will always have a uniform type.
|
||||
The inner nodes carry no data other than the hash of the nodes beneath them.
|
||||
So all of the leaf nodes of a particular `SHAMap` will always have a uniform type. The inner nodes carry no data other than the hash of the nodes beneath them.
|
||||
|
||||
All nodes are owned by shared_ptrs resident in either other nodes, or in case of
|
||||
the root node, a shared_ptr in the `SHAMap` itself. The use of shared_ptrs
|
||||
permits more than one `SHAMap` at a time to share ownership of a node. This
|
||||
occurs (for example), when a copy of a `SHAMap` is made.
|
||||
All nodes are owned by shared_ptrs resident in either other nodes, or in case of the root node, a shared_ptr in the `SHAMap` itself. The use of shared_ptrs permits more than one `SHAMap` at a time to share ownership of a node. This occurs (for example), when a copy of a `SHAMap` is made.
|
||||
|
||||
Copies are made with the `snapShot` function as opposed to the `SHAMap` copy
|
||||
constructor. See the section on `SHAMap` creation for more details about
|
||||
`snapShot`.
|
||||
Copies are made with the `snapShot` function as opposed to the `SHAMap` copy constructor. See the section on `SHAMap` creation for more details about `snapShot`.
|
||||
|
||||
Sequence numbers are used to further customize the node ownership strategy. See
|
||||
the section on sequence numbers for details on sequence numbers.
|
||||
Sequence numbers are used to further customize the node ownership strategy. See the section on sequence numbers for details on sequence numbers.
|
||||
|
||||

|
||||
|
||||
@@ -58,145 +44,63 @@ There are two different ways of building and using a `SHAMap`:
|
||||
1. A mutable `SHAMap` and
|
||||
2. An immutable `SHAMap`
|
||||
|
||||
The distinction here is not of the classic C++ immutable-means-unchanging sense.
|
||||
An immutable `SHAMap` contains _nodes_ that are immutable. Also, once a node has
|
||||
been located in an immutable `SHAMap`, that node is guaranteed to persist in that
|
||||
`SHAMap` for the lifetime of the `SHAMap`.
|
||||
The distinction here is not of the classic C++ immutable-means-unchanging sense. An immutable `SHAMap` contains _nodes_ that are immutable. Also, once a node has been located in an immutable `SHAMap`, that node is guaranteed to persist in that `SHAMap` for the lifetime of the `SHAMap`.
|
||||
|
||||
So, somewhat counter-intuitively, an immutable `SHAMap` may grow as new nodes are
|
||||
introduced. But an immutable `SHAMap` will never get smaller (until it entirely
|
||||
evaporates when it is destroyed). Nodes, once introduced to the immutable
|
||||
`SHAMap`, also never change their location in memory. So nodes in an immutable
|
||||
`SHAMap` can be handled using raw pointers (if you're careful).
|
||||
So, somewhat counter-intuitively, an immutable `SHAMap` may grow as new nodes are introduced. But an immutable `SHAMap` will never get smaller (until it entirely evaporates when it is destroyed). Nodes, once introduced to the immutable `SHAMap`, also never change their location in memory. So nodes in an immutable `SHAMap` can be handled using raw pointers (if you're careful).
|
||||
|
||||
One consequence of this design is that an immutable `SHAMap` can never be
|
||||
"trimmed". There is no way to identify unnecessary nodes in an immutable `SHAMap`
|
||||
that could be removed. Once a node has been brought into the in-memory `SHAMap`,
|
||||
that node stays in memory for the life of the `SHAMap`.
|
||||
One consequence of this design is that an immutable `SHAMap` can never be "trimmed". There is no way to identify unnecessary nodes in an immutable `SHAMap` that could be removed. Once a node has been brought into the in-memory `SHAMap`, that node stays in memory for the life of the `SHAMap`.
|
||||
|
||||
Most `SHAMap`s are immutable, in the sense that they don't modify or remove their
|
||||
contained nodes.
|
||||
Most `SHAMap`s are immutable, in the sense that they don't modify or remove their contained nodes.
|
||||
|
||||
An example where a mutable `SHAMap` is required is when we want to apply
|
||||
transactions to the last closed ledger. To do so we'd make a mutable snapshot
|
||||
of the state trie and then start applying transactions to it. Because the
|
||||
snapshot is mutable, changes to nodes in the snapshot will not affect nodes in
|
||||
other `SHAMap`s.
|
||||
An example where a mutable `SHAMap` is required is when we want to apply transactions to the last closed ledger. To do so we'd make a mutable snapshot of the state trie and then start applying transactions to it. Because the snapshot is mutable, changes to nodes in the snapshot will not affect nodes in other `SHAMap`s.
|
||||
|
||||
An example using a immutable ledger would be when there's an open ledger and
|
||||
some piece of code wishes to query the state of the ledger. In this case we
|
||||
don't wish to change the state of the `SHAMap`, so we'd use an immutable snapshot.
|
||||
An example using a immutable ledger would be when there's an open ledger and some piece of code wishes to query the state of the ledger. In this case we don't wish to change the state of the `SHAMap`, so we'd use an immutable snapshot.
|
||||
|
||||
## Sequence numbers
|
||||
|
||||
Both `SHAMap`s and their nodes carry a sequence number. This is simply an
|
||||
unsigned number that indicates ownership or membership, or a non-membership.
|
||||
Both `SHAMap`s and their nodes carry a sequence number. This is simply an unsigned number that indicates ownership or membership, or a non-membership.
|
||||
|
||||
`SHAMap`s sequence numbers normally start out as 1. However when a snap-shot of
|
||||
a `SHAMap` is made, the copy's sequence number is 1 greater than the original.
|
||||
`SHAMap`s sequence numbers normally start out as 1. However when a snap-shot of a `SHAMap` is made, the copy's sequence number is 1 greater than the original.
|
||||
|
||||
The nodes of a `SHAMap` have their own copy of a sequence number. If the `SHAMap`
|
||||
is mutable, meaning it can change, then all of its nodes must have the
|
||||
same sequence number as the `SHAMap` itself. This enforces an invariant that none
|
||||
of the nodes are shared with other `SHAMap`s.
|
||||
The nodes of a `SHAMap` have their own copy of a sequence number. If the `SHAMap` is mutable, meaning it can change, then all of its nodes must have the same sequence number as the `SHAMap` itself. This enforces an invariant that none of the nodes are shared with other `SHAMap`s.
|
||||
|
||||
When a `SHAMap` needs to have a private copy of a node, not shared by any other
|
||||
`SHAMap`, it first clones it and then sets the new copy to have a sequence number
|
||||
equal to the `SHAMap` sequence number. The `unshareNode` is a private utility
|
||||
which automates the task of first checking if the node is already sharable, and
|
||||
if so, cloning it and giving it the proper sequence number. An example case
|
||||
where a private copy is needed is when an inner node needs to have a child
|
||||
pointer altered. Any modification to a node will require a non-shared node.
|
||||
When a `SHAMap` needs to have a private copy of a node, not shared by any other `SHAMap`, it first clones it and then sets the new copy to have a sequence number equal to the `SHAMap` sequence number. The `unshareNode` is a private utility which automates the task of first checking if the node is already sharable, and if so, cloning it and giving it the proper sequence number. An example case where a private copy is needed is when an inner node needs to have a child pointer altered. Any modification to a node will require a non-shared node.
|
||||
|
||||
When a `SHAMap` decides that it is safe to share a node of its own, it sets the
|
||||
node's sequence number to 0 (a `SHAMap` never has a sequence number of 0). This
|
||||
is done for every node in the trie when `SHAMap::walkSubTree` is executed.
|
||||
When a `SHAMap` decides that it is safe to share a node of its own, it sets the node's sequence number to 0 (a `SHAMap` never has a sequence number of 0). This is done for every node in the trie when `SHAMap::walkSubTree` is executed.
|
||||
|
||||
Note that other objects in xrpld also have sequence numbers (e.g. ledgers).
|
||||
The `SHAMap` and node sequence numbers should not be confused with these other
|
||||
sequence numbers (no relation).
|
||||
Note that other objects in xrpld also have sequence numbers (e.g. ledgers). The `SHAMap` and node sequence numbers should not be confused with these other sequence numbers (no relation).
|
||||
|
||||
## SHAMap Creation
|
||||
|
||||
A `SHAMap` is usually not created from vacuum. Once an initial `SHAMap` is
|
||||
constructed, later `SHAMap`s are usually created by calling snapShot(bool
|
||||
isMutable) on the original `SHAMap`. The returned `SHAMap` has the expected
|
||||
characteristics (mutable or immutable) based on the passed in flag.
|
||||
A `SHAMap` is usually not created from vacuum. Once an initial `SHAMap` is constructed, later `SHAMap`s are usually created by calling snapShot(bool isMutable) on the original `SHAMap`. The returned `SHAMap` has the expected characteristics (mutable or immutable) based on the passed in flag.
|
||||
|
||||
It is cheaper to make an immutable snapshot of a `SHAMap` than to make a mutable
|
||||
snapshot. If the `SHAMap` snapshot is mutable then sharable nodes must be
|
||||
copied before they are placed in the mutable map.
|
||||
It is cheaper to make an immutable snapshot of a `SHAMap` than to make a mutable snapshot. If the `SHAMap` snapshot is mutable then sharable nodes must be copied before they are placed in the mutable map.
|
||||
|
||||
A new `SHAMap` is created with each new ledger round. Transactions not executed
|
||||
in the previous ledger populate the `SHAMap` for the new ledger.
|
||||
A new `SHAMap` is created with each new ledger round. Transactions not executed in the previous ledger populate the `SHAMap` for the new ledger.
|
||||
|
||||
## Storing SHAMap data in the database
|
||||
|
||||
When consensus is reached, the ledger is closed. As part of this process, the
|
||||
`SHAMap` is stored to the database by calling `SHAMap::flushDirty`.
|
||||
When consensus is reached, the ledger is closed. As part of this process, the `SHAMap` is stored to the database by calling `SHAMap::flushDirty`.
|
||||
|
||||
Both `unshare()` and `flushDirty` walk the `SHAMap` by calling
|
||||
`SHAMap::walkSubTree`. As `unshare()` walks the trie, nodes are not written to
|
||||
the database, and as `flushDirty` walks the trie nodes are written to the
|
||||
database. `walkSubTree` visits every node in the trie. This process must ensure
|
||||
that each node is only owned by this trie, and so "unshares" as it walks each
|
||||
node (from the root down). This is done in the `preFlushNode` function by
|
||||
ensuring that the node has a sequence number equal to that of the `SHAMap`. If
|
||||
the node doesn't, it is cloned.
|
||||
Both `unshare()` and `flushDirty` walk the `SHAMap` by calling `SHAMap::walkSubTree`. As `unshare()` walks the trie, nodes are not written to the database, and as `flushDirty` walks the trie nodes are written to the database. `walkSubTree` visits every node in the trie. This process must ensure that each node is only owned by this trie, and so "unshares" as it walks each node (from the root down). This is done in the `preFlushNode` function by ensuring that the node has a sequence number equal to that of the `SHAMap`. If the node doesn't, it is cloned.
|
||||
|
||||
For each inner node encountered (starting with the root node), each of the
|
||||
children are inspected (from 1 to 16). For each child, if it has a non-zero
|
||||
sequence number (unshareable), the child is first copied. Then if the child is
|
||||
an inner node, we recurse down to that node's children. Otherwise we've found a
|
||||
leaf node and that node is written to the database. A count of each leaf node
|
||||
that is visited is kept. The hash of the data in the leaf node is computed at
|
||||
this time, and the child is reassigned back into the parent inner node just in
|
||||
case the COW operation created a new pointer to this leaf node.
|
||||
For each inner node encountered (starting with the root node), each of the children are inspected (from 1 to 16). For each child, if it has a non-zero sequence number (unshareable), the child is first copied. Then if the child is an inner node, we recurse down to that node's children. Otherwise we've found a leaf node and that node is written to the database. A count of each leaf node that is visited is kept. The hash of the data in the leaf node is computed at this time, and the child is reassigned back into the parent inner node just in case the COW operation created a new pointer to this leaf node.
|
||||
|
||||
After processing each node, the node is then marked as sharable again by setting
|
||||
its sequence number to 0.
|
||||
After processing each node, the node is then marked as sharable again by setting its sequence number to 0.
|
||||
|
||||
After all of an inner node's children are processed, then its hash is updated
|
||||
and the inner node is written to the database. Then this inner node is assigned
|
||||
back into it's parent node, again in case the COW operation created a new
|
||||
pointer to it.
|
||||
After all of an inner node's children are processed, then its hash is updated and the inner node is written to the database. Then this inner node is assigned back into it's parent node, again in case the COW operation created a new pointer to it.
|
||||
|
||||
## Walking a SHAMap
|
||||
|
||||
The private function `SHAMap::walkTowardsKey` is a good example of _how_ to walk
|
||||
a `SHAMap`, and the various functions that call `walkTowardsKey` are good examples
|
||||
of _why_ one would want to walk a `SHAMap` (e.g. `SHAMap::findKey`).
|
||||
`walkTowardsKey` always starts at the root of the `SHAMap` and traverses down
|
||||
through the inner nodes, looking for a leaf node along a path in the trie
|
||||
designated by a `uint256`.
|
||||
The private function `SHAMap::walkTowardsKey` is a good example of _how_ to walk a `SHAMap`, and the various functions that call `walkTowardsKey` are good examples of _why_ one would want to walk a `SHAMap` (e.g. `SHAMap::findKey`). `walkTowardsKey` always starts at the root of the `SHAMap` and traverses down through the inner nodes, looking for a leaf node along a path in the trie designated by a `uint256`.
|
||||
|
||||
As one walks the trie, one can _optionally_ keep a stack of nodes that one has
|
||||
passed through. This isn't necessary for walking the trie, but many clients
|
||||
will use the stack after finding the desired node. For example if one is
|
||||
deleting a node from the trie, the stack is handy for repairing invariants in
|
||||
the trie after the deletion.
|
||||
As one walks the trie, one can _optionally_ keep a stack of nodes that one has passed through. This isn't necessary for walking the trie, but many clients will use the stack after finding the desired node. For example if one is deleting a node from the trie, the stack is handy for repairing invariants in the trie after the deletion.
|
||||
|
||||
To assist in walking the trie, `SHAMap::walkTowardsKey` uses a `SHAMapNodeID`
|
||||
that identifies a node by its path from the root and its depth in the trie. The
|
||||
path is just a "list" of numbers, each in the range [0 .. 15], depicting which
|
||||
child was chosen at each node starting from the root. Each choice is represented
|
||||
by 4 bits, and then packed in sequence into a `uint256` (such that the longest
|
||||
path possible has 256 / 4 = 64 steps). The high 4 bits of the first byte
|
||||
identify which child of the root is chosen, the lower 4 bits of the first byte
|
||||
identify the child of that node, and so on. The `SHAMapNodeID` identifying the
|
||||
root node has an ID of 0 and a depth of 0. See `selectBranch` for details of
|
||||
how we use a `SHAMapNodeID` to select a "branch" (child) by indexing into a
|
||||
path at a given depth.
|
||||
To assist in walking the trie, `SHAMap::walkTowardsKey` uses a `SHAMapNodeID` that identifies a node by its path from the root and its depth in the trie. The path is just a "list" of numbers, each in the range [0 .. 15], depicting which child was chosen at each node starting from the root. Each choice is represented by 4 bits, and then packed in sequence into a `uint256` (such that the longest path possible has 256 / 4 = 64 steps). The high 4 bits of the first byte identify which child of the root is chosen, the lower 4 bits of the first byte identify the child of that node, and so on. The `SHAMapNodeID` identifying the root node has an ID of 0 and a depth of 0. See `selectBranch` for details of how we use a `SHAMapNodeID` to select a "branch" (child) by indexing into a path at a given depth.
|
||||
|
||||
While the current node is an inner node, traversing down the trie from the root
|
||||
continues, unless the path indicates a child that does not exist. And in this
|
||||
case, `nullptr` is returned to indicate no leaf node along the given path
|
||||
exists. Otherwise a leaf node is found and a (non-owning) pointer to it is
|
||||
returned. At each step, if a stack is requested, a
|
||||
`pair<shared_ptr<SHAMapTreeNode>, SHAMapNodeID>` is pushed onto the stack.
|
||||
While the current node is an inner node, traversing down the trie from the root continues, unless the path indicates a child that does not exist. And in this case, `nullptr` is returned to indicate no leaf node along the given path exists. Otherwise a leaf node is found and a (non-owning) pointer to it is returned. At each step, if a stack is requested, a `pair<shared_ptr<SHAMapTreeNode>, SHAMapNodeID>` is pushed onto the stack.
|
||||
|
||||
When a child node is found by `selectBranch`, the traversal to that node
|
||||
consists of two steps:
|
||||
When a child node is found by `selectBranch`, the traversal to that node consists of two steps:
|
||||
|
||||
1. Update the `shared_ptr` to the current node.
|
||||
2. Update the `SHAMapNodeID`.
|
||||
@@ -207,126 +111,79 @@ The first step consists of several attempts to find the node in various places:
|
||||
2. In the node cache.
|
||||
3. In the database.
|
||||
|
||||
If the node is not found in the trie, then it is installed into the trie as part
|
||||
of the traversal process.
|
||||
If the node is not found in the trie, then it is installed into the trie as part of the traversal process.
|
||||
|
||||
## Late-arriving Nodes
|
||||
|
||||
As we noted earlier, `SHAMap`s (even immutable ones) may grow. If a `SHAMap` is
|
||||
searching for a node and runs into an empty spot in the trie, then the `SHAMap`
|
||||
looks to see if the node exists but has not yet been made part of the map. This
|
||||
operation is performed in the `SHAMap::fetchNodeNT()` method. The _NT_
|
||||
is this case stands for 'No Throw'.
|
||||
As we noted earlier, `SHAMap`s (even immutable ones) may grow. If a `SHAMap` is searching for a node and runs into an empty spot in the trie, then the `SHAMap` looks to see if the node exists but has not yet been made part of the map. This operation is performed in the `SHAMap::fetchNodeNT()` method. The _NT_ is this case stands for 'No Throw'.
|
||||
|
||||
The `fetchNodeNT()` method goes through three phases:
|
||||
|
||||
1. By calling `cacheLookup()` we attempt to locate the missing node in the
|
||||
TreeNodeCache. The TreeNodeCache is a cache of immutable SHAMapTreeNodes
|
||||
that are shared across all `SHAMap`s.
|
||||
1. By calling `cacheLookup()` we attempt to locate the missing node in the TreeNodeCache. The TreeNodeCache is a cache of immutable SHAMapTreeNodes that are shared across all `SHAMap`s.
|
||||
|
||||
Any SHAMapLeafNode that is immutable has a sequence number of zero
|
||||
(sharable). When a mutable `SHAMap` is created then its SHAMapTreeNodes are
|
||||
given non-zero sequence numbers (unshareable). But all nodes in the
|
||||
TreeNodeCache are immutable, so if one is found here, its sequence number
|
||||
will be 0.
|
||||
Any SHAMapLeafNode that is immutable has a sequence number of zero (sharable). When a mutable `SHAMap` is created then its SHAMapTreeNodes are given non-zero sequence numbers (unshareable). But all nodes in the TreeNodeCache are immutable, so if one is found here, its sequence number will be 0.
|
||||
|
||||
2. If the node is not in the TreeNodeCache, we attempt to locate the node
|
||||
in the historic data stored by the data base. The call to
|
||||
`fetchNodeFromDB(hash)` does that work for us.
|
||||
2. If the node is not in the TreeNodeCache, we attempt to locate the node in the historic data stored by the data base. The call to `fetchNodeFromDB(hash)` does that work for us.
|
||||
|
||||
3. Finally if a filter exists, we check if it can supply the node. This is
|
||||
typically the LedgerMaster which tracks the current ledger and ledgers
|
||||
in the process of closing.
|
||||
3. Finally if a filter exists, we check if it can supply the node. This is typically the LedgerMaster which tracks the current ledger and ledgers in the process of closing.
|
||||
|
||||
## Canonicalize
|
||||
|
||||
`canonicalize()` is called every time a node is introduced into the `SHAMap`.
|
||||
|
||||
A call to `canonicalize()` stores the node in the `TreeNodeCache` if it does not
|
||||
already exist in the `TreeNodeCache`.
|
||||
A call to `canonicalize()` stores the node in the `TreeNodeCache` if it does not already exist in the `TreeNodeCache`.
|
||||
|
||||
The calls to `canonicalize()` make sure that if the resulting node is already in
|
||||
the `SHAMap`, node `TreeNodeCache` or database, then we don't create duplicates
|
||||
by favoring the copy already in the `TreeNodeCache`.
|
||||
The calls to `canonicalize()` make sure that if the resulting node is already in the `SHAMap`, node `TreeNodeCache` or database, then we don't create duplicates by favoring the copy already in the `TreeNodeCache`.
|
||||
|
||||
By using `canonicalize()` we manage a thread race condition where two different
|
||||
threads might both recognize the lack of a SHAMapLeafNode at the same time
|
||||
(during a fetch). If they both attempt to insert the node into the `SHAMap`, then
|
||||
`canonicalize` makes sure that the first node in wins and the slower thread
|
||||
receives back a pointer to the node inserted by the faster thread. Recall
|
||||
that these two `SHAMap`s will share the same `TreeNodeCache`.
|
||||
By using `canonicalize()` we manage a thread race condition where two different threads might both recognize the lack of a SHAMapLeafNode at the same time (during a fetch). If they both attempt to insert the node into the `SHAMap`, then `canonicalize` makes sure that the first node in wins and the slower thread receives back a pointer to the node inserted by the faster thread. Recall that these two `SHAMap`s will share the same `TreeNodeCache`.
|
||||
|
||||
## `TreeNodeCache`
|
||||
|
||||
The `TreeNodeCache` is a `std::unordered_map` keyed on the hash of the
|
||||
`SHAMap` node. The stored type consists of `shared_ptr<SHAMapTreeNode>`,
|
||||
`weak_ptr<SHAMapTreeNode>`, and a time point indicating the most recent
|
||||
access of this node in the cache. The time point is based on
|
||||
`std::chrono::steady_clock`.
|
||||
The `TreeNodeCache` is a `std::unordered_map` keyed on the hash of the `SHAMap` node. The stored type consists of `shared_ptr<SHAMapTreeNode>`, `weak_ptr<SHAMapTreeNode>`, and a time point indicating the most recent access of this node in the cache. The time point is based on `std::chrono::steady_clock`.
|
||||
|
||||
The container uses a cryptographically secure hash that is randomly seeded.
|
||||
|
||||
The `TreeNodeCache` also carries with it various data used for statistics
|
||||
and logging, and a target age for the contained nodes. When the target age
|
||||
for a node is exceeded, and there are no more references to the node, the
|
||||
node is removed from the `TreeNodeCache`.
|
||||
The `TreeNodeCache` also carries with it various data used for statistics and logging, and a target age for the contained nodes. When the target age for a node is exceeded, and there are no more references to the node, the node is removed from the `TreeNodeCache`.
|
||||
|
||||
## `FullBelowCache`
|
||||
|
||||
This cache remembers which trie keys have all of their children resident in a
|
||||
`SHAMap`. This optimizes the process of acquiring a complete trie. This is used
|
||||
when creating the missing nodes list. Missing nodes are those nodes that a
|
||||
`SHAMap` refers to but that are not stored in the local database.
|
||||
This cache remembers which trie keys have all of their children resident in a `SHAMap`. This optimizes the process of acquiring a complete trie. This is used when creating the missing nodes list. Missing nodes are those nodes that a `SHAMap` refers to but that are not stored in the local database.
|
||||
|
||||
As a depth-first walk of a `SHAMap` is performed, if an inner node answers true to
|
||||
`isFullBelow()` then it is known that none of this node's children are missing
|
||||
nodes, and thus that subtree does not need to be walked. These nodes are stored
|
||||
in the FullBelowCache. Subsequent walks check the FullBelowCache first when
|
||||
encountering a node, and ignore that subtree if found.
|
||||
As a depth-first walk of a `SHAMap` is performed, if an inner node answers true to `isFullBelow()` then it is known that none of this node's children are missing nodes, and thus that subtree does not need to be walked. These nodes are stored in the FullBelowCache. Subsequent walks check the FullBelowCache first when encountering a node, and ignore that subtree if found.
|
||||
|
||||
## `SHAMapTreeNode`
|
||||
|
||||
This is an abstract base class for the concrete node types. It holds the
|
||||
following common data:
|
||||
This is an abstract base class for the concrete node types. It holds the following common data:
|
||||
|
||||
1. A hash
|
||||
2. An identifier used to perform copy-on-write operations
|
||||
|
||||
### `SHAMapInnerNode`
|
||||
|
||||
`SHAMapInnerNode` publicly inherits directly from `SHAMapTreeNode`. It holds
|
||||
the following data:
|
||||
`SHAMapInnerNode` publicly inherits directly from `SHAMapTreeNode`. It holds the following data:
|
||||
|
||||
1. Up to 16 child nodes, each held with a shared_ptr.
|
||||
2. A hash for each child.
|
||||
3. A bitset to indicate which of the 16 children exist.
|
||||
4. An identifier used to determine whether the map below this node is
|
||||
fully populated
|
||||
4. An identifier used to determine whether the map below this node is fully populated
|
||||
|
||||
### `SHAMapLeafNode`
|
||||
|
||||
`SHAMapLeafNode` is an abstract class which publicly inherits directly from
|
||||
`SHAMapTreeNode`. It isIt holds the
|
||||
following data:
|
||||
`SHAMapLeafNode` is an abstract class which publicly inherits directly from `SHAMapTreeNode`. It isIt holds the following data:
|
||||
|
||||
1. A shared_ptr to a const SHAMapItem.
|
||||
|
||||
#### `SHAMapAccountStateLeafNode`
|
||||
|
||||
`SHAMapAccountStateLeafNode` is a class which publicly inherits directly from
|
||||
`SHAMapLeafNode`. It is used to represent entries (i.e. account objects, escrow
|
||||
objects, trust lines, etc.) in a state map.
|
||||
`SHAMapAccountStateLeafNode` is a class which publicly inherits directly from `SHAMapLeafNode`. It is used to represent entries (i.e. account objects, escrow objects, trust lines, etc.) in a state map.
|
||||
|
||||
#### `SHAMapTxLeafNode`
|
||||
|
||||
`SHAMapTxLeafNode` is a class which publicly inherits directly from
|
||||
`SHAMapLeafNode`. It is used to represent transactions in a state map.
|
||||
`SHAMapTxLeafNode` is a class which publicly inherits directly from `SHAMapLeafNode`. It is used to represent transactions in a state map.
|
||||
|
||||
#### `SHAMapTxPlusMetaLeafNode`
|
||||
|
||||
`SHAMapTxPlusMetaLeafNode` is a class which publicly inherits directly from
|
||||
`SHAMapLeafNode`. It is used to represent transactions along with metadata
|
||||
associated with this transaction in a state map.
|
||||
`SHAMapTxPlusMetaLeafNode` is a class which publicly inherits directly from `SHAMapLeafNode`. It is used to represent transactions along with metadata associated with this transaction in a state map.
|
||||
|
||||
## SHAMapItem
|
||||
|
||||
|
||||
@@ -109,11 +109,6 @@ public:
|
||||
* @param lpTokens current LPT balance
|
||||
* @param lpTokensWithdraw amount of tokens to withdraw
|
||||
* @param tfee trading fee in basis points
|
||||
* @param freezeHandling whether a frozen balance is reported as zero
|
||||
* @param authHandling whether an unauthorized MPT balance is reported as
|
||||
* zero
|
||||
* @param reserveHandling whether the recipient owner-reserve check is
|
||||
* enforced when a trustline or MPToken has to be auto-created
|
||||
* @param withdrawAll if withdrawing all lptokens
|
||||
* @param priorBalance balance before fees
|
||||
* @return
|
||||
@@ -133,7 +128,6 @@ public:
|
||||
std::uint16_t tfee,
|
||||
FreezeHandling freezeHandling,
|
||||
AuthHandling authHandling,
|
||||
ReserveHandling reserveHandling,
|
||||
WithdrawAll withdrawAll,
|
||||
XRPAmount const& priorBalance,
|
||||
beast::Journal const& journal);
|
||||
@@ -156,11 +150,6 @@ public:
|
||||
* @param lpTokensAMMBalance current AMM LPT balance
|
||||
* @param lpTokensWithdraw amount of lptokens to withdraw
|
||||
* @param tfee trading fee in basis points
|
||||
* @param freezeHandling whether a frozen balance is reported as zero
|
||||
* @param authHandling whether an unauthorized MPT balance is reported as
|
||||
* zero
|
||||
* @param reserveHandling whether the recipient owner-reserve check is
|
||||
* enforced when a trustline or MPToken has to be auto-created
|
||||
* @param withdrawAll if withdraw all lptokens
|
||||
* @param priorBalance balance before fees
|
||||
* @return
|
||||
@@ -180,7 +169,6 @@ public:
|
||||
std::uint16_t tfee,
|
||||
FreezeHandling freezeHandling,
|
||||
AuthHandling authHandling,
|
||||
ReserveHandling reserveHandling,
|
||||
WithdrawAll withdrawAll,
|
||||
XRPAmount const& priorBalance,
|
||||
beast::Journal const& journal);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
# check-tools snapshots
|
||||
|
||||
These files capture the output of [`bin/check-tools.sh`](../../bin/check-tools.sh)
|
||||
— the version and resolved store path of each development tool — in each Nix
|
||||
environment:
|
||||
These files capture the output of [`bin/check-tools.sh`](../../bin/check-tools.sh) — the version and resolved store path of each development tool — in each Nix environment:
|
||||
|
||||
| File | Environment |
|
||||
| ---------------------- | ------------------------------------ |
|
||||
@@ -10,26 +8,15 @@ environment:
|
||||
| `nix-ubuntu-arm64.txt` | `nix-ubuntu` CI image, `linux/arm64` |
|
||||
| `macos.txt` | macOS, inside `nix develop` |
|
||||
|
||||
The [`check-tools`](../../.github/workflows/check-tools.yml) workflow regenerates
|
||||
each snapshot in its environment and fails if it differs from the committed file.
|
||||
So if you change the environment (bump the image tag in
|
||||
[`linux.json`](../../.github/scripts/strategy-matrix/linux.json), update
|
||||
`flake.lock`, change the tool list in `check-tools.sh`, …) you must regenerate
|
||||
and commit the affected snapshots.
|
||||
The [`check-tools`](../../.github/workflows/check-tools.yml) workflow regenerates each snapshot in its environment and fails if it differs from the committed file. So if you change the environment (bump the image tag in [`linux.json`](../../.github/scripts/strategy-matrix/linux.json), update `flake.lock`, change the tool list in `check-tools.sh`, …) you must regenerate and commit the affected snapshots.
|
||||
|
||||
Each snapshot is `check-tools.sh` stdout with the git-clone connectivity check
|
||||
skipped (`CHECK_TOOLS_SKIP_CLONE=1`), so it is deterministic for a given
|
||||
environment. On macOS the dev-shell greeting that `nix develop` prints first is
|
||||
dropped with `sed -n '/^Detected OS:/,$p'`.
|
||||
Each snapshot is `check-tools.sh` stdout with the git-clone connectivity check skipped (`CHECK_TOOLS_SKIP_CLONE=1`), so it is deterministic for a given environment. On macOS the dev-shell greeting that `nix develop` prints first is dropped with `sed -n '/^Detected OS:/,$p'`.
|
||||
|
||||
The store paths carry their derivation hash, so they change whenever a tool is
|
||||
rebuilt — a `flake.lock` update generally rewrites most of them even when no
|
||||
version moves. That is deliberate: it makes tooling changes visible in review.
|
||||
The store paths carry their derivation hash, so they change whenever a tool is rebuilt — a `flake.lock` update generally rewrites most of them even when no version moves. That is deliberate: it makes tooling changes visible in review.
|
||||
|
||||
## Regenerating
|
||||
|
||||
The two Linux snapshots come from the `nix-ubuntu` image (Docker or a compatible
|
||||
runtime such as Apple `container`). The image tag is pinned in `linux.json`:
|
||||
The two Linux snapshots come from the `nix-ubuntu` image (Docker or a compatible runtime such as Apple `container`). The image tag is pinned in `linux.json`:
|
||||
|
||||
```bash
|
||||
img="ghcr.io/xrplf/xrpld/nix-ubuntu:$(jq -r .image_tag .github/scripts/strategy-matrix/linux.json)"
|
||||
@@ -40,12 +27,9 @@ for arch in amd64 arm64; do
|
||||
done
|
||||
```
|
||||
|
||||
(With Docker, replace `container run … -a "${arch}"` with
|
||||
`docker run … --platform "linux/${arch}"`.)
|
||||
(With Docker, replace `container run … -a "${arch}"` with `docker run … --platform "linux/${arch}"`.)
|
||||
|
||||
The macOS snapshot is generated locally. `CI=` is unset so `check-tools.sh`
|
||||
checks the full dev-shell tool set (it otherwise skips some tools when `CI` is
|
||||
set):
|
||||
The macOS snapshot is generated locally. `CI=` is unset so `check-tools.sh` checks the full dev-shell tool set (it otherwise skips some tools when `CI` is set):
|
||||
|
||||
```bash
|
||||
CI= nix develop -c bash -c 'CHECK_TOOLS_SKIP_CLONE=1 bash bin/check-tools.sh' |
|
||||
|
||||
@@ -131,9 +131,6 @@ Rust toolchain:
|
||||
✅ rust-analyzer
|
||||
rust-analyzer 1.97.1 (8bab26f4 2026-07-14)
|
||||
/nix/store/j6apc5pmd0giy15da9p650r8zklslmvi-rust-analyzer-preview-1.97.1-aarch64-apple-darwin/bin/rust-analyzer
|
||||
✅ rust-nightly
|
||||
rustc 1.99.0-nightly (87e5904f5 2026-07-20)
|
||||
/nix/store/fqpjz4l0nsnji8b2pz57mnj0akbp6hcl-rust-nightly/bin/rust-nightly
|
||||
✅ rustc
|
||||
rustc 1.97.1 (8bab26f4f 2026-07-14)
|
||||
/nix/store/bnfk1sl4s9angb0vj1cj9a5y5zvqinwy-rust-minimal-1.97.1/bin/rustc
|
||||
@@ -143,4 +140,4 @@ Rust toolchain:
|
||||
|
||||
Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set).
|
||||
|
||||
✅ All 45 checked tools are present and runnable.
|
||||
✅ All 44 checked tools are present and runnable.
|
||||
|
||||
@@ -131,9 +131,6 @@ Rust toolchain:
|
||||
✅ rust-analyzer
|
||||
rust-analyzer 1.97.1 (8bab26f 2026-07-14)
|
||||
/nix/store/lr3m97p3hx1k22a7c44pb0wa7rbayhfi-rust-analyzer-preview-1.97.1-x86_64-unknown-linux-gnu/bin/rust-analyzer
|
||||
✅ rust-nightly
|
||||
rustc 1.99.0-nightly (87e5904f5 2026-07-20)
|
||||
/nix/store/j7kf7a5h4xypzp6x1skg4dsdx2k4fwb3-rust-nightly/bin/rust-nightly
|
||||
✅ rustc
|
||||
rustc 1.97.1 (8bab26f4f 2026-07-14)
|
||||
/nix/store/40d3mzka7r1ps71l0yv2fs6616nbw85m-rust-minimal-1.97.1/bin/rustc
|
||||
@@ -171,4 +168,4 @@ Mold:
|
||||
|
||||
Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set).
|
||||
|
||||
✅ All 53 checked tools are present and runnable.
|
||||
✅ All 52 checked tools are present and runnable.
|
||||
|
||||
@@ -131,9 +131,6 @@ Rust toolchain:
|
||||
✅ rust-analyzer
|
||||
rust-analyzer 1.97.1 (8bab26f 2026-07-14)
|
||||
/nix/store/262830dlw2517lnagfx7i7agqgl4fmsd-rust-analyzer-preview-1.97.1-aarch64-unknown-linux-gnu/bin/rust-analyzer
|
||||
✅ rust-nightly
|
||||
rustc 1.99.0-nightly (87e5904f5 2026-07-20)
|
||||
/nix/store/c59pxk1yikdlf129qwyg4fplmxcrha0k-rust-nightly/bin/rust-nightly
|
||||
✅ rustc
|
||||
rustc 1.97.1 (8bab26f4f 2026-07-14)
|
||||
/nix/store/a6p27cg6b8szfixfyvkssx6l0c345zw8-rust-minimal-1.97.1/bin/rustc
|
||||
@@ -171,4 +168,4 @@ Mold:
|
||||
|
||||
Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set).
|
||||
|
||||
✅ All 53 checked tools are present and runnable.
|
||||
✅ All 52 checked tools are present and runnable.
|
||||
|
||||
@@ -10,8 +10,6 @@ RUN mkdir -p ~/.config/nix && \
|
||||
COPY nix/ci-env.nix /tmp/build/nix/ci-env.nix
|
||||
COPY nix/linux.nix /tmp/build/nix/linux.nix
|
||||
COPY nix/packages.nix /tmp/build/nix/packages.nix
|
||||
COPY nix/rust-nightly.sh /tmp/build/nix/rust-nightly.sh
|
||||
COPY nix/rust.nix /tmp/build/nix/rust.nix
|
||||
COPY nix/utils.nix /tmp/build/nix/utils.nix
|
||||
COPY flake.nix /tmp/build/
|
||||
COPY flake.lock /tmp/build/
|
||||
|
||||
@@ -1,101 +1,52 @@
|
||||
# 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`.
|
||||
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`).
|
||||
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:
|
||||
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` | |
|
||||
| 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.
|
||||
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.
|
||||
- compiles the C++ test programs in
|
||||
[`test_files/cpp/sources/`](./test_files/cpp/sources) with both `g++` and
|
||||
`clang++`, and sanitizers, and
|
||||
- compiles the Rust test programs in
|
||||
[`test_files/rust/sources/`](./test_files/rust/sources) with `rustc`, and
|
||||
builds the [`test_files/rust/proc_macro/`](./test_files/rust/proc_macro)
|
||||
workspace with `cargo` to exercise proc-macro dylib loading.
|
||||
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.
|
||||
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.
|
||||
- compiles the C++ test programs in [`test_files/cpp/sources/`](./test_files/cpp/sources) with both `g++` and `clang++`, and sanitizers, and
|
||||
- compiles the Rust test programs in [`test_files/rust/sources/`](./test_files/rust/sources) with `rustc`, and builds the [`test_files/rust/proc_macro/`](./test_files/rust/proc_macro) workspace with `cargo` to exercise proc-macro dylib loading.
|
||||
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:
|
||||
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.
|
||||
- **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,
|
||||
[`bin/default-loader-path.sh`](../../bin/default-loader-path.sh) reports the
|
||||
expected loader path for the current architecture, so we can patch the binaries
|
||||
to use the correct loader.
|
||||
- **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, [`bin/default-loader-path.sh`](../../bin/default-loader-path.sh) reports the expected loader path for the current architecture, so we can patch the binaries to use the correct loader.
|
||||
|
||||
The build then verifies all of this end to end, and the C++ and Rust programs
|
||||
go through the same pipeline: each is compiled in `final`, has its `PT_INTERP`
|
||||
patched to the target loader, and is then run in the clean `tester` stage to
|
||||
confirm it emits the expected diagnostic on a stock base image. The C++ programs
|
||||
are in `test_files/cpp/sources/` (a regular binary plus ASan/TSan/UBSan
|
||||
variants); the Rust programs are in `test_files/rust/sources/` (a hello binary
|
||||
plus panic and overflow-check variants), plus the `test_files/rust/proc_macro/`
|
||||
workspace — a crate whose compilation additionally loads a proc-macro dylib, and
|
||||
whose resulting binary is patched and run like the others.
|
||||
The build then verifies all of this end to end, and the C++ and Rust programs go through the same pipeline: each is compiled in `final`, has its `PT_INTERP` patched to the target loader, and is then run in the clean `tester` stage to confirm it emits the expected diagnostic on a stock base image. The C++ programs are in `test_files/cpp/sources/` (a regular binary plus ASan/TSan/UBSan variants); the Rust programs are in `test_files/rust/sources/` (a hello binary plus panic and overflow-check variants), plus the `test_files/rust/proc_macro/` workspace — a crate whose compilation additionally loads a proc-macro dylib, and whose resulting binary is patched and run like the others.
|
||||
|
||||
## Files
|
||||
|
||||
| File | Purpose |
|
||||
| ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
|
||||
| [`./Dockerfile`](./Dockerfile) | Multi-stage build described above. |
|
||||
| [`./test_files/cpp/`](./test_files/cpp) | C++ sanitizer smoke test: sources + compile/run scripts. |
|
||||
| [`./test_files/rust/`](./test_files/rust) | Rust smoke test: rustc sources + a cargo proc-macro workspace + compile/run scripts. |
|
||||
| [`/bin/check-tools.sh`](../../bin/check-tools.sh) | Verify every expected tools are present and runnable. |
|
||||
| [`/bin/default-loader-path.sh`](../../bin/default-loader-path.sh) | Print the dynamic-linker (`PT_INTERP`) path for the current architecture. |
|
||||
| [`/bin/install-sanitizer-libs.sh`](../../bin/install-sanitizer-libs.sh) | Install `libasan`/`libtsan`/`libubsan` runtimes on the supported base images. |
|
||||
| File | Purpose |
|
||||
| --- | --- |
|
||||
| [`./Dockerfile`](./Dockerfile) | Multi-stage build described above. |
|
||||
| [`./test_files/cpp/`](./test_files/cpp) | C++ sanitizer smoke test: sources + compile/run scripts. |
|
||||
| [`./test_files/rust/`](./test_files/rust) | Rust smoke test: rustc sources + a cargo proc-macro workspace + compile/run scripts. |
|
||||
| [`/bin/check-tools.sh`](../../bin/check-tools.sh) | Verify every expected tools are present and runnable. |
|
||||
| [`/bin/default-loader-path.sh`](../../bin/default-loader-path.sh) | Print the dynamic-linker (`PT_INTERP`) path for the current architecture. |
|
||||
| [`/bin/install-sanitizer-libs.sh`](../../bin/install-sanitizer-libs.sh) | Install `libasan`/`libtsan`/`libubsan` runtimes on the supported base images. |
|
||||
|
||||
@@ -16,7 +16,23 @@ let
|
||||
exec ${pkgs.python3}/bin/python3 ${llvmPackages.clang-unwrapped}/bin/run-clang-tidy "$@"
|
||||
'';
|
||||
|
||||
rust = import ./rust.nix { inherit pkgs; };
|
||||
# rust-overlay's toolchain propagates the *default* stdenv.cc onto the PATH (so
|
||||
# cargo has a linker). That default may be different from the clang we pin here,
|
||||
# so it shadows our clang and the build can silently use a different compiler
|
||||
# version. Drop that cc from every propagation channel instead of pinning a
|
||||
# replacement: the toolchain then carries no compiler and cargo just uses the
|
||||
# active shell's stdenv cc. Must cover all channels — rust-overlay uses both
|
||||
# propagatedBuildInputs and depsHostHostPropagated.
|
||||
rustToolchainBase = pkgs.rust-bin.fromRustupToolchainFile ../rust-toolchain.toml;
|
||||
rustToolchain =
|
||||
let
|
||||
defaultCc = pkgs.stdenv.cc; # default compiler from nixpkgs stdenv
|
||||
withoutDefaultCc = builtins.filter (dep: (dep.outPath or "") != defaultCc.outPath);
|
||||
in
|
||||
rustToolchainBase.overrideAttrs (old: {
|
||||
propagatedBuildInputs = withoutDefaultCc (old.propagatedBuildInputs or [ ]);
|
||||
depsHostHostPropagated = withoutDefaultCc (old.depsHostHostPropagated or [ ]);
|
||||
});
|
||||
|
||||
# Nix wraps its toolchain so that binaries are exposed only under unsuffixed
|
||||
# names (gcc, g++, clang-tidy, ...). Several tools probe for a
|
||||
@@ -92,38 +108,41 @@ in
|
||||
mkGcov
|
||||
;
|
||||
|
||||
commonPackages =
|
||||
(with pkgs; [
|
||||
clangToolLinks
|
||||
runClangTidyLink
|
||||
ccache
|
||||
clangbuildanalyzer
|
||||
clangTools
|
||||
cmake
|
||||
conan
|
||||
curlMinimal # needed for codecov/codecov-action
|
||||
doxygen
|
||||
file # needed for cpack in Clio
|
||||
gcovr
|
||||
gh
|
||||
git
|
||||
git-cliff
|
||||
git-lfs
|
||||
gnumake
|
||||
gnupg # needed for signing commits & codecov/codecov-action
|
||||
graphviz
|
||||
less # needed for git diff
|
||||
mold
|
||||
nettools # provides netstat, used to debug failures in CI
|
||||
ninja
|
||||
patchelf
|
||||
perl # needed for openssl
|
||||
pkg-config
|
||||
pre-commit
|
||||
python3
|
||||
runClangTidy
|
||||
vim
|
||||
zip
|
||||
])
|
||||
++ rust.packages;
|
||||
commonPackages = with pkgs; [
|
||||
clangToolLinks
|
||||
runClangTidyLink
|
||||
ccache
|
||||
clangbuildanalyzer
|
||||
clangTools
|
||||
cmake
|
||||
conan
|
||||
curlMinimal # needed for codecov/codecov-action
|
||||
doxygen
|
||||
file # needed for cpack in Clio
|
||||
gcovr
|
||||
gh
|
||||
git
|
||||
git-cliff
|
||||
git-lfs
|
||||
gnumake
|
||||
gnupg # needed for signing commits & codecov/codecov-action
|
||||
graphviz
|
||||
less # needed for git diff
|
||||
mold
|
||||
nettools # provides netstat, used to debug failures in CI
|
||||
ninja
|
||||
patchelf
|
||||
perl # needed for openssl
|
||||
pkg-config
|
||||
pre-commit
|
||||
python3
|
||||
runClangTidy
|
||||
vim
|
||||
zip
|
||||
# Rust packages
|
||||
cargo-audit
|
||||
cargo-llvm-cov
|
||||
cargo-nextest
|
||||
rustToolchain
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
#!@runtimeShell@
|
||||
# Reaches the nightly Rust toolchain, which is deliberately kept off PATH.
|
||||
# Packaged by nix/rust.nix, which explains why.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
echo "usage: rust-nightly (path | run <command>...)" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
case "${1-}" in
|
||||
path) printf '%s\n' "@rustNightlyBin@" ;;
|
||||
run)
|
||||
shift
|
||||
if [[ $# -eq 0 ]]; then
|
||||
usage
|
||||
fi
|
||||
export PATH="@rustNightlyBin@:${PATH}"
|
||||
exec "$@"
|
||||
;;
|
||||
*) usage ;;
|
||||
esac
|
||||
84
nix/rust.nix
84
nix/rust.nix
@@ -1,84 +0,0 @@
|
||||
# The Rust half of the tool set shared by the CI environment and the dev shell:
|
||||
# the stable toolchain pinned by rust-toolchain.toml, the nightly the Rust
|
||||
# coverage job needs, and the cargo plugins. Consumed by packages.nix.
|
||||
{ pkgs }:
|
||||
let
|
||||
# rust-overlay's toolchain propagates the *default* stdenv.cc onto the PATH (so
|
||||
# cargo has a linker). That default may be different from the clang we pin
|
||||
# elsewhere, so it shadows our clang and the build can silently use a different
|
||||
# compiler version. Drop that cc from every propagation channel instead of
|
||||
# pinning a replacement: the toolchain then carries no compiler and cargo just
|
||||
# uses the active shell's stdenv cc.
|
||||
#
|
||||
# The channel list is every list mkDerivation propagates to a dependent's
|
||||
# environment (including the two legacy aliases). rust-overlay currently only
|
||||
# uses propagatedBuildInputs and depsHostHostPropagated, but covering all of
|
||||
# them means an upstream switch to another channel cannot quietly put the
|
||||
# compiler back on PATH.
|
||||
dropDefaultCc =
|
||||
toolchain:
|
||||
let
|
||||
defaultCc = pkgs.stdenv.cc; # default compiler from nixpkgs stdenv
|
||||
withoutDefaultCc = builtins.filter (dep: (dep.outPath or "") != defaultCc.outPath);
|
||||
in
|
||||
toolchain.overrideAttrs (
|
||||
old:
|
||||
pkgs.lib.genAttrs [
|
||||
"depsBuildBuildPropagated"
|
||||
"propagatedNativeBuildInputs" # alias of depsBuildHostPropagated
|
||||
"depsBuildTargetPropagated"
|
||||
"depsHostHostPropagated"
|
||||
"propagatedBuildInputs" # alias of depsHostTargetPropagated
|
||||
"depsTargetTargetPropagated"
|
||||
] (channel: withoutDefaultCc (old.${channel} or [ ]))
|
||||
);
|
||||
|
||||
rustToolchain = dropDefaultCc (pkgs.rust-bin.fromRustupToolchainFile ../rust-toolchain.toml);
|
||||
|
||||
# cargo-llvm-cov honours the #[coverage(off)] that keeps unit tests out of the
|
||||
# coverage report only under a nightly rustc, and looks for llvm-profdata and
|
||||
# llvm-cov in that same toolchain's sysroot — hence llvm-tools-preview.
|
||||
#
|
||||
# Not every nightly ships every component, so `nightly.latest` breaks on the
|
||||
# days llvm-tools-preview is absent; selectLatestNightlyWith walks back to the
|
||||
# newest one that has it. The result is the newest such nightly *known to the
|
||||
# locked rust-overlay*, which means updating flake.lock moves the compiler that
|
||||
# produces the coverage numbers — and with it the rustc version recorded in
|
||||
# nix/check-tools/*.txt, so those snapshots need regenerating alongside.
|
||||
rustNightly = dropDefaultCc (
|
||||
pkgs.rust-bin.selectLatestNightlyWith (
|
||||
toolchain: toolchain.minimal.override { extensions = [ "llvm-tools-preview" ]; }
|
||||
)
|
||||
);
|
||||
|
||||
# A second toolchain cannot go on PATH: its cargo and rustc would collide with
|
||||
# the pinned stable's in the ci-env buildEnv, which resolves collisions by
|
||||
# picking one silently. Reaching the nightly only through this wrapper keeps it
|
||||
# in the image closure (the Docker build copies the whole closure, not just
|
||||
# what is linked into /bin) while leaving it inactive everywhere that does not
|
||||
# ask for it.
|
||||
#
|
||||
# The script's `path` subcommand exists for scopes wider than one command — a
|
||||
# CI job appending to $GITHUB_PATH, so that the cargo cache action's own
|
||||
# `rustc -vV` probe, which runs in a step of its own, agrees with the toolchain
|
||||
# the build will use.
|
||||
rustNightlyScript = pkgs.replaceVarsWith {
|
||||
name = "rust-nightly";
|
||||
src = ./rust-nightly.sh;
|
||||
dir = "bin";
|
||||
isExecutable = true;
|
||||
replacements = {
|
||||
inherit (pkgs) runtimeShell;
|
||||
rustNightlyBin = "${rustNightly}/bin";
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
packages = [
|
||||
pkgs.cargo-audit
|
||||
pkgs.cargo-llvm-cov
|
||||
pkgs.cargo-nextest
|
||||
rustNightlyScript
|
||||
rustToolchain
|
||||
];
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
# Linux Packaging
|
||||
|
||||
This directory contains all files needed to build RPM and Debian packages for
|
||||
`xrpld`. The packages also ship the `validator-keys` tool, so packaging requires
|
||||
a build configured with `-Dvalidator_keys=ON`.
|
||||
This directory contains all files needed to build RPM and Debian packages for `xrpld`. The packages also ship the `validator-keys` tool, so packaging requires a build configured with `-Dvalidator_keys=ON`.
|
||||
|
||||
## Directory layout
|
||||
|
||||
@@ -15,7 +13,7 @@ package/
|
||||
publish_pkg.py Uploads built packages to the XRPLF Nexus repositories (called by CI, and shipped in that image)
|
||||
rpm/
|
||||
xrpld.spec RPM spec
|
||||
debian/ Debian control files (control, rules, copyright, xrpld.docs, xrpld.links, xrpld.lintian-overrides, source/format)
|
||||
debian/ Debian control files (control, rules, copyright, xrpld.docs, xrpld.links, source/format)
|
||||
shared/
|
||||
xrpld.service systemd unit file (used by both RPM and DEB)
|
||||
xrpld.sysusers sysusers.d config (used by both RPM and DEB)
|
||||
@@ -25,22 +23,14 @@ package/
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Packaging is declared on the build configs themselves, in
|
||||
[`.github/scripts/strategy-matrix/linux.json`](../.github/scripts/strategy-matrix/linux.json):
|
||||
a config that is also packaged carries a `package` map, so its binaries and its
|
||||
packaging job cannot drift apart. Today only `linux/amd64` is emitted. The map
|
||||
pins the full container image in `image` — edit that field to move to a new
|
||||
image and both CI and local builds pick it up — and names the format that image
|
||||
builds in `type`, which CI passes to `build_pkg.py` as `--package-type`; the two
|
||||
have to stay in step.
|
||||
Packaging is declared on the build configs themselves, in [`.github/scripts/strategy-matrix/linux.json`](../.github/scripts/strategy-matrix/linux.json): a config that is also packaged carries a `package` map, so its binaries and its packaging job cannot drift apart. Today only `linux/amd64` is emitted. The map pins the full container image in `image` — edit that field to move to a new image and both CI and local builds pick it up — and names the format that image builds in `type`, which CI passes to `build_pkg.py` as `--package-type`; the two have to stay in step.
|
||||
|
||||
| Package type | Image (`configs.<distro>[].package.image` in `linux.json`) | Tools required |
|
||||
| ------------ | ---------------------------------------------------------- | -------------------------------------------------------------- |
|
||||
| RPM | `ghcr.io/xrplf/xrpld/packaging-rhel:sha-<sha>` | `rpmbuild`, `rpmsign` |
|
||||
| DEB | `ghcr.io/xrplf/xrpld/packaging-debian:sha-<sha>` | `dpkg-buildpackage`, debhelper with compat level 13, `lintian` |
|
||||
| Package type | Image (`configs.<distro>[].package.image` in `linux.json`) | Tools required |
|
||||
| --- | --- | --- |
|
||||
| RPM | `ghcr.io/xrplf/xrpld/packaging-rhel:sha-<sha>` | `rpmbuild`, `rpmsign` |
|
||||
| DEB | `ghcr.io/xrplf/xrpld/packaging-debian:sha-<sha>` | `dpkg-buildpackage`, debhelper with compat level 13 |
|
||||
|
||||
To print the full packaging matrix (artifact names and images) for the current
|
||||
`linux.json`:
|
||||
To print the full packaging matrix (artifact names and images) for the current `linux.json`:
|
||||
|
||||
```bash
|
||||
./.github/scripts/strategy-matrix/generate.py --packaging
|
||||
@@ -50,38 +40,15 @@ To print the full packaging matrix (artifact names and images) for the current
|
||||
|
||||
### Via CI
|
||||
|
||||
Caller workflows (`on-pr.yml`, `on-tag.yml`, `on-trigger.yml`) call
|
||||
`reusable-package.yml`, which runs in three stages:
|
||||
Caller workflows (`on-pr.yml`, `on-tag.yml`, `on-trigger.yml`) call `reusable-package.yml`. That workflow generates its own packaging matrix from the configs that carry a `package` map (via `generate.py --packaging`) and fans out one job per distro. Each job downloads the pre-built `xrpld` and `validator-keys` binary artifacts and runs in that distro's container, building the format `package.type` declares. The packaging script derives the package version from the downloaded binary's `xrpld --version` output; no CMake configure or build step is needed inside the packaging job.
|
||||
|
||||
1. `package` fans out one job per config carrying a `package` map, building and
|
||||
signing in that config's container, and uploading `<config>-pkg` alongside
|
||||
`<config>-pkg-debug` for the much larger debug symbols.
|
||||
2. `test-install` installs `<config>-pkg` in the container of every distro the
|
||||
packages target and runs the binaries there, so one that cannot be installed
|
||||
never reaches Nexus.
|
||||
3. `publish` uploads both artifacts, or lists what it would upload.
|
||||
The binaries come from the `debian` and `rhel` build configs themselves — the ones carrying the `package` map — which pass `-Dvalidator_keys=ON` so that the build job produces `validator-keys` next to `xrpld` and uploads it as the `validator-keys-<config name>` artifact. The packaging matrix names both artifacts (`xrpld_artifact_name` and `validator_keys_artifact_name`) after that same config, so a packaged config must keep `-Dvalidator_keys=ON`. Those configs are not `minimal`, so `on-pr.yml` only packages once a PR runs the full matrix.
|
||||
|
||||
The packaging script derives the package version from the downloaded binary's
|
||||
`xrpld --version` output; no CMake configure or build step is needed inside the
|
||||
packaging job.
|
||||
|
||||
The binaries come from the `debian` and `rhel` build configs themselves — the
|
||||
ones carrying the `package` map — which pass `-Dvalidator_keys=ON` so that the
|
||||
build job produces `validator-keys` next to `xrpld` and uploads it as the
|
||||
`validator-keys-<config name>` artifact. The packaging matrix names both
|
||||
artifacts (`xrpld_artifact_name` and `validator_keys_artifact_name`) after that
|
||||
same config, so a packaged config must keep `-Dvalidator_keys=ON`. Those configs
|
||||
are not `minimal`, so `on-pr.yml` only packages once a PR runs the full matrix.
|
||||
|
||||
`validator-keys` is fetched from an exact commit pinned in
|
||||
[`cmake/XrplValidatorKeys.cmake`](../cmake/XrplValidatorKeys.cmake), so a given
|
||||
`xrpld` version always packages the same tool; bump that commit deliberately.
|
||||
`validator-keys` is fetched from an exact commit pinned in [`cmake/XrplValidatorKeys.cmake`](../cmake/XrplValidatorKeys.cmake), so a given `xrpld` version always packages the same tool; bump that commit deliberately.
|
||||
|
||||
### Locally (mirrors CI)
|
||||
|
||||
With `xrpld` and `validator-keys` binaries already built at `build/xrpld` and
|
||||
`build/validator-keys`, run the packaging step inside the same container CI uses.
|
||||
The image tag is derived from `linux.json` so you don't need to hardcode a SHA.
|
||||
With `xrpld` and `validator-keys` binaries already built at `build/xrpld` and `build/validator-keys`, run the packaging step inside the same container CI uses. The image tag is derived from `linux.json` so you don't need to hardcode a SHA.
|
||||
|
||||
```bash
|
||||
# From the repo root. Each distro's container image is the `package.image` field
|
||||
@@ -100,15 +67,14 @@ docker run --rm \
|
||||
--pkg-release "${PKG_RELEASE}" \
|
||||
--channel UNRELEASED
|
||||
|
||||
# Output (the deb image writes build/debbuild/*.deb instead):
|
||||
# Output:
|
||||
# build/debbuild/*.deb (DEB + dbgsym; Debian names both .deb)
|
||||
# build/rpmbuild/RPMS/x86_64/*.rpm
|
||||
```
|
||||
|
||||
### Via CMake (host-side target)
|
||||
|
||||
If you run CMake configure on a host that has `rpmbuild` or `dpkg-buildpackage`
|
||||
installed natively, you can use the CMake target directly — no container
|
||||
needed, but the host toolchain replaces the pinned CI image:
|
||||
If you run CMake configure on a host that has `rpmbuild` or `dpkg-buildpackage` installed natively, you can use the CMake target directly — no container needed, but the host toolchain replaces the pinned CI image:
|
||||
|
||||
```bash
|
||||
cmake \
|
||||
@@ -121,104 +87,49 @@ cmake \
|
||||
cmake --build . --target package # deb on Debian/Ubuntu, rpm on RHEL
|
||||
```
|
||||
|
||||
The `cmake/XrplPackaging.cmake` module defines the `package` target only if at
|
||||
least one of `rpmbuild` / `dpkg-buildpackage` is present and both the `xrpld` and
|
||||
`validator-keys` targets exist (`-Dxrpld=ON -Dvalidator_keys=ON`); the target
|
||||
builds both binaries before packaging, passing `--package-type deb` when
|
||||
`dpkg-buildpackage` is present and `rpm` otherwise, and `--channel UNRELEASED`.
|
||||
The packaging script installs to FHS-standard paths (`/usr/bin`, `/etc/xrpld`,
|
||||
etc.) regardless of `CMAKE_INSTALL_PREFIX`.
|
||||
The `cmake/XrplPackaging.cmake` module defines the `package` target only if at least one of `rpmbuild` / `dpkg-buildpackage` is present and both the `xrpld` and `validator-keys` targets exist (`-Dxrpld=ON -Dvalidator_keys=ON`); the target builds both binaries before packaging, passing `--package-type deb` when `dpkg-buildpackage` is present and `rpm` otherwise, and `--channel UNRELEASED`. The packaging script installs to FHS-standard paths (`/usr/bin`, `/etc/xrpld`, etc.) regardless of `CMAKE_INSTALL_PREFIX`.
|
||||
|
||||
The package version is not a CMake input on this path: `build_pkg.py` derives it
|
||||
from the just-built `xrpld` binary's `xrpld --version` output. The package
|
||||
release defaults to 1 and is overridable with `-Dpkg_release=N`.
|
||||
The package version is not a CMake input on this path: `build_pkg.py` derives it from the just-built `xrpld` binary's `xrpld --version` output. The package release defaults to 1 and is overridable with `-Dpkg_release=N`.
|
||||
|
||||
## Publishing packages
|
||||
|
||||
Packages are published to the XRPLF repositories on Sonatype Nexus at
|
||||
`https://packages.xrplf.org`. The `release-info` action decides the channel from
|
||||
the event, and `publish_pkg.py` maps that channel to its repositories:
|
||||
Packages are published to the XRPLF repositories on Sonatype Nexus at `https://packages.xrplf.org`. The `release-info` action decides the channel from the event, and `publish_pkg.py` maps that channel to its repositories:
|
||||
|
||||
| Event | Version | Channel | DEB repository | RPM upload repository |
|
||||
| ------------------------ | ----------------- | --------- | -------------- | --------------------- |
|
||||
| tag | `X.Y.Z` | `stable` | `deb-stable` | `rpm-stable-hosted` |
|
||||
| tag | `X.Y.Z-rcN` | `rc` | `deb-rc` | `rpm-rc-hosted` |
|
||||
| tag | `X.Y.Z-bN` | `beta` | `deb-beta` | `rpm-beta-hosted` |
|
||||
| push to `develop` | `xrpld --version` | `develop` | `deb-develop` | `rpm-develop-hosted` |
|
||||
| tag, non-public codebase | _any_ | `private` | `deb-private` | `rpm-private-hosted` |
|
||||
| Event | Version | Channel | DEB repository | RPM upload repository |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| tag | `X.Y.Z` | `stable` | `deb-stable` | `rpm-stable-hosted` |
|
||||
| tag | `X.Y.Z-rcN` | `rc` | `deb-rc` | `rpm-rc-hosted` |
|
||||
| tag | `X.Y.Z-bN` | `beta` | `deb-beta` | `rpm-beta-hosted` |
|
||||
| push to `develop` | `xrpld --version` | `develop` | `deb-develop` | `rpm-develop-hosted` |
|
||||
| tag, non-public codebase | _any_ | `private` | `deb-private` | `rpm-private-hosted` |
|
||||
|
||||
Only a tag names a channel — do not extend that to `develop`, where
|
||||
`BuildInfo.cpp`'s `versionString` moves through `-bN`, `-rcN` and even the final
|
||||
version during a release cycle, which would send develop builds into `stable`.
|
||||
Versions sort in row order, so moving to a more mature channel never downgrades.
|
||||
Only a tag names a channel — do not extend that to `develop`, where `BuildInfo.cpp`'s `versionString` moves through `-bN`, `-rcN` and even the final version during a release cycle, which would send develop builds into `stable`. Versions sort in row order, so moving to a more mature channel never downgrades.
|
||||
|
||||
The action decides the package release number on the same split: a tag's version
|
||||
is unique, so its packages are release 1, while develop repeats the same version
|
||||
and takes `<run number>.<commit date>git<commit hash>`, e.g.
|
||||
`857.20260826gitb6a8995` — the leading run number keeps each push superseding
|
||||
the last, and the date and hash say which commit a package on
|
||||
`packages.xrplf.org` came from. Both reach the packaging scripts as arguments,
|
||||
so neither script derives anything itself.
|
||||
The action decides the package release number on the same split: a tag's version is unique, so its packages are release 1, while develop repeats the same version and takes `<run number>.<commit date>git<commit hash>`, e.g. `857.20260826gitb6a8995` — the leading run number keeps each push superseding the last, and the date and hash say which commit a package on `packages.xrplf.org` came from. Both reach the packaging scripts as arguments, so neither script derives anything itself.
|
||||
|
||||
Publishing is its own job, gated behind `test-install`, uploading from the same
|
||||
image that built the packages with the `publish_pkg.py` shipped in it — the
|
||||
same copy other repositories run. Without `publish: true` the job is a
|
||||
`--dry-run`, listing the uploads it would make without needing credentials, so
|
||||
any run that builds packages also exercises the upload routing. `on-trigger.yml`
|
||||
passes `publish: true` for develop pushes in `XRPLF/rippled` and `on-tag.yml`
|
||||
for tags in any `XRPLF` repository, both authenticating with the
|
||||
`NEXUS_REMOTE_USERNAME` / `NEXUS_REMOTE_PASSWORD` secrets already used for the
|
||||
Conan remote; `on-pr.yml` never publishes.
|
||||
Publishing is the last step of each packaging job, uploading from the container that built the packages with the `publish_pkg.py` shipped in the image — the same copy other repositories run. Without `publish: true` the step is a `--dry-run`, listing the uploads it would make without needing credentials, so any run that builds packages also exercises the upload routing. `on-trigger.yml` passes `publish: true` for develop pushes in `XRPLF/rippled` and `on-tag.yml` for tags in any `XRPLF` repository, both authenticating with the `NEXUS_REMOTE_USERNAME` / `NEXUS_REMOTE_PASSWORD` secrets already used for the Conan remote; `on-pr.yml` never publishes.
|
||||
|
||||
Nexus owns the repository metadata; nothing here indexes anything. Worth knowing:
|
||||
|
||||
- Each apt-hosted repository needs a distribution (ours use `any`) and a PGP
|
||||
signing keypair configured in Nexus, which rejects one created without a
|
||||
keypair. Nexus signs the apt metadata with it, never the packages.
|
||||
- Hosted yum repositories cannot be signed by Nexus, so each `rpm-<channel>-hosted`
|
||||
repository sits behind a `rpm-<channel>` yum group repository whose metadata
|
||||
Nexus signs. Uploads go to the hosted repository; clients point at the group
|
||||
and verify the metadata with `repo_gpgcheck=1`. Nexus never signs the RPMs
|
||||
themselves, so `sign_rpm.py` signs them before they are uploaded, and clients
|
||||
verify them with `gpgcheck=1`.
|
||||
- yum metadata is rebuilt asynchronously, so a successful publish is not
|
||||
immediately installable.
|
||||
- Each job uploads only what it built, and uploads are not transactional, so a
|
||||
failure can leave one format published alone. Re-running is safe: both the apt
|
||||
POST and the yum PUT replace an existing asset.
|
||||
- The `develop` repositories gain a package per push, so they need a cleanup
|
||||
policy to stay bounded; tagged channels publish each version once.
|
||||
- Each apt-hosted repository needs a distribution (ours use `any`) and a PGP signing keypair configured in Nexus, which rejects one created without a keypair. Nexus signs the apt metadata with it, never the packages.
|
||||
- Hosted yum repositories cannot be signed by Nexus, so each `rpm-<channel>-hosted` repository sits behind a `rpm-<channel>` yum group repository whose metadata Nexus signs. Uploads go to the hosted repository; clients point at the group and verify the metadata with `repo_gpgcheck=1`. Nexus never signs the RPMs themselves, so `sign_rpm.py` signs them before they are uploaded, and clients verify them with `gpgcheck=1`.
|
||||
- yum metadata is rebuilt asynchronously, so a successful publish is not immediately installable.
|
||||
- Each job uploads only what it built, and uploads are not transactional, so a failure can leave one format published alone. Re-running is safe: both the apt POST and the yum PUT replace an existing asset.
|
||||
- The `develop` repositories gain a package per push, so they need a cleanup policy to stay bounded; tagged channels publish each version once.
|
||||
|
||||
### Publishing from other repositories
|
||||
|
||||
`publish_pkg.py` knows nothing about `xrpld`, so the packaging image
|
||||
installs it at `/usr/local/bin/publish_pkg.py` for other XRPLF repositories that
|
||||
build their packages elsewhere.
|
||||
`publish_pkg.py` knows nothing about `xrpld`, so the packaging image installs it at `/usr/local/bin/publish_pkg.py` for other XRPLF repositories that build their packages elsewhere.
|
||||
|
||||
## How `build_pkg.py` works
|
||||
|
||||
`build_pkg.py` derives the `xrpld` software version from
|
||||
`${BUILD_DIR}/xrpld --version` in both package formats.
|
||||
`build_pkg.py` derives the `xrpld` software version from `${BUILD_DIR}/xrpld --version` in both package formats.
|
||||
|
||||
The binary's version is already SemVer-validated by `BuildInfo`.
|
||||
`build_pkg.py` converts pre-release versions such as `3.2.0-b1` or
|
||||
`3.2.0-rc1` from `-` to `~` for package metadata so pre-releases sort before
|
||||
the final release. If that normalized package version still contains `-`,
|
||||
packaging fails because RPM forbids `-` in `Version`, and Debian uses `-` as
|
||||
the upstream/revision separator.
|
||||
The binary's version is already SemVer-validated by `BuildInfo`. `build_pkg.py` converts pre-release versions such as `3.2.0-b1` or `3.2.0-rc1` from `-` to `~` for package metadata so pre-releases sort before the final release. If that normalized package version still contains `-`, packaging fails because RPM forbids `-` in `Version`, and Debian uses `-` as the upstream/revision separator.
|
||||
|
||||
> [!NOTE]
|
||||
> Debug and sanitizer builds are not packaged yet.
|
||||
`pkg_version` is the normalized package metadata version derived inside `build_pkg.py` from the binary-reported `xrpld` version (`-` pre-release separator converted to `~`). It is not a separate user input.
|
||||
|
||||
`pkg_version` is the normalized package metadata version derived inside
|
||||
`build_pkg.py` from the binary-reported `xrpld` version (`-` pre-release
|
||||
separator converted to `~`). It is not a separate user input.
|
||||
|
||||
`PKG_RELEASE` is a different value: the package release iteration for that
|
||||
`xrpld` version. RPM receives the normalized `pkg_version` and `PKG_RELEASE` as
|
||||
the `pkg_version` and `pkg_release` macros for its `Version` and `Release`
|
||||
values; DEB writes them as `${pkg_version}-${PKG_RELEASE}` in
|
||||
`debian/changelog`.
|
||||
`PKG_RELEASE` is a different value: the package release iteration for that `xrpld` version. RPM receives the normalized `pkg_version` and `PKG_RELEASE` as the `pkg_version` and `pkg_release` macros for its `Version` and `Release` values; DEB writes them as `${pkg_version}-${PKG_RELEASE}` in `debian/changelog`.
|
||||
|
||||
With `PKG_RELEASE=1`, the package metadata becomes:
|
||||
|
||||
@@ -229,103 +140,59 @@ With `PKG_RELEASE=1`, the package metadata becomes:
|
||||
| `3.2.0-b1` | `3.2.0~b1-1%{?dist}` | `3.2.0~b1-1` |
|
||||
| `3.2.0-rc1` | `3.2.0~rc1-1%{?dist}` | `3.2.0~rc1-1` |
|
||||
|
||||
`build_pkg.py` defines `dist` as `.el9` rather than letting rpmbuild take it
|
||||
from the build host, so the RHEL image can track a newer release without
|
||||
changing what the packages claim to target.
|
||||
`build_pkg.py` defines `dist` as `.el9` rather than letting rpmbuild take it from the build host, so the RHEL image can track a newer release without changing what the packages claim to target.
|
||||
|
||||
The Debian changelog entry carries the channel passed as `--channel`, which
|
||||
only accepts the channels in the table above plus `UNRELEASED`, the Debian
|
||||
convention for a build that targets no channel at all — what local and CMake
|
||||
builds pass, since nothing publishes them. An unsupported pre-release, and
|
||||
build metadata on a final release such as `3.2.0+abc123`, are both rejected.
|
||||
The Debian changelog entry carries the channel passed as `--channel`, which only accepts the channels in the table above plus `UNRELEASED`, the Debian convention for a build that targets no channel at all — what local and CMake builds pass, since nothing publishes them. An unsupported pre-release, and build metadata on a final release such as `3.2.0+abc123`, are both rejected.
|
||||
|
||||
The RPM path intentionally uses `~` in `Version`, matching the Debian
|
||||
pre-release ordering convention, so RPM filenames/NVRs begin with forms like
|
||||
`xrpld-3.2.0~b1-...` and `xrpld-3.2.0~rc1-...` instead of encoding
|
||||
pre-releases with an older `0.<release>.<suffix>` RPM `Release` value.
|
||||
The RPM path intentionally uses `~` in `Version`, matching the Debian pre-release ordering convention, so RPM filenames/NVRs begin with forms like `xrpld-3.2.0~b1-...` and `xrpld-3.2.0~rc1-...` instead of encoding pre-releases with an older `0.<release>.<suffix>` RPM `Release` value.
|
||||
|
||||
The package format is `--package-type`, either `deb` or `rpm`. It is required,
|
||||
so a job never silently builds the wrong format for the image it runs in; the
|
||||
matching build tool still has to be on PATH.
|
||||
The package format is `--package-type`, either `deb` or `rpm`. It is required, so a job never silently builds the wrong format for the image it runs in; the matching build tool still has to be on PATH.
|
||||
|
||||
Every input is a named argument, and every argument but `--build-dir` and
|
||||
`--pkg-release` is required. The repository root is not an argument
|
||||
at all: the script reads it from its own location. Only secrets stay in the
|
||||
environment, so they never reach the process list -- `PKG_SIGNING_KEY` for
|
||||
`sign_rpm.py`, and `NEXUS_USERNAME` / `NEXUS_PASSWORD` for `publish_pkg.py`.
|
||||
Every input is a named argument, and every argument but `--build-dir` and `--pkg-release` is required. The repository root is not an argument at all: the script reads it from its own location. Only secrets stay in the environment, so they never reach the process list -- `PKG_SIGNING_KEY` for `sign_rpm.py`, and `NEXUS_USERNAME` / `NEXUS_PASSWORD` for `publish_pkg.py`.
|
||||
|
||||
Signing is not part of this script. `sign_rpm.py` does it in a separate CI step
|
||||
that only runs when publishing, so a published RPM is always signed and a local
|
||||
build never needs a key.
|
||||
Signing is not part of this script. `sign_rpm.py` does it in a separate CI step that only runs when publishing, so a published RPM is always signed and a local build never needs a key.
|
||||
|
||||
It resolves the build directory to an absolute path, then calls
|
||||
`stage_common()` to copy the `xrpld` and `validator-keys` binaries, config files,
|
||||
and shared support files into the staging area, and invokes the platform build
|
||||
tool. Both binaries must be present in the build directory and must run in the
|
||||
packaging environment; a missing or non-runnable one fails early. That runtime
|
||||
check is what catches a binary still linked against the Nix store's ELF loader (see
|
||||
`patch_nix_binary` in `cmake/PatchNixBinary.cmake`).
|
||||
It resolves the build directory to an absolute path, then calls `stage_common()` to copy the `xrpld` and `validator-keys` binaries, config files, and shared support files into the staging area, and invokes the platform build tool. Both binaries must be present in the build directory and must run in the packaging environment; a missing or non-runnable one fails early. That runtime check is what catches a binary still linked against the Nix store's ELF loader (see `patch_nix_binary` in `cmake/PatchNixBinary.cmake`).
|
||||
|
||||
### RPM
|
||||
|
||||
1. Creates the standard `rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}` tree inside the build directory.
|
||||
2. Copies `xrpld.spec` and all shared source files (binaries, configs, service files) into `SOURCES/`.
|
||||
3. Runs `rpmbuild -bb`, passing the normalized package metadata version as the
|
||||
`pkg_version` RPM macro and `PKG_RELEASE` as the `pkg_release` RPM macro.
|
||||
The spec uses manual `install` commands to place files, disables `dwz`, and
|
||||
generates debuginfo packages.
|
||||
3. Runs `rpmbuild -bb`, passing the normalized package metadata version as the `pkg_version` RPM macro and `PKG_RELEASE` as the `pkg_release` RPM macro. The spec uses manual `install` commands to place files, disables `dwz`, and generates debuginfo packages.
|
||||
4. Output: `rpmbuild/RPMS/x86_64/xrpld-*.rpm`
|
||||
|
||||
RPM upgrades intentionally do not restart a running `xrpld` service. The spec
|
||||
uses `%systemd_postun`, matching Debian's `dh_installsystemd
|
||||
--no-stop-on-upgrade` behavior; operators pick up the new binary on the next
|
||||
service restart.
|
||||
RPM upgrades intentionally do not restart a running `xrpld` service. The spec uses `%systemd_postun`, matching Debian's `dh_installsystemd --no-stop-on-upgrade` behavior; operators pick up the new binary on the next service restart.
|
||||
|
||||
### DEB
|
||||
|
||||
1. Creates a staging source tree at `debbuild/source/` inside the build directory.
|
||||
2. Stages the binaries, configs, `README.md`, `LICENSE.md`, and
|
||||
`validator-keys-LICENSE`.
|
||||
2. Stages the binaries, configs, `README.md`, `LICENSE.md`, and `validator-keys-LICENSE`.
|
||||
3. Copies `package/debian/` control files into `debbuild/source/debian/`.
|
||||
4. Copies shared service/sysusers/tmpfiles/logrotate into `debian/` where `dh_installsystemd`, `dh_installsysusers`, `dh_installtmpfiles` and `dh_installlogrotate` pick them up automatically.
|
||||
5. Generates a minimal `debian/changelog` using `${pkg_version}-${PKG_RELEASE}`,
|
||||
where `pkg_version` is derived from the binary-reported `xrpld` version.
|
||||
4. Copies shared service/sysusers/tmpfiles into `debian/` where `dh_installsystemd`, `dh_installsysusers`, and `dh_installtmpfiles` pick them up automatically.
|
||||
5. Generates a minimal `debian/changelog` using `${pkg_version}-${PKG_RELEASE}`, where `pkg_version` is derived from the binary-reported `xrpld` version.
|
||||
6. Runs `dpkg-buildpackage -b --no-sign -d` (`-d` skips the build-dependency check, since the binary is already built). `debian/rules` uses manual `install` commands.
|
||||
|
||||
It also rewrites the `libc6` bound to `LIBC_MIN` in `debian/rules`, the glibc
|
||||
the Nix toolchain builds against. `dpkg-shlibdeps` would otherwise derive it
|
||||
from the build host's symbols file — on trixie that yields `libc6 (>= 2.34)`
|
||||
because of `sysconf`, locking out distros the binaries run on. A check fails
|
||||
the build if either binary outgrows `LIBC_MIN`.
|
||||
|
||||
7. Output: `debbuild/*.deb`, the binary package and the `-dbgsym` package.
|
||||
Debian gives dbgsym packages a `.deb` extension; only Ubuntu uses `.ddeb`.
|
||||
7. Output: `debbuild/*.deb`, the binary package and the `-dbgsym` package. Debian gives dbgsym packages a `.deb` extension; only Ubuntu uses `.ddeb`.
|
||||
|
||||
## Post-build verification
|
||||
|
||||
```bash
|
||||
# DEB (one invocation per package: the dbgsym package is a .deb too)
|
||||
for deb in debbuild/*.deb; do dpkg-deb -c "${deb}"; done | grep -E 'systemd|sysusers|tmpfiles'
|
||||
lintian -I debbuild/*.deb
|
||||
# DEB
|
||||
dpkg-deb -c debbuild/*.deb | grep -E 'systemd|sysusers|tmpfiles'
|
||||
|
||||
# RPM
|
||||
rpm -qlp rpmbuild/RPMS/x86_64/*.rpm
|
||||
```
|
||||
|
||||
`lintian` still reports `embedded-library zlib`, `no-manual-page` and
|
||||
`initial-upload-closes-no-bugs`; only the `/usr/local` tags are overridden.
|
||||
# Optional, and not in the packaging image: apt-get install -y lintian
|
||||
lintian -I debbuild/*.deb
|
||||
```
|
||||
|
||||
## Reproducibility
|
||||
|
||||
Both formats build reproducibly as they are: the same binaries at the same
|
||||
commit give byte-identical packages on a rebuild, and nothing has to be
|
||||
exported by hand.
|
||||
`build_pkg.py` sets `SOURCE_DATE_EPOCH` from the latest git commit time and exports it; the RPM spec clamps file modification times to it via `%build_mtime_policy`. The remaining variables below further improve reproducibility but are _not_ set by the script — export them yourself if needed:
|
||||
|
||||
`build_pkg.py` sets `SOURCE_DATE_EPOCH` from the latest git commit time.
|
||||
`dpkg-buildpackage` honours it on its own; the RPM spec sets three macros:
|
||||
|
||||
- `%clamp_mtime_to_source_date_epoch` — file modification times, from
|
||||
`SOURCE_DATE_EPOCH`.
|
||||
- `%use_source_date_epoch_as_buildtime` — the `BUILDTIME` header, from the
|
||||
same.
|
||||
- `%_buildhost` — pinned, so the builder's hostname stays out of the header.
|
||||
```bash
|
||||
export TZ=UTC
|
||||
export LC_ALL=C.UTF-8
|
||||
export GZIP=-n
|
||||
export DEB_BUILD_OPTIONS="noautodbgsym reproducible=+fixfilepath"
|
||||
```
|
||||
|
||||
@@ -19,7 +19,7 @@ from pathlib import Path
|
||||
# This script lives in the repository it packages.
|
||||
SRC_DIR = Path(__file__).resolve().parents[1]
|
||||
|
||||
PRE_RELEASE = re.compile(r"^(b|rc)(0|[1-9][0-9]*)(\+.*)?$")
|
||||
PRE_RELEASE = re.compile(r"^(b0|b[1-9][0-9]*|rc[0-9]+)(\+.*)?$")
|
||||
|
||||
# Files both packaging systems consume, staged under the same names.
|
||||
STAGED_FROM_BUILD = ("xrpld", "validator-keys", "validator-keys-LICENSE")
|
||||
@@ -133,14 +133,6 @@ def stage_common(build_dir: Path, dest: Path) -> None:
|
||||
shutil.copy2(build_dir / name, dest / name)
|
||||
for source, name in STAGED_FROM_SRC.items():
|
||||
shutil.copy2(SRC_DIR / source, dest / name)
|
||||
|
||||
|
||||
def stage_units(dest: Path) -> None:
|
||||
"""Copy the systemd, sysusers, tmpfiles and logrotate files into dest.
|
||||
|
||||
Each format wants them somewhere else: rpmbuild reads them from SOURCES,
|
||||
debhelper from debian/.
|
||||
"""
|
||||
for name in STAGED_UNITS:
|
||||
shutil.copy2(SRC_DIR / "package" / "shared" / name, dest / name)
|
||||
|
||||
@@ -154,7 +146,6 @@ def build_rpm(build_dir: Path, *, version: str, pkg_release: str) -> None:
|
||||
spec = topdir / "SPECS" / "xrpld.spec"
|
||||
shutil.copy2(SRC_DIR / "package" / "rpm" / "xrpld.spec", spec)
|
||||
stage_common(build_dir, topdir / "SOURCES")
|
||||
stage_units(topdir / "SOURCES")
|
||||
|
||||
run(
|
||||
"rpmbuild",
|
||||
@@ -187,7 +178,8 @@ def build_deb(
|
||||
shutil.copytree(SRC_DIR / "package" / "debian", staging / "debian")
|
||||
|
||||
# debhelper picks these up from debian/ automatically.
|
||||
stage_units(staging / "debian")
|
||||
for name in STAGED_UNITS:
|
||||
shutil.copy2(staging / name, staging / "debian" / name)
|
||||
|
||||
date = datetime.fromtimestamp(epoch, timezone.utc).strftime(
|
||||
"%a, %d %b %Y %H:%M:%S %z"
|
||||
@@ -201,6 +193,8 @@ def build_deb(
|
||||
""")
|
||||
(staging / "debian" / "changelog").write_text(changelog)
|
||||
|
||||
(staging / "debian" / "rules").chmod(0o755)
|
||||
|
||||
run("dpkg-buildpackage", "-b", "--no-sign", "-d", cwd=staging)
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ Priority: optional
|
||||
Maintainer: XRPL Foundation <contact@xrplf.org>
|
||||
Rules-Requires-Root: no
|
||||
Build-Depends:
|
||||
binutils,
|
||||
debhelper-compat (= 13)
|
||||
Standards-Version: 4.7.0
|
||||
Homepage: https://github.com/XRPLF/rippled
|
||||
@@ -12,6 +11,8 @@ Vcs-Git: https://github.com/XRPLF/rippled.git
|
||||
Vcs-Browser: https://github.com/XRPLF/rippled
|
||||
|
||||
Package: xrpld
|
||||
Section: net
|
||||
Priority: optional
|
||||
Architecture: any
|
||||
Depends:
|
||||
${shlibs:Depends},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: xrpld
|
||||
Upstream-Name: rippled
|
||||
Source: https://github.com/XRPLF/rippled
|
||||
|
||||
Files: *
|
||||
@@ -15,7 +15,7 @@ Copyright: 2016, Ripple Labs Inc.
|
||||
2009-2010, Satoshi Nakamoto
|
||||
2011, The Bitcoin developers
|
||||
2003-2005, Tom Wu
|
||||
License: ISC and BSL-1.0 and MIT and Tom-Wu
|
||||
License: ISC
|
||||
Comment: Built from https://github.com/ripple/validator-keys-tool at the commit
|
||||
pinned in cmake/XrplValidatorKeys.cmake. Besides ISC-licensed code it
|
||||
incorporates work under the Boost Software License 1.0 (ASIO), the MIT/X11
|
||||
@@ -35,74 +35,3 @@ License: ISC
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
License: BSL-1.0
|
||||
Boost Software License - Version 1.0 - August 17th, 2003
|
||||
.
|
||||
Permission is hereby granted, free of charge, to any person or organization
|
||||
obtaining a copy of the software and accompanying documentation covered by
|
||||
this license (the "Software") to use, reproduce, display, distribute,
|
||||
execute, and transmit the Software, and to prepare derivative works of the
|
||||
Software, and to permit third-parties to whom the Software is furnished to
|
||||
do so, all subject to the following:
|
||||
.
|
||||
The copyright notices in the Software and this entire statement, including
|
||||
the above license grant, this restriction and the following disclaimer,
|
||||
must be included in all copies of the Software, in whole or in part, and
|
||||
all derivative works of the Software, unless such copies or derivative
|
||||
works are solely in the form of machine-executable object code generated by
|
||||
a source language processor.
|
||||
.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
License: MIT
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
.
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
License: Tom-Wu
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
.
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
.
|
||||
THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
|
||||
WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
||||
.
|
||||
IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
|
||||
INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
|
||||
RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
|
||||
THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
|
||||
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
.
|
||||
In addition, the following condition applies:
|
||||
.
|
||||
All redistributions must retain an intact copy of this copyright notice
|
||||
and disclaimer.
|
||||
|
||||
26
package/debian/rules
Executable file → Normal file
26
package/debian/rules
Executable file → Normal file
@@ -2,12 +2,6 @@
|
||||
|
||||
export DH_VERBOSE = 1
|
||||
|
||||
# The glibc the Nix toolchain builds against, and so the real floor for the
|
||||
# binaries. dpkg-shlibdeps would instead derive libc6 (>= 2.34) from the build
|
||||
# host's symbols file, where sysconf carries that minver, locking out distros
|
||||
# the binaries actually run on.
|
||||
LIBC_MIN = 2.31
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
@@ -17,8 +11,6 @@ override_dh_auto_configure override_dh_auto_build override_dh_auto_test:
|
||||
override_dh_installsystemd:
|
||||
dh_installsystemd --no-stop-on-upgrade xrpld.service
|
||||
|
||||
# The tmpfiles snippet sets ownership to the xrpld user, so the sysusers snippet
|
||||
# has to be emitted first: run it early and make its own sequence slot a no-op.
|
||||
execute_before_dh_installtmpfiles:
|
||||
dh_installsysusers
|
||||
|
||||
@@ -30,23 +22,5 @@ override_dh_install:
|
||||
install -D -m 0644 xrpld.cfg debian/xrpld/etc/xrpld/xrpld.cfg
|
||||
install -D -m 0644 validators.txt debian/xrpld/etc/xrpld/validators.txt
|
||||
|
||||
override_dh_shlibdeps:
|
||||
dh_shlibdeps
|
||||
# Guards against the toolchain moving past LIBC_MIN and the packages then
|
||||
# claiming a floor they do not meet.
|
||||
for binary in xrpld validator-keys; do \
|
||||
needed=$$(readelf --dyn-syms --wide $$binary \
|
||||
| grep -o 'GLIBC_[0-9.]*' | sed 's/GLIBC_//' | sort -uV | tail -1); \
|
||||
if [ -z "$$needed" ]; then \
|
||||
echo "$$binary: no GLIBC_ symbol versions read, cannot check LIBC_MIN" >&2; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
if dpkg --compare-versions "$$needed" gt "$(LIBC_MIN)"; then \
|
||||
echo "$$binary needs glibc $$needed, above LIBC_MIN $(LIBC_MIN)" >&2; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
done
|
||||
sed -i 's/libc6 (>= [0-9.]*)/libc6 (>= $(LIBC_MIN))/' debian/xrpld.substvars
|
||||
|
||||
override_dh_dwz:
|
||||
@:
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
README.md
|
||||
LICENSE.md
|
||||
validator-keys-LICENSE
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
# Legacy compatibility for pre-FHS package layouts.
|
||||
# TODO: remove after rippled fully deprecated.
|
||||
# Legacy compat symlinks (remove next major release)
|
||||
usr/bin/xrpld usr/local/bin/rippled
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
# The /usr/local/bin/rippled symlink is deliberate compatibility for pre-FHS
|
||||
# layouts, so the Policy 9.1.2 tags it raises are expected.
|
||||
# TODO: remove alongside debian/xrpld.links after rippled fully deprecated.
|
||||
xrpld: dir-in-usr-local [usr/local/bin/]
|
||||
xrpld: file-in-usr-local [usr/local/bin/rippled]
|
||||
xrpld: file-in-unusual-dir [usr/local/bin/rippled]
|
||||
@@ -2,9 +2,9 @@ ARG BASE_IMAGE=debian:trixie
|
||||
|
||||
FROM ${BASE_IMAGE}
|
||||
|
||||
# Bind-mounted rather than copied in, so the installer never lands in a layer.
|
||||
RUN --mount=type=bind,source=bin/install-packaging-tools.sh,target=/install-packaging-tools.sh \
|
||||
/install-packaging-tools.sh
|
||||
COPY bin/install-packaging-tools.sh /tmp/install-packaging-tools.sh
|
||||
|
||||
RUN /tmp/install-packaging-tools.sh
|
||||
|
||||
# See ../README.md, "Publishing from other repositories".
|
||||
COPY package/docker/publish_pkg.py /usr/local/bin/publish_pkg.py
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Publish built DEB and RPM packages to the XRPLF repositories on Nexus.
|
||||
|
||||
Knows nothing about what it uploads beyond the channel, so it publishes whatever
|
||||
built the packages; see package/README.md, "Publishing from other repositories".
|
||||
Takes packages and a channel, and nothing else, so it publishes whatever built
|
||||
them; see package/README.md, "Publishing from other repositories".
|
||||
|
||||
RPMs are uploaded to the hosted repository, but yum clients install from the
|
||||
'rpm-<channel>' group repository in front of it, which serves signed metadata.
|
||||
@@ -29,9 +29,6 @@ STALL_TIMEOUT = 300
|
||||
ATTEMPTS = 4
|
||||
RETRY_DELAY = 5
|
||||
|
||||
# 429 is Nexus asking to slow down, not a rejection, so it retries like a 5xx.
|
||||
RETRYABLE_STATUSES = (429,)
|
||||
|
||||
|
||||
def build_opener() -> urllib.request.OpenerDirector:
|
||||
"""An opener with no redirect handler, so a 3xx raises instead of being followed.
|
||||
@@ -50,9 +47,9 @@ def build_opener() -> urllib.request.OpenerDirector:
|
||||
def upload(url: str, method: str, headers: dict[str, str], package: Path) -> None:
|
||||
"""Send one package, retrying only what is worth retrying.
|
||||
|
||||
A 4xx other than 429 is a deterministic rejection, so it is reported at once
|
||||
rather than re-sending the whole body three more times. Nexus explains what
|
||||
it rejected in the response body, so that body is always surfaced.
|
||||
A 4xx is a deterministic rejection, so it is reported at once rather than
|
||||
re-sending the whole body three more times. Nexus explains what it rejected
|
||||
in the response body, so that body is always surfaced.
|
||||
"""
|
||||
opener = build_opener()
|
||||
|
||||
@@ -70,7 +67,7 @@ def upload(url: str, method: str, headers: dict[str, str], package: Path) -> Non
|
||||
except urllib.error.HTTPError as error:
|
||||
detail = error.read().decode(errors="replace").strip()
|
||||
reason = f"HTTP {error.code}: {detail}"
|
||||
retryable = error.code >= 500 or error.code in RETRYABLE_STATUSES
|
||||
retryable = error.code >= 500
|
||||
except (urllib.error.URLError, OSError) as error:
|
||||
reason = str(error)
|
||||
retryable = True
|
||||
@@ -124,8 +121,6 @@ def main() -> None:
|
||||
token = base64.b64encode(f"{username}:{password}".encode()).decode()
|
||||
auth = {"Authorization": f"Basic {token}"}
|
||||
|
||||
# Deliberately not shared with sign_rpm.py: this script ships standalone in
|
||||
# the packaging image for other repositories to run.
|
||||
packages = sorted(
|
||||
path
|
||||
for path in package_dir.rglob("*")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user