mirror of
https://github.com/Xahau/xahaud.git
synced 2026-07-07 07:10:09 +00:00
Compare commits
4 Commits
request-id
...
wasmtime-e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
132a3a8520 | ||
|
|
bb244ef772 | ||
|
|
0e8cedc7a9 | ||
|
|
e07b573753 |
@@ -124,7 +124,8 @@ find_package(date REQUIRED)
|
||||
find_package(xxHash REQUIRED)
|
||||
find_package(magic_enum REQUIRED)
|
||||
|
||||
include(deps/WasmEdge)
|
||||
include(deps/WasmEdge)
|
||||
include(deps/Wasmtime)
|
||||
if(TARGET nudb::core)
|
||||
set(nudb nudb::core)
|
||||
elseif(TARGET NuDB::nudb)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -58,6 +58,7 @@ target_link_libraries(xrpl.imports.main
|
||||
OpenSSL::Crypto
|
||||
Ripple::boost
|
||||
wasmedge::wasmedge
|
||||
wasmtime::wasmtime
|
||||
Ripple::opts
|
||||
Ripple::syslibs
|
||||
absl::random_random
|
||||
|
||||
1
cmake/deps/Wasmtime.cmake
Normal file
1
cmake/deps/Wasmtime.cmake
Normal file
@@ -0,0 +1 @@
|
||||
find_package(wasmtime REQUIRED)
|
||||
@@ -22,6 +22,7 @@ class Xrpl(ConanFile):
|
||||
'unity': [True, False],
|
||||
'xrpld': [True, False],
|
||||
'with_wasmedge': [True, False],
|
||||
'with_wasmtime': [True, False],
|
||||
'tool_requires_b2': [True, False],
|
||||
}
|
||||
|
||||
@@ -53,6 +54,7 @@ class Xrpl(ConanFile):
|
||||
'unity': False,
|
||||
'xrpld': False,
|
||||
'with_wasmedge': True,
|
||||
'with_wasmtime': True,
|
||||
'tool_requires_b2': False,
|
||||
|
||||
'date/*:header_only': False,
|
||||
@@ -121,6 +123,8 @@ class Xrpl(ConanFile):
|
||||
|
||||
if self.options.with_wasmedge:
|
||||
self.requires('wasmedge/0.11.2@xahaud/stable')
|
||||
if self.options.with_wasmtime:
|
||||
self.requires('wasmtime/44.0.1@xahaud/stable')
|
||||
if self.options.jemalloc:
|
||||
self.requires('jemalloc/5.3.0')
|
||||
if self.options.rocksdb:
|
||||
|
||||
25
external/wasmtime/conandata.yml
vendored
Normal file
25
external/wasmtime/conandata.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
sources:
|
||||
"44.0.1":
|
||||
Macos:
|
||||
"armv8":
|
||||
"gcc":
|
||||
url: "https://github.com/bytecodealliance/wasmtime/releases/download/v44.0.1/wasmtime-v44.0.1-aarch64-macos-c-api.tar.xz"
|
||||
sha256: "5da6d09fa6340db6d7afc8b73d189eff4973825c9a6fa5e814d2f9b1ddfd8998"
|
||||
"x86_64":
|
||||
"gcc":
|
||||
url: "https://github.com/bytecodealliance/wasmtime/releases/download/v44.0.1/wasmtime-v44.0.1-x86_64-macos-c-api.tar.xz"
|
||||
sha256: ""
|
||||
Linux:
|
||||
"x86_64":
|
||||
"gcc":
|
||||
url: "https://github.com/bytecodealliance/wasmtime/releases/download/v44.0.1/wasmtime-v44.0.1-x86_64-linux-c-api.tar.xz"
|
||||
sha256: "fe9b4bfa87724054cebcc09b83a17d5ded66a465054b77c8de0ebc166ad78d7a"
|
||||
"armv8":
|
||||
"gcc":
|
||||
url: "https://github.com/bytecodealliance/wasmtime/releases/download/v44.0.1/wasmtime-v44.0.1-aarch64-linux-c-api.tar.xz"
|
||||
sha256: ""
|
||||
Windows:
|
||||
"x86_64":
|
||||
"Visual Studio":
|
||||
url: "https://github.com/bytecodealliance/wasmtime/releases/download/v44.0.1/wasmtime-v44.0.1-x86_64-windows-c-api.zip"
|
||||
sha256: ""
|
||||
87
external/wasmtime/conanfile.py
vendored
Normal file
87
external/wasmtime/conanfile.py
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
from conan import ConanFile
|
||||
from conan.tools.files import get, copy
|
||||
from conan.errors import ConanInvalidConfiguration
|
||||
|
||||
import os
|
||||
|
||||
required_conan_version = ">=1.53.0"
|
||||
|
||||
class WasmtimeConan(ConanFile):
|
||||
name = "wasmtime"
|
||||
description = ("Wasmtime is a fast and secure runtime for WebAssembly. "
|
||||
"It is a standalone wasm-only optimizing runtime for WebAssembly and WASI.")
|
||||
license = "Apache-2.0"
|
||||
url = "https://github.com/bytecodealliance/wasmtime"
|
||||
homepage = "https://github.com/bytecodealliance/wasmtime"
|
||||
topics = ("webassembly", "wasm", "wasi")
|
||||
package_type = "static-library"
|
||||
settings = "os", "arch", "compiler", "build_type"
|
||||
|
||||
@property
|
||||
def _compiler_alias(self):
|
||||
return {
|
||||
"Visual Studio": "Visual Studio",
|
||||
"msvc": "Visual Studio",
|
||||
}.get(str(self.info.settings.compiler), "gcc")
|
||||
|
||||
def configure(self):
|
||||
self.settings.compiler.rm_safe("libcxx")
|
||||
self.settings.compiler.rm_safe("cppstd")
|
||||
|
||||
def validate(self):
|
||||
try:
|
||||
self.conan_data["sources"][self.version][str(self.settings.os)][str(self.settings.arch)][self._compiler_alias]
|
||||
except KeyError:
|
||||
raise ConanInvalidConfiguration(
|
||||
f"Binaries for this combination of version/os/arch/compiler are not available "
|
||||
f"({self.version}/{self.settings.os}/{self.settings.arch}/{self._compiler_alias})"
|
||||
)
|
||||
entry = self.conan_data["sources"][self.version][str(self.settings.os)][str(self.settings.arch)][self._compiler_alias]
|
||||
if not entry.get("sha256"):
|
||||
raise ConanInvalidConfiguration(
|
||||
f"SHA256 not configured for {self.version}/{self.settings.os}/{self.settings.arch}/{self._compiler_alias}"
|
||||
)
|
||||
|
||||
def package_id(self):
|
||||
# Make binary compatible across compiler versions (since we're downloading prebuilt)
|
||||
self.info.settings.rm_safe("compiler.version")
|
||||
# Group compilers by their binary compatibility
|
||||
compiler_name = str(self.info.settings.compiler)
|
||||
if compiler_name in ["Visual Studio", "msvc"]:
|
||||
self.info.settings.compiler = "Visual Studio"
|
||||
else:
|
||||
self.info.settings.compiler = "gcc"
|
||||
|
||||
def build(self):
|
||||
# Download the prebuilt tarball
|
||||
entry = self.conan_data["sources"][self.version][str(self.settings.os)][str(self.settings.arch)][self._compiler_alias]
|
||||
get(self, url=entry["url"], sha256=entry["sha256"],
|
||||
destination=self.source_folder, strip_root=True)
|
||||
|
||||
def package(self):
|
||||
copy(self, pattern="*.h", dst=os.path.join(self.package_folder, "include"),
|
||||
src=os.path.join(self.source_folder, "include"), keep_path=True)
|
||||
|
||||
srclibdir = os.path.join(self.source_folder, "lib")
|
||||
dstlibdir = os.path.join(self.package_folder, "lib")
|
||||
|
||||
if self.settings.os == "Windows":
|
||||
copy(self, pattern="wasmtime.lib", src=srclibdir, dst=dstlibdir, keep_path=False)
|
||||
copy(self, pattern="wasmtime.dll", src=srclibdir, dst=os.path.join(self.package_folder, "bin"), keep_path=False)
|
||||
elif self.settings.os == "Macos":
|
||||
copy(self, pattern="libwasmtime.a", src=srclibdir, dst=dstlibdir, keep_path=False)
|
||||
copy(self, pattern="libwasmtime.dylib", src=srclibdir, dst=dstlibdir, keep_path=False)
|
||||
else:
|
||||
copy(self, pattern="libwasmtime.a", src=srclibdir, dst=dstlibdir, keep_path=False)
|
||||
copy(self, pattern="libwasmtime.so*", src=srclibdir, dst=dstlibdir, keep_path=False)
|
||||
|
||||
copy(self, pattern="LICENSE", src=self.source_folder, dst=os.path.join(self.package_folder, "licenses"), keep_path=False)
|
||||
|
||||
def package_info(self):
|
||||
self.cpp_info.libs = ["wasmtime"]
|
||||
|
||||
if self.settings.os == "Windows":
|
||||
self.cpp_info.system_libs += ["ws2_32", "bcrypt", "advapi32", "userenv", "ntdll", "shell32", "ole32"]
|
||||
|
||||
if self.settings.os in ["Linux", "FreeBSD"]:
|
||||
self.cpp_info.system_libs += ["pthread", "dl", "m"]
|
||||
@@ -10,6 +10,8 @@
|
||||
* were then used.
|
||||
*/
|
||||
|
||||
#include <xrpl/hook/WasmTypes.h>
|
||||
|
||||
#define LPAREN (
|
||||
#define RPAREN )
|
||||
#define COMMA ,
|
||||
@@ -68,104 +70,90 @@
|
||||
#define SEP_uint64_t LPAREN uint64_t COMMA
|
||||
#define SEP_int64_t LPAREN int64_t COMMA
|
||||
|
||||
#define VAL_uint32_t WasmEdge_ValueGetI32(in[_stack++])
|
||||
#define VAL_int32_t WasmEdge_ValueGetI32(in[_stack++])
|
||||
#define VAL_uint64_t WasmEdge_ValueGetI64(in[_stack++])
|
||||
#define VAL_int64_t WasmEdge_ValueGetI64(in[_stack++])
|
||||
// VAL_* : extract typed value from WasmValue in[] array (engine-agnostic)
|
||||
#define VAL_uint32_t (uint32_t) in[_stack++].asI32()
|
||||
#define VAL_int32_t (int32_t) in[_stack++].asI32()
|
||||
#define VAL_uint64_t (uint64_t) in[_stack++].asI64()
|
||||
#define VAL_int64_t (int64_t) in[_stack++].asI64()
|
||||
|
||||
#define VAR_ASSIGN(T, V) T V = CAT(VAL_##T)
|
||||
|
||||
#define RET_uint32_t(return_code) WasmEdge_ValueGenI32(return_code)
|
||||
#define RET_int32_t(return_code) WasmEdge_ValueGenI32(return_code)
|
||||
#define RET_uint64_t(return_code) WasmEdge_ValueGenI64(return_code)
|
||||
#define RET_int64_t(return_code) WasmEdge_ValueGenI64(return_code)
|
||||
// RET_* : wrap a C return value into a WasmValue
|
||||
#define RET_uint32_t(return_code) hook::WasmValue::i32((uint32_t)(return_code))
|
||||
#define RET_int32_t(return_code) hook::WasmValue::i32((uint32_t)(return_code))
|
||||
#define RET_uint64_t(return_code) hook::WasmValue::i64((uint64_t)(return_code))
|
||||
#define RET_int64_t(return_code) hook::WasmValue::i64((uint64_t)(return_code))
|
||||
|
||||
#define RET_ASSIGN(T, return_code) CAT2(RET_, T(return_code))
|
||||
|
||||
#define TYP_uint32_t WasmEdge_ValType_I32
|
||||
#define TYP_int32_t WasmEdge_ValType_I32
|
||||
#define TYP_uint64_t WasmEdge_ValType_I64
|
||||
#define TYP_int64_t WasmEdge_ValType_I64
|
||||
// TYP_* : map C type to WasmValue::Kind (used by WasmEdgeEngine.cpp registry)
|
||||
#define TYP_uint32_t hook::WasmValue::Kind::I32
|
||||
#define TYP_int32_t hook::WasmValue::Kind::I32
|
||||
#define TYP_uint64_t hook::WasmValue::Kind::I64
|
||||
#define TYP_int64_t hook::WasmValue::Kind::I64
|
||||
|
||||
#define WASM_VAL_TYPE(T, b) CAT2(TYP_, T)
|
||||
|
||||
#define UNSIGNED_TYPE(T) std::make_unsigned_t<T>
|
||||
|
||||
// DECLARE_HOOK_FUNCTION: forward-declares the impl function and the engine
|
||||
// wrapper
|
||||
#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;
|
||||
hook::GuestMemory& mem __VA_OPT__(COMMA __VA_ARGS__)); \
|
||||
extern hook::HostCallStatus WasmFunction##F( \
|
||||
void* userData, \
|
||||
hook::GuestMemory& mem, \
|
||||
hook::WasmValue const* in, \
|
||||
size_t inLen, \
|
||||
hook::WasmValue* out, \
|
||||
size_t outLen);
|
||||
|
||||
// DEFINE_HOOK_FUNCTION: defines the engine-agnostic wrapper + the impl function
|
||||
// body
|
||||
#define DEFINE_HOOK_FUNCTION(R, F, ...) \
|
||||
WasmEdge_Result hook_api::WasmFunction##F( \
|
||||
hook::HostCallStatus hook_api::WasmFunction##F( \
|
||||
void* data_ptr, \
|
||||
const WasmEdge_CallingFrameContext* frameCtx, \
|
||||
const WasmEdge_Value* in, \
|
||||
WasmEdge_Value* out) \
|
||||
hook::GuestMemory& mem, \
|
||||
hook::WasmValue const* in, \
|
||||
size_t /*inLen*/, \
|
||||
hook::WasmValue* out, \
|
||||
size_t /*outLen*/) \
|
||||
{ \
|
||||
__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__))); \
|
||||
*hookCtx, mem __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; \
|
||||
return hook::HostCallStatus::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; \
|
||||
return hook::HostCallStatus::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__))
|
||||
hook::GuestMemory& mem __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) \
|
||||
#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]] unsigned char* memory = mem.base; \
|
||||
[[maybe_unused]] const uint64_t memory_length = mem.size; \
|
||||
[[maybe_unused]] auto& api = hookCtx.api(); \
|
||||
if (!memory || !memory_length) \
|
||||
return INTERNAL_ERROR;
|
||||
|
||||
#define HOOK_TEARDOWN() \
|
||||
@@ -197,11 +185,10 @@
|
||||
<< " bytes past end of wasm memory"; \
|
||||
return OUT_OF_BOUNDS; \
|
||||
} \
|
||||
if (!WasmEdge_ResultOK(WasmEdge_MemoryInstanceSetData( \
|
||||
memoryCtx, \
|
||||
reinterpret_cast<const uint8_t*>(host_src_ptr), \
|
||||
if (!mem.write( \
|
||||
guest_dst_ptr, \
|
||||
bytes_to_write))) \
|
||||
reinterpret_cast<uint8_t const*>(host_src_ptr), \
|
||||
static_cast<uint64_t>(bytes_to_write))) \
|
||||
return INTERNAL_ERROR; \
|
||||
bytes_written += bytes_to_write; \
|
||||
}
|
||||
|
||||
114
include/xrpl/hook/WasmTypes.h
Normal file
114
include/xrpl/hook/WasmTypes.h
Normal file
@@ -0,0 +1,114 @@
|
||||
#ifndef RIPPLE_HOOK_WASMTYPES_H_INCLUDED
|
||||
#define RIPPLE_HOOK_WASMTYPES_H_INCLUDED
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
namespace hook {
|
||||
|
||||
struct GuestMemory
|
||||
{
|
||||
uint8_t* base;
|
||||
uint64_t size;
|
||||
|
||||
inline bool
|
||||
inBounds(uint64_t guestPtr, uint64_t len) const
|
||||
{
|
||||
if (len > 0 && guestPtr > (UINT64_MAX - len + 1))
|
||||
return false;
|
||||
return (guestPtr + len) <= size;
|
||||
}
|
||||
|
||||
inline bool
|
||||
write(uint64_t guestPtr, void const* src, uint64_t len)
|
||||
{
|
||||
if (!inBounds(guestPtr, len))
|
||||
return false;
|
||||
std::memcpy(base + guestPtr, src, len);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool
|
||||
read(uint64_t guestPtr, void* dst, uint64_t len) const
|
||||
{
|
||||
if (!inBounds(guestPtr, len))
|
||||
return false;
|
||||
std::memcpy(dst, base + guestPtr, len);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct WasmValue
|
||||
{
|
||||
enum Kind : uint8_t
|
||||
{
|
||||
I32,
|
||||
I64
|
||||
} kind;
|
||||
|
||||
union
|
||||
{
|
||||
uint32_t u32;
|
||||
uint64_t u64;
|
||||
};
|
||||
|
||||
static inline WasmValue
|
||||
i32(uint32_t v)
|
||||
{
|
||||
WasmValue w;
|
||||
w.kind = I32;
|
||||
w.u32 = v;
|
||||
return w;
|
||||
}
|
||||
|
||||
static inline WasmValue
|
||||
i64(uint64_t v)
|
||||
{
|
||||
WasmValue w;
|
||||
w.kind = I64;
|
||||
w.u64 = v;
|
||||
return w;
|
||||
}
|
||||
|
||||
inline uint32_t
|
||||
asI32() const
|
||||
{
|
||||
return u32;
|
||||
}
|
||||
|
||||
inline uint64_t
|
||||
asI64() const
|
||||
{
|
||||
return u64;
|
||||
}
|
||||
};
|
||||
|
||||
enum class HostCallStatus
|
||||
{
|
||||
Success,
|
||||
Terminate,
|
||||
Trap,
|
||||
};
|
||||
|
||||
using HostFunctionFn = HostCallStatus (*)(
|
||||
void* userData,
|
||||
GuestMemory& mem,
|
||||
WasmValue const* in,
|
||||
size_t inLen,
|
||||
WasmValue* out,
|
||||
size_t outLen);
|
||||
|
||||
struct HostFunctionDecl
|
||||
{
|
||||
char const* name;
|
||||
HostFunctionFn fn;
|
||||
std::vector<WasmValue::Kind> params;
|
||||
WasmValue::Kind result;
|
||||
ripple::uint256 const* featureGate;
|
||||
};
|
||||
|
||||
} // namespace hook
|
||||
|
||||
#endif // RIPPLE_HOOK_WASMTYPES_H_INCLUDED
|
||||
@@ -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(WasmtimeEngine, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (HookMap, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FIX (GuardDepth32, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(NamedHooks, Supported::yes, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -15202,8 +15202,9 @@ public:
|
||||
using namespace test::jtx;
|
||||
static FeatureBitset const all{supported_amendments()};
|
||||
|
||||
static std::array<FeatureBitset, 8> const feats{
|
||||
static std::array<FeatureBitset, 9> const feats{
|
||||
all,
|
||||
all - featureWasmtimeEngine,
|
||||
all - fixXahauV2,
|
||||
all - fixXahauV1 - fixXahauV2,
|
||||
all - fixXahauV1 - fixXahauV2 - fixNSDelete,
|
||||
@@ -15474,7 +15475,8 @@ SETHOOK_TEST(3, false)
|
||||
SETHOOK_TEST(4, false)
|
||||
SETHOOK_TEST(5, false)
|
||||
SETHOOK_TEST(6, false)
|
||||
SETHOOK_TEST(7, true)
|
||||
SETHOOK_TEST(7, false)
|
||||
SETHOOK_TEST(8, true)
|
||||
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(SetHook0, app, ripple, 2);
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(SetHook1, app, ripple, 2);
|
||||
@@ -15484,6 +15486,7 @@ BEAST_DEFINE_TESTSUITE_PRIO(SetHook4, app, ripple, 2);
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(SetHook5, app, ripple, 2);
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(SetHook6, app, ripple, 2);
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(SetHook7, app, ripple, 2);
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(SetHook8, app, ripple, 2);
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
#undef M
|
||||
|
||||
@@ -97,7 +97,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
|
||||
|
||||
@@ -145,8 +145,7 @@ makeStubHookContext(
|
||||
.executeAgainAsWeak = result.executeAgainAsWeak,
|
||||
.provisionalMeta = result.provisionalMeta,
|
||||
},
|
||||
.emitFailure = stubHookContext.emitFailure,
|
||||
.module = nullptr};
|
||||
.emitFailure = stubHookContext.emitFailure};
|
||||
}
|
||||
|
||||
// Original function - WARNING: stateMap reference may become dangling
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include <queue>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <wasmedge/wasmedge.h>
|
||||
|
||||
namespace hook {
|
||||
struct HookContext;
|
||||
@@ -179,8 +178,6 @@ struct HookResult
|
||||
foreignStateGrantCache; // add found grants here to avoid rechecking
|
||||
};
|
||||
|
||||
class HookExecutor;
|
||||
|
||||
struct SlotEntry
|
||||
{
|
||||
std::shared_ptr<const ripple::STObject> storage;
|
||||
@@ -217,7 +214,6 @@ struct HookContext
|
||||
emitFailure; // if this is a callback from a failed
|
||||
// emitted txn then this optional becomes
|
||||
// populated with the SLE
|
||||
const HookExecutor* module = 0;
|
||||
|
||||
// Lazy-initialized HookAPI member
|
||||
mutable std::unique_ptr<HookAPI> api_;
|
||||
@@ -272,231 +268,9 @@ gatherHookParameters(
|
||||
std::map<std::vector<uint8_t>, std::vector<uint8_t>>& parameters,
|
||||
beast::Journal const& j_);
|
||||
|
||||
// RH TODO: call destruct for these on rippled shutdown
|
||||
#define ADD_HOOK_FUNCTION(F, ctx) \
|
||||
{ \
|
||||
WasmEdge_FunctionInstanceContext* hf = \
|
||||
WasmEdge_FunctionInstanceCreate( \
|
||||
hook_api::WasmFunctionType##F, \
|
||||
hook_api::WasmFunction##F, \
|
||||
(void*)(&ctx), \
|
||||
0); \
|
||||
WasmEdge_ModuleInstanceAddFunction( \
|
||||
importObj, hook_api::WasmFunctionName##F, hf); \
|
||||
}
|
||||
|
||||
#define HR_ACC() hookResult.account << "-" << hookResult.otxnAccount
|
||||
#define HC_ACC() hookCtx.result.account << "-" << hookCtx.result.otxnAccount
|
||||
|
||||
// create these once at boot and keep them
|
||||
static WasmEdge_String exportName = WasmEdge_StringCreateByCString("env");
|
||||
static WasmEdge_String tableName = WasmEdge_StringCreateByCString("table");
|
||||
static auto* tableType = WasmEdge_TableTypeCreate(
|
||||
WasmEdge_RefType_FuncRef,
|
||||
{.HasMax = true, .Shared = false, .Min = 10, .Max = 20});
|
||||
static auto* memType = WasmEdge_MemoryTypeCreate(
|
||||
{.HasMax = true, .Shared = false, .Min = 1, .Max = 1});
|
||||
static WasmEdge_String memName = WasmEdge_StringCreateByCString("memory");
|
||||
static WasmEdge_String cbakFunctionName =
|
||||
WasmEdge_StringCreateByCString("cbak");
|
||||
static WasmEdge_String hookFunctionName =
|
||||
WasmEdge_StringCreateByCString("hook");
|
||||
|
||||
// see: lib/system/allocator.cpp
|
||||
#define WasmEdge_kPageSize 65536ULL
|
||||
|
||||
/**
|
||||
* HookExecutor is effectively a two-part function:
|
||||
* The first part sets up the Hook Api inside the wasm import, ready for use
|
||||
* (this is done during object construction.)
|
||||
* The second part is actually executing webassembly instructions
|
||||
* this is done during execteWasm function.
|
||||
* The instance is single use.
|
||||
*/
|
||||
class HookExecutor
|
||||
{
|
||||
private:
|
||||
bool spent = false; // a HookExecutor can only be used once
|
||||
|
||||
public:
|
||||
HookContext& hookCtx;
|
||||
WasmEdge_ModuleInstanceContext* importObj;
|
||||
|
||||
class WasmEdgeVM
|
||||
{
|
||||
public:
|
||||
WasmEdge_ConfigureContext* conf = NULL;
|
||||
WasmEdge_VMContext* ctx = NULL;
|
||||
|
||||
WasmEdgeVM()
|
||||
{
|
||||
conf = WasmEdge_ConfigureCreate();
|
||||
if (!conf)
|
||||
return;
|
||||
WasmEdge_ConfigureStatisticsSetInstructionCounting(conf, true);
|
||||
ctx = WasmEdge_VMCreate(conf, NULL);
|
||||
}
|
||||
|
||||
bool
|
||||
sane()
|
||||
{
|
||||
return ctx && conf;
|
||||
}
|
||||
|
||||
~WasmEdgeVM()
|
||||
{
|
||||
if (conf)
|
||||
WasmEdge_ConfigureDelete(conf);
|
||||
if (ctx)
|
||||
WasmEdge_VMDelete(ctx);
|
||||
}
|
||||
};
|
||||
|
||||
// if an error occured return a string prefixed with `prefix` followed by
|
||||
// the error description
|
||||
static std::optional<std::string>
|
||||
getWasmError(std::string prefix, WasmEdge_Result& res)
|
||||
{
|
||||
if (WasmEdge_ResultOK(res))
|
||||
return {};
|
||||
|
||||
const char* msg = WasmEdge_ResultGetMessage(res);
|
||||
return prefix + ": " + (msg ? msg : "unknown error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that a web assembly blob can be loaded by wasmedge
|
||||
*/
|
||||
static std::optional<std::string>
|
||||
validateWasm(const void* wasm, size_t len)
|
||||
{
|
||||
WasmEdgeVM vm;
|
||||
|
||||
if (!vm.sane())
|
||||
return "Could not create WASMEDGE instance";
|
||||
|
||||
WasmEdge_Result res = WasmEdge_VMLoadWasmFromBuffer(
|
||||
vm.ctx, reinterpret_cast<const uint8_t*>(wasm), len);
|
||||
|
||||
if (auto err = getWasmError("VMLoadWasmFromBuffer failed", res); err)
|
||||
return *err;
|
||||
|
||||
res = WasmEdge_VMValidate(vm.ctx);
|
||||
|
||||
if (auto err = getWasmError("VMValidate failed", res); err)
|
||||
return *err;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute web assembly byte code against the constructed Hook Context
|
||||
* Once execution has occured the exector is spent and cannot be used again
|
||||
* and should be destructed Information about the execution is populated
|
||||
* into hookCtx
|
||||
*/
|
||||
void
|
||||
executeWasm(
|
||||
const void* wasm,
|
||||
size_t len,
|
||||
bool callback,
|
||||
uint32_t wasmParam,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
// HookExecutor can only execute once
|
||||
XRPL_ASSERT(
|
||||
!spent,
|
||||
"HookExecutor::executeWasm : HookExecutor can only execute once");
|
||||
|
||||
spent = true;
|
||||
|
||||
JLOG(j.trace()) << "HookInfo[" << HC_ACC()
|
||||
<< "]: creating wasm instance";
|
||||
|
||||
WasmEdge_LogOff();
|
||||
|
||||
WasmEdgeVM vm;
|
||||
|
||||
if (!vm.sane())
|
||||
{
|
||||
JLOG(j.warn()) << "HookError[" << HC_ACC()
|
||||
<< "]: Could not create WASMEDGE instance.";
|
||||
|
||||
hookCtx.result.exitType = hook_api::ExitType::WASM_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
WasmEdge_Result res =
|
||||
WasmEdge_VMRegisterModuleFromImport(vm.ctx, this->importObj);
|
||||
|
||||
if (auto err = getWasmError("Import phase failed", res); err)
|
||||
{
|
||||
hookCtx.result.exitType = hook_api::ExitType::WASM_ERROR;
|
||||
JLOG(j.trace()) << "HookError[" << HC_ACC() << "]: " << *err;
|
||||
return;
|
||||
}
|
||||
|
||||
WasmEdge_Value params[1] = {WasmEdge_ValueGenI32((int64_t)wasmParam)};
|
||||
WasmEdge_Value returns[1];
|
||||
|
||||
res = WasmEdge_VMRunWasmFromBuffer(
|
||||
vm.ctx,
|
||||
reinterpret_cast<const uint8_t*>(wasm),
|
||||
len,
|
||||
callback ? cbakFunctionName : hookFunctionName,
|
||||
params,
|
||||
1,
|
||||
returns,
|
||||
1);
|
||||
|
||||
if (auto err = getWasmError("WASM VM error", res); err)
|
||||
{
|
||||
JLOG(j.warn()) << "HookError[" << HC_ACC() << "]: " << *err;
|
||||
hookCtx.result.exitType = hook_api::ExitType::WASM_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
auto* statsCtx = WasmEdge_VMGetStatisticsContext(vm.ctx);
|
||||
hookCtx.result.instructionCount =
|
||||
WasmEdge_StatisticsGetInstrCount(statsCtx);
|
||||
|
||||
// RH NOTE: stack unwind will clean up WasmEdgeVM
|
||||
}
|
||||
|
||||
HookExecutor(HookContext& ctx)
|
||||
: hookCtx(ctx), importObj(WasmEdge_ModuleInstanceCreate(exportName))
|
||||
{
|
||||
ctx.module = this;
|
||||
|
||||
WasmEdge_LogSetDebugLevel();
|
||||
|
||||
#pragma push_macro("HOOK_API_DEFINITION")
|
||||
#undef HOOK_API_DEFINITION
|
||||
|
||||
#define HOOK_WRAP_PARAMS(...) __VA_ARGS__
|
||||
#define HOOK_API_DEFINITION(RETURN_TYPE, FUNCTION_NAME, PARAMS_TUPLE, ...) \
|
||||
ADD_HOOK_FUNCTION(FUNCTION_NAME, ctx);
|
||||
|
||||
#include <xrpl/hook/hook_api.macro>
|
||||
|
||||
#undef HOOK_API_DEFINITION
|
||||
#undef HOOK_WRAP_PARAMS
|
||||
#pragma pop_macro("HOOK_API_DEFINITION")
|
||||
|
||||
WasmEdge_TableInstanceContext* hostTable =
|
||||
WasmEdge_TableInstanceCreate(tableType);
|
||||
WasmEdge_ModuleInstanceAddTable(importObj, tableName, hostTable);
|
||||
WasmEdge_MemoryInstanceContext* hostMem =
|
||||
WasmEdge_MemoryInstanceCreate(memType);
|
||||
WasmEdge_ModuleInstanceAddMemory(importObj, memName, hostMem);
|
||||
}
|
||||
|
||||
~HookExecutor()
|
||||
{
|
||||
WasmEdge_ModuleInstanceDelete(importObj);
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace hook
|
||||
|
||||
#endif
|
||||
|
||||
336
src/xrpld/app/hook/detail/WasmEdgeEngine.cpp
Normal file
336
src/xrpld/app/hook/detail/WasmEdgeEngine.cpp
Normal file
@@ -0,0 +1,336 @@
|
||||
#include <xrpld/app/hook/detail/WasmEdgeEngine.h>
|
||||
#include <xrpld/app/hook/applyHook.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include <wasmedge/wasmedge.h>
|
||||
|
||||
using namespace ripple;
|
||||
|
||||
namespace hook {
|
||||
|
||||
namespace {
|
||||
|
||||
// ── Type-level helpers ──────────────────────────────────────────────────────
|
||||
|
||||
template <typename T>
|
||||
constexpr WasmValue::Kind
|
||||
kindOf()
|
||||
{
|
||||
if constexpr (
|
||||
std::is_same_v<T, uint64_t> || std::is_same_v<T, int64_t>)
|
||||
return WasmValue::Kind::I64;
|
||||
return WasmValue::Kind::I32;
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
std::vector<WasmValue::Kind>
|
||||
buildKinds()
|
||||
{
|
||||
return {kindOf<Ts>()...};
|
||||
}
|
||||
|
||||
// ── WasmEdge VM RAII ────────────────────────────────────────────────────────
|
||||
|
||||
struct WasmEdgeVM
|
||||
{
|
||||
WasmEdge_ConfigureContext* conf = nullptr;
|
||||
WasmEdge_VMContext* ctx = nullptr;
|
||||
|
||||
WasmEdgeVM()
|
||||
{
|
||||
conf = WasmEdge_ConfigureCreate();
|
||||
if (!conf)
|
||||
return;
|
||||
WasmEdge_ConfigureStatisticsSetInstructionCounting(conf, true);
|
||||
ctx = WasmEdge_VMCreate(conf, nullptr);
|
||||
}
|
||||
|
||||
bool
|
||||
sane() const
|
||||
{
|
||||
return ctx && conf;
|
||||
}
|
||||
|
||||
~WasmEdgeVM()
|
||||
{
|
||||
if (ctx)
|
||||
WasmEdge_VMDelete(ctx);
|
||||
if (conf)
|
||||
WasmEdge_ConfigureDelete(conf);
|
||||
}
|
||||
};
|
||||
|
||||
static std::optional<std::string>
|
||||
getWasmError(std::string const& prefix, WasmEdge_Result res)
|
||||
{
|
||||
if (WasmEdge_ResultOK(res))
|
||||
return {};
|
||||
const char* msg = WasmEdge_ResultGetMessage(res);
|
||||
return prefix + ": " + (msg ? msg : "unknown error");
|
||||
}
|
||||
|
||||
// ── Bridge: single WasmEdge callback → HostFunctionFn ──────────────────────
|
||||
|
||||
struct BridgeData
|
||||
{
|
||||
HostFunctionDecl const* decl;
|
||||
HookContext* ctx;
|
||||
};
|
||||
|
||||
static WasmEdge_Result
|
||||
bridgeFn(
|
||||
void* data_ptr,
|
||||
WasmEdge_CallingFrameContext const* frameCtx,
|
||||
WasmEdge_Value const* in,
|
||||
WasmEdge_Value* out)
|
||||
{
|
||||
auto* bridge = static_cast<BridgeData*>(data_ptr);
|
||||
|
||||
auto* memCtx = WasmEdge_CallingFrameGetMemoryInstance(frameCtx, 0);
|
||||
GuestMemory mem{
|
||||
WasmEdge_MemoryInstanceGetPointer(memCtx, 0, 0),
|
||||
WasmEdge_MemoryInstanceGetPageSize(memCtx) * 65536ULL};
|
||||
|
||||
size_t const paramCount = bridge->decl->params.size();
|
||||
std::vector<WasmValue> inVals(paramCount);
|
||||
for (size_t i = 0; i < paramCount; ++i)
|
||||
{
|
||||
if (bridge->decl->params[i] == WasmValue::Kind::I32)
|
||||
inVals[i] = WasmValue::i32((uint32_t)WasmEdge_ValueGetI32(in[i]));
|
||||
else
|
||||
inVals[i] = WasmValue::i64((uint64_t)WasmEdge_ValueGetI64(in[i]));
|
||||
}
|
||||
|
||||
WasmValue outVal;
|
||||
auto status = bridge->decl->fn(
|
||||
bridge->ctx, mem, inVals.data(), paramCount, &outVal, 1);
|
||||
|
||||
if (status == HostCallStatus::Terminate || status == HostCallStatus::Trap)
|
||||
return WasmEdge_Result_Terminate;
|
||||
|
||||
if (bridge->decl->result == WasmValue::Kind::I32)
|
||||
out[0] = WasmEdge_ValueGenI32((int32_t)outVal.asI32());
|
||||
else
|
||||
out[0] = WasmEdge_ValueGenI64((int64_t)outVal.asI64());
|
||||
|
||||
return WasmEdge_Result_Success;
|
||||
}
|
||||
|
||||
// ── WasmEdgeEngineImpl ──────────────────────────────────────────────────────
|
||||
|
||||
class WasmEdgeEngineImpl : public IWasmEngine
|
||||
{
|
||||
public:
|
||||
std::optional<std::string>
|
||||
validate(void const* wasm, size_t len) override
|
||||
{
|
||||
WasmEdgeVM vm;
|
||||
if (!vm.sane())
|
||||
return "Could not create WASMEDGE instance";
|
||||
|
||||
WasmEdge_Result res = WasmEdge_VMLoadWasmFromBuffer(
|
||||
vm.ctx, reinterpret_cast<uint8_t const*>(wasm), len);
|
||||
if (auto err = getWasmError("VMLoadWasmFromBuffer failed", res))
|
||||
return *err;
|
||||
|
||||
res = WasmEdge_VMValidate(vm.ctx);
|
||||
if (auto err = getWasmError("VMValidate failed", res))
|
||||
return *err;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ExecutionResult
|
||||
execute(
|
||||
void const* wasm,
|
||||
size_t len,
|
||||
bool isCallback,
|
||||
uint32_t wasmParam,
|
||||
HookContext& ctx,
|
||||
std::vector<HostFunctionDecl> const& imports,
|
||||
ripple::Rules const& rules,
|
||||
beast::Journal const& j) override
|
||||
{
|
||||
static WasmEdge_String exportName =
|
||||
WasmEdge_StringCreateByCString("env");
|
||||
static WasmEdge_String tableName =
|
||||
WasmEdge_StringCreateByCString("table");
|
||||
static auto* tableType = WasmEdge_TableTypeCreate(
|
||||
WasmEdge_RefType_FuncRef,
|
||||
{.HasMax = true, .Shared = false, .Min = 10, .Max = 20});
|
||||
static auto* memType = WasmEdge_MemoryTypeCreate(
|
||||
{.HasMax = true, .Shared = false, .Min = 1, .Max = 1});
|
||||
static WasmEdge_String memName =
|
||||
WasmEdge_StringCreateByCString("memory");
|
||||
static WasmEdge_String cbakFunctionName =
|
||||
WasmEdge_StringCreateByCString("cbak");
|
||||
static WasmEdge_String hookFunctionName =
|
||||
WasmEdge_StringCreateByCString("hook");
|
||||
|
||||
// Bridge data must not reallocate during registration
|
||||
std::vector<BridgeData> bridgeData;
|
||||
bridgeData.reserve(imports.size());
|
||||
|
||||
auto* importObj = WasmEdge_ModuleInstanceCreate(exportName);
|
||||
|
||||
for (auto const& decl : imports)
|
||||
{
|
||||
// featureGate: if non-null and not zero, check rules
|
||||
if (decl.featureGate && !(*decl.featureGate).isZero() &&
|
||||
!rules.enabled(*decl.featureGate))
|
||||
continue;
|
||||
|
||||
bridgeData.push_back({&decl, &ctx});
|
||||
|
||||
std::vector<WasmEdge_ValType> paramTypes;
|
||||
paramTypes.reserve(decl.params.size());
|
||||
for (auto k : decl.params)
|
||||
paramTypes.push_back(
|
||||
k == WasmValue::Kind::I32 ? WasmEdge_ValType_I32
|
||||
: WasmEdge_ValType_I64);
|
||||
|
||||
WasmEdge_ValType resultType =
|
||||
decl.result == WasmValue::Kind::I32 ? WasmEdge_ValType_I32
|
||||
: WasmEdge_ValType_I64;
|
||||
|
||||
auto* fnType = WasmEdge_FunctionTypeCreate(
|
||||
paramTypes.data(), paramTypes.size(), &resultType, 1);
|
||||
auto* fn = WasmEdge_FunctionInstanceCreate(
|
||||
fnType, bridgeFn, &bridgeData.back(), 0);
|
||||
WasmEdge_FunctionTypeDelete(fnType);
|
||||
|
||||
auto nameStr = WasmEdge_StringCreateByCString(decl.name);
|
||||
WasmEdge_ModuleInstanceAddFunction(importObj, nameStr, fn);
|
||||
WasmEdge_StringDelete(nameStr);
|
||||
}
|
||||
|
||||
WasmEdge_ModuleInstanceAddTable(
|
||||
importObj, tableName, WasmEdge_TableInstanceCreate(tableType));
|
||||
WasmEdge_ModuleInstanceAddMemory(
|
||||
importObj, memName, WasmEdge_MemoryInstanceCreate(memType));
|
||||
|
||||
JLOG(j.trace()) << "HookInfo[" << ctx.result.account << "-"
|
||||
<< ctx.result.otxnAccount
|
||||
<< "]: creating wasm instance";
|
||||
|
||||
WasmEdge_LogOff();
|
||||
|
||||
WasmEdgeVM vm;
|
||||
if (!vm.sane())
|
||||
{
|
||||
WasmEdge_ModuleInstanceDelete(importObj);
|
||||
JLOG(j.warn()) << "HookError[" << ctx.result.account << "-"
|
||||
<< ctx.result.otxnAccount
|
||||
<< "]: Could not create WASMEDGE instance.";
|
||||
return {false, 0, "Could not create WASMEDGE instance"};
|
||||
}
|
||||
|
||||
WasmEdge_Result res =
|
||||
WasmEdge_VMRegisterModuleFromImport(vm.ctx, importObj);
|
||||
if (auto err = getWasmError("Import phase failed", res))
|
||||
{
|
||||
JLOG(j.trace()) << "HookError[" << ctx.result.account << "-"
|
||||
<< ctx.result.otxnAccount << "]: " << *err;
|
||||
WasmEdge_ModuleInstanceDelete(importObj);
|
||||
return {false, 0, *err};
|
||||
}
|
||||
|
||||
WasmEdge_Value params[1] = {
|
||||
WasmEdge_ValueGenI32((int64_t)wasmParam)};
|
||||
WasmEdge_Value returns[1];
|
||||
|
||||
res = WasmEdge_VMRunWasmFromBuffer(
|
||||
vm.ctx,
|
||||
reinterpret_cast<uint8_t const*>(wasm),
|
||||
len,
|
||||
isCallback ? cbakFunctionName : hookFunctionName,
|
||||
params,
|
||||
1,
|
||||
returns,
|
||||
1);
|
||||
|
||||
ExecutionResult result;
|
||||
if (auto err = getWasmError("WASM VM error", res))
|
||||
{
|
||||
result.ok = false;
|
||||
result.instructionCount = 0;
|
||||
result.error = *err;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.ok = true;
|
||||
auto* statsCtx = WasmEdge_VMGetStatisticsContext(vm.ctx);
|
||||
result.instructionCount =
|
||||
WasmEdge_StatisticsGetInstrCount(statsCtx);
|
||||
}
|
||||
|
||||
WasmEdge_ModuleInstanceDelete(importObj);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// ── hookHostFunctionDecls registry ─────────────────────────────────────────
|
||||
// Expand hook_api.macro with custom HOOK_API_DEFINITION to build the list.
|
||||
// Each entry stores the WasmFunction##F pointer and parameter kinds.
|
||||
// For featureGate: uint256{} means "always available" (stored as nullptr),
|
||||
// named features are stored as their address.
|
||||
|
||||
#pragma push_macro("HOOK_API_DEFINITION")
|
||||
#pragma push_macro("HOOK_WRAP_PARAMS")
|
||||
#undef HOOK_API_DEFINITION
|
||||
#undef HOOK_WRAP_PARAMS
|
||||
|
||||
// Helper to convert feature gate argument: uint256{} → nullptr,
|
||||
// named feature variable → address of that variable.
|
||||
// We use an inline helper function template to do this.
|
||||
namespace {
|
||||
template <typename T>
|
||||
static inline uint256 const*
|
||||
featureGatePtr(T const& gate)
|
||||
{
|
||||
// If gate is zero (default uint256{}), return nullptr (no gate).
|
||||
if (gate.isZero())
|
||||
return nullptr;
|
||||
return &gate;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
#define HOOK_WRAP_PARAMS(...) __VA_ARGS__
|
||||
#define HOOK_API_DEFINITION(R, F, P, GATE) \
|
||||
{ \
|
||||
#F, \
|
||||
&hook_api::WasmFunction##F, \
|
||||
buildKinds<HOOK_WRAP_PARAMS P>(), \
|
||||
kindOf<R>(), \
|
||||
featureGatePtr(GATE), \
|
||||
},
|
||||
|
||||
std::vector<HostFunctionDecl> const&
|
||||
hookHostFunctionDecls()
|
||||
{
|
||||
static std::vector<HostFunctionDecl> const decls = {
|
||||
#include <xrpl/hook/hook_api.macro>
|
||||
};
|
||||
return decls;
|
||||
}
|
||||
|
||||
#undef HOOK_API_DEFINITION
|
||||
#undef HOOK_WRAP_PARAMS
|
||||
#pragma pop_macro("HOOK_WRAP_PARAMS")
|
||||
#pragma pop_macro("HOOK_API_DEFINITION")
|
||||
|
||||
// ── Factory ─────────────────────────────────────────────────────────────────
|
||||
|
||||
std::unique_ptr<IWasmEngine>
|
||||
makeWasmEdgeEngine()
|
||||
{
|
||||
return std::make_unique<WasmEdgeEngineImpl>();
|
||||
}
|
||||
|
||||
} // namespace hook
|
||||
14
src/xrpld/app/hook/detail/WasmEdgeEngine.h
Normal file
14
src/xrpld/app/hook/detail/WasmEdgeEngine.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef RIPPLE_APP_HOOK_DETAIL_WASMEDGEENGINE_H_INCLUDED
|
||||
#define RIPPLE_APP_HOOK_DETAIL_WASMEDGEENGINE_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/hook/detail/WasmEngine.h>
|
||||
#include <memory>
|
||||
|
||||
namespace hook {
|
||||
|
||||
std::unique_ptr<IWasmEngine>
|
||||
makeWasmEdgeEngine();
|
||||
|
||||
} // namespace hook
|
||||
|
||||
#endif // RIPPLE_APP_HOOK_DETAIL_WASMEDGEENGINE_H_INCLUDED
|
||||
51
src/xrpld/app/hook/detail/WasmEngine.h
Normal file
51
src/xrpld/app/hook/detail/WasmEngine.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef RIPPLE_APP_HOOK_DETAIL_WASMENGINE_H_INCLUDED
|
||||
#define RIPPLE_APP_HOOK_DETAIL_WASMENGINE_H_INCLUDED
|
||||
|
||||
#include <xrpl/hook/WasmTypes.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace hook {
|
||||
|
||||
/// Initial fuel budget per Hook execution (consensus-fixed; changing
|
||||
/// this value requires a separate Amendment).
|
||||
constexpr uint64_t kWasmtimeInitialFuel = 10'000'000'000ULL;
|
||||
|
||||
struct HookContext;
|
||||
|
||||
struct ExecutionResult
|
||||
{
|
||||
bool ok;
|
||||
uint64_t instructionCount;
|
||||
std::optional<std::string> error;
|
||||
};
|
||||
|
||||
class IWasmEngine
|
||||
{
|
||||
public:
|
||||
virtual ~IWasmEngine() = default;
|
||||
|
||||
virtual std::optional<std::string>
|
||||
validate(void const* wasm, size_t len) = 0;
|
||||
|
||||
virtual ExecutionResult
|
||||
execute(
|
||||
void const* wasm,
|
||||
size_t len,
|
||||
bool isCallback,
|
||||
uint32_t wasmParam,
|
||||
HookContext& ctx,
|
||||
std::vector<HostFunctionDecl> const& imports,
|
||||
ripple::Rules const& rules,
|
||||
beast::Journal const& j) = 0;
|
||||
};
|
||||
|
||||
std::vector<HostFunctionDecl> const&
|
||||
hookHostFunctionDecls();
|
||||
|
||||
} // namespace hook
|
||||
|
||||
#endif // RIPPLE_APP_HOOK_DETAIL_WASMENGINE_H_INCLUDED
|
||||
819
src/xrpld/app/hook/detail/WasmtimeEngine.cpp
Normal file
819
src/xrpld/app/hook/detail/WasmtimeEngine.cpp
Normal file
@@ -0,0 +1,819 @@
|
||||
// WasmtimeEngine.cpp — Wasmtime backend for the IWasmEngine abstraction.
|
||||
//
|
||||
// WHY THIS FILE IS LARGER THAN WasmEdgeEngine.cpp
|
||||
// ─────────────────────────────────────────────────
|
||||
// WasmEdge provides WasmEdge_CallingFrameGetMemoryInstance(frame, index),
|
||||
// which retrieves a module's linear memory by index regardless of whether it
|
||||
// is exported. Wasmtime has no equivalent: the only way to access memory
|
||||
// from inside a host-function callback is wasmtime_caller_export_get(), which
|
||||
// only works for *exported* memories.
|
||||
//
|
||||
// Hook WASM modules compiled by wasmcc define their own linear memory
|
||||
// (WebAssembly section 5) but do not export it. Without an export the memory
|
||||
// pointer is unavailable inside host callbacks, causing every Hook API call
|
||||
// that touches guest memory to see mem.base == nullptr and return
|
||||
// INTERNAL_ERROR, ultimately crashing the execution.
|
||||
//
|
||||
// The ensureMemoryExported() function (≈200 lines) works around this by
|
||||
// scanning the binary before instantiation and injecting a "memory" export
|
||||
// entry into section 7 when the module owns but does not export its memory.
|
||||
// The patched binary is semantically identical to the original.
|
||||
//
|
||||
// A potential alternative — storing the wasmtime_memory_t handle directly in
|
||||
// BridgeData — requires knowing at setup time which memory the module will
|
||||
// actually use (its own vs. an import from "env"), which itself requires
|
||||
// binary inspection. The export-injection approach is therefore the simplest
|
||||
// correct solution.
|
||||
#include <xrpld/app/hook/detail/WasmtimeEngine.h>
|
||||
#include <xrpld/app/hook/detail/WasmEngine.h>
|
||||
#include <xrpld/app/hook/applyHook.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
// wasmtime.h must come LAST to avoid int128_t pollution
|
||||
#include <wasmtime.h>
|
||||
|
||||
using namespace ripple;
|
||||
|
||||
namespace hook {
|
||||
|
||||
namespace {
|
||||
|
||||
// ── Type-level helpers ──────────────────────────────────────────────────────
|
||||
|
||||
template <typename T>
|
||||
constexpr WasmValue::Kind
|
||||
kindOf()
|
||||
{
|
||||
if constexpr (
|
||||
std::is_same_v<T, uint64_t> || std::is_same_v<T, int64_t>)
|
||||
return WasmValue::Kind::I64;
|
||||
return WasmValue::Kind::I32;
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
std::vector<WasmValue::Kind>
|
||||
buildKinds()
|
||||
{
|
||||
return {kindOf<Ts>()...};
|
||||
}
|
||||
|
||||
// ── Error helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
static std::optional<std::string>
|
||||
wasmtimeError(wasmtime_error_t* err)
|
||||
{
|
||||
if (!err)
|
||||
return {};
|
||||
wasm_byte_vec_t msg;
|
||||
wasmtime_error_message(err, &msg);
|
||||
std::string s(msg.data, msg.size);
|
||||
wasm_byte_vec_delete(&msg);
|
||||
wasmtime_error_delete(err);
|
||||
return s;
|
||||
}
|
||||
|
||||
static std::optional<std::string>
|
||||
wasmtimeTrap(wasm_trap_t* trap)
|
||||
{
|
||||
if (!trap)
|
||||
return {};
|
||||
wasm_byte_vec_t msg;
|
||||
wasm_trap_message(trap, &msg);
|
||||
std::string s(msg.data, msg.size);
|
||||
wasm_byte_vec_delete(&msg);
|
||||
wasm_trap_delete(trap);
|
||||
return s;
|
||||
}
|
||||
|
||||
// ── Process-global engine (thread-safe; created once) ────────────────────────
|
||||
|
||||
static wasm_engine_t*
|
||||
getGlobalEngine()
|
||||
{
|
||||
static wasm_engine_t* gEngine = []() -> wasm_engine_t* {
|
||||
wasm_config_t* cfg = wasm_config_new();
|
||||
if (!cfg)
|
||||
return nullptr;
|
||||
|
||||
// Consensus-fixed configuration – never change without an Amendment
|
||||
wasmtime_config_consume_fuel_set(cfg, true);
|
||||
wasmtime_config_wasm_simd_set(cfg, true);
|
||||
wasmtime_config_wasm_relaxed_simd_set(cfg, false);
|
||||
wasmtime_config_wasm_reference_types_set(cfg, true);
|
||||
wasmtime_config_wasm_bulk_memory_set(cfg, true);
|
||||
wasmtime_config_wasm_multi_value_set(cfg, false);
|
||||
wasmtime_config_cranelift_nan_canonicalization_set(cfg, true);
|
||||
|
||||
// wasm_threads is behind a feature flag; only set if available.
|
||||
// The compile-time feature guard in config.h controls whether
|
||||
// wasmtime_config_wasm_threads_set exists.
|
||||
#ifdef WASMTIME_FEATURE_THREADS
|
||||
wasmtime_config_wasm_threads_set(cfg, false);
|
||||
#endif
|
||||
|
||||
// wasm_engine_new_with_config takes ownership of cfg
|
||||
return wasm_engine_new_with_config(cfg);
|
||||
}();
|
||||
return gEngine;
|
||||
}
|
||||
|
||||
// ── WASM binary normalisation ─────────────────────────────────────────────────
|
||||
//
|
||||
// Hook WASM modules typically have their own memory (WebAssembly Section 5)
|
||||
// but do NOT export it. Wasmtime's wasmtime_caller_export_get() only works
|
||||
// for *exported* memories, so without an export the memory pointer is
|
||||
// unavailable inside host callbacks.
|
||||
//
|
||||
// This function inspects the binary and, when the module owns a memory but
|
||||
// does not already export it as "memory", injects a new export entry for
|
||||
// memory index 0. The resulting binary is semantically identical to the
|
||||
// original but now exposes its memory to the host.
|
||||
//
|
||||
// Modules that already export "memory" (or that import memory from "env") are
|
||||
// returned unchanged.
|
||||
|
||||
// LEB128 helpers (unsigned, forward-only)
|
||||
static uint32_t
|
||||
readUleb128(uint8_t const* p, size_t& pos, size_t limit)
|
||||
{
|
||||
uint32_t result = 0;
|
||||
uint32_t shift = 0;
|
||||
while (pos < limit)
|
||||
{
|
||||
uint8_t b = p[pos++];
|
||||
result |= (uint32_t)(b & 0x7F) << shift;
|
||||
if (!(b & 0x80))
|
||||
break;
|
||||
shift += 7;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Encode a value as unsigned LEB128 (appends to out)
|
||||
static void
|
||||
writeUleb128(std::vector<uint8_t>& out, uint32_t val)
|
||||
{
|
||||
do
|
||||
{
|
||||
uint8_t b = val & 0x7F;
|
||||
val >>= 7;
|
||||
if (val)
|
||||
b |= 0x80;
|
||||
out.push_back(b);
|
||||
} while (val);
|
||||
}
|
||||
|
||||
// Ensure the WASM binary exports its first memory as "memory".
|
||||
// Returns the original bytes unchanged if no patching is needed,
|
||||
// otherwise returns a patched copy.
|
||||
static std::vector<uint8_t>
|
||||
ensureMemoryExported(void const* wasm, size_t len)
|
||||
{
|
||||
uint8_t const* p = reinterpret_cast<uint8_t const*>(wasm);
|
||||
|
||||
// Validate magic + version.
|
||||
// NOTE: the magic bytes are 0x00 0x61 0x73 0x6D 0x01 0x00 0x00 0x00
|
||||
// Written as a string literal, "\x00asm…" is ambiguous because \x hex
|
||||
// escapes are greedy: "\x00a" == "\x0a" (newline). Use a byte array.
|
||||
static constexpr uint8_t kWasmMagic[8] = {
|
||||
0x00, 0x61, 0x73, 0x6D, // \0asm
|
||||
0x01, 0x00, 0x00, 0x00 // version 1
|
||||
};
|
||||
if (len < 8 || std::memcmp(p, kWasmMagic, 8) != 0)
|
||||
return {p, p + len}; // not a valid WASM binary, return as-is
|
||||
|
||||
bool hasOwnMemory = false;
|
||||
bool exportsMemory = false;
|
||||
|
||||
// offset to memory section (for the insert point) and export section
|
||||
size_t exportSectionOffset = 0; // byte offset of existing export section
|
||||
size_t exportSectionPayloadOff = 0; // offset of payload start
|
||||
size_t exportSectionPayloadLen = 0; // payload length
|
||||
|
||||
size_t pos = 8;
|
||||
while (pos + 1 < len)
|
||||
{
|
||||
uint8_t sectionId = p[pos++];
|
||||
size_t secLenPos = pos;
|
||||
uint32_t secLen = readUleb128(p, pos, len);
|
||||
size_t payloadStart = pos;
|
||||
|
||||
if (sectionId == 5) // memory section
|
||||
{
|
||||
// At least one memory entry means the module owns a memory
|
||||
size_t tmp = pos;
|
||||
uint32_t count = readUleb128(p, tmp, len);
|
||||
if (count > 0)
|
||||
hasOwnMemory = true;
|
||||
}
|
||||
else if (sectionId == 7) // export section
|
||||
{
|
||||
exportSectionOffset = secLenPos - 1; // start of section id byte
|
||||
exportSectionPayloadOff = payloadStart;
|
||||
exportSectionPayloadLen = secLen;
|
||||
|
||||
size_t tmp = pos;
|
||||
uint32_t count = readUleb128(p, tmp, len);
|
||||
for (uint32_t i = 0; i < count; ++i)
|
||||
{
|
||||
uint32_t nameLen = readUleb128(p, tmp, len);
|
||||
if (tmp + nameLen > len)
|
||||
break;
|
||||
bool isMemoryName = (nameLen == 6 &&
|
||||
std::memcmp(p + tmp, "memory", 6) == 0);
|
||||
tmp += nameLen;
|
||||
if (tmp >= len)
|
||||
break;
|
||||
uint8_t kind = p[tmp++]; // export kind byte
|
||||
readUleb128(p, tmp, len); // export index
|
||||
if (isMemoryName && kind == 0x02) // kind 2 == memory
|
||||
{
|
||||
exportsMemory = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pos = payloadStart + secLen;
|
||||
}
|
||||
|
||||
// No patching needed if:
|
||||
// - module does not own a memory (it may import one; caller_export_get works)
|
||||
// - module already exports its memory
|
||||
if (!hasOwnMemory || exportsMemory)
|
||||
return {p, p + len};
|
||||
|
||||
// Build a new memory export entry: \x06 m e m o r y \x02 \x00
|
||||
// (name_len=6, "memory", kind=2 (memory), index=0)
|
||||
std::vector<uint8_t> newExportEntry;
|
||||
writeUleb128(newExportEntry, 6); // name length
|
||||
for (char c : std::string("memory"))
|
||||
newExportEntry.push_back((uint8_t)c);
|
||||
newExportEntry.push_back(0x02); // export kind: memory
|
||||
writeUleb128(newExportEntry, 0); // memory index 0
|
||||
|
||||
if (exportSectionOffset == 0)
|
||||
{
|
||||
// No export section exists at all – create one from scratch.
|
||||
// New section: id=7, payload = count(1) + entry
|
||||
std::vector<uint8_t> newSection;
|
||||
newSection.push_back(0x07); // export section id
|
||||
std::vector<uint8_t> payload;
|
||||
writeUleb128(payload, 1); // 1 export
|
||||
payload.insert(payload.end(), newExportEntry.begin(), newExportEntry.end());
|
||||
writeUleb128(newSection, (uint32_t)payload.size());
|
||||
newSection.insert(newSection.end(), payload.begin(), payload.end());
|
||||
|
||||
// Insert the new section before the code section (id=10) or at end.
|
||||
// For correctness we append after the last known section.
|
||||
std::vector<uint8_t> result(p, p + len);
|
||||
result.insert(result.end(), newSection.begin(), newSection.end());
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Patch the existing export section: increment count, append entry.
|
||||
|
||||
// Read existing export count (as a LEB128)
|
||||
size_t countPos = exportSectionPayloadOff;
|
||||
uint32_t existingCount = readUleb128(p, countPos, len);
|
||||
uint32_t newCount = existingCount + 1;
|
||||
std::vector<uint8_t> newCountBytes;
|
||||
writeUleb128(newCountBytes, newCount);
|
||||
|
||||
// New payload = new count + existing entries (skip old count bytes) + new entry
|
||||
std::vector<uint8_t> newPayload;
|
||||
newPayload.insert(newPayload.end(), newCountBytes.begin(), newCountBytes.end());
|
||||
// Existing entries start at countPos, end at exportSectionPayloadOff + exportSectionPayloadLen
|
||||
size_t entriesStart = countPos;
|
||||
size_t entriesEnd = exportSectionPayloadOff + exportSectionPayloadLen;
|
||||
if (entriesEnd > len)
|
||||
entriesEnd = len;
|
||||
newPayload.insert(newPayload.end(), p + entriesStart, p + entriesEnd);
|
||||
newPayload.insert(newPayload.end(), newExportEntry.begin(), newExportEntry.end());
|
||||
|
||||
// Encode new section length as LEB128
|
||||
std::vector<uint8_t> newSecLen;
|
||||
writeUleb128(newSecLen, (uint32_t)newPayload.size());
|
||||
|
||||
// Reconstruct the full binary:
|
||||
// bytes before export section + id(0x07) + new_len + new_payload + bytes after
|
||||
std::vector<uint8_t> result;
|
||||
result.reserve(len + newExportEntry.size() + 4);
|
||||
// Part 1: everything before the export section
|
||||
result.insert(result.end(), p, p + exportSectionOffset);
|
||||
// Part 2: section id
|
||||
result.push_back(0x07);
|
||||
// Part 3: new length
|
||||
result.insert(result.end(), newSecLen.begin(), newSecLen.end());
|
||||
// Part 4: new payload
|
||||
result.insert(result.end(), newPayload.begin(), newPayload.end());
|
||||
// Part 5: everything after the export section
|
||||
size_t afterExport = exportSectionPayloadOff + exportSectionPayloadLen;
|
||||
if (afterExport < len)
|
||||
result.insert(result.end(), p + afterExport, p + len);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Per-execution state shared between the host callback and caller ──────────
|
||||
|
||||
struct ExecState
|
||||
{
|
||||
bool terminated = false;
|
||||
};
|
||||
|
||||
struct BridgeData
|
||||
{
|
||||
HostFunctionDecl const* decl;
|
||||
HookContext* ctx;
|
||||
ExecState* state;
|
||||
};
|
||||
|
||||
// ── wasm_functype_t builder helper ───────────────────────────────────────────
|
||||
|
||||
static wasm_functype_t*
|
||||
buildFuncType(
|
||||
std::vector<WasmValue::Kind> const& params,
|
||||
WasmValue::Kind result)
|
||||
{
|
||||
wasm_valtype_vec_t paramVec;
|
||||
wasm_valtype_vec_t resultVec;
|
||||
|
||||
if (params.empty())
|
||||
{
|
||||
wasm_valtype_vec_new_empty(¶mVec);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<wasm_valtype_t*> ptrs;
|
||||
ptrs.reserve(params.size());
|
||||
for (auto k : params)
|
||||
ptrs.push_back(
|
||||
wasm_valtype_new(k == WasmValue::Kind::I32 ? WASM_I32 : WASM_I64));
|
||||
wasm_valtype_vec_new(¶mVec, ptrs.size(), ptrs.data());
|
||||
}
|
||||
|
||||
{
|
||||
wasm_valtype_t* rs[1] = {
|
||||
wasm_valtype_new(result == WasmValue::Kind::I32 ? WASM_I32 : WASM_I64)};
|
||||
wasm_valtype_vec_new(&resultVec, 1, rs);
|
||||
}
|
||||
|
||||
return wasm_functype_new(¶mVec, &resultVec);
|
||||
}
|
||||
|
||||
// ── Bridge callback ──────────────────────────────────────────────────────────
|
||||
|
||||
static wasm_trap_t*
|
||||
bridgeFn(
|
||||
void* envPtr,
|
||||
wasmtime_caller_t* caller,
|
||||
wasmtime_val_t const* args,
|
||||
size_t nargs,
|
||||
wasmtime_val_t* results,
|
||||
size_t nresults)
|
||||
{
|
||||
auto* bridge = static_cast<BridgeData*>(envPtr);
|
||||
|
||||
// The module's memory is always exported as "memory" after
|
||||
// ensureMemoryExported() normalises the binary. For modules that import
|
||||
// memory from "env" (rare) the imported memory is also accessible via
|
||||
// the same export path once the linker resolves it.
|
||||
wasmtime_extern_t memExtern;
|
||||
GuestMemory mem{nullptr, 0};
|
||||
if (wasmtime_caller_export_get(
|
||||
caller, "memory", 6 /* strlen("memory") */, &memExtern))
|
||||
{
|
||||
if (memExtern.kind == WASMTIME_EXTERN_MEMORY)
|
||||
{
|
||||
wasmtime_context_t* ctx = wasmtime_caller_context(caller);
|
||||
mem.base = wasmtime_memory_data(ctx, &memExtern.of.memory);
|
||||
mem.size = wasmtime_memory_data_size(ctx, &memExtern.of.memory);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert incoming wasmtime_val_t args to WasmValue
|
||||
size_t const paramCount = bridge->decl->params.size();
|
||||
std::vector<WasmValue> inVals(paramCount);
|
||||
for (size_t i = 0; i < paramCount && i < nargs; ++i)
|
||||
{
|
||||
if (bridge->decl->params[i] == WasmValue::Kind::I32)
|
||||
inVals[i] = WasmValue::i32((uint32_t)args[i].of.i32);
|
||||
else
|
||||
inVals[i] = WasmValue::i64((uint64_t)args[i].of.i64);
|
||||
}
|
||||
|
||||
WasmValue outVal;
|
||||
auto status =
|
||||
bridge->decl->fn(bridge->ctx, mem, inVals.data(), paramCount, &outVal, 1);
|
||||
|
||||
if (status == HostCallStatus::Terminate || status == HostCallStatus::Trap)
|
||||
{
|
||||
// Signal termination to the outer caller
|
||||
bridge->state->terminated = true;
|
||||
// Return a trap to unwind the WASM stack
|
||||
return wasmtime_trap_new("hook terminated", 15);
|
||||
}
|
||||
|
||||
// Write result
|
||||
if (nresults > 0)
|
||||
{
|
||||
if (bridge->decl->result == WasmValue::Kind::I32)
|
||||
{
|
||||
results[0].kind = WASMTIME_I32;
|
||||
results[0].of.i32 = (int32_t)outVal.asI32();
|
||||
}
|
||||
else
|
||||
{
|
||||
results[0].kind = WASMTIME_I64;
|
||||
results[0].of.i64 = (int64_t)outVal.asI64();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr; // success
|
||||
}
|
||||
|
||||
// ── WasmtimeEngineImpl ───────────────────────────────────────────────────────
|
||||
|
||||
class WasmtimeEngineImpl : public IWasmEngine
|
||||
{
|
||||
public:
|
||||
std::optional<std::string>
|
||||
validate(void const* wasm, size_t len) override
|
||||
{
|
||||
wasm_engine_t* engine = getGlobalEngine();
|
||||
if (!engine)
|
||||
return "Could not create Wasmtime engine";
|
||||
|
||||
// Normalise the binary so validation sees the same form as execution.
|
||||
auto patched = ensureMemoryExported(wasm, len);
|
||||
|
||||
wasmtime_module_t* mod = nullptr;
|
||||
wasmtime_error_t* err = wasmtime_module_new(
|
||||
engine,
|
||||
patched.data(),
|
||||
patched.size(),
|
||||
&mod);
|
||||
|
||||
if (auto msg = wasmtimeError(err))
|
||||
return "Wasmtime validate failed: " + *msg;
|
||||
|
||||
wasmtime_module_delete(mod);
|
||||
return {};
|
||||
}
|
||||
|
||||
ExecutionResult
|
||||
execute(
|
||||
void const* wasm,
|
||||
size_t len,
|
||||
bool isCallback,
|
||||
uint32_t wasmParam,
|
||||
HookContext& ctx,
|
||||
std::vector<HostFunctionDecl> const& imports,
|
||||
ripple::Rules const& rules,
|
||||
beast::Journal const& j) override
|
||||
{
|
||||
wasm_engine_t* engine = getGlobalEngine();
|
||||
if (!engine)
|
||||
return {false, 0, "Could not obtain Wasmtime engine"};
|
||||
|
||||
// Ensure the module's memory is exported so that bridgeFn can access it
|
||||
// via wasmtime_caller_export_get("memory"). Hook modules often define
|
||||
// their own memory section without a corresponding export entry.
|
||||
auto patched = ensureMemoryExported(wasm, len);
|
||||
|
||||
// ── Compile module ─────────────────────────────────────────────────
|
||||
wasmtime_module_t* mod = nullptr;
|
||||
{
|
||||
wasmtime_error_t* err = wasmtime_module_new(
|
||||
engine,
|
||||
patched.data(),
|
||||
patched.size(),
|
||||
&mod);
|
||||
if (auto msg = wasmtimeError(err))
|
||||
return {false, 0, "Wasmtime compile failed: " + *msg};
|
||||
}
|
||||
|
||||
// ── Create store + context ─────────────────────────────────────────
|
||||
wasmtime_store_t* store = wasmtime_store_new(engine, nullptr, nullptr);
|
||||
if (!store)
|
||||
{
|
||||
wasmtime_module_delete(mod);
|
||||
return {false, 0, "Could not create Wasmtime store"};
|
||||
}
|
||||
wasmtime_context_t* storeCtx = wasmtime_store_context(store);
|
||||
|
||||
// Set initial fuel (consensus-fixed)
|
||||
{
|
||||
wasmtime_error_t* err =
|
||||
wasmtime_context_set_fuel(storeCtx, kWasmtimeInitialFuel);
|
||||
if (auto msg = wasmtimeError(err))
|
||||
{
|
||||
wasmtime_store_delete(store);
|
||||
wasmtime_module_delete(mod);
|
||||
return {false, 0, "Could not set fuel: " + *msg};
|
||||
}
|
||||
}
|
||||
|
||||
// ── Per-execution termination state ───────────────────────────────
|
||||
ExecState execState;
|
||||
|
||||
// ── Build bridge data (must stay stable in memory) ─────────────────
|
||||
std::vector<BridgeData> bridgeData;
|
||||
bridgeData.reserve(imports.size());
|
||||
|
||||
// ── Create linker ──────────────────────────────────────────────────
|
||||
wasmtime_linker_t* linker = wasmtime_linker_new(engine);
|
||||
|
||||
static const char kEnvModule[] = "env";
|
||||
static const size_t kEnvModuleLen = 3; // strlen("env")
|
||||
|
||||
for (auto const& decl : imports)
|
||||
{
|
||||
// Feature gate: skip if this API is behind a disabled Amendment
|
||||
if (decl.featureGate && !(*decl.featureGate).isZero() &&
|
||||
!rules.enabled(*decl.featureGate))
|
||||
continue;
|
||||
|
||||
bridgeData.push_back({&decl, &ctx, &execState});
|
||||
|
||||
wasm_functype_t* ft = buildFuncType(decl.params, decl.result);
|
||||
|
||||
// NOTE: wasmtime_linker_define_func takes a raw `data` pointer
|
||||
// but does NOT call the finalizer per-call (only when the linker
|
||||
// is deleted or shadowed). Since bridgeData is alive for the
|
||||
// duration of the execution, this is safe.
|
||||
wasmtime_error_t* err = wasmtime_linker_define_func(
|
||||
linker,
|
||||
kEnvModule,
|
||||
kEnvModuleLen,
|
||||
decl.name,
|
||||
std::strlen(decl.name),
|
||||
ft,
|
||||
bridgeFn,
|
||||
&bridgeData.back(),
|
||||
nullptr);
|
||||
|
||||
wasm_functype_delete(ft);
|
||||
|
||||
if (auto msg = wasmtimeError(err))
|
||||
{
|
||||
wasmtime_linker_delete(linker);
|
||||
wasmtime_store_delete(store);
|
||||
wasmtime_module_delete(mod);
|
||||
return {false, 0, "Linker define_func failed: " + *msg};
|
||||
}
|
||||
}
|
||||
|
||||
// ── Define the "table" import (funcref, min=10, max=20) ────────────
|
||||
{
|
||||
wasm_limits_t tableLimits = {10, 20};
|
||||
wasm_tabletype_t* tt =
|
||||
wasm_tabletype_new(wasm_valtype_new(WASM_FUNCREF), &tableLimits);
|
||||
|
||||
wasmtime_table_t tbl;
|
||||
wasmtime_val_t initVal;
|
||||
initVal.kind = WASMTIME_FUNCREF;
|
||||
wasmtime_funcref_set_null(&initVal.of.funcref);
|
||||
|
||||
wasmtime_error_t* err =
|
||||
wasmtime_table_new(storeCtx, tt, &initVal, &tbl);
|
||||
wasm_tabletype_delete(tt);
|
||||
|
||||
if (auto msg = wasmtimeError(err))
|
||||
{
|
||||
wasmtime_linker_delete(linker);
|
||||
wasmtime_store_delete(store);
|
||||
wasmtime_module_delete(mod);
|
||||
return {false, 0, "Table creation failed: " + *msg};
|
||||
}
|
||||
|
||||
wasmtime_extern_t ext;
|
||||
ext.kind = WASMTIME_EXTERN_TABLE;
|
||||
ext.of.table = tbl;
|
||||
|
||||
err = wasmtime_linker_define(
|
||||
linker,
|
||||
storeCtx,
|
||||
kEnvModule,
|
||||
kEnvModuleLen,
|
||||
"table",
|
||||
5,
|
||||
&ext);
|
||||
|
||||
if (auto msg = wasmtimeError(err))
|
||||
{
|
||||
wasmtime_linker_delete(linker);
|
||||
wasmtime_store_delete(store);
|
||||
wasmtime_module_delete(mod);
|
||||
return {false, 0, "Linker define table failed: " + *msg};
|
||||
}
|
||||
}
|
||||
|
||||
// ── Define the "memory" import (min=1, max=1 pages) ───────────────
|
||||
// This is provided for modules that import their memory from "env".
|
||||
// Modules with own memory sections (Section 5) ignore this definition
|
||||
// and use their own memory instead (which ensureMemoryExported() will
|
||||
// have made accessible via a "memory" export).
|
||||
{
|
||||
wasm_memorytype_t* mt;
|
||||
{
|
||||
wasmtime_error_t* err = wasmtime_memorytype_new(
|
||||
1, // min pages
|
||||
true, // max_present
|
||||
1, // max pages
|
||||
false, // is_64
|
||||
false, // shared
|
||||
0, // page_size_log2 (0 = default 64KiB)
|
||||
&mt);
|
||||
if (auto msg = wasmtimeError(err))
|
||||
{
|
||||
wasmtime_linker_delete(linker);
|
||||
wasmtime_store_delete(store);
|
||||
wasmtime_module_delete(mod);
|
||||
return {false, 0, "Memory type creation failed: " + *msg};
|
||||
}
|
||||
}
|
||||
|
||||
wasmtime_memory_t mem;
|
||||
wasmtime_error_t* err = wasmtime_memory_new(storeCtx, mt, &mem);
|
||||
wasm_memorytype_delete(mt);
|
||||
|
||||
if (auto msg = wasmtimeError(err))
|
||||
{
|
||||
wasmtime_linker_delete(linker);
|
||||
wasmtime_store_delete(store);
|
||||
wasmtime_module_delete(mod);
|
||||
return {false, 0, "Memory creation failed: " + *msg};
|
||||
}
|
||||
|
||||
wasmtime_extern_t ext;
|
||||
ext.kind = WASMTIME_EXTERN_MEMORY;
|
||||
ext.of.memory = mem;
|
||||
|
||||
err = wasmtime_linker_define(
|
||||
linker,
|
||||
storeCtx,
|
||||
kEnvModule,
|
||||
kEnvModuleLen,
|
||||
"memory",
|
||||
6,
|
||||
&ext);
|
||||
|
||||
if (auto msg = wasmtimeError(err))
|
||||
{
|
||||
wasmtime_linker_delete(linker);
|
||||
wasmtime_store_delete(store);
|
||||
wasmtime_module_delete(mod);
|
||||
return {false, 0, "Linker define memory failed: " + *msg};
|
||||
}
|
||||
}
|
||||
|
||||
JLOG(j.trace()) << "HookInfo[" << ctx.result.account << "-"
|
||||
<< ctx.result.otxnAccount
|
||||
<< "]: creating wasmtime instance";
|
||||
|
||||
// ── Instantiate ───────────────────────────────────────────────────
|
||||
wasmtime_instance_t instance;
|
||||
{
|
||||
wasm_trap_t* trap = nullptr;
|
||||
wasmtime_error_t* err = wasmtime_linker_instantiate(
|
||||
linker, storeCtx, mod, &instance, &trap);
|
||||
|
||||
if (auto msg = wasmtimeError(err))
|
||||
{
|
||||
wasmtime_linker_delete(linker);
|
||||
wasmtime_store_delete(store);
|
||||
wasmtime_module_delete(mod);
|
||||
return {false, 0, "Instantiation error: " + *msg};
|
||||
}
|
||||
if (trap)
|
||||
{
|
||||
auto msg = wasmtimeTrap(trap);
|
||||
wasmtime_linker_delete(linker);
|
||||
wasmtime_store_delete(store);
|
||||
wasmtime_module_delete(mod);
|
||||
return {
|
||||
false,
|
||||
0,
|
||||
"Instantiation trap: " + (msg ? *msg : "unknown")};
|
||||
}
|
||||
}
|
||||
|
||||
// ── Look up "hook" or "cbak" export ───────────────────────────────
|
||||
const char* funcName = isCallback ? "cbak" : "hook";
|
||||
size_t funcNameLen = isCallback ? 4 : 4;
|
||||
|
||||
wasmtime_extern_t funcExtern;
|
||||
if (!wasmtime_instance_export_get(
|
||||
storeCtx,
|
||||
&instance,
|
||||
funcName,
|
||||
funcNameLen,
|
||||
&funcExtern) ||
|
||||
funcExtern.kind != WASMTIME_EXTERN_FUNC)
|
||||
{
|
||||
wasmtime_linker_delete(linker);
|
||||
wasmtime_store_delete(store);
|
||||
wasmtime_module_delete(mod);
|
||||
return {
|
||||
false,
|
||||
0,
|
||||
std::string("WASM export '") + funcName + "' not found"};
|
||||
}
|
||||
|
||||
// ── Call hook/cbak with wasmParam ──────────────────────────────────
|
||||
wasmtime_val_t callArgs[1];
|
||||
callArgs[0].kind = WASMTIME_I32;
|
||||
callArgs[0].of.i32 = (int32_t)wasmParam;
|
||||
|
||||
wasmtime_val_t callResults[1];
|
||||
wasm_trap_t* callTrap = nullptr;
|
||||
|
||||
wasmtime_error_t* callErr = wasmtime_func_call(
|
||||
storeCtx,
|
||||
&funcExtern.of.func,
|
||||
callArgs,
|
||||
1,
|
||||
callResults,
|
||||
1,
|
||||
&callTrap);
|
||||
|
||||
// Capture fuel consumed before cleaning up
|
||||
uint64_t fuelRemaining = 0;
|
||||
wasmtime_context_get_fuel(storeCtx, &fuelRemaining);
|
||||
uint64_t instructionCount = 0;
|
||||
if (kWasmtimeInitialFuel >= fuelRemaining)
|
||||
instructionCount = kWasmtimeInitialFuel - fuelRemaining;
|
||||
|
||||
wasmtime_linker_delete(linker);
|
||||
wasmtime_store_delete(store);
|
||||
wasmtime_module_delete(mod);
|
||||
|
||||
// ── Interpret result ───────────────────────────────────────────────
|
||||
//
|
||||
// When a host callback returns a wasm_trap_t* to signal accept() or
|
||||
// rollback(), Wasmtime propagates that as a wasmtime_error_t (with the
|
||||
// trap embedded as the cause) rather than via the wasm_trap_t** output.
|
||||
// We therefore check execState.terminated FIRST — before inspecting
|
||||
// callErr or callTrap — so that clean hook terminations (accept/rollback)
|
||||
// are always reported as ok=true regardless of which output pointer
|
||||
// Wasmtime chose to use.
|
||||
|
||||
if (execState.terminated)
|
||||
{
|
||||
// Hook called accept() or rollback(): clean termination.
|
||||
// Free any error/trap resources Wasmtime may have allocated.
|
||||
if (callErr)
|
||||
wasmtime_error_delete(callErr);
|
||||
if (callTrap)
|
||||
wasm_trap_delete(callTrap);
|
||||
return {true, instructionCount, {}};
|
||||
}
|
||||
|
||||
// Programmer error (wrong arg types, mismatched store, etc.)
|
||||
if (callErr)
|
||||
{
|
||||
auto msg = wasmtimeError(callErr);
|
||||
return {false, instructionCount, "WASM call error: " + (msg ? *msg : "")};
|
||||
}
|
||||
|
||||
if (callTrap)
|
||||
{
|
||||
// Check for out-of-fuel trap
|
||||
wasmtime_trap_code_t code = 0;
|
||||
if (wasmtime_trap_code(callTrap, &code) &&
|
||||
code == WASMTIME_TRAP_CODE_OUT_OF_FUEL)
|
||||
{
|
||||
wasm_trap_delete(callTrap);
|
||||
return {false, instructionCount, "WASM out of fuel"};
|
||||
}
|
||||
|
||||
auto msg = wasmtimeTrap(callTrap);
|
||||
return {
|
||||
false,
|
||||
instructionCount,
|
||||
"WASM trap: " + (msg ? *msg : "unknown trap")};
|
||||
}
|
||||
|
||||
return {true, instructionCount, {}};
|
||||
}
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// ── Factory ──────────────────────────────────────────────────────────────────
|
||||
|
||||
std::unique_ptr<IWasmEngine>
|
||||
makeWasmtimeEngine()
|
||||
{
|
||||
return std::make_unique<WasmtimeEngineImpl>();
|
||||
}
|
||||
|
||||
} // namespace hook
|
||||
14
src/xrpld/app/hook/detail/WasmtimeEngine.h
Normal file
14
src/xrpld/app/hook/detail/WasmtimeEngine.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef RIPPLE_APP_HOOK_DETAIL_WASMTIMEENGINE_H_INCLUDED
|
||||
#define RIPPLE_APP_HOOK_DETAIL_WASMTIMEENGINE_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/hook/detail/WasmEngine.h>
|
||||
#include <memory>
|
||||
|
||||
namespace hook {
|
||||
|
||||
std::unique_ptr<IWasmEngine>
|
||||
makeWasmtimeEngine();
|
||||
|
||||
} // namespace hook
|
||||
|
||||
#endif // RIPPLE_APP_HOOK_DETAIL_WASMTIMEENGINE_H_INCLUDED
|
||||
@@ -19,7 +19,9 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <wasmedge/wasmedge.h>
|
||||
#include <xrpld/app/hook/detail/WasmEngine.h>
|
||||
#include <xrpld/app/hook/detail/WasmEdgeEngine.h>
|
||||
#include <xrpld/app/hook/detail/WasmtimeEngine.h>
|
||||
|
||||
using namespace ripple;
|
||||
|
||||
@@ -1082,10 +1084,25 @@ hook::apply(
|
||||
|
||||
auto const& j = applyCtx.app.journal("View");
|
||||
|
||||
HookExecutor executor{hookCtx};
|
||||
|
||||
executor.executeWasm(
|
||||
wasm.data(), (size_t)wasm.size(), isCallback, wasmParam, j);
|
||||
auto engine = applyCtx.view().rules().enabled(featureWasmtimeEngine)
|
||||
? makeWasmtimeEngine()
|
||||
: makeWasmEdgeEngine();
|
||||
auto const engineResult = engine->execute(
|
||||
wasm.data(),
|
||||
(size_t)wasm.size(),
|
||||
isCallback,
|
||||
wasmParam,
|
||||
hookCtx,
|
||||
hookHostFunctionDecls(),
|
||||
applyCtx.view().rules(),
|
||||
j);
|
||||
hookCtx.result.instructionCount = engineResult.instructionCount;
|
||||
if (!engineResult.ok)
|
||||
{
|
||||
hookCtx.result.exitType = hook_api::ExitType::WASM_ERROR;
|
||||
JLOG(j.warn()) << "HookError[" << HC_ACC() << "]: "
|
||||
<< engineResult.error.value_or("unknown");
|
||||
}
|
||||
|
||||
JLOG(j.trace()) << "HookInfo[" << HC_ACC() << "]: "
|
||||
<< (hookCtx.result.exitType == hook_api::ExitType::ROLLBACK
|
||||
@@ -1256,7 +1273,7 @@ DEFINE_HOOK_FUNCTION(
|
||||
{
|
||||
return state_foreign_set(
|
||||
hookCtx,
|
||||
frameCtx,
|
||||
mem,
|
||||
read_ptr,
|
||||
read_len,
|
||||
kread_ptr,
|
||||
@@ -1634,7 +1651,7 @@ DEFINE_HOOK_FUNCTION(
|
||||
{
|
||||
return state_foreign(
|
||||
hookCtx,
|
||||
frameCtx,
|
||||
mem,
|
||||
write_ptr,
|
||||
write_len,
|
||||
kread_ptr,
|
||||
@@ -3196,7 +3213,7 @@ DEFINE_HOOK_FUNCTION(
|
||||
// proxy only no setup or teardown
|
||||
auto ret = sto_emplace(
|
||||
hookCtx,
|
||||
frameCtx,
|
||||
mem,
|
||||
write_ptr,
|
||||
write_len,
|
||||
read_ptr,
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/hook/applyHook.h>
|
||||
#include <xrpld/app/hook/detail/WasmEdgeEngine.h>
|
||||
#include <xrpld/app/hook/detail/WasmtimeEngine.h>
|
||||
#include <xrpld/app/ledger/Ledger.h>
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/app/misc/AmendmentTable.h>
|
||||
@@ -637,8 +639,11 @@ Change::activateXahauGenesis()
|
||||
return;
|
||||
}
|
||||
|
||||
auto wasmValidator = ctx_.view().rules().enabled(featureWasmtimeEngine)
|
||||
? hook::makeWasmtimeEngine()
|
||||
: hook::makeWasmEdgeEngine();
|
||||
std::optional<std::string> result2 =
|
||||
hook::HookExecutor::validateWasm(
|
||||
wasmValidator->validate(
|
||||
wasmBytes.data(), (size_t)wasmBytes.size());
|
||||
|
||||
if (result2)
|
||||
|
||||
@@ -48,7 +48,9 @@
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <wasmedge/wasmedge.h>
|
||||
#include <xrpld/app/hook/detail/WasmEngine.h>
|
||||
#include <xrpld/app/hook/detail/WasmEdgeEngine.h>
|
||||
#include <xrpld/app/hook/detail/WasmtimeEngine.h>
|
||||
|
||||
#define DEBUG_GUARD_CHECK 1
|
||||
#define HS_ACC() \
|
||||
@@ -592,8 +594,11 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
<< "]: Trying to wasm instantiate proposed hook "
|
||||
<< "size = " << hook.size();
|
||||
|
||||
auto wasmValidator = ctx.rules.enabled(featureWasmtimeEngine)
|
||||
? hook::makeWasmtimeEngine()
|
||||
: hook::makeWasmEdgeEngine();
|
||||
std::optional<std::string> result2 =
|
||||
hook::HookExecutor::validateWasm(
|
||||
wasmValidator->validate(
|
||||
hook.data(), (size_t)hook.size());
|
||||
|
||||
if (result2)
|
||||
|
||||
Reference in New Issue
Block a user