mirror of
https://github.com/Xahau/xahaud.git
synced 2026-10-22 19:57:09 +00:00
Compare commits
79
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
456ecdacc2 | ||
|
|
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 | ||
|
|
c208a40ce8 | ||
|
|
401a35957d | ||
|
|
65ff61d518 | ||
|
|
6191c1e09d | ||
|
|
f1a3a775a1 | ||
|
|
c1dabe5576 | ||
|
|
c6d5b345eb | ||
|
|
bb244ef772 | ||
|
|
639ea34377 | ||
|
|
089c0dc3fe | ||
|
|
78167e09c0 | ||
|
|
607a7fdf98 | ||
|
|
c55420bcd8 | ||
|
|
cb91b4e88e | ||
|
|
90333b6fd0 | ||
|
|
7f9a9364b0 | ||
|
|
706d31f01d | ||
|
|
083e9e4315 | ||
|
|
b9e0c56def | ||
|
|
663ed4edb8 | ||
|
|
8422758d4d | ||
|
|
586c78e812 | ||
|
|
9b50b68d39 | ||
|
|
5e8d26f67a | ||
|
|
a6186d7855 | ||
|
|
b449599408 |
No files matched your search
@@ -1,6 +1,46 @@
|
||||
codecov:
|
||||
require_ci_to_pass: true
|
||||
|
||||
comment:
|
||||
behavior: default
|
||||
layout: reach,diff,flags,tree,reach
|
||||
show_carryforward_flags: false
|
||||
|
||||
coverage:
|
||||
range: "60..80"
|
||||
precision: 1
|
||||
round: nearest
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: 60%
|
||||
threshold: 2%
|
||||
patch:
|
||||
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:
|
||||
annotations: true
|
||||
|
||||
parsers:
|
||||
cobertura:
|
||||
partials_as_hits: true
|
||||
handle_missing_conditions : true
|
||||
|
||||
slack_app: false
|
||||
|
||||
ignore:
|
||||
- "src/test/"
|
||||
- "include/xrpl/beast/test/"
|
||||
- "include/xrpl/beast/unit_test/"
|
||||
+21
-4
@@ -1,8 +1,25 @@
|
||||
# This feature requires Git >= 2.24
|
||||
# To use it by default in git blame:
|
||||
# git config blame.ignoreRevsFile .git-blame-ignore-revs
|
||||
# Format first-party source according to .clang-format
|
||||
50760c693510894ca368e90369b0cc2dabfd07f3
|
||||
e2384885f5f630c8f0ffe4bf21a169b433a16858
|
||||
241b9ddde9e11beb7480600fd5ed90e1ef109b21
|
||||
760f16f56835663d9286bd29294d074de26a7ba6
|
||||
0eebe6a5f4246fced516d52b83ec4e7f47373edd
|
||||
# Reintroduce Clang-Format & Levelization
|
||||
da1d20d6d5d862716125d60899b80fab5302954a
|
||||
# Consolidate external libraries
|
||||
da1d20d6d5d862716125d60899b80fab5302954a
|
||||
# Rename .hpp to .h
|
||||
0345a2645d0f5ad900f4fbbcaff96040d3a887fc
|
||||
# Format formerly .hpp files
|
||||
5a227dc719016e10045e17c9396ad401118044f1
|
||||
# Rewrite includes
|
||||
e61880699997398f5a746e6c4034edc7632661f5
|
||||
# Move CMake directory (#4997)
|
||||
e47b1c1b3b97c3f6d11858ee02f463596e29e7f0
|
||||
# Rearrange sources (#4997)
|
||||
bfafa2bb39e562901736d656806bd700c3699a2f
|
||||
# Rewrite includes (#4997)
|
||||
e61880699997398f5a746e6c4034edc7632661f5
|
||||
# Recompute loops (#4997)
|
||||
d25b5dcd568bb96c18e347d55fac10fe901a1bfb
|
||||
# Reformat code with clang-format-18
|
||||
02749feea88ce61c1f7eeb2d61a57d8ecf07ab11
|
||||
@@ -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}"
|
||||
@@ -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,24 +334,41 @@ 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
|
||||
plugins: noop
|
||||
use_oidc: true
|
||||
|
||||
- name: Export server definitions
|
||||
if: matrix.job_type == 'build' && matrix.compiler_id == 'gcc-13-libstdcxx'
|
||||
run: |
|
||||
${{ env.build_dir }}/rippled --definitions | python3 -m json.tool > server_definitions.json
|
||||
|
||||
- name: Upload server definitions
|
||||
if: matrix.job_type == 'build' && matrix.compiler_id == 'gcc-13-libstdcxx'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: server-definitions
|
||||
path: server_definitions.json
|
||||
archive: false
|
||||
@@ -32,6 +32,7 @@ test.basics > xrpld.perflog
|
||||
test.basics > xrpld.rpc
|
||||
test.basics > xrpl.json
|
||||
test.basics > xrpl.protocol
|
||||
test.beast > test.unit_test
|
||||
test.beast > xrpl.basics
|
||||
test.conditions > xrpl.basics
|
||||
test.conditions > xrpld.conditions
|
||||
@@ -77,6 +78,11 @@ test.ledger > xrpld.app
|
||||
test.ledger > xrpld.core
|
||||
test.ledger > xrpld.ledger
|
||||
test.ledger > xrpl.protocol
|
||||
test.net > test.toplevel
|
||||
test.net > xrpl.basics
|
||||
test.net > xrpld.core
|
||||
test.net > xrpld.net
|
||||
test.net > xrpl.json
|
||||
test.nodestore > test.jtx
|
||||
test.nodestore > test.toplevel
|
||||
test.nodestore > test.unit_test
|
||||
@@ -84,11 +90,13 @@ test.nodestore > xrpl.basics
|
||||
test.nodestore > xrpld.core
|
||||
test.nodestore > xrpld.nodestore
|
||||
test.nodestore > xrpld.unity
|
||||
test.nodestore > xrpl.protocol
|
||||
test.overlay > test.jtx
|
||||
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
|
||||
@@ -118,6 +126,7 @@ test.rpc > xrpld.core
|
||||
test.rpc > xrpld.net
|
||||
test.rpc > xrpld.overlay
|
||||
test.rpc > xrpld.rpc
|
||||
test.rpc > xrpld.shamap
|
||||
test.rpc > xrpl.hook
|
||||
test.rpc > xrpl.json
|
||||
test.rpc > xrpl.protocol
|
||||
@@ -183,6 +192,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
|
||||
@@ -198,6 +208,7 @@ xrpld.rpc > xrpld.core
|
||||
xrpld.rpc > xrpld.ledger
|
||||
xrpld.rpc > xrpld.nodestore
|
||||
xrpld.rpc > xrpld.shamap
|
||||
xrpld.rpc > xrpl.hook
|
||||
xrpld.rpc > xrpl.json
|
||||
xrpld.rpc > xrpl.protocol
|
||||
xrpld.rpc > xrpl.resource
|
||||
|
||||
+10
-2
@@ -95,8 +95,16 @@ if [[ "$4" == "" ]]; then
|
||||
echo "Non GH, local building, no Action runner magic"
|
||||
else
|
||||
# GH Action, runner
|
||||
cp /io/release-build/xahaud /data/builds/$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4
|
||||
cp /io/release-build/release.info /data/builds/$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4.releaseinfo
|
||||
if [[ "$(git rev-parse --abbrev-ref HEAD)" == "release" ]]; then
|
||||
echo "building on the release branch... placing it in builds/candidate"
|
||||
mkdir /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
|
||||
echo "building non-release branch, placing it in builds root"
|
||||
cp /io/release-build/xahaud /data/builds/$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4
|
||||
cp /io/release-build/release.info /data/builds/$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4.releaseinfo
|
||||
fi
|
||||
echo "Published build to: http://build.xahau.tech/"
|
||||
echo $(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4
|
||||
fi
|
||||
|
||||
+14
-16
@@ -12,17 +12,16 @@ echo "-- GITHUB_REPOSITORY: $1"
|
||||
echo "-- GITHUB_SHA: $2"
|
||||
echo "-- GITHUB_RUN_NUMBER: $4"
|
||||
|
||||
umask 0000;
|
||||
umask 0000
|
||||
|
||||
####
|
||||
|
||||
cd /io;
|
||||
mkdir -p src/certs;
|
||||
curl --silent -k https://raw.githubusercontent.com/RichardAH/rippled-release-builder/main/ca-bundle/certbundle.h -o src/certs/certbundle.h;
|
||||
if [ "`grep certbundle.h src/xrpld/net/detail/RegisterSSLCerts.cpp | wc -l`" -eq "0" ]
|
||||
then
|
||||
cp src/xrpld/net/detail/RegisterSSLCerts.cpp src/xrpld/net/detail/RegisterSSLCerts.cpp.old
|
||||
perl -i -pe "s/^{/{
|
||||
cd /io
|
||||
mkdir -p src/certs
|
||||
curl --silent -k https://raw.githubusercontent.com/RichardAH/rippled-release-builder/main/ca-bundle/certbundle.h -o src/certs/certbundle.h
|
||||
if [ "$(grep certbundle.h src/xrpld/net/detail/RegisterSSLCerts.cpp | wc -l)" -eq "0" ]; then
|
||||
cp src/xrpld/net/detail/RegisterSSLCerts.cpp src/xrpld/net/detail/RegisterSSLCerts.cpp.old
|
||||
perl -i -pe "s/^{/{
|
||||
#ifdef EMBEDDED_CA_BUNDLE
|
||||
BIO *cbio = BIO_new_mem_buf(ca_bundle.data(), ca_bundle.size());
|
||||
X509_STORE *cts = SSL_CTX_get_cert_store(ctx.native_handle());
|
||||
@@ -68,15 +67,14 @@ fi
|
||||
source /opt/rh/gcc-toolset-11/enable
|
||||
export PATH=/usr/local/bin:$PATH
|
||||
export CC='/usr/lib64/ccache/gcc' &&
|
||||
export CXX='/usr/lib64/ccache/g++' &&
|
||||
echo "-- Build Rippled --" &&
|
||||
pwd &&
|
||||
export CXX='/usr/lib64/ccache/g++' &&
|
||||
echo "-- Build Rippled --" &&
|
||||
pwd &&
|
||||
echo "MOVING TO [ build-core.sh ]"
|
||||
|
||||
echo "MOVING TO [ build-core.sh ]";
|
||||
|
||||
printenv > .env.temp;
|
||||
cat .env.temp | grep '=' | sed s/\\\(^[^=]\\+=\\\)/\\1\\\"/g|sed s/\$/\\\"/g > .env;
|
||||
rm .env.temp;
|
||||
printenv >.env.temp
|
||||
cat .env.temp | grep '=' | sed s/\\\(^[^=]\\+=\\\)/\\1\\\"/g | sed s/\$/\\\"/g >.env
|
||||
rm .env.temp
|
||||
|
||||
echo "Persisting ENV:"
|
||||
cat .env
|
||||
|
||||
@@ -494,7 +494,7 @@
|
||||
#
|
||||
# Configure the maximum number of transactions to have in the job queue
|
||||
#
|
||||
# Must be a number between 100 and 1000, defaults to 250
|
||||
# Must be a number between 100 and 1000, defaults to 1000
|
||||
#
|
||||
#
|
||||
# [overlay]
|
||||
@@ -593,7 +593,7 @@
|
||||
# reaches or exceeds this number. After that the limit may still
|
||||
# change, but will stay above the target. If consensus is not
|
||||
# healthy, the limit will be clamped to this value or lower.
|
||||
# Default: 50.
|
||||
# Default: 1000.
|
||||
#
|
||||
# maximum_txn_in_ledger = <number>
|
||||
#
|
||||
|
||||
@@ -146,8 +146,6 @@ D686F2538F410C9D0D856788E98E3579595DAF7B38D38887F81ECAC934B06040 HooksUpdate1
|
||||
3C43D9A973AA4443EF3FC38E42DD306160FBFFDAB901CD8BAA15D09F2597EB87 NonFungibleTokensV1
|
||||
0285B7E5E08E1A8E4C15636F0591D87F73CB6A7B6452A932AD72BBC8E5D1CBE3 fixNFTokenDirV1
|
||||
36799EA497B1369B170805C078AEFE6188345F9B3E324C21E9CA3FF574E3C3D6 fixNFTokenNegOffer
|
||||
4C499D17719BB365B69010A436B64FD1A82AAB199FC1CEB06962EBD01059FB09 fixXahauV1
|
||||
215181D23BF5C173314B5FDB9C872C92DE6CC918483727DE037C0C13E7E6EE9D fixXahauV2
|
||||
0D8BF22FF7570D58598D1EF19EBB6E142AD46E59A223FD3816262FBB69345BEA Remit
|
||||
7CA0426E7F411D39BB014E57CD9E08F61DE1750F0D41FCD428D9FB80BB7596B0 ZeroB2M
|
||||
4B8466415FAB32FFA89D9DCBE166A42340115771DF611A7160F8D7439C87ECD8 fixNSDelete
|
||||
|
||||
@@ -95,6 +95,9 @@
|
||||
# - replace both functions setup_target_for_coverage_gcovr_* with a single setup_target_for_coverage_gcovr
|
||||
# - add support for all gcovr output formats
|
||||
#
|
||||
# 2024-04-03, Bronek Kozicki
|
||||
# - add support for output formats: jacoco, clover, lcov
|
||||
#
|
||||
# USAGE:
|
||||
#
|
||||
# 1. Copy this file into your cmake modules path.
|
||||
@@ -256,10 +259,10 @@ endif()
|
||||
# BASE_DIRECTORY "../" # Base directory for report
|
||||
# # (defaults to PROJECT_SOURCE_DIR)
|
||||
# FORMAT "cobertura" # Output format, one of:
|
||||
# # xml cobertura sonarqube json-summary
|
||||
# # json-details coveralls csv txt
|
||||
# # html-single html-nested html-details
|
||||
# # (xml is an alias to cobertura;
|
||||
# # xml cobertura sonarqube jacoco clover
|
||||
# # json-summary json-details coveralls csv
|
||||
# # txt html-single html-nested html-details
|
||||
# # lcov (xml is an alias to cobertura;
|
||||
# # if no format is set, defaults to xml)
|
||||
# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative
|
||||
# # to BASE_DIRECTORY, with CMake 3.4+)
|
||||
@@ -308,6 +311,8 @@ function(setup_target_for_coverage_gcovr)
|
||||
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.txt)
|
||||
elseif(Coverage_FORMAT STREQUAL "csv")
|
||||
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.csv)
|
||||
elseif(Coverage_FORMAT STREQUAL "lcov")
|
||||
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.lcov)
|
||||
else()
|
||||
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.xml)
|
||||
endif()
|
||||
@@ -320,6 +325,14 @@ function(setup_target_for_coverage_gcovr)
|
||||
set(Coverage_FORMAT cobertura) # overwrite xml
|
||||
elseif(Coverage_FORMAT STREQUAL "sonarqube")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --sonarqube "${GCOVR_OUTPUT_FILE}" )
|
||||
elseif(Coverage_FORMAT STREQUAL "jacoco")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --jacoco "${GCOVR_OUTPUT_FILE}" )
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --jacoco-pretty )
|
||||
elseif(Coverage_FORMAT STREQUAL "clover")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --clover "${GCOVR_OUTPUT_FILE}" )
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --clover-pretty )
|
||||
elseif(Coverage_FORMAT STREQUAL "lcov")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --lcov "${GCOVR_OUTPUT_FILE}" )
|
||||
elseif(Coverage_FORMAT STREQUAL "json-summary")
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --json-summary "${GCOVR_OUTPUT_FILE}" )
|
||||
list(APPEND GCOVR_ADDITIONAL_ARGS --json-summary-pretty)
|
||||
|
||||
@@ -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>)
|
||||
|
||||
|
||||
@@ -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")
|
||||
@@ -2,6 +2,9 @@
|
||||
// Generated using generate_extern.sh
|
||||
#include <stdint.h>
|
||||
#ifndef HOOK_EXTERN
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern int32_t __attribute__((noduplicate))
|
||||
_g(uint32_t guard_id, uint32_t maxiter);
|
||||
@@ -336,5 +339,8 @@ prepare(
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#define HOOK_EXTERN
|
||||
#endif // HOOK_EXTERN
|
||||
@@ -9,7 +9,7 @@ ENUM_FILE="$SCRIPT_DIR/../include/xrpl/hook/Enum.h"
|
||||
echo '// For documentation please see: https://xrpl-hooks.readme.io/reference/'
|
||||
echo '// Generated using generate_error.sh'
|
||||
echo '#ifndef HOOK_ERROR_CODES'
|
||||
sed -n '/enum hook_return_code/,/};/p' "$ENUM_FILE" |
|
||||
sed -n '/enum class hook_return_code/,/};/p' "$ENUM_FILE" |
|
||||
awk '
|
||||
function ltrim(s) { sub(/^[[:space:]]+/, "", s); return s }
|
||||
function rtrim(s) { sub(/[[:space:]]+$/, "", s); return s }
|
||||
@@ -31,7 +31,7 @@ sed -n '/enum hook_return_code/,/};/p' "$ENUM_FILE" |
|
||||
|
||||
{
|
||||
line = $0
|
||||
if (line ~ /enum[[:space:]]+hook_return_code/)
|
||||
if (line ~ /enum[[:space:]]+class[[:space:]]+hook_return_code/)
|
||||
next
|
||||
if (line ~ /^[[:space:]]*\{/)
|
||||
next
|
||||
|
||||
+29
-23
@@ -11,6 +11,9 @@ APPLY_HOOK="$SCRIPT_DIR/../include/xrpl/hook/hook_api.macro"
|
||||
echo '// Generated using generate_extern.sh'
|
||||
echo '#include <stdint.h>'
|
||||
echo '#ifndef HOOK_EXTERN'
|
||||
echo '#ifdef __cplusplus'
|
||||
echo 'extern "C" {'
|
||||
echo '#endif'
|
||||
echo
|
||||
awk '
|
||||
function trim(s) {
|
||||
@@ -19,33 +22,36 @@ 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"
|
||||
|
||||
echo '#ifdef __cplusplus'
|
||||
echo '}'
|
||||
echo '#endif'
|
||||
echo '#define HOOK_EXTERN'
|
||||
echo '#endif // HOOK_EXTERN'
|
||||
} | (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -152,6 +152,7 @@
|
||||
#define sfEscrowID ((5U << 16U) + 35U)
|
||||
#define sfURITokenID ((5U << 16U) + 36U)
|
||||
#define sfDomainID ((5U << 16U) + 37U)
|
||||
#define sfManifestID ((5U << 16U) + 91U)
|
||||
#define sfHookOnOutgoing ((5U << 16U) + 93U)
|
||||
#define sfHookOnIncoming ((5U << 16U) + 94U)
|
||||
#define sfCron ((5U << 16U) + 95U)
|
||||
@@ -189,6 +190,7 @@
|
||||
#define sfSignatureReward ((6U << 16U) + 29U)
|
||||
#define sfMinAccountCreateAmount ((6U << 16U) + 30U)
|
||||
#define sfLPTokenBalance ((6U << 16U) + 31U)
|
||||
#define sfTrustLineRewardAccumulator ((6U << 16U) + 99U)
|
||||
#define sfPublicKey ((7U << 16U) + 1U)
|
||||
#define sfMessageKey ((7U << 16U) + 2U)
|
||||
#define sfSigningPubKey ((7U << 16U) + 3U)
|
||||
@@ -220,6 +222,7 @@
|
||||
#define sfProvider ((7U << 16U) + 30U)
|
||||
#define sfMPTokenMetadata ((7U << 16U) + 31U)
|
||||
#define sfCredentialType ((7U << 16U) + 32U)
|
||||
#define sfHookName ((7U << 16U) + 97U)
|
||||
#define sfRemarkValue ((7U << 16U) + 98U)
|
||||
#define sfRemarkName ((7U << 16U) + 99U)
|
||||
#define sfAccount ((8U << 16U) + 1U)
|
||||
@@ -255,6 +258,7 @@
|
||||
#define sfIssuingChainIssue ((24U << 16U) + 2U)
|
||||
#define sfAsset ((24U << 16U) + 3U)
|
||||
#define sfAsset2 ((24U << 16U) + 4U)
|
||||
#define sfClaimCurrency ((24U << 16U) + 5U)
|
||||
#define sfXChainBridge ((25U << 16U) + 1U)
|
||||
#define sfTransactionMetaData ((14U << 16U) + 2U)
|
||||
#define sfCreatedNode ((14U << 16U) + 3U)
|
||||
@@ -285,6 +289,7 @@
|
||||
#define sfXChainCreateAccountAttestationCollectionElement ((14U << 16U) + 31U)
|
||||
#define sfPriceData ((14U << 16U) + 32U)
|
||||
#define sfCredential ((14U << 16U) + 33U)
|
||||
#define sfManifest ((14U << 16U) + 90U)
|
||||
#define sfAmountEntry ((14U << 16U) + 91U)
|
||||
#define sfMintURIToken ((14U << 16U) + 92U)
|
||||
#define sfHookEmission ((14U << 16U) + 93U)
|
||||
@@ -292,6 +297,8 @@
|
||||
#define sfActiveValidator ((14U << 16U) + 95U)
|
||||
#define sfGenesisMint ((14U << 16U) + 96U)
|
||||
#define sfRemark ((14U << 16U) + 97U)
|
||||
#define sfHighReward ((14U << 16U) + 98U)
|
||||
#define sfLowReward ((14U << 16U) + 99U)
|
||||
#define sfSigners ((15U << 16U) + 3U)
|
||||
#define sfSignerEntries ((15U << 16U) + 4U)
|
||||
#define sfTemplate ((15U << 16U) + 5U)
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
#define ttNFTOKEN_MODIFY 70
|
||||
#define ttPERMISSIONED_DOMAIN_SET 71
|
||||
#define ttPERMISSIONED_DOMAIN_DELETE 72
|
||||
#define ttMANIFEST_SET 91
|
||||
#define ttCRON 92
|
||||
#define ttCRON_SET 93
|
||||
#define ttREMARKS_SET 94
|
||||
|
||||
@@ -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,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...);
|
||||
}
|
||||
|
||||
|
||||
+22
-15
@@ -16,6 +16,7 @@
|
||||
#define featureHooksUpdate1 "1"
|
||||
#define featureHooksUpdate2 "1"
|
||||
#define fix20250131 "1"
|
||||
#define fixGuardDepth32 "1"
|
||||
namespace hook_api {
|
||||
struct Rules
|
||||
{
|
||||
@@ -312,6 +313,7 @@ enum keylet_code : uint32_t {
|
||||
MPTOKEN = 34,
|
||||
CREDENTIAL = 35,
|
||||
PERMISSIONED_DOMAIN = 36,
|
||||
MANIFEST = 37,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -319,7 +321,7 @@ namespace compare_mode {
|
||||
enum compare_mode : uint32_t { EQUAL = 1, LESS = 2, GREATER = 4 };
|
||||
}
|
||||
|
||||
enum hook_return_code : int64_t {
|
||||
enum class hook_return_code : int64_t {
|
||||
SUCCESS =
|
||||
0, // return codes > 0 are reserved for hook apis to return "success"
|
||||
OUT_OF_BOUNDS =
|
||||
@@ -381,12 +383,12 @@ enum hook_return_code : int64_t {
|
||||
INVALID_KEY = -41, // user supplied key was not valid
|
||||
NOT_A_STRING = -42, // nul terminator missing from a string argument
|
||||
MEM_OVERLAP = -43, // one or more specified buffers are the same memory
|
||||
TOO_MANY_STATE_MODIFICATIONS = -44, // more than 5000 modified state
|
||||
TOO_MANY_STATE_MODIFICATIONS = -44, // more than 256 modified state
|
||||
// entires in the combined hook chains
|
||||
TOO_MANY_NAMESPACES = -45
|
||||
};
|
||||
|
||||
enum ExitType : uint8_t {
|
||||
enum class ExitType : uint8_t {
|
||||
UNSET = 0,
|
||||
WASM_ERROR = 1,
|
||||
ROLLBACK = 2,
|
||||
@@ -402,6 +404,18 @@ 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
|
||||
@@ -413,25 +427,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;
|
||||
@@ -443,6 +447,7 @@ getImportWhitelist(Rules const& rules)
|
||||
|
||||
enum GuardRulesVersion : uint64_t {
|
||||
GuardRuleFix20250131 = 0x00000001,
|
||||
GuardRuleDepth32 = 0x00000002,
|
||||
};
|
||||
|
||||
inline uint64_t
|
||||
@@ -451,6 +456,8 @@ getGuardRulesVersion(Rules const& rules)
|
||||
uint64_t version = 0;
|
||||
if (rules.enabled(fix20250131))
|
||||
version |= GuardRuleFix20250131;
|
||||
if (rules.enabled(fixGuardDepth32))
|
||||
version |= GuardRuleDepth32;
|
||||
return version;
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ parseSignedLeb128(
|
||||
}
|
||||
*end_offset = i;
|
||||
if (shift < 64 && (b & 0x40U))
|
||||
val |= (~0 << shift);
|
||||
val |= (~int64_t{0} << shift);
|
||||
|
||||
return val;
|
||||
}
|
||||
@@ -204,9 +204,13 @@ struct WasmBlkInf
|
||||
}
|
||||
// compute worst case execution time
|
||||
inline uint64_t
|
||||
compute_wce(const WasmBlkInf* blk, int level, bool* recursion_limit_reached)
|
||||
compute_wce(
|
||||
const WasmBlkInf* blk,
|
||||
int level,
|
||||
int max_level,
|
||||
bool* recursion_limit_reached)
|
||||
{
|
||||
if (level > 16)
|
||||
if (level > max_level)
|
||||
{
|
||||
*recursion_limit_reached = true;
|
||||
return 0;
|
||||
@@ -233,8 +237,8 @@ compute_wce(const WasmBlkInf* blk, int level, bool* recursion_limit_reached)
|
||||
|
||||
if (blk->children.size() > 0)
|
||||
for (auto const& child : blk->children)
|
||||
worst_case_execution +=
|
||||
compute_wce(child, level + 1, recursion_limit_reached);
|
||||
worst_case_execution += compute_wce(
|
||||
child, level + 1, max_level, recursion_limit_reached);
|
||||
|
||||
if (parent == 0 ||
|
||||
parent->iteration_bound ==
|
||||
@@ -788,12 +792,17 @@ check_guard(
|
||||
}
|
||||
|
||||
bool recursion_limit_reached = false;
|
||||
uint64_t wce = compute_wce(&(*root), 0, &recursion_limit_reached);
|
||||
int max_level = 16;
|
||||
if (rulesVersion & hook_api::GuardRuleDepth32)
|
||||
max_level = 32;
|
||||
uint64_t wce =
|
||||
compute_wce(&(*root), 0, max_level, &recursion_limit_reached);
|
||||
if (recursion_limit_reached)
|
||||
{
|
||||
GUARDLOG(hook::log::NESTING_LIMIT)
|
||||
<< "GuardCheck "
|
||||
<< "Maximum allowable depth of blocks reached (16 levels). Flatten "
|
||||
<< "Maximum allowable depth of blocks reached (" << max_level
|
||||
<< " levels). Flatten "
|
||||
"your loops and conditions!.\n";
|
||||
return {};
|
||||
}
|
||||
|
||||
+67
-50
@@ -89,58 +89,69 @@
|
||||
|
||||
#define WASM_VAL_TYPE(T, b) CAT2(TYP_, T)
|
||||
|
||||
#define DECLARE_HOOK_FUNCTION(R, F, ...) \
|
||||
R F(hook::HookContext& hookCtx, \
|
||||
WasmEdge_CallingFrameContext const& frameCtx __VA_OPT__( \
|
||||
COMMA __VA_ARGS__)); \
|
||||
extern WasmEdge_Result WasmFunction##F( \
|
||||
void* data_ptr, \
|
||||
const WasmEdge_CallingFrameContext* frameCtx, \
|
||||
const WasmEdge_Value* in, \
|
||||
WasmEdge_Value* out); \
|
||||
extern WasmEdge_ValType WasmFunctionParams##F[]; \
|
||||
extern WasmEdge_ValType WasmFunctionResult##F[]; \
|
||||
extern WasmEdge_FunctionTypeContext* WasmFunctionType##F; \
|
||||
#define UNSIGNED_TYPE(T) std::make_unsigned_t<T>
|
||||
|
||||
#define DECLARE_HOOK_FUNCTION(R, F, ...) \
|
||||
std::variant<UNSIGNED_TYPE(R), hook_api::hook_return_code> F( \
|
||||
hook::HookContext& hookCtx, \
|
||||
WasmEdge_CallingFrameContext const& frameCtx __VA_OPT__( \
|
||||
COMMA __VA_ARGS__)); \
|
||||
extern WasmEdge_Result WasmFunction##F( \
|
||||
void* data_ptr, \
|
||||
const WasmEdge_CallingFrameContext* frameCtx, \
|
||||
const WasmEdge_Value* in, \
|
||||
WasmEdge_Value* out); \
|
||||
extern WasmEdge_ValType WasmFunctionParams##F[]; \
|
||||
extern WasmEdge_ValType WasmFunctionResult##F[]; \
|
||||
extern WasmEdge_FunctionTypeContext* WasmFunctionType##F; \
|
||||
extern WasmEdge_String WasmFunctionName##F;
|
||||
|
||||
#define DEFINE_HOOK_FUNCTION(R, F, ...) \
|
||||
WasmEdge_Result hook_api::WasmFunction##F( \
|
||||
void* data_ptr, \
|
||||
const WasmEdge_CallingFrameContext* frameCtx, \
|
||||
const WasmEdge_Value* in, \
|
||||
WasmEdge_Value* out) \
|
||||
{ \
|
||||
__VA_OPT__(int _stack = 0;) \
|
||||
__VA_OPT__(FOR_VARS(VAR_ASSIGN, 2, __VA_ARGS__);) \
|
||||
hook::HookContext* hookCtx = \
|
||||
reinterpret_cast<hook::HookContext*>(data_ptr); \
|
||||
R return_code = hook_api::F( \
|
||||
*hookCtx, \
|
||||
*const_cast<WasmEdge_CallingFrameContext*>(frameCtx) \
|
||||
__VA_OPT__(COMMA STRIP_TYPES(__VA_ARGS__))); \
|
||||
if (return_code == RC_ROLLBACK || return_code == RC_ACCEPT) \
|
||||
return WasmEdge_Result_Terminate; \
|
||||
out[0] = RET_ASSIGN(R, return_code); \
|
||||
return WasmEdge_Result_Success; \
|
||||
}; \
|
||||
WasmEdge_ValType hook_api::WasmFunctionParams##F[] = { \
|
||||
__VA_OPT__(FOR_VARS(WASM_VAL_TYPE, 0, __VA_ARGS__))}; \
|
||||
WasmEdge_ValType hook_api::WasmFunctionResult##F[1] = { \
|
||||
WASM_VAL_TYPE(R, dummy)}; \
|
||||
WasmEdge_FunctionTypeContext* hook_api::WasmFunctionType##F = \
|
||||
WasmEdge_FunctionTypeCreate( \
|
||||
WasmFunctionParams##F, \
|
||||
VA_NARGS(NULL __VA_OPT__(, __VA_ARGS__)), \
|
||||
WasmFunctionResult##F, \
|
||||
1); \
|
||||
WasmEdge_String hook_api::WasmFunctionName##F = \
|
||||
WasmEdge_StringCreateByCString(#F); \
|
||||
R hook_api::F( \
|
||||
hook::HookContext& hookCtx, \
|
||||
WasmEdge_CallingFrameContext const& frameCtx __VA_OPT__( \
|
||||
#define DEFINE_HOOK_FUNCTION(R, F, ...) \
|
||||
WasmEdge_Result hook_api::WasmFunction##F( \
|
||||
void* data_ptr, \
|
||||
const WasmEdge_CallingFrameContext* frameCtx, \
|
||||
const WasmEdge_Value* in, \
|
||||
WasmEdge_Value* out) \
|
||||
{ \
|
||||
__VA_OPT__(int _stack = 0;) \
|
||||
__VA_OPT__(FOR_VARS(VAR_ASSIGN, 2, __VA_ARGS__);) \
|
||||
hook::HookContext* hookCtx = \
|
||||
reinterpret_cast<hook::HookContext*>(data_ptr); \
|
||||
auto const& return_code = hook_api::F( \
|
||||
*hookCtx, \
|
||||
*const_cast<WasmEdge_CallingFrameContext*>(frameCtx) \
|
||||
__VA_OPT__(COMMA STRIP_TYPES(__VA_ARGS__))); \
|
||||
if (std::holds_alternative<hook_api::hook_return_code>(return_code) && \
|
||||
(std::get<hook_api::hook_return_code>(return_code) == \
|
||||
RC_ROLLBACK || \
|
||||
std::get<hook_api::hook_return_code>(return_code) == RC_ACCEPT)) \
|
||||
return WasmEdge_Result_Terminate; \
|
||||
out[0] = RET_ASSIGN( \
|
||||
R, \
|
||||
std::holds_alternative<UNSIGNED_TYPE(R)>(return_code) \
|
||||
? std::get<UNSIGNED_TYPE(R)>(return_code) \
|
||||
: R(std::get<hook_api::hook_return_code>(return_code))); \
|
||||
return WasmEdge_Result_Success; \
|
||||
}; \
|
||||
WasmEdge_ValType hook_api::WasmFunctionParams##F[] = { \
|
||||
__VA_OPT__(FOR_VARS(WASM_VAL_TYPE, 0, __VA_ARGS__))}; \
|
||||
WasmEdge_ValType hook_api::WasmFunctionResult##F[1] = { \
|
||||
WASM_VAL_TYPE(R, dummy)}; \
|
||||
WasmEdge_FunctionTypeContext* hook_api::WasmFunctionType##F = \
|
||||
WasmEdge_FunctionTypeCreate( \
|
||||
WasmFunctionParams##F, \
|
||||
VA_NARGS(NULL __VA_OPT__(, __VA_ARGS__)), \
|
||||
WasmFunctionResult##F, \
|
||||
1); \
|
||||
WasmEdge_String hook_api::WasmFunctionName##F = \
|
||||
WasmEdge_StringCreateByCString(#F); \
|
||||
std::variant<UNSIGNED_TYPE(R), hook_api::hook_return_code> hook_api::F( \
|
||||
hook::HookContext& hookCtx, \
|
||||
WasmEdge_CallingFrameContext const& frameCtx __VA_OPT__( \
|
||||
COMMA __VA_ARGS__))
|
||||
|
||||
#define HOOK_SETUP() \
|
||||
using enum hook_api::hook_return_code; \
|
||||
try \
|
||||
{ \
|
||||
[[maybe_unused]] ApplyContext& applyCtx = hookCtx.applyCtx; \
|
||||
@@ -203,7 +214,7 @@
|
||||
host_memory_ptr, \
|
||||
guest_memory_length) \
|
||||
{ \
|
||||
int64_t bytes_written = 0; \
|
||||
uint64_t bytes_written = 0; \
|
||||
WRITE_WASM_MEMORY( \
|
||||
bytes_written, \
|
||||
guest_dst_ptr, \
|
||||
@@ -233,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 \
|
||||
*/ \
|
||||
@@ -272,7 +289,7 @@
|
||||
data_ptr < (data_ptr_in)) \
|
||||
return INTERNAL_ERROR; \
|
||||
if (data_len == 0) \
|
||||
return 0; \
|
||||
return 0ULL; \
|
||||
if ((write_ptr_in) == 0) \
|
||||
return data_as_int64(data_ptr, data_len); \
|
||||
if (data_len > (write_len_in)) \
|
||||
|
||||
@@ -1,374 +1,301 @@
|
||||
// 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)
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,35 +33,39 @@
|
||||
*
|
||||
* Steps required to add new features to the code:
|
||||
*
|
||||
* 1) In this file, increment `numFeatures` and add a uint256 declaration
|
||||
* for the feature at the bottom
|
||||
* 2) Add a uint256 definition for the feature to the corresponding source
|
||||
* file (Feature.cpp). Use `registerFeature` to create the feature with
|
||||
* the feature's name, `Supported::no`, and `VoteBehavior::DefaultNo`. This
|
||||
* should be the only place the feature's name appears in code as a string.
|
||||
* 3) Use the uint256 as the parameter to `view.rules.enabled()` to
|
||||
* control flow into new code that this feature limits.
|
||||
* 4) If the feature development is COMPLETE, and the feature is ready to be
|
||||
* SUPPORTED, change the `registerFeature` parameter to Supported::yes.
|
||||
* 5) When the feature is ready to be ENABLED, change the `registerFeature`
|
||||
* parameter to `VoteBehavior::DefaultYes`.
|
||||
* In general, any newly supported amendments (`Supported::yes`) should have
|
||||
* a `VoteBehavior::DefaultNo` for at least one full release cycle. High
|
||||
* priority bug fixes can be an exception to this rule of thumb.
|
||||
* 1) Add the appropriate XRPL_FEATURE or XRPL_FIX macro definition for the
|
||||
* feature to features.macro with the feature's name, `Supported::no`, and
|
||||
* `VoteBehavior::DefaultNo`.
|
||||
*
|
||||
* 2) Use the generated variable name as the parameter to `view.rules.enabled()`
|
||||
* to control flow into new code that this feature limits. (featureName or
|
||||
* fixName)
|
||||
*
|
||||
* 3) If the feature development is COMPLETE, and the feature is ready to be
|
||||
* SUPPORTED, change the macro parameter in features.macro to Supported::yes.
|
||||
*
|
||||
* 4) In general, any newly supported amendments (`Supported::yes`) should have
|
||||
* a `VoteBehavior::DefaultNo` indefinitely so that external governance can
|
||||
* make the decision on when to activate it. High priority bug fixes can be
|
||||
* an exception to this rule. In such cases, ensure the fix has been
|
||||
* clearly communicated to the community using appropriate channels,
|
||||
* then change the macro parameter in features.macro to
|
||||
* `VoteBehavior::DefaultYes`. The communication process is beyond
|
||||
* the scope of these instructions.
|
||||
*
|
||||
*
|
||||
* When a feature has been enabled for several years, the conditional code
|
||||
* may be removed, and the feature "retired". To retire a feature:
|
||||
* 1) Remove the uint256 declaration from this file.
|
||||
* 2) MOVE the uint256 definition in Feature.cpp to the "retired features"
|
||||
* section at the end of the file.
|
||||
* 3) CHANGE the name of the variable to start with "retired".
|
||||
* 4) CHANGE the parameters of the `registerFeature` call to `Supported::yes`
|
||||
* and `VoteBehavior::DefaultNo`.
|
||||
*
|
||||
* 1) MOVE the macro definition in features.macro to the "retired features"
|
||||
* section at the end of the file, and change the macro to XRPL_RETIRE.
|
||||
*
|
||||
* The feature must remain registered and supported indefinitely because it
|
||||
* still exists in the ledger, but there is no need to vote for it because
|
||||
* there's nothing to vote for. If it is removed completely from the code, any
|
||||
* instances running that code will get amendment blocked. Removing the
|
||||
* feature from the ledger is beyond the scope of these instructions.
|
||||
* may exist in the Amendments object on ledger. There is no need to vote
|
||||
* for it because there's nothing to vote for. If the feature definition is
|
||||
* removed completely from the code, any instances running that code will get
|
||||
* amendment blocked. Removing the feature from the ledger is beyond the scope
|
||||
* of these instructions.
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -76,11 +80,32 @@ allAmendments();
|
||||
|
||||
namespace detail {
|
||||
|
||||
#pragma push_macro("XRPL_FEATURE")
|
||||
#undef XRPL_FEATURE
|
||||
#pragma push_macro("XRPL_FIX")
|
||||
#undef XRPL_FIX
|
||||
#pragma push_macro("XRPL_RETIRE")
|
||||
#undef XRPL_RETIRE
|
||||
|
||||
#define XRPL_FEATURE(name, supported, vote) +1
|
||||
#define XRPL_FIX(name, supported, vote) +1
|
||||
#define XRPL_RETIRE(name) +1
|
||||
|
||||
// This value SHOULD be equal to the number of amendments registered in
|
||||
// Feature.cpp. Because it's only used to reserve storage, and determine how
|
||||
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
|
||||
// the actual number of amendments. A LogicError on startup will verify this.
|
||||
static constexpr std::size_t numFeatures = 115;
|
||||
static constexpr std::size_t numFeatures =
|
||||
(0 +
|
||||
#include <xrpl/protocol/detail/features.macro>
|
||||
);
|
||||
|
||||
#undef XRPL_RETIRE
|
||||
#pragma pop_macro("XRPL_RETIRE")
|
||||
#undef XRPL_FIX
|
||||
#pragma pop_macro("XRPL_FIX")
|
||||
#undef XRPL_FEATURE
|
||||
#pragma pop_macro("XRPL_FEATURE")
|
||||
|
||||
/** Amendments that this server supports and the default voting behavior.
|
||||
Whether they are enabled depends on the Rules defined in the validated
|
||||
@@ -320,12 +345,17 @@ foreachFeature(FeatureBitset bs, F&& f)
|
||||
#undef XRPL_FEATURE
|
||||
#pragma push_macro("XRPL_FIX")
|
||||
#undef XRPL_FIX
|
||||
#pragma push_macro("XRPL_RETIRE")
|
||||
#undef XRPL_RETIRE
|
||||
|
||||
#define XRPL_FEATURE(name, supported, vote) extern uint256 const feature##name;
|
||||
#define XRPL_FIX(name, supported, vote) extern uint256 const fix##name;
|
||||
#define XRPL_RETIRE(name)
|
||||
|
||||
#include <xrpl/protocol/detail/features.macro>
|
||||
|
||||
#undef XRPL_RETIRE
|
||||
#pragma pop_macro("XRPL_RETIRE")
|
||||
#undef XRPL_FIX
|
||||
#pragma pop_macro("XRPL_FIX")
|
||||
#undef XRPL_FEATURE
|
||||
|
||||
@@ -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
|
||||
@@ -372,6 +372,10 @@ permissionedDomain(AccountID const& account, std::uint32_t seq) noexcept;
|
||||
|
||||
Keylet
|
||||
permissionedDomain(uint256 const& domainID) noexcept;
|
||||
|
||||
Keylet
|
||||
manifest(PublicKey const& pk) noexcept;
|
||||
|
||||
} // namespace keylet
|
||||
|
||||
// Everything below is deprecated and should be removed in favor of keylets:
|
||||
|
||||
@@ -69,6 +69,7 @@ enum TELcodes : TERUnderlyingType {
|
||||
telIMPORT_VL_KEY_NOT_RECOGNISED,
|
||||
telCAN_NOT_QUEUE_IMPORT,
|
||||
telENV_RPC_FAILED,
|
||||
telMANIFEST_FEE_MISMATCH,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -137,6 +138,7 @@ enum TEMcodes : TERUnderlyingType {
|
||||
temXCHAIN_BRIDGE_NONDOOR_OWNER,
|
||||
temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT,
|
||||
temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT,
|
||||
temXCHAIN_TOO_MANY_ATTESTATIONS, // RESERVED - not used
|
||||
|
||||
temHOOK_DATA_TOO_LARGE,
|
||||
temEMPTY_DID,
|
||||
@@ -193,6 +195,8 @@ enum TEFcodes : TERUnderlyingType {
|
||||
tefNONDIR_EMIT,
|
||||
tefIMPORT_BLACKHOLED,
|
||||
tefINVALID_LEDGER_FIX_TYPE,
|
||||
tefPAST_MANIFEST_SEQ,
|
||||
tefREVOKED_MANIFEST,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
#if !defined(XRPL_FIX)
|
||||
#error "undefined macro: XRPL_FIX"
|
||||
#endif
|
||||
#if !defined(XRPL_RETIRE)
|
||||
#error "undefined macro: XRPL_RETIRE"
|
||||
#endif
|
||||
|
||||
// clang-format off
|
||||
|
||||
@@ -31,13 +34,22 @@
|
||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||
// in include/xrpl/protocol/Feature.h.
|
||||
|
||||
XRPL_FIX (20261005, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FEATURE(EscrowDestinationCancel, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (20260929, Supported::yes, VoteBehavior::DefaultNo)
|
||||
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)
|
||||
XRPL_FEATURE(IOURewardClaim, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (IOULockedBalanceInvariant, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (ImportIssuer, Supported::yes, VoteBehavior::DefaultYes)
|
||||
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.
|
||||
@@ -54,18 +66,19 @@ XRPL_FIX (FillOrKill, Supported::yes, VoteBehavior::DefaultYe
|
||||
XRPL_FEATURE(DID, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (DisallowIncomingV1, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FEATURE(XChainBridge, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(AMM, Supported::yes, 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);
|
||||
XRPL_FIX (HookAPI20251128, Supported::yes, VoteBehavior::DefaultYes);
|
||||
XRPL_FIX (CronStacking, Supported::yes, VoteBehavior::DefaultYes);
|
||||
XRPL_FEATURE(ExtendedHookState, Supported::yes, VoteBehavior::DefaultNo);
|
||||
XRPL_FIX (InvalidTxFlags, Supported::yes, VoteBehavior::DefaultYes);
|
||||
XRPL_FEATURE(Cron, Supported::yes, VoteBehavior::DefaultNo);
|
||||
XRPL_FEATURE(IOUIssuerWeakTSH, Supported::yes, VoteBehavior::DefaultNo);
|
||||
XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo);
|
||||
XRPL_FIX (ProvisionalDoubleThreading, Supported::yes, VoteBehavior::DefaultYes);
|
||||
XRPL_FEATURE(HooksUpdate2, 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)
|
||||
XRPL_FIX (InvalidTxFlags, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FEATURE(Cron, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(IOUIssuerWeakTSH, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (ProvisionalDoubleThreading, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FEATURE(Clawback, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (RewardClaimFlags, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FEATURE(HookCanEmit, Supported::yes, VoteBehavior::DefaultNo)
|
||||
@@ -81,8 +94,6 @@ XRPL_FIX (240819, Supported::yes, VoteBehavior::DefaultYe
|
||||
XRPL_FIX (NSDelete, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(ZeroB2M, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(Remit, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (XahauV2, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (XahauV1, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(HooksUpdate1, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FEATURE(XahauGenesis, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FEATURE(Import, Supported::yes, VoteBehavior::DefaultYes)
|
||||
@@ -149,4 +160,26 @@ XRPL_FIX (NFTokenDirV1, Supported::yes, VoteBehavior::Obsolete)
|
||||
XRPL_FEATURE(NonFungibleTokensV1, Supported::yes, VoteBehavior::Obsolete)
|
||||
XRPL_FEATURE(CryptoConditionsSuite, Supported::yes, VoteBehavior::Obsolete)
|
||||
|
||||
// The following amendments have been active for at least two years. Their
|
||||
// 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(fixXahauV2)
|
||||
XRPL_RETIRE(fixXahauV1)
|
||||
XRPL_RETIRE(MultiSign)
|
||||
XRPL_RETIRE(TrustSetAuth)
|
||||
XRPL_RETIRE(FeeEscalation)
|
||||
XRPL_RETIRE(PayChan)
|
||||
XRPL_RETIRE(CryptoConditions)
|
||||
XRPL_RETIRE(TickSize)
|
||||
XRPL_RETIRE(fix1368)
|
||||
XRPL_RETIRE(Escrow)
|
||||
XRPL_RETIRE(fix1373)
|
||||
XRPL_RETIRE(EnforceInvariants)
|
||||
XRPL_RETIRE(SortedDirectories)
|
||||
XRPL_RETIRE(fix1201)
|
||||
XRPL_RETIRE(fix1512)
|
||||
XRPL_RETIRE(fix1523)
|
||||
XRPL_RETIRE(fix1528)
|
||||
|
||||
// clang-format on
|
||||
@@ -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
|
||||
|
||||
/**
|
||||
@@ -93,7 +93,7 @@ LEDGER_ENTRY(ltCHECK, 0x0043, Check, check, ({
|
||||
*/
|
||||
LEDGER_ENTRY(ltHOOK_DEFINITION, 'D', HookDefinition, hook_definition, ({
|
||||
{sfHookHash, soeREQUIRED},
|
||||
{sfHookOn, soeREQUIRED},
|
||||
{sfHookOn, soeOPTIONAL},
|
||||
{sfHookOnIncoming, soeOPTIONAL},
|
||||
{sfHookOnOutgoing, soeOPTIONAL},
|
||||
{sfHookCanEmit, soeOPTIONAL},
|
||||
@@ -262,6 +262,7 @@ LEDGER_ENTRY(ltACCOUNT_ROOT, 0x0061, AccountRoot, account, ({
|
||||
{sfHookStateScale, soeOPTIONAL},
|
||||
{sfCron, soeOPTIONAL},
|
||||
{sfAMMID, soeOPTIONAL},
|
||||
{sfManifestID, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** A ledger object which contains a list of object identifiers.
|
||||
@@ -396,6 +397,8 @@ LEDGER_ENTRY(ltRIPPLE_STATE, 0x0072, RippleState, state, ({
|
||||
{sfHighQualityOut, soeOPTIONAL},
|
||||
{sfLockedBalance, soeOPTIONAL},
|
||||
{sfLockCount, soeOPTIONAL},
|
||||
{sfHighReward, soeOPTIONAL},
|
||||
{sfLowReward, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** The ledger object which lists the network's fee settings.
|
||||
@@ -590,6 +593,26 @@ LEDGER_ENTRY(ltDID, 0x008D, DID, did, ({
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
}))
|
||||
|
||||
#undef EXPAND
|
||||
LEDGER_ENTRY(ltMANIFEST, 0x004D, Manifest, manifest_entry, ({
|
||||
{sfAccount, soeREQUIRED},
|
||||
{sfPublicKey, soeREQUIRED},
|
||||
{sfSigningPubKey, soeOPTIONAL}, /* may be absent if the master is revoked */
|
||||
{sfSequence, soeREQUIRED},
|
||||
/* sfVersion defaults to 0 and is omitted from the signed payload when
|
||||
absent. Storing it unconditionally would change the bytes and break
|
||||
signature verification when the manifest is reconstructed. */
|
||||
{sfVersion, soeOPTIONAL},
|
||||
{sfDomain, soeOPTIONAL},
|
||||
/* The signatures are mirrored so the object is a lossless copy of the
|
||||
manifest and can be independently verified, and re-served to peers,
|
||||
by any node reading it (see ManifestCache::applyLedger). */
|
||||
{sfMasterSignature, soeREQUIRED},
|
||||
{sfSignature, soeOPTIONAL}, /* absent if the master is revoked */
|
||||
{sfManifestID, soeOPTIONAL}, /* pointer to the identical other copy on the other key */
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
}))
|
||||
|
||||
#undef LEDGER_ENTRY_EXPAND
|
||||
#undef LEDGER_ENTRY_DUPLICATE
|
||||
|
||||
@@ -210,6 +210,7 @@ TYPED_SFIELD(sfOfferID, UINT256, 34)
|
||||
TYPED_SFIELD(sfEscrowID, UINT256, 35)
|
||||
TYPED_SFIELD(sfURITokenID, UINT256, 36)
|
||||
TYPED_SFIELD(sfDomainID, UINT256, 37)
|
||||
TYPED_SFIELD(sfManifestID, UINT256, 91)
|
||||
TYPED_SFIELD(sfHookOnOutgoing, UINT256, 93)
|
||||
TYPED_SFIELD(sfHookOnIncoming, UINT256, 94)
|
||||
TYPED_SFIELD(sfCron, UINT256, 95)
|
||||
@@ -257,6 +258,7 @@ TYPED_SFIELD(sfPrice, AMOUNT, 28)
|
||||
TYPED_SFIELD(sfSignatureReward, AMOUNT, 29)
|
||||
TYPED_SFIELD(sfMinAccountCreateAmount, AMOUNT, 30)
|
||||
TYPED_SFIELD(sfLPTokenBalance, AMOUNT, 31)
|
||||
TYPED_SFIELD(sfTrustLineRewardAccumulator,AMOUNT, 99)
|
||||
|
||||
// variable length (common)
|
||||
TYPED_SFIELD(sfPublicKey, VL, 1)
|
||||
@@ -292,6 +294,7 @@ TYPED_SFIELD(sfAssetClass, VL, 29)
|
||||
TYPED_SFIELD(sfProvider, VL, 30)
|
||||
TYPED_SFIELD(sfMPTokenMetadata, VL, 31)
|
||||
TYPED_SFIELD(sfCredentialType, VL, 32)
|
||||
TYPED_SFIELD(sfHookName, VL, 97)
|
||||
TYPED_SFIELD(sfRemarkValue, VL, 98)
|
||||
TYPED_SFIELD(sfRemarkName, VL, 99)
|
||||
|
||||
@@ -340,6 +343,7 @@ TYPED_SFIELD(sfLockingChainIssue, ISSUE, 1)
|
||||
TYPED_SFIELD(sfIssuingChainIssue, ISSUE, 2)
|
||||
TYPED_SFIELD(sfAsset, ISSUE, 3)
|
||||
TYPED_SFIELD(sfAsset2, ISSUE, 4)
|
||||
TYPED_SFIELD(sfClaimCurrency, ISSUE, 5)
|
||||
|
||||
// bridge
|
||||
TYPED_SFIELD(sfXChainBridge, XCHAIN_BRIDGE, 1)
|
||||
@@ -379,6 +383,7 @@ UNTYPED_SFIELD(sfXChainClaimAttestationCollectionElement, OBJECT, 30)
|
||||
UNTYPED_SFIELD(sfXChainCreateAccountAttestationCollectionElement, OBJECT, 31)
|
||||
UNTYPED_SFIELD(sfPriceData, OBJECT, 32)
|
||||
UNTYPED_SFIELD(sfCredential, OBJECT, 33)
|
||||
UNTYPED_SFIELD(sfManifest, OBJECT, 90)
|
||||
UNTYPED_SFIELD(sfAmountEntry, OBJECT, 91)
|
||||
UNTYPED_SFIELD(sfMintURIToken, OBJECT, 92)
|
||||
UNTYPED_SFIELD(sfHookEmission, OBJECT, 93)
|
||||
@@ -386,6 +391,8 @@ UNTYPED_SFIELD(sfImportVLKey, OBJECT, 94)
|
||||
UNTYPED_SFIELD(sfActiveValidator, OBJECT, 95)
|
||||
UNTYPED_SFIELD(sfGenesisMint, OBJECT, 96)
|
||||
UNTYPED_SFIELD(sfRemark, OBJECT, 97)
|
||||
UNTYPED_SFIELD(sfHighReward, OBJECT, 98)
|
||||
UNTYPED_SFIELD(sfLowReward, OBJECT, 99)
|
||||
|
||||
// array of objects (common)
|
||||
// ARRAY/1 is reserved for end of array
|
||||
|
||||
@@ -500,6 +500,12 @@ TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 72, PermissionedDomainDelete, ({
|
||||
{sfDomainID, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/* sfAccount is supplied by TxFormats::commonFields; listing it here as well
|
||||
makes the SOTemplate reject the format at construction. */
|
||||
TRANSACTION(ttMANIFEST_SET, 91, SetManifest, ({
|
||||
{sfManifest, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/* A pseudo-txn alarm signal for invoking a hook, emitted by validators after alarm set conditions are met */
|
||||
TRANSACTION(ttCRON, 92, Cron, ({
|
||||
{sfOwner, soeREQUIRED},
|
||||
@@ -550,6 +556,7 @@ TRANSACTION(ttIMPORT, 97, Import, ({
|
||||
* from a specified hook */
|
||||
TRANSACTION(ttCLAIM_REWARD, 98, ClaimReward, ({
|
||||
{sfIssuer, soeOPTIONAL},
|
||||
{sfClaimCurrency, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction invokes a hook, providing arbitrary data. Essentially as a 0 drop payment. **/
|
||||
|
||||
@@ -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);
|
||||
@@ -76,6 +80,7 @@ JSS(Holder); // field.
|
||||
JSS(HookApiVersion); // field
|
||||
JSS(HookCanEmit); // field
|
||||
JSS(HookHash); // field
|
||||
JSS(HookName); // field
|
||||
JSS(HookNamespace); // field
|
||||
JSS(HookOn); // field
|
||||
JSS(HookOnIncoming); // field
|
||||
@@ -291,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
|
||||
@@ -349,6 +356,7 @@ JSS(hash_mismatches); // out: catalogue
|
||||
JSS(have_header); // out: InboundLedger
|
||||
JSS(have_state); // out: InboundLedger
|
||||
JSS(have_transactions); // out: InboundLedger
|
||||
JSS(hooks); // in/out: AccountInfo
|
||||
JSS(high); // out: BookChanges
|
||||
JSS(highest_sequence); // out: AccountInfo
|
||||
JSS(highest_ticket); // out: AccountInfo
|
||||
|
||||
@@ -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,7 +291,7 @@ 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 ||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
+16
-17
@@ -11,11 +11,11 @@ echo "START BUILDING (HOST)"
|
||||
echo "Cleaning previously built binary"
|
||||
rm -f release-build/xahaud
|
||||
|
||||
BUILD_CORES=$(echo "scale=0 ; `nproc` / 1.337" | bc)
|
||||
BUILD_CORES=$(echo "scale=0 ; $(nproc) / 1.337" | bc)
|
||||
|
||||
if [[ "$GITHUB_REPOSITORY" == "" ]]; then
|
||||
#Default
|
||||
BUILD_CORES=${BUILD_CORES:-8}
|
||||
BUILD_CORES=${BUILD_CORES:-8}
|
||||
fi
|
||||
|
||||
# Ensure still works outside of GH Actions by setting these to /dev/null
|
||||
@@ -31,21 +31,19 @@ echo "-- GITHUB_SHA: $GITHUB_SHA"
|
||||
echo "-- GITHUB_RUN_NUMBER: $GITHUB_RUN_NUMBER"
|
||||
echo "-- CONTAINER_NAME: $CONTAINER_NAME"
|
||||
|
||||
which docker 2> /dev/null 2> /dev/null
|
||||
if [ "$?" -eq "1" ]
|
||||
then
|
||||
which docker 2>/dev/null 2>/dev/null
|
||||
if [ "$?" -eq "1" ]; then
|
||||
echo 'Docker not found. Install it first.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
stat .git 2> /dev/null 2> /dev/null
|
||||
if [ "$?" -eq "1" ]
|
||||
then
|
||||
stat .git 2>/dev/null 2>/dev/null
|
||||
if [ "$?" -eq "1" ]; then
|
||||
echo 'Run this inside the source directory. (.git dir not found).'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
STATIC_CONTAINER=$(docker ps -a | grep $CONTAINER_NAME |wc -l)
|
||||
STATIC_CONTAINER=$(docker ps -a | grep $CONTAINER_NAME | wc -l)
|
||||
|
||||
CACHE_VOLUME_NAME="xahau-release-builder-cache"
|
||||
|
||||
@@ -57,13 +55,14 @@ if false; then
|
||||
docker stop $CONTAINER_NAME
|
||||
else
|
||||
echo "No static container, build on temp container"
|
||||
rm -rf release-build;
|
||||
mkdir -p release-build;
|
||||
rm -rf release-build
|
||||
mkdir -p release-build
|
||||
|
||||
docker volume create $CACHE_VOLUME_NAME
|
||||
|
||||
# Create inline Dockerfile with environment setup for build-full.sh
|
||||
DOCKERFILE_CONTENT=$(cat <<'DOCKERFILE_EOF'
|
||||
DOCKERFILE_CONTENT=$(
|
||||
cat <<'DOCKERFILE_EOF'
|
||||
FROM ghcr.io/phusion/holy-build-box:4.0.1-amd64
|
||||
|
||||
ARG BUILD_CORES=8
|
||||
@@ -218,7 +217,7 @@ RUN /hbb_exe/activate-exec bash -c "ccache -M 100G && \
|
||||
ln -s ../../bin/ccache /usr/lib64/ccache/c++"
|
||||
|
||||
DOCKERFILE_EOF
|
||||
)
|
||||
)
|
||||
|
||||
# Build custom Docker image
|
||||
IMAGE_NAME="xahaud-builder:latest"
|
||||
@@ -228,14 +227,14 @@ DOCKERFILE_EOF
|
||||
if [[ "$GITHUB_REPOSITORY" == "" ]]; then
|
||||
# Non GH, local building
|
||||
echo "Non-GH runner, local building, temp container"
|
||||
docker run -i --user 0:$(id -g) --rm -v /data/builds:/data/builds -v `pwd`:/io -v "$CACHE_VOLUME_NAME":/cache --network host "$IMAGE_NAME" /hbb_exe/activate-exec bash -c "source /opt/rh/gcc-toolset-11/enable && bash -x /io/build-full.sh '$GITHUB_REPOSITORY' '$GITHUB_SHA' '$BUILD_CORES' '$GITHUB_RUN_NUMBER'"
|
||||
docker run -i --user 0:$(id -g) --rm -v /data/builds:/data/builds -v $(pwd):/io -v "$CACHE_VOLUME_NAME":/cache --network host "$IMAGE_NAME" /hbb_exe/activate-exec bash -c "source /opt/rh/gcc-toolset-11/enable && bash -x /io/build-full.sh '$GITHUB_REPOSITORY' '$GITHUB_SHA' '$BUILD_CORES' '$GITHUB_RUN_NUMBER'"
|
||||
else
|
||||
# GH Action, runner
|
||||
echo "GH Action, runner, clean & re-create create persistent container"
|
||||
docker rm -f $CONTAINER_NAME
|
||||
echo "echo 'Stopping container: $CONTAINER_NAME'" >> "$JOB_CLEANUP_SCRIPT"
|
||||
echo "docker stop --time=15 \"$CONTAINER_NAME\" || echo 'Failed to stop container or container not running'" >> "$JOB_CLEANUP_SCRIPT"
|
||||
docker run -di --user 0:$(id -g) --name $CONTAINER_NAME -v /data/builds:/data/builds -v `pwd`:/io -v "$CACHE_VOLUME_NAME":/cache --network host "$IMAGE_NAME" /hbb_exe/activate-exec bash
|
||||
echo "echo 'Stopping container: $CONTAINER_NAME'" >>"$JOB_CLEANUP_SCRIPT"
|
||||
echo "docker stop --time=15 \"$CONTAINER_NAME\" || echo 'Failed to stop container or container not running'" >>"$JOB_CLEANUP_SCRIPT"
|
||||
docker run -di --user 0:$(id -g) --name $CONTAINER_NAME -v /data/builds:/data/builds -v $(pwd):/io -v "$CACHE_VOLUME_NAME":/cache --network host "$IMAGE_NAME" /hbb_exe/activate-exec bash
|
||||
docker exec -i $CONTAINER_NAME /hbb_exe/activate-exec bash -c "source /opt/rh/gcc-toolset-11/enable && bash -x /io/build-full.sh '$GITHUB_REPOSITORY' '$GITHUB_SHA' '$BUILD_CORES' '$GITHUB_RUN_NUMBER'"
|
||||
docker stop $CONTAINER_NAME
|
||||
fi
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -66,7 +66,7 @@ invalidAMMAsset(
|
||||
Issue const& issue,
|
||||
std::optional<std::pair<Issue, Issue>> const& pair)
|
||||
{
|
||||
if (badCurrency() == issue.currency)
|
||||
if (isBadCurrency(issue.currency))
|
||||
return temBAD_CURRENCY;
|
||||
if (isXRP(issue) && issue.account.isNonZero())
|
||||
return temBAD_ISSUER;
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -250,12 +250,9 @@ FeatureCollections::registerFeature(
|
||||
Feature const* i = getByName(name);
|
||||
if (!i)
|
||||
{
|
||||
// If this check fails, and you just added a feature, increase the
|
||||
// numFeatures value in Feature.h
|
||||
check(
|
||||
features.size() < detail::numFeatures,
|
||||
"More features defined than allocated. Adjust numFeatures in "
|
||||
"Feature.h.");
|
||||
"More features defined than allocated.");
|
||||
|
||||
auto const f = sha512Half(Slice(name.data(), name.size()));
|
||||
|
||||
@@ -424,45 +421,26 @@ featureToName(uint256 const& f)
|
||||
#undef XRPL_FEATURE
|
||||
#pragma push_macro("XRPL_FIX")
|
||||
#undef XRPL_FIX
|
||||
#pragma push_macro("XRPL_RETIRE")
|
||||
#undef XRPL_RETIRE
|
||||
|
||||
#define XRPL_FEATURE(name, supported, vote) \
|
||||
uint256 const feature##name = registerFeature(#name, supported, vote);
|
||||
#define XRPL_FIX(name, supported, vote) \
|
||||
uint256 const fix##name = registerFeature("fix" #name, supported, vote);
|
||||
#define XRPL_RETIRE(name) \
|
||||
[[deprecated("The referenced amendment has been retired"), maybe_unused]] \
|
||||
uint256 const retired##name = retireFeature(#name);
|
||||
|
||||
#include <xrpl/protocol/detail/features.macro>
|
||||
|
||||
#undef XRPL_RETIRE
|
||||
#pragma pop_macro("XRPL_RETIRE")
|
||||
#undef XRPL_FIX
|
||||
#pragma pop_macro("XRPL_FIX")
|
||||
#undef XRPL_FEATURE
|
||||
#pragma pop_macro("XRPL_FEATURE")
|
||||
|
||||
// clang-format off
|
||||
|
||||
// The following amendments have been active for at least two years. Their
|
||||
// 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
|
||||
[[deprecated("The referenced amendment has been retired"), maybe_unused]]
|
||||
uint256 const
|
||||
retiredMultiSign = retireFeature("MultiSign"),
|
||||
retiredTrustSetAuth = retireFeature("TrustSetAuth"),
|
||||
retiredFeeEscalation = retireFeature("FeeEscalation"),
|
||||
retiredPayChan = retireFeature("PayChan"),
|
||||
retiredCryptoConditions = retireFeature("CryptoConditions"),
|
||||
retiredTickSize = retireFeature("TickSize"),
|
||||
retiredFix1368 = retireFeature("fix1368"),
|
||||
retiredEscrow = retireFeature("Escrow"),
|
||||
retiredFix1373 = retireFeature("fix1373"),
|
||||
retiredEnforceInvariants = retireFeature("EnforceInvariants"),
|
||||
retiredSortedDirectories = retireFeature("SortedDirectories"),
|
||||
retiredFix1201 = retireFeature("fix1201"),
|
||||
retiredFix1512 = retireFeature("fix1512"),
|
||||
retiredFix1523 = retireFeature("fix1523"),
|
||||
retiredFix1528 = retireFeature("fix1528");
|
||||
|
||||
// clang-format on
|
||||
|
||||
// All of the features should now be registered, since variables in a cpp file
|
||||
// are initialized from top to bottom.
|
||||
//
|
||||
|
||||
@@ -89,6 +89,7 @@ enum class LedgerNameSpace : std::uint16_t {
|
||||
ORACLE = LEDGER_NAMESPACE2(0x01, 'R'),
|
||||
MPTOKEN_ISSUANCE = '~',
|
||||
MPTOKEN = 't',
|
||||
MANIFEST = 'M',
|
||||
CREDENTIAL = LEDGER_NAMESPACE2(0x01, 'D'),
|
||||
PERMISSIONED_DOMAIN = 'm',
|
||||
|
||||
@@ -670,6 +671,12 @@ permissionedDomain(uint256 const& domainID) noexcept
|
||||
return {ltPERMISSIONED_DOMAIN, domainID};
|
||||
}
|
||||
|
||||
Keylet
|
||||
manifest(PublicKey const& pk) noexcept
|
||||
{
|
||||
return {ltMANIFEST, indexHash(LedgerNameSpace::MANIFEST, pk.slice())};
|
||||
}
|
||||
|
||||
} // namespace keylet
|
||||
|
||||
} // namespace ripple
|
||||
@@ -99,6 +99,7 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfHookOnOutgoing, soeOPTIONAL},
|
||||
{sfHookCanEmit, soeOPTIONAL},
|
||||
{sfHookApiVersion, soeOPTIONAL},
|
||||
{sfHookName, soeOPTIONAL},
|
||||
{sfFlags, soeOPTIONAL}});
|
||||
|
||||
add(sfHookGrant.jsonName,
|
||||
@@ -253,6 +254,24 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfIssuer, soeREQUIRED},
|
||||
{sfCredentialType, soeREQUIRED},
|
||||
});
|
||||
|
||||
add(sfHighReward.jsonName,
|
||||
sfHighReward.getCode(),
|
||||
{
|
||||
{sfRewardLgrFirst, soeREQUIRED},
|
||||
{sfRewardLgrLast, soeREQUIRED},
|
||||
{sfRewardTime, soeREQUIRED},
|
||||
{sfTrustLineRewardAccumulator, soeREQUIRED},
|
||||
});
|
||||
|
||||
add(sfLowReward.jsonName,
|
||||
sfLowReward.getCode(),
|
||||
{
|
||||
{sfRewardLgrFirst, soeREQUIRED},
|
||||
{sfRewardLgrLast, soeREQUIRED},
|
||||
{sfRewardTime, soeREQUIRED},
|
||||
{sfTrustLineRewardAccumulator, soeREQUIRED},
|
||||
});
|
||||
}
|
||||
|
||||
InnerObjectFormats const&
|
||||
|
||||
@@ -111,7 +111,7 @@ issueFromJson(Json::Value const& v)
|
||||
}
|
||||
|
||||
auto const currency = to_currency(curStr.asString());
|
||||
if (currency == badCurrency() || currency == noCurrency())
|
||||
if (isBadCurrency(currency) || currency == noCurrency())
|
||||
{
|
||||
Throw<Json::error>("issueFromJson currency must be a valid currency");
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ currencyFromJson(SField const& name, Json::Value const& v)
|
||||
}
|
||||
|
||||
auto const currency = to_currency(v.asString());
|
||||
if (currency == badCurrency() || currency == noCurrency())
|
||||
if (isBadCurrency(currency) || currency == noCurrency())
|
||||
{
|
||||
Throw<std::runtime_error>(
|
||||
"currencyFromJson currency must be a valid currency");
|
||||
|
||||
@@ -151,6 +151,8 @@ transResults()
|
||||
MAKE_ERROR(tefNONDIR_EMIT, "An emitted txn was injected into the ledger without a corresponding directory entry."),
|
||||
MAKE_ERROR(tefIMPORT_BLACKHOLED, "Cannot import keying because target account is blackholed."),
|
||||
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(telLOCAL_ERROR, "Local failure."),
|
||||
MAKE_ERROR(telBAD_DOMAIN, "Domain too long."),
|
||||
@@ -172,6 +174,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."),
|
||||
|
||||
@@ -48,6 +48,7 @@ TxFormats::TxFormats()
|
||||
{sfFirstLedgerSequence, soeOPTIONAL},
|
||||
{sfNetworkID, soeOPTIONAL},
|
||||
{sfHookParameters, soeOPTIONAL},
|
||||
{sfHookName, soeOPTIONAL},
|
||||
};
|
||||
|
||||
#pragma push_macro("UNWRAP")
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/algorithm/string/trim.hpp>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -205,7 +208,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&)
|
||||
{
|
||||
|
||||
@@ -32,9 +32,12 @@ class AMMClawback_test : public beast::unit_test::suite
|
||||
testcase("test invalid request");
|
||||
using namespace jtx;
|
||||
|
||||
auto const features =
|
||||
supported_amendments() | featureAMM | featureAMMClawback;
|
||||
|
||||
// Test if holder does not exist.
|
||||
{
|
||||
Env env(*this);
|
||||
Env env(*this, features);
|
||||
Account gw{"gateway"};
|
||||
Account alice{"alice"};
|
||||
env.fund(XRP(100000), gw, alice);
|
||||
@@ -60,7 +63,7 @@ class AMMClawback_test : public beast::unit_test::suite
|
||||
// Test if asset pair provided does not exist. This should
|
||||
// return terNO_AMM error.
|
||||
{
|
||||
Env env(*this);
|
||||
Env env(*this, features);
|
||||
Account gw{"gateway"};
|
||||
Account alice{"alice"};
|
||||
env.fund(XRP(100000), gw, alice);
|
||||
@@ -93,7 +96,7 @@ class AMMClawback_test : public beast::unit_test::suite
|
||||
// Test if the issuer field and holder field is the same. This should
|
||||
// return temMALFORMED error.
|
||||
{
|
||||
Env env(*this);
|
||||
Env env(*this, features);
|
||||
Account gw{"gateway"};
|
||||
Account alice{"alice"};
|
||||
env.fund(XRP(10000), gw, alice);
|
||||
@@ -123,7 +126,7 @@ class AMMClawback_test : public beast::unit_test::suite
|
||||
|
||||
// Test if the Asset field matches the Account field.
|
||||
{
|
||||
Env env(*this);
|
||||
Env env(*this, features);
|
||||
Account gw{"gateway"};
|
||||
Account alice{"alice"};
|
||||
env.fund(XRP(10000), gw, alice);
|
||||
@@ -155,7 +158,7 @@ class AMMClawback_test : public beast::unit_test::suite
|
||||
|
||||
// Test if the Amount field matches the Asset field.
|
||||
{
|
||||
Env env(*this);
|
||||
Env env(*this, features);
|
||||
Account gw{"gateway"};
|
||||
Account alice{"alice"};
|
||||
env.fund(XRP(10000), gw, alice);
|
||||
@@ -188,7 +191,7 @@ class AMMClawback_test : public beast::unit_test::suite
|
||||
|
||||
// Test if the Amount is invalid, which is less than zero.
|
||||
{
|
||||
Env env(*this);
|
||||
Env env(*this, features);
|
||||
Account gw{"gateway"};
|
||||
Account alice{"alice"};
|
||||
env.fund(XRP(10000), gw, alice);
|
||||
@@ -229,7 +232,7 @@ class AMMClawback_test : public beast::unit_test::suite
|
||||
// Test if the issuer did not set asfAllowTrustLineClawback, AMMClawback
|
||||
// transaction is prohibited.
|
||||
{
|
||||
Env env(*this);
|
||||
Env env(*this, features);
|
||||
Account gw{"gateway"};
|
||||
Account alice{"alice"};
|
||||
env.fund(XRP(10000), gw, alice);
|
||||
@@ -254,7 +257,7 @@ class AMMClawback_test : public beast::unit_test::suite
|
||||
|
||||
// Test invalid flag.
|
||||
{
|
||||
Env env(*this);
|
||||
Env env(*this, features);
|
||||
Account gw{"gateway"};
|
||||
Account alice{"alice"};
|
||||
env.fund(XRP(10000), gw, alice);
|
||||
@@ -282,7 +285,7 @@ class AMMClawback_test : public beast::unit_test::suite
|
||||
// Test if tfClawTwoAssets is set when the two assets in the AMM pool
|
||||
// are not issued by the same issuer.
|
||||
{
|
||||
Env env(*this);
|
||||
Env env(*this, features);
|
||||
Account gw{"gateway"};
|
||||
Account alice{"alice"};
|
||||
env.fund(XRP(10000), gw, alice);
|
||||
@@ -313,7 +316,7 @@ class AMMClawback_test : public beast::unit_test::suite
|
||||
|
||||
// Test clawing back XRP is being prohibited.
|
||||
{
|
||||
Env env(*this);
|
||||
Env env(*this, features);
|
||||
Account gw{"gateway"};
|
||||
Account alice{"alice"};
|
||||
env.fund(XRP(1000000), gw, alice);
|
||||
@@ -2085,7 +2088,8 @@ class AMMClawback_test : public beast::unit_test::suite
|
||||
void
|
||||
run() override
|
||||
{
|
||||
FeatureBitset const all{jtx::supported_amendments()};
|
||||
FeatureBitset const all{
|
||||
jtx::supported_amendments() | featureAMM | featureAMMClawback};
|
||||
|
||||
testInvalidRequest();
|
||||
testFeatureDisabled(all - featureAMMClawback);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -1403,7 +1406,8 @@ private:
|
||||
testOffers()
|
||||
{
|
||||
using namespace jtx;
|
||||
FeatureBitset const all{supported_amendments()};
|
||||
FeatureBitset const all{
|
||||
supported_amendments() | featureAMM | featureAMMClawback};
|
||||
testRmFundedOffer(all);
|
||||
testEnforceNoRipple(all);
|
||||
testFillModes(all);
|
||||
@@ -1989,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);
|
||||
@@ -2802,8 +2807,11 @@ private:
|
||||
testcase("limitQuality");
|
||||
using namespace jtx;
|
||||
|
||||
auto const features =
|
||||
supported_amendments() | featureAMM | featureAMMClawback;
|
||||
|
||||
{
|
||||
Env env(*this);
|
||||
Env env(*this, features);
|
||||
|
||||
fund(env, gw, {alice, bob, carol}, XRP(10'000), {USD(2'000)});
|
||||
|
||||
@@ -2829,11 +2837,13 @@ private:
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
auto const features =
|
||||
supported_amendments() | featureAMM | featureAMMClawback;
|
||||
|
||||
for (auto const withFix : {true, false})
|
||||
{
|
||||
auto const feats = withFix
|
||||
? supported_amendments()
|
||||
: supported_amendments() - FeatureBitset{fix1781};
|
||||
auto const feats =
|
||||
withFix ? features : features - FeatureBitset{fix1781};
|
||||
|
||||
// Payment path starting with XRP
|
||||
Env env(*this, feats);
|
||||
@@ -2861,7 +2871,7 @@ private:
|
||||
}
|
||||
{
|
||||
// Payment path ending with XRP
|
||||
Env env(*this);
|
||||
Env env(*this, features);
|
||||
// Note, if alice doesn't have default ripple, then pay fails
|
||||
// with tecPATH_DRY.
|
||||
fund(
|
||||
@@ -2885,7 +2895,7 @@ private:
|
||||
// Payment where loop is formed in the middle of the path, not
|
||||
// on an endpoint
|
||||
auto const JPY = gw["JPY"];
|
||||
Env env(*this);
|
||||
Env env(*this, features);
|
||||
// Note, if alice doesn't have default ripple, then pay fails
|
||||
// with tecPATH_DRY.
|
||||
fund(
|
||||
@@ -3113,7 +3123,10 @@ private:
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this);
|
||||
auto const features =
|
||||
supported_amendments() | featureAMM | featureAMMClawback;
|
||||
|
||||
Env env(*this, features);
|
||||
|
||||
fund(env, gw, {alice, bob, carol}, XRP(10'000));
|
||||
env.trust(USD(1'000), alice, bob, carol);
|
||||
@@ -3632,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,
|
||||
@@ -3828,7 +3841,8 @@ private:
|
||||
testFlow()
|
||||
{
|
||||
using namespace jtx;
|
||||
FeatureBitset const all{supported_amendments()};
|
||||
FeatureBitset const all{
|
||||
supported_amendments() | featureAMM | featureAMMClawback};
|
||||
FeatureBitset const ownerPaysFee{featureOwnerPaysFee};
|
||||
|
||||
testFalseDry(all);
|
||||
@@ -3844,7 +3858,8 @@ private:
|
||||
testCrossingLimits()
|
||||
{
|
||||
using namespace jtx;
|
||||
FeatureBitset const all{supported_amendments()};
|
||||
FeatureBitset const all{
|
||||
supported_amendments() | featureAMM | featureAMMClawback};
|
||||
testStepLimit(all);
|
||||
}
|
||||
|
||||
@@ -3852,14 +3867,16 @@ private:
|
||||
testDeliverMin()
|
||||
{
|
||||
using namespace jtx;
|
||||
FeatureBitset const all{supported_amendments()};
|
||||
FeatureBitset const all{
|
||||
supported_amendments() | featureAMM | featureAMMClawback};
|
||||
test_convert_all_of_an_asset(all);
|
||||
}
|
||||
|
||||
void
|
||||
testDepositAuth()
|
||||
{
|
||||
auto const supported{jtx::supported_amendments()};
|
||||
auto const supported{
|
||||
jtx::supported_amendments() | featureAMM | featureAMMClawback};
|
||||
testPayment(supported - featureDepositPreauth);
|
||||
testPayment(supported);
|
||||
testPayIOU();
|
||||
@@ -3869,7 +3886,8 @@ private:
|
||||
testFreeze()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
auto const sa = supported_amendments();
|
||||
auto const sa =
|
||||
supported_amendments() | featureAMM | featureAMMClawback;
|
||||
testRippleState(sa);
|
||||
testGlobalFreeze(sa);
|
||||
testOffersWhenFrozen(sa);
|
||||
@@ -3879,7 +3897,8 @@ private:
|
||||
testMultisign()
|
||||
{
|
||||
using namespace jtx;
|
||||
auto const all = supported_amendments();
|
||||
auto const all =
|
||||
supported_amendments() | featureAMM | featureAMMClawback;
|
||||
|
||||
testTxMultisign(
|
||||
all - featureMultiSignReserve - featureExpandedSignerList);
|
||||
@@ -3891,7 +3910,8 @@ private:
|
||||
testPayStrand()
|
||||
{
|
||||
using namespace jtx;
|
||||
auto const all = supported_amendments();
|
||||
auto const all =
|
||||
supported_amendments() | featureAMM | featureAMMClawback;
|
||||
|
||||
testToStrand(all);
|
||||
testRIPD1373(all);
|
||||
|
||||
+68
-43
@@ -56,6 +56,9 @@ private:
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
auto const features =
|
||||
supported_amendments() | featureAMM | featureAMMClawback;
|
||||
|
||||
// XRP to IOU
|
||||
testAMM([&](AMM& ammAlice, Env&) {
|
||||
BEAST_EXPECT(ammAlice.expectBalances(
|
||||
@@ -72,7 +75,7 @@ private:
|
||||
|
||||
// IOU to IOU + transfer fee
|
||||
{
|
||||
Env env{*this};
|
||||
Env env{*this, features};
|
||||
fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
|
||||
env(rate(gw, 1.25));
|
||||
env.close();
|
||||
@@ -86,7 +89,7 @@ private:
|
||||
|
||||
// Require authorization is set, account is authorized
|
||||
{
|
||||
Env env{*this};
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(30'000), gw, alice);
|
||||
env.close();
|
||||
env(fset(gw, asfRequireAuth));
|
||||
@@ -100,7 +103,7 @@ private:
|
||||
|
||||
// Cleared global freeze
|
||||
{
|
||||
Env env{*this};
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(30'000), gw, alice);
|
||||
env.close();
|
||||
env.trust(USD(30'000), alice);
|
||||
@@ -141,9 +144,12 @@ private:
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
auto const features =
|
||||
supported_amendments() | featureAMM | featureAMMClawback;
|
||||
|
||||
// Can't have both XRP tokens
|
||||
{
|
||||
Env env{*this};
|
||||
Env env{*this, features};
|
||||
fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
|
||||
AMM ammAlice(
|
||||
env, alice, XRP(10'000), XRP(10'000), ter(temBAD_AMM_TOKENS));
|
||||
@@ -152,7 +158,7 @@ private:
|
||||
|
||||
// Can't have both tokens the same IOU
|
||||
{
|
||||
Env env{*this};
|
||||
Env env{*this, features};
|
||||
fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
|
||||
AMM ammAlice(
|
||||
env, alice, USD(10'000), USD(10'000), ter(temBAD_AMM_TOKENS));
|
||||
@@ -161,7 +167,7 @@ private:
|
||||
|
||||
// Can't have zero or negative amounts
|
||||
{
|
||||
Env env{*this};
|
||||
Env env{*this, features};
|
||||
fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
|
||||
AMM ammAlice(env, alice, XRP(0), USD(10'000), ter(temBAD_AMOUNT));
|
||||
BEAST_EXPECT(!ammAlice.ammExists());
|
||||
@@ -177,7 +183,7 @@ private:
|
||||
|
||||
// Bad currency
|
||||
{
|
||||
Env env{*this};
|
||||
Env env{*this, features};
|
||||
fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
|
||||
AMM ammAlice(
|
||||
env, alice, XRP(10'000), BAD(10'000), ter(temBAD_CURRENCY));
|
||||
@@ -186,7 +192,7 @@ private:
|
||||
|
||||
// Insufficient IOU balance
|
||||
{
|
||||
Env env{*this};
|
||||
Env env{*this, features};
|
||||
fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
|
||||
AMM ammAlice(
|
||||
env, alice, XRP(10'000), USD(40'000), ter(tecUNFUNDED_AMM));
|
||||
@@ -195,7 +201,7 @@ private:
|
||||
|
||||
// Insufficient XRP balance
|
||||
{
|
||||
Env env{*this};
|
||||
Env env{*this, features};
|
||||
fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
|
||||
AMM ammAlice(
|
||||
env, alice, XRP(40'000), USD(10'000), ter(tecUNFUNDED_AMM));
|
||||
@@ -204,7 +210,7 @@ private:
|
||||
|
||||
// Invalid trading fee
|
||||
{
|
||||
Env env{*this};
|
||||
Env env{*this, features};
|
||||
fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
|
||||
AMM ammAlice(
|
||||
env,
|
||||
@@ -229,7 +235,7 @@ private:
|
||||
|
||||
// Invalid flags
|
||||
{
|
||||
Env env{*this};
|
||||
Env env{*this, features};
|
||||
fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
|
||||
AMM ammAlice(
|
||||
env,
|
||||
@@ -248,7 +254,7 @@ private:
|
||||
|
||||
// Invalid Account
|
||||
{
|
||||
Env env{*this};
|
||||
Env env{*this, features};
|
||||
Account bad("bad");
|
||||
env.memoize(bad);
|
||||
AMM ammAlice(
|
||||
@@ -268,7 +274,7 @@ private:
|
||||
|
||||
// Require authorization is set
|
||||
{
|
||||
Env env{*this};
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(30'000), gw, alice);
|
||||
env.close();
|
||||
env(fset(gw, asfRequireAuth));
|
||||
@@ -281,7 +287,7 @@ private:
|
||||
|
||||
// Globally frozen
|
||||
{
|
||||
Env env{*this};
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(30'000), gw, alice);
|
||||
env.close();
|
||||
env(fset(gw, asfGlobalFreeze));
|
||||
@@ -294,7 +300,7 @@ private:
|
||||
|
||||
// Individually frozen
|
||||
{
|
||||
Env env{*this};
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(30'000), gw, alice);
|
||||
env.close();
|
||||
env(trust(gw, alice["USD"](30'000)));
|
||||
@@ -307,7 +313,7 @@ private:
|
||||
|
||||
// Insufficient reserve, XRP/IOU
|
||||
{
|
||||
Env env(*this);
|
||||
Env env(*this, features);
|
||||
auto const starting_xrp =
|
||||
XRP(1'000) + reserve(env, 3) + env.current()->fees().base * 4;
|
||||
env.fund(starting_xrp, gw);
|
||||
@@ -324,7 +330,7 @@ private:
|
||||
|
||||
// Insufficient reserve, IOU/IOU
|
||||
{
|
||||
Env env(*this);
|
||||
Env env(*this, features);
|
||||
auto const starting_xrp =
|
||||
reserve(env, 4) + env.current()->fees().base * 5;
|
||||
env.fund(starting_xrp, gw);
|
||||
@@ -343,7 +349,7 @@ private:
|
||||
|
||||
// Insufficient fee
|
||||
{
|
||||
Env env(*this);
|
||||
Env env(*this, features);
|
||||
fund(env, gw, {alice}, XRP(2'000), {USD(2'000), EUR(2'000)});
|
||||
AMM ammAlice(
|
||||
env,
|
||||
@@ -394,7 +400,7 @@ private:
|
||||
|
||||
// Issuer has DefaultRipple disabled
|
||||
{
|
||||
Env env(*this);
|
||||
Env env(*this, features);
|
||||
env.fund(XRP(30'000), gw);
|
||||
env(fclear(gw, asfDefaultRipple));
|
||||
AMM ammGw(env, gw, XRP(10'000), USD(10'000), ter(terNO_RIPPLE));
|
||||
@@ -1119,7 +1125,7 @@ private:
|
||||
|
||||
// Insufficient reserve, XRP/IOU
|
||||
{
|
||||
Env env(*this);
|
||||
Env env(*this, features);
|
||||
auto const starting_xrp =
|
||||
reserve(env, 4) + env.current()->fees().base * 4;
|
||||
env.fund(XRP(10'000), gw);
|
||||
@@ -1154,7 +1160,7 @@ private:
|
||||
|
||||
// Insufficient reserve, IOU/IOU
|
||||
{
|
||||
Env env(*this);
|
||||
Env env(*this, features);
|
||||
auto const starting_xrp =
|
||||
reserve(env, 4) + env.current()->fees().base * 4;
|
||||
env.fund(XRP(10'000), gw);
|
||||
@@ -1356,7 +1362,8 @@ private:
|
||||
testcase("Deposit");
|
||||
|
||||
using namespace jtx;
|
||||
auto const all = supported_amendments();
|
||||
auto const all =
|
||||
supported_amendments() | featureAMM | featureAMMClawback;
|
||||
|
||||
// Equal deposit: 1000000 tokens, 10% of the current pool
|
||||
testAMM([&](AMM& ammAlice, Env& env) {
|
||||
@@ -1516,7 +1523,7 @@ private:
|
||||
|
||||
// IOU to IOU + transfer fee
|
||||
{
|
||||
Env env{*this};
|
||||
Env env{*this, all};
|
||||
fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
|
||||
env(rate(gw, 1.25));
|
||||
env.close();
|
||||
@@ -1657,7 +1664,8 @@ private:
|
||||
testcase("Invalid Withdraw");
|
||||
|
||||
using namespace jtx;
|
||||
auto const all = supported_amendments();
|
||||
auto const all =
|
||||
supported_amendments() | featureAMM | featureAMMClawback;
|
||||
|
||||
testAMM(
|
||||
[&](AMM& ammAlice, Env& env) {
|
||||
@@ -1680,7 +1688,7 @@ private:
|
||||
{{XRP(99), USD(99)}});
|
||||
|
||||
{
|
||||
Env env{*this};
|
||||
Env env{*this, all};
|
||||
env.fund(XRP(30'000), gw, alice, bob);
|
||||
env.close();
|
||||
env(fset(gw, asfRequireAuth));
|
||||
@@ -2228,7 +2236,8 @@ private:
|
||||
testcase("Withdraw");
|
||||
|
||||
using namespace jtx;
|
||||
auto const all = supported_amendments();
|
||||
auto const all =
|
||||
supported_amendments() | featureAMM | featureAMMClawback;
|
||||
|
||||
// Equal withdrawal by Carol: 1000000 of tokens, 10% of the current
|
||||
// pool
|
||||
@@ -2442,7 +2451,7 @@ private:
|
||||
|
||||
// IOU to IOU + transfer fee
|
||||
{
|
||||
Env env{*this};
|
||||
Env env{*this, all};
|
||||
fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
|
||||
env(rate(gw, 1.25));
|
||||
env.close();
|
||||
@@ -2605,7 +2614,8 @@ private:
|
||||
{
|
||||
testcase("Fee Vote");
|
||||
using namespace jtx;
|
||||
auto const all = supported_amendments();
|
||||
auto const all =
|
||||
supported_amendments() | featureAMM | featureAMMClawback;
|
||||
|
||||
// One vote sets fee to 1%.
|
||||
testAMM([&](AMM& ammAlice, Env& env) {
|
||||
@@ -2723,9 +2733,12 @@ private:
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
auto const features =
|
||||
supported_amendments() | featureAMM | featureAMMClawback;
|
||||
|
||||
// burn all the LPTokens through a AMMBid transaction
|
||||
{
|
||||
Env env(*this);
|
||||
Env env(*this, features);
|
||||
fund(env, gw, {alice}, XRP(2'000), {USD(2'000)});
|
||||
AMM amm(env, gw, XRP(1'000), USD(1'000), false, 1'000);
|
||||
|
||||
@@ -2744,7 +2757,7 @@ private:
|
||||
|
||||
// burn all the LPTokens through a AMMBid transaction
|
||||
{
|
||||
Env env(*this);
|
||||
Env env(*this, features);
|
||||
fund(env, gw, {alice}, XRP(2'000), {USD(2'000)});
|
||||
AMM amm(env, gw, XRP(1'000), USD(1'000), false, 1'000);
|
||||
|
||||
@@ -2922,7 +2935,7 @@ private:
|
||||
|
||||
// Bid all tokens, still own the slot
|
||||
{
|
||||
Env env(*this);
|
||||
Env env(*this, features);
|
||||
fund(env, gw, {alice, bob}, XRP(1'000), {USD(1'000)});
|
||||
AMM amm(env, gw, XRP(10), USD(1'000));
|
||||
auto const lpIssue = amm.lptIssue();
|
||||
@@ -3361,12 +3374,15 @@ private:
|
||||
using namespace std::chrono;
|
||||
using namespace std::literals::chrono_literals;
|
||||
|
||||
auto const features =
|
||||
supported_amendments() | featureAMM | featureAMMClawback;
|
||||
|
||||
// Can't pay into AMM account.
|
||||
// Can't pay out since there is no keys
|
||||
for (auto const& acct : {gw, alice})
|
||||
{
|
||||
{
|
||||
Env env(*this);
|
||||
Env env(*this, features);
|
||||
fund(env, gw, {alice, carol}, XRP(1'000), {USD(100)});
|
||||
// XRP balance is below reserve
|
||||
AMM ammAlice(env, acct, XRP(10), USD(10));
|
||||
@@ -3381,7 +3397,7 @@ private:
|
||||
ter(tecNO_PERMISSION));
|
||||
}
|
||||
{
|
||||
Env env(*this);
|
||||
Env env(*this, features);
|
||||
fund(env, gw, {alice, carol}, XRP(10'000'000), {USD(10'000)});
|
||||
// XRP balance is above reserve
|
||||
AMM ammAlice(env, acct, XRP(1'000'000), USD(100));
|
||||
@@ -4417,7 +4433,8 @@ private:
|
||||
{
|
||||
testcase("Amendment");
|
||||
using namespace jtx;
|
||||
FeatureBitset const all{supported_amendments()};
|
||||
FeatureBitset const all{
|
||||
supported_amendments() | featureAMM | featureAMMClawback};
|
||||
FeatureBitset const noAMM{all - featureAMM};
|
||||
FeatureBitset const noNumber{all - fixUniversalNumber};
|
||||
FeatureBitset const noAMMAndNumber{
|
||||
@@ -4467,6 +4484,9 @@ private:
|
||||
testcase("Rippling");
|
||||
using namespace jtx;
|
||||
|
||||
auto const features =
|
||||
supported_amendments() | featureAMM | featureAMMClawback;
|
||||
|
||||
// Rippling via AMM fails because AMM trust line has 0 limit.
|
||||
// Set up two issuers, A and B. Have each issue a token called TST.
|
||||
// Have another account C hold TST from both issuers,
|
||||
@@ -4478,7 +4498,7 @@ private:
|
||||
// to shift at a 1:1 rate with no fee applied has it not been
|
||||
// for 0 limit.
|
||||
{
|
||||
Env env(*this);
|
||||
Env env(*this, features);
|
||||
auto const A = Account("A");
|
||||
auto const B = Account("B");
|
||||
auto const TSTA = A["TST"];
|
||||
@@ -4984,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,
|
||||
@@ -5061,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,
|
||||
@@ -5075,7 +5095,8 @@ private:
|
||||
testcase("Auto Delete");
|
||||
|
||||
using namespace jtx;
|
||||
FeatureBitset const all{supported_amendments()};
|
||||
FeatureBitset const all{
|
||||
supported_amendments() | featureAMM | featureAMMClawback};
|
||||
|
||||
{
|
||||
Env env(
|
||||
@@ -5187,7 +5208,9 @@ private:
|
||||
{
|
||||
testcase("Clawback");
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
auto const features =
|
||||
supported_amendments() | featureAMM | featureAMMClawback;
|
||||
Env env(*this, features);
|
||||
env.fund(XRP(2'000), gw);
|
||||
env.fund(XRP(2'000), alice);
|
||||
AMM amm(env, gw, XRP(1'000), USD(1'000));
|
||||
@@ -5586,7 +5609,8 @@ private:
|
||||
{
|
||||
testcase("Fix Default Inner Object");
|
||||
using namespace jtx;
|
||||
FeatureBitset const all{supported_amendments()};
|
||||
FeatureBitset const all{
|
||||
supported_amendments() | featureAMM | featureAMMClawback};
|
||||
|
||||
auto test = [&](FeatureBitset features,
|
||||
TER const& err1,
|
||||
@@ -6305,7 +6329,7 @@ private:
|
||||
{{xrpPool, iouPool}},
|
||||
889,
|
||||
std::nullopt,
|
||||
{jtx::supported_amendments()});
|
||||
{jtx::supported_amendments() | featureAMM | featureAMMClawback});
|
||||
}
|
||||
|
||||
void
|
||||
@@ -7121,7 +7145,8 @@ private:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
FeatureBitset const all{jtx::supported_amendments()};
|
||||
FeatureBitset const all{
|
||||
jtx::supported_amendments() | featureAMM | featureAMMClawback};
|
||||
testInvalidInstance();
|
||||
testInstanceCreate();
|
||||
testInvalidDeposit(all);
|
||||
|
||||
@@ -88,9 +88,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
auto tx = fset(account, asfTshCollect);
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
testRPCCall(env, tx, "16");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -112,9 +110,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
auto tx = acctdelete(account, bene);
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "200000" : "200000";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
testRPCCall(env, tx, "200000");
|
||||
}
|
||||
|
||||
static uint256
|
||||
@@ -142,9 +138,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
auto tx = check::cancel(account, checkId);
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
testRPCCall(env, tx, "16");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -167,9 +161,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
auto tx = check::cash(dest, checkId, XRP(100));
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
testRPCCall(env, tx, "16");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -191,9 +183,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
auto tx = check::create(account, dest, XRP(100));
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
testRPCCall(env, tx, "16");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -214,9 +204,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
auto tx = reward::claim(account);
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
testRPCCall(env, tx, "16");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -238,9 +226,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
auto tx = deposit::auth(account, authed);
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
testRPCCall(env, tx, "16");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -262,9 +248,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
auto tx = cancel(account, account, seq1);
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
testRPCCall(env, tx, "16");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -286,8 +270,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
auto tx = escrow(account, dest, XRP(10));
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
|
||||
std::string const feeResult = "16";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
}
|
||||
|
||||
@@ -310,9 +293,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
auto tx = finish(account, account, seq1);
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
testRPCCall(env, tx, "16");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -334,9 +315,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
account, import::loadXpop(ImportTCAccountSet::w_seed));
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "106" : "100";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
testRPCCall(env, tx, "106");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -357,9 +336,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
auto tx = invoke::invoke(account);
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "16" : "16";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
testRPCCall(env, tx, "16");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -381,9 +358,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
auto tx = offer_cancel(account, offerSeq);
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
testRPCCall(env, tx, "16");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -406,9 +381,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
auto tx = offer(account, USD(1000), XRP(1000));
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
testRPCCall(env, tx, "16");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -430,9 +403,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
auto tx = pay(account, dest, XRP(1));
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
testRPCCall(env, tx, "16");
|
||||
}
|
||||
|
||||
static uint256
|
||||
@@ -468,9 +439,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
auto tx = paychan::claim(account, chan, reqBal, authAmt);
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
testRPCCall(env, tx, "16");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -494,9 +463,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
auto tx = paychan::create(account, dest, XRP(10), settleDelay, pk);
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
testRPCCall(env, tx, "16");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -519,9 +486,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
auto tx = paychan::fund(account, chan, XRP(1));
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
testRPCCall(env, tx, "16");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -552,9 +517,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
hookParams[jss::HookParameter][jss::HookParameterValue] = "DEADBEEF";
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "73022" : "73016";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
testRPCCall(env, tx, "73022");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -576,9 +539,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
auto tx = regkey(account, dest);
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "0" : "0";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
testRPCCall(env, tx, "0");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -601,9 +562,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
auto tx = signers(account, 2, {{signer1, 1}, {signer2, 1}});
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
testRPCCall(env, tx, "16");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -624,9 +583,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
auto tx = ticket::create(account, 2);
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
testRPCCall(env, tx, "16");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -649,9 +606,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
auto tx = trust(account, USD(1000));
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
testRPCCall(env, tx, "16");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -675,9 +630,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
auto tx = uritoken::burn(issuer, hexid);
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
testRPCCall(env, tx, "16");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -702,9 +655,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
tx[jss::Amount] = "1000000";
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
testRPCCall(env, tx, "16");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -728,9 +679,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
auto tx = uritoken::cancel(issuer, hexid);
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
testRPCCall(env, tx, "16");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -757,8 +706,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
tx[jss::Amount] = "1000000";
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
|
||||
std::string const feeResult = "16";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
}
|
||||
|
||||
@@ -781,9 +729,7 @@ class BaseFee_test : public beast::unit_test::suite
|
||||
auto tx = uritoken::mint(account, uri);
|
||||
|
||||
// verify hooks fee
|
||||
std::string const feeResult =
|
||||
env.current()->rules().enabled(fixXahauV1) ? "16" : "10";
|
||||
testRPCCall(env, tx, feeResult);
|
||||
testRPCCall(env, tx, "16");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -827,7 +773,6 @@ public:
|
||||
using namespace test::jtx;
|
||||
auto const sa = supported_amendments();
|
||||
testWithFeats(sa);
|
||||
testWithFeats(sa - fixXahauV1);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -27,6 +27,14 @@ namespace ripple {
|
||||
namespace test {
|
||||
struct ClaimReward_test : public beast::unit_test::suite
|
||||
{
|
||||
private:
|
||||
// helper
|
||||
void static overrideFlag(Json::Value& jv)
|
||||
{
|
||||
jv[jss::Flags] = hsfOVERRIDE;
|
||||
}
|
||||
|
||||
public:
|
||||
bool
|
||||
expectRewards(
|
||||
jtx::Env const& env,
|
||||
@@ -60,6 +68,52 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
expectRewardsIOU(
|
||||
jtx::Env const& env,
|
||||
jtx::Account const& acct,
|
||||
jtx::IOU const& iou,
|
||||
std::uint32_t ledgerFirst,
|
||||
std::uint32_t ledgerLast,
|
||||
STAmount accumulator,
|
||||
std::uint32_t time)
|
||||
{
|
||||
auto const sle = env.le(keylet::line(acct, iou.account, iou.currency));
|
||||
BEAST_EXPECT(!!sle);
|
||||
auto const& sfRewardField =
|
||||
std::minmax(acct.id(), iou.account.id()).first == acct.id()
|
||||
? sfLowReward
|
||||
: sfHighReward;
|
||||
|
||||
if (!sle->isFieldPresent(sfRewardField))
|
||||
return false;
|
||||
|
||||
auto const& reward =
|
||||
static_cast<STObject const&>(sle->peekAtField(sfRewardField));
|
||||
|
||||
if (!reward.isFieldPresent(sfRewardLgrFirst) ||
|
||||
reward.getFieldU32(sfRewardLgrFirst) != ledgerFirst)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!reward.isFieldPresent(sfRewardLgrLast) ||
|
||||
reward.getFieldU32(sfRewardLgrLast) != ledgerLast)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!reward.isFieldPresent(sfTrustLineRewardAccumulator) ||
|
||||
reward.getFieldAmount(sfTrustLineRewardAccumulator) != accumulator)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!reward.isFieldPresent(sfRewardTime) ||
|
||||
reward.getFieldU32(sfRewardTime) != time)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
expectNoRewards(jtx::Env const& env, jtx::Account const& acct)
|
||||
{
|
||||
@@ -83,6 +137,24 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
expectNoRewardsIOU(
|
||||
jtx::Env const& env,
|
||||
jtx::Account const& acct,
|
||||
jtx::IOU const& iou)
|
||||
{
|
||||
auto const sle = env.le(keylet::line(acct, iou.account, iou.currency));
|
||||
BEAST_EXPECT(!!sle);
|
||||
auto const& sfRewardField =
|
||||
std::minmax(acct.id(), iou.account.id()).first == acct.id()
|
||||
? sfLowReward
|
||||
: sfHighReward;
|
||||
|
||||
if (sle->isFieldPresent(sfRewardField))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
testEnabled(FeatureBitset features)
|
||||
{
|
||||
@@ -92,7 +164,7 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
|
||||
// setup env
|
||||
auto const alice = Account("alice");
|
||||
auto const issuer = Account("issuer");
|
||||
auto const issuer = Account::master;
|
||||
|
||||
for (bool const withClaimReward : {false, true})
|
||||
{
|
||||
@@ -102,7 +174,11 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
withClaimReward ? features : features - featureBalanceRewards;
|
||||
Env env{*this, amend};
|
||||
|
||||
env.fund(XRP(1000), alice, issuer);
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
auto const txResult =
|
||||
@@ -119,7 +195,10 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
.count();
|
||||
|
||||
// CLAIM
|
||||
env(reward::claim(alice), reward::issuer(issuer), txResult);
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
fee(XRP(1)),
|
||||
txResult);
|
||||
env.close();
|
||||
|
||||
if (withClaimReward)
|
||||
@@ -195,14 +274,19 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
Env env{*this, amend};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const issuer = Account("issuer");
|
||||
auto const issuer = Account::master;
|
||||
|
||||
env.fund(XRP(1000), alice, issuer);
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
auto tx = reward::claim(alice);
|
||||
env(tx,
|
||||
reward::issuer(issuer),
|
||||
fee(XRP(1)),
|
||||
txflags(tfFullyCanonicalSig),
|
||||
withFixFlags ? ter(tesSUCCESS) : ter(temINVALID_FLAG));
|
||||
env.close();
|
||||
@@ -221,6 +305,105 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
env(reward::claim(alice), reward::issuer(alice), ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// featureIOURewardClaim
|
||||
|
||||
// temDISABLED
|
||||
// featureIOURewardClaim amendment is disabled
|
||||
{
|
||||
test::jtx::Env env{
|
||||
*this,
|
||||
network::makeNetworkConfig(21337),
|
||||
features - featureIOURewardClaim};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const gw = Account("gw");
|
||||
env.fund(XRP(1000), alice, bob, gw);
|
||||
env.close();
|
||||
|
||||
jtx::IOU const USD = gw["USD"];
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(bob),
|
||||
reward::claimCurrency(USD),
|
||||
ter(temDISABLED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// temMALFORMED
|
||||
// ClaimCurrency.account cannot be the source account.
|
||||
{
|
||||
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
jtx::IOU const USD = alice["USD"];
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(bob),
|
||||
reward::claimCurrency(USD),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// temMALFORMED
|
||||
// Issuer cannot be Genesis account if ClaimCurrency is set.
|
||||
{
|
||||
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const gw = Account("gw");
|
||||
env.fund(XRP(1000), alice, gw);
|
||||
env.close();
|
||||
|
||||
jtx::IOU const USD = gw["USD"];
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(Account::master),
|
||||
reward::claimCurrency(USD),
|
||||
ter(temBAD_ISSUER));
|
||||
}
|
||||
|
||||
// MPT
|
||||
{
|
||||
// tested in testMPTInvalidInTx() at MPToken_test.cpp
|
||||
}
|
||||
|
||||
// XAH RewardClaim: Issuer must be the Genesis account if
|
||||
// featureXahauGenesis and featureIOURewardClaim are enabled.
|
||||
for (bool const withIOURewardClaim : {false, true})
|
||||
{
|
||||
auto const amend = withIOURewardClaim
|
||||
? features
|
||||
: features - featureIOURewardClaim;
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const badIssuer = Account("gw");
|
||||
auto const issuer = Account::master;
|
||||
auto const USD = badIssuer["USD"];
|
||||
|
||||
Env env{*this, amend};
|
||||
env.fund(XRP(1000), alice, badIssuer);
|
||||
env.close();
|
||||
|
||||
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(badIssuer),
|
||||
fee(XRP(1)),
|
||||
withIOURewardClaim ? ter(temBAD_ISSUER) : ter(tesSUCCESS));
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -244,6 +427,7 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
auto const alice = Account("alice");
|
||||
auto const issuer = Account("issuer");
|
||||
env.memoize(alice);
|
||||
auto USD = issuer["USD"];
|
||||
|
||||
env.fund(XRP(1000), issuer);
|
||||
env.close();
|
||||
@@ -251,7 +435,10 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
auto tx = reward::claim(alice);
|
||||
tx[jss::Sequence] = 0;
|
||||
tx[jss::Fee] = 10;
|
||||
env(tx, reward::issuer(issuer), ter(terNO_ACCOUNT));
|
||||
env(tx,
|
||||
reward::issuer(issuer),
|
||||
reward::claimCurrency(USD),
|
||||
ter(terNO_ACCOUNT));
|
||||
env.close();
|
||||
}
|
||||
|
||||
@@ -261,9 +448,9 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const issuer = Account("issuer");
|
||||
auto const issuer = Account::master;
|
||||
|
||||
env.fund(XRP(1000), alice, issuer);
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
@@ -294,18 +481,26 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
auto const issuer = Account("issuer");
|
||||
env.memoize(issuer);
|
||||
|
||||
auto USD = issuer["USD"];
|
||||
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
auto tx = reward::claim(alice);
|
||||
env(tx, reward::issuer(issuer), ter(tecNO_ISSUER));
|
||||
env(tx,
|
||||
reward::issuer(issuer),
|
||||
reward::claimCurrency(USD),
|
||||
ter(tecNO_ISSUER));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// tecNO_PERMISSION
|
||||
// issuer is an AMM account
|
||||
{
|
||||
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
|
||||
test::jtx::Env env{
|
||||
*this,
|
||||
network::makeNetworkConfig(21337),
|
||||
features | featureAMM};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const issuer = Account("issuer");
|
||||
@@ -320,9 +515,176 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(amm.ammAccount()),
|
||||
reward::claimCurrency(USD),
|
||||
ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// tecNO_TARGET
|
||||
// no claim reward hook
|
||||
{
|
||||
Env env{*this};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const issuer = Account::master;
|
||||
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
// Doesn't have hook
|
||||
{
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
ter(tecNO_TARGET));
|
||||
env.close();
|
||||
}
|
||||
// Invalid HookOn
|
||||
{
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook, overrideFlag);
|
||||
hookObj[jss::HookOn] = to_string(UINT256_BIT[ttCLAIM_REWARD]);
|
||||
env(hook(issuer, {{hookObj}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
ter(tecNO_TARGET));
|
||||
env.close();
|
||||
}
|
||||
// Invalid IncomingHookOn
|
||||
{
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook, overrideFlag);
|
||||
hookObj.removeMember(jss::HookOn);
|
||||
hookObj[jss::HookOnIncoming] =
|
||||
to_string(UINT256_BIT[ttCLAIM_REWARD]);
|
||||
hookObj[jss::HookOnOutgoing] = to_string(uint256{});
|
||||
env(hook(issuer, {{hookObj}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
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();
|
||||
}
|
||||
// Vaild HookOn
|
||||
{
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook, overrideFlag);
|
||||
hookObj[jss::HookOn] = to_string(~UINT256_BIT[ttCLAIM_REWARD]);
|
||||
env(hook(issuer, {{hookObj}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
}
|
||||
// Vaild IncomingHookOn
|
||||
{
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook, overrideFlag);
|
||||
hookObj.removeMember(jss::HookOn);
|
||||
hookObj[jss::HookOnIncoming] =
|
||||
to_string(~UINT256_BIT[ttCLAIM_REWARD]);
|
||||
hookObj[jss::HookOnOutgoing] = to_string(uint256{});
|
||||
env(hook(issuer, {{hookObj}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
}
|
||||
// Invalid Hooks Array
|
||||
{
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook, overrideFlag);
|
||||
hookObj[jss::HookOn] = to_string(UINT256_BIT[ttCLAIM_REWARD]);
|
||||
env(hook(
|
||||
issuer,
|
||||
{{
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
}},
|
||||
0),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
fee(XRP(1)),
|
||||
ter(tecNO_TARGET));
|
||||
}
|
||||
// Vaild Hooks Array
|
||||
{
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook, overrideFlag);
|
||||
hookObj[jss::HookOn] = to_string(UINT256_BIT[ttCLAIM_REWARD]);
|
||||
auto hookObj2 = hso(jtx::genesis::AcceptHook, overrideFlag);
|
||||
hookObj2[jss::HookOn] = to_string(~UINT256_BIT[ttCLAIM_REWARD]);
|
||||
env(hook(
|
||||
issuer,
|
||||
{{
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj,
|
||||
hookObj2,
|
||||
}},
|
||||
0),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
}
|
||||
}
|
||||
|
||||
// tecNO_LINE
|
||||
// trustline does not exist.
|
||||
{
|
||||
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
|
||||
auto const gw = Account("gw");
|
||||
env.fund(XRP(1000), alice, gw);
|
||||
env.close();
|
||||
|
||||
env(hook(gw, {{hso(jtx::genesis::AcceptHook)}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
jtx::IOU const USD = gw["USD"];
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(gw),
|
||||
reward::claimCurrency(USD),
|
||||
fee(XRP(1)),
|
||||
ter(tecNO_LINE));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -335,9 +697,17 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const gw = Account("gw");
|
||||
auto const issuer = Account("issuer");
|
||||
|
||||
env.fund(XRP(1000), alice, issuer);
|
||||
env.fund(XRP(1000), alice, bob, gw, issuer);
|
||||
env.close();
|
||||
|
||||
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
env(hook(Account::master, {{hso(jtx::genesis::AcceptHook)}}, 0),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
// test claim rewards - no opt out
|
||||
@@ -352,7 +722,7 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
.count();
|
||||
|
||||
auto tx = reward::claim(alice);
|
||||
env(tx, reward::issuer(issuer), ter(tesSUCCESS));
|
||||
env(tx, reward::issuer(Account::master), fee(XRP(1)), ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(
|
||||
@@ -365,6 +735,51 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(expectNoRewards(env, alice) == true);
|
||||
|
||||
// test iou claim rewards
|
||||
{
|
||||
// set trustline
|
||||
env(trust(bob, gw["USD"](10000)));
|
||||
env.close();
|
||||
|
||||
// opt in
|
||||
auto const currentLedger = env.current()->seq();
|
||||
auto const currentTime =
|
||||
std::chrono::duration_cast<std::chrono::seconds>(
|
||||
env.app()
|
||||
.getLedgerMaster()
|
||||
.getValidatedLedger()
|
||||
->info()
|
||||
.parentCloseTime.time_since_epoch())
|
||||
.count();
|
||||
|
||||
auto tx = reward::claim(bob);
|
||||
env(tx,
|
||||
reward::issuer(issuer),
|
||||
reward::claimCurrency(gw["USD"]),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(
|
||||
expectRewardsIOU(
|
||||
env,
|
||||
bob,
|
||||
gw["USD"],
|
||||
currentLedger,
|
||||
currentLedger,
|
||||
gw["USD"](0),
|
||||
currentTime) == true);
|
||||
|
||||
// opt out
|
||||
env(reward::claim(bob),
|
||||
reward::claimCurrency(gw["USD"]),
|
||||
txflags(tfOptOut),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(expectNoRewardsIOU(env, bob, gw["USD"]) == true);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -375,16 +790,20 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
using namespace std::literals::chrono_literals;
|
||||
Env env{*this, features};
|
||||
auto const alice = Account("alice");
|
||||
auto const issuer = Account("issuer");
|
||||
env.fund(XRP(10000), alice, issuer);
|
||||
auto const issuer = Account::master;
|
||||
env.fund(XRP(10000), alice);
|
||||
std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
|
||||
env(ticket::create(alice, 10));
|
||||
std::uint32_t const aliceSeq{env.seq(alice)};
|
||||
env.require(owners(alice, 10));
|
||||
|
||||
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(reward::claim(alice),
|
||||
reward::issuer(issuer),
|
||||
ticket::use(aliceTicketSeq++),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
|
||||
env.require(tickets(alice, env.seq(alice) - aliceTicketSeq));
|
||||
@@ -392,6 +811,317 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
env.require(owners(alice, 9));
|
||||
}
|
||||
|
||||
void
|
||||
testBalanceChanges(FeatureBitset features)
|
||||
{
|
||||
testcase("balance changes");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
|
||||
auto const getCurrentTime = [&](Env& env) {
|
||||
return std::chrono::duration_cast<std::chrono::seconds>(
|
||||
env.app()
|
||||
.getLedgerMaster()
|
||||
.getValidatedLedger()
|
||||
->info()
|
||||
.parentCloseTime.time_since_epoch())
|
||||
.count();
|
||||
};
|
||||
|
||||
// Native Reward Claim
|
||||
{
|
||||
Env env{*this, features};
|
||||
auto const alice = Account("alice");
|
||||
auto const gw = Account("gw");
|
||||
|
||||
auto const issuer = Account::master;
|
||||
env.fund(XRP(10001), alice, gw);
|
||||
env.close();
|
||||
|
||||
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
auto const currentTime = getCurrentTime(env);
|
||||
auto const currentLedger = env.current()->seq();
|
||||
|
||||
env(reward::claim(alice), reward::issuer(issuer), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(fset(alice, 0));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(
|
||||
expectRewards(
|
||||
env,
|
||||
alice,
|
||||
currentLedger,
|
||||
currentLedger + 1,
|
||||
10000, // 10000 XAH * time 1
|
||||
currentTime) == true);
|
||||
}
|
||||
|
||||
// IOU Reward Claim
|
||||
for (bool const fromHighAccount : {true, false})
|
||||
{
|
||||
Env env{*this, features};
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const issuer = Account("issuer");
|
||||
|
||||
auto const user = fromHighAccount ? alice : bob;
|
||||
auto const gw = fromHighAccount ? bob : alice;
|
||||
|
||||
if (fromHighAccount)
|
||||
BEAST_EXPECT(user.id() < gw.id());
|
||||
else
|
||||
BEAST_EXPECT(user.id() > gw.id());
|
||||
|
||||
env.fund(XRP(10000), user, gw, issuer);
|
||||
env(fset(gw, asfDefaultRipple));
|
||||
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook);
|
||||
hookObj[jss::HookOn] = to_string(~UINT256_BIT[ttCLAIM_REWARD]);
|
||||
env(hook(gw, {{hookObj}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(trust(user, gw["USD"](1000000)), fee(XRP(1)));
|
||||
env.close();
|
||||
env(pay(gw, user, gw["USD"](10000)));
|
||||
env.close();
|
||||
|
||||
auto currentTime = getCurrentTime(env);
|
||||
auto currentLedger = env.current()->seq();
|
||||
|
||||
env(reward::claim(user),
|
||||
reward::issuer(gw),
|
||||
reward::claimCurrency(gw["USD"]),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(pay(user, gw, gw["USD"](10000)));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(
|
||||
expectRewardsIOU(
|
||||
env,
|
||||
user,
|
||||
gw["USD"],
|
||||
currentLedger,
|
||||
currentLedger + 1,
|
||||
user["USD"](10000), // 10000 USD * time 1
|
||||
currentTime) == true);
|
||||
|
||||
env(pay(gw, user, gw["USD"](1)));
|
||||
env.close();
|
||||
|
||||
// check Balance == 0
|
||||
BEAST_EXPECT(
|
||||
expectRewardsIOU(
|
||||
env,
|
||||
user,
|
||||
gw["USD"],
|
||||
currentLedger,
|
||||
currentLedger + 2,
|
||||
user["USD"](10000), // 10000 USD * time 1 + 0 USD * time 1
|
||||
currentTime) == true);
|
||||
}
|
||||
|
||||
// Check Balance minus -> plus, plus -> minus
|
||||
for (bool const fromHighAccount : {true, false})
|
||||
{
|
||||
Env env{*this, features};
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const issuer = Account("issuer");
|
||||
|
||||
auto const user = fromHighAccount ? alice : bob;
|
||||
auto const gw = fromHighAccount ? bob : alice;
|
||||
|
||||
if (fromHighAccount)
|
||||
BEAST_EXPECT(user.id() < gw.id());
|
||||
else
|
||||
BEAST_EXPECT(user.id() > gw.id());
|
||||
|
||||
env.fund(XRP(10000), user, gw, issuer);
|
||||
env(fset(gw, asfDefaultRipple));
|
||||
env.close();
|
||||
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook);
|
||||
hookObj[jss::HookOn] = to_string(~UINT256_BIT[ttCLAIM_REWARD]);
|
||||
env(hook(gw, {{hookObj}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(trust(user, gw["USD"](1000000)));
|
||||
env.close();
|
||||
env(trust(gw, user["USD"](1000000)));
|
||||
env(pay(gw, user, gw["USD"](10000)));
|
||||
env.close();
|
||||
|
||||
auto currentTime = getCurrentTime(env);
|
||||
auto currentLedger = env.current()->seq();
|
||||
|
||||
env(reward::claim(user),
|
||||
reward::issuer(gw),
|
||||
reward::claimCurrency(gw["USD"]),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(pay(user, gw, gw["USD"](20000)));
|
||||
env.close();
|
||||
|
||||
env(pay(user, gw, gw["USD"](1)));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(
|
||||
expectRewardsIOU(
|
||||
env,
|
||||
user,
|
||||
gw["USD"],
|
||||
currentLedger,
|
||||
currentLedger + 2,
|
||||
user["USD"](10000), // 10000 USD * time 1 + 0 USD * time 1
|
||||
currentTime) == true);
|
||||
}
|
||||
|
||||
// test with escrow (locked balance)
|
||||
for (bool const fromHighAccount : {true, false})
|
||||
{
|
||||
for (bool const hasEscrow : {true, false})
|
||||
{
|
||||
Env env{*this, features};
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const issuer = Account("issuer");
|
||||
|
||||
auto const user = fromHighAccount ? alice : bob;
|
||||
auto const gw = fromHighAccount ? bob : alice;
|
||||
|
||||
if (fromHighAccount)
|
||||
BEAST_EXPECT(user.id() < gw.id());
|
||||
else
|
||||
BEAST_EXPECT(user.id() > gw.id());
|
||||
|
||||
env.fund(XRP(10000), user, gw, issuer);
|
||||
env(fset(gw, asfDefaultRipple));
|
||||
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook);
|
||||
hookObj[jss::HookOn] = to_string(~UINT256_BIT[ttCLAIM_REWARD]);
|
||||
env(hook(gw, {{hookObj}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
env(trust(user, gw["USD"](1000000)), fee(XRP(1)));
|
||||
env.close();
|
||||
env(pay(gw, user, gw["USD"](10000)));
|
||||
env.close();
|
||||
|
||||
if (hasEscrow)
|
||||
{
|
||||
env(escrow(user, user, gw["USD"](2000)),
|
||||
finish_time(env.now() + 1s),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
}
|
||||
|
||||
auto currentTime = getCurrentTime(env);
|
||||
auto currentLedger = env.current()->seq();
|
||||
|
||||
env(reward::claim(user),
|
||||
reward::issuer(gw),
|
||||
reward::claimCurrency(gw["USD"]),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
env(pay(user, gw, gw["USD"](5000)));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(
|
||||
expectRewardsIOU(
|
||||
env,
|
||||
user,
|
||||
gw["USD"],
|
||||
currentLedger,
|
||||
currentLedger + 1,
|
||||
user["USD"](10000), // 10000 USD * time 1
|
||||
currentTime) == true);
|
||||
|
||||
env(pay(gw, user, gw["USD"](1)));
|
||||
env.close();
|
||||
|
||||
// check Balance == 0
|
||||
BEAST_EXPECT(
|
||||
expectRewardsIOU(
|
||||
env,
|
||||
user,
|
||||
gw["USD"],
|
||||
currentLedger,
|
||||
currentLedger + 2,
|
||||
user["USD"](
|
||||
15000), // 10000 USD * time 1 + 5000 USD * time 1
|
||||
currentTime) == true);
|
||||
}
|
||||
}
|
||||
|
||||
// STAmount overflow in reward accumulation should not cause
|
||||
// transaction failure (tefEXCEPTION). The overflow should be
|
||||
// gracefully skipped via try-catch.
|
||||
for (bool const fromHighAccount : {true, false})
|
||||
{
|
||||
Env env{*this, features};
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
auto const issuer = Account("issuer");
|
||||
|
||||
auto const user = fromHighAccount ? alice : bob;
|
||||
auto const gw = fromHighAccount ? bob : alice;
|
||||
|
||||
env.fund(XRP(10000), user, gw, issuer);
|
||||
env(fset(gw, asfDefaultRipple));
|
||||
env.close();
|
||||
|
||||
auto hookObj = hso(jtx::genesis::AcceptHook);
|
||||
hookObj[jss::HookOn] = to_string(~UINT256_BIT[ttCLAIM_REWARD]);
|
||||
env(hook(gw, {{hookObj}}, 0), fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
// Use a near-max IOU balance at exponent 80. When
|
||||
// multiply(balance, STAmount(lgrElapsed), issue) is called
|
||||
// with lgrElapsed >= 2, the result exponent exceeds
|
||||
// cMaxOffset(80), causing IOUAmount::normalize to throw
|
||||
// std::overflow_error("value overflow").
|
||||
auto const bigUSD = STAmount{
|
||||
gw["USD"].issue(), std::uint64_t(5000000000000000ull), 80};
|
||||
// Payment amount must be large enough to register a
|
||||
// balance change given STAmount's 16-digit precision.
|
||||
auto const payBackUSD = STAmount{
|
||||
gw["USD"].issue(), std::uint64_t(1000000000000000ull), 80};
|
||||
|
||||
env(trust(user, bigUSD));
|
||||
env.close();
|
||||
env(pay(gw, user, bigUSD));
|
||||
env.close();
|
||||
|
||||
// Claim IOU reward to initialize reward tracking
|
||||
env(reward::claim(user),
|
||||
reward::issuer(gw),
|
||||
reward::claimCurrency(gw["USD"]),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
// Advance ledger so lgrElapsed >= 2. With lgrElapsed=1
|
||||
// the multiply result is exactly at cMaxOffset boundary
|
||||
// (no overflow). With lgrElapsed >= 2, the result exponent
|
||||
// exceeds cMaxOffset and triggers the overflow.
|
||||
env.close();
|
||||
|
||||
// This payment modifies the trustline balance, triggering
|
||||
// reward accumulation in Transactor. Without the try-catch
|
||||
// fix, multiply() throws std::overflow_error("value overflow")
|
||||
// and the transaction fails with tefEXCEPTION.
|
||||
env(pay(user, gw, payBackUSD), ter(tesSUCCESS));
|
||||
env.close();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testWithFeats(FeatureBitset features)
|
||||
{
|
||||
@@ -400,6 +1130,7 @@ struct ClaimReward_test : public beast::unit_test::suite
|
||||
testInvalidPreclaim(features);
|
||||
testValidNoHook(features);
|
||||
testUsingTickets(features);
|
||||
testBalanceChanges(features);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -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)
|
||||
|
||||
+582
-191
@@ -18,6 +18,7 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <test/jtx.h>
|
||||
#include <test/jtx/AMM.h>
|
||||
#include <xrpld/app/tx/applySteps.h>
|
||||
#include <xrpld/ledger/Dir.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -4316,6 +4317,93 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
env.close();
|
||||
}
|
||||
|
||||
void
|
||||
testIOUAMM(FeatureBitset features)
|
||||
{
|
||||
testcase("IOU AMM");
|
||||
using namespace test::jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
Account alice{"alice"};
|
||||
Account bob{"bob"};
|
||||
Account gw{"gw"};
|
||||
|
||||
auto const USD = gw["USD"];
|
||||
|
||||
// AMMCreate fails - insufficient balance
|
||||
Env env(*this, features | featureAMM | featureAMMClawback);
|
||||
env.fund(XRP(10000), alice, bob, gw);
|
||||
env.close();
|
||||
|
||||
env.trust(USD(100000), alice);
|
||||
env.close();
|
||||
env(pay(gw, alice, USD(1000)));
|
||||
env.close();
|
||||
|
||||
env(escrow(alice, bob, USD(1000)), finish_time(env.now() + 1s));
|
||||
env.close();
|
||||
|
||||
// AMMCreate fails - insufficient balance
|
||||
AMM ammFail(env, alice, XRP(1000), USD(1000), ter(tecUNFUNDED_AMM));
|
||||
|
||||
env(pay(gw, alice, USD(1000)));
|
||||
env.close();
|
||||
|
||||
AMM ammAlice(env, alice, XRP(1000), USD(1000));
|
||||
BEAST_EXPECT(ammAlice.ammExists());
|
||||
|
||||
// Single Asset Deposit fails - insufficient balance
|
||||
ammAlice.deposit(
|
||||
alice,
|
||||
USD(1000),
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
ter(tecUNFUNDED_AMM));
|
||||
|
||||
// Double Asset Deposit fails - insufficient balance
|
||||
ammAlice.deposit(
|
||||
alice,
|
||||
USD(1000),
|
||||
XRP(1000),
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
ter(tecUNFUNDED_AMM));
|
||||
|
||||
auto const lptoken = ammAlice.getLPTokensBalance(alice);
|
||||
// lock all LP tokens
|
||||
env(escrow(alice, bob, STAmount{lptoken, ammAlice.lptIssue()}),
|
||||
finish_time(env.now() + 1s));
|
||||
env.close();
|
||||
|
||||
// Withdraw
|
||||
ammAlice.withdraw(
|
||||
alice, USD(1000), std::nullopt, std::nullopt, ter(tecAMM_BALANCE));
|
||||
ammAlice.withdrawAll(alice, USD(1000), ter(tecAMM_BALANCE));
|
||||
|
||||
env(ammAlice.bid(BidArg{
|
||||
.account = alice,
|
||||
.bidMax = 100,
|
||||
.assets = {{USD, XRP}},
|
||||
}),
|
||||
ter(tecAMM_INVALID_TOKENS));
|
||||
|
||||
ammAlice.vote(
|
||||
alice,
|
||||
1'000,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
{{USD, XRP}},
|
||||
ter(tecAMM_INVALID_TOKENS));
|
||||
|
||||
// Cannot escrow clawbackable tokens, so we cannot ammClawback escrowed
|
||||
// tokens
|
||||
// env(amm::ammClawback(gw, alice, USD, XRP, USD(100)),
|
||||
// ter(tecAMM_BALANCE));
|
||||
// env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt),
|
||||
// ter(tecAMM_BALANCE));
|
||||
}
|
||||
|
||||
static uint256
|
||||
getEscrowIndex(AccountID const& account, std::uint32_t uSequence)
|
||||
{
|
||||
@@ -4334,216 +4422,160 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
auto const gw = Account{"gateway"};
|
||||
auto const USD = gw["USD"];
|
||||
|
||||
for (bool const withXahauV1 : {true, false})
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(10000), alice, bob, gw);
|
||||
env.close();
|
||||
env.trust(USD(1000000), alice);
|
||||
env.trust(USD(1000000), bob);
|
||||
env.close();
|
||||
env(pay(gw, alice, USD(10000)));
|
||||
env(pay(gw, bob, USD(10000)));
|
||||
env.close();
|
||||
|
||||
// EscrowCancel - EscrowID
|
||||
{
|
||||
auto const amend = withXahauV1 ? features : features - fixXahauV1;
|
||||
Env env{*this, amend};
|
||||
env.fund(XRP(10000), alice, bob, gw);
|
||||
env.close();
|
||||
env.trust(USD(1000000), alice);
|
||||
env.trust(USD(1000000), bob);
|
||||
env.close();
|
||||
env(pay(gw, alice, USD(10000)));
|
||||
env(pay(gw, bob, USD(10000)));
|
||||
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
|
||||
env(escrow(alice, bob, USD(1000)),
|
||||
finish_time(env.now() + 1s),
|
||||
cancel_time(env.now() + 2s),
|
||||
fee(1500));
|
||||
env.close();
|
||||
|
||||
// EscrowCancel - EscrowID
|
||||
{
|
||||
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
|
||||
env(escrow(alice, bob, USD(1000)),
|
||||
finish_time(env.now() + 1s),
|
||||
cancel_time(env.now() + 2s),
|
||||
fee(1500));
|
||||
env.close();
|
||||
// withXahauV1 - no OfferSequence
|
||||
env(cancel(bob, alice),
|
||||
escrow_id(escrowId),
|
||||
fee(1500),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
if (withXahauV1)
|
||||
{
|
||||
// withXahauV1 - no OfferSequence
|
||||
env(cancel(bob, alice),
|
||||
escrow_id(escrowId),
|
||||
fee(1500),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
// !withXahauV1 - OfferSequence == 0
|
||||
env(cancel(bob, alice, 0),
|
||||
escrow_id(escrowId),
|
||||
fee(1500),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
}
|
||||
auto const escrowLE = env.le(keylet::unchecked(escrowId));
|
||||
BEAST_EXPECT(!escrowLE);
|
||||
}
|
||||
|
||||
auto const escrowLE = env.le(keylet::unchecked(escrowId));
|
||||
BEAST_EXPECT(!escrowLE);
|
||||
}
|
||||
// EscrowCancel - no EscrowID or OfferSequence
|
||||
{
|
||||
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
|
||||
env(escrow(alice, bob, USD(1000)),
|
||||
finish_time(env.now() + 1s),
|
||||
cancel_time(env.now() + 2s),
|
||||
fee(1500));
|
||||
env.close();
|
||||
|
||||
// EscrowCancel - no EscrowID or OfferSequence
|
||||
{
|
||||
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
|
||||
env(escrow(alice, bob, USD(1000)),
|
||||
finish_time(env.now() + 1s),
|
||||
cancel_time(env.now() + 2s),
|
||||
fee(1500));
|
||||
env.close();
|
||||
env(cancel(bob, alice), fee(1500), ter(temMALFORMED));
|
||||
env.close();
|
||||
|
||||
env(cancel(bob, alice), fee(1500), ter(temMALFORMED));
|
||||
env.close();
|
||||
auto const escrowLE = env.le(keylet::unchecked(escrowId));
|
||||
BEAST_EXPECT(escrowLE);
|
||||
}
|
||||
|
||||
auto const escrowLE = env.le(keylet::unchecked(escrowId));
|
||||
BEAST_EXPECT(escrowLE);
|
||||
}
|
||||
// EscrowCancel - EscrowID & OfferSequence
|
||||
{
|
||||
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow(alice, bob, USD(1000)),
|
||||
finish_time(env.now() + 1s),
|
||||
cancel_time(env.now() + 2s),
|
||||
fee(1500));
|
||||
env.close();
|
||||
|
||||
// EscrowCancel - EscrowID & OfferSequence
|
||||
{
|
||||
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow(alice, bob, USD(1000)),
|
||||
finish_time(env.now() + 1s),
|
||||
cancel_time(env.now() + 2s),
|
||||
fee(1500));
|
||||
env.close();
|
||||
env(cancel(bob, alice, seq),
|
||||
escrow_id(escrowId),
|
||||
fee(1500),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
|
||||
env(cancel(bob, alice, seq),
|
||||
escrow_id(escrowId),
|
||||
fee(1500),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
auto const escrowLE = env.le(keylet::unchecked(escrowId));
|
||||
BEAST_EXPECT(escrowLE);
|
||||
}
|
||||
|
||||
auto const escrowLE = env.le(keylet::unchecked(escrowId));
|
||||
BEAST_EXPECT(escrowLE);
|
||||
}
|
||||
// EscrowCancel - EscrowID & OfferSequence 0
|
||||
{
|
||||
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
|
||||
env(escrow(alice, bob, USD(1000)),
|
||||
finish_time(env.now() + 1s),
|
||||
cancel_time(env.now() + 2s),
|
||||
fee(1500));
|
||||
env.close();
|
||||
|
||||
// EscrowCancel - EscrowID & OfferSequence 0
|
||||
{
|
||||
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
|
||||
env(escrow(alice, bob, USD(1000)),
|
||||
finish_time(env.now() + 1s),
|
||||
cancel_time(env.now() + 2s),
|
||||
fee(1500));
|
||||
env.close();
|
||||
// withXahauV1 - OfferSequence 0 == temMALFORMED
|
||||
env(cancel(bob, alice, 0),
|
||||
escrow_id(escrowId),
|
||||
fee(1500),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
auto const escrowLE = env.le(keylet::unchecked(escrowId));
|
||||
BEAST_EXPECT(escrowLE);
|
||||
}
|
||||
|
||||
if (withXahauV1)
|
||||
{
|
||||
// withXahauV1 - OfferSequence 0 == temMALFORMED
|
||||
env(cancel(bob, alice, 0),
|
||||
escrow_id(escrowId),
|
||||
fee(1500),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
auto const escrowLE = env.le(keylet::unchecked(escrowId));
|
||||
BEAST_EXPECT(escrowLE);
|
||||
}
|
||||
else
|
||||
{
|
||||
// withXahauV1 - OfferSequence 0 == tesSUCCESS
|
||||
env(cancel(bob, alice, 0),
|
||||
escrow_id(escrowId),
|
||||
fee(1500),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
auto const escrowLE = env.le(keylet::unchecked(escrowId));
|
||||
BEAST_EXPECT(!escrowLE);
|
||||
}
|
||||
}
|
||||
// EscrowFinish - EscrowID
|
||||
{
|
||||
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
|
||||
env(escrow(alice, bob, USD(1000)),
|
||||
finish_time(env.now() + 1s),
|
||||
fee(1500));
|
||||
env.close(5s);
|
||||
|
||||
// EscrowFinish - EscrowID
|
||||
{
|
||||
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
|
||||
env(escrow(alice, bob, USD(1000)),
|
||||
finish_time(env.now() + 1s),
|
||||
fee(1500));
|
||||
env.close(5s);
|
||||
// withXahauV1 - no OfferSequence
|
||||
env(finish(bob, alice),
|
||||
escrow_id(escrowId),
|
||||
fee(1500),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
if (withXahauV1)
|
||||
{
|
||||
// withXahauV1 - no OfferSequence
|
||||
env(finish(bob, alice),
|
||||
escrow_id(escrowId),
|
||||
fee(1500),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
// !withXahauV1 - OfferSequence == 0
|
||||
env(finish(bob, alice, 0),
|
||||
escrow_id(escrowId),
|
||||
fee(1500),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
}
|
||||
auto const escrowLE = env.le(keylet::unchecked(escrowId));
|
||||
BEAST_EXPECT(!escrowLE);
|
||||
}
|
||||
|
||||
auto const escrowLE = env.le(keylet::unchecked(escrowId));
|
||||
BEAST_EXPECT(!escrowLE);
|
||||
}
|
||||
// EscrowFinish - no EscrowID or OfferSequence
|
||||
{
|
||||
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
|
||||
env(escrow(alice, bob, USD(1000)),
|
||||
finish_time(env.now() + 1s),
|
||||
fee(1500));
|
||||
env.close();
|
||||
|
||||
// EscrowFinish - no EscrowID or OfferSequence
|
||||
{
|
||||
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
|
||||
env(escrow(alice, bob, USD(1000)),
|
||||
finish_time(env.now() + 1s),
|
||||
fee(1500));
|
||||
env.close();
|
||||
env(finish(bob, alice), fee(1500), ter(temMALFORMED));
|
||||
env.close();
|
||||
|
||||
env(finish(bob, alice), fee(1500), ter(temMALFORMED));
|
||||
env.close();
|
||||
auto const escrowLE = env.le(keylet::unchecked(escrowId));
|
||||
BEAST_EXPECT(escrowLE);
|
||||
}
|
||||
|
||||
auto const escrowLE = env.le(keylet::unchecked(escrowId));
|
||||
BEAST_EXPECT(escrowLE);
|
||||
}
|
||||
// EscrowFinish- EscrowID & OfferSequence
|
||||
{
|
||||
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow(alice, bob, USD(1000)),
|
||||
finish_time(env.now() + 1s),
|
||||
fee(1500));
|
||||
env.close(5s);
|
||||
|
||||
// EscrowFinish- EscrowID & OfferSequence
|
||||
{
|
||||
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow(alice, bob, USD(1000)),
|
||||
finish_time(env.now() + 1s),
|
||||
fee(1500));
|
||||
env.close(5s);
|
||||
env(finish(bob, alice, seq),
|
||||
escrow_id(escrowId),
|
||||
fee(1500),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
|
||||
env(finish(bob, alice, seq),
|
||||
escrow_id(escrowId),
|
||||
fee(1500),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
auto const escrowLE = env.le(keylet::unchecked(escrowId));
|
||||
BEAST_EXPECT(escrowLE);
|
||||
}
|
||||
|
||||
auto const escrowLE = env.le(keylet::unchecked(escrowId));
|
||||
BEAST_EXPECT(escrowLE);
|
||||
}
|
||||
// EscrowFinish- EscrowID & OfferSequence 0
|
||||
{
|
||||
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
|
||||
env(escrow(alice, bob, USD(1000)),
|
||||
finish_time(env.now() + 1s),
|
||||
fee(1500));
|
||||
env.close(5s);
|
||||
|
||||
// EscrowFinish- EscrowID & OfferSequence 0
|
||||
{
|
||||
uint256 const escrowId{getEscrowIndex(alice, env.seq(alice))};
|
||||
env(escrow(alice, bob, USD(1000)),
|
||||
finish_time(env.now() + 1s),
|
||||
fee(1500));
|
||||
env.close(5s);
|
||||
|
||||
if (withXahauV1)
|
||||
{
|
||||
// withXahauV1 - OfferSequence 0 == temMALFORMED
|
||||
env(finish(bob, alice, 0),
|
||||
escrow_id(escrowId),
|
||||
fee(1500),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
auto const escrowLE = env.le(keylet::unchecked(escrowId));
|
||||
BEAST_EXPECT(escrowLE);
|
||||
}
|
||||
else
|
||||
{
|
||||
// !withXahauV1 - OfferSequence 0 == tesSUCCESS
|
||||
env(finish(bob, alice, 0),
|
||||
escrow_id(escrowId),
|
||||
fee(1500),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
auto const escrowLE = env.le(keylet::unchecked(escrowId));
|
||||
BEAST_EXPECT(!escrowLE);
|
||||
}
|
||||
}
|
||||
// withXahauV1 - OfferSequence 0 == temMALFORMED
|
||||
env(finish(bob, alice, 0),
|
||||
escrow_id(escrowId),
|
||||
fee(1500),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
auto const escrowLE = env.le(keylet::unchecked(escrowId));
|
||||
BEAST_EXPECT(escrowLE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4690,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)
|
||||
{
|
||||
@@ -4729,6 +5114,7 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
testIOUTLINSF(features);
|
||||
testIOUPrecisionLoss(features);
|
||||
testIOUClawback(features);
|
||||
testIOUAMM(features);
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -4737,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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -336,10 +336,7 @@ struct GenesisMint_test : public beast::unit_test::suite
|
||||
env.close();
|
||||
|
||||
// validate emitted txn
|
||||
auto const txResult = env.current()->rules().enabled(fixXahauV1)
|
||||
? "tesSUCCESS"
|
||||
: "tecINTERNAL";
|
||||
validateEmittedTxn(env, txResult, __LINE__);
|
||||
validateEmittedTxn(env, "tesSUCCESS", __LINE__);
|
||||
}
|
||||
|
||||
// missing an amount
|
||||
@@ -569,39 +566,25 @@ struct GenesisMint_test : public beast::unit_test::suite
|
||||
validateEmittedTxn(env, "tesSUCCESS", __LINE__);
|
||||
}
|
||||
|
||||
auto const amtResult = env.current()->rules().enabled(fixXahauV1)
|
||||
? 30000000ULL
|
||||
: 10000000ULL;
|
||||
// try to include the same destination twice
|
||||
{
|
||||
auto const txResult = env.current()->rules().enabled(fixXahauV1)
|
||||
? ter(tesSUCCESS)
|
||||
: ter(tecHOOK_REJECTED);
|
||||
env(invoke::invoke(
|
||||
invoker,
|
||||
env.master,
|
||||
genesis::makeBlob({
|
||||
{greg.id(),
|
||||
XRP(10).value(),
|
||||
std::nullopt,
|
||||
std::nullopt},
|
||||
{greg.id(),
|
||||
XRP(10).value(),
|
||||
std::nullopt,
|
||||
std::nullopt},
|
||||
})),
|
||||
fee(XRP(1)),
|
||||
txResult);
|
||||
env.close();
|
||||
env.close();
|
||||
}
|
||||
env(invoke::invoke(
|
||||
invoker,
|
||||
env.master,
|
||||
genesis::makeBlob({
|
||||
{greg.id(), XRP(10).value(), std::nullopt, std::nullopt},
|
||||
{greg.id(), XRP(10).value(), std::nullopt, std::nullopt},
|
||||
})),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
env.close();
|
||||
|
||||
// check
|
||||
{
|
||||
auto const le = env.le(keylet::account(greg.id()));
|
||||
BEAST_EXPECT(
|
||||
!!le &&
|
||||
le->getFieldAmount(sfBalance).xrp().drops() == amtResult);
|
||||
le->getFieldAmount(sfBalance).xrp().drops() == 30000000ULL);
|
||||
}
|
||||
|
||||
// trip the supply cap invariant
|
||||
@@ -626,7 +609,7 @@ struct GenesisMint_test : public beast::unit_test::suite
|
||||
auto const le = env.le(keylet::account(greg.id()));
|
||||
BEAST_EXPECT(
|
||||
!!le &&
|
||||
le->getFieldAmount(sfBalance).xrp().drops() == amtResult);
|
||||
le->getFieldAmount(sfBalance).xrp().drops() == 30000000ULL);
|
||||
|
||||
auto const postCoins = env.current()->info().drops;
|
||||
auto const txnFee =
|
||||
@@ -698,7 +681,6 @@ public:
|
||||
using namespace test::jtx;
|
||||
auto const sa = supported_amendments();
|
||||
testWithFeats(sa);
|
||||
testWithFeats(sa - fixXahauV1);
|
||||
testWithFeats(sa - fixHookAPI20251128);
|
||||
}
|
||||
};
|
||||
|
||||
+786
-356
File diff suppressed because it is too large.
Load diff
+399
-27
@@ -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)
|
||||
{
|
||||
@@ -1663,11 +1858,9 @@ class Import_test : public beast::unit_test::suite
|
||||
|
||||
// temMALFORMED - Import: xpop did not contain an 80% quorum for the txn
|
||||
// it purports to prove.
|
||||
for (bool const withXahauV1 : {true, false})
|
||||
{
|
||||
auto const amend = withXahauV1 ? features : features - fixXahauV1;
|
||||
test::jtx::Env env{
|
||||
*this, network::makeNetworkVLConfig(21337, keys), amend};
|
||||
*this, network::makeNetworkVLConfig(21337, keys), features};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
env.fund(XRP(1000), alice);
|
||||
@@ -1679,16 +1872,10 @@ class Import_test : public beast::unit_test::suite
|
||||
"";
|
||||
valData["n9MTTavZe6EPqqyQ27pJbNWpfHw8ZNspVgznrwx5HWm7cdTjKQie"] =
|
||||
"";
|
||||
if (withXahauV1)
|
||||
{
|
||||
valData
|
||||
["n94J5LCRu9bBWrJiwRWujvuVECVPWbXFcQ2VN38qLD378F5pDSDM"] =
|
||||
"";
|
||||
}
|
||||
valData["n94J5LCRu9bBWrJiwRWujvuVECVPWbXFcQ2VN38qLD378F5pDSDM"] =
|
||||
"";
|
||||
tmpXpop[jss::validation][jss::data] = valData;
|
||||
auto const txResult =
|
||||
withXahauV1 ? ter(temMALFORMED) : ter(temMALFORMED);
|
||||
env(import::import(alice, tmpXpop), txResult);
|
||||
env(import::import(alice, tmpXpop), ter(temMALFORMED));
|
||||
}
|
||||
|
||||
test::jtx::Env env{*this, network::makeNetworkVLConfig(21337, keys)};
|
||||
@@ -1708,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.
|
||||
{
|
||||
@@ -1721,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));
|
||||
}
|
||||
@@ -1740,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));
|
||||
}
|
||||
@@ -2084,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
|
||||
{
|
||||
@@ -2135,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
|
||||
{
|
||||
@@ -2161,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
|
||||
{
|
||||
@@ -2209,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] =
|
||||
@@ -2239,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] =
|
||||
@@ -2667,8 +2885,9 @@ class Import_test : public beast::unit_test::suite
|
||||
}
|
||||
for (bool const withFixImportIssuer : {true, false})
|
||||
{
|
||||
auto const withAMM = features | featureAMM;
|
||||
auto const amend =
|
||||
withFixImportIssuer ? features : features - fixImportIssuer;
|
||||
withFixImportIssuer ? withAMM : withAMM - fixImportIssuer;
|
||||
test::jtx::Env env{
|
||||
*this, network::makeNetworkVLConfig(21337, keys), amend};
|
||||
env.fund(XRP(1000), alice, issuer);
|
||||
@@ -5237,7 +5456,6 @@ class Import_test : public beast::unit_test::suite
|
||||
*this, network::makeNetworkVLConfig(21337, keys)};
|
||||
|
||||
auto const feeDrops = env.current()->fees().base;
|
||||
bool const fixV2 = env.current()->rules().enabled(fixXahauV2);
|
||||
|
||||
// confirm total coins header
|
||||
auto const initCoins = env.current()->info().drops;
|
||||
@@ -5287,18 +5505,17 @@ class Import_test : public beast::unit_test::suite
|
||||
auto const preAlice = env.balance(alice);
|
||||
auto const preCoins = env.current()->info().drops;
|
||||
|
||||
auto const result = fixV2 ? ter(tesSUCCESS) : ter(tecHOOK_REJECTED);
|
||||
env(import::import(
|
||||
alice, import::loadXpop(ImportTCAccountSet::w_seed)),
|
||||
import::issuer(issuer),
|
||||
fee(1'000'000),
|
||||
result);
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// fixXahauV2
|
||||
bool const zeroBurn =
|
||||
env.current()->rules().enabled(featureZeroB2M);
|
||||
auto const mintXAH = fixV2 ? XRP(zeroBurn ? 0 : 1000) : XRP(0);
|
||||
auto const mintXAH = XRP(zeroBurn ? 0 : 1000);
|
||||
// confirm fee was burned mint / no mint
|
||||
auto const postAlice = env.balance(alice);
|
||||
BEAST_EXPECT(postAlice == preAlice - XRP(1) + mintXAH);
|
||||
@@ -5308,17 +5525,14 @@ class Import_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(postCoins == preCoins - XRP(1) + mintXAH);
|
||||
|
||||
// resubmit import without issuer
|
||||
auto const result2 =
|
||||
fixV2 ? ter(tefPAST_IMPORT_SEQ) : ter(tesSUCCESS);
|
||||
env(import::import(
|
||||
alice, import::loadXpop(ImportTCAccountSet::w_seed)),
|
||||
fee(feeDrops * 10),
|
||||
result2);
|
||||
ter(tefPAST_IMPORT_SEQ));
|
||||
env.close();
|
||||
|
||||
// total burn
|
||||
auto const totalBurn =
|
||||
fixV2 ? XRP(0) : XRP(zeroBurn ? 0 : 1000) - (feeDrops * 10);
|
||||
auto const totalBurn = XRP(0);
|
||||
|
||||
// confirm fee was minted / not minted
|
||||
auto const postAlice2 = env.balance(alice);
|
||||
@@ -6269,13 +6483,168 @@ 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
|
||||
{
|
||||
using namespace test::jtx;
|
||||
FeatureBitset const all{supported_amendments()};
|
||||
testWithFeats(all - fixXahauV2);
|
||||
testWithFeats(all - featureZeroB2M);
|
||||
testWithFeats(all);
|
||||
}
|
||||
@@ -6291,6 +6660,7 @@ public:
|
||||
testSyntaxCheckProofArray(features);
|
||||
testSyntaxCheckProofObject(features);
|
||||
testSyntaxCheckXPOP(features);
|
||||
testProofInnerNodeSpoof(features);
|
||||
testGetVLInfo(features);
|
||||
testEnabled(features);
|
||||
testInvalidPreflight(features);
|
||||
@@ -6310,6 +6680,8 @@ public:
|
||||
testMinMax(features);
|
||||
testHalving(features - featureOwnerPaysFee);
|
||||
testBlackhole(features);
|
||||
testGetVLWindow(features);
|
||||
testVLWindowClock(features);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -173,7 +173,10 @@ class Invoke_test : public beast::unit_test::suite
|
||||
// tecNO_PERMISSION
|
||||
// issuer is an AMM account
|
||||
{
|
||||
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
|
||||
test::jtx::Env env{
|
||||
*this,
|
||||
network::makeNetworkConfig(21337),
|
||||
features | featureAMM};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
auto const issuer = Account("issuer");
|
||||
|
||||
@@ -388,7 +388,7 @@ public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
FeatureBitset const all{jtx::supported_amendments()};
|
||||
FeatureBitset const all{jtx::supported_amendments() | featureAMM};
|
||||
|
||||
testDirectStep(all);
|
||||
testBookStep(all);
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#include <test/jtx.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <xrpld/app/ledger/LedgerMaster.h>
|
||||
#include <xrpld/app/ledger/detail/PublishGap.h>
|
||||
#include <xrpl/basics/RangeSet.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
namespace ripple {
|
||||
@@ -102,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)
|
||||
{
|
||||
@@ -112,18 +114,229 @@ class LedgerMaster_test : public beast::unit_test::suite
|
||||
startLegSeq + 1, txnIndex);
|
||||
BEAST_EXPECT(
|
||||
*result ==
|
||||
uint256("DCAECE52F028B9D0F7CA43CBE15AB4EF7B84A8EF5D455238992E1E"
|
||||
"DF2BDA1637"));
|
||||
uint256("722BD5EC9E4AE36E724B6223F5A5887927AADFD0B18A3053AAE57F"
|
||||
"434F8C9FE8"));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCanSkipPinnedGap()
|
||||
{
|
||||
// detail::canSkipPinnedGap is the lifted decision used by
|
||||
// findNewLedgersToPublish to decide whether pubSeq can leap
|
||||
// across a gap in the publish-target set without violating
|
||||
// contiguous publication of non-pinned ledgers. In production
|
||||
// this guard rarely fires (pinned ranges are old historical
|
||||
// catalogue data, pubSeq is at the live tip) — it matters in
|
||||
// test/standalone catalogue-load scenarios where pinned seqs
|
||||
// can sit near pubSeq. The invariant is small and crisp;
|
||||
// unit-testing it directly is far cheaper than reconstructing
|
||||
// a believable findNewLedgersToPublish state through Env.
|
||||
testcase("detail::canSkipPinnedGap");
|
||||
|
||||
using detail::canSkipPinnedGap;
|
||||
|
||||
// No gap (pubSeq is already at intervalStart) — always OK.
|
||||
{
|
||||
RangeSet<std::uint32_t> empty;
|
||||
BEAST_EXPECT(canSkipPinnedGap(10, 10, empty));
|
||||
BEAST_EXPECT(canSkipPinnedGap(10, 5, empty)); // past
|
||||
}
|
||||
|
||||
// One-element gap, exactly pinned: can skip.
|
||||
{
|
||||
RangeSet<std::uint32_t> pinned;
|
||||
pinned.insert(range(10u, 10u));
|
||||
BEAST_EXPECT(canSkipPinnedGap(10, 11, pinned));
|
||||
}
|
||||
|
||||
// One-element gap, NOT pinned: must halt.
|
||||
{
|
||||
RangeSet<std::uint32_t> empty;
|
||||
BEAST_EXPECT(!canSkipPinnedGap(10, 11, empty));
|
||||
}
|
||||
|
||||
// Multi-element gap entirely covered by pinned: can skip.
|
||||
{
|
||||
RangeSet<std::uint32_t> pinned;
|
||||
pinned.insert(range(11u, 15u));
|
||||
BEAST_EXPECT(canSkipPinnedGap(11, 16, pinned));
|
||||
}
|
||||
|
||||
// Multi-element gap, pinned covers most but leaves one
|
||||
// non-pinned seq exposed: must halt. This is the bug the
|
||||
// guard exists to prevent — silently publishing seq 16
|
||||
// while seq 15 (non-pinned) is unfetched.
|
||||
{
|
||||
RangeSet<std::uint32_t> pinned;
|
||||
pinned.insert(range(11u, 14u)); // missing 15
|
||||
BEAST_EXPECT(!canSkipPinnedGap(11, 16, pinned));
|
||||
}
|
||||
|
||||
// Pinned set extends beyond the gap — only the gap window
|
||||
// matters for the decision.
|
||||
{
|
||||
RangeSet<std::uint32_t> pinned;
|
||||
pinned.insert(range(0u, 1000u));
|
||||
BEAST_EXPECT(canSkipPinnedGap(50, 100, pinned));
|
||||
}
|
||||
|
||||
// Empty pinned, with a gap: must halt.
|
||||
{
|
||||
RangeSet<std::uint32_t> empty;
|
||||
BEAST_EXPECT(!canSkipPinnedGap(50, 100, empty));
|
||||
}
|
||||
|
||||
// Disjoint pinned that doesn't intersect the gap: must halt.
|
||||
{
|
||||
RangeSet<std::uint32_t> pinned;
|
||||
pinned.insert(range(200u, 300u));
|
||||
BEAST_EXPECT(!canSkipPinnedGap(50, 100, pinned));
|
||||
}
|
||||
|
||||
// Pinned partially intersects gap from the left only.
|
||||
{
|
||||
RangeSet<std::uint32_t> pinned;
|
||||
pinned.insert(range(50u, 75u));
|
||||
BEAST_EXPECT(!canSkipPinnedGap(50, 100, pinned));
|
||||
}
|
||||
|
||||
// Pinned partially intersects gap from the right only.
|
||||
{
|
||||
RangeSet<std::uint32_t> pinned;
|
||||
pinned.insert(range(75u, 99u));
|
||||
BEAST_EXPECT(!canSkipPinnedGap(50, 100, pinned));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testPinUnpinSymmetry()
|
||||
{
|
||||
testcase("pin/unpin symmetry");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env{*this, makeNetworkConfig(11111)};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
auto& lm = env.app().getLedgerMaster();
|
||||
|
||||
// The most recently closed ledger is the one we'll pin.
|
||||
auto const ledger = lm.getClosedLedger();
|
||||
BEAST_EXPECT(ledger);
|
||||
if (!ledger)
|
||||
return;
|
||||
auto const seq = ledger->info().seq;
|
||||
BEAST_EXPECT(seq > 0);
|
||||
|
||||
// Baseline — not pinned.
|
||||
BEAST_EXPECT(!lm.isPinned(seq));
|
||||
|
||||
// Pin via storeLedger(ledger, pin=true). This is the same path
|
||||
// catalogue_load uses just before saveValidatedLedger, which is why
|
||||
// a save failure must roll back via unpinLedger() to keep the
|
||||
// in-memory pinned-set consistent with state.db.
|
||||
lm.storeLedger(ledger, /*pin=*/true);
|
||||
BEAST_EXPECT(lm.isPinned(seq));
|
||||
BEAST_EXPECT(boost::icl::contains(lm.getPinnedLedgersRangeSet(), seq));
|
||||
|
||||
// Unpin reverses the pin (the rollback contract used by
|
||||
// catalogue_load's RAII guard on save failure).
|
||||
lm.unpinLedger(seq);
|
||||
BEAST_EXPECT(!lm.isPinned(seq));
|
||||
BEAST_EXPECT(!boost::icl::contains(lm.getPinnedLedgersRangeSet(), seq));
|
||||
|
||||
// Unpinning a seq that isn't pinned is a no-op (idempotent).
|
||||
lm.unpinLedger(seq);
|
||||
BEAST_EXPECT(!lm.isPinned(seq));
|
||||
}
|
||||
|
||||
void
|
||||
testSetPinnedRangesImmediateMerge()
|
||||
{
|
||||
// Regression test: when setPinnedLedgersRangeSet runs and
|
||||
// mCompleteLedgers is already non-empty (LOAD/standalone startup
|
||||
// already called setFullLedger), the deferred merge in
|
||||
// setFullLedger never fires because the one-shot guard relies on
|
||||
// a flag, not on emptiness. Without the immediate-merge branch
|
||||
// in setPinnedLedgersRangeSet, pinned ranges restored from
|
||||
// state.db would never appear in mCompleteLedgers and historical
|
||||
// pinned ledgers would not be queryable until the next
|
||||
// validation. This test asserts the immediate-merge behavior.
|
||||
testcase("setPinnedLedgersRangeSet immediate merge");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env{*this, makeNetworkConfig(11111)};
|
||||
|
||||
auto const alice = Account("alice");
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
auto& lm = env.app().getLedgerMaster();
|
||||
|
||||
// Why mCompleteLedgers is non-empty here:
|
||||
// env.close() above runs synchronous standalone consensus.
|
||||
// That hits LedgerMaster::switchLCL, which (in standalone)
|
||||
// calls setFullLedger directly on the calling thread, which
|
||||
// inserts the closed ledger's seq into mCompleteLedgers.
|
||||
// So by the time we get here, mCompleteLedgers has the
|
||||
// genesis + funded seqs in it.
|
||||
//
|
||||
// This is the precondition that triggers the immediate-merge
|
||||
// branch in setPinnedLedgersRangeSet (the LOAD/standalone
|
||||
// startup case). On a NORMAL/NETWORK production startup,
|
||||
// mCompleteLedgers would still be empty when state.db is read
|
||||
// and setPinnedLedgersRangeSet is called, and the merge would
|
||||
// be deferred to the first validated ledger via setFullLedger.
|
||||
// We're specifically testing the *other* branch here.
|
||||
auto const completeBefore = lm.getCompleteLedgersRangeSet();
|
||||
BEAST_EXPECT(!completeBefore.empty());
|
||||
|
||||
// Pinned ranges are far outside the existing range so we can
|
||||
// unambiguously detect them being merged in.
|
||||
std::uint32_t const pinLo = 100000;
|
||||
std::uint32_t const pinHi = 100099;
|
||||
RangeSet<std::uint32_t> pinned;
|
||||
pinned.insert(range(pinLo, pinHi));
|
||||
|
||||
// Restore pinned ranges (mirrors what SHAMapStoreImp::start does
|
||||
// after reading state.db's PinnedLedgers row).
|
||||
lm.setPinnedLedgersRangeSet(pinned);
|
||||
|
||||
// Pinned set is restored.
|
||||
BEAST_EXPECT(lm.isPinned(pinLo));
|
||||
BEAST_EXPECT(lm.isPinned(pinHi));
|
||||
BEAST_EXPECT(lm.isPinned((pinLo + pinHi) / 2));
|
||||
|
||||
// Critical invariant: pinned ranges are now in mCompleteLedgers
|
||||
// immediately, not deferred to the first validated ledger.
|
||||
// This is what makes haveLedger() return true for pinned seqs
|
||||
// after a LOAD/standalone restart, without which the historical
|
||||
// pinned data is on disk but the system reports "we don't have
|
||||
// it".
|
||||
BEAST_EXPECT(lm.haveLedger(pinLo));
|
||||
BEAST_EXPECT(lm.haveLedger(pinHi));
|
||||
BEAST_EXPECT(lm.haveLedger((pinLo + pinHi) / 2));
|
||||
|
||||
// The pre-existing complete range is still complete (merge,
|
||||
// not replace).
|
||||
for (auto const& interval : completeBefore)
|
||||
for (auto s = interval.lower(); s <= interval.upper(); ++s)
|
||||
BEAST_EXPECT(lm.haveLedger(s));
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
using namespace test::jtx;
|
||||
FeatureBitset const all{supported_amendments() - featureXahauGenesis};
|
||||
testCanSkipPinnedGap();
|
||||
testWithFeats(all);
|
||||
testPinUnpinSymmetry();
|
||||
testSetPinnedRangesImmediateMerge();
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -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(
|
||||
@@ -1878,6 +1878,13 @@ class MPToken_test : public beast::unit_test::suite
|
||||
[jss::Amount] = mpt.getJson(JsonOptions::none);
|
||||
test(jv, sfAmounts.jsonName.c_str());
|
||||
}
|
||||
// ClaimReward
|
||||
{
|
||||
Json::Value jv = reward::claim(alice);
|
||||
jv[sfClaimCurrency.jsonName][jss::mpt_issuance_id] =
|
||||
to_string(issue);
|
||||
test(jv, sfClaimCurrency.jsonName.c_str());
|
||||
}
|
||||
}
|
||||
for (const auto& str : txWithAmounts)
|
||||
printf("%s\n", str.c_str());
|
||||
@@ -1903,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();
|
||||
@@ -1912,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
|
||||
|
||||
+109
-43
@@ -5104,11 +5104,39 @@ public:
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
// OfferCreate
|
||||
for (bool const withXahauV1 : {true, false})
|
||||
// OfferID is only valid when Hooks are enabled.
|
||||
{
|
||||
auto const amend = withXahauV1 ? features : features - fixXahauV1;
|
||||
Env env{*this, amend};
|
||||
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};
|
||||
auto const gw = Account{"gateway"};
|
||||
auto const alice = Account{"alice"};
|
||||
auto const USD = gw["USD"];
|
||||
@@ -5192,10 +5220,8 @@ public:
|
||||
}
|
||||
|
||||
// OfferCancel
|
||||
for (bool const withXahauV1 : {true, false})
|
||||
{
|
||||
auto const amend = withXahauV1 ? features : features - fixXahauV1;
|
||||
Env env{*this, amend};
|
||||
Env env{*this, features};
|
||||
auto const gw = Account{"gateway"};
|
||||
auto const alice = Account{"alice"};
|
||||
auto const USD = gw["USD"];
|
||||
@@ -5227,24 +5253,10 @@ public:
|
||||
env(offer(alice, XRP(50), USD(50)));
|
||||
env.close();
|
||||
|
||||
if (withXahauV1)
|
||||
{
|
||||
env(offer_cancel(alice),
|
||||
offer_id(offerId),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
auto const offerLE = env.le(keylet::unchecked(offerId));
|
||||
BEAST_EXPECT(!offerLE);
|
||||
}
|
||||
else
|
||||
{
|
||||
env(offer_cancel(alice),
|
||||
offer_id(offerId),
|
||||
ter(temBAD_SEQUENCE));
|
||||
env.close();
|
||||
auto const offerLE = env.le(keylet::unchecked(offerId));
|
||||
BEAST_EXPECT(offerLE);
|
||||
}
|
||||
env(offer_cancel(alice), offer_id(offerId), ter(tesSUCCESS));
|
||||
env.close();
|
||||
auto const offerLE = env.le(keylet::unchecked(offerId));
|
||||
BEAST_EXPECT(!offerLE);
|
||||
}
|
||||
|
||||
// no offer id or offer sequence
|
||||
@@ -5266,24 +5278,12 @@ public:
|
||||
env(offer(alice, XRP(50), USD(50)));
|
||||
env.close();
|
||||
|
||||
if (withXahauV1)
|
||||
{
|
||||
env(offer_cancel(alice, offerSeqId),
|
||||
offer_id(offerId),
|
||||
ter(temBAD_SEQUENCE));
|
||||
env.close();
|
||||
auto const offerLE = env.le(keylet::unchecked(offerId));
|
||||
BEAST_EXPECT(offerLE);
|
||||
}
|
||||
else
|
||||
{
|
||||
env(offer_cancel(alice, offerSeqId),
|
||||
offer_id(offerId),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
auto const offerLE = env.le(keylet::unchecked(offerId));
|
||||
BEAST_EXPECT(!offerLE);
|
||||
}
|
||||
env(offer_cancel(alice, offerSeqId),
|
||||
offer_id(offerId),
|
||||
ter(temBAD_SEQUENCE));
|
||||
env.close();
|
||||
auto const offerLE = env.le(keylet::unchecked(offerId));
|
||||
BEAST_EXPECT(offerLE);
|
||||
}
|
||||
|
||||
// both offer id and offer sequence 0
|
||||
@@ -5511,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)
|
||||
{
|
||||
@@ -5574,6 +5639,7 @@ public:
|
||||
testRmSmallIncreasedQOffersIOU(features);
|
||||
testOfferID(features);
|
||||
testFillOrKill(features);
|
||||
testFixCancelOfferExploit(features);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -568,6 +568,46 @@ private:
|
||||
Oracle oracle(env, {.owner = owner});
|
||||
oracle.set(UpdateArg{.series = {{"XRP", "USD", 742, 2}}});
|
||||
}
|
||||
|
||||
// Should be same order as creation
|
||||
Env env(*this);
|
||||
auto const baseFee =
|
||||
static_cast<int>(env.current()->fees().base.drops());
|
||||
|
||||
auto test = [&](Env& env, DataSeries const& series) {
|
||||
env.fund(XRP(1'000), owner);
|
||||
Oracle oracle(
|
||||
env, {.owner = owner, .series = series, .fee = baseFee});
|
||||
BEAST_EXPECT(oracle.exists());
|
||||
auto sle = env.le(keylet::oracle(owner, oracle.documentID()));
|
||||
BEAST_EXPECT(
|
||||
sle->getFieldArray(sfPriceDataSeries).size() == series.size());
|
||||
|
||||
auto const beforeQuoteAssetName1 =
|
||||
sle->getFieldArray(sfPriceDataSeries)[0]
|
||||
.getFieldCurrency(sfQuoteAsset)
|
||||
.getText();
|
||||
auto const beforeQuoteAssetName2 =
|
||||
sle->getFieldArray(sfPriceDataSeries)[1]
|
||||
.getFieldCurrency(sfQuoteAsset)
|
||||
.getText();
|
||||
|
||||
oracle.set(UpdateArg{.series = series, .fee = baseFee});
|
||||
sle = env.le(keylet::oracle(owner, oracle.documentID()));
|
||||
|
||||
auto const afterQuoteAssetName1 =
|
||||
sle->getFieldArray(sfPriceDataSeries)[0]
|
||||
.getFieldCurrency(sfQuoteAsset)
|
||||
.getText();
|
||||
auto const afterQuoteAssetName2 =
|
||||
sle->getFieldArray(sfPriceDataSeries)[1]
|
||||
.getFieldCurrency(sfQuoteAsset)
|
||||
.getText();
|
||||
|
||||
BEAST_EXPECT(afterQuoteAssetName1 == beforeQuoteAssetName1);
|
||||
BEAST_EXPECT(afterQuoteAssetName2 == beforeQuoteAssetName2);
|
||||
};
|
||||
test(env, {{"XRP", "USD", 742, 2}, {"XRP", "EUR", 711, 2}});
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -413,7 +413,7 @@ struct Remit_test : public beast::unit_test::suite
|
||||
|
||||
// tecNO_PERMISSION - inform account is an AMM
|
||||
{
|
||||
Env env{*this, features};
|
||||
Env env{*this, features | featureAMM};
|
||||
env.fund(XRP(1000), gw, alice, bob);
|
||||
env.close();
|
||||
|
||||
@@ -2902,7 +2902,7 @@ struct Remit_test : public beast::unit_test::suite
|
||||
auto const gw = Account("gw");
|
||||
auto const USD = gw["USD"];
|
||||
|
||||
Env env{*this, features};
|
||||
Env env{*this, features | featureAMM};
|
||||
env.fund(XRP(10'000'000), alice, gw);
|
||||
env.close();
|
||||
|
||||
|
||||
@@ -22,10 +22,20 @@
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/app/main/NodeStoreScheduler.h>
|
||||
#include <xrpld/app/misc/SHAMapStore.h>
|
||||
#include <xrpld/app/misc/SHAMapStoreImp.h>
|
||||
#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 {
|
||||
@@ -43,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)
|
||||
{
|
||||
@@ -51,6 +74,25 @@ class SHAMapStore_test : public beast::unit_test::suite
|
||||
return cfg;
|
||||
}
|
||||
|
||||
static auto
|
||||
pinnedPersistent(
|
||||
std::unique_ptr<Config> cfg,
|
||||
std::string const& dbPath,
|
||||
std::string const& nodePath,
|
||||
std::string const& pinnedPath)
|
||||
{
|
||||
cfg = onlineDelete(std::move(cfg));
|
||||
cfg->legacy("database_path", dbPath);
|
||||
cfg->overwrite(SECTION_RELATIONAL_DB, "backend", "sqlite");
|
||||
|
||||
auto& section = cfg->section(ConfigSection::nodeDatabase());
|
||||
section.set("type", "rwdb");
|
||||
section.set("path", nodePath);
|
||||
section.set("pinned_type", "nudb");
|
||||
section.set("pinned_path", pinnedPath);
|
||||
return cfg;
|
||||
}
|
||||
|
||||
bool
|
||||
goodLedger(
|
||||
jtx::Env& env,
|
||||
@@ -285,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);
|
||||
@@ -549,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()
|
||||
{
|
||||
@@ -648,13 +1106,202 @@ public:
|
||||
BEAST_EXPECT(dbr->getName() == "3");
|
||||
}
|
||||
|
||||
void
|
||||
testPinnedRangeRestoreRequiresPinnedData()
|
||||
{
|
||||
testcase("pinned range restore requires pinned data");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
beast::temp_dir tempDir;
|
||||
auto const tempPath = boost::filesystem::path(tempDir.path());
|
||||
auto const dbPath = (tempPath / "db").string();
|
||||
auto const nodePath = (tempPath / "node").string();
|
||||
auto const pinnedPath = (tempPath / "pinned").string();
|
||||
|
||||
boost::filesystem::create_directories(dbPath);
|
||||
boost::filesystem::create_directories(nodePath);
|
||||
boost::filesystem::create_directories(pinnedPath);
|
||||
|
||||
{
|
||||
Env env(
|
||||
*this,
|
||||
envconfig(pinnedPersistent, dbPath, nodePath, pinnedPath));
|
||||
|
||||
RangeSet<std::uint32_t> ranges;
|
||||
ranges.insert(range(100u, 200u));
|
||||
env.app().getSHAMapStore().setPinnedRanges(ranges);
|
||||
|
||||
NodeStoreScheduler scheduler(env.app().getJobQueue());
|
||||
auto const journal = env.app().journal("SHAMapStoreTest");
|
||||
|
||||
try
|
||||
{
|
||||
SHAMapStoreImp restoreCheck(env.app(), scheduler, journal);
|
||||
restoreCheck.start();
|
||||
fail(
|
||||
"Expected startup failure for stale persisted pinned "
|
||||
"ranges");
|
||||
}
|
||||
catch (std::runtime_error const& e)
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
std::string(e.what()).find(
|
||||
"Persisted pinned interval 100-200") !=
|
||||
std::string::npos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: compare two RangeSet<uint32_t> for set-equality. The
|
||||
// test cares about which sequences are in the result, not the
|
||||
// particular interval representation.
|
||||
static bool
|
||||
sameRanges(
|
||||
RangeSet<std::uint32_t> const& a,
|
||||
RangeSet<std::uint32_t> const& b)
|
||||
{
|
||||
return a == b;
|
||||
}
|
||||
|
||||
void
|
||||
testComputeOnlineDeleteTargets()
|
||||
{
|
||||
// detail::computeOnlineDeleteTargets is the lifted interval
|
||||
// arithmetic used by SHAMapStoreImp::clearSqlRanges to decide
|
||||
// which ledger sequences to actually delete during online
|
||||
// delete. The base window is [minSeq, lastRotated - 1] and
|
||||
// pinned ranges are subtracted from it. The result must:
|
||||
// (a) be empty when the base is empty or fully pinned,
|
||||
// (b) preserve all pinned seqs (they survive rotation),
|
||||
// (c) cover every non-pinned seq in the base.
|
||||
// Lifting the math out keeps the contract testable without
|
||||
// standing up a full SHAMapStoreImp + database fixture.
|
||||
testcase("detail::computeOnlineDeleteTargets");
|
||||
|
||||
using detail::computeOnlineDeleteTargets;
|
||||
|
||||
// Empty base window (minSeq >= lastRotated): no work.
|
||||
{
|
||||
RangeSet<std::uint32_t> empty;
|
||||
BEAST_EXPECT(computeOnlineDeleteTargets(100, 100, empty).empty());
|
||||
BEAST_EXPECT(computeOnlineDeleteTargets(101, 100, empty).empty());
|
||||
}
|
||||
|
||||
// No pins: the entire base window is the target.
|
||||
{
|
||||
RangeSet<std::uint32_t> empty;
|
||||
RangeSet<std::uint32_t> expected;
|
||||
expected.insert(range(50u, 99u)); // [50, 100-1]
|
||||
BEAST_EXPECT(sameRanges(
|
||||
computeOnlineDeleteTargets(50, 100, empty), expected));
|
||||
}
|
||||
|
||||
// Pins fully cover the base window: nothing to delete. This
|
||||
// is the bug the pinning feature exists to prevent — pinned
|
||||
// ranges getting silently rotated away.
|
||||
{
|
||||
RangeSet<std::uint32_t> pinned;
|
||||
pinned.insert(range(50u, 99u));
|
||||
BEAST_EXPECT(computeOnlineDeleteTargets(50, 100, pinned).empty());
|
||||
}
|
||||
|
||||
// Pins extend beyond the base window: still nothing to delete.
|
||||
{
|
||||
RangeSet<std::uint32_t> pinned;
|
||||
pinned.insert(range(0u, 1000u));
|
||||
BEAST_EXPECT(computeOnlineDeleteTargets(50, 100, pinned).empty());
|
||||
}
|
||||
|
||||
// Pins cover only the leading section of the base. Result is
|
||||
// the trailing remainder.
|
||||
{
|
||||
RangeSet<std::uint32_t> pinned;
|
||||
pinned.insert(range(50u, 70u));
|
||||
RangeSet<std::uint32_t> expected;
|
||||
expected.insert(range(71u, 99u));
|
||||
BEAST_EXPECT(sameRanges(
|
||||
computeOnlineDeleteTargets(50, 100, pinned), expected));
|
||||
}
|
||||
|
||||
// Pins cover only the trailing section. Result is leading.
|
||||
{
|
||||
RangeSet<std::uint32_t> pinned;
|
||||
pinned.insert(range(80u, 99u));
|
||||
RangeSet<std::uint32_t> expected;
|
||||
expected.insert(range(50u, 79u));
|
||||
BEAST_EXPECT(sameRanges(
|
||||
computeOnlineDeleteTargets(50, 100, pinned), expected));
|
||||
}
|
||||
|
||||
// Pins cut a hole in the middle. Result is two disjoint
|
||||
// intervals — both get deleted, the hole is preserved.
|
||||
{
|
||||
RangeSet<std::uint32_t> pinned;
|
||||
pinned.insert(range(60u, 80u));
|
||||
RangeSet<std::uint32_t> expected;
|
||||
expected.insert(range(50u, 59u));
|
||||
expected.insert(range(81u, 99u));
|
||||
BEAST_EXPECT(sameRanges(
|
||||
computeOnlineDeleteTargets(50, 100, pinned), expected));
|
||||
}
|
||||
|
||||
// Multiple disjoint pinned ranges all inside the base. Result
|
||||
// is the base minus all pinned holes.
|
||||
{
|
||||
RangeSet<std::uint32_t> pinned;
|
||||
pinned.insert(range(55u, 60u));
|
||||
pinned.insert(range(70u, 75u));
|
||||
pinned.insert(range(90u, 95u));
|
||||
RangeSet<std::uint32_t> expected;
|
||||
expected.insert(range(50u, 54u));
|
||||
expected.insert(range(61u, 69u));
|
||||
expected.insert(range(76u, 89u));
|
||||
expected.insert(range(96u, 99u));
|
||||
BEAST_EXPECT(sameRanges(
|
||||
computeOnlineDeleteTargets(50, 100, pinned), expected));
|
||||
}
|
||||
|
||||
// Pinned ranges entirely outside the base window: no effect.
|
||||
{
|
||||
RangeSet<std::uint32_t> pinned;
|
||||
pinned.insert(range(0u, 49u));
|
||||
pinned.insert(range(100u, 200u));
|
||||
RangeSet<std::uint32_t> expected;
|
||||
expected.insert(range(50u, 99u));
|
||||
BEAST_EXPECT(sameRanges(
|
||||
computeOnlineDeleteTargets(50, 100, pinned), expected));
|
||||
}
|
||||
|
||||
// Single-sequence base window with a single-sequence pin
|
||||
// matching exactly: empty result.
|
||||
{
|
||||
RangeSet<std::uint32_t> pinned;
|
||||
pinned.insert(range(99u, 99u));
|
||||
BEAST_EXPECT(computeOnlineDeleteTargets(99, 100, pinned).empty());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testComputeOnlineDeleteTargets();
|
||||
testClear();
|
||||
testAutomatic();
|
||||
testAutomatic(false);
|
||||
testAutomatic(true);
|
||||
testRotatingAccessors();
|
||||
testCanDelete();
|
||||
testArchiveReadSurvivesRotation();
|
||||
testNullModeLedgerProgression();
|
||||
testNullModeRejectsFullHistory();
|
||||
testExplicitOnlineDeleteZero();
|
||||
testNullModeRequiresHistory();
|
||||
testRetainedLedgerCloseTime();
|
||||
testNullBackendIsPerConfig();
|
||||
testResidentLedgerLookup();
|
||||
testNullFactoryDropsWrites();
|
||||
testRotate();
|
||||
testPinnedRangeRestoreRequiresPinnedData();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+259
-200
@@ -30,7 +30,10 @@
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/hook/Enum.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
#include <xrpl/protocol/PayChan.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/Sign.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <vector>
|
||||
|
||||
@@ -737,15 +740,7 @@ private:
|
||||
{
|
||||
auto const executions = meta[sfHookExecutions.jsonName];
|
||||
auto const execution = executions[0u][sfHookExecution.jsonName];
|
||||
bool const fixV2 = env.current()->rules().enabled(fixXahauV2);
|
||||
if (fixV2)
|
||||
{
|
||||
BEAST_EXPECT(execution[sfFlags.jsonName] == expected);
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_REQUIRE(!execution[sfFlags.jsonName]);
|
||||
}
|
||||
BEAST_EXPECT(execution[sfFlags.jsonName] == expected);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -878,7 +873,7 @@ private:
|
||||
test::jtx::Env env{
|
||||
*this,
|
||||
network::makeNetworkConfig(21337, "10", "1000000", "200000"),
|
||||
features};
|
||||
features | featureAMM};
|
||||
|
||||
auto const issuer = Account("gw");
|
||||
auto const account = Account("alice");
|
||||
@@ -928,7 +923,7 @@ private:
|
||||
test::jtx::Env env{
|
||||
*this,
|
||||
network::makeNetworkConfig(21337, "10", "1000000", "200000"),
|
||||
features};
|
||||
features | featureAMM};
|
||||
|
||||
auto const issuer = Account("gw");
|
||||
auto const account = Account("alice");
|
||||
@@ -983,7 +978,7 @@ private:
|
||||
cfg->FEES.reference_fee = XRPAmount(1);
|
||||
return cfg;
|
||||
}),
|
||||
features);
|
||||
features | featureAMM);
|
||||
|
||||
auto const issuer = Account("gw");
|
||||
auto const account = Account("alice");
|
||||
@@ -1043,7 +1038,7 @@ private:
|
||||
test::jtx::Env env{
|
||||
*this,
|
||||
network::makeNetworkConfig(21337, "10", "1000000", "200000"),
|
||||
features};
|
||||
features | featureAMM | featureAMMClawback};
|
||||
|
||||
auto const issuer = Account("gw");
|
||||
auto const account = Account("alice");
|
||||
@@ -1101,7 +1096,7 @@ private:
|
||||
test::jtx::Env env{
|
||||
*this,
|
||||
network::makeNetworkConfig(21337, "10", "1000000", "200000"),
|
||||
features};
|
||||
features | featureAMM};
|
||||
|
||||
auto const issuer = Account("gw");
|
||||
auto const account = Account("alice");
|
||||
@@ -1156,7 +1151,7 @@ private:
|
||||
test::jtx::Env env{
|
||||
*this,
|
||||
network::makeNetworkConfig(21337, "10", "1000000", "200000"),
|
||||
features};
|
||||
features | featureAMM};
|
||||
|
||||
auto const issuer = Account("gw");
|
||||
auto const account = Account("alice");
|
||||
@@ -1203,7 +1198,7 @@ private:
|
||||
test::jtx::Env env{
|
||||
*this,
|
||||
network::makeNetworkConfig(21337, "10", "1000000", "200000"),
|
||||
features};
|
||||
features | featureAMM};
|
||||
|
||||
auto const issuer = Account("gw");
|
||||
auto const account = Account("alice");
|
||||
@@ -1647,8 +1642,12 @@ private:
|
||||
features};
|
||||
|
||||
auto const account = Account("alice");
|
||||
auto const issuer = Account("issuer");
|
||||
env.fund(XRP(1000), account, issuer);
|
||||
auto const issuer = Account::master;
|
||||
env.fund(XRP(1000), account);
|
||||
env.close();
|
||||
|
||||
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
// set tsh collect
|
||||
@@ -1680,8 +1679,8 @@ private:
|
||||
features};
|
||||
|
||||
auto const account = Account("alice");
|
||||
auto const issuer = Account("issuer");
|
||||
env.fund(XRP(1000), account, issuer);
|
||||
auto const issuer = Account::master;
|
||||
env.fund(XRP(1000), account);
|
||||
env.close();
|
||||
|
||||
// set tsh collect
|
||||
@@ -1963,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)
|
||||
@@ -2118,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{
|
||||
@@ -2126,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);
|
||||
@@ -2155,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
|
||||
@@ -2262,16 +2270,10 @@ private:
|
||||
setTSHHook(env, account, testStrong);
|
||||
|
||||
// cancel escrow
|
||||
Json::Value tx;
|
||||
if (!env.current()->rules().enabled(fixXahauV1))
|
||||
{
|
||||
tx = cancel(account, account, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
tx = cancel(account, account);
|
||||
}
|
||||
env(tx, escrow_id(escrowId), fee(XRP(1)), ter(tesSUCCESS));
|
||||
env(cancel(account, account),
|
||||
escrow_id(escrowId),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// verify tsh hook triggered
|
||||
@@ -2316,16 +2318,10 @@ private:
|
||||
setTSHHook(env, dest, testStrong);
|
||||
|
||||
// cancel escrow
|
||||
Json::Value tx;
|
||||
if (!env.current()->rules().enabled(fixXahauV1))
|
||||
{
|
||||
tx = cancel(account, account, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
tx = cancel(account, account);
|
||||
}
|
||||
env(tx, escrow_id(escrowId), fee(XRP(1)), ter(tesSUCCESS));
|
||||
env(cancel(account, account),
|
||||
escrow_id(escrowId),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// verify tsh hook triggered
|
||||
@@ -2374,16 +2370,10 @@ private:
|
||||
setTSHHook(env, dest, testStrong);
|
||||
|
||||
// cancel escrow
|
||||
Json::Value tx;
|
||||
if (!env.current()->rules().enabled(fixXahauV1))
|
||||
{
|
||||
tx = cancel(dest, account, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
tx = cancel(dest, account);
|
||||
}
|
||||
env(tx, escrow_id(escrowId), fee(XRP(1)), ter(tesSUCCESS));
|
||||
env(cancel(dest, account),
|
||||
escrow_id(escrowId),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// verify tsh hook triggered
|
||||
@@ -2392,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{
|
||||
@@ -2400,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);
|
||||
@@ -2425,23 +2420,16 @@ private:
|
||||
setTSHHook(env, account, testStrong);
|
||||
|
||||
// cancel escrow
|
||||
bool const fixV1 = env.current()->rules().enabled(fixXahauV1);
|
||||
Json::Value tx;
|
||||
if (!fixV1)
|
||||
{
|
||||
tx = cancel(dest, account, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
tx = cancel(dest, account);
|
||||
}
|
||||
env(tx, escrow_id(escrowId), fee(XRP(1)), ter(tesSUCCESS));
|
||||
env(cancel(dest, account),
|
||||
escrow_id(escrowId),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// verify tsh hook triggered
|
||||
auto const expected =
|
||||
(fixV1 ? (testStrong ? tshSTRONG : tshSTRONG)
|
||||
: (testStrong ? tshNONE : tshNONE));
|
||||
auto const expected = !withDestCancel ? tshSTRONG
|
||||
: !testStrong && withIOUIssuerWeakTSH ? tshWEAK
|
||||
: tshNONE;
|
||||
testTSHStrongWeak(env, expected, __LINE__);
|
||||
}
|
||||
|
||||
@@ -2490,16 +2478,10 @@ private:
|
||||
setTSHHook(env, gw, testStrong);
|
||||
|
||||
// cancel escrow
|
||||
Json::Value tx;
|
||||
if (!env.current()->rules().enabled(fixXahauV1))
|
||||
{
|
||||
tx = cancel(account, account, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
tx = cancel(account, account);
|
||||
}
|
||||
env(tx, escrow_id(escrowId), fee(XRP(1)), ter(tesSUCCESS));
|
||||
env(cancel(account, account),
|
||||
escrow_id(escrowId),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// verify tsh hook triggered
|
||||
@@ -2915,16 +2897,10 @@ private:
|
||||
setTSHHook(env, account, testStrong);
|
||||
|
||||
// finish escrow
|
||||
Json::Value tx;
|
||||
if (!env.current()->rules().enabled(fixXahauV1))
|
||||
{
|
||||
tx = finish(account, account, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
tx = finish(account, account);
|
||||
}
|
||||
env(tx, escrow_id(escrowId), fee(XRP(1)), ter(tesSUCCESS));
|
||||
env(finish(account, account),
|
||||
escrow_id(escrowId),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// verify tsh hook triggered
|
||||
@@ -2963,23 +2939,14 @@ private:
|
||||
setTSHHook(env, dest, testStrong);
|
||||
|
||||
// finish escrow
|
||||
bool const fixV1 = env.current()->rules().enabled(fixXahauV1);
|
||||
Json::Value tx;
|
||||
if (!fixV1)
|
||||
{
|
||||
tx = finish(account, account, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
tx = finish(account, account);
|
||||
}
|
||||
env(tx, escrow_id(escrowId), fee(XRP(1)), ter(tesSUCCESS));
|
||||
env(finish(account, account),
|
||||
escrow_id(escrowId),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// verify tsh hook triggered
|
||||
auto const expected =
|
||||
(fixV1 ? (testStrong ? tshSTRONG : tshSTRONG)
|
||||
: (testStrong ? tshNONE : tshNONE));
|
||||
auto const expected = testStrong ? tshSTRONG : tshSTRONG;
|
||||
testTSHStrongWeak(env, expected, __LINE__);
|
||||
}
|
||||
|
||||
@@ -3015,16 +2982,10 @@ private:
|
||||
setTSHHook(env, dest, testStrong);
|
||||
|
||||
// finish escrow
|
||||
Json::Value tx;
|
||||
if (!env.current()->rules().enabled(fixXahauV1))
|
||||
{
|
||||
tx = finish(dest, account, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
tx = finish(dest, account);
|
||||
}
|
||||
env(tx, escrow_id(escrowId), fee(XRP(1)), ter(tesSUCCESS));
|
||||
env(finish(dest, account),
|
||||
escrow_id(escrowId),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// verify tsh hook triggered
|
||||
@@ -3063,23 +3024,14 @@ private:
|
||||
setTSHHook(env, account, testStrong);
|
||||
|
||||
// finish escrow
|
||||
bool const fixV1 = env.current()->rules().enabled(fixXahauV1);
|
||||
Json::Value tx;
|
||||
if (!fixV1)
|
||||
{
|
||||
tx = finish(dest, account, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
tx = finish(dest, account);
|
||||
}
|
||||
env(tx, escrow_id(escrowId), fee(XRP(1)), ter(tesSUCCESS));
|
||||
env(finish(dest, account),
|
||||
escrow_id(escrowId),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// verify tsh hook triggered
|
||||
auto const expected =
|
||||
(fixV1 ? (testStrong ? tshSTRONG : tshSTRONG)
|
||||
: (testStrong ? tshNONE : tshNONE));
|
||||
auto const expected = testStrong ? tshSTRONG : tshSTRONG;
|
||||
testTSHStrongWeak(env, expected, __LINE__);
|
||||
}
|
||||
|
||||
@@ -3125,17 +3077,10 @@ private:
|
||||
setTSHHook(env, gw, testStrong);
|
||||
|
||||
// finish escrow
|
||||
bool const fixV1 = env.current()->rules().enabled(fixXahauV1);
|
||||
Json::Value tx;
|
||||
if (!fixV1)
|
||||
{
|
||||
tx = finish(dest, account, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
tx = finish(dest, account);
|
||||
}
|
||||
env(tx, escrow_id(escrowId), fee(XRP(1)), ter(tesSUCCESS));
|
||||
env(finish(dest, account),
|
||||
escrow_id(escrowId),
|
||||
fee(XRP(1)),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// verify tsh hook triggered
|
||||
@@ -3441,10 +3386,7 @@ private:
|
||||
env.close();
|
||||
|
||||
// verify tsh hook triggered
|
||||
bool const fixV2 = env.current()->rules().enabled(fixXahauV2);
|
||||
auto const expected =
|
||||
(fixV2 ? (testStrong ? tshNONE : tshWEAK)
|
||||
: (testStrong ? tshSTRONG : tshSTRONG));
|
||||
auto const expected = testStrong ? tshNONE : tshWEAK;
|
||||
testTSHStrongWeak(env, expected, __LINE__);
|
||||
}
|
||||
}
|
||||
@@ -4495,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 =
|
||||
@@ -6303,10 +6247,7 @@ private:
|
||||
env.close();
|
||||
|
||||
// verify tsh hook triggered
|
||||
bool const fixV2 = env.current()->rules().enabled(fixXahauV2);
|
||||
auto const expected =
|
||||
(fixV2 ? (testStrong ? tshSTRONG : tshSTRONG)
|
||||
: (testStrong ? tshNONE : tshNONE));
|
||||
auto const expected = testStrong ? tshSTRONG : tshSTRONG;
|
||||
testTSHStrongWeak(env, expected, __LINE__);
|
||||
}
|
||||
|
||||
@@ -6387,10 +6328,7 @@ private:
|
||||
env.close();
|
||||
|
||||
// verify tsh hook triggered
|
||||
bool const fixV2 = env.current()->rules().enabled(fixXahauV2);
|
||||
auto const expected =
|
||||
(fixV2 ? (testStrong ? tshSTRONG : tshSTRONG)
|
||||
: (testStrong ? tshNONE : tshNONE));
|
||||
auto const expected = testStrong ? tshSTRONG : tshSTRONG;
|
||||
testTSHStrongWeak(env, expected, __LINE__);
|
||||
}
|
||||
}
|
||||
@@ -6496,15 +6434,12 @@ private:
|
||||
env.close();
|
||||
|
||||
// verify tsh hook triggered
|
||||
bool const fixV1 = env.current()->rules().enabled(fixXahauV1);
|
||||
bool const withIOUIssuerWeakTSH =
|
||||
env.current()->rules().enabled(featureIOUIssuerWeakTSH);
|
||||
|
||||
auto const expected =
|
||||
(fixV1
|
||||
? (testStrong ? tshNONE
|
||||
: (withIOUIssuerWeakTSH ? tshWEAK : tshNONE))
|
||||
: (testStrong ? tshSTRONG : tshSTRONG));
|
||||
auto const expected = testStrong
|
||||
? tshNONE
|
||||
: (withIOUIssuerWeakTSH ? tshWEAK : tshNONE);
|
||||
testTSHStrongWeak(env, expected, __LINE__);
|
||||
}
|
||||
|
||||
@@ -6603,14 +6538,11 @@ private:
|
||||
env.close();
|
||||
|
||||
// verify tsh hook triggered
|
||||
bool const fixV1 = env.current()->rules().enabled(fixXahauV1);
|
||||
bool const withIOUIssuerWeakTSH =
|
||||
env.current()->rules().enabled(featureIOUIssuerWeakTSH);
|
||||
auto const expected =
|
||||
(fixV1
|
||||
? (testStrong ? tshNONE
|
||||
: (withIOUIssuerWeakTSH ? tshWEAK : tshNONE))
|
||||
: (testStrong ? tshSTRONG : tshSTRONG));
|
||||
auto const expected = testStrong
|
||||
? tshNONE
|
||||
: (withIOUIssuerWeakTSH ? tshWEAK : tshNONE);
|
||||
testTSHStrongWeak(env, expected, __LINE__);
|
||||
}
|
||||
|
||||
@@ -6660,15 +6592,12 @@ private:
|
||||
env.close();
|
||||
|
||||
// verify tsh hook triggered
|
||||
bool const fixV1 = env.current()->rules().enabled(fixXahauV1);
|
||||
bool const withIOUIssuerWeakTSH =
|
||||
env.current()->rules().enabled(featureIOUIssuerWeakTSH);
|
||||
|
||||
auto const expected =
|
||||
(fixV1
|
||||
? (testStrong ? tshNONE
|
||||
: (withIOUIssuerWeakTSH ? tshWEAK : tshNONE))
|
||||
: (testStrong ? tshSTRONG : tshSTRONG));
|
||||
auto const expected = testStrong
|
||||
? tshNONE
|
||||
: (withIOUIssuerWeakTSH ? tshWEAK : tshNONE);
|
||||
testTSHStrongWeak(env, expected, __LINE__);
|
||||
}
|
||||
|
||||
@@ -7728,7 +7657,6 @@ private:
|
||||
env.close();
|
||||
|
||||
auto const preDest = env.balance(dest);
|
||||
bool const withFix = env.current()->rules().enabled(fixXahauV2);
|
||||
|
||||
env.app().openLedger().modify([&](OpenView& view, beast::Journal j) {
|
||||
auto const tx =
|
||||
@@ -7736,24 +7664,15 @@ private:
|
||||
auto result =
|
||||
ripple::apply(env.app(), view, *tx, tapNONE, env.journal);
|
||||
|
||||
bool const applyResult = withFix ? false : true;
|
||||
if (withFix)
|
||||
{
|
||||
BEAST_EXPECT(result.ter == tefNONDIR_EMIT);
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(result.ter == tesSUCCESS);
|
||||
}
|
||||
BEAST_EXPECT(result.applied == applyResult);
|
||||
BEAST_EXPECT(result.ter == tefNONDIR_EMIT);
|
||||
BEAST_EXPECT(!result.applied);
|
||||
return result.applied;
|
||||
});
|
||||
|
||||
env.close();
|
||||
|
||||
auto const postDest = env.balance(dest);
|
||||
auto const postValue = withFix ? XRP(0) : XRP(1);
|
||||
BEAST_EXPECT(postDest == preDest + postValue);
|
||||
BEAST_EXPECT(postDest == preDest);
|
||||
|
||||
for (size_t i = 0; i < 4; i++)
|
||||
{
|
||||
@@ -7764,8 +7683,7 @@ private:
|
||||
}
|
||||
|
||||
auto const postDest1 = env.balance(dest);
|
||||
auto const postValue1 = withFix ? XRP(0) : XRP(2);
|
||||
BEAST_EXPECT(postDest1 == postDest + postValue1);
|
||||
BEAST_EXPECT(postDest1 == postDest);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -8379,16 +8297,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] =
|
||||
@@ -8441,6 +8359,150 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
// Builds a manifest signed by `master`, nominating `ephemeral` as the
|
||||
// signing key. A sequence of UINT32_MAX makes it a revocation, which by
|
||||
// definition carries no signing key.
|
||||
static std::string
|
||||
makeManifestString(
|
||||
jtx::Account const& master,
|
||||
jtx::Account const& ephemeral,
|
||||
std::uint32_t seq)
|
||||
{
|
||||
STObject st(sfGeneric);
|
||||
st[sfSequence] = seq;
|
||||
st[sfPublicKey] = master.pk();
|
||||
|
||||
if (seq != std::numeric_limits<std::uint32_t>::max())
|
||||
{
|
||||
st[sfSigningPubKey] = ephemeral.pk();
|
||||
sign(
|
||||
st,
|
||||
HashPrefix::manifest,
|
||||
*publicKeyType(ephemeral.pk()),
|
||||
ephemeral.sk());
|
||||
}
|
||||
|
||||
sign(
|
||||
st,
|
||||
HashPrefix::manifest,
|
||||
*publicKeyType(master.pk()),
|
||||
master.sk(),
|
||||
sfMasterSignature);
|
||||
|
||||
Serializer s;
|
||||
st.add(s);
|
||||
return std::string(static_cast<char const*>(s.data()), s.size());
|
||||
}
|
||||
|
||||
// A manifest transaction carries no account signature, so it cannot be
|
||||
// submitted through env() the way a signed transaction can. Returns the
|
||||
// resulting transaction id so the caller can inspect its metadata.
|
||||
uint256
|
||||
submitManifest(jtx::Env& env, std::string const& manifest)
|
||||
{
|
||||
Json::Value params;
|
||||
params[jss::manifest] = strHex(manifest);
|
||||
auto const jrr = env.rpc("json", "submit", to_string(params));
|
||||
|
||||
auto const& result = jrr[jss::result];
|
||||
|
||||
if (!BEAST_EXPECT(
|
||||
result[jss::engine_result].asString() == "tesSUCCESS"))
|
||||
{
|
||||
log << "submitManifest: " << to_string(jrr) << std::endl;
|
||||
return beast::zero;
|
||||
}
|
||||
|
||||
// An error response carries no tx_json, and strUnHex("") yields an
|
||||
// engaged but empty Blob, so fromVoid() would memcpy from nullptr.
|
||||
auto const blob = strUnHex(result[jss::tx_json][jss::hash].asString());
|
||||
auto const hash =
|
||||
blob ? uint256::fromVoidChecked(*blob) : std::optional<uint256>{};
|
||||
|
||||
if (!BEAST_EXPECT(hash.has_value()))
|
||||
{
|
||||
log << "submitManifest: " << to_string(jrr) << std::endl;
|
||||
return beast::zero;
|
||||
}
|
||||
|
||||
return *hash;
|
||||
}
|
||||
|
||||
// SetManifest
|
||||
// | otxn | tsh | manifest |
|
||||
// | M | M | N/A |
|
||||
// | M | E | W | ephemeral key's logical account
|
||||
void
|
||||
testSetManifestTSH(FeatureBitset features)
|
||||
{
|
||||
using namespace test::jtx;
|
||||
using namespace std::literals;
|
||||
testcase("set manifest TSH");
|
||||
|
||||
// OnChainManifests is not yet Supported::yes, so enable it explicitly.
|
||||
features = features | featureOnChainManifests;
|
||||
|
||||
// otxn: master
|
||||
// tsh: ephemeral
|
||||
// w/s: weak
|
||||
//
|
||||
// The ephemeral account is only named by the manifest, so it may
|
||||
// observe the transaction but not rollback it. It therefore fires only
|
||||
// when it has asked to collect.
|
||||
for (bool const testStrong : {true, false})
|
||||
{
|
||||
test::jtx::Env env{
|
||||
*this,
|
||||
network::makeNetworkConfig(21337, "10", "1000000", "200000"),
|
||||
features};
|
||||
|
||||
auto const master = Account("master", KeyType::ed25519);
|
||||
auto const ephemeral = Account("ephemeral", KeyType::ed25519);
|
||||
env.fund(XRP(1000), master, ephemeral);
|
||||
env.close();
|
||||
|
||||
if (!testStrong)
|
||||
addWeakTSH(env, ephemeral);
|
||||
|
||||
setTSHHook(env, ephemeral, testStrong);
|
||||
|
||||
auto const txHash =
|
||||
submitManifest(env, makeManifestString(master, ephemeral, 1));
|
||||
env.close();
|
||||
|
||||
// A strong hook on a weak stake holder is never reached.
|
||||
auto const expected = testStrong ? tshNONE : tshWEAK;
|
||||
testTSHStrongWeak(env, txHash, expected, __LINE__);
|
||||
}
|
||||
|
||||
// A revocation names no signing key, so there is no ephemeral stake
|
||||
// holder to notify at all.
|
||||
{
|
||||
test::jtx::Env env{
|
||||
*this,
|
||||
network::makeNetworkConfig(21337, "10", "1000000", "200000"),
|
||||
features};
|
||||
|
||||
auto const master = Account("master", KeyType::ed25519);
|
||||
auto const ephemeral = Account("ephemeral", KeyType::ed25519);
|
||||
env.fund(XRP(1000), master, ephemeral);
|
||||
env.close();
|
||||
|
||||
addWeakTSH(env, ephemeral);
|
||||
setTSHHook(env, ephemeral, false);
|
||||
|
||||
auto const txHash = submitManifest(
|
||||
env,
|
||||
makeManifestString(
|
||||
master,
|
||||
ephemeral,
|
||||
std::numeric_limits<std::uint32_t>::max()));
|
||||
env.close();
|
||||
|
||||
testTSHStrongWeak(env, txHash, tshNONE, __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testTSH(FeatureBitset features)
|
||||
{
|
||||
@@ -8470,10 +8532,9 @@ public:
|
||||
using namespace test::jtx;
|
||||
static FeatureBitset const all{supported_amendments()};
|
||||
|
||||
static std::array<FeatureBitset, 4> const feats{
|
||||
static std::array<FeatureBitset, 3> const feats{
|
||||
all,
|
||||
all - fixXahauV1 - fixXahauV2 - featureIOUIssuerWeakTSH,
|
||||
all - fixXahauV2 - featureIOUIssuerWeakTSH,
|
||||
all - featureEscrowDestinationCancel,
|
||||
all - featureIOUIssuerWeakTSH,
|
||||
};
|
||||
|
||||
@@ -8504,13 +8565,11 @@ public:
|
||||
};
|
||||
|
||||
SETHOOKTSH_TEST(1, false)
|
||||
SETHOOKTSH_TEST(2, false)
|
||||
SETHOOKTSH_TEST(3, true)
|
||||
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);
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(SetHookTSH3, app, ripple, 2);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
+1713
-161
File diff suppressed because it is too large.
Load diff
+1210
-313
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,975 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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.h>
|
||||
#include <test/jtx/network.h>
|
||||
#include <xrpld/app/ledger/OpenLedger.h>
|
||||
#include <xrpld/app/misc/HashRouter.h>
|
||||
#include <xrpld/app/misc/Manifest.h>
|
||||
#include <xrpld/app/misc/NetworkOPs.h>
|
||||
#include <xrpld/app/tx/apply.h>
|
||||
#include <xrpld/app/tx/detail/SetManifest.h>
|
||||
#include <xrpld/core/Config.h>
|
||||
#include <xrpld/ledger/OpenView.h>
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/basics/strHex.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/Sign.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/protocol/st.h>
|
||||
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
/** Tests the OnChainManifests amendment: the SetManifest transactor, the
|
||||
submit RPC path that builds its transaction, and reading manifests back out
|
||||
of the ledger into the manifest cache.
|
||||
*/
|
||||
struct SetManifest_test : public beast::unit_test::suite
|
||||
{
|
||||
// A manifest transaction is unsigned, so the network id is mandatory and
|
||||
// the network must be one that requires it (id > 1024).
|
||||
static std::unique_ptr<Config>
|
||||
makeConfig(std::string fee = "10")
|
||||
{
|
||||
return jtx::network::makeNetworkConfig(
|
||||
21337, std::move(fee), "1000000", "200000");
|
||||
}
|
||||
|
||||
/** Builds a manifest signed by `master` nominating `ephemeral`.
|
||||
|
||||
A sequence of UINT32_MAX makes it a revocation, which by definition
|
||||
names no signing key and carries no ephemeral signature.
|
||||
*/
|
||||
static std::string
|
||||
makeManifest(
|
||||
jtx::Account const& master,
|
||||
jtx::Account const& ephemeral,
|
||||
std::uint32_t seq)
|
||||
{
|
||||
STObject st(sfGeneric);
|
||||
st[sfSequence] = seq;
|
||||
st[sfPublicKey] = master.pk();
|
||||
|
||||
if (seq != std::numeric_limits<std::uint32_t>::max())
|
||||
{
|
||||
st[sfSigningPubKey] = ephemeral.pk();
|
||||
sign(
|
||||
st,
|
||||
HashPrefix::manifest,
|
||||
*publicKeyType(ephemeral.pk()),
|
||||
ephemeral.sk());
|
||||
}
|
||||
|
||||
sign(
|
||||
st,
|
||||
HashPrefix::manifest,
|
||||
*publicKeyType(master.pk()),
|
||||
master.sk(),
|
||||
sfMasterSignature);
|
||||
|
||||
Serializer s;
|
||||
st.add(s);
|
||||
return std::string(static_cast<char const*>(s.data()), s.size());
|
||||
}
|
||||
|
||||
/** Submits a manifest through the submit RPC.
|
||||
|
||||
The transaction is unsigned -- authority comes from the manifest's own
|
||||
master and ephemeral signatures -- so it cannot be submitted through
|
||||
env() the way a signed transaction can.
|
||||
*/
|
||||
static Json::Value
|
||||
submit(jtx::Env& env, std::string const& manifest)
|
||||
{
|
||||
Json::Value params;
|
||||
params[jss::manifest] = strHex(manifest);
|
||||
return env.rpc("json", "submit", to_string(params))[jss::result];
|
||||
}
|
||||
|
||||
/** Submits an already formed SetManifest transaction. */
|
||||
static Json::Value
|
||||
submit(jtx::Env& env, std::shared_ptr<STTx const> const& tx)
|
||||
{
|
||||
Serializer s;
|
||||
tx->add(s);
|
||||
return env.rpc("submit", strHex(s.slice()))[jss::result];
|
||||
}
|
||||
|
||||
static std::string
|
||||
engineResult(Json::Value const& result)
|
||||
{
|
||||
return result[jss::engine_result].asString();
|
||||
}
|
||||
|
||||
/** A well formed STObject that is not a well formed manifest.
|
||||
|
||||
The manifest format requires sfPublicKey and sfMasterSignature, so this
|
||||
parses as an object and then fails applyTemplate.
|
||||
*/
|
||||
static std::string
|
||||
makeUnparseableManifest()
|
||||
{
|
||||
STObject st(sfGeneric);
|
||||
st[sfSequence] = 1;
|
||||
|
||||
Serializer s;
|
||||
st.add(s);
|
||||
return std::string(static_cast<char const*>(s.data()), s.size());
|
||||
}
|
||||
|
||||
/** The envelope Submit would build for `manifest`, optionally tweaked.
|
||||
|
||||
makeSetManifestTx() rejects any manifest the transactor would reject,
|
||||
and checkValidity() rejects any envelope whose signatures do not check
|
||||
out, so several of the transactor's own rejections cannot be provoked
|
||||
through the submit RPC at all. Building the envelope here and applying
|
||||
it straight to the open view reaches them.
|
||||
*/
|
||||
static std::shared_ptr<STTx const>
|
||||
envelope(
|
||||
jtx::Env& env,
|
||||
std::string const& manifest,
|
||||
AccountID const& account,
|
||||
std::function<void(STObject&)> const& tweak = {})
|
||||
{
|
||||
auto const build = [&](XRPAmount fee, bool tweaked) {
|
||||
return std::make_shared<STTx const>(
|
||||
ttMANIFEST_SET, [&](STObject& obj) {
|
||||
obj.setAccountID(sfAccount, account);
|
||||
obj.setFieldU32(sfSequence, 0);
|
||||
obj.setFieldU32(sfNetworkID, env.app().config().NETWORK_ID);
|
||||
obj.setFieldAmount(sfFee, fee);
|
||||
obj.setFieldVL(sfSigningPubKey, Blob{});
|
||||
obj.setFieldVL(sfTxnSignature, Blob{});
|
||||
|
||||
SerialIter mit{makeSlice(manifest)};
|
||||
obj.peekFieldObject(sfManifest).set(mit);
|
||||
|
||||
if (tweaked && tweak)
|
||||
tweak(obj);
|
||||
});
|
||||
};
|
||||
|
||||
// The canonical Fee makeSetManifestTx() uses for the open ledger.
|
||||
SerialIter mit{makeSlice(manifest)};
|
||||
STObject const manifestObject{mit, sfManifest};
|
||||
return build(
|
||||
canonicalSetManifestFee(env.current()->fees().base, manifestObject)
|
||||
.value_or(XRPAmount{0}),
|
||||
true);
|
||||
}
|
||||
|
||||
/** A mutable copy of a ledger object, suitable for the RawView interface.
|
||||
|
||||
Round-tripped through the wire format rather than copy constructed.
|
||||
applyTemplate() rejects an object carrying a materialised soeDEFAULT
|
||||
field that holds its default value, and that is exactly what reading one
|
||||
out of a view hands back: an AccountRoot arrives with sfMintedNFTokens
|
||||
present and zero. Serializing omits those fields and deserializing
|
||||
re-materialises them.
|
||||
*/
|
||||
static std::shared_ptr<SLE>
|
||||
rawCopy(std::shared_ptr<SLE const> const& sle)
|
||||
{
|
||||
Serializer s;
|
||||
sle->add(s);
|
||||
SerialIter sit{s.slice()};
|
||||
return std::make_shared<SLE>(sit, sle->key());
|
||||
}
|
||||
|
||||
/** Removes a ledger object from a view, if present.
|
||||
|
||||
OpenView exposes only the RawView interface, so the entry has to be
|
||||
copied off the read side before it can be handed back to rawErase.
|
||||
*/
|
||||
static void
|
||||
rawErase(OpenView& view, Keylet const& kl)
|
||||
{
|
||||
if (auto const sle = view.read(kl))
|
||||
view.rawErase(rawCopy(sle));
|
||||
}
|
||||
|
||||
/** Applies `tx` to a throwaway copy of the open ledger and reports the TER.
|
||||
|
||||
`prepare` runs against the same view first, so a test can corrupt state
|
||||
that no ordinary transaction could produce. Nothing is retained.
|
||||
*/
|
||||
TER
|
||||
applyDirect(
|
||||
jtx::Env& env,
|
||||
std::shared_ptr<STTx const> const& tx,
|
||||
std::function<void(OpenView&)> const& prepare = {})
|
||||
{
|
||||
TER ret = tesSUCCESS;
|
||||
env.app().openLedger().modify([&](OpenView& view, beast::Journal j) {
|
||||
if (prepare)
|
||||
prepare(view);
|
||||
ret = ripple::apply(env.app(), view, *tx, tapNONE, j).ter;
|
||||
return false; // discard, corrupt or otherwise
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
testSubmission(FeatureBitset features)
|
||||
{
|
||||
testcase("submission");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, makeConfig(), features};
|
||||
|
||||
auto const master = Account("master", KeyType::ed25519);
|
||||
auto const ephemeral = Account("ephemeral", KeyType::ed25519);
|
||||
env.fund(XRP(1000), master);
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(
|
||||
engineResult(submit(env, makeManifest(master, ephemeral, 1))) ==
|
||||
"tesSUCCESS");
|
||||
env.close();
|
||||
|
||||
// A manifest is written twice so it can be found from either key, and
|
||||
// each copy points at the other.
|
||||
auto const byMaster = env.le(keylet::manifest(master.pk()));
|
||||
auto const byEphemeral = env.le(keylet::manifest(ephemeral.pk()));
|
||||
if (!BEAST_EXPECT(byMaster))
|
||||
return;
|
||||
if (!BEAST_EXPECT(byEphemeral))
|
||||
return;
|
||||
|
||||
BEAST_EXPECT(byMaster->getAccountID(sfAccount) == master.id());
|
||||
BEAST_EXPECT(byEphemeral->getAccountID(sfAccount) == master.id());
|
||||
BEAST_EXPECT(byMaster->getFieldU32(sfSequence) == 1);
|
||||
BEAST_EXPECT(
|
||||
byMaster->getFieldH256(sfManifestID) ==
|
||||
keylet::manifest(ephemeral.pk()).key);
|
||||
BEAST_EXPECT(
|
||||
byEphemeral->getFieldH256(sfManifestID) ==
|
||||
keylet::manifest(master.pk()).key);
|
||||
|
||||
// Both signatures are mirrored so the object can be verified, and
|
||||
// re-served, by any node that reads it.
|
||||
BEAST_EXPECT(byMaster->isFieldPresent(sfMasterSignature));
|
||||
BEAST_EXPECT(byMaster->isFieldPresent(sfSignature));
|
||||
|
||||
// The account root points at the master key's copy.
|
||||
auto const sleAcct = env.le(master);
|
||||
if (!BEAST_EXPECT(sleAcct))
|
||||
return;
|
||||
BEAST_EXPECT(
|
||||
sleAcct->getFieldH256(sfManifestID) ==
|
||||
keylet::manifest(master.pk()).key);
|
||||
|
||||
// The account sequence must be untouched: the transaction is unsigned
|
||||
// and pinned to sequence 0, so consuming a sequence would let a third
|
||||
// party burn the validator's sequence numbers -- and writing seq + 1
|
||||
// would reset the account to 1.
|
||||
BEAST_EXPECT(sleAcct->getFieldU32(sfSequence) == env.seq(master));
|
||||
}
|
||||
|
||||
void
|
||||
testUpdate(FeatureBitset features)
|
||||
{
|
||||
testcase("update and stale rejection");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, makeConfig(), features};
|
||||
|
||||
auto const master = Account("master", KeyType::ed25519);
|
||||
auto const eph1 = Account("eph1", KeyType::ed25519);
|
||||
auto const eph2 = Account("eph2", KeyType::ed25519);
|
||||
env.fund(XRP(1000), master);
|
||||
env.close();
|
||||
|
||||
submit(env, makeManifest(master, eph1, 1));
|
||||
env.close();
|
||||
|
||||
// Rotating the ephemeral key erases both old copies and writes two
|
||||
// new ones, so the two can never drift apart.
|
||||
BEAST_EXPECT(
|
||||
engineResult(submit(env, makeManifest(master, eph2, 2))) ==
|
||||
"tesSUCCESS");
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(!env.le(keylet::manifest(eph1.pk())));
|
||||
if (!BEAST_EXPECT(env.le(keylet::manifest(eph2.pk()))))
|
||||
return;
|
||||
BEAST_EXPECT(
|
||||
env.le(keylet::manifest(master.pk()))->getFieldU32(sfSequence) ==
|
||||
2);
|
||||
|
||||
// Replaying the manifest we just applied, and anything older, is
|
||||
// rejected on sequence. This is what prevents replay: the transaction
|
||||
// is unsigned, so nothing else would.
|
||||
BEAST_EXPECT(
|
||||
engineResult(submit(env, makeManifest(master, eph2, 2))) ==
|
||||
"tefPAST_MANIFEST_SEQ");
|
||||
BEAST_EXPECT(
|
||||
engineResult(submit(env, makeManifest(master, eph1, 1))) ==
|
||||
"tefPAST_MANIFEST_SEQ");
|
||||
}
|
||||
|
||||
void
|
||||
testRevocation(FeatureBitset features)
|
||||
{
|
||||
testcase("revocation");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, makeConfig(), features};
|
||||
|
||||
auto const master = Account("master", KeyType::ed25519);
|
||||
auto const ephemeral = Account("ephemeral", KeyType::ed25519);
|
||||
env.fund(XRP(1000), master);
|
||||
env.close();
|
||||
|
||||
submit(env, makeManifest(master, ephemeral, 1));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(
|
||||
engineResult(submit(
|
||||
env,
|
||||
makeManifest(
|
||||
master,
|
||||
ephemeral,
|
||||
std::numeric_limits<std::uint32_t>::max()))) ==
|
||||
"tesSUCCESS");
|
||||
env.close();
|
||||
|
||||
// A revocation has no signing key, so only the master key's copy
|
||||
// exists and it points at nothing.
|
||||
auto const byMaster = env.le(keylet::manifest(master.pk()));
|
||||
if (!BEAST_EXPECT(byMaster))
|
||||
return;
|
||||
BEAST_EXPECT(!env.le(keylet::manifest(ephemeral.pk())));
|
||||
BEAST_EXPECT(!byMaster->isFieldPresent(sfManifestID));
|
||||
BEAST_EXPECT(!byMaster->isFieldPresent(sfSigningPubKey));
|
||||
BEAST_EXPECT(
|
||||
byMaster->getFieldU32(sfSequence) ==
|
||||
std::numeric_limits<std::uint32_t>::max());
|
||||
|
||||
// Nothing supersedes a revocation.
|
||||
BEAST_EXPECT(
|
||||
engineResult(submit(env, makeManifest(master, ephemeral, 2))) ==
|
||||
"tefREVOKED_MANIFEST");
|
||||
}
|
||||
|
||||
void
|
||||
testRetrieval(FeatureBitset features)
|
||||
{
|
||||
testcase("retrieval into the manifest cache");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, makeConfig(), features};
|
||||
|
||||
auto const master = Account("master", KeyType::ed25519);
|
||||
auto const eph1 = Account("eph1", KeyType::ed25519);
|
||||
auto const eph2 = Account("eph2", KeyType::ed25519);
|
||||
env.fund(XRP(1000), master);
|
||||
env.close();
|
||||
|
||||
submit(env, makeManifest(master, eph1, 1));
|
||||
env.close();
|
||||
|
||||
auto& cache = env.app().validatorManifests();
|
||||
|
||||
// Nothing has fed the cache yet, so an unknown key maps to itself.
|
||||
BEAST_EXPECT(cache.getMasterKey(eph1.pk()) == eph1.pk());
|
||||
|
||||
// Reading the ledger resolves both directions of the mapping. The
|
||||
// manifest is reconstructed from the ledger object and verified, so a
|
||||
// lossy round trip would fail here rather than be accepted.
|
||||
BEAST_EXPECT(cache.applyLedger(*env.closed(), {master.pk()}) == 1);
|
||||
BEAST_EXPECT(cache.getMasterKey(eph1.pk()) == master.pk());
|
||||
BEAST_EXPECT(cache.getSigningKey(master.pk()) == eph1.pk());
|
||||
BEAST_EXPECT(cache.getSequence(master.pk()) == 1);
|
||||
|
||||
// Applying the same ledger again is a no-op: the cache is already at
|
||||
// that sequence.
|
||||
BEAST_EXPECT(cache.applyLedger(*env.closed(), {master.pk()}) == 0);
|
||||
|
||||
// A rotation on-ledger is picked up, and the superseded ephemeral key
|
||||
// stops resolving.
|
||||
submit(env, makeManifest(master, eph2, 2));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(cache.applyLedger(*env.closed(), {master.pk()}) == 1);
|
||||
BEAST_EXPECT(cache.getSigningKey(master.pk()) == eph2.pk());
|
||||
BEAST_EXPECT(cache.getMasterKey(eph1.pk()) == eph1.pk());
|
||||
|
||||
// A key we never ask about is never read.
|
||||
BEAST_EXPECT(cache.applyLedger(*env.closed(), {eph2.pk()}) == 0);
|
||||
|
||||
// A revocation reaches the cache too, and is the one thing that must
|
||||
// never be lost: forgetting it would mean trusting the old key again.
|
||||
submit(
|
||||
env,
|
||||
makeManifest(
|
||||
master, eph2, std::numeric_limits<std::uint32_t>::max()));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(cache.applyLedger(*env.closed(), {master.pk()}) == 1);
|
||||
BEAST_EXPECT(cache.revoked(master.pk()));
|
||||
}
|
||||
|
||||
void
|
||||
testSigningKeyRetrieval(FeatureBitset features)
|
||||
{
|
||||
testcase("retrieval by ephemeral key");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, makeConfig(), features};
|
||||
|
||||
auto const master = Account("master", KeyType::ed25519);
|
||||
auto const eph1 = Account("eph1", KeyType::ed25519);
|
||||
auto const eph2 = Account("eph2", KeyType::ed25519);
|
||||
auto const stranger = Account("stranger", KeyType::ed25519);
|
||||
env.fund(XRP(1000), master);
|
||||
env.close();
|
||||
|
||||
submit(env, makeManifest(master, eph1, 1));
|
||||
env.close();
|
||||
|
||||
auto& cache = env.app().validatorManifests();
|
||||
|
||||
// The situation applyLedger() cannot serve: a validation arrives
|
||||
// signed by eph1 and the node holds no manifest naming it, so the
|
||||
// master key to probe for is exactly what is missing.
|
||||
BEAST_EXPECT(cache.getMasterKey(eph1.pk()) == eph1.pk());
|
||||
|
||||
BEAST_EXPECT(
|
||||
cache.applyLedgerSigningKey(*env.closed(), eph1.pk()) ==
|
||||
master.pk());
|
||||
BEAST_EXPECT(cache.getMasterKey(eph1.pk()) == master.pk());
|
||||
BEAST_EXPECT(cache.getSigningKey(master.pk()) == eph1.pk());
|
||||
BEAST_EXPECT(cache.getSequence(master.pk()) == 1);
|
||||
|
||||
// A key with no manifest on-ledger resolves to nothing and leaves the
|
||||
// cache untouched.
|
||||
BEAST_EXPECT(
|
||||
!cache.applyLedgerSigningKey(*env.closed(), stranger.pk()));
|
||||
BEAST_EXPECT(cache.getMasterKey(stranger.pk()) == stranger.pk());
|
||||
|
||||
// A rotation is recovered from the new ephemeral key alone, and the
|
||||
// superseded key stops resolving because its object is gone.
|
||||
submit(env, makeManifest(master, eph2, 2));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(
|
||||
cache.applyLedgerSigningKey(*env.closed(), eph2.pk()) ==
|
||||
master.pk());
|
||||
BEAST_EXPECT(cache.getSigningKey(master.pk()) == eph2.pk());
|
||||
BEAST_EXPECT(cache.getMasterKey(eph1.pk()) == eph1.pk());
|
||||
BEAST_EXPECT(!env.le(keylet::manifest(eph1.pk())));
|
||||
BEAST_EXPECT(!cache.applyLedgerSigningKey(*env.closed(), eph1.pk()));
|
||||
|
||||
// Asking again is answered from the cache, ahead of the per-ledger
|
||||
// probe bookkeeping.
|
||||
BEAST_EXPECT(
|
||||
cache.applyLedgerSigningKey(*env.closed(), eph2.pk()) ==
|
||||
master.pk());
|
||||
|
||||
// A master key is not a signing key. The object at its keylet is a
|
||||
// perfectly good manifest and is ingested, but it binds eph2, not the
|
||||
// master key, so nothing is reported for the key asked about.
|
||||
BEAST_EXPECT(!cache.applyLedgerSigningKey(*env.closed(), master.pk()));
|
||||
|
||||
// A revoked master publishes no ephemeral object at all, so this
|
||||
// direction goes quiet. The revocation still reaches the cache by
|
||||
// master key, which is what applyLedger() is for.
|
||||
submit(
|
||||
env,
|
||||
makeManifest(
|
||||
master, eph2, std::numeric_limits<std::uint32_t>::max()));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(!env.le(keylet::manifest(eph2.pk())));
|
||||
BEAST_EXPECT(cache.applyLedger(*env.closed(), {master.pk()}) == 1);
|
||||
BEAST_EXPECT(cache.revoked(master.pk()));
|
||||
}
|
||||
|
||||
void
|
||||
testMalformed(FeatureBitset features)
|
||||
{
|
||||
testcase("malformed submissions");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, makeConfig(), features};
|
||||
|
||||
auto const master = Account("master", KeyType::ed25519);
|
||||
auto const other = Account("other", KeyType::ed25519);
|
||||
auto const ephemeral = Account("ephemeral", KeyType::ed25519);
|
||||
env.fund(XRP(1000), master, other);
|
||||
env.close();
|
||||
|
||||
// Not a manifest at all.
|
||||
BEAST_EXPECT(
|
||||
submit(env, "not a manifest")[jss::error].asString() ==
|
||||
"invalidManifest");
|
||||
|
||||
// A manifest whose ephemeral signature does not check out.
|
||||
{
|
||||
auto blob = makeManifest(master, ephemeral, 1);
|
||||
blob[blob.size() - 1] ^= 0xFF;
|
||||
BEAST_EXPECT(
|
||||
submit(env, blob)[jss::error].asString() == "invalidManifest");
|
||||
}
|
||||
|
||||
// The master key's account must exist: it pays the fee and holds the
|
||||
// pointer to the manifest.
|
||||
auto const unfunded = Account("unfunded", KeyType::ed25519);
|
||||
BEAST_EXPECT(
|
||||
engineResult(submit(env, makeManifest(unfunded, ephemeral, 1))) ==
|
||||
"terNO_ACCOUNT");
|
||||
|
||||
// An ephemeral key already claimed by a different account would
|
||||
// collide with -- and clobber -- that account's manifest object.
|
||||
submit(env, makeManifest(master, ephemeral, 1));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(
|
||||
engineResult(submit(env, makeManifest(other, ephemeral, 1))) ==
|
||||
"tecDUPLICATE");
|
||||
}
|
||||
|
||||
void
|
||||
testEnvelopeRejections(FeatureBitset features)
|
||||
{
|
||||
testcase("envelope rejections");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, makeConfig(), features};
|
||||
|
||||
auto const master = Account("master", KeyType::ed25519);
|
||||
auto const other = Account("other", KeyType::ed25519);
|
||||
auto const ephemeral = Account("ephemeral", KeyType::ed25519);
|
||||
env.fund(XRP(1000), master, other);
|
||||
env.close();
|
||||
|
||||
auto const good = makeManifest(master, ephemeral, 1);
|
||||
|
||||
// Sanity check: the unmodified envelope is the one Submit builds, so
|
||||
// every rejection below is attributable to the tweak and nothing else.
|
||||
BEAST_EXPECT(
|
||||
applyDirect(env, envelope(env, good, master.id())) == tesSUCCESS);
|
||||
|
||||
// Any bit but tfFullyCanonicalSig (0x80000000), which is tfUniversal
|
||||
// and so permitted on every transaction.
|
||||
BEAST_EXPECT(
|
||||
applyDirect(
|
||||
env, envelope(env, good, master.id(), [](STObject& obj) {
|
||||
obj.setFieldU32(sfFlags, 0x00000001);
|
||||
})) == temINVALID_FLAG);
|
||||
|
||||
// sfManifest parses as an object but not as a manifest.
|
||||
BEAST_EXPECT(
|
||||
applyDirect(
|
||||
env, envelope(env, makeUnparseableManifest(), master.id())) ==
|
||||
temMALFORMED);
|
||||
|
||||
// A manifest whose signatures do not check out. checkValidity() would
|
||||
// stop this before preflight, so only a direct apply reaches the
|
||||
// transactor's own verify() call.
|
||||
{
|
||||
auto bad = good;
|
||||
bad[bad.size() - 1] ^= 0xFF;
|
||||
BEAST_EXPECT(
|
||||
applyDirect(env, envelope(env, bad, master.id())) ==
|
||||
temMALFORMED);
|
||||
}
|
||||
|
||||
// The envelope's account must be the manifest's master key.
|
||||
BEAST_EXPECT(
|
||||
applyDirect(env, envelope(env, good, other.id())) == temMALFORMED);
|
||||
|
||||
// Every envelope field a relayer could otherwise choose is pinned: the
|
||||
// envelope is one exact transaction, with Account and NetworkID derived
|
||||
// rather than mirrored.
|
||||
for (auto const& [name, tweak] : std::vector<
|
||||
std::pair<char const*, std::function<void(STObject&)>>>{
|
||||
{"Sequence",
|
||||
[](STObject& obj) { obj.setFieldU32(sfSequence, 1); }},
|
||||
{"AccountTxnID",
|
||||
[](STObject& obj) {
|
||||
obj.setFieldH256(sfAccountTxnID, uint256{1});
|
||||
}},
|
||||
{"TicketSequence",
|
||||
[](STObject& obj) { obj.setFieldU32(sfTicketSequence, 1); }},
|
||||
{"SigningPubKey",
|
||||
[&](STObject& obj) {
|
||||
obj.setFieldVL(sfSigningPubKey, master.pk().slice());
|
||||
}},
|
||||
{"Memos",
|
||||
[](STObject& obj) {
|
||||
obj.setFieldArray(sfMemos, STArray(sfMemos, 1));
|
||||
STObject memo{sfMemo};
|
||||
memo.setFieldVL(sfMemoData, Blob{0x01});
|
||||
obj.peekFieldArray(sfMemos).emplace_back(std::move(memo));
|
||||
}},
|
||||
{"SourceTag",
|
||||
[](STObject& obj) { obj.setFieldU32(sfSourceTag, 1); }},
|
||||
{"LastLedgerSequence",
|
||||
[](STObject& obj) {
|
||||
obj.setFieldU32(sfLastLedgerSequence, 1'000'000);
|
||||
}},
|
||||
{"NetworkID",
|
||||
[](STObject& obj) { obj.setFieldU32(sfNetworkID, 21338); }}})
|
||||
{
|
||||
auto const variant = envelope(env, good, master.id(), tweak);
|
||||
// The generic network check runs first for a foreign NetworkID.
|
||||
auto const expected = std::string(name) == "NetworkID"
|
||||
? TER{telWRONG_NETWORK}
|
||||
: TER{temMALFORMED};
|
||||
BEAST_EXPECTS(applyDirect(env, variant) == expected, name);
|
||||
|
||||
// Ingress rejects the variant as a bad envelope before any
|
||||
// manifest signature work, and never as a soft fee miss.
|
||||
auto const [validity, reason] = checkValidity(
|
||||
env.app().getHashRouter(),
|
||||
*variant,
|
||||
env.current()->rules(),
|
||||
env.app().config());
|
||||
BEAST_EXPECTS(validity == Validity::SigBad, name);
|
||||
BEAST_EXPECTS(
|
||||
checkManifestIngressFee(env.app(), *variant).first ==
|
||||
ManifestIngressFee::NotApplicable,
|
||||
name);
|
||||
}
|
||||
|
||||
// The Fee is pinned to the one canonical value for the applying view's
|
||||
// base fee. A different value is a local miss, not a malformed
|
||||
// transaction, and ingress drops it before manifest crypto.
|
||||
auto const priced = envelope(env, good, master.id());
|
||||
auto const canonicalFee = priced->getFieldAmount(sfFee).xrp();
|
||||
BEAST_EXPECT(
|
||||
checkManifestIngressFee(env.app(), *priced).first ==
|
||||
ManifestIngressFee::Canonical);
|
||||
for (auto const fee :
|
||||
{canonicalFee + XRPAmount{1}, canonicalFee - XRPAmount{1}})
|
||||
{
|
||||
auto const variant =
|
||||
envelope(env, good, master.id(), [&](STObject& obj) {
|
||||
obj.setFieldAmount(sfFee, fee);
|
||||
});
|
||||
BEAST_EXPECT(
|
||||
checkManifestIngressFee(env.app(), *variant).first ==
|
||||
ManifestIngressFee::Refused);
|
||||
BEAST_EXPECT(applyDirect(env, variant) == telMANIFEST_FEE_MISMATCH);
|
||||
}
|
||||
|
||||
// At the canonical value exactly, which is what Submit sends.
|
||||
BEAST_EXPECT(applyDirect(env, priced) == tesSUCCESS);
|
||||
}
|
||||
|
||||
void
|
||||
testCanonicalFee(FeatureBitset features)
|
||||
{
|
||||
testcase("canonical fee");
|
||||
using namespace jtx;
|
||||
|
||||
auto const master = Account("master", KeyType::ed25519);
|
||||
auto const eph1 = Account("eph1", KeyType::ed25519);
|
||||
auto const units = [](STObject const& manifestObject) {
|
||||
return 100 +
|
||||
10 *
|
||||
static_cast<std::int64_t>(
|
||||
manifestObject.getSerializer().getDataLength());
|
||||
};
|
||||
auto const manifest = makeManifest(master, eph1, 1);
|
||||
|
||||
// The builder prices against the voted base fee of the open ledger.
|
||||
auto const build = [&](Env& env) -> std::shared_ptr<STTx const> {
|
||||
auto const hex = makeSetManifestTx(
|
||||
makeSlice(manifest),
|
||||
env.app().config().NETWORK_ID,
|
||||
*env.current(),
|
||||
env.app().journal("SetManifest_test"));
|
||||
if (!BEAST_EXPECT(hex))
|
||||
return nullptr;
|
||||
auto const bytes = strUnHex(*hex);
|
||||
if (!BEAST_EXPECT(bytes))
|
||||
return nullptr;
|
||||
SerialIter txIter{makeSlice(*bytes)};
|
||||
return std::make_shared<STTx const>(std::ref(txIter));
|
||||
};
|
||||
auto const manifestOf = [](STTx const& tx) -> STObject const& {
|
||||
return const_cast<STTx&>(tx)
|
||||
.getField(sfManifest)
|
||||
.downcast<STObject>();
|
||||
};
|
||||
|
||||
std::shared_ptr<STTx const> ordinaryTx;
|
||||
{
|
||||
Env ordinary{*this, makeConfig("10"), features};
|
||||
ordinaryTx = build(ordinary);
|
||||
if (!ordinaryTx)
|
||||
return;
|
||||
BEAST_EXPECT(
|
||||
(*ordinaryTx)[sfFee].xrp().drops() ==
|
||||
10 * units(manifestOf(*ordinaryTx)));
|
||||
|
||||
// A zero base would make the transaction free, so there is no
|
||||
// canonical Fee for it at all.
|
||||
BEAST_EXPECT(!canonicalSetManifestFee(
|
||||
XRPAmount{0}, manifestOf(*ordinaryTx)));
|
||||
}
|
||||
|
||||
// A different fee setting maps the same manifest to exactly one
|
||||
// different txid.
|
||||
Env expensive{*this, makeConfig("100000"), features};
|
||||
auto const expensiveTx = build(expensive);
|
||||
if (!expensiveTx)
|
||||
return;
|
||||
BEAST_EXPECT(
|
||||
(*expensiveTx)[sfFee].xrp().drops() ==
|
||||
100'000 * units(manifestOf(*expensiveTx)));
|
||||
BEAST_EXPECT(
|
||||
expensiveTx->getTransactionID() != ordinaryTx->getTransactionID());
|
||||
|
||||
expensive.fund(XRP(10000), master);
|
||||
expensive.close();
|
||||
|
||||
// A wrapper priced for another fee setting is a local miss, not a bad
|
||||
// signature: ingress drops it before manifest crypto without marking
|
||||
// it bad, and preclaim refuses it with a tel result.
|
||||
BEAST_EXPECT(
|
||||
checkManifestIngressFee(expensive.app(), *ordinaryTx).first ==
|
||||
ManifestIngressFee::Refused);
|
||||
BEAST_EXPECT(
|
||||
submit(expensive, ordinaryTx)[jss::error] == "invalidTransaction");
|
||||
BEAST_EXPECT(
|
||||
(expensive.app().getHashRouter().getFlags(
|
||||
ordinaryTx->getTransactionID()) &
|
||||
SF_BAD) == 0);
|
||||
BEAST_EXPECT(
|
||||
applyDirect(expensive, ordinaryTx) == telMANIFEST_FEE_MISMATCH);
|
||||
|
||||
BEAST_EXPECT(
|
||||
checkManifestIngressFee(expensive.app(), *expensiveTx).first ==
|
||||
ManifestIngressFee::Canonical);
|
||||
BEAST_EXPECT(engineResult(submit(expensive, manifest)) == "tesSUCCESS");
|
||||
expensive.close();
|
||||
BEAST_EXPECT(expensive.le(keylet::manifest(master.pk())));
|
||||
|
||||
// An amendment-blocked server has no base fee it can trust, so ingress
|
||||
// refuses even the canonical transaction rather than verify it.
|
||||
Env blocked{*this, makeConfig("10"), features};
|
||||
auto const blockedTx = build(blocked);
|
||||
if (!blockedTx)
|
||||
return;
|
||||
BEAST_EXPECT(
|
||||
checkManifestIngressFee(blocked.app(), *blockedTx).first ==
|
||||
ManifestIngressFee::Canonical);
|
||||
blocked.app().getOPs().setAmendmentBlocked();
|
||||
BEAST_EXPECT(
|
||||
checkManifestIngressFee(blocked.app(), *blockedTx).first ==
|
||||
ManifestIngressFee::Refused);
|
||||
}
|
||||
|
||||
void
|
||||
testCorruptLedger(FeatureBitset features)
|
||||
{
|
||||
testcase("corrupt manifest state");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, makeConfig(), features};
|
||||
|
||||
auto const master = Account("master", KeyType::ed25519);
|
||||
auto const eph1 = Account("eph1", KeyType::ed25519);
|
||||
auto const eph2 = Account("eph2", KeyType::ed25519);
|
||||
env.fund(XRP(1000), master);
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(
|
||||
engineResult(submit(env, makeManifest(master, eph1, 1))) ==
|
||||
"tesSUCCESS");
|
||||
env.close();
|
||||
|
||||
auto const update =
|
||||
envelope(env, makeManifest(master, eph2, 2), master.id());
|
||||
|
||||
// sfManifestID on the account root points at nothing.
|
||||
BEAST_EXPECT(applyDirect(env, update, [&](OpenView& view) {
|
||||
rawErase(view, keylet::manifest(master.pk()));
|
||||
}) == tefBAD_LEDGER);
|
||||
|
||||
// The master copy survives but the ephemeral copy it points at is
|
||||
// gone.
|
||||
BEAST_EXPECT(applyDirect(env, update, [&](OpenView& view) {
|
||||
rawErase(view, keylet::manifest(eph1.pk()));
|
||||
}) == tefBAD_LEDGER);
|
||||
|
||||
// The reverse: the account root has forgotten its manifest, so the
|
||||
// erase pass is skipped and the keylet is found occupied.
|
||||
BEAST_EXPECT(applyDirect(env, update, [&](OpenView& view) {
|
||||
auto replacement =
|
||||
rawCopy(view.read(keylet::account(master.id())));
|
||||
replacement->makeFieldAbsent(sfManifestID);
|
||||
view.rawReplace(replacement);
|
||||
}) == tefBAD_LEDGER);
|
||||
|
||||
// None of the above was retained, so the ledger is still intact and an
|
||||
// ordinary update still works.
|
||||
BEAST_EXPECT(applyDirect(env, update) == tesSUCCESS);
|
||||
}
|
||||
|
||||
void
|
||||
testGossipSelection(FeatureBitset features)
|
||||
{
|
||||
testcase("gossip selection");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, makeConfig(), features};
|
||||
|
||||
std::vector<Account> masters;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
masters.emplace_back("gm" + std::to_string(i), KeyType::ed25519);
|
||||
|
||||
auto const ephemeral = Account("gossipeph", KeyType::ed25519);
|
||||
|
||||
for (auto const& m : masters)
|
||||
env.fund(XRP(1000), m);
|
||||
env.close();
|
||||
|
||||
// Each master needs its own ephemeral key: preclaim rejects a key
|
||||
// already claimed by another account.
|
||||
std::vector<Account> ephs;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
ephs.emplace_back("ge" + std::to_string(i), KeyType::ed25519);
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
engineResult(
|
||||
submit(env, makeManifest(masters[i], ephs[i], 1))) ==
|
||||
"tesSUCCESS");
|
||||
env.close();
|
||||
}
|
||||
|
||||
ManifestCache cache;
|
||||
|
||||
hash_set<PublicKey> all;
|
||||
for (auto const& m : masters)
|
||||
all.insert(m.pk());
|
||||
|
||||
BEAST_EXPECT(cache.applyLedger(*env.closed(), all) == 4);
|
||||
|
||||
// The raw form is what a republishing node needs: the exact bytes the
|
||||
// master key signed, plus the sequence to compare against the ledger.
|
||||
auto const raw = cache.getRawManifest(masters[0].pk());
|
||||
if (BEAST_EXPECT(raw))
|
||||
{
|
||||
BEAST_EXPECT(raw->first == 1);
|
||||
BEAST_EXPECT(!raw->second.empty());
|
||||
}
|
||||
BEAST_EXPECT(!cache.getRawManifest(ephemeral.pk()));
|
||||
|
||||
// Pinning bumps the sequence so a cached gossip message is rebuilt,
|
||||
// and pinning the same set again does not.
|
||||
auto const seq = cache.sequence();
|
||||
cache.pin({masters[0].pk(), masters[1].pk()});
|
||||
BEAST_EXPECT(cache.sequence() > seq);
|
||||
|
||||
auto const seq2 = cache.sequence();
|
||||
cache.pin({masters[0].pk(), masters[1].pk()});
|
||||
BEAST_EXPECT(cache.sequence() == seq2);
|
||||
|
||||
// Everything is offered: two pinned plus two under the gossip limit.
|
||||
std::size_t reserved = 0;
|
||||
std::vector<PublicKey> offered;
|
||||
cache.for_each_gossip_manifest(
|
||||
[&](std::size_t n) { reserved = n; },
|
||||
[&](Manifest const& m) { offered.push_back(m.masterKey); });
|
||||
|
||||
BEAST_EXPECT(reserved == 4);
|
||||
BEAST_EXPECT(offered.size() == 4);
|
||||
BEAST_EXPECT(
|
||||
hash_set<PublicKey>(offered.begin(), offered.end()) == all);
|
||||
|
||||
// A pinned key with no manifest is counted in the reservation but not
|
||||
// offered, since the reservation is only an upper bound.
|
||||
cache.pin({masters[0].pk(), ephemeral.pk()});
|
||||
offered.clear();
|
||||
cache.for_each_gossip_manifest(
|
||||
[&](std::size_t n) { reserved = n; },
|
||||
[&](Manifest const& m) { offered.push_back(m.masterKey); });
|
||||
|
||||
BEAST_EXPECT(reserved == 5);
|
||||
BEAST_EXPECT(offered.size() == 4);
|
||||
}
|
||||
|
||||
void
|
||||
testDisabled(FeatureBitset features)
|
||||
{
|
||||
testcase("amendment gate");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, makeConfig(), features - featureOnChainManifests};
|
||||
|
||||
auto const master = Account("master", KeyType::ed25519);
|
||||
auto const ephemeral = Account("ephemeral", KeyType::ed25519);
|
||||
env.fund(XRP(1000), master);
|
||||
env.close();
|
||||
|
||||
// The submitted transaction carries no account signature, so with the
|
||||
// amendment off it would otherwise be rejected as unsigned and never
|
||||
// reach preflight's temDISABLED. The RPC therefore refuses it up
|
||||
// front, and says why.
|
||||
auto const result = submit(env, makeManifest(master, ephemeral, 1));
|
||||
BEAST_EXPECT(result[jss::error].asString() == "notEnabled");
|
||||
BEAST_EXPECT(!result.isMember(jss::engine_result));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(!env.le(keylet::manifest(master.pk())));
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
using namespace test::jtx;
|
||||
auto const sa = supported_amendments();
|
||||
testSubmission(sa);
|
||||
testUpdate(sa);
|
||||
testRevocation(sa);
|
||||
testRetrieval(sa);
|
||||
testSigningKeyRetrieval(sa);
|
||||
testMalformed(sa);
|
||||
testEnvelopeRejections(sa);
|
||||
testCanonicalFee(sa);
|
||||
testCorruptLedger(sa);
|
||||
testGossipSelection(sa);
|
||||
testDisabled(sa);
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(SetManifest, app, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -224,7 +224,7 @@ private:
|
||||
taker_offer,
|
||||
taker_quality,
|
||||
parse_amount(funds, issue_in),
|
||||
sell ? tfSell : 0,
|
||||
sell ? std::uint32_t(tfSell) : 0,
|
||||
rate_in,
|
||||
rate_out);
|
||||
|
||||
|
||||
Loaded 100 of 298 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user