mirror of
https://github.com/XRPLF/rippled.git
synced 2026-10-11 06:08:02 +00:00
Compare commits
10 Commits
token-payc
...
nb-baseuin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55b265a8c2 | ||
|
|
02e40e7a00 | ||
|
|
f14395427e | ||
|
|
b707bac74f | ||
|
|
da2b5d5eaf | ||
|
|
4de4902200 | ||
|
|
2ea1f09dcb | ||
|
|
68b4b894de | ||
|
|
defd83e5a6 | ||
|
|
452e31a05f |
10
.github/actions/build-deps/action.yml
vendored
10
.github/actions/build-deps/action.yml
vendored
@@ -44,12 +44,4 @@ 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"
|
||||
.
|
||||
|
||||
8
.github/scripts/strategy-matrix/linux.json
vendored
8
.github/scripts/strategy-matrix/linux.json
vendored
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"image_tag": "sha-ed96e60",
|
||||
"image_tag": "sha-060957e",
|
||||
"configs": {
|
||||
"ubuntu": [
|
||||
{
|
||||
@@ -74,7 +74,7 @@
|
||||
"extra_cmake_args": "-Dvalidator_keys=ON",
|
||||
"package": {
|
||||
"type": "deb",
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-e6055dd"
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-3a2d19f"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -86,7 +86,7 @@
|
||||
"extra_cmake_args": "-Dvalidator_keys=ON -Dassert=ON",
|
||||
"package": {
|
||||
"type": "deb",
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-e6055dd",
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-3a2d19f",
|
||||
"variant": "assert"
|
||||
}
|
||||
}
|
||||
@@ -101,7 +101,7 @@
|
||||
"extra_cmake_args": "-Dvalidator_keys=ON",
|
||||
"package": {
|
||||
"type": "rpm",
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-e6055dd"
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-3a2d19f"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
1
.github/workflows/build-nix-images.yml
vendored
1
.github/workflows/build-nix-images.yml
vendored
@@ -13,6 +13,7 @@ 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"
|
||||
|
||||
1
.github/workflows/build-packaging-images.yml
vendored
1
.github/workflows/build-packaging-images.yml
vendored
@@ -5,6 +5,7 @@ on:
|
||||
branches:
|
||||
- develop
|
||||
paths:
|
||||
- ".github/workflows/build-packaging-images.yml"
|
||||
- "bin/install-packaging-tools.sh"
|
||||
- "package/images/packaging/**"
|
||||
pull_request:
|
||||
|
||||
1
.github/workflows/build-pre-commit-image.yml
vendored
1
.github/workflows/build-pre-commit-image.yml
vendored
@@ -5,6 +5,7 @@ on:
|
||||
branches:
|
||||
- develop
|
||||
paths:
|
||||
- ".github/workflows/build-pre-commit-image.yml"
|
||||
- "bin/pre-commit/Dockerfile"
|
||||
- "rust-toolchain.toml"
|
||||
pull_request:
|
||||
|
||||
2
.github/workflows/cargo-audit.yml
vendored
2
.github/workflows/cargo-audit.yml
vendored
@@ -34,7 +34,7 @@ permissions:
|
||||
jobs:
|
||||
audit:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-ed96e60
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
permissions:
|
||||
contents: read
|
||||
# Needed to open an issue on scheduled failures.
|
||||
|
||||
2
.github/workflows/pre-commit.yml
vendored
2
.github/workflows/pre-commit.yml
vendored
@@ -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-3a2d19f" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/xrpld/pre-commit:sha-473fe44" }'
|
||||
|
||||
2
.github/workflows/publish-docs.yml
vendored
2
.github/workflows/publish-docs.yml
vendored
@@ -41,7 +41,7 @@ env:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-ed96e60
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
2
.github/workflows/reusable-clang-tidy.yml
vendored
2
.github/workflows/reusable-clang-tidy.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
needs: [determine-files]
|
||||
if: ${{ needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.need_full_run == 'true' }}
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-ed96e60"
|
||||
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-060957e"
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
6
.github/workflows/reusable-rust.yml
vendored
6
.github/workflows/reusable-rust.yml
vendored
@@ -28,7 +28,7 @@ permissions:
|
||||
jobs:
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-ed96e60
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
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-ed96e60
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
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-ed96e60
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
2
.github/workflows/reusable-upload-recipe.yml
vendored
2
.github/workflows/reusable-upload-recipe.yml
vendored
@@ -40,7 +40,7 @@ defaults:
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-ed96e60
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
env:
|
||||
REMOTE_NAME: ${{ inputs.remote_name }}
|
||||
CONAN_LOGIN_USERNAME_XRPLF: ${{ secrets.remote_username }}
|
||||
|
||||
@@ -29,20 +29,12 @@ 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.
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
#!/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 ]
|
||||
@@ -20,11 +20,9 @@
|
||||
# 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-<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 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 scoped to a single dev shell rather than to commonPackages are checked
|
||||
# only in that shell, keyed off XRPL_DEVSHELL.
|
||||
@@ -36,16 +34,9 @@
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
# 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)"
|
||||
# Version suffixes of the Nix tool links, tracking nix/packages.nix.
|
||||
gcc_version=15
|
||||
llvm_version=22
|
||||
|
||||
missing=()
|
||||
checked=0
|
||||
@@ -121,7 +112,6 @@ 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
|
||||
@@ -190,7 +180,6 @@ fi
|
||||
if [ "${os}" = "linux" ]; then
|
||||
echo
|
||||
echo "GCC toolchain:"
|
||||
gcc_version="$(major_version gcc)"
|
||||
check gcc
|
||||
check "gcc-${gcc_version}"
|
||||
check g++
|
||||
|
||||
@@ -23,7 +23,7 @@ apt-get clean
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
EOF
|
||||
|
||||
ARG PRE_COMMIT_VERSION=4.6.2
|
||||
ARG PRE_COMMIT_VERSION=4.6.0
|
||||
RUN pip install --no-cache --break-system-packages \
|
||||
pre-commit==${PRE_COMMIT_VERSION}
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ 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.
|
||||
@@ -37,21 +39,8 @@ TIDY_EXTENSIONS = {".cpp", ".h", ".hpp"}
|
||||
FILEPATH_RE = re.compile(r"^(\s*(?:-\s+)?FilePath:\s*)'((?:[^']|'')*)'\s*$")
|
||||
|
||||
|
||||
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:
|
||||
def find_tool(name: str) -> str | None:
|
||||
for candidate in (f"{name}-{CLANG_TIDY_VERSION}", name):
|
||||
if path := shutil.which(candidate):
|
||||
return path
|
||||
return None
|
||||
@@ -114,9 +103,8 @@ def main():
|
||||
if not files:
|
||||
return 0
|
||||
|
||||
version = clang_tidy_major()
|
||||
run_clang_tidy = find_tool("run-clang-tidy", version)
|
||||
clang_apply_replacements = find_tool("clang-apply-replacements", version)
|
||||
run_clang_tidy = find_tool("run-clang-tidy")
|
||||
clang_apply_replacements = find_tool("clang-apply-replacements")
|
||||
missing = [
|
||||
name
|
||||
for name, path in (
|
||||
@@ -126,10 +114,9 @@ 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}.",
|
||||
f"was not found in PATH (tried the '-{CLANG_TIDY_VERSION}' suffix too).",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
|
||||
@@ -292,14 +292,6 @@ 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
|
||||
|
||||
@@ -77,24 +77,19 @@ if(is_clang)
|
||||
message(STATUS " Ignorelist: ${ignorelist_path}")
|
||||
endif()
|
||||
|
||||
# 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.
|
||||
# Define SANITIZERS macro for BuildInfo.cpp
|
||||
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)
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace xrpl::ledger_entries {
|
||||
// builder's STObject and the wrapper's SLE.
|
||||
TEST(${name}Tests, BuilderSettersRoundTrip)
|
||||
{
|
||||
UInt256 const index{1u};
|
||||
constexpr UInt256 index{1};
|
||||
|
||||
% for field in fields:
|
||||
auto const ${field["paramName"]}Value = ${canonical_expr(field)};
|
||||
@@ -85,7 +85,7 @@ TEST(${name}Tests, BuilderSettersRoundTrip)
|
||||
// from that SLE, build a new wrapper, and verify all fields (and validate()).
|
||||
TEST(${name}Tests, BuilderFromSleRoundTrip)
|
||||
{
|
||||
UInt256 const index{2u};
|
||||
constexpr UInt256 index{2};
|
||||
|
||||
% for field in fields:
|
||||
auto const ${field["paramName"]}Value = ${canonical_expr(field)};
|
||||
@@ -146,7 +146,7 @@ TEST(${name}Tests, BuilderFromSleRoundTrip)
|
||||
// 3) Verify wrapper throws when constructed from wrong ledger entry type.
|
||||
TEST(${name}Tests, WrapperThrowsOnWrongEntryType)
|
||||
{
|
||||
UInt256 const index{3u};
|
||||
constexpr UInt256 index{3};
|
||||
|
||||
// Build a valid ledger entry of a different type
|
||||
// Ticket requires: Account, OwnerNode, TicketSequence, PreviousTxnID, PreviousTxnLgrSeq
|
||||
@@ -177,7 +177,7 @@ TEST(${name}Tests, WrapperThrowsOnWrongEntryType)
|
||||
// 4) Verify builder throws when constructed from wrong ledger entry type.
|
||||
TEST(${name}Tests, BuilderThrowsOnWrongEntryType)
|
||||
{
|
||||
UInt256 const index{4u};
|
||||
constexpr UInt256 index{4};
|
||||
|
||||
// Build a valid ledger entry of a different type
|
||||
% if wrong_le_include == "Ticket":
|
||||
@@ -207,7 +207,7 @@ TEST(${name}Tests, BuilderThrowsOnWrongEntryType)
|
||||
// 5) Build with only required fields and verify optional fields return nullopt.
|
||||
TEST(${name}Tests, OptionalFieldsReturnNullopt)
|
||||
{
|
||||
UInt256 const index{3u};
|
||||
constexpr UInt256 index{3};
|
||||
|
||||
% for field in required_fields:
|
||||
auto const ${field["paramName"]}Value = ${canonical_expr(field)};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"requires": [
|
||||
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1782392402.122708",
|
||||
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1782392402.420688",
|
||||
"xrpl-rpc-spec/0.1.20#6daa13eeb4e6c92b82ea6d78866e1da8%1790720024.368048",
|
||||
"xrpl-rpc-spec/0.1.19#870b2d3abcfbbf13b61c2d3c69495060%1790348286.187549",
|
||||
"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#aaa93ab6cda2f2baa6a84490582c8adf%1791284951.826256",
|
||||
"grpc/1.81.1#b87796a4269034856cbc1a2522db16eb%1788275071.530512",
|
||||
"fast_float/8.2.10#f6f28d6bb22112078e7dbda611caf681%1785888854.601666",
|
||||
"ed25519/2015.03#ae761bdc52730a843f0809bdf6c1b1f6%1782307148.15562",
|
||||
"date/3.0.4#862e11e80030356b53c2c38599ceb32b%1782392402.538492",
|
||||
@@ -34,15 +34,11 @@
|
||||
"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",
|
||||
|
||||
@@ -3,13 +3,5 @@
|
||||
core:non_interactive=True
|
||||
core.download:parallel={{ os.cpu_count() }}
|
||||
core.upload:parallel={{ os.cpu_count() }}
|
||||
# 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=5
|
||||
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)
|
||||
|
||||
@@ -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++-<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 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 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,17 +49,6 @@ 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, #}
|
||||
@@ -69,13 +58,3 @@ tools.info.package_id:confs+=["tools.build:exelinkflags"]
|
||||
{# 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 %}
|
||||
|
||||
@@ -5,9 +5,6 @@ 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" %}
|
||||
|
||||
@@ -40,7 +40,7 @@ class Xrpl(ConanFile):
|
||||
"nudb/2.0.9",
|
||||
"openssl/3.6.3",
|
||||
"soci/4.0.3",
|
||||
"xrpl-rpc-spec/0.1.20",
|
||||
"xrpl-rpc-spec/0.1.19",
|
||||
"zlib/1.3.2",
|
||||
]
|
||||
|
||||
@@ -49,8 +49,7 @@ class Xrpl(ConanFile):
|
||||
]
|
||||
|
||||
tool_requires = [
|
||||
"grpc/<host_version>",
|
||||
"protobuf/<host_version>",
|
||||
"protobuf/6.33.5",
|
||||
]
|
||||
|
||||
default_options = {
|
||||
@@ -124,10 +123,6 @@ 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
|
||||
|
||||
|
||||
16
docs/build/environment.md
vendored
16
docs/build/environment.md
vendored
@@ -10,15 +10,14 @@ 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 | `gccVersion` in [packages.nix] |
|
||||
| Clang | `llvmVersion` in [packages.nix] |
|
||||
| Apple Clang | 21 |
|
||||
| MSVC | Visual Studio 2026 |
|
||||
| Compiler | Version |
|
||||
| ----------- | ------------------ |
|
||||
| GCC | 15.2 |
|
||||
| Clang | 22 |
|
||||
| Apple Clang | 21 |
|
||||
| MSVC | Visual Studio 2026 |
|
||||
|
||||
LLVM tools (`clang-tidy` and `clang-format`)
|
||||
come from the same LLVM release as Clang.
|
||||
LLVM tools (`clang-tidy` and `clang-format`) are also pinned to version 22.
|
||||
|
||||
### Older compilers
|
||||
|
||||
@@ -157,4 +156,3 @@ 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
20
docs/build/nix.md
vendored
@@ -194,22 +194,10 @@ 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`. 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:
|
||||
writes the store into `PT_INTERP` and `RUNPATH`, and CI builds inside an image
|
||||
whose store is fixed for its lifetime, so that is fine. Only the binaries
|
||||
[`PatchNixBinary.cmake`](../../cmake/PatchNixBinary.cmake) retargets to the
|
||||
system loader have to be clean, and those are what CI checks:
|
||||
|
||||
```bash
|
||||
bin/check-nix-store-refs.sh build/xrpld
|
||||
|
||||
@@ -1,29 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/type_name.h>
|
||||
#include <xrpl/beast/utility/instrumentation.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.
|
||||
@@ -33,68 +35,147 @@ public:
|
||||
class Counter
|
||||
{
|
||||
public:
|
||||
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;
|
||||
Counter(std::string name) noexcept;
|
||||
|
||||
do
|
||||
{
|
||||
head = instance.head_.load();
|
||||
next_ = head;
|
||||
} while (instance.head_.exchange(this) != head);
|
||||
// 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;
|
||||
|
||||
++instance.count_;
|
||||
}
|
||||
|
||||
~Counter() noexcept = default;
|
||||
|
||||
int
|
||||
std::uint32_t
|
||||
increment() noexcept
|
||||
{
|
||||
return ++count_;
|
||||
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;
|
||||
}
|
||||
|
||||
int
|
||||
std::uint32_t
|
||||
decrement() noexcept
|
||||
{
|
||||
return --count_;
|
||||
auto const prev = count_.fetch_sub(1, std::memory_order::relaxed);
|
||||
XRPL_ASSERT(prev != 0, "xrpl::CountedObjects::Counter::decrement : no underflow");
|
||||
return prev - 1;
|
||||
}
|
||||
|
||||
[[nodiscard]] int
|
||||
getCount() const noexcept
|
||||
[[nodiscard]] std::uint32_t
|
||||
count() const noexcept
|
||||
{
|
||||
return count_.load();
|
||||
return count_.load(std::memory_order::relaxed);
|
||||
}
|
||||
|
||||
[[nodiscard]] Counter*
|
||||
getNext() const noexcept
|
||||
[[nodiscard]] std::uint32_t
|
||||
max() const noexcept
|
||||
{
|
||||
return next_;
|
||||
return std::max(
|
||||
count_.load(std::memory_order::relaxed),
|
||||
maxCount_.load(std::memory_order::relaxed));
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string const&
|
||||
getName() const noexcept
|
||||
name() 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_;
|
||||
};
|
||||
|
||||
private:
|
||||
CountedObjects() noexcept;
|
||||
~CountedObjects() noexcept = default;
|
||||
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<int> count_;
|
||||
std::atomic<Counter*> head_;
|
||||
std::atomic<Counter*> head_ = nullptr;
|
||||
};
|
||||
|
||||
/** 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))
|
||||
;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
@@ -103,27 +184,37 @@ private:
|
||||
* 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
|
||||
{
|
||||
private:
|
||||
static auto&
|
||||
getCounter() noexcept
|
||||
{
|
||||
static CountedObjects::Counter kC{beast::typeName<Object>()};
|
||||
return kC;
|
||||
}
|
||||
static inline CountedObjects::Counter counter{boost::core::type_name<Object>()};
|
||||
|
||||
CountedObject() noexcept
|
||||
{
|
||||
getCounter().increment();
|
||||
counter.increment();
|
||||
}
|
||||
|
||||
CountedObject(CountedObject const&) noexcept
|
||||
{
|
||||
getCounter().increment();
|
||||
counter.increment();
|
||||
}
|
||||
|
||||
CountedObject&
|
||||
@@ -132,7 +223,7 @@ private:
|
||||
public:
|
||||
~CountedObject() noexcept
|
||||
{
|
||||
getCounter().decrement();
|
||||
counter.decrement();
|
||||
}
|
||||
|
||||
friend Object;
|
||||
|
||||
@@ -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,6 +16,7 @@
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <typeinfo>
|
||||
#include <vector>
|
||||
|
||||
#if BOOST_OS_LINUX
|
||||
@@ -335,7 +336,7 @@ public:
|
||||
}) != cfg.end())
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"SlabAllocatorSet<" + beast::typeName<Type>() + ">: duplicate slab size");
|
||||
"SlabAllocatorSet<" + boost::core::type_name<Type>() + ">: duplicate slab size");
|
||||
}
|
||||
|
||||
for (auto const& c : cfg)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/sanitizers.h>
|
||||
#include <xrpl/beast/type_name.h>
|
||||
|
||||
#include <boost/core/type_name.hpp>
|
||||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
@@ -55,7 +56,7 @@ Throw(Args&&... args)
|
||||
std::is_convertible_v<E*, std::exception*>, "Exception must derive from std::exception.");
|
||||
|
||||
E e(std::forward<Args>(args)...);
|
||||
logThrow(std::string("Throwing exception of type " + beast::typeName<E>() + ": ") + e.what());
|
||||
logThrow("Throwing exception of type " + boost::core::type_name<E>() + ": " + e.what());
|
||||
throw std::move(e);
|
||||
}
|
||||
|
||||
|
||||
200
include/xrpl/basics/enum_bitops.h
Normal file
200
include/xrpl/basics/enum_bitops.h
Normal file
@@ -0,0 +1,200 @@
|
||||
#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
|
||||
@@ -17,7 +17,7 @@
|
||||
namespace xrpl {
|
||||
|
||||
template <typename Key>
|
||||
std::size_t
|
||||
static std::size_t
|
||||
extract(Key const& key)
|
||||
{
|
||||
return key;
|
||||
|
||||
@@ -2,107 +2,212 @@
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h> // IWYU pragma: keep
|
||||
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// 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.
|
||||
/** Every value of @p Src can be represented by @p Dest.
|
||||
|
||||
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));
|
||||
Given two integral types, the cast is safe when the destination can
|
||||
hold every possible value of the source.
|
||||
|
||||
template <class Dest, class 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>)
|
||||
constexpr Dest
|
||||
safeCast(Src s) noexcept
|
||||
requires(std::is_integral_v<Dest> && std::is_integral_v<Src>)
|
||||
{
|
||||
static_assert(
|
||||
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");
|
||||
SafeToCast<Src, Dest>, "This cast is not value-preserving. Please use unsafeCast instead.");
|
||||
return static_cast<Dest>(s);
|
||||
}
|
||||
|
||||
template <class Dest, class Src>
|
||||
template <typename Dest, typename Src>
|
||||
requires(std::is_enum_v<Dest> && std::is_integral_v<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 <class Dest, class Src>
|
||||
template <typename Dest, typename Src>
|
||||
requires(std::is_integral_v<Dest> && std::is_enum_v<Src>)
|
||||
constexpr Dest
|
||||
safeCast(Src s) noexcept
|
||||
requires(std::is_integral_v<Dest> && std::is_enum_v<Src>)
|
||||
{
|
||||
return safeCast<Dest>(static_cast<std::underlying_type_t<Src>>(s));
|
||||
return safeCast<Dest>(std::to_underlying(s));
|
||||
}
|
||||
/** @} */
|
||||
|
||||
// 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.
|
||||
/** Integral-to-integral cast that is known to be narrowing or sign-erasing.
|
||||
|
||||
template <class Dest, class Src>
|
||||
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>)
|
||||
constexpr Dest
|
||||
unsafeCast(Src s) noexcept
|
||||
requires(std::is_integral_v<Dest> && std::is_integral_v<Src>)
|
||||
{
|
||||
static_assert(
|
||||
!SafeToCast<Src, Dest>,
|
||||
"Only unsafe if casting signed to unsigned or "
|
||||
"destination is too small");
|
||||
!SafeToCast<Src, Dest>, "This cast is value-preserving. Please use safeCast instead.");
|
||||
return static_cast<Dest>(s);
|
||||
}
|
||||
|
||||
template <class Dest, class Src>
|
||||
template <typename Dest, typename Src>
|
||||
requires(std::is_enum_v<Dest> && std::is_integral_v<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 <class Dest, class Src>
|
||||
template <typename Dest, typename Src>
|
||||
requires(std::is_integral_v<Dest> && std::is_enum_v<Src>)
|
||||
constexpr Dest
|
||||
unsafeCast(Src s) noexcept
|
||||
requires(std::is_integral_v<Dest> && std::is_enum_v<Src>)
|
||||
{
|
||||
return unsafeCast<Dest>(static_cast<std::underlying_type_t<Src>>(s));
|
||||
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));
|
||||
}
|
||||
/** @} */
|
||||
|
||||
/** 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>
|
||||
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>))
|
||||
inline Dest
|
||||
safeDowncast(Src* s) noexcept
|
||||
{
|
||||
#ifdef NDEBUG
|
||||
XRPL_ASSERT(
|
||||
s == nullptr || dynamic_cast<Dest>(s) != nullptr, "xrpl::safeDowncast : valid downcast");
|
||||
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>
|
||||
requires(
|
||||
std::is_lvalue_reference_v<Dest> && std::is_polymorphic_v<Src> &&
|
||||
std::derived_from<std::remove_reference_t<Dest>, Src>)
|
||||
inline Dest
|
||||
safeDowncast(Src& s) noexcept
|
||||
{
|
||||
#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)
|
||||
return *safeDowncast<std::add_pointer_t<std::remove_reference_t<Dest>>>(std::addressof(s));
|
||||
}
|
||||
/** @} */
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -4,88 +4,215 @@
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
|
||||
#include <boost/predef/architecture.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <concepts>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
#ifndef __aarch64__
|
||||
#if BOOST_ARCH_X86
|
||||
#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
|
||||
{
|
||||
#ifdef __aarch64__
|
||||
asm volatile("yield");
|
||||
#else
|
||||
#if BOOST_ARCH_X86
|
||||
_mm_pause();
|
||||
#elif BOOST_ARCH_ARM
|
||||
asm volatile("yield" ::: "memory");
|
||||
#else
|
||||
#error No implementation available for spinPause to use
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/** @{ */
|
||||
/**
|
||||
* 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)
|
||||
*/
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 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
|
||||
/** 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.
|
||||
*/
|
||||
template <class 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.
|
||||
*/
|
||||
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>
|
||||
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_;
|
||||
|
||||
@@ -94,120 +221,49 @@ public:
|
||||
PackedSpinlock&
|
||||
operator=(PackedSpinlock const&) = delete;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
/** 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.
|
||||
*/
|
||||
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)
|
||||
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);
|
||||
}())
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
try_lock() // NOLINT(readability-identifier-naming)
|
||||
try_lock() noexcept // NOLINT(readability-identifier-naming)
|
||||
{
|
||||
T expected = 0;
|
||||
|
||||
return lock_.compare_exchange_weak(
|
||||
expected,
|
||||
std::numeric_limits<T>::max(),
|
||||
std::memory_order_acquire,
|
||||
std::memory_order_relaxed);
|
||||
return (bits_.fetch_or(mask_, std::memory_order::acquire) & mask_) == 0;
|
||||
}
|
||||
|
||||
void
|
||||
lock()
|
||||
lock() noexcept
|
||||
{
|
||||
while (!try_lock())
|
||||
do
|
||||
{
|
||||
// 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 (lock_.load(std::memory_order_relaxed) != 0)
|
||||
// of contention by avoiding writes that are unlikely to grab
|
||||
// the requested lock.
|
||||
while ((bits_.load(std::memory_order::relaxed) & mask_) != 0)
|
||||
detail::spinPause();
|
||||
}
|
||||
} while (!try_lock());
|
||||
}
|
||||
|
||||
void
|
||||
unlock()
|
||||
unlock() noexcept
|
||||
{
|
||||
lock_.store(0, std::memory_order_release);
|
||||
bits_.fetch_and(~mask_, std::memory_order::release);
|
||||
}
|
||||
};
|
||||
/** @} */
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
#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
|
||||
@@ -20,7 +20,7 @@ namespace beast::unit_test {
|
||||
namespace detail {
|
||||
|
||||
template <class String>
|
||||
std::string
|
||||
static std::string
|
||||
makeReason(String const& reason, char const* file, int line)
|
||||
{
|
||||
std::string s(reason);
|
||||
|
||||
@@ -2,26 +2,24 @@
|
||||
|
||||
#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.
|
||||
* like t > 1 or t != 1 do not. Comparing against kZero expresses exactly that,
|
||||
* without constructing a T.
|
||||
*
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
|
||||
struct Zero
|
||||
{
|
||||
explicit Zero() = default;
|
||||
@@ -30,115 +28,44 @@ struct Zero
|
||||
inline constexpr Zero kZero{};
|
||||
|
||||
/**
|
||||
* Default implementation of signum calls the method on the class.
|
||||
* Default implementation of signum: call the member function.
|
||||
*/
|
||||
template <typename T>
|
||||
auto
|
||||
signum(T const& t)
|
||||
template <class T>
|
||||
requires requires(T const& t) {
|
||||
{ t.signum() } -> std::integral;
|
||||
}
|
||||
[[nodiscard]] constexpr auto
|
||||
signum(T const& t) noexcept(noexcept(t.signum()))
|
||||
{
|
||||
return t.signum();
|
||||
}
|
||||
|
||||
namespace detail::zero_helper {
|
||||
namespace detail {
|
||||
|
||||
// For argument dependent lookup to function properly, calls to signum must
|
||||
// be made from a namespace that does not include overloads of the function..
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
template <class T>
|
||||
auto
|
||||
callSignum(T const& 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)))
|
||||
{
|
||||
return signum(t);
|
||||
return signum(t) == 0;
|
||||
}
|
||||
|
||||
} // namespace detail::zero_helper
|
||||
|
||||
// Handle operators where T is on the left side using signum.
|
||||
|
||||
template <typename T>
|
||||
bool
|
||||
operator==(T const& t, Zero)
|
||||
template <detail::HasSignum T>
|
||||
[[nodiscard]] constexpr std::strong_ordering
|
||||
operator<=>(T const& t, Zero) noexcept(noexcept(signum(t)))
|
||||
{
|
||||
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;
|
||||
return signum(t) <=> 0;
|
||||
}
|
||||
|
||||
} // namespace beast
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
#include <xrpl/basics/enum_bitops.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/OwnerCounts.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
@@ -22,80 +22,39 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// Bitwise flag enum with existing operator overloads
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-use-enum-class)
|
||||
enum ApplyFlags : std::uint32_t {
|
||||
TapNone = 0x00,
|
||||
enum class ApplyFlags : std::uint32_t {
|
||||
None = 0x00,
|
||||
|
||||
// This is a local transaction with the
|
||||
// fail_hard flag set.
|
||||
TapFailHard = 0x10,
|
||||
FailHard = 0x10,
|
||||
|
||||
// This is not the transaction's last pass
|
||||
// Transaction can be retried, soft failures allowed
|
||||
TapRetry = 0x20,
|
||||
Retry = 0x20,
|
||||
|
||||
// Transaction came from a privileged source
|
||||
TapUnlimited = 0x400,
|
||||
Unlimited = 0x400,
|
||||
|
||||
// Transaction is executing as part of a batch
|
||||
TapBatch = 0x800,
|
||||
Batch = 0x800,
|
||||
|
||||
// Transaction shouldn't be applied
|
||||
// Signatures shouldn't be checked
|
||||
TapDryRun = 0x1000,
|
||||
DryRun = 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).
|
||||
TapProposal = 0x2000
|
||||
Proposal = 0x2000
|
||||
};
|
||||
|
||||
constexpr ApplyFlags
|
||||
operator|(ApplyFlags const& lhs, ApplyFlags const& rhs)
|
||||
template <>
|
||||
struct enum_bitops::OptIn<ApplyFlags> : std::true_type
|
||||
{
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
#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 {
|
||||
|
||||
@@ -31,52 +25,6 @@ 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>;
|
||||
|
||||
@@ -1,111 +1,32 @@
|
||||
#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/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/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/XRPAmount.h>
|
||||
#include <xrpl/protocol/UintTypes.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(
|
||||
@@ -121,7 +42,7 @@ escrowUnlockApplyHelper(
|
||||
beast::Journal journal);
|
||||
|
||||
template <>
|
||||
TER
|
||||
inline TER
|
||||
escrowUnlockApplyHelper<Issue>(
|
||||
ApplyViewContext ctx,
|
||||
Rate lockedRate,
|
||||
@@ -132,10 +53,137 @@ escrowUnlockApplyHelper<Issue>(
|
||||
AccountID const& sender,
|
||||
AccountID const& receiver,
|
||||
bool createAsset,
|
||||
beast::Journal journal);
|
||||
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;
|
||||
}
|
||||
|
||||
template <>
|
||||
TER
|
||||
inline TER
|
||||
escrowUnlockApplyHelper<MPTIssue>(
|
||||
ApplyViewContext ctx,
|
||||
Rate lockedRate,
|
||||
@@ -146,6 +194,82 @@ escrowUnlockApplyHelper<MPTIssue>(
|
||||
AccountID const& sender,
|
||||
AccountID const& receiver,
|
||||
bool createAsset,
|
||||
beast::Journal journal);
|
||||
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
|
||||
|
||||
@@ -3,13 +3,7 @@
|
||||
#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>
|
||||
|
||||
@@ -18,66 +12,18 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Validate the token amount of a PaymentChannelCreate or PaymentChannelFund
|
||||
* transaction during preflight.
|
||||
*
|
||||
* @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
|
||||
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.
|
||||
* @param slep The SLE for the PayChannel object to close.
|
||||
* @param view The apply view in which ledger state modifications are made.
|
||||
* @param key The ledger key identifying the PayChannel entry.
|
||||
* @param j Journal used for fatal-level diagnostic messages.
|
||||
* @return tesSUCCESS on success; tefBAD_LEDGER if a directory removal
|
||||
* fails; tefINTERNAL if the source account SLE cannot be found.
|
||||
*/
|
||||
TER
|
||||
closeChannel(
|
||||
SLE::Ref slep,
|
||||
ApplyViewContext ctx,
|
||||
UInt256 const& key,
|
||||
AccountID const& txAccount,
|
||||
beast::Journal j);
|
||||
closeChannel(SLE::Ref slep, ApplyView& view, UInt256 const& key, beast::Journal j);
|
||||
|
||||
/**
|
||||
* Add two uint32_t values with saturation at UINT32_MAX.
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/nodestore/NodeObject.h>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl::node_store {
|
||||
|
||||
@@ -23,7 +25,7 @@ public:
|
||||
/**
|
||||
* Construct the decoded blob from raw data.
|
||||
*/
|
||||
DecodedBlob(void const* key, void const* value, int valueBytes);
|
||||
DecodedBlob(std::optional<uint256> key, void const* value, int valueBytes);
|
||||
|
||||
/**
|
||||
* Determine if the decoding was successful.
|
||||
@@ -43,10 +45,10 @@ public:
|
||||
private:
|
||||
bool success_{false};
|
||||
|
||||
void const* key_;
|
||||
uint256 key_;
|
||||
NodeObjectType objectType_{NodeObjectType::Unknown};
|
||||
unsigned char const* objectData_{nullptr};
|
||||
int dataBytes_;
|
||||
int dataBytes_ = 0;
|
||||
};
|
||||
|
||||
} // namespace xrpl::node_store
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/nodestore/NodeObject.h>
|
||||
|
||||
@@ -37,11 +38,6 @@ namespace xrpl::node_store {
|
||||
|
||||
class EncodedBlob
|
||||
{
|
||||
/**
|
||||
* The 32-byte key of the serialized object.
|
||||
*/
|
||||
std::array<std::uint8_t, 32> key_{};
|
||||
|
||||
/**
|
||||
* A pre-allocated buffer for the serialized object.
|
||||
*
|
||||
@@ -65,6 +61,11 @@ class EncodedBlob
|
||||
*/
|
||||
std::uint8_t* const ptr_;
|
||||
|
||||
/**
|
||||
* The 32-byte key of the serialized object.
|
||||
*/
|
||||
uint256 key_;
|
||||
|
||||
public:
|
||||
explicit EncodedBlob(std::shared_ptr<NodeObject> const& obj)
|
||||
: size_([&obj]() {
|
||||
@@ -76,11 +77,12 @@ public:
|
||||
return obj->getData().size() + 9;
|
||||
}())
|
||||
, ptr_((size_ <= payload_.size()) ? payload_.data() : new std::uint8_t[size_])
|
||||
, key_(obj->getHash())
|
||||
{
|
||||
std::fill_n(ptr_, 8, std::uint8_t{0});
|
||||
ptr_[8] = static_cast<std::uint8_t>(obj->getType());
|
||||
|
||||
std::copy_n(obj->getData().data(), obj->getData().size(), ptr_ + 9);
|
||||
std::copy_n(obj->getHash().data(), obj->getHash().size(), key_.data());
|
||||
}
|
||||
|
||||
~EncodedBlob()
|
||||
@@ -95,10 +97,10 @@ public:
|
||||
delete[] ptr_;
|
||||
}
|
||||
|
||||
[[nodiscard]] void const*
|
||||
[[nodiscard]] uint256 const&
|
||||
getKey() const noexcept
|
||||
{
|
||||
return static_cast<void const*>(key_.data());
|
||||
return key_;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::size_t
|
||||
|
||||
@@ -61,14 +61,22 @@ parseBase58(std::string const& s);
|
||||
/**
|
||||
* A special account that's used as the "issuer" for XRP.
|
||||
*/
|
||||
AccountID const&
|
||||
xrpAccount();
|
||||
constexpr inline AccountID const&
|
||||
xrpAccount() noexcept
|
||||
{
|
||||
static constexpr AccountID kAccount(beast::kZero);
|
||||
return kAccount;
|
||||
}
|
||||
|
||||
/**
|
||||
* A placeholder for empty accounts.
|
||||
*/
|
||||
AccountID const&
|
||||
noAccount();
|
||||
constexpr inline AccountID const&
|
||||
noAccount() noexcept
|
||||
{
|
||||
static constexpr AccountID kAccount = xrpAccount().next();
|
||||
return kAccount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert hex or base58 string to AccountID.
|
||||
@@ -80,10 +88,10 @@ bool
|
||||
toIssuer(AccountID&, std::string const&);
|
||||
|
||||
// DEPRECATED Should be checking the currency or native flag
|
||||
inline bool
|
||||
isXRP(AccountID const& c)
|
||||
constexpr inline bool
|
||||
isXRP(AccountID const& c) noexcept
|
||||
{
|
||||
return c == beast::kZero;
|
||||
return c == xrpAccount();
|
||||
}
|
||||
|
||||
// DEPRECATED
|
||||
@@ -101,22 +109,6 @@ 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
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -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_(beast::typeName<Derived>())
|
||||
KnownFormats() : name_(boost::core::type_name<Derived>())
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
28
include/xrpl/protocol/LedgerShortcut.h
Normal file
28
include/xrpl/protocol/LedgerShortcut.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#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
|
||||
@@ -2,17 +2,11 @@
|
||||
|
||||
#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)
|
||||
{
|
||||
@@ -21,23 +15,4 @@ 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
|
||||
|
||||
@@ -780,39 +780,6 @@ 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.
|
||||
*
|
||||
|
||||
@@ -33,7 +33,20 @@ public:
|
||||
|
||||
STBitString(SField const& n);
|
||||
STBitString(value_type const& v);
|
||||
|
||||
template <typename Tag>
|
||||
requires(!std::is_void_v<Tag>)
|
||||
STBitString(BaseUInt<Bits, Tag> const& v) : value_(v)
|
||||
{
|
||||
}
|
||||
|
||||
STBitString(SField const& n, value_type const& v);
|
||||
template <typename Tag>
|
||||
requires(!std::is_void_v<Tag>)
|
||||
STBitString(SField const& n, BaseUInt<Bits, Tag> const& v) : STBase(n), value_(v)
|
||||
{
|
||||
}
|
||||
|
||||
STBitString(SerialIter& sit, SField const& name);
|
||||
|
||||
[[nodiscard]] SerializedTypeID
|
||||
@@ -166,7 +179,7 @@ template <typename Tag>
|
||||
void
|
||||
STBitString<Bits>::setValue(BaseUInt<Bits, Tag> const& v)
|
||||
{
|
||||
value_ = v;
|
||||
value_ = value_type{v};
|
||||
}
|
||||
|
||||
template <int Bits>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#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>
|
||||
@@ -15,6 +16,7 @@
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -22,18 +24,8 @@ namespace xrpl {
|
||||
|
||||
class STPathElement final : public CountedObject<STPathElement>
|
||||
{
|
||||
unsigned int type_;
|
||||
AccountID accountID_;
|
||||
PathAsset assetID_;
|
||||
AccountID issuerID_;
|
||||
|
||||
bool isOffer_;
|
||||
std::size_t hashValue_;
|
||||
|
||||
public:
|
||||
// Bitwise values (typeCurrency | typeMPT)
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-use-enum-class)
|
||||
enum Type {
|
||||
enum class Type : std::uint8_t {
|
||||
TypeNone = 0x00,
|
||||
TypeAccount = 0x01, // Rippling through an account (vs taking an offer).
|
||||
TypeCurrency = 0x10, // Currency follows.
|
||||
@@ -45,6 +37,8 @@ public:
|
||||
// Combination of all types.
|
||||
};
|
||||
|
||||
using enum Type;
|
||||
|
||||
STPathElement();
|
||||
STPathElement(STPathElement const&) = default;
|
||||
STPathElement&
|
||||
@@ -62,42 +56,42 @@ public:
|
||||
bool forceAsset = false);
|
||||
|
||||
STPathElement(
|
||||
unsigned int uType,
|
||||
Type type,
|
||||
AccountID const& account,
|
||||
PathAsset const& asset,
|
||||
AccountID const& issuer);
|
||||
|
||||
[[nodiscard]] std::uint32_t
|
||||
getNodeType() const;
|
||||
[[nodiscard]] Type
|
||||
getNodeType() const noexcept;
|
||||
|
||||
[[nodiscard]] bool
|
||||
isOffer() const;
|
||||
isOffer() const noexcept;
|
||||
|
||||
[[nodiscard]] bool
|
||||
isAccount() const;
|
||||
isAccount() const noexcept;
|
||||
|
||||
[[nodiscard]] bool
|
||||
hasIssuer() const;
|
||||
hasIssuer() const noexcept;
|
||||
|
||||
[[nodiscard]] bool
|
||||
hasCurrency() const;
|
||||
hasCurrency() const noexcept;
|
||||
|
||||
[[nodiscard]] bool
|
||||
hasMPT() const;
|
||||
hasMPT() const noexcept;
|
||||
|
||||
[[nodiscard]] bool
|
||||
hasAsset() const;
|
||||
hasAsset() const noexcept;
|
||||
|
||||
[[nodiscard]] bool
|
||||
isNone() const;
|
||||
isNone() const noexcept;
|
||||
|
||||
// Nodes are either an account ID or a offer prefix. Offer prefixs denote a
|
||||
// class of offers.
|
||||
[[nodiscard]] AccountID const&
|
||||
getAccountID() const;
|
||||
getAccountID() const noexcept;
|
||||
|
||||
[[nodiscard]] PathAsset const&
|
||||
getPathAsset() const;
|
||||
getPathAsset() const noexcept;
|
||||
|
||||
[[nodiscard]] Currency const&
|
||||
getCurrency() const;
|
||||
@@ -106,17 +100,35 @@ public:
|
||||
getMPTID() const;
|
||||
|
||||
[[nodiscard]] AccountID const&
|
||||
getIssuerID() const;
|
||||
getIssuerID() const noexcept;
|
||||
|
||||
[[nodiscard]] bool
|
||||
isType(Type const& pe) const;
|
||||
isType(Type pe) const noexcept;
|
||||
|
||||
bool
|
||||
operator==(STPathElement const& t) 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_;
|
||||
}
|
||||
|
||||
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>
|
||||
@@ -124,7 +136,7 @@ void
|
||||
hash_append(Hasher& h, STPathElement const& e) noexcept
|
||||
{
|
||||
using beast::hash_append;
|
||||
hash_append(h, (e.getNodeType() & STPathElement::TypeAccount) != 0u);
|
||||
hash_append(h, e.isType(STPathElement::TypeAccount));
|
||||
hash_append(h, e.getAccountID());
|
||||
hash_append(h, e.getPathAsset());
|
||||
hash_append(h, e.getIssuerID());
|
||||
@@ -404,11 +416,11 @@ inline STPathElement::STPathElement(
|
||||
}
|
||||
|
||||
inline STPathElement::STPathElement(
|
||||
unsigned int uType,
|
||||
Type type,
|
||||
AccountID const& account,
|
||||
PathAsset const& asset,
|
||||
AccountID const& issuer)
|
||||
: type_(uType)
|
||||
: type_(type)
|
||||
, accountID_(account)
|
||||
, assetID_(asset)
|
||||
, issuerID_(issuer)
|
||||
@@ -423,56 +435,56 @@ inline STPathElement::STPathElement(
|
||||
hashValue_ = getHash(*this);
|
||||
}
|
||||
|
||||
inline std::uint32_t
|
||||
STPathElement::getNodeType() const
|
||||
inline STPathElement::Type
|
||||
STPathElement::getNodeType() const noexcept
|
||||
{
|
||||
return type_;
|
||||
}
|
||||
|
||||
inline bool
|
||||
STPathElement::isOffer() const
|
||||
STPathElement::isOffer() const noexcept
|
||||
{
|
||||
return isOffer_;
|
||||
}
|
||||
|
||||
inline bool
|
||||
STPathElement::isAccount() const
|
||||
STPathElement::isAccount() const noexcept
|
||||
{
|
||||
return !isOffer();
|
||||
}
|
||||
|
||||
inline bool
|
||||
STPathElement::isType(Type const& pe) const
|
||||
STPathElement::isType(Type pe) const noexcept
|
||||
{
|
||||
return (type_ & pe) != 0u;
|
||||
return (type_ & pe) != STPathElement::TypeNone;
|
||||
}
|
||||
|
||||
inline bool
|
||||
STPathElement::hasIssuer() const
|
||||
STPathElement::hasIssuer() const noexcept
|
||||
{
|
||||
return isType(STPathElement::TypeIssuer);
|
||||
}
|
||||
|
||||
inline bool
|
||||
STPathElement::hasCurrency() const
|
||||
STPathElement::hasCurrency() const noexcept
|
||||
{
|
||||
return isType(STPathElement::TypeCurrency);
|
||||
}
|
||||
|
||||
inline bool
|
||||
STPathElement::hasMPT() const
|
||||
STPathElement::hasMPT() const noexcept
|
||||
{
|
||||
return isType(STPathElement::TypeMpt);
|
||||
}
|
||||
|
||||
inline bool
|
||||
STPathElement::hasAsset() const
|
||||
STPathElement::hasAsset() const noexcept
|
||||
{
|
||||
return isType(STPathElement::TypeAsset);
|
||||
}
|
||||
|
||||
inline bool
|
||||
STPathElement::isNone() const
|
||||
STPathElement::isNone() const noexcept
|
||||
{
|
||||
return getNodeType() == STPathElement::TypeNone;
|
||||
}
|
||||
@@ -480,13 +492,13 @@ STPathElement::isNone() const
|
||||
// Nodes are either an account ID or a offer prefix. Offer prefixs denote a
|
||||
// class of offers.
|
||||
inline AccountID const&
|
||||
STPathElement::getAccountID() const
|
||||
STPathElement::getAccountID() const noexcept
|
||||
{
|
||||
return accountID_;
|
||||
}
|
||||
|
||||
inline PathAsset const&
|
||||
STPathElement::getPathAsset() const
|
||||
STPathElement::getPathAsset() const noexcept
|
||||
{
|
||||
return assetID_;
|
||||
}
|
||||
@@ -504,18 +516,11 @@ STPathElement::getMPTID() const
|
||||
}
|
||||
|
||||
inline AccountID const&
|
||||
STPathElement::getIssuerID() const
|
||||
STPathElement::getIssuerID() const noexcept
|
||||
{
|
||||
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))
|
||||
|
||||
@@ -596,7 +596,7 @@ template <std::size_t Bits, class Tag>
|
||||
BaseUInt<Bits, Tag>
|
||||
SerialIter::getBitString()
|
||||
{
|
||||
auto const n = Bits / 8;
|
||||
constexpr auto n = BaseUInt<Bits, Tag>::size();
|
||||
|
||||
if (remain_ < n)
|
||||
Throw<std::runtime_error>("invalid SerialIter getBitString");
|
||||
@@ -607,7 +607,7 @@ SerialIter::getBitString()
|
||||
used_ += n;
|
||||
remain_ -= n;
|
||||
|
||||
return BaseUInt<Bits, Tag>::fromVoid(x);
|
||||
return BaseUInt<Bits, Tag>{std::span<std::uint8_t const, n>{x, n}};
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace xrpl {
|
||||
// Various protocol and system specific constant globals.
|
||||
|
||||
/* The name of the system. */
|
||||
static inline std::string const&
|
||||
inline std::string const&
|
||||
systemName()
|
||||
{
|
||||
static std::string const kName = "xrpld";
|
||||
@@ -52,11 +52,10 @@ isLegalAmountSigned(XRPAmount const& amount)
|
||||
}
|
||||
|
||||
/* The currency code for the native currency. */
|
||||
static inline std::string const&
|
||||
inline std::string
|
||||
systemCurrencyCode()
|
||||
{
|
||||
static std::string const kCode = "XRP";
|
||||
return kCode;
|
||||
return "XRP";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -61,26 +61,40 @@ using Domain = BaseUInt<256>;
|
||||
/**
|
||||
* XRP currency.
|
||||
*/
|
||||
Currency const&
|
||||
xrpCurrency();
|
||||
constexpr inline Currency const&
|
||||
xrpCurrency() noexcept
|
||||
{
|
||||
static constexpr Currency const kCurrency(beast::kZero);
|
||||
return kCurrency;
|
||||
}
|
||||
|
||||
/**
|
||||
* A placeholder for empty currencies.
|
||||
*/
|
||||
Currency const&
|
||||
noCurrency();
|
||||
constexpr inline Currency const&
|
||||
noCurrency() noexcept
|
||||
{
|
||||
static constexpr Currency const kCurrency(xrpCurrency().next());
|
||||
return kCurrency;
|
||||
}
|
||||
|
||||
/**
|
||||
* We deliberately disallow the currency that looks like "XRP" because too
|
||||
* many people were using it instead of the correct XRP currency.
|
||||
*
|
||||
* Note that this doesn't catch "xRP" or "xrp" or other case variations.
|
||||
*/
|
||||
Currency const&
|
||||
badCurrency();
|
||||
|
||||
inline bool
|
||||
isXRP(Currency const& c)
|
||||
constexpr inline Currency const&
|
||||
badCurrency() noexcept
|
||||
{
|
||||
return c == beast::kZero;
|
||||
static constexpr Currency kCurrency{"0000000000000000000000005852500000000000"};
|
||||
return kCurrency;
|
||||
}
|
||||
|
||||
constexpr inline bool
|
||||
isXRP(Currency const& c) noexcept
|
||||
{
|
||||
return c == xrpCurrency();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,7 +112,7 @@ to_string(Currency const& c);
|
||||
* to rewrite some unit test code.
|
||||
*/
|
||||
bool
|
||||
toCurrency(Currency&, std::string const&);
|
||||
toCurrency(Currency&, std::string_view);
|
||||
|
||||
/**
|
||||
* Tries to convert a string to a Currency, returns noCurrency() on failure.
|
||||
@@ -107,8 +121,7 @@ toCurrency(Currency&, std::string const&);
|
||||
* unfortunate; changing this will require very careful checking
|
||||
* everywhere and may mean having to rewrite some unit test code.
|
||||
*/
|
||||
Currency
|
||||
toCurrency(std::string const&);
|
||||
Currency toCurrency(std::string_view);
|
||||
|
||||
inline std::ostream&
|
||||
operator<<(std::ostream& os, Currency const& x)
|
||||
|
||||
@@ -21,7 +21,6 @@ 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)
|
||||
|
||||
@@ -376,8 +376,6 @@ LEDGER_ENTRY(ltPAYCHAN, 0x0078, PayChannel, payment_channel, ({
|
||||
{sfPreviousTxnID, SoeRequired},
|
||||
{sfPreviousTxnLgrSeq, SoeRequired},
|
||||
{sfDestinationNode, SoeOptional},
|
||||
{sfTransferRate, SoeOptional},
|
||||
{sfIssuerNode, SoeOptional},
|
||||
}))
|
||||
|
||||
/** The ledger object which tracks the AMM.
|
||||
|
||||
@@ -182,7 +182,7 @@ TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet,
|
||||
#endif
|
||||
TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, ({.delegable = Delegation::Delegable}), ({
|
||||
{sfDestination, SoeRequired},
|
||||
{sfAmount, SoeRequired, SoeMptSupported},
|
||||
{sfAmount, SoeRequired},
|
||||
{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, SoeMptSupported},
|
||||
{sfAmount, SoeRequired},
|
||||
{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, SoeMptSupported},
|
||||
{sfBalance, SoeOptional, SoeMptSupported},
|
||||
{sfAmount, SoeOptional},
|
||||
{sfBalance, SoeOptional},
|
||||
{sfSignature, SoeOptional},
|
||||
{sfPublicKey, SoeOptional},
|
||||
{sfCredentialIDs, SoeOptional},
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <type_traits>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -182,7 +183,7 @@ public:
|
||||
operator result_type() noexcept
|
||||
{
|
||||
auto const digest = Sha512Hasher::result_type(h_);
|
||||
return result_type::fromVoid(digest.data());
|
||||
return result_type{std::span{digest}.first<result_type::size()>()};
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -392,6 +392,7 @@ 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
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <span>
|
||||
|
||||
namespace xrpl::nft {
|
||||
|
||||
@@ -98,7 +99,8 @@ getTaxon(UInt256 const& id)
|
||||
inline AccountID
|
||||
getIssuer(UInt256 const& id)
|
||||
{
|
||||
return AccountID::fromVoid(id.data() + 4);
|
||||
return AccountID{
|
||||
std::span<unsigned char const, uint256::size()>{id}.subspan<4, AccountID::size()>()};
|
||||
}
|
||||
|
||||
} // namespace xrpl::nft
|
||||
|
||||
@@ -2,13 +2,10 @@
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace xrpl::nft {
|
||||
|
||||
// NFT directory pages order their contents based only on the low 96 bits of
|
||||
// the NFToken value. This mask provides easy access to the necessary mask.
|
||||
constexpr UInt256 kPageMask(
|
||||
std::string_view("0000000000000000000000000000000000000000ffffffffffffffffffffffff"));
|
||||
constexpr UInt256 kPageMask{"0000000000000000000000000000000000000000ffffffffffffffffffffffff"};
|
||||
|
||||
} // namespace xrpl::nft
|
||||
|
||||
@@ -287,54 +287,6 @@ 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);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -556,28 +508,6 @@ 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.
|
||||
|
||||
@@ -60,7 +60,6 @@ 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]]
|
||||
@@ -87,7 +86,6 @@ 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]]
|
||||
@@ -248,7 +246,6 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Set sfAmount (SoeOptional)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
PaymentChannelClaimBuilder&
|
||||
@@ -260,7 +257,6 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Set sfBalance (SoeOptional)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
PaymentChannelClaimBuilder&
|
||||
|
||||
@@ -60,7 +60,6 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Get sfAmount (SoeRequired)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
@@ -208,7 +207,6 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Set sfAmount (SoeRequired)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
PaymentChannelCreateBuilder&
|
||||
|
||||
@@ -60,7 +60,6 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Get sfAmount (SoeRequired)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
* @return The field value.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
@@ -156,7 +155,6 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Set sfAmount (SoeRequired)
|
||||
* @note This field supports MPT (Multi-Purpose Token) amounts.
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
PaymentChannelFundBuilder&
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#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>
|
||||
@@ -104,6 +105,21 @@ 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;
|
||||
|
||||
@@ -51,7 +51,8 @@ public:
|
||||
beast::Journal journal = beast::Journal{beast::Journal::getNullSink()})
|
||||
: ApplyContext(registry, base, std::nullopt, tx, preclaimResult, baseFee, flags, journal)
|
||||
{
|
||||
XRPL_ASSERT((flags & TapBatch) == 0, "Batch apply flag should not be set");
|
||||
XRPL_ASSERT(
|
||||
(flags & ApplyFlags::Batch) == ApplyFlags::None, "Batch apply flag should not be set");
|
||||
}
|
||||
|
||||
std::reference_wrapper<ServiceRegistry> registry;
|
||||
|
||||
@@ -59,10 +59,11 @@ public:
|
||||
, parentBatchId(parentBatchId)
|
||||
, j(j)
|
||||
{
|
||||
XRPL_ASSERT((flags & TapBatch) == TapBatch, "Batch apply flag should be set");
|
||||
XRPL_ASSERT(
|
||||
(flags & ApplyFlags::Batch) == ApplyFlags::Batch, "Batch apply flag should be set");
|
||||
XRPL_ASSERT_IF(
|
||||
(flags & TapProposal) != TapNone,
|
||||
(flags & TapDryRun) != TapNone,
|
||||
(flags & ApplyFlags::Proposal) != ApplyFlags::None,
|
||||
(flags & ApplyFlags::DryRun) != ApplyFlags::None,
|
||||
"xrpl::PreflightContext : proposal preflight implies dry run");
|
||||
}
|
||||
|
||||
@@ -74,10 +75,11 @@ public:
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
: registry(registry), tx(tx), rules(std::move(rules)), flags(flags), j(j)
|
||||
{
|
||||
XRPL_ASSERT((flags & TapBatch) == 0, "Batch apply flag should not be set");
|
||||
XRPL_ASSERT(
|
||||
(flags & ApplyFlags::Batch) == ApplyFlags::None, "Batch apply flag should not be set");
|
||||
XRPL_ASSERT_IF(
|
||||
(flags & TapProposal) != TapNone,
|
||||
(flags & TapDryRun) != TapNone,
|
||||
(flags & ApplyFlags::Proposal) != ApplyFlags::None,
|
||||
(flags & ApplyFlags::DryRun) != ApplyFlags::None,
|
||||
"xrpl::PreflightContext : proposal preflight implies dry run");
|
||||
}
|
||||
|
||||
@@ -116,7 +118,7 @@ public:
|
||||
, j(j)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
parentBatchId.has_value() == ((flags & TapBatch) == TapBatch),
|
||||
parentBatchId.has_value() == ((flags & ApplyFlags::Batch) == ApplyFlags::Batch),
|
||||
"Parent Batch ID should be set if batch apply flag is set");
|
||||
}
|
||||
|
||||
@@ -129,7 +131,8 @@ public:
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
: PreclaimContext(registry, view, preflightResult, tx, flags, std::nullopt, j)
|
||||
{
|
||||
XRPL_ASSERT((flags & TapBatch) == 0, "Batch apply flag should not be set");
|
||||
XRPL_ASSERT(
|
||||
(flags & ApplyFlags::Batch) == ApplyFlags::None, "Batch apply flag should not be set");
|
||||
}
|
||||
|
||||
PreclaimContext&
|
||||
|
||||
@@ -41,7 +41,7 @@ struct ApplyResult
|
||||
inline bool
|
||||
isTecClaimHardFail(TER ter, ApplyFlags flags)
|
||||
{
|
||||
return isTecClaim(ter) && ((flags & TapRetry) == 0u);
|
||||
return isTecClaim(ter) && ((flags & ApplyFlags::Retry) == ApplyFlags::None);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,14 +42,6 @@ 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
|
||||
|
||||
@@ -23,9 +23,6 @@ public:
|
||||
static TxConsequences
|
||||
makeTxConsequences(PreflightContext const& ctx);
|
||||
|
||||
static bool
|
||||
checkExtraFeatures(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
|
||||
@@ -23,15 +23,9 @@ 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;
|
||||
|
||||
|
||||
@@ -57,12 +57,6 @@ 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,
|
||||
|
||||
@@ -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.33.0
|
||||
/nix/store/rp0zf22rx3iwnhxp4d53ncirn4lzg68j-conan-2.33.0/bin/conan
|
||||
Conan version 2.32.0
|
||||
/nix/store/921jqsgbilixmr3xchj95si0jl8pi13z-conan-2.32.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 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 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++
|
||||
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
|
||||
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
|
||||
✅ ClangBuildAnalyzer
|
||||
ClangBuildAnalyzer 1.6.0
|
||||
/nix/store/c7jjnw29ra78jdlzjww2m374izhy05n8-clangbuildanalyzer-1.6.0/bin/ClangBuildAnalyzer
|
||||
@@ -39,9 +39,6 @@ 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
|
||||
@@ -67,23 +64,23 @@ Development tooling:
|
||||
Zip 3.0
|
||||
/nix/store/dvawr36npnad4xa6dxi2ss0fw44aa82r-zip-3.0/bin/zip
|
||||
✅ clang-apply-replacements
|
||||
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-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-format
|
||||
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-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-tidy
|
||||
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
|
||||
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
|
||||
✅ dot
|
||||
dot - graphviz version 15.1.1 (0)
|
||||
/nix/store/mc99a6bpbk2waym2cnw6ifmxli4ndb4x-graphviz-15.1.1/bin/dot
|
||||
@@ -110,10 +107,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/zjbzr154xan5xg9pmjiv0zkwklyb94wv-run-clang-tidy/bin/run-clang-tidy
|
||||
✅ run-clang-tidy-23
|
||||
/nix/store/3xss1mm3mh1hzcmg182na6ki1p5rnxf1-run-clang-tidy/bin/run-clang-tidy
|
||||
✅ run-clang-tidy-22
|
||||
usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
|
||||
/nix/store/svhfrpgx2j581brsqmkpav9b286v22yy-run-clang-tidy-23/bin/run-clang-tidy-23
|
||||
/nix/store/w68r9z07hcq4fwbfyq3yvc7qxm8aqfbl-run-clang-tidy-22/bin/run-clang-tidy-22
|
||||
|
||||
Rust toolchain:
|
||||
✅ cargo
|
||||
@@ -146,4 +143,4 @@ Rust toolchain:
|
||||
|
||||
Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set).
|
||||
|
||||
✅ All 46 checked tools are present and runnable.
|
||||
✅ All 45 checked tools are present and runnable.
|
||||
|
||||
@@ -2,176 +2,173 @@ Detected OS: linux (Linux x86_64)
|
||||
|
||||
Core build tools:
|
||||
✅ cmake
|
||||
cmake version 4.4.3
|
||||
/nix/store/jcvvpih1046akxcwh9hdiak5q24jqcf0-cmake-4.4.3/bin/cmake
|
||||
cmake version 4.1.2
|
||||
/nix/store/r9941n32g4wyvggz2703dlplbdq8a6rd-cmake-4.1.2/bin/cmake
|
||||
✅ conan
|
||||
Conan version 2.32.0
|
||||
/nix/store/q0g5grbh7zm8gfhhnq5h6fm1rbs9yv3n-conan-2.32.0/bin/conan
|
||||
Conan version 2.28.1
|
||||
/nix/store/lxny9y4jvjdws7hgz1mygvb7hjrpmna5-conan-2.28.1/bin/conan
|
||||
✅ git
|
||||
git version 2.55.0
|
||||
/nix/store/lg46w7hrjx7kylsh6645c9l975i8h43b-git-2.55.0/bin/git
|
||||
git version 2.54.0
|
||||
/nix/store/bcnisk3ydfgv26v2gw3zlky24g00yww2-git-2.54.0/bin/git
|
||||
✅ python3
|
||||
Python 3.14.7
|
||||
/nix/store/lb41b0anx1f98y9y5s9mdv97gjgsq740-python3-3.14.7/bin/python3.14
|
||||
Python 3.13.13
|
||||
/nix/store/60m4rxhg2fldqaak400c0lry96ijrzqn-python3-3.13.13/bin/python3.13
|
||||
|
||||
Development tooling:
|
||||
✅ ccache
|
||||
ccache version 4.13.6
|
||||
/nix/store/p1a1s700dqsw1b3b8d2ba64nqw68zgpy-ccache-4.13.6/bin/ccache
|
||||
/nix/store/c9wwl7s5i6rsfwvf4v0xbbmzx5m6jgfr-ccache-4.13.6/bin/ccache
|
||||
✅ clang
|
||||
clang version 22.1.8
|
||||
/nix/store/dggxva67vs7virgsmd6xkwd6p7zsc5pn-clang-wrapper-22.1.8/bin/clang
|
||||
clang version 22.1.7
|
||||
/nix/store/ff0hrp9r9i3pa5arkdw0sgmzp8d576qi-clang-wrapper-22.1.7/bin/clang
|
||||
✅ clang-22
|
||||
clang version 22.1.8
|
||||
/nix/store/ckz6y10v5dbyswpf9f6hd8kjfv84f920-clang-22/bin/clang-22
|
||||
clang version 22.1.7
|
||||
/nix/store/dagc2rq44gfbr7w7yvvqca3yqpc9gqbq-clang-22/bin/clang-22
|
||||
✅ clang++
|
||||
clang version 22.1.8
|
||||
/nix/store/dggxva67vs7virgsmd6xkwd6p7zsc5pn-clang-wrapper-22.1.8/bin/clang++
|
||||
clang version 22.1.7
|
||||
/nix/store/ff0hrp9r9i3pa5arkdw0sgmzp8d576qi-clang-wrapper-22.1.7/bin/clang++
|
||||
✅ clang++-22
|
||||
clang version 22.1.8
|
||||
/nix/store/lsishk8fwbdmlwr5fbwyd3yhhjsm67qr-clang++-22/bin/clang++-22
|
||||
clang version 22.1.7
|
||||
/nix/store/l5m8clin1npl605wdkd8mr18ggxww3z4-clang++-22/bin/clang++-22
|
||||
✅ ClangBuildAnalyzer
|
||||
ClangBuildAnalyzer 1.6.0
|
||||
/nix/store/gvx8im1c89vspwb8ixa91lr1dagbi241-clangbuildanalyzer-1.6.0/bin/ClangBuildAnalyzer
|
||||
/nix/store/bshlmn8fqw55nsnm581xqlfbahfkykxx-clangbuildanalyzer-1.6.0/bin/ClangBuildAnalyzer
|
||||
✅ 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
|
||||
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
|
||||
✅ 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
|
||||
file-5.47
|
||||
/nix/store/bizyfqdw0h67wzqmp10knmf9s2pqahdb-file-5.47/bin/file
|
||||
✅ less
|
||||
less 710 (PCRE2 regular expressions)
|
||||
/nix/store/zi4d0awnc6crz18s177bv9y2yz9al3lq-less-710/bin/less
|
||||
less 692 (PCRE2 regular expressions)
|
||||
/nix/store/c6bacbn93qg4a7g9n4czww8rg24dvysr-less-692/bin/less
|
||||
✅ make
|
||||
GNU Make 4.4.1
|
||||
/nix/store/z565rgmn9vccv2h0g87z725pw610jmm8-gnumake-4.4.1/bin/make
|
||||
/nix/store/d3bwqm6bymhy3pdgbvf7vxjqfp31m3j1-gnumake-4.4.1/bin/make
|
||||
✅ netstat
|
||||
net-tools 2.10
|
||||
/nix/store/9i6j9lrsliyz1ww2f8xx18z36wc9n38a-net-tools-2.10/bin/netstat
|
||||
/nix/store/jmyzqvgflnswmws7rnxx6g3zbj680xvd-net-tools-2.10/bin/netstat
|
||||
✅ ninja
|
||||
1.13.2
|
||||
/nix/store/4nsdla28c4177ssp0hlyv5l2yzj8wcba-ninja-1.13.2/bin/ninja
|
||||
/nix/store/7a235m7crqbb4h49sak20fqxpw3n7hr0-ninja-1.13.2/bin/ninja
|
||||
✅ perl
|
||||
v5.42.3
|
||||
/nix/store/8g4dmf7l8q1mf9w4wfmlcl8gvdgmhb8v-perl-5.42.3/bin/perl
|
||||
v5.42.0
|
||||
/nix/store/6plwsm6pkq79yjv4xvy8csk2pd4hzr67-perl-5.42.0/bin/perl
|
||||
✅ pkg-config
|
||||
0.29.2
|
||||
/nix/store/b9izmrfjp0qnnyahdwwrzdwi87s1h179-pkg-config-wrapper-0.29.2/bin/pkg-config
|
||||
/nix/store/1m05k7xgfnw6jc21xxk5681ni3ar97wf-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/9swnd5pf8p8w1gwcbhdpm2yx705kbp38-vim-9.2.1001/bin/vim
|
||||
/nix/store/hvyqx52g4g2fxhgpans3fksjj6lmlyaw-vim-9.2.0389/bin/vim
|
||||
✅ zip
|
||||
Zip 3.0
|
||||
/nix/store/5ypvkry3qra47dylc9kfk36r1q9vha80-zip-3.0/bin/zip
|
||||
/nix/store/qnd2ag67hrjj0b6vbmisdshf50r6s72n-zip-3.0/bin/zip
|
||||
✅ 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 version 22.1.7
|
||||
/nix/store/4zp1rjpj2xijrv4kqpwsy3ixwb2r6nlk-clang-tools-22.1.7/bin/clang-apply-replacements
|
||||
✅ clang-apply-replacements-22
|
||||
clang-apply-replacements version 22.1.8
|
||||
/nix/store/qg0b99zn522gl3vz9hx9ni9ianb11d1r-clang-apply-replacements-22/bin/clang-apply-replacements-22
|
||||
clang-apply-replacements version 22.1.7
|
||||
/nix/store/py2wihg0a96qcppv4hjmww547xabr0fb-clang-apply-replacements-22/bin/clang-apply-replacements-22
|
||||
✅ clang-format
|
||||
clang-format version 22.1.8
|
||||
/nix/store/37jhp4j3y339zmkx09qbr3fpfy3q0kl0-clang-tools-22.1.8/bin/clang-format
|
||||
clang-format version 22.1.7
|
||||
/nix/store/4zp1rjpj2xijrv4kqpwsy3ixwb2r6nlk-clang-tools-22.1.7/bin/clang-format
|
||||
✅ clang-format-22
|
||||
clang-format version 22.1.8
|
||||
/nix/store/m1al4bygpn7i13q1kalavybwh9ldg6rz-clang-format-22/bin/clang-format-22
|
||||
clang-format version 22.1.7
|
||||
/nix/store/kz820ccifjlwqnwqjsx7kbiajrgsmbrh-clang-format-22/bin/clang-format-22
|
||||
✅ clang-tidy
|
||||
LLVM version 22.1.8
|
||||
/nix/store/37jhp4j3y339zmkx09qbr3fpfy3q0kl0-clang-tools-22.1.8/bin/clang-tidy
|
||||
LLVM version 22.1.7
|
||||
/nix/store/4zp1rjpj2xijrv4kqpwsy3ixwb2r6nlk-clang-tools-22.1.7/bin/clang-tidy
|
||||
✅ clang-tidy-22
|
||||
LLVM version 22.1.8
|
||||
/nix/store/h51xrqf8xgx4zil2rm1ydp909858y4xm-clang-tidy-22/bin/clang-tidy-22
|
||||
LLVM version 22.1.7
|
||||
/nix/store/gdrkvpw846lkyzh8y9p3zx50g6ml2v84-clang-tidy-22/bin/clang-tidy-22
|
||||
✅ dot
|
||||
dot - graphviz version 15.1.1 (0)
|
||||
/nix/store/f7r47nc0d13xa4c2xaaxa6dp3pzm9sp1-graphviz-15.1.1/bin/dot
|
||||
dot - graphviz version 12.2.1 (0)
|
||||
/nix/store/12rgns2296s4qcja778gvcbx61z77rc4-graphviz-12.2.1/bin/dot
|
||||
✅ doxygen
|
||||
1.17.0
|
||||
/nix/store/q6yx2nsimj9xmckmgw4br2cnapbgnqpk-doxygen-1.17.0/bin/doxygen
|
||||
1.16.1
|
||||
/nix/store/k0vzr5lvgq1byraknzwvk51wcgpnsrkh-doxygen-1.16.1/bin/doxygen
|
||||
✅ gcovr
|
||||
gcovr 8.4
|
||||
/nix/store/shx7mfl8n4kx11icvwyj5x7cmf6z5ahq-python3.14-gcovr-8.4/bin/gcovr
|
||||
/nix/store/iyzi7fpyclqrha054adnizvif02lg49x-python3.13-gcovr-8.4/bin/gcovr
|
||||
✅ gh
|
||||
gh version 2.102.0 (2026-09-30)
|
||||
/nix/store/66h5dr9v94x04myh2kabx06rq9763jfc-gh-2.102.0/bin/gh
|
||||
gh version 2.94.0 (nixpkgs)
|
||||
/nix/store/pidh15szlsb1vc41xdsa3xbdghdazvby-gh-2.94.0/bin/gh
|
||||
✅ git-cliff
|
||||
git-cliff 2.14.2
|
||||
/nix/store/sl19b42d69b3w2vb9pxp6jbqk841z8vj-git-cliff-2.14.2/bin/git-cliff
|
||||
git-cliff 2.13.1
|
||||
/nix/store/1q851fs62shgjhc03fxxdkpzxdjg7k11-git-cliff-2.13.1/bin/git-cliff
|
||||
✅ 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
|
||||
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
|
||||
✅ gpg
|
||||
gpg (GnuPG) 2.4.9
|
||||
/nix/store/8gm9aq3sspqmvvh7c0x7djcivj2x7yvn-gnupg-2.4.9/bin/gpg
|
||||
/nix/store/wx7vk8babxkgy813r70yc67vcwnmagbx-gnupg-2.4.9/bin/gpg
|
||||
✅ pre-commit
|
||||
pre-commit 4.6.2
|
||||
/nix/store/6blf72f7sdmlrqjggxvk5ij4cxb7fk4c-pre-commit-4.6.2/bin/pre-commit
|
||||
pre-commit 4.5.1
|
||||
/nix/store/bj6i9vl34cij5h0r165y40hrjqak0bmz-pre-commit-4.5.1/bin/pre-commit
|
||||
✅ run-clang-tidy
|
||||
usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
|
||||
/nix/store/a760dc8nldq1715410384bkk83idmm00-run-clang-tidy/bin/run-clang-tidy
|
||||
/nix/store/sbg911hs9dbclrzlp04br3iyfpgnaj6r-run-clang-tidy/bin/run-clang-tidy
|
||||
✅ run-clang-tidy-22
|
||||
usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
|
||||
/nix/store/xwh85m9sa5h8fnpnlmqwfnf2sz9l59mm-run-clang-tidy-22/bin/run-clang-tidy-22
|
||||
/nix/store/n8yak1ap308gvi7gmrniw0ybsx80fjws-run-clang-tidy-22/bin/run-clang-tidy-22
|
||||
|
||||
Rust toolchain:
|
||||
✅ cargo
|
||||
cargo 1.97.1 (c980f4866 2026-06-30)
|
||||
/nix/store/60b0cz400q34wj2gzav5a41i7y6jzlw3-cargo-1.97.1-x86_64-unknown-linux-gnu/bin/cargo
|
||||
/nix/store/88abzp43ywyzql1rhf8jh5aj5n5j7xzr-cargo-1.97.1-x86_64-unknown-linux-gnu/bin/cargo
|
||||
✅ cargo-audit
|
||||
cargo-audit-audit 0.22.2
|
||||
/nix/store/kksgynfwir3pfii9qkbj3gcc61an0d4s-cargo-audit-0.22.2/bin/cargo-audit
|
||||
cargo-audit-audit 0.22.1
|
||||
/nix/store/2w9if868piw98xz057sz97jnjvf7hnvf-cargo-audit-0.22.1/bin/cargo-audit
|
||||
✅ cargo-llvm-cov
|
||||
cargo-llvm-cov 0.9.0
|
||||
/nix/store/4y8qxx0mlm22hnxlw2cmbcwpxispw02h-cargo-llvm-cov-0.9.0/bin/cargo-llvm-cov
|
||||
cargo-llvm-cov 0.8.5
|
||||
/nix/store/jjpdf1l6izz6607a346ykra9sndzaw7h-cargo-llvm-cov-0.8.5/bin/cargo-llvm-cov
|
||||
✅ cargo-nextest
|
||||
cargo-nextest 0.9.146
|
||||
/nix/store/q0anzqrazmdr8n1049ca28098q6lv2y8-cargo-nextest-0.9.146/bin/cargo-nextest
|
||||
cargo-nextest 0.9.137
|
||||
/nix/store/jhkr7gwyrchkml33gyns9cy0yn7b57qc-cargo-nextest-0.9.137/bin/cargo-nextest
|
||||
✅ clippy-driver
|
||||
clippy 0.1.97 (8bab26f4f6 2026-07-14)
|
||||
/nix/store/kk0kqdc4f7am21yp30rr8jcrnib5xqzy-rust-minimal-1.97.1/bin/clippy-driver
|
||||
/nix/store/40d3mzka7r1ps71l0yv2fs6616nbw85m-rust-minimal-1.97.1/bin/clippy-driver
|
||||
✅ rust-analyzer
|
||||
rust-analyzer 1.97.1 (8bab26f 2026-07-14)
|
||||
/nix/store/4mglk7qw9jqbv5nwlx9ahdnpcn5sz70r-rust-analyzer-preview-1.97.1-x86_64-unknown-linux-gnu/bin/rust-analyzer
|
||||
/nix/store/lr3m97p3hx1k22a7c44pb0wa7rbayhfi-rust-analyzer-preview-1.97.1-x86_64-unknown-linux-gnu/bin/rust-analyzer
|
||||
✅ rust-nightly
|
||||
rustc 1.101.0-nightly (282215592 2026-10-04)
|
||||
/nix/store/qwyzkvba3f9569wjj45b4iyb7nz7qbnr-rust-nightly/bin/rust-nightly
|
||||
rustc 1.99.0-nightly (87e5904f5 2026-07-20)
|
||||
/nix/store/j7kf7a5h4xypzp6x1skg4dsdx2k4fwb3-rust-nightly/bin/rust-nightly
|
||||
✅ rustc
|
||||
rustc 1.97.1 (8bab26f4f 2026-07-14)
|
||||
/nix/store/kk0kqdc4f7am21yp30rr8jcrnib5xqzy-rust-minimal-1.97.1/bin/rustc
|
||||
/nix/store/40d3mzka7r1ps71l0yv2fs6616nbw85m-rust-minimal-1.97.1/bin/rustc
|
||||
✅ rustfmt
|
||||
rustfmt 1.9.0-stable (8bab26f4f6 2026-07-14)
|
||||
/nix/store/y5gf8a3hix7pnwzdwcfpf3acqhzv3xy6-rustfmt-preview-1.97.1-x86_64-unknown-linux-gnu/bin/rustfmt
|
||||
/nix/store/6f1icmb2za20kxn30pgmbv5jq9fnbf4z-rustfmt-preview-1.97.1-x86_64-unknown-linux-gnu/bin/rustfmt
|
||||
|
||||
GCC toolchain:
|
||||
✅ gcc
|
||||
gcc (GCC) 15.3.0
|
||||
/nix/store/bslmkbaiymxxdc6lwcdb1qyw745nds5j-gcc-wrapper-15.3.0/bin/gcc
|
||||
gcc (GCC) 15.2.0
|
||||
/nix/store/3dd6y3pq00i3r85l45jvz63wjya403nl-gcc-wrapper-15.2.0/bin/gcc
|
||||
✅ gcc-15
|
||||
gcc (GCC) 15.3.0
|
||||
/nix/store/h6f2qc8kgwf7r81gwb10csvpjqfc2404-gcc-15/bin/gcc-15
|
||||
gcc (GCC) 15.2.0
|
||||
/nix/store/d6iri2s6bzqq5ac3fg25j6hgnn1lz44f-gcc-15/bin/gcc-15
|
||||
✅ g++
|
||||
g++ (GCC) 15.3.0
|
||||
/nix/store/bslmkbaiymxxdc6lwcdb1qyw745nds5j-gcc-wrapper-15.3.0/bin/g++
|
||||
g++ (GCC) 15.2.0
|
||||
/nix/store/3dd6y3pq00i3r85l45jvz63wjya403nl-gcc-wrapper-15.2.0/bin/g++
|
||||
✅ g++-15
|
||||
g++ (GCC) 15.3.0
|
||||
/nix/store/jwncv76v61ld9l99v0b1dkmf810if65s-g++-15/bin/g++-15
|
||||
g++ (GCC) 15.2.0
|
||||
/nix/store/gm3msmmxq055lm9gprkfjj9d2gdz1mpg-g++-15/bin/g++-15
|
||||
✅ cpp
|
||||
cpp (GCC) 15.3.0
|
||||
/nix/store/bslmkbaiymxxdc6lwcdb1qyw745nds5j-gcc-wrapper-15.3.0/bin/cpp
|
||||
cpp (GCC) 15.2.0
|
||||
/nix/store/3dd6y3pq00i3r85l45jvz63wjya403nl-gcc-wrapper-15.2.0/bin/cpp
|
||||
✅ cpp-15
|
||||
cpp (GCC) 15.3.0
|
||||
/nix/store/jmi558xj8v7lz2fm6wrradh40dp48zx9-cpp-15/bin/cpp-15
|
||||
cpp (GCC) 15.2.0
|
||||
/nix/store/bn3gmn0m7g4gn2i0yml46fljc7mghiq5-cpp-15/bin/cpp-15
|
||||
✅ gcov
|
||||
gcov (GCC) 15.3.0
|
||||
/nix/store/n2ab7kf7mm84hna35x463mizcy52pf1h-gcc-15.3.0/bin/gcov
|
||||
gcov (GCC) 15.2.0
|
||||
/nix/store/xvv5sm5i8x0ks6ypfkzl7c4j9srnxz7k-gcc-15.2.0/bin/gcov
|
||||
|
||||
Mold:
|
||||
✅ mold
|
||||
mold 2.42.1 (compatible with GNU ld)
|
||||
/nix/store/9dcddrfxlfl9n3wfibi5jkmp22bjkvkh-mold-unwrapped-wrapper-2.42.1/bin/mold
|
||||
mold 2.41.0 (compatible with GNU ld)
|
||||
/nix/store/2w6fpgxjzzyqmd25wzplm23dfa49a0p2-mold-unwrapped-wrapper-2.41.0/bin/mold
|
||||
|
||||
Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set).
|
||||
|
||||
✅ All 54 checked tools are present and runnable.
|
||||
✅ All 53 checked tools are present and runnable.
|
||||
|
||||
@@ -2,176 +2,173 @@ Detected OS: linux (Linux aarch64)
|
||||
|
||||
Core build tools:
|
||||
✅ cmake
|
||||
cmake version 4.4.3
|
||||
/nix/store/39cq2g3d3flq2lmb6hc09grxs4xaax8p-cmake-4.4.3/bin/cmake
|
||||
cmake version 4.1.2
|
||||
/nix/store/nkcpxjifkambzlrwh27a8igvhnbchibg-cmake-4.1.2/bin/cmake
|
||||
✅ conan
|
||||
Conan version 2.32.0
|
||||
/nix/store/vbr044x1zbq062kkw53zm0sm3slv8g56-conan-2.32.0/bin/conan
|
||||
Conan version 2.28.1
|
||||
/nix/store/8i2gyqgc00xvxg9xm6y7n0ilncdv8imw-conan-2.28.1/bin/conan
|
||||
✅ git
|
||||
git version 2.55.0
|
||||
/nix/store/kj6aff4gmz3snpp35rl3gysbl9srd81a-git-2.55.0/bin/git
|
||||
git version 2.54.0
|
||||
/nix/store/ixp98f9avf8ikpdrmp40cj33g0dazyp9-git-2.54.0/bin/git
|
||||
✅ python3
|
||||
Python 3.14.7
|
||||
/nix/store/81ag9nxz1flw3xjkv1vvqg8y2ipx2ins-python3-3.14.7/bin/python3.14
|
||||
Python 3.13.13
|
||||
/nix/store/lqn6mbgzzdrqq2qkwddcmxj9z6amdd86-python3-3.13.13/bin/python3.13
|
||||
|
||||
Development tooling:
|
||||
✅ ccache
|
||||
ccache version 4.13.6
|
||||
/nix/store/nzb1lir9sw06fn4635kfx055myqanaz8-ccache-4.13.6/bin/ccache
|
||||
/nix/store/2q39xi2kbi04ibga7635f2sl148d1mzv-ccache-4.13.6/bin/ccache
|
||||
✅ clang
|
||||
clang version 22.1.8
|
||||
/nix/store/7z7by1cxa52mk7hjia1pimjvsd6aizhf-clang-wrapper-22.1.8/bin/clang
|
||||
clang version 22.1.7
|
||||
/nix/store/xjqffrq9i7la058s9865ig71l9sp1ys5-clang-wrapper-22.1.7/bin/clang
|
||||
✅ clang-22
|
||||
clang version 22.1.8
|
||||
/nix/store/pqn36q5paasw7v8sjgz5fd90mfpgkkqr-clang-22/bin/clang-22
|
||||
clang version 22.1.7
|
||||
/nix/store/vcf6ilfwn57828hwzyp6zlyr24j9j6yw-clang-22/bin/clang-22
|
||||
✅ clang++
|
||||
clang version 22.1.8
|
||||
/nix/store/7z7by1cxa52mk7hjia1pimjvsd6aizhf-clang-wrapper-22.1.8/bin/clang++
|
||||
clang version 22.1.7
|
||||
/nix/store/xjqffrq9i7la058s9865ig71l9sp1ys5-clang-wrapper-22.1.7/bin/clang++
|
||||
✅ clang++-22
|
||||
clang version 22.1.8
|
||||
/nix/store/m9a3slb0lcixm2csgjz9s0nvj9m5ysnq-clang++-22/bin/clang++-22
|
||||
clang version 22.1.7
|
||||
/nix/store/xby0f6gamr7m27zp5cndsvghbp9lgb3c-clang++-22/bin/clang++-22
|
||||
✅ ClangBuildAnalyzer
|
||||
ClangBuildAnalyzer 1.6.0
|
||||
/nix/store/ml2991pbdhh0qcxqj9331lhpsg449vky-clangbuildanalyzer-1.6.0/bin/ClangBuildAnalyzer
|
||||
/nix/store/h893hd4q1bb6ily2lby5dzyfrrzd2nvj-clangbuildanalyzer-1.6.0/bin/ClangBuildAnalyzer
|
||||
✅ 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
|
||||
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
|
||||
✅ 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
|
||||
file-5.47
|
||||
/nix/store/dx973zg9km2w9albsib2vw9wyvacfrlw-file-5.47/bin/file
|
||||
✅ less
|
||||
less 710 (PCRE2 regular expressions)
|
||||
/nix/store/n6z9dv3yjlgvfx1nmrx6qzyv8vzim1l2-less-710/bin/less
|
||||
less 692 (PCRE2 regular expressions)
|
||||
/nix/store/1blb3s7hhsr77wqi598m6k1qkfp3ms0w-less-692/bin/less
|
||||
✅ make
|
||||
GNU Make 4.4.1
|
||||
/nix/store/q07jxvck60qgrg4p1ywypddh5zz6i9s2-gnumake-4.4.1/bin/make
|
||||
/nix/store/9ngw1ippk25jjj5fjxv36xbp6iq7rxdx-gnumake-4.4.1/bin/make
|
||||
✅ netstat
|
||||
net-tools 2.10
|
||||
/nix/store/rfmhd3f8drg3wqyr2mhyrlkd7vyp2pxv-net-tools-2.10/bin/netstat
|
||||
/nix/store/7vdsz21f0s499s5yyqzp5s4676q4yxdd-net-tools-2.10/bin/netstat
|
||||
✅ ninja
|
||||
1.13.2
|
||||
/nix/store/lfkdyggn1ffslfd830l35jrvapr5lzri-ninja-1.13.2/bin/ninja
|
||||
/nix/store/8ksx98gsbn5lmlizcmw57yd4sg0k2p58-ninja-1.13.2/bin/ninja
|
||||
✅ perl
|
||||
v5.42.3
|
||||
/nix/store/nyp5q4d2cb0pghi4i7nscb97bqkb6kp3-perl-5.42.3/bin/perl
|
||||
v5.42.0
|
||||
/nix/store/5wnly69vv1i3y97al4v3xrqymf9hlzgq-perl-5.42.0/bin/perl
|
||||
✅ pkg-config
|
||||
0.29.2
|
||||
/nix/store/h82n3d0wdrgskdc3yvmz5l3c50h2snvp-pkg-config-wrapper-0.29.2/bin/pkg-config
|
||||
/nix/store/c7vwy0gl1q0agl2h22gi0m9dg7xxad2l-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/h39h0qq4i80sp3yd7sxjn2wf497v5rln-vim-9.2.1001/bin/vim
|
||||
/nix/store/v8c7pvx26irvy9k5sbwd183cyvckzzb3-vim-9.2.0389/bin/vim
|
||||
✅ zip
|
||||
Zip 3.0
|
||||
/nix/store/lfwhq17wfcmjy823fldhlhxj1hjchdzc-zip-3.0/bin/zip
|
||||
/nix/store/5mh19mvbv9ym2sm9vymyyaac5l2cj2jq-zip-3.0/bin/zip
|
||||
✅ 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 version 22.1.7
|
||||
/nix/store/s53p2m776iqaz7acgr5csgpsd18w15h7-clang-tools-22.1.7/bin/clang-apply-replacements
|
||||
✅ clang-apply-replacements-22
|
||||
clang-apply-replacements version 22.1.8
|
||||
/nix/store/mgcaw2063w2d4s61fz023bd0pf3vq5y7-clang-apply-replacements-22/bin/clang-apply-replacements-22
|
||||
clang-apply-replacements version 22.1.7
|
||||
/nix/store/bg4kn8z81hk7b9284rjqvr51wpfjqc24-clang-apply-replacements-22/bin/clang-apply-replacements-22
|
||||
✅ clang-format
|
||||
clang-format version 22.1.8
|
||||
/nix/store/6jr2pbz9mzxznf47lw1w6ss1arqsakw6-clang-tools-22.1.8/bin/clang-format
|
||||
clang-format version 22.1.7
|
||||
/nix/store/s53p2m776iqaz7acgr5csgpsd18w15h7-clang-tools-22.1.7/bin/clang-format
|
||||
✅ clang-format-22
|
||||
clang-format version 22.1.8
|
||||
/nix/store/2iwvl9784yrjfjhfac46z5ihwmbaavk7-clang-format-22/bin/clang-format-22
|
||||
clang-format version 22.1.7
|
||||
/nix/store/79v57mzcw8ng8kl7p961ck08ymhp31v7-clang-format-22/bin/clang-format-22
|
||||
✅ clang-tidy
|
||||
LLVM version 22.1.8
|
||||
/nix/store/6jr2pbz9mzxznf47lw1w6ss1arqsakw6-clang-tools-22.1.8/bin/clang-tidy
|
||||
LLVM version 22.1.7
|
||||
/nix/store/s53p2m776iqaz7acgr5csgpsd18w15h7-clang-tools-22.1.7/bin/clang-tidy
|
||||
✅ clang-tidy-22
|
||||
LLVM version 22.1.8
|
||||
/nix/store/2b7hn8hyp02yfarh8c40c4jsjkm789l0-clang-tidy-22/bin/clang-tidy-22
|
||||
LLVM version 22.1.7
|
||||
/nix/store/wdyd6cb9z1lyi37lbzvwldgcc7yv1n5c-clang-tidy-22/bin/clang-tidy-22
|
||||
✅ dot
|
||||
dot - graphviz version 15.1.1 (0)
|
||||
/nix/store/vz4zb8d1rfigzzdn3i0hd4kff28vk02l-graphviz-15.1.1/bin/dot
|
||||
dot - graphviz version 12.2.1 (0)
|
||||
/nix/store/58rrk4yzwpmyxvl8cqm18h3dhv24zf00-graphviz-12.2.1/bin/dot
|
||||
✅ doxygen
|
||||
1.17.0
|
||||
/nix/store/6q4z332wc9jnvad71wjj87lgjxidhgzl-doxygen-1.17.0/bin/doxygen
|
||||
1.16.1
|
||||
/nix/store/hq32kzwpl89wgr49iq0gmqn9r5n072zq-doxygen-1.16.1/bin/doxygen
|
||||
✅ gcovr
|
||||
gcovr 8.4
|
||||
/nix/store/v548fjz51k2nmr257vjnd0zp3bxpc2r1-python3.14-gcovr-8.4/bin/gcovr
|
||||
/nix/store/sml3xbbfhhlhk6h7jnlg19pdbx9b764b-python3.13-gcovr-8.4/bin/gcovr
|
||||
✅ gh
|
||||
gh version 2.102.0 (2026-09-30)
|
||||
/nix/store/vgg6gnq74h76b4r2ac2q9366f2s5qxwx-gh-2.102.0/bin/gh
|
||||
gh version 2.94.0 (nixpkgs)
|
||||
/nix/store/7hh2qi0gj2ifbxbl56cjzbiyfc379bji-gh-2.94.0/bin/gh
|
||||
✅ git-cliff
|
||||
git-cliff 2.14.2
|
||||
/nix/store/hrlgliq7l3fl2qrdlc197mvscs4n03rd-git-cliff-2.14.2/bin/git-cliff
|
||||
git-cliff 2.13.1
|
||||
/nix/store/bidn3pz53yd6qlg711917xx0q10hqmqv-git-cliff-2.13.1/bin/git-cliff
|
||||
✅ 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
|
||||
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
|
||||
✅ gpg
|
||||
gpg (GnuPG) 2.4.9
|
||||
/nix/store/3qc90b7qkxz7na3xnpd2qb8yvcq8zdp6-gnupg-2.4.9/bin/gpg
|
||||
/nix/store/ka4i8zz5ni3rzqnzcxbfvwr95fk8pn6q-gnupg-2.4.9/bin/gpg
|
||||
✅ pre-commit
|
||||
pre-commit 4.6.2
|
||||
/nix/store/5f3av75nc7n7xbf2hlvlagzpp2g8g562-pre-commit-4.6.2/bin/pre-commit
|
||||
pre-commit 4.5.1
|
||||
/nix/store/n981w6hjfar2l81kxbxs2wxl64vwa5kj-pre-commit-4.5.1/bin/pre-commit
|
||||
✅ run-clang-tidy
|
||||
usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
|
||||
/nix/store/b1yccjmij9qiha8a8d9yxayjdh5cg2vq-run-clang-tidy/bin/run-clang-tidy
|
||||
/nix/store/4z2fyklg78klallr7x9j02kz92hnxp4m-run-clang-tidy/bin/run-clang-tidy
|
||||
✅ run-clang-tidy-22
|
||||
usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
|
||||
/nix/store/v75dyrp7vwdgx5w2fzwsy00lf94qpz0i-run-clang-tidy-22/bin/run-clang-tidy-22
|
||||
/nix/store/f8m0p9ad40brp9ahy4i0h27kqjkya1j9-run-clang-tidy-22/bin/run-clang-tidy-22
|
||||
|
||||
Rust toolchain:
|
||||
✅ cargo
|
||||
cargo 1.97.1 (c980f4866 2026-06-30)
|
||||
/nix/store/nci0zxxj2fiq0dlkpqzbnqfh27wz4sq4-cargo-1.97.1-aarch64-unknown-linux-gnu/bin/cargo
|
||||
/nix/store/6hch2qrr86n2sa2m90lrpxrfxxwbkayl-cargo-1.97.1-aarch64-unknown-linux-gnu/bin/cargo
|
||||
✅ cargo-audit
|
||||
cargo-audit-audit 0.22.2
|
||||
/nix/store/zldaj03mfz9f21yiby5yymq7v8vqygd7-cargo-audit-0.22.2/bin/cargo-audit
|
||||
cargo-audit-audit 0.22.1
|
||||
/nix/store/9rxbrn9aa2r1z96186s69pc7vzizyfch-cargo-audit-0.22.1/bin/cargo-audit
|
||||
✅ cargo-llvm-cov
|
||||
cargo-llvm-cov 0.9.0
|
||||
/nix/store/iwpcfjah31fgn99ph32ajzz7i22j6al5-cargo-llvm-cov-0.9.0/bin/cargo-llvm-cov
|
||||
cargo-llvm-cov 0.8.5
|
||||
/nix/store/vwjsi159n89szrx4yh5pc3jlf2gp4fld-cargo-llvm-cov-0.8.5/bin/cargo-llvm-cov
|
||||
✅ cargo-nextest
|
||||
cargo-nextest 0.9.146
|
||||
/nix/store/p5svqr8x1rr7b1i8595pr8qafsr7s6fm-cargo-nextest-0.9.146/bin/cargo-nextest
|
||||
cargo-nextest 0.9.137
|
||||
/nix/store/qb6bcg2fjvm3r9s9j98nmffmf9xwh45s-cargo-nextest-0.9.137/bin/cargo-nextest
|
||||
✅ clippy-driver
|
||||
clippy 0.1.97 (8bab26f4f6 2026-07-14)
|
||||
/nix/store/ny64zks16ms5d70jk1qsiwsk0fr33ipp-rust-minimal-1.97.1/bin/clippy-driver
|
||||
/nix/store/a6p27cg6b8szfixfyvkssx6l0c345zw8-rust-minimal-1.97.1/bin/clippy-driver
|
||||
✅ rust-analyzer
|
||||
rust-analyzer 1.97.1 (8bab26f 2026-07-14)
|
||||
/nix/store/d9s5skhvi1qxj940mylwym2rjicnc2kq-rust-analyzer-preview-1.97.1-aarch64-unknown-linux-gnu/bin/rust-analyzer
|
||||
/nix/store/262830dlw2517lnagfx7i7agqgl4fmsd-rust-analyzer-preview-1.97.1-aarch64-unknown-linux-gnu/bin/rust-analyzer
|
||||
✅ rust-nightly
|
||||
rustc 1.101.0-nightly (282215592 2026-10-04)
|
||||
/nix/store/rbsf1gr6f9ikv2izlyh8im117w6hs7h5-rust-nightly/bin/rust-nightly
|
||||
rustc 1.99.0-nightly (87e5904f5 2026-07-20)
|
||||
/nix/store/c59pxk1yikdlf129qwyg4fplmxcrha0k-rust-nightly/bin/rust-nightly
|
||||
✅ rustc
|
||||
rustc 1.97.1 (8bab26f4f 2026-07-14)
|
||||
/nix/store/ny64zks16ms5d70jk1qsiwsk0fr33ipp-rust-minimal-1.97.1/bin/rustc
|
||||
/nix/store/a6p27cg6b8szfixfyvkssx6l0c345zw8-rust-minimal-1.97.1/bin/rustc
|
||||
✅ rustfmt
|
||||
rustfmt 1.9.0-stable (8bab26f4f6 2026-07-14)
|
||||
/nix/store/dplvgjikmv8909rk0dkqr90ryjg7xn0j-rustfmt-preview-1.97.1-aarch64-unknown-linux-gnu/bin/rustfmt
|
||||
/nix/store/nd8g81wv1smnvdpy4whpcyv2siwjmaan-rustfmt-preview-1.97.1-aarch64-unknown-linux-gnu/bin/rustfmt
|
||||
|
||||
GCC toolchain:
|
||||
✅ gcc
|
||||
gcc (GCC) 15.3.0
|
||||
/nix/store/g7z555pl99vnwif4jxa05nk1qdc33i1n-gcc-wrapper-15.3.0/bin/gcc
|
||||
gcc (GCC) 15.2.0
|
||||
/nix/store/rn6svg593xsmn8qcjzk8x9pa1i62c4kb-gcc-wrapper-15.2.0/bin/gcc
|
||||
✅ gcc-15
|
||||
gcc (GCC) 15.3.0
|
||||
/nix/store/vbdsfjk3j1xy5z329sl2d8vp6mq6x09i-gcc-15/bin/gcc-15
|
||||
gcc (GCC) 15.2.0
|
||||
/nix/store/h489d1rmjisfbxh5kmsb0a7c35j8qsdf-gcc-15/bin/gcc-15
|
||||
✅ g++
|
||||
g++ (GCC) 15.3.0
|
||||
/nix/store/g7z555pl99vnwif4jxa05nk1qdc33i1n-gcc-wrapper-15.3.0/bin/g++
|
||||
g++ (GCC) 15.2.0
|
||||
/nix/store/rn6svg593xsmn8qcjzk8x9pa1i62c4kb-gcc-wrapper-15.2.0/bin/g++
|
||||
✅ g++-15
|
||||
g++ (GCC) 15.3.0
|
||||
/nix/store/9ghl998ry4cxlsg6hnq5rn2iipnlkccm-g++-15/bin/g++-15
|
||||
g++ (GCC) 15.2.0
|
||||
/nix/store/9ywmhz8bmzknrn3pn84g46z8hj3vrmw5-g++-15/bin/g++-15
|
||||
✅ cpp
|
||||
cpp (GCC) 15.3.0
|
||||
/nix/store/g7z555pl99vnwif4jxa05nk1qdc33i1n-gcc-wrapper-15.3.0/bin/cpp
|
||||
cpp (GCC) 15.2.0
|
||||
/nix/store/rn6svg593xsmn8qcjzk8x9pa1i62c4kb-gcc-wrapper-15.2.0/bin/cpp
|
||||
✅ cpp-15
|
||||
cpp (GCC) 15.3.0
|
||||
/nix/store/wj0mv2fv2lhglvpjzhscqpbigkvik6br-cpp-15/bin/cpp-15
|
||||
cpp (GCC) 15.2.0
|
||||
/nix/store/vmjilh1b830qz9yh0a1jj5ads0jxizdk-cpp-15/bin/cpp-15
|
||||
✅ gcov
|
||||
gcov (GCC) 15.3.0
|
||||
/nix/store/mszfsj00qnv9ygqcqz3w02qzxxskhvlb-gcc-15.3.0/bin/gcov
|
||||
gcov (GCC) 15.2.0
|
||||
/nix/store/rmwf5hpi1y2m1wpnfvlxmrhksm4djk2j-gcc-15.2.0/bin/gcov
|
||||
|
||||
Mold:
|
||||
✅ mold
|
||||
mold 2.42.1 (compatible with GNU ld)
|
||||
/nix/store/gbq334xbibaz4gn184j7gs58m2spgwi7-mold-unwrapped-wrapper-2.42.1/bin/mold
|
||||
mold 2.41.0 (compatible with GNU ld)
|
||||
/nix/store/f5qh5a0bx1dslmnf5n5gx0s6aljbswq3-mold-unwrapped-wrapper-2.41.0/bin/mold
|
||||
|
||||
Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set).
|
||||
|
||||
✅ All 54 checked tools are present and runnable.
|
||||
✅ All 53 checked tools are present and runnable.
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (import ./packages.nix { inherit pkgs customGlibc; }) commonPackages;
|
||||
inherit (import ./packages.nix { inherit pkgs; }) commonPackages;
|
||||
|
||||
# Each forces something absent on the other platform, so both stay lazy.
|
||||
linux = import ./linux.nix { inherit pkgs customGlibc; };
|
||||
|
||||
@@ -31,9 +31,6 @@ 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;
|
||||
|
||||
@@ -89,11 +86,11 @@ let
|
||||
version ? null,
|
||||
versionedTools ? [ ],
|
||||
extraPackages ? [ ],
|
||||
# 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,
|
||||
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,
|
||||
}:
|
||||
let
|
||||
compilerVersionHook =
|
||||
@@ -113,8 +110,7 @@ let
|
||||
in
|
||||
(pkgs.mkShell.override { inherit stdenv; }) (
|
||||
{
|
||||
packages =
|
||||
(if plain then commonPackages else customCommonPackages) ++ versionedLinks ++ extraPackages;
|
||||
packages = commonPackages ++ 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;
|
||||
@@ -123,10 +119,10 @@ let
|
||||
${compilerVersionHook}
|
||||
${darwinLibresolvHook}
|
||||
${conanHook}
|
||||
${pkgs.lib.optionalString plain plainWarningHook}
|
||||
${warningHook}
|
||||
'';
|
||||
}
|
||||
// pkgs.lib.optionalAttrs plain { XRPLD_NO_PATCH_NIX_BINARY = "1"; }
|
||||
// pkgs.lib.optionalAttrs noPatchNixBinary { XRPLD_NO_PATCH_NIX_BINARY = "1"; }
|
||||
);
|
||||
in
|
||||
rec {
|
||||
@@ -184,7 +180,8 @@ rec {
|
||||
version = gccVersion;
|
||||
versionedTools = gccVersionedTools;
|
||||
extraPackages = [ plainGcov ];
|
||||
plain = true;
|
||||
warningHook = plainWarningHook;
|
||||
noPatchNixBinary = true;
|
||||
};
|
||||
|
||||
clang-plain = makeShell {
|
||||
@@ -193,6 +190,7 @@ rec {
|
||||
compilerName = "clang";
|
||||
version = llvmVersion;
|
||||
versionedTools = clangVersionedTools;
|
||||
plain = true;
|
||||
warningHook = plainWarningHook;
|
||||
noPatchNixBinary = true;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -146,7 +146,6 @@ in
|
||||
})
|
||||
];
|
||||
|
||||
clang = customClang;
|
||||
gccStdenv = customStdenv;
|
||||
clangStdenv = pkgs.stdenvAdapters.overrideCC pkgs.stdenv customClang;
|
||||
gcov = customGcov;
|
||||
|
||||
@@ -1,28 +1,14 @@
|
||||
{
|
||||
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,
|
||||
}:
|
||||
{ pkgs }:
|
||||
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 = 23;
|
||||
llvmVersion = 22;
|
||||
|
||||
gccPackage = pkgs."gcc${toString gccVersion}";
|
||||
llvmPackages = pkgs."llvmPackages_${toString llvmVersion}";
|
||||
|
||||
# Bound explicitly so it tracks llvmPackages above, not the `with pkgs` default.
|
||||
# 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;
|
||||
}
|
||||
);
|
||||
clangTools = llvmPackages.clang-tools;
|
||||
|
||||
# In LLVM 22, run-clang-tidy.py moved from share/clang/ to bin/, so nixpkgs
|
||||
# clang-tools no longer links it. Wrap it manually.
|
||||
@@ -30,21 +16,6 @@ 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
|
||||
@@ -54,11 +25,10 @@ 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++-<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.
|
||||
# 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.
|
||||
#
|
||||
# Compiler links must point at whichever compiler is active in a given
|
||||
# environment (the plain stdenv compiler in the dev shell, the custom-glibc
|
||||
@@ -142,7 +112,6 @@ 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
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
#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
|
||||
@@ -80,10 +80,10 @@ BookDirs::const_iterator::operator++()
|
||||
XRPL_ASSERT(index_ != kZero, "xrpl::BookDirs::const_iterator::operator++ : nonzero index");
|
||||
if (!cdirNext(*view_, curKey_, sle_, entry_, index_))
|
||||
{
|
||||
if (index_ == 0)
|
||||
if (index_ == kZero)
|
||||
curKey_ = view_->succ(++curKey_, nextQuality_).value_or(kZero);
|
||||
|
||||
if (index_ != 0 || curKey_ == kZero)
|
||||
if (index_ != kZero || curKey_ == kZero)
|
||||
{
|
||||
curKey_ = key_;
|
||||
entry_ = 0;
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#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>
|
||||
@@ -263,7 +262,7 @@ Ledger::Ledger(Ledger const& prevLedger, NetClock::time_point closeTime)
|
||||
{
|
||||
header_.seq = prevLedger.header_.seq + 1;
|
||||
header_.parentCloseTime = prevLedger.header_.closeTime;
|
||||
header_.hash = prevLedger.header().hash + UInt256(1);
|
||||
header_.hash = prevLedger.header().hash.next();
|
||||
header_.drops = prevLedger.header().drops;
|
||||
header_.closeTimeResolution = prevLedger.header_.closeTimeResolution;
|
||||
header_.parentHash = prevLedger.header().hash;
|
||||
@@ -641,7 +640,7 @@ HashSet<PublicKey>
|
||||
Ledger::negativeUNL() const
|
||||
{
|
||||
HashSet<PublicKey> negUnl;
|
||||
if (auto const sle = NegativeUNLEntryR(*this); sle && sle->isFieldPresent(sfDisabledValidators))
|
||||
if (auto sle = read(keylet::negativeUNL()); sle && sle->isFieldPresent(sfDisabledValidators))
|
||||
{
|
||||
auto const& nUnlData = sle->getFieldArray(sfDisabledValidators);
|
||||
for (auto const& n : nUnlData)
|
||||
@@ -665,7 +664,7 @@ Ledger::negativeUNL() const
|
||||
std::optional<PublicKey>
|
||||
Ledger::validatorToDisable() const
|
||||
{
|
||||
if (auto const sle = NegativeUNLEntryR(*this); sle && sle->isFieldPresent(sfValidatorToDisable))
|
||||
if (auto sle = read(keylet::negativeUNL()); sle && sle->isFieldPresent(sfValidatorToDisable))
|
||||
{
|
||||
auto d = sle->getFieldVL(sfValidatorToDisable);
|
||||
auto s = makeSlice(d);
|
||||
@@ -679,8 +678,7 @@ Ledger::validatorToDisable() const
|
||||
std::optional<PublicKey>
|
||||
Ledger::validatorToReEnable() const
|
||||
{
|
||||
if (auto const sle = NegativeUNLEntryR(*this);
|
||||
sle && sle->isFieldPresent(sfValidatorToReEnable))
|
||||
if (auto sle = read(keylet::negativeUNL()); sle && sle->isFieldPresent(sfValidatorToReEnable))
|
||||
{
|
||||
auto d = sle->getFieldVL(sfValidatorToReEnable);
|
||||
auto s = makeSlice(d);
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#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>
|
||||
@@ -73,8 +72,9 @@ makeRulesGivenLedger(
|
||||
std::optional const digest = ledger.digest(k.key);
|
||||
if (digest)
|
||||
{
|
||||
if (AmendmentsEntryR const entry(k, ledger); entry)
|
||||
return Rules(presets, digest, entry->getFieldV256(sfAmendments));
|
||||
auto const sle = ledger.read(k);
|
||||
if (sle)
|
||||
return Rules(presets, digest, sle->getFieldV256(sfAmendments));
|
||||
}
|
||||
return Rules(presets);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#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>
|
||||
@@ -310,13 +309,40 @@ areCompatible(
|
||||
std::set<UInt256>
|
||||
getEnabledAmendments(ReadView const& view)
|
||||
{
|
||||
return AmendmentsEntryR(view).enabledAmendments();
|
||||
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;
|
||||
}
|
||||
|
||||
MajorityAmendmentsT
|
||||
getMajorityAmendments(ReadView const& view)
|
||||
{
|
||||
return AmendmentsEntryR(view).majorityAmendments();
|
||||
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;
|
||||
}
|
||||
|
||||
std::optional<UInt256>
|
||||
|
||||
@@ -506,7 +506,7 @@ pseudoAccountAddress(ReadView const& view, UInt256 const& pseudoOwnerKey)
|
||||
RipeshaHasher rsh;
|
||||
auto const hash = sha512Half(i, view.header().parentHash, pseudoOwnerKey);
|
||||
rsh(hash.data(), hash.size());
|
||||
AccountID const ret = AccountID::fromRaw(static_cast<RipeshaHasher::result_type>(rsh));
|
||||
AccountID const ret{static_cast<RipeshaHasher::result_type>(rsh)};
|
||||
if (!view.read(keylet::account(ret)))
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1,510 +0,0 @@
|
||||
#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
|
||||
@@ -3,28 +3,16 @@
|
||||
#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>
|
||||
@@ -33,66 +21,9 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
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)
|
||||
closeChannel(SLE::Ref slep, ApplyView& view, UInt256 const& key, 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
|
||||
{
|
||||
@@ -107,9 +38,9 @@ closeChannel(
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -119,72 +50,15 @@ closeChannel(
|
||||
}
|
||||
}
|
||||
|
||||
// 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");
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance];
|
||||
decreaseOwnerCountForObject(view, sle, slep, 1, j);
|
||||
view.update(sle);
|
||||
|
||||
// Remove PayChan from ledger
|
||||
|
||||
@@ -18,10 +18,8 @@
|
||||
#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>
|
||||
@@ -1339,26 +1337,9 @@ directSendNoLimitMPT(
|
||||
}
|
||||
|
||||
// Sending 3rd party MPTs: transit.
|
||||
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);
|
||||
}
|
||||
}
|
||||
saActual = (waiveFee == WaiveTransferFee::Yes)
|
||||
? saAmount
|
||||
: multiply(saAmount, transferRate(view, saAmount.get<MPTIssue>().getMptID()));
|
||||
|
||||
JLOG(j.debug()) << "directSendNoLimitMPT> " << to_string(uSenderID) << " - > "
|
||||
<< to_string(uReceiverID) << " : deliver=" << saAmount.getFullText()
|
||||
@@ -1467,23 +1448,9 @@ directSendNoLimitMultiMPT(
|
||||
}
|
||||
|
||||
// Sending 3rd party MPTs: transit.
|
||||
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);
|
||||
}
|
||||
}
|
||||
STAmount const actualSend = (waiveFee == WaiveTransferFee::Yes)
|
||||
? amount
|
||||
: multiply(amount, transferRate(view, amount.get<MPTIssue>().getMptID()));
|
||||
actual += actualSend;
|
||||
takeFromSender += actualSend;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include <xrpl/nodestore/detail/DecodedBlob.h>
|
||||
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/nodestore/NodeObject.h>
|
||||
@@ -12,8 +11,13 @@
|
||||
|
||||
namespace xrpl::node_store {
|
||||
|
||||
DecodedBlob::DecodedBlob(void const* key, void const* value, int valueBytes) : key_(key)
|
||||
DecodedBlob::DecodedBlob(std::optional<UInt256> key, void const* value, int valueBytes)
|
||||
{
|
||||
if (!key)
|
||||
return;
|
||||
|
||||
key_ = key.value();
|
||||
|
||||
/* Data format:
|
||||
|
||||
Bytes
|
||||
@@ -63,7 +67,7 @@ DecodedBlob::createObject()
|
||||
{
|
||||
Blob data(objectData_, objectData_ + dataBytes_);
|
||||
|
||||
object = NodeObject::createObject(objectType_, std::move(data), UInt256::fromVoid(key_));
|
||||
object = NodeObject::createObject(objectType_, std::move(data), key_);
|
||||
}
|
||||
|
||||
return object;
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
@@ -216,7 +217,7 @@ public:
|
||||
[&hash, pno, &status](void const* data, std::size_t size) {
|
||||
nudb::detail::buffer bf;
|
||||
auto const result = nodeobjectDecompress(data, size, bf);
|
||||
DecodedBlob decoded(hash.data(), result.first, result.second);
|
||||
DecodedBlob decoded(hash, result.first, result.second);
|
||||
if (!decoded.wasOk())
|
||||
{
|
||||
status = Status::DataCorrupt;
|
||||
@@ -240,7 +241,7 @@ public:
|
||||
nudb::error_code ec;
|
||||
nudb::detail::buffer bf;
|
||||
auto const result = nodeobjectCompress(e.getData(), e.getSize(), bf);
|
||||
db.insert(e.getKey(), result.first, result.second, ec);
|
||||
db.insert(e.getKey().data(), result.first, result.second, ec);
|
||||
if (ec && ec != nudb::error::key_exists)
|
||||
Throw<nudb::system_error>(ec);
|
||||
}
|
||||
@@ -288,14 +289,25 @@ public:
|
||||
Throw<nudb::system_error>(ec);
|
||||
nudb::visit(
|
||||
dp,
|
||||
[&](void const* key,
|
||||
[&](void const* keyData,
|
||||
std::size_t keyBytes,
|
||||
void const* data,
|
||||
std::size_t size,
|
||||
nudb::error_code&) {
|
||||
auto key = uint256::fromRaw(
|
||||
std::span{static_cast<unsigned char const*>(keyData), keyBytes});
|
||||
|
||||
if (!key) [[unlikely]]
|
||||
{
|
||||
ec = make_error_code(nudb::error::invalid_key_size);
|
||||
return;
|
||||
}
|
||||
|
||||
nudb::detail::buffer bf;
|
||||
auto const result = nodeobjectDecompress(data, size, bf);
|
||||
|
||||
DecodedBlob decoded(key, result.first, result.second);
|
||||
|
||||
if (!decoded.wasOk())
|
||||
{
|
||||
ec = make_error_code(nudb::error::missing_value);
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include <format>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
@@ -111,6 +112,9 @@ public:
|
||||
if (!getIfExists(keyValues, Keys::kPath, name))
|
||||
Throw<std::runtime_error>("Missing path in RocksDBFactory backend");
|
||||
|
||||
if (keyBytes != uint256::size())
|
||||
Throw<std::runtime_error>("Incorrect key size: expected 32 bytes");
|
||||
|
||||
rocksdb::BlockBasedTableOptions tableOptions;
|
||||
options.env = env;
|
||||
|
||||
@@ -292,7 +296,7 @@ public:
|
||||
|
||||
if (getStatus.ok())
|
||||
{
|
||||
DecodedBlob decoded(hash.data(), string.data(), string.size());
|
||||
DecodedBlob decoded(hash, string.data(), string.size());
|
||||
|
||||
if (decoded.wasOk())
|
||||
{
|
||||
@@ -317,8 +321,8 @@ public:
|
||||
}
|
||||
else
|
||||
{
|
||||
status = static_cast<Status>(
|
||||
static_cast<int>(Status::CustomCode) + unsafeCast<int>(getStatus.code()));
|
||||
status = checkedCast<Status>(
|
||||
safeCast<int>(Status::CustomCode) + safeCast<int>(getStatus.code()));
|
||||
|
||||
JLOG(journal.error()) << getStatus.ToString();
|
||||
}
|
||||
@@ -347,7 +351,7 @@ public:
|
||||
EncodedBlob const encoded(e);
|
||||
|
||||
wb.Put(
|
||||
rocksdb::Slice(reinterpret_cast<char const*>(encoded.getKey()), keyBytes),
|
||||
rocksdb::Slice(reinterpret_cast<char const*>(encoded.getKey().data()), keyBytes),
|
||||
rocksdb::Slice(
|
||||
reinterpret_cast<char const*>(encoded.getData()), encoded.getSize()));
|
||||
}
|
||||
@@ -375,9 +379,12 @@ public:
|
||||
|
||||
for (it->SeekToFirst(); it->Valid(); it->Next())
|
||||
{
|
||||
if (it->key().size() == keyBytes)
|
||||
if (it->key().size() == keyBytes && it->key().size() == uint256::size())
|
||||
{
|
||||
DecodedBlob decoded(it->key().data(), it->value().data(), it->value().size());
|
||||
DecodedBlob decoded(
|
||||
uint256::fromRaw(std::span{it->key().data(), it->key().size()}),
|
||||
it->value().data(),
|
||||
it->value().size());
|
||||
|
||||
if (decoded.wasOk())
|
||||
{
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <xrpl/protocol/tokens.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
@@ -19,95 +21,143 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
namespace detail {
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Caches the base58 representations of AccountIDs
|
||||
/** 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.
|
||||
*/
|
||||
class AccountIdCache
|
||||
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
|
||||
{
|
||||
private:
|
||||
struct CachedAccountID
|
||||
{
|
||||
AccountID id;
|
||||
char encoding[40] = {0};
|
||||
};
|
||||
/** The sequence lock for the entry.
|
||||
|
||||
// The actual cache
|
||||
std::vector<CachedAccountID> cache_;
|
||||
An even value means that the data is stable, whereas an odd
|
||||
value indicates the data is being actively modified.
|
||||
|
||||
// We use a hash function designed to resist algorithmic complexity attacks
|
||||
HardenedHash<> hasher_;
|
||||
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};
|
||||
|
||||
// 64 spinlocks, packed into a single 64-bit value
|
||||
std::atomic<std::uint64_t> locks_ = 0;
|
||||
/** The account id. */
|
||||
AccountID id{};
|
||||
|
||||
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();
|
||||
}
|
||||
/** The length of the cached encoding.
|
||||
|
||||
std::string
|
||||
toBase58(AccountID const& id)
|
||||
{
|
||||
auto const index = hasher_(id) % cache_.size();
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
/** The actual string encoding. */
|
||||
char encoding[39] = {0};
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
static_assert(sizeof(CachedAccountID) == 64 && alignof(CachedAccountID) == 64);
|
||||
|
||||
static std::unique_ptr<detail::AccountIdCache> gAccountIdCache;
|
||||
// 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{};
|
||||
|
||||
void
|
||||
initAccountIdCache(std::size_t count)
|
||||
{
|
||||
if (!gAccountIdCache && count != 0)
|
||||
gAccountIdCache = std::make_unique<detail::AccountIdCache>(count);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::string
|
||||
toBase58(AccountID const& v)
|
||||
{
|
||||
if (gAccountIdCache)
|
||||
return gAccountIdCache->toBase58(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();
|
||||
|
||||
return encodeBase58Token(TokenType::AccountID, v.data(), v.size());
|
||||
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;
|
||||
}
|
||||
|
||||
template <>
|
||||
std::optional<AccountID>
|
||||
parseBase58(std::string const& s)
|
||||
{
|
||||
auto const result = decodeBase58Token(s, TokenType::AccountID);
|
||||
if (result.size() != AccountID::kBytes)
|
||||
return std::nullopt;
|
||||
return AccountID::fromRaw(result);
|
||||
return AccountID::fromRaw(decodeBase58Token(s, TokenType::AccountID));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -148,25 +198,9 @@ parseBase58(std::string const& s)
|
||||
AccountID
|
||||
calcAccountID(PublicKey const& pk)
|
||||
{
|
||||
static_assert(AccountID::kBytes == sizeof(RipeshaHasher::result_type));
|
||||
|
||||
RipeshaHasher rsh;
|
||||
rsh(pk.data(), pk.size());
|
||||
return AccountID::fromRaw(static_cast<RipeshaHasher::result_type>(rsh));
|
||||
}
|
||||
|
||||
AccountID const&
|
||||
xrpAccount()
|
||||
{
|
||||
static AccountID const kAccount(beast::kZero);
|
||||
return kAccount;
|
||||
}
|
||||
|
||||
AccountID const&
|
||||
noAccount()
|
||||
{
|
||||
static AccountID const kAccount(1);
|
||||
return kAccount;
|
||||
return AccountID{static_cast<RipeshaHasher::result_type>(rsh)};
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@@ -185,8 +185,9 @@ getBookBase(Book const& book)
|
||||
UInt256
|
||||
getQualityNext(UInt256 const& uBase)
|
||||
{
|
||||
static constexpr UInt256 kNextQuality(
|
||||
"0000000000000000000000000000000000000000000000010000000000000000");
|
||||
static constexpr UInt256 kNextQuality{
|
||||
"0000000000000000000000000000000000000000000000010000000000000000"};
|
||||
|
||||
return uBase + kNextQuality;
|
||||
}
|
||||
|
||||
@@ -429,9 +430,9 @@ payChannel(AccountID const& src, AccountID const& dst, SeqProxy const& seq) noex
|
||||
Keylet
|
||||
nftokenPageMin(AccountID const& owner)
|
||||
{
|
||||
std::array<std::uint8_t, 32> buf{};
|
||||
std::memcpy(buf.data(), owner.data(), owner.size());
|
||||
return {ltNFTOKEN_PAGE, UInt256::fromRaw(buf)};
|
||||
std::array<std::uint8_t, UInt256::size()> buf{};
|
||||
std::ranges::copy(owner, buf.begin());
|
||||
return {ltNFTOKEN_PAGE, UInt256{buf}};
|
||||
}
|
||||
|
||||
Keylet
|
||||
|
||||
@@ -295,11 +295,9 @@ verify(PublicKey const& publicKey, Slice const& m, Slice const& sig) noexcept
|
||||
NodeID
|
||||
calcNodeID(PublicKey const& pk)
|
||||
{
|
||||
static_assert(NodeID::kBytes == sizeof(RipeshaHasher::result_type));
|
||||
|
||||
RipeshaHasher h;
|
||||
h(pk.data(), pk.size());
|
||||
return NodeID::fromRaw(static_cast<RipeshaHasher::result_type>(h));
|
||||
return NodeID{static_cast<RipeshaHasher::result_type>(h)};
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -148,12 +148,12 @@ STAmount::STAmount(SerialIter& sit, SField const& name) : STBase(name)
|
||||
}
|
||||
|
||||
Issue issue;
|
||||
issue.currency = sit.get160();
|
||||
issue.currency = Currency{sit.get160()};
|
||||
|
||||
if (isXRP(issue.currency))
|
||||
Throw<std::runtime_error>("invalid native currency");
|
||||
|
||||
issue.account = sit.get160();
|
||||
issue.account = AccountID{sit.get160()};
|
||||
|
||||
if (isXRP(issue.account))
|
||||
Throw<std::runtime_error>("invalid native account");
|
||||
@@ -521,64 +521,6 @@ 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.
|
||||
*
|
||||
@@ -1700,9 +1642,7 @@ divRoundImpl(STAmount const& num, STAmount const& den, Asset const& asset, bool
|
||||
|
||||
bool const resultNegative = (num.negative() != den.negative());
|
||||
|
||||
// fixCleanup3_5_0: the legacy path below overflows on large MPT amounts.
|
||||
if (asset.holds<MPTIssue>() &&
|
||||
(isFeatureEnabled(featureMPTokensV2, true) || isFeatureEnabled(fixCleanup3_5_0, true)))
|
||||
if (asset.holds<MPTIssue>() && isFeatureEnabled(featureMPTokensV2, false))
|
||||
{
|
||||
// Match the multiply path above: Number performs the rounded
|
||||
// operation, then STAmount materializes the final MPT amount using the
|
||||
|
||||
@@ -19,16 +19,16 @@ STCurrency::STCurrency(SField const& name) : STBase{name}
|
||||
{
|
||||
}
|
||||
|
||||
STCurrency::STCurrency(SerialIter& sit, SField const& name) : STBase{name}
|
||||
{
|
||||
currency_ = sit.get160();
|
||||
}
|
||||
|
||||
STCurrency::STCurrency(SField const& name, Currency const& currency)
|
||||
: STBase{name}, currency_{currency}
|
||||
{
|
||||
}
|
||||
|
||||
STCurrency::STCurrency(SerialIter& sit, SField const& name)
|
||||
: STCurrency{name, Currency{sit.get160()}}
|
||||
{
|
||||
}
|
||||
|
||||
SerializedTypeID
|
||||
STCurrency::getSType() const
|
||||
{
|
||||
|
||||
@@ -28,48 +28,38 @@ STIssue::STIssue(SField const& name) : STBase{name}
|
||||
|
||||
STIssue::STIssue(SerialIter& sit, SField const& name) : STBase{name}
|
||||
{
|
||||
// Either the currency of an Issue or the issuer of an MPT; the
|
||||
// following 160 bits disambiguate.
|
||||
auto const currencyOrAccount = sit.get160();
|
||||
|
||||
if (isXRP(Currency::fromRaw(currencyOrAccount)))
|
||||
if (isXRP(Currency{currencyOrAccount}))
|
||||
{
|
||||
asset_ = xrpIssue();
|
||||
return;
|
||||
}
|
||||
// Check if MPT
|
||||
else
|
||||
|
||||
// MPT is serialized as:
|
||||
// - 160 bits MPT issuer account
|
||||
// - 160 bits black hole account
|
||||
// - 32 bits sequence
|
||||
AccountID const account{sit.get160()};
|
||||
|
||||
// MPT
|
||||
if (noAccount() == account)
|
||||
{
|
||||
// MPT is serialized as:
|
||||
// - 160 bits MPT issuer account
|
||||
// - 160 bits black hole account
|
||||
// - 32 bits sequence
|
||||
AccountID const account = AccountID::fromRaw(sit.get160());
|
||||
// MPT
|
||||
if (noAccount() == account)
|
||||
{
|
||||
MPTID mptID;
|
||||
std::uint32_t sequence = sit.get32();
|
||||
// MPTID stores the sequence in canonical big-endian bytes. STIssue
|
||||
// ledger bytes are the legacy LE-host encoding, so convert the
|
||||
// native get32() value to LE bytes before copying into the MPTID.
|
||||
sequence = boost::endian::native_to_little(sequence);
|
||||
static_assert(MPTID::size() == sizeof(sequence) + sizeof(currencyOrAccount));
|
||||
memcpy(mptID.data(), &sequence, sizeof(sequence));
|
||||
memcpy(
|
||||
mptID.data() + sizeof(sequence),
|
||||
currencyOrAccount.data(),
|
||||
sizeof(currencyOrAccount));
|
||||
MPTIssue const issue{mptID};
|
||||
asset_ = issue;
|
||||
}
|
||||
else
|
||||
{
|
||||
Issue issue;
|
||||
issue.currency = currencyOrAccount;
|
||||
issue.account = account;
|
||||
if (!isConsistent(issue))
|
||||
Throw<std::runtime_error>("invalid issue: currency and account native mismatch");
|
||||
asset_ = issue;
|
||||
}
|
||||
// MPT: issuer (160), noAccount() (160), sequence (32). The sequence
|
||||
// is stored in little-endian and get32() reads big-endian, so
|
||||
// reverse it before building the canonical ID.
|
||||
asset_ = MPTIssue{std::byteswap(sit.get32()), AccountID{currencyOrAccount}};
|
||||
return;
|
||||
}
|
||||
|
||||
Issue const issue{Currency{currencyOrAccount}, account};
|
||||
|
||||
if (!isConsistent(issue))
|
||||
Throw<std::runtime_error>("invalid issue: currency and account native mismatch");
|
||||
|
||||
asset_ = issue;
|
||||
}
|
||||
|
||||
SerializedTypeID
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#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>
|
||||
@@ -17,9 +18,13 @@
|
||||
#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>
|
||||
|
||||
@@ -36,10 +41,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 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.
|
||||
// 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.
|
||||
|
||||
for (auto const x : element.getAccountID())
|
||||
hashAccount += (hashAccount * 257) ^ x;
|
||||
@@ -106,12 +111,38 @@ STPathSet::operator=(STPathSet const& other)
|
||||
|
||||
STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name)
|
||||
{
|
||||
std::vector<STPathElement> path;
|
||||
for (;;)
|
||||
{
|
||||
int const iType = sit.get8();
|
||||
using enum STPathElement::Type;
|
||||
|
||||
if (iType == STPathElement::TypeNone || iType == STPathElement::TypeBoundary)
|
||||
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
|
||||
{
|
||||
type = parsePathElementType(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 (path.empty())
|
||||
{
|
||||
@@ -122,28 +153,11 @@ 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
|
||||
{
|
||||
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();
|
||||
bool const hasCurrency = ((*type & TypeCurrency) == TypeCurrency);
|
||||
bool const hasMPT = ((*type & TypeMpt) == TypeMpt);
|
||||
|
||||
if (hasCurrency && hasMPT)
|
||||
{
|
||||
@@ -151,18 +165,26 @@ 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 = AccountID{sit.get160()};
|
||||
|
||||
PathAsset asset;
|
||||
|
||||
if (hasCurrency)
|
||||
asset = Currency::fromRaw(sit.get160());
|
||||
asset = Currency{sit.get160()};
|
||||
|
||||
if (hasMPT)
|
||||
asset = sit.get192();
|
||||
|
||||
if (hasIssuer)
|
||||
issuer = sit.get160();
|
||||
if ((*type & TypeIssuer) == TypeIssuer)
|
||||
issuer = AccountID{sit.get160()};
|
||||
|
||||
path.emplace_back(account, asset, issuer, hasCurrency || hasMPT);
|
||||
}
|
||||
}
|
||||
} while (type != TypeNone);
|
||||
}
|
||||
|
||||
STBase*
|
||||
@@ -216,24 +238,22 @@ STPath::getJson(JsonOptions) const
|
||||
for (auto const& it : path_)
|
||||
{
|
||||
json::Value elem(json::ValueType::Object);
|
||||
auto const iType = it.getNodeType();
|
||||
|
||||
elem[jss::type] = iType;
|
||||
elem[jss::type] = safeCast<std::uint32_t>(it.getNodeType());
|
||||
|
||||
if ((iType & STPathElement::TypeAccount) != 0u)
|
||||
if (it.isType(STPathElement::TypeAccount))
|
||||
elem[jss::account] = to_string(it.getAccountID());
|
||||
|
||||
XRPL_ASSERT(
|
||||
((iType & STPathElement::TypeCurrency) == 0u) ||
|
||||
((iType & STPathElement::TypeMpt) == 0u),
|
||||
!(it.hasCurrency() && it.hasMPT()),
|
||||
"xrpl::STPath::getJson : not type Currency and MPT");
|
||||
if ((iType & STPathElement::TypeCurrency) != 0u)
|
||||
if (it.hasCurrency())
|
||||
elem[jss::currency] = to_string(it.getCurrency());
|
||||
|
||||
if ((iType & STPathElement::TypeMpt) != 0u)
|
||||
if (it.hasMPT())
|
||||
elem[jss::mpt_issuance_id] = to_string(it.getMPTID());
|
||||
|
||||
if ((iType & STPathElement::TypeIssuer) != 0u)
|
||||
if (it.hasIssuer())
|
||||
elem[jss::issuer] = to_string(it.getIssuerID());
|
||||
|
||||
ret.append(elem);
|
||||
@@ -265,34 +285,42 @@ 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(STPathElement::TypeBoundary);
|
||||
s.add8(toByte(STPathElement::TypeBoundary));
|
||||
|
||||
for (auto const& speElement : spPath)
|
||||
{
|
||||
int const iType = speElement.getNodeType();
|
||||
s.add8(toByte(speElement.getNodeType()));
|
||||
|
||||
s.add8(iType);
|
||||
|
||||
if ((iType & STPathElement::TypeAccount) != 0u)
|
||||
if (speElement.isType(STPathElement::TypeAccount))
|
||||
s.addBitString(speElement.getAccountID());
|
||||
|
||||
if ((iType & STPathElement::TypeMpt) != 0u)
|
||||
if (speElement.hasMPT())
|
||||
s.addBitString(speElement.getMPTID());
|
||||
|
||||
if ((iType & STPathElement::TypeCurrency) != 0u)
|
||||
if (speElement.hasCurrency())
|
||||
s.addBitString(speElement.getCurrency());
|
||||
|
||||
if ((iType & STPathElement::TypeIssuer) != 0u)
|
||||
if (speElement.hasIssuer())
|
||||
s.addBitString(speElement.getIssuerID());
|
||||
}
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
s.add8(STPathElement::TypeNone);
|
||||
s.add8(toByte(STPathElement::TypeNone));
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <format>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
@@ -17,7 +19,7 @@ namespace xrpl {
|
||||
|
||||
STVector256::STVector256(SerialIter& sit, SField const& name) : STBase(name)
|
||||
{
|
||||
auto const slice = sit.getSlice(sit.getVLDataLength());
|
||||
auto slice = sit.getSlice(sit.getVLDataLength());
|
||||
|
||||
if (slice.size() % UInt256::size() != 0)
|
||||
{
|
||||
@@ -25,12 +27,14 @@ STVector256::STVector256(SerialIter& sit, SField const& name) : STBase(name)
|
||||
std::format("Bad serialization for STVector256: {}", slice.size()));
|
||||
}
|
||||
|
||||
auto const cnt = slice.size() / UInt256::size();
|
||||
value_.reserve(slice.size() / UInt256::size());
|
||||
|
||||
value_.reserve(cnt);
|
||||
|
||||
for (std::size_t i = 0; i != cnt; ++i)
|
||||
value_.push_back(UInt256::fromRaw(slice.substr(i * UInt256::size(), UInt256::size())));
|
||||
while (!slice.empty())
|
||||
{
|
||||
value_.emplace_back(
|
||||
std::span<std::uint8_t const, UInt256::size()>{slice.data(), UInt256::size()});
|
||||
slice += UInt256::size();
|
||||
}
|
||||
}
|
||||
|
||||
STBase*
|
||||
|
||||
@@ -104,8 +104,9 @@ parseGenericSeed(std::string const& str, bool rfc1751)
|
||||
std::string key;
|
||||
if (RFC1751::getKeyFromEnglish(key, str) == 1)
|
||||
{
|
||||
Blob const blob(key.rbegin(), key.rend());
|
||||
return Seed{UInt128::fromRaw(blob)};
|
||||
std::array<std::uint8_t, UInt128::size()> bytes{};
|
||||
std::ranges::reverse_copy(key, bytes.begin());
|
||||
return Seed{UInt128{bytes}};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user