mirror of
https://github.com/Xahau/xahaud.git
synced 2026-08-24 17:00:53 +00:00
Compare commits
61 Commits
reduce-mag
...
jshookz
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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' \
|
||||
..
|
||||
|
||||
1
.github/workflows/build-in-docker.yml
vendored
1
.github/workflows/build-in-docker.yml
vendored
@@ -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: |
|
||||
|
||||
236
.github/workflows/jshookz-e2e.yml
vendored
Normal file
236
.github/workflows/jshookz-e2e.yml
vendored
Normal file
@@ -0,0 +1,236 @@
|
||||
# End-to-end proof for the external jshookz toolchain and real Xahau Env.
|
||||
name: JSHookz end to end
|
||||
|
||||
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:
|
||||
JSHOOKZ_REF: xahaud-pinned
|
||||
HOOKZ_REF: 846f5f0f80861985dceb41ad6f02ff914f8a0c3d
|
||||
XAHAUD_SCRIPTS_REF: e41a712c68a613d95a8911cbf89711db2d95aa3b
|
||||
WASI_SDK_VERSION: "32"
|
||||
WASI_SDK_SHA256: 55fc523ebfbc98f69d1034fcfcb83d1ff5610cd9ab7eceef6cd097a30ba4ef93
|
||||
BINARYEN_VERSION: "128"
|
||||
BINARYEN_SHA256: 4ce79586d1c4762502eebe9a1db071fa5e446ef8897f2f766eb1cce5ec6dee9e
|
||||
WIZER_VERSION: "10.0.0"
|
||||
WIZER_SHA256: 1e9dfaa2f6c436ca7acfdbb82c166f5b8a0062068f12916a554e82c97ca8d371
|
||||
WASI_SDK_PATH: ${{ github.workspace }}/.tools/wasi-sdk
|
||||
HOOKZ_WASI_SDK: ${{ github.workspace }}/.tools/wasi-sdk
|
||||
BINARYEN_HOME: ${{ github.workspace }}/.tools/binaryen
|
||||
JSHOOKZ_PROVIDER_BUNDLE: ${{ github.workspace }}/.deps/jshookz/build/xahau-provider
|
||||
XAHAU_QJS_PROVIDER_WASM: ${{ github.workspace }}/.deps/jshookz/build/xahau-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 jshookz
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: sublimator/jshookz
|
||||
ref: ${{ env.JSHOOKZ_REF }}
|
||||
path: .deps/jshookz
|
||||
persist-credentials: false
|
||||
|
||||
- name: Check out public hookz
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: sublimator/hookz
|
||||
ref: ${{ env.HOOKZ_REF }}
|
||||
path: .deps/hookz
|
||||
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
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
|
||||
- name: Install native and TypeScript tools
|
||||
run: |
|
||||
apt-get install -y \
|
||||
build-essential ccache clang-format-18 cmake curl g++-13 gcc-13 \
|
||||
libsqlite3-dev ninja-build pkg-config xz-utils
|
||||
ln -sf /usr/bin/clang-format-18 /usr/local/bin/clang-format
|
||||
ln -sf /usr/bin/gcov-13 /usr/local/bin/gcov
|
||||
npm install --global typescript@6.0.3
|
||||
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 wasi-sdk and Binaryen
|
||||
run: |
|
||||
mkdir -p .tools
|
||||
curl --fail --location --retry 3 \
|
||||
--output /tmp/wasi-sdk.tar.gz \
|
||||
"https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_SDK_VERSION}/wasi-sdk-${WASI_SDK_VERSION}.0-x86_64-linux.tar.gz"
|
||||
echo "$WASI_SDK_SHA256 /tmp/wasi-sdk.tar.gz" | sha256sum --check
|
||||
tar -xzf /tmp/wasi-sdk.tar.gz -C .tools
|
||||
mv ".tools/wasi-sdk-${WASI_SDK_VERSION}.0-x86_64-linux" "$WASI_SDK_PATH"
|
||||
|
||||
curl --fail --location --retry 3 \
|
||||
--output /tmp/binaryen.tar.gz \
|
||||
"https://github.com/WebAssembly/binaryen/releases/download/version_${BINARYEN_VERSION}/binaryen-version_${BINARYEN_VERSION}-x86_64-linux.tar.gz"
|
||||
echo "$BINARYEN_SHA256 /tmp/binaryen.tar.gz" | sha256sum --check
|
||||
tar -xzf /tmp/binaryen.tar.gz -C .tools
|
||||
mv ".tools/binaryen-version_${BINARYEN_VERSION}" "$BINARYEN_HOME"
|
||||
echo "$BINARYEN_HOME/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Install Wizer
|
||||
run: |
|
||||
curl --fail --location --retry 3 \
|
||||
--output /tmp/wizer.tar.xz \
|
||||
"https://github.com/bytecodealliance/wizer/releases/download/v${WIZER_VERSION}/wizer-v${WIZER_VERSION}-x86_64-linux.tar.xz"
|
||||
echo "$WIZER_SHA256 /tmp/wizer.tar.xz" | sha256sum --check
|
||||
tar -xJf /tmp/wizer.tar.xz -C .tools
|
||||
echo "$GITHUB_WORKSPACE/.tools/wizer-v${WIZER_VERSION}-x86_64-linux" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Install the Hook toolchain
|
||||
run: |
|
||||
if [ -f .deps/jshookz/python/jshookz/pyproject.toml ]; then
|
||||
jshookz_project=.deps/jshookz/python/jshookz
|
||||
else
|
||||
jshookz_project=.deps/jshookz/packages/jshookz
|
||||
fi
|
||||
uv sync --project "$jshookz_project" \
|
||||
--locked --group dev --python 3.13
|
||||
uv tool install --python 3.13 --force --from .deps/hookz hookz
|
||||
uv tool install --python 3.13 --force \
|
||||
--from .deps/xahaud-scripts xahaud-scripts
|
||||
jshookz_cmd="$jshookz_project/.venv/bin/jshookz"
|
||||
echo "JSHOOKZ_CMD=$jshookz_cmd" >> "$GITHUB_ENV"
|
||||
echo "JSHOOKZ_HOOK_COMPILER=$jshookz_cmd compile-hook" >> "$GITHUB_ENV"
|
||||
"$jshookz_cmd" info
|
||||
hookz --version
|
||||
x-run-tests --help >/dev/null
|
||||
|
||||
- name: Build and verify the sealed provider
|
||||
run: |
|
||||
"$JSHOOKZ_CMD" build provider
|
||||
test -s "$XAHAU_QJS_PROVIDER_WASM"
|
||||
cmp external/quickjs-provider/jshookz_provider.manifest.json \
|
||||
"$JSHOOKZ_PROVIDER_BUNDLE/jshookz_provider.manifest.json"
|
||||
cmp external/quickjs-provider/jshookz_provider.manifest.cmake \
|
||||
"$JSHOOKZ_PROVIDER_BUNDLE/jshookz_provider.manifest.cmake"
|
||||
cmp external/quickjs-provider/jshookz_provider.native-abi.json \
|
||||
"$JSHOOKZ_PROVIDER_BUNDLE/jshookz_provider.native-abi.json"
|
||||
cp "$XAHAU_QJS_PROVIDER_WASM" \
|
||||
external/quickjs-provider/jshookz_provider.wasm
|
||||
|
||||
- name: Prove generator-lock mutation reds
|
||||
run: python3 cmake/test_generate_quickjs_provider_bundle.py
|
||||
|
||||
- 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 \
|
||||
--compile-hooks src/test/app/JSHooks_test.cpp \
|
||||
--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
|
||||
|
||||
- name: Verify the checked-in fixture header is current
|
||||
run: git diff --exit-code -- src/test/app/JSHooks_test_hooks.h
|
||||
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)
|
||||
|
||||
135
bin/update-jshookz-snapshot
Executable file
135
bin/update-jshookz-snapshot
Executable file
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build and import one committed jshookz provider snapshot into Xahau."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
SNAPSHOT_FILES = (
|
||||
"jshookz_provider.manifest.cmake",
|
||||
"jshookz_provider.manifest.json",
|
||||
"jshookz_provider.native-abi.json",
|
||||
"jshookz_provider.wasm",
|
||||
)
|
||||
|
||||
|
||||
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 main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description=(
|
||||
"Build a committed jshookz checkout and copy its sealed "
|
||||
"provider lock into Xahau. CI tracks jshookz main; the lock "
|
||||
"is what must match."
|
||||
)
|
||||
)
|
||||
parser.add_argument("jshookz_checkout", type=Path)
|
||||
parser.add_argument(
|
||||
"--no-build",
|
||||
action="store_true",
|
||||
help="import an already-built build/xahau-provider bundle",
|
||||
)
|
||||
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"
|
||||
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)
|
||||
|
||||
missing = [name for name in SNAPSHOT_FILES if not (bundle / name).is_file()]
|
||||
if missing:
|
||||
parser.error(f"incomplete provider bundle: {', '.join(missing)}")
|
||||
|
||||
for name in SNAPSHOT_FILES:
|
||||
shutil.copy2(bundle / name, destination / name)
|
||||
|
||||
manifest = json.loads((bundle / "jshookz_provider.manifest.json").read_text())
|
||||
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: {manifest['provider']['sha256']}")
|
||||
print(f"bytecode ABI: {manifest['bytecode_abi_id']}")
|
||||
print(f"runtime profile: {manifest['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 "
|
||||
"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())
|
||||
752
cmake/GenerateQuickJSProviderBundle.py
Normal file
752
cmake/GenerateQuickJSProviderBundle.py
Normal file
@@ -0,0 +1,752 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Project the sealed QuickJS bundle into one C++ translation unit."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
CHUNK = 64
|
||||
CMAKE_SET = re.compile(r'^set\(XAHAU_QUICKJS_([A-Z0-9_]+) "([^"]*)"\)\s*$')
|
||||
WASM_VALTYPE = {"i32", "i64"}
|
||||
WASM_BYTE = {"i32": "0x7f", "i64": "0x7e"}
|
||||
WASM_STACK_BYTES = 131072
|
||||
WASM_PAGE_BYTES = 65536
|
||||
PROVIDER_MEMORY_MINIMUM_PAGES = 6
|
||||
PROVIDER_MEMORY_MAXIMUM_PAGES = 512
|
||||
PROVIDER_MEMORY_MAX_BYTES = PROVIDER_MEMORY_MAXIMUM_PAGES * WASM_PAGE_BYTES
|
||||
SEALED_DECLARATION_SHA256 = (
|
||||
"56b4b2974b8a63a550721abd60350e392990762e76666f050b1a7c810e584957"
|
||||
)
|
||||
SEALED_SURFACE_SHA256 = (
|
||||
"b112346b95da74d04930ba86bd597e56a428e41e11581804ccc49907ae206eb0"
|
||||
)
|
||||
SEALED_OBJECT_LIMITS = {
|
||||
"serialized_object_max_bytes": 1048576,
|
||||
"serialized_object_max_fields": 32768,
|
||||
"serialized_object_max_scopes": 32769,
|
||||
"serialized_object_max_depth": 10,
|
||||
}
|
||||
IMPORT_KEYS = frozenset({"module", "name", "params", "results"})
|
||||
FUNCTION_EXPORT_KEYS = frozenset({"kind", "name", "params", "results"})
|
||||
MEMORY_EXPORT_KEYS = frozenset(
|
||||
{"kind", "name", "minimum_pages", "maximum_pages", "memory64", "shared"}
|
||||
)
|
||||
|
||||
|
||||
def _import_row(name: str, params: list[str], results: list[str]) -> dict[str, Any]:
|
||||
return {"module": "env", "name": name, "params": params, "results": results}
|
||||
|
||||
|
||||
def _function_export(name: str, params: list[str], results: list[str]) -> dict[str, Any]:
|
||||
return {"kind": "function", "name": name, "params": params, "results": results}
|
||||
|
||||
|
||||
SEALED_IMPORTS = [
|
||||
_import_row("accept", ["i32", "i32", "i64"], ["i64"]),
|
||||
_import_row("emit", ["i32", "i32", "i32", "i32"], ["i64"]),
|
||||
_import_row("etxn_reserve", ["i32"], ["i64"]),
|
||||
_import_row("hook_account", ["i32", "i32"], ["i64"]),
|
||||
_import_row("ledger_last_hash", ["i32", "i32"], ["i64"]),
|
||||
_import_row("ledger_last_time", [], ["i64"]),
|
||||
_import_row("ledger_seq", [], ["i64"]),
|
||||
_import_row("otxn_type", [], ["i64"]),
|
||||
_import_row("prepare", ["i32", "i32", "i32", "i32"], ["i64"]),
|
||||
_import_row("rollback", ["i32", "i32", "i64"], ["i64"]),
|
||||
_import_row("state", ["i32", "i32", "i32", "i32"], ["i64"]),
|
||||
_import_row("state_set", ["i32", "i32", "i32", "i32"], ["i64"]),
|
||||
_import_row("trace", ["i32", "i32", "i32", "i32", "i32"], ["i64"]),
|
||||
]
|
||||
SEALED_EXPORTS = [
|
||||
_function_export("_initialize", [], []),
|
||||
_function_export("free", ["i32"], []),
|
||||
_function_export("malloc", ["i32"], ["i32"]),
|
||||
{
|
||||
"kind": "memory",
|
||||
"maximum_pages": 512,
|
||||
"memory64": False,
|
||||
"minimum_pages": 6,
|
||||
"name": "memory",
|
||||
"shared": False,
|
||||
},
|
||||
_function_export("qjs_cbak", ["i32", "i32", "i32"], ["i32"]),
|
||||
_function_export("qjs_compile", ["i32", "i32"], ["i32"]),
|
||||
_function_export("qjs_compile_module", ["i32", "i32"], ["i32"]),
|
||||
_function_export("qjs_destroy", [], []),
|
||||
_function_export("qjs_enable_coverage", ["i32"], []),
|
||||
_function_export("qjs_eval", ["i32", "i32"], ["i32"]),
|
||||
_function_export("qjs_eval_bytecode", ["i32", "i32"], ["i32"]),
|
||||
_function_export("qjs_eval_module", ["i32", "i32"], ["i32"]),
|
||||
_function_export("qjs_get_bytecode_len", [], ["i32"]),
|
||||
_function_export("qjs_get_bytecode_ptr", [], ["i32"]),
|
||||
_function_export("qjs_get_result_len", [], ["i32"]),
|
||||
_function_export("qjs_get_result_ptr", [], ["i32"]),
|
||||
_function_export("qjs_hook", ["i32", "i32", "i32"], ["i32"]),
|
||||
_function_export("qjs_init", [], []),
|
||||
_function_export("qjs_set_max_stack_size", ["i32"], []),
|
||||
_function_export("qjs_set_memory_limit", ["i32"], []),
|
||||
_function_export("qjs_set_seed", ["i32"], []),
|
||||
_function_export("qjs_validate_hook_module", ["i32", "i32"], ["i32"]),
|
||||
]
|
||||
|
||||
|
||||
class LockError(ValueError):
|
||||
"""A sealed QuickJS lock identity failed a fail-closed check."""
|
||||
|
||||
|
||||
def quote(value: str) -> str:
|
||||
if not isinstance(value, str) or any(c in value for c in "\n\r"):
|
||||
raise LockError(f"invalid QuickJS policy string: {value!r}")
|
||||
return json.dumps(value)
|
||||
|
||||
|
||||
def parse_cmake(path: Path) -> dict[str, str]:
|
||||
values: dict[str, str] = {}
|
||||
for line in path.read_text().splitlines():
|
||||
match = CMAKE_SET.match(line)
|
||||
if match:
|
||||
values[match.group(1)] = match.group(2)
|
||||
return values
|
||||
|
||||
|
||||
def hex_bytes(value: str) -> str:
|
||||
if len(value) != 64 or any(c not in "0123456789abcdefABCDEF" for c in value):
|
||||
raise LockError(f"QuickJS identity must be 32 hexadecimal bytes: {value!r}")
|
||||
return ", ".join(f"0x{value[i:i + 2]}" for i in range(0, 64, 2))
|
||||
|
||||
|
||||
def require(cmake: dict[str, str], key: str) -> str:
|
||||
if key not in cmake:
|
||||
raise LockError(f"QuickJS cmake projection missing {key}")
|
||||
return cmake[key]
|
||||
|
||||
|
||||
def require_int(cmake: dict[str, str], key: str) -> int:
|
||||
return int(require(cmake, key))
|
||||
|
||||
|
||||
def require_hex(value: object, label: str) -> str:
|
||||
text = str(value).lower()
|
||||
if len(text) != 64 or any(c not in "0123456789abcdef" for c in text):
|
||||
raise LockError(f"{label} is not a SHA-256 hex digest: {value!r}")
|
||||
return text
|
||||
|
||||
|
||||
def cpp_bool(value: object) -> str:
|
||||
if value is True:
|
||||
return "true"
|
||||
if value is False:
|
||||
return "false"
|
||||
raise LockError(f"expected JSON boolean, got {value!r}")
|
||||
|
||||
|
||||
def join_types(values: object, label: str) -> str:
|
||||
if not isinstance(values, list) or not all(isinstance(item, str) for item in values):
|
||||
raise LockError(f"{label} is not a list of wasm value types: {values!r}")
|
||||
for item in values:
|
||||
if item not in WASM_VALTYPE:
|
||||
raise LockError(f"{label} has unsupported wasm value type {item!r}")
|
||||
return ",".join(values)
|
||||
|
||||
|
||||
def wasm_signature(params: object, results: object, label: str) -> list[str]:
|
||||
join_types(params, f"{label} params")
|
||||
join_types(results, f"{label} results")
|
||||
assert isinstance(params, list) and isinstance(results, list)
|
||||
return [WASM_BYTE[item] for item in results] + [WASM_BYTE[item] for item in params]
|
||||
|
||||
|
||||
def embed_body(data: bytes | None) -> str:
|
||||
if data is None:
|
||||
return (
|
||||
"std::span<std::uint8_t const>\n"
|
||||
"embeddedQuickJSProvider()\n"
|
||||
"{\n"
|
||||
" return {};\n"
|
||||
"}\n"
|
||||
)
|
||||
lines = []
|
||||
for offset in range(0, len(data), CHUNK):
|
||||
chunk = data[offset : offset + CHUNK]
|
||||
lines.append(' "' + "".join(f"\\x{b:02x}" for b in chunk) + '"')
|
||||
return (
|
||||
"namespace {\n"
|
||||
"\n"
|
||||
"constexpr char const sealedProvider[] =\n"
|
||||
+ "\n".join(lines)
|
||||
+ ";\n"
|
||||
"\n"
|
||||
"static_assert(\n"
|
||||
f" sizeof(sealedProvider) - 1 == {len(data)},\n"
|
||||
' "embedded provider size disagrees with its manifest");\n'
|
||||
"\n"
|
||||
"} // namespace\n"
|
||||
"\n"
|
||||
"std::span<std::uint8_t const>\n"
|
||||
"embeddedQuickJSProvider()\n"
|
||||
"{\n"
|
||||
" return {\n"
|
||||
" reinterpret_cast<std::uint8_t const*>(sealedProvider),\n"
|
||||
" sizeof(sealedProvider) - 1};\n"
|
||||
"}\n"
|
||||
)
|
||||
|
||||
|
||||
def format_export_row(item: dict[str, Any]) -> str:
|
||||
kind = item["kind"]
|
||||
name = item["name"]
|
||||
if kind == "memory":
|
||||
fields = (
|
||||
quote(kind),
|
||||
quote(name),
|
||||
quote(""),
|
||||
quote(""),
|
||||
f"{int(item['minimum_pages'])}U",
|
||||
f"{int(item['maximum_pages'])}U",
|
||||
cpp_bool(item["memory64"]),
|
||||
cpp_bool(item["shared"]),
|
||||
)
|
||||
elif kind == "function":
|
||||
fields = (
|
||||
quote(kind),
|
||||
quote(name),
|
||||
quote(join_types(item["params"], f"export {name} params")),
|
||||
quote(join_types(item["results"], f"export {name} results")),
|
||||
"0U",
|
||||
"0U",
|
||||
"false",
|
||||
"false",
|
||||
)
|
||||
else:
|
||||
raise LockError(f"unsupported provider export kind: {kind!r}")
|
||||
return "{" + ", ".join(fields) + "}"
|
||||
|
||||
|
||||
def require_typed_exports(exports: object, label: str) -> list[dict[str, Any]]:
|
||||
if not isinstance(exports, list) or not all(isinstance(item, dict) for item in exports):
|
||||
raise LockError(f"{label} must be a list of typed export rows")
|
||||
names: list[str] = []
|
||||
memory_rows: list[dict[str, Any]] = []
|
||||
for item in exports:
|
||||
kind = item.get("kind")
|
||||
name = item.get("name")
|
||||
if not isinstance(kind, str) or not isinstance(name, str) or not name:
|
||||
raise LockError(f"{label} row is missing kind/name: {item!r}")
|
||||
if kind == "function":
|
||||
if set(item) != FUNCTION_EXPORT_KEYS:
|
||||
raise LockError(f"{label} function {name} has extra or missing keys")
|
||||
join_types(item.get("params"), f"{label} {name} params")
|
||||
join_types(item.get("results"), f"{label} {name} results")
|
||||
elif kind == "memory":
|
||||
if set(item) != MEMORY_EXPORT_KEYS:
|
||||
raise LockError(f"{label} memory row has extra or missing keys")
|
||||
for key in ("minimum_pages", "maximum_pages"):
|
||||
if not isinstance(item.get(key), int):
|
||||
raise LockError(f"{label} memory row missing integer {key}")
|
||||
if item.get("memory64") is not False or item.get("shared") is not False:
|
||||
raise LockError(
|
||||
f"{label} memory row is not memory64=false / shared=false"
|
||||
)
|
||||
memory_rows.append(item)
|
||||
else:
|
||||
raise LockError(f"{label} has unsupported export kind {kind!r}")
|
||||
names.append(name)
|
||||
if len(names) != len(set(names)):
|
||||
raise LockError(f"{label} contains duplicate export names")
|
||||
if names != sorted(names):
|
||||
raise LockError(f"{label} must be normalized by export name")
|
||||
if len(memory_rows) != 1 or memory_rows[0].get("name") != "memory":
|
||||
raise LockError(f"{label} must export exactly one memory named memory")
|
||||
return exports
|
||||
|
||||
|
||||
def require_typed_imports(imports: object, label: str) -> list[dict[str, Any]]:
|
||||
if not isinstance(imports, list) or not all(isinstance(item, dict) for item in imports):
|
||||
raise LockError(f"{label} must be a list of typed import rows")
|
||||
names: list[str] = []
|
||||
for item in imports:
|
||||
module = item.get("module")
|
||||
name = item.get("name")
|
||||
if not isinstance(module, str) or not isinstance(name, str) or not name:
|
||||
raise LockError(f"{label} row is missing module/name: {item!r}")
|
||||
if set(item) != IMPORT_KEYS:
|
||||
raise LockError(f"{label} {name} has extra or missing keys")
|
||||
wasm_signature(item.get("params"), item.get("results"), f"{label} {name}")
|
||||
names.append(name)
|
||||
if len(names) != len(set(names)):
|
||||
raise LockError(f"{label} contains duplicate import names")
|
||||
return imports
|
||||
|
||||
|
||||
def require_build(build: object, label: str) -> dict[str, Any]:
|
||||
if not isinstance(build, dict):
|
||||
raise LockError(f"{label} is missing build metadata")
|
||||
stack = build.get("wasm_stack_bytes")
|
||||
memory = build.get("wasm_memory_max_bytes")
|
||||
if stack != WASM_STACK_BYTES:
|
||||
raise LockError(
|
||||
f"{label} wasm_stack_bytes is {stack!r}, expected {WASM_STACK_BYTES}"
|
||||
)
|
||||
if memory != PROVIDER_MEMORY_MAX_BYTES:
|
||||
raise LockError(
|
||||
f"{label} wasm_memory_max_bytes is {memory!r}, "
|
||||
f"expected {PROVIDER_MEMORY_MAX_BYTES}"
|
||||
)
|
||||
return build
|
||||
|
||||
|
||||
def cross_compare_int(cmake: dict[str, str], key: str, actual: object, label: str) -> int:
|
||||
expected = require_int(cmake, key)
|
||||
if actual != expected:
|
||||
raise LockError(
|
||||
f"{label} disagrees between JSON ({actual!r}) and CMake {key} ({expected!r})"
|
||||
)
|
||||
return expected
|
||||
|
||||
|
||||
def cross_compare_str(cmake: dict[str, str], key: str, actual: object, label: str) -> str:
|
||||
expected = require(cmake, key)
|
||||
if str(actual) != expected:
|
||||
raise LockError(
|
||||
f"{label} disagrees between JSON ({actual!r}) and CMake {key} ({expected!r})"
|
||||
)
|
||||
return expected
|
||||
|
||||
|
||||
def validate_lock(
|
||||
profile: dict[str, Any],
|
||||
native: dict[str, Any],
|
||||
cmake: dict[str, str],
|
||||
wasmtime_version: str,
|
||||
) -> tuple[list[dict[str, Any]], list[dict[str, Any]], dict[str, Any], str, str, int]:
|
||||
schema = require(cmake, "MANIFEST_SCHEMA")
|
||||
if schema != "xahau.quickjs.runtime-profile-lock.v1" or profile.get("schema") != schema:
|
||||
raise LockError(f"unsupported QuickJS provider manifest schema: {schema}")
|
||||
|
||||
expected_sha = require_hex(require(cmake, "PROVIDER_SHA256"), "CMake provider SHA-256")
|
||||
json_sha = require_hex(profile.get("provider", {}).get("sha256"), "JSON provider SHA-256")
|
||||
if json_sha != expected_sha:
|
||||
raise LockError("provider sha256 disagrees between JSON lock and cmake")
|
||||
expected_size = require_int(cmake, "PROVIDER_SIZE")
|
||||
json_size = profile.get("provider", {}).get("size")
|
||||
if json_size != expected_size:
|
||||
raise LockError("provider size disagrees between JSON lock and cmake")
|
||||
|
||||
bytecode = require_hex(require(cmake, "BYTECODE_ABI_ID"), "CMake bytecode ABI")
|
||||
if require_hex(profile.get("bytecode_abi_id"), "JSON bytecode ABI") != bytecode:
|
||||
raise LockError("bytecode ABI disagrees between JSON lock and cmake")
|
||||
runtime_profile = require_hex(
|
||||
require(cmake, "RUNTIME_PROFILE_ID"), "CMake runtime-profile ID"
|
||||
)
|
||||
if require_hex(profile.get("runtime_profile_id"), "JSON runtime-profile ID") != runtime_profile:
|
||||
raise LockError("runtime-profile ID disagrees between JSON lock and cmake")
|
||||
|
||||
pinned_wasmtime = require(cmake, "WASMTIME_VERSION")
|
||||
engine_version = profile.get("source", {}).get("engine", {}).get("version")
|
||||
if wasmtime_version != pinned_wasmtime or engine_version != pinned_wasmtime:
|
||||
raise LockError(
|
||||
f"QuickJS provider requires Wasmtime {pinned_wasmtime}, "
|
||||
f"but CMake resolved {wasmtime_version} and JSON has {engine_version!r}"
|
||||
)
|
||||
|
||||
limits = profile.get("source", {}).get("limits")
|
||||
if not isinstance(limits, dict):
|
||||
raise LockError("JSON runtime-profile source is missing limits")
|
||||
cross_compare_int(
|
||||
cmake, "SERIALIZED_OBJECT_MAX_BYTES", limits.get("serialized_object_max_bytes"),
|
||||
"serialized_object_max_bytes",
|
||||
)
|
||||
cross_compare_int(
|
||||
cmake, "SERIALIZED_OBJECT_MAX_FIELDS", limits.get("serialized_object_max_fields"),
|
||||
"serialized_object_max_fields",
|
||||
)
|
||||
cross_compare_int(
|
||||
cmake, "SERIALIZED_OBJECT_MAX_SCOPES", limits.get("serialized_object_max_scopes"),
|
||||
"serialized_object_max_scopes",
|
||||
)
|
||||
cross_compare_int(
|
||||
cmake, "SERIALIZED_OBJECT_MAX_DEPTH", limits.get("serialized_object_max_depth"),
|
||||
"serialized_object_max_depth",
|
||||
)
|
||||
for key, expected in SEALED_OBJECT_LIMITS.items():
|
||||
if limits.get(key) != expected:
|
||||
raise LockError(
|
||||
f"{key} disagrees with the sealed Receipt-A table: "
|
||||
f"{limits.get(key)!r} != {expected!r}"
|
||||
)
|
||||
cross_compare_int(cmake, "HEAP_BYTES", limits.get("quickjs_heap_bytes"), "quickjs_heap_bytes")
|
||||
cross_compare_int(cmake, "STACK_BYTES", limits.get("quickjs_stack_bytes"), "quickjs_stack_bytes")
|
||||
cross_compare_int(
|
||||
cmake, "INITIALIZATION_FUEL", limits.get("wasmtime_fuel_per_initialization"),
|
||||
"initialization fuel",
|
||||
)
|
||||
cross_compare_int(
|
||||
cmake, "INVOCATION_FUEL", limits.get("wasmtime_fuel_per_invocation"),
|
||||
"invocation fuel",
|
||||
)
|
||||
cross_compare_int(cmake, "HOST_WORK_BUDGET", limits.get("host_work_budget"), "host_work_budget")
|
||||
cross_compare_int(
|
||||
cmake, "HOST_WORK_BASE_PER_CALL", limits.get("host_work_base_per_call"),
|
||||
"host_work_base_per_call",
|
||||
)
|
||||
cross_compare_int(
|
||||
cmake, "HOST_WORK_PER_ADDRESSED_BYTE", limits.get("host_work_per_addressed_byte"),
|
||||
"host_work_per_addressed_byte",
|
||||
)
|
||||
cross_compare_str(cmake, "HOST_WORK_METER", limits.get("host_work_meter"), "host_work_meter")
|
||||
cross_compare_str(
|
||||
cmake, "HOST_ADAPTER_POLICY",
|
||||
profile.get("source", {}).get("execution", {}).get("host_adapter_policy"),
|
||||
"host_adapter_policy",
|
||||
)
|
||||
cross_compare_int(
|
||||
cmake, "HOOK_API_VERSION",
|
||||
profile.get("source", {}).get("artifact", {}).get("hook_api_version"),
|
||||
"hook_api_version",
|
||||
)
|
||||
|
||||
provider = profile.get("provider")
|
||||
source_provider = profile.get("source", {}).get("provider")
|
||||
if not isinstance(provider, dict) or not isinstance(source_provider, dict):
|
||||
raise LockError("JSON lock is missing provider or source.provider")
|
||||
require_build(provider.get("build"), "provider.build")
|
||||
require_build(source_provider.get("build"), "source.provider.build")
|
||||
|
||||
expected_import_count = require_int(cmake, "PROVIDER_IMPORT_COUNT")
|
||||
provider_imports = require_typed_imports(provider.get("imports"), "provider.imports")
|
||||
source_imports = require_typed_imports(
|
||||
source_provider.get("imports"), "source.provider.imports"
|
||||
)
|
||||
native_imports = native.get("selected")
|
||||
if not isinstance(native_imports, list):
|
||||
raise LockError("native ABI snapshot is missing selected imports")
|
||||
if provider_imports != source_imports:
|
||||
raise LockError("provider import signatures disagree between JSON copies")
|
||||
if provider_imports != SEALED_IMPORTS:
|
||||
raise LockError(
|
||||
"provider import signatures disagree with the sealed Receipt-A table"
|
||||
)
|
||||
if (
|
||||
len(provider_imports) != expected_import_count
|
||||
or len(native_imports) != expected_import_count
|
||||
):
|
||||
raise LockError("QuickJS provider/native ABI import counts differ")
|
||||
provider_names = [item["name"] for item in provider_imports]
|
||||
native_names = [item["name"] for item in native_imports]
|
||||
if len(set(provider_names)) != expected_import_count or set(provider_names) != set(
|
||||
native_names
|
||||
):
|
||||
raise LockError("QuickJS provider/native ABI import sets differ")
|
||||
native_by_name = {item["name"]: item for item in native_imports}
|
||||
for item in provider_imports:
|
||||
native_item = native_by_name[item["name"]]
|
||||
expected_sig = wasm_signature(
|
||||
item["params"], item["results"], f"import {item['name']}"
|
||||
)
|
||||
if native_item.get("wasm_signature") != expected_sig:
|
||||
raise LockError(
|
||||
f"import {item['name']} wasm signature disagrees between JSON and native ABI"
|
||||
)
|
||||
|
||||
expected_export_count = require_int(cmake, "PROVIDER_EXPORT_COUNT")
|
||||
provider_exports = require_typed_exports(provider.get("exports"), "provider.exports")
|
||||
allowed_exports = require_typed_exports(
|
||||
source_provider.get("allowed_exports"), "source.provider.allowed_exports"
|
||||
)
|
||||
if len(provider_exports) != expected_export_count:
|
||||
raise LockError("QuickJS provider export count disagrees with its CMake lock")
|
||||
if provider_exports != allowed_exports:
|
||||
raise LockError("provider export signatures disagree between JSON copies")
|
||||
memory = next(item for item in provider_exports if item["kind"] == "memory")
|
||||
if (
|
||||
int(memory["minimum_pages"]) != PROVIDER_MEMORY_MINIMUM_PAGES
|
||||
or int(memory["maximum_pages"]) != PROVIDER_MEMORY_MAXIMUM_PAGES
|
||||
or memory["memory64"] is not False
|
||||
or memory["shared"] is not False
|
||||
):
|
||||
raise LockError(
|
||||
"QuickJS provider memory shape is not min 6 / max 512 / "
|
||||
"memory64=false / shared=false"
|
||||
)
|
||||
if provider_exports != SEALED_EXPORTS:
|
||||
raise LockError(
|
||||
"provider export signatures disagree with the sealed Receipt-A table"
|
||||
)
|
||||
|
||||
surface = profile.get("javascript_surface")
|
||||
nested_surface = profile.get("source", {}).get("javascript_surface")
|
||||
if not isinstance(surface, dict) or not isinstance(nested_surface, dict):
|
||||
raise LockError("JSON lock is missing javascript_surface")
|
||||
declaration_sha = require_hex(
|
||||
surface.get("declaration_sha256"), "javascript surface declaration SHA-256"
|
||||
)
|
||||
surface_sha = require_hex(surface.get("sha256"), "javascript surface SHA-256")
|
||||
if declaration_sha != SEALED_DECLARATION_SHA256:
|
||||
raise LockError(
|
||||
"javascript surface declaration SHA-256 disagrees with the sealed "
|
||||
"Receipt-A table"
|
||||
)
|
||||
if surface_sha != SEALED_SURFACE_SHA256:
|
||||
raise LockError(
|
||||
"javascript surface SHA-256 disagrees with the sealed Receipt-A table"
|
||||
)
|
||||
for key in ("declaration", "manifest", "schema"):
|
||||
if surface.get(key) != nested_surface.get(key):
|
||||
raise LockError(f"javascript_surface.{key} disagrees between JSON copies")
|
||||
|
||||
return (
|
||||
provider_imports,
|
||||
provider_exports,
|
||||
native_imports,
|
||||
declaration_sha,
|
||||
surface_sha,
|
||||
expected_size,
|
||||
)
|
||||
|
||||
|
||||
def render_source(
|
||||
*,
|
||||
provenance: str,
|
||||
cmake: dict[str, str],
|
||||
native: dict[str, Any],
|
||||
provider_imports: list[dict[str, Any]],
|
||||
provider_exports: list[dict[str, Any]],
|
||||
native_imports: list[dict[str, Any]],
|
||||
declaration_sha: str,
|
||||
surface_sha: str,
|
||||
expected_sha: str,
|
||||
expected_size: int,
|
||||
wasm_bytes: bytes | None,
|
||||
actual_native: str,
|
||||
) -> str:
|
||||
provider_names = [item["name"] for item in provider_imports]
|
||||
provider_rows = ",\n ".join(
|
||||
"{"
|
||||
+ ", ".join(
|
||||
(
|
||||
quote(item["module"]),
|
||||
quote(item["name"]),
|
||||
quote(join_types(item["params"], f"import {item['name']} params")),
|
||||
quote(join_types(item["results"], f"import {item['name']} results")),
|
||||
)
|
||||
)
|
||||
+ "}"
|
||||
for item in provider_imports
|
||||
)
|
||||
native_rows = ",\n ".join(
|
||||
"{"
|
||||
+ ", ".join(
|
||||
(
|
||||
quote(item["name"]),
|
||||
quote(item["return_type"]),
|
||||
quote(",".join(item["param_types"])),
|
||||
quote(item["amendment"] or ""),
|
||||
)
|
||||
)
|
||||
+ "}"
|
||||
for item in native_imports
|
||||
)
|
||||
export_rows = ",\n ".join(format_export_row(item) for item in provider_exports)
|
||||
source = native["source"]
|
||||
memory = next(item for item in provider_exports if item["kind"] == "memory")
|
||||
return f"""// Generated by cmake/GenerateQuickJSProviderBundle.py; do not edit.
|
||||
// Source: {provenance}
|
||||
#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 = {{
|
||||
{hex_bytes(expected_sha)}}};
|
||||
std::array<std::uint8_t, 32> const bytecodeABI = {{
|
||||
{hex_bytes(require(cmake, "BYTECODE_ABI_ID"))}}};
|
||||
std::array<std::uint8_t, 32> const runtimeProfile = {{
|
||||
{hex_bytes(require(cmake, "RUNTIME_PROFILE_ID"))}}};
|
||||
std::size_t const providerSize = {expected_size};
|
||||
std::uint16_t const hookApiVersion = {require(cmake, "HOOK_API_VERSION")};
|
||||
std::uint64_t const initializationFuel = {require(cmake, "INITIALIZATION_FUEL")}ULL;
|
||||
std::uint64_t const invocationFuel = {require(cmake, "INVOCATION_FUEL")}ULL;
|
||||
std::string_view const hostWorkMeter = {quote(require(cmake, "HOST_WORK_METER"))};
|
||||
std::uint64_t const hostWorkBudget = {require(cmake, "HOST_WORK_BUDGET")}ULL;
|
||||
std::uint64_t const hostWorkBasePerCall = {require(cmake, "HOST_WORK_BASE_PER_CALL")}ULL;
|
||||
std::uint64_t const hostWorkPerAddressedByte =
|
||||
{require(cmake, "HOST_WORK_PER_ADDRESSED_BYTE")}ULL;
|
||||
std::string_view const hostAdapterPolicy = {quote(require(cmake, "HOST_ADAPTER_POLICY"))};
|
||||
std::uint32_t const heapBytes = {require(cmake, "HEAP_BYTES")}U;
|
||||
std::uint32_t const stackBytes = {require(cmake, "STACK_BYTES")}U;
|
||||
std::uint32_t const wasmStackBytes = {WASM_STACK_BYTES}U;
|
||||
std::uint32_t const serializedObjectMaxBytes =
|
||||
{require(cmake, "SERIALIZED_OBJECT_MAX_BYTES")}U;
|
||||
std::uint32_t const serializedObjectMaxFields =
|
||||
{require(cmake, "SERIALIZED_OBJECT_MAX_FIELDS")}U;
|
||||
std::uint32_t const serializedObjectMaxScopes =
|
||||
{require(cmake, "SERIALIZED_OBJECT_MAX_SCOPES")}U;
|
||||
std::uint32_t const serializedObjectMaxDepth =
|
||||
{require(cmake, "SERIALIZED_OBJECT_MAX_DEPTH")}U;
|
||||
std::uint32_t const providerMemoryMinimumPages = {int(memory["minimum_pages"])}U;
|
||||
std::uint32_t const providerMemoryMaximumPages = {int(memory["maximum_pages"])}U;
|
||||
bool const providerMemory64 = false;
|
||||
bool const providerMemoryShared = false;
|
||||
std::string_view const javascriptSurfaceDeclarationSHA256 =
|
||||
{quote(declaration_sha)};
|
||||
std::string_view const javascriptSurfaceSHA256 = {quote(surface_sha)};
|
||||
|
||||
namespace {{
|
||||
|
||||
std::string_view const providerImportNames[] = {{
|
||||
{", ".join(quote(name) for name in provider_names)}}};
|
||||
std::string_view const providerExportNames[] = {{
|
||||
{", ".join(quote(item["name"]) for item in provider_exports)}}};
|
||||
ProviderImportSignature const providerImportSignatureData[] = {{
|
||||
{provider_rows}}};
|
||||
ProviderExportSignature const providerExportSignatureData[] = {{
|
||||
{export_rows}}};
|
||||
NativeImportSignature const nativeImportSignatureData[] = {{
|
||||
{native_rows}}};
|
||||
|
||||
}} // 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 = {quote(source["repository"])};
|
||||
std::string_view const nativeABISourceCommit = {quote(source["commit"])};
|
||||
std::string_view const nativeABISourcePath = {quote(source["path"])};
|
||||
std::string_view const nativeABISHA256 = {quote(actual_native)};
|
||||
std::size_t const nativeABICatalogueCount = {source["macro_function_count"]};
|
||||
|
||||
}} // namespace hook::artifact::generated
|
||||
|
||||
namespace hook {{
|
||||
|
||||
{embed_body(wasm_bytes)}
|
||||
}} // namespace hook
|
||||
"""
|
||||
|
||||
|
||||
def project_bundle(bundle: Path, wasmtime_version: str, output: Path) -> int:
|
||||
profile_path = bundle / "jshookz_provider.manifest.json"
|
||||
cmake_path = bundle / "jshookz_provider.manifest.cmake"
|
||||
native_path = bundle / "jshookz_provider.native-abi.json"
|
||||
for path in (profile_path, cmake_path, native_path):
|
||||
if not path.is_file():
|
||||
print(f"missing sealed QuickJS bundle file: {path}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
cmake = parse_cmake(cmake_path)
|
||||
actual_manifest = hashlib.sha256(profile_path.read_bytes()).hexdigest()
|
||||
if actual_manifest != require(cmake, "MANIFEST_SHA256"):
|
||||
print(
|
||||
"QuickJS provider JSON manifest does not match its CMake projection",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
|
||||
actual_native = hashlib.sha256(native_path.read_bytes()).hexdigest()
|
||||
if actual_native != require(cmake, "NATIVE_ABI_SHA256"):
|
||||
print(
|
||||
"QuickJS native ABI snapshot does not match its sealed digest",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
|
||||
try:
|
||||
profile = json.loads(profile_path.read_text())
|
||||
native = json.loads(native_path.read_text())
|
||||
(
|
||||
provider_imports,
|
||||
provider_exports,
|
||||
native_imports,
|
||||
declaration_sha,
|
||||
surface_sha,
|
||||
expected_size,
|
||||
) = validate_lock(profile, native, cmake, wasmtime_version)
|
||||
except LockError as error:
|
||||
print(str(error), file=sys.stderr)
|
||||
return 1
|
||||
|
||||
wasm_name = require(cmake, "PROVIDER_FILE")
|
||||
wasm_path = bundle / wasm_name
|
||||
expected_sha = require(cmake, "PROVIDER_SHA256").lower()
|
||||
fingerprint = hashlib.sha256()
|
||||
for path in (profile_path, cmake_path, native_path, Path(__file__)):
|
||||
fingerprint.update(path.read_bytes())
|
||||
fingerprint.update(wasmtime_version.encode())
|
||||
wasm_bytes: bytes | None = None
|
||||
if wasm_path.is_file():
|
||||
wasm_bytes = wasm_path.read_bytes()
|
||||
digest = hashlib.sha256(wasm_bytes).hexdigest()
|
||||
if digest != expected_sha or len(wasm_bytes) != expected_size:
|
||||
print(
|
||||
"QuickJS provider WASM does not match its generated manifest",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
fingerprint.update(b"wasm")
|
||||
fingerprint.update(bytes.fromhex(digest))
|
||||
provenance = f"{wasm_path} sha256={digest} size={len(wasm_bytes)}"
|
||||
else:
|
||||
fingerprint.update(b"absent")
|
||||
provenance = "no provider binary was present in the bundle directory"
|
||||
|
||||
stamp_path = output.with_name(output.name + ".stamp")
|
||||
stamp = fingerprint.hexdigest()
|
||||
if output.is_file() and stamp_path.is_file() and stamp_path.read_text() == stamp:
|
||||
return 0
|
||||
|
||||
if wasm_bytes is None:
|
||||
print(
|
||||
f"No QuickJS provider WASM at {wasm_path}; nothing is embedded "
|
||||
"and the daemon will refuse to start",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
text = render_source(
|
||||
provenance=provenance,
|
||||
cmake=cmake,
|
||||
native=native,
|
||||
provider_imports=provider_imports,
|
||||
provider_exports=provider_exports,
|
||||
native_imports=native_imports,
|
||||
declaration_sha=declaration_sha,
|
||||
surface_sha=surface_sha,
|
||||
expected_sha=expected_sha,
|
||||
expected_size=expected_size,
|
||||
wasm_bytes=wasm_bytes,
|
||||
actual_native=actual_native,
|
||||
)
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_text(text)
|
||||
stamp_path.write_text(stamp)
|
||||
return 0
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--bundle-dir", type=Path, required=True)
|
||||
parser.add_argument("--wasmtime-version", required=True)
|
||||
parser.add_argument("--output", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
return project_bundle(args.bundle_dir, args.wasmtime_version, args.output)
|
||||
except LockError as error:
|
||||
print(str(error), file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
74
cmake/QuickJSProvider.cmake
Normal file
74
cmake/QuickJSProvider.cmake
Normal file
@@ -0,0 +1,74 @@
|
||||
set(XAHAU_QUICKJS_PROVIDER_BUNDLE_DIR
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/external/quickjs-provider"
|
||||
CACHE PATH
|
||||
"Directory containing the sealed QuickJS provider WASM and generated manifests")
|
||||
get_filename_component(XAHAU_QUICKJS_PROVIDER_BUNDLE_DIR
|
||||
"${XAHAU_QUICKJS_PROVIDER_BUNDLE_DIR}"
|
||||
ABSOLUTE
|
||||
BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
set(XAHAU_QUICKJS_PROVIDER_MANIFEST
|
||||
"${XAHAU_QUICKJS_PROVIDER_BUNDLE_DIR}/jshookz_provider.manifest.cmake")
|
||||
set(XAHAU_QUICKJS_PROVIDER_PROFILE_LOCK
|
||||
"${XAHAU_QUICKJS_PROVIDER_BUNDLE_DIR}/jshookz_provider.manifest.json")
|
||||
set(XAHAU_QUICKJS_NATIVE_ABI
|
||||
"${XAHAU_QUICKJS_PROVIDER_BUNDLE_DIR}/jshookz_provider.native-abi.json")
|
||||
|
||||
foreach(_bundle_file
|
||||
"${XAHAU_QUICKJS_PROVIDER_MANIFEST}"
|
||||
"${XAHAU_QUICKJS_PROVIDER_PROFILE_LOCK}"
|
||||
"${XAHAU_QUICKJS_NATIVE_ABI}")
|
||||
if(NOT EXISTS "${_bundle_file}")
|
||||
message(FATAL_ERROR
|
||||
"Missing sealed QuickJS provider bundle file: ${_bundle_file}")
|
||||
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()
|
||||
|
||||
find_program(XAHAU_QUICKJS_PYTHON NAMES python3 python)
|
||||
if(NOT XAHAU_QUICKJS_PYTHON)
|
||||
message(FATAL_ERROR
|
||||
"Python is required to project the sealed QuickJS provider bundle")
|
||||
endif()
|
||||
|
||||
set(XAHAU_QUICKJS_GENERATED_INCLUDE_DIR
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/generated")
|
||||
set(XAHAU_QUICKJS_PROVIDER_VALUES_SOURCE
|
||||
"${XAHAU_QUICKJS_GENERATED_INCLUDE_DIR}/xrpld/app/hook/detail/QuickJSProviderValues.cpp")
|
||||
set(XAHAU_QUICKJS_BUNDLE_SCRIPT
|
||||
"${CMAKE_CURRENT_LIST_DIR}/GenerateQuickJSProviderBundle.py")
|
||||
|
||||
# The gitignored wasm is not a CMake depend (ninja cannot name a missing
|
||||
# file). Refresh on every rippled build; the script no-ops when inputs
|
||||
# including wasm existence/digest are unchanged.
|
||||
add_custom_target(xahau_quickjs_refresh_provider
|
||||
COMMAND
|
||||
"${XAHAU_QUICKJS_PYTHON}"
|
||||
"${XAHAU_QUICKJS_BUNDLE_SCRIPT}"
|
||||
--bundle-dir "${XAHAU_QUICKJS_PROVIDER_BUNDLE_DIR}"
|
||||
--wasmtime-version "${wasmtime_VERSION_STRING}"
|
||||
--output "${XAHAU_QUICKJS_PROVIDER_VALUES_SOURCE}"
|
||||
BYPRODUCTS "${XAHAU_QUICKJS_PROVIDER_VALUES_SOURCE}"
|
||||
DEPENDS
|
||||
"${XAHAU_QUICKJS_PROVIDER_PROFILE_LOCK}"
|
||||
"${XAHAU_QUICKJS_PROVIDER_MANIFEST}"
|
||||
"${XAHAU_QUICKJS_NATIVE_ABI}"
|
||||
"${XAHAU_QUICKJS_BUNDLE_SCRIPT}"
|
||||
COMMENT "Projecting sealed QuickJS provider values"
|
||||
VERBATIM)
|
||||
|
||||
message(STATUS
|
||||
"QuickJS provider values: ${XAHAU_QUICKJS_PROVIDER_VALUES_SOURCE}")
|
||||
|
||||
add_custom_target(xahau_quickjs_lock_mutations
|
||||
COMMAND
|
||||
"${XAHAU_QUICKJS_PYTHON}"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/test_generate_quickjs_provider_bundle.py"
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
COMMENT "Proving sealed QuickJS lock mutation reds"
|
||||
VERBATIM)
|
||||
@@ -174,18 +174,106 @@ 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}")
|
||||
|
||||
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
|
||||
|
||||
1
cmake/deps/Wasmtime.cmake
Normal file
1
cmake/deps/Wasmtime.cmake
Normal file
@@ -0,0 +1 @@
|
||||
find_package(wasmtime REQUIRED)
|
||||
427
cmake/test_generate_quickjs_provider_bundle.py
Normal file
427
cmake/test_generate_quickjs_provider_bundle.py
Normal file
@@ -0,0 +1,427 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fail-closed generator/checker mutation reds for the sealed QuickJS lock."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SCRIPT = Path(__file__).with_name("GenerateQuickJSProviderBundle.py")
|
||||
BUNDLE = ROOT / "external" / "quickjs-provider"
|
||||
WASM_VERSION = "47.0.3"
|
||||
CMAKE_SET = re.compile(r'^set\(XAHAU_QUICKJS_([A-Z0-9_]+) "([^"]*)"\)\s*$')
|
||||
|
||||
PIN = {
|
||||
"provider_sha256": "40f9ac0203afa9296196c627bc94e669ad789322ddc1e4ab58ba0cf6d32a5e21",
|
||||
"provider_size": "1101461",
|
||||
"manifest_sha256": "dce4535b8c9063ee4244c8ac7781d8d17f73fa5443c9724476c4bdb0a5a77f4b",
|
||||
"runtime_profile_id": "60950ca8b6fe4dd2a35559367051998bc04f75e8aa41934dd4caad0de5a1c3ba",
|
||||
"declaration_sha256": "56b4b2974b8a63a550721abd60350e392990762e76666f050b1a7c810e584957",
|
||||
"surface_sha256": "b112346b95da74d04930ba86bd597e56a428e41e11581804ccc49907ae206eb0",
|
||||
"bytecode_abi": "75ea54f357d397c4b33899e495bb385dad975a43b1c4a7a2cead30474327d33e",
|
||||
"native_abi": "328ec938dcad875f3bdf25b8d779dd77f3571589818ca9271cb98c64c7018f99",
|
||||
"wasm_stack_bytes": "131072",
|
||||
}
|
||||
|
||||
TYPED_EXPORT_ROWS = (
|
||||
'{"function", "_initialize", "", "", 0U, 0U, false, false}',
|
||||
'{"function", "free", "i32", "", 0U, 0U, false, false}',
|
||||
'{"function", "malloc", "i32", "i32", 0U, 0U, false, false}',
|
||||
'{"memory", "memory", "", "", 6U, 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}',
|
||||
)
|
||||
|
||||
|
||||
def json_path(bundle: Path) -> Path:
|
||||
return bundle / "jshookz_provider.manifest.json"
|
||||
|
||||
|
||||
def cmake_path(bundle: Path) -> Path:
|
||||
return bundle / "jshookz_provider.manifest.cmake"
|
||||
|
||||
|
||||
def native_path(bundle: Path) -> Path:
|
||||
return bundle / "jshookz_provider.native-abi.json"
|
||||
|
||||
|
||||
def wasm_path(bundle: Path) -> Path:
|
||||
return bundle / "jshookz_provider.wasm"
|
||||
|
||||
|
||||
def load_json(bundle: Path) -> dict:
|
||||
return json.loads(json_path(bundle).read_text())
|
||||
|
||||
|
||||
def write_json(bundle: Path, data: dict) -> None:
|
||||
json_path(bundle).write_text(json.dumps(data, indent=2) + "\n")
|
||||
|
||||
|
||||
def set_cmake(bundle: Path, key: str, value: str) -> None:
|
||||
path = cmake_path(bundle)
|
||||
lines = []
|
||||
replaced = False
|
||||
for line in path.read_text().splitlines():
|
||||
match = CMAKE_SET.match(line)
|
||||
if match and match.group(1) == key:
|
||||
lines.append(f'set(XAHAU_QUICKJS_{key} "{value}")')
|
||||
replaced = True
|
||||
else:
|
||||
lines.append(line)
|
||||
if not replaced:
|
||||
raise AssertionError(f"CMake lock is missing {key}")
|
||||
path.write_text("\n".join(lines) + "\n")
|
||||
|
||||
|
||||
def rehash_manifest(bundle: Path) -> None:
|
||||
digest = hashlib.sha256(json_path(bundle).read_bytes()).hexdigest()
|
||||
set_cmake(bundle, "MANIFEST_SHA256", digest)
|
||||
|
||||
|
||||
def rehash_native(bundle: Path) -> None:
|
||||
digest = hashlib.sha256(native_path(bundle).read_bytes()).hexdigest()
|
||||
set_cmake(bundle, "NATIVE_ABI_SHA256", digest)
|
||||
|
||||
|
||||
def clone_bundle(root: Path) -> Path:
|
||||
dest = root / "bundle"
|
||||
shutil.copytree(BUNDLE, dest, ignore=shutil.ignore_patterns("README.md"))
|
||||
return dest
|
||||
|
||||
|
||||
def generate(bundle: Path, work: Path) -> tuple[int, str, str]:
|
||||
output = work / "QuickJSProviderValues.cpp"
|
||||
completed = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
str(SCRIPT),
|
||||
"--bundle-dir",
|
||||
str(bundle),
|
||||
"--wasmtime-version",
|
||||
WASM_VERSION,
|
||||
"--output",
|
||||
str(output),
|
||||
],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
cpp = output.read_text() if output.is_file() else ""
|
||||
return completed.returncode, completed.stderr, cpp
|
||||
|
||||
|
||||
def hex_bytes(value: str) -> str:
|
||||
return ", ".join(f"0x{value[i:i + 2]}" for i in range(0, 64, 2))
|
||||
|
||||
|
||||
def pin_holds(returncode: int, cpp: str) -> bool:
|
||||
if returncode != 0 or not cpp:
|
||||
return False
|
||||
required = [
|
||||
hex_bytes(PIN["provider_sha256"]),
|
||||
f"std::size_t const providerSize = {PIN['provider_size']};",
|
||||
hex_bytes(PIN["runtime_profile_id"]),
|
||||
f'"{PIN["declaration_sha256"]}"',
|
||||
f'"{PIN["surface_sha256"]}"',
|
||||
hex_bytes(PIN["bytecode_abi"]),
|
||||
f'"{PIN["native_abi"]}"',
|
||||
f"std::uint32_t const wasmStackBytes = {PIN['wasm_stack_bytes']}U;",
|
||||
"std::uint32_t const serializedObjectMaxBytes =\n 1048576U;",
|
||||
"std::uint32_t const serializedObjectMaxFields =\n 32768U;",
|
||||
"std::uint32_t const serializedObjectMaxScopes =\n 32769U;",
|
||||
"std::uint32_t const serializedObjectMaxDepth =\n 10U;",
|
||||
"constexpr char const sealedProvider[] =",
|
||||
]
|
||||
if any(item not in cpp for item in required):
|
||||
return False
|
||||
return all(row in cpp for row in TYPED_EXPORT_ROWS)
|
||||
|
||||
|
||||
class GenerateQuickJSProviderBundleTest(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
missing = [
|
||||
path.name
|
||||
for path in (
|
||||
json_path(BUNDLE),
|
||||
cmake_path(BUNDLE),
|
||||
native_path(BUNDLE),
|
||||
wasm_path(BUNDLE),
|
||||
SCRIPT,
|
||||
)
|
||||
if not path.is_file()
|
||||
]
|
||||
if missing:
|
||||
raise AssertionError(
|
||||
"sealed QuickJS bundle is incomplete: " + ", ".join(missing)
|
||||
)
|
||||
|
||||
def mutate(self, mutator) -> tuple[int, str, str]:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
work = Path(tmp)
|
||||
bundle = clone_bundle(work)
|
||||
mutator(bundle)
|
||||
return generate(bundle, work / "out")
|
||||
|
||||
def assert_generator_red(self, mutator, fragment: str) -> None:
|
||||
returncode, stderr, cpp = self.mutate(mutator)
|
||||
self.assertNotEqual(returncode, 0, stderr)
|
||||
self.assertIn(fragment, stderr)
|
||||
self.assertFalse(pin_holds(returncode, cpp), stderr)
|
||||
|
||||
def test_unmutated_lock_projects_pin_and_typed_exports(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
work = Path(tmp)
|
||||
returncode, stderr, cpp = generate(clone_bundle(work), work / "out")
|
||||
self.assertEqual(returncode, 0, stderr)
|
||||
self.assertTrue(pin_holds(returncode, cpp), stderr)
|
||||
self.assertEqual(cpp.count("ProviderExportSignature const"), 2)
|
||||
|
||||
def test_provider_bytes_mutation(self) -> None:
|
||||
def mutate(bundle: Path) -> None:
|
||||
path = wasm_path(bundle)
|
||||
data = bytearray(path.read_bytes())
|
||||
data[-1] ^= 0xFF
|
||||
path.write_bytes(data)
|
||||
|
||||
self.assert_generator_red(mutate, "WASM does not match")
|
||||
|
||||
def test_provider_size_mutation(self) -> None:
|
||||
def mutate(bundle: Path) -> None:
|
||||
set_cmake(bundle, "PROVIDER_SIZE", "1101460")
|
||||
|
||||
self.assert_generator_red(mutate, "provider size disagrees")
|
||||
|
||||
def test_provider_sha_mutation(self) -> None:
|
||||
def mutate(bundle: Path) -> None:
|
||||
set_cmake(
|
||||
bundle,
|
||||
"PROVIDER_SHA256",
|
||||
"00" + PIN["provider_sha256"][2:],
|
||||
)
|
||||
|
||||
self.assert_generator_red(mutate, "provider sha256 disagrees")
|
||||
|
||||
def test_manifest_sha_mutation(self) -> None:
|
||||
def mutate(bundle: Path) -> None:
|
||||
set_cmake(
|
||||
bundle,
|
||||
"MANIFEST_SHA256",
|
||||
"00" + PIN["manifest_sha256"][2:],
|
||||
)
|
||||
|
||||
self.assert_generator_red(mutate, "JSON manifest does not match")
|
||||
|
||||
def test_runtime_profile_id_mutation(self) -> None:
|
||||
def mutate(bundle: Path) -> None:
|
||||
set_cmake(
|
||||
bundle,
|
||||
"RUNTIME_PROFILE_ID",
|
||||
"00" + PIN["runtime_profile_id"][2:],
|
||||
)
|
||||
|
||||
self.assert_generator_red(mutate, "runtime-profile ID disagrees")
|
||||
|
||||
def test_declaration_sha_mutation(self) -> None:
|
||||
def mutate(bundle: Path) -> None:
|
||||
data = load_json(bundle)
|
||||
data["javascript_surface"]["declaration_sha256"] = (
|
||||
"00" + PIN["declaration_sha256"][2:]
|
||||
)
|
||||
write_json(bundle, data)
|
||||
rehash_manifest(bundle)
|
||||
|
||||
self.assert_generator_red(mutate, "declaration SHA-256 disagrees")
|
||||
|
||||
def test_surface_sha_mutation(self) -> None:
|
||||
def mutate(bundle: Path) -> None:
|
||||
data = load_json(bundle)
|
||||
data["javascript_surface"]["sha256"] = "00" + PIN["surface_sha256"][2:]
|
||||
write_json(bundle, data)
|
||||
rehash_manifest(bundle)
|
||||
|
||||
self.assert_generator_red(mutate, "surface SHA-256 disagrees")
|
||||
|
||||
def test_bytecode_abi_mutation(self) -> None:
|
||||
def mutate(bundle: Path) -> None:
|
||||
set_cmake(bundle, "BYTECODE_ABI_ID", "00" + PIN["bytecode_abi"][2:])
|
||||
|
||||
self.assert_generator_red(mutate, "bytecode ABI disagrees")
|
||||
|
||||
def test_native_abi_mutation(self) -> None:
|
||||
def mutate(bundle: Path) -> None:
|
||||
set_cmake(bundle, "NATIVE_ABI_SHA256", "00" + PIN["native_abi"][2:])
|
||||
|
||||
self.assert_generator_red(mutate, "native ABI snapshot does not match")
|
||||
|
||||
def test_import_signature_mutation(self) -> None:
|
||||
def mutate(bundle: Path) -> None:
|
||||
data = load_json(bundle)
|
||||
data["provider"]["imports"][0]["params"].append("i32")
|
||||
write_json(bundle, data)
|
||||
rehash_manifest(bundle)
|
||||
|
||||
self.assert_generator_red(mutate, "import signatures disagree")
|
||||
|
||||
def test_export_signature_mutation(self) -> None:
|
||||
def mutate(bundle: Path) -> None:
|
||||
data = load_json(bundle)
|
||||
for item in data["provider"]["exports"]:
|
||||
if item["name"] == "qjs_hook":
|
||||
item["params"].append("i32")
|
||||
break
|
||||
write_json(bundle, data)
|
||||
rehash_manifest(bundle)
|
||||
|
||||
self.assert_generator_red(mutate, "export signatures disagree")
|
||||
|
||||
def test_coordinated_export_signature_mutation(self) -> None:
|
||||
def mutate(bundle: Path) -> None:
|
||||
data = load_json(bundle)
|
||||
for collection in (
|
||||
data["provider"]["exports"],
|
||||
data["source"]["provider"]["allowed_exports"],
|
||||
):
|
||||
for item in collection:
|
||||
if item["name"] == "qjs_hook":
|
||||
item["params"].append("i32")
|
||||
write_json(bundle, data)
|
||||
rehash_manifest(bundle)
|
||||
|
||||
self.assert_generator_red(mutate, "sealed Receipt-A table")
|
||||
|
||||
def test_coordinated_import_module_mutation(self) -> None:
|
||||
def mutate(bundle: Path) -> None:
|
||||
data = load_json(bundle)
|
||||
for collection in (
|
||||
data["provider"]["imports"],
|
||||
data["source"]["provider"]["imports"],
|
||||
):
|
||||
for item in collection:
|
||||
if item["name"] == "accept":
|
||||
item["module"] = "env2"
|
||||
write_json(bundle, data)
|
||||
rehash_manifest(bundle)
|
||||
|
||||
self.assert_generator_red(mutate, "sealed Receipt-A table")
|
||||
|
||||
def test_export_extra_key_mutation(self) -> None:
|
||||
def mutate(bundle: Path) -> None:
|
||||
data = load_json(bundle)
|
||||
for collection in (
|
||||
data["provider"]["exports"],
|
||||
data["source"]["provider"]["allowed_exports"],
|
||||
):
|
||||
for item in collection:
|
||||
if item["name"] == "qjs_init":
|
||||
item["note"] = "extra"
|
||||
write_json(bundle, data)
|
||||
rehash_manifest(bundle)
|
||||
|
||||
self.assert_generator_red(mutate, "extra or missing keys")
|
||||
|
||||
def test_memory_shape_mutation(self) -> None:
|
||||
def mutate(bundle: Path) -> None:
|
||||
data = load_json(bundle)
|
||||
for item in data["provider"]["exports"]:
|
||||
if item["kind"] == "memory":
|
||||
item["minimum_pages"] = 7
|
||||
for item in data["source"]["provider"]["allowed_exports"]:
|
||||
if item["kind"] == "memory":
|
||||
item["minimum_pages"] = 7
|
||||
write_json(bundle, data)
|
||||
rehash_manifest(bundle)
|
||||
|
||||
self.assert_generator_red(mutate, "memory shape")
|
||||
|
||||
def test_serialized_object_max_bytes_mutation(self) -> None:
|
||||
def mutate(bundle: Path) -> None:
|
||||
set_cmake(bundle, "SERIALIZED_OBJECT_MAX_BYTES", "1048577")
|
||||
|
||||
self.assert_generator_red(mutate, "serialized_object_max_bytes")
|
||||
|
||||
def test_serialized_object_max_fields_mutation(self) -> None:
|
||||
def mutate(bundle: Path) -> None:
|
||||
set_cmake(bundle, "SERIALIZED_OBJECT_MAX_FIELDS", "32769")
|
||||
|
||||
self.assert_generator_red(mutate, "serialized_object_max_fields")
|
||||
|
||||
def test_serialized_object_max_scopes_mutation(self) -> None:
|
||||
def mutate(bundle: Path) -> None:
|
||||
set_cmake(bundle, "SERIALIZED_OBJECT_MAX_SCOPES", "32770")
|
||||
|
||||
self.assert_generator_red(mutate, "serialized_object_max_scopes")
|
||||
|
||||
def test_serialized_object_max_depth_mutation(self) -> None:
|
||||
def mutate(bundle: Path) -> None:
|
||||
set_cmake(bundle, "SERIALIZED_OBJECT_MAX_DEPTH", "11")
|
||||
|
||||
self.assert_generator_red(mutate, "serialized_object_max_depth")
|
||||
|
||||
def _coordinated_limit(self, json_key: str, cmake_key: str, value: int) -> None:
|
||||
def mutate(bundle: Path) -> None:
|
||||
data = load_json(bundle)
|
||||
data["source"]["limits"][json_key] = value
|
||||
write_json(bundle, data)
|
||||
rehash_manifest(bundle)
|
||||
set_cmake(bundle, cmake_key, str(value))
|
||||
|
||||
self.assert_generator_red(mutate, "sealed Receipt-A table")
|
||||
|
||||
def test_coordinated_serialized_object_max_bytes_mutation(self) -> None:
|
||||
self._coordinated_limit(
|
||||
"serialized_object_max_bytes", "SERIALIZED_OBJECT_MAX_BYTES", 1048578
|
||||
)
|
||||
|
||||
def test_coordinated_serialized_object_max_fields_mutation(self) -> None:
|
||||
self._coordinated_limit(
|
||||
"serialized_object_max_fields", "SERIALIZED_OBJECT_MAX_FIELDS", 1
|
||||
)
|
||||
|
||||
def test_coordinated_serialized_object_max_scopes_mutation(self) -> None:
|
||||
self._coordinated_limit(
|
||||
"serialized_object_max_scopes", "SERIALIZED_OBJECT_MAX_SCOPES", 1
|
||||
)
|
||||
|
||||
def test_coordinated_serialized_object_max_depth_mutation(self) -> None:
|
||||
self._coordinated_limit(
|
||||
"serialized_object_max_depth", "SERIALIZED_OBJECT_MAX_DEPTH", 1
|
||||
)
|
||||
|
||||
def test_wasm_stack_bytes_mutation(self) -> None:
|
||||
def mutate(bundle: Path) -> None:
|
||||
data = load_json(bundle)
|
||||
data["provider"]["build"]["wasm_stack_bytes"] = 65536
|
||||
data["source"]["provider"]["build"]["wasm_stack_bytes"] = 65536
|
||||
write_json(bundle, data)
|
||||
rehash_manifest(bundle)
|
||||
|
||||
self.assert_generator_red(mutate, "wasm_stack_bytes")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
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")
|
||||
|
||||
44
external/quickjs-provider/README.md
vendored
Normal file
44
external/quickjs-provider/README.md
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
# QuickJS provider manifest
|
||||
|
||||
These files are generated together by a jshookz source checkout:
|
||||
|
||||
```sh
|
||||
# Current layout (cpp/provider, python/jshookz). Older checkouts used
|
||||
# packages/jshookz; bin/update-jshookz-snapshot accepts either CLI path.
|
||||
uv sync --project python/jshookz --locked --group dev
|
||||
python/jshookz/.venv/bin/jshookz build provider
|
||||
```
|
||||
|
||||
Update this Xahau branch from a clean, committed jshookz checkout with one
|
||||
command:
|
||||
|
||||
```sh
|
||||
bin/update-jshookz-snapshot /path/to/jshookz
|
||||
```
|
||||
|
||||
That builds and imports the sealed lock files. CI checks out jshookz
|
||||
`main` and fails if `build provider` does not reproduce that lock. The
|
||||
importer then prints a self-contained `x-run-tests` command that
|
||||
regenerates the embedded Hook fixtures and verifies the cut.
|
||||
|
||||
`jshookz_provider.manifest.json` is the verified runtime-profile lock.
|
||||
`jshookz_provider.manifest.cmake` is its minimal CMake projection. Xahau checks
|
||||
the JSON hash before generating the C++ profile constants, and the runtime
|
||||
checks any supplied provider WASM against the projected size and SHA-256.
|
||||
|
||||
The provider binary is not vendored in this integration slice. Tests inject
|
||||
the exact built artifact. Configure needs python3 to project these lock files.
|
||||
A daemon whose embed is empty will not start.
|
||||
|
||||
For a local producer-to-consumer build, point Xahau straight at the emitted
|
||||
bundle instead of copying it:
|
||||
|
||||
```sh
|
||||
cmake -S . -B build \
|
||||
-DXAHAU_QUICKJS_PROVIDER_BUNDLE_DIR=/path/to/jshookz/build/xahau-provider
|
||||
```
|
||||
|
||||
A build-time generator writes `QuickJSProviderValues.cpp` from those
|
||||
locks and, if present, the gitignored wasm. Configure only checks that
|
||||
the three lock files exist. Changing the bundle rebuilds that one
|
||||
translation unit; hash, Wasmtime, and optional-wasm checks run then.
|
||||
27
external/quickjs-provider/jshookz_provider.manifest.cmake
generated
vendored
Normal file
27
external/quickjs-provider/jshookz_provider.manifest.cmake
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# Generated by jshookz build provider; do not edit.
|
||||
set(XAHAU_QUICKJS_MANIFEST_SCHEMA "xahau.quickjs.runtime-profile-lock.v1")
|
||||
set(XAHAU_QUICKJS_MANIFEST_SHA256 "dce4535b8c9063ee4244c8ac7781d8d17f73fa5443c9724476c4bdb0a5a77f4b")
|
||||
set(XAHAU_QUICKJS_PROVIDER_FILE "jshookz_provider.wasm")
|
||||
set(XAHAU_QUICKJS_PROVIDER_SHA256 "40f9ac0203afa9296196c627bc94e669ad789322ddc1e4ab58ba0cf6d32a5e21")
|
||||
set(XAHAU_QUICKJS_PROVIDER_SIZE "1101461")
|
||||
set(XAHAU_QUICKJS_PROVIDER_IMPORT_COUNT "13")
|
||||
set(XAHAU_QUICKJS_PROVIDER_EXPORT_COUNT "22")
|
||||
set(XAHAU_QUICKJS_NATIVE_ABI_FILE "jshookz_provider.native-abi.json")
|
||||
set(XAHAU_QUICKJS_NATIVE_ABI_SHA256 "328ec938dcad875f3bdf25b8d779dd77f3571589818ca9271cb98c64c7018f99")
|
||||
set(XAHAU_QUICKJS_BYTECODE_ABI_ID "75ea54f357d397c4b33899e495bb385dad975a43b1c4a7a2cead30474327d33e")
|
||||
set(XAHAU_QUICKJS_RUNTIME_PROFILE_ID "60950ca8b6fe4dd2a35559367051998bc04f75e8aa41934dd4caad0de5a1c3ba")
|
||||
set(XAHAU_QUICKJS_HOOK_API_VERSION "1")
|
||||
set(XAHAU_QUICKJS_WASMTIME_VERSION "47.0.3")
|
||||
set(XAHAU_QUICKJS_INITIALIZATION_FUEL "5000000")
|
||||
set(XAHAU_QUICKJS_INVOCATION_FUEL "50000000")
|
||||
set(XAHAU_QUICKJS_HOST_WORK_METER "base-plus-addressed-byte-v1")
|
||||
set(XAHAU_QUICKJS_HOST_WORK_BUDGET "1000000")
|
||||
set(XAHAU_QUICKJS_HOST_WORK_BASE_PER_CALL "1")
|
||||
set(XAHAU_QUICKJS_HOST_WORK_PER_ADDRESSED_BYTE "1")
|
||||
set(XAHAU_QUICKJS_HOST_ADAPTER_POLICY "xahau-raw-hook-host-v1")
|
||||
set(XAHAU_QUICKJS_HEAP_BYTES "16777216")
|
||||
set(XAHAU_QUICKJS_STACK_BYTES "65536")
|
||||
set(XAHAU_QUICKJS_SERIALIZED_OBJECT_MAX_BYTES "1048576")
|
||||
set(XAHAU_QUICKJS_SERIALIZED_OBJECT_MAX_FIELDS "32768")
|
||||
set(XAHAU_QUICKJS_SERIALIZED_OBJECT_MAX_SCOPES "32769")
|
||||
set(XAHAU_QUICKJS_SERIALIZED_OBJECT_MAX_DEPTH "10")
|
||||
854
external/quickjs-provider/jshookz_provider.manifest.json
generated
vendored
Normal file
854
external/quickjs-provider/jshookz_provider.manifest.json
generated
vendored
Normal file
@@ -0,0 +1,854 @@
|
||||
{
|
||||
"bytecode_abi_id": "75ea54f357d397c4b33899e495bb385dad975a43b1c4a7a2cead30474327d33e",
|
||||
"javascript_surface": {
|
||||
"declaration": "python/jshookz/src/jshookz/types/xahau-quickjs-v1.d.ts",
|
||||
"declaration_sha256": "56b4b2974b8a63a550721abd60350e392990762e76666f050b1a7c810e584957",
|
||||
"manifest": "python/jshookz/src/jshookz/types/xahau-quickjs-v1.surface.json",
|
||||
"schema": "jshookz.javascript-surface.v1",
|
||||
"sha256": "b112346b95da74d04930ba86bd597e56a428e41e11581804ccc49907ae206eb0"
|
||||
},
|
||||
"provider": {
|
||||
"build": {
|
||||
"wasm_memory_max_bytes": 33554432,
|
||||
"wasm_stack_bytes": 131072
|
||||
},
|
||||
"exports": [
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "_initialize",
|
||||
"params": [],
|
||||
"results": []
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "free",
|
||||
"params": [
|
||||
"i32"
|
||||
],
|
||||
"results": []
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "malloc",
|
||||
"params": [
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "memory",
|
||||
"maximum_pages": 512,
|
||||
"memory64": false,
|
||||
"minimum_pages": 6,
|
||||
"name": "memory",
|
||||
"shared": false
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_cbak",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_compile",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_compile_module",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_destroy",
|
||||
"params": [],
|
||||
"results": []
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_enable_coverage",
|
||||
"params": [
|
||||
"i32"
|
||||
],
|
||||
"results": []
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_eval",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_eval_bytecode",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_eval_module",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_get_bytecode_len",
|
||||
"params": [],
|
||||
"results": [
|
||||
"i32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_get_bytecode_ptr",
|
||||
"params": [],
|
||||
"results": [
|
||||
"i32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_get_result_len",
|
||||
"params": [],
|
||||
"results": [
|
||||
"i32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_get_result_ptr",
|
||||
"params": [],
|
||||
"results": [
|
||||
"i32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_hook",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_init",
|
||||
"params": [],
|
||||
"results": []
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_set_max_stack_size",
|
||||
"params": [
|
||||
"i32"
|
||||
],
|
||||
"results": []
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_set_memory_limit",
|
||||
"params": [
|
||||
"i32"
|
||||
],
|
||||
"results": []
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_set_seed",
|
||||
"params": [
|
||||
"i32"
|
||||
],
|
||||
"results": []
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_validate_hook_module",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i32"
|
||||
]
|
||||
}
|
||||
],
|
||||
"imports": [
|
||||
{
|
||||
"module": "env",
|
||||
"name": "accept",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32",
|
||||
"i64"
|
||||
],
|
||||
"results": [
|
||||
"i64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "env",
|
||||
"name": "emit",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32",
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "env",
|
||||
"name": "etxn_reserve",
|
||||
"params": [
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "env",
|
||||
"name": "hook_account",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "env",
|
||||
"name": "ledger_last_hash",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "env",
|
||||
"name": "ledger_last_time",
|
||||
"params": [],
|
||||
"results": [
|
||||
"i64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "env",
|
||||
"name": "ledger_seq",
|
||||
"params": [],
|
||||
"results": [
|
||||
"i64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "env",
|
||||
"name": "otxn_type",
|
||||
"params": [],
|
||||
"results": [
|
||||
"i64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "env",
|
||||
"name": "prepare",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32",
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "env",
|
||||
"name": "rollback",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32",
|
||||
"i64"
|
||||
],
|
||||
"results": [
|
||||
"i64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "env",
|
||||
"name": "state",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32",
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "env",
|
||||
"name": "state_set",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32",
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "env",
|
||||
"name": "trace",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32",
|
||||
"i32",
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"sha256": "40f9ac0203afa9296196c627bc94e669ad789322ddc1e4ab58ba0cf6d32a5e21",
|
||||
"size": 1101461
|
||||
},
|
||||
"runtime_profile_id": "60950ca8b6fe4dd2a35559367051998bc04f75e8aa41934dd4caad0de5a1c3ba",
|
||||
"schema": "xahau.quickjs.runtime-profile-lock.v1",
|
||||
"source": {
|
||||
"artifact": {
|
||||
"envelope_version": 1,
|
||||
"hook_api_version": 1,
|
||||
"kind": "quickjs-bytecode"
|
||||
},
|
||||
"bytecode_abi": {
|
||||
"entry_exports": {
|
||||
"callback": "optional-callable",
|
||||
"main": "required-callable"
|
||||
},
|
||||
"quickjs_bytecode_version": 5,
|
||||
"quickjs_version": "2025-09-13",
|
||||
"read_flags": [
|
||||
"JS_READ_OBJ_BYTECODE"
|
||||
],
|
||||
"root": "es-module",
|
||||
"write_flags": [
|
||||
"JS_WRITE_OBJ_BYTECODE"
|
||||
]
|
||||
},
|
||||
"engine": {
|
||||
"configuration": {
|
||||
"consume_fuel": true,
|
||||
"cranelift_nan_canonicalization": true,
|
||||
"wasi": false,
|
||||
"wasm_memory64": false,
|
||||
"wasm_multi_memory": false,
|
||||
"wasm_relaxed_simd": false,
|
||||
"wasm_tail_call": false,
|
||||
"wasm_threads": false
|
||||
},
|
||||
"implementation": "wasmtime-native-c-api",
|
||||
"module_lifecycle": "compile-once-fresh-store-and-instance-per-invocation",
|
||||
"version": "47.0.3"
|
||||
},
|
||||
"execution": {
|
||||
"host_adapter_policy": "xahau-raw-hook-host-v1",
|
||||
"host_work_exhaustion": "uncatchable-wasmtime-trap-to-wasm-error",
|
||||
"initialization": "finite-cold-qjs-init-budget-before-invocation-fuel-reset",
|
||||
"ordinary_host_status": "returned-as-i64-never-trapped",
|
||||
"pending_jobs_after_entry": "not-drained",
|
||||
"result_failure_policy": "rollback.onFail-preserves-coded-failure;rollback.requirePresent-requires-success-and-non-nullish-value;rollback.requireTruthy-requires-success-and-truthy-value;accept.unlessPresent-accepts-on-failure-or-nullish-value;accept.unlessTruthy-accepts-on-failure-or-falsy-value;guards-use-explicit-message-and-optional-code-default-zero",
|
||||
"sfHookInstructionCount": "initial-wasmtime-invocation-fuel-minus-remaining-fuel",
|
||||
"terminals": "record-hook-result-and-uncatchable-wasmtime-trap"
|
||||
},
|
||||
"javascript_surface": {
|
||||
"declaration": "python/jshookz/src/jshookz/types/xahau-quickjs-v1.d.ts",
|
||||
"manifest": "python/jshookz/src/jshookz/types/xahau-quickjs-v1.surface.json",
|
||||
"schema": "jshookz.javascript-surface.v1"
|
||||
},
|
||||
"limits": {
|
||||
"host_work_addressed_length_indices": {
|
||||
"accept": [
|
||||
1
|
||||
],
|
||||
"emit": [
|
||||
1,
|
||||
3
|
||||
],
|
||||
"hook_account": [
|
||||
1
|
||||
],
|
||||
"ledger_last_hash": [
|
||||
1
|
||||
],
|
||||
"prepare": [
|
||||
1,
|
||||
3
|
||||
],
|
||||
"rollback": [
|
||||
1
|
||||
],
|
||||
"state": [
|
||||
1,
|
||||
3
|
||||
],
|
||||
"state_set": [
|
||||
1,
|
||||
3
|
||||
],
|
||||
"trace": [
|
||||
1,
|
||||
3
|
||||
]
|
||||
},
|
||||
"host_work_base_per_call": 1,
|
||||
"host_work_budget": 1000000,
|
||||
"host_work_meter": "base-plus-addressed-byte-v1",
|
||||
"host_work_per_addressed_byte": 1,
|
||||
"quickjs_heap_bytes": 16777216,
|
||||
"quickjs_stack_bytes": 65536,
|
||||
"serialized_object_max_bytes": 1048576,
|
||||
"serialized_object_max_depth": 10,
|
||||
"serialized_object_max_fields": 32768,
|
||||
"serialized_object_max_scopes": 32769,
|
||||
"wasmtime_fuel_per_initialization": 5000000,
|
||||
"wasmtime_fuel_per_invocation": 50000000
|
||||
},
|
||||
"name": "xahau-quickjs-v1",
|
||||
"provider": {
|
||||
"allowed_exports": [
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "_initialize",
|
||||
"params": [],
|
||||
"results": []
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "free",
|
||||
"params": [
|
||||
"i32"
|
||||
],
|
||||
"results": []
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "malloc",
|
||||
"params": [
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "memory",
|
||||
"maximum_pages": 512,
|
||||
"memory64": false,
|
||||
"minimum_pages": 6,
|
||||
"name": "memory",
|
||||
"shared": false
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_cbak",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_compile",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_compile_module",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_destroy",
|
||||
"params": [],
|
||||
"results": []
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_enable_coverage",
|
||||
"params": [
|
||||
"i32"
|
||||
],
|
||||
"results": []
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_eval",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_eval_bytecode",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_eval_module",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_get_bytecode_len",
|
||||
"params": [],
|
||||
"results": [
|
||||
"i32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_get_bytecode_ptr",
|
||||
"params": [],
|
||||
"results": [
|
||||
"i32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_get_result_len",
|
||||
"params": [],
|
||||
"results": [
|
||||
"i32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_get_result_ptr",
|
||||
"params": [],
|
||||
"results": [
|
||||
"i32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_hook",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i32"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_init",
|
||||
"params": [],
|
||||
"results": []
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_set_max_stack_size",
|
||||
"params": [
|
||||
"i32"
|
||||
],
|
||||
"results": []
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_set_memory_limit",
|
||||
"params": [
|
||||
"i32"
|
||||
],
|
||||
"results": []
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_set_seed",
|
||||
"params": [
|
||||
"i32"
|
||||
],
|
||||
"results": []
|
||||
},
|
||||
{
|
||||
"kind": "function",
|
||||
"name": "qjs_validate_hook_module",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i32"
|
||||
]
|
||||
}
|
||||
],
|
||||
"build": {
|
||||
"c_flags": [
|
||||
"-fwrapv",
|
||||
"-funsigned-char",
|
||||
"-mllvm",
|
||||
"-wasm-enable-sjlj"
|
||||
],
|
||||
"cxx_flags": [
|
||||
"-fno-exceptions",
|
||||
"-fno-rtti",
|
||||
"-mllvm",
|
||||
"-wasm-enable-sjlj"
|
||||
],
|
||||
"optimization": "O2",
|
||||
"target": "wasm32-wasi",
|
||||
"wasi_sdk": "32",
|
||||
"wasm_memory_max_bytes": 33554432,
|
||||
"wasm_stack_bytes": 131072
|
||||
},
|
||||
"forbidden_import_modules": [
|
||||
"wasi_snapshot_preview1"
|
||||
],
|
||||
"imports": [
|
||||
{
|
||||
"module": "env",
|
||||
"name": "accept",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32",
|
||||
"i64"
|
||||
],
|
||||
"results": [
|
||||
"i64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "env",
|
||||
"name": "emit",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32",
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "env",
|
||||
"name": "etxn_reserve",
|
||||
"params": [
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "env",
|
||||
"name": "hook_account",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "env",
|
||||
"name": "ledger_last_hash",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "env",
|
||||
"name": "ledger_last_time",
|
||||
"params": [],
|
||||
"results": [
|
||||
"i64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "env",
|
||||
"name": "ledger_seq",
|
||||
"params": [],
|
||||
"results": [
|
||||
"i64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "env",
|
||||
"name": "otxn_type",
|
||||
"params": [],
|
||||
"results": [
|
||||
"i64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "env",
|
||||
"name": "prepare",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32",
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "env",
|
||||
"name": "rollback",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32",
|
||||
"i64"
|
||||
],
|
||||
"results": [
|
||||
"i64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "env",
|
||||
"name": "state",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32",
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "env",
|
||||
"name": "state_set",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32",
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"module": "env",
|
||||
"name": "trace",
|
||||
"params": [
|
||||
"i32",
|
||||
"i32",
|
||||
"i32",
|
||||
"i32",
|
||||
"i32"
|
||||
],
|
||||
"results": [
|
||||
"i64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"javascript": {
|
||||
"atomics": false,
|
||||
"date_now": "previous-ledger-close-ripple-epoch-to-unix-milliseconds",
|
||||
"dynamic_eval": "enabled-and-metered",
|
||||
"finalization_registry": false,
|
||||
"math_random": "absent-use-ledger-random-api",
|
||||
"number": "enabled",
|
||||
"promise": "required-for-module-evaluation-no-post-entry-job-drain",
|
||||
"shared_array_buffer": false,
|
||||
"timezone": "UTC",
|
||||
"weak_ref": false
|
||||
}
|
||||
},
|
||||
"schema": "xahau.quickjs.runtime-profile-source.v1"
|
||||
}
|
||||
}
|
||||
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
@@ -0,0 +1 @@
|
||||
{"selected":[{"amendment":null,"name":"accept","param_types":["uint32_t","uint32_t","int64_t"],"return_type":"int64_t","wasm_signature":["0x7e","0x7f","0x7f","0x7e"]},{"amendment":null,"name":"rollback","param_types":["uint32_t","uint32_t","int64_t"],"return_type":"int64_t","wasm_signature":["0x7e","0x7f","0x7f","0x7e"]},{"amendment":null,"name":"ledger_seq","param_types":[],"return_type":"int64_t","wasm_signature":["0x7e"]},{"amendment":null,"name":"ledger_last_time","param_types":[],"return_type":"int64_t","wasm_signature":["0x7e"]},{"amendment":null,"name":"ledger_last_hash","param_types":["uint32_t","uint32_t"],"return_type":"int64_t","wasm_signature":["0x7e","0x7f","0x7f"]},{"amendment":null,"name":"otxn_type","param_types":[],"return_type":"int64_t","wasm_signature":["0x7e"]},{"amendment":null,"name":"hook_account","param_types":["uint32_t","uint32_t"],"return_type":"int64_t","wasm_signature":["0x7e","0x7f","0x7f"]},{"amendment":null,"name":"trace","param_types":["uint32_t","uint32_t","uint32_t","uint32_t","uint32_t"],"return_type":"int64_t","wasm_signature":["0x7e","0x7f","0x7f","0x7f","0x7f","0x7f"]},{"amendment":null,"name":"state","param_types":["uint32_t","uint32_t","uint32_t","uint32_t"],"return_type":"int64_t","wasm_signature":["0x7e","0x7f","0x7f","0x7f","0x7f"]},{"amendment":null,"name":"state_set","param_types":["uint32_t","uint32_t","uint32_t","uint32_t"],"return_type":"int64_t","wasm_signature":["0x7e","0x7f","0x7f","0x7f","0x7f"]},{"amendment":"featureHooksUpdate2","name":"prepare","param_types":["uint32_t","uint32_t","uint32_t","uint32_t"],"return_type":"int64_t","wasm_signature":["0x7e","0x7f","0x7f","0x7f","0x7f"]},{"amendment":null,"name":"etxn_reserve","param_types":["uint32_t"],"return_type":"int64_t","wasm_signature":["0x7e","0x7f"]},{"amendment":null,"name":"emit","param_types":["uint32_t","uint32_t","uint32_t","uint32_t"],"return_type":"int64_t","wasm_signature":["0x7e","0x7f","0x7f","0x7f","0x7f"]}],"source":{"commit":"bb244ef7729503a0317bcff0f8fdaa93ca5cb7d2","macro_function_count":75,"path":"include/xrpl/hook/hook_api.macro","repository":"https://github.com/Xahau/xahaud"}}
|
||||
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();
|
||||
|
||||
|
||||
102
include/xrpl/hook/HookArtifact.h
Normal file
102
include/xrpl/hook/HookArtifact.h
Normal file
@@ -0,0 +1,102 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 maxCreateCodeSize = 65'535;
|
||||
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 Error : std::uint8_t {
|
||||
tooLarge,
|
||||
tooShort,
|
||||
unknownMagic,
|
||||
truncatedQuickJSHeader,
|
||||
unsupportedEnvelopeVersion,
|
||||
unsupportedArtifactKind,
|
||||
nonCanonicalHeaderSize,
|
||||
nonZeroReserved,
|
||||
emptyPayload,
|
||||
lengthMismatch,
|
||||
zeroBytecodeABI,
|
||||
zeroRuntimeProfile,
|
||||
};
|
||||
|
||||
struct View
|
||||
{
|
||||
Kind kind;
|
||||
std::uint16_t hookApiVersion;
|
||||
Identity bytecodeABI;
|
||||
Identity runtimeProfile;
|
||||
ripple::Slice payload;
|
||||
};
|
||||
|
||||
/** 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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
235
src/test/app/HookArtifact_test.cpp
Normal file
235
src/test/app/HookArtifact_test.cpp
Normal file
@@ -0,0 +1,235 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
Tests for the consensus-visible Hook deployment envelope.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/hook/detail/QuickJSProviderProfile.h>
|
||||
#include <xrpl/beast/unit_test.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", "", "", 6, 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::vector<std::uint8_t>
|
||||
quickJSArtifact(std::vector<std::uint8_t> const& payload = {'a', 'b', 'c'})
|
||||
{
|
||||
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 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(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 == 1'101'461);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::nativeABISHA256 ==
|
||||
"328ec938dcad875f3bdf25b8d779dd77f3571589818ca9271cb98c64c7018f"
|
||||
"99");
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::providerImports.size() == 13);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::providerExports.size() == 22);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::providerExportSignatures.size() ==
|
||||
22);
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::quickJSProviderMemoryMinimumPages == 6);
|
||||
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::javascriptSurfaceDeclarationSHA256 ==
|
||||
"56b4b2974b8a63a550721abd60350e392990762e76666f050b1a7c810e5849"
|
||||
"57");
|
||||
BEAST_EXPECT(
|
||||
hook::artifact::generated::javascriptSurfaceSHA256 ==
|
||||
"b112346b95da74d04930ba86bd597e56a428e41e11581804ccc49907ae206e"
|
||||
"b0");
|
||||
BEAST_EXPECT(parsed->payload.size() == 3);
|
||||
BEAST_EXPECT(parsed->payload[0] == 'a');
|
||||
}
|
||||
|
||||
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] = 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] = 1;
|
||||
expectError(corrupted, hook::artifact::Error::nonZeroReserved);
|
||||
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("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
|
||||
308
src/test/app/HookWasmtimeHostCatalogue_test.cpp
Normal file
308
src/test/app/HookWasmtimeHostCatalogue_test.cpp
Normal file
@@ -0,0 +1,308 @@
|
||||
#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 (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
|
||||
1163
src/test/app/JSHooks_test.cpp
Normal file
1163
src/test/app/JSHooks_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1743
src/test/app/JSHooks_test_hooks.h
generated
Normal file
1743
src/test/app/JSHooks_test_hooks.h
generated
Normal file
File diff suppressed because it is too large
Load Diff
319
src/test/app/QuickJSHostPolicy_test.cpp
Normal file
319
src/test/app/QuickJSHostPolicy_test.cpp
Normal file
@@ -0,0 +1,319 @@
|
||||
#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() == 13);
|
||||
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() == 62);
|
||||
std::size_t outsideV1 = 0;
|
||||
for (auto const name : currentNames)
|
||||
if (!v1Names.contains(name))
|
||||
++outsideV1;
|
||||
BEAST_EXPECT(outsideV1 == 62);
|
||||
|
||||
testcase("Provider manifest has exact v1 Wasm signatures");
|
||||
BEAST_EXPECT(generated::providerImportSignatures.size() == 13);
|
||||
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() == 13);
|
||||
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 : 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};
|
||||
}
|
||||
return std::uint64_t{0};
|
||||
}();
|
||||
BEAST_EXPECT(
|
||||
declaredHostWork(
|
||||
descriptor.measure,
|
||||
std::span{arguments.data(), descriptor.parameterCount}) ==
|
||||
expected);
|
||||
}
|
||||
auto profile = hook::currentQuickJSRuntimeProfile();
|
||||
BEAST_EXPECT(quickJSHostWorkCost(profile, 0) == 1);
|
||||
BEAST_EXPECT(quickJSHostWorkCost(profile, 18) == 19);
|
||||
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
|
||||
198
src/xrpld/app/hook/QuickJSHookRuntime.h
Normal file
198
src/xrpld/app/hook/QuickJSHookRuntime.h
Normal file
@@ -0,0 +1,198 @@
|
||||
#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>
|
||||
|
||||
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);
|
||||
|
||||
/** 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 whether initialized cbak is
|
||||
callable.
|
||||
*/
|
||||
std::optional<std::string>
|
||||
validateQuickJSBytecode(
|
||||
QuickJSRuntimeHandle const& runtime,
|
||||
std::span<std::uint8_t const> bytecode,
|
||||
bool& hasCallback);
|
||||
|
||||
/** 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
|
||||
struct QuickJSValidationForTests
|
||||
{
|
||||
std::optional<std::string> error;
|
||||
bool hasCallback = false;
|
||||
std::uint64_t invocationFuelConsumed = 0;
|
||||
};
|
||||
|
||||
/** 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,
|
||||
|
||||
182
src/xrpld/app/hook/detail/HookArtifact.cpp
Normal file
182
src/xrpld/app/hook/detail/HookArtifact.cpp
Normal file
@@ -0,0 +1,182 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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;
|
||||
}
|
||||
|
||||
ripple::Expected<View, Error>
|
||||
parse(ripple::Slice code) noexcept
|
||||
{
|
||||
if (code.size() > maxCreateCodeSize)
|
||||
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,
|
||||
.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);
|
||||
if (readU16BE(bytes + 10) != 0)
|
||||
return ripple::Unexpected(Error::nonZeroReserved);
|
||||
|
||||
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),
|
||||
.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 v1 header size is non-canonical";
|
||||
case Error::nonZeroReserved:
|
||||
return "QuickJS v1 reserved bytes are nonzero";
|
||||
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
|
||||
79
src/xrpld/app/hook/detail/QuickJSProviderProfile.h
Normal file
79
src/xrpld/app/hook/detail/QuickJSProviderProfile.h
Normal file
@@ -0,0 +1,79 @@
|
||||
#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::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 javascriptSurfaceDeclarationSHA256;
|
||||
extern std::string_view const javascriptSurfaceSHA256;
|
||||
|
||||
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();
|
||||
|
||||
284
src/xrpld/app/hook/detail/quickjs/QuickJSHostCall.cpp
Normal file
284
src/xrpld/app/hook/detail/quickjs/QuickJSHostCall.cpp
Normal file
@@ -0,0 +1,284 @@
|
||||
#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 {
|
||||
|
||||
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_)
|
||||
, hostWorkRemaining(profile_.hostWorkBudget)
|
||||
{
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
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};
|
||||
if (binding->charging == WasmtimeHostBinding::Charging::quickJSV1 &&
|
||||
!call.charge(declaredHostWork(
|
||||
binding->measure, std::span{args, argumentCount})))
|
||||
return callbackTrap("Xahau Hook host-work budget exhausted");
|
||||
|
||||
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);
|
||||
return finishCallback(
|
||||
*invocation, *binding, status, output, results, resultCount);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return callbackTrap("uncaught Xahau Hook host exception");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace hook::quickjs
|
||||
90
src/xrpld/app/hook/detail/quickjs/QuickJSHostCall.h
Normal file
90
src/xrpld/app/hook/detail/quickjs/QuickJSHostCall.h
Normal file
@@ -0,0 +1,90 @@
|
||||
#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;
|
||||
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
|
||||
68
src/xrpld/app/hook/detail/quickjs/QuickJSHostPolicy.cpp
Normal file
68
src/xrpld/app/hook/detail/quickjs/QuickJSHostPolicy.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
#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::otxn_type>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::hook_account>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::trace>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::state>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::state_set>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::prepare>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::etxn_reserve>(),
|
||||
makeV1Descriptor<QuickJSV1ImportId::emit>()};
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::span<QuickJSV1ImportDescriptor const>
|
||||
quickJSHostPolicyV1Snapshot() noexcept
|
||||
{
|
||||
return v1SnapshotStorage();
|
||||
}
|
||||
|
||||
} // namespace hook::quickjs
|
||||
208
src/xrpld/app/hook/detail/quickjs/QuickJSHostPolicy.h
Normal file
208
src/xrpld/app/hook/detail/quickjs/QuickJSHostPolicy.h
Normal file
@@ -0,0 +1,208 @@
|
||||
#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;
|
||||
|
||||
/** Immutable import identity for the retained xahau-raw-hook-host-v1 policy.
|
||||
|
||||
This list is deliberately separate from the live 75-entry Hook catalogue.
|
||||
Once the profile is activated, later catalogue growth or reordering must
|
||||
not alter the policy selected by its retained runtime profile.
|
||||
*/
|
||||
enum class QuickJSV1ImportId : std::uint8_t {
|
||||
accept,
|
||||
rollback,
|
||||
ledger_seq,
|
||||
ledger_last_time,
|
||||
ledger_last_hash,
|
||||
otxn_type,
|
||||
hook_account,
|
||||
trace,
|
||||
state,
|
||||
state_set,
|
||||
prepare,
|
||||
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
|
||||
};
|
||||
|
||||
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(
|
||||
otxn_type,
|
||||
std::int64_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(
|
||||
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_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_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
|
||||
207
src/xrpld/app/hook/detail/quickjs/QuickJSValidation.cpp
Normal file
207
src/xrpld/app/hook/detail/quickjs/QuickJSValidation.cpp
Normal file
@@ -0,0 +1,207 @@
|
||||
#include <xrpld/app/hook/QuickJSHookRuntime.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>
|
||||
validate(
|
||||
QuickJSRuntimeHandle const& runtime,
|
||||
std::span<std::uint8_t const> bytecode,
|
||||
bool& hasCallback,
|
||||
std::uint64_t* invocationFuelConsumed)
|
||||
{
|
||||
hasCallback = false;
|
||||
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 flags = result[0].of.i32;
|
||||
if (flags != 1 && flags != 3)
|
||||
{
|
||||
detail = session->readDiagnostic();
|
||||
return detail.empty() ? "QuickJS Hook bytecode validation failed"
|
||||
: detail;
|
||||
}
|
||||
hasCallback = (flags & 2) != 0;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::optional<std::string>
|
||||
validateQuickJSBytecode(
|
||||
QuickJSRuntimeHandle const& runtime,
|
||||
std::span<std::uint8_t const> bytecode,
|
||||
bool& hasCallback)
|
||||
{
|
||||
return validate(runtime, bytecode, hasCallback, nullptr);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_TESTS
|
||||
QuickJSValidationForTests
|
||||
validateQuickJSBytecodeForTests(
|
||||
QuickJSRuntimeHandle const& runtime,
|
||||
std::span<std::uint8_t const> bytecode)
|
||||
{
|
||||
QuickJSValidationForTests result;
|
||||
result.error = validate(
|
||||
runtime, bytecode, result.hasCallback, &result.invocationFuelConsumed);
|
||||
return result;
|
||||
}
|
||||
|
||||
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);
|
||||
if (result[0].of.i32 != 1 && result[0].of.i32 != 3)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
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,55 @@ 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);
|
||||
bool hasCallback = false;
|
||||
auto const validationError = hook::validateQuickJSBytecode(
|
||||
runtime,
|
||||
std::span{
|
||||
artifact->payload.data(), artifact->payload.size()},
|
||||
hasCallback);
|
||||
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;
|
||||
}
|
||||
|
||||
// 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, 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 +706,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 +825,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 +834,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1588,6 +1726,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(
|
||||
@@ -1933,6 +2074,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