Compare commits

...

59 Commits

Author SHA1 Message Date
Nicholas Dudfield
6d6f011b5b fix(hooks): fail closed on sealed Receipt-A lock tables
Generator now rejects declaration/surface hashes, the 22 typed exports,
the 13 typed imports including module, and the four object limits unless
they match the immutable Receipt-A expectation. Coordinated JSON and
JSON+CMake mutations go red in the Python suite, which is also a CMake
target for the focused consumer gate.
2026-08-24 13:34:31 +07:00
Nicholas Dudfield
8e7a7f48f9 ci(hooks): feed JSHookz e2e from xahaud-pinned
Point JSHOOKZ_REF at the dedicated 78de1c4 convenience branch. Tracked
manifest/native locks remain the authority; no expected-commit check.
2026-08-24 13:27:14 +07:00
Nicholas Dudfield
07962562f6 test(hooks): project typed exports and fail-closed lock mutations
Project all 22 typed QuickJS provider export rows (kind/name/params/results
plus the memory row) and wasm_stack_bytes=131072 from the sealed 78de1c4
lock. Cross-compare JSON/CMake/native identities in the generator and add
durable mutation reds for provider bytes/size/SHA, manifest SHA, runtime
profile, declaration/surface, bytecode/native ABI, import/export
signatures, memory shape, and each serialized-object limit.
2026-08-24 13:13:02 +07:00
Nicholas Dudfield
50615166b1 chore(hooks): pin QuickJS provider 78de1c4 (Receipt B)
Consume the sealed lock at 1101461 bytes / SHA-256 40f9ac02… / runtime
60950ca8… / bytecode ABI 75ea54f3…. Project the four serialized-object
limits, 22 exports, memory min 6 / max 512, and javascript-surface
hashes. Native STObject/STArray fixture covers Amount.issue.currency,
repeated identity, and canonical JSON/bytes. Fuel ratchets step with
the larger provider; guest ABI is unchanged.
2026-08-24 12:54:02 +07:00
Nicholas Dudfield
f14e9236d2 fix(hooks): bill current-profile invocation fuel without incomplete type
SetHook cannot dereference the opaque QuickJS runtime handle. Use
currentQuickJSRuntimeProfile().invocationFuel for the CREATE fee
ceiling. Keep this out of the Receipt B provider pin.
2026-08-24 12:38:23 +07:00
Nicholas Dudfield
2b493ed881 fix(hooks): harden QuickJS consumer before next provider pin
Empty embed is fatal at daemon start. QuickJS CREATE bills the
invocation-fuel ceiling. TestEnv keeps the log rewriter on a shared
rewrite table. Wasmtime i32 results are kind-checked. Drop unused
pre-GuestMemory HOOK_SETUP/DECLARE/DEFINE copies from Macro.h. Docker
no longer skips jshookz; e2e also runs on pull_request.
2026-08-24 12:24:44 +07:00
Nicholas Dudfield
b9d0434e96 chore(hooks): pin provider after xahau-types and CFUNC rewrite
Seal lock to 6d05e793 (941714). Bytecode ABI unchanged. Four
HookInstructionCount ratchets step by one with the new wasm.
2026-08-18 12:34:39 +07:00
Nicholas Dudfield
3d5844e0ed ci(hooks): install pinned Wizer 10 in JSHookz e2e
Copy the provider workflow's wizer-v10.0.0 linux tarball install.
Without it, build provider dies after cmake with "wizer not found".
Also clang-format 18.1.8 the TestEnv / JSHooks files CI rejected.
2026-08-17 20:20:33 +07:00
Nicholas Dudfield
ad85d170c6 ci(hooks): check out jshookz main instead of a SHA
The sealed lock is the pin. CI builds whatever is on jshookz main
and fails if it does not reproduce the checked-in manifests. The
snapshot importer no longer rewrites JSHOOKZ_REF.
2026-08-17 20:10:08 +07:00
Nicholas Dudfield
32baf652ee chore(hooks): pin Wizered QuickJS provider snapshot
Seal the runtime-profile lock to jshookz 5d00e66 (wasm 6cbe9383,
941701, memory min 6). Bytecode ABI is unchanged. Default provider
build Wizers from jshookz_provider.unwizered.wasm.
2026-08-17 20:06:49 +07:00
Nicholas Dudfield
7bf8cd75e5 build(hooks): project provider values at build time
Replace configure-time header/embed generation with a static
QuickJSProviderProfile.h and one generated QuickJSProviderValues.cpp.
CMake only EXISTS-checks the three lock files. A refresh target plus
fingerprint stamp sees wasm appear or change without reconfigure.

The snapshot importer accepts python/jshookz or packages/jshookz.
2026-08-17 19:17:26 +07:00
Nicholas Dudfield
e910177838 chore(hooks): update JSHookz provider snapshot
Pin the sealed runtime-profile lock to local product 220bfdf
(wasm 9a0ff771, 852299). Bytecode ABI is unchanged; runtime
profile is now f606dec6. The e2e workflow finds python/jshookz
after the producer flatten. The wasm stays gitignored.

CI JSHOOKZ_REF is unpublished until that product commit is on
origin.
2026-08-17 17:33:57 +07:00
Nicholas Dudfield
d1cd4b22f0 fix(logs): give TestEnv a null-check rewriter
setTransform is installed in the TestEnv constructor and cleared
in the destructor. The empty path is a null check; SuiteJournal
writes the original string when nothing is installed.
2026-08-17 15:49:42 +07:00
Nicholas Dudfield
277bad787d test(hooks): add TestEnv, HooksTrace, and external hook-test wiring
TestEnv names accounts, prefixes phases, and honors XAHAU_TEST_LOG
(TESTENV_LOGGING alias). Logs::applyTransform copies the rewriter
under the existing mutex so suite journals can rewrite r-addresses
without racing setTransform. Hook host traces move off the View
journal onto a HooksTrace partition.

HOOKS_TEST_DIR / HOOKS_SOURCE_DIR pull producer-side *_test.cpp
into the rippled test binary. Coverage is not wired.
2026-08-17 15:14:01 +07:00
Nicholas Dudfield
dce17e2fa6 test(hooks): move JSHooks fixtures onto current v1 guest
Refresh the sealed provider pin to local product a48fb8a (wasm
95c40c7b, 852299). Guest sources now use rollback.onFail, total
hook.account(), STBlob.fromHex, CallbackInfo, and
//@jshookz-allow-malformed for undeployable modules. Regenerated
the bytecode header and fuel ratchets from a local JSHooks run
(17 cases, 343 tests). BEAST_EXPECT_EQ prints both sides.

CI JSHOOKZ_REF is that unpublished SHA; do not push until it is
on origin.
2026-08-17 15:13:15 +07:00
Nicholas Dudfield
e6403f1596 docs(hooks): launch contract names the daemon wiring as built 2026-08-15 18:46:50 +07:00
Nicholas Dudfield
ee0b677c2e chore(hooks): match embed generator file mode to its sibling script 2026-08-15 18:44:29 +07:00
Nicholas Dudfield
a4a43578de test(hooks): cost seam rejects failed validation; assert every stage ran
measureQuickJSSessionCostForTests now treats a validate call that did
not return callable flags (1 or 3) as an error instead of sampling it,
and the measuring case asserts each stage's minimum wall time is
non-zero, so a skipped stage or a rejecting session turns the case red
without pinning any number (grok, b6b57e3ec review).
2026-08-15 18:38:41 +07:00
Nicholas Dudfield
b6b57e3ec3 test(hooks): measure per-session quickjs provider cost
ENABLE_TESTS seam measureQuickJSSessionCostForTests runs N cold sessions
exactly as validation builds them (create → initialize → arm invocation
fuel → validate) and reports wall time per stage (sum and min) plus the
initialization and invocation fuel consumed; ProviderSession gains
initializationFuelConsumed beside invocationFuelConsumed. JSHooks_test
logs the figures (issue 0001 step 1: measure before changing provider
bytes) and asserts only the shape.

on this mac, release, live producer bundle: ~200us per cold session
(create 34us, initialize 157us, validate 6us), init fuel 1800041 of
5000000, invocation fuel 49832 of 50000000.
2026-08-15 18:12:45 +07:00
Nicholas Dudfield
41400f1cc6 build(hooks): never let a stale empty provider embed survive a bundle change
a stub written by a bundle-less configure is newer than a wasm copied in
afterwards, so ninja could skip the embed and the daemon would warn and
stay up. when the wasm is present, configure now drops an existing stub
(recognised by its provenance line) so the custom command must run; the
absent case is a configure-time warning instead of a status line. found by
grok's review of 77a1c2d65; verified by writing a stub, reconfiguring, and
watching the embed regenerate with the real bytes.
2026-08-15 18:09:32 +07:00
Nicholas Dudfield
77a1c2d65d feat(hooks): embed sealed quickjs provider and register it at daemon startup
cmake/EmbedQuickJSProvider.py projects the bundle's provider wasm into a
generated translation unit (one escaped string literal; ~0.7s to compile)
defining hook::embeddedQuickJSProvider(), re-verifying sha-256 and size
against the manifest at build time; a bundle without the binary embeds an
empty span. QuickJSProvider.cmake wires the custom command (or the absent
stub) and RippledCore adds the generated source to rippled.

the daemon path in Main.cpp launches the background registration before
Application::setup, awaits it after setup and before start, and treats a
refused launch or a failed registration as fatal, per the registry
contract: a node that cannot register its sealed provider must not stay
up. an empty embed logs a warning and continues. jtx::Env does not pass
through Main, so unit tests keep an unregistered current profile.

standalone probe on this machine: registered 1031425 bytes, awaited
135 ms after launch, before "Application starting".
2026-08-15 17:53:17 +07:00
Nicholas Dudfield
e4f4b28e0f fix(hooks): harden background provider registration per grok review
one static RegistryState holds mutex, runtimes and pending together so
teardown no longer depends on which accessor a process touches first
(error()-first then launch destroyed the registry before the pending
join). the pending entry is reserved under the lock with a promise-backed
future and the compile thread is started outside it and detached, so no
path can wait on the compile while holding the registry mutex and nothing
joins at exit; the worker catches and retains exceptions instead of
settling with an empty error.

launch now returns the refusal reason: mismatched profile against a
registered or launched identity, or a terminally failed earlier launch
(its retained error); same-profile relaunch stays a no-op.
quickJSRuntimeRegistrationError becomes awaitQuickJSRuntimeRegistration,
which is nullopt only when the identity is registered with this profile
and otherwise says why not (retained failure, different profile, or
never launched). header states the contract: launch returns before any
find/await, await before shutdown, failure is fatal at startup.

first tests for the lane on distinct runtimeProfile identities: launch
with concurrent finds agreeing on one handle, same/mismatched relaunch,
and a corrupted launch retaining its error and refusing relaunch.
2026-08-15 17:39:19 +07:00
Nicholas Dudfield
9870e5df67 feat(hooks): background quickjs provider compilation at startup
launchQuickJSRuntimeRegistration compiles/registers a provider on a
startup thread (std::async; the shared state joins on teardown so a
half-registered compile cannot be abandoned). findQuickJSRuntime waits
for a pending registration of the same identity before answering, so
background compilation is invisible to resolution except as startup
latency; failed identities retain their error via
quickJSRuntimeRegistrationError. in-memory only: no serialization, no
cache, nothing consensus-visible — the input is still the sha-verified
provider bytes (ai-docs issue 0001, ruled simplest step)
2026-08-15 17:13:25 +07:00
Nicholas Dudfield
dfc2a8ed83 chore(hooks): update JSHookz provider snapshot 2026-08-14 15:52:10 +07:00
Nicholas Dudfield
9de7ba734d chore(hooks): automate QuickJS provider snapshots 2026-08-14 15:50:45 +07:00
Nicholas Dudfield
359c3e7990 ci(hooks): update QuickJS policy test filter 2026-08-13 21:18:47 +07:00
Nicholas Dudfield
650b2d6e1c refactor(hooks): remove duplicate QuickJS catalogue 2026-08-13 20:18:52 +07:00
Nicholas Dudfield
94c61cc626 refactor(hooks): remove unused import categories 2026-08-13 19:58:10 +07:00
Nicholas Dudfield
b7881302e3 refactor(hooks): share host bodies with QuickJS 2026-08-13 19:46:03 +07:00
Nicholas Dudfield
4e87ccd924 chore(hooks): mark generated integration artifacts 2026-08-13 16:19:50 +07:00
Nicholas Dudfield
df86bebd5f test(hooks): focus proof on the QuickJS boundary 2026-08-13 16:19:36 +07:00
Nicholas Dudfield
670e77d4c0 refactor(hooks): simplify QuickJS guest memory 2026-08-13 16:18:53 +07:00
Nicholas Dudfield
ba54d7fd77 refactor(hooks): restore legacy WasmEdge execution 2026-08-13 15:50:18 +07:00
Nicholas Dudfield
78929910ee refactor(hooks): freeze QuickJS runtime policy 2026-08-13 14:50:05 +07:00
Nicholas Dudfield
3436c241e2 refactor(hooks): restore legacy host wrappers to applyHook 2026-08-13 10:42:31 +07:00
Nicholas Dudfield
398f6f01d7 ci: wait for both coverage reports 2026-08-13 09:11:11 +07:00
Nicholas Dudfield
aeea7a8251 ci(hooks): cover the shared host seam 2026-08-13 07:54:44 +07:00
Nicholas Dudfield
33510213d3 fix(hooks): reject null provider allocations 2026-08-13 07:54:38 +07:00
Nicholas Dudfield
bb136e616a fix(hooks): make admission and teardown deterministic 2026-08-13 07:31:19 +07:00
Nicholas Dudfield
e4ccc443c1 fix(ci): preserve Git checkout metadata 2026-08-13 07:13:35 +07:00
Nicholas Dudfield
0b38795617 ci(hooks): reuse Xahau build caches 2026-08-13 07:07:36 +07:00
Nicholas Dudfield
f3ed814dcf test(ci): report provider-backed coverage 2026-08-12 19:06:50 +07:00
Nicholas Dudfield
7c66f94a90 test(ci): exercise QuickJS provider in coverage 2026-08-12 18:40:48 +07:00
Nicholas Dudfield
240f33d398 fix(ci): refresh Wasmtime recipe 2026-08-12 18:21:24 +07:00
Nicholas Dudfield
a868a46848 fix(build): normalize Wasmtime package compiler 2026-08-12 18:14:01 +07:00
Nicholas Dudfield
b86112bca4 ci(hooks): defer HBB release packaging 2026-08-12 17:59:18 +07:00
Nicholas Dudfield
92832aac10 fix(ci): install server build dependencies 2026-08-12 17:51:39 +07:00
Nicholas Dudfield
8e7def3739 fix(hooks): keep hook macros in app layer 2026-08-12 17:51:32 +07:00
Nicholas Dudfield
552d1cf1e0 style(hooks): apply clang-format 2026-08-12 17:32:23 +07:00
Nicholas Dudfield
f35d8591d8 ci(hooks): prove jshookz integration 2026-08-12 17:31:24 +07:00
Nicholas Dudfield
124235d92e build(hooks): consume jshookz provider bundle 2026-08-12 16:42:23 +07:00
Nicholas Dudfield
56a456a7ff test(hooks): verify dual-runtime catalogue execution 2026-08-12 11:15:35 +07:00
Nicholas Dudfield
70814197f5 feat(hooks): expose raw Hook catalogue through Wasmtime 2026-08-12 11:13:45 +07:00
Nicholas Dudfield
d5334234fc build(hooks): pin Wasmtime provider dependencies 2026-08-12 11:13:23 +07:00
Nicholas Dudfield
d406e336a4 Factor shared Hook host operation seam 2026-08-11 16:10:11 +07:00
Nicholas Dudfield
4c58f4223c Route Hook installation through canonical artifacts 2026-08-11 15:30:22 +07:00
Nicholas Dudfield
2d48d21cc6 Refactor QuickJS Hook provider execution boundary 2026-08-11 13:01:46 +07:00
Nicholas Dudfield
7dd63aaac9 Add QuickJS Hook transaction integration 2026-08-11 12:24:41 +07:00
Richard Holland
bb244ef772 put release builds into a candidate folder to prevent auto-update scripts running before smoke tests (#761) 2026-06-21 12:12:43 +10:00
70 changed files with 12303 additions and 165 deletions

View File

@@ -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
View File

@@ -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

View File

@@ -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' \
..

View File

@@ -2,9 +2,9 @@ name: Build using Docker
on:
push:
branches: ["dev", "candidate", "release", "jshooks"]
branches: ["dev", "candidate", "release", "jshooks", "jshookz"]
pull_request:
branches: ["dev", "candidate", "release", "jshooks"]
branches: ["dev", "candidate", "release", "jshooks", "jshookz"]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}

View File

@@ -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
View 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]
pull_request:
branches: [jshookz, dev]
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

View File

@@ -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/)

View File

@@ -125,6 +125,10 @@ find_package(xxHash REQUIRED)
find_package(magic_enum REQUIRED)
include(deps/WasmEdge)
if(xrpld)
include(deps/Wasmtime)
include(QuickJSProvider)
endif()
if(TARGET nudb::core)
set(nudb nudb::core)
elseif(TARGET NuDB::nudb)

135
bin/update-jshookz-snapshot Executable file
View 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())

View File

@@ -95,8 +95,16 @@ if [[ "$4" == "" ]]; then
echo "Non GH, local building, no Action runner magic"
else
# GH Action, runner
cp /io/release-build/xahaud /data/builds/$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4
cp /io/release-build/release.info /data/builds/$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4.releaseinfo
if [[ "$(git rev-parse --abbrev-ref HEAD)" == "release" ]]; then
echo "building on the release branch... placing it in builds/candidate"
mkdir /data/builds/candidate
cp /io/release-build/xahaud /data/builds/candidate/$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4
cp /io/release-build/release.info /data/builds/candidate/$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4.releaseinfo
else
echo "building non-release branch, placing it in builds root"
cp /io/release-build/xahaud /data/builds/$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4
cp /io/release-build/release.info /data/builds/$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4.releaseinfo
fi
echo "Published build to: http://build.xahau.tech/"
echo $(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4
fi

View 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())

View 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)

View File

@@ -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

View File

@@ -0,0 +1 @@
find_package(wasmtime REQUIRED)

View 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()

View File

@@ -88,6 +88,7 @@ class Xrpl(ConanFile):
'soci/*:shared': False,
'soci/*:with_sqlite3': True,
'soci/*:with_boost': True,
'wasmtime/*:shared': False,
'xxhash/*:shared': False,
}
@@ -121,6 +122,8 @@ class Xrpl(ConanFile):
if self.options.with_wasmedge:
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')
if self.options.rocksdb:

44
external/quickjs-provider/README.md vendored Normal file
View 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.

View 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")

View 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"
}
}

View 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
View 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
View 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"]

View File

@@ -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();

View 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

View File

@@ -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)) || \

View File

@@ -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)

View File

@@ -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()));
}
}

View File

@@ -0,0 +1,242 @@
//------------------------------------------------------------------------------
/*
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

View 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

View 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

File diff suppressed because it is too large Load Diff

1743
src/test/app/JSHooks_test_hooks.h generated Normal file

File diff suppressed because it is too large Load Diff

View 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

View 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.

View 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;
}
}

View 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

View File

@@ -0,0 +1,257 @@
#!/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())

View 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"
]
}

View 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

View File

@@ -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>

View File

@@ -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
View 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

View File

@@ -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 {

View File

@@ -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

View File

@@ -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)

View File

@@ -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;
}
}

View 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

View 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

View 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

View 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

View File

@@ -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,

View 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

View 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

View 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

View File

@@ -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();

View 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

View 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

View 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

View 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

View 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(&parameters, 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(&parameters, &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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -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

View File

@@ -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)

View File

@@ -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),