mirror of
https://github.com/Xahau/xahaud.git
synced 2026-10-22 03:37:08 +00:00
Compare commits
47
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5b5efcc87 | ||
|
|
a77e6ee7fd | ||
|
|
67034cda77 | ||
|
|
aa1e4d38d3 | ||
|
|
d4417d3461 | ||
|
|
6f6e56952e | ||
|
|
e673cb5b80 | ||
|
|
cfb1b36bae | ||
|
|
628c942a98 | ||
|
|
4cb64497e9 | ||
|
|
96065296ab | ||
|
|
6ab87ed696 | ||
|
|
1b7c70d3ba | ||
|
|
ba2b188aaf | ||
|
|
7a78672983 | ||
|
|
14de6cc76d | ||
|
|
13f4313f64 | ||
|
|
2244fae464 | ||
|
|
cd00ed72d8 | ||
|
|
05a3e04f2d | ||
|
|
66f7294120 | ||
|
|
7f6ac75617 | ||
|
|
4150f0383c | ||
|
|
25123b370a | ||
|
|
f90ed41802 | ||
|
|
8c4c158d3a | ||
|
|
2d2951875d | ||
|
|
9bfca63574 | ||
|
|
1ba444ae7f | ||
|
|
f96d9b6e51 | ||
|
|
8d0e6eedea | ||
|
|
8cfee6c8a3 | ||
|
|
8673599d2b | ||
|
|
ec65e622aa | ||
|
|
65837f49e1 | ||
|
|
59580e2b28 | ||
|
|
e5b21f026e | ||
|
|
866119cf80 | ||
|
|
498f63651d | ||
|
|
4c5c9b14b7 | ||
|
|
689f3c07c9 | ||
|
|
407cc83241 | ||
|
|
97deee10ca | ||
|
|
0c8de81657 | ||
|
|
cb40a9d726 | ||
|
|
3d9f8aa7a9 | ||
|
|
d7fd2adb34 |
No files matched your search
@@ -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
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
} | (
|
||||
|
||||
@@ -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'
|
||||
@@ -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
|
||||
}
|
||||
'
|
||||
@@ -49,7 +49,4 @@
|
||||
#include "macro.h"
|
||||
#include "tts.h"
|
||||
|
||||
#include "ls_flags.h"
|
||||
#include "tx_flags.h"
|
||||
|
||||
#endif
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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)) \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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. **/
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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&
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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.
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+56
-1711
File diff suppressed because it is too large.
Load diff
+10
-483
@@ -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
@@ -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
|
||||
|
||||
@@ -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"});
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
|
||||
[[maybe_unused]] static Number
|
||||
static Number
|
||||
number(STAmount const& a)
|
||||
{
|
||||
if (isXRP(a))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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++;
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -69,6 +69,7 @@ class Hooks_test : public beast::unit_test::suite
|
||||
sfHookAccount,
|
||||
sfEmittedTxn,
|
||||
sfHook,
|
||||
sfHookDefinition,
|
||||
sfHookParameter,
|
||||
sfHookGrant,
|
||||
sfEmitDetails,
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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](
|
||||
|
||||
@@ -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
@@ -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() /
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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"))
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in new issue
Block a user