Compare commits

..
Author SHA1 Message Date
Richard Holland e5b5efcc87 Merge branch 'dev' into coverage 2026-04-28 11:34:19 +10:00
tequ a77e6ee7fd use clang-format-18 on check-genesis-hooks workflow 2026-04-28 11:30:54 +10:00
tequ 67034cda77 Update build_xahau.h.sh 2026-04-28 11:30:54 +10:00
tequ aa1e4d38d3 update workflow 2026-04-28 11:30:54 +10:00
tequ d4417d3461 remove workflow cache 2026-04-28 11:30:54 +10:00
tequ 6f6e56952e Remove path filters for Genesis Hooks in GitHub Actions workflow 2026-04-28 11:30:54 +10:00
tequ e673cb5b80 Update GitHub Actions workflow to install binaryen version 100 directly from GitHub Releases 2026-04-28 11:30:54 +10:00
tequ cfb1b36bae Add wasm-opt to required tools and enforce version check 2026-04-28 11:30:54 +10:00
tequ 628c942a98 update xahau.h using binaryen version=100 2026-04-28 11:30:54 +10:00
tequ 4cb64497e9 revert xahau.h 2026-04-28 11:30:54 +10:00
tequ 96065296ab revert arm 2026-04-28 11:30:54 +10:00
tequ 6ab87ed696 wasmcc version 2026-04-28 11:30:54 +10:00
tequ 1b7c70d3ba try arm 2026-04-28 11:30:54 +10:00
tequ ba2b188aaf fix 2026-04-28 11:30:54 +10:00
tequ 7a78672983 fix 2026-04-28 11:30:54 +10:00
tequ 14de6cc76d fix wasienv script 2026-04-28 11:30:54 +10:00
tequ 13f4313f64 restore headers for xahau.h and build, check in github actions 2026-04-28 11:30:54 +10:00
tequ 2244fae464 Merge branch 'dev' into coverage 2026-04-24 14:12:11 +09:00
Alloy Networks cd00ed72d8 change build instructions url 2026-04-24 11:12:28 +10:00
tequ 05a3e04f2d Fix BEAST_ENHANCED_LOGGING not working and restore original behavior 2026-04-24 11:11:40 +10:00
tequ 66f7294120 Test: hint build_test_hooks.sh when hook wasm is empty in hso() 2026-04-24 11:10:46 +10:00
Nicholas Dudfield 7f6ac75617 Revert "chore: use improved levelization script with threading and argparse"
This reverts commit 5c1d7d9ae9.
2026-04-24 11:09:19 +10:00
Nicholas Dudfield 4150f0383c chore: use improved levelization script with threading and argparse 2026-04-24 11:09:19 +10:00
Nicholas Dudfield 25123b370a chore: replace levelization shell script with python
Backport of XRPLF/rippled#6325. The python version runs ~80x faster.
2026-04-24 11:09:19 +10:00
tequ f90ed41802 enable ccache direct_mode 2026-04-24 11:06:51 +10:00
tequ 8c4c158d3a output ccache configuration in release-builder 2026-04-24 11:06:51 +10:00
tequ 2d2951875d fix: typo SignersListSet 2026-04-24 11:05:20 +10:00
tequ 9bfca63574 Update util_keylet fee test 2026-04-24 11:00:31 +10:00
tequ 1ba444ae7f Updated tests to align with the changes merged into the dev branch. 2026-04-24 11:00:31 +10:00
tequ f96d9b6e51 Add tests for Hooks fee 2026-04-24 11:00:31 +10:00
tequ 8d0e6eedea Merge branch 'dev' into coverage 2026-03-06 16:29:37 +09:00
tequ 8cfee6c8a3 Merge fixAMMClawbackRounding amendment into featureAMMClawback amendment 2026-02-25 19:07:45 +10:00
yinyiqian1 8673599d2b fixAMMClawbackRounding: adjust last holder's LPToken balance (#5513)
Due to rounding, the LPTokenBalance of the last LP might not match the LP's trustline balance. This was fixed for `AMMWithdraw` in `fixAMMv1_1` by adjusting the LPTokenBalance to be the same as the trustline balance. Since `AMMClawback` is also performing a withdrawal, we need to adjust LPTokenBalance as well in `AMMClawback.`

This change includes:
1. Refactored `verifyAndAdjustLPTokenBalance` function in `AMMUtils`, which both`AMMWithdraw` and `AMMClawback` call to adjust LPTokenBalance.
2. Added the unit test `testLastHolderLPTokenBalance` to test the scenario.
3. Modify the existing unit tests for `fixAMMClawbackRounding`.
2026-02-25 19:07:45 +10:00
tequ ec65e622aa Merge fixAMMv1_3 amendment into featureAMM amendment 2026-02-25 16:20:43 +10:00
Gregory Tsipenyuk 65837f49e1 fix: Add AMMv1_3 amendment (#5203)
* Add AMM bid/create/deposit/swap/withdraw/vote invariants:
  - Deposit, Withdrawal invariants: `sqrt(asset1Balance * asset2Balance) >= LPTokens`.
  - Bid: `sqrt(asset1Balance * asset2Balance) > LPTokens` and the pool balances don't change.
  - Create: `sqrt(asset1Balance * assetBalance2) == LPTokens`.
  - Swap: `asset1BalanceAfter * asset2BalanceAfter >= asset1BalanceBefore * asset2BalanceBefore`
     and `LPTokens` don't change.
  - Vote: `LPTokens` and pool balances don't change.
  - All AMM and swap transactions: amounts and tokens are greater than zero, except on withdrawal if all tokens
    are withdrawn.
* Add AMM deposit and withdraw rounding to ensure AMM invariant:
  - On deposit, tokens out are rounded downward and deposit amount is rounded upward.
  - On withdrawal, tokens in are rounded upward and withdrawal amount is rounded downward.
* Add Order Book Offer invariant to verify consumed amounts. Consumed amounts are less than the offer.
* Fix Bid validation. `AuthAccount` can't have duplicate accounts or the submitter account.
2026-02-25 16:20:43 +10:00
tequ 59580e2b28 Merge branch 'dev' into coverage 2026-02-24 17:09:52 +09:00
RichardAH e5b21f026e Merge pull request #692 from Xahau/sync-2.4.0-rebased
Sync: Ripple(d) 2.4.0
2026-02-24 16:07:09 +10:00
RichardAH 866119cf80 Merge branch 'dev' into coverage 2026-02-18 09:34:45 +10:00
Niq Dudfield 498f63651d Merge branch 'dev' into coverage 2026-02-17 11:14:33 +07:00
Niq Dudfield 4c5c9b14b7 fix: ci build action and coverage workflow fixes (#682)
- move --target before ${VERBOSE_FLAG} so [ci-ga-cmake-verbose] doesn't
  swallow the target argument
- add stdlib: default option for GCC entries (GCC doesn't support -stdlib flag)
- pass stdlib explicitly to coverage build step
- remove dead CMAKE_BUILD_PARALLEL_LEVEL export (doesn't persist across steps,
  and build step already uses --parallel)
- remove duplicate ccache config block
2026-02-17 10:22:59 +09:00
tequ 689f3c07c9 Merge branch 'dev' into coverage 2026-02-16 13:36:42 +09:00
tequ 407cc83241 Merge branch 'dev' into coverage 2026-01-28 14:12:07 +09:00
tequ 97deee10ca Merge branch 'dev' into coverage 2026-01-27 21:30:10 +09:00
tequ 0c8de81657 Merge branch 'dev' into coverage 2026-01-05 19:49:26 +09:00
tequ cb40a9d726 fix cmake-target 2025-12-24 22:03:45 +09:00
Bronek Kozicki 3d9f8aa7a9 test: improve code coverage reporting (#4849)
* Speed up the generation of coverage reports by using multiple cores.

* Add codecov step to coverage workflow.
2025-12-24 22:03:23 +09:00
tequ d7fd2adb34 Add coverage workflow 2025-12-24 17:10:46 +09:00
137 changed files with 3290 additions and 10120 deletions

No files matched your search

-31
View File
@@ -1,37 +1,6 @@
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%
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/"
+4 -21
View File
@@ -1,25 +1,8 @@
# 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
# 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
e2384885f5f630c8f0ffe4bf21a169b433a16858
241b9ddde9e11beb7480600fd5ed90e1ef109b21
760f16f56835663d9286bd29294d074de26a7ba6
0eebe6a5f4246fced516d52b83ec4e7f47373edd
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
sudo apt-get update
sudo apt-get install clang-format-${CLANG_VERSION}
- name: Format first-party sources
run: find include src -type f \( -name '*.cpp' -o -name '*.hpp' -o -name '*.h' -o -name '*.ipp' \) -exec clang-format-${CLANG_VERSION} -i {} +
run: find include src -type f \( -name '*.cpp' -o -name '*.hpp' -o -name '*.h' -o -name '*.ipp' \) -not -path "src/magic/magic_enum.h" -exec clang-format-${CLANG_VERSION} -i {} +
- name: Check for differences
id: assert
run: |
@@ -18,10 +18,6 @@ jobs:
generator: bash ./hook/generate_sfcodes.sh
- target: hook/tts.h
generator: ./hook/generate_tts.sh
- target: hook/ls_flags.h
generator: ./hook/generate_lsflags.sh
- target: hook/tx_flags.h
generator: ./hook/generate_txflags.sh
runs-on: ubuntu-24.04
env:
CLANG_VERSION: 18
+13 -26
View File
@@ -230,9 +230,6 @@ jobs:
build:
needs: matrix-setup
runs-on: ${{ fromJSON(matrix.runs_on) }}
permissions:
id-token: write
contents: read
container:
image: ubuntu:24.04
volumes:
@@ -335,7 +332,7 @@ jobs:
# Install gcovr for coverage jobs
if [ "${{ matrix.job_type }}" = "coverage" ]; then
pipx install "gcovr>=7,<9"
apt-get install -y curl lcov
apt-get install -y lcov
fi
- name: Check environment
@@ -410,8 +407,7 @@ 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"'
cmake-args: '-Dcoverage=ON -Dcoverage_format=xml -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_CXX_FLAGS="-O0" -DCMAKE_C_FLAGS="-O0"'
cmake-target: 'coverage'
ccache_max_size: '100G'
@@ -456,24 +452,15 @@ jobs:
- name: Upload coverage report
if: matrix.job_type == 'coverage'
uses: codecov/codecov-action@v5
uses: wandalen/wretry.action/main@v3
with:
files: coverage.xml
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
action: codecov/[email protected]
with: |
files: coverage.xml
fail_ci_if_error: true
disable_search: true
verbose: true
plugin: noop
token: ${{ secrets.CODECOV_TOKEN }}
attempt_limit: 5
attempt_delay: 210000 # in milliseconds
-2
View File
@@ -84,7 +84,6 @@ 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
@@ -119,7 +118,6 @@ 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
-1
View File
@@ -122,7 +122,6 @@ endif()
find_package(nudb REQUIRED)
find_package(date REQUIRED)
find_package(xxHash REQUIRED)
find_package(magic_enum REQUIRED)
include(deps/WasmEdge)
if(TARGET nudb::core)
+2 -10
View File
@@ -95,16 +95,8 @@ if [[ "$4" == "" ]]; then
echo "Non GH, local building, no Action runner magic"
else
# GH Action, runner
if [[ "$(git rev-parse --abbrev-ref HEAD)" == "release" ]]; then
echo "building on the release branch... placing it in builds/candidate"
mkdir /data/builds/candidate
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
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
echo "Published build to: http://build.xahau.tech/"
echo $(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4
fi
+16 -14
View File
@@ -12,16 +12,17 @@ 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());
@@ -67,14 +68,15 @@ 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 &&
echo "MOVING TO [ build-core.sh ]"
export CXX='/usr/lib64/ccache/g++' &&
echo "-- Build Rippled --" &&
pwd &&
printenv >.env.temp
cat .env.temp | grep '=' | sed s/\\\(^[^=]\\+=\\\)/\\1\\\"/g | sed s/\$/\\\"/g >.env
rm .env.temp
echo "MOVING TO [ build-core.sh ]";
printenv > .env.temp;
cat .env.temp | grep '=' | sed s/\\\(^[^=]\\+=\\\)/\\1\\\"/g|sed s/\$/\\\"/g > .env;
rm .env.temp;
echo "Persisting ENV:"
cat .env
+4 -18
View File
@@ -95,9 +95,6 @@
# - 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.
@@ -259,10 +256,10 @@ endif()
# BASE_DIRECTORY "../" # Base directory for report
# # (defaults to PROJECT_SOURCE_DIR)
# FORMAT "cobertura" # Output format, one of:
# # 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;
# # xml cobertura sonarqube json-summary
# # json-details coveralls csv txt
# # html-single html-nested html-details
# # (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+)
@@ -311,8 +308,6 @@ 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()
@@ -325,14 +320,6 @@ 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)
@@ -393,7 +380,6 @@ function(setup_target_for_coverage_gcovr)
${GCOVR_PATH}
--gcov-executable ${GCOV_TOOL}
--gcov-ignore-parse-errors=negative_hits.warn_once_per_file
--gcov-ignore-parse-errors=suspicious_hits.warn_once_per_file
-r ${BASEDIR}
${GCOVR_ADDITIONAL_ARGS}
${GCOVR_EXCLUDE_ARGS}
-1
View File
@@ -54,7 +54,6 @@ add_library(xrpl.imports.main INTERFACE)
target_link_libraries(xrpl.imports.main
INTERFACE
LibArchive::LibArchive
magic_enum::magic_enum
OpenSSL::Crypto
Ripple::boost
wasmedge::wasmedge
-1
View File
@@ -29,7 +29,6 @@ class Xrpl(ConanFile):
'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',
-6
View File
@@ -2,9 +2,6 @@
// 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);
@@ -339,8 +336,5 @@ prepare(
uint32_t read_ptr,
uint32_t read_len);
#ifdef __cplusplus
}
#endif
#define HOOK_EXTERN
#endif // HOOK_EXTERN
+2 -2
View File
@@ -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 class hook_return_code/,/};/p' "$ENUM_FILE" |
sed -n '/enum 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 class hook_return_code/,/};/p' "$ENUM_FILE" |
{
line = $0
if (line ~ /enum[[:space:]]+class[[:space:]]+hook_return_code/)
if (line ~ /enum[[:space:]]+hook_return_code/)
next
if (line ~ /^[[:space:]]*\{/)
next
-6
View File
@@ -11,9 +11,6 @@ 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) {
@@ -49,9 +46,6 @@ APPLY_HOOK="$SCRIPT_DIR/../include/xrpl/hook/hook_api.macro"
}
' "$APPLY_HOOK"
echo '#ifdef __cplusplus'
echo '}'
echo '#endif'
echo '#define HOOK_EXTERN'
echo '#endif // HOOK_EXTERN'
} | (
-82
View File
@@ -1,82 +0,0 @@
#!/bin/bash
set -eu
SCRIPT_DIR=$(dirname "$0")
SCRIPT_DIR=$(cd "$SCRIPT_DIR" && pwd)
RIPPLED_ROOT="$SCRIPT_DIR/../include/xrpl"
LEDGER_FORMATS="$RIPPLED_ROOT/protocol/LedgerFormats.h"
echo '// Generated using generate_lsflags.sh'
echo ''
echo '#ifndef HOOKLSFLAGS_INCLUDED'
echo '#define HOOKLSFLAGS_INCLUDED 1'
echo ''
awk '
function ltrim(s) { sub(/^[[:space:]]+/, "", s); return s }
function rtrim(s) { sub(/[[:space:]]+$/, "", s); return s }
function trim(s) { return rtrim(ltrim(s)) }
function flush_group() {
if (entry_count > 0 && group != "") {
printf "enum %s {\n", group
for (i = 1; i <= entry_count; i++) {
printf " %s,\n", entries[i]
}
printf "};\n"
}
delete entries
entry_count = 0
}
/enum LedgerSpecificFlags \{/ { inside = 1; next }
inside && /^\};/ { inside = 0; flush_group(); next }
!inside { next }
# Group header comments: // ltFOO or // remarks
/^[[:space:]]*\/\/[[:space:]]*(lt[A-Z_]+|remarks)[[:space:]]*$/ {
flush_group()
line = $0
sub(/.*\/\/[[:space:]]*/, "", line)
group = trim(line)
next
}
# Skip pure comment lines (not group headers)
/^[[:space:]]*\/\// { next }
# Skip blank lines
/^[[:space:]]*$/ { next }
# Accumulate flag lines (handle multi-line values)
{
line = $0
# Strip inline comments
sub(/\/\/.*/, "", line)
line = trim(line)
if (line == "") next
if (pending != "") {
pending = pending " " line
} else {
pending = line
}
# If line ends with comma, the entry is complete
if (pending ~ /,$/) {
# Remove trailing comma
sub(/,$/, "", pending)
entries[++entry_count] = pending
pending = ""
}
}
BEGIN {
inside = 0
group = ""
pending = ""
entry_count = 0
}
' "$LEDGER_FORMATS"
echo ''
echo '#endif // HOOKLSFLAGS_INCLUDED'
-25
View File
@@ -1,25 +0,0 @@
#!/bin/bash
set -eu
SCRIPT_DIR=$(dirname "$0")
SCRIPT_DIR=$(cd "$SCRIPT_DIR" && pwd)
RIPPLED_ROOT="$SCRIPT_DIR/../include/xrpl"
TX_FLAGS="$RIPPLED_ROOT/protocol/TxFlags.h"
echo '// Generated using generate_txflags.sh'
echo '#include "ls_flags.h"'
echo '#include <stdint.h>'
echo ''
cat "$TX_FLAGS" |
awk '
/^[[:space:]]*enum / {
if (count > 0) print ""
inside = 1
count++
}
inside {
print
if (/};/) inside = 0
}
'
-3
View File
@@ -49,7 +49,4 @@
#include "macro.h"
#include "tts.h"
#include "ls_flags.h"
#include "tx_flags.h"
#endif
-75
View File
@@ -1,75 +0,0 @@
// Generated using generate_lsflags.sh
#ifndef HOOKLSFLAGS_INCLUDED
#define HOOKLSFLAGS_INCLUDED 1
enum ltACCOUNT_ROOT {
lsfPasswordSpent = 0x00010000,
lsfRequireDestTag = 0x00020000,
lsfRequireAuth = 0x00040000,
lsfDisallowXRP = 0x00080000,
lsfDisableMaster = 0x00100000,
lsfNoFreeze = 0x00200000,
lsfGlobalFreeze = 0x00400000,
lsfDefaultRipple = 0x00800000,
lsfDepositAuth = 0x01000000,
lsfTshCollect = 0x02000000,
lsfDisallowIncomingNFTokenOffer = 0x04000000,
lsfDisallowIncomingCheck = 0x08000000,
lsfDisallowIncomingPayChan = 0x10000000,
lsfDisallowIncomingTrustline = 0x20000000,
lsfURITokenIssuer = 0x40000000,
lsfDisallowIncomingRemit = 0x80000000,
lsfAllowTrustLineClawback = 0x00001000,
};
enum ltOFFER {
lsfPassive = 0x00010000,
lsfSell = 0x00020000,
};
enum ltRIPPLE_STATE {
lsfLowReserve = 0x00010000,
lsfHighReserve = 0x00020000,
lsfLowAuth = 0x00040000,
lsfHighAuth = 0x00080000,
lsfLowNoRipple = 0x00100000,
lsfHighNoRipple = 0x00200000,
lsfLowFreeze = 0x00400000,
lsfHighFreeze = 0x00800000,
lsfLowDeepFreeze = 0x02000000,
lsfHighDeepFreeze = 0x04000000,
lsfAMMNode = 0x01000000,
};
enum ltSIGNER_LIST {
lsfOneOwnerCount = 0x00010000,
};
enum ltDIR_NODE {
lsfNFTokenBuyOffers = 0x00000001,
lsfNFTokenSellOffers = 0x00000002,
lsfEmittedDir = 0x00000004,
};
enum ltNFTOKEN_OFFER {
lsfSellNFToken = 0x00000001,
};
enum ltURI_TOKEN {
lsfBurnable = 0x00000001,
};
enum remarks {
lsfImmutable = 1,
};
enum ltMPTOKEN_ISSUANCE {
lsfMPTLocked = 0x00000001,
lsfMPTCanLock = 0x00000002,
lsfMPTRequireAuth = 0x00000004,
lsfMPTCanEscrow = 0x00000008,
lsfMPTCanTrade = 0x00000010,
lsfMPTCanTransfer = 0x00000020,
lsfMPTCanClawback = 0x00000040,
};
enum ltMPTOKEN {
lsfMPTAuthorized = 0x00000002,
};
enum ltCREDENTIAL {
lsfAccepted = 0x00010000,
};
#endif // HOOKLSFLAGS_INCLUDED
+1 -6
View File
@@ -189,7 +189,6 @@
#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)
@@ -221,7 +220,6 @@
#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)
@@ -242,7 +240,6 @@
#define sfLockingChainDoor ((8U << 16U) + 22U)
#define sfIssuingChainDoor ((8U << 16U) + 23U)
#define sfSubject ((8U << 16U) + 24U)
#define sfBrokerAccount ((8U << 16U) + 98U)
#define sfInform ((8U << 16U) + 99U)
#define sfIndexes ((19U << 16U) + 1U)
#define sfHashes ((19U << 16U) + 2U)
@@ -258,7 +255,6 @@
#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)
@@ -278,6 +274,7 @@
#define sfDisabledValidator ((14U << 16U) + 19U)
#define sfEmittedTxn ((14U << 16U) + 20U)
#define sfHookExecution ((14U << 16U) + 21U)
#define sfHookDefinition ((14U << 16U) + 22U)
#define sfHookParameter ((14U << 16U) + 23U)
#define sfHookGrant ((14U << 16U) + 24U)
#define sfVoteEntry ((14U << 16U) + 25U)
@@ -296,8 +293,6 @@
#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)
-117
View File
@@ -1,117 +0,0 @@
// Generated using generate_txflags.sh
#include "ls_flags.h"
#include <stdint.h>
enum UniversalFlags : uint32_t {
tfFullyCanonicalSig = 0x80000000,
};
enum AccountSetFlags : uint32_t {
tfRequireDestTag = 0x00010000,
tfOptionalDestTag = 0x00020000,
tfRequireAuth = 0x00040000,
tfOptionalAuth = 0x00080000,
tfDisallowXRP = 0x00100000,
tfAllowXRP = 0x00200000,
};
enum AccountFlags : uint32_t {
asfRequireDest = 1,
asfRequireAuth = 2,
asfDisallowXRP = 3,
asfDisableMaster = 4,
asfAccountTxnID = 5,
asfNoFreeze = 6,
asfGlobalFreeze = 7,
asfDefaultRipple = 8,
asfDepositAuth = 9,
asfAuthorizedNFTokenMinter = 10,
asfTshCollect = 11,
asfDisallowIncomingNFTokenOffer = 12,
asfDisallowIncomingCheck = 13,
asfDisallowIncomingPayChan = 14,
asfDisallowIncomingTrustline = 15,
asfDisallowIncomingRemit = 16,
asfAllowTrustLineClawback = 17,
};
enum OfferCreateFlags : uint32_t {
tfPassive = 0x00010000,
tfImmediateOrCancel = 0x00020000,
tfFillOrKill = 0x00040000,
tfSell = 0x00080000,
};
enum PaymentFlags : uint32_t {
tfNoRippleDirect = 0x00010000,
tfPartialPayment = 0x00020000,
tfLimitQuality = 0x00040000,
};
enum TrustSetFlags : uint32_t {
tfSetfAuth = 0x00010000,
tfSetNoRipple = 0x00020000,
tfClearNoRipple = 0x00040000,
tfSetFreeze = 0x00100000,
tfClearFreeze = 0x00200000,
tfSetDeepFreeze = 0x00400000,
tfClearDeepFreeze = 0x00800000
};
enum EnableAmendmentFlags : uint32_t {
tfGotMajority = 0x00010000,
tfLostMajority = 0x00020000,
tfTestSuite = 0x80000000,
};
enum PaymentChannelClaimFlags : uint32_t {
tfRenew = 0x00010000,
tfClose = 0x00020000,
};
enum NFTokenMintFlags : uint32_t {
tfBurnable = 0x00000001,
tfOnlyXRP = 0x00000002,
tfTrustLine = 0x00000004,
tfTransferable = 0x00000008,
tfMutable = 0x00000010,
tfStrongTSH = 0x00008000,
};
enum MPTokenIssuanceCreateFlags : uint32_t {
tfMPTCanLock = lsfMPTCanLock,
tfMPTRequireAuth = lsfMPTRequireAuth,
tfMPTCanEscrow = lsfMPTCanEscrow,
tfMPTCanTrade = lsfMPTCanTrade,
tfMPTCanTransfer = lsfMPTCanTransfer,
tfMPTCanClawback = lsfMPTCanClawback,
};
enum MPTokenAuthorizeFlags : uint32_t {
tfMPTUnauthorize = 0x00000001,
};
enum MPTokenIssuanceSetFlags : uint32_t {
tfMPTLock = 0x00000001,
tfMPTUnlock = 0x00000002,
};
enum NFTokenCreateOfferFlags : uint32_t {
tfSellNFToken = 0x00000001,
};
enum ClaimRewardFlags : uint32_t {
tfOptOut = 0x00000001,
};
enum CronSetFlags : uint32_t {
tfCronUnset = 0x00000001,
};
enum AMMClawbackFlags : uint32_t {
tfClawTwoAssets = 0x00000001,
};
enum BridgeModifyFlags : uint32_t {
tfClearAccountCreateAmount = 0x00010000,
};
+2 -6
View File
@@ -16,7 +16,6 @@
#define featureHooksUpdate1 "1"
#define featureHooksUpdate2 "1"
#define fix20250131 "1"
#define fixGuardDepth32 "1"
namespace hook_api {
struct Rules
{
@@ -320,7 +319,7 @@ namespace compare_mode {
enum compare_mode : uint32_t { EQUAL = 1, LESS = 2, GREATER = 4 };
}
enum class hook_return_code : int64_t {
enum hook_return_code : int64_t {
SUCCESS =
0, // return codes > 0 are reserved for hook apis to return "success"
OUT_OF_BOUNDS =
@@ -387,7 +386,7 @@ enum class hook_return_code : int64_t {
TOO_MANY_NAMESPACES = -45
};
enum class ExitType : uint8_t {
enum ExitType : uint8_t {
UNSET = 0,
WASM_ERROR = 1,
ROLLBACK = 2,
@@ -444,7 +443,6 @@ getImportWhitelist(Rules const& rules)
enum GuardRulesVersion : uint64_t {
GuardRuleFix20250131 = 0x00000001,
GuardRuleDepth32 = 0x00000002,
};
inline uint64_t
@@ -453,8 +451,6 @@ getGuardRulesVersion(Rules const& rules)
uint64_t version = 0;
if (rules.enabled(fix20250131))
version |= GuardRuleFix20250131;
if (rules.enabled(fixGuardDepth32))
version |= GuardRuleDepth32;
return version;
}
+6 -15
View File
@@ -204,13 +204,9 @@ struct WasmBlkInf
}
// compute worst case execution time
inline uint64_t
compute_wce(
const WasmBlkInf* blk,
int level,
int max_level,
bool* recursion_limit_reached)
compute_wce(const WasmBlkInf* blk, int level, bool* recursion_limit_reached)
{
if (level > max_level)
if (level > 16)
{
*recursion_limit_reached = true;
return 0;
@@ -237,8 +233,8 @@ compute_wce(
if (blk->children.size() > 0)
for (auto const& child : blk->children)
worst_case_execution += compute_wce(
child, level + 1, max_level, recursion_limit_reached);
worst_case_execution +=
compute_wce(child, level + 1, recursion_limit_reached);
if (parent == 0 ||
parent->iteration_bound ==
@@ -792,17 +788,12 @@ check_guard(
}
bool recursion_limit_reached = false;
int max_level = 16;
if (rulesVersion & hook_api::GuardRuleDepth32)
max_level = 32;
uint64_t wce =
compute_wce(&(*root), 0, max_level, &recursion_limit_reached);
uint64_t wce = compute_wce(&(*root), 0, &recursion_limit_reached);
if (recursion_limit_reached)
{
GUARDLOG(hook::log::NESTING_LIMIT)
<< "GuardCheck "
<< "Maximum allowable depth of blocks reached (" << max_level
<< " levels). Flatten "
<< "Maximum allowable depth of blocks reached (16 levels). Flatten "
"your loops and conditions!.\n";
return {};
}
+49 -60
View File
@@ -89,69 +89,58 @@
#define WASM_VAL_TYPE(T, b) CAT2(TYP_, T)
#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; \
#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; \
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); \
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__( \
#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__( \
COMMA __VA_ARGS__))
#define HOOK_SETUP() \
using enum hook_api::hook_return_code; \
try \
{ \
[[maybe_unused]] ApplyContext& applyCtx = hookCtx.applyCtx; \
@@ -214,7 +203,7 @@
host_memory_ptr, \
guest_memory_length) \
{ \
uint64_t bytes_written = 0; \
int64_t bytes_written = 0; \
WRITE_WASM_MEMORY( \
bytes_written, \
guest_dst_ptr, \
@@ -283,7 +272,7 @@
data_ptr < (data_ptr_in)) \
return INTERNAL_ERROR; \
if (data_len == 0) \
return 0ULL; \
return 0; \
if ((write_ptr_in) == 0) \
return data_as_int64(data_ptr, data_len); \
if (data_len > (write_len_in)) \
+26 -56
View File
@@ -33,39 +33,35 @@
*
* Steps required to add new features to the code:
*
* 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.
*
* 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.
*
* When a feature has been enabled for several years, the conditional code
* may be removed, and the feature "retired". To retire a feature:
*
* 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.
*
* 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`.
* The feature must remain registered and supported indefinitely because it
* 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.
* 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.
*
*/
@@ -80,32 +76,11 @@ 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 =
(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")
static constexpr std::size_t numFeatures = 113;
/** Amendments that this server supports and the default voting behavior.
Whether they are enabled depends on the Rules defined in the validated
@@ -345,17 +320,12 @@ 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
-1
View File
@@ -137,7 +137,6 @@ 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,
+1 -1
View File
@@ -132,7 +132,7 @@ constexpr std::uint32_t tfTrustSetMask =
tfClearFreeze | tfSetDeepFreeze | tfClearDeepFreeze);
// EnableAmendment flags:
enum EnableAmendmentFlags : uint32_t {
enum EnableAmendmentFlags : std::uint32_t {
tfGotMajority = 0x00010000,
tfLostMajority = 0x00020000,
tfTestSuite = 0x80000000,
+11 -41
View File
@@ -23,9 +23,6 @@
#if !defined(XRPL_FIX)
#error "undefined macro: XRPL_FIX"
#endif
#if !defined(XRPL_RETIRE)
#error "undefined macro: XRPL_RETIRE"
#endif
// clang-format off
@@ -34,13 +31,6 @@
// If you add an amendment here, then do not forget to increment `numFeatures`
// in include/xrpl/protocol/Feature.h.
XRPL_FEATURE(URITokenBroker, 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)
@@ -62,18 +52,18 @@ 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::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(AMM, Supported::yes, 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);
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(Clawback, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (RewardClaimFlags, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(HookCanEmit, Supported::yes, VoteBehavior::DefaultNo)
@@ -157,24 +147,4 @@ 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(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
@@ -93,7 +93,7 @@ LEDGER_ENTRY(ltCHECK, 0x0043, Check, check, ({
*/
LEDGER_ENTRY(ltHOOK_DEFINITION, 'D', HookDefinition, hook_definition, ({
{sfHookHash, soeREQUIRED},
{sfHookOn, soeOPTIONAL},
{sfHookOn, soeREQUIRED},
{sfHookOnIncoming, soeOPTIONAL},
{sfHookOnOutgoing, soeOPTIONAL},
{sfHookCanEmit, soeOPTIONAL},
@@ -396,8 +396,6 @@ 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.
+1 -7
View File
@@ -257,7 +257,6 @@ 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)
@@ -293,7 +292,6 @@ 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)
@@ -319,7 +317,6 @@ TYPED_SFIELD(sfAttestationRewardAccount, ACCOUNT, 21)
TYPED_SFIELD(sfLockingChainDoor, ACCOUNT, 22)
TYPED_SFIELD(sfIssuingChainDoor, ACCOUNT, 23)
TYPED_SFIELD(sfSubject, ACCOUNT, 24)
TYPED_SFIELD(sfBrokerAccount, ACCOUNT, 98)
TYPED_SFIELD(sfInform, ACCOUNT, 99)
// vector of 256-bit
@@ -343,7 +340,6 @@ 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)
@@ -371,7 +367,7 @@ UNTYPED_SFIELD(sfMajority, OBJECT, 18)
UNTYPED_SFIELD(sfDisabledValidator, OBJECT, 19)
UNTYPED_SFIELD(sfEmittedTxn, OBJECT, 20)
UNTYPED_SFIELD(sfHookExecution, OBJECT, 21)
// 22 unused
UNTYPED_SFIELD(sfHookDefinition, OBJECT, 22)
UNTYPED_SFIELD(sfHookParameter, OBJECT, 23)
UNTYPED_SFIELD(sfHookGrant, OBJECT, 24)
UNTYPED_SFIELD(sfVoteEntry, OBJECT, 25)
@@ -390,8 +386,6 @@ 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
@@ -314,7 +314,6 @@ TRANSACTION(ttURITOKEN_BURN, 46, URITokenBurn, ({
TRANSACTION(ttURITOKEN_BUY, 47, URITokenBuy, ({
{sfURITokenID, soeREQUIRED},
{sfAmount, soeREQUIRED},
{sfBrokerAccount, soeOPTIONAL},
}))
TRANSACTION(ttURITOKEN_CREATE_SELL_OFFER, 48, URITokenCreateSellOffer, ({
@@ -551,7 +550,6 @@ 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. **/
-1
View File
@@ -76,7 +76,6 @@ JSS(Holder); // field.
JSS(HookApiVersion); // field
JSS(HookCanEmit); // field
JSS(HookHash); // field
JSS(HookName); // field
JSS(HookNamespace); // field
JSS(HookOn); // field
JSS(HookOnIncoming); // field
+17 -16
View File
@@ -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,19 +31,21 @@ 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"
@@ -55,14 +57,13 @@ 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
@@ -217,7 +218,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"
@@ -227,14 +228,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
+1 -1
View File
@@ -66,7 +66,7 @@ invalidAMMAsset(
Issue const& issue,
std::optional<std::pair<Issue, Issue>> const& pair)
{
if (isBadCurrency(issue.currency))
if (badCurrency() == issue.currency)
return temBAD_CURRENCY;
if (isXRP(issue) && issue.account.isNonZero())
return temBAD_ISSUER;
+30 -8
View File
@@ -250,9 +250,12 @@ 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.");
"More features defined than allocated. Adjust numFeatures in "
"Feature.h.");
auto const f = sha512Half(Slice(name.data(), name.size()));
@@ -421,26 +424,45 @@ 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.
//
+4 -6
View File
@@ -31,8 +31,6 @@
namespace ripple {
#define LEDGER_NAMESPACE2(value1, value2) (uint16_t(value1) << 8) | value2
/** Type-specific prefix for calculating ledger indices.
The identifier for a given object within the ledger is calculated based
@@ -82,14 +80,14 @@ enum class LedgerNameSpace : std::uint16_t {
UNL_REPORT = 'R',
CRON = 'L',
AMM = 'A',
BRIDGE = LEDGER_NAMESPACE2(0x01, 'H'),
BRIDGE = 'H',
XCHAIN_CLAIM_ID = 'Q',
XCHAIN_CREATE_ACCOUNT_CLAIM_ID = 'K',
DID = LEDGER_NAMESPACE2(0x01, 'I'),
ORACLE = LEDGER_NAMESPACE2(0x01, 'R'),
DID = 'I',
ORACLE = 'R',
MPTOKEN_ISSUANCE = '~',
MPTOKEN = 't',
CREDENTIAL = LEDGER_NAMESPACE2(0x01, 'D'),
CREDENTIAL = 'D',
PERMISSIONED_DOMAIN = 'm',
// No longer used or supported. Left here to reserve the space
+13 -19
View File
@@ -87,6 +87,19 @@ InnerObjectFormats::InnerObjectFormats()
{sfEmittedTxnID, soeREQUIRED},
{sfEmitNonce, soeOPTIONAL}});
add(sfHookDefinition.jsonName,
sfHookDefinition.getCode(),
{{sfCreateCode, soeREQUIRED},
{sfHookNamespace, soeREQUIRED},
{sfHookParameters, soeREQUIRED},
{sfHookOn, soeOPTIONAL},
{sfHookOnIncoming, soeOPTIONAL},
{sfHookOnOutgoing, soeOPTIONAL},
{sfHookCanEmit, soeOPTIONAL},
{sfHookApiVersion, soeREQUIRED},
{sfFlags, soeREQUIRED},
{sfFee, soeREQUIRED}});
add(sfHook.jsonName,
sfHook.getCode(),
{{sfHookHash, soeOPTIONAL},
@@ -99,7 +112,6 @@ InnerObjectFormats::InnerObjectFormats()
{sfHookOnOutgoing, soeOPTIONAL},
{sfHookCanEmit, soeOPTIONAL},
{sfHookApiVersion, soeOPTIONAL},
{sfHookName, soeOPTIONAL},
{sfFlags, soeOPTIONAL}});
add(sfHookGrant.jsonName,
@@ -254,24 +266,6 @@ 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&
+1 -1
View File
@@ -111,7 +111,7 @@ issueFromJson(Json::Value const& v)
}
auto const currency = to_currency(curStr.asString());
if (isBadCurrency(currency) || currency == noCurrency())
if (currency == badCurrency() || currency == noCurrency())
{
Throw<Json::error>("issueFromJson currency must be a valid currency");
}
+1 -1
View File
@@ -103,7 +103,7 @@ currencyFromJson(SField const& name, Json::Value const& v)
}
auto const currency = to_currency(v.asString());
if (isBadCurrency(currency) || currency == noCurrency())
if (currency == badCurrency() || currency == noCurrency())
{
Throw<std::runtime_error>(
"currencyFromJson currency must be a valid currency");
-1
View File
@@ -48,7 +48,6 @@ TxFormats::TxFormats()
{sfFirstLedgerSequence, soeOPTIONAL},
{sfNetworkID, soeOPTIONAL},
{sfHookParameters, soeOPTIONAL},
{sfHookName, soeOPTIONAL},
};
#pragma push_macro("UNWRAP")
File diff suppressed because it is too large. Load diff
+11 -15
View File
@@ -32,12 +32,9 @@ 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, features);
Env env(*this);
Account gw{"gateway"};
Account alice{"alice"};
env.fund(XRP(100000), gw, alice);
@@ -63,7 +60,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, features);
Env env(*this);
Account gw{"gateway"};
Account alice{"alice"};
env.fund(XRP(100000), gw, alice);
@@ -96,7 +93,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, features);
Env env(*this);
Account gw{"gateway"};
Account alice{"alice"};
env.fund(XRP(10000), gw, alice);
@@ -126,7 +123,7 @@ class AMMClawback_test : public beast::unit_test::suite
// Test if the Asset field matches the Account field.
{
Env env(*this, features);
Env env(*this);
Account gw{"gateway"};
Account alice{"alice"};
env.fund(XRP(10000), gw, alice);
@@ -158,7 +155,7 @@ class AMMClawback_test : public beast::unit_test::suite
// Test if the Amount field matches the Asset field.
{
Env env(*this, features);
Env env(*this);
Account gw{"gateway"};
Account alice{"alice"};
env.fund(XRP(10000), gw, alice);
@@ -191,7 +188,7 @@ class AMMClawback_test : public beast::unit_test::suite
// Test if the Amount is invalid, which is less than zero.
{
Env env(*this, features);
Env env(*this);
Account gw{"gateway"};
Account alice{"alice"};
env.fund(XRP(10000), gw, alice);
@@ -232,7 +229,7 @@ class AMMClawback_test : public beast::unit_test::suite
// Test if the issuer did not set asfAllowTrustLineClawback, AMMClawback
// transaction is prohibited.
{
Env env(*this, features);
Env env(*this);
Account gw{"gateway"};
Account alice{"alice"};
env.fund(XRP(10000), gw, alice);
@@ -257,7 +254,7 @@ class AMMClawback_test : public beast::unit_test::suite
// Test invalid flag.
{
Env env(*this, features);
Env env(*this);
Account gw{"gateway"};
Account alice{"alice"};
env.fund(XRP(10000), gw, alice);
@@ -285,7 +282,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, features);
Env env(*this);
Account gw{"gateway"};
Account alice{"alice"};
env.fund(XRP(10000), gw, alice);
@@ -316,7 +313,7 @@ class AMMClawback_test : public beast::unit_test::suite
// Test clawing back XRP is being prohibited.
{
Env env(*this, features);
Env env(*this);
Account gw{"gateway"};
Account alice{"alice"};
env.fund(XRP(1000000), gw, alice);
@@ -2088,8 +2085,7 @@ class AMMClawback_test : public beast::unit_test::suite
void
run() override
{
FeatureBitset const all{
jtx::supported_amendments() | featureAMM | featureAMMClawback};
FeatureBitset const all{jtx::supported_amendments()};
testInvalidRequest();
testFeatureDisabled(all - featureAMMClawback);
+15 -31
View File
@@ -1403,8 +1403,7 @@ private:
testOffers()
{
using namespace jtx;
FeatureBitset const all{
supported_amendments() | featureAMM | featureAMMClawback};
FeatureBitset const all{supported_amendments()};
testRmFundedOffer(all);
testEnforceNoRipple(all);
testFillModes(all);
@@ -2803,11 +2802,8 @@ private:
testcase("limitQuality");
using namespace jtx;
auto const features =
supported_amendments() | featureAMM | featureAMMClawback;
{
Env env(*this, features);
Env env(*this);
fund(env, gw, {alice, bob, carol}, XRP(10'000), {USD(2'000)});
@@ -2833,13 +2829,11 @@ private:
using namespace jtx;
auto const features =
supported_amendments() | featureAMM | featureAMMClawback;
for (auto const withFix : {true, false})
{
auto const feats =
withFix ? features : features - FeatureBitset{fix1781};
auto const feats = withFix
? supported_amendments()
: supported_amendments() - FeatureBitset{fix1781};
// Payment path starting with XRP
Env env(*this, feats);
@@ -2867,7 +2861,7 @@ private:
}
{
// Payment path ending with XRP
Env env(*this, features);
Env env(*this);
// Note, if alice doesn't have default ripple, then pay fails
// with tecPATH_DRY.
fund(
@@ -2891,7 +2885,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, features);
Env env(*this);
// Note, if alice doesn't have default ripple, then pay fails
// with tecPATH_DRY.
fund(
@@ -3119,10 +3113,7 @@ private:
using namespace jtx;
auto const features =
supported_amendments() | featureAMM | featureAMMClawback;
Env env(*this, features);
Env env(*this);
fund(env, gw, {alice, bob, carol}, XRP(10'000));
env.trust(USD(1'000), alice, bob, carol);
@@ -3837,8 +3828,7 @@ private:
testFlow()
{
using namespace jtx;
FeatureBitset const all{
supported_amendments() | featureAMM | featureAMMClawback};
FeatureBitset const all{supported_amendments()};
FeatureBitset const ownerPaysFee{featureOwnerPaysFee};
testFalseDry(all);
@@ -3854,8 +3844,7 @@ private:
testCrossingLimits()
{
using namespace jtx;
FeatureBitset const all{
supported_amendments() | featureAMM | featureAMMClawback};
FeatureBitset const all{supported_amendments()};
testStepLimit(all);
}
@@ -3863,16 +3852,14 @@ private:
testDeliverMin()
{
using namespace jtx;
FeatureBitset const all{
supported_amendments() | featureAMM | featureAMMClawback};
FeatureBitset const all{supported_amendments()};
test_convert_all_of_an_asset(all);
}
void
testDepositAuth()
{
auto const supported{
jtx::supported_amendments() | featureAMM | featureAMMClawback};
auto const supported{jtx::supported_amendments()};
testPayment(supported - featureDepositPreauth);
testPayment(supported);
testPayIOU();
@@ -3882,8 +3869,7 @@ private:
testFreeze()
{
using namespace test::jtx;
auto const sa =
supported_amendments() | featureAMM | featureAMMClawback;
auto const sa = supported_amendments();
testRippleState(sa);
testGlobalFreeze(sa);
testOffersWhenFrozen(sa);
@@ -3893,8 +3879,7 @@ private:
testMultisign()
{
using namespace jtx;
auto const all =
supported_amendments() | featureAMM | featureAMMClawback;
auto const all = supported_amendments();
testTxMultisign(
all - featureMultiSignReserve - featureExpandedSignerList);
@@ -3906,8 +3891,7 @@ private:
testPayStrand()
{
using namespace jtx;
auto const all =
supported_amendments() | featureAMM | featureAMMClawback;
auto const all = supported_amendments();
testToStrand(all);
testRIPD1373(all);
+40 -65
View File
@@ -56,9 +56,6 @@ private:
using namespace jtx;
auto const features =
supported_amendments() | featureAMM | featureAMMClawback;
// XRP to IOU
testAMM([&](AMM& ammAlice, Env&) {
BEAST_EXPECT(ammAlice.expectBalances(
@@ -75,7 +72,7 @@ private:
// IOU to IOU + transfer fee
{
Env env{*this, features};
Env env{*this};
fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
env(rate(gw, 1.25));
env.close();
@@ -89,7 +86,7 @@ private:
// Require authorization is set, account is authorized
{
Env env{*this, features};
Env env{*this};
env.fund(XRP(30'000), gw, alice);
env.close();
env(fset(gw, asfRequireAuth));
@@ -103,7 +100,7 @@ private:
// Cleared global freeze
{
Env env{*this, features};
Env env{*this};
env.fund(XRP(30'000), gw, alice);
env.close();
env.trust(USD(30'000), alice);
@@ -144,12 +141,9 @@ private:
using namespace jtx;
auto const features =
supported_amendments() | featureAMM | featureAMMClawback;
// Can't have both XRP tokens
{
Env env{*this, features};
Env env{*this};
fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
AMM ammAlice(
env, alice, XRP(10'000), XRP(10'000), ter(temBAD_AMM_TOKENS));
@@ -158,7 +152,7 @@ private:
// Can't have both tokens the same IOU
{
Env env{*this, features};
Env env{*this};
fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
AMM ammAlice(
env, alice, USD(10'000), USD(10'000), ter(temBAD_AMM_TOKENS));
@@ -167,7 +161,7 @@ private:
// Can't have zero or negative amounts
{
Env env{*this, features};
Env env{*this};
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());
@@ -183,7 +177,7 @@ private:
// Bad currency
{
Env env{*this, features};
Env env{*this};
fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
AMM ammAlice(
env, alice, XRP(10'000), BAD(10'000), ter(temBAD_CURRENCY));
@@ -192,7 +186,7 @@ private:
// Insufficient IOU balance
{
Env env{*this, features};
Env env{*this};
fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
AMM ammAlice(
env, alice, XRP(10'000), USD(40'000), ter(tecUNFUNDED_AMM));
@@ -201,7 +195,7 @@ private:
// Insufficient XRP balance
{
Env env{*this, features};
Env env{*this};
fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
AMM ammAlice(
env, alice, XRP(40'000), USD(10'000), ter(tecUNFUNDED_AMM));
@@ -210,7 +204,7 @@ private:
// Invalid trading fee
{
Env env{*this, features};
Env env{*this};
fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
AMM ammAlice(
env,
@@ -235,7 +229,7 @@ private:
// Invalid flags
{
Env env{*this, features};
Env env{*this};
fund(env, gw, {alice}, {USD(30'000)}, Fund::All);
AMM ammAlice(
env,
@@ -254,7 +248,7 @@ private:
// Invalid Account
{
Env env{*this, features};
Env env{*this};
Account bad("bad");
env.memoize(bad);
AMM ammAlice(
@@ -274,7 +268,7 @@ private:
// Require authorization is set
{
Env env{*this, features};
Env env{*this};
env.fund(XRP(30'000), gw, alice);
env.close();
env(fset(gw, asfRequireAuth));
@@ -287,7 +281,7 @@ private:
// Globally frozen
{
Env env{*this, features};
Env env{*this};
env.fund(XRP(30'000), gw, alice);
env.close();
env(fset(gw, asfGlobalFreeze));
@@ -300,7 +294,7 @@ private:
// Individually frozen
{
Env env{*this, features};
Env env{*this};
env.fund(XRP(30'000), gw, alice);
env.close();
env(trust(gw, alice["USD"](30'000)));
@@ -313,7 +307,7 @@ private:
// Insufficient reserve, XRP/IOU
{
Env env(*this, features);
Env env(*this);
auto const starting_xrp =
XRP(1'000) + reserve(env, 3) + env.current()->fees().base * 4;
env.fund(starting_xrp, gw);
@@ -330,7 +324,7 @@ private:
// Insufficient reserve, IOU/IOU
{
Env env(*this, features);
Env env(*this);
auto const starting_xrp =
reserve(env, 4) + env.current()->fees().base * 5;
env.fund(starting_xrp, gw);
@@ -349,7 +343,7 @@ private:
// Insufficient fee
{
Env env(*this, features);
Env env(*this);
fund(env, gw, {alice}, XRP(2'000), {USD(2'000), EUR(2'000)});
AMM ammAlice(
env,
@@ -400,7 +394,7 @@ private:
// Issuer has DefaultRipple disabled
{
Env env(*this, features);
Env env(*this);
env.fund(XRP(30'000), gw);
env(fclear(gw, asfDefaultRipple));
AMM ammGw(env, gw, XRP(10'000), USD(10'000), ter(terNO_RIPPLE));
@@ -1125,7 +1119,7 @@ private:
// Insufficient reserve, XRP/IOU
{
Env env(*this, features);
Env env(*this);
auto const starting_xrp =
reserve(env, 4) + env.current()->fees().base * 4;
env.fund(XRP(10'000), gw);
@@ -1160,7 +1154,7 @@ private:
// Insufficient reserve, IOU/IOU
{
Env env(*this, features);
Env env(*this);
auto const starting_xrp =
reserve(env, 4) + env.current()->fees().base * 4;
env.fund(XRP(10'000), gw);
@@ -1362,8 +1356,7 @@ private:
testcase("Deposit");
using namespace jtx;
auto const all =
supported_amendments() | featureAMM | featureAMMClawback;
auto const all = supported_amendments();
// Equal deposit: 1000000 tokens, 10% of the current pool
testAMM([&](AMM& ammAlice, Env& env) {
@@ -1523,7 +1516,7 @@ private:
// IOU to IOU + transfer fee
{
Env env{*this, all};
Env env{*this};
fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
env(rate(gw, 1.25));
env.close();
@@ -1664,8 +1657,7 @@ private:
testcase("Invalid Withdraw");
using namespace jtx;
auto const all =
supported_amendments() | featureAMM | featureAMMClawback;
auto const all = supported_amendments();
testAMM(
[&](AMM& ammAlice, Env& env) {
@@ -1688,7 +1680,7 @@ private:
{{XRP(99), USD(99)}});
{
Env env{*this, all};
Env env{*this};
env.fund(XRP(30'000), gw, alice, bob);
env.close();
env(fset(gw, asfRequireAuth));
@@ -2236,8 +2228,7 @@ private:
testcase("Withdraw");
using namespace jtx;
auto const all =
supported_amendments() | featureAMM | featureAMMClawback;
auto const all = supported_amendments();
// Equal withdrawal by Carol: 1000000 of tokens, 10% of the current
// pool
@@ -2451,7 +2442,7 @@ private:
// IOU to IOU + transfer fee
{
Env env{*this, all};
Env env{*this};
fund(env, gw, {alice}, {USD(20'000), BTC(0.5)}, Fund::All);
env(rate(gw, 1.25));
env.close();
@@ -2614,8 +2605,7 @@ private:
{
testcase("Fee Vote");
using namespace jtx;
auto const all =
supported_amendments() | featureAMM | featureAMMClawback;
auto const all = supported_amendments();
// One vote sets fee to 1%.
testAMM([&](AMM& ammAlice, Env& env) {
@@ -2733,12 +2723,9 @@ 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, features);
Env env(*this);
fund(env, gw, {alice}, XRP(2'000), {USD(2'000)});
AMM amm(env, gw, XRP(1'000), USD(1'000), false, 1'000);
@@ -2757,7 +2744,7 @@ private:
// burn all the LPTokens through a AMMBid transaction
{
Env env(*this, features);
Env env(*this);
fund(env, gw, {alice}, XRP(2'000), {USD(2'000)});
AMM amm(env, gw, XRP(1'000), USD(1'000), false, 1'000);
@@ -2935,7 +2922,7 @@ private:
// Bid all tokens, still own the slot
{
Env env(*this, features);
Env env(*this);
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();
@@ -3374,15 +3361,12 @@ 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, features);
Env env(*this);
fund(env, gw, {alice, carol}, XRP(1'000), {USD(100)});
// XRP balance is below reserve
AMM ammAlice(env, acct, XRP(10), USD(10));
@@ -3397,7 +3381,7 @@ private:
ter(tecNO_PERMISSION));
}
{
Env env(*this, features);
Env env(*this);
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));
@@ -4433,8 +4417,7 @@ private:
{
testcase("Amendment");
using namespace jtx;
FeatureBitset const all{
supported_amendments() | featureAMM | featureAMMClawback};
FeatureBitset const all{supported_amendments()};
FeatureBitset const noAMM{all - featureAMM};
FeatureBitset const noNumber{all - fixUniversalNumber};
FeatureBitset const noAMMAndNumber{
@@ -4484,9 +4467,6 @@ 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,
@@ -4498,7 +4478,7 @@ private:
// to shift at a 1:1 rate with no fee applied has it not been
// for 0 limit.
{
Env env(*this, features);
Env env(*this);
auto const A = Account("A");
auto const B = Account("B");
auto const TSTA = A["TST"];
@@ -5095,8 +5075,7 @@ private:
testcase("Auto Delete");
using namespace jtx;
FeatureBitset const all{
supported_amendments() | featureAMM | featureAMMClawback};
FeatureBitset const all{supported_amendments()};
{
Env env(
@@ -5208,9 +5187,7 @@ private:
{
testcase("Clawback");
using namespace jtx;
auto const features =
supported_amendments() | featureAMM | featureAMMClawback;
Env env(*this, features);
Env env(*this);
env.fund(XRP(2'000), gw);
env.fund(XRP(2'000), alice);
AMM amm(env, gw, XRP(1'000), USD(1'000));
@@ -5609,8 +5586,7 @@ private:
{
testcase("Fix Default Inner Object");
using namespace jtx;
FeatureBitset const all{
supported_amendments() | featureAMM | featureAMMClawback};
FeatureBitset const all{supported_amendments()};
auto test = [&](FeatureBitset features,
TER const& err1,
@@ -6329,7 +6305,7 @@ private:
{{xrpPool, iouPool}},
889,
std::nullopt,
{jtx::supported_amendments() | featureAMM | featureAMMClawback});
{jtx::supported_amendments()});
}
void
@@ -7145,8 +7121,7 @@ private:
void
run() override
{
FeatureBitset const all{
jtx::supported_amendments() | featureAMM | featureAMMClawback};
FeatureBitset const all{jtx::supported_amendments()};
testInvalidInstance();
testInstanceCreate();
testInvalidDeposit(all);
+13 -753
View File
@@ -18,7 +18,6 @@
//==============================================================================
#include <test/jtx.h>
#include <test/jtx/AMM.h>
#include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/jss.h>
@@ -27,14 +26,6 @@ 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,
@@ -68,52 +59,6 @@ public:
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)
{
@@ -137,24 +82,6 @@ public:
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)
{
@@ -164,7 +91,7 @@ public:
// setup env
auto const alice = Account("alice");
auto const issuer = Account::master;
auto const issuer = Account("issuer");
for (bool const withClaimReward : {false, true})
{
@@ -174,11 +101,7 @@ public:
withClaimReward ? features : features - featureBalanceRewards;
Env env{*this, amend};
env.fund(XRP(1000), alice);
env.close();
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0),
fee(XRP(1)));
env.fund(XRP(1000), alice, issuer);
env.close();
auto const txResult =
@@ -195,10 +118,7 @@ public:
.count();
// CLAIM
env(reward::claim(alice),
reward::issuer(issuer),
fee(XRP(1)),
txResult);
env(reward::claim(alice), reward::issuer(issuer), txResult);
env.close();
if (withClaimReward)
@@ -274,19 +194,14 @@ public:
Env env{*this, amend};
auto const alice = Account("alice");
auto const issuer = Account::master;
auto const issuer = Account("issuer");
env.fund(XRP(1000), alice);
env.close();
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0),
fee(XRP(1)));
env.fund(XRP(1000), alice, issuer);
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();
@@ -305,105 +220,6 @@ public:
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
@@ -427,7 +243,6 @@ public:
auto const alice = Account("alice");
auto const issuer = Account("issuer");
env.memoize(alice);
auto USD = issuer["USD"];
env.fund(XRP(1000), issuer);
env.close();
@@ -435,10 +250,7 @@ public:
auto tx = reward::claim(alice);
tx[jss::Sequence] = 0;
tx[jss::Fee] = 10;
env(tx,
reward::issuer(issuer),
reward::claimCurrency(USD),
ter(terNO_ACCOUNT));
env(tx, reward::issuer(issuer), ter(terNO_ACCOUNT));
env.close();
}
@@ -448,9 +260,9 @@ public:
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
auto const alice = Account("alice");
auto const issuer = Account::master;
auto const issuer = Account("issuer");
env.fund(XRP(1000), alice);
env.fund(XRP(1000), alice, issuer);
env.close();
env(reward::claim(alice),
@@ -481,196 +293,13 @@ public:
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),
reward::claimCurrency(USD),
ter(tecNO_ISSUER));
env(tx, reward::issuer(issuer), ter(tecNO_ISSUER));
env.close();
}
// tecNO_PERMISSION
// issuer is an AMM account
{
test::jtx::Env env{
*this,
network::makeNetworkConfig(21337),
features | featureAMM};
auto const alice = Account("alice");
auto const issuer = Account("issuer");
auto const USD = issuer["USD"];
env.fund(XRP(1000), alice, issuer);
env.close();
AMM amm(env, issuer, XRP(100), USD(100));
BEAST_EXPECT(amm.ammExists());
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));
}
// 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
@@ -683,17 +312,9 @@ public:
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, 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.fund(XRP(1000), alice, issuer);
env.close();
// test claim rewards - no opt out
@@ -708,7 +329,7 @@ public:
.count();
auto tx = reward::claim(alice);
env(tx, reward::issuer(Account::master), fee(XRP(1)), ter(tesSUCCESS));
env(tx, reward::issuer(issuer), ter(tesSUCCESS));
env.close();
BEAST_EXPECT(
@@ -721,51 +342,6 @@ public:
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
@@ -776,20 +352,16 @@ public:
using namespace std::literals::chrono_literals;
Env env{*this, features};
auto const alice = Account("alice");
auto const issuer = Account::master;
env.fund(XRP(10000), alice);
auto const issuer = Account("issuer");
env.fund(XRP(10000), alice, issuer);
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));
@@ -797,317 +369,6 @@ public:
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)
{
@@ -1116,7 +377,6 @@ public:
testInvalidPreclaim(features);
testValidNoHook(features);
testUsingTickets(features);
testBalanceChanges(features);
}
public:
-89
View File
@@ -18,7 +18,6 @@
//==============================================================================
#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>
@@ -4317,93 +4316,6 @@ 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)
{
@@ -4817,7 +4729,6 @@ struct Escrow_test : public beast::unit_test::suite
testIOUTLINSF(features);
testIOUPrecisionLoss(features);
testIOUClawback(features);
testIOUAMM(features);
}
public:
+1 -326
View File
@@ -50,7 +50,6 @@ private:
}
public:
using enum hook_api::hook_return_code;
void
test_accept(FeatureBitset features)
{
@@ -3632,9 +3631,8 @@ public:
auto const alice = Account{"alice"};
auto const bob = Account{"bob"};
auto const claire = Account{"claire"};
Env env{*this, features};
env.fund(XRP(100000), alice, bob, claire);
env.fund(XRP(100000), alice, bob);
env.close();
// Compute hook hash for the accept hook
@@ -3774,84 +3772,6 @@ public:
BEAST_EXPECT(result2.has_value());
BEAST_EXPECT(result2.value() == newData.size());
}
{
// fixHookMap: foreign state set without grant after state previous
// modified
HookStateMap stateMap;
auto hookCtx = makeStubHookContext(
applyCtx, alice.id(), bob.id(), {}, stateMap);
AccountID const aliceid = alice.id();
// Pre-populate stateMap
stateMap[alice.id()] = {
100, // availableForReserves
1, // namespaceCount
1, // hookStateScale
{}};
auto& api = hookCtx.api();
// setup a hook on alice, and on claire, no grants
env(hook(alice, {{hso(genesis::AcceptHook)}}, 0), fee(XRP(1)));
env(hook(claire, {{hso(genesis::AcceptHook)}}, 0), fee(XRP(1)));
env.close();
// First modification
auto result1 =
api.state_foreign_set(testKey, testNs, aliceid, testData);
BEAST_EXPECT(result1.has_value());
// Second modification this time using bob as hookacc (should hit
// cache)
auto hookCtx2 = makeStubHookContext(
applyCtx, claire.id(), bob.id(), {}, stateMap);
// check the state entry is carried into the second context
// does the map contain the account?
BEAST_EXPECT(
hookCtx2.result.stateMap.find(aliceid) !=
hookCtx2.result.stateMap.end());
// the name space?
BEAST_EXPECT(
std::get<3>(hookCtx2.result.stateMap[aliceid]).find(testNs) !=
std::get<3>(hookCtx2.result.stateMap[aliceid]).end());
// the key entry?
BEAST_EXPECT(
std::get<3>(hookCtx2.result.stateMap[aliceid])[testNs].find(
testKey) !=
std::get<3>(hookCtx2.result.stateMap[aliceid])[testNs].end());
// is the entry marked as modified?
BEAST_EXPECT(
std::get<3>(hookCtx2.result.stateMap[aliceid])[testNs][testKey]
.first);
auto& api2 = hookCtx2.api();
Bytes newData{0x04, 0x05};
auto result2 =
api2.state_foreign_set(testKey, testNs, aliceid, newData);
if (features[fixHookMap])
{
// new behaviour: grant is missing, cannot write
BEAST_EXPECT(!result2.has_value());
BEAST_EXPECT(result2.error() == NOT_AUTHORIZED);
BEAST_EXPECT(hookCtx2.result.foreignStateSetDisabled);
}
else
{
// old behaviour: allow this illegal write due to the entry
// being modified previously in the map
BEAST_EXPECT(result2.has_value());
BEAST_EXPECT(result2.value() == newData.size());
}
}
}
void
@@ -4593,249 +4513,6 @@ public:
BEAST_EXPECT(ok.value());
}
void
testHookFloat(FeatureBitset features)
{
// testcase("Test hook float");
using namespace jtx;
using namespace hook::hook_float;
{
// get_exponent
testcase << "get_exponent";
BEAST_EXPECT(get_exponent(-1).error() == INVALID_FLOAT);
BEAST_EXPECT(get_exponent(0).value() == 0);
// 1234567891000000 * 10^(-5)
BEAST_EXPECT(get_exponent(6270245249190730432).value() == -5);
}
{
// get_mantissa
testcase << "get_mantissa";
BEAST_EXPECT(get_mantissa(-1).error() == INVALID_FLOAT);
BEAST_EXPECT(get_mantissa(0).value() == 0);
// 1234567891000000 * 10^(-5)
BEAST_EXPECT(
get_mantissa(6270245249190730432).value() == 1234567891000000);
}
{
// is_negative
testcase << "is_negative";
// 1234567891000000 * 10^(-5)
BEAST_EXPECT(is_negative(6270245249190730432) == false);
// -1234567891000000 * 10^(-5)
BEAST_EXPECT(is_negative(1658559230763342528) == true);
}
{
// invert_sign
testcase << "invert_sign";
BEAST_EXPECT(
invert_sign(6270245249190730432) == 1658559230763342528);
BEAST_EXPECT(
invert_sign(1658559230763342528) == 6270245249190730432);
}
{
// set_sign
testcase << "set_sign";
BEAST_EXPECT(
set_sign(6270245249190730432, true) == 1658559230763342528);
BEAST_EXPECT(
set_sign(1658559230763342528, false) == 6270245249190730432);
BEAST_EXPECT(
set_sign(6270245249190730432, false) == 6270245249190730432);
BEAST_EXPECT(
set_sign(1658559230763342528, true) == 1658559230763342528);
}
{
// set_mantissa
testcase << "set_mantissa";
BEAST_EXPECT(
set_mantissa(6270245249190730432, maxMantissa + 1).error() ==
MANTISSA_OVERSIZED);
BEAST_EXPECT(
set_mantissa(6270245249190730432, minMantissa - 1).error() ==
MANTISSA_UNDERSIZED);
BEAST_EXPECT(
set_mantissa(6270245249190730432, 1234567891000000).value() ==
6270245249190730432);
BEAST_EXPECT(
set_mantissa(1658559230763342528, 1234567891000000).value() ==
1658559230763342528);
BEAST_EXPECT(
set_mantissa(6270245249190730432, 1098765432100000).value() ==
6270109446731830432);
BEAST_EXPECT(
set_mantissa(1658559230763342528, 1098765432100000).value() ==
1658423428304442528);
}
{
// set_exponent
testcase << "set_exponent";
BEAST_EXPECT(
set_exponent(6270245249190730432, maxExponent + 1).error() ==
EXPONENT_OVERSIZED);
BEAST_EXPECT(
set_exponent(6270245249190730432, minExponent - 1).error() ==
EXPONENT_UNDERSIZED);
BEAST_EXPECT(
set_exponent(6270245249190730432, 40).value() ==
7080893182117419712);
BEAST_EXPECT(
set_exponent(6270245249190730432, -40).value() ==
5639741301358860992);
}
{
// make_float
testcase << "make_float";
BEAST_EXPECT(make_float(0, -5, false).value() == 0);
// invalid mantissa
BEAST_EXPECT(
make_float(maxMantissa + 1, -5, false).error() ==
MANTISSA_OVERSIZED);
BEAST_EXPECT(
make_float(minMantissa - 1, -5, false).error() ==
MANTISSA_UNDERSIZED);
// invalid exponent
BEAST_EXPECT(
make_float(1234567891000000, maxExponent + 1, false).error() ==
EXPONENT_OVERSIZED);
BEAST_EXPECT(
make_float(1234567891000000, minExponent - 1, false).error() ==
EXPONENT_UNDERSIZED);
BEAST_EXPECT(
make_float(1234567891000000, -5, false).value() ==
6270245249190730432);
BEAST_EXPECT(
make_float(1234567891000000, -5, true).value() ==
1658559230763342528);
}
{
// normalize_xfl
testcase << "normalize_xfl";
{
// zero
uint64_t mantissa = 0;
int32_t exponent = -5;
BEAST_EXPECT(
normalize_xfl(mantissa, exponent, false).value() == 0);
BEAST_EXPECT(mantissa == 0);
BEAST_EXPECT(exponent == -5);
}
{
// scale the mantissa up into the canonical range
uint64_t mantissa = 12345;
int32_t exponent = 0;
BEAST_EXPECT(
normalize_xfl(mantissa, exponent, false).value() ==
make_float(1234500000000000ULL, -11, false).value());
BEAST_EXPECT(mantissa == 1234500000000000ULL);
BEAST_EXPECT(exponent == -11);
}
{
// scale the mantissa down into the canonical range
uint64_t mantissa = 1234567891000000000ULL;
int32_t exponent = -5;
BEAST_EXPECT(
normalize_xfl(mantissa, exponent, false).value() ==
make_float(1234567891000000ULL, -2, false).value());
BEAST_EXPECT(mantissa == 1234567891000000ULL);
BEAST_EXPECT(exponent == -2);
}
{
// explicit negative sign
uint64_t mantissa = 12345;
int32_t exponent = 0;
BEAST_EXPECT(
normalize_xfl(mantissa, exponent, true).value() ==
make_float(1234500000000000ULL, -11, true).value());
BEAST_EXPECT(mantissa == 1234500000000000ULL);
BEAST_EXPECT(exponent == -11);
}
{
// signed negative mantissa
int64_t mantissa = -12345;
int32_t exponent = 0;
BEAST_EXPECT(
normalize_xfl(mantissa, exponent).value() ==
make_float(1234500000000000ULL, -11, true).value());
BEAST_EXPECT(mantissa == -1234500000000000LL);
BEAST_EXPECT(exponent == -11);
}
{
// signed minimum is adjusted before taking the absolute value
int64_t mantissa = std::numeric_limits<int64_t>::min();
int32_t exponent = 0;
BEAST_EXPECT(
normalize_xfl(mantissa, exponent).value() ==
make_float(9223372036854775ULL, 3, true).value());
BEAST_EXPECT(mantissa == -9223372036854775LL);
BEAST_EXPECT(exponent == 3);
}
{
// one below the minimum mantissa is rounded into range
uint64_t mantissa = minMantissa - 1;
int32_t exponent = -5;
BEAST_EXPECT(
normalize_xfl(mantissa, exponent, false).value() ==
make_float(minMantissa, -5, false).value());
BEAST_EXPECT(mantissa == minMantissa);
BEAST_EXPECT(exponent == -5);
}
{
// underflow returns canonical zero
uint64_t mantissa = 1;
int32_t exponent = minExponent;
BEAST_EXPECT(
normalize_xfl(mantissa, exponent, false).value() == 0);
BEAST_EXPECT(mantissa == 0);
BEAST_EXPECT(exponent == 0);
}
{
// exponent overflow is reported
uint64_t mantissa = minMantissa;
int32_t exponent = maxExponent + 1;
BEAST_EXPECT(
normalize_xfl(mantissa, exponent, false).error() ==
XFL_OVERFLOW);
BEAST_EXPECT(mantissa == minMantissa);
BEAST_EXPECT(exponent == maxExponent + 1);
}
}
}
void
testWithFeatures(FeatureBitset features)
{
@@ -4911,7 +4588,6 @@ public:
test_state(features);
test_state_foreign(features);
test_state_foreign_set(features - fixHookMap);
test_state_foreign_set(features);
test_state_foreign_set_max(features);
test_state_set(features);
@@ -4939,7 +4615,6 @@ public:
{
using namespace test::jtx;
testWithFeatures(supported_amendments());
testHookFloat(supported_amendments());
}
};
-73
View File
@@ -19,7 +19,6 @@
#include <test/app/Import_json.h>
#include <test/jtx.h>
#include <test/jtx/AMM.h>
#include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpld/app/misc/AmendmentTable.h>
#include <xrpld/app/misc/HashRouter.h>
@@ -2636,78 +2635,6 @@ class Import_test : public beast::unit_test::suite
ter(temDISABLED));
}
// tecNO_ISSUER, tecNO_PERMISSION
// issuer not found, issuer is an AMM account
{
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const issuer = Account("issuer");
auto const USD = issuer["USD"];
for (bool const withFixImportIssuer : {true, false})
{
auto const amend =
withFixImportIssuer ? features : features - fixImportIssuer;
test::jtx::Env env{
*this, network::makeNetworkVLConfig(21337, keys), amend};
env.fund(XRP(1000), alice, issuer);
env.close();
// burn 10'000 xrp
auto const master = Account("masterpassphrase");
env(noop(master), fee(10'000'000'000), ter(tesSUCCESS));
env.close();
env(import::import(
alice, import::loadXpop(ImportTCAccountSet::w_seed)),
import::issuer(bob),
fee(100'000'000),
withFixImportIssuer ? ter(tecNO_ISSUER) : ter(tesSUCCESS));
env.close();
}
for (bool const withFixImportIssuer : {true, false})
{
auto const withAMM = features | featureAMM;
auto const amend =
withFixImportIssuer ? withAMM : withAMM - fixImportIssuer;
test::jtx::Env env{
*this, network::makeNetworkVLConfig(21337, keys), amend};
env.fund(XRP(1000), alice, issuer);
env.close();
// burn 10'000 xrp
auto const master = Account("masterpassphrase");
env(noop(master), fee(10'000'000'000), ter(tesSUCCESS));
env.close();
AMM amm(env, issuer, XRP(100), USD(100));
BEAST_EXPECT(amm.ammExists());
env(import::import(
alice, import::loadXpop(ImportTCAccountSet::w_seed)),
import::issuer(amm.ammAccount()),
fee(100'000'000),
withFixImportIssuer ? ter(tecNO_PERMISSION)
: ter(tesSUCCESS));
env.close();
}
// env.enableFeature(fixImportIssuer);
// env.close();
// env(import::import(
// carol, import::loadXpop(ImportTCAccountSet::w_seed)),
// import::issuer(bob),
// ter(tecNO_ISSUER));
// env.close();
// env(import::import(
// dave, import::loadXpop(ImportTCAccountSet::w_seed)),
// import::issuer(amm.ammAccount()),
// // fee(100'000'000),
// ter(tecNO_PERMISSION));
// env.close();
}
// tefINTERNAL
// during preclaim could not parse xpop, bailing.
{
-25
View File
@@ -18,7 +18,6 @@
//==============================================================================
#include <test/jtx.h>
#include <test/jtx/AMM.h>
#include <xrpl/protocol/jss.h>
namespace ripple {
@@ -169,30 +168,6 @@ class Invoke_test : public beast::unit_test::suite
fee(feeDrops),
ter(tecNO_TARGET));
}
// tecNO_PERMISSION
// issuer is an AMM account
{
test::jtx::Env env{
*this,
network::makeNetworkConfig(21337),
features | featureAMM};
auto const alice = Account("alice");
auto const issuer = Account("issuer");
auto const USD = issuer["USD"];
env.fund(XRP(1000), alice, issuer);
env.close();
AMM amm(env, issuer, XRP(100), USD(100));
BEAST_EXPECT(amm.ammExists());
env(invoke::invoke(alice),
invoke::dest(amm.ammAccount()),
ter(tecNO_PERMISSION));
env.close();
}
}
void
+1 -1
View File
@@ -388,7 +388,7 @@ public:
void
run() override
{
FeatureBitset const all{jtx::supported_amendments() | featureAMM};
FeatureBitset const all{jtx::supported_amendments()};
testDirectStep(all);
testBookStep(all);
-213
View File
@@ -20,8 +20,6 @@
#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 {
@@ -119,224 +117,13 @@ class LedgerMaster_test : public beast::unit_test::suite
}
}
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
-7
View File
@@ -1878,13 +1878,6 @@ 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());
-40
View File
@@ -568,46 +568,6 @@ 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
-162
View File
@@ -20,8 +20,6 @@
#include <test/jtx.h>
#include <test/jtx/envconfig.h>
#include <xrpld/app/paths/AccountCurrencies.h>
#include <xrpld/app/paths/Pathfinder.h>
#include <xrpld/app/paths/RippleLineCache.h>
#include <xrpld/core/JobQueue.h>
#include <xrpld/rpc/Context.h>
#include <xrpld/rpc/RPCHandler.h>
@@ -575,164 +573,6 @@ public:
BEAST_EXPECT(equal(sa, Account("alice")["USD"](5)));
}
Json::Value
six_path_append_request_result()
{
using namespace jtx;
Env env = pathTestEnv();
Account A1{"A1"};
Account A2{"A2"};
Account G1{"G1"};
Account G2{"G2"};
Account M1{"M1"};
Account M2{"M2"};
Account M3{"M3"};
Account M4{"M4"};
Account M5{"M5"};
Account M6{"M6"};
Account MM{"MM"};
env.fund(XRP(1000), A1, A2, G1, G2, M1, M2, M3, M4, M5, M6, MM);
env.close();
env.trust(G1["HKD"](2000), A1);
env.trust(G2["HKD"](2000), A2);
env.trust(G1["HKD"](100000), M1, M2, M3, M4, M5, M6, MM);
env.trust(G2["HKD"](100000), M1, M2, M3, M4, M5, M6, MM);
env.close();
env(pay(G1, A1, G1["HKD"](1000)));
env(pay(G1, M1, G1["HKD"](10)));
env(pay(G1, M2, G1["HKD"](10)));
env(pay(G1, M3, G1["HKD"](10)));
env(pay(G1, M4, G1["HKD"](10)));
env(pay(G1, M5, G1["HKD"](10)));
env(pay(G1, M6, G1["HKD"](10)));
env(pay(G1, MM, G1["HKD"](1000)));
env(pay(G2, M1, G2["HKD"](10)));
env(pay(G2, M2, G2["HKD"](10)));
env(pay(G2, M3, G2["HKD"](10)));
env(pay(G2, M4, G2["HKD"](10)));
env(pay(G2, M5, G2["HKD"](10)));
env(pay(G2, M6, G2["HKD"](10)));
env(pay(G2, MM, G2["HKD"](1000)));
env.close();
env(offer(MM, G1["HKD"](1000), G2["HKD"](100)));
env.close();
return find_paths_request(
env, A1, A2, A2["HKD"](60), std::nullopt, G1["HKD"].currency);
}
void
pathfind_paths_computed_never_exceeds_six()
{
testcase("pathfind paths_computed never exceeds six");
auto const result = six_path_append_request_result();
BEAST_EXPECT(result.isMember(jss::alternatives));
if (!result.isMember(jss::alternatives))
return;
BEAST_EXPECT(result[jss::alternatives].isArray());
if (!result[jss::alternatives].isArray())
return;
bool sawPathsComputed = false;
for (auto const& alt : result[jss::alternatives])
{
if (!alt.isMember(jss::paths_computed))
continue;
sawPathsComputed = true;
BEAST_EXPECT(alt[jss::paths_computed].isArray());
if (alt[jss::paths_computed].isArray())
BEAST_EXPECT(alt[jss::paths_computed].size() <= 6);
}
BEAST_EXPECT(sawPathsComputed);
}
void
pathfind_can_return_six_paths_with_append()
{
testcase("pathfind can return six paths with append");
using namespace jtx;
Env env = pathTestEnv();
Account A1{"A1"};
Account A2{"A2"};
Account G1{"G1"};
Account G2{"G2"};
Account M1{"M1"};
Account M2{"M2"};
Account M3{"M3"};
Account M4{"M4"};
Account M5{"M5"};
Account M6{"M6"};
Account MM{"MM"};
env.fund(XRP(1000), A1, A2, G1, G2, M1, M2, M3, M4, M5, M6, MM);
env.close();
env.trust(G1["HKD"](2000), A1);
env.trust(G2["HKD"](2000), A2);
env.trust(G1["HKD"](100000), M1, M2, M3, M4, M5, M6, MM);
env.trust(G2["HKD"](100000), M1, M2, M3, M4, M5, M6, MM);
env.close();
env(pay(G1, A1, G1["HKD"](1000)));
env(pay(G1, M1, G1["HKD"](10)));
env(pay(G1, M2, G1["HKD"](10)));
env(pay(G1, M3, G1["HKD"](10)));
env(pay(G1, M4, G1["HKD"](10)));
env(pay(G1, M5, G1["HKD"](10)));
env(pay(G1, M6, G1["HKD"](10)));
env(pay(G1, MM, G1["HKD"](1000)));
env(pay(G2, M1, G2["HKD"](10)));
env(pay(G2, M2, G2["HKD"](10)));
env(pay(G2, M3, G2["HKD"](10)));
env(pay(G2, M4, G2["HKD"](10)));
env(pay(G2, M5, G2["HKD"](10)));
env(pay(G2, M6, G2["HKD"](10)));
env(pay(G2, MM, G2["HKD"](1000)));
env.close();
env(offer(MM, G1["HKD"](1000), G2["HKD"](100)));
env.close();
auto cache = std::make_shared<RippleLineCache>(
env.current(), env.app().journal("RippleLineCache"));
Pathfinder pf(
cache,
A1.id(),
A2.id(),
G1["HKD"].currency,
std::nullopt,
A2["HKD"](60),
std::nullopt,
env.app());
BEAST_EXPECT(pf.findPaths(7));
pf.computePathRanks(5);
STPath fullLiquidityPath;
auto bestPaths =
pf.getBestPaths(5, fullLiquidityPath, STPathSet{}, A1.id());
BEAST_EXPECT(bestPaths.size() == 5);
BEAST_EXPECT(!fullLiquidityPath.empty());
if (!fullLiquidityPath.empty())
bestPaths.push_back(fullLiquidityPath);
BEAST_EXPECT(bestPaths.size() == 6);
}
void
issues_path_negative_issue()
{
@@ -1541,8 +1381,6 @@ public:
path_find_04();
path_find_05();
path_find_06();
pathfind_paths_computed_never_exceeds_six();
pathfind_can_return_six_paths_with_append();
}
};
-50
View File
@@ -18,7 +18,6 @@
//==============================================================================
#include <test/jtx.h>
#include <test/jtx/AMM.h>
#include <xrpld/core/ConfigSections.h>
#include <xrpld/ledger/Dir.h>
#include <xrpl/basics/chrono.h>
@@ -411,21 +410,6 @@ struct Remit_test : public beast::unit_test::suite
env.close();
}
// tecNO_PERMISSION - inform account is an AMM
{
Env env{*this, features | featureAMM};
env.fund(XRP(1000), gw, alice, bob);
env.close();
AMM amm(env, gw, XRP(100), USD(100));
BEAST_EXPECT(amm.ammExists());
auto tx = remit::remit(alice, bob);
tx[sfInform.jsonName] = to_string(amm.ammAccount());
env(tx, alice, ter(tecNO_PERMISSION));
env.close();
}
// tecNO_PERMISSION - lsfDisallowIncomingRemit
// DA: see testAllowIncoming
@@ -2891,39 +2875,6 @@ struct Remit_test : public beast::unit_test::suite
}
}
void
testDestAMM(FeatureBitset features)
{
testcase("remit to AMM Account");
using namespace test::jtx;
using namespace std::literals;
auto const alice = Account("alice");
auto const gw = Account("gw");
auto const USD = gw["USD"];
Env env{*this, features | featureAMM};
env.fund(XRP(10'000'000), alice, gw);
env.close();
env.trust(USD(100'000), alice);
env.close();
env(pay(gw, alice, USD(100'000)));
env.close();
AMM ammAlice(env, alice, XRP(10'000), USD(10'000));
env(remit::remit(alice, ammAlice.ammAccount()),
remit::amts({USD(10'000)}),
ter(tecNO_PERMISSION));
env(remit::remit(alice, ammAlice.ammAccount()),
remit::amts({XRP(10'000)}),
ter(tecNO_PERMISSION));
env(remit::remit(gw, ammAlice.ammAccount()),
remit::amts({gw["EUR"](10'000)}),
ter(tecNO_PERMISSION));
}
void
testWithFeats(FeatureBitset features)
{
@@ -2945,7 +2896,6 @@ struct Remit_test : public beast::unit_test::suite
testRippling(features);
testURIToken(features);
testOptionals(features);
testDestAMM(features);
}
public:
-201
View File
@@ -22,14 +22,10 @@
#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/detail/DatabaseRotatingImp.h>
#include <xrpl/beast/utility/temp_dir.h>
#include <xrpl/protocol/jss.h>
#include <boost/filesystem.hpp>
namespace ripple {
namespace test {
@@ -55,25 +51,6 @@ 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,
@@ -671,191 +648,13 @@ 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();
testCanDelete();
testRotate();
testPinnedRangeRestoreRequiresPinnedData();
}
};
File diff suppressed because it is too large. Load diff
+10 -483
View File
@@ -690,8 +690,6 @@ public:
bool const hasHookCanEmit =
env.current()->rules().enabled(featureHookCanEmit);
bool const hasNamedHooks =
env.current()->rules().enabled(featureNamedHooks);
auto const alice = Account{"alice"};
env.fund(XRP(10000), alice);
@@ -728,8 +726,7 @@ public:
}
// grants, parameters, hookon, hookonincoming, hookonoutgoing,
// hookcanemit, hookapiversion, hooknamespace, hookname keys must be
// absent
// hookcanemit, hookapiversion, hooknamespace keys must be absent
for (auto const& [key, value] : JSSMap{
{jss::HookGrants, Json::arrayValue},
{jss::HookParameters, Json::arrayValue},
@@ -746,16 +743,11 @@ public:
"000000000000000000000000000000000000000000000000000000000000"
"0000"},
{jss::HookApiVersion, "0"},
{jss::HookNamespace, to_string(uint256{beast::zero})},
{jss::HookName, strHex(std::string{"DEADBEEF"})},
})
{jss::HookNamespace, to_string(uint256{beast::zero})}})
{
if (!hasHookCanEmit && key == jss::HookCanEmit)
continue;
if (!hasNamedHooks && key == jss::HookName)
continue;
Json::Value iv;
iv[jss::CreateCode] = "";
iv[key] = value;
@@ -763,7 +755,7 @@ public:
env(jv,
M("Hook DELETE operation cannot include: grants, params, "
"hookon, HookOnIncoming, HookOnOutgoing, hookcanemit, "
"apiversion, namespace, hookname"),
"apiversion, namespace"),
HSFEE,
ter(temMALFORMED));
env.close();
@@ -902,8 +894,6 @@ public:
bool const fixNS = env.current()->rules().enabled(fixNSDelete);
bool const hasHookCanEmit =
env.current()->rules().enabled(featureHookCanEmit);
bool const hasNamedHooks =
env.current()->rules().enabled(featureNamedHooks);
auto const alice = Account{"alice"};
env.fund(XRP(10000), alice);
@@ -936,15 +926,11 @@ public:
"000000000000000000000000000000000000000000000000000000000000"
"0000"},
{jss::HookApiVersion, "0"},
{jss::HookName, strHex(std::string{"DEADBEEF"})},
})
{
if (!hasHookCanEmit && key == jss::HookCanEmit)
continue;
if (!hasNamedHooks && key == jss::HookName)
continue;
Json::Value iv;
iv[key] = value;
iv[jss::Flags] = hsfNSDELETE;
@@ -953,7 +939,7 @@ public:
env(jv,
M("Hook NSDELETE operation cannot include: grants, params, "
"hookon, hookonincoming, hookonoutgoing, hookcanemit, "
"apiversion, hookname"),
"apiversion"),
HSFEE,
ter(temMALFORMED));
env.close();
@@ -1698,340 +1684,6 @@ public:
}
}
void
testHookName(FeatureBitset features)
{
testcase("Test hook name");
using namespace jtx;
auto const alice = Account{"alice"};
auto const bob = Account{"bob"};
auto const charlie = Account{"charlie"};
auto const USD = alice["USD"];
Env env{*this, features};
env.fund(XRP(10000), alice, bob);
env.close();
// Invalid hook name (length=3,17, not utf-8)
for (auto const name : {
"414243", // ABC (length=3)
"4142434445464748494A4B4C4D4E4F5051", // ABCDEFGHIJKLMNOPQ
// (length=17)
"DEADBEEF", // not utf-8
})
{
auto jvh = hso(accept_wasm);
jvh[jss::HookName] = name;
env(ripple::test::jtx::hook(alice, {{jvh}}, 0),
M("Hook name must be between 8 and 32 hex characters and be a "
"valid UTF-8 string"),
HSFEE,
features[featureNamedHooks] ? ter(temMALFORMED)
: ter(temDISABLED));
auto jvi = invoke::invoke(alice);
jvi[jss::HookName] = name;
env(jvi,
M("Call named hook with the invalid hook name"),
HSFEE,
ter(temMALFORMED));
}
if (!features[featureNamedHooks])
return;
{
/// Create, Install, Update named Hook
Env env{*this, features};
env.fund(XRP(10000), alice, bob, charlie);
env.close();
// Create with hook name
auto jvh = hso(accept_wasm);
jvh[jss::HookName] = "41424344";
env(ripple::test::jtx::hook(alice, {{jvh}}, 0),
M("Create named hook"),
HSFEE);
env.close();
// Check hook definition
{
auto const hookDef =
env.le(keylet::hookDefinition(accept_hash));
BEAST_EXPECT(hookDef);
BEAST_EXPECT(!hookDef->isFieldPresent(sfHookName));
}
// Check Hook
{
auto const hooks = env.le(keylet::hook(alice));
BEAST_EXPECT(hooks && hooks->isFieldPresent(sfHooks));
auto const& hooksArray = hooks->getFieldArray(sfHooks);
BEAST_EXPECT(hooksArray.size() == 1);
BEAST_EXPECT(hooksArray[0].isFieldPresent(sfHookName));
BEAST_EXPECT(
strHex(hooksArray[0].getFieldVL(sfHookName)) == "41424344");
}
// install with hook name
jvh[jss::HookName] = "41424344";
env(ripple::test::jtx::hook(bob, {{jvh}}, 0),
M("Install named hook"),
HSFEE);
env.close();
// Check Hook
{
auto const hooks = env.le(keylet::hook(bob));
BEAST_EXPECT(hooks && hooks->isFieldPresent(sfHooks));
auto const& hooksArray = hooks->getFieldArray(sfHooks);
BEAST_EXPECT(hooksArray.size() == 1);
BEAST_EXPECT(hooksArray[0].isFieldPresent(sfHookName));
BEAST_EXPECT(
strHex(hooksArray[0].getFieldVL(sfHookName)) == "41424344");
}
// install without hook name
jvh.removeMember(jss::HookName);
env(ripple::test::jtx::hook(charlie, {{jvh}}, 0),
M("Install non-named hook"),
HSFEE);
env.close();
// Check Hook
{
auto const hooks = env.le(keylet::hook(charlie));
BEAST_EXPECT(hooks && hooks->isFieldPresent(sfHooks));
auto const& hooksArray = hooks->getFieldArray(sfHooks);
BEAST_EXPECT(hooksArray.size() == 1);
BEAST_EXPECT(!hooksArray[0].isFieldPresent(sfHookName));
}
// Update named hook to non-named hook
jvh[jss::HookName] = "";
jvh[jss::Flags] = hsfOVERRIDE;
env(ripple::test::jtx::hook(alice, {{jvh}}, 0),
M("Update named hook to non-named hook"),
HSFEE);
env.close();
// Check Hook
{
auto const hooks = env.le(keylet::hook(alice));
BEAST_EXPECT(hooks && hooks->isFieldPresent(sfHooks));
auto const& hooksArray = hooks->getFieldArray(sfHooks);
BEAST_EXPECT(hooksArray.size() == 1);
BEAST_EXPECT(!hooksArray[0].isFieldPresent(sfHookName));
}
// Update named hook to named hook
jvh[jss::HookName] = "4142434445";
jvh[jss::Flags] = hsfOVERRIDE;
env(ripple::test::jtx::hook(bob, {{jvh}}, 0),
M("Update non-named hook to named hook"),
HSFEE);
env.close();
// Check Hook
{
auto const hooks = env.le(keylet::hook(bob));
BEAST_EXPECT(hooks && hooks->isFieldPresent(sfHooks));
auto const& hooksArray = hooks->getFieldArray(sfHooks);
BEAST_EXPECT(hooksArray.size() == 1);
BEAST_EXPECT(hooksArray[0].isFieldPresent(sfHookName));
BEAST_EXPECT(
strHex(hooksArray[0].getFieldVL(sfHookName)) ==
"4142434445");
}
// Update non-named hook to named hook
jvh[jss::HookName] = "41424344";
jvh[jss::Flags] = hsfOVERRIDE;
env(ripple::test::jtx::hook(charlie, {{jvh}}, 0),
M("Update non-named hook to named hook"),
HSFEE);
env.close();
// Check Hook
{
auto const hooks = env.le(keylet::hook(charlie));
BEAST_EXPECT(hooks && hooks->isFieldPresent(sfHooks));
auto const& hooksArray = hooks->getFieldArray(sfHooks);
BEAST_EXPECT(hooksArray.size() == 1);
BEAST_EXPECT(hooksArray[0].isFieldPresent(sfHookName));
BEAST_EXPECT(
strHex(hooksArray[0].getFieldVL(sfHookName)) == "41424344");
}
}
// Install named hook
auto jvh = hso(accept_wasm);
jvh[jss::HookName] = "41424344";
jvh[jss::Flags] = hsfCOLLECT;
auto jvh2 = hso(accept2_wasm);
jvh2[jss::Flags] = hsfCOLLECT;
env(ripple::test::jtx::hook(alice, {{jvh, jvh2}}, 0),
M("Install named hook"),
HSFEE);
env.close();
//
// Test Strong
//
// Call named hook without specifying the hook name
{
auto jv = invoke::invoke(alice);
auto expectedFee =
calculateBaseFee(*env.current(), *env.jt(jv).stx);
BEAST_EXPECT(expectedFee == drops(19));
env(jv,
M("Call named hook without specifying the hook name"),
HSFEE);
env.close();
// execute only non-named hook
auto const hookExecutions =
env.meta()->getFieldArray(sfHookExecutions);
BEAST_EXPECT(hookExecutions.size() == 1);
BEAST_EXPECT(
hookExecutions[0].getFieldH256(sfHookHash) == accept2_hash);
}
// Call named hook with the wrong hook name
{
auto jv = invoke::invoke(alice);
jv[jss::HookName] = "41424345";
auto expectedFee =
calculateBaseFee(*env.current(), *env.jt(jv).stx);
BEAST_EXPECT(expectedFee == drops(19));
env(jv, M("Call named hook with the wrong hook name"), HSFEE);
env.close();
// execute only non-named hook
auto const hookExecutions =
env.meta()->getFieldArray(sfHookExecutions);
BEAST_EXPECT(hookExecutions.size() == 1);
BEAST_EXPECT(
hookExecutions[0].getFieldH256(sfHookHash) == accept2_hash);
}
// Call named hook with the correct hook name
{
auto jv = invoke::invoke(alice);
jv[jss::HookName] = "41424344";
auto expectedFee =
calculateBaseFee(*env.current(), *env.jt(jv).stx);
BEAST_EXPECT(expectedFee == drops(28));
env(jv, M("Call named hook with the correct hook name"), HSFEE);
env.close();
// execute both named and non-named hooks
auto const hookExecutions =
env.meta()->getFieldArray(sfHookExecutions);
BEAST_EXPECT(hookExecutions.size() == 2);
BEAST_EXPECT(
hookExecutions[0].getFieldH256(sfHookHash) == accept_hash);
BEAST_EXPECT(
hookExecutions[1].getFieldH256(sfHookHash) == accept2_hash);
}
//
// Test Weak
//
env(fset(alice, asfTshCollect), fee(XRP(1)));
env.close();
// Call named hook without specifying the hook name
{
auto jv = trust(bob, USD(1000));
env(jv,
M("Call named hook without specifying the hook name"),
HSFEE);
env.close();
// execute only non-named hook
auto const hookExecutions =
env.meta()->getFieldArray(sfHookExecutions);
BEAST_EXPECT(hookExecutions.size() == 1);
BEAST_EXPECT(
hookExecutions[0].getFieldH256(sfHookHash) == accept2_hash);
}
// Call named hook with the wrong hook name
{
auto jv = trust(bob, USD(1000));
jv[jss::HookName] = "41424345";
env(jv, M("Call named hook with the wrong hook name"), HSFEE);
env.close();
// execute only non-named hook
auto const hookExecutions =
env.meta()->getFieldArray(sfHookExecutions);
BEAST_EXPECT(hookExecutions.size() == 1);
BEAST_EXPECT(
hookExecutions[0].getFieldH256(sfHookHash) == accept2_hash);
}
// Call named hook with the correct hook name
{
auto jv = invoke::invoke(alice);
jv[jss::HookName] = "41424344";
env(jv, M("Call named hook with the correct hook name"), HSFEE);
env.close();
// execute both named and non-named hooks
auto const hookExecutions =
env.meta()->getFieldArray(sfHookExecutions);
BEAST_EXPECT(hookExecutions.size() == 2);
BEAST_EXPECT(
hookExecutions[0].getFieldH256(sfHookHash) == accept_hash);
BEAST_EXPECT(
hookExecutions[1].getFieldH256(sfHookHash) == accept2_hash);
}
env(fclear(alice, asfTshCollect), fee(XRP(1)));
env.close();
//
// Callback Execution
//
jvh = hso(emit_invoke_wasm);
jvh[jss::HookName] = "41424344";
jvh[jss::Flags] = hsfOVERRIDE;
env(ripple::test::jtx::hook(alice, {{jvh}}, 0),
M("Install named callback hook"),
HSFEE);
env.close();
// Call named hook without specifying the hook name
{
auto jv = invoke::invoke(alice);
env(jv,
M("Call named hook without specifying the hook name"),
HSFEE);
env.close();
// execute only non-named hook
BEAST_EXPECT(!env.meta()->isFieldPresent(sfHookEmissions));
}
// Call named hook with the wrong hook name
{
auto jv = invoke::invoke(alice);
jv[jss::HookName] = "41424345";
env(jv, M("Call named hook with the wrong hook name"), HSFEE);
env.close();
// execute only non-named hook
BEAST_EXPECT(!env.meta()->isFieldPresent(sfHookEmissions));
}
// Call named hook with the correct hook name
{
auto jv = invoke::invoke(alice);
jv[jss::HookName] = "41424344";
env(jv, M("Call named hook with the correct hook name"), HSFEE);
env.close();
// execute both named and non-named hooks
BEAST_EXPECT(env.meta()->isFieldPresent(sfHookEmissions));
auto const hookEmissions =
env.meta()->getFieldArray(sfHookEmissions)[0];
auto const etxn = hookEmissions.getFieldH256(sfEmittedTxnID);
env.close();
auto const tx = env.closed()->txRead(etxn);
BEAST_EXPECT(tx.first && tx.second);
BEAST_EXPECT(!tx.first->isFieldPresent(sfHookName));
// Callback transaction doesn't need to have hook name
BEAST_EXPECT(tx.second->isFieldPresent(sfHookExecutions));
}
}
void
testFillCopy(FeatureBitset features)
{
@@ -2089,8 +1741,6 @@ public:
bool const hasHookCanEmit =
env.current()->rules().enabled(featureHookCanEmit);
bool const hasNamedHooks =
env.current()->rules().enabled(featureNamedHooks);
auto const bob = Account{"bob"};
env.fund(XRP(10000), bob);
@@ -2139,8 +1789,6 @@ public:
iv[jss::HookCanEmit] =
"0000000000000000000000000000000000000000000000000000000000"
"000000";
if (hasNamedHooks)
iv[jss::HookName] = strHex(std::string{"DEADBEEF"});
jv[jss::Hooks][0U] = Json::Value{};
jv[jss::Hooks][0U][jss::Hook] = iv;
@@ -2163,8 +1811,6 @@ public:
iv[jss::HookCanEmit] =
"0000000000000000000000000000000000000000000000000000000000"
"000000";
if (hasNamedHooks)
iv[jss::HookName] = strHex(std::string{"DEADBEEF"});
jv[jss::Hooks][0U] = Json::Value{};
jv[jss::Hooks][0U][jss::Hook] = iv;
@@ -2188,8 +1834,6 @@ public:
iv[jss::HookCanEmit] =
"0000000000000000000000000000000000000000000000000000000000"
"000000";
if (hasNamedHooks)
iv[jss::HookName] = strHex(std::string{"DEADBEEF"});
jv[jss::Hooks][0U] = Json::Value{};
jv[jss::Hooks][0U][jss::Hook] = iv;
@@ -2210,8 +1854,6 @@ public:
iv[jss::HookCanEmit] =
"0000000000000000000000000000000000000000000000000000000000"
"000000";
if (hasNamedHooks)
iv[jss::HookName] = strHex(std::string{"DEADBEEF"});
jv[jss::Hooks][0U] = Json::Value{};
jv[jss::Hooks][0U][jss::Hook] = iv;
@@ -2360,8 +2002,6 @@ public:
bool const hasHookCanEmit =
env.current()->rules().enabled(featureHookCanEmit);
bool const hasNamedHooks =
env.current()->rules().enabled(featureNamedHooks);
auto const alice = Account{"alice"};
env.fund(XRP(10000), alice);
@@ -2388,8 +2028,6 @@ public:
iv[jss::HookCanEmit] =
"0000000000000000000000000000000000000000000000000000000000"
"000000";
if (hasNamedHooks)
iv[jss::HookName] = strHex(std::string{"DEADBEEF"});
iv[jss::HookParameters] = Json::Value{Json::arrayValue};
iv[jss::HookParameters][0U] = Json::Value{};
iv[jss::HookParameters][0U][jss::HookParameter] = Json::Value{};
@@ -2478,16 +2116,11 @@ public:
"CAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFE"
"CAFECAFE"},
{jss::HookParameters, params},
{jss::HookGrants, grants},
{jss::HookName, strHex(std::string{"DEADBEEF"})},
})
{jss::HookGrants, grants}})
{
if (!hasHookCanEmit && key == jss::HookCanEmit)
continue;
if (!hasNamedHooks && key == jss::HookName)
continue;
Json::Value iv;
iv[key] = value;
jv[jss::Hooks][0U] = Json::Value{};
@@ -2559,16 +2192,11 @@ public:
{jss::HookCanEmit,
"00000000000000000000000000000000000000000000000000000000"
"00000000"},
{jss::HookNamespace, to_string(uint256{beast::zero})},
{jss::HookName, strHex(std::string{"DEADBEEF"})},
})
{jss::HookNamespace, to_string(uint256{beast::zero})}})
{
if (key == jss::HookCanEmit && !hasHookCanEmit)
continue;
if (!hasNamedHooks && key == jss::HookName)
continue;
Json::Value iv;
iv[key] = value;
jv[jss::Hooks][0U] = Json::Value{};
@@ -2817,16 +2445,11 @@ public:
"CAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFE"
"CAFECAFE"},
{jss::HookParameters, params},
{jss::HookGrants, grants},
{jss::HookName, strHex(std::string{"DEADBEEF"})},
})
{jss::HookGrants, grants}})
{
if (key == jss::HookCanEmit && !hasHookCanEmit)
continue;
if (!hasNamedHooks && key == jss::HookName)
continue;
Json::Value iv;
iv[key] = value;
jv[jss::Hooks][0U] = Json::Value{};
@@ -13499,7 +13122,7 @@ public:
0,0
)));
ASSERT(INVALID_ARGUMENT == (e=util_keylet(buf, 34, KEYLET_AMM,
ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_AMM,
SBUF(asset1), SBUF(asset2),
0,0
)));
@@ -15098,7 +14721,6 @@ public:
testPageCap(features);
testHookOnV2(features);
testHookName(features);
testFillCopy(features);
@@ -15202,7 +14824,7 @@ public:
using namespace test::jtx;
static FeatureBitset const all{supported_amendments()};
static std::array<FeatureBitset, 8> const feats{
static std::array<FeatureBitset, 7> const feats{
all,
all - fixXahauV2,
all - fixXahauV1 - fixXahauV2,
@@ -15212,7 +14834,6 @@ public:
featureHookCanEmit,
all - fixXahauV1 - fixXahauV2 - fixNSDelete - fixPageCap -
featureExtendedHookState,
all - featureNamedHooks,
};
if (BEAST_EXPECT(instance < feats.size()))
@@ -15364,98 +14985,6 @@ private:
)[test.hook]"];
HASH_WASM(accept2);
// This hook is used to test Callback
TestHook emit_invoke_wasm = // WASM: 7
wasm[
R"[test.hook](
#include <stdint.h>
extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t emit (uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
extern int64_t hook_account(uint32_t write_ptr, uint32_t write_len);
extern int64_t etxn_reserve(uint32_t);
extern int64_t etxn_fee_base (uint32_t read_ptr, uint32_t read_len);
extern int64_t etxn_details (uint32_t write_ptr, uint32_t write_len);
extern int64_t ledger_seq (void);
#define SBUF(x) (uint32_t)x,sizeof(x)
// clang-format off
uint8_t txn[229] =
{
/* size, upto, field name */
/* 3, 0, tt = Invoke */ 0x12U, 0x00U, 0x63U,
/* 5, 3, flags */ 0x22U, 0x00U, 0x00U, 0x00U, 0x00U,
/* 5, 8, sequence */ 0x24U, 0x00U, 0x00U, 0x00U, 0x00U,
/* 6, 13, firstledgersequence */ 0x20U, 0x1AU, 0x00U, 0x00U, 0x00U, 0x00U,
/* 6, 19, lastledgersequence */ 0x20U, 0x1BU, 0x00U, 0x00U, 0x00U, 0x00U,
/* 9, 25, fee */ 0x68U, 0x40U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
/* 35, 34, signingpubkey */ 0x73U, 0x21U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
/* 22, 69, account */ 0x81U, 0x14U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
/* 116, 91, emit details */
/* 0, 229, */
};
// clang-format on
// TX BUILDER
#define FLAGS_OUT (txn + 4U)
#define FLS_OUT (txn + 15U)
#define LLS_OUT (txn + 21U)
#define FEE_OUT (txn + 26U)
#define ACCOUNT_OUT (txn + 71U)
#define EMIT_OUT (txn + 91U)
#define FLIP_ENDIAN_32(value) \
(uint32_t)(((value & 0xFFU) << 24) | ((value & 0xFF00U) << 8) | \
((value & 0xFF0000U) >> 8) | ((value & 0xFF000000U) >> 24))
#define SET_UINT32(ptr, value) *((uint32_t *)(ptr)) = FLIP_ENDIAN_32(value);
#define SET_NATIVE_AMOUNT(ptr, amount) \
do { \
uint8_t *b = (ptr); \
*b++ = 0b01000000 + ((amount >> 56) & 0b00111111); \
*b++ = (amount >> 48) & 0xFFU; \
*b++ = (amount >> 40) & 0xFFU; \
*b++ = (amount >> 32) & 0xFFU; \
*b++ = (amount >> 24) & 0xFFU; \
*b++ = (amount >> 16) & 0xFFU; \
*b++ = (amount >> 8) & 0xFFU; \
*b++ = (amount >> 0) & 0xFFU; \
} while (0)
#define PREPARE_TXN() \
do { \
etxn_reserve(1); \
uint32_t fls = (uint32_t)ledger_seq() + 1; \
SET_UINT32(FLS_OUT, fls); \
SET_UINT32(LLS_OUT, fls + 4); \
hook_account(ACCOUNT_OUT, 20); \
etxn_details(EMIT_OUT, 138U); \
int64_t fee = etxn_fee_base(SBUF(txn)); \
SET_NATIVE_AMOUNT(FEE_OUT, fee); \
} while (0)
int64_t cbak(uint32_t r)
{
_g(1,1);
return accept(0,0,0);
}
int64_t hook(uint32_t reserved)
{
_g(1,1);
PREPARE_TXN();
uint8_t emithash[32];
int64_t emit_result = emit(SBUF(emithash), SBUF(txn));
if (emit_result < 0)
return rollback(SBUF("Emit failed."), __LINE__);
return accept(SBUF("Emit succeeded."), __LINE__);
}
)[test.hook]"];
HASH_WASM(emit_invoke);
};
#define SETHOOK_TEST(i, last) \
@@ -15473,8 +15002,7 @@ SETHOOK_TEST(2, false)
SETHOOK_TEST(3, false)
SETHOOK_TEST(4, false)
SETHOOK_TEST(5, false)
SETHOOK_TEST(6, false)
SETHOOK_TEST(7, true)
SETHOOK_TEST(6, true)
BEAST_DEFINE_TESTSUITE_PRIO(SetHook0, app, ripple, 2);
BEAST_DEFINE_TESTSUITE_PRIO(SetHook1, app, ripple, 2);
@@ -15483,7 +15011,6 @@ BEAST_DEFINE_TESTSUITE_PRIO(SetHook3, app, ripple, 2);
BEAST_DEFINE_TESTSUITE_PRIO(SetHook4, app, ripple, 2);
BEAST_DEFINE_TESTSUITE_PRIO(SetHook5, app, ripple, 2);
BEAST_DEFINE_TESTSUITE_PRIO(SetHook6, app, ripple, 2);
BEAST_DEFINE_TESTSUITE_PRIO(SetHook7, app, ripple, 2);
} // namespace test
} // namespace ripple
#undef M
+100 -289
View File
@@ -20453,7 +20453,7 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
0,0
)));
ASSERT(INVALID_ARGUMENT == (e=util_keylet(buf, 34, KEYLET_AMM,
ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_AMM,
SBUF(asset1), SBUF(asset2),
0,0
)));
@@ -20534,9 +20534,9 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
0x6CU, 0x62U, 0x61U, 0x63U, 0x6BU, 0x00U, 0x02U, 0x03U, 0x65U, 0x6EU,
0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x02U,
0x03U, 0x02U, 0x01U, 0x03U, 0x05U, 0x03U, 0x01U, 0x00U, 0x02U, 0x06U,
0x51U, 0x0DU, 0x7FU, 0x01U, 0x41U, 0xC0U, 0xA6U, 0x04U, 0x0BU, 0x7FU,
0x00U, 0x41U, 0xBEU, 0x26U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U,
0x0BU, 0x7FU, 0x00U, 0x41U, 0xC0U, 0xA6U, 0x04U, 0x0BU, 0x7FU, 0x00U,
0x51U, 0x0DU, 0x7FU, 0x01U, 0x41U, 0xB0U, 0xA6U, 0x04U, 0x0BU, 0x7FU,
0x00U, 0x41U, 0xB0U, 0x26U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U,
0x0BU, 0x7FU, 0x00U, 0x41U, 0xB0U, 0xA6U, 0x04U, 0x0BU, 0x7FU, 0x00U,
0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0xF0U, 0x08U, 0x0BU,
0x7FU, 0x00U, 0x41U, 0x90U, 0x09U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0xE4U,
0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U,
@@ -21016,64 +21016,64 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
0x1AU, 0x0BU, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0x22U, 0x41U, 0x1BU,
0x41U, 0xB0U, 0x89U, 0x80U, 0x80U, 0x00U, 0x41U, 0x28U, 0x41U, 0xE0U,
0x89U, 0x80U, 0x80U, 0x00U, 0x41U, 0x28U, 0x41U, 0x00U, 0x41U, 0x00U,
0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x79U, 0x51U, 0x0DU,
0x00U, 0x41U, 0x89U, 0x9FU, 0x80U, 0x80U, 0x00U, 0x41U, 0xDBU, 0x00U,
0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x22U, 0x51U, 0x0DU,
0x00U, 0x41U, 0x89U, 0x9FU, 0x80U, 0x80U, 0x00U, 0x41U, 0xCDU, 0x00U,
0x42U, 0xD0U, 0x04U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU,
0x0BU, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0x22U, 0x41U, 0x1CU, 0x41U,
0xF0U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x14U, 0x41U, 0x90U, 0x89U,
0x80U, 0x80U, 0x00U, 0x41U, 0x14U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U,
0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x79U, 0x51U, 0x0DU, 0x00U,
0x41U, 0xE4U, 0x9FU, 0x80U, 0x80U, 0x00U, 0x41U, 0xD4U, 0x00U, 0x42U,
0x41U, 0xD6U, 0x9FU, 0x80U, 0x80U, 0x00U, 0x41U, 0xD4U, 0x00U, 0x42U,
0xD5U, 0x04U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU,
0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0x22U, 0x41U, 0x1DU, 0x41U, 0xF0U,
0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x14U, 0x41U, 0x90U, 0x89U, 0x80U,
0x80U, 0x00U, 0x41U, 0x14U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x81U,
0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x79U, 0x51U, 0x0DU, 0x00U, 0x41U,
0xB8U, 0xA0U, 0x80U, 0x80U, 0x00U, 0x41U, 0xE3U, 0x00U, 0x42U, 0xDAU,
0xAAU, 0xA0U, 0x80U, 0x80U, 0x00U, 0x41U, 0xE3U, 0x00U, 0x42U, 0xDAU,
0x04U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U,
0x40U, 0x20U, 0x01U, 0x41U, 0x22U, 0x41U, 0x1EU, 0x41U, 0xF0U, 0x88U,
0x80U, 0x80U, 0x00U, 0x41U, 0x14U, 0x41U, 0x90U, 0x89U, 0x80U, 0x80U,
0x00U, 0x41U, 0x14U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x81U, 0x80U,
0x80U, 0x80U, 0x00U, 0x42U, 0x79U, 0x51U, 0x0DU, 0x00U, 0x41U, 0x9BU,
0x80U, 0x80U, 0x00U, 0x42U, 0x79U, 0x51U, 0x0DU, 0x00U, 0x41U, 0x8DU,
0xA1U, 0x80U, 0x80U, 0x00U, 0x41U, 0xF2U, 0x00U, 0x42U, 0xDFU, 0x04U,
0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U,
0x20U, 0x01U, 0x41U, 0x22U, 0x41U, 0x1FU, 0x41U, 0xF0U, 0x88U, 0x80U,
0x80U, 0x00U, 0x41U, 0x14U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U,
0x41U, 0x00U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x79U,
0x51U, 0x0DU, 0x00U, 0x41U, 0x8DU, 0xA2U, 0x80U, 0x80U, 0x00U, 0x41U,
0x51U, 0x0DU, 0x00U, 0x41U, 0xFFU, 0xA1U, 0x80U, 0x80U, 0x00U, 0x41U,
0xCDU, 0x00U, 0x42U, 0xE5U, 0x04U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U,
0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0x22U, 0x41U,
0x20U, 0x41U, 0xF0U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x14U, 0x41U,
0x03U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x81U, 0x80U,
0x80U, 0x80U, 0x00U, 0x42U, 0x22U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xDAU,
0x80U, 0x80U, 0x00U, 0x42U, 0x22U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xCCU,
0xA2U, 0x80U, 0x80U, 0x00U, 0x41U, 0xC3U, 0x00U, 0x42U, 0xEBU, 0x04U,
0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U,
0x20U, 0x01U, 0x41U, 0x22U, 0x41U, 0x21U, 0x41U, 0xF0U, 0x88U, 0x80U,
0x80U, 0x00U, 0x41U, 0x14U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U,
0x41U, 0x00U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x79U,
0x51U, 0x0DU, 0x00U, 0x41U, 0x9DU, 0xA3U, 0x80U, 0x80U, 0x00U, 0x41U,
0x51U, 0x0DU, 0x00U, 0x41U, 0x8FU, 0xA3U, 0x80U, 0x80U, 0x00U, 0x41U,
0xDAU, 0x00U, 0x42U, 0xF1U, 0x04U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U,
0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0x22U, 0x41U,
0x22U, 0x41U, 0xF0U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x14U, 0x41U,
0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x81U, 0x80U,
0x80U, 0x80U, 0x00U, 0x42U, 0x79U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xF7U,
0x80U, 0x80U, 0x00U, 0x42U, 0x79U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xE9U,
0xA3U, 0x80U, 0x80U, 0x00U, 0x41U, 0xD1U, 0x00U, 0x42U, 0xF7U, 0x04U,
0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U,
0x20U, 0x01U, 0x41U, 0x22U, 0x41U, 0x23U, 0x41U, 0xF0U, 0x88U, 0x80U,
0x80U, 0x00U, 0x41U, 0x14U, 0x41U, 0x90U, 0x89U, 0x80U, 0x80U, 0x00U,
0x41U, 0x14U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x81U, 0x80U, 0x80U,
0x80U, 0x00U, 0x42U, 0x79U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xC8U, 0xA4U,
0x80U, 0x00U, 0x42U, 0x79U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xBAU, 0xA4U,
0x80U, 0x80U, 0x00U, 0x41U, 0xD8U, 0x00U, 0x42U, 0xFCU, 0x04U, 0x10U,
0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x20U,
0x01U, 0x41U, 0x22U, 0x41U, 0x24U, 0x41U, 0xF0U, 0x88U, 0x80U, 0x80U,
0x00U, 0x41U, 0x14U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U,
0x00U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x79U, 0x51U,
0x0DU, 0x00U, 0x41U, 0xA0U, 0xA5U, 0x80U, 0x80U, 0x00U, 0x41U, 0xDDU,
0x0DU, 0x00U, 0x41U, 0x92U, 0xA5U, 0x80U, 0x80U, 0x00U, 0x41U, 0xDDU,
0x00U, 0x42U, 0x82U, 0x05U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U,
0x1AU, 0x0BU, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0x22U, 0x41U, 0x1AU,
0x41U, 0xF0U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x14U, 0x41U, 0x01U,
0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x81U, 0x80U, 0x80U,
0x80U, 0x00U, 0x42U, 0x22U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xFDU, 0xA5U,
0x80U, 0x00U, 0x42U, 0x22U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xEFU, 0xA5U,
0x80U, 0x80U, 0x00U, 0x41U, 0xC1U, 0x00U, 0x42U, 0x84U, 0x05U, 0x10U,
0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x02U,
0x40U, 0x20U, 0x01U, 0x29U, 0x03U, 0x00U, 0x42U, 0x80U, 0x82U, 0xDDU,
@@ -21088,7 +21088,7 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U,
0x42U, 0x00U, 0x10U, 0x83U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U,
0x01U, 0x41U, 0x30U, 0x6AU, 0x24U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U,
0x20U, 0x02U, 0x0BU, 0x0BU, 0xCDU, 0x1EU, 0x02U, 0x00U, 0x41U, 0x80U,
0x20U, 0x02U, 0x0BU, 0x0BU, 0xBFU, 0x1EU, 0x02U, 0x00U, 0x41U, 0x80U,
0x08U, 0x0BU, 0x88U, 0x02U, 0xC5U, 0xD0U, 0xF3U, 0x4BU, 0x0AU, 0x19U,
0x05U, 0xBCU, 0x3BU, 0x29U, 0xAAU, 0x1BU, 0xE1U, 0x39U, 0xFEU, 0x04U,
0xD6U, 0x0CU, 0x86U, 0x94U, 0xD3U, 0x95U, 0x0AU, 0x8DU, 0x80U, 0x25U,
@@ -21116,7 +21116,7 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
0x45U, 0x48U, 0x52U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x3AU, 0x51U,
0x8AU, 0x22U, 0x53U, 0x81U, 0x60U, 0x84U, 0x1CU, 0x14U, 0x32U, 0xFEU,
0x6FU, 0x3EU, 0x6DU, 0x6EU, 0x76U, 0x29U, 0xFBU, 0xBAU, 0x00U, 0x41U,
0x88U, 0x0AU, 0x0BU, 0xB6U, 0x1CU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU,
0x88U, 0x0AU, 0x0BU, 0xA8U, 0x1CU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU,
0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U, 0x31U, 0x30U, 0x30U,
0x30U, 0x30U, 0x30U, 0x30U, 0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU, 0x20U,
0x4BU, 0x45U, 0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x53U, 0x4BU, 0x49U,
@@ -21385,83 +21385,20 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
0x54U, 0x45U, 0x5FU, 0x44U, 0x49U, 0x52U, 0x2CU, 0x20U, 0x53U, 0x42U,
0x55U, 0x46U, 0x28U, 0x61U, 0x29U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U,
0x46U, 0x28U, 0x6EU, 0x73U, 0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U,
0x20U, 0x29U, 0x29U, 0x00U, 0x49U, 0x4EU, 0x56U, 0x41U, 0x4CU, 0x49U,
0x44U, 0x5FU, 0x41U, 0x52U, 0x47U, 0x55U, 0x4DU, 0x45U, 0x4EU, 0x54U,
0x20U, 0x3DU, 0x3DU, 0x20U, 0x28U, 0x65U, 0x3DU, 0x75U, 0x74U, 0x69U,
0x6CU, 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U, 0x62U,
0x75U, 0x66U, 0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU, 0x45U,
0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x41U, 0x4DU, 0x4DU, 0x2CU, 0x20U,
0x20U, 0x29U, 0x29U, 0x00U, 0x33U, 0x34U, 0x20U, 0x3DU, 0x3DU, 0x20U,
0x28U, 0x65U, 0x3DU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x6BU, 0x65U,
0x79U, 0x6CU, 0x65U, 0x74U, 0x28U, 0x62U, 0x75U, 0x66U, 0x2CU, 0x20U,
0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU, 0x45U, 0x59U, 0x4CU, 0x45U, 0x54U,
0x5FU, 0x41U, 0x4DU, 0x4DU, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U,
0x28U, 0x61U, 0x73U, 0x73U, 0x65U, 0x74U, 0x31U, 0x29U, 0x2CU, 0x20U,
0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x61U, 0x73U, 0x73U, 0x65U, 0x74U,
0x31U, 0x29U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x61U,
0x73U, 0x73U, 0x65U, 0x74U, 0x32U, 0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU,
0x30U, 0x20U, 0x29U, 0x29U, 0x00U, 0x49U, 0x4EU, 0x56U, 0x41U, 0x4CU,
0x49U, 0x44U, 0x5FU, 0x41U, 0x52U, 0x47U, 0x55U, 0x4DU, 0x45U, 0x4EU,
0x54U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x28U, 0x65U, 0x3DU, 0x75U, 0x74U,
0x69U, 0x6CU, 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U,
0x62U, 0x75U, 0x66U, 0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU,
0x45U, 0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x42U, 0x52U, 0x49U, 0x44U,
0x47U, 0x45U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x61U,
0x29U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x62U, 0x29U,
0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x20U, 0x29U, 0x29U, 0x00U, 0x49U,
0x4EU, 0x56U, 0x41U, 0x4CU, 0x49U, 0x44U, 0x5FU, 0x41U, 0x52U, 0x47U,
0x55U, 0x4DU, 0x45U, 0x4EU, 0x54U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x28U,
0x65U, 0x3DU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x6BU, 0x65U, 0x79U,
0x6CU, 0x65U, 0x74U, 0x28U, 0x62U, 0x75U, 0x66U, 0x2CU, 0x20U, 0x33U,
0x34U, 0x2CU, 0x20U, 0x4BU, 0x45U, 0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU,
0x58U, 0x43U, 0x48U, 0x41U, 0x49U, 0x4EU, 0x5FU, 0x4FU, 0x57U, 0x4EU,
0x45U, 0x44U, 0x5FU, 0x43U, 0x4CU, 0x41U, 0x49U, 0x4DU, 0x5FU, 0x49U,
0x44U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x61U, 0x29U,
0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x62U, 0x29U, 0x2CU,
0x20U, 0x30U, 0x2CU, 0x30U, 0x20U, 0x29U, 0x29U, 0x00U, 0x49U, 0x4EU,
0x56U, 0x41U, 0x4CU, 0x49U, 0x44U, 0x5FU, 0x41U, 0x52U, 0x47U, 0x55U,
0x4DU, 0x45U, 0x4EU, 0x54U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x28U, 0x65U,
0x3DU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU,
0x65U, 0x74U, 0x28U, 0x62U, 0x75U, 0x66U, 0x2CU, 0x20U, 0x33U, 0x34U,
0x2CU, 0x20U, 0x4BU, 0x45U, 0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x58U,
0x43U, 0x48U, 0x41U, 0x49U, 0x4EU, 0x5FU, 0x4FU, 0x57U, 0x4EU, 0x45U,
0x44U, 0x5FU, 0x43U, 0x52U, 0x45U, 0x41U, 0x54U, 0x45U, 0x5FU, 0x41U,
0x43U, 0x43U, 0x4FU, 0x55U, 0x4EU, 0x54U, 0x5FU, 0x43U, 0x4CU, 0x41U,
0x49U, 0x4DU, 0x5FU, 0x49U, 0x44U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U,
0x46U, 0x28U, 0x61U, 0x29U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U,
0x28U, 0x62U, 0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x20U, 0x29U,
0x29U, 0x00U, 0x49U, 0x4EU, 0x56U, 0x41U, 0x4CU, 0x49U, 0x44U, 0x5FU,
0x41U, 0x52U, 0x47U, 0x55U, 0x4DU, 0x45U, 0x4EU, 0x54U, 0x20U, 0x3DU,
0x3DU, 0x20U, 0x28U, 0x65U, 0x3DU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU,
0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U, 0x62U, 0x75U, 0x66U,
0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU, 0x45U, 0x59U, 0x4CU,
0x45U, 0x54U, 0x5FU, 0x44U, 0x49U, 0x44U, 0x2CU, 0x20U, 0x53U, 0x42U,
0x55U, 0x46U, 0x28U, 0x61U, 0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U,
0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x20U, 0x29U, 0x29U, 0x00U, 0x33U,
0x34U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x28U, 0x65U, 0x3DU, 0x75U, 0x74U,
0x69U, 0x6CU, 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U,
0x62U, 0x75U, 0x66U, 0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU,
0x45U, 0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x4FU, 0x52U, 0x41U, 0x43U,
0x4CU, 0x45U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x61U,
0x29U, 0x2CU, 0x20U, 0x33U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x20U, 0x30U,
0x2CU, 0x30U, 0x20U, 0x29U, 0x29U, 0x00U, 0x49U, 0x4EU, 0x56U, 0x41U,
0x4CU, 0x49U, 0x44U, 0x5FU, 0x41U, 0x52U, 0x47U, 0x55U, 0x4DU, 0x45U,
0x4EU, 0x54U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x28U, 0x65U, 0x3DU, 0x75U,
0x74U, 0x69U, 0x6CU, 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U,
0x28U, 0x62U, 0x75U, 0x66U, 0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU, 0x20U,
0x4BU, 0x45U, 0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x4DU, 0x50U, 0x54U,
0x4FU, 0x4BU, 0x45U, 0x4EU, 0x5FU, 0x49U, 0x53U, 0x53U, 0x55U, 0x41U,
0x4EU, 0x43U, 0x45U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U,
0x61U, 0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x2CU, 0x20U, 0x30U,
0x2CU, 0x30U, 0x20U, 0x29U, 0x29U, 0x00U, 0x49U, 0x4EU, 0x56U, 0x41U,
0x4CU, 0x49U, 0x44U, 0x5FU, 0x41U, 0x52U, 0x47U, 0x55U, 0x4DU, 0x45U,
0x4EU, 0x54U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x28U, 0x65U, 0x3DU, 0x75U,
0x74U, 0x69U, 0x6CU, 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U,
0x28U, 0x62U, 0x75U, 0x66U, 0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU, 0x20U,
0x4BU, 0x45U, 0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x4DU, 0x50U, 0x54U,
0x4FU, 0x4BU, 0x45U, 0x4EU, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U,
0x28U, 0x61U, 0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x2CU, 0x20U,
0x30U, 0x2CU, 0x30U, 0x20U, 0x29U, 0x29U, 0x00U, 0x49U, 0x4EU, 0x56U,
0x41U, 0x4CU, 0x49U, 0x44U, 0x5FU, 0x41U, 0x52U, 0x47U, 0x55U, 0x4DU,
0x45U, 0x4EU, 0x54U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x28U, 0x65U, 0x3DU,
0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U,
0x74U, 0x28U, 0x62U, 0x75U, 0x66U, 0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU,
0x20U, 0x4BU, 0x45U, 0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x43U, 0x52U,
0x45U, 0x44U, 0x45U, 0x4EU, 0x54U, 0x49U, 0x41U, 0x4CU, 0x2CU, 0x20U,
0x32U, 0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x20U, 0x29U, 0x29U,
0x00U, 0x49U, 0x4EU, 0x56U, 0x41U, 0x4CU, 0x49U, 0x44U, 0x5FU, 0x41U,
0x52U, 0x47U, 0x55U, 0x4DU, 0x45U, 0x4EU, 0x54U, 0x20U, 0x3DU, 0x3DU,
0x20U, 0x28U, 0x65U, 0x3DU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x6BU,
0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U, 0x62U, 0x75U, 0x66U, 0x2CU,
0x20U, 0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU, 0x45U, 0x59U, 0x4CU, 0x45U,
0x54U, 0x5FU, 0x42U, 0x52U, 0x49U, 0x44U, 0x47U, 0x45U, 0x2CU, 0x20U,
0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x61U, 0x29U, 0x2CU, 0x20U, 0x53U,
0x42U, 0x55U, 0x46U, 0x28U, 0x62U, 0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU,
0x30U, 0x20U, 0x29U, 0x29U, 0x00U, 0x49U, 0x4EU, 0x56U, 0x41U, 0x4CU,
@@ -21469,18 +21406,79 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
0x54U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x28U, 0x65U, 0x3DU, 0x75U, 0x74U,
0x69U, 0x6CU, 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U,
0x62U, 0x75U, 0x66U, 0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU,
0x45U, 0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x50U, 0x45U, 0x52U, 0x4DU,
0x49U, 0x53U, 0x53U, 0x49U, 0x4FU, 0x4EU, 0x45U, 0x44U, 0x5FU, 0x44U,
0x4FU, 0x4DU, 0x41U, 0x49U, 0x4EU, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U,
0x46U, 0x28U, 0x61U, 0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x2CU,
0x20U, 0x30U, 0x2CU, 0x30U, 0x20U, 0x29U, 0x29U, 0x00U, 0x33U, 0x34U,
0x45U, 0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x58U, 0x43U, 0x48U, 0x41U,
0x49U, 0x4EU, 0x5FU, 0x4FU, 0x57U, 0x4EU, 0x45U, 0x44U, 0x5FU, 0x43U,
0x4CU, 0x41U, 0x49U, 0x4DU, 0x5FU, 0x49U, 0x44U, 0x2CU, 0x20U, 0x53U,
0x42U, 0x55U, 0x46U, 0x28U, 0x61U, 0x29U, 0x2CU, 0x20U, 0x53U, 0x42U,
0x55U, 0x46U, 0x28U, 0x62U, 0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U,
0x20U, 0x29U, 0x29U, 0x00U, 0x49U, 0x4EU, 0x56U, 0x41U, 0x4CU, 0x49U,
0x44U, 0x5FU, 0x41U, 0x52U, 0x47U, 0x55U, 0x4DU, 0x45U, 0x4EU, 0x54U,
0x20U, 0x3DU, 0x3DU, 0x20U, 0x28U, 0x65U, 0x3DU, 0x75U, 0x74U, 0x69U,
0x6CU, 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U, 0x62U,
0x75U, 0x66U, 0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU, 0x45U,
0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x43U, 0x52U, 0x4FU, 0x4EU, 0x2CU,
0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x58U, 0x43U, 0x48U, 0x41U, 0x49U,
0x4EU, 0x5FU, 0x4FU, 0x57U, 0x4EU, 0x45U, 0x44U, 0x5FU, 0x43U, 0x52U,
0x45U, 0x41U, 0x54U, 0x45U, 0x5FU, 0x41U, 0x43U, 0x43U, 0x4FU, 0x55U,
0x4EU, 0x54U, 0x5FU, 0x43U, 0x4CU, 0x41U, 0x49U, 0x4DU, 0x5FU, 0x49U,
0x44U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x61U, 0x29U,
0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x62U, 0x29U, 0x2CU,
0x20U, 0x30U, 0x2CU, 0x30U, 0x20U, 0x29U, 0x29U, 0x00U, 0x49U, 0x4EU,
0x56U, 0x41U, 0x4CU, 0x49U, 0x44U, 0x5FU, 0x41U, 0x52U, 0x47U, 0x55U,
0x4DU, 0x45U, 0x4EU, 0x54U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x28U, 0x65U,
0x3DU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU,
0x65U, 0x74U, 0x28U, 0x62U, 0x75U, 0x66U, 0x2CU, 0x20U, 0x33U, 0x34U,
0x2CU, 0x20U, 0x4BU, 0x45U, 0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x44U,
0x49U, 0x44U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x61U,
0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x2CU, 0x20U, 0x30U, 0x2CU,
0x30U, 0x20U, 0x29U, 0x29U, 0x00U, 0x33U, 0x34U, 0x20U, 0x3DU, 0x3DU,
0x20U, 0x28U, 0x65U, 0x3DU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x6BU,
0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U, 0x62U, 0x75U, 0x66U, 0x2CU,
0x20U, 0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU, 0x45U, 0x59U, 0x4CU, 0x45U,
0x54U, 0x5FU, 0x4FU, 0x52U, 0x41U, 0x43U, 0x4CU, 0x45U, 0x2CU, 0x20U,
0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x61U, 0x29U, 0x2CU, 0x20U, 0x33U,
0x2CU, 0x20U, 0x30U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x20U, 0x29U,
0x29U, 0x00U, 0x49U, 0x4EU, 0x56U, 0x41U, 0x4CU, 0x49U, 0x44U, 0x5FU,
0x41U, 0x52U, 0x47U, 0x55U, 0x4DU, 0x45U, 0x4EU, 0x54U, 0x20U, 0x3DU,
0x3DU, 0x20U, 0x28U, 0x65U, 0x3DU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU,
0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U, 0x62U, 0x75U, 0x66U,
0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU, 0x45U, 0x59U, 0x4CU,
0x45U, 0x54U, 0x5FU, 0x4DU, 0x50U, 0x54U, 0x4FU, 0x4BU, 0x45U, 0x4EU,
0x5FU, 0x49U, 0x53U, 0x53U, 0x55U, 0x41U, 0x4EU, 0x43U, 0x45U, 0x2CU,
0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x61U, 0x29U, 0x2CU, 0x20U,
0x31U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x20U, 0x30U,
0x29U, 0x29U, 0x00U,
0x30U, 0x2CU, 0x30U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x20U, 0x29U,
0x29U, 0x00U, 0x49U, 0x4EU, 0x56U, 0x41U, 0x4CU, 0x49U, 0x44U, 0x5FU,
0x41U, 0x52U, 0x47U, 0x55U, 0x4DU, 0x45U, 0x4EU, 0x54U, 0x20U, 0x3DU,
0x3DU, 0x20U, 0x28U, 0x65U, 0x3DU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU,
0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U, 0x62U, 0x75U, 0x66U,
0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU, 0x45U, 0x59U, 0x4CU,
0x45U, 0x54U, 0x5FU, 0x4DU, 0x50U, 0x54U, 0x4FU, 0x4BU, 0x45U, 0x4EU,
0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x61U, 0x29U, 0x2CU,
0x20U, 0x30U, 0x2CU, 0x30U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x20U,
0x29U, 0x29U, 0x00U, 0x49U, 0x4EU, 0x56U, 0x41U, 0x4CU, 0x49U, 0x44U,
0x5FU, 0x41U, 0x52U, 0x47U, 0x55U, 0x4DU, 0x45U, 0x4EU, 0x54U, 0x20U,
0x3DU, 0x3DU, 0x20U, 0x28U, 0x65U, 0x3DU, 0x75U, 0x74U, 0x69U, 0x6CU,
0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U, 0x62U, 0x75U,
0x66U, 0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU, 0x45U, 0x59U,
0x4CU, 0x45U, 0x54U, 0x5FU, 0x43U, 0x52U, 0x45U, 0x44U, 0x45U, 0x4EU,
0x54U, 0x49U, 0x41U, 0x4CU, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U,
0x28U, 0x61U, 0x29U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U,
0x62U, 0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x20U, 0x29U, 0x29U,
0x00U, 0x49U, 0x4EU, 0x56U, 0x41U, 0x4CU, 0x49U, 0x44U, 0x5FU, 0x41U,
0x52U, 0x47U, 0x55U, 0x4DU, 0x45U, 0x4EU, 0x54U, 0x20U, 0x3DU, 0x3DU,
0x20U, 0x28U, 0x65U, 0x3DU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x6BU,
0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U, 0x62U, 0x75U, 0x66U, 0x2CU,
0x20U, 0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU, 0x45U, 0x59U, 0x4CU, 0x45U,
0x54U, 0x5FU, 0x50U, 0x45U, 0x52U, 0x4DU, 0x49U, 0x53U, 0x53U, 0x49U,
0x4FU, 0x4EU, 0x45U, 0x44U, 0x5FU, 0x44U, 0x4FU, 0x4DU, 0x41U, 0x49U,
0x4EU, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x61U, 0x29U,
0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U,
0x20U, 0x29U, 0x29U, 0x00U, 0x33U, 0x34U, 0x20U, 0x3DU, 0x3DU, 0x20U,
0x28U, 0x65U, 0x3DU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x6BU, 0x65U,
0x79U, 0x6CU, 0x65U, 0x74U, 0x28U, 0x62U, 0x75U, 0x66U, 0x2CU, 0x20U,
0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU, 0x45U, 0x59U, 0x4CU, 0x45U, 0x54U,
0x5FU, 0x43U, 0x52U, 0x4FU, 0x4EU, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U,
0x46U, 0x28U, 0x61U, 0x29U, 0x2CU, 0x20U, 0x31U, 0x2CU, 0x20U, 0x30U,
0x2CU, 0x20U, 0x30U, 0x2CU, 0x20U, 0x30U, 0x29U, 0x29U, 0x00U,
}},
/* ==== WASM: 89 ==== */
@@ -34112,193 +34110,6 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
0x0BU,
}},
/* ==== WASM: 100 ==== */
{R"[test.hook](
#include <stdint.h>
extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t emit (uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
extern int64_t hook_account(uint32_t write_ptr, uint32_t write_len);
extern int64_t etxn_reserve(uint32_t);
extern int64_t etxn_fee_base (uint32_t read_ptr, uint32_t read_len);
extern int64_t etxn_details (uint32_t write_ptr, uint32_t write_len);
extern int64_t ledger_seq (void);
#define SBUF(x) (uint32_t)x,sizeof(x)
// clang-format off
uint8_t txn[229] =
{
/* size, upto, field name */
/* 3, 0, tt = Invoke */ 0x12U, 0x00U, 0x63U,
/* 5, 3, flags */ 0x22U, 0x00U, 0x00U, 0x00U, 0x00U,
/* 5, 8, sequence */ 0x24U, 0x00U, 0x00U, 0x00U, 0x00U,
/* 6, 13, firstledgersequence */ 0x20U, 0x1AU, 0x00U, 0x00U, 0x00U, 0x00U,
/* 6, 19, lastledgersequence */ 0x20U, 0x1BU, 0x00U, 0x00U, 0x00U, 0x00U,
/* 9, 25, fee */ 0x68U, 0x40U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
/* 35, 34, signingpubkey */ 0x73U, 0x21U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
/* 22, 69, account */ 0x81U, 0x14U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
/* 116, 91, emit details */
/* 0, 229, */
};
// clang-format on
// TX BUILDER
#define FLAGS_OUT (txn + 4U)
#define FLS_OUT (txn + 15U)
#define LLS_OUT (txn + 21U)
#define FEE_OUT (txn + 26U)
#define ACCOUNT_OUT (txn + 71U)
#define EMIT_OUT (txn + 91U)
#define FLIP_ENDIAN_32(value) \
(uint32_t)(((value & 0xFFU) << 24) | ((value & 0xFF00U) << 8) | \
((value & 0xFF0000U) >> 8) | ((value & 0xFF000000U) >> 24))
#define SET_UINT32(ptr, value) *((uint32_t *)(ptr)) = FLIP_ENDIAN_32(value);
#define SET_NATIVE_AMOUNT(ptr, amount) \
do { \
uint8_t *b = (ptr); \
*b++ = 0b01000000 + ((amount >> 56) & 0b00111111); \
*b++ = (amount >> 48) & 0xFFU; \
*b++ = (amount >> 40) & 0xFFU; \
*b++ = (amount >> 32) & 0xFFU; \
*b++ = (amount >> 24) & 0xFFU; \
*b++ = (amount >> 16) & 0xFFU; \
*b++ = (amount >> 8) & 0xFFU; \
*b++ = (amount >> 0) & 0xFFU; \
} while (0)
#define PREPARE_TXN() \
do { \
etxn_reserve(1); \
uint32_t fls = (uint32_t)ledger_seq() + 1; \
SET_UINT32(FLS_OUT, fls); \
SET_UINT32(LLS_OUT, fls + 4); \
hook_account(ACCOUNT_OUT, 20); \
etxn_details(EMIT_OUT, 138U); \
int64_t fee = etxn_fee_base(SBUF(txn)); \
SET_NATIVE_AMOUNT(FEE_OUT, fee); \
} while (0)
int64_t cbak(uint32_t r)
{
_g(1,1);
return accept(0,0,0);
}
int64_t hook(uint32_t reserved)
{
_g(1,1);
PREPARE_TXN();
uint8_t emithash[32];
int64_t emit_result = emit(SBUF(emithash), SBUF(txn));
if (emit_result < 0)
return rollback(SBUF("Emit failed."), __LINE__);
return accept(SBUF("Emit succeeded."), __LINE__);
}
)[test.hook]",
{
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, 0x25U,
0x06U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x03U, 0x7FU,
0x7FU, 0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x60U,
0x00U, 0x01U, 0x7EU, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U,
0x04U, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x02U, 0x8FU, 0x01U,
0x09U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, 0x00U, 0x00U,
0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U,
0x74U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0CU, 0x65U, 0x74U,
0x78U, 0x6EU, 0x5FU, 0x72U, 0x65U, 0x73U, 0x65U, 0x72U, 0x76U, 0x65U,
0x00U, 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0AU, 0x6CU, 0x65U, 0x64U,
0x67U, 0x65U, 0x72U, 0x5FU, 0x73U, 0x65U, 0x71U, 0x00U, 0x03U, 0x03U,
0x65U, 0x6EU, 0x76U, 0x0CU, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x61U,
0x63U, 0x63U, 0x6FU, 0x75U, 0x6EU, 0x74U, 0x00U, 0x04U, 0x03U, 0x65U,
0x6EU, 0x76U, 0x0CU, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x64U, 0x65U,
0x74U, 0x61U, 0x69U, 0x6CU, 0x73U, 0x00U, 0x04U, 0x03U, 0x65U, 0x6EU,
0x76U, 0x0DU, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x66U, 0x65U, 0x65U,
0x5FU, 0x62U, 0x61U, 0x73U, 0x65U, 0x00U, 0x04U, 0x03U, 0x65U, 0x6EU,
0x76U, 0x04U, 0x65U, 0x6DU, 0x69U, 0x74U, 0x00U, 0x05U, 0x03U, 0x65U,
0x6EU, 0x76U, 0x08U, 0x72U, 0x6FU, 0x6CU, 0x6CU, 0x62U, 0x61U, 0x63U,
0x6BU, 0x00U, 0x01U, 0x03U, 0x03U, 0x02U, 0x02U, 0x02U, 0x05U, 0x03U,
0x01U, 0x00U, 0x02U, 0x06U, 0x27U, 0x06U, 0x7FU, 0x01U, 0x41U, 0x90U,
0x8AU, 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x82U, 0x0AU, 0x0BU, 0x7FU,
0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x90U, 0x8AU,
0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U,
0x41U, 0x80U, 0x08U, 0x0BU, 0x07U, 0x0FU, 0x02U, 0x04U, 0x63U, 0x62U,
0x61U, 0x6BU, 0x00U, 0x09U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U,
0x0AU, 0x0AU, 0xA8U, 0x83U, 0x00U, 0x02U, 0x99U, 0x80U, 0x00U, 0x00U,
0x41U, 0x01U, 0x41U, 0x01U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U,
0x1AU, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x00U, 0x10U, 0x81U, 0x80U,
0x80U, 0x80U, 0x00U, 0x0BU, 0x88U, 0x83U, 0x00U, 0x02U, 0x03U, 0x7FU,
0x01U, 0x7EU, 0x23U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x41U, 0x20U,
0x6BU, 0x22U, 0x01U, 0x24U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x41U,
0x01U, 0x41U, 0x01U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU,
0x41U, 0x01U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x41U,
0x00U, 0x10U, 0x83U, 0x80U, 0x80U, 0x80U, 0x00U, 0xA7U, 0x22U, 0x02U,
0x41U, 0x05U, 0x6AU, 0x22U, 0x03U, 0x41U, 0x18U, 0x74U, 0x20U, 0x03U,
0x41U, 0x08U, 0x74U, 0x41U, 0x80U, 0x80U, 0xFCU, 0x07U, 0x71U, 0x72U,
0x20U, 0x03U, 0x41U, 0x08U, 0x76U, 0x41U, 0x80U, 0xFEU, 0x03U, 0x71U,
0x20U, 0x03U, 0x41U, 0x18U, 0x76U, 0x72U, 0x72U, 0x36U, 0x02U, 0x95U,
0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x00U, 0x20U, 0x02U, 0x41U, 0x01U,
0x6AU, 0x22U, 0x03U, 0x41U, 0x18U, 0x74U, 0x20U, 0x03U, 0x41U, 0x08U,
0x74U, 0x41U, 0x80U, 0x80U, 0xFCU, 0x07U, 0x71U, 0x72U, 0x20U, 0x03U,
0x41U, 0x08U, 0x76U, 0x41U, 0x80U, 0xFEU, 0x03U, 0x71U, 0x20U, 0x03U,
0x41U, 0x18U, 0x76U, 0x72U, 0x72U, 0x36U, 0x02U, 0x8FU, 0x88U, 0x80U,
0x80U, 0x00U, 0x41U, 0xC7U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x14U,
0x10U, 0x84U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x41U, 0xDBU, 0x88U,
0x80U, 0x80U, 0x00U, 0x41U, 0x8AU, 0x01U, 0x10U, 0x85U, 0x80U, 0x80U,
0x80U, 0x00U, 0x1AU, 0x41U, 0x00U, 0x41U, 0x80U, 0x88U, 0x80U, 0x80U,
0x00U, 0x41U, 0xE5U, 0x01U, 0x10U, 0x86U, 0x80U, 0x80U, 0x80U, 0x00U,
0x22U, 0x04U, 0x3CU, 0x00U, 0xA1U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U,
0x00U, 0x20U, 0x04U, 0x42U, 0x08U, 0x88U, 0x3CU, 0x00U, 0xA0U, 0x88U,
0x80U, 0x80U, 0x00U, 0x41U, 0x00U, 0x20U, 0x04U, 0x42U, 0x10U, 0x88U,
0x3CU, 0x00U, 0x9FU, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x00U, 0x20U,
0x04U, 0x42U, 0x18U, 0x88U, 0x3CU, 0x00U, 0x9EU, 0x88U, 0x80U, 0x80U,
0x00U, 0x41U, 0x00U, 0x20U, 0x04U, 0x42U, 0x20U, 0x88U, 0x3CU, 0x00U,
0x9DU, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x00U, 0x20U, 0x04U, 0x42U,
0x28U, 0x88U, 0x3CU, 0x00U, 0x9CU, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U,
0x00U, 0x20U, 0x04U, 0x42U, 0x30U, 0x88U, 0x3CU, 0x00U, 0x9BU, 0x88U,
0x80U, 0x80U, 0x00U, 0x41U, 0x00U, 0x20U, 0x04U, 0x42U, 0x38U, 0x88U,
0xA7U, 0x41U, 0x3FU, 0x71U, 0x41U, 0xC0U, 0x00U, 0x72U, 0x3AU, 0x00U,
0x9AU, 0x88U, 0x80U, 0x80U, 0x00U, 0x02U, 0x40U, 0x02U, 0x40U, 0x20U,
0x01U, 0x41U, 0x20U, 0x41U, 0x80U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U,
0xE5U, 0x01U, 0x10U, 0x87U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x7FU,
0x55U, 0x0DU, 0x00U, 0x41U, 0xE5U, 0x89U, 0x80U, 0x80U, 0x00U, 0x41U,
0x0DU, 0x42U, 0xD3U, 0x00U, 0x10U, 0x88U, 0x80U, 0x80U, 0x80U, 0x00U,
0x21U, 0x04U, 0x0CU, 0x01U, 0x0BU, 0x41U, 0xF2U, 0x89U, 0x80U, 0x80U,
0x00U, 0x41U, 0x10U, 0x42U, 0xD4U, 0x00U, 0x10U, 0x81U, 0x80U, 0x80U,
0x80U, 0x00U, 0x21U, 0x04U, 0x0BU, 0x20U, 0x01U, 0x41U, 0x20U, 0x6AU,
0x24U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x20U, 0x04U, 0x0BU, 0x0BU,
0x90U, 0x02U, 0x02U, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0xE5U, 0x01U,
0x12U, 0x00U, 0x63U, 0x22U, 0x00U, 0x00U, 0x00U, 0x00U, 0x24U, 0x00U,
0x00U, 0x00U, 0x00U, 0x20U, 0x1AU, 0x00U, 0x00U, 0x00U, 0x00U, 0x20U,
0x1BU, 0x00U, 0x00U, 0x00U, 0x00U, 0x68U, 0x40U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x73U, 0x21U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x81U,
0x14U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x41U, 0xE5U, 0x09U, 0x0BU, 0x1DU, 0x45U, 0x6DU, 0x69U, 0x74U, 0x20U,
0x66U, 0x61U, 0x69U, 0x6CU, 0x65U, 0x64U, 0x2EU, 0x00U, 0x45U, 0x6DU,
0x69U, 0x74U, 0x20U, 0x73U, 0x75U, 0x63U, 0x63U, 0x65U, 0x65U, 0x64U,
0x65U, 0x64U, 0x2EU, 0x00U,
}},
};
}
} // namespace ripple
+4 -10
View File
@@ -207,25 +207,19 @@ private:
test::jtx::Env env{*this, envconfig(), features};
auto const alice = Account("alice");
auto const issuer = Account::master;
env.fund(XRP(1000), alice);
env.close();
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0), fee(XRP(1)));
auto const issuer = Account("issuer");
env.fund(XRP(1000), alice, issuer);
env.close();
// claim reward
env(reward::claim(alice),
reward::issuer(issuer),
fee(XRP(1)),
ter(tesSUCCESS));
env(reward::claim(alice), reward::issuer(issuer), ter(tesSUCCESS));
env.close();
// verify touch
validateTouch(env, alice, {"ClaimReward", "tesSUCCESS"});
auto const tt = env.current()->rules().enabled(featureTouch)
? "ClaimReward"
: "SetHook";
: "AccountSet";
validateTouch(env, issuer, {tt, "tesSUCCESS"});
}
-465
View File
@@ -1,465 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2026 XRPL-Labs
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <test/jtx.h>
#include <test/jtx/AMM.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/TxFlags.h>
namespace ripple {
namespace test {
struct URITokenBroker_test : public beast::unit_test::suite
{
static Json::Value
brokeredBuy(
jtx::Account const& buyer,
std::string const& tokenID,
STAmount const& amount,
jtx::Account const& broker)
{
auto tx = jtx::uritoken::buy(buyer, tokenID);
tx[sfAmount.jsonName] = amount.getJson(JsonOptions::none);
tx[sfBrokerAccount.jsonName] = broker.human();
return tx;
}
static std::string
mintAndSell(
jtx::Env& env,
jtx::Account const& seller,
std::string const& uri,
STAmount const& amount,
std::optional<jtx::Account> const& destination = std::nullopt)
{
auto const id = strHex(jtx::uritoken::tokenid(seller, uri));
env(jtx::uritoken::mint(seller, uri));
if (destination)
env(jtx::uritoken::sell(seller, id),
jtx::uritoken::amt(amount),
jtx::uritoken::dest(*destination));
else
env(jtx::uritoken::sell(seller, id), jtx::uritoken::amt(amount));
env.close();
return id;
}
static std::shared_ptr<SLE const>
token(jtx::Env const& env, uint256 const& id)
{
return env.le(Keylet{ltURI_TOKEN, id});
}
static std::shared_ptr<SLE const>
token(jtx::Env const& env, std::string const& id)
{
uint256 tokenID;
if (!tokenID.parseHex(id))
return nullptr;
return token(env, tokenID);
}
void
testAmendment(FeatureBitset const& features)
{
testcase("amendment");
using namespace jtx;
Account const seller{"seller"};
Account const buyer{"buyer"};
Account const broker{"broker"};
// A broker-less buy remains valid when the broker amendment is off.
{
Env env{*this, features - featureURITokenBroker};
env.fund(XRP(1000), seller, buyer, broker);
auto const id = mintAndSell(env, seller, "no-broker", XRP(10));
env(uritoken::buy(buyer, id), uritoken::amt(XRP(10)));
}
// BrokerAccount requires both URITokenBroker and fixXahauV1.
for (auto const disabled : {featureURITokenBroker, fixXahauV1})
{
Env env{*this, features - disabled};
env.fund(XRP(1000), seller, buyer, broker);
auto const id =
mintAndSell(env, seller, to_string(disabled), XRP(10));
env(brokeredBuy(buyer, id, XRP(11), broker), ter(temDISABLED));
}
}
void
testMalformed(FeatureBitset const& features)
{
testcase("preflight");
using namespace jtx;
Env env{*this, features};
Account const seller{"seller"};
Account const buyer{"buyer"};
env.fund(XRP(1000), seller, buyer);
auto const id = mintAndSell(env, seller, "preflight", XRP(10));
env(brokeredBuy(buyer, id, XRP(11), buyer), ter(temREDUNDANT));
auto zero = uritoken::buy(buyer, id);
zero[sfAmount.jsonName] = XRP(11).value().getJson(JsonOptions::none);
zero[sfBrokerAccount.jsonName] = "rrrrrrrrrrrrrrrrrrrrrhoLvTp";
env(zero, ter(temMALFORMED));
// BrokerAccount is only part of the URITokenBuy template.
auto mint = uritoken::mint(seller, "broker-on-mint");
mint[sfBrokerAccount.jsonName] = buyer.human();
env(mint, ter(temMALFORMED));
// BrokerAccount belongs only to URITokenBuy's serialization template.
auto payment = pay(buyer, seller, XRP(1));
payment[sfBrokerAccount.jsonName] = seller.human();
env(payment, ter(temMALFORMED));
}
void
testPreclaim(FeatureBitset const& features)
{
testcase("preclaim");
using namespace jtx;
Account const seller{"seller"};
Account const buyer{"buyer"};
Account const broker{"broker"};
Account const missing{"missing"};
// Missing broker account.
{
Env env{*this, features};
env.fund(XRP(1000), seller, buyer);
env.memoize(missing);
auto const id = mintAndSell(env, seller, "missing", XRP(10));
env(brokeredBuy(buyer, id, XRP(11), missing), ter(tecNO_TARGET));
}
// The seller cannot also receive the broker spread.
{
Env env{*this, features};
env.fund(XRP(1000), seller, buyer);
auto const id = mintAndSell(env, seller, "seller", XRP(10));
env(brokeredBuy(buyer, id, XRP(11), seller), ter(tecNO_PERMISSION));
}
// A brokered sale requires both a positive price and positive spread.
{
Env env{*this, features};
env.fund(XRP(1000), seller, buyer, broker);
auto const zeroID = mintAndSell(env, seller, "zero", XRP(0), buyer);
env(brokeredBuy(buyer, zeroID, XRP(1), broker),
ter(tecNO_PERMISSION));
// The legacy destination-restricted zero-price buy is unchanged.
env(uritoken::buy(buyer, zeroID), uritoken::amt(XRP(0)));
auto const id = mintAndSell(env, seller, "spread", XRP(10));
env(brokeredBuy(buyer, id, XRP(9), broker),
ter(tecINSUFFICIENT_PAYMENT));
env(brokeredBuy(buyer, id, XRP(10), broker), ter(tecNO_PERMISSION));
}
// An IOU broker must already have a trust line.
{
Env env{*this, features};
Account const gw{"gateway"};
auto const USD = gw["USD"];
env.fund(XRP(1000), seller, buyer, broker, gw);
env.trust(USD(1000), seller, buyer);
env.close();
env(pay(gw, buyer, USD(100)));
auto const id = mintAndSell(env, seller, "no-line", USD(10));
env(brokeredBuy(buyer, id, USD(11), broker), ter(tecNO_LINE));
}
// AMM pseudo-accounts cannot be named as brokers.
{
Env env{*this, features | featureAMM};
Account const gw{"gateway"};
auto const USD = gw["USD"];
env.fund(XRP(30'000), seller, buyer, gw);
env.trust(USD(30'000), seller, buyer);
env.close();
env(pay(gw, seller, USD(20'000)));
AMM amm{env, seller, XRP(10'000), USD(10'000)};
auto const id = mintAndSell(env, seller, "amm", XRP(10));
auto tx = uritoken::buy(buyer, id);
tx[sfAmount.jsonName] = XRP(11).value().getJson(JsonOptions::none);
tx[sfBrokerAccount.jsonName] = toBase58(amm.ammAccount());
env(tx, ter(tecNO_PERMISSION));
}
}
void
testXAH(FeatureBitset const& features)
{
testcase("XAH settlement");
using namespace jtx;
Env env{*this, features};
Account const seller{"seller"};
Account const buyer{"buyer"};
Account const broker{"broker"};
env.fund(XRP(1000), seller, buyer, broker);
env(fset(seller, asfDepositAuth));
env(fset(broker, asfDepositAuth));
env.close();
auto const id = mintAndSell(env, seller, "xah", XRP(100));
auto const sellerBefore = env.balance(seller);
auto const buyerBefore = env.balance(buyer);
auto const brokerBefore = env.balance(broker);
auto const buyerOwners = env.ownerCount(buyer);
auto const brokerOwners = env.ownerCount(broker);
auto const fee = env.current()->fees().base;
env(brokeredBuy(buyer, id, XRP(110), broker));
env.close();
BEAST_EXPECT(env.balance(seller) == sellerBefore + XRP(100));
BEAST_EXPECT(env.balance(broker) == brokerBefore + XRP(10));
BEAST_EXPECT(env.balance(buyer) == buyerBefore - XRP(110) - fee);
BEAST_EXPECT(env.ownerCount(buyer) == buyerOwners + 1);
BEAST_EXPECT(env.ownerCount(broker) == brokerOwners);
auto const sle = token(env, id);
if (!BEAST_EXPECT(sle))
return;
BEAST_EXPECT((*sle)[sfOwner] == buyer.id());
BEAST_EXPECT(!sle->isFieldPresent(sfAmount));
BEAST_EXPECT(!sle->isFieldPresent(sfDestination));
}
void
testXAHReserve(FeatureBitset const& features)
{
testcase("XAH reserve boundary");
using namespace jtx;
Env env{*this, features};
Account const seller{"seller"};
Account const buyer{"buyer"};
Account const broker{"broker"};
env.fund(XRP(1000), seller, broker);
env.fund(XRP(300), buyer);
env.close();
auto const id = mintAndSell(env, seller, "reserve", XRP(1));
auto const sellerBefore = env.balance(seller);
auto const brokerBefore = env.balance(broker);
// The buyer needs the next owner reserve plus the full signed amount,
// not merely the seller's one-XAH listing amount.
env(brokeredBuy(buyer, id, XRP(51), broker),
ter(tecINSUFFICIENT_FUNDS));
BEAST_EXPECT(env.balance(seller) == sellerBefore);
BEAST_EXPECT(env.balance(broker) == brokerBefore);
auto const sle = token(env, id);
BEAST_EXPECT(sle && (*sle)[sfOwner] == seller.id());
}
void
testIOU(FeatureBitset const& features)
{
testcase("IOU settlement");
using namespace jtx;
Account const seller{"seller"};
Account const buyer{"buyer"};
Account const broker{"broker"};
Account const gw{"gateway"};
auto const USD = gw["USD"];
// Both recipients bear the transfer rate; buyer debit remains exact.
{
Env env{*this, features};
env.fund(XRP(1000), seller, buyer, broker, gw);
env(rate(gw, 1.25));
env.trust(USD(1000), seller, buyer, broker);
env.close();
env(trust(gw, seller["USD"](1000), tfClearNoRipple));
env(trust(gw, buyer["USD"](1000), tfClearNoRipple));
env(trust(gw, broker["USD"](1000), tfClearNoRipple));
env.close();
env(pay(gw, buyer, USD(1000)));
auto const id = mintAndSell(env, seller, "rate", USD(100));
auto const buyerBefore = env.balance(buyer, USD.issue());
auto const brokerOwners = env.ownerCount(broker);
env(brokeredBuy(buyer, id, USD(110), broker));
env.close();
BEAST_EXPECT(env.balance(seller, USD.issue()) == USD(80));
BEAST_EXPECT(env.balance(broker, USD.issue()) == USD(8));
BEAST_EXPECT(
env.balance(buyer, USD.issue()) == buyerBefore - USD(110));
BEAST_EXPECT(env.ownerCount(broker) == brokerOwners);
}
// The issuer may be the broker and needs no trust line.
{
Env env{*this, features};
env.fund(XRP(1000), seller, buyer, gw);
env(rate(gw, 1.25));
env.trust(USD(1000), seller, buyer);
env.close();
env(trust(gw, seller["USD"](1000), tfClearNoRipple));
env(trust(gw, buyer["USD"](1000), tfClearNoRipple));
env.close();
env(pay(gw, buyer, USD(1000)));
auto const id = mintAndSell(env, seller, "issuer", USD(100));
auto const buyerBefore = env.balance(buyer, USD.issue());
env(brokeredBuy(buyer, id, USD(110), gw));
env.close();
BEAST_EXPECT(env.balance(seller, USD.issue()) == USD(80));
BEAST_EXPECT(
env.balance(buyer, USD.issue()) == buyerBefore - USD(110));
}
// Existing line limits are not consulted and no owner object is added.
{
Env env{*this, features};
env.fund(XRP(1000), seller, buyer, broker, gw);
env.trust(USD(1000), seller, buyer);
env.trust(USD(1), broker);
env.close();
env(trust(gw, seller["USD"](1000), tfClearNoRipple));
env(trust(gw, buyer["USD"](1000), tfClearNoRipple));
env(trust(gw, broker["USD"](1), tfClearNoRipple));
env.close();
env(pay(gw, buyer, USD(100)));
env(pay(gw, broker, USD(1)));
auto const id = mintAndSell(env, seller, "limit", USD(10));
auto const brokerOwners = env.ownerCount(broker);
env(brokeredBuy(buyer, id, USD(20), broker));
env.close();
BEAST_EXPECT(env.balance(broker, USD.issue()) == USD(11));
BEAST_EXPECT(env.ownerCount(broker) == brokerOwners);
}
}
void
testIOUPermissions(FeatureBitset const& features)
{
testcase("IOU permissions");
using namespace jtx;
Account const seller{"seller"};
Account const buyer{"buyer"};
Account const broker{"broker"};
Account const gw{"gateway"};
auto const USD = gw["USD"];
// A frozen broker line rejects the broker leg atomically.
{
Env env{*this, features};
env.fund(XRP(1000), seller, buyer, broker, gw);
env.trust(USD(1000), seller, buyer, broker);
env.close();
env(trust(gw, seller["USD"](1000), tfClearNoRipple));
env(trust(gw, buyer["USD"](1000), tfClearNoRipple));
env(pay(gw, buyer, USD(100)));
env(trust(gw, broker["USD"](1000), tfSetFreeze | tfClearNoRipple));
env.close();
auto const id = mintAndSell(env, seller, "freeze", USD(10));
auto const buyerBefore = env.balance(buyer, USD.issue());
env(brokeredBuy(buyer, id, USD(11), broker), ter(tecFROZEN));
BEAST_EXPECT(env.balance(seller, USD.issue()) == USD(0));
BEAST_EXPECT(env.balance(broker, USD.issue()) == USD(0));
BEAST_EXPECT(env.balance(buyer, USD.issue()) == buyerBefore);
}
// Under RequireAuth, an existing but unauthorized broker line fails.
{
Env env{*this, features};
auto const sellerUSD = seller["USD"];
auto const buyerUSD = buyer["USD"];
env.fund(XRP(1000), seller, buyer, broker, gw);
env(fset(gw, asfRequireAuth));
env.close();
env.trust(USD(1000), seller, buyer, broker);
env(trust(gw, sellerUSD(1000)),
txflags(tfSetfAuth | tfClearNoRipple));
env(trust(gw, buyerUSD(1000)),
txflags(tfSetfAuth | tfClearNoRipple));
env(trust(gw, broker["USD"](1000)), txflags(tfClearNoRipple));
env.close();
env(pay(gw, buyer, USD(100)));
auto const id = mintAndSell(env, seller, "auth", USD(10));
env(brokeredBuy(buyer, id, USD(11), broker), ter(tecNO_AUTH));
}
// NoRipple on the broker leg rejects the whole transaction; the
// already-computed seller leg must also be rolled back.
{
Env env{*this, features};
env.fund(XRP(1000), seller, buyer, broker, gw);
env.trust(USD(1000), seller, buyer, broker);
env.close();
env(trust(gw, seller["USD"](1000), tfClearNoRipple));
env(trust(gw, buyer["USD"](1000), tfClearNoRipple));
env(pay(gw, buyer, USD(100)));
env(trust(gw, broker["USD"](1000), tfSetNoRipple));
env.close();
auto const id = mintAndSell(env, seller, "no-ripple", USD(10));
auto const sellerBefore = env.balance(seller, USD.issue());
auto const buyerBefore = env.balance(buyer, USD.issue());
auto const brokerBefore = env.balance(broker, USD.issue());
env(brokeredBuy(buyer, id, USD(11), broker), ter(tecPATH_DRY));
BEAST_EXPECT(env.balance(seller, USD.issue()) == sellerBefore);
BEAST_EXPECT(env.balance(buyer, USD.issue()) == buyerBefore);
BEAST_EXPECT(env.balance(broker, USD.issue()) == brokerBefore);
auto const sle = token(env, id);
BEAST_EXPECT(sle && (*sle)[sfOwner] == seller.id());
}
}
public:
void
run() override
{
auto const features = jtx::supported_amendments();
testAmendment(features);
testMalformed(features);
testPreclaim(features);
testXAH(features);
testXAHReserve(features);
testIOU(features);
testIOUPermissions(features);
}
};
BEAST_DEFINE_TESTSUITE(URITokenBroker, app, ripple);
} // namespace test
} // namespace ripple
+7 -5
View File
@@ -140,7 +140,8 @@ struct XahauGenesis_test : public beast::unit_test::suite
bool skipTests = false,
bool const testFlag = false,
bool const badNetID = false,
uint32_t const expectedOwnerCount = 14 /** case for testFlag=false */)
uint32_t const expectedOwnerCount =
10 /** testFlag ? 10 : 14 (default) */)
{
using namespace jtx;
@@ -249,7 +250,9 @@ struct XahauGenesis_test : public beast::unit_test::suite
genesisAccRoot->getFieldAmount(sfBalance) ==
XahauGenesis::GenesisAmount);
BEAST_EXPECT(
genesisAccRoot->getFieldU32(sfOwnerCount) == expectedOwnerCount);
genesisAccRoot->getFieldU32(sfOwnerCount) == !testFlag
? expectedOwnerCount
: 14);
// ensure the definitions are correctly set
{
@@ -592,8 +595,7 @@ struct XahauGenesis_test : public beast::unit_test::suite
false,
true,
{},
2 /*Hook objects *2 */ + 3 /* IRR,IRD,IMC HookStates */ +
members.size());
3 /* IRR,IRD,IMC */ + members.size() + tables.size());
env.close();
env.close();
@@ -2325,7 +2327,7 @@ struct XahauGenesis_test : public beast::unit_test::suite
{
BEAST_EXPECT(
root->getFieldU32(sfOwnerCount) ==
(mc * 2 + 2 + paramsCount));
mc * 2 + 2 + paramsCount);
BEAST_EXPECT(root->getFieldU32(sfFlags) & lsfDisableMaster);
BEAST_EXPECT(root->getAccountID(sfRegularKey) == noAccount());
}
-128
View File
@@ -1513,133 +1513,6 @@ r.ripple.com:51235
}
}
void
testDatabasePinnedValidation()
{
testcase("DatabasePinned config validation");
// Test 1: pinned_type=rwdb in non-standalone mode (should throw)
{
Config c;
std::string toLoad =
"[node_db]\n"
"type=NuDB\n"
"path=main\n"
"online_delete=256\n"
"pinned_type=rwdb\n"
"pinned_path=pinned\n";
c.setupControl(true, true, false); // standalone = false
try
{
c.loadFromString(toLoad);
fail("Expected exception for non-durable pinned_type");
}
catch (std::runtime_error const& e)
{
BEAST_EXPECT(
std::string(e.what()).find("durable backend") !=
std::string::npos);
pass();
}
}
// Test 2: pinned_type=none in non-standalone mode (should throw)
{
Config c;
std::string toLoad =
"[node_db]\n"
"type=NuDB\n"
"path=main\n"
"online_delete=256\n"
"pinned_type=none\n"
"pinned_path=pinned\n";
c.setupControl(true, true, false); // standalone = false
try
{
c.loadFromString(toLoad);
fail("Expected exception for non-durable pinned_type");
}
catch (std::runtime_error const& e)
{
BEAST_EXPECT(
std::string(e.what()).find("durable backend") !=
std::string::npos);
pass();
}
}
// Test 3: pinned_type=rwdb in standalone mode (should succeed
// — standalone is unrestricted)
{
Config c;
std::string toLoad =
"[node_db]\n"
"type=NuDB\n"
"path=main\n"
"online_delete=256\n"
"pinned_type=rwdb\n"
"pinned_path=pinned\n";
c.setupControl(true, true, true); // standalone = true
try
{
c.loadFromString(toLoad);
pass();
}
catch (std::runtime_error const& e)
{
fail("Should not throw in standalone mode");
}
}
// Test 4: pinned_type=NuDB in non-standalone mode (should succeed)
{
Config c;
std::string toLoad =
"[node_db]\n"
"type=NuDB\n"
"path=main\n"
"online_delete=256\n"
"pinned_type=NuDB\n"
"pinned_path=pinned\n";
c.setupControl(true, true, false); // standalone = false
try
{
c.loadFromString(toLoad);
pass();
}
catch (std::runtime_error const& e)
{
fail("Should not throw for durable pinned_type");
}
}
// Test 5: pinned_type without online_delete (should throw)
{
Config c;
std::string toLoad =
"[node_db]\n"
"type=NuDB\n"
"path=main\n"
"pinned_type=NuDB\n"
"pinned_path=pinned\n";
c.setupControl(true, true, false);
try
{
c.loadFromString(toLoad);
fail(
"Expected exception for pinned_type without "
"online_delete");
}
catch (std::runtime_error const& e)
{
BEAST_EXPECT(
std::string(e.what()).find("online_delete") !=
std::string::npos);
pass();
}
}
}
void
testOverlay()
{
@@ -1731,7 +1604,6 @@ r.ripple.com:51235
testGetters();
testAmendment();
testRWDBOnlineDelete();
testDatabasePinnedValidation();
testOverlay();
testNetworkID();
}
+1 -2
View File
@@ -93,8 +93,7 @@ protected:
std::optional<std::pair<STAmount, STAmount>> const& pool = std::nullopt,
std::uint16_t tfee = 0,
std::optional<jtx::ter> const& ter = std::nullopt,
std::vector<FeatureBitset> const& features = {
supported_amendments() | featureAMM | featureAMMClawback});
std::vector<FeatureBitset> const& features = {supported_amendments()});
void
testAMM(
+1 -1
View File
@@ -31,7 +31,7 @@ namespace ripple {
namespace test {
namespace jtx {
[[maybe_unused]] static Number
static Number
number(STAmount const& a)
{
if (isXRP(a))
+1 -2
View File
@@ -197,8 +197,7 @@ AMMTest::pathTestEnv()
cfg->PATH_SEARCH_MAX = 10;
return cfg;
}),
(supported_amendments() | featureAMM | featureAMMClawback) -
featureXahauGenesis - featureTouch);
supported_amendments() - featureXahauGenesis - featureTouch);
}
Json::Value
+1 -1
View File
@@ -44,7 +44,7 @@ import(jtx::Account const& account, Json::Value const& xpop)
void
issuer::operator()(Env& env, JTx& jt) const
{
jt.jv[sfIssuer.jsonName] = to_string(issuer_);
jt.jv[sfIssuer.jsonName] = issuer_.human();
}
Json::Value
+1 -1
View File
@@ -66,7 +66,7 @@ blob::operator()(Env& env, JTx& jt) const
void
dest::operator()(Env& env, JTx& jt) const
{
jt.jv[sfDestination.jsonName] = to_string(dest_);
jt.jv[sfDestination.jsonName] = dest_.human();
}
} // namespace invoke
+2 -15
View File
@@ -29,31 +29,18 @@ namespace reward {
// Claim a reward.
Json::Value
claim(jtx::Account const& account)
{
return claim(account.id());
}
Json::Value
claim(AccountID const& account)
{
using namespace jtx;
Json::Value jv;
jv[jss::TransactionType] = jss::ClaimReward;
jv[jss::Account] = to_string(account);
jv[jss::Account] = account.human();
return jv;
}
void
issuer::operator()(Env& env, JTx& jt) const
{
jt.jv[sfIssuer.jsonName] = to_string(issuer_);
}
void
claimCurrency::operator()(Env& env, JTx& jt) const
{
jt.jv[sfClaimCurrency.jsonName] =
STIssue{sfClaimCurrency, claimCurrency_}.getJson(JsonOptions::none);
jt.jv[sfIssuer.jsonName] = issuer_.human();
}
} // namespace reward
+1 -1
View File
@@ -63,7 +63,7 @@ countTx(std::shared_ptr<SHAMap> const& txSet)
auto tx = std::make_shared<STTx
const>(SerialIter{sit.getSlice(sit.getVLDataLength())});
if (tx->getTxnType() == ttUNL_MODIFY)
if (tx->getFieldU16(sfTransactionType) == ttUNL_MODIFY)
counter++;
}
*/
+2 -2
View File
@@ -37,10 +37,10 @@ import(jtx::Account const& account, Json::Value const& xpop);
class issuer
{
private:
AccountID issuer_;
jtx::Account issuer_;
public:
explicit issuer(AccountID const& issuer) : issuer_(issuer)
explicit issuer(jtx::Account const& issuer) : issuer_(issuer)
{
}
+2 -2
View File
@@ -58,10 +58,10 @@ public:
class dest
{
private:
AccountID dest_;
jtx::Account dest_;
public:
explicit dest(AccountID const& dest) : dest_(dest)
explicit dest(jtx::Account const& dest) : dest_(dest)
{
}
+1 -23
View File
@@ -34,39 +34,17 @@ namespace reward {
Json::Value
claim(jtx::Account const& account);
Json::Value
claim(AccountID const& account);
/** Sets the optional Issuer on a JTx. */
class issuer
{
private:
AccountID issuer_;
jtx::Account issuer_;
public:
explicit issuer(jtx::Account const& issuer) : issuer_(issuer)
{
}
explicit issuer(AccountID const& issuer) : issuer_(issuer)
{
}
void
operator()(Env&, JTx& jtx) const;
};
/** Sets the optional ClaimCurrency on a JTx. */
class claimCurrency
{
private:
Issue claimCurrency_;
public:
explicit claimCurrency(Issue const& issue) : claimCurrency_(issue)
{
}
void
operator()(Env&, JTx& jtx) const;
};
+3 -41
View File
@@ -28,6 +28,7 @@
#include <xrpl/protocol/STLedgerEntry.h>
#include <boost/algorithm/string/predicate.hpp>
#include <regex>
namespace ripple {
@@ -78,7 +79,7 @@ class Invariants_test : public beast::unit_test::suite
{
using namespace test::jtx;
FeatureBitset amendments =
supported_amendments() | featureInvariantsV1_1 | featureAMM;
supported_amendments() | featureInvariantsV1_1;
Env env{*this, amendments};
Account const A1{"A1"};
@@ -136,9 +137,7 @@ class Invariants_test : public beast::unit_test::suite
if (sink.messages().str().find(m) == std::string::npos)
{
// uncomment if you want to log the invariant failure
// message
// log << sink.messages().str() << std::endl;
// log << " --> " << m << std::endl;
// message log << " --> " << m << std::endl;
fail();
}
}
@@ -1234,42 +1233,6 @@ class Invariants_test : public beast::unit_test::suite
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
}
void
testLockedBalance()
{
using namespace test::jtx;
testcase << "ValidLockedBalance";
doInvariantCheck(
{{"Invariant failed: IOU locked balance is greater than balance"}},
[&](Account const& A1, Account const& A2, ApplyContext& ac) {
IOU const USD{A2["USD"]};
auto const sle =
std::make_shared<SLE>(keylet::line(A1, A2, USD.currency));
sle->setFieldAmount(sfHighLimit, A1["USD"](100));
sle->setFieldAmount(sfLowLimit, A2["USD"](100));
sle->setFieldAmount(sfBalance, USD(100));
sle->setFieldAmount(sfLockedBalance, USD(101));
ac.view().insert(sle);
return true;
});
doInvariantCheck(
{{"Invariant failed: IOU locked balance is greater than balance"}},
[&](Account const& A1, Account const& A2, ApplyContext& ac) {
IOU const USD{A2["USD"]};
auto const sle =
std::make_shared<SLE>(keylet::line(A1, A2, USD.currency));
sle->setFieldAmount(sfHighLimit, A1["USD"](100));
sle->setFieldAmount(sfLowLimit, A2["USD"](100));
sle->setFieldAmount(sfBalance, USD(-100));
sle->setFieldAmount(sfLockedBalance, USD(-101));
ac.view().insert(sle);
return true;
});
}
public:
void
run() override
@@ -1288,7 +1251,6 @@ public:
testValidNewAccountRoot();
testNFTokenPageInvariants();
testPermissionedDomainInvariants();
testLockedBalance();
}
};
-195
View File
@@ -1,195 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2026 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/envconfig.h>
#include <xrpld/core/ConfigSections.h>
#include <xrpld/nodestore/Database.h>
#include <xrpld/nodestore/NodeObject.h>
#include <xrpld/nodestore/detail/DatabasePinnedImp.h>
#include <xrpl/basics/Buffer.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/protocol/digest.h>
namespace ripple {
namespace test {
class DatabasePinned_test : public beast::unit_test::suite
{
// Build an Env whose nodestore is wired up as DatabasePinnedImp.
// pinned_type=rwdb is allowed in standalone mode (the catalogue
// tests do the same).
static std::unique_ptr<Config>
pinnedEnvconfig()
{
auto cfg = jtx::envconfig();
auto& nodeDb = cfg->section(ConfigSection::nodeDatabase());
nodeDb.set("pinned_type", "rwdb");
nodeDb.set("online_delete", "256");
return cfg;
}
// Make a NodeObject with deterministic but unique-per-call hash so
// we can store it and fetch it back.
static std::shared_ptr<NodeObject>
makeObject(NodeObjectType type, std::uint8_t marker)
{
Blob data(64, marker);
// Hash is just a stable function of (type, marker) so each
// (type, marker) pair has a distinct key.
Serializer s;
s.add32(static_cast<std::uint32_t>(type));
s.add8(marker);
auto const hash = sha512Half(makeSlice(s.peekData()));
return NodeObject::createObject(type, std::move(data), hash);
}
// Borrow the running app's nodestore. With pinned_type configured
// it must be a DatabasePinnedImp; otherwise the test environment
// is set up wrong and we can't proceed.
NodeStore::DatabasePinnedImp*
getPinnedDb(jtx::Env& env)
{
auto* db = dynamic_cast<NodeStore::DatabasePinnedImp*>(
&env.app().getNodeStore());
BEAST_EXPECT(db != nullptr);
return db;
}
void
testStoreRoutingByType()
{
// The architectural core of DatabasePinnedImp: store() routes
// by NodeObjectType. pinned* types go to the persistent
// backend (which never rotates); everything else goes to the
// rotating backend (which is rotated by online_delete). If
// routing breaks, pinned data ends up rotating away and the
// whole feature fails silently.
testcase("store routing by NodeObjectType");
using namespace jtx;
Env env{*this, pinnedEnvconfig()};
auto* db = getPinnedDb(env);
if (!db)
return;
auto const pinnedBefore = db->pinnedStoreCount();
auto const hotBefore = db->hotStoreCount();
// Pinned types: should each increment pinnedStoreCount.
// The store() implementation also reduces them to their hot
// equivalents before serialization (single-byte on-disk type
// field), but the routing decision happens first.
for (auto const type :
{pinnedLEDGER, pinnedACCOUNT_NODE, pinnedTRANSACTION_NODE})
{
db->store(
type,
Blob(64, 0xAB),
sha512Half(
makeSlice(std::string{"pin-"} + std::to_string(type))),
/*ledgerSeq=*/100);
}
// Hot types: should each increment hotStoreCount.
for (auto const type :
{hotLEDGER, hotACCOUNT_NODE, hotTRANSACTION_NODE})
{
db->store(
type,
Blob(64, 0xCD),
sha512Half(
makeSlice(std::string{"hot-"} + std::to_string(type))),
/*ledgerSeq=*/100);
}
// 3 pinned stores routed to persistent_, 3 hot to rotating_.
BEAST_EXPECT(db->pinnedStoreCount() - pinnedBefore == 3u);
BEAST_EXPECT(db->hotStoreCount() - hotBefore == 3u);
}
void
testStoreFetchRoundTrip()
{
// Verifies that data routed to either backend is retrievable
// through the unified Database interface (fetchNodeObject
// returns the right object regardless of which backend it
// landed in). This is the read-side counterpart to the
// routing test — confirms callers don't need to know about
// the dual-backend split.
testcase("store/fetch round-trip across both backends");
using namespace jtx;
Env env{*this, pinnedEnvconfig()};
auto& nodeStore = env.app().getNodeStore();
// Store one object of each type with distinct content so the
// fetched data is unambiguously the one we stored.
struct Item
{
NodeObjectType type;
std::uint8_t marker;
std::shared_ptr<NodeObject> obj;
};
std::vector<Item> items{
{pinnedLEDGER, 0x01, nullptr},
{pinnedACCOUNT_NODE, 0x02, nullptr},
{pinnedTRANSACTION_NODE, 0x03, nullptr},
{hotLEDGER, 0x04, nullptr},
{hotACCOUNT_NODE, 0x05, nullptr},
{hotTRANSACTION_NODE, 0x06, nullptr},
};
for (auto& it : items)
{
it.obj = makeObject(it.type, it.marker);
// Database::store takes (type, data, hash, ledgerSeq).
// Pass a copy of the data because store() takes Blob&&.
nodeStore.store(
it.type,
Blob(it.obj->getData()),
it.obj->getHash(),
/*ledgerSeq=*/100);
}
for (auto const& it : items)
{
auto const fetched =
nodeStore.fetchNodeObject(it.obj->getHash(), 100);
BEAST_EXPECT(fetched != nullptr);
if (!fetched)
continue;
BEAST_EXPECT(fetched->getData() == it.obj->getData());
}
}
public:
void
run() override
{
testStoreRoutingByType();
testStoreFetchRoundTrip();
}
};
BEAST_DEFINE_TESTSUITE(DatabasePinned, nodestore, ripple);
} // namespace test
} // namespace ripple
+1
View File
@@ -69,6 +69,7 @@ class Hooks_test : public beast::unit_test::suite
sfHookAccount,
sfEmittedTxn,
sfHook,
sfHookDefinition,
sfHookParameter,
sfHookGrant,
sfEmitDetails,
-119
View File
@@ -541,124 +541,6 @@ public:
}
}
void
testRangeDeletionOperations(
std::string const& backend,
std::unique_ptr<Config> config)
{
testcase("Range deletion operations - " + backend);
using namespace test::jtx;
config->LEDGER_HISTORY = 1000;
Env env(*this, std::move(config));
auto& db = env.app().getRelationalDatabase();
Account alice("alice");
Account bob("bob");
env.fund(XRP(10000), alice, bob);
env.close();
auto* sqliteDb = getInterface(db);
BEAST_EXPECT(sqliteDb != nullptr);
if (!sqliteDb)
return;
// Create 5 ledgers with transactions
for (int i = 0; i < 5; ++i)
{
env(pay(alice, bob, XRP(100 + i)));
env.close();
}
auto minSeq = sqliteDb->getMinLedgerSeq();
auto maxSeq = sqliteDb->getMaxLedgerSeq();
BEAST_EXPECT(minSeq && maxSeq);
if (!minSeq || !maxSeq)
return;
auto initialTxCount = sqliteDb->getTransactionCount();
auto initialAcctTxCount = sqliteDb->getAccountTransactionCount();
// Use a range that covers ledgers with transactions
// (funded ledger + 5 payment ledgers = sequences 3..7 typically)
LedgerIndex rangeStart = *minSeq;
LedgerIndex rangeEnd = *maxSeq;
// Test deleteTransactionsInRange with limit — ensures forward
// progress (no infinite loop on partial deletes)
{
std::size_t totalDeleted = 0;
std::size_t iterations = 0;
std::size_t const limit = 2;
while (true)
{
auto deleted = sqliteDb->deleteTransactionsInRange(
rangeStart, rangeEnd, limit);
if (deleted == 0)
break;
totalDeleted += deleted;
++iterations;
// Safety: prevent infinite loop in case of bug
if (iterations > initialTxCount + 1)
{
fail(
"deleteTransactionsInRange: too many iterations, "
"possible infinite loop");
break;
}
}
BEAST_EXPECT(totalDeleted > 0);
BEAST_EXPECT(totalDeleted <= initialTxCount);
auto afterTxCount = sqliteDb->getTransactionCount();
BEAST_EXPECT(afterTxCount == initialTxCount - totalDeleted);
}
// Test deleteAccountTransactionsInRange with limit
{
std::size_t totalDeleted = 0;
std::size_t iterations = 0;
std::size_t const limit = 2;
while (true)
{
auto deleted = sqliteDb->deleteAccountTransactionsInRange(
rangeStart, rangeEnd, limit);
if (deleted == 0)
break;
totalDeleted += deleted;
++iterations;
if (iterations > initialAcctTxCount + 1)
{
fail(
"deleteAccountTransactionsInRange: too many "
"iterations, possible infinite loop");
break;
}
}
BEAST_EXPECT(totalDeleted > 0);
BEAST_EXPECT(totalDeleted <= initialAcctTxCount);
auto afterAcctTxCount = sqliteDb->getAccountTransactionCount();
BEAST_EXPECT(afterAcctTxCount == initialAcctTxCount - totalDeleted);
}
// Test deleteLedgersInRange
auto initialLedgerCount = sqliteDb->getLedgerCountMinMax();
auto ledgersDeleted =
sqliteDb->deleteLedgersInRange(rangeStart + 1, rangeEnd - 1);
BEAST_EXPECT(ledgersDeleted > 0);
auto afterLedgerCount = sqliteDb->getLedgerCountMinMax();
BEAST_EXPECT(
afterLedgerCount.numberOfRows ==
initialLedgerCount.numberOfRows - ledgersDeleted);
// Test range deletion with no matching range
auto emptyDeleted = sqliteDb->deleteLedgersInRange(999999, 999999);
BEAST_EXPECT(emptyDeleted == 0);
}
void
testDatabaseSpaceOperations(
std::string const& backend,
@@ -862,7 +744,6 @@ public:
testAccountTransactionOperations(backend, makeConfig(backend));
testAccountTransactionPaging(backend, makeConfig(backend));
testDeletionOperations(backend, makeConfig(backend));
testRangeDeletionOperations(backend, makeConfig(backend));
testDatabaseSpaceOperations(backend, makeConfig(backend));
testTransactionMinLedgerSeq(backend, makeConfig(backend));
}
+1 -2
View File
@@ -347,8 +347,7 @@ public:
run() override
{
using namespace jtx;
auto const all =
supported_amendments() | featureAMM | featureAMMClawback;
auto const all = supported_amendments();
testErrors();
testSimpleRpc();
testVoteAndBid(all);
+2 -2
View File
@@ -577,8 +577,8 @@ public:
Env env(
*this,
features | featureAMM | featureXChainBridge | featureDID |
featureCredentials | featurePermissionedDomains);
features | featureXChainBridge | featureDID | featureCredentials |
featurePermissionedDomains);
// Make a lambda we can use to get "account_objects" easily.
auto acctObjs = [&env](
-322
View File
@@ -1,322 +0,0 @@
//------------------------------------------------------------------------------
/*
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 <xrpld/rpc/detail/CatalogueStream.h>
#include <xrpld/shamap/SHAMap.h>
#include <xrpld/shamap/SHAMapItem.h>
#include <xrpld/shamap/SHAMapTreeNode.h>
#include <xrpl/beast/unit_test.h>
#include <boost/iostreams/device/array.hpp>
#include <cstdint>
#include <cstring>
#include <vector>
namespace ripple {
namespace test {
class CatalogueStream_test : public beast::unit_test::suite
{
// The deserialize* public API takes `CatalogueInputStream&` which is just
// `boost::iostreams::filtering_istream`. So no production-code change is
// needed for fault-injection — we hand-build a byte vector that violates
// the wire format, push it through an `array_source`, and feed it to
// deserialize. Each test below targets one error branch in
// CatalogueStream.cpp.
//
// Wire format reminder (from serializeSHAMapToStream):
// leaf : [type:u8 ][key:32][size:u32][data:size]
// remove: [254 :u8 ][key:32]
// end : [255 :u8 ]
using Bytes = std::vector<std::uint8_t>;
static void
appendByte(Bytes& b, std::uint8_t v)
{
b.push_back(v);
}
static void
appendBytes(Bytes& b, void const* p, std::size_t n)
{
auto const* src = reinterpret_cast<std::uint8_t const*>(p);
b.insert(b.end(), src, src + n);
}
static void
appendKey(Bytes& b, std::uint8_t fillByte = 0xAA)
{
b.insert(b.end(), 32, fillByte);
}
static void
appendU32(Bytes& b, std::uint32_t v)
{
// CatalogueStream writes raw bytes — no endian conversion. Match.
appendBytes(b, &v, sizeof(v));
}
// Build a fresh filtering_istream over `bytes`. The array_source is a
// value type stored by the filter chain, so `bytes` must outlive it.
static void
makeStream(Bytes const& bytes, RPC::CatalogueInputStream& out)
{
boost::iostreams::array_source src(
reinterpret_cast<char const*>(bytes.data()), bytes.size());
out.push(src);
}
// Construct an empty SHAMap of the requested type using the test app's
// node family. Same pattern used in src/test/consensus/UNLReport_test.cpp.
static std::shared_ptr<SHAMap>
makeMap(jtx::Env& env, SHAMapType type)
{
return std::make_shared<SHAMap>(type, env.app().getNodeFamily());
}
public:
void
testTruncatedKey()
{
testcase("deserialize: truncated read of key");
jtx::Env env(*this);
auto map = makeMap(env, SHAMapType::STATE);
// 1 byte (the node type) and nothing after — read of the 32-byte key
// hits EOF inside deserializeLeaf, triggers the "stream stopped while
// trying to read key" branch.
Bytes b;
appendByte(b, tnACCOUNT_STATE);
RPC::CatalogueInputStream stream;
makeStream(b, stream);
BEAST_EXPECT(!RPC::deserializeStateMapFromStream(
*map, stream, hotACCOUNT_NODE, env.app().journal("Test")));
}
void
testTruncatedSize()
{
testcase("deserialize: truncated read of size");
jtx::Env env(*this);
auto map = makeMap(env, SHAMapType::TRANSACTION);
// type + key but no 4-byte size — triggers the "stream stopped while
// trying to read size" branch. Use tx map so the type byte isn't a
// remove (which would short-circuit before the size read).
Bytes b;
appendByte(b, tnTRANSACTION_MD);
appendKey(b);
RPC::CatalogueInputStream stream;
makeStream(b, stream);
BEAST_EXPECT(!RPC::deserializeTxMapFromStream(
*map, stream, hotTRANSACTION_NODE, env.app().journal("Test")));
}
void
testSizeTooLarge()
{
testcase("deserialize: size > 1 GiB rejected");
jtx::Env env(*this);
auto map = makeMap(env, SHAMapType::STATE);
// type + key + size of 2 GiB — bails before any data read attempt.
Bytes b;
appendByte(b, tnACCOUNT_STATE);
appendKey(b);
appendU32(b, 2u * 1024u * 1024u * 1024u);
RPC::CatalogueInputStream stream;
makeStream(b, stream);
BEAST_EXPECT(!RPC::deserializeStateMapFromStream(
*map, stream, hotACCOUNT_NODE, env.app().journal("Test")));
}
void
testTruncatedData()
{
testcase("deserialize: truncated read of data payload");
jtx::Env env(*this);
auto map = makeMap(env, SHAMapType::STATE);
// type + key + size=10 + only 5 bytes — hits the "Unexpected EOF
// while reading data" branch.
Bytes b;
appendByte(b, tnACCOUNT_STATE);
appendKey(b);
appendU32(b, 10);
for (int i = 0; i < 5; ++i)
appendByte(b, 0x42);
RPC::CatalogueInputStream stream;
makeStream(b, stream);
BEAST_EXPECT(!RPC::deserializeStateMapFromStream(
*map, stream, hotACCOUNT_NODE, env.app().journal("Test")));
}
void
testRemoveDisallowedInTxMap()
{
testcase(
"deserialize: remove sentinel rejected when allowRemoval=false");
jtx::Env env(*this);
auto map = makeMap(env, SHAMapType::TRANSACTION);
// tx maps don't accept tnREMOVE — the deserialize for tx maps calls
// the inner helper with allowRemoval=false. We send a remove record
// and expect rejection at "unexpected removal in this map type".
Bytes b;
appendByte(b, tnREMOVE);
appendKey(b);
RPC::CatalogueInputStream stream;
makeStream(b, stream);
BEAST_EXPECT(!RPC::deserializeTxMapFromStream(
*map, stream, hotTRANSACTION_NODE, env.app().journal("Test")));
}
void
testRemoveOfAbsentKey()
{
testcase("deserialize: remove of absent key rejected");
jtx::Env env(*this);
auto map = makeMap(env, SHAMapType::STATE);
// State map allows tnREMOVE, but the key has to be present first.
// Empty map + remove of any key → "key is already absent" branch.
Bytes b;
appendByte(b, tnREMOVE);
appendKey(b, 0x77);
RPC::CatalogueInputStream stream;
makeStream(b, stream);
BEAST_EXPECT(!RPC::deserializeStateMapFromStream(
*map, stream, hotACCOUNT_NODE, env.app().journal("Test")));
}
void
testMissingTerminator()
{
testcase("deserialize: EOF before terminal sentinel");
jtx::Env env(*this);
auto map = makeMap(env, SHAMapType::STATE);
// One valid leaf, then EOF (no tnTERMINAL). The outer loop exits via
// stream.eof() with lastParsed = tnACCOUNT_STATE (not tnTERMINAL),
// so the "Unexpected EOF, terminal node not found" branch fires.
// Payload must be >= 12 bytes — SHAMapLeafNode asserts that for
// state-tree leaves.
Bytes b;
appendByte(b, tnACCOUNT_STATE);
appendKey(b, 0x11);
appendU32(b, 16);
for (int i = 0; i < 16; ++i)
appendByte(b, 0xCC);
RPC::CatalogueInputStream stream;
makeStream(b, stream);
BEAST_EXPECT(!RPC::deserializeStateMapFromStream(
*map, stream, hotACCOUNT_NODE, env.app().journal("Test")));
}
void
testEmptyStreamAcceptsTerminator()
{
testcase("deserialize: bare terminator → empty map, success");
jtx::Env env(*this);
auto map = makeMap(env, SHAMapType::STATE);
Bytes b;
appendByte(b, tnTERMINAL);
RPC::CatalogueInputStream stream;
makeStream(b, stream);
BEAST_EXPECT(RPC::deserializeStateMapFromStream(
*map, stream, hotACCOUNT_NODE, env.app().journal("Test")));
}
void
testHappyPathRoundTrip()
{
testcase("deserialize: valid leaf + terminator → success");
jtx::Env env(*this);
auto map = makeMap(env, SHAMapType::STATE);
// type + key + size=16 + 16 data bytes + terminator. Confirms the
// happy path through addGiveItem and flushDirty. Size must be >= 12
// — SHAMapLeafNode asserts that for state-tree leaves.
Bytes b;
appendByte(b, tnACCOUNT_STATE);
appendKey(b, 0x55);
appendU32(b, 16);
for (int i = 0; i < 16; ++i)
appendByte(b, static_cast<std::uint8_t>(i));
appendByte(b, tnTERMINAL);
RPC::CatalogueInputStream stream;
makeStream(b, stream);
BEAST_EXPECT(RPC::deserializeStateMapFromStream(
*map, stream, hotACCOUNT_NODE, env.app().journal("Test")));
uint256 key;
std::memset(key.data(), 0x55, 32);
BEAST_EXPECT(map->hasItem(key));
}
void
run() override
{
testTruncatedKey();
testTruncatedSize();
testSizeTooLarge();
testTruncatedData();
testRemoveDisallowedInTxMap();
testRemoveOfAbsentKey();
testMissingTerminator();
testEmptyStreamAcceptsTerminator();
testHappyPathRoundTrip();
}
};
BEAST_DEFINE_TESTSUITE(CatalogueStream, rpc, ripple);
} // namespace test
} // namespace ripple
+18 -243
View File
@@ -19,103 +19,15 @@
#include <test/jtx.h>
#include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpld/core/ConfigSections.h>
#include <xrpl/beast/unit_test.h>
#include <xrpl/beast/utility/temp_dir.h>
#include <xrpl/protocol/jss.h>
#include <boost/filesystem.hpp>
#include <chrono>
#include <cstdlib>
#include <fstream>
#include <grpc/impl/codegen/compression_types.h>
#include <thread>
namespace ripple {
namespace {
// Parameters for polling ledger retrieval
struct LedgerRetryParams
{
test::jtx::Env& env;
std::uint32_t seq = 0;
std::chrono::milliseconds pollInterval = std::chrono::milliseconds(500);
std::chrono::milliseconds maxWaitTime = std::chrono::milliseconds(10000);
bool requireValidated = false;
};
// Poll for ledger availability with retry logic
// Returns nullptr if ledger cannot be retrieved within the timeout period
std::shared_ptr<Ledger const>
getLedgerWithRetry(const LedgerRetryParams& params)
{
auto start = std::chrono::steady_clock::now();
while (true)
{
// First try to get the hash for this sequence
auto hash = params.env.app().getLedgerMaster().getHashBySeq(params.seq);
if (hash.isNonZero())
{
// Hash found, now try to get the ledger
auto ledger =
params.env.app().getLedgerMaster().getLedgerByHash(hash);
if (ledger)
{
// If we don't require validation, or if it's already validated,
// return it
if (!params.requireValidated || ledger->info().validated)
{
return ledger;
}
// Otherwise continue polling until it becomes validated
}
}
// Check if we've exceeded the timeout
auto elapsed = std::chrono::steady_clock::now() - start;
if (elapsed >= params.maxWaitTime)
{
return nullptr;
}
// Wait before next attempt
std::this_thread::sleep_for(params.pollInterval);
}
}
// Options for catalogueEnvconfig. Use designated initializers at call sites
// so each test reads as a small diff against the default — easy to scan and
// trivially extensible (add fields here, no call-site churn).
// catalogueEnvconfig() // happy path
// catalogueEnvconfig({.withPinnedType = false}) // exercise guard
struct CatalogueEnvOpts
{
// Set [node_db] pinned_type=rwdb so catalogue_load passes its guard.
// false → omit pinned_type entirely (drives the rejection path).
bool withPinnedType = true;
// [node_db] online_delete value (lifetime of rotating store, in ledgers).
std::string onlineDelete = "256";
};
// envconfig with pinned_type set so catalogue_load passes the config guard.
// Uses rwdb for in-process speed (allowed in standalone).
inline std::unique_ptr<Config>
catalogueEnvconfig(CatalogueEnvOpts const& opts = {})
{
auto cfg = test::jtx::envconfig();
auto& nodeDb = cfg->section(ConfigSection::nodeDatabase());
if (opts.withPinnedType)
nodeDb.set("pinned_type", "rwdb");
nodeDb.set("online_delete", opts.onlineDelete);
return cfg;
}
} // anonymous namespace
#pragma pack(push, 1) // pack the struct tightly
struct TestCATLHeader
{
@@ -303,24 +215,7 @@ class Catalogue_test : public beast::unit_test::suite
{
testcase("catalogue_load: Invalid parameters");
using namespace test::jtx;
Env env{*this, catalogueEnvconfig(), features};
// Missing [node_db] pinned_type — catalogue_load rejects up front so
// loaded data isn't lost on the next rotation. Use a separate Env
// because this guard fires before any input_file parsing.
{
Env envNoPin{
*this, catalogueEnvconfig({.withPinnedType = false}), features};
Json::Value params{Json::objectValue};
params[jss::input_file] = "/tmp/anything.catl";
auto const result =
envNoPin.client().invoke("catalogue_load", params)[jss::result];
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(
result[jss::error_message].asString().find("pinned_type") !=
std::string::npos);
}
Env env{*this, envconfig(), features};
// No parameters
{
@@ -358,42 +253,6 @@ class Catalogue_test : public beast::unit_test::suite
BEAST_EXPECT(result[jss::error] == "internal");
BEAST_EXPECT(result[jss::status] == "error");
}
// Header has min_ledger > max_ledger
{
boost::filesystem::path tempDir =
boost::filesystem::temp_directory_path() /
boost::filesystem::unique_path();
boost::filesystem::create_directories(tempDir);
auto cataloguePath = (tempDir / "invalid-range.catl").string();
TestCATLHeader header;
header.min_ledger = 20;
header.max_ledger = 10;
header.version = 1;
header.network_id = env.app().config().NETWORK_ID;
header.filesize = sizeof(TestCATLHeader);
std::ofstream outfile(
cataloguePath.c_str(), std::ios::out | std::ios::binary);
BEAST_EXPECT(outfile.good());
outfile.write(
reinterpret_cast<char const*>(&header), sizeof(header));
outfile.close();
Json::Value params{Json::objectValue};
params[jss::input_file] = cataloguePath;
auto const result =
env.client().invoke("catalogue_load", params)[jss::result];
BEAST_EXPECT(result[jss::error] == "invalidParams");
BEAST_EXPECT(result[jss::status] == "error");
BEAST_EXPECT(
result[jss::error_message].asString().find(
"min_ledger must be <= max_ledger") != std::string::npos);
boost::filesystem::remove_all(tempDir);
}
}
void
@@ -403,25 +262,9 @@ class Catalogue_test : public beast::unit_test::suite
using namespace test::jtx;
// Create environment and test data
Env env{*this, catalogueEnvconfig(), nullptr, beast::severities::kNone};
Env env{*this, envconfig(), features};
prepareLedgerData(env, 5);
auto noop = [](test::jtx::Env& env,
std::string partition,
std::string severity) {
Json::Value params{Json::objectValue};
params[jss::severity] = severity;
params[jss::partition] = partition;
// env.client().invoke("log_level", params);
};
// Create journal for debugging
auto j = env.app().logs().journal("Catalogue_test");
noop(env, "CatalogueTools", "trace");
noop(env, "Catalogue_test", "trace");
JLOG(j.trace()) << "Test environment created, prepared ledger data";
// Store some key state information before catalogue creation
auto const sourceLedger = env.closed();
auto const bobKeylet = keylet::account(Account("bob").id());
@@ -431,9 +274,6 @@ class Catalogue_test : public beast::unit_test::suite
Account("bob").id(),
Currency(to_currency("EUR")));
JLOG(j.trace()) << "Source ledger seq: " << sourceLedger->info().seq
<< " hash: " << sourceLedger->info().hash;
// Get original state entries
auto const bobAcct = sourceLedger->read(bobKeylet);
auto const charlieAcct = sourceLedger->read(charlieKeylet);
@@ -462,10 +302,6 @@ class Catalogue_test : public beast::unit_test::suite
// First create a catalogue
uint32_t minLedger = 3;
uint32_t maxLedger = sourceLedger->info().seq;
JLOG(j.trace()) << "Creating catalogue from ledger " << minLedger
<< " to " << maxLedger;
{
Json::Value params{Json::objectValue};
params[jss::min_ledger] = minLedger;
@@ -475,43 +311,27 @@ class Catalogue_test : public beast::unit_test::suite
auto const result =
env.client().invoke("catalogue_create", params)[jss::result];
BEAST_EXPECT(result[jss::status] == jss::success);
JLOG(j.trace()) << "Catalogue created: " << result.toStyledString();
}
// Create a new environment for loading the catalogue
// We use a separate environment with incremented ports to avoid
// conflicts Note: The default RWDB backend works fine - the key insight
// is that pinned ledgers bypass the cache, so we need to poll for
// availability as the async publishAcqLedger jobs complete
// Create a new environment for loading with unique port
Env loadEnv{
*this,
catalogueEnvconfig(),
test::jtx::envconfig(),
features,
};
noop(loadEnv, "CatalogueTools", "trace");
noop(loadEnv, "Catalogue_test", "trace");
// Create journal for load environment
auto loadJ = loadEnv.app().logs().journal("Catalogue_test");
// Now load the catalogue
Json::Value params{Json::objectValue};
params[jss::input_file] = cataloguePath;
JLOG(loadJ.trace()) << "Loading catalogue from " << cataloguePath;
auto const result =
loadEnv.client().invoke("catalogue_load", params)[jss::result];
JLOG(loadJ.trace())
<< "Catalogue load result: " << result.toStyledString();
BEAST_EXPECT(result[jss::status] == jss::success);
BEAST_EXPECT(result[jss::ledger_min] == minLedger);
BEAST_EXPECT(result[jss::ledger_max] == maxLedger);
BEAST_EXPECT(result[jss::ledger_count] == (maxLedger - minLedger + 1));
// Verify complete_ledgers reflects loaded ledgers
auto const newCompleteLedgers =
loadEnv.app().getLedgerMaster().getCompleteLedgers();
@@ -526,36 +346,20 @@ class Catalogue_test : public beast::unit_test::suite
// Compare all ledgers from 3 to 16 inclusive
for (std::uint32_t seq = 3; seq <= 16; ++seq)
{
JLOG(j.trace()) << "Comparing ledger " << seq;
auto const sourceLedger =
env.app().getLedgerMaster().getLedgerByHash(
env.app().getLedgerMaster().getHashBySeq(seq));
// Get the source ledger (doesn't need to be validated)
auto const sourceLedger = getLedgerWithRetry(
{.env = env,
.seq = seq,
.pollInterval = std::chrono::milliseconds(100),
.maxWaitTime = std::chrono::milliseconds(5000),
.requireValidated = false});
// Get the loaded ledger (must be validated)
auto const loadedLedger = getLedgerWithRetry(
{.env = loadEnv,
.seq = seq,
.pollInterval = std::chrono::milliseconds(500),
.maxWaitTime = std::chrono::milliseconds(30000),
.requireValidated = true});
auto const loadedLedger =
loadEnv.app().getLedgerMaster().getLedgerByHash(
loadEnv.app().getLedgerMaster().getHashBySeq(seq));
if (!sourceLedger || !loadedLedger)
{
JLOG(j.trace())
<< "Failed to get ledger " << seq
<< " source: " << (sourceLedger ? "ok" : "missing")
<< " loaded: " << (loadedLedger ? "ok" : "missing");
BEAST_EXPECT(false); // Test failure
continue;
}
JLOG(j.trace()) << "Got both ledgers for seq " << seq;
// Check basic ledger properties
BEAST_EXPECT(sourceLedger->info().seq == loadedLedger->info().seq);
BEAST_EXPECT(
@@ -600,10 +404,6 @@ class Catalogue_test : public beast::unit_test::suite
std::size_t sourceCount = std::ranges::distance(sourceLedger->sles);
std::size_t loadedCount = std::ranges::distance(loadedLedger->sles);
JLOG(j.trace())
<< "Ledger " << seq << " SLE count - source: " << sourceCount
<< " loaded: " << loadedCount;
BEAST_EXPECT(sourceCount == loadedCount);
// Check existence of imported keylets
@@ -611,13 +411,6 @@ class Catalogue_test : public beast::unit_test::suite
{
auto const key = sle->key();
bool exists = loadedLedger->exists(keylet::unchecked(key));
if (!exists)
{
JLOG(j.trace())
<< "Ledger " << seq << " missing key: " << key;
}
BEAST_EXPECT(exists);
// If it exists, check the serialized form matches
@@ -628,13 +421,6 @@ class Catalogue_test : public beast::unit_test::suite
sle->add(s1);
loadedSle->add(s2);
bool serializedEqual = (s1.peekData() == s2.peekData());
if (!serializedEqual)
{
JLOG(j.trace())
<< "Ledger " << seq << " mismatch for key: " << key;
}
BEAST_EXPECT(serializedEqual);
}
}
@@ -643,20 +429,10 @@ class Catalogue_test : public beast::unit_test::suite
for (auto const& sle : loadedLedger->sles)
{
auto const key = sle->key();
bool exists = sourceLedger->exists(keylet::unchecked(key));
if (!exists)
{
JLOG(j.trace())
<< "Ledger " << seq << " extra key in loaded: " << key;
}
BEAST_EXPECT(exists);
BEAST_EXPECT(sourceLedger->exists(keylet::unchecked(key)));
}
}
JLOG(j.trace()) << "Ledger comparison complete";
auto const loadedBobAcct = loadedLedger->read(bobKeylet);
auto const loadedCharlieAcct = loadedLedger->read(charlieKeylet);
auto const loadedEurTrust = loadedLedger->read(eurTrustKeylet);
@@ -747,11 +523,10 @@ class Catalogue_test : public beast::unit_test::suite
// Try to load catalogue in environment with different network ID
Env env2{
*this,
[&]() {
auto cfg = catalogueEnvconfig();
envconfig([](std::unique_ptr<Config> cfg) {
cfg->NETWORK_ID = 456;
return cfg;
}(),
}),
features,
};
@@ -778,7 +553,7 @@ class Catalogue_test : public beast::unit_test::suite
// Create environment and test data
Env env{
*this,
catalogueEnvconfig(),
envconfig(),
features,
nullptr,
beast::severities::kDisabled,
@@ -875,7 +650,7 @@ class Catalogue_test : public beast::unit_test::suite
// Create environment and test data
Env env{
*this,
catalogueEnvconfig(),
envconfig(),
features,
nullptr,
beast::severities::kDisabled,
@@ -954,7 +729,7 @@ class Catalogue_test : public beast::unit_test::suite
using namespace test::jtx;
// Create environment and test data
Env env{*this, catalogueEnvconfig(), features};
Env env{*this, envconfig(), features};
prepareLedgerData(env, 5);
boost::filesystem::path tempDir =
@@ -1043,7 +818,7 @@ class Catalogue_test : public beast::unit_test::suite
using namespace test::jtx;
// Create environment
Env env{*this, catalogueEnvconfig(), features};
Env env{*this, envconfig(), features};
boost::filesystem::path tempDir =
boost::filesystem::temp_directory_path() /
+2 -34
View File
@@ -55,9 +55,9 @@ public:
using namespace test::jtx;
Env env(*this);
auto const result = env.rpc("server_definitions");
{
Env env(*this);
auto const result = env.rpc("server_definitions");
BEAST_EXPECT(!result[jss::result].isMember(jss::error));
BEAST_EXPECT(result[jss::result].isMember(jss::FIELDS));
BEAST_EXPECT(result[jss::result].isMember(jss::LEDGER_ENTRY_TYPES));
@@ -71,38 +71,6 @@ public:
BEAST_EXPECT(result[jss::result].isMember(jss::hash));
BEAST_EXPECT(result[jss::result][jss::status] == "success");
}
// check exception SFields
{
auto const fieldExists = [&](std::string name) {
for (auto& field : result[jss::result][jss::FIELDS])
{
if (field[0u].asString() == name)
{
return true;
}
}
return false;
};
BEAST_EXPECT(fieldExists("Generic"));
BEAST_EXPECT(fieldExists("Invalid"));
BEAST_EXPECT(fieldExists("ObjectEndMarker"));
BEAST_EXPECT(fieldExists("ArrayEndMarker"));
BEAST_EXPECT(fieldExists("taker_gets_funded"));
BEAST_EXPECT(fieldExists("taker_pays_funded"));
BEAST_EXPECT(fieldExists("hash"));
BEAST_EXPECT(fieldExists("index"));
}
// verify no duplicate field names in FIELDS array
{
std::set<std::string> fieldNames;
for (auto const& field : result[jss::result][jss::FIELDS])
{
auto const name = field[0u].asString();
BEAST_EXPECT(fieldNames.insert(name).second);
}
}
}
void
+5 -292
View File
@@ -3,297 +3,10 @@
#include <xrpld/app/misc/Transaction.h>
#include <xrpl/hook/Enum.h>
#include <cfenv>
namespace hook {
using HookReturnCode = hook_api::hook_return_code;
namespace hook_float {
using enum hook_api::hook_return_code;
// power of 10 LUT for fast integer math
static int64_t power_of_ten[19] = {
1LL,
10LL,
100LL,
1000LL,
10000LL,
100000LL,
1000000LL,
10000000LL,
100000000LL,
1000000000LL,
10000000000LL,
100000000000LL,
1000000000000LL,
10000000000000LL,
100000000000000LL,
1000000000000000LL, // 15
10000000000000000LL,
100000000000000000LL,
1000000000000000000LL,
};
using namespace hook_api;
static int64_t const minMantissa = 1000000000000000ull;
static int64_t const maxMantissa = 9999999999999999ull;
static int32_t const minExponent = -96;
static int32_t const maxExponent = 80;
inline Expected<int32_t, HookReturnCode>
get_exponent(int64_t float1)
{
using enum hook_api::hook_return_code;
if (float1 < 0)
return Unexpected(INVALID_FLOAT);
if (float1 == 0)
return 0u;
uint64_t float_in = (uint64_t)float1;
float_in >>= 54U;
float_in &= 0xFFU;
return static_cast<int32_t>(float_in) - 97;
}
inline Expected<uint64_t, HookReturnCode>
get_mantissa(int64_t float1)
{
using enum hook_api::hook_return_code;
if (float1 < 0)
return Unexpected(INVALID_FLOAT);
if (float1 == 0)
return 0ULL;
float1 -= ((((uint64_t)float1) >> 54U) << 54U);
return float1;
}
inline bool
is_negative(int64_t float1)
{
return ((float1 >> 62U) & 1ULL) == 0;
}
inline int64_t
invert_sign(int64_t float1)
{
int64_t r = (int64_t)(((uint64_t)float1) ^ (1ULL << 62U));
return r;
}
inline int64_t
set_sign(int64_t float1, bool set_negative)
{
bool neg = is_negative(float1);
if ((neg && set_negative) || (!neg && !set_negative))
return float1;
return invert_sign(float1);
}
inline Expected<uint64_t, HookReturnCode>
set_mantissa(int64_t float1, uint64_t mantissa)
{
using enum hook_api::hook_return_code;
if (mantissa > maxMantissa)
return Unexpected(MANTISSA_OVERSIZED);
if (mantissa < minMantissa)
return Unexpected(MANTISSA_UNDERSIZED);
return float1 - get_mantissa(float1).value() + mantissa;
}
inline Expected<uint64_t, HookReturnCode>
set_exponent(int64_t float1, int32_t exponent)
{
using enum hook_api::hook_return_code;
if (exponent > maxExponent)
return Unexpected(EXPONENT_OVERSIZED);
if (exponent < minExponent)
return Unexpected(EXPONENT_UNDERSIZED);
uint64_t exp = (exponent + 97);
exp <<= 54U;
float1 &= ~(0xFFLL << 54);
float1 += (int64_t)exp;
return float1;
}
inline Expected<uint64_t, HookReturnCode>
make_float(ripple::IOUAmount& amt)
{
using enum hook_api::hook_return_code;
int64_t man_out = amt.mantissa();
int64_t float_out = 0;
bool neg = man_out < 0;
if (neg)
man_out *= -1;
float_out = set_sign(float_out, neg);
auto const mantissa = set_mantissa(float_out, (uint64_t)man_out);
if (!mantissa)
// TODO: This change requires the amendment.
// return Unexpected(mantissa.error());
float_out = (int64_t)mantissa.error();
else
float_out = mantissa.value();
auto const exponent = set_exponent(float_out, amt.exponent());
if (!exponent)
return Unexpected(exponent.error());
float_out = exponent.value();
return float_out;
}
inline Expected<uint64_t, HookReturnCode>
make_float(uint64_t mantissa, int32_t exponent, bool neg)
{
using enum hook_api::hook_return_code;
if (mantissa == 0)
return 0ULL;
if (mantissa > maxMantissa)
return Unexpected(MANTISSA_OVERSIZED);
if (mantissa < minMantissa)
return Unexpected(MANTISSA_UNDERSIZED);
if (exponent > maxExponent)
return Unexpected(EXPONENT_OVERSIZED);
if (exponent < minExponent)
return Unexpected(EXPONENT_UNDERSIZED);
int64_t out = 0;
auto const m = set_mantissa(out, mantissa);
if (!m)
return Unexpected(m.error()); // LCOV_EXCL_LINE
out = m.value();
auto const e = set_exponent(out, exponent);
if (!e)
return Unexpected(e.error()); // LCOV_EXCL_LINE
out = e.value();
out = set_sign(out, neg);
return out;
}
/**
* This function normalizes the mantissa and exponent passed, if it can.
* It returns the XFL and mutates the supplied manitssa and exponent.
* If a negative mantissa is provided then the returned XFL has the negative
* flag set. If there is an overflow error return XFL_OVERFLOW. On underflow
* returns canonical 0
*/
template <typename T>
inline Expected<uint64_t, HookReturnCode>
normalize_xfl(T& man, int32_t& exp, bool neg = false)
{
if (man == 0)
return 0ULL;
if (man == std::numeric_limits<int64_t>::min())
man++;
constexpr bool sman = std::is_same<T, int64_t>::value;
static_assert(sman || std::is_same<T, uint64_t>());
if constexpr (sman)
{
if (man < 0)
{
man *= -1LL;
neg = true;
}
}
// mantissa order
std::feclearexcept(FE_ALL_EXCEPT);
int32_t mo = log10(man);
// defensively ensure log10 produces a sane result; we'll borrow the
// overflow error code if it didn't
if (std::fetestexcept(FE_INVALID))
return Unexpected(XFL_OVERFLOW); // LCOV_EXCL_LINE
int32_t adjust = 15 - mo;
if (adjust > 0)
{
// defensive check
if (adjust > 18)
return 0ULL; // LCOV_EXCL_LINE
man *= power_of_ten[adjust];
exp -= adjust;
}
else if (adjust < 0)
{
// defensive check
if (-adjust > 18)
return Unexpected(XFL_OVERFLOW); // LCOV_EXCL_LINE
man /= power_of_ten[-adjust];
exp -= adjust;
}
if (man == 0)
{
exp = 0;
return 0ULL;
}
// even after adjustment the mantissa can be outside the range by one place
// improving the math above would probably alleviate the need for these
// branches
if (man < minMantissa)
{
if (man == minMantissa - 1LL)
man += 1LL;
else
{
man *= 10LL;
exp--;
}
}
if (man > maxMantissa)
{
if (man == maxMantissa + 1LL)
man -= 1LL;
else
{
man /= 10LL;
exp++;
}
}
if (exp < minExponent)
{
man = 0;
exp = 0;
return 0ULL;
}
if (man == 0)
{
exp = 0;
return 0ULL;
}
if (exp > maxExponent)
return Unexpected(XFL_OVERFLOW);
auto const ret = make_float((uint64_t)man, exp, neg);
if constexpr (sman)
{
if (neg)
man *= -1LL;
}
if (!ret)
return Unexpected(ret.error());
return ret;
}
const int64_t float_one_internal =
make_float(1000000000000000ull, -15, false).value();
} // namespace hook_float
using namespace ripple;
using HookReturnCode = hook_api::hook_return_code;
using Bytes = std::vector<std::uint8_t>;
@@ -457,7 +170,7 @@ public:
Expected<ripple::uint256, HookReturnCode>
hook_hash(int32_t hook_no) const;
Expected<uint64_t, HookReturnCode>
Expected<int64_t, HookReturnCode>
hook_again() const;
Expected<Blob, HookReturnCode>
@@ -607,7 +320,7 @@ private:
bool modified) const;
// these are only used by get_stobject_length below
enum class STOParseErrorCode {
enum parse_error {
pe_unexpected_end = -1,
pe_unknown_type_early = -2, // detected early
pe_unknown_type_late = -3, // end of function
@@ -616,8 +329,8 @@ private:
};
inline Expected<
uint32_t,
STOParseErrorCode>
int32_t,
parse_error>
get_stobject_length(
unsigned char* start, // in - begin iterator
unsigned char* maxptr, // in - end iterator
-3
View File
@@ -15,7 +15,6 @@
#include <memory>
#include <optional>
#include <queue>
#include <utility>
#include <vector>
#include <wasmedge/wasmedge.h>
@@ -175,8 +174,6 @@ struct HookResult
false; // hook_again allows strong pre-apply to nominate
// additional weak post-apply execution
std::shared_ptr<STObject const> provisionalMeta;
std::set<std::pair<AccountID, uint256 /* namespace */>>
foreignStateGrantCache; // add found grants here to avoid rechecking
};
class HookExecutor;
+359 -109
View File
@@ -5,8 +5,285 @@
#include <xrpld/app/ledger/TransactionMaster.h>
#include <xrpld/app/tx/detail/Import.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <cfenv>
namespace hook {
namespace hook_float {
// power of 10 LUT for fast integer math
static int64_t power_of_ten[19] = {
1LL,
10LL,
100LL,
1000LL,
10000LL,
100000LL,
1000000LL,
10000000LL,
100000000LL,
1000000000LL,
10000000000LL,
100000000000LL,
1000000000000LL,
10000000000000LL,
100000000000000LL,
1000000000000000LL, // 15
10000000000000000LL,
100000000000000000LL,
1000000000000000000LL,
};
using namespace hook_api;
static int64_t const minMantissa = 1000000000000000ull;
static int64_t const maxMantissa = 9999999999999999ull;
static int32_t const minExponent = -96;
static int32_t const maxExponent = 80;
inline Expected<int32_t, HookReturnCode>
get_exponent(int64_t float1)
{
if (float1 < 0)
return Unexpected(INVALID_FLOAT);
if (float1 == 0)
return 0;
uint64_t float_in = (uint64_t)float1;
float_in >>= 54U;
float_in &= 0xFFU;
return ((int32_t)float_in) - 97;
}
inline Expected<uint64_t, HookReturnCode>
get_mantissa(int64_t float1)
{
if (float1 < 0)
return Unexpected(INVALID_FLOAT);
if (float1 == 0)
return 0;
float1 -= ((((uint64_t)float1) >> 54U) << 54U);
return float1;
}
inline bool
is_negative(int64_t float1)
{
return ((float1 >> 62U) & 1ULL) == 0;
}
inline int64_t
invert_sign(int64_t float1)
{
int64_t r = (int64_t)(((uint64_t)float1) ^ (1ULL << 62U));
return r;
}
inline int64_t
set_sign(int64_t float1, bool set_negative)
{
bool neg = is_negative(float1);
if ((neg && set_negative) || (!neg && !set_negative))
return float1;
return invert_sign(float1);
}
inline Expected<uint64_t, HookReturnCode>
set_mantissa(int64_t float1, uint64_t mantissa)
{
if (mantissa > maxMantissa)
return Unexpected(MANTISSA_OVERSIZED);
if (mantissa < minMantissa)
return Unexpected(MANTISSA_UNDERSIZED);
return float1 - get_mantissa(float1).value() + mantissa;
}
inline Expected<uint64_t, HookReturnCode>
set_exponent(int64_t float1, int32_t exponent)
{
if (exponent > maxExponent)
return Unexpected(EXPONENT_OVERSIZED);
if (exponent < minExponent)
return Unexpected(EXPONENT_UNDERSIZED);
uint64_t exp = (exponent + 97);
exp <<= 54U;
float1 &= ~(0xFFLL << 54);
float1 += (int64_t)exp;
return float1;
}
inline Expected<uint64_t, HookReturnCode>
make_float(ripple::IOUAmount& amt)
{
int64_t man_out = amt.mantissa();
int64_t float_out = 0;
bool neg = man_out < 0;
if (neg)
man_out *= -1;
float_out = set_sign(float_out, neg);
auto const mantissa = set_mantissa(float_out, (uint64_t)man_out);
if (!mantissa)
// TODO: This change requires the amendment.
// return Unexpected(mantissa.error());
float_out = mantissa.error();
else
float_out = mantissa.value();
auto const exponent = set_exponent(float_out, amt.exponent());
if (!exponent)
return Unexpected(exponent.error());
float_out = exponent.value();
return float_out;
}
inline Expected<uint64_t, HookReturnCode>
make_float(uint64_t mantissa, int32_t exponent, bool neg)
{
if (mantissa == 0)
return 0;
if (mantissa > maxMantissa)
return Unexpected(MANTISSA_OVERSIZED);
if (mantissa < minMantissa)
return Unexpected(MANTISSA_UNDERSIZED);
if (exponent > maxExponent)
return Unexpected(EXPONENT_OVERSIZED);
if (exponent < minExponent)
return Unexpected(EXPONENT_UNDERSIZED);
int64_t out = 0;
auto const m = set_mantissa(out, mantissa);
if (!m)
return m.error();
out = m.value();
auto const e = set_exponent(out, exponent);
if (!e)
return e.error();
out = e.value();
out = set_sign(out, neg);
return out;
}
/**
* This function normalizes the mantissa and exponent passed, if it can.
* It returns the XFL and mutates the supplied manitssa and exponent.
* If a negative mantissa is provided then the returned XFL has the negative
* flag set. If there is an overflow error return XFL_OVERFLOW. On underflow
* returns canonical 0
*/
template <typename T>
inline Expected<uint64_t, HookReturnCode>
normalize_xfl(T& man, int32_t& exp, bool neg = false)
{
if (man == 0)
return 0;
if (man == std::numeric_limits<int64_t>::min())
man++;
constexpr bool sman = std::is_same<T, int64_t>::value;
static_assert(sman || std::is_same<T, uint64_t>());
if constexpr (sman)
{
if (man < 0)
{
man *= -1LL;
neg = true;
}
}
// mantissa order
std::feclearexcept(FE_ALL_EXCEPT);
int32_t mo = log10(man);
// defensively ensure log10 produces a sane result; we'll borrow the
// overflow error code if it didn't
if (std::fetestexcept(FE_INVALID))
return Unexpected(XFL_OVERFLOW);
int32_t adjust = 15 - mo;
if (adjust > 0)
{
// defensive check
if (adjust > 18)
return 0;
man *= power_of_ten[adjust];
exp -= adjust;
}
else if (adjust < 0)
{
// defensive check
if (-adjust > 18)
return Unexpected(XFL_OVERFLOW);
man /= power_of_ten[-adjust];
exp -= adjust;
}
if (man == 0)
{
exp = 0;
return 0;
}
// even after adjustment the mantissa can be outside the range by one place
// improving the math above would probably alleviate the need for these
// branches
if (man < minMantissa)
{
if (man == minMantissa - 1LL)
man += 1LL;
else
{
man *= 10LL;
exp--;
}
}
if (man > maxMantissa)
{
if (man == maxMantissa + 1LL)
man -= 1LL;
else
{
man /= 10LL;
exp++;
}
}
if (exp < minExponent)
{
man = 0;
exp = 0;
return 0;
}
if (man == 0)
{
exp = 0;
return 0;
}
if (exp > maxExponent)
return Unexpected(XFL_OVERFLOW);
auto const ret = make_float((uint64_t)man, exp, neg);
if constexpr (sman)
{
if (neg)
man *= -1LL;
}
if (!ret)
return ret.error();
return ret;
}
const int64_t float_one_internal =
make_float(1000000000000000ull, -15, false).value();
} // namespace hook_float
using namespace ripple;
using namespace hook_float;
@@ -901,7 +1178,7 @@ HookAPI::etxn_details(uint8_t* out_ptr) const
auto hash = etxn_nonce();
if (!hash.has_value())
return Unexpected(INTERNAL_ERROR);
return INTERNAL_ERROR;
memcpy(out, hash->data(), 32);
@@ -996,7 +1273,7 @@ HookAPI::float_set(int32_t exponent, int64_t mantissa) const
{
if (normalized.error() == XFL_OVERFLOW)
return Unexpected(INVALID_FLOAT);
return Unexpected(normalized.error());
return normalized.error();
}
if (normalized.value() == 0)
return Unexpected(INVALID_FLOAT);
@@ -1044,7 +1321,7 @@ HookAPI::float_mulratio(
auto const result = make_float((uint64_t)man1, exp1, is_negative(float1));
if (!result)
return Unexpected(result.error());
return result.error();
return result;
}
@@ -1655,7 +1932,7 @@ HookAPI::hook_hash(int32_t hook_no) const
return hook.getFieldH256(sfHookHash);
}
Expected<uint64_t, HookReturnCode>
Expected<int64_t, HookReturnCode>
HookAPI::hook_again() const
{
if (hookCtx.result.executeAgainAsWeak)
@@ -1664,7 +1941,7 @@ HookAPI::hook_again() const
if (hookCtx.result.isStrong)
{
hookCtx.result.executeAgainAsWeak = true;
return 1ULL;
return 1;
}
return Unexpected(PREREQUISITE_NOT_MET);
@@ -1920,114 +2197,88 @@ HookAPI::state_foreign_set(
if (hookCtx.result.foreignStateSetDisabled)
return Unexpected(PREVIOUS_FAILURE_PREVENTS_RETRY);
bool const hasFix = hookCtx.applyCtx.view().rules().enabled(fixHookMap);
if (!hasFix)
// first check if we've already modified this state
auto cacheEntry = lookup_state_cache(account, ns, key);
if (cacheEntry && cacheEntry->get().first)
{
// first check if we've already modified this state
auto cacheEntry = lookup_state_cache(account, ns, key);
if (cacheEntry && cacheEntry->get().first)
{
// if a cache entry already exists and it has already been modified
// don't check grants again
if (auto ret = set_state_cache(account, ns, key, data, true);
!ret.has_value())
return Unexpected(ret.error());
// if a cache entry already exists and it has already been modified
// don't check grants again
if (auto ret = set_state_cache(account, ns, key, data, true);
!ret.has_value())
return Unexpected(ret.error());
return data.size();
}
return data.size();
}
// check if we've used a grant to modify this state entry before, if not
// look up possible grants
if (!hasFix ||
hookCtx.result.foreignStateGrantCache.find({account, ns}) ==
hookCtx.result.foreignStateGrantCache.end())
// cache miss or cache was present but entry was not marked as previously
// modified therefore before continuing we need to check grants
auto const sle =
hookCtx.applyCtx.view().read(ripple::keylet::hook(account));
if (!sle)
return Unexpected(INTERNAL_ERROR);
bool found_auth = false;
// we do this by iterating the hooks installed on the foreign account and in
// turn their grants and namespaces
auto const& hooks = sle->getFieldArray(sfHooks);
for (auto const& hookObj : hooks)
{
auto const sle =
hookCtx.applyCtx.view().read(ripple::keylet::hook(account));
// skip blank entries
if (!hookObj.isFieldPresent(sfHookHash))
continue;
if (!sle)
if (!hookObj.isFieldPresent(sfHookGrants))
continue;
auto const& hookGrants = hookObj.getFieldArray(sfHookGrants);
if (hookGrants.size() < 1)
continue;
// the grant allows the hook to modify the granter's namespace only
if (hookObj.isFieldPresent(sfHookNamespace))
{
if (hasFix)
{
hookCtx.result.foreignStateSetDisabled = true;
return Unexpected(NOT_AUTHORIZED);
}
return Unexpected(INTERNAL_ERROR);
if (hookObj.getFieldH256(sfHookNamespace) != ns)
continue;
}
else
{
// fetch the hook definition
auto const def =
hookCtx.applyCtx.view().read(ripple::keylet::hookDefinition(
hookObj.getFieldH256(sfHookHash)));
if (!def) // should never happen except in a rare race condition
continue;
if (def->getFieldH256(sfHookNamespace) != ns)
continue;
}
// RH TODO: test this code path more completely
bool found_auth = false;
// we do this by iterating the hooks installed on the foreign account
// and in turn their grants and namespaces
auto const& hooks = sle->getFieldArray(sfHooks);
for (auto const& hookObj : hooks)
// this is expensive search so we'll disallow after one failed attempt
for (auto const& hookGrantObj : hookGrants)
{
// skip blank entries
if (!hookObj.isFieldPresent(sfHookHash))
continue;
bool hasAuthorizedField = hookGrantObj.isFieldPresent(sfAuthorize);
if (!hookObj.isFieldPresent(sfHookGrants))
continue;
auto const& hookGrants = hookObj.getFieldArray(sfHookGrants);
if (hookGrants.size() < 1)
continue;
// the grant allows the hook to modify the granter's namespace only
if (hookObj.isFieldPresent(sfHookNamespace))
if (hookGrantObj.getFieldH256(sfHookHash) ==
hookCtx.result.hookHash &&
(!hasAuthorizedField ||
hookGrantObj.getAccountID(sfAuthorize) ==
hookCtx.result.account))
{
if (hookObj.getFieldH256(sfHookNamespace) != ns)
continue;
}
else
{
// fetch the hook definition
auto const def =
hookCtx.applyCtx.view().read(ripple::keylet::hookDefinition(
hookObj.getFieldH256(sfHookHash)));
if (!def) // should never happen except in a rare race
// condition
continue;
if (def->getFieldH256(sfHookNamespace) != ns)
continue;
}
// this is expensive search so we'll disallow after one failed
// attempt
for (auto const& hookGrantObj : hookGrants)
{
bool hasAuthorizedField =
hookGrantObj.isFieldPresent(sfAuthorize);
if (hookGrantObj.getFieldH256(sfHookHash) ==
hookCtx.result.hookHash &&
(!hasAuthorizedField ||
hookGrantObj.getAccountID(sfAuthorize) ==
hookCtx.result.account))
{
found_auth = true;
break;
}
}
if (found_auth)
found_auth = true;
break;
}
}
if (!found_auth)
{
// hook only gets one attempt
hookCtx.result.foreignStateSetDisabled = true;
return Unexpected(NOT_AUTHORIZED);
}
if (found_auth)
break;
}
// add the grant to the cache
hookCtx.result.foreignStateGrantCache.emplace(account, ns);
if (!found_auth)
{
// hook only gets one attempt
hookCtx.result.foreignStateSetDisabled = true;
return Unexpected(NOT_AUTHORIZED);
}
if (auto ret = set_state_cache(account, ns, key, data, true);
@@ -2356,7 +2607,7 @@ HookAPI::slot_float(uint32_t slot_no) const
normalized = ret.value();
}
if (normalized == (int64_t)EXPONENT_UNDERSIZED)
if (normalized == EXPONENT_UNDERSIZED)
/* exponent undersized (underflow) */
return 0; // return 0 in this case
return normalized;
@@ -2404,7 +2655,7 @@ HookAPI::meta_slot(uint32_t slot_into) const
Expected<std::pair<uint32_t, uint32_t>, HookReturnCode>
HookAPI::xpop_slot(uint32_t slot_into_tx, uint32_t slot_into_meta) const
{
if (hookCtx.applyCtx.tx.getTxnType() != ttIMPORT)
if (hookCtx.applyCtx.tx.getFieldU16(sfTransactionType) != ttIMPORT)
return Unexpected(PREREQUISITE_NOT_MET);
if (slot_into_tx > hook_api::max_slots ||
@@ -2883,8 +3134,8 @@ HookAPI::set_state_cache(
// including header bytes (and footer bytes in the event of array or object)
// negative indicates error
inline Expected<
uint32_t,
HookAPI::STOParseErrorCode>
int32_t,
HookAPI::parse_error>
HookAPI::get_stobject_length(
unsigned char* start, // in - begin iterator
unsigned char* maxptr, // in - end iterator
@@ -2897,7 +3148,6 @@ HookAPI::get_stobject_length(
int recursion_depth) // used internally
const
{
using enum HookAPI::STOParseErrorCode;
if (recursion_depth > 10)
return Unexpected(pe_excessive_nesting);
@@ -2906,7 +3156,7 @@ HookAPI::get_stobject_length(
: STI_VECTOR256;
if (type > max_sti_type)
return Unexpected(pe_unknown_type_early);
return pe_unknown_type_early;
unsigned char* end = maxptr;
unsigned char* upto = start;
@@ -2966,7 +3216,7 @@ HookAPI::get_stobject_length(
// not supported types
if (type == STI_NUMBER || type == STI_UINT96 || type == STI_UINT192 ||
type == STI_UINT384 || type == STI_UINT512)
return Unexpected(pe_unknown_type_early);
return pe_unknown_type_early;
bool is_vl =
(type == STI_ACCOUNT || type == STI_VL ||
@@ -3036,7 +3286,7 @@ HookAPI::get_stobject_length(
length = 20;
break;
default:
return Unexpected(pe_unknown_type_late);
return -1;
}
}
else if (type == STI_AMOUNT) /* AMOUNT */
@@ -3058,7 +3308,7 @@ HookAPI::get_stobject_length(
int flag = *(upto + length++);
// flag shoud be 0x01 or 0x10 or 0x20 or those union
if (flag == 0 || flag & ~(0x01 | 0x10 | 0x20))
return Unexpected(pe_unexpected_end);
return pe_unexpected_end;
if (flag & 0x01) // account
length += 20;
if (flag & 0x10) // currency
@@ -3079,10 +3329,10 @@ HookAPI::get_stobject_length(
else if (lastflag == 0x00)
break; // end byte
else
return Unexpected(pe_unexpected_end);
return pe_unexpected_end;
}
if (upto >= end)
return Unexpected(pe_unexpected_end);
return pe_unexpected_end;
}
else if (type == STI_ISSUE)
{
+246 -101
View File
@@ -14,6 +14,8 @@
#include <xrpl/protocol/st.h>
#include <xrpl/protocol/tokens.h>
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <any>
#include <cfenv>
#include <memory>
#include <optional>
#include <string>
@@ -39,7 +41,7 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
if (!otxnAcc)
return {};
TxType const& tt = tx.getTxnType();
uint16_t tt = tx.getFieldU16(sfTransactionType);
std::map<AccountID, std::pair<int, bool>> tshEntries;
@@ -200,10 +202,6 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
issuer,
(ut->getFlags() & lsfBurnable) ? tshSTRONG : tshWEAK);
if (rv.rules().enabled(featureURITokenBroker) &&
tx.isFieldPresent(sfBrokerAccount))
ADD_TSH(tx.getAccountID(sfBrokerAccount), tshWEAK);
break;
}
@@ -303,14 +301,18 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
bool issuerCanRollback = nft::getFlags(nid) & tfStrongTSH;
ADD_TSH(issuer, issuerCanRollback);
for (auto const& offer : {bo, so})
if (bo)
{
if (offer)
{
ADD_TSH(offer->getAccountID(sfOwner), tshSTRONG);
if (offer->isFieldPresent(sfDestination))
ADD_TSH(offer->getAccountID(sfDestination), tshSTRONG);
}
ADD_TSH(bo->getAccountID(sfOwner), tshSTRONG);
if (bo->isFieldPresent(sfDestination))
ADD_TSH(bo->getAccountID(sfDestination), tshSTRONG);
}
if (so)
{
ADD_TSH(so->getAccountID(sfOwner), tshSTRONG);
if (so->isFieldPresent(sfDestination))
ADD_TSH(so->getAccountID(sfDestination), tshSTRONG);
}
break;
@@ -549,8 +551,7 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
break;
}
case ttLEDGER_STATE_FIX: {
if (tx.isFieldPresent(sfOwner))
ADD_TSH(tx.getAccountID(sfOwner), tshWEAK);
// TODO: Implement if needed
break;
}
case ttMPTOKEN_ISSUANCE_CREATE:
@@ -600,9 +601,160 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
} // namespace hook
using namespace hook::hook_float;
namespace hook_float {
using namespace hook_api;
static int64_t const minMantissa = 1000000000000000ull;
static int64_t const maxMantissa = 9999999999999999ull;
static int32_t const minExponent = -96;
static int32_t const maxExponent = 80;
inline int32_t
get_exponent(int64_t float1)
{
if (float1 < 0)
return INVALID_FLOAT;
if (float1 == 0)
return 0;
uint64_t float_in = (uint64_t)float1;
float_in >>= 54U;
float_in &= 0xFFU;
return ((int32_t)float_in) - 97;
}
inline int64_t
get_mantissa(int64_t float1)
{
if (float1 < 0)
return INVALID_FLOAT;
if (float1 == 0)
return 0;
float1 -= ((((uint64_t)float1) >> 54U) << 54U);
return float1;
}
inline bool
is_negative(int64_t float1)
{
return ((float1 >> 62U) & 1ULL) == 0;
}
inline int64_t
invert_sign(int64_t float1)
{
int64_t r = (int64_t)(((uint64_t)float1) ^ (1ULL << 62U));
return r;
}
inline int64_t
set_sign(int64_t float1, bool set_negative)
{
bool neg = is_negative(float1);
if ((neg && set_negative) || (!neg && !set_negative))
return float1;
return invert_sign(float1);
}
inline int64_t
set_mantissa(int64_t float1, uint64_t mantissa)
{
if (mantissa > maxMantissa)
return MANTISSA_OVERSIZED;
if (mantissa < minMantissa)
return MANTISSA_UNDERSIZED;
return float1 - get_mantissa(float1) + mantissa;
}
inline int64_t
set_exponent(int64_t float1, int32_t exponent)
{
if (exponent > maxExponent)
return EXPONENT_OVERSIZED;
if (exponent < minExponent)
return EXPONENT_UNDERSIZED;
uint64_t exp = (exponent + 97);
exp <<= 54U;
float1 &= ~(0xFFLL << 54);
float1 += (int64_t)exp;
return float1;
}
inline int64_t
make_float(ripple::IOUAmount& amt)
{
int64_t man_out = amt.mantissa();
int64_t float_out = 0;
bool neg = man_out < 0;
if (neg)
man_out *= -1;
float_out = set_sign(float_out, neg);
float_out = set_mantissa(float_out, (uint64_t)man_out);
float_out = set_exponent(float_out, amt.exponent());
return float_out;
}
inline int64_t
make_float(uint64_t mantissa, int32_t exponent, bool neg)
{
if (mantissa == 0)
return 0;
if (mantissa > maxMantissa)
return MANTISSA_OVERSIZED;
if (mantissa < minMantissa)
return MANTISSA_UNDERSIZED;
if (exponent > maxExponent)
return EXPONENT_OVERSIZED;
if (exponent < minExponent)
return EXPONENT_UNDERSIZED;
int64_t out = 0;
out = set_mantissa(out, mantissa);
out = set_exponent(out, exponent);
out = set_sign(out, neg);
return out;
}
} // namespace hook_float
using namespace hook_float;
using hook::Bytes;
inline int32_t
no_free_slots(hook::HookContext& hookCtx)
{
return hook_api::max_slots - hookCtx.slot.size() <= 0;
}
inline std::optional<int32_t>
get_free_slot(hook::HookContext& hookCtx)
{
// allocate a slot
int32_t slot_into = 0;
if (hookCtx.slot_free.size() > 0)
{
slot_into = hookCtx.slot_free.front();
hookCtx.slot_free.pop();
return slot_into;
}
// no slots were available in the queue so increment slot counter until we
// find a free slot usually this will be the next available but the hook
// developer may have allocated any slot ahead of when the counter gets
// there
do
{
slot_into = ++hookCtx.slot_counter;
} while (hookCtx.slot.find(slot_into) != hookCtx.slot.end() &&
// this condition should always be met, if for some reason, somehow
// it is not then we will return the final slot every time.
hookCtx.slot_counter <= hook_api::max_slots);
if (hookCtx.slot_counter > hook_api::max_slots)
return {};
return slot_into;
}
// cu_ptr is a pointer into memory, bounds check is assumed to have already
// happened
inline std::optional<Currency>
@@ -658,7 +810,7 @@ parseCurrency(uint8_t* cu_ptr, uint32_t cu_len)
return {};
}
inline std::variant<uint64_t, hook_api::hook_return_code>
inline int64_t
serialize_keylet(
ripple::Keylet& kl,
uint8_t* memory,
@@ -666,7 +818,7 @@ serialize_keylet(
uint32_t write_len)
{
if (write_len < 34)
return TOO_SMALL;
return hook_api::TOO_SMALL;
memory[write_ptr + 0] = (kl.type >> 8) & 0xFFU;
memory[write_ptr + 1] = (kl.type >> 0) & 0xFFU;
@@ -674,7 +826,7 @@ serialize_keylet(
for (int i = 0; i < 32; ++i)
memory[write_ptr + 2 + i] = kl.key.data()[i];
return 34ULL;
return 34;
}
std::optional<ripple::Keylet>
@@ -717,19 +869,19 @@ hook::computeCreationFee(uint64_t byteCount)
}
// many datatypes can be encoded into an int64_t
inline std::variant<uint64_t, hook_api::hook_return_code>
inline int64_t
data_as_int64(void const* ptr_raw, uint32_t len)
{
if (len > 8)
return TOO_BIG;
return hook_api::hook_return_code::TOO_BIG;
uint8_t const* ptr = reinterpret_cast<uint8_t const*>(ptr_raw);
uint64_t output = 0;
for (int i = 0, j = (len - 1) * 8; i < len; ++i, j -= 8)
output += (((uint64_t)ptr[i]) << j);
if ((1ULL << 63U) & output)
return TOO_BIG;
return output;
return hook_api::hook_return_code::TOO_BIG;
return (int64_t)output;
}
/* returns true iff every even char is ascii and every odd char is 00
@@ -1116,7 +1268,7 @@ DEFINE_HOOK_FUNCTION(
return OUT_OF_BOUNDS;
if (!j.trace())
return 0ULL;
return 0;
if (read_len > 128)
read_len = 128;
@@ -1134,12 +1286,12 @@ DEFINE_HOOK_FUNCTION(
(const char*)memory + read_ptr, read_len)
<< ": " << number;
return 0ULL;
return 0;
}
}
j.trace() << "HookTrace[" << HC_ACC() << "]: " << number;
return 0ULL;
return 0;
HOOK_TEARDOWN();
}
@@ -1159,7 +1311,7 @@ DEFINE_HOOK_FUNCTION(
return OUT_OF_BOUNDS;
if (!j.trace())
return 0ULL;
return 0;
if (mread_len > 128)
mread_len = 128;
@@ -1222,7 +1374,7 @@ DEFINE_HOOK_FUNCTION(
<< std::string_view((const char*)output_storage, out_len);
}
return 0ULL;
return 0;
HOOK_TEARDOWN();
}
@@ -1327,8 +1479,7 @@ DEFINE_HOOK_FUNCTION(
auto const sleAccount = view.peek(hookCtx.result.accountKeylet);
if (!sleAccount && view.rules().enabled(featureExtendedHookState))
// should return hook_api::hook_return_code
return static_cast<hook_api::hook_return_code>(tefINTERNAL);
return tefINTERNAL;
uint16_t const hookStateScale = sleAccount->isFieldPresent(sfHookStateScale)
? sleAccount->getFieldU16(sfHookStateScale)
@@ -1352,8 +1503,7 @@ DEFINE_HOOK_FUNCTION(
{
auto const sleAccount = view.peek(hookCtx.result.accountKeylet);
if (!sleAccount)
// should return hook_api::hook_return_code
return static_cast<hook_api::hook_return_code>(tefINTERNAL);
return tefINTERNAL;
}
if (!key)
@@ -1568,8 +1718,7 @@ hook::finalizeHookResult(
// add a metadata entry for this hook execution result
{
STObject meta{sfHookExecution};
meta.setFieldU8(
sfHookResult, static_cast<uint8_t>(hookResult.exitType));
meta.setFieldU8(sfHookResult, hookResult.exitType);
meta.setAccountID(sfHookAccount, hookResult.account);
// RH NOTE: this is probably not necessary, a direct cast should always
@@ -2079,7 +2228,7 @@ DEFINE_HOOK_FUNCTION(int64_t, slot_type, uint32_t slot_no, uint32_t flags)
if (flags == 0)
{
auto const base = std::get<0>(*result);
return static_cast<uint64_t>(base.getFName().fieldCode);
return base.getFName().fieldCode;
}
else
{
@@ -2581,23 +2730,26 @@ DEFINE_HOOK_FUNCTION(
return serialize_keylet(kl, memory, write_ptr, write_len);
}
// These keylet types are not yet implemented. Their
// corresponding amendments are not yet supported on the
// network. Each case needs a full implementation (see
// above cases for reference) before its amendment can be
// enabled.
// featureXChainBridge
case keylet_code::BRIDGE:
case keylet_code::XCHAIN_OWNED_CLAIM_ID:
case keylet_code::XCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID:
// featureMPTokensV1
case keylet_code::XCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID: {
if (!applyCtx.view().rules().enabled(featureXChainBridge))
return INVALID_ARGUMENT;
}
case keylet_code::MPTOKEN_ISSUANCE:
case keylet_code::MPTOKEN:
// featureCredentials
case keylet_code::CREDENTIAL:
// featurePermissionedDomains
case keylet_code::PERMISSIONED_DOMAIN:
return INVALID_ARGUMENT;
case keylet_code::MPTOKEN: {
if (!applyCtx.view().rules().enabled(featureMPTokensV1))
return INVALID_ARGUMENT;
}
case keylet_code::CREDENTIAL: {
if (!applyCtx.view().rules().enabled(featureCredentials))
return INVALID_ARGUMENT;
}
case keylet_code::PERMISSIONED_DOMAIN: {
if (!applyCtx.view().rules().enabled(
featurePermissionedDomains))
return INVALID_ARGUMENT;
}
}
}
catch (std::exception& e)
@@ -2690,8 +2842,7 @@ DEFINE_HOOK_FUNCTION(
if (NOT_IN_BOUNDS(write_ptr, txID.size(), memory_length))
return OUT_OF_BOUNDS;
auto const write_txid =
[&]() -> std::variant<uint64_t, hook_api::hook_return_code> {
auto const write_txid = [&]() -> int64_t {
WRITE_WASM_MEMORY_AND_RETURN(
write_ptr,
txID.size(),
@@ -2701,15 +2852,12 @@ DEFINE_HOOK_FUNCTION(
memory_length);
};
auto result = write_txid();
if (std::holds_alternative<hook_api::hook_return_code>(result))
return std::get<hook_api::hook_return_code>(result);
int64_t result = write_txid();
auto const value = std::get<uint64_t>(result);
if (value == 32)
if (result == 32)
hookCtx.result.emittedTxn.push(tpTrans);
return value;
return result;
HOOK_TEARDOWN();
}
@@ -3198,7 +3346,7 @@ DEFINE_HOOK_FUNCTION(
uint32_t field_id)
{
// proxy only no setup or teardown
auto ret = sto_emplace(
int64_t ret = sto_emplace(
hookCtx,
frameCtx,
write_ptr,
@@ -3209,12 +3357,8 @@ DEFINE_HOOK_FUNCTION(
0,
field_id);
if (std::holds_alternative<uint64_t>(ret))
{
auto const value = std::get<uint64_t>(ret);
if (value > 0 && value == read_len)
return DOESNT_EXIST;
}
if (ret > 0 && ret == read_len)
return DOESNT_EXIST;
return ret;
}
@@ -3237,7 +3381,7 @@ DEFINE_HOOK_FUNCTION(
auto const result = api.sto_validate(data);
if (!result)
return result.error();
return result.value() ? 1ULL : 0ULL;
return result.value() ? 1 : 0;
HOOK_TEARDOWN();
}
@@ -3273,7 +3417,7 @@ DEFINE_HOOK_FUNCTION(
auto const result = api.util_verify(data, sig, key);
if (!result)
return result.error();
return result.value() ? 1ULL : 0ULL;
return result.value() ? 1 : 0;
HOOK_TEARDOWN();
}
@@ -3368,30 +3512,26 @@ DEFINE_HOOK_FUNCTION(int32_t, _g, uint32_t id, uint32_t maxitr)
<< "Iterations: " << hookCtx.guard_map[id];
}
hookCtx.result.exitType = hook_api::ExitType::ROLLBACK;
hookCtx.result.exitCode = (int64_t)GUARD_VIOLATION;
hookCtx.result.exitCode = GUARD_VIOLATION;
return RC_ROLLBACK;
}
return 1U;
return 1;
HOOK_TEARDOWN();
}
#define RETURN_IF_INVALID_FLOAT(float1) \
{ \
if (float1 < 0) \
return INVALID_FLOAT; \
if (float1 != 0) \
{ \
auto const mantissa = get_mantissa(float1); \
auto const exponent = get_exponent(float1); \
if (!mantissa || !exponent) \
return INVALID_FLOAT; \
if (mantissa.value() < minMantissa || \
mantissa.value() > maxMantissa || \
exponent.value() > maxExponent || \
exponent.value() < minExponent) \
return INVALID_FLOAT; \
} \
#define RETURN_IF_INVALID_FLOAT(float1) \
{ \
if (float1 < 0) \
return hook_api::INVALID_FLOAT; \
if (float1 != 0) \
{ \
uint64_t mantissa = get_mantissa(float1); \
int32_t exponent = get_exponent(float1); \
if (mantissa < minMantissa || mantissa > maxMantissa || \
exponent > maxExponent || exponent < minExponent) \
return INVALID_FLOAT; \
} \
}
DEFINE_HOOK_FUNCTION(
@@ -3408,7 +3548,7 @@ DEFINE_HOOK_FUNCTION(
return OUT_OF_BOUNDS;
if (!j.trace())
return 0ULL;
return 0;
if (read_len > 128)
read_len = 128;
@@ -3418,33 +3558,38 @@ DEFINE_HOOK_FUNCTION(
*((const char*)memory + read_ptr + read_len - 1) == '\0')
read_len--;
auto const messageKey = (read_len == 0)
? ""
: std::string_view((const char*)memory + read_ptr, read_len);
if (float1 == 0)
{
j.trace() << "HookTrace[" << HC_ACC() << "]: " << messageKey
j.trace() << "HookTrace[" << HC_ACC() << "]: "
<< (read_len == 0
? ""
: std::string_view(
(const char*)memory + read_ptr, read_len))
<< ": Float 0*10^(0) <ZERO>";
return 0ULL;
return 0;
}
auto const man = get_mantissa(float1);
auto const exp = get_exponent(float1);
uint64_t man = get_mantissa(float1);
int32_t exp = get_exponent(float1);
bool neg = is_negative(float1);
if (!man || !exp || man.value() < minMantissa ||
man.value() > maxMantissa || exp.value() < minExponent ||
exp.value() > maxExponent)
if (man < minMantissa || man > maxMantissa || exp < minExponent ||
exp > maxExponent)
{
j.trace() << "HookTrace[" << HC_ACC() << "]: " << messageKey
j.trace() << "HookTrace[" << HC_ACC() << "]:"
<< (read_len == 0
? ""
: std::string_view(
(const char*)memory + read_ptr, read_len))
<< ": Float <INVALID>";
return 0ULL;
return 0;
}
j.trace() << "HookTrace[" << HC_ACC() << "]:" << messageKey << ": Float "
<< (neg ? "-" : "") << man.value() << "*10^(" << exp.value()
<< ")";
return 0ULL;
j.trace() << "HookTrace[" << HC_ACC() << "]:"
<< (read_len == 0 ? ""
: std::string_view(
(const char*)memory + read_ptr, read_len))
<< ": Float " << (neg ? "-" : "") << man << "*10^(" << exp << ")";
return 0;
HOOK_TEARDOWN();
}
+4 -14
View File
@@ -1044,8 +1044,7 @@ pendSaveValidated(
Application& app,
std::shared_ptr<Ledger const> const& ledger,
bool isSynchronous,
bool isCurrent,
std::function<void(bool)> callback)
bool isCurrent)
{
if (!app.getHashRouter().setFlags(ledger->info().hash, SF_SAVED))
{
@@ -1057,8 +1056,6 @@ pendSaveValidated(
{
// Either we don't need it to be finished
// or it is finished
if (callback)
callback(true);
return true;
}
}
@@ -1072,8 +1069,6 @@ pendSaveValidated(
JLOG(stream) << "Pend save with seq in pending saves "
<< ledger->info().seq;
if (callback)
callback(true);
return true;
}
@@ -1082,20 +1077,15 @@ pendSaveValidated(
app.getJobQueue().addJob(
isCurrent ? jtPUBLEDGER : jtPUBOLDLEDGER,
std::to_string(ledger->seq()),
[&app, ledger, isCurrent, callback]() {
bool success = saveValidatedLedger(app, ledger, isCurrent);
if (callback)
callback(success);
[&app, ledger, isCurrent]() {
saveValidatedLedger(app, ledger, isCurrent);
}))
{
return true;
}
// The JobQueue won't do the Job. Do the save synchronously.
bool success = saveValidatedLedger(app, ledger, isCurrent);
if (callback)
callback(success);
return success;
return saveValidatedLedger(app, ledger, isCurrent);
}
void
+1 -2
View File
@@ -452,8 +452,7 @@ pendSaveValidated(
Application& app,
std::shared_ptr<Ledger const> const& ledger,
bool isSynchronous,
bool isCurrent,
std::function<void(bool)> callback = {});
bool isCurrent);
std::shared_ptr<Ledger>
loadLedgerHelper(LedgerInfo const& sinfo, Application& app, bool acquire);
-12
View File
@@ -139,9 +139,6 @@ public:
RangeSet<std::uint32_t>
getPinnedLedgersRangeSet();
void
setPinnedLedgersRangeSet(const RangeSet<std::uint32_t>& range_set);
/** Apply held transactions to the open ledger
This is normally called as we close the ledger.
The open ledger remains open to handle new transactions
@@ -208,10 +205,6 @@ public:
void
clearLedger(std::uint32_t seq);
bool
isPinned(std::uint32_t seq);
void
unpinLedger(std::uint32_t seq);
bool
isValidated(ReadView const& ledger);
bool
getValidatedRange(std::uint32_t& minVal, std::uint32_t& maxVal);
@@ -366,12 +359,7 @@ private:
std::recursive_mutex mCompleteLock;
RangeSet<std::uint32_t> mCompleteLedgers;
// When both locks are needed, acquire them together with std::scoped_lock.
std::mutex mPinnedLock;
RangeSet<std::uint32_t> mPinnedLedgers; // Track pinned ledger ranges
bool mPinnedMergedToComplete{
false}; // One-shot: pinned merged into mCompleteLedgers
// Publish thread is running.
bool mAdvanceThread{false};
+49 -207
View File
@@ -24,7 +24,6 @@
#include <xrpld/app/ledger/OpenLedger.h>
#include <xrpld/app/ledger/OrderBookDB.h>
#include <xrpld/app/ledger/PendingSaves.h>
#include <xrpld/app/ledger/detail/PublishGap.h>
#include <xrpld/app/main/Application.h>
#include <xrpld/app/misc/AmendmentTable.h>
#include <xrpld/app/misc/HashRouter.h>
@@ -447,25 +446,14 @@ bool
LedgerMaster::storeLedger(std::shared_ptr<Ledger const> ledger, bool pin)
{
bool validated = ledger->info().validated;
// Returns true if we already had the ledger.
//
// When pin=true, we deliberately skip mLedgerHistory insertion.
// A single catalogue pack can contain tens of thousands of ledgers,
// each holding a full SHAMap of account/transaction nodes. Keeping
// them all in the in-memory history cache would consume hundreds of
// GB of RAM. Instead, pinned ledgers are persisted to the nodestore
// and SQLite, and are retrievable via RPC (which reads from SQLite),
// but are NOT kept in memory.
if (!pin && !mLedgerHistory.insert(std::move(ledger), validated))
// Returns true if we already had the ledger
if (!mLedgerHistory.insert(std::move(ledger), validated))
return false;
if (pin)
{
uint32_t seq = ledger->info().seq;
{
std::lock_guard sl(mPinnedLock);
mPinnedLedgers.insert(range(seq, seq));
}
mPinnedLedgers.insert(range(seq, seq));
JLOG(m_journal.info()) << "Pinned ledger : " << seq;
}
return true;
@@ -524,7 +512,7 @@ LedgerMaster::haveLedger(std::uint32_t seq)
void
LedgerMaster::clearLedger(std::uint32_t seq)
{
std::scoped_lock lock(mCompleteLock, mPinnedLock);
std::lock_guard sl(mCompleteLock);
// Don't clear pinned ledgers
if (boost::icl::contains(mPinnedLedgers, seq))
@@ -537,20 +525,6 @@ LedgerMaster::clearLedger(std::uint32_t seq)
mCompleteLedgers.erase(seq);
}
bool
LedgerMaster::isPinned(std::uint32_t seq)
{
std::lock_guard sl(mPinnedLock);
return boost::icl::contains(mPinnedLedgers, seq);
}
void
LedgerMaster::unpinLedger(std::uint32_t seq)
{
std::lock_guard sl(mPinnedLock);
mPinnedLedgers.erase(range(seq, seq));
}
bool
LedgerMaster::isValidated(ReadView const& ledger)
{
@@ -887,19 +861,7 @@ LedgerMaster::setFullLedger(
pendSaveValidated(app_, ledger, isSynchronous, isCurrent);
{
std::scoped_lock lock(mCompleteLock, mPinnedLock);
// One-time merge of pinned ranges into mCompleteLedgers.
// For NORMAL/NETWORK startup, this fires on the first validated
// ledger (network quorum). For LOAD/standalone, the merge
// already happened in setPinnedLedgersRangeSet() and this
// is a no-op (mPinnedMergedToComplete is already true).
if (!mPinnedMergedToComplete && !mPinnedLedgers.empty())
{
for (auto const& interval : mPinnedLedgers)
mCompleteLedgers.insert(interval);
mPinnedMergedToComplete = true;
}
std::lock_guard ml(mCompleteLock);
mCompleteLedgers.insert(ledger->info().seq);
}
@@ -1324,134 +1286,53 @@ LedgerMaster::findNewLedgersToPublish(
auto valLedger = mValidLedger.get();
std::uint32_t valSeq = valLedger->info().seq;
// Create a range of ledgers we need to publish
RangeSet<std::uint32_t> toPublish;
toPublish.insert(range(pubSeq, valSeq));
// CRITICAL: Skip publishing pinned ledgers (except the most recent).
//
// When loading catalogues, we deliberately avoid storing pinned ledgers in
// the AcceptedLedger cache to prevent memory bloat with millions of
// ledgers. However, NetworkOPs::pubLedger() expects every published ledger
// to either:
// 1. Already exist in the AcceptedLedger cache, OR
// 2. Be able to create and cache a new AcceptedLedger
//
// For pinned ledgers, we skip the cache entirely during
// saveValidatedLedger. If we try to publish them, pubLedger creates a new
// AcceptedLedger, but canonicalize_replace_client might return a different
// instance, causing: "Assertion failed: alpAccepted->getLedger().get() ==
// lpAccepted.get()"
//
// This primarily affects STANDALONE MODE where catalogue_load calls
// switchLCL when loading historical ledgers newer than the current closed
// ledger. In live network mode, historical ledgers are typically older than
// the current ledger, so switchLCL -> tryAdvance -> publish isn't
// triggered.
//
// Solution: Only publish the most recent validated ledger (which needs
// publishing) and skip all intermediate pinned ledgers (already on disk).
{
std::lock_guard sll(mPinnedLock);
RangeSet<std::uint32_t> pinnedExceptLast = mPinnedLedgers;
// Remove the most recent from the pinned set so we always publish it
if (boost::icl::contains(pinnedExceptLast, valSeq))
pinnedExceptLast.erase(range(valSeq, valSeq));
// Subtract pinned ledgers from the set to publish
toPublish -= pinnedExceptLast;
}
scope_unlock sul{sl};
try
{
for (auto const& interval : toPublish)
for (std::uint32_t seq = pubSeq; seq <= valSeq; ++seq)
{
// Some sequences may be absent from toPublish because they are
// pinned ledgers already persisted to disk and intentionally not
// republished. The decision logic — "can pubSeq safely jump
// across this gap?" — lives in detail::canSkipPinnedGap so it
// is unit-testable without spinning up a full LedgerMaster.
//
// Core invariant (enforced by canSkipPinnedGap): never publish
// across a gap in non-pinned ledgers. Publication must remain
// contiguous for every non-pinned sequence from
// mPubLedgerSeq + 1 forward. If pubSeq is lagging because an
// earlier non-pinned ledger could not be fetched/published, we
// stop here rather than publish newer ledgers out of order.
//
// In production this branch is essentially dead code: pinned
// ranges are old historical catalogue data and pubSeq tracks
// the recent published tip, so toPublish won't have pinned-
// induced gaps. It only matters in test/standalone catalogue-
// load scenarios where pinned ranges can sit near pubSeq.
if (pubSeq < interval.first())
JLOG(m_journal.trace())
<< "Trying to fetch/publish valid ledger " << seq;
std::shared_ptr<Ledger const> ledger;
// This can throw
auto hash = hashOfSeq(*valLedger, seq, m_journal);
// VFALCO TODO Restructure this code so that zero is not
// used.
if (!hash)
hash = beast::zero; // kludge
if (seq == valSeq)
{
bool canSkip;
{
std::lock_guard sll(mPinnedLock);
canSkip = detail::canSkipPinnedGap(
pubSeq, interval.first(), mPinnedLedgers);
}
if (!canSkip)
{
JLOG(m_journal.trace())
<< "Stopping publish at seq " << pubSeq
<< " — skipped span [" << pubSeq << ", "
<< interval.first() << ") contains non-pinned ledgers";
break;
}
pubSeq = interval.first();
// We need to publish the ledger we just fully validated
ledger = valLedger;
}
else if (hash->isZero())
{
JLOG(m_journal.fatal()) << "Ledger: " << valSeq
<< " does not have hash for " << seq;
UNREACHABLE(
"ripple::LedgerMaster::findNewLedgersToPublish : ledger "
"not found");
}
else
{
ledger = mLedgerHistory.getLedgerByHash(*hash);
}
for (std::uint32_t seq = interval.first(); seq <= interval.last();
++seq)
if (!app_.config().LEDGER_REPLAY)
{
JLOG(m_journal.trace())
<< "Trying to fetch/publish valid ledger " << seq;
// Can we try to acquire the ledger we need?
if (!ledger && (++acqCount < ledger_fetch_size_))
ledger = app_.getInboundLedgers().acquire(
*hash, seq, InboundLedger::Reason::GENERIC);
}
std::shared_ptr<Ledger const> ledger;
// This can throw
auto hash = hashOfSeq(*valLedger, seq, m_journal);
// VFALCO TODO Restructure this code so that zero is not
// used.
if (!hash)
hash = beast::zero; // kludge
if (seq == valSeq)
{
// We need to publish the ledger we just fully validated
ledger = valLedger;
}
else if (hash->isZero())
{
JLOG(m_journal.fatal())
<< "Ledger: " << valSeq << " does not have hash for "
<< seq;
UNREACHABLE(
"ripple::LedgerMaster::findNewLedgersToPublish : "
"ledger not found");
}
else
{
ledger = mLedgerHistory.getLedgerByHash(*hash);
}
if (!app_.config().LEDGER_REPLAY)
{
// Can we try to acquire the ledger we need?
if (!ledger && (++acqCount < ledger_fetch_size_))
ledger = app_.getInboundLedgers().acquire(
*hash, seq, InboundLedger::Reason::GENERIC);
}
// Did we acquire the next ledger we need to publish?
if (ledger && (ledger->info().seq == pubSeq))
{
ledger->setValidated();
ret.push_back(ledger);
++pubSeq;
}
// Did we acquire the next ledger we need to publish?
if (ledger && (ledger->info().seq == pubSeq))
{
ledger->setValidated();
ret.push_back(ledger);
++pubSeq;
}
}
@@ -1751,7 +1632,7 @@ LedgerMaster::getCompleteLedgers()
std::string
LedgerMaster::getPinnedLedgers()
{
std::lock_guard sl(mPinnedLock);
std::lock_guard sl(mCompleteLock);
return to_string(mPinnedLedgers);
}
@@ -1765,46 +1646,10 @@ LedgerMaster::getCompleteLedgersRangeSet()
RangeSet<std::uint32_t>
LedgerMaster::getPinnedLedgersRangeSet()
{
std::lock_guard sl(mPinnedLock);
std::lock_guard sl(mCompleteLock);
return mPinnedLedgers;
}
void
LedgerMaster::setPinnedLedgersRangeSet(const RangeSet<std::uint32_t>& range_set)
{
std::scoped_lock lock(mCompleteLock, mPinnedLock);
if (!mPinnedLedgers.empty())
{
Throw<std::runtime_error>(
"Expected mPinnedLedgers to be empty on startup");
}
mPinnedLedgers.assign(range_set);
// Normally, we defer merging pinned ranges into mCompleteLedgers until
// the first validated ledger (in setFullLedger), so that pinned history
// only becomes queryable after the node has network quorum. But if
// mCompleteLedgers is already non-empty (LOAD/standalone startup already
// called setFullLedger before we got here), the one-shot in
// setFullLedger won't fire again. Merge immediately in that case.
if (!mCompleteLedgers.empty())
{
for (auto const& interval : mPinnedLedgers)
mCompleteLedgers.insert(interval);
mPinnedMergedToComplete = true;
JLOG(m_journal.info())
<< "Merged pinned ranges into complete ledgers immediately "
<< "(startup already past setFullLedger): " << to_string(range_set);
}
else
{
// mCompleteLedgers is empty — defer merge to setFullLedger()
// when the first validated ledger arrives (network quorum).
JLOG(m_journal.info())
<< "Loaded pinned ranges, will merge on first validation: "
<< to_string(range_set);
}
}
std::optional<NetClock::time_point>
LedgerMaster::getCloseTimeBySeq(LedgerIndex ledgerIndex)
{
@@ -1968,18 +1813,15 @@ LedgerMaster::setLedgerRangePresent(
std::uint32_t maxV,
bool pin)
{
std::lock_guard sl(mCompleteLock);
mCompleteLedgers.insert(range(minV, maxV));
if (pin)
{
std::scoped_lock lock(mCompleteLock, mPinnedLock);
mCompleteLedgers.insert(range(minV, maxV));
mPinnedLedgers.insert(range(minV, maxV));
JLOG(m_journal.info())
<< "Pinned ledger range: " << minV << " - " << maxV;
return;
}
std::lock_guard cl(mCompleteLock);
mCompleteLedgers.insert(range(minV, maxV));
}
void
@@ -1999,7 +1841,7 @@ LedgerMaster::getCacheHitRate()
void
LedgerMaster::clearPriorLedgers(LedgerIndex seq)
{
std::scoped_lock lock(mCompleteLock, mPinnedLock);
std::lock_guard sl(mCompleteLock);
if (seq <= 0)
return;
-87
View File
@@ -1,87 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2026 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_APP_LEDGER_DETAIL_PUBLISH_GAP_H_INCLUDED
#define RIPPLE_APP_LEDGER_DETAIL_PUBLISH_GAP_H_INCLUDED
#include <xrpl/basics/RangeSet.h>
#include <cstdint>
namespace ripple {
namespace detail {
/** Decide whether pubSeq can safely advance across a gap in the
publication set, given the pinned-ledger ranges.
Used by LedgerMaster::findNewLedgersToPublish: when the
publish-target set has been split into intervals (because pinned
ranges were subtracted from it), pubSeq may be behind the start of
the next interval. We may only "jump" pubSeq forward across that
gap if every skipped sequence is pinned — pinned ledgers are
persisted on disk and are intentionally never republished, so
skipping them does not violate the contiguous-publication
invariant. If any sequence in the gap is *not* pinned, then pubSeq
is lagging because an earlier non-pinned ledger could not be
fetched/published; we must halt rather than publish newer ledgers
out of order.
Practical note: in production this guard is essentially never
exercised. Pinned ranges are old historical catalogue ranges and
pubSeq tracks the recent published tip — they don't overlap. The
guard matters only in test/standalone catalogue-load scenarios
where a catalogue can be loaded into an environment whose ledger
history places pinned seqs near the publish horizon. We keep the
guard rather than rely on the assumption holding because (a) the
cost is one RangeSet subtraction per gap and (b) silently
publishing across a non-pinned gap would corrupt the stream
contract.
@param pubSeq The next sequence the publisher would like
to publish.
@param intervalStart The first sequence in the next non-pinned
interval to publish.
@param pinned The current pinned-ledger ranges.
@return true pubSeq can safely jump to intervalStart
(pubSeq >= intervalStart, or every skipped seq is
pinned).
@return false the gap [pubSeq, intervalStart) contains at least
one non-pinned sequence; the publisher must stop.
*/
inline bool
canSkipPinnedGap(
std::uint32_t pubSeq,
std::uint32_t intervalStart,
RangeSet<std::uint32_t> const& pinned)
{
// No gap.
if (pubSeq >= intervalStart)
return true;
RangeSet<std::uint32_t> skipped;
skipped.insert(range(pubSeq, intervalStart - 1));
skipped -= pinned;
return skipped.empty();
}
} // namespace detail
} // namespace ripple
#endif
+1 -10
View File
@@ -24,7 +24,6 @@
#include <xrpld/core/TimeKeeper.h>
#include <xrpld/net/RPCCall.h>
#include <xrpld/rpc/RPCHandler.h>
#include <xrpld/rpc/handlers/Handlers.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/contract.h>
@@ -388,8 +387,7 @@ run(int argc, char** argv)
po::value<std::string>(),
"Specify the range of present ledgers for testing purposes. Min and "
"max values are comma separated.")(
"version", "Display the build version.")(
"definitions", "Output server definitions as JSON and exit.");
"version", "Display the build version.");
po::options_description data("Ledger/Data Options");
data.add_options()("import", importText.c_str())(
@@ -531,13 +529,6 @@ run(int argc, char** argv)
return 0;
}
if (vm.count("definitions"))
{
auto defs = getStaticServerDefinitions();
std::cout << Json::FastWriter().write(defs);
return 0;
}
#ifndef ENABLE_TESTS
if (vm.count("unittest") || vm.count("unittest-child"))
{
+1 -1
View File
@@ -152,4 +152,4 @@ deletion routine.
* [fetch_depth] will be silently set to equal the online_delete setting if
online_delete is greater than fetch_depth.
* In the [node_db] section, there is a performance tuning option, delete_batch,
which sets the maximum number of SQL rows deleted per batch.
which sets the maximum size in ledgers for each SQL DELETE query.
-4
View File
@@ -22,7 +22,6 @@
#include <xrpld/app/ledger/Ledger.h>
#include <xrpld/nodestore/Manager.h>
#include <xrpl/basics/RangeSet.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <optional>
@@ -62,9 +61,6 @@ public:
virtual LedgerIndex
setCanDelete(LedgerIndex canDelete) = 0;
virtual void
setPinnedRanges(RangeSet<std::uint32_t> const& ranges) = 0;
/** Whether advisory delete is enabled. */
virtual bool
advisoryDelete() const = 0;
+62 -233
View File
@@ -21,20 +21,15 @@
#include <xrpld/app/ledger/TransactionMaster.h>
#include <xrpld/app/misc/NetworkOPs.h>
#include <xrpld/app/misc/detail/OnlineDeleteRanges.h>
#include <xrpld/app/rdb/State.h>
#include <xrpld/app/rdb/backend/SQLiteDatabase.h>
#include <xrpld/core/ConfigSections.h>
#include <xrpld/nodestore/Scheduler.h>
#include <xrpld/nodestore/detail/DatabasePinnedImp.h>
#include <xrpld/nodestore/detail/DatabaseRotatingImp.h>
#include <xrpld/shamap/SHAMapMissingNode.h>
#include <xrpl/beast/core/CurrentThreadName.h>
#include <boost/algorithm/string/predicate.hpp>
#include <iostream>
#include <xrpl/json/json_reader.h>
namespace ripple {
void
@@ -84,20 +79,6 @@ SHAMapStoreImp::SavedStateDB::setLastRotated(LedgerIndex seq)
ripple::setLastRotated(sqlDb_, seq);
}
std::string
SHAMapStoreImp::SavedStateDB::getPinnedRanges()
{
std::lock_guard lock(mutex_);
return ripple::getPinnedRanges(sqlDb_);
}
void
SHAMapStoreImp::SavedStateDB::setPinnedRanges(std::string const& ranges)
{
std::lock_guard lock(mutex_);
ripple::setPinnedRanges(sqlDb_, ranges);
}
//------------------------------------------------------------------------------
SHAMapStoreImp::SHAMapStoreImp(
@@ -136,9 +117,6 @@ SHAMapStoreImp::SHAMapStoreImp(
get_if_exists(section, "online_delete", deleteInterval_);
// Always initialize state database for pinned ranges persistence
state_db_.init(config, dbName_);
if (deleteInterval_)
{
// Configuration that affects the behavior of online delete
@@ -175,6 +153,7 @@ SHAMapStoreImp::SHAMapStoreImp(
std::to_string(config.LEDGER_HISTORY) + ")");
}
state_db_.init(config, dbName_);
if (!config.mem_backend())
dbPaths();
}
@@ -204,8 +183,6 @@ SHAMapStoreImp::makeNodeStore(int readThreads)
{
SavedState state = state_db_.getState();
// Create the rotation backends - needed for both DatabaseRotating and
// DatabasePinned
auto writableBackend = makeBackendRotating(state.writableDb);
auto archiveBackend = makeBackendRotating(state.archiveDb);
if (!state.writableDb.size())
@@ -215,58 +192,19 @@ SHAMapStoreImp::makeNodeStore(int readThreads)
state_db_.setState(state);
}
// Check if DatabasePinned should be created
if (nscfg.exists("pinned_type"))
{
// DatabasePinned uses the same rotation backends as
// DatabaseRotating but adds a persistent backend for pinned nodes
// Create persistent backend for pinned data
Section pinnedConfig = nscfg;
pinnedConfig.set("type", *nscfg.get("pinned_type"));
if (auto pinnedPath = nscfg.get("pinned_path"))
pinnedConfig.set("path", *pinnedPath);
auto pinnedBackend = NodeStore::Manager::instance().make_Backend(
pinnedConfig,
megabytes(app_.config().getValueFor(
SizedItem::burstSize, std::nullopt)),
scheduler_,
app_.logs().journal(nodeStoreName_));
pinnedBackend->open();
// Create DatabasePinned with rotation backends + persistent
auto dbp = std::make_unique<NodeStore::DatabasePinnedImp>(
app_,
scheduler_,
readThreads,
std::move(writableBackend),
std::move(archiveBackend),
std::move(pinnedBackend),
nscfg,
app_.logs().journal(NodeStore::DatabasePinnedImp::JournalName));
fdRequired_ += dbp->fdRequired();
dbRotating_ =
dbp.get(); // DatabasePinned inherits from DatabaseRotating
db.reset(dynamic_cast<NodeStore::Database*>(dbp.release()));
}
else
{
// Create NodeStore with two backends to allow online deletion of
// data
auto dbr = std::make_unique<NodeStore::DatabaseRotatingImp>(
app_,
scheduler_,
readThreads,
std::move(writableBackend),
std::move(archiveBackend),
nscfg,
app_.logs().journal(nodeStoreName_));
fdRequired_ += dbr->fdRequired();
dbRotating_ = dbr.get();
db.reset(dynamic_cast<NodeStore::Database*>(dbr.release()));
}
// Create NodeStore with two backends to allow online deletion of
// data
auto dbr = std::make_unique<NodeStore::DatabaseRotatingImp>(
app_,
scheduler_,
readThreads,
std::move(writableBackend),
std::move(archiveBackend),
nscfg,
app_.logs().journal(nodeStoreName_));
fdRequired_ += dbr->fdRequired();
dbRotating_ = dbr.get();
db.reset(dynamic_cast<NodeStore::Database*>(dbr.release()));
}
else
{
@@ -327,58 +265,6 @@ SHAMapStoreImp::copyNode(std::uint64_t& nodeCount, SHAMapTreeNode const& node)
return true;
}
void
SHAMapStoreImp::loadPinnedRanges()
{
auto rangesStr = state_db_.getPinnedRanges();
if (rangesStr.empty())
return;
auto const& nscfg = app_.config().section(ConfigSection::nodeDatabase());
if (!nscfg.exists("pinned_type"))
{
JLOG(journal_.warn())
<< "Ignoring persisted pinned ranges because [node_db] "
"pinned_type is not configured: "
<< rangesStr;
return;
}
RangeSet<std::uint32_t> persistedRanges;
if (!from_string(persistedRanges, rangesStr))
{
Throw<std::runtime_error>(
"Failed to parse persisted pinned ranges in state.db: " +
rangesStr);
}
// state.db alone is not sufficient evidence that pinned history is
// available in the currently configured backend. Validate each interval
// boundary against the actual node store before advertising it as pinned.
auto const hasLedgerData = [this](std::uint32_t seq) {
auto const hash = app_.getRelationalDatabase().getHashByIndex(seq);
return hash.isNonZero() &&
static_cast<bool>(app_.getNodeStore().fetchNodeObject(hash, seq));
};
for (auto const& interval : persistedRanges)
{
if (!hasLedgerData(interval.lower()) ||
!hasLedgerData(interval.upper()))
{
Throw<std::runtime_error>(
"Persisted pinned interval " +
std::to_string(interval.lower()) + "-" +
std::to_string(interval.upper()) +
" is not present in the currently configured pinned store");
}
}
JLOG(journal_.info()) << "Loaded pinned ranges from database: "
<< rangesStr;
app_.getLedgerMaster().setPinnedLedgersRangeSet(persistedRanges);
}
void
SHAMapStoreImp::run()
{
@@ -600,19 +486,17 @@ SHAMapStoreImp::makeBackendRotating(std::string path)
Section section{app_.config().section(ConfigSection::nodeDatabase())};
boost::filesystem::path newPath;
if (!path.empty())
if (path.size())
{
newPath = path;
}
else
{
// Create new empty backend with unique path
boost::filesystem::path p = get(section, "path");
p /= dbPrefix_;
p += ".%%%%";
newPath = boost::filesystem::unique_path(p);
}
section.set("path", newPath.string());
auto backend{NodeStore::Manager::instance().make_Backend(
@@ -626,43 +510,56 @@ SHAMapStoreImp::makeBackendRotating(std::string path)
}
void
SHAMapStoreImp::clearSqlRanges(
SHAMapStoreImp::clearSql(
LedgerIndex lastRotated,
RangeSet<std::uint32_t> const& pinned,
std::string const& tableName,
std::string const& TableName,
std::function<std::optional<LedgerIndex>()> const& getMinSeq,
std::function<void(RangeSet<std::uint32_t> const&)> const& deleteInRanges)
std::function<void(LedgerIndex)> const& deleteBeforeSeq)
{
XRPL_ASSERT(
deleteInterval_,
"ripple::SHAMapStoreImp::clearSql : nonzero delete interval");
auto m = getMinSeq();
if (!m)
return;
LedgerIndex min = std::numeric_limits<LedgerIndex>::max();
if (healthWait() == stopping)
return;
// The pure interval-math is in detail::computeOnlineDeleteTargets so
// it can be unit-tested without spinning up SHAMapStoreImp. It
// returns the disjoint deletable intervals after subtracting pinned
// ranges from the base window [minSeq, lastRotated - 1].
auto const target =
detail::computeOnlineDeleteTargets(*m, lastRotated, pinned);
if (target.empty())
{
JLOG(journal_.trace()) << "Nothing to delete from " << tableName
<< " after considering pins.";
JLOG(journal_.trace())
<< "Begin: Look up lowest value of: " << TableName;
auto m = getMinSeq();
JLOG(journal_.trace()) << "End: Look up lowest value of: " << TableName;
if (!m)
return;
min = *m;
}
if (min > lastRotated || healthWait() == stopping)
return;
if (min == lastRotated)
{
// Micro-optimization mainly to clarify logs
JLOG(journal_.trace()) << "Nothing to delete from " << TableName;
return;
}
JLOG(journal_.debug()) << "Pruning " << tableName
<< ". Target ranges: " << to_string(target);
// The deleteInRanges lambda will handle the actual database operations
deleteInRanges(target);
JLOG(journal_.debug()) << "finished deleting from: " << tableName;
JLOG(journal_.debug()) << "start deleting in: " << TableName << " from "
<< min << " to " << lastRotated;
while (min < lastRotated)
{
min = std::min(lastRotated, min + deleteBatch_);
JLOG(journal_.trace())
<< "Begin: Delete up to " << deleteBatch_
<< " rows with LedgerSeq < " << min << " from: " << TableName;
deleteBeforeSeq(min);
JLOG(journal_.trace())
<< "End: Delete up to " << deleteBatch_ << " rows with LedgerSeq < "
<< min << " from: " << TableName;
if (healthWait() == stopping)
return;
if (min < lastRotated)
std::this_thread::sleep_for(backOff_);
if (healthWait() == stopping)
return;
}
JLOG(journal_.debug()) << "finished deleting from: " << TableName;
}
void
@@ -684,14 +581,6 @@ SHAMapStoreImp::freshenCaches()
void
SHAMapStoreImp::clearPrior(LedgerIndex lastRotated)
{
// Get pinned ranges to exclude from deletion
auto pinnedRanges = app_.getLedgerMaster().getPinnedLedgersRangeSet();
if (!pinnedRanges.empty())
{
JLOG(journal_.info())
<< "Online delete with pinned ranges: " << to_string(pinnedRanges);
}
// Do not allow ledgers to be acquired from the network
// that are about to be deleted.
minimumOnline_ = lastRotated + 1;
@@ -711,96 +600,36 @@ SHAMapStoreImp::clearPrior(LedgerIndex lastRotated)
if (app_.config().useTxTables())
{
clearSqlRanges(
clearSql(
lastRotated,
pinnedRanges,
"Transactions",
[&db]() -> std::optional<LedgerIndex> {
return db->getTransactionsMinLedgerSeq();
},
[this, &db](RangeSet<std::uint32_t> const& ranges) -> void {
for (auto const& interval : ranges)
{
// Simple delete loop with LIMIT
while (true)
{
if (healthWait() == stopping)
return;
auto deleted = db->deleteTransactionsInRange(
interval.lower(), interval.upper(), deleteBatch_);
if (deleted == 0)
break;
JLOG(journal_.trace()) << "Deleted " << deleted
<< " rows from Transactions";
std::this_thread::sleep_for(backOff_);
}
}
[&db](LedgerIndex min) -> void {
db->deleteTransactionsBeforeLedgerSeq(min);
});
if (healthWait() == stopping)
return;
clearSqlRanges(
clearSql(
lastRotated,
pinnedRanges,
"AccountTransactions",
[&db]() -> std::optional<LedgerIndex> {
return db->getAccountTransactionsMinLedgerSeq();
},
[this, &db](RangeSet<std::uint32_t> const& ranges) -> void {
for (auto const& interval : ranges)
{
// Simple delete loop with LIMIT
while (true)
{
if (healthWait() == stopping)
return;
auto deleted = db->deleteAccountTransactionsInRange(
interval.lower(), interval.upper(), deleteBatch_);
if (deleted == 0)
break;
JLOG(journal_.trace())
<< "Deleted " << deleted
<< " rows from AccountTransactions";
std::this_thread::sleep_for(backOff_);
}
}
[&db](LedgerIndex min) -> void {
db->deleteAccountTransactionsBeforeLedgerSeq(min);
});
if (healthWait() == stopping)
return;
}
clearSqlRanges(
clearSql(
lastRotated,
pinnedRanges,
"Ledgers",
[db]() -> std::optional<LedgerIndex> { return db->getMinLedgerSeq(); },
[this, db](RangeSet<std::uint32_t> const& ranges) -> void {
for (auto const& interval : ranges)
{
// Simple delete loop with LIMIT
while (true)
{
if (healthWait() == stopping)
return;
auto deleted = db->deleteLedgersInRange(
interval.lower(), interval.upper(), deleteBatch_);
if (deleted == 0)
break;
JLOG(journal_.trace())
<< "Deleted " << deleted << " rows from Ledgers";
std::this_thread::sleep_for(backOff_);
}
}
});
[db](LedgerIndex min) -> void { db->deleteBeforeLedgerSeq(min); });
if (healthWait() == stopping)
return;
}
+3 -38
View File
@@ -24,10 +24,8 @@
#include <xrpld/app/misc/SHAMapStore.h>
#include <xrpld/app/rdb/RelationalDatabase.h>
#include <xrpld/app/rdb/State.h>
#include <xrpld/app/rdb/backend/SQLiteDatabase.h>
#include <xrpld/core/DatabaseCon.h>
#include <xrpld/nodestore/DatabaseRotating.h>
#include <xrpl/basics/RangeSet.h>
#include <xrpld/nodestore/Scheduler.h>
#include <atomic>
@@ -69,10 +67,6 @@ private:
setState(SavedState const& state);
void
setLastRotated(LedgerIndex seq);
std::string
getPinnedRanges();
void
setPinnedRanges(std::string const& ranges);
};
Application& app_;
@@ -138,12 +132,6 @@ public:
: fetch_depth;
}
bool
isOnlineDeleteEnabled() const
{
return deleteInterval_ > 0;
}
std::unique_ptr<NodeStore::Database>
makeNodeStore(int readThreads) override;
@@ -180,14 +168,6 @@ public:
void
onLedgerClosed(std::shared_ptr<Ledger const> const& ledger) override;
void
setPinnedRanges(RangeSet<std::uint32_t> const& ranges) override
{
// Save pinned ranges to database for persistence
std::string rangesStr = to_string(ranges);
state_db_.setPinnedRanges(rangesStr);
}
void
rendezvous() const override;
int
@@ -230,14 +210,11 @@ private:
* Call with mutex object unlocked.
*/
void
clearSqlRanges(
clearSql(
LedgerIndex lastRotated,
RangeSet<std::uint32_t> const& pinned,
std::string const& tableName,
std::string const& TableName,
std::function<std::optional<LedgerIndex>()> const& getMinSeq,
std::function<void(RangeSet<std::uint32_t> const&)> const&
deleteInRanges);
std::function<void(LedgerIndex)> const& deleteBeforeSeq);
void
clearCaches(LedgerIndex validatedSeq);
void
@@ -256,24 +233,12 @@ private:
[[nodiscard]] HealthResult
healthWait();
void
loadPinnedRanges();
public:
void
start() override
{
// Always load pinned ranges on startup
loadPinnedRanges();
// NOTE: Startup cleanup of unpinned ledgers was removed - it caused
// SQLite lock contention and confusing pauses. Database will shrink
// incrementally during rotation. Custom tools will provide pruning
// management for specialized needs.
if (deleteInterval_)
{
thread_ = std::thread(&SHAMapStoreImp::run, this);
}
}
void
-25
View File
@@ -148,31 +148,6 @@ ammLPHolds(
// Put balance in account terms.
amount.negate();
}
// If tokens can be escrowed then they can be locked in the trustline
// which means we must never spend them until the escrow is released.
if (view.rules().enabled(featurePaychanAndEscrowForTokens) &&
sle->isFieldPresent(sfLockedBalance))
{
STAmount const lockedBalance = sle->getFieldAmount(sfLockedBalance);
STAmount const spendableBalance = amount -
(lpAccount > ammAccount ? -lockedBalance : lockedBalance);
// RH NOTE: this is defensively programmed, it should never fire
// if something bad does happen the trustline acts as a frozen line.
if (spendableBalance < beast::zero || spendableBalance > amount)
{
// LCOV_EXCL_START
JLOG(j.error())
<< "SpendableBalance has illegal value in accountHolds "
<< spendableBalance;
amount.clear(Issue{currency, ammAccount});
// LCOV_EXCL_STOP
}
else
amount = spendableBalance;
}
amount.setIssuer(ammAccount);
JLOG(j.trace()) << "ammLPHolds:"
@@ -1,77 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2026 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_APP_MISC_DETAIL_ONLINE_DELETE_RANGES_H_INCLUDED
#define RIPPLE_APP_MISC_DETAIL_ONLINE_DELETE_RANGES_H_INCLUDED
#include <xrpl/basics/RangeSet.h>
#include <cstdint>
namespace ripple {
namespace detail {
/** Compute the actual SQL-deletion target windows for an online-delete
pass, given a base [minSeq, lastRotated - 1] window and the pinned
ranges that must be preserved.
The base window is the half-open span the caller would normally
delete (everything older than `lastRotated`, but no older than the
minimum sequence currently stored in the table being cleared).
Pinned ranges (catalogue history that must survive rotation) are
subtracted from the base — the result may be empty (everything is
pinned), one interval (no overlap, or pinned only trims an edge),
or multiple disjoint intervals (pins cut a hole in the base).
Pure function so the math is unit-testable without standing up
SHAMapStoreImp. The actual side-effecting deletions (driven by
a per-table lambda in clearSqlRanges) operate on whatever interval
set this helper returns.
@param minSeq The smallest sequence currently stored in the
table being cleared. If >= lastRotated, the
base window is empty and we return {}.
@param lastRotated The exclusive upper bound — sequences from
this seq forward are kept.
@param pinned Pinned-ledger ranges to preserve.
@return The set of intervals to actually delete. Empty if either
the base window is empty or pinned ranges fully cover it.
*/
inline RangeSet<std::uint32_t>
computeOnlineDeleteTargets(
std::uint32_t minSeq,
std::uint32_t lastRotated,
RangeSet<std::uint32_t> const& pinned)
{
// Empty base window: nothing newer than minSeq qualifies for
// deletion.
if (minSeq >= lastRotated)
return {};
RangeSet<std::uint32_t> target;
target.insert(range(minSeq, lastRotated - 1));
target -= pinned;
return target;
}
} // namespace detail
} // namespace ripple
#endif
+1 -1
View File
@@ -170,7 +170,7 @@ private:
std::chrono::steady_clock::time_point quick_reply_;
std::chrono::steady_clock::time_point full_reply_;
static unsigned int const max_paths_ = 5;
static unsigned int const max_paths_ = 4;
};
} // namespace ripple
+3 -5
View File
@@ -1324,13 +1324,11 @@ Pathfinder::initPathTable()
fillPaths(
pt_nonXRP_to_XRP,
{{1, "sxd"}, // gateway buys XRP
{2, "saxd"}, // source -> gateway -> book(XRP) -> dest
{5, "sabxd"}, // source -> gateway -> book -> book(XRP) -> dest
{{1, "sxd"}, // gateway buys XRP
{2, "saxd"}, // source -> gateway -> book(XRP) -> dest
{6, "saaxd"},
{7, "sbxd"},
{8,
"sabbxd"}, // source -> gateway -> book -> book -> book(XRP) -> dest
{8, "sabxd"},
{9, "sabaxd"}});
// non-XRP to non-XRP (same currency)
Loaded 100 of 137 files, more files were not shown because too many files have changed in this diff. Show more