mirror of
https://github.com/Xahau/xahaud.git
synced 2026-10-11 22:38:02 +00:00
Compare commits
87 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a713203075 | ||
|
|
c6f0c33202 | ||
|
|
8a04cc7307 | ||
|
|
00ef8d2052 | ||
|
|
a636eb1851 | ||
|
|
64e3bb4cab | ||
|
|
efe96fc9c1 | ||
|
|
b1766938ff | ||
|
|
e9055c914d | ||
|
|
e5d7e673ea | ||
|
|
e77576819b | ||
|
|
2e2b5eb00a | ||
|
|
715ccb3af3 | ||
|
|
542c9a2c5a | ||
|
|
d97ed746b0 | ||
|
|
02f097ace4 | ||
|
|
1ee0609406 | ||
|
|
8f3ca291fd | ||
|
|
045b2ee80f | ||
|
|
1e7a5937a1 | ||
|
|
ff8f79f88c | ||
|
|
931baf3c3f | ||
|
|
b1408d3b6c | ||
|
|
a5094033b4 | ||
|
|
7694602d10 | ||
|
|
f3e9d2b6a7 | ||
|
|
ff7de7207a | ||
|
|
1b3722c012 | ||
|
|
e2f87c0b03 | ||
|
|
6d6f011b5b | ||
|
|
8e7a7f48f9 | ||
|
|
07962562f6 | ||
|
|
50615166b1 | ||
|
|
f14e9236d2 | ||
|
|
2b493ed881 | ||
|
|
b9d0434e96 | ||
|
|
3d5844e0ed | ||
|
|
ad85d170c6 | ||
|
|
32baf652ee | ||
|
|
7bf8cd75e5 | ||
|
|
e910177838 | ||
|
|
d1cd4b22f0 | ||
|
|
277bad787d | ||
|
|
dce17e2fa6 | ||
|
|
e6403f1596 | ||
|
|
ee0b677c2e | ||
|
|
a4a43578de | ||
|
|
b6b57e3ec3 | ||
|
|
41400f1cc6 | ||
|
|
77a1c2d65d | ||
|
|
e4f4b28e0f | ||
|
|
9870e5df67 | ||
|
|
dfc2a8ed83 | ||
|
|
9de7ba734d | ||
|
|
359c3e7990 | ||
|
|
650b2d6e1c | ||
|
|
94c61cc626 | ||
|
|
b7881302e3 | ||
|
|
4e87ccd924 | ||
|
|
df86bebd5f | ||
|
|
670e77d4c0 | ||
|
|
ba54d7fd77 | ||
|
|
78929910ee | ||
|
|
3436c241e2 | ||
|
|
398f6f01d7 | ||
|
|
aeea7a8251 | ||
|
|
33510213d3 | ||
|
|
bb136e616a | ||
|
|
e4ccc443c1 | ||
|
|
0b38795617 | ||
|
|
f3ed814dcf | ||
|
|
7c66f94a90 | ||
|
|
240f33d398 | ||
|
|
a868a46848 | ||
|
|
b86112bca4 | ||
|
|
92832aac10 | ||
|
|
8e7def3739 | ||
|
|
552d1cf1e0 | ||
|
|
f35d8591d8 | ||
|
|
124235d92e | ||
|
|
56a456a7ff | ||
|
|
70814197f5 | ||
|
|
d5334234fc | ||
|
|
d406e336a4 | ||
|
|
4c58f4223c | ||
|
|
2d48d21cc6 | ||
|
|
7dd63aaac9 |
@@ -1,5 +1,8 @@
|
||||
codecov:
|
||||
require_ci_to_pass: true
|
||||
# This integration branch uploads the normal suite and JSHookz E2E coverage.
|
||||
notify:
|
||||
after_n_builds: 2
|
||||
|
||||
comment:
|
||||
behavior: default
|
||||
|
||||
6
.gitattributes
vendored
6
.gitattributes
vendored
@@ -11,3 +11,9 @@ LICENSE binary
|
||||
*.vcxproj text eol=crlf
|
||||
*.props text eol=crlf
|
||||
*.filters text eol=crlf
|
||||
|
||||
# JSHookz projections checked for freshness by the integration workflow.
|
||||
external/quickjs-provider/jshookz_provider.manifest.cmake linguist-generated=true
|
||||
external/quickjs-provider/jshookz_provider.manifest.json linguist-generated=true
|
||||
external/quickjs-provider/jshookz_provider.native-abi.json linguist-generated=true
|
||||
src/test/app/JSHooks_test_hooks.h linguist-generated=true
|
||||
|
||||
@@ -145,6 +145,9 @@ runs:
|
||||
# Export wasmedge if not already exported
|
||||
conan list wasmedge/0.11.2@xahaud/stable 2>/dev/null | (grep -q "not found" && exit 1 || exit 0) || \
|
||||
conan export external/wasmedge --version 0.11.2 --user xahaud --channel stable
|
||||
|
||||
# Keep the cached recipe in sync with this checkout; package binaries remain cached.
|
||||
conan export external/wasmtime --version 47.0.3 --user xahaud --channel stable
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
env:
|
||||
@@ -159,4 +162,6 @@ runs:
|
||||
--output-folder . \
|
||||
--build missing \
|
||||
--settings build_type=${{ inputs.configuration }} \
|
||||
--options '&:xrpld=True' \
|
||||
--options '&:tests=True' \
|
||||
..
|
||||
|
||||
5
.github/workflows/build-in-docker.yml
vendored
5
.github/workflows/build-in-docker.yml
vendored
@@ -2,9 +2,9 @@ name: Build using Docker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["dev", "candidate", "release", "jshooks"]
|
||||
branches: ["dev", "candidate", "release", "jshookz"]
|
||||
pull_request:
|
||||
branches: ["dev", "candidate", "release", "jshooks"]
|
||||
branches: ["dev", "candidate", "release"]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -33,6 +33,7 @@ jobs:
|
||||
fetch-depth: 2 # Only get the last 2 commits, to avoid fetching all history
|
||||
|
||||
build:
|
||||
if: github.head_ref != 'jshookz'
|
||||
runs-on: [self-hosted, xahaud-build]
|
||||
needs: [checkout]
|
||||
defaults:
|
||||
|
||||
3
.github/workflows/check-genesis-hooks.yml
vendored
3
.github/workflows/check-genesis-hooks.yml
vendored
@@ -82,6 +82,9 @@ jobs:
|
||||
command -v clang-format || (echo "Error: clang-format not found" && exit 1)
|
||||
echo "All tools verified successfully"
|
||||
|
||||
- name: Verify independent Wasmtime host catalogue fixture
|
||||
run: src/test/app/jshooks/raw-catalogue/generate.py --check
|
||||
|
||||
# Execute build script to regenerate xahau.h
|
||||
- name: Run build_xahau_h.sh
|
||||
run: |
|
||||
|
||||
153
.github/workflows/jshookz-e2e.yml
vendored
Normal file
153
.github/workflows/jshookz-e2e.yml
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
# Consumer proof for the sealed JSHookz provider release and real Xahau Env.
|
||||
name: JSHookz provider consumer
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [jshookz]
|
||||
# PRs targeting jshookz/dev currently duplicate the branch-push e2e
|
||||
# (different github.ref, so concurrency does not cancel). Re-enable later.
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: jshookz-e2e-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
XAHAUD_SCRIPTS_REF: e41a712c68a613d95a8911cbf89711db2d95aa3b
|
||||
XAHAU_QJS_PROVIDER_WASM: ${{ github.workspace }}/external/quickjs-provider/jshookz_provider.wasm
|
||||
XAHAU_REQUIRE_QJS_PROVIDER_TESTS: "1"
|
||||
CC: gcc-13
|
||||
CXX: g++-13
|
||||
CCACHE_DIR: /github/home/.ccache-cache
|
||||
CMAKE_BUILD_PARALLEL_LEVEL: "2"
|
||||
|
||||
jobs:
|
||||
env-proof:
|
||||
runs-on: [self-hosted, generic, 20.04]
|
||||
timeout-minutes: 120
|
||||
container:
|
||||
image: ubuntu:24.04
|
||||
volumes:
|
||||
- /home/runner/.conan-cache:/.conan-cache
|
||||
- /home/runner/.ccache-cache:/github/home/.ccache-cache
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- name: Install checkout prerequisite
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y git
|
||||
|
||||
- name: Check out Xahau integration
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Check out public xahaud-scripts
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: sublimator/xahaud-scripts
|
||||
ref: ${{ env.XAHAUD_SCRIPTS_REF }}
|
||||
path: .deps/xahaud-scripts
|
||||
persist-credentials: false
|
||||
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
|
||||
- name: Install native tools
|
||||
run: |
|
||||
apt-get install -y \
|
||||
build-essential ccache clang-format-18 cmake curl g++-13 gcc-13 \
|
||||
libsqlite3-dev ninja-build pkg-config python3 xz-utils
|
||||
ln -sf /usr/bin/clang-format-18 /usr/local/bin/clang-format
|
||||
ln -sf /usr/bin/gcov-13 /usr/local/bin/gcov
|
||||
uv python install 3.13
|
||||
uv tool install --python 3.13 conan==2.21.0
|
||||
uv tool install --python 3.13 'gcovr>=7,<9'
|
||||
|
||||
- name: Install the Xahau test runner
|
||||
run: |
|
||||
uv tool install --python 3.13 --force \
|
||||
--from .deps/xahaud-scripts xahaud-scripts
|
||||
x-run-tests --help >/dev/null
|
||||
|
||||
- name: Fetch and verify the sealed provider release
|
||||
run: bin/fetch-jshookz-provider
|
||||
|
||||
- name: Prove receipt mutation reds
|
||||
run: |
|
||||
cmake \
|
||||
-DQUICKJS_PROVIDER_RECEIPT=external/quickjs-provider/jshookz_provider.receipt \
|
||||
-DQUICKJS_RECEIPT_TEST_TMP="$RUNNER_TEMP/quickjs-receipt-tests" \
|
||||
-P cmake/TestQuickJSProviderReceipt.cmake
|
||||
|
||||
- name: Configure Conan for Xahau
|
||||
run: |
|
||||
mkdir -p \
|
||||
/.conan-cache/conan2 \
|
||||
/.conan-cache/conan2_download \
|
||||
/.conan-cache/conan2_sources
|
||||
mkdir -p "$HOME/.conan2"
|
||||
printf '%s\n' \
|
||||
'core.cache:storage_path=/.conan-cache/conan2' \
|
||||
'core.download:download_cache=/.conan-cache/conan2_download' \
|
||||
'core.sources:download_cache=/.conan-cache/conan2_sources' \
|
||||
> "$HOME/.conan2/global.conf"
|
||||
mkdir -p "$HOME/.conan2/profiles"
|
||||
cat > "$HOME/.conan2/profiles/default" <<'EOF'
|
||||
[settings]
|
||||
arch=x86_64
|
||||
build_type=Debug
|
||||
compiler=gcc
|
||||
compiler.cppstd=20
|
||||
compiler.libcxx=libstdc++11
|
||||
compiler.version=13
|
||||
os=Linux
|
||||
|
||||
[buildenv]
|
||||
CC=/usr/bin/gcc-13
|
||||
CXX=/usr/bin/g++-13
|
||||
|
||||
[conf]
|
||||
tools.build:compiler_executables={"c": "/usr/bin/gcc-13", "cpp": "/usr/bin/g++-13"}
|
||||
|
||||
[options]
|
||||
&:xrpld=True
|
||||
&:tests=True
|
||||
date/*:use_system_tz_db=True
|
||||
EOF
|
||||
conan remote add --force xrplf https://conan.ripplex.io
|
||||
conan profile show
|
||||
conan export external/snappy --version 1.1.10 \
|
||||
--user xahaud --channel stable
|
||||
conan export external/soci --version 4.0.3 \
|
||||
--user xahaud --channel stable
|
||||
conan export external/wasmedge --version 0.11.2 \
|
||||
--user xahaud --channel stable
|
||||
conan export external/wasmtime --version 47.0.3 \
|
||||
--user xahaud --channel stable
|
||||
|
||||
- name: Build Xahau and run the authoritative Env suite
|
||||
run: |
|
||||
x-run-tests \
|
||||
--build-type Coverage \
|
||||
--build-dir build-jshookz-e2e \
|
||||
--reconfigure-build \
|
||||
--ccache \
|
||||
-j 2 \
|
||||
-- \
|
||||
ripple.app.JSHooks,ripple.app.QuickJSHostPolicy,ripple.app.HookGuestMemory,ripple.app.HookAPI,ripple.app.HookWasmtimeHostCatalogue,ripple.app.HookArtifact
|
||||
|
||||
- name: Upload JSHookz coverage
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
files: build-jshookz-e2e/coverage/coverage.json
|
||||
fail_ci_if_error: true
|
||||
disable_search: true
|
||||
verbose: true
|
||||
plugins: noop
|
||||
use_oidc: true
|
||||
2
BUILD.md
2
BUILD.md
@@ -35,7 +35,7 @@ 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/)
|
||||
- [Python 3.7](https://www.python.org/downloads/) (configure also uses it to project the QuickJS provider)
|
||||
- [Conan 2.x](https://conan.io/downloads)
|
||||
- [CMake 3.16](https://cmake.org/download/)
|
||||
|
||||
|
||||
137
CMakeLists.txt
137
CMakeLists.txt
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required (VERSION 3.16)
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
@@ -18,41 +18,52 @@ if(POLICY CMP0144)
|
||||
cmake_policy(SET CMP0144 NEW)
|
||||
endif()
|
||||
|
||||
project (xrpl)
|
||||
project(xrpl)
|
||||
set(Boost_NO_BOOST_CMAKE ON)
|
||||
|
||||
# make GIT_COMMIT_HASH define available to all sources
|
||||
find_package(Git)
|
||||
if(Git_FOUND)
|
||||
execute_process(COMMAND ${GIT_EXECUTABLE} --git-dir=${CMAKE_CURRENT_SOURCE_DIR}/.git rev-parse HEAD
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE gch)
|
||||
if(gch)
|
||||
set(GIT_COMMIT_HASH "${gch}")
|
||||
message(STATUS gch: ${GIT_COMMIT_HASH})
|
||||
add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}")
|
||||
endif()
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} --git-dir=${CMAKE_CURRENT_SOURCE_DIR}/.git
|
||||
rev-parse HEAD
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
OUTPUT_VARIABLE gch)
|
||||
if(gch)
|
||||
set(GIT_COMMIT_HASH "${gch}")
|
||||
message(STATUS gch: ${GIT_COMMIT_HASH})
|
||||
add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}")
|
||||
endif()
|
||||
|
||||
execute_process(COMMAND ${GIT_EXECUTABLE} --git-dir=${CMAKE_CURRENT_SOURCE_DIR}/.git rev-parse --abbrev-ref HEAD
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE gb)
|
||||
if(gb)
|
||||
set(GIT_BRANCH "${gb}")
|
||||
message(STATUS gb: ${GIT_BRANCH})
|
||||
add_definitions(-DGIT_BRANCH="${GIT_BRANCH}")
|
||||
endif()
|
||||
endif() #git
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} --git-dir=${CMAKE_CURRENT_SOURCE_DIR}/.git
|
||||
rev-parse --abbrev-ref HEAD
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
OUTPUT_VARIABLE gb)
|
||||
if(gb)
|
||||
set(GIT_BRANCH "${gb}")
|
||||
message(STATUS gb: ${GIT_BRANCH})
|
||||
add_definitions(-DGIT_BRANCH="${GIT_BRANCH}")
|
||||
endif()
|
||||
endif() # git
|
||||
|
||||
# make SOURCE_ROOT_PATH define available for logging
|
||||
set(SOURCE_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/src/")
|
||||
add_definitions(-DSOURCE_ROOT_PATH="${SOURCE_ROOT_PATH}")
|
||||
|
||||
# BEAST_ENHANCED_LOGGING - adds file:line numbers and formatting to logs
|
||||
# Automatically enabled for Debug builds via generator expression
|
||||
# Can be explicitly controlled with -DBEAST_ENHANCED_LOGGING=ON/OFF
|
||||
option(BEAST_ENHANCED_LOGGING "Include file and line numbers in log messages (auto: Debug=ON, Release=OFF)" OFF)
|
||||
# Automatically enabled for Debug builds via generator expression Can be
|
||||
# explicitly controlled with -DBEAST_ENHANCED_LOGGING=ON/OFF
|
||||
option(
|
||||
BEAST_ENHANCED_LOGGING
|
||||
"Include file and line numbers in log messages (auto: Debug=ON, Release=OFF)"
|
||||
OFF)
|
||||
message(STATUS "BEAST_ENHANCED_LOGGING option: ${BEAST_ENHANCED_LOGGING}")
|
||||
|
||||
if(thread_safety_analysis)
|
||||
add_compile_options(-Wthread-safety -D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS -DRIPPLE_ENABLE_THREAD_SAFETY_ANNOTATIONS)
|
||||
add_compile_options(
|
||||
-Wthread-safety -D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS
|
||||
-DRIPPLE_ENABLE_THREAD_SAFETY_ANNOTATIONS)
|
||||
add_compile_options("-stdlib=libc++")
|
||||
add_link_options("-stdlib=libc++")
|
||||
endif()
|
||||
@@ -62,40 +73,48 @@ option(USE_CONAN "Use Conan package manager for dependencies" OFF)
|
||||
if(CMAKE_TOOLCHAIN_FILE)
|
||||
# Check if the toolchain file path contains "conan_toolchain"
|
||||
if(CMAKE_TOOLCHAIN_FILE MATCHES "conan_toolchain")
|
||||
set(USE_CONAN ON CACHE BOOL "Using Conan detected from toolchain file" FORCE)
|
||||
set(USE_CONAN
|
||||
ON
|
||||
CACHE BOOL "Using Conan detected from toolchain file" FORCE)
|
||||
message(STATUS "Conan toolchain detected: ${CMAKE_TOOLCHAIN_FILE}")
|
||||
message(STATUS "Building with Conan dependencies")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include (CheckCXXCompilerFlag)
|
||||
include (FetchContent)
|
||||
include (ExternalProject)
|
||||
include (CMakeFuncs) # must come *after* ExternalProject b/c it overrides one function in EP
|
||||
if (target)
|
||||
message (FATAL_ERROR "The target option has been removed - use native cmake options to control build")
|
||||
endif ()
|
||||
include(CheckCXXCompilerFlag)
|
||||
include(FetchContent)
|
||||
include(ExternalProject)
|
||||
include(CMakeFuncs) # must come *after* ExternalProject b/c it overrides one
|
||||
# function in EP
|
||||
if(target)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"The target option has been removed - use native cmake options to control build"
|
||||
)
|
||||
endif()
|
||||
|
||||
include(RippledSanity)
|
||||
include(RippledVersion)
|
||||
include(RippledSettings)
|
||||
|
||||
# this check has to remain in the top-level cmake
|
||||
# because of the early return statement
|
||||
if (packages_only)
|
||||
if (NOT TARGET rpm)
|
||||
message (FATAL_ERROR "packages_only requested, but targets were not created - is docker installed?")
|
||||
# this check has to remain in the top-level cmake because of the early return
|
||||
# statement
|
||||
if(packages_only)
|
||||
if(NOT TARGET rpm)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"packages_only requested, but targets were not created - is docker installed?"
|
||||
)
|
||||
endif()
|
||||
return ()
|
||||
endif ()
|
||||
return()
|
||||
endif()
|
||||
include(RippledCompiler)
|
||||
include(RippledInterface)
|
||||
|
||||
include(deps/Boost)
|
||||
find_package(OpenSSL 1.1.1 REQUIRED)
|
||||
set_target_properties(OpenSSL::SSL PROPERTIES
|
||||
INTERFACE_COMPILE_DEFINITIONS OPENSSL_NO_SSL2
|
||||
)
|
||||
set_target_properties(OpenSSL::SSL PROPERTIES INTERFACE_COMPILE_DEFINITIONS
|
||||
OPENSSL_NO_SSL2)
|
||||
set(SECP256K1_INSTALL TRUE)
|
||||
add_subdirectory(external/secp256k1)
|
||||
add_library(secp256k1::secp256k1 ALIAS secp256k1)
|
||||
@@ -103,9 +122,9 @@ add_subdirectory(external/ed25519-donna)
|
||||
add_subdirectory(external/antithesis-sdk)
|
||||
find_package(gRPC REQUIRED)
|
||||
find_package(lz4 REQUIRED)
|
||||
# Target names with :: are not allowed in a generator expression.
|
||||
# We need to pull the include directories and imported location properties
|
||||
# from separate targets.
|
||||
# Target names with :: are not allowed in a generator expression. We need to
|
||||
# pull the include directories and imported location properties from separate
|
||||
# targets.
|
||||
find_package(LibArchive REQUIRED)
|
||||
find_package(SOCI REQUIRED)
|
||||
find_package(SQLite3 REQUIRED)
|
||||
@@ -113,9 +132,9 @@ find_package(SQLite3 REQUIRED)
|
||||
option(rocksdb "Enable RocksDB" ON)
|
||||
if(rocksdb)
|
||||
find_package(RocksDB REQUIRED)
|
||||
set_target_properties(RocksDB::rocksdb PROPERTIES
|
||||
INTERFACE_COMPILE_DEFINITIONS RIPPLE_ROCKSDB_AVAILABLE=1
|
||||
)
|
||||
set_target_properties(
|
||||
RocksDB::rocksdb PROPERTIES INTERFACE_COMPILE_DEFINITIONS
|
||||
RIPPLE_ROCKSDB_AVAILABLE=1)
|
||||
target_link_libraries(ripple_libs INTERFACE RocksDB::rocksdb)
|
||||
endif()
|
||||
|
||||
@@ -124,8 +143,12 @@ find_package(date REQUIRED)
|
||||
find_package(xxHash REQUIRED)
|
||||
find_package(magic_enum REQUIRED)
|
||||
|
||||
include(deps/WasmEdge)
|
||||
if(TARGET nudb::core)
|
||||
include(deps/WasmEdge)
|
||||
if(xrpld)
|
||||
include(deps/Wasmtime)
|
||||
include(QuickJSProvider)
|
||||
endif()
|
||||
if(TARGET nudb::core)
|
||||
set(nudb nudb::core)
|
||||
elseif(TARGET NuDB::nudb)
|
||||
set(nudb NuDB::nudb)
|
||||
@@ -134,16 +157,16 @@ else()
|
||||
endif()
|
||||
target_link_libraries(ripple_libs INTERFACE ${nudb})
|
||||
|
||||
target_link_libraries(ripple_libs INTERFACE
|
||||
ed25519::ed25519
|
||||
LibArchive::LibArchive
|
||||
lz4::lz4
|
||||
OpenSSL::Crypto
|
||||
OpenSSL::SSL
|
||||
secp256k1::secp256k1
|
||||
soci::soci
|
||||
SQLite::SQLite3
|
||||
)
|
||||
target_link_libraries(
|
||||
ripple_libs
|
||||
INTERFACE ed25519::ed25519
|
||||
LibArchive::LibArchive
|
||||
lz4::lz4
|
||||
OpenSSL::Crypto
|
||||
OpenSSL::SSL
|
||||
secp256k1::secp256k1
|
||||
soci::soci
|
||||
SQLite::SQLite3)
|
||||
|
||||
if(coverage)
|
||||
include(RippledCov)
|
||||
|
||||
61
bin/fetch-jshookz-provider
Executable file
61
bin/fetch-jshookz-provider
Executable file
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
bundle="${root}/external/quickjs-provider"
|
||||
receipt="${bundle}/jshookz_provider.receipt"
|
||||
provider="${bundle}/jshookz_provider.wasm"
|
||||
|
||||
sha256_file()
|
||||
{
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
sha256sum "$1" | awk '{print $1}'
|
||||
else
|
||||
shasum -a 256 "$1" | awk '{print $1}'
|
||||
fi
|
||||
}
|
||||
|
||||
receipt_value()
|
||||
{
|
||||
awk -v key="$1" '
|
||||
$1 == key {
|
||||
if (found || NF != 2) exit 2
|
||||
value = $2
|
||||
found = 1
|
||||
}
|
||||
END {
|
||||
if (!found) exit 1
|
||||
print value
|
||||
}
|
||||
' "${receipt}"
|
||||
}
|
||||
|
||||
provider_sha=$(receipt_value provider_sha256)
|
||||
provider_size=$(receipt_value provider_size)
|
||||
receipt_sha=$(sha256_file "${receipt}")
|
||||
provider_url="${XAHAU_QJS_PROVIDER_URL:-https://github.com/sublimator/jshookz/releases/download/provider-${receipt_sha}/jshookz_provider.wasm}"
|
||||
|
||||
verify_provider()
|
||||
{
|
||||
actual_sha=$(sha256_file "$1")
|
||||
actual_size=$(wc -c <"$1" | tr -d '[:space:]')
|
||||
if [[ "${actual_sha}" != "${provider_sha}" ||
|
||||
"${actual_size}" != "${provider_size}" ]]; then
|
||||
echo "provider receipt mismatch: sha256=${actual_sha} size=${actual_size}" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ -f "${provider}" ]] && verify_provider "${provider}"; then
|
||||
echo "Using locked JSHookz provider: ${provider}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
temporary_provider="${provider}.tmp"
|
||||
trap 'rm -f "${temporary_provider}"' EXIT
|
||||
curl --fail --location --retry 3 --proto '=https' --tlsv1.2 \
|
||||
"${provider_url}" --output "${temporary_provider}"
|
||||
verify_provider "${temporary_provider}"
|
||||
mv "${temporary_provider}" "${provider}"
|
||||
trap - EXIT
|
||||
92
bin/generate-jshooks-bypass-fixture
Executable file
92
bin/generate-jshooks-bypass-fixture
Executable file
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate the unchecked QuickJS bytecode fixture used by JSHooks_test."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
|
||||
from jshookz.host import WasmHost
|
||||
from jshookz.xfl_profile import XFLArithmeticProfile
|
||||
|
||||
|
||||
def compile_once(source: str, wasm: Path) -> bytes:
|
||||
host = WasmHost(wasm_path=wasm)
|
||||
host.init()
|
||||
try:
|
||||
bytecode = host.compile_source(source, module=True)
|
||||
validation = host.validate_hook_bytecode(bytecode)
|
||||
finally:
|
||||
host.destroy()
|
||||
if not validation.valid:
|
||||
raise RuntimeError(
|
||||
"unchecked fixture failed provider validation: "
|
||||
f"{validation.error or 'unknown error'}"
|
||||
)
|
||||
if validation.profile is not XFLArithmeticProfile.NEAREST_EVEN_V1:
|
||||
raise RuntimeError(
|
||||
"unchecked fixture selected profile "
|
||||
f"{validation.profile.value}, expected nearestEvenV1"
|
||||
)
|
||||
return bytecode
|
||||
|
||||
|
||||
def render(source: Path, wasm: Path, bytecode: bytes) -> str:
|
||||
source_sha = hashlib.sha256(source.read_bytes()).hexdigest()
|
||||
provider_sha = hashlib.sha256(wasm.read_bytes()).hexdigest()
|
||||
bytecode_sha = hashlib.sha256(bytecode).hexdigest()
|
||||
rows = []
|
||||
for offset in range(0, len(bytecode), 12):
|
||||
chunk = bytecode[offset : offset + 12]
|
||||
rows.append(" " + ", ".join(f"0x{value:02x}U" for value in chunk) + ",")
|
||||
return f"""// Generated by bin/generate-jshooks-bypass-fixture; do not edit.
|
||||
// clang-format off
|
||||
// Source SHA-256: {source_sha}
|
||||
// Provider SHA-256: {provider_sha}
|
||||
// Bytecode SHA-256: {bytecode_sha}
|
||||
#ifndef JSHOOKS_BYPASS_TEST_HOOK_INCLUDED
|
||||
#define JSHOOKS_BYPASS_TEST_HOOK_INCLUDED
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ripple::test {{
|
||||
|
||||
inline constexpr char jshooksBypassSourceSHA256[] = "{source_sha}";
|
||||
inline constexpr char jshooksBypassProviderSHA256[] = "{provider_sha}";
|
||||
inline constexpr char jshooksBypassBytecodeSHA256[] = "{bytecode_sha}";
|
||||
inline constexpr std::array<std::uint8_t, {len(bytecode)}>
|
||||
jshooksNearestEvenBypassBytecode = {{{{
|
||||
{chr(10).join(rows)}
|
||||
}}}};
|
||||
// clang-format on
|
||||
|
||||
}} // namespace ripple::test
|
||||
|
||||
#endif
|
||||
"""
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--source", required=True, type=Path)
|
||||
parser.add_argument("--wasm", required=True, type=Path)
|
||||
parser.add_argument("--output", required=True, type=Path)
|
||||
args = parser.parse_args()
|
||||
|
||||
source = args.source.resolve()
|
||||
wasm = args.wasm.resolve()
|
||||
first = compile_once(source.read_text(), wasm)
|
||||
second = compile_once(source.read_text(), wasm)
|
||||
if first != second:
|
||||
raise RuntimeError("unchecked fixture compilation is not deterministic")
|
||||
args.output.write_text(render(source, wasm, first))
|
||||
print(
|
||||
f"wrote {args.output}: {len(first)} bytes, "
|
||||
f"sha256 {hashlib.sha256(first).hexdigest()}"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
133
bin/update-jshookz-snapshot
Executable file
133
bin/update-jshookz-snapshot
Executable file
@@ -0,0 +1,133 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build and import one committed jshookz provider snapshot into Xahau."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def run(*args: str, cwd: Path, capture: bool = False) -> str:
|
||||
completed = subprocess.run(
|
||||
args,
|
||||
cwd=cwd,
|
||||
check=True,
|
||||
text=True,
|
||||
stdout=subprocess.PIPE if capture else None,
|
||||
)
|
||||
return completed.stdout.strip() if capture else ""
|
||||
|
||||
|
||||
def read_receipt(path: Path) -> dict[str, str]:
|
||||
values: dict[str, str] = {}
|
||||
for line in path.read_text().splitlines():
|
||||
key, value = line.split(" ")
|
||||
values[key] = value
|
||||
return values
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description=(
|
||||
"Build a committed jshookz checkout and export its sealed "
|
||||
"consumer bundle into Xahau. Consumers derive the immutable "
|
||||
"release URL from the imported receipt SHA-256."
|
||||
)
|
||||
)
|
||||
parser.add_argument("jshookz_checkout", type=Path)
|
||||
parser.add_argument(
|
||||
"--no-build",
|
||||
action="store_true",
|
||||
help="export an already-built provider without rebuilding it",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
xahau_root = Path(__file__).resolve().parents[1]
|
||||
jshookz_root = args.jshookz_checkout.expanduser().resolve()
|
||||
cli = next(
|
||||
(
|
||||
candidate
|
||||
for candidate in (
|
||||
jshookz_root / "python/jshookz/.venv/bin/jshookz",
|
||||
jshookz_root / "packages/jshookz/.venv/bin/jshookz",
|
||||
)
|
||||
if candidate.is_file()
|
||||
),
|
||||
None,
|
||||
)
|
||||
if cli is None:
|
||||
parser.error(
|
||||
"missing jshookz CLI under python/jshookz/.venv or packages/jshookz/.venv"
|
||||
)
|
||||
bundle = jshookz_root / "build/xahau-provider-bundle"
|
||||
destination = xahau_root / "external/quickjs-provider"
|
||||
|
||||
dirty = run(
|
||||
"git",
|
||||
"status",
|
||||
"--porcelain",
|
||||
"--untracked-files=no",
|
||||
cwd=jshookz_root,
|
||||
capture=True,
|
||||
)
|
||||
if dirty:
|
||||
parser.error(
|
||||
"jshookz checkout has uncommitted tracked changes; commit them "
|
||||
"before creating a pinned snapshot"
|
||||
)
|
||||
|
||||
commit = run("git", "rev-parse", "HEAD", cwd=jshookz_root, capture=True)
|
||||
if not args.no_build:
|
||||
run(str(cli), "build", "provider", cwd=jshookz_root)
|
||||
run(
|
||||
str(cli),
|
||||
"export-bundle",
|
||||
"provider",
|
||||
"-o",
|
||||
str(destination),
|
||||
cwd=jshookz_root,
|
||||
)
|
||||
receipt = read_receipt(destination / "jshookz_provider.receipt")
|
||||
wasi_sdk = os.environ.get("HOOKZ_WASI_SDK")
|
||||
if not wasi_sdk:
|
||||
try:
|
||||
wasi_sdk = str(
|
||||
Path(
|
||||
run(
|
||||
"mise",
|
||||
"where",
|
||||
"wasi-sdk",
|
||||
cwd=jshookz_root,
|
||||
capture=True,
|
||||
)
|
||||
)
|
||||
/ "wasi-sdk"
|
||||
)
|
||||
except (FileNotFoundError, subprocess.CalledProcessError):
|
||||
wasi_sdk = "/path/to/wasi-sdk"
|
||||
|
||||
print(f"jshookz commit: {commit}")
|
||||
print(f"provider sha256: {receipt['provider_sha256']}")
|
||||
print(f"bytecode ABI: {receipt['bytecode_abi_id']}")
|
||||
print(f"runtime profile: {receipt['runtime_profile_id']}")
|
||||
print("snapshot imported; regenerate fixtures and run the focused suite with:")
|
||||
print(
|
||||
" env "
|
||||
f"JSHOOKZ_HOOK_COMPILER={shlex.quote(f'{cli} compile-hook')} "
|
||||
f"HOOKZ_WASI_SDK={shlex.quote(wasi_sdk)} "
|
||||
f"XAHAU_QJS_PROVIDER_WASM={shlex.quote(str(bundle / 'jshookz_provider.wasm'))} "
|
||||
"XAHAU_REQUIRE_QJS_PROVIDER_TESTS=1 "
|
||||
"XAHAU_UPDATE_QJS_FUEL_SNAPSHOT=1 "
|
||||
"x-run-tests --compile-hooks src/test/app/JSHooks_test.cpp "
|
||||
"--build-dir build --ccache -- "
|
||||
"ripple.app.JSHooks,ripple.app.QuickJSHostPolicy,"
|
||||
"ripple.app.HookGuestMemory,ripple.app.HookAPI"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -52,8 +52,10 @@ export CMAKE_STATIC_LINKER_FLAGS="-static-libstdc++"
|
||||
git config --global --add safe.directory /io &&
|
||||
git checkout src/libxrpl/protocol/BuildInfo.cpp &&
|
||||
sed -i s/\"0.0.0\"/\"$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)$(if [ -n "$4" ]; then echo "+$4"; fi)\"/g src/libxrpl/protocol/BuildInfo.cpp &&
|
||||
./bin/fetch-jshookz-provider &&
|
||||
conan export external/snappy --version 1.1.10 --user xahaud --channel stable &&
|
||||
conan export external/soci --version 4.0.3 --user xahaud --channel stable &&
|
||||
conan export external/wasmtime --version 47.0.3 --user xahaud --channel stable &&
|
||||
cd release-build &&
|
||||
# Install dependencies - tool_requires in conanfile.py handles glibc 2.28 compatibility
|
||||
# for build tools (protoc, grpc plugins, b2) in HBB environment
|
||||
@@ -61,7 +63,8 @@ cd release-build &&
|
||||
# with the correct toolchain, avoiding the GLIBCXX_3.4.29 issue
|
||||
echo "=== Installing dependencies ===" &&
|
||||
conan install .. --output-folder . --build missing --settings build_type=$BUILD_TYPE \
|
||||
-o with_wasmedge=False -o tool_requires_b2=True &&
|
||||
-o '&:xrpld=True' -o '&:tests=True' \
|
||||
-o '&:with_wasmedge=False' -o '&:tool_requires_b2=True' &&
|
||||
cmake .. -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=$BUILD_TYPE \
|
||||
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \
|
||||
|
||||
125
cmake/EmbedQuickJSProvider.cmake
Normal file
125
cmake/EmbedQuickJSProvider.cmake
Normal file
@@ -0,0 +1,125 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
foreach(_required
|
||||
INPUT
|
||||
VALUES_INPUT
|
||||
EXPECTED_SHA256
|
||||
EXPECTED_SIZE
|
||||
EXPECTED_VALUES_SHA256
|
||||
OUTPUT)
|
||||
if(NOT DEFINED ${_required} OR "${${_required}}" STREQUAL "")
|
||||
message(FATAL_ERROR "EmbedQuickJSProvider.cmake requires ${_required}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if(NOT EXISTS "${VALUES_INPUT}")
|
||||
message(FATAL_ERROR "Missing preprojected QuickJS values: ${VALUES_INPUT}")
|
||||
endif()
|
||||
file(SHA256 "${VALUES_INPUT}" _actual_values_sha256)
|
||||
if(NOT _actual_values_sha256 STREQUAL EXPECTED_VALUES_SHA256)
|
||||
message(FATAL_ERROR
|
||||
"Preprojected QuickJS values do not match the sealed receipt")
|
||||
endif()
|
||||
|
||||
file(SHA256 "${CMAKE_CURRENT_LIST_FILE}" _script_sha256)
|
||||
if(EXISTS "${INPUT}")
|
||||
file(SHA256 "${INPUT}" _actual_sha256)
|
||||
file(SIZE "${INPUT}" _actual_size)
|
||||
if(NOT _actual_sha256 STREQUAL EXPECTED_SHA256 OR
|
||||
NOT "${_actual_size}" STREQUAL "${EXPECTED_SIZE}")
|
||||
message(FATAL_ERROR
|
||||
"QuickJS provider WASM does not match the sealed receipt: "
|
||||
"sha256=${_actual_sha256} size=${_actual_size}")
|
||||
endif()
|
||||
set(_input_state "${_actual_sha256}:${_actual_size}")
|
||||
else()
|
||||
set(_input_state absent)
|
||||
endif()
|
||||
|
||||
set(_stamp "${_script_sha256}:${_actual_values_sha256}:${_input_state}")
|
||||
set(_stamp_file "${OUTPUT}.stamp")
|
||||
if(EXISTS "${OUTPUT}" AND EXISTS "${_stamp_file}")
|
||||
file(READ "${_stamp_file}" _old_stamp)
|
||||
if(_old_stamp STREQUAL _stamp)
|
||||
return()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
get_filename_component(_output_dir "${OUTPUT}" DIRECTORY)
|
||||
file(MAKE_DIRECTORY "${_output_dir}")
|
||||
set(_temporary "${OUTPUT}.tmp")
|
||||
file(WRITE "${_temporary}" [=[// Generated by cmake/EmbedQuickJSProvider.cmake; do not edit.
|
||||
#include <xrpld/app/hook/QuickJSHookRuntime.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
|
||||
namespace hook {
|
||||
|
||||
]=])
|
||||
|
||||
if(EXISTS "${INPUT}")
|
||||
file(APPEND "${_temporary}" "namespace {\n\nconstexpr char const sealedProvider[] =\n")
|
||||
|
||||
# Read 16 KiB at a time, formatting it as adjacent 64-byte C++ string
|
||||
# literals. This avoids holding the 5 MiB escaped representation in CMake.
|
||||
set(_byte_offset 0)
|
||||
while(_byte_offset LESS _actual_size)
|
||||
math(EXPR _bytes_left "${_actual_size} - ${_byte_offset}")
|
||||
if(_bytes_left GREATER 16384)
|
||||
set(_bytes_to_read 16384)
|
||||
else()
|
||||
set(_bytes_to_read "${_bytes_left}")
|
||||
endif()
|
||||
file(READ "${INPUT}" _hex
|
||||
OFFSET "${_byte_offset}" LIMIT "${_bytes_to_read}" HEX)
|
||||
string(LENGTH "${_hex}" _hex_length)
|
||||
set(_hex_offset 0)
|
||||
set(_block "")
|
||||
while(_hex_offset LESS _hex_length)
|
||||
math(EXPR _hex_left "${_hex_length} - ${_hex_offset}")
|
||||
if(_hex_left GREATER 128)
|
||||
set(_line_length 128)
|
||||
else()
|
||||
set(_line_length "${_hex_left}")
|
||||
endif()
|
||||
string(SUBSTRING "${_hex}" "${_hex_offset}" "${_line_length}" _line)
|
||||
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "\\\\x\\1" _line "${_line}")
|
||||
string(APPEND _block " \"${_line}\"\n")
|
||||
math(EXPR _hex_offset "${_hex_offset} + ${_line_length}")
|
||||
endwhile()
|
||||
file(APPEND "${_temporary}" "${_block}")
|
||||
math(EXPR _byte_offset "${_byte_offset} + ${_bytes_to_read}")
|
||||
endwhile()
|
||||
|
||||
file(APPEND "${_temporary}" [=[;
|
||||
|
||||
static_assert(
|
||||
sizeof(sealedProvider) - 1 == ]=] "${_actual_size}" [=[,
|
||||
"embedded provider size disagrees with its receipt");
|
||||
|
||||
} // namespace
|
||||
|
||||
std::span<std::uint8_t const>
|
||||
embeddedQuickJSProvider()
|
||||
{
|
||||
return {
|
||||
reinterpret_cast<std::uint8_t const*>(sealedProvider),
|
||||
sizeof(sealedProvider) - 1};
|
||||
}
|
||||
]=])
|
||||
else()
|
||||
message(WARNING
|
||||
"No QuickJS provider WASM at ${INPUT}; the daemon will refuse to start")
|
||||
file(APPEND "${_temporary}" [=[std::span<std::uint8_t const>
|
||||
embeddedQuickJSProvider()
|
||||
{
|
||||
return {};
|
||||
}
|
||||
]=])
|
||||
endif()
|
||||
|
||||
file(APPEND "${_temporary}" "\n} // namespace hook\n")
|
||||
file(RENAME "${_temporary}" "${OUTPUT}")
|
||||
file(WRITE "${_stamp_file}.tmp" "${_stamp}")
|
||||
file(RENAME "${_stamp_file}.tmp" "${_stamp_file}")
|
||||
176
cmake/QuickJSProvider.cmake
Normal file
176
cmake/QuickJSProvider.cmake
Normal file
@@ -0,0 +1,176 @@
|
||||
set(XAHAU_QUICKJS_PROVIDER_BUNDLE_DIR
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/external/quickjs-provider"
|
||||
CACHE PATH
|
||||
"Directory containing the sealed QuickJS provider receipt and artifacts")
|
||||
get_filename_component(XAHAU_QUICKJS_PROVIDER_BUNDLE_DIR
|
||||
"${XAHAU_QUICKJS_PROVIDER_BUNDLE_DIR}"
|
||||
ABSOLUTE
|
||||
BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
set(XAHAU_QUICKJS_PROVIDER_RECEIPT
|
||||
"${XAHAU_QUICKJS_PROVIDER_BUNDLE_DIR}/jshookz_provider.receipt")
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/QuickJSProviderReceipt.cmake")
|
||||
xahau_read_quickjs_provider_receipt(
|
||||
"${XAHAU_QUICKJS_PROVIDER_RECEIPT}"
|
||||
XAHAU_QUICKJS_RECEIPT)
|
||||
|
||||
if(NOT XAHAU_QUICKJS_RECEIPT_schema STREQUAL
|
||||
"jshookz.provider-receipt.v1")
|
||||
message(FATAL_ERROR
|
||||
"Unsupported QuickJS provider receipt schema: "
|
||||
"${XAHAU_QUICKJS_RECEIPT_schema}")
|
||||
endif()
|
||||
if(NOT XAHAU_QUICKJS_RECEIPT_product STREQUAL "provider")
|
||||
message(FATAL_ERROR
|
||||
"QuickJS provider receipt names unsupported product: "
|
||||
"${XAHAU_QUICKJS_RECEIPT_product}")
|
||||
endif()
|
||||
|
||||
foreach(_file_key
|
||||
api_artifacts_file
|
||||
manifest_file
|
||||
native_abi_file
|
||||
provider_file
|
||||
values_file)
|
||||
set(_file_name "${XAHAU_QUICKJS_RECEIPT_${_file_key}}")
|
||||
get_filename_component(_file_basename "${_file_name}" NAME)
|
||||
if(NOT _file_name STREQUAL _file_basename OR
|
||||
NOT _file_name MATCHES "^[A-Za-z0-9_.-]+$")
|
||||
message(FATAL_ERROR
|
||||
"QuickJS provider receipt ${_file_key} is not a safe basename")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
foreach(_sha_key
|
||||
api_artifacts_sha256
|
||||
broad_declaration_sha256
|
||||
bytecode_abi_id
|
||||
exact_v1_declaration_sha256
|
||||
manifest_sha256
|
||||
native_abi_sha256
|
||||
provider_sha256
|
||||
runtime_profile_id
|
||||
selected_surface_sha256
|
||||
values_sha256
|
||||
xfl_profile_ledger_sha256)
|
||||
set(_sha "${XAHAU_QUICKJS_RECEIPT_${_sha_key}}")
|
||||
string(LENGTH "${_sha}" _sha_length)
|
||||
if(NOT _sha_length EQUAL 64 OR NOT _sha MATCHES "^[0-9a-f]+$")
|
||||
message(FATAL_ERROR
|
||||
"QuickJS provider receipt ${_sha_key} is not a SHA-256 digest")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
foreach(_integer_key
|
||||
heap_bytes
|
||||
hook_api_version
|
||||
host_work_base_per_call
|
||||
host_work_budget
|
||||
host_work_per_addressed_byte
|
||||
initialization_fuel
|
||||
invocation_fuel
|
||||
provider_export_count
|
||||
provider_import_count
|
||||
provider_memory_maximum_pages
|
||||
provider_memory_minimum_pages
|
||||
provider_size
|
||||
serialized_object_max_bytes
|
||||
serialized_object_max_depth
|
||||
serialized_object_max_fields
|
||||
serialized_object_max_scopes
|
||||
stack_bytes
|
||||
wasm_stack_bytes)
|
||||
if(NOT XAHAU_QUICKJS_RECEIPT_${_integer_key} MATCHES "^[0-9]+$")
|
||||
message(FATAL_ERROR
|
||||
"QuickJS provider receipt ${_integer_key} is not an unsigned integer")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if(NOT DEFINED wasmtime_VERSION_STRING OR wasmtime_VERSION_STRING STREQUAL "")
|
||||
message(FATAL_ERROR
|
||||
"QuickJS provider requires an exact Wasmtime version, but the resolved "
|
||||
"package did not publish one")
|
||||
endif()
|
||||
if(NOT wasmtime_VERSION_STRING STREQUAL
|
||||
XAHAU_QUICKJS_RECEIPT_wasmtime_version)
|
||||
message(FATAL_ERROR
|
||||
"QuickJS provider requires Wasmtime "
|
||||
"${XAHAU_QUICKJS_RECEIPT_wasmtime_version}, but the build resolved "
|
||||
"${wasmtime_VERSION_STRING}")
|
||||
endif()
|
||||
|
||||
set(XAHAU_QUICKJS_PROVIDER_WASM
|
||||
"${XAHAU_QUICKJS_PROVIDER_BUNDLE_DIR}/${XAHAU_QUICKJS_RECEIPT_provider_file}")
|
||||
set(XAHAU_QUICKJS_PROVIDER_VALUES_SOURCE
|
||||
"${XAHAU_QUICKJS_PROVIDER_BUNDLE_DIR}/${XAHAU_QUICKJS_RECEIPT_values_file}")
|
||||
if(NOT EXISTS "${XAHAU_QUICKJS_PROVIDER_VALUES_SOURCE}")
|
||||
message(FATAL_ERROR
|
||||
"Missing preprojected QuickJS provider values: "
|
||||
"${XAHAU_QUICKJS_PROVIDER_VALUES_SOURCE}")
|
||||
endif()
|
||||
file(SHA256 "${XAHAU_QUICKJS_PROVIDER_VALUES_SOURCE}"
|
||||
_quickjs_values_sha256)
|
||||
if(NOT _quickjs_values_sha256 STREQUAL
|
||||
XAHAU_QUICKJS_RECEIPT_values_sha256)
|
||||
message(FATAL_ERROR
|
||||
"Preprojected QuickJS provider values do not match the sealed receipt")
|
||||
endif()
|
||||
|
||||
if(EXISTS "${XAHAU_QUICKJS_PROVIDER_WASM}")
|
||||
file(SHA256 "${XAHAU_QUICKJS_PROVIDER_WASM}" _quickjs_wasm_sha256)
|
||||
file(SIZE "${XAHAU_QUICKJS_PROVIDER_WASM}" _quickjs_wasm_size)
|
||||
if(NOT _quickjs_wasm_sha256 STREQUAL
|
||||
XAHAU_QUICKJS_RECEIPT_provider_sha256 OR
|
||||
NOT "${_quickjs_wasm_size}" STREQUAL
|
||||
"${XAHAU_QUICKJS_RECEIPT_provider_size}")
|
||||
message(FATAL_ERROR
|
||||
"QuickJS provider WASM does not match the sealed receipt")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
|
||||
"${XAHAU_QUICKJS_PROVIDER_RECEIPT}"
|
||||
"${XAHAU_QUICKJS_PROVIDER_VALUES_SOURCE}")
|
||||
|
||||
set(XAHAU_QUICKJS_GENERATED_INCLUDE_DIR
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/generated")
|
||||
set(XAHAU_QUICKJS_PROVIDER_EMBED_SOURCE
|
||||
"${XAHAU_QUICKJS_GENERATED_INCLUDE_DIR}/xrpld/app/hook/detail/QuickJSProviderEmbed.cpp")
|
||||
set(XAHAU_QUICKJS_EMBED_SCRIPT
|
||||
"${CMAKE_CURRENT_LIST_DIR}/EmbedQuickJSProvider.cmake")
|
||||
set_source_files_properties(
|
||||
"${XAHAU_QUICKJS_PROVIDER_EMBED_SOURCE}" PROPERTIES GENERATED TRUE)
|
||||
|
||||
# The gitignored wasm cannot be a Ninja dependency when it is absent. This
|
||||
# target therefore runs every rippled build; the CMake script verifies both
|
||||
# pinned artifacts and no-ops when its stamp is current.
|
||||
add_custom_target(xahau_quickjs_refresh_provider
|
||||
COMMAND "${CMAKE_COMMAND}"
|
||||
"-DINPUT=${XAHAU_QUICKJS_PROVIDER_WASM}"
|
||||
"-DVALUES_INPUT=${XAHAU_QUICKJS_PROVIDER_VALUES_SOURCE}"
|
||||
"-DEXPECTED_SHA256=${XAHAU_QUICKJS_RECEIPT_provider_sha256}"
|
||||
"-DEXPECTED_SIZE=${XAHAU_QUICKJS_RECEIPT_provider_size}"
|
||||
"-DEXPECTED_VALUES_SHA256=${XAHAU_QUICKJS_RECEIPT_values_sha256}"
|
||||
"-DOUTPUT=${XAHAU_QUICKJS_PROVIDER_EMBED_SOURCE}"
|
||||
-P "${XAHAU_QUICKJS_EMBED_SCRIPT}"
|
||||
BYPRODUCTS "${XAHAU_QUICKJS_PROVIDER_EMBED_SOURCE}"
|
||||
DEPENDS
|
||||
"${XAHAU_QUICKJS_PROVIDER_RECEIPT}"
|
||||
"${XAHAU_QUICKJS_PROVIDER_VALUES_SOURCE}"
|
||||
"${XAHAU_QUICKJS_EMBED_SCRIPT}"
|
||||
COMMENT "Verifying and embedding the sealed QuickJS provider"
|
||||
VERBATIM)
|
||||
|
||||
message(STATUS
|
||||
"QuickJS provider receipt: ${XAHAU_QUICKJS_PROVIDER_RECEIPT}")
|
||||
message(STATUS
|
||||
"QuickJS provider values: ${XAHAU_QUICKJS_PROVIDER_VALUES_SOURCE}")
|
||||
|
||||
add_custom_target(xahau_quickjs_receipt_mutations
|
||||
COMMAND "${CMAKE_COMMAND}"
|
||||
"-DQUICKJS_PROVIDER_RECEIPT=${XAHAU_QUICKJS_PROVIDER_RECEIPT}"
|
||||
"-DQUICKJS_RECEIPT_TEST_TMP=${CMAKE_CURRENT_BINARY_DIR}/quickjs-receipt-tests"
|
||||
-P "${CMAKE_CURRENT_LIST_DIR}/TestQuickJSProviderReceipt.cmake"
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
COMMENT "Proving sealed QuickJS receipt mutation reds"
|
||||
VERBATIM)
|
||||
87
cmake/QuickJSProviderReceipt.cmake
Normal file
87
cmake/QuickJSProviderReceipt.cmake
Normal file
@@ -0,0 +1,87 @@
|
||||
# Parse the producer's non-executable, CMake-3.16-compatible receipt.
|
||||
|
||||
function(xahau_read_quickjs_provider_receipt receipt prefix)
|
||||
if(NOT EXISTS "${receipt}")
|
||||
message(FATAL_ERROR "Missing sealed QuickJS provider receipt: ${receipt}")
|
||||
endif()
|
||||
|
||||
file(READ "${receipt}" _receipt_text)
|
||||
if(_receipt_text STREQUAL "" OR NOT "${_receipt_text}" MATCHES "\n$")
|
||||
message(FATAL_ERROR
|
||||
"QuickJS provider receipt must be non-empty and newline-terminated")
|
||||
endif()
|
||||
foreach(_forbidden "\r" "\t" ";" "#" "\n\n")
|
||||
string(FIND "${_receipt_text}" "${_forbidden}" _forbidden_at)
|
||||
if(NOT _forbidden_at EQUAL -1)
|
||||
message(FATAL_ERROR
|
||||
"QuickJS provider receipt contains forbidden whitespace or syntax")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
set(_expected_keys
|
||||
api_artifacts_file
|
||||
api_artifacts_sha256
|
||||
broad_declaration_sha256
|
||||
bytecode_abi_id
|
||||
exact_v1_declaration_sha256
|
||||
heap_bytes
|
||||
hook_api_version
|
||||
host_adapter_policy
|
||||
host_work_base_per_call
|
||||
host_work_budget
|
||||
host_work_meter
|
||||
host_work_per_addressed_byte
|
||||
initialization_fuel
|
||||
invocation_fuel
|
||||
manifest_file
|
||||
manifest_schema
|
||||
manifest_sha256
|
||||
native_abi_file
|
||||
native_abi_sha256
|
||||
product
|
||||
provider_export_count
|
||||
provider_file
|
||||
provider_import_count
|
||||
provider_memory_maximum_pages
|
||||
provider_memory_minimum_pages
|
||||
provider_sha256
|
||||
provider_size
|
||||
runtime_profile_id
|
||||
schema
|
||||
selected_surface_sha256
|
||||
serialized_object_max_bytes
|
||||
serialized_object_max_depth
|
||||
serialized_object_max_fields
|
||||
serialized_object_max_scopes
|
||||
stack_bytes
|
||||
values_file
|
||||
values_sha256
|
||||
wasm_stack_bytes
|
||||
wasmtime_version
|
||||
xfl_profile_ledger_sha256)
|
||||
|
||||
file(STRINGS "${receipt}" _receipt_lines ENCODING UTF-8)
|
||||
set(_actual_keys)
|
||||
foreach(_line IN LISTS _receipt_lines)
|
||||
if(NOT "${_line}" MATCHES "^([a-z0-9_]+) ([!-~]+)$")
|
||||
message(FATAL_ERROR
|
||||
"QuickJS provider receipt line is not exactly 'key value': ${_line}")
|
||||
endif()
|
||||
set(_key "${CMAKE_MATCH_1}")
|
||||
set(_value "${CMAKE_MATCH_2}")
|
||||
if(DEFINED _receipt_value_${_key})
|
||||
message(FATAL_ERROR "QuickJS provider receipt repeats key: ${_key}")
|
||||
endif()
|
||||
set(_receipt_value_${_key} "${_value}")
|
||||
list(APPEND _actual_keys "${_key}")
|
||||
endforeach()
|
||||
|
||||
if(NOT "${_actual_keys}" STREQUAL "${_expected_keys}")
|
||||
message(FATAL_ERROR
|
||||
"QuickJS provider receipt keys are missing, extra, or not sorted")
|
||||
endif()
|
||||
|
||||
foreach(_key IN LISTS _expected_keys)
|
||||
set("${prefix}_${_key}" "${_receipt_value_${_key}}" PARENT_SCOPE)
|
||||
endforeach()
|
||||
endfunction()
|
||||
@@ -174,18 +174,108 @@ if(xrpld)
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/xrpld/*.cpp"
|
||||
)
|
||||
target_sources(rippled PRIVATE ${sources})
|
||||
add_dependencies(rippled xahau_quickjs_refresh_provider)
|
||||
target_sources(rippled PRIVATE
|
||||
"${XAHAU_QUICKJS_PROVIDER_VALUES_SOURCE}"
|
||||
"${XAHAU_QUICKJS_PROVIDER_EMBED_SOURCE}")
|
||||
|
||||
if(tests)
|
||||
file(GLOB_RECURSE sources CONFIGURE_DEPENDS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/test/*.cpp"
|
||||
)
|
||||
if(HOOKS_TEST_ONLY OR DEFINED ENV{HOOKS_TEST_ONLY})
|
||||
list(FILTER sources EXCLUDE REGEX "_test\\.cpp$")
|
||||
message(STATUS "HOOKS_TEST_ONLY: excluded *_test.cpp from src/test/")
|
||||
endif()
|
||||
target_sources(rippled PRIVATE ${sources})
|
||||
|
||||
# External Env tests live next to the producer. Point at a directory of
|
||||
# *_test.cpp files (typically jshookz/env-tests). hookz build-test-hooks
|
||||
# must be on PATH. Coverage is not wired here.
|
||||
if(NOT HOOKS_TEST_DIR AND DEFINED ENV{HOOKS_TEST_DIR})
|
||||
set(HOOKS_TEST_DIR $ENV{HOOKS_TEST_DIR})
|
||||
endif()
|
||||
if(NOT HOOKS_SOURCE_DIR AND DEFINED ENV{HOOKS_SOURCE_DIR})
|
||||
set(HOOKS_SOURCE_DIR $ENV{HOOKS_SOURCE_DIR})
|
||||
endif()
|
||||
if(HOOKS_TEST_DIR AND EXISTS "${HOOKS_TEST_DIR}")
|
||||
file(GLOB EXTERNAL_HOOK_TESTS CONFIGURE_DEPENDS
|
||||
"${HOOKS_TEST_DIR}/*_test.cpp"
|
||||
)
|
||||
if(EXTERNAL_HOOK_TESTS)
|
||||
set(_hooks_extra_args "")
|
||||
set(_hooks_source_deps "")
|
||||
if(HOOKS_SOURCE_DIR)
|
||||
foreach(_dir ${HOOKS_SOURCE_DIR})
|
||||
list(APPEND _hooks_extra_args "--hooks-c-dir" "${_dir}")
|
||||
string(REGEX REPLACE "^[^=]+=" "" _hook_dir "${_dir}")
|
||||
if(EXISTS "${_hook_dir}")
|
||||
file(GLOB_RECURSE _hook_dir_deps CONFIGURE_DEPENDS
|
||||
"${_hook_dir}/*.c"
|
||||
"${_hook_dir}/*.h"
|
||||
"${_hook_dir}/*.ts"
|
||||
"${_hook_dir}/*.js"
|
||||
)
|
||||
if(HOOKS_TEST_DIR)
|
||||
list(FILTER _hook_dir_deps EXCLUDE REGEX "^${HOOKS_TEST_DIR}/")
|
||||
endif()
|
||||
list(APPEND _hooks_source_deps ${_hook_dir_deps})
|
||||
endif()
|
||||
endforeach()
|
||||
list(REMOVE_DUPLICATES _hooks_source_deps)
|
||||
endif()
|
||||
set(_hooks_always_run OFF)
|
||||
if(HOOKS_FORCE_RECOMPILE OR DEFINED ENV{HOOKS_FORCE_RECOMPILE})
|
||||
list(APPEND _hooks_extra_args "--force-write" "--no-cache")
|
||||
set(_hooks_always_run ON)
|
||||
message(STATUS "Hook force recompile enabled (cache bypassed)")
|
||||
endif()
|
||||
foreach(_test_file ${EXTERNAL_HOOK_TESTS})
|
||||
get_filename_component(_stem ${_test_file} NAME_WE)
|
||||
set(_hooks_header "${HOOKS_TEST_DIR}/${_stem}_hooks.h")
|
||||
if(_hooks_always_run)
|
||||
add_custom_target(compile_hooks_${_stem} ALL
|
||||
COMMAND hookz build-test-hooks "${_test_file}" ${_hooks_extra_args}
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
COMMENT "Compiling hooks for ${_stem} (forced)"
|
||||
VERBATIM
|
||||
)
|
||||
list(APPEND EXTERNAL_HOOK_TARGETS compile_hooks_${_stem})
|
||||
else()
|
||||
add_custom_command(
|
||||
OUTPUT "${_hooks_header}"
|
||||
COMMAND hookz build-test-hooks "${_test_file}" ${_hooks_extra_args}
|
||||
DEPENDS "${_test_file}" ${_hooks_source_deps}
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
COMMENT "Compiling hooks for ${_stem}"
|
||||
VERBATIM
|
||||
)
|
||||
list(APPEND EXTERNAL_HOOK_HEADERS "${_hooks_header}")
|
||||
endif()
|
||||
endforeach()
|
||||
if(_hooks_always_run)
|
||||
foreach(_tgt ${EXTERNAL_HOOK_TARGETS})
|
||||
add_dependencies(rippled ${_tgt})
|
||||
endforeach()
|
||||
else()
|
||||
add_custom_target(compile_external_hooks DEPENDS ${EXTERNAL_HOOK_HEADERS})
|
||||
add_dependencies(rippled compile_external_hooks)
|
||||
endif()
|
||||
target_sources(rippled PRIVATE ${EXTERNAL_HOOK_TESTS})
|
||||
set_property(
|
||||
SOURCE ${EXTERNAL_HOOK_TESTS}
|
||||
APPEND PROPERTY INCLUDE_DIRECTORIES "${HOOKS_TEST_DIR}"
|
||||
)
|
||||
message(STATUS "Including external hook tests from: ${HOOKS_TEST_DIR}")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
target_link_libraries(rippled
|
||||
Ripple::boost
|
||||
Ripple::opts
|
||||
Ripple::libs
|
||||
wasmtime::wasmtime
|
||||
xrpl.libxrpl
|
||||
# Workaround for a Conan 1.x bug that prevents static linking of libstdc++
|
||||
# when a dependency (snappy) modifies system_libs. See the comment in
|
||||
|
||||
58
cmake/TestQuickJSProviderReceipt.cmake
Normal file
58
cmake/TestQuickJSProviderReceipt.cmake
Normal file
@@ -0,0 +1,58 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
if(NOT DEFINED QUICKJS_PROVIDER_RECEIPT OR
|
||||
NOT DEFINED QUICKJS_RECEIPT_TEST_TMP)
|
||||
message(FATAL_ERROR
|
||||
"Set QUICKJS_PROVIDER_RECEIPT and QUICKJS_RECEIPT_TEST_TMP")
|
||||
endif()
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/QuickJSProviderReceipt.cmake")
|
||||
|
||||
if(DEFINED QUICKJS_RECEIPT_CHILD)
|
||||
xahau_read_quickjs_provider_receipt(
|
||||
"${QUICKJS_PROVIDER_RECEIPT}" TEST_RECEIPT)
|
||||
return()
|
||||
endif()
|
||||
|
||||
xahau_read_quickjs_provider_receipt(
|
||||
"${QUICKJS_PROVIDER_RECEIPT}" TEST_RECEIPT)
|
||||
if(NOT TEST_RECEIPT_schema STREQUAL "jshookz.provider-receipt.v1" OR
|
||||
NOT TEST_RECEIPT_product STREQUAL "provider" OR
|
||||
NOT TEST_RECEIPT_provider_sha256 MATCHES "^[0-9a-f]+$")
|
||||
message(FATAL_ERROR "Current QuickJS provider receipt did not project")
|
||||
endif()
|
||||
|
||||
file(READ "${QUICKJS_PROVIDER_RECEIPT}" _valid)
|
||||
file(MAKE_DIRECTORY "${QUICKJS_RECEIPT_TEST_TMP}")
|
||||
|
||||
string(REGEX REPLACE "\n$" "" _missing_newline "${_valid}")
|
||||
set(_unknown_key "${_valid}unknown_key value\n")
|
||||
string(REPLACE "\n" "\n\n" _blank_line "${_valid}")
|
||||
set(_duplicate_key "${_valid}schema jshookz.provider-receipt.v1\n")
|
||||
set(_bad_space " bad value\n${_valid}")
|
||||
set(_bad_tab "bad_key\tvalue\n${_valid}")
|
||||
|
||||
foreach(_case
|
||||
missing_newline
|
||||
unknown_key
|
||||
blank_line
|
||||
duplicate_key
|
||||
bad_space
|
||||
bad_tab)
|
||||
set(_mutant "${QUICKJS_RECEIPT_TEST_TMP}/${_case}.receipt")
|
||||
file(WRITE "${_mutant}" "${_${_case}}")
|
||||
execute_process(
|
||||
COMMAND "${CMAKE_COMMAND}"
|
||||
-DQUICKJS_PROVIDER_RECEIPT=${_mutant}
|
||||
-DQUICKJS_RECEIPT_TEST_TMP=${QUICKJS_RECEIPT_TEST_TMP}
|
||||
-DQUICKJS_RECEIPT_CHILD=ON
|
||||
-P "${CMAKE_CURRENT_LIST_FILE}"
|
||||
RESULT_VARIABLE _result
|
||||
OUTPUT_QUIET
|
||||
ERROR_QUIET)
|
||||
if(_result EQUAL 0)
|
||||
message(FATAL_ERROR "QuickJS provider receipt mutant passed: ${_case}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
message(STATUS "QuickJS provider receipt mutation checks passed")
|
||||
1
cmake/deps/Wasmtime.cmake
Normal file
1
cmake/deps/Wasmtime.cmake
Normal file
@@ -0,0 +1 @@
|
||||
find_package(wasmtime REQUIRED)
|
||||
258
conanfile.py
258
conanfile.py
@@ -2,158 +2,162 @@ from conan import ConanFile
|
||||
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
|
||||
import re
|
||||
|
||||
class Xrpl(ConanFile):
|
||||
name = 'xrpl'
|
||||
|
||||
license = 'ISC'
|
||||
author = 'John Freeman <jfreeman@ripple.com>'
|
||||
url = 'https://github.com/xrplf/rippled'
|
||||
description = 'The XRP Ledger'
|
||||
settings = 'os', 'compiler', 'build_type', 'arch'
|
||||
class Xrpl(ConanFile):
|
||||
name = "xrpl"
|
||||
|
||||
license = "ISC"
|
||||
author = "John Freeman <jfreeman@ripple.com>"
|
||||
url = "https://github.com/xrplf/rippled"
|
||||
description = "The XRP Ledger"
|
||||
settings = "os", "compiler", "build_type", "arch"
|
||||
options = {
|
||||
'assertions': [True, False],
|
||||
'coverage': [True, False],
|
||||
'fPIC': [True, False],
|
||||
'jemalloc': [True, False],
|
||||
'rocksdb': [True, False],
|
||||
'shared': [True, False],
|
||||
'static': [True, False],
|
||||
'tests': [True, False],
|
||||
'unity': [True, False],
|
||||
'xrpld': [True, False],
|
||||
'with_wasmedge': [True, False],
|
||||
'tool_requires_b2': [True, False],
|
||||
"assertions": [True, False],
|
||||
"coverage": [True, False],
|
||||
"fPIC": [True, False],
|
||||
"jemalloc": [True, False],
|
||||
"rocksdb": [True, False],
|
||||
"shared": [True, False],
|
||||
"static": [True, False],
|
||||
"tests": [True, False],
|
||||
"unity": [True, False],
|
||||
"xrpld": [True, False],
|
||||
"with_wasmedge": [True, False],
|
||||
"tool_requires_b2": [True, False],
|
||||
}
|
||||
|
||||
requires = [
|
||||
'date/3.0.3',
|
||||
'grpc/1.50.1',
|
||||
'libarchive/3.7.6',
|
||||
'magic_enum/0.9.5',
|
||||
'nudb/2.0.8',
|
||||
'openssl/3.6.0',
|
||||
'soci/4.0.3@xahaud/stable',
|
||||
'xxhash/0.8.2',
|
||||
'zlib/1.3.1',
|
||||
"date/3.0.3",
|
||||
"grpc/1.50.1",
|
||||
"libarchive/3.7.6",
|
||||
"magic_enum/0.9.5",
|
||||
"nudb/2.0.8",
|
||||
"openssl/3.6.0",
|
||||
"soci/4.0.3@xahaud/stable",
|
||||
"xxhash/0.8.2",
|
||||
"zlib/1.3.1",
|
||||
]
|
||||
|
||||
tool_requires = [
|
||||
'protobuf/3.21.12',
|
||||
"protobuf/3.21.12",
|
||||
]
|
||||
|
||||
default_options = {
|
||||
'assertions': False,
|
||||
'coverage': False,
|
||||
'fPIC': True,
|
||||
'jemalloc': False,
|
||||
'rocksdb': True,
|
||||
'shared': False,
|
||||
'static': True,
|
||||
'tests': False,
|
||||
'unity': False,
|
||||
'xrpld': False,
|
||||
'with_wasmedge': True,
|
||||
'tool_requires_b2': False,
|
||||
|
||||
'date/*:header_only': False,
|
||||
'grpc/*:shared': False,
|
||||
'grpc/*:secure': True,
|
||||
'libarchive/*:shared': False,
|
||||
'libarchive/*:with_acl': False,
|
||||
'libarchive/*:with_bzip2': False,
|
||||
'libarchive/*:with_cng': False,
|
||||
'libarchive/*:with_expat': False,
|
||||
'libarchive/*:with_iconv': False,
|
||||
'libarchive/*:with_libxml2': False,
|
||||
'libarchive/*:with_lz4': True,
|
||||
'libarchive/*:with_lzma': False,
|
||||
'libarchive/*:with_lzo': False,
|
||||
'libarchive/*:with_nettle': False,
|
||||
'libarchive/*:with_openssl': False,
|
||||
'libarchive/*:with_pcreposix': False,
|
||||
'libarchive/*:with_xattr': False,
|
||||
'libarchive/*:with_zlib': False,
|
||||
'lz4/*:shared': False,
|
||||
'openssl/*:shared': False,
|
||||
'protobuf/*:shared': False,
|
||||
'protobuf/*:with_zlib': True,
|
||||
'rocksdb/*:enable_sse': False,
|
||||
'rocksdb/*:lite': False,
|
||||
'rocksdb/*:shared': False,
|
||||
'rocksdb/*:use_rtti': True,
|
||||
'rocksdb/*:with_jemalloc': False,
|
||||
'rocksdb/*:with_lz4': True,
|
||||
'rocksdb/*:with_snappy': True,
|
||||
'snappy/*:shared': False,
|
||||
'soci/*:shared': False,
|
||||
'soci/*:with_sqlite3': True,
|
||||
'soci/*:with_boost': True,
|
||||
'xxhash/*:shared': False,
|
||||
"assertions": False,
|
||||
"coverage": False,
|
||||
"fPIC": True,
|
||||
"jemalloc": False,
|
||||
"rocksdb": True,
|
||||
"shared": False,
|
||||
"static": True,
|
||||
"tests": False,
|
||||
"unity": False,
|
||||
"xrpld": False,
|
||||
"with_wasmedge": True,
|
||||
"tool_requires_b2": False,
|
||||
"date/*:header_only": False,
|
||||
"grpc/*:shared": False,
|
||||
"grpc/*:secure": True,
|
||||
"libarchive/*:shared": False,
|
||||
"libarchive/*:with_acl": False,
|
||||
"libarchive/*:with_bzip2": False,
|
||||
"libarchive/*:with_cng": False,
|
||||
"libarchive/*:with_expat": False,
|
||||
"libarchive/*:with_iconv": False,
|
||||
"libarchive/*:with_libxml2": False,
|
||||
"libarchive/*:with_lz4": True,
|
||||
"libarchive/*:with_lzma": False,
|
||||
"libarchive/*:with_lzo": False,
|
||||
"libarchive/*:with_nettle": False,
|
||||
"libarchive/*:with_openssl": False,
|
||||
"libarchive/*:with_pcreposix": False,
|
||||
"libarchive/*:with_xattr": False,
|
||||
"libarchive/*:with_zlib": False,
|
||||
"lz4/*:shared": False,
|
||||
"openssl/*:shared": False,
|
||||
"protobuf/*:shared": False,
|
||||
"protobuf/*:with_zlib": True,
|
||||
"rocksdb/*:enable_sse": False,
|
||||
"rocksdb/*:lite": False,
|
||||
"rocksdb/*:shared": False,
|
||||
"rocksdb/*:use_rtti": True,
|
||||
"rocksdb/*:with_jemalloc": False,
|
||||
"rocksdb/*:with_lz4": True,
|
||||
"rocksdb/*:with_snappy": True,
|
||||
"snappy/*:shared": False,
|
||||
"soci/*:shared": False,
|
||||
"soci/*:with_sqlite3": True,
|
||||
"soci/*:with_boost": True,
|
||||
"wasmtime/*:shared": False,
|
||||
"xxhash/*:shared": False,
|
||||
}
|
||||
|
||||
def set_version(self):
|
||||
path = f'{self.recipe_folder}/src/libxrpl/protocol/BuildInfo.cpp'
|
||||
regex = r'versionString\s?=\s?\"(.*)\"'
|
||||
with open(path, 'r') as file:
|
||||
path = f"{self.recipe_folder}/src/libxrpl/protocol/BuildInfo.cpp"
|
||||
regex = r"versionString\s?=\s?\"(.*)\""
|
||||
with open(path, "r") as file:
|
||||
matches = (re.search(regex, line) for line in file)
|
||||
match = next(m for m in matches if m)
|
||||
self.version = match.group(1)
|
||||
|
||||
def build_requirements(self):
|
||||
# These provide build tools (protoc, grpc plugins) that run during build
|
||||
self.tool_requires('grpc/1.50.1')
|
||||
self.tool_requires("grpc/1.50.1")
|
||||
# Explicitly require b2 (e.g. for building from source for glibc compatibility)
|
||||
if self.options.tool_requires_b2:
|
||||
self.tool_requires('b2/5.3.2')
|
||||
self.tool_requires("b2/5.3.2")
|
||||
|
||||
def configure(self):
|
||||
if self.settings.compiler == 'apple-clang':
|
||||
self.options['boost/*'].visibility = 'global'
|
||||
if self.settings.compiler == "apple-clang":
|
||||
self.options["boost/*"].visibility = "global"
|
||||
|
||||
def requirements(self):
|
||||
# Force sqlite3 version to avoid conflicts with soci
|
||||
self.requires('sqlite3/3.47.0', override=True)
|
||||
self.requires("sqlite3/3.47.0", override=True)
|
||||
# Force our custom snappy build for all dependencies
|
||||
self.requires('snappy/1.1.10@xahaud/stable', override=True)
|
||||
self.requires("snappy/1.1.10@xahaud/stable", override=True)
|
||||
# Force boost version for all dependencies to avoid conflicts
|
||||
self.requires('boost/1.86.0', override=True)
|
||||
self.requires('lz4/1.10.0', force=True)
|
||||
self.requires("boost/1.86.0", override=True)
|
||||
self.requires("lz4/1.10.0", force=True)
|
||||
|
||||
if self.options.with_wasmedge:
|
||||
self.requires('wasmedge/0.11.2@xahaud/stable')
|
||||
self.requires("wasmedge/0.11.2@xahaud/stable")
|
||||
if self.options.xrpld:
|
||||
self.requires("wasmtime/47.0.3@xahaud/stable")
|
||||
if self.options.jemalloc:
|
||||
self.requires('jemalloc/5.3.0')
|
||||
self.requires("jemalloc/5.3.0")
|
||||
if self.options.rocksdb:
|
||||
self.requires('rocksdb/6.29.5')
|
||||
self.requires("rocksdb/6.29.5")
|
||||
|
||||
exports_sources = (
|
||||
'CMakeLists.txt',
|
||||
'bin/getRippledInfo',
|
||||
'cfg/*',
|
||||
'cmake/*',
|
||||
'external/*',
|
||||
'include/*',
|
||||
'src/*',
|
||||
"CMakeLists.txt",
|
||||
"bin/getRippledInfo",
|
||||
"cfg/*",
|
||||
"cmake/*",
|
||||
"external/*",
|
||||
"include/*",
|
||||
"src/*",
|
||||
)
|
||||
|
||||
def layout(self):
|
||||
cmake_layout(self)
|
||||
# Fix this setting to follow the default introduced in Conan 1.48
|
||||
# to align with our build instructions.
|
||||
self.folders.generators = 'build/generators'
|
||||
self.folders.generators = "build/generators"
|
||||
|
||||
generators = "CMakeDeps"
|
||||
|
||||
generators = 'CMakeDeps'
|
||||
def generate(self):
|
||||
tc = CMakeToolchain(self)
|
||||
tc.variables['tests'] = self.options.tests
|
||||
tc.variables['assert'] = self.options.assertions
|
||||
tc.variables['coverage'] = self.options.coverage
|
||||
tc.variables['jemalloc'] = self.options.jemalloc
|
||||
tc.variables['rocksdb'] = self.options.rocksdb
|
||||
tc.variables['BUILD_SHARED_LIBS'] = self.options.shared
|
||||
tc.variables['static'] = self.options.static
|
||||
tc.variables['unity'] = self.options.unity
|
||||
tc.variables['xrpld'] = self.options.xrpld
|
||||
tc.variables["tests"] = self.options.tests
|
||||
tc.variables["assert"] = self.options.assertions
|
||||
tc.variables["coverage"] = self.options.coverage
|
||||
tc.variables["jemalloc"] = self.options.jemalloc
|
||||
tc.variables["rocksdb"] = self.options.rocksdb
|
||||
tc.variables["BUILD_SHARED_LIBS"] = self.options.shared
|
||||
tc.variables["static"] = self.options.static
|
||||
tc.variables["unity"] = self.options.unity
|
||||
tc.variables["xrpld"] = self.options.xrpld
|
||||
tc.generate()
|
||||
|
||||
def build(self):
|
||||
@@ -168,29 +172,29 @@ class Xrpl(ConanFile):
|
||||
cmake.install()
|
||||
|
||||
def package_info(self):
|
||||
libxrpl = self.cpp_info.components['libxrpl']
|
||||
libxrpl = self.cpp_info.components["libxrpl"]
|
||||
libxrpl.libs = [
|
||||
'xrpl',
|
||||
'xrpl.libpb',
|
||||
'ed25519',
|
||||
'secp256k1',
|
||||
"xrpl",
|
||||
"xrpl.libpb",
|
||||
"ed25519",
|
||||
"secp256k1",
|
||||
]
|
||||
# TODO: Fix the protobufs to include each other relative to
|
||||
# `include/`, not `include/ripple/proto/`.
|
||||
libxrpl.includedirs = ['include', 'include/ripple/proto']
|
||||
libxrpl.includedirs = ["include", "include/ripple/proto"]
|
||||
libxrpl.requires = [
|
||||
'boost::boost',
|
||||
'date::date',
|
||||
'grpc::grpc++',
|
||||
'libarchive::libarchive',
|
||||
'lz4::lz4',
|
||||
'nudb::nudb',
|
||||
'openssl::crypto',
|
||||
'protobuf::libprotobuf',
|
||||
'soci::soci',
|
||||
'sqlite3::sqlite',
|
||||
'xxhash::xxhash',
|
||||
'zlib::zlib',
|
||||
"boost::boost",
|
||||
"date::date",
|
||||
"grpc::grpc++",
|
||||
"libarchive::libarchive",
|
||||
"lz4::lz4",
|
||||
"nudb::nudb",
|
||||
"openssl::crypto",
|
||||
"protobuf::libprotobuf",
|
||||
"soci::soci",
|
||||
"sqlite3::sqlite",
|
||||
"xxhash::xxhash",
|
||||
"zlib::zlib",
|
||||
]
|
||||
if self.options.rocksdb:
|
||||
libxrpl.requires.append('rocksdb::librocksdb')
|
||||
libxrpl.requires.append("rocksdb::librocksdb")
|
||||
|
||||
@@ -8,4 +8,9 @@ if [[ "$GITHUB_REPOSITORY" == "" ]]; then
|
||||
fi
|
||||
|
||||
echo "Mounting $(pwd)/io in ubuntu and running unit tests"
|
||||
docker run --rm -i -v $(pwd):/io --platform=linux/amd64 -e BUILD_CORES=$BUILD_CORES ubuntu sh -c '/io/release-build/xahaud --unittest-jobs $BUILD_CORES -u'
|
||||
./bin/fetch-jshookz-provider &&
|
||||
docker run --rm -i -v $(pwd):/io --platform=linux/amd64 \
|
||||
-e BUILD_CORES=$BUILD_CORES \
|
||||
-e XAHAU_QJS_PROVIDER_WASM=/io/external/quickjs-provider/jshookz_provider.wasm \
|
||||
-e XAHAU_REQUIRE_QJS_PROVIDER_TESTS=1 \
|
||||
ubuntu sh -c '/io/release-build/xahaud --unittest-jobs $BUILD_CORES -u'
|
||||
|
||||
63
docs/build/jshookz-provider.md
vendored
Normal file
63
docs/build/jshookz-provider.md
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
# QuickJS provider receipt
|
||||
|
||||
The jshookz producer exports the complete consumer bundle with:
|
||||
|
||||
```sh
|
||||
python/jshookz/.venv/bin/jshookz build provider
|
||||
python/jshookz/.venv/bin/jshookz export-bundle provider \
|
||||
-o /path/to/xahaud/external/quickjs-provider
|
||||
```
|
||||
|
||||
From a clean, committed producer checkout, the Xahau wrapper performs both
|
||||
steps and prints the focused runtime test command:
|
||||
|
||||
```sh
|
||||
bin/update-jshookz-snapshot /path/to/jshookz
|
||||
```
|
||||
|
||||
`jshookz_provider.receipt` is the sole consumer acceptance record. It is a
|
||||
strict, sorted `key value` file that pins the provider WASM, the preprojected
|
||||
C++ values, the producer provenance, the runtime profile, the bytecode ABI,
|
||||
and the exact Wasmtime version. It is data, not an executable CMake include.
|
||||
|
||||
The Xahau build consumes exactly three bundle files:
|
||||
|
||||
- `jshookz_provider.receipt`;
|
||||
- `jshookz_provider.values.cpp`, verified by the receipt and compiled as-is;
|
||||
- `jshookz_provider.wasm`, verified by the receipt and embedded by CMake.
|
||||
|
||||
The JSON manifests, declarations, and JavaScript surfaces shipped in the
|
||||
producer bundle remain reviewable provenance. Xahau does not parse or require
|
||||
them during configure or build; their identities are already projected into
|
||||
the receipt and values source by the producer's fail-closed export.
|
||||
|
||||
Provider releases use `provider-<receipt sha256>` as the tag. The receipt pins
|
||||
every other build input, so a metadata or API change behind unchanged WASM
|
||||
bytes still creates a distinct release. `bin/fetch-jshookz-provider` derives
|
||||
that tag from the committed receipt, downloads the gitignored WASM, and checks
|
||||
its pinned digest and size without Python.
|
||||
|
||||
For a local producer-to-consumer build, point Xahau directly at the exported
|
||||
bundle:
|
||||
|
||||
```sh
|
||||
cmake -S . -B build \
|
||||
-DXAHAU_QUICKJS_PROVIDER_BUNDLE_DIR=/path/to/jshookz/build/xahau-provider-bundle
|
||||
```
|
||||
|
||||
If the WASM is absent, configuration remains possible but the generated embed
|
||||
is empty and the daemon refuses to start.
|
||||
|
||||
## Fuel snapshot
|
||||
|
||||
Exact native execution measurements live together in
|
||||
`src/test/app/JSHooks_fuel.snapshot`. They are consumer-side values because
|
||||
they cover the provider, Wasmtime, Xahau's native host, and the compiled test
|
||||
Hooks together; they are not producer release metadata.
|
||||
|
||||
`bin/update-jshookz-snapshot` prints one authoritative `x-run-tests` command
|
||||
with `XAHAU_UPDATE_QJS_FUEL_SNAPSHOT=1`. That run compiles the current Hook
|
||||
fixtures, executes every JSHooks measurement, echoes the complete sorted
|
||||
`key value` snapshot, and rewrites the file only after the suite reaches its
|
||||
end. A normal run reads the snapshot and fails if a named value changes or a
|
||||
row is missing or added.
|
||||
11
external/quickjs-provider/api-artifacts.json
vendored
Normal file
11
external/quickjs-provider/api-artifacts.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"artifacts": {
|
||||
"python/jshookz/src/jshookz/types/hooks-api.d.ts": "0b375f79416e2898dd89d7ba76bec0773005ed1b86eb2fdd26edb93bfc39c845",
|
||||
"python/jshookz/src/jshookz/types/xahau-quickjs-v1-consensus-entropy.d.ts": "882aa6da0e3a8e9cd1e335f324fc6769a2ef2b34d4421dfe24341d213091aa15",
|
||||
"python/jshookz/src/jshookz/types/xahau-quickjs-v1-consensus-entropy.surface.json": "94714f9ea09735fb7d5a0e777034e03a0511019f699deed6adebadfec69fa231",
|
||||
"python/jshookz/src/jshookz/types/xahau-quickjs-v1.d.ts": "6a48d5c9ac889abc13e5abe151e6485a4cc634cb9cc152065b8ac50e384cf959",
|
||||
"python/jshookz/src/jshookz/types/xahau-quickjs-v1.surface.json": "123ccfb489e01b0de335b582a8acb0ae6a4bcb5bd2c12914c2bc632699b32f67",
|
||||
"python/jshookz/src/jshookz/xfl_profile_ledger.ts": "cfcb68fe9a195f6e70c88a1b8f2d2936838b8c98b3d70cbe2cab9a53e056fd80"
|
||||
},
|
||||
"schema": "jshookz.api-artifacts.v1"
|
||||
}
|
||||
3556
external/quickjs-provider/hooks-api.d.ts
vendored
Normal file
3556
external/quickjs-provider/hooks-api.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1270
external/quickjs-provider/jshookz_provider.manifest.json
generated
vendored
Normal file
1270
external/quickjs-provider/jshookz_provider.manifest.json
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
external/quickjs-provider/jshookz_provider.native-abi.json
generated
vendored
Normal file
1
external/quickjs-provider/jshookz_provider.native-abi.json
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
40
external/quickjs-provider/jshookz_provider.receipt
vendored
Normal file
40
external/quickjs-provider/jshookz_provider.receipt
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
api_artifacts_file api-artifacts.json
|
||||
api_artifacts_sha256 3d1595ee0f2c073ef8aec56712e268c324eee4abbdb0f41465cc1875853ed7e3
|
||||
broad_declaration_sha256 0b375f79416e2898dd89d7ba76bec0773005ed1b86eb2fdd26edb93bfc39c845
|
||||
bytecode_abi_id 75ea54f357d397c4b33899e495bb385dad975a43b1c4a7a2cead30474327d33e
|
||||
exact_v1_declaration_sha256 6a48d5c9ac889abc13e5abe151e6485a4cc634cb9cc152065b8ac50e384cf959
|
||||
heap_bytes 16777216
|
||||
hook_api_version 1
|
||||
host_adapter_policy xahau-raw-hook-host-v1
|
||||
host_work_base_per_call 1
|
||||
host_work_budget 2097152
|
||||
host_work_meter base-plus-addressed-byte-v1
|
||||
host_work_per_addressed_byte 1
|
||||
initialization_fuel 5000000
|
||||
invocation_fuel 50000000
|
||||
manifest_file jshookz_provider.manifest.json
|
||||
manifest_schema xahau.quickjs.runtime-profile-lock.v1
|
||||
manifest_sha256 8bb9cf4d25f1b6941475c6073cf53dc8f176ebdbeae10a638216cf9dac684b47
|
||||
native_abi_file jshookz_provider.native-abi.json
|
||||
native_abi_sha256 972a12bac2add4335b4e02097ec2ed7276fbea5b31dd045e9ae04f38a1762157
|
||||
product provider
|
||||
provider_export_count 22
|
||||
provider_file jshookz_provider.wasm
|
||||
provider_import_count 28
|
||||
provider_memory_maximum_pages 512
|
||||
provider_memory_minimum_pages 8
|
||||
provider_sha256 484a4af5ad997008e7ff9039b0b311c8641ea4f9a03d7d92a2c1a1fe24429dfe
|
||||
provider_size 1259233
|
||||
runtime_profile_id 8ed353f184b49e684fc8a4a6b26994a7d4e0d90721fe7f4cc5db2de1122f358c
|
||||
schema jshookz.provider-receipt.v1
|
||||
selected_surface_sha256 123ccfb489e01b0de335b582a8acb0ae6a4bcb5bd2c12914c2bc632699b32f67
|
||||
serialized_object_max_bytes 1048576
|
||||
serialized_object_max_depth 10
|
||||
serialized_object_max_fields 32768
|
||||
serialized_object_max_scopes 32769
|
||||
stack_bytes 65536
|
||||
values_file jshookz_provider.values.cpp
|
||||
values_sha256 65aa47d04422c0804fd226178ad233feaf375ba89fd5dd7684c9592d8c4bc808
|
||||
wasm_stack_bytes 131072
|
||||
wasmtime_version 47.0.3
|
||||
xfl_profile_ledger_sha256 cfcb68fe9a195f6e70c88a1b8f2d2936838b8c98b3d70cbe2cab9a53e056fd80
|
||||
186
external/quickjs-provider/jshookz_provider.values.cpp
vendored
Normal file
186
external/quickjs-provider/jshookz_provider.values.cpp
vendored
Normal file
@@ -0,0 +1,186 @@
|
||||
// Generated by jshookz build; do not edit.
|
||||
// Provider: jshookz_provider.wasm sha256=484a4af5ad997008e7ff9039b0b311c8641ea4f9a03d7d92a2c1a1fe24429dfe size=1259233
|
||||
#include <xrpld/app/hook/QuickJSHookRuntime.h>
|
||||
#include <xrpld/app/hook/detail/QuickJSProviderProfile.h>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
|
||||
namespace hook::artifact::generated {
|
||||
|
||||
std::array<std::uint8_t, 32> const providerSHA256 = {
|
||||
0x48, 0x4a, 0x4a, 0xf5, 0xad, 0x99, 0x70, 0x08, 0xe7, 0xff, 0x90, 0x39, 0xb0, 0xb3, 0x11, 0xc8, 0x64, 0x1e, 0xa4, 0xf9, 0xa0, 0x3d, 0x7d, 0x92, 0xa2, 0xc1, 0xa1, 0xfe, 0x24, 0x42, 0x9d, 0xfe};
|
||||
std::array<std::uint8_t, 32> const bytecodeABI = {
|
||||
0x75, 0xea, 0x54, 0xf3, 0x57, 0xd3, 0x97, 0xc4, 0xb3, 0x38, 0x99, 0xe4, 0x95, 0xbb, 0x38, 0x5d, 0xad, 0x97, 0x5a, 0x43, 0xb1, 0xc4, 0xa7, 0xa2, 0xce, 0xad, 0x30, 0x47, 0x43, 0x27, 0xd3, 0x3e};
|
||||
std::array<std::uint8_t, 32> const runtimeProfile = {
|
||||
0x8e, 0xd3, 0x53, 0xf1, 0x84, 0xb4, 0x9e, 0x68, 0x4f, 0xc8, 0xa4, 0xa6, 0xb2, 0x69, 0x94, 0xa7, 0xd4, 0xe0, 0xd9, 0x07, 0x21, 0xfe, 0x7f, 0x4c, 0xc5, 0xdb, 0x2d, 0xe1, 0x12, 0x2f, 0x35, 0x8c};
|
||||
std::string_view const providerProduct = "provider";
|
||||
std::string_view const providerManifestSHA256 = "8bb9cf4d25f1b6941475c6073cf53dc8f176ebdbeae10a638216cf9dac684b47";
|
||||
std::size_t const providerSize = 1259233;
|
||||
std::uint16_t const hookApiVersion = 1;
|
||||
std::uint64_t const initializationFuel = 5000000ULL;
|
||||
std::uint64_t const invocationFuel = 50000000ULL;
|
||||
std::string_view const hostWorkMeter = "base-plus-addressed-byte-v1";
|
||||
std::uint64_t const hostWorkBudget = 2097152ULL;
|
||||
std::uint64_t const hostWorkBasePerCall = 1ULL;
|
||||
std::uint64_t const hostWorkPerAddressedByte =
|
||||
1ULL;
|
||||
std::string_view const hostAdapterPolicy = "xahau-raw-hook-host-v1";
|
||||
std::uint32_t const heapBytes = 16777216U;
|
||||
std::uint32_t const stackBytes = 65536U;
|
||||
std::uint32_t const wasmStackBytes = 131072U;
|
||||
std::uint32_t const serializedObjectMaxBytes =
|
||||
1048576U;
|
||||
std::uint32_t const serializedObjectMaxFields =
|
||||
32768U;
|
||||
std::uint32_t const serializedObjectMaxScopes =
|
||||
32769U;
|
||||
std::uint32_t const serializedObjectMaxDepth =
|
||||
10U;
|
||||
std::uint32_t const providerMemoryMinimumPages = 8U;
|
||||
std::uint32_t const providerMemoryMaximumPages = 512U;
|
||||
bool const providerMemory64 = false;
|
||||
bool const providerMemoryShared = false;
|
||||
std::string_view const javascriptBroadDeclarationSHA256 =
|
||||
"0b375f79416e2898dd89d7ba76bec0773005ed1b86eb2fdd26edb93bfc39c845";
|
||||
std::string_view const javascriptExactV1DeclarationSHA256 =
|
||||
"6a48d5c9ac889abc13e5abe151e6485a4cc634cb9cc152065b8ac50e384cf959";
|
||||
std::string_view const javascriptSurfaceSHA256 = "123ccfb489e01b0de335b582a8acb0ae6a4bcb5bd2c12914c2bc632699b32f67";
|
||||
std::string_view const javascriptXFLProfileLedgerSHA256 =
|
||||
"cfcb68fe9a195f6e70c88a1b8f2d2936838b8c98b3d70cbe2cab9a53e056fd80";
|
||||
std::string_view const javascriptAPIArtifactManifestSHA256 =
|
||||
"3d1595ee0f2c073ef8aec56712e268c324eee4abbdb0f41465cc1875853ed7e3";
|
||||
std::uint8_t const xqjsEnvelopeVersion = 1U;
|
||||
std::uint16_t const xflArithmeticProfileNone =
|
||||
0U;
|
||||
std::uint16_t const xflArithmeticProfileXahauFloatV1 =
|
||||
1U;
|
||||
std::uint16_t const xflArithmeticProfileNearestEvenV1 =
|
||||
2U;
|
||||
std::uint32_t const moduleValidationLayoutVersion =
|
||||
1U;
|
||||
std::int32_t const moduleValidationFailureSentinel =
|
||||
-1;
|
||||
std::uint32_t const moduleValidationMainBit =
|
||||
1U;
|
||||
std::uint32_t const moduleValidationCallbackBit =
|
||||
2U;
|
||||
std::uint32_t const moduleValidationEntryMask =
|
||||
3U;
|
||||
std::uint32_t const moduleValidationReservedMask =
|
||||
2147483900U;
|
||||
std::uint32_t const moduleValidationProfileMask =
|
||||
16776960U;
|
||||
std::uint32_t const moduleValidationProfileShift =
|
||||
8U;
|
||||
std::uint32_t const moduleValidationVersionMask =
|
||||
2130706432U;
|
||||
std::uint32_t const moduleValidationVersionShift =
|
||||
24U;
|
||||
|
||||
namespace {
|
||||
|
||||
std::string_view const providerImportNames[] = {
|
||||
"accept", "emit", "etxn_details", "etxn_fee_base", "etxn_reserve", "fee_base", "hook_account", "hook_again", "hook_param", "ledger_last_hash", "ledger_last_time", "ledger_nonce", "ledger_seq", "otxn_id", "otxn_param", "otxn_slot", "otxn_type", "prepare", "rollback", "slot", "slot_clear", "slot_set", "slot_size", "state", "state_foreign", "state_foreign_set", "state_set", "trace"};
|
||||
std::string_view const providerExportNames[] = {
|
||||
"_initialize", "free", "malloc", "memory", "qjs_cbak", "qjs_compile", "qjs_compile_module", "qjs_destroy", "qjs_enable_coverage", "qjs_eval", "qjs_eval_bytecode", "qjs_eval_module", "qjs_get_bytecode_len", "qjs_get_bytecode_ptr", "qjs_get_result_len", "qjs_get_result_ptr", "qjs_hook", "qjs_init", "qjs_set_max_stack_size", "qjs_set_memory_limit", "qjs_set_seed", "qjs_validate_hook_module"};
|
||||
ProviderImportSignature const providerImportSignatureData[] = {
|
||||
{"env", "accept", "i32,i32,i64", "i64"},
|
||||
{"env", "emit", "i32,i32,i32,i32", "i64"},
|
||||
{"env", "etxn_details", "i32,i32", "i64"},
|
||||
{"env", "etxn_fee_base", "i32,i32", "i64"},
|
||||
{"env", "etxn_reserve", "i32", "i64"},
|
||||
{"env", "fee_base", "", "i64"},
|
||||
{"env", "hook_account", "i32,i32", "i64"},
|
||||
{"env", "hook_again", "", "i64"},
|
||||
{"env", "hook_param", "i32,i32,i32,i32", "i64"},
|
||||
{"env", "ledger_last_hash", "i32,i32", "i64"},
|
||||
{"env", "ledger_last_time", "", "i64"},
|
||||
{"env", "ledger_nonce", "i32,i32", "i64"},
|
||||
{"env", "ledger_seq", "", "i64"},
|
||||
{"env", "otxn_id", "i32,i32,i32", "i64"},
|
||||
{"env", "otxn_param", "i32,i32,i32,i32", "i64"},
|
||||
{"env", "otxn_slot", "i32", "i64"},
|
||||
{"env", "otxn_type", "", "i64"},
|
||||
{"env", "prepare", "i32,i32,i32,i32", "i64"},
|
||||
{"env", "rollback", "i32,i32,i64", "i64"},
|
||||
{"env", "slot", "i32,i32,i32", "i64"},
|
||||
{"env", "slot_clear", "i32", "i64"},
|
||||
{"env", "slot_set", "i32,i32,i32", "i64"},
|
||||
{"env", "slot_size", "i32", "i64"},
|
||||
{"env", "state", "i32,i32,i32,i32", "i64"},
|
||||
{"env", "state_foreign", "i32,i32,i32,i32,i32,i32,i32,i32", "i64"},
|
||||
{"env", "state_foreign_set", "i32,i32,i32,i32,i32,i32,i32,i32", "i64"},
|
||||
{"env", "state_set", "i32,i32,i32,i32", "i64"},
|
||||
{"env", "trace", "i32,i32,i32,i32,i32", "i64"}};
|
||||
ProviderExportSignature const providerExportSignatureData[] = {
|
||||
{"function", "_initialize", "", "", 0U, 0U, false, false},
|
||||
{"function", "free", "i32", "", 0U, 0U, false, false},
|
||||
{"function", "malloc", "i32", "i32", 0U, 0U, false, false},
|
||||
{"memory", "memory", "", "", 8U, 512U, false, false},
|
||||
{"function", "qjs_cbak", "i32,i32,i32", "i32", 0U, 0U, false, false},
|
||||
{"function", "qjs_compile", "i32,i32", "i32", 0U, 0U, false, false},
|
||||
{"function", "qjs_compile_module", "i32,i32", "i32", 0U, 0U, false, false},
|
||||
{"function", "qjs_destroy", "", "", 0U, 0U, false, false},
|
||||
{"function", "qjs_enable_coverage", "i32", "", 0U, 0U, false, false},
|
||||
{"function", "qjs_eval", "i32,i32", "i32", 0U, 0U, false, false},
|
||||
{"function", "qjs_eval_bytecode", "i32,i32", "i32", 0U, 0U, false, false},
|
||||
{"function", "qjs_eval_module", "i32,i32", "i32", 0U, 0U, false, false},
|
||||
{"function", "qjs_get_bytecode_len", "", "i32", 0U, 0U, false, false},
|
||||
{"function", "qjs_get_bytecode_ptr", "", "i32", 0U, 0U, false, false},
|
||||
{"function", "qjs_get_result_len", "", "i32", 0U, 0U, false, false},
|
||||
{"function", "qjs_get_result_ptr", "", "i32", 0U, 0U, false, false},
|
||||
{"function", "qjs_hook", "i32,i32,i32", "i32", 0U, 0U, false, false},
|
||||
{"function", "qjs_init", "", "", 0U, 0U, false, false},
|
||||
{"function", "qjs_set_max_stack_size", "i32", "", 0U, 0U, false, false},
|
||||
{"function", "qjs_set_memory_limit", "i32", "", 0U, 0U, false, false},
|
||||
{"function", "qjs_set_seed", "i32", "", 0U, 0U, false, false},
|
||||
{"function", "qjs_validate_hook_module", "i32,i32", "i32", 0U, 0U, false, false}};
|
||||
NativeImportSignature const nativeImportSignatureData[] = {
|
||||
{"accept", "int64_t", "uint32_t,uint32_t,int64_t", ""},
|
||||
{"rollback", "int64_t", "uint32_t,uint32_t,int64_t", ""},
|
||||
{"ledger_seq", "int64_t", "", ""},
|
||||
{"ledger_last_time", "int64_t", "", ""},
|
||||
{"ledger_last_hash", "int64_t", "uint32_t,uint32_t", ""},
|
||||
{"ledger_nonce", "int64_t", "uint32_t,uint32_t", ""},
|
||||
{"fee_base", "int64_t", "", ""},
|
||||
{"otxn_type", "int64_t", "", ""},
|
||||
{"otxn_id", "int64_t", "uint32_t,uint32_t,uint32_t", ""},
|
||||
{"otxn_param", "int64_t", "uint32_t,uint32_t,uint32_t,uint32_t", ""},
|
||||
{"otxn_slot", "int64_t", "uint32_t", ""},
|
||||
{"slot_set", "int64_t", "uint32_t,uint32_t,uint32_t", ""},
|
||||
{"slot_size", "int64_t", "uint32_t", ""},
|
||||
{"slot", "int64_t", "uint32_t,uint32_t,uint32_t", ""},
|
||||
{"slot_clear", "int64_t", "uint32_t", ""},
|
||||
{"hook_account", "int64_t", "uint32_t,uint32_t", ""},
|
||||
{"hook_param", "int64_t", "uint32_t,uint32_t,uint32_t,uint32_t", ""},
|
||||
{"hook_again", "int64_t", "", ""},
|
||||
{"trace", "int64_t", "uint32_t,uint32_t,uint32_t,uint32_t,uint32_t", ""},
|
||||
{"state", "int64_t", "uint32_t,uint32_t,uint32_t,uint32_t", ""},
|
||||
{"state_foreign", "int64_t", "uint32_t,uint32_t,uint32_t,uint32_t,uint32_t,uint32_t,uint32_t,uint32_t", ""},
|
||||
{"state_foreign_set", "int64_t", "uint32_t,uint32_t,uint32_t,uint32_t,uint32_t,uint32_t,uint32_t,uint32_t", ""},
|
||||
{"state_set", "int64_t", "uint32_t,uint32_t,uint32_t,uint32_t", ""},
|
||||
{"prepare", "int64_t", "uint32_t,uint32_t,uint32_t,uint32_t", "featureHooksUpdate2"},
|
||||
{"etxn_details", "int64_t", "uint32_t,uint32_t", ""},
|
||||
{"etxn_fee_base", "int64_t", "uint32_t,uint32_t", ""},
|
||||
{"etxn_reserve", "int64_t", "uint32_t", ""},
|
||||
{"emit", "int64_t", "uint32_t,uint32_t,uint32_t,uint32_t", ""}};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::span<std::string_view const> const providerImports{providerImportNames};
|
||||
std::span<std::string_view const> const providerExports{providerExportNames};
|
||||
std::span<ProviderImportSignature const> const providerImportSignatures{
|
||||
providerImportSignatureData};
|
||||
std::span<ProviderExportSignature const> const providerExportSignatures{
|
||||
providerExportSignatureData};
|
||||
std::span<NativeImportSignature const> const nativeImportSignatures{
|
||||
nativeImportSignatureData};
|
||||
std::string_view const nativeABISourceRepository = "https://github.com/Xahau/xahaud";
|
||||
std::string_view const nativeABISourceCommit = "bb244ef7729503a0317bcff0f8fdaa93ca5cb7d2";
|
||||
std::string_view const nativeABISourcePath = "include/xrpl/hook/hook_api.macro";
|
||||
std::string_view const nativeABISHA256 = "972a12bac2add4335b4e02097ec2ed7276fbea5b31dd045e9ae04f38a1762157";
|
||||
std::size_t const nativeABICatalogueCount = 75;
|
||||
|
||||
} // namespace hook::artifact::generated
|
||||
2612
external/quickjs-provider/xahau-quickjs-v1-consensus-entropy.d.ts
vendored
Normal file
2612
external/quickjs-provider/xahau-quickjs-v1-consensus-entropy.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1504
external/quickjs-provider/xahau-quickjs-v1-consensus-entropy.surface.json
vendored
Normal file
1504
external/quickjs-provider/xahau-quickjs-v1-consensus-entropy.surface.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2567
external/quickjs-provider/xahau-quickjs-v1.d.ts
vendored
Normal file
2567
external/quickjs-provider/xahau-quickjs-v1.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1489
external/quickjs-provider/xahau-quickjs-v1.surface.json
vendored
Normal file
1489
external/quickjs-provider/xahau-quickjs-v1.surface.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
16
external/quickjs-provider/xfl-profile-ledger.ts
vendored
Normal file
16
external/quickjs-provider/xfl-profile-ledger.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
// Generated by `hooks-api project` from the @profile-sensitive
|
||||
// annotations in hooks-api.source.d.ts. Do not edit.
|
||||
|
||||
export const XFL_PROFILE_LEDGER_SCHEMA = "jshookz.xfl-profile-ledger.v1";
|
||||
export const XFL_PROFILE_SENSITIVE_MEMBERS = [
|
||||
"XFLDecimal.add",
|
||||
"XFLDecimal.divide",
|
||||
"XFLDecimal.multiply",
|
||||
"XFLDecimal.subtract",
|
||||
] as const;
|
||||
|
||||
export const XFL_PROFILE_IMPLEMENTATIONS = {
|
||||
"none": [],
|
||||
"xahauFloatV1": ["XFLDecimal.add", "XFLDecimal.divide", "XFLDecimal.multiply", "XFLDecimal.subtract"],
|
||||
"nearestEvenV1": [],
|
||||
} as const;
|
||||
27
external/wasmtime/conandata.yml
vendored
Normal file
27
external/wasmtime/conandata.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
sources:
|
||||
47.0.3:
|
||||
Linux:
|
||||
armv8:
|
||||
sha256: c1ef99bb4ef0151282f339e22a67f8229d9ab336f9a17387e65f140b79c45be1
|
||||
url: https://github.com/bytecodealliance/wasmtime/releases/download/v47.0.3/wasmtime-v47.0.3-aarch64-linux-c-api.tar.xz
|
||||
x86_64:
|
||||
sha256: aaa3621f2a3d8393696702897f8f78a1cc504437d500701496d560125aefd732
|
||||
url: https://github.com/bytecodealliance/wasmtime/releases/download/v47.0.3/wasmtime-v47.0.3-x86_64-linux-c-api.tar.xz
|
||||
Macos:
|
||||
armv8:
|
||||
sha256: 1854c8f03a764c89afe77fa88d9092ab89a368e527cd27a12959b1d91152324e
|
||||
url: https://github.com/bytecodealliance/wasmtime/releases/download/v47.0.3/wasmtime-v47.0.3-aarch64-macos-c-api.tar.xz
|
||||
x86_64:
|
||||
sha256: 627622087b77b92c163e826ec6ebf834a70d78735828043edf7ace263f8a9e62
|
||||
url: https://github.com/bytecodealliance/wasmtime/releases/download/v47.0.3/wasmtime-v47.0.3-x86_64-macos-c-api.tar.xz
|
||||
MinGW:
|
||||
x86_64:
|
||||
sha256: 355a0c7b49b92bb011e74a4047cc1186271aeb8c8cfcc4360248caaf3c4d5f38
|
||||
url: https://github.com/bytecodealliance/wasmtime/releases/download/v47.0.3/wasmtime-v47.0.3-x86_64-mingw-c-api.zip
|
||||
Windows:
|
||||
armv8:
|
||||
sha256: 57107b14b28ac2152e903501fe1722007b90fc11ccd63ad6d62ed574c5dd90db
|
||||
url: https://github.com/bytecodealliance/wasmtime/releases/download/v47.0.3/wasmtime-v47.0.3-aarch64-windows-c-api.zip
|
||||
x86_64:
|
||||
sha256: 952dfbc864295eccc89caa92bc303686017ca4ea205a1f21878809f374bb77c5
|
||||
url: https://github.com/bytecodealliance/wasmtime/releases/download/v47.0.3/wasmtime-v47.0.3-x86_64-windows-c-api.zip
|
||||
162
external/wasmtime/conanfile.py
vendored
Normal file
162
external/wasmtime/conanfile.py
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
import os
|
||||
|
||||
from conan import ConanFile
|
||||
from conan.errors import ConanInvalidConfiguration
|
||||
from conan.tools.files import copy, get
|
||||
from conan.tools.microsoft import is_msvc
|
||||
|
||||
|
||||
required_conan_version = ">=2.0"
|
||||
|
||||
|
||||
class WasmtimeConan(ConanFile):
|
||||
name = "wasmtime"
|
||||
description = "Standalone WebAssembly runtime using Cranelift"
|
||||
license = "Apache-2.0 WITH LLVM-exception"
|
||||
url = "https://github.com/Xahau/xahaud"
|
||||
homepage = "https://github.com/bytecodealliance/wasmtime"
|
||||
topics = ("webassembly", "wasm")
|
||||
package_type = "library"
|
||||
settings = "os", "arch", "compiler", "build_type"
|
||||
options = {"shared": [True, False]}
|
||||
default_options = {"shared": False}
|
||||
no_copy_source = True
|
||||
|
||||
@property
|
||||
def _sources_os_key(self):
|
||||
if is_msvc(self):
|
||||
return "Windows"
|
||||
if self.settings.os == "Windows" and self.settings.compiler == "gcc":
|
||||
return "MinGW"
|
||||
return str(self.settings.os)
|
||||
|
||||
def configure(self):
|
||||
self.settings.rm_safe("compiler.libcxx")
|
||||
self.settings.rm_safe("compiler.cppstd")
|
||||
|
||||
def package_id(self):
|
||||
# Official release archives contain a prebuilt C ABI. Compiler patch
|
||||
# versions do not change the packaged bytes.
|
||||
if self.info.settings.compiler == "clang":
|
||||
self.info.settings.compiler = "gcc"
|
||||
del self.info.settings.compiler.version
|
||||
|
||||
def validate(self):
|
||||
try:
|
||||
self.conan_data["sources"][self.version][self._sources_os_key][
|
||||
str(self.settings.arch)
|
||||
]
|
||||
except KeyError as error:
|
||||
raise ConanInvalidConfiguration(
|
||||
"No official Wasmtime C-API archive for "
|
||||
f"{self.settings.os}/{self.settings.arch}"
|
||||
) from error
|
||||
|
||||
def build(self):
|
||||
get(
|
||||
self,
|
||||
**self.conan_data["sources"][self.version][self._sources_os_key][
|
||||
str(self.settings.arch)
|
||||
],
|
||||
destination=self.build_folder,
|
||||
strip_root=True,
|
||||
)
|
||||
|
||||
def package(self):
|
||||
copy(
|
||||
self,
|
||||
"*.h",
|
||||
dst=os.path.join(self.package_folder, "include"),
|
||||
src=os.path.join(self.build_folder, "include"),
|
||||
)
|
||||
copy(
|
||||
self,
|
||||
"*.hh",
|
||||
dst=os.path.join(self.package_folder, "include"),
|
||||
src=os.path.join(self.build_folder, "include"),
|
||||
)
|
||||
|
||||
source_lib = os.path.join(self.build_folder, "lib")
|
||||
package_lib = os.path.join(self.package_folder, "lib")
|
||||
package_bin = os.path.join(self.package_folder, "bin")
|
||||
if self.options.shared:
|
||||
copy(
|
||||
self,
|
||||
"wasmtime.dll.lib",
|
||||
dst=package_lib,
|
||||
src=source_lib,
|
||||
keep_path=False,
|
||||
)
|
||||
copy(
|
||||
self,
|
||||
"wasmtime.dll",
|
||||
dst=package_bin,
|
||||
src=source_lib,
|
||||
keep_path=False,
|
||||
)
|
||||
copy(
|
||||
self,
|
||||
"libwasmtime.dll.a",
|
||||
dst=package_lib,
|
||||
src=source_lib,
|
||||
keep_path=False,
|
||||
)
|
||||
copy(
|
||||
self,
|
||||
"libwasmtime.so*",
|
||||
dst=package_lib,
|
||||
src=source_lib,
|
||||
keep_path=False,
|
||||
)
|
||||
copy(
|
||||
self,
|
||||
"libwasmtime.dylib",
|
||||
dst=package_lib,
|
||||
src=source_lib,
|
||||
keep_path=False,
|
||||
)
|
||||
else:
|
||||
copy(
|
||||
self,
|
||||
"wasmtime.lib",
|
||||
dst=package_lib,
|
||||
src=source_lib,
|
||||
keep_path=False,
|
||||
)
|
||||
copy(
|
||||
self,
|
||||
"libwasmtime.a",
|
||||
dst=package_lib,
|
||||
src=source_lib,
|
||||
keep_path=False,
|
||||
)
|
||||
|
||||
copy(
|
||||
self,
|
||||
"LICENSE",
|
||||
dst=os.path.join(self.package_folder, "licenses"),
|
||||
src=self.build_folder,
|
||||
)
|
||||
|
||||
def package_info(self):
|
||||
if self.options.shared:
|
||||
self.cpp_info.libs = [
|
||||
"wasmtime.dll" if self.settings.os == "Windows" else "wasmtime"
|
||||
]
|
||||
else:
|
||||
if self.settings.os == "Windows":
|
||||
self.cpp_info.defines = ["WASM_API_EXTERN=", "WASI_API_EXTERN="]
|
||||
self.cpp_info.libs = ["wasmtime"]
|
||||
|
||||
if self.settings.os == "Windows":
|
||||
self.cpp_info.system_libs = [
|
||||
"ws2_32",
|
||||
"bcrypt",
|
||||
"advapi32",
|
||||
"userenv",
|
||||
"ntdll",
|
||||
"shell32",
|
||||
"ole32",
|
||||
]
|
||||
elif self.settings.os in ("Linux", "FreeBSD"):
|
||||
self.cpp_info.system_libs = ["pthread", "dl", "m", "rt"]
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <boost/beast/core/string.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@@ -165,6 +166,8 @@ private:
|
||||
beast::severities::Severity thresh_;
|
||||
File file_;
|
||||
bool silent_ = false;
|
||||
using Transform = std::function<std::string(std::string const&)>;
|
||||
Transform transform_;
|
||||
|
||||
public:
|
||||
Logs(beast::severities::Severity level);
|
||||
@@ -203,6 +206,28 @@ public:
|
||||
std::string const& text,
|
||||
bool console);
|
||||
|
||||
/** Rewrite test log lines. Not synchronized.
|
||||
*
|
||||
* Test-only. Installed once for the life of Logs. Empty is a null check.
|
||||
*/
|
||||
void
|
||||
setTransform(Transform fn)
|
||||
{
|
||||
transform_ = std::move(fn);
|
||||
}
|
||||
|
||||
bool
|
||||
hasTransform() const
|
||||
{
|
||||
return static_cast<bool>(transform_);
|
||||
}
|
||||
|
||||
std::string
|
||||
applyTransform(std::string const& text) const
|
||||
{
|
||||
return transform_ ? transform_(text) : text;
|
||||
}
|
||||
|
||||
std::string
|
||||
rotate();
|
||||
|
||||
|
||||
119
include/xrpl/hook/HookArtifact.h
Normal file
119
include/xrpl/hook/HookArtifact.h
Normal file
@@ -0,0 +1,119 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Canonical Hook artifact classification and QuickJS deployment envelope.
|
||||
|
||||
Legacy C Hooks remain raw WebAssembly. QuickJS bytecode is never deployed
|
||||
raw: it is carried in a fixed-width, big-endian XQJS envelope whose exact
|
||||
bytes participate in HookHash.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef XRPL_HOOK_HOOKARTIFACT_H_INCLUDED
|
||||
#define XRPL_HOOK_HOOKARTIFACT_H_INCLUDED
|
||||
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
namespace hook::artifact {
|
||||
|
||||
inline constexpr std::size_t identitySize = 32;
|
||||
inline constexpr std::size_t quickJSHeaderSize = 80;
|
||||
inline constexpr std::size_t maxLegacyCreateCodeSize = 65'535;
|
||||
inline constexpr std::size_t maxQuickJSCreateCodeSize = 128 * 1024;
|
||||
inline constexpr std::array<std::uint8_t, 4> quickJSMagic = {
|
||||
'X',
|
||||
'Q',
|
||||
'J',
|
||||
'S'};
|
||||
inline constexpr std::uint8_t quickJSEnvelopeVersion = 1;
|
||||
inline constexpr std::uint8_t quickJSBytecodeKind = 1;
|
||||
|
||||
using Identity = std::array<std::uint8_t, identitySize>;
|
||||
|
||||
enum class Kind : std::uint8_t {
|
||||
legacyWasm,
|
||||
quickJSBytecode,
|
||||
};
|
||||
|
||||
enum class XFLArithmeticProfile {
|
||||
none,
|
||||
xahauFloatV1,
|
||||
nearestEvenV1,
|
||||
};
|
||||
|
||||
enum class Error : std::uint8_t {
|
||||
tooLarge,
|
||||
tooShort,
|
||||
unknownMagic,
|
||||
truncatedQuickJSHeader,
|
||||
unsupportedEnvelopeVersion,
|
||||
unsupportedArtifactKind,
|
||||
nonCanonicalHeaderSize,
|
||||
unsupportedXFLArithmeticProfile,
|
||||
emptyPayload,
|
||||
lengthMismatch,
|
||||
zeroBytecodeABI,
|
||||
zeroRuntimeProfile,
|
||||
};
|
||||
|
||||
struct View
|
||||
{
|
||||
Kind kind;
|
||||
std::uint16_t hookApiVersion;
|
||||
XFLArithmeticProfile xflArithmeticProfile;
|
||||
Identity bytecodeABI;
|
||||
Identity runtimeProfile;
|
||||
ripple::Slice payload;
|
||||
};
|
||||
|
||||
/** Check the byte-size gate before full Hook artifact parsing.
|
||||
|
||||
Legacy Wasm and unknown payloads remain capped at 65,535 bytes. An XQJS
|
||||
magic prefix makes a candidate eligible for full parsing through 128 KiB;
|
||||
parse() must still recognize the complete v1 envelope before admission.
|
||||
*/
|
||||
bool
|
||||
fitsCreateCodeSizeLimit(ripple::Slice code) noexcept;
|
||||
|
||||
/** Parse either a legacy raw-Wasm Hook or a canonical QuickJS artifact.
|
||||
|
||||
The returned slices retain no ownership and must not outlive `code`.
|
||||
*/
|
||||
ripple::Expected<View, Error>
|
||||
parse(ripple::Slice code) noexcept;
|
||||
|
||||
std::string_view
|
||||
toString(Error error) noexcept;
|
||||
|
||||
extern Identity const quickJSBytecodeABI;
|
||||
extern Identity const quickJSRuntimeProfile;
|
||||
extern Identity const quickJSProviderSHA256;
|
||||
extern std::size_t const quickJSProviderSize;
|
||||
extern std::uint16_t const quickJSHookApiVersion;
|
||||
extern std::uint64_t const quickJSInitializationFuel;
|
||||
extern std::uint64_t const quickJSInvocationFuel;
|
||||
extern std::string_view const quickJSHostWorkMeter;
|
||||
extern std::uint64_t const quickJSHostWorkBudget;
|
||||
extern std::uint64_t const quickJSHostWorkBasePerCall;
|
||||
extern std::uint64_t const quickJSHostWorkPerAddressedByte;
|
||||
extern std::string_view const quickJSHostAdapterPolicy;
|
||||
extern std::uint32_t const quickJSHeapBytes;
|
||||
extern std::uint32_t const quickJSStackBytes;
|
||||
extern std::uint32_t const quickJSSerializedObjectMaxBytes;
|
||||
extern std::uint32_t const quickJSSerializedObjectMaxFields;
|
||||
extern std::uint32_t const quickJSSerializedObjectMaxScopes;
|
||||
extern std::uint32_t const quickJSSerializedObjectMaxDepth;
|
||||
extern std::uint32_t const quickJSProviderMemoryMinimumPages;
|
||||
extern std::uint32_t const quickJSProviderMemoryMaximumPages;
|
||||
extern std::uint32_t const quickJSProviderWasmStackBytes;
|
||||
|
||||
bool
|
||||
isCurrentQuickJS(View const& artifact) noexcept;
|
||||
|
||||
} // namespace hook::artifact
|
||||
|
||||
#endif // XRPL_HOOK_HOOKARTIFACT_H_INCLUDED
|
||||
@@ -91,83 +91,6 @@
|
||||
|
||||
#define UNSIGNED_TYPE(T) std::make_unsigned_t<T>
|
||||
|
||||
#define DECLARE_HOOK_FUNCTION(R, F, ...) \
|
||||
std::variant<UNSIGNED_TYPE(R), hook_api::hook_return_code> F( \
|
||||
hook::HookContext& hookCtx, \
|
||||
WasmEdge_CallingFrameContext const& frameCtx __VA_OPT__( \
|
||||
COMMA __VA_ARGS__)); \
|
||||
extern WasmEdge_Result WasmFunction##F( \
|
||||
void* data_ptr, \
|
||||
const WasmEdge_CallingFrameContext* frameCtx, \
|
||||
const WasmEdge_Value* in, \
|
||||
WasmEdge_Value* out); \
|
||||
extern WasmEdge_ValType WasmFunctionParams##F[]; \
|
||||
extern WasmEdge_ValType WasmFunctionResult##F[]; \
|
||||
extern WasmEdge_FunctionTypeContext* WasmFunctionType##F; \
|
||||
extern WasmEdge_String WasmFunctionName##F;
|
||||
|
||||
#define DEFINE_HOOK_FUNCTION(R, F, ...) \
|
||||
WasmEdge_Result hook_api::WasmFunction##F( \
|
||||
void* data_ptr, \
|
||||
const WasmEdge_CallingFrameContext* frameCtx, \
|
||||
const WasmEdge_Value* in, \
|
||||
WasmEdge_Value* out) \
|
||||
{ \
|
||||
__VA_OPT__(int _stack = 0;) \
|
||||
__VA_OPT__(FOR_VARS(VAR_ASSIGN, 2, __VA_ARGS__);) \
|
||||
hook::HookContext* hookCtx = \
|
||||
reinterpret_cast<hook::HookContext*>(data_ptr); \
|
||||
auto const& return_code = hook_api::F( \
|
||||
*hookCtx, \
|
||||
*const_cast<WasmEdge_CallingFrameContext*>(frameCtx) \
|
||||
__VA_OPT__(COMMA STRIP_TYPES(__VA_ARGS__))); \
|
||||
if (std::holds_alternative<hook_api::hook_return_code>(return_code) && \
|
||||
(std::get<hook_api::hook_return_code>(return_code) == \
|
||||
RC_ROLLBACK || \
|
||||
std::get<hook_api::hook_return_code>(return_code) == RC_ACCEPT)) \
|
||||
return WasmEdge_Result_Terminate; \
|
||||
out[0] = RET_ASSIGN( \
|
||||
R, \
|
||||
std::holds_alternative<UNSIGNED_TYPE(R)>(return_code) \
|
||||
? std::get<UNSIGNED_TYPE(R)>(return_code) \
|
||||
: R(std::get<hook_api::hook_return_code>(return_code))); \
|
||||
return WasmEdge_Result_Success; \
|
||||
}; \
|
||||
WasmEdge_ValType hook_api::WasmFunctionParams##F[] = { \
|
||||
__VA_OPT__(FOR_VARS(WASM_VAL_TYPE, 0, __VA_ARGS__))}; \
|
||||
WasmEdge_ValType hook_api::WasmFunctionResult##F[1] = { \
|
||||
WASM_VAL_TYPE(R, dummy)}; \
|
||||
WasmEdge_FunctionTypeContext* hook_api::WasmFunctionType##F = \
|
||||
WasmEdge_FunctionTypeCreate( \
|
||||
WasmFunctionParams##F, \
|
||||
VA_NARGS(NULL __VA_OPT__(, __VA_ARGS__)), \
|
||||
WasmFunctionResult##F, \
|
||||
1); \
|
||||
WasmEdge_String hook_api::WasmFunctionName##F = \
|
||||
WasmEdge_StringCreateByCString(#F); \
|
||||
std::variant<UNSIGNED_TYPE(R), hook_api::hook_return_code> hook_api::F( \
|
||||
hook::HookContext& hookCtx, \
|
||||
WasmEdge_CallingFrameContext const& frameCtx __VA_OPT__( \
|
||||
COMMA __VA_ARGS__))
|
||||
|
||||
#define HOOK_SETUP() \
|
||||
using enum hook_api::hook_return_code; \
|
||||
try \
|
||||
{ \
|
||||
[[maybe_unused]] ApplyContext& applyCtx = hookCtx.applyCtx; \
|
||||
[[maybe_unused]] auto& view = applyCtx.view(); \
|
||||
[[maybe_unused]] auto j = applyCtx.app.journal("View"); \
|
||||
[[maybe_unused]] WasmEdge_MemoryInstanceContext* memoryCtx = \
|
||||
WasmEdge_CallingFrameGetMemoryInstance(&frameCtx, 0); \
|
||||
[[maybe_unused]] unsigned char* memory = \
|
||||
WasmEdge_MemoryInstanceGetPointer(memoryCtx, 0, 0); \
|
||||
[[maybe_unused]] const uint64_t memory_length = \
|
||||
WasmEdge_MemoryInstanceGetPageSize(memoryCtx) * \
|
||||
WasmEdge_kPageSize; \
|
||||
[[maybe_unused]] auto& api = hookCtx.api(); \
|
||||
if (!memoryCtx || !memory || !memory_length) \
|
||||
return INTERNAL_ERROR;
|
||||
|
||||
#define HOOK_TEARDOWN() \
|
||||
} \
|
||||
catch (const std::exception& e) \
|
||||
@@ -178,54 +101,6 @@
|
||||
return INTERNAL_ERROR; \
|
||||
}
|
||||
|
||||
#define WRITE_WASM_MEMORY( \
|
||||
bytes_written, \
|
||||
guest_dst_ptr, \
|
||||
guest_dst_len, \
|
||||
host_src_ptr, \
|
||||
host_src_len, \
|
||||
host_memory_ptr, \
|
||||
guest_memory_length) \
|
||||
{ \
|
||||
int64_t bytes_to_write = std::min( \
|
||||
static_cast<int64_t>(host_src_len), \
|
||||
static_cast<int64_t>(guest_dst_len)); \
|
||||
if (guest_dst_ptr + bytes_to_write > guest_memory_length) \
|
||||
{ \
|
||||
JLOG(j.warn()) << "HookError[" << HC_ACC() << "]: " << __func__ \
|
||||
<< " tried to retreive blob of " << host_src_len \
|
||||
<< " bytes past end of wasm memory"; \
|
||||
return OUT_OF_BOUNDS; \
|
||||
} \
|
||||
if (!WasmEdge_ResultOK(WasmEdge_MemoryInstanceSetData( \
|
||||
memoryCtx, \
|
||||
reinterpret_cast<const uint8_t*>(host_src_ptr), \
|
||||
guest_dst_ptr, \
|
||||
bytes_to_write))) \
|
||||
return INTERNAL_ERROR; \
|
||||
bytes_written += bytes_to_write; \
|
||||
}
|
||||
|
||||
#define WRITE_WASM_MEMORY_AND_RETURN( \
|
||||
guest_dst_ptr, \
|
||||
guest_dst_len, \
|
||||
host_src_ptr, \
|
||||
host_src_len, \
|
||||
host_memory_ptr, \
|
||||
guest_memory_length) \
|
||||
{ \
|
||||
uint64_t bytes_written = 0; \
|
||||
WRITE_WASM_MEMORY( \
|
||||
bytes_written, \
|
||||
guest_dst_ptr, \
|
||||
guest_dst_len, \
|
||||
host_src_ptr, \
|
||||
host_src_len, \
|
||||
host_memory_ptr, \
|
||||
guest_memory_length); \
|
||||
return bytes_written; \
|
||||
}
|
||||
|
||||
// ptr = pointer inside the wasm memory space
|
||||
#define NOT_IN_BOUNDS(ptr, len, memory_length) \
|
||||
((static_cast<uint64_t>(ptr) >= static_cast<uint64_t>(memory_length)) || \
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||
// in include/xrpl/protocol/Feature.h.
|
||||
|
||||
XRPL_FEATURE(JSHooks, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (HookMap, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FIX (GuardDepth32, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(NamedHooks, Supported::yes, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -19,11 +19,17 @@
|
||||
#include <test/app/Import_json.h>
|
||||
#include <test/jtx.h>
|
||||
#include <xrpld/app/hook/HookAPI.h>
|
||||
#include <xrpld/app/hook/HookHostFunction.h>
|
||||
#include <xrpld/app/hook/applyHook.h>
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/json/json_writer.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAccount.h>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
@@ -153,6 +159,90 @@ public:
|
||||
auto const result2 =
|
||||
api.emit(Slice(result.value().data(), result.value().size()));
|
||||
BEAST_EXPECT(result2.has_value());
|
||||
|
||||
testcase("QuickJS prepare fixed buffer");
|
||||
auto const source = s.slice();
|
||||
auto const preparedSize = result.value().size();
|
||||
|
||||
auto invokeHostPrepare =
|
||||
[&](std::size_t writeLength,
|
||||
hook::HookGuestMemory::WriteContract contract =
|
||||
hook::HookGuestMemory::WriteContract::fixedBuffer) {
|
||||
auto rawHookCtx = makeStubHookContext(
|
||||
applyCtx,
|
||||
alice.id(),
|
||||
alice.id(),
|
||||
{
|
||||
.expected_etxn_count = 1,
|
||||
});
|
||||
std::vector<std::uint8_t> memory(
|
||||
preparedSize + source.size(), 0xA5);
|
||||
std::memcpy(
|
||||
memory.data() + preparedSize,
|
||||
source.data(),
|
||||
source.size());
|
||||
auto const before = memory;
|
||||
hook::HookGuestMemory guestMemory{
|
||||
memory.data(),
|
||||
memory.size(),
|
||||
nullptr,
|
||||
nullptr,
|
||||
contract};
|
||||
std::array<hook::HookHostValue, 4> inputs{
|
||||
hook::HookHostValue::u32(0),
|
||||
hook::HookHostValue::u32(
|
||||
static_cast<std::uint32_t>(writeLength)),
|
||||
hook::HookHostValue::u32(
|
||||
static_cast<std::uint32_t>(preparedSize)),
|
||||
hook::HookHostValue::u32(
|
||||
static_cast<std::uint32_t>(source.size()))};
|
||||
hook::HookHostValue output{};
|
||||
auto const* operation =
|
||||
hook::findHookHostFunction("prepare");
|
||||
auto const status = operation
|
||||
? operation->function(
|
||||
&rawHookCtx,
|
||||
guestMemory,
|
||||
inputs.data(),
|
||||
inputs.size(),
|
||||
&output,
|
||||
1)
|
||||
: hook::HookHostCallStatus::trap;
|
||||
return std::tuple{
|
||||
status, output, std::move(memory), before};
|
||||
};
|
||||
|
||||
auto [exactStatus, exactResult, exactMemory, exactBefore] =
|
||||
invokeHostPrepare(preparedSize);
|
||||
BEAST_EXPECT(exactStatus == hook::HookHostCallStatus::success);
|
||||
BEAST_EXPECT(exactResult.kind == hook::HookHostValueKind::i64);
|
||||
BEAST_EXPECT(exactResult.asI64() == preparedSize);
|
||||
BEAST_EXPECT(exactMemory != exactBefore);
|
||||
BEAST_EXPECT(std::equal(
|
||||
result.value().begin(),
|
||||
result.value().end(),
|
||||
exactMemory.begin()));
|
||||
|
||||
auto [shortStatus, shortResult, shortMemory, shortBefore] =
|
||||
invokeHostPrepare(preparedSize - 1);
|
||||
BEAST_EXPECT(shortStatus == hook::HookHostCallStatus::success);
|
||||
BEAST_EXPECT(
|
||||
std::bit_cast<std::int64_t>(shortResult.asI64()) ==
|
||||
static_cast<std::int64_t>(TOO_SMALL));
|
||||
BEAST_EXPECT(shortMemory == shortBefore);
|
||||
|
||||
testcase("Legacy C prepare actual-size copy");
|
||||
auto [legacyStatus, legacyResult, legacyMemory, legacyBefore] =
|
||||
invokeHostPrepare(
|
||||
preparedSize - 1,
|
||||
hook::HookGuestMemory::WriteContract::legacyActualSize);
|
||||
BEAST_EXPECT(legacyStatus == hook::HookHostCallStatus::success);
|
||||
BEAST_EXPECT(legacyResult.asI64() == preparedSize);
|
||||
BEAST_EXPECT(legacyMemory != legacyBefore);
|
||||
BEAST_EXPECT(std::equal(
|
||||
result.value().begin(),
|
||||
result.value().end(),
|
||||
legacyMemory.begin()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2943,7 +3033,10 @@ public:
|
||||
BEAST_EXPECT(newSlot == 112);
|
||||
BEAST_EXPECT(hookCtx.slot.contains(112));
|
||||
BEAST_EXPECT(hookCtx.slot[112].entry != nullptr);
|
||||
// TODO: test slot content
|
||||
BEAST_EXPECT(hookCtx.slot[112].storage != nullptr);
|
||||
BEAST_EXPECT(
|
||||
hookCtx.slot[112].entry == hookCtx.slot[112].storage.get());
|
||||
BEAST_EXPECT(hookCtx.slot[112].entry->isEquivalent(tx));
|
||||
}
|
||||
|
||||
{
|
||||
@@ -2958,7 +3051,37 @@ public:
|
||||
BEAST_EXPECT(newSlot == 200);
|
||||
BEAST_EXPECT(hookCtx.slot.contains(200));
|
||||
BEAST_EXPECT(hookCtx.slot[newSlot].entry != nullptr);
|
||||
// TODO: test slot content
|
||||
BEAST_EXPECT(hookCtx.slot[newSlot].storage != nullptr);
|
||||
BEAST_EXPECT(
|
||||
hookCtx.slot[newSlot].entry ==
|
||||
hookCtx.slot[newSlot].storage.get());
|
||||
BEAST_EXPECT(hookCtx.slot[newSlot].entry->isEquivalent(tx));
|
||||
}
|
||||
|
||||
{
|
||||
// Emit-failure callbacks expose that object, not applyCtx.tx.
|
||||
STObject emitFailure(sfGeneric);
|
||||
emitFailure.setFieldU16(sfTransactionType, ttACCOUNT_SET);
|
||||
emitFailure.setFieldU32(sfSequence, 42);
|
||||
StubHookContext stubCtx{.emitFailure = emitFailure};
|
||||
auto hookCtx =
|
||||
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
||||
auto& api = hookCtx.api();
|
||||
auto const result = api.otxn_slot(0);
|
||||
BEAST_EXPECT(result.has_value());
|
||||
if (result)
|
||||
{
|
||||
auto const newSlot = result.value();
|
||||
BEAST_EXPECT(newSlot > 0);
|
||||
BEAST_EXPECT(hookCtx.slot.contains(newSlot));
|
||||
BEAST_EXPECT(hookCtx.slot[newSlot].entry != nullptr);
|
||||
BEAST_EXPECT(hookCtx.slot[newSlot].storage != nullptr);
|
||||
BEAST_EXPECT(
|
||||
hookCtx.slot[newSlot].entry ==
|
||||
hookCtx.slot[newSlot].storage.get());
|
||||
BEAST_EXPECT(*hookCtx.slot[newSlot].entry == emitFailure);
|
||||
BEAST_EXPECT(*hookCtx.slot[newSlot].entry != tx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
475
src/test/app/HookArtifact_test.cpp
Normal file
475
src/test/app/HookArtifact_test.cpp
Normal file
@@ -0,0 +1,475 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Tests for the consensus-visible Hook deployment envelope.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/hook/QuickJSHookRuntime.h>
|
||||
#include <xrpld/app/hook/detail/QuickJSProviderProfile.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/hook/Enum.h>
|
||||
#include <xrpl/hook/HookArtifact.h>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple::test {
|
||||
namespace {
|
||||
|
||||
struct ExpectedProviderExport
|
||||
{
|
||||
std::string_view kind;
|
||||
std::string_view name;
|
||||
std::string_view parameters;
|
||||
std::string_view results;
|
||||
std::uint32_t minimumPages;
|
||||
std::uint32_t maximumPages;
|
||||
bool memory64;
|
||||
bool shared;
|
||||
};
|
||||
|
||||
constexpr ExpectedProviderExport expectedProviderExports[] = {
|
||||
{"function", "_initialize", "", "", 0, 0, false, false},
|
||||
{"function", "free", "i32", "", 0, 0, false, false},
|
||||
{"function", "malloc", "i32", "i32", 0, 0, false, false},
|
||||
{"memory", "memory", "", "", 8, 512, false, false},
|
||||
{"function", "qjs_cbak", "i32,i32,i32", "i32", 0, 0, false, false},
|
||||
{"function", "qjs_compile", "i32,i32", "i32", 0, 0, false, false},
|
||||
{"function", "qjs_compile_module", "i32,i32", "i32", 0, 0, false, false},
|
||||
{"function", "qjs_destroy", "", "", 0, 0, false, false},
|
||||
{"function", "qjs_enable_coverage", "i32", "", 0, 0, false, false},
|
||||
{"function", "qjs_eval", "i32,i32", "i32", 0, 0, false, false},
|
||||
{"function", "qjs_eval_bytecode", "i32,i32", "i32", 0, 0, false, false},
|
||||
{"function", "qjs_eval_module", "i32,i32", "i32", 0, 0, false, false},
|
||||
{"function", "qjs_get_bytecode_len", "", "i32", 0, 0, false, false},
|
||||
{"function", "qjs_get_bytecode_ptr", "", "i32", 0, 0, false, false},
|
||||
{"function", "qjs_get_result_len", "", "i32", 0, 0, false, false},
|
||||
{"function", "qjs_get_result_ptr", "", "i32", 0, 0, false, false},
|
||||
{"function", "qjs_hook", "i32,i32,i32", "i32", 0, 0, false, false},
|
||||
{"function", "qjs_init", "", "", 0, 0, false, false},
|
||||
{"function", "qjs_set_max_stack_size", "i32", "", 0, 0, false, false},
|
||||
{"function", "qjs_set_memory_limit", "i32", "", 0, 0, false, false},
|
||||
{"function", "qjs_set_seed", "i32", "", 0, 0, false, false},
|
||||
{"function",
|
||||
"qjs_validate_hook_module",
|
||||
"i32,i32",
|
||||
"i32",
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
false},
|
||||
};
|
||||
|
||||
std::uint16_t
|
||||
xflProfileCode(hook::artifact::XFLArithmeticProfile profile)
|
||||
{
|
||||
switch (profile)
|
||||
{
|
||||
case hook::artifact::XFLArithmeticProfile::none:
|
||||
return hook::artifact::generated::xflArithmeticProfileNone;
|
||||
case hook::artifact::XFLArithmeticProfile::xahauFloatV1:
|
||||
return hook::artifact::generated::xflArithmeticProfileXahauFloatV1;
|
||||
case hook::artifact::XFLArithmeticProfile::nearestEvenV1:
|
||||
return hook::artifact::generated::xflArithmeticProfileNearestEvenV1;
|
||||
}
|
||||
return hook::artifact::generated::xflArithmeticProfileNone;
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t>
|
||||
quickJSArtifact(
|
||||
std::vector<std::uint8_t> const& payload = {'a', 'b', 'c'},
|
||||
hook::artifact::XFLArithmeticProfile profile =
|
||||
hook::artifact::XFLArithmeticProfile::none)
|
||||
{
|
||||
std::vector<std::uint8_t> result(
|
||||
hook::artifact::quickJSHeaderSize + payload.size(), 0);
|
||||
std::copy(
|
||||
hook::artifact::quickJSMagic.begin(),
|
||||
hook::artifact::quickJSMagic.end(),
|
||||
result.begin());
|
||||
result[4] = hook::artifact::quickJSEnvelopeVersion;
|
||||
result[5] = hook::artifact::quickJSBytecodeKind;
|
||||
result[6] = 0;
|
||||
result[7] = hook::artifact::quickJSHeaderSize;
|
||||
result[8] = 0;
|
||||
result[9] = 1;
|
||||
auto const profileCode = xflProfileCode(profile);
|
||||
result[10] = static_cast<std::uint8_t>(profileCode >> 8);
|
||||
result[11] = static_cast<std::uint8_t>(profileCode);
|
||||
auto const length = static_cast<std::uint32_t>(payload.size());
|
||||
result[12] = static_cast<std::uint8_t>(length >> 24);
|
||||
result[13] = static_cast<std::uint8_t>(length >> 16);
|
||||
result[14] = static_cast<std::uint8_t>(length >> 8);
|
||||
result[15] = static_cast<std::uint8_t>(length);
|
||||
std::copy(
|
||||
hook::artifact::quickJSBytecodeABI.begin(),
|
||||
hook::artifact::quickJSBytecodeABI.end(),
|
||||
result.begin() + 16);
|
||||
std::copy(
|
||||
hook::artifact::quickJSRuntimeProfile.begin(),
|
||||
hook::artifact::quickJSRuntimeProfile.end(),
|
||||
result.begin() + 48);
|
||||
std::copy(payload.begin(), payload.end(), result.begin() + 80);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class HookArtifact_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testcase("Legacy Wasm classification");
|
||||
std::array<std::uint8_t, 8> const wasm = {
|
||||
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00};
|
||||
auto legacy = hook::artifact::parse(makeSlice(wasm));
|
||||
BEAST_EXPECT(legacy);
|
||||
if (legacy)
|
||||
{
|
||||
BEAST_EXPECT(legacy->kind == hook::artifact::Kind::legacyWasm);
|
||||
BEAST_EXPECT(legacy->hookApiVersion == 0);
|
||||
BEAST_EXPECT(legacy->payload == makeSlice(wasm));
|
||||
}
|
||||
|
||||
testcase("QuickJS v1 wire contract");
|
||||
auto encoded = quickJSArtifact();
|
||||
auto parsed = hook::artifact::parse(makeSlice(encoded));
|
||||
BEAST_EXPECT(parsed);
|
||||
if (parsed)
|
||||
{
|
||||
BEAST_EXPECT(parsed->kind == hook::artifact::Kind::quickJSBytecode);
|
||||
BEAST_EXPECT(parsed->hookApiVersion == 1);
|
||||
BEAST_EXPECT(
|
||||
parsed->xflArithmeticProfile ==
|
||||
hook::artifact::XFLArithmeticProfile::none);
|
||||
BEAST_EXPECT(hook::artifact::isCurrentQuickJS(*parsed));
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::quickJSSerializedObjectMaxBytes == 1'048'576);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::quickJSSerializedObjectMaxFields == 32'768);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::quickJSSerializedObjectMaxScopes == 32'769);
|
||||
BEAST_EXPECT(hook::artifact::quickJSSerializedObjectMaxDepth == 10);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::quickJSProviderSize ==
|
||||
hook::artifact::generated::providerSize);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::providerProduct == "provider");
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::providerManifestSHA256.size() == 64);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::nativeABISHA256.size() == 64);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::providerImports.size() == 28);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::providerExports.size() == 22);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::providerExportSignatures.size() ==
|
||||
22);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::quickJSProviderMemoryMinimumPages == 8);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::quickJSProviderMemoryMaximumPages == 512);
|
||||
BEAST_EXPECT(!hook::artifact::generated::providerMemory64);
|
||||
BEAST_EXPECT(!hook::artifact::generated::providerMemoryShared);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::quickJSProviderWasmStackBytes == 131'072);
|
||||
BEAST_EXPECT(hook::artifact::generated::wasmStackBytes == 131'072);
|
||||
constexpr auto exportCount = sizeof(expectedProviderExports) /
|
||||
sizeof(expectedProviderExports[0]);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::providerExportSignatures.size() ==
|
||||
exportCount);
|
||||
if (hook::artifact::generated::providerExportSignatures.size() ==
|
||||
exportCount)
|
||||
{
|
||||
for (std::size_t index = 0; index < exportCount; ++index)
|
||||
{
|
||||
auto const& actual = hook::artifact::generated::
|
||||
providerExportSignatures[index];
|
||||
auto const& expected = expectedProviderExports[index];
|
||||
BEAST_EXPECT(actual.kind == expected.kind);
|
||||
BEAST_EXPECT(actual.name == expected.name);
|
||||
BEAST_EXPECT(actual.parameters == expected.parameters);
|
||||
BEAST_EXPECT(actual.results == expected.results);
|
||||
BEAST_EXPECT(actual.minimumPages == expected.minimumPages);
|
||||
BEAST_EXPECT(actual.maximumPages == expected.maximumPages);
|
||||
BEAST_EXPECT(actual.memory64 == expected.memory64);
|
||||
BEAST_EXPECT(actual.shared == expected.shared);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::providerExports[index] ==
|
||||
expected.name);
|
||||
}
|
||||
}
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::javascriptBroadDeclarationSHA256
|
||||
.size() == 64);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::javascriptExactV1DeclarationSHA256
|
||||
.size() == 64);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::javascriptSurfaceSHA256.size() ==
|
||||
64);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::javascriptXFLProfileLedgerSHA256
|
||||
.size() == 64);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::javascriptAPIArtifactManifestSHA256
|
||||
.size() == 64);
|
||||
BEAST_EXPECT(hook::artifact::generated::xqjsEnvelopeVersion == 1);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::quickJSEnvelopeVersion ==
|
||||
hook::artifact::generated::xqjsEnvelopeVersion);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::xflArithmeticProfileNone == 0);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::xflArithmeticProfileXahauFloatV1 ==
|
||||
1);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::xflArithmeticProfileNearestEvenV1 ==
|
||||
2);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::moduleValidationLayoutVersion == 1);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::moduleValidationFailureSentinel ==
|
||||
-1);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::moduleValidationMainBit == 1);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::moduleValidationCallbackBit == 2);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::moduleValidationEntryMask == 3);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::moduleValidationReservedMask ==
|
||||
0x800000FCU);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::moduleValidationProfileMask ==
|
||||
0x00FFFF00U);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::moduleValidationProfileShift == 8);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::moduleValidationVersionMask ==
|
||||
0x7F000000U);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::moduleValidationVersionShift == 24);
|
||||
BEAST_EXPECT(parsed->payload.size() == 3);
|
||||
BEAST_EXPECT(parsed->payload[0] == 'a');
|
||||
}
|
||||
|
||||
for (auto const profile :
|
||||
{hook::artifact::XFLArithmeticProfile::none,
|
||||
hook::artifact::XFLArithmeticProfile::xahauFloatV1,
|
||||
hook::artifact::XFLArithmeticProfile::nearestEvenV1})
|
||||
{
|
||||
auto const profiled = quickJSArtifact({'x'}, profile);
|
||||
auto const profiledView =
|
||||
hook::artifact::parse(makeSlice(profiled));
|
||||
BEAST_EXPECT(profiledView);
|
||||
if (profiledView)
|
||||
BEAST_EXPECT(profiledView->xflArithmeticProfile == profile);
|
||||
}
|
||||
|
||||
testcase("Kind-aware CreateCode size limits");
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::maxLegacyCreateCodeSize == hook::maxHookWasmSize());
|
||||
BEAST_EXPECT(hook::artifact::maxQuickJSCreateCodeSize == 128U * 1024U);
|
||||
|
||||
std::vector<std::uint8_t> wasmBoundary(
|
||||
hook::artifact::maxLegacyCreateCodeSize, 0);
|
||||
std::copy_n(wasm.begin(), 4, wasmBoundary.begin());
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::fitsCreateCodeSizeLimit(makeSlice(wasmBoundary)));
|
||||
auto const parsedWasmBoundary =
|
||||
hook::artifact::parse(makeSlice(wasmBoundary));
|
||||
BEAST_EXPECT(parsedWasmBoundary);
|
||||
if (parsedWasmBoundary)
|
||||
BEAST_EXPECT(
|
||||
parsedWasmBoundary->kind == hook::artifact::Kind::legacyWasm);
|
||||
|
||||
auto wasmTooLarge = wasmBoundary;
|
||||
wasmTooLarge.push_back(0);
|
||||
BEAST_EXPECT(
|
||||
!hook::artifact::fitsCreateCodeSizeLimit(makeSlice(wasmTooLarge)));
|
||||
auto const parsedWasmTooLarge =
|
||||
hook::artifact::parse(makeSlice(wasmTooLarge));
|
||||
BEAST_EXPECT(!parsedWasmTooLarge);
|
||||
if (!parsedWasmTooLarge)
|
||||
BEAST_EXPECT(
|
||||
parsedWasmTooLarge.error() == hook::artifact::Error::tooLarge);
|
||||
|
||||
auto quickJSBoundary = quickJSArtifact(std::vector<std::uint8_t>(
|
||||
hook::artifact::maxQuickJSCreateCodeSize -
|
||||
hook::artifact::quickJSHeaderSize,
|
||||
0));
|
||||
BEAST_EXPECT(
|
||||
quickJSBoundary.size() == hook::artifact::maxQuickJSCreateCodeSize);
|
||||
BEAST_EXPECT(hook::artifact::fitsCreateCodeSizeLimit(
|
||||
makeSlice(quickJSBoundary)));
|
||||
auto const parsedQuickJSBoundary =
|
||||
hook::artifact::parse(makeSlice(quickJSBoundary));
|
||||
BEAST_EXPECT(parsedQuickJSBoundary);
|
||||
if (parsedQuickJSBoundary)
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
parsedQuickJSBoundary->kind ==
|
||||
hook::artifact::Kind::quickJSBytecode);
|
||||
BEAST_EXPECT(
|
||||
parsedQuickJSBoundary->payload.size() ==
|
||||
hook::artifact::maxQuickJSCreateCodeSize -
|
||||
hook::artifact::quickJSHeaderSize);
|
||||
}
|
||||
|
||||
auto quickJSTooLarge = quickJSBoundary;
|
||||
quickJSTooLarge.push_back(0);
|
||||
BEAST_EXPECT(!hook::artifact::fitsCreateCodeSizeLimit(
|
||||
makeSlice(quickJSTooLarge)));
|
||||
auto const parsedQuickJSTooLarge =
|
||||
hook::artifact::parse(makeSlice(quickJSTooLarge));
|
||||
BEAST_EXPECT(!parsedQuickJSTooLarge);
|
||||
if (!parsedQuickJSTooLarge)
|
||||
BEAST_EXPECT(
|
||||
parsedQuickJSTooLarge.error() ==
|
||||
hook::artifact::Error::tooLarge);
|
||||
|
||||
std::vector<std::uint8_t> malformedNonXQJS(
|
||||
hook::artifact::maxLegacyCreateCodeSize + 1, 0xA5U);
|
||||
BEAST_EXPECT(!hook::artifact::fitsCreateCodeSizeLimit(
|
||||
makeSlice(malformedNonXQJS)));
|
||||
auto const parsedMalformedNonXQJS =
|
||||
hook::artifact::parse(makeSlice(malformedNonXQJS));
|
||||
BEAST_EXPECT(!parsedMalformedNonXQJS);
|
||||
if (!parsedMalformedNonXQJS)
|
||||
BEAST_EXPECT(
|
||||
parsedMalformedNonXQJS.error() ==
|
||||
hook::artifact::Error::tooLarge);
|
||||
|
||||
testcase("QuickJS v1 corruption rejection");
|
||||
auto expectError = [this](
|
||||
std::vector<std::uint8_t> bytes,
|
||||
hook::artifact::Error expected) {
|
||||
auto result = hook::artifact::parse(makeSlice(bytes));
|
||||
BEAST_EXPECT(!result);
|
||||
if (!result)
|
||||
BEAST_EXPECT(result.error() == expected);
|
||||
};
|
||||
|
||||
auto corrupted = encoded;
|
||||
corrupted[4] = 0;
|
||||
expectError(
|
||||
corrupted, hook::artifact::Error::unsupportedEnvelopeVersion);
|
||||
corrupted = encoded;
|
||||
corrupted[4] = 2;
|
||||
expectError(
|
||||
corrupted, hook::artifact::Error::unsupportedEnvelopeVersion);
|
||||
corrupted = encoded;
|
||||
corrupted[5] = 2;
|
||||
expectError(corrupted, hook::artifact::Error::unsupportedArtifactKind);
|
||||
corrupted = encoded;
|
||||
corrupted[7] = 81;
|
||||
expectError(corrupted, hook::artifact::Error::nonCanonicalHeaderSize);
|
||||
corrupted = encoded;
|
||||
corrupted[11] = 3;
|
||||
expectError(
|
||||
corrupted, hook::artifact::Error::unsupportedXFLArithmeticProfile);
|
||||
corrupted = encoded;
|
||||
corrupted[10] = 1;
|
||||
expectError(
|
||||
corrupted, hook::artifact::Error::unsupportedXFLArithmeticProfile);
|
||||
corrupted = encoded;
|
||||
corrupted[10] = 0xff;
|
||||
corrupted[11] = 0xff;
|
||||
expectError(
|
||||
corrupted, hook::artifact::Error::unsupportedXFLArithmeticProfile);
|
||||
corrupted = encoded;
|
||||
corrupted[15] = 4;
|
||||
expectError(corrupted, hook::artifact::Error::lengthMismatch);
|
||||
corrupted = encoded;
|
||||
std::fill(corrupted.begin() + 16, corrupted.begin() + 48, 0);
|
||||
expectError(corrupted, hook::artifact::Error::zeroBytecodeABI);
|
||||
corrupted = encoded;
|
||||
std::fill(corrupted.begin() + 48, corrupted.begin() + 80, 0);
|
||||
expectError(corrupted, hook::artifact::Error::zeroRuntimeProfile);
|
||||
|
||||
testcase("QuickJS module validation result ABI");
|
||||
struct LegalValidationWord
|
||||
{
|
||||
std::int32_t word;
|
||||
bool hasCallback;
|
||||
hook::artifact::XFLArithmeticProfile profile;
|
||||
};
|
||||
constexpr LegalValidationWord legalWords[] = {
|
||||
{0x01000001, false, hook::artifact::XFLArithmeticProfile::none},
|
||||
{0x01000003, true, hook::artifact::XFLArithmeticProfile::none},
|
||||
{0x01000101,
|
||||
false,
|
||||
hook::artifact::XFLArithmeticProfile::xahauFloatV1},
|
||||
{0x01000103,
|
||||
true,
|
||||
hook::artifact::XFLArithmeticProfile::xahauFloatV1},
|
||||
{0x01000201,
|
||||
false,
|
||||
hook::artifact::XFLArithmeticProfile::nearestEvenV1},
|
||||
{0x01000203,
|
||||
true,
|
||||
hook::artifact::XFLArithmeticProfile::nearestEvenV1},
|
||||
};
|
||||
for (auto const& expected : legalWords)
|
||||
{
|
||||
hook::QuickJSModuleValidation validation;
|
||||
auto const error = hook::decodeQuickJSModuleValidationForTests(
|
||||
expected.word, validation);
|
||||
BEAST_EXPECT(!error);
|
||||
BEAST_EXPECT(validation.hasCallback == expected.hasCallback);
|
||||
BEAST_EXPECT(validation.xflArithmeticProfile == expected.profile);
|
||||
}
|
||||
|
||||
constexpr std::int32_t malformedWords[] = {
|
||||
-2,
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
3,
|
||||
0x00000001,
|
||||
0x02000001,
|
||||
0x01000000,
|
||||
0x01000002,
|
||||
0x01000005,
|
||||
0x01000041,
|
||||
0x01000301,
|
||||
0x0100FF01,
|
||||
0x01010001,
|
||||
0x017FFF01,
|
||||
static_cast<std::int32_t>(0x81000001U),
|
||||
};
|
||||
for (auto const word : malformedWords)
|
||||
{
|
||||
hook::QuickJSModuleValidation validation{
|
||||
.hasCallback = true,
|
||||
.xflArithmeticProfile =
|
||||
hook::artifact::XFLArithmeticProfile::nearestEvenV1};
|
||||
auto const error =
|
||||
hook::decodeQuickJSModuleValidationForTests(word, validation);
|
||||
BEAST_EXPECT(error);
|
||||
BEAST_EXPECT(!validation.hasCallback);
|
||||
BEAST_EXPECT(
|
||||
validation.xflArithmeticProfile ==
|
||||
hook::artifact::XFLArithmeticProfile::none);
|
||||
}
|
||||
|
||||
testcase("Raw QuickJS bytecode is not deployable");
|
||||
std::vector<std::uint8_t> rawBytecode = {0x05, 0x0D, 0x14, 0x3C};
|
||||
auto raw = hook::artifact::parse(makeSlice(rawBytecode));
|
||||
BEAST_EXPECT(!raw);
|
||||
if (!raw)
|
||||
BEAST_EXPECT(raw.error() == hook::artifact::Error::unknownMagic);
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(HookArtifact, app, ripple);
|
||||
|
||||
} // namespace ripple::test
|
||||
135
src/test/app/HookGuestMemory_test.cpp
Normal file
135
src/test/app/HookGuestMemory_test.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
#include <xrpld/app/hook/HookGuestMemory.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ripple::test {
|
||||
|
||||
class HookGuestMemory_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testcase("Checked guest spans");
|
||||
std::array<std::uint8_t, 8> bytes{};
|
||||
hook::HookGuestMemory memory{bytes.data(), bytes.size()};
|
||||
|
||||
BEAST_EXPECT(memory.valid());
|
||||
BEAST_EXPECT(memory.data() == bytes.data());
|
||||
BEAST_EXPECT(memory.contains(0, 0));
|
||||
BEAST_EXPECT(memory.contains(7, 1));
|
||||
BEAST_EXPECT(!memory.contains(8, 0));
|
||||
BEAST_EXPECT(!memory.contains(9, 0));
|
||||
BEAST_EXPECT(!memory.contains(7, 2));
|
||||
BEAST_EXPECT(!memory.contains(UINT32_MAX, 2));
|
||||
BEAST_EXPECT(!!memory.read(7, 1));
|
||||
BEAST_EXPECT(!memory.read(8, 0));
|
||||
BEAST_EXPECT(!!memory.write(7, 1));
|
||||
BEAST_EXPECT(!memory.write(8, 0));
|
||||
|
||||
auto writable = memory.write(2, 3);
|
||||
BEAST_EXPECT(!!writable);
|
||||
if (writable)
|
||||
{
|
||||
(*writable)[0] = 1;
|
||||
(*writable)[1] = 2;
|
||||
(*writable)[2] = 3;
|
||||
}
|
||||
auto readable = memory.read(2, 3);
|
||||
BEAST_EXPECT(!!readable);
|
||||
if (readable)
|
||||
BEAST_EXPECT((*readable)[0] == 1 && (*readable)[2] == 3);
|
||||
|
||||
auto const before = bytes;
|
||||
auto rejected = memory.write(7, 2);
|
||||
BEAST_EXPECT(!rejected);
|
||||
BEAST_EXPECT(bytes == before);
|
||||
|
||||
testcase("Compatibility copy");
|
||||
std::array<std::uint8_t, 2> source{9, 8};
|
||||
BEAST_EXPECT(memory.compatibilityCopy(
|
||||
6, std::span<std::uint8_t const>{source.data(), source.size()}));
|
||||
BEAST_EXPECT(bytes[6] == 9 && bytes[7] == 8);
|
||||
BEAST_EXPECT(memory.compatibilityCopy(
|
||||
8, std::span<std::uint8_t const>{source.data(), 0}));
|
||||
BEAST_EXPECT(
|
||||
memory.compatibilityCopy(8, std::span<std::uint8_t const>{}));
|
||||
BEAST_EXPECT(!memory.compatibilityCopy(
|
||||
9, std::span<std::uint8_t const>{source.data(), 0}));
|
||||
auto const copied = bytes;
|
||||
BEAST_EXPECT(!memory.compatibilityCopy(
|
||||
7, std::span<std::uint8_t const>{source.data(), source.size()}));
|
||||
BEAST_EXPECT(!memory.compatibilityCopy(
|
||||
UINT32_MAX,
|
||||
std::span<std::uint8_t const>{source.data(), source.size()}));
|
||||
BEAST_EXPECT(bytes == copied);
|
||||
|
||||
hook::HookGuestMemory empty{nullptr, 0};
|
||||
BEAST_EXPECT(!empty.valid());
|
||||
BEAST_EXPECT(!empty.compatibilityCopy(
|
||||
0, std::span<std::uint8_t const>{source.data(), 0}));
|
||||
|
||||
testcase("Compatibility writer");
|
||||
struct WriterState
|
||||
{
|
||||
bool called = false;
|
||||
bool succeed = false;
|
||||
} writerState;
|
||||
auto const writer = [](void* context,
|
||||
std::uint32_t,
|
||||
std::span<std::uint8_t const>) noexcept {
|
||||
auto& state = *static_cast<WriterState*>(context);
|
||||
state.called = true;
|
||||
return state.succeed;
|
||||
};
|
||||
hook::HookGuestMemory writerMemory{
|
||||
bytes.data(), bytes.size(), &writerState, writer};
|
||||
BEAST_EXPECT(!writerMemory.compatibilityCopy(
|
||||
0, std::span<std::uint8_t const>{source.data(), source.size()}));
|
||||
BEAST_EXPECT(writerState.called);
|
||||
writerState.called = false;
|
||||
writerState.succeed = true;
|
||||
BEAST_EXPECT(
|
||||
writerMemory.compatibilityCopy(8, std::span<std::uint8_t const>{}));
|
||||
BEAST_EXPECT(writerState.called);
|
||||
|
||||
testcase("Deferred memory resolution");
|
||||
struct ResolverState
|
||||
{
|
||||
std::uint8_t* data;
|
||||
std::size_t size;
|
||||
std::size_t calls = 0;
|
||||
} resolverState{bytes.data(), bytes.size()};
|
||||
auto const resolver =
|
||||
[](void* context,
|
||||
std::uint8_t*& data,
|
||||
std::size_t& size,
|
||||
void*&,
|
||||
hook::HookGuestMemory::CompatibilityWriter&) noexcept {
|
||||
auto& state = *static_cast<ResolverState*>(context);
|
||||
++state.calls;
|
||||
data = state.data;
|
||||
size = state.size;
|
||||
};
|
||||
hook::HookGuestMemory deferred{
|
||||
&resolverState,
|
||||
resolver,
|
||||
hook::HookGuestMemory::WriteContract::legacyActualSize};
|
||||
BEAST_EXPECT(resolverState.calls == 0);
|
||||
BEAST_EXPECT(
|
||||
deferred.writeContract() ==
|
||||
hook::HookGuestMemory::WriteContract::legacyActualSize);
|
||||
BEAST_EXPECT(resolverState.calls == 0);
|
||||
deferred.resolve();
|
||||
BEAST_EXPECT(resolverState.calls == 1);
|
||||
BEAST_EXPECT(deferred.valid());
|
||||
BEAST_EXPECT(deferred.data() == bytes.data());
|
||||
BEAST_EXPECT(deferred.size() == bytes.size());
|
||||
BEAST_EXPECT(resolverState.calls == 1);
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(HookGuestMemory, app, ripple);
|
||||
|
||||
} // namespace ripple::test
|
||||
316
src/test/app/HookWasmtimeHostCatalogue_test.cpp
Normal file
316
src/test/app/HookWasmtimeHostCatalogue_test.cpp
Normal file
@@ -0,0 +1,316 @@
|
||||
#include <test/jtx.h>
|
||||
#include <test/jtx/hook.h>
|
||||
#include <xrpld/app/hook/QuickJSHookRuntime.h>
|
||||
#include <xrpld/app/hook/detail/quickjs/QuickJSHostCall.h>
|
||||
#include <xrpld/app/hook/detail/quickjs/QuickJSLinker.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include "jshooks/raw-catalogue/catalogue_wasm.h"
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple::test {
|
||||
namespace {
|
||||
|
||||
using EnginePtr = std::unique_ptr<wasm_engine_t, decltype(&wasm_engine_delete)>;
|
||||
using LinkerPtr =
|
||||
std::unique_ptr<wasmtime_linker_t, decltype(&wasmtime_linker_delete)>;
|
||||
using ModulePtr =
|
||||
std::unique_ptr<wasmtime_module_t, decltype(&wasmtime_module_delete)>;
|
||||
using StorePtr =
|
||||
std::unique_ptr<wasmtime_store_t, decltype(&wasmtime_store_delete)>;
|
||||
|
||||
std::string
|
||||
takeError(wasmtime_error_t* error)
|
||||
{
|
||||
wasm_name_t message;
|
||||
wasmtime_error_message(error, &message);
|
||||
std::string result{message.data, message.size};
|
||||
wasm_byte_vec_delete(&message);
|
||||
wasmtime_error_delete(error);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string
|
||||
takeTrap(wasm_trap_t* trap)
|
||||
{
|
||||
wasm_name_t message;
|
||||
wasm_trap_message(trap, &message);
|
||||
std::string result{message.data, message.size};
|
||||
wasm_byte_vec_delete(&message);
|
||||
wasm_trap_delete(trap);
|
||||
return result;
|
||||
}
|
||||
|
||||
EnginePtr
|
||||
makeEngine()
|
||||
{
|
||||
auto* configuration = wasm_config_new();
|
||||
if (!configuration)
|
||||
return {nullptr, &wasm_engine_delete};
|
||||
wasmtime_config_cranelift_nan_canonicalization_set(configuration, true);
|
||||
wasmtime_config_wasm_threads_set(configuration, false);
|
||||
wasmtime_config_wasm_relaxed_simd_set(configuration, false);
|
||||
wasmtime_config_wasm_memory64_set(configuration, false);
|
||||
wasmtime_config_wasm_multi_memory_set(configuration, false);
|
||||
wasmtime_config_wasm_tail_call_set(configuration, false);
|
||||
return {wasm_engine_new_with_config(configuration), &wasm_engine_delete};
|
||||
}
|
||||
|
||||
std::vector<hook::quickjs::WasmtimeHostBinding>
|
||||
allEnabledBindings()
|
||||
{
|
||||
using namespace hook;
|
||||
using namespace hook::quickjs;
|
||||
std::vector<WasmtimeHostBinding> result;
|
||||
result.reserve(hookHostFunctionCatalogue().size());
|
||||
for (auto const& operation : hookHostFunctionCatalogue())
|
||||
{
|
||||
std::array<HookHostValueKind, maxImportParameters> parameters{};
|
||||
std::copy(
|
||||
operation.parameters.begin(),
|
||||
operation.parameters.end(),
|
||||
parameters.begin());
|
||||
result.push_back(
|
||||
{.module = "env",
|
||||
.name = operation.name,
|
||||
.parameters = parameters,
|
||||
.parameterCount =
|
||||
static_cast<std::uint8_t>(operation.parameters.size()),
|
||||
.result = operation.result,
|
||||
.implementationVersion = operation.implementationVersion,
|
||||
.operation = &operation,
|
||||
.amendment = {},
|
||||
.measure = HostWorkMeasureKind::zeroV1,
|
||||
.terminal = operation.name == "accept" ||
|
||||
operation.name == "rollback" || operation.name == "_g"
|
||||
? TerminalBehavior::hookTerminal
|
||||
: TerminalBehavior::ordinaryStatus,
|
||||
.charging = WasmtimeHostBinding::Charging::none});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class HookWasmtimeHostCatalogue_test : public beast::unit_test::suite
|
||||
{
|
||||
ApplyContext
|
||||
makeApplyContext(jtx::Env& env, OpenView& view, STTx const& transaction)
|
||||
{
|
||||
return ApplyContext{
|
||||
env.app(),
|
||||
view,
|
||||
transaction,
|
||||
tesSUCCESS,
|
||||
env.current()->fees().base,
|
||||
tapNONE,
|
||||
env.journal};
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
using namespace hook;
|
||||
using namespace hook::quickjs;
|
||||
using namespace jtx;
|
||||
using namespace raw_hook_catalogue_fixture;
|
||||
|
||||
testcase("All 75 independent imports bind one generic callback");
|
||||
auto engine = makeEngine();
|
||||
BEAST_EXPECT(!!engine);
|
||||
if (!engine)
|
||||
return;
|
||||
|
||||
wasmtime_module_t* rawModule = nullptr;
|
||||
auto* compileError = wasmtime_module_new(
|
||||
engine.get(), wasm.data(), wasm.size(), &rawModule);
|
||||
if (compileError)
|
||||
{
|
||||
BEAST_EXPECTS(false, takeError(compileError));
|
||||
return;
|
||||
}
|
||||
BEAST_EXPECT(true);
|
||||
ModulePtr module{rawModule, &wasmtime_module_delete};
|
||||
|
||||
auto bindings = allEnabledBindings();
|
||||
BEAST_EXPECT(bindings.size() == 75);
|
||||
QuickJSHostAdapterPolicy const testPolicy{
|
||||
.id = "all-host-functions-test-only",
|
||||
.hostWorkMeter = "none",
|
||||
.chargeOrder = HostChargeOrder::amendmentBeforeCharge,
|
||||
.debitBehavior = HostDebitBehavior::saturatedBasePlusAddressedBytes,
|
||||
.imports = bindings};
|
||||
BEAST_EXPECT(testPolicy.complete());
|
||||
|
||||
LinkerPtr linker{
|
||||
wasmtime_linker_new(engine.get()), &wasmtime_linker_delete};
|
||||
BEAST_EXPECT(!!linker);
|
||||
if (!linker)
|
||||
return;
|
||||
std::string linkError;
|
||||
auto const linked = defineImports(linker.get(), testPolicy, linkError);
|
||||
BEAST_EXPECTS(linked, linkError);
|
||||
if (!linked)
|
||||
return;
|
||||
|
||||
Env env{*this, supported_amendments()};
|
||||
auto const alice = Account{"alice"};
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
STTx transaction{ttINVOKE, [&](STObject& object) {
|
||||
object[sfAccount] = alice.id();
|
||||
}};
|
||||
OpenView view{*env.current()};
|
||||
auto applyContext = makeApplyContext(env, view, transaction);
|
||||
auto const profile = currentQuickJSRuntimeProfile();
|
||||
|
||||
BEAST_EXPECT(names.size() == bindings.size());
|
||||
for (auto const name : names)
|
||||
{
|
||||
HookStateMap state;
|
||||
auto hookContext = makeStubHookContext(
|
||||
applyContext,
|
||||
alice.id(),
|
||||
alice.id(),
|
||||
{.expected_etxn_count = 1},
|
||||
state);
|
||||
QuickJSInvocation invocation{hookContext, profile};
|
||||
StorePtr store{
|
||||
wasmtime_store_new(engine.get(), &invocation, nullptr),
|
||||
&wasmtime_store_delete};
|
||||
BEAST_EXPECTS(!!store, std::string{name});
|
||||
if (!store)
|
||||
continue;
|
||||
auto* context = wasmtime_store_context(store.get());
|
||||
|
||||
wasmtime_instance_t instance;
|
||||
wasm_trap_t* instantiateTrap = nullptr;
|
||||
auto* instantiateError = wasmtime_linker_instantiate(
|
||||
linker.get(),
|
||||
context,
|
||||
module.get(),
|
||||
&instance,
|
||||
&instantiateTrap);
|
||||
auto const instantiateFailed =
|
||||
instantiateError != nullptr || instantiateTrap != nullptr;
|
||||
auto const instantiateDetail = instantiateError
|
||||
? takeError(instantiateError)
|
||||
: instantiateTrap ? takeTrap(instantiateTrap)
|
||||
: std::string{};
|
||||
BEAST_EXPECTS(
|
||||
!instantiateFailed,
|
||||
std::string{name} + ": " + instantiateDetail);
|
||||
if (instantiateFailed)
|
||||
continue;
|
||||
|
||||
auto const exportName = "call_" + std::string{name};
|
||||
wasmtime_extern_t entry;
|
||||
auto const found = wasmtime_instance_export_get(
|
||||
context,
|
||||
&instance,
|
||||
exportName.data(),
|
||||
exportName.size(),
|
||||
&entry);
|
||||
BEAST_EXPECTS(
|
||||
found && entry.kind == WASMTIME_EXTERN_FUNC, exportName);
|
||||
if (!found || entry.kind != WASMTIME_EXTERN_FUNC)
|
||||
continue;
|
||||
|
||||
wasmtime_val_t result{};
|
||||
wasm_trap_t* callTrap = nullptr;
|
||||
auto* callError = wasmtime_func_call(
|
||||
context, &entry.of.func, nullptr, 0, &result, 1, &callTrap);
|
||||
auto const terminal = name == "accept" || name == "rollback";
|
||||
auto const hadCallError = callError != nullptr;
|
||||
auto const hadCallTrap = callTrap != nullptr;
|
||||
auto const callDetail = callError ? takeError(callError)
|
||||
: callTrap ? takeTrap(callTrap)
|
||||
: std::string{};
|
||||
BEAST_EXPECTS(
|
||||
terminal ? invocation.terminal && (hadCallError || hadCallTrap)
|
||||
: !hadCallError && !hadCallTrap,
|
||||
std::string{name} + ": " + callDetail);
|
||||
if (terminal)
|
||||
BEAST_EXPECT(
|
||||
hookContext.result.exitType ==
|
||||
(name == "accept" ? hook_api::ExitType::ACCEPT
|
||||
: hook_api::ExitType::ROLLBACK));
|
||||
|
||||
if (!terminal && !hadCallError && !hadCallTrap && name == "_g")
|
||||
{
|
||||
BEAST_EXPECT(result.kind == WASMTIME_I32);
|
||||
BEAST_EXPECT(result.of.i32 == 1);
|
||||
}
|
||||
if (!terminal && !hadCallError && !hadCallTrap && name == "state")
|
||||
{
|
||||
BEAST_EXPECT(result.kind == WASMTIME_I64);
|
||||
BEAST_EXPECT(
|
||||
result.of.i64 ==
|
||||
static_cast<std::int64_t>(
|
||||
hook_api::hook_return_code::TOO_SMALL));
|
||||
}
|
||||
if (!terminal && !hadCallError && !hadCallTrap && name == "otxn_id")
|
||||
{
|
||||
BEAST_EXPECT(result.kind == WASMTIME_I64);
|
||||
BEAST_EXPECT(
|
||||
result.of.i64 ==
|
||||
static_cast<std::int64_t>(
|
||||
hook_api::hook_return_code::TOO_SMALL));
|
||||
}
|
||||
|
||||
if (name == "_g" && !hadCallError && !hadCallTrap)
|
||||
{
|
||||
constexpr std::string_view guardViolation = "call__g_rollback";
|
||||
wasmtime_extern_t violationEntry;
|
||||
auto const violationFound = wasmtime_instance_export_get(
|
||||
context,
|
||||
&instance,
|
||||
guardViolation.data(),
|
||||
guardViolation.size(),
|
||||
&violationEntry);
|
||||
BEAST_EXPECT(
|
||||
violationFound &&
|
||||
violationEntry.kind == WASMTIME_EXTERN_FUNC);
|
||||
if (!violationFound ||
|
||||
violationEntry.kind != WASMTIME_EXTERN_FUNC)
|
||||
continue;
|
||||
|
||||
wasmtime_val_t violationResult{};
|
||||
wasm_trap_t* violationTrap = nullptr;
|
||||
auto* violationError = wasmtime_func_call(
|
||||
context,
|
||||
&violationEntry.of.func,
|
||||
nullptr,
|
||||
0,
|
||||
&violationResult,
|
||||
1,
|
||||
&violationTrap);
|
||||
auto const hadViolationError = violationError != nullptr;
|
||||
auto const hadViolationTrap = violationTrap != nullptr;
|
||||
auto const violationDetail = violationError
|
||||
? takeError(violationError)
|
||||
: violationTrap ? takeTrap(violationTrap)
|
||||
: std::string{};
|
||||
BEAST_EXPECTS(
|
||||
invocation.terminal &&
|
||||
(hadViolationError || hadViolationTrap),
|
||||
violationDetail);
|
||||
BEAST_EXPECT(
|
||||
hookContext.result.exitType ==
|
||||
hook_api::ExitType::ROLLBACK);
|
||||
BEAST_EXPECT(
|
||||
hookContext.result.exitCode ==
|
||||
static_cast<std::int64_t>(
|
||||
hook_api::hook_return_code::GUARD_VIOLATION));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(HookWasmtimeHostCatalogue, app, ripple);
|
||||
|
||||
} // namespace ripple::test
|
||||
224
src/test/app/JSHooks_bypass_test_hook.h
Normal file
224
src/test/app/JSHooks_bypass_test_hook.h
Normal file
@@ -0,0 +1,224 @@
|
||||
// Generated by bin/generate-jshooks-bypass-fixture; do not edit.
|
||||
// clang-format off
|
||||
// Source SHA-256: 5b9a8ad2b527e241bb14826fc69d3ea14107f45e4043eb5cabfe557cebe89165
|
||||
// Provider SHA-256: 104eeea1db4d2c3fc80282bfb460721c3cc830507eb64f8401df477f8fac41ee
|
||||
// Bytecode SHA-256: 78c75bb58738ba7b5b348c5b7b663f84abf123cf082ad2bf0275236d495baabe
|
||||
#ifndef JSHOOKS_BYPASS_TEST_HOOK_INCLUDED
|
||||
#define JSHOOKS_BYPASS_TEST_HOOK_INCLUDED
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ripple::test {
|
||||
|
||||
inline constexpr char jshooksBypassSourceSHA256[] = "5b9a8ad2b527e241bb14826fc69d3ea14107f45e4043eb5cabfe557cebe89165";
|
||||
inline constexpr char jshooksBypassProviderSHA256[] = "104eeea1db4d2c3fc80282bfb460721c3cc830507eb64f8401df477f8fac41ee";
|
||||
inline constexpr char jshooksBypassBytecodeSHA256[] = "78c75bb58738ba7b5b348c5b7b663f84abf123cf082ad2bf0275236d495baabe";
|
||||
inline constexpr std::array<std::uint8_t, 2411>
|
||||
jshooksNearestEvenBypassBytecode = {{
|
||||
0x05U, 0x26U, 0x14U, 0x3cU, 0x63U, 0x6fU, 0x6eU, 0x74U, 0x72U, 0x61U, 0x63U, 0x74U,
|
||||
0x3eU, 0x14U, 0x68U, 0x6fU, 0x6fU, 0x6bU, 0x43U, 0x6fU, 0x6eU, 0x66U, 0x69U, 0x67U,
|
||||
0x08U, 0x6dU, 0x61U, 0x69U, 0x6eU, 0x0eU, 0x64U, 0x65U, 0x63U, 0x69U, 0x6dU, 0x61U,
|
||||
0x6cU, 0x10U, 0x72U, 0x6fU, 0x6cU, 0x6cU, 0x62U, 0x61U, 0x63U, 0x6bU, 0x08U, 0x75U,
|
||||
0x74U, 0x69U, 0x6cU, 0x0cU, 0x53U, 0x54U, 0x42U, 0x6cU, 0x6fU, 0x62U, 0x0aU, 0x46U,
|
||||
0x69U, 0x65U, 0x6cU, 0x64U, 0x0aU, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x0cU, 0x61U,
|
||||
0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x20U, 0x64U, 0x65U, 0x66U, 0x69U, 0x6eU, 0x65U,
|
||||
0x48U, 0x6fU, 0x6fU, 0x6bU, 0x43U, 0x6fU, 0x6eU, 0x66U, 0x69U, 0x67U, 0x14U, 0x58U,
|
||||
0x46U, 0x4cU, 0x50U, 0x72U, 0x6fU, 0x66U, 0x69U, 0x6cU, 0x65U, 0x1aU, 0x6eU, 0x65U,
|
||||
0x61U, 0x72U, 0x65U, 0x73U, 0x74U, 0x45U, 0x76U, 0x65U, 0x6eU, 0x56U, 0x31U, 0x1aU,
|
||||
0x78U, 0x66U, 0x6cU, 0x41U, 0x72U, 0x69U, 0x74U, 0x68U, 0x6dU, 0x65U, 0x74U, 0x69U,
|
||||
0x63U, 0x06U, 0x68U, 0x65U, 0x78U, 0x1cU, 0x72U, 0x65U, 0x71U, 0x75U, 0x69U, 0x72U,
|
||||
0x65U, 0x50U, 0x72U, 0x65U, 0x73U, 0x65U, 0x6eU, 0x74U, 0x18U, 0x64U, 0x65U, 0x63U,
|
||||
0x6fU, 0x64U, 0x65U, 0x4fU, 0x62U, 0x6aU, 0x65U, 0x63U, 0x74U, 0x0eU, 0x66U, 0x72U,
|
||||
0x6fU, 0x6dU, 0x48U, 0x65U, 0x78U, 0x0cU, 0x41U, 0x6dU, 0x6fU, 0x75U, 0x6eU, 0x74U,
|
||||
0x1aU, 0x62U, 0x79U, 0x70U, 0x61U, 0x73U, 0x73U, 0x20U, 0x61U, 0x6dU, 0x6fU, 0x75U,
|
||||
0x6eU, 0x74U, 0x0aU, 0x74U, 0x6fU, 0x58U, 0x46U, 0x4cU, 0x12U, 0x5fU, 0x72U, 0x65U,
|
||||
0x73U, 0x65U, 0x72U, 0x76U, 0x65U, 0x64U, 0x08U, 0x6cU, 0x65U, 0x66U, 0x74U, 0x0aU,
|
||||
0x72U, 0x69U, 0x67U, 0x68U, 0x74U, 0x0eU, 0x66U, 0x61U, 0x69U, 0x6cU, 0x75U, 0x72U,
|
||||
0x65U, 0x10U, 0x72U, 0x65U, 0x74U, 0x75U, 0x72U, 0x6eU, 0x65U, 0x64U, 0x0aU, 0x65U,
|
||||
0x72U, 0x72U, 0x6fU, 0x72U, 0xc4U, 0x01U, 0x36U, 0x31U, 0x44U, 0x38U, 0x34U, 0x41U,
|
||||
0x38U, 0x41U, 0x46U, 0x41U, 0x38U, 0x44U, 0x34U, 0x30U, 0x39U, 0x36U, 0x31U, 0x33U,
|
||||
0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U,
|
||||
0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U,
|
||||
0x35U, 0x35U, 0x35U, 0x33U, 0x34U, 0x34U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U,
|
||||
0x30U, 0x30U, 0x30U, 0x30U, 0x42U, 0x35U, 0x46U, 0x37U, 0x36U, 0x32U, 0x37U, 0x39U,
|
||||
0x38U, 0x41U, 0x35U, 0x33U, 0x44U, 0x35U, 0x34U, 0x33U, 0x41U, 0x30U, 0x31U, 0x34U,
|
||||
0x43U, 0x41U, 0x46U, 0x38U, 0x42U, 0x32U, 0x39U, 0x37U, 0x43U, 0x46U, 0x46U, 0x38U,
|
||||
0x46U, 0x32U, 0x46U, 0x39U, 0x33U, 0x37U, 0x45U, 0x38U, 0xc4U, 0x01U, 0x36U, 0x31U,
|
||||
0x44U, 0x38U, 0x34U, 0x38U, 0x30U, 0x32U, 0x36U, 0x44U, 0x32U, 0x35U, 0x46U, 0x39U,
|
||||
0x41U, 0x38U, 0x37U, 0x36U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U,
|
||||
0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U,
|
||||
0x30U, 0x30U, 0x30U, 0x30U, 0x35U, 0x35U, 0x35U, 0x33U, 0x34U, 0x34U, 0x30U, 0x30U,
|
||||
0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x42U, 0x35U, 0x46U, 0x37U,
|
||||
0x36U, 0x32U, 0x37U, 0x39U, 0x38U, 0x41U, 0x35U, 0x33U, 0x44U, 0x35U, 0x34U, 0x33U,
|
||||
0x41U, 0x30U, 0x31U, 0x34U, 0x43U, 0x41U, 0x46U, 0x38U, 0x42U, 0x32U, 0x39U, 0x37U,
|
||||
0x43U, 0x46U, 0x46U, 0x38U, 0x46U, 0x32U, 0x46U, 0x39U, 0x33U, 0x37U, 0x45U, 0x38U,
|
||||
0x0cU, 0x64U, 0x69U, 0x76U, 0x69U, 0x64U, 0x65U, 0x0cU, 0x6fU, 0x6eU, 0x46U, 0x61U,
|
||||
0x69U, 0x6cU, 0x14U, 0x78U, 0x66U, 0x6cU, 0x2dU, 0x62U, 0x79U, 0x70U, 0x61U, 0x73U,
|
||||
0x73U, 0x22U, 0x75U, 0x6eU, 0x65U, 0x78U, 0x70U, 0x65U, 0x63U, 0x74U, 0x65U, 0x64U,
|
||||
0x2dU, 0x72U, 0x65U, 0x74U, 0x75U, 0x72U, 0x6eU, 0x38U, 0x62U, 0x79U, 0x70U, 0x61U,
|
||||
0x73U, 0x73U, 0x20U, 0x73U, 0x65U, 0x6eU, 0x74U, 0x69U, 0x6eU, 0x65U, 0x6cU, 0x20U,
|
||||
0x77U, 0x72U, 0x69U, 0x74U, 0x65U, 0x20U, 0x66U, 0x61U, 0x69U, 0x6cU, 0x65U, 0x64U,
|
||||
0x38U, 0x75U, 0x6eU, 0x65U, 0x78U, 0x70U, 0x65U, 0x63U, 0x74U, 0x65U, 0x64U, 0x20U,
|
||||
0x61U, 0x72U, 0x69U, 0x74U, 0x68U, 0x6dU, 0x65U, 0x74U, 0x69U, 0x63U, 0x20U, 0x72U,
|
||||
0x65U, 0x74U, 0x75U, 0x72U, 0x6eU, 0x9aU, 0x01U, 0x54U, 0x79U, 0x70U, 0x65U, 0x45U,
|
||||
0x72U, 0x72U, 0x6fU, 0x72U, 0x3aU, 0x20U, 0x58U, 0x46U, 0x4cU, 0x44U, 0x65U, 0x63U,
|
||||
0x69U, 0x6dU, 0x61U, 0x6cU, 0x2eU, 0x64U, 0x69U, 0x76U, 0x69U, 0x64U, 0x65U, 0x3aU,
|
||||
0x20U, 0x61U, 0x72U, 0x69U, 0x74U, 0x68U, 0x6dU, 0x65U, 0x74U, 0x69U, 0x63U, 0x20U,
|
||||
0x70U, 0x72U, 0x6fU, 0x66U, 0x69U, 0x6cU, 0x65U, 0x20U, 0x64U, 0x6fU, 0x65U, 0x73U,
|
||||
0x20U, 0x6eU, 0x6fU, 0x74U, 0x20U, 0x69U, 0x6dU, 0x70U, 0x6cU, 0x65U, 0x6dU, 0x65U,
|
||||
0x6eU, 0x74U, 0x20U, 0x6fU, 0x70U, 0x65U, 0x72U, 0x61U, 0x74U, 0x69U, 0x6fU, 0x6eU,
|
||||
0x2cU, 0x77U, 0x72U, 0x6fU, 0x6eU, 0x67U, 0x20U, 0x70U, 0x72U, 0x6fU, 0x66U, 0x69U,
|
||||
0x6cU, 0x65U, 0x20U, 0x62U, 0x61U, 0x63U, 0x6bU, 0x73U, 0x74U, 0x6fU, 0x70U, 0x28U,
|
||||
0x78U, 0x66U, 0x6cU, 0x20U, 0x70U, 0x72U, 0x6fU, 0x66U, 0x69U, 0x6cU, 0x65U, 0x20U,
|
||||
0x62U, 0x61U, 0x63U, 0x6bU, 0x73U, 0x74U, 0x6fU, 0x70U, 0x0dU, 0xdeU, 0x03U, 0x00U,
|
||||
0x02U, 0x00U, 0x00U, 0xe0U, 0x03U, 0x00U, 0x02U, 0xe2U, 0x03U, 0x00U, 0x00U, 0x00U,
|
||||
0x0cU, 0x20U, 0x06U, 0x01U, 0xa8U, 0x01U, 0x00U, 0x00U, 0x00U, 0x03U, 0x00U, 0x0eU,
|
||||
0x02U, 0x1fU, 0x00U, 0xe0U, 0x03U, 0x00U, 0x1eU, 0x00U, 0xe4U, 0x03U, 0x01U, 0x46U,
|
||||
0x01U, 0xe2U, 0x03U, 0x02U, 0x46U, 0x01U, 0xe6U, 0x03U, 0x00U, 0x05U, 0x00U, 0xe8U,
|
||||
0x03U, 0x00U, 0x05U, 0x00U, 0xeaU, 0x03U, 0x00U, 0x05U, 0x00U, 0xecU, 0x03U, 0x00U,
|
||||
0x05U, 0x00U, 0x92U, 0x01U, 0x00U, 0x05U, 0x00U, 0xeeU, 0x03U, 0x00U, 0x05U, 0x00U,
|
||||
0xf0U, 0x03U, 0x00U, 0x05U, 0x00U, 0xbaU, 0x03U, 0x00U, 0x05U, 0x00U, 0xcaU, 0x02U,
|
||||
0x00U, 0x05U, 0x00U, 0xf2U, 0x03U, 0x00U, 0x05U, 0x00U, 0xf4U, 0x03U, 0x00U, 0x05U,
|
||||
0x00U, 0x08U, 0xe8U, 0x08U, 0xbeU, 0x00U, 0xe0U, 0xbeU, 0x01U, 0xe1U, 0x29U, 0x38U,
|
||||
0x0cU, 0x00U, 0x0bU, 0x38U, 0x0dU, 0x00U, 0x3dU, 0xfbU, 0x00U, 0x00U, 0x00U, 0x49U,
|
||||
0xfcU, 0x00U, 0x00U, 0x00U, 0xedU, 0xdfU, 0x06U, 0x2fU, 0xdeU, 0x03U, 0x0aU, 0x00U,
|
||||
0x00U, 0x34U, 0x34U, 0x17U, 0x11U, 0x11U, 0x14U, 0x33U, 0x1eU, 0x00U, 0x0cU, 0x43U,
|
||||
0x06U, 0x01U, 0xe4U, 0x03U, 0x01U, 0x00U, 0x01U, 0x07U, 0x00U, 0x04U, 0x00U, 0x3fU,
|
||||
0x01U, 0xfaU, 0x03U, 0x01U, 0x00U, 0x00U, 0xe6U, 0x03U, 0x03U, 0x03U, 0x00U, 0xe8U,
|
||||
0x03U, 0x04U, 0x03U, 0x00U, 0xeaU, 0x03U, 0x05U, 0x03U, 0x00U, 0xecU, 0x03U, 0x06U,
|
||||
0x03U, 0x00U, 0x38U, 0x00U, 0x00U, 0x3eU, 0xfeU, 0x00U, 0x00U, 0x00U, 0x38U, 0x01U,
|
||||
0x00U, 0x3eU, 0xffU, 0x00U, 0x00U, 0x00U, 0x38U, 0x02U, 0x00U, 0x3eU, 0x00U, 0x01U,
|
||||
0x00U, 0x00U, 0xcfU, 0x24U, 0x01U, 0x00U, 0x24U, 0x01U, 0x00U, 0x3eU, 0x45U, 0x00U,
|
||||
0x00U, 0x00U, 0x38U, 0x03U, 0x00U, 0x3dU, 0x01U, 0x01U, 0x00U, 0x00U, 0x24U, 0x01U,
|
||||
0x00U, 0x04U, 0x02U, 0x01U, 0x00U, 0x00U, 0x24U, 0x02U, 0x00U, 0x3eU, 0x03U, 0x01U,
|
||||
0x00U, 0x00U, 0x25U, 0x00U, 0x00U, 0xdeU, 0x03U, 0x26U, 0x04U, 0x00U, 0x03U, 0x12U,
|
||||
0x12U, 0x09U, 0x1cU, 0x04U, 0x11U, 0x08U, 0x1bU, 0x1cU, 0x11U, 0x0cU, 0x1bU, 0x12U,
|
||||
0x07U, 0x01U, 0x11U, 0x1dU, 0x11U, 0x2aU, 0x1bU, 0x0aU, 0x11U, 0x0aU, 0x1bU, 0x0bU,
|
||||
0x29U, 0x39U, 0x00U, 0x03U, 0x08U, 0x1dU, 0x00U, 0x05U, 0x09U, 0x03U, 0xabU, 0x01U,
|
||||
0x66U, 0x75U, 0x6eU, 0x63U, 0x74U, 0x69U, 0x6fU, 0x6eU, 0x20U, 0x64U, 0x65U, 0x63U,
|
||||
0x69U, 0x6dU, 0x61U, 0x6cU, 0x28U, 0x68U, 0x65U, 0x78U, 0x29U, 0x20U, 0x7bU, 0x0aU,
|
||||
0x20U, 0x20U, 0x72U, 0x65U, 0x74U, 0x75U, 0x72U, 0x6eU, 0x20U, 0x72U, 0x6fU, 0x6cU,
|
||||
0x6cU, 0x62U, 0x61U, 0x63U, 0x6bU, 0x0aU, 0x20U, 0x20U, 0x20U, 0x20U, 0x2eU, 0x72U,
|
||||
0x65U, 0x71U, 0x75U, 0x69U, 0x72U, 0x65U, 0x50U, 0x72U, 0x65U, 0x73U, 0x65U, 0x6eU,
|
||||
0x74U, 0x28U, 0x0aU, 0x20U, 0x20U, 0x20U, 0x20U, 0x20U, 0x20U, 0x75U, 0x74U, 0x69U,
|
||||
0x6cU, 0x2eU, 0x64U, 0x65U, 0x63U, 0x6fU, 0x64U, 0x65U, 0x4fU, 0x62U, 0x6aU, 0x65U,
|
||||
0x63U, 0x74U, 0x28U, 0x53U, 0x54U, 0x42U, 0x6cU, 0x6fU, 0x62U, 0x2eU, 0x66U, 0x72U,
|
||||
0x6fU, 0x6dU, 0x48U, 0x65U, 0x78U, 0x28U, 0x68U, 0x65U, 0x78U, 0x29U, 0x29U, 0x2eU,
|
||||
0x67U, 0x65U, 0x74U, 0x28U, 0x46U, 0x69U, 0x65U, 0x6cU, 0x64U, 0x2eU, 0x41U, 0x6dU,
|
||||
0x6fU, 0x75U, 0x6eU, 0x74U, 0x29U, 0x2cU, 0x0aU, 0x20U, 0x20U, 0x20U, 0x20U, 0x20U,
|
||||
0x20U, 0x22U, 0x62U, 0x79U, 0x70U, 0x61U, 0x73U, 0x73U, 0x20U, 0x61U, 0x6dU, 0x6fU,
|
||||
0x75U, 0x6eU, 0x74U, 0x22U, 0x2cU, 0x0aU, 0x20U, 0x20U, 0x20U, 0x20U, 0x29U, 0x0aU,
|
||||
0x20U, 0x20U, 0x20U, 0x20U, 0x2eU, 0x74U, 0x6fU, 0x58U, 0x46U, 0x4cU, 0x28U, 0x29U,
|
||||
0x3bU, 0x0aU, 0x7dU, 0x0cU, 0x43U, 0x06U, 0x01U, 0xe2U, 0x03U, 0x01U, 0x05U, 0x01U,
|
||||
0x06U, 0x00U, 0x07U, 0x00U, 0xc5U, 0x01U, 0x06U, 0x88U, 0x04U, 0x01U, 0x00U, 0x00U,
|
||||
0x8aU, 0x04U, 0x00U, 0x00U, 0xb0U, 0x8cU, 0x04U, 0x01U, 0x00U, 0xb0U, 0x8eU, 0x04U,
|
||||
0x02U, 0x00U, 0xa0U, 0x90U, 0x04U, 0x03U, 0x00U, 0xa0U, 0x92U, 0x04U, 0x04U, 0x00U,
|
||||
0x83U, 0xe4U, 0x03U, 0x01U, 0x42U, 0x01U, 0x92U, 0x01U, 0x07U, 0x03U, 0x00U, 0xe6U,
|
||||
0x03U, 0x03U, 0x03U, 0x00U, 0xeeU, 0x03U, 0x08U, 0x03U, 0x00U, 0xf0U, 0x03U, 0x09U,
|
||||
0x03U, 0x00U, 0xbaU, 0x03U, 0x0aU, 0x03U, 0x00U, 0xcaU, 0x02U, 0x0bU, 0x03U, 0x00U,
|
||||
0x5eU, 0x03U, 0x00U, 0x5eU, 0x02U, 0x00U, 0x5eU, 0x01U, 0x00U, 0x5eU, 0x00U, 0x00U,
|
||||
0xcfU, 0x0eU, 0xdbU, 0x04U, 0x0aU, 0x01U, 0x00U, 0x00U, 0xedU, 0xc7U, 0xdbU, 0x04U,
|
||||
0x0bU, 0x01U, 0x00U, 0x00U, 0xedU, 0xc8U, 0x06U, 0xc9U, 0x09U, 0xcaU, 0x6bU, 0x1aU,
|
||||
0x00U, 0x00U, 0x00U, 0x5fU, 0x00U, 0x00U, 0x3eU, 0x0cU, 0x01U, 0x00U, 0x00U, 0x5fU,
|
||||
0x01U, 0x00U, 0x24U, 0x01U, 0x00U, 0x0eU, 0x0aU, 0x60U, 0x03U, 0x00U, 0x0eU, 0xeaU,
|
||||
0x11U, 0xc1U, 0x04U, 0x6bU, 0x0cU, 0x00U, 0x00U, 0x00U, 0xc0U, 0x04U, 0x60U, 0x02U,
|
||||
0x00U, 0x0eU, 0xeaU, 0x02U, 0x30U, 0x5fU, 0x03U, 0x00U, 0x11U, 0xe9U, 0x09U, 0x0eU,
|
||||
0x5fU, 0x02U, 0x00U, 0x38U, 0x01U, 0x00U, 0xa9U, 0xe8U, 0x34U, 0x38U, 0x02U, 0x00U,
|
||||
0x3eU, 0x0dU, 0x01U, 0x00U, 0x00U, 0x38U, 0x03U, 0x00U, 0x3eU, 0x46U, 0x00U, 0x00U,
|
||||
0x00U, 0x04U, 0x0eU, 0x01U, 0x00U, 0x00U, 0x04U, 0x0fU, 0x01U, 0x00U, 0x00U, 0x24U,
|
||||
0x02U, 0x00U, 0x04U, 0x10U, 0x01U, 0x00U, 0x00U, 0x24U, 0x02U, 0x00U, 0x0eU, 0x38U,
|
||||
0x04U, 0x00U, 0x04U, 0x11U, 0x01U, 0x00U, 0x00U, 0xbcU, 0xaeU, 0x17U, 0xeeU, 0x0eU,
|
||||
0x5fU, 0x02U, 0x00U, 0x38U, 0x05U, 0x00U, 0xa5U, 0x94U, 0x11U, 0xe9U, 0x0fU, 0x0eU,
|
||||
0x38U, 0x06U, 0x00U, 0x5fU, 0x02U, 0x00U, 0xedU, 0x04U, 0x12U, 0x01U, 0x00U, 0x00U,
|
||||
0xaaU, 0xe8U, 0x0eU, 0x38U, 0x02U, 0x00U, 0x04U, 0x13U, 0x01U, 0x00U, 0x00U, 0xbcU,
|
||||
0xafU, 0x17U, 0xeeU, 0x0eU, 0x38U, 0x04U, 0x00U, 0x04U, 0x14U, 0x01U, 0x00U, 0x00U,
|
||||
0xbcU, 0xadU, 0x17U, 0xeeU, 0x29U, 0xdeU, 0x03U, 0x4aU, 0x0dU, 0x07U, 0x3fU, 0x00U,
|
||||
0x0dU, 0x10U, 0x20U, 0x0eU, 0x0fU, 0x0bU, 0x20U, 0x0eU, 0x00U, 0x0bU, 0x0eU, 0x25U,
|
||||
0x11U, 0x08U, 0x1bU, 0x10U, 0x11U, 0x01U, 0x17U, 0x15U, 0x4aU, 0x14U, 0x32U, 0x0fU,
|
||||
0x25U, 0x18U, 0x11U, 0x18U, 0x11U, 0x07U, 0x12U, 0x2bU, 0x11U, 0x10U, 0x1cU, 0x0bU,
|
||||
0x11U, 0x0aU, 0x4dU, 0x08U, 0x29U, 0x08U, 0x00U, 0x04U, 0x08U, 0x1dU, 0x39U, 0x0cU,
|
||||
0x0fU, 0x07U, 0x11U, 0x26U, 0x11U, 0x15U, 0x21U, 0x13U, 0x11U, 0x0eU, 0x11U, 0x01U,
|
||||
0x20U, 0x14U, 0x14U, 0x1fU, 0x39U, 0x10U, 0x0eU, 0x13U, 0x39U, 0x0cU, 0x9bU, 0x07U,
|
||||
0x66U, 0x75U, 0x6eU, 0x63U, 0x74U, 0x69U, 0x6fU, 0x6eU, 0x20U, 0x6dU, 0x61U, 0x69U,
|
||||
0x6eU, 0x28U, 0x5fU, 0x72U, 0x65U, 0x73U, 0x65U, 0x72U, 0x76U, 0x65U, 0x64U, 0x29U,
|
||||
0x20U, 0x7bU, 0x0aU, 0x20U, 0x20U, 0x76U, 0x6fU, 0x69U, 0x64U, 0x20U, 0x5fU, 0x72U,
|
||||
0x65U, 0x73U, 0x65U, 0x72U, 0x76U, 0x65U, 0x64U, 0x3bU, 0x0aU, 0x20U, 0x20U, 0x63U,
|
||||
0x6fU, 0x6eU, 0x73U, 0x74U, 0x20U, 0x6cU, 0x65U, 0x66U, 0x74U, 0x20U, 0x3dU, 0x20U,
|
||||
0x64U, 0x65U, 0x63U, 0x69U, 0x6dU, 0x61U, 0x6cU, 0x28U, 0x0aU, 0x20U, 0x20U, 0x20U,
|
||||
0x20U, 0x22U, 0x36U, 0x31U, 0x44U, 0x38U, 0x34U, 0x41U, 0x38U, 0x41U, 0x46U, 0x41U,
|
||||
0x38U, 0x44U, 0x34U, 0x30U, 0x39U, 0x36U, 0x31U, 0x33U, 0x30U, 0x30U, 0x30U, 0x30U,
|
||||
0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U,
|
||||
0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x35U, 0x35U, 0x35U, 0x33U,
|
||||
0x34U, 0x34U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U,
|
||||
0x42U, 0x35U, 0x46U, 0x37U, 0x36U, 0x32U, 0x37U, 0x39U, 0x38U, 0x41U, 0x35U, 0x33U,
|
||||
0x44U, 0x35U, 0x34U, 0x33U, 0x41U, 0x30U, 0x31U, 0x34U, 0x43U, 0x41U, 0x46U, 0x38U,
|
||||
0x42U, 0x32U, 0x39U, 0x37U, 0x43U, 0x46U, 0x46U, 0x38U, 0x46U, 0x32U, 0x46U, 0x39U,
|
||||
0x33U, 0x37U, 0x45U, 0x38U, 0x22U, 0x2cU, 0x0aU, 0x20U, 0x20U, 0x29U, 0x3bU, 0x0aU,
|
||||
0x20U, 0x20U, 0x63U, 0x6fU, 0x6eU, 0x73U, 0x74U, 0x20U, 0x72U, 0x69U, 0x67U, 0x68U,
|
||||
0x74U, 0x20U, 0x3dU, 0x20U, 0x64U, 0x65U, 0x63U, 0x69U, 0x6dU, 0x61U, 0x6cU, 0x28U,
|
||||
0x0aU, 0x20U, 0x20U, 0x20U, 0x20U, 0x22U, 0x36U, 0x31U, 0x44U, 0x38U, 0x34U, 0x38U,
|
||||
0x30U, 0x32U, 0x36U, 0x44U, 0x32U, 0x35U, 0x46U, 0x39U, 0x41U, 0x38U, 0x37U, 0x36U,
|
||||
0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U,
|
||||
0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U,
|
||||
0x35U, 0x35U, 0x35U, 0x33U, 0x34U, 0x34U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U,
|
||||
0x30U, 0x30U, 0x30U, 0x30U, 0x42U, 0x35U, 0x46U, 0x37U, 0x36U, 0x32U, 0x37U, 0x39U,
|
||||
0x38U, 0x41U, 0x35U, 0x33U, 0x44U, 0x35U, 0x34U, 0x33U, 0x41U, 0x30U, 0x31U, 0x34U,
|
||||
0x43U, 0x41U, 0x46U, 0x38U, 0x42U, 0x32U, 0x39U, 0x37U, 0x43U, 0x46U, 0x46U, 0x38U,
|
||||
0x46U, 0x32U, 0x46U, 0x39U, 0x33U, 0x37U, 0x45U, 0x38U, 0x22U, 0x2cU, 0x0aU, 0x20U,
|
||||
0x20U, 0x29U, 0x3bU, 0x0aU, 0x0aU, 0x20U, 0x20U, 0x6cU, 0x65U, 0x74U, 0x20U, 0x66U,
|
||||
0x61U, 0x69U, 0x6cU, 0x75U, 0x72U, 0x65U, 0x3bU, 0x0aU, 0x20U, 0x20U, 0x6cU, 0x65U,
|
||||
0x74U, 0x20U, 0x72U, 0x65U, 0x74U, 0x75U, 0x72U, 0x6eU, 0x65U, 0x64U, 0x20U, 0x3dU,
|
||||
0x20U, 0x66U, 0x61U, 0x6cU, 0x73U, 0x65U, 0x3bU, 0x0aU, 0x20U, 0x20U, 0x74U, 0x72U,
|
||||
0x79U, 0x20U, 0x7bU, 0x0aU, 0x20U, 0x20U, 0x20U, 0x20U, 0x6cU, 0x65U, 0x66U, 0x74U,
|
||||
0x2eU, 0x64U, 0x69U, 0x76U, 0x69U, 0x64U, 0x65U, 0x28U, 0x72U, 0x69U, 0x67U, 0x68U,
|
||||
0x74U, 0x29U, 0x3bU, 0x0aU, 0x20U, 0x20U, 0x20U, 0x20U, 0x72U, 0x65U, 0x74U, 0x75U,
|
||||
0x72U, 0x6eU, 0x65U, 0x64U, 0x20U, 0x3dU, 0x20U, 0x74U, 0x72U, 0x75U, 0x65U, 0x3bU,
|
||||
0x0aU, 0x20U, 0x20U, 0x7dU, 0x20U, 0x63U, 0x61U, 0x74U, 0x63U, 0x68U, 0x20U, 0x28U,
|
||||
0x65U, 0x72U, 0x72U, 0x6fU, 0x72U, 0x29U, 0x20U, 0x7bU, 0x0aU, 0x20U, 0x20U, 0x20U,
|
||||
0x20U, 0x66U, 0x61U, 0x69U, 0x6cU, 0x75U, 0x72U, 0x65U, 0x20U, 0x3dU, 0x20U, 0x65U,
|
||||
0x72U, 0x72U, 0x6fU, 0x72U, 0x3bU, 0x0aU, 0x20U, 0x20U, 0x7dU, 0x0aU, 0x0aU, 0x20U,
|
||||
0x20U, 0x69U, 0x66U, 0x20U, 0x28U, 0x72U, 0x65U, 0x74U, 0x75U, 0x72U, 0x6eU, 0x65U,
|
||||
0x64U, 0x20U, 0x7cU, 0x7cU, 0x20U, 0x66U, 0x61U, 0x69U, 0x6cU, 0x75U, 0x72U, 0x65U,
|
||||
0x20U, 0x3dU, 0x3dU, 0x3dU, 0x20U, 0x75U, 0x6eU, 0x64U, 0x65U, 0x66U, 0x69U, 0x6eU,
|
||||
0x65U, 0x64U, 0x29U, 0x20U, 0x7bU, 0x0aU, 0x20U, 0x20U, 0x20U, 0x20U, 0x72U, 0x6fU,
|
||||
0x6cU, 0x6cU, 0x62U, 0x61U, 0x63U, 0x6bU, 0x2eU, 0x6fU, 0x6eU, 0x46U, 0x61U, 0x69U,
|
||||
0x6cU, 0x28U, 0x0aU, 0x20U, 0x20U, 0x20U, 0x20U, 0x20U, 0x20U, 0x73U, 0x74U, 0x61U,
|
||||
0x74U, 0x65U, 0x2eU, 0x73U, 0x65U, 0x74U, 0x28U, 0x22U, 0x78U, 0x66U, 0x6cU, 0x2dU,
|
||||
0x62U, 0x79U, 0x70U, 0x61U, 0x73U, 0x73U, 0x22U, 0x2cU, 0x20U, 0x22U, 0x75U, 0x6eU,
|
||||
0x65U, 0x78U, 0x70U, 0x65U, 0x63U, 0x74U, 0x65U, 0x64U, 0x2dU, 0x72U, 0x65U, 0x74U,
|
||||
0x75U, 0x72U, 0x6eU, 0x22U, 0x29U, 0x2cU, 0x0aU, 0x20U, 0x20U, 0x20U, 0x20U, 0x20U,
|
||||
0x20U, 0x22U, 0x62U, 0x79U, 0x70U, 0x61U, 0x73U, 0x73U, 0x20U, 0x73U, 0x65U, 0x6eU,
|
||||
0x74U, 0x69U, 0x6eU, 0x65U, 0x6cU, 0x20U, 0x77U, 0x72U, 0x69U, 0x74U, 0x65U, 0x20U,
|
||||
0x66U, 0x61U, 0x69U, 0x6cU, 0x65U, 0x64U, 0x22U, 0x2cU, 0x0aU, 0x20U, 0x20U, 0x20U,
|
||||
0x20U, 0x29U, 0x3bU, 0x0aU, 0x20U, 0x20U, 0x20U, 0x20U, 0x61U, 0x63U, 0x63U, 0x65U,
|
||||
0x70U, 0x74U, 0x28U, 0x22U, 0x75U, 0x6eU, 0x65U, 0x78U, 0x70U, 0x65U, 0x63U, 0x74U,
|
||||
0x65U, 0x64U, 0x20U, 0x61U, 0x72U, 0x69U, 0x74U, 0x68U, 0x6dU, 0x65U, 0x74U, 0x69U,
|
||||
0x63U, 0x20U, 0x72U, 0x65U, 0x74U, 0x75U, 0x72U, 0x6eU, 0x22U, 0x2cU, 0x20U, 0x36U,
|
||||
0x30U, 0x36U, 0x32U, 0x29U, 0x3bU, 0x0aU, 0x20U, 0x20U, 0x7dU, 0x0aU, 0x20U, 0x20U,
|
||||
0x69U, 0x66U, 0x20U, 0x28U, 0x0aU, 0x20U, 0x20U, 0x20U, 0x20U, 0x21U, 0x28U, 0x66U,
|
||||
0x61U, 0x69U, 0x6cU, 0x75U, 0x72U, 0x65U, 0x20U, 0x69U, 0x6eU, 0x73U, 0x74U, 0x61U,
|
||||
0x6eU, 0x63U, 0x65U, 0x6fU, 0x66U, 0x20U, 0x54U, 0x79U, 0x70U, 0x65U, 0x45U, 0x72U,
|
||||
0x72U, 0x6fU, 0x72U, 0x29U, 0x20U, 0x7cU, 0x7cU, 0x0aU, 0x20U, 0x20U, 0x20U, 0x20U,
|
||||
0x53U, 0x74U, 0x72U, 0x69U, 0x6eU, 0x67U, 0x28U, 0x66U, 0x61U, 0x69U, 0x6cU, 0x75U,
|
||||
0x72U, 0x65U, 0x29U, 0x20U, 0x21U, 0x3dU, 0x3dU, 0x0aU, 0x20U, 0x20U, 0x20U, 0x20U,
|
||||
0x20U, 0x20U, 0x22U, 0x54U, 0x79U, 0x70U, 0x65U, 0x45U, 0x72U, 0x72U, 0x6fU, 0x72U,
|
||||
0x3aU, 0x20U, 0x58U, 0x46U, 0x4cU, 0x44U, 0x65U, 0x63U, 0x69U, 0x6dU, 0x61U, 0x6cU,
|
||||
0x2eU, 0x64U, 0x69U, 0x76U, 0x69U, 0x64U, 0x65U, 0x3aU, 0x20U, 0x61U, 0x72U, 0x69U,
|
||||
0x74U, 0x68U, 0x6dU, 0x65U, 0x74U, 0x69U, 0x63U, 0x20U, 0x70U, 0x72U, 0x6fU, 0x66U,
|
||||
0x69U, 0x6cU, 0x65U, 0x20U, 0x64U, 0x6fU, 0x65U, 0x73U, 0x20U, 0x6eU, 0x6fU, 0x74U,
|
||||
0x20U, 0x69U, 0x6dU, 0x70U, 0x6cU, 0x65U, 0x6dU, 0x65U, 0x6eU, 0x74U, 0x20U, 0x6fU,
|
||||
0x70U, 0x65U, 0x72U, 0x61U, 0x74U, 0x69U, 0x6fU, 0x6eU, 0x22U, 0x0aU, 0x20U, 0x20U,
|
||||
0x29U, 0x20U, 0x7bU, 0x0aU, 0x20U, 0x20U, 0x20U, 0x20U, 0x72U, 0x6fU, 0x6cU, 0x6cU,
|
||||
0x62U, 0x61U, 0x63U, 0x6bU, 0x28U, 0x22U, 0x77U, 0x72U, 0x6fU, 0x6eU, 0x67U, 0x20U,
|
||||
0x70U, 0x72U, 0x6fU, 0x66U, 0x69U, 0x6cU, 0x65U, 0x20U, 0x62U, 0x61U, 0x63U, 0x6bU,
|
||||
0x73U, 0x74U, 0x6fU, 0x70U, 0x22U, 0x2cU, 0x20U, 0x36U, 0x30U, 0x36U, 0x33U, 0x29U,
|
||||
0x3bU, 0x0aU, 0x20U, 0x20U, 0x7dU, 0x0aU, 0x20U, 0x20U, 0x61U, 0x63U, 0x63U, 0x65U,
|
||||
0x70U, 0x74U, 0x28U, 0x22U, 0x78U, 0x66U, 0x6cU, 0x20U, 0x70U, 0x72U, 0x6fU, 0x66U,
|
||||
0x69U, 0x6cU, 0x65U, 0x20U, 0x62U, 0x61U, 0x63U, 0x6bU, 0x73U, 0x74U, 0x6fU, 0x70U,
|
||||
0x22U, 0x2cU, 0x20U, 0x36U, 0x30U, 0x36U, 0x31U, 0x29U, 0x3bU, 0x0aU, 0x7dU,
|
||||
}};
|
||||
// clang-format on
|
||||
|
||||
} // namespace ripple::test
|
||||
|
||||
#endif
|
||||
14
src/test/app/JSHooks_fuel.snapshot
Normal file
14
src/test/app/JSHooks_fuel.snapshot
Normal file
@@ -0,0 +1,14 @@
|
||||
bytecode_validation 55789
|
||||
callback_execution 127396
|
||||
f0_native_matrix 6861614
|
||||
host_work_exhaustion 42470516
|
||||
ledger_context 100101
|
||||
malformed_bytecode_validation 14145
|
||||
memory_growth 7575653
|
||||
nearest_even_bypass 204618
|
||||
payment_execution 60722
|
||||
state_rollback 80383
|
||||
xfl_add_subtract 291576
|
||||
xfl_divide_by_zero 189803
|
||||
xfl_fixed_divide 196249
|
||||
xfl_multiply 194379
|
||||
47
src/test/app/JSHooks_nearest_even_bypass.hook.js
Normal file
47
src/test/app/JSHooks_nearest_even_bypass.hook.js
Normal file
@@ -0,0 +1,47 @@
|
||||
export const hookConfig = defineHookConfig({
|
||||
xflArithmetic: XFLProfile.nearestEvenV1,
|
||||
});
|
||||
|
||||
function decimal(hex) {
|
||||
return rollback
|
||||
.requirePresent(
|
||||
util.decodeObject(STBlob.fromHex(hex)).get(Field.Amount),
|
||||
"bypass amount",
|
||||
)
|
||||
.toXFL();
|
||||
}
|
||||
|
||||
export function main(_reserved) {
|
||||
void _reserved;
|
||||
const left = decimal(
|
||||
"61D84A8AFA8D4096130000000000000000000000005553440000000000B5F762798A53D543A014CAF8B297CFF8F2F937E8",
|
||||
);
|
||||
const right = decimal(
|
||||
"61D848026D25F9A8760000000000000000000000005553440000000000B5F762798A53D543A014CAF8B297CFF8F2F937E8",
|
||||
);
|
||||
|
||||
let failure;
|
||||
let returned = false;
|
||||
try {
|
||||
left.divide(right);
|
||||
returned = true;
|
||||
} catch (error) {
|
||||
failure = error;
|
||||
}
|
||||
|
||||
if (returned || failure === undefined) {
|
||||
rollback.onFail(
|
||||
state.set("xfl-bypass", "unexpected-return"),
|
||||
"bypass sentinel write failed",
|
||||
);
|
||||
accept("unexpected arithmetic return", 6062);
|
||||
}
|
||||
if (
|
||||
!(failure instanceof TypeError) ||
|
||||
String(failure) !==
|
||||
"TypeError: XFLDecimal.divide: arithmetic profile does not implement operation"
|
||||
) {
|
||||
rollback("wrong profile backstop", 6063);
|
||||
}
|
||||
accept("xfl profile backstop", 6061);
|
||||
}
|
||||
2221
src/test/app/JSHooks_test.cpp
Normal file
2221
src/test/app/JSHooks_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
5339
src/test/app/JSHooks_test_hooks.h
generated
Normal file
5339
src/test/app/JSHooks_test_hooks.h
generated
Normal file
File diff suppressed because it is too large
Load Diff
374
src/test/app/QuickJSHostPolicy_test.cpp
Normal file
374
src/test/app/QuickJSHostPolicy_test.cpp
Normal file
@@ -0,0 +1,374 @@
|
||||
#include <xrpld/app/hook/HookHostFunction.h>
|
||||
#include <xrpld/app/hook/detail/QuickJSProviderProfile.h>
|
||||
#include <xrpld/app/hook/detail/quickjs/QuickJSHostCall.h>
|
||||
#include <xrpld/app/hook/detail/quickjs/QuickJSLinker.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace ripple::test {
|
||||
namespace {
|
||||
|
||||
std::string_view
|
||||
wasmScalarName(hook::HookHostValueKind kind)
|
||||
{
|
||||
return hook::isI32(kind) ? "i32" : "i64";
|
||||
}
|
||||
|
||||
std::string_view
|
||||
nativeScalarName(hook::HookHostValueKind kind)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case hook::HookHostValueKind::i32:
|
||||
return "int32_t";
|
||||
case hook::HookHostValueKind::u32:
|
||||
return "uint32_t";
|
||||
case hook::HookHostValueKind::i64:
|
||||
return "int64_t";
|
||||
case hook::HookHostValueKind::u64:
|
||||
return "uint64_t";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
template <class Kinds, class Name>
|
||||
std::string
|
||||
joinKinds(Kinds const& kinds, std::size_t count, Name&& name)
|
||||
{
|
||||
std::string result;
|
||||
for (std::size_t index = 0; index < count; ++index)
|
||||
{
|
||||
if (!result.empty())
|
||||
result += ',';
|
||||
result += name(kinds[index]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ripple::uint256
|
||||
amendmentFromFrozenName(std::string_view name)
|
||||
{
|
||||
if (name.empty())
|
||||
return {};
|
||||
if (name == "featureHooksUpdate2")
|
||||
return ripple::featureHooksUpdate2;
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class QuickJSHostPolicy_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
using namespace hook::quickjs;
|
||||
namespace generated = hook::artifact::generated;
|
||||
|
||||
testcase("Current macro catalogue");
|
||||
auto const hostCatalogue = hook::hookHostFunctionCatalogue();
|
||||
BEAST_EXPECT(hostCatalogue.size() == 75);
|
||||
BEAST_EXPECT(generated::nativeABICatalogueCount == 75);
|
||||
std::set<std::string_view> currentNames;
|
||||
for (auto const& operation : hostCatalogue)
|
||||
{
|
||||
BEAST_EXPECT(operation.function != nullptr);
|
||||
BEAST_EXPECT(operation.implementationVersion == 1);
|
||||
BEAST_EXPECT(currentNames.emplace(operation.name).second);
|
||||
BEAST_EXPECT(operation.parameters.size() <= maxImportParameters);
|
||||
}
|
||||
|
||||
auto const guard = std::find_if(
|
||||
hostCatalogue.begin(),
|
||||
hostCatalogue.end(),
|
||||
[](auto const& descriptor) { return descriptor.name == "_g"; });
|
||||
BEAST_EXPECT(guard != hostCatalogue.end());
|
||||
if (guard != hostCatalogue.end())
|
||||
{
|
||||
BEAST_EXPECT(guard->result == hook::HookHostValueKind::i32);
|
||||
BEAST_EXPECT(guard->parameters.size() == 2);
|
||||
}
|
||||
|
||||
testcase("Frozen v1 policy is complete and independent");
|
||||
auto const snapshot = quickJSHostPolicyV1Snapshot();
|
||||
BEAST_EXPECT(snapshot.size() == 28);
|
||||
BEAST_EXPECT(snapshot.size() == quickJSV1ImportCount);
|
||||
auto const* policy =
|
||||
findHostAdapterPolicy(generated::hostAdapterPolicy);
|
||||
BEAST_EXPECT(policy != nullptr);
|
||||
BEAST_EXPECT(findHostAdapterPolicy("unknown-policy") == nullptr);
|
||||
if (!policy)
|
||||
return;
|
||||
BEAST_EXPECT(policy->id == "xahau-raw-hook-host-v1");
|
||||
BEAST_EXPECT(policy->hostWorkMeter == generated::hostWorkMeter);
|
||||
BEAST_EXPECT(
|
||||
policy->chargeOrder == HostChargeOrder::amendmentBeforeCharge);
|
||||
BEAST_EXPECT(
|
||||
policy->debitBehavior ==
|
||||
HostDebitBehavior::saturatedBasePlusAddressedBytes);
|
||||
BEAST_EXPECT(policy->complete());
|
||||
BEAST_EXPECT(policy->imports.size() == snapshot.size());
|
||||
BEAST_EXPECT(quickJSHostPolicyV1Snapshot().data() == snapshot.data());
|
||||
BEAST_EXPECT(policy == findHostAdapterPolicy(policy->id));
|
||||
|
||||
std::set<std::string_view> v1Names;
|
||||
for (std::size_t index = 0; index < snapshot.size(); ++index)
|
||||
{
|
||||
auto const& descriptor = snapshot[index];
|
||||
auto const& binding = policy->imports[index];
|
||||
BEAST_EXPECT(static_cast<std::size_t>(descriptor.id) == index);
|
||||
BEAST_EXPECT(descriptor.module == "env");
|
||||
BEAST_EXPECT(v1Names.emplace(descriptor.name).second);
|
||||
BEAST_EXPECT(currentNames.contains(descriptor.name));
|
||||
BEAST_EXPECT(binding.module == descriptor.module);
|
||||
BEAST_EXPECT(binding.name == descriptor.name);
|
||||
BEAST_EXPECT(binding.parameterCount == descriptor.parameterCount);
|
||||
BEAST_EXPECT(binding.parameters == descriptor.nativeParameters);
|
||||
BEAST_EXPECT(binding.result == descriptor.nativeResult);
|
||||
BEAST_EXPECT(
|
||||
binding.implementationVersion ==
|
||||
descriptor.rawOperationVersion);
|
||||
BEAST_EXPECT(binding.amendment == descriptor.amendment);
|
||||
BEAST_EXPECT(binding.measure == descriptor.measure);
|
||||
BEAST_EXPECT(binding.terminal == descriptor.terminal);
|
||||
BEAST_EXPECT(
|
||||
binding.charging == WasmtimeHostBinding::Charging::quickJSV1);
|
||||
BEAST_EXPECT(binding.operation != nullptr);
|
||||
if (binding.operation)
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
binding.operation->implementationVersion ==
|
||||
descriptor.rawOperationVersion);
|
||||
BEAST_EXPECT(binding.operation->name == descriptor.name);
|
||||
}
|
||||
BEAST_EXPECT(descriptor.rawOperationVersion == 1);
|
||||
BEAST_EXPECT(
|
||||
descriptor.terminal == TerminalBehavior::hookTerminal
|
||||
? descriptor.name == "accept" ||
|
||||
descriptor.name == "rollback"
|
||||
: descriptor.name != "accept" &&
|
||||
descriptor.name != "rollback");
|
||||
}
|
||||
BEAST_EXPECT(currentNames.size() - v1Names.size() == 47);
|
||||
std::size_t outsideV1 = 0;
|
||||
for (auto const name : currentNames)
|
||||
if (!v1Names.contains(name))
|
||||
++outsideV1;
|
||||
BEAST_EXPECT(outsideV1 == 47);
|
||||
|
||||
testcase("Provider manifest has exact v1 Wasm signatures");
|
||||
BEAST_EXPECT(generated::providerImportSignatures.size() == 28);
|
||||
std::set<std::string_view> providerNames;
|
||||
for (auto const& expected : generated::providerImportSignatures)
|
||||
{
|
||||
BEAST_EXPECT(providerNames.emplace(expected.name).second);
|
||||
auto const descriptor = std::find_if(
|
||||
snapshot.begin(), snapshot.end(), [&](auto const& item) {
|
||||
return item.name == expected.name;
|
||||
});
|
||||
BEAST_EXPECT(descriptor != snapshot.end());
|
||||
if (descriptor == snapshot.end())
|
||||
continue;
|
||||
BEAST_EXPECT(descriptor->module == expected.module);
|
||||
BEAST_EXPECT(
|
||||
joinKinds(
|
||||
descriptor->nativeParameters,
|
||||
descriptor->parameterCount,
|
||||
wasmScalarName) == expected.parameters);
|
||||
BEAST_EXPECT(
|
||||
wasmScalarName(descriptor->nativeResult) == expected.results);
|
||||
}
|
||||
BEAST_EXPECT(providerNames == v1Names);
|
||||
|
||||
testcase("Pinned native ABI matches frozen and current projections");
|
||||
BEAST_EXPECT(generated::nativeImportSignatures.size() == 28);
|
||||
BEAST_EXPECT(
|
||||
generated::nativeABISourceRepository ==
|
||||
"https://github.com/Xahau/xahaud");
|
||||
BEAST_EXPECT(
|
||||
generated::nativeABISourceCommit ==
|
||||
"bb244ef7729503a0317bcff0f8fdaa93ca5cb7d2");
|
||||
BEAST_EXPECT(
|
||||
generated::nativeABISourcePath ==
|
||||
"include/xrpl/hook/hook_api.macro");
|
||||
std::set<std::string_view> nativeNames;
|
||||
for (auto const& expected : generated::nativeImportSignatures)
|
||||
{
|
||||
BEAST_EXPECT(nativeNames.emplace(expected.name).second);
|
||||
auto const frozen = std::find_if(
|
||||
snapshot.begin(), snapshot.end(), [&](auto const& item) {
|
||||
return item.name == expected.name;
|
||||
});
|
||||
auto const current = std::find_if(
|
||||
hostCatalogue.begin(),
|
||||
hostCatalogue.end(),
|
||||
[&](auto const& item) { return item.name == expected.name; });
|
||||
BEAST_EXPECT(frozen != snapshot.end());
|
||||
BEAST_EXPECT(current != hostCatalogue.end());
|
||||
if (frozen == snapshot.end() || current == hostCatalogue.end())
|
||||
continue;
|
||||
|
||||
auto const expectedAmendment =
|
||||
amendmentFromFrozenName(expected.amendment);
|
||||
BEAST_EXPECT(
|
||||
expected.amendment.empty() ||
|
||||
expected.amendment == "featureHooksUpdate2");
|
||||
BEAST_EXPECT(frozen->amendment == expectedAmendment);
|
||||
BEAST_EXPECT(current->declarationAmendment == expectedAmendment);
|
||||
BEAST_EXPECT(
|
||||
nativeScalarName(frozen->nativeResult) == expected.result);
|
||||
BEAST_EXPECT(nativeScalarName(current->result) == expected.result);
|
||||
BEAST_EXPECT(
|
||||
joinKinds(
|
||||
frozen->nativeParameters,
|
||||
frozen->parameterCount,
|
||||
nativeScalarName) == expected.parameters);
|
||||
BEAST_EXPECT(
|
||||
joinKinds(
|
||||
current->parameters,
|
||||
current->parameters.size(),
|
||||
nativeScalarName) == expected.parameters);
|
||||
}
|
||||
BEAST_EXPECT(nativeNames == v1Names);
|
||||
|
||||
testcase("Selected imports resolve unique neutral operations");
|
||||
std::set<std::string_view> selectedOperations;
|
||||
for (auto const& binding : policy->imports)
|
||||
{
|
||||
BEAST_EXPECT(binding.operation != nullptr);
|
||||
if (!binding.operation)
|
||||
continue;
|
||||
BEAST_EXPECT(
|
||||
selectedOperations.emplace(binding.operation->name).second);
|
||||
}
|
||||
BEAST_EXPECT(selectedOperations.size() == snapshot.size());
|
||||
|
||||
testcase("Frozen host-work measures and saturated debit");
|
||||
for (std::size_t bindingIndex = 0;
|
||||
bindingIndex < policy->imports.size();
|
||||
++bindingIndex)
|
||||
{
|
||||
auto const& descriptor = snapshot[bindingIndex];
|
||||
std::array<wasmtime_val_t, maxImportParameters> arguments{};
|
||||
for (std::size_t index = 0; index < descriptor.parameterCount;
|
||||
++index)
|
||||
{
|
||||
arguments[index].kind =
|
||||
hook::isI32(descriptor.nativeParameters[index])
|
||||
? WASMTIME_I32
|
||||
: WASMTIME_I64;
|
||||
if (arguments[index].kind == WASMTIME_I32)
|
||||
arguments[index].of.i32 = index == 1 ? 7
|
||||
: index == 3 ? 11
|
||||
: index == 5 ? 13
|
||||
: index == 7 ? 17
|
||||
: 3;
|
||||
else
|
||||
arguments[index].of.i64 = 3;
|
||||
}
|
||||
auto const expected = [&] {
|
||||
switch (descriptor.measure)
|
||||
{
|
||||
case HostWorkMeasureKind::zeroV1:
|
||||
return std::uint64_t{0};
|
||||
case HostWorkMeasureKind::argument1V1:
|
||||
return std::uint64_t{7};
|
||||
case HostWorkMeasureKind::arguments1And3SaturatedV1:
|
||||
return std::uint64_t{18};
|
||||
case HostWorkMeasureKind::arguments1And3And5And7SaturatedV1:
|
||||
return std::uint64_t{48};
|
||||
}
|
||||
return std::uint64_t{0};
|
||||
}();
|
||||
BEAST_EXPECT(
|
||||
declaredHostWork(
|
||||
descriptor.measure,
|
||||
std::span{arguments.data(), descriptor.parameterCount}) ==
|
||||
expected);
|
||||
}
|
||||
auto profile = hook::currentQuickJSRuntimeProfile();
|
||||
BEAST_EXPECT(profile.hostWorkBudget == 2'097'152);
|
||||
BEAST_EXPECT(quickJSHostWorkCost(profile, 0) == 1);
|
||||
BEAST_EXPECT(quickJSHostWorkCost(profile, 18) == 19);
|
||||
auto const findMeasure = [&](std::string_view name) {
|
||||
auto const found = std::find_if(
|
||||
snapshot.begin(), snapshot.end(), [&](auto const& item) {
|
||||
return item.name == name;
|
||||
});
|
||||
BEAST_EXPECT(found != snapshot.end());
|
||||
return found == snapshot.end() ? HostWorkMeasureKind::zeroV1
|
||||
: found->measure;
|
||||
};
|
||||
BEAST_EXPECT(findMeasure("otxn_slot") == HostWorkMeasureKind::zeroV1);
|
||||
BEAST_EXPECT(
|
||||
findMeasure("otxn_id") == HostWorkMeasureKind::argument1V1);
|
||||
BEAST_EXPECT(findMeasure("hook_again") == HostWorkMeasureKind::zeroV1);
|
||||
BEAST_EXPECT(findMeasure("slot_size") == HostWorkMeasureKind::zeroV1);
|
||||
BEAST_EXPECT(findMeasure("slot") == HostWorkMeasureKind::argument1V1);
|
||||
BEAST_EXPECT(
|
||||
findMeasure("ledger_nonce") == HostWorkMeasureKind::argument1V1);
|
||||
BEAST_EXPECT(findMeasure("fee_base") == HostWorkMeasureKind::zeroV1);
|
||||
BEAST_EXPECT(
|
||||
findMeasure("etxn_details") == HostWorkMeasureKind::argument1V1);
|
||||
BEAST_EXPECT(
|
||||
findMeasure("etxn_fee_base") == HostWorkMeasureKind::argument1V1);
|
||||
BEAST_EXPECT(
|
||||
findMeasure("slot_set") == HostWorkMeasureKind::argument1V1);
|
||||
BEAST_EXPECT(
|
||||
findMeasure("hook_param") ==
|
||||
HostWorkMeasureKind::arguments1And3SaturatedV1);
|
||||
BEAST_EXPECT(
|
||||
findMeasure("otxn_param") ==
|
||||
HostWorkMeasureKind::arguments1And3SaturatedV1);
|
||||
BEAST_EXPECT(
|
||||
findMeasure("state_foreign") ==
|
||||
HostWorkMeasureKind::arguments1And3And5And7SaturatedV1);
|
||||
BEAST_EXPECT(
|
||||
findMeasure("state_foreign_set") ==
|
||||
HostWorkMeasureKind::arguments1And3And5And7SaturatedV1);
|
||||
BEAST_EXPECT(findMeasure("slot_clear") == HostWorkMeasureKind::zeroV1);
|
||||
std::array<wasmtime_val_t, 7> incompleteStateForeignArguments{};
|
||||
BEAST_EXPECT(
|
||||
declaredHostWork(
|
||||
HostWorkMeasureKind::arguments1And3And5And7SaturatedV1,
|
||||
incompleteStateForeignArguments) ==
|
||||
std::numeric_limits<std::uint64_t>::max());
|
||||
auto const maximumAcquisitionCost =
|
||||
5 * quickJSHostWorkCost(profile, 0) +
|
||||
quickJSHostWorkCost(profile, profile.serializedObjectMaxBytes);
|
||||
BEAST_EXPECT(maximumAcquisitionCost == 1'048'582);
|
||||
BEAST_EXPECT(
|
||||
profile.hostWorkBudget - maximumAcquisitionCost == 1'048'570);
|
||||
profile.hostWorkBasePerCall =
|
||||
std::numeric_limits<std::uint64_t>::max() - 1;
|
||||
profile.hostWorkPerAddressedByte = 2;
|
||||
BEAST_EXPECT(
|
||||
quickJSHostWorkCost(profile, 2) ==
|
||||
std::numeric_limits<std::uint64_t>::max());
|
||||
|
||||
testcase("Missing required v1 binding fails closed");
|
||||
auto brokenImports =
|
||||
std::array<WasmtimeHostBinding, quickJSV1ImportCount>{};
|
||||
std::copy(
|
||||
policy->imports.begin(),
|
||||
policy->imports.end(),
|
||||
brokenImports.begin());
|
||||
brokenImports.front().operation = nullptr;
|
||||
auto broken = *policy;
|
||||
broken.imports = brokenImports;
|
||||
BEAST_EXPECT(!broken.complete());
|
||||
std::string error;
|
||||
BEAST_EXPECT(!defineImports(nullptr, broken, error));
|
||||
BEAST_EXPECT(!error.empty());
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(QuickJSHostPolicy, app, ripple);
|
||||
|
||||
} // namespace ripple::test
|
||||
18
src/test/app/jshooks/raw-catalogue/README.md
Normal file
18
src/test/app/jshooks/raw-catalogue/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Independent Wasmtime host-catalogue fixture
|
||||
|
||||
`generate.py` uses Clang's AST for the public `hook/extern.h` declarations,
|
||||
generates one retained/exported wrapper per function, and compiles an
|
||||
independent Wasm test guest with one callable wrapper per host import. It does
|
||||
not read `hook_api.macro` or the runtime descriptor table, so binding it through
|
||||
the live generic Wasmtime callback is independent evidence rather than a
|
||||
generator testing itself.
|
||||
|
||||
This fixture tests the host adapter only. It is not a selectable C-Hook
|
||||
Wasmtime engine and is not production execution machinery.
|
||||
|
||||
Regenerate with `./generate.py`. By default `wasmcc` from the active toolchain
|
||||
parses the declarations and builds the guest, avoiding a host-Clang dependency.
|
||||
The generated-header workflow runs `./generate.py --check`; the recorded
|
||||
compiler identities, flags, declaration hash, and artifact hashes in
|
||||
`manifest.json` make toolchain drift fail freshness rather than silently
|
||||
rewriting the fixture.
|
||||
544
src/test/app/jshooks/raw-catalogue/catalogue.c
Normal file
544
src/test/app/jshooks/raw-catalogue/catalogue.c
Normal file
@@ -0,0 +1,544 @@
|
||||
/* Generated by generate.py from hook/extern.h. Do not edit. */
|
||||
#include "extern.h"
|
||||
|
||||
#define RAW_EXPORT __attribute__((noinline, used))
|
||||
|
||||
RAW_EXPORT
|
||||
int32_t call__g(void)
|
||||
{
|
||||
return _g((uint32_t)0, (uint32_t)1);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_accept(void)
|
||||
{
|
||||
return accept((uint32_t)0, (uint32_t)0, (int64_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_rollback(void)
|
||||
{
|
||||
return rollback((uint32_t)0, (uint32_t)0, (int64_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_util_raddr(void)
|
||||
{
|
||||
return util_raddr((uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_util_accid(void)
|
||||
{
|
||||
return util_accid((uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_util_verify(void)
|
||||
{
|
||||
return util_verify((uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_util_sha512h(void)
|
||||
{
|
||||
return util_sha512h((uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_util_keylet(void)
|
||||
{
|
||||
return util_keylet((uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_sto_validate(void)
|
||||
{
|
||||
return sto_validate((uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_sto_subfield(void)
|
||||
{
|
||||
return sto_subfield((uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_sto_subarray(void)
|
||||
{
|
||||
return sto_subarray((uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_sto_emplace(void)
|
||||
{
|
||||
return sto_emplace((uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_sto_erase(void)
|
||||
{
|
||||
return sto_erase((uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_etxn_burden(void)
|
||||
{
|
||||
return etxn_burden();
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_etxn_details(void)
|
||||
{
|
||||
return etxn_details((uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_etxn_fee_base(void)
|
||||
{
|
||||
return etxn_fee_base((uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_etxn_reserve(void)
|
||||
{
|
||||
return etxn_reserve((uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_etxn_generation(void)
|
||||
{
|
||||
return etxn_generation();
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_etxn_nonce(void)
|
||||
{
|
||||
return etxn_nonce((uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_emit(void)
|
||||
{
|
||||
return emit((uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_float_set(void)
|
||||
{
|
||||
return float_set((int32_t)0, (int64_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_float_multiply(void)
|
||||
{
|
||||
return float_multiply((int64_t)0, (int64_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_float_mulratio(void)
|
||||
{
|
||||
return float_mulratio((int64_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_float_negate(void)
|
||||
{
|
||||
return float_negate((int64_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_float_compare(void)
|
||||
{
|
||||
return float_compare((int64_t)0, (int64_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_float_sum(void)
|
||||
{
|
||||
return float_sum((int64_t)0, (int64_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_float_sto(void)
|
||||
{
|
||||
return float_sto((uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (int64_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_float_sto_set(void)
|
||||
{
|
||||
return float_sto_set((uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_float_invert(void)
|
||||
{
|
||||
return float_invert((int64_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_float_divide(void)
|
||||
{
|
||||
return float_divide((int64_t)0, (int64_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_float_one(void)
|
||||
{
|
||||
return float_one();
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_float_mantissa(void)
|
||||
{
|
||||
return float_mantissa((int64_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_float_sign(void)
|
||||
{
|
||||
return float_sign((int64_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_float_int(void)
|
||||
{
|
||||
return float_int((int64_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_float_log(void)
|
||||
{
|
||||
return float_log((int64_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_float_root(void)
|
||||
{
|
||||
return float_root((int64_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_fee_base(void)
|
||||
{
|
||||
return fee_base();
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_ledger_seq(void)
|
||||
{
|
||||
return ledger_seq();
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_ledger_last_time(void)
|
||||
{
|
||||
return ledger_last_time();
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_ledger_last_hash(void)
|
||||
{
|
||||
return ledger_last_hash((uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_ledger_nonce(void)
|
||||
{
|
||||
return ledger_nonce((uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_ledger_keylet(void)
|
||||
{
|
||||
return ledger_keylet((uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_hook_account(void)
|
||||
{
|
||||
return hook_account((uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_hook_hash(void)
|
||||
{
|
||||
return hook_hash((uint32_t)0, (uint32_t)0, (int32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_hook_param_set(void)
|
||||
{
|
||||
return hook_param_set((uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_hook_param(void)
|
||||
{
|
||||
return hook_param((uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_hook_again(void)
|
||||
{
|
||||
return hook_again();
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_hook_skip(void)
|
||||
{
|
||||
return hook_skip((uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_hook_pos(void)
|
||||
{
|
||||
return hook_pos();
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_slot(void)
|
||||
{
|
||||
return slot((uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_slot_clear(void)
|
||||
{
|
||||
return slot_clear((uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_slot_count(void)
|
||||
{
|
||||
return slot_count((uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_slot_set(void)
|
||||
{
|
||||
return slot_set((uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_slot_size(void)
|
||||
{
|
||||
return slot_size((uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_slot_subarray(void)
|
||||
{
|
||||
return slot_subarray((uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_slot_subfield(void)
|
||||
{
|
||||
return slot_subfield((uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_slot_type(void)
|
||||
{
|
||||
return slot_type((uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_slot_float(void)
|
||||
{
|
||||
return slot_float((uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_state_set(void)
|
||||
{
|
||||
return state_set((uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_state_foreign_set(void)
|
||||
{
|
||||
return state_foreign_set((uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_state(void)
|
||||
{
|
||||
return state((uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_state_foreign(void)
|
||||
{
|
||||
return state_foreign((uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_trace(void)
|
||||
{
|
||||
return trace((uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_trace_num(void)
|
||||
{
|
||||
return trace_num((uint32_t)0, (uint32_t)0, (int64_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_trace_float(void)
|
||||
{
|
||||
return trace_float((uint32_t)0, (uint32_t)0, (int64_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_otxn_burden(void)
|
||||
{
|
||||
return otxn_burden();
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_otxn_field(void)
|
||||
{
|
||||
return otxn_field((uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_otxn_generation(void)
|
||||
{
|
||||
return otxn_generation();
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_otxn_id(void)
|
||||
{
|
||||
return otxn_id((uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_otxn_type(void)
|
||||
{
|
||||
return otxn_type();
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_otxn_slot(void)
|
||||
{
|
||||
return otxn_slot((uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_otxn_param(void)
|
||||
{
|
||||
return otxn_param((uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_meta_slot(void)
|
||||
{
|
||||
return meta_slot((uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_xpop_slot(void)
|
||||
{
|
||||
return xpop_slot((uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t call_prepare(void)
|
||||
{
|
||||
return prepare((uint32_t)0, (uint32_t)0, (uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int32_t call__g_rollback(void)
|
||||
{
|
||||
return _g((uint32_t)0, (uint32_t)0);
|
||||
}
|
||||
|
||||
RAW_EXPORT
|
||||
int64_t hook(uint32_t selector)
|
||||
{
|
||||
switch (selector)
|
||||
{
|
||||
case 0: return call__g();
|
||||
case 1: return call_accept();
|
||||
case 2: return call_rollback();
|
||||
case 3: return call_util_raddr();
|
||||
case 4: return call_util_accid();
|
||||
case 5: return call_util_verify();
|
||||
case 6: return call_util_sha512h();
|
||||
case 7: return call_util_keylet();
|
||||
case 8: return call_sto_validate();
|
||||
case 9: return call_sto_subfield();
|
||||
case 10: return call_sto_subarray();
|
||||
case 11: return call_sto_emplace();
|
||||
case 12: return call_sto_erase();
|
||||
case 13: return call_etxn_burden();
|
||||
case 14: return call_etxn_details();
|
||||
case 15: return call_etxn_fee_base();
|
||||
case 16: return call_etxn_reserve();
|
||||
case 17: return call_etxn_generation();
|
||||
case 18: return call_etxn_nonce();
|
||||
case 19: return call_emit();
|
||||
case 20: return call_float_set();
|
||||
case 21: return call_float_multiply();
|
||||
case 22: return call_float_mulratio();
|
||||
case 23: return call_float_negate();
|
||||
case 24: return call_float_compare();
|
||||
case 25: return call_float_sum();
|
||||
case 26: return call_float_sto();
|
||||
case 27: return call_float_sto_set();
|
||||
case 28: return call_float_invert();
|
||||
case 29: return call_float_divide();
|
||||
case 30: return call_float_one();
|
||||
case 31: return call_float_mantissa();
|
||||
case 32: return call_float_sign();
|
||||
case 33: return call_float_int();
|
||||
case 34: return call_float_log();
|
||||
case 35: return call_float_root();
|
||||
case 36: return call_fee_base();
|
||||
case 37: return call_ledger_seq();
|
||||
case 38: return call_ledger_last_time();
|
||||
case 39: return call_ledger_last_hash();
|
||||
case 40: return call_ledger_nonce();
|
||||
case 41: return call_ledger_keylet();
|
||||
case 42: return call_hook_account();
|
||||
case 43: return call_hook_hash();
|
||||
case 44: return call_hook_param_set();
|
||||
case 45: return call_hook_param();
|
||||
case 46: return call_hook_again();
|
||||
case 47: return call_hook_skip();
|
||||
case 48: return call_hook_pos();
|
||||
case 49: return call_slot();
|
||||
case 50: return call_slot_clear();
|
||||
case 51: return call_slot_count();
|
||||
case 52: return call_slot_set();
|
||||
case 53: return call_slot_size();
|
||||
case 54: return call_slot_subarray();
|
||||
case 55: return call_slot_subfield();
|
||||
case 56: return call_slot_type();
|
||||
case 57: return call_slot_float();
|
||||
case 58: return call_state_set();
|
||||
case 59: return call_state_foreign_set();
|
||||
case 60: return call_state();
|
||||
case 61: return call_state_foreign();
|
||||
case 62: return call_trace();
|
||||
case 63: return call_trace_num();
|
||||
case 64: return call_trace_float();
|
||||
case 65: return call_otxn_burden();
|
||||
case 66: return call_otxn_field();
|
||||
case 67: return call_otxn_generation();
|
||||
case 68: return call_otxn_id();
|
||||
case 69: return call_otxn_type();
|
||||
case 70: return call_otxn_slot();
|
||||
case 71: return call_otxn_param();
|
||||
case 72: return call_meta_slot();
|
||||
case 73: return call_xpop_slot();
|
||||
case 74: return call_prepare();
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
773
src/test/app/jshooks/raw-catalogue/catalogue_wasm.h
Normal file
773
src/test/app/jshooks/raw-catalogue/catalogue_wasm.h
Normal file
@@ -0,0 +1,773 @@
|
||||
// Generated by generate.py. Do not edit.
|
||||
#ifndef XAHAU_TEST_RAW_HOOK_CATALOGUE_WASM_H_INCLUDED
|
||||
#define XAHAU_TEST_RAW_HOOK_CATALOGUE_WASM_H_INCLUDED
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
namespace ripple::test::raw_hook_catalogue_fixture {
|
||||
inline constexpr std::array<std::uint8_t, 7507> wasm{{
|
||||
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, 0xA3U, 0x01U,
|
||||
0x16U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x03U, 0x7FU, 0x7FU,
|
||||
0x7EU, 0x01U, 0x7EU, 0x60U, 0x04U, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU,
|
||||
0x60U, 0x06U, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U,
|
||||
0x09U, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x01U,
|
||||
0x7EU, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x03U, 0x7FU, 0x7FU,
|
||||
0x7FU, 0x01U, 0x7EU, 0x60U, 0x07U, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU,
|
||||
0x7FU, 0x01U, 0x7EU, 0x60U, 0x05U, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x01U,
|
||||
0x7EU, 0x60U, 0x00U, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x60U,
|
||||
0x02U, 0x7FU, 0x7EU, 0x01U, 0x7EU, 0x60U, 0x02U, 0x7EU, 0x7EU, 0x01U, 0x7EU,
|
||||
0x60U, 0x04U, 0x7EU, 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7EU,
|
||||
0x01U, 0x7EU, 0x60U, 0x03U, 0x7EU, 0x7EU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x08U,
|
||||
0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7EU, 0x7FU, 0x01U, 0x7EU, 0x60U,
|
||||
0x03U, 0x7EU, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U, 0x02U, 0x7EU, 0x7FU, 0x01U,
|
||||
0x7EU, 0x60U, 0x08U, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x7FU,
|
||||
0x01U, 0x7EU, 0x60U, 0x00U, 0x00U, 0x60U, 0x00U, 0x01U, 0x7FU, 0x02U, 0x99U,
|
||||
0x0AU, 0x4BU, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, 0x00U, 0x00U,
|
||||
0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U,
|
||||
0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x08U, 0x72U, 0x6FU, 0x6CU, 0x6CU,
|
||||
0x62U, 0x61U, 0x63U, 0x6BU, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0AU,
|
||||
0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x72U, 0x61U, 0x64U, 0x64U, 0x72U, 0x00U,
|
||||
0x02U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0AU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU,
|
||||
0x61U, 0x63U, 0x63U, 0x69U, 0x64U, 0x00U, 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U,
|
||||
0x0BU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x76U, 0x65U, 0x72U, 0x69U, 0x66U,
|
||||
0x79U, 0x00U, 0x03U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0CU, 0x75U, 0x74U, 0x69U,
|
||||
0x6CU, 0x5FU, 0x73U, 0x68U, 0x61U, 0x35U, 0x31U, 0x32U, 0x68U, 0x00U, 0x02U,
|
||||
0x03U, 0x65U, 0x6EU, 0x76U, 0x0BU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x6BU,
|
||||
0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x00U, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U,
|
||||
0x0CU, 0x73U, 0x74U, 0x6FU, 0x5FU, 0x76U, 0x61U, 0x6CU, 0x69U, 0x64U, 0x61U,
|
||||
0x74U, 0x65U, 0x00U, 0x05U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0CU, 0x73U, 0x74U,
|
||||
0x6FU, 0x5FU, 0x73U, 0x75U, 0x62U, 0x66U, 0x69U, 0x65U, 0x6CU, 0x64U, 0x00U,
|
||||
0x06U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0CU, 0x73U, 0x74U, 0x6FU, 0x5FU, 0x73U,
|
||||
0x75U, 0x62U, 0x61U, 0x72U, 0x72U, 0x61U, 0x79U, 0x00U, 0x06U, 0x03U, 0x65U,
|
||||
0x6EU, 0x76U, 0x0BU, 0x73U, 0x74U, 0x6FU, 0x5FU, 0x65U, 0x6DU, 0x70U, 0x6CU,
|
||||
0x61U, 0x63U, 0x65U, 0x00U, 0x07U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x09U, 0x73U,
|
||||
0x74U, 0x6FU, 0x5FU, 0x65U, 0x72U, 0x61U, 0x73U, 0x65U, 0x00U, 0x08U, 0x03U,
|
||||
0x65U, 0x6EU, 0x76U, 0x0BU, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x62U, 0x75U,
|
||||
0x72U, 0x64U, 0x65U, 0x6EU, 0x00U, 0x09U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0CU,
|
||||
0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x64U, 0x65U, 0x74U, 0x61U, 0x69U, 0x6CU,
|
||||
0x73U, 0x00U, 0x05U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0DU, 0x65U, 0x74U, 0x78U,
|
||||
0x6EU, 0x5FU, 0x66U, 0x65U, 0x65U, 0x5FU, 0x62U, 0x61U, 0x73U, 0x65U, 0x00U,
|
||||
0x05U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0CU, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU,
|
||||
0x72U, 0x65U, 0x73U, 0x65U, 0x72U, 0x76U, 0x65U, 0x00U, 0x0AU, 0x03U, 0x65U,
|
||||
0x6EU, 0x76U, 0x0FU, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x67U, 0x65U, 0x6EU,
|
||||
0x65U, 0x72U, 0x61U, 0x74U, 0x69U, 0x6FU, 0x6EU, 0x00U, 0x09U, 0x03U, 0x65U,
|
||||
0x6EU, 0x76U, 0x0AU, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x6EU, 0x6FU, 0x6EU,
|
||||
0x63U, 0x65U, 0x00U, 0x05U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x04U, 0x65U, 0x6DU,
|
||||
0x69U, 0x74U, 0x00U, 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x09U, 0x66U, 0x6CU,
|
||||
0x6FU, 0x61U, 0x74U, 0x5FU, 0x73U, 0x65U, 0x74U, 0x00U, 0x0BU, 0x03U, 0x65U,
|
||||
0x6EU, 0x76U, 0x0EU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x6DU, 0x75U,
|
||||
0x6CU, 0x74U, 0x69U, 0x70U, 0x6CU, 0x79U, 0x00U, 0x0CU, 0x03U, 0x65U, 0x6EU,
|
||||
0x76U, 0x0EU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x6DU, 0x75U, 0x6CU,
|
||||
0x72U, 0x61U, 0x74U, 0x69U, 0x6FU, 0x00U, 0x0DU, 0x03U, 0x65U, 0x6EU, 0x76U,
|
||||
0x0CU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x6EU, 0x65U, 0x67U, 0x61U,
|
||||
0x74U, 0x65U, 0x00U, 0x0EU, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0DU, 0x66U, 0x6CU,
|
||||
0x6FU, 0x61U, 0x74U, 0x5FU, 0x63U, 0x6FU, 0x6DU, 0x70U, 0x61U, 0x72U, 0x65U,
|
||||
0x00U, 0x0FU, 0x03U, 0x65U, 0x6EU, 0x76U, 0x09U, 0x66U, 0x6CU, 0x6FU, 0x61U,
|
||||
0x74U, 0x5FU, 0x73U, 0x75U, 0x6DU, 0x00U, 0x0CU, 0x03U, 0x65U, 0x6EU, 0x76U,
|
||||
0x09U, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x73U, 0x74U, 0x6FU, 0x00U,
|
||||
0x10U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0DU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U,
|
||||
0x5FU, 0x73U, 0x74U, 0x6FU, 0x5FU, 0x73U, 0x65U, 0x74U, 0x00U, 0x05U, 0x03U,
|
||||
0x65U, 0x6EU, 0x76U, 0x0CU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x69U,
|
||||
0x6EU, 0x76U, 0x65U, 0x72U, 0x74U, 0x00U, 0x0EU, 0x03U, 0x65U, 0x6EU, 0x76U,
|
||||
0x0CU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x64U, 0x69U, 0x76U, 0x69U,
|
||||
0x64U, 0x65U, 0x00U, 0x0CU, 0x03U, 0x65U, 0x6EU, 0x76U, 0x09U, 0x66U, 0x6CU,
|
||||
0x6FU, 0x61U, 0x74U, 0x5FU, 0x6FU, 0x6EU, 0x65U, 0x00U, 0x09U, 0x03U, 0x65U,
|
||||
0x6EU, 0x76U, 0x0EU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x6DU, 0x61U,
|
||||
0x6EU, 0x74U, 0x69U, 0x73U, 0x73U, 0x61U, 0x00U, 0x0EU, 0x03U, 0x65U, 0x6EU,
|
||||
0x76U, 0x0AU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x73U, 0x69U, 0x67U,
|
||||
0x6EU, 0x00U, 0x0EU, 0x03U, 0x65U, 0x6EU, 0x76U, 0x09U, 0x66U, 0x6CU, 0x6FU,
|
||||
0x61U, 0x74U, 0x5FU, 0x69U, 0x6EU, 0x74U, 0x00U, 0x11U, 0x03U, 0x65U, 0x6EU,
|
||||
0x76U, 0x09U, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x6CU, 0x6FU, 0x67U,
|
||||
0x00U, 0x0EU, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0AU, 0x66U, 0x6CU, 0x6FU, 0x61U,
|
||||
0x74U, 0x5FU, 0x72U, 0x6FU, 0x6FU, 0x74U, 0x00U, 0x12U, 0x03U, 0x65U, 0x6EU,
|
||||
0x76U, 0x08U, 0x66U, 0x65U, 0x65U, 0x5FU, 0x62U, 0x61U, 0x73U, 0x65U, 0x00U,
|
||||
0x09U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0AU, 0x6CU, 0x65U, 0x64U, 0x67U, 0x65U,
|
||||
0x72U, 0x5FU, 0x73U, 0x65U, 0x71U, 0x00U, 0x09U, 0x03U, 0x65U, 0x6EU, 0x76U,
|
||||
0x10U, 0x6CU, 0x65U, 0x64U, 0x67U, 0x65U, 0x72U, 0x5FU, 0x6CU, 0x61U, 0x73U,
|
||||
0x74U, 0x5FU, 0x74U, 0x69U, 0x6DU, 0x65U, 0x00U, 0x09U, 0x03U, 0x65U, 0x6EU,
|
||||
0x76U, 0x10U, 0x6CU, 0x65U, 0x64U, 0x67U, 0x65U, 0x72U, 0x5FU, 0x6CU, 0x61U,
|
||||
0x73U, 0x74U, 0x5FU, 0x68U, 0x61U, 0x73U, 0x68U, 0x00U, 0x05U, 0x03U, 0x65U,
|
||||
0x6EU, 0x76U, 0x0CU, 0x6CU, 0x65U, 0x64U, 0x67U, 0x65U, 0x72U, 0x5FU, 0x6EU,
|
||||
0x6FU, 0x6EU, 0x63U, 0x65U, 0x00U, 0x05U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0DU,
|
||||
0x6CU, 0x65U, 0x64U, 0x67U, 0x65U, 0x72U, 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU,
|
||||
0x65U, 0x74U, 0x00U, 0x03U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0CU, 0x68U, 0x6FU,
|
||||
0x6FU, 0x6BU, 0x5FU, 0x61U, 0x63U, 0x63U, 0x6FU, 0x75U, 0x6EU, 0x74U, 0x00U,
|
||||
0x05U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x09U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU,
|
||||
0x68U, 0x61U, 0x73U, 0x68U, 0x00U, 0x06U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0EU,
|
||||
0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x70U, 0x61U, 0x72U, 0x61U, 0x6DU, 0x5FU,
|
||||
0x73U, 0x65U, 0x74U, 0x00U, 0x03U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0AU, 0x68U,
|
||||
0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x70U, 0x61U, 0x72U, 0x61U, 0x6DU, 0x00U, 0x02U,
|
||||
0x03U, 0x65U, 0x6EU, 0x76U, 0x0AU, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x61U,
|
||||
0x67U, 0x61U, 0x69U, 0x6EU, 0x00U, 0x09U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x09U,
|
||||
0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x73U, 0x6BU, 0x69U, 0x70U, 0x00U, 0x06U,
|
||||
0x03U, 0x65U, 0x6EU, 0x76U, 0x08U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x70U,
|
||||
0x6FU, 0x73U, 0x00U, 0x09U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x04U, 0x73U, 0x6CU,
|
||||
0x6FU, 0x74U, 0x00U, 0x06U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0AU, 0x73U, 0x6CU,
|
||||
0x6FU, 0x74U, 0x5FU, 0x63U, 0x6CU, 0x65U, 0x61U, 0x72U, 0x00U, 0x0AU, 0x03U,
|
||||
0x65U, 0x6EU, 0x76U, 0x0AU, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x63U, 0x6FU,
|
||||
0x75U, 0x6EU, 0x74U, 0x00U, 0x0AU, 0x03U, 0x65U, 0x6EU, 0x76U, 0x08U, 0x73U,
|
||||
0x6CU, 0x6FU, 0x74U, 0x5FU, 0x73U, 0x65U, 0x74U, 0x00U, 0x06U, 0x03U, 0x65U,
|
||||
0x6EU, 0x76U, 0x09U, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x73U, 0x69U, 0x7AU,
|
||||
0x65U, 0x00U, 0x0AU, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0DU, 0x73U, 0x6CU, 0x6FU,
|
||||
0x74U, 0x5FU, 0x73U, 0x75U, 0x62U, 0x61U, 0x72U, 0x72U, 0x61U, 0x79U, 0x00U,
|
||||
0x06U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0DU, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU,
|
||||
0x73U, 0x75U, 0x62U, 0x66U, 0x69U, 0x65U, 0x6CU, 0x64U, 0x00U, 0x06U, 0x03U,
|
||||
0x65U, 0x6EU, 0x76U, 0x09U, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x74U, 0x79U,
|
||||
0x70U, 0x65U, 0x00U, 0x05U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0AU, 0x73U, 0x6CU,
|
||||
0x6FU, 0x74U, 0x5FU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x00U, 0x0AU, 0x03U,
|
||||
0x65U, 0x6EU, 0x76U, 0x09U, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x73U,
|
||||
0x65U, 0x74U, 0x00U, 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x11U, 0x73U, 0x74U,
|
||||
0x61U, 0x74U, 0x65U, 0x5FU, 0x66U, 0x6FU, 0x72U, 0x65U, 0x69U, 0x67U, 0x6EU,
|
||||
0x5FU, 0x73U, 0x65U, 0x74U, 0x00U, 0x13U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x05U,
|
||||
0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x00U, 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U,
|
||||
0x0DU, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x66U, 0x6FU, 0x72U, 0x65U,
|
||||
0x69U, 0x67U, 0x6EU, 0x00U, 0x13U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x05U, 0x74U,
|
||||
0x72U, 0x61U, 0x63U, 0x65U, 0x00U, 0x08U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x09U,
|
||||
0x74U, 0x72U, 0x61U, 0x63U, 0x65U, 0x5FU, 0x6EU, 0x75U, 0x6DU, 0x00U, 0x01U,
|
||||
0x03U, 0x65U, 0x6EU, 0x76U, 0x0BU, 0x74U, 0x72U, 0x61U, 0x63U, 0x65U, 0x5FU,
|
||||
0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U,
|
||||
0x0BU, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x62U, 0x75U, 0x72U, 0x64U, 0x65U,
|
||||
0x6EU, 0x00U, 0x09U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0AU, 0x6FU, 0x74U, 0x78U,
|
||||
0x6EU, 0x5FU, 0x66U, 0x69U, 0x65U, 0x6CU, 0x64U, 0x00U, 0x06U, 0x03U, 0x65U,
|
||||
0x6EU, 0x76U, 0x0FU, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x67U, 0x65U, 0x6EU,
|
||||
0x65U, 0x72U, 0x61U, 0x74U, 0x69U, 0x6FU, 0x6EU, 0x00U, 0x09U, 0x03U, 0x65U,
|
||||
0x6EU, 0x76U, 0x07U, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x69U, 0x64U, 0x00U,
|
||||
0x06U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x09U, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU,
|
||||
0x74U, 0x79U, 0x70U, 0x65U, 0x00U, 0x09U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x09U,
|
||||
0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x00U, 0x0AU,
|
||||
0x03U, 0x65U, 0x6EU, 0x76U, 0x0AU, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x70U,
|
||||
0x61U, 0x72U, 0x61U, 0x6DU, 0x00U, 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x09U,
|
||||
0x6DU, 0x65U, 0x74U, 0x61U, 0x5FU, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x00U, 0x0AU,
|
||||
0x03U, 0x65U, 0x6EU, 0x76U, 0x09U, 0x78U, 0x70U, 0x6FU, 0x70U, 0x5FU, 0x73U,
|
||||
0x6CU, 0x6FU, 0x74U, 0x00U, 0x05U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x07U, 0x70U,
|
||||
0x72U, 0x65U, 0x70U, 0x61U, 0x72U, 0x65U, 0x00U, 0x02U, 0x03U, 0x4FU, 0x4EU,
|
||||
0x14U, 0x15U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U,
|
||||
0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U,
|
||||
0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U,
|
||||
0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U,
|
||||
0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U,
|
||||
0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U,
|
||||
0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x09U, 0x15U,
|
||||
0x0AU, 0x04U, 0x05U, 0x01U, 0x70U, 0x01U, 0x01U, 0x01U, 0x05U, 0x03U, 0x01U,
|
||||
0x00U, 0x02U, 0x06U, 0x21U, 0x05U, 0x7FU, 0x01U, 0x41U, 0x80U, 0x88U, 0x04U,
|
||||
0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U,
|
||||
0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x88U, 0x04U, 0x0BU, 0x7FU, 0x00U,
|
||||
0x41U, 0x80U, 0x08U, 0x0BU, 0x07U, 0xEEU, 0x0BU, 0x53U, 0x06U, 0x6DU, 0x65U,
|
||||
0x6DU, 0x6FU, 0x72U, 0x79U, 0x02U, 0x00U, 0x11U, 0x5FU, 0x5FU, 0x77U, 0x61U,
|
||||
0x73U, 0x6DU, 0x5FU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x63U, 0x74U, 0x6FU,
|
||||
0x72U, 0x73U, 0x00U, 0x4BU, 0x0AU, 0x5FU, 0x5FU, 0x64U, 0x61U, 0x74U, 0x61U,
|
||||
0x5FU, 0x65U, 0x6EU, 0x64U, 0x03U, 0x01U, 0x0DU, 0x5FU, 0x5FU, 0x67U, 0x6CU,
|
||||
0x6FU, 0x62U, 0x61U, 0x6CU, 0x5FU, 0x62U, 0x61U, 0x73U, 0x65U, 0x03U, 0x02U,
|
||||
0x0BU, 0x5FU, 0x5FU, 0x68U, 0x65U, 0x61U, 0x70U, 0x5FU, 0x62U, 0x61U, 0x73U,
|
||||
0x65U, 0x03U, 0x03U, 0x0CU, 0x5FU, 0x5FU, 0x64U, 0x73U, 0x6FU, 0x5FU, 0x68U,
|
||||
0x61U, 0x6EU, 0x64U, 0x6CU, 0x65U, 0x03U, 0x04U, 0x07U, 0x63U, 0x61U, 0x6CU,
|
||||
0x6CU, 0x5FU, 0x5FU, 0x67U, 0x00U, 0x4CU, 0x0BU, 0x63U, 0x61U, 0x6CU, 0x6CU,
|
||||
0x5FU, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x4DU, 0x0DU, 0x63U,
|
||||
0x61U, 0x6CU, 0x6CU, 0x5FU, 0x72U, 0x6FU, 0x6CU, 0x6CU, 0x62U, 0x61U, 0x63U,
|
||||
0x6BU, 0x00U, 0x4EU, 0x0FU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x75U, 0x74U,
|
||||
0x69U, 0x6CU, 0x5FU, 0x72U, 0x61U, 0x64U, 0x64U, 0x72U, 0x00U, 0x4FU, 0x0FU,
|
||||
0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x61U,
|
||||
0x63U, 0x63U, 0x69U, 0x64U, 0x00U, 0x50U, 0x10U, 0x63U, 0x61U, 0x6CU, 0x6CU,
|
||||
0x5FU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x76U, 0x65U, 0x72U, 0x69U, 0x66U,
|
||||
0x79U, 0x00U, 0x51U, 0x11U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x75U, 0x74U,
|
||||
0x69U, 0x6CU, 0x5FU, 0x73U, 0x68U, 0x61U, 0x35U, 0x31U, 0x32U, 0x68U, 0x00U,
|
||||
0x52U, 0x10U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x75U, 0x74U, 0x69U, 0x6CU,
|
||||
0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x00U, 0x53U, 0x11U, 0x63U,
|
||||
0x61U, 0x6CU, 0x6CU, 0x5FU, 0x73U, 0x74U, 0x6FU, 0x5FU, 0x76U, 0x61U, 0x6CU,
|
||||
0x69U, 0x64U, 0x61U, 0x74U, 0x65U, 0x00U, 0x54U, 0x11U, 0x63U, 0x61U, 0x6CU,
|
||||
0x6CU, 0x5FU, 0x73U, 0x74U, 0x6FU, 0x5FU, 0x73U, 0x75U, 0x62U, 0x66U, 0x69U,
|
||||
0x65U, 0x6CU, 0x64U, 0x00U, 0x55U, 0x11U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU,
|
||||
0x73U, 0x74U, 0x6FU, 0x5FU, 0x73U, 0x75U, 0x62U, 0x61U, 0x72U, 0x72U, 0x61U,
|
||||
0x79U, 0x00U, 0x56U, 0x10U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x73U, 0x74U,
|
||||
0x6FU, 0x5FU, 0x65U, 0x6DU, 0x70U, 0x6CU, 0x61U, 0x63U, 0x65U, 0x00U, 0x57U,
|
||||
0x0EU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x73U, 0x74U, 0x6FU, 0x5FU, 0x65U,
|
||||
0x72U, 0x61U, 0x73U, 0x65U, 0x00U, 0x58U, 0x10U, 0x63U, 0x61U, 0x6CU, 0x6CU,
|
||||
0x5FU, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x62U, 0x75U, 0x72U, 0x64U, 0x65U,
|
||||
0x6EU, 0x00U, 0x59U, 0x11U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x65U, 0x74U,
|
||||
0x78U, 0x6EU, 0x5FU, 0x64U, 0x65U, 0x74U, 0x61U, 0x69U, 0x6CU, 0x73U, 0x00U,
|
||||
0x5AU, 0x12U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x65U, 0x74U, 0x78U, 0x6EU,
|
||||
0x5FU, 0x66U, 0x65U, 0x65U, 0x5FU, 0x62U, 0x61U, 0x73U, 0x65U, 0x00U, 0x5BU,
|
||||
0x11U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU,
|
||||
0x72U, 0x65U, 0x73U, 0x65U, 0x72U, 0x76U, 0x65U, 0x00U, 0x5CU, 0x14U, 0x63U,
|
||||
0x61U, 0x6CU, 0x6CU, 0x5FU, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x67U, 0x65U,
|
||||
0x6EU, 0x65U, 0x72U, 0x61U, 0x74U, 0x69U, 0x6FU, 0x6EU, 0x00U, 0x5DU, 0x0FU,
|
||||
0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x6EU,
|
||||
0x6FU, 0x6EU, 0x63U, 0x65U, 0x00U, 0x5EU, 0x09U, 0x63U, 0x61U, 0x6CU, 0x6CU,
|
||||
0x5FU, 0x65U, 0x6DU, 0x69U, 0x74U, 0x00U, 0x5FU, 0x0EU, 0x63U, 0x61U, 0x6CU,
|
||||
0x6CU, 0x5FU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x73U, 0x65U, 0x74U,
|
||||
0x00U, 0x60U, 0x13U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x66U, 0x6CU, 0x6FU,
|
||||
0x61U, 0x74U, 0x5FU, 0x6DU, 0x75U, 0x6CU, 0x74U, 0x69U, 0x70U, 0x6CU, 0x79U,
|
||||
0x00U, 0x61U, 0x13U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x66U, 0x6CU, 0x6FU,
|
||||
0x61U, 0x74U, 0x5FU, 0x6DU, 0x75U, 0x6CU, 0x72U, 0x61U, 0x74U, 0x69U, 0x6FU,
|
||||
0x00U, 0x62U, 0x11U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x66U, 0x6CU, 0x6FU,
|
||||
0x61U, 0x74U, 0x5FU, 0x6EU, 0x65U, 0x67U, 0x61U, 0x74U, 0x65U, 0x00U, 0x63U,
|
||||
0x12U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U,
|
||||
0x5FU, 0x63U, 0x6FU, 0x6DU, 0x70U, 0x61U, 0x72U, 0x65U, 0x00U, 0x64U, 0x0EU,
|
||||
0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU,
|
||||
0x73U, 0x75U, 0x6DU, 0x00U, 0x65U, 0x0EU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU,
|
||||
0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x73U, 0x74U, 0x6FU, 0x00U, 0x66U,
|
||||
0x12U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U,
|
||||
0x5FU, 0x73U, 0x74U, 0x6FU, 0x5FU, 0x73U, 0x65U, 0x74U, 0x00U, 0x67U, 0x11U,
|
||||
0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU,
|
||||
0x69U, 0x6EU, 0x76U, 0x65U, 0x72U, 0x74U, 0x00U, 0x68U, 0x11U, 0x63U, 0x61U,
|
||||
0x6CU, 0x6CU, 0x5FU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x64U, 0x69U,
|
||||
0x76U, 0x69U, 0x64U, 0x65U, 0x00U, 0x69U, 0x0EU, 0x63U, 0x61U, 0x6CU, 0x6CU,
|
||||
0x5FU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x6FU, 0x6EU, 0x65U, 0x00U,
|
||||
0x6AU, 0x13U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x66U, 0x6CU, 0x6FU, 0x61U,
|
||||
0x74U, 0x5FU, 0x6DU, 0x61U, 0x6EU, 0x74U, 0x69U, 0x73U, 0x73U, 0x61U, 0x00U,
|
||||
0x6BU, 0x0FU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x66U, 0x6CU, 0x6FU, 0x61U,
|
||||
0x74U, 0x5FU, 0x73U, 0x69U, 0x67U, 0x6EU, 0x00U, 0x6CU, 0x0EU, 0x63U, 0x61U,
|
||||
0x6CU, 0x6CU, 0x5FU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x69U, 0x6EU,
|
||||
0x74U, 0x00U, 0x6DU, 0x0EU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x66U, 0x6CU,
|
||||
0x6FU, 0x61U, 0x74U, 0x5FU, 0x6CU, 0x6FU, 0x67U, 0x00U, 0x6EU, 0x0FU, 0x63U,
|
||||
0x61U, 0x6CU, 0x6CU, 0x5FU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x72U,
|
||||
0x6FU, 0x6FU, 0x74U, 0x00U, 0x6FU, 0x0DU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU,
|
||||
0x66U, 0x65U, 0x65U, 0x5FU, 0x62U, 0x61U, 0x73U, 0x65U, 0x00U, 0x70U, 0x0FU,
|
||||
0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x6CU, 0x65U, 0x64U, 0x67U, 0x65U, 0x72U,
|
||||
0x5FU, 0x73U, 0x65U, 0x71U, 0x00U, 0x71U, 0x15U, 0x63U, 0x61U, 0x6CU, 0x6CU,
|
||||
0x5FU, 0x6CU, 0x65U, 0x64U, 0x67U, 0x65U, 0x72U, 0x5FU, 0x6CU, 0x61U, 0x73U,
|
||||
0x74U, 0x5FU, 0x74U, 0x69U, 0x6DU, 0x65U, 0x00U, 0x72U, 0x15U, 0x63U, 0x61U,
|
||||
0x6CU, 0x6CU, 0x5FU, 0x6CU, 0x65U, 0x64U, 0x67U, 0x65U, 0x72U, 0x5FU, 0x6CU,
|
||||
0x61U, 0x73U, 0x74U, 0x5FU, 0x68U, 0x61U, 0x73U, 0x68U, 0x00U, 0x73U, 0x11U,
|
||||
0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x6CU, 0x65U, 0x64U, 0x67U, 0x65U, 0x72U,
|
||||
0x5FU, 0x6EU, 0x6FU, 0x6EU, 0x63U, 0x65U, 0x00U, 0x74U, 0x12U, 0x63U, 0x61U,
|
||||
0x6CU, 0x6CU, 0x5FU, 0x6CU, 0x65U, 0x64U, 0x67U, 0x65U, 0x72U, 0x5FU, 0x6BU,
|
||||
0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x00U, 0x75U, 0x11U, 0x63U, 0x61U, 0x6CU,
|
||||
0x6CU, 0x5FU, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x61U, 0x63U, 0x63U, 0x6FU,
|
||||
0x75U, 0x6EU, 0x74U, 0x00U, 0x76U, 0x0EU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU,
|
||||
0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x68U, 0x61U, 0x73U, 0x68U, 0x00U, 0x77U,
|
||||
0x13U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU,
|
||||
0x70U, 0x61U, 0x72U, 0x61U, 0x6DU, 0x5FU, 0x73U, 0x65U, 0x74U, 0x00U, 0x78U,
|
||||
0x0FU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU,
|
||||
0x70U, 0x61U, 0x72U, 0x61U, 0x6DU, 0x00U, 0x79U, 0x0FU, 0x63U, 0x61U, 0x6CU,
|
||||
0x6CU, 0x5FU, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x61U, 0x67U, 0x61U, 0x69U,
|
||||
0x6EU, 0x00U, 0x7AU, 0x0EU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x68U, 0x6FU,
|
||||
0x6FU, 0x6BU, 0x5FU, 0x73U, 0x6BU, 0x69U, 0x70U, 0x00U, 0x7BU, 0x0DU, 0x63U,
|
||||
0x61U, 0x6CU, 0x6CU, 0x5FU, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x70U, 0x6FU,
|
||||
0x73U, 0x00U, 0x7CU, 0x09U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x73U, 0x6CU,
|
||||
0x6FU, 0x74U, 0x00U, 0x7DU, 0x0FU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x73U,
|
||||
0x6CU, 0x6FU, 0x74U, 0x5FU, 0x63U, 0x6CU, 0x65U, 0x61U, 0x72U, 0x00U, 0x7EU,
|
||||
0x0FU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU,
|
||||
0x63U, 0x6FU, 0x75U, 0x6EU, 0x74U, 0x00U, 0x7FU, 0x0DU, 0x63U, 0x61U, 0x6CU,
|
||||
0x6CU, 0x5FU, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x73U, 0x65U, 0x74U, 0x00U,
|
||||
0x80U, 0x01U, 0x0EU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x73U, 0x6CU, 0x6FU,
|
||||
0x74U, 0x5FU, 0x73U, 0x69U, 0x7AU, 0x65U, 0x00U, 0x81U, 0x01U, 0x12U, 0x63U,
|
||||
0x61U, 0x6CU, 0x6CU, 0x5FU, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x73U, 0x75U,
|
||||
0x62U, 0x61U, 0x72U, 0x72U, 0x61U, 0x79U, 0x00U, 0x82U, 0x01U, 0x12U, 0x63U,
|
||||
0x61U, 0x6CU, 0x6CU, 0x5FU, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x73U, 0x75U,
|
||||
0x62U, 0x66U, 0x69U, 0x65U, 0x6CU, 0x64U, 0x00U, 0x83U, 0x01U, 0x0EU, 0x63U,
|
||||
0x61U, 0x6CU, 0x6CU, 0x5FU, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x74U, 0x79U,
|
||||
0x70U, 0x65U, 0x00U, 0x84U, 0x01U, 0x0FU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU,
|
||||
0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x00U,
|
||||
0x85U, 0x01U, 0x0EU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x73U, 0x74U, 0x61U,
|
||||
0x74U, 0x65U, 0x5FU, 0x73U, 0x65U, 0x74U, 0x00U, 0x86U, 0x01U, 0x16U, 0x63U,
|
||||
0x61U, 0x6CU, 0x6CU, 0x5FU, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x66U,
|
||||
0x6FU, 0x72U, 0x65U, 0x69U, 0x67U, 0x6EU, 0x5FU, 0x73U, 0x65U, 0x74U, 0x00U,
|
||||
0x87U, 0x01U, 0x0AU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x73U, 0x74U, 0x61U,
|
||||
0x74U, 0x65U, 0x00U, 0x88U, 0x01U, 0x12U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU,
|
||||
0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x66U, 0x6FU, 0x72U, 0x65U, 0x69U,
|
||||
0x67U, 0x6EU, 0x00U, 0x89U, 0x01U, 0x0AU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU,
|
||||
0x74U, 0x72U, 0x61U, 0x63U, 0x65U, 0x00U, 0x8AU, 0x01U, 0x0EU, 0x63U, 0x61U,
|
||||
0x6CU, 0x6CU, 0x5FU, 0x74U, 0x72U, 0x61U, 0x63U, 0x65U, 0x5FU, 0x6EU, 0x75U,
|
||||
0x6DU, 0x00U, 0x8BU, 0x01U, 0x10U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x74U,
|
||||
0x72U, 0x61U, 0x63U, 0x65U, 0x5FU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x00U,
|
||||
0x8CU, 0x01U, 0x10U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x6FU, 0x74U, 0x78U,
|
||||
0x6EU, 0x5FU, 0x62U, 0x75U, 0x72U, 0x64U, 0x65U, 0x6EU, 0x00U, 0x8DU, 0x01U,
|
||||
0x0FU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU,
|
||||
0x66U, 0x69U, 0x65U, 0x6CU, 0x64U, 0x00U, 0x8EU, 0x01U, 0x14U, 0x63U, 0x61U,
|
||||
0x6CU, 0x6CU, 0x5FU, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x67U, 0x65U, 0x6EU,
|
||||
0x65U, 0x72U, 0x61U, 0x74U, 0x69U, 0x6FU, 0x6EU, 0x00U, 0x8FU, 0x01U, 0x0CU,
|
||||
0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x69U,
|
||||
0x64U, 0x00U, 0x90U, 0x01U, 0x0EU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x6FU,
|
||||
0x74U, 0x78U, 0x6EU, 0x5FU, 0x74U, 0x79U, 0x70U, 0x65U, 0x00U, 0x91U, 0x01U,
|
||||
0x0EU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU,
|
||||
0x73U, 0x6CU, 0x6FU, 0x74U, 0x00U, 0x92U, 0x01U, 0x0FU, 0x63U, 0x61U, 0x6CU,
|
||||
0x6CU, 0x5FU, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x70U, 0x61U, 0x72U, 0x61U,
|
||||
0x6DU, 0x00U, 0x93U, 0x01U, 0x0EU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x6DU,
|
||||
0x65U, 0x74U, 0x61U, 0x5FU, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x00U, 0x94U, 0x01U,
|
||||
0x0EU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x78U, 0x70U, 0x6FU, 0x70U, 0x5FU,
|
||||
0x73U, 0x6CU, 0x6FU, 0x74U, 0x00U, 0x95U, 0x01U, 0x0CU, 0x63U, 0x61U, 0x6CU,
|
||||
0x6CU, 0x5FU, 0x70U, 0x72U, 0x65U, 0x70U, 0x61U, 0x72U, 0x65U, 0x00U, 0x96U,
|
||||
0x01U, 0x10U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x5FU, 0x67U, 0x5FU, 0x72U,
|
||||
0x6FU, 0x6CU, 0x6CU, 0x62U, 0x61U, 0x63U, 0x6BU, 0x00U, 0x97U, 0x01U, 0x04U,
|
||||
0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x98U, 0x01U, 0x0AU, 0x94U, 0x0FU, 0x4EU,
|
||||
0x02U, 0x00U, 0x0BU, 0x0CU, 0x00U, 0x41U, 0x00U, 0x41U, 0x01U, 0x10U, 0x80U,
|
||||
0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0EU, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U,
|
||||
0x42U, 0x00U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0EU, 0x00U,
|
||||
0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x00U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U,
|
||||
0x00U, 0x0BU, 0x10U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U,
|
||||
0x00U, 0x10U, 0x83U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x10U, 0x00U, 0x41U,
|
||||
0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x84U, 0x80U, 0x80U,
|
||||
0x80U, 0x00U, 0x0BU, 0x14U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U,
|
||||
0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x85U, 0x80U, 0x80U, 0x80U,
|
||||
0x00U, 0x0BU, 0x10U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U,
|
||||
0x00U, 0x10U, 0x86U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x1AU, 0x00U, 0x41U,
|
||||
0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U,
|
||||
0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x87U, 0x80U, 0x80U, 0x80U,
|
||||
0x00U, 0x0BU, 0x0CU, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x88U, 0x80U,
|
||||
0x80U, 0x80U, 0x00U, 0x0BU, 0x0EU, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U,
|
||||
0x00U, 0x10U, 0x89U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0EU, 0x00U, 0x41U,
|
||||
0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x8AU, 0x80U, 0x80U, 0x80U, 0x00U,
|
||||
0x0BU, 0x16U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U,
|
||||
0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x8BU, 0x80U, 0x80U, 0x80U,
|
||||
0x00U, 0x0BU, 0x12U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U,
|
||||
0x00U, 0x41U, 0x00U, 0x10U, 0x8CU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x08U,
|
||||
0x00U, 0x10U, 0x8DU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0CU, 0x00U, 0x41U,
|
||||
0x00U, 0x41U, 0x00U, 0x10U, 0x8EU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0CU,
|
||||
0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x8FU, 0x80U, 0x80U, 0x80U, 0x00U,
|
||||
0x0BU, 0x0AU, 0x00U, 0x41U, 0x00U, 0x10U, 0x90U, 0x80U, 0x80U, 0x80U, 0x00U,
|
||||
0x0BU, 0x08U, 0x00U, 0x10U, 0x91U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0CU,
|
||||
0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x92U, 0x80U, 0x80U, 0x80U, 0x00U,
|
||||
0x0BU, 0x10U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U,
|
||||
0x10U, 0x93U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0CU, 0x00U, 0x41U, 0x00U,
|
||||
0x42U, 0x00U, 0x10U, 0x94U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0CU, 0x00U,
|
||||
0x42U, 0x00U, 0x42U, 0x00U, 0x10U, 0x95U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU,
|
||||
0x10U, 0x00U, 0x42U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U,
|
||||
0x96U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0AU, 0x00U, 0x42U, 0x00U, 0x10U,
|
||||
0x97U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0EU, 0x00U, 0x42U, 0x00U, 0x42U,
|
||||
0x00U, 0x41U, 0x00U, 0x10U, 0x98U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0CU,
|
||||
0x00U, 0x42U, 0x00U, 0x42U, 0x00U, 0x10U, 0x99U, 0x80U, 0x80U, 0x80U, 0x00U,
|
||||
0x0BU, 0x18U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U,
|
||||
0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x00U, 0x41U, 0x00U, 0x10U, 0x9AU, 0x80U,
|
||||
0x80U, 0x80U, 0x00U, 0x0BU, 0x0CU, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U,
|
||||
0x9BU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0AU, 0x00U, 0x42U, 0x00U, 0x10U,
|
||||
0x9CU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0CU, 0x00U, 0x42U, 0x00U, 0x42U,
|
||||
0x00U, 0x10U, 0x9DU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x08U, 0x00U, 0x10U,
|
||||
0x9EU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0AU, 0x00U, 0x42U, 0x00U, 0x10U,
|
||||
0x9FU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0AU, 0x00U, 0x42U, 0x00U, 0x10U,
|
||||
0xA0U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0EU, 0x00U, 0x42U, 0x00U, 0x41U,
|
||||
0x00U, 0x41U, 0x00U, 0x10U, 0xA1U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0AU,
|
||||
0x00U, 0x42U, 0x00U, 0x10U, 0xA2U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0CU,
|
||||
0x00U, 0x42U, 0x00U, 0x41U, 0x00U, 0x10U, 0xA3U, 0x80U, 0x80U, 0x80U, 0x00U,
|
||||
0x0BU, 0x08U, 0x00U, 0x10U, 0xA4U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x08U,
|
||||
0x00U, 0x10U, 0xA5U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x08U, 0x00U, 0x10U,
|
||||
0xA6U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0CU, 0x00U, 0x41U, 0x00U, 0x41U,
|
||||
0x00U, 0x10U, 0xA7U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0CU, 0x00U, 0x41U,
|
||||
0x00U, 0x41U, 0x00U, 0x10U, 0xA8U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x14U,
|
||||
0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U,
|
||||
0x41U, 0x00U, 0x10U, 0xA9U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0CU, 0x00U,
|
||||
0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0xAAU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU,
|
||||
0x0EU, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0xABU, 0x80U,
|
||||
0x80U, 0x80U, 0x00U, 0x0BU, 0x14U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U,
|
||||
0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0xACU, 0x80U, 0x80U,
|
||||
0x80U, 0x00U, 0x0BU, 0x10U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U,
|
||||
0x41U, 0x00U, 0x10U, 0xADU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x08U, 0x00U,
|
||||
0x10U, 0xAEU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0EU, 0x00U, 0x41U, 0x00U,
|
||||
0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0xAFU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU,
|
||||
0x08U, 0x00U, 0x10U, 0xB0U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0EU, 0x00U,
|
||||
0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0xB1U, 0x80U, 0x80U, 0x80U,
|
||||
0x00U, 0x0BU, 0x0AU, 0x00U, 0x41U, 0x00U, 0x10U, 0xB2U, 0x80U, 0x80U, 0x80U,
|
||||
0x00U, 0x0BU, 0x0AU, 0x00U, 0x41U, 0x00U, 0x10U, 0xB3U, 0x80U, 0x80U, 0x80U,
|
||||
0x00U, 0x0BU, 0x0EU, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U,
|
||||
0xB4U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0AU, 0x00U, 0x41U, 0x00U, 0x10U,
|
||||
0xB5U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0EU, 0x00U, 0x41U, 0x00U, 0x41U,
|
||||
0x00U, 0x41U, 0x00U, 0x10U, 0xB6U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0EU,
|
||||
0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0xB7U, 0x80U, 0x80U,
|
||||
0x80U, 0x00U, 0x0BU, 0x0CU, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0xB8U,
|
||||
0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0AU, 0x00U, 0x41U, 0x00U, 0x10U, 0xB9U,
|
||||
0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x10U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U,
|
||||
0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0xBAU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU,
|
||||
0x18U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U,
|
||||
0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0xBBU, 0x80U, 0x80U,
|
||||
0x80U, 0x00U, 0x0BU, 0x10U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U,
|
||||
0x41U, 0x00U, 0x10U, 0xBCU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x18U, 0x00U,
|
||||
0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U,
|
||||
0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0xBDU, 0x80U, 0x80U, 0x80U, 0x00U,
|
||||
0x0BU, 0x12U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U,
|
||||
0x41U, 0x00U, 0x10U, 0xBEU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0EU, 0x00U,
|
||||
0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x00U, 0x10U, 0xBFU, 0x80U, 0x80U, 0x80U,
|
||||
0x00U, 0x0BU, 0x0EU, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x00U, 0x10U,
|
||||
0xC0U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x08U, 0x00U, 0x10U, 0xC1U, 0x80U,
|
||||
0x80U, 0x80U, 0x00U, 0x0BU, 0x0EU, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U,
|
||||
0x00U, 0x10U, 0xC2U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x08U, 0x00U, 0x10U,
|
||||
0xC3U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0EU, 0x00U, 0x41U, 0x00U, 0x41U,
|
||||
0x00U, 0x41U, 0x00U, 0x10U, 0xC4U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x08U,
|
||||
0x00U, 0x10U, 0xC5U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x0AU, 0x00U, 0x41U,
|
||||
0x00U, 0x10U, 0xC6U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x10U, 0x00U, 0x41U,
|
||||
0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0xC7U, 0x80U, 0x80U,
|
||||
0x80U, 0x00U, 0x0BU, 0x0AU, 0x00U, 0x41U, 0x00U, 0x10U, 0xC8U, 0x80U, 0x80U,
|
||||
0x80U, 0x00U, 0x0BU, 0x0CU, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0xC9U,
|
||||
0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, 0x10U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U,
|
||||
0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0xCAU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU,
|
||||
0x0CU, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U,
|
||||
0x00U, 0x0BU, 0xCEU, 0x06U, 0x00U, 0x02U, 0x40U, 0x20U, 0x00U, 0x41U, 0xCAU,
|
||||
0x00U, 0x4DU, 0x0DU, 0x00U, 0x42U, 0x7FU, 0x0FU, 0x0BU, 0x02U, 0x40U, 0x02U,
|
||||
0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U,
|
||||
0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U,
|
||||
0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U,
|
||||
0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U,
|
||||
0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U,
|
||||
0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U,
|
||||
0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U,
|
||||
0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U,
|
||||
0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U,
|
||||
0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U,
|
||||
0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U,
|
||||
0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U,
|
||||
0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U, 0x02U, 0x40U,
|
||||
0x02U, 0x40U, 0x02U, 0x40U, 0x20U, 0x00U, 0x0EU, 0x4BU, 0x00U, 0x01U, 0x02U,
|
||||
0x03U, 0x04U, 0x05U, 0x06U, 0x07U, 0x08U, 0x09U, 0x0AU, 0x0BU, 0x0CU, 0x0DU,
|
||||
0x0EU, 0x0FU, 0x10U, 0x11U, 0x12U, 0x13U, 0x14U, 0x15U, 0x16U, 0x17U, 0x18U,
|
||||
0x19U, 0x1AU, 0x1BU, 0x1CU, 0x1DU, 0x1EU, 0x1FU, 0x20U, 0x21U, 0x22U, 0x23U,
|
||||
0x24U, 0x25U, 0x26U, 0x27U, 0x28U, 0x29U, 0x2AU, 0x2BU, 0x2CU, 0x2DU, 0x2EU,
|
||||
0x2FU, 0x30U, 0x31U, 0x32U, 0x33U, 0x34U, 0x35U, 0x36U, 0x37U, 0x38U, 0x39U,
|
||||
0x3AU, 0x3BU, 0x3CU, 0x3DU, 0x3EU, 0x3FU, 0x40U, 0x41U, 0x42U, 0x43U, 0x44U,
|
||||
0x45U, 0x46U, 0x47U, 0x48U, 0x49U, 0x4AU, 0x00U, 0x0BU, 0x10U, 0xCCU, 0x80U,
|
||||
0x80U, 0x80U, 0x00U, 0xACU, 0x0FU, 0x0BU, 0x10U, 0xCDU, 0x80U, 0x80U, 0x80U,
|
||||
0x00U, 0x0FU, 0x0BU, 0x10U, 0xCEU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU,
|
||||
0x10U, 0xCFU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0xD0U, 0x80U,
|
||||
0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0xD1U, 0x80U, 0x80U, 0x80U, 0x00U,
|
||||
0x0FU, 0x0BU, 0x10U, 0xD2U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U,
|
||||
0xD3U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0xD4U, 0x80U, 0x80U,
|
||||
0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0xD5U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU,
|
||||
0x0BU, 0x10U, 0xD6U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0xD7U,
|
||||
0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0xD8U, 0x80U, 0x80U, 0x80U,
|
||||
0x00U, 0x0FU, 0x0BU, 0x10U, 0xD9U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU,
|
||||
0x10U, 0xDAU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0xDBU, 0x80U,
|
||||
0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0xDCU, 0x80U, 0x80U, 0x80U, 0x00U,
|
||||
0x0FU, 0x0BU, 0x10U, 0xDDU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U,
|
||||
0xDEU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0xDFU, 0x80U, 0x80U,
|
||||
0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0xE0U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU,
|
||||
0x0BU, 0x10U, 0xE1U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0xE2U,
|
||||
0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0xE3U, 0x80U, 0x80U, 0x80U,
|
||||
0x00U, 0x0FU, 0x0BU, 0x10U, 0xE4U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU,
|
||||
0x10U, 0xE5U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0xE6U, 0x80U,
|
||||
0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0xE7U, 0x80U, 0x80U, 0x80U, 0x00U,
|
||||
0x0FU, 0x0BU, 0x10U, 0xE8U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U,
|
||||
0xE9U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0xEAU, 0x80U, 0x80U,
|
||||
0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0xEBU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU,
|
||||
0x0BU, 0x10U, 0xECU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0xEDU,
|
||||
0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0xEEU, 0x80U, 0x80U, 0x80U,
|
||||
0x00U, 0x0FU, 0x0BU, 0x10U, 0xEFU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU,
|
||||
0x10U, 0xF0U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0xF1U, 0x80U,
|
||||
0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0xF2U, 0x80U, 0x80U, 0x80U, 0x00U,
|
||||
0x0FU, 0x0BU, 0x10U, 0xF3U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U,
|
||||
0xF4U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0xF5U, 0x80U, 0x80U,
|
||||
0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0xF6U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU,
|
||||
0x0BU, 0x10U, 0xF7U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0xF8U,
|
||||
0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0xF9U, 0x80U, 0x80U, 0x80U,
|
||||
0x00U, 0x0FU, 0x0BU, 0x10U, 0xFAU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU,
|
||||
0x10U, 0xFBU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0xFCU, 0x80U,
|
||||
0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0xFDU, 0x80U, 0x80U, 0x80U, 0x00U,
|
||||
0x0FU, 0x0BU, 0x10U, 0xFEU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U,
|
||||
0xFFU, 0x80U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0x80U, 0x81U, 0x80U,
|
||||
0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0x81U, 0x81U, 0x80U, 0x80U, 0x00U, 0x0FU,
|
||||
0x0BU, 0x10U, 0x82U, 0x81U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0x83U,
|
||||
0x81U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0x84U, 0x81U, 0x80U, 0x80U,
|
||||
0x00U, 0x0FU, 0x0BU, 0x10U, 0x85U, 0x81U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU,
|
||||
0x10U, 0x86U, 0x81U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0x87U, 0x81U,
|
||||
0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0x88U, 0x81U, 0x80U, 0x80U, 0x00U,
|
||||
0x0FU, 0x0BU, 0x10U, 0x89U, 0x81U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U,
|
||||
0x8AU, 0x81U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0x8BU, 0x81U, 0x80U,
|
||||
0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0x8CU, 0x81U, 0x80U, 0x80U, 0x00U, 0x0FU,
|
||||
0x0BU, 0x10U, 0x8DU, 0x81U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0x8EU,
|
||||
0x81U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0x8FU, 0x81U, 0x80U, 0x80U,
|
||||
0x00U, 0x0FU, 0x0BU, 0x10U, 0x90U, 0x81U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU,
|
||||
0x10U, 0x91U, 0x81U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0x92U, 0x81U,
|
||||
0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0x93U, 0x81U, 0x80U, 0x80U, 0x00U,
|
||||
0x0FU, 0x0BU, 0x10U, 0x94U, 0x81U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U,
|
||||
0x95U, 0x81U, 0x80U, 0x80U, 0x00U, 0x0FU, 0x0BU, 0x10U, 0x96U, 0x81U, 0x80U,
|
||||
0x80U, 0x00U, 0x0BU, 0x00U, 0x87U, 0x12U, 0x04U, 0x6EU, 0x61U, 0x6DU, 0x65U,
|
||||
0x01U, 0xFFU, 0x11U, 0x99U, 0x01U, 0x00U, 0x02U, 0x5FU, 0x67U, 0x01U, 0x06U,
|
||||
0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x02U, 0x08U, 0x72U, 0x6FU, 0x6CU,
|
||||
0x6CU, 0x62U, 0x61U, 0x63U, 0x6BU, 0x03U, 0x0AU, 0x75U, 0x74U, 0x69U, 0x6CU,
|
||||
0x5FU, 0x72U, 0x61U, 0x64U, 0x64U, 0x72U, 0x04U, 0x0AU, 0x75U, 0x74U, 0x69U,
|
||||
0x6CU, 0x5FU, 0x61U, 0x63U, 0x63U, 0x69U, 0x64U, 0x05U, 0x0BU, 0x75U, 0x74U,
|
||||
0x69U, 0x6CU, 0x5FU, 0x76U, 0x65U, 0x72U, 0x69U, 0x66U, 0x79U, 0x06U, 0x0CU,
|
||||
0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x73U, 0x68U, 0x61U, 0x35U, 0x31U, 0x32U,
|
||||
0x68U, 0x07U, 0x0BU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x6BU, 0x65U, 0x79U,
|
||||
0x6CU, 0x65U, 0x74U, 0x08U, 0x0CU, 0x73U, 0x74U, 0x6FU, 0x5FU, 0x76U, 0x61U,
|
||||
0x6CU, 0x69U, 0x64U, 0x61U, 0x74U, 0x65U, 0x09U, 0x0CU, 0x73U, 0x74U, 0x6FU,
|
||||
0x5FU, 0x73U, 0x75U, 0x62U, 0x66U, 0x69U, 0x65U, 0x6CU, 0x64U, 0x0AU, 0x0CU,
|
||||
0x73U, 0x74U, 0x6FU, 0x5FU, 0x73U, 0x75U, 0x62U, 0x61U, 0x72U, 0x72U, 0x61U,
|
||||
0x79U, 0x0BU, 0x0BU, 0x73U, 0x74U, 0x6FU, 0x5FU, 0x65U, 0x6DU, 0x70U, 0x6CU,
|
||||
0x61U, 0x63U, 0x65U, 0x0CU, 0x09U, 0x73U, 0x74U, 0x6FU, 0x5FU, 0x65U, 0x72U,
|
||||
0x61U, 0x73U, 0x65U, 0x0DU, 0x0BU, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x62U,
|
||||
0x75U, 0x72U, 0x64U, 0x65U, 0x6EU, 0x0EU, 0x0CU, 0x65U, 0x74U, 0x78U, 0x6EU,
|
||||
0x5FU, 0x64U, 0x65U, 0x74U, 0x61U, 0x69U, 0x6CU, 0x73U, 0x0FU, 0x0DU, 0x65U,
|
||||
0x74U, 0x78U, 0x6EU, 0x5FU, 0x66U, 0x65U, 0x65U, 0x5FU, 0x62U, 0x61U, 0x73U,
|
||||
0x65U, 0x10U, 0x0CU, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x72U, 0x65U, 0x73U,
|
||||
0x65U, 0x72U, 0x76U, 0x65U, 0x11U, 0x0FU, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU,
|
||||
0x67U, 0x65U, 0x6EU, 0x65U, 0x72U, 0x61U, 0x74U, 0x69U, 0x6FU, 0x6EU, 0x12U,
|
||||
0x0AU, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x6EU, 0x6FU, 0x6EU, 0x63U, 0x65U,
|
||||
0x13U, 0x04U, 0x65U, 0x6DU, 0x69U, 0x74U, 0x14U, 0x09U, 0x66U, 0x6CU, 0x6FU,
|
||||
0x61U, 0x74U, 0x5FU, 0x73U, 0x65U, 0x74U, 0x15U, 0x0EU, 0x66U, 0x6CU, 0x6FU,
|
||||
0x61U, 0x74U, 0x5FU, 0x6DU, 0x75U, 0x6CU, 0x74U, 0x69U, 0x70U, 0x6CU, 0x79U,
|
||||
0x16U, 0x0EU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x6DU, 0x75U, 0x6CU,
|
||||
0x72U, 0x61U, 0x74U, 0x69U, 0x6FU, 0x17U, 0x0CU, 0x66U, 0x6CU, 0x6FU, 0x61U,
|
||||
0x74U, 0x5FU, 0x6EU, 0x65U, 0x67U, 0x61U, 0x74U, 0x65U, 0x18U, 0x0DU, 0x66U,
|
||||
0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x63U, 0x6FU, 0x6DU, 0x70U, 0x61U, 0x72U,
|
||||
0x65U, 0x19U, 0x09U, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x73U, 0x75U,
|
||||
0x6DU, 0x1AU, 0x09U, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x73U, 0x74U,
|
||||
0x6FU, 0x1BU, 0x0DU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x73U, 0x74U,
|
||||
0x6FU, 0x5FU, 0x73U, 0x65U, 0x74U, 0x1CU, 0x0CU, 0x66U, 0x6CU, 0x6FU, 0x61U,
|
||||
0x74U, 0x5FU, 0x69U, 0x6EU, 0x76U, 0x65U, 0x72U, 0x74U, 0x1DU, 0x0CU, 0x66U,
|
||||
0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x64U, 0x69U, 0x76U, 0x69U, 0x64U, 0x65U,
|
||||
0x1EU, 0x09U, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x6FU, 0x6EU, 0x65U,
|
||||
0x1FU, 0x0EU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x6DU, 0x61U, 0x6EU,
|
||||
0x74U, 0x69U, 0x73U, 0x73U, 0x61U, 0x20U, 0x0AU, 0x66U, 0x6CU, 0x6FU, 0x61U,
|
||||
0x74U, 0x5FU, 0x73U, 0x69U, 0x67U, 0x6EU, 0x21U, 0x09U, 0x66U, 0x6CU, 0x6FU,
|
||||
0x61U, 0x74U, 0x5FU, 0x69U, 0x6EU, 0x74U, 0x22U, 0x09U, 0x66U, 0x6CU, 0x6FU,
|
||||
0x61U, 0x74U, 0x5FU, 0x6CU, 0x6FU, 0x67U, 0x23U, 0x0AU, 0x66U, 0x6CU, 0x6FU,
|
||||
0x61U, 0x74U, 0x5FU, 0x72U, 0x6FU, 0x6FU, 0x74U, 0x24U, 0x08U, 0x66U, 0x65U,
|
||||
0x65U, 0x5FU, 0x62U, 0x61U, 0x73U, 0x65U, 0x25U, 0x0AU, 0x6CU, 0x65U, 0x64U,
|
||||
0x67U, 0x65U, 0x72U, 0x5FU, 0x73U, 0x65U, 0x71U, 0x26U, 0x10U, 0x6CU, 0x65U,
|
||||
0x64U, 0x67U, 0x65U, 0x72U, 0x5FU, 0x6CU, 0x61U, 0x73U, 0x74U, 0x5FU, 0x74U,
|
||||
0x69U, 0x6DU, 0x65U, 0x27U, 0x10U, 0x6CU, 0x65U, 0x64U, 0x67U, 0x65U, 0x72U,
|
||||
0x5FU, 0x6CU, 0x61U, 0x73U, 0x74U, 0x5FU, 0x68U, 0x61U, 0x73U, 0x68U, 0x28U,
|
||||
0x0CU, 0x6CU, 0x65U, 0x64U, 0x67U, 0x65U, 0x72U, 0x5FU, 0x6EU, 0x6FU, 0x6EU,
|
||||
0x63U, 0x65U, 0x29U, 0x0DU, 0x6CU, 0x65U, 0x64U, 0x67U, 0x65U, 0x72U, 0x5FU,
|
||||
0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x2AU, 0x0CU, 0x68U, 0x6FU, 0x6FU,
|
||||
0x6BU, 0x5FU, 0x61U, 0x63U, 0x63U, 0x6FU, 0x75U, 0x6EU, 0x74U, 0x2BU, 0x09U,
|
||||
0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x68U, 0x61U, 0x73U, 0x68U, 0x2CU, 0x0EU,
|
||||
0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x70U, 0x61U, 0x72U, 0x61U, 0x6DU, 0x5FU,
|
||||
0x73U, 0x65U, 0x74U, 0x2DU, 0x0AU, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x70U,
|
||||
0x61U, 0x72U, 0x61U, 0x6DU, 0x2EU, 0x0AU, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU,
|
||||
0x61U, 0x67U, 0x61U, 0x69U, 0x6EU, 0x2FU, 0x09U, 0x68U, 0x6FU, 0x6FU, 0x6BU,
|
||||
0x5FU, 0x73U, 0x6BU, 0x69U, 0x70U, 0x30U, 0x08U, 0x68U, 0x6FU, 0x6FU, 0x6BU,
|
||||
0x5FU, 0x70U, 0x6FU, 0x73U, 0x31U, 0x04U, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x32U,
|
||||
0x0AU, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x63U, 0x6CU, 0x65U, 0x61U, 0x72U,
|
||||
0x33U, 0x0AU, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x63U, 0x6FU, 0x75U, 0x6EU,
|
||||
0x74U, 0x34U, 0x08U, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x73U, 0x65U, 0x74U,
|
||||
0x35U, 0x09U, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x73U, 0x69U, 0x7AU, 0x65U,
|
||||
0x36U, 0x0DU, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x73U, 0x75U, 0x62U, 0x61U,
|
||||
0x72U, 0x72U, 0x61U, 0x79U, 0x37U, 0x0DU, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU,
|
||||
0x73U, 0x75U, 0x62U, 0x66U, 0x69U, 0x65U, 0x6CU, 0x64U, 0x38U, 0x09U, 0x73U,
|
||||
0x6CU, 0x6FU, 0x74U, 0x5FU, 0x74U, 0x79U, 0x70U, 0x65U, 0x39U, 0x0AU, 0x73U,
|
||||
0x6CU, 0x6FU, 0x74U, 0x5FU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x3AU, 0x09U,
|
||||
0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x73U, 0x65U, 0x74U, 0x3BU, 0x11U,
|
||||
0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x66U, 0x6FU, 0x72U, 0x65U, 0x69U,
|
||||
0x67U, 0x6EU, 0x5FU, 0x73U, 0x65U, 0x74U, 0x3CU, 0x05U, 0x73U, 0x74U, 0x61U,
|
||||
0x74U, 0x65U, 0x3DU, 0x0DU, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x66U,
|
||||
0x6FU, 0x72U, 0x65U, 0x69U, 0x67U, 0x6EU, 0x3EU, 0x05U, 0x74U, 0x72U, 0x61U,
|
||||
0x63U, 0x65U, 0x3FU, 0x09U, 0x74U, 0x72U, 0x61U, 0x63U, 0x65U, 0x5FU, 0x6EU,
|
||||
0x75U, 0x6DU, 0x40U, 0x0BU, 0x74U, 0x72U, 0x61U, 0x63U, 0x65U, 0x5FU, 0x66U,
|
||||
0x6CU, 0x6FU, 0x61U, 0x74U, 0x41U, 0x0BU, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU,
|
||||
0x62U, 0x75U, 0x72U, 0x64U, 0x65U, 0x6EU, 0x42U, 0x0AU, 0x6FU, 0x74U, 0x78U,
|
||||
0x6EU, 0x5FU, 0x66U, 0x69U, 0x65U, 0x6CU, 0x64U, 0x43U, 0x0FU, 0x6FU, 0x74U,
|
||||
0x78U, 0x6EU, 0x5FU, 0x67U, 0x65U, 0x6EU, 0x65U, 0x72U, 0x61U, 0x74U, 0x69U,
|
||||
0x6FU, 0x6EU, 0x44U, 0x07U, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x69U, 0x64U,
|
||||
0x45U, 0x09U, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x74U, 0x79U, 0x70U, 0x65U,
|
||||
0x46U, 0x09U, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x73U, 0x6CU, 0x6FU, 0x74U,
|
||||
0x47U, 0x0AU, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x70U, 0x61U, 0x72U, 0x61U,
|
||||
0x6DU, 0x48U, 0x09U, 0x6DU, 0x65U, 0x74U, 0x61U, 0x5FU, 0x73U, 0x6CU, 0x6FU,
|
||||
0x74U, 0x49U, 0x09U, 0x78U, 0x70U, 0x6FU, 0x70U, 0x5FU, 0x73U, 0x6CU, 0x6FU,
|
||||
0x74U, 0x4AU, 0x07U, 0x70U, 0x72U, 0x65U, 0x70U, 0x61U, 0x72U, 0x65U, 0x4BU,
|
||||
0x11U, 0x5FU, 0x5FU, 0x77U, 0x61U, 0x73U, 0x6DU, 0x5FU, 0x63U, 0x61U, 0x6CU,
|
||||
0x6CU, 0x5FU, 0x63U, 0x74U, 0x6FU, 0x72U, 0x73U, 0x4CU, 0x07U, 0x63U, 0x61U,
|
||||
0x6CU, 0x6CU, 0x5FU, 0x5FU, 0x67U, 0x4DU, 0x0BU, 0x63U, 0x61U, 0x6CU, 0x6CU,
|
||||
0x5FU, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x4EU, 0x0DU, 0x63U, 0x61U,
|
||||
0x6CU, 0x6CU, 0x5FU, 0x72U, 0x6FU, 0x6CU, 0x6CU, 0x62U, 0x61U, 0x63U, 0x6BU,
|
||||
0x4FU, 0x0FU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x75U, 0x74U, 0x69U, 0x6CU,
|
||||
0x5FU, 0x72U, 0x61U, 0x64U, 0x64U, 0x72U, 0x50U, 0x0FU, 0x63U, 0x61U, 0x6CU,
|
||||
0x6CU, 0x5FU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x61U, 0x63U, 0x63U, 0x69U,
|
||||
0x64U, 0x51U, 0x10U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x75U, 0x74U, 0x69U,
|
||||
0x6CU, 0x5FU, 0x76U, 0x65U, 0x72U, 0x69U, 0x66U, 0x79U, 0x52U, 0x11U, 0x63U,
|
||||
0x61U, 0x6CU, 0x6CU, 0x5FU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x73U, 0x68U,
|
||||
0x61U, 0x35U, 0x31U, 0x32U, 0x68U, 0x53U, 0x10U, 0x63U, 0x61U, 0x6CU, 0x6CU,
|
||||
0x5FU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U,
|
||||
0x74U, 0x54U, 0x11U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x73U, 0x74U, 0x6FU,
|
||||
0x5FU, 0x76U, 0x61U, 0x6CU, 0x69U, 0x64U, 0x61U, 0x74U, 0x65U, 0x55U, 0x11U,
|
||||
0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x73U, 0x74U, 0x6FU, 0x5FU, 0x73U, 0x75U,
|
||||
0x62U, 0x66U, 0x69U, 0x65U, 0x6CU, 0x64U, 0x56U, 0x11U, 0x63U, 0x61U, 0x6CU,
|
||||
0x6CU, 0x5FU, 0x73U, 0x74U, 0x6FU, 0x5FU, 0x73U, 0x75U, 0x62U, 0x61U, 0x72U,
|
||||
0x72U, 0x61U, 0x79U, 0x57U, 0x10U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x73U,
|
||||
0x74U, 0x6FU, 0x5FU, 0x65U, 0x6DU, 0x70U, 0x6CU, 0x61U, 0x63U, 0x65U, 0x58U,
|
||||
0x0EU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x73U, 0x74U, 0x6FU, 0x5FU, 0x65U,
|
||||
0x72U, 0x61U, 0x73U, 0x65U, 0x59U, 0x10U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU,
|
||||
0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x62U, 0x75U, 0x72U, 0x64U, 0x65U, 0x6EU,
|
||||
0x5AU, 0x11U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x65U, 0x74U, 0x78U, 0x6EU,
|
||||
0x5FU, 0x64U, 0x65U, 0x74U, 0x61U, 0x69U, 0x6CU, 0x73U, 0x5BU, 0x12U, 0x63U,
|
||||
0x61U, 0x6CU, 0x6CU, 0x5FU, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x66U, 0x65U,
|
||||
0x65U, 0x5FU, 0x62U, 0x61U, 0x73U, 0x65U, 0x5CU, 0x11U, 0x63U, 0x61U, 0x6CU,
|
||||
0x6CU, 0x5FU, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x72U, 0x65U, 0x73U, 0x65U,
|
||||
0x72U, 0x76U, 0x65U, 0x5DU, 0x14U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x65U,
|
||||
0x74U, 0x78U, 0x6EU, 0x5FU, 0x67U, 0x65U, 0x6EU, 0x65U, 0x72U, 0x61U, 0x74U,
|
||||
0x69U, 0x6FU, 0x6EU, 0x5EU, 0x0FU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x65U,
|
||||
0x74U, 0x78U, 0x6EU, 0x5FU, 0x6EU, 0x6FU, 0x6EU, 0x63U, 0x65U, 0x5FU, 0x09U,
|
||||
0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x65U, 0x6DU, 0x69U, 0x74U, 0x60U, 0x0EU,
|
||||
0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU,
|
||||
0x73U, 0x65U, 0x74U, 0x61U, 0x13U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x66U,
|
||||
0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x6DU, 0x75U, 0x6CU, 0x74U, 0x69U, 0x70U,
|
||||
0x6CU, 0x79U, 0x62U, 0x13U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x66U, 0x6CU,
|
||||
0x6FU, 0x61U, 0x74U, 0x5FU, 0x6DU, 0x75U, 0x6CU, 0x72U, 0x61U, 0x74U, 0x69U,
|
||||
0x6FU, 0x63U, 0x11U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x66U, 0x6CU, 0x6FU,
|
||||
0x61U, 0x74U, 0x5FU, 0x6EU, 0x65U, 0x67U, 0x61U, 0x74U, 0x65U, 0x64U, 0x12U,
|
||||
0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU,
|
||||
0x63U, 0x6FU, 0x6DU, 0x70U, 0x61U, 0x72U, 0x65U, 0x65U, 0x0EU, 0x63U, 0x61U,
|
||||
0x6CU, 0x6CU, 0x5FU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x73U, 0x75U,
|
||||
0x6DU, 0x66U, 0x0EU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x66U, 0x6CU, 0x6FU,
|
||||
0x61U, 0x74U, 0x5FU, 0x73U, 0x74U, 0x6FU, 0x67U, 0x12U, 0x63U, 0x61U, 0x6CU,
|
||||
0x6CU, 0x5FU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x73U, 0x74U, 0x6FU,
|
||||
0x5FU, 0x73U, 0x65U, 0x74U, 0x68U, 0x11U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU,
|
||||
0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x69U, 0x6EU, 0x76U, 0x65U, 0x72U,
|
||||
0x74U, 0x69U, 0x11U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x66U, 0x6CU, 0x6FU,
|
||||
0x61U, 0x74U, 0x5FU, 0x64U, 0x69U, 0x76U, 0x69U, 0x64U, 0x65U, 0x6AU, 0x0EU,
|
||||
0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU,
|
||||
0x6FU, 0x6EU, 0x65U, 0x6BU, 0x13U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x66U,
|
||||
0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x6DU, 0x61U, 0x6EU, 0x74U, 0x69U, 0x73U,
|
||||
0x73U, 0x61U, 0x6CU, 0x0FU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x66U, 0x6CU,
|
||||
0x6FU, 0x61U, 0x74U, 0x5FU, 0x73U, 0x69U, 0x67U, 0x6EU, 0x6DU, 0x0EU, 0x63U,
|
||||
0x61U, 0x6CU, 0x6CU, 0x5FU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x69U,
|
||||
0x6EU, 0x74U, 0x6EU, 0x0EU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x66U, 0x6CU,
|
||||
0x6FU, 0x61U, 0x74U, 0x5FU, 0x6CU, 0x6FU, 0x67U, 0x6FU, 0x0FU, 0x63U, 0x61U,
|
||||
0x6CU, 0x6CU, 0x5FU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x5FU, 0x72U, 0x6FU,
|
||||
0x6FU, 0x74U, 0x70U, 0x0DU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x66U, 0x65U,
|
||||
0x65U, 0x5FU, 0x62U, 0x61U, 0x73U, 0x65U, 0x71U, 0x0FU, 0x63U, 0x61U, 0x6CU,
|
||||
0x6CU, 0x5FU, 0x6CU, 0x65U, 0x64U, 0x67U, 0x65U, 0x72U, 0x5FU, 0x73U, 0x65U,
|
||||
0x71U, 0x72U, 0x15U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x6CU, 0x65U, 0x64U,
|
||||
0x67U, 0x65U, 0x72U, 0x5FU, 0x6CU, 0x61U, 0x73U, 0x74U, 0x5FU, 0x74U, 0x69U,
|
||||
0x6DU, 0x65U, 0x73U, 0x15U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x6CU, 0x65U,
|
||||
0x64U, 0x67U, 0x65U, 0x72U, 0x5FU, 0x6CU, 0x61U, 0x73U, 0x74U, 0x5FU, 0x68U,
|
||||
0x61U, 0x73U, 0x68U, 0x74U, 0x11U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x6CU,
|
||||
0x65U, 0x64U, 0x67U, 0x65U, 0x72U, 0x5FU, 0x6EU, 0x6FU, 0x6EU, 0x63U, 0x65U,
|
||||
0x75U, 0x12U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x6CU, 0x65U, 0x64U, 0x67U,
|
||||
0x65U, 0x72U, 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x76U, 0x11U,
|
||||
0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x61U,
|
||||
0x63U, 0x63U, 0x6FU, 0x75U, 0x6EU, 0x74U, 0x77U, 0x0EU, 0x63U, 0x61U, 0x6CU,
|
||||
0x6CU, 0x5FU, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x68U, 0x61U, 0x73U, 0x68U,
|
||||
0x78U, 0x13U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x68U, 0x6FU, 0x6FU, 0x6BU,
|
||||
0x5FU, 0x70U, 0x61U, 0x72U, 0x61U, 0x6DU, 0x5FU, 0x73U, 0x65U, 0x74U, 0x79U,
|
||||
0x0FU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU,
|
||||
0x70U, 0x61U, 0x72U, 0x61U, 0x6DU, 0x7AU, 0x0FU, 0x63U, 0x61U, 0x6CU, 0x6CU,
|
||||
0x5FU, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x61U, 0x67U, 0x61U, 0x69U, 0x6EU,
|
||||
0x7BU, 0x0EU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x68U, 0x6FU, 0x6FU, 0x6BU,
|
||||
0x5FU, 0x73U, 0x6BU, 0x69U, 0x70U, 0x7CU, 0x0DU, 0x63U, 0x61U, 0x6CU, 0x6CU,
|
||||
0x5FU, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x70U, 0x6FU, 0x73U, 0x7DU, 0x09U,
|
||||
0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x7EU, 0x0FU,
|
||||
0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x63U,
|
||||
0x6CU, 0x65U, 0x61U, 0x72U, 0x7FU, 0x0FU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU,
|
||||
0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x63U, 0x6FU, 0x75U, 0x6EU, 0x74U, 0x80U,
|
||||
0x01U, 0x0DU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x73U, 0x6CU, 0x6FU, 0x74U,
|
||||
0x5FU, 0x73U, 0x65U, 0x74U, 0x81U, 0x01U, 0x0EU, 0x63U, 0x61U, 0x6CU, 0x6CU,
|
||||
0x5FU, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x73U, 0x69U, 0x7AU, 0x65U, 0x82U,
|
||||
0x01U, 0x12U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x73U, 0x6CU, 0x6FU, 0x74U,
|
||||
0x5FU, 0x73U, 0x75U, 0x62U, 0x61U, 0x72U, 0x72U, 0x61U, 0x79U, 0x83U, 0x01U,
|
||||
0x12U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU,
|
||||
0x73U, 0x75U, 0x62U, 0x66U, 0x69U, 0x65U, 0x6CU, 0x64U, 0x84U, 0x01U, 0x0EU,
|
||||
0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x74U,
|
||||
0x79U, 0x70U, 0x65U, 0x85U, 0x01U, 0x0FU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU,
|
||||
0x73U, 0x6CU, 0x6FU, 0x74U, 0x5FU, 0x66U, 0x6CU, 0x6FU, 0x61U, 0x74U, 0x86U,
|
||||
0x01U, 0x0EU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x73U, 0x74U, 0x61U, 0x74U,
|
||||
0x65U, 0x5FU, 0x73U, 0x65U, 0x74U, 0x87U, 0x01U, 0x16U, 0x63U, 0x61U, 0x6CU,
|
||||
0x6CU, 0x5FU, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x5FU, 0x66U, 0x6FU, 0x72U,
|
||||
0x65U, 0x69U, 0x67U, 0x6EU, 0x5FU, 0x73U, 0x65U, 0x74U, 0x88U, 0x01U, 0x0AU,
|
||||
0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x73U, 0x74U, 0x61U, 0x74U, 0x65U, 0x89U,
|
||||
0x01U, 0x12U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x73U, 0x74U, 0x61U, 0x74U,
|
||||
0x65U, 0x5FU, 0x66U, 0x6FU, 0x72U, 0x65U, 0x69U, 0x67U, 0x6EU, 0x8AU, 0x01U,
|
||||
0x0AU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x74U, 0x72U, 0x61U, 0x63U, 0x65U,
|
||||
0x8BU, 0x01U, 0x0EU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x74U, 0x72U, 0x61U,
|
||||
0x63U, 0x65U, 0x5FU, 0x6EU, 0x75U, 0x6DU, 0x8CU, 0x01U, 0x10U, 0x63U, 0x61U,
|
||||
0x6CU, 0x6CU, 0x5FU, 0x74U, 0x72U, 0x61U, 0x63U, 0x65U, 0x5FU, 0x66U, 0x6CU,
|
||||
0x6FU, 0x61U, 0x74U, 0x8DU, 0x01U, 0x10U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU,
|
||||
0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x62U, 0x75U, 0x72U, 0x64U, 0x65U, 0x6EU,
|
||||
0x8EU, 0x01U, 0x0FU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x6FU, 0x74U, 0x78U,
|
||||
0x6EU, 0x5FU, 0x66U, 0x69U, 0x65U, 0x6CU, 0x64U, 0x8FU, 0x01U, 0x14U, 0x63U,
|
||||
0x61U, 0x6CU, 0x6CU, 0x5FU, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x67U, 0x65U,
|
||||
0x6EU, 0x65U, 0x72U, 0x61U, 0x74U, 0x69U, 0x6FU, 0x6EU, 0x90U, 0x01U, 0x0CU,
|
||||
0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x69U,
|
||||
0x64U, 0x91U, 0x01U, 0x0EU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x6FU, 0x74U,
|
||||
0x78U, 0x6EU, 0x5FU, 0x74U, 0x79U, 0x70U, 0x65U, 0x92U, 0x01U, 0x0EU, 0x63U,
|
||||
0x61U, 0x6CU, 0x6CU, 0x5FU, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x73U, 0x6CU,
|
||||
0x6FU, 0x74U, 0x93U, 0x01U, 0x0FU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x6FU,
|
||||
0x74U, 0x78U, 0x6EU, 0x5FU, 0x70U, 0x61U, 0x72U, 0x61U, 0x6DU, 0x94U, 0x01U,
|
||||
0x0EU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x6DU, 0x65U, 0x74U, 0x61U, 0x5FU,
|
||||
0x73U, 0x6CU, 0x6FU, 0x74U, 0x95U, 0x01U, 0x0EU, 0x63U, 0x61U, 0x6CU, 0x6CU,
|
||||
0x5FU, 0x78U, 0x70U, 0x6FU, 0x70U, 0x5FU, 0x73U, 0x6CU, 0x6FU, 0x74U, 0x96U,
|
||||
0x01U, 0x0CU, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU, 0x70U, 0x72U, 0x65U, 0x70U,
|
||||
0x61U, 0x72U, 0x65U, 0x97U, 0x01U, 0x10U, 0x63U, 0x61U, 0x6CU, 0x6CU, 0x5FU,
|
||||
0x5FU, 0x67U, 0x5FU, 0x72U, 0x6FU, 0x6CU, 0x6CU, 0x62U, 0x61U, 0x63U, 0x6BU,
|
||||
0x98U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x75U, 0x09U, 0x70U,
|
||||
0x72U, 0x6FU, 0x64U, 0x75U, 0x63U, 0x65U, 0x72U, 0x73U, 0x01U, 0x0CU, 0x70U,
|
||||
0x72U, 0x6FU, 0x63U, 0x65U, 0x73U, 0x73U, 0x65U, 0x64U, 0x2DU, 0x62U, 0x79U,
|
||||
0x01U, 0x05U, 0x63U, 0x6CU, 0x61U, 0x6EU, 0x67U, 0x55U, 0x39U, 0x2EU, 0x30U,
|
||||
0x2EU, 0x30U, 0x20U, 0x28U, 0x68U, 0x74U, 0x74U, 0x70U, 0x73U, 0x3AU, 0x2FU,
|
||||
0x2FU, 0x67U, 0x69U, 0x74U, 0x68U, 0x75U, 0x62U, 0x2EU, 0x63U, 0x6FU, 0x6DU,
|
||||
0x2FU, 0x6CU, 0x6CU, 0x76U, 0x6DU, 0x2FU, 0x6CU, 0x6CU, 0x76U, 0x6DU, 0x2DU,
|
||||
0x70U, 0x72U, 0x6FU, 0x6AU, 0x65U, 0x63U, 0x74U, 0x20U, 0x30U, 0x33U, 0x39U,
|
||||
0x39U, 0x64U, 0x35U, 0x61U, 0x39U, 0x36U, 0x38U, 0x32U, 0x62U, 0x33U, 0x63U,
|
||||
0x65U, 0x66U, 0x37U, 0x31U, 0x63U, 0x36U, 0x35U, 0x33U, 0x33U, 0x37U, 0x33U,
|
||||
0x65U, 0x33U, 0x38U, 0x38U, 0x39U, 0x30U, 0x63U, 0x36U, 0x33U, 0x63U, 0x34U,
|
||||
0x63U, 0x33U, 0x36U, 0x35U, 0x29U,
|
||||
}};
|
||||
inline constexpr std::array<std::string_view, 75> names{{
|
||||
"_g",
|
||||
"accept",
|
||||
"rollback",
|
||||
"util_raddr",
|
||||
"util_accid",
|
||||
"util_verify",
|
||||
"util_sha512h",
|
||||
"util_keylet",
|
||||
"sto_validate",
|
||||
"sto_subfield",
|
||||
"sto_subarray",
|
||||
"sto_emplace",
|
||||
"sto_erase",
|
||||
"etxn_burden",
|
||||
"etxn_details",
|
||||
"etxn_fee_base",
|
||||
"etxn_reserve",
|
||||
"etxn_generation",
|
||||
"etxn_nonce",
|
||||
"emit",
|
||||
"float_set",
|
||||
"float_multiply",
|
||||
"float_mulratio",
|
||||
"float_negate",
|
||||
"float_compare",
|
||||
"float_sum",
|
||||
"float_sto",
|
||||
"float_sto_set",
|
||||
"float_invert",
|
||||
"float_divide",
|
||||
"float_one",
|
||||
"float_mantissa",
|
||||
"float_sign",
|
||||
"float_int",
|
||||
"float_log",
|
||||
"float_root",
|
||||
"fee_base",
|
||||
"ledger_seq",
|
||||
"ledger_last_time",
|
||||
"ledger_last_hash",
|
||||
"ledger_nonce",
|
||||
"ledger_keylet",
|
||||
"hook_account",
|
||||
"hook_hash",
|
||||
"hook_param_set",
|
||||
"hook_param",
|
||||
"hook_again",
|
||||
"hook_skip",
|
||||
"hook_pos",
|
||||
"slot",
|
||||
"slot_clear",
|
||||
"slot_count",
|
||||
"slot_set",
|
||||
"slot_size",
|
||||
"slot_subarray",
|
||||
"slot_subfield",
|
||||
"slot_type",
|
||||
"slot_float",
|
||||
"state_set",
|
||||
"state_foreign_set",
|
||||
"state",
|
||||
"state_foreign",
|
||||
"trace",
|
||||
"trace_num",
|
||||
"trace_float",
|
||||
"otxn_burden",
|
||||
"otxn_field",
|
||||
"otxn_generation",
|
||||
"otxn_id",
|
||||
"otxn_type",
|
||||
"otxn_slot",
|
||||
"otxn_param",
|
||||
"meta_slot",
|
||||
"xpop_slot",
|
||||
"prepare",
|
||||
}};
|
||||
} // namespace ripple::test::raw_hook_catalogue_fixture
|
||||
|
||||
#endif
|
||||
251
src/test/app/jshooks/raw-catalogue/generate.py
Executable file
251
src/test/app/jshooks/raw-catalogue/generate.py
Executable file
@@ -0,0 +1,251 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate the independent all-import Wasmtime host-adapter fixture.
|
||||
|
||||
The runtime catalogue comes only from hook_api.macro. This test fixture takes
|
||||
the deliberately independent route: Clang parses the public hook/extern.h
|
||||
declarations, generated C retains and exports one wrapper per declaration, and
|
||||
wasmcc compiles the result with an explicit memory export.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
ROOT = HERE.parents[4]
|
||||
EXTERN = ROOT / "hook" / "extern.h"
|
||||
SOURCE = HERE / "catalogue.c"
|
||||
HEADER = HERE / "catalogue_wasm.h"
|
||||
MANIFEST = HERE / "manifest.json"
|
||||
EXPECTED_COUNT = 75
|
||||
FLAGS = ["-O2", "-Wl,--allow-undefined", "-Wl,--export-all"]
|
||||
|
||||
|
||||
def run(command: list[str]) -> subprocess.CompletedProcess[str]:
|
||||
result = subprocess.run(
|
||||
command,
|
||||
cwd=ROOT,
|
||||
check=False,
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
raise SystemExit("command failed: " + " ".join(command) + "\n" + result.stderr)
|
||||
return result
|
||||
|
||||
|
||||
def walk(node: dict):
|
||||
yield node
|
||||
for child in node.get("inner", []):
|
||||
yield from walk(child)
|
||||
|
||||
|
||||
def declarations(clang: str) -> list[dict]:
|
||||
ast = json.loads(
|
||||
run(
|
||||
[
|
||||
clang,
|
||||
"-Xclang",
|
||||
"-ast-dump=json",
|
||||
"-fsyntax-only",
|
||||
"-x",
|
||||
"c",
|
||||
str(EXTERN),
|
||||
]
|
||||
).stdout
|
||||
)
|
||||
result = []
|
||||
for node in walk(ast):
|
||||
if node.get("kind") != "FunctionDecl" or node.get("storageClass") != "extern":
|
||||
continue
|
||||
function_type = node["type"]["qualType"]
|
||||
return_type = function_type.split(" (", 1)[0]
|
||||
parameters = [
|
||||
child["type"]["qualType"]
|
||||
for child in node.get("inner", [])
|
||||
if child.get("kind") == "ParmVarDecl"
|
||||
]
|
||||
if return_type not in {"int32_t", "int64_t"} or any(
|
||||
item not in {"uint32_t", "int32_t", "uint64_t", "int64_t"}
|
||||
for item in parameters
|
||||
):
|
||||
raise SystemExit(
|
||||
f"unsupported public Hook ABI declaration: {node['name']} {function_type}"
|
||||
)
|
||||
result.append(
|
||||
{
|
||||
"name": node["name"],
|
||||
"return": return_type,
|
||||
"parameters": parameters,
|
||||
}
|
||||
)
|
||||
if len(result) != EXPECTED_COUNT:
|
||||
raise SystemExit(
|
||||
f"expected {EXPECTED_COUNT} public Hook declarations, found {len(result)}"
|
||||
)
|
||||
names = [item["name"] for item in result]
|
||||
if len(names) != len(set(names)):
|
||||
raise SystemExit("duplicate public Hook declaration")
|
||||
return result
|
||||
|
||||
|
||||
def render_source(items: list[dict]) -> bytes:
|
||||
lines = [
|
||||
"/* Generated by generate.py from hook/extern.h. Do not edit. */",
|
||||
'#include "extern.h"',
|
||||
"",
|
||||
"#define RAW_EXPORT __attribute__((noinline, used))",
|
||||
"",
|
||||
]
|
||||
for index, item in enumerate(items):
|
||||
values = ["0"] * len(item["parameters"])
|
||||
if item["name"] == "_g":
|
||||
values[1] = "1"
|
||||
arguments = ", ".join(
|
||||
f"({kind}){value}"
|
||||
for kind, value in zip(item["parameters"], values, strict=True)
|
||||
)
|
||||
wrapper = f"call_{item['name']}"
|
||||
lines.extend(
|
||||
[
|
||||
"RAW_EXPORT",
|
||||
f"{item['return']} {wrapper}(void)",
|
||||
"{",
|
||||
f" return {item['name']}({arguments});",
|
||||
"}",
|
||||
"",
|
||||
]
|
||||
)
|
||||
lines.extend(
|
||||
[
|
||||
"RAW_EXPORT",
|
||||
"int32_t call__g_rollback(void)",
|
||||
"{",
|
||||
" return _g((uint32_t)0, (uint32_t)0);",
|
||||
"}",
|
||||
"",
|
||||
]
|
||||
)
|
||||
lines.extend(
|
||||
[
|
||||
"RAW_EXPORT",
|
||||
"int64_t hook(uint32_t selector)",
|
||||
"{",
|
||||
" switch (selector)",
|
||||
" {",
|
||||
]
|
||||
)
|
||||
for index, _item in enumerate(items):
|
||||
lines.append(f" case {index}: return call_{_item['name']}();")
|
||||
lines.extend([" default: return -1;", " }", "}", ""])
|
||||
return "\n".join(lines).encode()
|
||||
|
||||
|
||||
def render_header(wasm: bytes, items: list[dict]) -> bytes:
|
||||
values = []
|
||||
for offset in range(0, len(wasm), 11):
|
||||
chunk = ", ".join(f"0x{value:02X}U" for value in wasm[offset : offset + 11])
|
||||
values.append(f" {chunk},")
|
||||
names = [f' "{item["name"]}",' for item in items]
|
||||
text = [
|
||||
"// Generated by generate.py. Do not edit.",
|
||||
"#ifndef XAHAU_TEST_RAW_HOOK_CATALOGUE_WASM_H_INCLUDED",
|
||||
"#define XAHAU_TEST_RAW_HOOK_CATALOGUE_WASM_H_INCLUDED",
|
||||
"#include <array>",
|
||||
"#include <cstdint>",
|
||||
"#include <string_view>",
|
||||
"",
|
||||
"namespace ripple::test::raw_hook_catalogue_fixture {",
|
||||
f"inline constexpr std::array<std::uint8_t, {len(wasm)}> wasm{{{{",
|
||||
*values,
|
||||
"}};",
|
||||
f"inline constexpr std::array<std::string_view, {len(items)}> names{{{{",
|
||||
*names,
|
||||
"}};",
|
||||
"} // namespace ripple::test::raw_hook_catalogue_fixture",
|
||||
"",
|
||||
"#endif",
|
||||
"",
|
||||
]
|
||||
return "\n".join(text).encode()
|
||||
|
||||
|
||||
def sha256(data: bytes) -> str:
|
||||
return hashlib.sha256(data).hexdigest()
|
||||
|
||||
|
||||
def compiler_identity(command: str) -> str:
|
||||
probe = run([command, "-v", "-E", "-x", "c", "/dev/null"])
|
||||
return next(line for line in probe.stderr.splitlines() if "clang version" in line)
|
||||
|
||||
|
||||
def generate(clang: str, wasmcc: str) -> dict[Path, bytes]:
|
||||
items = declarations(clang)
|
||||
source = render_source(items)
|
||||
with tempfile.TemporaryDirectory(prefix="xahau-raw-hook-catalogue-") as tmp:
|
||||
source_path = Path(tmp) / "catalogue.c"
|
||||
wasm_path = Path(tmp) / "catalogue.wasm"
|
||||
source_path.write_bytes(source)
|
||||
command = [
|
||||
wasmcc,
|
||||
"-x",
|
||||
"c",
|
||||
str(source_path),
|
||||
"-I",
|
||||
str(EXTERN.parent),
|
||||
"-o",
|
||||
str(wasm_path),
|
||||
*FLAGS,
|
||||
]
|
||||
run(command)
|
||||
wasm = wasm_path.read_bytes()
|
||||
|
||||
manifest = {
|
||||
"schema": 1,
|
||||
"declaration_source": "hook/extern.h",
|
||||
"declaration_sha256": sha256(EXTERN.read_bytes()),
|
||||
"declaration_count": len(items),
|
||||
"ast_parser": compiler_identity(clang),
|
||||
"wasm_compiler": compiler_identity(wasmcc),
|
||||
"wasmcc_flags": FLAGS,
|
||||
"source_sha256": sha256(source),
|
||||
"wasm_sha256": sha256(wasm),
|
||||
"wasm_size": len(wasm),
|
||||
}
|
||||
return {
|
||||
SOURCE: source,
|
||||
HEADER: render_header(wasm, items),
|
||||
MANIFEST: (json.dumps(manifest, indent=2, sort_keys=True) + "\n").encode(),
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--check", action="store_true")
|
||||
parser.add_argument("--clang", default=os.environ.get("HOOK_AST_CLANG"))
|
||||
parser.add_argument("--wasmcc", default=os.environ.get("WASMCC", "wasmcc"))
|
||||
args = parser.parse_args()
|
||||
|
||||
outputs = generate(args.clang or args.wasmcc, args.wasmcc)
|
||||
stale = []
|
||||
for path, content in outputs.items():
|
||||
if args.check:
|
||||
if not path.exists() or path.read_bytes() != content:
|
||||
stale.append(str(path.relative_to(ROOT)))
|
||||
else:
|
||||
path.write_bytes(content)
|
||||
if stale:
|
||||
raise SystemExit("stale raw Hook catalogue fixture: " + ", ".join(stale))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
16
src/test/app/jshooks/raw-catalogue/manifest.json
Normal file
16
src/test/app/jshooks/raw-catalogue/manifest.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"ast_parser": "clang version 9.0.0 (https://github.com/llvm/llvm-project 0399d5a9682b3cef71c653373e38890c63c4c365)",
|
||||
"declaration_count": 75,
|
||||
"declaration_sha256": "83b077aa763f1a3bbbc38cc3669039ec9fcbc7543f5c1c63226bfd4bfbf94eb0",
|
||||
"declaration_source": "hook/extern.h",
|
||||
"schema": 1,
|
||||
"source_sha256": "28f252fea72ed9b381e610f01597f267a8b253e696b9f33d33b5746aa0a32420",
|
||||
"wasm_compiler": "clang version 9.0.0 (https://github.com/llvm/llvm-project 0399d5a9682b3cef71c653373e38890c63c4c365)",
|
||||
"wasm_sha256": "4e79d2917fab669b6b6915a531d75e3c285ac958f75cec4fc2a7f454a50b0242",
|
||||
"wasm_size": 7507,
|
||||
"wasmcc_flags": [
|
||||
"-O2",
|
||||
"-Wl,--allow-undefined",
|
||||
"-Wl,--export-all"
|
||||
]
|
||||
}
|
||||
43
src/test/basics/LogTransform_test.cpp
Normal file
43
src/test/basics/LogTransform_test.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Logs::setTransform is a test hook. Empty path is a null check.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
class LogTransform_test : public beast::unit_test::suite
|
||||
{
|
||||
void
|
||||
testIdentityAndRewrite()
|
||||
{
|
||||
Logs logs{beast::severities::kError};
|
||||
BEAST_EXPECT(!logs.hasTransform());
|
||||
BEAST_EXPECT(logs.applyTransform("alice") == "alice");
|
||||
logs.setTransform(
|
||||
[](std::string const& text) { return "Account(" + text + ")"; });
|
||||
BEAST_EXPECT(logs.hasTransform());
|
||||
BEAST_EXPECT(logs.applyTransform("alice") == "Account(alice)");
|
||||
logs.setTransform(nullptr);
|
||||
BEAST_EXPECT(!logs.hasTransform());
|
||||
BEAST_EXPECT(logs.applyTransform("alice") == "alice");
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testcase("identity, rewrite, and clear");
|
||||
testIdentityAndRewrite();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(LogTransform, ripple_basics, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/Env_ss.h>
|
||||
#include <test/jtx/JTx.h>
|
||||
#include <test/jtx/TestEnv.h>
|
||||
#include <test/jtx/TestHelpers.h>
|
||||
#include <test/jtx/account_txn_id.h>
|
||||
#include <test/jtx/acctdelete.h>
|
||||
|
||||
@@ -106,7 +106,8 @@ public:
|
||||
std::string const& partition,
|
||||
beast::severities::Severity threshold) override
|
||||
{
|
||||
return std::make_unique<SuiteJournalSink>(partition, threshold, suite_);
|
||||
return std::make_unique<SuiteJournalSink>(
|
||||
partition, threshold, suite_, this);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
142
src/test/jtx/TestEnv.h
Normal file
142
src/test/jtx/TestEnv.h
Normal file
@@ -0,0 +1,142 @@
|
||||
#ifndef TEST_JTX_TESTENV_H_INCLUDED
|
||||
#define TEST_JTX_TESTENV_H_INCLUDED
|
||||
|
||||
#include <test/jtx/Env.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <cstdlib>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
|
||||
/** Env with named accounts, log rewriting, and per-partition levels.
|
||||
|
||||
Named accounts: env.account("alice") is stable for the life of the Env.
|
||||
|
||||
Log lines replace r-addresses with Account(name). setPrefix() tags a
|
||||
phase so traces from one step are easy to find.
|
||||
|
||||
Partition levels, comma-separated:
|
||||
|
||||
XAHAU_TEST_LOG=HooksTrace=trace,View=debug
|
||||
|
||||
TESTENV_LOGGING is accepted as an alias. Levels: trace, debug, info,
|
||||
warning, error, fatal.
|
||||
*/
|
||||
class TestEnv : public Env
|
||||
{
|
||||
// Logs owns the rewriter until it dies after the app thread joins.
|
||||
// Capturing this table (not TestEnv) keeps late log lines from UAF.
|
||||
struct Rewrite
|
||||
{
|
||||
std::string prefix;
|
||||
std::map<std::string, Account> accounts;
|
||||
};
|
||||
std::shared_ptr<Rewrite> rewrite_ = std::make_shared<Rewrite>();
|
||||
|
||||
public:
|
||||
TestEnv(beast::unit_test::suite& suite, FeatureBitset features)
|
||||
: Env(suite, features)
|
||||
{
|
||||
installTransform();
|
||||
applyLoggingEnv();
|
||||
}
|
||||
|
||||
TestEnv(
|
||||
beast::unit_test::suite& suite,
|
||||
std::unique_ptr<Config> config,
|
||||
FeatureBitset features,
|
||||
std::unique_ptr<Logs> logs = nullptr,
|
||||
beast::severities::Severity thresh = beast::severities::kError)
|
||||
: Env(suite, std::move(config), features, std::move(logs), thresh)
|
||||
{
|
||||
installTransform();
|
||||
applyLoggingEnv();
|
||||
}
|
||||
|
||||
Account const&
|
||||
account(std::string const& name)
|
||||
{
|
||||
return rewrite_->accounts.try_emplace(name, name).first->second;
|
||||
}
|
||||
|
||||
void
|
||||
setPrefix(std::string const& prefix)
|
||||
{
|
||||
rewrite_->prefix = prefix.empty() ? "" : "[" + prefix + "] ";
|
||||
}
|
||||
|
||||
private:
|
||||
static beast::severities::Severity
|
||||
parseSeverity(std::string const& s)
|
||||
{
|
||||
if (s == "trace")
|
||||
return beast::severities::kTrace;
|
||||
if (s == "debug")
|
||||
return beast::severities::kDebug;
|
||||
if (s == "info")
|
||||
return beast::severities::kInfo;
|
||||
if (s == "warning")
|
||||
return beast::severities::kWarning;
|
||||
if (s == "error")
|
||||
return beast::severities::kError;
|
||||
if (s == "fatal")
|
||||
return beast::severities::kFatal;
|
||||
return beast::severities::kError;
|
||||
}
|
||||
|
||||
void
|
||||
applyLoggingEnv()
|
||||
{
|
||||
auto const* envVal = std::getenv("XAHAU_TEST_LOG");
|
||||
if (!envVal || !envVal[0])
|
||||
envVal = std::getenv("TESTENV_LOGGING");
|
||||
if (!envVal || !envVal[0])
|
||||
return;
|
||||
|
||||
std::istringstream ss(envVal);
|
||||
std::string pair;
|
||||
while (std::getline(ss, pair, ','))
|
||||
{
|
||||
auto const eq = pair.find('=');
|
||||
if (eq == std::string::npos)
|
||||
continue;
|
||||
app()
|
||||
.logs()
|
||||
.get(pair.substr(0, eq))
|
||||
.threshold(parseSeverity(pair.substr(eq + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
installTransform()
|
||||
{
|
||||
app().logs().setTransform(
|
||||
[rewrite = rewrite_](std::string const& text) {
|
||||
std::string out = rewrite->prefix + text;
|
||||
for (auto const& [name, acc] : rewrite->accounts)
|
||||
{
|
||||
auto const raddr = toBase58(acc.id());
|
||||
std::string::size_type pos = 0;
|
||||
std::string const replacement = "Account(" + name + ")";
|
||||
while ((pos = out.find(raddr, pos)) != std::string::npos)
|
||||
{
|
||||
out.replace(pos, raddr.size(), replacement);
|
||||
pos += replacement.size();
|
||||
}
|
||||
}
|
||||
return out;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -27,8 +27,30 @@
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
/** Compare with both values in the failure text.
|
||||
|
||||
Beast's BEAST_EXPECT only reports file:line. Use this when the
|
||||
interesting part is the mismatch itself.
|
||||
*/
|
||||
#define BEAST_EXPECT_EQ(actual, expected) \
|
||||
[&]() -> bool { \
|
||||
auto const& beastExpectEqActual = (actual); \
|
||||
auto const& beastExpectEqExpected = (expected); \
|
||||
if (beastExpectEqActual == beastExpectEqExpected) \
|
||||
{ \
|
||||
pass(); \
|
||||
return true; \
|
||||
} \
|
||||
std::ostringstream beastExpectEqWhy; \
|
||||
beastExpectEqWhy << #actual " = " << beastExpectEqActual \
|
||||
<< ", expected " << beastExpectEqExpected; \
|
||||
fail(beastExpectEqWhy.str(), __FILE__, __LINE__); \
|
||||
return false; \
|
||||
}()
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
|
||||
@@ -41,9 +41,21 @@ hook(
|
||||
Json::Value
|
||||
hso(std::vector<uint8_t> const& wasmBytes, void (*f)(Json::Value& jv) = 0);
|
||||
|
||||
Json::Value
|
||||
hsoVersioned(
|
||||
std::vector<uint8_t> const& hookCode,
|
||||
std::uint16_t apiVersion,
|
||||
void (*f)(Json::Value& jv) = 0);
|
||||
|
||||
Json::Value
|
||||
hso(std::string const& wasmHex, void (*f)(Json::Value& jv) = 0);
|
||||
|
||||
Json::Value
|
||||
hsoVersioned(
|
||||
std::string const& hookCodeHex,
|
||||
std::uint16_t apiVersion,
|
||||
void (*f)(Json::Value& jv) = 0);
|
||||
|
||||
Json::Value
|
||||
hso_delete(void (*f)(Json::Value& jv) = 0);
|
||||
|
||||
@@ -97,7 +109,6 @@ struct StubHookContext
|
||||
std::map<uint32_t, uint32_t> guard_map{};
|
||||
StubHookResult result = {};
|
||||
std::optional<ripple::STObject> emitFailure = std::nullopt;
|
||||
const hook::HookExecutor* module = 0;
|
||||
};
|
||||
|
||||
// Overload that takes external stateMap to avoid dangling reference
|
||||
|
||||
@@ -68,8 +68,26 @@ hso(std::vector<uint8_t> const& wasmBytes, void (*f)(Json::Value& jv))
|
||||
return hso(strHex(wasmBytes), f);
|
||||
}
|
||||
|
||||
Json::Value
|
||||
hsoVersioned(
|
||||
std::vector<uint8_t> const& hookCode,
|
||||
std::uint16_t apiVersion,
|
||||
void (*f)(Json::Value& jv))
|
||||
{
|
||||
return hsoVersioned(strHex(hookCode), apiVersion, f);
|
||||
}
|
||||
|
||||
Json::Value
|
||||
hso(std::string const& wasmHex, void (*f)(Json::Value& jv))
|
||||
{
|
||||
return hsoVersioned(wasmHex, 0, f);
|
||||
}
|
||||
|
||||
Json::Value
|
||||
hsoVersioned(
|
||||
std::string const& wasmHex,
|
||||
std::uint16_t apiVersion,
|
||||
void (*f)(Json::Value& jv))
|
||||
{
|
||||
if (wasmHex.size() == 0)
|
||||
throw std::runtime_error(
|
||||
@@ -82,7 +100,7 @@ hso(std::string const& wasmHex, void (*f)(Json::Value& jv))
|
||||
jv[jss::HookOn] =
|
||||
"0000000000000000000000000000000000000000000000000000000000000000";
|
||||
jv[jss::HookNamespace] = to_string(uint256{beast::zero});
|
||||
jv[jss::HookApiVersion] = Json::Value{0};
|
||||
jv[jss::HookApiVersion] = Json::Value{apiVersion};
|
||||
}
|
||||
|
||||
if (f)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#ifndef TEST_UNIT_TEST_SUITE_JOURNAL_H
|
||||
#define TEST_UNIT_TEST_SUITE_JOURNAL_H
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <mutex>
|
||||
@@ -31,13 +32,18 @@ class SuiteJournalSink : public beast::Journal::Sink
|
||||
{
|
||||
std::string partition_;
|
||||
beast::unit_test::suite& suite_;
|
||||
Logs* logs_ = nullptr;
|
||||
|
||||
public:
|
||||
SuiteJournalSink(
|
||||
std::string const& partition,
|
||||
beast::severities::Severity threshold,
|
||||
beast::unit_test::suite& suite)
|
||||
: Sink(threshold, false), partition_(partition + " "), suite_(suite)
|
||||
beast::unit_test::suite& suite,
|
||||
Logs* logs = nullptr)
|
||||
: Sink(threshold, false)
|
||||
, partition_(partition + " ")
|
||||
, suite_(suite)
|
||||
, logs_(logs)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -101,7 +107,11 @@ SuiteJournalSink::writeAlways(
|
||||
// crashes
|
||||
static std::mutex log_mutex;
|
||||
std::lock_guard lock(log_mutex);
|
||||
suite_.log << s << partition_ << text << std::endl;
|
||||
if (logs_ && logs_->hasTransform())
|
||||
suite_.log << s << partition_ << logs_->applyTransform(text)
|
||||
<< std::endl;
|
||||
else
|
||||
suite_.log << s << partition_ << text << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
168
src/xrpld/app/hook/HookGuestMemory.h
Normal file
168
src/xrpld/app/hook/HookGuestMemory.h
Normal file
@@ -0,0 +1,168 @@
|
||||
#ifndef XRPLD_APP_HOOK_HOOKGUESTMEMORY_H_INCLUDED
|
||||
#define XRPLD_APP_HOOK_HOOKGUESTMEMORY_H_INCLUDED
|
||||
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/hook/Enum.h>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <span>
|
||||
|
||||
namespace hook {
|
||||
|
||||
/** Non-owning view of one Hook invocation's linear memory.
|
||||
|
||||
Engine adapters construct this view from the current callback's memory.
|
||||
All pointer arithmetic is widened before bounds checks; the class never
|
||||
truncates or grows guest buffers.
|
||||
*/
|
||||
class HookGuestMemory
|
||||
{
|
||||
public:
|
||||
using Error = hook_api::hook_return_code;
|
||||
enum class WriteContract : std::uint8_t { fixedBuffer, legacyActualSize };
|
||||
using CompatibilityWriter = bool (*)(
|
||||
void* context,
|
||||
std::uint32_t offset,
|
||||
std::span<std::uint8_t const> bytes) noexcept;
|
||||
using Resolver = void (*)(
|
||||
void* context,
|
||||
std::uint8_t*& data,
|
||||
std::size_t& size,
|
||||
void*& writerContext,
|
||||
CompatibilityWriter& writer) noexcept;
|
||||
|
||||
HookGuestMemory(
|
||||
std::uint8_t* data,
|
||||
std::size_t size,
|
||||
void* writerContext = nullptr,
|
||||
CompatibilityWriter writer = nullptr,
|
||||
WriteContract writeContract = WriteContract::fixedBuffer) noexcept
|
||||
: data_(data)
|
||||
, size_(size)
|
||||
, writerContext_(writerContext)
|
||||
, writer_(writer)
|
||||
, writeContract_(writeContract)
|
||||
{
|
||||
}
|
||||
|
||||
HookGuestMemory(
|
||||
void* resolverContext,
|
||||
Resolver resolver,
|
||||
WriteContract writeContract) noexcept
|
||||
: resolverContext_(resolverContext)
|
||||
, resolver_(resolver)
|
||||
, resolved_(false)
|
||||
, writeContract_(writeContract)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
resolve() const noexcept
|
||||
{
|
||||
if (resolved_)
|
||||
return;
|
||||
resolved_ = true;
|
||||
if (resolver_)
|
||||
resolver_(resolverContext_, data_, size_, writerContext_, writer_);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::uint8_t*
|
||||
data() const noexcept
|
||||
{
|
||||
resolve();
|
||||
return data_;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::size_t
|
||||
size() const noexcept
|
||||
{
|
||||
resolve();
|
||||
return size_;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
valid() const noexcept
|
||||
{
|
||||
resolve();
|
||||
return data_ != nullptr && size_ != 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] WriteContract
|
||||
writeContract() const noexcept
|
||||
{
|
||||
return writeContract_;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
contains(std::uint32_t offset, std::uint32_t length) const noexcept
|
||||
{
|
||||
resolve();
|
||||
auto const start = static_cast<std::uint64_t>(offset);
|
||||
auto const count = static_cast<std::uint64_t>(length);
|
||||
auto const extent = static_cast<std::uint64_t>(size_);
|
||||
return data_ != nullptr && start < extent && count <= extent - start;
|
||||
}
|
||||
|
||||
[[nodiscard]] ripple::Expected<std::span<std::uint8_t const>, Error>
|
||||
read(std::uint32_t offset, std::uint32_t length) const noexcept
|
||||
{
|
||||
if (!contains(offset, length))
|
||||
return ripple::Unexpected(Error::OUT_OF_BOUNDS);
|
||||
return std::span<std::uint8_t const>{data_ + offset, length};
|
||||
}
|
||||
|
||||
[[nodiscard]] ripple::Expected<std::span<std::uint8_t>, Error>
|
||||
write(std::uint32_t offset, std::uint32_t length) const noexcept
|
||||
{
|
||||
if (!contains(offset, length))
|
||||
return ripple::Unexpected(Error::OUT_OF_BOUNDS);
|
||||
return std::span<std::uint8_t>{data_ + offset, length};
|
||||
}
|
||||
|
||||
/** Copy bytes with the raw-operation compatibility boundary.
|
||||
|
||||
Unlike contains(), a zero-byte write at exactly the end of memory is
|
||||
permitted.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
canCompatibilityCopy(std::uint32_t offset, std::size_t count) const noexcept
|
||||
{
|
||||
resolve();
|
||||
auto const start = static_cast<std::uint64_t>(offset);
|
||||
auto const length = static_cast<std::uint64_t>(count);
|
||||
auto const extent = static_cast<std::uint64_t>(size_);
|
||||
return data_ != nullptr && start <= extent && length <= extent - start;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
compatibilityCopy(std::uint32_t offset, std::span<std::uint8_t const> bytes)
|
||||
const noexcept
|
||||
{
|
||||
if (!canCompatibilityCopy(offset, bytes.size()))
|
||||
return false;
|
||||
|
||||
if (writer_)
|
||||
return writer_(writerContext_, offset, bytes);
|
||||
|
||||
if (bytes.empty())
|
||||
return true;
|
||||
|
||||
std::memcpy(data_ + offset, bytes.data(), bytes.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::uint8_t* data_ = nullptr;
|
||||
mutable std::size_t size_ = 0;
|
||||
mutable void* writerContext_ = nullptr;
|
||||
mutable CompatibilityWriter writer_ = nullptr;
|
||||
void* resolverContext_ = nullptr;
|
||||
Resolver resolver_ = nullptr;
|
||||
mutable bool resolved_ = true;
|
||||
WriteContract writeContract_;
|
||||
};
|
||||
|
||||
} // namespace hook
|
||||
|
||||
#endif // XRPLD_APP_HOOK_HOOKGUESTMEMORY_H_INCLUDED
|
||||
120
src/xrpld/app/hook/HookHostFunction.h
Normal file
120
src/xrpld/app/hook/HookHostFunction.h
Normal file
@@ -0,0 +1,120 @@
|
||||
#ifndef XRPLD_APP_HOOK_HOOKHOSTFUNCTION_H_INCLUDED
|
||||
#define XRPLD_APP_HOOK_HOOKHOSTFUNCTION_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/hook/HookGuestMemory.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace hook {
|
||||
|
||||
enum class HookHostValueKind : std::uint8_t { i32, u32, i64, u64 };
|
||||
|
||||
struct HookHostValue
|
||||
{
|
||||
HookHostValueKind kind;
|
||||
union
|
||||
{
|
||||
std::uint32_t bits32;
|
||||
std::uint64_t bits64;
|
||||
};
|
||||
|
||||
static HookHostValue
|
||||
i32(std::uint32_t value) noexcept
|
||||
{
|
||||
HookHostValue result;
|
||||
result.kind = HookHostValueKind::i32;
|
||||
result.bits32 = value;
|
||||
return result;
|
||||
}
|
||||
|
||||
static HookHostValue
|
||||
u32(std::uint32_t value) noexcept
|
||||
{
|
||||
HookHostValue result;
|
||||
result.kind = HookHostValueKind::u32;
|
||||
result.bits32 = value;
|
||||
return result;
|
||||
}
|
||||
|
||||
static HookHostValue
|
||||
i64(std::uint64_t value) noexcept
|
||||
{
|
||||
HookHostValue result;
|
||||
result.kind = HookHostValueKind::i64;
|
||||
result.bits64 = value;
|
||||
return result;
|
||||
}
|
||||
|
||||
static HookHostValue
|
||||
u64(std::uint64_t value) noexcept
|
||||
{
|
||||
HookHostValue result;
|
||||
result.kind = HookHostValueKind::u64;
|
||||
result.bits64 = value;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
asI32() const noexcept
|
||||
{
|
||||
return bits32;
|
||||
}
|
||||
|
||||
std::uint64_t
|
||||
asI64() const noexcept
|
||||
{
|
||||
return bits64;
|
||||
}
|
||||
};
|
||||
|
||||
enum class HookHostCallStatus : std::uint8_t {
|
||||
success,
|
||||
accept,
|
||||
rollback,
|
||||
trap
|
||||
};
|
||||
|
||||
constexpr bool
|
||||
isI32(HookHostValueKind kind) noexcept
|
||||
{
|
||||
return kind == HookHostValueKind::i32 || kind == HookHostValueKind::u32;
|
||||
}
|
||||
|
||||
using HookHostFunction = HookHostCallStatus (*)(
|
||||
void* userData,
|
||||
HookGuestMemory& memory,
|
||||
HookHostValue const* inputs,
|
||||
std::size_t inputCount,
|
||||
HookHostValue* outputs,
|
||||
std::size_t outputCount);
|
||||
|
||||
struct HookHostFunctionDescriptor
|
||||
{
|
||||
std::string_view name;
|
||||
HookHostFunction function;
|
||||
std::vector<HookHostValueKind> parameters;
|
||||
HookHostValueKind result;
|
||||
ripple::uint256 declarationAmendment;
|
||||
std::uint16_t implementationVersion;
|
||||
};
|
||||
|
||||
// v1 is pre-activation; freeze bodies before featureJSHooks is supported.
|
||||
|
||||
std::span<HookHostFunctionDescriptor const>
|
||||
hookHostFunctionCatalogue() noexcept;
|
||||
|
||||
HookHostFunctionDescriptor const*
|
||||
findHookHostFunction(std::string_view name) noexcept;
|
||||
|
||||
HookHostFunctionDescriptor const*
|
||||
findHookHostFunction(
|
||||
std::string_view name,
|
||||
std::uint16_t implementationVersion) noexcept;
|
||||
|
||||
} // namespace hook
|
||||
|
||||
#endif // XRPLD_APP_HOOK_HOOKHOSTFUNCTION_H_INCLUDED
|
||||
244
src/xrpld/app/hook/HookHostMacros.h
Normal file
244
src/xrpld/app/hook/HookHostMacros.h
Normal file
@@ -0,0 +1,244 @@
|
||||
#ifndef XRPLD_APP_HOOK_HOOKHOSTMACROS_H_INCLUDED
|
||||
#define XRPLD_APP_HOOK_HOOKHOSTMACROS_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/hook/HookHostFunction.h>
|
||||
#include <xrpl/hook/Macro.h>
|
||||
#include <bit>
|
||||
#include <span>
|
||||
#include <wasmedge/wasmedge.h>
|
||||
|
||||
namespace hook::detail {
|
||||
|
||||
inline bool
|
||||
writeWasmEdgeGuestMemory(
|
||||
void* context,
|
||||
std::uint32_t offset,
|
||||
std::span<std::uint8_t const> bytes) noexcept
|
||||
{
|
||||
auto* memory = static_cast<WasmEdge_MemoryInstanceContext*>(context);
|
||||
return memory &&
|
||||
WasmEdge_ResultOK(WasmEdge_MemoryInstanceSetData(
|
||||
memory, bytes.data(), offset, bytes.size()));
|
||||
}
|
||||
|
||||
inline void
|
||||
resolveWasmEdgeGuestMemory(
|
||||
void* context,
|
||||
std::uint8_t*& data,
|
||||
std::size_t& size,
|
||||
void*& writerContext,
|
||||
HookGuestMemory::CompatibilityWriter& writer) noexcept
|
||||
{
|
||||
auto const* frame =
|
||||
static_cast<WasmEdge_CallingFrameContext const*>(context);
|
||||
auto* memoryContext = WasmEdge_CallingFrameGetMemoryInstance(frame, 0);
|
||||
data = WasmEdge_MemoryInstanceGetPointer(memoryContext, 0, 0);
|
||||
// Keep the legacy C-Hook 64-KiB Wasm page calculation.
|
||||
size = WasmEdge_MemoryInstanceGetPageSize(memoryContext) * 65536ULL;
|
||||
writerContext = memoryContext;
|
||||
writer = &writeWasmEdgeGuestMemory;
|
||||
}
|
||||
|
||||
inline HookGuestMemory
|
||||
wasmEdgeGuestMemory(WasmEdge_CallingFrameContext const& frame) noexcept
|
||||
{
|
||||
return {
|
||||
const_cast<WasmEdge_CallingFrameContext*>(&frame),
|
||||
&resolveWasmEdgeGuestMemory,
|
||||
HookGuestMemory::WriteContract::legacyActualSize};
|
||||
}
|
||||
|
||||
} // namespace hook::detail
|
||||
|
||||
#undef DECLARE_HOOK_FUNCTION
|
||||
#undef DEFINE_HOOK_FUNCTION
|
||||
#undef HOOK_SETUP
|
||||
#undef WRITE_WASM_MEMORY
|
||||
#undef WRITE_WASM_MEMORY_AND_RETURN
|
||||
|
||||
#define HOST_VAL_uint32_t static_cast<uint32_t>(inputs[_hostStack++].asI32())
|
||||
#define HOST_VAL_int32_t std::bit_cast<int32_t>(inputs[_hostStack++].asI32())
|
||||
#define HOST_VAL_uint64_t static_cast<uint64_t>(inputs[_hostStack++].asI64())
|
||||
#define HOST_VAL_int64_t std::bit_cast<int64_t>(inputs[_hostStack++].asI64())
|
||||
#define HOST_KIND_uint32_t hook::HookHostValueKind::u32
|
||||
#define HOST_KIND_int32_t hook::HookHostValueKind::i32
|
||||
#define HOST_KIND_uint64_t hook::HookHostValueKind::u64
|
||||
#define HOST_KIND_int64_t hook::HookHostValueKind::i64
|
||||
#define HOST_VAR_ASSIGN(T, V) \
|
||||
if (inputs[_hostStack].kind != CAT(HOST_KIND_##T)) \
|
||||
return hook::HookHostCallStatus::trap; \
|
||||
T V = CAT(HOST_VAL_##T)
|
||||
|
||||
#define HOST_RET_uint32_t(value) \
|
||||
hook::HookHostValue::u32(static_cast<uint32_t>(value))
|
||||
#define HOST_RET_int32_t(value) \
|
||||
hook::HookHostValue::i32(static_cast<uint32_t>(value))
|
||||
#define HOST_RET_uint64_t(value) \
|
||||
hook::HookHostValue::u64(static_cast<uint64_t>(value))
|
||||
#define HOST_RET_int64_t(value) \
|
||||
hook::HookHostValue::i64(static_cast<uint64_t>(value))
|
||||
#define HOST_RET_ASSIGN(T, value) CAT2(HOST_RET_, T(value))
|
||||
|
||||
#define DECLARE_HOOK_FUNCTION(R, F, ...) \
|
||||
std::variant<UNSIGNED_TYPE(R), hook_api::hook_return_code> F( \
|
||||
hook::HookContext& hookCtx, \
|
||||
hook::HookGuestMemory& frameCtx __VA_OPT__(COMMA __VA_ARGS__)); \
|
||||
extern WasmEdge_Result WasmFunction##F( \
|
||||
void* data_ptr, \
|
||||
WasmEdge_CallingFrameContext const* frameCtx, \
|
||||
WasmEdge_Value const* in, \
|
||||
WasmEdge_Value* out); \
|
||||
extern WasmEdge_ValType WasmFunctionParams##F[]; \
|
||||
extern WasmEdge_ValType WasmFunctionResult##F[]; \
|
||||
extern WasmEdge_FunctionTypeContext* WasmFunctionType##F; \
|
||||
extern WasmEdge_String WasmFunctionName##F; \
|
||||
extern hook::HookHostCallStatus HostFunction##F( \
|
||||
void* userData, \
|
||||
hook::HookGuestMemory& guestMemory, \
|
||||
hook::HookHostValue const* inputs, \
|
||||
std::size_t inputCount, \
|
||||
hook::HookHostValue* outputs, \
|
||||
std::size_t outputCount);
|
||||
|
||||
#define DEFINE_HOOK_FUNCTION(R, F, ...) \
|
||||
WasmEdge_Result hook_api::WasmFunction##F( \
|
||||
void* data_ptr, \
|
||||
WasmEdge_CallingFrameContext const* frameCtx, \
|
||||
WasmEdge_Value const* in, \
|
||||
WasmEdge_Value* out) \
|
||||
{ \
|
||||
__VA_OPT__(int _stack = 0;) \
|
||||
__VA_OPT__(FOR_VARS(VAR_ASSIGN, 2, __VA_ARGS__);) \
|
||||
auto* hookCtx = reinterpret_cast<hook::HookContext*>(data_ptr); \
|
||||
auto guestMemory = hook::detail::wasmEdgeGuestMemory(*frameCtx); \
|
||||
auto const& return_code = hook_api::F( \
|
||||
*hookCtx, guestMemory __VA_OPT__(COMMA STRIP_TYPES(__VA_ARGS__))); \
|
||||
if (std::holds_alternative<hook_api::hook_return_code>(return_code) && \
|
||||
(std::get<hook_api::hook_return_code>(return_code) == \
|
||||
RC_ROLLBACK || \
|
||||
std::get<hook_api::hook_return_code>(return_code) == RC_ACCEPT)) \
|
||||
return WasmEdge_Result_Terminate; \
|
||||
out[0] = RET_ASSIGN( \
|
||||
R, \
|
||||
std::holds_alternative<UNSIGNED_TYPE(R)>(return_code) \
|
||||
? std::get<UNSIGNED_TYPE(R)>(return_code) \
|
||||
: R(std::get<hook_api::hook_return_code>(return_code))); \
|
||||
return WasmEdge_Result_Success; \
|
||||
} \
|
||||
WasmEdge_ValType hook_api::WasmFunctionParams##F[] = { \
|
||||
__VA_OPT__(FOR_VARS(WASM_VAL_TYPE, 0, __VA_ARGS__))}; \
|
||||
WasmEdge_ValType hook_api::WasmFunctionResult##F[1] = { \
|
||||
WASM_VAL_TYPE(R, dummy)}; \
|
||||
WasmEdge_FunctionTypeContext* hook_api::WasmFunctionType##F = \
|
||||
WasmEdge_FunctionTypeCreate( \
|
||||
WasmFunctionParams##F, \
|
||||
VA_NARGS(NULL __VA_OPT__(, __VA_ARGS__)), \
|
||||
WasmFunctionResult##F, \
|
||||
1); \
|
||||
WasmEdge_String hook_api::WasmFunctionName##F = \
|
||||
WasmEdge_StringCreateByCString(#F); \
|
||||
hook::HookHostCallStatus hook_api::HostFunction##F( \
|
||||
void* userData, \
|
||||
hook::HookGuestMemory& guestMemory, \
|
||||
hook::HookHostValue const* inputs, \
|
||||
std::size_t inputCount, \
|
||||
hook::HookHostValue* outputs, \
|
||||
std::size_t outputCount) \
|
||||
{ \
|
||||
constexpr auto expectedInputs = \
|
||||
VA_NARGS(NULL __VA_OPT__(, __VA_ARGS__)); \
|
||||
if (inputCount != expectedInputs || outputCount != 1 || !outputs || \
|
||||
(expectedInputs != 0 && !inputs)) \
|
||||
return hook::HookHostCallStatus::trap; \
|
||||
__VA_OPT__(int _hostStack = 0;) \
|
||||
__VA_OPT__(FOR_VARS(HOST_VAR_ASSIGN, 2, __VA_ARGS__);) \
|
||||
auto* hookCtx = reinterpret_cast<hook::HookContext*>(userData); \
|
||||
if (!hookCtx) \
|
||||
return hook::HookHostCallStatus::trap; \
|
||||
auto const& returnCode = hook_api::F( \
|
||||
*hookCtx, guestMemory __VA_OPT__(COMMA STRIP_TYPES(__VA_ARGS__))); \
|
||||
if (std::holds_alternative<hook_api::hook_return_code>(returnCode) && \
|
||||
(std::get<hook_api::hook_return_code>(returnCode) == \
|
||||
hook_api::hook_return_code::RC_ROLLBACK || \
|
||||
std::get<hook_api::hook_return_code>(returnCode) == \
|
||||
hook_api::hook_return_code::RC_ACCEPT)) \
|
||||
return std::get<hook_api::hook_return_code>(returnCode) == \
|
||||
hook_api::hook_return_code::RC_ACCEPT \
|
||||
? hook::HookHostCallStatus::accept \
|
||||
: hook::HookHostCallStatus::rollback; \
|
||||
outputs[0] = HOST_RET_ASSIGN( \
|
||||
R, \
|
||||
std::holds_alternative<UNSIGNED_TYPE(R)>(returnCode) \
|
||||
? std::get<UNSIGNED_TYPE(R)>(returnCode) \
|
||||
: R(std::get<hook_api::hook_return_code>(returnCode))); \
|
||||
return hook::HookHostCallStatus::success; \
|
||||
} \
|
||||
std::variant<UNSIGNED_TYPE(R), hook_api::hook_return_code> hook_api::F( \
|
||||
hook::HookContext& hookCtx, \
|
||||
hook::HookGuestMemory& frameCtx __VA_OPT__(COMMA __VA_ARGS__))
|
||||
|
||||
#define HOOK_SETUP() \
|
||||
using enum hook_api::hook_return_code; \
|
||||
try \
|
||||
{ \
|
||||
[[maybe_unused]] ApplyContext& applyCtx = hookCtx.applyCtx; \
|
||||
[[maybe_unused]] auto& view = applyCtx.view(); \
|
||||
[[maybe_unused]] auto j = applyCtx.app.journal("View"); \
|
||||
[[maybe_unused]] auto jh = applyCtx.app.journal("HooksTrace"); \
|
||||
frameCtx.resolve(); \
|
||||
[[maybe_unused]] auto& guestMemory = frameCtx; \
|
||||
[[maybe_unused]] unsigned char* memory = guestMemory.data(); \
|
||||
[[maybe_unused]] const uint64_t memory_length = guestMemory.size(); \
|
||||
[[maybe_unused]] auto& api = hookCtx.api(); \
|
||||
if (!guestMemory.valid()) \
|
||||
return INTERNAL_ERROR;
|
||||
|
||||
#define WRITE_WASM_MEMORY( \
|
||||
bytes_written, \
|
||||
guest_dst_ptr, \
|
||||
guest_dst_len, \
|
||||
host_src_ptr, \
|
||||
host_src_len, \
|
||||
host_memory_ptr, \
|
||||
guest_memory_length) \
|
||||
{ \
|
||||
int64_t bytes_to_write = std::min( \
|
||||
static_cast<int64_t>(host_src_len), \
|
||||
static_cast<int64_t>(guest_dst_len)); \
|
||||
if (guest_dst_ptr + bytes_to_write > guest_memory_length) \
|
||||
{ \
|
||||
JLOG(j.warn()) << "HookError[" << HC_ACC() << "]: " << __func__ \
|
||||
<< " tried to retreive blob of " << host_src_len \
|
||||
<< " bytes past end of wasm memory"; \
|
||||
return OUT_OF_BOUNDS; \
|
||||
} \
|
||||
if (!guestMemory.compatibilityCopy( \
|
||||
guest_dst_ptr, \
|
||||
std::span<std::uint8_t const>{ \
|
||||
reinterpret_cast<uint8_t const*>(host_src_ptr), \
|
||||
static_cast<std::size_t>(bytes_to_write)})) \
|
||||
return INTERNAL_ERROR; \
|
||||
bytes_written += bytes_to_write; \
|
||||
}
|
||||
|
||||
#define WRITE_WASM_MEMORY_AND_RETURN( \
|
||||
guest_dst_ptr, \
|
||||
guest_dst_len, \
|
||||
host_src_ptr, \
|
||||
host_src_len, \
|
||||
host_memory_ptr, \
|
||||
guest_memory_length) \
|
||||
{ \
|
||||
uint64_t bytes_written = 0; \
|
||||
WRITE_WASM_MEMORY( \
|
||||
bytes_written, \
|
||||
guest_dst_ptr, \
|
||||
guest_dst_len, \
|
||||
host_src_ptr, \
|
||||
host_src_len, \
|
||||
host_memory_ptr, \
|
||||
guest_memory_length); \
|
||||
return bytes_written; \
|
||||
}
|
||||
|
||||
#endif // XRPLD_APP_HOOK_HOOKHOSTMACROS_H_INCLUDED
|
||||
247
src/xrpld/app/hook/QuickJSHookRuntime.h
Normal file
247
src/xrpld/app/hook/QuickJSHookRuntime.h
Normal file
@@ -0,0 +1,247 @@
|
||||
#ifndef XRPLD_APP_HOOK_QUICKJSHOOKRUNTIME_H_INCLUDED
|
||||
#define XRPLD_APP_HOOK_QUICKJSHOOKRUNTIME_H_INCLUDED
|
||||
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/hook/HookArtifact.h>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#ifdef ENABLE_TESTS
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
class STObject;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace hook {
|
||||
|
||||
struct HookContext;
|
||||
class QuickJSProviderRuntime;
|
||||
using QuickJSRuntimeHandle = std::shared_ptr<QuickJSProviderRuntime const>;
|
||||
|
||||
/** Consensus-relevant data needed to retain and execute one runtime profile.
|
||||
|
||||
The profile identity keys the registry, while the bytecode ABI and API
|
||||
version prevent an artifact from resolving through a merely compatible
|
||||
but historically different provider.
|
||||
*/
|
||||
struct QuickJSRuntimeProfile
|
||||
{
|
||||
artifact::Identity bytecodeABI;
|
||||
artifact::Identity runtimeProfile;
|
||||
artifact::Identity providerSHA256;
|
||||
std::size_t providerSize;
|
||||
std::uint16_t hookApiVersion;
|
||||
std::uint64_t initializationFuel;
|
||||
std::uint64_t invocationFuel;
|
||||
std::uint64_t hostWorkBudget;
|
||||
std::uint64_t hostWorkBasePerCall;
|
||||
std::uint64_t hostWorkPerAddressedByte;
|
||||
std::string hostWorkMeter;
|
||||
std::string hostAdapterPolicy;
|
||||
std::uint32_t heapBytes;
|
||||
std::uint32_t stackBytes;
|
||||
std::uint32_t serializedObjectMaxBytes;
|
||||
std::uint32_t serializedObjectMaxFields;
|
||||
std::uint32_t serializedObjectMaxScopes;
|
||||
std::uint32_t serializedObjectMaxDepth;
|
||||
std::uint32_t providerMemoryMinimumPages;
|
||||
std::uint32_t providerMemoryMaximumPages;
|
||||
|
||||
bool
|
||||
operator==(QuickJSRuntimeProfile const&) const = default;
|
||||
};
|
||||
|
||||
QuickJSRuntimeProfile
|
||||
currentQuickJSRuntimeProfile();
|
||||
|
||||
/** The sealed provider projected into this binary at build time, or empty
|
||||
when the build's bundle directory carried no provider binary. Defined by
|
||||
the generated QuickJSProviderValues.cpp; the bytes are still SHA-verified
|
||||
against the current profile when registered. */
|
||||
std::span<std::uint8_t const>
|
||||
embeddedQuickJSProvider();
|
||||
|
||||
/** Verify and compile one provider, retaining it by profile/ABI/API key.
|
||||
|
||||
Provider bytes are hashed only here. Stores, instances, guest memory, and
|
||||
HookContext remain callback-scoped and are never retained in the registry.
|
||||
*/
|
||||
std::optional<std::string>
|
||||
registerQuickJSRuntime(
|
||||
QuickJSRuntimeProfile profile,
|
||||
std::span<std::uint8_t const> provider);
|
||||
|
||||
/** Begin verifying/compiling one provider on a background thread.
|
||||
|
||||
Cranelift compilation of the whole provider is CPU-heavy; this moves it
|
||||
off the caller's thread so process startup is not serialized behind it.
|
||||
The compiled module lives in memory for the life of the process and is
|
||||
thrown away with it — no serialization, no on-disk cache, nothing new is
|
||||
consensus-visible (the input is still the SHA-verified provider bytes).
|
||||
|
||||
Returns immediately. nullopt means the identity is registered, still
|
||||
compiling, or was just started; otherwise the reason the launch was
|
||||
refused: the identity is already registered or launched with a
|
||||
different profile, an earlier launch of it failed (the retained error;
|
||||
failure is terminal, there is no relaunch), or no thread could be
|
||||
started. A same-profile relaunch is a no-op.
|
||||
|
||||
Contract for callers:
|
||||
- launch must return before any findQuickJSRuntime or await for the
|
||||
identity; a find that races the launch answers as if nothing was
|
||||
launched.
|
||||
- the compile thread is detached and nothing joins it at exit, so the
|
||||
process must awaitQuickJSRuntimeRegistration before it shuts down.
|
||||
The daemon (Main.cpp) launches before Application::setup, awaits
|
||||
after it and before start, and treats an error as fatal — a node
|
||||
that cannot compile the sealed provider must not stay up, or it will
|
||||
disagree with its peers on every QuickJS artifact.
|
||||
*/
|
||||
std::optional<std::string>
|
||||
launchQuickJSRuntimeRegistration(
|
||||
QuickJSRuntimeProfile profile,
|
||||
ripple::Blob provider);
|
||||
|
||||
/** Wait for a launched registration of this identity to settle.
|
||||
|
||||
Returns nullopt once the identity is registered with this profile —
|
||||
by the launched compile or by a synchronous registerQuickJSRuntime —
|
||||
otherwise why it is not: the retained compile failure, a registration
|
||||
of the same identity with a different profile, or, when nothing was
|
||||
launched or registered, a message saying so. A startup that forgets to
|
||||
launch therefore does not look healthy.
|
||||
*/
|
||||
std::optional<std::string>
|
||||
awaitQuickJSRuntimeRegistration(QuickJSRuntimeProfile const& profile);
|
||||
|
||||
/** Resolve the exact historical runtime selected by a deployment envelope.
|
||||
|
||||
Waits for a launched registration of the same identity to settle before
|
||||
answering, so background compilation is invisible to resolution except
|
||||
as latency; the startup barrier is awaitQuickJSRuntimeRegistration, and
|
||||
this wait is only the safety net for a caller that races a
|
||||
still-compiling process. */
|
||||
QuickJSRuntimeHandle
|
||||
findQuickJSRuntime(artifact::View const& artifact);
|
||||
|
||||
struct QuickJSModuleValidation
|
||||
{
|
||||
bool hasCallback = false;
|
||||
artifact::XFLArithmeticProfile xflArithmeticProfile =
|
||||
artifact::XFLArithmeticProfile::none;
|
||||
};
|
||||
|
||||
/** Validate payload bytecode in the exact retained provider.
|
||||
|
||||
Module initialization runs in a bounded, disposable Store with no Hook
|
||||
invocation context, so it must be synchronous and host-free. The entry
|
||||
points are not invoked. On success, reports the observed immutable module
|
||||
profile and whether initialized cbak is callable.
|
||||
*/
|
||||
std::optional<std::string>
|
||||
validateQuickJSBytecode(
|
||||
QuickJSRuntimeHandle const& runtime,
|
||||
std::span<std::uint8_t const> bytecode,
|
||||
QuickJSModuleValidation& validation);
|
||||
|
||||
/** Execute payload-only bytecode with a previously registered runtime. */
|
||||
void
|
||||
executeQuickJSBytecode(
|
||||
HookContext& hookCtx,
|
||||
QuickJSRuntimeHandle const& runtime,
|
||||
std::span<std::uint8_t const> bytecode,
|
||||
bool callback,
|
||||
std::uint32_t reserved,
|
||||
beast::Journal const& journal);
|
||||
|
||||
#ifdef ENABLE_TESTS
|
||||
namespace quickjs {
|
||||
struct QuickJSHostCallObservationForTests
|
||||
{
|
||||
std::uint64_t invocation = 0;
|
||||
std::string name;
|
||||
std::uint64_t declaredBytes = 0;
|
||||
std::uint64_t cost = 0;
|
||||
std::uint64_t hostWorkBefore = 0;
|
||||
std::uint64_t hostWorkAfter = 0;
|
||||
bool dispatched = false;
|
||||
std::size_t liveSlots = 0;
|
||||
std::optional<std::size_t> liveSlotSerializedBytes;
|
||||
std::weak_ptr<ripple::STObject const> liveSlotOwner;
|
||||
};
|
||||
|
||||
/** Observe raw QuickJS host crossings on the calling test thread.
|
||||
|
||||
The destination is non-owning and must outlive every observed invocation.
|
||||
Passing nullptr disables observation and resets the invocation sequence.
|
||||
*/
|
||||
void
|
||||
setQuickJSHostCallObservationsForTests(
|
||||
std::vector<QuickJSHostCallObservationForTests>* observations) noexcept;
|
||||
|
||||
/** Override the calling test thread's per-invocation host-work budget. */
|
||||
void
|
||||
setQuickJSHostWorkBudgetForTests(
|
||||
std::optional<std::uint64_t> hostWorkBudget) noexcept;
|
||||
} // namespace quickjs
|
||||
|
||||
struct QuickJSValidationForTests : QuickJSModuleValidation
|
||||
{
|
||||
std::optional<std::string> error;
|
||||
std::uint64_t invocationFuelConsumed = 0;
|
||||
};
|
||||
|
||||
/** Decode one raw provider validation word through the production decoder. */
|
||||
std::optional<std::string>
|
||||
decodeQuickJSModuleValidationForTests(
|
||||
std::int32_t word,
|
||||
QuickJSModuleValidation& validation);
|
||||
|
||||
/** Validate through the production path while exposing its fuel delta. */
|
||||
QuickJSValidationForTests
|
||||
validateQuickJSBytecodeForTests(
|
||||
QuickJSRuntimeHandle const& runtime,
|
||||
std::span<std::uint8_t const> bytecode);
|
||||
|
||||
/** Register the currently generated profile using a test-supplied provider. */
|
||||
std::optional<std::string>
|
||||
setQuickJSProviderForTests(ripple::Blob provider);
|
||||
|
||||
/** Per-session cost of the retained provider, measured cold N times.
|
||||
|
||||
Each iteration is one disposable session exactly as validation builds
|
||||
it: create (Store, imports, instantiate) → initialize (`_initialize`,
|
||||
`qjs_init`, limits) → arm invocation fuel → validate the given bytecode.
|
||||
Wall times are summed over the iterations and the minimum per stage is
|
||||
kept; fuel figures are from the last iteration (they are deterministic).
|
||||
*/
|
||||
struct QuickJSSessionCostForTests
|
||||
{
|
||||
std::size_t iterations = 0;
|
||||
std::uint64_t createNanosTotal = 0;
|
||||
std::uint64_t createNanosMin = 0;
|
||||
std::uint64_t initializeNanosTotal = 0;
|
||||
std::uint64_t initializeNanosMin = 0;
|
||||
std::uint64_t validateNanosTotal = 0;
|
||||
std::uint64_t validateNanosMin = 0;
|
||||
std::uint64_t initializationFuelConsumed = 0;
|
||||
std::uint64_t invocationFuelConsumed = 0;
|
||||
std::optional<std::string> error;
|
||||
};
|
||||
|
||||
QuickJSSessionCostForTests
|
||||
measureQuickJSSessionCostForTests(
|
||||
QuickJSRuntimeHandle const& runtime,
|
||||
std::span<std::uint8_t const> bytecode,
|
||||
std::size_t iterations);
|
||||
#endif
|
||||
|
||||
} // namespace hook
|
||||
|
||||
#endif // XRPLD_APP_HOOK_QUICKJSHOOKRUNTIME_H_INCLUDED
|
||||
@@ -1,12 +1,12 @@
|
||||
#ifndef APPLY_HOOK_INCLUDED
|
||||
#define APPLY_HOOK_INCLUDED 1
|
||||
#include <xrpld/app/hook/HookAPI.h>
|
||||
#include <xrpld/app/hook/HookHostMacros.h>
|
||||
#include <xrpld/app/misc/Transaction.h>
|
||||
#include <xrpld/app/tx/detail/ApplyContext.h>
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/hook/Enum.h>
|
||||
#include <xrpl/hook/Macro.h>
|
||||
#include <xrpl/hook/Misc.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
@@ -104,6 +104,7 @@ apply(
|
||||
used for caching (one day) */
|
||||
ripple::uint256 const&
|
||||
hookHash, /* hash of the actual hook byte code, used for metadata */
|
||||
uint16_t hookApiVersion,
|
||||
ripple::uint256 const& hookCanEmit,
|
||||
ripple::uint256 const& hookNamespace,
|
||||
ripple::Blob const& wasm,
|
||||
|
||||
208
src/xrpld/app/hook/detail/HookArtifact.cpp
Normal file
208
src/xrpld/app/hook/detail/HookArtifact.cpp
Normal file
@@ -0,0 +1,208 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/hook/detail/QuickJSProviderProfile.h>
|
||||
#include <xrpl/hook/HookArtifact.h>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace hook::artifact {
|
||||
|
||||
Identity const quickJSBytecodeABI = generated::bytecodeABI;
|
||||
Identity const quickJSRuntimeProfile = generated::runtimeProfile;
|
||||
Identity const quickJSProviderSHA256 = generated::providerSHA256;
|
||||
std::size_t const quickJSProviderSize = generated::providerSize;
|
||||
std::uint16_t const quickJSHookApiVersion = generated::hookApiVersion;
|
||||
std::uint64_t const quickJSInitializationFuel = generated::initializationFuel;
|
||||
std::uint64_t const quickJSInvocationFuel = generated::invocationFuel;
|
||||
std::string_view const quickJSHostWorkMeter = generated::hostWorkMeter;
|
||||
std::uint64_t const quickJSHostWorkBudget = generated::hostWorkBudget;
|
||||
std::uint64_t const quickJSHostWorkBasePerCall = generated::hostWorkBasePerCall;
|
||||
std::uint64_t const quickJSHostWorkPerAddressedByte =
|
||||
generated::hostWorkPerAddressedByte;
|
||||
std::string_view const quickJSHostAdapterPolicy = generated::hostAdapterPolicy;
|
||||
std::uint32_t const quickJSHeapBytes = generated::heapBytes;
|
||||
std::uint32_t const quickJSStackBytes = generated::stackBytes;
|
||||
std::uint32_t const quickJSSerializedObjectMaxBytes =
|
||||
generated::serializedObjectMaxBytes;
|
||||
std::uint32_t const quickJSSerializedObjectMaxFields =
|
||||
generated::serializedObjectMaxFields;
|
||||
std::uint32_t const quickJSSerializedObjectMaxScopes =
|
||||
generated::serializedObjectMaxScopes;
|
||||
std::uint32_t const quickJSSerializedObjectMaxDepth =
|
||||
generated::serializedObjectMaxDepth;
|
||||
std::uint32_t const quickJSProviderMemoryMinimumPages =
|
||||
generated::providerMemoryMinimumPages;
|
||||
std::uint32_t const quickJSProviderMemoryMaximumPages =
|
||||
generated::providerMemoryMaximumPages;
|
||||
std::uint32_t const quickJSProviderWasmStackBytes = generated::wasmStackBytes;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::array<std::uint8_t, 4> wasmMagic = {0x00, 0x61, 0x73, 0x6D};
|
||||
|
||||
bool
|
||||
startsWith(ripple::Slice bytes, std::array<std::uint8_t, 4> const& prefix)
|
||||
{
|
||||
return bytes.size() >= prefix.size() &&
|
||||
std::equal(prefix.begin(), prefix.end(), bytes.begin());
|
||||
}
|
||||
|
||||
std::uint16_t
|
||||
readU16BE(std::uint8_t const* bytes)
|
||||
{
|
||||
return (static_cast<std::uint16_t>(bytes[0]) << 8) |
|
||||
static_cast<std::uint16_t>(bytes[1]);
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
readU32BE(std::uint8_t const* bytes)
|
||||
{
|
||||
return (static_cast<std::uint32_t>(bytes[0]) << 24) |
|
||||
(static_cast<std::uint32_t>(bytes[1]) << 16) |
|
||||
(static_cast<std::uint32_t>(bytes[2]) << 8) |
|
||||
static_cast<std::uint32_t>(bytes[3]);
|
||||
}
|
||||
|
||||
bool
|
||||
isZero(Identity const& identity)
|
||||
{
|
||||
return std::all_of(
|
||||
identity.begin(), identity.end(), [](std::uint8_t value) {
|
||||
return value == 0;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool
|
||||
isCurrentQuickJS(View const& artifact) noexcept
|
||||
{
|
||||
return artifact.kind == Kind::quickJSBytecode &&
|
||||
artifact.hookApiVersion == quickJSHookApiVersion &&
|
||||
artifact.bytecodeABI == quickJSBytecodeABI &&
|
||||
artifact.runtimeProfile == quickJSRuntimeProfile;
|
||||
}
|
||||
|
||||
bool
|
||||
fitsCreateCodeSizeLimit(ripple::Slice code) noexcept
|
||||
{
|
||||
if (code.size() <= maxLegacyCreateCodeSize)
|
||||
return true;
|
||||
if (code.size() > maxQuickJSCreateCodeSize)
|
||||
return false;
|
||||
return startsWith(code, quickJSMagic);
|
||||
}
|
||||
|
||||
ripple::Expected<View, Error>
|
||||
parse(ripple::Slice code) noexcept
|
||||
{
|
||||
if (!fitsCreateCodeSizeLimit(code))
|
||||
return ripple::Unexpected(Error::tooLarge);
|
||||
if (code.size() < 4)
|
||||
return ripple::Unexpected(Error::tooShort);
|
||||
|
||||
if (startsWith(code, wasmMagic))
|
||||
{
|
||||
return View{
|
||||
.kind = Kind::legacyWasm,
|
||||
.hookApiVersion = 0,
|
||||
.xflArithmeticProfile = XFLArithmeticProfile::none,
|
||||
.bytecodeABI = {},
|
||||
.runtimeProfile = {},
|
||||
.payload = code};
|
||||
}
|
||||
|
||||
if (!startsWith(code, quickJSMagic))
|
||||
return ripple::Unexpected(Error::unknownMagic);
|
||||
if (code.size() < quickJSHeaderSize)
|
||||
return ripple::Unexpected(Error::truncatedQuickJSHeader);
|
||||
|
||||
auto const* bytes = code.data();
|
||||
if (bytes[4] != quickJSEnvelopeVersion)
|
||||
return ripple::Unexpected(Error::unsupportedEnvelopeVersion);
|
||||
if (bytes[5] != quickJSBytecodeKind)
|
||||
return ripple::Unexpected(Error::unsupportedArtifactKind);
|
||||
if (readU16BE(bytes + 6) != quickJSHeaderSize)
|
||||
return ripple::Unexpected(Error::nonCanonicalHeaderSize);
|
||||
auto const profileCode = readU16BE(bytes + 10);
|
||||
XFLArithmeticProfile xflArithmeticProfile;
|
||||
if (profileCode == generated::xflArithmeticProfileNone)
|
||||
{
|
||||
xflArithmeticProfile = XFLArithmeticProfile::none;
|
||||
}
|
||||
else if (profileCode == generated::xflArithmeticProfileXahauFloatV1)
|
||||
{
|
||||
xflArithmeticProfile = XFLArithmeticProfile::xahauFloatV1;
|
||||
}
|
||||
else if (profileCode == generated::xflArithmeticProfileNearestEvenV1)
|
||||
{
|
||||
xflArithmeticProfile = XFLArithmeticProfile::nearestEvenV1;
|
||||
}
|
||||
else
|
||||
return ripple::Unexpected(Error::unsupportedXFLArithmeticProfile);
|
||||
|
||||
auto const payloadSize = readU32BE(bytes + 12);
|
||||
if (payloadSize == 0)
|
||||
return ripple::Unexpected(Error::emptyPayload);
|
||||
if (payloadSize != code.size() - quickJSHeaderSize)
|
||||
return ripple::Unexpected(Error::lengthMismatch);
|
||||
|
||||
Identity bytecodeABI;
|
||||
std::copy_n(bytes + 16, identitySize, bytecodeABI.begin());
|
||||
if (isZero(bytecodeABI))
|
||||
return ripple::Unexpected(Error::zeroBytecodeABI);
|
||||
|
||||
Identity runtimeProfile;
|
||||
std::copy_n(bytes + 48, identitySize, runtimeProfile.begin());
|
||||
if (isZero(runtimeProfile))
|
||||
return ripple::Unexpected(Error::zeroRuntimeProfile);
|
||||
|
||||
return View{
|
||||
.kind = Kind::quickJSBytecode,
|
||||
.hookApiVersion = readU16BE(bytes + 8),
|
||||
.xflArithmeticProfile = xflArithmeticProfile,
|
||||
.bytecodeABI = bytecodeABI,
|
||||
.runtimeProfile = runtimeProfile,
|
||||
.payload = ripple::Slice{bytes + quickJSHeaderSize, payloadSize}};
|
||||
}
|
||||
|
||||
std::string_view
|
||||
toString(Error error) noexcept
|
||||
{
|
||||
switch (error)
|
||||
{
|
||||
case Error::tooLarge:
|
||||
return "artifact exceeds the sfCreateCode limit";
|
||||
case Error::tooShort:
|
||||
return "artifact is too short to identify";
|
||||
case Error::unknownMagic:
|
||||
return "artifact has unknown magic";
|
||||
case Error::truncatedQuickJSHeader:
|
||||
return "QuickJS artifact header is truncated";
|
||||
case Error::unsupportedEnvelopeVersion:
|
||||
return "QuickJS envelope version is unsupported";
|
||||
case Error::unsupportedArtifactKind:
|
||||
return "QuickJS artifact kind is unsupported";
|
||||
case Error::nonCanonicalHeaderSize:
|
||||
return "QuickJS artifact header size is non-canonical";
|
||||
case Error::unsupportedXFLArithmeticProfile:
|
||||
return "QuickJS XFL arithmetic profile is unsupported";
|
||||
case Error::emptyPayload:
|
||||
return "QuickJS artifact payload is empty";
|
||||
case Error::lengthMismatch:
|
||||
return "QuickJS payload length does not match sfCreateCode";
|
||||
case Error::zeroBytecodeABI:
|
||||
return "QuickJS bytecode ABI identity is zero";
|
||||
case Error::zeroRuntimeProfile:
|
||||
return "QuickJS runtime profile identity is zero";
|
||||
}
|
||||
return "unknown Hook artifact error";
|
||||
}
|
||||
|
||||
} // namespace hook::artifact
|
||||
86
src/xrpld/app/hook/detail/HookHostFunctions.cpp
Normal file
86
src/xrpld/app/hook/detail/HookHostFunctions.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#include <xrpld/app/hook/HookHostFunction.h>
|
||||
#include <xrpld/app/hook/applyHook.h>
|
||||
#include <type_traits>
|
||||
|
||||
namespace hook {
|
||||
namespace {
|
||||
|
||||
template <class T>
|
||||
constexpr HookHostValueKind
|
||||
kindOf() noexcept
|
||||
{
|
||||
if constexpr (std::is_same_v<T, std::int32_t>)
|
||||
return HookHostValueKind::i32;
|
||||
else if constexpr (std::is_same_v<T, std::uint32_t>)
|
||||
return HookHostValueKind::u32;
|
||||
else if constexpr (std::is_same_v<T, std::int64_t>)
|
||||
return HookHostValueKind::i64;
|
||||
else
|
||||
{
|
||||
static_assert(std::is_same_v<T, std::uint64_t>);
|
||||
return HookHostValueKind::u64;
|
||||
}
|
||||
}
|
||||
|
||||
template <class... Ts>
|
||||
std::vector<HookHostValueKind>
|
||||
kindsOf()
|
||||
{
|
||||
return {kindOf<Ts>()...};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::span<HookHostFunctionDescriptor const>
|
||||
hookHostFunctionCatalogue() noexcept
|
||||
{
|
||||
#pragma push_macro("HOOK_API_DEFINITION")
|
||||
#pragma push_macro("HOOK_WRAP_PARAMS")
|
||||
#undef HOOK_API_DEFINITION
|
||||
#undef HOOK_WRAP_PARAMS
|
||||
|
||||
#define HOOK_WRAP_PARAMS(...) __VA_ARGS__
|
||||
#define HOOK_API_DEFINITION(RETURN_TYPE, FUNCTION_NAME, PARAMS_TUPLE, GATE) \
|
||||
{ \
|
||||
#FUNCTION_NAME, \
|
||||
&hook_api::HostFunction##FUNCTION_NAME, \
|
||||
kindsOf<HOOK_WRAP_PARAMS PARAMS_TUPLE>(), \
|
||||
kindOf<RETURN_TYPE>(), \
|
||||
ripple::GATE, \
|
||||
1, \
|
||||
},
|
||||
|
||||
static std::vector<HookHostFunctionDescriptor> const catalogue = {
|
||||
#include <xrpl/hook/hook_api.macro>
|
||||
};
|
||||
|
||||
#undef HOOK_API_DEFINITION
|
||||
#undef HOOK_WRAP_PARAMS
|
||||
#pragma pop_macro("HOOK_WRAP_PARAMS")
|
||||
#pragma pop_macro("HOOK_API_DEFINITION")
|
||||
|
||||
return catalogue;
|
||||
}
|
||||
|
||||
HookHostFunctionDescriptor const*
|
||||
findHookHostFunction(std::string_view name) noexcept
|
||||
{
|
||||
for (auto const& descriptor : hookHostFunctionCatalogue())
|
||||
if (descriptor.name == name)
|
||||
return &descriptor;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
HookHostFunctionDescriptor const*
|
||||
findHookHostFunction(
|
||||
std::string_view name,
|
||||
std::uint16_t implementationVersion) noexcept
|
||||
{
|
||||
for (auto const& descriptor : hookHostFunctionCatalogue())
|
||||
if (descriptor.name == name &&
|
||||
descriptor.implementationVersion == implementationVersion)
|
||||
return &descriptor;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace hook
|
||||
98
src/xrpld/app/hook/detail/QuickJSProviderProfile.h
Normal file
98
src/xrpld/app/hook/detail/QuickJSProviderProfile.h
Normal file
@@ -0,0 +1,98 @@
|
||||
#ifndef XRPLD_APP_HOOK_DETAIL_QUICKJSPROVIDERPROFILE_H_INCLUDED
|
||||
#define XRPLD_APP_HOOK_DETAIL_QUICKJSPROVIDERPROFILE_H_INCLUDED
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
|
||||
namespace hook::artifact::generated {
|
||||
|
||||
struct ProviderImportSignature
|
||||
{
|
||||
std::string_view module;
|
||||
std::string_view name;
|
||||
std::string_view parameters;
|
||||
std::string_view results;
|
||||
};
|
||||
|
||||
struct ProviderExportSignature
|
||||
{
|
||||
std::string_view kind;
|
||||
std::string_view name;
|
||||
std::string_view parameters;
|
||||
std::string_view results;
|
||||
std::uint32_t minimumPages;
|
||||
std::uint32_t maximumPages;
|
||||
bool memory64;
|
||||
bool shared;
|
||||
};
|
||||
|
||||
struct NativeImportSignature
|
||||
{
|
||||
std::string_view name;
|
||||
std::string_view result;
|
||||
std::string_view parameters;
|
||||
std::string_view amendment;
|
||||
};
|
||||
|
||||
extern std::array<std::uint8_t, 32> const providerSHA256;
|
||||
extern std::array<std::uint8_t, 32> const bytecodeABI;
|
||||
extern std::array<std::uint8_t, 32> const runtimeProfile;
|
||||
extern std::string_view const providerProduct;
|
||||
extern std::string_view const providerManifestSHA256;
|
||||
extern std::size_t const providerSize;
|
||||
extern std::uint16_t const hookApiVersion;
|
||||
extern std::uint64_t const initializationFuel;
|
||||
extern std::uint64_t const invocationFuel;
|
||||
extern std::string_view const hostWorkMeter;
|
||||
extern std::uint64_t const hostWorkBudget;
|
||||
extern std::uint64_t const hostWorkBasePerCall;
|
||||
extern std::uint64_t const hostWorkPerAddressedByte;
|
||||
extern std::string_view const hostAdapterPolicy;
|
||||
extern std::uint32_t const heapBytes;
|
||||
extern std::uint32_t const stackBytes;
|
||||
extern std::uint32_t const wasmStackBytes;
|
||||
extern std::uint32_t const serializedObjectMaxBytes;
|
||||
extern std::uint32_t const serializedObjectMaxFields;
|
||||
extern std::uint32_t const serializedObjectMaxScopes;
|
||||
extern std::uint32_t const serializedObjectMaxDepth;
|
||||
extern std::uint32_t const providerMemoryMinimumPages;
|
||||
extern std::uint32_t const providerMemoryMaximumPages;
|
||||
extern bool const providerMemory64;
|
||||
extern bool const providerMemoryShared;
|
||||
extern std::string_view const javascriptBroadDeclarationSHA256;
|
||||
extern std::string_view const javascriptExactV1DeclarationSHA256;
|
||||
extern std::string_view const javascriptSurfaceSHA256;
|
||||
extern std::string_view const javascriptXFLProfileLedgerSHA256;
|
||||
extern std::string_view const javascriptAPIArtifactManifestSHA256;
|
||||
extern std::uint8_t const xqjsEnvelopeVersion;
|
||||
extern std::uint16_t const xflArithmeticProfileNone;
|
||||
extern std::uint16_t const xflArithmeticProfileXahauFloatV1;
|
||||
extern std::uint16_t const xflArithmeticProfileNearestEvenV1;
|
||||
extern std::uint32_t const moduleValidationLayoutVersion;
|
||||
extern std::int32_t const moduleValidationFailureSentinel;
|
||||
extern std::uint32_t const moduleValidationMainBit;
|
||||
extern std::uint32_t const moduleValidationCallbackBit;
|
||||
extern std::uint32_t const moduleValidationEntryMask;
|
||||
extern std::uint32_t const moduleValidationReservedMask;
|
||||
extern std::uint32_t const moduleValidationProfileMask;
|
||||
extern std::uint32_t const moduleValidationProfileShift;
|
||||
extern std::uint32_t const moduleValidationVersionMask;
|
||||
extern std::uint32_t const moduleValidationVersionShift;
|
||||
|
||||
extern std::span<std::string_view const> const providerImports;
|
||||
extern std::span<std::string_view const> const providerExports;
|
||||
extern std::span<ProviderImportSignature const> const providerImportSignatures;
|
||||
extern std::span<ProviderExportSignature const> const providerExportSignatures;
|
||||
extern std::span<NativeImportSignature const> const nativeImportSignatures;
|
||||
extern std::string_view const nativeABISourceRepository;
|
||||
extern std::string_view const nativeABISourceCommit;
|
||||
extern std::string_view const nativeABISourcePath;
|
||||
extern std::string_view const nativeABISHA256;
|
||||
extern std::size_t const nativeABICatalogueCount;
|
||||
|
||||
} // namespace hook::artifact::generated
|
||||
|
||||
#endif
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <xrpld/app/hook/HookAPI.h>
|
||||
#include <xrpld/app/hook/QuickJSHookRuntime.h>
|
||||
#include <xrpld/app/hook/applyHook.h>
|
||||
#include <xrpld/app/ledger/OpenLedger.h>
|
||||
#include <xrpld/app/misc/HashRouter.h>
|
||||
@@ -9,6 +10,7 @@
|
||||
#include <xrpld/app/tx/detail/NFTokenUtils.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/hook/HookArtifact.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/st.h>
|
||||
@@ -19,7 +21,6 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <wasmedge/wasmedge.h>
|
||||
|
||||
using namespace ripple;
|
||||
|
||||
@@ -1021,6 +1022,7 @@ hook::apply(
|
||||
used for caching (one day) */
|
||||
ripple::uint256 const&
|
||||
hookHash, /* hash of the actual hook byte code, used for metadata */
|
||||
uint16_t hookApiVersion,
|
||||
ripple::uint256 const& hookCanEmit,
|
||||
ripple::uint256 const& hookNamespace,
|
||||
ripple::Blob const& wasm,
|
||||
@@ -1082,10 +1084,54 @@ hook::apply(
|
||||
|
||||
auto const& j = applyCtx.app.journal("View");
|
||||
|
||||
HookExecutor executor{hookCtx};
|
||||
auto const artifact = hook::artifact::parse(makeSlice(wasm));
|
||||
if (!artifact || artifact->hookApiVersion != hookApiVersion)
|
||||
{
|
||||
JLOG(j.warn())
|
||||
<< "HookError[" << HC_ACC()
|
||||
<< "]: Stored Hook artifact is invalid or disagrees with "
|
||||
"sfHookApiVersion"
|
||||
<< (artifact ? "" : ": ")
|
||||
<< (artifact ? std::string_view{}
|
||||
: hook::artifact::toString(artifact.error()));
|
||||
hookCtx.result.exitType = hook_api::ExitType::WASM_ERROR;
|
||||
return hookCtx.result;
|
||||
}
|
||||
|
||||
executor.executeWasm(
|
||||
wasm.data(), (size_t)wasm.size(), isCallback, wasmParam, j);
|
||||
std::optional<HookExecutor> executor;
|
||||
switch (artifact->kind)
|
||||
{
|
||||
case hook::artifact::Kind::legacyWasm: {
|
||||
executor.emplace(hookCtx);
|
||||
executor->executeWasm(
|
||||
artifact->payload.data(),
|
||||
artifact->payload.size(),
|
||||
isCallback,
|
||||
wasmParam,
|
||||
j);
|
||||
break;
|
||||
}
|
||||
|
||||
case hook::artifact::Kind::quickJSBytecode: {
|
||||
auto runtime = findQuickJSRuntime(*artifact);
|
||||
if (!runtime)
|
||||
{
|
||||
JLOG(j.warn())
|
||||
<< "HookError[" << HC_ACC()
|
||||
<< "]: QuickJS runtime profile is not registered";
|
||||
hookCtx.result.exitType = hook_api::ExitType::WASM_ERROR;
|
||||
break;
|
||||
}
|
||||
executeQuickJSBytecode(
|
||||
hookCtx,
|
||||
runtime,
|
||||
std::span{artifact->payload.data(), artifact->payload.size()},
|
||||
isCallback,
|
||||
wasmParam,
|
||||
j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
JLOG(j.trace()) << "HookInfo[" << HC_ACC() << "]: "
|
||||
<< (hookCtx.result.exitType == hook_api::ExitType::ROLLBACK
|
||||
@@ -1111,7 +1157,7 @@ DEFINE_HOOK_FUNCTION(
|
||||
if (NOT_IN_BOUNDS(read_ptr, read_len, memory_length))
|
||||
return OUT_OF_BOUNDS;
|
||||
|
||||
if (!j.trace())
|
||||
if (!jh.trace())
|
||||
return 0ULL;
|
||||
|
||||
if (read_len > 128)
|
||||
@@ -1125,16 +1171,16 @@ DEFINE_HOOK_FUNCTION(
|
||||
|
||||
if (read_len > 0)
|
||||
{
|
||||
j.trace() << "HookTrace[" << HC_ACC() << "]: "
|
||||
<< std::string_view(
|
||||
(const char*)memory + read_ptr, read_len)
|
||||
<< ": " << number;
|
||||
JLOG(jh.trace())
|
||||
<< "HookTrace[" << HC_ACC() << "]: "
|
||||
<< std::string_view((const char*)memory + read_ptr, read_len)
|
||||
<< ": " << number;
|
||||
|
||||
return 0ULL;
|
||||
}
|
||||
}
|
||||
|
||||
j.trace() << "HookTrace[" << HC_ACC() << "]: " << number;
|
||||
JLOG(jh.trace()) << "HookTrace[" << HC_ACC() << "]: " << number;
|
||||
return 0ULL;
|
||||
HOOK_TEARDOWN();
|
||||
}
|
||||
@@ -1154,7 +1200,7 @@ DEFINE_HOOK_FUNCTION(
|
||||
NOT_IN_BOUNDS(dread_ptr, dread_len, memory_length))
|
||||
return OUT_OF_BOUNDS;
|
||||
|
||||
if (!j.trace())
|
||||
if (!jh.trace())
|
||||
return 0ULL;
|
||||
|
||||
if (mread_len > 128)
|
||||
@@ -1214,8 +1260,9 @@ DEFINE_HOOK_FUNCTION(
|
||||
|
||||
if (out_len > 0)
|
||||
{
|
||||
j.trace() << "HookTrace[" << HC_ACC() << "]: "
|
||||
<< std::string_view((const char*)output_storage, out_len);
|
||||
JLOG(jh.trace()) << "HookTrace[" << HC_ACC() << "]: "
|
||||
<< std::string_view(
|
||||
(const char*)output_storage, out_len);
|
||||
}
|
||||
|
||||
return 0ULL;
|
||||
@@ -2634,6 +2681,11 @@ DEFINE_HOOK_FUNCTION(
|
||||
|
||||
auto tx_blob = res.value();
|
||||
|
||||
if (frameCtx.writeContract() ==
|
||||
hook::HookGuestMemory::WriteContract::fixedBuffer &&
|
||||
tx_blob.size() > write_len)
|
||||
return TOO_SMALL;
|
||||
|
||||
WRITE_WASM_MEMORY_AND_RETURN(
|
||||
write_ptr,
|
||||
tx_blob.size(),
|
||||
@@ -3403,7 +3455,7 @@ DEFINE_HOOK_FUNCTION(
|
||||
if (NOT_IN_BOUNDS(read_ptr, read_len, memory_length))
|
||||
return OUT_OF_BOUNDS;
|
||||
|
||||
if (!j.trace())
|
||||
if (!jh.trace())
|
||||
return 0ULL;
|
||||
|
||||
if (read_len > 128)
|
||||
@@ -3420,8 +3472,8 @@ DEFINE_HOOK_FUNCTION(
|
||||
|
||||
if (float1 == 0)
|
||||
{
|
||||
j.trace() << "HookTrace[" << HC_ACC() << "]: " << messageKey
|
||||
<< ": Float 0*10^(0) <ZERO>";
|
||||
JLOG(jh.trace()) << "HookTrace[" << HC_ACC() << "]: " << messageKey
|
||||
<< ": Float 0*10^(0) <ZERO>";
|
||||
return 0ULL;
|
||||
}
|
||||
|
||||
@@ -3432,14 +3484,14 @@ DEFINE_HOOK_FUNCTION(
|
||||
man.value() > maxMantissa || exp.value() < minExponent ||
|
||||
exp.value() > maxExponent)
|
||||
{
|
||||
j.trace() << "HookTrace[" << HC_ACC() << "]: " << messageKey
|
||||
<< ": Float <INVALID>";
|
||||
JLOG(jh.trace()) << "HookTrace[" << HC_ACC() << "]: " << messageKey
|
||||
<< ": Float <INVALID>";
|
||||
return 0ULL;
|
||||
}
|
||||
|
||||
j.trace() << "HookTrace[" << HC_ACC() << "]:" << messageKey << ": Float "
|
||||
<< (neg ? "-" : "") << man.value() << "*10^(" << exp.value()
|
||||
<< ")";
|
||||
JLOG(jh.trace()) << "HookTrace[" << HC_ACC() << "]:" << messageKey
|
||||
<< ": Float " << (neg ? "-" : "") << man.value() << "*10^("
|
||||
<< exp.value() << ")";
|
||||
return 0ULL;
|
||||
|
||||
HOOK_TEARDOWN();
|
||||
|
||||
386
src/xrpld/app/hook/detail/quickjs/QuickJSHostCall.cpp
Normal file
386
src/xrpld/app/hook/detail/quickjs/QuickJSHostCall.cpp
Normal file
@@ -0,0 +1,386 @@
|
||||
#include <xrpld/app/hook/applyHook.h>
|
||||
#include <xrpld/app/hook/detail/quickjs/QuickJSHostCall.h>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <limits>
|
||||
#include <string_view>
|
||||
|
||||
namespace hook::quickjs {
|
||||
namespace {
|
||||
|
||||
#ifdef ENABLE_TESTS
|
||||
thread_local std::vector<QuickJSHostCallObservationForTests>*
|
||||
hostCallObservationsForTests = nullptr;
|
||||
thread_local std::optional<std::uint64_t> hostWorkBudgetForTests;
|
||||
thread_local std::uint64_t observationInvocationForTests = 0;
|
||||
|
||||
std::uint64_t
|
||||
initialHostWork(QuickJSRuntimeProfile const& profile) noexcept
|
||||
{
|
||||
if (hostWorkBudgetForTests)
|
||||
return *hostWorkBudgetForTests;
|
||||
return profile.hostWorkBudget;
|
||||
}
|
||||
|
||||
void
|
||||
recordHostCallObservation(
|
||||
QuickJSInvocation const& invocation,
|
||||
WasmtimeHostBinding const& binding,
|
||||
std::uint64_t declaredBytes,
|
||||
std::uint64_t hostWorkBefore,
|
||||
bool dispatched) noexcept
|
||||
{
|
||||
if (!hostCallObservationsForTests)
|
||||
return;
|
||||
try
|
||||
{
|
||||
QuickJSHostCallObservationForTests observation{
|
||||
.invocation = invocation.observationInvocation,
|
||||
.name = std::string{binding.name},
|
||||
.declaredBytes = declaredBytes,
|
||||
.cost = binding.charging == WasmtimeHostBinding::Charging::quickJSV1
|
||||
? quickJSHostWorkCost(invocation.profile, declaredBytes)
|
||||
: 0,
|
||||
.hostWorkBefore = hostWorkBefore,
|
||||
.hostWorkAfter = invocation.hostWorkRemaining,
|
||||
.dispatched = dispatched,
|
||||
.liveSlots = invocation.hookCtx.slot.size()};
|
||||
if (!invocation.hookCtx.slot.empty())
|
||||
{
|
||||
auto const& owner = invocation.hookCtx.slot.begin()->second.storage;
|
||||
if (owner)
|
||||
{
|
||||
observation.liveSlotSerializedBytes =
|
||||
owner->getSerializer().getDataLength();
|
||||
observation.liveSlotOwner = owner;
|
||||
}
|
||||
}
|
||||
hostCallObservationsForTests->push_back(std::move(observation));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
QuickJSInvocation*
|
||||
invocationFrom(wasmtime_caller_t* caller) noexcept
|
||||
{
|
||||
auto* context = wasmtime_caller_context(caller);
|
||||
return static_cast<QuickJSInvocation*>(wasmtime_context_get_data(context));
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
callbackTrap(std::string_view message) noexcept
|
||||
{
|
||||
return wasmtime_trap_new(message.data(), message.size());
|
||||
}
|
||||
|
||||
bool
|
||||
matchesKind(HookHostValueKind expected, wasmtime_valkind_t actual) noexcept
|
||||
{
|
||||
return (isI32(expected) && actual == WASMTIME_I32) ||
|
||||
(!isI32(expected) && actual == WASMTIME_I64);
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
ordinaryUnavailableResult(
|
||||
HookHostFunctionDescriptor const& operation,
|
||||
wasmtime_val_t* results,
|
||||
std::size_t resultCount) noexcept
|
||||
{
|
||||
if (resultCount != 1 || !results)
|
||||
return callbackTrap("invalid Xahau Hook callback result shape");
|
||||
auto const status =
|
||||
static_cast<std::int64_t>(hook_api::hook_return_code::NOT_IMPLEMENTED);
|
||||
if (isI32(operation.result))
|
||||
{
|
||||
results[0] = wasmtime_val_t{
|
||||
.kind = WASMTIME_I32,
|
||||
.of = {.i32 = static_cast<std::int32_t>(status)}};
|
||||
return nullptr;
|
||||
}
|
||||
if (!isI32(operation.result))
|
||||
{
|
||||
results[0] =
|
||||
wasmtime_val_t{.kind = WASMTIME_I64, .of = {.i64 = status}};
|
||||
return nullptr;
|
||||
}
|
||||
return callbackTrap("unsupported Xahau Hook callback result type");
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
finishCallback(
|
||||
QuickJSInvocation& invocation,
|
||||
WasmtimeHostBinding const& binding,
|
||||
HookHostCallStatus status,
|
||||
HookHostValue const& output,
|
||||
wasmtime_val_t* results,
|
||||
std::size_t resultCount) noexcept
|
||||
{
|
||||
if (resultCount != 1 || !results)
|
||||
return callbackTrap("invalid Xahau Hook callback result shape");
|
||||
|
||||
if (status == HookHostCallStatus::accept ||
|
||||
status == HookHostCallStatus::rollback)
|
||||
{
|
||||
if (binding.terminal != TerminalBehavior::hookTerminal)
|
||||
return callbackTrap(
|
||||
"ordinary Xahau Hook import returned a terminal code");
|
||||
auto const expected = status == HookHostCallStatus::accept
|
||||
? hook_api::ExitType::ACCEPT
|
||||
: hook_api::ExitType::ROLLBACK;
|
||||
if (invocation.hookCtx.result.exitType != expected)
|
||||
return callbackTrap(
|
||||
"Xahau Hook terminal lacks matching HookContext state");
|
||||
invocation.terminal = true;
|
||||
return callbackTrap("Xahau Hook terminal");
|
||||
}
|
||||
if (status == HookHostCallStatus::trap)
|
||||
return callbackTrap("Xahau Hook host operation trapped");
|
||||
|
||||
if (isI32(binding.operation->result))
|
||||
{
|
||||
if (output.kind != binding.operation->result)
|
||||
return callbackTrap("Xahau Hook host result width mismatch");
|
||||
results[0] = wasmtime_val_t{
|
||||
.kind = WASMTIME_I32,
|
||||
.of = {.i32 = std::bit_cast<std::int32_t>(output.asI32())}};
|
||||
return nullptr;
|
||||
}
|
||||
if (!isI32(binding.operation->result))
|
||||
{
|
||||
if (output.kind != binding.operation->result)
|
||||
return callbackTrap("Xahau Hook host result width mismatch");
|
||||
results[0] = wasmtime_val_t{
|
||||
.kind = WASMTIME_I64,
|
||||
.of = {.i64 = std::bit_cast<std::int64_t>(output.asI64())}};
|
||||
return nullptr;
|
||||
}
|
||||
return callbackTrap("unsupported Xahau Hook callback result type");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::uint64_t
|
||||
quickJSHostWorkCost(
|
||||
QuickJSRuntimeProfile const& profile,
|
||||
std::uint64_t declaredBytes) noexcept
|
||||
{
|
||||
return declaredBytes > (std::numeric_limits<std::uint64_t>::max() -
|
||||
profile.hostWorkBasePerCall) /
|
||||
profile.hostWorkPerAddressedByte
|
||||
? std::numeric_limits<std::uint64_t>::max()
|
||||
: profile.hostWorkBasePerCall +
|
||||
declaredBytes * profile.hostWorkPerAddressedByte;
|
||||
}
|
||||
|
||||
QuickJSInvocation::QuickJSInvocation(
|
||||
HookContext& hookCtx_,
|
||||
QuickJSRuntimeProfile const& profile_) noexcept
|
||||
: hookCtx(hookCtx_)
|
||||
, profile(profile_)
|
||||
#ifdef ENABLE_TESTS
|
||||
, hostWorkRemaining(initialHostWork(profile_))
|
||||
, observationInvocation(++observationInvocationForTests)
|
||||
#else
|
||||
, hostWorkRemaining(profile_.hostWorkBudget)
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
#ifdef ENABLE_TESTS
|
||||
void
|
||||
setQuickJSHostCallObservationsForTests(
|
||||
std::vector<QuickJSHostCallObservationForTests>* observations) noexcept
|
||||
{
|
||||
hostCallObservationsForTests = observations;
|
||||
observationInvocationForTests = 0;
|
||||
}
|
||||
|
||||
void
|
||||
setQuickJSHostWorkBudgetForTests(
|
||||
std::optional<std::uint64_t> hostWorkBudget) noexcept
|
||||
{
|
||||
hostWorkBudgetForTests = hostWorkBudget;
|
||||
}
|
||||
#endif
|
||||
|
||||
QuickJSHostCall::QuickJSHostCall(
|
||||
QuickJSInvocation& invocation,
|
||||
wasmtime_caller_t* caller) noexcept
|
||||
: invocation_(invocation), caller_(caller)
|
||||
{
|
||||
}
|
||||
|
||||
std::optional<HookGuestMemory>
|
||||
QuickJSHostCall::memory() const noexcept
|
||||
{
|
||||
wasmtime_extern_t memoryExport;
|
||||
if (!wasmtime_caller_export_get(caller_, "memory", 6, &memoryExport) ||
|
||||
memoryExport.kind != WASMTIME_EXTERN_MEMORY)
|
||||
return std::nullopt;
|
||||
auto* context = wasmtime_caller_context(caller_);
|
||||
return HookGuestMemory{
|
||||
wasmtime_memory_data(context, &memoryExport.of.memory),
|
||||
wasmtime_memory_data_size(context, &memoryExport.of.memory)};
|
||||
}
|
||||
|
||||
bool
|
||||
QuickJSHostCall::charge(std::uint64_t declaredBytes) noexcept
|
||||
{
|
||||
auto const& profile = invocation_.profile;
|
||||
auto const cost = quickJSHostWorkCost(profile, declaredBytes);
|
||||
if (cost > invocation_.hostWorkRemaining)
|
||||
return false;
|
||||
invocation_.hostWorkRemaining -= cost;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::uint64_t
|
||||
declaredHostWork(
|
||||
HostWorkMeasureKind measure,
|
||||
std::span<wasmtime_val_t const> values) noexcept
|
||||
{
|
||||
switch (measure)
|
||||
{
|
||||
case HostWorkMeasureKind::zeroV1:
|
||||
return 0;
|
||||
case HostWorkMeasureKind::argument1V1:
|
||||
return values.size() > 1
|
||||
? static_cast<std::uint32_t>(values[1].of.i32)
|
||||
: std::numeric_limits<std::uint64_t>::max();
|
||||
case HostWorkMeasureKind::arguments1And3SaturatedV1:
|
||||
if (values.size() <= 3)
|
||||
return std::numeric_limits<std::uint64_t>::max();
|
||||
return static_cast<std::uint64_t>(
|
||||
static_cast<std::uint32_t>(values[1].of.i32)) +
|
||||
static_cast<std::uint32_t>(values[3].of.i32);
|
||||
case HostWorkMeasureKind::arguments1And3And5And7SaturatedV1:
|
||||
if (values.size() <= 7)
|
||||
return std::numeric_limits<std::uint64_t>::max();
|
||||
return static_cast<std::uint64_t>(
|
||||
static_cast<std::uint32_t>(values[1].of.i32)) +
|
||||
static_cast<std::uint32_t>(values[3].of.i32) +
|
||||
static_cast<std::uint32_t>(values[5].of.i32) +
|
||||
static_cast<std::uint32_t>(values[7].of.i32);
|
||||
}
|
||||
return std::numeric_limits<std::uint64_t>::max();
|
||||
}
|
||||
|
||||
bool
|
||||
validWasmtimeHostBinding(WasmtimeHostBinding const& binding) noexcept
|
||||
{
|
||||
if (binding.module.empty() || binding.name.empty() ||
|
||||
binding.parameterCount > maxImportParameters || !binding.operation ||
|
||||
!binding.operation->function ||
|
||||
binding.operation->name != binding.name ||
|
||||
binding.operation->implementationVersion !=
|
||||
binding.implementationVersion ||
|
||||
binding.operation->parameters.size() != binding.parameterCount ||
|
||||
binding.operation->result != binding.result)
|
||||
return false;
|
||||
for (std::size_t parameter = 0;
|
||||
parameter < binding.operation->parameters.size();
|
||||
++parameter)
|
||||
if (binding.operation->parameters[parameter] !=
|
||||
binding.parameters[parameter])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
rawHookCallback(
|
||||
void* environment,
|
||||
wasmtime_caller_t* caller,
|
||||
wasmtime_val_t const* args,
|
||||
std::size_t argumentCount,
|
||||
wasmtime_val_t* results,
|
||||
std::size_t resultCount) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
auto const* binding =
|
||||
static_cast<WasmtimeHostBinding const*>(environment);
|
||||
if (!binding || !validWasmtimeHostBinding(*binding))
|
||||
return callbackTrap("missing Xahau Hook import descriptor");
|
||||
auto const& operation = *binding->operation;
|
||||
if (argumentCount != operation.parameters.size() ||
|
||||
(argumentCount != 0 && !args))
|
||||
return callbackTrap("invalid Xahau Hook callback arguments");
|
||||
for (std::size_t index = 0; index < argumentCount; ++index)
|
||||
if (!matchesKind(operation.parameters[index], args[index].kind))
|
||||
return callbackTrap(
|
||||
"invalid Xahau Hook callback argument type");
|
||||
|
||||
auto* invocation = invocationFrom(caller);
|
||||
if (!invocation)
|
||||
return callbackTrap("missing Xahau Hook invocation context");
|
||||
if (binding->amendment != ripple::uint256{} &&
|
||||
!invocation->hookCtx.applyCtx.view().rules().enabled(
|
||||
binding->amendment))
|
||||
return ordinaryUnavailableResult(operation, results, resultCount);
|
||||
QuickJSHostCall call{*invocation, caller};
|
||||
#ifdef ENABLE_TESTS
|
||||
auto const declaredBytes =
|
||||
declaredHostWork(binding->measure, std::span{args, argumentCount});
|
||||
auto const hostWorkBefore = invocation->hostWorkRemaining;
|
||||
if (binding->charging == WasmtimeHostBinding::Charging::quickJSV1 &&
|
||||
!call.charge(declaredBytes))
|
||||
{
|
||||
recordHostCallObservation(
|
||||
*invocation, *binding, declaredBytes, hostWorkBefore, false);
|
||||
return callbackTrap("Xahau Hook host-work budget exhausted");
|
||||
}
|
||||
#else
|
||||
if (binding->charging == WasmtimeHostBinding::Charging::quickJSV1 &&
|
||||
!call.charge(declaredHostWork(
|
||||
binding->measure, std::span{args, argumentCount})))
|
||||
return callbackTrap("Xahau Hook host-work budget exhausted");
|
||||
#endif
|
||||
|
||||
if (argumentCount > maxImportParameters)
|
||||
return callbackTrap("too many Xahau Hook host arguments");
|
||||
std::array<HookHostValue, maxImportParameters> neutralInputs{};
|
||||
for (std::size_t index = 0; index < argumentCount; ++index)
|
||||
{
|
||||
auto const kind = operation.parameters[index];
|
||||
if (kind == HookHostValueKind::i32)
|
||||
neutralInputs[index] = HookHostValue::i32(
|
||||
static_cast<std::uint32_t>(args[index].of.i32));
|
||||
else if (kind == HookHostValueKind::u32)
|
||||
neutralInputs[index] = HookHostValue::u32(
|
||||
static_cast<std::uint32_t>(args[index].of.i32));
|
||||
else if (kind == HookHostValueKind::i64)
|
||||
neutralInputs[index] = HookHostValue::i64(
|
||||
static_cast<std::uint64_t>(args[index].of.i64));
|
||||
else
|
||||
neutralInputs[index] = HookHostValue::u64(
|
||||
static_cast<std::uint64_t>(args[index].of.i64));
|
||||
}
|
||||
|
||||
auto memory = call.memory();
|
||||
HookGuestMemory emptyMemory{nullptr, 0};
|
||||
auto& guestMemory = memory ? *memory : emptyMemory;
|
||||
HookHostValue output{};
|
||||
auto const status = operation.function(
|
||||
&invocation->hookCtx,
|
||||
guestMemory,
|
||||
neutralInputs.data(),
|
||||
argumentCount,
|
||||
&output,
|
||||
1);
|
||||
#ifdef ENABLE_TESTS
|
||||
recordHostCallObservation(
|
||||
*invocation, *binding, declaredBytes, hostWorkBefore, true);
|
||||
#endif
|
||||
return finishCallback(
|
||||
*invocation, *binding, status, output, results, resultCount);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return callbackTrap("uncaught Xahau Hook host exception");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace hook::quickjs
|
||||
93
src/xrpld/app/hook/detail/quickjs/QuickJSHostCall.h
Normal file
93
src/xrpld/app/hook/detail/quickjs/QuickJSHostCall.h
Normal file
@@ -0,0 +1,93 @@
|
||||
#ifndef XRPLD_APP_HOOK_DETAIL_QUICKJS_QUICKJSHOSTCALL_H_INCLUDED
|
||||
#define XRPLD_APP_HOOK_DETAIL_QUICKJS_QUICKJSHOSTCALL_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/hook/HookGuestMemory.h>
|
||||
#include <xrpld/app/hook/HookHostFunction.h>
|
||||
#include <xrpld/app/hook/QuickJSHookRuntime.h>
|
||||
#include <xrpld/app/hook/detail/quickjs/QuickJSHostPolicy.h>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <wasmtime.h>
|
||||
|
||||
namespace hook {
|
||||
struct HookContext;
|
||||
|
||||
namespace quickjs {
|
||||
|
||||
std::uint64_t
|
||||
quickJSHostWorkCost(
|
||||
QuickJSRuntimeProfile const& profile,
|
||||
std::uint64_t declaredBytes) noexcept;
|
||||
|
||||
struct QuickJSInvocation
|
||||
{
|
||||
HookContext& hookCtx;
|
||||
QuickJSRuntimeProfile const& profile;
|
||||
std::uint64_t hostWorkRemaining;
|
||||
#ifdef ENABLE_TESTS
|
||||
std::uint64_t observationInvocation;
|
||||
#endif
|
||||
bool terminal = false;
|
||||
|
||||
QuickJSInvocation(
|
||||
HookContext& hookCtx,
|
||||
QuickJSRuntimeProfile const& profile) noexcept;
|
||||
};
|
||||
|
||||
class QuickJSHostCall
|
||||
{
|
||||
public:
|
||||
QuickJSHostCall(
|
||||
QuickJSInvocation& invocation,
|
||||
wasmtime_caller_t* caller) noexcept;
|
||||
|
||||
std::optional<HookGuestMemory>
|
||||
memory() const noexcept;
|
||||
|
||||
bool
|
||||
charge(std::uint64_t declaredBytes) noexcept;
|
||||
|
||||
private:
|
||||
QuickJSInvocation& invocation_;
|
||||
wasmtime_caller_t* caller_;
|
||||
};
|
||||
|
||||
struct WasmtimeHostBinding
|
||||
{
|
||||
std::string_view module;
|
||||
std::string_view name;
|
||||
std::array<HookHostValueKind, maxImportParameters> parameters;
|
||||
std::uint8_t parameterCount;
|
||||
HookHostValueKind result;
|
||||
std::uint16_t implementationVersion;
|
||||
HookHostFunctionDescriptor const* operation;
|
||||
ripple::uint256 amendment;
|
||||
HostWorkMeasureKind measure;
|
||||
TerminalBehavior terminal;
|
||||
enum class Charging : std::uint8_t { none, quickJSV1 } charging;
|
||||
};
|
||||
|
||||
bool
|
||||
validWasmtimeHostBinding(WasmtimeHostBinding const& binding) noexcept;
|
||||
|
||||
std::uint64_t
|
||||
declaredHostWork(
|
||||
HostWorkMeasureKind measure,
|
||||
std::span<wasmtime_val_t const> values) noexcept;
|
||||
|
||||
wasm_trap_t*
|
||||
rawHookCallback(
|
||||
void* environment,
|
||||
wasmtime_caller_t* caller,
|
||||
wasmtime_val_t const* args,
|
||||
std::size_t argumentCount,
|
||||
wasmtime_val_t* results,
|
||||
std::size_t resultCount) noexcept;
|
||||
|
||||
} // namespace quickjs
|
||||
} // namespace hook
|
||||
|
||||
#endif // XRPLD_APP_HOOK_DETAIL_QUICKJS_QUICKJSHOSTCALL_H_INCLUDED
|
||||
83
src/xrpld/app/hook/detail/quickjs/QuickJSHostPolicy.cpp
Normal file
83
src/xrpld/app/hook/detail/quickjs/QuickJSHostPolicy.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#include <xrpld/app/hook/detail/quickjs/QuickJSHostPolicy.h>
|
||||
#include <utility>
|
||||
|
||||
namespace hook::quickjs {
|
||||
namespace {
|
||||
|
||||
template <class Tuple, std::size_t... Indices>
|
||||
std::array<NativeScalarKind, maxImportParameters>
|
||||
nativeParameterKinds(std::index_sequence<Indices...>) noexcept
|
||||
{
|
||||
std::array<NativeScalarKind, maxImportParameters> result{};
|
||||
((result[Indices] = nativeScalarKind<std::tuple_element_t<Indices, Tuple>>),
|
||||
...);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <QuickJSV1ImportId Id>
|
||||
QuickJSV1ImportDescriptor
|
||||
makeV1Descriptor()
|
||||
{
|
||||
using Traits = V1ImportTraits<Id>;
|
||||
constexpr auto parameterCount =
|
||||
std::tuple_size_v<typename Traits::Parameters>;
|
||||
static_assert(parameterCount <= maxImportParameters);
|
||||
return {
|
||||
.id = Id,
|
||||
.module = Traits::module,
|
||||
.name = Traits::name,
|
||||
.amendment = Traits::amendment(),
|
||||
.nativeResult = nativeScalarKind<typename Traits::Return>,
|
||||
.nativeParameters = nativeParameterKinds<typename Traits::Parameters>(
|
||||
std::make_index_sequence<parameterCount>{}),
|
||||
.parameterCount = static_cast<std::uint8_t>(parameterCount),
|
||||
.measure = Traits::measure,
|
||||
.terminal = Traits::terminal,
|
||||
.rawOperationVersion = Traits::rawOperationVersion};
|
||||
}
|
||||
|
||||
std::array<QuickJSV1ImportDescriptor, quickJSV1ImportCount> const&
|
||||
v1SnapshotStorage()
|
||||
{
|
||||
static std::array<QuickJSV1ImportDescriptor, quickJSV1ImportCount> const
|
||||
value{
|
||||
makeV1Descriptor<QuickJSV1ImportId::accept>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::rollback>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::ledger_seq>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::ledger_last_time>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::ledger_last_hash>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::ledger_nonce>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::fee_base>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::otxn_type>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::otxn_id>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::otxn_param>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::otxn_slot>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::slot_size>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::slot>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::slot_set>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::slot_clear>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::hook_account>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::hook_param>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::hook_again>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::trace>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::state>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::state_foreign>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::state_foreign_set>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::state_set>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::prepare>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::etxn_details>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::etxn_fee_base>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::etxn_reserve>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::emit>()};
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::span<QuickJSV1ImportDescriptor const>
|
||||
quickJSHostPolicyV1Snapshot() noexcept
|
||||
{
|
||||
return v1SnapshotStorage();
|
||||
}
|
||||
|
||||
} // namespace hook::quickjs
|
||||
342
src/xrpld/app/hook/detail/quickjs/QuickJSHostPolicy.h
Normal file
342
src/xrpld/app/hook/detail/quickjs/QuickJSHostPolicy.h
Normal file
@@ -0,0 +1,342 @@
|
||||
#ifndef XRPLD_APP_HOOK_DETAIL_QUICKJS_QUICKJSHOSTPOLICY_H_INCLUDED
|
||||
#define XRPLD_APP_HOOK_DETAIL_QUICKJS_QUICKJSHOSTPOLICY_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/hook/HookHostFunction.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
|
||||
namespace hook::quickjs {
|
||||
|
||||
using NativeScalarKind = HookHostValueKind;
|
||||
|
||||
template <class T>
|
||||
inline constexpr NativeScalarKind nativeScalarKind = [] {
|
||||
if constexpr (std::is_same_v<T, std::int32_t>)
|
||||
return NativeScalarKind::i32;
|
||||
else if constexpr (std::is_same_v<T, std::uint32_t>)
|
||||
return NativeScalarKind::u32;
|
||||
else if constexpr (std::is_same_v<T, std::int64_t>)
|
||||
return NativeScalarKind::i64;
|
||||
else
|
||||
{
|
||||
static_assert(std::is_same_v<T, std::uint64_t>);
|
||||
return NativeScalarKind::u64;
|
||||
}
|
||||
}();
|
||||
|
||||
inline constexpr std::size_t maxImportParameters = 9;
|
||||
|
||||
/** Import identity for the retained xahau-raw-hook-host-v1 policy.
|
||||
|
||||
This list is deliberately separate from the live 75-entry Hook catalogue
|
||||
and contains the exact imports selected by the sealed provider profile.
|
||||
*/
|
||||
enum class QuickJSV1ImportId : std::uint8_t {
|
||||
accept,
|
||||
rollback,
|
||||
ledger_seq,
|
||||
ledger_last_time,
|
||||
ledger_last_hash,
|
||||
ledger_nonce,
|
||||
fee_base,
|
||||
otxn_type,
|
||||
otxn_id,
|
||||
otxn_param,
|
||||
otxn_slot,
|
||||
slot_size,
|
||||
slot,
|
||||
slot_set,
|
||||
slot_clear,
|
||||
hook_account,
|
||||
hook_param,
|
||||
hook_again,
|
||||
trace,
|
||||
state,
|
||||
state_foreign,
|
||||
state_foreign_set,
|
||||
state_set,
|
||||
prepare,
|
||||
etxn_details,
|
||||
etxn_fee_base,
|
||||
etxn_reserve,
|
||||
emit,
|
||||
count
|
||||
};
|
||||
|
||||
inline constexpr std::size_t quickJSV1ImportCount =
|
||||
static_cast<std::size_t>(QuickJSV1ImportId::count);
|
||||
|
||||
enum class TerminalBehavior : std::uint8_t { ordinaryStatus, hookTerminal };
|
||||
|
||||
enum class HostWorkMeasureKind : std::uint8_t {
|
||||
zeroV1,
|
||||
argument1V1,
|
||||
arguments1And3SaturatedV1,
|
||||
arguments1And3And5And7SaturatedV1
|
||||
};
|
||||
|
||||
template <QuickJSV1ImportId>
|
||||
struct V1ImportTraits;
|
||||
|
||||
#define QUICKJS_V1_EXPAND_PARAMS(...) __VA_ARGS__
|
||||
#define QUICKJS_V1_IMPORT_TRAITS( \
|
||||
ID, RETURN_TYPE, PARAMS_TUPLE, AMENDMENT, MEASURE, TERMINAL) \
|
||||
template <> \
|
||||
struct V1ImportTraits<QuickJSV1ImportId::ID> \
|
||||
{ \
|
||||
using Return = RETURN_TYPE; \
|
||||
using Parameters = std::tuple<QUICKJS_V1_EXPAND_PARAMS PARAMS_TUPLE>; \
|
||||
static constexpr std::string_view module = "env"; \
|
||||
static constexpr std::string_view name = #ID; \
|
||||
static ripple::uint256 \
|
||||
amendment() \
|
||||
{ \
|
||||
return ripple::AMENDMENT; \
|
||||
} \
|
||||
static constexpr TerminalBehavior terminal = \
|
||||
TerminalBehavior::TERMINAL; \
|
||||
static constexpr HostWorkMeasureKind measure = \
|
||||
HostWorkMeasureKind::MEASURE; \
|
||||
static constexpr std::uint16_t rawOperationVersion = 1; \
|
||||
}
|
||||
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
accept,
|
||||
std::int64_t,
|
||||
(std::uint32_t, std::uint32_t, std::int64_t),
|
||||
uint256{},
|
||||
argument1V1,
|
||||
hookTerminal);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
rollback,
|
||||
std::int64_t,
|
||||
(std::uint32_t, std::uint32_t, std::int64_t),
|
||||
uint256{},
|
||||
argument1V1,
|
||||
hookTerminal);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
ledger_seq,
|
||||
std::int64_t,
|
||||
(),
|
||||
uint256{},
|
||||
zeroV1,
|
||||
ordinaryStatus);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
ledger_last_time,
|
||||
std::int64_t,
|
||||
(),
|
||||
uint256{},
|
||||
zeroV1,
|
||||
ordinaryStatus);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
ledger_last_hash,
|
||||
std::int64_t,
|
||||
(std::uint32_t, std::uint32_t),
|
||||
uint256{},
|
||||
argument1V1,
|
||||
ordinaryStatus);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
ledger_nonce,
|
||||
std::int64_t,
|
||||
(std::uint32_t, std::uint32_t),
|
||||
uint256{},
|
||||
argument1V1,
|
||||
ordinaryStatus);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
fee_base,
|
||||
std::int64_t,
|
||||
(),
|
||||
uint256{},
|
||||
zeroV1,
|
||||
ordinaryStatus);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
otxn_type,
|
||||
std::int64_t,
|
||||
(),
|
||||
uint256{},
|
||||
zeroV1,
|
||||
ordinaryStatus);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
otxn_id,
|
||||
std::int64_t,
|
||||
(std::uint32_t, std::uint32_t, std::uint32_t),
|
||||
uint256{},
|
||||
argument1V1,
|
||||
ordinaryStatus);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
otxn_param,
|
||||
std::int64_t,
|
||||
(std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t),
|
||||
uint256{},
|
||||
arguments1And3SaturatedV1,
|
||||
ordinaryStatus);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
otxn_slot,
|
||||
std::int64_t,
|
||||
(std::uint32_t),
|
||||
uint256{},
|
||||
zeroV1,
|
||||
ordinaryStatus);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
slot_size,
|
||||
std::int64_t,
|
||||
(std::uint32_t),
|
||||
uint256{},
|
||||
zeroV1,
|
||||
ordinaryStatus);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
slot,
|
||||
std::int64_t,
|
||||
(std::uint32_t, std::uint32_t, std::uint32_t),
|
||||
uint256{},
|
||||
argument1V1,
|
||||
ordinaryStatus);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
slot_set,
|
||||
std::int64_t,
|
||||
(std::uint32_t, std::uint32_t, std::uint32_t),
|
||||
uint256{},
|
||||
argument1V1,
|
||||
ordinaryStatus);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
slot_clear,
|
||||
std::int64_t,
|
||||
(std::uint32_t),
|
||||
uint256{},
|
||||
zeroV1,
|
||||
ordinaryStatus);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
hook_account,
|
||||
std::int64_t,
|
||||
(std::uint32_t, std::uint32_t),
|
||||
uint256{},
|
||||
argument1V1,
|
||||
ordinaryStatus);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
hook_param,
|
||||
std::int64_t,
|
||||
(std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t),
|
||||
uint256{},
|
||||
arguments1And3SaturatedV1,
|
||||
ordinaryStatus);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
hook_again,
|
||||
std::int64_t,
|
||||
(),
|
||||
uint256{},
|
||||
zeroV1,
|
||||
ordinaryStatus);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
trace,
|
||||
std::int64_t,
|
||||
(std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t),
|
||||
uint256{},
|
||||
arguments1And3SaturatedV1,
|
||||
ordinaryStatus);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
state,
|
||||
std::int64_t,
|
||||
(std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t),
|
||||
uint256{},
|
||||
arguments1And3SaturatedV1,
|
||||
ordinaryStatus);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
state_foreign,
|
||||
std::int64_t,
|
||||
(std::uint32_t,
|
||||
std::uint32_t,
|
||||
std::uint32_t,
|
||||
std::uint32_t,
|
||||
std::uint32_t,
|
||||
std::uint32_t,
|
||||
std::uint32_t,
|
||||
std::uint32_t),
|
||||
uint256{},
|
||||
arguments1And3And5And7SaturatedV1,
|
||||
ordinaryStatus);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
state_foreign_set,
|
||||
std::int64_t,
|
||||
(std::uint32_t,
|
||||
std::uint32_t,
|
||||
std::uint32_t,
|
||||
std::uint32_t,
|
||||
std::uint32_t,
|
||||
std::uint32_t,
|
||||
std::uint32_t,
|
||||
std::uint32_t),
|
||||
uint256{},
|
||||
arguments1And3And5And7SaturatedV1,
|
||||
ordinaryStatus);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
state_set,
|
||||
std::int64_t,
|
||||
(std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t),
|
||||
uint256{},
|
||||
arguments1And3SaturatedV1,
|
||||
ordinaryStatus);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
prepare,
|
||||
std::int64_t,
|
||||
(std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t),
|
||||
featureHooksUpdate2,
|
||||
arguments1And3SaturatedV1,
|
||||
ordinaryStatus);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
etxn_details,
|
||||
std::int64_t,
|
||||
(std::uint32_t, std::uint32_t),
|
||||
uint256{},
|
||||
argument1V1,
|
||||
ordinaryStatus);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
etxn_fee_base,
|
||||
std::int64_t,
|
||||
(std::uint32_t, std::uint32_t),
|
||||
uint256{},
|
||||
argument1V1,
|
||||
ordinaryStatus);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
etxn_reserve,
|
||||
std::int64_t,
|
||||
(std::uint32_t),
|
||||
uint256{},
|
||||
zeroV1,
|
||||
ordinaryStatus);
|
||||
QUICKJS_V1_IMPORT_TRAITS(
|
||||
emit,
|
||||
std::int64_t,
|
||||
(std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t),
|
||||
uint256{},
|
||||
arguments1And3SaturatedV1,
|
||||
ordinaryStatus);
|
||||
|
||||
#undef QUICKJS_V1_IMPORT_TRAITS
|
||||
#undef QUICKJS_V1_EXPAND_PARAMS
|
||||
|
||||
struct QuickJSV1ImportDescriptor
|
||||
{
|
||||
QuickJSV1ImportId id;
|
||||
std::string_view module;
|
||||
std::string_view name;
|
||||
ripple::uint256 amendment;
|
||||
NativeScalarKind nativeResult;
|
||||
std::array<NativeScalarKind, maxImportParameters> nativeParameters;
|
||||
std::uint8_t parameterCount;
|
||||
HostWorkMeasureKind measure;
|
||||
TerminalBehavior terminal;
|
||||
std::uint16_t rawOperationVersion;
|
||||
};
|
||||
|
||||
std::span<QuickJSV1ImportDescriptor const>
|
||||
quickJSHostPolicyV1Snapshot() noexcept;
|
||||
|
||||
} // namespace hook::quickjs
|
||||
|
||||
#endif // XRPLD_APP_HOOK_DETAIL_QUICKJS_QUICKJSHOSTPOLICY_H_INCLUDED
|
||||
153
src/xrpld/app/hook/detail/quickjs/QuickJSLinker.cpp
Normal file
153
src/xrpld/app/hook/detail/quickjs/QuickJSLinker.cpp
Normal file
@@ -0,0 +1,153 @@
|
||||
#include <xrpld/app/hook/detail/quickjs/QuickJSLinker.h>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
|
||||
namespace hook::quickjs {
|
||||
namespace {
|
||||
|
||||
std::string
|
||||
takeError(wasmtime_error_t* error)
|
||||
{
|
||||
wasm_name_t message;
|
||||
wasmtime_error_message(error, &message);
|
||||
std::string result{message.data, message.size};
|
||||
wasm_byte_vec_delete(&message);
|
||||
wasmtime_error_delete(error);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::array<WasmtimeHostBinding, quickJSV1ImportCount> const&
|
||||
v1Bindings()
|
||||
{
|
||||
static std::array<WasmtimeHostBinding, quickJSV1ImportCount> const
|
||||
bindings = [] {
|
||||
std::array<WasmtimeHostBinding, quickJSV1ImportCount> result{};
|
||||
auto const snapshot = quickJSHostPolicyV1Snapshot();
|
||||
for (std::size_t index = 0; index < snapshot.size(); ++index)
|
||||
{
|
||||
auto const& descriptor = snapshot[index];
|
||||
result[index] = {
|
||||
.module = descriptor.module,
|
||||
.name = descriptor.name,
|
||||
.parameters = descriptor.nativeParameters,
|
||||
.parameterCount = descriptor.parameterCount,
|
||||
.result = descriptor.nativeResult,
|
||||
.implementationVersion = descriptor.rawOperationVersion,
|
||||
.operation = findHookHostFunction(
|
||||
descriptor.name, descriptor.rawOperationVersion),
|
||||
.amendment = descriptor.amendment,
|
||||
.measure = descriptor.measure,
|
||||
.terminal = descriptor.terminal,
|
||||
.charging = WasmtimeHostBinding::Charging::quickJSV1};
|
||||
}
|
||||
return result;
|
||||
}();
|
||||
return bindings;
|
||||
}
|
||||
|
||||
QuickJSHostAdapterPolicy const&
|
||||
v1Policy()
|
||||
{
|
||||
static QuickJSHostAdapterPolicy const policy{
|
||||
.id = "xahau-raw-hook-host-v1",
|
||||
.hostWorkMeter = "base-plus-addressed-byte-v1",
|
||||
.chargeOrder = HostChargeOrder::amendmentBeforeCharge,
|
||||
.debitBehavior = HostDebitBehavior::saturatedBasePlusAddressedBytes,
|
||||
.imports = v1Bindings()};
|
||||
return policy;
|
||||
}
|
||||
|
||||
bool
|
||||
defineImport(
|
||||
wasmtime_linker_t* linker,
|
||||
WasmtimeHostBinding const& binding,
|
||||
std::string& error)
|
||||
{
|
||||
if (!binding.operation)
|
||||
{
|
||||
error = std::string{"missing required host binding for "} +
|
||||
std::string{binding.name};
|
||||
return false;
|
||||
}
|
||||
wasm_valtype_vec_t parameters;
|
||||
wasm_valtype_vec_new_uninitialized(¶meters, binding.parameterCount);
|
||||
for (std::size_t index = 0; index < binding.parameterCount; ++index)
|
||||
parameters.data[index] = wasm_valtype_new(
|
||||
isI32(binding.parameters[index]) ? WASM_I32 : WASM_I64);
|
||||
|
||||
wasm_valtype_vec_t results;
|
||||
wasm_valtype_vec_new_uninitialized(&results, 1);
|
||||
results.data[0] =
|
||||
wasm_valtype_new(isI32(binding.result) ? WASM_I32 : WASM_I64);
|
||||
auto* type = wasm_functype_new(¶meters, &results);
|
||||
if (!type)
|
||||
{
|
||||
error = std::string{"could not create import type for "} +
|
||||
std::string{binding.name};
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* defineError = wasmtime_linker_define_func(
|
||||
linker,
|
||||
binding.module.data(),
|
||||
binding.module.size(),
|
||||
binding.name.data(),
|
||||
binding.name.size(),
|
||||
type,
|
||||
rawHookCallback,
|
||||
const_cast<WasmtimeHostBinding*>(&binding),
|
||||
nullptr);
|
||||
wasm_functype_delete(type);
|
||||
if (defineError)
|
||||
{
|
||||
error = takeError(defineError);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool
|
||||
QuickJSHostAdapterPolicy::complete() const noexcept
|
||||
{
|
||||
if (imports.empty())
|
||||
return false;
|
||||
for (std::size_t index = 0; index < imports.size(); ++index)
|
||||
{
|
||||
auto const& binding = imports[index];
|
||||
if (!validWasmtimeHostBinding(binding))
|
||||
return false;
|
||||
for (std::size_t prior = 0; prior < index; ++prior)
|
||||
if (imports[prior].module == binding.module &&
|
||||
imports[prior].name == binding.name)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QuickJSHostAdapterPolicy const*
|
||||
findHostAdapterPolicy(std::string_view id) noexcept
|
||||
{
|
||||
auto const& policy = v1Policy();
|
||||
return id == policy.id ? &policy : nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
defineImports(
|
||||
wasmtime_linker_t* linker,
|
||||
QuickJSHostAdapterPolicy const& policy,
|
||||
std::string& error)
|
||||
{
|
||||
if (!policy.complete())
|
||||
{
|
||||
error = "QuickJS host policy has an invalid host binding";
|
||||
return false;
|
||||
}
|
||||
for (auto const& binding : policy.imports)
|
||||
if (!defineImport(linker, binding, error))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace hook::quickjs
|
||||
39
src/xrpld/app/hook/detail/quickjs/QuickJSLinker.h
Normal file
39
src/xrpld/app/hook/detail/quickjs/QuickJSLinker.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#ifndef XRPLD_APP_HOOK_DETAIL_QUICKJS_QUICKJSLINKER_H_INCLUDED
|
||||
#define XRPLD_APP_HOOK_DETAIL_QUICKJS_QUICKJSLINKER_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/hook/detail/quickjs/QuickJSHostCall.h>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <wasmtime.h>
|
||||
|
||||
namespace hook::quickjs {
|
||||
|
||||
enum class HostChargeOrder : std::uint8_t { amendmentBeforeCharge };
|
||||
|
||||
enum class HostDebitBehavior : std::uint8_t { saturatedBasePlusAddressedBytes };
|
||||
|
||||
struct QuickJSHostAdapterPolicy
|
||||
{
|
||||
std::string_view id;
|
||||
std::string_view hostWorkMeter;
|
||||
HostChargeOrder chargeOrder;
|
||||
HostDebitBehavior debitBehavior;
|
||||
std::span<WasmtimeHostBinding const> imports;
|
||||
|
||||
bool
|
||||
complete() const noexcept;
|
||||
};
|
||||
|
||||
QuickJSHostAdapterPolicy const*
|
||||
findHostAdapterPolicy(std::string_view id) noexcept;
|
||||
|
||||
bool
|
||||
defineImports(
|
||||
wasmtime_linker_t* linker,
|
||||
QuickJSHostAdapterPolicy const& policy,
|
||||
std::string& error);
|
||||
|
||||
} // namespace hook::quickjs
|
||||
|
||||
#endif // XRPLD_APP_HOOK_DETAIL_QUICKJS_QUICKJSLINKER_H_INCLUDED
|
||||
408
src/xrpld/app/hook/detail/quickjs/QuickJSProviderSession.cpp
Normal file
408
src/xrpld/app/hook/detail/quickjs/QuickJSProviderSession.cpp
Normal file
@@ -0,0 +1,408 @@
|
||||
#include <xrpld/app/hook/HookGuestMemory.h>
|
||||
#include <xrpld/app/hook/detail/quickjs/QuickJSProviderSession.h>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace hook::quickjs {
|
||||
namespace {
|
||||
|
||||
constexpr std::uint32_t maxDiagnosticLength = 4096;
|
||||
|
||||
std::string
|
||||
takeError(wasmtime_error_t* error)
|
||||
{
|
||||
wasm_name_t message;
|
||||
wasmtime_error_message(error, &message);
|
||||
std::string result{message.data, message.size};
|
||||
wasm_byte_vec_delete(&message);
|
||||
wasmtime_error_delete(error);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string
|
||||
takeTrap(wasm_trap_t* trap)
|
||||
{
|
||||
wasm_name_t message;
|
||||
wasm_trap_message(trap, &message);
|
||||
std::string result{message.data, message.size};
|
||||
wasm_byte_vec_delete(&message);
|
||||
wasm_trap_delete(trap);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool
|
||||
findFunction(
|
||||
wasmtime_context_t* context,
|
||||
wasmtime_instance_t const& instance,
|
||||
char const* name,
|
||||
wasmtime_func_t& result,
|
||||
std::string& detail)
|
||||
{
|
||||
wasmtime_extern_t item;
|
||||
if (!wasmtime_instance_export_get(
|
||||
context, &instance, name, std::strlen(name), &item) ||
|
||||
item.kind != WASMTIME_EXTERN_FUNC)
|
||||
{
|
||||
detail =
|
||||
std::string{"missing QuickJS provider function export: "} + name;
|
||||
return false;
|
||||
}
|
||||
result = item.of.func;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
call(
|
||||
wasmtime_context_t* context,
|
||||
wasmtime_func_t const& function,
|
||||
wasmtime_val_t const* arguments,
|
||||
std::size_t argumentCount,
|
||||
wasmtime_val_t* results,
|
||||
std::size_t resultCount,
|
||||
std::string& detail)
|
||||
{
|
||||
wasm_trap_t* trap = nullptr;
|
||||
auto* callError = wasmtime_func_call(
|
||||
context,
|
||||
&function,
|
||||
arguments,
|
||||
argumentCount,
|
||||
results,
|
||||
resultCount,
|
||||
&trap);
|
||||
if (callError)
|
||||
{
|
||||
detail = takeError(callError);
|
||||
if (trap)
|
||||
wasm_trap_delete(trap);
|
||||
return false;
|
||||
}
|
||||
if (trap)
|
||||
{
|
||||
detail = takeTrap(trap);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
asI32(wasmtime_val_t const& value, std::int32_t& out, std::string& detail)
|
||||
{
|
||||
if (value.kind != WASMTIME_I32)
|
||||
{
|
||||
detail = "QuickJS provider export returned a non-i32";
|
||||
return false;
|
||||
}
|
||||
out = value.of.i32;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string_view
|
||||
providerStageName(ProviderStage stage) noexcept
|
||||
{
|
||||
switch (stage)
|
||||
{
|
||||
case ProviderStage::none:
|
||||
return {};
|
||||
case ProviderStage::storeCreation:
|
||||
return "QuickJS Store creation failed";
|
||||
case ProviderStage::initializationFuel:
|
||||
return "QuickJS initialization fuel setup failed";
|
||||
case ProviderStage::importRegistration:
|
||||
return "QuickJS Hook import registration failed";
|
||||
case ProviderStage::instantiation:
|
||||
return "QuickJS provider instantiation failed";
|
||||
case ProviderStage::memoryExport:
|
||||
return "QuickJS provider exports no memory";
|
||||
case ProviderStage::reactorInitialization:
|
||||
return "QuickJS reactor initialization failed";
|
||||
case ProviderStage::quickJSInitialization:
|
||||
return "QuickJS initialization failed";
|
||||
case ProviderStage::memoryLimit:
|
||||
return "QuickJS memory limit setup failed";
|
||||
case ProviderStage::stackLimit:
|
||||
return "QuickJS stack limit setup failed";
|
||||
case ProviderStage::invocationFuel:
|
||||
return "QuickJS invocation fuel setup failed";
|
||||
case ProviderStage::bytecodeInput:
|
||||
return "QuickJS Hook bytecode is invalid";
|
||||
case ProviderStage::bytecodeAllocation:
|
||||
return "QuickJS bytecode allocation failed";
|
||||
case ProviderStage::bytecodeCopy:
|
||||
return "QuickJS bytecode copy was out of bounds";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ProviderSession::ProviderSession(
|
||||
QuickJSProviderRuntime const& runtime,
|
||||
StorePtr&& store,
|
||||
LinkerPtr&& linker,
|
||||
wasmtime_instance_t instance,
|
||||
wasmtime_memory_t memory) noexcept
|
||||
: runtime_(runtime)
|
||||
, store_(std::move(store))
|
||||
, linker_(std::move(linker))
|
||||
, context_(wasmtime_store_context(store_.get()))
|
||||
, instance_(instance)
|
||||
, memory_(memory)
|
||||
{
|
||||
}
|
||||
|
||||
std::unique_ptr<ProviderSession>
|
||||
ProviderSession::create(
|
||||
QuickJSProviderRuntime const& runtime,
|
||||
void* storeData,
|
||||
ProviderStage& stage,
|
||||
std::string& detail)
|
||||
{
|
||||
stage = ProviderStage::none;
|
||||
detail.clear();
|
||||
StorePtr store{
|
||||
wasmtime_store_new(runtime.engine.get(), storeData, nullptr),
|
||||
wasmtime_store_delete};
|
||||
if (!store)
|
||||
{
|
||||
stage = ProviderStage::storeCreation;
|
||||
return {};
|
||||
}
|
||||
auto* context = wasmtime_store_context(store.get());
|
||||
if (auto* fuelError = wasmtime_context_set_fuel(
|
||||
context, runtime.profile.initializationFuel))
|
||||
{
|
||||
stage = ProviderStage::initializationFuel;
|
||||
detail = takeError(fuelError);
|
||||
return {};
|
||||
}
|
||||
|
||||
LinkerPtr linker{
|
||||
wasmtime_linker_new(runtime.engine.get()), wasmtime_linker_delete};
|
||||
if (!linker || !defineImports(linker.get(), runtime.hostPolicy, detail))
|
||||
{
|
||||
stage = ProviderStage::importRegistration;
|
||||
return {};
|
||||
}
|
||||
|
||||
wasmtime_instance_t instance;
|
||||
wasm_trap_t* trap = nullptr;
|
||||
auto* instantiateError = wasmtime_linker_instantiate(
|
||||
linker.get(), context, runtime.module.get(), &instance, &trap);
|
||||
if (instantiateError || trap)
|
||||
{
|
||||
stage = ProviderStage::instantiation;
|
||||
detail =
|
||||
instantiateError ? takeError(instantiateError) : takeTrap(trap);
|
||||
if (instantiateError && trap)
|
||||
wasm_trap_delete(trap);
|
||||
return {};
|
||||
}
|
||||
|
||||
wasmtime_extern_t memoryExport;
|
||||
if (!wasmtime_instance_export_get(
|
||||
context, &instance, "memory", 6, &memoryExport) ||
|
||||
memoryExport.kind != WASMTIME_EXTERN_MEMORY)
|
||||
{
|
||||
stage = ProviderStage::memoryExport;
|
||||
return {};
|
||||
}
|
||||
|
||||
return std::unique_ptr<ProviderSession>{new ProviderSession{
|
||||
runtime,
|
||||
std::move(store),
|
||||
std::move(linker),
|
||||
instance,
|
||||
memoryExport.of.memory}};
|
||||
}
|
||||
|
||||
bool
|
||||
ProviderSession::callExport(
|
||||
char const* name,
|
||||
wasmtime_val_t const* arguments,
|
||||
std::size_t argumentCount,
|
||||
wasmtime_val_t* results,
|
||||
std::size_t resultCount,
|
||||
std::string& detail)
|
||||
{
|
||||
wasmtime_func_t function;
|
||||
return findFunction(context_, instance_, name, function, detail) &&
|
||||
call(context_,
|
||||
function,
|
||||
arguments,
|
||||
argumentCount,
|
||||
results,
|
||||
resultCount,
|
||||
detail);
|
||||
}
|
||||
|
||||
bool
|
||||
ProviderSession::initialize(ProviderStage& stage, std::string& detail)
|
||||
{
|
||||
stage = ProviderStage::none;
|
||||
detail.clear();
|
||||
if (!callExport("_initialize", nullptr, 0, nullptr, 0, detail))
|
||||
{
|
||||
stage = ProviderStage::reactorInitialization;
|
||||
return false;
|
||||
}
|
||||
if (!callExport("qjs_init", nullptr, 0, nullptr, 0, detail))
|
||||
{
|
||||
stage = ProviderStage::quickJSInitialization;
|
||||
return false;
|
||||
}
|
||||
|
||||
wasmtime_val_t limit[1] = {
|
||||
{.kind = WASMTIME_I32,
|
||||
.of = {.i32 = static_cast<std::int32_t>(runtime_.profile.heapBytes)}}};
|
||||
if (!callExport("qjs_set_memory_limit", limit, 1, nullptr, 0, detail))
|
||||
{
|
||||
stage = ProviderStage::memoryLimit;
|
||||
return false;
|
||||
}
|
||||
limit[0].of.i32 = static_cast<std::int32_t>(runtime_.profile.stackBytes);
|
||||
if (!callExport("qjs_set_max_stack_size", limit, 1, nullptr, 0, detail))
|
||||
{
|
||||
stage = ProviderStage::stackLimit;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ProviderSession::resetInvocationFuel(ProviderStage& stage, std::string& detail)
|
||||
{
|
||||
stage = ProviderStage::none;
|
||||
detail.clear();
|
||||
if (auto* fuelError = wasmtime_context_set_fuel(
|
||||
context_, runtime_.profile.invocationFuel))
|
||||
{
|
||||
stage = ProviderStage::invocationFuel;
|
||||
detail = takeError(fuelError);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<std::uint32_t>
|
||||
ProviderSession::allocateAndCopy(
|
||||
std::span<std::uint8_t const> bytes,
|
||||
ProviderStage& stage,
|
||||
std::string& detail)
|
||||
{
|
||||
stage = ProviderStage::none;
|
||||
detail.clear();
|
||||
if (bytes.empty() ||
|
||||
bytes.size() >
|
||||
static_cast<std::size_t>(std::numeric_limits<std::int32_t>::max()))
|
||||
{
|
||||
stage = ProviderStage::bytecodeInput;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
wasmtime_val_t argument[1] = {
|
||||
{.kind = WASMTIME_I32,
|
||||
.of = {.i32 = static_cast<std::int32_t>(bytes.size())}}};
|
||||
wasmtime_val_t result[1];
|
||||
if (!callExport("malloc", argument, 1, result, 1, detail))
|
||||
{
|
||||
stage = ProviderStage::bytecodeAllocation;
|
||||
return std::nullopt;
|
||||
}
|
||||
std::int32_t mallocResult = 0;
|
||||
if (!asI32(result[0], mallocResult, detail))
|
||||
{
|
||||
stage = ProviderStage::bytecodeAllocation;
|
||||
return std::nullopt;
|
||||
}
|
||||
auto const pointer = static_cast<std::uint32_t>(mallocResult);
|
||||
if (pointer == 0)
|
||||
{
|
||||
stage = ProviderStage::bytecodeAllocation;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// malloc may grow the Wasm memory. Reacquire the native view only now.
|
||||
HookGuestMemory memory{
|
||||
wasmtime_memory_data(context_, &memory_),
|
||||
wasmtime_memory_data_size(context_, &memory_)};
|
||||
auto destination =
|
||||
memory.write(pointer, static_cast<std::uint32_t>(bytes.size()));
|
||||
if (!destination)
|
||||
{
|
||||
stage = ProviderStage::bytecodeCopy;
|
||||
return std::nullopt;
|
||||
}
|
||||
std::copy(bytes.begin(), bytes.end(), destination->begin());
|
||||
return pointer;
|
||||
}
|
||||
|
||||
std::string
|
||||
ProviderSession::readDiagnostic() noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
std::string ignored;
|
||||
wasmtime_val_t pointerResult[1];
|
||||
wasmtime_val_t lengthResult[1];
|
||||
if (!callExport(
|
||||
"qjs_get_result_ptr", nullptr, 0, pointerResult, 1, ignored) ||
|
||||
!callExport(
|
||||
"qjs_get_result_len", nullptr, 0, lengthResult, 1, ignored))
|
||||
return {};
|
||||
|
||||
std::int32_t pointerBits = 0;
|
||||
std::int32_t lengthBits = 0;
|
||||
if (!asI32(pointerResult[0], pointerBits, ignored) ||
|
||||
!asI32(lengthResult[0], lengthBits, ignored))
|
||||
return {};
|
||||
auto const pointer = static_cast<std::uint32_t>(pointerBits);
|
||||
auto const length = std::min<std::uint32_t>(
|
||||
static_cast<std::uint32_t>(lengthBits), maxDiagnosticLength);
|
||||
if (length == 0)
|
||||
return {};
|
||||
|
||||
// Either diagnostic export may grow memory. Reacquire after both.
|
||||
HookGuestMemory memory{
|
||||
wasmtime_memory_data(context_, &memory_),
|
||||
wasmtime_memory_data_size(context_, &memory_)};
|
||||
auto const bytes = memory.read(pointer, length);
|
||||
if (!bytes)
|
||||
return {};
|
||||
return {reinterpret_cast<char const*>(bytes->data()), bytes->size()};
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::uint64_t>
|
||||
ProviderSession::fuelConsumedFrom(std::uint64_t armed) const noexcept
|
||||
{
|
||||
std::uint64_t remaining = 0;
|
||||
if (auto* fuelError = wasmtime_context_get_fuel(context_, &remaining))
|
||||
{
|
||||
wasmtime_error_delete(fuelError);
|
||||
return std::nullopt;
|
||||
}
|
||||
return armed - std::min(armed, remaining);
|
||||
}
|
||||
|
||||
std::optional<std::uint64_t>
|
||||
ProviderSession::initializationFuelConsumed() const noexcept
|
||||
{
|
||||
return fuelConsumedFrom(runtime_.profile.initializationFuel);
|
||||
}
|
||||
|
||||
std::optional<std::uint64_t>
|
||||
ProviderSession::invocationFuelConsumed() const noexcept
|
||||
{
|
||||
return fuelConsumedFrom(runtime_.profile.invocationFuel);
|
||||
}
|
||||
|
||||
} // namespace hook::quickjs
|
||||
114
src/xrpld/app/hook/detail/quickjs/QuickJSProviderSession.h
Normal file
114
src/xrpld/app/hook/detail/quickjs/QuickJSProviderSession.h
Normal file
@@ -0,0 +1,114 @@
|
||||
#ifndef XRPLD_APP_HOOK_DETAIL_QUICKJS_QUICKJSPROVIDERSESSION_H_INCLUDED
|
||||
#define XRPLD_APP_HOOK_DETAIL_QUICKJS_QUICKJSPROVIDERSESSION_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/hook/detail/quickjs/QuickJSRuntimeInternal.h>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <wasmtime.h>
|
||||
|
||||
namespace hook::quickjs {
|
||||
|
||||
enum class ProviderStage : std::uint8_t {
|
||||
none,
|
||||
storeCreation,
|
||||
initializationFuel,
|
||||
importRegistration,
|
||||
instantiation,
|
||||
memoryExport,
|
||||
reactorInitialization,
|
||||
quickJSInitialization,
|
||||
memoryLimit,
|
||||
stackLimit,
|
||||
invocationFuel,
|
||||
bytecodeInput,
|
||||
bytecodeAllocation,
|
||||
bytecodeCopy
|
||||
};
|
||||
|
||||
std::string_view
|
||||
providerStageName(ProviderStage stage) noexcept;
|
||||
|
||||
/** One callback-scoped instance of a retained QuickJS provider.
|
||||
|
||||
The session owns its Store and Linker. It may retain the Wasmtime memory
|
||||
handle, but it never retains a native data pointer, size, span, or
|
||||
HookGuestMemory view across a Wasm call.
|
||||
*/
|
||||
class ProviderSession
|
||||
{
|
||||
public:
|
||||
using LinkerPtr =
|
||||
std::unique_ptr<wasmtime_linker_t, decltype(&wasmtime_linker_delete)>;
|
||||
using StorePtr =
|
||||
std::unique_ptr<wasmtime_store_t, decltype(&wasmtime_store_delete)>;
|
||||
|
||||
static std::unique_ptr<ProviderSession>
|
||||
create(
|
||||
QuickJSProviderRuntime const& runtime,
|
||||
void* storeData,
|
||||
ProviderStage& stage,
|
||||
std::string& detail);
|
||||
|
||||
ProviderSession(ProviderSession const&) = delete;
|
||||
ProviderSession&
|
||||
operator=(ProviderSession const&) = delete;
|
||||
|
||||
bool
|
||||
initialize(ProviderStage& stage, std::string& detail);
|
||||
|
||||
bool
|
||||
resetInvocationFuel(ProviderStage& stage, std::string& detail);
|
||||
|
||||
std::optional<std::uint32_t>
|
||||
allocateAndCopy(
|
||||
std::span<std::uint8_t const> bytes,
|
||||
ProviderStage& stage,
|
||||
std::string& detail);
|
||||
|
||||
bool
|
||||
callExport(
|
||||
char const* name,
|
||||
wasmtime_val_t const* arguments,
|
||||
std::size_t argumentCount,
|
||||
wasmtime_val_t* results,
|
||||
std::size_t resultCount,
|
||||
std::string& detail);
|
||||
|
||||
std::string
|
||||
readDiagnostic() noexcept;
|
||||
|
||||
/** Fuel spent so far against the initialization budget; meaningful
|
||||
between create and resetInvocationFuel. */
|
||||
std::optional<std::uint64_t>
|
||||
initializationFuelConsumed() const noexcept;
|
||||
|
||||
std::optional<std::uint64_t>
|
||||
invocationFuelConsumed() const noexcept;
|
||||
|
||||
private:
|
||||
std::optional<std::uint64_t>
|
||||
fuelConsumedFrom(std::uint64_t armed) const noexcept;
|
||||
|
||||
ProviderSession(
|
||||
QuickJSProviderRuntime const& runtime,
|
||||
StorePtr&& store,
|
||||
LinkerPtr&& linker,
|
||||
wasmtime_instance_t instance,
|
||||
wasmtime_memory_t memory) noexcept;
|
||||
|
||||
QuickJSProviderRuntime const& runtime_;
|
||||
StorePtr store_;
|
||||
LinkerPtr linker_;
|
||||
wasmtime_context_t* context_;
|
||||
wasmtime_instance_t instance_;
|
||||
wasmtime_memory_t memory_;
|
||||
};
|
||||
|
||||
} // namespace hook::quickjs
|
||||
|
||||
#endif // XRPLD_APP_HOOK_DETAIL_QUICKJS_QUICKJSPROVIDERSESSION_H_INCLUDED
|
||||
116
src/xrpld/app/hook/detail/quickjs/QuickJSRuntime.cpp
Normal file
116
src/xrpld/app/hook/detail/quickjs/QuickJSRuntime.cpp
Normal file
@@ -0,0 +1,116 @@
|
||||
#include <xrpld/app/hook/QuickJSHookRuntime.h>
|
||||
#include <xrpld/app/hook/applyHook.h>
|
||||
#include <xrpld/app/hook/detail/quickjs/QuickJSHostCall.h>
|
||||
#include <xrpld/app/hook/detail/quickjs/QuickJSProviderSession.h>
|
||||
#include <xrpl/basics/scope.h>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <wasmtime.h>
|
||||
|
||||
namespace hook {
|
||||
|
||||
void
|
||||
executeQuickJSBytecode(
|
||||
HookContext& hookCtx,
|
||||
QuickJSRuntimeHandle const& runtime,
|
||||
std::span<std::uint8_t const> bytecode,
|
||||
bool callback,
|
||||
std::uint32_t reserved,
|
||||
beast::Journal const& journal)
|
||||
{
|
||||
auto fail = [&](std::string_view phase, std::string const& detail) {
|
||||
JLOG(journal.warn()) << "HookError[" << hookCtx.result.account << '-'
|
||||
<< hookCtx.result.otxnAccount << "]: " << phase
|
||||
<< (detail.empty() ? "" : ": ") << detail;
|
||||
hookCtx.result.exitType = hook_api::ExitType::WASM_ERROR;
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
if (!runtime)
|
||||
{
|
||||
fail("QuickJS runtime profile is not registered", {});
|
||||
return;
|
||||
}
|
||||
|
||||
quickjs::ProviderStage stage = quickjs::ProviderStage::none;
|
||||
std::string detail;
|
||||
quickjs::QuickJSInvocation invocation{hookCtx, runtime->profile};
|
||||
auto session = quickjs::ProviderSession::create(
|
||||
*runtime, &invocation, stage, detail);
|
||||
if (!session)
|
||||
{
|
||||
fail(quickjs::providerStageName(stage), detail);
|
||||
return;
|
||||
}
|
||||
if (!session->initialize(stage, detail) ||
|
||||
!session->resetInvocationFuel(stage, detail))
|
||||
{
|
||||
fail(quickjs::providerStageName(stage), detail);
|
||||
return;
|
||||
}
|
||||
|
||||
// This guard is created after the session, so it samples fuel before
|
||||
// the Store is destroyed on every post-reset exit path.
|
||||
ripple::scope_exit recordFuel{[&] {
|
||||
if (auto const consumed = session->invocationFuelConsumed())
|
||||
hookCtx.result.instructionCount = *consumed;
|
||||
}};
|
||||
|
||||
auto const bytecodePointer =
|
||||
session->allocateAndCopy(bytecode, stage, detail);
|
||||
if (!bytecodePointer)
|
||||
{
|
||||
fail(quickjs::providerStageName(stage), detail);
|
||||
return;
|
||||
}
|
||||
|
||||
wasmtime_val_t arguments[3] = {
|
||||
{.kind = WASMTIME_I32,
|
||||
.of = {.i32 = static_cast<std::int32_t>(*bytecodePointer)}},
|
||||
{.kind = WASMTIME_I32,
|
||||
.of = {.i32 = static_cast<std::int32_t>(bytecode.size())}},
|
||||
{.kind = WASMTIME_I32,
|
||||
.of = {.i32 = static_cast<std::int32_t>(reserved)}}};
|
||||
wasmtime_val_t result[1];
|
||||
if (!session->callExport(
|
||||
callback ? "qjs_cbak" : "qjs_hook",
|
||||
arguments,
|
||||
3,
|
||||
result,
|
||||
1,
|
||||
detail))
|
||||
{
|
||||
if (invocation.terminal)
|
||||
return;
|
||||
fail("QuickJS entry invocation trapped", detail);
|
||||
return;
|
||||
}
|
||||
if (result[0].kind != WASMTIME_I32)
|
||||
{
|
||||
fail("QuickJS entry invocation returned a non-i32", {});
|
||||
return;
|
||||
}
|
||||
|
||||
if (result[0].of.i32 != 0)
|
||||
{
|
||||
detail = session->readDiagnostic();
|
||||
hookCtx.result.exitReason = detail;
|
||||
fail("QuickJS entry invocation returned an error", detail);
|
||||
return;
|
||||
}
|
||||
|
||||
fail("JavaScript Hook returned without a terminal", {});
|
||||
}
|
||||
catch (std::exception const& exception)
|
||||
{
|
||||
fail("uncaught QuickJS runtime exception", exception.what());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
fail("uncaught QuickJS runtime exception", {});
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace hook
|
||||
40
src/xrpld/app/hook/detail/quickjs/QuickJSRuntimeInternal.h
Normal file
40
src/xrpld/app/hook/detail/quickjs/QuickJSRuntimeInternal.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#ifndef XRPLD_APP_HOOK_DETAIL_QUICKJS_QUICKJSRUNTIMEINTERNAL_H_INCLUDED
|
||||
#define XRPLD_APP_HOOK_DETAIL_QUICKJS_QUICKJSRUNTIMEINTERNAL_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/hook/QuickJSHookRuntime.h>
|
||||
#include <xrpld/app/hook/detail/quickjs/QuickJSLinker.h>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <wasmtime.h>
|
||||
|
||||
namespace hook {
|
||||
|
||||
using QuickJSEnginePtr =
|
||||
std::unique_ptr<wasm_engine_t, decltype(&wasm_engine_delete)>;
|
||||
using QuickJSModulePtr =
|
||||
std::unique_ptr<wasmtime_module_t, decltype(&wasmtime_module_delete)>;
|
||||
|
||||
class QuickJSProviderRuntime
|
||||
{
|
||||
public:
|
||||
QuickJSRuntimeProfile const profile;
|
||||
quickjs::QuickJSHostAdapterPolicy const& hostPolicy;
|
||||
QuickJSEnginePtr engine;
|
||||
QuickJSModulePtr module;
|
||||
|
||||
QuickJSProviderRuntime(
|
||||
QuickJSRuntimeProfile profile,
|
||||
quickjs::QuickJSHostAdapterPolicy const& hostPolicy,
|
||||
QuickJSEnginePtr&& engine,
|
||||
QuickJSModulePtr&& module)
|
||||
: profile(std::move(profile))
|
||||
, hostPolicy(hostPolicy)
|
||||
, engine(std::move(engine))
|
||||
, module(std::move(module))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace hook
|
||||
|
||||
#endif // XRPLD_APP_HOOK_DETAIL_QUICKJS_QUICKJSRUNTIMEINTERNAL_H_INCLUDED
|
||||
359
src/xrpld/app/hook/detail/quickjs/QuickJSRuntimeRegistry.cpp
Normal file
359
src/xrpld/app/hook/detail/quickjs/QuickJSRuntimeRegistry.cpp
Normal file
@@ -0,0 +1,359 @@
|
||||
#include <xrpld/app/hook/QuickJSHookRuntime.h>
|
||||
#include <xrpld/app/hook/detail/QuickJSProviderProfile.h>
|
||||
#include <xrpld/app/hook/detail/quickjs/QuickJSRuntimeInternal.h>
|
||||
#include <xrpl/hook/HookArtifact.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <chrono>
|
||||
#include <exception>
|
||||
#include <future>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
namespace hook {
|
||||
namespace {
|
||||
|
||||
using RuntimeKey =
|
||||
std::tuple<std::uint16_t, artifact::Identity, artifact::Identity>;
|
||||
|
||||
RuntimeKey
|
||||
keyFor(QuickJSRuntimeProfile const& profile)
|
||||
{
|
||||
return {
|
||||
profile.hookApiVersion, profile.bytecodeABI, profile.runtimeProfile};
|
||||
}
|
||||
|
||||
RuntimeKey
|
||||
keyFor(artifact::View const& artifact)
|
||||
{
|
||||
return {
|
||||
artifact.hookApiVersion, artifact.bytecodeABI, artifact.runtimeProfile};
|
||||
}
|
||||
|
||||
// A launched (background) registration, keyed like the registry. The future
|
||||
// settles when the compile thread has finished registering or failed; the
|
||||
// launched profile lets a mismatched relaunch be refused, and the retained
|
||||
// error string keeps a failed identity diagnosable for the life of the
|
||||
// process. Entries are never erased: launch is at most once per identity.
|
||||
struct PendingRegistration
|
||||
{
|
||||
QuickJSRuntimeProfile profile;
|
||||
std::shared_future<void> settled;
|
||||
std::shared_ptr<std::optional<std::string>> error;
|
||||
};
|
||||
|
||||
// One static holds the mutex, the registry, and the pending map so their
|
||||
// lifetimes do not depend on which accessor a process happens to touch
|
||||
// first. Nothing here joins a compile thread: the thread is detached, its
|
||||
// future is promise-backed, and the process must await settlement before
|
||||
// shutdown (see the header contract).
|
||||
struct RegistryState
|
||||
{
|
||||
std::mutex mutex;
|
||||
std::map<RuntimeKey, QuickJSRuntimeHandle> runtimes;
|
||||
std::map<RuntimeKey, PendingRegistration> pending;
|
||||
};
|
||||
|
||||
RegistryState&
|
||||
registryState()
|
||||
{
|
||||
static RegistryState state;
|
||||
return state;
|
||||
}
|
||||
|
||||
// Copy a pending entry under the registry lock, or nothing.
|
||||
std::optional<PendingRegistration>
|
||||
pendingFor(RuntimeKey const& key)
|
||||
{
|
||||
auto& state = registryState();
|
||||
std::lock_guard lock{state.mutex};
|
||||
auto const position = state.pending.find(key);
|
||||
if (position == state.pending.end())
|
||||
return std::nullopt;
|
||||
return position->second;
|
||||
}
|
||||
|
||||
std::string
|
||||
takeError(wasmtime_error_t* error)
|
||||
{
|
||||
wasm_name_t message;
|
||||
wasmtime_error_message(error, &message);
|
||||
std::string result{message.data, message.size};
|
||||
wasm_byte_vec_delete(&message);
|
||||
wasmtime_error_delete(error);
|
||||
return result;
|
||||
}
|
||||
|
||||
QuickJSRuntimeHandle
|
||||
compileRuntime(
|
||||
QuickJSRuntimeProfile profile,
|
||||
std::span<std::uint8_t const> provider,
|
||||
std::string& error)
|
||||
{
|
||||
if (profile.initializationFuel == 0 || profile.invocationFuel == 0 ||
|
||||
profile.hostWorkBudget == 0 || profile.hostWorkPerAddressedByte == 0 ||
|
||||
profile.heapBytes == 0 || profile.stackBytes == 0 ||
|
||||
profile.serializedObjectMaxBytes == 0 ||
|
||||
profile.serializedObjectMaxFields == 0 ||
|
||||
profile.serializedObjectMaxScopes == 0 ||
|
||||
profile.serializedObjectMaxDepth == 0 ||
|
||||
profile.providerMemoryMinimumPages == 0 ||
|
||||
profile.providerMemoryMaximumPages < profile.providerMemoryMinimumPages)
|
||||
{
|
||||
error = "runtime profile has an invalid zero execution limit";
|
||||
return {};
|
||||
}
|
||||
auto const* hostPolicy =
|
||||
quickjs::findHostAdapterPolicy(profile.hostAdapterPolicy);
|
||||
if (!hostPolicy)
|
||||
{
|
||||
error = "runtime profile names an unknown host-adapter policy";
|
||||
return {};
|
||||
}
|
||||
if (!hostPolicy->complete())
|
||||
{
|
||||
error = "runtime profile host policy has a missing required binding";
|
||||
return {};
|
||||
}
|
||||
if (profile.hostWorkMeter != hostPolicy->hostWorkMeter)
|
||||
{
|
||||
error =
|
||||
"runtime profile names the wrong host-work meter for its "
|
||||
"host policy";
|
||||
return {};
|
||||
}
|
||||
if (provider.size() != profile.providerSize)
|
||||
{
|
||||
error = "provider size does not match its runtime profile";
|
||||
return {};
|
||||
}
|
||||
ripple::sha256_hasher hasher;
|
||||
hasher(provider.data(), provider.size());
|
||||
if (static_cast<ripple::sha256_hasher::result_type>(hasher) !=
|
||||
profile.providerSHA256)
|
||||
{
|
||||
error = "provider SHA-256 does not match its runtime profile";
|
||||
return {};
|
||||
}
|
||||
|
||||
auto* config = wasm_config_new();
|
||||
if (!config)
|
||||
{
|
||||
error = "could not allocate Wasmtime configuration";
|
||||
return {};
|
||||
}
|
||||
wasmtime_config_consume_fuel_set(config, true);
|
||||
wasmtime_config_cranelift_nan_canonicalization_set(config, true);
|
||||
wasmtime_config_wasm_threads_set(config, false);
|
||||
wasmtime_config_wasm_relaxed_simd_set(config, false);
|
||||
wasmtime_config_wasm_memory64_set(config, false);
|
||||
wasmtime_config_wasm_multi_memory_set(config, false);
|
||||
wasmtime_config_wasm_tail_call_set(config, false);
|
||||
|
||||
QuickJSEnginePtr engine{
|
||||
wasm_engine_new_with_config(config), wasm_engine_delete};
|
||||
if (!engine)
|
||||
{
|
||||
error = "could not create the pinned Wasmtime engine";
|
||||
return {};
|
||||
}
|
||||
|
||||
wasmtime_module_t* module = nullptr;
|
||||
if (auto* compileError = wasmtime_module_new(
|
||||
engine.get(), provider.data(), provider.size(), &module))
|
||||
{
|
||||
error = takeError(compileError);
|
||||
return {};
|
||||
}
|
||||
|
||||
return std::make_shared<QuickJSProviderRuntime>(
|
||||
std::move(profile),
|
||||
*hostPolicy,
|
||||
std::move(engine),
|
||||
QuickJSModulePtr{module, wasmtime_module_delete});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QuickJSRuntimeProfile
|
||||
currentQuickJSRuntimeProfile()
|
||||
{
|
||||
return {
|
||||
.bytecodeABI = artifact::quickJSBytecodeABI,
|
||||
.runtimeProfile = artifact::quickJSRuntimeProfile,
|
||||
.providerSHA256 = artifact::quickJSProviderSHA256,
|
||||
.providerSize = artifact::quickJSProviderSize,
|
||||
.hookApiVersion = artifact::quickJSHookApiVersion,
|
||||
.initializationFuel = artifact::quickJSInitializationFuel,
|
||||
.invocationFuel = artifact::quickJSInvocationFuel,
|
||||
.hostWorkBudget = artifact::quickJSHostWorkBudget,
|
||||
.hostWorkBasePerCall = artifact::quickJSHostWorkBasePerCall,
|
||||
.hostWorkPerAddressedByte = artifact::quickJSHostWorkPerAddressedByte,
|
||||
.hostWorkMeter = std::string{artifact::quickJSHostWorkMeter},
|
||||
.hostAdapterPolicy = std::string{artifact::quickJSHostAdapterPolicy},
|
||||
.heapBytes = artifact::quickJSHeapBytes,
|
||||
.stackBytes = artifact::quickJSStackBytes,
|
||||
.serializedObjectMaxBytes = artifact::quickJSSerializedObjectMaxBytes,
|
||||
.serializedObjectMaxFields = artifact::quickJSSerializedObjectMaxFields,
|
||||
.serializedObjectMaxScopes = artifact::quickJSSerializedObjectMaxScopes,
|
||||
.serializedObjectMaxDepth = artifact::quickJSSerializedObjectMaxDepth,
|
||||
.providerMemoryMinimumPages =
|
||||
artifact::quickJSProviderMemoryMinimumPages,
|
||||
.providerMemoryMaximumPages =
|
||||
artifact::quickJSProviderMemoryMaximumPages};
|
||||
}
|
||||
|
||||
std::optional<std::string>
|
||||
registerQuickJSRuntime(
|
||||
QuickJSRuntimeProfile profile,
|
||||
std::span<std::uint8_t const> provider)
|
||||
{
|
||||
std::string error;
|
||||
auto candidate = compileRuntime(profile, provider, error);
|
||||
if (!candidate)
|
||||
return error;
|
||||
|
||||
auto& state = registryState();
|
||||
std::lock_guard lock{state.mutex};
|
||||
auto const [position, inserted] =
|
||||
state.runtimes.emplace(keyFor(profile), std::move(candidate));
|
||||
if (!inserted && position->second->profile != profile)
|
||||
return "runtime profile identity is already registered with different "
|
||||
"consensus limits or host policy";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::string>
|
||||
launchQuickJSRuntimeRegistration(
|
||||
QuickJSRuntimeProfile profile,
|
||||
ripple::Blob provider)
|
||||
{
|
||||
auto const key = keyFor(profile);
|
||||
auto error = std::make_shared<std::optional<std::string>>();
|
||||
auto settled = std::make_shared<std::promise<void>>();
|
||||
{
|
||||
auto& state = registryState();
|
||||
std::lock_guard lock{state.mutex};
|
||||
if (auto const registered = state.runtimes.find(key);
|
||||
registered != state.runtimes.end())
|
||||
{
|
||||
if (registered->second->profile == profile)
|
||||
return std::nullopt;
|
||||
return "runtime profile identity is already registered with "
|
||||
"different consensus limits or host policy";
|
||||
}
|
||||
if (auto const launched = state.pending.find(key);
|
||||
launched != state.pending.end())
|
||||
{
|
||||
if (launched->second.profile != profile)
|
||||
return "runtime profile identity was already launched with "
|
||||
"different consensus limits or host policy";
|
||||
// Same identity, same limits: still compiling, already
|
||||
// registered, or terminally failed. Only the failure is loud.
|
||||
if (launched->second.settled.wait_for(std::chrono::seconds{0}) ==
|
||||
std::future_status::ready)
|
||||
return *launched->second.error;
|
||||
return std::nullopt;
|
||||
}
|
||||
// Reserve the entry before any thread exists, so nothing below can
|
||||
// wait on the compile while holding the registry lock, and so a
|
||||
// find/await that observes the entry always has a valid future.
|
||||
state.pending.emplace(
|
||||
key,
|
||||
PendingRegistration{profile, settled->get_future().share(), error});
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
std::thread{[profile = std::move(profile),
|
||||
provider = std::move(provider),
|
||||
error,
|
||||
settled]() mutable {
|
||||
try
|
||||
{
|
||||
*error = registerQuickJSRuntime(profile, provider);
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
*error =
|
||||
std::string{"provider registration threw: "} + e.what();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
*error = "provider registration threw an unknown exception";
|
||||
}
|
||||
settled->set_value();
|
||||
}}.detach();
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
// No thread runs the lambda when its constructor throws; settle the
|
||||
// reserved entry so waiters see the failure instead of blocking.
|
||||
*error = std::string{"could not start the provider compile thread: "} +
|
||||
e.what();
|
||||
settled->set_value();
|
||||
return *error;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::string>
|
||||
awaitQuickJSRuntimeRegistration(QuickJSRuntimeProfile const& profile)
|
||||
{
|
||||
auto const key = keyFor(profile);
|
||||
// Wait outside the lock: the compile thread needs it to publish.
|
||||
if (auto pending = pendingFor(key))
|
||||
pending->settled.wait();
|
||||
|
||||
auto& state = registryState();
|
||||
std::lock_guard lock{state.mutex};
|
||||
auto const registered = state.runtimes.find(key);
|
||||
if (registered != state.runtimes.end() &&
|
||||
registered->second->profile == profile)
|
||||
return std::nullopt;
|
||||
if (auto const launched = state.pending.find(key);
|
||||
launched != state.pending.end() && *launched->second.error)
|
||||
return *launched->second.error;
|
||||
if (registered != state.runtimes.end())
|
||||
return "runtime profile identity is registered with different "
|
||||
"consensus limits or host policy";
|
||||
return "no runtime was registered or launched for this profile identity";
|
||||
}
|
||||
|
||||
QuickJSRuntimeHandle
|
||||
findQuickJSRuntime(artifact::View const& artifact)
|
||||
{
|
||||
if (artifact.kind != artifact::Kind::quickJSBytecode)
|
||||
return {};
|
||||
auto const key = keyFor(artifact);
|
||||
auto& state = registryState();
|
||||
{
|
||||
std::lock_guard lock{state.mutex};
|
||||
auto const position = state.runtimes.find(key);
|
||||
if (position != state.runtimes.end())
|
||||
return position->second;
|
||||
}
|
||||
// Not registered yet: a launched compile for this identity may still be
|
||||
// running. Wait outside the lock (the compile thread needs the lock to
|
||||
// publish), then answer from the settled registry state.
|
||||
if (auto pending = pendingFor(key))
|
||||
pending->settled.wait();
|
||||
std::lock_guard lock{state.mutex};
|
||||
auto const position = state.runtimes.find(key);
|
||||
return position == state.runtimes.end() ? QuickJSRuntimeHandle{}
|
||||
: position->second;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_TESTS
|
||||
std::optional<std::string>
|
||||
setQuickJSProviderForTests(ripple::Blob provider)
|
||||
{
|
||||
return registerQuickJSRuntime(currentQuickJSRuntimeProfile(), provider);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace hook
|
||||
282
src/xrpld/app/hook/detail/quickjs/QuickJSValidation.cpp
Normal file
282
src/xrpld/app/hook/detail/quickjs/QuickJSValidation.cpp
Normal file
@@ -0,0 +1,282 @@
|
||||
#include <xrpld/app/hook/QuickJSHookRuntime.h>
|
||||
#include <xrpld/app/hook/detail/QuickJSProviderProfile.h>
|
||||
#include <xrpld/app/hook/detail/quickjs/QuickJSProviderSession.h>
|
||||
#include <xrpl/basics/scope.h>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <wasmtime.h>
|
||||
|
||||
namespace hook {
|
||||
namespace {
|
||||
|
||||
std::optional<std::string>
|
||||
decodeModuleValidationResult(
|
||||
std::int32_t word,
|
||||
QuickJSModuleValidation& validation)
|
||||
{
|
||||
validation = {};
|
||||
if (word <= 0)
|
||||
return "QuickJS provider returned an invalid module-validation "
|
||||
"success word";
|
||||
|
||||
auto const value = static_cast<std::uint32_t>(word);
|
||||
if ((value & artifact::generated::moduleValidationReservedMask) != 0)
|
||||
return "QuickJS provider module-validation result has reserved bits";
|
||||
|
||||
auto const version =
|
||||
(value & artifact::generated::moduleValidationVersionMask) >>
|
||||
artifact::generated::moduleValidationVersionShift;
|
||||
if (version != artifact::generated::moduleValidationLayoutVersion)
|
||||
return "QuickJS provider module-validation result has an unsupported "
|
||||
"layout version";
|
||||
|
||||
auto const entries = value & artifact::generated::moduleValidationEntryMask;
|
||||
if ((entries & artifact::generated::moduleValidationMainBit) == 0)
|
||||
return "QuickJS provider module-validation result has no callable "
|
||||
"main entry";
|
||||
auto const mainOnly = artifact::generated::moduleValidationMainBit;
|
||||
auto const mainAndCallback =
|
||||
mainOnly | artifact::generated::moduleValidationCallbackBit;
|
||||
if (entries != mainOnly && entries != mainAndCallback)
|
||||
return "QuickJS provider module-validation result has invalid entry "
|
||||
"bits";
|
||||
|
||||
auto const profileCode =
|
||||
(value & artifact::generated::moduleValidationProfileMask) >>
|
||||
artifact::generated::moduleValidationProfileShift;
|
||||
if (profileCode == artifact::generated::xflArithmeticProfileNone)
|
||||
{
|
||||
validation.xflArithmeticProfile = artifact::XFLArithmeticProfile::none;
|
||||
}
|
||||
else if (
|
||||
profileCode == artifact::generated::xflArithmeticProfileXahauFloatV1)
|
||||
{
|
||||
validation.xflArithmeticProfile =
|
||||
artifact::XFLArithmeticProfile::xahauFloatV1;
|
||||
}
|
||||
else if (
|
||||
profileCode == artifact::generated::xflArithmeticProfileNearestEvenV1)
|
||||
{
|
||||
validation.xflArithmeticProfile =
|
||||
artifact::XFLArithmeticProfile::nearestEvenV1;
|
||||
}
|
||||
else
|
||||
return "QuickJS provider module-validation result has an unknown "
|
||||
"XFL arithmetic profile";
|
||||
validation.hasCallback =
|
||||
(entries & artifact::generated::moduleValidationCallbackBit) != 0;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::string>
|
||||
validate(
|
||||
QuickJSRuntimeHandle const& runtime,
|
||||
std::span<std::uint8_t const> bytecode,
|
||||
QuickJSModuleValidation& validation,
|
||||
std::uint64_t* invocationFuelConsumed)
|
||||
{
|
||||
validation = {};
|
||||
if (!runtime)
|
||||
return "QuickJS runtime profile is not registered";
|
||||
if (bytecode.empty() ||
|
||||
bytecode.size() >
|
||||
static_cast<std::size_t>(std::numeric_limits<std::int32_t>::max()))
|
||||
return "QuickJS Hook bytecode is empty or too large";
|
||||
|
||||
quickjs::ProviderStage stage = quickjs::ProviderStage::none;
|
||||
std::string detail;
|
||||
auto const failure = [&]() {
|
||||
return detail.empty() ? std::string{quickjs::providerStageName(stage)}
|
||||
: detail;
|
||||
};
|
||||
auto session =
|
||||
quickjs::ProviderSession::create(*runtime, nullptr, stage, detail);
|
||||
if (!session)
|
||||
return failure();
|
||||
if (!session->initialize(stage, detail) ||
|
||||
!session->resetInvocationFuel(stage, detail))
|
||||
return failure();
|
||||
|
||||
ripple::scope_exit recordFuel{[&] {
|
||||
if (invocationFuelConsumed)
|
||||
if (auto const consumed = session->invocationFuelConsumed())
|
||||
*invocationFuelConsumed = *consumed;
|
||||
}};
|
||||
|
||||
auto const pointer = session->allocateAndCopy(bytecode, stage, detail);
|
||||
if (!pointer)
|
||||
return failure();
|
||||
|
||||
wasmtime_val_t arguments[2] = {
|
||||
{.kind = WASMTIME_I32,
|
||||
.of = {.i32 = static_cast<std::int32_t>(*pointer)}},
|
||||
{.kind = WASMTIME_I32,
|
||||
.of = {.i32 = static_cast<std::int32_t>(bytecode.size())}}};
|
||||
wasmtime_val_t result[1];
|
||||
if (!session->callExport(
|
||||
"qjs_validate_hook_module", arguments, 2, result, 1, detail))
|
||||
return detail;
|
||||
if (result[0].kind != WASMTIME_I32)
|
||||
return "QuickJS provider export returned a non-i32";
|
||||
|
||||
auto const word = result[0].of.i32;
|
||||
if (word == artifact::generated::moduleValidationFailureSentinel)
|
||||
{
|
||||
detail = session->readDiagnostic();
|
||||
return detail.empty() ? "QuickJS Hook bytecode validation failed"
|
||||
: detail;
|
||||
}
|
||||
return decodeModuleValidationResult(word, validation);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::optional<std::string>
|
||||
validateQuickJSBytecode(
|
||||
QuickJSRuntimeHandle const& runtime,
|
||||
std::span<std::uint8_t const> bytecode,
|
||||
QuickJSModuleValidation& validation)
|
||||
{
|
||||
return validate(runtime, bytecode, validation, nullptr);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_TESTS
|
||||
QuickJSValidationForTests
|
||||
validateQuickJSBytecodeForTests(
|
||||
QuickJSRuntimeHandle const& runtime,
|
||||
std::span<std::uint8_t const> bytecode)
|
||||
{
|
||||
QuickJSValidationForTests result;
|
||||
result.error =
|
||||
validate(runtime, bytecode, result, &result.invocationFuelConsumed);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<std::string>
|
||||
decodeQuickJSModuleValidationForTests(
|
||||
std::int32_t word,
|
||||
QuickJSModuleValidation& validation)
|
||||
{
|
||||
return decodeModuleValidationResult(word, validation);
|
||||
}
|
||||
|
||||
QuickJSSessionCostForTests
|
||||
measureQuickJSSessionCostForTests(
|
||||
QuickJSRuntimeHandle const& runtime,
|
||||
std::span<std::uint8_t const> bytecode,
|
||||
std::size_t iterations)
|
||||
{
|
||||
using clock = std::chrono::steady_clock;
|
||||
auto const nanosSince = [](clock::time_point const& start) {
|
||||
return static_cast<std::uint64_t>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
clock::now() - start)
|
||||
.count());
|
||||
};
|
||||
auto const keepMin = [](std::uint64_t& slot, std::uint64_t sample) {
|
||||
slot = slot == 0 ? sample : std::min(slot, sample);
|
||||
};
|
||||
|
||||
QuickJSSessionCostForTests cost;
|
||||
if (!runtime)
|
||||
{
|
||||
cost.error = "QuickJS runtime profile is not registered";
|
||||
return cost;
|
||||
}
|
||||
for (std::size_t iteration = 0; iteration < iterations; ++iteration)
|
||||
{
|
||||
quickjs::ProviderStage stage = quickjs::ProviderStage::none;
|
||||
std::string detail;
|
||||
auto const failure = [&]() {
|
||||
return detail.empty()
|
||||
? std::string{quickjs::providerStageName(stage)}
|
||||
: detail;
|
||||
};
|
||||
|
||||
auto const createStart = clock::now();
|
||||
auto session =
|
||||
quickjs::ProviderSession::create(*runtime, nullptr, stage, detail);
|
||||
auto const createNanos = nanosSince(createStart);
|
||||
if (!session)
|
||||
{
|
||||
cost.error = failure();
|
||||
return cost;
|
||||
}
|
||||
|
||||
auto const initializeStart = clock::now();
|
||||
if (!session->initialize(stage, detail))
|
||||
{
|
||||
cost.error = failure();
|
||||
return cost;
|
||||
}
|
||||
auto const initializeNanos = nanosSince(initializeStart);
|
||||
if (auto const consumed = session->initializationFuelConsumed())
|
||||
cost.initializationFuelConsumed = *consumed;
|
||||
if (!session->resetInvocationFuel(stage, detail))
|
||||
{
|
||||
cost.error = failure();
|
||||
return cost;
|
||||
}
|
||||
|
||||
auto const validateStart = clock::now();
|
||||
auto const pointer = session->allocateAndCopy(bytecode, stage, detail);
|
||||
if (!pointer)
|
||||
{
|
||||
cost.error = failure();
|
||||
return cost;
|
||||
}
|
||||
wasmtime_val_t arguments[2] = {
|
||||
{.kind = WASMTIME_I32,
|
||||
.of = {.i32 = static_cast<std::int32_t>(*pointer)}},
|
||||
{.kind = WASMTIME_I32,
|
||||
.of = {.i32 = static_cast<std::int32_t>(bytecode.size())}}};
|
||||
wasmtime_val_t result[1];
|
||||
if (!session->callExport(
|
||||
"qjs_validate_hook_module", arguments, 2, result, 1, detail))
|
||||
{
|
||||
cost.error = detail;
|
||||
return cost;
|
||||
}
|
||||
if (result[0].kind != WASMTIME_I32)
|
||||
{
|
||||
cost.error = "QuickJS provider export returned a non-i32";
|
||||
return cost;
|
||||
}
|
||||
auto const validateNanos = nanosSince(validateStart);
|
||||
auto const word = result[0].of.i32;
|
||||
if (word == artifact::generated::moduleValidationFailureSentinel)
|
||||
{
|
||||
// A cost sample from a session that rejected the bytecode is not
|
||||
// the cost being measured.
|
||||
detail = session->readDiagnostic();
|
||||
cost.error = detail.empty()
|
||||
? "QuickJS Hook bytecode validation failed during measurement"
|
||||
: detail;
|
||||
return cost;
|
||||
}
|
||||
QuickJSModuleValidation validation;
|
||||
if (auto const decodeError =
|
||||
decodeModuleValidationResult(word, validation))
|
||||
{
|
||||
cost.error = *decodeError;
|
||||
return cost;
|
||||
}
|
||||
if (auto const consumed = session->invocationFuelConsumed())
|
||||
cost.invocationFuelConsumed = *consumed;
|
||||
|
||||
++cost.iterations;
|
||||
cost.createNanosTotal += createNanos;
|
||||
keepMin(cost.createNanosMin, createNanos);
|
||||
cost.initializeNanosTotal += initializeNanos;
|
||||
keepMin(cost.initializeNanosMin, initializeNanos);
|
||||
cost.validateNanosTotal += validateNanos;
|
||||
keepMin(cost.validateNanosMin, validateNanos);
|
||||
}
|
||||
return cost;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace hook
|
||||
@@ -16,6 +16,7 @@
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
#include <xrpld/app/hook/QuickJSHookRuntime.h>
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/app/main/DBInit.h>
|
||||
#include <xrpld/app/rdb/Vacuum.h>
|
||||
@@ -46,6 +47,7 @@
|
||||
#include <boost/process.hpp>
|
||||
#include <boost/program_options.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
@@ -833,7 +835,49 @@ run(int argc, char** argv)
|
||||
auto app = make_Application(
|
||||
std::move(config), std::move(logs), std::make_unique<TimeKeeper>());
|
||||
|
||||
if (!app->setup(vm))
|
||||
// Compile the sealed QuickJS provider on a background thread while
|
||||
// the application sets itself up, and await it before start: the
|
||||
// contract in QuickJSHookRuntime.h. A node that cannot register its
|
||||
// sealed provider must not stay up, or it disagrees with its peers on
|
||||
// every QuickJS artifact.
|
||||
auto const quickJSProfile = hook::currentQuickJSRuntimeProfile();
|
||||
auto const quickJSProvider = hook::embeddedQuickJSProvider();
|
||||
auto quickJSJournal = app->logs().journal("QuickJS");
|
||||
auto const quickJSLaunched = std::chrono::steady_clock::now();
|
||||
if (quickJSProvider.empty())
|
||||
{
|
||||
JLOG(quickJSJournal.fatal())
|
||||
<< "No QuickJS provider is embedded in this build";
|
||||
return -1;
|
||||
}
|
||||
if (auto const refused = hook::launchQuickJSRuntimeRegistration(
|
||||
quickJSProfile,
|
||||
Blob{quickJSProvider.begin(), quickJSProvider.end()}))
|
||||
{
|
||||
JLOG(quickJSJournal.fatal())
|
||||
<< "QuickJS provider registration refused: " << *refused;
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto const setupComplete = app->setup(vm);
|
||||
|
||||
if (auto const error =
|
||||
hook::awaitQuickJSRuntimeRegistration(quickJSProfile))
|
||||
{
|
||||
JLOG(quickJSJournal.fatal())
|
||||
<< "QuickJS provider registration failed: " << *error;
|
||||
return -1;
|
||||
}
|
||||
JLOG(quickJSJournal.info())
|
||||
<< "QuickJS provider registered: " << quickJSProvider.size()
|
||||
<< " bytes, runtime profile "
|
||||
<< strHex(quickJSProfile.runtimeProfile) << ", awaited "
|
||||
<< std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - quickJSLaunched)
|
||||
.count()
|
||||
<< " ms after launch";
|
||||
|
||||
if (!setupComplete)
|
||||
return -1;
|
||||
|
||||
// With our configuration parsed, ensure we have
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <xrpld/app/tx/detail/SetHook.h>
|
||||
|
||||
#include <xrpld/app/hook/QuickJSHookRuntime.h>
|
||||
#include <xrpld/app/hook/applyHook.h>
|
||||
#include <xrpld/app/ledger/Ledger.h>
|
||||
#include <xrpld/app/ledger/LedgerMaster.h>
|
||||
@@ -28,6 +29,7 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/hook/Enum.h>
|
||||
#include <xrpl/hook/Guard.h>
|
||||
#include <xrpl/hook/HookArtifact.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/STAccount.h>
|
||||
@@ -41,6 +43,7 @@
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <span>
|
||||
#include <stack>
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
@@ -58,6 +61,63 @@ namespace ripple {
|
||||
|
||||
using GrantKey = std::pair<uint256, std::optional<AccountID>>;
|
||||
|
||||
namespace {
|
||||
|
||||
enum class ArtifactInstallability {
|
||||
allowed,
|
||||
apiMismatch,
|
||||
amendmentDisabled,
|
||||
unsupportedProfile,
|
||||
};
|
||||
|
||||
ArtifactInstallability
|
||||
artifactInstallability(
|
||||
hook::artifact::View const& artifact,
|
||||
std::uint16_t declaredApiVersion,
|
||||
Rules const& rules)
|
||||
{
|
||||
if (artifact.hookApiVersion != declaredApiVersion)
|
||||
return ArtifactInstallability::apiMismatch;
|
||||
|
||||
switch (artifact.kind)
|
||||
{
|
||||
case hook::artifact::Kind::legacyWasm:
|
||||
return declaredApiVersion == 0
|
||||
? ArtifactInstallability::allowed
|
||||
: ArtifactInstallability::apiMismatch;
|
||||
|
||||
case hook::artifact::Kind::quickJSBytecode:
|
||||
if (declaredApiVersion != 1)
|
||||
return ArtifactInstallability::apiMismatch;
|
||||
if (!rules.enabled(featureJSHooks))
|
||||
return ArtifactInstallability::amendmentDisabled;
|
||||
// Consensus eligibility must not read the process-local registry.
|
||||
if (hook::artifact::isCurrentQuickJS(artifact))
|
||||
return ArtifactInstallability::allowed;
|
||||
return ArtifactInstallability::unsupportedProfile;
|
||||
}
|
||||
return ArtifactInstallability::unsupportedProfile;
|
||||
}
|
||||
|
||||
std::string_view
|
||||
toString(ArtifactInstallability result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case ArtifactInstallability::allowed:
|
||||
return "allowed";
|
||||
case ArtifactInstallability::apiMismatch:
|
||||
return "artifact and sfHookApiVersion disagree";
|
||||
case ArtifactInstallability::amendmentDisabled:
|
||||
return "JSHooks is disabled";
|
||||
case ArtifactInstallability::unsupportedProfile:
|
||||
return "QuickJS bytecode ABI or runtime profile is unsupported";
|
||||
}
|
||||
return "unknown artifact installation policy";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool
|
||||
validateHookGrants(SetHookCtx& ctx, STArray const& hookGrants)
|
||||
{
|
||||
@@ -445,9 +505,16 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
}
|
||||
|
||||
auto version = hookSetObj.getFieldU16(sfHookApiVersion);
|
||||
if (version != 0)
|
||||
auto const hasCreateCode = hookSetObj.isFieldPresent(sfCreateCode);
|
||||
Blob hook =
|
||||
hasCreateCode ? hookSetObj.getFieldVL(sfCreateCode) : Blob{};
|
||||
auto const artifact = hook::artifact::parse(makeSlice(hook));
|
||||
if (artifact &&
|
||||
artifact->kind == hook::artifact::Kind::legacyWasm &&
|
||||
version != 0)
|
||||
{
|
||||
// we currently only accept api version 0
|
||||
// Reject version != 0 before HookOn so tesMALFORMED codes stay
|
||||
// stable.
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::API_INVALID << ")[" << HS_ACC()
|
||||
<< "]: Malformed transaction: SetHook "
|
||||
@@ -524,10 +591,65 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
|
||||
// finally validate web assembly byte code
|
||||
{
|
||||
if (!hookSetObj.isFieldPresent(sfCreateCode))
|
||||
if (!hasCreateCode)
|
||||
return {};
|
||||
|
||||
Blob hook = hookSetObj.getFieldVL(sfCreateCode);
|
||||
if (!artifact)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::WASM_INVALID << ")["
|
||||
<< HS_ACC() << "]: Malformed Hook artifact: "
|
||||
<< hook::artifact::toString(artifact.error());
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const installability =
|
||||
artifactInstallability(*artifact, version, ctx.rules);
|
||||
if (installability != ArtifactInstallability::allowed)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::API_INVALID << ")["
|
||||
<< HS_ACC() << "]: Hook artifact cannot be installed: "
|
||||
<< toString(installability);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (artifact->kind == hook::artifact::Kind::quickJSBytecode)
|
||||
{
|
||||
auto const runtime = hook::findQuickJSRuntime(*artifact);
|
||||
hook::QuickJSModuleValidation validation;
|
||||
auto const validationError = hook::validateQuickJSBytecode(
|
||||
runtime,
|
||||
std::span{
|
||||
artifact->payload.data(), artifact->payload.size()},
|
||||
validation);
|
||||
if (validationError || !runtime)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::WASM_INVALID << ")["
|
||||
<< HS_ACC() << "]: Invalid QuickJS Hook bytecode: "
|
||||
<< (validationError ? *validationError
|
||||
: "runtime is not registered");
|
||||
return false;
|
||||
}
|
||||
if (validation.xflArithmeticProfile !=
|
||||
artifact->xflArithmeticProfile)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::WASM_INVALID << ")["
|
||||
<< HS_ACC()
|
||||
<< "]: QuickJS Hook XFL arithmetic profile "
|
||||
"disagrees between envelope and module";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bill the armed invocation-fuel ceiling; host-work
|
||||
// pricing is a later consensus change.
|
||||
auto const units =
|
||||
hook::currentQuickJSRuntimeProfile().invocationFuel;
|
||||
return std::pair<uint64_t, uint64_t>{
|
||||
units, validation.hasCallback ? units : 0};
|
||||
}
|
||||
|
||||
// RH NOTE: validateGuards has a generic non-rippled specific
|
||||
// interface so it can be used in other projects (i.e. tooling).
|
||||
@@ -594,7 +716,7 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
|
||||
std::optional<std::string> result2 =
|
||||
hook::HookExecutor::validateWasm(
|
||||
hook.data(), (size_t)hook.size());
|
||||
artifact->payload.data(), artifact->payload.size());
|
||||
|
||||
if (result2)
|
||||
{
|
||||
@@ -713,7 +835,8 @@ SetHook::preclaim(ripple::PreclaimContext const& ctx)
|
||||
|
||||
auto const& hash = hookSetObj.getFieldH256(sfHookHash);
|
||||
{
|
||||
if (!ctx.view.exists(keylet::hookDefinition(hash)))
|
||||
auto const definition = ctx.view.read(keylet::hookDefinition(hash));
|
||||
if (!definition)
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "HookSet(" << hook::log::HOOK_DEF_MISSING
|
||||
<< ")[" << HS_ACC()
|
||||
@@ -721,6 +844,31 @@ SetHook::preclaim(ripple::PreclaimContext const& ctx)
|
||||
"exists with the specified hash.";
|
||||
return terNO_HOOK;
|
||||
}
|
||||
|
||||
if (!definition->isFieldPresent(sfCreateCode) ||
|
||||
!definition->isFieldPresent(sfHookApiVersion))
|
||||
return tefBAD_LEDGER;
|
||||
|
||||
auto const code = definition->getFieldVL(sfCreateCode);
|
||||
auto const artifact = hook::artifact::parse(makeSlice(code));
|
||||
if (!artifact)
|
||||
return tefBAD_LEDGER;
|
||||
|
||||
auto const installability = artifactInstallability(
|
||||
*artifact,
|
||||
definition->getFieldU16(sfHookApiVersion),
|
||||
ctx.view.rules());
|
||||
if (installability == ArtifactInstallability::apiMismatch)
|
||||
return tefBAD_LEDGER;
|
||||
if (installability != ArtifactInstallability::allowed)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::API_INVALID << ")[" << HS_ACC()
|
||||
<< "]: HookHash targets a definition that is not "
|
||||
"installable: "
|
||||
<< toString(installability);
|
||||
return temDISABLED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -793,8 +941,8 @@ SetHook::preflight(PreflightContext const& ctx)
|
||||
}
|
||||
|
||||
if (hookSetObj.isFieldPresent(sfCreateCode) &&
|
||||
hookSetObj.getFieldVL(sfCreateCode).size() >
|
||||
hook::maxHookWasmSize())
|
||||
!hook::artifact::fitsCreateCodeSizeLimit(
|
||||
makeSlice(hookSetObj.getFieldVL(sfCreateCode))))
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::WASM_TOO_BIG << ")[" << HS_ACC()
|
||||
@@ -1307,7 +1455,6 @@ SetHook::setHook()
|
||||
.app = ctx_.app,
|
||||
.rules = ctx_.view().rules()};
|
||||
|
||||
const int blobMax = hook::maxHookWasmSize();
|
||||
auto const accountKeylet = keylet::account(account_);
|
||||
auto const hookKeylet = keylet::hook(account_);
|
||||
|
||||
@@ -1588,6 +1735,9 @@ SetHook::setHook()
|
||||
if (!oldDefSLE || !oldHook)
|
||||
return tecNO_ENTRY;
|
||||
|
||||
// Installed hooks may still be updated after their profile
|
||||
// stops accepting CREATE.
|
||||
|
||||
// initially carry over the prior non-array values, whatever
|
||||
// those were
|
||||
newHook.setFieldH256(
|
||||
@@ -1755,7 +1905,8 @@ SetHook::setHook()
|
||||
ripple::Blob wasmBytes =
|
||||
hookSetObj->get().getFieldVL(sfCreateCode);
|
||||
|
||||
if (wasmBytes.size() > blobMax)
|
||||
if (!hook::artifact::fitsCreateCodeSizeLimit(
|
||||
makeSlice(wasmBytes)))
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "HookSet(" << hook::log::WASM_TOO_BIG << ")["
|
||||
@@ -1933,6 +2084,22 @@ SetHook::setHook()
|
||||
return tecNO_ENTRY;
|
||||
}
|
||||
|
||||
// CREATE-dedup and hash INSTALL must see a still-installable
|
||||
// definition.
|
||||
if (!newDefSLE->isFieldPresent(sfCreateCode) ||
|
||||
!newDefSLE->isFieldPresent(sfHookApiVersion))
|
||||
return tefBAD_LEDGER;
|
||||
auto const definitionCode = newDefSLE->getFieldVL(sfCreateCode);
|
||||
auto const definitionArtifact =
|
||||
hook::artifact::parse(makeSlice(definitionCode));
|
||||
if (!definitionArtifact)
|
||||
return tefBAD_LEDGER;
|
||||
if (artifactInstallability(
|
||||
*definitionArtifact,
|
||||
newDefSLE->getFieldU16(sfHookApiVersion),
|
||||
view().rules()) != ArtifactInstallability::allowed)
|
||||
return tefBAD_LEDGER;
|
||||
|
||||
// decrement the hook definition and mark it for deletion if
|
||||
// appropriate
|
||||
if (oldDefSLE)
|
||||
|
||||
@@ -1412,6 +1412,7 @@ Transactor::executeHookChain(
|
||||
results.push_back(hook::apply(
|
||||
hookDef->getFieldH256(sfHookSetTxnID),
|
||||
hookHash,
|
||||
hookDef->getFieldU16(sfHookApiVersion),
|
||||
hookCanEmit,
|
||||
ns,
|
||||
hookDef->getFieldVL(sfCreateCode),
|
||||
@@ -1570,6 +1571,7 @@ Transactor::doHookCallback(
|
||||
hook::HookResult callbackResult = hook::apply(
|
||||
hookDef->getFieldH256(sfHookSetTxnID),
|
||||
callbackHookHash,
|
||||
hookDef->getFieldU16(sfHookApiVersion),
|
||||
hookCanEmit,
|
||||
ns,
|
||||
hookDef->getFieldVL(sfCreateCode),
|
||||
@@ -1877,6 +1879,7 @@ Transactor::doAgainAsWeak(
|
||||
hook::HookResult aawResult = hook::apply(
|
||||
hookDef->getFieldH256(sfHookSetTxnID),
|
||||
hookHash,
|
||||
hookDef->getFieldU16(sfHookApiVersion),
|
||||
hookCanEmit,
|
||||
ns,
|
||||
hookDef->getFieldVL(sfCreateCode),
|
||||
|
||||
Reference in New Issue
Block a user