mirror of
https://github.com/Xahau/xahaud.git
synced 2026-07-18 04:30:11 +00:00
Compare commits
3 Commits
fixHookMem
...
wasmtime-e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
132a3a8520 | ||
|
|
0e8cedc7a9 | ||
|
|
e07b573753 |
@@ -124,7 +124,8 @@ find_package(date REQUIRED)
|
|||||||
find_package(xxHash REQUIRED)
|
find_package(xxHash REQUIRED)
|
||||||
find_package(magic_enum REQUIRED)
|
find_package(magic_enum REQUIRED)
|
||||||
|
|
||||||
include(deps/WasmEdge)
|
include(deps/WasmEdge)
|
||||||
|
include(deps/Wasmtime)
|
||||||
if(TARGET nudb::core)
|
if(TARGET nudb::core)
|
||||||
set(nudb nudb::core)
|
set(nudb nudb::core)
|
||||||
elseif(TARGET NuDB::nudb)
|
elseif(TARGET NuDB::nudb)
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ target_link_libraries(xrpl.imports.main
|
|||||||
OpenSSL::Crypto
|
OpenSSL::Crypto
|
||||||
Ripple::boost
|
Ripple::boost
|
||||||
wasmedge::wasmedge
|
wasmedge::wasmedge
|
||||||
|
wasmtime::wasmtime
|
||||||
Ripple::opts
|
Ripple::opts
|
||||||
Ripple::syslibs
|
Ripple::syslibs
|
||||||
absl::random_random
|
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],
|
'unity': [True, False],
|
||||||
'xrpld': [True, False],
|
'xrpld': [True, False],
|
||||||
'with_wasmedge': [True, False],
|
'with_wasmedge': [True, False],
|
||||||
|
'with_wasmtime': [True, False],
|
||||||
'tool_requires_b2': [True, False],
|
'tool_requires_b2': [True, False],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,6 +54,7 @@ class Xrpl(ConanFile):
|
|||||||
'unity': False,
|
'unity': False,
|
||||||
'xrpld': False,
|
'xrpld': False,
|
||||||
'with_wasmedge': True,
|
'with_wasmedge': True,
|
||||||
|
'with_wasmtime': True,
|
||||||
'tool_requires_b2': False,
|
'tool_requires_b2': False,
|
||||||
|
|
||||||
'date/*:header_only': False,
|
'date/*:header_only': False,
|
||||||
@@ -121,6 +123,8 @@ class Xrpl(ConanFile):
|
|||||||
|
|
||||||
if self.options.with_wasmedge:
|
if self.options.with_wasmedge:
|
||||||
self.requires('wasmedge/0.11.2@xahaud/stable')
|
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:
|
if self.options.jemalloc:
|
||||||
self.requires('jemalloc/5.3.0')
|
self.requires('jemalloc/5.3.0')
|
||||||
if self.options.rocksdb:
|
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.
|
* were then used.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <xrpl/hook/WasmTypes.h>
|
||||||
|
|
||||||
#define LPAREN (
|
#define LPAREN (
|
||||||
#define RPAREN )
|
#define RPAREN )
|
||||||
#define COMMA ,
|
#define COMMA ,
|
||||||
@@ -68,104 +70,90 @@
|
|||||||
#define SEP_uint64_t LPAREN uint64_t COMMA
|
#define SEP_uint64_t LPAREN uint64_t COMMA
|
||||||
#define SEP_int64_t LPAREN int64_t COMMA
|
#define SEP_int64_t LPAREN int64_t COMMA
|
||||||
|
|
||||||
#define VAL_uint32_t WasmEdge_ValueGetI32(in[_stack++])
|
// VAL_* : extract typed value from WasmValue in[] array (engine-agnostic)
|
||||||
#define VAL_int32_t WasmEdge_ValueGetI32(in[_stack++])
|
#define VAL_uint32_t (uint32_t) in[_stack++].asI32()
|
||||||
#define VAL_uint64_t WasmEdge_ValueGetI64(in[_stack++])
|
#define VAL_int32_t (int32_t) in[_stack++].asI32()
|
||||||
#define VAL_int64_t WasmEdge_ValueGetI64(in[_stack++])
|
#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 VAR_ASSIGN(T, V) T V = CAT(VAL_##T)
|
||||||
|
|
||||||
#define RET_uint32_t(return_code) WasmEdge_ValueGenI32(return_code)
|
// RET_* : wrap a C return value into a WasmValue
|
||||||
#define RET_int32_t(return_code) WasmEdge_ValueGenI32(return_code)
|
#define RET_uint32_t(return_code) hook::WasmValue::i32((uint32_t)(return_code))
|
||||||
#define RET_uint64_t(return_code) WasmEdge_ValueGenI64(return_code)
|
#define RET_int32_t(return_code) hook::WasmValue::i32((uint32_t)(return_code))
|
||||||
#define RET_int64_t(return_code) WasmEdge_ValueGenI64(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 RET_ASSIGN(T, return_code) CAT2(RET_, T(return_code))
|
||||||
|
|
||||||
#define TYP_uint32_t WasmEdge_ValType_I32
|
// TYP_* : map C type to WasmValue::Kind (used by WasmEdgeEngine.cpp registry)
|
||||||
#define TYP_int32_t WasmEdge_ValType_I32
|
#define TYP_uint32_t hook::WasmValue::Kind::I32
|
||||||
#define TYP_uint64_t WasmEdge_ValType_I64
|
#define TYP_int32_t hook::WasmValue::Kind::I32
|
||||||
#define TYP_int64_t WasmEdge_ValType_I64
|
#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 WASM_VAL_TYPE(T, b) CAT2(TYP_, T)
|
||||||
|
|
||||||
#define UNSIGNED_TYPE(T) std::make_unsigned_t<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, ...) \
|
#define DECLARE_HOOK_FUNCTION(R, F, ...) \
|
||||||
std::variant<UNSIGNED_TYPE(R), hook_api::hook_return_code> F( \
|
std::variant<UNSIGNED_TYPE(R), hook_api::hook_return_code> F( \
|
||||||
hook::HookContext& hookCtx, \
|
hook::HookContext& hookCtx, \
|
||||||
WasmEdge_CallingFrameContext const& frameCtx __VA_OPT__( \
|
hook::GuestMemory& mem __VA_OPT__(COMMA __VA_ARGS__)); \
|
||||||
COMMA __VA_ARGS__)); \
|
extern hook::HostCallStatus WasmFunction##F( \
|
||||||
extern WasmEdge_Result WasmFunction##F( \
|
void* userData, \
|
||||||
void* data_ptr, \
|
hook::GuestMemory& mem, \
|
||||||
const WasmEdge_CallingFrameContext* frameCtx, \
|
hook::WasmValue const* in, \
|
||||||
const WasmEdge_Value* in, \
|
size_t inLen, \
|
||||||
WasmEdge_Value* out); \
|
hook::WasmValue* out, \
|
||||||
extern WasmEdge_ValType WasmFunctionParams##F[]; \
|
size_t outLen);
|
||||||
extern WasmEdge_ValType WasmFunctionResult##F[]; \
|
|
||||||
extern WasmEdge_FunctionTypeContext* WasmFunctionType##F; \
|
|
||||||
extern WasmEdge_String WasmFunctionName##F;
|
|
||||||
|
|
||||||
|
// DEFINE_HOOK_FUNCTION: defines the engine-agnostic wrapper + the impl function
|
||||||
|
// body
|
||||||
#define DEFINE_HOOK_FUNCTION(R, F, ...) \
|
#define DEFINE_HOOK_FUNCTION(R, F, ...) \
|
||||||
WasmEdge_Result hook_api::WasmFunction##F( \
|
hook::HostCallStatus hook_api::WasmFunction##F( \
|
||||||
void* data_ptr, \
|
void* data_ptr, \
|
||||||
const WasmEdge_CallingFrameContext* frameCtx, \
|
hook::GuestMemory& mem, \
|
||||||
const WasmEdge_Value* in, \
|
hook::WasmValue const* in, \
|
||||||
WasmEdge_Value* out) \
|
size_t /*inLen*/, \
|
||||||
|
hook::WasmValue* out, \
|
||||||
|
size_t /*outLen*/) \
|
||||||
{ \
|
{ \
|
||||||
__VA_OPT__(int _stack = 0;) \
|
__VA_OPT__(int _stack = 0;) \
|
||||||
__VA_OPT__(FOR_VARS(VAR_ASSIGN, 2, __VA_ARGS__);) \
|
__VA_OPT__(FOR_VARS(VAR_ASSIGN, 2, __VA_ARGS__);) \
|
||||||
hook::HookContext* hookCtx = \
|
hook::HookContext* hookCtx = \
|
||||||
reinterpret_cast<hook::HookContext*>(data_ptr); \
|
reinterpret_cast<hook::HookContext*>(data_ptr); \
|
||||||
auto const& return_code = hook_api::F( \
|
auto const& return_code = hook_api::F( \
|
||||||
*hookCtx, \
|
*hookCtx, mem __VA_OPT__(COMMA STRIP_TYPES(__VA_ARGS__))); \
|
||||||
*const_cast<WasmEdge_CallingFrameContext*>(frameCtx) \
|
|
||||||
__VA_OPT__(COMMA STRIP_TYPES(__VA_ARGS__))); \
|
|
||||||
if (std::holds_alternative<hook_api::hook_return_code>(return_code) && \
|
if (std::holds_alternative<hook_api::hook_return_code>(return_code) && \
|
||||||
(std::get<hook_api::hook_return_code>(return_code) == \
|
(std::get<hook_api::hook_return_code>(return_code) == \
|
||||||
RC_ROLLBACK || \
|
RC_ROLLBACK || \
|
||||||
std::get<hook_api::hook_return_code>(return_code) == RC_ACCEPT)) \
|
std::get<hook_api::hook_return_code>(return_code) == RC_ACCEPT)) \
|
||||||
return WasmEdge_Result_Terminate; \
|
return hook::HostCallStatus::Terminate; \
|
||||||
out[0] = RET_ASSIGN( \
|
out[0] = RET_ASSIGN( \
|
||||||
R, \
|
R, \
|
||||||
std::holds_alternative<UNSIGNED_TYPE(R)>(return_code) \
|
std::holds_alternative<UNSIGNED_TYPE(R)>(return_code) \
|
||||||
? std::get<UNSIGNED_TYPE(R)>(return_code) \
|
? std::get<UNSIGNED_TYPE(R)>(return_code) \
|
||||||
: R(std::get<hook_api::hook_return_code>(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( \
|
std::variant<UNSIGNED_TYPE(R), hook_api::hook_return_code> hook_api::F( \
|
||||||
hook::HookContext& hookCtx, \
|
hook::HookContext& hookCtx, \
|
||||||
WasmEdge_CallingFrameContext const& frameCtx __VA_OPT__( \
|
hook::GuestMemory& mem __VA_OPT__(COMMA __VA_ARGS__))
|
||||||
COMMA __VA_ARGS__))
|
|
||||||
|
|
||||||
#define HOOK_SETUP() \
|
#define HOOK_SETUP() \
|
||||||
using enum hook_api::hook_return_code; \
|
using enum hook_api::hook_return_code; \
|
||||||
try \
|
try \
|
||||||
{ \
|
{ \
|
||||||
[[maybe_unused]] ApplyContext& applyCtx = hookCtx.applyCtx; \
|
[[maybe_unused]] ApplyContext& applyCtx = hookCtx.applyCtx; \
|
||||||
[[maybe_unused]] auto& view = applyCtx.view(); \
|
[[maybe_unused]] auto& view = applyCtx.view(); \
|
||||||
[[maybe_unused]] auto j = applyCtx.app.journal("View"); \
|
[[maybe_unused]] auto j = applyCtx.app.journal("View"); \
|
||||||
[[maybe_unused]] WasmEdge_MemoryInstanceContext* memoryCtx = \
|
[[maybe_unused]] unsigned char* memory = mem.base; \
|
||||||
WasmEdge_CallingFrameGetMemoryInstance(&frameCtx, 0); \
|
[[maybe_unused]] const uint64_t memory_length = mem.size; \
|
||||||
[[maybe_unused]] unsigned char* memory = \
|
[[maybe_unused]] auto& api = hookCtx.api(); \
|
||||||
WasmEdge_MemoryInstanceGetPointer(memoryCtx, 0, 0); \
|
if (!memory || !memory_length) \
|
||||||
[[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;
|
return INTERNAL_ERROR;
|
||||||
|
|
||||||
#define HOOK_TEARDOWN() \
|
#define HOOK_TEARDOWN() \
|
||||||
@@ -197,11 +185,10 @@
|
|||||||
<< " bytes past end of wasm memory"; \
|
<< " bytes past end of wasm memory"; \
|
||||||
return OUT_OF_BOUNDS; \
|
return OUT_OF_BOUNDS; \
|
||||||
} \
|
} \
|
||||||
if (!WasmEdge_ResultOK(WasmEdge_MemoryInstanceSetData( \
|
if (!mem.write( \
|
||||||
memoryCtx, \
|
|
||||||
reinterpret_cast<const uint8_t*>(host_src_ptr), \
|
|
||||||
guest_dst_ptr, \
|
guest_dst_ptr, \
|
||||||
bytes_to_write))) \
|
reinterpret_cast<uint8_t const*>(host_src_ptr), \
|
||||||
|
static_cast<uint64_t>(bytes_to_write))) \
|
||||||
return INTERNAL_ERROR; \
|
return INTERNAL_ERROR; \
|
||||||
bytes_written += bytes_to_write; \
|
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`
|
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||||
// in include/xrpl/protocol/Feature.h.
|
// in include/xrpl/protocol/Feature.h.
|
||||||
|
|
||||||
|
XRPL_FEATURE(WasmtimeEngine, Supported::yes, VoteBehavior::DefaultNo)
|
||||||
XRPL_FIX (HookMap, Supported::yes, VoteBehavior::DefaultYes)
|
XRPL_FIX (HookMap, Supported::yes, VoteBehavior::DefaultYes)
|
||||||
XRPL_FIX (GuardDepth32, Supported::yes, VoteBehavior::DefaultNo)
|
XRPL_FIX (GuardDepth32, Supported::yes, VoteBehavior::DefaultNo)
|
||||||
XRPL_FEATURE(NamedHooks, Supported::yes, VoteBehavior::DefaultNo)
|
XRPL_FEATURE(NamedHooks, Supported::yes, VoteBehavior::DefaultNo)
|
||||||
|
|||||||
@@ -15202,8 +15202,9 @@ public:
|
|||||||
using namespace test::jtx;
|
using namespace test::jtx;
|
||||||
static FeatureBitset const all{supported_amendments()};
|
static FeatureBitset const all{supported_amendments()};
|
||||||
|
|
||||||
static std::array<FeatureBitset, 8> const feats{
|
static std::array<FeatureBitset, 9> const feats{
|
||||||
all,
|
all,
|
||||||
|
all - featureWasmtimeEngine,
|
||||||
all - fixXahauV2,
|
all - fixXahauV2,
|
||||||
all - fixXahauV1 - fixXahauV2,
|
all - fixXahauV1 - fixXahauV2,
|
||||||
all - fixXahauV1 - fixXahauV2 - fixNSDelete,
|
all - fixXahauV1 - fixXahauV2 - fixNSDelete,
|
||||||
@@ -15474,7 +15475,8 @@ SETHOOK_TEST(3, false)
|
|||||||
SETHOOK_TEST(4, false)
|
SETHOOK_TEST(4, false)
|
||||||
SETHOOK_TEST(5, false)
|
SETHOOK_TEST(5, false)
|
||||||
SETHOOK_TEST(6, 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(SetHook0, app, ripple, 2);
|
||||||
BEAST_DEFINE_TESTSUITE_PRIO(SetHook1, 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(SetHook5, app, ripple, 2);
|
||||||
BEAST_DEFINE_TESTSUITE_PRIO(SetHook6, app, ripple, 2);
|
BEAST_DEFINE_TESTSUITE_PRIO(SetHook6, app, ripple, 2);
|
||||||
BEAST_DEFINE_TESTSUITE_PRIO(SetHook7, app, ripple, 2);
|
BEAST_DEFINE_TESTSUITE_PRIO(SetHook7, app, ripple, 2);
|
||||||
|
BEAST_DEFINE_TESTSUITE_PRIO(SetHook8, app, ripple, 2);
|
||||||
} // namespace test
|
} // namespace test
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
#undef M
|
#undef M
|
||||||
|
|||||||
@@ -97,7 +97,6 @@ struct StubHookContext
|
|||||||
std::map<uint32_t, uint32_t> guard_map{};
|
std::map<uint32_t, uint32_t> guard_map{};
|
||||||
StubHookResult result = {};
|
StubHookResult result = {};
|
||||||
std::optional<ripple::STObject> emitFailure = std::nullopt;
|
std::optional<ripple::STObject> emitFailure = std::nullopt;
|
||||||
const hook::HookExecutor* module = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Overload that takes external stateMap to avoid dangling reference
|
// Overload that takes external stateMap to avoid dangling reference
|
||||||
|
|||||||
@@ -145,8 +145,7 @@ makeStubHookContext(
|
|||||||
.executeAgainAsWeak = result.executeAgainAsWeak,
|
.executeAgainAsWeak = result.executeAgainAsWeak,
|
||||||
.provisionalMeta = result.provisionalMeta,
|
.provisionalMeta = result.provisionalMeta,
|
||||||
},
|
},
|
||||||
.emitFailure = stubHookContext.emitFailure,
|
.emitFailure = stubHookContext.emitFailure};
|
||||||
.module = nullptr};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Original function - WARNING: stateMap reference may become dangling
|
// Original function - WARNING: stateMap reference may become dangling
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
#include <queue>
|
#include <queue>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <wasmedge/wasmedge.h>
|
|
||||||
|
|
||||||
namespace hook {
|
namespace hook {
|
||||||
struct HookContext;
|
struct HookContext;
|
||||||
@@ -179,8 +178,6 @@ struct HookResult
|
|||||||
foreignStateGrantCache; // add found grants here to avoid rechecking
|
foreignStateGrantCache; // add found grants here to avoid rechecking
|
||||||
};
|
};
|
||||||
|
|
||||||
class HookExecutor;
|
|
||||||
|
|
||||||
struct SlotEntry
|
struct SlotEntry
|
||||||
{
|
{
|
||||||
std::shared_ptr<const ripple::STObject> storage;
|
std::shared_ptr<const ripple::STObject> storage;
|
||||||
@@ -217,7 +214,6 @@ struct HookContext
|
|||||||
emitFailure; // if this is a callback from a failed
|
emitFailure; // if this is a callback from a failed
|
||||||
// emitted txn then this optional becomes
|
// emitted txn then this optional becomes
|
||||||
// populated with the SLE
|
// populated with the SLE
|
||||||
const HookExecutor* module = 0;
|
|
||||||
|
|
||||||
// Lazy-initialized HookAPI member
|
// Lazy-initialized HookAPI member
|
||||||
mutable std::unique_ptr<HookAPI> api_;
|
mutable std::unique_ptr<HookAPI> api_;
|
||||||
@@ -272,231 +268,9 @@ gatherHookParameters(
|
|||||||
std::map<std::vector<uint8_t>, std::vector<uint8_t>>& parameters,
|
std::map<std::vector<uint8_t>, std::vector<uint8_t>>& parameters,
|
||||||
beast::Journal const& j_);
|
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 HR_ACC() hookResult.account << "-" << hookResult.otxnAccount
|
||||||
#define HC_ACC() hookCtx.result.account << "-" << hookCtx.result.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
|
} // namespace hook
|
||||||
|
|
||||||
#endif
|
#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 <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#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;
|
using namespace ripple;
|
||||||
|
|
||||||
@@ -1082,10 +1084,25 @@ hook::apply(
|
|||||||
|
|
||||||
auto const& j = applyCtx.app.journal("View");
|
auto const& j = applyCtx.app.journal("View");
|
||||||
|
|
||||||
HookExecutor executor{hookCtx};
|
auto engine = applyCtx.view().rules().enabled(featureWasmtimeEngine)
|
||||||
|
? makeWasmtimeEngine()
|
||||||
executor.executeWasm(
|
: makeWasmEdgeEngine();
|
||||||
wasm.data(), (size_t)wasm.size(), isCallback, wasmParam, j);
|
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() << "]: "
|
JLOG(j.trace()) << "HookInfo[" << HC_ACC() << "]: "
|
||||||
<< (hookCtx.result.exitType == hook_api::ExitType::ROLLBACK
|
<< (hookCtx.result.exitType == hook_api::ExitType::ROLLBACK
|
||||||
@@ -1256,7 +1273,7 @@ DEFINE_HOOK_FUNCTION(
|
|||||||
{
|
{
|
||||||
return state_foreign_set(
|
return state_foreign_set(
|
||||||
hookCtx,
|
hookCtx,
|
||||||
frameCtx,
|
mem,
|
||||||
read_ptr,
|
read_ptr,
|
||||||
read_len,
|
read_len,
|
||||||
kread_ptr,
|
kread_ptr,
|
||||||
@@ -1634,7 +1651,7 @@ DEFINE_HOOK_FUNCTION(
|
|||||||
{
|
{
|
||||||
return state_foreign(
|
return state_foreign(
|
||||||
hookCtx,
|
hookCtx,
|
||||||
frameCtx,
|
mem,
|
||||||
write_ptr,
|
write_ptr,
|
||||||
write_len,
|
write_len,
|
||||||
kread_ptr,
|
kread_ptr,
|
||||||
@@ -3196,7 +3213,7 @@ DEFINE_HOOK_FUNCTION(
|
|||||||
// proxy only no setup or teardown
|
// proxy only no setup or teardown
|
||||||
auto ret = sto_emplace(
|
auto ret = sto_emplace(
|
||||||
hookCtx,
|
hookCtx,
|
||||||
frameCtx,
|
mem,
|
||||||
write_ptr,
|
write_ptr,
|
||||||
write_len,
|
write_len,
|
||||||
read_ptr,
|
read_ptr,
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
#include <xrpld/app/hook/applyHook.h>
|
#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/ledger/Ledger.h>
|
||||||
#include <xrpld/app/main/Application.h>
|
#include <xrpld/app/main/Application.h>
|
||||||
#include <xrpld/app/misc/AmendmentTable.h>
|
#include <xrpld/app/misc/AmendmentTable.h>
|
||||||
@@ -637,8 +639,11 @@ Change::activateXahauGenesis()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto wasmValidator = ctx_.view().rules().enabled(featureWasmtimeEngine)
|
||||||
|
? hook::makeWasmtimeEngine()
|
||||||
|
: hook::makeWasmEdgeEngine();
|
||||||
std::optional<std::string> result2 =
|
std::optional<std::string> result2 =
|
||||||
hook::HookExecutor::validateWasm(
|
wasmValidator->validate(
|
||||||
wasmBytes.data(), (size_t)wasmBytes.size());
|
wasmBytes.data(), (size_t)wasmBytes.size());
|
||||||
|
|
||||||
if (result2)
|
if (result2)
|
||||||
|
|||||||
@@ -48,7 +48,9 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
#include <vector>
|
#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 DEBUG_GUARD_CHECK 1
|
||||||
#define HS_ACC() \
|
#define HS_ACC() \
|
||||||
@@ -592,8 +594,11 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
|||||||
<< "]: Trying to wasm instantiate proposed hook "
|
<< "]: Trying to wasm instantiate proposed hook "
|
||||||
<< "size = " << hook.size();
|
<< "size = " << hook.size();
|
||||||
|
|
||||||
|
auto wasmValidator = ctx.rules.enabled(featureWasmtimeEngine)
|
||||||
|
? hook::makeWasmtimeEngine()
|
||||||
|
: hook::makeWasmEdgeEngine();
|
||||||
std::optional<std::string> result2 =
|
std::optional<std::string> result2 =
|
||||||
hook::HookExecutor::validateWasm(
|
wasmValidator->validate(
|
||||||
hook.data(), (size_t)hook.size());
|
hook.data(), (size_t)hook.size());
|
||||||
|
|
||||||
if (result2)
|
if (result2)
|
||||||
|
|||||||
Reference in New Issue
Block a user