From 92e3a927fce68c227456423c5e765f54a6461539 Mon Sep 17 00:00:00 2001 From: tequ Date: Thu, 9 Oct 2025 19:02:14 +0900 Subject: [PATCH 01/20] refactor KEYLET_LINE in utils_keylet (#502) Fixes the use of high and low in variable names, as these are determined by ripple::keylet::line processing. Co-authored-by: RichardAH --- src/ripple/app/hook/impl/applyHook.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ripple/app/hook/impl/applyHook.cpp b/src/ripple/app/hook/impl/applyHook.cpp index 0616ccf5f..5b5f28cf2 100644 --- a/src/ripple/app/hook/impl/applyHook.cpp +++ b/src/ripple/app/hook/impl/applyHook.cpp @@ -3150,15 +3150,15 @@ DEFINE_HOOK_FUNCTION( if (a == 0 || b == 0 || c == 0 || d == 0 || e == 0 || f == 0) return INVALID_ARGUMENT; - uint32_t hi_ptr = a, hi_len = b, lo_ptr = c, lo_len = d, + uint32_t acc1_ptr = a, acc1_len = b, acc2_ptr = c, acc2_len = d, cu_ptr = e, cu_len = f; - if (NOT_IN_BOUNDS(hi_ptr, hi_len, memory_length) || - NOT_IN_BOUNDS(lo_ptr, lo_len, memory_length) || + if (NOT_IN_BOUNDS(acc1_ptr, acc1_len, memory_length) || + NOT_IN_BOUNDS(acc2_ptr, acc2_len, memory_length) || NOT_IN_BOUNDS(cu_ptr, cu_len, memory_length)) return OUT_OF_BOUNDS; - if (hi_len != 20 || lo_len != 20) + if (acc1_len != 20 || acc2_len != 20) return INVALID_ARGUMENT; std::optional cur = @@ -3167,8 +3167,8 @@ DEFINE_HOOK_FUNCTION( return INVALID_ARGUMENT; auto kl = ripple::keylet::line( - AccountID::fromVoid(memory + hi_ptr), - AccountID::fromVoid(memory + lo_ptr), + AccountID::fromVoid(memory + acc1_ptr), + AccountID::fromVoid(memory + acc2_ptr), *cur); return serialize_keylet(kl, memory, write_ptr, write_len); } From fa1b93bfd879f93e0b8fcf369b86fb18d39390b4 Mon Sep 17 00:00:00 2001 From: Niq Dudfield Date: Fri, 10 Oct 2025 10:57:46 +0700 Subject: [PATCH 02/20] build: migrate to conan 2 (#585) Migrates the build system from Conan 1 to Conan 2 --- .github/actions/xahau-ga-build/action.yml | 4 + .../actions/xahau-ga-dependencies/action.yml | 5 +- .github/workflows/xahau-ga-macos.yml | 52 +++++++-- .github/workflows/xahau-ga-nix.yml | 36 ++++-- BUILD.md | 72 ++++++++---- build-core.sh | 27 ++++- conanfile.py | 103 ++++++++++-------- external/wasmedge/conanfile.py | 11 +- release-builder.sh | 60 +++++++--- 9 files changed, 256 insertions(+), 114 deletions(-) diff --git a/.github/actions/xahau-ga-build/action.yml b/.github/actions/xahau-ga-build/action.yml index 3387f0391..66fe45d62 100644 --- a/.github/actions/xahau-ga-build/action.yml +++ b/.github/actions/xahau-ga-build/action.yml @@ -94,6 +94,10 @@ runs: fi # Run CMake configure + # Note: conanfile.py hardcodes 'build/generators' as the output path. + # If we're in a 'build' folder, Conan detects this and uses just 'generators/' + # If we're in '.build' (non-standard), Conan adds the full 'build/generators/' + # So we get: .build/build/generators/ with our non-standard folder name cmake .. \ -G "${{ inputs.generator }}" \ $CCACHE_ARGS \ diff --git a/.github/actions/xahau-ga-dependencies/action.yml b/.github/actions/xahau-ga-dependencies/action.yml index d295e20eb..cb14e4e57 100644 --- a/.github/actions/xahau-ga-dependencies/action.yml +++ b/.github/actions/xahau-ga-dependencies/action.yml @@ -78,8 +78,9 @@ runs: - name: Export custom recipes shell: bash run: | - conan export external/snappy snappy/1.1.10@xahaud/stable - conan export external/soci soci/4.0.3@xahaud/stable + conan export external/snappy --version 1.1.10 --user xahaud --channel stable + conan export external/soci --version 4.0.3 --user xahaud --channel stable + conan export external/wasmedge --version 0.11.2 --user xahaud --channel stable - name: Install dependencies shell: bash diff --git a/.github/workflows/xahau-ga-macos.yml b/.github/workflows/xahau-ga-macos.yml index efb1a2001..66c7e6877 100644 --- a/.github/workflows/xahau-ga-macos.yml +++ b/.github/workflows/xahau-ga-macos.yml @@ -32,9 +32,9 @@ jobs: - name: Install Conan run: | - brew install conan@1 - # Add Conan 1 to the PATH for this job - echo "$(brew --prefix conan@1)/bin" >> $GITHUB_PATH + brew install conan + # Verify Conan 2 is installed + conan --version - name: Install Coreutils run: | @@ -60,12 +60,20 @@ jobs: - name: Install CMake run: | - if which cmake > /dev/null 2>&1; then - echo "cmake executable exists" - cmake --version - else - brew install cmake - fi + # Install CMake 3.x to match local dev environments + # With Conan 2 and the policy args passed to CMake, newer versions + # can have issues with dependencies that require cmake_minimum_required < 3.5 + brew uninstall cmake --ignore-dependencies 2>/dev/null || true + + # Download and install CMake 3.31.7 directly + curl -L https://github.com/Kitware/CMake/releases/download/v3.31.7/cmake-3.31.7-macos-universal.tar.gz -o cmake.tar.gz + tar -xzf cmake.tar.gz + + # Move the entire CMake.app to /Applications + sudo mv cmake-3.31.7-macos-universal/CMake.app /Applications/ + + echo "/Applications/CMake.app/Contents/bin" >> $GITHUB_PATH + /Applications/CMake.app/Contents/bin/cmake --version - name: Install ccache run: brew install ccache @@ -92,8 +100,30 @@ jobs: - name: Configure Conan run: | - conan profile new default --detect || true # Ignore error if profile exists - conan profile update settings.compiler.cppstd=20 default + # Create the default profile directory if it doesn't exist + mkdir -p ~/.conan2/profiles + + # Detect compiler version + COMPILER_VERSION=$(clang --version | grep -oE 'version [0-9]+' | grep -oE '[0-9]+') + + # Create profile with our specific settings + cat > ~/.conan2/profiles/default <=2.0" - name: Configure ccache uses: ./.github/actions/xahau-configure-ccache @@ -54,18 +54,30 @@ jobs: - name: Configure Conan run: | - conan profile new default --detect || true # Ignore error if profile exists - conan profile update settings.compiler.cppstd=20 default - conan profile update settings.compiler=${{ matrix.compiler }} default - conan profile update settings.compiler.libcxx=libstdc++11 default - conan profile update env.CC=/usr/bin/${{ matrix.cc }} default - conan profile update env.CXX=/usr/bin/${{ matrix.cxx }} default - conan profile update conf.tools.build:compiler_executables='{"c": "/usr/bin/${{ matrix.cc }}", "cpp": "/usr/bin/${{ matrix.cxx }}"}' default + # Create the default profile directory if it doesn't exist + mkdir -p ~/.conan2/profiles + + # Create profile with our specific settings + cat > ~/.conan2/profiles/default <` header. @@ -65,13 +65,24 @@ can't build earlier Boost versions. 1. (Optional) If you've never used Conan, use autodetect to set up a default profile. ``` - conan profile new default --detect + conan profile detect --force ``` 2. Update the compiler settings. + For Conan 2, you can edit the profile directly at `~/.conan2/profiles/default`, + or use the Conan CLI. Ensure C++20 is set: + ``` - conan profile update settings.compiler.cppstd=20 default + conan profile show + ``` + + Look for `compiler.cppstd=20` in the output. If it's not set, edit the profile: + + ``` + # Edit ~/.conan2/profiles/default and ensure these settings exist: + [settings] + compiler.cppstd=20 ``` Linux developers will commonly have a default Conan [profile][] that compiles @@ -80,7 +91,9 @@ can't build earlier Boost versions. then you will need to choose the `libstdc++11` ABI. ``` - conan profile update settings.compiler.libcxx=libstdc++11 default + # In ~/.conan2/profiles/default, ensure: + [settings] + compiler.libcxx=libstdc++11 ``` On Windows, you should use the x64 native build tools. @@ -91,7 +104,9 @@ can't build earlier Boost versions. architecture. ``` - conan profile update settings.arch=x86_64 default + # In ~/.conan2/profiles/default, ensure: + [settings] + arch=x86_64 ``` 3. (Optional) If you have multiple compilers installed on your platform, @@ -100,16 +115,18 @@ can't build earlier Boost versions. in the generated CMake toolchain file. ``` - conan profile update 'conf.tools.build:compiler_executables={"c": "", "cpp": ""}' default + # In ~/.conan2/profiles/default, add under [conf] section: + [conf] + tools.build:compiler_executables={"c": "", "cpp": ""} ``` - It should choose the compiler for dependencies as well, - but not all of them have a Conan recipe that respects this setting (yet). - For the rest, you can set these environment variables: + For setting environment variables for dependencies: ``` - conan profile update env.CC= default - conan profile update env.CXX= default + # In ~/.conan2/profiles/default, add under [buildenv] section: + [buildenv] + CC= + CXX= ``` 4. Export our [Conan recipe for Snappy](./external/snappy). @@ -117,14 +134,20 @@ can't build earlier Boost versions. which allows you to statically link it with GCC, if you want. ``` - conan export external/snappy snappy/1.1.10@xahaud/stable + conan export external/snappy --version 1.1.10 --user xahaud --channel stable ``` 5. Export our [Conan recipe for SOCI](./external/soci). It patches their CMake to correctly import its dependencies. ``` - conan export external/soci soci/4.0.3@xahaud/stable + conan export external/soci --version 4.0.3 --user xahaud --channel stable + ``` + +6. Export our [Conan recipe for WasmEdge](./external/wasmedge). + + ``` + conan export external/wasmedge --version 0.11.2 --user xahaud --channel stable ``` ### Build and Test @@ -259,23 +282,26 @@ and can be helpful for detecting `#include` omissions. If you have trouble building dependencies after changing Conan settings, try removing the Conan cache. +For Conan 2: ``` -rm -rf ~/.conan/data +rm -rf ~/.conan2/p +``` + +Or clear the entire Conan 2 cache: +``` +conan cache clean "*" ``` -### no std::result_of +### macOS compilation with Apple Clang 17+ -If your compiler version is recent enough to have removed `std::result_of` as -part of C++20, e.g. Apple Clang 15.0, then you might need to add a preprocessor -definition to your build. +If you're on macOS with Apple Clang 17 or newer, you need to add a compiler flag to work around a compilation error in gRPC dependencies. + +Edit `~/.conan2/profiles/default` and add under the `[conf]` section: ``` -conan profile update 'options.boost:extra_b2_flags="define=BOOST_ASIO_HAS_STD_INVOKE_RESULT"' default -conan profile update 'env.CFLAGS="-DBOOST_ASIO_HAS_STD_INVOKE_RESULT"' default -conan profile update 'env.CXXFLAGS="-DBOOST_ASIO_HAS_STD_INVOKE_RESULT"' default -conan profile update 'conf.tools.build:cflags+=["-DBOOST_ASIO_HAS_STD_INVOKE_RESULT"]' default -conan profile update 'conf.tools.build:cxxflags+=["-DBOOST_ASIO_HAS_STD_INVOKE_RESULT"]' default +[conf] +tools.build:cxxflags=["-Wno-missing-template-arg-list-after-template-kw"] ``` diff --git a/build-core.sh b/build-core.sh index 7b1eb78dc..ce78c7625 100755 --- a/build-core.sh +++ b/build-core.sh @@ -12,6 +12,13 @@ echo "-- GITHUB_REPOSITORY: $1" echo "-- GITHUB_SHA: $2" echo "-- GITHUB_RUN_NUMBER: $4" +# Use mounted filesystem for temp files to avoid container space limits +export TMPDIR=/io/tmp +export TEMP=/io/tmp +export TMP=/io/tmp +mkdir -p /io/tmp +echo "=== Using temp directory: /io/tmp ===" + umask 0000; cd /io/ && @@ -43,10 +50,17 @@ export LDFLAGS="-static-libstdc++" git config --global --add safe.directory /io && git checkout src/ripple/protocol/impl/BuildInfo.cpp && sed -i s/\"0.0.0\"/\"$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)$(if [ -n "$4" ]; then echo "+$4"; fi)\"/g src/ripple/protocol/impl/BuildInfo.cpp && -conan export external/snappy snappy/1.1.10@xahaud/stable && -conan export external/soci soci/4.0.3@xahaud/stable && +conan export external/snappy --version 1.1.10 --user xahaud --channel stable && +conan export external/soci --version 4.0.3 --user xahaud --channel stable && +conan export external/wasmedge --version 0.11.2 --user xahaud --channel stable && cd release-build && -conan install .. --output-folder . --build missing --settings build_type=$BUILD_TYPE && +# Install dependencies - tool_requires in conanfile.py handles glibc 2.28 compatibility +# for build tools (protoc, grpc plugins, b2) in HBB environment +# The tool_requires('b2/5.3.2') in conanfile.py should force b2 to build from source +# with the correct toolchain, avoiding the GLIBCXX_3.4.29 issue +echo "=== Installing dependencies ===" && +conan install .. --output-folder . --build missing --settings build_type=$BUILD_TYPE \ + -o with_wasmedge=False -o tool_requires_b2=True && cmake .. -G Ninja \ -DCMAKE_BUILD_TYPE=$BUILD_TYPE \ -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \ @@ -56,10 +70,13 @@ cmake .. -G Ninja \ -Dxrpld=TRUE \ -Dtests=TRUE && ccache -z && -ninja -j $3 && +ninja -j $3 && echo "=== Re-running final link with verbose output ===" && rm -f rippled && ninja -v rippled && ccache -s && -strip -s rippled && +strip -s rippled && mv rippled xahaud && +echo "=== Full ldd output ===" && +ldd xahaud && +echo "=== Running libcheck ===" && libcheck xahaud && echo "Build host: `hostname`" > release.info && echo "Build date: `date`" >> release.info && diff --git a/conanfile.py b/conanfile.py index 0a5bc0b4a..4b98b84db 100644 --- a/conanfile.py +++ b/conanfile.py @@ -21,22 +21,20 @@ class Xrpl(ConanFile): 'static': [True, False], 'tests': [True, False], 'unity': [True, False], + 'with_wasmedge': [True, False], + 'tool_requires_b2': [True, False], } requires = [ - 'boost/1.86.0', 'date/3.0.1', 'libarchive/3.6.0', - 'lz4/1.9.3', + 'lz4/1.9.4', 'grpc/1.50.1', 'nudb/2.0.8', 'openssl/1.1.1u', - 'protobuf/3.21.9', - 'snappy/1.1.10@xahaud/stable', + 'protobuf/3.21.12', 'soci/4.0.3@xahaud/stable', - 'sqlite3/3.42.0', - 'zlib/1.2.13', - 'wasmedge/0.11.2', + 'zlib/1.3.1', ] default_options = { @@ -50,42 +48,44 @@ class Xrpl(ConanFile): 'static': True, 'tests': True, 'unity': False, + 'with_wasmedge': True, + 'tool_requires_b2': False, - 'cassandra-cpp-driver:shared': False, - 'date:header_only': True, - 'grpc:shared': False, - 'grpc:secure': True, - 'libarchive:shared': False, - 'libarchive:with_acl': False, - 'libarchive:with_bzip2': False, - 'libarchive:with_cng': False, - 'libarchive:with_expat': False, - 'libarchive:with_iconv': False, - 'libarchive:with_libxml2': False, - 'libarchive:with_lz4': True, - 'libarchive:with_lzma': False, - 'libarchive:with_lzo': False, - 'libarchive:with_nettle': False, - 'libarchive:with_openssl': False, - 'libarchive:with_pcreposix': False, - 'libarchive:with_xattr': False, - 'libarchive:with_zlib': False, - 'libpq:shared': False, - 'lz4:shared': False, - 'openssl:shared': False, - 'protobuf:shared': False, - 'protobuf:with_zlib': True, - 'rocksdb:enable_sse': False, - 'rocksdb:lite': False, - 'rocksdb:shared': False, - 'rocksdb:use_rtti': True, - 'rocksdb:with_jemalloc': False, - 'rocksdb:with_lz4': True, - 'rocksdb:with_snappy': True, - 'snappy:shared': False, - 'soci:shared': False, - 'soci:with_sqlite3': True, - 'soci:with_boost': True, + 'cassandra-cpp-driver/*:shared': False, + 'date/*:header_only': True, + 'grpc/*:shared': False, + 'grpc/*:secure': True, + 'libarchive/*:shared': False, + 'libarchive/*:with_acl': False, + 'libarchive/*:with_bzip2': False, + 'libarchive/*:with_cng': False, + 'libarchive/*:with_expat': False, + 'libarchive/*:with_iconv': False, + 'libarchive/*:with_libxml2': False, + 'libarchive/*:with_lz4': True, + 'libarchive/*:with_lzma': False, + 'libarchive/*:with_lzo': False, + 'libarchive/*:with_nettle': False, + 'libarchive/*:with_openssl': False, + 'libarchive/*:with_pcreposix': False, + 'libarchive/*:with_xattr': False, + 'libarchive/*:with_zlib': False, + 'libpq/*:shared': False, + 'lz4/*:shared': False, + 'openssl/*:shared': False, + 'protobuf/*:shared': False, + 'protobuf/*:with_zlib': True, + 'rocksdb/*:enable_sse': False, + 'rocksdb/*:lite': False, + 'rocksdb/*:shared': False, + 'rocksdb/*:use_rtti': True, + 'rocksdb/*:with_jemalloc': False, + 'rocksdb/*:with_lz4': True, + 'rocksdb/*:with_snappy': True, + 'snappy/*:shared': False, + 'soci/*:shared': False, + 'soci/*:with_sqlite3': True, + 'soci/*:with_boost': True, } def set_version(self): @@ -96,11 +96,28 @@ class Xrpl(ConanFile): match = next(m for m in matches if m) self.version = match.group(1) + def build_requirements(self): + # These provide build tools (protoc, grpc plugins) that run during build + self.tool_requires('protobuf/3.21.12') + self.tool_requires('grpc/1.50.1') + # Explicitly require b2 (e.g. for building from source for glibc compatibility) + if self.options.tool_requires_b2: + self.tool_requires('b2/5.3.2') + def configure(self): if self.settings.compiler == 'apple-clang': - self.options['boost'].visibility = 'global' + self.options['boost/*'].visibility = 'global' def requirements(self): + # Force sqlite3 version to avoid conflicts with soci + self.requires('sqlite3/3.42.0', override=True) + # Force our custom snappy build for all dependencies + self.requires('snappy/1.1.10@xahaud/stable', override=True) + # Force boost version for all dependencies to avoid conflicts + self.requires('boost/1.86.0', override=True) + + if self.options.with_wasmedge: + self.requires('wasmedge/0.11.2@xahaud/stable') if self.options.jemalloc: self.requires('jemalloc/5.2.1') if self.options.reporting: diff --git a/external/wasmedge/conanfile.py b/external/wasmedge/conanfile.py index 4b7c42050..cefda286d 100644 --- a/external/wasmedge/conanfile.py +++ b/external/wasmedge/conanfile.py @@ -38,8 +38,15 @@ class WasmedgeConan(ConanFile): raise ConanInvalidConfiguration("Binaries for this combination of version/os/arch/compiler are not available") def package_id(self): - del self.info.settings.compiler.version - self.info.settings.compiler = self._compiler_alias + # Make binary compatible across compiler versions (since we're downloading prebuilt) + self.info.settings.rm_safe("compiler.version") + # Group compilers by their binary compatibility + # Note: We must use self.info.settings here, not self.settings (forbidden in Conan 2) + compiler_name = str(self.info.settings.compiler) + if compiler_name in ["Visual Studio", "msvc"]: + self.info.settings.compiler = "Visual Studio" + else: + self.info.settings.compiler = "gcc" def build(self): # This is packaging binaries so the download needs to be in build diff --git a/release-builder.sh b/release-builder.sh index f2a64a673..672e68042 100755 --- a/release-builder.sh +++ b/release-builder.sh @@ -1,9 +1,11 @@ -#!/bin/bash +#!/bin/bash # We use set -e and bash with -u to bail on first non zero exit code of any # processes launched or upon any unbound variable. # We use set -x to print commands before running them to help with # debugging. +set -ex + echo "START BUILDING (HOST)" echo "Cleaning previously built binary" @@ -90,29 +92,37 @@ RUN /hbb_exe/activate-exec bash -c "dnf install -y epel-release && \ llvm14-static llvm14-devel && \ dnf clean all" -# Install Conan and CMake -RUN /hbb_exe/activate-exec pip3 install "conan==1.66.0" && \ +# Install Conan 2 and CMake +RUN /hbb_exe/activate-exec pip3 install "conan>=2.0,<3.0" && \ /hbb_exe/activate-exec wget -q https://github.com/Kitware/CMake/releases/download/v3.23.1/cmake-3.23.1-linux-x86_64.tar.gz -O cmake.tar.gz && \ mkdir cmake && \ tar -xzf cmake.tar.gz --strip-components=1 -C cmake && \ rm cmake.tar.gz -# Install Boost 1.86.0 -RUN /hbb_exe/activate-exec bash -c "cd /tmp && \ +# Dual Boost configuration in HBB environment: +# - Manual Boost in /usr/local (minimal: for WasmEdge which is pre-built in Docker) +# - Conan Boost (full: for the application and all dependencies via toolchain) +# +# Install minimal Boost 1.86.0 for WasmEdge only (filesystem and its dependencies) +# The main application will use Conan-provided Boost for all other components +# IMPORTANT: Understanding Boost linking options: +# - link=static: Creates static Boost libraries (.a files) instead of shared (.so files) +# - runtime-link=shared: Links Boost libraries against shared libc (glibc) +# WasmEdge only needs boost::filesystem and boost::system +RUN /hbb_exe/activate-exec bash -c "echo 'Boost cache bust: v5-minimal' && \ + rm -rf /usr/local/lib/libboost* /usr/local/include/boost && \ + cd /tmp && \ wget -q https://archives.boost.io/release/1.86.0/source/boost_1_86_0.tar.gz -O boost.tar.gz && \ mkdir boost && \ tar -xzf boost.tar.gz --strip-components=1 -C boost && \ cd boost && \ ./bootstrap.sh && \ - ./b2 link=static -j${BUILD_CORES} && \ - ./b2 install && \ + ./b2 install \ + link=static runtime-link=shared -j${BUILD_CORES} \ + --with-filesystem --with-system && \ cd /tmp && \ rm -rf boost boost.tar.gz" -ENV BOOST_ROOT=/usr/local/src/boost_1_86_0 -ENV Boost_LIBRARY_DIRS=/usr/local/lib -ENV BOOST_INCLUDEDIR=/usr/local/src/boost_1_86_0 - ENV CMAKE_EXE_LINKER_FLAGS="-static-libstdc++" ENV LLVM_DIR=/usr/lib64/llvm14/lib/cmake/llvm @@ -155,6 +165,10 @@ RUN cd /tmp && \ cd build && \ /hbb_exe/activate-exec bash -c "source /opt/rh/gcc-toolset-11/enable && \ ln -sf /opt/rh/gcc-toolset-11/root/usr/bin/ar /usr/bin/ar && \ + ln -sf /opt/rh/gcc-toolset-11/root/usr/bin/ranlib /usr/bin/ranlib && \ + echo '=== Binutils version check ===' && \ + ar --version | head -1 && \ + ranlib --version | head -1 && \ cmake .. \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/usr/local \ @@ -176,14 +190,28 @@ RUN cd /tmp && \ # Set environment variables ENV PATH=/usr/local/bin:$PATH -# Configure ccache and Conan +# Configure ccache and Conan 2 +# NOTE: Using echo commands instead of heredocs because heredocs in Docker RUN commands are finnicky RUN /hbb_exe/activate-exec bash -c "ccache -M 10G && \ ccache -o cache_dir=/cache/ccache && \ ccache -o compiler_check=content && \ - conan config set storage.path=/cache/conan && \ - (conan profile new default --detect || true) && \ - conan profile update settings.compiler.libcxx=libstdc++11 default && \ - conan profile update settings.compiler.cppstd=20 default" + mkdir -p ~/.conan2 /cache/conan2 /cache/conan2_download /cache/conan2_sources && \ + echo 'core.cache:storage_path=/cache/conan2' > ~/.conan2/global.conf && \ + echo 'core.download:download_cache=/cache/conan2_download' >> ~/.conan2/global.conf && \ + echo 'core.sources:download_cache=/cache/conan2_sources' >> ~/.conan2/global.conf && \ + conan profile detect --force && \ + echo '[settings]' > ~/.conan2/profiles/default && \ + echo 'arch=x86_64' >> ~/.conan2/profiles/default && \ + echo 'build_type=Release' >> ~/.conan2/profiles/default && \ + echo 'compiler=gcc' >> ~/.conan2/profiles/default && \ + echo 'compiler.cppstd=20' >> ~/.conan2/profiles/default && \ + echo 'compiler.libcxx=libstdc++11' >> ~/.conan2/profiles/default && \ + echo 'compiler.version=11' >> ~/.conan2/profiles/default && \ + echo 'os=Linux' >> ~/.conan2/profiles/default && \ + echo '' >> ~/.conan2/profiles/default && \ + echo '[conf]' >> ~/.conan2/profiles/default && \ + echo '# Force building from source for packages with binary compatibility issues' >> ~/.conan2/profiles/default && \ + echo '*:tools.system.package_manager:mode=build' >> ~/.conan2/profiles/default" DOCKERFILE_EOF ) From b3e6a902cb594738b8f654f5c3dcc46d8c1fbd85 Mon Sep 17 00:00:00 2001 From: "J. Scott Branson" <18340247+jscottbranson@users.noreply.github.com> Date: Thu, 9 Oct 2025 23:59:39 -0400 Subject: [PATCH 03/20] Update Sample Configuration Files in /cfg for Congruence with xahaud (#584) --- README.md | 6 +- cfg/validators-example.txt | 49 ++-- ...rippled-example.cfg => xahaud-example.cfg} | 207 +++++++++-------- ...led-reporting.cfg => xahaud-reporting.cfg} | 209 +++++++++--------- ...d-standalone.cfg => xahaud-standalone.cfg} | 9 +- 5 files changed, 238 insertions(+), 242 deletions(-) rename cfg/{rippled-example.cfg => xahaud-example.cfg} (90%) rename cfg/{rippled-reporting.cfg => xahaud-reporting.cfg} (90%) rename cfg/{rippled-standalone.cfg => xahaud-standalone.cfg} (97%) mode change 100755 => 100644 diff --git a/README.md b/README.md index c9335cfc1..0a7e17ddd 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **Note:** Throughout this README, references to "we" or "our" pertain to the community and contributors involved in the Xahau network. It does not imply a legal entity or a specific collection of individuals. -[Xahau](https://xahau.network/) is a decentralized cryptographic ledger that builds upon the robust foundation of the XRP Ledger. It inherits the XRP Ledger's Byzantine Fault Tolerant consensus algorithm and enhances it with additional features and functionalities. Developers and users familiar with the XRP Ledger will find that most documentation and tutorials available on [xrpl.org](https://xrpl.org) are relevant and applicable to Xahau, including those related to running validators and managing validator keys. For Xahau specific documentation you can visit our [documentation](https://docs.xahau.network/) +[Xahau](https://xahau.network/) is a decentralized cryptographic ledger that builds upon the robust foundation of the XRP Ledger. It inherits the XRP Ledger's Byzantine Fault Tolerant consensus algorithm and enhances it with additional features and functionalities. Developers and users familiar with the XRP Ledger will find that most documentation and tutorials available on [xrpl.org](https://xrpl.org) are relevant and applicable to Xahau, including those related to running validators and managing validator keys. For Xahau specific documentation you can visit our [documentation](https://xahau.network/) ## XAH XAH is the public, counterparty-free asset native to Xahau and functions primarily as network gas. Transactions submitted to the Xahau network must supply an appropriate amount of XAH, to be burnt by the network as a fee, in order to be successfully included in a validated ledger. In addition, XAH also acts as a bridge currency within the Xahau DEX. XAH is traded on the open-market and is available for anyone to access. Xahau was created in 2023 with a supply of 600 million units of XAH. @@ -12,7 +12,7 @@ The server software that powers Xahau is called `xahaud` and is available in thi ### Build from Source -* [Read the build instructions in our documentation](https://docs.xahau.network/infrastructure/building-xahau) +* [Read the build instructions in our documentation](https://xahau.network/infrastructure/building-xahau) * If you encounter any issues, please [open an issue](https://github.com/xahau/xahaud/issues) ## Highlights of Xahau @@ -58,7 +58,7 @@ git-subtree. See those directories' README files for more details. - **Documentation**: Documentation for XRPL, Xahau and Hooks. - [Xrpl Documentation](https://xrpl.org) - - [Xahau Documentation](https://docs.xahau.network/) + - [Xahau Documentation](https://xahau.network/) - [Hooks Technical Documentation](https://xrpl-hooks.readme.io/) - **Explorers**: Explore the Xahau ledger using various explorers: - [xahauexplorer.com](https://xahauexplorer.com) diff --git a/cfg/validators-example.txt b/cfg/validators-example.txt index 6c2314ebd..11ab76c2a 100644 --- a/cfg/validators-example.txt +++ b/cfg/validators-example.txt @@ -1,7 +1,7 @@ # # Default validators.txt # -# This file is located in the same folder as your rippled.cfg file +# This file is located in the same folder as your xahaud.cfg file # and defines which validators your server trusts not to collude. # # This file is UTF-8 with DOS, UNIX, or Mac style line endings. @@ -17,18 +17,17 @@ # See validator_list_sites and validator_list_keys below. # # Examples: -# n9KorY8QtTdRx7TVDpwnG9NvyxsDwHUKUEeDLY3AkiGncVaSXZi5 -# n9MqiExBcoG19UXwoLjBJnhsxEhAZMuWwJDRdkyDz1EkEkwzQTNt +# n9L3GdotB8a3AqtsvS7NXt4BUTQSAYyJUr9xtFj2qXJjfbZsawKY +# n9M7G6eLwQtUjfCthWUmTN8L4oEZn1sNr46yvKrpsq58K1C6LAxz # # [validator_list_sites] # # List of URIs serving lists of recommended validators. # # Examples: -# https://vl.ripple.com -# https://vl.xrplf.org +# https://vl.xahau.org # http://127.0.0.1:8000 -# file:///etc/opt/ripple/vl.txt +# file:///etc/opt/xahaud/vl.txt # # [validator_list_keys] # @@ -39,50 +38,48 @@ # Validator list keys should be hex-encoded. # # Examples: -# ED2677ABFFD1B33AC6FBC3062B71F1E8397C1505E1C42C64D11AD1B28FF73F4734 -# ED307A760EE34F2D0CAA103377B1969117C38B8AA0AA1E2A24DAC1F32FC97087ED +# EDA46E9C39B1389894E690E58914DC1029602870370A0993E5B87C4A24EAF4A8E8 # # [import_vl_keys] # # This section is used to import the public keys of trusted validator list publishers. # The keys are used to authenticate and accept new lists of trusted validators. -# In this example, the key for the publisher "vl.xrplf.org" is imported. # Each key is represented as a hexadecimal string. # # Examples: -# ED2677ABFFD1B33AC6FBC3062B71F1E8397C1505E1C42C64D11AD1B28FF73F4734 +# ED45D1840EE724BE327ABE9146503D5848EFD5F38B6D5FEDE71E80ACCE5E6E738B +# ED42AEC58B701EEBB77356FFFEC26F83C1F0407263530F068C7C73D392C7E06FD1 +# ED2677ABFFD1B33AC6FBC3062B71F1E8397C1505E1C42C64D11AD1B28FF73F4734 -# The default validator list publishers that the rippled instance +# The default validator list publishers that the xahaud instance # trusts. # -# WARNING: Changing these values can cause your rippled instance to see a -# validated ledger that contradicts other rippled instances' +# WARNING: Changing these values can cause your xahaud instance to see a +# validated ledger that contradicts other xahaud instances' # validated ledgers (aka a ledger fork) if your validator list(s) # do not sufficiently overlap with the list(s) used by others. # See: https://arxiv.org/pdf/1802.07242.pdf [validator_list_sites] -https://vl.ripple.com -https://vl.xrplf.org +https://vl.xahau.org [validator_list_keys] -#vl.ripple.com -ED2677ABFFD1B33AC6FBC3062B71F1E8397C1505E1C42C64D11AD1B28FF73F4734 -# vl.xrplf.org -ED45D1840EE724BE327ABE9146503D5848EFD5F38B6D5FEDE71E80ACCE5E6E738B +# vl.xahau.org +EDA46E9C39B1389894E690E58914DC1029602870370A0993E5B87C4A24EAF4A8E8 [import_vl_keys] -# vl.xrplf.org +ED45D1840EE724BE327ABE9146503D5848EFD5F38B6D5FEDE71E80ACCE5E6E738B +ED42AEC58B701EEBB77356FFFEC26F83C1F0407263530F068C7C73D392C7E06FD1 ED2677ABFFD1B33AC6FBC3062B71F1E8397C1505E1C42C64D11AD1B28FF73F4734 -# To use the test network (see https://xrpl.org/connect-your-rippled-to-the-xrp-test-net.html), +# To use the test network (see https://xahau.network/docs/infrastructure/installing-xahaud), # use the following configuration instead: # -# [validator_list_sites] -# https://vl.altnet.rippletest.net -# -# [validator_list_keys] -# ED264807102805220DA0F312E71FC2C69E1552C9C5790F6C25E3729DEB573D5860 +# [validators] +# nHBoJCE3wPgkTcrNPMHyTJFQ2t77EyCAqcBRspFCpL6JhwCm94VZ +# nHUVv4g47bFMySAZFUKVaXUYEmfiUExSoY4FzwXULNwJRzju4XnQ +# nHBvr8avSFTz4TFxZvvi4rEJZZtyqE3J6KAAcVWVtifsE7edPM7q +# nHUH3Z8TRU57zetHbEPr1ynyrJhxQCwrJvNjr4j1SMjYADyW1WWe # # [import_vl_keys] # ED264807102805220DA0F312E71FC2C69E1552C9C5790F6C25E3729DEB573D5860 diff --git a/cfg/rippled-example.cfg b/cfg/xahaud-example.cfg similarity index 90% rename from cfg/rippled-example.cfg rename to cfg/xahaud-example.cfg index 80ec0a980..85520f93f 100644 --- a/cfg/rippled-example.cfg +++ b/cfg/xahaud-example.cfg @@ -9,7 +9,7 @@ # # 2. Peer Protocol # -# 3. Ripple Protocol +# 3. XRPL Protocol # # 4. HTTPS Client # @@ -29,18 +29,17 @@ # # Purpose # -# This file documents and provides examples of all rippled server process -# configuration options. When the rippled server instance is launched, it +# This file documents and provides examples of all xahaud server process +# configuration options. When the xahaud server instance is launched, it # looks for a file with the following name: # -# rippled.cfg +# xahaud.cfg # -# For more information on where the rippled server instance searches for the -# file, visit: +# To run xahaud with a custom configuration file, use the "--conf {file}" flag. +# By default, xahaud will look in the local working directory or the home directory. # -# https://xrpl.org/commandline-usage.html#generic-options # -# This file should be named rippled.cfg. This file is UTF-8 with DOS, UNIX, +# This file should be named xahaud.cfg. This file is UTF-8 with DOS, UNIX, # or Mac style end of lines. Blank lines and lines beginning with '#' are # ignored. Undefined sections are reserved. No escapes are currently defined. # @@ -89,8 +88,8 @@ # # # -# rippled offers various server protocols to clients making inbound -# connections. The listening ports rippled uses are "universal" ports +# xahaud offers various server protocols to clients making inbound +# connections. The listening ports xahaud uses are "universal" ports # which may be configured to handshake in one or more of the available # supported protocols. These universal ports simplify administration: # A single open port can be used for multiple protocols. @@ -103,7 +102,7 @@ # # A list of port names and key/value pairs. A port name must start with a # letter and contain only letters and numbers. The name is not case-sensitive. -# For each name in this list, rippled will look for a configuration file +# For each name in this list, xahaud will look for a configuration file # section with the same name and use it to create a listening port. The # name is informational only; the choice of name does not affect the function # of the listening port. @@ -134,7 +133,7 @@ # ip = 127.0.0.1 # protocol = http # -# When rippled is used as a command line client (for example, issuing a +# When xahaud is used as a command line client (for example, issuing a # server stop command), the first port advertising the http or https # protocol will be used to make the connection. # @@ -175,7 +174,7 @@ # same time. It is possible have both Websockets and Secure Websockets # together in one port. # -# NOTE If no ports support the peer protocol, rippled cannot +# NOTE If no ports support the peer protocol, xahaud cannot # receive incoming peer connections or become a superpeer. # # limit = @@ -194,7 +193,7 @@ # required. IP address restrictions, if any, will be checked in addition # to the credentials specified here. # -# When acting in the client role, rippled will supply these credentials +# When acting in the client role, xahaud will supply these credentials # using HTTP's Basic Authentication headers when making outbound HTTP/S # requests. # @@ -237,7 +236,7 @@ # WS, or WSS protocol interfaces. If administrative commands are # disabled for a port, these credentials have no effect. # -# When acting in the client role, rippled will supply these credentials +# When acting in the client role, xahaud will supply these credentials # in the submitted JSON for any administrative command requests when # invoking JSON-RPC commands on remote servers. # @@ -258,7 +257,7 @@ # resource controls will default to those for non-administrative users. # # The secure_gateway IP addresses are intended to represent -# proxies. Since rippled trusts these hosts, they must be +# proxies. Since xahaud trusts these hosts, they must be # responsible for properly authenticating the remote user. # # If some IP addresses are included for both "admin" and @@ -272,7 +271,7 @@ # Use the specified files when configuring SSL on the port. # # NOTE If no files are specified and secure protocols are selected, -# rippled will generate an internal self-signed certificate. +# xahaud will generate an internal self-signed certificate. # # The files have these meanings: # @@ -295,12 +294,12 @@ # Control the ciphers which the server will support over SSL on the port, # specified using the OpenSSL "cipher list format". # -# NOTE If unspecified, rippled will automatically configure a modern +# NOTE If unspecified, xahaud will automatically configure a modern # cipher suite. This default suite should be widely supported. # # You should not modify this string unless you have a specific # reason and cryptographic expertise. Incorrect modification may -# keep rippled from connecting to other instances of rippled or +# keep xahaud from connecting to other instances of xahaud or # prevent RPC and WebSocket clients from connecting. # # send_queue_limit = [1..65535] @@ -351,7 +350,7 @@ # # Examples: # { "command" : "server_info" } -# { "command" : "log_level", "partition" : "ripplecalc", "severity" : "trace" } +# { "command" : "log_level", "partition" : "xahaudcalc", "severity" : "trace" } # # # @@ -380,16 +379,15 @@ #----------------- # # These settings control security and access attributes of the Peer to Peer -# server section of the rippled process. Peer Protocol implements the -# Ripple Payment protocol. It is over peer connections that transactions -# and validations are passed from to machine to machine, to determine the -# contents of validated ledgers. +# server section of the xahaud process. It is over peer connections that +# transactions and validations are passed from to machine to machine, to +# determine the contents of validated ledgers. # # # # [ips] # -# List of hostnames or ips where the Ripple protocol is served. A default +# List of hostnames or ips where the XRPL protocol is served. A default # starter list is included in the code and used if no other hostnames are # available. # @@ -398,24 +396,23 @@ # does not generally matter. # # The default list of entries is: -# - r.ripple.com 51235 -# - zaphod.alloy.ee 51235 -# - sahyadri.isrdc.in 51235 +# - hubs.xahau.as16089.net 21337 +# - bacab.alloy.ee 21337 # # Examples: # # [ips] # 192.168.0.1 -# 192.168.0.1 2459 -# r.ripple.com 51235 +# 192.168.0.1 21337 +# bacab.alloy.ee 21337 # # # [ips_fixed] # -# List of IP addresses or hostnames to which rippled should always attempt to +# List of IP addresses or hostnames to which xahaud should always attempt to # maintain peer connections with. This is useful for manually forming private # networks, for example to configure a validation server that connects to the -# Ripple network through a public-facing server, or for building a set +# Xahau Network through a public-facing server, or for building a set # of cluster peers. # # One address or domain names per line is allowed. A port must be specified @@ -465,7 +462,7 @@ # # IP address or domain of NTP servers to use for time synchronization. # -# These NTP servers are suitable for rippled servers located in the United +# These NTP servers are suitable for xahaud servers located in the United # States: # time.windows.com # time.apple.com @@ -566,7 +563,7 @@ # # minimum_txn_in_ledger_standalone = # -# Like minimum_txn_in_ledger when rippled is running in standalone +# Like minimum_txn_in_ledger when xahaud is running in standalone # mode. Default: 1000. # # target_txn_in_ledger = @@ -703,7 +700,7 @@ # # [validator_token] # -# This is an alternative to [validation_seed] that allows rippled to perform +# This is an alternative to [validation_seed] that allows xahaud to perform # validation without having to store the validator keys on the network # connected server. The field should contain a single token in the form of a # base64-encoded blob. @@ -738,19 +735,18 @@ # # Specify the file by its name or path. # Unless an absolute path is specified, it will be considered relative to -# the folder in which the rippled.cfg file is located. +# the folder in which the xahaud.cfg file is located. # # Examples: -# /home/ripple/validators.txt -# C:/home/ripple/validators.txt +# /home/xahaud/validators.txt +# C:/home/xahaud/validators.txt # # Example content: # [validators] -# n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7 -# n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj -# n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C -# n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS -# n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA +# n9L3GdotB8a3AqtsvS7NXt4BUTQSAYyJUr9xtFj2qXJjfbZsawKY +# n9LQDHLWyFuAn5BXJuW2ow5J9uGqpmSjRYS2cFRpxf6uJbxwDzvM +# n9MCWyKVUkiatXVJTKUrAESB5kBFP8R3hm43jGHtg8WBnjv3iDfb +# n9KWXCLRhjpajuZtULTXsy6R5xbisA6ozGxM4zdEJFq6uHiFZDvW # # # @@ -833,7 +829,7 @@ # # 0: Disable the ledger replay feature [default] # 1: Enable the ledger replay feature. With this feature enabled, when -# acquiring a ledger from the network, a rippled node only downloads +# acquiring a ledger from the network, a xahaud node only downloads # the ledger header and the transactions instead of the whole ledger. # And the ledger is built by applying the transactions to the parent # ledger. @@ -844,10 +840,9 @@ # #---------------- # -# The rippled server instance uses HTTPS GET requests in a variety of +# The xahaud server instance uses HTTPS GET requests in a variety of # circumstances, including but not limited to contacting trusted domains to -# fetch information such as mapping an email address to a Ripple Payment -# Network address. +# fetch information such as mapping an email address to a user's r address. # # [ssl_verify] # @@ -884,15 +879,15 @@ # #------------ # -# rippled has an optional operating mode called Reporting Mode. In Reporting -# Mode, rippled does not connect to the peer to peer network. Instead, rippled -# will continuously extract data from one or more rippled servers that are +# xahaud has an optional operating mode called Reporting Mode. In Reporting +# Mode, xahaud does not connect to the peer to peer network. Instead, xahaud +# will continuously extract data from one or more xahaud servers that are # connected to the peer to peer network (referred to as an ETL source). # Reporting mode servers will forward RPC requests that require access to the # peer to peer network (submit, fee, etc) to an ETL source. # # [reporting] Settings for Reporting Mode. If and only if this section is -# present, rippled will start in reporting mode. This section +# present, xahaud will start in reporting mode. This section # contains a list of ETL source names, and key-value pairs. The # ETL source names each correspond to a configuration file # section; the names must match exactly. The key-value pairs are @@ -997,16 +992,16 @@ # #------------ # -# rippled creates 4 SQLite database to hold bookkeeping information +# xahaud creates 4 SQLite database to hold bookkeeping information # about transactions, local credentials, and various other things. # It also creates the NodeDB, which holds all the objects that -# make up the current and historical ledgers. In Reporting Mode, rippled +# make up the current and historical ledgers. In Reporting Mode, xahauad # uses a Postgres database instead of SQLite. # # The simplest way to work with Postgres is to install it locally. # When it is running, execute the initdb.sh script in the current # directory as: sudo -u postgres ./initdb.sh -# This will create the rippled user and an empty database of the same name. +# This will create the xahaud user and an empty database of the same name. # # The size of the NodeDB grows in proportion to the amount of new data and the # amount of historical data (a configurable setting) so the performance of the @@ -1014,7 +1009,7 @@ # the performance of the server. # # Partial pathnames will be considered relative to the location of -# the rippled.cfg file. +# the xahaud.cfg file. # # [node_db] Settings for the Node Database (required) # @@ -1025,18 +1020,18 @@ # # Example: # type=nudb -# path=db/nudb +# path=/opt/xahaud/db/nudb # # The "type" field must be present and controls the choice of backend: # # type = NuDB # # NuDB is a high-performance database written by Ripple Labs and optimized -# for rippled and solid-state drives. +# for and solid-state drives. # # NuDB maintains its high speed regardless of the amount of history # stored. Online delete may be selected, but is not required. NuDB is -# available on all platforms that rippled runs on. +# available on all platforms that xahaud runs on. # # type = RocksDB # @@ -1124,7 +1119,7 @@ # # Optional keys for NuDB and RocksDB: # -# earliest_seq The default is 32570 to match the XRP ledger +# earliest_seq The default is 32570 to match the XRP Ledger's # network's earliest allowed sequence. Alternate # networks may set this value. Minimum value of 1. # If a [shard_db] section is defined, and this @@ -1166,7 +1161,7 @@ # # recovery_wait_seconds # The online delete process checks periodically -# that rippled is still in sync with the network, +# that xahaud is still in sync with the network, # and that the validated ledger is less than # 'age_threshold_seconds' old. If not, then continue # sleeping for this number of seconds and @@ -1205,8 +1200,8 @@ # The server creates and maintains 4 to 5 bookkeeping SQLite databases in # the 'database_path' location. If you omit this configuration setting, # the server creates a directory called "db" located in the same place as -# your rippled.cfg file. -# Partial pathnames are relative to the location of the rippled executable. +# your xahaud.cfg file. +# Partial pathnames are relative to the location of the xahaud executable. # # [shard_db] Settings for the Shard Database (optional) # @@ -1282,7 +1277,7 @@ # The default is "wal", which uses a write-ahead # log to implement database transactions. # Alternately, "memory" saves disk I/O, but if -# rippled crashes during a transaction, the +# xahaud crashes during a transaction, the # database is likely to be corrupted. # See https://www.sqlite.org/pragma.html#pragma_journal_mode # for more details about the available options. @@ -1292,7 +1287,7 @@ # synchronous Valid values: off, normal, full, extra # The default is "normal", which works well with # the "wal" journal mode. Alternatively, "off" -# allows rippled to continue as soon as data is +# allows xahaud to continue as soon as data is # passed to the OS, which can significantly # increase speed, but risks data corruption if # the host computer crashes before writing that @@ -1306,7 +1301,7 @@ # The default is "file", which will use files # for temporary database tables and indices. # Alternatively, "memory" may save I/O, but -# rippled does not currently use many, if any, +# xahaud does not currently use many, if any, # of these temporary objects. # See https://www.sqlite.org/pragma.html#pragma_temp_store # for more details about the available options. @@ -1318,9 +1313,9 @@ # conninfo Info for connecting to Postgres. Format is # postgres://[username]:[password]@[ip]/[database]. # The database and user must already exist. If this -# section is missing and rippled is running in -# Reporting Mode, rippled will connect as the -# user running rippled to a database with the +# section is missing and xahaud is running in +# Reporting Mode, xahaud will connect as the +# user running xahaud to a database with the # same name. On Linux and Mac OS X, the connection # will take place using the server's UNIX domain # socket. On Windows, through the localhost IP @@ -1329,7 +1324,7 @@ # use_tx_tables Valid values: 1, 0 # The default is 1 (true). Determines whether to use # the SQLite transaction database. If set to 0, -# rippled will not write to the transaction database, +# xahaud will not write to the transaction database, # and will reject tx, account_tx and tx_history RPCs. # In Reporting Mode, this setting is ignored. # @@ -1357,7 +1352,7 @@ # # These settings are designed to help server administrators diagnose # problems, and obtain detailed information about the activities being -# performed by the rippled process. +# performed by the xahaud process. # # # @@ -1374,7 +1369,7 @@ # # Configuration parameters for the Beast. Insight stats collection module. # -# Insight is a module that collects information from the areas of rippled +# Insight is a module that collects information from the areas of xahaud # that have instrumentation. The configuration parameters control where the # collection metrics are sent. The parameters are expressed as key = value # pairs with no white space. The main parameter is the choice of server: @@ -1383,7 +1378,7 @@ # # Choice of server to send metrics to. Currently the only choice is # "statsd" which sends UDP packets to a StatsD daemon, which must be -# running while rippled is running. More information on StatsD is +# running while xahaud is running. More information on StatsD is # available here: # https://github.com/b/statsd_spec # @@ -1393,7 +1388,7 @@ # in the format, n.n.n.n:port. # # "prefix" A string prepended to each collected metric. This is used -# to distinguish between different running instances of rippled. +# to distinguish between different running instances of xahaud. # # If this section is missing, or the server type is unspecified or unknown, # statistics are not collected or reported. @@ -1420,7 +1415,7 @@ # # Example: # [perf] -# perf_log=/var/log/rippled/perf.log +# perf_log=/var/log/xahaud/perf.log # log_interval=2 # #------------------------------------------------------------------------------- @@ -1429,8 +1424,8 @@ # #---------- # -# The vote settings configure settings for the entire Ripple network. -# While a single instance of rippled cannot unilaterally enforce network-wide +# The vote settings configure settings for the entire Xahau Network. +# While a single instance of xahaud cannot unilaterally enforce network-wide # settings, these choices become part of the instance's vote during the # consensus process for each voting ledger. # @@ -1442,9 +1437,9 @@ # # The cost of the reference transaction fee, specified in drops. # The reference transaction is the simplest form of transaction. -# It represents an XRP payment between two parties. +# It represents an XAH payment between two parties. # -# If this parameter is unspecified, rippled will use an internal +# If this parameter is unspecified, xahaud will use an internal # default. Don't change this without understanding the consequences. # # Example: @@ -1453,26 +1448,26 @@ # account_reserve = # # The account reserve requirement is specified in drops. The portion of an -# account's XRP balance that is at or below the reserve may only be +# account's XAH balance that is at or below the reserve may only be # spent on transaction fees, and not transferred out of the account. # -# If this parameter is unspecified, rippled will use an internal +# If this parameter is unspecified, xahaud will use an internal # default. Don't change this without understanding the consequences. # # Example: -# account_reserve = 10000000 # 10 XRP +# account_reserve = 10000000 # 10 XAH # # owner_reserve = # -# The owner reserve is the amount of XRP reserved in the account for +# The owner reserve is the amount of XAH reserved in the account for # each ledger item owned by the account. Ledger items an account may # own include trust lines, open orders, and tickets. # -# If this parameter is unspecified, rippled will use an internal +# If this parameter is unspecified, xahaud will use an internal # default. Don't change this without understanding the consequences. # # Example: -# owner_reserve = 2000000 # 2 XRP +# owner_reserve = 2000000 # 2 XAH # #------------------------------------------------------------------------------- # @@ -1510,7 +1505,7 @@ # tool instead. # # This flag has no effect on the "sign" and "sign_for" command line options -# that rippled makes available. +# that xahaud makes available. # # The default value of this field is "false" # @@ -1589,7 +1584,7 @@ #-------------------- # # Administrators can use these values as a starting point for configuring -# their instance of rippled, but each value should be checked to make sure +# their instance of xahaud, but each value should be checked to make sure # it meets the business requirements for the organization. # # Server @@ -1599,7 +1594,7 @@ # "peer" # # Peer protocol open to everyone. This is required to accept -# incoming rippled connections. This does not affect automatic +# incoming xahaud connections. This does not affect automatic # or manual outgoing Peer protocol connections. # # "rpc" @@ -1627,8 +1622,8 @@ # NOTE # # To accept connections on well known ports such as 80 (HTTP) or -# 443 (HTTPS), most operating systems will require rippled to -# run with administrator privileges, or else rippled will not start. +# 443 (HTTPS), most operating systems will require xahaud to +# run with administrator privileges, or else xahaud will not start. [server] port_rpc_admin_local @@ -1639,20 +1634,20 @@ port_ws_admin_local #ssl_cert = /etc/ssl/certs/server.crt [port_rpc_admin_local] -port = 5005 +port = 5009 ip = 127.0.0.1 admin = 127.0.0.1 protocol = http [port_peer] -port = 51235 +port = 21337 ip = 0.0.0.0 # alternatively, to accept connections on IPv4 + IPv6, use: #ip = :: protocol = peer [port_ws_admin_local] -port = 6006 +port = 6009 ip = 127.0.0.1 admin = 127.0.0.1 protocol = ws @@ -1663,15 +1658,15 @@ ip = 127.0.0.1 secure_gateway = 127.0.0.1 #[port_ws_public] -#port = 6005 +#port = 6008 #ip = 127.0.0.1 #protocol = wss #------------------------------------------------------------------------------- -# This is primary persistent datastore for rippled. This includes transaction +# This is primary persistent datastore for xahaud. This includes transaction # metadata, account states, and ledger headers. Helpful information can be -# found at https://xrpl.org/capacity-planning.html#node-db-type +# found at https://xahau.network/docs/infrastructure/system-requirements # type=NuDB is recommended for non-validators with fast SSDs. Validators or # slow / spinning disks should use RocksDB. Caution: Spinning disks are # not recommended. They do not perform well enough to consistently remain @@ -1684,16 +1679,16 @@ secure_gateway = 127.0.0.1 # deletion. [node_db] type=NuDB -path=/var/lib/rippled/db/nudb +path=/opt/xahaud/db/nudb online_delete=512 advisory_delete=0 # This is the persistent datastore for shards. It is important for the health -# of the ripple network that rippled operators shard as much as practical. +# of the Xahau Network that xahaud operators shard as much as practical. # NuDB requires SSD storage. Helpful information can be found at # https://xrpl.org/history-sharding.html #[shard_db] -#path=/var/lib/rippled/db/shards/nudb +#path=/opt/xahaud/db/shards/nudb #max_historical_shards=50 # # This optional section can be configured with a list @@ -1704,7 +1699,7 @@ advisory_delete=0 #/path/2 [database_path] -/var/lib/rippled/db +/opt/xahaud/db # To use Postgres, uncomment this section and fill in the appropriate connection @@ -1719,7 +1714,7 @@ advisory_delete=0 # This needs to be an absolute directory reference, not a relative one. # Modify this value as required. [debug_logfile] -/var/log/rippled/debug.log +/var/log/xahaud/debug.log [sntp_servers] time.windows.com @@ -1727,15 +1722,19 @@ time.apple.com time.nist.gov pool.ntp.org -# To use the XRP test network -# (see https://xrpl.org/connect-your-rippled-to-the-xrp-test-net.html), +# To use the Xahau Test Network +# (see https://xahau.network/docs/infrastructure/installing-xahaud), # use the following [ips] section: # [ips] -# r.altnet.rippletest.net 51235 +# 79.110.60.121 21338 +# 79.110.60.122 21338 +# 79.110.60.124 21338 +# 79.110.60.125 21338 + # File containing trusted validator keys or validator list publishers. # Unless an absolute path is specified, it will be considered relative to the -# folder in which the rippled.cfg file is located. +# folder in which the xahaud.cfg file is located. [validators_file] validators.txt diff --git a/cfg/rippled-reporting.cfg b/cfg/xahaud-reporting.cfg similarity index 90% rename from cfg/rippled-reporting.cfg rename to cfg/xahaud-reporting.cfg index dbafdd497..03ac0f33e 100644 --- a/cfg/rippled-reporting.cfg +++ b/cfg/xahaud-reporting.cfg @@ -9,7 +9,7 @@ # # 2. Peer Protocol # -# 3. Ripple Protocol +# 3. XRPL Protocol # # 4. HTTPS Client # @@ -29,18 +29,16 @@ # # Purpose # -# This file documents and provides examples of all rippled server process -# configuration options. When the rippled server instance is launched, it +# This file documents and provides examples of all xahaud server process +# configuration options. When the xahaud server instance is launched, it # looks for a file with the following name: # -# rippled.cfg +# xahaud.cfg # -# For more information on where the rippled server instance searches for the -# file, visit: +# To run xahaud with a custom configuration file, use the "--conf {file}" flag. +# By default, xahaud will look in the local working directory or the home directory # -# https://xrpl.org/commandline-usage.html#generic-options -# -# This file should be named rippled.cfg. This file is UTF-8 with DOS, UNIX, +# This file should be named xahaud.cfg. This file is UTF-8 with DOS, UNIX, # or Mac style end of lines. Blank lines and lines beginning with '#' are # ignored. Undefined sections are reserved. No escapes are currently defined. # @@ -89,8 +87,8 @@ # # # -# rippled offers various server protocols to clients making inbound -# connections. The listening ports rippled uses are "universal" ports +# xahaud offers various server protocols to clients making inbound +# connections. The listening ports xahaud uses are "universal" ports # which may be configured to handshake in one or more of the available # supported protocols. These universal ports simplify administration: # A single open port can be used for multiple protocols. @@ -103,7 +101,7 @@ # # A list of port names and key/value pairs. A port name must start with a # letter and contain only letters and numbers. The name is not case-sensitive. -# For each name in this list, rippled will look for a configuration file +# For each name in this list, xahaud will look for a configuration file # section with the same name and use it to create a listening port. The # name is informational only; the choice of name does not affect the function # of the listening port. @@ -134,7 +132,7 @@ # ip = 127.0.0.1 # protocol = http # -# When rippled is used as a command line client (for example, issuing a +# When xahaud is used as a command line client (for example, issuing a # server stop command), the first port advertising the http or https # protocol will be used to make the connection. # @@ -175,7 +173,7 @@ # same time. It is possible have both Websockets and Secure Websockets # together in one port. # -# NOTE If no ports support the peer protocol, rippled cannot +# NOTE If no ports support the peer protocol, xahaud cannot # receive incoming peer connections or become a superpeer. # # limit = @@ -194,7 +192,7 @@ # required. IP address restrictions, if any, will be checked in addition # to the credentials specified here. # -# When acting in the client role, rippled will supply these credentials +# When acting in the client role, xahaud will supply these credentials # using HTTP's Basic Authentication headers when making outbound HTTP/S # requests. # @@ -227,7 +225,7 @@ # WS, or WSS protocol interfaces. If administrative commands are # disabled for a port, these credentials have no effect. # -# When acting in the client role, rippled will supply these credentials +# When acting in the client role, xahaud will supply these credentials # in the submitted JSON for any administrative command requests when # invoking JSON-RPC commands on remote servers. # @@ -247,11 +245,11 @@ # resource controls will default to those for non-administrative users. # # The secure_gateway IP addresses are intended to represent -# proxies. Since rippled trusts these hosts, they must be +# proxies. Since xahaud trusts these hosts, they must be # responsible for properly authenticating the remote user. # # The same IP address cannot be used in both "admin" and "secure_gateway" -# lists for the same port. In this case, rippled will abort with an error +# lists for the same port. In this case, xahaud will abort with an error # message to the console shortly after startup # # ssl_key = @@ -261,7 +259,7 @@ # Use the specified files when configuring SSL on the port. # # NOTE If no files are specified and secure protocols are selected, -# rippled will generate an internal self-signed certificate. +# xahaud will generate an internal self-signed certificate. # # The files have these meanings: # @@ -284,12 +282,12 @@ # Control the ciphers which the server will support over SSL on the port, # specified using the OpenSSL "cipher list format". # -# NOTE If unspecified, rippled will automatically configure a modern +# NOTE If unspecified, xahaud will automatically configure a modern # cipher suite. This default suite should be widely supported. # # You should not modify this string unless you have a specific # reason and cryptographic expertise. Incorrect modification may -# keep rippled from connecting to other instances of rippled or +# keep xahaud from connecting to other instances of xahaud or # prevent RPC and WebSocket clients from connecting. # # send_queue_limit = [1..65535] @@ -340,7 +338,7 @@ # # Examples: # { "command" : "server_info" } -# { "command" : "log_level", "partition" : "ripplecalc", "severity" : "trace" } +# { "command" : "log_level", "partition" : "xahaucalc", "severity" : "trace" } # # # @@ -369,8 +367,8 @@ #----------------- # # These settings control security and access attributes of the Peer to Peer -# server section of the rippled process. Peer Protocol implements the -# Ripple Payment protocol. It is over peer connections that transactions +# server section of the xahaud process. Peer Protocol implements the +# XRPL Payment protocol. It is over peer connections that transactions # and validations are passed from to machine to machine, to determine the # contents of validated ledgers. # @@ -378,7 +376,7 @@ # # [ips] # -# List of hostnames or ips where the Ripple protocol is served. A default +# List of hostnames or ips where the XRPL protocol is served. A default # starter list is included in the code and used if no other hostnames are # available. # @@ -387,24 +385,23 @@ # does not generally matter. # # The default list of entries is: -# - r.ripple.com 51235 -# - zaphod.alloy.ee 51235 -# - sahyadri.isrdc.in 51235 +# - bacab.alloy.ee 21337 +# - hubs.xahau.as16089.net 21337 # # Examples: # # [ips] # 192.168.0.1 -# 192.168.0.1 2459 -# r.ripple.com 51235 +# 192.168.0.1 21337 +# bacab.alloy.ee 21337 # # # [ips_fixed] # -# List of IP addresses or hostnames to which rippled should always attempt to +# List of IP addresses or hostnames to which xahaud should always attempt to # maintain peer connections with. This is useful for manually forming private # networks, for example to configure a validation server that connects to the -# Ripple network through a public-facing server, or for building a set +# Xahau Network through a public-facing server, or for building a set # of cluster peers. # # One address or domain names per line is allowed. A port must be specified @@ -454,7 +451,7 @@ # # IP address or domain of NTP servers to use for time synchronization. # -# These NTP servers are suitable for rippled servers located in the United +# These NTP servers are suitable for xahaud servers located in the United # States: # time.windows.com # time.apple.com @@ -555,7 +552,7 @@ # # minimum_txn_in_ledger_standalone = # -# Like minimum_txn_in_ledger when rippled is running in standalone +# Like minimum_txn_in_ledger when xahaud is running in standalone # mode. Default: 1000. # # target_txn_in_ledger = @@ -682,7 +679,7 @@ # # [validator_token] # -# This is an alternative to [validation_seed] that allows rippled to perform +# This is an alternative to [validation_seed] that allows xahaud to perform # validation without having to store the validator keys on the network # connected server. The field should contain a single token in the form of a # base64-encoded blob. @@ -717,22 +714,21 @@ # # Specify the file by its name or path. # Unless an absolute path is specified, it will be considered relative to -# the folder in which the rippled.cfg file is located. +# the folder in which the xahaud.cfg file is located. # # Examples: -# /home/ripple/validators.txt -# C:/home/ripple/validators.txt +# /home/xahaud/validators.txt +# C:/home/xahaud/validators.txt # # Example content: # [validators] -# n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7 -# n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj -# n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C -# n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS -# n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA -# -# -# +# n9L3GdotB8a3AqtsvS7NXt4BUTQSAYyJUr9xtFj2qXJjfbZsawKY +# n9LQDHLWyFuAn5BXJuW2ow5J9uGqpmSjRYS2cFRpxf6uJbxwDzvM +# n9MCWyKVUkiatXVJTKUrAESB5kBFP8R3hm43jGHtg8WBnjv3iDfb +# n9KWXCLRhjpajuZtULTXsy6R5xbisA6ozGxM4zdEJFq6uHiFZDvW + + + # [path_search] # When searching for paths, the default search aggressiveness. This can take # exponentially more resources as the size is increased. @@ -795,7 +791,7 @@ # # 0: Disable the ledger replay feature [default] # 1: Enable the ledger replay feature. With this feature enabled, when -# acquiring a ledger from the network, a rippled node only downloads +# acquiring a ledger from the network, a xahaud node only downloads # the ledger header and the transactions instead of the whole ledger. # And the ledger is built by applying the transactions to the parent # ledger. @@ -806,9 +802,9 @@ # #---------------- # -# The rippled server instance uses HTTPS GET requests in a variety of +# The xahaud server instance uses HTTPS GET requests in a variety of # circumstances, including but not limited to contacting trusted domains to -# fetch information such as mapping an email address to a Ripple Payment +# fetch information such as mapping an email address to a XRPL Payment # Network address. # # [ssl_verify] @@ -846,15 +842,15 @@ # #------------ # -# rippled has an optional operating mode called Reporting Mode. In Reporting -# Mode, rippled does not connect to the peer to peer network. Instead, rippled -# will continuously extract data from one or more rippled servers that are +# xahaud has an optional operating mode called Reporting Mode. In Reporting +# Mode, xahaud does not connect to the peer to peer network. Instead, xahaud +# will continuously extract data from one or more xahaud servers that are # connected to the peer to peer network (referred to as an ETL source). # Reporting mode servers will forward RPC requests that require access to the # peer to peer network (submit, fee, etc) to an ETL source. # # [reporting] Settings for Reporting Mode. If and only if this section is -# present, rippled will start in reporting mode. This section +# present, xahaud will start in reporting mode. This section # contains a list of ETL source names, and key-value pairs. The # ETL source names each correspond to a configuration file # section; the names must match exactly. The key-value pairs are @@ -959,16 +955,16 @@ # #------------ # -# rippled creates 4 SQLite database to hold bookkeeping information +# xahaud creates 4 SQLite database to hold bookkeeping information # about transactions, local credentials, and various other things. # It also creates the NodeDB, which holds all the objects that -# make up the current and historical ledgers. In Reporting Mode, rippled +# make up the current and historical ledgers. In Reporting Mode, xahaud # uses a Postgres database instead of SQLite. # # The simplest way to work with Postgres is to install it locally. # When it is running, execute the initdb.sh script in the current # directory as: sudo -u postgres ./initdb.sh -# This will create the rippled user and an empty database of the same name. +# This will create the xahaud user and an empty database of the same name. # # The size of the NodeDB grows in proportion to the amount of new data and the # amount of historical data (a configurable setting) so the performance of the @@ -976,7 +972,7 @@ # the performance of the server. # # Partial pathnames will be considered relative to the location of -# the rippled.cfg file. +# the xahaud.cfg file. # # [node_db] Settings for the Node Database (required) # @@ -994,11 +990,11 @@ # type = NuDB # # NuDB is a high-performance database written by Ripple Labs and optimized -# for rippled and solid-state drives. +# for solid-state drives. # # NuDB maintains its high speed regardless of the amount of history # stored. Online delete may be selected, but is not required. NuDB is -# available on all platforms that rippled runs on. +# available on all platforms that xahaud runs on. # # type = RocksDB # @@ -1103,14 +1099,14 @@ # # recovery_wait_seconds # The online delete process checks periodically -# that rippled is still in sync with the network, +# that xahaud is still in sync with the network, # and that the validated ledger is less than # 'age_threshold_seconds' old. By default, if it # is not the online delete process aborts and # tries again later. If 'recovery_wait_seconds' -# is set and rippled is out of sync, but likely to +# is set and xahaud is out of sync, but likely to # recover quickly, then online delete will wait -# this number of seconds for rippled to get back +# this number of seconds for xahaud to get back # into sync before it aborts. # Set this value if the node is otherwise staying # in sync, or recovering quickly, but the online @@ -1146,8 +1142,8 @@ # The server creates and maintains 4 to 5 bookkeeping SQLite databases in # the 'database_path' location. If you omit this configuration setting, # the server creates a directory called "db" located in the same place as -# your rippled.cfg file. -# Partial pathnames are relative to the location of the rippled executable. +# your xahaud.cfg file. +# Partial pathnames are relative to the location of the xahaud executable. # # [shard_db] Settings for the Shard Database (optional) # @@ -1223,7 +1219,7 @@ # The default is "wal", which uses a write-ahead # log to implement database transactions. # Alternately, "memory" saves disk I/O, but if -# rippled crashes during a transaction, the +# xahaud crashes during a transaction, the # database is likely to be corrupted. # See https://www.sqlite.org/pragma.html#pragma_journal_mode # for more details about the available options. @@ -1233,7 +1229,7 @@ # synchronous Valid values: off, normal, full, extra # The default is "normal", which works well with # the "wal" journal mode. Alternatively, "off" -# allows rippled to continue as soon as data is +# allows xahaud to continue as soon as data is # passed to the OS, which can significantly # increase speed, but risks data corruption if # the host computer crashes before writing that @@ -1247,7 +1243,7 @@ # The default is "file", which will use files # for temporary database tables and indices. # Alternatively, "memory" may save I/O, but -# rippled does not currently use many, if any, +# xahaud does not currently use many, if any, # of these temporary objects. # See https://www.sqlite.org/pragma.html#pragma_temp_store # for more details about the available options. @@ -1259,9 +1255,9 @@ # conninfo Info for connecting to Postgres. Format is # postgres://[username]:[password]@[ip]/[database]. # The database and user must already exist. If this -# section is missing and rippled is running in -# Reporting Mode, rippled will connect as the -# user running rippled to a database with the +# section is missing and xahaud is running in +# Reporting Mode, xahaud will connect as the +# user running xahaud to a database with the # same name. On Linux and Mac OS X, the connection # will take place using the server's UNIX domain # socket. On Windows, through the localhost IP @@ -1270,7 +1266,7 @@ # use_tx_tables Valid values: 1, 0 # The default is 1 (true). Determines whether to use # the SQLite transaction database. If set to 0, -# rippled will not write to the transaction database, +# xahaud will not write to the transaction database, # and will reject tx, account_tx and tx_history RPCs. # In Reporting Mode, this setting is ignored. # @@ -1298,7 +1294,7 @@ # # These settings are designed to help server administrators diagnose # problems, and obtain detailed information about the activities being -# performed by the rippled process. +# performed by the xahaud process. # # # @@ -1315,7 +1311,7 @@ # # Configuration parameters for the Beast. Insight stats collection module. # -# Insight is a module that collects information from the areas of rippled +# Insight is a module that collects information from the areas of xahaud # that have instrumentation. The configuration parameters control where the # collection metrics are sent. The parameters are expressed as key = value # pairs with no white space. The main parameter is the choice of server: @@ -1324,7 +1320,7 @@ # # Choice of server to send metrics to. Currently the only choice is # "statsd" which sends UDP packets to a StatsD daemon, which must be -# running while rippled is running. More information on StatsD is +# running while xahaud is running. More information on StatsD is # available here: # https://github.com/b/statsd_spec # @@ -1334,7 +1330,7 @@ # in the format, n.n.n.n:port. # # "prefix" A string prepended to each collected metric. This is used -# to distinguish between different running instances of rippled. +# to distinguish between different running instances of xahaud. # # If this section is missing, or the server type is unspecified or unknown, # statistics are not collected or reported. @@ -1361,7 +1357,7 @@ # # Example: # [perf] -# perf_log=/var/log/rippled/perf.log +# perf_log=/var/log/xahaud/perf.log # log_interval=2 # #------------------------------------------------------------------------------- @@ -1370,8 +1366,8 @@ # #---------- # -# The vote settings configure settings for the entire Ripple network. -# While a single instance of rippled cannot unilaterally enforce network-wide +# The vote settings configure settings for the entire Xahau Network. +# While a single instance of xahaud cannot unilaterally enforce network-wide # settings, these choices become part of the instance's vote during the # consensus process for each voting ledger. # @@ -1383,9 +1379,9 @@ # # The cost of the reference transaction fee, specified in drops. # The reference transaction is the simplest form of transaction. -# It represents an XRP payment between two parties. +# It represents an XAH payment between two parties. # -# If this parameter is unspecified, rippled will use an internal +# If this parameter is unspecified, xahaud will use an internal # default. Don't change this without understanding the consequences. # # Example: @@ -1394,26 +1390,26 @@ # account_reserve = # # The account reserve requirement is specified in drops. The portion of an -# account's XRP balance that is at or below the reserve may only be +# account's XAH balance that is at or below the reserve may only be # spent on transaction fees, and not transferred out of the account. # -# If this parameter is unspecified, rippled will use an internal +# If this parameter is unspecified, xahaud will use an internal # default. Don't change this without understanding the consequences. # # Example: -# account_reserve = 10000000 # 10 XRP +# account_reserve = 10000000 # 10 XAH # # owner_reserve = # -# The owner reserve is the amount of XRP reserved in the account for +# The owner reserve is the amount of XAH reserved in the account for # each ledger item owned by the account. Ledger items an account may # own include trust lines, open orders, and tickets. # -# If this parameter is unspecified, rippled will use an internal +# If this parameter is unspecified, xahaud will use an internal # default. Don't change this without understanding the consequences. # # Example: -# owner_reserve = 2000000 # 2 XRP +# owner_reserve = 2000000 # 2 XAH # #------------------------------------------------------------------------------- # @@ -1451,7 +1447,7 @@ # tool instead. # # This flag has no effect on the "sign" and "sign_for" command line options -# that rippled makes available. +# that xahaud makes available. # # The default value of this field is "false" # @@ -1530,7 +1526,7 @@ #-------------------- # # Administrators can use these values as a starting point for configuring -# their instance of rippled, but each value should be checked to make sure +# their instance of xahaud, but each value should be checked to make sure # it meets the business requirements for the organization. # # Server @@ -1540,7 +1536,7 @@ # "peer" # # Peer protocol open to everyone. This is required to accept -# incoming rippled connections. This does not affect automatic +# incoming xahaud connections. This does not affect automatic # or manual outgoing Peer protocol connections. # # "rpc" @@ -1568,8 +1564,8 @@ # NOTE # # To accept connections on well known ports such as 80 (HTTP) or -# 443 (HTTPS), most operating systems will require rippled to -# run with administrator privileges, or else rippled will not start. +# 443 (HTTPS), most operating systems will require xahaud to +# run with administrator privileges, or else xahaud will not start. [server] port_rpc_admin_local @@ -1587,7 +1583,7 @@ admin = 127.0.0.1 protocol = http [port_peer] -port = 51235 +port = 21337 ip = 0.0.0.0 # alternatively, to accept connections on IPv4 + IPv6, use: #ip = :: @@ -1611,9 +1607,9 @@ protocol = ws #------------------------------------------------------------------------------- -# This is primary persistent datastore for rippled. This includes transaction +# This is primary persistent datastore for xahaud. This includes transaction # metadata, account states, and ledger headers. Helpful information can be -# found at https://xrpl.org/capacity-planning.html#node-db-type +# found at https://xahau.network/docs/infrastructure/system-requirements # type=NuDB is recommended for non-validators with fast SSDs. Validators or # slow / spinning disks should use RocksDB. Caution: Spinning disks are # not recommended. They do not perform well enough to consistently remain @@ -1626,16 +1622,16 @@ protocol = ws # deletion. [node_db] type=NuDB -path=/var/lib/rippled-reporting/db/nudb +path=/opt/xahaud-reporting/db/nudb # online_delete=512 # advisory_delete=0 # This is the persistent datastore for shards. It is important for the health -# of the ripple network that rippled operators shard as much as practical. +# of the Xahau Network that xahaud operators shard as much as practical. # NuDB requires SSD storage. Helpful information can be found at # https://xrpl.org/history-sharding.html #[shard_db] -#path=/var/lib/rippled/db/shards/nudb +#path=/opt/xahaud-reporting/db/shards/nudb #max_historical_shards=50 # # This optional section can be configured with a list @@ -1646,7 +1642,7 @@ advisory_delete=0 #/path/2 [database_path] -/var/lib/rippled-reporting/db +/opt/xahaud-reporting/db # To use Postgres, uncomment this section and fill in the appropriate connection # info. Postgres can only be used in Reporting Mode. @@ -1660,7 +1656,7 @@ advisory_delete=0 # This needs to be an absolute directory reference, not a relative one. # Modify this value as required. [debug_logfile] -/var/log/rippled-reporting/debug.log +/var/log/xahaud-reporting/debug.log [sntp_servers] time.windows.com @@ -1668,17 +1664,20 @@ time.apple.com time.nist.gov pool.ntp.org -# To use the XRP test network -# (see https://xrpl.org/connect-your-rippled-to-the-xrp-test-net.html), +# To use the Xahau Test Network +# (see https://xahau.network/docs/infrastructure/installing-xahaud), # use the following [ips] section: # [ips] -# r.altnet.rippletest.net 51235 +# 79.110.60.121 21338 +# 79.110.60.122 21338 +# 79.110.60.124 21338 +# 79.110.60.125 21338 # File containing trusted validator keys or validator list publishers. # Unless an absolute path is specified, it will be considered relative to the -# folder in which the rippled.cfg file is located. +# folder in which the xahaud.cfg file is located. [validators_file] -/opt/rippled-reporting/etc/validators.txt +/opt/xahaud-reporting/etc/validators.txt # Turn down default logging to save disk space in the long run. # Valid values here are trace, debug, info, warning, error, and fatal @@ -1699,5 +1698,5 @@ etl_source [etl_source] source_grpc_port=50051 -source_ws_port=6005 +source_ws_port=6008 source_ip=127.0.0.1 diff --git a/cfg/rippled-standalone.cfg b/cfg/xahaud-standalone.cfg old mode 100755 new mode 100644 similarity index 97% rename from cfg/rippled-standalone.cfg rename to cfg/xahaud-standalone.cfg index 3301299ca..3933b4ba8 --- a/cfg/rippled-standalone.cfg +++ b/cfg/xahaud-standalone.cfg @@ -1,4 +1,4 @@ -# standalone: ./rippled -a --ledgerfile config/genesis.json --conf config/rippled-standalone.cfg +# standalone: ./xahaud -a --ledgerfile config/genesis.json --conf config/xahaud-standalone.cfg [server] port_rpc_admin_local port_ws_public @@ -21,7 +21,7 @@ ip = 0.0.0.0 protocol = ws # [port_peer] -# port = 51235 +# port = 21337 # ip = 0.0.0.0 # protocol = peer @@ -69,7 +69,8 @@ time.nist.gov pool.ntp.org [ips] -r.ripple.com 51235 +bacab.alloy.ee 21337 +hubs.xahau.as16089.net 21337 [validators_file] validators-example.txt @@ -94,7 +95,7 @@ validators-example.txt 1000000 [network_id] -21338 +21337 [amendments] 740352F2412A9909880C23A559FCECEDA3BE2126FED62FC7660D628A06927F11 Flow From 39d1c439015883e26135e8d149c47a5142e36556 Mon Sep 17 00:00:00 2001 From: Niq Dudfield Date: Fri, 10 Oct 2025 16:53:35 +0700 Subject: [PATCH 04/20] build: upgrade openssl from 1.1.1u to 3.6.0 (#587) Updates OpenSSL dependency to the latest 3.x series available on Conan Center. --- conanfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conanfile.py b/conanfile.py index 4b98b84db..e29a880d8 100644 --- a/conanfile.py +++ b/conanfile.py @@ -31,7 +31,7 @@ class Xrpl(ConanFile): 'lz4/1.9.4', 'grpc/1.50.1', 'nudb/2.0.8', - 'openssl/1.1.1u', + 'openssl/3.6.0', 'protobuf/3.21.12', 'soci/4.0.3@xahaud/stable', 'zlib/1.3.1', From 094f011006f6a7501cf721f90aedfb7464292d1c Mon Sep 17 00:00:00 2001 From: tequ Date: Sat, 11 Oct 2025 10:43:09 +0900 Subject: [PATCH 05/20] Fix `emit` Hook API testcase name (#580) --- src/test/app/SetHook_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/app/SetHook_test.cpp b/src/test/app/SetHook_test.cpp index c3b336d05..3fb16176b 100644 --- a/src/test/app/SetHook_test.cpp +++ b/src/test/app/SetHook_test.cpp @@ -2434,7 +2434,7 @@ public: void test_emit(FeatureBitset features) { - testcase("Test float_emit"); + testcase("Test emit"); using namespace jtx; Env env{*this, features}; From e580f7cfc0c02986132360f1669e82b6df2f9599 Mon Sep 17 00:00:00 2001 From: tequ Date: Sat, 11 Oct 2025 10:43:50 +0900 Subject: [PATCH 06/20] chore(vscode): enable format on save in settings.json (#578) --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 1642d6324..313281c33 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,6 +8,6 @@ "editor.semanticHighlighting.enabled": true, "editor.tabSize": 4, "editor.defaultFormatter": "xaver.clang-format", - "editor.formatOnSave": false + "editor.formatOnSave": true } } From ad0531ad6c7f970d03423e688d27472babf55cf7 Mon Sep 17 00:00:00 2001 From: Niq Dudfield Date: Sat, 11 Oct 2025 08:47:13 +0700 Subject: [PATCH 07/20] chore: fix warnings (#509) Co-authored-by: Denis Angell Co-authored-by: RichardAH --- src/ripple/app/rdb/backend/RWDBDatabase.h | 3 ++- src/ripple/overlay/impl/Handshake.cpp | 12 +++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/ripple/app/rdb/backend/RWDBDatabase.h b/src/ripple/app/rdb/backend/RWDBDatabase.h index f91e50cda..308c4366f 100644 --- a/src/ripple/app/rdb/backend/RWDBDatabase.h +++ b/src/ripple/app/rdb/backend/RWDBDatabase.h @@ -221,7 +221,8 @@ public: if (!ledger->info().accountHash.isNonZero()) { - JLOG(j.fatal()) << "AH is zero: " << getJson({*ledger, {}}); + JLOG(j.fatal()) + << "AH is zero: " << getJson({*ledger, {}}).asString(); assert(false); } diff --git a/src/ripple/overlay/impl/Handshake.cpp b/src/ripple/overlay/impl/Handshake.cpp index 11b75e28d..73d40e6c5 100644 --- a/src/ripple/overlay/impl/Handshake.cpp +++ b/src/ripple/overlay/impl/Handshake.cpp @@ -239,19 +239,17 @@ verifyHandshake( throw std::runtime_error("Invalid server domain"); } - // Check the network. Omitting Network-ID (on either side ours, or theirs) - // means NID=0 + // Check network ID, treating absent/empty as default network 0 { - uint32_t peer_nid = 0; + std::uint32_t nid{0}; + if (auto const iter = headers.find("Network-ID"); iter != headers.end()) { - if (!beast::lexicalCastChecked( - peer_nid, std::string(iter->value()))) + if (!beast::lexicalCastChecked(nid, std::string(iter->value()))) throw std::runtime_error("Invalid peer network identifier"); } - uint32_t our_nid = networkID ? *networkID : 0; - if (peer_nid != our_nid) + if (networkID.value_or(0) != nid) throw std::runtime_error("Peer is on a different network"); } From 1f12b9ec5aed58bf9c722ae0b449dba1643fd9bc Mon Sep 17 00:00:00 2001 From: Niq Dudfield Date: Tue, 14 Oct 2025 07:44:03 +0700 Subject: [PATCH 08/20] feat(logs): add -DBEAST_ENHANCED_LOGGING with file:line numbers for JLOG macro (#552) --- .../actions/xahau-ga-dependencies/action.yml | 2 + Builds/CMake/RippledCore.cmake | 13 ++ CMakeLists.txt | 19 +++ conanfile.py | 4 +- src/ripple/app/misc/impl/Manifest.cpp | 57 ++++----- src/ripple/basics/Log.h | 11 +- src/ripple/basics/impl/Log.cpp | 51 +++++++- src/ripple/beast/utility/EnhancedLogging.h | 85 +++++++++++++ src/ripple/beast/utility/Journal.h | 61 ++++++++++ .../utility/src/beast_EnhancedLogging.cpp | 114 ++++++++++++++++++ .../beast/utility/src/beast_Journal.cpp | 48 +++++++- src/test/ledger/Invariants_test.cpp | 25 +++- 12 files changed, 448 insertions(+), 42 deletions(-) create mode 100644 src/ripple/beast/utility/EnhancedLogging.h create mode 100644 src/ripple/beast/utility/src/beast_EnhancedLogging.cpp diff --git a/.github/actions/xahau-ga-dependencies/action.yml b/.github/actions/xahau-ga-dependencies/action.yml index cb14e4e57..afa1067b6 100644 --- a/.github/actions/xahau-ga-dependencies/action.yml +++ b/.github/actions/xahau-ga-dependencies/action.yml @@ -84,6 +84,8 @@ runs: - name: Install dependencies shell: bash + env: + CONAN_REQUEST_TIMEOUT: 180 # Increase timeout to 3 minutes for slow mirrors run: | # Create build directory mkdir -p ${{ inputs.build_dir }} diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 6b876997b..58cd234e4 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -50,6 +50,12 @@ target_sources (xrpl_core PRIVATE src/ripple/beast/utility/src/beast_Journal.cpp src/ripple/beast/utility/src/beast_PropertyStream.cpp) +# Conditionally add enhanced logging source when BEAST_ENHANCED_LOGGING is enabled +if(DEFINED BEAST_ENHANCED_LOGGING AND BEAST_ENHANCED_LOGGING) + target_sources(xrpl_core PRIVATE + src/ripple/beast/utility/src/beast_EnhancedLogging.cpp) +endif() + #[===============================[ core sources #]===============================] @@ -155,6 +161,13 @@ target_link_libraries (xrpl_core ed25519::ed25519 date::date Ripple::opts) + +# Link date-tz library when enhanced logging is enabled +if(DEFINED BEAST_ENHANCED_LOGGING AND BEAST_ENHANCED_LOGGING) + if(TARGET date::date-tz) + target_link_libraries(xrpl_core PUBLIC date::date-tz) + endif() +endif() #[=================================[ main/core headers installation #]=================================] diff --git a/CMakeLists.txt b/CMakeLists.txt index a50560cb3..9a31d4931 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,25 @@ if(Git_FOUND) endif() endif() #git +# make SOURCE_ROOT_PATH define available for logging +set(SOURCE_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/src/") +add_definitions(-DSOURCE_ROOT_PATH="${SOURCE_ROOT_PATH}") + +# BEAST_ENHANCED_LOGGING option - adds file:line numbers and formatting to logs +# Default to ON for Debug builds, OFF for Release +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + option(BEAST_ENHANCED_LOGGING "Include file and line numbers in log messages" ON) +else() + option(BEAST_ENHANCED_LOGGING "Include file and line numbers in log messages" OFF) +endif() + +if(BEAST_ENHANCED_LOGGING) + add_definitions(-DBEAST_ENHANCED_LOGGING=1) + message(STATUS "Log line numbers enabled") +else() + message(STATUS "Log line numbers disabled") +endif() + if(thread_safety_analysis) add_compile_options(-Wthread-safety -D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS -DRIPPLE_ENABLE_THREAD_SAFETY_ANNOTATIONS) add_compile_options("-stdlib=libc++") diff --git a/conanfile.py b/conanfile.py index e29a880d8..477ee3825 100644 --- a/conanfile.py +++ b/conanfile.py @@ -26,7 +26,7 @@ class Xrpl(ConanFile): } requires = [ - 'date/3.0.1', + 'date/3.0.3', 'libarchive/3.6.0', 'lz4/1.9.4', 'grpc/1.50.1', @@ -52,7 +52,7 @@ class Xrpl(ConanFile): 'tool_requires_b2': False, 'cassandra-cpp-driver/*:shared': False, - 'date/*:header_only': True, + 'date/*:header_only': False, 'grpc/*:shared': False, 'grpc/*:secure': True, 'libarchive/*:shared': False, diff --git a/src/ripple/app/misc/impl/Manifest.cpp b/src/ripple/app/misc/impl/Manifest.cpp index 60b521330..44e19244a 100644 --- a/src/ripple/app/misc/impl/Manifest.cpp +++ b/src/ripple/app/misc/impl/Manifest.cpp @@ -156,34 +156,22 @@ deserializeManifest(Slice s, beast::Journal journal) } } -template -Stream& -logMftAct( - Stream& s, - std::string const& action, - PublicKey const& pk, - std::uint32_t seq) -{ - s << "Manifest: " << action - << ";Pk: " << toBase58(TokenType::NodePublic, pk) << ";Seq: " << seq - << ";"; - return s; -} +// Helper macros to format manifest log messages while preserving line numbers +#define LOG_MANIFEST_ACTION(stream, action, pk, seq) \ + do \ + { \ + JLOG(stream) << "Manifest: " << action \ + << ";Pk: " << toBase58(TokenType::NodePublic, pk) \ + << ";Seq: " << seq << ";"; \ + } while (0) -template -Stream& -logMftAct( - Stream& s, - std::string const& action, - PublicKey const& pk, - std::uint32_t seq, - std::uint32_t oldSeq) -{ - s << "Manifest: " << action - << ";Pk: " << toBase58(TokenType::NodePublic, pk) << ";Seq: " << seq - << ";OldSeq: " << oldSeq << ";"; - return s; -} +#define LOG_MANIFEST_ACTION_WITH_OLD(stream, action, pk, seq, oldSeq) \ + do \ + { \ + JLOG(stream) << "Manifest: " << action \ + << ";Pk: " << toBase58(TokenType::NodePublic, pk) \ + << ";Seq: " << seq << ";OldSeq: " << oldSeq << ";"; \ + } while (0) bool Manifest::verify() const @@ -381,7 +369,7 @@ ManifestCache::applyManifest(Manifest m) // several cases including when we receive manifests from a peer who // doesn't have the latest data. if (auto stream = j_.debug()) - logMftAct( + LOG_MANIFEST_ACTION_WITH_OLD( stream, "Stale", m.masterKey, @@ -393,7 +381,7 @@ ManifestCache::applyManifest(Manifest m) if (checkSignature && !m.verify()) { if (auto stream = j_.warn()) - logMftAct(stream, "Invalid", m.masterKey, m.sequence); + LOG_MANIFEST_ACTION(stream, "Invalid", m.masterKey, m.sequence); return ManifestDisposition::invalid; } @@ -407,7 +395,7 @@ ManifestCache::applyManifest(Manifest m) bool const revoked = m.revoked(); if (auto stream = j_.warn(); stream && revoked) - logMftAct(stream, "Revoked", m.masterKey, m.sequence); + LOG_MANIFEST_ACTION(stream, "Revoked", m.masterKey, m.sequence); // Sanity check: the master key of this manifest should not be used as // the ephemeral key of another manifest: @@ -476,7 +464,7 @@ ManifestCache::applyManifest(Manifest m) if (iter == map_.end()) { if (auto stream = j_.info()) - logMftAct(stream, "AcceptedNew", m.masterKey, m.sequence); + LOG_MANIFEST_ACTION(stream, "AcceptedNew", m.masterKey, m.sequence); if (!revoked) signingToMasterKeys_[m.signingKey] = m.masterKey; @@ -489,7 +477,7 @@ ManifestCache::applyManifest(Manifest m) // An ephemeral key was revoked and superseded by a new key. This is // expected, but should happen infrequently. if (auto stream = j_.info()) - logMftAct( + LOG_MANIFEST_ACTION_WITH_OLD( stream, "AcceptedUpdate", m.masterKey, @@ -584,4 +572,9 @@ ManifestCache::save( saveManifests(*db, dbTable, isTrusted, map_, j_); } + +// Clean up macros to avoid namespace pollution +#undef LOG_MANIFEST_ACTION +#undef LOG_MANIFEST_ACTION_WITH_OLD + } // namespace ripple diff --git a/src/ripple/basics/Log.h b/src/ripple/basics/Log.h index 6722ab813..182062ac7 100644 --- a/src/ripple/basics/Log.h +++ b/src/ripple/basics/Log.h @@ -249,13 +249,22 @@ private: // Wraps a Journal::Stream to skip evaluation of // expensive argument lists if the stream is not active. #ifndef JLOG +#ifdef BEAST_ENHANCED_LOGGING #define JLOG(x) \ - if (!x) \ + if (!(x)) \ + { \ + } \ + else \ + (x).withLocation(__FILE__, __LINE__) +#else +#define JLOG(x) \ + if (!(x)) \ { \ } \ else \ x #endif +#endif //------------------------------------------------------------------------------ // Debug logging: diff --git a/src/ripple/basics/impl/Log.cpp b/src/ripple/basics/impl/Log.cpp index c023bc164..f4f52cdc5 100644 --- a/src/ripple/basics/impl/Log.cpp +++ b/src/ripple/basics/impl/Log.cpp @@ -17,11 +17,19 @@ */ //============================================================================== +#include + #include #include #include +#ifdef BEAST_ENHANCED_LOGGING +#include +#include +#endif #include #include +#include +#include #include #include #include @@ -316,11 +324,46 @@ Logs::format( { output.reserve(message.size() + partition.size() + 100); - output = to_string(std::chrono::system_clock::now()); +#ifdef BEAST_ENHANCED_LOGGING + // Environment variables are used instead of config file because: + // 1. Logging starts before config parsing (needed to debug config issues) + // 2. This is a developer feature - devs can easily set env vars + // 3. Allows per-run overrides without editing config files + static const char* fmt = []() { + const char* env = std::getenv("LOG_DATE_FORMAT"); + return env ? env : "%Y-%b-%d %T %Z"; // Default format + }(); + + // Check if we should use local time + static const bool useLocalTime = []() { + const char* env = std::getenv("LOG_DATE_LOCAL"); + return env && std::strcmp(env, "1") == 0; + }(); + + if (useLocalTime) + { + auto now = std::chrono::system_clock::now(); + auto local = date::make_zoned(date::current_zone(), now); + output = date::format(fmt, local); + } + else + { + output = date::format(fmt, std::chrono::system_clock::now()); + } +#else + output = to_string(std::chrono::system_clock::now()); +#endif + + if (!output.empty()) // Allow setting date format to an empty string + output += " "; - output += " "; if (!partition.empty()) + { +#ifdef BEAST_ENHANCED_LOGGING + output += beast::detail::get_log_highlight_color(); +#endif output += partition + ":"; + } using namespace beast::severities; switch (severity) @@ -348,6 +391,10 @@ Logs::format( break; } +#ifdef BEAST_ENHANCED_LOGGING + output += "\033[0m"; +#endif + output += message; // Limit the maximum length of the output diff --git a/src/ripple/beast/utility/EnhancedLogging.h b/src/ripple/beast/utility/EnhancedLogging.h new file mode 100644 index 000000000..198e0b52f --- /dev/null +++ b/src/ripple/beast/utility/EnhancedLogging.h @@ -0,0 +1,85 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + 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 BEAST_UTILITY_ENHANCEDLOGGING_H_INCLUDED +#define BEAST_UTILITY_ENHANCEDLOGGING_H_INCLUDED + +#include // for size_t +#include // for std::ostream + +namespace beast { +namespace detail { + +// Check if we should use colors - cached at startup +bool +should_log_use_colors(); + +// Get the log highlight color - can be overridden via +// LOG_HIGHLIGHT_COLOR +const char* +get_log_highlight_color(); + +// Strip source root path from __FILE__ at compile time +// IMPORTANT: This MUST stay in the header as constexpr for compile-time +// evaluation! +constexpr const char* +strip_source_root(const char* file) +{ +#ifdef SOURCE_ROOT_PATH + constexpr const char* sourceRoot = SOURCE_ROOT_PATH; + constexpr auto strlen_constexpr = [](const char* s) constexpr + { + const char* p = s; + while (*p) + ++p; + return p - s; + }; + constexpr auto strncmp_constexpr = + [](const char* a, const char* b, size_t n) constexpr + { + for (size_t i = 0; i < n; ++i) + { + if (a[i] != b[i]) + return a[i] - b[i]; + if (a[i] == '\0') + break; + } + return 0; + }; + constexpr size_t sourceRootLen = strlen_constexpr(sourceRoot); + return (strncmp_constexpr(file, sourceRoot, sourceRootLen) == 0) + ? file + sourceRootLen + : file; +#else + return file; +#endif +} + +// Check if location info should be shown - cached at startup +bool +should_show_location(); + +// Helper to write location string (no leading/trailing space) +void +log_write_location_string(std::ostream& os, const char* file, int line); + +} // namespace detail +} // namespace beast + +#endif diff --git a/src/ripple/beast/utility/Journal.h b/src/ripple/beast/utility/Journal.h index 333a743a6..f31eb5049 100644 --- a/src/ripple/beast/utility/Journal.h +++ b/src/ripple/beast/utility/Journal.h @@ -146,6 +146,10 @@ private: ScopedStream(Sink& sink, Severity level); +#ifdef BEAST_ENHANCED_LOGGING + ScopedStream(Sink& sink, Severity level, const char* file, int line); +#endif + template ScopedStream(Stream const& stream, T const& t); @@ -173,6 +177,10 @@ private: Sink& m_sink; Severity const m_level; std::ostringstream mutable m_ostream; +#ifdef BEAST_ENHANCED_LOGGING + const char* file_ = nullptr; + int line_ = 0; +#endif }; #ifndef __INTELLISENSE__ @@ -191,6 +199,33 @@ private: //-------------------------------------------------------------------------- public: /** Provide a light-weight way to check active() before string formatting */ + +#ifdef BEAST_ENHANCED_LOGGING + /** Stream with location information that prepends file:line to the first + * message */ + class StreamWithLocation + { + public: + StreamWithLocation(Stream const& stream, const char* file, int line) + : file_(file), line_(line), stream_(stream) + { + } + + /** Override to inject file:line before the first output */ + template + ScopedStream + operator<<(T const& t) const; + + ScopedStream + operator<<(std::ostream& manip(std::ostream&)) const; + + private: + const char* file_; + int line_; + const Stream& stream_; + }; +#endif + class Stream { public: @@ -255,6 +290,15 @@ public: operator<<(T const& t) const; /** @} */ +#ifdef BEAST_ENHANCED_LOGGING + /** Create a StreamWithLocation that prepends file:line info */ + StreamWithLocation + withLocation(const char* file, int line) const + { + return StreamWithLocation(*this, file, line); + } +#endif + private: Sink& m_sink; Severity m_level; @@ -354,6 +398,8 @@ static_assert(std::is_nothrow_destructible::value == true, ""); //------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + template Journal::ScopedStream::ScopedStream(Journal::Stream const& stream, T const& t) : ScopedStream(stream.sink(), stream.level()) @@ -378,6 +424,21 @@ Journal::Stream::operator<<(T const& t) const return ScopedStream(*this, t); } +#ifdef BEAST_ENHANCED_LOGGING +//------------------------------------------------------------------------------ + +template +Journal::ScopedStream +Journal::StreamWithLocation::operator<<(T const& t) const +{ + // Create a ScopedStream with location info + ScopedStream scoped(stream_.sink(), stream_.level(), file_, line_); + scoped.ostream() << t; + return scoped; +} + +#endif + namespace detail { template > diff --git a/src/ripple/beast/utility/src/beast_EnhancedLogging.cpp b/src/ripple/beast/utility/src/beast_EnhancedLogging.cpp new file mode 100644 index 000000000..c5f34bd88 --- /dev/null +++ b/src/ripple/beast/utility/src/beast_EnhancedLogging.cpp @@ -0,0 +1,114 @@ +//------------------------------------------------------------------------------ +/* + This file is part of Beast: https://github.com/vinniefalco/Beast + Copyright 2013, Vinnie Falco + + 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 +#include +#include +#include +#include + +namespace beast { +namespace detail { + +// Check if we should use colors - cached at startup +bool +should_log_use_colors() +{ + static const bool use_colors = []() { + // Honor NO_COLOR environment variable (standard) + if (std::getenv("NO_COLOR")) + return false; + + // Honor FORCE_COLOR to override terminal detection + if (std::getenv("FORCE_COLOR")) + return true; + + // Check if stderr is a terminal + return isatty(STDERR_FILENO) != 0; + }(); + return use_colors; +} + +// Get the log highlight color - can be overridden via +// LOG_HIGHLIGHT_COLOR +const char* +get_log_highlight_color() +{ + static const char* escape = []() { + const char* env = std::getenv("LOG_HIGHLIGHT_COLOR"); + if (!env) + return "\033[36m"; // Default: cyan + + // Simple map of color names to escape sequences + if (std::strcmp(env, "red") == 0) + return "\033[31m"; + if (std::strcmp(env, "green") == 0) + return "\033[32m"; + if (std::strcmp(env, "yellow") == 0) + return "\033[33m"; + if (std::strcmp(env, "blue") == 0) + return "\033[34m"; + if (std::strcmp(env, "magenta") == 0) + return "\033[35m"; + if (std::strcmp(env, "cyan") == 0) + return "\033[36m"; + if (std::strcmp(env, "white") == 0) + return "\033[37m"; + if (std::strcmp(env, "gray") == 0 || std::strcmp(env, "grey") == 0) + return "\033[90m"; // Bright black (gray) + if (std::strcmp(env, "orange") == 0) + return "\033[93m"; // Bright yellow (appears orange-ish) + if (std::strcmp(env, "none") == 0) + return ""; + + // Default to cyan if unknown color name + return "\033[36m"; + }(); + return escape; +} + +// Check if location info should be shown - cached at startup +bool +should_show_location() +{ + static const bool show = []() { + const char* env = std::getenv("LOG_DISABLE"); + // Show location by default, hide if LOG_DISABLE=1 + return !env || std::strcmp(env, "1") != 0; + }(); + return show; +} + +// Helper to write location string (no leading/trailing space) +void +log_write_location_string(std::ostream& os, const char* file, int line) +{ + if (detail::should_log_use_colors()) + { + os << detail::get_log_highlight_color() << "[" + << detail::strip_source_root(file) << ":" << line << "]\033[0m"; + } + else + { + os << "[" << detail::strip_source_root(file) << ":" << line << "]"; + } +} + +} // namespace detail +} // namespace beast diff --git a/src/ripple/beast/utility/src/beast_Journal.cpp b/src/ripple/beast/utility/src/beast_Journal.cpp index 7c332bf6b..037da0b76 100644 --- a/src/ripple/beast/utility/src/beast_Journal.cpp +++ b/src/ripple/beast/utility/src/beast_Journal.cpp @@ -19,6 +19,11 @@ #include #include +#ifdef BEAST_ENHANCED_LOGGING +#include +#include +#include +#endif namespace beast { @@ -131,9 +136,36 @@ Journal::ScopedStream::ScopedStream( m_ostream << manip; } +#ifdef BEAST_ENHANCED_LOGGING +Journal::ScopedStream::ScopedStream( + Sink& sink, + Severity level, + const char* file, + int line) + : m_sink(sink), m_level(level), file_(file), line_(line) +{ + // Modifiers applied from all ctors + m_ostream << std::boolalpha << std::showbase; +} +#endif + Journal::ScopedStream::~ScopedStream() { - std::string const& s(m_ostream.str()); + std::string s(m_ostream.str()); + +#ifdef BEAST_ENHANCED_LOGGING + // Add suffix if location is enabled + if (file_ && detail::should_show_location() && !s.empty() && s != "\n") + { + std::ostringstream combined; + combined << s; + if (!s.empty() && s.back() != ' ') + combined << " "; + detail::log_write_location_string(combined, file_, line_); + s = combined.str(); + } +#endif + if (!s.empty()) { if (s == "\n") @@ -157,4 +189,18 @@ Journal::Stream::operator<<(std::ostream& manip(std::ostream&)) const return ScopedStream(*this, manip); } +#ifdef BEAST_ENHANCED_LOGGING + +// Implementation moved to use new constructor +Journal::ScopedStream +Journal::StreamWithLocation::operator<<( + std::ostream& manip(std::ostream&)) const +{ + // Create a ScopedStream with location info + ScopedStream scoped(stream_.sink(), stream_.level(), file_, line_); + scoped.ostream() << manip; + return scoped; +} +#endif + } // namespace beast diff --git a/src/test/ledger/Invariants_test.cpp b/src/test/ledger/Invariants_test.cpp index 2e5c2f402..8843d402b 100644 --- a/src/test/ledger/Invariants_test.cpp +++ b/src/test/ledger/Invariants_test.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -90,10 +91,26 @@ class Invariants_test : public beast::unit_test::suite { terActual = ac.checkInvariants(terActual, fee); BEAST_EXPECT(terExpect == terActual); - BEAST_EXPECT( - sink.messages().str().starts_with("Invariant failed:") || - sink.messages().str().starts_with( - "Transaction caused an exception")); + // Handle both with and without BEAST_ENHANCED_LOGGING + auto const msg = sink.messages().str(); + bool hasExpectedPrefix = false; + +#ifdef BEAST_ENHANCED_LOGGING + // When BEAST_ENHANCED_LOGGING is enabled, messages may include ANSI + // color codes and start with [file:line]. Just search for the + // message content. + hasExpectedPrefix = + msg.find("Invariant failed:") != std::string::npos || + msg.find("Transaction caused an exception") != + std::string::npos; +#else + // Without BEAST_ENHANCED_LOGGING, messages start directly with the + // text + hasExpectedPrefix = msg.starts_with("Invariant failed:") || + msg.starts_with("Transaction caused an exception"); +#endif + + BEAST_EXPECT(hasExpectedPrefix); for (auto const& m : expect_logs) { if (sink.messages().str().find(m) == std::string::npos) From 15c7ad6f787677c6af7fbb93ce7db9aeabc61731 Mon Sep 17 00:00:00 2001 From: tequ Date: Tue, 14 Oct 2025 14:35:48 +0900 Subject: [PATCH 09/20] Fix Invalid Tx flags (#514) --- src/ripple/app/tx/impl/SetHook.cpp | 8 ++++++ src/ripple/app/tx/impl/SetSignerList.cpp | 8 ++++++ src/ripple/protocol/Feature.h | 3 ++- src/ripple/protocol/impl/Feature.cpp | 1 + src/test/app/Import_test.cpp | 4 +-- src/test/app/MultiSign_test.cpp | 33 ++++++++++++++++++++++++ src/test/app/SetHook_test.cpp | 30 +++++++++++++++++++++ src/test/rpc/AccountTx_test.cpp | 10 +++---- 8 files changed, 88 insertions(+), 9 deletions(-) diff --git a/src/ripple/app/tx/impl/SetHook.cpp b/src/ripple/app/tx/impl/SetHook.cpp index d19a3337a..79e68e01d 100644 --- a/src/ripple/app/tx/impl/SetHook.cpp +++ b/src/ripple/app/tx/impl/SetHook.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -665,6 +666,13 @@ SetHook::preflight(PreflightContext const& ctx) if (!isTesSuccess(ret)) return ret; + if (ctx.rules.enabled(fixInvalidTxFlags) && + ctx.tx.getFlags() & tfUniversalMask) + { + JLOG(ctx.j.trace()) << "SetHook: Invalid flags set."; + return temINVALID_FLAG; + } + if (!ctx.tx.isFieldPresent(sfHooks)) { JLOG(ctx.j.trace()) diff --git a/src/ripple/app/tx/impl/SetSignerList.cpp b/src/ripple/app/tx/impl/SetSignerList.cpp index 87f69c51d..71ed014c7 100644 --- a/src/ripple/app/tx/impl/SetSignerList.cpp +++ b/src/ripple/app/tx/impl/SetSignerList.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -81,6 +82,13 @@ SetSignerList::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; + if (ctx.rules.enabled(fixInvalidTxFlags) && + (ctx.tx.getFlags() & tfUniversalMask)) + { + JLOG(ctx.j.trace()) << "SetSignerList: invalid flags."; + return temINVALID_FLAG; + } + auto const result = determineOperation(ctx.tx, ctx.flags, ctx.j); if (!isTesSuccess(std::get<0>(result))) diff --git a/src/ripple/protocol/Feature.h b/src/ripple/protocol/Feature.h index 6982b866c..b52a78265 100644 --- a/src/ripple/protocol/Feature.h +++ b/src/ripple/protocol/Feature.h @@ -74,7 +74,7 @@ namespace detail { // Feature.cpp. Because it's only used to reserve storage, and determine how // large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than // the actual number of amendments. A LogicError on startup will verify this. -static constexpr std::size_t numFeatures = 85; +static constexpr std::size_t numFeatures = 86; /** Amendments that this server supports and the default voting behavior. Whether they are enabled depends on the Rules defined in the validated @@ -373,6 +373,7 @@ extern uint256 const fixProvisionalDoubleThreading; extern uint256 const featureClawback; extern uint256 const featureDeepFreeze; extern uint256 const featureIOUIssuerWeakTSH; +extern uint256 const fixInvalidTxFlags; } // namespace ripple diff --git a/src/ripple/protocol/impl/Feature.cpp b/src/ripple/protocol/impl/Feature.cpp index 9775b7d81..789423efa 100644 --- a/src/ripple/protocol/impl/Feature.cpp +++ b/src/ripple/protocol/impl/Feature.cpp @@ -479,6 +479,7 @@ REGISTER_FEATURE(Clawback, Supported::yes, VoteBehavior::De REGISTER_FIX (fixProvisionalDoubleThreading, Supported::yes, VoteBehavior::DefaultYes); REGISTER_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo); REGISTER_FEATURE(IOUIssuerWeakTSH, Supported::yes, VoteBehavior::DefaultNo); +REGISTER_FIX (fixInvalidTxFlags, Supported::yes, VoteBehavior::DefaultYes); // The following amendments are obsolete, but must remain supported // because they could potentially get enabled. diff --git a/src/test/app/Import_test.cpp b/src/test/app/Import_test.cpp index 2bb56fbe9..057952836 100644 --- a/src/test/app/Import_test.cpp +++ b/src/test/app/Import_test.cpp @@ -5203,8 +5203,8 @@ class Import_test : public beast::unit_test::suite std::string ns_str = "CAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECA" "FE"; - Json::Value jv = ripple::test::jtx::hook( - issuer, {{hso(createCodeHex)}}, hsfOVERRIDE | hsfCOLLECT); + Json::Value jv = + ripple::test::jtx::hook(issuer, {{hso(createCodeHex)}}, 0); jv[jss::Hooks][0U][jss::Hook][jss::HookNamespace] = ns_str; jv[jss::Hooks][0U][jss::Hook][jss::HookOn] = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFFFFFFFFFFFFFFFFFFBFFF" diff --git a/src/test/app/MultiSign_test.cpp b/src/test/app/MultiSign_test.cpp index 7668ef6be..1c4e537a3 100644 --- a/src/test/app/MultiSign_test.cpp +++ b/src/test/app/MultiSign_test.cpp @@ -1659,6 +1659,36 @@ public: BEAST_EXPECT(env.seq(alice) == aliceSeq + 1); } + void + test_signerListSetFlags(FeatureBitset features) + { + using namespace test::jtx; + + for (bool const withFixInvalidTxFlags : {false, true}) + { + Env env{ + *this, + withFixInvalidTxFlags ? features + : features - fixInvalidTxFlags}; + Account const alice{"alice"}; + + env.fund(XRP(1000), alice); + env.close(); + + bool const enabled = features[fixInvalidTxFlags]; + testcase( + std::string("SignerListSet flag, fix ") + + (withFixInvalidTxFlags ? "enabled" : "disabled")); + + ter const expected( + withFixInvalidTxFlags ? TER(temINVALID_FLAG) : TER(tesSUCCESS)); + env(signers(alice, 2, {{bogie, 1}, {ghost, 1}}), + expected, + txflags(tfPassive)); + env.close(); + } + } + void testAll(FeatureBitset features) { @@ -1695,6 +1725,9 @@ public: testAll(all - featureMultiSignReserve - featureExpandedSignerList); testAll(all - featureExpandedSignerList); testAll(all); + + test_signerListSetFlags(all); + test_amendmentTransition(); } }; diff --git a/src/test/app/SetHook_test.cpp b/src/test/app/SetHook_test.cpp index 3fb16176b..75caade5e 100644 --- a/src/test/app/SetHook_test.cpp +++ b/src/test/app/SetHook_test.cpp @@ -364,6 +364,35 @@ public: } } + void + testInvalidTxFlags(FeatureBitset features) + { + testcase("Checks invalid tx flags"); + using namespace jtx; + + for (bool const withFixInvalidTxFlags : {false, true}) + { + Env env{ + *this, + withFixInvalidTxFlags ? features + : features - fixInvalidTxFlags}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10000), alice); + env.close(); + + Json::Value jv = + ripple::test::jtx::hook(alice, {{{hso_delete()}}}, 0); + jv[jss::Flags] = tfUniversalMask; + + env(jv, + M("Invalid SetHook flags"), + HSFEE, + withFixInvalidTxFlags ? ter(temINVALID_FLAG) : ter(tesSUCCESS)); + env.close(); + } + } + void testGrants(FeatureBitset features) { @@ -12737,6 +12766,7 @@ public: testHooksOwnerDir(features); testHooksDisabled(features); testTxStructure(features); + testInvalidTxFlags(features); testInferHookSetOperation(); testParams(features); testGrants(features); diff --git a/src/test/rpc/AccountTx_test.cpp b/src/test/rpc/AccountTx_test.cpp index 85dd2978d..aaed4defe 100644 --- a/src/test/rpc/AccountTx_test.cpp +++ b/src/test/rpc/AccountTx_test.cpp @@ -523,8 +523,7 @@ class AccountTx_test : public beast::unit_test::suite "0B"; Json::Value jhv = hso(updateHookHex); jhv[jss::Flags] = hsfOVERRIDE; - Json::Value jv = - ripple::test::jtx::hook(account, {{jhv}}, hsfOVERRIDE); + Json::Value jv = ripple::test::jtx::hook(account, {{jhv}}, 0); return jv; }; env(updateHook(alice), HSFEE, sig(alie)); @@ -553,8 +552,7 @@ class AccountTx_test : public beast::unit_test::suite "000000"; jhv[jss::HookNamespace] = to_string(uint256{beast::zero}); jhv[jss::HookHash] = to_string(hookHash); - Json::Value jv = - ripple::test::jtx::hook(account, {{jhv}}, hsfOVERRIDE); + Json::Value jv = ripple::test::jtx::hook(account, {{jhv}}, 0); return jv; }; uint256 const hid = hh(env, alice); @@ -563,8 +561,8 @@ class AccountTx_test : public beast::unit_test::suite // Delete Hook auto deleteHook = [](test::jtx::Account const& account) { - Json::Value jv = ripple::test::jtx::hook( - account, {{hso_delete()}}, hsfOVERRIDE); + Json::Value jv = + ripple::test::jtx::hook(account, {{hso_delete()}}, 0); return jv; }; env(deleteHook(alice), HSFEE, sig(alie)); From 83f09fd8abf52edabec37c2f8db7ac4eeeebcfb5 Mon Sep 17 00:00:00 2001 From: Niq Dudfield Date: Wed, 15 Oct 2025 08:26:31 +0700 Subject: [PATCH 10/20] ci: add clang to build matrix [ci-nix-full-matrix] (#569) --- .github/actions/xahau-ga-build/action.yml | 46 +++- .../actions/xahau-ga-dependencies/action.yml | 10 +- .github/workflows/xahau-ga-nix.yml | 223 +++++++++++++++++- 3 files changed, 264 insertions(+), 15 deletions(-) diff --git a/.github/actions/xahau-ga-build/action.yml b/.github/actions/xahau-ga-build/action.yml index 66fe45d62..41731620d 100644 --- a/.github/actions/xahau-ga-build/action.yml +++ b/.github/actions/xahau-ga-build/action.yml @@ -21,7 +21,7 @@ inputs: required: false default: '' compiler-id: - description: 'Unique identifier for compiler/version combination used for cache keys' + description: 'Unique identifier: compiler-version-stdlib[-gccversion] (e.g. clang-14-libstdcxx-gcc11, gcc-13-libstdcxx)' required: false default: '' cache_version: @@ -36,6 +36,17 @@ inputs: description: 'Main branch name for restore keys' required: false default: 'dev' + stdlib: + description: 'C++ standard library to use' + required: true + type: choice + options: + - libstdcxx + - libcxx + clang_gcc_toolchain: + description: 'GCC version to use for Clang toolchain (e.g. 11, 13)' + required: false + default: '' runs: using: 'composite' @@ -93,6 +104,38 @@ runs: CCACHE_ARGS="-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache" fi + # Configure C++ standard library if specified + # libstdcxx used for clang-14/16 to work around missing lexicographical_compare_three_way in libc++ + # libcxx can be used with clang-17+ which has full C++20 support + # Note: -stdlib flag is Clang-specific, GCC always uses libstdc++ + CMAKE_CXX_FLAGS="" + if [[ "${{ inputs.cxx }}" == clang* ]]; then + # Only Clang needs the -stdlib flag + if [ "${{ inputs.stdlib }}" = "libstdcxx" ]; then + CMAKE_CXX_FLAGS="-stdlib=libstdc++" + elif [ "${{ inputs.stdlib }}" = "libcxx" ]; then + CMAKE_CXX_FLAGS="-stdlib=libc++" + fi + fi + # GCC always uses libstdc++ and doesn't need/support the -stdlib flag + + # Configure GCC toolchain for Clang if specified + if [ -n "${{ inputs.clang_gcc_toolchain }}" ] && [[ "${{ inputs.cxx }}" == clang* ]]; then + # Extract Clang version from compiler executable name (e.g., clang++-14 -> 14) + clang_version=$(echo "${{ inputs.cxx }}" | grep -oE '[0-9]+$') + + # Clang 16+ supports --gcc-install-dir (precise path specification) + # Clang <16 only has --gcc-toolchain (uses discovery heuristics) + if [ -n "$clang_version" ] && [ "$clang_version" -ge "16" ]; then + # Clang 16+ uses --gcc-install-dir (canonical, precise) + CMAKE_CXX_FLAGS="$CMAKE_CXX_FLAGS --gcc-install-dir=/usr/lib/gcc/x86_64-linux-gnu/${{ inputs.clang_gcc_toolchain }}" + else + # Clang 14-15 uses --gcc-toolchain (deprecated but necessary) + # Note: This still uses discovery, so we hide newer GCC versions in the workflow + CMAKE_CXX_FLAGS="$CMAKE_CXX_FLAGS --gcc-toolchain=/usr" + fi + fi + # Run CMake configure # Note: conanfile.py hardcodes 'build/generators' as the output path. # If we're in a 'build' folder, Conan detects this and uses just 'generators/' @@ -101,6 +144,7 @@ runs: cmake .. \ -G "${{ inputs.generator }}" \ $CCACHE_ARGS \ + ${CMAKE_CXX_FLAGS:+-DCMAKE_CXX_FLAGS="$CMAKE_CXX_FLAGS"} \ -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \ -DCMAKE_BUILD_TYPE=${{ inputs.configuration }} diff --git a/.github/actions/xahau-ga-dependencies/action.yml b/.github/actions/xahau-ga-dependencies/action.yml index afa1067b6..38307c4be 100644 --- a/.github/actions/xahau-ga-dependencies/action.yml +++ b/.github/actions/xahau-ga-dependencies/action.yml @@ -10,7 +10,7 @@ inputs: required: false default: '.build' compiler-id: - description: 'Unique identifier for compiler/version combination used for cache keys' + description: 'Unique identifier: compiler-version-stdlib[-gccversion] (e.g. clang-14-libstdcxx-gcc11, gcc-13-libstdcxx)' required: false default: '' cache_version: @@ -25,6 +25,13 @@ inputs: description: 'Main branch name for restore keys' required: false default: 'dev' + stdlib: + description: 'C++ standard library for Conan configuration (note: also in compiler-id)' + required: true + type: choice + options: + - libstdcxx + - libcxx outputs: cache-hit: @@ -70,6 +77,7 @@ runs: path: | ~/.conan ~/.conan2 + # Note: compiler-id format is compiler-version-stdlib[-gccversion] key: ${{ runner.os }}-conan-v${{ inputs.cache_version }}-${{ inputs.compiler-id }}-${{ hashFiles('**/conanfile.txt', '**/conanfile.py') }}-${{ inputs.configuration }} restore-keys: | ${{ runner.os }}-conan-v${{ inputs.cache_version }}-${{ inputs.compiler-id }}-${{ hashFiles('**/conanfile.txt', '**/conanfile.py') }}- diff --git a/.github/workflows/xahau-ga-nix.yml b/.github/workflows/xahau-ga-nix.yml index b602e71c8..8e5c4c4c6 100644 --- a/.github/workflows/xahau-ga-nix.yml +++ b/.github/workflows/xahau-ga-nix.yml @@ -13,21 +13,146 @@ concurrency: cancel-in-progress: true jobs: - build-job: + matrix-setup: + runs-on: ubuntu-latest + container: python:3-slim + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Generate build matrix + id: set-matrix + shell: python + run: | + import json + import os + + # Full matrix with all 6 compiler configurations + # Each configuration includes all parameters needed by the build job + full_matrix = [ + { + "compiler_id": "gcc-11-libstdcxx", + "compiler": "gcc", + "cc": "gcc-11", + "cxx": "g++-11", + "compiler_version": 11, + "stdlib": "libstdcxx", + "configuration": "Debug" + }, + { + "compiler_id": "gcc-13-libstdcxx", + "compiler": "gcc", + "cc": "gcc-13", + "cxx": "g++-13", + "compiler_version": 13, + "stdlib": "libstdcxx", + "configuration": "Debug" + }, + { + "compiler_id": "clang-14-libstdcxx-gcc11", + "compiler": "clang", + "cc": "clang-14", + "cxx": "clang++-14", + "compiler_version": 14, + "stdlib": "libstdcxx", + "clang_gcc_toolchain": 11, + "configuration": "Debug" + }, + { + "compiler_id": "clang-16-libstdcxx-gcc13", + "compiler": "clang", + "cc": "clang-16", + "cxx": "clang++-16", + "compiler_version": 16, + "stdlib": "libstdcxx", + "clang_gcc_toolchain": 13, + "configuration": "Debug" + }, + { + "compiler_id": "clang-17-libcxx", + "compiler": "clang", + "cc": "clang-17", + "cxx": "clang++-17", + "compiler_version": 17, + "stdlib": "libcxx", + "configuration": "Debug" + }, + { + # Clang 18 - testing if it's faster than Clang 17 with libc++ + # Requires patching Conan v1 settings.yml to add version 18 + "compiler_id": "clang-18-libcxx", + "compiler": "clang", + "cc": "clang-18", + "cxx": "clang++-18", + "compiler_version": 18, + "stdlib": "libcxx", + "configuration": "Debug" + } + ] + + # Minimal matrix for PRs and feature branches + minimal_matrix = [ + full_matrix[1], # gcc-13 (middle-ground gcc) + full_matrix[2] # clang-14 (mature, stable clang) + ] + + # Determine which matrix to use based on the target branch + ref = "${{ github.ref }}" + base_ref = "${{ github.base_ref }}" # For PRs, this is the target branch + event_name = "${{ github.event_name }}" + commit_message = """${{ github.event.head_commit.message }}""" + pr_title = """${{ github.event.pull_request.title }}""" + + # Debug logging + print(f"Event: {event_name}") + print(f"Ref: {ref}") + print(f"Base ref: {base_ref}") + print(f"PR title: {pr_title}") + print(f"Commit message: {commit_message}") + + # Check for override tags in commit message or PR title + force_full = "[ci-nix-full-matrix]" in commit_message or "[ci-nix-full-matrix]" in pr_title + print(f"Force full matrix: {force_full}") + + # Check if this is targeting a main branch + # For PRs: check base_ref (target branch) + # For pushes: check ref (current branch) + main_branches = ["refs/heads/dev", "refs/heads/release", "refs/heads/candidate"] + + if force_full: + # Override: always use full matrix if tag is present + use_full = True + elif event_name == "pull_request": + # For PRs, base_ref is just the branch name (e.g., "dev", not "refs/heads/dev") + # Check if the PR targets release or candidate (more critical branches) + use_full = base_ref in ["release", "candidate"] + else: + # For pushes, ref is the full reference (e.g., "refs/heads/dev") + use_full = ref in main_branches + + # Select the appropriate matrix + if use_full: + if force_full: + print(f"Using FULL matrix (6 configs) - forced by [ci-nix-full-matrix] tag") + else: + print(f"Using FULL matrix (6 configs) - targeting main branch") + matrix = full_matrix + else: + print(f"Using MINIMAL matrix (2 configs) - feature branch/PR") + matrix = minimal_matrix + + # Output the matrix as JSON + output = json.dumps({"include": matrix}) + with open(os.environ['GITHUB_OUTPUT'], 'a') as f: + f.write(f"matrix={output}\n") + + build: + needs: matrix-setup runs-on: ubuntu-latest outputs: artifact_name: ${{ steps.set-artifact-name.outputs.artifact_name }} strategy: fail-fast: false - matrix: - compiler: [gcc] - configuration: [Debug] - include: - - compiler: gcc - cc: gcc-13 - cxx: g++-13 - compiler_id: gcc-13 - compiler_version: 13 + matrix: ${{ fromJSON(needs.matrix-setup.outputs.matrix) }} env: build_dir: .build # Bump this number to invalidate all caches globally. @@ -41,8 +166,70 @@ jobs: run: | sudo apt-get update sudo apt-get install -y ninja-build ${{ matrix.cc }} ${{ matrix.cxx }} ccache + + # Install the specific GCC version needed for Clang + if [ -n "${{ matrix.clang_gcc_toolchain }}" ]; then + echo "=== Installing GCC ${{ matrix.clang_gcc_toolchain }} for Clang ===" + sudo apt-get install -y gcc-${{ matrix.clang_gcc_toolchain }} g++-${{ matrix.clang_gcc_toolchain }} libstdc++-${{ matrix.clang_gcc_toolchain }}-dev + + echo "=== GCC versions available after installation ===" + ls -la /usr/lib/gcc/x86_64-linux-gnu/ | grep -E "^d" + fi + + # For Clang < 16 with --gcc-toolchain, hide newer GCC versions + # This is needed because --gcc-toolchain still picks the highest version + # + # THE GREAT GCC HIDING TRICK (for Clang < 16): + # Clang versions before 16 don't have --gcc-install-dir, only --gcc-toolchain + # which is deprecated and still uses discovery heuristics that ALWAYS pick + # the highest version number. So we play a sneaky game... + # + # We rename newer GCC versions to very low integers (1, 2, 3...) which makes + # Clang think they're ancient GCC versions. Since 11 > 3 > 2 > 1, Clang will + # pick GCC 11 over our renamed versions. It's dumb but it works! + # + # Example: GCC 12→1, GCC 13→2, GCC 14→3, so Clang picks 11 (highest number) + if [ -n "${{ matrix.clang_gcc_toolchain }}" ] && [ "${{ matrix.compiler_version }}" -lt "16" ]; then + echo "=== Hiding GCC versions newer than ${{ matrix.clang_gcc_toolchain }} for Clang < 16 ===" + target_version=${{ matrix.clang_gcc_toolchain }} + counter=1 # Start with 1 - these will be seen as "GCC version 1, 2, 3" etc + for dir in /usr/lib/gcc/x86_64-linux-gnu/*/; do + if [ -d "$dir" ]; then + version=$(basename "$dir") + # Check if version is numeric and greater than target + if [[ "$version" =~ ^[0-9]+$ ]] && [ "$version" -gt "$target_version" ]; then + echo "Hiding GCC $version -> renaming to $counter (will be seen as GCC version $counter)" + # Safety check: ensure target doesn't already exist + if [ ! -e "/usr/lib/gcc/x86_64-linux-gnu/$counter" ]; then + sudo mv "$dir" "/usr/lib/gcc/x86_64-linux-gnu/$counter" + else + echo "ERROR: Cannot rename GCC $version - /usr/lib/gcc/x86_64-linux-gnu/$counter already exists" + exit 1 + fi + counter=$((counter + 1)) + fi + fi + done + fi + + # Verify what Clang will use + if [ -n "${{ matrix.clang_gcc_toolchain }}" ]; then + echo "=== Verifying GCC toolchain selection ===" + echo "Available GCC versions:" + ls -la /usr/lib/gcc/x86_64-linux-gnu/ | grep -E "^d.*[0-9]+$" || true + + echo "" + echo "Clang's detected GCC installation:" + ${{ matrix.cxx }} -v -E -x c++ /dev/null -o /dev/null 2>&1 | grep "Found candidate GCC installation" || true + fi + + # Install libc++ dev packages if using libc++ (not needed for libstdc++) + if [ "${{ matrix.stdlib }}" = "libcxx" ]; then + sudo apt-get install -y libc++-${{ matrix.compiler_version }}-dev libc++abi-${{ matrix.compiler_version }}-dev + fi + # Install Conan 2 - pip install --upgrade "conan>=2.0" + pip install --upgrade "conan>=2.0,<3" - name: Configure ccache uses: ./.github/actions/xahau-configure-ccache @@ -57,14 +244,21 @@ jobs: # Create the default profile directory if it doesn't exist mkdir -p ~/.conan2/profiles + # Determine the correct libcxx based on stdlib parameter + if [ "${{ matrix.stdlib }}" = "libcxx" ]; then + LIBCXX="libc++" + else + LIBCXX="libstdc++11" + fi + # Create profile with our specific settings cat > ~/.conan2/profiles/default < Date: Wed, 15 Oct 2025 11:32:31 +0900 Subject: [PATCH 11/20] Remove unused variable `enabled` in MultiSign_test.cpp (#592) --- src/test/app/MultiSign_test.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/app/MultiSign_test.cpp b/src/test/app/MultiSign_test.cpp index 1c4e537a3..7dfbfdd78 100644 --- a/src/test/app/MultiSign_test.cpp +++ b/src/test/app/MultiSign_test.cpp @@ -1675,7 +1675,6 @@ public: env.fund(XRP(1000), alice); env.close(); - bool const enabled = features[fixInvalidTxFlags]; testcase( std::string("SignerListSet flag, fix ") + (withFixInvalidTxFlags ? "enabled" : "disabled")); From 9c8b005406f4a117b3f9a19d9f6c022108b1e988 Mon Sep 17 00:00:00 2001 From: tequ Date: Wed, 15 Oct 2025 11:33:32 +0900 Subject: [PATCH 12/20] fix: improve logging for transaction preflight failures in applyHook.cpp (#566) --- src/ripple/app/hook/impl/applyHook.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ripple/app/hook/impl/applyHook.cpp b/src/ripple/app/hook/impl/applyHook.cpp index 5b5f28cf2..53aca6ed3 100644 --- a/src/ripple/app/hook/impl/applyHook.cpp +++ b/src/ripple/app/hook/impl/applyHook.cpp @@ -3577,7 +3577,7 @@ DEFINE_HOOK_FUNCTION( { JLOG(j.trace()) << "HookEmit[" << HC_ACC() << "]: Transaction preflight failure: " - << preflightResult.ter; + << transHuman(preflightResult.ter); return EMISSION_FAILURE; } From 1ec31e79c9b77cb5ba6d01613b352182054ccf75 Mon Sep 17 00:00:00 2001 From: RichardAH Date: Fri, 17 Oct 2025 18:45:16 +1000 Subject: [PATCH 13/20] Cron (on ledger cronjobs) (#590) Co-authored-by: tequ --- Builds/CMake/RippledCore.cmake | 4 + src/ripple/app/hook/impl/applyHook.cpp | 5 + src/ripple/app/misc/impl/TxQ.cpp | 58 +++ src/ripple/app/tx/impl/Cron.cpp | 188 +++++++ src/ripple/app/tx/impl/Cron.h | 60 +++ src/ripple/app/tx/impl/InvariantCheck.cpp | 1 + src/ripple/app/tx/impl/SetCron.cpp | 255 ++++++++++ src/ripple/app/tx/impl/SetCron.h | 56 +++ src/ripple/app/tx/impl/applySteps.cpp | 22 + src/ripple/protocol/Feature.h | 3 +- src/ripple/protocol/Indexes.h | 3 + src/ripple/protocol/LedgerFormats.h | 6 + src/ripple/protocol/SField.h | 3 + src/ripple/protocol/TxFlags.h | 6 + src/ripple/protocol/TxFormats.h | 6 + src/ripple/protocol/impl/Feature.cpp | 1 + src/ripple/protocol/impl/Indexes.cpp | 46 ++ src/ripple/protocol/impl/LedgerFormats.cpp | 11 + src/ripple/protocol/impl/SField.cpp | 3 + src/ripple/protocol/impl/STTx.cpp | 2 +- src/ripple/protocol/impl/TxFormats.cpp | 16 + src/ripple/protocol/jss.h | 18 +- src/ripple/rpc/handlers/ServerDefinitions.cpp | 1 + src/test/app/Cron_test.cpp | 461 ++++++++++++++++++ src/test/app/SetHookTSH_test.cpp | 116 ++++- src/test/jtx.h | 1 + src/test/jtx/cron.h | 74 +++ src/test/jtx/impl/cron.cpp | 56 +++ 28 files changed, 1470 insertions(+), 12 deletions(-) create mode 100644 src/ripple/app/tx/impl/Cron.cpp create mode 100644 src/ripple/app/tx/impl/Cron.h create mode 100644 src/ripple/app/tx/impl/SetCron.cpp create mode 100644 src/ripple/app/tx/impl/SetCron.h create mode 100644 src/test/app/Cron_test.cpp create mode 100644 src/test/jtx/cron.h create mode 100644 src/test/jtx/impl/cron.cpp diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 58cd234e4..b0423b02b 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -457,6 +457,7 @@ target_sources (rippled PRIVATE src/ripple/app/tx/impl/CreateCheck.cpp src/ripple/app/tx/impl/CreateOffer.cpp src/ripple/app/tx/impl/CreateTicket.cpp + src/ripple/app/tx/impl/Cron.cpp src/ripple/app/tx/impl/DeleteAccount.cpp src/ripple/app/tx/impl/DepositPreauth.cpp src/ripple/app/tx/impl/Escrow.cpp @@ -474,6 +475,7 @@ target_sources (rippled PRIVATE src/ripple/app/tx/impl/Payment.cpp src/ripple/app/tx/impl/Remit.cpp src/ripple/app/tx/impl/SetAccount.cpp + src/ripple/app/tx/impl/SetCron.cpp src/ripple/app/tx/impl/SetHook.cpp src/ripple/app/tx/impl/SetRemarks.cpp src/ripple/app/tx/impl/SetRegularKey.cpp @@ -734,6 +736,7 @@ if (tests) src/test/app/BaseFee_test.cpp src/test/app/Check_test.cpp src/test/app/ClaimReward_test.cpp + src/test/app/Cron_test.cpp src/test/app/Clawback_test.cpp src/test/app/CrossingLimits_test.cpp src/test/app/DeliverMin_test.cpp @@ -898,6 +901,7 @@ if (tests) src/test/jtx/impl/amount.cpp src/test/jtx/impl/balance.cpp src/test/jtx/impl/check.cpp + src/test/jtx/impl/cron.cpp src/test/jtx/impl/delivermin.cpp src/test/jtx/impl/deposit.cpp src/test/jtx/impl/envconfig.cpp diff --git a/src/ripple/app/hook/impl/applyHook.cpp b/src/ripple/app/hook/impl/applyHook.cpp index 53aca6ed3..fa9c5c614 100644 --- a/src/ripple/app/hook/impl/applyHook.cpp +++ b/src/ripple/app/hook/impl/applyHook.cpp @@ -75,6 +75,11 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv) switch (tt) { + case ttCRON: { + ADD_TSH(tx.getAccountID(sfOwner), tshWEAK); + break; + } + case ttREMIT: { if (destAcc) ADD_TSH(*destAcc, tshSTRONG); diff --git a/src/ripple/app/misc/impl/TxQ.cpp b/src/ripple/app/misc/impl/TxQ.cpp index 72898ee7e..dbd690a45 100644 --- a/src/ripple/app/misc/impl/TxQ.cpp +++ b/src/ripple/app/misc/impl/TxQ.cpp @@ -1477,6 +1477,64 @@ TxQ::accept(Application& app, OpenView& view) } } + // Inject cron transactions, if any + if (view.rules().enabled(featureCron)) + { + uint32_t currentTime = + view.parentCloseTime().time_since_epoch().count(); + uint256 klStart = keylet::cron(0, AccountID(beast::zero)).key; + uint256 const klEnd = + keylet::cron(currentTime + 1, AccountID(beast::zero)).key; + + std::set cronAccs; + + auto counter = 0; + // include max 128 cron txns in the ledger + while (++counter < 129 && klStart < klEnd) + { + std::optional next = view.succ(klStart, klEnd); + if (!next.has_value()) + break; + + Keylet kl{ltANY, *next}; + + if (view.exists(kl)) + { + auto sle = view.read(kl); + if (safe_cast( + sle->getFieldU16(sfLedgerEntryType)) == ltCRON) + { + // valid cron object, add it to the list + cronAccs.emplace(sle->getAccountID(sfOwner)); + } + } + + klStart = *next; + } + + auto const seq = view.info().seq; + + // insert Cron pseudos for each of the accs we need to ping + for (AccountID const& id : cronAccs) + { + STTx cronTx(ttCRON, [=](auto& obj) { + obj[sfAccount] = AccountID(); + obj[sfLedgerSequence] = seq; + obj[sfOwner] = id; + }); + + uint256 txID = cronTx.getTransactionID(); + + auto s = std::make_shared(); + cronTx.add(*s); + + app.getHashRouter().setFlags(txID, SF_PRIVATE2); + app.getHashRouter().setFlags(txID, SF_EMITTED); + view.rawTxInsert(txID, std::move(s), nullptr); + ledgerChanged = true; + } + } + // Inject emitted transactions if any if (view.rules().enabled(featureHooks)) do diff --git a/src/ripple/app/tx/impl/Cron.cpp b/src/ripple/app/tx/impl/Cron.cpp new file mode 100644 index 000000000..befcdd425 --- /dev/null +++ b/src/ripple/app/tx/impl/Cron.cpp @@ -0,0 +1,188 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { + +TxConsequences +Cron::makeTxConsequences(PreflightContext const& ctx) +{ + return TxConsequences{ctx.tx, TxConsequences::normal}; +} + +NotTEC +Cron::preflight(PreflightContext const& ctx) +{ + if (!ctx.rules.enabled(featureCron)) + return temDISABLED; + + auto const ret = preflight0(ctx); + if (!isTesSuccess(ret)) + return ret; + + auto account = ctx.tx.getAccountID(sfAccount); + if (account != beast::zero) + { + JLOG(ctx.j.warn()) << "Cron: Bad source id"; + return temBAD_SRC_ACCOUNT; + } + + // No point in going any further if the transaction fee is malformed. + auto const fee = ctx.tx.getFieldAmount(sfFee); + if (!fee.native() || fee != beast::zero) + { + JLOG(ctx.j.warn()) << "Cron: invalid fee"; + return temBAD_FEE; + } + + if (!ctx.tx.getSigningPubKey().empty() || !ctx.tx.getSignature().empty() || + ctx.tx.isFieldPresent(sfSigners)) + { + JLOG(ctx.j.warn()) << "Cron: Bad signature"; + return temBAD_SIGNATURE; + } + + if (ctx.tx.getFieldU32(sfSequence) != 0 || + ctx.tx.isFieldPresent(sfPreviousTxnID)) + { + JLOG(ctx.j.warn()) << "Cron: Bad sequence"; + return temBAD_SEQUENCE; + } + + return tesSUCCESS; +} + +TER +Cron::preclaim(PreclaimContext const& ctx) +{ + if (!ctx.view.rules().enabled(featureCron)) + return temDISABLED; + + return tesSUCCESS; +} + +TER +Cron::doApply() +{ + auto& view = ctx_.view(); + auto const& tx = ctx_.tx; + + AccountID const& id = tx.getAccountID(sfOwner); + + auto sle = view.peek(keylet::account(id)); + if (!sle) + { + // should never happen... but might in a race condition with acc delete + JLOG(j_.warn()) << "Cron: sfOwner account missing. " << id; + return tesSUCCESS; + } + + if (!sle->isFieldPresent(sfCron)) + { + JLOG(j_.warn()) << "Cron: sfCron missing from account " << id; + return tefINTERNAL; + } + + uint256 ptr = sle->getFieldH256(sfCron); + Keylet klOld{ltCRON, ptr}; + auto sleCron = view.peek(klOld); + if (!sleCron) + { + JLOG(j_.warn()) << "Cron: Cron object missing for account " << id; + return tesSUCCESS; + } + + uint32_t delay = sleCron->getFieldU32(sfDelaySeconds); + uint32_t recur = sleCron->getFieldU32(sfRepeatCount); + + uint32_t currentTime = view.parentCloseTime().time_since_epoch().count(); + + // do all this sanity checking before we modify the ledger... + uint32_t afterTime = currentTime + delay; + if (afterTime < currentTime) + return tefINTERNAL; + + // in all circumstances the Cron object is deleted... + // if there are further crons to do then a new one is created at the next + // time point + + if (!view.dirRemove( + keylet::ownerDir(id), (*sleCron)[sfOwnerNode], klOld, false)) + return tefBAD_LEDGER; + + view.erase(sleCron); + + if (recur == 0) + { + // already at last execution, stop here + adjustOwnerCount(view, sle, -1, j_); + sle->makeFieldAbsent(sfCron); + view.update(sle); + return tesSUCCESS; + } + + // more executions remain, so create a new object + + Keylet klCron = keylet::cron(afterTime, id); + + // insert into owner dir, we don't need to check reserve because we've just + // deleted an object + auto const page = + view.dirInsert(keylet::ownerDir(id), klCron, describeOwnerDir(id)); + if (!page) + return tecDIR_FULL; + + sleCron = std::make_shared(klCron); + + sleCron->setFieldU64(sfOwnerNode, *page); + sleCron->setFieldU32(sfDelaySeconds, delay); + sleCron->setFieldU32(sfRepeatCount, recur - 1); + sleCron->setAccountID(sfOwner, id); + + sle->setFieldH256(sfCron, klCron.key); + + view.insert(sleCron); + view.update(sle); + + return tesSUCCESS; +} + +void +Cron::preCompute() +{ + assert(account_ == beast::zero); +} + +XRPAmount +Cron::calculateBaseFee(ReadView const& view, STTx const& tx) +{ + return XRPAmount{0}; +} + +} // namespace ripple diff --git a/src/ripple/app/tx/impl/Cron.h b/src/ripple/app/tx/impl/Cron.h new file mode 100644 index 000000000..9d0498c87 --- /dev/null +++ b/src/ripple/app/tx/impl/Cron.h @@ -0,0 +1,60 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 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_TX_CRON_H_INCLUDED +#define RIPPLE_TX_CRON_H_INCLUDED + +#include +#include +#include +#include + +namespace ripple { + +class Cron : public Transactor +{ +public: + static constexpr ConsequencesFactoryType ConsequencesFactory{Custom}; + + explicit Cron(ApplyContext& ctx) : Transactor(ctx) + { + } + + static XRPAmount + calculateBaseFee(ReadView const& view, STTx const& tx); + + static TxConsequences + makeTxConsequences(PreflightContext const& ctx); + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const&); + + TER + doApply() override; + + void + preCompute() override; +}; + +} // namespace ripple + +#endif diff --git a/src/ripple/app/tx/impl/InvariantCheck.cpp b/src/ripple/app/tx/impl/InvariantCheck.cpp index 231828a82..5679a2b47 100644 --- a/src/ripple/app/tx/impl/InvariantCheck.cpp +++ b/src/ripple/app/tx/impl/InvariantCheck.cpp @@ -491,6 +491,7 @@ LedgerEntryTypesMatch::visitEntry( case ltNFTOKEN_PAGE: case ltNFTOKEN_OFFER: case ltURI_TOKEN: + case ltCRON: case ltIMPORT_VLSEQ: case ltUNL_REPORT: break; diff --git a/src/ripple/app/tx/impl/SetCron.cpp b/src/ripple/app/tx/impl/SetCron.cpp new file mode 100644 index 000000000..3e2e4e6f4 --- /dev/null +++ b/src/ripple/app/tx/impl/SetCron.cpp @@ -0,0 +1,255 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 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 +#include +#include +#include +#include +#include +#include + +namespace ripple { + +TxConsequences +SetCron::makeTxConsequences(PreflightContext const& ctx) +{ + return TxConsequences{ctx.tx, TxConsequences::normal}; +} + +NotTEC +SetCron::preflight(PreflightContext const& ctx) +{ + if (!ctx.rules.enabled(featureCron)) + return temDISABLED; + + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) + return ret; + + auto& tx = ctx.tx; + auto& j = ctx.j; + + if (tx.getFlags() & tfCronSetMask) + { + JLOG(j.warn()) << "SetCron: Invalid flags set."; + return temINVALID_FLAG; + } + + // DelaySeconds (D), RepeatCount (R) + // DR - Set Cron with Delay and Repeat + // D- - Set Cron (once off) with Delay only (repat implicitly 0) + // -R - Invalid + // -- - Clear any existing cron (succeeds even if there isn't one) / with + // tfCronUnset flag set + + bool const hasDelay = tx.isFieldPresent(sfDelaySeconds); + bool const hasRepeat = tx.isFieldPresent(sfRepeatCount); + + if (tx.isFlag(tfCronUnset)) + { + if (hasDelay || hasRepeat) + { + JLOG(j.debug()) << "SetCron: tfCronUnset flag cannot be used with " + "DelaySeconds or RepeatCount."; + return temMALFORMED; + } + } + else + { + if (!hasDelay) + { + JLOG(j.debug()) << "SetCron: DelaySeconds must be " + "specified to create a cron."; + return temMALFORMED; + } + + // check delay is not too high + auto delay = tx.getFieldU32(sfDelaySeconds); + if (delay > 31536000UL /* 365 days in seconds */) + { + JLOG(j.debug()) << "SetCron: DelaySeconds was too high. (max 365 " + "days in seconds)."; + return temMALFORMED; + } + + // check repeat is not too high + if (hasRepeat) + { + auto recur = tx.getFieldU32(sfRepeatCount); + if (recur > 256) + { + JLOG(j.debug()) + << "SetCron: RepeatCount too high. Limit is 256. Issue " + "new SetCron to increase."; + return temMALFORMED; + } + } + } + + return preflight2(ctx); +} + +TER +SetCron::preclaim(PreclaimContext const& ctx) +{ + return tesSUCCESS; +} + +TER +SetCron::doApply() +{ + auto& view = ctx_.view(); + auto const& tx = ctx_.tx; + + bool const isDelete = tx.isFlag(tfCronUnset); + + // delay can be zero, in which case the cron will usually execute next + // ledger. + uint32_t delay{0}; + uint32_t recur{0}; + + if (!isDelete) + { + if (tx.isFieldPresent(sfDelaySeconds)) + delay = tx.getFieldU32(sfDelaySeconds); + if (tx.isFieldPresent(sfRepeatCount)) + recur = tx.getFieldU32(sfRepeatCount); + } + + uint32_t currentTime = view.parentCloseTime().time_since_epoch().count(); + + // do all this sanity checking before we modify the ledger... + // even for a delete operation this will fall through without incident + + uint32_t afterTime = currentTime + delay; + if (afterTime < currentTime) + return tefINTERNAL; + + AccountID const& id = tx.getAccountID(sfAccount); + auto sle = view.peek(keylet::account(id)); + if (!sle) + return tefINTERNAL; + + // in all cases whatsoever, this transaction will delete an existing + // old cron object and return the owner reserve to the owner. + + if (sle->isFieldPresent(sfCron)) + { + Keylet klOld{ltCRON, sle->getFieldH256(sfCron)}; + + auto sleCron = view.peek(klOld); + if (!sleCron) + { + JLOG(j_.warn()) << "SetCron: Cron object didn't exist."; + return tefBAD_LEDGER; + } + + if (safe_cast( + sleCron->getFieldU16(sfLedgerEntryType)) != ltCRON) + { + JLOG(j_.warn()) << "SetCron: sfCron pointed to non-cron object!!"; + return tefBAD_LEDGER; + } + + if (!view.dirRemove( + keylet::ownerDir(id), (*sleCron)[sfOwnerNode], klOld, false)) + { + JLOG(j_.warn()) << "SetCron: Ownerdir bad. " << id; + return tefBAD_LEDGER; + } + + view.erase(sleCron); + adjustOwnerCount(view, sle, -1, j_); + sle->makeFieldAbsent(sfCron); + } + + // if the operation is a delete (no delay or recur specified then stop + // here.) + if (isDelete) + { + view.update(sle); + return tesSUCCESS; + } + + // execution to here means we're creating a new Cron object and adding it to + // the user's owner dir + + Keylet klCron = keylet::cron(afterTime, id); + + std::shared_ptr sleCron = std::make_shared(klCron); + + STAmount const reserve{ + view.fees().accountReserve(sle->getFieldU32(sfOwnerCount) + 1)}; + + STAmount const afterFee = + mPriorBalance - ctx_.tx.getFieldAmount(sfFee).xrp(); + + if (afterFee > mPriorBalance || afterFee < reserve) + return tecINSUFFICIENT_RESERVE; + + // add to owner dir + auto const page = + view.dirInsert(keylet::ownerDir(id), klCron, describeOwnerDir(id)); + if (!page) + return tecDIR_FULL; + + sleCron->setFieldU64(sfOwnerNode, *page); + + adjustOwnerCount(view, sle, 1, j_); + + // set the fields + sleCron->setFieldU32(sfDelaySeconds, delay); + sleCron->setFieldU32(sfRepeatCount, recur); + sleCron->setAccountID(sfOwner, id); + + sle->setFieldH256(sfCron, klCron.key); + + view.update(sle); + + view.insert(sleCron); + + return tesSUCCESS; +} + +XRPAmount +SetCron::calculateBaseFee(ReadView const& view, STTx const& tx) +{ + auto const baseFee = Transactor::calculateBaseFee(view, tx); + + auto const hasRepeat = tx.isFieldPresent(sfRepeatCount); + + if (tx.isFlag(tfCronUnset)) + // delete cron + return baseFee; + + // factor a cost based on the total number of txns expected + // for RepeatCount of 0 we have this txn (SetCron) and the + // single Cron txn (2). For a RepeatCount of 1 we have this txn, + // the first time the cron executes, and the second time (3). + uint32_t const additionalExpectedExecutions = + hasRepeat ? tx.getFieldU32(sfRepeatCount) + 1 : 1; + auto const additionalFee = baseFee * additionalExpectedExecutions; + + if (baseFee + additionalFee < baseFee) + return baseFee; + + return baseFee + additionalFee; +} + +} // namespace ripple diff --git a/src/ripple/app/tx/impl/SetCron.h b/src/ripple/app/tx/impl/SetCron.h new file mode 100644 index 000000000..3f255ffc7 --- /dev/null +++ b/src/ripple/app/tx/impl/SetCron.h @@ -0,0 +1,56 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 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_TX_SETCRON_H_INCLUDED +#define RIPPLE_TX_SETCRON_H_INCLUDED + +#include +#include +#include + +namespace ripple { + +class SetCron : public Transactor +{ +public: + static constexpr ConsequencesFactoryType ConsequencesFactory{Custom}; + + explicit SetCron(ApplyContext& ctx) : Transactor(ctx) + { + } + + static XRPAmount + calculateBaseFee(ReadView const& view, STTx const& tx); + + static TxConsequences + makeTxConsequences(PreflightContext const& ctx); + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const&); + + TER + doApply() override; +}; + +} // namespace ripple + +#endif diff --git a/src/ripple/app/tx/impl/applySteps.cpp b/src/ripple/app/tx/impl/applySteps.cpp index aaf103153..7e3d42cd8 100644 --- a/src/ripple/app/tx/impl/applySteps.cpp +++ b/src/ripple/app/tx/impl/applySteps.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -181,6 +183,10 @@ invoke_preflight(PreflightContext const& ctx) case ttURITOKEN_CREATE_SELL_OFFER: case ttURITOKEN_CANCEL_SELL_OFFER: return invoke_preflight_helper(ctx); + case ttCRON_SET: + return invoke_preflight_helper(ctx); + case ttCRON: + return invoke_preflight_helper(ctx); default: assert(false); return {temUNKNOWN, TxConsequences{temUNKNOWN}}; @@ -306,6 +312,10 @@ invoke_preclaim(PreclaimContext const& ctx) case ttURITOKEN_CREATE_SELL_OFFER: case ttURITOKEN_CANCEL_SELL_OFFER: return invoke_preclaim(ctx); + case ttCRON_SET: + return invoke_preclaim(ctx); + case ttCRON: + return invoke_preclaim(ctx); default: assert(false); return temUNKNOWN; @@ -393,6 +403,10 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx) case ttURITOKEN_CREATE_SELL_OFFER: case ttURITOKEN_CANCEL_SELL_OFFER: return URIToken::calculateBaseFee(view, tx); + case ttCRON_SET: + return SetCron::calculateBaseFee(view, tx); + case ttCRON: + return Cron::calculateBaseFee(view, tx); default: return XRPAmount{0}; } @@ -586,6 +600,14 @@ invoke_apply(ApplyContext& ctx) URIToken p(ctx); return p(); } + case ttCRON_SET: { + SetCron p(ctx); + return p(); + } + case ttCRON: { + Cron p(ctx); + return p(); + } default: assert(false); return {temUNKNOWN, false}; diff --git a/src/ripple/protocol/Feature.h b/src/ripple/protocol/Feature.h index b52a78265..ad5b22bd7 100644 --- a/src/ripple/protocol/Feature.h +++ b/src/ripple/protocol/Feature.h @@ -74,7 +74,7 @@ namespace detail { // Feature.cpp. Because it's only used to reserve storage, and determine how // large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than // the actual number of amendments. A LogicError on startup will verify this. -static constexpr std::size_t numFeatures = 86; +static constexpr std::size_t numFeatures = 87; /** Amendments that this server supports and the default voting behavior. Whether they are enabled depends on the Rules defined in the validated @@ -373,6 +373,7 @@ extern uint256 const fixProvisionalDoubleThreading; extern uint256 const featureClawback; extern uint256 const featureDeepFreeze; extern uint256 const featureIOUIssuerWeakTSH; +extern uint256 const featureCron; extern uint256 const fixInvalidTxFlags; } // namespace ripple diff --git a/src/ripple/protocol/Indexes.h b/src/ripple/protocol/Indexes.h index 2d8c32140..597a90414 100644 --- a/src/ripple/protocol/Indexes.h +++ b/src/ripple/protocol/Indexes.h @@ -297,6 +297,9 @@ import_vlseq(PublicKey const& key) noexcept; Keylet uritoken(AccountID const& issuer, Blob const& uri); +Keylet +cron(uint32_t timestamp, AccountID const& id); + } // namespace keylet // Everything below is deprecated and should be removed in favor of keylets: diff --git a/src/ripple/protocol/LedgerFormats.h b/src/ripple/protocol/LedgerFormats.h index 0ecd94207..f87f7ae3f 100644 --- a/src/ripple/protocol/LedgerFormats.h +++ b/src/ripple/protocol/LedgerFormats.h @@ -58,6 +58,12 @@ enum LedgerEntryType : std::uint16_t */ ltACCOUNT_ROOT = 0x0061, + /** A ledger object representing a scheduled cron execution on an account. + + \sa keylet::cron + */ + ltCRON = 0x0041, + /** A ledger object which contains a list of object identifiers. \sa keylet::page, keylet::quality, keylet::book, keylet::next and diff --git a/src/ripple/protocol/SField.h b/src/ripple/protocol/SField.h index ae1c1007a..a75488310 100644 --- a/src/ripple/protocol/SField.h +++ b/src/ripple/protocol/SField.h @@ -410,6 +410,8 @@ extern SF_UINT32 const sfRewardLgrLast; extern SF_UINT32 const sfFirstNFTokenSequence; extern SF_UINT32 const sfImportSequence; extern SF_UINT32 const sfXahauActivationLgrSeq; +extern SF_UINT32 const sfDelaySeconds; +extern SF_UINT32 const sfRepeatCount; // 64-bit integers (common) extern SF_UINT64 const sfIndexNext; @@ -486,6 +488,7 @@ extern SF_UINT256 const sfURITokenID; extern SF_UINT256 const sfGovernanceFlags; extern SF_UINT256 const sfGovernanceMarks; extern SF_UINT256 const sfEmittedTxnID; +extern SF_UINT256 const sfCron; // currency amount (common) extern SF_AMOUNT const sfAmount; diff --git a/src/ripple/protocol/TxFlags.h b/src/ripple/protocol/TxFlags.h index a97e8b63c..60f0d11a0 100644 --- a/src/ripple/protocol/TxFlags.h +++ b/src/ripple/protocol/TxFlags.h @@ -200,6 +200,12 @@ constexpr std::uint32_t const tfImmutable = 1; // Clawback flags: constexpr std::uint32_t const tfClawbackMask = ~tfUniversal; +// CronSet Flags: +enum CronSetFlags : uint32_t { + tfCronUnset = 0x00000001, +}; +constexpr std::uint32_t const tfCronSetMask = ~(tfUniversal | tfCronUnset); + // clang-format on } // namespace ripple diff --git a/src/ripple/protocol/TxFormats.h b/src/ripple/protocol/TxFormats.h index e7fcc0d60..bea5905cb 100644 --- a/src/ripple/protocol/TxFormats.h +++ b/src/ripple/protocol/TxFormats.h @@ -149,6 +149,12 @@ enum TxType : std::uint16_t ttURITOKEN_CREATE_SELL_OFFER = 48, ttURITOKEN_CANCEL_SELL_OFFER = 49, + /* A pseudo-txn alarm signal for invoking a hook, emitted by validators after alarm set conditions are met */ + ttCRON = 92, + + /* Sechedule an alarm for later */ + ttCRON_SET = 93, + /* A note attaching transactor that allows the owner or issuer (on a object by object basis) to attach remarks */ ttREMARKS_SET = 94, diff --git a/src/ripple/protocol/impl/Feature.cpp b/src/ripple/protocol/impl/Feature.cpp index 789423efa..415e18830 100644 --- a/src/ripple/protocol/impl/Feature.cpp +++ b/src/ripple/protocol/impl/Feature.cpp @@ -479,6 +479,7 @@ REGISTER_FEATURE(Clawback, Supported::yes, VoteBehavior::De REGISTER_FIX (fixProvisionalDoubleThreading, Supported::yes, VoteBehavior::DefaultYes); REGISTER_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo); REGISTER_FEATURE(IOUIssuerWeakTSH, Supported::yes, VoteBehavior::DefaultNo); +REGISTER_FEATURE(Cron, Supported::yes, VoteBehavior::DefaultNo); REGISTER_FIX (fixInvalidTxFlags, Supported::yes, VoteBehavior::DefaultYes); // The following amendments are obsolete, but must remain supported diff --git a/src/ripple/protocol/impl/Indexes.cpp b/src/ripple/protocol/impl/Indexes.cpp index a03dc1ca2..d8f802fe2 100644 --- a/src/ripple/protocol/impl/Indexes.cpp +++ b/src/ripple/protocol/impl/Indexes.cpp @@ -72,6 +72,7 @@ enum class LedgerNameSpace : std::uint16_t { URI_TOKEN = 'U', IMPORT_VLSEQ = 'I', UNL_REPORT = 'R', + CRON = 'A', // No longer used or supported. Left here to reserve the space // to avoid accidental reuse. @@ -443,6 +444,51 @@ uritoken(AccountID const& issuer, Blob const& uri) LedgerNameSpace::URI_TOKEN, issuer, Slice{uri.data(), uri.size()})}; } +// Constructs an ordered CRON keylet (32 bytes): +// [8-byte namespace][4-byte timestamp (big-endian, seconds)][20-byte +// AccountID] +// +// Properties +// - Namespacing: first 8 bytes are the most-significant bytes of +// indexHash(LedgerNameSpace::CRON). +// - Uniqueness (per ts,acct): exactly ONE cron per (timestamp, AccountID). +// Insert is upsert (last-write-wins). +// This is fine because we only ever allow one cron per account at a time—if +// the same (ts,acct) is written, it’s simply an update to that single entry +// (idempotent; no duplicate leaves). +// - Iteration order: chronological by timestamp (BE), then by raw AccountID +// bytes. +// NOTE: raw AccountID ordering may bias priority; consider hashing AccountID +// for uniform per-timestamp spread. +// - Expected accidental prefix collisions (foreign objects sharing the 8-byte +// namespace): n / 2^64, +// assuming uniform high-64-bit distribution of other objects. +// Examples: 100M → ~5.4e-12, 1B → ~5.4e-11, 10B → ~5.4e-10, 100B → ~5.4e-9 +// (negligible). +Keylet +cron(uint32_t timestamp, AccountID const& id) +{ + static const uint256 ns = indexHash(LedgerNameSpace::CRON); + + uint8_t h[32]; + + // first 8 bytes are the namespacing + std::memcpy(h, ns.data(), 8); + + // next 4 bytes are the timestamp in BE + h[8] = static_cast((timestamp >> 24) & 0xFFU); + h[9] = static_cast((timestamp >> 16) & 0xFFU); + h[10] = static_cast((timestamp >> 8) & 0xFFU); + h[11] = static_cast((timestamp >> 0) & 0xFFU); + + const uint256 accHash = indexHash(LedgerNameSpace::CRON, timestamp, id); + + // final 20 bytes are account ID + std::memcpy(h + 12, accHash.cdata(), 20); + + return {ltCRON, uint256::fromVoid(h)}; +} + } // namespace keylet } // namespace ripple diff --git a/src/ripple/protocol/impl/LedgerFormats.cpp b/src/ripple/protocol/impl/LedgerFormats.cpp index ef811667c..78c3f61e8 100644 --- a/src/ripple/protocol/impl/LedgerFormats.cpp +++ b/src/ripple/protocol/impl/LedgerFormats.cpp @@ -68,6 +68,7 @@ LedgerFormats::LedgerFormats() {sfGovernanceMarks, soeOPTIONAL}, {sfAccountIndex, soeOPTIONAL}, {sfTouchCount, soeOPTIONAL}, + {sfCron, soeOPTIONAL}, }, commonFields); @@ -366,6 +367,16 @@ LedgerFormats::LedgerFormats() }, commonFields); + add(jss::Cron, + ltCRON, + { + {sfOwner, soeREQUIRED}, + {sfDelaySeconds, soeREQUIRED}, + {sfRepeatCount, soeREQUIRED}, + {sfOwnerNode, soeREQUIRED}, + }, + commonFields); + // clang-format on } diff --git a/src/ripple/protocol/impl/SField.cpp b/src/ripple/protocol/impl/SField.cpp index 6d205adbe..85b52ec2f 100644 --- a/src/ripple/protocol/impl/SField.cpp +++ b/src/ripple/protocol/impl/SField.cpp @@ -157,6 +157,8 @@ CONSTRUCT_TYPED_SFIELD(sfLockCount, "LockCount", UINT32, CONSTRUCT_TYPED_SFIELD(sfFirstNFTokenSequence, "FirstNFTokenSequence", UINT32, 50); +CONSTRUCT_TYPED_SFIELD(sfRepeatCount, "RepeatCount", UINT32, 94); +CONSTRUCT_TYPED_SFIELD(sfDelaySeconds, "DelaySeconds", UINT32, 95); CONSTRUCT_TYPED_SFIELD(sfXahauActivationLgrSeq, "XahauActivationLgrSeq",UINT32, 96); CONSTRUCT_TYPED_SFIELD(sfImportSequence, "ImportSequence", UINT32, 97); CONSTRUCT_TYPED_SFIELD(sfRewardTime, "RewardTime", UINT32, 98); @@ -239,6 +241,7 @@ CONSTRUCT_TYPED_SFIELD(sfGovernanceFlags, "GovernanceFlags", UINT256, CONSTRUCT_TYPED_SFIELD(sfGovernanceMarks, "GovernanceMarks", UINT256, 98); CONSTRUCT_TYPED_SFIELD(sfEmittedTxnID, "EmittedTxnID", UINT256, 97); CONSTRUCT_TYPED_SFIELD(sfHookCanEmit, "HookCanEmit", UINT256, 96); +CONSTRUCT_TYPED_SFIELD(sfCron, "Cron", UINT256, 95); // currency amount (common) CONSTRUCT_TYPED_SFIELD(sfAmount, "Amount", AMOUNT, 1); diff --git a/src/ripple/protocol/impl/STTx.cpp b/src/ripple/protocol/impl/STTx.cpp index cfa350381..2fbb19421 100644 --- a/src/ripple/protocol/impl/STTx.cpp +++ b/src/ripple/protocol/impl/STTx.cpp @@ -615,7 +615,7 @@ isPseudoTx(STObject const& tx) auto tt = safe_cast(*t); return tt == ttAMENDMENT || tt == ttFEE || tt == ttUNL_MODIFY || - tt == ttEMIT_FAILURE || tt == ttUNL_REPORT; + tt == ttEMIT_FAILURE || tt == ttUNL_REPORT || tt == ttCRON; } } // namespace ripple diff --git a/src/ripple/protocol/impl/TxFormats.cpp b/src/ripple/protocol/impl/TxFormats.cpp index 73bae26c3..b0a995bb8 100644 --- a/src/ripple/protocol/impl/TxFormats.cpp +++ b/src/ripple/protocol/impl/TxFormats.cpp @@ -472,6 +472,22 @@ TxFormats::TxFormats() {sfTicketSequence, soeOPTIONAL}, }, commonFields); + + add(jss::Cron, + ttCRON, + { + {sfOwner, soeREQUIRED}, + {sfLedgerSequence, soeREQUIRED}, + }, + commonFields); + + add(jss::CronSet, + ttCRON_SET, + { + {sfDelaySeconds, soeOPTIONAL}, + {sfRepeatCount, soeOPTIONAL}, + }, + commonFields); } TxFormats const& diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index 8bf510e2f..5cba9fb3c 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -51,14 +51,16 @@ JSS(Amendments); // ledger type. JSS(Amount); // in: TransactionSign; field. JSS(Authorize); // field JSS(Blob); -JSS(Check); // ledger type. -JSS(CheckCancel); // transaction type. -JSS(CheckCash); // transaction type. -JSS(CheckCreate); // transaction type. -JSS(ClaimReward); // transaction type. -JSS(Clawback); // transaction type. -JSS(ClearFlag); // field. -JSS(CreateCode); // field. +JSS(Check); // ledger type. +JSS(CheckCancel); // transaction type. +JSS(CheckCash); // transaction type. +JSS(CheckCreate); // transaction type. +JSS(ClaimReward); // transaction type. +JSS(Clawback); // transaction type. +JSS(ClearFlag); // field. +JSS(CreateCode); // field. +JSS(Cron); +JSS(CronSet); JSS(DeliverMin); // in: TransactionSign JSS(DepositPreauth); // transaction and ledger type. JSS(Destination); // in: TransactionSign; field. diff --git a/src/ripple/rpc/handlers/ServerDefinitions.cpp b/src/ripple/rpc/handlers/ServerDefinitions.cpp index 09c0cebc6..fa418e2d9 100644 --- a/src/ripple/rpc/handlers/ServerDefinitions.cpp +++ b/src/ripple/rpc/handlers/ServerDefinitions.cpp @@ -423,6 +423,7 @@ private: addFlagsToJson(ret, "NFTokenMint"); addFlagsToJson(ret, "NFTokenCreateOffer"); addFlagsToJson(ret, "ClaimReward"); + addFlagsToJson(ret, "CronSet"); struct FlagData { std::string name; diff --git a/src/test/app/Cron_test.cpp b/src/test/app/Cron_test.cpp new file mode 100644 index 000000000..a1f96ce46 --- /dev/null +++ b/src/test/app/Cron_test.cpp @@ -0,0 +1,461 @@ +//------------------------------------------------------------------------------ +/* + 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 +#include +#include +#include + +namespace ripple { +namespace test { +struct Cron_test : public beast::unit_test::suite +{ + void + testEnabled(FeatureBitset features) + { + testcase("enabled"); + using namespace jtx; + using namespace std::literals::chrono_literals; + + // setup env + auto const alice = Account("alice"); + auto const issuer = Account("issuer"); + + for (bool const withCron : {false, true}) + { + auto const amend = withCron ? features : features - featureCron; + Env env{*this, amend}; + + env.fund(XRP(1000), alice, issuer); + env.close(); + + auto const expectResult = + withCron ? ter(tesSUCCESS) : ter(temDISABLED); + + auto tx = cron::set(alice); + // CLAIM + env(cron::set(alice), cron::delay(100), fee(XRP(1)), expectResult); + env.close(); + } + } + + void + testFee(FeatureBitset features) + { + testcase("fee"); + using namespace jtx; + using namespace std::literals::chrono_literals; + + auto const alice = Account("alice"); + Env env{*this, features | featureCron}; + + auto const baseFee = env.current()->fees().base; + + env.fund(XRP(1000), alice); + env.close(); + + // create with RepeatCount + auto expected = baseFee * 2 + baseFee * 256; + env(cron::set(alice), + cron::delay(356 * 24 * 60 * 60), + cron::repeat(256), + fee(expected - 1), + ter(telINSUF_FEE_P)); + env.close(); + + env(cron::set(alice), + cron::delay(356 * 24 * 60 * 60), + cron::repeat(256), + fee(expected), + ter(tesSUCCESS)); + env.close(); + + // create with no RepeatCount + expected = baseFee * 2; + env(cron::set(alice), + cron::delay(356 * 24 * 60 * 60), + fee(expected - 1), + ter(telINSUF_FEE_P)); + env.close(); + + env(cron::set(alice), + cron::delay(356 * 24 * 60 * 60), + fee(expected), + ter(tesSUCCESS)); + env.close(); + + // delete + expected = baseFee; + env(cron::set(alice), + txflags(tfCronUnset), + fee(expected - 1), + ter(telINSUF_FEE_P)); + env.close(); + + env(cron::set(alice), + txflags(tfCronUnset), + fee(expected), + ter(tesSUCCESS)); + env.close(); + } + + void + testInvalidPreflight(FeatureBitset features) + { + testcase("invalid preflight"); + using namespace test::jtx; + using namespace std::literals; + + auto const alice = Account("alice"); + + test::jtx::Env env{ + *this, network::makeNetworkConfig(21337), features | featureCron}; + + env.fund(XRP(1000), alice); + env.close(); + + //---------------------------------------------------------------------- + // preflight + + // temINVALID_FLAG + { + env(cron::set(alice), txflags(tfClose), ter(temINVALID_FLAG)); + env(cron::set(alice), + txflags(tfUniversalMask), + ter(temINVALID_FLAG)); + } + + // temMALFORMED + { + // Invalid both DelaySeconds and RepeatCount are not specified + env(cron::set(alice), ter(temMALFORMED)); + + // Invalid DelaySeconds and RepeatCount combination + // (only RepeatCount specified) + env(cron::set(alice), cron::repeat(256), ter(temMALFORMED)); + + // Invalid DelaySeconds + env(cron::set(alice), + cron::delay(365 * 24 * 60 * 60 + 1), + cron::repeat(256), + ter(temMALFORMED)); + + // Invalid RepeatCount + env(cron::set(alice), + cron::delay(365 * 24 * 60 * 60), + cron::repeat(257), + ter(temMALFORMED)); + + // Invalid tfCronUnset flag + env(cron::set(alice), + cron::delay(365 * 24 * 60 * 60), + txflags(tfCronUnset), + ter(temMALFORMED)); + } + } + + void + testInvalidPreclaim(FeatureBitset features) + { + testcase("invalid preclaim"); + using namespace test::jtx; + using namespace std::literals; + + auto const alice = Account("alice"); + Env env{*this, features | featureCron}; + + // there is no check in preclaim + BEAST_EXPECT(true); + } + + void + testDoApply(FeatureBitset features) + { + testcase("doApply"); + using namespace jtx; + using namespace std::literals::chrono_literals; + auto const alice = Account("alice"); + Env env{*this, features | featureCron}; + + env.fund(XRP(1000), alice); + env.close(); + + auto const aliceOwnerCount = ownerCount(env, alice); + + // create cron + env(cron::set(alice), + cron::delay(356 * 24 * 60 * 60), + cron::repeat(256), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // increment owner count + BEAST_EXPECT(ownerCount(env, alice) == aliceOwnerCount + 1); + + auto const accSle = env.le(keylet::account(alice.id())); + BEAST_EXPECT(accSle); + BEAST_EXPECT(accSle->isFieldPresent(sfCron)); + + auto const cronKey = keylet::child(accSle->getFieldH256(sfCron)); + auto const cronSle = env.le(cronKey); + BEAST_EXPECT(cronSle); + BEAST_EXPECT( + cronSle->getFieldU32(sfDelaySeconds) == 356 * 24 * 60 * 60); + BEAST_EXPECT(cronSle->getFieldU32(sfRepeatCount) == 256); + + // update cron + env(cron::set(alice), + cron::delay(100), + cron::repeat(10), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // owner count does not change + BEAST_EXPECT(ownerCount(env, alice) == aliceOwnerCount + 1); + + auto const accSle2 = env.le(keylet::account(alice.id())); + BEAST_EXPECT(accSle2); + BEAST_EXPECT(accSle2->isFieldPresent(sfCron)); + + // old cron sle is deleted + BEAST_EXPECT(!env.le(cronKey)); + + auto const cronKey2 = keylet::child(accSle2->getFieldH256(sfCron)); + auto const cronSle2 = env.le(cronKey2); + BEAST_EXPECT(cronSle2); + BEAST_EXPECT(cronSle2->getFieldU32(sfDelaySeconds) == 100); + BEAST_EXPECT(cronSle2->getFieldU32(sfRepeatCount) == 10); + + // delete cron + env(cron::set(alice), + fee(XRP(1)), + txflags(tfCronUnset), + ter(tesSUCCESS)); + env.close(); + + // owner count decremented + BEAST_EXPECT(ownerCount(env, alice) == aliceOwnerCount); + + auto const accSle3 = env.le(keylet::account(alice.id())); + BEAST_EXPECT(accSle3); + BEAST_EXPECT(!accSle3->isFieldPresent(sfCron)); + + // old cron sle is deleted + BEAST_EXPECT(!env.le(cronKey2)); + + // delete cron without object will succeed + env(cron::set(alice), + fee(XRP(1)), + txflags(tfCronUnset), + ter(tesSUCCESS)); + env.close(); + } + + void + testCronExecution(FeatureBitset features) + { + testcase("cron execution"); + using namespace jtx; + using namespace std::literals::chrono_literals; + auto const alice = Account("alice"); + + { + // test ttCron execution and repeatCount + Env env{*this, features | featureCron}; + + env.fund(XRP(1000), alice); + env.close(); + + auto baseTime = env.timeKeeper().now().time_since_epoch().count(); + + auto repeatCount = 10; + + env(cron::set(alice), + cron::delay(100), + cron::repeat(repeatCount), + fee(XRP(1))); + env.close(10s); + + auto lastCronKeylet = + keylet::child(env.le(alice)->getFieldH256(sfCron)); + + while (repeatCount >= 0) + { + // close ledger until 100 seconds has passed + while (env.timeKeeper().now().time_since_epoch().count() - + baseTime < + 100) + { + env.close(10s); + auto txns = env.closed()->txs; + auto size = std::distance(txns.begin(), txns.end()); + BEAST_EXPECT(size == 0); + } + + // close after 100 seconds passed + env.close(); + + auto txns = env.closed()->txs; + auto size = std::distance(txns.begin(), txns.end()); + BEAST_EXPECT(size == 1); + for (auto it = txns.begin(); it != txns.end(); ++it) + { + auto const& tx = *it->first; + // check pseudo txn format + BEAST_EXPECT(tx.getTxnType() == ttCRON); + BEAST_EXPECT(tx.getAccountID(sfAccount) == AccountID()); + BEAST_EXPECT(tx.getAccountID(sfOwner) == alice.id()); + BEAST_EXPECT( + tx.getFieldU32(sfLedgerSequence) == + env.closed()->info().seq); + BEAST_EXPECT(tx.getFieldAmount(sfFee) == XRP(0)); + BEAST_EXPECT(tx.getFieldVL(sfSigningPubKey).size() == 0); + + // check old Cron object is deleted + BEAST_EXPECT(!env.le(lastCronKeylet)); + + if (repeatCount > 0) + { + // check new Cron object + auto const cronKeylet = + keylet::child(env.le(alice)->getFieldH256(sfCron)); + auto const cronSle = env.le(cronKeylet); + BEAST_EXPECT(cronSle); + BEAST_EXPECT( + cronSle->getFieldU32(sfDelaySeconds) == 100); + BEAST_EXPECT( + cronSle->getFieldU32(sfRepeatCount) == + --repeatCount); + BEAST_EXPECT( + cronSle->getAccountID(sfOwner) == alice.id()); + + // set new base time + baseTime = + env.timeKeeper().now().time_since_epoch().count(); + lastCronKeylet = cronKeylet; + } + else + { + // after all executions, the cron object should be + // deleted + BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfCron)); + BEAST_EXPECT(!env.le(lastCronKeylet)); + --repeatCount; // decrement for break double loop + } + } + } + } + + { + // test ttCron limit in a ledger + Env env{*this, features | featureCron}; + std::vector accounts; + accounts.reserve(300); + for (int i = 0; i < 300; ++i) + { + auto const& account = accounts.emplace_back( + Account("account_" + std::to_string(i))); + accounts.emplace_back(account); + env.fund(XRP(10000), account); + } + env.close(); + + for (auto const& account : accounts) + { + env(cron::set(account), cron::delay(0), fee(XRP(1))); + } + env.close(); + + // proceed ledger + env.close(); + { + auto const txns = env.closed()->txs; + auto size = std::distance(txns.begin(), txns.end()); + BEAST_EXPECT(size == 128); + for (auto it = txns.begin(); it != txns.end(); ++it) + { + auto const& tx = *it->first; + BEAST_EXPECT(tx.getTxnType() == ttCRON); + } + } + + // proceed ledger + env.close(); + { + auto const txns = env.closed()->txs; + auto size = std::distance(txns.begin(), txns.end()); + BEAST_EXPECT(size == 128); + for (auto it = txns.begin(); it != txns.end(); ++it) + { + auto const& tx = *it->first; + BEAST_EXPECT(tx.getTxnType() == ttCRON); + } + } + + // proceed ledger + env.close(); + { + auto const txns = env.closed()->txs; + auto size = std::distance(txns.begin(), txns.end()); + BEAST_EXPECT(size == 44); + for (auto it = txns.begin(); it != txns.end(); ++it) + { + auto const& tx = *it->first; + BEAST_EXPECT(tx.getTxnType() == ttCRON); + } + } + + // proceed ledger + env.close(); + { + auto const txns = env.closed()->txs; + auto size = std::distance(txns.begin(), txns.end()); + BEAST_EXPECT(size == 0); + } + } + } + + void + testWithFeats(FeatureBitset features) + { + testEnabled(features); + testFee(features); + testInvalidPreflight(features); + testInvalidPreclaim(features); + testDoApply(features); + + testCronExecution(features); + } + +public: + void + run() override + { + using namespace test::jtx; + auto const sa = supported_amendments(); + testWithFeats(sa); + } +}; + +BEAST_DEFINE_TESTSUITE(Cron, app, ripple); + +} // namespace test +} // namespace ripple diff --git a/src/test/app/SetHookTSH_test.cpp b/src/test/app/SetHookTSH_test.cpp index feaf74ba0..d3849c3bb 100644 --- a/src/test/app/SetHookTSH_test.cpp +++ b/src/test/app/SetHookTSH_test.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -748,10 +749,22 @@ private: jtx::Env& env, int const& expected, uint64_t const& lineno) + { + auto const hashStr = + env.tx()->getJson(JsonOptions::none)[jss::hash].asString(); + uint256 const txHash = uint256::fromVoid(strUnHex(hashStr)->data()); + testTSHStrongWeak(env, txHash, expected, lineno); + } + + void + testTSHStrongWeak( + jtx::Env& env, + uint256 const& txHash, + int const& expected, + uint64_t const& lineno) { Json::Value params; - params[jss::transaction] = - env.tx()->getJson(JsonOptions::none)[jss::hash]; + params[jss::transaction] = strHex(txHash); auto const jrr = env.rpc("json", "tx", to_string(params)); auto const meta = jrr[jss::result][jss::meta]; validateTSHStrongWeak(meta, expected, lineno); @@ -6251,6 +6264,103 @@ private: } } + // CronSet + // | otxn | tsh | cset | + // | A | A | S | + void + testCronSetTSH(FeatureBitset features) + { + testcase("cron set tsh"); + + using namespace test::jtx; + using namespace std::literals; + + // otxn: account + // tsh account + // w/s: strong + for (bool const testStrong : {true, false}) + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + env.fund(XRP(1000), account); + env.close(); + + if (!testStrong) + addWeakTSH(env, account); + + // set tsh hook + setTSHHook(env, account, testStrong); + + // cron set + env(cron::set(account), + cron::delay(100), + cron::repeat(1), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // verify tsh hook triggered + testTSHStrongWeak(env, tshSTRONG, __LINE__); + } + } + + // | otxn | tsh | cron | + // | - | O | W | + void + testCronTSH(FeatureBitset features) + { + testcase("cron tsh"); + + using namespace test::jtx; + using namespace std::literals; + + // otxn: - + // tsh owner + // w/s: weak + for (bool const testStrong : {true, false}) + { + test::jtx::Env env{ + *this, + network::makeNetworkConfig(21337, "10", "1000000", "200000"), + features}; + + auto const account = Account("alice"); + env.fund(XRP(1000), account); + env.close(); + + // cron set + env(cron::set(account), + cron::delay(100), + cron::repeat(1), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + if (!testStrong) + addWeakTSH(env, account); + + // set tsh hook + setTSHHook(env, account, testStrong); + + // proceed ledger + env.close(100s); + + // close ledger + env.close(); + + // verify tsh hook triggered + auto const expected = testStrong ? tshNONE : tshWEAK; + auto const txs = env.closed()->txs; + BEAST_EXPECT(std::distance(txs.begin(), txs.end()) == 1); + auto const tx = txs.begin()->first; + BEAST_EXPECT(tx->getTxnType() == ttCRON); + testTSHStrongWeak(env, tx->getTransactionID(), expected, __LINE__); + } + } void testEmissionOrdering(FeatureBitset features) { @@ -6398,6 +6508,8 @@ private: testURITokenCancelSellOfferTSH(features); testURITokenCreateSellOfferTSH(features); testRemitTSH(features); + testCronSetTSH(features); + testCronTSH(features); } void diff --git a/src/test/jtx.h b/src/test/jtx.h index 0220cbed0..b46b41e47 100644 --- a/src/test/jtx.h +++ b/src/test/jtx.h @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include diff --git a/src/test/jtx/cron.h b/src/test/jtx/cron.h new file mode 100644 index 000000000..e8ccc9ad4 --- /dev/null +++ b/src/test/jtx/cron.h @@ -0,0 +1,74 @@ +//------------------------------------------------------------------------------ +/* + 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_TEST_JTX_CRON_H_INCLUDED +#define RIPPLE_TEST_JTX_CRON_H_INCLUDED + +#include +#include + +namespace ripple { +namespace test { +namespace jtx { + +/** Cron operations. */ +namespace cron { + +/** Set a cron. */ +Json::Value +set(jtx::Account const& account); + +/** Sets the optional DelaySeconds on a JTx. */ +class delay +{ +private: + uint32_t delay_; + +public: + explicit delay(uint32_t delay) : delay_(delay) + { + } + + void + operator()(Env&, JTx& jtx) const; +}; + +/** Sets the optional RepeatCount on a JTx. */ +class repeat +{ +private: + uint32_t repeat_; + +public: + explicit repeat(uint32_t repeat) : repeat_(repeat) + { + } + + void + operator()(Env&, JTx& jtx) const; +}; + +} // namespace cron + +} // namespace jtx + +} // namespace test +} // namespace ripple + +#endif // RIPPLE_TEST_JTX_CRON_H_INCLUDED diff --git a/src/test/jtx/impl/cron.cpp b/src/test/jtx/impl/cron.cpp new file mode 100644 index 000000000..8b7fda57c --- /dev/null +++ b/src/test/jtx/impl/cron.cpp @@ -0,0 +1,56 @@ +//------------------------------------------------------------------------------ +/* + 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 +#include + +namespace ripple { +namespace test { +namespace jtx { + +namespace cron { + +// Set a cron. +Json::Value +set(jtx::Account const& account) +{ + using namespace jtx; + Json::Value jv; + jv[jss::TransactionType] = jss::CronSet; + jv[jss::Account] = account.human(); + return jv; +} + +void +delay::operator()(Env& env, JTx& jt) const +{ + jt.jv[sfDelaySeconds.jsonName] = delay_; +} + +void +repeat::operator()(Env& env, JTx& jt) const +{ + jt.jv[sfRepeatCount.jsonName] = repeat_; +} + +} // namespace cron + +} // namespace jtx +} // namespace test +} // namespace ripple From b0fcd36bcd0ec22d19f2cf4b271c3083367e61e2 Mon Sep 17 00:00:00 2001 From: RichardAH Date: Sat, 18 Oct 2025 16:27:05 +1000 Subject: [PATCH 14/20] import_vl_keys logic fix (flap fix) (#588) --- src/ripple/app/tx/impl/Change.cpp | 50 ++++++++++++++++++--------- src/test/consensus/UNLReport_test.cpp | 28 ++++++++++++++- 2 files changed, 61 insertions(+), 17 deletions(-) diff --git a/src/ripple/app/tx/impl/Change.cpp b/src/ripple/app/tx/impl/Change.cpp index 61134ca25..37a436fee 100644 --- a/src/ripple/app/tx/impl/Change.cpp +++ b/src/ripple/app/tx/impl/Change.cpp @@ -94,21 +94,6 @@ Change::preflight(PreflightContext const& ctx) "of sfImportVLKey, sfActiveValidator"; return temMALFORMED; } - - // if we do specify import_vl_keys in config then we won't approve keys - // that aren't on our list - if (ctx.tx.isFieldPresent(sfImportVLKey) && - !ctx.app.config().IMPORT_VL_KEYS.empty()) - { - auto const& inner = const_cast(ctx.tx) - .getField(sfImportVLKey) - .downcast(); - auto const pk = inner.getFieldVL(sfPublicKey); - std::string const strPk = strHex(makeSlice(pk)); - if (ctx.app.config().IMPORT_VL_KEYS.find(strPk) == - ctx.app.config().IMPORT_VL_KEYS.end()) - return telIMPORT_VL_KEY_NOT_RECOGNISED; - } } return tesSUCCESS; @@ -168,9 +153,42 @@ Change::preclaim(PreclaimContext const& ctx) return tesSUCCESS; case ttAMENDMENT: case ttUNL_MODIFY: - case ttUNL_REPORT: case ttEMIT_FAILURE: return tesSUCCESS; + case ttUNL_REPORT: { + if (!ctx.tx.isFieldPresent(sfImportVLKey) || + ctx.app.config().IMPORT_VL_KEYS.empty()) + return tesSUCCESS; + + // if we do specify import_vl_keys in config then we won't approve + // keys that aren't on our list and/or aren't in the ledger object + auto const& inner = const_cast(ctx.tx) + .getField(sfImportVLKey) + .downcast(); + auto const pkBlob = inner.getFieldVL(sfPublicKey); + std::string const strPk = strHex(makeSlice(pkBlob)); + if (ctx.app.config().IMPORT_VL_KEYS.find(strPk) != + ctx.app.config().IMPORT_VL_KEYS.end()) + return tesSUCCESS; + + auto const pkType = publicKeyType(makeSlice(pkBlob)); + if (!pkType) + return tefINTERNAL; + + PublicKey const pk(makeSlice(pkBlob)); + + // check on ledger + if (auto const unlRep = ctx.view.read(keylet::UNLReport()); + unlRep && unlRep->isFieldPresent(sfImportVLKeys)) + { + auto const& vlKeys = unlRep->getFieldArray(sfImportVLKeys); + for (auto const& k : vlKeys) + if (PublicKey(k[sfPublicKey]) == pk) + return tesSUCCESS; + } + + return telIMPORT_VL_KEY_NOT_RECOGNISED; + } default: return temUNKNOWN; } diff --git a/src/test/consensus/UNLReport_test.cpp b/src/test/consensus/UNLReport_test.cpp index e2a3021bd..b8dc217a2 100644 --- a/src/test/consensus/UNLReport_test.cpp +++ b/src/test/consensus/UNLReport_test.cpp @@ -357,6 +357,32 @@ class UNLReport_test : public beast::unit_test::suite BEAST_EXPECT(isImportVL(env, ivlKeys[0]) == true); BEAST_EXPECT(isImportVL(env, ivlKeys[1]) == false); BEAST_EXPECT(isActiveValidator(env, vlKeys[0]) == true); + + // now test unrecognised keys that are already present in the ledger + // object (flap fix) + l = std::make_shared( + *l, env.app().timeKeeper().closeTime()); + + // insert a ttUNL_REPORT pseudo into the open ledger + env.app().openLedger().modify( + [&](OpenView& view, beast::Journal j) -> bool { + STTx tx = createUNLRTx(l->seq(), ivlKeys[1], vlKeys[0]); + uint256 txID = tx.getTransactionID(); + auto s = std::make_shared(); + tx.add(*s); + env.app().getHashRouter().setFlags(txID, SF_PRIVATE2); + view.rawTxInsert(txID, std::move(s), nullptr); + return true; + }); + + BEAST_EXPECT(hasUNLReport(env) == true); + + // close the ledger + env.close(); + + BEAST_EXPECT(isImportVL(env, ivlKeys[0]) == true); + BEAST_EXPECT(isImportVL(env, ivlKeys[1]) == false); + BEAST_EXPECT(isActiveValidator(env, vlKeys[0]) == true); } } @@ -1324,4 +1350,4 @@ createUNLRTx( } } // namespace test -} // namespace ripple \ No newline at end of file +} // namespace ripple From 6fa6a96e3abb0f857d1c1dbbd02974aa86a4ae32 Mon Sep 17 00:00:00 2001 From: tequ Date: Tue, 21 Oct 2025 13:17:53 +0900 Subject: [PATCH 15/20] Introduce `StartTime` in `CronSet` and improve next execution scheduling (#596) --- hook/sfcodes.h | 6 +- hook/tts.h | 4 +- src/ripple/app/tx/impl/Cron.cpp | 7 +- src/ripple/app/tx/impl/SetCron.cpp | 106 +++++++++++++++------ src/ripple/protocol/SField.h | 1 + src/ripple/protocol/impl/LedgerFormats.cpp | 1 + src/ripple/protocol/impl/SField.cpp | 1 + src/ripple/protocol/impl/TxFormats.cpp | 1 + src/test/app/Cron_test.cpp | 91 ++++++++++++------ src/test/app/SetHookTSH_test.cpp | 4 + src/test/jtx/cron.h | 15 +++ src/test/jtx/impl/cron.cpp | 6 ++ 12 files changed, 182 insertions(+), 61 deletions(-) diff --git a/hook/sfcodes.h b/hook/sfcodes.h index e7c36c535..242d14499 100644 --- a/hook/sfcodes.h +++ b/hook/sfcodes.h @@ -62,6 +62,9 @@ #define sfEmitGeneration ((2U << 16U) + 46U) #define sfLockCount ((2U << 16U) + 49U) #define sfFirstNFTokenSequence ((2U << 16U) + 50U) +#define sfStartTime ((2U << 16U) + 93U) +#define sfRepeatCount ((2U << 16U) + 94U) +#define sfDelaySeconds ((2U << 16U) + 95U) #define sfXahauActivationLgrSeq ((2U << 16U) + 96U) #define sfImportSequence ((2U << 16U) + 97U) #define sfRewardTime ((2U << 16U) + 98U) @@ -129,6 +132,7 @@ #define sfGovernanceFlags ((5U << 16U) + 99U) #define sfGovernanceMarks ((5U << 16U) + 98U) #define sfEmittedTxnID ((5U << 16U) + 97U) +#define sfCron ((5U << 16U) + 95U) #define sfAmount ((6U << 16U) + 1U) #define sfBalance ((6U << 16U) + 2U) #define sfLimitAmount ((6U << 16U) + 3U) @@ -236,4 +240,4 @@ #define sfActiveValidators ((15U << 16U) + 95U) #define sfImportVLKeys ((15U << 16U) + 94U) #define sfHookEmissions ((15U << 16U) + 93U) -#define sfAmounts ((15U << 16U) + 92U) \ No newline at end of file +#define sfAmounts ((15U << 16U) + 92U) diff --git a/hook/tts.h b/hook/tts.h index 0973fa189..75ce47470 100644 --- a/hook/tts.h +++ b/hook/tts.h @@ -31,6 +31,8 @@ #define ttURITOKEN_BUY 47 #define ttURITOKEN_CREATE_SELL_OFFER 48 #define ttURITOKEN_CANCEL_SELL_OFFER 49 +#define ttCRON 92 +#define ttCRON_SET 93 #define ttREMIT 95 #define ttGENESIS_MINT 96 #define ttIMPORT 97 @@ -40,4 +42,4 @@ #define ttFEE 101 #define ttUNL_MODIFY 102 #define ttEMIT_FAILURE 103 -#define ttUNL_REPORT 104 \ No newline at end of file +#define ttUNL_REPORT 104 diff --git a/src/ripple/app/tx/impl/Cron.cpp b/src/ripple/app/tx/impl/Cron.cpp index befcdd425..67826b408 100644 --- a/src/ripple/app/tx/impl/Cron.cpp +++ b/src/ripple/app/tx/impl/Cron.cpp @@ -121,11 +121,11 @@ Cron::doApply() uint32_t delay = sleCron->getFieldU32(sfDelaySeconds); uint32_t recur = sleCron->getFieldU32(sfRepeatCount); - uint32_t currentTime = view.parentCloseTime().time_since_epoch().count(); + uint32_t lastStartTime = sleCron->getFieldU32(sfStartTime); // do all this sanity checking before we modify the ledger... - uint32_t afterTime = currentTime + delay; - if (afterTime < currentTime) + uint32_t afterTime = lastStartTime + delay; + if (afterTime < lastStartTime) return tefINTERNAL; // in all circumstances the Cron object is deleted... @@ -163,6 +163,7 @@ Cron::doApply() sleCron->setFieldU64(sfOwnerNode, *page); sleCron->setFieldU32(sfDelaySeconds, delay); sleCron->setFieldU32(sfRepeatCount, recur - 1); + sleCron->setFieldU32(sfStartTime, afterTime); sleCron->setAccountID(sfOwner, id); sle->setFieldH256(sfCron, klCron.key); diff --git a/src/ripple/app/tx/impl/SetCron.cpp b/src/ripple/app/tx/impl/SetCron.cpp index 3e2e4e6f4..3325702a8 100644 --- a/src/ripple/app/tx/impl/SetCron.cpp +++ b/src/ripple/app/tx/impl/SetCron.cpp @@ -51,47 +51,74 @@ SetCron::preflight(PreflightContext const& ctx) return temINVALID_FLAG; } - // DelaySeconds (D), RepeatCount (R) - // DR - Set Cron with Delay and Repeat - // D- - Set Cron (once off) with Delay only (repat implicitly 0) - // -R - Invalid + // DelaySeconds (D), RepeatCount (R), StartTime (S) + // DRS - Set Cron with Delay and Repeat and StartTime + // DR- - Invalid(StartTime is required) + // D-S - Invalid (both DelaySeconds and RepeatCount are required) + // -RS - Invalid (both DelaySeconds and RepeatCount are required) + // --S - Onetime cron with StartTime only // -- - Clear any existing cron (succeeds even if there isn't one) / with // tfCronUnset flag set bool const hasDelay = tx.isFieldPresent(sfDelaySeconds); bool const hasRepeat = tx.isFieldPresent(sfRepeatCount); + bool const hasStartTime = tx.isFieldPresent(sfStartTime); if (tx.isFlag(tfCronUnset)) { - if (hasDelay || hasRepeat) + // delete operation + if (hasDelay || hasRepeat || hasStartTime) { JLOG(j.debug()) << "SetCron: tfCronUnset flag cannot be used with " - "DelaySeconds or RepeatCount."; + "DelaySeconds, RepeatCount or StartTime."; return temMALFORMED; } } else { - if (!hasDelay) + // create operation + + if (!hasStartTime) { - JLOG(j.debug()) << "SetCron: DelaySeconds must be " - "specified to create a cron."; + JLOG(j.debug()) + << "SetCron: StartTime is required. Use StartTime=0 for " + "immediate execution, or specify a future timestamp."; + return temMALFORMED; + } + + if ((!hasDelay && hasRepeat) || (hasDelay && !hasRepeat)) + { + JLOG(j.debug()) + << "SetCron: DelaySeconds and RepeatCount must both be present " + "for recurring crons, or both absent for one-off crons."; return temMALFORMED; } // check delay is not too high - auto delay = tx.getFieldU32(sfDelaySeconds); - if (delay > 31536000UL /* 365 days in seconds */) + if (hasDelay) { - JLOG(j.debug()) << "SetCron: DelaySeconds was too high. (max 365 " - "days in seconds)."; - return temMALFORMED; + auto delay = tx.getFieldU32(sfDelaySeconds); + if (delay > 31536000UL /* 365 days in seconds */) + { + JLOG(j.debug()) + << "SetCron: DelaySeconds was too high. (max 365 " + "days in seconds)."; + return temMALFORMED; + } } // check repeat is not too high if (hasRepeat) { auto recur = tx.getFieldU32(sfRepeatCount); + if (recur == 0) + { + JLOG(j.debug()) + << "SetCron: RepeatCount must be greater than 0." + "For one-time execution, omit DelaySeconds and " + "RepeatCount."; + return temMALFORMED; + } if (recur > 256) { JLOG(j.debug()) @@ -108,6 +135,30 @@ SetCron::preflight(PreflightContext const& ctx) TER SetCron::preclaim(PreclaimContext const& ctx) { + if (ctx.tx.isFieldPresent(sfStartTime) && + ctx.tx.getFieldU32(sfStartTime) != 0) + { + // StartTime 0 means the cron will execute immediately + + auto const startTime = ctx.tx.getFieldU32(sfStartTime); + auto const parentCloseTime = + ctx.view.parentCloseTime().time_since_epoch().count(); + + if (startTime < parentCloseTime) + { + JLOG(ctx.j.debug()) << "SetCron: StartTime must be in the future " + "(or 0 for immediate execution)"; + return tecEXPIRED; + } + + if (startTime > ctx.view.parentCloseTime().time_since_epoch().count() + + 365 * 24 * 60 * 60) + { + JLOG(ctx.j.debug()) << "SetCron: StartTime is too far in the " + "future (max 365 days)."; + return tecEXPIRED; + } + } return tesSUCCESS; } @@ -123,6 +174,7 @@ SetCron::doApply() // ledger. uint32_t delay{0}; uint32_t recur{0}; + uint32_t startTime{0}; if (!isDelete) { @@ -130,17 +182,14 @@ SetCron::doApply() delay = tx.getFieldU32(sfDelaySeconds); if (tx.isFieldPresent(sfRepeatCount)) recur = tx.getFieldU32(sfRepeatCount); + if (tx.isFieldPresent(sfStartTime)) + { + startTime = tx.getFieldU32(sfStartTime); + if (startTime == 0) + startTime = view.parentCloseTime().time_since_epoch().count(); + } } - uint32_t currentTime = view.parentCloseTime().time_since_epoch().count(); - - // do all this sanity checking before we modify the ledger... - // even for a delete operation this will fall through without incident - - uint32_t afterTime = currentTime + delay; - if (afterTime < currentTime) - return tefINTERNAL; - AccountID const& id = tx.getAccountID(sfAccount); auto sle = view.peek(keylet::account(id)); if (!sle) @@ -190,7 +239,7 @@ SetCron::doApply() // execution to here means we're creating a new Cron object and adding it to // the user's owner dir - Keylet klCron = keylet::cron(afterTime, id); + Keylet klCron = keylet::cron(startTime, id); std::shared_ptr sleCron = std::make_shared(klCron); @@ -214,6 +263,7 @@ SetCron::doApply() adjustOwnerCount(view, sle, 1, j_); // set the fields + sleCron->setFieldU32(sfStartTime, startTime); sleCron->setFieldU32(sfDelaySeconds, delay); sleCron->setFieldU32(sfRepeatCount, recur); sleCron->setAccountID(sfOwner, id); @@ -232,18 +282,18 @@ SetCron::calculateBaseFee(ReadView const& view, STTx const& tx) { auto const baseFee = Transactor::calculateBaseFee(view, tx); - auto const hasRepeat = tx.isFieldPresent(sfRepeatCount); - if (tx.isFlag(tfCronUnset)) // delete cron return baseFee; + auto const repeatCount = + tx.isFieldPresent(sfRepeatCount) ? tx.getFieldU32(sfRepeatCount) : 0; + // factor a cost based on the total number of txns expected // for RepeatCount of 0 we have this txn (SetCron) and the // single Cron txn (2). For a RepeatCount of 1 we have this txn, // the first time the cron executes, and the second time (3). - uint32_t const additionalExpectedExecutions = - hasRepeat ? tx.getFieldU32(sfRepeatCount) + 1 : 1; + uint32_t const additionalExpectedExecutions = 1 + repeatCount; auto const additionalFee = baseFee * additionalExpectedExecutions; if (baseFee + additionalFee < baseFee) diff --git a/src/ripple/protocol/SField.h b/src/ripple/protocol/SField.h index a75488310..2f9619de9 100644 --- a/src/ripple/protocol/SField.h +++ b/src/ripple/protocol/SField.h @@ -412,6 +412,7 @@ extern SF_UINT32 const sfImportSequence; extern SF_UINT32 const sfXahauActivationLgrSeq; extern SF_UINT32 const sfDelaySeconds; extern SF_UINT32 const sfRepeatCount; +extern SF_UINT32 const sfStartTime; // 64-bit integers (common) extern SF_UINT64 const sfIndexNext; diff --git a/src/ripple/protocol/impl/LedgerFormats.cpp b/src/ripple/protocol/impl/LedgerFormats.cpp index 78c3f61e8..83303e620 100644 --- a/src/ripple/protocol/impl/LedgerFormats.cpp +++ b/src/ripple/protocol/impl/LedgerFormats.cpp @@ -371,6 +371,7 @@ LedgerFormats::LedgerFormats() ltCRON, { {sfOwner, soeREQUIRED}, + {sfStartTime, soeREQUIRED}, {sfDelaySeconds, soeREQUIRED}, {sfRepeatCount, soeREQUIRED}, {sfOwnerNode, soeREQUIRED}, diff --git a/src/ripple/protocol/impl/SField.cpp b/src/ripple/protocol/impl/SField.cpp index 85b52ec2f..eeecaa126 100644 --- a/src/ripple/protocol/impl/SField.cpp +++ b/src/ripple/protocol/impl/SField.cpp @@ -157,6 +157,7 @@ CONSTRUCT_TYPED_SFIELD(sfLockCount, "LockCount", UINT32, CONSTRUCT_TYPED_SFIELD(sfFirstNFTokenSequence, "FirstNFTokenSequence", UINT32, 50); +CONSTRUCT_TYPED_SFIELD(sfStartTime, "StartTime", UINT32, 93); CONSTRUCT_TYPED_SFIELD(sfRepeatCount, "RepeatCount", UINT32, 94); CONSTRUCT_TYPED_SFIELD(sfDelaySeconds, "DelaySeconds", UINT32, 95); CONSTRUCT_TYPED_SFIELD(sfXahauActivationLgrSeq, "XahauActivationLgrSeq",UINT32, 96); diff --git a/src/ripple/protocol/impl/TxFormats.cpp b/src/ripple/protocol/impl/TxFormats.cpp index b0a995bb8..efcb44752 100644 --- a/src/ripple/protocol/impl/TxFormats.cpp +++ b/src/ripple/protocol/impl/TxFormats.cpp @@ -486,6 +486,7 @@ TxFormats::TxFormats() { {sfDelaySeconds, soeOPTIONAL}, {sfRepeatCount, soeOPTIONAL}, + {sfStartTime, soeOPTIONAL}, }, commonFields); } diff --git a/src/test/app/Cron_test.cpp b/src/test/app/Cron_test.cpp index a1f96ce46..6e8248183 100644 --- a/src/test/app/Cron_test.cpp +++ b/src/test/app/Cron_test.cpp @@ -48,9 +48,13 @@ struct Cron_test : public beast::unit_test::suite auto const expectResult = withCron ? ter(tesSUCCESS) : ter(temDISABLED); - auto tx = cron::set(alice); // CLAIM - env(cron::set(alice), cron::delay(100), fee(XRP(1)), expectResult); + env(cron::set(alice), + cron::startTime(0), + cron::repeat(100), + cron::delay(100), + fee(XRP(1)), + expectResult); env.close(); } } @@ -70,9 +74,10 @@ struct Cron_test : public beast::unit_test::suite env.fund(XRP(1000), alice); env.close(); - // create with RepeatCount + // create auto expected = baseFee * 2 + baseFee * 256; env(cron::set(alice), + cron::startTime(0), cron::delay(356 * 24 * 60 * 60), cron::repeat(256), fee(expected - 1), @@ -80,26 +85,13 @@ struct Cron_test : public beast::unit_test::suite env.close(); env(cron::set(alice), + cron::startTime(0), cron::delay(356 * 24 * 60 * 60), cron::repeat(256), fee(expected), ter(tesSUCCESS)); env.close(); - // create with no RepeatCount - expected = baseFee * 2; - env(cron::set(alice), - cron::delay(356 * 24 * 60 * 60), - fee(expected - 1), - ter(telINSUF_FEE_P)); - env.close(); - - env(cron::set(alice), - cron::delay(356 * 24 * 60 * 60), - fee(expected), - ter(tesSUCCESS)); - env.close(); - // delete expected = baseFee; env(cron::set(alice), @@ -143,30 +135,47 @@ struct Cron_test : public beast::unit_test::suite // temMALFORMED { - // Invalid both DelaySeconds and RepeatCount are not specified + // Invalid DelaySeconds and RepeatCount and StartTime are not + // specified env(cron::set(alice), ter(temMALFORMED)); - // Invalid DelaySeconds and RepeatCount combination - // (only RepeatCount specified) - env(cron::set(alice), cron::repeat(256), ter(temMALFORMED)); + // Invalid DelaySeconds and RepeatCount combination with StartTime + env(cron::set(alice), + cron::startTime(100), + cron::delay(356 * 24 * 60 * 60), + ter(temMALFORMED)); + env(cron::set(alice), + cron::startTime(100), + cron::repeat(256), + ter(temMALFORMED)); // Invalid DelaySeconds env(cron::set(alice), + cron::startTime(100), cron::delay(365 * 24 * 60 * 60 + 1), cron::repeat(256), ter(temMALFORMED)); // Invalid RepeatCount env(cron::set(alice), + cron::startTime(100), cron::delay(365 * 24 * 60 * 60), cron::repeat(257), ter(temMALFORMED)); - // Invalid tfCronUnset flag + // Invalid with tfCronUnset flag env(cron::set(alice), cron::delay(365 * 24 * 60 * 60), txflags(tfCronUnset), ter(temMALFORMED)); + env(cron::set(alice), + cron::repeat(100), + txflags(tfCronUnset), + ter(temMALFORMED)); + env(cron::set(alice), + cron::startTime(100), + txflags(tfCronUnset), + ter(temMALFORMED)); } } @@ -179,9 +188,25 @@ struct Cron_test : public beast::unit_test::suite auto const alice = Account("alice"); Env env{*this, features | featureCron}; + env.fund(XRP(1000), alice); + env.close(); - // there is no check in preclaim - BEAST_EXPECT(true); + // Past StartTime + env(cron::set(alice), + cron::startTime( + env.timeKeeper().now().time_since_epoch().count() - 1), + fee(XRP(1)), + ter(tecEXPIRED)); + env.close(); + + // Too far Future StartTime + env(cron::set(alice), + cron::startTime( + env.timeKeeper().now().time_since_epoch().count() + + 365 * 24 * 60 * 60 + 1), + fee(XRP(1)), + ter(tecEXPIRED)); + env.close(); } void @@ -199,7 +224,10 @@ struct Cron_test : public beast::unit_test::suite auto const aliceOwnerCount = ownerCount(env, alice); // create cron + auto parentCloseTime = + env.current()->parentCloseTime().time_since_epoch().count(); env(cron::set(alice), + cron::startTime(parentCloseTime + 356 * 24 * 60 * 60), cron::delay(356 * 24 * 60 * 60), cron::repeat(256), fee(XRP(1)), @@ -219,9 +247,15 @@ struct Cron_test : public beast::unit_test::suite BEAST_EXPECT( cronSle->getFieldU32(sfDelaySeconds) == 356 * 24 * 60 * 60); BEAST_EXPECT(cronSle->getFieldU32(sfRepeatCount) == 256); + BEAST_EXPECT( + cronSle->getFieldU32(sfStartTime) == + parentCloseTime + 356 * 24 * 60 * 60); // update cron + parentCloseTime = + env.current()->parentCloseTime().time_since_epoch().count(); env(cron::set(alice), + cron::startTime(0), cron::delay(100), cron::repeat(10), fee(XRP(1)), @@ -243,6 +277,7 @@ struct Cron_test : public beast::unit_test::suite BEAST_EXPECT(cronSle2); BEAST_EXPECT(cronSle2->getFieldU32(sfDelaySeconds) == 100); BEAST_EXPECT(cronSle2->getFieldU32(sfRepeatCount) == 10); + BEAST_EXPECT(cronSle2->getFieldU32(sfStartTime) == parentCloseTime); // delete cron env(cron::set(alice), @@ -289,6 +324,7 @@ struct Cron_test : public beast::unit_test::suite auto repeatCount = 10; env(cron::set(alice), + cron::startTime(baseTime + 100), cron::delay(100), cron::repeat(repeatCount), fee(XRP(1))); @@ -311,7 +347,7 @@ struct Cron_test : public beast::unit_test::suite } // close after 100 seconds passed - env.close(); + env.close(10s); auto txns = env.closed()->txs; auto size = std::distance(txns.begin(), txns.end()); @@ -348,8 +384,7 @@ struct Cron_test : public beast::unit_test::suite cronSle->getAccountID(sfOwner) == alice.id()); // set new base time - baseTime = - env.timeKeeper().now().time_since_epoch().count(); + baseTime = baseTime + 100; lastCronKeylet = cronKeylet; } else @@ -380,7 +415,7 @@ struct Cron_test : public beast::unit_test::suite for (auto const& account : accounts) { - env(cron::set(account), cron::delay(0), fee(XRP(1))); + env(cron::set(account), cron::startTime(0), fee(XRP(1))); } env.close(); diff --git a/src/test/app/SetHookTSH_test.cpp b/src/test/app/SetHookTSH_test.cpp index d3849c3bb..5df238a98 100644 --- a/src/test/app/SetHookTSH_test.cpp +++ b/src/test/app/SetHookTSH_test.cpp @@ -6297,6 +6297,7 @@ private: // cron set env(cron::set(account), + cron::startTime(0), cron::delay(100), cron::repeat(1), fee(XRP(1)), @@ -6332,8 +6333,11 @@ private: env.fund(XRP(1000), account); env.close(); + auto const baseTime = + env.current()->parentCloseTime().time_since_epoch().count(); // cron set env(cron::set(account), + cron::startTime(baseTime + 100), cron::delay(100), cron::repeat(1), fee(XRP(1)), diff --git a/src/test/jtx/cron.h b/src/test/jtx/cron.h index e8ccc9ad4..8c2113aa0 100644 --- a/src/test/jtx/cron.h +++ b/src/test/jtx/cron.h @@ -34,6 +34,21 @@ namespace cron { Json::Value set(jtx::Account const& account); +/** Sets the optional StartTime on a JTx. */ +class startTime +{ +private: + uint32_t startTime_; + +public: + explicit startTime(uint32_t startTime) : startTime_(startTime) + { + } + + void + operator()(Env&, JTx& jtx) const; +}; + /** Sets the optional DelaySeconds on a JTx. */ class delay { diff --git a/src/test/jtx/impl/cron.cpp b/src/test/jtx/impl/cron.cpp index 8b7fda57c..e78f08405 100644 --- a/src/test/jtx/impl/cron.cpp +++ b/src/test/jtx/impl/cron.cpp @@ -37,6 +37,12 @@ set(jtx::Account const& account) return jv; } +void +startTime::operator()(Env& env, JTx& jt) const +{ + jt.jv[sfStartTime.jsonName] = startTime_; +} + void delay::operator()(Env& env, JTx& jt) const { From 9378f1a0ad7a396a00d95c736904d48347696bd8 Mon Sep 17 00:00:00 2001 From: Alloy Networks <45832257+alloynetworks@users.noreply.github.com> Date: Tue, 21 Oct 2025 07:20:10 +0300 Subject: [PATCH 16/20] Update CONTRIBUTING.md (#599) --- CONTRIBUTING.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 587f7fc97..07c8a339f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -176,10 +176,11 @@ existing maintainer without a vote. ## Current Maintainers -* [Richard Holland](https://github.com/RichardAH) (XRPL Labs + XRP Ledger Foundation) -* [Denis Angell](https://github.com/dangell7) (XRPL Labs + XRP Ledger Foundation) -* [Wietse Wind](https://github.com/WietseWind) (XRPL Labs + XRP Ledger Foundation) +* [Richard Holland](https://github.com/RichardAH) (XRPL Labs + INFTF) +* [Denis Angell](https://github.com/dangell7) (XRPL Labs + INFTF) +* [Wietse Wind](https://github.com/WietseWind) (XRPL Labs + INFTF) +* [tequ](https://github.com/tequdev) (Independent + INFTF) [1]: https://docs.github.com/en/get-started/quickstart/contributing-to-projects -[2]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/about-pull-request-merges#squash-and-merge-your-commits \ No newline at end of file +[2]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/about-pull-request-merges#squash-and-merge-your-commits From 74477d2c137038a5c3aaa82d237644828b0bd456 Mon Sep 17 00:00:00 2001 From: Niq Dudfield Date: Wed, 22 Oct 2025 11:15:12 +0700 Subject: [PATCH 17/20] added configurable NuDB block size support in xahaud (#601) --- Builds/CMake/RippledCore.cmake | 1 + cfg/xahaud-example.cfg | 33 ++ src/ripple/nodestore/Backend.h | 9 + src/ripple/nodestore/backend/NuDBFactory.cpp | 63 +++- src/test/nodestore/NuDBFactory_test.cpp | 357 +++++++++++++++++++ 5 files changed, 462 insertions(+), 1 deletion(-) create mode 100644 src/test/nodestore/NuDBFactory_test.cpp diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index b0423b02b..a104f5626 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -965,6 +965,7 @@ if (tests) src/test/nodestore/Basics_test.cpp src/test/nodestore/DatabaseShard_test.cpp src/test/nodestore/Database_test.cpp + src/test/nodestore/NuDBFactory_test.cpp src/test/nodestore/Timing_test.cpp src/test/nodestore/import_test.cpp src/test/nodestore/varint_test.cpp diff --git a/cfg/xahaud-example.cfg b/cfg/xahaud-example.cfg index 85520f93f..024468c3f 100644 --- a/cfg/xahaud-example.cfg +++ b/cfg/xahaud-example.cfg @@ -1127,6 +1127,39 @@ # it must be defined with the same value in both # sections. # +# Optional keys for NuDB only: +# +# nudb_block_size EXPERIMENTAL: Block size in bytes for NuDB storage. +# Must be a power of 2 between 4096 and 32768. Default is 4096. +# +# This parameter controls the fundamental storage unit +# size for NuDB's internal data structures. The choice +# of block size can significantly impact performance +# depending on your storage hardware and filesystem: +# +# - 4096 bytes: Optimal for most standard SSDs and +# traditional filesystems (ext4, NTFS, HFS+). +# Provides good balance of performance and storage +# efficiency. Recommended for most deployments. +# +# - 8192-16384 bytes: May improve performance on +# high-end NVMe SSDs and copy-on-write filesystems +# like ZFS or Btrfs that benefit from larger block +# alignment. Can reduce metadata overhead for large +# databases. +# +# - 32768 bytes (32K): Maximum supported block size +# for high-performance scenarios with very fast +# storage. May increase memory usage and reduce +# efficiency for smaller databases. +# +# Note: This setting cannot be changed after database +# creation without rebuilding the entire database. +# Choose carefully based on your hardware and expected +# database size. +# +# Example: nudb_block_size=4096 +# # These keys modify the behavior of online_delete, and thus are only # relevant if online_delete is defined and non-zero: diff --git a/src/ripple/nodestore/Backend.h b/src/ripple/nodestore/Backend.h index d773be741..25315a979 100644 --- a/src/ripple/nodestore/Backend.h +++ b/src/ripple/nodestore/Backend.h @@ -23,6 +23,7 @@ #include #include #include +#include namespace ripple { namespace NodeStore { @@ -175,6 +176,14 @@ public: virtual int fdRequired() const = 0; + /** Get the block size for backends that support it + */ + virtual std::optional + getBlockSize() const + { + return std::nullopt; + } + /** Returns read and write stats. @note The Counters struct is specific to and only used diff --git a/src/ripple/nodestore/backend/NuDBFactory.cpp b/src/ripple/nodestore/backend/NuDBFactory.cpp index 30b848e82..448068146 100644 --- a/src/ripple/nodestore/backend/NuDBFactory.cpp +++ b/src/ripple/nodestore/backend/NuDBFactory.cpp @@ -18,6 +18,7 @@ //============================================================================== #include +#include #include #include #include @@ -31,6 +32,7 @@ #include #include #include +#include namespace ripple { namespace NodeStore { @@ -48,6 +50,7 @@ public: size_t const keyBytes_; std::size_t const burstSize_; std::string const name_; + std::size_t const blockSize_; nudb::store db_; std::atomic deletePath_; Scheduler& scheduler_; @@ -62,6 +65,7 @@ public: , keyBytes_(keyBytes) , burstSize_(burstSize) , name_(get(keyValues, "path")) + , blockSize_(parseBlockSize(name_, keyValues, journal)) , deletePath_(false) , scheduler_(scheduler) { @@ -81,6 +85,7 @@ public: , keyBytes_(keyBytes) , burstSize_(burstSize) , name_(get(keyValues, "path")) + , blockSize_(parseBlockSize(name_, keyValues, journal)) , db_(context) , deletePath_(false) , scheduler_(scheduler) @@ -110,6 +115,12 @@ public: return name_; } + std::optional + getBlockSize() const override + { + return blockSize_; + } + void open(bool createIfMissing, uint64_t appType, uint64_t uid, uint64_t salt) override @@ -137,7 +148,7 @@ public: uid, salt, keyBytes_, - nudb::block_size(kp), + blockSize_, 0.50, ec); if (ec == nudb::errc::file_exists) @@ -362,6 +373,56 @@ public: { return 3; } + +private: + static std::size_t + parseBlockSize( + std::string const& name, + Section const& keyValues, + beast::Journal journal) + { + using namespace boost::filesystem; + auto const folder = path(name); + auto const kp = (folder / "nudb.key").string(); + + std::size_t const defaultSize = + nudb::block_size(kp); // Default 4K from NuDB + std::size_t blockSize = defaultSize; + std::string blockSizeStr; + + if (!get_if_exists(keyValues, "nudb_block_size", blockSizeStr)) + { + return blockSize; // Early return with default + } + + try + { + std::size_t const parsedBlockSize = + beast::lexicalCastThrow(blockSizeStr); + + // Validate: must be power of 2 between 4K and 32K + if (parsedBlockSize < 4096 || parsedBlockSize > 32768 || + (parsedBlockSize & (parsedBlockSize - 1)) != 0) + { + std::stringstream s; + s << "Invalid nudb_block_size: " << parsedBlockSize + << ". Must be power of 2 between 4096 and 32768."; + Throw(s.str()); + } + + JLOG(journal.info()) + << "Using custom NuDB block size: " << parsedBlockSize + << " bytes"; + return parsedBlockSize; + } + catch (std::exception const& e) + { + std::stringstream s; + s << "Invalid nudb_block_size value: " << blockSizeStr + << ". Error: " << e.what(); + Throw(s.str()); + } + } }; //------------------------------------------------------------------------------ diff --git a/src/test/nodestore/NuDBFactory_test.cpp b/src/test/nodestore/NuDBFactory_test.cpp new file mode 100644 index 000000000..6ae37a2c4 --- /dev/null +++ b/src/test/nodestore/NuDBFactory_test.cpp @@ -0,0 +1,357 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace ripple { +namespace NodeStore { + +class NuDBFactory_test : public TestBase +{ +private: + // Helper function to create a Section with specified parameters + Section + createSection(std::string const& path, std::string const& blockSize = "") + { + Section params; + params.set("type", "nudb"); + params.set("path", path); + if (!blockSize.empty()) + params.set("nudb_block_size", blockSize); + return params; + } + + // Helper function to create a backend and test basic functionality + bool + testBackendFunctionality( + Section const& params, + std::size_t expectedBlocksize) + { + try + { + DummyScheduler scheduler; + test::SuiteJournal journal("NuDBFactory_test", *this); + + auto backend = Manager::instance().make_Backend( + params, megabytes(4), scheduler, journal); + + if (!BEAST_EXPECT(backend)) + return false; + + if (!BEAST_EXPECT(backend->getBlockSize() == expectedBlocksize)) + return false; + + backend->open(); + + if (!BEAST_EXPECT(backend->isOpen())) + return false; + + // Test basic store/fetch functionality + auto batch = createPredictableBatch(10, 12345); + storeBatch(*backend, batch); + + Batch copy; + fetchCopyOfBatch(*backend, ©, batch); + + backend->close(); + + return areBatchesEqual(batch, copy); + } + catch (...) + { + return false; + } + } + + // Helper function to test log messages + void + testLogMessage( + Section const& params, + beast::severities::Severity level, + std::string const& expectedMessage) + { + test::StreamSink sink(level); + beast::Journal journal(sink); + + DummyScheduler scheduler; + auto backend = Manager::instance().make_Backend( + params, megabytes(4), scheduler, journal); + + std::string logOutput = sink.messages().str(); + BEAST_EXPECT(logOutput.find(expectedMessage) != std::string::npos); + } + +public: + void + testDefaultBlockSize() + { + testcase("Default block size (no nudb_block_size specified)"); + + beast::temp_dir tempDir; + auto params = createSection(tempDir.path()); + + // Should work with default 4096 block size + BEAST_EXPECT(testBackendFunctionality(params, 4096)); + } + + void + testValidBlockSizes() + { + testcase("Valid block sizes"); + + std::vector validSizes = {4096, 8192, 16384, 32768}; + + for (auto const& size : validSizes) + { + beast::temp_dir tempDir; + auto params = createSection(tempDir.path(), to_string(size)); + + BEAST_EXPECT(testBackendFunctionality(params, size)); + } + } + + void + testInvalidBlockSizes() + { + testcase("Invalid block sizes"); + + std::vector invalidSizes = { + "2048", // Too small + "1024", // Too small + "65536", // Too large + "131072", // Too large + "5000", // Not power of 2 + "6000", // Not power of 2 + "10000", // Not power of 2 + "0", // Zero + "-1", // Negative + "abc", // Non-numeric + "4k", // Invalid format + "4096.5" // Decimal + }; + + for (auto const& size : invalidSizes) + { + beast::temp_dir tempDir; + auto params = createSection(tempDir.path(), size); + + DummyScheduler scheduler; + test::SuiteJournal journal("NuDBFactory_test", *this); + + // Should throw exception for invalid sizes + try + { + auto backend = Manager::instance().make_Backend( + params, megabytes(4), scheduler, journal); + // If we get here, the test failed + BEAST_EXPECT(false); + } + catch (std::exception const& e) + { + // Expected exception + std::string error{e.what()}; + BEAST_EXPECT( + error.find("Invalid nudb_block_size") != std::string::npos); + } + } + } + + void + testLogMessages() + { + testcase("Log message verification"); + + // Test valid custom block size logging + { + beast::temp_dir tempDir; + auto params = createSection(tempDir.path(), "8192"); + + testLogMessage( + params, + beast::severities::kInfo, + "Using custom NuDB block size: 8192"); + } + + // Test invalid block size exception message + { + beast::temp_dir tempDir; + auto params = createSection(tempDir.path(), "5000"); + + test::StreamSink sink(beast::severities::kWarning); + beast::Journal journal(sink); + + DummyScheduler scheduler; + try + { + auto backend = Manager::instance().make_Backend( + params, megabytes(4), scheduler, journal); + fail(); + } + catch (std::exception const& e) + { + std::string logOutput{e.what()}; + BEAST_EXPECT( + logOutput.find("Invalid nudb_block_size: 5000") != + std::string::npos); + BEAST_EXPECT( + logOutput.find( + "Must be power of 2 between 4096 and 32768") != + std::string::npos); + } + } + + // Test non-numeric value exception message + { + beast::temp_dir tempDir; + auto params = createSection(tempDir.path(), "invalid"); + + test::StreamSink sink(beast::severities::kWarning); + beast::Journal journal(sink); + + DummyScheduler scheduler; + try + { + auto backend = Manager::instance().make_Backend( + params, megabytes(4), scheduler, journal); + + fail(); + } + catch (std::exception const& e) + { + std::string logOutput{e.what()}; + BEAST_EXPECT( + logOutput.find("Invalid nudb_block_size value: invalid") != + std::string::npos); + } + } + } + + void + testPowerOfTwoValidation() + { + testcase("Power of 2 validation logic"); + + // Test edge cases around valid range + std::vector> testCases = { + {"4095", false}, // Just below minimum + {"4096", true}, // Minimum valid + {"4097", false}, // Just above minimum, not power of 2 + {"8192", true}, // Valid power of 2 + {"8193", false}, // Just above valid power of 2 + {"16384", true}, // Valid power of 2 + {"32768", true}, // Maximum valid + {"32769", false}, // Just above maximum + {"65536", false} // Power of 2 but too large + }; + + for (auto const& [size, shouldWork] : testCases) + { + beast::temp_dir tempDir; + auto params = createSection(tempDir.path(), size); + + test::StreamSink sink(beast::severities::kWarning); + beast::Journal journal(sink); + + DummyScheduler scheduler; + try + { + auto backend = Manager::instance().make_Backend( + params, megabytes(4), scheduler, journal); + BEAST_EXPECT(shouldWork); + } + catch (std::exception const& e) + { + std::string logOutput{e.what()}; + BEAST_EXPECT( + logOutput.find("Invalid nudb_block_size") != + std::string::npos); + BEAST_EXPECT(!shouldWork); + } + } + } + + void + testDataPersistence() + { + testcase("Data persistence with different block sizes"); + + std::vector blockSizes = { + "4096", "8192", "16384", "32768"}; + + for (auto const& size : blockSizes) + { + beast::temp_dir tempDir; + auto params = createSection(tempDir.path(), size); + + DummyScheduler scheduler; + test::SuiteJournal journal("NuDBFactory_test", *this); + + // Create test data + auto batch = createPredictableBatch(50, 54321); + + // Store data + { + auto backend = Manager::instance().make_Backend( + params, megabytes(4), scheduler, journal); + backend->open(); + storeBatch(*backend, batch); + backend->close(); + } + + // Retrieve data in new backend instance + { + auto backend = Manager::instance().make_Backend( + params, megabytes(4), scheduler, journal); + backend->open(); + + Batch copy; + fetchCopyOfBatch(*backend, ©, batch); + + BEAST_EXPECT(areBatchesEqual(batch, copy)); + backend->close(); + } + } + } + + void + run() override + { + testDefaultBlockSize(); + testValidBlockSizes(); + testInvalidBlockSizes(); + testLogMessages(); + testPowerOfTwoValidation(); + testDataPersistence(); + } +}; + +BEAST_DEFINE_TESTSUITE(NuDBFactory, ripple_core, ripple); + +} // namespace NodeStore +} // namespace ripple From 96222baf5ebb5faba6c9e0d3dcb13df88b10830c Mon Sep 17 00:00:00 2001 From: tequ Date: Wed, 22 Oct 2025 14:25:38 +0900 Subject: [PATCH 18/20] Add hook header generators and CI verification workflow (#597) --- .../workflows/verify-generated-headers.yml | 36 +++++ hook/error.h | 2 +- hook/extern.h | 25 +-- hook/generate_error.sh | 58 +++++++ hook/generate_extern.sh | 145 ++++++++++++++++++ hook/generate_sfcodes.sh | 11 +- hook/generate_tts.sh | 38 +++++ hook/sfcodes.h | 6 + hook/tts.h | 4 + src/ripple/app/hook/applyHook.h | 68 ++++---- 10 files changed, 331 insertions(+), 62 deletions(-) create mode 100644 .github/workflows/verify-generated-headers.yml create mode 100755 hook/generate_error.sh create mode 100755 hook/generate_extern.sh create mode 100755 hook/generate_tts.sh diff --git a/.github/workflows/verify-generated-headers.yml b/.github/workflows/verify-generated-headers.yml new file mode 100644 index 000000000..4120f7b7f --- /dev/null +++ b/.github/workflows/verify-generated-headers.yml @@ -0,0 +1,36 @@ +name: Verify Generated Hook Headers + +on: + push: + pull_request: + +jobs: + verify-generated-headers: + strategy: + fail-fast: false + matrix: + include: + - target: hook/error.h + generator: ./hook/generate_error.sh + - target: hook/extern.h + generator: ./hook/generate_extern.sh + - target: hook/sfcodes.h + generator: bash ./hook/generate_sfcodes.sh + - target: hook/tts.h + generator: ./hook/generate_tts.sh + runs-on: ubuntu-latest + name: ${{ matrix.target }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Verify ${{ matrix.target }} + run: | + set -euo pipefail + chmod +x hook/generate_*.sh || true + + tmp=$(mktemp) + trap 'rm -f "$tmp"' EXIT + + ${{ matrix.generator }} > "$tmp" + diff -u ${{ matrix.target }} "$tmp" diff --git a/hook/error.h b/hook/error.h index 2d7060ee1..63e0c03bb 100644 --- a/hook/error.h +++ b/hook/error.h @@ -48,4 +48,4 @@ #define TOO_MANY_STATE_MODIFICATIONS -44 #define TOO_MANY_NAMESPACES -45 #define HOOK_ERROR_CODES -#endif //HOOK_ERROR_CODES \ No newline at end of file +#endif //HOOK_ERROR_CODES diff --git a/hook/extern.h b/hook/extern.h index baf0e6de2..ee34af6ff 100644 --- a/hook/extern.h +++ b/hook/extern.h @@ -12,8 +12,6 @@ accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code); extern int64_t rollback(uint32_t read_ptr, uint32_t read_len, int64_t error_code); -// UTIL - extern int64_t util_raddr( uint32_t write_ptr, @@ -56,8 +54,6 @@ util_keylet( uint32_t e, uint32_t f); -// STO - extern int64_t sto_validate(uint32_t tread_ptr, uint32_t tread_len); @@ -85,8 +81,6 @@ sto_erase( uint32_t read_len, uint32_t field_id); -// EMITTED TXN - extern int64_t etxn_burden(void); @@ -112,8 +106,6 @@ emit( uint32_t read_ptr, uint32_t read_len); -// FLOAT - extern int64_t float_set(int32_t exponent, int64_t mantissa); @@ -174,8 +166,6 @@ float_log(int64_t float1); extern int64_t float_root(int64_t float1, uint32_t n); -// LEDGER - extern int64_t fee_base(void); @@ -200,8 +190,6 @@ ledger_keylet( uint32_t hread_ptr, uint32_t hread_len); -// HOOK - extern int64_t hook_account(uint32_t write_ptr, uint32_t write_len); @@ -233,8 +221,6 @@ hook_skip(uint32_t read_ptr, uint32_t read_len, uint32_t flags); extern int64_t hook_pos(void); -// SLOT - extern int64_t slot(uint32_t write_ptr, uint32_t write_len, uint32_t slot); @@ -262,8 +248,6 @@ slot_type(uint32_t slot_no, uint32_t flags); extern int64_t slot_float(uint32_t slot_no); -// STATE - extern int64_t state_set( uint32_t read_ptr, @@ -300,8 +284,6 @@ state_foreign( uint32_t aread_ptr, uint32_t aread_len); -// TRACE - extern int64_t trace( uint32_t mread_ptr, @@ -316,8 +298,6 @@ trace_num(uint32_t read_ptr, uint32_t read_len, int64_t number); extern int64_t trace_float(uint32_t read_ptr, uint32_t read_len, int64_t float1); -// OTXN - extern int64_t otxn_burden(void); @@ -346,9 +326,8 @@ otxn_param( extern int64_t meta_slot(uint32_t slot_no); -// featureHooks1 - -extern int64_t xpop_slot(uint32_t, uint32_t); +extern int64_t +xpop_slot(uint32_t slot_no_tx, uint32_t slot_no_meta); #define HOOK_EXTERN #endif // HOOK_EXTERN diff --git a/hook/generate_error.sh b/hook/generate_error.sh new file mode 100755 index 000000000..1e5aaadb2 --- /dev/null +++ b/hook/generate_error.sh @@ -0,0 +1,58 @@ +#!/bin/bash +set -eu + +SCRIPT_DIR=$(dirname "$0") +SCRIPT_DIR=$(cd "$SCRIPT_DIR" && pwd) + +ENUM_FILE="$SCRIPT_DIR/../src/ripple/app/hook/Enum.h" + +echo '// For documentation please see: https://xrpl-hooks.readme.io/reference/' +echo '// Generated using generate_error.sh' +echo '#ifndef HOOK_ERROR_CODES' +sed -n '/enum hook_return_code/,/};/p' "$ENUM_FILE" | + awk ' + function ltrim(s) { sub(/^[[:space:]]+/, "", s); return s } + function rtrim(s) { sub(/[[:space:]]+$/, "", s); return s } + function trim(s) { return rtrim(ltrim(s)) } + function emit(entry) { + entry = trim(entry) + if (entry == "") + return + gsub(/,[[:space:]]*$/, "", entry) + split(entry, parts, "=") + if (length(parts) < 2) + return + name = trim(parts[1]) + value = trim(parts[2]) + if (name == "" || value == "") + return + printf "#define %s %s\n", name, value + } + + { + line = $0 + if (line ~ /enum[[:space:]]+hook_return_code/) + next + if (line ~ /^[[:space:]]*\{/) + next + + sub(/\/\/.*$/, "", line) + + if (line ~ /^[[:space:]]*\};/) { + emit(buffer) + exit + } + + if (line ~ /^[[:space:]]*$/) + next + + buffer = buffer line " " + + if (line ~ /,[[:space:]]*$/) { + emit(buffer) + buffer = "" + } + } + ' +echo '#define HOOK_ERROR_CODES' +echo '#endif //HOOK_ERROR_CODES' diff --git a/hook/generate_extern.sh b/hook/generate_extern.sh new file mode 100755 index 000000000..2e9598ad0 --- /dev/null +++ b/hook/generate_extern.sh @@ -0,0 +1,145 @@ +#!/bin/bash +set -eu + +SCRIPT_DIR=$(dirname "$0") +SCRIPT_DIR=$(cd "$SCRIPT_DIR" && pwd) + +APPLY_HOOK="$SCRIPT_DIR/../src/ripple/app/hook/applyHook.h" + +{ + echo '// For documentation please see: https://xrpl-hooks.readme.io/reference/' + echo '// Generated using generate_extern.sh' + echo '#include ' + echo '#ifndef HOOK_EXTERN' + echo + awk ' + function trim(s) { + sub(/^[[:space:]]+/, "", s); + sub(/[[:space:]]+$/, "", s); + return s; + } + + function emit(ret, name, argc, argt, argn) { + attr = (name == "_g") ? " __attribute__((noduplicate))" : ""; + if (!first) + printf("\n"); + first = 0; + printf("extern %s%s\n", ret, attr); + if (argc == 0) { + printf("%s(void);\n", name); + return; + } + if (argc <= 3) { + line = argt[1] " " argn[1]; + for (i = 2; i <= argc; ++i) + line = line ", " argt[i] " " argn[i]; + printf("%s(%s);\n", name, line); + return; + } + printf("%s(\n", name); + for (i = 1; i <= argc; ++i) { + sep = (i < argc) ? "," : ");"; + printf(" %s %s%s\n", argt[i], argn[i], sep); + } + } + + function process(buffer, kind, payload, parts, n, i, arg, tokens, argc, argt, argn) { + if (kind == "func") + sub(/^DECLARE_HOOK_FUNCTION[[:space:]]*\(/, "", buffer); + else + sub(/^DECLARE_HOOK_FUNCNARG[[:space:]]*\(/, "", buffer); + buffer = trim(buffer); + sub(/\)[[:space:]]*$/, "", buffer); + n = split(buffer, parts, ","); + for (i = 1; i <= n; ++i) + parts[i] = trim(parts[i]); + ret = parts[1]; + name = parts[2]; + argc = 0; + delete argt; + delete argn; + for (i = 3; i <= n; ++i) { + arg = parts[i]; + if (arg == "") + continue; + split(arg, tokens, /[[:space:]]+/); + if (length(tokens) < 2) + continue; + ++argc; + argt[argc] = tokens[1]; + argn[argc] = tokens[2]; + } + emit(ret, name, argc, argt, argn); + } + + BEGIN { + first = 1; + in_block = 0; + in_macro = 0; + } + + { + line = $0; + if (in_block) { + if (line ~ /\*\//) { + sub(/.*\*\//, "", line); + in_block = 0; + } + else + next; + } + while (line ~ /\/\*/) { + if (line ~ /\/\*.*\*\//) { + gsub(/\/\*.*\*\//, "", line); + } + else { + sub(/\/\*.*/, "", line); + in_block = 1; + break; + } + } + sub(/\/\/.*$/, "", line); + line = trim(line); + if (line == "") + next; + + if (!in_macro && line ~ /^DECLARE_HOOK_FUNCTION\(/) { + buffer = line; + kind = "func"; + if (line ~ /\);[[:space:]]*$/) { + sub(/\);[[:space:]]*$/, "", buffer); + process(buffer, kind); + } + else + in_macro = 1; + next; + } + if (!in_macro && line ~ /^DECLARE_HOOK_FUNCNARG\(/) { + buffer = line; + kind = "narg"; + if (line ~ /\);[[:space:]]*$/) { + sub(/\);[[:space:]]*$/, "", buffer); + process(buffer, kind); + } + else + in_macro = 1; + next; + } + if (in_macro) { + buffer = buffer " " line; + if (line ~ /\);[[:space:]]*$/) { + sub(/\);[[:space:]]*$/, "", buffer); + process(buffer, kind); + in_macro = 0; + } + } + } + + END { + printf("\n"); + } + ' "$APPLY_HOOK" + + echo '#define HOOK_EXTERN' + echo '#endif // HOOK_EXTERN' +} diff --git a/hook/generate_sfcodes.sh b/hook/generate_sfcodes.sh index 255bbc63a..9a7ba99aa 100755 --- a/hook/generate_sfcodes.sh +++ b/hook/generate_sfcodes.sh @@ -1,8 +1,13 @@ -#/bin/bash -RIPPLED_ROOT="../src/ripple" +#!/bin/bash +set -eu + +SCRIPT_DIR=$(dirname "$0") +SCRIPT_DIR=$(cd "$SCRIPT_DIR" && pwd) + +RIPPLED_ROOT="$SCRIPT_DIR/../src/ripple" echo '// For documentation please see: https://xrpl-hooks.readme.io/reference/' echo '// Generated using generate_sfcodes.sh' -cat $RIPPLED_ROOT/protocol/impl/SField.cpp | grep -E '^CONSTRUCT_' | +cat "$RIPPLED_ROOT/protocol/impl/SField.cpp" | grep -E '^CONSTRUCT_' | sed 's/UINT16,/1,/g' | sed 's/UINT32,/2,/g' | sed 's/UINT64,/3,/g' | diff --git a/hook/generate_tts.sh b/hook/generate_tts.sh new file mode 100755 index 000000000..b27b8237c --- /dev/null +++ b/hook/generate_tts.sh @@ -0,0 +1,38 @@ +#!/bin/bash +set -eu + +SCRIPT_DIR=$(dirname "$0") +SCRIPT_DIR=$(cd "$SCRIPT_DIR" && pwd) + +RIPPLED_ROOT="$SCRIPT_DIR/../src/ripple" +TX_FORMATS="$RIPPLED_ROOT/protocol/TxFormats.h" + +echo '// For documentation please see: https://xrpl-hooks.readme.io/reference/' +echo '// Generated using generate_tts.sh' +sed -n '/enum TxType/,/};/p' "$TX_FORMATS" | + awk ' + function ltrim(s) { sub(/^[[:space:]]+/, "", s); return s } + function rtrim(s) { sub(/[[:space:]]+$/, "", s); return s } + function trim(s) { return rtrim(ltrim(s)) } + + /^[ \t]*tt[A-Z0-9_]+/ { + line = $0 + deprecated = (line ~ /\[\[deprecated/) + gsub(/\[\[deprecated[^]]*\]\]/, "", line) + sub(/\/\/.*$/, "", line) + gsub(/,[[:space:]]*$/, "", line) + + split(line, parts, "=") + if (length(parts) < 2) + next + + name = trim(parts[1]) + value = trim(parts[2]) + if (name == "" || value == "") + next + + prefix = deprecated ? "// " : "" + postfix = deprecated ? " // deprecated" : "" + printf "%s#define %s %s%s\n", prefix, name, value, postfix + } + ' diff --git a/hook/sfcodes.h b/hook/sfcodes.h index 242d14499..77d13f6aa 100644 --- a/hook/sfcodes.h +++ b/hook/sfcodes.h @@ -108,6 +108,7 @@ #define sfEmitParentTxnID ((5U << 16U) + 11U) #define sfEmitNonce ((5U << 16U) + 12U) #define sfEmitHookHash ((5U << 16U) + 13U) +#define sfObjectID ((5U << 16U) + 14U) #define sfBookDirectory ((5U << 16U) + 16U) #define sfInvoiceID ((5U << 16U) + 17U) #define sfNickname ((5U << 16U) + 18U) @@ -132,6 +133,7 @@ #define sfGovernanceFlags ((5U << 16U) + 99U) #define sfGovernanceMarks ((5U << 16U) + 98U) #define sfEmittedTxnID ((5U << 16U) + 97U) +#define sfHookCanEmit ((5U << 16U) + 96U) #define sfCron ((5U << 16U) + 95U) #define sfAmount ((6U << 16U) + 1U) #define sfBalance ((6U << 16U) + 2U) @@ -177,6 +179,8 @@ #define sfHookParameterName ((7U << 16U) + 24U) #define sfHookParameterValue ((7U << 16U) + 25U) #define sfBlob ((7U << 16U) + 26U) +#define sfRemarkValue ((7U << 16U) + 98U) +#define sfRemarkName ((7U << 16U) + 99U) #define sfAccount ((8U << 16U) + 1U) #define sfOwner ((8U << 16U) + 2U) #define sfDestination ((8U << 16U) + 3U) @@ -216,6 +220,7 @@ #define sfHookDefinition ((14U << 16U) + 22U) #define sfHookParameter ((14U << 16U) + 23U) #define sfHookGrant ((14U << 16U) + 24U) +#define sfRemark ((14U << 16U) + 97U) #define sfGenesisMint ((14U << 16U) + 96U) #define sfActiveValidator ((14U << 16U) + 95U) #define sfImportVLKey ((14U << 16U) + 94U) @@ -236,6 +241,7 @@ #define sfHookExecutions ((15U << 16U) + 18U) #define sfHookParameters ((15U << 16U) + 19U) #define sfHookGrants ((15U << 16U) + 20U) +#define sfRemarks ((15U << 16U) + 97U) #define sfGenesisMints ((15U << 16U) + 96U) #define sfActiveValidators ((15U << 16U) + 95U) #define sfImportVLKeys ((15U << 16U) + 94U) diff --git a/hook/tts.h b/hook/tts.h index 75ce47470..bd03b0399 100644 --- a/hook/tts.h +++ b/hook/tts.h @@ -1,4 +1,5 @@ // For documentation please see: https://xrpl-hooks.readme.io/reference/ +// Generated using generate_tts.sh #define ttPAYMENT 0 #define ttESCROW_CREATE 1 #define ttESCROW_FINISH 2 @@ -8,6 +9,7 @@ // #define ttNICKNAME_SET 6 // deprecated #define ttOFFER_CREATE 7 #define ttOFFER_CANCEL 8 +// #define ttCONTRACT 9 // deprecated #define ttTICKET_CREATE 10 // #define ttSPINAL_TAP 11 // deprecated #define ttSIGNER_LIST_SET 12 @@ -26,6 +28,7 @@ #define ttNFTOKEN_CREATE_OFFER 27 #define ttNFTOKEN_CANCEL_OFFER 28 #define ttNFTOKEN_ACCEPT_OFFER 29 +#define ttCLAWBACK 30 #define ttURITOKEN_MINT 45 #define ttURITOKEN_BURN 46 #define ttURITOKEN_BUY 47 @@ -33,6 +36,7 @@ #define ttURITOKEN_CANCEL_SELL_OFFER 49 #define ttCRON 92 #define ttCRON_SET 93 +#define ttREMARKS_SET 94 #define ttREMIT 95 #define ttGENESIS_MINT 96 #define ttIMPORT 97 diff --git a/src/ripple/app/hook/applyHook.h b/src/ripple/app/hook/applyHook.h index d6855969a..011f50f3e 100644 --- a/src/ripple/app/hook/applyHook.h +++ b/src/ripple/app/hook/applyHook.h @@ -74,6 +74,7 @@ DECLARE_HOOK_FUNCTION( uint32_t read_ptr, uint32_t read_len, int64_t error_code); + DECLARE_HOOK_FUNCTION( int64_t, util_raddr, @@ -97,6 +98,26 @@ DECLARE_HOOK_FUNCTION( uint32_t sread_len, uint32_t kread_ptr, uint32_t kread_len); +DECLARE_HOOK_FUNCTION( + int64_t, + util_sha512h, + uint32_t write_ptr, + uint32_t write_len, + uint32_t read_ptr, + uint32_t read_len); +DECLARE_HOOK_FUNCTION( + int64_t, + util_keylet, + uint32_t write_ptr, + uint32_t write_len, + uint32_t keylet_type, + uint32_t a, + uint32_t b, + uint32_t c, + uint32_t d, + uint32_t e, + uint32_t f); + DECLARE_HOOK_FUNCTION( int64_t, sto_validate, @@ -133,25 +154,6 @@ DECLARE_HOOK_FUNCTION( uint32_t read_len, uint32_t field_id); -DECLARE_HOOK_FUNCTION( - int64_t, - util_sha512h, - uint32_t write_ptr, - uint32_t write_len, - uint32_t read_ptr, - uint32_t read_len); -DECLARE_HOOK_FUNCTION( - int64_t, - util_keylet, - uint32_t write_ptr, - uint32_t write_len, - uint32_t keylet_type, - uint32_t a, - uint32_t b, - uint32_t c, - uint32_t d, - uint32_t e, - uint32_t f); DECLARE_HOOK_FUNCNARG(int64_t, etxn_burden); DECLARE_HOOK_FUNCTION( int64_t, @@ -214,7 +216,6 @@ DECLARE_HOOK_FUNCTION( DECLARE_HOOK_FUNCTION(int64_t, float_invert, int64_t float1); DECLARE_HOOK_FUNCTION(int64_t, float_divide, int64_t float1, int64_t float2); DECLARE_HOOK_FUNCNARG(int64_t, float_one); - DECLARE_HOOK_FUNCTION(int64_t, float_mantissa, int64_t float1); DECLARE_HOOK_FUNCTION(int64_t, float_sign, int64_t float1); DECLARE_HOOK_FUNCTION( @@ -226,17 +227,6 @@ DECLARE_HOOK_FUNCTION( DECLARE_HOOK_FUNCTION(int64_t, float_log, int64_t float1); DECLARE_HOOK_FUNCTION(int64_t, float_root, int64_t float1, uint32_t n); -DECLARE_HOOK_FUNCTION( - int64_t, - hook_account, - uint32_t write_ptr, - uint32_t write_len); -DECLARE_HOOK_FUNCTION( - int64_t, - hook_hash, - uint32_t write_ptr, - uint32_t write_len, - int32_t hook_no); DECLARE_HOOK_FUNCNARG(int64_t, fee_base); DECLARE_HOOK_FUNCNARG(int64_t, ledger_seq); DECLARE_HOOK_FUNCNARG(int64_t, ledger_last_time); @@ -250,7 +240,6 @@ DECLARE_HOOK_FUNCTION( ledger_nonce, uint32_t write_ptr, uint32_t write_len); - DECLARE_HOOK_FUNCTION( int64_t, ledger_keylet, @@ -261,6 +250,17 @@ DECLARE_HOOK_FUNCTION( uint32_t hread_ptr, uint32_t hread_len); +DECLARE_HOOK_FUNCTION( + int64_t, + hook_account, + uint32_t write_ptr, + uint32_t write_len); +DECLARE_HOOK_FUNCTION( + int64_t, + hook_hash, + uint32_t write_ptr, + uint32_t write_len, + int32_t hook_no); DECLARE_HOOK_FUNCTION( int64_t, hook_param_set, @@ -270,7 +270,6 @@ DECLARE_HOOK_FUNCTION( uint32_t kread_len, uint32_t hread_ptr, uint32_t hread_len); - DECLARE_HOOK_FUNCTION( int64_t, hook_param, @@ -278,9 +277,7 @@ DECLARE_HOOK_FUNCTION( uint32_t write_len, uint32_t read_ptr, uint32_t read_len); - DECLARE_HOOK_FUNCNARG(int64_t, hook_again); - DECLARE_HOOK_FUNCTION( int64_t, hook_skip, @@ -355,6 +352,7 @@ DECLARE_HOOK_FUNCTION( uint32_t nread_len, uint32_t aread_ptr, uint32_t aread_len); + DECLARE_HOOK_FUNCTION( int64_t, trace, From 6f148a8ac7cbb2d9a97a2ac45a35bf075e07b17c Mon Sep 17 00:00:00 2001 From: tequ Date: Thu, 23 Oct 2025 17:57:38 +0900 Subject: [PATCH 19/20] ExtendedHookState (#406) --- hook/sfcodes.h | 1 + src/ripple/app/hook/Enum.h | 24 +- src/ripple/app/hook/applyHook.h | 8 +- src/ripple/app/hook/impl/applyHook.cpp | 80 +- src/ripple/app/tx/impl/SetAccount.cpp | 71 ++ src/ripple/app/tx/impl/SetHook.cpp | 25 +- src/ripple/protocol/Feature.h | 3 +- src/ripple/protocol/SField.h | 1 + src/ripple/protocol/TER.h | 1 + src/ripple/protocol/impl/Feature.cpp | 1 + src/ripple/protocol/impl/LedgerFormats.cpp | 1 + src/ripple/protocol/impl/SField.cpp | 1 + src/ripple/protocol/impl/TER.cpp | 2 + src/ripple/protocol/impl/TxFormats.cpp | 1 + src/test/app/SetHook_test.cpp | 455 +++++++++- src/test/app/SetHook_wasm.h | 999 ++++++++++++++++----- src/test/rpc/AccountSet_test.cpp | 101 +++ 17 files changed, 1490 insertions(+), 285 deletions(-) diff --git a/hook/sfcodes.h b/hook/sfcodes.h index 77d13f6aa..8c7bc48f4 100644 --- a/hook/sfcodes.h +++ b/hook/sfcodes.h @@ -15,6 +15,7 @@ #define sfHookEmitCount ((1U << 16U) + 18U) #define sfHookExecutionIndex ((1U << 16U) + 19U) #define sfHookApiVersion ((1U << 16U) + 20U) +#define sfHookStateScale ((1U << 16U) + 21U) #define sfNetworkID ((2U << 16U) + 1U) #define sfFlags ((2U << 16U) + 2U) #define sfSourceTag ((2U << 16U) + 3U) diff --git a/src/ripple/app/hook/Enum.h b/src/ripple/app/hook/Enum.h index 19209389e..7f449958d 100644 --- a/src/ripple/app/hook/Enum.h +++ b/src/ripple/app/hook/Enum.h @@ -29,6 +29,8 @@ enum HookEmissionFlags : uint16_t { }; } // namespace ripple +using namespace ripple; + namespace hook { // RH TODO: put these somewhere better, and allow rules to be fed in inline uint32_t @@ -43,10 +45,26 @@ maxHookParameterValueSize(void) return 256; } -inline uint32_t -maxHookStateDataSize(void) +inline uint16_t +maxHookStateScale(void) { - return 256U; + return 16; +} + +inline uint32_t +maxHookStateDataSize(uint16_t hookStateScale) +{ + if (hookStateScale == 0) + { + // should not happen, but just in case + return 256U; + } + if (hookStateScale > maxHookStateScale()) + { + // should not happen, but just in case + return 256 * maxHookStateScale(); + } + return 256U * hookStateScale; } inline uint32_t diff --git a/src/ripple/app/hook/applyHook.h b/src/ripple/app/hook/applyHook.h index 011f50f3e..2c1f250bb 100644 --- a/src/ripple/app/hook/applyHook.h +++ b/src/ripple/app/hook/applyHook.h @@ -30,8 +30,9 @@ isEmittedTxn(ripple::STTx const& tx); class HookStateMap : public std::map< ripple::AccountID, // account that owns the state std::tuple< - int64_t, // remaining available ownercount - int64_t, // total namespace count + int64_t, // remaining available ownercount + int64_t, // total namespace count + uint16_t, // hook state scale std::map< ripple::uint256, // namespace std::map< @@ -465,9 +466,6 @@ apply( struct HookContext; -uint32_t -computeHookStateOwnerCount(uint32_t hookStateCount); - int64_t computeExecutionFee(uint64_t instructionCount); int64_t diff --git a/src/ripple/app/hook/impl/applyHook.cpp b/src/ripple/app/hook/impl/applyHook.cpp index fa9c5c614..d07e19be7 100644 --- a/src/ripple/app/hook/impl/applyHook.cpp +++ b/src/ripple/app/hook/impl/applyHook.cpp @@ -862,12 +862,6 @@ parseCurrency(uint8_t* cu_ptr, uint32_t cu_len) return {}; } -uint32_t -hook::computeHookStateOwnerCount(uint32_t hookStateCount) -{ - return hookStateCount; -} - inline int64_t serialize_keylet( ripple::Keylet& kl, @@ -1080,14 +1074,18 @@ hook::setHookState( return tefINTERNAL; // if the blob is too large don't set it - if (data.size() > hook::maxHookStateDataSize()) + uint16_t const hookStateScale = sleAccount->isFieldPresent(sfHookStateScale) + ? sleAccount->getFieldU16(sfHookStateScale) + : 1; + + if (data.size() > hook::maxHookStateDataSize(hookStateScale)) return temHOOK_DATA_TOO_LARGE; auto hookStateKeylet = ripple::keylet::hookState(acc, key, ns); auto hookStateDirKeylet = ripple::keylet::hookStateDir(acc, ns); uint32_t stateCount = sleAccount->getFieldU32(sfHookStateCount); - uint32_t oldStateReserve = computeHookStateOwnerCount(stateCount); + uint32_t oldStateCount = stateCount; auto hookState = view.peek(hookStateKeylet); @@ -1118,13 +1116,15 @@ hook::setHookState( if (stateCount > 0) --stateCount; // guard this because in the "impossible" event it is // already 0 we'll wrap back to int_max - // if removing this state entry would destroy the allotment then reduce // the owner count - if (computeHookStateOwnerCount(stateCount) < oldStateReserve) - adjustOwnerCount(view, sleAccount, -1, j); + if (stateCount < oldStateCount) + adjustOwnerCount(view, sleAccount, -hookStateScale, j); - sleAccount->setFieldU32(sfHookStateCount, stateCount); + if (view.rules().enabled(featureExtendedHookState) && stateCount == 0) + sleAccount->makeFieldAbsent(sfHookStateCount); + else + sleAccount->setFieldU32(sfHookStateCount, stateCount); if (nsDestroyed) hook::removeHookNamespaceEntry(*sleAccount, ns); @@ -1151,19 +1151,19 @@ hook::setHookState( { ++stateCount; - if (computeHookStateOwnerCount(stateCount) > oldStateReserve) + if (stateCount > oldStateCount) { // the hook used its allocated allotment of state entries for its // previous ownercount increment ownercount and give it another // allotment - ++ownerCount; + ownerCount += hookStateScale; XRPAmount const newReserve{view.fees().accountReserve(ownerCount)}; if (STAmount((*sleAccount)[sfBalance]).xrp() < newReserve) return tecINSUFFICIENT_RESERVE; - adjustOwnerCount(view, sleAccount, 1, j); + adjustOwnerCount(view, sleAccount, hookStateScale, j); } // update state count @@ -1451,7 +1451,7 @@ lookup_state_cache( if (stateMap.find(acc) == stateMap.end()) return std::nullopt; - auto& stateMapAcc = std::get<2>(stateMap[acc]); + auto& stateMapAcc = std::get<3>(stateMap[acc]); if (stateMapAcc.find(ns) == stateMapAcc.end()) return std::nullopt; @@ -1486,6 +1486,7 @@ set_state_cache( if (stateMap.find(acc) == stateMap.end()) { + // new Account Key // if this is the first time this account has been interacted with // we will compute how many available reserve positions there are auto const& fees = hookCtx.applyCtx.view().fees(); @@ -1497,6 +1498,10 @@ set_state_cache( STAmount bal = accSLE->getFieldAmount(sfBalance); + uint16_t const hookStateScale = accSLE->isFieldPresent(sfHookStateScale) + ? accSLE->getFieldU16(sfHookStateScale) + : 1; + int64_t availableForReserves = bal.xrp().drops() - fees.accountReserve(accSLE->getFieldU32(sfOwnerCount)).drops(); @@ -1507,7 +1512,7 @@ set_state_cache( availableForReserves /= increment; - if (availableForReserves < 1 && modified) + if (availableForReserves < hookStateScale && modified) return RESERVE_INSUFFICIENT; int64_t namespaceCount = accSLE->isFieldPresent(sfHookNamespaces) @@ -1526,20 +1531,28 @@ set_state_cache( stateMap.modified_entry_count++; + // sanity check + if (view.rules().enabled(featureExtendedHookState) && + availableForReserves < hookStateScale) + return INTERNAL_ERROR; + stateMap[acc] = { - availableForReserves - 1, + availableForReserves - hookStateScale, namespaceCount, + hookStateScale, {{ns, {{key, {modified, data}}}}}}; return 1; } auto& availableForReserves = std::get<0>(stateMap[acc]); auto& namespaceCount = std::get<1>(stateMap[acc]); - auto& stateMapAcc = std::get<2>(stateMap[acc]); - bool const canReserveNew = availableForReserves > 0; + auto& hookStateScale = std::get<2>(stateMap[acc]); + auto& stateMapAcc = std::get<3>(stateMap[acc]); + bool const canReserveNew = availableForReserves >= hookStateScale; if (stateMapAcc.find(ns) == stateMapAcc.end()) { + // new Namespace Key if (modified) { if (!canReserveNew) @@ -1557,7 +1570,11 @@ set_state_cache( namespaceCount++; } - availableForReserves--; + if (view.rules().enabled(featureExtendedHookState) && + availableForReserves < hookStateScale) + return INTERNAL_ERROR; + + availableForReserves -= hookStateScale; stateMap.modified_entry_count++; } @@ -1569,11 +1586,17 @@ set_state_cache( auto& stateMapNs = stateMapAcc[ns]; if (stateMapNs.find(key) == stateMapNs.end()) { + // new State Key if (modified) { if (!canReserveNew) return RESERVE_INSUFFICIENT; - availableForReserves--; + + if (view.rules().enabled(featureExtendedHookState) && + availableForReserves < hookStateScale) + return INTERNAL_ERROR; + + availableForReserves -= hookStateScale; stateMap.modified_entry_count++; } @@ -1582,6 +1605,7 @@ set_state_cache( return 1; } + // existing State Key if (modified) { if (!stateMapNs[key].first) @@ -1670,7 +1694,15 @@ DEFINE_HOOK_FUNCTION( (aread_len && NOT_IN_BOUNDS(aread_ptr, aread_len, memory_length))) return OUT_OF_BOUNDS; - uint32_t maxSize = hook::maxHookStateDataSize(); + auto const sleAccount = view.peek(hookCtx.result.accountKeylet); + if (!sleAccount && view.rules().enabled(featureExtendedHookState)) + return tefINTERNAL; + + uint16_t const hookStateScale = sleAccount->isFieldPresent(sfHookStateScale) + ? sleAccount->getFieldU16(sfHookStateScale) + : 1; + + uint32_t maxSize = hook::maxHookStateDataSize(hookStateScale); if (read_len > maxSize) return TOO_BIG; @@ -1814,7 +1846,7 @@ hook::finalizeHookState( for (const auto& accEntry : stateMap) { const auto& acc = accEntry.first; - for (const auto& nsEntry : std::get<2>(accEntry.second)) + for (const auto& nsEntry : std::get<3>(accEntry.second)) { const auto& ns = nsEntry.first; for (const auto& cacheEntry : nsEntry.second) diff --git a/src/ripple/app/tx/impl/SetAccount.cpp b/src/ripple/app/tx/impl/SetAccount.cpp index 8cbee5fd7..94850eaad 100644 --- a/src/ripple/app/tx/impl/SetAccount.cpp +++ b/src/ripple/app/tx/impl/SetAccount.cpp @@ -184,6 +184,19 @@ SetAccount::preflight(PreflightContext const& ctx) return temMALFORMED; } + // HookStateScale + if (tx.isFieldPresent(sfHookStateScale)) + { + if (!ctx.rules.enabled(featureExtendedHookState)) + return temMALFORMED; + + uint16_t scale = tx.getFieldU16(sfHookStateScale); + if (scale == 0 || + scale > hook::maxHookStateScale()) // Min: 1, Max: 16 (256 + // * 16 = 4096 bytes) + return temMALFORMED; + } + return preflight2(ctx); } @@ -249,6 +262,20 @@ SetAccount::preclaim(PreclaimContext const& ctx) } } + // HookStateScale + if (ctx.tx.isFieldPresent(sfHookStateScale)) + { + uint16_t const newScale = ctx.tx.getFieldU16(sfHookStateScale); + uint16_t const currentScale = sle->getFieldU16(sfHookStateScale); + uint32_t const stateCount = sle->getFieldU32(sfHookStateCount); + if (stateCount > 0 && newScale < currentScale) + { + JLOG(ctx.j.trace()) + << "Cannot decrease HookStateScale if state count is not zero."; + return tecHAS_HOOK_STATE; + } + } + return tesSUCCESS; } @@ -629,6 +656,50 @@ SetAccount::doApply() if (uFlagsIn != uFlagsOut) sle->setFieldU32(sfFlags, uFlagsOut); + // HookStateScale + if (tx.isFieldPresent(sfHookStateScale)) + { + uint16_t const newScale = tx.getFieldU16(sfHookStateScale); + uint16_t const oldScale = sle->isFieldPresent(sfHookStateScale) + ? sle->getFieldU16(sfHookStateScale) + : 1; + if (newScale == oldScale) + { + // do nothing + } + else if (newScale == 1) + { + sle->makeFieldAbsent(sfHookStateScale); + } + else + { + // increase OwnerCount + uint32_t const stateCount = sle->getFieldU32(sfHookStateCount); + uint32_t const oldOwnerCount = sle->getFieldU32(sfOwnerCount); + + uint32_t const newOwnerCount = oldOwnerCount - + (oldScale * stateCount) + (newScale * stateCount); + + // sanity check + if (newOwnerCount < oldOwnerCount) + return tecINTERNAL; + + if (newOwnerCount != oldOwnerCount) + { + STAmount const balance = STAmount((*sle)[sfBalance]).xrp(); + XRPAmount const reserve = + view().fees().accountReserve(newOwnerCount); + if (balance < reserve) + return tecINSUFFICIENT_RESERVE; + + adjustOwnerCount( + view(), sle, newOwnerCount - oldOwnerCount, j_); + } + + sle->setFieldU16(sfHookStateScale, newScale); + } + } + return tesSUCCESS; } diff --git a/src/ripple/app/tx/impl/SetHook.cpp b/src/ripple/app/tx/impl/SetHook.cpp index 79e68e01d..9ca829557 100644 --- a/src/ripple/app/tx/impl/SetHook.cpp +++ b/src/ripple/app/tx/impl/SetHook.cpp @@ -858,6 +858,9 @@ SetHook::destroyNamespace( bool const fixEnabled = ctx.rules.enabled(fixNSDelete); bool partialDelete = false; uint32_t oldStateCount = sleAccount->getFieldU32(sfHookStateCount); + uint16_t scale = sleAccount->isFieldPresent(sfHookStateScale) + ? sleAccount->getFieldU16(sfHookStateScale) + : 1; std::vector toDelete; toDelete.reserve(sleDir->getFieldV256(sfIndexes).size()); @@ -929,6 +932,15 @@ SetHook::destroyNamespace( view.erase(sleItem); } + if (view.rules().enabled(featureExtendedHookState) && + oldStateCount < toDelete.size()) + { + JLOG(ctx.j.fatal()) << "HookSet(" << hook::log::NSDELETE_COUNT << ")[" + << HS_ACC() << "]: DeleteState " + << "stateCount less than zero (overflow)"; + return tefBAD_LEDGER; + } + uint32_t stateCount = oldStateCount - toDelete.size(); if (stateCount > oldStateCount) { @@ -945,7 +957,18 @@ SetHook::destroyNamespace( sleAccount->setFieldU32(sfHookStateCount, stateCount); if (ctx.rules.enabled(fixNSDelete)) - adjustOwnerCount(view, sleAccount, -toDelete.size(), ctx.j); + { + auto const ownerCount = sleAccount->getFieldU32(sfOwnerCount); + if (view.rules().enabled(featureExtendedHookState) && + ownerCount < toDelete.size() * scale) + { + JLOG(ctx.j.fatal()) << "HookSet(" << hook::log::NSDELETE_COUNT + << ")[" << HS_ACC() << "]: DeleteState " + << "OwnerCount less than zero (overflow)"; + return tefBAD_LEDGER; + } + adjustOwnerCount(view, sleAccount, -toDelete.size() * scale, ctx.j); + } if (!partialDelete && sleAccount->isFieldPresent(sfHookNamespaces)) hook::removeHookNamespaceEntry(*sleAccount, ns); diff --git a/src/ripple/protocol/Feature.h b/src/ripple/protocol/Feature.h index ad5b22bd7..ed6b4e73f 100644 --- a/src/ripple/protocol/Feature.h +++ b/src/ripple/protocol/Feature.h @@ -74,7 +74,7 @@ namespace detail { // Feature.cpp. Because it's only used to reserve storage, and determine how // large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than // the actual number of amendments. A LogicError on startup will verify this. -static constexpr std::size_t numFeatures = 87; +static constexpr std::size_t numFeatures = 88; /** Amendments that this server supports and the default voting behavior. Whether they are enabled depends on the Rules defined in the validated @@ -375,6 +375,7 @@ extern uint256 const featureDeepFreeze; extern uint256 const featureIOUIssuerWeakTSH; extern uint256 const featureCron; extern uint256 const fixInvalidTxFlags; +extern uint256 const featureExtendedHookState; } // namespace ripple diff --git a/src/ripple/protocol/SField.h b/src/ripple/protocol/SField.h index 2f9619de9..f507b2232 100644 --- a/src/ripple/protocol/SField.h +++ b/src/ripple/protocol/SField.h @@ -354,6 +354,7 @@ extern SF_UINT16 const sfHookStateChangeCount; extern SF_UINT16 const sfHookEmitCount; extern SF_UINT16 const sfHookExecutionIndex; extern SF_UINT16 const sfHookApiVersion; +extern SF_UINT16 const sfHookStateScale; // 32-bit integers (common) extern SF_UINT32 const sfNetworkID; diff --git a/src/ripple/protocol/TER.h b/src/ripple/protocol/TER.h index 31ad16a3c..573e0b628 100644 --- a/src/ripple/protocol/TER.h +++ b/src/ripple/protocol/TER.h @@ -343,6 +343,7 @@ enum TECcodes : TERUnderlyingType { tecINSUF_RESERVE_SELLER = 187, tecIMMUTABLE = 188, tecTOO_MANY_REMARKS = 189, + tecHAS_HOOK_STATE = 190, tecLAST_POSSIBLE_ENTRY = 255, }; diff --git a/src/ripple/protocol/impl/Feature.cpp b/src/ripple/protocol/impl/Feature.cpp index 415e18830..64d05f134 100644 --- a/src/ripple/protocol/impl/Feature.cpp +++ b/src/ripple/protocol/impl/Feature.cpp @@ -481,6 +481,7 @@ REGISTER_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::De REGISTER_FEATURE(IOUIssuerWeakTSH, Supported::yes, VoteBehavior::DefaultNo); REGISTER_FEATURE(Cron, Supported::yes, VoteBehavior::DefaultNo); REGISTER_FIX (fixInvalidTxFlags, Supported::yes, VoteBehavior::DefaultYes); +REGISTER_FEATURE(ExtendedHookState, Supported::yes, VoteBehavior::DefaultNo); // The following amendments are obsolete, but must remain supported // because they could potentially get enabled. diff --git a/src/ripple/protocol/impl/LedgerFormats.cpp b/src/ripple/protocol/impl/LedgerFormats.cpp index 83303e620..def5f3000 100644 --- a/src/ripple/protocol/impl/LedgerFormats.cpp +++ b/src/ripple/protocol/impl/LedgerFormats.cpp @@ -68,6 +68,7 @@ LedgerFormats::LedgerFormats() {sfGovernanceMarks, soeOPTIONAL}, {sfAccountIndex, soeOPTIONAL}, {sfTouchCount, soeOPTIONAL}, + {sfHookStateScale, soeOPTIONAL}, {sfCron, soeOPTIONAL}, }, commonFields); diff --git a/src/ripple/protocol/impl/SField.cpp b/src/ripple/protocol/impl/SField.cpp index eeecaa126..c4f2ef85a 100644 --- a/src/ripple/protocol/impl/SField.cpp +++ b/src/ripple/protocol/impl/SField.cpp @@ -102,6 +102,7 @@ CONSTRUCT_TYPED_SFIELD(sfHookStateChangeCount, "HookStateChangeCount", UINT16, CONSTRUCT_TYPED_SFIELD(sfHookEmitCount, "HookEmitCount", UINT16, 18); CONSTRUCT_TYPED_SFIELD(sfHookExecutionIndex, "HookExecutionIndex", UINT16, 19); CONSTRUCT_TYPED_SFIELD(sfHookApiVersion, "HookApiVersion", UINT16, 20); +CONSTRUCT_TYPED_SFIELD(sfHookStateScale, "HookStateScale", UINT16, 21); // 32-bit integers (common) CONSTRUCT_TYPED_SFIELD(sfNetworkID, "NetworkID", UINT32, 1); diff --git a/src/ripple/protocol/impl/TER.cpp b/src/ripple/protocol/impl/TER.cpp index 30bcf46e2..08992c6a3 100644 --- a/src/ripple/protocol/impl/TER.cpp +++ b/src/ripple/protocol/impl/TER.cpp @@ -94,6 +94,8 @@ transResults() MAKE_ERROR(tecINSUF_RESERVE_SELLER, "The seller of an object has insufficient reserves, and thus cannot complete the sale."), MAKE_ERROR(tecIMMUTABLE, "The remark is marked immutable on the object, and therefore cannot be updated."), MAKE_ERROR(tecTOO_MANY_REMARKS, "The number of remarks on the object would exceed the limit of 32."), + MAKE_ERROR(tecHAS_HOOK_STATE, "Delete all hook state before reducing scale"), + MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."), MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."), MAKE_ERROR(tefBAD_AUTH, "Transaction's public key is not authorized."), diff --git a/src/ripple/protocol/impl/TxFormats.cpp b/src/ripple/protocol/impl/TxFormats.cpp index efcb44752..789de36f2 100644 --- a/src/ripple/protocol/impl/TxFormats.cpp +++ b/src/ripple/protocol/impl/TxFormats.cpp @@ -60,6 +60,7 @@ TxFormats::TxFormats() {sfTickSize, soeOPTIONAL}, {sfTicketSequence, soeOPTIONAL}, {sfNFTokenMinter, soeOPTIONAL}, + {sfHookStateScale, soeOPTIONAL}, }, commonFields); diff --git a/src/test/app/SetHook_test.cpp b/src/test/app/SetHook_test.cpp index 75caade5e..8fef47656 100644 --- a/src/test/app/SetHook_test.cpp +++ b/src/test/app/SetHook_test.cpp @@ -879,6 +879,9 @@ public: auto const bob = Account{"bob"}; env.fund(XRP(10000), bob); + auto const carol = Account{"carol"}; + env.fund(XRP(10000), carol); + Json::Value jv; jv[jss::Account] = alice.human(); jv[jss::TransactionType] = jss::SetHook; @@ -962,6 +965,7 @@ public: data[3] == 'u' && data[4] == 'e' && data[5] == '\0'); BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2); + BEAST_EXPECT((*env.le(alice))[sfHookStateCount] == 1); } // delete the namespace @@ -990,7 +994,113 @@ public: // ensure the state object is gone BEAST_EXPECT(!env.le(stateKeylet)); - BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == fixNS ? 1 : 2); + BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == (fixNS ? 1 : 2)); + BEAST_EXPECT(!(env.le("alice")->isFieldPresent(sfHookStateCount))); + } + + if (env.current()->rules().enabled(featureExtendedHookState)) + { + // Test hook with scaled state data + TestHook scaled_state_wasm = wasm[ + R"[test.hook]( + #include + 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); extern int64_t rollback + (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + extern int64_t state_set (uint32_t read_ptr, uint32_t + read_len, uint32_t kread_ptr, uint32_t kread_len); + + extern int64_t util_keylet(uint32_t, uint32_t, uint32_t, + uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); + extern int64_t slot_set(uint32_t, uint32_t, uint32_t); + extern int64_t slot_subfield(uint32_t, uint32_t, uint32_t); + extern int64_t slot(uint32_t, uint32_t, uint32_t); + extern int64_t hook_account(uint32_t, uint32_t); + extern int64_t util_keylet(uint32_t, uint32_t, uint32_t, + uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); + + #define SBUF(x) x, sizeof(x) + #define TOO_BIG -3 + #define DOESNT_EXIST -5 + #define KEYLET_ACCOUNT 3 + + #define sfHookStateScale ((1U << 16U) + 21U) + + #define ASSERT(x)\ + if (!(x))\ + rollback((uint32_t)#x,sizeof(#x),__LINE__) + int64_t hook(uint32_t reserved ) + { + _g(1,1); + + uint8_t hook_acc[20]; + ASSERT(hook_account(hook_acc, 20) == 20); + uint8_t account_keylet[34]; + ASSERT(util_keylet(account_keylet, 34, KEYLET_ACCOUNT, + hook_acc, 20, 0,0,0,0) == 34); + + ASSERT(slot_set(account_keylet, 34, 1) == 1); + slot_subfield(1, sfHookStateScale, 2); + int64_t scale = slot(0,0,2); + + if (scale == 5) { + ASSERT(state_set(0, 256, SBUF("test1")) == 256); + ASSERT(state_set(0, 256*2, SBUF("test2")) == 256*2); + ASSERT(state_set(0, 256*3, SBUF("test3")) == 256*3); + ASSERT(state_set(0, 256*4, SBUF("test4")) == 256*4); + ASSERT(state_set(0, 256*5, SBUF("test5")) == 256*5); + ASSERT(state_set(0, 256*5+1, SBUF("test")) == TOO_BIG); + accept(0,0,scale); + } + rollback(0,0,scale); + } + )[test.hook]"]; + + HASH_WASM(scaled_state); + BEAST_EXPECT(!env.le(carol)->isFieldPresent(sfHookStateCount)); + + // Install hook on carol + Json::Value jv = + ripple::test::jtx::hook(carol, {{hso(scaled_state_wasm)}}, 0); + jv[jss::Hooks][0U][jss::Hook][jss::HookNamespace] = ns_str; + jv[jss::Hooks][0U][jss::Hook][jss::HookOn] = + to_string(UINT256_BIT[ttACCOUNT_SET]); + env(jv, M("Create scaled state hook"), HSFEE, ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT((*env.le(carol))[sfOwnerCount] == 1); + BEAST_EXPECT(!env.le(carol)->isFieldPresent(sfHookStateCount)); + + { + // HookStateScale => 5 + Json::Value jv = noop(carol); + jv[sfHookStateScale.fieldName] = 5; + env(jv, HSFEE); + env.close(); + BEAST_EXPECT((*env.le(carol))[sfOwnerCount] == 1); + BEAST_EXPECT(!env.le(carol)->isFieldPresent(sfHookStateCount)); + + Json::Value invoke = invoke::invoke(carol); + env(invoke, HSFEE); + env.close(); + BEAST_EXPECT((*env.le(carol))[sfOwnerCount] == 26); + BEAST_EXPECT((*env.le(carol))[sfHookStateCount] == 5); + } + + // Delete namespace to clean up state + Json::Value iv; + iv[jss::Flags] = hsfNSDELETE; + iv[jss::HookNamespace] = ns_str; + jv[jss::Hooks][0U][jss::Hook] = iv; + env(jv, M("Delete namespace"), HSFEE); + env.close(); + + // Verify state cleanup + BEAST_EXPECT( + (*env.le(carol))[sfOwnerCount] == features[fixNSDelete] ? 1 + : 26); + BEAST_EXPECT(!env.le(carol)->isFieldPresent(sfHookStateCount)); } } @@ -9028,12 +9138,16 @@ public: auto const david = Account{"david"}; auto const eve = Account{"eve"}; // small balance auto const frank = Account{"frank"}; // big balance + auto const gary = Account{"gary"}; + auto const hank = Account{"hank"}; env.fund(XRP(10000), alice); env.fund(XRP(10000), bob); env.fund(XRP(10000), cho); env.fund(XRP(1000000), david); env.fund(XRP(2600), eve); env.fund(XRP(1000000000), frank); + env.fund(XRP(10000), gary); + env.fund(XRP(10000), hank); // install a rollback hook on cho env(ripple::test::jtx::hook( @@ -9058,6 +9172,101 @@ public: BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 0); } + // bounds and buffer size checks + { + TestHook hook = wasm[R"[test.hook]( + #include + 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); + extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + extern int64_t state_set ( + uint32_t read_ptr, + uint32_t read_len, + uint32_t kread_ptr, + uint32_t kread_len + ); + extern int64_t otxn_param(uint32_t, uint32_t, uint32_t, uint32_t); + + #define ASSERT(x)\ + if (!(x))\ + rollback((uint32_t)#x, sizeof(#x), __LINE__); + #define TOO_SMALL (-4) + #define TOO_BIG (-3) + #define OUT_OF_BOUNDS (-1) + + int64_t hook(uint32_t reserved) + { + _g(1,1); + + // bounds and buffer size checks + { + // RH NOTE: readptr/len 0/0 = delete entry + + ASSERT(state_set(0,0,0,0) == TOO_SMALL); + ASSERT(state_set(0,0,0,33) == TOO_BIG); + ASSERT(state_set(0,0,0,1000000) == TOO_BIG); + ASSERT(state_set(0,0,1000000,1) == OUT_OF_BOUNDS); + + ASSERT(state_set(0,1000000, 0, 32) == OUT_OF_BOUNDS); + ASSERT(state_set(1000000, 0, 0, 32) == OUT_OF_BOUNDS); + + uint16_t size; + ASSERT(otxn_param(&size, 2, "SIZE", 4) > 0); + ASSERT(state_set(0, size, 0, 32) == TOO_BIG); + } + accept(0,0,0); + } + )[test.hook]"]; + + // install the hook on alice + env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + M("set state_set 1"), + HSFEE); + env.close(); + + BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1); + + // invoke the hook with cho (rollback after alice's hooks have + // executed) + Json::Value payJv1 = pay(alice, cho, XRP(1)); + { + Json::Value params{Json::arrayValue}; + params[0U][jss::HookParameter][jss::HookParameterName] = + strHex(std::string("SIZE")); + params[0U][jss::HookParameter][jss::HookParameterValue] = + features[featureExtendedHookState] ? "0108" /* 2049 */ + : "0101" /* 257 */; + payJv1[jss::HookParameters] = params; + } + env(payJv1, + M("test state_set 1 rollback"), + fee(XRP(1)), + ter(tecHOOK_REJECTED)); + + BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1); + + auto const nsdir = env.le(nsdirkl); + BEAST_EXPECT(!nsdir); + + auto const state1 = env.le( + ripple::keylet::hookState(aliceid, beast::zero, beast::zero)); + BEAST_EXPECT(!state1); + + // invoke the hook from bob to alice, this will work + Json::Value payJv2 = pay(bob, alice, XRP(1)); + { + Json::Value params{Json::arrayValue}; + params[0U][jss::HookParameter][jss::HookParameterName] = + strHex(std::string("SIZE")); + params[0U][jss::HookParameter][jss::HookParameterValue] = + features[featureExtendedHookState] ? "0108" /* 2049 */ + : "0101" /* 257 */; + payJv2[jss::HookParameters] = params; + } + env(payJv2, M("test state_set 1"), fee(XRP(1))); + env.close(); + } + // first hook will set two state objects with different keys and data on // alice { @@ -9094,32 +9303,12 @@ public: #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); - #define TOO_SMALL (-4) - #define TOO_BIG (-3) - #define OUT_OF_BOUNDS (-1) #define SBUF(x) (uint32_t)(x), sizeof(x) - int64_t hook(uint32_t reserved ) + int64_t hook(uint32_t reserved) { _g(1,1); - - // bounds and buffer size checks - { - // RH NOTE: readptr/len 0/0 = delete entry - - ASSERT(state_set(0,0,0,0) == TOO_SMALL); - ASSERT(state_set(0,0,0,33) == TOO_BIG); - ASSERT(state_set(0,0,0,1000000) == TOO_BIG); - ASSERT(state_set(0,0,1000000,1) == OUT_OF_BOUNDS); - - ASSERT(state_set(0,1000000, 0, 32) == OUT_OF_BOUNDS); - ASSERT(state_set(1000000, 0, 0, 32) == OUT_OF_BOUNDS); - - ASSERT(state_set(0, 257, 0, 32) == TOO_BIG); - } - - // create state 1 { uint8_t key[32] = @@ -9130,27 +9319,24 @@ public: 0,0,0,0,0,0,0,0 }; - uint8_t data[4] = + uint8_t data[4] = { 0xCAU,0xFEU,0xBAU,0xBEU }; - ASSERT(state_set(SBUF(data), SBUF(key)) == sizeof(data)); } - // create state 2 + // create state 2 { uint8_t key[3] = { 1,2,3 }; - ASSERT(state_set(SBUF(data2), SBUF(key)) == sizeof(data2)); } - accept(0,0,0); } @@ -9719,6 +9905,210 @@ public: BEAST_EXPECT((*env.le("frank"))[sfOwnerCount] == 260); } + if (env.current()->rules().enabled(featureExtendedHookState)) + { + // Test hook with scaled state data + TestHook scaled_state_wasm = wasm[ + R"[test.hook]( + #include + 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); extern int64_t rollback + (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + extern int64_t state_set (uint32_t read_ptr, uint32_t + read_len, uint32_t kread_ptr, uint32_t kread_len); + + extern int64_t util_keylet(uint32_t, uint32_t, uint32_t, + uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); + extern int64_t slot_set(uint32_t, uint32_t, uint32_t); + extern int64_t slot_subfield(uint32_t, uint32_t, uint32_t); + extern int64_t slot(uint32_t, uint32_t, uint32_t); + extern int64_t hook_account(uint32_t, uint32_t); + extern int64_t util_keylet(uint32_t, uint32_t, uint32_t, + uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); + + #define SBUF(x) x, sizeof(x) + #define TOO_BIG -3 + #define DOESNT_EXIST -5 + #define KEYLET_ACCOUNT 3 + + #define sfHookStateScale ((1U << 16U) + 21U) + + #define ASSERT(x)\ + if (!(x))\ + rollback((uint32_t)#x,sizeof(#x),__LINE__) + int64_t hook(uint32_t reserved ) + { + _g(1,1); + + uint8_t hook_acc[20]; + ASSERT(hook_account(hook_acc, 20) == 20); + uint8_t account_keylet[34]; + ASSERT(util_keylet(account_keylet, 34, KEYLET_ACCOUNT, + hook_acc, 20, 0,0,0,0) == 34); + + ASSERT(slot_set(account_keylet, 34, 1) == 1); + slot_subfield(1, sfHookStateScale, 2); + int64_t scale = slot(0,0,2); + + if (scale == DOESNT_EXIST) { + ASSERT(state_set(0, 256, SBUF("test0")) == 256); + ASSERT(state_set(0, 257, SBUF("test")) == TOO_BIG); + accept(0,0,scale); + } + if (scale == 2) { + ASSERT(state_set(0, 256, SBUF("test1")) == 256); + ASSERT(state_set(0, 256*2, SBUF("test2")) == 256*2); + ASSERT(state_set(0, 256*2+1, SBUF("test")) == + TOO_BIG); accept(0,0,scale); + } + if (scale == 5) { + ASSERT(state_set(0, 256, SBUF("test3")) == 256); + ASSERT(state_set(0, 256*5, SBUF("test4")) == 256*5); + ASSERT(state_set(0, 256*5+1, SBUF("test")) == + TOO_BIG); accept(0,0,scale); + } + rollback(0,0,scale); + } + )[test.hook]"]; + + HASH_WASM(scaled_state); + BEAST_EXPECT(!env.le(gary)->isFieldPresent(sfHookStateCount)); + + // Install hook on carol + Json::Value jv = + ripple::test::jtx::hook(gary, {{hso(scaled_state_wasm)}}, 0); + // jv[jss::Hooks][0U][jss::Hook][jss::HookNamespace] = ns_str; + jv[jss::Hooks][0U][jss::Hook][jss::HookOn] = + to_string(UINT256_BIT[ttACCOUNT_SET]); + env(jv, M("Create scaled state hook"), HSFEE, ter(tesSUCCESS)); + env.close(); + + BEAST_EXPECT((*env.le(gary))[sfOwnerCount] == 1); + BEAST_EXPECT(!env.le(gary)->isFieldPresent(sfHookStateCount)); + + { + // no HookStateScale + Json::Value invoke = invoke::invoke(gary); + env(invoke, HSFEE); + env.close(); + BEAST_EXPECT((*env.le(gary))[sfOwnerCount] == 2); + BEAST_EXPECT((*env.le(gary))[sfHookStateCount] == 1); + } + + { + // HookStateScale => 2 + Json::Value jv = noop(gary); + jv[sfHookStateScale.fieldName] = 2; + env(jv, HSFEE); + env.close(); + BEAST_EXPECT((*env.le(gary))[sfOwnerCount] == 3); + BEAST_EXPECT((*env.le(gary))[sfHookStateCount] == 1); + + Json::Value invoke = invoke::invoke(gary); + env(invoke, HSFEE); + env.close(); + BEAST_EXPECT((*env.le(gary))[sfOwnerCount] == 7); + BEAST_EXPECT((*env.le(gary))[sfHookStateCount] == 3); + } + { + // HookStateScale => 5 + Json::Value jv = noop(gary); + jv[sfHookStateScale.fieldName] = 5; + env(jv, HSFEE); + env.close(); + BEAST_EXPECT((*env.le(gary))[sfOwnerCount] == 16); + BEAST_EXPECT((*env.le(gary))[sfHookStateCount] == 3); + + Json::Value invoke = invoke::invoke(gary); + env(invoke, HSFEE); + env.close(); + BEAST_EXPECT((*env.le(gary))[sfOwnerCount] == 26); + BEAST_EXPECT((*env.le(gary))[sfHookStateCount] == 5); + } + } + + { + bool extHookStateEnabled = features[featureExtendedHookState]; + // tests for set_state_cache + if (extHookStateEnabled) + { + TestHook extended_state_reserve_hook = wasm[R"[test.hook]( + #include + 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); + extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + extern int64_t state_set ( + uint32_t read_ptr, + uint32_t read_len, + uint32_t kread_ptr, + uint32_t kread_len + ); + extern int64_t state_foreign_set ( + uint32_t read_ptr, + uint32_t read_len, + uint32_t kread_ptr, + uint32_t kread_len, + uint32_t nread_ptr, + uint32_t nread_len, + uint32_t aread_ptr, + uint32_t aread_len + ); + extern int64_t otxn_param(uint32_t, uint32_t, uint32_t, uint32_t); + #define RESERVE_INSUFFICIENT -38 + #define ASSERT(x)\ + if (!(x))\ + rollback((uint32_t)#x, sizeof(#x), __LINE__); + #define ASSERT_EQUAL(x, y)\ + if (!(x == y))\ + rollback((uint32_t)#x, sizeof(#x), x); + int64_t hook(uint32_t reserved) + { + _g(1,1); + { + // 1. first account for StateMap + ASSERT_EQUAL(state_set(0, 1, "1", 1), RESERVE_INSUFFICIENT); + // 2. first namespace for StateMap + ASSERT_EQUAL(state_foreign_set(0, 1, "1", 1, "1", 32, 0, 0), RESERVE_INSUFFICIENT); + // 3. first statekey for StateMap + ASSERT_EQUAL(state_set(0, 1, "2", 1), RESERVE_INSUFFICIENT); + // 4. existing statedata + ASSERT_EQUAL(state_set(0, 1, "1", 1), RESERVE_INSUFFICIENT); + } + accept(0,0,0); + } + )[test.hook]"]; + + // install the hook on gary + Json::Value jv = hso(extended_state_reserve_hook, overrideFlag); + jv[jss::HookOn] = + "fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff" + "bfffff"; // only invoke high + env(ripple::test::jtx::hook(hank, {{jv}}, 0), HSFEE); + env.close(); + + Json::Value jv1 = noop(hank); + jv1[sfHookStateScale.fieldName] = 8; + env(jv1, HSFEE); + env.close(); + + auto const caller = Account{"caller"}; + env.fund(XRP(10000), caller); + env.close(); + auto const payAmount = env.balance(hank) - + (env.current()->fees().accountReserve(1 + 8)) - + drops(1); // 8 + Hook + // reduce hank's balance + env(pay(hank, Account{"master"}, payAmount), fee(XRP(1))); + env.close(); + + // invoke the hook from alice + Json::Value invokeJv5 = invoke::invoke(caller, hank, ""); + env(invokeJv5, M("test state_set 15"), fee(XRP(1))); + env.close(); + } + } + // RH TODO: // check state can be set on emit callback // check namespacing provides for non-collision of same key @@ -12884,14 +13274,17 @@ public: using namespace test::jtx; static FeatureBitset const all{supported_amendments()}; - static std::array const feats{ + static std::array const feats{ all, all - fixXahauV2, all - fixXahauV1 - fixXahauV2, all - fixXahauV1 - fixXahauV2 - fixNSDelete, all - fixXahauV1 - fixXahauV2 - fixNSDelete - fixPageCap, all - fixXahauV1 - fixXahauV2 - fixNSDelete - fixPageCap - - featureHookCanEmit}; + featureHookCanEmit, + all - fixXahauV1 - fixXahauV2 - fixNSDelete - fixPageCap - + featureExtendedHookState, + }; if (BEAST_EXPECT(instance < feats.size())) { @@ -13058,7 +13451,8 @@ SETHOOK_TEST(1, false) SETHOOK_TEST(2, false) SETHOOK_TEST(3, false) SETHOOK_TEST(4, false) -SETHOOK_TEST(5, true) +SETHOOK_TEST(5, false) +SETHOOK_TEST(6, true) BEAST_DEFINE_TESTSUITE_PRIO(SetHook0, app, ripple, 2); BEAST_DEFINE_TESTSUITE_PRIO(SetHook1, app, ripple, 2); @@ -13066,6 +13460,7 @@ BEAST_DEFINE_TESTSUITE_PRIO(SetHook2, app, ripple, 2); BEAST_DEFINE_TESTSUITE_PRIO(SetHook3, app, ripple, 2); BEAST_DEFINE_TESTSUITE_PRIO(SetHook4, app, ripple, 2); BEAST_DEFINE_TESTSUITE_PRIO(SetHook5, app, ripple, 2); +BEAST_DEFINE_TESTSUITE_PRIO(SetHook6, app, ripple, 2); } // namespace test } // namespace ripple #undef M diff --git a/src/test/app/SetHook_wasm.h b/src/test/app/SetHook_wasm.h index a7e75c827..626e4a849 100644 --- a/src/test/app/SetHook_wasm.h +++ b/src/test/app/SetHook_wasm.h @@ -10,6 +10,187 @@ namespace ripple { namespace test { std::map> wasm = { /* ==== WASM: 0 ==== */ + {R"[test.hook]( + #include + 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); extern int64_t rollback + (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + extern int64_t state_set (uint32_t read_ptr, uint32_t + read_len, uint32_t kread_ptr, uint32_t kread_len); + + extern int64_t util_keylet(uint32_t, uint32_t, uint32_t, + uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); + extern int64_t slot_set(uint32_t, uint32_t, uint32_t); + extern int64_t slot_subfield(uint32_t, uint32_t, uint32_t); + extern int64_t slot(uint32_t, uint32_t, uint32_t); + extern int64_t hook_account(uint32_t, uint32_t); + extern int64_t util_keylet(uint32_t, uint32_t, uint32_t, + uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); + + #define SBUF(x) x, sizeof(x) + #define TOO_BIG -3 + #define DOESNT_EXIST -5 + #define KEYLET_ACCOUNT 3 + + #define sfHookStateScale ((1U << 16U) + 21U) + + #define ASSERT(x)\ + if (!(x))\ + rollback((uint32_t)#x,sizeof(#x),__LINE__) + int64_t hook(uint32_t reserved ) + { + _g(1,1); + + uint8_t hook_acc[20]; + ASSERT(hook_account(hook_acc, 20) == 20); + uint8_t account_keylet[34]; + ASSERT(util_keylet(account_keylet, 34, KEYLET_ACCOUNT, + hook_acc, 20, 0,0,0,0) == 34); + + ASSERT(slot_set(account_keylet, 34, 1) == 1); + slot_subfield(1, sfHookStateScale, 2); + int64_t scale = slot(0,0,2); + + if (scale == 5) { + ASSERT(state_set(0, 256, SBUF("test1")) == 256); + ASSERT(state_set(0, 256*2, SBUF("test2")) == 256*2); + ASSERT(state_set(0, 256*3, SBUF("test3")) == 256*3); + ASSERT(state_set(0, 256*4, SBUF("test4")) == 256*4); + ASSERT(state_set(0, 256*5, SBUF("test5")) == 256*5); + ASSERT(state_set(0, 256*5+1, SBUF("test")) == TOO_BIG); + accept(0,0,scale); + } + rollback(0,0,scale); + } + )[test.hook]", + { + 0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, 0x35U, + 0x07U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x02U, 0x7FU, + 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7EU, 0x01U, 0x7EU, + 0x60U, 0x09U, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU, + 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, + 0x60U, 0x04U, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x01U, + 0x7FU, 0x01U, 0x7EU, 0x02U, 0x89U, 0x01U, 0x09U, 0x03U, 0x65U, 0x6EU, + 0x76U, 0x02U, 0x5FU, 0x67U, 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, + 0x0CU, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x61U, 0x63U, 0x63U, 0x6FU, + 0x75U, 0x6EU, 0x74U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x08U, + 0x72U, 0x6FU, 0x6CU, 0x6CU, 0x62U, 0x61U, 0x63U, 0x6BU, 0x00U, 0x02U, + 0x03U, 0x65U, 0x6EU, 0x76U, 0x0BU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, + 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x00U, 0x03U, 0x03U, 0x65U, + 0x6EU, 0x76U, 0x08U, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x73U, 0x65U, + 0x74U, 0x00U, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0DU, 0x73U, 0x6CU, + 0x6FU, 0x74U, 0x5FU, 0x73U, 0x75U, 0x62U, 0x66U, 0x69U, 0x65U, 0x6CU, + 0x64U, 0x00U, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x04U, 0x73U, 0x6CU, + 0x6FU, 0x74U, 0x00U, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x09U, 0x73U, + 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x73U, 0x65U, 0x74U, 0x00U, 0x05U, + 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, + 0x74U, 0x00U, 0x02U, 0x03U, 0x02U, 0x01U, 0x06U, 0x05U, 0x03U, 0x01U, + 0x00U, 0x02U, 0x06U, 0x21U, 0x05U, 0x7FU, 0x01U, 0x41U, 0xC0U, 0x8BU, + 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0xBDU, 0x0BU, 0x0BU, 0x7FU, 0x00U, + 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0xC0U, 0x8BU, 0x04U, + 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x07U, 0x08U, 0x01U, + 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x09U, 0x0AU, 0x81U, 0x84U, + 0x00U, 0x01U, 0xFDU, 0x83U, 0x00U, 0x02U, 0x01U, 0x7FU, 0x01U, 0x7EU, + 0x23U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x41U, 0xD0U, 0x00U, 0x6BU, + 0x22U, 0x01U, 0x24U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x41U, 0x01U, + 0x41U, 0x01U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x02U, + 0x40U, 0x20U, 0x01U, 0x41U, 0x30U, 0x6AU, 0x41U, 0x14U, 0x10U, 0x81U, + 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x14U, 0x51U, 0x0DU, 0x00U, 0x41U, + 0x80U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x21U, 0x42U, 0x22U, 0x10U, + 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x20U, + 0x01U, 0x41U, 0x22U, 0x41U, 0x03U, 0x20U, 0x01U, 0x41U, 0x30U, 0x6AU, + 0x41U, 0x14U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, + 0x10U, 0x83U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x22U, 0x51U, 0x0DU, + 0x00U, 0x41U, 0xA1U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0xCDU, 0x00U, + 0x42U, 0x25U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, + 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0x22U, 0x41U, 0x01U, 0x10U, 0x84U, + 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x01U, 0x51U, 0x0DU, 0x00U, 0x41U, + 0xEEU, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x25U, 0x42U, 0x27U, 0x10U, + 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x41U, 0x01U, 0x41U, + 0x95U, 0x80U, 0x04U, 0x41U, 0x02U, 0x10U, 0x85U, 0x80U, 0x80U, 0x80U, + 0x00U, 0x1AU, 0x02U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x02U, + 0x10U, 0x86U, 0x80U, 0x80U, 0x80U, 0x00U, 0x22U, 0x02U, 0x42U, 0x05U, + 0x52U, 0x0DU, 0x00U, 0x02U, 0x40U, 0x41U, 0x00U, 0x41U, 0x80U, 0x02U, + 0x41U, 0x93U, 0x89U, 0x80U, 0x80U, 0x00U, 0x41U, 0x06U, 0x10U, 0x87U, + 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x80U, 0x02U, 0x51U, 0x0DU, 0x00U, + 0x41U, 0x99U, 0x89U, 0x80U, 0x80U, 0x00U, 0x41U, 0x28U, 0x42U, 0x2CU, + 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, + 0x41U, 0x00U, 0x41U, 0x80U, 0x04U, 0x41U, 0xC1U, 0x89U, 0x80U, 0x80U, + 0x00U, 0x41U, 0x06U, 0x10U, 0x87U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, + 0x80U, 0x04U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xC7U, 0x89U, 0x80U, 0x80U, + 0x00U, 0x41U, 0x2CU, 0x42U, 0x2DU, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, + 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x41U, 0x00U, 0x41U, 0x80U, 0x06U, + 0x41U, 0xF3U, 0x89U, 0x80U, 0x80U, 0x00U, 0x41U, 0x06U, 0x10U, 0x87U, + 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x80U, 0x06U, 0x51U, 0x0DU, 0x00U, + 0x41U, 0xF9U, 0x89U, 0x80U, 0x80U, 0x00U, 0x41U, 0x2CU, 0x42U, 0x2EU, + 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, + 0x41U, 0x00U, 0x41U, 0x80U, 0x08U, 0x41U, 0xA5U, 0x8AU, 0x80U, 0x80U, + 0x00U, 0x41U, 0x06U, 0x10U, 0x87U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, + 0x80U, 0x08U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xABU, 0x8AU, 0x80U, 0x80U, + 0x00U, 0x41U, 0x2CU, 0x42U, 0x2FU, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, + 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x41U, 0x00U, 0x41U, 0x80U, 0x0AU, + 0x41U, 0xD7U, 0x8AU, 0x80U, 0x80U, 0x00U, 0x41U, 0x06U, 0x10U, 0x87U, + 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x80U, 0x0AU, 0x51U, 0x0DU, 0x00U, + 0x41U, 0xDDU, 0x8AU, 0x80U, 0x80U, 0x00U, 0x41U, 0x2CU, 0x42U, 0x30U, + 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, + 0x41U, 0x00U, 0x41U, 0x81U, 0x0AU, 0x41U, 0x89U, 0x8BU, 0x80U, 0x80U, + 0x00U, 0x41U, 0x05U, 0x10U, 0x87U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, + 0x7DU, 0x51U, 0x0DU, 0x00U, 0x41U, 0x8EU, 0x8BU, 0x80U, 0x80U, 0x00U, + 0x41U, 0x2FU, 0x42U, 0x31U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, + 0x1AU, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x05U, 0x10U, 0x88U, + 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, + 0x20U, 0x02U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, + 0x01U, 0x41U, 0xD0U, 0x00U, 0x6AU, 0x24U, 0x80U, 0x80U, 0x80U, 0x80U, + 0x00U, 0x20U, 0x02U, 0x0BU, 0x0BU, 0xC5U, 0x03U, 0x01U, 0x00U, 0x41U, + 0x80U, 0x08U, 0x0BU, 0xBDU, 0x03U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, + 0x61U, 0x63U, 0x63U, 0x6FU, 0x75U, 0x6EU, 0x74U, 0x28U, 0x68U, 0x6FU, + 0x6FU, 0x6BU, 0x5FU, 0x61U, 0x63U, 0x63U, 0x2CU, 0x20U, 0x32U, 0x30U, + 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x32U, 0x30U, 0x00U, 0x75U, 0x74U, + 0x69U, 0x6CU, 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U, + 0x61U, 0x63U, 0x63U, 0x6FU, 0x75U, 0x6EU, 0x74U, 0x5FU, 0x6BU, 0x65U, + 0x79U, 0x6CU, 0x65U, 0x74U, 0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU, 0x20U, + 0x4BU, 0x45U, 0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x41U, 0x43U, 0x43U, + 0x4FU, 0x55U, 0x4EU, 0x54U, 0x2CU, 0x20U, 0x68U, 0x6FU, 0x6FU, 0x6BU, + 0x5FU, 0x61U, 0x63U, 0x63U, 0x2CU, 0x20U, 0x32U, 0x30U, 0x2CU, 0x20U, + 0x30U, 0x2CU, 0x30U, 0x2CU, 0x30U, 0x2CU, 0x30U, 0x29U, 0x20U, 0x3DU, + 0x3DU, 0x20U, 0x33U, 0x34U, 0x00U, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, + 0x73U, 0x65U, 0x74U, 0x28U, 0x61U, 0x63U, 0x63U, 0x6FU, 0x75U, 0x6EU, + 0x74U, 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x2CU, 0x20U, + 0x33U, 0x34U, 0x2CU, 0x20U, 0x31U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, + 0x31U, 0x00U, 0x74U, 0x65U, 0x73U, 0x74U, 0x31U, 0x00U, 0x73U, 0x74U, + 0x61U, 0x74U, 0x65U, 0x5FU, 0x73U, 0x65U, 0x74U, 0x28U, 0x30U, 0x2CU, + 0x20U, 0x32U, 0x35U, 0x36U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, + 0x28U, 0x22U, 0x74U, 0x65U, 0x73U, 0x74U, 0x31U, 0x22U, 0x29U, 0x29U, + 0x20U, 0x3DU, 0x3DU, 0x20U, 0x32U, 0x35U, 0x36U, 0x00U, 0x74U, 0x65U, + 0x73U, 0x74U, 0x32U, 0x00U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, + 0x73U, 0x65U, 0x74U, 0x28U, 0x30U, 0x2CU, 0x20U, 0x32U, 0x35U, 0x36U, + 0x2AU, 0x32U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x22U, + 0x74U, 0x65U, 0x73U, 0x74U, 0x32U, 0x22U, 0x29U, 0x29U, 0x20U, 0x3DU, + 0x3DU, 0x20U, 0x32U, 0x35U, 0x36U, 0x2AU, 0x32U, 0x00U, 0x74U, 0x65U, + 0x73U, 0x74U, 0x33U, 0x00U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, + 0x73U, 0x65U, 0x74U, 0x28U, 0x30U, 0x2CU, 0x20U, 0x32U, 0x35U, 0x36U, + 0x2AU, 0x33U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x22U, + 0x74U, 0x65U, 0x73U, 0x74U, 0x33U, 0x22U, 0x29U, 0x29U, 0x20U, 0x3DU, + 0x3DU, 0x20U, 0x32U, 0x35U, 0x36U, 0x2AU, 0x33U, 0x00U, 0x74U, 0x65U, + 0x73U, 0x74U, 0x34U, 0x00U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, + 0x73U, 0x65U, 0x74U, 0x28U, 0x30U, 0x2CU, 0x20U, 0x32U, 0x35U, 0x36U, + 0x2AU, 0x34U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x22U, + 0x74U, 0x65U, 0x73U, 0x74U, 0x34U, 0x22U, 0x29U, 0x29U, 0x20U, 0x3DU, + 0x3DU, 0x20U, 0x32U, 0x35U, 0x36U, 0x2AU, 0x34U, 0x00U, 0x74U, 0x65U, + 0x73U, 0x74U, 0x35U, 0x00U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, + 0x73U, 0x65U, 0x74U, 0x28U, 0x30U, 0x2CU, 0x20U, 0x32U, 0x35U, 0x36U, + 0x2AU, 0x35U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x22U, + 0x74U, 0x65U, 0x73U, 0x74U, 0x35U, 0x22U, 0x29U, 0x29U, 0x20U, 0x3DU, + 0x3DU, 0x20U, 0x32U, 0x35U, 0x36U, 0x2AU, 0x35U, 0x00U, 0x74U, 0x65U, + 0x73U, 0x74U, 0x00U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x73U, + 0x65U, 0x74U, 0x28U, 0x30U, 0x2CU, 0x20U, 0x32U, 0x35U, 0x36U, 0x2AU, + 0x35U, 0x2BU, 0x31U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, + 0x22U, 0x74U, 0x65U, 0x73U, 0x74U, 0x22U, 0x29U, 0x29U, 0x20U, 0x3DU, + 0x3DU, 0x20U, 0x54U, 0x4FU, 0x4FU, 0x5FU, 0x42U, 0x49U, 0x47U, 0x00U, + }}, + + /* ==== WASM: 1 ==== */ {R"[test.hook]( (module (type (;0;) (func (param i32 i32) (result i64))) @@ -59,7 +240,7 @@ std::map> wasm = { 0x0BU, }}, - /* ==== WASM: 1 ==== */ + /* ==== WASM: 2 ==== */ {R"[test.hook]( (module (type (;0;) (func (param i32 i32) (result i64))) @@ -113,7 +294,7 @@ std::map> wasm = { 0x14U, 0x10U, 0x00U, 0x1AU, 0x0CU, 0x00U, 0x0BU, 0x0BU, }}, - /* ==== WASM: 2 ==== */ + /* ==== WASM: 3 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -180,7 +361,7 @@ std::map> wasm = { 0x80U, 0x80U, 0x00U, 0x20U, 0x02U, 0x0BU, }}, - /* ==== WASM: 3 ==== */ + /* ==== WASM: 4 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -286,7 +467,7 @@ std::map> wasm = { 0x6AU, 0x24U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x20U, 0x03U, 0x0BU, }}, - /* ==== WASM: 4 ==== */ + /* ==== WASM: 5 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -361,7 +542,7 @@ std::map> wasm = { 0x6AU, 0x24U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x20U, 0x05U, 0x0BU, }}, - /* ==== WASM: 5 ==== */ + /* ==== WASM: 6 ==== */ {R"[test.hook]( #include extern int32_t _g(uint32_t, uint32_t); @@ -978,7 +1159,7 @@ std::map> wasm = { 0x78U, 0x29U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x33U, 0x32U, 0x00U, }}, - /* ==== WASM: 6 ==== */ + /* ==== WASM: 7 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -1083,7 +1264,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 7 ==== */ + /* ==== WASM: 8 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -1180,7 +1361,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 8 ==== */ + /* ==== WASM: 9 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -1303,7 +1484,7 @@ std::map> wasm = { 0x4EU, 0x59U, 0x5FU, 0x4EU, 0x4FU, 0x4EU, 0x43U, 0x45U, 0x53U, 0x00U, }}, - /* ==== WASM: 9 ==== */ + /* ==== WASM: 10 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -1383,7 +1564,7 @@ std::map> wasm = { 0x54U, 0x00U, }}, - /* ==== WASM: 10 ==== */ + /* ==== WASM: 11 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -1428,7 +1609,7 @@ std::map> wasm = { 0x30U, 0x00U, }}, - /* ==== WASM: 11 ==== */ + /* ==== WASM: 12 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -1747,7 +1928,7 @@ std::map> wasm = { 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, }}, - /* ==== WASM: 12 ==== */ + /* ==== WASM: 13 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -2533,7 +2714,7 @@ std::map> wasm = { 0x37U, 0x36U, 0x33U, 0x4CU, 0x4CU, 0x20U, 0x29U, 0x00U, }}, - /* ==== WASM: 13 ==== */ + /* ==== WASM: 14 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -2923,7 +3104,7 @@ std::map> wasm = { 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, }}, - /* ==== WASM: 14 ==== */ + /* ==== WASM: 15 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -3104,7 +3285,7 @@ std::map> wasm = { 0x38U, 0x4CU, 0x4CU, 0x29U, 0x00U, }}, - /* ==== WASM: 15 ==== */ + /* ==== WASM: 16 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -3293,7 +3474,7 @@ std::map> wasm = { 0x29U, 0x00U, }}, - /* ==== WASM: 16 ==== */ + /* ==== WASM: 17 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -3532,7 +3713,7 @@ std::map> wasm = { 0x00U, 0x42U, 0x00U, 0x10U, 0x85U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, }}, - /* ==== WASM: 17 ==== */ + /* ==== WASM: 18 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -4234,7 +4415,7 @@ std::map> wasm = { 0x38U, 0x35U, 0x35U, 0x32U, 0x55U, 0x29U, 0x00U, }}, - /* ==== WASM: 18 ==== */ + /* ==== WASM: 19 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -5579,7 +5760,7 @@ std::map> wasm = { 0x20U, 0x29U, 0x00U, }}, - /* ==== WASM: 19 ==== */ + /* ==== WASM: 20 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -5681,7 +5862,7 @@ std::map> wasm = { 0x84U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, }}, - /* ==== WASM: 20 ==== */ + /* ==== WASM: 21 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -5724,7 +5905,7 @@ std::map> wasm = { 0x00U, 0x0BU, }}, - /* ==== WASM: 21 ==== */ + /* ==== WASM: 22 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -5872,7 +6053,7 @@ std::map> wasm = { 0x34U, 0x34U, 0x4CU, 0x4CU, 0x2CU, 0x20U, 0x33U, 0x29U, 0x00U, }}, - /* ==== WASM: 22 ==== */ + /* ==== WASM: 23 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -6200,7 +6381,7 @@ std::map> wasm = { 0x38U, 0x34U, 0x39U, 0x30U, 0x4CU, 0x4CU, 0x00U, }}, - /* ==== WASM: 23 ==== */ + /* ==== WASM: 24 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -6405,7 +6586,7 @@ std::map> wasm = { 0x10U, 0x85U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, }}, - /* ==== WASM: 24 ==== */ + /* ==== WASM: 25 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -7089,7 +7270,7 @@ std::map> wasm = { 0x20U, 0x3DU, 0x3DU, 0x20U, 0x30U, 0x00U, }}, - /* ==== WASM: 25 ==== */ + /* ==== WASM: 26 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -7384,7 +7565,7 @@ std::map> wasm = { 0x32U, 0x34U, 0x31U, 0x36U, 0x55U, 0x4CU, 0x4CU, 0x00U, }}, - /* ==== WASM: 26 ==== */ + /* ==== WASM: 27 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -8097,7 +8278,7 @@ std::map> wasm = { 0x31U, 0x33U, 0x33U, 0x38U, 0x20U, 0x29U, 0x00U, }}, - /* ==== WASM: 27 ==== */ + /* ==== WASM: 28 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -8182,7 +8363,7 @@ std::map> wasm = { 0x30U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x32U, 0x30U, 0x00U, }}, - /* ==== WASM: 28 ==== */ + /* ==== WASM: 29 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -8244,7 +8425,7 @@ std::map> wasm = { 0x80U, 0x80U, 0x00U, 0x0BU, }}, - /* ==== WASM: 29 ==== */ + /* ==== WASM: 30 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -8321,7 +8502,7 @@ std::map> wasm = { 0x31U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x33U, 0x32U, 0x00U, }}, - /* ==== WASM: 30 ==== */ + /* ==== WASM: 31 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -8398,7 +8579,7 @@ std::map> wasm = { 0x31U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x33U, 0x32U, 0x00U, }}, - /* ==== WASM: 31 ==== */ + /* ==== WASM: 32 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -8655,7 +8836,7 @@ std::map> wasm = { 0x00U, 0x00U, }}, - /* ==== WASM: 32 ==== */ + /* ==== WASM: 33 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -8813,7 +8994,7 @@ std::map> wasm = { 0x04U, 0x00U, 0x00U, }}, - /* ==== WASM: 33 ==== */ + /* ==== WASM: 34 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -9166,7 +9347,7 @@ std::map> wasm = { 0x00U, 0x2AU, 0x04U, 0x00U, 0x00U, 0x31U, 0x04U, 0x00U, 0x00U, }}, - /* ==== WASM: 34 ==== */ + /* ==== WASM: 35 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -9199,7 +9380,7 @@ std::map> wasm = { 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x0BU, }}, - /* ==== WASM: 35 ==== */ + /* ==== WASM: 36 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -9426,7 +9607,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 36 ==== */ + /* ==== WASM: 37 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -9457,7 +9638,7 @@ std::map> wasm = { 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x0BU, }}, - /* ==== WASM: 37 ==== */ + /* ==== WASM: 38 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -9768,7 +9949,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 38 ==== */ + /* ==== WASM: 39 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -9845,7 +10026,7 @@ std::map> wasm = { 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x33U, 0x32U, 0x00U, }}, - /* ==== WASM: 39 ==== */ + /* ==== WASM: 40 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -9879,7 +10060,7 @@ std::map> wasm = { 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x0BU, }}, - /* ==== WASM: 40 ==== */ + /* ==== WASM: 41 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -9966,7 +10147,7 @@ std::map> wasm = { 0x32U, 0x00U, }}, - /* ==== WASM: 41 ==== */ + /* ==== WASM: 42 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -10000,7 +10181,7 @@ std::map> wasm = { 0x0BU, }}, - /* ==== WASM: 42 ==== */ + /* ==== WASM: 43 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -10140,7 +10321,7 @@ std::map> wasm = { 0x54U, 0x5FU, 0x4DU, 0x45U, 0x54U, 0x00U, }}, - /* ==== WASM: 43 ==== */ + /* ==== WASM: 44 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -10330,7 +10511,7 @@ std::map> wasm = { 0x31U, 0x34U, 0x00U, }}, - /* ==== WASM: 44 ==== */ + /* ==== WASM: 45 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -10484,7 +10665,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 45 ==== */ + /* ==== WASM: 46 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -10645,7 +10826,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 46 ==== */ + /* ==== WASM: 47 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -10802,7 +10983,7 @@ std::map> wasm = { 0x53U, 0x4CU, 0x4FU, 0x54U, 0x53U, 0x00U, }}, - /* ==== WASM: 47 ==== */ + /* ==== WASM: 48 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -10887,7 +11068,7 @@ std::map> wasm = { 0x74U, 0x79U, 0x70U, 0x65U, 0x28U, 0x29U, 0x00U, }}, - /* ==== WASM: 48 ==== */ + /* ==== WASM: 49 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -11144,7 +11325,7 @@ std::map> wasm = { 0x00U, 0x00U, }}, - /* ==== WASM: 49 ==== */ + /* ==== WASM: 50 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -11382,7 +11563,7 @@ std::map> wasm = { 0x3EU, 0x20U, 0x30U, 0x00U, }}, - /* ==== WASM: 50 ==== */ + /* ==== WASM: 51 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -11478,7 +11659,7 @@ std::map> wasm = { 0x53U, 0x54U, 0x00U, }}, - /* ==== WASM: 51 ==== */ + /* ==== WASM: 52 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -11588,7 +11769,7 @@ std::map> wasm = { 0x20U, 0x31U, 0x00U, }}, - /* ==== WASM: 52 ==== */ + /* ==== WASM: 53 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -11718,7 +11899,7 @@ std::map> wasm = { 0x30U, 0x30U, 0x30U, 0x4CU, 0x4CU, 0x00U, }}, - /* ==== WASM: 53 ==== */ + /* ==== WASM: 54 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -11992,7 +12173,7 @@ std::map> wasm = { 0x30U, 0x00U, }}, - /* ==== WASM: 54 ==== */ + /* ==== WASM: 55 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -12129,7 +12310,7 @@ std::map> wasm = { 0x2CU, 0x20U, 0x73U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x31U, 0x00U, }}, - /* ==== WASM: 55 ==== */ + /* ==== WASM: 56 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -12423,7 +12604,7 @@ std::map> wasm = { 0x5FU, 0x53U, 0x4CU, 0x4FU, 0x54U, 0x53U, 0x00U, }}, - /* ==== WASM: 56 ==== */ + /* ==== WASM: 57 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -12657,7 +12838,7 @@ std::map> wasm = { 0x5FU, 0x53U, 0x4CU, 0x4FU, 0x54U, 0x53U, 0x00U, }}, - /* ==== WASM: 57 ==== */ + /* ==== WASM: 58 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -12953,7 +13134,7 @@ std::map> wasm = { 0x2CU, 0x20U, 0x31U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x30U, 0x00U, }}, - /* ==== WASM: 58 ==== */ + /* ==== WASM: 59 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -13157,7 +13338,7 @@ std::map> wasm = { 0x6EU, 0x74U, 0x32U, 0x22U, 0x20U, 0x2BU, 0x20U, 0x69U, 0x29U, 0x00U, }}, - /* ==== WASM: 59 ==== */ + /* ==== WASM: 60 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -13274,7 +13455,7 @@ std::map> wasm = { 0x69U, 0x29U, 0x00U, }}, - /* ==== WASM: 60 ==== */ + /* ==== WASM: 61 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -13383,7 +13564,7 @@ std::map> wasm = { 0x6EU, 0x74U, 0x65U, 0x6EU, 0x74U, 0x32U, 0x22U, 0x29U, 0x00U, }}, - /* ==== WASM: 61 ==== */ + /* ==== WASM: 62 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -13656,7 +13837,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 62 ==== */ + /* ==== WASM: 63 ==== */ {R"[test.hook]( #include #define sfInvoiceID ((5U << 16U) + 17U) @@ -13875,7 +14056,7 @@ std::map> wasm = { 0x30U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x33U, 0x32U, 0x00U, }}, - /* ==== WASM: 63 ==== */ + /* ==== WASM: 64 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -13987,7 +14168,7 @@ std::map> wasm = { 0x58U, 0x49U, 0x53U, 0x54U, 0x00U, }}, - /* ==== WASM: 64 ==== */ + /* ==== WASM: 65 ==== */ {R"[test.hook]( #include #define sfInvoiceID ((5U << 16U) + 17U) @@ -14123,7 +14304,143 @@ std::map> wasm = { 0x3DU, 0x20U, 0x33U, 0x32U, 0x00U, }}, - /* ==== WASM: 65 ==== */ + /* ==== WASM: 66 ==== */ + {R"[test.hook]( + #include + 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); + extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + extern int64_t state_set ( + uint32_t read_ptr, + uint32_t read_len, + uint32_t kread_ptr, + uint32_t kread_len + ); + extern int64_t otxn_param(uint32_t, uint32_t, uint32_t, uint32_t); + + #define ASSERT(x)\ + if (!(x))\ + rollback((uint32_t)#x, sizeof(#x), __LINE__); + #define TOO_SMALL (-4) + #define TOO_BIG (-3) + #define OUT_OF_BOUNDS (-1) + + int64_t hook(uint32_t reserved) + { + _g(1,1); + + // bounds and buffer size checks + { + // RH NOTE: readptr/len 0/0 = delete entry + + ASSERT(state_set(0,0,0,0) == TOO_SMALL); + ASSERT(state_set(0,0,0,33) == TOO_BIG); + ASSERT(state_set(0,0,0,1000000) == TOO_BIG); + ASSERT(state_set(0,0,1000000,1) == OUT_OF_BOUNDS); + + ASSERT(state_set(0,1000000, 0, 32) == OUT_OF_BOUNDS); + ASSERT(state_set(1000000, 0, 0, 32) == OUT_OF_BOUNDS); + + uint16_t size; + ASSERT(otxn_param(&size, 2, "SIZE", 4) > 0); + ASSERT(state_set(0, size, 0, 32) == TOO_BIG); + } + accept(0,0,0); + } + )[test.hook]", + { + 0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, 0x1BU, + 0x04U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x04U, 0x7FU, + 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7EU, + 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U, 0x47U, 0x05U, + 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, 0x00U, 0x00U, 0x03U, + 0x65U, 0x6EU, 0x76U, 0x09U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, + 0x73U, 0x65U, 0x74U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x08U, + 0x72U, 0x6FU, 0x6CU, 0x6CU, 0x62U, 0x61U, 0x63U, 0x6BU, 0x00U, 0x02U, + 0x03U, 0x65U, 0x6EU, 0x76U, 0x0AU, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, + 0x70U, 0x61U, 0x72U, 0x61U, 0x6DU, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, + 0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x02U, + 0x03U, 0x02U, 0x01U, 0x03U, 0x05U, 0x03U, 0x01U, 0x00U, 0x02U, 0x06U, + 0x21U, 0x05U, 0x7FU, 0x01U, 0x41U, 0xC0U, 0x8AU, 0x04U, 0x0BU, 0x7FU, + 0x00U, 0x41U, 0xB6U, 0x0AU, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, + 0x0BU, 0x7FU, 0x00U, 0x41U, 0xC0U, 0x8AU, 0x04U, 0x0BU, 0x7FU, 0x00U, + 0x41U, 0x80U, 0x08U, 0x0BU, 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, + 0x6FU, 0x6BU, 0x00U, 0x05U, 0x0AU, 0x8AU, 0x83U, 0x00U, 0x01U, 0x86U, + 0x83U, 0x00U, 0x02U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x23U, 0x80U, 0x80U, + 0x80U, 0x80U, 0x00U, 0x41U, 0x10U, 0x6BU, 0x22U, 0x01U, 0x24U, 0x80U, + 0x80U, 0x80U, 0x80U, 0x00U, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U, 0x80U, + 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x02U, 0x40U, 0x41U, 0x00U, 0x41U, + 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, + 0x00U, 0x42U, 0x7CU, 0x51U, 0x0DU, 0x00U, 0x41U, 0x80U, 0x88U, 0x80U, + 0x80U, 0x00U, 0x41U, 0x20U, 0x42U, 0x1DU, 0x10U, 0x82U, 0x80U, 0x80U, + 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, + 0x41U, 0x00U, 0x41U, 0x21U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, + 0x42U, 0x7DU, 0x51U, 0x0DU, 0x00U, 0x41U, 0xA0U, 0x88U, 0x80U, 0x80U, + 0x00U, 0x41U, 0x1FU, 0x42U, 0x1EU, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, + 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, + 0x00U, 0x41U, 0xC0U, 0x84U, 0x3DU, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, + 0x00U, 0x42U, 0x7DU, 0x51U, 0x0DU, 0x00U, 0x41U, 0xBFU, 0x88U, 0x80U, + 0x80U, 0x00U, 0x41U, 0x24U, 0x42U, 0x1FU, 0x10U, 0x82U, 0x80U, 0x80U, + 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, + 0x41U, 0xC0U, 0x84U, 0x3DU, 0x41U, 0x01U, 0x10U, 0x81U, 0x80U, 0x80U, + 0x80U, 0x00U, 0x42U, 0x7FU, 0x51U, 0x0DU, 0x00U, 0x41U, 0xE3U, 0x88U, + 0x80U, 0x80U, 0x00U, 0x41U, 0x2AU, 0x42U, 0x20U, 0x10U, 0x82U, 0x80U, + 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x41U, 0x00U, 0x41U, + 0xC0U, 0x84U, 0x3DU, 0x41U, 0x00U, 0x41U, 0x20U, 0x10U, 0x81U, 0x80U, + 0x80U, 0x80U, 0x00U, 0x42U, 0x7FU, 0x51U, 0x0DU, 0x00U, 0x41U, 0x8DU, + 0x89U, 0x80U, 0x80U, 0x00U, 0x41U, 0x2DU, 0x42U, 0x22U, 0x10U, 0x82U, + 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x41U, 0xC0U, + 0x84U, 0x3DU, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x20U, 0x10U, 0x81U, + 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x7FU, 0x51U, 0x0DU, 0x00U, 0x41U, + 0xBAU, 0x89U, 0x80U, 0x80U, 0x00U, 0x41U, 0x2EU, 0x42U, 0x23U, 0x10U, + 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x20U, + 0x01U, 0x41U, 0x0EU, 0x6AU, 0x41U, 0x02U, 0x41U, 0xE8U, 0x89U, 0x80U, + 0x80U, 0x00U, 0x41U, 0x04U, 0x10U, 0x83U, 0x80U, 0x80U, 0x80U, 0x00U, + 0x42U, 0x00U, 0x55U, 0x0DU, 0x00U, 0x41U, 0xEDU, 0x89U, 0x80U, 0x80U, + 0x00U, 0x41U, 0x24U, 0x42U, 0x26U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, + 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x41U, 0x00U, 0x20U, 0x01U, 0x2FU, + 0x01U, 0x0EU, 0x41U, 0x00U, 0x41U, 0x20U, 0x10U, 0x81U, 0x80U, 0x80U, + 0x80U, 0x00U, 0x42U, 0x7DU, 0x51U, 0x0DU, 0x00U, 0x41U, 0x91U, 0x8AU, + 0x80U, 0x80U, 0x00U, 0x41U, 0x25U, 0x42U, 0x27U, 0x10U, 0x82U, 0x80U, + 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, + 0x00U, 0x10U, 0x84U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, + 0x41U, 0x10U, 0x6AU, 0x24U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x20U, + 0x02U, 0x0BU, 0x0BU, 0xBEU, 0x02U, 0x01U, 0x00U, 0x41U, 0x80U, 0x08U, + 0x0BU, 0xB6U, 0x02U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x73U, + 0x65U, 0x74U, 0x28U, 0x30U, 0x2CU, 0x30U, 0x2CU, 0x30U, 0x2CU, 0x30U, + 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x54U, 0x4FU, 0x4FU, 0x5FU, 0x53U, + 0x4DU, 0x41U, 0x4CU, 0x4CU, 0x00U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, + 0x5FU, 0x73U, 0x65U, 0x74U, 0x28U, 0x30U, 0x2CU, 0x30U, 0x2CU, 0x30U, + 0x2CU, 0x33U, 0x33U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x54U, 0x4FU, + 0x4FU, 0x5FU, 0x42U, 0x49U, 0x47U, 0x00U, 0x73U, 0x74U, 0x61U, 0x74U, + 0x65U, 0x5FU, 0x73U, 0x65U, 0x74U, 0x28U, 0x30U, 0x2CU, 0x30U, 0x2CU, + 0x30U, 0x2CU, 0x31U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x29U, + 0x20U, 0x3DU, 0x3DU, 0x20U, 0x54U, 0x4FU, 0x4FU, 0x5FU, 0x42U, 0x49U, + 0x47U, 0x00U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x73U, 0x65U, + 0x74U, 0x28U, 0x30U, 0x2CU, 0x30U, 0x2CU, 0x31U, 0x30U, 0x30U, 0x30U, + 0x30U, 0x30U, 0x30U, 0x2CU, 0x31U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, + 0x4FU, 0x55U, 0x54U, 0x5FU, 0x4FU, 0x46U, 0x5FU, 0x42U, 0x4FU, 0x55U, + 0x4EU, 0x44U, 0x53U, 0x00U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, + 0x73U, 0x65U, 0x74U, 0x28U, 0x30U, 0x2CU, 0x31U, 0x30U, 0x30U, 0x30U, + 0x30U, 0x30U, 0x30U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x20U, 0x33U, 0x32U, + 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x4FU, 0x55U, 0x54U, 0x5FU, 0x4FU, + 0x46U, 0x5FU, 0x42U, 0x4FU, 0x55U, 0x4EU, 0x44U, 0x53U, 0x00U, 0x73U, + 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x73U, 0x65U, 0x74U, 0x28U, 0x31U, + 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x2CU, 0x20U, 0x30U, 0x2CU, + 0x20U, 0x30U, 0x2CU, 0x20U, 0x33U, 0x32U, 0x29U, 0x20U, 0x3DU, 0x3DU, + 0x20U, 0x4FU, 0x55U, 0x54U, 0x5FU, 0x4FU, 0x46U, 0x5FU, 0x42U, 0x4FU, + 0x55U, 0x4EU, 0x44U, 0x53U, 0x00U, 0x53U, 0x49U, 0x5AU, 0x45U, 0x00U, + 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x70U, 0x61U, 0x72U, 0x61U, 0x6DU, + 0x28U, 0x26U, 0x73U, 0x69U, 0x7AU, 0x65U, 0x2CU, 0x20U, 0x32U, 0x2CU, + 0x20U, 0x22U, 0x53U, 0x49U, 0x5AU, 0x45U, 0x22U, 0x2CU, 0x20U, 0x34U, + 0x29U, 0x20U, 0x3EU, 0x20U, 0x30U, 0x00U, 0x73U, 0x74U, 0x61U, 0x74U, + 0x65U, 0x5FU, 0x73U, 0x65U, 0x74U, 0x28U, 0x30U, 0x2CU, 0x20U, 0x73U, + 0x69U, 0x7AU, 0x65U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x20U, 0x33U, 0x32U, + 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x54U, 0x4FU, 0x4FU, 0x5FU, 0x42U, + 0x49U, 0x47U, 0x00U, + }}, + + /* ==== WASM: 67 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -14157,32 +14474,12 @@ std::map> wasm = { #define ASSERT(x)\ if (!(x))\ rollback((uint32_t)#x, sizeof(#x), __LINE__); - #define TOO_SMALL (-4) - #define TOO_BIG (-3) - #define OUT_OF_BOUNDS (-1) #define SBUF(x) (uint32_t)(x), sizeof(x) - int64_t hook(uint32_t reserved ) + int64_t hook(uint32_t reserved) { _g(1,1); - - // bounds and buffer size checks - { - // RH NOTE: readptr/len 0/0 = delete entry - - ASSERT(state_set(0,0,0,0) == TOO_SMALL); - ASSERT(state_set(0,0,0,33) == TOO_BIG); - ASSERT(state_set(0,0,0,1000000) == TOO_BIG); - ASSERT(state_set(0,0,1000000,1) == OUT_OF_BOUNDS); - - ASSERT(state_set(0,1000000, 0, 32) == OUT_OF_BOUNDS); - ASSERT(state_set(1000000, 0, 0, 32) == OUT_OF_BOUNDS); - - ASSERT(state_set(0, 257, 0, 32) == TOO_BIG); - } - - // create state 1 { uint8_t key[32] = @@ -14193,27 +14490,24 @@ std::map> wasm = { 0,0,0,0,0,0,0,0 }; - uint8_t data[4] = + uint8_t data[4] = { 0xCAU,0xFEU,0xBAU,0xBEU }; - ASSERT(state_set(SBUF(data), SBUF(key)) == sizeof(data)); } - // create state 2 + // create state 2 { uint8_t key[3] = { 1,2,3 }; - ASSERT(state_set(SBUF(data2), SBUF(key)) == sizeof(data2)); } - accept(0,0,0); } @@ -14229,119 +14523,63 @@ std::map> wasm = { 0x72U, 0x6FU, 0x6CU, 0x6CU, 0x62U, 0x61U, 0x63U, 0x6BU, 0x00U, 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x02U, 0x03U, 0x02U, 0x01U, 0x03U, 0x05U, 0x03U, 0x01U, - 0x00U, 0x02U, 0x06U, 0x27U, 0x06U, 0x7FU, 0x01U, 0x41U, 0x80U, 0x8CU, - 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0xF3U, 0x0BU, 0x0BU, 0x7FU, 0x00U, - 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x8CU, 0x04U, + 0x00U, 0x02U, 0x06U, 0x27U, 0x06U, 0x7FU, 0x01U, 0x41U, 0xF0U, 0x89U, + 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0xE7U, 0x09U, 0x0BU, 0x7FU, 0x00U, + 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0xF0U, 0x89U, 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, - 0x6BU, 0x00U, 0x04U, 0x0AU, 0x85U, 0x84U, 0x00U, 0x01U, 0x81U, 0x84U, + 0x6BU, 0x00U, 0x04U, 0x0AU, 0xEAU, 0x81U, 0x00U, 0x01U, 0xE6U, 0x81U, 0x00U, 0x02U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x23U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x41U, 0x30U, 0x6BU, 0x22U, 0x01U, 0x24U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U, 0x80U, 0x80U, - 0x80U, 0x80U, 0x00U, 0x1AU, 0x02U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, - 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, - 0x42U, 0x7CU, 0x51U, 0x0DU, 0x00U, 0x41U, 0x80U, 0x89U, 0x80U, 0x80U, - 0x00U, 0x41U, 0x20U, 0x42U, 0x30U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, - 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, - 0x00U, 0x41U, 0x21U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, - 0x7DU, 0x51U, 0x0DU, 0x00U, 0x41U, 0xA0U, 0x89U, 0x80U, 0x80U, 0x00U, - 0x41U, 0x1FU, 0x42U, 0x31U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, - 0x1AU, 0x0BU, 0x02U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, - 0x41U, 0xC0U, 0x84U, 0x3DU, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, - 0x42U, 0x7DU, 0x51U, 0x0DU, 0x00U, 0x41U, 0xBFU, 0x89U, 0x80U, 0x80U, - 0x00U, 0x41U, 0x24U, 0x42U, 0x32U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, - 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, - 0xC0U, 0x84U, 0x3DU, 0x41U, 0x01U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, - 0x00U, 0x42U, 0x7FU, 0x51U, 0x0DU, 0x00U, 0x41U, 0xE3U, 0x89U, 0x80U, - 0x80U, 0x00U, 0x41U, 0x2AU, 0x42U, 0x33U, 0x10U, 0x82U, 0x80U, 0x80U, - 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x41U, 0x00U, 0x41U, 0xC0U, - 0x84U, 0x3DU, 0x41U, 0x00U, 0x41U, 0x20U, 0x10U, 0x81U, 0x80U, 0x80U, - 0x80U, 0x00U, 0x42U, 0x7FU, 0x51U, 0x0DU, 0x00U, 0x41U, 0x8DU, 0x8AU, - 0x80U, 0x80U, 0x00U, 0x41U, 0x2DU, 0x42U, 0x35U, 0x10U, 0x82U, 0x80U, - 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x41U, 0xC0U, 0x84U, - 0x3DU, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x20U, 0x10U, 0x81U, 0x80U, - 0x80U, 0x80U, 0x00U, 0x42U, 0x7FU, 0x51U, 0x0DU, 0x00U, 0x41U, 0xBAU, - 0x8AU, 0x80U, 0x80U, 0x00U, 0x41U, 0x2EU, 0x42U, 0x36U, 0x10U, 0x82U, - 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x41U, 0x00U, - 0x41U, 0x81U, 0x02U, 0x41U, 0x00U, 0x41U, 0x20U, 0x10U, 0x81U, 0x80U, - 0x80U, 0x80U, 0x00U, 0x42U, 0x7DU, 0x51U, 0x0DU, 0x00U, 0x41U, 0xE8U, - 0x8AU, 0x80U, 0x80U, 0x00U, 0x41U, 0x24U, 0x42U, 0x38U, 0x10U, 0x82U, - 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x20U, 0x01U, 0x41U, 0x28U, - 0x6AU, 0x42U, 0x00U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, 0x20U, - 0x6AU, 0x42U, 0x00U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x42U, 0x00U, - 0x37U, 0x03U, 0x18U, 0x20U, 0x01U, 0x42U, 0x00U, 0x37U, 0x03U, 0x10U, - 0x20U, 0x01U, 0x41U, 0xCAU, 0xFDU, 0xEBU, 0xF5U, 0x7BU, 0x36U, 0x02U, - 0x0CU, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0x0CU, 0x6AU, 0x41U, 0x04U, - 0x20U, 0x01U, 0x41U, 0x10U, 0x6AU, 0x41U, 0x20U, 0x10U, 0x81U, 0x80U, - 0x80U, 0x80U, 0x00U, 0x42U, 0x04U, 0x51U, 0x0DU, 0x00U, 0x41U, 0x8CU, - 0x8BU, 0x80U, 0x80U, 0x00U, 0x41U, 0x31U, 0x42U, 0xCCU, 0x00U, 0x10U, - 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x20U, 0x01U, 0x41U, - 0x12U, 0x6AU, 0x41U, 0x00U, 0x2DU, 0x00U, 0xBFU, 0x8BU, 0x80U, 0x80U, - 0x00U, 0x3AU, 0x00U, 0x00U, 0x20U, 0x01U, 0x41U, 0x00U, 0x2FU, 0x00U, - 0xBDU, 0x8BU, 0x80U, 0x80U, 0x00U, 0x3BU, 0x01U, 0x10U, 0x02U, 0x40U, - 0x41U, 0x80U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x80U, 0x01U, 0x20U, - 0x01U, 0x41U, 0x10U, 0x6AU, 0x41U, 0x03U, 0x10U, 0x81U, 0x80U, 0x80U, - 0x80U, 0x00U, 0x42U, 0x80U, 0x01U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xC0U, - 0x8BU, 0x80U, 0x80U, 0x00U, 0x41U, 0x33U, 0x42U, 0xD7U, 0x00U, 0x10U, - 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x41U, 0x00U, 0x41U, - 0x00U, 0x42U, 0x00U, 0x10U, 0x83U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, - 0x20U, 0x01U, 0x41U, 0x30U, 0x6AU, 0x24U, 0x80U, 0x80U, 0x80U, 0x80U, - 0x00U, 0x20U, 0x02U, 0x0BU, 0x0BU, 0x82U, 0x04U, 0x02U, 0x00U, 0x41U, - 0x80U, 0x08U, 0x0BU, 0x80U, 0x01U, 0x23U, 0x13U, 0x96U, 0x68U, 0x78U, - 0xDCU, 0xABU, 0xC4U, 0x40U, 0x26U, 0x07U, 0x2BU, 0xA3U, 0xD2U, 0x0CU, - 0x69U, 0x40U, 0xDDU, 0xCDU, 0xE7U, 0x38U, 0x9BU, 0x0BU, 0xA9U, 0x6CU, - 0x3CU, 0xB3U, 0x87U, 0x37U, 0x02U, 0x81U, 0xE8U, 0x2BU, 0xDDU, 0x5DU, - 0xBBU, 0x40U, 0xD9U, 0x66U, 0x96U, 0x6FU, 0xC1U, 0x6BU, 0xE8U, 0xD4U, - 0x7CU, 0x7BU, 0x62U, 0x14U, 0x4CU, 0xD1U, 0x4BU, 0xAAU, 0x99U, 0x36U, - 0x75U, 0xE9U, 0x22U, 0xADU, 0x0FU, 0x5FU, 0x94U, 0x1DU, 0x86U, 0xEBU, - 0xA8U, 0x13U, 0x99U, 0xF9U, 0x98U, 0xFFU, 0xCAU, 0x5BU, 0x86U, 0x2FU, - 0xDFU, 0x67U, 0x8FU, 0xE2U, 0xE3U, 0xC3U, 0x37U, 0xCCU, 0x47U, 0x0FU, - 0x33U, 0x88U, 0xB0U, 0x33U, 0x3BU, 0x02U, 0x55U, 0x67U, 0x16U, 0xA4U, - 0xFBU, 0x8EU, 0x85U, 0x6FU, 0xD8U, 0x84U, 0x16U, 0xA3U, 0x54U, 0x18U, - 0x34U, 0x06U, 0x0EU, 0xF6U, 0x65U, 0x34U, 0x05U, 0x26U, 0x7EU, 0x05U, - 0x74U, 0xDAU, 0x09U, 0xBFU, 0x55U, 0x8CU, 0x75U, 0x92U, 0xACU, 0x33U, - 0xFBU, 0x01U, 0x8DU, 0x00U, 0x41U, 0x80U, 0x09U, 0x0BU, 0xF3U, 0x02U, - 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x73U, 0x65U, 0x74U, 0x28U, - 0x30U, 0x2CU, 0x30U, 0x2CU, 0x30U, 0x2CU, 0x30U, 0x29U, 0x20U, 0x3DU, - 0x3DU, 0x20U, 0x54U, 0x4FU, 0x4FU, 0x5FU, 0x53U, 0x4DU, 0x41U, 0x4CU, - 0x4CU, 0x00U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x73U, 0x65U, - 0x74U, 0x28U, 0x30U, 0x2CU, 0x30U, 0x2CU, 0x30U, 0x2CU, 0x33U, 0x33U, - 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x54U, 0x4FU, 0x4FU, 0x5FU, 0x42U, - 0x49U, 0x47U, 0x00U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x73U, - 0x65U, 0x74U, 0x28U, 0x30U, 0x2CU, 0x30U, 0x2CU, 0x30U, 0x2CU, 0x31U, - 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x29U, 0x20U, 0x3DU, 0x3DU, - 0x20U, 0x54U, 0x4FU, 0x4FU, 0x5FU, 0x42U, 0x49U, 0x47U, 0x00U, 0x73U, - 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x73U, 0x65U, 0x74U, 0x28U, 0x30U, - 0x2CU, 0x30U, 0x2CU, 0x31U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, - 0x2CU, 0x31U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x4FU, 0x55U, 0x54U, - 0x5FU, 0x4FU, 0x46U, 0x5FU, 0x42U, 0x4FU, 0x55U, 0x4EU, 0x44U, 0x53U, - 0x00U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x73U, 0x65U, 0x74U, - 0x28U, 0x30U, 0x2CU, 0x31U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, - 0x2CU, 0x20U, 0x30U, 0x2CU, 0x20U, 0x33U, 0x32U, 0x29U, 0x20U, 0x3DU, - 0x3DU, 0x20U, 0x4FU, 0x55U, 0x54U, 0x5FU, 0x4FU, 0x46U, 0x5FU, 0x42U, - 0x4FU, 0x55U, 0x4EU, 0x44U, 0x53U, 0x00U, 0x73U, 0x74U, 0x61U, 0x74U, - 0x65U, 0x5FU, 0x73U, 0x65U, 0x74U, 0x28U, 0x31U, 0x30U, 0x30U, 0x30U, - 0x30U, 0x30U, 0x30U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x20U, 0x30U, 0x2CU, - 0x20U, 0x33U, 0x32U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x4FU, 0x55U, - 0x54U, 0x5FU, 0x4FU, 0x46U, 0x5FU, 0x42U, 0x4FU, 0x55U, 0x4EU, 0x44U, - 0x53U, 0x00U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x73U, 0x65U, - 0x74U, 0x28U, 0x30U, 0x2CU, 0x20U, 0x32U, 0x35U, 0x37U, 0x2CU, 0x20U, - 0x30U, 0x2CU, 0x20U, 0x33U, 0x32U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, - 0x54U, 0x4FU, 0x4FU, 0x5FU, 0x42U, 0x49U, 0x47U, 0x00U, 0x73U, 0x74U, + 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x41U, 0x28U, 0x6AU, 0x42U, + 0x00U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, 0x20U, 0x6AU, 0x42U, + 0x00U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x42U, 0x00U, 0x37U, 0x03U, + 0x18U, 0x20U, 0x01U, 0x42U, 0x00U, 0x37U, 0x03U, 0x10U, 0x20U, 0x01U, + 0x41U, 0xCAU, 0xFDU, 0xEBU, 0xF5U, 0x7BU, 0x36U, 0x02U, 0x0CU, 0x02U, + 0x40U, 0x20U, 0x01U, 0x41U, 0x0CU, 0x6AU, 0x41U, 0x04U, 0x20U, 0x01U, + 0x41U, 0x10U, 0x6AU, 0x41U, 0x20U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, + 0x00U, 0x42U, 0x04U, 0x51U, 0x0DU, 0x00U, 0x41U, 0x80U, 0x89U, 0x80U, + 0x80U, 0x00U, 0x41U, 0x31U, 0x42U, 0x37U, 0x10U, 0x82U, 0x80U, 0x80U, + 0x80U, 0x00U, 0x1AU, 0x0BU, 0x20U, 0x01U, 0x41U, 0x12U, 0x6AU, 0x41U, + 0x00U, 0x2DU, 0x00U, 0xB3U, 0x89U, 0x80U, 0x80U, 0x00U, 0x3AU, 0x00U, + 0x00U, 0x20U, 0x01U, 0x41U, 0x00U, 0x2FU, 0x00U, 0xB1U, 0x89U, 0x80U, + 0x80U, 0x00U, 0x3BU, 0x01U, 0x10U, 0x02U, 0x40U, 0x41U, 0x80U, 0x88U, + 0x80U, 0x80U, 0x00U, 0x41U, 0x80U, 0x01U, 0x20U, 0x01U, 0x41U, 0x10U, + 0x6AU, 0x41U, 0x03U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, + 0x80U, 0x01U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xB4U, 0x89U, 0x80U, 0x80U, + 0x00U, 0x41U, 0x33U, 0x42U, 0xC1U, 0x00U, 0x10U, 0x82U, 0x80U, 0x80U, + 0x80U, 0x00U, 0x1AU, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x00U, + 0x10U, 0x83U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x41U, + 0x30U, 0x6AU, 0x24U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x20U, 0x02U, + 0x0BU, 0x0BU, 0xF5U, 0x01U, 0x02U, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, + 0x80U, 0x01U, 0x23U, 0x13U, 0x96U, 0x68U, 0x78U, 0xDCU, 0xABU, 0xC4U, + 0x40U, 0x26U, 0x07U, 0x2BU, 0xA3U, 0xD2U, 0x0CU, 0x69U, 0x40U, 0xDDU, + 0xCDU, 0xE7U, 0x38U, 0x9BU, 0x0BU, 0xA9U, 0x6CU, 0x3CU, 0xB3U, 0x87U, + 0x37U, 0x02U, 0x81U, 0xE8U, 0x2BU, 0xDDU, 0x5DU, 0xBBU, 0x40U, 0xD9U, + 0x66U, 0x96U, 0x6FU, 0xC1U, 0x6BU, 0xE8U, 0xD4U, 0x7CU, 0x7BU, 0x62U, + 0x14U, 0x4CU, 0xD1U, 0x4BU, 0xAAU, 0x99U, 0x36U, 0x75U, 0xE9U, 0x22U, + 0xADU, 0x0FU, 0x5FU, 0x94U, 0x1DU, 0x86U, 0xEBU, 0xA8U, 0x13U, 0x99U, + 0xF9U, 0x98U, 0xFFU, 0xCAU, 0x5BU, 0x86U, 0x2FU, 0xDFU, 0x67U, 0x8FU, + 0xE2U, 0xE3U, 0xC3U, 0x37U, 0xCCU, 0x47U, 0x0FU, 0x33U, 0x88U, 0xB0U, + 0x33U, 0x3BU, 0x02U, 0x55U, 0x67U, 0x16U, 0xA4U, 0xFBU, 0x8EU, 0x85U, + 0x6FU, 0xD8U, 0x84U, 0x16U, 0xA3U, 0x54U, 0x18U, 0x34U, 0x06U, 0x0EU, + 0xF6U, 0x65U, 0x34U, 0x05U, 0x26U, 0x7EU, 0x05U, 0x74U, 0xDAU, 0x09U, + 0xBFU, 0x55U, 0x8CU, 0x75U, 0x92U, 0xACU, 0x33U, 0xFBU, 0x01U, 0x8DU, + 0x00U, 0x41U, 0x80U, 0x09U, 0x0BU, 0x67U, 0x73U, 0x74U, 0x61U, 0x74U, + 0x65U, 0x5FU, 0x73U, 0x65U, 0x74U, 0x28U, 0x53U, 0x42U, 0x55U, 0x46U, + 0x28U, 0x64U, 0x61U, 0x74U, 0x61U, 0x29U, 0x2CU, 0x20U, 0x53U, 0x42U, + 0x55U, 0x46U, 0x28U, 0x6BU, 0x65U, 0x79U, 0x29U, 0x29U, 0x20U, 0x3DU, + 0x3DU, 0x20U, 0x73U, 0x69U, 0x7AU, 0x65U, 0x6FU, 0x66U, 0x28U, 0x64U, + 0x61U, 0x74U, 0x61U, 0x29U, 0x00U, 0x01U, 0x02U, 0x03U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x73U, 0x65U, 0x74U, 0x28U, 0x53U, 0x42U, - 0x55U, 0x46U, 0x28U, 0x64U, 0x61U, 0x74U, 0x61U, 0x29U, 0x2CU, 0x20U, - 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x6BU, 0x65U, 0x79U, 0x29U, 0x29U, - 0x20U, 0x3DU, 0x3DU, 0x20U, 0x73U, 0x69U, 0x7AU, 0x65U, 0x6FU, 0x66U, - 0x28U, 0x64U, 0x61U, 0x74U, 0x61U, 0x29U, 0x00U, 0x01U, 0x02U, 0x03U, - 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x73U, 0x65U, 0x74U, 0x28U, - 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x64U, 0x61U, 0x74U, 0x61U, 0x32U, - 0x29U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x6BU, 0x65U, - 0x79U, 0x29U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x73U, 0x69U, 0x7AU, - 0x65U, 0x6FU, 0x66U, 0x28U, 0x64U, 0x61U, 0x74U, 0x61U, 0x32U, 0x29U, - 0x00U, + 0x55U, 0x46U, 0x28U, 0x64U, 0x61U, 0x74U, 0x61U, 0x32U, 0x29U, 0x2CU, + 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x6BU, 0x65U, 0x79U, 0x29U, + 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x73U, 0x69U, 0x7AU, 0x65U, 0x6FU, + 0x66U, 0x28U, 0x64U, 0x61U, 0x74U, 0x61U, 0x32U, 0x29U, 0x00U, }}, - /* ==== WASM: 66 ==== */ + /* ==== WASM: 68 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -14453,7 +14691,7 @@ std::map> wasm = { 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x30U, 0x00U, }}, - /* ==== WASM: 67 ==== */ + /* ==== WASM: 69 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -14547,7 +14785,7 @@ std::map> wasm = { 0x61U, 0x64U, 0x5BU, 0x69U, 0x5DU, 0x00U, }}, - /* ==== WASM: 68 ==== */ + /* ==== WASM: 70 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -14680,7 +14918,7 @@ std::map> wasm = { 0x64U, 0x5BU, 0x69U, 0x5DU, 0x00U, }}, - /* ==== WASM: 69 ==== */ + /* ==== WASM: 71 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -14768,7 +15006,7 @@ std::map> wasm = { 0x61U, 0x74U, 0x61U, 0x29U, 0x00U, }}, - /* ==== WASM: 70 ==== */ + /* ==== WASM: 72 ==== */ {R"[test.hook]( #include #define sfInvoiceID ((5U << 16U) + 17U) @@ -14879,7 +15117,326 @@ std::map> wasm = { 0x20U, 0x33U, 0x32U, 0x00U, }}, - /* ==== WASM: 71 ==== */ + /* ==== WASM: 73 ==== */ + {R"[test.hook]( + #include + 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); extern int64_t rollback + (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + extern int64_t state_set (uint32_t read_ptr, uint32_t + read_len, uint32_t kread_ptr, uint32_t kread_len); + + extern int64_t util_keylet(uint32_t, uint32_t, uint32_t, + uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); + extern int64_t slot_set(uint32_t, uint32_t, uint32_t); + extern int64_t slot_subfield(uint32_t, uint32_t, uint32_t); + extern int64_t slot(uint32_t, uint32_t, uint32_t); + extern int64_t hook_account(uint32_t, uint32_t); + extern int64_t util_keylet(uint32_t, uint32_t, uint32_t, + uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); + + #define SBUF(x) x, sizeof(x) + #define TOO_BIG -3 + #define DOESNT_EXIST -5 + #define KEYLET_ACCOUNT 3 + + #define sfHookStateScale ((1U << 16U) + 21U) + + #define ASSERT(x)\ + if (!(x))\ + rollback((uint32_t)#x,sizeof(#x),__LINE__) + int64_t hook(uint32_t reserved ) + { + _g(1,1); + + uint8_t hook_acc[20]; + ASSERT(hook_account(hook_acc, 20) == 20); + uint8_t account_keylet[34]; + ASSERT(util_keylet(account_keylet, 34, KEYLET_ACCOUNT, + hook_acc, 20, 0,0,0,0) == 34); + + ASSERT(slot_set(account_keylet, 34, 1) == 1); + slot_subfield(1, sfHookStateScale, 2); + int64_t scale = slot(0,0,2); + + if (scale == DOESNT_EXIST) { + ASSERT(state_set(0, 256, SBUF("test0")) == 256); + ASSERT(state_set(0, 257, SBUF("test")) == TOO_BIG); + accept(0,0,scale); + } + if (scale == 2) { + ASSERT(state_set(0, 256, SBUF("test1")) == 256); + ASSERT(state_set(0, 256*2, SBUF("test2")) == 256*2); + ASSERT(state_set(0, 256*2+1, SBUF("test")) == + TOO_BIG); accept(0,0,scale); + } + if (scale == 5) { + ASSERT(state_set(0, 256, SBUF("test3")) == 256); + ASSERT(state_set(0, 256*5, SBUF("test4")) == 256*5); + ASSERT(state_set(0, 256*5+1, SBUF("test")) == + TOO_BIG); accept(0,0,scale); + } + rollback(0,0,scale); + } + )[test.hook]", + { + 0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, 0x35U, + 0x07U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x02U, 0x7FU, + 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7EU, 0x01U, 0x7EU, + 0x60U, 0x09U, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU, + 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, + 0x60U, 0x04U, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x01U, + 0x7FU, 0x01U, 0x7EU, 0x02U, 0x89U, 0x01U, 0x09U, 0x03U, 0x65U, 0x6EU, + 0x76U, 0x02U, 0x5FU, 0x67U, 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, + 0x0CU, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x61U, 0x63U, 0x63U, 0x6FU, + 0x75U, 0x6EU, 0x74U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x08U, + 0x72U, 0x6FU, 0x6CU, 0x6CU, 0x62U, 0x61U, 0x63U, 0x6BU, 0x00U, 0x02U, + 0x03U, 0x65U, 0x6EU, 0x76U, 0x0BU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, + 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x00U, 0x03U, 0x03U, 0x65U, + 0x6EU, 0x76U, 0x08U, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x73U, 0x65U, + 0x74U, 0x00U, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0DU, 0x73U, 0x6CU, + 0x6FU, 0x74U, 0x5FU, 0x73U, 0x75U, 0x62U, 0x66U, 0x69U, 0x65U, 0x6CU, + 0x64U, 0x00U, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x04U, 0x73U, 0x6CU, + 0x6FU, 0x74U, 0x00U, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x09U, 0x73U, + 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x73U, 0x65U, 0x74U, 0x00U, 0x05U, + 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, + 0x74U, 0x00U, 0x02U, 0x03U, 0x02U, 0x01U, 0x06U, 0x05U, 0x03U, 0x01U, + 0x00U, 0x02U, 0x06U, 0x21U, 0x05U, 0x7FU, 0x01U, 0x41U, 0x90U, 0x8CU, + 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x8FU, 0x0CU, 0x0BU, 0x7FU, 0x00U, + 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x90U, 0x8CU, 0x04U, + 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x07U, 0x08U, 0x01U, + 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x09U, 0x0AU, 0x82U, 0x85U, + 0x00U, 0x01U, 0xFEU, 0x84U, 0x00U, 0x02U, 0x01U, 0x7FU, 0x02U, 0x7EU, + 0x23U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x41U, 0xD0U, 0x00U, 0x6BU, + 0x22U, 0x01U, 0x24U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x41U, 0x01U, + 0x41U, 0x01U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x02U, + 0x40U, 0x20U, 0x01U, 0x41U, 0x30U, 0x6AU, 0x41U, 0x14U, 0x10U, 0x81U, + 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x14U, 0x51U, 0x0DU, 0x00U, 0x41U, + 0x80U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x21U, 0x42U, 0x22U, 0x10U, + 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x20U, + 0x01U, 0x41U, 0x22U, 0x41U, 0x03U, 0x20U, 0x01U, 0x41U, 0x30U, 0x6AU, + 0x41U, 0x14U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, + 0x10U, 0x83U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x22U, 0x51U, 0x0DU, + 0x00U, 0x41U, 0xA1U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0xCDU, 0x00U, + 0x42U, 0x25U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, + 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0x22U, 0x41U, 0x01U, 0x10U, 0x84U, + 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x01U, 0x51U, 0x0DU, 0x00U, 0x41U, + 0xEEU, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x25U, 0x42U, 0x27U, 0x10U, + 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x41U, 0x01U, 0x41U, + 0x95U, 0x80U, 0x04U, 0x41U, 0x02U, 0x10U, 0x85U, 0x80U, 0x80U, 0x80U, + 0x00U, 0x1AU, 0x02U, 0x40U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x02U, + 0x10U, 0x86U, 0x80U, 0x80U, 0x80U, 0x00U, 0x22U, 0x02U, 0x42U, 0x05U, + 0x7CU, 0x22U, 0x03U, 0x42U, 0x0AU, 0x56U, 0x0DU, 0x00U, 0x02U, 0x40U, + 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x20U, 0x03U, 0xA7U, 0x0EU, + 0x0BU, 0x00U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x04U, 0x01U, 0x04U, + 0x04U, 0x02U, 0x00U, 0x0BU, 0x02U, 0x40U, 0x41U, 0x00U, 0x41U, 0x80U, + 0x02U, 0x41U, 0x93U, 0x89U, 0x80U, 0x80U, 0x00U, 0x41U, 0x06U, 0x10U, + 0x87U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x80U, 0x02U, 0x51U, 0x0DU, + 0x00U, 0x41U, 0x99U, 0x89U, 0x80U, 0x80U, 0x00U, 0x41U, 0x28U, 0x42U, + 0x2CU, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x42U, + 0x7BU, 0x21U, 0x03U, 0x41U, 0x00U, 0x41U, 0x81U, 0x02U, 0x41U, 0xC1U, + 0x89U, 0x80U, 0x80U, 0x00U, 0x41U, 0x05U, 0x10U, 0x87U, 0x80U, 0x80U, + 0x80U, 0x00U, 0x42U, 0x7DU, 0x51U, 0x0DU, 0x02U, 0x41U, 0xC6U, 0x89U, + 0x80U, 0x80U, 0x00U, 0x41U, 0x2BU, 0x42U, 0x2DU, 0x10U, 0x82U, 0x80U, + 0x80U, 0x80U, 0x00U, 0x1AU, 0x0CU, 0x02U, 0x0BU, 0x02U, 0x40U, 0x41U, + 0x00U, 0x41U, 0x80U, 0x02U, 0x41U, 0xF1U, 0x89U, 0x80U, 0x80U, 0x00U, + 0x41U, 0x06U, 0x10U, 0x87U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x80U, + 0x02U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xF7U, 0x89U, 0x80U, 0x80U, 0x00U, + 0x41U, 0x28U, 0x42U, 0x31U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, + 0x1AU, 0x0BU, 0x02U, 0x40U, 0x41U, 0x00U, 0x41U, 0x80U, 0x04U, 0x41U, + 0x9FU, 0x8AU, 0x80U, 0x80U, 0x00U, 0x41U, 0x06U, 0x10U, 0x87U, 0x80U, + 0x80U, 0x80U, 0x00U, 0x42U, 0x80U, 0x04U, 0x51U, 0x0DU, 0x00U, 0x41U, + 0xA5U, 0x8AU, 0x80U, 0x80U, 0x00U, 0x41U, 0x2CU, 0x42U, 0x32U, 0x10U, + 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x42U, 0x02U, 0x21U, + 0x03U, 0x41U, 0x00U, 0x41U, 0x81U, 0x04U, 0x41U, 0xC1U, 0x89U, 0x80U, + 0x80U, 0x00U, 0x41U, 0x05U, 0x10U, 0x87U, 0x80U, 0x80U, 0x80U, 0x00U, + 0x42U, 0x7DU, 0x51U, 0x0DU, 0x01U, 0x41U, 0xD1U, 0x8AU, 0x80U, 0x80U, + 0x00U, 0x41U, 0x2FU, 0x42U, 0x34U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, + 0x00U, 0x1AU, 0x0CU, 0x01U, 0x0BU, 0x02U, 0x40U, 0x41U, 0x00U, 0x41U, + 0x80U, 0x02U, 0x41U, 0x80U, 0x8BU, 0x80U, 0x80U, 0x00U, 0x41U, 0x06U, + 0x10U, 0x87U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x80U, 0x02U, 0x51U, + 0x0DU, 0x00U, 0x41U, 0x86U, 0x8BU, 0x80U, 0x80U, 0x00U, 0x41U, 0x28U, + 0x42U, 0x37U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, + 0x02U, 0x40U, 0x41U, 0x00U, 0x41U, 0x80U, 0x0AU, 0x41U, 0xAEU, 0x8BU, + 0x80U, 0x80U, 0x00U, 0x41U, 0x06U, 0x10U, 0x87U, 0x80U, 0x80U, 0x80U, + 0x00U, 0x42U, 0x80U, 0x0AU, 0x51U, 0x0DU, 0x00U, 0x41U, 0xB4U, 0x8BU, + 0x80U, 0x80U, 0x00U, 0x41U, 0x2CU, 0x42U, 0x38U, 0x10U, 0x82U, 0x80U, + 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x42U, 0x05U, 0x21U, 0x03U, 0x41U, + 0x00U, 0x41U, 0x81U, 0x0AU, 0x41U, 0xC1U, 0x89U, 0x80U, 0x80U, 0x00U, + 0x41U, 0x05U, 0x10U, 0x87U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x7DU, + 0x51U, 0x0DU, 0x00U, 0x41U, 0xE0U, 0x8BU, 0x80U, 0x80U, 0x00U, 0x41U, + 0x2FU, 0x42U, 0x3AU, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, + 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, 0x03U, 0x10U, 0x88U, 0x80U, + 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x20U, + 0x02U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, + 0x41U, 0xD0U, 0x00U, 0x6AU, 0x24U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, + 0x20U, 0x02U, 0x0BU, 0x0BU, 0x97U, 0x04U, 0x01U, 0x00U, 0x41U, 0x80U, + 0x08U, 0x0BU, 0x8FU, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x61U, + 0x63U, 0x63U, 0x6FU, 0x75U, 0x6EU, 0x74U, 0x28U, 0x68U, 0x6FU, 0x6FU, + 0x6BU, 0x5FU, 0x61U, 0x63U, 0x63U, 0x2CU, 0x20U, 0x32U, 0x30U, 0x29U, + 0x20U, 0x3DU, 0x3DU, 0x20U, 0x32U, 0x30U, 0x00U, 0x75U, 0x74U, 0x69U, + 0x6CU, 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U, 0x61U, + 0x63U, 0x63U, 0x6FU, 0x75U, 0x6EU, 0x74U, 0x5FU, 0x6BU, 0x65U, 0x79U, + 0x6CU, 0x65U, 0x74U, 0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU, + 0x45U, 0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x41U, 0x43U, 0x43U, 0x4FU, + 0x55U, 0x4EU, 0x54U, 0x2CU, 0x20U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, + 0x61U, 0x63U, 0x63U, 0x2CU, 0x20U, 0x32U, 0x30U, 0x2CU, 0x20U, 0x30U, + 0x2CU, 0x30U, 0x2CU, 0x30U, 0x2CU, 0x30U, 0x29U, 0x20U, 0x3DU, 0x3DU, + 0x20U, 0x33U, 0x34U, 0x00U, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x73U, + 0x65U, 0x74U, 0x28U, 0x61U, 0x63U, 0x63U, 0x6FU, 0x75U, 0x6EU, 0x74U, + 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x2CU, 0x20U, 0x33U, + 0x34U, 0x2CU, 0x20U, 0x31U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x31U, + 0x00U, 0x74U, 0x65U, 0x73U, 0x74U, 0x30U, 0x00U, 0x73U, 0x74U, 0x61U, + 0x74U, 0x65U, 0x5FU, 0x73U, 0x65U, 0x74U, 0x28U, 0x30U, 0x2CU, 0x20U, + 0x32U, 0x35U, 0x36U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, + 0x22U, 0x74U, 0x65U, 0x73U, 0x74U, 0x30U, 0x22U, 0x29U, 0x29U, 0x20U, + 0x3DU, 0x3DU, 0x20U, 0x32U, 0x35U, 0x36U, 0x00U, 0x74U, 0x65U, 0x73U, + 0x74U, 0x00U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x73U, 0x65U, + 0x74U, 0x28U, 0x30U, 0x2CU, 0x20U, 0x32U, 0x35U, 0x37U, 0x2CU, 0x20U, + 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x22U, 0x74U, 0x65U, 0x73U, 0x74U, + 0x22U, 0x29U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x54U, 0x4FU, 0x4FU, + 0x5FU, 0x42U, 0x49U, 0x47U, 0x00U, 0x74U, 0x65U, 0x73U, 0x74U, 0x31U, + 0x00U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x73U, 0x65U, 0x74U, + 0x28U, 0x30U, 0x2CU, 0x20U, 0x32U, 0x35U, 0x36U, 0x2CU, 0x20U, 0x53U, + 0x42U, 0x55U, 0x46U, 0x28U, 0x22U, 0x74U, 0x65U, 0x73U, 0x74U, 0x31U, + 0x22U, 0x29U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x32U, 0x35U, 0x36U, + 0x00U, 0x74U, 0x65U, 0x73U, 0x74U, 0x32U, 0x00U, 0x73U, 0x74U, 0x61U, + 0x74U, 0x65U, 0x5FU, 0x73U, 0x65U, 0x74U, 0x28U, 0x30U, 0x2CU, 0x20U, + 0x32U, 0x35U, 0x36U, 0x2AU, 0x32U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, + 0x46U, 0x28U, 0x22U, 0x74U, 0x65U, 0x73U, 0x74U, 0x32U, 0x22U, 0x29U, + 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x32U, 0x35U, 0x36U, 0x2AU, 0x32U, + 0x00U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x73U, 0x65U, 0x74U, + 0x28U, 0x30U, 0x2CU, 0x20U, 0x32U, 0x35U, 0x36U, 0x2AU, 0x32U, 0x2BU, + 0x31U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x22U, 0x74U, + 0x65U, 0x73U, 0x74U, 0x22U, 0x29U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, + 0x54U, 0x4FU, 0x4FU, 0x5FU, 0x42U, 0x49U, 0x47U, 0x00U, 0x74U, 0x65U, + 0x73U, 0x74U, 0x33U, 0x00U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, + 0x73U, 0x65U, 0x74U, 0x28U, 0x30U, 0x2CU, 0x20U, 0x32U, 0x35U, 0x36U, + 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x22U, 0x74U, 0x65U, + 0x73U, 0x74U, 0x33U, 0x22U, 0x29U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, + 0x32U, 0x35U, 0x36U, 0x00U, 0x74U, 0x65U, 0x73U, 0x74U, 0x34U, 0x00U, + 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x73U, 0x65U, 0x74U, 0x28U, + 0x30U, 0x2CU, 0x20U, 0x32U, 0x35U, 0x36U, 0x2AU, 0x35U, 0x2CU, 0x20U, + 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x22U, 0x74U, 0x65U, 0x73U, 0x74U, + 0x34U, 0x22U, 0x29U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x32U, 0x35U, + 0x36U, 0x2AU, 0x35U, 0x00U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, + 0x73U, 0x65U, 0x74U, 0x28U, 0x30U, 0x2CU, 0x20U, 0x32U, 0x35U, 0x36U, + 0x2AU, 0x35U, 0x2BU, 0x31U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, + 0x28U, 0x22U, 0x74U, 0x65U, 0x73U, 0x74U, 0x22U, 0x29U, 0x29U, 0x20U, + 0x3DU, 0x3DU, 0x20U, 0x54U, 0x4FU, 0x4FU, 0x5FU, 0x42U, 0x49U, 0x47U, + 0x00U, + }}, + + /* ==== WASM: 74 ==== */ + {R"[test.hook]( + #include + 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); + extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + extern int64_t state_set ( + uint32_t read_ptr, + uint32_t read_len, + uint32_t kread_ptr, + uint32_t kread_len + ); + extern int64_t state_foreign_set ( + uint32_t read_ptr, + uint32_t read_len, + uint32_t kread_ptr, + uint32_t kread_len, + uint32_t nread_ptr, + uint32_t nread_len, + uint32_t aread_ptr, + uint32_t aread_len + ); + extern int64_t otxn_param(uint32_t, uint32_t, uint32_t, uint32_t); + #define RESERVE_INSUFFICIENT -38 + #define ASSERT(x)\ + if (!(x))\ + rollback((uint32_t)#x, sizeof(#x), __LINE__); + #define ASSERT_EQUAL(x, y)\ + if (!(x == y))\ + rollback((uint32_t)#x, sizeof(#x), x); + int64_t hook(uint32_t reserved) + { + _g(1,1); + { + // 1. first account for StateMap + ASSERT_EQUAL(state_set(0, 1, "1", 1), RESERVE_INSUFFICIENT); + // 2. first namespace for StateMap + ASSERT_EQUAL(state_foreign_set(0, 1, "1", 1, "1", 32, 0, 0), RESERVE_INSUFFICIENT); + // 3. first statekey for StateMap + ASSERT_EQUAL(state_set(0, 1, "2", 1), RESERVE_INSUFFICIENT); + // 4. existing statedata + ASSERT_EQUAL(state_set(0, 1, "1", 1), RESERVE_INSUFFICIENT); + } + accept(0,0,0); + } + )[test.hook]", + { + 0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, 0x27U, + 0x05U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x04U, 0x7FU, + 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7EU, + 0x01U, 0x7EU, 0x60U, 0x08U, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU, + 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U, + 0x4EU, 0x05U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, 0x00U, + 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x09U, 0x73U, 0x74U, 0x61U, 0x74U, + 0x65U, 0x5FU, 0x73U, 0x65U, 0x74U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, + 0x76U, 0x08U, 0x72U, 0x6FU, 0x6CU, 0x6CU, 0x62U, 0x61U, 0x63U, 0x6BU, + 0x00U, 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x11U, 0x73U, 0x74U, 0x61U, + 0x74U, 0x65U, 0x5FU, 0x66U, 0x6FU, 0x72U, 0x65U, 0x69U, 0x67U, 0x6EU, + 0x5FU, 0x73U, 0x65U, 0x74U, 0x00U, 0x03U, 0x03U, 0x65U, 0x6EU, 0x76U, + 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x02U, 0x03U, + 0x02U, 0x01U, 0x04U, 0x05U, 0x03U, 0x01U, 0x00U, 0x02U, 0x06U, 0x21U, + 0x05U, 0x7FU, 0x01U, 0x41U, 0xF0U, 0x88U, 0x04U, 0x0BU, 0x7FU, 0x00U, + 0x41U, 0xE3U, 0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, + 0x7FU, 0x00U, 0x41U, 0xF0U, 0x88U, 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, + 0x80U, 0x08U, 0x0BU, 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, + 0x6BU, 0x00U, 0x05U, 0x0AU, 0xA6U, 0x82U, 0x00U, 0x01U, 0xA2U, 0x82U, + 0x00U, 0x01U, 0x01U, 0x7EU, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U, 0x80U, + 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x02U, 0x40U, 0x41U, 0x00U, 0x41U, + 0x01U, 0x41U, 0x80U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x01U, 0x10U, + 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x5AU, 0x51U, 0x0DU, 0x00U, + 0x41U, 0x82U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x18U, 0x41U, 0x00U, + 0x41U, 0x01U, 0x41U, 0x80U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x01U, + 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x10U, 0x82U, 0x80U, 0x80U, + 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x41U, 0x00U, 0x41U, 0x01U, + 0x41U, 0x80U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x01U, 0x41U, 0x80U, + 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x20U, 0x41U, 0x00U, 0x41U, 0x00U, + 0x10U, 0x83U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x5AU, 0x51U, 0x0DU, + 0x00U, 0x41U, 0x9AU, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x2FU, 0x41U, + 0x00U, 0x41U, 0x01U, 0x41U, 0x80U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, + 0x01U, 0x41U, 0x80U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x20U, 0x41U, + 0x00U, 0x41U, 0x00U, 0x10U, 0x83U, 0x80U, 0x80U, 0x80U, 0x00U, 0x10U, + 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x41U, + 0x00U, 0x41U, 0x01U, 0x41U, 0xC9U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, + 0x01U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x5AU, 0x51U, + 0x0DU, 0x00U, 0x41U, 0xCBU, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x18U, + 0x41U, 0x00U, 0x41U, 0x01U, 0x41U, 0xC9U, 0x88U, 0x80U, 0x80U, 0x00U, + 0x41U, 0x01U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x10U, 0x82U, + 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x41U, 0x00U, + 0x41U, 0x01U, 0x41U, 0x80U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x01U, + 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x5AU, 0x51U, 0x0DU, + 0x00U, 0x41U, 0x82U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x18U, 0x41U, + 0x00U, 0x41U, 0x01U, 0x41U, 0x80U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, + 0x01U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x10U, 0x82U, 0x80U, + 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, + 0x00U, 0x10U, 0x84U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, + 0x0BU, 0x0BU, 0x6AU, 0x01U, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x63U, + 0x31U, 0x00U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x73U, 0x65U, + 0x74U, 0x28U, 0x30U, 0x2CU, 0x20U, 0x31U, 0x2CU, 0x20U, 0x22U, 0x31U, + 0x22U, 0x2CU, 0x20U, 0x31U, 0x29U, 0x00U, 0x73U, 0x74U, 0x61U, 0x74U, + 0x65U, 0x5FU, 0x66U, 0x6FU, 0x72U, 0x65U, 0x69U, 0x67U, 0x6EU, 0x5FU, + 0x73U, 0x65U, 0x74U, 0x28U, 0x30U, 0x2CU, 0x20U, 0x31U, 0x2CU, 0x20U, + 0x22U, 0x31U, 0x22U, 0x2CU, 0x20U, 0x31U, 0x2CU, 0x20U, 0x22U, 0x31U, + 0x22U, 0x2CU, 0x20U, 0x33U, 0x32U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x20U, + 0x30U, 0x29U, 0x00U, 0x32U, 0x00U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, + 0x5FU, 0x73U, 0x65U, 0x74U, 0x28U, 0x30U, 0x2CU, 0x20U, 0x31U, 0x2CU, + 0x20U, 0x22U, 0x32U, 0x22U, 0x2CU, 0x20U, 0x31U, 0x29U, 0x00U, + }}, + + /* ==== WASM: 75 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -15491,7 +16048,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 72 ==== */ + /* ==== WASM: 76 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -15841,7 +16398,7 @@ std::map> wasm = { 0x20U, 0x30U, 0x00U, }}, - /* ==== WASM: 73 ==== */ + /* ==== WASM: 77 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -15977,7 +16534,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 74 ==== */ + /* ==== WASM: 78 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -16150,7 +16707,7 @@ std::map> wasm = { 0x54U, 0x5FU, 0x45U, 0x58U, 0x49U, 0x53U, 0x54U, 0x00U, }}, - /* ==== WASM: 75 ==== */ + /* ==== WASM: 79 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -16298,7 +16855,7 @@ std::map> wasm = { 0x30U, 0x00U, 0x22U, 0x00U, 0x00U, 0x00U, 0x00U, }}, - /* ==== WASM: 76 ==== */ + /* ==== WASM: 80 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -16395,7 +16952,7 @@ std::map> wasm = { 0x3DU, 0x20U, 0x30U, 0x00U, }}, - /* ==== WASM: 77 ==== */ + /* ==== WASM: 81 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -16454,7 +17011,7 @@ std::map> wasm = { 0x4FU, 0x46U, 0x5FU, 0x42U, 0x4FU, 0x55U, 0x4EU, 0x44U, 0x53U, 0x00U, }}, - /* ==== WASM: 78 ==== */ + /* ==== WASM: 82 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -16513,7 +17070,7 @@ std::map> wasm = { 0x4EU, 0x44U, 0x53U, 0x00U, }}, - /* ==== WASM: 79 ==== */ + /* ==== WASM: 83 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -18342,7 +18899,7 @@ std::map> wasm = { 0x53U, 0x4DU, 0x41U, 0x4CU, 0x4CU, 0x00U, }}, - /* ==== WASM: 80 ==== */ + /* ==== WASM: 84 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -19622,7 +20179,7 @@ std::map> wasm = { 0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x20U, 0x29U, 0x29U, 0x00U, }}, - /* ==== WASM: 81 ==== */ + /* ==== WASM: 85 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -22555,7 +23112,7 @@ std::map> wasm = { 0x4FU, 0x4FU, 0x5FU, 0x53U, 0x4DU, 0x41U, 0x4CU, 0x4CU, 0x00U, }}, - /* ==== WASM: 82 ==== */ + /* ==== WASM: 86 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -24520,7 +25077,7 @@ std::map> wasm = { 0x54U, 0x4FU, 0x4FU, 0x5FU, 0x53U, 0x4DU, 0x41U, 0x4CU, 0x4CU, 0x00U, }}, - /* ==== WASM: 83 ==== */ + /* ==== WASM: 87 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -24805,7 +25362,7 @@ std::map> wasm = { 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x30U, 0x00U, }}, - /* ==== WASM: 84 ==== */ + /* ==== WASM: 88 ==== */ {R"[test.hook]( #include extern int32_t _g(uint32_t, uint32_t); @@ -25392,7 +25949,7 @@ std::map> wasm = { 0x4EU, 0x5FU, 0x46U, 0x41U, 0x49U, 0x4CU, 0x55U, 0x52U, 0x45U, 0x00U, }}, - /* ==== WASM: 85 ==== */ + /* ==== WASM: 89 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -25421,7 +25978,7 @@ std::map> wasm = { 0x0BU, }}, - /* ==== WASM: 86 ==== */ + /* ==== WASM: 90 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -25453,7 +26010,7 @@ std::map> wasm = { 0x20U, 0x52U, 0x65U, 0x6AU, 0x65U, 0x63U, 0x74U, 0x65U, 0x64U, 0x00U, }}, - /* ==== WASM: 87 ==== */ + /* ==== WASM: 91 ==== */ {R"[test.hook]( (module (type (;0;) (func (param i32 i32 i64) (result i64))) @@ -25480,7 +26037,7 @@ std::map> wasm = { 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x00U, 0x10U, 0x00U, 0x0BU, }}, - /* ==== WASM: 88 ==== */ + /* ==== WASM: 92 ==== */ {R"[test.hook]( (module (type (;0;) (func (param i32 i32) (result i32))) @@ -25533,7 +26090,7 @@ std::map> wasm = { 0x00U, 0x1AU, 0x0BU, }}, - /* ==== WASM: 89 ==== */ + /* ==== WASM: 93 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -32176,7 +32733,7 @@ std::map> wasm = { 0x39U, 0x30U, 0x31U, 0x32U, 0x33U, 0x00U, }}, - /* ==== WASM: 90 ==== */ + /* ==== WASM: 94 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -32222,7 +32779,7 @@ std::map> wasm = { 0x0BU, 0x06U, 0x76U, 0x61U, 0x6CU, 0x75U, 0x65U, 0x00U, }}, - /* ==== WASM: 91 ==== */ + /* ==== WASM: 95 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); diff --git a/src/test/rpc/AccountSet_test.cpp b/src/test/rpc/AccountSet_test.cpp index 19a5a12dd..ebb08cefe 100644 --- a/src/test/rpc/AccountSet_test.cpp +++ b/src/test/rpc/AccountSet_test.cpp @@ -544,6 +544,8 @@ public: void testTicket() { + testcase("Ticket"); + using namespace test::jtx; Env env(*this); Account const alice("alice"); @@ -574,6 +576,104 @@ public: env.close(); } + void + testHookStateScale() + { + testcase("HookStateScale"); + + using namespace test::jtx; + Env env(*this, supported_amendments() - featureExtendedHookState); + Account const alice("alice"); + + env.fund(XRP(10000), alice); + env.close(); + + // disabled + auto jt = noop(alice); + jt[sfHookStateScale.fieldName] = 1; + env(jt, ter(temMALFORMED)); + env.close(); + + env.enableFeature(featureExtendedHookState); + env.close(); + + // set invalid HookStateScale (0 or > 16) + for (uint16_t scale : {0, 17}) + { + jt[sfHookStateScale.fieldName] = scale; + env(jt, ter(temMALFORMED)); + } + + // set HookStateScale to 1 + jt[sfHookStateScale.fieldName] = 1; + env(jt); + env.close(); + BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfHookStateScale)); + + // increase HookStateScale to 16 + jt[sfHookStateScale.fieldName] = 16; + env(jt); + env.close(); + BEAST_EXPECT(env.le(alice)->getFieldU16(sfHookStateScale) == 16); + + // decrease HookStateScale to 8 + jt[sfHookStateScale.fieldName] = 8; + env(jt); + env.close(); + BEAST_EXPECT(env.le(alice)->getFieldU16(sfHookStateScale) == 8); + + // reset HookStateScale + jt[sfHookStateScale.fieldName] = 1; + env(jt); + env.close(); + BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfHookStateScale)); + + // test OwnerCount + // This prevents an exception for sfMintedNFTokens when the AccountRoot + // template is applied. + { + uint256 const nftId0{token::getNextID(env, alice, 0u)}; + env(token::mint(alice, 0u)); + env(token::burn(alice, nftId0)); + env.close(); + } + auto applyCount = [&](uint16_t scale, + uint32_t stateCount, + uint32_t ownerCount) { + return env.app().openLedger().modify( + [&](OpenView& view, beast::Journal j) -> bool { + auto const sle = view.read(keylet::account(alice.id())); + if (!sle) + return false; + auto replacement = std::make_shared(*sle, sle->key()); + (*replacement)[sfHookStateScale] = scale; + (*replacement)[sfHookStateCount] = stateCount; + (*replacement)[sfOwnerCount] = ownerCount; + view.rawReplace(replacement); + return true; + }); + }; + applyCount(5, 10, 100); + + // remove, but HookStateCount exists + jt[sfHookStateScale.fieldName] = 1; + env(jt, ter(tecHAS_HOOK_STATE)); + // decrease, but HookStateCount exists + jt[sfHookStateScale.fieldName] = 4; + env(jt, ter(tecHAS_HOOK_STATE)); + // increase + jt[sfHookStateScale.fieldName] = 6; + env(jt, ter(tesSUCCESS)); + BEAST_EXPECT(env.le(alice)->getFieldU16(sfHookStateScale) == 6); + BEAST_EXPECT(env.le(alice)->getFieldU32(sfHookStateCount) == 10); + BEAST_EXPECT(env.le(alice)->getFieldU32(sfOwnerCount) == 110); + + // check InsufficientReserve + applyCount(1, 100, 200); + jt[sfHookStateScale.fieldName] = 16; + env(jt, ter(tecINSUFFICIENT_RESERVE)); + } + void run() override { @@ -590,6 +690,7 @@ public: testRequireAuthWithDir(); testTransferRate(); testTicket(); + testHookStateScale(); } }; From 2a10013dfc9fd55e52c2d9b4a4aeafa5e884af7f Mon Sep 17 00:00:00 2001 From: tequ Date: Fri, 24 Oct 2025 16:05:14 +0900 Subject: [PATCH 20/20] Support 'cron' with ledger_entry RPC (#608) --- src/ripple/protocol/impl/Indexes.cpp | 2 +- src/ripple/protocol/jss.h | 1 + src/ripple/rpc/handlers/LedgerEntry.cpp | 30 +++++++++ src/test/rpc/LedgerRPC_test.cpp | 83 +++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/src/ripple/protocol/impl/Indexes.cpp b/src/ripple/protocol/impl/Indexes.cpp index d8f802fe2..c09070574 100644 --- a/src/ripple/protocol/impl/Indexes.cpp +++ b/src/ripple/protocol/impl/Indexes.cpp @@ -72,7 +72,7 @@ enum class LedgerNameSpace : std::uint16_t { URI_TOKEN = 'U', IMPORT_VLSEQ = 'I', UNL_REPORT = 'R', - CRON = 'A', + CRON = 'L', // No longer used or supported. Left here to reserve the space // to avoid accidental reuse. diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index 5cba9fb3c..316037268 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -256,6 +256,7 @@ JSS(coins); JSS(children); JSS(ctid); // in/out: Tx RPC JSS(cres); +JSS(cron); JSS(currency_a); // out: BookChanges JSS(currency_b); // out: BookChanges JSS(currentShard); // out: NodeToShardStatus diff --git a/src/ripple/rpc/handlers/LedgerEntry.cpp b/src/ripple/rpc/handlers/LedgerEntry.cpp index aa5ffb02a..bf861333a 100644 --- a/src/ripple/rpc/handlers/LedgerEntry.cpp +++ b/src/ripple/rpc/handlers/LedgerEntry.cpp @@ -506,6 +506,36 @@ doLedgerEntry(RPC::JsonContext& context) jvResult[jss::error] = "malformedRequest"; } } + else if (context.params.isMember(jss::cron)) + { + expectedType = ltCRON; + if (!context.params[jss::cron].isObject()) + { + if (!uNodeIndex.parseHex(context.params[jss::cron].asString())) + { + uNodeIndex = beast::zero; + jvResult[jss::error] = "malformedRequest"; + } + } + else if ( + !context.params[jss::cron].isMember(jss::owner) || + !context.params[jss::cron].isMember(jss::time)) + { + jvResult[jss::error] = "malformedRequest"; + } + else + { + auto const id = parseBase58( + context.params[jss::cron][jss::owner].asString()); + if (!id) + jvResult[jss::error] = "malformedAddress"; + else + uNodeIndex = + keylet::cron( + context.params[jss::cron][jss::time].asUInt(), *id) + .key; + } + } else { if (context.params.isMember("params") && diff --git a/src/test/rpc/LedgerRPC_test.cpp b/src/test/rpc/LedgerRPC_test.cpp index e6f766916..6c0819377 100644 --- a/src/test/rpc/LedgerRPC_test.cpp +++ b/src/test/rpc/LedgerRPC_test.cpp @@ -1839,6 +1839,88 @@ public: } } + void + testLedgerEntryCron() + { + testcase("ledger_entry Request Cron"); + using namespace test::jtx; + + Env env{*this}; + + Account const alice{"alice"}; + env.fund(XRP(10000), alice); + env.close(); + + auto const startTime = + env.current()->parentCloseTime().time_since_epoch().count() + 100; + env(cron::set(alice), + cron::startTime(startTime), + cron::delay(100), + cron::repeat(200), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + std::string const ledgerHash{to_string(env.closed()->info().hash)}; + + uint256 const cronIndex{keylet::cron(startTime, alice).key}; + { + // Request the cron using its index. + Json::Value jvParams; + jvParams[jss::cron] = to_string(cronIndex); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][sfOwner.jsonName] == alice.human()); + BEAST_EXPECT(jrr[jss::node][sfStartTime.jsonName] == startTime); + BEAST_EXPECT(jrr[jss::node][sfDelaySeconds.jsonName] == 100); + BEAST_EXPECT(jrr[jss::node][sfRepeatCount.jsonName] == 200); + } + { + // Request the cron using its owner and time. + Json::Value jvParams; + jvParams[jss::cron] = Json::objectValue; + jvParams[jss::cron][jss::owner] = alice.human(); + jvParams[jss::cron][jss::time] = startTime; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][sfOwner.jsonName] == alice.human()); + BEAST_EXPECT(jrr[jss::node][sfStartTime.jsonName] == startTime); + BEAST_EXPECT(jrr[jss::node][sfDelaySeconds.jsonName] == 100); + BEAST_EXPECT(jrr[jss::node][sfRepeatCount.jsonName] == 200); + } + { + // Malformed uritoken object. Missing owner member. + Json::Value jvParams; + jvParams[jss::cron] = Json::objectValue; + jvParams[jss::cron][jss::time] = startTime; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Malformed uritoken object. Missing time member. + Json::Value jvParams; + jvParams[jss::cron] = Json::objectValue; + jvParams[jss::cron][jss::owner] = alice.human(); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Request an index that is not a uritoken. + Json::Value jvParams; + jvParams[jss::cron] = ledgerHash; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "entryNotFound", ""); + } + } + void testLedgerEntryUnknownOption() { @@ -2365,6 +2447,7 @@ public: testLedgerEntryTicket(); testLedgerEntryURIToken(); testLedgerEntryImportVLSeq(); + testLedgerEntryCron(); testLedgerEntryUnknownOption(); testLookupLedger(); testNoQueue();