mirror of
https://github.com/Xahau/xahaud.git
synced 2026-10-24 04:37:10 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8845f4672e | ||
|
|
d20e8ffb56 | ||
|
|
c63ee8cc5f | ||
|
|
64a30b2a15 | ||
|
|
0025355f43 | ||
|
|
7046616cdb | ||
|
|
a2e7f3c645 | ||
|
|
004752d1c2 | ||
|
|
22e6ad21b4 | ||
|
|
3423ee4665 | ||
|
|
a2a1e09c2e | ||
|
|
505c28768a | ||
|
|
d7f320bd17 | ||
|
|
22a94c4429 | ||
|
|
42a33a224d | ||
|
|
ee33d825fc | ||
|
|
9ef66a65b8 | ||
|
|
98fdaa4afa | ||
|
|
d9cc66afe9 | ||
|
|
b260b445e9 | ||
|
|
4e0e108654 | ||
|
|
919d817edc | ||
|
|
0373045bff | ||
|
|
2baa23d14b | ||
|
|
de9d5e53f1 | ||
|
|
80c70ec03d | ||
|
|
2566bcf6fa | ||
|
|
7f37aeac39 | ||
|
|
21fab66a17 | ||
|
|
0f3258d235 | ||
|
|
3fbeb130f8 | ||
|
|
58efea062c | ||
|
|
43d90b8549 | ||
|
|
a3bd76fdc9 | ||
|
|
4de217a2c6 | ||
|
|
5e49dc8474 | ||
|
|
77e309fff5 | ||
|
|
79319aaf96 | ||
|
|
a5163eeb50 | ||
|
|
3bc435864f | ||
|
|
4d08a9e603 | ||
|
|
f155556017 | ||
|
|
c4570a699f | ||
|
|
426ff27e42 | ||
|
|
e6bdd55803 | ||
|
|
9dd103fad4 | ||
|
|
a7f9c683c4 | ||
|
|
48a94af13d | ||
|
|
1ddda79c34 | ||
|
|
76b7111098 | ||
|
|
20f2b7f456 | ||
|
|
c6a292ca55 | ||
|
|
1ae3fb3f57 | ||
|
|
1ea6b76853 | ||
|
|
8f4569a63c | ||
|
|
b775f3c3c2 | ||
|
|
03d75df295 | ||
|
|
c2e253b365 | ||
|
|
ba130f8363 | ||
|
|
e87eabd8be | ||
|
|
2e32b5c6bb | ||
|
|
902ed9b492 |
No files matched your search
@@ -19,6 +19,15 @@ coverage:
|
||||
default:
|
||||
target: auto
|
||||
threshold: 2%
|
||||
paths:
|
||||
# PeerImp is historically hard to exercise in the current unit-test
|
||||
# harness. Keep this list narrow; new testable code should remain
|
||||
# covered by the default patch gate.
|
||||
- "!src/xrpld/overlay/detail/PeerImp.cpp"
|
||||
historically-untested:
|
||||
target: 0%
|
||||
paths:
|
||||
- "src/xrpld/overlay/detail/PeerImp.cpp"
|
||||
changes: false
|
||||
|
||||
github_checks:
|
||||
|
||||
@@ -50,6 +50,10 @@ inputs:
|
||||
options:
|
||||
- libstdcxx
|
||||
- libcxx
|
||||
conan_deps_cxxflags:
|
||||
description: 'Extra cxxflags applied to Conan dependency package builds only (NOT the rippled build). JSON object keyed by Conan package pattern, e.g. {"grpc/*":["-Wno-foo"]}. Maps to <pattern>:tools.build:cxxflags.'
|
||||
required: false
|
||||
default: '{}'
|
||||
|
||||
outputs:
|
||||
cache-hit:
|
||||
@@ -81,6 +85,8 @@ runs:
|
||||
|
||||
- name: Configure Conan
|
||||
shell: bash
|
||||
env:
|
||||
CONAN_DEPS_CXXFLAGS: ${{ inputs.conan_deps_cxxflags }}
|
||||
run: |
|
||||
# Create the default profile directory if it doesn't exist
|
||||
mkdir -p ~/.conan2/profiles
|
||||
@@ -105,7 +111,14 @@ runs:
|
||||
os=${{ inputs.os }}
|
||||
EOF
|
||||
|
||||
# Add buildenv and conf sections for Linux (not needed for macOS)
|
||||
# [buildenv] + [conf] sections.
|
||||
# Linux pins compiler executables; macOS uses the system toolchain.
|
||||
# conan_deps_cxxflags (matrix-driven) optionally adds package-pattern
|
||||
# scoped tools.build:cxxflags for Conan dependency builds only - typically
|
||||
# grpc workarounds for newer clang's stricter diagnostics. Because these
|
||||
# are profile-pattern scoped (e.g. grpc/*:...), they do NOT affect the
|
||||
# consumer/rippled toolchain generated for the main build.
|
||||
NEED_CONF=0
|
||||
if [ "${{ inputs.os }}" = "Linux" ] && [ -n "${{ inputs.cc }}" ]; then
|
||||
cat >> ~/.conan2/profiles/default <<EOF
|
||||
|
||||
@@ -116,16 +129,38 @@ runs:
|
||||
[conf]
|
||||
tools.build:compiler_executables={"c": "/usr/bin/${{ inputs.cc }}", "cpp": "/usr/bin/${{ inputs.cxx }}"}
|
||||
EOF
|
||||
NEED_CONF=1
|
||||
fi
|
||||
|
||||
# Add macOS-specific conf if needed
|
||||
if [ "${{ inputs.os }}" = "Macos" ]; then
|
||||
cat >> ~/.conan2/profiles/default <<EOF
|
||||
if [ -n "${CONAN_DEPS_CXXFLAGS}" ] && [ "${CONAN_DEPS_CXXFLAGS}" != "{}" ]; then
|
||||
CONAN_DEPS_CXXFLAGS_LINES="$(python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
[conf]
|
||||
# Workaround for gRPC with newer Apple Clang
|
||||
tools.build:cxxflags=["-Wno-missing-template-arg-list-after-template-kw"]
|
||||
EOF
|
||||
raw = os.environ["CONAN_DEPS_CXXFLAGS"]
|
||||
data = json.loads(raw)
|
||||
if not isinstance(data, dict):
|
||||
sys.exit("conan_deps_cxxflags must be a JSON object like {\"grpc/*\": [\"-Wno-...\"]}")
|
||||
|
||||
for pattern, flags in data.items():
|
||||
if not isinstance(pattern, str) or not pattern:
|
||||
sys.exit("conan_deps_cxxflags keys must be non-empty Conan package patterns")
|
||||
if pattern == "&":
|
||||
sys.exit("conan_deps_cxxflags must target dependency package patterns, not the consumer (&)")
|
||||
if not isinstance(flags, list) or not all(isinstance(flag, str) for flag in flags):
|
||||
sys.exit(f"{pattern}: cxxflags must be a JSON string list")
|
||||
if flags:
|
||||
print(f"{pattern}:tools.build:cxxflags={json.dumps(flags, separators=(',', ':'))}")
|
||||
PY
|
||||
)"
|
||||
if [ -n "${CONAN_DEPS_CXXFLAGS_LINES}" ] && [ "$NEED_CONF" = "0" ]; then
|
||||
echo "" >> ~/.conan2/profiles/default
|
||||
echo "[conf]" >> ~/.conan2/profiles/default
|
||||
fi
|
||||
if [ -n "${CONAN_DEPS_CXXFLAGS_LINES}" ]; then
|
||||
printf '%s\n' "${CONAN_DEPS_CXXFLAGS_LINES}" >> ~/.conan2/profiles/default
|
||||
fi
|
||||
fi
|
||||
|
||||
# Display profile for verification
|
||||
|
||||
@@ -15,6 +15,9 @@ env:
|
||||
|
||||
jobs:
|
||||
checkout:
|
||||
# build publishes to build.xahau.tech, so this workflow only runs from
|
||||
# Xahau/xahaud. build and tests are skipped along with this job.
|
||||
if: github.repository == 'Xahau/xahaud'
|
||||
runs-on: [self-hosted, vanity]
|
||||
outputs:
|
||||
checkout_path: ${{ steps.vars.outputs.checkout_path }}
|
||||
@@ -86,10 +89,16 @@ jobs:
|
||||
cleanup:
|
||||
runs-on: [self-hosted, xahaud-build]
|
||||
needs: [tests, checkout]
|
||||
if: always()
|
||||
if: always() && github.repository == 'Xahau/xahaud'
|
||||
steps:
|
||||
- name: Cleanup workspace
|
||||
run: |
|
||||
CHECKOUT_PATH="${{ needs.checkout.outputs.checkout_path }}"
|
||||
# A run cancelled before checkout has no path; an empty one would
|
||||
# make the rm below delete every run's checkout on the runner.
|
||||
if [[ -z "${CHECKOUT_PATH}" ]]; then
|
||||
echo "No checkout path (cancelled before checkout); nothing to clean"
|
||||
exit 0
|
||||
fi
|
||||
echo "Cleaning workspace for ${CHECKOUT_PATH}"
|
||||
rm -rf "${{ github.workspace }}/${CHECKOUT_PATH}"
|
||||
@@ -1,25 +0,0 @@
|
||||
name: rng-tripwire
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Check for vendor-defined random algorithms
|
||||
run: python3 Builds/rng_tripwire/rng_tripwire.py
|
||||
- name: What happened?
|
||||
if: failure()
|
||||
env:
|
||||
MESSAGE: |
|
||||
A standard-library random algorithm showed up outside the
|
||||
allowlist. These algorithms are not the same on every standard
|
||||
library, so a new call site can change a deterministic trace.
|
||||
|
||||
Either draw through ripple::rand_int, or add the path to
|
||||
Builds/rng_tripwire/allowlist.txt with a one-line justification.
|
||||
Comments are ignored. See Builds/rng_tripwire/allowlist.txt.
|
||||
run: |
|
||||
echo "${MESSAGE}"
|
||||
exit 1
|
||||
@@ -107,6 +107,9 @@ jobs:
|
||||
compiler: apple-clang
|
||||
compiler_version: ${{ steps.detect-compiler.outputs.compiler_version }}
|
||||
stdlib: libcxx
|
||||
# grpc 1.50.1 trips clang-19+ -Werror=missing-template-arg-list-after-template-kw
|
||||
# on Apple Clang. Drop when grpc is bumped past the fix.
|
||||
conan_deps_cxxflags: '{"grpc/*":["-Wno-missing-template-arg-list-after-template-kw"]}'
|
||||
|
||||
- name: Build
|
||||
uses: ./.github/actions/xahau-ga-build
|
||||
|
||||
@@ -8,12 +8,6 @@ on:
|
||||
types: [opened, synchronize, reopened, labeled, unlabeled]
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
full_matrix:
|
||||
description: "Force full matrix (6 configs)"
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -30,37 +24,14 @@ jobs:
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- name: escape double quotes
|
||||
id: escape
|
||||
shell: bash
|
||||
env:
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
run: |
|
||||
ESCAPED_PR_TITLE="${PR_TITLE//\"/\\\"}"
|
||||
echo "title=${ESCAPED_PR_TITLE}" >> "$GITHUB_OUTPUT"
|
||||
- name: Generate build matrix
|
||||
id: set-matrix
|
||||
shell: python
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
import json
|
||||
import os
|
||||
import urllib.request
|
||||
|
||||
# Full matrix with all 6 compiler configurations
|
||||
# Each configuration includes all parameters needed by the build job
|
||||
full_matrix = [
|
||||
{
|
||||
"compiler_id": "gcc-11-libstdcxx",
|
||||
"compiler": "gcc",
|
||||
"cc": "gcc-11",
|
||||
"cxx": "g++-11",
|
||||
"compiler_version": 11,
|
||||
"stdlib": "default",
|
||||
"configuration": "Debug",
|
||||
"job_type": "build"
|
||||
},
|
||||
matrix = [
|
||||
{
|
||||
"compiler_id": "gcc-13-libstdcxx",
|
||||
"compiler": "gcc",
|
||||
@@ -72,26 +43,26 @@ jobs:
|
||||
"job_type": "build"
|
||||
},
|
||||
{
|
||||
"compiler_id": "gcc-13-libstdcxx",
|
||||
"compiler": "gcc",
|
||||
"cc": "gcc-13",
|
||||
"cxx": "g++-13",
|
||||
"gcov": "gcov-13",
|
||||
"compiler_version": 13,
|
||||
"stdlib": "default",
|
||||
"configuration": "Debug",
|
||||
"job_type": "coverage"
|
||||
},
|
||||
{
|
||||
"compiler_id": "clang-14-libstdcxx-gcc11",
|
||||
# Latest stable Clang for the most accurate source-based
|
||||
# coverage mapping (newer language features, fewer bugs in
|
||||
# llvm-cov region inference). Pulled from apt.llvm.org since
|
||||
# Ubuntu 24.04 default repos cap at clang-18.
|
||||
"compiler_id": "clang-20-libcxx",
|
||||
"compiler": "clang",
|
||||
"cc": "clang-14",
|
||||
"cxx": "clang++-14",
|
||||
"compiler_version": 14,
|
||||
"stdlib": "libstdcxx",
|
||||
"clang_gcc_toolchain": 11,
|
||||
"cc": "clang-20",
|
||||
"cxx": "clang++-20",
|
||||
"compiler_version": 20,
|
||||
"stdlib": "libcxx",
|
||||
"configuration": "Debug",
|
||||
"job_type": "build"
|
||||
"job_type": "coverage",
|
||||
"coverage_tool": "llvm",
|
||||
"coverage_format": "lcov",
|
||||
# grpc 1.50.1 uses `Foo::template Bar(...)` without an
|
||||
# angle-bracket arg list; clang-19+ promoted that to
|
||||
# -Werror. Drop when grpc is bumped past the fix.
|
||||
"conan_deps_cxxflags": {
|
||||
"grpc/*": ["-Wno-missing-template-arg-list-after-template-kw"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"compiler_id": "clang-16-libstdcxx-gcc13",
|
||||
@@ -103,117 +74,8 @@ jobs:
|
||||
"clang_gcc_toolchain": 13,
|
||||
"configuration": "Debug",
|
||||
"job_type": "build"
|
||||
},
|
||||
{
|
||||
"compiler_id": "clang-17-libcxx",
|
||||
"compiler": "clang",
|
||||
"cc": "clang-17",
|
||||
"cxx": "clang++-17",
|
||||
"compiler_version": 17,
|
||||
"stdlib": "libcxx",
|
||||
"configuration": "Debug",
|
||||
"job_type": "build"
|
||||
},
|
||||
{
|
||||
# Clang 18 - testing if it's faster than Clang 17 with libc++
|
||||
# Requires patching Conan v1 settings.yml to add version 18
|
||||
"compiler_id": "clang-18-libcxx",
|
||||
"compiler": "clang",
|
||||
"cc": "clang-18",
|
||||
"cxx": "clang++-18",
|
||||
"compiler_version": 18,
|
||||
"stdlib": "libcxx",
|
||||
"configuration": "Debug",
|
||||
"job_type": "build"
|
||||
}
|
||||
]
|
||||
|
||||
# Minimal matrix for PRs and feature branches
|
||||
minimal_matrix = [
|
||||
full_matrix[1], # gcc-13 (middle-ground gcc)
|
||||
full_matrix[2], # gcc-13 coverage
|
||||
full_matrix[3] # clang-14 (mature, stable clang)
|
||||
]
|
||||
|
||||
# Determine which matrix to use based on the target branch
|
||||
ref = "${{ github.ref }}"
|
||||
base_ref = "${{ github.base_ref }}" # For PRs, this is the target branch
|
||||
event_name = "${{ github.event_name }}"
|
||||
pr_title = """${{ steps.escape.outputs.title }}"""
|
||||
pr_labels = """${{ join(github.event.pull_request.labels.*.name, ',') }}"""
|
||||
pr_head_sha = "${{ github.event.pull_request.head.sha }}"
|
||||
|
||||
# Get commit message - for PRs, fetch via API since head_commit.message is empty
|
||||
if event_name == "pull_request" and pr_head_sha:
|
||||
try:
|
||||
url = f"https://api.github.com/repos/${{ github.repository }}/commits/{pr_head_sha}"
|
||||
req = urllib.request.Request(url, headers={
|
||||
"Accept": "application/vnd.github.v3+json",
|
||||
"Authorization": f"Bearer {os.environ.get('GH_TOKEN', '')}"
|
||||
})
|
||||
with urllib.request.urlopen(req) as response:
|
||||
data = json.load(response)
|
||||
commit_message = data["commit"]["message"]
|
||||
except Exception as e:
|
||||
print(f"Failed to fetch commit message: {e}")
|
||||
commit_message = ""
|
||||
else:
|
||||
commit_message = """${{ github.event.head_commit.message }}"""
|
||||
|
||||
# Debug logging
|
||||
print(f"Event: {event_name}")
|
||||
print(f"Ref: {ref}")
|
||||
print(f"Base ref: {base_ref}")
|
||||
print(f"PR head SHA: {pr_head_sha}")
|
||||
print(f"PR title: {pr_title}")
|
||||
print(f"PR labels: {pr_labels}")
|
||||
print(f"Commit message: {commit_message}")
|
||||
|
||||
# Manual trigger input to force full matrix.
|
||||
manual_full = "${{ github.event.inputs.full_matrix || 'false' }}" == "true"
|
||||
|
||||
# Label/manual overrides, while preserving existing title/commit behavior.
|
||||
force_full = (
|
||||
manual_full
|
||||
or "[ci-nix-full-matrix]" in commit_message
|
||||
or "[ci-nix-full-matrix]" in pr_title
|
||||
or ("ci-full-build" in pr_labels and "ci-nix-full-matrix" in pr_labels)
|
||||
)
|
||||
force_min = (
|
||||
"ci-full-build" in pr_labels
|
||||
)
|
||||
print(f"Force full matrix: {force_full}")
|
||||
print(f"Force min matrix: {force_min}")
|
||||
|
||||
# Check if this is targeting a main branch
|
||||
# For PRs: check base_ref (target branch)
|
||||
# For pushes: check ref (current branch)
|
||||
main_branches = ["refs/heads/dev", "refs/heads/release", "refs/heads/candidate"]
|
||||
|
||||
if force_full:
|
||||
# Override: always use full matrix if forced by manual input or label.
|
||||
use_full = True
|
||||
elif force_min:
|
||||
# Override: always use minimal matrix if ci-full-build label is present.
|
||||
use_full = False
|
||||
elif event_name == "pull_request":
|
||||
# For PRs, base_ref is just the branch name (e.g., "dev", not "refs/heads/dev")
|
||||
# Check if the PR targets release or candidate (more critical branches)
|
||||
use_full = base_ref in ["release", "candidate"]
|
||||
else:
|
||||
# For pushes, ref is the full reference (e.g., "refs/heads/dev")
|
||||
use_full = ref in main_branches
|
||||
|
||||
# Select the appropriate matrix
|
||||
if use_full:
|
||||
if force_full:
|
||||
print(f"Using FULL matrix (7 configs) - forced by [ci-nix-full-matrix] tag")
|
||||
else:
|
||||
print(f"Using FULL matrix (7 configs) - targeting main branch")
|
||||
matrix = full_matrix
|
||||
else:
|
||||
print(f"Using MINIMAL matrix (3 configs) - feature branch/PR")
|
||||
matrix = minimal_matrix
|
||||
|
||||
# Add runs_on based on job_type
|
||||
for entry in matrix:
|
||||
@@ -257,9 +119,25 @@ jobs:
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
# Bump apt's default 3 retries; papers over short upstream blips
|
||||
# like the recurring ppa.launchpadcontent.net outages.
|
||||
echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries
|
||||
apt-get update
|
||||
apt-get install -y software-properties-common
|
||||
add-apt-repository ppa:ubuntu-toolchain-r/test -y
|
||||
|
||||
# apt.llvm.org for Clang versions newer than what Ubuntu 24.04 ships
|
||||
# (24.04 default repos cap at clang-18). The bootstrap script adds
|
||||
# the LLVM apt source for the requested version and runs apt-get update.
|
||||
if [ "${{ matrix.compiler }}" = "clang" ] && [ "${{ matrix.compiler_version }}" -ge 19 ]; then
|
||||
apt-get install -y wget gnupg lsb-release
|
||||
wget -qO /tmp/llvm.sh https://apt.llvm.org/llvm.sh
|
||||
chmod +x /tmp/llvm.sh
|
||||
# `all` installs clang + libllvm + lldb + lld + the llvm-N package
|
||||
# (which provides llvm-profdata-N / llvm-cov-N for coverage runs).
|
||||
/tmp/llvm.sh ${{ matrix.compiler_version }} all
|
||||
fi
|
||||
|
||||
apt-get update
|
||||
apt-get install -y git python3 python-is-python3 pipx
|
||||
pipx ensurepath
|
||||
@@ -332,10 +210,16 @@ jobs:
|
||||
pipx install "conan>=2.0,<3"
|
||||
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
|
||||
# Install gcovr for coverage jobs
|
||||
# Install coverage tooling
|
||||
if [ "${{ matrix.job_type }}" = "coverage" ]; then
|
||||
pipx install "gcovr>=7,<9"
|
||||
apt-get install -y curl lcov
|
||||
if [ "${{ matrix.coverage_tool }}" = "llvm" ]; then
|
||||
# Native LLVM source-based coverage: llvm-profdata + llvm-cov.
|
||||
# The clang-N package doesn't pull these in; the llvm-N package does.
|
||||
apt-get install -y "llvm-${{ matrix.compiler_version }}"
|
||||
else
|
||||
pipx install "gcovr>=7,<9"
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Check environment
|
||||
@@ -348,10 +232,15 @@ jobs:
|
||||
which ${{ matrix.cxx }} && ${{ matrix.cxx }} --version || echo "${{ matrix.cxx }} not found"
|
||||
which ccache && ccache --version || echo "ccache not found"
|
||||
|
||||
# Check gcovr for coverage jobs
|
||||
# Check coverage tooling
|
||||
if [ "${{ matrix.job_type }}" = "coverage" ]; then
|
||||
which gcov && gcov --version || echo "gcov not found"
|
||||
which gcovr && gcovr --version || echo "gcovr not found"
|
||||
if [ "${{ matrix.coverage_tool }}" = "llvm" ]; then
|
||||
which "llvm-profdata-${{ matrix.compiler_version }}" && "llvm-profdata-${{ matrix.compiler_version }}" --version || echo "llvm-profdata not found"
|
||||
which "llvm-cov-${{ matrix.compiler_version }}" && "llvm-cov-${{ matrix.compiler_version }}" --version || echo "llvm-cov not found"
|
||||
else
|
||||
which gcov && gcov --version || echo "gcov not found"
|
||||
which gcovr && gcovr --version || echo "gcovr not found"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "---- Full Environment ----"
|
||||
@@ -378,6 +267,7 @@ jobs:
|
||||
cc: ${{ matrix.cc }}
|
||||
cxx: ${{ matrix.cxx }}
|
||||
stdlib: ${{ matrix.stdlib }}
|
||||
conan_deps_cxxflags: ${{ matrix.conan_deps_cxxflags && toJson(matrix.conan_deps_cxxflags) || '{}' }}
|
||||
gha_cache_enabled: 'false' # Disable caching for self hosted runner
|
||||
|
||||
- name: Build
|
||||
@@ -410,8 +300,9 @@ jobs:
|
||||
cache_version: ${{ env.CACHE_VERSION }}
|
||||
main_branch: ${{ env.MAIN_BRANCH_NAME }}
|
||||
stdlib: ${{ matrix.stdlib }}
|
||||
# Coverage builds are slower due to instrumentation; use fewer parallel jobs to avoid flakiness
|
||||
cmake-args: '-Dcoverage=ON -Dcoverage_format=xml -Dcoverage_test_parallelism=$(($(nproc)/2)) -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_CXX_FLAGS="-O0" -DCMAKE_C_FLAGS="-O0"'
|
||||
# Coverage builds are slower due to instrumentation; use fewer parallel jobs to avoid flakiness.
|
||||
# Use *_FLAGS_DEBUG so the build action's stdlib flag (e.g. -stdlib=libc++) in CMAKE_CXX_FLAGS isn't clobbered.
|
||||
cmake-args: '-Dcoverage=ON -Dcoverage_tool=${{ matrix.coverage_tool }} -Dcoverage_format=${{ matrix.coverage_format }} -Dcoverage_test_parallelism=$(($(nproc)/2)) -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_CXX_FLAGS_DEBUG="-g -O0" -DCMAKE_C_FLAGS_DEBUG="-g -O0"'
|
||||
cmake-target: 'coverage'
|
||||
ccache_max_size: '100G'
|
||||
|
||||
@@ -443,22 +334,26 @@ jobs:
|
||||
- name: Move coverage report
|
||||
if: matrix.job_type == 'coverage'
|
||||
shell: bash
|
||||
env:
|
||||
COVERAGE_FILE: ${{ matrix.coverage_tool == 'llvm' && 'coverage.lcov' || 'coverage.xml' }}
|
||||
run: |
|
||||
mv "${{ env.build_dir }}/coverage.xml" ./
|
||||
mv "${{ env.build_dir }}/${COVERAGE_FILE}" ./
|
||||
echo "COVERAGE_FILE=${COVERAGE_FILE}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Archive coverage report
|
||||
if: matrix.job_type == 'coverage'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage.xml
|
||||
path: coverage.xml
|
||||
name: ${{ env.COVERAGE_FILE }}-${{ matrix.compiler_id }}
|
||||
path: ${{ env.COVERAGE_FILE }}
|
||||
retention-days: 30
|
||||
|
||||
- name: Upload coverage report
|
||||
if: matrix.job_type == 'coverage'
|
||||
if: matrix.job_type == 'coverage' && github.repository == 'Xahau/xahaud'
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
files: coverage.xml
|
||||
files: ${{ env.COVERAGE_FILE }}
|
||||
flags: ${{ matrix.coverage_tool }}
|
||||
fail_ci_if_error: true
|
||||
disable_search: true
|
||||
verbose: true
|
||||
|
||||
@@ -8,7 +8,7 @@ Loop: test.jtx test.toplevel
|
||||
test.toplevel > test.jtx
|
||||
|
||||
Loop: test.jtx test.unit_test
|
||||
test.unit_test ~= test.jtx
|
||||
test.unit_test == test.jtx
|
||||
|
||||
Loop: xrpl.protocol xrpld.app
|
||||
xrpld.app > xrpl.protocol
|
||||
@@ -37,6 +37,9 @@ Loop: xrpld.app xrpld.rpc
|
||||
Loop: xrpld.app xrpld.shamap
|
||||
xrpld.app > xrpld.shamap
|
||||
|
||||
Loop: xrpld.core xrpld.net
|
||||
xrpld.net > xrpld.core
|
||||
|
||||
Loop: xrpld.core xrpld.perflog
|
||||
xrpld.perflog == xrpld.core
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ libxrpl.server > xrpl.basics
|
||||
libxrpl.server > xrpl.json
|
||||
libxrpl.server > xrpl.protocol
|
||||
libxrpl.server > xrpl.server
|
||||
test.app > test.shamap
|
||||
test.app > test.toplevel
|
||||
test.app > test.unit_test
|
||||
test.app > xrpl.basics
|
||||
@@ -21,6 +22,7 @@ test.app > xrpld.ledger
|
||||
test.app > xrpld.nodestore
|
||||
test.app > xrpld.overlay
|
||||
test.app > xrpld.rpc
|
||||
test.app > xrpld.shamap
|
||||
test.app > xrpl.hook
|
||||
test.app > xrpl.json
|
||||
test.app > xrpl.protocol
|
||||
@@ -37,7 +39,6 @@ test.beast > xrpl.basics
|
||||
test.conditions > xrpl.basics
|
||||
test.conditions > xrpld.conditions
|
||||
test.consensus > test.csf
|
||||
test.consensus > test.jtx
|
||||
test.consensus > test.toplevel
|
||||
test.consensus > test.unit_test
|
||||
test.consensus > xrpl.basics
|
||||
@@ -45,8 +46,6 @@ test.consensus > xrpld.app
|
||||
test.consensus > xrpld.consensus
|
||||
test.consensus > xrpld.core
|
||||
test.consensus > xrpld.ledger
|
||||
test.consensus > xrpld.net
|
||||
test.consensus > xrpld.overlay
|
||||
test.consensus > xrpl.protocol
|
||||
test.core > test.jtx
|
||||
test.core > test.toplevel
|
||||
@@ -68,8 +67,6 @@ test.jtx > xrpld.consensus
|
||||
test.jtx > xrpld.core
|
||||
test.jtx > xrpld.ledger
|
||||
test.jtx > xrpld.net
|
||||
test.jtx > xrpld.overlay
|
||||
test.jtx > xrpld.peerfinder
|
||||
test.jtx > xrpld.rpc
|
||||
test.jtx > xrpl.hook
|
||||
test.jtx > xrpl.json
|
||||
@@ -101,10 +98,12 @@ test.overlay > test.toplevel
|
||||
test.overlay > test.unit_test
|
||||
test.overlay > xrpl.basics
|
||||
test.overlay > xrpld.app
|
||||
test.overlay > xrpld.core
|
||||
test.overlay > xrpld.overlay
|
||||
test.overlay > xrpld.peerfinder
|
||||
test.overlay > xrpld.shamap
|
||||
test.overlay > xrpl.protocol
|
||||
test.overlay > xrpl.resource
|
||||
test.peerfinder > test.beast
|
||||
test.peerfinder > test.unit_test
|
||||
test.peerfinder > xrpl.basics
|
||||
@@ -184,7 +183,6 @@ xrpld.ledger > xrpld.core
|
||||
xrpld.ledger > xrpl.json
|
||||
xrpld.ledger > xrpl.protocol
|
||||
xrpld.net > xrpl.basics
|
||||
xrpld.net > xrpld.core
|
||||
xrpld.net > xrpl.json
|
||||
xrpld.net > xrpl.protocol
|
||||
xrpld.net > xrpl.resource
|
||||
@@ -197,6 +195,7 @@ xrpld.overlay > xrpl.basics
|
||||
xrpld.overlay > xrpld.core
|
||||
xrpld.overlay > xrpld.peerfinder
|
||||
xrpld.overlay > xrpld.perflog
|
||||
xrpld.overlay > xrpld.shamap
|
||||
xrpld.overlay > xrpl.json
|
||||
xrpld.overlay > xrpl.protocol
|
||||
xrpld.overlay > xrpl.resource
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# path-prefix justification
|
||||
# A hit under src/ or include/ is accepted only when its path starts with one
|
||||
# of these prefixes. The justification is for reviewers; the script ignores it.
|
||||
|
||||
src/test/ Tests are not on the consensus schedule.
|
||||
include/xrpl/basics/random.h Seeding overloads only. rand_int does not call these distributions.
|
||||
include/xrpl/basics/hardened_hash.h One-time hash salt drawn from OS entropy.
|
||||
include/xrpl/crypto/csprng.h CSPRNG declaration. OS entropy, not a consensus draw.
|
||||
src/libxrpl/crypto/csprng.cpp OS entropy for the CSPRNG.
|
||||
src/xrpld/peerfinder/ default_prng shuffles. Not the harness engine.
|
||||
src/xrpld/overlay/detail/OverlayImpl.cpp std::shuffle of relay peers. Runs only when TX_REDUCE_RELAY_ENABLE is on, and that flag is off. Uses app_.getPrng when it does run.
|
||||
@@ -1,125 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fail if a vendor-defined <random> algorithm appears outside the allowlist.
|
||||
|
||||
Scans src/ and include/ for the standard algorithms whose mapping is not
|
||||
portable across standard libraries. Comments are ignored. A hit is accepted
|
||||
only when its path is listed in allowlist.txt. Unlisted hits are printed as
|
||||
file:line and the process exits non-zero.
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ALGORITHMS = (
|
||||
"uniform_int_distribution",
|
||||
"uniform_real_distribution",
|
||||
"bernoulli_distribution",
|
||||
"binomial_distribution",
|
||||
"negative_binomial_distribution",
|
||||
"geometric_distribution",
|
||||
"poisson_distribution",
|
||||
"exponential_distribution",
|
||||
"gamma_distribution",
|
||||
"weibull_distribution",
|
||||
"extreme_value_distribution",
|
||||
"normal_distribution",
|
||||
"lognormal_distribution",
|
||||
"chi_squared_distribution",
|
||||
"cauchy_distribution",
|
||||
"fisher_f_distribution",
|
||||
"student_t_distribution",
|
||||
"discrete_distribution",
|
||||
"piecewise_constant_distribution",
|
||||
"piecewise_linear_distribution",
|
||||
"sample",
|
||||
"shuffle",
|
||||
"generate_canonical",
|
||||
"random_device",
|
||||
)
|
||||
|
||||
HIT = re.compile(r"\bstd::(?:" + "|".join(ALGORITHMS) + r")\b")
|
||||
SUFFIXES = {".h", ".hh", ".hpp", ".cpp", ".cc", ".cxx", ".ipp", ".inc"}
|
||||
|
||||
|
||||
def repo_root() -> Path:
|
||||
here = Path(__file__).resolve().parent
|
||||
for candidate in (here, *here.parents):
|
||||
if (candidate / "src").is_dir() and (candidate / "include").is_dir():
|
||||
return candidate
|
||||
sys.exit("rng_tripwire: cannot find the repository root")
|
||||
|
||||
|
||||
def load_allowlist(path: Path) -> list[tuple[str, str]]:
|
||||
entries = []
|
||||
for raw in path.read_text().splitlines():
|
||||
line = raw.split("#", 1)[0].strip()
|
||||
if not line:
|
||||
continue
|
||||
prefix, _, why = line.partition(" ")
|
||||
entries.append((prefix.strip(), why.strip()))
|
||||
return entries
|
||||
|
||||
|
||||
def allowed(rel: str, entries: list[tuple[str, str]]) -> bool:
|
||||
for prefix, _why in entries:
|
||||
if rel == prefix or rel.startswith(prefix):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def strip_comments(text: str) -> list[str]:
|
||||
lines = []
|
||||
in_block = False
|
||||
for line in text.splitlines():
|
||||
out = []
|
||||
i = 0
|
||||
while i < len(line):
|
||||
if in_block:
|
||||
end = line.find("*/", i)
|
||||
if end < 0:
|
||||
i = len(line)
|
||||
break
|
||||
in_block = False
|
||||
i = end + 2
|
||||
continue
|
||||
if line.startswith("//", i):
|
||||
break
|
||||
if line.startswith("/*", i):
|
||||
in_block = True
|
||||
i += 2
|
||||
continue
|
||||
out.append(line[i])
|
||||
i += 1
|
||||
lines.append("".join(out))
|
||||
return lines
|
||||
|
||||
|
||||
def main() -> int:
|
||||
root = repo_root()
|
||||
allow_path = Path(__file__).resolve().parent / "allowlist.txt"
|
||||
entries = load_allowlist(allow_path)
|
||||
hits = []
|
||||
for base in ("src", "include"):
|
||||
for path in sorted((root / base).rglob("*")):
|
||||
if not path.is_file() or path.suffix not in SUFFIXES:
|
||||
continue
|
||||
rel = path.relative_to(root).as_posix()
|
||||
try:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
except UnicodeError:
|
||||
text = path.read_text(encoding="latin-1")
|
||||
for number, line in enumerate(strip_comments(text), start=1):
|
||||
if HIT.search(line):
|
||||
hits.append((allowed(rel, entries), f"{rel}:{number}"))
|
||||
unlisted = [item for ok, item in hits if not ok]
|
||||
for ok, item in hits:
|
||||
print(("ALLOW " if ok else "HIT ") + item)
|
||||
print(f"{len(hits)} hits, {len(unlisted)} unlisted")
|
||||
for item in unlisted:
|
||||
print(item)
|
||||
return 1 if unlisted else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
+1
-1
@@ -97,7 +97,7 @@ else
|
||||
# GH Action, runner
|
||||
if [[ "$(git rev-parse --abbrev-ref HEAD)" == "release" ]]; then
|
||||
echo "building on the release branch... placing it in builds/candidate"
|
||||
mkdir /data/builds/candidate
|
||||
mkdir -p /data/builds/candidate
|
||||
cp /io/release-build/xahaud /data/builds/candidate/$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4
|
||||
cp /io/release-build/release.info /data/builds/candidate/$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4.releaseinfo
|
||||
else
|
||||
|
||||
@@ -166,10 +166,13 @@
|
||||
# ws Websockets
|
||||
# wss Secure Websockets
|
||||
# peer Peer Protocol
|
||||
# pwa On-ledger AppLoader documents (featurePWALoader), for a
|
||||
# reverse proxy only. Read "pwa ports" below first.
|
||||
#
|
||||
# Restrictions:
|
||||
#
|
||||
# Only one port may be configured to support the peer protocol.
|
||||
# The pwa protocol cannot be combined with any other protocol.
|
||||
# A port cannot have websocket and non websocket protocols at the
|
||||
# same time. It is possible have both Websockets and Secure Websockets
|
||||
# together in one port.
|
||||
@@ -264,6 +267,119 @@
|
||||
# "secure_gateway" connections, then they will be treated as
|
||||
# "admin" addresses.
|
||||
#
|
||||
# pwa ports
|
||||
#
|
||||
# A port whose protocol is "pwa" serves accounts' on-ledger AppLoader
|
||||
# documents (featurePWALoader) as HTML. This makes the node a web host
|
||||
# for third-party content: anyone can publish a loader to an account
|
||||
# for about a drop per byte, and nothing in protocol takes one down.
|
||||
# Hosting, and a hosting provider's obligations, are yours.
|
||||
#
|
||||
# One host per account, on a zone used for nothing else:
|
||||
#
|
||||
# https://pwa<account-index>.<domain>/<r-address>
|
||||
# https://<domain>/<r-address> 301 to the above
|
||||
# https://<custom host>/ see custom_domains
|
||||
#
|
||||
# The first label of the Host header is "pwa" followed by the decimal
|
||||
# AccountIndex of the account (an account created before AccountIndex
|
||||
# existed has no host); the rest must be one of the port's "domain"
|
||||
# values; the path must be that account's exact r-address. One
|
||||
# trailing slash and a query string are ignored; anything else is
|
||||
# 404. The bare zone never serves a document (every page would share
|
||||
# one origin): it redirects, keeping :port and query.
|
||||
# Never set cookies at or above the zone and never serve your own
|
||||
# site from it. Wildcard DNS and a wildcard certificate cover it.
|
||||
#
|
||||
# Rules enforced at startup and on every request:
|
||||
#
|
||||
# - "pwa" is the port's only protocol; no admin, user/password or
|
||||
# ssl_* keys (terminate TLS at the proxy).
|
||||
# - "domain" is required: the zone(s), comma-separated. The proxy must
|
||||
# forward the Host header unchanged.
|
||||
# - "secure_gateway" is required and may not cover every address.
|
||||
# Every request must come from it carrying X-Forwarded-For, whose
|
||||
# RIGHTMOST entry (the one the proxy wrote) is the client. The
|
||||
# "Forwarded" header is ignored.
|
||||
# - Clients are metered per IPv4 address, per IPv6 /64; a miss costs
|
||||
# what a hit does; over-use is answered 503 until it decays.
|
||||
# - GET and HEAD only; every response closes the connection; only
|
||||
# validated state is served, with an ETag (304 on If-None-Match).
|
||||
# X-NetworkID, X-LedgerSeq, X-LedgerHash and X-PWASeq name the
|
||||
# validated ledger and AppLoaderSequence the document came from, so
|
||||
# a client (a browser extension, say) can vet it against a second
|
||||
# node with the pwa_validate RPC (account, hash of the bytes, host).
|
||||
# - Lookups run on at most two job queue threads, below client RPC;
|
||||
# beyond a bounded backlog they are refused with 503. Put a cache in
|
||||
# front (the ETag makes revalidation cheap).
|
||||
# - The admin RPC "pwa_info" reports requests by outcome and bytes
|
||||
# served; "reset": true zeroes them, "flush_dns": true drops the
|
||||
# custom domain cache (CLI: xahaud pwa_info [reset] [flush_dns]).
|
||||
#
|
||||
# Example (also add port_pwa to the [server] list):
|
||||
#
|
||||
# [port_pwa]
|
||||
# port = 8088
|
||||
# ip = 127.0.0.1
|
||||
# protocol = pwa
|
||||
# secure_gateway = 127.0.0.1
|
||||
# domain = pwapp.example
|
||||
#
|
||||
# with nginx in front, TLS terminated there, Host passed through:
|
||||
#
|
||||
# server {
|
||||
# listen 443 ssl;
|
||||
# server_name pwapp.example ~^pwa[0-9]+\.pwapp\.example$;
|
||||
# ssl_certificate /etc/ssl/wildcard.pwapp.example.pem;
|
||||
# ssl_certificate_key /etc/ssl/wildcard.pwapp.example.key;
|
||||
# location / {
|
||||
# proxy_pass http://127.0.0.1:8088;
|
||||
# proxy_set_header Host $host;
|
||||
# proxy_set_header X-Forwarded-For $remote_addr;
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# custom_domains = 0 | 1
|
||||
#
|
||||
# Default 0. With 1, a request whose Host is outside every zone is
|
||||
# served as the account named by DNS, so a domain owner can point
|
||||
# their own name at your proxy:
|
||||
#
|
||||
# app.example.org. CNAME pwa-proxy.your.example.
|
||||
# _xahau-pwa.app.example.org. TXT "rAbC..."
|
||||
#
|
||||
# The record lives on the _xahau-pwa label beneath the host because
|
||||
# the host is usually a CNAME to your proxy. Exactly one address must
|
||||
# be present there (other TXT strings are ignored; two addresses count
|
||||
# as none). The named account must consent: its Domain field must
|
||||
# equal the host or a parent of it (ignoring case), else 503. Lists
|
||||
# apply as at the account's own host; only the root path is a
|
||||
# document. No usable record is 404 (cached 15 minutes), a resolver
|
||||
# failure 503 (cached 60 s), a record is cached for its TTL (clamped
|
||||
# to 60..3600 s). Lookups run on their own job queue threads, at most
|
||||
# four at a time and one per host; a request that misses the cache is
|
||||
# answered 429 with Retry-After and a refreshing page rather than
|
||||
# waiting, so a flood of Host headers cannot tie up the node. The
|
||||
# admin RPC pwa_info with "flush_dns": true drops the cache. Your
|
||||
# proxy must accept, and hold certificates for, the hosts you will
|
||||
# serve (e.g. Caddy on-demand TLS); xahaud only sees the Host header.
|
||||
#
|
||||
# resolver = <IPv4 address>[:port]
|
||||
#
|
||||
# The recursive resolver for custom_domains; default is the system
|
||||
# resolver. xahaud does not validate DNSSEC, so prefer a validating
|
||||
# resolver on the same host.
|
||||
#
|
||||
# Documents are served with "Content-Security-Policy: sandbox
|
||||
# allow-scripts ...": an opaque origin that cannot read anything from
|
||||
# the node but can still send requests anywhere, including the
|
||||
# visitor's own machine, so keep admin ports behind credentials.
|
||||
#
|
||||
# [pwa_allowlist] and [pwa_blocklist], one r-address per line, apply to
|
||||
# every pwa port: if the allowlist is non-empty only those accounts are
|
||||
# served; blocklisted accounts are never served (403), whatever the
|
||||
# allowlist says. An account on both is a startup error.
|
||||
#
|
||||
# ssl_key = <filename>
|
||||
# ssl_cert = <filename>
|
||||
# ssl_chain = <filename>
|
||||
@@ -448,6 +564,30 @@
|
||||
#
|
||||
#
|
||||
#
|
||||
# [udp_highway]
|
||||
#
|
||||
# 0 or 1.
|
||||
#
|
||||
# 0: Disable the Xahau UDP Superhighway (XUSH). [default]
|
||||
# 1: Enable the Xahau UDP Superhighway (XUSH).
|
||||
#
|
||||
# The UDP Superhighway is a best-effort transport that runs alongside the
|
||||
# peer protocol. When it is enabled the server also listens for UDP
|
||||
# datagrams on its peer port number, and every transaction it relays to its
|
||||
# peers is also sent, over UDP, to a small random set of other servers that
|
||||
# have the highway enabled. It never replaces the peer protocol: anything
|
||||
# sent over the highway is also relayed normally.
|
||||
#
|
||||
# To receive highway traffic, inbound UDP must be allowed to the peer port
|
||||
# (in addition to inbound TCP).
|
||||
#
|
||||
# A server that keeps its address private (peer_private = 1, or any server
|
||||
# configured as a validator) only exchanges highway traffic with the peers
|
||||
# listed in [ips_fixed], and never advertises highway endpoints. Fixed peers
|
||||
# are never advertised to other servers.
|
||||
#
|
||||
#
|
||||
#
|
||||
# [peers_max]
|
||||
#
|
||||
# The largest number of desired peer connections (incoming or outgoing).
|
||||
@@ -674,6 +814,38 @@
|
||||
#
|
||||
#
|
||||
#
|
||||
# [early_manifest_sync]
|
||||
#
|
||||
# Controls whether, while acquiring its first ledger, the server fetches and
|
||||
# reads the on-ledger validator manifests ahead of the rest of that ledger's
|
||||
# state. Doing so lets it resolve validators' current signing keys, including
|
||||
# rotations and revocations published on-ledger, within seconds of starting
|
||||
# a sync rather than once the whole state has arrived.
|
||||
#
|
||||
# The manifests read are those of the master keys on the server's own
|
||||
# validator lists, fetched by key, and as many more from the on-ledger
|
||||
# manifest directory as the manifest cache keeps.
|
||||
#
|
||||
# The cost is a dozen or so request/response rounds with peers. With more
|
||||
# than one peer serving the ledger these overlap with fetching the rest of
|
||||
# its state; with only one, they come first.
|
||||
#
|
||||
# It also lets a server that starts with nothing but the master keys of its
|
||||
# validators and a few peers find its way. Until it has validated a ledger,
|
||||
# while it holds manifests for too few of its validators to make a quorum,
|
||||
# it reads them from up to three of the ledgers its peers report, the most
|
||||
# widely reported first, every 15 seconds. Every manifest read is checked
|
||||
# against its master key's signature, so a ledger reported by a peer that
|
||||
# is not on the network can leave something out but cannot add anything.
|
||||
#
|
||||
# Legal values are:
|
||||
# 1 - Fetch the manifests first, and from peers' ledgers if needed
|
||||
# 0 - Fetch the ledger's state in the usual order, and read the manifests
|
||||
# from it once it is complete
|
||||
#
|
||||
# The default is 1. A ledger without on-ledger manifests costs only the few
|
||||
# nodes on the paths to where their directory and objects would be.
|
||||
#
|
||||
#
|
||||
#
|
||||
# [ledger_history]
|
||||
@@ -1416,6 +1588,19 @@
|
||||
# contains breaking changes that require a new API version number.
|
||||
# They are not ready for public consumption.
|
||||
#
|
||||
# [hook_validate_rpc]
|
||||
#
|
||||
# 0 or 1.
|
||||
#
|
||||
# 0: Disable the hook_validate RPC method
|
||||
# 1: Enable the hook_validate RPC method, which runs the Hook guard
|
||||
# checker and WASM VM validation against caller-supplied code using
|
||||
# the current open ledger's rules.
|
||||
#
|
||||
# The default is 0 on networked nodes and 1 in standalone mode (-a).
|
||||
# When enabled the method is available to non-admin (user role) clients,
|
||||
# so consider the CPU cost before enabling it on a public-facing node.
|
||||
#
|
||||
#-------------------------------------------------------------------------------
|
||||
#
|
||||
# 10. Example Settings
|
||||
|
||||
@@ -151,4 +151,3 @@ D686F2538F410C9D0D856788E98E3579595DAF7B38D38887F81ECAC934B06040 HooksUpdate1
|
||||
4B8466415FAB32FFA89D9DCBE166A42340115771DF611A7160F8D7439C87ECD8 fixNSDelete
|
||||
EDB4EE4C524E16BDD91D9A529332DED08DCAAA51CC6DC897ACFA1A0ED131C5B6 fix240819
|
||||
8063140E9260799D6716756B891CEC3E7006C4E4F277AB84670663A88F94B9C4 fixPageCap
|
||||
88693F108C3CD8A967F3F4253A32DEF5E35F9406ACD2A11B88B11D90865763A9 fix240911
|
||||
@@ -0,0 +1,156 @@
|
||||
#[===================================================================[
|
||||
Native LLVM source-based code coverage helper.
|
||||
|
||||
Drives the -fprofile-instr-generate / -fcoverage-mapping pipeline:
|
||||
1. Run instrumented binary with LLVM_PROFILE_FILE=...%m-%p.profraw
|
||||
2. llvm-profdata merge -sparse -> coverage.profdata
|
||||
3. llvm-cov export/show/report -> final report
|
||||
|
||||
Output filename per coverage_format:
|
||||
lcov -> coverage.lcov
|
||||
json -> coverage.json
|
||||
txt | text -> coverage.txt
|
||||
html | html-details -> <NAME>/index.html
|
||||
#]===================================================================]
|
||||
|
||||
include(CMakeParseArguments)
|
||||
|
||||
# Locate llvm-profdata / llvm-cov, preferring versioned variants matching the
|
||||
# Clang we're building with so we don't accidentally pair clang-20 with
|
||||
# llvm-cov-14 (profile format mismatch -> hard failure).
|
||||
function(_find_llvm_cov_tools)
|
||||
if(LLVM_PROFDATA_PATH AND LLVM_COV_PATH)
|
||||
return()
|
||||
endif()
|
||||
|
||||
string(REGEX MATCH "^[0-9]+" _major "${CMAKE_CXX_COMPILER_VERSION}")
|
||||
|
||||
set(_pd_names llvm-profdata)
|
||||
set(_cov_names llvm-cov)
|
||||
if(_major)
|
||||
list(PREPEND _pd_names "llvm-profdata-${_major}")
|
||||
list(PREPEND _cov_names "llvm-cov-${_major}")
|
||||
endif()
|
||||
|
||||
# Only delegate to xcrun when the *compiler* is AppleClang. On macOS with
|
||||
# Homebrew/system clang-N, xcrun would resolve to Xcode's llvm tools which
|
||||
# could be a different version - exactly the mismatch we want to avoid.
|
||||
if(APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
|
||||
execute_process(COMMAND xcrun -f llvm-profdata
|
||||
OUTPUT_VARIABLE _pd_xcrun OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET RESULT_VARIABLE _pd_rc)
|
||||
if(_pd_rc EQUAL 0 AND _pd_xcrun)
|
||||
set(LLVM_PROFDATA_PATH "${_pd_xcrun}" CACHE FILEPATH "llvm-profdata" FORCE)
|
||||
endif()
|
||||
execute_process(COMMAND xcrun -f llvm-cov
|
||||
OUTPUT_VARIABLE _cov_xcrun OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET RESULT_VARIABLE _cov_rc)
|
||||
if(_cov_rc EQUAL 0 AND _cov_xcrun)
|
||||
set(LLVM_COV_PATH "${_cov_xcrun}" CACHE FILEPATH "llvm-cov" FORCE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT LLVM_PROFDATA_PATH)
|
||||
find_program(LLVM_PROFDATA_PATH NAMES ${_pd_names})
|
||||
endif()
|
||||
if(NOT LLVM_COV_PATH)
|
||||
find_program(LLVM_COV_PATH NAMES ${_cov_names})
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(setup_target_for_coverage_llvm)
|
||||
set(oneValueArgs NAME FORMAT)
|
||||
set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
|
||||
cmake_parse_arguments(Cov "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
|
||||
_find_llvm_cov_tools()
|
||||
if(NOT LLVM_PROFDATA_PATH)
|
||||
message(FATAL_ERROR "llvm-profdata not found (needed for coverage_tool=llvm)")
|
||||
endif()
|
||||
if(NOT LLVM_COV_PATH)
|
||||
message(FATAL_ERROR "llvm-cov not found (needed for coverage_tool=llvm)")
|
||||
endif()
|
||||
|
||||
if(NOT Cov_FORMAT)
|
||||
set(Cov_FORMAT lcov)
|
||||
endif()
|
||||
|
||||
set(_profraw_dir "${PROJECT_BINARY_DIR}/${Cov_NAME}-profraw")
|
||||
set(_profdata "${PROJECT_BINARY_DIR}/${Cov_NAME}.profdata")
|
||||
|
||||
# Resolve binary path: accept either an absolute path or a bare target name
|
||||
# (resolved against PROJECT_BINARY_DIR). Splice the resolved path back into
|
||||
# Cov_EXECUTABLE so the run command invokes it via absolute path - bare
|
||||
# names aren't on PATH and the build dir isn't `.` either.
|
||||
list(GET Cov_EXECUTABLE 0 _exec_name)
|
||||
if(IS_ABSOLUTE "${_exec_name}")
|
||||
set(_binary "${_exec_name}")
|
||||
else()
|
||||
set(_binary "${PROJECT_BINARY_DIR}/${_exec_name}")
|
||||
list(REMOVE_AT Cov_EXECUTABLE 0)
|
||||
list(PREPEND Cov_EXECUTABLE "${_binary}")
|
||||
endif()
|
||||
|
||||
# llvm-cov takes a single -ignore-filename-regex; OR our excludes together.
|
||||
set(_ignore_regex "")
|
||||
foreach(EXC IN LISTS Cov_EXCLUDE)
|
||||
if(_ignore_regex)
|
||||
string(APPEND _ignore_regex "|")
|
||||
endif()
|
||||
string(APPEND _ignore_regex "${EXC}")
|
||||
endforeach()
|
||||
set(_filter "")
|
||||
if(_ignore_regex)
|
||||
set(_filter "-ignore-filename-regex='${_ignore_regex}'")
|
||||
endif()
|
||||
|
||||
# Pick llvm-cov subcommand + output file for the requested format. Each
|
||||
# branch builds a single shell command string that we'll hand to bash -c.
|
||||
if(Cov_FORMAT STREQUAL "lcov")
|
||||
set(_output "${PROJECT_BINARY_DIR}/coverage.lcov")
|
||||
set(_report_sh "${LLVM_COV_PATH} export -instr-profile='${_profdata}' -format=lcov ${_filter} '${_binary}' > '${_output}'")
|
||||
elseif(Cov_FORMAT STREQUAL "json")
|
||||
set(_output "${PROJECT_BINARY_DIR}/coverage.json")
|
||||
set(_report_sh "${LLVM_COV_PATH} export -instr-profile='${_profdata}' -format=text ${_filter} '${_binary}' > '${_output}'")
|
||||
elseif(Cov_FORMAT STREQUAL "txt" OR Cov_FORMAT STREQUAL "text")
|
||||
set(_output "${PROJECT_BINARY_DIR}/coverage.txt")
|
||||
set(_report_sh "${LLVM_COV_PATH} report -instr-profile='${_profdata}' ${_filter} '${_binary}' > '${_output}'")
|
||||
elseif(Cov_FORMAT STREQUAL "html" OR Cov_FORMAT STREQUAL "html-details")
|
||||
set(_output "${PROJECT_BINARY_DIR}/${Cov_NAME}/index.html")
|
||||
set(_report_sh "${LLVM_COV_PATH} show -instr-profile='${_profdata}' -format=html -output-dir='${PROJECT_BINARY_DIR}/${Cov_NAME}' ${_filter} '${_binary}'")
|
||||
else()
|
||||
message(FATAL_ERROR "coverage_tool=llvm: unsupported coverage_format '${Cov_FORMAT}' (use lcov|json|txt|html)")
|
||||
endif()
|
||||
|
||||
set(_merge_sh "${LLVM_PROFDATA_PATH} merge -sparse -o '${_profdata}' '${_profraw_dir}'/*.profraw")
|
||||
|
||||
if(CODE_COVERAGE_VERBOSE)
|
||||
message(STATUS "[coverage:llvm] binary: ${_binary}")
|
||||
message(STATUS "[coverage:llvm] profraw: ${_profraw_dir}")
|
||||
message(STATUS "[coverage:llvm] profdata: ${_profdata}")
|
||||
message(STATUS "[coverage:llvm] format: ${Cov_FORMAT}")
|
||||
message(STATUS "[coverage:llvm] output: ${_output}")
|
||||
if(_ignore_regex)
|
||||
message(STATUS "[coverage:llvm] ignore: ${_ignore_regex}")
|
||||
endif()
|
||||
message(STATUS "[coverage:llvm] merge: ${_merge_sh}")
|
||||
message(STATUS "[coverage:llvm] report: ${_report_sh}")
|
||||
endif()
|
||||
|
||||
# %m: hash of the binary, %p: pid. Wipe the dir up front so stale profraw
|
||||
# files can't leak into a fresh merge.
|
||||
add_custom_target(${Cov_NAME}
|
||||
COMMAND ${CMAKE_COMMAND} -E rm -rf "${_profraw_dir}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${_profraw_dir}"
|
||||
COMMAND ${CMAKE_COMMAND} -E env
|
||||
"LLVM_PROFILE_FILE=${_profraw_dir}/rippled-%m-%p.profraw"
|
||||
${Cov_EXECUTABLE} ${Cov_EXECUTABLE_ARGS}
|
||||
COMMAND bash -c "${_merge_sh}"
|
||||
COMMAND bash -c "${_report_sh}"
|
||||
BYPRODUCTS ${_output}
|
||||
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
|
||||
DEPENDS ${Cov_DEPENDENCIES}
|
||||
VERBATIM
|
||||
COMMENT "Running llvm-cov (${Cov_FORMAT}) -> ${_output}"
|
||||
)
|
||||
endfunction()
|
||||
@@ -11,6 +11,21 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(coverage_tool STREQUAL "llvm")
|
||||
include(CodeCoverageLLVM)
|
||||
|
||||
setup_target_for_coverage_llvm(
|
||||
NAME coverage
|
||||
FORMAT ${coverage_format}
|
||||
EXECUTABLE rippled
|
||||
EXECUTABLE_ARGS --unittest$<$<BOOL:${coverage_test}>:=${coverage_test}> --unittest-jobs ${coverage_test_parallelism} --quiet --unittest-log
|
||||
EXCLUDE "src/test" "include/xrpl/beast/test" "include/xrpl/beast/unit_test" "${CMAKE_BINARY_DIR}/pb-xrpl.libpb"
|
||||
DEPENDENCIES rippled
|
||||
)
|
||||
return()
|
||||
endif()
|
||||
|
||||
# coverage_tool == "gcov" (default): existing gcovr-driven pipeline.
|
||||
include(CodeCoverage)
|
||||
|
||||
# The instructions for these commands come from the `CodeCoverage` module,
|
||||
|
||||
@@ -28,15 +28,17 @@ target_compile_options (opts
|
||||
$<$<AND:$<BOOL:${is_gcc}>,$<COMPILE_LANGUAGE:CXX>>:-Wsuggest-override>
|
||||
$<$<BOOL:${is_gcc}>:-Wno-maybe-uninitialized>
|
||||
$<$<BOOL:${perf}>:-fno-omit-frame-pointer>
|
||||
$<$<AND:$<BOOL:${is_gcc}>,$<BOOL:${coverage}>>:-g --coverage -fprofile-abs-path>
|
||||
$<$<AND:$<BOOL:${is_clang}>,$<BOOL:${coverage}>>:-g --coverage>
|
||||
$<$<AND:$<BOOL:${is_gcc}>,$<BOOL:${coverage}>,$<STREQUAL:${coverage_tool},gcov>>:-g --coverage -fprofile-abs-path>
|
||||
$<$<AND:$<BOOL:${is_clang}>,$<BOOL:${coverage}>,$<STREQUAL:${coverage_tool},gcov>>:-g --coverage>
|
||||
$<$<AND:$<BOOL:${is_clang}>,$<BOOL:${coverage}>,$<STREQUAL:${coverage_tool},llvm>>:-g -fprofile-instr-generate -fcoverage-mapping>
|
||||
$<$<BOOL:${profile}>:-pg>
|
||||
$<$<AND:$<BOOL:${is_gcc}>,$<BOOL:${profile}>>:-p>)
|
||||
|
||||
target_link_libraries (opts
|
||||
INTERFACE
|
||||
$<$<AND:$<BOOL:${is_gcc}>,$<BOOL:${coverage}>>:-g --coverage -fprofile-abs-path>
|
||||
$<$<AND:$<BOOL:${is_clang}>,$<BOOL:${coverage}>>:-g --coverage>
|
||||
$<$<AND:$<BOOL:${is_gcc}>,$<BOOL:${coverage}>,$<STREQUAL:${coverage_tool},gcov>>:-g --coverage -fprofile-abs-path>
|
||||
$<$<AND:$<BOOL:${is_clang}>,$<BOOL:${coverage}>,$<STREQUAL:${coverage_tool},gcov>>:-g --coverage>
|
||||
$<$<AND:$<BOOL:${is_clang}>,$<BOOL:${coverage}>,$<STREQUAL:${coverage_tool},llvm>>:-g -fprofile-instr-generate -fcoverage-mapping>
|
||||
$<$<BOOL:${profile}>:-pg>
|
||||
$<$<AND:$<BOOL:${is_gcc}>,$<BOOL:${profile}>>:-p>)
|
||||
|
||||
@@ -95,6 +97,7 @@ target_link_libraries (ripple_syslibs
|
||||
crypt32
|
||||
>
|
||||
$<$<NOT:$<BOOL:${MSVC}>>:dl>
|
||||
$<$<NOT:$<BOOL:${MSVC}>>:resolv>
|
||||
$<$<NOT:$<OR:$<BOOL:${MSVC}>,$<BOOL:${APPLE}>>>:rt>)
|
||||
|
||||
if (NOT MSVC)
|
||||
|
||||
@@ -29,13 +29,25 @@ if(is_gcc OR is_clang)
|
||||
"Unit tests parallelism for the purpose of coverage report.")
|
||||
set(coverage_format "html-details" CACHE STRING
|
||||
"Output format of the coverage report.")
|
||||
set(coverage_tool "gcov" CACHE STRING
|
||||
"Coverage instrumentation tool: 'gcov' (default, gcc/clang via --coverage + gcovr) or 'llvm' (clang only, native source-based coverage via -fprofile-instr-generate).")
|
||||
set_property(CACHE coverage_tool PROPERTY STRINGS "gcov" "llvm")
|
||||
if(NOT coverage_tool MATCHES "^(gcov|llvm)$")
|
||||
message(FATAL_ERROR "coverage_tool must be 'gcov' or 'llvm', got '${coverage_tool}'")
|
||||
endif()
|
||||
set(coverage_extra_args "" CACHE STRING
|
||||
"Additional arguments to pass to gcovr.")
|
||||
"Additional arguments to pass to gcovr (gcov tool only).")
|
||||
set(coverage_test "" CACHE STRING
|
||||
"On gcc & clang, the specific unit test(s) to run for coverage. Default is all tests.")
|
||||
if(coverage_test AND NOT coverage)
|
||||
set(coverage ON CACHE BOOL "gcc/clang only" FORCE)
|
||||
endif()
|
||||
# Validate after coverage_test may have flipped coverage on, otherwise
|
||||
# `-Dcoverage_tool=llvm -Dcoverage_test=Foo` on gcc would silently slip
|
||||
# past the Clang guard and produce a broken instrumentation combo.
|
||||
if(coverage AND coverage_tool STREQUAL "llvm" AND NOT is_clang)
|
||||
message(FATAL_ERROR "coverage_tool=llvm requires Clang (got ${CMAKE_CXX_COMPILER_ID})")
|
||||
endif()
|
||||
option(wextra "compile with extra gcc/clang warnings enabled" ON)
|
||||
else()
|
||||
set(profile OFF CACHE BOOL "gcc/clang only" FORCE)
|
||||
@@ -112,7 +124,7 @@ option(beast_no_unit_test_inline
|
||||
"Prevents unit test definitions from being inserted into global table"
|
||||
OFF)
|
||||
option(single_io_service_thread
|
||||
"Restricts the number of threads calling io_service::run to one. \
|
||||
"Restricts the number of threads calling io_context::run to one. \
|
||||
This can be useful when debugging."
|
||||
OFF)
|
||||
option(boost_show_deprecated
|
||||
|
||||
@@ -32,9 +32,9 @@ target_link_libraries(ripple_boost
|
||||
Boost::date_time
|
||||
Boost::filesystem
|
||||
Boost::json
|
||||
Boost::process
|
||||
Boost::program_options
|
||||
Boost::regex
|
||||
Boost::system
|
||||
Boost::iostreams
|
||||
Boost::thread)
|
||||
if(Boost_COMPILER)
|
||||
|
||||
+129
-127
@@ -2,158 +2,160 @@ from conan import ConanFile
|
||||
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
|
||||
import re
|
||||
|
||||
class Xrpl(ConanFile):
|
||||
name = 'xrpl'
|
||||
|
||||
license = 'ISC'
|
||||
author = 'John Freeman <[email protected]>'
|
||||
url = 'https://github.com/xrplf/rippled'
|
||||
description = 'The XRP Ledger'
|
||||
settings = 'os', 'compiler', 'build_type', 'arch'
|
||||
class Xrpl(ConanFile):
|
||||
name = "xrpl"
|
||||
|
||||
license = "ISC"
|
||||
author = "John Freeman <[email protected]>"
|
||||
url = "https://github.com/xrplf/rippled"
|
||||
description = "The XRP Ledger"
|
||||
settings = "os", "compiler", "build_type", "arch"
|
||||
options = {
|
||||
'assertions': [True, False],
|
||||
'coverage': [True, False],
|
||||
'fPIC': [True, False],
|
||||
'jemalloc': [True, False],
|
||||
'rocksdb': [True, False],
|
||||
'shared': [True, False],
|
||||
'static': [True, False],
|
||||
'tests': [True, False],
|
||||
'unity': [True, False],
|
||||
'xrpld': [True, False],
|
||||
'with_wasmedge': [True, False],
|
||||
'tool_requires_b2': [True, False],
|
||||
"assertions": [True, False],
|
||||
"coverage": [True, False],
|
||||
"fPIC": [True, False],
|
||||
"jemalloc": [True, False],
|
||||
"rocksdb": [True, False],
|
||||
"shared": [True, False],
|
||||
"static": [True, False],
|
||||
"tests": [True, False],
|
||||
"unity": [True, False],
|
||||
"xrpld": [True, False],
|
||||
"with_wasmedge": [True, False],
|
||||
"tool_requires_b2": [True, False],
|
||||
}
|
||||
|
||||
requires = [
|
||||
'date/3.0.3',
|
||||
'grpc/1.50.1',
|
||||
'libarchive/3.7.6',
|
||||
'magic_enum/0.9.5',
|
||||
'nudb/2.0.8',
|
||||
'openssl/3.6.0',
|
||||
'soci/4.0.3@xahaud/stable',
|
||||
'xxhash/0.8.2',
|
||||
'zlib/1.3.1',
|
||||
"date/3.0.3",
|
||||
"grpc/1.50.1",
|
||||
"libarchive/3.7.6",
|
||||
"magic_enum/0.9.5",
|
||||
"nudb/2.0.9",
|
||||
"openssl/3.6.0",
|
||||
"soci/4.0.3@xahaud/stable",
|
||||
"xxhash/0.8.2",
|
||||
"zlib/1.3.1",
|
||||
]
|
||||
|
||||
tool_requires = [
|
||||
'protobuf/3.21.12',
|
||||
"protobuf/3.21.12",
|
||||
]
|
||||
|
||||
default_options = {
|
||||
'assertions': False,
|
||||
'coverage': False,
|
||||
'fPIC': True,
|
||||
'jemalloc': False,
|
||||
'rocksdb': True,
|
||||
'shared': False,
|
||||
'static': True,
|
||||
'tests': False,
|
||||
'unity': False,
|
||||
'xrpld': False,
|
||||
'with_wasmedge': True,
|
||||
'tool_requires_b2': False,
|
||||
|
||||
'date/*:header_only': False,
|
||||
'grpc/*:shared': False,
|
||||
'grpc/*:secure': True,
|
||||
'libarchive/*:shared': False,
|
||||
'libarchive/*:with_acl': False,
|
||||
'libarchive/*:with_bzip2': False,
|
||||
'libarchive/*:with_cng': False,
|
||||
'libarchive/*:with_expat': False,
|
||||
'libarchive/*:with_iconv': False,
|
||||
'libarchive/*:with_libxml2': False,
|
||||
'libarchive/*:with_lz4': True,
|
||||
'libarchive/*:with_lzma': False,
|
||||
'libarchive/*:with_lzo': False,
|
||||
'libarchive/*:with_nettle': False,
|
||||
'libarchive/*:with_openssl': False,
|
||||
'libarchive/*:with_pcreposix': False,
|
||||
'libarchive/*:with_xattr': False,
|
||||
'libarchive/*:with_zlib': False,
|
||||
'lz4/*:shared': False,
|
||||
'openssl/*:shared': False,
|
||||
'protobuf/*:shared': False,
|
||||
'protobuf/*:with_zlib': True,
|
||||
'rocksdb/*:enable_sse': False,
|
||||
'rocksdb/*:lite': False,
|
||||
'rocksdb/*:shared': False,
|
||||
'rocksdb/*:use_rtti': True,
|
||||
'rocksdb/*:with_jemalloc': False,
|
||||
'rocksdb/*:with_lz4': True,
|
||||
'rocksdb/*:with_snappy': True,
|
||||
'snappy/*:shared': False,
|
||||
'soci/*:shared': False,
|
||||
'soci/*:with_sqlite3': True,
|
||||
'soci/*:with_boost': True,
|
||||
'xxhash/*:shared': False,
|
||||
"assertions": False,
|
||||
"coverage": False,
|
||||
"fPIC": True,
|
||||
"jemalloc": False,
|
||||
"rocksdb": True,
|
||||
"shared": False,
|
||||
"static": True,
|
||||
"tests": False,
|
||||
"unity": False,
|
||||
"xrpld": False,
|
||||
"with_wasmedge": True,
|
||||
"tool_requires_b2": False,
|
||||
"boost/*:without_cobalt": True,
|
||||
"date/*:header_only": False,
|
||||
"grpc/*:shared": False,
|
||||
"grpc/*:secure": True,
|
||||
"libarchive/*:shared": False,
|
||||
"libarchive/*:with_acl": False,
|
||||
"libarchive/*:with_bzip2": False,
|
||||
"libarchive/*:with_cng": False,
|
||||
"libarchive/*:with_expat": False,
|
||||
"libarchive/*:with_iconv": False,
|
||||
"libarchive/*:with_libxml2": False,
|
||||
"libarchive/*:with_lz4": True,
|
||||
"libarchive/*:with_lzma": False,
|
||||
"libarchive/*:with_lzo": False,
|
||||
"libarchive/*:with_nettle": False,
|
||||
"libarchive/*:with_openssl": False,
|
||||
"libarchive/*:with_pcreposix": False,
|
||||
"libarchive/*:with_xattr": False,
|
||||
"libarchive/*:with_zlib": False,
|
||||
"lz4/*:shared": False,
|
||||
"openssl/*:shared": False,
|
||||
"protobuf/*:shared": False,
|
||||
"protobuf/*:with_zlib": True,
|
||||
"rocksdb/*:enable_sse": False,
|
||||
"rocksdb/*:lite": False,
|
||||
"rocksdb/*:shared": False,
|
||||
"rocksdb/*:use_rtti": True,
|
||||
"rocksdb/*:with_jemalloc": False,
|
||||
"rocksdb/*:with_lz4": True,
|
||||
"rocksdb/*:with_snappy": True,
|
||||
"snappy/*:shared": False,
|
||||
"soci/*:shared": False,
|
||||
"soci/*:with_sqlite3": True,
|
||||
"soci/*:with_boost": True,
|
||||
"xxhash/*:shared": False,
|
||||
}
|
||||
|
||||
def set_version(self):
|
||||
path = f'{self.recipe_folder}/src/libxrpl/protocol/BuildInfo.cpp'
|
||||
regex = r'versionString\s?=\s?\"(.*)\"'
|
||||
with open(path, 'r') as file:
|
||||
path = f"{self.recipe_folder}/src/libxrpl/protocol/BuildInfo.cpp"
|
||||
regex = r"versionString\s?=\s?\"(.*)\""
|
||||
with open(path, "r") as file:
|
||||
matches = (re.search(regex, line) for line in file)
|
||||
match = next(m for m in matches if m)
|
||||
self.version = match.group(1)
|
||||
|
||||
def build_requirements(self):
|
||||
# These provide build tools (protoc, grpc plugins) that run during build
|
||||
self.tool_requires('grpc/1.50.1')
|
||||
self.tool_requires("grpc/1.50.1")
|
||||
# Explicitly require b2 (e.g. for building from source for glibc compatibility)
|
||||
if self.options.tool_requires_b2:
|
||||
self.tool_requires('b2/5.3.2')
|
||||
self.tool_requires("b2/5.3.2")
|
||||
|
||||
def configure(self):
|
||||
if self.settings.compiler == 'apple-clang':
|
||||
self.options['boost/*'].visibility = 'global'
|
||||
if self.settings.compiler == "apple-clang":
|
||||
self.options["boost/*"].visibility = "global"
|
||||
|
||||
def requirements(self):
|
||||
# Force sqlite3 version to avoid conflicts with soci
|
||||
self.requires('sqlite3/3.47.0', override=True)
|
||||
self.requires("sqlite3/3.47.0", override=True)
|
||||
# Force our custom snappy build for all dependencies
|
||||
self.requires('snappy/1.1.10@xahaud/stable', override=True)
|
||||
self.requires("snappy/1.1.10@xahaud/stable", override=True)
|
||||
# Force boost version for all dependencies to avoid conflicts
|
||||
self.requires('boost/1.86.0', override=True)
|
||||
self.requires('lz4/1.10.0', force=True)
|
||||
self.requires("boost/1.91.0", override=True)
|
||||
self.requires("lz4/1.10.0", force=True)
|
||||
|
||||
if self.options.with_wasmedge:
|
||||
self.requires('wasmedge/0.11.2@xahaud/stable')
|
||||
self.requires("wasmedge/0.11.2@xahaud/stable")
|
||||
if self.options.jemalloc:
|
||||
self.requires('jemalloc/5.3.0')
|
||||
self.requires("jemalloc/5.3.0")
|
||||
if self.options.rocksdb:
|
||||
self.requires('rocksdb/6.29.5')
|
||||
self.requires("rocksdb/6.29.5")
|
||||
|
||||
exports_sources = (
|
||||
'CMakeLists.txt',
|
||||
'bin/getRippledInfo',
|
||||
'cfg/*',
|
||||
'cmake/*',
|
||||
'external/*',
|
||||
'include/*',
|
||||
'src/*',
|
||||
"CMakeLists.txt",
|
||||
"bin/getRippledInfo",
|
||||
"cfg/*",
|
||||
"cmake/*",
|
||||
"external/*",
|
||||
"include/*",
|
||||
"src/*",
|
||||
)
|
||||
|
||||
def layout(self):
|
||||
cmake_layout(self)
|
||||
# Fix this setting to follow the default introduced in Conan 1.48
|
||||
# to align with our build instructions.
|
||||
self.folders.generators = 'build/generators'
|
||||
self.folders.generators = "build/generators"
|
||||
|
||||
generators = "CMakeDeps"
|
||||
|
||||
generators = 'CMakeDeps'
|
||||
def generate(self):
|
||||
tc = CMakeToolchain(self)
|
||||
tc.variables['tests'] = self.options.tests
|
||||
tc.variables['assert'] = self.options.assertions
|
||||
tc.variables['coverage'] = self.options.coverage
|
||||
tc.variables['jemalloc'] = self.options.jemalloc
|
||||
tc.variables['rocksdb'] = self.options.rocksdb
|
||||
tc.variables['BUILD_SHARED_LIBS'] = self.options.shared
|
||||
tc.variables['static'] = self.options.static
|
||||
tc.variables['unity'] = self.options.unity
|
||||
tc.variables['xrpld'] = self.options.xrpld
|
||||
tc.variables["tests"] = self.options.tests
|
||||
tc.variables["assert"] = self.options.assertions
|
||||
tc.variables["coverage"] = self.options.coverage
|
||||
tc.variables["jemalloc"] = self.options.jemalloc
|
||||
tc.variables["rocksdb"] = self.options.rocksdb
|
||||
tc.variables["BUILD_SHARED_LIBS"] = self.options.shared
|
||||
tc.variables["static"] = self.options.static
|
||||
tc.variables["unity"] = self.options.unity
|
||||
tc.variables["xrpld"] = self.options.xrpld
|
||||
tc.generate()
|
||||
|
||||
def build(self):
|
||||
@@ -168,29 +170,29 @@ class Xrpl(ConanFile):
|
||||
cmake.install()
|
||||
|
||||
def package_info(self):
|
||||
libxrpl = self.cpp_info.components['libxrpl']
|
||||
libxrpl = self.cpp_info.components["libxrpl"]
|
||||
libxrpl.libs = [
|
||||
'xrpl',
|
||||
'xrpl.libpb',
|
||||
'ed25519',
|
||||
'secp256k1',
|
||||
"xrpl",
|
||||
"xrpl.libpb",
|
||||
"ed25519",
|
||||
"secp256k1",
|
||||
]
|
||||
# TODO: Fix the protobufs to include each other relative to
|
||||
# `include/`, not `include/ripple/proto/`.
|
||||
libxrpl.includedirs = ['include', 'include/ripple/proto']
|
||||
libxrpl.includedirs = ["include", "include/ripple/proto"]
|
||||
libxrpl.requires = [
|
||||
'boost::boost',
|
||||
'date::date',
|
||||
'grpc::grpc++',
|
||||
'libarchive::libarchive',
|
||||
'lz4::lz4',
|
||||
'nudb::nudb',
|
||||
'openssl::crypto',
|
||||
'protobuf::libprotobuf',
|
||||
'soci::soci',
|
||||
'sqlite3::sqlite',
|
||||
'xxhash::xxhash',
|
||||
'zlib::zlib',
|
||||
"boost::boost",
|
||||
"date::date",
|
||||
"grpc::grpc++",
|
||||
"libarchive::libarchive",
|
||||
"lz4::lz4",
|
||||
"nudb::nudb",
|
||||
"openssl::crypto",
|
||||
"protobuf::libprotobuf",
|
||||
"soci::soci",
|
||||
"sqlite3::sqlite",
|
||||
"xxhash::xxhash",
|
||||
"zlib::zlib",
|
||||
]
|
||||
if self.options.rocksdb:
|
||||
libxrpl.requires.append('rocksdb::librocksdb')
|
||||
libxrpl.requires.append("rocksdb::librocksdb")
|
||||
@@ -47,5 +47,6 @@
|
||||
#define MEM_OVERLAP -43
|
||||
#define TOO_MANY_STATE_MODIFICATIONS -44
|
||||
#define TOO_MANY_NAMESPACES -45
|
||||
#define TOO_LITTLE_ENTROPY -46
|
||||
#define HOOK_ERROR_CODES
|
||||
#endif //HOOK_ERROR_CODES
|
||||
@@ -339,6 +339,12 @@ prepare(
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len);
|
||||
|
||||
extern int64_t
|
||||
dice(uint32_t sides);
|
||||
|
||||
extern int64_t
|
||||
util_random(uint32_t write_ptr, uint32_t write_len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
+23
-23
@@ -22,29 +22,29 @@ APPLY_HOOK="$SCRIPT_DIR/../include/xrpl/hook/hook_api.macro"
|
||||
return s;
|
||||
}
|
||||
|
||||
{
|
||||
line = $0;
|
||||
|
||||
# Skip block comments
|
||||
if (line ~ /\/\*/) {
|
||||
next;
|
||||
}
|
||||
|
||||
# Look for comment lines that start with // and contain function signature
|
||||
if (line ~ /^[[:space:]]*\/\/[[:space:]]*[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]+[a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*\(/) {
|
||||
# Remove leading // and trim
|
||||
sub(/^[[:space:]]*\/\/[[:space:]]*/, "", line);
|
||||
line = trim(line);
|
||||
|
||||
# Check if function name is "_g" to add attribute
|
||||
if (line ~ /[[:space:]]+_g[[:space:]]*\(/) {
|
||||
# Insert __attribute__((noduplicate)) before _g
|
||||
sub(/[[:space:]]+_g/, " __attribute__((noduplicate)) _g", line);
|
||||
}
|
||||
|
||||
# printf("\n");
|
||||
|
||||
printf("extern %s\n\n", line);
|
||||
# Each entry is HOOK_API_DEFINITION(RET, NAME, (PARAMS), AMENDMENT),
|
||||
# possibly spread over several lines. Collect until the parentheses
|
||||
# balance, then split on the first two commas; the parameter tuple
|
||||
# is the first parenthesised group.
|
||||
function emit(s, i, ret, name, p, q, params) {
|
||||
sub(/^[[:space:]]*HOOK_API_DEFINITION[[:space:]]*\(/, "", s);
|
||||
gsub(/[[:space:]]+/, " ", s);
|
||||
i = index(s, ","); ret = trim(substr(s, 1, i - 1)); s = substr(s, i + 1);
|
||||
i = index(s, ","); name = trim(substr(s, 1, i - 1)); s = substr(s, i + 1);
|
||||
p = index(s, "("); q = index(s, ")");
|
||||
params = trim(substr(s, p + 1, q - p - 1));
|
||||
if (name == "_g")
|
||||
name = "__attribute__((noduplicate)) _g";
|
||||
printf("extern %s %s(%s);\n\n", ret, name, params);
|
||||
}
|
||||
|
||||
/^[[:space:]]*HOOK_API_DEFINITION[[:space:]]*\(/ { buf = ""; collecting = 1; }
|
||||
|
||||
collecting {
|
||||
buf = buf " " $0;
|
||||
if (gsub(/\(/, "(", buf) == gsub(/\)/, ")", buf)) {
|
||||
collecting = 0;
|
||||
emit(buf);
|
||||
}
|
||||
}
|
||||
' "$APPLY_HOOK"
|
||||
|
||||
+2
-1
@@ -38,6 +38,7 @@
|
||||
#define KEYLET_HOOK_DEFINITION 24
|
||||
#define KEYLET_HOOK_STATE_DIR 25
|
||||
#define KEYLET_CRON 26
|
||||
#define KEYLET_MANIFEST 37
|
||||
|
||||
#define COMPARE_EQUAL 1U
|
||||
#define COMPARE_LESS 2U
|
||||
@@ -45,8 +46,8 @@
|
||||
|
||||
#include "error.h"
|
||||
#include "extern.h"
|
||||
#include "sfcodes.h"
|
||||
#include "macro.h"
|
||||
#include "sfcodes.h"
|
||||
#include "tts.h"
|
||||
|
||||
#include "ls_flags.h"
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
#define sfHookApiVersion ((1U << 16U) + 20U)
|
||||
#define sfHookStateScale ((1U << 16U) + 21U)
|
||||
#define sfLedgerFixType ((1U << 16U) + 22U)
|
||||
#define sfEntropyCount ((1U << 16U) + 99U)
|
||||
#define sfEntropyDenominator ((1U << 16U) + 100U)
|
||||
#define sfNetworkID ((2U << 16U) + 1U)
|
||||
#define sfFlags ((2U << 16U) + 2U)
|
||||
#define sfSourceTag ((2U << 16U) + 3U)
|
||||
@@ -80,6 +82,7 @@
|
||||
#define sfRewardTime ((2U << 16U) + 98U)
|
||||
#define sfRewardLgrFirst ((2U << 16U) + 99U)
|
||||
#define sfRewardLgrLast ((2U << 16U) + 100U)
|
||||
#define sfAppLoaderSequence ((2U << 16U) + 101U)
|
||||
#define sfIndexNext ((3U << 16U) + 1U)
|
||||
#define sfIndexPrevious ((3U << 16U) + 2U)
|
||||
#define sfBookNode ((3U << 16U) + 3U)
|
||||
@@ -152,6 +155,8 @@
|
||||
#define sfEscrowID ((5U << 16U) + 35U)
|
||||
#define sfURITokenID ((5U << 16U) + 36U)
|
||||
#define sfDomainID ((5U << 16U) + 37U)
|
||||
#define sfNextRandomDigest ((5U << 16U) + 89U)
|
||||
#define sfRandomData ((5U << 16U) + 90U)
|
||||
#define sfManifestID ((5U << 16U) + 91U)
|
||||
#define sfHookOnOutgoing ((5U << 16U) + 93U)
|
||||
#define sfHookOnIncoming ((5U << 16U) + 94U)
|
||||
@@ -222,6 +227,7 @@
|
||||
#define sfProvider ((7U << 16U) + 30U)
|
||||
#define sfMPTokenMetadata ((7U << 16U) + 31U)
|
||||
#define sfCredentialType ((7U << 16U) + 32U)
|
||||
#define sfAppLoader ((7U << 16U) + 96U)
|
||||
#define sfHookName ((7U << 16U) + 97U)
|
||||
#define sfRemarkValue ((7U << 16U) + 98U)
|
||||
#define sfRemarkName ((7U << 16U) + 99U)
|
||||
@@ -289,6 +295,7 @@
|
||||
#define sfXChainCreateAccountAttestationCollectionElement ((14U << 16U) + 31U)
|
||||
#define sfPriceData ((14U << 16U) + 32U)
|
||||
#define sfCredential ((14U << 16U) + 33U)
|
||||
#define sfRandomDigestEntry ((14U << 16U) + 88U)
|
||||
#define sfManifest ((14U << 16U) + 90U)
|
||||
#define sfAmountEntry ((14U << 16U) + 91U)
|
||||
#define sfMintURIToken ((14U << 16U) + 92U)
|
||||
@@ -321,6 +328,7 @@
|
||||
#define sfAuthorizeCredentials ((15U << 16U) + 26U)
|
||||
#define sfUnauthorizeCredentials ((15U << 16U) + 27U)
|
||||
#define sfAcceptedCredentials ((15U << 16U) + 28U)
|
||||
#define sfRandomDigests ((15U << 16U) + 91U)
|
||||
#define sfAmounts ((15U << 16U) + 92U)
|
||||
#define sfHookEmissions ((15U << 16U) + 93U)
|
||||
#define sfImportVLKeys ((15U << 16U) + 94U)
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
#define ttNFTOKEN_MODIFY 70
|
||||
#define ttPERMISSIONED_DOMAIN_SET 71
|
||||
#define ttPERMISSIONED_DOMAIN_DELETE 72
|
||||
#define ttENTROPY 89
|
||||
#define ttMANIFEST_SET 91
|
||||
#define ttCRON 92
|
||||
#define ttCRON_SET 93
|
||||
|
||||
@@ -23,9 +23,66 @@
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/** Add two signed 64-bit integers, returning std::nullopt when the exact
|
||||
* mathematical sum is not representable in std::int64_t.
|
||||
*/
|
||||
[[nodiscard]] constexpr std::optional<std::int64_t>
|
||||
checkedAdd(std::int64_t a, std::int64_t b) noexcept
|
||||
{
|
||||
using L = std::numeric_limits<std::int64_t>;
|
||||
|
||||
if ((b > 0 && a > L::max() - b) || (b < 0 && a < L::min() - b))
|
||||
return std::nullopt;
|
||||
|
||||
return a + b;
|
||||
}
|
||||
|
||||
/** Subtract two signed 64-bit integers, returning std::nullopt when the exact
|
||||
* mathematical difference is not representable in std::int64_t.
|
||||
*/
|
||||
[[nodiscard]] constexpr std::optional<std::int64_t>
|
||||
checkedSub(std::int64_t a, std::int64_t b) noexcept
|
||||
{
|
||||
using L = std::numeric_limits<std::int64_t>;
|
||||
|
||||
if ((b > 0 && a < L::min() + b) || (b < 0 && a > L::max() + b))
|
||||
return std::nullopt;
|
||||
|
||||
return a - b;
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
using i64_limits = std::numeric_limits<std::int64_t>;
|
||||
|
||||
static_assert(checkedAdd(0, 0) == 0);
|
||||
static_assert(checkedAdd(1, -1) == 0);
|
||||
static_assert(checkedAdd(-5, 2) == -3);
|
||||
static_assert(!checkedAdd(i64_limits::max(), 1).has_value());
|
||||
static_assert(!checkedAdd(i64_limits::min(), -1).has_value());
|
||||
static_assert(checkedAdd(i64_limits::max() - 1, 1) == i64_limits::max());
|
||||
static_assert(checkedAdd(i64_limits::min(), i64_limits::max()) == -1);
|
||||
static_assert(checkedAdd(i64_limits::max(), i64_limits::min()) == -1);
|
||||
static_assert(!checkedAdd(i64_limits::max(), i64_limits::max()).has_value());
|
||||
static_assert(!checkedAdd(i64_limits::min(), i64_limits::min()).has_value());
|
||||
|
||||
static_assert(checkedSub(0, 0) == 0);
|
||||
static_assert(checkedSub(1, 1) == 0);
|
||||
static_assert(checkedSub(-5, 2) == -7);
|
||||
static_assert(checkedSub(-5, -2) == -3);
|
||||
static_assert(!checkedSub(i64_limits::min(), 1).has_value());
|
||||
static_assert(!checkedSub(i64_limits::max(), -1).has_value());
|
||||
static_assert(checkedSub(i64_limits::min() + 1, 1) == i64_limits::min());
|
||||
static_assert(checkedSub(-1, i64_limits::max()) == i64_limits::min());
|
||||
static_assert(!checkedSub(i64_limits::max(), i64_limits::min()).has_value());
|
||||
static_assert(!checkedSub(i64_limits::min(), i64_limits::max()).has_value());
|
||||
} // namespace detail
|
||||
|
||||
/** Calculate one number divided by another number in percentage.
|
||||
* The result is rounded up to the next integer, and capped in the range [0,100]
|
||||
* E.g. calculatePercent(1, 100) = 1 because 1/100 = 0.010000
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
#pragma once
|
||||
|
||||
#include <shared_mutex>
|
||||
|
||||
// On Linux (glibc), std::shared_mutex wraps pthread_rwlock_t initialised
|
||||
// with PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP. This means a
|
||||
// pending exclusive lock() blocks new shared (reader) acquisitions,
|
||||
// causing reader starvation when writers contend frequently.
|
||||
//
|
||||
// On macOS / ARM (libc++), std::shared_mutex is already reader-preferring,
|
||||
// so the same code behaves differently across platforms.
|
||||
//
|
||||
// This header provides ReaderPreferringSharedMutex:
|
||||
// - On Linux it wraps pthread_rwlock_t initialised with
|
||||
// PTHREAD_RWLOCK_PREFER_READER_NP, matching macOS semantics.
|
||||
// - On all other platforms it is a type alias for std::shared_mutex.
|
||||
//
|
||||
// The interface is identical to std::shared_mutex, so it works with
|
||||
// std::shared_lock and std::unique_lock.
|
||||
|
||||
#if defined(__linux__)
|
||||
|
||||
#include <cerrno>
|
||||
#include <pthread.h>
|
||||
#include <system_error>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class ReaderPreferringSharedMutex
|
||||
{
|
||||
pthread_rwlock_t rwlock_;
|
||||
|
||||
public:
|
||||
ReaderPreferringSharedMutex()
|
||||
{
|
||||
pthread_rwlockattr_t attr;
|
||||
pthread_rwlockattr_init(&attr);
|
||||
pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_READER_NP);
|
||||
int rc = pthread_rwlock_init(&rwlock_, &attr);
|
||||
pthread_rwlockattr_destroy(&attr);
|
||||
if (rc != 0)
|
||||
throw std::system_error(
|
||||
rc, std::system_category(), "pthread_rwlock_init");
|
||||
}
|
||||
|
||||
~ReaderPreferringSharedMutex()
|
||||
{
|
||||
pthread_rwlock_destroy(&rwlock_);
|
||||
}
|
||||
|
||||
ReaderPreferringSharedMutex(ReaderPreferringSharedMutex const&) = delete;
|
||||
ReaderPreferringSharedMutex&
|
||||
operator=(ReaderPreferringSharedMutex const&) = delete;
|
||||
|
||||
// Exclusive (writer) locking
|
||||
void
|
||||
lock()
|
||||
{
|
||||
pthread_rwlock_wrlock(&rwlock_);
|
||||
}
|
||||
|
||||
bool
|
||||
try_lock()
|
||||
{
|
||||
return pthread_rwlock_trywrlock(&rwlock_) == 0;
|
||||
}
|
||||
|
||||
void
|
||||
unlock()
|
||||
{
|
||||
pthread_rwlock_unlock(&rwlock_);
|
||||
}
|
||||
|
||||
// Shared (reader) locking
|
||||
void
|
||||
lock_shared()
|
||||
{
|
||||
pthread_rwlock_rdlock(&rwlock_);
|
||||
}
|
||||
|
||||
bool
|
||||
try_lock_shared()
|
||||
{
|
||||
return pthread_rwlock_tryrdlock(&rwlock_) == 0;
|
||||
}
|
||||
|
||||
void
|
||||
unlock_shared()
|
||||
{
|
||||
pthread_rwlock_unlock(&rwlock_);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#else // !__linux__
|
||||
|
||||
namespace ripple {
|
||||
|
||||
// macOS, Windows, etc. — std::shared_mutex is already reader-preferring.
|
||||
using ReaderPreferringSharedMutex = std::shared_mutex;
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -22,7 +22,8 @@
|
||||
|
||||
#include <xrpl/basics/Resolver.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <boost/asio/io_service.hpp>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -32,7 +33,7 @@ public:
|
||||
explicit ResolverAsio() = default;
|
||||
|
||||
static std::unique_ptr<ResolverAsio>
|
||||
New(boost::asio::io_service&, beast::Journal);
|
||||
New(boost::asio::io_context&, beast::Journal);
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -77,7 +77,7 @@ strUnHex(std::size_t strSize, Iterator begin, Iterator end)
|
||||
|
||||
if (strSize & 1)
|
||||
{
|
||||
int c = unxtab[*iter++];
|
||||
int c = unxtab[static_cast<unsigned char>(*iter++)];
|
||||
|
||||
if (c < 0)
|
||||
return {};
|
||||
@@ -87,12 +87,12 @@ strUnHex(std::size_t strSize, Iterator begin, Iterator end)
|
||||
|
||||
while (iter != end)
|
||||
{
|
||||
int cHigh = unxtab[*iter++];
|
||||
int cHigh = unxtab[static_cast<unsigned char>(*iter++)];
|
||||
|
||||
if (cHigh < 0)
|
||||
return {};
|
||||
|
||||
int cLow = unxtab[*iter++];
|
||||
int cLow = unxtab[static_cast<unsigned char>(*iter++)];
|
||||
|
||||
if (cLow < 0)
|
||||
return {};
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 XRPL Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_BASICS_UTF8_H_INCLUDED
|
||||
#define RIPPLE_BASICS_UTF8_H_INCLUDED
|
||||
|
||||
#include <xrpl/basics/Slice.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/** Check that a byte sequence is well-formed UTF-8, excluding the two
|
||||
noncharacters U+FFFE and U+FFFF.
|
||||
|
||||
Rejects:
|
||||
- overlong encodings;
|
||||
- UTF-16 surrogate halves (U+D800..U+DFFF);
|
||||
- scalar values above U+10FFFF, and 0xF5..0xFF lead bytes;
|
||||
- stray continuation bytes;
|
||||
- multi-byte sequences truncated by the end of the buffer;
|
||||
- U+FFFE and U+FFFF (EF BF BE, EF BF BF).
|
||||
|
||||
U+FFFE and U+FFFF are well-formed, but have no use in a URI or a name
|
||||
and exist to be misread: U+FFFE is a byte-swapped BOM, so a client that
|
||||
transcodes to UTF-16/32 and sniffs the byte order can be pushed into
|
||||
decoding the rest of the string with the wrong endianness, and both are
|
||||
commonly used as in-band sentinels. They have always been rejected by
|
||||
URIToken and HookName validation, and this function preserves that.
|
||||
|
||||
This is consensus-critical. For every input the previous decoder could
|
||||
evaluate without reading past the end of the buffer, the result is
|
||||
identical to it. The only difference is that truncated trailing
|
||||
sequences, where the previous decoder read out of bounds, are now always
|
||||
rejected. Any further change to what this accepts needs an amendment.
|
||||
|
||||
@param s The bytes to check.
|
||||
|
||||
@note Total: every byte read is within s.
|
||||
*/
|
||||
bool
|
||||
isValidUTF8(Slice const& s) noexcept;
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/beast/xor_shift_engine.h>
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
@@ -50,87 +49,6 @@ namespace detail {
|
||||
// Determines if a type can be called like an Engine
|
||||
template <class Engine, class Result = typename Engine::result_type>
|
||||
using is_engine = std::is_invocable_r<Result, Engine>;
|
||||
|
||||
// 64 bits from the engine. Width comes from max()-min(), not from the
|
||||
// storage type: a 32-bit engine may use a 64-bit result_type.
|
||||
template <class Engine>
|
||||
std::uint64_t
|
||||
randomU64(Engine& engine)
|
||||
{
|
||||
static_assert(std::is_unsigned_v<typename Engine::result_type>);
|
||||
static_assert(
|
||||
std::numeric_limits<typename Engine::result_type>::digits <= 64);
|
||||
static_assert(Engine::min() < Engine::max());
|
||||
auto const draw = [&engine]() -> std::uint64_t {
|
||||
return static_cast<std::uint64_t>(engine() - Engine::min());
|
||||
};
|
||||
|
||||
// A wrapped cardinality of 0 means 2^64 values: one full-range draw.
|
||||
constexpr auto span =
|
||||
static_cast<std::uint64_t>(Engine::max() - Engine::min());
|
||||
constexpr std::uint64_t range = span + 1u;
|
||||
constexpr bool full = range == 0;
|
||||
constexpr bool powerOfTwo = full || (range & (range - 1u)) == 0;
|
||||
|
||||
if constexpr (powerOfTwo)
|
||||
{
|
||||
constexpr int width = full ? 64 : std::bit_width(range) - 1;
|
||||
if constexpr (width >= 64)
|
||||
return draw();
|
||||
|
||||
std::uint64_t value = 0;
|
||||
int filled = 0;
|
||||
while (filled < 64)
|
||||
{
|
||||
auto const take = width < (64 - filled) ? width : (64 - filled);
|
||||
auto const mask = (std::uint64_t{1} << take) - 1;
|
||||
value |= (draw() & mask) << filled;
|
||||
filled += take;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// [rand.adapt.ibits] for w = 64. R is not a power of two.
|
||||
constexpr int m = std::bit_width(range) - 1;
|
||||
constexpr int nCeil = (64 + m - 1) / m;
|
||||
constexpr int w0Try = 64 / nCeil;
|
||||
constexpr auto y0Try = (std::uint64_t{1} << w0Try) * (range >> w0Try);
|
||||
constexpr bool bump =
|
||||
(range - y0Try) > (y0Try / static_cast<std::uint64_t>(nCeil));
|
||||
constexpr int n = bump ? nCeil + 1 : nCeil;
|
||||
constexpr int w0 = 64 / n;
|
||||
constexpr int n0 = n - (64 % n);
|
||||
constexpr auto y0 = (std::uint64_t{1} << w0) * (range >> w0);
|
||||
constexpr auto y1 =
|
||||
(std::uint64_t{1} << (w0 + 1)) * (range >> (w0 + 1));
|
||||
// R == 3 gives n == 65 and w0 == 0. The first group still
|
||||
// consumes its draw even though it contributes no output bits.
|
||||
static_assert(w0 >= 0 && w0 < 63);
|
||||
|
||||
std::uint64_t word = 0;
|
||||
for (int k = 0; k != n0; ++k)
|
||||
{
|
||||
std::uint64_t u;
|
||||
do
|
||||
{
|
||||
u = draw();
|
||||
} while (u >= y0);
|
||||
word = (word << w0) + (u & ((std::uint64_t{1} << w0) - 1));
|
||||
}
|
||||
for (int k = n0; k != n; ++k)
|
||||
{
|
||||
std::uint64_t u;
|
||||
do
|
||||
{
|
||||
u = draw();
|
||||
} while (u >= y1);
|
||||
constexpr int bits = w0 + 1;
|
||||
word = (word << bits) + (u & ((std::uint64_t{1} << bits) - 1));
|
||||
}
|
||||
return word;
|
||||
}
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
/** Return the default random engine.
|
||||
@@ -198,24 +116,10 @@ rand_int(Engine& engine, Integral min, Integral max)
|
||||
{
|
||||
XRPL_ASSERT(max > min, "ripple::rand_int : max over min inputs");
|
||||
|
||||
// Closed interval. Rejection sampling keeps the result uniform and the
|
||||
// same on libc++ and libstdc++ for a given engine sequence.
|
||||
using U = std::make_unsigned_t<Integral>;
|
||||
auto const span = static_cast<U>(static_cast<U>(max) - static_cast<U>(min));
|
||||
auto const count = static_cast<std::uint64_t>(span) + 1u;
|
||||
if (count == 0)
|
||||
return static_cast<Integral>(detail::randomU64(engine));
|
||||
|
||||
// Values below this threshold are the leftover that would bias x % count.
|
||||
auto const slack = static_cast<std::uint64_t>(-count) % count;
|
||||
std::uint64_t draw;
|
||||
do
|
||||
{
|
||||
draw = detail::randomU64(engine);
|
||||
} while (draw < slack);
|
||||
|
||||
auto const offset = static_cast<U>(draw % count);
|
||||
return static_cast<Integral>(static_cast<U>(static_cast<U>(min) + offset));
|
||||
// This should have no state and constructing it should
|
||||
// be very cheap. If that turns out not to be the case
|
||||
// it could be hand-optimized.
|
||||
return std::uniform_int_distribution<Integral>(min, max)(engine);
|
||||
}
|
||||
|
||||
template <class Integral>
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <boost/asio/basic_waitable_timer.hpp>
|
||||
#include <boost/asio/io_service.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
@@ -31,7 +32,7 @@
|
||||
|
||||
namespace beast {
|
||||
|
||||
/** Measures handler latency on an io_service queue. */
|
||||
/** Measures handler latency on an io_context queue. */
|
||||
template <class Clock>
|
||||
class io_latency_probe
|
||||
{
|
||||
@@ -43,12 +44,12 @@ private:
|
||||
std::condition_variable_any m_cond;
|
||||
std::size_t m_count;
|
||||
duration const m_period;
|
||||
boost::asio::io_service& m_ios;
|
||||
boost::asio::io_context& m_ios;
|
||||
boost::asio::basic_waitable_timer<std::chrono::steady_clock> m_timer;
|
||||
bool m_cancel;
|
||||
|
||||
public:
|
||||
io_latency_probe(duration const& period, boost::asio::io_service& ios)
|
||||
io_latency_probe(duration const& period, boost::asio::io_context& ios)
|
||||
: m_count(1)
|
||||
, m_period(period)
|
||||
, m_ios(ios)
|
||||
@@ -63,16 +64,16 @@ public:
|
||||
cancel(lock, true);
|
||||
}
|
||||
|
||||
/** Return the io_service associated with the latency probe. */
|
||||
/** Return the io_context associated with the latency probe. */
|
||||
/** @{ */
|
||||
boost::asio::io_service&
|
||||
get_io_service()
|
||||
boost::asio::io_context&
|
||||
get_io_context()
|
||||
{
|
||||
return m_ios;
|
||||
}
|
||||
|
||||
boost::asio::io_service const&
|
||||
get_io_service() const
|
||||
boost::asio::io_context const&
|
||||
get_io_context() const
|
||||
{
|
||||
return m_ios;
|
||||
}
|
||||
@@ -108,8 +109,10 @@ public:
|
||||
std::lock_guard lock(m_mutex);
|
||||
if (m_cancel)
|
||||
throw std::logic_error("io_latency_probe is canceled");
|
||||
m_ios.post(sample_op<Handler>(
|
||||
std::forward<Handler>(handler), Clock::now(), false, this));
|
||||
boost::asio::post(
|
||||
m_ios,
|
||||
sample_op<Handler>(
|
||||
std::forward<Handler>(handler), Clock::now(), false, this));
|
||||
}
|
||||
|
||||
/** Initiate continuous i/o latency sampling.
|
||||
@@ -123,8 +126,10 @@ public:
|
||||
std::lock_guard lock(m_mutex);
|
||||
if (m_cancel)
|
||||
throw std::logic_error("io_latency_probe is canceled");
|
||||
m_ios.post(sample_op<Handler>(
|
||||
std::forward<Handler>(handler), Clock::now(), true, this));
|
||||
boost::asio::post(
|
||||
m_ios,
|
||||
sample_op<Handler>(
|
||||
std::forward<Handler>(handler), Clock::now(), true, this));
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -235,12 +240,13 @@ private:
|
||||
// The latency is too high to maintain the desired
|
||||
// period so don't bother with a timer.
|
||||
//
|
||||
m_probe->m_ios.post(
|
||||
boost::asio::post(
|
||||
m_probe->m_ios,
|
||||
sample_op<Handler>(m_handler, now, m_repeat, m_probe));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_probe->m_timer.expires_from_now(when - now);
|
||||
m_probe->m_timer.expires_after(when - now);
|
||||
m_probe->m_timer.async_wait(
|
||||
sample_op<Handler>(m_handler, now, m_repeat, m_probe));
|
||||
}
|
||||
@@ -253,7 +259,8 @@ private:
|
||||
if (!m_probe)
|
||||
return;
|
||||
typename Clock::time_point const now(Clock::now());
|
||||
m_probe->m_ios.post(
|
||||
boost::asio::post(
|
||||
m_probe->m_ios,
|
||||
sample_op<Handler>(m_handler, now, m_repeat, m_probe));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,9 +8,12 @@
|
||||
#ifndef BEAST_TEST_YIELD_TO_HPP
|
||||
#define BEAST_TEST_YIELD_TO_HPP
|
||||
|
||||
#include <boost/asio/io_service.hpp>
|
||||
#include <boost/asio/executor_work_guard.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/thread/csbl/memory/allocator_arg.hpp>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
@@ -29,10 +32,12 @@ namespace test {
|
||||
class enable_yield_to
|
||||
{
|
||||
protected:
|
||||
boost::asio::io_service ios_;
|
||||
boost::asio::io_context ios_;
|
||||
|
||||
private:
|
||||
boost::optional<boost::asio::io_service::work> work_;
|
||||
boost::optional<boost::asio::executor_work_guard<
|
||||
boost::asio::io_context::executor_type>>
|
||||
work_;
|
||||
std::vector<std::thread> threads_;
|
||||
std::mutex m_;
|
||||
std::condition_variable cv_;
|
||||
@@ -42,7 +47,8 @@ public:
|
||||
/// The type of yield context passed to functions.
|
||||
using yield_context = boost::asio::yield_context;
|
||||
|
||||
explicit enable_yield_to(std::size_t concurrency = 1) : work_(ios_)
|
||||
explicit enable_yield_to(std::size_t concurrency = 1)
|
||||
: work_(boost::asio::make_work_guard(ios_))
|
||||
{
|
||||
threads_.reserve(concurrency);
|
||||
while (concurrency--)
|
||||
@@ -56,9 +62,9 @@ public:
|
||||
t.join();
|
||||
}
|
||||
|
||||
/// Return the `io_service` associated with the object
|
||||
boost::asio::io_service&
|
||||
get_io_service()
|
||||
/// Return the `io_context` associated with the object
|
||||
boost::asio::io_context&
|
||||
get_io_context()
|
||||
{
|
||||
return ios_;
|
||||
}
|
||||
@@ -111,13 +117,18 @@ enable_yield_to::spawn(F0&& f, FN&&... fn)
|
||||
{
|
||||
boost::asio::spawn(
|
||||
ios_,
|
||||
boost::allocator_arg,
|
||||
boost::context::fixedsize_stack(2 * 1024 * 1024),
|
||||
[&](yield_context yield) {
|
||||
f(yield);
|
||||
std::lock_guard lock{m_};
|
||||
if (--running_ == 0)
|
||||
cv_.notify_all();
|
||||
},
|
||||
boost::coroutines::attributes(2 * 1024 * 1024));
|
||||
[](std::exception_ptr e) {
|
||||
if (e)
|
||||
std::rethrow_exception(e);
|
||||
});
|
||||
spawn(fn...);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,12 +12,9 @@
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/throw_exception.hpp>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
namespace beast {
|
||||
namespace unit_test {
|
||||
@@ -70,64 +67,28 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
// Journal writers and direct suite logging share this buffer. Expose no
|
||||
// put area, so every write takes the lock through the streambuf virtuals.
|
||||
// Keep unfinished text per thread: one writer's flush must not consume
|
||||
// another writer's partial message.
|
||||
template <class CharT, class Traits, class Allocator>
|
||||
class log_buf : public std::basic_streambuf<CharT, Traits>
|
||||
class log_buf : public std::basic_stringbuf<CharT, Traits, Allocator>
|
||||
{
|
||||
using string_type = std::basic_string<CharT, Traits, Allocator>;
|
||||
using int_type = typename Traits::int_type;
|
||||
|
||||
suite& suite_;
|
||||
std::mutex mutex_;
|
||||
std::map<std::thread::id, string_type> pending_;
|
||||
|
||||
public:
|
||||
explicit log_buf(suite& self) : suite_(self)
|
||||
{
|
||||
}
|
||||
|
||||
~log_buf() override
|
||||
~log_buf()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
for (auto const& [id, text] : pending_)
|
||||
if (!text.empty())
|
||||
suite_.runner_->log(text);
|
||||
}
|
||||
|
||||
protected:
|
||||
int_type
|
||||
overflow(int_type ch) override
|
||||
{
|
||||
if (Traits::eq_int_type(ch, Traits::eof()))
|
||||
return Traits::not_eof(ch);
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
pending_[std::this_thread::get_id()].push_back(
|
||||
Traits::to_char_type(ch));
|
||||
return ch;
|
||||
}
|
||||
|
||||
std::streamsize
|
||||
xsputn(CharT const* text, std::streamsize size) override
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
pending_[std::this_thread::get_id()].append(
|
||||
text, static_cast<std::size_t>(size));
|
||||
return size;
|
||||
sync();
|
||||
}
|
||||
|
||||
int
|
||||
sync() override
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto& text = pending_[std::this_thread::get_id()];
|
||||
if (!text.empty())
|
||||
{
|
||||
suite_.runner_->log(text);
|
||||
text.clear();
|
||||
}
|
||||
auto const& s = this->str();
|
||||
if (s.size() > 0)
|
||||
suite_.runner_->log(s);
|
||||
this->str("");
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
+20
-13
@@ -15,6 +15,7 @@
|
||||
#define uint256 std::string
|
||||
#define featureHooksUpdate1 "1"
|
||||
#define featureHooksUpdate2 "1"
|
||||
#define featureRNG "1"
|
||||
#define fix20250131 "1"
|
||||
#define fixGuardDepth32 "1"
|
||||
namespace hook_api {
|
||||
@@ -313,6 +314,7 @@ enum keylet_code : uint32_t {
|
||||
MPTOKEN = 34,
|
||||
CREDENTIAL = 35,
|
||||
PERMISSIONED_DOMAIN = 36,
|
||||
MANIFEST = 37,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -384,7 +386,9 @@ enum class hook_return_code : int64_t {
|
||||
MEM_OVERLAP = -43, // one or more specified buffers are the same memory
|
||||
TOO_MANY_STATE_MODIFICATIONS = -44, // more than 256 modified state
|
||||
// entires in the combined hook chains
|
||||
TOO_MANY_NAMESPACES = -45
|
||||
TOO_MANY_NAMESPACES = -45,
|
||||
TOO_LITTLE_ENTROPY = -46 // featureRNG: no seed for this ledger, or too few
|
||||
// validators contributed to it
|
||||
};
|
||||
|
||||
enum class ExitType : uint8_t {
|
||||
@@ -397,12 +401,25 @@ enum class ExitType : uint8_t {
|
||||
const uint16_t max_state_modifications = 256;
|
||||
const uint8_t max_slots = 255;
|
||||
const uint8_t max_nonce = 255;
|
||||
const uint16_t max_rng_draws = 256; // featureRNG, per Hook execution
|
||||
const uint8_t max_emit = 255;
|
||||
const uint8_t max_params = 16;
|
||||
const double fee_base_multiplier = 1.1f;
|
||||
|
||||
using APIWhitelist = std::map<std::string, std::vector<uint8_t>>;
|
||||
|
||||
namespace detail {
|
||||
template <class T>
|
||||
constexpr uint8_t wasmTypeCode = sizeof(T) == 8 ? 0x7EU : 0x7FU;
|
||||
|
||||
template <class R, class... Args>
|
||||
std::vector<uint8_t>
|
||||
wasmSignature(R (*)(Args...))
|
||||
{
|
||||
return {wasmTypeCode<R>, wasmTypeCode<Args>...};
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
// RH NOTE: Find descriptions of api functions in ./impl/applyHook.cpp and
|
||||
// hookapi.h (include for hooks) this is a map of the api name to its return
|
||||
// code (vec[0] and its parameters vec[>0]) as wasm type codes
|
||||
@@ -414,25 +431,15 @@ getImportWhitelist(Rules const& rules)
|
||||
#pragma push_macro("HOOK_API_DEFINITION")
|
||||
#undef HOOK_API_DEFINITION
|
||||
|
||||
#define int64_t 0x7EU
|
||||
#define int32_t 0x7FU
|
||||
#define uint32_t 0x7FU
|
||||
|
||||
#define HOOK_WRAP_PARAMS(...) __VA_ARGS__
|
||||
|
||||
#define HOOK_API_DEFINITION( \
|
||||
RETURN_TYPE, FUNCTION_NAME, PARAMS_TUPLE, AMENDMENT) \
|
||||
if (AMENDMENT == uint256{} || rules.enabled(AMENDMENT)) \
|
||||
whitelist[#FUNCTION_NAME] = { \
|
||||
RETURN_TYPE, HOOK_WRAP_PARAMS PARAMS_TUPLE};
|
||||
whitelist[#FUNCTION_NAME] = \
|
||||
detail::wasmSignature((RETURN_TYPE(*) PARAMS_TUPLE) nullptr);
|
||||
|
||||
#include "hook_api.macro"
|
||||
|
||||
#undef HOOK_API_DEFINITION
|
||||
#undef HOOK_WRAP_PARAMS
|
||||
#undef int64_t
|
||||
#undef int32_t
|
||||
#undef uint32_t
|
||||
#pragma pop_macro("HOOK_API_DEFINITION")
|
||||
|
||||
return whitelist;
|
||||
|
||||
@@ -87,7 +87,7 @@ parseSignedLeb128(
|
||||
}
|
||||
*end_offset = i;
|
||||
if (shift < 64 && (b & 0x40U))
|
||||
val |= (~0 << shift);
|
||||
val |= (~int64_t{0} << shift);
|
||||
|
||||
return val;
|
||||
}
|
||||
@@ -1096,7 +1096,11 @@ validateGuards(
|
||||
{
|
||||
int name_len = parseLeb128(wasm, i, &i);
|
||||
CHECK_SHORT_HOOK();
|
||||
if (name_len == 4)
|
||||
// Only compare the name if all four bytes are in bounds. A
|
||||
// name running past the end is rejected as a short hook below
|
||||
// either way, so this does not change which hooks are valid.
|
||||
if (name_len == 4 &&
|
||||
static_cast<std::size_t>(i) + 4 <= wasm.size())
|
||||
{
|
||||
if (wasm[i] == 'h' && wasm[i + 1] == 'o' &&
|
||||
wasm[i + 2] == 'o' && wasm[i + 3] == 'k')
|
||||
|
||||
@@ -244,7 +244,13 @@
|
||||
<< "Tried to accept/rollback but specified " \
|
||||
"memory outside of the wasm instance " \
|
||||
<< "limit when specifying a reason string"; \
|
||||
return OUT_OF_BOUNDS; \
|
||||
if (!view.rules().enabled(fix20260929)) \
|
||||
return OUT_OF_BOUNDS; \
|
||||
/* fix20260929: terminate as a rollback so the \
|
||||
failure is visible in HookExecution metadata */ \
|
||||
hookCtx.result.exitType = hook_api::ExitType::ROLLBACK; \
|
||||
hookCtx.result.exitCode = (int64_t)OUT_OF_BOUNDS; \
|
||||
return RC_ROLLBACK; \
|
||||
} \
|
||||
/* assembly script and some other languages use utf16 for strings \
|
||||
*/ \
|
||||
|
||||
@@ -1,374 +1,309 @@
|
||||
// int32_t _g(uint32_t guard_id, uint32_t maxiter);
|
||||
// clang-format off
|
||||
HOOK_API_DEFINITION(
|
||||
int32_t, _g, (uint32_t, uint32_t),
|
||||
int32_t, _g, (uint32_t guard_id, uint32_t maxiter),
|
||||
uint256{})
|
||||
|
||||
// int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, accept, (uint32_t, uint32_t, int64_t),
|
||||
int64_t, accept, (uint32_t read_ptr, uint32_t read_len, int64_t error_code),
|
||||
uint256{})
|
||||
|
||||
// int64_t rollback(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, rollback, (uint32_t, uint32_t, int64_t),
|
||||
int64_t, rollback, (uint32_t read_ptr, uint32_t read_len, int64_t error_code),
|
||||
uint256{})
|
||||
|
||||
// int64_t util_raddr(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, util_raddr, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, util_raddr, (uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t util_accid(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, util_accid, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, util_accid, (uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t util_verify(uint32_t dread_ptr, uint32_t dread_len, uint32_t sread_ptr, uint32_t sread_len, uint32_t kread_ptr, uint32_t kread_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, util_verify, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, util_verify, (uint32_t dread_ptr, uint32_t dread_len, uint32_t sread_ptr, uint32_t sread_len, uint32_t kread_ptr, uint32_t kread_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t util_sha512h(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, util_sha512h, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, util_sha512h, (uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t util_keylet(uint32_t write_ptr, uint32_t write_len, uint32_t keylet_type, uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t e, uint32_t f);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, util_keylet, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, util_keylet, (uint32_t write_ptr, uint32_t write_len, uint32_t keylet_type, uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t e, uint32_t f),
|
||||
uint256{})
|
||||
|
||||
// int64_t sto_validate(uint32_t tread_ptr, uint32_t tread_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, sto_validate, (uint32_t, uint32_t),
|
||||
int64_t, sto_validate, (uint32_t tread_ptr, uint32_t tread_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t sto_subfield(uint32_t read_ptr, uint32_t read_len, uint32_t field_id);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, sto_subfield, (uint32_t, uint32_t, uint32_t),
|
||||
int64_t, sto_subfield, (uint32_t read_ptr, uint32_t read_len, uint32_t field_id),
|
||||
uint256{})
|
||||
|
||||
// int64_t sto_subarray(uint32_t read_ptr, uint32_t read_len, uint32_t array_id);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, sto_subarray, (uint32_t, uint32_t, uint32_t),
|
||||
int64_t, sto_subarray, (uint32_t read_ptr, uint32_t read_len, uint32_t array_id),
|
||||
uint256{})
|
||||
|
||||
// int64_t sto_emplace(uint32_t write_ptr, uint32_t write_len, uint32_t sread_ptr, uint32_t sread_len, uint32_t fread_ptr, uint32_t fread_len, uint32_t field_id);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, sto_emplace, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, sto_emplace, (uint32_t write_ptr, uint32_t write_len, uint32_t sread_ptr, uint32_t sread_len, uint32_t fread_ptr, uint32_t fread_len, uint32_t field_id),
|
||||
uint256{})
|
||||
|
||||
// int64_t sto_erase(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len, uint32_t field_id);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, sto_erase, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, sto_erase, (uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len, uint32_t field_id),
|
||||
uint256{})
|
||||
|
||||
// int64_t etxn_burden();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, etxn_burden, (),
|
||||
uint256{})
|
||||
|
||||
// int64_t etxn_details(uint32_t write_ptr, uint32_t write_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, etxn_details, (uint32_t, uint32_t),
|
||||
int64_t, etxn_details, (uint32_t write_ptr, uint32_t write_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t etxn_fee_base(uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, etxn_fee_base, (uint32_t, uint32_t),
|
||||
int64_t, etxn_fee_base, (uint32_t read_ptr, uint32_t read_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t etxn_reserve(uint32_t count);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, etxn_reserve, (uint32_t),
|
||||
int64_t, etxn_reserve, (uint32_t count),
|
||||
uint256{})
|
||||
|
||||
// int64_t etxn_generation();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, etxn_generation, (),
|
||||
uint256{})
|
||||
|
||||
// int64_t etxn_nonce(uint32_t write_ptr, uint32_t write_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, etxn_nonce, (uint32_t, uint32_t),
|
||||
int64_t, etxn_nonce, (uint32_t write_ptr, uint32_t write_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t emit(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, emit, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, emit, (uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_set(int32_t exponent, int64_t mantissa);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_set, (int32_t, int64_t),
|
||||
int64_t, float_set, (int32_t exponent, int64_t mantissa),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_multiply(int64_t float1, int64_t float2);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_multiply, (int64_t, int64_t),
|
||||
int64_t, float_multiply, (int64_t float1, int64_t float2),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_mulratio(int64_t float1, uint32_t round_up, uint32_t numerator, uint32_t denominator);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_mulratio, (int64_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, float_mulratio, (int64_t float1, uint32_t round_up, uint32_t numerator, uint32_t denominator),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_negate(int64_t float1);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_negate, (int64_t),
|
||||
int64_t, float_negate, (int64_t float1),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_compare(int64_t float1, int64_t float2, uint32_t mode);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_compare, (int64_t, int64_t, uint32_t),
|
||||
int64_t, float_compare, (int64_t float1, int64_t float2, uint32_t mode),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_sum(int64_t float1, int64_t float2);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_sum, (int64_t, int64_t),
|
||||
int64_t, float_sum, (int64_t float1, int64_t float2),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_sto(uint32_t write_ptr, uint32_t write_len, uint32_t cread_ptr, uint32_t cread_len, uint32_t iread_ptr, uint32_t iread_len, int64_t float1, uint32_t field_code);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_sto, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, int64_t, uint32_t),
|
||||
int64_t, float_sto, (uint32_t write_ptr, uint32_t write_len, uint32_t cread_ptr, uint32_t cread_len, uint32_t iread_ptr, uint32_t iread_len, int64_t float1, uint32_t field_code),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_sto_set(uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_sto_set, (uint32_t, uint32_t),
|
||||
int64_t, float_sto_set, (uint32_t read_ptr, uint32_t read_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_invert(int64_t float1);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_invert, (int64_t),
|
||||
int64_t, float_invert, (int64_t float1),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_divide(int64_t float1, int64_t float2);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_divide, (int64_t, int64_t),
|
||||
int64_t, float_divide, (int64_t float1, int64_t float2),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_one();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_one, (),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_mantissa(int64_t float1);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_mantissa, (int64_t),
|
||||
int64_t, float_mantissa, (int64_t float1),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_sign(int64_t float1);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_sign, (int64_t),
|
||||
int64_t, float_sign, (int64_t float1),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_int(int64_t float1, uint32_t decimal_places, uint32_t abs);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_int, (int64_t, uint32_t, uint32_t),
|
||||
int64_t, float_int, (int64_t float1, uint32_t decimal_places, uint32_t abs),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_log(int64_t float1);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_log, (int64_t),
|
||||
int64_t, float_log, (int64_t float1),
|
||||
uint256{})
|
||||
|
||||
// int64_t float_root(int64_t float1, uint32_t n);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_root, (int64_t, uint32_t),
|
||||
int64_t, float_root, (int64_t float1, uint32_t n),
|
||||
uint256{})
|
||||
|
||||
// int64_t fee_base();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, fee_base, (),
|
||||
uint256{})
|
||||
|
||||
// int64_t ledger_seq();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, ledger_seq, (),
|
||||
uint256{})
|
||||
|
||||
// int64_t ledger_last_time();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, ledger_last_time, (),
|
||||
uint256{})
|
||||
|
||||
// int64_t ledger_last_hash(uint32_t write_ptr, uint32_t write_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, ledger_last_hash, (uint32_t, uint32_t),
|
||||
int64_t, ledger_last_hash, (uint32_t write_ptr, uint32_t write_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t ledger_nonce(uint32_t write_ptr, uint32_t write_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, ledger_nonce, (uint32_t, uint32_t),
|
||||
int64_t, ledger_nonce, (uint32_t write_ptr, uint32_t write_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t ledger_keylet(uint32_t write_ptr, uint32_t write_len, uint32_t lread_ptr, uint32_t lread_len, uint32_t hread_ptr, uint32_t hread_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, ledger_keylet, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, ledger_keylet, (uint32_t write_ptr, uint32_t write_len, uint32_t lread_ptr, uint32_t lread_len, uint32_t hread_ptr, uint32_t hread_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t hook_account(uint32_t write_ptr, uint32_t write_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, hook_account, (uint32_t, uint32_t),
|
||||
int64_t, hook_account, (uint32_t write_ptr, uint32_t write_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t hook_hash(uint32_t write_ptr, uint32_t write_len, int32_t hook_no);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, hook_hash, (uint32_t, uint32_t, int32_t),
|
||||
int64_t, hook_hash, (uint32_t write_ptr, uint32_t write_len, int32_t hook_no),
|
||||
uint256{})
|
||||
|
||||
// int64_t hook_param_set(uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len, uint32_t hread_ptr, uint32_t hread_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, hook_param_set, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, hook_param_set, (uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len, uint32_t hread_ptr, uint32_t hread_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t hook_param(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, hook_param, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, hook_param, (uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t hook_again();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, hook_again, (),
|
||||
uint256{})
|
||||
|
||||
// int64_t hook_skip(uint32_t read_ptr, uint32_t read_len, uint32_t flags);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, hook_skip, (uint32_t, uint32_t, uint32_t),
|
||||
int64_t, hook_skip, (uint32_t read_ptr, uint32_t read_len, uint32_t flags),
|
||||
uint256{})
|
||||
|
||||
// int64_t hook_pos();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, hook_pos, (),
|
||||
uint256{})
|
||||
|
||||
// int64_t slot(uint32_t write_ptr, uint32_t write_len, uint32_t slot);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot, (uint32_t, uint32_t, uint32_t),
|
||||
int64_t, slot, (uint32_t write_ptr, uint32_t write_len, uint32_t slot),
|
||||
uint256{})
|
||||
|
||||
// int64_t slot_clear(uint32_t slot);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot_clear, (uint32_t),
|
||||
int64_t, slot_clear, (uint32_t slot),
|
||||
uint256{})
|
||||
|
||||
// int64_t slot_count(uint32_t slot);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot_count, (uint32_t),
|
||||
int64_t, slot_count, (uint32_t slot),
|
||||
uint256{})
|
||||
|
||||
// int64_t slot_set(uint32_t read_ptr, uint32_t read_len, uint32_t slot);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot_set, (uint32_t, uint32_t, uint32_t),
|
||||
int64_t, slot_set, (uint32_t read_ptr, uint32_t read_len, uint32_t slot),
|
||||
uint256{})
|
||||
|
||||
// int64_t slot_size(uint32_t slot);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot_size, (uint32_t),
|
||||
int64_t, slot_size, (uint32_t slot),
|
||||
uint256{})
|
||||
|
||||
// int64_t slot_subarray(uint32_t parent_slot, uint32_t array_id, uint32_t new_slot);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot_subarray, (uint32_t, uint32_t, uint32_t),
|
||||
int64_t, slot_subarray, (uint32_t parent_slot, uint32_t array_id, uint32_t new_slot),
|
||||
uint256{})
|
||||
|
||||
// int64_t slot_subfield(uint32_t parent_slot, uint32_t field_id, uint32_t new_slot);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot_subfield, (uint32_t, uint32_t, uint32_t),
|
||||
int64_t, slot_subfield, (uint32_t parent_slot, uint32_t field_id, uint32_t new_slot),
|
||||
uint256{})
|
||||
|
||||
// int64_t slot_type(uint32_t slot_no, uint32_t flags);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot_type, (uint32_t, uint32_t),
|
||||
int64_t, slot_type, (uint32_t slot_no, uint32_t flags),
|
||||
uint256{})
|
||||
|
||||
// int64_t slot_float(uint32_t slot_no);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot_float, (uint32_t),
|
||||
int64_t, slot_float, (uint32_t slot_no),
|
||||
uint256{})
|
||||
|
||||
// int64_t state_set(uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, state_set, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, state_set, (uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t state_foreign_set(uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len, uint32_t nread_ptr, uint32_t nread_len, uint32_t aread_ptr, uint32_t aread_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, state_foreign_set, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, state_foreign_set, (uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len, uint32_t nread_ptr, uint32_t nread_len, uint32_t aread_ptr, uint32_t aread_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t state(uint32_t write_ptr, uint32_t write_len, uint32_t kread_ptr, uint32_t kread_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, state, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, state, (uint32_t write_ptr, uint32_t write_len, uint32_t kread_ptr, uint32_t kread_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t state_foreign(uint32_t write_ptr, uint32_t write_len, uint32_t kread_ptr, uint32_t kread_len, uint32_t nread_ptr, uint32_t nread_len, uint32_t aread_ptr, uint32_t aread_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, state_foreign, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, state_foreign, (uint32_t write_ptr, uint32_t write_len, uint32_t kread_ptr, uint32_t kread_len, uint32_t nread_ptr, uint32_t nread_len, uint32_t aread_ptr, uint32_t aread_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t trace(uint32_t mread_ptr, uint32_t mread_len, uint32_t dread_ptr, uint32_t dread_len, uint32_t as_hex);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, trace, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, trace, (uint32_t mread_ptr, uint32_t mread_len, uint32_t dread_ptr, uint32_t dread_len, uint32_t as_hex),
|
||||
uint256{})
|
||||
|
||||
// int64_t trace_num(uint32_t read_ptr, uint32_t read_len, int64_t number);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, trace_num, (uint32_t, uint32_t, int64_t),
|
||||
int64_t, trace_num, (uint32_t read_ptr, uint32_t read_len, int64_t number),
|
||||
uint256{})
|
||||
|
||||
// int64_t trace_float(uint32_t read_ptr, uint32_t read_len, int64_t float1);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, trace_float, (uint32_t, uint32_t, int64_t),
|
||||
int64_t, trace_float, (uint32_t read_ptr, uint32_t read_len, int64_t float1),
|
||||
uint256{})
|
||||
|
||||
// int64_t otxn_burden();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, otxn_burden, (),
|
||||
uint256{})
|
||||
|
||||
// int64_t otxn_field(uint32_t write_ptr, uint32_t write_len, uint32_t field_id);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, otxn_field, (uint32_t, uint32_t, uint32_t),
|
||||
int64_t, otxn_field, (uint32_t write_ptr, uint32_t write_len, uint32_t field_id),
|
||||
uint256{})
|
||||
|
||||
// int64_t otxn_generation();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, otxn_generation, (),
|
||||
uint256{})
|
||||
|
||||
// int64_t otxn_id(uint32_t write_ptr, uint32_t write_len, uint32_t flags);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, otxn_id, (uint32_t, uint32_t, uint32_t),
|
||||
int64_t, otxn_id, (uint32_t write_ptr, uint32_t write_len, uint32_t flags),
|
||||
uint256{})
|
||||
|
||||
// int64_t otxn_type();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, otxn_type, (),
|
||||
uint256{})
|
||||
|
||||
// int64_t otxn_slot(uint32_t slot_no);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, otxn_slot, (uint32_t),
|
||||
int64_t, otxn_slot, (uint32_t slot_no),
|
||||
uint256{})
|
||||
|
||||
// int64_t otxn_param(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, otxn_param, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, otxn_param, (uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len),
|
||||
uint256{})
|
||||
|
||||
// int64_t meta_slot(uint32_t slot_no);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, meta_slot, (uint32_t),
|
||||
int64_t, meta_slot, (uint32_t slot_no),
|
||||
uint256{})
|
||||
|
||||
// int64_t xpop_slot(uint32_t slot_no_tx, uint32_t slot_no_meta);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, xpop_slot, (uint32_t, uint32_t),
|
||||
int64_t, xpop_slot, (uint32_t slot_no_tx, uint32_t slot_no_meta),
|
||||
featureHooksUpdate1)
|
||||
|
||||
// int64_t prepare(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, prepare, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
int64_t, prepare, (uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len),
|
||||
featureHooksUpdate2)
|
||||
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, dice, (uint32_t sides),
|
||||
featureRNG)
|
||||
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, util_random, (uint32_t write_ptr, uint32_t write_len),
|
||||
featureRNG)
|
||||
// clang-format on
|
||||
@@ -215,7 +215,7 @@ Reader::parse(Value& root, BufferSequence const& bs)
|
||||
std::string s;
|
||||
s.reserve(buffer_size(bs));
|
||||
for (auto const& b : bs)
|
||||
s.append(buffer_cast<char const*>(b), buffer_size(b));
|
||||
s.append(static_cast<char const*>(b.data()), buffer_size(b));
|
||||
return parse(s, root);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 XRPL Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_PROTOCOL_APPLOADER_H_INCLUDED
|
||||
#define RIPPLE_PROTOCOL_APPLOADER_H_INCLUDED
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/** Validation of an AppLoader document (sfAppLoader), the bootstrap page of
|
||||
a Progressive Web App served from an account.
|
||||
|
||||
Consensus cannot afford a real HTML parser, so this is a shallow sanity
|
||||
gate and nothing more. A document must be non-empty and at most
|
||||
maxAppLoaderLength bytes; well-formed UTF-8; free of C0 control
|
||||
characters other than TAB, LF, FF and CR, and of DEL; begin, after an
|
||||
optional BOM and whitespace, with an HTML doctype or an <html start tag;
|
||||
and contain an <html start tag followed later by a </html> end tag with
|
||||
nothing but whitespace after the last one. Nothing parses comments,
|
||||
scripts or attributes, and content is not policed.
|
||||
|
||||
Consensus critical: a change to what it accepts needs an amendment.
|
||||
*/
|
||||
namespace appLoader {
|
||||
|
||||
enum class Result : std::uint8_t {
|
||||
ok = 0,
|
||||
empty,
|
||||
tooLarge,
|
||||
badUTF8,
|
||||
badControlChar,
|
||||
noDoctype, // does not open with a doctype or <html tag
|
||||
noHtmlElement, // no <html start tag
|
||||
unclosed, // no </html> end tag after it
|
||||
trailingGarbage, // non-whitespace after the last </html>
|
||||
};
|
||||
|
||||
/** Total: every input yields a result. `data` may be null iff size is 0. */
|
||||
Result
|
||||
validate(std::uint8_t const* data, std::size_t size);
|
||||
|
||||
char const*
|
||||
to_string(Result r);
|
||||
|
||||
} // namespace appLoader
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/basics/base64.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/json/json_reader.h>
|
||||
#include <charconv>
|
||||
|
||||
@@ -118,8 +119,9 @@ syntaxCheckProof(
|
||||
{
|
||||
if (!proof["hash"].isString() ||
|
||||
proof["hash"].asString().size() != 64 ||
|
||||
!proof["key"].isString() ||
|
||||
!isHex(proof["hash"].asString()) || !proof["key"].isString() ||
|
||||
proof["key"].asString().size() != 64 ||
|
||||
!isHex(proof["key"].asString()) ||
|
||||
!proof["children"].isObject())
|
||||
{
|
||||
JLOG(j.warn()) << "XPOP.transaction.proof tree node has wrong "
|
||||
@@ -132,7 +134,11 @@ syntaxCheckProof(
|
||||
|
||||
for (const auto& branch : proof.getMemberNames())
|
||||
{
|
||||
if (branch.size() != 1 || !isHex(branch))
|
||||
// Upper case only: the txroot computation only looks up "0"-"F",
|
||||
// so any other key would be silently ignored.
|
||||
if (branch.size() != 1 ||
|
||||
branch.find_first_not_of("0123456789ABCDEF") !=
|
||||
std::string::npos)
|
||||
{
|
||||
JLOG(j.warn())
|
||||
<< "XPOP.transaction.proof child node was not 0-F "
|
||||
@@ -143,9 +149,9 @@ syntaxCheckProof(
|
||||
const auto& node = proof[branch];
|
||||
if (!node.isObject() || !node["hash"].isString() ||
|
||||
node["hash"].asString().size() != 64 ||
|
||||
!node["key"].isString() ||
|
||||
!isHex(node["hash"].asString()) || !node["key"].isString() ||
|
||||
node["key"].asString().size() != 64 ||
|
||||
!node["children"].isObject())
|
||||
!isHex(node["key"].asString()) || !node["children"].isObject())
|
||||
{
|
||||
JLOG(j.warn())
|
||||
<< "XPOP.transaction.proof tree node has wrong format";
|
||||
@@ -465,6 +471,45 @@ getVLInfo(Json::Value const& xpop, beast::Journal const& j)
|
||||
return {{sequence, m->masterKey}};
|
||||
}
|
||||
|
||||
// <effective, expiration> of the unl blob, effective defaulting to the epoch
|
||||
inline std::optional<std::pair<NetClock::time_point, NetClock::time_point>>
|
||||
getVLWindow(Json::Value const& xpop, beast::Journal const& j)
|
||||
{
|
||||
auto const data =
|
||||
base64_decode(xpop[jss::validation][jss::unl][jss::blob].asString());
|
||||
Json::Reader r;
|
||||
Json::Value parsed;
|
||||
if (!r.parse(data, parsed) || !parsed.isObject())
|
||||
{
|
||||
JLOG(j.warn()) << "Import: unl blob was not a json object (after "
|
||||
"base64 decoding)";
|
||||
return {};
|
||||
}
|
||||
|
||||
// const, so that operator[] does not insert missing members
|
||||
Json::Value const& list = parsed;
|
||||
auto const time = [&list](Json::StaticString const& field)
|
||||
-> std::optional<NetClock::time_point> {
|
||||
auto const& v = list[field];
|
||||
if (!v.isInt() || v.asInt() < 0)
|
||||
return {};
|
||||
return NetClock::time_point{NetClock::duration{v.asUInt()}};
|
||||
};
|
||||
|
||||
auto const validUntil = time(jss::expiration);
|
||||
auto const validFrom = list.isMember(jss::effective)
|
||||
? time(jss::effective)
|
||||
: std::optional<NetClock::time_point>{NetClock::time_point{}};
|
||||
if (!validUntil || !validFrom)
|
||||
{
|
||||
JLOG(j.warn()) << "Import: unl blob json (after base64 decoding) "
|
||||
"had missing or invalid effective/expiration";
|
||||
return {};
|
||||
}
|
||||
|
||||
return {{*validFrom, *validUntil}};
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -118,6 +118,10 @@ negativeUNL() noexcept;
|
||||
Keylet const&
|
||||
UNLReport() noexcept;
|
||||
|
||||
/** The (fixed) index of the featureRNG random number generator object. */
|
||||
Keylet const&
|
||||
random() noexcept;
|
||||
|
||||
/** The beginning of an order book */
|
||||
struct book_t
|
||||
{
|
||||
@@ -328,6 +332,14 @@ xChainCreateAccountClaimID(STXChainBridge const& bridge, std::uint64_t seq);
|
||||
Keylet
|
||||
did(AccountID const& account) noexcept;
|
||||
|
||||
/** An AppLoader belonging to an account.
|
||||
|
||||
There is at most one per account, so the key is derived from the owner's
|
||||
AccountID alone.
|
||||
*/
|
||||
Keylet
|
||||
appLoader(AccountID const& account) noexcept;
|
||||
|
||||
Keylet
|
||||
oracle(AccountID const& account, std::uint32_t const& documentID) noexcept;
|
||||
|
||||
@@ -376,6 +388,16 @@ permissionedDomain(uint256 const& domainID) noexcept;
|
||||
Keylet
|
||||
manifest(PublicKey const& pk) noexcept;
|
||||
|
||||
/** The directory listing every master key's ltMANIFEST object.
|
||||
|
||||
One entry per master key, made when its first manifest is published and
|
||||
kept for good: a newer manifest, a revocation included, replaces the object
|
||||
at the same key and keeps its place. The copy at the ephemeral key is not
|
||||
listed; it is named by the master object.
|
||||
*/
|
||||
Keylet const&
|
||||
manifestDir() noexcept;
|
||||
|
||||
} // namespace keylet
|
||||
|
||||
// Everything below is deprecated and should be removed in favor of keylets:
|
||||
@@ -406,7 +428,7 @@ struct keyletDesc
|
||||
|
||||
// This list should include all of the keylet functions that take a single
|
||||
// AccountID parameter.
|
||||
std::array<keyletDesc<AccountID const&>, 6> const directAccountKeylets{
|
||||
std::array<keyletDesc<AccountID const&>, 7> const directAccountKeylets{
|
||||
{{&keylet::account, jss::AccountRoot, false},
|
||||
{&keylet::ownerDir, jss::DirectoryNode, true},
|
||||
{&keylet::signers, jss::SignerList, true},
|
||||
@@ -414,7 +436,8 @@ std::array<keyletDesc<AccountID const&>, 6> const directAccountKeylets{
|
||||
// test it anyway, since the invariant checks for it.
|
||||
{&keylet::nftpage_min, jss::NFTokenPage, true},
|
||||
{&keylet::nftpage_max, jss::NFTokenPage, true},
|
||||
{&keylet::did, jss::DID, true}}};
|
||||
{&keylet::did, jss::DID, true},
|
||||
{&keylet::appLoader, jss::AppLoader, true}}};
|
||||
|
||||
MPTID
|
||||
makeMptID(std::uint32_t sequence, AccountID const& account);
|
||||
|
||||
@@ -96,6 +96,25 @@ std::size_t constexpr maxDIDAttestationLength = 256;
|
||||
/** The maximum length of a domain */
|
||||
std::size_t constexpr maxDomainLength = 256;
|
||||
|
||||
/** The maximum length of an AppLoader document.
|
||||
|
||||
The AppLoader holds the bootstrap document of a Progressive Web App
|
||||
served from an account. It is deliberately capped: the blob lives in a
|
||||
ledger object that carries no owner reserve at all, so the only things
|
||||
standing between it and unbounded ledger growth are this limit and the
|
||||
per-byte transaction fee below.
|
||||
*/
|
||||
std::size_t constexpr maxAppLoaderLength = 4096;
|
||||
|
||||
/** Drops of transaction fee charged per byte of AppLoader document.
|
||||
|
||||
Storage is paid for through the fee rather than through an owner
|
||||
reserve, so that a loader can be published by an account sitting at its
|
||||
reserve floor. The fee is charged on every AccountSet that carries the
|
||||
field, including overwrites.
|
||||
*/
|
||||
std::uint64_t constexpr appLoaderFeeDropsPerByte = 1;
|
||||
|
||||
/** The maximum length of a URI inside a Credential */
|
||||
std::size_t constexpr maxCredentialURILength = 256;
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ enum TELcodes : TERUnderlyingType {
|
||||
telIMPORT_VL_KEY_NOT_RECOGNISED,
|
||||
telCAN_NOT_QUEUE_IMPORT,
|
||||
telENV_RPC_FAILED,
|
||||
telMANIFEST_FEE_MISMATCH,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -196,6 +197,7 @@ enum TEFcodes : TERUnderlyingType {
|
||||
tefINVALID_LEDGER_FIX_TYPE,
|
||||
tefPAST_MANIFEST_SEQ,
|
||||
tefREVOKED_MANIFEST,
|
||||
tefMANIFEST_KEY_IN_USE,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -152,6 +152,26 @@ public:
|
||||
return static_cast<bool>(mHookEmissions);
|
||||
}
|
||||
|
||||
/** featureRNG: the RNG state this transaction's first Hook draw saw.
|
||||
Every value the transaction's Hooks drew is derived from it. */
|
||||
void
|
||||
setRandomData(uint256 const& randomData)
|
||||
{
|
||||
mRandomData = randomData;
|
||||
}
|
||||
|
||||
bool
|
||||
hasRandomData() const
|
||||
{
|
||||
return static_cast<bool>(mRandomData);
|
||||
}
|
||||
|
||||
uint256 const&
|
||||
getRandomData() const
|
||||
{
|
||||
return *mRandomData;
|
||||
}
|
||||
|
||||
STAmount
|
||||
getDeliveredAmount() const
|
||||
{
|
||||
@@ -176,6 +196,7 @@ private:
|
||||
std::optional<STAmount> mDelivered;
|
||||
std::optional<STArray> mHookExecutions;
|
||||
std::optional<STArray> mHookEmissions;
|
||||
std::optional<uint256> mRandomData;
|
||||
|
||||
STArray mNodes;
|
||||
};
|
||||
|
||||
@@ -34,7 +34,13 @@
|
||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||
// in include/xrpl/protocol/Feature.h.
|
||||
|
||||
XRPL_FEATURE(OnChainManifests, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(RNG, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(PWALoader, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (20261005, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FEATURE(EscrowDestinationCancel, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (20260929, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FEATURE(HookOnV2_1, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(OnChainManifests, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (HookMap, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FIX (GuardDepth32, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(NamedHooks, Supported::yes, VoteBehavior::DefaultNo)
|
||||
@@ -45,7 +51,7 @@ XRPL_FEATURE(HookAPISerializedType240, Supported::yes, VoteBehavior::DefaultNo
|
||||
XRPL_FEATURE(PermissionedDomains, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(DynamicNFT, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(Credentials, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(AMMClawback, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(AMMClawback, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(MPTokensV1, Supported::no, VoteBehavior::DefaultNo)
|
||||
// InvariantsV1_1 will be changes to Supported::yes when all the
|
||||
// invariants expected to be included under it are complete.
|
||||
@@ -65,7 +71,8 @@ XRPL_FEATURE(XChainBridge, Supported::no, VoteBehavior::DefaultNo
|
||||
XRPL_FEATURE(AMM, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (ReducedOffersV1, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FEATURE(HooksUpdate2, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(HookOnV2, Supported::yes, VoteBehavior::DefaultNo)
|
||||
// replaced to HookOnV2_1
|
||||
XRPL_FEATURE(HookOnV2, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (HookAPI20251128, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FIX (CronStacking, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FEATURE(ExtendedHookState, Supported::yes, VoteBehavior::DefaultNo)
|
||||
@@ -83,7 +90,6 @@ XRPL_FIX (ReduceImport, Supported::yes, VoteBehavior::DefaultYe
|
||||
XRPL_FEATURE(Touch, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(Remarks, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (FloatDivide, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FIX (240911, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FIX (PageCap, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FIX (240819, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FIX (NSDelete, Supported::yes, VoteBehavior::DefaultNo)
|
||||
@@ -159,6 +165,7 @@ XRPL_FEATURE(CryptoConditionsSuite, Supported::yes, VoteBehavior::Obsolete)
|
||||
// pre-amendment code has been removed and the identifiers are deprecated.
|
||||
// All known amendments and amendments that may appear in a validated
|
||||
// ledger must be registered either here or above with the "active" amendments
|
||||
XRPL_RETIRE(fix240911)
|
||||
XRPL_RETIRE(fixXahauV2)
|
||||
XRPL_RETIRE(fixXahauV1)
|
||||
XRPL_RETIRE(MultiSign)
|
||||
|
||||
@@ -22,14 +22,14 @@
|
||||
#endif
|
||||
|
||||
#ifndef LEDGER_ENTRY_DUPLICATE
|
||||
// The EXPAND macro is needed for Windows
|
||||
// The LEDGER_ENTRY_EXPAND macro is needed for Windows
|
||||
// https://stackoverflow.com/questions/5134523/msvc-doesnt-expand-va-args-correctly
|
||||
#define EXPAND(x) x
|
||||
#define LEDGER_ENTRY_EXPAND(x) x
|
||||
|
||||
// The `LEDGER_ENTRY_DUPLICATE macro is needed to avoid JSS conflicts
|
||||
// Since some transactions and ledger entries have the same name (like `DepositPreauth`)
|
||||
// The compiler won't accept two instances of `JSS(DepositPreauth)`
|
||||
#define LEDGER_ENTRY_DUPLICATE(...) EXPAND(LEDGER_ENTRY(__VA_ARGS__))
|
||||
#define LEDGER_ENTRY_DUPLICATE(...) LEDGER_ENTRY_EXPAND(LEDGER_ENTRY(__VA_ARGS__))
|
||||
#endif
|
||||
|
||||
/**
|
||||
@@ -142,6 +142,22 @@ LEDGER_ENTRY(ltIMPORT_VLSEQ, 0x0049, ImportVLSequence, import_vlseq, ({
|
||||
{sfPreviousTxnLgrSeq, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** The bootstrap document of a Progressive Web App (featurePWALoader).
|
||||
|
||||
At most one per account, keyed on the owner alone (keylet::appLoader).
|
||||
It is in no owner directory and takes no reserve: its storage is paid
|
||||
for by a per-byte fee on the AccountSet that writes it, and
|
||||
DeleteAccount erases it explicitly. AppLoaderSequence equals the owner's
|
||||
AccountRoot AppLoaderSequence as of the AccountSet that wrote it.
|
||||
*/
|
||||
LEDGER_ENTRY(ltAPP_LOADER, 0x004c, AppLoader, app_loader, ({
|
||||
{sfOwner, soeREQUIRED},
|
||||
{sfAppLoader, soeREQUIRED},
|
||||
{sfAppLoaderSequence, soeREQUIRED},
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** The ledger object which tracks the current negative UNL state.
|
||||
|
||||
\note This is a singleton: only one such object exists in the ledger.
|
||||
@@ -180,6 +196,26 @@ LEDGER_ENTRY_DUPLICATE(ltUNL_REPORT, 0x0052, UNLReport, unl_report, ({
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** featureRNG: the consensus random number generator.
|
||||
|
||||
A singleton. sfRandomData is seeded from validator reveals by the
|
||||
ttENTROPY transactions applied at the start of each ledger, and then
|
||||
iterated by every Hook API draw for the rest of that ledger.
|
||||
sfRandomData and sfRandomDigests are never written to metadata, and
|
||||
Hooks cannot load this object.
|
||||
|
||||
\sa keylet::random
|
||||
*/
|
||||
LEDGER_ENTRY(ltRANDOM, 0x526E, Random, random, ({
|
||||
{sfLedgerSequence, soeREQUIRED},
|
||||
{sfRandomData, soeREQUIRED},
|
||||
{sfEntropyCount, soeREQUIRED},
|
||||
{sfEntropyDenominator, soeREQUIRED},
|
||||
{sfRandomDigests, soeREQUIRED},
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** A ledger object which contains a signer list for an account.
|
||||
|
||||
\sa keylet::signers
|
||||
@@ -262,6 +298,7 @@ LEDGER_ENTRY(ltACCOUNT_ROOT, 0x0061, AccountRoot, account, ({
|
||||
{sfHookStateScale, soeOPTIONAL},
|
||||
{sfCron, soeOPTIONAL},
|
||||
{sfAMMID, soeOPTIONAL},
|
||||
{sfAppLoaderSequence, soeOPTIONAL},
|
||||
{sfManifestID, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
@@ -609,10 +646,11 @@ LEDGER_ENTRY(ltMANIFEST, 0x004D, Manifest, manifest_entry, ({
|
||||
{sfMasterSignature, soeREQUIRED},
|
||||
{sfSignature, soeOPTIONAL}, /* absent if the master is revoked */
|
||||
{sfManifestID, soeOPTIONAL}, /* pointer to the identical other copy on the other key */
|
||||
{sfOwnerNode, soeOPTIONAL}, /* master copy only: its page in keylet::manifestDir() */
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
}))
|
||||
|
||||
#undef EXPAND
|
||||
#undef LEDGER_ENTRY_EXPAND
|
||||
#undef LEDGER_ENTRY_DUPLICATE
|
||||
|
||||
@@ -59,6 +59,8 @@ TYPED_SFIELD(sfHookExecutionIndex, UINT16, 19)
|
||||
TYPED_SFIELD(sfHookApiVersion, UINT16, 20)
|
||||
TYPED_SFIELD(sfHookStateScale, UINT16, 21)
|
||||
TYPED_SFIELD(sfLedgerFixType, UINT16, 22)
|
||||
TYPED_SFIELD(sfEntropyCount, UINT16, 99)
|
||||
TYPED_SFIELD(sfEntropyDenominator, UINT16, 100)
|
||||
|
||||
// 32-bit integers (common)
|
||||
TYPED_SFIELD(sfNetworkID, UINT32, 1)
|
||||
@@ -123,6 +125,7 @@ TYPED_SFIELD(sfImportSequence, UINT32, 97)
|
||||
TYPED_SFIELD(sfRewardTime, UINT32, 98)
|
||||
TYPED_SFIELD(sfRewardLgrFirst, UINT32, 99)
|
||||
TYPED_SFIELD(sfRewardLgrLast, UINT32, 100)
|
||||
TYPED_SFIELD(sfAppLoaderSequence, UINT32, 101)
|
||||
|
||||
// 64-bit integers (common)
|
||||
TYPED_SFIELD(sfIndexNext, UINT64, 1)
|
||||
@@ -210,6 +213,9 @@ TYPED_SFIELD(sfOfferID, UINT256, 34)
|
||||
TYPED_SFIELD(sfEscrowID, UINT256, 35)
|
||||
TYPED_SFIELD(sfURITokenID, UINT256, 36)
|
||||
TYPED_SFIELD(sfDomainID, UINT256, 37)
|
||||
TYPED_SFIELD(sfNextRandomDigest, UINT256, 89)
|
||||
// RNG running state: never written to metadata (see featureRNG)
|
||||
TYPED_SFIELD(sfRandomData, UINT256, 90, SField::sMD_Never)
|
||||
TYPED_SFIELD(sfManifestID, UINT256, 91)
|
||||
TYPED_SFIELD(sfHookOnOutgoing, UINT256, 93)
|
||||
TYPED_SFIELD(sfHookOnIncoming, UINT256, 94)
|
||||
@@ -294,6 +300,7 @@ TYPED_SFIELD(sfAssetClass, VL, 29)
|
||||
TYPED_SFIELD(sfProvider, VL, 30)
|
||||
TYPED_SFIELD(sfMPTokenMetadata, VL, 31)
|
||||
TYPED_SFIELD(sfCredentialType, VL, 32)
|
||||
TYPED_SFIELD(sfAppLoader, VL, 96)
|
||||
TYPED_SFIELD(sfHookName, VL, 97)
|
||||
TYPED_SFIELD(sfRemarkValue, VL, 98)
|
||||
TYPED_SFIELD(sfRemarkName, VL, 99)
|
||||
@@ -383,6 +390,7 @@ UNTYPED_SFIELD(sfXChainClaimAttestationCollectionElement, OBJECT, 30)
|
||||
UNTYPED_SFIELD(sfXChainCreateAccountAttestationCollectionElement, OBJECT, 31)
|
||||
UNTYPED_SFIELD(sfPriceData, OBJECT, 32)
|
||||
UNTYPED_SFIELD(sfCredential, OBJECT, 33)
|
||||
UNTYPED_SFIELD(sfRandomDigestEntry, OBJECT, 88)
|
||||
UNTYPED_SFIELD(sfManifest, OBJECT, 90)
|
||||
UNTYPED_SFIELD(sfAmountEntry, OBJECT, 91)
|
||||
UNTYPED_SFIELD(sfMintURIToken, OBJECT, 92)
|
||||
@@ -423,6 +431,8 @@ UNTYPED_SFIELD(sfAuthAccounts, ARRAY, 25)
|
||||
UNTYPED_SFIELD(sfAuthorizeCredentials, ARRAY, 26)
|
||||
UNTYPED_SFIELD(sfUnauthorizeCredentials, ARRAY, 27)
|
||||
UNTYPED_SFIELD(sfAcceptedCredentials, ARRAY, 28)
|
||||
// RNG validator commitments: never written to metadata (see featureRNG)
|
||||
UNTYPED_SFIELD(sfRandomDigests, ARRAY, 91, SField::sMD_Never)
|
||||
UNTYPED_SFIELD(sfAmounts, ARRAY, 92)
|
||||
UNTYPED_SFIELD(sfHookEmissions, ARRAY, 93)
|
||||
UNTYPED_SFIELD(sfImportVLKeys, ARRAY, 94)
|
||||
|
||||
@@ -74,6 +74,7 @@ TRANSACTION(ttACCOUNT_SET, 3, AccountSet, ({
|
||||
{sfTickSize, soeOPTIONAL},
|
||||
{sfNFTokenMinter, soeOPTIONAL},
|
||||
{sfHookStateScale, soeOPTIONAL},
|
||||
{sfAppLoader, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type cancels an existing escrow. */
|
||||
@@ -500,6 +501,22 @@ TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 72, PermissionedDomainDelete, ({
|
||||
{sfDomainID, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** featureRNG: a validator's entropy contribution for one ledger.
|
||||
|
||||
Each proposing UNLReport validator places exactly one of these in its own
|
||||
initial position when it closes the ledger. It reveals the secret behind
|
||||
the validator's commitment currently stored in the Random object (if any)
|
||||
and commits to the secret it will reveal next time. It never enters the
|
||||
open ledger, so a reveal only becomes public after the revealing
|
||||
validator's transaction set is locked.
|
||||
*/
|
||||
TRANSACTION(ttENTROPY, 89, Entropy, ({
|
||||
{sfLedgerSequence, soeREQUIRED},
|
||||
{sfPublicKey, soeREQUIRED},
|
||||
{sfNextRandomDigest, soeREQUIRED},
|
||||
{sfRandomData, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/* sfAccount is supplied by TxFormats::commonFields; listing it here as well
|
||||
makes the SOTemplate reject the format at construction. */
|
||||
TRANSACTION(ttMANIFEST_SET, 91, SetManifest, ({
|
||||
|
||||
@@ -47,6 +47,7 @@ JSS(AL_hit_rate); // out: GetCounts
|
||||
JSS(AcceptedCredentials); // out: AccountObjects
|
||||
JSS(Account); // in: TransactionSign; field.
|
||||
JSS(AMMID); // field
|
||||
JSS(API); // out: RPC server_definitions (HOOK)
|
||||
JSS(Amount); // in: TransactionSign; field.
|
||||
JSS(Authorize); // field
|
||||
JSS(Amount2); // in/out: AMM IOU/XRP pool, deposit, withdraw amount
|
||||
@@ -68,6 +69,9 @@ JSS(Destination); // in: TransactionSign; field.
|
||||
JSS(EPrice); // in: AMM Deposit option
|
||||
JSS(Fee); // in/out: TransactionSign; field.
|
||||
JSS(FIELDS); // out: RPC server_definitions
|
||||
JSS(HOOK); // out: RPC server_definitions
|
||||
JSS(KEYLET_TYPES); // out: RPC server_definitions (HOOK)
|
||||
JSS(RETURN_CODES); // out: RPC server_definitions (HOOK)
|
||||
JSS(Flags); // in/out: TransactionSign; field.
|
||||
JSS(GenesisMints);
|
||||
JSS(GovernanceMarks);
|
||||
@@ -234,7 +238,7 @@ JSS(closed); // out: NetworkOPs, LedgerToJson,
|
||||
// handlers/Ledger
|
||||
JSS(closed_ledger); // out: NetworkOPs
|
||||
JSS(cluster); // out: PeerImp
|
||||
JSS(code); // out: errors
|
||||
JSS(code); // in: HookValidate; out: errors
|
||||
JSS(command); // in: RPCHandler
|
||||
JSS(complete); // out: NetworkOPs, InboundLedger
|
||||
JSS(complete_ledgers); // out: NetworkOPs, PeerImp
|
||||
@@ -292,7 +296,9 @@ JSS(duration_us); // out: NetworkOPs
|
||||
JSS(effective); // out: ValidatorList
|
||||
// in: UNL
|
||||
JSS(elapsed_seconds);
|
||||
JSS(enabled); // out: AmendmentTable
|
||||
JSS(enabled); // out: AmendmentTable (on-ledger);
|
||||
// ServerDefinitions (this server)
|
||||
JSS(ledger_enabled); // out: ServerDefinitions (on-ledger)
|
||||
JSS(engine_result); // out: NetworkOPs, TransactionSign, Submit
|
||||
JSS(engine_result_code); // out: NetworkOPs, TransactionSign, Submit
|
||||
JSS(engine_result_message); // out: NetworkOPs, TransactionSign, Submit
|
||||
@@ -372,6 +378,8 @@ JSS(index); // in: LedgerEntry,
|
||||
// LedgerEntry, TxHistory, LedgerData
|
||||
JSS(info); // out: ServerInfo, ConsensusInfo, FetchInfo
|
||||
JSS(initial_sync_duration_us);
|
||||
JSS(instruction_count_cbak); // out: HookValidate
|
||||
JSS(instruction_count_hook); // out: HookValidate
|
||||
JSS(internal_command); // in: Internal
|
||||
JSS(invalid_API_version); // out: Many, when a request has an invalid
|
||||
// version
|
||||
@@ -445,6 +453,7 @@ JSS(local); // out: resource/Logic.h
|
||||
JSS(local_txs); // out: GetCounts
|
||||
JSS(local_static_keys); // out: ValidatorList
|
||||
JSS(locked_balance); // out: AccountLines
|
||||
JSS(log); // out: HookValidate
|
||||
JSS(low); // out: BookChanges
|
||||
JSS(lock_count); // out: AccountLines
|
||||
JSS(lowest_sequence); // out: AccountInfo
|
||||
@@ -583,7 +592,6 @@ JSS(queue_data); // out: AccountInfo
|
||||
JSS(queued); // out: SubmitTransaction
|
||||
JSS(queued_duration_us);
|
||||
JSS(quote_asset); // in: get_aggregate_price
|
||||
JSS(random); // out: Random
|
||||
JSS(raw_meta); // out: AcceptedLedgerTx
|
||||
JSS(receive_currencies); // out: AccountCurrencies
|
||||
JSS(reference_level); // out: TxQ
|
||||
@@ -601,6 +609,7 @@ JSS(reserve_base_native); // out: NetworkOPs
|
||||
JSS(reserve_inc); // out: NetworkOPs
|
||||
JSS(reserve_inc_xrp); // out: NetworkOPs
|
||||
JSS(reserve_inc_native); // out: NetworkOPs
|
||||
JSS(reset); // in/out: PWAInfo
|
||||
JSS(response); // websocket
|
||||
JSS(result); // RPC
|
||||
JSS(ripple_lines); // out: NetworkOPs
|
||||
@@ -742,6 +751,7 @@ JSS(url_password); // in: Subscribe
|
||||
JSS(url_username); // in: Subscribe
|
||||
JSS(urlgravatar); //
|
||||
JSS(username); // in: Subscribe
|
||||
JSS(valid); // out: HookValidate
|
||||
JSS(validated); // out: NetworkOPs, RPCHelpers, AccountTx*
|
||||
// Tx
|
||||
JSS(validator_list_expires); // out: NetworkOps, ValidatorList
|
||||
@@ -763,6 +773,7 @@ JSS(validator_sites); // out: ValidatorSites
|
||||
JSS(value); // out: STAmount
|
||||
JSS(version); // out: RPCVersion
|
||||
JSS(vetoed); // out: AmendmentTableImpl
|
||||
JSS(vm_error); // out: HookValidate
|
||||
JSS(volume_a); // out: BookChanges
|
||||
JSS(volume_b); // out: BookChanges
|
||||
JSS(vote); // in: Feature
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <xrpl/basics/BasicConfig.h>
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/asio/ip/network_v4.hpp>
|
||||
#include <boost/asio/ip/network_v6.hpp>
|
||||
@@ -57,6 +58,12 @@ struct Port
|
||||
std::vector<boost::asio::ip::network_v6> admin_nets_v6;
|
||||
std::vector<boost::asio::ip::network_v4> secure_gateway_nets_v4;
|
||||
std::vector<boost::asio::ip::network_v6> secure_gateway_nets_v6;
|
||||
// "pwa" ports only; see the "pwa ports" section of xahaud-example.cfg.
|
||||
std::set<std::string> pwa_domains; // zones, lower case, no trailing dot
|
||||
std::set<AccountID> pwa_allowlist; // if non-empty, only these are served
|
||||
std::set<AccountID> pwa_blocklist; // never served; wins over allowlist
|
||||
bool pwa_custom_domains = false; // serve hosts named by TXT records
|
||||
std::optional<beast::IP::Endpoint> pwa_resolver; // for those; else system
|
||||
std::string user;
|
||||
std::string password;
|
||||
std::string admin_user;
|
||||
@@ -93,8 +100,18 @@ struct Port
|
||||
return protocol.count("udp") > 0;
|
||||
}
|
||||
|
||||
bool
|
||||
has_peer() const
|
||||
{
|
||||
return protocol.count("peer") > 0;
|
||||
}
|
||||
|
||||
// Maximum UDP packet size (default 64KB)
|
||||
std::size_t udp_packet_size = 65536;
|
||||
|
||||
// If this is a peer port, also listen for UDP Superhighway (XUSH)
|
||||
// datagrams on the same port number.
|
||||
bool udp_highway = false;
|
||||
};
|
||||
|
||||
std::ostream&
|
||||
@@ -126,6 +143,9 @@ struct ParsedPort
|
||||
std::vector<boost::asio::ip::network_v6> admin_nets_v6;
|
||||
std::vector<boost::asio::ip::network_v4> secure_gateway_nets_v4;
|
||||
std::vector<boost::asio::ip::network_v6> secure_gateway_nets_v6;
|
||||
std::set<std::string> pwa_domains;
|
||||
bool pwa_custom_domains = false;
|
||||
std::optional<beast::IP::Endpoint> pwa_resolver;
|
||||
};
|
||||
|
||||
void
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
#include <xrpl/beast/utility/PropertyStream.h>
|
||||
#include <xrpl/server/Port.h>
|
||||
#include <xrpl/server/detail/ServerImpl.h>
|
||||
#include <boost/asio/io_service.hpp>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -33,10 +34,10 @@ template <class Handler>
|
||||
std::unique_ptr<Server>
|
||||
make_Server(
|
||||
Handler& handler,
|
||||
boost::asio::io_service& io_service,
|
||||
boost::asio::io_context& io_context,
|
||||
beast::Journal journal)
|
||||
{
|
||||
return std::make_unique<ServerImpl<Handler>>(handler, io_service, journal);
|
||||
return std::make_unique<ServerImpl<Handler>>(handler, io_context, journal);
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -86,9 +86,7 @@ public:
|
||||
++iter)
|
||||
{
|
||||
typename BufferSequence::value_type const& buffer(*iter);
|
||||
write(
|
||||
boost::asio::buffer_cast<void const*>(buffer),
|
||||
boost::asio::buffer_size(buffer));
|
||||
write(buffer.data(), boost::asio::buffer_size(buffer));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +100,7 @@ public:
|
||||
|
||||
/** Detach the session.
|
||||
This holds the session open so that the response can be sent
|
||||
asynchronously. Calls to io_service::run made by the server
|
||||
asynchronously. Calls to io_context::run made by the server
|
||||
will not return until all detached sessions are closed.
|
||||
*/
|
||||
virtual std::shared_ptr<Session>
|
||||
|
||||
@@ -24,10 +24,12 @@
|
||||
#include <xrpl/beast/net/IPAddressConversion.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/server/Session.h>
|
||||
#include <xrpl/server/detail/Spawn.h>
|
||||
#include <xrpl/server/detail/io_list.h>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/asio/ssl/stream.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/asio/streambuf.hpp>
|
||||
#include <boost/beast/core/stream_traits.hpp>
|
||||
#include <boost/beast/http/dynamic_body.hpp>
|
||||
@@ -214,8 +216,8 @@ BaseHTTPPeer<Handler, Impl>::BaseHTTPPeer(
|
||||
ConstBufferSequence const& buffers)
|
||||
: port_(port)
|
||||
, handler_(handler)
|
||||
, work_(executor)
|
||||
, strand_(executor)
|
||||
, work_(boost::asio::make_work_guard(executor))
|
||||
, strand_(boost::asio::make_strand(executor))
|
||||
, remote_address_(remote_address)
|
||||
, journal_(journal)
|
||||
{
|
||||
@@ -300,7 +302,13 @@ BaseHTTPPeer<Handler, Impl>::do_read(yield_context do_yield)
|
||||
{
|
||||
complete_ = false;
|
||||
error_code ec;
|
||||
start_timer();
|
||||
// Do not treat an established keep-alive waiting for its next request as
|
||||
// an unfinished loopback request.
|
||||
if (request_count_ == 0)
|
||||
start_timer();
|
||||
else
|
||||
boost::beast::get_lowest_layer(impl().stream_)
|
||||
.expires_after(std::chrono::seconds(timeoutSeconds));
|
||||
boost::beast::http::async_read(
|
||||
impl().stream_, read_buf_, message_, do_yield[ec]);
|
||||
cancel_timer();
|
||||
@@ -355,7 +363,7 @@ BaseHTTPPeer<Handler, Impl>::on_write(
|
||||
return;
|
||||
if (graceful_)
|
||||
return do_close();
|
||||
boost::asio::spawn(
|
||||
util::spawn(
|
||||
strand_,
|
||||
std::bind(
|
||||
&BaseHTTPPeer<Handler, Impl>::do_read,
|
||||
@@ -374,7 +382,7 @@ BaseHTTPPeer<Handler, Impl>::do_writer(
|
||||
{
|
||||
auto const p = impl().shared_from_this();
|
||||
resume = std::function<void(void)>([this, p, writer, keep_alive]() {
|
||||
boost::asio::spawn(
|
||||
util::spawn(
|
||||
strand_,
|
||||
std::bind(
|
||||
&BaseHTTPPeer<Handler, Impl>::do_writer,
|
||||
@@ -405,7 +413,7 @@ BaseHTTPPeer<Handler, Impl>::do_writer(
|
||||
if (!keep_alive)
|
||||
return do_close();
|
||||
|
||||
boost::asio::spawn(
|
||||
util::spawn(
|
||||
strand_,
|
||||
std::bind(
|
||||
&BaseHTTPPeer<Handler, Impl>::do_read,
|
||||
@@ -447,14 +455,14 @@ BaseHTTPPeer<Handler, Impl>::write(
|
||||
std::shared_ptr<Writer> const& writer,
|
||||
bool keep_alive)
|
||||
{
|
||||
boost::asio::spawn(bind_executor(
|
||||
util::spawn(
|
||||
strand_,
|
||||
std::bind(
|
||||
&BaseHTTPPeer<Handler, Impl>::do_writer,
|
||||
impl().shared_from_this(),
|
||||
writer,
|
||||
keep_alive,
|
||||
std::placeholders::_1)));
|
||||
std::placeholders::_1));
|
||||
}
|
||||
|
||||
// DEPRECATED
|
||||
@@ -489,12 +497,12 @@ BaseHTTPPeer<Handler, Impl>::complete()
|
||||
}
|
||||
|
||||
// keep-alive
|
||||
boost::asio::spawn(bind_executor(
|
||||
util::spawn(
|
||||
strand_,
|
||||
std::bind(
|
||||
&BaseHTTPPeer<Handler, Impl>::do_read,
|
||||
impl().shared_from_this(),
|
||||
std::placeholders::_1)));
|
||||
std::placeholders::_1));
|
||||
}
|
||||
|
||||
// DEPRECATED
|
||||
|
||||
@@ -89,8 +89,8 @@ BasePeer<Handler, Impl>::BasePeer(
|
||||
return "##" + std::to_string(++id) + " ";
|
||||
}())
|
||||
, j_(sink_)
|
||||
, work_(executor)
|
||||
, strand_(executor)
|
||||
, work_(boost::asio::make_work_guard(executor))
|
||||
, strand_(boost::asio::make_strand(executor))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <xrpl/server/detail/BasePeer.h>
|
||||
#include <xrpl/server/detail/LowestLayer.h>
|
||||
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/beast/core/multi_buffer.hpp>
|
||||
#include <boost/beast/http/message.hpp>
|
||||
#include <boost/beast/websocket.hpp>
|
||||
@@ -420,11 +421,17 @@ BaseWSPeer<Handler, Impl>::start_timer()
|
||||
// Max seconds without completing a message
|
||||
static constexpr std::chrono::seconds timeout{30};
|
||||
static constexpr std::chrono::seconds timeoutLocal{3};
|
||||
error_code ec;
|
||||
timer_.expires_from_now(
|
||||
remote_endpoint().address().is_loopback() ? timeoutLocal : timeout, ec);
|
||||
if (ec)
|
||||
return fail(ec, "start_timer");
|
||||
|
||||
try
|
||||
{
|
||||
timer_.expires_after(
|
||||
remote_endpoint().address().is_loopback() ? timeoutLocal : timeout);
|
||||
}
|
||||
catch (boost::system::system_error const& e)
|
||||
{
|
||||
return fail(e.code(), "start_timer");
|
||||
}
|
||||
|
||||
timer_.async_wait(bind_executor(
|
||||
strand_,
|
||||
std::bind(
|
||||
@@ -438,8 +445,14 @@ template <class Handler, class Impl>
|
||||
void
|
||||
BaseWSPeer<Handler, Impl>::cancel_timer()
|
||||
{
|
||||
error_code ec;
|
||||
timer_.cancel(ec);
|
||||
try
|
||||
{
|
||||
timer_.cancel();
|
||||
}
|
||||
catch (boost::system::system_error const&)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
template <class Handler, class Impl>
|
||||
|
||||
@@ -69,7 +69,7 @@ private:
|
||||
stream_type stream_;
|
||||
socket_type& socket_;
|
||||
endpoint_type remote_address_;
|
||||
boost::asio::io_context::strand strand_;
|
||||
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
|
||||
beast::Journal const j_;
|
||||
|
||||
public:
|
||||
@@ -95,7 +95,7 @@ private:
|
||||
Handler& handler_;
|
||||
boost::asio::io_context& ioc_;
|
||||
acceptor_type acceptor_;
|
||||
boost::asio::io_context::strand strand_;
|
||||
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
|
||||
bool ssl_;
|
||||
bool plain_;
|
||||
|
||||
@@ -155,7 +155,7 @@ Door<Handler>::Detector::Detector(
|
||||
, stream_(std::move(stream))
|
||||
, socket_(stream_.socket())
|
||||
, remote_address_(remote_address)
|
||||
, strand_(ioc_)
|
||||
, strand_(boost::asio::make_strand(ioc_))
|
||||
, j_(j)
|
||||
{
|
||||
}
|
||||
@@ -164,7 +164,7 @@ template <class Handler>
|
||||
void
|
||||
Door<Handler>::Detector::run()
|
||||
{
|
||||
boost::asio::spawn(
|
||||
util::spawn(
|
||||
strand_,
|
||||
std::bind(
|
||||
&Detector::do_detect,
|
||||
@@ -269,7 +269,7 @@ Door<Handler>::reOpen()
|
||||
Throw<std::exception>();
|
||||
}
|
||||
|
||||
acceptor_.listen(boost::asio::socket_base::max_connections, ec);
|
||||
acceptor_.listen(boost::asio::socket_base::max_listen_connections, ec);
|
||||
if (ec)
|
||||
{
|
||||
JLOG(j_.error()) << "Listen on port '" << port_.name
|
||||
@@ -291,14 +291,14 @@ Door<Handler>::Door(
|
||||
, handler_(handler)
|
||||
, ioc_(io_context)
|
||||
, acceptor_(io_context)
|
||||
, strand_(io_context)
|
||||
, strand_(boost::asio::make_strand(io_context))
|
||||
, ssl_(
|
||||
port_.protocol.count("https") > 0 ||
|
||||
port_.protocol.count("wss") > 0 || port_.protocol.count("wss2") > 0 ||
|
||||
port_.protocol.count("peer") > 0)
|
||||
, plain_(
|
||||
port_.protocol.count("http") > 0 || port_.protocol.count("ws") > 0 ||
|
||||
port_.protocol.count("ws2"))
|
||||
port_.protocol.count("ws2") || port_.protocol.count("pwa"))
|
||||
{
|
||||
reOpen();
|
||||
}
|
||||
@@ -307,7 +307,7 @@ template <class Handler>
|
||||
void
|
||||
Door<Handler>::run()
|
||||
{
|
||||
boost::asio::spawn(
|
||||
util::spawn(
|
||||
strand_,
|
||||
std::bind(
|
||||
&Door<Handler>::do_accept,
|
||||
@@ -320,7 +320,8 @@ void
|
||||
Door<Handler>::close()
|
||||
{
|
||||
if (!strand_.running_in_this_thread())
|
||||
return strand_.post(
|
||||
return boost::asio::post(
|
||||
strand_,
|
||||
std::bind(&Door<Handler>::close, this->shared_from_this()));
|
||||
error_code ec;
|
||||
acceptor_.close(ec);
|
||||
@@ -400,7 +401,7 @@ Door<Handler>::do_accept(boost::asio::yield_context do_yield)
|
||||
{
|
||||
create(
|
||||
ssl_,
|
||||
boost::asio::null_buffers{},
|
||||
boost::asio::const_buffer{},
|
||||
std::move(stream),
|
||||
remote_address);
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ PlainHTTPPeer<Handler>::run()
|
||||
{
|
||||
if (!this->handler_.onAccept(this->session(), this->remote_address_))
|
||||
{
|
||||
boost::asio::spawn(
|
||||
util::spawn(
|
||||
this->strand_,
|
||||
std::bind(&PlainHTTPPeer::do_close, this->shared_from_this()));
|
||||
return;
|
||||
@@ -112,7 +112,7 @@ PlainHTTPPeer<Handler>::run()
|
||||
if (!socket_.is_open())
|
||||
return;
|
||||
|
||||
boost::asio::spawn(
|
||||
util::spawn(
|
||||
this->strand_,
|
||||
std::bind(
|
||||
&PlainHTTPPeer::do_read,
|
||||
|
||||
@@ -113,14 +113,14 @@ SSLHTTPPeer<Handler>::run()
|
||||
{
|
||||
if (!this->handler_.onAccept(this->session(), this->remote_address_))
|
||||
{
|
||||
boost::asio::spawn(
|
||||
util::spawn(
|
||||
this->strand_,
|
||||
std::bind(&SSLHTTPPeer::do_close, this->shared_from_this()));
|
||||
return;
|
||||
}
|
||||
if (!socket_.is_open())
|
||||
return;
|
||||
boost::asio::spawn(
|
||||
util::spawn(
|
||||
this->strand_,
|
||||
std::bind(
|
||||
&SSLHTTPPeer::do_handshake,
|
||||
@@ -162,7 +162,7 @@ SSLHTTPPeer<Handler>::do_handshake(yield_context do_yield)
|
||||
this->port().protocol.count("https") > 0;
|
||||
if (http)
|
||||
{
|
||||
boost::asio::spawn(
|
||||
util::spawn(
|
||||
this->strand_,
|
||||
std::bind(
|
||||
&SSLHTTPPeer::do_read,
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
#include <xrpl/server/detail/io_list.h>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/executor_work_guard.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
@@ -86,9 +88,11 @@ private:
|
||||
|
||||
Handler& handler_;
|
||||
beast::Journal const j_;
|
||||
boost::asio::io_service& io_service_;
|
||||
boost::asio::io_service::strand strand_;
|
||||
std::optional<boost::asio::io_service::work> work_;
|
||||
boost::asio::io_context& io_context_;
|
||||
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
|
||||
std::optional<boost::asio::executor_work_guard<
|
||||
boost::asio::io_context::executor_type>>
|
||||
work_;
|
||||
|
||||
std::mutex m_;
|
||||
std::vector<Port> ports_;
|
||||
@@ -101,7 +105,7 @@ private:
|
||||
public:
|
||||
ServerImpl(
|
||||
Handler& handler,
|
||||
boost::asio::io_service& io_service,
|
||||
boost::asio::io_context& io_context,
|
||||
beast::Journal journal);
|
||||
|
||||
~ServerImpl();
|
||||
@@ -124,10 +128,10 @@ public:
|
||||
return ios_;
|
||||
}
|
||||
|
||||
boost::asio::io_service&
|
||||
get_io_service()
|
||||
boost::asio::io_context&
|
||||
get_io_context()
|
||||
{
|
||||
return io_service_;
|
||||
return io_context_;
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -141,13 +145,13 @@ private:
|
||||
template <class Handler>
|
||||
ServerImpl<Handler>::ServerImpl(
|
||||
Handler& handler,
|
||||
boost::asio::io_service& io_service,
|
||||
boost::asio::io_context& io_context,
|
||||
beast::Journal journal)
|
||||
: handler_(handler)
|
||||
, j_(journal)
|
||||
, io_service_(io_service)
|
||||
, strand_(io_service_)
|
||||
, work_(io_service_)
|
||||
, io_context_(io_context)
|
||||
, strand_(boost::asio::make_strand(io_context_))
|
||||
, work_(std::in_place, boost::asio::make_work_guard(io_context_))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -179,7 +183,7 @@ ServerImpl<Handler>::ports(std::vector<Port> const& ports)
|
||||
{
|
||||
// UDP-RPC door
|
||||
if (auto sp = ios_.emplace<UDPDoor<Handler>>(
|
||||
handler_, io_service_, internalPort, j_))
|
||||
handler_, io_context_, internalPort, j_))
|
||||
{
|
||||
auto ep = sp->get_endpoint();
|
||||
if (!internalPort.port)
|
||||
@@ -193,7 +197,7 @@ ServerImpl<Handler>::ports(std::vector<Port> const& ports)
|
||||
{
|
||||
// Standard TCP door
|
||||
if (auto sp = ios_.emplace<Door<Handler>>(
|
||||
handler_, io_service_, internalPort, j_))
|
||||
handler_, io_context_, internalPort, j_))
|
||||
{
|
||||
list_.push_back(sp);
|
||||
|
||||
@@ -204,6 +208,16 @@ ServerImpl<Handler>::ports(std::vector<Port> const& ports)
|
||||
|
||||
sp->run();
|
||||
}
|
||||
|
||||
// UDP Superhighway (XUSH) datagrams arrive on the peer port
|
||||
// number. TCP and UDP port numbers are independent, so this
|
||||
// needs no socket sharing with the TCP door.
|
||||
if (internalPort.has_peer() && internalPort.udp_highway)
|
||||
{
|
||||
if (auto sp = ios_.emplace<UDPDoor<Handler>>(
|
||||
handler_, io_context_, internalPort, j_))
|
||||
sp->run();
|
||||
}
|
||||
}
|
||||
}
|
||||
return eps;
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright(c) 2025 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_SERVER_SPAWN_H_INCLUDED
|
||||
#define RIPPLE_SERVER_SPAWN_H_INCLUDED
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
|
||||
#include <concepts>
|
||||
#include <type_traits>
|
||||
|
||||
namespace ripple::util {
|
||||
namespace impl {
|
||||
|
||||
template <typename T>
|
||||
concept IsStrand = std::same_as<
|
||||
std::decay_t<T>,
|
||||
boost::asio::strand<typename std::decay_t<T>::inner_executor_type>>;
|
||||
|
||||
/**
|
||||
* @brief A completion handler that restores `boost::asio::spawn`'s behaviour
|
||||
* from Boost 1.83
|
||||
*
|
||||
* This is intended to be passed as the third argument to `boost::asio::spawn`
|
||||
* so that exceptions are not ignored but propagated to `io_context.run()` call
|
||||
* site.
|
||||
*
|
||||
* @param ePtr The exception that was caught on the coroutine
|
||||
*/
|
||||
inline constexpr auto kPROPAGATE_EXCEPTIONS = [](std::exception_ptr ePtr) {
|
||||
if (ePtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::rethrow_exception(ePtr);
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
JLOG(debugLog().warn()) << "Spawn exception: " << e.what();
|
||||
throw;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
JLOG(debugLog().warn()) << "Spawn exception: Unknown";
|
||||
throw;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace impl
|
||||
|
||||
/**
|
||||
* @brief Spawns a coroutine using `boost::asio::spawn`
|
||||
*
|
||||
* @note This uses kPROPAGATE_EXCEPTIONS to force asio to propagate exceptions
|
||||
* through `io_context`
|
||||
* @note Since implicit strand was removed from boost::asio::spawn this helper
|
||||
* function adds the strand back
|
||||
*
|
||||
* @tparam Ctx The type of the context/strand
|
||||
* @tparam F The type of the function to execute
|
||||
* @param ctx The execution context
|
||||
* @param func The function to execute. Must return `void`
|
||||
*/
|
||||
template <typename Ctx, typename F>
|
||||
requires std::is_invocable_r_v<void, F, boost::asio::yield_context>
|
||||
void
|
||||
spawn(Ctx&& ctx, F&& func)
|
||||
{
|
||||
if constexpr (impl::IsStrand<Ctx>)
|
||||
{
|
||||
boost::asio::spawn(
|
||||
std::forward<Ctx>(ctx),
|
||||
std::forward<F>(func),
|
||||
impl::kPROPAGATE_EXCEPTIONS);
|
||||
}
|
||||
else
|
||||
{
|
||||
// make_strand accepts an executor or an execution context directly.
|
||||
// Going through get_associated_executor here resolved a plain
|
||||
// executor to asio's inline executor, which cannot be stranded and
|
||||
// did not compile; nothing in the tree had instantiated this branch.
|
||||
boost::asio::spawn(
|
||||
boost::asio::make_strand(std::forward<Ctx>(ctx)),
|
||||
std::forward<F>(func),
|
||||
impl::kPROPAGATE_EXCEPTIONS);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ripple::util
|
||||
|
||||
#endif
|
||||
@@ -78,10 +78,9 @@ public:
|
||||
error_code ec;
|
||||
|
||||
// Create UDP endpoint from port configuration
|
||||
auto const addr = port_.ip.to_v4();
|
||||
boost::asio::ip::udp::endpoint udp_endpoint(addr, port_.port);
|
||||
boost::asio::ip::udp::endpoint udp_endpoint(port_.ip, port_.port);
|
||||
|
||||
socket_.open(boost::asio::ip::udp::v4(), ec);
|
||||
socket_.open(udp_endpoint.protocol(), ec);
|
||||
if (ec)
|
||||
{
|
||||
JLOG(j_.error()) << "UDP socket open failed: " << ec.message();
|
||||
@@ -94,17 +93,22 @@ public:
|
||||
{
|
||||
JLOG(j_.error())
|
||||
<< "UDP set reuse_address failed: " << ec.message();
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
socket_.bind(udp_endpoint, ec);
|
||||
if (ec)
|
||||
{
|
||||
JLOG(j_.error()) << "UDP socket bind failed: " << ec.message();
|
||||
// Close the socket so that run() does not start receiving on an
|
||||
// unbound socket, which would silently bind an ephemeral port.
|
||||
JLOG(j_.error()) << "UDP socket bind to " << udp_endpoint
|
||||
<< " failed: " << ec.message();
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
JLOG(j_.info()) << "UDP-RPC listening on " << udp_endpoint;
|
||||
JLOG(j_.info()) << "UDP listening on " << udp_endpoint;
|
||||
}
|
||||
|
||||
endpoint_type
|
||||
@@ -169,6 +173,7 @@ private:
|
||||
handler_.onUDPMessage(
|
||||
std::string(recv_buffer_.data(), bytes_transferred),
|
||||
tcp_endpoint,
|
||||
port_,
|
||||
[this, tcp_endpoint](std::string const& response) {
|
||||
do_send(response, tcp_endpoint);
|
||||
});
|
||||
|
||||
@@ -166,7 +166,7 @@ public:
|
||||
May be called concurrently.
|
||||
|
||||
Preconditions:
|
||||
No call to io_service::run on any io_service
|
||||
No call to io_context::run on any io_context
|
||||
used by work objects associated with this io_list
|
||||
exists in the caller's call stack.
|
||||
*/
|
||||
|
||||
@@ -22,7 +22,14 @@
|
||||
#include <xrpl/beast/net/IPAddressConversion.h>
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
#include <boost/asio/bind_executor.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/system/detail/error_code.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
@@ -110,8 +117,8 @@ public:
|
||||
|
||||
beast::Journal m_journal;
|
||||
|
||||
boost::asio::io_service& m_io_service;
|
||||
boost::asio::io_service::strand m_strand;
|
||||
boost::asio::io_context& m_io_context;
|
||||
boost::asio::strand<boost::asio::io_context::executor_type> m_strand;
|
||||
boost::asio::ip::tcp::resolver m_resolver;
|
||||
|
||||
std::condition_variable m_cv;
|
||||
@@ -141,12 +148,12 @@ public:
|
||||
std::deque<Work> m_work;
|
||||
|
||||
ResolverAsioImpl(
|
||||
boost::asio::io_service& io_service,
|
||||
boost::asio::io_context& io_context,
|
||||
beast::Journal journal)
|
||||
: m_journal(journal)
|
||||
, m_io_service(io_service)
|
||||
, m_strand(io_service)
|
||||
, m_resolver(io_service)
|
||||
, m_io_context(io_context)
|
||||
, m_strand(boost::asio::make_strand(io_context))
|
||||
, m_resolver(io_context)
|
||||
, m_asyncHandlersCompleted(true)
|
||||
, m_stop_called(false)
|
||||
, m_stopped(true)
|
||||
@@ -202,8 +209,14 @@ public:
|
||||
{
|
||||
if (m_stop_called.exchange(true) == false)
|
||||
{
|
||||
m_io_service.dispatch(m_strand.wrap(std::bind(
|
||||
&ResolverAsioImpl::do_stop, this, CompletionCounter(this))));
|
||||
boost::asio::dispatch(
|
||||
m_io_context,
|
||||
boost::asio::bind_executor(
|
||||
m_strand,
|
||||
std::bind(
|
||||
&ResolverAsioImpl::do_stop,
|
||||
this,
|
||||
CompletionCounter(this))));
|
||||
|
||||
JLOG(m_journal.debug()) << "Queued a stop request";
|
||||
}
|
||||
@@ -234,12 +247,16 @@ public:
|
||||
|
||||
// TODO NIKB use rvalue references to construct and move
|
||||
// reducing cost.
|
||||
m_io_service.dispatch(m_strand.wrap(std::bind(
|
||||
&ResolverAsioImpl::do_resolve,
|
||||
this,
|
||||
names,
|
||||
handler,
|
||||
CompletionCounter(this))));
|
||||
boost::asio::dispatch(
|
||||
m_io_context,
|
||||
boost::asio::bind_executor(
|
||||
m_strand,
|
||||
std::bind(
|
||||
&ResolverAsioImpl::do_resolve,
|
||||
this,
|
||||
names,
|
||||
handler,
|
||||
CompletionCounter(this))));
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
@@ -265,19 +282,20 @@ public:
|
||||
std::string name,
|
||||
boost::system::error_code const& ec,
|
||||
HandlerType handler,
|
||||
boost::asio::ip::tcp::resolver::iterator iter,
|
||||
boost::asio::ip::tcp::resolver::results_type results,
|
||||
CompletionCounter)
|
||||
{
|
||||
if (ec == boost::asio::error::operation_aborted)
|
||||
return;
|
||||
|
||||
std::vector<beast::IP::Endpoint> addresses;
|
||||
auto iter = results.begin();
|
||||
|
||||
// If we get an error message back, we don't return any
|
||||
// results that we may have gotten.
|
||||
if (!ec)
|
||||
{
|
||||
while (iter != boost::asio::ip::tcp::resolver::iterator())
|
||||
while (iter != results.end())
|
||||
{
|
||||
addresses.push_back(
|
||||
beast::IPAddressConversion::from_asio(*iter));
|
||||
@@ -287,8 +305,14 @@ public:
|
||||
|
||||
handler(name, addresses);
|
||||
|
||||
m_io_service.post(m_strand.wrap(std::bind(
|
||||
&ResolverAsioImpl::do_work, this, CompletionCounter(this))));
|
||||
boost::asio::post(
|
||||
m_io_context,
|
||||
boost::asio::bind_executor(
|
||||
m_strand,
|
||||
std::bind(
|
||||
&ResolverAsioImpl::do_work,
|
||||
this,
|
||||
CompletionCounter(this))));
|
||||
}
|
||||
|
||||
HostAndPort
|
||||
@@ -369,16 +393,21 @@ public:
|
||||
{
|
||||
JLOG(m_journal.error()) << "Unable to parse '" << name << "'";
|
||||
|
||||
m_io_service.post(m_strand.wrap(std::bind(
|
||||
&ResolverAsioImpl::do_work, this, CompletionCounter(this))));
|
||||
boost::asio::post(
|
||||
m_io_context,
|
||||
boost::asio::bind_executor(
|
||||
m_strand,
|
||||
std::bind(
|
||||
&ResolverAsioImpl::do_work,
|
||||
this,
|
||||
CompletionCounter(this))));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
boost::asio::ip::tcp::resolver::query query(host, port);
|
||||
|
||||
m_resolver.async_resolve(
|
||||
query,
|
||||
host,
|
||||
port,
|
||||
std::bind(
|
||||
&ResolverAsioImpl::do_finish,
|
||||
this,
|
||||
@@ -409,10 +438,14 @@ public:
|
||||
|
||||
if (m_work.size() > 0)
|
||||
{
|
||||
m_io_service.post(m_strand.wrap(std::bind(
|
||||
&ResolverAsioImpl::do_work,
|
||||
this,
|
||||
CompletionCounter(this))));
|
||||
boost::asio::post(
|
||||
m_io_context,
|
||||
boost::asio::bind_executor(
|
||||
m_strand,
|
||||
std::bind(
|
||||
&ResolverAsioImpl::do_work,
|
||||
this,
|
||||
CompletionCounter(this))));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -421,9 +454,9 @@ public:
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
std::unique_ptr<ResolverAsio>
|
||||
ResolverAsio::New(boost::asio::io_service& io_service, beast::Journal journal)
|
||||
ResolverAsio::New(boost::asio::io_context& io_context, beast::Journal journal)
|
||||
{
|
||||
return std::make_unique<ResolverAsioImpl>(io_service, journal);
|
||||
return std::make_unique<ResolverAsioImpl>(io_context, journal);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 XRPL Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpl/basics/UTF8.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
bool
|
||||
isValidUTF8(Slice const& s) noexcept
|
||||
{
|
||||
std::size_t const size = s.size();
|
||||
|
||||
// Markus Kuhn's utf8_check.c
|
||||
// (https://www.cl.cam.ac.uk/~mgk25/ucs/utf8_check.c), the decoder
|
||||
// URIToken has always used, with one change: each branch establishes
|
||||
// that the whole sequence is present before reading any continuation
|
||||
// byte, so a truncated tail is invalid rather than an out-of-bounds read.
|
||||
std::size_t i = 0;
|
||||
while (i < size)
|
||||
{
|
||||
std::uint8_t const c0 = s[i];
|
||||
|
||||
if (c0 < 0x80)
|
||||
{
|
||||
// 0xxxxxxx
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((c0 & 0xE0) == 0xC0)
|
||||
{
|
||||
// 110xxxxx 10xxxxxx
|
||||
if (size - i < 2)
|
||||
return false;
|
||||
if ((s[i + 1] & 0xC0) != 0x80)
|
||||
return false;
|
||||
if ((c0 & 0xFE) == 0xC0) // overlong
|
||||
return false;
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((c0 & 0xF0) == 0xE0)
|
||||
{
|
||||
// 1110xxxx 10xxxxxx 10xxxxxx
|
||||
if (size - i < 3)
|
||||
return false;
|
||||
std::uint8_t const c1 = s[i + 1];
|
||||
if ((c1 & 0xC0) != 0x80 || (s[i + 2] & 0xC0) != 0x80)
|
||||
return false;
|
||||
if (c0 == 0xE0 && (c1 & 0xE0) == 0x80) // overlong
|
||||
return false;
|
||||
if (c0 == 0xED && (c1 & 0xE0) == 0xA0) // UTF-16 surrogate
|
||||
return false;
|
||||
if (c0 == 0xEF && c1 == 0xBF &&
|
||||
(s[i + 2] & 0xFE) == 0xBE) // U+FFFE or U+FFFF
|
||||
return false;
|
||||
i += 3;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((c0 & 0xF8) == 0xF0)
|
||||
{
|
||||
// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
if (size - i < 4)
|
||||
return false;
|
||||
std::uint8_t const c1 = s[i + 1];
|
||||
if ((c1 & 0xC0) != 0x80 || (s[i + 2] & 0xC0) != 0x80 ||
|
||||
(s[i + 3] & 0xC0) != 0x80)
|
||||
return false;
|
||||
if (c0 == 0xF0 && (c1 & 0xF0) == 0x80) // overlong
|
||||
return false;
|
||||
if (c0 > 0xF4 || (c0 == 0xF4 && c1 > 0x8F)) // > U+10FFFF
|
||||
return false;
|
||||
i += 4;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Stray continuation byte or 0xF8..0xFF.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
@@ -26,10 +26,24 @@
|
||||
#include <xrpl/beast/insight/StatsDCollector.h>
|
||||
#include <xrpl/beast/net/IPAddressConversion.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
|
||||
#include <boost/asio/basic_waitable_timer.hpp>
|
||||
#include <boost/asio/bind_executor.hpp>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/asio/executor_work_guard.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ip/udp.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/system/detail/error_code.hpp>
|
||||
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
@@ -223,9 +237,11 @@ private:
|
||||
Journal m_journal;
|
||||
IP::Endpoint m_address;
|
||||
std::string m_prefix;
|
||||
boost::asio::io_service m_io_service;
|
||||
std::optional<boost::asio::io_service::work> m_work;
|
||||
boost::asio::io_service::strand m_strand;
|
||||
boost::asio::io_context m_io_context;
|
||||
std::optional<boost::asio::executor_work_guard<
|
||||
boost::asio::io_context::executor_type>>
|
||||
m_work;
|
||||
boost::asio::strand<boost::asio::io_context::executor_type> m_strand;
|
||||
boost::asio::basic_waitable_timer<std::chrono::steady_clock> m_timer;
|
||||
boost::asio::ip::udp::socket m_socket;
|
||||
std::deque<std::string> m_data;
|
||||
@@ -249,18 +265,24 @@ public:
|
||||
: m_journal(journal)
|
||||
, m_address(address)
|
||||
, m_prefix(prefix)
|
||||
, m_work(std::ref(m_io_service))
|
||||
, m_strand(m_io_service)
|
||||
, m_timer(m_io_service)
|
||||
, m_socket(m_io_service)
|
||||
, m_work(boost::asio::make_work_guard(m_io_context))
|
||||
, m_strand(boost::asio::make_strand(m_io_context))
|
||||
, m_timer(m_io_context)
|
||||
, m_socket(m_io_context)
|
||||
, m_thread(&StatsDCollectorImp::run, this)
|
||||
{
|
||||
}
|
||||
|
||||
~StatsDCollectorImp() override
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
m_timer.cancel(ec);
|
||||
try
|
||||
{
|
||||
m_timer.cancel();
|
||||
}
|
||||
catch (boost::system::system_error const&)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
m_work.reset();
|
||||
m_thread.join();
|
||||
@@ -319,10 +341,10 @@ public:
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
boost::asio::io_service&
|
||||
get_io_service()
|
||||
boost::asio::io_context&
|
||||
get_io_context()
|
||||
{
|
||||
return m_io_service;
|
||||
return m_io_context;
|
||||
}
|
||||
|
||||
std::string const&
|
||||
@@ -340,8 +362,14 @@ public:
|
||||
void
|
||||
post_buffer(std::string&& buffer)
|
||||
{
|
||||
m_io_service.dispatch(m_strand.wrap(std::bind(
|
||||
&StatsDCollectorImp::do_post_buffer, this, std::move(buffer))));
|
||||
boost::asio::dispatch(
|
||||
m_io_context,
|
||||
boost::asio::bind_executor(
|
||||
m_strand,
|
||||
std::bind(
|
||||
&StatsDCollectorImp::do_post_buffer,
|
||||
this,
|
||||
std::move(buffer))));
|
||||
}
|
||||
|
||||
// The keepAlive parameter makes sure the buffers sent to
|
||||
@@ -371,7 +399,7 @@ public:
|
||||
for (auto const& buffer : buffers)
|
||||
{
|
||||
std::string const s(
|
||||
boost::asio::buffer_cast<char const*>(buffer),
|
||||
static_cast<char const*>(buffer.data()),
|
||||
boost::asio::buffer_size(buffer));
|
||||
std::cerr << s;
|
||||
}
|
||||
@@ -441,7 +469,7 @@ public:
|
||||
set_timer()
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
m_timer.expires_from_now(1s);
|
||||
m_timer.expires_after(1s);
|
||||
m_timer.async_wait(std::bind(
|
||||
&StatsDCollectorImp::on_timer, this, std::placeholders::_1));
|
||||
}
|
||||
@@ -483,13 +511,13 @@ public:
|
||||
|
||||
set_timer();
|
||||
|
||||
m_io_service.run();
|
||||
m_io_context.run();
|
||||
|
||||
m_socket.shutdown(boost::asio::ip::udp::socket::shutdown_send, ec);
|
||||
|
||||
m_socket.close();
|
||||
|
||||
m_io_service.poll();
|
||||
m_io_context.poll();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -532,10 +560,12 @@ StatsDCounterImpl::~StatsDCounterImpl()
|
||||
void
|
||||
StatsDCounterImpl::increment(CounterImpl::value_type amount)
|
||||
{
|
||||
m_impl->get_io_service().dispatch(std::bind(
|
||||
&StatsDCounterImpl::do_increment,
|
||||
std::static_pointer_cast<StatsDCounterImpl>(shared_from_this()),
|
||||
amount));
|
||||
boost::asio::dispatch(
|
||||
m_impl->get_io_context(),
|
||||
std::bind(
|
||||
&StatsDCounterImpl::do_increment,
|
||||
std::static_pointer_cast<StatsDCounterImpl>(shared_from_this()),
|
||||
amount));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -577,10 +607,12 @@ StatsDEventImpl::StatsDEventImpl(
|
||||
void
|
||||
StatsDEventImpl::notify(EventImpl::value_type const& value)
|
||||
{
|
||||
m_impl->get_io_service().dispatch(std::bind(
|
||||
&StatsDEventImpl::do_notify,
|
||||
std::static_pointer_cast<StatsDEventImpl>(shared_from_this()),
|
||||
value));
|
||||
boost::asio::dispatch(
|
||||
m_impl->get_io_context(),
|
||||
std::bind(
|
||||
&StatsDEventImpl::do_notify,
|
||||
std::static_pointer_cast<StatsDEventImpl>(shared_from_this()),
|
||||
value));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -610,19 +642,23 @@ StatsDGaugeImpl::~StatsDGaugeImpl()
|
||||
void
|
||||
StatsDGaugeImpl::set(GaugeImpl::value_type value)
|
||||
{
|
||||
m_impl->get_io_service().dispatch(std::bind(
|
||||
&StatsDGaugeImpl::do_set,
|
||||
std::static_pointer_cast<StatsDGaugeImpl>(shared_from_this()),
|
||||
value));
|
||||
boost::asio::dispatch(
|
||||
m_impl->get_io_context(),
|
||||
std::bind(
|
||||
&StatsDGaugeImpl::do_set,
|
||||
std::static_pointer_cast<StatsDGaugeImpl>(shared_from_this()),
|
||||
value));
|
||||
}
|
||||
|
||||
void
|
||||
StatsDGaugeImpl::increment(GaugeImpl::difference_type amount)
|
||||
{
|
||||
m_impl->get_io_service().dispatch(std::bind(
|
||||
&StatsDGaugeImpl::do_increment,
|
||||
std::static_pointer_cast<StatsDGaugeImpl>(shared_from_this()),
|
||||
amount));
|
||||
boost::asio::dispatch(
|
||||
m_impl->get_io_context(),
|
||||
std::bind(
|
||||
&StatsDGaugeImpl::do_increment,
|
||||
std::static_pointer_cast<StatsDGaugeImpl>(shared_from_this()),
|
||||
amount));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -698,10 +734,12 @@ StatsDMeterImpl::~StatsDMeterImpl()
|
||||
void
|
||||
StatsDMeterImpl::increment(MeterImpl::value_type amount)
|
||||
{
|
||||
m_impl->get_io_service().dispatch(std::bind(
|
||||
&StatsDMeterImpl::do_increment,
|
||||
std::static_pointer_cast<StatsDMeterImpl>(shared_from_this()),
|
||||
amount));
|
||||
boost::asio::dispatch(
|
||||
m_impl->get_io_context(),
|
||||
std::bind(
|
||||
&StatsDMeterImpl::do_increment,
|
||||
std::static_pointer_cast<StatsDMeterImpl>(shared_from_this()),
|
||||
amount));
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -28,11 +28,11 @@ namespace IP {
|
||||
bool
|
||||
is_private(AddressV4 const& addr)
|
||||
{
|
||||
return ((addr.to_ulong() & 0xff000000) ==
|
||||
return ((addr.to_uint() & 0xff000000) ==
|
||||
0x0a000000) || // Prefix /8, 10. #.#.#
|
||||
((addr.to_ulong() & 0xfff00000) ==
|
||||
((addr.to_uint() & 0xfff00000) ==
|
||||
0xac100000) || // Prefix /12 172. 16.#.# - 172.31.#.#
|
||||
((addr.to_ulong() & 0xffff0000) ==
|
||||
((addr.to_uint() & 0xffff0000) ==
|
||||
0xc0a80000) || // Prefix /16 192.168.#.#
|
||||
addr.is_loopback();
|
||||
}
|
||||
@@ -47,7 +47,7 @@ char
|
||||
get_class(AddressV4 const& addr)
|
||||
{
|
||||
static char const* table = "AAAABBCD";
|
||||
return table[(addr.to_ulong() & 0xE0000000) >> 29];
|
||||
return table[(addr.to_uint() & 0xE0000000) >> 29];
|
||||
}
|
||||
|
||||
} // namespace IP
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#include <xrpl/beast/net/IPAddressV4.h>
|
||||
#include <xrpl/beast/net/IPAddressV6.h>
|
||||
|
||||
#include <boost/asio/ip/address_v4.hpp>
|
||||
|
||||
namespace beast {
|
||||
namespace IP {
|
||||
|
||||
@@ -28,7 +30,9 @@ is_private(AddressV6 const& addr)
|
||||
{
|
||||
return (
|
||||
(addr.to_bytes()[0] & 0xfd) || // TODO fc00::/8 too ?
|
||||
(addr.is_v4_mapped() && is_private(addr.to_v4())));
|
||||
(addr.is_v4_mapped() &&
|
||||
is_private(boost::asio::ip::make_address_v4(
|
||||
boost::asio::ip::v4_mapped, addr))));
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@@ -18,7 +18,19 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpl/beast/net/IPEndpoint.h>
|
||||
|
||||
#include <boost/algorithm/string/trim.hpp>
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include <boost/asio/ip/address_v4.hpp>
|
||||
#include <boost/system/detail/error_code.hpp>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <cctype>
|
||||
#include <ios>
|
||||
#include <istream>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace beast {
|
||||
namespace IP {
|
||||
@@ -157,7 +169,7 @@ operator>>(std::istream& is, Endpoint& endpoint)
|
||||
}
|
||||
|
||||
boost::system::error_code ec;
|
||||
auto addr = Address::from_string(addrStr, ec);
|
||||
auto addr = boost::asio::ip::make_address(addrStr, ec);
|
||||
if (ec)
|
||||
{
|
||||
is.setstate(std::ios_base::failbit);
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 XRPL Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/UTF8.h>
|
||||
#include <xrpl/protocol/AppLoader.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace ripple {
|
||||
namespace appLoader {
|
||||
|
||||
namespace {
|
||||
|
||||
// HTML "ASCII whitespace": TAB, LF, FF, CR, SPACE.
|
||||
inline bool
|
||||
isSpace(std::uint8_t c) noexcept
|
||||
{
|
||||
return c == 0x09 || c == 0x0A || c == 0x0C || c == 0x0D || c == 0x20;
|
||||
}
|
||||
|
||||
inline std::uint8_t
|
||||
lower(std::uint8_t c) noexcept
|
||||
{
|
||||
return (c >= 'A' && c <= 'Z') ? static_cast<std::uint8_t>(c + 32) : c;
|
||||
}
|
||||
|
||||
// Case-insensitive match of lower-case `lit` at `pos`; never reads past
|
||||
// the end.
|
||||
bool
|
||||
matchAt(
|
||||
std::uint8_t const* data,
|
||||
std::size_t size,
|
||||
std::size_t pos,
|
||||
std::string_view lit) noexcept
|
||||
{
|
||||
if (pos > size || size - pos < lit.size())
|
||||
return false;
|
||||
for (std::size_t i = 0; i < lit.size(); ++i)
|
||||
if (lower(data[pos + i]) != static_cast<std::uint8_t>(lit[i]))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::size_t
|
||||
findFrom(
|
||||
std::uint8_t const* data,
|
||||
std::size_t size,
|
||||
std::size_t from,
|
||||
std::string_view lit) noexcept
|
||||
{
|
||||
for (std::size_t i = from; i + lit.size() <= size; ++i)
|
||||
if (matchAt(data, size, i, lit))
|
||||
return i;
|
||||
return size;
|
||||
}
|
||||
|
||||
// So that "<htmlish>" is not mistaken for "<html>".
|
||||
inline bool
|
||||
isTagNameEnd(std::uint8_t c) noexcept
|
||||
{
|
||||
return isSpace(c) || c == '>' || c == '/';
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Result
|
||||
validate(std::uint8_t const* data, std::size_t size)
|
||||
{
|
||||
if (size == 0 || data == nullptr)
|
||||
return Result::empty;
|
||||
if (size > maxAppLoaderLength)
|
||||
return Result::tooLarge;
|
||||
if (!isValidUTF8(Slice(data, size)))
|
||||
return Result::badUTF8;
|
||||
for (std::size_t i = 0; i < size; ++i)
|
||||
if (!isSpace(data[i]) && (data[i] < 0x20 || data[i] == 0x7F))
|
||||
return Result::badControlChar;
|
||||
|
||||
std::size_t begin = 0;
|
||||
if (size >= 3 && data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF)
|
||||
begin = 3;
|
||||
while (begin < size && isSpace(data[begin]))
|
||||
++begin;
|
||||
if (begin == size)
|
||||
return Result::empty;
|
||||
|
||||
// "<!doctype" followed by whitespace is the only doctype form any
|
||||
// browser treats as standards-mode HTML.
|
||||
bool const doctype = matchAt(data, size, begin, "<!doctype") &&
|
||||
begin + 9 < size && isSpace(data[begin + 9]);
|
||||
bool const html = matchAt(data, size, begin, "<html") && begin + 5 < size &&
|
||||
isTagNameEnd(data[begin + 5]);
|
||||
if (!doctype && !html)
|
||||
return Result::noDoctype;
|
||||
|
||||
std::size_t open = begin;
|
||||
for (;;)
|
||||
{
|
||||
open = findFrom(data, size, open, "<html");
|
||||
if (open == size)
|
||||
return Result::noHtmlElement;
|
||||
if (open + 5 < size && isTagNameEnd(data[open + 5]))
|
||||
break;
|
||||
++open;
|
||||
}
|
||||
|
||||
// The LAST well-formed </html>, so that one quoted inside a script
|
||||
// does not shadow the real one. Linear: each probe resumes one past
|
||||
// the previous match.
|
||||
// closeEnd == 0 means none found: a well-formed </html> always ends
|
||||
// past position 0.
|
||||
std::size_t closeEnd = 0;
|
||||
for (std::size_t probe = open;;)
|
||||
{
|
||||
std::size_t const found = findFrom(data, size, probe, "</html");
|
||||
if (found == size)
|
||||
break;
|
||||
std::size_t j = found + 6;
|
||||
while (j < size && isSpace(data[j]))
|
||||
++j;
|
||||
if (j < size && data[j] == '>')
|
||||
closeEnd = j + 1;
|
||||
probe = found + 1;
|
||||
}
|
||||
if (closeEnd == 0)
|
||||
return Result::unclosed;
|
||||
|
||||
for (std::size_t i = closeEnd; i < size; ++i)
|
||||
if (!isSpace(data[i]))
|
||||
return Result::trailingGarbage;
|
||||
return Result::ok;
|
||||
}
|
||||
|
||||
char const*
|
||||
to_string(Result r)
|
||||
{
|
||||
switch (r)
|
||||
{
|
||||
case Result::ok:
|
||||
return "ok";
|
||||
case Result::empty:
|
||||
return "empty document";
|
||||
case Result::tooLarge:
|
||||
return "document exceeds maxAppLoaderLength";
|
||||
case Result::badUTF8:
|
||||
return "malformed UTF-8";
|
||||
case Result::badControlChar:
|
||||
return "forbidden control character";
|
||||
case Result::noDoctype:
|
||||
return "does not open with a doctype or <html> tag";
|
||||
case Result::noHtmlElement:
|
||||
return "missing <html> start tag";
|
||||
case Result::unclosed:
|
||||
return "missing or misplaced </html> end tag";
|
||||
case Result::trailingGarbage:
|
||||
return "content after </html>";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
} // namespace appLoader
|
||||
} // namespace ripple
|
||||
@@ -56,9 +56,9 @@ getVersionString()
|
||||
(versionString == std::string("0.") + std::string("0.0") ||
|
||||
versionString == std::string("0.0.0+DEBUG")))
|
||||
{
|
||||
std::string y = std::string(__DATE__ + 7);
|
||||
std::string y = std::string(&__DATE__[7]);
|
||||
std::string d = std::string(
|
||||
__DATE__ + 4 + (__DATE__[4] == ' ' ? 1 : 0),
|
||||
&__DATE__[4 + (__DATE__[4] == ' ' ? 1 : 0)],
|
||||
__DATE__[4] == ' ' ? 1 : 2);
|
||||
std::string m;
|
||||
switch (__DATE__[0])
|
||||
|
||||
@@ -81,6 +81,7 @@ enum class LedgerNameSpace : std::uint16_t {
|
||||
IMPORT_VLSEQ = 'I',
|
||||
UNL_REPORT = 'R',
|
||||
CRON = 'L',
|
||||
APP_LOADER = 'W',
|
||||
AMM = 'A',
|
||||
BRIDGE = LEDGER_NAMESPACE2(0x01, 'H'),
|
||||
XCHAIN_CLAIM_ID = 'Q',
|
||||
@@ -90,8 +91,10 @@ enum class LedgerNameSpace : std::uint16_t {
|
||||
MPTOKEN_ISSUANCE = '~',
|
||||
MPTOKEN = 't',
|
||||
MANIFEST = 'M',
|
||||
MANIFEST_DIR = LEDGER_NAMESPACE2(0x01, 'M'),
|
||||
CREDENTIAL = LEDGER_NAMESPACE2(0x01, 'D'),
|
||||
PERMISSIONED_DOMAIN = 'm',
|
||||
RANDOM = 0x526E, // Rn
|
||||
|
||||
// No longer used or supported. Left here to reserve the space
|
||||
// to avoid accidental reuse.
|
||||
@@ -276,6 +279,13 @@ UNLReport() noexcept
|
||||
return ret;
|
||||
}
|
||||
|
||||
Keylet const&
|
||||
random() noexcept
|
||||
{
|
||||
static Keylet const ret{ltRANDOM, indexHash(LedgerNameSpace::RANDOM)};
|
||||
return ret;
|
||||
}
|
||||
|
||||
Keylet
|
||||
book_t::operator()(Book const& b) const
|
||||
{
|
||||
@@ -613,6 +623,12 @@ did(AccountID const& account) noexcept
|
||||
return {ltDID, indexHash(LedgerNameSpace::DID, account)};
|
||||
}
|
||||
|
||||
Keylet
|
||||
appLoader(AccountID const& account) noexcept
|
||||
{
|
||||
return {ltAPP_LOADER, indexHash(LedgerNameSpace::APP_LOADER, account)};
|
||||
}
|
||||
|
||||
Keylet
|
||||
oracle(AccountID const& account, std::uint32_t const& documentID) noexcept
|
||||
{
|
||||
@@ -677,6 +693,14 @@ manifest(PublicKey const& pk) noexcept
|
||||
return {ltMANIFEST, indexHash(LedgerNameSpace::MANIFEST, pk.slice())};
|
||||
}
|
||||
|
||||
Keylet const&
|
||||
manifestDir() noexcept
|
||||
{
|
||||
static Keylet const ret{
|
||||
ltDIR_NODE, indexHash(LedgerNameSpace::MANIFEST_DIR)};
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace keylet
|
||||
|
||||
} // namespace ripple
|
||||
@@ -143,6 +143,14 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfAccount, soeOPTIONAL},
|
||||
});
|
||||
|
||||
add(sfRandomDigestEntry.jsonName,
|
||||
sfRandomDigestEntry.getCode(),
|
||||
{
|
||||
{sfPublicKey, soeREQUIRED},
|
||||
{sfNextRandomDigest, soeREQUIRED},
|
||||
{sfLedgerSequence, soeREQUIRED},
|
||||
});
|
||||
|
||||
add(sfAmountEntry.jsonName,
|
||||
sfAmountEntry.getCode(),
|
||||
{
|
||||
|
||||
@@ -684,7 +684,8 @@ isPseudoTx(STObject const& tx)
|
||||
|
||||
auto tt = safe_cast<TxType>(*t);
|
||||
return tt == ttAMENDMENT || tt == ttFEE || tt == ttUNL_MODIFY ||
|
||||
tt == ttEMIT_FAILURE || tt == ttUNL_REPORT || tt == ttCRON;
|
||||
tt == ttEMIT_FAILURE || tt == ttUNL_REPORT || tt == ttCRON ||
|
||||
tt == ttENTROPY;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
@@ -153,6 +153,7 @@ transResults()
|
||||
MAKE_ERROR(tefINVALID_LEDGER_FIX_TYPE, "The LedgerFixType field has an invalid value."),
|
||||
MAKE_ERROR(tefPAST_MANIFEST_SEQ, "The submitted manifest's sequence is not newer than the current."),
|
||||
MAKE_ERROR(tefREVOKED_MANIFEST, "The submitted manifest is for a revoked master key."),
|
||||
MAKE_ERROR(tefMANIFEST_KEY_IN_USE, "A key the submitted manifest names is already in use by another manifest."),
|
||||
|
||||
MAKE_ERROR(telLOCAL_ERROR, "Local failure."),
|
||||
MAKE_ERROR(telBAD_DOMAIN, "Domain too long."),
|
||||
@@ -174,6 +175,7 @@ transResults()
|
||||
MAKE_ERROR(telIMPORT_VL_KEY_NOT_RECOGNISED, "Import vl key was not recognized."),
|
||||
MAKE_ERROR(telCAN_NOT_QUEUE_IMPORT, "Import transaction was not able to be directly applied and cannot be queued."),
|
||||
MAKE_ERROR(telENV_RPC_FAILED, "Unit test RPC failure."),
|
||||
MAKE_ERROR(telMANIFEST_FEE_MISMATCH, "Unsigned SetManifest fee is not, or cannot be confirmed as, canonical for this server's ledger fee settings."),
|
||||
|
||||
MAKE_ERROR(temMALFORMED, "Malformed transaction."),
|
||||
MAKE_ERROR(temBAD_AMM_TOKENS, "Malformed: Invalid LPTokens."),
|
||||
|
||||
@@ -49,6 +49,9 @@ TxMeta::TxMeta(
|
||||
|
||||
if (obj.isFieldPresent(sfHookEmissions))
|
||||
setHookEmissions(obj.getFieldArray(sfHookEmissions));
|
||||
|
||||
if (obj.isFieldPresent(sfRandomData))
|
||||
setRandomData(obj.getFieldH256(sfRandomData));
|
||||
}
|
||||
|
||||
TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj)
|
||||
@@ -75,6 +78,9 @@ TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj)
|
||||
|
||||
if (obj.isFieldPresent(sfHookEmissions))
|
||||
setHookEmissions(obj.getFieldArray(sfHookEmissions));
|
||||
|
||||
if (obj.isFieldPresent(sfRandomData))
|
||||
setRandomData(obj.getFieldH256(sfRandomData));
|
||||
}
|
||||
|
||||
TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, Blob const& vec)
|
||||
@@ -245,6 +251,9 @@ TxMeta::getAsObject() const
|
||||
if (hasHookEmissions())
|
||||
metaData.setFieldArray(sfHookEmissions, getHookEmissions());
|
||||
|
||||
if (hasRandomData())
|
||||
metaData.setFieldH256(sfRandomData, getRandomData());
|
||||
|
||||
return metaData;
|
||||
}
|
||||
|
||||
|
||||
@@ -545,7 +545,7 @@ b58_to_b256_be(std::string_view input, std::span<std::uint8_t> out)
|
||||
XRPL_ASSERT(
|
||||
num_b_58_10_coeffs <= b_58_10_coeff.size(),
|
||||
"ripple::b58_fast::detail::b58_to_b256_be : maximum coeff");
|
||||
for (auto c : input.substr(0, partial_coeff_len))
|
||||
for (unsigned char const c : input.substr(0, partial_coeff_len))
|
||||
{
|
||||
auto cur_val = ::ripple::alphabetReverse[c];
|
||||
if (cur_val < 0)
|
||||
@@ -559,7 +559,7 @@ b58_to_b256_be(std::string_view input, std::span<std::uint8_t> out)
|
||||
{
|
||||
for (int j = 0; j < num_full_coeffs; ++j)
|
||||
{
|
||||
auto c = input[partial_coeff_len + j * 10 + i];
|
||||
unsigned char const c = input[partial_coeff_len + j * 10 + i];
|
||||
auto cur_val = ::ripple::alphabetReverse[c];
|
||||
if (cur_val < 0)
|
||||
{
|
||||
@@ -580,6 +580,12 @@ b58_to_b256_be(std::string_view input, std::span<std::uint8_t> out)
|
||||
{
|
||||
std::uint64_t const c = b_58_10_coeff[i];
|
||||
|
||||
// Each digit is < 58, so at most 4 words are in use here and the
|
||||
// span below always fits `result`. Guard the invariant anyway rather
|
||||
// than letting a violation index past the end of the stack array.
|
||||
if (cur_result_size >= result.size())
|
||||
return Unexpected(TokenCodecErrc::inputTooLarge);
|
||||
|
||||
{
|
||||
auto code = ripple::b58_fast::detail::inplace_bigint_mul(
|
||||
std::span(&result[0], cur_result_size + 1), B_58_10);
|
||||
|
||||
@@ -21,9 +21,15 @@
|
||||
#include <xrpl/beast/core/LexicalCast.h>
|
||||
#include <xrpl/beast/rfc2616.h>
|
||||
#include <xrpl/server/Port.h>
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include <boost/algorithm/string/trim.hpp>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -63,6 +69,14 @@ operator<<(std::ostream& os, Port const& p)
|
||||
}
|
||||
}
|
||||
|
||||
if (!p.pwa_domains.empty())
|
||||
{
|
||||
os << "domains:";
|
||||
for (auto const& d : p.pwa_domains)
|
||||
os << " " << d;
|
||||
os << ", ";
|
||||
}
|
||||
|
||||
if (p.secure_gateway_nets_v4.size() || p.secure_gateway_nets_v6.size())
|
||||
{
|
||||
os << "secure_gateway nets:";
|
||||
@@ -205,7 +219,7 @@ parse_Port(ParsedPort& port, Section const& section, std::ostream& log)
|
||||
{
|
||||
try
|
||||
{
|
||||
port.ip = boost::asio::ip::address::from_string(*optResult);
|
||||
port.ip = boost::asio::ip::make_address(*optResult);
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
@@ -308,6 +322,64 @@ parse_Port(ParsedPort& port, Section const& section, std::ostream& log)
|
||||
port.secure_gateway_nets_v4,
|
||||
port.secure_gateway_nets_v6);
|
||||
|
||||
// pwa ports: the zone(s) documents are served under, normalised like a
|
||||
// Host header: lower case, no trailing dot, LDH labels of 1..63, with
|
||||
// room left for the longest "pwa<AccountIndex>" label in front.
|
||||
if (auto const optResult = section.get("domain"))
|
||||
{
|
||||
std::vector<std::string> domains;
|
||||
boost::algorithm::split(
|
||||
domains, *optResult, boost::algorithm::is_any_of(","));
|
||||
for (auto& domain : domains)
|
||||
{
|
||||
boost::algorithm::trim(domain);
|
||||
boost::algorithm::to_lower(domain);
|
||||
if (!domain.empty() && domain.back() == '.')
|
||||
domain.pop_back();
|
||||
if (domain.empty())
|
||||
continue;
|
||||
bool valid = domain.size() + 24 <= 253;
|
||||
std::vector<std::string> labels;
|
||||
boost::algorithm::split(
|
||||
labels, domain, boost::algorithm::is_any_of("."));
|
||||
for (auto const& l : labels)
|
||||
valid = valid && !l.empty() && l.size() <= 63 &&
|
||||
l.front() != '-' && l.back() != '-' &&
|
||||
l.find_first_not_of(
|
||||
"abcdefghijklmnopqrstuvwxyz0123456789-") ==
|
||||
std::string::npos;
|
||||
if (!valid)
|
||||
{
|
||||
log << "Invalid value '" << domain << "' for key 'domain' in ["
|
||||
<< section.name()
|
||||
<< "]: expected a DNS zone such as pwapp.example";
|
||||
Throw<std::exception>();
|
||||
}
|
||||
port.pwa_domains.insert(domain);
|
||||
}
|
||||
}
|
||||
|
||||
// Custom domains on pwa ports, and the IPv4 recursive resolver to use for
|
||||
// their TXT records (default: the system resolver).
|
||||
if (auto const v = section.get("custom_domains"))
|
||||
if (!beast::lexicalCastChecked(port.pwa_custom_domains, *v))
|
||||
{
|
||||
log << "Invalid value '" << *v << "' for key 'custom_domains' in ["
|
||||
<< section.name() << "]: expected 0 or 1";
|
||||
Throw<std::exception>();
|
||||
}
|
||||
if (auto const v = section.get("resolver"))
|
||||
{
|
||||
auto const ep = beast::IP::Endpoint::from_string_checked(*v);
|
||||
if (!ep || !ep->is_v4() || ep->address().is_unspecified())
|
||||
{
|
||||
log << "Invalid value '" << *v << "' for key 'resolver' in ["
|
||||
<< section.name() << "]: expected an IPv4 address[:port]";
|
||||
Throw<std::exception>();
|
||||
}
|
||||
port.pwa_resolver = ep->port() ? *ep : ep->at_port(53);
|
||||
}
|
||||
|
||||
set(port.user, "user", section);
|
||||
set(port.password, "password", section);
|
||||
set(port.admin_user, "admin_user", section);
|
||||
|
||||
@@ -150,12 +150,15 @@ private:
|
||||
|
||||
env.fund(XRP(20'000), alice, bob, carol, gw1, gw2);
|
||||
env.fund(XRP(20'000), dan);
|
||||
env.close();
|
||||
env.trust(USD1(20'000), alice, bob, carol, dan);
|
||||
env.trust(USD2(1'000), alice, bob, carol, dan);
|
||||
env.close();
|
||||
|
||||
env(pay(gw1, dan, USD1(10'050)));
|
||||
env(pay(gw1, bob, USD1(50)));
|
||||
env(pay(gw2, bob, USD2(50)));
|
||||
env.close();
|
||||
|
||||
AMM ammDan(env, dan, XRP(10'000), USD1(10'050));
|
||||
|
||||
@@ -1990,6 +1993,7 @@ private:
|
||||
|
||||
env.fund(XRP(10'000), alice, carol, gw);
|
||||
env.fund(XRP(10'000), bob);
|
||||
env.close();
|
||||
env.trust(USD(1'000), alice, bob, carol);
|
||||
env.trust(BTC(1'000), alice, bob, carol);
|
||||
env.trust(EUR(1'000), alice, bob, carol);
|
||||
@@ -3641,7 +3645,7 @@ private:
|
||||
USD(10'000),
|
||||
false,
|
||||
0,
|
||||
ammCrtFee(env).drops(),
|
||||
ammCrtFee(env).drops() + 2 * env.current()->fees().base.drops(),
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
ms,
|
||||
|
||||
@@ -5004,8 +5004,8 @@ private:
|
||||
alice,
|
||||
STAmount{USD, UINT64_C(30'000'0000000003), -10}));
|
||||
// alice XRP balance is 30,000initial - 50 ammcreate fee -
|
||||
// 10drops fee
|
||||
BEAST_EXPECT(accountBalance(env, alice) == "29949999990");
|
||||
// 10drops ammcreate base fee - 10drops fee
|
||||
BEAST_EXPECT(accountBalance(env, alice) == "29949999980");
|
||||
},
|
||||
std::nullopt,
|
||||
0,
|
||||
@@ -5081,7 +5081,7 @@ private:
|
||||
(xrpBalance + drops(5)).getText());
|
||||
BEAST_EXPECT(
|
||||
accountBalance(env, alice) ==
|
||||
std::to_string(29'950'000'000 - baseFee + 80));
|
||||
std::to_string(29'950'000'000 - 2 * baseFee + 80));
|
||||
},
|
||||
std::nullopt,
|
||||
0,
|
||||
|
||||
@@ -563,6 +563,39 @@ public:
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
ter(tecNO_TARGET));
|
||||
env.close();
|
||||
}
|
||||
// Invalid HookName
|
||||
{
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook, overrideFlag);
|
||||
hookObj[jss::HookOn] = to_string(~UINT256_BIT[ttCLAIM_REWARD]);
|
||||
hookObj[jss::HookName] = "4142434445";
|
||||
env(hook(issuer, {{hookObj}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
ter(tecNO_TARGET));
|
||||
env.close();
|
||||
}
|
||||
// HookName removed with a HookName-only update
|
||||
// The sfHooks entry keeps an STI_NOTPRESENT placeholder for
|
||||
// sfHookName, which preclaim must treat as "no name".
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::Account] = issuer.human();
|
||||
jv[jss::TransactionType] = jss::SetHook;
|
||||
jv[jss::Flags] = 0;
|
||||
jv[jss::Hooks] = Json::Value{Json::arrayValue};
|
||||
jv[jss::Hooks][0U][jss::Hook][jss::HookName] = "";
|
||||
env(jv, fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
}
|
||||
// Vaild HookOn
|
||||
{
|
||||
|
||||
@@ -61,7 +61,7 @@ public:
|
||||
pUrl_.domain,
|
||||
pUrl_.path,
|
||||
port_,
|
||||
env_.app().getIOService(),
|
||||
env_.app().getIOContext(),
|
||||
env_.journal,
|
||||
env_.app().config(),
|
||||
lastEndpoint,
|
||||
@@ -78,10 +78,11 @@ public:
|
||||
isMultipleEndpoints()
|
||||
{
|
||||
using boost::asio::ip::tcp;
|
||||
tcp::resolver resolver(env_.app().getIOService());
|
||||
tcp::resolver resolver(env_.app().getIOContext());
|
||||
std::string port = pUrl_.port ? std::to_string(*pUrl_.port) : "443";
|
||||
tcp::resolver::iterator it = resolver.resolve(pUrl_.domain, port);
|
||||
tcp::resolver::iterator end;
|
||||
auto results = resolver.resolve(pUrl_.domain, port);
|
||||
auto it = results.begin();
|
||||
auto end = results.end();
|
||||
int n = 0;
|
||||
for (; it != end; ++it)
|
||||
++n;
|
||||
|
||||
@@ -301,8 +301,14 @@ struct DepositAuth_test : public beast::unit_test::suite
|
||||
Env env(*this, features);
|
||||
|
||||
env.fund(XRP(10000), gw1, alice, bob);
|
||||
env(trust(gw1, alice["USD"](10), noRipplePrev ? tfSetNoRipple : 0));
|
||||
env(trust(gw1, bob["USD"](10), noRippleNext ? tfSetNoRipple : 0));
|
||||
env(trust(
|
||||
gw1,
|
||||
alice["USD"](10),
|
||||
noRipplePrev ? std::uint32_t(tfSetNoRipple) : 0));
|
||||
env(trust(
|
||||
gw1,
|
||||
bob["USD"](10),
|
||||
noRippleNext ? std::uint32_t(tfSetNoRipple) : 0));
|
||||
env.trust(USD1(10), alice, bob);
|
||||
|
||||
env(pay(gw1, alice, USD1(10)));
|
||||
@@ -324,8 +330,14 @@ struct DepositAuth_test : public beast::unit_test::suite
|
||||
Env env(*this, features);
|
||||
|
||||
env.fund(XRP(10000), gw1, gw2, alice);
|
||||
env(trust(alice, USD1(10), noRipplePrev ? tfSetNoRipple : 0));
|
||||
env(trust(alice, USD2(10), noRippleNext ? tfSetNoRipple : 0));
|
||||
env(trust(
|
||||
alice,
|
||||
USD1(10),
|
||||
noRipplePrev ? std::uint32_t(tfSetNoRipple) : 0));
|
||||
env(trust(
|
||||
alice,
|
||||
USD2(10),
|
||||
noRippleNext ? std::uint32_t(tfSetNoRipple) : 0));
|
||||
env(pay(gw2, alice, USD2(10)));
|
||||
|
||||
if (withDepositAuth)
|
||||
|
||||
@@ -4722,6 +4722,359 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testDestinationCancel(FeatureBitset features)
|
||||
{
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
bool const enabled = features[featureEscrowDestinationCancel];
|
||||
TER const destResult =
|
||||
enabled ? TER{tesSUCCESS} : TER{tecNO_PERMISSION};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const carol = Account("carol");
|
||||
auto const gw = Account{"gateway"};
|
||||
auto const USD = gw["USD"];
|
||||
|
||||
auto const ownerDirEmpty = [](Env& env, Account const& acct) {
|
||||
ripple::Dir dir(*env.current(), keylet::ownerDir(acct.id()));
|
||||
return std::distance(dir.begin(), dir.end()) == 0;
|
||||
};
|
||||
|
||||
{
|
||||
testcase("Destination Cancel: before CancelAfter");
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(5000), alice, bob, carol);
|
||||
env.close();
|
||||
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow(alice, bob, XRP(1000)),
|
||||
finish_time(env.now() + 100s),
|
||||
cancel_time(env.now() + 200s));
|
||||
env.close();
|
||||
|
||||
// Neither the owner nor a third party may cancel early.
|
||||
env(cancel(alice, alice, seq), ter(tecNO_PERMISSION));
|
||||
env(cancel(carol, alice, seq), ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
STAmount const preAlice = env.balance(alice);
|
||||
env(cancel(bob, alice, seq), ter(destResult));
|
||||
env.close();
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), seq)));
|
||||
// Funds go back to the owner, not the destination.
|
||||
env.require(balance(alice, preAlice + XRP(1000)));
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
BEAST_EXPECT(ownerDirEmpty(env, alice));
|
||||
BEAST_EXPECT(ownerDirEmpty(env, bob));
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(env.le(keylet::escrow(alice.id(), seq)));
|
||||
// Rejected outright: nothing moves.
|
||||
env.require(balance(alice, preAlice));
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 1);
|
||||
BEAST_EXPECT(!ownerDirEmpty(env, bob));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Destination Cancel: after FinishAfter");
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(5000), alice, bob);
|
||||
env.close();
|
||||
|
||||
// Finishable, and with no CancelAfter, otherwise never cancellable.
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow(alice, bob, XRP(1000)), finish_time(env.now() + 1s));
|
||||
env.close(5s);
|
||||
|
||||
env(cancel(alice, alice, seq), ter(tecNO_PERMISSION));
|
||||
env(cancel(bob, alice, seq), ter(destResult));
|
||||
env.close();
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), seq)));
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(env.le(keylet::escrow(alice.id(), seq)));
|
||||
env(finish(bob, alice, seq));
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), seq)));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Destination Cancel: conditional");
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(5000), alice, bob);
|
||||
env.close();
|
||||
|
||||
// The destination does not need the fulfillment to decline.
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow(alice, bob, XRP(1000)),
|
||||
condition(cb1),
|
||||
cancel_time(env.now() + 200s));
|
||||
env.close();
|
||||
|
||||
env(cancel(bob, alice, seq), ter(destResult));
|
||||
env.close();
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), seq)));
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(env.le(keylet::escrow(alice.id(), seq)));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Destination Cancel: self-escrow stays locked");
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(5000), alice);
|
||||
env.close();
|
||||
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow(alice, alice, XRP(1000)),
|
||||
finish_time(env.now() + 100s),
|
||||
cancel_time(env.now() + 200s));
|
||||
env.close();
|
||||
|
||||
env(cancel(alice, alice, seq), ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.le(keylet::escrow(alice.id(), seq)));
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Destination Cancel: EscrowID");
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(5000), alice, bob);
|
||||
env.close();
|
||||
|
||||
uint256 const escrowId{keylet::escrow(alice, env.seq(alice)).key};
|
||||
env(escrow(alice, bob, XRP(1000)),
|
||||
finish_time(env.now() + 100s),
|
||||
cancel_time(env.now() + 200s));
|
||||
env.close();
|
||||
|
||||
env(cancel(bob, alice), escrow_id(escrowId), ter(destResult));
|
||||
env.close();
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
BEAST_EXPECT(!env.le(keylet::unchecked(escrowId)));
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(env.le(keylet::unchecked(escrowId)));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Destination Cancel: owner hook cannot block");
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(5000), alice, bob, carol);
|
||||
env.close();
|
||||
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow(alice, bob, XRP(1000)),
|
||||
finish_time(env.now() + 1s),
|
||||
cancel_time(env.now() + 2s));
|
||||
env.close();
|
||||
env.close();
|
||||
|
||||
// Past CancelAfter, so only the owner's hook stands in the way.
|
||||
env(jtx::hook(alice, {{hso(jtx::genesis::RollbackHook)}}, 0),
|
||||
fee(XRP(2)));
|
||||
env.close();
|
||||
|
||||
// The owner stays a strong tsh when a third party cancels.
|
||||
env(cancel(carol, alice, seq), fee(XRP(1)), ter(tecHOOK_REJECTED));
|
||||
env.close();
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
env(cancel(bob, alice, seq), fee(XRP(1)));
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), seq)));
|
||||
}
|
||||
else
|
||||
{
|
||||
env(cancel(bob, alice, seq),
|
||||
fee(XRP(1)),
|
||||
ter(tecHOOK_REJECTED));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.le(keylet::escrow(alice.id(), seq)));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Destination Cancel: IOU");
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(5000), alice, bob, gw);
|
||||
env.close();
|
||||
env.trust(USD(10000), alice, bob);
|
||||
env.close();
|
||||
env(pay(gw, alice, USD(5000)));
|
||||
env.close();
|
||||
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow(alice, bob, USD(1000)),
|
||||
finish_time(env.now() + 100s),
|
||||
cancel_time(env.now() + 200s));
|
||||
env.close();
|
||||
BEAST_EXPECT(-lockedAmount(env, alice, gw, USD) == USD(1000));
|
||||
|
||||
env(cancel(bob, alice, seq), ter(destResult));
|
||||
env.close();
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), seq)));
|
||||
BEAST_EXPECT(-lockedAmount(env, alice, gw, USD) == USD(0));
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(env.le(keylet::escrow(alice.id(), seq)));
|
||||
BEAST_EXPECT(-lockedAmount(env, alice, gw, USD) == USD(1000));
|
||||
}
|
||||
env.require(balance(alice, USD(5000)));
|
||||
env.require(balance(bob, USD(0)));
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Destination Cancel: issuer-owned IOU");
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(5000), alice, gw);
|
||||
env.close();
|
||||
env.trust(USD(10000), alice);
|
||||
env.close();
|
||||
|
||||
// An issuer escrowing its own IOU locks nothing, so the cancel
|
||||
// only has to unwind the escrow itself.
|
||||
auto const seq = env.seq(gw);
|
||||
env(escrow(gw, alice, USD(1000)),
|
||||
finish_time(env.now() + 100s),
|
||||
cancel_time(env.now() + 200s));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.ownerCount(gw) == 1);
|
||||
|
||||
env(cancel(gw, gw, seq), ter(tecNO_PERMISSION));
|
||||
env(cancel(alice, gw, seq), ter(destResult));
|
||||
env.close();
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
BEAST_EXPECT(!env.le(keylet::escrow(gw.id(), seq)));
|
||||
BEAST_EXPECT(env.ownerCount(gw) == 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(env.le(keylet::escrow(gw.id(), seq)));
|
||||
BEAST_EXPECT(env.ownerCount(gw) == 1);
|
||||
}
|
||||
env.require(balance(alice, USD(0)));
|
||||
}
|
||||
|
||||
{
|
||||
testcase("Destination Cancel: owner hook chain not charged");
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(5000), alice, bob, carol);
|
||||
env.close();
|
||||
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow(alice, bob, XRP(1000)),
|
||||
finish_time(env.now() + 1s),
|
||||
cancel_time(env.now() + 2s));
|
||||
env.close();
|
||||
env.close();
|
||||
|
||||
auto const feeFor = [&](Account const& acct) {
|
||||
return calculateBaseFee(
|
||||
*env.current(), *env.jt(cancel(acct, alice, seq)).stx);
|
||||
};
|
||||
|
||||
XRPAmount const bobBefore = feeFor(bob);
|
||||
XRPAmount const carolBefore = feeFor(carol);
|
||||
|
||||
env(jtx::hook(alice, {{hso(jtx::genesis::RollbackHook)}}, 0),
|
||||
fee(XRP(2)));
|
||||
env.close();
|
||||
|
||||
// A third party still pays for the owner's (strong) hook chain.
|
||||
BEAST_EXPECT(feeFor(carol) > carolBefore);
|
||||
|
||||
// The destination does too, but only without the amendment.
|
||||
if (enabled)
|
||||
{
|
||||
BEAST_EXPECT(feeFor(bob) == bobBefore);
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(feeFor(bob) > bobBefore);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// fix1571 is still a live (DefaultYes) amendment on Xahau, so the
|
||||
// legacy CancelAfter check in EscrowCancel remains reachable.
|
||||
testcase("Destination Cancel: without fix1571");
|
||||
Env env{*this, features - fix1571};
|
||||
env.fund(XRP(5000), alice, bob, carol);
|
||||
env.close();
|
||||
|
||||
// No CancelAfter.
|
||||
auto const seq1 = env.seq(alice);
|
||||
env(escrow(alice, bob, XRP(1000)), finish_time(env.now() + 100s));
|
||||
|
||||
// CancelAfter in the future.
|
||||
auto const seq2 = env.seq(alice);
|
||||
env(escrow(alice, bob, XRP(1000)),
|
||||
finish_time(env.now() + 100s),
|
||||
cancel_time(env.now() + 200s));
|
||||
|
||||
// CancelAfter about to pass.
|
||||
auto const seq3 = env.seq(alice);
|
||||
env(escrow(alice, bob, XRP(1000)),
|
||||
finish_time(env.now() + 1s),
|
||||
cancel_time(env.now() + 2s));
|
||||
env.close();
|
||||
|
||||
env(cancel(alice, alice, seq1), ter(tecNO_PERMISSION));
|
||||
env(cancel(carol, alice, seq2), ter(tecNO_PERMISSION));
|
||||
env(cancel(bob, alice, seq1), ter(destResult));
|
||||
env(cancel(bob, alice, seq2), ter(destResult));
|
||||
env.close();
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), seq1)));
|
||||
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), seq2)));
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(env.le(keylet::escrow(alice.id(), seq1)));
|
||||
BEAST_EXPECT(env.le(keylet::escrow(alice.id(), seq2)));
|
||||
}
|
||||
|
||||
// Past CancelAfter anyone may cancel, with or without the
|
||||
// amendment.
|
||||
env(cancel(carol, alice, seq3));
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(keylet::escrow(alice.id(), seq3)));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testWithFeats(FeatureBitset features)
|
||||
{
|
||||
@@ -4770,12 +5123,17 @@ public:
|
||||
{
|
||||
using namespace test::jtx;
|
||||
FeatureBitset const all{supported_amendments() | featureCredentials};
|
||||
testWithFeats(all - featurePaychanAndEscrowForTokens);
|
||||
testWithFeats(all);
|
||||
testIOUWithFeats(all - featureClawback);
|
||||
testIOUWithFeats(all);
|
||||
// The pre-existing suites assert that the destination cannot cancel
|
||||
// early, so they run without featureEscrowDestinationCancel.
|
||||
FeatureBitset const legacy{all - featureEscrowDestinationCancel};
|
||||
testWithFeats(legacy - featurePaychanAndEscrowForTokens);
|
||||
testWithFeats(legacy);
|
||||
testIOUWithFeats(legacy - featureClawback);
|
||||
testIOUWithFeats(legacy);
|
||||
testEscrowID(all);
|
||||
testCredentials(all);
|
||||
testDestinationCancel(legacy);
|
||||
testDestinationCancel(all);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+718
-355
File diff suppressed because it is too large.
Load diff
@@ -20,6 +20,7 @@
|
||||
#include <test/app/Import_json.h>
|
||||
#include <test/jtx.h>
|
||||
#include <test/jtx/AMM.h>
|
||||
#include <test/jtx/CaptureLogs.h>
|
||||
#include <xrpld/app/ledger/LedgerMaster.h>
|
||||
#include <xrpld/app/misc/AmendmentTable.h>
|
||||
#include <xrpld/app/misc/HashRouter.h>
|
||||
@@ -29,7 +30,9 @@
|
||||
#include <xrpl/json/json_reader.h>
|
||||
#include <xrpl/json/json_writer.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
#include <xrpl/protocol/Import.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#define BEAST_REQUIRE(x) \
|
||||
@@ -194,6 +197,35 @@ class Import_test : public beast::unit_test::suite
|
||||
env(import::import(alice, tmpXpop), ter(temMALFORMED));
|
||||
}
|
||||
|
||||
auto const upperHash =
|
||||
"E000CC0736630D66DAB573A2642E1BD0646DFB13A871B2CCFA6E4226F477D88C";
|
||||
auto const makeList = [](std::string const& hash) {
|
||||
Json::Value proof{Json::arrayValue};
|
||||
for (int i = 0; i < 16; ++i)
|
||||
proof.append(hash);
|
||||
return proof;
|
||||
};
|
||||
|
||||
// XPOP.transaction.proof list should be exactly 16 entries
|
||||
{
|
||||
Json::Value proof = makeList(upperHash);
|
||||
proof.append(upperHash);
|
||||
BEAST_EXPECT(syntaxCheckProof(proof, env.journal) == false);
|
||||
}
|
||||
// XPOP.transaction.proof list entry missing or wrong format
|
||||
// (should be hex string with 64 characters)
|
||||
{
|
||||
Json::Value proof = makeList(upperHash);
|
||||
proof[Json::Value::ArrayIndex{0}] = "wrong format";
|
||||
BEAST_EXPECT(syntaxCheckProof(proof, env.journal) == false);
|
||||
}
|
||||
// XPOP.transaction.proof list entry has wrong format
|
||||
{
|
||||
Json::Value proof = makeList(upperHash);
|
||||
proof[Json::Value::ArrayIndex{0}] = 1234;
|
||||
BEAST_EXPECT(syntaxCheckProof(proof, env.journal) == false);
|
||||
}
|
||||
|
||||
// // XPOP.transaction.proof list should be exactly 16 entries
|
||||
// {
|
||||
// Json::Value tmpXpop =
|
||||
@@ -334,6 +366,58 @@ class Import_test : public beast::unit_test::suite
|
||||
tmpProof[jss::children]["3"];
|
||||
BEAST_EXPECT(syntaxCheckProof(tmpProof, env.journal) == false);
|
||||
}
|
||||
// XPOP.transaction.proof tree node has wrong format
|
||||
// invalid hash (must be hex)
|
||||
{
|
||||
Json::Value tmpProof = proof;
|
||||
tmpProof[jss::children]["3"][jss::hash] =
|
||||
"ZZ126BA0486ADAE575BBC5335E42E236275A452037CA9A876D1A8CDACA1AE5"
|
||||
"42";
|
||||
BEAST_EXPECT(syntaxCheckProof(tmpProof, env.journal) == false);
|
||||
}
|
||||
// XPOP.transaction.proof tree node has wrong format
|
||||
// invalid key (must be hex)
|
||||
{
|
||||
Json::Value tmpProof = proof;
|
||||
tmpProof[jss::children]["3"][jss::key] =
|
||||
"ZZD6F6AB7D0A827DD1502B7B9571626A2B601DBE5BC786EF8ADD00E0CB7FCE"
|
||||
"B3";
|
||||
BEAST_EXPECT(syntaxCheckProof(tmpProof, env.journal) == false);
|
||||
}
|
||||
// XPOP.transaction.proof tree node has wrong format (root)
|
||||
// invalid root hash (must be hex)
|
||||
{
|
||||
Json::Value tmpProof = proof;
|
||||
tmpProof[jss::hash] =
|
||||
"ZZ9C8D073DDB5AB07FD2CD4F14467A8F3BC8FFBA16A0032D12D823D8511C12"
|
||||
"F4";
|
||||
BEAST_EXPECT(syntaxCheckProof(tmpProof, env.journal) == false);
|
||||
}
|
||||
// XPOP.transaction.proof child node was not 0-F hex nibble
|
||||
// lower case nibble
|
||||
{
|
||||
Json::Value tmpProof = proof;
|
||||
tmpProof[jss::children]["a"] = tmpProof[jss::children]["3"];
|
||||
BEAST_EXPECT(syntaxCheckProof(tmpProof, env.journal) == false);
|
||||
}
|
||||
// success: hash and key values may be upper or lower case hex. Only
|
||||
// child branch keys are constrained to upper case 0-F.
|
||||
{
|
||||
Json::Value tmpProof = proof;
|
||||
tmpProof[jss::hash] =
|
||||
"409c8d073ddb5ab07fd2cd4f14467a8f3bc8ffba16a0032d12d823d8511c12"
|
||||
"f4";
|
||||
tmpProof[jss::key] =
|
||||
"00000000000000000000000000000000000000000000000000000000000000"
|
||||
"00";
|
||||
tmpProof[jss::children]["3"][jss::hash] =
|
||||
"aa126ba0486adae575bbc5335e42e236275a452037ca9a876d1a8cdaca1ae5"
|
||||
"42";
|
||||
tmpProof[jss::children]["3"][jss::key] =
|
||||
"39d6f6ab7d0a827dd1502b7b9571626a2b601dbe5bc786ef8add00e0cb7fce"
|
||||
"b3";
|
||||
BEAST_EXPECT(syntaxCheckProof(tmpProof, env.journal) == true);
|
||||
}
|
||||
// success
|
||||
{
|
||||
BEAST_EXPECT(syntaxCheckProof(proof, env.journal) == true);
|
||||
@@ -1484,6 +1568,117 @@ class Import_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the XPOP with its blob/meta pair optionally tampered with
|
||||
// (tamperMeta bumps the metadata's TransactionIndex, so the pair no longer
|
||||
// hashes to any leaf in the proof) and, if plant is set, that pair's leaf
|
||||
// hash written over the declared hash of the proof's nibble 0 child. In
|
||||
// the w_seed fixture that child is an inner node, whose declared hash the
|
||||
// txroot computation ignores.
|
||||
static Json::Value
|
||||
spoofInnerNode(Json::Value xpop, bool tamperMeta, bool plant)
|
||||
{
|
||||
auto& txn = xpop[jss::transaction];
|
||||
auto const rawTx = *strUnHex(txn[jss::blob].asString());
|
||||
auto rawMeta = *strUnHex(txn[jss::meta].asString());
|
||||
|
||||
if (tamperMeta)
|
||||
{
|
||||
STObject meta(
|
||||
SerialIter(rawMeta.data(), rawMeta.size()), sfMetadata);
|
||||
meta.setFieldU32(
|
||||
sfTransactionIndex, meta.getFieldU32(sfTransactionIndex) + 1);
|
||||
Serializer ms;
|
||||
meta.add(ms);
|
||||
rawMeta = ms.peekData();
|
||||
txn[jss::meta] = strHex(rawMeta);
|
||||
}
|
||||
|
||||
if (plant)
|
||||
{
|
||||
// leaf hash exactly as Import::preflight computes it
|
||||
STTx const stx(SerialIter(rawTx.data(), rawTx.size()));
|
||||
Serializer s(rawTx.size() + rawMeta.size() + 40);
|
||||
s.addVL(rawTx);
|
||||
s.addVL(rawMeta);
|
||||
s.addBitString(stx.getTransactionID());
|
||||
txn[jss::proof][jss::children]["0"][jss::hash] =
|
||||
strHex(sha512Half(HashPrefix::txNode, s.slice()));
|
||||
}
|
||||
|
||||
return xpop;
|
||||
}
|
||||
|
||||
void
|
||||
testProofInnerNodeSpoof(FeatureBitset features)
|
||||
{
|
||||
testcase("import proof inner node spoof");
|
||||
|
||||
using namespace test::jtx;
|
||||
using namespace std::literals;
|
||||
|
||||
// the spoof relies on nibble 0 being an inner node in this fixture
|
||||
auto const fixture = import::loadXpop(ImportTCAccountSet::w_seed);
|
||||
auto const& nibble0 =
|
||||
fixture[jss::transaction][jss::proof][jss::children]["0"];
|
||||
BEAST_EXPECT(nibble0[jss::children].size() != 0);
|
||||
|
||||
std::string const notContained =
|
||||
"xpop proof did not contain the specified txn hash";
|
||||
|
||||
// import the (possibly spoofed) w_seed XPOP into a fresh account and
|
||||
// return the captured log
|
||||
auto const runImport =
|
||||
[&](bool tamperMeta, bool plant, TER expected) -> std::string {
|
||||
std::string logs;
|
||||
{
|
||||
test::jtx::Env env{
|
||||
*this,
|
||||
network::makeNetworkVLConfig(21337, keys),
|
||||
features,
|
||||
std::make_unique<CaptureLogs>(&logs),
|
||||
beast::severities::kWarning};
|
||||
|
||||
// burn 10'000 xrp
|
||||
auto const master = Account("masterpassphrase");
|
||||
env(noop(master), fee(10'000'000'000), ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
auto const alice = Account("alice");
|
||||
env.memoize(alice);
|
||||
|
||||
Json::Value tx = import::import(
|
||||
alice, spoofInnerNode(fixture, tamperMeta, plant));
|
||||
tx[jss::Sequence] = 0;
|
||||
tx[jss::Fee] = 0;
|
||||
env(tx, alice, ter(expected));
|
||||
env.close();
|
||||
|
||||
auto const [acct, acctSle] =
|
||||
accountKeyAndSle(*env.current(), alice);
|
||||
BEAST_EXPECT((acctSle != nullptr) == isTesSuccess(expected));
|
||||
}
|
||||
return logs;
|
||||
};
|
||||
|
||||
// control: overwriting the inner node's declared hash alone leaves the
|
||||
// computed txroot unchanged, so the genuine pair still imports
|
||||
BEAST_EXPECT(
|
||||
runImport(false, true, tesSUCCESS).find(notContained) ==
|
||||
std::string::npos);
|
||||
|
||||
// a tampered blob/meta pair is not in the proof
|
||||
BEAST_EXPECT(
|
||||
runImport(true, false, temMALFORMED).find(notContained) !=
|
||||
std::string::npos);
|
||||
|
||||
// spoof: the tampered pair's hash planted on the inner node. The
|
||||
// txroot still verifies (see control), so the containment check is
|
||||
// the only thing that can reject it
|
||||
BEAST_EXPECT(
|
||||
runImport(true, true, temMALFORMED).find(notContained) !=
|
||||
std::string::npos);
|
||||
}
|
||||
|
||||
void
|
||||
testGetVLInfo(FeatureBitset features)
|
||||
{
|
||||
@@ -1700,6 +1895,12 @@ class Import_test : public beast::unit_test::suite
|
||||
env.fund(XRP(1000), alice, bob, carol, dave);
|
||||
env.close();
|
||||
|
||||
auto const importWithUnlBlob = [&](std::string const& json) {
|
||||
Json::Value tmpXpop = import::loadXpop(ImportTCAccountSet::w_seed);
|
||||
tmpXpop[jss::validation][jss::unl][jss::blob] = base64_encode(json);
|
||||
env(import::import(alice, tmpXpop), ter(temMALFORMED));
|
||||
};
|
||||
|
||||
// temMALFORMED
|
||||
// Issuer cannot be the source account.
|
||||
{
|
||||
@@ -1713,7 +1914,7 @@ class Import_test : public beast::unit_test::suite
|
||||
{
|
||||
Json::Value tx = import::import(
|
||||
alice, import::loadXpop(ImportTCAccountSet::w_seed));
|
||||
STAmount const& fee = XRP(0);
|
||||
STAmount const fee = XRP(0);
|
||||
tx[jss::Fee] = fee.getJson(JsonOptions::none);
|
||||
env(tx, ter(telINSUF_FEE_P));
|
||||
}
|
||||
@@ -1732,7 +1933,7 @@ class Import_test : public beast::unit_test::suite
|
||||
{
|
||||
Json::Value tx = import::import(
|
||||
alice, import::loadXpop(ImportTCAccountSet::w_seed));
|
||||
STAmount const& amount = XRP(-1);
|
||||
STAmount const amount = XRP(-1);
|
||||
tx[jss::Amount] = amount.getJson(JsonOptions::none);
|
||||
env(tx, ter(temMALFORMED));
|
||||
}
|
||||
@@ -2076,6 +2277,12 @@ class Import_test : public beast::unit_test::suite
|
||||
"WFo3VFFFPSJ9XX0=";
|
||||
env(import::import(alice, tmpXpop), ter(temMALFORMED));
|
||||
}
|
||||
// temMALFORMED - Import: unl blob json (after base64 decoding) wrong
|
||||
// required field (sequence) and/or types: negative int
|
||||
{
|
||||
importWithUnlBlob(
|
||||
R"({"sequence":-1,"expiration":741398400,"validators":[]})");
|
||||
}
|
||||
// temMALFORMED - Import: unl blob json (after base64 decoding)
|
||||
// lacked required field (expiration) and/or types
|
||||
{
|
||||
@@ -2127,6 +2334,12 @@ class Import_test : public beast::unit_test::suite
|
||||
"WFo3VFFFPSJ9XX0=";
|
||||
env(import::import(alice, tmpXpop), ter(temMALFORMED));
|
||||
}
|
||||
// temMALFORMED - Import: unl blob json (after base64 decoding) wrong
|
||||
// required field (expiration) and/or types: negative int
|
||||
{
|
||||
importWithUnlBlob(
|
||||
R"({"sequence":2,"expiration":-1,"validators":[]})");
|
||||
}
|
||||
// temMALFORMED - Import: unl blob json (after base64 decoding)
|
||||
// lacked required field (effective) and/or types
|
||||
{
|
||||
@@ -2153,6 +2366,13 @@ class Import_test : public beast::unit_test::suite
|
||||
"WGJuS1dqVmhxTmFHbTU3N0s2SjY5N1haN1RRRT0ifV19";
|
||||
env(import::import(alice, tmpXpop), ter(temMALFORMED));
|
||||
}
|
||||
// temMALFORMED - Import: unl blob json (after base64 decoding) wrong
|
||||
// required field (effective) and/or types: negative int
|
||||
{
|
||||
importWithUnlBlob(
|
||||
R"({"sequence":2,"effective":-1,"expiration":741398400,)"
|
||||
R"("validators":[]})");
|
||||
}
|
||||
// temMALFORMED - Import: unl blob json (after base64 decoding)
|
||||
// lacked required field (validators) and/or types
|
||||
{
|
||||
@@ -2201,7 +2421,10 @@ class Import_test : public beast::unit_test::suite
|
||||
env(import::import(alice, tmpXpop), ter(temMALFORMED));
|
||||
}
|
||||
|
||||
// temMALFORMED - Import: unl blob expired
|
||||
// temMALFORMED - unl blob expired. The window is now checked in
|
||||
// preclaim (see testVLWindowClock). This blob's validator list
|
||||
// does not match the xpop's validations, so preflight rejects it
|
||||
// first.
|
||||
{
|
||||
Json::Value tmpXpop = import::loadXpop(ImportTCAccountSet::w_seed);
|
||||
tmpXpop[jss::validation][jss::unl][jss::blob] =
|
||||
@@ -2231,7 +2454,10 @@ class Import_test : public beast::unit_test::suite
|
||||
env(import::import(alice, tmpXpop), ter(temMALFORMED));
|
||||
}
|
||||
|
||||
// temMALFORMED - Import: unl blob not yet valid
|
||||
// temMALFORMED - unl blob not yet valid. The window is now checked in
|
||||
// preclaim (see testVLWindowClock). This blob's validator list
|
||||
// does not match the xpop's validations, so preflight rejects it
|
||||
// first.
|
||||
{
|
||||
Json::Value tmpXpop = import::loadXpop(ImportTCAccountSet::w_seed);
|
||||
tmpXpop[jss::validation][jss::unl][jss::blob] =
|
||||
@@ -6257,6 +6483,162 @@ class Import_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testGetVLWindow(FeatureBitset features)
|
||||
{
|
||||
testcase("import utils - getVLWindow");
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
beast::Journal const j{beast::Journal::getNullSink()};
|
||||
|
||||
auto const at = [](std::uint32_t s) {
|
||||
return NetClock::time_point{NetClock::duration{s}};
|
||||
};
|
||||
auto const withBlob = [](std::string const& json) {
|
||||
Json::Value xpop;
|
||||
xpop[jss::validation][jss::unl][jss::blob] = base64_encode(json);
|
||||
return xpop;
|
||||
};
|
||||
|
||||
// effective absent: valid from the epoch
|
||||
{
|
||||
auto const window =
|
||||
getVLWindow(import::loadXpop(ImportTCAccountSet::w_seed), j);
|
||||
BEAST_REQUIRE(window);
|
||||
BEAST_EXPECT(window->first == at(0));
|
||||
BEAST_EXPECT(window->second == at(757349624));
|
||||
}
|
||||
|
||||
// effective present
|
||||
{
|
||||
auto const window = getVLWindow(
|
||||
withBlob(R"({"sequence":1,"effective":3600,)"
|
||||
R"("expiration":86400,"validators":[]})"),
|
||||
j);
|
||||
BEAST_REQUIRE(window);
|
||||
BEAST_EXPECT(window->first == at(3600));
|
||||
BEAST_EXPECT(window->second == at(86400));
|
||||
}
|
||||
|
||||
// not json, not an object, or missing / ill-typed fields
|
||||
for (std::string const bad :
|
||||
{"badJson",
|
||||
"[1,2,3]",
|
||||
R"({"sequence":1})",
|
||||
R"({"expiration":"86400"})",
|
||||
R"({"expiration":-1})",
|
||||
R"({"effective":"0","expiration":86400})",
|
||||
R"({"effective":-1,"expiration":86400})"})
|
||||
BEAST_EXPECT(!getVLWindow(withBlob(bad), j));
|
||||
}
|
||||
|
||||
void
|
||||
testVLWindowClock(FeatureBitset features)
|
||||
{
|
||||
testcase("import vl window clock");
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
auto const at = [](std::uint32_t s) {
|
||||
return NetClock::time_point{NetClock::duration{s}};
|
||||
};
|
||||
|
||||
// expiration of the w_seed unl blob
|
||||
std::uint32_t const expiry = 757349624;
|
||||
auto const before = at(expiry - 1000);
|
||||
auto const after = at(expiry + 1000);
|
||||
auto const closeParentAt = [&](Env& env,
|
||||
NetClock::time_point closeTime) {
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
auto const resolution = env.closed()->info().closeTimeResolution;
|
||||
env.close(closeTime - resolution + 1s);
|
||||
};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const importTx = [&alice]() {
|
||||
Json::Value tx = import::import(
|
||||
alice, import::loadXpop(ImportTCAccountSet::w_seed));
|
||||
tx[jss::Sequence] = 0;
|
||||
tx[jss::Fee] = 0;
|
||||
return tx;
|
||||
};
|
||||
// Keep this test focused on the VL clock, not max-supply accounting.
|
||||
// w_seed creates an account and, without ZeroB2M, credits its 1000 XRP
|
||||
// inner fee as valid legacy burn-to-mint.
|
||||
auto const burnMintHeadroom = [&](Env& env) {
|
||||
auto const initCoins = env.current()->info().drops;
|
||||
auto headroom = Import::computeStartingBonus(*env.current());
|
||||
if (!env.current()->rules().enabled(featureZeroB2M))
|
||||
headroom += XRP(1000).value().xrp();
|
||||
auto const master = Account("masterpassphrase");
|
||||
env(noop(master),
|
||||
fee(static_cast<std::uint64_t>(headroom.drops())),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.current()->info().drops == initCoins - headroom);
|
||||
};
|
||||
|
||||
// parent ledger inside the window, node clock past it
|
||||
{
|
||||
Env env{*this, network::makeNetworkVLConfig(21337, keys), features};
|
||||
burnMintHeadroom(env);
|
||||
env.memoize(alice);
|
||||
env.close(before);
|
||||
BEAST_EXPECT(env.current()->parentCloseTime() < at(expiry));
|
||||
|
||||
env.timeKeeper().set(after);
|
||||
env(importTx(), alice, ter(tesSUCCESS));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.le(alice) != nullptr);
|
||||
}
|
||||
|
||||
// parent ledger past the window, node clock inside it
|
||||
{
|
||||
Env env{*this, network::makeNetworkVLConfig(21337, keys), features};
|
||||
burnMintHeadroom(env);
|
||||
env.memoize(alice);
|
||||
env.close(after);
|
||||
BEAST_EXPECT(env.current()->parentCloseTime() >= at(expiry));
|
||||
|
||||
env.timeKeeper().set(before);
|
||||
env(importTx(), alice, ter(temMALFORMED));
|
||||
BEAST_EXPECT(env.le(alice) == nullptr);
|
||||
}
|
||||
|
||||
// Env::close uses close-time resolution, so the requested expiration
|
||||
// time lands on the closest representable parent close before
|
||||
// expiration for this blob.
|
||||
{
|
||||
Env env{*this, network::makeNetworkVLConfig(21337, keys), features};
|
||||
burnMintHeadroom(env);
|
||||
env.memoize(alice);
|
||||
closeParentAt(env, at(expiry));
|
||||
BEAST_EXPECT(env.current()->parentCloseTime() < at(expiry));
|
||||
|
||||
env(importTx(), alice, ter(tesSUCCESS));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.le(alice) != nullptr);
|
||||
}
|
||||
|
||||
// accepted into the open ledger, which then closes after the window.
|
||||
// Env::close() sets the node clock to the close time before the
|
||||
// ledger is built, so this also covers the rebuild
|
||||
{
|
||||
Env env{*this, network::makeNetworkVLConfig(21337, keys), features};
|
||||
burnMintHeadroom(env);
|
||||
env.memoize(alice);
|
||||
env.close(before);
|
||||
env(importTx(), alice, ter(tesSUCCESS));
|
||||
BEAST_EXPECT(env.le(alice) != nullptr);
|
||||
|
||||
env.close(after);
|
||||
BEAST_EXPECT(env.closed()->info().parentCloseTime < at(expiry));
|
||||
BEAST_EXPECT(env.le(alice) != nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
@@ -6278,6 +6660,7 @@ public:
|
||||
testSyntaxCheckProofArray(features);
|
||||
testSyntaxCheckProofObject(features);
|
||||
testSyntaxCheckXPOP(features);
|
||||
testProofInnerNodeSpoof(features);
|
||||
testGetVLInfo(features);
|
||||
testEnabled(features);
|
||||
testInvalidPreflight(features);
|
||||
@@ -6297,6 +6680,8 @@ public:
|
||||
testMinMax(features);
|
||||
testHalving(features - featureOwnerPaysFee);
|
||||
testBlackhole(features);
|
||||
testGetVLWindow(features);
|
||||
testVLWindowClock(features);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -104,8 +104,8 @@ class LedgerMaster_test : public beast::unit_test::suite
|
||||
startLegSeq, txnIndex);
|
||||
BEAST_EXPECT(
|
||||
*result ==
|
||||
uint256("0CC11AD1AD89661689F6B6148D82CB6A7101DA4D66EC843670262D"
|
||||
"0B618F5745"));
|
||||
uint256("DCAECE52F028B9D0F7CA43CBE15AB4EF7B84A8EF5D455238992E1E"
|
||||
"DF2BDA1637"));
|
||||
}
|
||||
// success (second tx)
|
||||
{
|
||||
@@ -114,8 +114,8 @@ class LedgerMaster_test : public beast::unit_test::suite
|
||||
startLegSeq + 1, txnIndex);
|
||||
BEAST_EXPECT(
|
||||
*result ==
|
||||
uint256("DCAECE52F028B9D0F7CA43CBE15AB4EF7B84A8EF5D455238992E1E"
|
||||
"DF2BDA1637"));
|
||||
uint256("722BD5EC9E4AE36E724B6223F5A5887927AADFD0B18A3053AAE57F"
|
||||
"434F8C9FE8"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1111,7 +1111,7 @@ struct LedgerReplayer_test : public beast::unit_test::suite
|
||||
return false;
|
||||
|
||||
beast::IP::Address addr =
|
||||
boost::asio::ip::address::from_string("172.1.1.100");
|
||||
boost::asio::ip::make_address("172.1.1.100");
|
||||
jtx::Env serverEnv(*this);
|
||||
serverEnv.app().config().LEDGER_REPLAY = server;
|
||||
auto http_resp = ripple::makeResponse(
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/envconfig.h>
|
||||
#include <xrpld/app/main/LoadManager.h>
|
||||
#include <xrpld/app/misc/LoadFeeTrack.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/**
|
||||
* Unit tests for LoadFeeTrack fee adjustment mechanics.
|
||||
*
|
||||
* These tests document the expected per-second behaviour of
|
||||
* raiseLocalFee() and lowerLocalFee() as called by LoadManager::run().
|
||||
*
|
||||
* Background: prior to the fix in this PR, the fee adjustment block in
|
||||
* LoadManager::run() was placed *outside* the while loop, meaning
|
||||
* raiseLocalFee()/lowerLocalFee() fired only once on shutdown rather
|
||||
* than every second during normal operation. localTxnLoadFee_ (and thus
|
||||
* load_factor_local in server_info) was therefore never adjusted by
|
||||
* local job queue load during normal operation.
|
||||
*/
|
||||
class LoadManager_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
// The normal/minimum fee factor (kLftNormalFee in LoadFeeTrack)
|
||||
static constexpr std::uint32_t kNormalFee = 256;
|
||||
|
||||
void
|
||||
testRaiseHysteresis()
|
||||
{
|
||||
testcase("raiseLocalFee requires two consecutive calls");
|
||||
|
||||
LoadFeeTrack track;
|
||||
|
||||
// First call: raiseCount_ goes from 0 to 1, returns false (hysteresis)
|
||||
BEAST_EXPECT(!track.raiseLocalFee());
|
||||
BEAST_EXPECT(track.getLocalFee() == kNormalFee);
|
||||
|
||||
// Second call: raiseCount_ reaches 2, fee is raised
|
||||
BEAST_EXPECT(track.raiseLocalFee());
|
||||
BEAST_EXPECT(track.getLocalFee() > kNormalFee);
|
||||
}
|
||||
|
||||
void
|
||||
testLowerDecaysToBaseline()
|
||||
{
|
||||
testcase("lowerLocalFee decays elevated fee back to baseline");
|
||||
|
||||
LoadFeeTrack track;
|
||||
|
||||
// Raise the fee above baseline (requires two calls due to hysteresis)
|
||||
track.raiseLocalFee();
|
||||
track.raiseLocalFee();
|
||||
auto const elevated = track.getLocalFee();
|
||||
BEAST_EXPECT(elevated > kNormalFee);
|
||||
|
||||
// lowerLocalFee() should decay it each call
|
||||
// Each call reduces by 1/kLftFeeDecFraction (1/4), so a few
|
||||
// iterations should bring it back to kNormalFee
|
||||
bool changed = false;
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
changed |= track.lowerLocalFee();
|
||||
if (track.getLocalFee() == kNormalFee)
|
||||
break;
|
||||
}
|
||||
|
||||
BEAST_EXPECT(changed);
|
||||
BEAST_EXPECT(track.getLocalFee() == kNormalFee);
|
||||
}
|
||||
|
||||
void
|
||||
testLowerAtBaselineIsNoop()
|
||||
{
|
||||
testcase("lowerLocalFee at baseline returns false");
|
||||
|
||||
LoadFeeTrack track;
|
||||
|
||||
// Already at baseline — should return false (no change)
|
||||
BEAST_EXPECT(!track.lowerLocalFee());
|
||||
BEAST_EXPECT(track.getLocalFee() == kNormalFee);
|
||||
}
|
||||
|
||||
void
|
||||
testRaiseResetsOnLower()
|
||||
{
|
||||
testcase("lowerLocalFee resets raiseCount");
|
||||
|
||||
LoadFeeTrack track;
|
||||
|
||||
// One raise call (hysteresis: count=1, no change yet)
|
||||
BEAST_EXPECT(!track.raiseLocalFee());
|
||||
|
||||
// Lower resets raiseCount_ to 0
|
||||
track.lowerLocalFee();
|
||||
|
||||
// Next raise call starts from 0 again — still needs two calls
|
||||
BEAST_EXPECT(!track.raiseLocalFee());
|
||||
BEAST_EXPECT(track.getLocalFee() == kNormalFee);
|
||||
}
|
||||
|
||||
void
|
||||
testIsLoadedLocal()
|
||||
{
|
||||
testcase("isLoadedLocal reflects fee state correctly");
|
||||
|
||||
LoadFeeTrack track;
|
||||
|
||||
// At baseline, not loaded
|
||||
BEAST_EXPECT(!track.isLoadedLocal());
|
||||
|
||||
// After one raise (hysteresis, fee unchanged) — raiseCount_ != 0
|
||||
track.raiseLocalFee();
|
||||
BEAST_EXPECT(track.isLoadedLocal());
|
||||
|
||||
// After lower, raiseCount_ reset and fee back at baseline
|
||||
track.lowerLocalFee();
|
||||
BEAST_EXPECT(!track.isLoadedLocal());
|
||||
}
|
||||
|
||||
void
|
||||
testLoadManagerLoop()
|
||||
{
|
||||
testcase("LoadManager loop raises and decays load_factor_local");
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env(*this, envconfig());
|
||||
|
||||
// Stop the real LoadManager thread so we can drive the fee track
|
||||
// manually without races. On stock code the fee adjustment block
|
||||
// is outside the while loop and never fires during normal operation,
|
||||
// so getFeeTrack().getLocalFee() stays at kNormalFee (256) regardless.
|
||||
env.app().getLoadManager().stop();
|
||||
|
||||
auto& feeTrack = env.app().getFeeTrack();
|
||||
|
||||
// Confirm baseline
|
||||
BEAST_EXPECT(feeTrack.getLocalFee() == kNormalFee);
|
||||
BEAST_EXPECT(!feeTrack.isLoadedLocal());
|
||||
|
||||
// raiseLocalFee() requires two consecutive calls (hysteresis guard).
|
||||
// This is the exact sequence LoadManager::run() executes each tick
|
||||
// when the job queue is overloaded.
|
||||
BEAST_EXPECT(!feeTrack.raiseLocalFee()); // tick 1: count=1, no change
|
||||
BEAST_EXPECT(feeTrack.raiseLocalFee()); // tick 2: count=2, fee raised
|
||||
BEAST_EXPECT(feeTrack.getLocalFee() > kNormalFee);
|
||||
BEAST_EXPECT(feeTrack.isLoadedLocal());
|
||||
|
||||
// Now simulate the LoadManager loop calling lowerLocalFee() each tick
|
||||
// until the fee decays back to baseline. On stock code this block
|
||||
// never runs during normal operation so the fee would stay elevated.
|
||||
bool decayed = false;
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
feeTrack.lowerLocalFee();
|
||||
if (feeTrack.getLocalFee() == kNormalFee)
|
||||
{
|
||||
decayed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BEAST_EXPECT(decayed);
|
||||
BEAST_EXPECT(feeTrack.getLocalFee() == kNormalFee);
|
||||
BEAST_EXPECT(!feeTrack.isLoadedLocal());
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testRaiseHysteresis();
|
||||
testLowerDecaysToBaseline();
|
||||
testLowerAtBaselineIsNoop();
|
||||
testRaiseResetsOnLower();
|
||||
testIsLoadedLocal();
|
||||
testLoadManagerLoop();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(LoadManager, app, ripple);
|
||||
|
||||
} // namespace ripple
|
||||
@@ -1174,11 +1174,11 @@ class MPToken_test : public beast::unit_test::suite
|
||||
JsonOptions::none)[sfAffectedNodes.fieldName];
|
||||
// Issuer got 10 in the transfer fees
|
||||
BEAST_EXPECT(
|
||||
meta[2u][sfModifiedNode.fieldName][sfFinalFields.fieldName]
|
||||
meta[0u][sfModifiedNode.fieldName][sfFinalFields.fieldName]
|
||||
[sfOutstandingAmount.fieldName] == "9990");
|
||||
// Destination account got 9'990
|
||||
BEAST_EXPECT(
|
||||
meta[0u][sfModifiedNode.fieldName][sfFinalFields.fieldName]
|
||||
meta[3u][sfModifiedNode.fieldName][sfFinalFields.fieldName]
|
||||
[sfMPTAmount.fieldName] == "9990");
|
||||
// Source account spent 10'000
|
||||
BEAST_EXPECT(
|
||||
@@ -1910,8 +1910,8 @@ class MPToken_test : public beast::unit_test::suite
|
||||
env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
|
||||
BEAST_EXPECTS(
|
||||
txHash ==
|
||||
"42175D43E3D236A86B0F1B07182A1B2C78AD382372CFA09A48239C395FFFEE"
|
||||
"30",
|
||||
"61041108F0DAD50BAFD2F7B1102AC70B283EBDCED194CEBAF04D0184E4A70B"
|
||||
"02",
|
||||
txHash);
|
||||
Json::Value const meta = env.rpc("tx", txHash)[jss::result][jss::meta];
|
||||
auto const id = meta[jss::mpt_issuance_id].asString();
|
||||
@@ -1919,7 +1919,7 @@ class MPToken_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(meta.isMember(jss::mpt_issuance_id));
|
||||
BEAST_EXPECT(id == to_string(mptAlice.issuanceID()));
|
||||
BEAST_EXPECTS(
|
||||
id == "00000001AE123A8556F3CF91154711376AFB0F894F832B3D", id);
|
||||
id == "00000002AE123A8556F3CF91154711376AFB0F894F832B3D", id);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -24,6 +24,7 @@
|
||||
#include <xrpld/app/rdb/Wallet.h>
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/basics/base64.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/protocol/STExchange.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
@@ -975,9 +976,482 @@ public:
|
||||
".example.com"));
|
||||
}
|
||||
|
||||
struct Validator
|
||||
{
|
||||
SecretKey masterSecret;
|
||||
PublicKey master;
|
||||
SecretKey signingSecret;
|
||||
PublicKey signing;
|
||||
};
|
||||
|
||||
static Validator
|
||||
makeValidator()
|
||||
{
|
||||
auto const ms = randomSecretKey();
|
||||
auto const ss = randomSecretKey();
|
||||
return {
|
||||
ms,
|
||||
derivePublicKey(KeyType::ed25519, ms),
|
||||
ss,
|
||||
derivePublicKey(KeyType::secp256k1, ss)};
|
||||
}
|
||||
|
||||
/** The same master key with a fresh ephemeral key. */
|
||||
static Validator
|
||||
rotate(Validator const& v)
|
||||
{
|
||||
auto const ss = randomSecretKey();
|
||||
return {
|
||||
v.masterSecret,
|
||||
v.master,
|
||||
ss,
|
||||
derivePublicKey(KeyType::secp256k1, ss)};
|
||||
}
|
||||
|
||||
/** Another master key claiming the same ephemeral key. */
|
||||
static Validator
|
||||
impostor(Validator const& v)
|
||||
{
|
||||
auto const ms = randomSecretKey();
|
||||
return {
|
||||
ms,
|
||||
derivePublicKey(KeyType::ed25519, ms),
|
||||
v.signingSecret,
|
||||
v.signing};
|
||||
}
|
||||
|
||||
Manifest
|
||||
manifestFor(Validator const& v, int seq, bool invalidSig = false)
|
||||
{
|
||||
return makeManifest(
|
||||
v.masterSecret,
|
||||
KeyType::ed25519,
|
||||
v.signingSecret,
|
||||
KeyType::secp256k1,
|
||||
seq,
|
||||
invalidSig);
|
||||
}
|
||||
|
||||
static std::size_t
|
||||
offered(ManifestCache const& cache)
|
||||
{
|
||||
std::size_t n = 0;
|
||||
cache.for_each_gossip_manifest(
|
||||
[](std::size_t) {}, [&n](Manifest const&) { ++n; });
|
||||
return n;
|
||||
}
|
||||
|
||||
void
|
||||
testGossipAdmission()
|
||||
{
|
||||
testcase("gossip admission");
|
||||
|
||||
using D = ManifestDisposition;
|
||||
auto const gossip = ManifestSource::gossip;
|
||||
|
||||
TestStopwatch clock;
|
||||
ManifestCache cache{
|
||||
beast::Journal{beast::Journal::getNullSink()}, clock};
|
||||
|
||||
// Gossip for a master key not held, before its ephemeral key has
|
||||
// signed anything: held back, not admitted.
|
||||
auto const a = makeValidator();
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(a, 1), gossip) == D::unseen);
|
||||
BEAST_EXPECT(cache.getMasterKey(a.signing) == a.signing);
|
||||
BEAST_EXPECT(!cache.getTier(a.master));
|
||||
|
||||
// Its first validation releases it, once.
|
||||
auto const seq = cache.sequence();
|
||||
auto const released = cache.noteValidation(a.signing);
|
||||
BEAST_EXPECT(released && released->masterKey == a.master);
|
||||
BEAST_EXPECT(cache.getMasterKey(a.signing) == a.master);
|
||||
BEAST_EXPECT(cache.getTier(a.master) == gossip);
|
||||
BEAST_EXPECT(cache.sequence() > seq);
|
||||
BEAST_EXPECT(!cache.noteValidation(a.signing));
|
||||
|
||||
// Seen first, then gossiped: admitted at once.
|
||||
auto const b = makeValidator();
|
||||
BEAST_EXPECT(!cache.noteValidation(b.signing));
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(b, 1), gossip) == D::accepted);
|
||||
|
||||
// A master key held from gossip rotates without its new key having
|
||||
// been seen...
|
||||
auto const b2 = rotate(b);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(b2, 2), gossip) == D::accepted);
|
||||
BEAST_EXPECT(cache.getMasterKey(b2.signing) == b.master);
|
||||
BEAST_EXPECT(cache.getMasterKey(b.signing) == b.signing);
|
||||
|
||||
// ...and is revoked from gossip too, since that only takes trust away.
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(
|
||||
makeRevocation(b.masterSecret, KeyType::ed25519), gossip) ==
|
||||
D::accepted);
|
||||
BEAST_EXPECT(cache.revoked(b.master));
|
||||
BEAST_EXPECT(cache.getMasterKey(b2.signing) == b2.signing);
|
||||
BEAST_EXPECT(cache.getTier(b.master) == gossip);
|
||||
|
||||
// A revocation for a master key not held names no ephemeral key that
|
||||
// could have been seen, so gossip cannot introduce one...
|
||||
auto const c = makeValidator();
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(
|
||||
makeRevocation(c.masterSecret, KeyType::ed25519), gossip) ==
|
||||
D::unseen);
|
||||
BEAST_EXPECT(!cache.getTier(c.master));
|
||||
|
||||
// ...but a list can.
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(makeRevocation(
|
||||
c.masterSecret, KeyType::ed25519)) == D::accepted);
|
||||
BEAST_EXPECT(cache.getTier(c.master) == ManifestSource::list);
|
||||
BEAST_EXPECT(cache.revoked(c.master));
|
||||
|
||||
// A forgery waits unverified, and is dropped when released.
|
||||
auto const d = makeValidator();
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(d, 1, true), gossip) == D::unseen);
|
||||
BEAST_EXPECT(!cache.noteValidation(d.signing));
|
||||
BEAST_EXPECT(!cache.getTier(d.master));
|
||||
|
||||
// A key is remembered for three to four epochs of the filter.
|
||||
auto const e = makeValidator();
|
||||
cache.noteValidation(e.signing);
|
||||
clock.advance(ManifestCache::seenInterval * 3);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(e, 1), gossip) == D::accepted);
|
||||
|
||||
auto const f = makeValidator();
|
||||
cache.noteValidation(f.signing);
|
||||
clock.advance(ManifestCache::seenInterval * 4);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(f, 1), gossip) == D::unseen);
|
||||
}
|
||||
|
||||
void
|
||||
testPendingSubstitution()
|
||||
{
|
||||
testcase("pending ring: forged replacement");
|
||||
|
||||
using D = ManifestDisposition;
|
||||
auto const gossip = ManifestSource::gossip;
|
||||
|
||||
// Same master and signing key, higher sequence, bad master signature.
|
||||
{
|
||||
TestStopwatch clock;
|
||||
ManifestCache cache{
|
||||
beast::Journal{beast::Journal::getNullSink()}, clock};
|
||||
auto const v = makeValidator();
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(v, 1), gossip) == D::unseen);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(v, 2, true), gossip) ==
|
||||
D::unseen);
|
||||
auto const released = cache.noteValidation(v.signing);
|
||||
BEAST_EXPECT(released && released->sequence == 1);
|
||||
BEAST_EXPECT(cache.getMasterKey(v.signing) == v.master);
|
||||
}
|
||||
|
||||
// Different master claiming the same signing key, bad master signature.
|
||||
{
|
||||
TestStopwatch clock;
|
||||
ManifestCache cache{
|
||||
beast::Journal{beast::Journal::getNullSink()}, clock};
|
||||
auto const v = makeValidator();
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(v, 1), gossip) == D::unseen);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(
|
||||
manifestFor(impostor(v), 2, true), gossip) == D::unseen);
|
||||
auto const released = cache.noteValidation(v.signing);
|
||||
BEAST_EXPECT(released && released->masterKey == v.master);
|
||||
BEAST_EXPECT(cache.getMasterKey(v.signing) == v.master);
|
||||
}
|
||||
|
||||
// A forgery that arrives first, for the same master key with a
|
||||
// higher sequence, does not keep out the genuine manifest.
|
||||
{
|
||||
TestStopwatch clock;
|
||||
ManifestCache cache{
|
||||
beast::Journal{beast::Journal::getNullSink()}, clock};
|
||||
auto const v = makeValidator();
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(v, 2, true), gossip) ==
|
||||
D::unseen);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(v, 1), gossip) == D::unseen);
|
||||
auto const released = cache.noteValidation(v.signing);
|
||||
BEAST_EXPECT(released && released->sequence == 1);
|
||||
BEAST_EXPECT(cache.getMasterKey(v.signing) == v.master);
|
||||
}
|
||||
|
||||
// Nor does one that arrives first for another master key.
|
||||
{
|
||||
TestStopwatch clock;
|
||||
ManifestCache cache{
|
||||
beast::Journal{beast::Journal::getNullSink()}, clock};
|
||||
auto const v = makeValidator();
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(
|
||||
manifestFor(impostor(v), 2, true), gossip) == D::unseen);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(v, 1), gossip) == D::unseen);
|
||||
auto const released = cache.noteValidation(v.signing);
|
||||
BEAST_EXPECT(released && released->masterKey == v.master);
|
||||
BEAST_EXPECT(cache.getMasterKey(v.signing) == v.master);
|
||||
}
|
||||
|
||||
// A genuine manifest waiting is not displaced by an older genuine one.
|
||||
{
|
||||
TestStopwatch clock;
|
||||
ManifestCache cache{
|
||||
beast::Journal{beast::Journal::getNullSink()}, clock};
|
||||
auto const v = makeValidator();
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(v, 2), gossip) == D::unseen);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(v, 1), gossip) == D::unseen);
|
||||
auto const released = cache.noteValidation(v.signing);
|
||||
BEAST_EXPECT(released && released->sequence == 2);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testGossipCapacity()
|
||||
{
|
||||
testcase("gossip capacity and decay");
|
||||
|
||||
using D = ManifestDisposition;
|
||||
auto const gossip = ManifestSource::gossip;
|
||||
|
||||
TestStopwatch clock;
|
||||
ManifestCache cache{
|
||||
beast::Journal{beast::Journal::getNullSink()}, clock};
|
||||
|
||||
std::vector<Validator> held;
|
||||
for (std::size_t i = 0; i < ManifestCache::gossipCapacity; ++i)
|
||||
{
|
||||
held.push_back(makeValidator());
|
||||
cache.noteValidation(held.back().signing);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(held.back(), 1), gossip) ==
|
||||
D::accepted);
|
||||
}
|
||||
BEAST_EXPECT(offered(cache) == ManifestCache::gossipCapacity);
|
||||
|
||||
// Every entry is exactly as active as a newcomer: none gives way.
|
||||
auto const newcomer = makeValidator();
|
||||
cache.noteValidation(newcomer.signing);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(newcomer, 1), gossip) == D::full);
|
||||
|
||||
// Four half-lives on, all but the first have gone on validating. The
|
||||
// first has decayed to a sixteenth of a sighting, the rest stand at
|
||||
// seventeen sixteenths, and a newcomer is worth a whole one: it
|
||||
// displaces the first and nothing else.
|
||||
clock.advance(ManifestCache::halfLife * 4);
|
||||
for (std::size_t i = 1; i < held.size(); ++i)
|
||||
cache.noteValidation(held[i].signing);
|
||||
|
||||
auto const seq = cache.sequence();
|
||||
cache.noteValidation(newcomer.signing);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(newcomer, 1), gossip) ==
|
||||
D::accepted);
|
||||
BEAST_EXPECT(cache.sequence() > seq);
|
||||
BEAST_EXPECT(!cache.getTier(held[0].master));
|
||||
BEAST_EXPECT(cache.getMasterKey(held[0].signing) == held[0].signing);
|
||||
for (std::size_t i = 1; i < held.size(); ++i)
|
||||
BEAST_EXPECT(cache.getTier(held[i].master) == gossip);
|
||||
BEAST_EXPECT(offered(cache) == ManifestCache::gossipCapacity);
|
||||
|
||||
// The newcomer is now the least active, but no less active than
|
||||
// another newcomer, which therefore finds no room.
|
||||
auto const late = makeValidator();
|
||||
cache.noteValidation(late.signing);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(late, 1), gossip) == D::full);
|
||||
|
||||
// Far enough on that the weights are rebased. Their order survives:
|
||||
// the one entry that stopped validating is the one displaced.
|
||||
clock.advance(ManifestCache::halfLife * 300);
|
||||
for (std::size_t i = 2; i < held.size(); ++i)
|
||||
cache.noteValidation(held[i].signing);
|
||||
cache.noteValidation(newcomer.signing);
|
||||
cache.noteValidation(late.signing);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(late, 1), gossip) == D::accepted);
|
||||
BEAST_EXPECT(!cache.getTier(held[1].master));
|
||||
BEAST_EXPECT(cache.getTier(newcomer.master) == gossip);
|
||||
for (std::size_t i = 2; i < held.size(); ++i)
|
||||
BEAST_EXPECT(cache.getTier(held[i].master) == gossip);
|
||||
}
|
||||
|
||||
void
|
||||
testPinning()
|
||||
{
|
||||
testcase("pinning");
|
||||
|
||||
using D = ManifestDisposition;
|
||||
auto const gossip = ManifestSource::gossip;
|
||||
|
||||
TestStopwatch clock;
|
||||
ManifestCache cache{
|
||||
beast::Journal{beast::Journal::getNullSink()}, clock};
|
||||
|
||||
std::vector<Validator> held;
|
||||
for (std::size_t i = 0; i < ManifestCache::gossipCapacity; ++i)
|
||||
{
|
||||
held.push_back(makeValidator());
|
||||
cache.noteValidation(held.back().signing);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(held.back(), 1), gossip) ==
|
||||
D::accepted);
|
||||
}
|
||||
|
||||
// Pinned, a gossip entry is retained as list and frees its slot.
|
||||
auto const seq = cache.sequence();
|
||||
cache.pin({held[0].master});
|
||||
BEAST_EXPECT(cache.sequence() > seq);
|
||||
BEAST_EXPECT(cache.getTier(held[0].master) == ManifestSource::list);
|
||||
|
||||
auto const newcomer = makeValidator();
|
||||
cache.noteValidation(newcomer.signing);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(newcomer, 1), gossip) ==
|
||||
D::accepted);
|
||||
for (auto const& v : held)
|
||||
BEAST_EXPECT(cache.getTier(v.master));
|
||||
|
||||
// Unpinned, it falls back into a full tier, which sheds its least
|
||||
// active entry: one of those that stopped validating.
|
||||
clock.advance(ManifestCache::halfLife);
|
||||
cache.noteValidation(held[0].signing);
|
||||
cache.noteValidation(newcomer.signing);
|
||||
cache.pin({});
|
||||
BEAST_EXPECT(cache.getTier(held[0].master) == gossip);
|
||||
BEAST_EXPECT(cache.getTier(newcomer.master) == gossip);
|
||||
std::size_t remaining = 0;
|
||||
for (std::size_t i = 1; i < held.size(); ++i)
|
||||
remaining += cache.getTier(held[i].master) ? 1 : 0;
|
||||
BEAST_EXPECT(remaining == held.size() - 2);
|
||||
BEAST_EXPECT(offered(cache) == ManifestCache::gossipCapacity);
|
||||
|
||||
// A master key a list vouched for stays in the list tier however it
|
||||
// is updated later, and is offered alongside gossip.
|
||||
auto const listed = makeValidator();
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(listed, 1)) == D::accepted);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(rotate(listed), 2), gossip) ==
|
||||
D::accepted);
|
||||
BEAST_EXPECT(cache.getTier(listed.master) == ManifestSource::list);
|
||||
BEAST_EXPECT(offered(cache) == ManifestCache::gossipCapacity + 1);
|
||||
}
|
||||
|
||||
void
|
||||
testPrecedence()
|
||||
{
|
||||
testcase("tier precedence on key conflicts");
|
||||
|
||||
using D = ManifestDisposition;
|
||||
auto const gossip = ManifestSource::gossip;
|
||||
|
||||
TestStopwatch clock;
|
||||
ManifestCache cache{
|
||||
beast::Journal{beast::Journal::getNullSink()}, clock};
|
||||
|
||||
// Gossip binds an ephemeral key to a master key...
|
||||
auto const a = makeValidator();
|
||||
cache.noteValidation(a.signing);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(a, 1), gossip) == D::accepted);
|
||||
|
||||
// ...and a list binding the same key to another master key wins,
|
||||
// evicting it, where before it would have been refused.
|
||||
auto const b = impostor(a);
|
||||
BEAST_EXPECT(cache.applyManifest(manifestFor(b, 1)) == D::accepted);
|
||||
BEAST_EXPECT(cache.getMasterKey(a.signing) == b.master);
|
||||
BEAST_EXPECT(!cache.getTier(a.master));
|
||||
|
||||
// Gossip cannot do the same to the list...
|
||||
auto const c = impostor(a);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(c, 1), gossip) ==
|
||||
D::badEphemeralKey);
|
||||
BEAST_EXPECT(cache.getMasterKey(a.signing) == b.master);
|
||||
|
||||
// ...and between equals the first holds, as it always has.
|
||||
auto const d = impostor(a);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(d, 1)) == D::badEphemeralKey);
|
||||
BEAST_EXPECT(cache.getMasterKey(a.signing) == b.master);
|
||||
}
|
||||
|
||||
void
|
||||
testPending()
|
||||
{
|
||||
testcase("pending");
|
||||
|
||||
using D = ManifestDisposition;
|
||||
auto const gossip = ManifestSource::gossip;
|
||||
|
||||
{
|
||||
// A newer manifest for a key already waiting replaces it.
|
||||
TestStopwatch clock;
|
||||
ManifestCache cache{
|
||||
beast::Journal{beast::Journal::getNullSink()}, clock};
|
||||
|
||||
auto const v = makeValidator();
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(v, 2), gossip) == D::unseen);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(v, 1), gossip) == D::unseen);
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(manifestFor(v, 3), gossip) == D::unseen);
|
||||
auto const released = cache.noteValidation(v.signing);
|
||||
BEAST_EXPECT(released && released->sequence == 3);
|
||||
BEAST_EXPECT(cache.getSequence(v.master) == 3);
|
||||
}
|
||||
|
||||
{
|
||||
// The ring holds pendingCapacity; the first to arrive makes way
|
||||
// for the one after.
|
||||
TestStopwatch clock;
|
||||
ManifestCache cache{
|
||||
beast::Journal{beast::Journal::getNullSink()}, clock};
|
||||
|
||||
std::vector<Validator> waiting;
|
||||
for (std::size_t i = 0; i <= ManifestCache::pendingCapacity; ++i)
|
||||
{
|
||||
waiting.push_back(makeValidator());
|
||||
BEAST_EXPECT(
|
||||
cache.applyManifest(
|
||||
manifestFor(waiting.back(), 1), gossip) == D::unseen);
|
||||
}
|
||||
|
||||
BEAST_EXPECT(!cache.noteValidation(waiting.front().signing));
|
||||
BEAST_EXPECT(!cache.getTier(waiting.front().master));
|
||||
|
||||
std::size_t released = 0;
|
||||
for (std::size_t i = 1; i < waiting.size(); ++i)
|
||||
released += cache.noteValidation(waiting[i].signing) ? 1 : 0;
|
||||
BEAST_EXPECT(released == ManifestCache::pendingCapacity);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testGossipAdmission();
|
||||
testGossipCapacity();
|
||||
testPendingSubstitution();
|
||||
testPinning();
|
||||
testPrecedence();
|
||||
testPending();
|
||||
|
||||
ManifestCache cache;
|
||||
{
|
||||
testcase("apply");
|
||||
|
||||
@@ -5104,6 +5104,36 @@ public:
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
// OfferID is only valid when Hooks are enabled.
|
||||
{
|
||||
Env env{*this, features - featureHooks};
|
||||
auto const gw = Account{"gateway"};
|
||||
auto const alice = Account{"alice"};
|
||||
auto const USD = gw["USD"];
|
||||
|
||||
env.fund(XRP(10000), gw, alice);
|
||||
env.close();
|
||||
|
||||
env(trust(alice, USD(1000)));
|
||||
env.close();
|
||||
|
||||
env(pay(gw, alice, USD(200)));
|
||||
env.close();
|
||||
|
||||
uint256 const offerId{getOfferIndex(alice, env.seq(alice))};
|
||||
env(offer(alice, XRP(50), USD(50)));
|
||||
env.close();
|
||||
|
||||
uint256 const newOfferId{getOfferIndex(alice, env.seq(alice))};
|
||||
env(offer(alice, XRP(50), USD(50)),
|
||||
offer_id(offerId),
|
||||
ter(temDISABLED));
|
||||
env(offer_cancel(alice), offer_id(offerId), ter(temDISABLED));
|
||||
|
||||
BEAST_EXPECT(env.le(keylet::unchecked(offerId)));
|
||||
BEAST_EXPECT(!env.le(keylet::unchecked(newOfferId)));
|
||||
}
|
||||
|
||||
// OfferCreate
|
||||
{
|
||||
Env env{*this, features};
|
||||
@@ -5481,6 +5511,71 @@ public:
|
||||
env.balance(taker, XRP) == takerXRPBalance);
|
||||
}
|
||||
|
||||
void
|
||||
testFixCancelOfferExploit(FeatureBitset features)
|
||||
{
|
||||
testcase("fixCancelOfferExploit");
|
||||
using namespace jtx;
|
||||
Account const alice("alice");
|
||||
Account const bob("bob");
|
||||
Account const charlie("charlie");
|
||||
auto const USD = alice["USD"];
|
||||
auto const bobUSD = bob["USD"];
|
||||
|
||||
Env env(*this, features);
|
||||
|
||||
env.fund(XRP(1000), alice, bob);
|
||||
env.close();
|
||||
|
||||
// OfferCancel: not ltOffer, exist account(alice)
|
||||
uint256 const nonOfferId{keylet::account(alice.id()).key};
|
||||
env(offer_cancel(bob), offer_id(nonOfferId), ter(tecNO_PERMISSION));
|
||||
BEAST_EXPECT(env.le(keylet::unchecked(nonOfferId)));
|
||||
|
||||
// OfferCreate: not ltOffer
|
||||
env(offer(bob, XRP(100), bobUSD(100)),
|
||||
offer_id(nonOfferId),
|
||||
ter(tecNO_PERMISSION));
|
||||
BEAST_EXPECT(env.le(keylet::unchecked(nonOfferId)));
|
||||
|
||||
// OfferCancel: not ltOffer, not exist account(charlie)
|
||||
uint256 const nonExistId{keylet::account(charlie.id()).key};
|
||||
env(offer_cancel(bob), offer_id(nonExistId), ter(tesSUCCESS));
|
||||
BEAST_EXPECT(!env.le(keylet::unchecked(nonExistId)));
|
||||
|
||||
// OfferCreate: not ltOffer, not exist account(charlie)
|
||||
env(offer(bob, XRP(100), bobUSD(100)),
|
||||
offer_id(nonExistId),
|
||||
ter(tesSUCCESS));
|
||||
BEAST_EXPECT(!env.le(keylet::unchecked(nonExistId)));
|
||||
|
||||
// create alice offer
|
||||
uint256 const aliceOfferId{getOfferIndex(alice, env.seq(alice))};
|
||||
env(offer(alice, XRP(100), USD(100)));
|
||||
env.close();
|
||||
|
||||
// OfferCancel: bob cannot cancel alice's offer
|
||||
env(offer_cancel(bob), offer_id(aliceOfferId), ter(tecNO_PERMISSION));
|
||||
BEAST_EXPECT(env.le(keylet::unchecked(aliceOfferId)));
|
||||
|
||||
// OfferCreates: bob cannot cancel alice's offer
|
||||
env(offer(bob, XRP(100), bobUSD(100)),
|
||||
offer_id(aliceOfferId),
|
||||
ter(tecNO_PERMISSION));
|
||||
BEAST_EXPECT(env.le(keylet::unchecked(aliceOfferId)));
|
||||
|
||||
// An expired OfferCreate still cannot cancel another account's Offer.
|
||||
uint256 const expiredBobOfferId{getOfferIndex(bob, env.seq(bob))};
|
||||
env(offer(bob, XRP(100), bobUSD(100)),
|
||||
offer_id(aliceOfferId),
|
||||
json(sfExpiration.fieldName, lastClose(env)),
|
||||
ter(tecNO_PERMISSION));
|
||||
BEAST_EXPECT(env.le(keylet::unchecked(aliceOfferId)));
|
||||
BEAST_EXPECT(!env.le(keylet::unchecked(expiredBobOfferId)));
|
||||
|
||||
env(offer_cancel(alice), offer_id(aliceOfferId), ter(tesSUCCESS));
|
||||
}
|
||||
|
||||
void
|
||||
testAll(FeatureBitset features)
|
||||
{
|
||||
@@ -5544,6 +5639,7 @@ public:
|
||||
testRmSmallIncreasedQOffersIOU(features);
|
||||
testOfferID(features);
|
||||
testFillOrKill(features);
|
||||
testFixCancelOfferExploit(features);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -0,0 +1,400 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 XRPL Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <test/jtx.h>
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/protocol/AppLoader.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
class PWALoader_test : public beast::unit_test::suite
|
||||
{
|
||||
static appLoader::Result
|
||||
check(std::string const& s)
|
||||
{
|
||||
return appLoader::validate(
|
||||
reinterpret_cast<std::uint8_t const*>(s.data()), s.size());
|
||||
}
|
||||
|
||||
static std::string
|
||||
docOfSize(std::size_t len)
|
||||
{
|
||||
std::string const head = "<!DOCTYPE html><html><body>";
|
||||
std::string const tail = "</body></html>";
|
||||
if (len < head.size() + tail.size())
|
||||
return head + tail;
|
||||
return head + std::string(len - head.size() - tail.size(), 'x') + tail;
|
||||
}
|
||||
|
||||
static std::string
|
||||
doc()
|
||||
{
|
||||
return "<!DOCTYPE html><html><body>hi</body></html>";
|
||||
}
|
||||
|
||||
// AccountSet carrying sfAppLoader.
|
||||
static Json::Value
|
||||
set(jtx::Account const& a, std::string const& d)
|
||||
{
|
||||
auto jt = jtx::noop(a);
|
||||
jt[sfAppLoader.fieldName] = strHex(d);
|
||||
return jt;
|
||||
}
|
||||
|
||||
// validate() is consensus critical: these pin its edges, so a refactor
|
||||
// that moves any of them fails here rather than forking the network.
|
||||
void
|
||||
testValidator()
|
||||
{
|
||||
testcase("validator");
|
||||
using R = appLoader::Result;
|
||||
using namespace std::string_literals;
|
||||
|
||||
for (auto const& s :
|
||||
{"<!DOCTYPE html><html></html>"s,
|
||||
"<!doctype html><html></html>"s,
|
||||
"<html></html>"s,
|
||||
"<HTML></HTML>"s,
|
||||
"<html lang=\"en\" class=\"root\"></html>"s,
|
||||
" \n\t<html></html>\n\n "s,
|
||||
"<html></html >"s,
|
||||
"<html></html\n>"s,
|
||||
"\xEF\xBB\xBF<html></html>"s,
|
||||
"\xEF\xBB\xBF<!DOCTYPE html><html></html>"s,
|
||||
"<html><body>\xE6\xBC\xA2\xE5\xAD\x97</body></html>"s,
|
||||
"<html><script>var s=\"</html>\";</script></html>"s,
|
||||
"<html>\t\n\r\f</html>"s,
|
||||
"<html></html> \n"s,
|
||||
"<!DOCTYPE html>\n<!-- c --><html></html>"s,
|
||||
"<html>\xC2\x85</html>"s, // C1 controls are not rejected
|
||||
docOfSize(maxAppLoaderLength)})
|
||||
BEAST_EXPECTS(check(s) == R::ok, s);
|
||||
|
||||
std::vector<std::pair<std::string, R>> const bad{
|
||||
{""s, R::empty},
|
||||
{" \n"s, R::empty},
|
||||
{"\xEF\xBB\xBF"s, R::empty},
|
||||
{docOfSize(maxAppLoaderLength + 1), R::tooLarge},
|
||||
{"<html>\xC0\xAF</html>"s, R::badUTF8},
|
||||
{"<html>\xED\xA0\x80</html>"s, R::badUTF8},
|
||||
{"<html>\xEF\xBF\xBE</html>"s, R::badUTF8},
|
||||
{"<html>\xE6\xBC</html>"s, R::badUTF8},
|
||||
{"<html></html>\xE6"s, R::badUTF8},
|
||||
{"<html>\0</html>"s, R::badControlChar},
|
||||
{"<html>\x1B</html>"s, R::badControlChar},
|
||||
{"<html>\x7F</html>"s, R::badControlChar},
|
||||
{"hello"s, R::noDoctype},
|
||||
{"<div>frag</div>"s, R::noDoctype},
|
||||
{"<htmlish></html>"s, R::noDoctype},
|
||||
{"<!DOCTYPEhtml><html></html>"s, R::noDoctype},
|
||||
{"<!DOCTYPE"s, R::noDoctype},
|
||||
{"<!DOCTYPE html>"s, R::noHtmlElement},
|
||||
{"<!DOCTYPE html><htmlish></html>"s, R::noHtmlElement},
|
||||
{"<html>"s, R::unclosed},
|
||||
{"<html></html"s, R::unclosed},
|
||||
{"<html></htmlx>"s, R::unclosed},
|
||||
{"<html></html>trailing"s, R::trailingGarbage},
|
||||
{"<html></html><p>"s, R::trailingGarbage},
|
||||
{"<html></html>\xC2\xA0"s, R::trailingGarbage}, // NBSP
|
||||
};
|
||||
for (auto const& [s, r] : bad)
|
||||
BEAST_EXPECTS(
|
||||
check(s) == r, s + ": " + appLoader::to_string(check(s)));
|
||||
}
|
||||
|
||||
void
|
||||
testEnabled()
|
||||
{
|
||||
testcase("amendment gate");
|
||||
using namespace jtx;
|
||||
Env env{*this, supported_amendments() - featurePWALoader};
|
||||
Account const alice{"alice"};
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
env(set(alice, doc()), fee(XRP(1)), ter(temDISABLED));
|
||||
env(set(alice, ""), fee(XRP(1)), ter(temDISABLED));
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(keylet::appLoader(alice.id())));
|
||||
BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfAppLoaderSequence));
|
||||
}
|
||||
|
||||
void
|
||||
testSetAndRemove()
|
||||
{
|
||||
testcase("set, overwrite, remove, sequence");
|
||||
using namespace jtx;
|
||||
Env env{*this};
|
||||
Account const alice{"alice"};
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
auto const kl = keylet::appLoader(alice.id());
|
||||
auto seq = [&] {
|
||||
return (*env.le(alice))[~sfAppLoaderSequence].value_or(0);
|
||||
};
|
||||
|
||||
BEAST_EXPECT(!env.le(kl));
|
||||
BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfAppLoaderSequence));
|
||||
|
||||
// Create: the object, with the owner's new sequence; nothing inline
|
||||
// on the AccountRoot, no reserve, not in the owner directory.
|
||||
env(set(alice, doc()), fee(XRP(1)));
|
||||
env.close();
|
||||
auto sle = env.le(kl);
|
||||
if (BEAST_EXPECT(sle))
|
||||
{
|
||||
BEAST_EXPECT((*sle)[sfAppLoader] == makeSlice(doc()));
|
||||
BEAST_EXPECT((*sle)[sfOwner] == alice.id());
|
||||
BEAST_EXPECT((*sle)[sfAppLoaderSequence] == 1);
|
||||
}
|
||||
BEAST_EXPECT(seq() == 1);
|
||||
BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfAppLoader));
|
||||
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
|
||||
BEAST_EXPECT(
|
||||
env.rpc(
|
||||
"json",
|
||||
"account_objects",
|
||||
R"({"account":")" + alice.human() +
|
||||
R"("})")[jss::result][jss::account_objects]
|
||||
.size() == 0);
|
||||
auto const txid = (*sle)[sfPreviousTxnID];
|
||||
|
||||
// Overwrite: new content, next sequence, rethreaded.
|
||||
env(set(alice, "<html><body>v2</body></html>"), fee(XRP(1)));
|
||||
env.close();
|
||||
sle = env.le(kl);
|
||||
if (BEAST_EXPECT(sle))
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
(*sle)[sfAppLoader] ==
|
||||
makeSlice(std::string("<html><body>v2</body></html>")));
|
||||
BEAST_EXPECT((*sle)[sfAppLoaderSequence] == 2);
|
||||
BEAST_EXPECT((*sle)[sfPreviousTxnID] != txid);
|
||||
}
|
||||
BEAST_EXPECT(seq() == 2);
|
||||
|
||||
// An AccountSet without the field leaves everything alone.
|
||||
env(noop(alice));
|
||||
env.close();
|
||||
BEAST_EXPECT((*env.le(kl))[sfAppLoaderSequence] == 2 && seq() == 2);
|
||||
|
||||
// Remove with an empty blob: the object goes, the sequence still
|
||||
// advances (so a copy of sequence 2 is known to be stale), and
|
||||
// removing again is a no-op that advances it again.
|
||||
env(set(alice, ""), fee(XRP(1)));
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(kl) && seq() == 3);
|
||||
env(set(alice, ""), fee(XRP(1)));
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(kl) && seq() == 4);
|
||||
|
||||
// Republish: the sequence carries on.
|
||||
env(set(alice, doc()), fee(XRP(1)));
|
||||
env.close();
|
||||
BEAST_EXPECT((*env.le(kl))[sfAppLoaderSequence] == 5 && seq() == 5);
|
||||
|
||||
// ledger_entry reaches it by account or by index.
|
||||
for (auto const& req :
|
||||
{R"({"app_loader":")" + alice.human() + R"("})",
|
||||
R"({"index":")" + to_string(kl.key) + R"("})"})
|
||||
{
|
||||
auto const r = env.rpc("json", "ledger_entry", req)[jss::result];
|
||||
BEAST_EXPECTS(
|
||||
r[jss::node][sfLedgerEntryType.fieldName] == jss::AppLoader &&
|
||||
r[jss::node][sfAppLoaderSequence.fieldName] == 5,
|
||||
req);
|
||||
}
|
||||
BEAST_EXPECT(
|
||||
env.rpc(
|
||||
"json",
|
||||
"ledger_entry",
|
||||
R"({"app_loader":"nope"})")[jss::result][jss::error] ==
|
||||
"malformedAddress");
|
||||
}
|
||||
|
||||
void
|
||||
testFee()
|
||||
{
|
||||
testcase("fee: base plus one drop per byte");
|
||||
using namespace jtx;
|
||||
Env env{*this};
|
||||
Account const alice{"alice"};
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
auto const base = env.current()->fees().base;
|
||||
auto const kl = keylet::appLoader(alice.id());
|
||||
|
||||
auto const expected = base + XRPAmount{1024};
|
||||
env(set(alice, docOfSize(1024)),
|
||||
fee(expected - XRPAmount{1}),
|
||||
ter(telINSUF_FEE_P));
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(kl));
|
||||
auto const before = env.balance(alice);
|
||||
env(set(alice, docOfSize(1024)), fee(expected));
|
||||
env.close();
|
||||
BEAST_EXPECT(
|
||||
env.le(kl) && before - env.balance(alice) == drops(expected));
|
||||
|
||||
// Overwrites pay again; removal is base fee only.
|
||||
env(set(alice, docOfSize(100)),
|
||||
fee(base + XRPAmount{99}),
|
||||
ter(telINSUF_FEE_P));
|
||||
env(set(alice, docOfSize(100)), fee(base + XRPAmount{100}));
|
||||
env(set(alice, ""), fee(base));
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(kl));
|
||||
|
||||
// Publishing takes no reserve: an account at its reserve floor can.
|
||||
Account const poor{"poor"};
|
||||
auto const reserve = env.current()->fees().accountReserve(0);
|
||||
env.fund(drops(reserve + base + XRPAmount{100}), noripple(poor));
|
||||
env.close();
|
||||
env(set(poor, docOfSize(100)), fee(base + XRPAmount{100}));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.le(keylet::appLoader(poor.id())));
|
||||
BEAST_EXPECT(env.balance(poor) == drops(reserve));
|
||||
env.require(owners(poor, 0));
|
||||
}
|
||||
|
||||
void
|
||||
testMalformed()
|
||||
{
|
||||
testcase("malformed documents are temMALFORMED");
|
||||
using namespace jtx;
|
||||
Env env{*this};
|
||||
Account const alice{"alice"};
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
for (auto const& d :
|
||||
{std::string("hello"),
|
||||
std::string("<html>"),
|
||||
std::string("<html></html>x"),
|
||||
std::string("<html>\0</html>", 14),
|
||||
std::string("<html>\xC0\xAF</html>"),
|
||||
docOfSize(maxAppLoaderLength + 1)})
|
||||
{
|
||||
env(set(alice, d), fee(XRP(1)), ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
BEAST_EXPECT(!env.le(keylet::appLoader(alice.id())));
|
||||
BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfAppLoaderSequence));
|
||||
}
|
||||
|
||||
void
|
||||
testAccountDelete()
|
||||
{
|
||||
testcase("account delete erases the loader");
|
||||
using namespace jtx;
|
||||
Env env{*this};
|
||||
Account const alice{"alice"}, bob{"bob"};
|
||||
env.fund(XRP(1000), alice, bob);
|
||||
env.close();
|
||||
env(set(alice, doc()), fee(XRP(1)));
|
||||
env.close();
|
||||
for (int i = 0; i < 256; ++i)
|
||||
env.close();
|
||||
// Not in the owner directory, so it cannot block deletion; erased
|
||||
// explicitly, or AccountRootsDeletedClean would fire on the orphan.
|
||||
env(acctdelete(alice, bob),
|
||||
fee(drops(env.current()->fees().increment)));
|
||||
env.close();
|
||||
BEAST_EXPECT(!env.le(alice));
|
||||
BEAST_EXPECT(!env.le(keylet::appLoader(alice.id())));
|
||||
}
|
||||
|
||||
void
|
||||
testValidateRPC()
|
||||
{
|
||||
testcase("pwa_validate");
|
||||
using namespace jtx;
|
||||
Env env{*this};
|
||||
Account const alice{"alice"};
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
auto validate = [&](std::string const& d, std::string const& domain) {
|
||||
Json::Value p(Json::objectValue);
|
||||
p[jss::account] = alice.human();
|
||||
p[jss::hash] = to_string(sha512Half(makeSlice(d)));
|
||||
if (!domain.empty())
|
||||
p[jss::domain] = domain;
|
||||
return env.rpc("json", "pwa_validate", to_string(p))[jss::result];
|
||||
};
|
||||
|
||||
BEAST_EXPECT(validate(doc(), "")["reason"] == "no loader");
|
||||
env(set(alice, doc()), fee(XRP(1)));
|
||||
env.close();
|
||||
auto r = validate(doc(), "");
|
||||
BEAST_EXPECT(r["valid"] == true && r["app_loader_sequence"] == 1);
|
||||
BEAST_EXPECT(r[jss::ledger_index] == env.closed()->seq());
|
||||
BEAST_EXPECT(
|
||||
validate("<html></html>", "")["reason"] == "hash mismatch");
|
||||
|
||||
// domain: the account's Domain, or a host beneath it, ignoring case.
|
||||
BEAST_EXPECT(
|
||||
validate(doc(), "pwa.test")["reason"] ==
|
||||
"domain not endorsed by account");
|
||||
auto jt = noop(alice);
|
||||
jt[sfDomain.fieldName] = strHex(std::string("Pwa.Test"));
|
||||
env(jt);
|
||||
env.close();
|
||||
for (auto const d :
|
||||
{"pwa.test", "PWA.TEST.", "pwaabcd.pwa.test", "a.b.pwa.test"})
|
||||
BEAST_EXPECTS(validate(doc(), d)["valid"] == true, d);
|
||||
for (auto const d : {"other.test", "xpwa.test", "pwa.test.evil"})
|
||||
BEAST_EXPECTS(validate(doc(), d)["valid"] == false, d);
|
||||
|
||||
// Malformed input.
|
||||
BEAST_EXPECT(
|
||||
env.rpc(
|
||||
"json",
|
||||
"pwa_validate",
|
||||
R"({"account":"x","hash":"00"})")[jss::result][jss::error] ==
|
||||
"actMalformed");
|
||||
BEAST_EXPECT(
|
||||
env.rpc(
|
||||
"pwa_validate", alice.human(), "zz")[jss::result][jss::error] ==
|
||||
"invalidParams");
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testValidator();
|
||||
testEnabled();
|
||||
testSetAndRemove();
|
||||
testFee();
|
||||
testMalformed();
|
||||
testAccountDelete();
|
||||
testValidateRPC();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(PWALoader, app, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,111 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2026 XRPL-Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/misc/RotatingBloomFilter.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
class RotatingBloomFilter_test : public beast::unit_test::suite
|
||||
{
|
||||
using time_point = std::chrono::steady_clock::time_point;
|
||||
|
||||
void
|
||||
testWindow()
|
||||
{
|
||||
testcase("window");
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
RotatingBloomFilter<1024, 4, 4> filter{10s};
|
||||
time_point const t{};
|
||||
std::string const a{"a"};
|
||||
std::string const b{"b"};
|
||||
|
||||
filter.insert(a, t);
|
||||
BEAST_EXPECT(filter.contains(a, t));
|
||||
BEAST_EXPECT(!filter.contains(b, t));
|
||||
|
||||
// Remembered to the end of the third epoch after its own, and
|
||||
// forgotten at the start of the fourth.
|
||||
BEAST_EXPECT(filter.contains(a, t + 39s));
|
||||
BEAST_EXPECT(!filter.contains(a, t + 40s));
|
||||
|
||||
// Inserting again extends it.
|
||||
filter.insert(a, t + 35s);
|
||||
BEAST_EXPECT(filter.contains(a, t + 69s));
|
||||
BEAST_EXPECT(!filter.contains(a, t + 70s));
|
||||
|
||||
// A clock that jumps finds everything expired.
|
||||
filter.insert(b, t + 70s);
|
||||
BEAST_EXPECT(!filter.contains(b, t + 1h));
|
||||
|
||||
// An insertion that read the clock before another thread rotated its
|
||||
// slot lands in the newer epoch rather than being lost.
|
||||
filter.insert(b, t + 1h + 40s);
|
||||
filter.insert(a, t + 1h);
|
||||
BEAST_EXPECT(filter.contains(a, t + 1h + 40s));
|
||||
}
|
||||
|
||||
void
|
||||
testFalsePositives()
|
||||
{
|
||||
testcase("false positives");
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
using Filter = RotatingBloomFilter<std::size_t{1} << 14, 4, 4>;
|
||||
static_assert(Filter::bytes == 8192);
|
||||
|
||||
Filter filter{120s};
|
||||
time_point const t{};
|
||||
|
||||
std::uint64_t const inserted = 1000;
|
||||
for (std::uint64_t i = 0; i < inserted; ++i)
|
||||
filter.insert(i, t);
|
||||
|
||||
bool all = true;
|
||||
for (std::uint64_t i = 0; i < inserted; ++i)
|
||||
all = all && filter.contains(i, t);
|
||||
BEAST_EXPECT(all);
|
||||
|
||||
// About 0.24% in theory for a thousand elements in one generation.
|
||||
std::uint64_t const probes = 100000;
|
||||
std::uint64_t hits = 0;
|
||||
for (std::uint64_t i = inserted; i < inserted + probes; ++i)
|
||||
hits += filter.contains(i, t) ? 1 : 0;
|
||||
BEAST_EXPECT(hits < probes / 100);
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testWindow();
|
||||
testFalsePositives();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(RotatingBloomFilter, app, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -26,11 +26,17 @@
|
||||
#include <xrpld/app/misc/detail/OnlineDeleteRanges.h>
|
||||
#include <xrpld/app/rdb/backend/SQLiteDatabase.h>
|
||||
#include <xrpld/core/ConfigSections.h>
|
||||
#include <xrpld/nodestore/NodeObject.h>
|
||||
#include <xrpld/nodestore/detail/DatabaseRotatingImp.h>
|
||||
#include <xrpl/beast/utility/temp_dir.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <exception>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
@@ -47,6 +53,19 @@ class SHAMapStore_test : public beast::unit_test::suite
|
||||
return cfg;
|
||||
}
|
||||
|
||||
// Same as onlineDelete, but on a node store that persists nodes, so
|
||||
// SHAMapStoreImp builds DatabaseRotatingImp and really rotates.
|
||||
// envconfig defaults to type=rwdb, which is the null node store.
|
||||
static auto
|
||||
onlineDeleteStoring(std::unique_ptr<Config> cfg, std::string const& path)
|
||||
{
|
||||
cfg = onlineDelete(std::move(cfg));
|
||||
auto& section = cfg->section(ConfigSection::nodeDatabase());
|
||||
section.set("type", "memory");
|
||||
section.set("path", path);
|
||||
return cfg;
|
||||
}
|
||||
|
||||
static auto
|
||||
advisoryDelete(std::unique_ptr<Config> cfg)
|
||||
{
|
||||
@@ -308,13 +327,23 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
testAutomatic()
|
||||
testAutomatic(bool storing)
|
||||
{
|
||||
testcase("automatic online_delete");
|
||||
testcase(
|
||||
storing ? "automatic online_delete (rotating node store)"
|
||||
: "automatic online_delete (null node store)");
|
||||
using namespace jtx;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
Env env(*this, envconfig(onlineDelete));
|
||||
beast::temp_dir nodeDir;
|
||||
Env env(
|
||||
*this,
|
||||
storing ? envconfig(onlineDeleteStoring, nodeDir.path())
|
||||
: envconfig(onlineDelete));
|
||||
BEAST_EXPECT(
|
||||
storing ==
|
||||
(dynamic_cast<NodeStore::DatabaseRotating*>(
|
||||
&env.app().getNodeStore()) != nullptr));
|
||||
auto& store = env.app().getSHAMapStore();
|
||||
|
||||
auto ledgerSeq = waitForReady(env);
|
||||
@@ -572,6 +601,412 @@ public:
|
||||
return backend;
|
||||
}
|
||||
|
||||
std::unique_ptr<NodeStore::Backend>
|
||||
makeMemoryBackend(
|
||||
jtx::Env& env,
|
||||
NodeStoreScheduler& scheduler,
|
||||
std::string const& name)
|
||||
{
|
||||
Section section;
|
||||
section.set("type", "memory");
|
||||
section.set("path", name);
|
||||
auto backend = NodeStore::Manager::instance().make_Backend(
|
||||
section,
|
||||
megabytes(env.app().config().getValueFor(
|
||||
SizedItem::burstSize, std::nullopt)),
|
||||
scheduler,
|
||||
env.app().logs().journal("NodeStoreTest"));
|
||||
backend->open();
|
||||
return backend;
|
||||
}
|
||||
|
||||
// Ordinary fetch (duplicate == false) must copy an archived object
|
||||
// into the writable backend. The following rotation deletes the
|
||||
// archive. Without that copy, a transaction node read between
|
||||
// rotations disappears while callers can still name it.
|
||||
void
|
||||
testArchiveReadSurvivesRotation()
|
||||
{
|
||||
testcase("archive read survives rotation");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this, envconfig(onlineDelete));
|
||||
NodeStoreScheduler scheduler(env.app().getJobQueue());
|
||||
|
||||
Section nscfg;
|
||||
nscfg.set("type", "memory");
|
||||
nscfg.set("path", "rotating");
|
||||
|
||||
auto dbr = std::make_unique<NodeStore::DatabaseRotatingImp>(
|
||||
env.app(),
|
||||
scheduler,
|
||||
1,
|
||||
makeMemoryBackend(env, scheduler, "writable"),
|
||||
makeMemoryBackend(env, scheduler, "archive"),
|
||||
nscfg,
|
||||
env.app().logs().journal("NodeStoreTest"));
|
||||
|
||||
Blob const expected(32, 0xab);
|
||||
auto const hash = sha512Half(makeSlice(expected));
|
||||
{
|
||||
Blob data = expected;
|
||||
dbr->store(hotTRANSACTION_NODE, std::move(data), hash, 1);
|
||||
}
|
||||
|
||||
auto const matches = [&](std::shared_ptr<NodeObject> const& object) {
|
||||
return object && object->getType() == hotTRANSACTION_NODE &&
|
||||
object->getData() == expected;
|
||||
};
|
||||
|
||||
// Public fetch defaults duplicate to false. The private override
|
||||
// is the rotation copy pass.
|
||||
NodeStore::Database& db = *dbr;
|
||||
BEAST_EXPECT(matches(db.fetchNodeObject(hash)));
|
||||
|
||||
auto const nop = [](std::string const&, std::string const&) {};
|
||||
// Writable becomes the archive. The object now lives only there.
|
||||
dbr->rotate(makeMemoryBackend(env, scheduler, "writable-2"), nop);
|
||||
|
||||
BEAST_EXPECT(matches(db.fetchNodeObject(hash)));
|
||||
|
||||
dbr->rotate(makeMemoryBackend(env, scheduler, "writable-3"), nop);
|
||||
BEAST_EXPECT(matches(db.fetchNodeObject(hash)));
|
||||
}
|
||||
|
||||
// getWriteLoad, sync, storeLedger, importDatabase and for_each changed
|
||||
// lock type. In null mode no Env builds a DatabaseRotatingImp, so
|
||||
// drive them directly.
|
||||
void
|
||||
testRotatingAccessors()
|
||||
{
|
||||
testcase("rotating database accessors");
|
||||
|
||||
using namespace jtx;
|
||||
// Source ledger must come from a node store that holds its nodes.
|
||||
Env env(*this, envconfig([](std::unique_ptr<Config> cfg) {
|
||||
auto& section = cfg->section(ConfigSection::nodeDatabase());
|
||||
section.set("type", "memory");
|
||||
section.set("path", "rotating-accessors-app");
|
||||
return cfg;
|
||||
}));
|
||||
env.fund(XRP(10000), Account{"alice"});
|
||||
env.close();
|
||||
|
||||
NodeStoreScheduler scheduler(env.app().getJobQueue());
|
||||
Section nscfg;
|
||||
nscfg.set("type", "memory");
|
||||
nscfg.set("path", "rotating-accessors");
|
||||
auto makeDb = [&](std::string const& prefix) {
|
||||
return std::make_unique<NodeStore::DatabaseRotatingImp>(
|
||||
env.app(),
|
||||
scheduler,
|
||||
1,
|
||||
makeMemoryBackend(env, scheduler, prefix + "-writable"),
|
||||
makeMemoryBackend(env, scheduler, prefix + "-archive"),
|
||||
nscfg,
|
||||
env.app().logs().journal("NodeStoreTest"));
|
||||
};
|
||||
|
||||
auto src = makeDb("rotating-accessors-src");
|
||||
BEAST_EXPECT(src->getName() == "rotating-accessors-src-writable");
|
||||
BEAST_EXPECT(src->getWriteLoad() >= 0);
|
||||
src->sync();
|
||||
|
||||
auto const ledger = env.app().getLedgerMaster().getClosedLedger();
|
||||
if (!BEAST_EXPECT(ledger))
|
||||
return;
|
||||
BEAST_EXPECT(src->storeLedger(ledger));
|
||||
|
||||
NodeStore::Database& srcDb = *src;
|
||||
auto const stateRoot = ledger->stateMap().getHash().as_uint256();
|
||||
BEAST_EXPECT(srcDb.fetchNodeObject(ledger->info().hash));
|
||||
BEAST_EXPECT(srcDb.fetchNodeObject(stateRoot));
|
||||
|
||||
// importDatabase walks the source with for_each.
|
||||
auto dst = makeDb("rotating-accessors-dst");
|
||||
dst->importDatabase(*src);
|
||||
NodeStore::Database& dstDb = *dst;
|
||||
BEAST_EXPECT(dstDb.fetchNodeObject(ledger->info().hash));
|
||||
BEAST_EXPECT(dstDb.fetchNodeObject(stateRoot));
|
||||
}
|
||||
|
||||
static auto
|
||||
nullBackend(std::unique_ptr<Config> cfg)
|
||||
{
|
||||
cfg->LEDGER_HISTORY = 8;
|
||||
auto& section = cfg->section(ConfigSection::nodeDatabase());
|
||||
section.set("type", "rwdb");
|
||||
section.set("path", "main");
|
||||
return cfg;
|
||||
}
|
||||
|
||||
static auto
|
||||
nullBackendFullHistory(std::unique_ptr<Config> cfg)
|
||||
{
|
||||
cfg = nullBackend(std::move(cfg));
|
||||
cfg->LEDGER_HISTORY = std::numeric_limits<std::uint32_t>::max();
|
||||
return cfg;
|
||||
}
|
||||
|
||||
static auto
|
||||
nullBackendDeleteOff(std::unique_ptr<Config> cfg)
|
||||
{
|
||||
cfg = nullBackend(std::move(cfg));
|
||||
cfg->LEDGER_HISTORY = 256;
|
||||
cfg->section(ConfigSection::nodeDatabase()).set("online_delete", "0");
|
||||
return cfg;
|
||||
}
|
||||
|
||||
// Payments keep applying after more closes than ledger_history,
|
||||
// with no node-store record of the state tree. Close time comes
|
||||
// from the resident ledger, not a node-store header walk.
|
||||
void
|
||||
testNullModeLedgerProgression()
|
||||
{
|
||||
testcase("null mode ledger progression");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this, envconfig(nullBackend));
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
auto const bobBefore = env.balance(bob);
|
||||
constexpr int payments = 12;
|
||||
for (int i = 0; i < payments; ++i)
|
||||
{
|
||||
env(pay(alice, bob, XRP(1)));
|
||||
env.close();
|
||||
}
|
||||
|
||||
BEAST_EXPECT(env.balance(bob) == bobBefore + XRP(payments));
|
||||
BEAST_EXPECT(env.seq(alice) > 1);
|
||||
|
||||
auto const info = env.rpc(
|
||||
"json",
|
||||
"account_info",
|
||||
std::string{"{\"account\": \""} + alice.human() + "\"}");
|
||||
BEAST_EXPECT(
|
||||
info[jss::result].isMember(jss::account_data) &&
|
||||
info[jss::result][jss::account_data][jss::Account] ==
|
||||
alice.human());
|
||||
|
||||
auto const ledger = env.app().getLedgerMaster().getClosedLedger();
|
||||
if (!BEAST_EXPECT(ledger))
|
||||
return;
|
||||
BEAST_EXPECT(ledger->read(keylet::account(alice.id())));
|
||||
|
||||
auto const closeTime = env.app().getLedgerMaster().getCloseTimeByHash(
|
||||
ledger->info().hash, ledger->info().seq);
|
||||
BEAST_EXPECT(closeTime && *closeTime == ledger->info().closeTime);
|
||||
|
||||
auto const validated = env.rpc("ledger", "validated");
|
||||
BEAST_EXPECT(goodLedger(
|
||||
env, validated, std::to_string(ledger->info().seq), true));
|
||||
}
|
||||
|
||||
// ledger_history=full would make the retained-ledger window grow
|
||||
// without bound, since nothing else can drop a null-mode tree.
|
||||
void
|
||||
testNullModeRejectsFullHistory()
|
||||
{
|
||||
testcase("RWDB null mode rejects ledger_history=full");
|
||||
|
||||
using namespace jtx;
|
||||
try
|
||||
{
|
||||
Env env(*this, envconfig(nullBackendFullHistory));
|
||||
fail("Env should throw when ledger_history is full");
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
std::string(e.what()).find("ledger_history") !=
|
||||
std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
// online_delete=0 is an explicit disable. It must not be treated
|
||||
// as a missing key and replaced with ledger_history.
|
||||
void
|
||||
testExplicitOnlineDeleteZero()
|
||||
{
|
||||
testcase("explicit online_delete zero stays off");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this, envconfig(nullBackendDeleteOff));
|
||||
|
||||
Account const alice{"alice"};
|
||||
env.fund(XRP(10000), alice);
|
||||
env.close();
|
||||
auto const balance = env.balance(alice);
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.app().getSHAMapStore().getLastRotated() == 0);
|
||||
BEAST_EXPECT(env.balance(alice) == balance);
|
||||
}
|
||||
|
||||
void
|
||||
testNullModeRequiresHistory()
|
||||
{
|
||||
testcase("RWDB null mode requires ledger_history > 0");
|
||||
|
||||
using namespace jtx;
|
||||
try
|
||||
{
|
||||
Env env(*this, envconfig([](std::unique_ptr<Config> cfg) {
|
||||
cfg->LEDGER_HISTORY = 0;
|
||||
auto& section = cfg->section(ConfigSection::nodeDatabase());
|
||||
section.set("type", "rwdb");
|
||||
section.set("path", "main");
|
||||
return cfg;
|
||||
}));
|
||||
fail("Env should throw when ledger_history is 0");
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
std::string(e.what()).find("ledger_history") !=
|
||||
std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testRetainedLedgerCloseTime()
|
||||
{
|
||||
testcase("retained ledger close time survives later closes");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this, envconfig(nullBackend));
|
||||
|
||||
Account const alice{"alice"};
|
||||
env.fund(XRP(10000), alice);
|
||||
env.close();
|
||||
|
||||
auto const first = env.app().getLedgerMaster().getClosedLedger();
|
||||
if (!BEAST_EXPECT(first))
|
||||
return;
|
||||
auto const firstHash = first->info().hash;
|
||||
auto const firstSeq = first->info().seq;
|
||||
auto const firstClose = first->info().closeTime;
|
||||
|
||||
for (int i = 0; i < 12; ++i)
|
||||
{
|
||||
env(pay(alice, env.master, XRP(1)));
|
||||
env.close();
|
||||
}
|
||||
|
||||
auto const closeTime =
|
||||
env.app().getLedgerMaster().getCloseTimeByHash(firstHash, firstSeq);
|
||||
BEAST_EXPECT(closeTime && *closeTime == firstClose);
|
||||
BEAST_EXPECT(env.balance(alice) < XRP(10000));
|
||||
}
|
||||
|
||||
void
|
||||
testNullBackendIsPerConfig()
|
||||
{
|
||||
testcase("null backend flag is per configuration");
|
||||
|
||||
Config rwdbCfg;
|
||||
Config upperCfg;
|
||||
Config nudbCfg;
|
||||
Config emptyCfg;
|
||||
rwdbCfg.section(ConfigSection::nodeDatabase()).set("type", "rwdb");
|
||||
upperCfg.section(ConfigSection::nodeDatabase()).set("type", "RWDB");
|
||||
nudbCfg.section(ConfigSection::nodeDatabase()).set("type", "NuDB");
|
||||
|
||||
// Evaluated per Config object, not latched process-wide.
|
||||
BEAST_EXPECT(rwdbCfg.nullBackend());
|
||||
BEAST_EXPECT(upperCfg.nullBackend());
|
||||
BEAST_EXPECT(!nudbCfg.nullBackend());
|
||||
BEAST_EXPECT(!emptyCfg.nullBackend());
|
||||
BEAST_EXPECT(rwdbCfg.nullBackend());
|
||||
}
|
||||
|
||||
// Resident lookups are used with peer-supplied values. They must find
|
||||
// in-memory ledgers but never load or edit the complete-ledger set.
|
||||
void
|
||||
testResidentLedgerLookup()
|
||||
{
|
||||
testcase("resident ledger lookup does not load or clear");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this, envconfig(nullBackend));
|
||||
|
||||
Account const alice{"alice"};
|
||||
env.fund(XRP(10000), alice);
|
||||
env.close();
|
||||
for (int i = 0; i < 4; ++i)
|
||||
env.close();
|
||||
|
||||
auto& lm = env.app().getLedgerMaster();
|
||||
auto const closed = lm.getClosedLedger();
|
||||
if (!BEAST_EXPECT(closed))
|
||||
return;
|
||||
|
||||
auto const complete = lm.getCompleteLedgers();
|
||||
|
||||
auto const bySeq = lm.getResidentLedgerBySeq(closed->info().seq);
|
||||
BEAST_EXPECT(bySeq && bySeq->info().hash == closed->info().hash);
|
||||
|
||||
auto const byHash = lm.getResidentLedgerByHash(closed->info().hash);
|
||||
BEAST_EXPECT(byHash && byHash->info().seq == closed->info().seq);
|
||||
|
||||
// A recent ledger still inside the retained window.
|
||||
auto const prior = lm.getResidentLedgerBySeq(closed->info().seq - 2);
|
||||
BEAST_EXPECT(prior && prior->info().seq == closed->info().seq - 2);
|
||||
|
||||
BEAST_EXPECT(!lm.getResidentLedgerByHash(uint256{12345}));
|
||||
BEAST_EXPECT(!lm.getResidentLedgerBySeq(closed->info().seq + 1000));
|
||||
|
||||
BEAST_EXPECT(lm.getCompleteLedgers() == complete);
|
||||
|
||||
// Drop every ledger from the history cache. Null mode must still
|
||||
// reach recent ledgers through the closed/validated holders and
|
||||
// the retained window, without loading anything.
|
||||
env.app().getJobQueue().rendezvous();
|
||||
auto const older = lm.getResidentLedgerBySeq(closed->info().seq - 3);
|
||||
if (!BEAST_EXPECT(older))
|
||||
return;
|
||||
lm.clearLedgerCachePrior(closed->info().seq + 1);
|
||||
|
||||
auto const closedAgain = lm.getResidentLedgerBySeq(closed->info().seq);
|
||||
BEAST_EXPECT(
|
||||
closedAgain && closedAgain->info().hash == closed->info().hash);
|
||||
auto const closedByHash =
|
||||
lm.getResidentLedgerByHash(closed->info().hash);
|
||||
BEAST_EXPECT(
|
||||
closedByHash && closedByHash->info().seq == closed->info().seq);
|
||||
|
||||
auto const retainedBySeq = lm.getResidentLedgerBySeq(older->info().seq);
|
||||
BEAST_EXPECT(
|
||||
retainedBySeq && retainedBySeq->info().hash == older->info().hash);
|
||||
auto const retainedByHash =
|
||||
lm.getResidentLedgerByHash(older->info().hash);
|
||||
BEAST_EXPECT(
|
||||
retainedByHash && retainedByHash->info().seq == older->info().seq);
|
||||
|
||||
BEAST_EXPECT(!lm.getResidentLedgerByHash(uint256{12345}));
|
||||
BEAST_EXPECT(lm.getCompleteLedgers() == complete);
|
||||
}
|
||||
|
||||
void
|
||||
testNullFactoryDropsWrites()
|
||||
{
|
||||
testcase("type=rwdb uses NullFactory and drops node-store writes");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this, envconfig(nullBackend));
|
||||
|
||||
auto& db = env.app().getNodeStore();
|
||||
uint256 const hash{2};
|
||||
Blob data(32, 7);
|
||||
db.store(hotACCOUNT_NODE, std::move(data), hash, 1);
|
||||
BEAST_EXPECT(!db.fetchNodeObject(hash, 1));
|
||||
}
|
||||
|
||||
void
|
||||
testRotate()
|
||||
{
|
||||
@@ -852,8 +1287,19 @@ public:
|
||||
{
|
||||
testComputeOnlineDeleteTargets();
|
||||
testClear();
|
||||
testAutomatic();
|
||||
testAutomatic(false);
|
||||
testAutomatic(true);
|
||||
testRotatingAccessors();
|
||||
testCanDelete();
|
||||
testArchiveReadSurvivesRotation();
|
||||
testNullModeLedgerProgression();
|
||||
testNullModeRejectsFullHistory();
|
||||
testExplicitOnlineDeleteZero();
|
||||
testNullModeRequiresHistory();
|
||||
testRetainedLedgerCloseTime();
|
||||
testNullBackendIsPerConfig();
|
||||
testResidentLedgerLookup();
|
||||
testNullFactoryDropsWrites();
|
||||
testRotate();
|
||||
testPinnedRangeRestoreRequiresPinnedData();
|
||||
}
|
||||
|
||||
@@ -1962,7 +1962,8 @@ private:
|
||||
// | A | A | S | S | S | S | S
|
||||
// | A | D | N | N | S | S | S
|
||||
// | D | D | S | S | N/A | S | S
|
||||
// | D | A | S | S | N/A | S | S
|
||||
// | D | A | W* | W* | N/A | S | S
|
||||
// * S without featureEscrowDestinationCancel
|
||||
|
||||
static uint256
|
||||
getEscrowIndex(AccountID const& account, std::uint32_t uSequence)
|
||||
@@ -2117,7 +2118,7 @@ private:
|
||||
|
||||
// otxn: dest
|
||||
// tsh account
|
||||
// w/s: strong
|
||||
// w/s: weak (strong without featureEscrowDestinationCancel)
|
||||
for (bool const testStrong : {true, false})
|
||||
{
|
||||
test::jtx::Env env{
|
||||
@@ -2125,6 +2126,11 @@ private:
|
||||
network::makeNetworkConfig(21337, "10", "1000000", "200000"),
|
||||
features};
|
||||
|
||||
bool const withIOUIssuerWeakTSH =
|
||||
env.current()->rules().enabled(featureIOUIssuerWeakTSH);
|
||||
bool const withDestCancel =
|
||||
env.current()->rules().enabled(featureEscrowDestinationCancel);
|
||||
|
||||
auto const account = Account("alice");
|
||||
auto const dest = Account("bob");
|
||||
env.fund(XRP(1000), account, dest);
|
||||
@@ -2154,7 +2160,10 @@ private:
|
||||
env.close();
|
||||
|
||||
// verify tsh hook triggered
|
||||
testTSHStrongWeak(env, tshSTRONG, __LINE__);
|
||||
auto const expected = !withDestCancel ? tshSTRONG
|
||||
: !testStrong && withIOUIssuerWeakTSH ? tshWEAK
|
||||
: tshNONE;
|
||||
testTSHStrongWeak(env, expected, __LINE__);
|
||||
}
|
||||
|
||||
// otxn: account
|
||||
@@ -2373,7 +2382,7 @@ private:
|
||||
|
||||
// otxn: dest
|
||||
// tsh account
|
||||
// w/s: strong
|
||||
// w/s: weak (strong without featureEscrowDestinationCancel)
|
||||
for (bool const testStrong : {true, false})
|
||||
{
|
||||
test::jtx::Env env{
|
||||
@@ -2381,6 +2390,11 @@ private:
|
||||
network::makeNetworkConfig(21337, "10", "1000000", "200000"),
|
||||
features};
|
||||
|
||||
bool const withIOUIssuerWeakTSH =
|
||||
env.current()->rules().enabled(featureIOUIssuerWeakTSH);
|
||||
bool const withDestCancel =
|
||||
env.current()->rules().enabled(featureEscrowDestinationCancel);
|
||||
|
||||
auto const account = Account("alice");
|
||||
auto const dest = Account("bob");
|
||||
env.fund(XRP(1000), account, dest);
|
||||
@@ -2413,7 +2427,9 @@ private:
|
||||
env.close();
|
||||
|
||||
// verify tsh hook triggered
|
||||
auto const expected = testStrong ? tshSTRONG : tshSTRONG;
|
||||
auto const expected = !withDestCancel ? tshSTRONG
|
||||
: !testStrong && withIOUIssuerWeakTSH ? tshWEAK
|
||||
: tshNONE;
|
||||
testTSHStrongWeak(env, expected, __LINE__);
|
||||
}
|
||||
|
||||
@@ -4421,9 +4437,11 @@ private:
|
||||
env,
|
||||
issuer,
|
||||
0,
|
||||
mintFlags | (strongIssuerTSH ? tfStrongTSH : 0));
|
||||
mintFlags | (strongIssuerTSH ? std::uint32_t(tfStrongTSH) : 0));
|
||||
env(token::mint(issuer),
|
||||
txflags(mintFlags | (strongIssuerTSH ? tfStrongTSH : 0)));
|
||||
txflags(
|
||||
mintFlags |
|
||||
(strongIssuerTSH ? std::uint32_t(tfStrongTSH) : 0)));
|
||||
env.close();
|
||||
|
||||
auto const offerIndex =
|
||||
@@ -6128,6 +6146,15 @@ private:
|
||||
pass();
|
||||
}
|
||||
|
||||
void
|
||||
testEntropyTSH(FeatureBitset features)
|
||||
{
|
||||
testcase("entropy tsh");
|
||||
|
||||
// pseudo transaction
|
||||
pass();
|
||||
}
|
||||
|
||||
// | otxn | tfBurnable | tsh | mint | burn | buy | sell | cancel
|
||||
// | O | false | O | N/A | S | N/A | S | S
|
||||
// | O | false | I | N/A | N | N/A | W | N/A
|
||||
@@ -8279,16 +8306,16 @@ private:
|
||||
|
||||
// validate the emitted txn ids
|
||||
std::vector<std::string> const txIds = {
|
||||
"9610F73CDD6590EB6B3C82E5EC55D4B4C80CD7128B98AA556F7EC9DD96AE7056",
|
||||
"2F4582A29272390C0C25A80D4A3BCE5A14ACE6D86D8D0CB2C57719EB6FA881AE",
|
||||
"89A301CFEF0DD781AB9032A6A2DCE0937BC0119D2CDD06033B8B2FD80968E519",
|
||||
"DD8721B59024E168480B4DF8F8E93778601F0BD2E77FC991F3DA1182F5AD8B1E",
|
||||
"5D735C2EE3CB8289F8E11621FDC9565F9D6D67F3AE59D65332EACE591D67945F",
|
||||
"F02470E01731C968881AF4CBDEC90BB9E1F7AB0BE1CC22AF15451FB6D191096D",
|
||||
"8AD65E541DECD49B1693F8C17DFD8A2B906F49C673C4FD2034FF772E2BE50C30",
|
||||
"9F225229059CCC6257814D03C107884CF588C1C246A89ADFC16E50DF671B834C",
|
||||
"13C2A54A14BADF3648CED05175E1CCAD713F7E5EA56D9735CF8813CD5551F281",
|
||||
"87C60F41A96554587CED289F83F52DEE3CF670EEB189B067E6066B9A06056ADF",
|
||||
"2FBEF981BAC322225D13C274D0118FECC1B93A89EDBC0AB3CBD8B30A1591024E",
|
||||
"3C0A12F6A322486ABDA4180DC2B424BD252FD2FA6A33553B0B5D1526C020C475",
|
||||
"AFFD55D757136FC0BE665F16E0D8CFBDC0D37CF85B5D3329BE1DBD3A44BB281C",
|
||||
"BBE43C01068CE511DBF24F84A928E481451FF448AF2493E1A9BE099628D1F523",
|
||||
"45970753F7B52448C23D66E5E19DBBC65F5BE6055C04AC3BA9896D7D67891DB1",
|
||||
"97F4E09B2AEF4D798D378B1390B0624A68F304247B358334167E93217D502A16",
|
||||
"770D3B786C8E3BEF560D9589B02557B0BDFC1C7FD15C6CF42E86068A6A411EFC",
|
||||
"239DFA86EE8391418170D7FDF5AA8B8EEE0BBEF5E9F928BC29C23EEF6F1858AD",
|
||||
"9FAA93AC6340C87B5C722FF0E8D4D3F39B9A0CA5E9799EC4CE316BFD5E379540",
|
||||
"8672B2D8AA5E9EF86929B8662FE436541B2F739052732020B874BC304601C4DB",
|
||||
};
|
||||
Json::Value params;
|
||||
params[jss::transaction] =
|
||||
@@ -8421,8 +8448,8 @@ private:
|
||||
using namespace std::literals;
|
||||
testcase("set manifest TSH");
|
||||
|
||||
if (!features[featureOnChainManifests])
|
||||
return;
|
||||
// OnChainManifests is not yet Supported::yes, so enable it explicitly.
|
||||
features = features | featureOnChainManifests;
|
||||
|
||||
// otxn: master
|
||||
// tsh: ephemeral
|
||||
@@ -8514,8 +8541,9 @@ public:
|
||||
using namespace test::jtx;
|
||||
static FeatureBitset const all{supported_amendments()};
|
||||
|
||||
static std::array<FeatureBitset, 2> const feats{
|
||||
static std::array<FeatureBitset, 3> const feats{
|
||||
all,
|
||||
all - featureEscrowDestinationCancel,
|
||||
all - featureIOUIssuerWeakTSH,
|
||||
};
|
||||
|
||||
@@ -8545,10 +8573,12 @@ public:
|
||||
} \
|
||||
};
|
||||
|
||||
SETHOOKTSH_TEST(1, true)
|
||||
SETHOOKTSH_TEST(1, false)
|
||||
SETHOOKTSH_TEST(2, true)
|
||||
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(SetHookTSH0, app, ripple, 2);
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(SetHookTSH1, app, ripple, 2);
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(SetHookTSH2, app, ripple, 2);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
+1543
-109
File diff suppressed because it is too large.
Load diff
+1232
-119
File diff suppressed because it is too large.
Load diff
Loaded 100 of 369 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user