Compare commits

..
Author SHA1 Message Date
tequ 68387927e0 Merge branch 'dev' into fix/normal-tx-consequences-factory 2026-09-15 12:48:40 +09:00
tequ aab273c575 Use normal consequences factory for standard transactions
Replace redundant custom consequence factories that returned normal consequences with the built-in Normal factory.
2026-08-03 21:57:18 +09:00
45 changed files with 889 additions and 1944 deletions

No files matched your search

-9
View File
@@ -19,15 +19,6 @@ coverage:
default:
target: auto
threshold: 2%
paths:
# PeerImp is historically hard to exercise in the current unit-test
# harness. Keep this list narrow; new testable code should remain
# covered by the default patch gate.
- "!src/xrpld/overlay/detail/PeerImp.cpp"
historically-untested:
target: 0%
paths:
- "src/xrpld/overlay/detail/PeerImp.cpp"
changes: false
github_checks:
@@ -50,10 +50,6 @@ inputs:
options:
- libstdcxx
- libcxx
conan_deps_cxxflags:
description: 'Extra cxxflags applied to Conan dependency package builds only (NOT the rippled build). JSON object keyed by Conan package pattern, e.g. {"grpc/*":["-Wno-foo"]}. Maps to <pattern>:tools.build:cxxflags.'
required: false
default: '{}'
outputs:
cache-hit:
@@ -85,8 +81,6 @@ runs:
- name: Configure Conan
shell: bash
env:
CONAN_DEPS_CXXFLAGS: ${{ inputs.conan_deps_cxxflags }}
run: |
# Create the default profile directory if it doesn't exist
mkdir -p ~/.conan2/profiles
@@ -111,14 +105,7 @@ runs:
os=${{ inputs.os }}
EOF
# [buildenv] + [conf] sections.
# Linux pins compiler executables; macOS uses the system toolchain.
# conan_deps_cxxflags (matrix-driven) optionally adds package-pattern
# scoped tools.build:cxxflags for Conan dependency builds only - typically
# grpc workarounds for newer clang's stricter diagnostics. Because these
# are profile-pattern scoped (e.g. grpc/*:...), they do NOT affect the
# consumer/rippled toolchain generated for the main build.
NEED_CONF=0
# Add buildenv and conf sections for Linux (not needed for macOS)
if [ "${{ inputs.os }}" = "Linux" ] && [ -n "${{ inputs.cc }}" ]; then
cat >> ~/.conan2/profiles/default <<EOF
@@ -129,38 +116,16 @@ runs:
[conf]
tools.build:compiler_executables={"c": "/usr/bin/${{ inputs.cc }}", "cpp": "/usr/bin/${{ inputs.cxx }}"}
EOF
NEED_CONF=1
fi
if [ -n "${CONAN_DEPS_CXXFLAGS}" ] && [ "${CONAN_DEPS_CXXFLAGS}" != "{}" ]; then
CONAN_DEPS_CXXFLAGS_LINES="$(python3 - <<'PY'
import json
import os
import sys
# Add macOS-specific conf if needed
if [ "${{ inputs.os }}" = "Macos" ]; then
cat >> ~/.conan2/profiles/default <<EOF
raw = os.environ["CONAN_DEPS_CXXFLAGS"]
data = json.loads(raw)
if not isinstance(data, dict):
sys.exit("conan_deps_cxxflags must be a JSON object like {\"grpc/*\": [\"-Wno-...\"]}")
for pattern, flags in data.items():
if not isinstance(pattern, str) or not pattern:
sys.exit("conan_deps_cxxflags keys must be non-empty Conan package patterns")
if pattern == "&":
sys.exit("conan_deps_cxxflags must target dependency package patterns, not the consumer (&)")
if not isinstance(flags, list) or not all(isinstance(flag, str) for flag in flags):
sys.exit(f"{pattern}: cxxflags must be a JSON string list")
if flags:
print(f"{pattern}:tools.build:cxxflags={json.dumps(flags, separators=(',', ':'))}")
PY
)"
if [ -n "${CONAN_DEPS_CXXFLAGS_LINES}" ] && [ "$NEED_CONF" = "0" ]; then
echo "" >> ~/.conan2/profiles/default
echo "[conf]" >> ~/.conan2/profiles/default
fi
if [ -n "${CONAN_DEPS_CXXFLAGS_LINES}" ]; then
printf '%s\n' "${CONAN_DEPS_CXXFLAGS_LINES}" >> ~/.conan2/profiles/default
fi
[conf]
# Workaround for gRPC with newer Apple Clang
tools.build:cxxflags=["-Wno-missing-template-arg-list-after-template-kw"]
EOF
fi
# Display profile for verification
-3
View File
@@ -107,9 +107,6 @@ jobs:
compiler: apple-clang
compiler_version: ${{ steps.detect-compiler.outputs.compiler_version }}
stdlib: libcxx
# grpc 1.50.1 trips clang-19+ -Werror=missing-template-arg-list-after-template-kw
# on Apple Clang. Drop when grpc is bumped past the fix.
conan_deps_cxxflags: '{"grpc/*":["-Wno-missing-template-arg-list-after-template-kw"]}'
- name: Build
uses: ./.github/actions/xahau-ga-build
+22 -66
View File
@@ -72,26 +72,15 @@ jobs:
"job_type": "build"
},
{
# Latest stable Clang for the most accurate source-based
# coverage mapping (newer language features, fewer bugs in
# llvm-cov region inference). Pulled from apt.llvm.org since
# Ubuntu 24.04 default repos cap at clang-18.
"compiler_id": "clang-20-libcxx",
"compiler": "clang",
"cc": "clang-20",
"cxx": "clang++-20",
"compiler_version": 20,
"stdlib": "libcxx",
"compiler_id": "gcc-13-libstdcxx",
"compiler": "gcc",
"cc": "gcc-13",
"cxx": "g++-13",
"gcov": "gcov-13",
"compiler_version": 13,
"stdlib": "default",
"configuration": "Debug",
"job_type": "coverage",
"coverage_tool": "llvm",
"coverage_format": "lcov",
# grpc 1.50.1 uses `Foo::template Bar(...)` without an
# angle-bracket arg list; clang-19+ promoted that to
# -Werror. Drop when grpc is bumped past the fix.
"conan_deps_cxxflags": {
"grpc/*": ["-Wno-missing-template-arg-list-after-template-kw"]
}
"job_type": "coverage"
},
{
"compiler_id": "clang-14-libstdcxx-gcc11",
@@ -142,7 +131,7 @@ jobs:
# Minimal matrix for PRs and feature branches
minimal_matrix = [
full_matrix[1], # gcc-13 (middle-ground gcc)
full_matrix[2], # clang-20 llvm-cov coverage
full_matrix[2], # gcc-13 coverage
full_matrix[3] # clang-14 (mature, stable clang)
]
@@ -218,9 +207,9 @@ jobs:
# Select the appropriate matrix
if use_full:
if force_full:
print(f"Using FULL matrix (7 configs (build x6 + clang-20 llvm-cov coverage)) - forced by [ci-nix-full-matrix] tag")
print(f"Using FULL matrix (7 configs) - forced by [ci-nix-full-matrix] tag")
else:
print(f"Using FULL matrix (7 configs (build x6 + clang-20 llvm-cov coverage)) - targeting main branch")
print(f"Using FULL matrix (7 configs) - targeting main branch")
matrix = full_matrix
else:
print(f"Using MINIMAL matrix (3 configs) - feature branch/PR")
@@ -268,25 +257,9 @@ jobs:
- name: Install build dependencies
run: |
# Bump apt's default 3 retries; papers over short upstream blips
# like the recurring ppa.launchpadcontent.net outages.
echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries
apt-get update
apt-get install -y software-properties-common
add-apt-repository ppa:ubuntu-toolchain-r/test -y
# apt.llvm.org for Clang versions newer than what Ubuntu 24.04 ships
# (24.04 default repos cap at clang-18). The bootstrap script adds
# the LLVM apt source for the requested version and runs apt-get update.
if [ "${{ matrix.compiler }}" = "clang" ] && [ "${{ matrix.compiler_version }}" -ge 19 ]; then
apt-get install -y wget gnupg lsb-release
wget -qO /tmp/llvm.sh https://apt.llvm.org/llvm.sh
chmod +x /tmp/llvm.sh
# `all` installs clang + libllvm + lldb + lld + the llvm-N package
# (which provides llvm-profdata-N / llvm-cov-N for coverage runs).
/tmp/llvm.sh ${{ matrix.compiler_version }} all
fi
apt-get update
apt-get install -y git python3 python-is-python3 pipx
pipx ensurepath
@@ -359,16 +332,10 @@ jobs:
pipx install "conan>=2.0,<3"
echo "$HOME/.local/bin" >> $GITHUB_PATH
# Install coverage tooling
# Install gcovr for coverage jobs
if [ "${{ matrix.job_type }}" = "coverage" ]; then
pipx install "gcovr>=7,<9"
apt-get install -y curl lcov
if [ "${{ matrix.coverage_tool }}" = "llvm" ]; then
# Native LLVM source-based coverage: llvm-profdata + llvm-cov.
# The clang-N package doesn't pull these in; the llvm-N package does.
apt-get install -y "llvm-${{ matrix.compiler_version }}"
else
pipx install "gcovr>=7,<9"
fi
fi
- name: Check environment
@@ -381,15 +348,10 @@ jobs:
which ${{ matrix.cxx }} && ${{ matrix.cxx }} --version || echo "${{ matrix.cxx }} not found"
which ccache && ccache --version || echo "ccache not found"
# Check coverage tooling
# Check gcovr for coverage jobs
if [ "${{ matrix.job_type }}" = "coverage" ]; then
if [ "${{ matrix.coverage_tool }}" = "llvm" ]; then
which "llvm-profdata-${{ matrix.compiler_version }}" && "llvm-profdata-${{ matrix.compiler_version }}" --version || echo "llvm-profdata not found"
which "llvm-cov-${{ matrix.compiler_version }}" && "llvm-cov-${{ matrix.compiler_version }}" --version || echo "llvm-cov not found"
else
which gcov && gcov --version || echo "gcov not found"
which gcovr && gcovr --version || echo "gcovr not found"
fi
which gcov && gcov --version || echo "gcov not found"
which gcovr && gcovr --version || echo "gcovr not found"
fi
echo "---- Full Environment ----"
@@ -416,7 +378,6 @@ jobs:
cc: ${{ matrix.cc }}
cxx: ${{ matrix.cxx }}
stdlib: ${{ matrix.stdlib }}
conan_deps_cxxflags: ${{ matrix.conan_deps_cxxflags && toJson(matrix.conan_deps_cxxflags) || '{}' }}
gha_cache_enabled: 'false' # Disable caching for self hosted runner
- name: Build
@@ -449,9 +410,8 @@ 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.
# Use *_FLAGS_DEBUG so the build action's stdlib flag (e.g. -stdlib=libc++) in CMAKE_CXX_FLAGS isn't clobbered.
cmake-args: '-Dcoverage=ON -Dcoverage_tool=${{ matrix.coverage_tool }} -Dcoverage_format=${{ matrix.coverage_format }} -Dcoverage_test_parallelism=$(($(nproc)/2)) -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_CXX_FLAGS_DEBUG="-g -O0" -DCMAKE_C_FLAGS_DEBUG="-g -O0"'
# 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-target: 'coverage'
ccache_max_size: '100G'
@@ -483,26 +443,22 @@ jobs:
- name: Move coverage report
if: matrix.job_type == 'coverage'
shell: bash
env:
COVERAGE_FILE: ${{ matrix.coverage_tool == 'llvm' && 'coverage.lcov' || 'coverage.xml' }}
run: |
mv "${{ env.build_dir }}/${COVERAGE_FILE}" ./
echo "COVERAGE_FILE=${COVERAGE_FILE}" >> "$GITHUB_ENV"
mv "${{ env.build_dir }}/coverage.xml" ./
- name: Archive coverage report
if: matrix.job_type == 'coverage'
uses: actions/upload-artifact@v4
with:
name: ${{ env.COVERAGE_FILE }}-${{ matrix.compiler_id }}
path: ${{ env.COVERAGE_FILE }}
name: coverage.xml
path: coverage.xml
retention-days: 30
- name: Upload coverage report
if: matrix.job_type == 'coverage'
uses: codecov/codecov-action@v5
with:
files: ${{ env.COVERAGE_FILE }}
flags: ${{ matrix.coverage_tool }}
files: coverage.xml
fail_ci_if_error: true
disable_search: true
verbose: true
-156
View File
@@ -1,156 +0,0 @@
#[===================================================================[
Native LLVM source-based code coverage helper.
Drives the -fprofile-instr-generate / -fcoverage-mapping pipeline:
1. Run instrumented binary with LLVM_PROFILE_FILE=...%m-%p.profraw
2. llvm-profdata merge -sparse -> coverage.profdata
3. llvm-cov export/show/report -> final report
Output filename per coverage_format:
lcov -> coverage.lcov
json -> coverage.json
txt | text -> coverage.txt
html | html-details -> <NAME>/index.html
#]===================================================================]
include(CMakeParseArguments)
# Locate llvm-profdata / llvm-cov, preferring versioned variants matching the
# Clang we're building with so we don't accidentally pair clang-20 with
# llvm-cov-14 (profile format mismatch -> hard failure).
function(_find_llvm_cov_tools)
if(LLVM_PROFDATA_PATH AND LLVM_COV_PATH)
return()
endif()
string(REGEX MATCH "^[0-9]+" _major "${CMAKE_CXX_COMPILER_VERSION}")
set(_pd_names llvm-profdata)
set(_cov_names llvm-cov)
if(_major)
list(PREPEND _pd_names "llvm-profdata-${_major}")
list(PREPEND _cov_names "llvm-cov-${_major}")
endif()
# Only delegate to xcrun when the *compiler* is AppleClang. On macOS with
# Homebrew/system clang-N, xcrun would resolve to Xcode's llvm tools which
# could be a different version - exactly the mismatch we want to avoid.
if(APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
execute_process(COMMAND xcrun -f llvm-profdata
OUTPUT_VARIABLE _pd_xcrun OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET RESULT_VARIABLE _pd_rc)
if(_pd_rc EQUAL 0 AND _pd_xcrun)
set(LLVM_PROFDATA_PATH "${_pd_xcrun}" CACHE FILEPATH "llvm-profdata" FORCE)
endif()
execute_process(COMMAND xcrun -f llvm-cov
OUTPUT_VARIABLE _cov_xcrun OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET RESULT_VARIABLE _cov_rc)
if(_cov_rc EQUAL 0 AND _cov_xcrun)
set(LLVM_COV_PATH "${_cov_xcrun}" CACHE FILEPATH "llvm-cov" FORCE)
endif()
endif()
if(NOT LLVM_PROFDATA_PATH)
find_program(LLVM_PROFDATA_PATH NAMES ${_pd_names})
endif()
if(NOT LLVM_COV_PATH)
find_program(LLVM_COV_PATH NAMES ${_cov_names})
endif()
endfunction()
function(setup_target_for_coverage_llvm)
set(oneValueArgs NAME FORMAT)
set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
cmake_parse_arguments(Cov "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
_find_llvm_cov_tools()
if(NOT LLVM_PROFDATA_PATH)
message(FATAL_ERROR "llvm-profdata not found (needed for coverage_tool=llvm)")
endif()
if(NOT LLVM_COV_PATH)
message(FATAL_ERROR "llvm-cov not found (needed for coverage_tool=llvm)")
endif()
if(NOT Cov_FORMAT)
set(Cov_FORMAT lcov)
endif()
set(_profraw_dir "${PROJECT_BINARY_DIR}/${Cov_NAME}-profraw")
set(_profdata "${PROJECT_BINARY_DIR}/${Cov_NAME}.profdata")
# Resolve binary path: accept either an absolute path or a bare target name
# (resolved against PROJECT_BINARY_DIR). Splice the resolved path back into
# Cov_EXECUTABLE so the run command invokes it via absolute path - bare
# names aren't on PATH and the build dir isn't `.` either.
list(GET Cov_EXECUTABLE 0 _exec_name)
if(IS_ABSOLUTE "${_exec_name}")
set(_binary "${_exec_name}")
else()
set(_binary "${PROJECT_BINARY_DIR}/${_exec_name}")
list(REMOVE_AT Cov_EXECUTABLE 0)
list(PREPEND Cov_EXECUTABLE "${_binary}")
endif()
# llvm-cov takes a single -ignore-filename-regex; OR our excludes together.
set(_ignore_regex "")
foreach(EXC IN LISTS Cov_EXCLUDE)
if(_ignore_regex)
string(APPEND _ignore_regex "|")
endif()
string(APPEND _ignore_regex "${EXC}")
endforeach()
set(_filter "")
if(_ignore_regex)
set(_filter "-ignore-filename-regex='${_ignore_regex}'")
endif()
# Pick llvm-cov subcommand + output file for the requested format. Each
# branch builds a single shell command string that we'll hand to bash -c.
if(Cov_FORMAT STREQUAL "lcov")
set(_output "${PROJECT_BINARY_DIR}/coverage.lcov")
set(_report_sh "${LLVM_COV_PATH} export -instr-profile='${_profdata}' -format=lcov ${_filter} '${_binary}' > '${_output}'")
elseif(Cov_FORMAT STREQUAL "json")
set(_output "${PROJECT_BINARY_DIR}/coverage.json")
set(_report_sh "${LLVM_COV_PATH} export -instr-profile='${_profdata}' -format=text ${_filter} '${_binary}' > '${_output}'")
elseif(Cov_FORMAT STREQUAL "txt" OR Cov_FORMAT STREQUAL "text")
set(_output "${PROJECT_BINARY_DIR}/coverage.txt")
set(_report_sh "${LLVM_COV_PATH} report -instr-profile='${_profdata}' ${_filter} '${_binary}' > '${_output}'")
elseif(Cov_FORMAT STREQUAL "html" OR Cov_FORMAT STREQUAL "html-details")
set(_output "${PROJECT_BINARY_DIR}/${Cov_NAME}/index.html")
set(_report_sh "${LLVM_COV_PATH} show -instr-profile='${_profdata}' -format=html -output-dir='${PROJECT_BINARY_DIR}/${Cov_NAME}' ${_filter} '${_binary}'")
else()
message(FATAL_ERROR "coverage_tool=llvm: unsupported coverage_format '${Cov_FORMAT}' (use lcov|json|txt|html)")
endif()
set(_merge_sh "${LLVM_PROFDATA_PATH} merge -sparse -o '${_profdata}' '${_profraw_dir}'/*.profraw")
if(CODE_COVERAGE_VERBOSE)
message(STATUS "[coverage:llvm] binary: ${_binary}")
message(STATUS "[coverage:llvm] profraw: ${_profraw_dir}")
message(STATUS "[coverage:llvm] profdata: ${_profdata}")
message(STATUS "[coverage:llvm] format: ${Cov_FORMAT}")
message(STATUS "[coverage:llvm] output: ${_output}")
if(_ignore_regex)
message(STATUS "[coverage:llvm] ignore: ${_ignore_regex}")
endif()
message(STATUS "[coverage:llvm] merge: ${_merge_sh}")
message(STATUS "[coverage:llvm] report: ${_report_sh}")
endif()
# %m: hash of the binary, %p: pid. Wipe the dir up front so stale profraw
# files can't leak into a fresh merge.
add_custom_target(${Cov_NAME}
COMMAND ${CMAKE_COMMAND} -E rm -rf "${_profraw_dir}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${_profraw_dir}"
COMMAND ${CMAKE_COMMAND} -E env
"LLVM_PROFILE_FILE=${_profraw_dir}/rippled-%m-%p.profraw"
${Cov_EXECUTABLE} ${Cov_EXECUTABLE_ARGS}
COMMAND bash -c "${_merge_sh}"
COMMAND bash -c "${_report_sh}"
BYPRODUCTS ${_output}
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
DEPENDS ${Cov_DEPENDENCIES}
VERBATIM
COMMENT "Running llvm-cov (${Cov_FORMAT}) -> ${_output}"
)
endfunction()
-15
View File
@@ -11,21 +11,6 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
return()
endif()
if(coverage_tool STREQUAL "llvm")
include(CodeCoverageLLVM)
setup_target_for_coverage_llvm(
NAME coverage
FORMAT ${coverage_format}
EXECUTABLE rippled
EXECUTABLE_ARGS --unittest$<$<BOOL:${coverage_test}>:=${coverage_test}> --unittest-jobs ${coverage_test_parallelism} --quiet --unittest-log
EXCLUDE "src/test" "include/xrpl/beast/test" "include/xrpl/beast/unit_test" "${CMAKE_BINARY_DIR}/pb-xrpl.libpb"
DEPENDENCIES rippled
)
return()
endif()
# coverage_tool == "gcov" (default): existing gcovr-driven pipeline.
include(CodeCoverage)
# The instructions for these commands come from the `CodeCoverage` module,
+4 -6
View File
@@ -28,17 +28,15 @@ target_compile_options (opts
$<$<AND:$<BOOL:${is_gcc}>,$<COMPILE_LANGUAGE:CXX>>:-Wsuggest-override>
$<$<BOOL:${is_gcc}>:-Wno-maybe-uninitialized>
$<$<BOOL:${perf}>:-fno-omit-frame-pointer>
$<$<AND:$<BOOL:${is_gcc}>,$<BOOL:${coverage}>,$<STREQUAL:${coverage_tool},gcov>>:-g --coverage -fprofile-abs-path>
$<$<AND:$<BOOL:${is_clang}>,$<BOOL:${coverage}>,$<STREQUAL:${coverage_tool},gcov>>:-g --coverage>
$<$<AND:$<BOOL:${is_clang}>,$<BOOL:${coverage}>,$<STREQUAL:${coverage_tool},llvm>>:-g -fprofile-instr-generate -fcoverage-mapping>
$<$<AND:$<BOOL:${is_gcc}>,$<BOOL:${coverage}>>:-g --coverage -fprofile-abs-path>
$<$<AND:$<BOOL:${is_clang}>,$<BOOL:${coverage}>>:-g --coverage>
$<$<BOOL:${profile}>:-pg>
$<$<AND:$<BOOL:${is_gcc}>,$<BOOL:${profile}>>:-p>)
target_link_libraries (opts
INTERFACE
$<$<AND:$<BOOL:${is_gcc}>,$<BOOL:${coverage}>,$<STREQUAL:${coverage_tool},gcov>>:-g --coverage -fprofile-abs-path>
$<$<AND:$<BOOL:${is_clang}>,$<BOOL:${coverage}>,$<STREQUAL:${coverage_tool},gcov>>:-g --coverage>
$<$<AND:$<BOOL:${is_clang}>,$<BOOL:${coverage}>,$<STREQUAL:${coverage_tool},llvm>>:-g -fprofile-instr-generate -fcoverage-mapping>
$<$<AND:$<BOOL:${is_gcc}>,$<BOOL:${coverage}>>:-g --coverage -fprofile-abs-path>
$<$<AND:$<BOOL:${is_clang}>,$<BOOL:${coverage}>>:-g --coverage>
$<$<BOOL:${profile}>:-pg>
$<$<AND:$<BOOL:${is_gcc}>,$<BOOL:${profile}>>:-p>)
+1 -13
View File
@@ -29,25 +29,13 @@ if(is_gcc OR is_clang)
"Unit tests parallelism for the purpose of coverage report.")
set(coverage_format "html-details" CACHE STRING
"Output format of the coverage report.")
set(coverage_tool "gcov" CACHE STRING
"Coverage instrumentation tool: 'gcov' (default, gcc/clang via --coverage + gcovr) or 'llvm' (clang only, native source-based coverage via -fprofile-instr-generate).")
set_property(CACHE coverage_tool PROPERTY STRINGS "gcov" "llvm")
if(NOT coverage_tool MATCHES "^(gcov|llvm)$")
message(FATAL_ERROR "coverage_tool must be 'gcov' or 'llvm', got '${coverage_tool}'")
endif()
set(coverage_extra_args "" CACHE STRING
"Additional arguments to pass to gcovr (gcov tool only).")
"Additional arguments to pass to gcovr.")
set(coverage_test "" CACHE STRING
"On gcc & clang, the specific unit test(s) to run for coverage. Default is all tests.")
if(coverage_test AND NOT coverage)
set(coverage ON CACHE BOOL "gcc/clang only" FORCE)
endif()
# Validate after coverage_test may have flipped coverage on, otherwise
# `-Dcoverage_tool=llvm -Dcoverage_test=Foo` on gcc would silently slip
# past the Clang guard and produce a broken instrumentation combo.
if(coverage AND coverage_tool STREQUAL "llvm" AND NOT is_clang)
message(FATAL_ERROR "coverage_tool=llvm requires Clang (got ${CMAKE_CXX_COMPILER_ID})")
endif()
option(wextra "compile with extra gcc/clang warnings enabled" ON)
else()
set(profile OFF CACHE BOOL "gcc/clang only" FORCE)
+1 -7
View File
@@ -244,13 +244,7 @@
<< "Tried to accept/rollback but specified " \
"memory outside of the wasm instance " \
<< "limit when specifying a reason string"; \
if (!view.rules().enabled(fixHookExitOutOfBounds)) \
return OUT_OF_BOUNDS; \
/* fixHookExitOutOfBounds: terminate as a rollback so the \
failure is visible in HookExecution metadata */ \
hookCtx.result.exitType = hook_api::ExitType::ROLLBACK; \
hookCtx.result.exitCode = (int64_t)OUT_OF_BOUNDS; \
return RC_ROLLBACK; \
return OUT_OF_BOUNDS; \
} \
/* assembly script and some other languages use utf16 for strings \
*/ \
-60
View File
@@ -1,60 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2025 XRPL Labs
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_PROTOCOL_UTF8_H_INCLUDED
#define RIPPLE_PROTOCOL_UTF8_H_INCLUDED
#include <xrpl/basics/Slice.h>
namespace ripple {
/** Check that a byte sequence is well-formed UTF-8, excluding the two
noncharacters U+FFFE and U+FFFF.
Rejects:
- overlong encodings;
- UTF-16 surrogate halves (U+D800..U+DFFF);
- scalar values above U+10FFFF, and 0xF5..0xFF lead bytes;
- stray continuation bytes;
- multi-byte sequences truncated by the end of the buffer;
- U+FFFE and U+FFFF (EF BF BE, EF BF BF).
U+FFFE and U+FFFF are well-formed, but have no use in a URI or a name
and exist to be misread: U+FFFE is a byte-swapped BOM, so a client that
transcodes to UTF-16/32 and sniffs the byte order can be pushed into
decoding the rest of the string with the wrong endianness, and both are
commonly used as in-band sentinels. They have always been rejected by
URIToken and HookName validation, and this function preserves that.
This is consensus-critical. For every input the previous decoder could
evaluate without reading past the end of the buffer, the result is
identical to it. The only difference is that truncated trailing
sequences, where the previous decoder read out of bounds, are now always
rejected. Any further change to what this accepts needs an amendment.
@param s The bytes to check.
@note Total: every byte read is within s.
*/
bool
isValidUTF8(Slice const& s) noexcept;
} // namespace ripple
#endif
+2 -8
View File
@@ -34,11 +34,6 @@
// If you add an amendment here, then do not forget to increment `numFeatures`
// in include/xrpl/protocol/Feature.h.
XRPL_FIX (20261001, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FIX (HookNameValidation, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FIX (HookExitOutOfBounds, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (HookAPISType, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(HookOnV2_1, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(OnChainManifests, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (HookMap, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FIX (GuardDepth32, Supported::yes, VoteBehavior::DefaultNo)
@@ -50,7 +45,7 @@ XRPL_FEATURE(HookAPISerializedType240, Supported::yes, VoteBehavior::DefaultNo
XRPL_FEATURE(PermissionedDomains, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(DynamicNFT, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(Credentials, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(AMMClawback, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(AMMClawback, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(MPTokensV1, Supported::no, VoteBehavior::DefaultNo)
// InvariantsV1_1 will be changes to Supported::yes when all the
// invariants expected to be included under it are complete.
@@ -70,8 +65,7 @@ XRPL_FEATURE(XChainBridge, Supported::no, VoteBehavior::DefaultNo
XRPL_FEATURE(AMM, Supported::no, VoteBehavior::DefaultNo)
XRPL_FIX (ReducedOffersV1, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(HooksUpdate2, Supported::yes, VoteBehavior::DefaultNo)
// replaced to HookOnV2_1
XRPL_FEATURE(HookOnV2, Supported::no, 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)
+1 -3
View File
@@ -292,9 +292,7 @@ JSS(duration_us); // out: NetworkOPs
JSS(effective); // out: ValidatorList
// in: UNL
JSS(elapsed_seconds);
JSS(enabled); // out: AmendmentTable (on-ledger);
// ServerDefinitions (this server)
JSS(ledger_enabled); // out: ServerDefinitions (on-ledger)
JSS(enabled); // out: AmendmentTable
JSS(engine_result); // out: NetworkOPs, TransactionSign, Submit
JSS(engine_result_code); // out: NetworkOPs, TransactionSign, Submit
JSS(engine_result_message); // out: NetworkOPs, TransactionSign, Submit
+1 -7
View File
@@ -300,13 +300,7 @@ BaseHTTPPeer<Handler, Impl>::do_read(yield_context do_yield)
{
complete_ = false;
error_code ec;
// Do not treat an established keep-alive waiting for its next request as
// an unfinished loopback request.
if (request_count_ == 0)
start_timer();
else
boost::beast::get_lowest_layer(impl().stream_)
.expires_after(std::chrono::seconds(timeoutSeconds));
start_timer();
boost::beast::http::async_read(
impl().stream_, read_buf_, message_, do_yield[ec]);
cancel_timer();
-101
View File
@@ -1,101 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2025 XRPL Labs
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpl/protocol/UTF8.h>
namespace ripple {
bool
isValidUTF8(Slice const& s) noexcept
{
std::size_t const size = s.size();
// Markus Kuhn's utf8_check.c
// (https://www.cl.cam.ac.uk/~mgk25/ucs/utf8_check.c), the decoder
// URIToken has always used, with one change: each branch establishes
// that the whole sequence is present before reading any continuation
// byte, so a truncated tail is invalid rather than an out-of-bounds read.
std::size_t i = 0;
while (i < size)
{
std::uint8_t const c0 = s[i];
if (c0 < 0x80)
{
// 0xxxxxxx
++i;
continue;
}
if ((c0 & 0xE0) == 0xC0)
{
// 110xxxxx 10xxxxxx
if (size - i < 2)
return false;
if ((s[i + 1] & 0xC0) != 0x80)
return false;
if ((c0 & 0xFE) == 0xC0) // overlong
return false;
i += 2;
continue;
}
if ((c0 & 0xF0) == 0xE0)
{
// 1110xxxx 10xxxxxx 10xxxxxx
if (size - i < 3)
return false;
std::uint8_t const c1 = s[i + 1];
if ((c1 & 0xC0) != 0x80 || (s[i + 2] & 0xC0) != 0x80)
return false;
if (c0 == 0xE0 && (c1 & 0xE0) == 0x80) // overlong
return false;
if (c0 == 0xED && (c1 & 0xE0) == 0xA0) // UTF-16 surrogate
return false;
if (c0 == 0xEF && c1 == 0xBF &&
(s[i + 2] & 0xFE) == 0xBE) // U+FFFE or U+FFFF
return false;
i += 3;
continue;
}
if ((c0 & 0xF8) == 0xF0)
{
// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
if (size - i < 4)
return false;
std::uint8_t const c1 = s[i + 1];
if ((c1 & 0xC0) != 0x80 || (s[i + 2] & 0xC0) != 0x80 ||
(s[i + 3] & 0xC0) != 0x80)
return false;
if (c0 == 0xF0 && (c1 & 0xF0) == 0x80) // overlong
return false;
if (c0 > 0xF4 || (c0 == 0xF4 && c1 > 0x8F)) // > U+10FFFF
return false;
i += 4;
continue;
}
// Stray continuation byte or 0xF8..0xFF.
return false;
}
return true;
}
} // namespace ripple
+1 -5
View File
@@ -150,15 +150,12 @@ private:
env.fund(XRP(20'000), alice, bob, carol, gw1, gw2);
env.fund(XRP(20'000), dan);
env.close();
env.trust(USD1(20'000), alice, bob, carol, dan);
env.trust(USD2(1'000), alice, bob, carol, dan);
env.close();
env(pay(gw1, dan, USD1(10'050)));
env(pay(gw1, bob, USD1(50)));
env(pay(gw2, bob, USD2(50)));
env.close();
AMM ammDan(env, dan, XRP(10'000), USD1(10'050));
@@ -1993,7 +1990,6 @@ private:
env.fund(XRP(10'000), alice, carol, gw);
env.fund(XRP(10'000), bob);
env.close();
env.trust(USD(1'000), alice, bob, carol);
env.trust(BTC(1'000), alice, bob, carol);
env.trust(EUR(1'000), alice, bob, carol);
@@ -3645,7 +3641,7 @@ private:
USD(10'000),
false,
0,
ammCrtFee(env).drops() + 2 * env.current()->fees().base.drops(),
ammCrtFee(env).drops(),
std::nullopt,
std::nullopt,
ms,
+3 -3
View File
@@ -5004,8 +5004,8 @@ private:
alice,
STAmount{USD, UINT64_C(30'000'0000000003), -10}));
// alice XRP balance is 30,000initial - 50 ammcreate fee -
// 10drops ammcreate base fee - 10drops fee
BEAST_EXPECT(accountBalance(env, alice) == "29949999980");
// 10drops fee
BEAST_EXPECT(accountBalance(env, alice) == "29949999990");
},
std::nullopt,
0,
@@ -5081,7 +5081,7 @@ private:
(xrpBalance + drops(5)).getText());
BEAST_EXPECT(
accountBalance(env, alice) ==
std::to_string(29'950'000'000 - 2 * baseFee + 80));
std::to_string(29'950'000'000 - baseFee + 80));
},
std::nullopt,
0,
+319 -188
View File
@@ -3941,142 +3941,310 @@ public:
auto const _source_object =
*strUnHex("81140000000000000000000000000000000000000000");
std::vector<std::pair<uint32_t, std::string>> data_list = {
{// UINT16 Version
sfVersion.getCode(),
"10100001"},
{// UINT32 Sequence
sfSequence.getCode(),
"2400000001"},
// UINT64
{// Uint64 ExchangeRate
sfExchangeRate.getCode(),
"360000000000000001"},
{// UINT256 EmailHash
sfEmailHash.getCode(),
"4100000000000000000000000000000000"},
{// UINT256 ObjectID
sfObjectID.getCode(),
"5E000000000000000000000000000000000000000000000000000000000000000"
"0"},
{// Amount NativeAmount
sfAmount.getCode(),
"61999999999999999999999999999999999999999999999999999999999999999"
"999999999999999999999999999999999"},
{// Amount IOUAmount
sfAmount.getCode(),
"614999999999999999"},
{// Object AmountEntry
sfAmountEntry.getCode(),
"E05B614000000000000064E1"},
{// Array Memos
sfMemos.getCode(),
"F9EA7D04DEADBEEFE1F1"},
{// UINT8 TickSize
sfTickSize.getCode(),
"00101001"},
{// UINT160 TakerPaysCurrency
sfTakerPaysCurrency.getCode(),
"01110000000000000000000000005553440000000000"},
{// PathSet Paths
sfPaths.getCode(),
"0112300000000000000000000000005553440000000000054F6F784A58F9EFB0A"
"9EB90B83464F9D166461900"},
{// Vector256 Amendments
sfAmendments.getCode(),
"03132042426C4D4F1009EE67080A9B7965B44656D7714D104A72F9B4369F97ABF"
"044EE"},
{// Issue ClaimCurrency
sfClaimCurrency.getCode(),
"05180000000000000000000000005553440000000000054F6F784A58F9EFB0A9E"
"B90B83464F9D1664619"},
{// XChainBridge Native-Native
sfXChainBridge.getCode(),
"011914AE123A8556F3CF91154711376AFB0F894F832B3D000000000000"
"000000000000000000000000000014AE123A8556F3CF91154711376AFB"
"0F894F832B3D0000000000000000000000000000000000000000"},
{// Currency BaseAsset
sfBaseAsset.getCode(),
"011A0000000000000000000000005553440000000000"},
};
if (env.closed()->rules().enabled(fixHookAPISType))
{
// encodings the legacy parser measures incorrectly
data_list.insert(
data_list.end(),
{
// MPT is not supported yet
// {// Issue ClaimCurrency MPT
// sfClaimCurrency.getCode(),
// "0518AE123A8556F3CF91154711376AFB0F894F832B3D0000000000"
// "00000000000000000000000000000100000001"},
{// XChainBridge default door + IOU, Native
sfXChainBridge.getCode(),
"0119000000000000000000000000005553440000000000AE123A85"
"56F3CF91154711376AFB0F894F832B3D14AE123A8556F3CF911547"
"11376AFB0F894F832B3D00000000000000000000000000000000000"
"00000"},
});
}
bool const fixEnabled = env.closed()->rules().enabled(fixHookAPISType);
for (auto const& data : data_list)
{
// use UINT16
auto source_object = _source_object;
auto field_object = *strUnHex(data.second);
auto field_id = data.first;
auto field_object = *strUnHex("10100001");
// Version: 1
// test invalid insert: trailing byte after the field
auto ft = field_object;
ft.push_back(0);
auto const result3 = api.sto_emplace(source_object, ft, field_id);
// legacy parser ignores trailing bytes in the field
if (fixEnabled)
BEAST_EXPECTS(!result3.has_value(), data.second);
else
BEAST_EXPECTS(result3.has_value(), data.second);
auto const result = api.sto_emplace(
source_object, field_object, sfVersion.getCode());
BEAST_EXPECT(result.has_value());
BEAST_EXPECT(
result.value().size() ==
source_object.size() + field_object.size());
field_object.insert(
field_object.end(), source_object.begin(), source_object.end());
BEAST_EXPECT(result.value() == field_object);
// truncated field / source are only exercised after the fix;
// the legacy parser may read past the end of these buffers
if (!fixEnabled)
continue;
auto const fo = Blob{field_object.begin(), field_object.end() - 1};
auto const result1 = api.sto_emplace(source_object, fo, field_id);
BEAST_EXPECTS(!result1.has_value(), data.second);
auto const so =
Blob{source_object.begin(), source_object.end() - 1};
auto const result2 = api.sto_emplace(so, field_object, field_id);
BEAST_EXPECTS(!result2.has_value(), data.second);
auto const erase_result = api.sto_emplace(
result.value(), std::nullopt, sfVersion.getCode());
BEAST_EXPECT(erase_result.has_value());
BEAST_EXPECT(erase_result.value().size() == _source_object.size());
BEAST_EXPECT(erase_result.value() == _source_object);
}
for (auto const& data : data_list)
{
// use UINT32
auto source_object = _source_object;
auto field_object = *strUnHex(data.second);
auto field_id = data.first;
auto field_object = *strUnHex("2400000001");
// Sequence: 1
auto const result = api.sto_emplace(
source_object, field_object, sfSequence.getCode());
BEAST_EXPECT(result.has_value());
BEAST_EXPECT(
result.value().size() ==
source_object.size() + field_object.size());
field_object.insert(
field_object.end(), source_object.begin(), source_object.end());
BEAST_EXPECT(result.value() == field_object);
auto const erase_result = api.sto_emplace(
result.value(), std::nullopt, sfSequence.getCode());
BEAST_EXPECT(erase_result.has_value());
BEAST_EXPECT(erase_result.value().size() == _source_object.size());
BEAST_EXPECT(erase_result.value() == _source_object);
}
{
// use UINT64
auto source_object = _source_object;
auto field_object = *strUnHex("360000000000000001");
// ExchangeRate: 1
auto const result = api.sto_emplace(
source_object, field_object, sfExchangeRate.getCode());
BEAST_EXPECT(result.has_value());
BEAST_EXPECT(
result.value().size() ==
source_object.size() + field_object.size());
field_object.insert(
field_object.end(), source_object.begin(), source_object.end());
BEAST_EXPECT(result.value() == field_object);
auto const erase_result = api.sto_emplace(
result.value(), std::nullopt, sfExchangeRate.getCode());
BEAST_EXPECT(erase_result.has_value());
BEAST_EXPECT(erase_result.value().size() == _source_object.size());
BEAST_EXPECT(erase_result.value() == _source_object);
}
{
// use UINT128
auto source_object = _source_object;
auto field_object = *strUnHex("4100000000000000000000000000000000");
// EmailHash: 1
auto const result = api.sto_emplace(
source_object, field_object, sfEmailHash.getCode());
BEAST_EXPECT(result.has_value());
BEAST_EXPECT(
result.value().size() ==
source_object.size() + field_object.size());
field_object.insert(
field_object.end(), source_object.begin(), source_object.end());
BEAST_EXPECT(result.value() == field_object);
auto const erase_result = api.sto_emplace(
result.value(), std::nullopt, sfEmailHash.getCode());
BEAST_EXPECT(erase_result.has_value());
BEAST_EXPECT(erase_result.value().size() == _source_object.size());
BEAST_EXPECT(erase_result.value() == _source_object);
}
{
// use UINT256
auto source_object = _source_object;
auto field_object = *strUnHex(
"5E000000000000000000000000000000000000000000000000000000000000"
"0000");
// ObjectID:
// "0000000000000000000000000000000000000000000000000000000000000000"
auto const result = api.sto_emplace(
source_object, field_object, sfObjectID.getCode());
BEAST_EXPECT(result.has_value());
BEAST_EXPECT(
result.value().size() ==
source_object.size() + field_object.size());
field_object.insert(
field_object.end(), source_object.begin(), source_object.end());
BEAST_EXPECT(result.value() == field_object);
auto const erase_result = api.sto_emplace(
result.value(), std::nullopt, sfObjectID.getCode());
BEAST_EXPECT(erase_result.has_value());
BEAST_EXPECT(erase_result.value().size() == _source_object.size());
BEAST_EXPECT(erase_result.value() == _source_object);
}
{
// use AMOUNT
auto source_object = _source_object;
auto nativeamount = *strUnHex(
"61999999999999999999999999999999999999999999999999999999999999"
"999999999999999999999999999999999999");
auto iouamount = *strUnHex("614999999999999999");
for (auto field_object : {nativeamount, iouamount})
{
auto const result = api.sto_emplace(
source_object, field_object, sfAmount.getCode());
BEAST_EXPECT(result.has_value());
BEAST_EXPECT(
result.value().size() ==
source_object.size() + field_object.size());
field_object.insert(
field_object.end(),
source_object.begin(),
source_object.end());
BEAST_EXPECT(result.value() == field_object);
auto const erase_result = api.sto_emplace(
result.value(), std::nullopt, sfAmount.getCode());
BEAST_EXPECT(erase_result.has_value());
BEAST_EXPECT(
erase_result.value().size() == _source_object.size());
BEAST_EXPECT(erase_result.value() == _source_object);
}
}
{
// OBJECT
auto source_object = _source_object;
auto field_object = *strUnHex("E05B614000000000000064E1");
// {"AmountEntry": {"Amount": "100"}}
auto const result = api.sto_emplace(
source_object, field_object, sfAmountEntry.getCode());
BEAST_EXPECT(result.has_value());
BEAST_EXPECT(
result.value().size() ==
source_object.size() + field_object.size());
source_object.insert(
source_object.end(), field_object.begin(), field_object.end());
BEAST_EXPECT(result.value() == source_object);
auto const erase_result = api.sto_emplace(
result.value(), std::nullopt, sfAmountEntry.getCode());
BEAST_EXPECT(erase_result.has_value());
BEAST_EXPECT(erase_result.value().size() == _source_object.size());
BEAST_EXPECT(erase_result.value() == _source_object);
}
{
// ARRAY
auto source_object = _source_object;
auto field_object = *strUnHex("F9EA7D04DEADBEEFE1F1");
// {"Memos": [{"Memo":{ "MemoData": "DEADBEEF" }}]}
auto const result =
api.sto_emplace(source_object, field_object, field_id);
BEAST_EXPECTS(result.has_value(), data.second);
auto const r = result.value();
api.sto_emplace(source_object, field_object, sfMemos.getCode());
BEAST_EXPECT(result.has_value());
BEAST_EXPECT(
r.size() == source_object.size() + field_object.size());
field_object.insert(
field_id < sfAccount.getCode() ? field_object.end()
: field_object.begin(),
source_object.begin(),
source_object.end());
BEAST_EXPECT(r == field_object);
result.value().size() ==
source_object.size() + field_object.size());
source_object.insert(
source_object.end(), field_object.begin(), field_object.end());
BEAST_EXPECT(result.value() == source_object);
// test erase
auto const erase_result =
api.sto_emplace(result.value(), std::nullopt, field_id);
BEAST_EXPECTS(erase_result.has_value(), data.second);
auto const er = erase_result.value();
BEAST_EXPECT(er.size() == _source_object.size());
BEAST_EXPECT(er == _source_object);
auto const erase_result = api.sto_emplace(
result.value(), std::nullopt, sfMemos.getCode());
BEAST_EXPECT(erase_result.has_value());
BEAST_EXPECT(erase_result.value().size() == _source_object.size());
BEAST_EXPECT(erase_result.value() == _source_object);
}
{
// UINT8
auto source_object = _source_object;
auto field_object = *strUnHex("00101001");
// {"TickSize": 1}
auto const result = api.sto_emplace(
source_object, field_object, sfTickSize.getCode());
BEAST_EXPECT(result.has_value());
BEAST_EXPECT(
result.value().size() ==
source_object.size() + field_object.size());
source_object.insert(
source_object.end(), field_object.begin(), field_object.end());
BEAST_EXPECT(result.value() == source_object);
auto const erase_result = api.sto_emplace(
result.value(), std::nullopt, sfTickSize.getCode());
BEAST_EXPECT(erase_result.has_value());
BEAST_EXPECT(erase_result.value().size() == _source_object.size());
BEAST_EXPECT(erase_result.value() == _source_object);
}
{
// UINT160
auto source_object = _source_object;
auto field_object =
*strUnHex("01110000000000000000000000005553440000000000");
// {"TakerPaysCurrency": "0000000000000000000000005553440000000000"}
auto const result = api.sto_emplace(
source_object, field_object, sfTakerPaysCurrency.getCode());
BEAST_EXPECT(result.has_value());
BEAST_EXPECT(
result.value().size() ==
source_object.size() + field_object.size());
source_object.insert(
source_object.end(), field_object.begin(), field_object.end());
BEAST_EXPECT(result.value() == source_object);
auto const erase_result = api.sto_emplace(
result.value(), std::nullopt, sfTakerPaysCurrency.getCode());
BEAST_EXPECT(erase_result.has_value());
BEAST_EXPECT(erase_result.value().size() == _source_object.size());
BEAST_EXPECT(erase_result.value() == _source_object);
}
{
// PATHSET
auto source_object = _source_object;
auto field_object = *strUnHex(
"0112300000000000000000000000005553440000000000054F6F784A58F9EF"
"B0A9EB90B83464F9D166461900");
// {"Paths": [[{ "currency": "USD", "issuer":
// "rVnYNK9yuxBz4uP8zC8LEFokM2nqH3poc" }]]}
auto const result =
api.sto_emplace(source_object, field_object, sfPaths.getCode());
BEAST_EXPECT(result.has_value());
BEAST_EXPECT(
result.value().size() ==
source_object.size() + field_object.size());
source_object.insert(
source_object.end(), field_object.begin(), field_object.end());
BEAST_EXPECT(result.value() == source_object);
auto const erase_result = api.sto_emplace(
result.value(), std::nullopt, sfPaths.getCode());
BEAST_EXPECT(erase_result.has_value());
BEAST_EXPECT(erase_result.value().size() == _source_object.size());
BEAST_EXPECT(erase_result.value() == _source_object);
}
{
// VECTOR256
auto source_object = _source_object;
auto field_object = *strUnHex(
"03132042426C4D4F1009EE67080A9B7965B44656D7714D104A72F9B4369F97"
"ABF044EE");
// {"Amendments":["42426C4D4F1009EE67080A9B7965B44656D7714D104A72F9B4369F97ABF044EE"]}
auto const result = api.sto_emplace(
source_object, field_object, sfAmendments.getCode());
BEAST_EXPECT(result.has_value());
BEAST_EXPECT(
result.value().size() ==
source_object.size() + field_object.size());
source_object.insert(
source_object.end(), field_object.begin(), field_object.end());
BEAST_EXPECT(result.value() == source_object);
auto const erase_result = api.sto_emplace(
result.value(), std::nullopt, sfAmendments.getCode());
BEAST_EXPECT(erase_result.has_value());
BEAST_EXPECT(erase_result.value().size() == _source_object.size());
BEAST_EXPECT(erase_result.value() == _source_object);
}
{
// UINT96
}
{
// UINT384
}
{
// UINT512
}
}
@@ -4115,38 +4283,6 @@ public:
PARSE_ERROR);
}
{
// Invalid: wrapped array has an invalid end marker.
// should be "F9EA7D02BEEFE1F1" {Memos:[{Memo:{MemoData:"BEEF"}}]}
auto const invalid_end_marker = *strUnHex("F9EA7D02BEEFE100");
auto const result = api.sto_subarray(invalid_end_marker, 0);
if (env.closed()->rules().enabled(fixHookAPISType))
{
BEAST_EXPECT(!result.has_value());
BEAST_EXPECT(result.error() == PARSE_ERROR);
}
else
{
BEAST_EXPECT(result.value() == std::make_pair(1u, 6u));
}
}
{
// Invalid: the first element is valid but a following element is
// truncated.
auto const invalid_tail = *strUnHex("F9EA7D02BEEFE1EA7D02BEEFF1");
auto const result = api.sto_subarray(invalid_tail, 0);
if (env.closed()->rules().enabled(fixHookAPISType))
{
BEAST_EXPECT(!result.has_value());
BEAST_EXPECT(result.error() == PARSE_ERROR);
}
else
{
BEAST_EXPECT(result.value() == std::make_pair(1u, 6u));
}
}
{
// doesn't found
// { Memos: [{Memo: {MemoData: "BEEF"}}] }
@@ -4202,25 +4338,6 @@ public:
PARSE_ERROR);
}
{
// Invalid: the requested field is valid but a later field is
// truncated.
auto const data = *strUnHex(
"240000000181140000000000000000000000000000000000000000");
auto const invalid_tail = Blob{data.begin(), data.end() - 1};
auto const result =
api.sto_subfield(invalid_tail, sfSequence.getCode());
if (env.closed()->rules().enabled(fixHookAPISType))
{
BEAST_EXPECT(!result.has_value());
BEAST_EXPECT(result.error() == PARSE_ERROR);
}
else
{
BEAST_EXPECT(result.value() == std::make_pair(1u, 4u));
}
}
{
// doesn't found
// { Memo: {MemoData: "BEEF"} }
@@ -4280,17 +4397,34 @@ public:
// { AmountEntry: {Amount: "100"} }
auto const amountEntry = *strUnHex("E05B614000000000000064E1");
for (auto const& data : {memos, amounts, memo, amountEntry})
{
// Valid data
BEAST_EXPECT(api.sto_validate(data).value() == true);
BEAST_EXPECT(api.sto_validate(memos).value() == true);
BEAST_EXPECT(api.sto_validate(amounts).value() == true);
BEAST_EXPECT(api.sto_validate(memo).value() == true);
BEAST_EXPECT(api.sto_validate(amountEntry).value() == true);
// Invalid data
auto const partial_data = Blob{data.begin(), data.end() - 1};
BEAST_EXPECT(api.sto_validate(partial_data).value() == false);
auto const partial_data2 = Blob{data.begin() + 1, data.end()};
BEAST_EXPECT(api.sto_validate(partial_data2).value() == false);
}
// Invalid data
BEAST_EXPECT(
api.sto_validate(Bytes{0xFF, 0xFF, 0xFF, 0xFF}).value() == false);
Bytes const i_memos(&memos[0], &memos[memos.size() - 1]);
Bytes const i_amounts(&amounts[0], &amounts[amounts.size() - 1]);
Bytes const i_memo(&memo[0], &memo[memo.size() - 1]);
Bytes const i_amountEntry(
&amountEntry[0], &amountEntry[amountEntry.size() - 1]);
BEAST_EXPECT(api.sto_validate(i_memos).value() == false);
BEAST_EXPECT(api.sto_validate(i_amounts).value() == false);
BEAST_EXPECT(api.sto_validate(i_memo).value() == false);
BEAST_EXPECT(api.sto_validate(i_amountEntry).value() == false);
Bytes const i2_memos(&memos[1], &memos[memos.size()]);
Bytes const i2_amounts(&amounts[1], &amounts[amounts.size()]);
Bytes const i2_memo(&memo[1], &memo[memo.size()]);
Bytes const i2_amountEntry(
&amountEntry[1], &amountEntry[amountEntry.size()]);
BEAST_EXPECT(api.sto_validate(i_memos).value() == false);
BEAST_EXPECT(api.sto_validate(i_amounts).value() == false);
BEAST_EXPECT(api.sto_validate(i_memo).value() == false);
BEAST_EXPECT(api.sto_validate(i_amountEntry).value() == false);
}
void
@@ -4783,12 +4917,9 @@ public:
test_state_set(features);
test_sto_emplace(features);
test_sto_emplace(features - fixHookAPISType);
// test_sto_erase(features); // tested in test_sto_emplace
test_sto_subarray(features);
test_sto_subarray(features - fixHookAPISType);
test_sto_subfield(features);
test_sto_subfield(features - fixHookAPISType);
test_sto_validate(features);
test_trace(features);
+4 -4
View File
@@ -104,8 +104,8 @@ class LedgerMaster_test : public beast::unit_test::suite
startLegSeq, txnIndex);
BEAST_EXPECT(
*result ==
uint256("DCAECE52F028B9D0F7CA43CBE15AB4EF7B84A8EF5D455238992E1E"
"DF2BDA1637"));
uint256("0CC11AD1AD89661689F6B6148D82CB6A7101DA4D66EC843670262D"
"0B618F5745"));
}
// success (second tx)
{
@@ -114,8 +114,8 @@ class LedgerMaster_test : public beast::unit_test::suite
startLegSeq + 1, txnIndex);
BEAST_EXPECT(
*result ==
uint256("722BD5EC9E4AE36E724B6223F5A5887927AADFD0B18A3053AAE57F"
"434F8C9FE8"));
uint256("DCAECE52F028B9D0F7CA43CBE15AB4EF7B84A8EF5D455238992E1E"
"DF2BDA1637"));
}
}
+5 -5
View File
@@ -1174,11 +1174,11 @@ class MPToken_test : public beast::unit_test::suite
JsonOptions::none)[sfAffectedNodes.fieldName];
// Issuer got 10 in the transfer fees
BEAST_EXPECT(
meta[0u][sfModifiedNode.fieldName][sfFinalFields.fieldName]
meta[2u][sfModifiedNode.fieldName][sfFinalFields.fieldName]
[sfOutstandingAmount.fieldName] == "9990");
// Destination account got 9'990
BEAST_EXPECT(
meta[3u][sfModifiedNode.fieldName][sfFinalFields.fieldName]
meta[0u][sfModifiedNode.fieldName][sfFinalFields.fieldName]
[sfMPTAmount.fieldName] == "9990");
// Source account spent 10'000
BEAST_EXPECT(
@@ -1910,8 +1910,8 @@ class MPToken_test : public beast::unit_test::suite
env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
BEAST_EXPECTS(
txHash ==
"61041108F0DAD50BAFD2F7B1102AC70B283EBDCED194CEBAF04D0184E4A70B"
"02",
"42175D43E3D236A86B0F1B07182A1B2C78AD382372CFA09A48239C395FFFEE"
"30",
txHash);
Json::Value const meta = env.rpc("tx", txHash)[jss::result][jss::meta];
auto const id = meta[jss::mpt_issuance_id].asString();
@@ -1919,7 +1919,7 @@ class MPToken_test : public beast::unit_test::suite
BEAST_EXPECT(meta.isMember(jss::mpt_issuance_id));
BEAST_EXPECT(id == to_string(mptAlice.issuanceID()));
BEAST_EXPECTS(
id == "00000002AE123A8556F3CF91154711376AFB0F894F832B3D", id);
id == "00000001AE123A8556F3CF91154711376AFB0F894F832B3D", id);
}
void
+10 -10
View File
@@ -8279,16 +8279,16 @@ private:
// validate the emitted txn ids
std::vector<std::string> const txIds = {
"2FBEF981BAC322225D13C274D0118FECC1B93A89EDBC0AB3CBD8B30A1591024E",
"3C0A12F6A322486ABDA4180DC2B424BD252FD2FA6A33553B0B5D1526C020C475",
"AFFD55D757136FC0BE665F16E0D8CFBDC0D37CF85B5D3329BE1DBD3A44BB281C",
"BBE43C01068CE511DBF24F84A928E481451FF448AF2493E1A9BE099628D1F523",
"45970753F7B52448C23D66E5E19DBBC65F5BE6055C04AC3BA9896D7D67891DB1",
"97F4E09B2AEF4D798D378B1390B0624A68F304247B358334167E93217D502A16",
"770D3B786C8E3BEF560D9589B02557B0BDFC1C7FD15C6CF42E86068A6A411EFC",
"239DFA86EE8391418170D7FDF5AA8B8EEE0BBEF5E9F928BC29C23EEF6F1858AD",
"9FAA93AC6340C87B5C722FF0E8D4D3F39B9A0CA5E9799EC4CE316BFD5E379540",
"8672B2D8AA5E9EF86929B8662FE436541B2F739052732020B874BC304601C4DB",
"9610F73CDD6590EB6B3C82E5EC55D4B4C80CD7128B98AA556F7EC9DD96AE7056",
"2F4582A29272390C0C25A80D4A3BCE5A14ACE6D86D8D0CB2C57719EB6FA881AE",
"89A301CFEF0DD781AB9032A6A2DCE0937BC0119D2CDD06033B8B2FD80968E519",
"DD8721B59024E168480B4DF8F8E93778601F0BD2E77FC991F3DA1182F5AD8B1E",
"5D735C2EE3CB8289F8E11621FDC9565F9D6D67F3AE59D65332EACE591D67945F",
"F02470E01731C968881AF4CBDEC90BB9E1F7AB0BE1CC22AF15451FB6D191096D",
"8AD65E541DECD49B1693F8C17DFD8A2B906F49C673C4FD2034FF772E2BE50C30",
"9F225229059CCC6257814D03C107884CF588C1C246A89ADFC16E50DF671B834C",
"13C2A54A14BADF3648CED05175E1CCAD713F7E5EA56D9735CF8813CD5551F281",
"87C60F41A96554587CED289F83F52DEE3CF670EEB189B067E6066B9A06056ADF",
};
Json::Value params;
params[jss::transaction] =
+78 -540
View File
@@ -1355,16 +1355,11 @@ public:
auto const baseFee = drops[jss::base_fee_no_hooks];
BEAST_EXPECT(baseFee == to_string(feeDrops));
auto const openLedgerFee = drops[jss::open_ledger_fee];
BEAST_EXPECTS(
openLedgerFee == expected,
"openLedgerFee: " + openLedgerFee.asString() +
", expected: " + expected);
BEAST_EXPECT(openLedgerFee == expected);
// verify hooks fee
auto const hooksFee = jrr[jss::result][jss::fee_hooks_feeunits];
BEAST_EXPECTS(
hooksFee == expected,
"hooksFee: " + hooksFee.asString() + ", expected: " + expected);
BEAST_EXPECT(hooksFee == expected);
}
void
@@ -1374,8 +1369,7 @@ public:
using namespace jtx;
Env env{*this, features};
bool const hookOnV2 = env.current()->rules().enabled(featureHookOnV2) ||
env.current()->rules().enabled(featureHookOnV2_1);
bool const hookOnV2 = env.current()->rules().enabled(featureHookOnV2);
auto const alice = Account{"alice"};
auto const bob = Account{"bob"};
@@ -1538,10 +1532,12 @@ public:
jv.removeMember(jss::HookOn);
jv[jss::HookOnIncoming] =
"0000000000000000000000000000000000000000000000000000000000"
"000000";
"0000"
"00";
jv[jss::HookOnOutgoing] =
"0000000000000000000000000000000000000000000000000000000000"
"000001";
"0000"
"01";
env(ripple::test::jtx::hook(alice, {{jv}}, 0),
M("Execution: Install"),
HSFEE);
@@ -1606,10 +1602,12 @@ public:
jv.removeMember(jss::HookOn);
jv[jss::HookOnIncoming] =
"fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff"
"bfffff"; // Invoke high
"bfff"
"ff"; // Invoke high
jv[jss::HookOnOutgoing] =
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
"bffffe"; // Payment high
"bfff"
"fe"; // Payment high
env(ripple::test::jtx::hook(alice, {{jv}}, 0),
M("Execution: Install"),
HSFEE);
@@ -1620,7 +1618,8 @@ public:
jv[jss::Flags] = hsfOVERRIDE;
jv[jss::HookOn] =
"0000000000000000000000000000000000000000000000000000000000"
"000000";
"0000"
"00";
env(ripple::test::jtx::hook(alice, {{jv}}, 0),
M("Execution: Install"),
HSFEE);
@@ -1654,285 +1653,18 @@ public:
deleteHook(alice);
}
for (auto const& withFix : {false, true})
{
// test featureHookOnV2_1 preflight
auto f = features - featureHookOnV2 - featureHookOnV2_1;
if (withFix)
f = f | featureHookOnV2_1;
else
f = f | featureHookOnV2;
Env env{*this, f};
env.fund(XRP(10000), alice, bob);
env.close();
TER const expected =
env.current()->rules().enabled(featureHookOnV2_1)
? TER(temMALFORMED)
: TER(tesSUCCESS);
// 0. only one direction HookOn is not allowed
for (auto const& key : {jss::HookOnIncoming, jss::HookOnOutgoing})
{
auto noopJv = Json::Value{};
noopJv[key] =
"fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff"
"bfffff";
env(ripple::test::jtx::hook(alice, {{noopJv}}, 0),
M("Lone direction HookOn NOOP"),
HSFEE,
ter(expected));
env.close();
if (!withFix)
BEAST_EXPECT(!env.le(keylet::hook(alice)));
}
// 1. Create Hook with HookOnIncoming/Outgoing to alice
auto jv = hso(accept_wasm);
jv.removeMember(jss::HookOn);
jv[jss::HookOnIncoming] =
"fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff"
"bfffff"; // Invoke high
jv[jss::HookOnOutgoing] =
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
"bffffe"; // Payment high
env(ripple::test::jtx::hook(alice, {{jv}}, 0), HSFEE);
env.close();
// 2. Install Hook with HookOn, Incoming/Outgoing to bob
jv = Json::Value{};
jv[jss::HookHash] = accept_hash_str;
jv[jss::Flags] = hsfOVERRIDE;
jv[jss::HookOn] =
"0000000000000000000000000000000000000000000000000000000000"
"000000";
jv[jss::HookOnIncoming] =
"fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff"
"bfffff"; // Invoke high
jv[jss::HookOnOutgoing] =
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
"bffffe"; // Payment high
env(ripple::test::jtx::hook(bob, {{jv}}, 0), HSFEE, ter(expected));
env.close();
// 3. Update Hook with HookOn, Incoming/Outgoing to alice
jv = Json::Value{};
jv[jss::HookOn] =
"0000000000000000000000000000000000000000000000000000000000"
"000000";
jv[jss::HookOnIncoming] =
"fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff"
"bfffff"; // Invoke high
jv[jss::HookOnOutgoing] =
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
"bffffe"; // Payment high
env(ripple::test::jtx::hook(alice, {{jv}}, 0),
HSFEE,
ter(expected));
env.close();
}
for (auto const& withFix : {false, true})
{
// test featureHookOnV2_1 install/update
auto f = features - featureHookOnV2 - featureHookOnV2_1;
if (withFix)
f = f | featureHookOnV2_1;
else
f = f | featureHookOnV2;
{
// install:: HookDefinition: HookOn -> Hook:Incoming/Outgoing
// update:: Hook: HookOn -> Hook:Incoming/Outgoing
Env env{*this, f};
env.fund(XRP(10000), alice, bob);
env.close();
// Create Hook with HookOn to alice
auto jv = hso(accept_wasm);
jv.removeMember(jss::HookOn);
jv[jss::HookOn] =
"0000000000000000000000000000000000000000000000000000000000"
"000000";
env(ripple::test::jtx::hook(alice, {{jv}}, 0), HSFEE);
env.close();
// Install Hook with Incoming/Outgoing to bob
jv = Json::Value{};
jv[jss::HookHash] = accept_hash_str;
jv[jss::Flags] = hsfOVERRIDE;
jv[jss::HookOnIncoming] =
"fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff"
"bfffff"; // Invoke high
jv[jss::HookOnOutgoing] =
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
"bffffe"; // Payment high
env(ripple::test::jtx::hook(bob, {{jv}}, 0), HSFEE);
env.close();
{
auto const hooksObj = env.le(keylet::hook(bob));
BEAST_EXPECT(hooksObj && hooksObj->isFieldPresent(sfHooks));
auto const& hooks = hooksObj->getFieldArray(sfHooks);
BEAST_EXPECT(hooks.size() == 1);
auto const& h = hooks[0];
BEAST_EXPECT(!h.isFieldPresent(sfHookOn));
BEAST_EXPECT(h.isFieldPresent(sfHookOnOutgoing));
BEAST_EXPECT(h.isFieldPresent(sfHookOnIncoming));
}
// Update Hook with HookOn, Incoming/Outgoing to alice
jv = Json::Value{};
jv[jss::HookOnIncoming] =
"fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff"
"bfffff"; // Invoke high
jv[jss::HookOnOutgoing] =
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
"bffffe"; // Payment high
env(ripple::test::jtx::hook(alice, {{jv}}, 0), HSFEE);
env.close();
{
auto const hooksObj = env.le(keylet::hook(alice));
BEAST_EXPECT(hooksObj && hooksObj->isFieldPresent(sfHooks));
auto const& hooks = hooksObj->getFieldArray(sfHooks);
BEAST_EXPECT(hooks.size() == 1);
auto const& h = hooks[0];
BEAST_EXPECT(!h.isFieldPresent(sfHookOn));
BEAST_EXPECT(h.isFieldPresent(sfHookOnOutgoing));
BEAST_EXPECT(h.isFieldPresent(sfHookOnIncoming));
}
// Re-update Hook with HookOn, Incoming/Outgoing to alice
jv = Json::Value{};
jv[jss::HookOn] =
"fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff"
"bfffff";
env(ripple::test::jtx::hook(alice, {{jv}}, 0), HSFEE);
env.close();
{
auto const hooksObj = env.le(keylet::hook(alice));
BEAST_EXPECT(hooksObj && hooksObj->isFieldPresent(sfHooks));
auto const& hooks = hooksObj->getFieldArray(sfHooks);
BEAST_EXPECT(hooks.size() == 1);
auto const& h = hooks[0];
BEAST_EXPECT(h.isFieldPresent(sfHookOn));
if (withFix)
{
BEAST_EXPECT(!h.isFieldPresent(sfHookOnOutgoing));
BEAST_EXPECT(!h.isFieldPresent(sfHookOnIncoming));
}
else
{
BEAST_EXPECT(h.isFieldPresent(sfHookOnOutgoing));
BEAST_EXPECT(h.isFieldPresent(sfHookOnIncoming));
}
}
}
{
// install:: HookDefinition: Incoming/Outgoing -> Hook:HookOn
// update:: Hook: Incoming/Outgoing -> Hook:HookOn
Env env{*this, f};
env.fund(XRP(10000), alice, bob);
env.close();
// Create Hook with HookOn to alice
auto jv = hso(accept_wasm);
jv.removeMember(jss::HookOn);
jv[jss::HookOnIncoming] =
"fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff"
"bfffff"; // Invoke high
jv[jss::HookOnOutgoing] =
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
"bffffe"; // Payment high
env(ripple::test::jtx::hook(alice, {{jv}}, 0), HSFEE);
env.close();
// Install Hook with Incoming/Outgoing to bob
jv = Json::Value{};
jv[jss::HookHash] = accept_hash_str;
jv[jss::Flags] = hsfOVERRIDE;
jv[jss::HookOn] =
"0000000000000000000000000000000000000000000000000000000000"
"000000";
env(ripple::test::jtx::hook(bob, {{jv}}, 0), HSFEE);
env.close();
{
auto const hooksObj = env.le(keylet::hook(bob));
BEAST_EXPECT(hooksObj && hooksObj->isFieldPresent(sfHooks));
auto const& hooks = hooksObj->getFieldArray(sfHooks);
BEAST_EXPECT(hooks.size() == 1);
auto const& h = hooks[0];
BEAST_EXPECT(h.isFieldPresent(sfHookOn));
BEAST_EXPECT(!h.isFieldPresent(sfHookOnOutgoing));
BEAST_EXPECT(!h.isFieldPresent(sfHookOnIncoming));
}
// Update Hook with HookOn, Incoming/Outgoing to alice
jv = Json::Value{};
jv[jss::HookOn] =
"0000000000000000000000000000000000000000000000000000000000"
"000000";
env(ripple::test::jtx::hook(alice, {{jv}}, 0), HSFEE);
env.close();
{
auto const hooksObj = env.le(keylet::hook(alice));
BEAST_EXPECT(hooksObj && hooksObj->isFieldPresent(sfHooks));
auto const& hooks = hooksObj->getFieldArray(sfHooks);
BEAST_EXPECT(hooks.size() == 1);
auto const& h = hooks[0];
if (withFix)
BEAST_EXPECT(h.isFieldPresent(sfHookOn));
// Cause different results between gcc and clang due to
// undefined behavior so we don't test it
// else
// BEAST_EXPECT(!h.isFieldPresent(sfHookOn));
BEAST_EXPECT(!h.isFieldPresent(sfHookOnOutgoing));
BEAST_EXPECT(!h.isFieldPresent(sfHookOnIncoming));
}
// Re-update Hook with HookOn, Incoming/Outgoing to alice
jv = Json::Value{};
jv[jss::HookOnIncoming] =
"fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff"
"bffff0"; // Invoke high
jv[jss::HookOnOutgoing] =
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
"bffff1"; // Payment high
env(ripple::test::jtx::hook(alice, {{jv}}, 0), HSFEE);
env.close();
{
auto const hooksObj = env.le(keylet::hook(alice));
BEAST_EXPECT(hooksObj && hooksObj->isFieldPresent(sfHooks));
auto const& hooks = hooksObj->getFieldArray(sfHooks);
BEAST_EXPECT(hooks.size() == 1);
auto const& h = hooks[0];
if (withFix)
BEAST_EXPECT(!h.isFieldPresent(sfHookOn));
// Cause different results between gcc and clang due to
// undefined behavior so we don't test it
// else
// BEAST_EXPECT(h.isFieldPresent(sfHookOn));
BEAST_EXPECT(h.isFieldPresent(sfHookOnOutgoing));
BEAST_EXPECT(h.isFieldPresent(sfHookOnIncoming));
}
}
}
// Fee RPC
{
auto jv = hso(accept_wasm);
jv.removeMember(jss::HookOn);
jv[jss::HookOnIncoming] =
"fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff"
"bfffff"; // Invoke high
"bfff"
"ff"; // Invoke high
jv[jss::HookOnOutgoing] =
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
"bffffe"; // Payment high
"bfff"
"fe"; // Payment high
env(ripple::test::jtx::hook(alice, {{jv}}, 0), HSFEE);
env.close();
@@ -2268,64 +2000,11 @@ public:
BEAST_EXPECT(!env.meta()->isFieldPresent(sfHookEmissions));
}
// Call named hook with the wrong hook name (size == 0)
for (auto const fix : {true, false})
{
auto f = features - fixHookNameValidation;
if (fix)
f = f | fixHookNameValidation;
Env env{*this, f};
env.fund(XRP(10000), alice);
// execute both named and non-named hooks
auto jv = invoke::invoke(alice);
jv[jss::HookName] = "";
auto const expected = fix ? ter(temMALFORMED) : ter(tesSUCCESS);
env(jv,
M("Call named hook with the wrong hook name (size == 0)"),
HSFEE,
ter(expected));
env.close();
}
// Call a named hook with a HookName containing a noncharacter
// (U+FFFF followed by 'A'). Well-formed UTF-8, but always rejected.
{
Env env{*this, features};
env.fund(XRP(10000), alice);
auto jv = invoke::invoke(alice);
jv[jss::HookName] = "EFBFBF41";
env(jv,
M("Call named hook with a noncharacter in the hook name"),
HSFEE,
ter(temMALFORMED));
env.close();
}
// Call a named hook with a HookName ending in a truncated sequence.
{
Env env{*this, features};
env.fund(XRP(10000), alice);
auto jv = invoke::invoke(alice);
jv[jss::HookName] = "414243E0";
env(jv,
M("Call named hook with a truncated hook name"),
HSFEE,
ter(temMALFORMED));
env.close();
}
// Call named hook with the wrong hook name (size > 0)
// 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 (size > 0)"),
HSFEE);
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));
@@ -3324,37 +3003,25 @@ public:
testInferHookSetOperation()
{
testcase("Test operation inference");
using namespace jtx;
auto const alice = Account{"alice"};
Env env{*this};
env.fund(XRP(10000), alice);
env(noop(alice));
SetHookCtx shCtx{
.j = env.app().journal("SetHook"),
.tx = *(env.tx()),
.app = env.app(),
.rules = env.current()->rules()};
// hsoNOOP
{
STObject hso{sfHook};
BEAST_EXPECT(SetHook::inferOperation(shCtx, hso) == hsoNOOP);
BEAST_EXPECT(SetHook::inferOperation(hso) == hsoNOOP);
}
// hsoCREATE
{
STObject hso{sfHook};
hso.setFieldVL(sfCreateCode, {1}); // non-empty create code
BEAST_EXPECT(SetHook::inferOperation(shCtx, hso) == hsoCREATE);
BEAST_EXPECT(SetHook::inferOperation(hso) == hsoCREATE);
}
// hsoDELETE
{
STObject hso{sfHook};
hso.setFieldVL(sfCreateCode, ripple::Blob{}); // empty create code
BEAST_EXPECT(SetHook::inferOperation(shCtx, hso) == hsoDELETE);
BEAST_EXPECT(SetHook::inferOperation(hso) == hsoDELETE);
}
// hsoINSTALL
@@ -3362,7 +3029,7 @@ public:
STObject hso{sfHook};
hso.setFieldH256(
sfHookHash, uint256{beast::zero}); // all zeros hook hash
BEAST_EXPECT(SetHook::inferOperation(shCtx, hso) == hsoINSTALL);
BEAST_EXPECT(SetHook::inferOperation(hso) == hsoINSTALL);
}
// hsoNSDELETE
@@ -3371,14 +3038,14 @@ public:
hso.setFieldH256(
sfHookNamespace, uint256{beast::zero}); // all zeros hook hash
hso.setFieldU32(sfFlags, hsfNSDELETE);
BEAST_EXPECT(SetHook::inferOperation(shCtx, hso) == hsoNSDELETE);
BEAST_EXPECT(SetHook::inferOperation(hso) == hsoNSDELETE);
}
// hsoUPDATE
{
STObject hso{sfHook};
hso.setFieldH256(sfHookOn, UINT256_BIT[0]);
BEAST_EXPECT(SetHook::inferOperation(shCtx, hso) == hsoUPDATE);
BEAST_EXPECT(SetHook::inferOperation(hso) == hsoUPDATE);
}
// hsoINVALID
@@ -3387,39 +3054,7 @@ public:
hso.setFieldVL(sfCreateCode, {1}); // non-empty create code
hso.setFieldH256(
sfHookHash, uint256{beast::zero}); // all zeros hook hash
BEAST_EXPECT(SetHook::inferOperation(shCtx, hso) == hsoINVALID);
for (auto fix : {true, false})
{
auto feature = supported_amendments() - featureHookOnV2 -
featureHookOnV2_1;
if (fix)
feature = feature | featureHookOnV2_1;
else
feature = feature | featureHookOnV2;
Env env{*this, feature};
SetHookCtx shCtx{
.j = env.app().journal("SetHook"),
.tx = *env.tx(),
.app = env.app(),
.rules = env.current()->rules()};
STObject hso2{sfHook};
hso2.setFieldH256(
sfHookOnOutgoing,
uint256{beast::zero}); // all zeros hook on outgoing
BEAST_EXPECT(
SetHook::inferOperation(shCtx, hso2) ==
(fix ? hsoINVALID : hsoNOOP));
STObject hso3{sfHook};
hso3.setFieldH256(
sfHookOnIncoming,
uint256{beast::zero}); // all zeros hook on incoming
BEAST_EXPECT(
SetHook::inferOperation(shCtx, hso3) ==
(fix ? hsoINVALID : hsoNOOP));
}
BEAST_EXPECT(SetHook::inferOperation(hso) == hsoINVALID);
}
}
@@ -3491,81 +3126,6 @@ public:
env.close();
}
void
test_exit_out_of_bounds(FeatureBitset features)
{
testcase("Test accept()/rollback() with out of bounds reason");
using namespace jtx;
// reason string pointer outside of wasm memory, then fall through
TestHook accept_oob_wasm = wasm[R"[test.hook](
#include <stdint.h>
extern int32_t _g (uint32_t id, uint32_t maxiter);
extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
int64_t hook(uint32_t reserved )
{
_g(1,1);
accept(0xFFFFFF00U, 32, 42);
return 0;
}
)[test.hook]"];
TestHook rollback_oob_wasm = wasm[R"[test.hook](
#include <stdint.h>
extern int32_t _g (uint32_t id, uint32_t maxiter);
extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
int64_t hook(uint32_t reserved )
{
_g(1,1);
rollback(0xFFFFFF00U, 32, 42);
return 0;
}
)[test.hook]"];
for (bool const withFix : {true, false})
{
for (auto const& hook_wasm : {accept_oob_wasm, rollback_oob_wasm})
{
Env env{
*this,
withFix ? features : features - fixHookExitOutOfBounds};
auto const alice = Account{"alice"};
auto const bob = Account{"bob"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
env(ripple::test::jtx::hook(alice, {{hso(hook_wasm)}}, 0),
M("Install OOB exit hook"),
HSFEE);
env.close();
env(pay(bob, alice, XRP(1)),
M("Test OOB exit hook"),
fee(XRP(1)),
ter(tecHOOK_REJECTED));
env.close();
auto const meta = env.meta();
BEAST_REQUIRE(meta && meta->isFieldPresent(sfHookExecutions));
auto const& execs = meta->getFieldArray(sfHookExecutions);
BEAST_REQUIRE(execs.size() == 1);
// with the fix: ROLLBACK with OUT_OF_BOUNDS (-1)
// without: UNSET (fixXahauV3) with default -1
BEAST_EXPECT(
execs[0].getFieldU8(sfHookResult) ==
static_cast<uint8_t>(
withFix ? hook_api::ExitType::ROLLBACK
: hook_api::ExitType::UNSET));
BEAST_EXPECT(
execs[0].getFieldU64(sfHookReturnCode) ==
0x8000000000000001ULL);
BEAST_EXPECT(execs[0].getFieldVL(sfHookReturnString).empty());
}
}
}
void
testGuards(FeatureBitset features)
{
@@ -4717,10 +4277,10 @@ public:
uint8_t expected1[49] = {
0xEDU, 0x20U, 0x2EU, 0x00U, 0x00U, 0x00U, 0x01U, 0x3DU, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x01U, 0x5BU, 0xBCU, 0x09U, 0x95U,
0xFDU, 0x03U, 0x0FU, 0xF1U, 0x85U, 0xF4U, 0xECU, 0xACU, 0x94U, 0xDBU,
0x02U, 0x19U, 0x26U, 0xFEU, 0x58U, 0xD4U, 0x8DU, 0x48U, 0x21U, 0xCCU,
0x82U, 0x2EU, 0x4AU, 0x6AU, 0xC9U, 0xC8U, 0xC6U, 0x7CU, 0x88U
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x01U, 0x5BU, 0xB8U, 0x05U, 0xD6U,
0xC3U, 0x52U, 0xDFU, 0x7AU, 0x27U, 0x76U, 0x6DU, 0xC0U, 0x20U, 0x47U,
0xB7U, 0x64U, 0x22U, 0x5AU, 0xB7U, 0x5DU, 0xF3U, 0xFAU, 0x0DU, 0xE3U,
0xBDU, 0xC6U, 0x40U, 0xBAU, 0xD0U, 0x0AU, 0x66U, 0xEBU, 0x68U,
};
// 0x5CU
// EmitNonce 32bytes
@@ -12495,77 +12055,57 @@ public:
)[test.hook]"];
HASH_WASM(hook);
for (bool const with20251128 : {true, false})
for (auto isfixHookAPI20251128 : {true, false})
{
for (bool const withSType : {true, false})
Env env{
*this,
isfixHookAPI20251128 ? features | fixHookAPI20251128
: features - fixHookAPI20251128};
env.fund(XRP(10000), alice, bob);
env.close();
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hso(hook_wasm, overrideFlag)}}, 0),
M("set sto_subarray"),
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 19);
// invoke the hook
env(pay(bob, alice, XRP(1)),
M("test sto_subarray"),
fee(XRP(1)));
env.close();
auto const meta = env.meta();
BEAST_REQUIRE(meta);
BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions));
auto const hookExecutions =
meta->getFieldArray(sfHookExecutions);
BEAST_REQUIRE(hookExecutions.size() == 1);
auto const hookExecution = hookExecutions[0];
BEAST_REQUIRE(hookExecution.isFieldPresent(sfHookReturnCode));
auto const returnCode =
hookExecution.getFieldU64(sfHookReturnCode);
if (isfixHookAPI20251128)
{
auto feats =
features - fixHookAPI20251128 - fixHookAPISType;
if (with20251128)
feats = feats | fixHookAPI20251128;
if (withSType)
feats = feats | fixHookAPISType;
Env env{*this, feats};
env.fund(XRP(10000), alice, bob);
env.close();
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hso(hook_wasm, overrideFlag)}}, 0),
M("set sto_subarray"),
HSFEE);
env.close();
EXPECT_HOOK_FEE(hook, 19);
// invoke the hook
env(pay(bob, alice, XRP(1)),
M("test sto_subarray"),
fee(XRP(1)));
env.close();
auto const meta = env.meta();
BEAST_REQUIRE(meta);
BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions));
auto const hookExecutions =
meta->getFieldArray(sfHookExecutions);
BEAST_REQUIRE(hookExecutions.size() == 1);
auto const hookExecution = hookExecutions[0];
BEAST_REQUIRE(
hookExecution.isFieldPresent(sfHookReturnCode));
auto const returnCode =
hookExecution.getFieldU64(sfHookReturnCode);
auto const doesntExistError = -5;
auto const position = 2;
auto const length = 12;
BEAST_REQUIRE(
returnCode ==
(doesntExistError + ((int64_t)position << 32) +
length));
}
else
{
auto const parseError = -18;
if (with20251128)
{
auto const position = 2;
auto const length = 12;
// unwrapped correctly: 1 element at pos 2, len 12
BEAST_REQUIRE(
returnCode ==
(doesntExistError + ((int64_t)position << 32) +
length));
}
else if (withSType)
{
// legacy unwrap leaves 0x5C as a 32 byte field header,
// which is rejected as out of bounds.
// negative codes are stored as sign bit + magnitude
BEAST_REQUIRE(
returnCode ==
(0x8000'0000'0000'0000ULL |
-(parseError + parseError)));
}
else
{
auto const position = 1;
auto const length = 33;
// legacy: 32 byte field overruns the 13 byte buffer
BEAST_REQUIRE(
returnCode ==
(parseError + ((int64_t)position << 32) + length));
}
auto const position = 1;
auto const length = 33;
BEAST_REQUIRE(
returnCode ==
(parseError + ((int64_t)position << 32) + length));
}
}
}
@@ -15535,8 +15075,7 @@ public:
testNSDeletePartial(features);
testPageCap(features);
testHookOnV2((features | featureHookOnV2) - featureHookOnV2_1);
testHookOnV2((features | featureHookOnV2_1) - featureHookOnV2);
testHookOnV2(features);
testHookName(features);
testFillCopy(features);
@@ -15544,7 +15083,6 @@ public:
testWasm(features);
test_accept(features);
test_rollback(features);
test_exit_out_of_bounds(features);
testGuards(features);
+8 -66
View File
@@ -189,64 +189,6 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
0x3DU, 0x20U, 0x54U, 0x4FU, 0x4FU, 0x5FU, 0x42U, 0x49U, 0x47U, 0x00U,
}},
{R"[test.hook](
#include <stdint.h>
extern int32_t _g (uint32_t id, uint32_t maxiter);
extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
int64_t hook(uint32_t reserved )
{
_g(1,1);
accept(0xFFFFFF00U, 32, 42);
return 0;
}
)[test.hook]",
{
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, 0x13U,
0x03U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x03U, 0x7FU,
0x7FU, 0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U,
0x17U, 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, 0x00U,
0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U,
0x70U, 0x74U, 0x00U, 0x01U, 0x03U, 0x02U, 0x01U, 0x02U, 0x05U, 0x03U,
0x01U, 0x00U, 0x02U, 0x06U, 0x21U, 0x05U, 0x7FU, 0x01U, 0x41U, 0x80U,
0x88U, 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU,
0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x88U,
0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x07U, 0x08U,
0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x02U, 0x0AU, 0xA1U,
0x80U, 0x00U, 0x01U, 0x9DU, 0x80U, 0x00U, 0x00U, 0x41U, 0x01U, 0x41U,
0x01U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x41U, 0x80U,
0x7EU, 0x41U, 0x20U, 0x42U, 0x2AU, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U,
0x00U, 0x1AU, 0x42U, 0x00U, 0x0BU,
}},
{R"[test.hook](
#include <stdint.h>
extern int32_t _g (uint32_t id, uint32_t maxiter);
extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
int64_t hook(uint32_t reserved )
{
_g(1,1);
rollback(0xFFFFFF00U, 32, 42);
return 0;
}
)[test.hook]",
{
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, 0x13U,
0x03U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x03U, 0x7FU,
0x7FU, 0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U,
0x19U, 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, 0x00U,
0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x08U, 0x72U, 0x6FU, 0x6CU, 0x6CU,
0x62U, 0x61U, 0x63U, 0x6BU, 0x00U, 0x01U, 0x03U, 0x02U, 0x01U, 0x02U,
0x05U, 0x03U, 0x01U, 0x00U, 0x02U, 0x06U, 0x21U, 0x05U, 0x7FU, 0x01U,
0x41U, 0x80U, 0x88U, 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U,
0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U,
0x80U, 0x88U, 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU,
0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x02U,
0x0AU, 0xA1U, 0x80U, 0x00U, 0x01U, 0x9DU, 0x80U, 0x00U, 0x00U, 0x41U,
0x01U, 0x41U, 0x01U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU,
0x41U, 0x80U, 0x7EU, 0x41U, 0x20U, 0x42U, 0x2AU, 0x10U, 0x81U, 0x80U,
0x80U, 0x80U, 0x00U, 0x1AU, 0x42U, 0x00U, 0x0BU,
}},
{R"[test.hook](
(module
(type (;0;) (func (param i32 i32) (result i64)))
@@ -1671,10 +1613,10 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
uint8_t expected1[49] = {
0xEDU, 0x20U, 0x2EU, 0x00U, 0x00U, 0x00U, 0x01U, 0x3DU, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x01U, 0x5BU, 0xBCU, 0x09U, 0x95U,
0xFDU, 0x03U, 0x0FU, 0xF1U, 0x85U, 0xF4U, 0xECU, 0xACU, 0x94U, 0xDBU,
0x02U, 0x19U, 0x26U, 0xFEU, 0x58U, 0xD4U, 0x8DU, 0x48U, 0x21U, 0xCCU,
0x82U, 0x2EU, 0x4AU, 0x6AU, 0xC9U, 0xC8U, 0xC6U, 0x7CU, 0x88U
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x01U, 0x5BU, 0xB8U, 0x05U, 0xD6U,
0xC3U, 0x52U, 0xDFU, 0x7AU, 0x27U, 0x76U, 0x6DU, 0xC0U, 0x20U, 0x47U,
0xB7U, 0x64U, 0x22U, 0x5AU, 0xB7U, 0x5DU, 0xF3U, 0xFAU, 0x0DU, 0xE3U,
0xBDU, 0xC6U, 0x40U, 0xBAU, 0xD0U, 0x0AU, 0x66U, 0xEBU, 0x68U,
};
// 0x5CU
// EmitNonce 32bytes
@@ -1821,10 +1763,10 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
0x36U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0xEDU, 0x20U, 0x2EU, 0x00U, 0x00U, 0x00U, 0x01U,
0x3DU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x01U, 0x5BU,
0xBCU, 0x09U, 0x95U, 0xFDU, 0x03U, 0x0FU, 0xF1U, 0x85U, 0xF4U, 0xECU,
0xACU, 0x94U, 0xDBU, 0x02U, 0x19U, 0x26U, 0xFEU, 0x58U, 0xD4U, 0x8DU,
0x48U, 0x21U, 0xCCU, 0x82U, 0x2EU, 0x4AU, 0x6AU, 0xC9U, 0xC8U, 0xC6U,
0x7CU, 0x88U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x68U, 0x61U, 0x73U,
0xB8U, 0x05U, 0xD6U, 0xC3U, 0x52U, 0xDFU, 0x7AU, 0x27U, 0x76U, 0x6DU,
0xC0U, 0x20U, 0x47U, 0xB7U, 0x64U, 0x22U, 0x5AU, 0xB7U, 0x5DU, 0xF3U,
0xFAU, 0x0DU, 0xE3U, 0xBDU, 0xC6U, 0x40U, 0xBAU, 0xD0U, 0x0AU, 0x66U,
0xEBU, 0x68U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x68U, 0x61U, 0x73U,
0x68U, 0x28U, 0x28U, 0x75U, 0x69U, 0x6EU, 0x74U, 0x33U, 0x32U, 0x5FU,
0x74U, 0x29U, 0x65U, 0x78U, 0x70U, 0x65U, 0x63U, 0x74U, 0x65U, 0x64U,
0x5FU, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x68U, 0x61U, 0x73U, 0x68U,
+114 -164
View File
@@ -24,7 +24,6 @@
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/UTF8.h>
#include <xrpl/protocol/jss.h>
#include <chrono>
@@ -981,7 +980,6 @@ struct URIToken_test : public beast::unit_test::suite
// setup env
env.fund(XRP(1000), alice, bob, gw);
env.close();
env.trust(USD(100000), alice, bob);
env.close();
env(pay(gw, alice, USD(1000)));
@@ -2312,16 +2310,12 @@ struct URIToken_test : public beast::unit_test::suite
auto const bob = Account("bob");
Env env{*this, features};
env.fund(XRP(10000), alice, bob);
env.close();
std::string uri = "";
// =========================================================================
// Cases that should succeed:
// valid well-formed UTF-8 that is not a noncharacter
// =========================================================================
// test utf-8 success
{
// case: kosme
uri = "κόσμε";
@@ -2354,83 +2348,133 @@ struct URIToken_test : public beast::unit_test::suite
// case: ipfs metadata url
uri = "https://example.com/ipfs/";
env(uritoken::mint(alice, uri));
}
// =========================================================================
// Cases that should fail:
// genuinely malformed UTF-8 (well-formedness failures)
// =========================================================================
{
// BOUNDRY - START
// ----------------------------------------------------------------
// case: 5 bytes (U-00200000) - beyond valid UTF-8 range
// case: 1 byte (U-00000000)
uri = "\x00";
env(uritoken::mint(alice, uri),
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
// case: 2 bytes (U-00000080)
uri = "\xC2\x80";
env(uritoken::mint(alice, uri));
// case: 3 bytes (U-00000800)
uri = "\xE0\xA0\x80";
env(uritoken::mint(alice, uri));
// case: 4 bytes (U-00010000)
uri = "\xF0\x90\x80\x80";
env(uritoken::mint(alice, uri));
// case: 5 bytes (U-00200000)
uri = "\xF8\x88\x80\x80\x80";
env(uritoken::mint(alice, uri), ter(temMALFORMED));
// case: 6 bytes (U-04000000) - beyond valid UTF-8 range
env(uritoken::mint(alice, uri),
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
// case: 6 bytes (U-04000000)
uri = "\xFC\x84\x80\x80\x80\x80";
env(uritoken::mint(alice, uri), ter(temMALFORMED));
env(uritoken::mint(alice, uri),
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
// BOUNDRY - END
// ----------------------------------------------------------------
// case: 3 bytes max (U-0000FFFF) - but this is U+FFFF
// noncharacter, rejected, see below
// case: 4 bytes max (U-001FFFFF) - beyond valid BMP
// case: 1 byte (U-0000007F)
uri = "\x7F";
env(uritoken::mint(alice, uri));
// case: 2 bytes (U-000007FF)
uri = "\xDF\xBF";
env(uritoken::mint(alice, uri));
// case: 3 bytes (U-0000FFFF)
uri = "\xEF\xBF\xBF";
env(uritoken::mint(alice, uri),
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
// case: 4 bytes (U-001FFFFF)
uri = "\xF7\xBF\xBF\xBF";
env(uritoken::mint(alice, uri), ter(temMALFORMED));
// case: 5 bytes max (U-03FFFFFF) - beyond valid UTF-8
env(uritoken::mint(alice, uri),
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
// case: 5 bytes (U-03FFFFFF)
uri = "\xFB\xBF\xBF\xBF\xBF";
env(uritoken::mint(alice, uri), ter(temMALFORMED));
// case: 6 bytes max (U-7FFFFFFF) - beyond valid UTF-8
env(uritoken::mint(alice, uri),
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
// case: 6 bytes (U-7FFFFFFF)
uri = "\xFD\xBF\xBF\xBF\xBF\xBF";
env(uritoken::mint(alice, uri), ter(temMALFORMED));
env(uritoken::mint(alice, uri),
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
// BOUNDRY - OTHER
// // BOUNDRY - OTHER
// ----------------------------------------------------------------
// case: 1 bytes (U-0000D7FF) - incomplete 2-byte sequence
// case: 1 bytes (U-0000D7FF)
uri = "\xD7\xFF";
env(uritoken::mint(alice, uri), ter(temMALFORMED));
// case: 4 bytes (U-00110000) - beyond valid UTF-8 range
env(uritoken::mint(alice, uri),
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
// case: 2 bytes (U-0000E000)
uri = "\xEE\x80\x80";
env(uritoken::mint(alice, uri));
// case: 3 bytes (U-0000FFFD)
uri = "\xEF\xBF\xBD";
env(uritoken::mint(alice, uri));
// // case: 4 bytes (U-0010FFFF)
uri = "\xF4\x8F\xBF\xBF";
env(uritoken::mint(alice, uri));
// // case: 4 bytes (U-00110000)
uri = "\xF4\x90\x80\x80";
env(uritoken::mint(alice, uri), ter(temMALFORMED));
env(uritoken::mint(alice, uri),
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
}
// =========================================================================
// Genuinely malformed UTF-8 sequences - always fail
// =========================================================================
// test utf8 malformed
{
// MALFORMED - START
// MALFORMED - END
// ----------------------------------------------------------------
// First continuation byte 0x80:
uri = "\x80";
env(uritoken::mint(alice, uri), ter(temMALFORMED));
// Last continuation byte 0xbf
uri = "\xBF";
env(uritoken::mint(alice, uri), ter(temMALFORMED));
// 2 continuation bytes
uri = "��";
env(uritoken::mint(alice, uri)); // TODO: REVIEW - SHOULD FAIL
// 3 continuation bytes
uri = "���";
env(uritoken::mint(alice, uri)); // TODO: REVIEW - SHOULD FAIL
// 4 continuation bytes
uri = "����";
env(uritoken::mint(alice, uri)); // TODO: REVIEW - SHOULD FAIL
// 5 continuation bytes
uri = "�����";
env(uritoken::mint(alice, uri)); // TODO: REVIEW - SHOULD FAIL
// 6 continuation bytes
uri = "������";
env(uritoken::mint(alice, uri)); // TODO: REVIEW - SHOULD FAIL
// 7 continuation bytes
uri = "�������";
env(uritoken::mint(alice, uri)); // TODO: REVIEW - SHOULD FAIL
// Sequence of all 64 possible continuation bytes (0x80-0xbf)
uri =
"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D"
"\x8E"
"\x8F\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C"
"\x9D"
"\x9E\x9F\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB"
"\xAC"
"\xAD\xAE\xAF\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA"
"\xBB"
"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E"
"\x8F\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D"
"\x9E\x9F\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC"
"\xAD\xAE\xAF\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB"
"\xBC\xBD\xBE\xBF";
env(uritoken::mint(alice, uri), ter(temMALFORMED));
// All 16 first bytes of 3-byte sequences (0xe0-0xef), each
// followed by a space character
// TODO: REVIEW - THIS IS NOT THE CORRECT 32 byte sequence.
// All 32 first bytes of 2-byte sequences (0xc0-0xdf), each followed
// by a space character
// uri = "\xE0\x80\x80 \xE0\x80\x81 \xE0\x80\x82 \xE0\x80\x83
// \xE0\x80\x84 \xE0\x80\x85 \xE0\x80\x86 \xE0\x80\x87 \xE0\x80\x88
// \xE0\x80\x89 \xE0\x80\x8A \xE0\x80\x8B \xE0\x80\x8C \xE0\x80\x8D
// \xE0\x80\x8E \xE0\x80\x8F \xE0\x80\x90";
// env(uritoken::mint(alice, uri), ter(temMALFORMED));
// All 16 first bytes of 3-byte sequences (0xe0-0xef), each followed
// by a space character
uri =
"\xE0\x80\x80 \xE0\x80\x81 \xE0\x80\x82 \xE0\x80\x83 "
"\xE0\x80\x84 \xE0\x80\x85 \xE0\x80\x86 \xE0\x80\x87 "
@@ -2439,26 +2483,31 @@ struct URIToken_test : public beast::unit_test::suite
"\xE0\x80\x90";
env(uritoken::mint(alice, uri), ter(temMALFORMED));
// All 8 first bytes of 4-byte sequences (0xf0-0xf7), each
// followed by a space character
// All 8 first bytes of 4-byte sequences (0xf0-0xf7), each followed
// by a space character
uri =
"\xF0\x90\x80\x80 \xF0\x90\x80\x81 \xF0\x90\x80\x82 "
"\xF0\x90\x80\x83 \xF0\x90\x80\x84 \xF0\x90\x80\x85 "
"\xF0\x90\x80\x86 \xF0\x90\x80\x87";
env(uritoken::mint(alice, uri));
env(uritoken::mint(alice, uri)); // TODO: REVIEW - SHOULD FAIL
// All 4 first bytes of 5-byte sequences (0xf8-0xfb), each
// followed by a space character
// All 4 first bytes of 5-byte sequences (0xf8-0xfb), each followed
// by a space character
uri =
"\xF8\x88\x80\x80\x80 \xF8\x88\x80\x80\x81 "
"\xF8\x88\x80\x80\x82 \xF8\x88\x80\x80\x83";
env(uritoken::mint(alice, uri), ter(temMALFORMED));
env(uritoken::mint(alice, uri),
ter(temMALFORMED)); // TODO: REVIEW - SHOULD FAIL
// All 2 first bytes of 6-byte sequences (0xfc-0xfd), each
// followed by a space character
// All 2 first bytes of 6-byte sequences (0xfc-0xfd), each followed
// by a space character
uri = "\xFC\x84\x80\x80\x80\x80 \xFC\x84\x80\x80\x80\x81";
env(uritoken::mint(alice, uri), ter(temMALFORMED));
// Sequences with last continuation byte missing
// Concatenation of incomplete sequences
// Impossible bytes
uri = "\xFE";
env(uritoken::mint(alice, uri), ter(temMALFORMED));
@@ -2567,113 +2616,15 @@ struct URIToken_test : public beast::unit_test::suite
uri = "\xED\xAF\xBF\xED\xBF\xBF";
env(uritoken::mint(alice, uri), ter(temMALFORMED));
// MALFORMED - END
// ----------------------------------------------------------------
}
// =========================================================================
// Noncharacters U+FFFE and U+FFFF: well-formed UTF-8, but
// deliberately rejected (U+FFFE is a byte-swapped BOM).
// =========================================================================
{
uri = "\xEF\xBF\xBE"; // U+FFFE
// problematic noncharacters in 16-bit applications
// case: (U+FFFE)
uri = "\xEF\xBF\xBE";
env(uritoken::mint(alice, uri), ter(temMALFORMED));
uri = "\xEF\xBF\xBF"; // U+FFFF
// case: (U+FFFF)
uri = "\xEF\xBF\xBF";
env(uritoken::mint(alice, uri), ter(temMALFORMED));
uri = "ipfs://x\xEF\xBF\xBE"; // not only at the start
env(uritoken::mint(alice, uri), ter(temMALFORMED));
}
// =========================================================================
// Valid boundary cases that should always succeed
// =========================================================================
{
// BOUNDRY - START (valid minimums)
// ----------------------------------------------------------------
// case: 1 byte (U-00000000). Must use the string
// constructor, not a C-string literal, to avoid strlen
// truncation to an empty URI.
uri = std::string(1, '\0');
env(uritoken::mint(alice, uri));
// case: 2 bytes (U-00000080)
uri = "\xC2\x80";
env(uritoken::mint(alice, uri));
// case: 3 bytes (U-00000800)
uri = "\xE0\xA0\x80";
env(uritoken::mint(alice, uri));
// case: 4 bytes (U-00010000)
uri = "\xF0\x90\x80\x80";
env(uritoken::mint(alice, uri));
// BOUNDRY - END (valid maximums)
// ----------------------------------------------------------------
// case: 1 byte (U-0000007F)
uri = "\x7F";
env(uritoken::mint(alice, uri));
// case: 2 bytes (U-000007FF)
uri = "\xDF\xBF";
env(uritoken::mint(alice, uri));
// case: 3 bytes (U-0000FFFF) - noncharacter, rejected, see
// above.
// case: 4 bytes (U-001FFFFF) - beyond U+10FFFF, always
// rejected, see above.
// BOUNDRY - OTHER
// ----------------------------------------------------------------
// case: 2 bytes (U-0000E000) - above surrogates
uri = "\xEE\x80\x80";
env(uritoken::mint(alice, uri));
// case: 3 bytes (U-0000FFFD) - replacement character, valid
uri = "\xEF\xBF\xBD";
env(uritoken::mint(alice, uri));
// case: 4 bytes (U-0010FFFF) - max valid code point
uri = "\xF4\x8F\xBF\xBF";
env(uritoken::mint(alice, uri));
}
}
void
testUTF8Validator()
{
testcase("uri_utf8 validator");
auto const check = [](std::string const& s) {
return isValidUTF8(makeSlice(s));
};
// Sequences truncated by the end of the buffer are invalid. The
// previous decoder read past the end of the vector here.
BEAST_EXPECT(!check("a\xC2"));
BEAST_EXPECT(!check("a\xE0\xA4"));
BEAST_EXPECT(!check("a\xEF\xBF"));
BEAST_EXPECT(!check("a\xF0\x90\x8D"));
BEAST_EXPECT(!check(std::string("\xC2", 1)));
BEAST_EXPECT(!check(std::string("\xF4\x8F\xBF", 3)));
// The complete sequences are fine.
BEAST_EXPECT(check("a\xC2\xA2"));
BEAST_EXPECT(check("a\xE0\xA4\xB9"));
BEAST_EXPECT(check("a\xF0\x90\x8D\x88"));
BEAST_EXPECT(check("a\xF4\x8F\xBF\xBF"));
BEAST_EXPECT(check(""));
// U+FFFE and U+FFFF are rejected wherever they appear.
BEAST_EXPECT(!check("\xEF\xBF\xBE"));
BEAST_EXPECT(!check("ab\xEF\xBF\xBF"));
BEAST_EXPECT(!check("\xE2\x82\xAC\xEF\xBF\xBEz"));
// Their neighbours are not, and nor are the other noncharacters,
// which the validator has never rejected.
BEAST_EXPECT(check("\xEF\xBF\xBD")); // U+FFFD
BEAST_EXPECT(check("\xEF\xBE\xBF")); // U+FFBF
BEAST_EXPECT(check("\xEF\xBB\xBF")); // U+FEFF (BOM)
BEAST_EXPECT(check("\xF0\x9F\xBF\xBE")); // U+1FFFE
}
void
testWithFeats(FeatureBitset features)
{
@@ -2707,7 +2658,6 @@ public:
{
using namespace test::jtx;
auto const sa = supported_amendments();
testUTF8Validator();
testWithFeats(sa);
}
};
-21
View File
@@ -28,9 +28,7 @@
#include <xrpl/protocol/jss.h>
#include <boost/lexical_cast.hpp>
#include <chrono>
#include <optional>
#include <thread>
#include <utility>
namespace ripple {
@@ -908,24 +906,6 @@ public:
pass();
}
void
testJSONRPCClientKeepAlive()
{
testcase("JSON-RPC keep-alive survives test work");
using namespace std::chrono_literals;
using namespace jtx;
Env env{*this};
env.client().invoke("server_info", {});
// The normal loopback message timeout is three seconds. Established
// Env connections have a separate idle lease because test work can
// legitimately take longer between RPCs.
std::this_thread::sleep_for(4s);
auto const response = env.client().invoke("server_info", {});
BEAST_EXPECT(response[jss::result][jss::status] == "success");
}
void
run() override
{
@@ -953,7 +933,6 @@ public:
testSignAndSubmit(all);
testFeatures(all);
testExceptionalShutdown();
testJSONRPCClientKeepAlive();
}
};
+1 -3
View File
@@ -141,9 +141,7 @@ AMM::create(
if (fee_ != 0)
jv[sfFee] = std::to_string(fee_);
else
jv[jss::Fee] = std::to_string(
env_.current()->fees().increment.drops() +
env_.current()->fees().base.drops());
jv[jss::Fee] = std::to_string(env_.current()->fees().increment.drops());
submit(jv, seq, ter);
if (!ter || env_.ter() == tesSUCCESS)
+1 -1
View File
@@ -180,7 +180,7 @@ AMMTest::reserve(jtx::Env& env, std::uint32_t count) const
XRPAmount
AMMTest::ammCrtFee(jtx::Env& env) const
{
return env.current()->fees().increment + env.current()->fees().base;
return env.current()->fees().increment;
}
jtx::Env
+32 -40
View File
@@ -37,36 +37,20 @@ namespace test {
static char const* bobs_account_objects[] = {
R"json({
"Account" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
"BookDirectory" : "50AD0A9E54D2B381288D535EB724E4275FFBF41580D28A925D038D7EA4C68000",
"BookNode" : "0",
"Flags" : 65536,
"LedgerEntryType" : "Offer",
"OwnerNode" : "0",
"Sequence" : 4,
"TakerGets" : {
"currency" : "USD",
"issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
"value" : "1"
},
"TakerPays" : "100000000",
"index" : "A984D036A0E562433A8377CA57D1A1E056E58C0D04818F8DFD3A1AA3F217DD82"
})json",
R"json({
"Account" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
"BookDirectory" : "B025997A323F5C3E03DDF1334471F5984ABDE31C59D463525D038D7EA4C68000",
"BookNode" : "0",
"Flags" : 65536,
"LedgerEntryType" : "Offer",
"OwnerNode" : "0",
"Sequence" : 5,
"TakerGets" : {
"currency" : "USD",
"issuer" : "r32rQHyesiTtdWFU7UJVtff4nCR5SHCbJW",
"value" : "1"
},
"TakerPays" : "100000000",
"index" : "CAFE32332D752387B01083B60CC63069BA4A969C9730836929F841450F6A718E"
"Account" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
"BookDirectory" : "B025997A323F5C3E03DDF1334471F5984ABDE31C59D463525D038D7EA4C68000",
"BookNode" : "0",
"Flags" : 65536,
"LedgerEntryType" : "Offer",
"OwnerNode" : "0",
"Sequence" : 4,
"TakerGets" : {
"currency" : "USD",
"issuer" : "r32rQHyesiTtdWFU7UJVtff4nCR5SHCbJW",
"value" : "1"
},
"TakerPays" : "100000000",
"index" : "A984D036A0E562433A8377CA57D1A1E056E58C0D04818F8DFD3A1AA3F217DD82"
})json",
R"json({
"Balance" : {
@@ -111,6 +95,22 @@ static char const* bobs_account_objects[] = {
},
"LowNode" : "0",
"index" : "D89BC239086183EB9458C396E643795C1134963E6550E682A190A5F021766D43"
})json",
R"json({
"Account" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
"BookDirectory" : "50AD0A9E54D2B381288D535EB724E4275FFBF41580D28A925D038D7EA4C68000",
"BookNode" : "0",
"Flags" : 65536,
"LedgerEntryType" : "Offer",
"OwnerNode" : "0",
"Sequence" : 3,
"TakerGets" : {
"currency" : "USD",
"issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
"value" : "1"
},
"TakerPays" : "100000000",
"index" : "E11029302EE744401427793A4F37BCB18F698D55C96851BEC5ABBD6242CF03D7"
})json"};
class AccountObjects_test : public beast::unit_test::suite
@@ -332,7 +332,7 @@ public:
auto& aobj = resp[jss::result][jss::account_objects][i];
aobj.removeMember("PreviousTxnID");
aobj.removeMember("PreviousTxnLgrSeq");
BEAST_EXPECT(aobj == bobj[i + 2]);
BEAST_EXPECT(aobj == bobj[i + 1]);
}
}
// test stepped one-at-a-time with limit=1, resume from prev marker
@@ -1135,15 +1135,7 @@ public:
return types == typesOut;
};
// Find AMM objects
// gw has a hook installed, so pay the hook execution fee too.
AMM amm(
env,
gw,
XRP(1'000),
USD(1'000),
false,
0,
env.current()->fees().increment.drops() + 1'000'000);
AMM amm(env, gw, XRP(1'000), USD(1'000));
amm.deposit(alice, USD(1));
// AMM account has 4 objects: AMM object and 3 trustlines
auto const lines = getAccountLines(env, amm.ammAccount());
+6 -6
View File
@@ -117,9 +117,9 @@ public:
BEAST_EXPECT(jroOuter[0u][jss::quality] == "100000000");
BEAST_EXPECT(jroOuter[0u][jss::taker_gets][jss::currency] == "USD");
BEAST_EXPECT(
jroOuter[0u][jss::taker_gets][jss::issuer] == gw.human());
BEAST_EXPECT(jroOuter[0u][jss::taker_gets][jss::value] == "2");
BEAST_EXPECT(jroOuter[0u][jss::taker_pays] == "200000000");
jroOuter[0u][jss::taker_gets][jss::issuer] == bob.human());
BEAST_EXPECT(jroOuter[0u][jss::taker_gets][jss::value] == "1");
BEAST_EXPECT(jroOuter[0u][jss::taker_pays] == "100000000");
BEAST_EXPECT(jroOuter[1u][jss::quality] == "5000000");
BEAST_EXPECT(jroOuter[1u][jss::taker_gets][jss::currency] == "USD");
@@ -131,9 +131,9 @@ public:
BEAST_EXPECT(jroOuter[2u][jss::quality] == "100000000");
BEAST_EXPECT(jroOuter[2u][jss::taker_gets][jss::currency] == "USD");
BEAST_EXPECT(
jroOuter[2u][jss::taker_gets][jss::issuer] == bob.human());
BEAST_EXPECT(jroOuter[2u][jss::taker_gets][jss::value] == "1");
BEAST_EXPECT(jroOuter[2u][jss::taker_pays] == "100000000");
jroOuter[2u][jss::taker_gets][jss::issuer] == gw.human());
BEAST_EXPECT(jroOuter[2u][jss::taker_gets][jss::value] == "2");
BEAST_EXPECT(jroOuter[2u][jss::taker_pays] == "200000000");
}
{
+8 -128
View File
@@ -163,7 +163,7 @@ public:
void
testNoParams(FeatureBitset features)
{
testcase("Default Env: config-forced, none on-ledger");
testcase("No Params, None Enabled");
using namespace test::jtx;
Env env{*this};
@@ -178,6 +178,8 @@ public:
{
if (!BEAST_EXPECT(feature.isMember(jss::name)))
return;
// default config - so all should be disabled, and
// supported. Some may be vetoed.
bool expectVeto =
(votes.at(feature[jss::name].asString()) ==
VoteBehavior::DefaultNo);
@@ -186,12 +188,8 @@ public:
VoteBehavior::Obsolete);
BEAST_EXPECTS(
feature.isMember(jss::enabled) &&
feature[jss::enabled].asBool(),
!feature[jss::enabled].asBool(),
feature[jss::name].asString() + " enabled");
BEAST_EXPECTS(
feature.isMember(jss::ledger_enabled) &&
!feature[jss::ledger_enabled].asBool(),
feature[jss::name].asString() + " ledger_enabled");
BEAST_EXPECTS(
feature.isMember(jss::vetoed) &&
feature[jss::vetoed].isBool() == !expectObsolete &&
@@ -210,7 +208,7 @@ public:
void
testSomeEnabled(FeatureBitset features)
{
testcase("Two config-forced, none on-ledger");
testcase("No Params, Some Enabled");
using namespace test::jtx;
Env env{
@@ -230,10 +228,7 @@ public:
(void)id.parseHex(it.key().asString().c_str());
if (!BEAST_EXPECT((*it).isMember(jss::name)))
return;
bool const expectOnLedger =
env.app().getAmendmentTable().isEnabled(id);
bool const expectForced =
id == featureDepositAuth || id == featureDepositPreauth;
bool expectEnabled = env.app().getAmendmentTable().isEnabled(id);
bool expectSupported =
env.app().getAmendmentTable().isSupported(id);
bool expectVeto =
@@ -244,14 +239,9 @@ public:
VoteBehavior::Obsolete);
BEAST_EXPECTS(
(*it).isMember(jss::enabled) &&
(*it)[jss::enabled].asBool() ==
(expectOnLedger || expectForced),
(*it)[jss::enabled].asBool() == expectEnabled,
(*it)[jss::name].asString() + " enabled");
BEAST_EXPECTS(
(*it).isMember(jss::ledger_enabled) &&
(*it)[jss::ledger_enabled].asBool() == expectOnLedger,
(*it)[jss::name].asString() + " ledger_enabled");
if (expectOnLedger)
if (expectEnabled)
BEAST_EXPECTS(
!(*it).isMember(jss::vetoed),
(*it)[jss::name].asString() + " vetoed");
@@ -370,122 +360,12 @@ public:
}
}
void
testConfigForced(FeatureBitset features)
{
testcase("Config-forced features ([features] stanza)");
using namespace test::jtx;
auto const forced = featurePriceOracle;
auto const forcedHex = to_string(forced);
Env env{*this, FeatureBitset(forced)};
auto jrr = env.rpc("server_definitions")[jss::result];
if (!BEAST_EXPECT(jrr.isMember(jss::features)))
return;
bool sawForced = false;
for (auto it = jrr[jss::features].begin();
it != jrr[jss::features].end();
++it)
{
auto const& f = *it;
auto const name = f[jss::name].asString();
if (!BEAST_EXPECTS(
f.isMember(jss::enabled) && f.isMember(jss::ledger_enabled),
name + " enabled/ledger_enabled"))
return;
BEAST_EXPECTS(
!f[jss::ledger_enabled].asBool(), name + " ledger_enabled");
if (it.key().asString() == forcedHex)
{
sawForced = true;
BEAST_EXPECTS(f[jss::enabled].asBool(), name + " enabled");
}
else
{
BEAST_EXPECTS(!f[jss::enabled].asBool(), name + " enabled");
}
}
BEAST_EXPECT(sawForced);
}
void
testOnLedger(FeatureBitset features)
{
testcase("On-ledger plus one config-forced");
using namespace test::jtx;
// Veto XahauGenesis: FRESH would otherwise enable it via pseudo-tx.
auto const forced = featurePriceOracle;
auto const forcedHex = to_string(forced);
Env env{
*this,
envconfig([](std::unique_ptr<Config> cfg) {
cfg->START_UP = Config::FRESH;
cfg->section(SECTION_VETO_AMENDMENTS)
.append(to_string(featureXahauGenesis) + " XahauGenesis");
return cfg;
}),
FeatureBitset(forced)};
auto jrr = env.rpc("server_definitions")[jss::result];
if (!BEAST_EXPECT(jrr.isMember(jss::features)))
return;
bool sawOnLedger = false;
bool sawForced = false;
for (auto it = jrr[jss::features].begin();
it != jrr[jss::features].end();
++it)
{
uint256 id;
(void)id.parseHex(it.key().asString().c_str());
auto const& f = *it;
auto const name = f[jss::name].asString();
if (!BEAST_EXPECTS(
f.isMember(jss::enabled) && f.isMember(jss::ledger_enabled),
name + " enabled/ledger_enabled"))
return;
bool const onLedger = env.app().getAmendmentTable().isEnabled(id);
bool const isForced = it.key().asString() == forcedHex;
BEAST_EXPECTS(
f[jss::ledger_enabled].asBool() == onLedger,
name + " ledger_enabled");
BEAST_EXPECTS(
f[jss::enabled].asBool() == (onLedger || isForced),
name + " enabled");
if (onLedger)
sawOnLedger = true;
if (isForced)
{
sawForced = true;
BEAST_EXPECTS(!onLedger, name + " forced not on-ledger");
}
}
BEAST_EXPECT(sawOnLedger);
BEAST_EXPECT(sawForced);
}
void
testServerFeatures(FeatureBitset features)
{
testNoParams(features);
testSomeEnabled(features);
testWithMajorities(features);
testConfigForced(features);
testOnLedger(features);
}
void
+16 -79
View File
@@ -101,14 +101,6 @@ HookAPI::sto_subfield(Bytes const& data, uint32_t field_id) const
if (data.size() < 2)
return Unexpected(TOO_SMALL);
if (hookCtx.applyCtx.view().rules().enabled(fixHookAPISType))
{
// validate the data
auto const valid = sto_validate(data);
if (!valid || !valid.value())
return Unexpected(PARSE_ERROR);
}
unsigned char* start = const_cast<unsigned char*>(data.data());
unsigned char* upto = start;
unsigned char* end = start + data.size();
@@ -173,18 +165,6 @@ HookAPI::sto_subarray(Bytes const& data, uint32_t index_id) const
unsigned char* upto = start;
unsigned char* end = start + data.size();
if (hookCtx.applyCtx.view().rules().enabled(fixHookAPISType))
{
// check if the array has valid trailing data
if ((*upto & 0xF0U) == 0xF0U && *(end - 1) != 0xF1U)
return Unexpected(PARSE_ERROR);
// validate the array
auto const valid = sto_validate(data);
if (!valid || !valid.value())
return Unexpected(PARSE_ERROR);
}
// unwrap the array if it is wrapped,
// by removing a byte from the start and end
// why here 0xF0?
@@ -279,13 +259,6 @@ HookAPI::sto_emplace(
return Unexpected(TOO_SMALL);
}
if (hookCtx.applyCtx.view().rules().enabled(fixHookAPISType))
{
auto const source_valid = sto_validate(source_object);
if (!source_valid || !source_valid.value())
return Unexpected(PARSE_ERROR);
}
if (field_object.has_value() &&
hookCtx.applyCtx.view().rules().enabled(fixHookAPI20251128))
{
@@ -306,9 +279,6 @@ HookAPI::sto_emplace(
0);
if (!length)
return Unexpected(PARSE_ERROR);
if (hookCtx.applyCtx.view().rules().enabled(fixHookAPISType) &&
length.value() != field_object->size())
return Unexpected(PARSE_ERROR);
if ((type << 16) + field != field_id)
{
return Unexpected(PARSE_ERROR);
@@ -3078,7 +3048,6 @@ HookAPI::get_stobject_length(
type == STI_PATHSET && rules.enabled(featureHookAPISerializedType240))
{
length = 0;
bool terminated = false;
while (upto + length < end)
{
// iterate Path step
@@ -3096,9 +3065,6 @@ HookAPI::get_stobject_length(
if (flag & 0x20) // issuer
length += 20;
if (rules.enabled(fixHookAPISType) && upto + length >= end)
return Unexpected(pe_unexpected_end);
int next_flag = *(upto + length);
if (next_flag == 0x00 || next_flag == 0xff)
// end of Path step
@@ -3110,68 +3076,43 @@ HookAPI::get_stobject_length(
if (lastflag == 0xff)
continue; // continue byte
else if (lastflag == 0x00)
{
terminated = true;
break; // end byte
}
else
return Unexpected(pe_unexpected_end);
}
if (upto >= end)
return Unexpected(pe_unexpected_end);
if (rules.enabled(fixHookAPISType) && !terminated)
return Unexpected(pe_unexpected_end);
}
else if (type == STI_ISSUE)
{
bool const fix = rules.enabled(fixHookAPISType);
auto zero20 = std::array<char, 20>{0};
// if first 20 byte is all zeros return 20
// else return 40
if (fix && end - upto < 20)
return Unexpected(pe_unexpected_end);
if (memcmp(upto, zero20.data(), 20) == 0)
length = 20;
// MPT is not supported yet
// else if (
// fix && end - upto >= 40 &&
// memcmp(upto + 20, noAccount().data(), 20) == 0)
// length = 44; // MPT: Issuer + noAccount + Sequence
else
length = 40;
}
else if (type == STI_XCHAIN_BRIDGE)
{
bool const fix = rules.enabled(fixHookAPISType);
auto zero20 = std::array<char, 20>{0};
length = 0;
for (int i = 0; i < 2; ++i) // Locking Chain, Issuing Chain
{
// Door Account
if (!fix)
length += 21;
else if (end - upto <= length)
return Unexpected(pe_unexpected_end);
else if (upto[length] == 0)
length += 1; // default (empty) account
else if (upto[length] == 20)
length += 21;
else
return Unexpected(pe_unexpected_end);
// Lock Chain
length = 1; // Door Account1 prefix length
length += 20; // Door Account1 length
// Door Issue1
if (memcmp(upto + length, zero20.data(), 20) == 0)
length += 20; // only Currency
else
length += 40; // Currency and Issue
// Door Issue
if (fix && end - upto < length + 20)
return Unexpected(pe_unexpected_end);
if (memcmp(upto + length, zero20.data(), 20) == 0)
length += 20; // only Currency
// MPT is not supported yet
// else if (
// fix && end - upto >= length + 40 &&
// memcmp(upto + length + 20, noAccount().data(), 20) == 0)
// length += 44; // MPT: Issuer + noAccount + Sequence
else
length += 40; // Currency and Issuer
}
// Issuing Chain
length += 1; // Door Account2 prefix length
length += 20; // Door Account2 length
// Door Issue2
if (memcmp(upto + length, zero20.data(), 20) == 0)
length += 20; // only Currency
else
length += 40; // Currency and Issue
}
if (length > -1)
@@ -3188,10 +3129,6 @@ HookAPI::get_stobject_length(
length,
payload_start,
payload_length);
if (rules.enabled(fixHookAPISType) && length > end - upto)
return Unexpected(pe_unexpected_end);
return length + (upto - start);
}
+8 -3
View File
@@ -81,8 +81,8 @@ AMMCreate::preflight(PreflightContext const& ctx)
XRPAmount
AMMCreate::calculateBaseFee(ReadView const& view, STTx const& tx)
{
// The least fee required for AMMCreate is one owner reserve.
return Transactor::calculateBaseFee(view, tx) + view.fees().increment;
// The fee required for AMMCreate is one owner reserve.
return view.fees().increment;
}
TER
@@ -252,7 +252,12 @@ applyCreate(
auto sleAMMRoot = std::make_shared<SLE>(keylet::account(*ammAccount));
sleAMMRoot->setAccountID(sfAccount, *ammAccount);
sleAMMRoot->setFieldAmount(sfBalance, STAmount{});
std::uint32_t const seqno = newAccountSeqNo(ctx_.view());
std::uint32_t const seqno{
ctx_.view().rules().enabled(featureXahauGenesis)
? ctx_.view().info().parentCloseTime.time_since_epoch().count()
: ctx_.view().rules().enabled(featureDeletableAccounts)
? ctx_.view().seq()
: 1};
sleAMMRoot->setFieldU32(sfSequence, seqno);
// Ignore reserves requirement, disable the master key, allow default
// rippling (AMM LPToken can be used in payments and offer crossing but
+2 -1
View File
@@ -554,7 +554,8 @@ Change::activateXahauGenesis()
{
sle = std::make_shared<SLE>(kl);
sle->setAccountID(sfAccount, accid);
std::uint32_t const seqno = newAccountSeqNo(sb);
std::uint32_t const seqno{
sb.info().parentCloseTime.time_since_epoch().count()};
sle->setFieldU32(sfSequence, seqno);
}
+2 -1
View File
@@ -243,7 +243,8 @@ GenesisMint::doApply()
if (created)
{
// Create the account.
std::uint32_t const seqno = newAccountSeqNo(view());
std::uint32_t const seqno{
view().info().parentCloseTime.time_since_epoch().count()};
sle = std::make_shared<SLE>(k);
sle->setAccountID(sfAccount, id);
+6 -1
View File
@@ -1307,7 +1307,12 @@ Import::doApply()
if (create)
{
// Create the account.
std::uint32_t const seqno = newAccountSeqNo(view());
std::uint32_t const seqno{
view().rules().enabled(featureXahauGenesis)
? view().info().parentCloseTime.time_since_epoch().count()
: view().rules().enabled(featureDeletableAccounts)
? view().seq()
: 1};
sle = std::make_shared<SLE>(keylet::account(id));
sle->setAccountID(sfAccount, id);
+5 -1
View File
@@ -1041,7 +1041,11 @@ ValidNewAccountRoot::finalize(
tt == ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION) &&
isTesSuccess(result))
{
std::uint32_t const startingSeq = newAccountSeqNo(view);
std::uint32_t const startingSeq{
view.rules().enabled(featureXahauGenesis)
? view.info().parentCloseTime.time_since_epoch().count()
: view.rules().enabled(featureDeletableAccounts) ? view.seq()
: 1};
if (accountSeq_ != startingSeq)
{
+6 -1
View File
@@ -354,7 +354,12 @@ Payment::doApply()
if (!sleDst)
{
std::uint32_t const seqno = newAccountSeqNo(view());
std::uint32_t const seqno{
view().rules().enabled(featureXahauGenesis)
? view().info().parentCloseTime.time_since_epoch().count()
: view().rules().enabled(featureDeletableAccounts)
? view().seq()
: 1};
// Create the account.
sleDst = std::make_shared<SLE>(k);
+7 -3
View File
@@ -18,12 +18,12 @@
//==============================================================================
#include <xrpld/app/tx/detail/Remit.h>
#include <xrpld/app/tx/detail/URIToken.h>
#include <xrpld/ledger/View.h>
#include <xrpl/basics/Log.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/UTF8.h>
#include <xrpl/protocol/st.h>
namespace ripple {
@@ -201,7 +201,7 @@ Remit::preflight(PreflightContext const& ctx)
return temMALFORMED;
}
if (!isValidUTF8(makeSlice(uri)))
if (!URIToken::validateUTF8(uri))
{
JLOG(ctx.j.warn())
<< "Malformed transaction: Invalid UTF8 inside MintURIToken.";
@@ -328,7 +328,11 @@ Remit::doApply()
nativeRemit += accountReserve;
// Create the account.
std::uint32_t const seqno = newAccountSeqNo(sb);
std::uint32_t const seqno{
sb.rules().enabled(featureXahauGenesis)
? sb.info().parentCloseTime.time_since_epoch().count()
: sb.rules().enabled(featureDeletableAccounts) ? sb.seq()
: 1};
sleDstAcc = std::make_shared<SLE>(keylet::account(dstAccID));
sleDstAcc->setAccountID(sfAccount, dstAccID);
+58 -115
View File
@@ -23,6 +23,7 @@
#include <xrpld/app/ledger/Ledger.h>
#include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpld/app/ledger/OpenLedger.h>
#include <xrpld/app/tx/detail/URIToken.h>
#include <xrpld/ledger/ApplyView.h>
#include <xrpl/basics/Log.h>
#include <xrpl/hook/Enum.h>
@@ -34,7 +35,6 @@
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/UTF8.h>
#include <algorithm>
#include <cstdint>
#include <exception>
@@ -207,7 +207,7 @@ validateHookParams(SetHookCtx& ctx, STArray const& hookParams)
// infer which operation the user is attempting to execute from the present and
// absent fields
HookSetOperation
SetHook::inferOperation(SetHookCtx& ctx, STObject const& hookSetObj)
SetHook::inferOperation(STObject const& hookSetObj)
{
uint64_t wasmByteCount = hookSetObj.isFieldPresent(sfCreateCode)
? hookSetObj.getFieldVL(sfCreateCode).size()
@@ -216,12 +216,7 @@ SetHook::inferOperation(SetHookCtx& ctx, STObject const& hookSetObj)
bool hasHash = hookSetObj.isFieldPresent(sfHookHash);
bool hasCode = hookSetObj.isFieldPresent(sfCreateCode);
bool invalidHookOn = ctx.rules.enabled(featureHookOnV2_1) &&
hookSetObj.isFieldPresent(sfHookOnOutgoing) !=
hookSetObj.isFieldPresent(sfHookOnIncoming);
if ((hasHash && hasCode) || invalidHookOn) // Both HookHash and CreateCode
// or invalid HookOn: invalid
if (hasHash && hasCode) // Both HookHash and CreateCode: invalid
return hsoINVALID;
else if (hasHash) // Hookhash only: install
return hsoINSTALL;
@@ -249,62 +244,6 @@ SetHook::inferOperation(SetHookCtx& ctx, STObject const& hookSetObj)
: hsoUPDATE;
}
bool
validateHookOn(SetHookCtx& ctx, STObject const& hookSetObj)
{
if (!hookSetObj.isFieldPresent(sfHookOn))
{
if (!ctx.rules.enabled(featureHookOnV2) &&
!ctx.rules.enabled(featureHookOnV2_1))
{
JLOG(ctx.j.trace())
<< "HookSet(" << hook::log::HOOKON_MISSING << ")[" << HS_ACC()
<< "]: Malformed transaction: SetHook must include "
"sfHookOn before featureHookOnV2_1 is enabled.";
return false;
}
if (!hookSetObj.isFieldPresent(sfHookOnOutgoing) ||
!hookSetObj.isFieldPresent(sfHookOnIncoming))
{
JLOG(ctx.j.trace())
<< "HookSet(" << hook::log::HOOKON_MISSING << ")[" << HS_ACC()
<< "]: Malformed transaction: SetHook must include "
"sfHookOnOutgoing and sfHookOnIncoming "
"when creating a new hook without sfHookOn.";
return false;
}
auto const outgoing = hookSetObj.getFieldH256(sfHookOnOutgoing);
auto const incoming = hookSetObj.getFieldH256(sfHookOnIncoming);
if (outgoing == incoming)
{
JLOG(ctx.j.trace())
<< "HookSet(" << hook::log::HOOKON_MISSING << ")[" << HS_ACC()
<< "]: Malformed transaction: SetHook outgoing and "
"incoming hookon must be different.";
return false;
}
return true;
}
else
{
if (hookSetObj.isFieldPresent(sfHookOnOutgoing) ||
hookSetObj.isFieldPresent(sfHookOnIncoming))
{
JLOG(ctx.j.trace())
<< "HookSet(" << hook::log::HOOKON_MISSING << ")[" << HS_ACC()
<< "]: Malformed transaction: SetHook must no"
"include sfHookOnOutgoing and sfHookOnIncoming "
"when creating a new hook with sfHookOn.";
return false;
}
}
return true;
}
// This is a context-free validation, it does not take into account the current
// state of the ledger returns < valid, instruction count > may throw
// overflow_error
@@ -315,7 +254,7 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
? hookSetObj.getFieldU32(sfFlags)
: 0;
switch (inferOperation(ctx, hookSetObj))
switch (inferOperation(hookSetObj))
{
case hsoNOOP: {
return true;
@@ -424,13 +363,6 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
// hookon may be present if the user so chooses
// flags may be present if the user so chooses
if (ctx.rules.enabled(featureHookOnV2_1) &&
(hookSetObj.isFieldPresent(sfHookOn) ||
hookSetObj.isFieldPresent(sfHookOnOutgoing) ||
hookSetObj.isFieldPresent(sfHookOnIncoming)) &&
!validateHookOn(ctx, hookSetObj))
return false;
return true;
}
@@ -475,13 +407,6 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
// hookon may be present if the user so chooses
// flags may be present if the user so chooses
if (ctx.rules.enabled(featureHookOnV2_1) &&
(hookSetObj.isFieldPresent(sfHookOn) ||
hookSetObj.isFieldPresent(sfHookOnOutgoing) ||
hookSetObj.isFieldPresent(sfHookOnIncoming)) &&
!validateHookOn(ctx, hookSetObj))
return false;
return true;
}
@@ -531,8 +456,56 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
}
// validate sfHookOn
if (!validateHookOn(ctx, hookSetObj))
return false;
if (!hookSetObj.isFieldPresent(sfHookOn))
{
if (!ctx.rules.enabled(featureHookOnV2))
{
JLOG(ctx.j.trace())
<< "HookSet(" << hook::log::HOOKON_MISSING << ")["
<< HS_ACC()
<< "]: Malformed transaction: SetHook must include "
"sfHookOn before featureHookOnV2 is enabled.";
return false;
}
if (!hookSetObj.isFieldPresent(sfHookOnOutgoing) ||
!hookSetObj.isFieldPresent(sfHookOnIncoming))
{
JLOG(ctx.j.trace())
<< "HookSet(" << hook::log::HOOKON_MISSING << ")["
<< HS_ACC()
<< "]: Malformed transaction: SetHook must include "
"sfHookOnOutgoing and sfHookOnIncoming "
"when creating a new hook without sfHookOn.";
return false;
}
auto const outgoing = hookSetObj.getFieldH256(sfHookOnOutgoing);
auto const incoming = hookSetObj.getFieldH256(sfHookOnIncoming);
if (outgoing == incoming)
{
JLOG(ctx.j.trace())
<< "HookSet(" << hook::log::HOOKON_MISSING << ")["
<< HS_ACC()
<< "]: Malformed transaction: SetHook outgoing and "
"incoming hookon must be different.";
return false;
}
}
else
{
if (hookSetObj.isFieldPresent(sfHookOnOutgoing) ||
hookSetObj.isFieldPresent(sfHookOnIncoming))
{
JLOG(ctx.j.trace())
<< "HookSet(" << hook::log::HOOKON_MISSING << ")["
<< HS_ACC()
<< "]: Malformed transaction: SetHook must no"
"include sfHookOnOutgoing and sfHookOnIncoming "
"when creating a new hook with sfHookOn.";
return false;
}
}
// validate sfHookCanEmit
// HookCanEmit field is an optional field for backward compatibility
@@ -657,7 +630,7 @@ SetHook::validateHookName(Blob const& name, beast::Journal const& j)
<< "sfHookName must be between 8 and 32 hex characters.";
return false;
}
if (!isValidUTF8(makeSlice(name)))
if (!URIToken::validateUTF8(name))
{
JLOG(j.trace()) << "sfHookName must be a valid UTF-8 string.";
return false;
@@ -1432,7 +1405,7 @@ SetHook::setHook()
HookSetOperation op = hsoNOOP;
if (hookSetObj)
op = inferOperation(ctx, hookSetObj->get());
op = inferOperation(hookSetObj->get());
// these flags are not able to be passed onto the ledger object
int newFlags = 0;
@@ -1654,23 +1627,10 @@ SetHook::setHook()
newHook.setFieldH256(sfHookNamespace, *newNamespace);
}
if (ctx.rules.enabled(featureHookOnV2_1))
{
// sanity check
if (newHookOn && (newHookOnOutgoing || newHookOnIncoming))
return tecINTERNAL; // LCOV_EXCL_LINE
if ((!newHookOnOutgoing && newHookOnIncoming) ||
(newHookOnOutgoing && !newHookOnIncoming))
return tecINTERNAL; // LCOV_EXCL_LINE
}
// set the hookon field if it differs from definition
if (newHookOn)
{
if ((!view().rules().enabled(featureHookOnV2_1) ||
defHookOn.has_value()) &&
*defHookOn == *newHookOn)
if (*defHookOn == *newHookOn)
{
if (newHook.isFieldPresent(sfHookOn))
newHook.makeFieldAbsent(sfHookOn);
@@ -1705,22 +1665,6 @@ SetHook::setHook()
sfHookOnIncoming, *newHookOnIncoming);
}
if (ctx.rules.enabled(featureHookOnV2_1))
{
if (newHookOn)
{
if (newHook.isFieldPresent(sfHookOnIncoming))
newHook.makeFieldAbsent(sfHookOnIncoming);
if (newHook.isFieldPresent(sfHookOnOutgoing))
newHook.makeFieldAbsent(sfHookOnOutgoing);
}
if (newHookOnOutgoing || newHookOnIncoming)
{
if (newHook.isFieldPresent(sfHookOn))
newHook.makeFieldAbsent(sfHookOn);
}
}
// set the hookcanemit field if it differs from definition
if (newHookCanEmit)
{
@@ -1899,8 +1843,7 @@ SetHook::setHook()
newHookDef->setFieldH256(sfHookHash, *createHookHash);
// only HookOn or (HookOnOutgoing and HookOnIncoming)
if ((!view().rules().enabled(featureHookOnV2) &&
!view().rules().enabled(featureHookOnV2_1)) ||
if (!view().rules().enabled(featureHookOnV2) ||
(!newHookOnOutgoing && !newHookOnIncoming))
newHookDef->setFieldH256(sfHookOn, *newHookOn);
else
+1 -1
View File
@@ -86,7 +86,7 @@ public:
calculateBaseFee(ReadView const& view, STTx const& tx);
static HookSetOperation
inferOperation(SetHookCtx& ctx, STObject const& hookSetObj);
inferOperation(STObject const& hookSetObj);
static HookSetValidation
validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj);
+1 -5
View File
@@ -155,11 +155,7 @@ preflight1(PreflightContext const& ctx)
!ctx.rules.enabled(featureNamedHooks))
return temMALFORMED;
auto const& name = ctx.tx.getFieldVL(sfHookName);
if (name.size() == 0 && ctx.rules.enabled(fixHookNameValidation))
return temMALFORMED;
if (!SetHook::validateHookName(name, ctx.j))
if (!SetHook::validateHookName(ctx.tx.getFieldVL(sfHookName), ctx.j))
return temMALFORMED;
}
+1 -2
View File
@@ -26,7 +26,6 @@
#include <xrpl/protocol/STAccount.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/UTF8.h>
namespace ripple {
@@ -99,7 +98,7 @@ URIToken::preflight(PreflightContext const& ctx)
return temMALFORMED;
}
if (!isValidUTF8(makeSlice(uri)))
if (!validateUTF8(uri))
{
JLOG(ctx.j.warn()) << "Malformed transaction. URI must be a "
"valid utf-8 string.";
+50
View File
@@ -30,6 +30,56 @@ namespace ripple {
class URIToken : public Transactor
{
public:
bool inline static validateUTF8(std::vector<uint8_t> const& u)
{
// this code is from
// https://www.cl.cam.ac.uk/~mgk25/ucs/utf8_check.c
uint8_t const* s = (uint8_t const*)u.data();
uint8_t const* end = s + u.size();
while (s < end)
{
if (*s < 0x80)
/* 0xxxxxxx */
s++;
else if ((s[0] & 0xe0) == 0xc0)
{
/* 110XXXXx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 ||
(s[0] & 0xfe) == 0xc0) /* overlong? */
return false;
else
s += 2;
}
else if ((s[0] & 0xf0) == 0xe0)
{
/* 1110XXXX 10Xxxxxx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 ||
(s[0] == 0xe0 && (s[1] & 0xe0) == 0x80) || /* overlong? */
(s[0] == 0xed && (s[1] & 0xe0) == 0xa0) || /* surrogate? */
(s[0] == 0xef && s[1] == 0xbf &&
(s[2] & 0xfe) == 0xbe)) /* U+FFFE or U+FFFF? */
return false;
else
s += 3;
}
else if ((s[0] & 0xf8) == 0xf0)
{
/* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */
if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 ||
(s[3] & 0xc0) != 0x80 ||
(s[0] == 0xf0 && (s[1] & 0xf0) == 0x80) || /* overlong? */
(s[0] == 0xf4 && s[1] > 0x8f) ||
s[0] > 0xf4) /* > U+10FFFF? */
return false;
else
s += 4;
}
else
return false;
}
return true;
}
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit URIToken(ApplyContext& ctx) : Transactor(ctx)
+5 -1
View File
@@ -481,7 +481,11 @@ transferHelper(
}
// Create the account.
std::uint32_t const seqno = newAccountSeqNo(psb);
std::uint32_t const seqno{
psb.rules().enabled(featureXahauGenesis)
? psb.info().parentCloseTime.time_since_epoch().count()
: psb.rules().enabled(featureDeletableAccounts) ? psb.seq()
: 1};
sleDst = std::make_shared<SLE>(dstK);
sleDst->setAccountID(sfAccount, dst);
-18
View File
@@ -1266,24 +1266,6 @@ deleteAMMTrustLine(
std::optional<AccountID> const& ammAccountID,
beast::Journal j);
[[nodiscard]] inline std::uint32_t
newAccountSeqNo(ReadView const& view)
{
auto const time = view.info().parentCloseTime.time_since_epoch().count();
return view.rules().enabled(featureXahauGenesis)
// TEQU:
// When creating accounts in GenesisLedger, we previously set
// the account Sequence to 0. However, since this conflicts with
// the PseudoAccount requirements, we are changing it to 1.
// There will be no impact on networks that are already running,
// and for future networks, there won't be any impact unless you
// create accounts via GenesisLedger.
// This specifically addresses an issue that comes up during
// unittests.
? (time == 0 ? 1 : time)
: view.rules().enabled(featureDeletableAccounts) ? view.seq() : 1;
}
} // namespace ripple
#endif
+91 -31
View File
@@ -22,10 +22,12 @@
#include <xrpld/app/main/Application.h>
#include <xrpld/app/misc/AmendmentTable.h>
#include <xrpld/app/misc/NetworkOPs.h>
#include <xrpld/core/Config.h>
#include <xrpld/rpc/detail/TransactionSign.h>
#include <xrpl/json/json_value.h>
#include <xrpl/json/json_writer.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/RPCErr.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/digest.h>
#include <xrpl/protocol/jss.h>
@@ -33,6 +35,14 @@
#include <magic_enum.hpp>
#include <sstream>
#define MAGIC_ENUM(x, _min, _max) \
template <> \
struct magic_enum::customize::enum_range<x> \
{ \
static constexpr int min = _min; \
static constexpr int max = _max; \
};
#define MAGIC_ENUM_16(x) \
template <> \
struct magic_enum::customize::enum_range<x> \
@@ -48,6 +58,15 @@
static constexpr bool is_flags = true; \
};
MAGIC_ENUM(ripple::SerializedTypeID, -2, 10004);
MAGIC_ENUM(ripple::LedgerEntryType, 0, 255);
MAGIC_ENUM(ripple::TELcodes, -399, 300);
MAGIC_ENUM(ripple::TEMcodes, -299, -200);
MAGIC_ENUM(ripple::TEFcodes, -199, -100);
MAGIC_ENUM(ripple::TERcodes, -99, -1);
MAGIC_ENUM(ripple::TEScodes, 0, 1);
MAGIC_ENUM(ripple::TECcodes, 100, 255);
MAGIC_ENUM_16(ripple::TxType);
MAGIC_ENUM_FLAG(ripple::UniversalFlags);
MAGIC_ENUM_FLAG(ripple::AccountSetFlags);
MAGIC_ENUM_FLAG(ripple::OfferCreateFlags);
@@ -173,19 +192,24 @@ private:
ret[jss::TYPES]["Done"] = -1;
std::map<int32_t, std::string> type_map{{-1, "Done"}};
for (auto const& [rawName, typeValue] : sTypeMap)
for (auto const& entry : magic_enum::enum_entries<SerializedTypeID>())
{
std::string typeName =
translate(std::string(rawName).substr(4) /* remove STI_ */);
ret[jss::TYPES][typeName] = typeValue;
type_map[typeValue] = typeName;
const auto name = entry.second;
std::string type_name =
translate(name.data() + 4 /* remove STI_ */);
int32_t type_value = static_cast<int32_t>(entry.first);
ret[jss::TYPES][type_name] = type_value;
type_map[type_value] = type_name;
}
ret[jss::LEDGER_ENTRY_TYPES] = Json::objectValue;
ret[jss::LEDGER_ENTRY_TYPES][jss::Invalid] = -1;
for (auto const& f : LedgerFormats::getInstance())
for (auto const& entry : magic_enum::enum_entries<LedgerEntryType>())
{
ret[jss::LEDGER_ENTRY_TYPES][f.getName()] = f.getType();
const auto name = entry.second;
std::string type_name = translate(name.data() + 2 /* remove lt_ */);
int32_t type_value = static_cast<int32_t>(entry.first);
ret[jss::LEDGER_ENTRY_TYPES][type_name] = type_value;
}
ret[jss::FIELDS] = Json::arrayValue;
@@ -302,16 +326,71 @@ private:
}
ret[jss::TRANSACTION_RESULTS] = Json::objectValue;
for (auto const& [code, terInfo] : transResults())
for (auto const& entry : magic_enum::enum_entries<TELcodes>())
{
ret[jss::TRANSACTION_RESULTS][terInfo.first] = code;
const auto name = entry.second;
ret[jss::TRANSACTION_RESULTS][STR(name)] =
static_cast<int32_t>(entry.first);
}
for (auto const& entry : magic_enum::enum_entries<TEMcodes>())
{
const auto name = entry.second;
ret[jss::TRANSACTION_RESULTS][STR(name)] =
static_cast<int32_t>(entry.first);
}
for (auto const& entry : magic_enum::enum_entries<TEFcodes>())
{
const auto name = entry.second;
ret[jss::TRANSACTION_RESULTS][STR(name)] =
static_cast<int32_t>(entry.first);
}
for (auto const& entry : magic_enum::enum_entries<TERcodes>())
{
const auto name = entry.second;
ret[jss::TRANSACTION_RESULTS][STR(name)] =
static_cast<int32_t>(entry.first);
}
for (auto const& entry : magic_enum::enum_entries<TEScodes>())
{
const auto name = entry.second;
ret[jss::TRANSACTION_RESULTS][STR(name)] =
static_cast<int32_t>(entry.first);
}
for (auto const& entry : magic_enum::enum_entries<TECcodes>())
{
const auto name = entry.second;
ret[jss::TRANSACTION_RESULTS][STR(name)] =
static_cast<int32_t>(entry.first);
}
auto const translate_tt = [](std::string inp) -> std::string {
if (inp == "Amendment")
return "EnableAmendment";
if (inp == "Fee")
return "SetFee";
if (inp == "PaychanClaim")
return "PaymentChannelClaim";
if (inp == "PaychanCreate")
return "PaymentChannelCreate";
if (inp == "PaychanFund")
return "PaymentChannelFund";
if (inp == "RegularKeySet")
return "SetRegularKey";
if (inp == "HookSet")
return "SetHook";
if (inp == "RemarksSet")
return "SetRemarks";
return inp;
};
ret[jss::TRANSACTION_TYPES] = Json::objectValue;
ret[jss::TRANSACTION_TYPES][jss::Invalid] = -1;
for (auto const& f : TxFormats::getInstance())
for (auto const& entry : magic_enum::enum_entries<TxType>())
{
ret[jss::TRANSACTION_TYPES][f.getName()] = f.getType();
const auto name = entry.second;
std::string type_name = translate_tt(translate(name.data() + 2));
int32_t type_value = static_cast<int32_t>(entry.first);
ret[jss::TRANSACTION_TYPES][type_name] = type_value;
}
// Transaction Flags:
@@ -466,25 +545,6 @@ doServerDefinitions(RPC::JsonContext& context)
features[to_string(h)][jss::majority] =
t.time_since_epoch().count();
// getJson's enabled is on-ledger; [features] also apply here.
for (auto const& name : features.getMemberNames())
{
Json::Value& entry = features[name];
entry[jss::ledger_enabled] = entry[jss::enabled].asBool();
}
for (auto const& h : context.app.config().features)
{
Json::Value& entry = features[to_string(h)];
if (!entry.isMember(jss::name))
{
if (auto const fname = featureToName(h); !fname.empty())
entry[jss::name] = fname;
}
if (!entry.isMember(jss::ledger_enabled))
entry[jss::ledger_enabled] = false;
entry[jss::enabled] = true;
}
lastFeatures = features;
{
const std::string out = Json::FastWriter().write(features);