Compare commits

..

64 Commits

Author SHA1 Message Date
Denis Angell
d260b4be6d docs: state what the claim precision check guarantees 2026-10-07 10:57:54 -04:00
Denis Angell
df4835d4f0 test: show IOU escrow credit rounding and the close-refund limit check 2026-10-07 10:31:12 -04:00
Denis Angell
522689aea6 chore: merge develop into token-paychan 2026-10-06 21:12:40 -04:00
Shawn Xie
60195e6d37 fix: Fix MPT partial payment overflow (#8302) 2026-10-06 23:16:13 +00:00
Ayaz Salikhov
3d526d456e build: Use LLVM 23 (#8522) 2026-10-06 21:19:57 +00:00
Denis Angell
795eb2fdfb test: close an expired token channel through fund without creating a holding 2026-10-06 16:28:47 -04:00
Denis Angell
e1ce7f40d8 fix: reject token channel locks that would round the source balance 2026-10-06 16:10:01 -04:00
Denis Angell
f05c9f7913 refactor: Extract escrow lock helpers and paychan test helpers (#7882)
Co-authored-by: Mayukha Vadari <mvadari@ripple.com>
2026-10-06 19:58:52 +00:00
Harshit Gupta
c2a4bc3aa1 fix: Validate vetoed parameter type in feature RPC (#7583)
Co-authored-by: Mayukha Vadari <mvadari@ripple.com>
2026-10-06 19:44:56 +00:00
Mayukha Vadari
718185e3b2 refactor: Use CheckEntry everywhere (#8349) 2026-10-06 19:26:35 +00:00
Ayaz Salikhov
1d7783bb86 build: Make conan retry with Conan Center's source backups (#8524) 2026-10-06 19:23:38 +00:00
Mayukha Vadari
9fd2c552f5 refactor: Use AmendmentsEntry everywhere (#8368) 2026-10-06 17:59:44 +00:00
Denis Angell
cac9d753e1 docs: list token paychan API changes under 3.5.0 2026-10-06 13:19:57 -04:00
Denis Angell
9696e9021a refactor: move escrow and paychan helper specializations to source files 2026-10-06 13:18:28 -04:00
Denis Angell
8e82bc8de1 chore: merge develop into token-paychan 2026-10-06 13:06:44 -04:00
Denis Angell
21c4a3b02d fix: reject vault shares in token payment channels 2026-10-06 12:53:03 -04:00
Ayaz Salikhov
70b8fd301b chore: Update docker images; link Conan-built tools with a static runtime (#8520) 2026-10-06 16:47:35 +00:00
Denis Angell
6a1cb5fc1a fix: keep a token channel's unclaimed remainder exact 2026-10-06 12:38:22 -04:00
Denis Angell
346316e9a3 fix: release the channel reserve before the token refund on close 2026-10-06 11:17:05 -04:00
Mayukha Vadari
e6564f553d refactor: Use NegativeUNLEntry everywhere (#8365) 2026-10-06 14:23:24 +00:00
Ayaz Salikhov
ed96e60ce3 build: Make clang-tools custom in Nix, to match what's being built (#8521) 2026-10-06 13:59:12 +00:00
Alex Kremer
2ebd745a1b refactor: Migrate handlers to rpc-spec (A) (#8345) 2026-10-06 13:19:20 +00:00
Mayukha Vadari
cfcbe45b60 test: Declare, not define, entry instantiations in SLEBase test (#8357) 2026-10-06 12:12:22 +00:00
Denis Angell
41aeb2149a fix: treat an overflowing IOU sum as inexact in isExactSum 2026-10-05 20:27:23 -04:00
Denis Angell
489d2d84fa Merge remote-tracking branch 'origin/develop' into token-paychan 2026-10-05 20:11:45 -04:00
yinyiqian1
9cbf78ba99 test: Add more tests for granular permissions (#8459)
Co-authored-by: Bart <bthomee@users.noreply.github.com>
2026-10-05 23:18:58 +00:00
Timur Yalymov
b8d8738f81 fix: Enforce that MPT issuance flags are never cleared (#8152) 2026-10-05 23:17:36 +00:00
Timur Yalymov
63c97e719f feat: Allow lending transactions in Batch (#8244)
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Vito Tumas <5780819+Tapanito@users.noreply.github.com>
2026-10-05 23:17:26 +00:00
Mayukha Vadari
3ac26f23c7 feat: Add full support for all objects in ledger_entry (#6319)
Co-authored-by: Timur Yalymov <36795566+tyalymov@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Vito Tumas <5780819+Tapanito@users.noreply.github.com>
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
2026-10-05 23:17:14 +00:00
Bart
ff9410bc56 build: Define XRPL_ASAN, XRPL_TSAN, and XRPL_UBSAN compile definitions (#8483)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-10-05 23:15:08 +00:00
Denis Angell
32c8893ee8 test: name the isExactSum operands so the swapped-argument check passes 2026-10-05 13:19:30 -04:00
Denis Angell
20b1c3efc2 fix: serialize token claim authorizations as the Amount field value 2026-10-05 11:42:45 -04:00
Denis Angell
59975e1742 fix: require an exact IOU sum when funding a payment channel 2026-10-05 11:32:36 -04:00
Denis Angell
0dc1ae9783 test: expect the large mantissa under MPTokensV2 in PayChanToken 2026-10-05 10:43:00 -04:00
Denis Angell
a5610d8443 Merge remote-tracking branch 'refs/remotes/origin/develop' into token-paychan 2026-10-05 10:31:26 -04:00
Denis Angell
0536d4f0dd fix: include optional in PayChanToken test 2026-10-05 10:29:55 -04:00
Denis Angell
41bcd23edd fix: Guard MPT channel funding against sfAmount overflow 2026-10-03 06:41:46 +00:00
Denis Angell
4dbffc8201 fix: Validate PaymentChannelClaim Amount asset independently of Balance 2026-10-03 00:26:44 +00:00
Denis Angell
d2ac3a0b58 Local merge develop into token-paychan 2026-10-02 21:26:19 +00:00
Denis Angell
830bc0d1f3 docs: Add token-enabled PaymentChannel schema changes to API-CHANGELOG 2026-10-01 20:57:24 +00:00
Denis Angell
9214e7d3fd fix: Gate MPT-denominated PaymentChannelClaim fields on featureMPTokensV1 2026-10-01 20:57:22 +00:00
Denis Angell
0c0a0e8794 fix: satisfy clang-tidy in token channel RPC tests 2026-09-30 11:49:52 -04:00
Denis Angell
86b3835d54 feat: accept token amounts in channel_authorize and channel_verify 2026-09-30 11:28:39 -04:00
Denis Angell
7990cfb8a0 fix: drop unused SeqProxy include from TestHelpers 2026-09-30 10:43:59 -04:00
Denis Angell
c01a0539fe Merge remote-tracking branch 'origin/develop' into token-paychan 2026-09-30 10:41:48 -04:00
Denis Angell
cdfe9cb65d fix: guard paychan rate helper and share gateway locked totals 2026-09-30 10:15:21 -04:00
Denis Angell
4258eca938 fix: include TestHelpers.h in PayChan_test for the Gate helper from develop 2026-09-13 19:25:35 -04:00
Denis Angell
01919230a5 Merge remote-tracking branch 'origin/develop' into token-paychan 2026-09-13 19:19:33 -04:00
Denis Angell
2ba6f4cc68 Merge remote-tracking branch 'origin/develop' into token-paychan
# Conflicts:
#	include/xrpl/ledger/helpers/EscrowHelpers.h
#	src/libxrpl/tx/invariants/MPTInvariant.cpp
#	src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp
#	src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp
#	src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp
#	src/test/jtx/impl/TestHelpers.cpp
2026-08-31 11:35:47 -04:00
Denis Angell
c5ae59c4a0 fix: gate paychan claim MPToken invariant relaxation on featureTokenPaychan 2026-08-25 11:10:39 -04:00
Denis Angell
1ac1d40d06 fix: satisfy clang-tidy include-cleaner and brace style in paychan 2026-08-03 12:26:20 -04:00
Denis Angell
fb10515cff Merge remote-tracking branch 'origin/develop' into token-paychan
# Conflicts:
#	include/xrpl/protocol/detail/features.macro
2026-08-03 11:38:43 -04:00
Denis Angell
3c85a0180f refactor: address review nits 2026-08-02 16:05:25 -04:00
Denis Angell
096fa967da fix: repair bad develop merge resolutions 2026-08-02 16:05:22 -04:00
dangell8
1ced7dc8f3 Merge branch 'develop' into token-paychan
Includes post-merge compile fixes (build-verified).
2026-07-17 15:02:03 +00:00
Denis Angell
df1458a136 test: expand token paychan transactor phase coverage 2026-07-13 19:25:43 -04:00
Denis Angell
cdd5ebf4c1 fix: validate paychan asset match in preclaim with tecWRONG_ASSET 2026-07-13 16:33:49 -04:00
Denis Angell
1a9a11fc31 style: remove includes flagged by misc-include-cleaner 2026-07-13 15:28:42 -04:00
Denis Angell
a9430d4778 style: conform doxygen comments to check-doxygen-style hook 2026-07-13 15:11:48 -04:00
Denis Angell
fed5093488 fix: adapt token paychan to develop merge helpers and sponsor reserve 2026-07-13 14:59:54 -04:00
Denis Angell
6c74fc1a7d Merge branch 'develop' into token-paychan 2026-07-13 14:05:56 -04:00
dangell8
939f8b1a49 Merge develop into token-paychan 2026-07-11 04:27:08 +00:00
Denis Angell
1f8c462c42 feat: XLS-93d Token-Enabled Payment Channels 2026-07-10 12:14:12 -04:00
Denis Angell
47df026dcc refactor: Move escrow lock/unlock helpers to EscrowHelpers.h 2026-07-10 11:55:13 -04:00
196 changed files with 11570 additions and 3125 deletions

View File

@@ -44,4 +44,12 @@ runs:
--conf:all tools.build:jobs=${BUILD_NPROC} \
--conf:all tools.build:verbosity="${LOG_VERBOSITY}" \
--conf:all tools.compilation:verbosity="${LOG_VERBOSITY}" \
.
--format=json \
. >"${RUNNER_TEMP}/conan-graph.json"
# Tools that run during the build may only load glibc from the Nix store,
# as their package ID survives a GCC runtime update.
- name: Check build-context packages for Nix store dependencies (Linux)
if: ${{ runner.os == 'Linux' }}
shell: bash
run: ./bin/check-build-context-runtime.sh "${RUNNER_TEMP}/conan-graph.json"

View File

@@ -1,5 +1,5 @@
{
"image_tag": "sha-060957e",
"image_tag": "sha-ed96e60",
"configs": {
"ubuntu": [
{
@@ -74,7 +74,7 @@
"extra_cmake_args": "-Dvalidator_keys=ON",
"package": {
"type": "deb",
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-3a2d19f"
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-e6055dd"
}
},
{
@@ -86,7 +86,7 @@
"extra_cmake_args": "-Dvalidator_keys=ON -Dassert=ON",
"package": {
"type": "deb",
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-3a2d19f",
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-e6055dd",
"variant": "assert"
}
}
@@ -101,7 +101,7 @@
"extra_cmake_args": "-Dvalidator_keys=ON",
"package": {
"type": "rpm",
"image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-3a2d19f"
"image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-e6055dd"
}
}
]

View File

@@ -13,7 +13,6 @@ on:
- "!nix/devshell.nix"
- "!nix/check-tools/**"
- "bin/default-loader-path.sh"
- "bin/install-sanitizer-libs.sh"
pull_request:
paths:
- ".github/workflows/build-nix-images.yml"

View File

@@ -5,7 +5,6 @@ on:
branches:
- develop
paths:
- ".github/workflows/build-packaging-images.yml"
- "bin/install-packaging-tools.sh"
- "package/images/packaging/**"
pull_request:

View File

@@ -5,7 +5,6 @@ on:
branches:
- develop
paths:
- ".github/workflows/build-pre-commit-image.yml"
- "bin/pre-commit/Dockerfile"
- "rust-toolchain.toml"
pull_request:

View File

@@ -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-ed96e60
permissions:
contents: read
# Needed to open an issue on scheduled failures.

View File

@@ -17,4 +17,4 @@ jobs:
uses: XRPLF/actions/.github/workflows/pre-commit.yml@279ec358f4a1be4088be3e024b07916fa97c75b6
with:
runs_on: ubuntu-latest
container: '{ "image": "ghcr.io/xrplf/xrpld/pre-commit:sha-473fe44" }'
container: '{ "image": "ghcr.io/xrplf/xrpld/pre-commit:sha-3a2d19f" }'

View File

@@ -41,7 +41,7 @@ env:
jobs:
build:
runs-on: ubuntu-latest
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-ed96e60
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

View File

@@ -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-ed96e60"
permissions:
contents: read
issues: write

View File

@@ -28,7 +28,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-ed96e60
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -41,7 +41,7 @@ jobs:
coverage:
runs-on: ubuntu-latest
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-ed96e60
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -70,7 +70,7 @@ jobs:
doc:
runs-on: ubuntu-latest
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-ed96e60
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

View File

@@ -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-ed96e60
env:
REMOTE_NAME: ${{ inputs.remote_name }}
CONAN_LOGIN_USERNAME_XRPLF: ${{ secrets.remote_username }}

View File

@@ -29,12 +29,20 @@ Version 3.5.0 is not yet released.
### Additions in 3.5.0
- `subscribe`, `unsubscribe`: Added an optional `mpt_issuances` request field, an array of MPT issuance IDs (hex strings). Subscribers receive a message with `type` `mptTransaction` for each validated transaction whose metadata affects a subscribed issuance; the message has the same fields as the `transactions` stream. MPT issuance subscriptions count toward the per-connection subscription limit. An empty array, a non-array value, or an invalid ID returns `invalidParams`. ([#5671](https://github.com/XRPLF/rippled/pull/5671))
- `ledger_entry`: Add full support for checks, NFT offers, payment channels, and signer lists. ([#6319](https://github.com/XRPLF/rippled/pull/6319))
- `channel_authorize`, `channel_verify`: `amount` now also accepts the transaction `Amount` JSON object for a token payment channel (`currency`, `issuer` and `value` for a trust line token; `mpt_issuance_id` and `value` for an MPT), and the claim authorization is serialized for that token. A string of drops is still used for an XRP channel. An object that names XRP, is negative or is malformed returns `channelAmtMalformed`. On the command line, the amount argument may be that JSON object as a single argument. For a token channel the signed message is the `PaymentChannelClaim` hash prefix, the channel ID and the amount serialized as an `Amount` field value without its field header; the XRP message is unchanged. ([#7935](https://github.com/XRPLF/rippled/pull/7935))
- `PaymentChannelCreate`, `PaymentChannelFund`, `PaymentChannelClaim`: `Amount` (and `Balance` on `PaymentChannelClaim`) now accept a trust line or MPT value in addition to XRP, gated by the `TokenPaychan` amendment. ([#7935](https://github.com/XRPLF/rippled/pull/7935))
- `ledger_entry`, `account_objects`: The `PayChannel` ledger entry now includes optional `TransferRate` and `IssuerNode` fields, present on a channel funded with a trust line or MPT value. ([#7935](https://github.com/XRPLF/rippled/pull/7935))
### Bugfixes in 3.5.0
- `channel_authorize`: The `channel_id` field now returns an `invalidParams` error if the value is not a string. [#7582](https://github.com/XRPLF/rippled/pull/7582)
- `channel_verify`: The `channel_id` and `signature` fields now return an `invalidParams` error if the value is not a string. [#7582](https://github.com/XRPLF/rippled/pull/7582)
### Bugfixes in 3.5.0
- `feature`: The admin-only `vetoed` field now returns `invalidParams` unless its value is a boolean. [#7583](https://github.com/XRPLF/rippled/pull/7583)
## XRP Ledger server version 3.4.0
Version 3.4.0 is not yet released. These changes are available in the 3.4.0 beta releases.

View File

@@ -0,0 +1,65 @@
#!/usr/bin/env bash
# Fail if a binary of a build-context Conan package loads anything from the Nix
# store other than glibc, or cannot resolve a library at all.
#
# Only binaries linked by the Nix toolchain are checked, i.e. those recording a
# store path as their interpreter or RUNPATH. Prebuilt upstream binaries (such
# as the ones the cmake package ships) use the system loader instead.
#
# Build-context packages provide the tools that run during the build (protoc,
# grpc_cpp_plugin, ...). Their package ID does not change when a Nix toolchain
# update moves the GCC runtime to a new store path, so a cached binary has to
# get by with the pinned glibc alone. See docs/build/nix.md.
#
# Usage: bin/check-build-context-runtime.sh <graph.json>
# <graph.json> is the output of `conan install --format=json`.
set -euo pipefail
if [ "$#" -ne 1 ]; then
echo "usage: $0 <graph.json>" >&2
exit 2
fi
if [ "$(uname -s)" != "Linux" ]; then
echo "$0: Linux only" >&2
exit 2
fi
folders="$(jq -r '.graph.nodes[] | select(.context == "build" and .package_folder) | .package_folder' "$1" | sort -u)"
checked=0
failed=0
while IFS= read -r file; do
case "$(file -b "${file}")" in
ELF*) ;;
*) continue ;;
esac
[[ "$(readelf -ldW "${file}")" == */nix/store/* ]] || continue
checked=$((checked + 1))
# `ldd` lists the interpreter and every library as the loader resolves them.
if deps="$(ldd "${file}" 2>&1)"; then
bad="$(printf '%s\n' "${deps}" |
grep -E 'not found|/nix/store/' |
grep -vE '/nix/store/[^/]+-glibc-[^/]+/' || true)"
else
case "${deps}" in
*"not a dynamic executable"*) continue ;;
esac
bad="${deps}"
fi
if [ -n "${bad}" ]; then
failed=$((failed + 1))
echo "::error file=${file}::loads a library from the Nix store other than glibc"
echo "${file}"
echo "${bad}" | sed 's/^/ /'
fi
done < <(
# shellcheck disable=SC2086 # one folder per line, no spaces in Conan paths
[ -z "${folders}" ] || find ${folders} -type f \( -perm -u+x -o -name '*.so*' \)
)
echo "Build-context packages: checked ${checked} binaries, ${failed} failed."
[ "${failed}" -eq 0 ]

View File

@@ -20,9 +20,11 @@
# development setups, but not in the macOS CI environment. They are checked
# everywhere except when running in CI on macOS.
#
# Tools that Nix also exposes under a version-suffixed name (`clang-tidy-22`,
# `g++-15`, ...) are probed under both names: a suffixed name can break while
# the plain one still works (see mkVersionedToolLinks in nix/packages.nix).
# Tools that Nix also exposes under a version-suffixed name
# (`clang-tidy-<v>`, `g++-<v>`, ...) are probed under both names:
# a suffixed name can break while the plain one still works
# (see mkVersionedToolLinks in nix/packages.nix).
# The suffix is the major version of the plain `clang` / `gcc` on PATH.
#
# Tools scoped to a single dev shell rather than to commonPackages are checked
# only in that shell, keyed off XRPL_DEVSHELL.
@@ -34,9 +36,16 @@
set -uo pipefail
# Version suffixes of the Nix tool links, tracking nix/packages.nix.
gcc_version=15
llvm_version=22
# major_version <compiler>
# Major version of a compiler on PATH, or "unknown" when it isn't there.
major_version() {
local version
version="$("$1" -dumpversion 2>/dev/null)" || version=""
version="${version%%.*}"
printf '%s' "${version:-unknown}"
}
llvm_version="$(major_version clang)"
missing=()
checked=0
@@ -112,6 +121,7 @@ if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then
check ClangBuildAnalyzer
check curl
check file
check jq
check less
check make
# net-tools netstat reports "net-tools X.Y"; macOS ships BSD netstat with no
@@ -180,6 +190,7 @@ fi
if [ "${os}" = "linux" ]; then
echo
echo "GCC toolchain:"
gcc_version="$(major_version gcc)"
check gcc
check "gcc-${gcc_version}"
check g++

View File

@@ -23,7 +23,7 @@ apt-get clean
rm -rf /var/lib/apt/lists/*
EOF
ARG PRE_COMMIT_VERSION=4.6.0
ARG PRE_COMMIT_VERSION=4.6.2
RUN pip install --no-cache --break-system-packages \
pre-commit==${PRE_COMMIT_VERSION}

View File

@@ -26,8 +26,6 @@ import sys
import tempfile
from pathlib import Path
CLANG_TIDY_VERSION = 22
# Extensions run-clang-tidy can analyse: `.cpp` translation units and, thanks to
# the `verify_headers` build option, `.h`/`.hpp` headers (each has its own
# compile_commands.json entry). `.ipp` fragments have no entry and are skipped.
@@ -39,8 +37,21 @@ TIDY_EXTENSIONS = {".cpp", ".h", ".hpp"}
FILEPATH_RE = re.compile(r"^(\s*(?:-\s+)?FilePath:\s*)'((?:[^']|'')*)'\s*$")
def find_tool(name: str) -> str | None:
for candidate in (f"{name}-{CLANG_TIDY_VERSION}", name):
def clang_tidy_major() -> str | None:
"""Major version of the `clang-tidy` on PATH, which run-clang-tidy invokes."""
if not (clang_tidy := shutil.which("clang-tidy")):
return None
output = subprocess.run(
[clang_tidy, "--version"], capture_output=True, text=True
).stdout
m = re.search(r"LLVM version (\d+)", output)
return m.group(1) if m else None
def find_tool(name: str, version: str | None) -> str | None:
"""Prefer `<name>-<version>`, so a host tool of another version can't win."""
candidates = ([f"{name}-{version}"] if version else []) + [name]
for candidate in candidates:
if path := shutil.which(candidate):
return path
return None
@@ -103,8 +114,9 @@ def main():
if not files:
return 0
run_clang_tidy = find_tool("run-clang-tidy")
clang_apply_replacements = find_tool("clang-apply-replacements")
version = clang_tidy_major()
run_clang_tidy = find_tool("run-clang-tidy", version)
clang_apply_replacements = find_tool("clang-apply-replacements", version)
missing = [
name
for name, path in (
@@ -114,9 +126,10 @@ def main():
if not path
]
if missing:
tried = f" (tried the '-{version}' suffix too)" if version else ""
print(
f"clang-tidy check failed: TIDY is enabled but {' and '.join(missing)} "
f"was not found in PATH (tried the '-{CLANG_TIDY_VERSION}' suffix too).",
f"was not found in PATH{tried}.",
file=sys.stderr,
)
return 1

View File

@@ -292,6 +292,14 @@ if(xrpld)
)
target_sources(xrpld PRIVATE ${sources})
rpcspec_generate_instantiations(
OUT_VAR rpcspec_instantiations
VALUE_TYPE "::json::Value"
VIEW_HEADER "xrpld/rpc/detail/JsonObjectView.hpp"
HANDLERS ledger
)
target_sources(xrpld PRIVATE ${rpcspec_instantiations})
if(tests)
file(
GLOB_RECURSE sources

View File

@@ -77,19 +77,24 @@ if(is_clang)
message(STATUS " Ignorelist: ${ignorelist_path}")
endif()
# Define SANITIZERS macro for BuildInfo.cpp
# Define the SANITIZERS macro for BuildInfo.cpp, plus one of XRPL_ASAN,
# XRPL_TSAN and XRPL_UBSAN per active sanitizer, so that code can test for a
# specific one with #ifdef instead of parsing the dot-joined SANITIZERS string.
set(sanitizers_list)
if(SANITIZERS MATCHES "address")
set(enable_asan ON)
list(APPEND sanitizers_list "ASAN")
target_compile_definitions(common INTERFACE XRPL_ASAN)
endif()
if(SANITIZERS MATCHES "thread")
set(enable_tsan ON)
list(APPEND sanitizers_list "TSAN")
target_compile_definitions(common INTERFACE XRPL_TSAN)
endif()
if(SANITIZERS MATCHES "undefinedbehavior")
set(enable_ubsan ON)
list(APPEND sanitizers_list "UBSAN")
target_compile_definitions(common INTERFACE XRPL_UBSAN)
endif()
if(sanitizers_list)

View File

@@ -3,7 +3,7 @@
"requires": [
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1782392402.122708",
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1782392402.420688",
"xrpl-rpc-spec/0.1.19#870b2d3abcfbbf13b61c2d3c69495060%1790348286.187549",
"xrpl-rpc-spec/0.1.20#6daa13eeb4e6c92b82ea6d78866e1da8%1790720024.368048",
"sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1782392403.185447",
"soci/4.0.3#e726491a03468795453f7c83fc924a96%1782392402.679521",
"snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1782307151.633168",
@@ -20,7 +20,7 @@
"libarchive/3.8.7#c446109bd1f1d8ba7936c94189bc50e6%1782392403.066892",
"jemalloc/5.3.1#1fc58d55316041f10fbc1e8a2eae632a%1776700028.228",
"gtest/1.17.0#5224b3b3ff3b4ce1133cbdd27d53ee7d%1782392402.791979",
"grpc/1.81.1#b87796a4269034856cbc1a2522db16eb%1788275071.530512",
"grpc/1.81.1#aaa93ab6cda2f2baa6a84490582c8adf%1791284951.826256",
"fast_float/8.2.10#f6f28d6bb22112078e7dbda611caf681%1785888854.601666",
"ed25519/2015.03#ae761bdc52730a843f0809bdf6c1b1f6%1782307148.15562",
"date/3.0.4#862e11e80030356b53c2c38599ceb32b%1782392402.538492",
@@ -34,11 +34,15 @@
"build_requires": [
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1782392402.122708",
"strawberryperl/5.32.1.1#8d114504d172cfea8ea1662d09b6333e%1782395692.540639",
"re2/20251105#8579cfd0bda4daf0683f9e3898f964b4%1782392402.431897",
"protobuf/6.33.5#ff253ead763bd8d9904a52979cd21e81%1782392410.233933",
"openssl/3.6.3#f806de8933e3bf6f01016c6a888cee2e%1783945160.863288",
"nasm/2.16.01#31e26f2ee3c4346ecd347911bd126904%1782395690.33162",
"msys2/cci.latest#d22fe7b2808f5fd34d0a7923ace9c54f%1770657326.649",
"m4/1.4.19#1727f439cf74e83826ec96d0b4904eee%1784541921.659",
"grpc/1.81.1#aaa93ab6cda2f2baa6a84490582c8adf%1791284951.826256",
"cmake/4.3.3#840cf00ea09777e05c2050a50a82c722%1782392418.696091",
"c-ares/1.34.6#545240bb1c40e2cacd4362d6b8967650%1782392402.681654",
"b2/5.4.2#ffd6084a119587e70f11cd45d1a386e2%1782392402.624226",
"automake/1.16.5#b91b7c384c3deaa9d535be02da14d04f%1755524470.56",
"autoconf/2.71#51077f068e61700d65bb05541ea1e4b0%1731054366.86",

View File

@@ -3,5 +3,13 @@
core:non_interactive=True
core.download:parallel={{ os.cpu_count() }}
core.upload:parallel={{ os.cpu_count() }}
tools.files.download:retry=5
# Fall back to Conan Center's source backups when a recipe's upstream URL is down
# (e.g. the GNU FTP mirrors), see
# https://github.com/conan-io/conan-center-index/issues/28147#issuecomment-3183544772
# The backups are only tried once every upstream URL has used up its retries,
# so keep the retries low.
core.sources:download_urls=["origin", "https://c3i.jfrog.io/artifactory/conan-center-backup-sources"]
tools.files.download:retry=1
tools.files.download:retry_wait=10
# Fail fast on unreachable hosts (default connect timeout is 30s), keep the 60s read timeout.
core.net.http:timeout=(5, 60)

View File

@@ -29,9 +29,9 @@ os.version={{ min_macos_version }}
[conf]
{# The Boost recipe builds with b2, which doesn't use Conan's toolchain files. #}
{# Instead it hand-rolls the compiler for user-config.jam, #}
{# and its fallback probes a version-suffixed binary (e.g. `g++-15`) before plain `g++`. #}
{# Inside the Nix shell the wrapper only provides `g++`/`gcc` (no `-15` suffix), #}
{# so on a host that also has a system `g++-15` the probe escapes Nix #}
{# and its fallback probes a version-suffixed binary (e.g. `g++-<major>`) before plain `g++`. #}
{# Inside the Nix shell the wrapper only provides `g++`/`gcc` (no `-<major>` suffix), #}
{# so on a host that also has a system `g++-<major>` the probe escapes Nix #}
{# and picks the system compiler, which is mismatched with the Nix libraries #}
{# and breaks the build (e.g. Boost.Stacktrace link checks fail). #}
{# Pinning the executables here short-circuits that probe so Boost (and the rest of the toolchain) #}
@@ -49,6 +49,17 @@ tools.build:compiler_executables={'c':'{{ cc_exe }}','cpp':'{{ cxx_exe }}'}
user.package:cppstd_version=23
tools.info.package_id:confs+=["user.package:cppstd_version"]
{% if os == "Linux" and context == "build" %}
{# Build-context executables (protoc, grpc_cpp_plugin, build tools) run during the build #}
{# and would otherwise load libstdc++/libgcc from a Nix store path #}
{# that might change with a Nix toolchain update. #}
{# --as-needed drops the ones they link but don't use, #}
{# such as libatomic for grpc_cpp_plugin on arm64. #}
{% set static_runtime_flags = ["-static-libstdc++", "-static-libgcc", "-Wl,--as-needed"] %}
tools.build:exelinkflags+={{ static_runtime_flags }}
tools.info.package_id:confs+=["tools.build:exelinkflags"]
{% endif %}
{% if os == "Macos" %}
[buildenv]
{# os.version adds -mmacosx-version-min to compiler command lines, #}
@@ -58,3 +69,13 @@ tools.info.package_id:confs+=["user.package:cppstd_version"]
{# Scoped to boost/* since it is the only gap. #}
boost/*:MACOSX_DEPLOYMENT_TARGET={{ min_macos_version }}
{% endif %}
{% if os == "Linux" and context == "build" %}
{# b2 links itself with its own script, which ignores exelinkflags #}
{# and only takes CXXFLAGS when use_cxx_env is set. #}
[options]
b2/*:use_cxx_env=True
[buildenv]
b2/*:CXXFLAGS={{ static_runtime_flags | join(" ") }}
{% endif %}

View File

@@ -5,6 +5,9 @@ include(default)
{% if not sanitizers %}
{# Sanitizers not configured; no additional settings needed #}
{% elif context == "build" %}
{# Build-context packages are tools we run, not code we test, #}
{# so don't instrument them #}
{% else %}
{% if compiler == "msvc" %}

View File

@@ -40,7 +40,7 @@ class Xrpl(ConanFile):
"nudb/2.0.9",
"openssl/3.6.3",
"soci/4.0.3",
"xrpl-rpc-spec/0.1.19",
"xrpl-rpc-spec/0.1.20",
"zlib/1.3.2",
]
@@ -49,7 +49,8 @@ class Xrpl(ConanFile):
]
tool_requires = [
"protobuf/6.33.5",
"grpc/<host_version>",
"protobuf/<host_version>",
]
default_options = {
@@ -123,6 +124,10 @@ class Xrpl(ConanFile):
"xxhash/*:shared": False,
}
# default_options only reach the host context;
# give tool_requires (and their dependencies) the same dependency options.
default_build_options = {k: v for k, v in default_options.items() if "/" in k}
def set_version(self):
self.version = self.version or DEV_VERSION

View File

@@ -10,14 +10,15 @@ This document explains how to set one up.
support it — see [compiler support for C++23][cpp23-support].
The versions currently tested in CI are:
| Compiler | Version |
| ----------- | ------------------ |
| GCC | 15.2 |
| Clang | 22 |
| Apple Clang | 21 |
| MSVC | Visual Studio 2026 |
| Compiler | Version |
| ----------- | ------------------------------- |
| GCC | `gccVersion` in [packages.nix] |
| Clang | `llvmVersion` in [packages.nix] |
| Apple Clang | 21 |
| MSVC | Visual Studio 2026 |
LLVM tools (`clang-tidy` and `clang-format`) are also pinned to version 22.
LLVM tools (`clang-tidy` and `clang-format`)
come from the same LLVM release as Clang.
### Older compilers
@@ -156,3 +157,4 @@ 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
[packages.nix]: ../../nix/packages.nix

20
docs/build/nix.md vendored
View File

@@ -194,10 +194,22 @@ 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:
writes the store into `PT_INTERP` and `RUNPATH`. That is fine for the pinned
glibc, whose path does not move, but not for the GCC runtime, which moves with
every GCC update. So [`conan/profiles/default`](../../conan/profiles/default)
links build-context packages, whose executables run during the build, with
`-static-libstdc++ -static-libgcc -Wl,--as-needed`, and
[`conan/profiles/sanitizers`](../../conan/profiles/sanitizers) does not
instrument them. CI checks that they load nothing from the store but glibc, from
the graph `conan install --format=json` writes:
```bash
bin/check-build-context-runtime.sh graph.json
```
Only the binaries [`PatchNixBinary.cmake`](../../cmake/PatchNixBinary.cmake)
retargets to the system loader have to be fully clean, and those are what CI
checks:
```bash
bin/check-nix-store-refs.sh build/xrpld

View File

@@ -1,31 +1,29 @@
#pragma once
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/beast/type_name.h>
#include <boost/core/type_name.hpp>
#include <algorithm>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
namespace xrpl {
/**
* Manages all counted object types.
*
* Counters register themselves on a lock-free intrusive list maintained
* by this object when constructed. Because counters are never destroyed
* or removed, the ABA problem does not apply.
*
* The registry is iterable as a forward range.
*/
class CountedObjects
{
public:
static CountedObjects&
getInstance() noexcept;
using Entry = std::pair<std::string, int>;
using List = std::vector<Entry>;
[[nodiscard]] List
getCounts(int minimumThreshold) const;
public:
/**
* Implementation for @ref CountedObject.
@@ -35,147 +33,68 @@ public:
class Counter
{
public:
Counter(std::string name) noexcept;
Counter(std::string name) noexcept : name_(std::move(name)), count_(0)
{
// Insert ourselves at the front of the lock-free linked list
CountedObjects& instance = CountedObjects::getInstance();
Counter* head = nullptr;
// Counters are intrusive list nodes whose addresses are published
// in the registry; they must never be copied or moved. The atomic
// members already force this, but we make it explicit.
Counter(Counter const&) = delete;
Counter&
operator=(Counter const&) = delete;
Counter(Counter&&) = delete;
Counter&
operator=(Counter&&) = delete;
do
{
head = instance.head_.load();
next_ = head;
} while (instance.head_.exchange(this) != head);
std::uint32_t
++instance.count_;
}
~Counter() noexcept = default;
int
increment() noexcept
{
auto const newCount = count_.fetch_add(1, std::memory_order::relaxed) + 1;
XRPL_ASSERT(newCount != 0, "xrpl::CountedObjects::Counter::increment : no overflow");
auto maxCount = maxCount_.load(std::memory_order::relaxed);
while (newCount > maxCount &&
!maxCount_.compare_exchange_weak(maxCount, newCount, std::memory_order::relaxed))
{
}
return newCount;
return ++count_;
}
std::uint32_t
int
decrement() noexcept
{
auto const prev = count_.fetch_sub(1, std::memory_order::relaxed);
XRPL_ASSERT(prev != 0, "xrpl::CountedObjects::Counter::decrement : no underflow");
return prev - 1;
return --count_;
}
[[nodiscard]] std::uint32_t
count() const noexcept
[[nodiscard]] int
getCount() const noexcept
{
return count_.load(std::memory_order::relaxed);
return count_.load();
}
[[nodiscard]] std::uint32_t
max() const noexcept
[[nodiscard]] Counter*
getNext() const noexcept
{
return std::max(
count_.load(std::memory_order::relaxed),
maxCount_.load(std::memory_order::relaxed));
return next_;
}
[[nodiscard]] std::string const&
name() const noexcept
getName() const noexcept
{
return name_;
}
private:
friend class CountedObjects;
Counter* next_ = nullptr;
std::atomic<std::uint32_t> count_ = 0;
std::atomic<std::uint32_t> maxCount_ = 0;
std::string const name_;
std::atomic<int> count_;
Counter* next_;
};
class Iterator
{
public:
using value_type = Counter const;
using reference = value_type&;
using pointer = value_type*;
using difference_type = std::ptrdiff_t;
using iterator_category = std::forward_iterator_tag;
explicit Iterator(Counter* c = nullptr) noexcept : current_(c)
{
}
reference
operator*() const noexcept
{
return *current_;
}
pointer
operator->() const noexcept
{
return current_;
}
Iterator&
operator++() noexcept
{
current_ = current_->next_;
return *this;
}
Iterator
operator++(int) noexcept
{
auto tmp = *this;
++*this;
return tmp;
}
bool
operator==(Iterator const&) const noexcept = default;
private:
Counter* current_;
};
constexpr CountedObjects() noexcept = default;
[[nodiscard]] auto
begin() const noexcept
{
return Iterator{head_.load(std::memory_order::acquire)};
}
[[nodiscard]] auto
end() const noexcept
{
return Iterator{};
}
private:
std::atomic<Counter*> head_ = nullptr;
CountedObjects() noexcept;
~CountedObjects() noexcept = default;
private:
std::atomic<int> count_;
std::atomic<Counter*> head_;
};
/** The global counted object registry. */
inline constinit CountedObjects gCountedObjects;
inline CountedObjects::Counter::Counter(std::string name) noexcept
: next_(gCountedObjects.head_.load(std::memory_order::relaxed)), name_(std::move(name))
{
while (!gCountedObjects.head_.compare_exchange_weak(
next_, this, std::memory_order::release, std::memory_order::relaxed))
;
}
//------------------------------------------------------------------------------
/**
@@ -184,37 +103,27 @@ inline CountedObjects::Counter::Counter(std::string name) noexcept
* Derived classes have their instances counted automatically. This is used
* for reporting purposes.
*
* The constructors are private and `Object` is befriended so that the
* CRTP parameter must be the deriving class itself: a copy-paste error
* like `class B : public CountedObject<A>` fails to compile instead of
* silently polluting A's count.
*
* @note This class has no move operations by design: a derived class's
* move constructor falls back to the copy constructor for this
* base, so the newly created instance is counted. This keeps the
* invariant that count is the number of outstanding subobjects.
*
* @warning Counted objects constructed during dynamic initialization of
* other translation units may have their increments discarded when
* counter itself is dynamically initialized. Do not create counted
* objects before main() begins.
*
* @ingroup basics
*/
template <class Object>
requires std::is_class_v<Object>
class CountedObject
{
static inline CountedObjects::Counter counter{boost::core::type_name<Object>()};
private:
static auto&
getCounter() noexcept
{
static CountedObjects::Counter kC{beast::typeName<Object>()};
return kC;
}
CountedObject() noexcept
{
counter.increment();
getCounter().increment();
}
CountedObject(CountedObject const&) noexcept
{
counter.increment();
getCounter().increment();
}
CountedObject&
@@ -223,7 +132,7 @@ class CountedObject
public:
~CountedObject() noexcept
{
counter.decrement();
getCounter().decrement();
}
friend Object;

View File

@@ -3,11 +3,11 @@
#pragma once
#include <xrpl/basics/ByteUtilities.h>
#include <xrpl/beast/type_name.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <boost/align.hpp>
#include <boost/container/static_vector.hpp>
#include <boost/core/type_name.hpp>
#include <boost/predef.h>
#include <algorithm>
@@ -16,7 +16,6 @@
#include <cstring>
#include <mutex>
#include <stdexcept>
#include <typeinfo>
#include <vector>
#if BOOST_OS_LINUX
@@ -336,7 +335,7 @@ public:
}) != cfg.end())
{
throw std::runtime_error(
"SlabAllocatorSet<" + boost::core::type_name<Type>() + ">: duplicate slab size");
"SlabAllocatorSet<" + beast::typeName<Type>() + ">: duplicate slab size");
}
for (auto const& c : cfg)

View File

@@ -1,8 +1,7 @@
#pragma once
#include <xrpl/basics/sanitizers.h>
#include <boost/core/type_name.hpp>
#include <xrpl/beast/type_name.h>
#include <exception>
#include <string>
@@ -56,7 +55,7 @@ Throw(Args&&... args)
std::is_convertible_v<E*, std::exception*>, "Exception must derive from std::exception.");
E e(std::forward<Args>(args)...);
logThrow("Throwing exception of type " + boost::core::type_name<E>() + ": " + e.what());
logThrow(std::string("Throwing exception of type " + beast::typeName<E>() + ": ") + e.what());
throw std::move(e);
}

View File

@@ -1,200 +0,0 @@
#pragma once
#include <concepts>
#include <type_traits>
#include <utility>
namespace xrpl {
/**
* Opt-in bitwise operators for scoped enumerations.
*
* Provides `&`, `|`, `^`, `~` and the corresponding compound assignment forms
* for any scoped enumeration with an unsigned underlying type that opts in by
* specializing @ref OptIn:
*
* @code
* namespace xrpl {
*
* enum class MyFlags : std::uint32_t { a = 1, b = 2, c = 4 };
*
* template <>
* struct enum_bitops::OptIn<MyFlags> : std::true_type {};
*
* } // namespace xrpl
* @endcode
*
* The operators are constexpr and noexcept, and return the enumeration type.
*
* @par Where the specialization may be declared
* In namespace xrpl, or at global scope with full qualification, and after
* the enumeration is defined but before the operators are first used. This
* cannot be done in a nested namespace or at class scope; for enumerations
* nested in a class this means after the class definition.
*
* @par Where the operators are found
* The operators are declared in namespace xrpl, so argument-dependent lookup
* will find them only for enumerations declared in xrpl or nested in a class
* declared directly in xrpl. Enumerations in a nested namespace will only be
* found from code inside xrpl, and only if no enclosing scope declares an
* operator of the same name; elsewhere they require using-declarations.
*/
namespace enum_bitops {
/**
* Types that may be opted in to the bitwise operators.
*
* Satisfied by a scoped enumeration whose underlying type is an unsigned
* integer type.
*
* @tparam T The type to test.
*/
template <typename T>
concept Eligible = std::is_scoped_enum_v<T> && std::unsigned_integral<std::underlying_type_t<T>>;
/**
* Opt-in switch for the bitwise operators.
*
* The primary template derives from std::false_type. Specialize it to derive
* from std::true_type to enable the operators for an @ref Eligible enumeration.
*
* @tparam T The scoped enumeration to opt in.
*/
template <Eligible T>
struct OptIn : std::false_type
{
};
/**
* Enumerations for which the bitwise operators are enabled.
*
* Satisfied when @p T satisfies @ref Eligible and @ref OptIn has been
* specialized for it to derive from std::true_type.
*
* @tparam T The type to test.
*/
template <typename T>
concept Candidate = Eligible<T> && OptIn<T>::value;
} // namespace enum_bitops
// These are declared in xrpl, not in enum_bitops, so that argument-dependent
// lookup finds them for enumerations whose associated namespace is xrpl.
/**
* @name Bitwise operators for opted-in scoped enumerations
*
* Each operator applies the corresponding built-in operator to the
* underlying values and converts the result back to the enumeration type.
* Both operands must have the same enumeration type; there is no implicit
* conversion to or from the underlying type.
*/
/** @{ */
/**
* Bitwise AND.
*
* @param lhs The left operand.
* @param rhs The right operand.
* @return The bits set in both @p lhs and @p rhs.
*/
template <enum_bitops::Candidate T>
constexpr T
operator&(T lhs, T rhs) noexcept
{
return static_cast<T>(std::to_underlying(lhs) & std::to_underlying(rhs));
}
/**
* Bitwise OR.
*
* @param lhs The left operand.
* @param rhs The right operand.
* @return The bits set in @p lhs, in @p rhs, or in both.
*/
template <enum_bitops::Candidate T>
constexpr T
operator|(T lhs, T rhs) noexcept
{
return static_cast<T>(std::to_underlying(lhs) | std::to_underlying(rhs));
}
/**
* Bitwise exclusive OR.
*
* @param lhs The left operand.
* @param rhs The right operand.
* @return The bits set in exactly one of @p lhs and @p rhs.
*/
template <enum_bitops::Candidate T>
constexpr T
operator^(T lhs, T rhs) noexcept
{
return static_cast<T>(std::to_underlying(lhs) ^ std::to_underlying(rhs));
}
/**
* Bitwise complement.
*
* The result has every bit of the underlying type that is clear in
* @p val, including bits that no enumerator names. It is intended for
* clearing flags, as in `flags & ~flag`.
*
* @param val The operand.
* @return The complement of @p val.
*/
template <enum_bitops::Candidate T>
constexpr T
operator~(T val) noexcept
{
return static_cast<T>(~std::to_underlying(val));
}
/**
* Bitwise AND assignment.
*
* @param lhs The value to modify.
* @param rhs The right operand.
* @return A reference to @p lhs.
*/
template <enum_bitops::Candidate T>
constexpr T&
operator&=(T& lhs, T rhs) noexcept
{
lhs = lhs & rhs;
return lhs;
}
/**
* Bitwise OR assignment.
*
* @param lhs The value to modify.
* @param rhs The right operand.
* @return A reference to @p lhs.
*/
template <enum_bitops::Candidate T>
constexpr T&
operator|=(T& lhs, T rhs) noexcept
{
lhs = lhs | rhs;
return lhs;
}
/**
* Bitwise exclusive OR assignment.
*
* @param lhs The value to modify.
* @param rhs The right operand.
* @return A reference to @p lhs.
*/
template <enum_bitops::Candidate T>
constexpr T&
operator^=(T& lhs, T rhs) noexcept
{
lhs = lhs ^ rhs;
return lhs;
}
/** @} */
} // namespace xrpl

View File

@@ -17,7 +17,7 @@
namespace xrpl {
template <typename Key>
static std::size_t
std::size_t
extract(Key const& key)
{
return key;

View File

@@ -2,212 +2,107 @@
#include <xrpl/beast/utility/instrumentation.h> // IWYU pragma: keep
#include <concepts>
#include <cstdint>
#include <limits>
#include <memory>
#include <type_traits>
#include <utility>
namespace xrpl {
/** Every value of @p Src can be represented by @p Dest.
// safe_cast adds compile-time checks to a static_cast to ensure that
// the destination can hold all values of the source. This is particularly
// handy when the source or destination is an enumeration type.
Given two integral types, the cast is safe when the destination can
hold every possible value of the source.
template <class Src, class Dest>
concept SafeToCast = (std::is_integral_v<Src> && std::is_integral_v<Dest>) &&
(std::is_signed_v<Src> || std::is_unsigned_v<Dest>) &&
(std::is_signed_v<Src> != std::is_signed_v<Dest> ? sizeof(Dest) > sizeof(Src)
: sizeof(Dest) >= sizeof(Src));
Comparing the bounds requires care: we use @c std::cmp_less_equal and
@c std::cmp_greater_equal; the plain relational operators would apply
arithmetic conversions, resulting in incorrect results when comparing
across signedness.
Because @c std::cmp_* requires standard signed or unsigned integer
arguments, which excludes character types and bool, we first widen
all type bounds to the maximum-width integer type while preserving
signedness.
@note Extended integer types, like __int128 on gcc, cannot be safely
widened to a standard integer type, so the concept will reject
them.
*/
template <typename Src, typename Dest>
concept SafeToCast = std::is_integral_v<Src> && std::is_integral_v<Dest> && []() consteval {
using WideSrc = std::conditional_t<std::is_signed_v<Src>, std::intmax_t, std::uintmax_t>;
using WideDest = std::conditional_t<std::is_signed_v<Dest>, std::intmax_t, std::uintmax_t>;
// Note: this guard must be an evaluated branch, and not a
// static_assert. The lambda body is outside the immediate
// context, so a substitution-time failure here would be a
// hard error rather than leaving the concept unsatisfied.
if constexpr (sizeof(Src) > sizeof(WideSrc) || sizeof(Dest) > sizeof(WideDest))
{
return false;
}
else
{
return std::cmp_less_equal(
static_cast<WideDest>(std::numeric_limits<Dest>::min()),
static_cast<WideSrc>(std::numeric_limits<Src>::min())) &&
std::cmp_greater_equal(
static_cast<WideDest>(std::numeric_limits<Dest>::max()),
static_cast<WideSrc>(std::numeric_limits<Src>::max()));
}
}();
/** Compile-time-checked static_cast that rejects non-value preserving casts.
@note There is deliberately no enum-to-enum overload, and no overload
returning the underlying type of an enum. For the latter, use
@c std::to_underlying.
*/
/** @{ */
template <typename Dest, typename Src>
requires(std::is_integral_v<Dest> && std::is_integral_v<Src>)
template <class Dest, class Src>
constexpr Dest
safeCast(Src s) noexcept
requires(std::is_integral_v<Dest> && std::is_integral_v<Src>)
{
static_assert(
SafeToCast<Src, Dest>, "This cast is not value-preserving. Please use unsafeCast instead.");
std::is_signed_v<Dest> || std::is_unsigned_v<Src>, "Cannot cast signed to unsigned");
constexpr unsigned kNotSame = std::is_signed_v<Dest> != std::is_signed_v<Src>;
static_assert(
sizeof(Dest) >= sizeof(Src) + kNotSame,
"Destination is too small to hold all values of source");
return static_cast<Dest>(s);
}
template <typename Dest, typename Src>
requires(std::is_enum_v<Dest> && std::is_integral_v<Src>)
template <class Dest, class Src>
constexpr Dest
safeCast(Src s) noexcept
requires(std::is_enum_v<Dest> && std::is_integral_v<Src>)
{
return static_cast<Dest>(safeCast<std::underlying_type_t<Dest>>(s));
}
template <typename Dest, typename Src>
requires(std::is_integral_v<Dest> && std::is_enum_v<Src>)
template <class Dest, class Src>
constexpr Dest
safeCast(Src s) noexcept
requires(std::is_integral_v<Dest> && std::is_enum_v<Src>)
{
return safeCast<Dest>(std::to_underlying(s));
return safeCast<Dest>(static_cast<std::underlying_type_t<Src>>(s));
}
/** @} */
/** Integral-to-integral cast that is known to be narrowing or sign-erasing.
// unsafe_cast explicitly flags a static_cast as not necessarily able to hold
// all values of the source. It includes a compile-time check so that if
// underlying types become safe, it can be converted to a safe_cast.
This explicitly flags a conversion that can lose information for some
values of the source type, where the call site accepts that loss (or
truncation is the intended behavior).
The compile-time check ensures the cast remains "unsafe": if the types
involved later change such that the conversion becomes inherently
value-preserving, the static assertion fires with instructions to
migrate the call site to @ref safeCast.
If the conversion's safety depends on a runtime precondition rather
than on the types, or varies across instantiations of generic code,
use @ref checkedCast instead.
*/
/** @{ */
template <typename Dest, typename Src>
requires(std::is_integral_v<Dest> && std::is_integral_v<Src>)
template <class Dest, class Src>
constexpr Dest
unsafeCast(Src s) noexcept
requires(std::is_integral_v<Dest> && std::is_integral_v<Src>)
{
static_assert(
!SafeToCast<Src, Dest>, "This cast is value-preserving. Please use safeCast instead.");
!SafeToCast<Src, Dest>,
"Only unsafe if casting signed to unsigned or "
"destination is too small");
return static_cast<Dest>(s);
}
template <typename Dest, typename Src>
requires(std::is_enum_v<Dest> && std::is_integral_v<Src>)
template <class Dest, class Src>
constexpr Dest
unsafeCast(Src s) noexcept
requires(std::is_enum_v<Dest> && std::is_integral_v<Src>)
{
return static_cast<Dest>(unsafeCast<std::underlying_type_t<Dest>>(s));
}
template <typename Dest, typename Src>
requires(std::is_integral_v<Dest> && std::is_enum_v<Src>)
template <class Dest, class Src>
constexpr Dest
unsafeCast(Src s) noexcept
{
return unsafeCast<Dest>(std::to_underlying(s));
}
/** @} */
/** Integral-to-integral cast when the caller has performed a bounds check.
This documents that a runtime precondition or external invariant, which
is not necessarily visible to the compiler, guarantees that the requested
conversion is value-preserving for the values that can actually occur.
This is primarily meant for generic code, where the same expression may
be value-preserving for one instantiation but not for another, making
both @ref safeCast and @ref unsafeCast unusable.
Unlike @ref safeCast and @ref unsafeCast, this imposes no static check
on the type relationship: the caller's claim is about runtime values,
not about types.
*/
/** @{ */
template <typename Dest, typename Src>
requires(std::is_integral_v<Dest> && std::is_integral_v<Src>)
constexpr Dest
checkedCast(Src s) noexcept
{
return static_cast<Dest>(s);
}
template <typename Dest, typename Src>
requires(std::is_enum_v<Dest> && std::is_integral_v<Src>)
constexpr Dest
checkedCast(Src s) noexcept
{
return static_cast<Dest>(checkedCast<std::underlying_type_t<Dest>>(s));
}
template <typename Dest, typename Src>
requires(std::is_integral_v<Dest> && std::is_enum_v<Src>)
constexpr Dest
checkedCast(Src s) noexcept
{
return checkedCast<Dest>(std::to_underlying(s));
return unsafeCast<Dest>(static_cast<std::underlying_type_t<Src>>(s));
}
/** @} */
/** Downcast within a class hierarchy, verified in debug builds.
Performs a static_cast down a hierarchy, but in debug builds verifies
via dynamic_cast that the object's dynamic type actually permits the
downcast. Both build modes execute the same conversion; debug builds
merely add the check.
The pointer form passes null through unchanged, as dynamic_cast does.
@note The check requires @p Src to be polymorphic; in release builds
an invalid downcast is undefined behavior on use, exactly as
with a bare static_cast.
*/
/** @{ */
template <class Dest, class Src>
requires(
std::is_pointer_v<Dest> && std::is_polymorphic_v<Src> &&
std::derived_from<std::remove_pointer_t<Dest>, Src> &&
(std::is_const_v<std::remove_pointer_t<Dest>> || !std::is_const_v<Src>) &&
(std::is_volatile_v<std::remove_pointer_t<Dest>> || !std::is_volatile_v<Src>))
requires std::is_pointer_v<Dest>
inline Dest
safeDowncast(Src* s) noexcept
{
XRPL_ASSERT(
s == nullptr || dynamic_cast<Dest>(s) != nullptr, "xrpl::safeDowncast : valid downcast");
#ifdef NDEBUG
return static_cast<Dest>(s); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
#else
auto* result = dynamic_cast<Dest>(s);
XRPL_ASSERT(result != nullptr, "xrpl::safeDowncast : pointer downcast is valid");
return result;
#endif
}
template <class Dest, class Src>
requires(
std::is_lvalue_reference_v<Dest> && std::is_polymorphic_v<Src> &&
std::derived_from<std::remove_reference_t<Dest>, Src>)
requires std::is_lvalue_reference_v<Dest>
inline Dest
safeDowncast(Src& s) noexcept
{
return *safeDowncast<std::add_pointer_t<std::remove_reference_t<Dest>>>(std::addressof(s));
#ifndef NDEBUG
XRPL_ASSERT(
dynamic_cast<std::add_pointer_t<std::remove_reference_t<Dest>>>(&s) != nullptr,
"xrpl::safeDowncast : reference downcast is valid");
#endif
return static_cast<Dest>(s); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
}
/** @} */
} // namespace xrpl

View File

@@ -4,215 +4,88 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <boost/predef/architecture.h>
#include <atomic>
#include <concepts>
#include <limits>
#include <type_traits>
#if BOOST_ARCH_X86
#ifndef __aarch64__
#include <immintrin.h>
#endif
namespace xrpl {
/** An unsigned integral type suitable for use as a spinlock.
The type must be always lock-free when wrapped in std::atomic, so
that lock operations cannot themselves take a (library-level) lock.
*/
template <typename T>
concept SpinlockValueType = std::is_unsigned_v<T> && std::atomic<T>::is_always_lock_free;
/** A spinlock value type that additionally supports the atomic bitwise
operations required to pack multiple locks into a single integer.
*/
template <typename T>
concept PackedSpinlockValueType = SpinlockValueType<T> && requires(std::atomic<T>& a, T v) {
{ a.fetch_or(v) } -> std::same_as<T>;
{ a.fetch_and(v) } -> std::same_as<T>;
};
namespace detail {
/** Inform the processor that we are in a tight spin-wait loop.
Spinlocks caught in tight loops can result in the processor's pipeline
filling up with comparison operations, resulting in a misprediction at
the time the lock is finally acquired, necessitating pipeline flushing
which is ridiculously expensive and results in very high latency.
This function instructs the processor to "pause" for some architecture
specific amount of time, to prevent this.
/**
* Inform the processor that we are in a tight spin-wait loop.
*
* Spinlocks caught in tight loops can result in the processor's pipeline
* filling up with comparison operations, resulting in a misprediction at
* the time the lock is finally acquired, necessitating pipeline flushing
* which is ridiculously expensive and results in very high latency.
*
* This function instructs the processor to "pause" for some architecture
* specific amount of time, to prevent this.
*/
inline void
spinPause() noexcept
{
#if BOOST_ARCH_X86
_mm_pause();
#elif BOOST_ARCH_ARM
asm volatile("yield" ::: "memory");
#ifdef __aarch64__
asm volatile("yield");
#else
#error No implementation available for spinPause to use
_mm_pause();
#endif
}
} // namespace detail
//------------------------------------------------------------------------------
/** Attempt to acquire a spinlock without blocking.
@note This interface is primarily intended for one-shot attempts to
acquire the lock. Avoid calling this function directly from a
loop and use @ref spinLock instead.
@tparam T An unsigned integral type.
@param lock The atomic variable used as the lock.
@return true if the lock was acquired, false if it was already held.
/** @{ */
/**
* Classes to handle arrays of spinlocks packed into a single atomic integer:
*
* Packed spinlocks allow for tremendously space-efficient lock-sharding
* but they come at a cost.
*
* First, the implementation is necessarily low-level and uses advanced
* features like memory ordering and highly platform-specific tricks to
* maximize performance. This imposes a significant and ongoing cost to
* developers.
*
* Second, and perhaps most important, is that the packing of multiple
* locks into a single integer which, albeit space-efficient, also has
* performance implications stemming from data dependencies, increased
* cache-coherency traffic between processors and heavier loads on the
* processor's load/store units.
*
* To be sure, these locks can have advantages but they are definitely
* not general purpose locks and should not be thought of or used that
* way. The use cases for them are likely few and far between; without
* a compelling reason to use them, backed by profiling data, it might
* be best to use one of the standard locking primitives instead. Note
* that in most common platforms, `std::mutex` is so heavily optimized
* that it can, usually, outperform spinlocks.
*
* @tparam T An unsigned integral type (e.g. std::uint16_t)
*/
template <SpinlockValueType T>
[[nodiscard]] bool
spinTryLock(std::atomic<T>& lock) noexcept
{
// A compare-exchange is required here, not an unconditional exchange:
// a failed attempt must not modify the lock word, in case the atomic
// is shared with PackedSpinlock).
T expected = 0;
return lock.compare_exchange_strong(
expected,
std::numeric_limits<T>::max(),
std::memory_order::acquire,
std::memory_order::relaxed);
;
}
/** Acquire a spinlock, blocking until available.
Uses a TTAS (test-and-test-and-set) pattern, so waiters share the cache
line read-only, helping to avoid unnecessary coherency traffic.
@tparam T An unsigned integral type.
@param lock The atomic variable used as the lock.
/**
* A class that grabs a single packed spinlock from an atomic integer.
*
* This class meets the requirements of Lockable:
* https://en.cppreference.com/w/cpp/named_req/Lockable
*/
template <SpinlockValueType T>
void
spinLock(std::atomic<T>& lock) noexcept
{
do
{
// Relaxed ordering is sufficient for the spin: this load is only
// a filter. The acquire on the successful exchange in spinTryLock
// is what synchronizes the critical section.
while (lock.load(std::memory_order::relaxed) != 0)
detail::spinPause();
} while (!spinTryLock(lock));
}
/** Release a spinlock.
@tparam T An unsigned integral type.
@param lock The atomic variable used as the lock.
*/
template <SpinlockValueType T>
void
spinUnlock(std::atomic<T>& lock) noexcept
{
lock.store(0, std::memory_order::release);
}
//------------------------------------------------------------------------------
/** A Lockable interface to a spinlock implemented on top of an atomic.
@tparam T An unsigned integral type.
@note Using `PackedSpinlock` and `Spinlock` against the same underlying
atomic integer is possible but can result in `Spinlock` not being
able to acquire the lock during periods of high contention due to
the way the two locks operate: `Spinlock` spins and tries to grab
all the bits at once, whereas any given `PackedSpinlock` instance
only tries to grab one bit at a time. Caveat emptor.
This class meets the requirements of Lockable:
https://en.cppreference.com/w/cpp/named_req/Lockable
*/
template <SpinlockValueType T>
class Spinlock
{
std::atomic<T>& lock_;
public:
Spinlock(Spinlock const&) = delete;
Spinlock&
operator=(Spinlock const&) = delete;
/** Construct a spinlock handle.
@param lock The atomic integer to spin against.
@note For performance reasons, you should strive to have `lock` be
on a cacheline by itself.
*/
explicit Spinlock(std::atomic<T>& lock) noexcept : lock_(lock)
{
}
[[nodiscard]] bool
try_lock() noexcept // NOLINT(readability-identifier-naming)
{
return spinTryLock(lock_);
}
void
lock() noexcept
{
spinLock(lock_);
}
void
unlock() noexcept
{
spinUnlock(lock_);
}
};
//------------------------------------------------------------------------------
/** A Lockable interface to a packed spinlock implemented on top of an atomic.
Packed spinlocks offer tremendous space-efficient lock-sharding but
they come at a cost.
First, the implementation is necessarily low-level and uses advanced
features like memory ordering and highly platform-specific tricks to
maximize performance. This imposes a significant and ongoing cost to
developers.
Second, and perhaps most important, is that the packing of multiple
locks into a single integer which, albeit space-efficient, also has
performance implications stemming from data dependencies, increased
cache-coherency traffic between processors and heavier loads on the
processor's load/store units.
To be sure, these locks can have advantages but they are definitely
not general purpose locks and should not be thought of or used that
way. The use cases for them are likely few and far between; without
a compelling reason to use them, backed by profiling data, it might
be best to use one of the standard locking primitives instead. Note
that in most common platforms, `std::mutex` is so heavily optimized
that it can, usually, outperform spinlocks.
@tparam T An unsigned integral type (e.g. std::uint16_t)
This class meets the requirements of Lockable:
https://en.cppreference.com/w/cpp/named_req/Lockable
*/
template <PackedSpinlockValueType T>
template <class T>
class PackedSpinlock
{
// clang-format off
static_assert(std::is_unsigned_v<T>);
static_assert(std::atomic<T>::is_always_lock_free);
static_assert(
std::is_same_v<decltype(std::declval<std::atomic<T>&>().fetch_or(0)), T> &&
std::is_same_v<decltype(std::declval<std::atomic<T>&>().fetch_and(0)), T>,
"std::atomic<T>::fetch_and(T) and std::atomic<T>::fetch_and(T) are required by packed_spinlock");
// clang-format on
private:
std::atomic<T>& bits_;
T const mask_;
@@ -221,49 +94,120 @@ public:
PackedSpinlock&
operator=(PackedSpinlock const&) = delete;
/** Construct a packed spinlock handle for a single bit.
@param lock The atomic integer inside which the spinlock is packed.
@param index The index of the spinlock this object acquires.
@note For performance reasons, you should strive to have `lock` be
on a cacheline by itself.
/**
* A single spinlock packed inside the specified atomic
*
* @param lock The atomic integer inside which the spinlock is packed.
* @param index The index of the spinlock this object acquires.
*
* @note For performance reasons, you should strive to have `lock` be
* on a cacheline by itself.
*/
PackedSpinlock(std::atomic<T>& lock, int index) noexcept
: bits_(lock), mask_([index]() {
XRPL_ASSERT(
index >= 0 && index < std::numeric_limits<T>::digits,
"xrpl::PackedSpinlock::PackedSpinlock : valid index");
return static_cast<T>(T{1} << index);
}())
PackedSpinlock(std::atomic<T>& lock, int index) : bits_(lock), mask_(static_cast<T>(1) << index)
{
XRPL_ASSERT(
index >= 0 && (mask_ != 0),
"xrpl::PackedSpinlock::PackedSpinlock : valid index and mask");
}
[[nodiscard]] bool
try_lock() // NOLINT(readability-identifier-naming)
{
return (bits_.fetch_or(mask_, std::memory_order_acquire) & mask_) == 0;
}
void
lock()
{
while (!try_lock())
{
// The use of relaxed memory ordering here is intentional and
// serves to help reduce cache coherency traffic during times
// of contention by avoiding writes that would definitely not
// result in the lock being acquired.
while ((bits_.load(std::memory_order_relaxed) & mask_) != 0)
detail::spinPause();
}
}
void
unlock()
{
bits_.fetch_and(~mask_, std::memory_order_release);
}
};
/**
* A spinlock implemented on top of an atomic integer.
*
* @note Using `packed_spinlock` and `spinlock` against the same underlying
* atomic integer can result in `spinlock` not being able to actually
* acquire the lock during periods of high contention, because of how
* the two locks operate: `spinlock` will spin trying to grab all the
* bits at once, whereas any given `packed_spinlock` will only try to
* grab one bit at a time. Caveat emptor.
*
* This class meets the requirements of Lockable:
* https://en.cppreference.com/w/cpp/named_req/Lockable
*/
template <class T>
class Spinlock
{
static_assert(std::is_unsigned_v<T>);
static_assert(std::atomic<T>::is_always_lock_free);
private:
std::atomic<T>& lock_;
public:
Spinlock(Spinlock const&) = delete;
Spinlock&
operator=(Spinlock const&) = delete;
/**
* Grabs the
*
* @param lock The atomic integer to spin against.
*
* @note For performance reasons, you should strive to have `lock` be
* on a cacheline by itself.
*/
Spinlock(std::atomic<T>& lock) : lock_(lock)
{
}
[[nodiscard]] bool
try_lock() noexcept // NOLINT(readability-identifier-naming)
try_lock() // NOLINT(readability-identifier-naming)
{
return (bits_.fetch_or(mask_, std::memory_order::acquire) & mask_) == 0;
T expected = 0;
return lock_.compare_exchange_weak(
expected,
std::numeric_limits<T>::max(),
std::memory_order_acquire,
std::memory_order_relaxed);
}
void
lock() noexcept
lock()
{
do
while (!try_lock())
{
// The use of relaxed memory ordering here is intentional and
// serves to help reduce cache coherency traffic during times
// of contention by avoiding writes that are unlikely to grab
// the requested lock.
while ((bits_.load(std::memory_order::relaxed) & mask_) != 0)
// of contention by avoiding writes that would definitely not
// result in the lock being acquired.
while (lock_.load(std::memory_order_relaxed) != 0)
detail::spinPause();
} while (!try_lock());
}
}
void
unlock() noexcept
unlock()
{
bits_.fetch_and(~mask_, std::memory_order::release);
lock_.store(0, std::memory_order_release);
}
};
/** @} */
} // namespace xrpl

View File

@@ -0,0 +1,47 @@
#pragma once
#include <cstdlib>
#include <string>
#include <type_traits>
#include <typeinfo>
#ifndef _MSC_VER
#include <cxxabi.h>
#endif
namespace beast {
template <typename T>
std::string
typeName()
{
using TR = std::remove_reference_t<T>;
std::string name = typeid(TR).name();
#ifndef _MSC_VER
if (auto s = abi::__cxa_demangle(name.c_str(), nullptr, nullptr, nullptr))
{
name = s;
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
std::free(s);
}
#endif
if (std::is_const_v<TR>)
name += " const";
if (std::is_volatile_v<TR>)
name += " volatile";
if (std::is_lvalue_reference_v<T>)
{
name += "&";
}
else if (std::is_rvalue_reference_v<T>)
{
name += "&&";
}
return name;
}
} // namespace beast

View File

@@ -20,7 +20,7 @@ namespace beast::unit_test {
namespace detail {
template <class String>
static std::string
std::string
makeReason(String const& reason, char const* file, int line)
{
std::string s(reason);

View File

@@ -2,24 +2,26 @@
#pragma once
#include <compare>
#include <concepts>
namespace beast {
/**
* Zero allows classes to offer efficient comparisons to zero.
*
* Zero is a struct to allow classes to efficiently compare with zero without
* requiring an rvalue construction.
*
* It's often the case that we have classes which combine a number and a unit.
* In such cases, comparisons like t > 0 or t != 0 make sense, but comparisons
* like t > 1 or t != 1 do not. Comparing against kZero expresses exactly that,
* without constructing a T.
* like t > 1 or t != 1 do not.
*
* A type T participates if either `t.signum()` or an unqualified `signum(t)`
* found by argument-dependent lookup returns an integer that is negative,
* zero, or positive according to the sign of t. Both `t == kZero` and
* `kZero == t` work, as do all six relational operators in either order.
* The class Zero allows such comparisons to be easily made.
*
* The comparing class T either needs to have a method called signum() which
* returns a positive number, 0, or a negative; or there needs to be a signum
* function which resolves in the namespace which takes an instance of T and
* returns a positive, zero or negative number.
*/
struct Zero
{
explicit Zero() = default;
@@ -28,44 +30,115 @@ struct Zero
inline constexpr Zero kZero{};
/**
* Default implementation of signum: call the member function.
* Default implementation of signum calls the method on the class.
*/
template <class T>
requires requires(T const& t) {
{ t.signum() } -> std::integral;
}
[[nodiscard]] constexpr auto
signum(T const& t) noexcept(noexcept(t.signum()))
template <typename T>
auto
signum(T const& t)
{
return t.signum();
}
namespace detail {
namespace detail::zero_helper {
/**
* A type with a usable signum: either the member-based default above, or a
* `signum(t)` overload in T's own namespace, found by ADL. A user overload
* that is a better match than the template wins, as usual.
*/
// For argument dependent lookup to function properly, calls to signum must
// be made from a namespace that does not include overloads of the function..
template <class T>
concept HasSignum = requires(T const& t) {
{ signum(t) } -> std::integral;
};
} // namespace detail
template <detail::HasSignum T>
[[nodiscard]] constexpr bool
operator==(T const& t, Zero) noexcept(noexcept(signum(t)))
auto
callSignum(T const& t)
{
return signum(t) == 0;
return signum(t);
}
template <detail::HasSignum T>
[[nodiscard]] constexpr std::strong_ordering
operator<=>(T const& t, Zero) noexcept(noexcept(signum(t)))
} // namespace detail::zero_helper
// Handle operators where T is on the left side using signum.
template <typename T>
bool
operator==(T const& t, Zero)
{
return signum(t) <=> 0;
return detail::zero_helper::callSignum(t) == 0;
}
template <typename T>
bool
operator!=(T const& t, Zero)
{
return detail::zero_helper::callSignum(t) != 0;
}
template <typename T>
bool
operator<(T const& t, Zero)
{
return detail::zero_helper::callSignum(t) < 0;
}
template <typename T>
bool
operator>(T const& t, Zero)
{
return detail::zero_helper::callSignum(t) > 0;
}
template <typename T>
bool
operator>=(T const& t, Zero)
{
return detail::zero_helper::callSignum(t) >= 0;
}
template <typename T>
bool
operator<=(T const& t, Zero)
{
return detail::zero_helper::callSignum(t) <= 0;
}
// Handle operators where T is on the right side by
// reversing the operation, so that T is on the left side.
template <typename T>
bool
operator==(Zero, T const& t)
{
return t == kZero;
}
template <typename T>
bool
operator!=(Zero, T const& t)
{
return t != kZero;
}
template <typename T>
bool
operator<(Zero, T const& t)
{
return t > kZero;
}
template <typename T>
bool
operator>(Zero, T const& t)
{
return t < kZero;
}
template <typename T>
bool
operator>=(Zero, T const& t)
{
return t <= kZero;
}
template <typename T>
bool
operator<=(Zero, T const& t)
{
return t >= kZero;
}
} // namespace beast

View File

@@ -1,7 +1,7 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/enum_bitops.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/OwnerCounts.h>
#include <xrpl/ledger/ReadView.h>
@@ -22,39 +22,80 @@
namespace xrpl {
enum class ApplyFlags : std::uint32_t {
None = 0x00,
// Bitwise flag enum with existing operator overloads
// NOLINTNEXTLINE(cppcoreguidelines-use-enum-class)
enum ApplyFlags : std::uint32_t {
TapNone = 0x00,
// This is a local transaction with the
// fail_hard flag set.
FailHard = 0x10,
TapFailHard = 0x10,
// This is not the transaction's last pass
// Transaction can be retried, soft failures allowed
Retry = 0x20,
TapRetry = 0x20,
// Transaction came from a privileged source
Unlimited = 0x400,
TapUnlimited = 0x400,
// Transaction is executing as part of a batch
Batch = 0x800,
TapBatch = 0x800,
// Transaction shouldn't be applied
// Signatures shouldn't be checked
DryRun = 0x1000,
TapDryRun = 0x1000,
// Transaction is being preflighted as the payload of a
// TransactionProposalCreate. Its signatures are collected on-ledger
// afterward, so signature-presence checks (e.g. Batch signer matching)
// are skipped at proposal-creation time (On-Chain Cosigner spec
// §5.3.1.2).
Proposal = 0x2000
TapProposal = 0x2000
};
template <>
struct enum_bitops::OptIn<ApplyFlags> : std::true_type
constexpr ApplyFlags
operator|(ApplyFlags const& lhs, ApplyFlags const& rhs)
{
};
return safeCast<ApplyFlags>(
safeCast<std::underlying_type_t<ApplyFlags>>(lhs) |
safeCast<std::underlying_type_t<ApplyFlags>>(rhs));
}
static_assert((TapFailHard | TapRetry) == safeCast<ApplyFlags>(0x30u), "ApplyFlags operator |");
static_assert((TapRetry | TapFailHard) == safeCast<ApplyFlags>(0x30u), "ApplyFlags operator |");
constexpr ApplyFlags
operator&(ApplyFlags const& lhs, ApplyFlags const& rhs)
{
return safeCast<ApplyFlags>(
safeCast<std::underlying_type_t<ApplyFlags>>(lhs) &
safeCast<std::underlying_type_t<ApplyFlags>>(rhs));
}
static_assert((TapFailHard & TapRetry) == TapNone, "ApplyFlags operator &");
static_assert((TapRetry & TapFailHard) == TapNone, "ApplyFlags operator &");
constexpr ApplyFlags
operator~(ApplyFlags const& flags)
{
return safeCast<ApplyFlags>(~safeCast<std::underlying_type_t<ApplyFlags>>(flags));
}
static_assert(~TapRetry == safeCast<ApplyFlags>(0xFFFFFFDFu), "ApplyFlags operator ~");
inline ApplyFlags
operator|=(ApplyFlags& lhs, ApplyFlags const& rhs)
{
lhs = lhs | rhs;
return lhs;
}
inline ApplyFlags
operator&=(ApplyFlags& lhs, ApplyFlags const& rhs)
{
lhs = lhs & rhs;
return lhs;
}
//------------------------------------------------------------------------------

View File

@@ -1,11 +1,17 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/SLEBase.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
#include <map>
#include <set>
namespace xrpl {
@@ -25,6 +31,52 @@ public:
: Base(keylet::amendments(), view, j)
{
}
/**
* Returns the set of amendments this entry reports as enabled.
*
* @return the set of enabled amendments.
*/
[[nodiscard]] std::set<UInt256>
enabledAmendments() const
{
std::set<UInt256> amendments;
if (this->exists() && (*this)->isFieldPresent(sfAmendments))
{
auto const& v = (*this)->getFieldV256(sfAmendments);
amendments.insert_range(v);
}
return amendments;
}
/**
* Returns a map of amendments that have achieved majority, to the time
* majority was reached.
*
* @return a map of amendment to the time majority was reached.
*/
[[nodiscard]] std::map<UInt256, NetClock::time_point>
majorityAmendments() const
{
std::map<UInt256, NetClock::time_point> ret;
if (this->exists() && (*this)->isFieldPresent(sfMajorities))
{
using TimePoint = NetClock::time_point;
using Duration = TimePoint::duration;
auto const majorities = (*this)->getFieldArray(sfMajorities);
for (auto const& m : majorities)
{
ret[m.getFieldH256(sfAmendment)] = TimePoint(Duration(m.getFieldU32(sfCloseTime)));
}
}
return ret;
}
};
using AmendmentsEntryR = AmendmentsEntry<ReadView>;

View File

@@ -1,32 +1,111 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/MPTAmount.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/XRPAmount.h>
namespace xrpl {
/**
* Validate that @p account may lock @p amount of a token for later delivery
* to @p dest.
*
* The lock-side counterpart of escrowUnlockPreclaimHelper: every issuer
* control (locking opt-in, authorization, freeze/lock, transferability,
* spendable balance) that gates locking token value lives here, so any
* transactor that locks funds applies the same rules. The signature is
* view-based rather than PreclaimContext-based so it can also run from
* doApply.
*/
template <ValidIssueType T>
TER
escrowLockPreclaimHelper(
ReadView const& view,
AccountID const& account,
AccountID const& dest,
STAmount const& amount,
beast::Journal j);
template <>
TER
escrowLockPreclaimHelper<Issue>(
ReadView const& view,
AccountID const& account,
AccountID const& dest,
STAmount const& amount,
beast::Journal j);
template <>
TER
escrowLockPreclaimHelper<MPTIssue>(
ReadView const& view,
AccountID const& account,
AccountID const& dest,
STAmount const& amount,
beast::Journal j);
template <ValidIssueType T>
TER
escrowLockApplyHelper(
ApplyView& view,
AccountID const& issuer,
AccountID const& sender,
STAmount const& amount,
beast::Journal journal);
template <>
TER
escrowLockApplyHelper<Issue>(
ApplyView& view,
AccountID const& issuer,
AccountID const& sender,
STAmount const& amount,
beast::Journal journal);
template <>
TER
escrowLockApplyHelper<MPTIssue>(
ApplyView& view,
AccountID const& issuer,
AccountID const& sender,
STAmount const& amount,
beast::Journal journal);
template <ValidIssueType T>
TER
escrowUnlockPreclaimHelper(
ReadView const& view,
AccountID const& account,
STAmount const& amount,
bool checkFreeze = true);
template <>
TER
escrowUnlockPreclaimHelper<Issue>(
ReadView const& view,
AccountID const& account,
STAmount const& amount,
bool checkFreeze);
template <>
TER
escrowUnlockPreclaimHelper<MPTIssue>(
ReadView const& view,
AccountID const& account,
STAmount const& amount,
bool checkFreeze);
//------------------------------------------------------------------------------
template <ValidIssueType T>
TER
escrowUnlockApplyHelper(
@@ -42,7 +121,7 @@ escrowUnlockApplyHelper(
beast::Journal journal);
template <>
inline TER
TER
escrowUnlockApplyHelper<Issue>(
ApplyViewContext ctx,
Rate lockedRate,
@@ -53,137 +132,10 @@ escrowUnlockApplyHelper<Issue>(
AccountID const& sender,
AccountID const& receiver,
bool createAsset,
beast::Journal journal)
{
auto const& issue = amount.get<Issue>();
Keylet const trustLineKey = keylet::trustLine(receiver, issue);
bool const recvLow = issuer > receiver;
bool const senderIssuer = issuer == sender;
bool const receiverIssuer = issuer == receiver;
if (senderIssuer)
return tecINTERNAL; // LCOV_EXCL_LINE
if (receiverIssuer)
return tesSUCCESS;
if (!ctx.view.exists(trustLineKey) && createAsset)
{
// Can the account cover the trust line's reserve?
auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, sleDest);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkReserve(
ctx,
sleDest,
xrpBalance,
*sponsorSle,
{.ownerCountDelta = 1},
journal,
tecNO_LINE_INSUF_RESERVE);
!isTesSuccess(ret))
{
JLOG(journal.trace()) << "Trust line does not exist. "
"Insufficient reserve to create line.";
return ret;
}
Currency const currency = issue.currency;
STAmount initialBalance(issue);
initialBalance.get<Issue>().account = noAccount();
if (TER const ter = trustCreate(
ctx.view, // payment sandbox
recvLow, // is dest low?
issuer, // source
receiver, // destination
trustLineKey.key, // ledger index
sleDest, // Account to add to
false, // authorize account
!sleDest->isFlag(lsfDefaultRipple), //
false, // freeze trust line
false, // deep freeze trust line
initialBalance, // zero initial balance
Issue(currency, receiver), // limit of zero
0, // quality in
0, // quality out
*sponsorSle, // sponsor
journal); // journal
!isTesSuccess(ter))
{
return ter; // LCOV_EXCL_LINE
}
ctx.view.update(sleDest);
}
if (!ctx.view.exists(trustLineKey) && !receiverIssuer)
return tecNO_LINE;
auto const xferRate = transferRate(ctx.view, amount);
// update if issuer rate is less than locked rate
if (xferRate < lockedRate)
lockedRate = xferRate;
// Transfer Rate only applies when:
// 1. Issuer is not involved in the transfer (senderIssuer or
// receiverIssuer)
// 2. The locked rate is different from the parity rate
// NOTE: Transfer fee in escrow works a bit differently from a normal
// payment. In escrow, the fee is deducted from the locked/sending amount,
// whereas in a normal payment, the transfer fee is taken on top of the
// sending amount.
auto finalAmt = amount;
if ((!senderIssuer && !receiverIssuer) && lockedRate != kParityRate)
{
// compute transfer fee, if any
auto const xferFee =
amount.value() - divideRound(amount, lockedRate, amount.get<Issue>(), true);
// compute balance to transfer
finalAmt = amount.value() - xferFee;
}
// validate the line limit if the account submitting txn is not the receiver
// of the funds
if (!createAsset)
{
auto const sleRippleState = ctx.view.peek(trustLineKey);
if (!sleRippleState)
return tecINTERNAL; // LCOV_EXCL_LINE
// if the issuer is the high, then we use the low limit
// otherwise we use the high limit
STAmount const lineLimit =
sleRippleState->getFieldAmount(recvLow ? sfLowLimit : sfHighLimit);
STAmount lineBalance = sleRippleState->getFieldAmount(sfBalance);
// flip the sign of the line balance if the issuer is not high
if (!recvLow)
lineBalance.negate();
// add the final amount to the line balance
lineBalance += finalAmt;
// if the transfer would exceed the line limit return tecLIMIT_EXCEEDED
if (lineLimit < lineBalance)
return tecLIMIT_EXCEEDED;
}
// if destination is not the issuer then transfer funds
if (!receiverIssuer)
{
auto const ter = directSendNoFee(ctx.view, issuer, receiver, finalAmt, true, journal);
if (!isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
}
return tesSUCCESS;
}
beast::Journal journal);
template <>
inline TER
TER
escrowUnlockApplyHelper<MPTIssue>(
ApplyViewContext ctx,
Rate lockedRate,
@@ -194,82 +146,6 @@ escrowUnlockApplyHelper<MPTIssue>(
AccountID const& sender,
AccountID const& receiver,
bool createAsset,
beast::Journal journal)
{
bool const senderIssuer = issuer == sender;
bool const receiverIssuer = issuer == receiver;
auto const mptID = amount.get<MPTIssue>().getMptID();
auto const issuanceKey = keylet::mptokenIssuance(mptID);
auto const mptKeylet = keylet::mptoken(issuanceKey.key, receiver);
if (!ctx.view.exists(mptKeylet) && createAsset && !receiverIssuer)
{
auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, sleDest);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkReserve(
ctx, sleDest, xrpBalance, *sponsorSle, {.ownerCountDelta = 1}, journal);
!isTesSuccess(ret))
return ret;
if (auto const ter = createMPToken(ctx.view, mptID, receiver, *sponsorSle, 0);
!isTesSuccess(ter))
{
return ter; // LCOV_EXCL_LINE
}
// update owner count.
increaseOwnerCount(ctx.view, sleDest, *sponsorSle, 1, journal);
}
if (!ctx.view.exists(mptKeylet) && !receiverIssuer)
return tecNO_PERMISSION;
auto const xferRate = transferRate(ctx.view, amount);
// update if issuer rate is less than locked rate
if (xferRate < lockedRate)
lockedRate = xferRate;
// Transfer Rate only applies when:
// 1. Issuer is not involved in the transfer (senderIssuer or
// receiverIssuer)
// 2. The locked rate is different from the parity rate
// NOTE: Transfer fee in escrow works a bit differently from a normal
// payment. In escrow, the fee is deducted from the locked/sending amount,
// whereas in a normal payment, the transfer fee is taken on top of the
// sending amount.
auto finalAmt = amount;
if ((!senderIssuer && !receiverIssuer) && lockedRate != kParityRate)
{
if (ctx.view.rules().enabled(fixCleanup3_4_0))
{
XRPL_ASSERT(
lockedRate >= kParityRate,
"xrpl::escrowUnlockApplyHelper<MPTIssue> : lockedRate is at least parity");
// MPTs are integral, so round the delivered amount down and
// charge any fractional transfer fee to the escrowed amount.
auto const delivered =
mulRatio(amount.mpt(), kParityRate.value, lockedRate.value, false);
finalAmt = STAmount(amount.asset(), delivered.value());
}
else
{
// compute transfer fee, if any
auto const xferFee =
amount.value() - divideRound(amount, lockedRate, amount.asset(), true);
// compute balance to transfer
finalAmt = amount.value() - xferFee;
}
}
return unlockEscrowMPT(
ctx.view,
sender,
receiver,
finalAmt,
ctx.view.rules().enabled(fixTokenEscrowV1) ? amount : finalAmt,
journal);
}
beast::Journal journal);
} // namespace xrpl

View File

@@ -3,7 +3,13 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
@@ -13,17 +19,65 @@
namespace xrpl {
/**
* Close a payment channel and return its remaining funds to the channel owner.
* Validate the token amount of a PaymentChannelCreate or PaymentChannelFund
* transaction during preflight.
*
* @param slep The SLE for the PayChannel object to close.
* @param view The apply view in which ledger state modifications are made.
* @param key The ledger key identifying the PayChannel entry.
* @param j Journal used for fatal-level diagnostic messages.
* @return tesSUCCESS on success; tefBAD_LEDGER if a directory removal
* fails; tefINTERNAL if the source account SLE cannot be found.
* @param rules The current ledger rules used to check amendment status.
* @param amount The channel or funding amount from the transaction.
* @return tesSUCCESS if the amount is valid; temBAD_AMOUNT, temBAD_CURRENCY,
* or temDISABLED otherwise.
*/
template <ValidIssueType T>
NotTEC
payChanAmountPreflightHelper(Rules const& rules, STAmount const& amount);
template <>
NotTEC
payChanAmountPreflightHelper<Issue>(Rules const&, STAmount const& amount);
template <>
NotTEC
payChanAmountPreflightHelper<MPTIssue>(Rules const& rules, STAmount const& amount);
/**
* Check that locking @p amount debits the source's trust line by exactly
* @p amount.
*
* @param view The ledger view holding the source's trust line.
* @param account The channel source whose balance is debited.
* @param amount The amount to lock in the channel.
* @param j Journal passed to the balance lookup.
* @return tecPRECISION_LOSS if @p amount is an IOU and the source's balance
* less @p amount is not exactly representable; tesSUCCESS otherwise,
* including for XRP and MPT amounts.
*/
TER
closeChannel(SLE::Ref slep, ApplyView& view, UInt256 const& key, beast::Journal j);
payChanLockPrecisionHelper(
ReadView const& view,
AccountID const& account,
STAmount const& amount,
beast::Journal j);
/**
* Close a payment channel and return its remaining funds to the channel owner.
*
* @param slep The SLE for the PayChannel object to close.
* @param ctx The apply view context (view and transaction) in which ledger
* state modifications are made.
* @param key The ledger key identifying the PayChannel entry.
* @param txAccount The account submitting the transaction that closes the
* channel.
* @param j Journal used for fatal-level diagnostic messages.
* @return tesSUCCESS on success; tefBAD_LEDGER if a directory removal
* fails; tefINTERNAL if the source account SLE cannot be found.
*/
TER
closeChannel(
SLE::Ref slep,
ApplyViewContext ctx,
UInt256 const& key,
AccountID const& txAccount,
beast::Journal j);
/**
* Add two uint32_t values with saturation at UINT32_MAX.

View File

@@ -101,6 +101,22 @@ operator<<(std::ostream& os, AccountID const& x)
return os;
}
/**
* Initialize the global cache used to map AccountID to base58 conversions.
*
* The cache is optional and need not be initialized. But because conversion
* is expensive (it requires a SHA-256 operation) in most cases the overhead
* of the cache is worth the benefit.
*
* @param count The number of entries the cache should accommodate. Zero will
* disable the cache, releasing any memory associated with it.
*
* @note The function will only initialize the cache the first time it is
* invoked. Subsequent invocations do nothing.
*/
void
initAccountIdCache(std::size_t count);
} // namespace xrpl
//------------------------------------------------------------------------------

View File

@@ -1,10 +1,10 @@
#pragma once
#include <xrpl/basics/contract.h>
#include <xrpl/beast/type_name.h>
#include <xrpl/protocol/SOTemplate.h>
#include <boost/container/flat_map.hpp>
#include <boost/core/type_name.hpp>
#include <algorithm>
#include <cstddef>
@@ -84,7 +84,7 @@ public:
* Derived classes will load the object with all the known formats.
*/
private:
KnownFormats() : name_(boost::core::type_name<Derived>())
KnownFormats() : name_(beast::typeName<Derived>())
{
}

View File

@@ -1,28 +0,0 @@
#pragma once
namespace xrpl {
/**
* @brief Enumeration of ledger shortcuts for specifying which ledger to use.
*
* These shortcuts provide a convenient way to reference commonly used ledgers
* without needing to specify their exact hash or sequence number.
*/
enum class LedgerShortcut {
/**
* The current working ledger (open, not yet closed)
*/
Current,
/**
* The most recently closed ledger (may not be validated)
*/
Closed,
/**
* The most recently validated ledger
*/
Validated
};
} // namespace xrpl

View File

@@ -2,11 +2,17 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/HashPrefix.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/XRPAmount.h>
namespace xrpl {
/**
* Serialize the PaymentChannelClaim authorization message:
* HashPrefix::PaymentChannelClaim, the channel key and the drops as a
* 64-bit integer.
*/
inline void
serializePayChanAuthorization(Serializer& msg, UInt256 const& key, XRPAmount const& amt)
{
@@ -15,4 +21,23 @@ serializePayChanAuthorization(Serializer& msg, UInt256 const& key, XRPAmount con
msg.add64(amt.drops());
}
/**
* Serialize the PaymentChannelClaim authorization message for any asset: a
* native amount uses the XRP layout; a token amount follows
* HashPrefix::PaymentChannelClaim and the channel key with STAmount::add, the
* Amount field value without its field header.
*/
inline void
serializePayChanAuthorization(Serializer& msg, UInt256 const& key, STAmount const& amt)
{
if (amt.native())
{
serializePayChanAuthorization(msg, key, amt.xrp());
return;
}
msg.add32(HashPrefix::PaymentChannelClaim);
msg.addBitString(key);
amt.add(msg);
}
} // namespace xrpl

View File

@@ -780,6 +780,39 @@ canAdd(STAmount const& amt1, STAmount const& amt2);
bool
canSubtract(STAmount const& amt1, STAmount const& amt2);
/**
* Determine whether `amt1 + amt2` equals the mathematical sum.
*
* XRP and MPT sums are integer additions, so this is canAdd(). An IOU sum is
* rounded to the mantissa width and is exact only if subtracting each
* operand from it gives back the other.
*
* @param amt1 The first addend.
* @param amt2 The second addend.
* @return true if the sum is exact; false if it rounds, exceeds the largest
* amount the asset can represent, or the amounts are not comparable.
* Never throws.
*/
bool
isExactSum(STAmount const& amt1, STAmount const& amt2);
/**
* Determine whether `amt1 - amt2` equals the mathematical difference.
*
* XRP and MPT differences are integer subtractions, so this is canSubtract().
* An IOU difference is rounded to the mantissa width and is exact only if
* adding the subtrahend back gives the minuend and subtracting the difference
* from the minuend gives back the subtrahend.
*
* @param amt1 The minuend.
* @param amt2 The subtrahend.
* @return true if the difference is exact; false if it rounds, underflows,
* exceeds the largest amount the asset can represent, or the amounts are
* not comparable. Never throws.
*/
bool
isExactDifference(STAmount const& amt1, STAmount const& amt2);
/**
* Get the scale of a Number for a given asset.
*

View File

@@ -2,7 +2,6 @@
#include <xrpl/basics/CountedObject.h>
#include <xrpl/basics/UnorderedContainers.h>
#include <xrpl/basics/enum_bitops.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/AccountID.h>
@@ -16,7 +15,6 @@
#include <cstdint>
#include <memory>
#include <optional>
#include <type_traits>
#include <utility>
#include <vector>
@@ -24,8 +22,18 @@ namespace xrpl {
class STPathElement final : public CountedObject<STPathElement>
{
unsigned int type_;
AccountID accountID_;
PathAsset assetID_;
AccountID issuerID_;
bool isOffer_;
std::size_t hashValue_;
public:
enum class Type : std::uint8_t {
// Bitwise values (typeCurrency | typeMPT)
// NOLINTNEXTLINE(cppcoreguidelines-use-enum-class)
enum Type {
TypeNone = 0x00,
TypeAccount = 0x01, // Rippling through an account (vs taking an offer).
TypeCurrency = 0x10, // Currency follows.
@@ -37,8 +45,6 @@ public:
// Combination of all types.
};
using enum Type;
STPathElement();
STPathElement(STPathElement const&) = default;
STPathElement&
@@ -56,42 +62,42 @@ public:
bool forceAsset = false);
STPathElement(
Type type,
unsigned int uType,
AccountID const& account,
PathAsset const& asset,
AccountID const& issuer);
[[nodiscard]] Type
getNodeType() const noexcept;
[[nodiscard]] std::uint32_t
getNodeType() const;
[[nodiscard]] bool
isOffer() const noexcept;
isOffer() const;
[[nodiscard]] bool
isAccount() const noexcept;
isAccount() const;
[[nodiscard]] bool
hasIssuer() const noexcept;
hasIssuer() const;
[[nodiscard]] bool
hasCurrency() const noexcept;
hasCurrency() const;
[[nodiscard]] bool
hasMPT() const noexcept;
hasMPT() const;
[[nodiscard]] bool
hasAsset() const noexcept;
hasAsset() const;
[[nodiscard]] bool
isNone() const noexcept;
isNone() const;
// Nodes are either an account ID or a offer prefix. Offer prefixs denote a
// class of offers.
[[nodiscard]] AccountID const&
getAccountID() const noexcept;
getAccountID() const;
[[nodiscard]] PathAsset const&
getPathAsset() const noexcept;
getPathAsset() const;
[[nodiscard]] Currency const&
getCurrency() const;
@@ -100,35 +106,17 @@ public:
getMPTID() const;
[[nodiscard]] AccountID const&
getIssuerID() const noexcept;
getIssuerID() const;
[[nodiscard]] bool
isType(Type pe) const noexcept;
isType(Type const& pe) const;
friend bool
operator==(STPathElement const& lhs, STPathElement const& rhs) noexcept
{
return lhs.isType(TypeAccount) == rhs.isType(TypeAccount) &&
lhs.hashValue_ == rhs.hashValue_ && lhs.accountID_ == rhs.accountID_ &&
lhs.assetID_ == rhs.assetID_ && lhs.issuerID_ == rhs.issuerID_;
}
bool
operator==(STPathElement const& t) const;
private:
static std::size_t
getHash(STPathElement const& element);
Type type_;
AccountID accountID_;
PathAsset assetID_;
AccountID issuerID_;
bool isOffer_;
std::size_t hashValue_;
};
template <>
struct enum_bitops::OptIn<STPathElement::Type> : std::true_type
{
};
template <class Hasher>
@@ -136,7 +124,7 @@ void
hash_append(Hasher& h, STPathElement const& e) noexcept
{
using beast::hash_append;
hash_append(h, e.isType(STPathElement::TypeAccount));
hash_append(h, (e.getNodeType() & STPathElement::TypeAccount) != 0u);
hash_append(h, e.getAccountID());
hash_append(h, e.getPathAsset());
hash_append(h, e.getIssuerID());
@@ -416,11 +404,11 @@ inline STPathElement::STPathElement(
}
inline STPathElement::STPathElement(
Type type,
unsigned int uType,
AccountID const& account,
PathAsset const& asset,
AccountID const& issuer)
: type_(type)
: type_(uType)
, accountID_(account)
, assetID_(asset)
, issuerID_(issuer)
@@ -435,56 +423,56 @@ inline STPathElement::STPathElement(
hashValue_ = getHash(*this);
}
inline STPathElement::Type
STPathElement::getNodeType() const noexcept
inline std::uint32_t
STPathElement::getNodeType() const
{
return type_;
}
inline bool
STPathElement::isOffer() const noexcept
STPathElement::isOffer() const
{
return isOffer_;
}
inline bool
STPathElement::isAccount() const noexcept
STPathElement::isAccount() const
{
return !isOffer();
}
inline bool
STPathElement::isType(Type pe) const noexcept
STPathElement::isType(Type const& pe) const
{
return (type_ & pe) != STPathElement::TypeNone;
return (type_ & pe) != 0u;
}
inline bool
STPathElement::hasIssuer() const noexcept
STPathElement::hasIssuer() const
{
return isType(STPathElement::TypeIssuer);
}
inline bool
STPathElement::hasCurrency() const noexcept
STPathElement::hasCurrency() const
{
return isType(STPathElement::TypeCurrency);
}
inline bool
STPathElement::hasMPT() const noexcept
STPathElement::hasMPT() const
{
return isType(STPathElement::TypeMpt);
}
inline bool
STPathElement::hasAsset() const noexcept
STPathElement::hasAsset() const
{
return isType(STPathElement::TypeAsset);
}
inline bool
STPathElement::isNone() const noexcept
STPathElement::isNone() const
{
return getNodeType() == STPathElement::TypeNone;
}
@@ -492,13 +480,13 @@ STPathElement::isNone() const noexcept
// Nodes are either an account ID or a offer prefix. Offer prefixs denote a
// class of offers.
inline AccountID const&
STPathElement::getAccountID() const noexcept
STPathElement::getAccountID() const
{
return accountID_;
}
inline PathAsset const&
STPathElement::getPathAsset() const noexcept
STPathElement::getPathAsset() const
{
return assetID_;
}
@@ -516,11 +504,18 @@ STPathElement::getMPTID() const
}
inline AccountID const&
STPathElement::getIssuerID() const noexcept
STPathElement::getIssuerID() const
{
return issuerID_;
}
inline bool
STPathElement::operator==(STPathElement const& t) const
{
return (type_ & TypeAccount) == (t.type_ & TypeAccount) && hashValue_ == t.hashValue_ &&
accountID_ == t.accountID_ && assetID_ == t.assetID_ && issuerID_ == t.issuerID_;
}
// ------------ STPath ------------
inline STPath::STPath(std::vector<STPathElement> p) : path_(std::move(p))

View File

@@ -21,6 +21,7 @@ XRPL_FEATURE(LendingProtocolV1_2, Supported::No, VoteBehavior::DefaultN
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(TokenPaychan, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(Sponsor, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(BatchV1_1, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(LendingProtocolV1_1, Supported::Yes, VoteBehavior::DefaultNo)

View File

@@ -376,6 +376,8 @@ LEDGER_ENTRY(ltPAYCHAN, 0x0078, PayChannel, payment_channel, ({
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfDestinationNode, SoeOptional},
{sfTransferRate, SoeOptional},
{sfIssuerNode, SoeOptional},
}))
/** The ledger object which tracks the AMM.

View File

@@ -182,7 +182,7 @@ TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet,
#endif
TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, ({.delegable = Delegation::Delegable}), ({
{sfDestination, SoeRequired},
{sfAmount, SoeRequired},
{sfAmount, SoeRequired, SoeMptSupported},
{sfSettleDelay, SoeRequired},
{sfPublicKey, SoeRequired},
{sfCancelAfter, SoeOptional},
@@ -195,7 +195,7 @@ TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, ({.delegable = Delegatio
#endif
TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund, ({.delegable = Delegation::Delegable}), ({
{sfChannel, SoeRequired},
{sfAmount, SoeRequired},
{sfAmount, SoeRequired, SoeMptSupported},
{sfExpiration, SoeOptional},
}))
@@ -205,8 +205,8 @@ TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund, ({.delegable = Delegation::D
#endif
TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, ({.delegable = Delegation::Delegable}), ({
{sfChannel, SoeRequired},
{sfAmount, SoeOptional},
{sfBalance, SoeOptional},
{sfAmount, SoeOptional, SoeMptSupported},
{sfBalance, SoeOptional, SoeMptSupported},
{sfSignature, SoeOptional},
{sfPublicKey, SoeOptional},
{sfCredentialIDs, SoeOptional},

View File

@@ -392,7 +392,6 @@ JSS(max_ledger); // in/out: LedgerCleaner
JSS(max_queue_size); // out: TxQ
JSS(max_spend_drops); // out: AccountInfo
JSS(max_spend_drops_total); // out: AccountInfo
JSS(maximum);
JSS(mean); // out: get_aggregate_price
JSS(median); // out: get_aggregate_price
JSS(median_fee); // out: TxQ

View File

@@ -287,6 +287,54 @@ public:
{
return this->sle_->isFieldPresent(sfDestinationNode);
}
/**
* @brief Get sfTransferRate (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getTransferRate() const
{
if (hasTransferRate())
return this->sle_->at(sfTransferRate);
return std::nullopt;
}
/**
* @brief Check if sfTransferRate is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasTransferRate() const
{
return this->sle_->isFieldPresent(sfTransferRate);
}
/**
* @brief Get sfIssuerNode (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT64::type::value_type>
getIssuerNode() const
{
if (hasIssuerNode())
return this->sle_->at(sfIssuerNode);
return std::nullopt;
}
/**
* @brief Check if sfIssuerNode is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasIssuerNode() const
{
return this->sle_->isFieldPresent(sfIssuerNode);
}
};
/**
@@ -508,6 +556,28 @@ public:
return *this;
}
/**
* @brief Set sfTransferRate (SoeOptional)
* @return Reference to this builder for method chaining.
*/
PayChannelBuilder&
setTransferRate(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfTransferRate] = value;
return *this;
}
/**
* @brief Set sfIssuerNode (SoeOptional)
* @return Reference to this builder for method chaining.
*/
PayChannelBuilder&
setIssuerNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfIssuerNode] = value;
return *this;
}
/**
* @brief Build and return the completed PayChannel wrapper.
* @param index The ledger entry index.

View File

@@ -60,6 +60,7 @@ public:
/**
* @brief Get sfAmount (SoeOptional)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
@@ -86,6 +87,7 @@ public:
/**
* @brief Get sfBalance (SoeOptional)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
@@ -246,6 +248,7 @@ public:
/**
* @brief Set sfAmount (SoeOptional)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
PaymentChannelClaimBuilder&
@@ -257,6 +260,7 @@ public:
/**
* @brief Set sfBalance (SoeOptional)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
PaymentChannelClaimBuilder&

View File

@@ -60,6 +60,7 @@ public:
/**
* @brief Get sfAmount (SoeRequired)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -207,6 +208,7 @@ public:
/**
* @brief Set sfAmount (SoeRequired)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
PaymentChannelCreateBuilder&

View File

@@ -60,6 +60,7 @@ public:
/**
* @brief Get sfAmount (SoeRequired)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -155,6 +156,7 @@ public:
/**
* @brief Set sfAmount (SoeRequired)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
PaymentChannelFundBuilder&

View File

@@ -9,7 +9,6 @@
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/LedgerHeader.h>
#include <xrpl/protocol/LedgerShortcut.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/TxMeta.h>
#include <xrpl/protocol/TxSearched.h>
@@ -105,21 +104,6 @@ public:
using TxnMetaLedgerType = std::tuple<Blob, Blob, std::uint32_t>;
using MetaTxsList = std::vector<TxnMetaLedgerType>;
using LedgerSequence = uint32_t;
using LedgerHash = UInt256;
using LedgerSpecifier = std::variant<LedgerRange, LedgerShortcut, LedgerSequence, LedgerHash>;
struct AccountTxArgs
{
AccountID account;
std::optional<LedgerSpecifier> ledger;
bool binary = false;
bool forward = false;
uint32_t limit = 0;
std::optional<AccountTxMarker> marker;
std::optional<DelegateFilter> delegate;
};
struct AccountTxResult
{
std::variant<AccountTxs, MetaTxsList> transactions;

View File

@@ -51,8 +51,7 @@ public:
beast::Journal journal = beast::Journal{beast::Journal::getNullSink()})
: ApplyContext(registry, base, std::nullopt, tx, preclaimResult, baseFee, flags, journal)
{
XRPL_ASSERT(
(flags & ApplyFlags::Batch) == ApplyFlags::None, "Batch apply flag should not be set");
XRPL_ASSERT((flags & TapBatch) == 0, "Batch apply flag should not be set");
}
std::reference_wrapper<ServiceRegistry> registry;

View File

@@ -59,11 +59,10 @@ public:
, parentBatchId(parentBatchId)
, j(j)
{
XRPL_ASSERT(
(flags & ApplyFlags::Batch) == ApplyFlags::Batch, "Batch apply flag should be set");
XRPL_ASSERT((flags & TapBatch) == TapBatch, "Batch apply flag should be set");
XRPL_ASSERT_IF(
(flags & ApplyFlags::Proposal) != ApplyFlags::None,
(flags & ApplyFlags::DryRun) != ApplyFlags::None,
(flags & TapProposal) != TapNone,
(flags & TapDryRun) != TapNone,
"xrpl::PreflightContext : proposal preflight implies dry run");
}
@@ -75,11 +74,10 @@ public:
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: registry(registry), tx(tx), rules(std::move(rules)), flags(flags), j(j)
{
XRPL_ASSERT(
(flags & ApplyFlags::Batch) == ApplyFlags::None, "Batch apply flag should not be set");
XRPL_ASSERT((flags & TapBatch) == 0, "Batch apply flag should not be set");
XRPL_ASSERT_IF(
(flags & ApplyFlags::Proposal) != ApplyFlags::None,
(flags & ApplyFlags::DryRun) != ApplyFlags::None,
(flags & TapProposal) != TapNone,
(flags & TapDryRun) != TapNone,
"xrpl::PreflightContext : proposal preflight implies dry run");
}
@@ -118,7 +116,7 @@ public:
, j(j)
{
XRPL_ASSERT(
parentBatchId.has_value() == ((flags & ApplyFlags::Batch) == ApplyFlags::Batch),
parentBatchId.has_value() == ((flags & TapBatch) == TapBatch),
"Parent Batch ID should be set if batch apply flag is set");
}
@@ -131,8 +129,7 @@ public:
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
: PreclaimContext(registry, view, preflightResult, tx, flags, std::nullopt, j)
{
XRPL_ASSERT(
(flags & ApplyFlags::Batch) == ApplyFlags::None, "Batch apply flag should not be set");
XRPL_ASSERT((flags & TapBatch) == 0, "Batch apply flag should not be set");
}
PreclaimContext&

View File

@@ -41,7 +41,7 @@ struct ApplyResult
inline bool
isTecClaimHardFail(TER ter, ApplyFlags flags)
{
return isTecClaim(ter) && ((flags & ApplyFlags::Retry) == ApplyFlags::None);
return isTecClaim(ter) && ((flags & TapRetry) == 0u);
}
/**

View File

@@ -42,6 +42,14 @@ class ValidMPTIssuance
*/
bool referenceHoldingMutated_ = false;
/**
* Flags cleared on an existing MPTokenIssuance, except lsfMPTLocked,
* which tfMPTUnlock clears legitimately. Every other issuance flag is
* fixed at creation or set-once via MPTokenIssuanceSet, so any bit
* accumulated here is a bug. Enforced post-fixCleanup3_5_0.
*/
std::uint32_t issuanceFlagsCleared_ = 0;
/**
* MPTokens and RippleStates deleted during apply. finalize() checks each
* holder's AccountRoot to detect vault pseudo-account holdings deleted

View File

@@ -23,6 +23,9 @@ public:
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);

View File

@@ -23,9 +23,15 @@ public:
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;

View File

@@ -57,6 +57,12 @@ public:
ReadView const& view,
beast::Journal const& j) override;
/**
* Inner transaction types that preflight rejects before LendingProtocolV1_2.
*
* Once the amendment is enabled the list only matters when replaying older
* ledgers. Remove it when LendingProtocolV1_2 is retired.
*/
static constexpr auto kDisabledTxTypes = std::to_array<TxType>({
ttVAULT_CREATE,
ttVAULT_SET,

View File

@@ -5,8 +5,8 @@ Core build tools:
cmake version 4.4.3
/nix/store/q85csxf4s4shx89zif097h1ql4ax1rrz-cmake-4.4.3/bin/cmake
✅ conan
Conan version 2.32.0
/nix/store/921jqsgbilixmr3xchj95si0jl8pi13z-conan-2.32.0/bin/conan
Conan version 2.33.0
/nix/store/rp0zf22rx3iwnhxp4d53ncirn4lzg68j-conan-2.33.0/bin/conan
✅ git
git version 2.55.0
/nix/store/gw7c7m0dwca5lg4152x9lpmxpj171bzw-git-2.55.0/bin/git
@@ -19,17 +19,17 @@ Development tooling:
ccache version 4.13.6
/nix/store/ydr5nlzxp1djb5256y0zzqa87vz55gsk-ccache-4.13.6/bin/ccache
✅ clang
clang version 22.1.8
/nix/store/dakcxgwz4ixkk6ps50b7vn3dvblyzsnf-clang-wrapper-22.1.8/bin/clang
✅ clang-22
clang version 22.1.8
/nix/store/w826sqb98nnym6kzackg829664iqdlxj-clang-22/bin/clang-22
clang version 23.1.0
/nix/store/2c16z6akvv65znb8330sl5qpklaf3194-clang-wrapper-23.1.0/bin/clang
✅ clang-23
clang version 23.1.0
/nix/store/vfd0q2395vyn0bsjsh1b4068icy1x9ph-clang-23/bin/clang-23
✅ clang++
clang version 22.1.8
/nix/store/dakcxgwz4ixkk6ps50b7vn3dvblyzsnf-clang-wrapper-22.1.8/bin/clang++
✅ clang++-22
clang version 22.1.8
/nix/store/wnwyw3yfqrhpxij65bl3pa1x65q2zg3m-clang++-22/bin/clang++-22
clang version 23.1.0
/nix/store/2c16z6akvv65znb8330sl5qpklaf3194-clang-wrapper-23.1.0/bin/clang++
✅ clang++-23
clang version 23.1.0
/nix/store/0i6kfnif2v4sqyv5fzfg8a9n09rw9lkp-clang++-23/bin/clang++-23
✅ ClangBuildAnalyzer
ClangBuildAnalyzer 1.6.0
/nix/store/c7jjnw29ra78jdlzjww2m374izhy05n8-clangbuildanalyzer-1.6.0/bin/ClangBuildAnalyzer
@@ -39,6 +39,9 @@ Development tooling:
✅ file
file-5.48
/nix/store/y44vpziwbzfm8020dzlqqbybyyy7lb6g-file-5.48/bin/file
✅ jq
jq-1.8.2
/nix/store/wm7v59a2lhj1w2k8jz53spcs1iizm9lv-jq-1.8.2-bin/bin/jq
✅ less
less 710 (PCRE2 regular expressions)
/nix/store/3yn3dx5fwg5i0inqqzwnxq7f6lwhpjqa-less-710/bin/less
@@ -64,23 +67,23 @@ Development tooling:
Zip 3.0
/nix/store/dvawr36npnad4xa6dxi2ss0fw44aa82r-zip-3.0/bin/zip
✅ clang-apply-replacements
clang-apply-replacements version 22.1.8
/nix/store/5bij4zn161lagwyndmrr21vmxjg054nv-clang-tools-22.1.8/bin/clang-apply-replacements
✅ clang-apply-replacements-22
clang-apply-replacements version 22.1.8
/nix/store/w4bz034k4l0w719rrmbwnqcgyjbswy81-clang-apply-replacements-22/bin/clang-apply-replacements-22
clang-apply-replacements version 23.1.0
/nix/store/mva6ngy45m9in3m2lsfccj1y0z6n1f5s-clang-tools-23.1.0/bin/clang-apply-replacements
✅ clang-apply-replacements-23
clang-apply-replacements version 23.1.0
/nix/store/872k02mqr21hxwcpfwaqkxljfj60aak0-clang-apply-replacements-23/bin/clang-apply-replacements-23
✅ clang-format
clang-format version 22.1.8
/nix/store/5bij4zn161lagwyndmrr21vmxjg054nv-clang-tools-22.1.8/bin/clang-format
✅ clang-format-22
clang-format version 22.1.8
/nix/store/w7z346l0r8y36b6lc5i7jzj3w0581l2y-clang-format-22/bin/clang-format-22
clang-format version 23.1.0
/nix/store/mva6ngy45m9in3m2lsfccj1y0z6n1f5s-clang-tools-23.1.0/bin/clang-format
✅ clang-format-23
clang-format version 23.1.0
/nix/store/wf30x6lcya44n2y1fagd6q7x7p4gyl6f-clang-format-23/bin/clang-format-23
✅ clang-tidy
LLVM version 22.1.8
/nix/store/5bij4zn161lagwyndmrr21vmxjg054nv-clang-tools-22.1.8/bin/clang-tidy
✅ clang-tidy-22
LLVM version 22.1.8
/nix/store/0lv25qd3ddgjm3ndn70q61jfr6krlkl7-clang-tidy-22/bin/clang-tidy-22
LLVM version 23.1.0
/nix/store/mva6ngy45m9in3m2lsfccj1y0z6n1f5s-clang-tools-23.1.0/bin/clang-tidy
✅ clang-tidy-23
LLVM version 23.1.0
/nix/store/kgx00aass66g02p1s4gqqf65kq39m4ci-clang-tidy-23/bin/clang-tidy-23
✅ dot
dot - graphviz version 15.1.1 (0)
/nix/store/mc99a6bpbk2waym2cnw6ifmxli4ndb4x-graphviz-15.1.1/bin/dot
@@ -107,10 +110,10 @@ Development tooling:
/nix/store/ivh54xypg9qvd3ha9lis0i8p4nkf8n7a-pre-commit-4.6.2/bin/pre-commit
✅ run-clang-tidy
usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
/nix/store/3xss1mm3mh1hzcmg182na6ki1p5rnxf1-run-clang-tidy/bin/run-clang-tidy
✅ run-clang-tidy-22
/nix/store/zjbzr154xan5xg9pmjiv0zkwklyb94wv-run-clang-tidy/bin/run-clang-tidy
✅ run-clang-tidy-23
usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
/nix/store/w68r9z07hcq4fwbfyq3yvc7qxm8aqfbl-run-clang-tidy-22/bin/run-clang-tidy-22
/nix/store/svhfrpgx2j581brsqmkpav9b286v22yy-run-clang-tidy-23/bin/run-clang-tidy-23
Rust toolchain:
✅ cargo
@@ -143,4 +146,4 @@ Rust toolchain:
Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set).
✅ All 45 checked tools are present and runnable.
✅ All 46 checked tools are present and runnable.

View File

@@ -2,173 +2,176 @@ Detected OS: linux (Linux x86_64)
Core build tools:
✅ cmake
cmake version 4.1.2
/nix/store/r9941n32g4wyvggz2703dlplbdq8a6rd-cmake-4.1.2/bin/cmake
cmake version 4.4.3
/nix/store/jcvvpih1046akxcwh9hdiak5q24jqcf0-cmake-4.4.3/bin/cmake
✅ conan
Conan version 2.28.1
/nix/store/lxny9y4jvjdws7hgz1mygvb7hjrpmna5-conan-2.28.1/bin/conan
Conan version 2.32.0
/nix/store/q0g5grbh7zm8gfhhnq5h6fm1rbs9yv3n-conan-2.32.0/bin/conan
✅ git
git version 2.54.0
/nix/store/bcnisk3ydfgv26v2gw3zlky24g00yww2-git-2.54.0/bin/git
git version 2.55.0
/nix/store/lg46w7hrjx7kylsh6645c9l975i8h43b-git-2.55.0/bin/git
✅ python3
Python 3.13.13
/nix/store/60m4rxhg2fldqaak400c0lry96ijrzqn-python3-3.13.13/bin/python3.13
Python 3.14.7
/nix/store/lb41b0anx1f98y9y5s9mdv97gjgsq740-python3-3.14.7/bin/python3.14
Development tooling:
✅ ccache
ccache version 4.13.6
/nix/store/c9wwl7s5i6rsfwvf4v0xbbmzx5m6jgfr-ccache-4.13.6/bin/ccache
/nix/store/p1a1s700dqsw1b3b8d2ba64nqw68zgpy-ccache-4.13.6/bin/ccache
✅ clang
clang version 22.1.7
/nix/store/ff0hrp9r9i3pa5arkdw0sgmzp8d576qi-clang-wrapper-22.1.7/bin/clang
clang version 22.1.8
/nix/store/dggxva67vs7virgsmd6xkwd6p7zsc5pn-clang-wrapper-22.1.8/bin/clang
✅ clang-22
clang version 22.1.7
/nix/store/dagc2rq44gfbr7w7yvvqca3yqpc9gqbq-clang-22/bin/clang-22
clang version 22.1.8
/nix/store/ckz6y10v5dbyswpf9f6hd8kjfv84f920-clang-22/bin/clang-22
✅ clang++
clang version 22.1.7
/nix/store/ff0hrp9r9i3pa5arkdw0sgmzp8d576qi-clang-wrapper-22.1.7/bin/clang++
clang version 22.1.8
/nix/store/dggxva67vs7virgsmd6xkwd6p7zsc5pn-clang-wrapper-22.1.8/bin/clang++
✅ clang++-22
clang version 22.1.7
/nix/store/l5m8clin1npl605wdkd8mr18ggxww3z4-clang++-22/bin/clang++-22
clang version 22.1.8
/nix/store/lsishk8fwbdmlwr5fbwyd3yhhjsm67qr-clang++-22/bin/clang++-22
✅ ClangBuildAnalyzer
ClangBuildAnalyzer 1.6.0
/nix/store/bshlmn8fqw55nsnm581xqlfbahfkykxx-clangbuildanalyzer-1.6.0/bin/ClangBuildAnalyzer
/nix/store/gvx8im1c89vspwb8ixa91lr1dagbi241-clangbuildanalyzer-1.6.0/bin/ClangBuildAnalyzer
✅ curl
curl 8.20.0 (x86_64-pc-linux-gnu) libcurl/8.20.0 OpenSSL/3.6.2 zlib/1.3.2 libssh2/1.11.1 nghttp2/1.69.0 mit-krb5/1.22.1
/nix/store/zbwymrp4lcfjc4kkk0n4779v0kjjz58z-curl-8.20.0-bin/bin/curl
curl 8.22.0 (x86_64-pc-linux-gnu) libcurl/8.22.0 OpenSSL/3.5.8 zlib/1.3.2 libssh2/1.11.1 nghttp2/1.70.0 mit-krb5/1.22.2
/nix/store/305w9ipyg497mz9dq31abdf61qk58k29-curl-8.22.0-bin/bin/curl
✅ file
file-5.47
/nix/store/bizyfqdw0h67wzqmp10knmf9s2pqahdb-file-5.47/bin/file
file-5.48
/nix/store/g0fbnccaan8q0rxlw43xnbyb90i94vlc-file-5.48/bin/file
✅ jq
jq-1.8.2
/nix/store/m3y7hqmy20nd1ps6qy8410a326255bf0-jq-1.8.2-bin/bin/jq
✅ less
less 692 (PCRE2 regular expressions)
/nix/store/c6bacbn93qg4a7g9n4czww8rg24dvysr-less-692/bin/less
less 710 (PCRE2 regular expressions)
/nix/store/zi4d0awnc6crz18s177bv9y2yz9al3lq-less-710/bin/less
✅ make
GNU Make 4.4.1
/nix/store/d3bwqm6bymhy3pdgbvf7vxjqfp31m3j1-gnumake-4.4.1/bin/make
/nix/store/z565rgmn9vccv2h0g87z725pw610jmm8-gnumake-4.4.1/bin/make
✅ netstat
net-tools 2.10
/nix/store/jmyzqvgflnswmws7rnxx6g3zbj680xvd-net-tools-2.10/bin/netstat
/nix/store/9i6j9lrsliyz1ww2f8xx18z36wc9n38a-net-tools-2.10/bin/netstat
✅ ninja
1.13.2
/nix/store/7a235m7crqbb4h49sak20fqxpw3n7hr0-ninja-1.13.2/bin/ninja
/nix/store/4nsdla28c4177ssp0hlyv5l2yzj8wcba-ninja-1.13.2/bin/ninja
✅ perl
v5.42.0
/nix/store/6plwsm6pkq79yjv4xvy8csk2pd4hzr67-perl-5.42.0/bin/perl
v5.42.3
/nix/store/8g4dmf7l8q1mf9w4wfmlcl8gvdgmhb8v-perl-5.42.3/bin/perl
✅ pkg-config
0.29.2
/nix/store/1m05k7xgfnw6jc21xxk5681ni3ar97wf-pkg-config-wrapper-0.29.2/bin/pkg-config
/nix/store/b9izmrfjp0qnnyahdwwrzdwi87s1h179-pkg-config-wrapper-0.29.2/bin/pkg-config
✅ vim
VIM - Vi IMproved 9.2 (2026 Feb 14, compiled Jan 01 1980 00:00:00)
/nix/store/hvyqx52g4g2fxhgpans3fksjj6lmlyaw-vim-9.2.0389/bin/vim
/nix/store/9swnd5pf8p8w1gwcbhdpm2yx705kbp38-vim-9.2.1001/bin/vim
✅ zip
Zip 3.0
/nix/store/qnd2ag67hrjj0b6vbmisdshf50r6s72n-zip-3.0/bin/zip
/nix/store/5ypvkry3qra47dylc9kfk36r1q9vha80-zip-3.0/bin/zip
✅ clang-apply-replacements
clang-apply-replacements version 22.1.7
/nix/store/4zp1rjpj2xijrv4kqpwsy3ixwb2r6nlk-clang-tools-22.1.7/bin/clang-apply-replacements
clang-apply-replacements version 22.1.8
/nix/store/37jhp4j3y339zmkx09qbr3fpfy3q0kl0-clang-tools-22.1.8/bin/clang-apply-replacements
✅ clang-apply-replacements-22
clang-apply-replacements version 22.1.7
/nix/store/py2wihg0a96qcppv4hjmww547xabr0fb-clang-apply-replacements-22/bin/clang-apply-replacements-22
clang-apply-replacements version 22.1.8
/nix/store/qg0b99zn522gl3vz9hx9ni9ianb11d1r-clang-apply-replacements-22/bin/clang-apply-replacements-22
✅ clang-format
clang-format version 22.1.7
/nix/store/4zp1rjpj2xijrv4kqpwsy3ixwb2r6nlk-clang-tools-22.1.7/bin/clang-format
clang-format version 22.1.8
/nix/store/37jhp4j3y339zmkx09qbr3fpfy3q0kl0-clang-tools-22.1.8/bin/clang-format
✅ clang-format-22
clang-format version 22.1.7
/nix/store/kz820ccifjlwqnwqjsx7kbiajrgsmbrh-clang-format-22/bin/clang-format-22
clang-format version 22.1.8
/nix/store/m1al4bygpn7i13q1kalavybwh9ldg6rz-clang-format-22/bin/clang-format-22
✅ clang-tidy
LLVM version 22.1.7
/nix/store/4zp1rjpj2xijrv4kqpwsy3ixwb2r6nlk-clang-tools-22.1.7/bin/clang-tidy
LLVM version 22.1.8
/nix/store/37jhp4j3y339zmkx09qbr3fpfy3q0kl0-clang-tools-22.1.8/bin/clang-tidy
✅ clang-tidy-22
LLVM version 22.1.7
/nix/store/gdrkvpw846lkyzh8y9p3zx50g6ml2v84-clang-tidy-22/bin/clang-tidy-22
LLVM version 22.1.8
/nix/store/h51xrqf8xgx4zil2rm1ydp909858y4xm-clang-tidy-22/bin/clang-tidy-22
✅ dot
dot - graphviz version 12.2.1 (0)
/nix/store/12rgns2296s4qcja778gvcbx61z77rc4-graphviz-12.2.1/bin/dot
dot - graphviz version 15.1.1 (0)
/nix/store/f7r47nc0d13xa4c2xaaxa6dp3pzm9sp1-graphviz-15.1.1/bin/dot
✅ doxygen
1.16.1
/nix/store/k0vzr5lvgq1byraknzwvk51wcgpnsrkh-doxygen-1.16.1/bin/doxygen
1.17.0
/nix/store/q6yx2nsimj9xmckmgw4br2cnapbgnqpk-doxygen-1.17.0/bin/doxygen
✅ gcovr
gcovr 8.4
/nix/store/iyzi7fpyclqrha054adnizvif02lg49x-python3.13-gcovr-8.4/bin/gcovr
/nix/store/shx7mfl8n4kx11icvwyj5x7cmf6z5ahq-python3.14-gcovr-8.4/bin/gcovr
✅ gh
gh version 2.94.0 (nixpkgs)
/nix/store/pidh15szlsb1vc41xdsa3xbdghdazvby-gh-2.94.0/bin/gh
gh version 2.102.0 (2026-09-30)
/nix/store/66h5dr9v94x04myh2kabx06rq9763jfc-gh-2.102.0/bin/gh
✅ git-cliff
git-cliff 2.13.1
/nix/store/1q851fs62shgjhc03fxxdkpzxdjg7k11-git-cliff-2.13.1/bin/git-cliff
git-cliff 2.14.2
/nix/store/sl19b42d69b3w2vb9pxp6jbqk841z8vj-git-cliff-2.14.2/bin/git-cliff
✅ git-lfs
git-lfs/3.7.1 (3.7.1; linux amd64; go 1.26.3)
/nix/store/6ljwpal7b1756708m33vj0crpral7mvl-git-lfs-3.7.1/bin/git-lfs
git-lfs/3.8.0 (3.8.0; linux amd64; go 1.26.8)
/nix/store/rp9ld1f6h83xmf2rb5fpib5wmr46fy9f-git-lfs-3.8.0/bin/git-lfs
✅ gpg
gpg (GnuPG) 2.4.9
/nix/store/wx7vk8babxkgy813r70yc67vcwnmagbx-gnupg-2.4.9/bin/gpg
/nix/store/8gm9aq3sspqmvvh7c0x7djcivj2x7yvn-gnupg-2.4.9/bin/gpg
✅ pre-commit
pre-commit 4.5.1
/nix/store/bj6i9vl34cij5h0r165y40hrjqak0bmz-pre-commit-4.5.1/bin/pre-commit
pre-commit 4.6.2
/nix/store/6blf72f7sdmlrqjggxvk5ij4cxb7fk4c-pre-commit-4.6.2/bin/pre-commit
✅ run-clang-tidy
usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
/nix/store/sbg911hs9dbclrzlp04br3iyfpgnaj6r-run-clang-tidy/bin/run-clang-tidy
/nix/store/a760dc8nldq1715410384bkk83idmm00-run-clang-tidy/bin/run-clang-tidy
✅ run-clang-tidy-22
usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
/nix/store/n8yak1ap308gvi7gmrniw0ybsx80fjws-run-clang-tidy-22/bin/run-clang-tidy-22
/nix/store/xwh85m9sa5h8fnpnlmqwfnf2sz9l59mm-run-clang-tidy-22/bin/run-clang-tidy-22
Rust toolchain:
✅ cargo
cargo 1.97.1 (c980f4866 2026-06-30)
/nix/store/88abzp43ywyzql1rhf8jh5aj5n5j7xzr-cargo-1.97.1-x86_64-unknown-linux-gnu/bin/cargo
/nix/store/60b0cz400q34wj2gzav5a41i7y6jzlw3-cargo-1.97.1-x86_64-unknown-linux-gnu/bin/cargo
✅ cargo-audit
cargo-audit-audit 0.22.1
/nix/store/2w9if868piw98xz057sz97jnjvf7hnvf-cargo-audit-0.22.1/bin/cargo-audit
cargo-audit-audit 0.22.2
/nix/store/kksgynfwir3pfii9qkbj3gcc61an0d4s-cargo-audit-0.22.2/bin/cargo-audit
✅ cargo-llvm-cov
cargo-llvm-cov 0.8.5
/nix/store/jjpdf1l6izz6607a346ykra9sndzaw7h-cargo-llvm-cov-0.8.5/bin/cargo-llvm-cov
cargo-llvm-cov 0.9.0
/nix/store/4y8qxx0mlm22hnxlw2cmbcwpxispw02h-cargo-llvm-cov-0.9.0/bin/cargo-llvm-cov
✅ cargo-nextest
cargo-nextest 0.9.137
/nix/store/jhkr7gwyrchkml33gyns9cy0yn7b57qc-cargo-nextest-0.9.137/bin/cargo-nextest
cargo-nextest 0.9.146
/nix/store/q0anzqrazmdr8n1049ca28098q6lv2y8-cargo-nextest-0.9.146/bin/cargo-nextest
✅ clippy-driver
clippy 0.1.97 (8bab26f4f6 2026-07-14)
/nix/store/40d3mzka7r1ps71l0yv2fs6616nbw85m-rust-minimal-1.97.1/bin/clippy-driver
/nix/store/kk0kqdc4f7am21yp30rr8jcrnib5xqzy-rust-minimal-1.97.1/bin/clippy-driver
✅ 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
/nix/store/4mglk7qw9jqbv5nwlx9ahdnpcn5sz70r-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 1.101.0-nightly (282215592 2026-10-04)
/nix/store/qwyzkvba3f9569wjj45b4iyb7nz7qbnr-rust-nightly/bin/rust-nightly
✅ rustc
rustc 1.97.1 (8bab26f4f 2026-07-14)
/nix/store/40d3mzka7r1ps71l0yv2fs6616nbw85m-rust-minimal-1.97.1/bin/rustc
/nix/store/kk0kqdc4f7am21yp30rr8jcrnib5xqzy-rust-minimal-1.97.1/bin/rustc
✅ rustfmt
rustfmt 1.9.0-stable (8bab26f4f6 2026-07-14)
/nix/store/6f1icmb2za20kxn30pgmbv5jq9fnbf4z-rustfmt-preview-1.97.1-x86_64-unknown-linux-gnu/bin/rustfmt
/nix/store/y5gf8a3hix7pnwzdwcfpf3acqhzv3xy6-rustfmt-preview-1.97.1-x86_64-unknown-linux-gnu/bin/rustfmt
GCC toolchain:
✅ gcc
gcc (GCC) 15.2.0
/nix/store/3dd6y3pq00i3r85l45jvz63wjya403nl-gcc-wrapper-15.2.0/bin/gcc
gcc (GCC) 15.3.0
/nix/store/bslmkbaiymxxdc6lwcdb1qyw745nds5j-gcc-wrapper-15.3.0/bin/gcc
✅ gcc-15
gcc (GCC) 15.2.0
/nix/store/d6iri2s6bzqq5ac3fg25j6hgnn1lz44f-gcc-15/bin/gcc-15
gcc (GCC) 15.3.0
/nix/store/h6f2qc8kgwf7r81gwb10csvpjqfc2404-gcc-15/bin/gcc-15
✅ g++
g++ (GCC) 15.2.0
/nix/store/3dd6y3pq00i3r85l45jvz63wjya403nl-gcc-wrapper-15.2.0/bin/g++
g++ (GCC) 15.3.0
/nix/store/bslmkbaiymxxdc6lwcdb1qyw745nds5j-gcc-wrapper-15.3.0/bin/g++
✅ g++-15
g++ (GCC) 15.2.0
/nix/store/gm3msmmxq055lm9gprkfjj9d2gdz1mpg-g++-15/bin/g++-15
g++ (GCC) 15.3.0
/nix/store/jwncv76v61ld9l99v0b1dkmf810if65s-g++-15/bin/g++-15
✅ cpp
cpp (GCC) 15.2.0
/nix/store/3dd6y3pq00i3r85l45jvz63wjya403nl-gcc-wrapper-15.2.0/bin/cpp
cpp (GCC) 15.3.0
/nix/store/bslmkbaiymxxdc6lwcdb1qyw745nds5j-gcc-wrapper-15.3.0/bin/cpp
✅ cpp-15
cpp (GCC) 15.2.0
/nix/store/bn3gmn0m7g4gn2i0yml46fljc7mghiq5-cpp-15/bin/cpp-15
cpp (GCC) 15.3.0
/nix/store/jmi558xj8v7lz2fm6wrradh40dp48zx9-cpp-15/bin/cpp-15
✅ gcov
gcov (GCC) 15.2.0
/nix/store/xvv5sm5i8x0ks6ypfkzl7c4j9srnxz7k-gcc-15.2.0/bin/gcov
gcov (GCC) 15.3.0
/nix/store/n2ab7kf7mm84hna35x463mizcy52pf1h-gcc-15.3.0/bin/gcov
Mold:
✅ mold
mold 2.41.0 (compatible with GNU ld)
/nix/store/2w6fpgxjzzyqmd25wzplm23dfa49a0p2-mold-unwrapped-wrapper-2.41.0/bin/mold
mold 2.42.1 (compatible with GNU ld)
/nix/store/9dcddrfxlfl9n3wfibi5jkmp22bjkvkh-mold-unwrapped-wrapper-2.42.1/bin/mold
Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set).
✅ All 53 checked tools are present and runnable.
✅ All 54 checked tools are present and runnable.

View File

@@ -2,173 +2,176 @@ Detected OS: linux (Linux aarch64)
Core build tools:
✅ cmake
cmake version 4.1.2
/nix/store/nkcpxjifkambzlrwh27a8igvhnbchibg-cmake-4.1.2/bin/cmake
cmake version 4.4.3
/nix/store/39cq2g3d3flq2lmb6hc09grxs4xaax8p-cmake-4.4.3/bin/cmake
✅ conan
Conan version 2.28.1
/nix/store/8i2gyqgc00xvxg9xm6y7n0ilncdv8imw-conan-2.28.1/bin/conan
Conan version 2.32.0
/nix/store/vbr044x1zbq062kkw53zm0sm3slv8g56-conan-2.32.0/bin/conan
✅ git
git version 2.54.0
/nix/store/ixp98f9avf8ikpdrmp40cj33g0dazyp9-git-2.54.0/bin/git
git version 2.55.0
/nix/store/kj6aff4gmz3snpp35rl3gysbl9srd81a-git-2.55.0/bin/git
✅ python3
Python 3.13.13
/nix/store/lqn6mbgzzdrqq2qkwddcmxj9z6amdd86-python3-3.13.13/bin/python3.13
Python 3.14.7
/nix/store/81ag9nxz1flw3xjkv1vvqg8y2ipx2ins-python3-3.14.7/bin/python3.14
Development tooling:
✅ ccache
ccache version 4.13.6
/nix/store/2q39xi2kbi04ibga7635f2sl148d1mzv-ccache-4.13.6/bin/ccache
/nix/store/nzb1lir9sw06fn4635kfx055myqanaz8-ccache-4.13.6/bin/ccache
✅ clang
clang version 22.1.7
/nix/store/xjqffrq9i7la058s9865ig71l9sp1ys5-clang-wrapper-22.1.7/bin/clang
clang version 22.1.8
/nix/store/7z7by1cxa52mk7hjia1pimjvsd6aizhf-clang-wrapper-22.1.8/bin/clang
✅ clang-22
clang version 22.1.7
/nix/store/vcf6ilfwn57828hwzyp6zlyr24j9j6yw-clang-22/bin/clang-22
clang version 22.1.8
/nix/store/pqn36q5paasw7v8sjgz5fd90mfpgkkqr-clang-22/bin/clang-22
✅ clang++
clang version 22.1.7
/nix/store/xjqffrq9i7la058s9865ig71l9sp1ys5-clang-wrapper-22.1.7/bin/clang++
clang version 22.1.8
/nix/store/7z7by1cxa52mk7hjia1pimjvsd6aizhf-clang-wrapper-22.1.8/bin/clang++
✅ clang++-22
clang version 22.1.7
/nix/store/xby0f6gamr7m27zp5cndsvghbp9lgb3c-clang++-22/bin/clang++-22
clang version 22.1.8
/nix/store/m9a3slb0lcixm2csgjz9s0nvj9m5ysnq-clang++-22/bin/clang++-22
✅ ClangBuildAnalyzer
ClangBuildAnalyzer 1.6.0
/nix/store/h893hd4q1bb6ily2lby5dzyfrrzd2nvj-clangbuildanalyzer-1.6.0/bin/ClangBuildAnalyzer
/nix/store/ml2991pbdhh0qcxqj9331lhpsg449vky-clangbuildanalyzer-1.6.0/bin/ClangBuildAnalyzer
✅ curl
curl 8.20.0 (aarch64-unknown-linux-gnu) libcurl/8.20.0 OpenSSL/3.6.2 zlib/1.3.2 libssh2/1.11.1 nghttp2/1.69.0 mit-krb5/1.22.1
/nix/store/i1s0lqwlrmjd2dxzgy2p84cxqqsb0bmk-curl-8.20.0-bin/bin/curl
curl 8.22.0 (aarch64-unknown-linux-gnu) libcurl/8.22.0 OpenSSL/3.5.8 zlib/1.3.2 libssh2/1.11.1 nghttp2/1.70.0 mit-krb5/1.22.2
/nix/store/94c2x2bl3xb7mx43sg1bs1hd7pnw83ac-curl-8.22.0-bin/bin/curl
✅ file
file-5.47
/nix/store/dx973zg9km2w9albsib2vw9wyvacfrlw-file-5.47/bin/file
file-5.48
/nix/store/66sd1x1hls0yijibn00iz08jh4fnas9z-file-5.48/bin/file
✅ jq
jq-1.8.2
/nix/store/kr5py0dcqc9f3l9148qd1axh0z77lld3-jq-1.8.2-bin/bin/jq
✅ less
less 692 (PCRE2 regular expressions)
/nix/store/1blb3s7hhsr77wqi598m6k1qkfp3ms0w-less-692/bin/less
less 710 (PCRE2 regular expressions)
/nix/store/n6z9dv3yjlgvfx1nmrx6qzyv8vzim1l2-less-710/bin/less
✅ make
GNU Make 4.4.1
/nix/store/9ngw1ippk25jjj5fjxv36xbp6iq7rxdx-gnumake-4.4.1/bin/make
/nix/store/q07jxvck60qgrg4p1ywypddh5zz6i9s2-gnumake-4.4.1/bin/make
✅ netstat
net-tools 2.10
/nix/store/7vdsz21f0s499s5yyqzp5s4676q4yxdd-net-tools-2.10/bin/netstat
/nix/store/rfmhd3f8drg3wqyr2mhyrlkd7vyp2pxv-net-tools-2.10/bin/netstat
✅ ninja
1.13.2
/nix/store/8ksx98gsbn5lmlizcmw57yd4sg0k2p58-ninja-1.13.2/bin/ninja
/nix/store/lfkdyggn1ffslfd830l35jrvapr5lzri-ninja-1.13.2/bin/ninja
✅ perl
v5.42.0
/nix/store/5wnly69vv1i3y97al4v3xrqymf9hlzgq-perl-5.42.0/bin/perl
v5.42.3
/nix/store/nyp5q4d2cb0pghi4i7nscb97bqkb6kp3-perl-5.42.3/bin/perl
✅ pkg-config
0.29.2
/nix/store/c7vwy0gl1q0agl2h22gi0m9dg7xxad2l-pkg-config-wrapper-0.29.2/bin/pkg-config
/nix/store/h82n3d0wdrgskdc3yvmz5l3c50h2snvp-pkg-config-wrapper-0.29.2/bin/pkg-config
✅ vim
VIM - Vi IMproved 9.2 (2026 Feb 14, compiled Jan 01 1980 00:00:00)
/nix/store/v8c7pvx26irvy9k5sbwd183cyvckzzb3-vim-9.2.0389/bin/vim
/nix/store/h39h0qq4i80sp3yd7sxjn2wf497v5rln-vim-9.2.1001/bin/vim
✅ zip
Zip 3.0
/nix/store/5mh19mvbv9ym2sm9vymyyaac5l2cj2jq-zip-3.0/bin/zip
/nix/store/lfwhq17wfcmjy823fldhlhxj1hjchdzc-zip-3.0/bin/zip
✅ clang-apply-replacements
clang-apply-replacements version 22.1.7
/nix/store/s53p2m776iqaz7acgr5csgpsd18w15h7-clang-tools-22.1.7/bin/clang-apply-replacements
clang-apply-replacements version 22.1.8
/nix/store/6jr2pbz9mzxznf47lw1w6ss1arqsakw6-clang-tools-22.1.8/bin/clang-apply-replacements
✅ clang-apply-replacements-22
clang-apply-replacements version 22.1.7
/nix/store/bg4kn8z81hk7b9284rjqvr51wpfjqc24-clang-apply-replacements-22/bin/clang-apply-replacements-22
clang-apply-replacements version 22.1.8
/nix/store/mgcaw2063w2d4s61fz023bd0pf3vq5y7-clang-apply-replacements-22/bin/clang-apply-replacements-22
✅ clang-format
clang-format version 22.1.7
/nix/store/s53p2m776iqaz7acgr5csgpsd18w15h7-clang-tools-22.1.7/bin/clang-format
clang-format version 22.1.8
/nix/store/6jr2pbz9mzxznf47lw1w6ss1arqsakw6-clang-tools-22.1.8/bin/clang-format
✅ clang-format-22
clang-format version 22.1.7
/nix/store/79v57mzcw8ng8kl7p961ck08ymhp31v7-clang-format-22/bin/clang-format-22
clang-format version 22.1.8
/nix/store/2iwvl9784yrjfjhfac46z5ihwmbaavk7-clang-format-22/bin/clang-format-22
✅ clang-tidy
LLVM version 22.1.7
/nix/store/s53p2m776iqaz7acgr5csgpsd18w15h7-clang-tools-22.1.7/bin/clang-tidy
LLVM version 22.1.8
/nix/store/6jr2pbz9mzxznf47lw1w6ss1arqsakw6-clang-tools-22.1.8/bin/clang-tidy
✅ clang-tidy-22
LLVM version 22.1.7
/nix/store/wdyd6cb9z1lyi37lbzvwldgcc7yv1n5c-clang-tidy-22/bin/clang-tidy-22
LLVM version 22.1.8
/nix/store/2b7hn8hyp02yfarh8c40c4jsjkm789l0-clang-tidy-22/bin/clang-tidy-22
✅ dot
dot - graphviz version 12.2.1 (0)
/nix/store/58rrk4yzwpmyxvl8cqm18h3dhv24zf00-graphviz-12.2.1/bin/dot
dot - graphviz version 15.1.1 (0)
/nix/store/vz4zb8d1rfigzzdn3i0hd4kff28vk02l-graphviz-15.1.1/bin/dot
✅ doxygen
1.16.1
/nix/store/hq32kzwpl89wgr49iq0gmqn9r5n072zq-doxygen-1.16.1/bin/doxygen
1.17.0
/nix/store/6q4z332wc9jnvad71wjj87lgjxidhgzl-doxygen-1.17.0/bin/doxygen
✅ gcovr
gcovr 8.4
/nix/store/sml3xbbfhhlhk6h7jnlg19pdbx9b764b-python3.13-gcovr-8.4/bin/gcovr
/nix/store/v548fjz51k2nmr257vjnd0zp3bxpc2r1-python3.14-gcovr-8.4/bin/gcovr
✅ gh
gh version 2.94.0 (nixpkgs)
/nix/store/7hh2qi0gj2ifbxbl56cjzbiyfc379bji-gh-2.94.0/bin/gh
gh version 2.102.0 (2026-09-30)
/nix/store/vgg6gnq74h76b4r2ac2q9366f2s5qxwx-gh-2.102.0/bin/gh
✅ git-cliff
git-cliff 2.13.1
/nix/store/bidn3pz53yd6qlg711917xx0q10hqmqv-git-cliff-2.13.1/bin/git-cliff
git-cliff 2.14.2
/nix/store/hrlgliq7l3fl2qrdlc197mvscs4n03rd-git-cliff-2.14.2/bin/git-cliff
✅ git-lfs
git-lfs/3.7.1 (3.7.1; linux arm64; go 1.26.3)
/nix/store/4rsklvkbac5bayy0zv12kxyvspi4sshd-git-lfs-3.7.1/bin/git-lfs
git-lfs/3.8.0 (3.8.0; linux arm64; go 1.26.8)
/nix/store/pa2g4xdvr4a73m6mv4cqs0pik1ka1xp5-git-lfs-3.8.0/bin/git-lfs
✅ gpg
gpg (GnuPG) 2.4.9
/nix/store/ka4i8zz5ni3rzqnzcxbfvwr95fk8pn6q-gnupg-2.4.9/bin/gpg
/nix/store/3qc90b7qkxz7na3xnpd2qb8yvcq8zdp6-gnupg-2.4.9/bin/gpg
✅ pre-commit
pre-commit 4.5.1
/nix/store/n981w6hjfar2l81kxbxs2wxl64vwa5kj-pre-commit-4.5.1/bin/pre-commit
pre-commit 4.6.2
/nix/store/5f3av75nc7n7xbf2hlvlagzpp2g8g562-pre-commit-4.6.2/bin/pre-commit
✅ run-clang-tidy
usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
/nix/store/4z2fyklg78klallr7x9j02kz92hnxp4m-run-clang-tidy/bin/run-clang-tidy
/nix/store/b1yccjmij9qiha8a8d9yxayjdh5cg2vq-run-clang-tidy/bin/run-clang-tidy
✅ run-clang-tidy-22
usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
/nix/store/f8m0p9ad40brp9ahy4i0h27kqjkya1j9-run-clang-tidy-22/bin/run-clang-tidy-22
/nix/store/v75dyrp7vwdgx5w2fzwsy00lf94qpz0i-run-clang-tidy-22/bin/run-clang-tidy-22
Rust toolchain:
✅ cargo
cargo 1.97.1 (c980f4866 2026-06-30)
/nix/store/6hch2qrr86n2sa2m90lrpxrfxxwbkayl-cargo-1.97.1-aarch64-unknown-linux-gnu/bin/cargo
/nix/store/nci0zxxj2fiq0dlkpqzbnqfh27wz4sq4-cargo-1.97.1-aarch64-unknown-linux-gnu/bin/cargo
✅ cargo-audit
cargo-audit-audit 0.22.1
/nix/store/9rxbrn9aa2r1z96186s69pc7vzizyfch-cargo-audit-0.22.1/bin/cargo-audit
cargo-audit-audit 0.22.2
/nix/store/zldaj03mfz9f21yiby5yymq7v8vqygd7-cargo-audit-0.22.2/bin/cargo-audit
✅ cargo-llvm-cov
cargo-llvm-cov 0.8.5
/nix/store/vwjsi159n89szrx4yh5pc3jlf2gp4fld-cargo-llvm-cov-0.8.5/bin/cargo-llvm-cov
cargo-llvm-cov 0.9.0
/nix/store/iwpcfjah31fgn99ph32ajzz7i22j6al5-cargo-llvm-cov-0.9.0/bin/cargo-llvm-cov
✅ cargo-nextest
cargo-nextest 0.9.137
/nix/store/qb6bcg2fjvm3r9s9j98nmffmf9xwh45s-cargo-nextest-0.9.137/bin/cargo-nextest
cargo-nextest 0.9.146
/nix/store/p5svqr8x1rr7b1i8595pr8qafsr7s6fm-cargo-nextest-0.9.146/bin/cargo-nextest
✅ clippy-driver
clippy 0.1.97 (8bab26f4f6 2026-07-14)
/nix/store/a6p27cg6b8szfixfyvkssx6l0c345zw8-rust-minimal-1.97.1/bin/clippy-driver
/nix/store/ny64zks16ms5d70jk1qsiwsk0fr33ipp-rust-minimal-1.97.1/bin/clippy-driver
✅ 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
/nix/store/d9s5skhvi1qxj940mylwym2rjicnc2kq-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 1.101.0-nightly (282215592 2026-10-04)
/nix/store/rbsf1gr6f9ikv2izlyh8im117w6hs7h5-rust-nightly/bin/rust-nightly
✅ rustc
rustc 1.97.1 (8bab26f4f 2026-07-14)
/nix/store/a6p27cg6b8szfixfyvkssx6l0c345zw8-rust-minimal-1.97.1/bin/rustc
/nix/store/ny64zks16ms5d70jk1qsiwsk0fr33ipp-rust-minimal-1.97.1/bin/rustc
✅ rustfmt
rustfmt 1.9.0-stable (8bab26f4f6 2026-07-14)
/nix/store/nd8g81wv1smnvdpy4whpcyv2siwjmaan-rustfmt-preview-1.97.1-aarch64-unknown-linux-gnu/bin/rustfmt
/nix/store/dplvgjikmv8909rk0dkqr90ryjg7xn0j-rustfmt-preview-1.97.1-aarch64-unknown-linux-gnu/bin/rustfmt
GCC toolchain:
✅ gcc
gcc (GCC) 15.2.0
/nix/store/rn6svg593xsmn8qcjzk8x9pa1i62c4kb-gcc-wrapper-15.2.0/bin/gcc
gcc (GCC) 15.3.0
/nix/store/g7z555pl99vnwif4jxa05nk1qdc33i1n-gcc-wrapper-15.3.0/bin/gcc
✅ gcc-15
gcc (GCC) 15.2.0
/nix/store/h489d1rmjisfbxh5kmsb0a7c35j8qsdf-gcc-15/bin/gcc-15
gcc (GCC) 15.3.0
/nix/store/vbdsfjk3j1xy5z329sl2d8vp6mq6x09i-gcc-15/bin/gcc-15
✅ g++
g++ (GCC) 15.2.0
/nix/store/rn6svg593xsmn8qcjzk8x9pa1i62c4kb-gcc-wrapper-15.2.0/bin/g++
g++ (GCC) 15.3.0
/nix/store/g7z555pl99vnwif4jxa05nk1qdc33i1n-gcc-wrapper-15.3.0/bin/g++
✅ g++-15
g++ (GCC) 15.2.0
/nix/store/9ywmhz8bmzknrn3pn84g46z8hj3vrmw5-g++-15/bin/g++-15
g++ (GCC) 15.3.0
/nix/store/9ghl998ry4cxlsg6hnq5rn2iipnlkccm-g++-15/bin/g++-15
✅ cpp
cpp (GCC) 15.2.0
/nix/store/rn6svg593xsmn8qcjzk8x9pa1i62c4kb-gcc-wrapper-15.2.0/bin/cpp
cpp (GCC) 15.3.0
/nix/store/g7z555pl99vnwif4jxa05nk1qdc33i1n-gcc-wrapper-15.3.0/bin/cpp
✅ cpp-15
cpp (GCC) 15.2.0
/nix/store/vmjilh1b830qz9yh0a1jj5ads0jxizdk-cpp-15/bin/cpp-15
cpp (GCC) 15.3.0
/nix/store/wj0mv2fv2lhglvpjzhscqpbigkvik6br-cpp-15/bin/cpp-15
✅ gcov
gcov (GCC) 15.2.0
/nix/store/rmwf5hpi1y2m1wpnfvlxmrhksm4djk2j-gcc-15.2.0/bin/gcov
gcov (GCC) 15.3.0
/nix/store/mszfsj00qnv9ygqcqz3w02qzxxskhvlb-gcc-15.3.0/bin/gcov
Mold:
✅ mold
mold 2.41.0 (compatible with GNU ld)
/nix/store/f5qh5a0bx1dslmnf5n5gx0s6aljbswq3-mold-unwrapped-wrapper-2.41.0/bin/mold
mold 2.42.1 (compatible with GNU ld)
/nix/store/gbq334xbibaz4gn184j7gs58m2spgwi7-mold-unwrapped-wrapper-2.42.1/bin/mold
Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set).
✅ All 53 checked tools are present and runnable.
✅ All 54 checked tools are present and runnable.

View File

@@ -7,7 +7,7 @@
...
}:
let
inherit (import ./packages.nix { inherit pkgs; }) commonPackages;
inherit (import ./packages.nix { inherit pkgs customGlibc; }) commonPackages;
# Each forces something absent on the other platform, so both stay lazy.
linux = import ./linux.nix { inherit pkgs customGlibc; };

View File

@@ -31,6 +31,9 @@ let
};
customGccGcov = if pkgs.stdenv.hostPlatform.isLinux then linux.gcov else plainGcov;
# commonPackages whose clang tools parse with the custom toolchain's headers.
customCommonPackages = (import ./packages.nix { inherit pkgs customGlibc; }).commonPackages;
# Whole directory: init.sh locates the profiles relative to itself.
conanDir = ../conan;
@@ -86,11 +89,11 @@ let
version ? null,
versionedTools ? [ ],
extraPackages ? [ ],
warningHook ? "",
# Opt out of PatchNixBinary.cmake retargeting binaries to the system
# loader. The plain toolchain links a newer glibc, so it must not be
# patched; the custom toolchain patches by default.
noPatchNixBinary ? false,
# The stock nixpkgs toolchain: warn that it doesn't match CI, keep the
# clang tools off the custom toolchain, and opt out of PatchNixBinary.cmake
# retargeting binaries to the system loader (the plain toolchain links a
# newer glibc, so it must not be patched).
plain ? false,
}:
let
compilerVersionHook =
@@ -110,7 +113,8 @@ let
in
(pkgs.mkShell.override { inherit stdenv; }) (
{
packages = commonPackages ++ versionedLinks ++ extraPackages;
packages =
(if plain then commonPackages else customCommonPackages) ++ versionedLinks ++ extraPackages;
# Marks a managed dev shell, so the build (XrplSanity.cmake) can tell an
# intentional Nix toolchain from one leaked into a bare shell.
XRPL_DEVSHELL = shellName;
@@ -119,10 +123,10 @@ let
${compilerVersionHook}
${darwinLibresolvHook}
${conanHook}
${warningHook}
${pkgs.lib.optionalString plain plainWarningHook}
'';
}
// pkgs.lib.optionalAttrs noPatchNixBinary { XRPLD_NO_PATCH_NIX_BINARY = "1"; }
// pkgs.lib.optionalAttrs plain { XRPLD_NO_PATCH_NIX_BINARY = "1"; }
);
in
rec {
@@ -180,8 +184,7 @@ rec {
version = gccVersion;
versionedTools = gccVersionedTools;
extraPackages = [ plainGcov ];
warningHook = plainWarningHook;
noPatchNixBinary = true;
plain = true;
};
clang-plain = makeShell {
@@ -190,7 +193,6 @@ rec {
compilerName = "clang";
version = llvmVersion;
versionedTools = clangVersionedTools;
warningHook = plainWarningHook;
noPatchNixBinary = true;
plain = true;
};
}

View File

@@ -146,6 +146,7 @@ in
})
];
clang = customClang;
gccStdenv = customStdenv;
clangStdenv = pkgs.stdenvAdapters.overrideCC pkgs.stdenv customClang;
gcov = customGcov;

View File

@@ -1,14 +1,28 @@
{ pkgs }:
{
pkgs,
# With the custom glibc, the clang tools (clang-tidy, ...) parse code with the
# Linux custom toolchain's headers, i.e. the same glibc and libstdc++ as the
# build. Without it, they use the nixpkgs default compiler's.
customGlibc ? null,
}:
let
# Compiler versions used across the dev shell and the CI environment.
# Docs link here and scripts read the version off the tools;
# only the clang-format rev in .pre-commit-config.yaml
# has to be bumped alongside llvmVersion.
gccVersion = 15;
llvmVersion = 22;
llvmVersion = 23;
gccPackage = pkgs."gcc${toString gccVersion}";
llvmPackages = pkgs."llvmPackages_${toString llvmVersion}";
# Bound explicitly so it tracks llvmPackages above, not the `with pkgs` default.
clangTools = llvmPackages.clang-tools;
# isLinux first: darwin must not evaluate the custom glibc.
clangTools = llvmPackages.clang-tools.override (
pkgs.lib.optionalAttrs (pkgs.stdenv.hostPlatform.isLinux && customGlibc != null) {
inherit (import ./linux.nix { inherit pkgs customGlibc; }) clang;
}
);
# In LLVM 22, run-clang-tidy.py moved from share/clang/ to bin/, so nixpkgs
# clang-tools no longer links it. Wrap it manually.
@@ -16,6 +30,21 @@ let
exec ${pkgs.python3}/bin/python3 ${llvmPackages.clang-unwrapped}/bin/run-clang-tidy "$@"
'';
# Conan 2.33 is the first release whose settings.yml accepts clang 23.
# TODO: drop once nixpkgs ships it.
conan = pkgs.conan.overridePythonAttrs (old: {
version = "2.33.0";
src = pkgs.fetchFromGitHub {
owner = "conan-io";
repo = "conan";
tag = "2.33.0";
hash = "sha256-FDJjesqvPiAUAEjeqQt088Vnet/KXHbUyEPytP9MyG4=";
};
dependencies = old.dependencies ++ [ pkgs.python3Packages.truststore ];
# The upstream test suite takes long and needs network access.
doCheck = false;
});
rust = import ./rust.nix { inherit pkgs; };
# Nix wraps its toolchain so that binaries are exposed only under unsuffixed
@@ -25,10 +54,11 @@ let
# - Conan's Boost recipe looks up `g++-<major>` before plain `g++`.
# - bin/pre-commit/clang_tidy_check.py looks up `run-clang-tidy-<v>` and
# `clang-apply-replacements-<v>` before the unsuffixed names.
# On a host that also has the matching system binary (e.g. Ubuntu's
# `/usr/bin/g++-15` or `clang-tidy-22`) the probe escapes Nix and mixes a
# system tool into the Nix environment. Generate version-suffixed symlinks
# next to a package's tools so those probes resolve to the Nix ones.
# On a host that also has the matching system binary
# (e.g. Ubuntu's `/usr/bin/g++-<v>` or `clang-tidy-<v>`),
# the probe escapes Nix and mixes a system tool into the Nix environment.
# Generate version-suffixed symlinks next to a package's tools
# so those probes resolve to the Nix ones.
#
# Compiler links must point at whichever compiler is active in a given
# environment (the plain stdenv compiler in the dev shell, the custom-glibc
@@ -112,6 +142,7 @@ in
gnumake
gnupg # needed for signing commits & codecov/codecov-action
graphviz
jq
less # needed for git diff
mold
nettools # provides netstat, used to debug failures in CI

View File

@@ -0,0 +1,39 @@
#include <xrpl/basics/CountedObject.h>
#include <algorithm>
namespace xrpl {
CountedObjects&
CountedObjects::getInstance() noexcept
{
static CountedObjects kInstance;
return kInstance;
}
CountedObjects::CountedObjects() noexcept : count_(0), head_(nullptr)
{
}
CountedObjects::List
CountedObjects::getCounts(int minimumThreshold) const
{
List counts;
// When other operations are concurrent, the count
// might be temporarily less than the actual count.
counts.reserve(count_.load());
for (auto* ctr = head_.load(); ctr != nullptr; ctr = ctr->getNext())
{
if (ctr->getCount() >= minimumThreshold)
counts.emplace_back(ctr->getName(), ctr->getCount());
}
std::ranges::sort(counts);
return counts;
}
} // namespace xrpl

View File

@@ -12,6 +12,7 @@
#include <xrpl/ledger/LedgerTiming.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/FeeSettingsEntry.h>
#include <xrpl/ledger/entries/NegativeUNLEntry.h>
#include <xrpl/nodestore/NodeObject.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Fees.h>
@@ -640,7 +641,7 @@ HashSet<PublicKey>
Ledger::negativeUNL() const
{
HashSet<PublicKey> negUnl;
if (auto sle = read(keylet::negativeUNL()); sle && sle->isFieldPresent(sfDisabledValidators))
if (auto const sle = NegativeUNLEntryR(*this); sle && sle->isFieldPresent(sfDisabledValidators))
{
auto const& nUnlData = sle->getFieldArray(sfDisabledValidators);
for (auto const& n : nUnlData)
@@ -664,7 +665,7 @@ Ledger::negativeUNL() const
std::optional<PublicKey>
Ledger::validatorToDisable() const
{
if (auto sle = read(keylet::negativeUNL()); sle && sle->isFieldPresent(sfValidatorToDisable))
if (auto const sle = NegativeUNLEntryR(*this); sle && sle->isFieldPresent(sfValidatorToDisable))
{
auto d = sle->getFieldVL(sfValidatorToDisable);
auto s = makeSlice(d);
@@ -678,7 +679,8 @@ Ledger::validatorToDisable() const
std::optional<PublicKey>
Ledger::validatorToReEnable() const
{
if (auto sle = read(keylet::negativeUNL()); sle && sle->isFieldPresent(sfValidatorToReEnable))
if (auto const sle = NegativeUNLEntryR(*this);
sle && sle->isFieldPresent(sfValidatorToReEnable))
{
auto d = sle->getFieldVL(sfValidatorToReEnable);
auto s = makeSlice(d);

View File

@@ -3,6 +3,7 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/hash/uhash.h>
#include <xrpl/ledger/detail/ReadViewFwdRange.h>
#include <xrpl/ledger/entries/AmendmentsEntry.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/Rules.h>
@@ -72,9 +73,8 @@ makeRulesGivenLedger(
std::optional const digest = ledger.digest(k.key);
if (digest)
{
auto const sle = ledger.read(k);
if (sle)
return Rules(presets, digest, sle->getFieldV256(sfAmendments));
if (AmendmentsEntryR const entry(k, ledger); entry)
return Rules(presets, digest, entry->getFieldV256(sfAmendments));
}
return Rules(presets);
}

View File

@@ -9,6 +9,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/AmendmentsEntry.h>
#include <xrpl/ledger/entries/LedgerHashesEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
@@ -309,40 +310,13 @@ areCompatible(
std::set<UInt256>
getEnabledAmendments(ReadView const& view)
{
std::set<UInt256> amendments;
if (auto const sle = view.read(keylet::amendments()))
{
if (sle->isFieldPresent(sfAmendments))
{
auto const& v = sle->getFieldV256(sfAmendments);
amendments.insert(v.begin(), v.end());
}
}
return amendments;
return AmendmentsEntryR(view).enabledAmendments();
}
MajorityAmendmentsT
getMajorityAmendments(ReadView const& view)
{
MajorityAmendmentsT ret;
if (auto const sle = view.read(keylet::amendments()))
{
if (sle->isFieldPresent(sfMajorities))
{
using Tp = NetClock::time_point;
using D = Tp::duration;
auto const majorities = sle->getFieldArray(sfMajorities);
for (auto const& m : majorities)
ret[m.getFieldH256(sfAmendment)] = Tp(D(m.getFieldU32(sfCloseTime)));
}
}
return ret;
return AmendmentsEntryR(view).majorityAmendments();
}
std::optional<UInt256>

View File

@@ -0,0 +1,510 @@
#include <xrpl/ledger/helpers/EscrowHelpers.h>
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/MPTAmount.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/XRPAmount.h>
namespace xrpl {
template <>
TER
escrowLockPreclaimHelper<Issue>(
ReadView const& view,
AccountID const& account,
AccountID const& dest,
STAmount const& amount,
beast::Journal j)
{
auto const& issue = amount.get<Issue>();
auto const& issuer = amount.getIssuer();
// If the issuer is the same as the account, return tecNO_PERMISSION
if (issuer == account)
return tecNO_PERMISSION;
// If the lsfAllowTrustLineLocking is not enabled, return tecNO_PERMISSION
auto const sleIssuer = view.read(keylet::account(issuer));
if (!sleIssuer)
return tecNO_ISSUER;
if (!sleIssuer->isFlag(lsfAllowTrustLineLocking))
return tecNO_PERMISSION;
// If the account does not have a trustline to the issuer, return tecNO_LINE
auto const sleRippleState = view.read(keylet::trustLine(account, issuer, issue.currency));
if (!sleRippleState)
return tecNO_LINE;
STAmount const balance = (*sleRippleState)[sfBalance];
// If balance is positive, issuer must have higher address than account
if (balance > beast::kZero && issuer < account)
return tecNO_PERMISSION; // LCOV_EXCL_LINE
// If balance is negative, issuer must have lower address than account
if (balance < beast::kZero && issuer > account)
return tecNO_PERMISSION; // LCOV_EXCL_LINE
// If the issuer has requireAuth set, check if the account is authorized
if (auto const ter = requireAuth(view, issue, account); !isTesSuccess(ter))
return ter;
// If the issuer has requireAuth set, check if the destination is authorized
if (auto const ter = requireAuth(view, issue, dest); !isTesSuccess(ter))
return ter;
// If the issuer has frozen the account, return tecFROZEN
if (isFrozen(view, account, issue))
return tecFROZEN;
// If the issuer has frozen the destination, return tecFROZEN
if (isFrozen(view, dest, issue))
return tecFROZEN;
STAmount const spendableAmount =
accountHolds(view, account, issue.currency, issuer, FreezeHandling::IgnoreFreeze, j);
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
if (spendableAmount <= beast::kZero)
return tecINSUFFICIENT_FUNDS;
// If the spendable amount is less than the amount, return
// tecINSUFFICIENT_FUNDS
if (spendableAmount < amount)
return tecINSUFFICIENT_FUNDS;
// If the amount is not addable to the balance, return tecPRECISION_LOSS
if (!canAdd(spendableAmount, amount))
return tecPRECISION_LOSS;
return tesSUCCESS;
}
template <>
TER
escrowLockPreclaimHelper<MPTIssue>(
ReadView const& view,
AccountID const& account,
AccountID const& dest,
STAmount const& amount,
beast::Journal j)
{
AccountID const issuer = amount.getIssuer();
// If the issuer is the same as the account, return tecNO_PERMISSION
if (issuer == account)
return tecNO_PERMISSION;
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
auto const sleIssuance = view.read(issuanceKey);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
// If the lsfMPTCanEscrow is not enabled, return tecNO_PERMISSION
if (!sleIssuance->isFlag(lsfMPTCanEscrow))
return tecNO_PERMISSION;
// If the issuer is not the same as the issuer of the mpt, return
// tecNO_PERMISSION
if (sleIssuance->getAccountID(sfIssuer) != issuer)
return tecNO_PERMISSION; // LCOV_EXCL_LINE
// If the account does not have the mpt, return tecOBJECT_NOT_FOUND
if (!view.exists(keylet::mptoken(issuanceKey.key, account)))
return tecOBJECT_NOT_FOUND;
// If the issuer has requireAuth set, check if the account is
// authorized
auto const& mptIssue = amount.get<MPTIssue>();
if (auto const ter = requireAuth(view, mptIssue, account, AuthType::WeakAuth);
!isTesSuccess(ter))
return ter;
// If the issuer has requireAuth set, check if the destination is
// authorized
if (auto const ter = requireAuth(view, mptIssue, dest, AuthType::WeakAuth); !isTesSuccess(ter))
return ter;
// If the issuer has frozen the account, return tecLOCKED
if (isFrozen(view, account, *sleIssuance))
return tecLOCKED;
// If the issuer has frozen the destination, return tecLOCKED
if (isFrozen(view, dest, *sleIssuance))
return tecLOCKED;
// If the mpt cannot be transferred, return tecNO_AUTH
if (auto const ter = canTransfer(view, mptIssue, account, dest); !isTesSuccess(ter))
return ter;
STAmount const spendableAmount = accountHolds(
view,
account,
amount.get<MPTIssue>(),
FreezeHandling::IgnoreFreeze,
AuthHandling::IgnoreAuth,
j);
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
if (spendableAmount <= beast::kZero)
return tecINSUFFICIENT_FUNDS;
// If the spendable amount is less than the amount, return
// tecINSUFFICIENT_FUNDS
if (spendableAmount < amount)
return tecINSUFFICIENT_FUNDS;
return tesSUCCESS;
}
template <>
TER
escrowLockApplyHelper<Issue>(
ApplyView& view,
AccountID const& issuer,
AccountID const& sender,
STAmount const& amount,
beast::Journal journal)
{
// Defensive: Issuer cannot create an escrow
if (issuer == sender)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const ter =
directSendNoFee(view, sender, issuer, amount, !amount.holds<MPTIssue>(), journal);
if (!isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
return tesSUCCESS;
}
template <>
TER
escrowLockApplyHelper<MPTIssue>(
ApplyView& view,
AccountID const& issuer,
AccountID const& sender,
STAmount const& amount,
beast::Journal journal)
{
// Defensive: Issuer cannot create an escrow
if (issuer == sender)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const ter = lockEscrowMPT(view, sender, amount, journal);
if (!isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
return tesSUCCESS;
}
template <>
TER
escrowUnlockPreclaimHelper<Issue>(
ReadView const& view,
AccountID const& account,
STAmount const& amount,
bool checkFreeze)
{
AccountID const& issuer = amount.getIssuer();
// If the issuer is the same as the account, return tesSUCCESS
if (issuer == account)
return tesSUCCESS;
// If the issuer has requireAuth set, check if the destination is authorized
if (auto const ter = requireAuth(view, amount.get<Issue>(), account); !isTesSuccess(ter))
return ter;
// If the issuer has deep frozen the destination, return tecFROZEN
if (checkFreeze &&
isDeepFrozen(view, account, amount.get<Issue>().currency, amount.getIssuer()))
return tecFROZEN;
return tesSUCCESS;
}
template <>
TER
escrowUnlockPreclaimHelper<MPTIssue>(
ReadView const& view,
AccountID const& account,
STAmount const& amount,
bool checkFreeze)
{
AccountID const& issuer = amount.getIssuer();
// If the issuer is the same as the account, return tesSUCCESS
if (issuer == account)
return tesSUCCESS;
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
auto const sleIssuance = view.read(issuanceKey);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
// If the issuer has requireAuth set, check if the account is
// authorized
auto const& mptIssue = amount.get<MPTIssue>();
if (auto const ter = requireAuth(view, mptIssue, account, AuthType::WeakAuth);
!isTesSuccess(ter))
return ter;
// If the issuer has frozen the account, return tecLOCKED
if (checkFreeze && isFrozen(view, account, *sleIssuance))
return tecLOCKED;
return tesSUCCESS;
}
template <>
TER
escrowUnlockApplyHelper<Issue>(
ApplyViewContext ctx,
Rate lockedRate,
SLE::Ref sleDest,
XRPAmount xrpBalance,
STAmount const& amount,
AccountID const& issuer,
AccountID const& sender,
AccountID const& receiver,
bool createAsset,
beast::Journal journal)
{
bool const senderIssuer = issuer == sender;
bool const receiverIssuer = issuer == receiver;
if (senderIssuer)
return tecINTERNAL; // LCOV_EXCL_LINE
if (receiverIssuer)
return tesSUCCESS;
auto const& issue = amount.get<Issue>();
Keylet const trustLineKey = keylet::trustLine(receiver, issue);
bool const recvLow = issuer > receiver;
if (!ctx.view.exists(trustLineKey) && createAsset)
{
// Can the account cover the trust line's reserve?
auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, sleDest);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkReserve(
ctx,
sleDest,
xrpBalance,
*sponsorSle,
{.ownerCountDelta = 1},
journal,
tecNO_LINE_INSUF_RESERVE);
!isTesSuccess(ret))
{
JLOG(journal.trace()) << "Trust line does not exist. "
"Insufficient reserve to create line.";
return ret;
}
Currency const currency = issue.currency;
STAmount initialBalance(issue);
initialBalance.get<Issue>().account = noAccount();
if (TER const ter = trustCreate(
ctx.view, // payment sandbox
recvLow, // is dest low?
issuer, // source
receiver, // destination
trustLineKey.key, // ledger index
sleDest, // Account to add to
false, // authorize account
!sleDest->isFlag(lsfDefaultRipple), //
false, // freeze trust line
false, // deep freeze trust line
initialBalance, // zero initial balance
Issue(currency, receiver), // limit of zero
0, // quality in
0, // quality out
*sponsorSle, // sponsor
journal); // journal
!isTesSuccess(ter))
{
return ter; // LCOV_EXCL_LINE
}
ctx.view.update(sleDest);
}
if (!ctx.view.exists(trustLineKey) && !receiverIssuer)
return tecNO_LINE;
auto const xferRate = transferRate(ctx.view, amount);
// update if issuer rate is less than locked rate
if (xferRate < lockedRate)
lockedRate = xferRate;
// Transfer Rate only applies when:
// 1. Issuer is not involved in the transfer (senderIssuer or
// receiverIssuer)
// 2. The locked rate is different from the parity rate
// NOTE: Transfer fee in escrow works a bit differently from a normal
// payment. In escrow, the fee is deducted from the locked/sending amount,
// whereas in a normal payment, the transfer fee is taken on top of the
// sending amount.
auto finalAmt = amount;
if ((!senderIssuer && !receiverIssuer) && lockedRate != kParityRate)
{
// compute transfer fee, if any
auto const xferFee =
amount.value() - divideRound(amount, lockedRate, amount.get<Issue>(), true);
// compute balance to transfer
finalAmt = amount.value() - xferFee;
}
// validate the line limit if the account submitting txn is not the receiver
// of the funds
if (!createAsset)
{
auto const sleRippleState = ctx.view.peek(trustLineKey);
if (!sleRippleState)
return tecINTERNAL; // LCOV_EXCL_LINE
// if the issuer is the high, then we use the low limit
// otherwise we use the high limit
STAmount const lineLimit =
sleRippleState->getFieldAmount(recvLow ? sfLowLimit : sfHighLimit);
STAmount lineBalance = sleRippleState->getFieldAmount(sfBalance);
// flip the sign of the line balance if the issuer is not high
if (!recvLow)
lineBalance.negate();
// add the final amount to the line balance
lineBalance += finalAmt;
// if the transfer would exceed the line limit return tecLIMIT_EXCEEDED
if (lineLimit < lineBalance)
return tecLIMIT_EXCEEDED;
}
// if destination is not the issuer then transfer funds
if (!receiverIssuer)
{
auto const ter = directSendNoFee(ctx.view, issuer, receiver, finalAmt, true, journal);
if (!isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
}
return tesSUCCESS;
}
template <>
TER
escrowUnlockApplyHelper<MPTIssue>(
ApplyViewContext ctx,
Rate lockedRate,
SLE::Ref sleDest,
XRPAmount xrpBalance,
STAmount const& amount,
AccountID const& issuer,
AccountID const& sender,
AccountID const& receiver,
bool createAsset,
beast::Journal journal)
{
bool const senderIssuer = issuer == sender;
bool const receiverIssuer = issuer == receiver;
auto const mptID = amount.get<MPTIssue>().getMptID();
auto const issuanceKey = keylet::mptokenIssuance(mptID);
auto const mptKeylet = keylet::mptoken(issuanceKey.key, receiver);
if (!ctx.view.exists(mptKeylet) && createAsset && !receiverIssuer)
{
auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, sleDest);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkReserve(
ctx, sleDest, xrpBalance, *sponsorSle, {.ownerCountDelta = 1}, journal);
!isTesSuccess(ret))
return ret;
if (auto const ter = createMPToken(ctx.view, mptID, receiver, *sponsorSle, 0);
!isTesSuccess(ter))
{
return ter; // LCOV_EXCL_LINE
}
// update owner count.
increaseOwnerCount(ctx.view, sleDest, *sponsorSle, 1, journal);
}
if (!ctx.view.exists(mptKeylet) && !receiverIssuer)
return tecNO_PERMISSION;
auto const xferRate = transferRate(ctx.view, amount);
// update if issuer rate is less than locked rate
if (xferRate < lockedRate)
lockedRate = xferRate;
// Transfer Rate only applies when:
// 1. Issuer is not involved in the transfer (senderIssuer or
// receiverIssuer)
// 2. The locked rate is different from the parity rate
// NOTE: Transfer fee in escrow works a bit differently from a normal
// payment. In escrow, the fee is deducted from the locked/sending amount,
// whereas in a normal payment, the transfer fee is taken on top of the
// sending amount.
auto finalAmt = amount;
if ((!senderIssuer && !receiverIssuer) && lockedRate != kParityRate)
{
if (ctx.view.rules().enabled(fixCleanup3_4_0))
{
XRPL_ASSERT(
lockedRate >= kParityRate,
"xrpl::escrowUnlockApplyHelper<MPTIssue> : lockedRate is at least parity");
// MPTs are integral, so round the delivered amount down and
// charge any fractional transfer fee to the escrowed amount.
auto const delivered =
mulRatio(amount.mpt(), kParityRate.value, lockedRate.value, false);
finalAmt = STAmount(amount.asset(), delivered.value());
}
else
{
// compute transfer fee, if any
auto const xferFee =
amount.value() - divideRound(amount, lockedRate, amount.asset(), true);
// compute balance to transfer
finalAmt = amount.value() - xferFee;
}
}
return unlockEscrowMPT(
ctx.view,
sender,
receiver,
finalAmt,
ctx.view.rules().enabled(fixTokenEscrowV1) ? amount : finalAmt,
journal);
}
} // namespace xrpl

View File

@@ -3,16 +3,28 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/EscrowHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/MPTAmount.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
#include <algorithm>
#include <cstdint>
@@ -21,9 +33,66 @@
namespace xrpl {
TER
closeChannel(SLE::Ref slep, ApplyView& view, UInt256 const& key, beast::Journal j)
template <>
NotTEC
payChanAmountPreflightHelper<Issue>(Rules const&, STAmount const& amount)
{
if (amount.native() || amount <= beast::kZero)
return temBAD_AMOUNT;
if (badCurrency() == amount.get<Issue>().currency)
return temBAD_CURRENCY;
return tesSUCCESS;
}
template <>
NotTEC
payChanAmountPreflightHelper<MPTIssue>(Rules const& rules, STAmount const& amount)
{
if (!rules.enabled(fixCleanup3_2_0) && !rules.enabled(featureMPTokensV1))
return temDISABLED;
if (amount.native() || amount.mpt() > MPTAmount{kMaxMpTokenAmount} || amount <= beast::kZero)
return temBAD_AMOUNT;
return tesSUCCESS;
}
TER
payChanLockPrecisionHelper(
ReadView const& view,
AccountID const& account,
STAmount const& amount,
beast::Journal j)
{
if (!amount.holds<Issue>())
return tesSUCCESS;
// The trust line debit rounds to the mantissa, so require an exact
// difference rather than canAdd's relative tolerance.
STAmount const spendable = accountHolds(
view,
account,
amount.get<Issue>().currency,
amount.getIssuer(),
FreezeHandling::IgnoreFreeze,
j);
if (!isExactDifference(spendable, amount))
return tecPRECISION_LOSS;
return tesSUCCESS;
}
TER
closeChannel(
SLE::Ref slep,
ApplyViewContext ctx,
UInt256 const& key,
AccountID const& txAccount,
beast::Journal j)
{
ApplyView& view = ctx.view;
AccountID const src = (*slep)[sfAccount];
// Remove PayChan from owner directory
{
@@ -38,9 +107,9 @@ closeChannel(SLE::Ref slep, ApplyView& view, UInt256 const& key, beast::Journal
}
// Remove PayChan from recipient's owner directory, if present.
AccountID const dst = (*slep)[sfDestination];
if (auto const page = (*slep)[~sfDestinationNode])
{
auto const dst = (*slep)[sfDestination];
if (!view.dirRemove(keylet::ownerDir(dst), *page, key, true))
{
// LCOV_EXCL_START
@@ -50,15 +119,72 @@ closeChannel(SLE::Ref slep, ApplyView& view, UInt256 const& key, beast::Journal
}
}
// Transfer amount back to owner, decrement owner count
auto const sle = view.peek(keylet::account(src));
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
// Release the channel's reserve first: a token refund can re-create a
// holding the owner deleted while the channel was open.
decreaseOwnerCountForObject(view, sle, slep, 1, j);
XRPL_ASSERT(
(*slep)[sfAmount] >= (*slep)[sfBalance], "xrpl::closeChannel : minimum channel amount");
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance];
decreaseOwnerCountForObject(view, sle, slep, 1, j);
XRPL_ASSERT(
isExactDifference((*slep)[sfAmount], (*slep)[sfBalance]),
"xrpl::closeChannel : exact refund");
auto const reqDelta = (*slep)[sfAmount] - (*slep)[sfBalance];
auto const& issuer = reqDelta.getIssuer();
// Only update the balance if there is a positive delta.
if (reqDelta > beast::kZero)
{
if (isXRP(reqDelta))
{
(*sle)[sfBalance] = (*sle)[sfBalance] + reqDelta;
}
else
{
if (!view.rules().enabled(featureTokenPaychan))
return temDISABLED;
if (auto const ret = reqDelta.asset().visit([&]<typename T>(T const&) {
return escrowUnlockPreclaimHelper<T>(view, src, reqDelta, false);
});
!isTesSuccess(ret))
return ret;
bool const createAsset = src == txAccount;
if (auto const ret = reqDelta.asset().visit([&]<typename T>(T const&) {
return escrowUnlockApplyHelper<T>(
ctx,
kParityRate,
sle,
STAmount{(*sle)[sfBalance]}.xrp(),
reqDelta,
issuer,
src,
src,
createAsset,
j);
});
!isTesSuccess(ret))
return ret;
}
}
// Remove PayChan from issuer's owner directory, if present.
if (auto const optPage = (*slep)[~sfIssuerNode])
{
if (!view.dirRemove(keylet::ownerDir(issuer), *optPage, key, true))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Could not remove paychan from issuer owner directory";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
}
view.update(sle);
// Remove PayChan from ledger

View File

@@ -18,8 +18,10 @@
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/MPTAmount.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/Quality.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
@@ -1337,9 +1339,26 @@ directSendNoLimitMPT(
}
// Sending 3rd party MPTs: transit.
saActual = (waiveFee == WaiveTransferFee::Yes)
? saAmount
: multiply(saAmount, transferRate(view, saAmount.get<MPTIssue>().getMptID()));
if (waiveFee == WaiveTransferFee::Yes)
{
saActual = saAmount;
}
else
{
auto const rate = transferRate(view, saAmount.get<MPTIssue>().getMptID());
if (view.rules().enabled(fixCleanup3_5_0))
{
// Number math loses precision on large MPT amounts, which can
// overcharge the sender. MPTs are integral, so compute the cost
// exactly and round it up, matching the payment engine.
auto const cost = mulRatio(saAmount.mpt(), rate.value, QUALITY_ONE, true);
saActual = STAmount(saAmount.asset(), cost.value());
}
else
{
saActual = multiply(saAmount, rate);
}
}
JLOG(j.debug()) << "directSendNoLimitMPT> " << to_string(uSenderID) << " - > "
<< to_string(uReceiverID) << " : deliver=" << saAmount.getFullText()
@@ -1448,9 +1467,23 @@ directSendNoLimitMultiMPT(
}
// Sending 3rd party MPTs: transit.
STAmount const actualSend = (waiveFee == WaiveTransferFee::Yes)
? amount
: multiply(amount, transferRate(view, amount.get<MPTIssue>().getMptID()));
STAmount actualSend = amount;
if (waiveFee != WaiveTransferFee::Yes)
{
auto const rate = transferRate(view, amount.get<MPTIssue>().getMptID());
if (view.rules().enabled(fixCleanup3_5_0))
{
// Number math loses precision on large MPT amounts, which can
// overcharge the sender. MPTs are integral, so compute the
// cost exactly and round it up, matching the payment engine.
auto const cost = mulRatio(amount.mpt(), rate.value, QUALITY_ONE, true);
actualSend = STAmount(amount.asset(), cost.value());
}
else
{
actualSend = multiply(amount, rate);
}
}
actual += actualSend;
takeFromSender += actualSend;

View File

@@ -317,8 +317,8 @@ public:
}
else
{
status = checkedCast<Status>(
safeCast<int>(Status::CustomCode) + safeCast<int>(getStatus.code()));
status = static_cast<Status>(
static_cast<int>(Status::CustomCode) + unsafeCast<int>(getStatus.code()));
JLOG(journal.error()) << getStatus.ToString();
}

View File

@@ -8,8 +8,6 @@
#include <xrpl/protocol/digest.h>
#include <xrpl/protocol/tokens.h>
#include <algorithm>
#include <array>
#include <atomic>
#include <cstdint>
#include <cstring>
@@ -21,136 +19,85 @@
namespace xrpl {
namespace {
namespace detail {
/** If true, we implement a cache for account ID lookups.
This is generally a good idea and can improve performance, especially
when serving client requests.
The cache may generate warnings under some sanitizers; if you want to
disable caching, set this variable to false.
/**
* Caches the base58 representations of AccountIDs
*/
constexpr bool cacheAccountIDs = true;
/** Represents a cached entry for the given AccountID.
We keep the size and alignment of the structure at 64 bytes to
make sure it fits within a cache line and avoid false sharing.
*/
struct alignas(64) CachedAccountID
class AccountIdCache
{
/** The sequence lock for the entry.
private:
struct CachedAccountID
{
AccountID id;
char encoding[40] = {0};
};
An even value means that the data is stable, whereas an odd
value indicates the data is being actively modified.
// The actual cache
std::vector<CachedAccountID> cache_;
We use a 32-bit value to ensure that the counter will never
return to an observed value during a read window.
*/
std::atomic<std::uint32_t> seq{0};
// We use a hash function designed to resist algorithmic complexity attacks
HardenedHash<> hasher_;
/** The account id. */
AccountID id{};
// 64 spinlocks, packed into a single 64-bit value
std::atomic<std::uint64_t> locks_ = 0;
/** The length of the cached encoding.
public:
AccountIdCache(std::size_t count) : cache_(count)
{
// This is non-binding, but we try to avoid wasting memory that
// is caused by overallocation.
cache_.shrink_to_fit();
}
This field disambiguates collisions between ACCOUNT_ZERO and
never-written-to slots, since they would otherwise share the
same encoded bits.
*/
std::uint8_t len = 0;
std::string
toBase58(AccountID const& id)
{
auto const index = hasher_(id) % cache_.size();
/** The actual string encoding. */
char encoding[39] = {0};
PackedSpinlock sl(locks_, index % 64);
{
std::scoped_lock const lock(sl);
// The check against the first character of the encoding ensures
// that we don't mishandle the case of the all-zero account:
if (cache_[index].encoding[0] != 0 && cache_[index].id == id)
return cache_[index].encoding;
}
auto ret = encodeBase58Token(TokenType::AccountID, id.data(), id.size());
XRPL_ASSERT(ret.size() <= 38, "xrpl::detail::AccountIdCache : maximum result size");
{
std::scoped_lock const lock(sl);
cache_[index].id = id;
std::strcpy(cache_[index].encoding, ret.c_str());
}
return ret;
}
};
static_assert(sizeof(CachedAccountID) == 64 && alignof(CachedAccountID) == 64);
} // namespace detail
// The actual cache to use: a simple, direct-mapped, best-effort construct.
//
// We do not use a hardened hash for this because an AccountID is already
// just cryptographically generated (i.e. uniformly distributed) bits and
// there is no amplification, since collisions do not carry an additional
// cost.
//
// The size is a "nice" power of 2 because it makes the calculation of an
// index easier.
constinit std::array<CachedAccountID, 65536> gAccountIdCache{};
static std::unique_ptr<detail::AccountIdCache> gAccountIdCache;
} // namespace
void
initAccountIdCache(std::size_t count)
{
if (!gAccountIdCache && count != 0)
gAccountIdCache = std::make_unique<detail::AccountIdCache>(count);
}
std::string
toBase58(AccountID const& v)
{
// Since the input bits are generated by a cryptographically secure
// hash construct, we can just pick any subset of them, no muss, no
// fuss.
auto const index = [&v]() -> std::uint16_t {
auto const data = v.data();
if (gAccountIdCache)
return gAccountIdCache->toBase58(v);
return static_cast<std::uint16_t>(data[0]) | (static_cast<std::uint16_t>(data[1]) << 8);
}();
if constexpr (cacheAccountIDs)
{
auto& slot = gAccountIdCache[index];
// This is the optimistic, store-free read path: a collision with a
// concurrent writer will be detected because the seq recheck fails
// and we fall through to the slow path.
//
// These unsynchronized reads of the slot fields are, formally, data
// races and, technically speaking, result in undefined behavior and
// a tool like TSAN might flag them. These should not have an impact
// on the correctness of the returned data: the sequence lock checks
// ensure we discard any data that was in flux.
if (auto const s0 = slot.seq.load(std::memory_order::acquire);
(s0 & 1) == 0 && slot.len != 0 && slot.id == v)
{
char buf[38];
auto const len = std::min<std::size_t>(slot.len, sizeof(buf));
std::memcpy(buf, slot.encoding, len);
// Prevents the loads above from sinking below the recheck; the
// acquire on s0 constrains later operations, not earlier ones.
std::atomic_thread_fence(std::memory_order::acquire);
// Everything read from the slot — the gates above and the bytes in
// buf — precedes this recheck and is validated by it. No slot access
// may follow it.
if (slot.seq.load(std::memory_order::relaxed) == s0)
return std::string(buf, len);
}
}
auto ret = encodeBase58Token(TokenType::AccountID, v.data(), v.size());
XRPL_ASSERT(!ret.empty() && ret.size() <= 38, "xrpl::toBase58 : maximum result size");
if constexpr (cacheAccountIDs)
{
auto& slot = gAccountIdCache[index];
// The CAS to an odd value excludes other writers; if it fails, another
// thread wrote or is writing into this slot. That is not a problem: we
// just don't store our result into the cache this time.
if (auto s0 = slot.seq.load(std::memory_order::relaxed); (s0 & 1) == 0 &&
slot.seq.compare_exchange_strong(s0, s0 + 1, std::memory_order::acquire))
{
slot.id = v;
slot.len = static_cast<std::uint8_t>(ret.size());
std::copy_n(ret.data(), ret.size(), slot.encoding);
// The acquire on the CAS above keeps the writes from becoming visible
// before the seqlock transitions to an odd value, which means readers
// can detect an in-progress write. This release publishes them before
// the seqlock transitions to an even value.
slot.seq.store(s0 + 2, std::memory_order::release);
}
}
return ret;
return encodeBase58Token(TokenType::AccountID, v.data(), v.size());
}
template <>

View File

@@ -521,6 +521,64 @@ canAdd(STAmount const& a, STAmount const& b)
// LCOV_EXCL_STOP
}
/**
* Integral types defer to canAdd(). For IOU amounts the rounding error of the
* sum is a multiple of the finer operand's unit in the last place, so
* subtracting the coarser operand back from the sum returns the finer one
* only when that error is zero. Both differences are checked because either
* operand may be the finer. IOUAmount throws std::overflow_error when a sum
* or difference exceeds the largest IOU value; the exact result is then not
* representable, so the sum is not exact.
*/
bool
isExactSum(STAmount const& a, STAmount const& b)
{
if (!areComparable(a, b))
return false;
if (a.integral())
return canAdd(a, b);
try
{
STAmount const sum = a + b;
return sum - a == b && sum - b == a;
}
catch (std::overflow_error const&)
{
return false;
}
}
/**
* Integral types defer to canSubtract(). For IOU amounts each check misses
* the case where one operand is dust against the other. With a dust
* subtrahend, a - b rounds to a and adding b back rounds to a again, so only
* a - diff != b shows the loss. With a dust minuend, a - b rounds to -b and
* a - diff rounds to b, so only diff + b != a shows it. Both are checked.
* IOUAmount throws std::overflow_error when a step rounds past the largest
* IOU value; the difference is then not exact.
*/
bool
isExactDifference(STAmount const& a, STAmount const& b)
{
if (!areComparable(a, b))
return false;
if (a.integral())
return canSubtract(a, b);
try
{
STAmount const diff = a - b;
return diff + b == a && a - diff == b;
}
catch (std::overflow_error const&)
{
return false;
}
}
/**
* @brief Determines if it is safe to subtract one STAmount from another.
*
@@ -1642,7 +1700,9 @@ divRoundImpl(STAmount const& num, STAmount const& den, Asset const& asset, bool
bool const resultNegative = (num.negative() != den.negative());
if (asset.holds<MPTIssue>() && isFeatureEnabled(featureMPTokensV2, false))
// fixCleanup3_5_0: the legacy path below overflows on large MPT amounts.
if (asset.holds<MPTIssue>() &&
(isFeatureEnabled(featureMPTokensV2, true) || isFeatureEnabled(fixCleanup3_5_0, true)))
{
// Match the multiply path above: Number performs the rounded
// operation, then STAmount materializes the final MPT amount using the

View File

@@ -5,7 +5,6 @@
#include <xrpl/basics/UnorderedContainers.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/beast/hash/uhash.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/json/json_value.h>
@@ -18,13 +17,9 @@
#include <xrpl/protocol/jss.h>
#include <algorithm>
#include <concepts>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include <vector>
@@ -41,10 +36,10 @@ STPathElement::getHash(STPathElement const& element)
// important. We don't even really need to fully hash the whole
// base_uint here, as a few bytes would do for our use.
//
// The note above is only true because the result of this function is only
// used by the STPathElement equality operator as a fast-reject prefilter,
// ahead of the field comparisons that then decide the answer. Do not use
// it to key a container.
// The note above is only true because the result of this function reaches
// nothing but STPathElement::operator==, where it is a fast-reject
// prefilter ahead of the field comparisons that decide the answer. Do not
// use it to key a container.
for (auto const x : element.getAccountID())
hashAccount += (hashAccount * 257) ^ x;
@@ -111,38 +106,12 @@ STPathSet::operator=(STPathSet const& other)
STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name)
{
using enum STPathElement::Type;
auto parsePathElementType = [](std::underlying_type_t<STPathElement::Type> byte) noexcept
-> std::optional<STPathElement::Type> {
if (byte == std::to_underlying(TypeBoundary))
return TypeBoundary;
constexpr auto knownBits = static_cast<std::underlying_type_t<STPathElement::Type>>(
std::to_underlying(TypeAccount) | std::to_underlying(TypeCurrency) |
std::to_underlying(TypeIssuer) | std::to_underlying(TypeMpt));
if ((byte & ~knownBits) != 0)
return std::nullopt;
return static_cast<STPathElement::Type>(byte);
};
std::vector<STPathElement> path;
std::optional<STPathElement::Type> type;
do
for (;;)
{
type = parsePathElementType(sit.get8());
int const iType = sit.get8();
if (!type)
{
JLOG(debugLog().error()) << "Bad path element in pathset";
Throw<std::runtime_error>("bad path element");
}
if (type == TypeNone || type == TypeBoundary)
if (iType == STPathElement::TypeNone || iType == STPathElement::TypeBoundary)
{
if (path.empty())
{
@@ -153,11 +122,28 @@ STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name)
// Move rather than converting the vector to an STPath by copy.
value_.emplace_back(std::move(path));
path.clear();
if (iType == STPathElement::TypeNone)
return;
}
else if ((iType & ~STPathElement::TypeAll) != 0)
{
JLOG(debugLog().error()) << "Bad path element " << iType << " in pathset";
Throw<std::runtime_error>("bad path element");
}
else
{
bool const hasCurrency = ((*type & TypeCurrency) == TypeCurrency);
bool const hasMPT = ((*type & TypeMpt) == TypeMpt);
auto const hasAccount = (iType & STPathElement::TypeAccount) != 0u;
auto const hasCurrency = (iType & STPathElement::TypeCurrency) != 0u;
auto const hasIssuer = (iType & STPathElement::TypeIssuer) != 0u;
auto const hasMPT = (iType & STPathElement::TypeMpt) != 0u;
AccountID account;
PathAsset asset;
AccountID issuer;
if (hasAccount)
account = sit.get160();
if (hasCurrency && hasMPT)
{
@@ -165,26 +151,18 @@ STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name)
Throw<std::runtime_error>("bad path element: MPT and Currency");
}
AccountID account;
AccountID issuer;
if ((*type & TypeAccount) == TypeAccount)
account = sit.get160();
PathAsset asset;
if (hasCurrency)
asset = Currency::fromRaw(sit.get160());
if (hasMPT)
asset = sit.get192();
if ((*type & TypeIssuer) == TypeIssuer)
if (hasIssuer)
issuer = sit.get160();
path.emplace_back(account, asset, issuer, hasCurrency || hasMPT);
}
} while (type != TypeNone);
}
}
STBase*
@@ -238,22 +216,24 @@ STPath::getJson(JsonOptions) const
for (auto const& it : path_)
{
json::Value elem(json::ValueType::Object);
auto const iType = it.getNodeType();
elem[jss::type] = safeCast<std::uint32_t>(it.getNodeType());
elem[jss::type] = iType;
if (it.isType(STPathElement::TypeAccount))
if ((iType & STPathElement::TypeAccount) != 0u)
elem[jss::account] = to_string(it.getAccountID());
XRPL_ASSERT(
!(it.hasCurrency() && it.hasMPT()),
((iType & STPathElement::TypeCurrency) == 0u) ||
((iType & STPathElement::TypeMpt) == 0u),
"xrpl::STPath::getJson : not type Currency and MPT");
if (it.hasCurrency())
if ((iType & STPathElement::TypeCurrency) != 0u)
elem[jss::currency] = to_string(it.getCurrency());
if (it.hasMPT())
if ((iType & STPathElement::TypeMpt) != 0u)
elem[jss::mpt_issuance_id] = to_string(it.getMPTID());
if (it.hasIssuer())
if ((iType & STPathElement::TypeIssuer) != 0u)
elem[jss::issuer] = to_string(it.getIssuerID());
ret.append(elem);
@@ -285,42 +265,34 @@ STPathSet::add(Serializer& s) const
XRPL_ASSERT(getFName().fieldType == STI_PATHSET, "xrpl::STPathSet::add : valid field type");
bool first = true;
// Workaround, since `Serializer` does not accept strongly-typed enums
// yet. When support is added, the static_assert will trigger, and the
// lambda can be removed entirely.
auto const toByte = [](std::same_as<STPathElement::Type> auto t) noexcept {
static_assert(
!requires(Serializer& ser) { ser.add8(t); },
"Serializer::add8 now accepts STPathElement::Type directly; remove toByte.");
return std::to_underlying(t);
};
for (auto const& spPath : value_)
{
if (!first)
s.add8(toByte(STPathElement::TypeBoundary));
s.add8(STPathElement::TypeBoundary);
for (auto const& speElement : spPath)
{
s.add8(toByte(speElement.getNodeType()));
int const iType = speElement.getNodeType();
if (speElement.isType(STPathElement::TypeAccount))
s.add8(iType);
if ((iType & STPathElement::TypeAccount) != 0u)
s.addBitString(speElement.getAccountID());
if (speElement.hasMPT())
if ((iType & STPathElement::TypeMpt) != 0u)
s.addBitString(speElement.getMPTID());
if (speElement.hasCurrency())
if ((iType & STPathElement::TypeCurrency) != 0u)
s.addBitString(speElement.getCurrency());
if (speElement.hasIssuer())
if ((iType & STPathElement::TypeIssuer) != 0u)
s.addBitString(speElement.getIssuerID());
}
first = false;
}
s.add8(toByte(STPathElement::TypeNone));
s.add8(STPathElement::TypeNone);
}
} // namespace xrpl

View File

@@ -38,7 +38,7 @@ ApplyContext::ApplyContext(
, parentBatchId_(parentBatchId)
{
XRPL_ASSERT(
parentBatchId.has_value() == ((flags_ & ApplyFlags::Batch) == ApplyFlags::Batch),
parentBatchId.has_value() == ((flags_ & TapBatch) == TapBatch),
"Parent Batch ID should be set if batch apply flag is set");
view_.emplace(&base_, flags_);
}
@@ -53,14 +53,13 @@ std::optional<TxMeta>
ApplyContext::apply(TER ter)
{
// NOLINTNEXTLINE(bugprone-unchecked-optional-access) view_ emplaced in constructor
return view_->apply(
base_, tx, ter, parentBatchId_, (flags_ & ApplyFlags::DryRun) != ApplyFlags::None, journal);
return view_->apply(base_, tx, ter, parentBatchId_, (flags_ & TapDryRun) != 0u, journal);
}
void
ApplyContext::addOrderBook(Book const& book)
{
if ((flags_ & ApplyFlags::DryRun) == ApplyFlags::None)
if ((flags_ & TapDryRun) == TapNone)
registry.get().getOrderBookDB().addOrderBook(book);
}

View File

@@ -134,7 +134,7 @@ preflightCheckSigningKey(STObject const& sigObject, beast::Journal j)
std::optional<NotTEC>
preflightCheckSimulateKeys(ApplyFlags flags, STObject const& sigObject, beast::Journal j)
{
if ((flags & ApplyFlags::DryRun) != ApplyFlags::None) // simulation
if ((flags & TapDryRun) != 0u) // simulation
{
std::optional<Slice> const signature = sigObject[~sfTxnSignature];
if (signature && !signature->empty())
@@ -510,8 +510,7 @@ Transactor::minimumFee(
Fees const& fees,
ApplyFlags flags)
{
return scaleFeeLoad(
baseFee, registry.getFeeTrack(), fees, (flags & ApplyFlags::Unlimited) != ApplyFlags::None);
return scaleFeeLoad(baseFee, registry.getFeeTrack(), fees, (flags & TapUnlimited) != 0u);
}
TER
@@ -522,7 +521,7 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
auto const feePaid = ctx.tx[sfFee].xrp();
if ((ctx.flags & ApplyFlags::Batch) != ApplyFlags::None)
if ((ctx.flags & TapBatch) != 0u)
{
if (feePaid == beast::kZero)
return tesSUCCESS;
@@ -952,8 +951,7 @@ Transactor::checkSign(
return tesSUCCESS;
}
if (((flags & ApplyFlags::DryRun) != ApplyFlags::None) && pkSigner.empty() &&
!sigObject.isFieldPresent(sfSigners))
if (((flags & TapDryRun) != 0u) && pkSigner.empty() && !sigObject.isFieldPresent(sfSigners))
{
// simulate: skip signature validation when neither SigningPubKey nor
// Signers are provided
@@ -1124,7 +1122,7 @@ Transactor::checkMultiSign(
}
XRPL_ASSERT(
(flags & ApplyFlags::DryRun) != ApplyFlags::None || !spk.empty(),
(flags & TapDryRun) || !spk.empty(),
"xrpl::Transactor::checkMultiSign : non-empty signer or "
"simulation");
AccountID const signingAcctIDFromPubKey =
@@ -1606,7 +1604,7 @@ Transactor::operator()()
if (ctx_.size() > kOversizeMetaDataCap)
result = tecOVERSIZE;
if (isTecClaim(result) && ((view().flags() & ApplyFlags::FailHard) != ApplyFlags::None))
if (isTecClaim(result) && ((view().flags() & TapFailHard) != 0u))
{
// If the TapFailHard flag is set, a tec result
// must not do anything
@@ -1686,7 +1684,7 @@ Transactor::operator()()
// Once we call apply, we will no longer be able to look at view()
metadata = ctx_.apply(result);
if ((ctx_.flags() & ApplyFlags::DryRun) != ApplyFlags::None)
if ((ctx_.flags() & TapDryRun) != 0u)
return logger(result, false, std::move(metadata));
return logger(result, canApply, std::move(metadata));

View File

@@ -215,7 +215,7 @@ applyBatchTransactions(
auto applyOneTransaction = [&registry, &j, &parentBatchId, &batchView](STTx const& tx) {
OpenView perTxBatchView(kBatchView, batchView);
auto const ret = apply(registry, perTxBatchView, parentBatchId, tx, ApplyFlags::Batch, j);
auto const ret = apply(registry, perTxBatchView, parentBatchId, tx, TapBatch, j);
XRPL_ASSERT(
ret.applied == (isTesSuccess(ret.ter) || isTecClaim(ret.ter)),
"Inner transaction should not be applied");
@@ -274,7 +274,7 @@ applyTransaction(
{
// Returns false if the transaction has need not be retried.
if (retryAssured)
flags = flags | ApplyFlags::Retry;
flags = flags | TapRetry;
JLOG(j.debug()) << "TXN " << txn.getTransactionID() << (retryAssured ? "/retry" : "/final");

View File

@@ -346,8 +346,7 @@ preflight(
// builds; re-check the same invariant here so a release build can't
// silently skip a proposed transaction's signature-presence checks
// outside of a dry run.
if ((flags & ApplyFlags::Proposal) != ApplyFlags::None &&
(flags & ApplyFlags::DryRun) == ApplyFlags::None)
if ((flags & TapProposal) != TapNone && (flags & TapDryRun) == TapNone)
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "apply (preflight): TapProposal set without TapDryRun.";
@@ -378,8 +377,7 @@ preflight(
PreflightContext const pfCtx(registry, tx, parentBatchId, rules, flags, j);
// See the comment in the other preflight() overload above.
if ((flags & ApplyFlags::Proposal) != ApplyFlags::None &&
(flags & ApplyFlags::DryRun) == ApplyFlags::None)
if ((flags & TapProposal) != TapNone && (flags & TapDryRun) == TapNone)
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "apply (preflight): TapProposal set without TapDryRun.";

View File

@@ -148,7 +148,8 @@ XRPNotCreated::visitEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef aft
drops_ -= (*before)[sfBalance].xrp().drops();
break;
case ltPAYCHAN:
drops_ -= ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
if (isXRP((*before)[sfAmount]))
drops_ -= ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
break;
case ltESCROW:
if (isXRP((*before)[sfAmount]))
@@ -181,7 +182,7 @@ XRPNotCreated::visitEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef aft
drops_ += (*after)[sfBalance].xrp().drops();
break;
case ltPAYCHAN:
if (!isDelete)
if (!isDelete && isXRP((*after)[sfAmount]))
drops_ += ((*after)[sfAmount] - (*after)[sfBalance]).xrp().drops();
break;
case ltESCROW:

View File

@@ -89,17 +89,24 @@ ValidMPTIssuance::visitEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef
if (fix320Enabled && after->isFieldPresent(sfReferenceHolding))
referenceHoldingSetOnCreate_ = true;
}
else if (fix320Enabled)
else
{
// Modified issuance: detect any change to sfReferenceHolding.
bool const beforePresent = before->isFieldPresent(sfReferenceHolding);
bool const afterPresent = after->isFieldPresent(sfReferenceHolding);
if (beforePresent != afterPresent ||
(afterPresent &&
before->getFieldH256(sfReferenceHolding) !=
after->getFieldH256(sfReferenceHolding)))
// lsfMPTLocked is the only issuance flag with a legal clear path
// (tfMPTUnlock); the rest are fixed at creation or set-once.
issuanceFlagsCleared_ |= before->getFlags() & ~after->getFlags() & ~lsfMPTLocked;
if (fix320Enabled)
{
referenceHoldingMutated_ = true;
// Modified issuance: detect any change to sfReferenceHolding.
bool const beforePresent = before->isFieldPresent(sfReferenceHolding);
bool const afterPresent = after->isFieldPresent(sfReferenceHolding);
if (beforePresent != afterPresent ||
(afterPresent &&
before->getFieldH256(sfReferenceHolding) !=
after->getFieldH256(sfReferenceHolding)))
{
referenceHoldingMutated_ = true;
}
}
}
}
@@ -199,6 +206,16 @@ ValidMPTIssuance::finalize(
return false;
}
// Post-fixCleanup3_5_0: no transaction may clear an issuance flag other
// than lsfMPTLocked, so downstream code can trust set-once flags such as
// lsfMPTCanTransfer.
if (rules.enabled(fixCleanup3_5_0) && issuanceFlagsCleared_ != 0)
{
JLOG(j.fatal()) << "Invariant failed: immutable MPTokenIssuance flag cleared: "
<< issuanceFlagsCleared_;
return false;
}
// Deleting an MPToken with a non-zero MPTAmount is rejected.
if (rules.enabled(fixCleanup3_5_0))
{
@@ -417,6 +434,12 @@ ValidMPTIssuance::finalize(
return true;
}
// A token paychan claim may auto-create the destination's MPToken.
if (rules.enabled(featureTokenPaychan) && txnType == ttPAYCHAN_CLAIM &&
mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 && mptokensDeleted_ == 0 &&
mptokensCreated_ <= 1)
return true;
if (hasPrivilege(tx, Privilege::MayDeleteMpt) &&
((txnType == ttAMM_DELETE && mptokensDeleted_ <= 2) || mptokensDeleted_ == 1) &&
mptokensCreated_ == 0 && mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0)

View File

@@ -13,7 +13,6 @@
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/MPTAmount.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/Quality.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/STAmount.h>
@@ -100,28 +99,6 @@ AMMLiquidity<TIn, TOut>::generateFibSeqOffer(TAmounts<TIn, TOut> const& balances
}
namespace {
template <typename T>
constexpr T
maxAmount()
{
if constexpr (std::is_same_v<T, XRPAmount>)
{
return XRPAmount(STAmount::kMaxNative);
}
else if constexpr (std::is_same_v<T, IOUAmount>)
{
return IOUAmount(STAmount::kMaxValue / 2, STAmount::kMaxOffset);
}
else if constexpr (std::is_same_v<T, STAmount>)
{
return STAmount(STAmount::kMaxValue / 2, STAmount::kMaxOffset);
}
else if constexpr (std::is_same_v<T, MPTAmount>)
{
return MPTAmount(kMaxMpTokenAmount);
}
}
template <typename T>
T
maxOut(T const& out, Asset const& asset)

View File

@@ -985,7 +985,7 @@ std::optional<std::variant<Quality, AMMOffer<TIn, TOut>>>
BookStep<TIn, TOut, TDerived>::tip(ReadView const& view) const
{
// This can be simplified (and sped up) if directories are never empty.
Sandbox sb(&view, ApplyFlags::None);
Sandbox sb(&view, TapNone);
BookTip bt(sb, book_);
auto const lobQuality = bt.step(j_) ? std::optional<Quality>(bt.quality()) : std::nullopt;
// Multi-path offer generates an offer with the quality

View File

@@ -57,8 +57,10 @@ checkNear(IOUAmount const& expected, IOUAmount const& actual)
static bool
isXRPAccount(STPathElement const& pe)
{
return pe.getNodeType() == STPathElement::TypeAccount && isXRP(pe.getAccountID());
}
if (pe.getNodeType() != STPathElement::TypeAccount)
return false;
return isXRP(pe.getAccountID());
};
static std::pair<TER, std::unique_ptr<Step>>
toStep(
@@ -69,8 +71,11 @@ toStep(
{
auto& j = ctx.j;
if (ctx.isFirst && e1->isAccount() && e1->hasCurrency() && e1->getPathAsset().isXRP())
if (ctx.isFirst && e1->isAccount() &&
((e1->getNodeType() & STPathElement::TypeCurrency) != 0u) && e1->getPathAsset().isXRP())
{
return makeXrpEndpointStep(ctx, e1->getAccountID());
}
if (ctx.isLast && isXRPAccount(*e1) && e2->isAccount())
return makeXrpEndpointStep(ctx, e2->getAccountID());
@@ -112,9 +117,15 @@ toStep(
// LCOV_EXCL_STOP
}
XRPL_ASSERT(e2->hasAsset() || e2->hasIssuer(), "xrpl::toStep : currency or issuer");
PathAsset const outAsset = e2->hasAsset() ? e2->getPathAsset() : curAsset;
auto const outIssuer = e2->hasIssuer() ? e2->getIssuerID() : curAsset.getIssuer();
XRPL_ASSERT(
(e2->getNodeType() & STPathElement::TypeAsset) ||
(e2->getNodeType() & STPathElement::TypeIssuer),
"xrpl::toStep : currency or issuer");
PathAsset const outAsset =
((e2->getNodeType() & STPathElement::TypeAsset) != 0u) ? e2->getPathAsset() : curAsset;
auto const outIssuer = ((e2->getNodeType() & STPathElement::TypeIssuer) != 0u)
? e2->getIssuerID()
: curAsset.getIssuer();
if (isXRP(curAsset) && outAsset.isXRP())
{
@@ -186,15 +197,16 @@ toStrand(
for (std::size_t i = 0; i < path.size(); ++i)
{
auto const& pe = path[i];
auto const t = pe.getNodeType();
if ((pe.getNodeType() & ~STPathElement::TypeAll) != STPathElement::TypeNone || pe.isNone())
if (((t & ~STPathElement::TypeAll) != 0u) || (t == 0u))
return {temBAD_PATH, Strand{}};
bool const hasAccount = pe.isType(STPathElement::TypeAccount);
bool const hasIssuer = pe.hasIssuer();
bool const hasCurrency = pe.hasCurrency();
bool const hasMPT = pe.hasMPT();
bool const hasAsset = pe.hasAsset();
bool const hasAccount = (t & STPathElement::TypeAccount) != 0u;
bool const hasIssuer = (t & STPathElement::TypeIssuer) != 0u;
bool const hasCurrency = (t & STPathElement::TypeCurrency) != 0u;
bool const hasMPT = (t & STPathElement::TypeMpt) != 0u;
bool const hasAsset = (t & STPathElement::TypeAsset) != 0u;
if (hasAccount && (hasIssuer || hasCurrency))
return {temBAD_PATH, Strand{}};
@@ -238,7 +250,9 @@ toStrand(
}();
// Currency or MPT
auto hasAsset = [](STPathElement const& pe) { return pe.hasAsset(); };
auto hasAsset = [](STPathElement const pe) {
return pe.getNodeType() & STPathElement::TypeAsset;
};
std::vector<STPathElement> normPath;
// reserve enough for the path, the implied source, destination,

View File

@@ -187,8 +187,7 @@ AccountSet::preclaim(PreclaimContext const& ctx)
if (!dirIsEmpty(ctx.view, keylet::ownerDir(id)))
{
JLOG(ctx.j.trace()) << "Retry: Owner directory not empty.";
return ((ctx.flags & ApplyFlags::Retry) != ApplyFlags::None) ? TER{terOWNERS}
: TER{tecOWNERS};
return ((ctx.flags & TapRetry) != 0u) ? TER{terOWNERS} : TER{tecOWNERS};
}
}

View File

@@ -3,7 +3,9 @@
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/CheckEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
@@ -30,7 +32,7 @@ CheckCancel::preflight(PreflightContext const& ctx)
TER
CheckCancel::preclaim(PreclaimContext const& ctx)
{
auto const sleCheck = ctx.view.read(keylet::check(ctx.tx[sfCheckID]));
CheckEntryR const sleCheck(ctx.tx[sfCheckID], ctx.view);
if (!sleCheck)
{
JLOG(ctx.j.warn()) << "Check does not exist.";
@@ -59,7 +61,7 @@ CheckCancel::preclaim(PreclaimContext const& ctx)
TER
CheckCancel::doApply()
{
auto const sleCheck = view().peek(keylet::check(ctx_.tx[sfCheckID]));
CheckEntryW sleCheck(ctx_.tx[sfCheckID], view(), j_);
if (!sleCheck)
{
// Error should have been caught in preclaim.
@@ -96,10 +98,10 @@ CheckCancel::doApply()
}
// If we succeeded, update the check owner's reserve.
decreaseOwnerCountForObject(view(), srcId, sleCheck, 1, viewJ);
decreaseOwnerCountForObject(view(), srcId, sleCheck.mutableRawSle(), 1, viewJ);
// Remove check from ledger.
view().erase(sleCheck);
sleCheck.erase();
return tesSUCCESS;
}

View File

@@ -7,6 +7,7 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/PaymentSandbox.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/CheckEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
@@ -87,7 +88,7 @@ CheckCash::preflight(PreflightContext const& ctx)
TER
CheckCash::preclaim(PreclaimContext const& ctx)
{
auto const sleCheck = ctx.view.read(keylet::check(ctx.tx[sfCheckID]));
CheckEntryR const sleCheck(ctx.tx[sfCheckID], ctx.view);
if (!sleCheck)
{
JLOG(ctx.j.warn()) << "Check does not exist.";
@@ -296,8 +297,9 @@ CheckCash::doApply()
// directly on a View.
PaymentSandbox psb(&ctx_.view());
auto sleCheck = psb.peek(keylet::check(ctx_.tx[sfCheckID]));
if (!sleCheck)
std::optional<CheckEntryW> sleCheck;
sleCheck.emplace(ctx_.tx[sfCheckID], psb, j_);
if (!*sleCheck)
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Precheck did not verify check's existence.";
@@ -305,7 +307,7 @@ CheckCash::doApply()
// LCOV_EXCL_STOP
}
AccountID const srcId{sleCheck->getAccountID(sfAccount)};
AccountID const srcId{(*sleCheck)->getAccountID(sfAccount)};
if (!psb.exists(keylet::account(srcId)) || !psb.exists(keylet::account(accountID_)))
{
// LCOV_EXCL_START
@@ -314,7 +316,7 @@ CheckCash::doApply()
// LCOV_EXCL_STOP
}
auto const sponsorCheckSle = getLedgerEntryReserveSponsor(psb, sleCheck);
auto const sponsorCheckSle = getLedgerEntryReserveSponsor(psb, sleCheck->rawSle());
// Preclaim already checked that source has at least the requested
// funds.
@@ -331,7 +333,7 @@ CheckCash::doApply()
if (srcId != accountID_)
{
STAmount const sendMax = sleCheck->at(sfSendMax);
STAmount const sendMax = (*sleCheck)->at(sfSendMax);
// Flow() doesn't do XRP to XRP transfers.
if (sendMax.native())
@@ -569,7 +571,7 @@ CheckCash::doApply()
true, // owner pays transfer fee
OfferCrossing::No,
std::nullopt,
sleCheck->getFieldAmount(sfSendMax),
(*sleCheck)->getFieldAmount(sfSendMax),
std::nullopt, // check does not support domain
viewJ);
@@ -594,7 +596,7 @@ CheckCash::doApply()
// for DeliverMin.
ctx_.deliver(result.actualAmountOut);
sleCheck = psb.peek(keylet::check(ctx_.tx[sfCheckID]));
sleCheck.emplace(ctx_.tx[sfCheckID], psb, j_);
}
}
@@ -602,7 +604,10 @@ CheckCash::doApply()
// check link from destination directory.
if (srcId != accountID_ &&
!psb.dirRemove(
keylet::ownerDir(accountID_), sleCheck->at(sfDestinationNode), sleCheck->key(), true))
keylet::ownerDir(accountID_),
(*sleCheck)->at(sfDestinationNode),
(*sleCheck)->key(),
true))
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete check from destination.";
@@ -611,7 +616,8 @@ CheckCash::doApply()
}
// Remove check from check owner's directory.
if (!psb.dirRemove(keylet::ownerDir(srcId), sleCheck->at(sfOwnerNode), sleCheck->key(), true))
if (!psb.dirRemove(
keylet::ownerDir(srcId), (*sleCheck)->at(sfOwnerNode), (*sleCheck)->key(), true))
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Unable to delete check from owner.";
@@ -620,10 +626,10 @@ CheckCash::doApply()
}
// If we succeeded, update the check owner's reserve.
decreaseOwnerCountForObject(psb, srcId, sleCheck, 1, viewJ);
decreaseOwnerCountForObject(psb, srcId, sleCheck->mutableRawSle(), 1, viewJ);
// Remove check from ledger.
psb.erase(sleCheck);
sleCheck->erase();
psb.apply(ctx_.rawView());
return tesSUCCESS;

View File

@@ -4,6 +4,7 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/CheckEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
@@ -25,7 +26,6 @@
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/Transactor.h>
#include <memory>
#include <optional>
namespace xrpl {
@@ -201,8 +201,9 @@ CheckCreate::doApply()
// Note that we use the value from the sequence or ticket as the
// Check sequence. For more explanation see comments in SeqProxy.h.
auto const seq = ctx_.tx.getSeqProxy();
Keylet const checkKeylet = keylet::check(accountID_, seq);
auto sleCheck = std::make_shared<SLE>(checkKeylet);
CheckEntryW sleCheck(accountID_, seq, view(), j_);
Keylet const checkKeylet = sleCheck.keylet();
sleCheck.newSLE();
sleCheck->setAccountID(sfAccount, accountID_);
AccountID const dstAccountId = ctx_.tx[sfDestination];
@@ -218,7 +219,7 @@ CheckCreate::doApply()
if (auto const expiry = ctx_.tx[~sfExpiration])
sleCheck->setFieldU32(sfExpiration, *expiry);
view().insert(sleCheck);
sleCheck.insert();
auto viewJ = ctx_.registry.get().getJournal("View");
// If it's not a self-send (and it shouldn't be), add Check to the
@@ -252,7 +253,7 @@ CheckCreate::doApply()
// If we succeeded, the new entry counts against the creator's reserve.
increaseOwnerCount(ctx_.getApplyViewContext(), sle, 1, viewJ);
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), sleCheck);
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), sleCheck.mutableRawSle());
return tesSUCCESS;
}

View File

@@ -295,8 +295,7 @@ OfferCreate::checkAcceptAsset(
JLOG(j.debug()) << "delay: can't receive IOUs from non-existent issuer: "
<< to_string(asset.getIssuer());
return ((flags & ApplyFlags::Retry) != ApplyFlags::None) ? TER{terNO_ACCOUNT}
: TER{tecNO_ISSUER};
return ((flags & TapRetry) != 0u) ? TER{terNO_ACCOUNT} : TER{tecNO_ISSUER};
}
// An account cannot create a trustline to itself, so no line can exist
@@ -319,8 +318,7 @@ OfferCreate::checkAcceptAsset(
{
JLOG(j.debug()) << "delay: can't receive IOUs from issuer with "
"DisallowIncomingTrustline set";
return ((flags & ApplyFlags::Retry) != ApplyFlags::None) ? TER{terNO_LINE}
: TER{tecNO_LINE};
return ((flags & TapRetry) != 0u) ? TER{terNO_LINE} : TER{tecNO_LINE};
}
}
@@ -328,8 +326,7 @@ OfferCreate::checkAcceptAsset(
{
if (!trustLine)
{
return ((flags & ApplyFlags::Retry) != ApplyFlags::None) ? TER{terNO_LINE}
: TER{tecNO_LINE};
return ((flags & TapRetry) != 0u) ? TER{terNO_LINE} : TER{tecNO_LINE};
}
// Entries have a canonical representation, determined by a
@@ -345,8 +342,7 @@ OfferCreate::checkAcceptAsset(
JLOG(j.debug()) << "delay: can't receive IOUs from "
"issuer without auth.";
return ((flags & ApplyFlags::Retry) != ApplyFlags::None) ? TER{terNO_AUTH}
: TER{tecNO_AUTH};
return ((flags & TapRetry) != 0u) ? TER{terNO_AUTH} : TER{tecNO_AUTH};
}
}

View File

@@ -6,15 +6,10 @@
#include <xrpl/ledger/entries/EscrowEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/EscrowHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
@@ -25,8 +20,6 @@
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/Transactor.h>
#include <variant>
namespace xrpl {
NotTEC
@@ -35,60 +28,6 @@ EscrowCancel::preflight(PreflightContext const& ctx)
return tesSUCCESS;
}
template <ValidIssueType T>
static TER
escrowCancelPreclaimHelper(
PreclaimContext const& ctx,
AccountID const& account,
STAmount const& amount);
template <>
TER
escrowCancelPreclaimHelper<Issue>(
PreclaimContext const& ctx,
AccountID const& account,
STAmount const& amount)
{
AccountID const& issuer = amount.getIssuer();
// If the issuer is the same as the account, return tecINTERNAL
if (issuer == account)
return tecINTERNAL; // LCOV_EXCL_LINE
// If the issuer has requireAuth set, check if the account is authorized
if (auto const ter = requireAuth(ctx.view, amount.get<Issue>(), account); !isTesSuccess(ter))
return ter;
return tesSUCCESS;
}
template <>
TER
escrowCancelPreclaimHelper<MPTIssue>(
PreclaimContext const& ctx,
AccountID const& account,
STAmount const& amount)
{
AccountID const issuer = amount.getIssuer();
// If the issuer is the same as the account, return tecINTERNAL
if (issuer == account)
return tecINTERNAL; // LCOV_EXCL_LINE
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
auto const sleIssuance = ctx.view.read(issuanceKey);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
// If the issuer has requireAuth set, check if the account is
// authorized
auto const& mptIssue = amount.get<MPTIssue>();
if (auto const ter = requireAuth(ctx.view, mptIssue, account, AuthType::WeakAuth);
!isTesSuccess(ter))
return ter;
return tesSUCCESS;
}
TER
EscrowCancel::preclaim(PreclaimContext const& ctx)
{
@@ -104,11 +43,9 @@ EscrowCancel::preclaim(PreclaimContext const& ctx)
if (!isXRP(amount))
{
if (auto const ret = std::visit(
[&]<typename T>(T const&) {
return escrowCancelPreclaimHelper<T>(ctx, account, amount);
},
amount.asset().value());
if (auto const ret = amount.asset().visit([&]<typename T>(T const&) {
return escrowUnlockPreclaimHelper<T>(ctx.view, account, amount, false);
});
!isTesSuccess(ret))
return ret;
}
@@ -187,21 +124,19 @@ EscrowCancel::doApply()
auto const issuer = amount.getIssuer();
bool const createAsset = account == accountID_;
if (auto const ret = std::visit(
[&]<typename T>(T const&) {
return escrowUnlockApplyHelper<T>(
ctx_.getApplyViewContext(),
kParityRate,
ctx_.view().rules().enabled(fixCleanup3_2_0) ? sle : escrow.mutableRawSle(),
preFeeBalance_,
amount,
issuer,
account, // sender and receiver are the same
account,
createAsset,
j_);
},
amount.asset().value());
if (auto const ret = amount.asset().visit([&]<typename T>(T const&) {
return escrowUnlockApplyHelper<T>(
ctx_.getApplyViewContext(),
kParityRate,
ctx_.view().rules().enabled(fixCleanup3_2_0) ? sle : escrow.mutableRawSle(),
preFeeBalance_,
amount,
issuer,
account, // sender and receiver are the same
account,
createAsset,
j_);
});
!isTesSuccess(ret))
return ret; // LCOV_EXCL_LINE

View File

@@ -10,8 +10,7 @@
#include <xrpl/ledger/entries/EscrowEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/EscrowHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
@@ -19,6 +18,7 @@
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/MPTAmount.h>
#include <xrpl/protocol/MPTIssue.h>
@@ -36,7 +36,6 @@
#include <memory>
#include <system_error>
#include <variant>
namespace xrpl {
@@ -139,9 +138,8 @@ EscrowCreate::preflight(PreflightContext const& ctx)
if (!ctx.rules.enabled(featureTokenEscrow))
return temBAD_AMOUNT;
if (auto const ret = std::visit(
[&]<typename T>(T const&) { return escrowCreatePreflightHelper<T>(ctx); },
amount.asset().value());
if (auto const ret = amount.asset().visit(
[&]<typename T>(T const&) { return escrowCreatePreflightHelper<T>(ctx); });
!isTesSuccess(ret))
return ret;
}
@@ -185,162 +183,6 @@ EscrowCreate::preflight(PreflightContext const& ctx)
return tesSUCCESS;
}
template <ValidIssueType T>
static TER
escrowCreatePreclaimHelper(
PreclaimContext const& ctx,
AccountID const& account,
AccountID const& dest,
STAmount const& amount);
template <>
TER
escrowCreatePreclaimHelper<Issue>(
PreclaimContext const& ctx,
AccountID const& account,
AccountID const& dest,
STAmount const& amount)
{
auto const& issue = amount.get<Issue>();
AccountID const& issuer = amount.getIssuer();
// If the issuer is the same as the account, return tecNO_PERMISSION
if (issuer == account)
return tecNO_PERMISSION;
// If the lsfAllowTrustLineLocking is not enabled, return tecNO_PERMISSION
auto const sleIssuer = ctx.view.read(keylet::account(issuer));
if (!sleIssuer)
return tecNO_ISSUER;
if (!sleIssuer->isFlag(lsfAllowTrustLineLocking))
return tecNO_PERMISSION;
// If the account does not have a trustline to the issuer, return tecNO_LINE
auto const sleRippleState = ctx.view.read(keylet::trustLine(account, issuer, issue.currency));
if (!sleRippleState)
return tecNO_LINE;
STAmount const balance = (*sleRippleState)[sfBalance];
// If balance is positive, issuer must have higher address than account
if (balance > beast::kZero && issuer < account)
return tecNO_PERMISSION; // LCOV_EXCL_LINE
// If balance is negative, issuer must have lower address than account
if (balance < beast::kZero && issuer > account)
return tecNO_PERMISSION; // LCOV_EXCL_LINE
// If the issuer has requireAuth set, check if the account is authorized
if (auto const ter = requireAuth(ctx.view, issue, account); !isTesSuccess(ter))
return ter;
// If the issuer has requireAuth set, check if the destination is authorized
if (auto const ter = requireAuth(ctx.view, issue, dest); !isTesSuccess(ter))
return ter;
// If the issuer has frozen the account, return tecFROZEN
if (isFrozen(ctx.view, account, issue))
return tecFROZEN;
// If the issuer has frozen the destination, return tecFROZEN
if (isFrozen(ctx.view, dest, issue))
return tecFROZEN;
STAmount const spendableAmount = accountHolds(
ctx.view, account, issue.currency, issuer, FreezeHandling::IgnoreFreeze, ctx.j);
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
if (spendableAmount <= beast::kZero)
return tecINSUFFICIENT_FUNDS;
// If the spendable amount is less than the amount, return
// tecINSUFFICIENT_FUNDS
if (spendableAmount < amount)
return tecINSUFFICIENT_FUNDS;
// If the amount is not addable to the balance, return tecPRECISION_LOSS
if (!canAdd(spendableAmount, amount))
return tecPRECISION_LOSS;
return tesSUCCESS;
}
template <>
TER
escrowCreatePreclaimHelper<MPTIssue>(
PreclaimContext const& ctx,
AccountID const& account,
AccountID const& dest,
STAmount const& amount)
{
AccountID const issuer = amount.getIssuer();
// If the issuer is the same as the account, return tecNO_PERMISSION
if (issuer == account)
return tecNO_PERMISSION;
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
auto const sleIssuance = ctx.view.read(issuanceKey);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
// If the lsfMPTCanEscrow is not enabled, return tecNO_PERMISSION
if (!sleIssuance->isFlag(lsfMPTCanEscrow))
return tecNO_PERMISSION;
// If the issuer is not the same as the issuer of the mpt, return
// tecNO_PERMISSION
if (sleIssuance->getAccountID(sfIssuer) != issuer)
return tecNO_PERMISSION; // LCOV_EXCL_LINE
// If the account does not have the mpt, return tecOBJECT_NOT_FOUND
if (!ctx.view.exists(keylet::mptoken(issuanceKey.key, account)))
return tecOBJECT_NOT_FOUND;
// If the issuer has requireAuth set, check if the account is
// authorized
auto const& mptIssue = amount.get<MPTIssue>();
if (auto const ter = requireAuth(ctx.view, mptIssue, account, AuthType::WeakAuth);
!isTesSuccess(ter))
return ter;
// If the issuer has requireAuth set, check if the destination is
// authorized
if (auto const ter = requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth);
!isTesSuccess(ter))
return ter;
// If the issuer has frozen the account, return tecLOCKED
if (isFrozen(ctx.view, account, *sleIssuance))
return tecLOCKED;
// If the issuer has frozen the destination, return tecLOCKED
if (isFrozen(ctx.view, dest, *sleIssuance))
return tecLOCKED;
// If the mpt cannot be transferred, return tecNO_AUTH
if (auto const ter = canTransfer(ctx.view, mptIssue, account, dest); !isTesSuccess(ter))
return ter;
STAmount const spendableAmount = accountHolds(
ctx.view,
account,
amount.get<MPTIssue>(),
FreezeHandling::IgnoreFreeze,
AuthHandling::IgnoreAuth,
ctx.j);
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
if (spendableAmount <= beast::kZero)
return tecINSUFFICIENT_FUNDS;
// If the spendable amount is less than the amount, return
// tecINSUFFICIENT_FUNDS
if (spendableAmount < amount)
return tecINSUFFICIENT_FUNDS;
return tesSUCCESS;
}
TER
EscrowCreate::preclaim(PreclaimContext const& ctx)
{
@@ -364,65 +206,15 @@ EscrowCreate::preclaim(PreclaimContext const& ctx)
if (!ctx.view.rules().enabled(featureTokenEscrow))
return temDISABLED; // LCOV_EXCL_LINE
if (auto const ret = std::visit(
[&]<typename T>(T const&) {
return escrowCreatePreclaimHelper<T>(ctx, account, dest, amount);
},
amount.asset().value());
if (auto const ret = amount.asset().visit([&]<typename T>(T const&) {
return escrowLockPreclaimHelper<T>(ctx.view, account, dest, amount, ctx.j);
});
!isTesSuccess(ret))
return ret;
}
return tesSUCCESS;
}
template <ValidIssueType T>
static TER
escrowLockApplyHelper(
ApplyView& view,
AccountID const& issuer,
AccountID const& sender,
STAmount const& amount,
beast::Journal journal);
template <>
TER
escrowLockApplyHelper<Issue>(
ApplyView& view,
AccountID const& issuer,
AccountID const& sender,
STAmount const& amount,
beast::Journal journal)
{
// Defensive: Issuer cannot create an escrow
if (issuer == sender)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const ter =
directSendNoFee(view, sender, issuer, amount, !amount.holds<MPTIssue>(), journal);
if (!isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
return tesSUCCESS;
}
template <>
TER
escrowLockApplyHelper<MPTIssue>(
ApplyView& view,
AccountID const& issuer,
AccountID const& sender,
STAmount const& amount,
beast::Journal journal)
{
// Defensive: Issuer cannot create an escrow
if (issuer == sender)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const ter = lockEscrowMPT(view, sender, amount, journal);
if (!isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
return tesSUCCESS;
}
TER
EscrowCreate::doApply()
{
@@ -548,11 +340,9 @@ EscrowCreate::doApply()
}
else
{
if (auto const ret = std::visit(
[&]<typename T>(T const&) {
return escrowLockApplyHelper<T>(ctx_.view(), issuer, accountID_, amount, j_);
},
amount.asset().value());
if (auto const ret = amount.asset().visit([&]<typename T>(T const&) {
return escrowLockApplyHelper<T>(ctx_.view(), issuer, accountID_, amount, j_);
});
!isTesSuccess(ret))
{
return ret; // LCOV_EXCL_LINE

View File

@@ -13,15 +13,10 @@
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/EscrowHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
@@ -33,7 +28,6 @@
#include <xrpl/tx/Transactor.h>
#include <system_error>
#include <variant>
namespace xrpl {
@@ -137,68 +131,6 @@ EscrowFinish::calculateBaseFee(ReadView const& view, STTx const& tx)
return Transactor::calculateBaseFee(view, tx) + extraFee;
}
template <ValidIssueType T>
static TER
escrowFinishPreclaimHelper(
PreclaimContext const& ctx,
AccountID const& dest,
STAmount const& amount);
template <>
TER
escrowFinishPreclaimHelper<Issue>(
PreclaimContext const& ctx,
AccountID const& dest,
STAmount const& amount)
{
AccountID const& issuer = amount.getIssuer();
// If the issuer is the same as the account, return tesSUCCESS
if (issuer == dest)
return tesSUCCESS;
// If the issuer has requireAuth set, check if the destination is authorized
if (auto const ter = requireAuth(ctx.view, amount.get<Issue>(), dest); !isTesSuccess(ter))
return ter;
// If the issuer has deep frozen the destination, return tecFROZEN
if (isDeepFrozen(ctx.view, dest, amount.get<Issue>().currency, amount.getIssuer()))
return tecFROZEN;
return tesSUCCESS;
}
template <>
TER
escrowFinishPreclaimHelper<MPTIssue>(
PreclaimContext const& ctx,
AccountID const& dest,
STAmount const& amount)
{
AccountID const& issuer = amount.getIssuer();
// If the issuer is the same as the dest, return tesSUCCESS
if (issuer == dest)
return tesSUCCESS;
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
auto const sleIssuance = ctx.view.read(issuanceKey);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
// If the issuer has requireAuth set, check if the destination is
// authorized
auto const& mptIssue = amount.get<MPTIssue>();
if (auto const ter = requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth);
!isTesSuccess(ter))
return ter;
// If the issuer has frozen the destination, return tecLOCKED
if (isFrozen(ctx.view, dest, *sleIssuance))
return tecLOCKED;
return tesSUCCESS;
}
TER
EscrowFinish::preclaim(PreclaimContext const& ctx)
{
@@ -221,11 +153,9 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
if (!isXRP(amount))
{
if (auto const ret = std::visit(
[&]<typename T>(T const&) {
return escrowFinishPreclaimHelper<T>(ctx, dest, amount);
},
amount.asset().value());
if (auto const ret = amount.asset().visit([&]<typename T>(T const&) {
return escrowUnlockPreclaimHelper<T>(ctx.view, dest, amount);
});
!isTesSuccess(ret))
return ret;
}
@@ -372,21 +302,19 @@ EscrowFinish::doApply()
: kParityRate;
auto const issuer = amount.getIssuer();
bool const createAsset = destID == accountID_;
if (auto const ret = std::visit(
[&]<typename T>(T const&) {
return escrowUnlockApplyHelper<T>(
ctx_.getApplyViewContext(),
lockedRate,
sled,
preFeeBalance_,
amount,
issuer,
account,
destID,
createAsset,
j_);
},
amount.asset().value());
if (auto const ret = amount.asset().visit([&]<typename T>(T const&) {
return escrowUnlockApplyHelper<T>(
ctx_.getApplyViewContext(),
lockedRate,
sled,
preFeeBalance_,
amount,
issuer,
account,
destID,
createAsset,
j_);
});
!isTesSuccess(ret))
return ret;

View File

@@ -91,8 +91,7 @@ LoanSet::preflight(PreflightContext const& ctx)
// A proposed LoanSet is stored unsigned; its CounterpartySignature is
// collected on-ledger afterward, so its absence here is expected, not an
// error (On-Chain Cosigner spec §5.3.1.2).
if (!tx.isFlag(tfInnerBatchTxn) && !counterPartySig &&
(ctx.flags & ApplyFlags::Proposal) == ApplyFlags::None)
if (!tx.isFlag(tfInnerBatchTxn) && !counterPartySig && (ctx.flags & TapProposal) == 0)
{
JLOG(ctx.j.warn()) << "LoanSet transaction must have a CounterpartySignature.";
return temBAD_SIGNER;

View File

@@ -18,6 +18,7 @@
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/MPTAmount.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Permissions.h>
#include <xrpl/protocol/Quality.h>
@@ -715,22 +716,55 @@ Payment::doApply()
// Amount to deliver.
STAmount amountDeliver = dstAmount;
// Factor in the transfer rate.
// No rounding. It'll change once MPT integrated into DEX.
STAmount requiredMaxSourceAmount = multiply(dstAmount, rate);
// True if the sender would pay more than SendMax.
bool exceedsSendMax = false;
// Send more than the account wants to pay or less than
// the account wants to deliver (if no SendMax).
// Adjust the amount to deliver.
if (partialPaymentAllowed && requiredMaxSourceAmount > maxSourceAmount)
if (view().rules().enabled(fixCleanup3_5_0))
{
requiredMaxSourceAmount = maxSourceAmount;
// The legacy multiply() and divide() can overflow on large MPT
// amounts, and Number rounding can charge more than SendMax.
// MPTs are integral, so use exact integer arithmetic. The cost
// is rounded up, matching the transfer fee that accountSend
// charges. If the cost overflows, it exceeds any SendMax.
auto const requiredMaxSourceAmount =
tryMulRatio(dstAmount.mpt(), rate.value, QUALITY_ONE, true);
exceedsSendMax =
!requiredMaxSourceAmount || maxSourceAmount.mpt() < *requiredMaxSourceAmount;
// Send more than the account wants to pay or less than
// the account wants to deliver (if no SendMax).
// Adjust the amount to deliver.
if (partialPaymentAllowed && exceedsSendMax)
{
// Round the delivered amount down so that the sender is
// never charged more than SendMax.
auto const delivered =
mulRatio(maxSourceAmount.mpt(), QUALITY_ONE, rate.value, false);
amountDeliver = STAmount(maxSourceAmount.asset(), delivered.value());
if (amountDeliver <= beast::kZero)
return tecPATH_PARTIAL;
exceedsSendMax = false;
}
}
else
{
// Factor in the transfer rate.
// No rounding. It'll change once MPT integrated into DEX.
amountDeliver = divide(maxSourceAmount, rate);
STAmount requiredMaxSourceAmount = multiply(dstAmount, rate);
// Send more than the account wants to pay or less than
// the account wants to deliver (if no SendMax).
// Adjust the amount to deliver.
if (partialPaymentAllowed && requiredMaxSourceAmount > maxSourceAmount)
{
requiredMaxSourceAmount = maxSourceAmount;
// No rounding. It'll change once MPT integrated into DEX.
amountDeliver = divide(maxSourceAmount, rate);
}
exceedsSendMax = requiredMaxSourceAmount > maxSourceAmount;
}
if (requiredMaxSourceAmount > maxSourceAmount ||
(deliverMin && amountDeliver < *deliverMin))
if (exceedsSendMax || (deliverMin && amountDeliver < *deliverMin))
return tecPATH_PARTIAL;
PaymentSandbox pv(&view());

View File

@@ -4,14 +4,18 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/EscrowHelpers.h>
#include <xrpl/ledger/helpers/PaymentChannelHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/PayChan.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
@@ -46,11 +50,29 @@ PaymentChannelClaim::preflight(PreflightContext const& ctx)
return temMALFORMED;
auto const bal = ctx.tx[~sfBalance];
if (bal && (!isXRP(*bal) || *bal <= beast::kZero))
if (bal && *bal <= beast::kZero)
return temBAD_AMOUNT;
auto const amt = ctx.tx[~sfAmount];
if (amt && (!isXRP(*amt) || *amt <= beast::kZero))
if (amt && *amt <= beast::kZero)
return temBAD_AMOUNT;
if ((bal && !isXRP(*bal)) || (amt && !isXRP(*amt)))
{
if (!ctx.rules.enabled(featureTokenPaychan))
return temBAD_AMOUNT;
// PaymentChannelCreate and PaymentChannelFund gate an MPT-denominated
// Amount on featureMPTokensV1 (payChanAmountPreflightHelper); apply
// the same gate here for sfBalance and sfAmount.
if (((bal && bal->holds<MPTIssue>()) || (amt && amt->holds<MPTIssue>())) &&
!ctx.rules.enabled(featureMPTokensV1))
return temDISABLED;
}
// Both bal and amt must reference the same asset before comparing,
// otherwise STAmount comparison throws.
if (bal && amt && bal->asset() != amt->asset())
return temBAD_AMOUNT;
if (bal && amt && *bal > *amt)
@@ -70,8 +92,8 @@ PaymentChannelClaim::preflight(PreflightContext const& ctx)
// The signature isn't needed if txAccount == src, but if it's
// present, check it
auto const reqBalance = bal->xrp();
auto const authAmt = amt ? amt->xrp() : reqBalance;
auto const reqBalance = *bal;
auto const authAmt = amt ? *amt : reqBalance;
if (reqBalance > authAmt)
return temBAD_AMOUNT;
@@ -96,12 +118,42 @@ PaymentChannelClaim::preflight(PreflightContext const& ctx)
TER
PaymentChannelClaim::preclaim(PreclaimContext const& ctx)
{
if (!ctx.view.rules().enabled(featureCredentials))
return Transactor::preclaim(ctx);
if (ctx.view.rules().enabled(featureCredentials))
{
if (auto const err = credentials::valid(ctx.tx, ctx.view, ctx.tx[sfAccount], ctx.j);
!isTesSuccess(err))
return err;
}
if (auto const err = credentials::valid(ctx.tx, ctx.view, ctx.tx[sfAccount], ctx.j);
!isTesSuccess(err))
return err;
if (ctx.view.rules().enabled(featureTokenPaychan))
{
Keylet const k{ltPAYCHAN, ctx.tx[sfChannel]};
auto const slep = ctx.view.read(k);
if (!slep)
return tecNO_TARGET;
AccountID const dest = (*slep)[sfDestination];
auto const& chanFunds = slep->getFieldAmount(sfAmount);
// The requested balance and the optional amount must each match
// the channel's asset; otherwise the STAmount comparisons and
// subtractions in doApply would throw on mismatched issues. amount
// is checked independently of balance: a claim can carry Amount
// without Balance (the signature-authorized ceiling alone).
if (auto const bal = ctx.tx[~sfBalance]; bal && bal->asset() != chanFunds.asset())
return tecWRONG_ASSET;
if (auto const amt = ctx.tx[~sfAmount]; amt && amt->asset() != chanFunds.asset())
return tecWRONG_ASSET;
if (!isXRP(chanFunds) && ctx.tx.isFieldPresent(sfBalance))
{
if (auto const ret = chanFunds.asset().visit([&]<typename T>(T const&) {
return escrowUnlockPreclaimHelper<T>(ctx.view, dest, chanFunds);
});
!isTesSuccess(ret))
return ret;
}
}
return tesSUCCESS;
}
@@ -116,25 +168,35 @@ PaymentChannelClaim::doApply()
AccountID const src = (*slep)[sfAccount];
AccountID const dst = (*slep)[sfDestination];
AccountID const txAccount = ctx_.tx[sfAccount];
auto const curExpiration = (*slep)[~sfExpiration];
if (isChannelExpired(ctx_.view(), (*slep)[~sfCancelAfter]) ||
isChannelExpired(ctx_.view(), curExpiration))
{
return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.get().getJournal("View"));
return closeChannel(
slep,
ctx_.getApplyViewContext(),
k.key,
accountID_,
ctx_.registry.get().getJournal("View"));
}
if (txAccount != src && txAccount != dst)
if (accountID_ != src && accountID_ != dst)
return tecNO_PERMISSION;
if (ctx_.tx[~sfBalance])
{
auto const chanBalance = slep->getFieldAmount(sfBalance).xrp();
auto const chanFunds = slep->getFieldAmount(sfAmount).xrp();
auto const reqBalance = ctx_.tx[sfBalance].xrp();
auto const chanBalance = slep->getFieldAmount(sfBalance);
auto const chanFunds = slep->getFieldAmount(sfAmount);
auto const reqBalance = ctx_.tx[sfBalance];
if (txAccount == dst && !ctx_.tx[~sfSignature])
// Asset consistency between the requested balance/amount and the
// channel was validated in preclaim.
XRPL_ASSERT(
reqBalance.asset() == chanFunds.asset(),
"xrpl::PaymentChannelClaim::doApply : balance matches channel asset");
if (accountID_ == dst && !ctx_.tx[~sfSignature])
{
return ctx_.view().rules().enabled(fixCleanup3_2_0) ? TER{tecNO_PERMISSION}
: TER{temBAD_SIGNATURE};
@@ -159,27 +221,67 @@ PaymentChannelClaim::doApply()
return tecUNFUNDED_PAYMENT;
}
// The payout reqBalance - sfBalance and the unclaimed remainder
// sfAmount - reqBalance must both be exact so that the amounts
// unlocked by this claim and by the close equal what sfBalance and
// sfAmount record.
if (!isExactDifference(chanFunds, reqBalance) ||
!isExactDifference(reqBalance, chanBalance))
return tecPRECISION_LOSS;
auto const sled = ctx_.view().peek(keylet::account(dst));
if (!sled)
return tecNO_DST;
if (auto err =
verifyDepositPreauth(ctx_.tx, ctx_.view(), txAccount, dst, sled, ctx_.journal);
verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, dst, sled, ctx_.journal);
!isTesSuccess(err))
return err;
(*slep)[sfBalance] = ctx_.tx[sfBalance];
XRPAmount const reqDelta = reqBalance - chanBalance;
STAmount const reqDelta = reqBalance - chanBalance;
XRPL_ASSERT(
reqDelta >= beast::kZero, "xrpl::PaymentChannelClaim::doApply : minimum balance delta");
(*sled)[sfBalance] = (*sled)[sfBalance] + reqDelta;
// Transfer amount to destination
if (isXRP(reqDelta))
{
(*sled)[sfBalance] = (*sled)[sfBalance] + reqDelta;
}
else
{
if (!ctx_.view().rules().enabled(featureTokenPaychan))
return temDISABLED;
Rate lockedRate = slep->isFieldPresent(sfTransferRate)
? xrpl::Rate(slep->getFieldU32(sfTransferRate))
: kParityRate;
auto const& issuer = reqDelta.getIssuer();
bool const createAsset = dst == accountID_;
if (auto const ret = reqDelta.asset().visit([&]<typename T>(T const&) {
return escrowUnlockApplyHelper<T>(
ctx_.getApplyViewContext(),
lockedRate,
sled,
preFeeBalance_,
reqDelta,
issuer,
src,
dst,
createAsset,
j_);
});
!isTesSuccess(ret))
return ret;
}
ctx_.view().update(sled);
ctx_.view().update(slep);
}
if (ctx_.tx.isFlag(tfRenew))
{
if (src != txAccount)
if (src != accountID_)
return tecNO_PERMISSION;
(*slep)[~sfExpiration] = std::nullopt;
ctx_.view().update(slep);
@@ -188,8 +290,15 @@ PaymentChannelClaim::doApply()
if (ctx_.tx.isFlag(tfClose))
{
// Channel will close immediately if dry or the receiver closes
if (dst == txAccount || (*slep)[sfBalance] == (*slep)[sfAmount])
return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.get().getJournal("View"));
if (dst == accountID_ || (*slep)[sfBalance] == (*slep)[sfAmount])
{
return closeChannel(
slep,
ctx_.getApplyViewContext(),
k.key,
accountID_,
ctx_.registry.get().getJournal("View"));
}
auto const settleExpiration = saturatingAdd(
ctx_.view().rules(),

View File

@@ -1,18 +1,25 @@
#include <xrpl/tx/transactors/payment_channel/PaymentChannelCreate.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/EscrowHelpers.h>
#include <xrpl/ledger/helpers/PaymentChannelHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
@@ -51,14 +58,38 @@ namespace xrpl {
TxConsequences
PaymentChannelCreate::makeTxConsequences(PreflightContext const& ctx)
{
return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()};
return TxConsequences{ctx.tx, isXRP(ctx.tx[sfAmount]) ? ctx.tx[sfAmount].xrp() : beast::kZero};
}
bool
PaymentChannelCreate::checkExtraFeatures(PreflightContext const& ctx)
{
// Only require featureMPTokensV1 when the channel amount is an MPT and
// fixCleanup3_2_0 is active; XRP/IOU channels are unaffected by this gate.
if (ctx.rules.enabled(fixCleanup3_2_0) && ctx.tx[sfAmount].holds<MPTIssue>())
return ctx.rules.enabled(featureMPTokensV1);
return true;
}
NotTEC
PaymentChannelCreate::preflight(PreflightContext const& ctx)
{
if (!isXRP(ctx.tx[sfAmount]) || (ctx.tx[sfAmount] <= beast::kZero))
auto const amount = ctx.tx[sfAmount];
if (!isXRP(amount))
{
if (!ctx.rules.enabled(featureTokenPaychan))
return temBAD_AMOUNT;
if (auto const ret = amount.asset().visit([&]<typename T>(T const&) {
return payChanAmountPreflightHelper<T>(ctx.rules, amount);
});
!isTesSuccess(ret))
return ret;
}
else if (amount <= beast::kZero)
{
return temBAD_AMOUNT;
}
if (ctx.tx[sfAccount] == ctx.tx[sfDestination])
return temDST_IS_SRC;
@@ -77,6 +108,8 @@ PaymentChannelCreate::preclaim(PreclaimContext const& ctx)
if (!sle)
return terNO_ACCOUNT;
auto const amount = ctx.tx[sfAmount];
// Check reserve and funds availability
if (!ctx.view.rules().enabled(featureSponsor))
{
@@ -86,15 +119,15 @@ PaymentChannelCreate::preclaim(PreclaimContext const& ctx)
if (balance < reserve)
return tecINSUFFICIENT_RESERVE;
if (balance < reserve + ctx.tx[sfAmount])
if (isXRP(amount) && balance < reserve + ctx.tx[sfAmount])
return tecUNFUNDED;
}
auto const dst = ctx.tx[sfDestination];
auto const dest = ctx.tx[sfDestination];
{
// Check destination account
auto const sled = ctx.view.read(keylet::account(dst));
auto const sled = ctx.view.read(keylet::account(dest));
if (!sled)
return tecNO_DST;
@@ -115,6 +148,24 @@ PaymentChannelCreate::preclaim(PreclaimContext const& ctx)
return tecNO_PERMISSION;
}
if (!isXRP(amount))
{
// Vault shares (an MPT issued by a pseudo-account) cannot be locked
// in a channel.
if (amount.holds<MPTIssue>() && isPseudoAccount(ctx.view, amount.getIssuer()))
return tecWRONG_ASSET;
if (auto const ret = amount.asset().visit([&]<typename T>(T const&) {
return escrowLockPreclaimHelper<T>(ctx.view, account, dest, amount, ctx.j);
});
!isTesSuccess(ret))
return ret;
if (auto const ret = payChanLockPrecisionHelper(ctx.view, account, amount, ctx.j);
!isTesSuccess(ret))
return ret;
}
return tesSUCCESS;
}
@@ -122,6 +173,7 @@ TER
PaymentChannelCreate::doApply()
{
auto const account = ctx_.tx[sfAccount];
auto const amount = ctx_.tx[sfAmount];
auto const sle = ctx_.view().peek(keylet::account(account));
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
@@ -159,25 +211,28 @@ PaymentChannelCreate::doApply()
// - unsponsored: 1 — source owes reserve including the new increment.
auto const sourceReserve = accountReserve(
ctx_.view(), sle, j_, {.ownerCountDelta = getTxReserveSponsorID(ctx_.tx) ? 0 : 1});
if (preFeeBalance_ - ctx_.tx[sfAmount].xrp() < sourceReserve)
// Token channels lock no XRP, so only a native amount reduces the
// source's spendable balance.
auto const lockedXrp = isXRP(amount) ? amount.xrp() : XRPAmount{0};
if (preFeeBalance_ - lockedXrp < sourceReserve)
return tecUNFUNDED;
}
auto const dst = ctx_.tx[sfDestination];
auto const dest = ctx_.tx[sfDestination];
// Create PayChan in ledger.
//
// Note that we use the value from the sequence or ticket as the
// payChan sequence. For more explanation see comments in SeqProxy.h.
Keylet const payChanKeylet = keylet::payChannel(account, dst, ctx_.tx.getSeqProxy());
Keylet const payChanKeylet = keylet::payChannel(account, dest, ctx_.tx.getSeqProxy());
auto const slep = std::make_shared<SLE>(payChanKeylet);
// Funds held in this channel
(*slep)[sfAmount] = ctx_.tx[sfAmount];
(*slep)[sfAmount] = amount;
// Amount channel has already paid
(*slep)[sfBalance] = ctx_.tx[sfAmount].zeroed();
(*slep)[sfBalance] = amount.zeroed();
(*slep)[sfAccount] = account;
(*slep)[sfDestination] = dst;
(*slep)[sfDestination] = dest;
(*slep)[sfSettleDelay] = ctx_.tx[sfSettleDelay];
(*slep)[sfPublicKey] = ctx_.tx[sfPublicKey];
(*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
@@ -188,6 +243,13 @@ PaymentChannelCreate::doApply()
(*slep)[sfSequence] = ctx_.tx.getSeqProxy().value();
}
if (ctx_.view().rules().enabled(featureTokenPaychan) && !isXRP(amount))
{
auto const xferRate = transferRate(ctx_.view(), amount);
if (xferRate != kParityRate)
(*slep)[sfTransferRate] = xferRate.value;
}
ctx_.view().insert(slep);
// Add PayChan to owner directory
@@ -202,14 +264,37 @@ PaymentChannelCreate::doApply()
// Add PayChan to the recipient's owner directory
{
auto const page =
ctx_.view().dirInsert(keylet::ownerDir(dst), payChanKeylet, describeOwnerDir(dst));
ctx_.view().dirInsert(keylet::ownerDir(dest), payChanKeylet, describeOwnerDir(dest));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*slep)[sfDestinationNode] = *page;
}
// Add PayChan to the issuer's owner directory, if applicable
auto const& issuer = amount.getIssuer();
if (!isXRP(amount) && issuer != accountID_ && issuer != dest && !amount.holds<MPTIssue>())
{
auto page = ctx_.view().dirInsert(
keylet::ownerDir(issuer), payChanKeylet, describeOwnerDir(issuer));
if (!page)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*slep)[sfIssuerNode] = *page;
}
// Deduct owner's balance, increment owner count
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
if (isXRP(amount))
{
(*sle)[sfBalance] = (*sle)[sfBalance] - amount;
}
else
{
if (auto const ret = amount.asset().visit([&]<typename T>(T const&) {
return escrowLockApplyHelper<T>(ctx_.view(), issuer, accountID_, amount, j_);
});
!isTesSuccess(ret))
return ret;
}
increaseOwnerCount(ctx_.getApplyViewContext(), sle, 1, ctx_.journal);
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), slep);
ctx_.view().update(sle);

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