From e06b48d144e94580c80dc699dcc2afefe14529ca Mon Sep 17 00:00:00 2001 From: tequ Date: Wed, 18 Feb 2026 10:48:13 +0900 Subject: [PATCH] Support new STIs for `sto_*` HookAPI (#657) --- .gitignore | 1 + BUILD.md | 224 +--- cfg/xahaud-example.cfg | 341 ++++++- docs/build/environment.md | 4 +- docs/build/install.md | 177 +--- hook/extern.h | 7 + hook/sfcodes.h | 2 + include/xrpl/hook/Enum.h | 1 + include/xrpl/hook/HookAPI.h | 20 +- include/xrpl/hook/hook_api.macro | 5 + include/xrpl/protocol/Feature.h | 2 +- include/xrpl/protocol/detail/features.macro | 6 + .../xrpl/protocol/detail/ledger_entries.macro | 4 +- include/xrpl/protocol/detail/sfields.macro | 2 + include/xrpl/protocol/jss.h | 4 + src/libxrpl/protocol/InnerObjectFormats.cpp | 6 +- src/libxrpl/protocol/STTx.cpp | 2 +- src/test/app/HookAPI_test.cpp | 79 ++ src/test/app/SetHook_test.cpp | 964 +++++++++++++++++- src/test/app/SetHook_wasm.h | 763 ++++++++++++-- src/test/app/XahauGenesis_test.cpp | 27 +- src/xrpld/app/hook/applyHook.h | 6 + src/xrpld/app/hook/detail/HookAPI.cpp | 275 ++++- src/xrpld/app/hook/detail/applyHook.cpp | 54 + src/xrpld/app/tx/detail/Change.cpp | 18 +- src/xrpld/app/tx/detail/SetHook.cpp | 233 ++++- src/xrpld/app/tx/detail/SetHook.h | 3 + src/xrpld/app/tx/detail/Transactor.cpp | 50 +- src/xrpld/app/tx/detail/Transactor.h | 2 + 29 files changed, 2708 insertions(+), 574 deletions(-) diff --git a/.gitignore b/.gitignore index e929508c4..585a69efb 100644 --- a/.gitignore +++ b/.gitignore @@ -76,6 +76,7 @@ docs/html_doc # Xcode .DS_Store */build/* +!/docs/build/ *.pbxuser !default.pbxuser *.mode1v3 diff --git a/BUILD.md b/BUILD.md index bb0403a65..3f0aafcd1 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,7 +1,7 @@ -| :warning: **WARNING** :warning: -|---| -| These instructions assume you have a C++ development environment ready with Git, Python, Conan, CMake, and a C++ compiler. For help setting one up on Linux, macOS, or Windows, [see this guide](./docs/build/environment.md). | - +> These instructions assume you have a C++ development environment ready +> with Git, Python, Conan, CMake, and a C++ compiler. For help setting one up +> on Linux, macOS, or Windows, see [our guide](./docs/build/environment.md). +> > These instructions also assume a basic familiarity with Conan and CMake. > If you are unfamiliar with Conan, > you can read our [crash course](./docs/build/conan.md) @@ -10,7 +10,7 @@ ## Branches For a stable release, choose the `master` branch or one of the [tagged -releases](https://github.com/ripple/rippled/releases). +releases](https://github.com/Xahau/xahaud/releases). ``` git checkout master @@ -29,70 +29,46 @@ branch. git checkout develop ``` + ## Minimum Requirements -See [System Requirements](https://xrpl.org/system-requirements.html). - -Building rippled generally requires git, Python, Conan, CMake, and a C++ compiler. Some guidance on setting up such a [C++ development environment can be found here](./docs/build/environment.md). - - [Python 3.7](https://www.python.org/downloads/) - [Conan 2.x](https://conan.io/downloads) - [CMake 3.16](https://cmake.org/download/) -[^1]: It is possible to build with Conan 2.x, -but the instructions are significantly different, -which is why we are not recommending it yet. -Notably, the `conan profile update` command is removed in 2.x. -Profiles must be edited by hand. - -`rippled` is written in the C++20 dialect and includes the `` header. +`xahaud` is written in the C++20 dialect and includes the `` header. The [minimum compiler versions][2] required are: | Compiler | Version | |-------------|---------| -| GCC | 11 | +| GCC | 10 | | Clang | 13 | | Apple Clang | 13.1.6 | | MSVC | 19.23 | -### Linux +We don't recommend Windows for `xahaud` production at this time. As of +November 2025, Ubuntu has the highest level of quality assurance, testing, +and support. -The Ubuntu operating system has received the highest level of -quality assurance, testing, and support. +Windows developers should use Visual Studio 2019. `xahaud` isn't +compatible with [Boost](https://www.boost.org/) 1.78 or 1.79, and Conan +can't build earlier Boost versions. -Here are [sample instructions for setting up a C++ development environment on Linux](./docs/build/environment.md#linux). +**Note:** 32-bit Windows development isn't supported. -### Mac - -Many rippled engineers use macOS for development. - -Here are [sample instructions for setting up a C++ development environment on macOS](./docs/build/environment.md#macos). - -### Windows - -Windows is not recommended for production use at this time. - -- Additionally, 32-bit Windows development is not supported. - -[Boost]: https://www.boost.org/ ## Steps + ### Set Up Conan -After you have a [C++ development environment](./docs/build/environment.md) ready with Git, Python, Conan, CMake, and a C++ compiler, you may need to set up your Conan profile. - -These instructions assume a basic familiarity with Conan and CMake. - -If you are unfamiliar with Conan, then please read [this crash course](./docs/build/conan.md) or the official [Getting Started][3] walkthrough. - -You'll need at least one Conan profile: +1. (Optional) If you've never used Conan, use autodetect to set up a default profile. ``` conan profile detect --force ``` -Update the compiler settings: +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: @@ -109,16 +85,10 @@ Update the compiler settings: compiler.cppstd=20 ``` -Configure Conan (1.x only) to use recipe revisions: - - ``` - conan config set general.revisions_enabled=1 - ``` - -**Linux** developers will commonly have a default Conan [profile][] that compiles -with GCC and links with libstdc++. -If you are linking with libstdc++ (see profile setting `compiler.libcxx`), -then you will need to choose the `libstdc++11` ABI: + Linux developers will commonly have a default Conan [profile][] that compiles + with GCC and links with libstdc++. + If you are linking with libstdc++ (see profile setting `compiler.libcxx`), + then you will need to choose the `libstdc++11` ABI. ``` # In ~/.conan2/profiles/default, ensure: @@ -126,26 +96,12 @@ then you will need to choose the `libstdc++11` ABI: compiler.libcxx=libstdc++11 ``` + On Windows, you should use the x64 native build tools. + An easy way to do that is to run the shortcut "x64 Native Tools Command + Prompt" for the version of Visual Studio that you have installed. -Ensure inter-operability between `boost::string_view` and `std::string_view` types: - -``` -conan profile update 'conf.tools.build:cxxflags+=["-DBOOST_BEAST_USE_STD_STRING_VIEW"]' default -conan profile update 'env.CXXFLAGS="-DBOOST_BEAST_USE_STD_STRING_VIEW"' default -``` - -If you have other flags in the `conf.tools.build` or `env.CXXFLAGS` sections, make sure to retain the existing flags and append the new ones. You can check them with: -``` -conan profile show default -``` - - -**Windows** developers may need to use the x64 native build tools. -An easy way to do that is to run the shortcut "x64 Native Tools Command -Prompt" for the version of Visual Studio that you have installed. - - Windows developers must also build `rippled` and its dependencies for the x64 - architecture: + Windows developers must also build `xahaud` and its dependencies for the x64 + architecture. ``` # In ~/.conan2/profiles/default, ensure: @@ -181,8 +137,8 @@ Prompt" for the version of Visual Studio that you have installed. conan export external/snappy --version 1.1.10 --user xahaud --channel stable ``` -Export our [Conan recipe for RocksDB](./external/rocksdb). -It does not override paths to dependencies when building with Visual Studio. +5. Export our [Conan recipe for SOCI](./external/soci). + It patches their CMake to correctly import its dependencies. ``` conan export external/soci --version 4.0.3 --user xahaud --channel stable @@ -212,15 +168,13 @@ It does not override paths to dependencies when building with Visual Studio. the `install-folder` or `-if` option to every `conan install` command in the next step. -2. Use conan to generate CMake files for every configuration you want to build: +2. Generate CMake files for every configuration you want to build. ``` conan install .. --output-folder . --build missing --settings build_type=Release conan install .. --output-folder . --build missing --settings build_type=Debug ``` - To build Debug, in the next step, be sure to set `-DCMAKE_BUILD_TYPE=Debug` - For a single-configuration generator, e.g. `Unix Makefiles` or `Ninja`, you only need to run this command once. For a multi-configuration generator, e.g. `Visual Studio`, you may want to @@ -231,13 +185,13 @@ It does not override paths to dependencies when building with Visual Studio. generated by the first. You can pass the build type on the command line with `--settings build_type=$BUILD_TYPE` or in the profile itself, under the section `[settings]` with the key `build_type`. - + If you are using a Microsoft Visual C++ compiler, then you will need to ensure consistency between the `build_type` setting and the `compiler.runtime` setting. - + When `build_type` is `Release`, `compiler.runtime` should be `MT`. - + When `build_type` is `Debug`, `compiler.runtime` should be `MTd`. ``` @@ -249,31 +203,29 @@ It does not override paths to dependencies when building with Visual Studio. `$OUTPUT_FOLDER/build/generators/conan_toolchain.cmake`. Single-config generators: + + ``` + cmake -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release .. + ``` Pass the CMake variable [`CMAKE_BUILD_TYPE`][build_type] - and make sure it matches the one of the `build_type` settings - you chose in the previous step. + and make sure it matches the `build_type` setting you chose in the previous + step. - For example, to build Debug, in the next command, replace "Release" with "Debug" + Multi-config gnerators: ``` - cmake -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release -Dxrpld=ON -Dtests=ON .. + cmake -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake .. ``` + **Note:** You can pass build options for `xahaud` in this step. - Multi-config generators: - - ``` - cmake -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake -Dxrpld=ON -Dtests=ON .. - ``` - - **Note:** You can pass build options for `rippled` in this step. - -5. Build `rippled`. +4. Build `xahaud`. For a single-configuration generator, it will build whatever configuration you passed for `CMAKE_BUILD_TYPE`. For a multi-configuration generator, you must pass the option `--config` to select the build configuration. + The output file is currently named 'rippled'. Single-config generators: @@ -282,13 +234,13 @@ It does not override paths to dependencies when building with Visual Studio. ``` Multi-config generators: - + ``` cmake --build . --config Release cmake --build . --config Debug ``` -6. Test rippled. +5. Test xahaud. Single-config generators: @@ -303,79 +255,19 @@ It does not override paths to dependencies when building with Visual Studio. ./Debug/rippled --unittest ``` - The location of `rippled` in your build directory depends on your CMake + The location of `xahaud` in your build directory depends on your CMake generator. Pass `--help` to see the rest of the command line options. -## Coverage report - -The coverage report is intended for developers using compilers GCC -or Clang (including Apple Clang). It is generated by the build target `coverage`, -which is only enabled when the `coverage` option is set, e.g. with -`--options coverage=True` in `conan` or `-Dcoverage=ON` variable in `cmake` - -Prerequisites for the coverage report: - -- [gcovr tool][gcovr] (can be installed e.g. with [pip][python-pip]) -- `gcov` for GCC (installed with the compiler by default) or -- `llvm-cov` for Clang (installed with the compiler by default) -- `Debug` build type - -A coverage report is created when the following steps are completed, in order: - -1. `rippled` binary built with instrumentation data, enabled by the `coverage` - option mentioned above -2. completed run of unit tests, which populates coverage capture data -3. completed run of the `gcovr` tool (which internally invokes either `gcov` or `llvm-cov`) - to assemble both instrumentation data and the coverage capture data into a coverage report - -The above steps are automated into a single target `coverage`. The instrumented -`rippled` binary can also be used for regular development or testing work, at -the cost of extra disk space utilization and a small performance hit -(to store coverage capture). In case of a spurious failure of unit tests, it is -possible to re-run the `coverage` target without rebuilding the `rippled` binary -(since it is simply a dependency of the coverage report target). It is also possible -to select only specific tests for the purpose of the coverage report, by setting -the `coverage_test` variable in `cmake` - -The default coverage report format is `html-details`, but the user -can override it to any of the formats listed in `cmake/CodeCoverage.cmake` -by setting the `coverage_format` variable in `cmake`. It is also possible -to generate more than one format at a time by setting the `coverage_extra_args` -variable in `cmake`. The specific command line used to run the `gcovr` tool will be -displayed if the `CODE_COVERAGE_VERBOSE` variable is set. - -By default, the code coverage tool runs parallel unit tests with `--unittest-jobs` - set to the number of available CPU cores. This may cause spurious test -errors on Apple. Developers can override the number of unit test jobs with -the `coverage_test_parallelism` variable in `cmake`. - -Example use with some cmake variables set: - -``` -cd .build -conan install .. --output-folder . --build missing --settings build_type=Debug -cmake -DCMAKE_BUILD_TYPE=Debug -Dcoverage=ON -Dxrpld=ON -Dtests=ON -Dcoverage_test_parallelism=2 -Dcoverage_format=html-details -Dcoverage_extra_args="--json coverage.json" -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake .. -cmake --build . --target coverage -``` - -After the `coverage` target is completed, the generated coverage report will be -stored inside the build directory, as either of: - -- file named `coverage.`_extension_ , with a suitable extension for the report format, or -- directory named `coverage`, with the `index.html` and other files inside, for the `html-details` or `html-nested` report formats. - - ## Options | Option | Default Value | Description | | --- | ---| ---| | `assert` | OFF | Enable assertions. -| `coverage` | OFF | Prepare the coverage report. | -| `san` | N/A | Enable a sanitizer with Clang. Choices are `thread` and `address`. | -| `tests` | OFF | Build tests. | +| `reporting` | OFF | Build the reporting mode feature. | +| `tests` | ON | Build tests. | | `unity` | ON | Configure a unity build. | -| `xrpld` | OFF | Build the xrpld (`rippled`) application, and not just the libxrpl library. | +| `san` | N/A | Enable a sanitizer with Clang. Choices are `thread` and `address`. | [Unity builds][5] may be faster for the first build (at the cost of much more memory) since they concatenate sources into fewer @@ -414,18 +306,6 @@ tools.build:cxxflags=["-Wno-missing-template-arg-list-after-template-kw"] ``` -### call to 'async_teardown' is ambiguous - -If you are compiling with an early version of Clang 16, then you might hit -a [regression][6] when compiling C++20 that manifests as an [error in a Boost -header][7]. You can workaround it by adding this preprocessor definition: - -``` -conan profile update 'env.CXXFLAGS="-DBOOST_ASIO_DISABLE_CONCEPTS"' default -conan profile update 'conf.tools.build:cxxflags+=["-DBOOST_ASIO_DISABLE_CONCEPTS"]' default -``` - - ### recompile with -fPIC If you get a linker error suggesting that you recompile Boost with @@ -577,10 +457,6 @@ but it is more convenient to put them in a [profile][profile]. [1]: https://github.com/conan-io/conan-center-index/issues/13168 [5]: https://en.wikipedia.org/wiki/Unity_build -[6]: https://github.com/boostorg/beast/issues/2648 -[7]: https://github.com/boostorg/beast/issues/2661 -[gcovr]: https://gcovr.com/en/stable/getting-started.html -[python-pip]: https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/ [build_type]: https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html [runtime]: https://cmake.org/cmake/help/latest/variable/CMAKE_MSVC_RUNTIME_LIBRARY.html [toolchain]: https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html diff --git a/cfg/xahaud-example.cfg b/cfg/xahaud-example.cfg index f46cce7e8..098bb19ce 100644 --- a/cfg/xahaud-example.cfg +++ b/cfg/xahaud-example.cfg @@ -13,7 +13,7 @@ # # 4. HTTPS Client # -# 5. +# 5. Reporting Mode # # 6. Database # @@ -282,14 +282,12 @@ # ssl_cert # # Specifies the path to the SSL certificate file in PEM format. -# This is not needed if the chain includes it. Use ssl_chain if -# your certificate includes one or more intermediates. +# This is not needed if the chain includes it. # # ssl_chain # # If you need a certificate chain, specify the path to the # certificate chain here. The chain may include the end certificate. -# This must be used if the certificate includes intermediates. # # ssl_ciphers = # @@ -387,32 +385,15 @@ # # # -# [compression] -# -# true or false -# -# true - enables compression -# false - disables compression [default]. -# -# The xahaud server can save bandwidth by compressing its peer-to-peer communications, -# at a cost of greater CPU usage. If you enable link compression, -# the server automatically compresses communications with peer servers -# that also have link compression enabled. -# https://xrpl.org/enable-link-compression.html -# -# -# # [ips] # # 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. # -# One address or domain name per line is allowed. A port may be specified -# after adding a space to the address. If a port is not specified, the default -# port of 2459 will be used. Many servers still use the legacy port of 51235. -# To connect to such servers, you must specify the port number. The ordering -# of entries does not generally matter. +# One address or domain name per line is allowed. A port may must be +# specified after adding a space to the address. The ordering of entries +# does not generally matter. # # The default list of entries is: # - hubs.xahau.as16089.net 21337 @@ -477,6 +458,19 @@ # # # +# [sntp_servers] +# +# IP address or domain of NTP servers to use for time synchronization. +# +# These NTP servers are suitable for xahaud servers located in the United +# States: +# time.windows.com +# time.apple.com +# time.nist.gov +# pool.ntp.org +# +# +# # [max_transactions] # # Configure the maximum number of transactions to have in the job queue @@ -881,6 +875,119 @@ # #------------------------------------------------------------------------------- # +# 5. Reporting Mode +# +#------------ +# +# 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, 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 +# optional. +# +# +# [] +# +# A series of key/value pairs that specify an ETL source. +# +# source_ip = +# +# Required. IP address of the ETL source. Can also be a DNS record. +# +# source_ws_port = +# +# Required. Port on which ETL source is accepting unencrypted websocket +# connections. +# +# source_grpc_port = +# +# Required for ETL. Port on which ETL source is accepting gRPC requests. +# If this option is ommitted, this ETL source cannot actually be used for +# ETL; the Reporting Mode server can still forward RPCs to this ETL +# source, but cannot extract data from this ETL source. +# +# +# Key-value pairs (all optional): +# +# read_only Valid values: 0, 1. Default is 0. If set to 1, the server +# will start in strict read-only mode, and will not perform +# ETL. The server will still handle RPC requests, and will +# still forward RPC requests that require access to the p2p +# network. +# +# start_sequence +# Sequence of first ledger to extract if the database is empty. +# ETL extracts ledgers in order. If this setting is absent and +# the database is empty, ETL will start with the next ledger +# validated by the network. If this setting is present and the +# database is not empty, an exception is thrown. +# +# num_markers Degree of parallelism used during the initial ledger +# download. Only used if the database is empty. Valid values +# are 1-256. A higher degree of parallelism results in a +# faster download, but puts more load on the ETL source. +# Default is 2. +# +# Example: +# +# [reporting] +# etl_source1 +# etl_source2 +# read_only=0 +# start_sequence=32570 +# num_markers=8 +# +# [etl_source1] +# source_ip=1.2.3.4 +# source_ws_port=6005 +# source_grpc_port=50051 +# +# [etl_source2] +# source_ip=5.6.7.8 +# source_ws_port=6005 +# source_grpc_port=50051 +# +# Minimal Example: +# +# [reporting] +# etl_source1 +# +# [etl_source1] +# source_ip=1.2.3.4 +# source_ws_port=6005 +# source_grpc_port=50051 +# +# +# Notes: +# +# Reporting Mode requires Postgres (instead of SQLite). The Postgres +# connection info is specified under the [ledger_tx_tables] config section; +# see the Database section for further documentation. +# +# Each ETL source specified must have gRPC enabled (by adding a [port_grpc] +# section to the config). It is recommended to add a secure_gateway entry to +# the gRPC section, in order to bypass the server's rate limiting. +# This section needs to be added to the config of the ETL source, not +# the config of the reporting node. In the example below, the +# reporting server is running at 127.0.0.1. Multiple IPs can be +# specified in secure_gateway via a comma separated list. +# +# [port_grpc] +# ip = 0.0.0.0 +# port = 50051 +# secure_gateway = 127.0.0.1 +# +# +#------------------------------------------------------------------------------- +# # 6. Database # #------------ @@ -888,7 +995,13 @@ # 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. +# 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 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 @@ -930,6 +1043,14 @@ # keeping full history is not advised, and using online delete is # recommended. # +# type = Cassandra +# +# Apache Cassandra is an open-source, distributed key-value store - see +# https://cassandra.apache.org/ for more details. +# +# Cassandra is an alternative backend to be used only with Reporting Mode. +# See the Reporting Mode section for more details about Reporting Mode. +# # type = RWDB # # RWDB is a high-performance memory store written by XRPL-Labs and optimized @@ -941,6 +1062,27 @@ # # path Location to store the database # +# Required keys for RWDB: +# +# online_delete Required. RWDB stores data in memory and will +# grow unbounded without online_delete. See the +# online_delete section below. +# +# Required keys for Cassandra: +# +# contact_points IP of a node in the Cassandra cluster +# +# port CQL Native Transport Port +# +# secure_connect_bundle +# Absolute path to a secure connect bundle. When using +# a secure connect bundle, contact_points and port are +# not required. +# +# keyspace Name of Cassandra keyspace to use +# +# table_name Name of table in above keyspace to use +# # Optional keys # # cache_size Size of cache for database records. Default is 16384. @@ -957,7 +1099,7 @@ # default value for the unspecified parameter. # # Note: the cache will not be created if online_delete -# is specified. +# is specified, or if shards are used. # # fast_load Boolean. If set, load the last persisted ledger # from disk upon process start before syncing to @@ -980,6 +1122,10 @@ # 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 +# value is present either [node_db] or [shard_db], +# it must be defined with the same value in both +# sections. # # Optional keys for NuDB only: # @@ -1055,6 +1201,25 @@ # checking until healthy. # Default is 5. # +# Optional keys for Cassandra: +# +# username Username to use if Cassandra cluster requires +# authentication +# +# password Password to use if Cassandra cluster requires +# authentication +# +# max_requests_outstanding +# Limits the maximum number of concurrent database +# writes. Default is 10 million. For slower clusters, +# large numbers of concurrent writes can overload the +# cluster. Setting this option can help eliminate +# write timeouts and other write errors due to the +# cluster being overloaded. +# io_threads +# Set the number of IO threads used by the +# Cassandra driver. Defaults to 4. +# # Notes: # The 'node_db' entry configures the primary, persistent storage. # @@ -1071,6 +1236,32 @@ # your xahaud.cfg file. # Partial pathnames are relative to the location of the xahaud executable. # +# [shard_db] Settings for the Shard Database (optional) +# +# Format (without spaces): +# One or more lines of case-insensitive key / value pairs: +# '=' +# ... +# +# Example: +# path=db/shards/nudb +# +# Required keys: +# path Location to store the database +# +# Optional keys: +# max_historical_shards +# The maximum number of historical shards +# to store. +# +# [historical_shard_paths] Additional storage paths for the Shard Database (optional) +# +# Format (without spaces): +# One or more lines, each expressing a full path for storing historical shards: +# /mnt/disk1 +# /mnt/disk2 +# ... +# # [sqlite] Tuning settings for the SQLite databases (optional) # # Format (without spaces): @@ -1150,18 +1341,40 @@ # This setting may not be combined with the # "safety_level" setting. # -# page_size Valid values: integer (MUST be power of 2 between 512 and 65536) -# The default is 4096 bytes. This setting determines -# the size of a page in the transaction.db file. -# See https://www.sqlite.org/pragma.html#pragma_page_size -# for more details about the available options. +# [ledger_tx_tables] (optional) # -# journal_size_limit Valid values: integer -# The default is 1582080. This setting limits -# the size of the journal for transaction.db file. When the limit is -# reached, older entries will be deleted. -# See https://www.sqlite.org/pragma.html#pragma_journal_size_limit -# for more details about the available options. +# 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 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 +# address. Default is empty. +# +# use_tx_tables Valid values: 1, 0 +# The default is 1 (true). Determines whether to use +# the SQLite transaction database. If set to 0, +# 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. +# +# max_connections Valid values: any positive integer up to 64 bit +# storage length. This configures the maximum +# number of concurrent connections to postgres. +# Default is the maximum possible value to +# fit in a 64 bit integer. +# +# timeout Number of seconds after which idle postgres +# connections are discconnected. If set to 0, +# connections never timeout. Default is 600. +# +# +# remember_ip Value values: 1, 0 +# Default is 1 (true). Whether to cache host and +# port connection settings. # # #------------------------------------------------------------------------------- @@ -1427,12 +1640,6 @@ # Admin level API commands over Secure Websockets, when originating # from the same machine (via the loopback adapter at 127.0.0.1). # -# "grpc" -# -# ETL commands for Clio. We recommend setting secure_gateway -# in this section to a comma-separated list of the addresses -# of your Clio servers, in order to bypass xahaud's rate limiting. -# # This port is commented out but can be enabled by removing # the '#' from each corresponding line including the entry under [server] # @@ -1477,7 +1684,6 @@ port = 6009 ip = 127.0.0.1 admin = 127.0.0.1 protocol = ws -send_queue_limit = 500 [port_grpc] port = 50051 @@ -1488,7 +1694,6 @@ secure_gateway = 127.0.0.1 #port = 6008 #ip = 127.0.0.1 #protocol = wss -#send_queue_limit = 500 #------------------------------------------------------------------------------- @@ -1505,22 +1710,45 @@ secure_gateway = 127.0.0.1 # when the node has approximately two times the "online_delete" value of # ledgers. No external administrative command is required to initiate # deletion. +[ledger_history] +256 + [node_db] type=NuDB path=/opt/xahaud/db/nudb -online_delete=512 +online_delete=256 advisory_delete=0 [database_path] /opt/xahaud/db +# To use Postgres, uncomment this section and fill in the appropriate connection +# info. Postgres can only be used in Reporting Mode. +# To disable writing to the transaction database, uncomment this section, and +# set use_tx_tables=0 +# [ledger_tx_tables] +# conninfo = postgres://[username]:[password]@[ip]/[database] +# use_tx_tables=1 + + # This needs to be an absolute directory reference, not a relative one. # Modify this value as required. [debug_logfile] /var/log/xahaud/debug.log -# To use the Xahau test network +[sntp_servers] +time.windows.com +time.apple.com +time.nist.gov +pool.ntp.org + +# Use the following [ips] section for the main network: +[ips] +bacab.alloy.ee 21337 +hubs.xahau.as16089.net 21337 + +# To use the Xahau Test Network # (see https://xahau.network/docs/infrastructure/installing-xahaud), # use the following [ips] section: # [ips] @@ -1546,3 +1774,22 @@ validators-xahau.txt # set to ssl_verify to 0. [ssl_verify] 1 + +# Define which network xahaud is connecting to +# 21337 for the Main Xahau Network +# 21338 for the Test Xahau Network +[network_id] +21337 +# 21338 + + +# To run in Reporting Mode, uncomment this section and fill in the appropriate +# connection info for one or more ETL sources. +# [reporting] +# etl_source +# +# +# [etl_source] +# source_grpc_port=50051 +# source_ws_port=6005 +# source_ip=127.0.0.1 diff --git a/docs/build/environment.md b/docs/build/environment.md index 7fe89ffb4..524cd5be1 100644 --- a/docs/build/environment.md +++ b/docs/build/environment.md @@ -11,11 +11,11 @@ platforms: Linux, macOS, or Windows. Package ecosystems vary across Linux distributions, so there is no one set of instructions that will work for every Linux user. These instructions are written for Ubuntu 22.04. -They are largely copied from the [script][1] used to configure our Docker +They are largely copied from the [script][1] used to configure a Docker container for continuous integration. That script handles many more responsibilities. These instructions are just the bare minimum to build one configuration of -rippled. +xahaud. You can check that codebase for other Linux distributions and versions. If you cannot find yours there, then we hope that these instructions can at least guide you in the right diff --git a/docs/build/install.md b/docs/build/install.md index af0d6f335..b9f0e4840 100644 --- a/docs/build/install.md +++ b/docs/build/install.md @@ -1,159 +1,30 @@ -This document contains instructions for installing rippled. -The APT package manager is common on Debian-based Linux distributions like -Ubuntu, -while the YUM package manager is common on Red Hat-based Linux distributions -like CentOS. -Installing from source is an option for all platforms, -and the only supported option for installing custom builds. +Comprehensive instructions for installing and running xahaud are available on the [https://Xahau.Network](https://xahau.network/docs/infrastructure/installing-xahaud) documentation website. +## Create the Runtime Environment +xahaud can be [built from source](../../BUILD.md) or installed using the binary files available from [https://build.xahau.tech](https://build.xahau.tech/). After obtaining a working xahaud binary, users will need to provide a suitable runtime environment. The following setup can be used for Linux or Docker environments. -## From source - -From a source build, you can install rippled and libxrpl using CMake's -`--install` mode: +1. Create or download two configuration files: the main xahaud.cfg configuration file and a second validators-xahau.txt file defining which validators or UNL list publishers are trusted. The default location for these files in this xahaud repository is `cfg/`. +2. Provide a directory structure that is congruent with the contents of xahaud.cfg. This will include a location for logfiles, such as `/var/log/xahaud/`, as well as database files, `/opt/xahaud/db/`. Configuration files are, by default, sourced from `/etc/xahaud/`. It is possible to provide a symbolic link, if users wish to store configuration files elsewhere. +3. If desired, created a xahaud user and group, and change ownership of the binary and directories. Servers used for validating nodes should use the most restrictive permissions possible for `xahaud.cfg`, as the validation token is stored therein. +4. If desired, create a systemd service file: `/etc/systemd/system/xahaud.service`, enabling xahaud to run as a daemon. Alternately, run: `/path/to/binary/xahaud --conf=/path/to/xahaud.cfg`. +## Example systemd Service File ``` -cmake --install . --prefix /opt/local +[Unit] +Description=Xahaud Daemon +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +ExecStart=/path/to/xahaud --silent --conf /path/to/xahaud.cfg +Restart=on-failure +User=xahaud +Group=xahaud +LimitNOFILE=65536 + +[Install] +WantedBy=multi-user.target ``` -The default [prefix][1] is typically `/usr/local` on Linux and macOS and -`C:/Program Files/rippled` on Windows. - -[1]: https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_PREFIX.html - - -## With the APT package manager - -1. Update repositories: - - sudo apt update -y - -2. Install utilities: - - sudo apt install -y apt-transport-https ca-certificates wget gnupg - -3. Add Ripple's package-signing GPG key to your list of trusted keys: - - sudo mkdir /usr/local/share/keyrings/ - wget -q -O - "https://repos.ripple.com/repos/api/gpg/key/public" | gpg --dearmor > ripple-key.gpg - sudo mv ripple-key.gpg /usr/local/share/keyrings - - -4. Check the fingerprint of the newly-added key: - - gpg /usr/local/share/keyrings/ripple-key.gpg - - The output should include an entry for Ripple such as the following: - - gpg: WARNING: no command supplied. Trying to guess what you mean ... - pub rsa3072 2019-02-14 [SC] [expires: 2026-02-17] - C0010EC205B35A3310DC90DE395F97FFCCAFD9A2 - uid TechOps Team at Ripple - sub rsa3072 2019-02-14 [E] [expires: 2026-02-17] - - - In particular, make sure that the fingerprint matches. (In the above example, the fingerprint is on the third line, starting with `C001`.) - -4. Add the appropriate Ripple repository for your operating system version: - - echo "deb [signed-by=/usr/local/share/keyrings/ripple-key.gpg] https://repos.ripple.com/repos/rippled-deb focal stable" | \ - sudo tee -a /etc/apt/sources.list.d/ripple.list - - The above example is appropriate for **Ubuntu 20.04 Focal Fossa**. For other operating systems, replace the word `focal` with one of the following: - - - `jammy` for **Ubuntu 22.04 Jammy Jellyfish** - - `bionic` for **Ubuntu 18.04 Bionic Beaver** - - `bullseye` for **Debian 11 Bullseye** - - `buster` for **Debian 10 Buster** - - If you want access to development or pre-release versions of `rippled`, use one of the following instead of `stable`: - - - `unstable` - Pre-release builds ([`release` branch](https://github.com/ripple/rippled/tree/release)) - - `nightly` - Experimental/development builds ([`develop` branch](https://github.com/ripple/rippled/tree/develop)) - - **Warning:** Unstable and nightly builds may be broken at any time. Do not use these builds for production servers. - -5. Fetch the Ripple repository. - - sudo apt -y update - -6. Install the `rippled` software package: - - sudo apt -y install rippled - -7. Check the status of the `rippled` service: - - systemctl status rippled.service - - The `rippled` service should start automatically. If not, you can start it manually: - - sudo systemctl start rippled.service - -8. Optional: allow `rippled` to bind to privileged ports. - - This allows you to serve incoming API requests on port 80 or 443. (If you want to do so, you must also update the config file's port settings.) - - sudo setcap 'cap_net_bind_service=+ep' /opt/ripple/bin/rippled - - -## With the YUM package manager - -1. Install the Ripple RPM repository: - - Choose the appropriate RPM repository for the stability of releases you want: - - - `stable` for the latest production release (`master` branch) - - `unstable` for pre-release builds (`release` branch) - - `nightly` for experimental/development builds (`develop` branch) - - *Stable* - - cat << REPOFILE | sudo tee /etc/yum.repos.d/ripple.repo - [ripple-stable] - name=XRP Ledger Packages - enabled=1 - gpgcheck=0 - repo_gpgcheck=1 - baseurl=https://repos.ripple.com/repos/rippled-rpm/stable/ - gpgkey=https://repos.ripple.com/repos/rippled-rpm/stable/repodata/repomd.xml.key - REPOFILE - - *Unstable* - - cat << REPOFILE | sudo tee /etc/yum.repos.d/ripple.repo - [ripple-unstable] - name=XRP Ledger Packages - enabled=1 - gpgcheck=0 - repo_gpgcheck=1 - baseurl=https://repos.ripple.com/repos/rippled-rpm/unstable/ - gpgkey=https://repos.ripple.com/repos/rippled-rpm/unstable/repodata/repomd.xml.key - REPOFILE - - *Nightly* - - cat << REPOFILE | sudo tee /etc/yum.repos.d/ripple.repo - [ripple-nightly] - name=XRP Ledger Packages - enabled=1 - gpgcheck=0 - repo_gpgcheck=1 - baseurl=https://repos.ripple.com/repos/rippled-rpm/nightly/ - gpgkey=https://repos.ripple.com/repos/rippled-rpm/nightly/repodata/repomd.xml.key - REPOFILE - -2. Fetch the latest repo updates: - - sudo yum -y update - -3. Install the new `rippled` package: - - sudo yum install -y rippled - -4. Configure the `rippled` service to start on boot: - - sudo systemctl enable rippled.service - -5. Start the `rippled` service: - - sudo systemctl start rippled.service +After the systemd service file is installed, it must be loaded with: `systemctl daemon-reload`. xahaud can then be enabled: `systemctl enable --now xahaud`. diff --git a/hook/extern.h b/hook/extern.h index 667cec2a4..d9c14d7a6 100644 --- a/hook/extern.h +++ b/hook/extern.h @@ -329,5 +329,12 @@ meta_slot(uint32_t slot_no); extern int64_t xpop_slot(uint32_t slot_no_tx, uint32_t slot_no_meta); +extern int64_t +prepare( + uint32_t write_ptr, + uint32_t write_len, + uint32_t read_ptr, + uint32_t read_len); + #define HOOK_EXTERN #endif // HOOK_EXTERN diff --git a/hook/sfcodes.h b/hook/sfcodes.h index d0e9b7a78..10977f65d 100644 --- a/hook/sfcodes.h +++ b/hook/sfcodes.h @@ -157,6 +157,8 @@ #define sfEmittedTxnID ((5U << 16U) + 97U) #define sfHookCanEmit ((5U << 16U) + 96U) #define sfCron ((5U << 16U) + 95U) +#define sfHookOnIncoming ((5U << 16U) + 94U) +#define sfHookOnOutgoing ((5U << 16U) + 93U) #define sfNumber ((9U << 16U) + 1U) #define sfAmount ((6U << 16U) + 1U) #define sfBalance ((6U << 16U) + 2U) diff --git a/include/xrpl/hook/Enum.h b/include/xrpl/hook/Enum.h index 571108170..29f7ed16b 100644 --- a/include/xrpl/hook/Enum.h +++ b/include/xrpl/hook/Enum.h @@ -14,6 +14,7 @@ // Override uint256, Feature and Rules for guard checker build #define uint256 std::string #define featureHooksUpdate1 "1" +#define featureHooksUpdate2 "1" #define fix20250131 "1" namespace hook_api { struct Rules diff --git a/include/xrpl/hook/HookAPI.h b/include/xrpl/hook/HookAPI.h index fd8e46271..91c23e091 100644 --- a/include/xrpl/hook/HookAPI.h +++ b/include/xrpl/hook/HookAPI.h @@ -58,6 +58,9 @@ public: // sto_erase(): same as sto_emplace with field_object = nullopt /// etxn APIs + Expected + prepare(Slice const& txBlob) const; + Expected, HookReturnCode> emit(Slice const& txBlob) const; @@ -329,14 +332,15 @@ private: int32_t, parse_error> get_stobject_length( - unsigned char* start, // in - begin iterator - unsigned char* maxptr, // in - end iterator - int& type, // out - populated by serialized type code - int& field, // out - populated by serialized field code - int& payload_start, // out - the start of actual payload data for - // this type - int& payload_length, // out - the length of actual payload data for - // this type + unsigned char* start, // in - begin iterator + unsigned char* maxptr, // in - end iterator + int& type, // out - populated by serialized type code + int& field, // out - populated by serialized field code + int& payload_start, // out - the start of actual payload data for + // this type + int& payload_length, // out - the length of actual payload data for + // this type + Rules const& rules, int recursion_depth = 0) // used internally const; }; diff --git a/include/xrpl/hook/hook_api.macro b/include/xrpl/hook/hook_api.macro index 6f76f4f8d..9f92cf844 100644 --- a/include/xrpl/hook/hook_api.macro +++ b/include/xrpl/hook/hook_api.macro @@ -367,3 +367,8 @@ HOOK_API_DEFINITION( HOOK_API_DEFINITION( int64_t, xpop_slot, (uint32_t, uint32_t), featureHooksUpdate1) + +// int64_t prepare(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len); +HOOK_API_DEFINITION( + int64_t, prepare, (uint32_t, uint32_t, uint32_t, uint32_t), + featureHooksUpdate2) diff --git a/include/xrpl/protocol/Feature.h b/include/xrpl/protocol/Feature.h index 823bc03a2..ed0732930 100644 --- a/include/xrpl/protocol/Feature.h +++ b/include/xrpl/protocol/Feature.h @@ -80,7 +80,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 = 110; +static constexpr std::size_t numFeatures = 113; /** Amendments that this server supports and the default voting behavior. Whether they are enabled depends on the Rules defined in the validated diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 1dfe26779..eb43d9d25 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -24,11 +24,14 @@ #error "undefined macro: XRPL_FIX" #endif +// clang-format off + // Add new amendments to the top of this list. // Keep it sorted in reverse chronological order. // If you add an amendment here, then do not forget to increment `numFeatures` // in include/xrpl/protocol/Feature.h. +XRPL_FEATURE(HookAPISerializedType240, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(PermissionedDomains, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(DynamicNFT, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(Credentials, Supported::no, VoteBehavior::DefaultNo) @@ -50,6 +53,8 @@ XRPL_FEATURE(DID, Supported::no, VoteBehavior::DefaultNo XRPL_FIX (DisallowIncomingV1, Supported::yes, VoteBehavior::DefaultYes) XRPL_FEATURE(XChainBridge, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(AMM, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FEATURE(HookOnV2, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FEATURE(HooksUpdate2, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (HookAPI20251128, Supported::yes, VoteBehavior::DefaultYes) XRPL_FIX (CronStacking, Supported::yes, VoteBehavior::DefaultYes) XRPL_FEATURE(ExtendedHookState, Supported::yes, VoteBehavior::DefaultNo) @@ -142,3 +147,4 @@ XRPL_FIX (NFTokenDirV1, Supported::yes, VoteBehavior::Obsolete) XRPL_FEATURE(NonFungibleTokensV1, Supported::yes, VoteBehavior::Obsolete) XRPL_FEATURE(CryptoConditionsSuite, Supported::yes, VoteBehavior::Obsolete) +// clang-format on diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index d3fdaed2f..dce9f6bcb 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -93,7 +93,9 @@ LEDGER_ENTRY(ltCHECK, 0x0043, Check, check, ({ */ LEDGER_ENTRY(ltHOOK_DEFINITION, 'D', HookDefinition, hook_definition, ({ {sfHookHash, soeREQUIRED}, - {sfHookOn, soeREQUIRED}, + {sfHookOn, soeOPTIONAL}, + {sfHookOnIncoming, soeOPTIONAL}, + {sfHookOnOutgoing, soeOPTIONAL}, {sfHookCanEmit, soeOPTIONAL}, {sfHookNamespace, soeREQUIRED}, {sfHookParameters, soeREQUIRED}, diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro index eba81fb11..1b94377d5 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -215,6 +215,8 @@ TYPED_SFIELD(sfGovernanceMarks, UINT256, 98) TYPED_SFIELD(sfEmittedTxnID, UINT256, 97) TYPED_SFIELD(sfHookCanEmit, UINT256, 96) TYPED_SFIELD(sfCron, UINT256, 95) +TYPED_SFIELD(sfHookOnIncoming, UINT256, 94) +TYPED_SFIELD(sfHookOnOutgoing, UINT256, 93) // number (common) TYPED_SFIELD(sfNumber, NUMBER, 1) diff --git a/include/xrpl/protocol/jss.h b/include/xrpl/protocol/jss.h index c6875140d..499f24ed9 100644 --- a/include/xrpl/protocol/jss.h +++ b/include/xrpl/protocol/jss.h @@ -78,6 +78,8 @@ JSS(HookCanEmit); // field JSS(HookHash); // field JSS(HookNamespace); // field JSS(HookOn); // field +JSS(HookOnIncoming); // field +JSS(HookOnOutgoing); // field JSS(Hooks); // field JSS(HookGrants); // field JSS(HookParameters); // field @@ -96,6 +98,7 @@ JSS(Issuer); // in: Credential transactions JSS(InvoiceID); // field JSS(LastLedgerSequence); // in: TransactionSign; field JSS(LastUpdateTime); // field. +JSS(FirstLedgerSequence); // in: TransactionSign; field JSS(LimitAmount); // field. JSS(NetworkID); // field. JSS(LPTokenOut); // in: AMM Liquidity Provider deposit tokens @@ -119,6 +122,7 @@ JSS(Signer); // field. JSS(Signers); // field. JSS(HookStateData); // field. JSS(HookStateKey); // field. +JSS(EmitDetails); // field. JSS(SigningPubKey); // field. JSS(Subject); // in: Credential transactions JSS(TakerGets); // field. diff --git a/src/libxrpl/protocol/InnerObjectFormats.cpp b/src/libxrpl/protocol/InnerObjectFormats.cpp index 9ad0361a6..ea0f59851 100644 --- a/src/libxrpl/protocol/InnerObjectFormats.cpp +++ b/src/libxrpl/protocol/InnerObjectFormats.cpp @@ -92,7 +92,9 @@ InnerObjectFormats::InnerObjectFormats() {{sfCreateCode, soeREQUIRED}, {sfHookNamespace, soeREQUIRED}, {sfHookParameters, soeREQUIRED}, - {sfHookOn, soeREQUIRED}, + {sfHookOn, soeOPTIONAL}, + {sfHookOnIncoming, soeOPTIONAL}, + {sfHookOnOutgoing, soeOPTIONAL}, {sfHookCanEmit, soeOPTIONAL}, {sfHookApiVersion, soeREQUIRED}, {sfFlags, soeREQUIRED}, @@ -106,6 +108,8 @@ InnerObjectFormats::InnerObjectFormats() {sfHookNamespace, soeOPTIONAL}, {sfHookParameters, soeOPTIONAL}, {sfHookOn, soeOPTIONAL}, + {sfHookOnIncoming, soeOPTIONAL}, + {sfHookOnOutgoing, soeOPTIONAL}, {sfHookCanEmit, soeOPTIONAL}, {sfHookApiVersion, soeOPTIONAL}, {sfFlags, soeOPTIONAL}}); diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index 8d45f54cd..7758a6ae1 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -487,7 +487,7 @@ isMemoOkay(STObject const& st, std::string& reason) if (!paramObj->isFieldPresent(sfHookParameterValue) || paramObj->getFieldVL(sfHookParameterValue).size() > maxVal) { - reason = "HookParameterValue cannot exceed 128 bytes."; + reason = "HookParameterValue cannot exceed 256 bytes."; return false; } } diff --git a/src/test/app/HookAPI_test.cpp b/src/test/app/HookAPI_test.cpp index c71cd5745..e9ba5592e 100644 --- a/src/test/app/HookAPI_test.cpp +++ b/src/test/app/HookAPI_test.cpp @@ -75,6 +75,84 @@ public: BEAST_EXPECT(true); } + void + test_prepare(FeatureBitset features) + { + testcase("Test prepare"); + using namespace jtx; + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + + using namespace hook_api; + Env env{*this, features}; + + STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {}); + OpenView ov{*env.current()}; + ApplyContext applyCtx = createApplyContext(env, ov, invokeTx); + + STTx const emitInvokeTx = STTx(ttINVOKE, [&](STObject& obj) {}); + + { + // PREREQUISITE_NOT_MET + auto hookCtx = + makeStubHookContext(applyCtx, alice.id(), alice.id(), {}); + auto& api = hookCtx.api(); + auto tx = emitInvokeTx; + Serializer s = tx.getSerializer(); + s.add8(0); // invalid value + auto const result = api.prepare(s.slice()); + BEAST_EXPECT(result.error() == PREREQUISITE_NOT_MET); + } + + { + // Invalid txn + auto hookCtx = makeStubHookContext( + applyCtx, + alice.id(), + alice.id(), + { + .expected_etxn_count = 1, + }); + auto& api = hookCtx.api(); + auto tx = emitInvokeTx; + Serializer s = tx.getSerializer(); + s.add8(0); // invalid value + auto const result = api.prepare(s.slice()); + BEAST_EXPECT(result.error() == INVALID_ARGUMENT); + } + + { + // success + auto hookCtx = makeStubHookContext( + applyCtx, + alice.id(), + alice.id(), + { + .expected_etxn_count = 1, + }); + auto& api = hookCtx.api(); + auto tx = emitInvokeTx; + Serializer s = tx.getSerializer(); + auto const result = api.prepare(s.slice()); + BEAST_EXPECT(result.has_value()); + + SerialIter sit(Slice(result.value().data(), result.value().size())); + STObject st(sit, sfGeneric); + BEAST_EXPECT(st.getFieldAmount(sfFee) > XRPAmount(0)); + BEAST_EXPECT(st.getFieldU32(sfSequence) == 0); + BEAST_EXPECT(st.getAccountID(sfAccount) == alice.id()); + auto const seq = applyCtx.view().info().seq; + BEAST_EXPECT(st.getFieldU32(sfFirstLedgerSequence) == seq + 1); + BEAST_EXPECT(st.getFieldU32(sfLastLedgerSequence) == seq + 5); + BEAST_EXPECT(st.isFieldPresent(sfEmitDetails)); + + auto const result2 = + api.emit(Slice(result.value().data(), result.value().size())); + BEAST_EXPECT(result2.has_value()); + } + } + void test_emit(FeatureBitset features) { @@ -4441,6 +4519,7 @@ public: test_rollback(features); testGuards(features); + test_prepare(features); test_emit(features); test_etxn_burden(features); test_etxn_generation(features); diff --git a/src/test/app/SetHook_test.cpp b/src/test/app/SetHook_test.cpp index 5caf6b33b..4c2fc39d3 100644 --- a/src/test/app/SetHook_test.cpp +++ b/src/test/app/SetHook_test.cpp @@ -238,7 +238,12 @@ public: using namespace jtx; - Env env{*this, features}; + Env env{ + *this, + envconfig(), + features, + nullptr, + beast::severities::kDisabled}; auto const alice = Account{"alice"}; auto const gw = Account{"gateway"}; @@ -710,14 +715,20 @@ public: env.close(); } - // grants, parameters, hookon, hookcanemit, hookapiversion, - // hooknamespace keys must be absent + // grants, parameters, hookon, hookonincoming, hookonoutgoing, + // hookcanemit, hookapiversion, hooknamespace keys must be absent for (auto const& [key, value] : JSSMap{ {jss::HookGrants, Json::arrayValue}, {jss::HookParameters, Json::arrayValue}, {jss::HookOn, "000000000000000000000000000000000000000000000000000000000000" "0000"}, + {jss::HookOnIncoming, + "000000000000000000000000000000000000000000000000000000000000" + "0000"}, + {jss::HookOnOutgoing, + "000000000000000000000000000000000000000000000000000000000000" + "0000"}, {jss::HookCanEmit, "000000000000000000000000000000000000000000000000000000000000" "0000"}, @@ -733,7 +744,8 @@ public: jv[jss::Hooks][0U][jss::Hook] = iv; env(jv, M("Hook DELETE operation cannot include: grants, params, " - "hookon, hookcanemit, apiversion, namespace"), + "hookon, HookOnIncoming, HookOnOutgoing, hookcanemit, " + "apiversion, namespace"), HSFEE, ter(temMALFORMED)); env.close(); @@ -894,6 +906,12 @@ public: {jss::HookOn, "000000000000000000000000000000000000000000000000000000000000" "0000"}, + {jss::HookOnIncoming, + "000000000000000000000000000000000000000000000000000000000000" + "0000"}, + {jss::HookOnOutgoing, + "000000000000000000000000000000000000000000000000000000000000" + "0000"}, {jss::HookCanEmit, "000000000000000000000000000000000000000000000000000000000000" "0000"}, @@ -910,7 +928,8 @@ public: jv[jss::Hooks][0U][jss::Hook] = iv; env(jv, M("Hook NSDELETE operation cannot include: grants, params, " - "hookon, hookcanemit, apiversion"), + "hookon, hookonincoming, hookonoutgoing, hookcanemit, " + "apiversion"), HSFEE, ter(temMALFORMED)); env.close(); @@ -1294,6 +1313,367 @@ public: : preHookCount + 66); } + void + testFeeRPC(jtx::Env& env, Json::Value tx, std::string expected) + { + auto const jtx = env.jt(tx); + + auto const feeDrops = env.current()->fees().base; + + // build tx_blob + Json::Value params; + params[jss::tx_blob] = strHex(jtx.stx->getSerializer().slice()); + + // fee request + auto const jrr = env.rpc("json", "fee", to_string(params)); + // std::cout << "RESULT: " << jrr << "\n"; + + // verify base fee & open ledger fee + auto const drops = jrr[jss::result][jss::drops]; + auto const baseFee = drops[jss::base_fee_no_hooks]; + BEAST_EXPECT(baseFee == to_string(feeDrops)); + auto const openLedgerFee = drops[jss::open_ledger_fee]; + BEAST_EXPECT(openLedgerFee == expected); + + // verify hooks fee + auto const hooksFee = jrr[jss::result][jss::fee_hooks_feeunits]; + BEAST_EXPECT(hooksFee == expected); + } + + void + testHookOnV2(FeatureBitset features) + { + testcase("Test hook on v2"); + using namespace jtx; + Env env{*this, features}; + + bool const hookOnV2 = env.current()->rules().enabled(featureHookOnV2); + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + env.fund(XRP(10000), alice); + env.fund(XRP(10000), bob); + env.close(); + + auto const deleteHook = [&env](Account const& account) { + Json::Value jv; + jv[jss::Account] = account.human(); + jv[jss::TransactionType] = jss::SetHook; + jv[jss::Flags] = 0; + jv[jss::Hooks] = Json::Value{Json::arrayValue}; + Json::Value iv; + iv[jss::CreateCode] = ""; + iv[jss::Flags] = hsfOVERRIDE; + jv[jss::Hooks][0U][jss::Hook] = iv; + + env(jv, M("hook DELETE"), HSFEE); + env.close(); + }; + + // Disabled + { + auto jv = hso(accept_wasm); + jv.removeMember(jss::HookOn); + jv[jss::HookOnIncoming] = + "00000000000000000000000000000000000000000000000000000000000000" + "01"; + jv[jss::HookOnOutgoing] = + "00000000000000000000000000000000000000000000000000000000000000" + "02"; + // create + env(ripple::test::jtx::hook(alice, {{jv}}, 0), + M("Create: Disabled"), + HSFEE, + !hookOnV2 ? ter(temMALFORMED) : ter(tesSUCCESS)); + deleteHook(alice); + + // install + env(ripple::test::jtx::hook(bob, {{hso(accept_wasm)}}, 0), + M("Install: Disabled prepare"), + HSFEE); + env.close(); + jv[jss::Flags] = hsfOVERRIDE; + env(ripple::test::jtx::hook(alice, {{jv}}, 0), + M("Install: Disabled"), + HSFEE, + !hookOnV2 ? ter(temMALFORMED) : ter(tesSUCCESS)); + env.close(); + deleteHook(alice); + deleteHook(bob); + + // update + env(ripple::test::jtx::hook(alice, {{hso(accept_wasm)}}, 0), + M("Update: Disabled prepare"), + HSFEE); + env.close(); + jv[jss::Flags] = hsfOVERRIDE; + env(ripple::test::jtx::hook(alice, {{jv}}, 0), + M("Update: Disabled"), + HSFEE, + !hookOnV2 ? ter(temMALFORMED) : ter(tesSUCCESS)); + env.close(); + deleteHook(alice); + deleteHook(bob); + } + if (!hookOnV2) + return; + + for (int i = 0; i < 3; i++) + { + if (i == 0) + { + // Create + } + if (i == 1) + { + // Install + env(ripple::test::jtx::hook(bob, {{hso(accept_wasm)}}, 0), + M("Install: prepare"), + HSFEE); + env.close(); + } + if (i == 2) + { + // Update + env(ripple::test::jtx::hook(alice, {{hso(accept_wasm)}}, 0), + M("Update: prepare"), + HSFEE); + env.close(); + } + auto jv = hso(accept_wasm); + jv[jss::Flags] = hsfOVERRIDE; + jv.removeMember(jss::HookOn); + + for (auto const& key : {jss::HookOnIncoming, jss::HookOnOutgoing}) + { + jv[key] = + "0000000000000000000000000000000000000000000000000000000000" + "0000" + "00"; + env(ripple::test::jtx::hook(alice, {{jv}}, 0), + M("Only Incomig/Outgoing HookOn"), + HSFEE, + ter(temMALFORMED)); + + jv[jss::HookOn] = + "0000000000000000000000000000000000000000000000000000000000" + "0000" + "00"; + env(ripple::test::jtx::hook(alice, {{jv}}, 0), + M("One Incomig/Outgoing HookOn and HookOn"), + HSFEE, + ter(temMALFORMED)); + jv.removeMember(key); + jv.removeMember(jss::HookOn); + } + // Incoming == Outgoing + jv[jss::HookOnIncoming] = + "0000000000000000000000000000000000000000000000000000000000" + "000123"; + jv[jss::HookOnOutgoing] = + "0000000000000000000000000000000000000000000000000000000000" + "000123"; + env(ripple::test::jtx::hook(alice, {{jv}}, 0), + M("Incoming == Outgoing"), + ter(temMALFORMED)); + jv.removeMember(jss::HookOnIncoming); + jv.removeMember(jss::HookOnOutgoing); + + // HookOn and both Fields + jv[jss::HookOn] = + "0000000000000000000000000000000000000000000000000000000000" + "000000"; + jv[jss::HookOnIncoming] = + "0000000000000000000000000000000000000000000000000000000000" + "000001"; + jv[jss::HookOnOutgoing] = + "0000000000000000000000000000000000000000000000000000000000" + "000002"; + env(ripple::test::jtx::hook(alice, {{jv}}, 0), + M("HookOn and both Fields"), + ter(temMALFORMED)); + deleteHook(alice); + deleteHook(bob); + } + + // Execution + for (int i = 1; i < 3; i++) + { + if (i == 0) + { + // HookOn from HookDefinition object + } + if (i == 1) + { + // HookOn from Hook Object (definition: incoming/outgoing) + auto jv = hso(accept_wasm); + jv.removeMember(jss::HookOn); + jv[jss::HookOnIncoming] = + "0000000000000000000000000000000000000000000000000000000000" + "0000" + "00"; + jv[jss::HookOnOutgoing] = + "0000000000000000000000000000000000000000000000000000000000" + "0000" + "01"; + env(ripple::test::jtx::hook(alice, {{jv}}, 0), + M("Execution: Install"), + HSFEE); + env.close(); + } + if (i == 2) + { + // HookOn from Hook Object (definition: HookOn) + auto jv = hso(accept_wasm); + env(ripple::test::jtx::hook(alice, {{jv}}, 0), + M("Execution: Install"), + HSFEE); + env.close(); + } + + auto jv = hso(accept_wasm); + jv.removeMember(jss::HookOn); + jv[jss::Flags] = hsfOVERRIDE; + jv[jss::HookOnIncoming] = + "fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffbfff" + "ff"; // Invoke high + jv[jss::HookOnOutgoing] = + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbfff" + "fe"; // Payment high + env(ripple::test::jtx::hook(alice, {{jv}}, 0), + M("Execution: Install"), + HSFEE); + env.close(); + + auto hookExecuted = [this, &env]() -> bool { + auto meta = env.meta(); + BEAST_EXPECT(meta); + return meta->isFieldPresent(sfHookExecutions); + }; + + // Check Incoming high + env(invoke::invoke(bob), + invoke::dest(alice), + M("Incoming high"), + fee(XRP(1))); + env.close(); + BEAST_EXPECT(hookExecuted()); + // Check Incoming low + env(pay(bob, alice, XRP(1)), M("Incoming low"), fee(XRP(1))); + env.close(); + BEAST_EXPECT(!hookExecuted()); + // Check Outgoing high + env(pay(alice, bob, XRP(1)), M("Outgoing high"), fee(XRP(1))); + env.close(); + BEAST_EXPECT(hookExecuted()); + // Check Outgoing low + env(invoke::invoke(alice), M("Outgoing high"), fee(XRP(1))); + env.close(); + BEAST_EXPECT(!hookExecuted()); + deleteHook(alice); + } + + { + // sfHookOn from Hook Object (definition: incoming/outgoing) + { + auto jv = hso(accept_wasm); + jv.removeMember(jss::HookOn); + jv[jss::HookOnIncoming] = + "fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff" + "bfff" + "ff"; // Invoke high + jv[jss::HookOnOutgoing] = + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "bfff" + "fe"; // Payment high + env(ripple::test::jtx::hook(alice, {{jv}}, 0), + M("Execution: Install"), + HSFEE); + env.close(); + } + + auto jv = hso(accept_wasm); + jv[jss::Flags] = hsfOVERRIDE; + jv[jss::HookOn] = + "0000000000000000000000000000000000000000000000000000000000" + "0000" + "00"; + env(ripple::test::jtx::hook(alice, {{jv}}, 0), + M("Execution: Install"), + HSFEE); + env.close(); + + auto hookExecuted = [this, &env]() -> bool { + auto meta = env.meta(); + BEAST_EXPECT(meta); + return meta->isFieldPresent(sfHookExecutions); + }; + + // Check Incoming high + env(invoke::invoke(bob), + invoke::dest(alice), + M("Incoming high"), + fee(XRP(1))); + env.close(); + BEAST_EXPECT(hookExecuted()); + // Check Incoming low + env(pay(bob, alice, XRP(1)), M("Incoming low"), fee(XRP(1))); + env.close(); + BEAST_EXPECT(hookExecuted()); + // Check Outgoing high + env(pay(alice, bob, XRP(1)), M("Outgoing high"), fee(XRP(1))); + env.close(); + BEAST_EXPECT(hookExecuted()); + // Check Outgoing low + env(invoke::invoke(alice), M("Outgoing high"), fee(XRP(1))); + env.close(); + BEAST_EXPECT(hookExecuted()); + deleteHook(alice); + } + + // Fee RPC + { + auto jv = hso(accept_wasm); + jv.removeMember(jss::HookOn); + jv[jss::HookOnIncoming] = + "fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff" + "bfff" + "ff"; // Invoke high + jv[jss::HookOnOutgoing] = + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "bfff" + "fe"; // Payment high + env(ripple::test::jtx::hook(alice, {{jv}}, 0), HSFEE); + env.close(); + + { + // incoming high + auto tx = invoke::invoke(bob); + tx[jss::Destination] = alice.human(); + std::string const feeResult = "19"; + testFeeRPC(env, tx, feeResult); + } + { + // incoming low + auto tx = pay(bob, alice, XRP(1)); + std::string const feeResult = "10"; + testFeeRPC(env, tx, feeResult); + } + { + // outgoing high + auto tx = pay(alice, bob, XRP(1)); + std::string const feeResult = "19"; + testFeeRPC(env, tx, feeResult); + } + { + // outgoing low + auto tx = invoke::invoke(alice); + std::string const feeResult = "10"; + testFeeRPC(env, tx, feeResult); + } + } + } + void testFillCopy(FeatureBitset features) { @@ -3073,6 +3453,397 @@ public: } } + void + test_prepare(FeatureBitset features) + { + testcase("Test prepare"); + using namespace jtx; + Env env{ + *this, envconfig(), features, nullptr, beast::severities::kError + // beast::severities::kTrace + }; + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + env.fund(XRP(10000), alice); + env.fund(XRP(10000), bob); + + TestHook hook = wasm[R"[test.hook]( + #include + extern int32_t _g(uint32_t, uint32_t); + extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + extern int64_t emit (uint32_t, uint32_t, uint32_t, uint32_t); + extern int64_t etxn_details (uint32_t, uint32_t); + extern int64_t etxn_reserve(uint32_t); + extern int64_t otxn_param(uint32_t, uint32_t, uint32_t, uint32_t); + extern int64_t prepare(uint32_t, uint32_t, uint32_t, uint32_t); + extern int64_t otxn_field ( + uint32_t write_ptr, + uint32_t write_len, + uint32_t field_id + ); + #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) + #define OUT_OF_BOUNDS (-1) + #define ttPAYMENT 0 + #define tfCANONICAL 0x80000000UL + #define amAMOUNT 1U + #define DOESNT_EXIST (-5) + #define atDESTINATION 3U + #define SBUF(x) (uint32_t)x,sizeof(x) + + #define PREREQUISITE_NOT_MET -9 + #define ENCODE_DROPS_SIZE 9 + #define ENCODE_DROPS(buf_out, drops, amount_type ) \ + {\ + uint8_t uat = amount_type; \ + uint64_t udrops = drops; \ + buf_out[0] = 0x60U +(uat & 0x0FU ); \ + buf_out[1] = 0b01000000 + (( udrops >> 56 ) & 0b00111111 ); \ + buf_out[2] = (udrops >> 48) & 0xFFU; \ + buf_out[3] = (udrops >> 40) & 0xFFU; \ + buf_out[4] = (udrops >> 32) & 0xFFU; \ + buf_out[5] = (udrops >> 24) & 0xFFU; \ + buf_out[6] = (udrops >> 16) & 0xFFU; \ + buf_out[7] = (udrops >> 8) & 0xFFU; \ + buf_out[8] = (udrops >> 0) & 0xFFU; \ + buf_out += ENCODE_DROPS_SIZE; \ + } + + #define ENCODE_DROPS_AMOUNT(buf_out, drops )\ + ENCODE_DROPS(buf_out, drops, amAMOUNT ); + #define _06_01_ENCODE_DROPS_AMOUNT(buf_out, drops )\ + ENCODE_DROPS_AMOUNT(buf_out, drops ); + + #define ENCODE_TT_SIZE 3 + #define ENCODE_TT(buf_out, tt )\ + {\ + uint8_t utt = tt;\ + buf_out[0] = 0x12U;\ + buf_out[1] =(utt >> 8 ) & 0xFFU;\ + buf_out[2] =(utt >> 0 ) & 0xFFU;\ + buf_out += ENCODE_TT_SIZE; \ + } + #define _01_02_ENCODE_TT(buf_out, tt)\ + ENCODE_TT(buf_out, tt); + + #define ENCODE_ACCOUNT_SIZE 22 + #define ENCODE_ACCOUNT(buf_out, account_id, account_type)\ + {\ + uint8_t uat = account_type;\ + buf_out[0] = 0x80U + uat;\ + buf_out[1] = 0x14U;\ + *(uint64_t*)(buf_out + 2) = *(uint64_t*)(account_id + 0);\ + *(uint64_t*)(buf_out + 10) = *(uint64_t*)(account_id + 8);\ + *(uint32_t*)(buf_out + 18) = *(uint32_t*)(account_id + 16);\ + buf_out += ENCODE_ACCOUNT_SIZE;\ + } + + #define ENCODE_ACCOUNT_DST_SIZE 22 + #define ENCODE_ACCOUNT_DST(buf_out, account_id)\ + ENCODE_ACCOUNT(buf_out, account_id, atDESTINATION); + #define _08_03_ENCODE_ACCOUNT_DST(buf_out, account_id)\ + ENCODE_ACCOUNT_DST(buf_out, account_id); + + #define ENCODE_UINT32_COMMON_SIZE 5U + #define ENCODE_UINT32_COMMON(buf_out, i, field)\ + {\ + uint32_t ui = i; \ + uint8_t uf = field; \ + buf_out[0] = 0x20U +(uf & 0x0FU); \ + buf_out[1] =(ui >> 24 ) & 0xFFU; \ + buf_out[2] =(ui >> 16 ) & 0xFFU; \ + buf_out[3] =(ui >> 8 ) & 0xFFU; \ + buf_out[4] =(ui >> 0 ) & 0xFFU; \ + buf_out += ENCODE_UINT32_COMMON_SIZE; \ + } + + #define ENCODE_TAG_SRC_SIZE 5 + #define ENCODE_TAG_SRC(buf_out, tag )\ + ENCODE_UINT32_COMMON(buf_out, tag, 0x3U ); + #define _02_03_ENCODE_TAG_SRC(buf_out, tag )\ + ENCODE_TAG_SRC(buf_out, tag ); + + #define ENCODE_TAG_DST_SIZE 5 + #define ENCODE_TAG_DST(buf_out, tag )\ + ENCODE_UINT32_COMMON(buf_out, tag, 0xEU ); + #define _02_14_ENCODE_TAG_DST(buf_out, tag )\ + ENCODE_TAG_DST(buf_out, tag ); + + #define ENCODE_FLAGS_SIZE 5 + #define ENCODE_FLAGS(buf_out, tag )\ + ENCODE_UINT32_COMMON(buf_out, tag, 0x2U ); + #define _02_02_ENCODE_FLAGS(buf_out, tag )\ + ENCODE_FLAGS(buf_out, tag ); + + #define PREPARE_PAYMENT_SIMPLE_SIZE 270U + #define PREPARE_PAYMENT_SIMPLE(prepared_buf, drops_amount_raw, to_address, dest_tag_raw, src_tag_raw)\ + {\ + uint8_t* buf_out = prepared_buf;\ + uint8_t buf[49];\ + uint8_t* buf_tx = buf;\ + uint64_t drops_amount = (drops_amount_raw);\ + uint32_t dest_tag = (dest_tag_raw);\ + uint32_t src_tag = (src_tag_raw);\ + _01_02_ENCODE_TT (buf_tx, ttPAYMENT ); /* uint16 | size 3 */ \ + _02_02_ENCODE_FLAGS (buf_tx, tfCANONICAL ); /* uint32 | size 5 */ \ + _02_03_ENCODE_TAG_SRC (buf_tx, src_tag ); /* uint32 | size 5 */ \ + _02_14_ENCODE_TAG_DST (buf_tx, dest_tag ); /* uint32 | size 5 */ \ + _06_01_ENCODE_DROPS_AMOUNT (buf_tx, drops_amount ); /* amount | size 9 */ \ + _08_03_ENCODE_ACCOUNT_DST (buf_tx, to_address ); /* account | size 22 */ \ + ASSERT(prepare(buf_out, 1000, SBUF(buf)) > 0);\ + } + + #define UINT16_FROM_BUF(buf)\ + (((uint64_t)((buf)[0]) << 8U) +\ + ((uint64_t)((buf)[1]) << 0U)) + + #define BUFFER_EQUAL_32(buf1, buf2)\ + (\ + *(((uint64_t*)(buf1)) + 0) == *(((uint64_t*)(buf2)) + 0) &&\ + *(((uint64_t*)(buf1)) + 1) == *(((uint64_t*)(buf2)) + 1) &&\ + *(((uint64_t*)(buf1)) + 2) == *(((uint64_t*)(buf2)) + 2) &&\ + *(((uint64_t*)(buf1)) + 3) == *(((uint64_t*)(buf2)) + 3) &&\ + *(((uint64_t*)(buf1)) + 4) == *(((uint64_t*)(buf2)) + 4) &&\ + *(((uint64_t*)(buf1)) + 5) == *(((uint64_t*)(buf2)) + 5) &&\ + *(((uint64_t*)(buf1)) + 6) == *(((uint64_t*)(buf2)) + 6) &&\ + *(((uint64_t*)(buf1)) + 7) == *(((uint64_t*)(buf2)) + 7)) + + #define ASSERT(x)\ + if (!(x))\ + rollback((uint32_t)#x,sizeof(#x),__LINE__) + + #define sfDestination ((8U << 16U) + 3U) + + extern int64_t etxn_generation(void); + extern int64_t otxn_generation(void); + extern int64_t otxn_burden(void); + extern int64_t etxn_burden(void); + + int64_t cbak(uint32_t r) + { + // on callback we emit 2 more txns + uint8_t bob[20]; + ASSERT(otxn_field(SBUF(bob), sfDestination) == 20); + + ASSERT(otxn_generation() + 1 == etxn_generation()); + + ASSERT(etxn_burden() == PREREQUISITE_NOT_MET); + + ASSERT(etxn_reserve(2) == 2); + + ASSERT(otxn_burden() > 0); + ASSERT(etxn_burden() == otxn_burden() * 2); + + uint8_t tx[PREPARE_PAYMENT_SIMPLE_SIZE]; + PREPARE_PAYMENT_SIMPLE(tx, 1000, bob, 0, 0); + + uint8_t hash1[32]; + ASSERT(emit(SBUF(hash1), SBUF(tx)) == 32); + + ASSERT(etxn_details(tx + 132, 138) == 138); + uint8_t hash2[32]; + ASSERT(emit(SBUF(hash2), SBUF(tx)) == 32); + + ASSERT(!BUFFER_EQUAL_32(hash1, hash2)); + + return accept(0,0,0); + } + + int64_t hook(uint32_t r) + { + _g(1,1); + + etxn_reserve(1); + + // bounds checks + ASSERT(prepare(1000000, 32, 0, 32) == OUT_OF_BOUNDS); + ASSERT(prepare(0,1000000, 0, 32) == OUT_OF_BOUNDS); + ASSERT(prepare(0,32, 1000000, 32) == OUT_OF_BOUNDS); + ASSERT(prepare(0,32, 0, 1000000) == OUT_OF_BOUNDS); + + uint8_t bob[20]; + ASSERT(otxn_param(SBUF(bob), "bob", 3) == 20); + + uint8_t tx[PREPARE_PAYMENT_SIMPLE_SIZE]; + PREPARE_PAYMENT_SIMPLE(tx, 1000, bob, 0, 0); + + uint8_t hash[32]; + ASSERT(emit(SBUF(hash), SBUF(tx)) == 32); + + return accept(0,0,0); + } + )[test.hook]"]; + + env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + M("set emit"), + HSFEE); + env.close(); + + Json::Value invoke; + invoke[jss::TransactionType] = "Invoke"; + invoke[jss::Account] = alice.human(); + + Json::Value params{Json::arrayValue}; + params[0U][jss::HookParameter][jss::HookParameterName] = + strHex(std::string("bob")); + params[0U][jss::HookParameter][jss::HookParameterValue] = + strHex(bob.id()); + + invoke[jss::HookParameters] = params; + + env(invoke, M("test emit"), fee(XRP(1))); + + bool const fixV2 = env.current()->rules().enabled(fixXahauV2); + + std::optional emithash; + { + auto meta = env.meta(); // meta can close + + // ensure hook execution occured + BEAST_REQUIRE(meta); + BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions)); + + auto const hookEmissions = meta->getFieldArray(sfHookEmissions); + BEAST_EXPECT( + hookEmissions[0u].isFieldPresent(sfEmitNonce) == fixV2 ? true + : false); + BEAST_EXPECT( + hookEmissions[0u].getAccountID(sfHookAccount) == alice.id()); + + auto const hookExecutions = meta->getFieldArray(sfHookExecutions); + BEAST_REQUIRE(hookExecutions.size() == 1); + + // ensure there was one emitted txn + BEAST_EXPECT(hookExecutions[0].getFieldU16(sfHookEmitCount) == 1); + + BEAST_REQUIRE(meta->isFieldPresent(sfAffectedNodes)); + + BEAST_REQUIRE(meta->getFieldArray(sfAffectedNodes).size() == 3); + + for (auto const& node : meta->getFieldArray(sfAffectedNodes)) + { + SField const& metaType = node.getFName(); + uint16_t nodeType = node.getFieldU16(sfLedgerEntryType); + if (metaType == sfCreatedNode && nodeType == ltEMITTED_TXN) + { + BEAST_REQUIRE(node.isFieldPresent(sfNewFields)); + + auto const& nf = const_cast(node) + .getField(sfNewFields) + .downcast(); + + auto const& et = const_cast(nf) + .getField(sfEmittedTxn) + .downcast(); + + auto const& em = const_cast(et) + .getField(sfEmitDetails) + .downcast(); + + BEAST_EXPECT(em.getFieldU32(sfEmitGeneration) == 1); + BEAST_EXPECT(em.getFieldU64(sfEmitBurden) == 1); + + Blob txBlob = et.getSerializer().getData(); + auto const tx = std::make_unique( + Slice{txBlob.data(), txBlob.size()}); + emithash = tx->getTransactionID(); + + break; + } + } + + BEAST_REQUIRE(emithash); + BEAST_EXPECT( + emithash == hookEmissions[0u].getFieldH256(sfEmittedTxnID)); + } + + { + auto balbefore = env.balance(bob).value().xrp().drops(); + + env.close(); + + auto const ledger = env.closed(); + + int txcount = 0; + for (auto& i : ledger->txs) + { + auto const& hash = i.first->getTransactionID(); + txcount++; + BEAST_EXPECT(hash == *emithash); + } + + BEAST_EXPECT(txcount == 1); + + auto balafter = env.balance(bob).value().xrp().drops(); + + BEAST_EXPECT(balafter - balbefore == 1000); + + env.close(); + } + + uint64_t burden_expected = 2; + for (int j = 0; j < 7; ++j) + { + auto const ledger = env.closed(); + for (auto& i : ledger->txs) + { + auto const& em = const_cast(*(i.first)) + .getField(sfEmitDetails) + .downcast(); + BEAST_EXPECT(em.getFieldU64(sfEmitBurden) == burden_expected); + BEAST_EXPECT(em.getFieldU32(sfEmitGeneration) == j + 2); + BEAST_REQUIRE(i.second->isFieldPresent(sfHookExecutions)); + auto const hookExecutions = + i.second->getFieldArray(sfHookExecutions); + BEAST_EXPECT(hookExecutions.size() == 1); + BEAST_EXPECT( + hookExecutions[0].getFieldU64(sfHookReturnCode) == 0); + BEAST_EXPECT(hookExecutions[0].getFieldU8(sfHookResult) == 3); + BEAST_EXPECT( + hookExecutions[0].getFieldU16(sfHookEmitCount) == 2); + if (fixV2) + BEAST_EXPECT(hookExecutions[0].getFieldU32(sfFlags) == 2); + } + env.close(); + burden_expected *= 2U; + } + + { + auto const ledger = env.closed(); + int txcount = 0; + for (auto& i : ledger->txs) + { + txcount++; + auto const& em = const_cast(*(i.first)) + .getField(sfEmitDetails) + .downcast(); + BEAST_EXPECT(em.getFieldU64(sfEmitBurden) == 256); + BEAST_EXPECT(em.getFieldU32(sfEmitGeneration) == 9); + BEAST_REQUIRE(i.second->isFieldPresent(sfHookExecutions)); + auto const hookExecutions = + i.second->getFieldArray(sfHookExecutions); + BEAST_EXPECT(hookExecutions.size() == 1); + BEAST_EXPECT( + hookExecutions[0].getFieldU64(sfHookReturnCode) == + 172); // emission failure on first emit + if (fixV2) + BEAST_EXPECT(hookExecutions[0].getFieldU32(sfFlags) == 2); + } + BEAST_EXPECT(txcount == 256); + } + + // next close will lead to zero transactions + env.close(); + { + auto const ledger = env.closed(); + int txcount = 0; + for ([[maybe_unused]] auto& i : ledger->txs) + txcount++; + BEAST_EXPECT(txcount == 0); + } + } + void test_etxn_details(FeatureBitset features) { @@ -10951,6 +11722,186 @@ public: // invoke the hook env(pay(bob, alice, XRP(1)), M("test sto_validate"), fee(XRP(1))); + + { + // test STIs + TestHook hook = wasm[R"[test.hook]( + #include + extern int32_t _g (uint32_t id, uint32_t maxiter); + #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) + 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 sto_validate(uint32_t, uint32_t); + 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 SBUF(x) (uint32_t)(x), sizeof(x) + + uint8_t buf[1000]; + int64_t hook(uint32_t reserved) + { + _g(1,1); + + int64_t size = otxn_param(SBUF(buf), "V", 1); + int64_t result = sto_validate(buf, size); + + accept(0,0,result); + } + )[test.hook]"]; + + for (auto feature : { + features - featureHookAPISerializedType240, + features | featureHookAPISerializedType240, + }) + { + Env env{*this, feature}; + + env.fund(XRP(10000), alice, bob); + env.close(); + + auto hasEnabled = env.current()->rules().enabled( + featureHookAPISerializedType240); + + // install the hook on alice + env(ripple::test::jtx::hook( + alice, {{hso(hook, overrideFlag)}}, 0), + M("set sto_validate"), + HSFEE); + env.close(); + + // invoke the hook + auto buildTx = [&](std::string value) { + auto payJv = pay(bob, alice, XRP(1)); + + Json::Value params{Json::arrayValue}; + auto& param = params[0U][jss::HookParameter]; + param[jss::HookParameterName] = strHex(std::string("V")); + param[jss::HookParameterValue] = value; + payJv[jss::HookParameters] = params; + return payJv; + }; + + auto testSTI = [&](std::string value, bool expectedResult) { + auto tx = buildTx(value); + env(tx, M("test STI"), fee(XRP(1))); + env.close(); + + auto const result = env.meta() + ->getFieldArray(sfHookExecutions)[0] + .getFieldU64(sfHookReturnCode); + if (expectedResult) + BEAST_EXPECTS(result == 1, value); + else + BEAST_EXPECTS(result == 0, value); + }; + + // STI_UINT32 + testSTI("2200000001", true); + // STI_UINT64 + testSTI("301100000000000003E8", true); + // STI_UINT128 + testSTI("4100000000000000000000000000000000", true); + // STI_UINT256 + testSTI( + "5060000000000000000000000000000000000000000000000000000000" + "0000000000", + true); + // STI_AMOUNT + testSTI("614000000000000064", true); + testSTI( + "61D5038D7EA4C680000000000000000000000000005553440000000000" + "AE123A8556F3CF91154711376AFB0F894F832B3D", + true); + // STI_VL + testSTI("7504DEADBEEF", true); + // STI_ACCOUNT + testSTI("8114AE123A8556F3CF91154711376AFB0F894F832B3D", true); + // STI_NUMBER + // testSTI("000400000000000000000000000000000001", true); + // STI_OBJECT + testSTI("E05C22000000017504DEADBEEFE1", true); + // STI_ARRAY + testSTI( + "F05CE05B614000000000000064E1E05B61D5038D7EA4C6800000000000" + "00000000000000005553440000000000AE123A8556F3CF91154711376A" + "FB0F894F832B3DE1F1", + true); + // STI_UINT8 + testSTI("00101003", true); + // STI_UINT160 + testSTI("01110000000000000000000000000000000000000000", true); + // STI_PATHSET + testSTI( + "0112300000000000000000000000005553440000000000AE123A8556F3" + "CF91154711376AFB0F894F832B3D00", + hasEnabled); + testSTI( + "0112310A20B3C85F482532A9578DBB3950B85CA06594D1000000000000" + "00000000000042544300000000000A20B3C85F482532A9578DBB3950B8" + "5CA06594D13000000000000000000000000055534400000000000A20B3" + "C85F482532A9578DBB3950B85CA06594D1FF3157180C769B66D942EE69" + "E6DCC940CA48D82337AD00000000000000000000000042544300000000" + "0057180C769B66D942EE69E6DCC940CA48D82337AD1000000000000000" + "0000000000000000000000000030000000000000000000000000555344" + "00000000000A20B3C85F482532A9578DBB3950B85CA06594D100", + hasEnabled); + // STI_VECTOR256 + testSTI( + "0013634000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000" + "00000000000000000000", + true); + // STI_UINT96 + // testSTI("000400000000000000000000000000000001", true); + // STI_UINT192 + // testSTI("000400000000000000000000000000000001", true); + // STI_UINT384 + // testSTI("000400000000000000000000000000000001", true); + // STI_UINT512 + // testSTI("000400000000000000000000000000000001", true); + // STI_ISSUE + testSTI( + "03180000000000000000000000005553440000000000AE123A8556F3CF" + "91154711376AFB0F894F832B3D", + hasEnabled); + testSTI( + "03180000000000000000000000000000000000000000", hasEnabled); + // STI_XCHAIN_BRIDGE + /// Native-Native + testSTI( + "011914AE123A8556F3CF91154711376AFB0F894F832B3D000000000000" + "000000000000000000000000000014AE123A8556F3CF91154711376AFB" + "0F894F832B3D0000000000000000000000000000000000000000", + hasEnabled); + /// IOU-Native + testSTI( + "011914AE123A8556F3CF91154711376AFB0F894F832B3D000000000000" + "0000000000005553440000000000AE123A8556F3CF91154711376AFB0F" + "894F832B3D14AE123A8556F3CF91154711376AFB0F894F832B3D000000" + "0000" + "000000000000000000000000000000", + hasEnabled); + /// Native-IOU + testSTI( + "011914AE123A8556F3CF91154711376AFB0F894F832B3D000000000000" + "0000000000005553440000000000AE123A8556F3CF91154711376AFB0F" + "894F832B3D14AE123A8556F3CF91154711376AFB0F894F832B3D000000" + "0000000000000000000000000000000000", + hasEnabled); + /// IOU-IOU + testSTI( + "011914AE123A8556F3CF91154711376AFB0F894F832B3D000000000000" + "0000000000005553440000000000AE123A8556F3CF91154711376AFB0F" + "894F832B3D14AE123A8556F3CF91154711376AFB0F894F832B3D000000" + "0000000000000000005553440000000000AE123A8556F3CF9115471137" + "6AFB0F894F832B3D", + hasEnabled); + // STI_CURRENCY + testSTI( + "011A0000000000000000000000005553440000000000", hasEnabled); + } + } } void @@ -13548,6 +14499,8 @@ public: testNSDeletePartial(features); testPageCap(features); + testHookOnV2(features); + testFillCopy(features); testWasm(features); @@ -13557,6 +14510,7 @@ public: testGuards(features); test_emit(features); // + test_prepare(features); // test_etxn_burden(features); // tested above // test_etxn_generation(features); // tested above // test_otxn_burden(features); // tested above diff --git a/src/test/app/SetHook_wasm.h b/src/test/app/SetHook_wasm.h index faf21ac31..0f04e876a 100644 --- a/src/test/app/SetHook_wasm.h +++ b/src/test/app/SetHook_wasm.h @@ -1160,6 +1160,435 @@ std::map> wasm = { }}, /* ==== WASM: 7 ==== */ + {R"[test.hook]( + #include + extern int32_t _g(uint32_t, uint32_t); + extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + extern int64_t emit (uint32_t, uint32_t, uint32_t, uint32_t); + extern int64_t etxn_details (uint32_t, uint32_t); + extern int64_t etxn_reserve(uint32_t); + extern int64_t otxn_param(uint32_t, uint32_t, uint32_t, uint32_t); + extern int64_t prepare(uint32_t, uint32_t, uint32_t, uint32_t); + extern int64_t otxn_field ( + uint32_t write_ptr, + uint32_t write_len, + uint32_t field_id + ); + #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) + #define OUT_OF_BOUNDS (-1) + #define ttPAYMENT 0 + #define tfCANONICAL 0x80000000UL + #define amAMOUNT 1U + #define DOESNT_EXIST (-5) + #define atDESTINATION 3U + #define SBUF(x) (uint32_t)x,sizeof(x) + + #define PREREQUISITE_NOT_MET -9 + #define ENCODE_DROPS_SIZE 9 + #define ENCODE_DROPS(buf_out, drops, amount_type ) \ + {\ + uint8_t uat = amount_type; \ + uint64_t udrops = drops; \ + buf_out[0] = 0x60U +(uat & 0x0FU ); \ + buf_out[1] = 0b01000000 + (( udrops >> 56 ) & 0b00111111 ); \ + buf_out[2] = (udrops >> 48) & 0xFFU; \ + buf_out[3] = (udrops >> 40) & 0xFFU; \ + buf_out[4] = (udrops >> 32) & 0xFFU; \ + buf_out[5] = (udrops >> 24) & 0xFFU; \ + buf_out[6] = (udrops >> 16) & 0xFFU; \ + buf_out[7] = (udrops >> 8) & 0xFFU; \ + buf_out[8] = (udrops >> 0) & 0xFFU; \ + buf_out += ENCODE_DROPS_SIZE; \ + } + + #define ENCODE_DROPS_AMOUNT(buf_out, drops )\ + ENCODE_DROPS(buf_out, drops, amAMOUNT ); + #define _06_01_ENCODE_DROPS_AMOUNT(buf_out, drops )\ + ENCODE_DROPS_AMOUNT(buf_out, drops ); + + #define ENCODE_TT_SIZE 3 + #define ENCODE_TT(buf_out, tt )\ + {\ + uint8_t utt = tt;\ + buf_out[0] = 0x12U;\ + buf_out[1] =(utt >> 8 ) & 0xFFU;\ + buf_out[2] =(utt >> 0 ) & 0xFFU;\ + buf_out += ENCODE_TT_SIZE; \ + } + #define _01_02_ENCODE_TT(buf_out, tt)\ + ENCODE_TT(buf_out, tt); + + #define ENCODE_ACCOUNT_SIZE 22 + #define ENCODE_ACCOUNT(buf_out, account_id, account_type)\ + {\ + uint8_t uat = account_type;\ + buf_out[0] = 0x80U + uat;\ + buf_out[1] = 0x14U;\ + *(uint64_t*)(buf_out + 2) = *(uint64_t*)(account_id + 0);\ + *(uint64_t*)(buf_out + 10) = *(uint64_t*)(account_id + 8);\ + *(uint32_t*)(buf_out + 18) = *(uint32_t*)(account_id + 16);\ + buf_out += ENCODE_ACCOUNT_SIZE;\ + } + + #define ENCODE_ACCOUNT_DST_SIZE 22 + #define ENCODE_ACCOUNT_DST(buf_out, account_id)\ + ENCODE_ACCOUNT(buf_out, account_id, atDESTINATION); + #define _08_03_ENCODE_ACCOUNT_DST(buf_out, account_id)\ + ENCODE_ACCOUNT_DST(buf_out, account_id); + + #define ENCODE_UINT32_COMMON_SIZE 5U + #define ENCODE_UINT32_COMMON(buf_out, i, field)\ + {\ + uint32_t ui = i; \ + uint8_t uf = field; \ + buf_out[0] = 0x20U +(uf & 0x0FU); \ + buf_out[1] =(ui >> 24 ) & 0xFFU; \ + buf_out[2] =(ui >> 16 ) & 0xFFU; \ + buf_out[3] =(ui >> 8 ) & 0xFFU; \ + buf_out[4] =(ui >> 0 ) & 0xFFU; \ + buf_out += ENCODE_UINT32_COMMON_SIZE; \ + } + + #define ENCODE_TAG_SRC_SIZE 5 + #define ENCODE_TAG_SRC(buf_out, tag )\ + ENCODE_UINT32_COMMON(buf_out, tag, 0x3U ); + #define _02_03_ENCODE_TAG_SRC(buf_out, tag )\ + ENCODE_TAG_SRC(buf_out, tag ); + + #define ENCODE_TAG_DST_SIZE 5 + #define ENCODE_TAG_DST(buf_out, tag )\ + ENCODE_UINT32_COMMON(buf_out, tag, 0xEU ); + #define _02_14_ENCODE_TAG_DST(buf_out, tag )\ + ENCODE_TAG_DST(buf_out, tag ); + + #define ENCODE_FLAGS_SIZE 5 + #define ENCODE_FLAGS(buf_out, tag )\ + ENCODE_UINT32_COMMON(buf_out, tag, 0x2U ); + #define _02_02_ENCODE_FLAGS(buf_out, tag )\ + ENCODE_FLAGS(buf_out, tag ); + + #define PREPARE_PAYMENT_SIMPLE_SIZE 270U + #define PREPARE_PAYMENT_SIMPLE(prepared_buf, drops_amount_raw, to_address, dest_tag_raw, src_tag_raw)\ + {\ + uint8_t* buf_out = prepared_buf;\ + uint8_t buf[49];\ + uint8_t* buf_tx = buf;\ + uint64_t drops_amount = (drops_amount_raw);\ + uint32_t dest_tag = (dest_tag_raw);\ + uint32_t src_tag = (src_tag_raw);\ + _01_02_ENCODE_TT (buf_tx, ttPAYMENT ); /* uint16 | size 3 */ \ + _02_02_ENCODE_FLAGS (buf_tx, tfCANONICAL ); /* uint32 | size 5 */ \ + _02_03_ENCODE_TAG_SRC (buf_tx, src_tag ); /* uint32 | size 5 */ \ + _02_14_ENCODE_TAG_DST (buf_tx, dest_tag ); /* uint32 | size 5 */ \ + _06_01_ENCODE_DROPS_AMOUNT (buf_tx, drops_amount ); /* amount | size 9 */ \ + _08_03_ENCODE_ACCOUNT_DST (buf_tx, to_address ); /* account | size 22 */ \ + ASSERT(prepare(buf_out, 1000, SBUF(buf)) > 0);\ + } + + #define UINT16_FROM_BUF(buf)\ + (((uint64_t)((buf)[0]) << 8U) +\ + ((uint64_t)((buf)[1]) << 0U)) + + #define BUFFER_EQUAL_32(buf1, buf2)\ + (\ + *(((uint64_t*)(buf1)) + 0) == *(((uint64_t*)(buf2)) + 0) &&\ + *(((uint64_t*)(buf1)) + 1) == *(((uint64_t*)(buf2)) + 1) &&\ + *(((uint64_t*)(buf1)) + 2) == *(((uint64_t*)(buf2)) + 2) &&\ + *(((uint64_t*)(buf1)) + 3) == *(((uint64_t*)(buf2)) + 3) &&\ + *(((uint64_t*)(buf1)) + 4) == *(((uint64_t*)(buf2)) + 4) &&\ + *(((uint64_t*)(buf1)) + 5) == *(((uint64_t*)(buf2)) + 5) &&\ + *(((uint64_t*)(buf1)) + 6) == *(((uint64_t*)(buf2)) + 6) &&\ + *(((uint64_t*)(buf1)) + 7) == *(((uint64_t*)(buf2)) + 7)) + + #define ASSERT(x)\ + if (!(x))\ + rollback((uint32_t)#x,sizeof(#x),__LINE__) + + #define sfDestination ((8U << 16U) + 3U) + + extern int64_t etxn_generation(void); + extern int64_t otxn_generation(void); + extern int64_t otxn_burden(void); + extern int64_t etxn_burden(void); + + int64_t cbak(uint32_t r) + { + // on callback we emit 2 more txns + uint8_t bob[20]; + ASSERT(otxn_field(SBUF(bob), sfDestination) == 20); + + ASSERT(otxn_generation() + 1 == etxn_generation()); + + ASSERT(etxn_burden() == PREREQUISITE_NOT_MET); + + ASSERT(etxn_reserve(2) == 2); + + ASSERT(otxn_burden() > 0); + ASSERT(etxn_burden() == otxn_burden() * 2); + + uint8_t tx[PREPARE_PAYMENT_SIMPLE_SIZE]; + PREPARE_PAYMENT_SIMPLE(tx, 1000, bob, 0, 0); + + uint8_t hash1[32]; + ASSERT(emit(SBUF(hash1), SBUF(tx)) == 32); + + ASSERT(etxn_details(tx + 132, 138) == 138); + uint8_t hash2[32]; + ASSERT(emit(SBUF(hash2), SBUF(tx)) == 32); + + ASSERT(!BUFFER_EQUAL_32(hash1, hash2)); + + return accept(0,0,0); + } + + int64_t hook(uint32_t r) + { + _g(1,1); + + etxn_reserve(1); + + // bounds checks + ASSERT(prepare(1000000, 32, 0, 32) == OUT_OF_BOUNDS); + ASSERT(prepare(0,1000000, 0, 32) == OUT_OF_BOUNDS); + ASSERT(prepare(0,32, 1000000, 32) == OUT_OF_BOUNDS); + ASSERT(prepare(0,32, 0, 1000000) == OUT_OF_BOUNDS); + + uint8_t bob[20]; + ASSERT(otxn_param(SBUF(bob), "bob", 3) == 20); + + uint8_t tx[PREPARE_PAYMENT_SIMPLE_SIZE]; + PREPARE_PAYMENT_SIMPLE(tx, 1000, bob, 0, 0); + + uint8_t hash[32]; + ASSERT(emit(SBUF(hash), SBUF(tx)) == 32); + + return accept(0,0,0); + } + )[test.hook]", + { + 0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, 0x2CU, + 0x07U, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, + 0x7FU, 0x7FU, 0x7EU, 0x01U, 0x7EU, 0x60U, 0x00U, 0x01U, 0x7EU, 0x60U, + 0x01U, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x04U, 0x7FU, 0x7FU, 0x7FU, 0x7FU, + 0x01U, 0x7EU, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x02U, + 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x02U, 0xD7U, 0x01U, 0x0DU, 0x03U, 0x65U, + 0x6EU, 0x76U, 0x0AU, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x66U, 0x69U, + 0x65U, 0x6CU, 0x64U, 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x08U, + 0x72U, 0x6FU, 0x6CU, 0x6CU, 0x62U, 0x61U, 0x63U, 0x6BU, 0x00U, 0x01U, + 0x03U, 0x65U, 0x6EU, 0x76U, 0x0FU, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, + 0x67U, 0x65U, 0x6EU, 0x65U, 0x72U, 0x61U, 0x74U, 0x69U, 0x6FU, 0x6EU, + 0x00U, 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0FU, 0x65U, 0x74U, 0x78U, + 0x6EU, 0x5FU, 0x67U, 0x65U, 0x6EU, 0x65U, 0x72U, 0x61U, 0x74U, 0x69U, + 0x6FU, 0x6EU, 0x00U, 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0BU, 0x65U, + 0x74U, 0x78U, 0x6EU, 0x5FU, 0x62U, 0x75U, 0x72U, 0x64U, 0x65U, 0x6EU, + 0x00U, 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0CU, 0x65U, 0x74U, 0x78U, + 0x6EU, 0x5FU, 0x72U, 0x65U, 0x73U, 0x65U, 0x72U, 0x76U, 0x65U, 0x00U, + 0x03U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0BU, 0x6FU, 0x74U, 0x78U, 0x6EU, + 0x5FU, 0x62U, 0x75U, 0x72U, 0x64U, 0x65U, 0x6EU, 0x00U, 0x02U, 0x03U, + 0x65U, 0x6EU, 0x76U, 0x07U, 0x70U, 0x72U, 0x65U, 0x70U, 0x61U, 0x72U, + 0x65U, 0x00U, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x04U, 0x65U, 0x6DU, + 0x69U, 0x74U, 0x00U, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0CU, 0x65U, + 0x74U, 0x78U, 0x6EU, 0x5FU, 0x64U, 0x65U, 0x74U, 0x61U, 0x69U, 0x6CU, + 0x73U, 0x00U, 0x05U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U, + 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, + 0x02U, 0x5FU, 0x67U, 0x00U, 0x06U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0AU, + 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x70U, 0x61U, 0x72U, 0x61U, 0x6DU, + 0x00U, 0x04U, 0x03U, 0x03U, 0x02U, 0x03U, 0x03U, 0x05U, 0x03U, 0x01U, + 0x00U, 0x02U, 0x06U, 0x21U, 0x05U, 0x7FU, 0x01U, 0x41U, 0xF0U, 0x8CU, + 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0xECU, 0x0CU, 0x0BU, 0x7FU, 0x00U, + 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0xF0U, 0x8CU, 0x04U, + 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x07U, 0x0FU, 0x02U, + 0x04U, 0x63U, 0x62U, 0x61U, 0x6BU, 0x00U, 0x0DU, 0x04U, 0x68U, 0x6FU, + 0x6FU, 0x6BU, 0x00U, 0x0EU, 0x0AU, 0xBCU, 0x89U, 0x00U, 0x02U, 0xCDU, + 0x85U, 0x00U, 0x02U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x23U, 0x80U, 0x80U, + 0x80U, 0x80U, 0x00U, 0x41U, 0x90U, 0x03U, 0x6BU, 0x22U, 0x01U, 0x24U, + 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, + 0xF0U, 0x02U, 0x6AU, 0x41U, 0x14U, 0x41U, 0x83U, 0x80U, 0x20U, 0x10U, + 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x14U, 0x51U, 0x0DU, 0x00U, + 0x41U, 0x80U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x2BU, 0x42U, 0x9DU, + 0x01U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, + 0x40U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x01U, 0x7CU, + 0x10U, 0x83U, 0x80U, 0x80U, 0x80U, 0x00U, 0x51U, 0x0DU, 0x00U, 0x41U, + 0xABU, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x2BU, 0x42U, 0x9FU, 0x01U, + 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, + 0x10U, 0x84U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x77U, 0x51U, 0x0DU, + 0x00U, 0x41U, 0xD6U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x26U, 0x42U, + 0xA1U, 0x01U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, + 0x02U, 0x40U, 0x41U, 0x02U, 0x10U, 0x85U, 0x80U, 0x80U, 0x80U, 0x00U, + 0x42U, 0x02U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xFCU, 0x88U, 0x80U, 0x80U, + 0x00U, 0x41U, 0x15U, 0x42U, 0xA3U, 0x01U, 0x10U, 0x81U, 0x80U, 0x80U, + 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x10U, 0x86U, 0x80U, 0x80U, + 0x80U, 0x00U, 0x42U, 0x00U, 0x55U, 0x0DU, 0x00U, 0x41U, 0x91U, 0x89U, + 0x80U, 0x80U, 0x00U, 0x41U, 0x12U, 0x42U, 0xA5U, 0x01U, 0x10U, 0x81U, + 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x10U, 0x84U, + 0x80U, 0x80U, 0x80U, 0x00U, 0x10U, 0x86U, 0x80U, 0x80U, 0x80U, 0x00U, + 0x42U, 0x01U, 0x86U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xA3U, 0x89U, 0x80U, + 0x80U, 0x00U, 0x41U, 0x23U, 0x42U, 0xA6U, 0x01U, 0x10U, 0x81U, 0x80U, + 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x20U, 0x01U, 0x41U, 0x38U, 0x6AU, + 0x41U, 0x00U, 0x3AU, 0x00U, 0x00U, 0x20U, 0x01U, 0x41U, 0x00U, 0x36U, + 0x02U, 0x34U, 0x20U, 0x01U, 0x41U, 0xE1U, 0x80U, 0x01U, 0x3BU, 0x01U, + 0x32U, 0x20U, 0x01U, 0x41U, 0x00U, 0x36U, 0x01U, 0x2EU, 0x20U, 0x01U, + 0x41U, 0x2EU, 0x3AU, 0x00U, 0x2DU, 0x20U, 0x01U, 0x41U, 0x00U, 0x36U, + 0x00U, 0x29U, 0x20U, 0x01U, 0x41U, 0x23U, 0x3AU, 0x00U, 0x28U, 0x20U, + 0x01U, 0x42U, 0x92U, 0x80U, 0x80U, 0x90U, 0x82U, 0x10U, 0x37U, 0x03U, + 0x20U, 0x20U, 0x01U, 0x41U, 0x83U, 0xD0U, 0x8FU, 0xA4U, 0x01U, 0x36U, + 0x00U, 0x39U, 0x20U, 0x01U, 0x20U, 0x01U, 0x29U, 0x03U, 0xF0U, 0x02U, + 0x37U, 0x03U, 0x3DU, 0x20U, 0x01U, 0x20U, 0x01U, 0x29U, 0x03U, 0xF8U, + 0x02U, 0x37U, 0x03U, 0x45U, 0x20U, 0x01U, 0x20U, 0x01U, 0x28U, 0x02U, + 0x80U, 0x03U, 0x36U, 0x02U, 0x4DU, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, + 0xE0U, 0x00U, 0x6AU, 0x41U, 0xE8U, 0x07U, 0x20U, 0x01U, 0x41U, 0x20U, + 0x6AU, 0x41U, 0x31U, 0x10U, 0x87U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, + 0x00U, 0x55U, 0x0DU, 0x00U, 0x41U, 0xC6U, 0x89U, 0x80U, 0x80U, 0x00U, + 0x41U, 0x26U, 0x42U, 0xA9U, 0x01U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, + 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0x20U, 0x6AU, + 0x41U, 0x20U, 0x20U, 0x01U, 0x41U, 0xE0U, 0x00U, 0x6AU, 0x41U, 0x8EU, + 0x02U, 0x10U, 0x88U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x20U, 0x51U, + 0x0DU, 0x00U, 0x41U, 0xECU, 0x89U, 0x80U, 0x80U, 0x00U, 0x41U, 0x22U, + 0x42U, 0xACU, 0x01U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, + 0x0BU, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0xE4U, 0x01U, 0x6AU, 0x41U, + 0x8AU, 0x01U, 0x10U, 0x89U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x8AU, + 0x01U, 0x51U, 0x0DU, 0x00U, 0x41U, 0x8EU, 0x8AU, 0x80U, 0x80U, 0x00U, + 0x41U, 0x23U, 0x42U, 0xAEU, 0x01U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, + 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0x20U, 0x20U, + 0x01U, 0x41U, 0xE0U, 0x00U, 0x6AU, 0x41U, 0x8EU, 0x02U, 0x10U, 0x88U, + 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x20U, 0x51U, 0x0DU, 0x00U, 0x41U, + 0xB1U, 0x8AU, 0x80U, 0x80U, 0x00U, 0x41U, 0x22U, 0x42U, 0xB0U, 0x01U, + 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, + 0x20U, 0x01U, 0x29U, 0x03U, 0x20U, 0x20U, 0x01U, 0x29U, 0x03U, 0x00U, + 0x52U, 0x0DU, 0x00U, 0x20U, 0x01U, 0x29U, 0x03U, 0x28U, 0x20U, 0x01U, + 0x29U, 0x03U, 0x08U, 0x52U, 0x0DU, 0x00U, 0x20U, 0x01U, 0x29U, 0x03U, + 0x30U, 0x20U, 0x01U, 0x29U, 0x03U, 0x10U, 0x52U, 0x0DU, 0x00U, 0x20U, + 0x01U, 0x29U, 0x03U, 0x38U, 0x20U, 0x01U, 0x29U, 0x03U, 0x18U, 0x52U, + 0x0DU, 0x00U, 0x20U, 0x01U, 0x29U, 0x03U, 0x40U, 0x20U, 0x01U, 0x29U, + 0x03U, 0x20U, 0x52U, 0x0DU, 0x00U, 0x20U, 0x01U, 0x41U, 0x20U, 0x6AU, + 0x41U, 0x28U, 0x6AU, 0x29U, 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, 0x28U, + 0x6AU, 0x29U, 0x03U, 0x00U, 0x52U, 0x0DU, 0x00U, 0x20U, 0x01U, 0x41U, + 0x20U, 0x6AU, 0x41U, 0x30U, 0x6AU, 0x29U, 0x03U, 0x00U, 0x20U, 0x01U, + 0x41U, 0x30U, 0x6AU, 0x29U, 0x03U, 0x00U, 0x52U, 0x0DU, 0x00U, 0x20U, + 0x01U, 0x41U, 0x20U, 0x6AU, 0x41U, 0x38U, 0x6AU, 0x29U, 0x03U, 0x00U, + 0x20U, 0x01U, 0x41U, 0x38U, 0x6AU, 0x29U, 0x03U, 0x00U, 0x52U, 0x0DU, + 0x00U, 0x41U, 0xD3U, 0x8AU, 0x80U, 0x80U, 0x00U, 0x41U, 0x1FU, 0x42U, + 0xB2U, 0x01U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, + 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x00U, 0x10U, 0x8AU, 0x80U, 0x80U, + 0x80U, 0x00U, 0x21U, 0x02U, 0x20U, 0x01U, 0x41U, 0x90U, 0x03U, 0x6AU, + 0x24U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x20U, 0x02U, 0x0BU, 0xE8U, + 0x83U, 0x00U, 0x02U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x23U, 0x80U, 0x80U, + 0x80U, 0x80U, 0x00U, 0x41U, 0xF0U, 0x02U, 0x6BU, 0x22U, 0x01U, 0x24U, + 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U, + 0x8BU, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x41U, 0x01U, 0x10U, 0x85U, + 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x02U, 0x40U, 0x41U, 0xC0U, 0x84U, + 0x3DU, 0x41U, 0x20U, 0x41U, 0x00U, 0x41U, 0x20U, 0x10U, 0x87U, 0x80U, + 0x80U, 0x80U, 0x00U, 0x42U, 0x7FU, 0x51U, 0x0DU, 0x00U, 0x41U, 0xF2U, + 0x8AU, 0x80U, 0x80U, 0x00U, 0x41U, 0x2DU, 0x42U, 0xBEU, 0x01U, 0x10U, + 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x41U, + 0x00U, 0x41U, 0xC0U, 0x84U, 0x3DU, 0x41U, 0x00U, 0x41U, 0x20U, 0x10U, + 0x87U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x7FU, 0x51U, 0x0DU, 0x00U, + 0x41U, 0x9FU, 0x8BU, 0x80U, 0x80U, 0x00U, 0x41U, 0x2BU, 0x42U, 0xBFU, + 0x01U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, + 0x40U, 0x41U, 0x00U, 0x41U, 0x20U, 0x41U, 0xC0U, 0x84U, 0x3DU, 0x41U, + 0x20U, 0x10U, 0x87U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x7FU, 0x51U, + 0x0DU, 0x00U, 0x41U, 0xCAU, 0x8BU, 0x80U, 0x80U, 0x00U, 0x41U, 0x2CU, + 0x42U, 0xC0U, 0x01U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, + 0x0BU, 0x02U, 0x40U, 0x41U, 0x00U, 0x41U, 0x20U, 0x41U, 0x00U, 0x41U, + 0xC0U, 0x84U, 0x3DU, 0x10U, 0x87U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, + 0x7FU, 0x51U, 0x0DU, 0x00U, 0x41U, 0xF6U, 0x8BU, 0x80U, 0x80U, 0x00U, + 0x41U, 0x2BU, 0x42U, 0xC1U, 0x01U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, + 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0xD0U, 0x02U, + 0x6AU, 0x41U, 0x14U, 0x41U, 0xA1U, 0x8CU, 0x80U, 0x80U, 0x00U, 0x41U, + 0x03U, 0x10U, 0x8CU, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x14U, 0x51U, + 0x0DU, 0x00U, 0x41U, 0xA5U, 0x8CU, 0x80U, 0x80U, 0x00U, 0x41U, 0x26U, + 0x42U, 0xC4U, 0x01U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, + 0x0BU, 0x20U, 0x01U, 0x41U, 0x18U, 0x6AU, 0x41U, 0x00U, 0x3AU, 0x00U, + 0x00U, 0x20U, 0x01U, 0x41U, 0x00U, 0x36U, 0x02U, 0x14U, 0x20U, 0x01U, + 0x41U, 0xE1U, 0x80U, 0x01U, 0x3BU, 0x01U, 0x12U, 0x20U, 0x01U, 0x41U, + 0x00U, 0x36U, 0x01U, 0x0EU, 0x20U, 0x01U, 0x41U, 0x2EU, 0x3AU, 0x00U, + 0x0DU, 0x20U, 0x01U, 0x41U, 0x00U, 0x36U, 0x00U, 0x09U, 0x20U, 0x01U, + 0x41U, 0x23U, 0x3AU, 0x00U, 0x08U, 0x20U, 0x01U, 0x42U, 0x92U, 0x80U, + 0x80U, 0x90U, 0x82U, 0x10U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, 0x41U, + 0x83U, 0xD0U, 0x8FU, 0xA4U, 0x01U, 0x36U, 0x00U, 0x19U, 0x20U, 0x01U, + 0x20U, 0x01U, 0x29U, 0x03U, 0xD0U, 0x02U, 0x37U, 0x03U, 0x1DU, 0x20U, + 0x01U, 0x20U, 0x01U, 0x29U, 0x03U, 0xD8U, 0x02U, 0x37U, 0x03U, 0x25U, + 0x20U, 0x01U, 0x20U, 0x01U, 0x28U, 0x02U, 0xE0U, 0x02U, 0x36U, 0x02U, + 0x2DU, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0xC0U, 0x00U, 0x6AU, 0x41U, + 0xE8U, 0x07U, 0x20U, 0x01U, 0x41U, 0x31U, 0x10U, 0x87U, 0x80U, 0x80U, + 0x80U, 0x00U, 0x42U, 0x00U, 0x55U, 0x0DU, 0x00U, 0x41U, 0xC6U, 0x89U, + 0x80U, 0x80U, 0x00U, 0x41U, 0x26U, 0x42U, 0xC7U, 0x01U, 0x10U, 0x81U, + 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x20U, 0x01U, + 0x41U, 0x20U, 0x20U, 0x01U, 0x41U, 0xC0U, 0x00U, 0x6AU, 0x41U, 0x8EU, + 0x02U, 0x10U, 0x88U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x20U, 0x51U, + 0x0DU, 0x00U, 0x41U, 0xCBU, 0x8CU, 0x80U, 0x80U, 0x00U, 0x41U, 0x21U, + 0x42U, 0xCAU, 0x01U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, + 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x00U, 0x10U, 0x8AU, 0x80U, + 0x80U, 0x80U, 0x00U, 0x21U, 0x02U, 0x20U, 0x01U, 0x41U, 0xF0U, 0x02U, + 0x6AU, 0x24U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x20U, 0x02U, 0x0BU, + 0x0BU, 0xF4U, 0x04U, 0x01U, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0xECU, + 0x04U, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x66U, 0x69U, 0x65U, 0x6CU, + 0x64U, 0x28U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x62U, 0x6FU, 0x62U, + 0x29U, 0x2CU, 0x20U, 0x73U, 0x66U, 0x44U, 0x65U, 0x73U, 0x74U, 0x69U, + 0x6EU, 0x61U, 0x74U, 0x69U, 0x6FU, 0x6EU, 0x29U, 0x20U, 0x3DU, 0x3DU, + 0x20U, 0x32U, 0x30U, 0x00U, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x67U, + 0x65U, 0x6EU, 0x65U, 0x72U, 0x61U, 0x74U, 0x69U, 0x6FU, 0x6EU, 0x28U, + 0x29U, 0x20U, 0x2BU, 0x20U, 0x31U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x65U, + 0x74U, 0x78U, 0x6EU, 0x5FU, 0x67U, 0x65U, 0x6EU, 0x65U, 0x72U, 0x61U, + 0x74U, 0x69U, 0x6FU, 0x6EU, 0x28U, 0x29U, 0x00U, 0x65U, 0x74U, 0x78U, + 0x6EU, 0x5FU, 0x62U, 0x75U, 0x72U, 0x64U, 0x65U, 0x6EU, 0x28U, 0x29U, + 0x20U, 0x3DU, 0x3DU, 0x20U, 0x50U, 0x52U, 0x45U, 0x52U, 0x45U, 0x51U, + 0x55U, 0x49U, 0x53U, 0x49U, 0x54U, 0x45U, 0x5FU, 0x4EU, 0x4FU, 0x54U, + 0x5FU, 0x4DU, 0x45U, 0x54U, 0x00U, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, + 0x72U, 0x65U, 0x73U, 0x65U, 0x72U, 0x76U, 0x65U, 0x28U, 0x32U, 0x29U, + 0x20U, 0x3DU, 0x3DU, 0x20U, 0x32U, 0x00U, 0x6FU, 0x74U, 0x78U, 0x6EU, + 0x5FU, 0x62U, 0x75U, 0x72U, 0x64U, 0x65U, 0x6EU, 0x28U, 0x29U, 0x20U, + 0x3EU, 0x20U, 0x30U, 0x00U, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x62U, + 0x75U, 0x72U, 0x64U, 0x65U, 0x6EU, 0x28U, 0x29U, 0x20U, 0x3DU, 0x3DU, + 0x20U, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x62U, 0x75U, 0x72U, 0x64U, + 0x65U, 0x6EU, 0x28U, 0x29U, 0x20U, 0x2AU, 0x20U, 0x32U, 0x00U, 0x70U, + 0x72U, 0x65U, 0x70U, 0x61U, 0x72U, 0x65U, 0x28U, 0x62U, 0x75U, 0x66U, + 0x5FU, 0x6FU, 0x75U, 0x74U, 0x2CU, 0x20U, 0x31U, 0x30U, 0x30U, 0x30U, + 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x62U, 0x75U, 0x66U, + 0x29U, 0x29U, 0x20U, 0x3EU, 0x20U, 0x30U, 0x00U, 0x65U, 0x6DU, 0x69U, + 0x74U, 0x28U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x68U, 0x61U, 0x73U, + 0x68U, 0x31U, 0x29U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, + 0x74U, 0x78U, 0x29U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x33U, 0x32U, + 0x00U, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x64U, 0x65U, 0x74U, 0x61U, + 0x69U, 0x6CU, 0x73U, 0x28U, 0x74U, 0x78U, 0x20U, 0x2BU, 0x20U, 0x31U, + 0x33U, 0x32U, 0x2CU, 0x20U, 0x31U, 0x33U, 0x38U, 0x29U, 0x20U, 0x3DU, + 0x3DU, 0x20U, 0x31U, 0x33U, 0x38U, 0x00U, 0x65U, 0x6DU, 0x69U, 0x74U, + 0x28U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x68U, 0x61U, 0x73U, 0x68U, + 0x32U, 0x29U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x74U, + 0x78U, 0x29U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x33U, 0x32U, 0x00U, + 0x21U, 0x42U, 0x55U, 0x46U, 0x46U, 0x45U, 0x52U, 0x5FU, 0x45U, 0x51U, + 0x55U, 0x41U, 0x4CU, 0x5FU, 0x33U, 0x32U, 0x28U, 0x68U, 0x61U, 0x73U, + 0x68U, 0x31U, 0x2CU, 0x20U, 0x68U, 0x61U, 0x73U, 0x68U, 0x32U, 0x29U, + 0x00U, 0x70U, 0x72U, 0x65U, 0x70U, 0x61U, 0x72U, 0x65U, 0x28U, 0x31U, + 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x2CU, 0x20U, 0x33U, 0x32U, + 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, 0x70U, 0x72U, 0x65U, 0x70U, + 0x61U, 0x72U, 0x65U, 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, 0x70U, + 0x72U, 0x65U, 0x70U, 0x61U, 0x72U, 0x65U, 0x28U, 0x30U, 0x2CU, 0x33U, + 0x32U, 0x2CU, 0x20U, 0x31U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, + 0x2CU, 0x20U, 0x33U, 0x32U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x4FU, + 0x55U, 0x54U, 0x5FU, 0x4FU, 0x46U, 0x5FU, 0x42U, 0x4FU, 0x55U, 0x4EU, + 0x44U, 0x53U, 0x00U, 0x70U, 0x72U, 0x65U, 0x70U, 0x61U, 0x72U, 0x65U, + 0x28U, 0x30U, 0x2CU, 0x33U, 0x32U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x20U, + 0x31U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x29U, 0x20U, 0x3DU, + 0x3DU, 0x20U, 0x4FU, 0x55U, 0x54U, 0x5FU, 0x4FU, 0x46U, 0x5FU, 0x42U, + 0x4FU, 0x55U, 0x4EU, 0x44U, 0x53U, 0x00U, 0x62U, 0x6FU, 0x62U, 0x00U, + 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x70U, 0x61U, 0x72U, 0x61U, 0x6DU, + 0x28U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x62U, 0x6FU, 0x62U, 0x29U, + 0x2CU, 0x20U, 0x22U, 0x62U, 0x6FU, 0x62U, 0x22U, 0x2CU, 0x20U, 0x33U, + 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x32U, 0x30U, 0x00U, 0x65U, 0x6DU, + 0x69U, 0x74U, 0x28U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x68U, 0x61U, + 0x73U, 0x68U, 0x29U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, + 0x74U, 0x78U, 0x29U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x33U, 0x32U, + 0x00U, + }}, + + /* ==== WASM: 8 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -1365,7 +1794,7 @@ std::map> wasm = { 0x3DU, 0x20U, 0x30U, 0x78U, 0x45U, 0x31U, 0x00U, }}, - /* ==== WASM: 8 ==== */ + /* ==== WASM: 9 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -1487,7 +1916,7 @@ std::map> wasm = { 0x58U, 0x4EU, 0x00U, }}, - /* ==== WASM: 9 ==== */ + /* ==== WASM: 10 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -1620,7 +2049,7 @@ std::map> wasm = { 0x4FU, 0x4EU, 0x43U, 0x45U, 0x53U, 0x00U, }}, - /* ==== WASM: 10 ==== */ + /* ==== WASM: 11 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -1700,7 +2129,7 @@ std::map> wasm = { 0x54U, 0x00U, }}, - /* ==== WASM: 11 ==== */ + /* ==== WASM: 12 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -1745,7 +2174,7 @@ std::map> wasm = { 0x30U, 0x00U, }}, - /* ==== WASM: 12 ==== */ + /* ==== WASM: 13 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -2064,7 +2493,7 @@ std::map> wasm = { 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, }}, - /* ==== WASM: 13 ==== */ + /* ==== WASM: 14 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -2850,7 +3279,7 @@ std::map> wasm = { 0x37U, 0x36U, 0x33U, 0x4CU, 0x4CU, 0x20U, 0x29U, 0x00U, }}, - /* ==== WASM: 14 ==== */ + /* ==== WASM: 15 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -3240,7 +3669,7 @@ std::map> wasm = { 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, }}, - /* ==== WASM: 15 ==== */ + /* ==== WASM: 16 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -3421,7 +3850,7 @@ std::map> wasm = { 0x38U, 0x4CU, 0x4CU, 0x29U, 0x00U, }}, - /* ==== WASM: 16 ==== */ + /* ==== WASM: 17 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -3610,7 +4039,7 @@ std::map> wasm = { 0x29U, 0x00U, }}, - /* ==== WASM: 17 ==== */ + /* ==== WASM: 18 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -3849,7 +4278,7 @@ std::map> wasm = { 0x00U, 0x42U, 0x00U, 0x10U, 0x85U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, }}, - /* ==== WASM: 18 ==== */ + /* ==== WASM: 19 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -4551,7 +4980,7 @@ std::map> wasm = { 0x38U, 0x35U, 0x35U, 0x32U, 0x55U, 0x29U, 0x00U, }}, - /* ==== WASM: 19 ==== */ + /* ==== WASM: 20 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -5896,7 +6325,7 @@ std::map> wasm = { 0x20U, 0x29U, 0x00U, }}, - /* ==== WASM: 20 ==== */ + /* ==== WASM: 21 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -5998,7 +6427,7 @@ std::map> wasm = { 0x84U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, }}, - /* ==== WASM: 21 ==== */ + /* ==== WASM: 22 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -6041,7 +6470,7 @@ std::map> wasm = { 0x00U, 0x0BU, }}, - /* ==== WASM: 22 ==== */ + /* ==== WASM: 23 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -6189,7 +6618,7 @@ std::map> wasm = { 0x34U, 0x34U, 0x4CU, 0x4CU, 0x2CU, 0x20U, 0x33U, 0x29U, 0x00U, }}, - /* ==== WASM: 23 ==== */ + /* ==== WASM: 24 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -6517,7 +6946,7 @@ std::map> wasm = { 0x38U, 0x34U, 0x39U, 0x30U, 0x4CU, 0x4CU, 0x00U, }}, - /* ==== WASM: 24 ==== */ + /* ==== WASM: 25 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -6722,7 +7151,7 @@ std::map> wasm = { 0x10U, 0x85U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, }}, - /* ==== WASM: 25 ==== */ + /* ==== WASM: 26 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -7406,7 +7835,7 @@ std::map> wasm = { 0x20U, 0x3DU, 0x3DU, 0x20U, 0x30U, 0x00U, }}, - /* ==== WASM: 26 ==== */ + /* ==== WASM: 27 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -7701,7 +8130,7 @@ std::map> wasm = { 0x32U, 0x34U, 0x31U, 0x36U, 0x55U, 0x4CU, 0x4CU, 0x00U, }}, - /* ==== WASM: 27 ==== */ + /* ==== WASM: 28 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -8414,7 +8843,7 @@ std::map> wasm = { 0x31U, 0x33U, 0x33U, 0x38U, 0x20U, 0x29U, 0x00U, }}, - /* ==== WASM: 28 ==== */ + /* ==== WASM: 29 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -8499,7 +8928,7 @@ std::map> wasm = { 0x30U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x32U, 0x30U, 0x00U, }}, - /* ==== WASM: 29 ==== */ + /* ==== WASM: 30 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -8561,7 +8990,7 @@ std::map> wasm = { 0x80U, 0x80U, 0x00U, 0x0BU, }}, - /* ==== WASM: 30 ==== */ + /* ==== WASM: 31 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -8638,7 +9067,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); @@ -8715,7 +9144,7 @@ std::map> wasm = { 0x31U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x33U, 0x32U, 0x00U, }}, - /* ==== WASM: 32 ==== */ + /* ==== WASM: 33 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -8972,7 +9401,7 @@ std::map> wasm = { 0x00U, 0x00U, }}, - /* ==== WASM: 33 ==== */ + /* ==== WASM: 34 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -9130,7 +9559,7 @@ std::map> wasm = { 0x04U, 0x00U, 0x00U, }}, - /* ==== WASM: 34 ==== */ + /* ==== WASM: 35 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -9483,7 +9912,7 @@ std::map> wasm = { 0x00U, 0x2AU, 0x04U, 0x00U, 0x00U, 0x31U, 0x04U, 0x00U, 0x00U, }}, - /* ==== WASM: 35 ==== */ + /* ==== WASM: 36 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -9516,7 +9945,7 @@ std::map> wasm = { 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x0BU, }}, - /* ==== WASM: 36 ==== */ + /* ==== WASM: 37 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -9743,7 +10172,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 37 ==== */ + /* ==== WASM: 38 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -9774,7 +10203,7 @@ std::map> wasm = { 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x0BU, }}, - /* ==== WASM: 38 ==== */ + /* ==== WASM: 39 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -10085,7 +10514,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 39 ==== */ + /* ==== WASM: 40 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -10162,7 +10591,7 @@ std::map> wasm = { 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x33U, 0x32U, 0x00U, }}, - /* ==== WASM: 40 ==== */ + /* ==== WASM: 41 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -10196,7 +10625,7 @@ std::map> wasm = { 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x0BU, }}, - /* ==== WASM: 41 ==== */ + /* ==== WASM: 42 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -10283,7 +10712,7 @@ std::map> wasm = { 0x32U, 0x00U, }}, - /* ==== WASM: 42 ==== */ + /* ==== WASM: 43 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -10317,7 +10746,7 @@ std::map> wasm = { 0x0BU, }}, - /* ==== WASM: 43 ==== */ + /* ==== WASM: 44 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -10457,7 +10886,7 @@ std::map> wasm = { 0x54U, 0x5FU, 0x4DU, 0x45U, 0x54U, 0x00U, }}, - /* ==== WASM: 44 ==== */ + /* ==== WASM: 45 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -10647,7 +11076,7 @@ std::map> wasm = { 0x31U, 0x34U, 0x00U, }}, - /* ==== WASM: 45 ==== */ + /* ==== WASM: 46 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -10801,7 +11230,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 46 ==== */ + /* ==== WASM: 47 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -10962,7 +11391,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 47 ==== */ + /* ==== WASM: 48 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -11119,7 +11548,7 @@ std::map> wasm = { 0x53U, 0x4CU, 0x4FU, 0x54U, 0x53U, 0x00U, }}, - /* ==== WASM: 48 ==== */ + /* ==== WASM: 49 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -11204,7 +11633,7 @@ std::map> wasm = { 0x74U, 0x79U, 0x70U, 0x65U, 0x28U, 0x29U, 0x00U, }}, - /* ==== WASM: 49 ==== */ + /* ==== WASM: 50 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -11461,7 +11890,7 @@ std::map> wasm = { 0x00U, 0x00U, }}, - /* ==== WASM: 50 ==== */ + /* ==== WASM: 51 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -11699,7 +12128,7 @@ std::map> wasm = { 0x3EU, 0x20U, 0x30U, 0x00U, }}, - /* ==== WASM: 51 ==== */ + /* ==== WASM: 52 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -11795,7 +12224,7 @@ std::map> wasm = { 0x53U, 0x54U, 0x00U, }}, - /* ==== WASM: 52 ==== */ + /* ==== WASM: 53 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -11905,7 +12334,7 @@ std::map> wasm = { 0x20U, 0x31U, 0x00U, }}, - /* ==== WASM: 53 ==== */ + /* ==== WASM: 54 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -12035,7 +12464,7 @@ std::map> wasm = { 0x30U, 0x30U, 0x30U, 0x4CU, 0x4CU, 0x00U, }}, - /* ==== WASM: 54 ==== */ + /* ==== WASM: 55 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -12309,7 +12738,7 @@ std::map> wasm = { 0x30U, 0x00U, }}, - /* ==== WASM: 55 ==== */ + /* ==== WASM: 56 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -12446,7 +12875,7 @@ std::map> wasm = { 0x2CU, 0x20U, 0x73U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x31U, 0x00U, }}, - /* ==== WASM: 56 ==== */ + /* ==== WASM: 57 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -12740,7 +13169,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); @@ -12974,7 +13403,7 @@ std::map> wasm = { 0x5FU, 0x53U, 0x4CU, 0x4FU, 0x54U, 0x53U, 0x00U, }}, - /* ==== WASM: 58 ==== */ + /* ==== WASM: 59 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -13270,7 +13699,7 @@ std::map> wasm = { 0x2CU, 0x20U, 0x31U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x30U, 0x00U, }}, - /* ==== WASM: 59 ==== */ + /* ==== WASM: 60 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -13474,7 +13903,7 @@ std::map> wasm = { 0x6EU, 0x74U, 0x32U, 0x22U, 0x20U, 0x2BU, 0x20U, 0x69U, 0x29U, 0x00U, }}, - /* ==== WASM: 60 ==== */ + /* ==== WASM: 61 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -13591,7 +14020,7 @@ std::map> wasm = { 0x69U, 0x29U, 0x00U, }}, - /* ==== WASM: 61 ==== */ + /* ==== WASM: 62 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -13700,7 +14129,7 @@ std::map> wasm = { 0x6EU, 0x74U, 0x65U, 0x6EU, 0x74U, 0x32U, 0x22U, 0x29U, 0x00U, }}, - /* ==== WASM: 62 ==== */ + /* ==== WASM: 63 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -13973,7 +14402,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 63 ==== */ + /* ==== WASM: 64 ==== */ {R"[test.hook]( #include #define sfInvoiceID ((5U << 16U) + 17U) @@ -14192,7 +14621,7 @@ std::map> wasm = { 0x30U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x33U, 0x32U, 0x00U, }}, - /* ==== WASM: 64 ==== */ + /* ==== WASM: 65 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -14304,7 +14733,7 @@ std::map> wasm = { 0x58U, 0x49U, 0x53U, 0x54U, 0x00U, }}, - /* ==== WASM: 65 ==== */ + /* ==== WASM: 66 ==== */ {R"[test.hook]( #include #define sfInvoiceID ((5U << 16U) + 17U) @@ -14440,7 +14869,7 @@ std::map> wasm = { 0x3DU, 0x20U, 0x33U, 0x32U, 0x00U, }}, - /* ==== WASM: 66 ==== */ + /* ==== WASM: 67 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -14576,7 +15005,7 @@ std::map> wasm = { 0x49U, 0x47U, 0x00U, }}, - /* ==== WASM: 67 ==== */ + /* ==== WASM: 68 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -14715,7 +15144,7 @@ std::map> wasm = { 0x66U, 0x28U, 0x64U, 0x61U, 0x74U, 0x61U, 0x32U, 0x29U, 0x00U, }}, - /* ==== WASM: 68 ==== */ + /* ==== WASM: 69 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -14827,7 +15256,7 @@ std::map> wasm = { 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x30U, 0x00U, }}, - /* ==== WASM: 69 ==== */ + /* ==== WASM: 70 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -14921,7 +15350,7 @@ std::map> wasm = { 0x61U, 0x64U, 0x5BU, 0x69U, 0x5DU, 0x00U, }}, - /* ==== WASM: 70 ==== */ + /* ==== WASM: 71 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -15054,7 +15483,7 @@ std::map> wasm = { 0x64U, 0x5BU, 0x69U, 0x5DU, 0x00U, }}, - /* ==== WASM: 71 ==== */ + /* ==== WASM: 72 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -15142,7 +15571,7 @@ std::map> wasm = { 0x61U, 0x74U, 0x61U, 0x29U, 0x00U, }}, - /* ==== WASM: 72 ==== */ + /* ==== WASM: 73 ==== */ {R"[test.hook]( #include #define sfInvoiceID ((5U << 16U) + 17U) @@ -15253,7 +15682,7 @@ std::map> wasm = { 0x20U, 0x33U, 0x32U, 0x00U, }}, - /* ==== WASM: 73 ==== */ + /* ==== WASM: 74 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -15464,7 +15893,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 74 ==== */ + /* ==== WASM: 75 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -15572,7 +16001,7 @@ std::map> wasm = { 0x20U, 0x22U, 0x32U, 0x22U, 0x2CU, 0x20U, 0x31U, 0x29U, 0x00U, }}, - /* ==== WASM: 75 ==== */ + /* ==== WASM: 76 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -16184,7 +16613,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 76 ==== */ + /* ==== WASM: 77 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -16371,7 +16800,7 @@ std::map> wasm = { 0x63U, 0x65U, 0x29U, 0x20U, 0x3EU, 0x20U, 0x30U, 0x00U, }}, - /* ==== WASM: 77 ==== */ + /* ==== WASM: 78 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -16721,7 +17150,7 @@ std::map> wasm = { 0x20U, 0x30U, 0x00U, }}, - /* ==== WASM: 78 ==== */ + /* ==== WASM: 79 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -16857,7 +17286,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 79 ==== */ + /* ==== WASM: 80 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -16916,7 +17345,7 @@ std::map> wasm = { 0x64U, 0xE1U, 0xF1U, }}, - /* ==== WASM: 79 ==== */ + /* ==== WASM: 81 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -17089,7 +17518,7 @@ std::map> wasm = { 0x54U, 0x5FU, 0x45U, 0x58U, 0x49U, 0x53U, 0x54U, 0x00U, }}, - /* ==== WASM: 80 ==== */ + /* ==== WASM: 82 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -17237,7 +17666,161 @@ std::map> wasm = { 0x30U, 0x00U, 0x22U, 0x00U, 0x00U, 0x00U, 0x00U, }}, - /* ==== WASM: 81 ==== */ + /* ==== WASM: 83 ==== */ + {R"[test.hook]( + #include + extern int32_t _g (uint32_t id, uint32_t maxiter); + #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) + 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 sto_validate(uint32_t, uint32_t); + 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 SBUF(x) (uint32_t)(x), sizeof(x) + + uint8_t buf[1000]; + int64_t hook(uint32_t reserved) + { + _g(1,1); + + int64_t size = otxn_param(SBUF(buf), "V", 1); + int64_t result = sto_validate(buf, size); + + accept(0,0,result); + } + )[test.hook]", + { + 0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, 0x21U, + 0x05U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x04U, 0x7FU, + 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, + 0x7EU, 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, + 0x7FU, 0x01U, 0x7EU, 0x02U, 0x3BU, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, + 0x02U, 0x5FU, 0x67U, 0x00U, 0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0AU, + 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x70U, 0x61U, 0x72U, 0x61U, 0x6DU, + 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0CU, 0x73U, 0x74U, 0x6FU, + 0x5FU, 0x76U, 0x61U, 0x6CU, 0x69U, 0x64U, 0x61U, 0x74U, 0x65U, 0x00U, + 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, + 0x70U, 0x74U, 0x00U, 0x03U, 0x03U, 0x02U, 0x01U, 0x04U, 0x05U, 0x03U, + 0x01U, 0x00U, 0x02U, 0x06U, 0x27U, 0x06U, 0x7FU, 0x01U, 0x41U, 0xF0U, + 0x8FU, 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0xEAU, 0x0FU, 0x0BU, 0x7FU, + 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0xF0U, 0x8FU, + 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, 0xC4U, 0x80U, 0x00U, 0x01U, 0xC0U, + 0x80U, 0x00U, 0x01U, 0x01U, 0x7EU, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U, + 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x41U, 0x00U, 0x41U, 0x00U, + 0x41U, 0x80U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x80U, 0x88U, 0x80U, + 0x80U, 0x00U, 0x41U, 0xE8U, 0x07U, 0x41U, 0xE8U, 0x8FU, 0x80U, 0x80U, + 0x00U, 0x41U, 0x01U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0xA7U, + 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x10U, 0x83U, 0x80U, 0x80U, + 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x0BU, 0x0BU, 0xF8U, 0x07U, 0x02U, + 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0xE8U, 0x07U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x41U, 0xE8U, + 0x0FU, 0x0BU, 0x02U, 0x56U, 0x00U, + }}, + + /* ==== WASM: 84 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -17334,7 +17917,7 @@ std::map> wasm = { 0x3DU, 0x20U, 0x30U, 0x00U, }}, - /* ==== WASM: 82 ==== */ + /* ==== WASM: 84 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -17393,7 +17976,7 @@ std::map> wasm = { 0x4FU, 0x46U, 0x5FU, 0x42U, 0x4FU, 0x55U, 0x4EU, 0x44U, 0x53U, 0x00U, }}, - /* ==== WASM: 83 ==== */ + /* ==== WASM: 85 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -17452,7 +18035,7 @@ std::map> wasm = { 0x4EU, 0x44U, 0x53U, 0x00U, }}, - /* ==== WASM: 84 ==== */ + /* ==== WASM: 86 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -19281,7 +19864,7 @@ std::map> wasm = { 0x53U, 0x4DU, 0x41U, 0x4CU, 0x4CU, 0x00U, }}, - /* ==== WASM: 85 ==== */ + /* ==== WASM: 87 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -20898,7 +21481,7 @@ std::map> wasm = { 0x2CU, 0x20U, 0x30U, 0x2CU, 0x20U, 0x30U, 0x29U, 0x29U, 0x00U, }}, - /* ==== WASM: 86 ==== */ + /* ==== WASM: 88 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -23831,7 +24414,7 @@ std::map> wasm = { 0x4FU, 0x4FU, 0x5FU, 0x53U, 0x4DU, 0x41U, 0x4CU, 0x4CU, 0x00U, }}, - /* ==== WASM: 87 ==== */ + /* ==== WASM: 89 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -25796,7 +26379,7 @@ std::map> wasm = { 0x54U, 0x4FU, 0x4FU, 0x5FU, 0x53U, 0x4DU, 0x41U, 0x4CU, 0x4CU, 0x00U, }}, - /* ==== WASM: 88 ==== */ + /* ==== WASM: 90 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -26081,7 +26664,7 @@ std::map> wasm = { 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x30U, 0x00U, }}, - /* ==== WASM: 89 ==== */ + /* ==== WASM: 91 ==== */ {R"[test.hook]( #include extern int32_t _g(uint32_t, uint32_t); @@ -26668,7 +27251,7 @@ std::map> wasm = { 0x4EU, 0x5FU, 0x46U, 0x41U, 0x49U, 0x4CU, 0x55U, 0x52U, 0x45U, 0x00U, }}, - /* ==== WASM: 90 ==== */ + /* ==== WASM: 92 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -26697,7 +27280,7 @@ std::map> wasm = { 0x0BU, }}, - /* ==== WASM: 91 ==== */ + /* ==== WASM: 93 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -26729,7 +27312,7 @@ std::map> wasm = { 0x20U, 0x52U, 0x65U, 0x6AU, 0x65U, 0x63U, 0x74U, 0x65U, 0x64U, 0x00U, }}, - /* ==== WASM: 92 ==== */ + /* ==== WASM: 94 ==== */ {R"[test.hook]( (module (type (;0;) (func (param i32 i32 i64) (result i64))) @@ -26756,7 +27339,7 @@ std::map> wasm = { 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x00U, 0x10U, 0x00U, 0x0BU, }}, - /* ==== WASM: 93 ==== */ + /* ==== WASM: 95 ==== */ {R"[test.hook]( (module (type (;0;) (func (param i32 i32) (result i32))) @@ -26809,7 +27392,7 @@ std::map> wasm = { 0x00U, 0x1AU, 0x0BU, }}, - /* ==== WASM: 94 ==== */ + /* ==== WASM: 96 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -33452,7 +34035,7 @@ std::map> wasm = { 0x39U, 0x30U, 0x31U, 0x32U, 0x33U, 0x00U, }}, - /* ==== WASM: 95 ==== */ + /* ==== WASM: 97 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -33498,7 +34081,7 @@ std::map> wasm = { 0x0BU, 0x06U, 0x76U, 0x61U, 0x6CU, 0x75U, 0x65U, 0x00U, }}, - /* ==== WASM: 96 ==== */ + /* ==== WASM: 98 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); diff --git a/src/test/app/XahauGenesis_test.cpp b/src/test/app/XahauGenesis_test.cpp index 18c7903e5..9c73977f0 100644 --- a/src/test/app/XahauGenesis_test.cpp +++ b/src/test/app/XahauGenesis_test.cpp @@ -139,7 +139,9 @@ struct XahauGenesis_test : public beast::unit_test::suite false, // means the calling test already burned some of the genesis bool skipTests = false, bool const testFlag = false, - bool const badNetID = false) + bool const badNetID = false, + uint32_t const expectedOwnerCount = + 10 /** testFlag ? 10 : 14 (default) */) { using namespace jtx; @@ -247,7 +249,10 @@ struct XahauGenesis_test : public beast::unit_test::suite BEAST_EXPECT( genesisAccRoot->getFieldAmount(sfBalance) == XahauGenesis::GenesisAmount); - BEAST_EXPECT(genesisAccRoot->getFieldU32(sfOwnerCount) == 2); + BEAST_EXPECT( + genesisAccRoot->getFieldU32(sfOwnerCount) == !testFlag + ? expectedOwnerCount + : 14); // ensure the definitions are correctly set { @@ -583,7 +588,14 @@ struct XahauGenesis_test : public beast::unit_test::suite toBase58(t), membersStr); } - activate(__LINE__, env, true, false, true); + activate( + __LINE__, + env, + true, + false, + true, + {}, + 3 /* IRR,IRD,IMC */ + members.size() + tables.size()); env.close(); env.close(); @@ -2235,6 +2247,8 @@ struct XahauGenesis_test : public beast::unit_test::suite BEAST_EXPECT(!!hookLE); uint256 const ns = beast::zero; uint8_t mc = 0; + uint8_t paramsCount = 0; + if (hookLE) { auto const hooksArray = hookLE->getFieldArray(sfHooks); @@ -2242,6 +2256,9 @@ struct XahauGenesis_test : public beast::unit_test::suite hooksArray.size() == 1 && hooksArray[0].getFieldH256(sfHookHash) == governHookHash); + paramsCount = + hooksArray[0].getFieldArray(sfHookParameters).size(); + for (Account const* m : members) { auto const mVec = vecFromAcc(*m); @@ -2308,7 +2325,9 @@ struct XahauGenesis_test : public beast::unit_test::suite BEAST_EXPECT(!!root); if (root) { - BEAST_EXPECT(root->getFieldU32(sfOwnerCount) == mc * 2 + 2); + BEAST_EXPECT( + root->getFieldU32(sfOwnerCount) == + mc * 2 + 2 + paramsCount); BEAST_EXPECT(root->getFieldU32(sfFlags) & lsfDisableMaster); BEAST_EXPECT(root->getAccountID(sfRegularKey) == noAccount()); } diff --git a/src/xrpld/app/hook/applyHook.h b/src/xrpld/app/hook/applyHook.h index e87956376..dda770c75 100644 --- a/src/xrpld/app/hook/applyHook.h +++ b/src/xrpld/app/hook/applyHook.h @@ -89,6 +89,12 @@ canEmit(ripple::TxType txType, ripple::uint256 hookCanEmit); ripple::uint256 getHookCanEmit(ripple::STObject const& hookObj, SLE::pointer const& hookDef); +ripple::uint256 +getHookOn( + ripple::STObject const& obj, + std::shared_ptr const& def, + ripple::SField const& field); + struct HookResult; HookResult diff --git a/src/xrpld/app/hook/detail/HookAPI.cpp b/src/xrpld/app/hook/detail/HookAPI.cpp index 71f4aadca..5afe48ea4 100644 --- a/src/xrpld/app/hook/detail/HookAPI.cpp +++ b/src/xrpld/app/hook/detail/HookAPI.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include namespace hook { @@ -355,7 +356,14 @@ HookAPI::sto_validate(Bytes const& data) const { int type = -1, field = -1, payload_start = -1, payload_length = -1; auto const length = get_stobject_length( - upto, end, type, field, payload_start, payload_length, 0); + upto, + end, + type, + field, + payload_start, + payload_length, + hookCtx.applyCtx.view().rules(), + 0); if (!length) return 0; upto += length.value(); @@ -389,7 +397,14 @@ HookAPI::sto_subfield(Bytes const& data, uint32_t field_id) const { int type = -1, field = -1, payload_start = -1, payload_length = -1; auto const length = get_stobject_length( - upto, end, type, field, payload_start, payload_length, 0); + upto, + end, + type, + field, + payload_start, + payload_length, + hookCtx.applyCtx.view().rules(), + 0); if (!length) return Unexpected(PARSE_ERROR); if ((type << 16) + field == field_id) @@ -464,7 +479,14 @@ HookAPI::sto_subarray(Bytes const& data, uint32_t index_id) const { int type = -1, field = -1, payload_start = -1, payload_length = -1; auto const length = get_stobject_length( - upto, end, type, field, payload_start, payload_length, 0); + upto, + end, + type, + field, + payload_start, + payload_length, + hookCtx.applyCtx.view().rules(), + 0); if (!length) return Unexpected(PARSE_ERROR); @@ -530,6 +552,7 @@ HookAPI::sto_emplace( field, payload_start, payload_length, + hookCtx.applyCtx.view().rules(), 0); if (!length) return Unexpected(PARSE_ERROR); @@ -565,7 +588,14 @@ HookAPI::sto_emplace( { int type = -1, field = -1, payload_start = -1, payload_length = -1; auto const length = get_stobject_length( - upto, end, type, field, payload_start, payload_length, 0); + upto, + end, + type, + field, + payload_start, + payload_length, + hookCtx.applyCtx.view().rules(), + 0); if (!length) return Unexpected(PARSE_ERROR); if ((type << 16) + field == field_id) @@ -625,6 +655,115 @@ HookAPI::sto_emplace( // sto_erase /// etxn APIs +Expected +HookAPI::prepare(Slice const& txBlob) const +{ + auto& applyCtx = hookCtx.applyCtx; + auto j = applyCtx.app.journal("View"); + + if (hookCtx.expected_etxn_count < 0) + return Unexpected(PREREQUISITE_NOT_MET); + + Json::Value json; + + try + { + SerialIter sitTrans{txBlob}; + json = + STObject(std::ref(sitTrans), sfGeneric).getJson(JsonOptions::none); + } + catch (std::exception& e) + { + JLOG(j.trace()) << "HookInfo[" << HC_ACC() << "]: prepare Failed " + << e.what() << "\n"; + return Unexpected(INVALID_ARGUMENT); + } + + // add a dummy fee + json[jss::Fee] = "0"; + + // force key to empty + json[jss::SigningPubKey] = + "000000000000000000000000000000000000000000000000000000000000000000"; + + // force sequence to 0 + json[jss::Sequence] = Json::Value(0u); + + std::string raddr = encodeBase58Token( + TokenType::AccountID, hookCtx.result.account.data(), 20); + + json[jss::Account] = raddr; + + uint32_t seq = applyCtx.view().info().seq; + if (!json.isMember(jss::FirstLedgerSequence)) + json[jss::FirstLedgerSequence] = Json::Value(seq + 1); + + if (!json.isMember(jss::LastLedgerSequence)) + json[jss::LastLedgerSequence] = Json::Value(seq + 5); + + uint8_t details[512]; + if (!json.isMember(jss::EmitDetails)) + { + auto ret = etxn_details(details); + if (!ret || ret.value() < 2) + return Unexpected(INTERNAL_ERROR); + + // truncate the head and tail (emit details object markers) + Slice s( + reinterpret_cast(details + 1), + (size_t)(ret.value() - 2)); + + try + { + SerialIter sit{s}; + STObject st{sit, sfEmitDetails}; + json[jss::EmitDetails] = st.getJson(JsonOptions::none); + } + catch (std::exception const& ex) + { + JLOG(j.warn()) << "HookInfo[" << HC_ACC() << "]: Exception in " + << __func__ << ": " << ex.what(); + return Unexpected(INTERNAL_ERROR); + } + } + + Blob tx_blob; + { + STParsedJSONObject parsed(std::string(jss::tx_json), json); + if (!parsed.object.has_value()) + return Unexpected(INVALID_ARGUMENT); + + STObject& obj = *(parsed.object); + + // serialize it + Serializer s; + obj.add(s); + tx_blob = s.getData(); + } + + // run it through the fee estimate, this doubles as a txn sanity check + auto fee = etxn_fee_base(Slice(tx_blob.data(), tx_blob.size())); + if (!fee) + return Unexpected(INVALID_ARGUMENT); + + json[jss::Fee] = to_string(fee.value()); + + { + STParsedJSONObject parsed(std::string(jss::tx_json), json); + if (!parsed.object.has_value()) + return Unexpected(INVALID_ARGUMENT); + + STObject& obj = *(parsed.object); + + // serialize it + Serializer s; + obj.add(s); + tx_blob = s.getData(); + } + + return tx_blob; +} + Expected, HookReturnCode> HookAPI::emit(Slice const& txBlob) const { @@ -2998,12 +3137,20 @@ HookAPI::get_stobject_length( int& payload_start, // out - the start of actual payload data for this type int& payload_length, // out - the length of actual payload data for this // type + Rules const& rules, int recursion_depth) // used internally const { if (recursion_depth > 10) return Unexpected(pe_excessive_nesting); + uint16_t max_sti_type = rules.enabled(featureHookAPISerializedType240) + ? STI_CURRENCY + : STI_VECTOR256; + + if (type > max_sti_type) + return pe_unknown_type_early; + unsigned char* end = maxptr; unsigned char* upto = start; int high = *upto >> 4; @@ -3055,14 +3202,20 @@ HookAPI::get_stobject_length( auto const& fieldObj = ripple::SField::getField; */ - if (type < 1 || type > 19 || (type >= 9 && type <= 13)) + // type 10~13 are reserved + if (type < 1 || max_sti_type < type || (10 <= type && type <= 13)) return Unexpected(pe_unknown_type_early); + // not supported types + if (type == STI_NUMBER || type == STI_UINT96 || type == STI_UINT192 || + type == STI_UINT384 || type == STI_UINT512) + return pe_unknown_type_early; + bool is_vl = - (type == SerializedTypeID::STI_ACCOUNT || - type == SerializedTypeID::STI_VL || - type == SerializedTypeID::STI_PATHSET || - type == SerializedTypeID::STI_VECTOR256); + (type == STI_ACCOUNT || type == STI_VL || + (type == STI_PATHSET && + !rules.enabled(featureHookAPISerializedType240)) || + type == STI_VECTOR256); int length = -1; if (is_vl) @@ -3095,42 +3248,116 @@ HookAPI::get_stobject_length( return Unexpected(pe_unexpected_end); } } - else if ((type >= 1 && type <= 5) || type == 16 || type == 17) + else if ( + (type >= STI_UINT16 && type <= STI_UINT256) || type == STI_UINT8 || + type == STI_UINT160 || type == STI_CURRENCY) { switch (type) { - case SerializedTypeID::STI_UINT16: + case STI_UINT16: length = 2; break; - case SerializedTypeID::STI_UINT32: + case STI_UINT32: length = 4; break; - case SerializedTypeID::STI_UINT64: + case STI_UINT64: length = 8; break; - case SerializedTypeID::STI_UINT128: + case STI_UINT128: length = 16; break; - case SerializedTypeID::STI_UINT256: + case STI_UINT256: length = 32; break; - case SerializedTypeID::STI_UINT8: + case STI_UINT8: length = 1; break; - case SerializedTypeID::STI_UINT160: + case STI_UINT160: + length = 20; + break; + case STI_CURRENCY: length = 20; break; default: - length = -1; - break; + return -1; } } - else if (type == SerializedTypeID::STI_AMOUNT) + else if (type == STI_AMOUNT) /* AMOUNT */ { length = (*upto >> 6 == 1) ? 8 : 48; if (upto >= end) return Unexpected(pe_unexpected_end); } + else if ( + type == STI_PATHSET && rules.enabled(featureHookAPISerializedType240)) + { + length = 0; + while (upto + length < end) + { + // iterate Path step + while (*(upto + length) & 0x01 || *(upto + length) & 0x10 || + *(upto + length) & 0x20) + { + int flag = *(upto + length++); + // flag shoud be 0x01 or 0x10 or 0x20 or those union + if (flag == 0 || flag & ~(0x01 | 0x10 | 0x20)) + return pe_unexpected_end; + if (flag & 0x01) // account + length += 20; + if (flag & 0x10) // currency + length += 20; + if (flag & 0x20) // issuer + length += 20; + + int next_flag = *(upto + length); + if (next_flag == 0x00 || next_flag == 0xff) + // end of Path step + break; + } + + // continue or end of Paths + int lastflag = *(upto + length++); + if (lastflag == 0xff) + continue; // continue byte + else if (lastflag == 0x00) + break; // end byte + else + return pe_unexpected_end; + } + if (upto >= end) + return pe_unexpected_end; + } + else if (type == STI_ISSUE) + { + auto zero20 = std::array{0}; + // if first 20 byte is all zeros return 20 + // else return 40 + if (memcmp(upto, zero20.data(), 20) == 0) + length = 20; + else + length = 40; + } + else if (type == STI_XCHAIN_BRIDGE) + { + auto zero20 = std::array{0}; + // Lock Chain + length = 1; // Door Account1 prefix length + length += 20; // Door Account1 length + // Door Issue1 + if (memcmp(upto + length, zero20.data(), 20) == 0) + length += 20; // only Currency + else + length += 40; // Currency and Issue + + // Issuing Chain + length += 1; // Door Account2 prefix length + length += 20; // Door Account2 length + // Door Issue2 + if (memcmp(upto + length, zero20.data(), 20) == 0) + length += 20; // only Currency + else + length += 40; // Currency and Issue + } if (length > -1) { @@ -3149,8 +3376,7 @@ HookAPI::get_stobject_length( return length + (upto - start); } - if (type == SerializedTypeID::STI_OBJECT || - type == SerializedTypeID::STI_ARRAY) + if (type == STI_OBJECT || type == STI_ARRAY) { payload_start = upto - start; @@ -3165,6 +3391,7 @@ HookAPI::get_stobject_length( subfield, payload_start_, payload_length_, + hookCtx.applyCtx.view().rules(), recursion_depth + 1); DBG_PRINTF( "%d get_stobject_length i %d %d-%d, upto %d sublength %d\n", @@ -3180,8 +3407,8 @@ HookAPI::get_stobject_length( if (upto >= end) return Unexpected(pe_unexpected_end); - if ((*upto == 0xE1U && type == 0xEU) || - (*upto == 0xF1U && type == 0xFU)) + if ((*upto == 0xE1U && type == 0xEU) || // STI_OBJECT Maker + (*upto == 0xF1U && type == 0xFU)) // STI_ARRAY Maker { payload_length = upto - start - payload_start; upto++; diff --git a/src/xrpld/app/hook/detail/applyHook.cpp b/src/xrpld/app/hook/detail/applyHook.cpp index 6ecee9564..b7e56bc2b 100644 --- a/src/xrpld/app/hook/detail/applyHook.cpp +++ b/src/xrpld/app/hook/detail/applyHook.cpp @@ -1001,6 +1001,23 @@ hook::getHookCanEmit( return hookCanEmit; } +ripple::uint256 +hook::getHookOn( + STObject const& obj, + std::shared_ptr const& def, + SField const& field) +{ + if (obj.isFieldPresent(field)) + return obj.getFieldH256(field); + if (obj.isFieldPresent(sfHookOn)) + return obj.getFieldH256(sfHookOn); + if (def->isFieldPresent(field)) + return def->getFieldH256(field); + if (def->isFieldPresent(sfHookOn)) + return def->getFieldH256(sfHookOn); + return uint256{0}; +} + // Update HookState ledger objects for the hook... only called after accept() // assumes the specified acc has already been checked for authoriation (hook // grants) @@ -2745,6 +2762,43 @@ DEFINE_HOOK_FUNCTION( HOOK_TEARDOWN(); } +DEFINE_HOOK_FUNCTION( + int64_t, + prepare, + uint32_t write_ptr, + uint32_t write_len, + uint32_t read_ptr, + uint32_t read_len) +{ + HOOK_SETUP(); // populates memory_ctx, memory, memory_length, applyCtx, + // hookCtx on current stack + + if (NOT_IN_BOUNDS(read_ptr, read_len, memory_length)) + return OUT_OF_BOUNDS; + + if (NOT_IN_BOUNDS(write_ptr, write_len, memory_length)) + return OUT_OF_BOUNDS; + + ripple::Slice txBlob{ + reinterpret_cast(memory + read_ptr), read_len}; + + auto const res = api.prepare(txBlob); + if (!res) + return res.error(); + + auto tx_blob = res.value(); + + WRITE_WASM_MEMORY_AND_RETURN( + write_ptr, + tx_blob.size(), + tx_blob.data(), + tx_blob.size(), + memory, + memory_length); + + HOOK_TEARDOWN(); +} + /* Emit a transaction from this hook. Transaction must be in STObject form, * fully formed and valid. XRPLD does not modify transactions it only checks * them for validity. */ diff --git a/src/xrpld/app/tx/detail/Change.cpp b/src/xrpld/app/tx/detail/Change.cpp index 704524eda..8b437dda3 100644 --- a/src/xrpld/app/tx/detail/Change.cpp +++ b/src/xrpld/app/tx/detail/Change.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -585,10 +586,6 @@ Change::activateXahauGenesis() SetSignerList::removeFromLedger(ctx_.app, sb, accid, j_); // Step 4: install genesis hooks - sle->setFieldU32( - sfOwnerCount, sle->getFieldU32(sfOwnerCount) + genesis_hooks.size()); - sb.update(sle); - if (sb.exists(keylet::hook(accid))) { JLOG(j_.warn()) << "featureXahauGenesis genesis account already has " @@ -600,6 +597,7 @@ Change::activateXahauGenesis() ripple::STArray hooks{ sfHooks, static_cast(genesis_hooks.size())}; int hookCount = 0; + uint32_t hookReserve = 0; for (auto const& [hookOn, wasmBytes, params] : genesis_hooks) { @@ -705,8 +703,14 @@ Change::activateXahauGenesis() } hooks.push_back(hookObj); + + hookReserve += SetHook::computeHookReserve(hookObj); } + sle->setFieldU32( + sfOwnerCount, sle->getFieldU32(sfOwnerCount) + hookReserve); + sb.update(sle); + auto sle = std::make_shared(keylet::hook(accid)); sle->setFieldArray(sfHooks, hooks); sle->setAccountID(sfAccount, accid); @@ -747,6 +751,8 @@ Change::activateXahauGenesis() ripple::STArray hooks{sfHooks, 1}; STObject hookObj{sfHook}; hookObj.setFieldH256(sfHookHash, governHash); + + uint32_t hookReserve = 0; // parameters { std::vector vec; @@ -762,6 +768,7 @@ Change::activateXahauGenesis() sfHookParameters, STArray(vec, sfHookParameters)); } + hookReserve += SetHook::computeHookReserve(hookObj); hooks.push_back(hookObj); auto sle = std::make_shared(hookKL); @@ -788,7 +795,8 @@ Change::activateXahauGenesis() sle->setAccountID(sfRegularKey, noAccount()); sle->setFieldU32(sfFlags, lsfDisableMaster); - sle->setFieldU32(sfOwnerCount, sle->getFieldU32(sfOwnerCount) + 1); + sle->setFieldU32( + sfOwnerCount, sle->getFieldU32(sfOwnerCount) + hookReserve); sb.update(sle); } } diff --git a/src/xrpld/app/tx/detail/SetHook.cpp b/src/xrpld/app/tx/detail/SetHook.cpp index 0d3e63ac1..3e1a48a4e 100644 --- a/src/xrpld/app/tx/detail/SetHook.cpp +++ b/src/xrpld/app/tx/detail/SetHook.cpp @@ -225,7 +225,9 @@ SetHook::inferOperation(STObject const& hookSetObj) !hasHash && !hasCode && !hookSetObj.isFieldPresent(sfHookGrants) && !hookSetObj.isFieldPresent(sfHookNamespace) && !hookSetObj.isFieldPresent(sfHookParameters) && - !hookSetObj.isFieldPresent(sfHookOn) && + !(hookSetObj.isFieldPresent(sfHookOn) || + (hookSetObj.isFieldPresent(sfHookOnOutgoing) && + hookSetObj.isFieldPresent(sfHookOnIncoming))) && !hookSetObj.isFieldPresent(sfHookCanEmit) && !hookSetObj.isFieldPresent(sfHookApiVersion) && !hookSetObj.isFieldPresent(sfFlags)) @@ -261,6 +263,8 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj) if (hookSetObj.isFieldPresent(sfHookGrants) || hookSetObj.isFieldPresent(sfHookParameters) || hookSetObj.isFieldPresent(sfHookOn) || + hookSetObj.isFieldPresent(sfHookOnOutgoing) || + hookSetObj.isFieldPresent(sfHookOnIncoming) || hookSetObj.isFieldPresent(sfHookCanEmit) || hookSetObj.isFieldPresent(sfHookApiVersion) || !hookSetObj.isFieldPresent(sfFlags) || @@ -291,6 +295,8 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj) if (hookSetObj.isFieldPresent(sfHookGrants) || hookSetObj.isFieldPresent(sfHookParameters) || hookSetObj.isFieldPresent(sfHookOn) || + hookSetObj.isFieldPresent(sfHookOnOutgoing) || + hookSetObj.isFieldPresent(sfHookOnIncoming) || hookSetObj.isFieldPresent(sfHookCanEmit) || hookSetObj.isFieldPresent(sfHookApiVersion) || hookSetObj.isFieldPresent(sfHookNamespace) || @@ -448,12 +454,53 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj) // validate sfHookOn if (!hookSetObj.isFieldPresent(sfHookOn)) { - JLOG(ctx.j.trace()) - << "HookSet(" << hook::log::HOOKON_MISSING << ")[" - << HS_ACC() - << "]: Malformed transaction: SetHook must include " - "sfHookOn when creating a new hook."; - return false; + if (!ctx.rules.enabled(featureHookOnV2)) + { + JLOG(ctx.j.trace()) + << "HookSet(" << hook::log::HOOKON_MISSING << ")[" + << HS_ACC() + << "]: Malformed transaction: SetHook must include " + "sfHookOn before featureHookOnV2 is enabled."; + return false; + } + + if (!hookSetObj.isFieldPresent(sfHookOnOutgoing) || + !hookSetObj.isFieldPresent(sfHookOnIncoming)) + { + JLOG(ctx.j.trace()) + << "HookSet(" << hook::log::HOOKON_MISSING << ")[" + << HS_ACC() + << "]: Malformed transaction: SetHook must include " + "sfHookOnOutgoing and sfHookOnIncoming " + "when creating a new hook without sfHookOn."; + return false; + } + + auto const outgoing = hookSetObj.getFieldH256(sfHookOnOutgoing); + auto const incoming = hookSetObj.getFieldH256(sfHookOnIncoming); + if (outgoing == incoming) + { + JLOG(ctx.j.trace()) + << "HookSet(" << hook::log::HOOKON_MISSING << ")[" + << HS_ACC() + << "]: Malformed transaction: SetHook outgoing and " + "incoming hookon must be different."; + return false; + } + } + else + { + if (hookSetObj.isFieldPresent(sfHookOnOutgoing) || + hookSetObj.isFieldPresent(sfHookOnIncoming)) + { + JLOG(ctx.j.trace()) + << "HookSet(" << hook::log::HOOKON_MISSING << ")[" + << HS_ACC() + << "]: Malformed transaction: SetHook must no" + "include sfHookOnOutgoing and sfHookOnIncoming " + "when creating a new hook with sfHookOn."; + return false; + } } // validate sfHookCanEmit @@ -742,7 +789,8 @@ SetHook::preflight(PreflightContext const& ctx) if (name != sfCreateCode && name != sfHookHash && name != sfHookNamespace && name != sfHookParameters && - name != sfHookOn && name != sfHookGrants && + name != sfHookOn && name != sfHookOnOutgoing && + name != sfHookOnIncoming && name != sfHookGrants && name != sfHookApiVersion && name != sfFlags && name != sfHookCanEmit) { @@ -1174,6 +1222,29 @@ updateHookParameters( return tesSUCCESS; } +/** + * Compute the reserve required for a hook object. + * @param hookObj The hook object to compute the reserve for.(not Transaction + * field, use the Hook object inside the ltHook object.) + * @return The reserve required for the hook object. + */ +uint32_t +SetHook::computeHookReserve(STObject const& hookObj) +{ + if (!hookObj.isFieldPresent(sfHookHash)) + return 0; + + int reserve{1}; + + if (hookObj.isFieldPresent(sfHookParameters)) + reserve += hookObj.getFieldArray(sfHookParameters).size(); + + if (hookObj.isFieldPresent(sfHookGrants)) + reserve += hookObj.getFieldArray(sfHookGrants).size(); + + return reserve; +}; + struct KeyletComparator { bool @@ -1265,10 +1336,14 @@ SetHook::setHook() std::optional newNamespace; std::optional newDirKeylet; - std::optional oldHookOn; std::optional newHookOn; std::optional defHookOn; + std::optional newHookOnOutgoing; + std::optional newHookOnIncoming; + std::optional defHookOnOutgoing; + std::optional defHookOnIncoming; + std::optional oldHookCanEmit; std::optional newHookCanEmit; std::optional defHookCanEmit; @@ -1326,13 +1401,18 @@ SetHook::setHook() oldDirKeylet = keylet::hookStateDir(account_, *oldNamespace); oldDirSLE = view().peek(*oldDirKeylet); - if (oldDefSLE) + if (oldDefSLE && oldDefSLE->isFieldPresent(sfHookOn)) defHookOn = oldDefSLE->getFieldH256(sfHookOn); - if (oldHook->get().isFieldPresent(sfHookOn)) - oldHookOn = oldHook->get().getFieldH256(sfHookOn); - else if (defHookOn) - oldHookOn = *defHookOn; + if (oldDefSLE) + { + if (oldDefSLE->isFieldPresent(sfHookOnOutgoing)) + defHookOnOutgoing = + oldDefSLE->getFieldH256(sfHookOnOutgoing); + if (oldDefSLE->isFieldPresent(sfHookOnIncoming)) + defHookOnIncoming = + oldDefSLE->getFieldH256(sfHookOnIncoming); + } if (oldDefSLE && oldDefSLE->isFieldPresent(sfHookCanEmit)) defHookCanEmit = oldDefSLE->getFieldH256(sfHookCanEmit); @@ -1357,6 +1437,14 @@ SetHook::setHook() if (hookSetObj->get().isFieldPresent(sfHookOn)) newHookOn = hookSetObj->get().getFieldH256(sfHookOn); + if (hookSetObj->get().isFieldPresent(sfHookOnOutgoing)) + newHookOnOutgoing = + hookSetObj->get().getFieldH256(sfHookOnOutgoing); + + if (hookSetObj->get().isFieldPresent(sfHookOnIncoming)) + newHookOnIncoming = + hookSetObj->get().getFieldH256(sfHookOnIncoming); + if (hookSetObj->get().isFieldPresent(sfHookCanEmit)) newHookCanEmit = hookSetObj->get().getFieldH256(sfHookCanEmit); @@ -1470,6 +1558,14 @@ SetHook::setHook() if (oldHook->get().isFieldPresent(sfHookOn)) newHook.setFieldH256( sfHookOn, oldHook->get().getFieldH256(sfHookOn)); + if (oldHook->get().isFieldPresent(sfHookOnOutgoing)) + newHook.setFieldH256( + sfHookOnOutgoing, + oldHook->get().getFieldH256(sfHookOnOutgoing)); + if (oldHook->get().isFieldPresent(sfHookOnIncoming)) + newHook.setFieldH256( + sfHookOnIncoming, + oldHook->get().getFieldH256(sfHookOnIncoming)); if (oldHook->get().isFieldPresent(sfHookCanEmit)) newHook.setFieldH256( sfHookCanEmit, @@ -1503,6 +1599,32 @@ SetHook::setHook() newHook.setFieldH256(sfHookOn, *newHookOn); } + if (newHookOnOutgoing) + { + if (defHookOnOutgoing.has_value() && + *defHookOnOutgoing == *newHookOnOutgoing) + { + if (newHook.isFieldPresent(sfHookOnOutgoing)) + newHook.makeFieldAbsent(sfHookOnOutgoing); + } + else + newHook.setFieldH256( + sfHookOnOutgoing, *newHookOnOutgoing); + } + + if (newHookOnIncoming) + { + if (defHookOnIncoming.has_value() && + *defHookOnIncoming == *newHookOnIncoming) + { + if (newHook.isFieldPresent(sfHookOnIncoming)) + newHook.makeFieldAbsent(sfHookOnIncoming); + } + else + newHook.setFieldH256( + sfHookOnIncoming, *newHookOnIncoming); + } + // set the hookcanemit field if it differs from definition if (newHookCanEmit) { @@ -1665,7 +1787,19 @@ SetHook::setHook() auto newHookDef = std::make_shared(keylet); newHookDef->setFieldH256(sfHookHash, *createHookHash); - newHookDef->setFieldH256(sfHookOn, *newHookOn); + + // only HookOn or (HookOnOutgoing and HookOnIncoming) + if (!view().rules().enabled(featureHookOnV2) || + (!newHookOnOutgoing && !newHookOnIncoming)) + newHookDef->setFieldH256(sfHookOn, *newHookOn); + else + { + newHookDef->setFieldH256( + sfHookOnOutgoing, *newHookOnOutgoing); + newHookDef->setFieldH256( + sfHookOnIncoming, *newHookOnIncoming); + } + if (newHookCanEmit) newHookDef->setFieldH256( sfHookCanEmit, *newHookCanEmit); @@ -1761,7 +1895,7 @@ SetHook::setHook() // change which definition we're using to the new target defNamespace = newDefSLE->getFieldH256(sfHookNamespace); - defHookOn = newDefSLE->getFieldH256(sfHookOn); + if (newDefSLE->isFieldPresent(sfHookCanEmit)) defHookCanEmit = newDefSLE->getFieldH256(sfHookCanEmit); @@ -1769,9 +1903,46 @@ SetHook::setHook() if (newNamespace && *defNamespace != *newNamespace) newHook.setFieldH256(sfHookNamespace, *newNamespace); + if (newDefSLE->isFieldPresent(sfHookOn)) + defHookOn = newDefSLE->getFieldH256(sfHookOn); + if (newDefSLE->isFieldPresent(sfHookOnIncoming)) + defHookOnIncoming = + newDefSLE->getFieldH256(sfHookOnIncoming); + if (newDefSLE->isFieldPresent(sfHookOnOutgoing)) + defHookOnOutgoing = + newDefSLE->getFieldH256(sfHookOnOutgoing); + // set the hookon field if it differs from definition - if (newHookOn && *defHookOn != *newHookOn) - newHook.setFieldH256(sfHookOn, *newHookOn); + if (newHookOn) + { + auto const diffFromDef = defHookOn != *newHookOn; + auto const hasIncOutgDef = defHookOnIncoming.has_value() && + defHookOnOutgoing.has_value() && + (*defHookOnIncoming != *defHookOnOutgoing || + *newHookOn != *defHookOnIncoming); + if (diffFromDef || hasIncOutgDef) + { + newHook.setFieldH256(sfHookOn, *newHookOn); + } + } + + // set the incoming/outgoing hookon field if it differs from + // definition + if (newHookOnIncoming && newHookOnOutgoing) + { + auto const diffFromDef = + defHookOnIncoming != newHookOnIncoming || + defHookOnOutgoing != newHookOnOutgoing; + auto const hasHookOnDef = defHookOn != newHookOnIncoming || + defHookOn != newHookOnOutgoing; + if (diffFromDef || hasHookOnDef) + { + newHook.setFieldH256( + sfHookOnIncoming, *newHookOnIncoming); + newHook.setFieldH256( + sfHookOnOutgoing, *newHookOnOutgoing); + } + } // set the hookcanemit field if it differs from definition if (newHookCanEmit && @@ -1827,8 +1998,8 @@ SetHook::setHook() // sfHook: 1 reserve PER non-blank entry // sfParameters: 1 reserve PER entry // sfGrants are: 1 reserve PER entry - // sfHookHash, sfHookNamespace, sfHookOn, sfHookCanEmit, - // sfHookApiVersion, sfFlags: free + // sfHookHash, sfHookNamespace, sfHookOn, sfHookOnOutgoing, + // sfHookOnIncoming, sfHookCanEmit sfHookApiVersion, sfFlags: free // sfHookDefinition is not reserved because it is an unowned object, // rather the uploader is billed via fee according to the following: @@ -1839,28 +2010,14 @@ SetHook::setHook() int oldHookReserve = 0; int newHookReserve = 0; - auto const computeHookReserve = [](STObject const& hookObj) -> int { - if (!hookObj.isFieldPresent(sfHookHash)) - return 0; - - int reserve{1}; - - if (hookObj.isFieldPresent(sfHookParameters)) - reserve += hookObj.getFieldArray(sfHookParameters).size(); - - if (hookObj.isFieldPresent(sfHookGrants)) - reserve += hookObj.getFieldArray(sfHookGrants).size(); - - return reserve; - }; - for (int i = 0; i < hook::maxHookChainLength(); ++i) { if (oldHooks && i < oldHookCount) - oldHookReserve += computeHookReserve(((*oldHooks).get())[i]); + oldHookReserve += + SetHook::computeHookReserve(((*oldHooks).get())[i]); if (i < newHooks.size()) - newHookReserve += computeHookReserve(newHooks[i]); + newHookReserve += SetHook::computeHookReserve(newHooks[i]); } reserveDelta = newHookReserve - oldHookReserve; @@ -1990,6 +2147,6 @@ SetHook::setHook() } return nsDeleteResult; -} +} // namespace ripple } // namespace ripple diff --git a/src/xrpld/app/tx/detail/SetHook.h b/src/xrpld/app/tx/detail/SetHook.h index 979968e29..8aa53b54c 100644 --- a/src/xrpld/app/tx/detail/SetHook.h +++ b/src/xrpld/app/tx/detail/SetHook.h @@ -91,6 +91,9 @@ public: static HookSetValidation validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj); + static uint32_t + computeHookReserve(STObject const& hookObj); + private: TER setHook(); diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 0e8818fa0..2ed792fed 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -241,6 +241,7 @@ Transactor::calculateHookChainFee( ReadView const& view, STTx const& tx, Keylet const& hookKeylet, + bool isOutgoing, bool collectCallsOnly) { std::shared_ptr hookSLE = view.read(hookKeylet); @@ -258,7 +259,7 @@ Transactor::calculateHookChainFee( uint256 const& hash = hookObj.getFieldH256(sfHookHash); - std::shared_ptr hookDef = + std::shared_ptr const& hookDef = view.read(keylet::hookDefinition(hash)); // this is an edge case that happens when a hook is deleted and executed @@ -269,18 +270,16 @@ Transactor::calculateHookChainFee( continue; } - // check if the hook can fire - uint256 hookOn = - (hookObj.isFieldPresent(sfHookOn) - ? hookObj.getFieldH256(sfHookOn) - : hookDef->getFieldH256(sfHookOn)); - uint32_t flags = 0; if (hookObj.isFieldPresent(sfFlags)) flags = hookObj.getFieldU32(sfFlags); else flags = hookDef->getFieldU32(sfFlags); + // check if the hook can fire + uint256 hookOn = hook::getHookOn( + hookObj, hookDef, isOutgoing ? sfHookOnOutgoing : sfHookOnIncoming); + if (hook::canHook(tx.getTxnType(), hookOn) && (!collectCallsOnly || (flags & hook::hsfCOLLECT))) { @@ -366,7 +365,7 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx) } else hookExecutionFee += calculateHookChainFee( - view, tx, keylet::hook(tx.getAccountID(sfAccount))); + view, tx, keylet::hook(tx.getAccountID(sfAccount)), true); // find any additional stakeholders whose hooks will be executed and // charged to this transaction @@ -375,8 +374,8 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx) for (auto& [tshAcc, canRollback] : tsh) if (canRollback) - hookExecutionFee += - calculateHookChainFee(view, tx, keylet::hook(tshAcc)); + hookExecutionFee += calculateHookChainFee( + view, tx, keylet::hook(tshAcc), false); } XRPAmount accumulator = baseFee; @@ -1290,6 +1289,7 @@ Transactor::executeHookChain( std::vector& results, ripple::AccountID const& account, bool strong, + bool isOutgoing, std::shared_ptr const& provisionalMeta) { std::set hookSkips; @@ -1324,10 +1324,8 @@ Transactor::executeHookChain( } // check if the hook can fire - uint256 hookOn = - (hookObj.isFieldPresent(sfHookOn) - ? hookObj.getFieldH256(sfHookOn) - : hookDef->getFieldH256(sfHookOn)); + uint256 hookOn = hook::getHookOn( + hookObj, hookDef, isOutgoing ? sfHookOnOutgoing : sfHookOnIncoming); if (!hook::canHook(ctx_.tx.getTxnType(), hookOn)) continue; // skip if it can't @@ -1346,8 +1344,8 @@ Transactor::executeHookChain( if (!strong && !(flags & hsfCOLLECT)) continue; - // fetch the namespace either from the hook object of, if absent, the - // hook def + // fetch the namespace either from the hook object of, if absent, + // the hook def uint256 const& ns = (hookObj.isFieldPresent(sfHookNamespace) ? hookObj.getFieldH256(sfHookNamespace) @@ -1689,8 +1687,8 @@ Transactor::doTSH( continue; // compute and deduct fees for the TSH if applicable - XRPAmount tshFeeDrops = - calculateHookChainFee(view, ctx_.tx, klTshHook, !canRollback); + XRPAmount tshFeeDrops = calculateHookChainFee( + view, ctx_.tx, klTshHook, false, !canRollback); // no hooks to execute, skip tsh if (tshFeeDrops == 0) @@ -1755,7 +1753,13 @@ Transactor::doTSH( // execution to here means we can run the TSH's hook chain TER tshResult = executeHookChain( - tshHook, stateMap, results, tshAccountID, strong, provisionalMeta); + tshHook, + stateMap, + results, + tshAccountID, + strong, + false, + provisionalMeta); if (canRollback && (!isTesSuccess(tshResult))) return tshResult; @@ -1948,7 +1952,13 @@ Transactor::operator()() if (hooksOriginator && hooksOriginator->isFieldPresent(sfHooks) && !ctx_.isEmittedTxn()) result = executeHookChain( - hooksOriginator, stateMap, hookResults, accountID, true, {}); + hooksOriginator, + stateMap, + hookResults, + accountID, + true, + true, + {}); if (isTesSuccess(result)) { diff --git a/src/xrpld/app/tx/detail/Transactor.h b/src/xrpld/app/tx/detail/Transactor.h index e2f4891aa..3f0386d24 100644 --- a/src/xrpld/app/tx/detail/Transactor.h +++ b/src/xrpld/app/tx/detail/Transactor.h @@ -179,6 +179,7 @@ public: ReadView const& view, STTx const& tx, Keylet const& hookKeylet, + bool isOutgoing, bool collectCallsOnly = false); protected: @@ -211,6 +212,7 @@ protected: std::vector& results, ripple::AccountID const& account, bool strong, + bool isOutgoing, std::shared_ptr const& provisionalMeta); void