mirror of
https://github.com/Xahau/xahaud.git
synced 2026-04-15 00:02:22 +00:00
Compare commits
14 Commits
dev
...
external-e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e2c69deb2 | ||
|
|
ff763a500c | ||
|
|
a605aec57a | ||
|
|
bfcbbc3c5e | ||
|
|
d782f8cab4 | ||
|
|
8a61dd44e0 | ||
|
|
a8ca62a148 | ||
|
|
b7aeff95a9 | ||
|
|
b880c80c2b | ||
|
|
8666cdfb71 | ||
|
|
6d2a0b4e8b | ||
|
|
739ebfaba4 | ||
|
|
65166a9329 | ||
|
|
ca469b5d22 |
@@ -68,6 +68,17 @@ target_link_libraries(xrpl.imports.main
|
|||||||
$<$<BOOL:${voidstar}>:antithesis-sdk-cpp>
|
$<$<BOOL:${voidstar}>:antithesis-sdk-cpp>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# date-tz for enhanced logging (always linked, code is #ifdef guarded)
|
||||||
|
if(TARGET date::date-tz)
|
||||||
|
target_link_libraries(xrpl.imports.main INTERFACE date::date-tz)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# BEAST_ENHANCED_LOGGING: enable for Debug builds OR when explicitly requested
|
||||||
|
# Uses generator expression so it works with multi-config generators (Xcode, VS, Ninja Multi-Config)
|
||||||
|
target_compile_definitions(xrpl.imports.main INTERFACE
|
||||||
|
$<$<OR:$<CONFIG:Debug>,$<BOOL:${BEAST_ENHANCED_LOGGING}>>:BEAST_ENHANCED_LOGGING=1>
|
||||||
|
)
|
||||||
|
|
||||||
include(add_module)
|
include(add_module)
|
||||||
include(target_link_modules)
|
include(target_link_modules)
|
||||||
|
|
||||||
@@ -167,7 +178,108 @@ if(xrpld)
|
|||||||
file(GLOB_RECURSE sources CONFIGURE_DEPENDS
|
file(GLOB_RECURSE sources CONFIGURE_DEPENDS
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/test/*.cpp"
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/test/*.cpp"
|
||||||
)
|
)
|
||||||
|
if(HOOKS_TEST_ONLY OR DEFINED ENV{HOOKS_TEST_ONLY})
|
||||||
|
# Keep test infra but drop the individual *_test.cpp files
|
||||||
|
list(FILTER sources EXCLUDE REGEX "_test\\.cpp$")
|
||||||
|
message(STATUS "HOOKS_TEST_ONLY: excluded *_test.cpp from src/test/")
|
||||||
|
endif()
|
||||||
target_sources(rippled PRIVATE ${sources})
|
target_sources(rippled PRIVATE ${sources})
|
||||||
|
|
||||||
|
# Optional: include external hook test sources from another directory.
|
||||||
|
# Set via -DHOOKS_TEST_DIR=/path/to/tests or env HOOKS_TEST_DIR.
|
||||||
|
# Optionally set HOOKS_C_DIR to pass --hooks-c-dir args to the compiler
|
||||||
|
# (e.g. "tipbot=/path/to/hooks" — multiple values separated by ";").
|
||||||
|
#
|
||||||
|
# hookz build-test-hooks must be on PATH. It auto-compiles hooks referenced
|
||||||
|
# in each *_test.cpp and generates *_test_hooks.h next to the test file.
|
||||||
|
if(NOT HOOKS_TEST_DIR AND DEFINED ENV{HOOKS_TEST_DIR})
|
||||||
|
set(HOOKS_TEST_DIR $ENV{HOOKS_TEST_DIR})
|
||||||
|
endif()
|
||||||
|
if(NOT HOOKS_C_DIR AND DEFINED ENV{HOOKS_C_DIR})
|
||||||
|
set(HOOKS_C_DIR $ENV{HOOKS_C_DIR})
|
||||||
|
endif()
|
||||||
|
if(HOOKS_TEST_DIR AND EXISTS "${HOOKS_TEST_DIR}")
|
||||||
|
file(GLOB EXTERNAL_HOOK_TESTS CONFIGURE_DEPENDS
|
||||||
|
"${HOOKS_TEST_DIR}/*_test.cpp"
|
||||||
|
)
|
||||||
|
if(EXTERNAL_HOOK_TESTS)
|
||||||
|
# Build extra args for hookz build-test-hooks
|
||||||
|
set(_hooks_extra_args "")
|
||||||
|
set(_hooks_source_deps "")
|
||||||
|
if(HOOKS_C_DIR)
|
||||||
|
foreach(_dir ${HOOKS_C_DIR})
|
||||||
|
list(APPEND _hooks_extra_args "--hooks-c-dir" "${_dir}")
|
||||||
|
|
||||||
|
string(REGEX REPLACE "^[^=]+=" "" _hook_dir "${_dir}")
|
||||||
|
if(EXISTS "${_hook_dir}")
|
||||||
|
file(GLOB_RECURSE _hook_dir_deps CONFIGURE_DEPENDS
|
||||||
|
"${_hook_dir}/*.c"
|
||||||
|
"${_hook_dir}/*.h"
|
||||||
|
)
|
||||||
|
if(HOOKS_TEST_DIR)
|
||||||
|
list(FILTER _hook_dir_deps EXCLUDE REGEX "^${HOOKS_TEST_DIR}/")
|
||||||
|
endif()
|
||||||
|
list(APPEND _hooks_source_deps ${_hook_dir_deps})
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
list(REMOVE_DUPLICATES _hooks_source_deps)
|
||||||
|
endif()
|
||||||
|
if(HOOKS_COVERAGE OR DEFINED ENV{HOOKS_COVERAGE})
|
||||||
|
list(APPEND _hooks_extra_args "--hook-coverage")
|
||||||
|
message(STATUS "Hook coverage enabled: compiling hooks with hookz")
|
||||||
|
endif()
|
||||||
|
if(HOOKS_FORCE_RECOMPILE OR DEFINED ENV{HOOKS_FORCE_RECOMPILE})
|
||||||
|
list(APPEND _hooks_extra_args "--force-write" "--no-cache")
|
||||||
|
message(STATUS "Hook force recompile enabled (cache bypassed)")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Run hookz build-test-hooks on each test file before compilation
|
||||||
|
foreach(_test_file ${EXTERNAL_HOOK_TESTS})
|
||||||
|
get_filename_component(_stem ${_test_file} NAME_WE)
|
||||||
|
set(_hooks_header "${HOOKS_TEST_DIR}/${_stem}_hooks.h")
|
||||||
|
if(HOOKS_FORCE_RECOMPILE OR DEFINED ENV{HOOKS_FORCE_RECOMPILE})
|
||||||
|
# Always run — no DEPENDS, no OUTPUT caching
|
||||||
|
add_custom_target(compile_hooks_${_stem} ALL
|
||||||
|
COMMAND hookz build-test-hooks "${_test_file}" ${_hooks_extra_args}
|
||||||
|
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||||
|
COMMENT "Compiling hooks for ${_stem} (forced)"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
list(APPEND EXTERNAL_HOOK_TARGETS compile_hooks_${_stem})
|
||||||
|
else()
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${_hooks_header}"
|
||||||
|
COMMAND hookz build-test-hooks "${_test_file}" ${_hooks_extra_args}
|
||||||
|
DEPENDS "${_test_file}" ${_hooks_source_deps}
|
||||||
|
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||||
|
COMMENT "Compiling hooks for ${_stem}"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
list(APPEND EXTERNAL_HOOK_HEADERS "${_hooks_header}")
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
# Ensure headers are generated before rippled compiles
|
||||||
|
if(HOOKS_FORCE_RECOMPILE OR DEFINED ENV{HOOKS_FORCE_RECOMPILE})
|
||||||
|
foreach(_tgt ${EXTERNAL_HOOK_TARGETS})
|
||||||
|
add_dependencies(rippled ${_tgt})
|
||||||
|
endforeach()
|
||||||
|
else()
|
||||||
|
add_custom_target(compile_external_hooks DEPENDS ${EXTERNAL_HOOK_HEADERS})
|
||||||
|
add_dependencies(rippled compile_external_hooks)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_sources(rippled PRIVATE ${EXTERNAL_HOOK_TESTS})
|
||||||
|
# Keep the generated hook-header include path scoped to the external
|
||||||
|
# test sources so changing HOOKS_TEST_DIR doesn't invalidate the
|
||||||
|
# compile command for the rest of rippled.
|
||||||
|
set_property(
|
||||||
|
SOURCE ${EXTERNAL_HOOK_TESTS}
|
||||||
|
APPEND PROPERTY INCLUDE_DIRECTORIES "${HOOKS_TEST_DIR}"
|
||||||
|
)
|
||||||
|
message(STATUS "Including external hook tests from: ${HOOKS_TEST_DIR}")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_link_libraries(rippled
|
target_link_libraries(rippled
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <functional>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
@@ -165,6 +166,7 @@ private:
|
|||||||
beast::severities::Severity thresh_;
|
beast::severities::Severity thresh_;
|
||||||
File file_;
|
File file_;
|
||||||
bool silent_ = false;
|
bool silent_ = false;
|
||||||
|
std::function<std::string(std::string const&)> transform_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Logs(beast::severities::Severity level);
|
Logs(beast::severities::Severity level);
|
||||||
@@ -203,6 +205,33 @@ public:
|
|||||||
std::string const& text,
|
std::string const& text,
|
||||||
bool console);
|
bool console);
|
||||||
|
|
||||||
|
/** Set a transform applied to every log message before output.
|
||||||
|
* Useful in tests to replace raw account IDs with human-readable names.
|
||||||
|
* Pass nullptr to clear.
|
||||||
|
*
|
||||||
|
* TODO: This is test-only infrastructure (used by TestEnv). Consider
|
||||||
|
* moving to SuiteLogs or a test-specific subclass if the Logs interface
|
||||||
|
* needs to stay clean for production.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
setTransform(std::function<std::string(std::string const&)> fn)
|
||||||
|
{
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
transform_ = std::move(fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Apply the current transform to text (or return as-is if none set). */
|
||||||
|
std::string const&
|
||||||
|
applyTransform(std::string const& text) const
|
||||||
|
{
|
||||||
|
if (!transform_)
|
||||||
|
return text;
|
||||||
|
// Store in thread_local to return a const ref
|
||||||
|
thread_local std::string buf;
|
||||||
|
buf = transform_(text);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
std::string
|
std::string
|
||||||
rotate();
|
rotate();
|
||||||
|
|
||||||
|
|||||||
@@ -416,6 +416,7 @@ getImportWhitelist(Rules const& rules)
|
|||||||
#define int64_t 0x7EU
|
#define int64_t 0x7EU
|
||||||
#define int32_t 0x7FU
|
#define int32_t 0x7FU
|
||||||
#define uint32_t 0x7FU
|
#define uint32_t 0x7FU
|
||||||
|
#define void_t 0x00U
|
||||||
|
|
||||||
#define HOOK_WRAP_PARAMS(...) __VA_ARGS__
|
#define HOOK_WRAP_PARAMS(...) __VA_ARGS__
|
||||||
|
|
||||||
@@ -427,11 +428,15 @@ getImportWhitelist(Rules const& rules)
|
|||||||
|
|
||||||
#include "hook_api.macro"
|
#include "hook_api.macro"
|
||||||
|
|
||||||
|
// Coverage callback: void __on_source_line(uint32_t line, uint32_t col)
|
||||||
|
whitelist["__on_source_line"] = {void_t, uint32_t, uint32_t};
|
||||||
|
|
||||||
#undef HOOK_API_DEFINITION
|
#undef HOOK_API_DEFINITION
|
||||||
#undef HOOK_WRAP_PARAMS
|
#undef HOOK_WRAP_PARAMS
|
||||||
#undef int64_t
|
#undef int64_t
|
||||||
#undef int32_t
|
#undef int32_t
|
||||||
#undef uint32_t
|
#undef uint32_t
|
||||||
|
#undef void_t
|
||||||
#pragma pop_macro("HOOK_API_DEFINITION")
|
#pragma pop_macro("HOOK_API_DEFINITION")
|
||||||
|
|
||||||
return whitelist;
|
return whitelist;
|
||||||
|
|||||||
@@ -1374,21 +1374,52 @@ validateGuards(
|
|||||||
int result_count = parseLeb128(wasm, i, &i);
|
int result_count = parseLeb128(wasm, i, &i);
|
||||||
CHECK_SHORT_HOOK();
|
CHECK_SHORT_HOOK();
|
||||||
|
|
||||||
// this needs a reliable hook cleaner otherwise it will catch
|
if (j == hook_type_idx)
|
||||||
// most compilers out
|
|
||||||
if (result_count != 1)
|
|
||||||
{
|
{
|
||||||
GUARDLOG(hook::log::FUNC_RETURN_COUNT)
|
// hook/cbak must return exactly one value (i64)
|
||||||
<< "Malformed transaction. "
|
if (result_count != 1)
|
||||||
<< "Hook declares a function type that returns fewer "
|
{
|
||||||
"or more than one value. "
|
GUARDLOG(hook::log::FUNC_RETURN_COUNT)
|
||||||
<< "\n";
|
<< "Malformed transaction. "
|
||||||
return {};
|
<< "hook/cbak function type must return exactly "
|
||||||
|
"one value. "
|
||||||
|
<< "\n";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (first_signature)
|
||||||
|
{
|
||||||
|
// For whitelisted imports, check expected return count.
|
||||||
|
// void_t (0x00) means 0 return values.
|
||||||
|
uint8_t expected_return =
|
||||||
|
(*first_signature).get()[0];
|
||||||
|
int expected_result_count =
|
||||||
|
(expected_return == 0x00U) ? 0 : 1;
|
||||||
|
if (result_count != expected_result_count)
|
||||||
|
{
|
||||||
|
GUARDLOG(hook::log::FUNC_RETURN_COUNT)
|
||||||
|
<< "Malformed transaction. "
|
||||||
|
<< "Hook API: " << *first_name
|
||||||
|
<< " has wrong return count "
|
||||||
|
<< "(expected " << expected_result_count
|
||||||
|
<< ", got " << result_count << ")."
|
||||||
|
<< "\n";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (result_count != 1)
|
||||||
|
{
|
||||||
|
GUARDLOG(hook::log::FUNC_RETURN_COUNT)
|
||||||
|
<< "Malformed transaction. "
|
||||||
|
<< "Hook declares a function type that returns "
|
||||||
|
"fewer or more than one value. "
|
||||||
|
<< "\n";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this can only ever be 1 in production, but in testing it may
|
|
||||||
// also be 0 or >1 so for completeness this loop is here but can
|
|
||||||
// be taken out in prod
|
|
||||||
for (int k = 0; k < result_count; ++k)
|
for (int k = 0; k < result_count; ++k)
|
||||||
{
|
{
|
||||||
int result_type = parseLeb128(wasm, i, &i);
|
int result_type = parseLeb128(wasm, i, &i);
|
||||||
|
|||||||
@@ -146,6 +146,7 @@
|
|||||||
[[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]] auto jh = applyCtx.app.journal("HooksTrace"); \
|
||||||
[[maybe_unused]] WasmEdge_MemoryInstanceContext* memoryCtx = \
|
[[maybe_unused]] WasmEdge_MemoryInstanceContext* memoryCtx = \
|
||||||
WasmEdge_CallingFrameGetMemoryInstance(&frameCtx, 0); \
|
WasmEdge_CallingFrameGetMemoryInstance(&frameCtx, 0); \
|
||||||
[[maybe_unused]] unsigned char* memory = \
|
[[maybe_unused]] unsigned char* memory = \
|
||||||
|
|||||||
@@ -196,9 +196,10 @@ Logs::write(
|
|||||||
std::string const& text,
|
std::string const& text,
|
||||||
bool console)
|
bool console)
|
||||||
{
|
{
|
||||||
std::string s;
|
|
||||||
format(s, text, level, partition);
|
|
||||||
std::lock_guard lock(mutex_);
|
std::lock_guard lock(mutex_);
|
||||||
|
std::string const& transformed = transform_ ? transform_(text) : text;
|
||||||
|
std::string s;
|
||||||
|
format(s, transformed, level, partition);
|
||||||
file_.writeln(s);
|
file_.writeln(s);
|
||||||
if (!silent_)
|
if (!silent_)
|
||||||
std::cerr << s << '\n';
|
std::cerr << s << '\n';
|
||||||
|
|||||||
@@ -106,7 +106,8 @@ public:
|
|||||||
std::string const& partition,
|
std::string const& partition,
|
||||||
beast::severities::Severity threshold) override
|
beast::severities::Severity threshold) override
|
||||||
{
|
{
|
||||||
return std::make_unique<SuiteJournalSink>(partition, threshold, suite_);
|
return std::make_unique<SuiteJournalSink>(
|
||||||
|
partition, threshold, suite_, this);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
148
src/test/jtx/TestEnv.h
Normal file
148
src/test/jtx/TestEnv.h
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
#ifndef TEST_JTX_TESTENV_H_INCLUDED
|
||||||
|
#define TEST_JTX_TESTENV_H_INCLUDED
|
||||||
|
|
||||||
|
#include <test/jtx/Env.h>
|
||||||
|
#include <xrpl/basics/Log.h>
|
||||||
|
#include <xrpl/protocol/AccountID.h>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <map>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace ripple {
|
||||||
|
namespace test {
|
||||||
|
namespace jtx {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TestEnv wraps Env with:
|
||||||
|
* - Named account registry: env.account("alice")
|
||||||
|
* - Auto log transform: replaces r-addresses with Account(name) in log output
|
||||||
|
* - Env-var driven per-partition log levels via TESTENV_LOGGING
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* TestEnv env{suite, features};
|
||||||
|
* auto const& alice = env.account("alice");
|
||||||
|
* auto const& bob = env.account("bob");
|
||||||
|
* env.fund(XRP(10000), alice, bob);
|
||||||
|
* // Logs now show Account(alice), Account(bob) instead of r-addresses
|
||||||
|
*
|
||||||
|
* Log levels via env var:
|
||||||
|
* TESTENV_LOGGING="HooksTrace=trace,View=debug"
|
||||||
|
*
|
||||||
|
* Valid levels: trace, debug, info, warning, error, fatal
|
||||||
|
*/
|
||||||
|
class TestEnv : public Env
|
||||||
|
{
|
||||||
|
std::map<std::string, Account> accounts_;
|
||||||
|
std::string prefix_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
TestEnv(beast::unit_test::suite& suite, FeatureBitset features)
|
||||||
|
: Env(suite, features)
|
||||||
|
{
|
||||||
|
installTransform();
|
||||||
|
applyLoggingEnvVar();
|
||||||
|
}
|
||||||
|
|
||||||
|
TestEnv(
|
||||||
|
beast::unit_test::suite& suite,
|
||||||
|
std::unique_ptr<Config> config,
|
||||||
|
FeatureBitset features,
|
||||||
|
std::unique_ptr<Logs> logs = nullptr,
|
||||||
|
beast::severities::Severity thresh = beast::severities::kError)
|
||||||
|
: Env(suite, std::move(config), features, std::move(logs), thresh)
|
||||||
|
{
|
||||||
|
installTransform();
|
||||||
|
applyLoggingEnvVar();
|
||||||
|
}
|
||||||
|
|
||||||
|
~TestEnv()
|
||||||
|
{
|
||||||
|
app().logs().setTransform(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get or create a named account.
|
||||||
|
/// First call creates the Account; subsequent calls return the same one.
|
||||||
|
Account const&
|
||||||
|
account(std::string const& name)
|
||||||
|
{
|
||||||
|
auto [it, inserted] = accounts_.try_emplace(name, name);
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a prefix that appears at the start of every log line.
|
||||||
|
/// Useful for visually separating test phases in trace output.
|
||||||
|
/// Pass empty string to clear.
|
||||||
|
void
|
||||||
|
setPrefix(std::string const& prefix)
|
||||||
|
{
|
||||||
|
prefix_ = prefix.empty() ? "" : "[" + prefix + "] ";
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static beast::severities::Severity
|
||||||
|
parseSeverity(std::string const& s)
|
||||||
|
{
|
||||||
|
if (s == "trace")
|
||||||
|
return beast::severities::kTrace;
|
||||||
|
if (s == "debug")
|
||||||
|
return beast::severities::kDebug;
|
||||||
|
if (s == "info")
|
||||||
|
return beast::severities::kInfo;
|
||||||
|
if (s == "warning")
|
||||||
|
return beast::severities::kWarning;
|
||||||
|
if (s == "error")
|
||||||
|
return beast::severities::kError;
|
||||||
|
if (s == "fatal")
|
||||||
|
return beast::severities::kFatal;
|
||||||
|
return beast::severities::kError;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
applyLoggingEnvVar()
|
||||||
|
{
|
||||||
|
// Parse TESTENV_LOGGING="Partition1=level,Partition2=level"
|
||||||
|
auto const* envVal = std::getenv("TESTENV_LOGGING");
|
||||||
|
if (!envVal || !envVal[0])
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::istringstream ss(envVal);
|
||||||
|
std::string pair;
|
||||||
|
while (std::getline(ss, pair, ','))
|
||||||
|
{
|
||||||
|
auto eq = pair.find('=');
|
||||||
|
if (eq == std::string::npos)
|
||||||
|
continue;
|
||||||
|
auto partition = pair.substr(0, eq);
|
||||||
|
auto level = pair.substr(eq + 1);
|
||||||
|
app().logs().get(partition).threshold(parseSeverity(level));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
installTransform()
|
||||||
|
{
|
||||||
|
app().logs().setTransform([this](std::string const& text) {
|
||||||
|
std::string out = prefix_ + text;
|
||||||
|
for (auto const& [name, acc] : accounts_)
|
||||||
|
{
|
||||||
|
auto raddr = toBase58(acc.id());
|
||||||
|
std::string::size_type pos = 0;
|
||||||
|
std::string replacement = "Account(" + name + ")";
|
||||||
|
while ((pos = out.find(raddr, pos)) != std::string::npos)
|
||||||
|
{
|
||||||
|
out.replace(pos, raddr.size(), replacement);
|
||||||
|
pos += replacement.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace jtx
|
||||||
|
} // namespace test
|
||||||
|
} // namespace ripple
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#ifndef TEST_UNIT_TEST_SUITE_JOURNAL_H
|
#ifndef TEST_UNIT_TEST_SUITE_JOURNAL_H
|
||||||
#define TEST_UNIT_TEST_SUITE_JOURNAL_H
|
#define TEST_UNIT_TEST_SUITE_JOURNAL_H
|
||||||
|
#include <xrpl/basics/Log.h>
|
||||||
#include <xrpl/beast/unit_test.h>
|
#include <xrpl/beast/unit_test.h>
|
||||||
#include <xrpl/beast/utility/Journal.h>
|
#include <xrpl/beast/utility/Journal.h>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
@@ -31,13 +32,18 @@ class SuiteJournalSink : public beast::Journal::Sink
|
|||||||
{
|
{
|
||||||
std::string partition_;
|
std::string partition_;
|
||||||
beast::unit_test::suite& suite_;
|
beast::unit_test::suite& suite_;
|
||||||
|
Logs* logs_ = nullptr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SuiteJournalSink(
|
SuiteJournalSink(
|
||||||
std::string const& partition,
|
std::string const& partition,
|
||||||
beast::severities::Severity threshold,
|
beast::severities::Severity threshold,
|
||||||
beast::unit_test::suite& suite)
|
beast::unit_test::suite& suite,
|
||||||
: Sink(threshold, false), partition_(partition + " "), suite_(suite)
|
Logs* logs = nullptr)
|
||||||
|
: Sink(threshold, false)
|
||||||
|
, partition_(partition + " ")
|
||||||
|
, suite_(suite)
|
||||||
|
, logs_(logs)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,11 +103,12 @@ SuiteJournalSink::writeAlways(
|
|||||||
// Only write the string if the level at least equals the threshold.
|
// Only write the string if the level at least equals the threshold.
|
||||||
if (level >= threshold())
|
if (level >= threshold())
|
||||||
{
|
{
|
||||||
|
std::string const& output = logs_ ? logs_->applyTransform(text) : text;
|
||||||
// std::endl flushes → sync() → str()/str("") race in shared buffer →
|
// std::endl flushes → sync() → str()/str("") race in shared buffer →
|
||||||
// crashes
|
// crashes
|
||||||
static std::mutex log_mutex;
|
static std::mutex log_mutex;
|
||||||
std::lock_guard lock(log_mutex);
|
std::lock_guard lock(log_mutex);
|
||||||
suite_.log << s << partition_ << text << std::endl;
|
suite_.log << s << partition_ << output << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,11 @@
|
|||||||
#include <xrpl/protocol/TER.h>
|
#include <xrpl/protocol/TER.h>
|
||||||
#include <xrpl/protocol/digest.h>
|
#include <xrpl/protocol/digest.h>
|
||||||
#include <any>
|
#include <any>
|
||||||
|
#include <fstream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
#include <set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <wasmedge/wasmedge.h>
|
#include <wasmedge/wasmedge.h>
|
||||||
|
|
||||||
@@ -302,6 +304,130 @@ static WasmEdge_String hookFunctionName =
|
|||||||
// see: lib/system/allocator.cpp
|
// see: lib/system/allocator.cpp
|
||||||
#define WasmEdge_kPageSize 65536ULL
|
#define WasmEdge_kPageSize 65536ULL
|
||||||
|
|
||||||
|
// --- Coverage infrastructure ---
|
||||||
|
//
|
||||||
|
// Global coverage accumulator keyed by hook hash. Persists across all hook
|
||||||
|
// executions in the process. Each __on_source_line call records a (line, col)
|
||||||
|
// pair under the executing hook's hash.
|
||||||
|
//
|
||||||
|
// Test API:
|
||||||
|
// hook::coverageReset() — clear all accumulated data
|
||||||
|
// hook::coverageHits(hookHash) — get hits for a specific hook
|
||||||
|
// hook::coverageLabel(hash, label) — register a human-readable label
|
||||||
|
// hook::coverageDump(path) — write all data to a file
|
||||||
|
//
|
||||||
|
// The dump file format is:
|
||||||
|
// [label or hash]
|
||||||
|
// hits=<line:col>,<line:col>,...
|
||||||
|
|
||||||
|
struct CoverageData
|
||||||
|
{
|
||||||
|
std::set<uint32_t> hits{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Global accumulator — survives across HookContext lifetimes
|
||||||
|
inline std::map<ripple::uint256, CoverageData>&
|
||||||
|
coverageMap()
|
||||||
|
{
|
||||||
|
static std::map<ripple::uint256, CoverageData> map;
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash → label mapping (e.g. hash → "file:tipbot/tip.c")
|
||||||
|
inline std::map<ripple::uint256, std::string>&
|
||||||
|
coverageLabels()
|
||||||
|
{
|
||||||
|
static std::map<ripple::uint256, std::string> labels;
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
coverageReset()
|
||||||
|
{
|
||||||
|
coverageMap().clear();
|
||||||
|
coverageLabels().clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
coverageLabel(ripple::uint256 const& hookHash, std::string const& label)
|
||||||
|
{
|
||||||
|
coverageLabels()[hookHash] = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::set<uint32_t> const*
|
||||||
|
coverageHits(ripple::uint256 const& hookHash)
|
||||||
|
{
|
||||||
|
auto& map = coverageMap();
|
||||||
|
auto it = map.find(hookHash);
|
||||||
|
if (it == map.end())
|
||||||
|
return nullptr;
|
||||||
|
return &it->second.hits;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool
|
||||||
|
coverageDump(std::string const& path)
|
||||||
|
{
|
||||||
|
auto& map = coverageMap();
|
||||||
|
if (map.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto& labels = coverageLabels();
|
||||||
|
|
||||||
|
std::ofstream out(path);
|
||||||
|
if (!out)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (auto const& [hash, data] : map)
|
||||||
|
{
|
||||||
|
auto it = labels.find(hash);
|
||||||
|
if (it != labels.end())
|
||||||
|
out << "[" << it->second << "]\n";
|
||||||
|
else
|
||||||
|
out << "[" << to_string(hash) << "]\n";
|
||||||
|
|
||||||
|
out << "hits=";
|
||||||
|
bool first = true;
|
||||||
|
for (auto key : data.hits)
|
||||||
|
{
|
||||||
|
if (!first)
|
||||||
|
out << ",";
|
||||||
|
out << (key >> 16) << ":" << (key & 0xFFFF);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
out << "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Coverage host callback ---
|
||||||
|
|
||||||
|
inline WasmEdge_Result
|
||||||
|
onSourceLine(
|
||||||
|
void* data_ptr,
|
||||||
|
const WasmEdge_CallingFrameContext* frameCtx,
|
||||||
|
const WasmEdge_Value* in,
|
||||||
|
WasmEdge_Value* out)
|
||||||
|
{
|
||||||
|
// Called by hookz-instrumented WASM at each DWARF source location.
|
||||||
|
// in[0] = line number, in[1] = column number.
|
||||||
|
(void)out;
|
||||||
|
(void)frameCtx;
|
||||||
|
auto* hookCtx = reinterpret_cast<HookContext*>(data_ptr);
|
||||||
|
if (!hookCtx)
|
||||||
|
return WasmEdge_Result_Success;
|
||||||
|
|
||||||
|
uint32_t line = WasmEdge_ValueGetI32(in[0]);
|
||||||
|
uint32_t col = WasmEdge_ValueGetI32(in[1]);
|
||||||
|
|
||||||
|
// Pack (line, col) into a single uint32_t key.
|
||||||
|
// Limits: line < 65536, col < 65536 — more than sufficient for hooks.
|
||||||
|
uint32_t key = (line << 16) | (col & 0xFFFF);
|
||||||
|
coverageMap()[hookCtx->result.hookHash].hits.insert(key);
|
||||||
|
|
||||||
|
return WasmEdge_Result_Success;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HookExecutor is effectively a two-part function:
|
* HookExecutor is effectively a two-part function:
|
||||||
* The first part sets up the Hook Api inside the wasm import, ready for use
|
* The first part sets up the Hook Api inside the wasm import, ready for use
|
||||||
@@ -480,6 +606,22 @@ public:
|
|||||||
#undef HOOK_WRAP_PARAMS
|
#undef HOOK_WRAP_PARAMS
|
||||||
#pragma pop_macro("HOOK_API_DEFINITION")
|
#pragma pop_macro("HOOK_API_DEFINITION")
|
||||||
|
|
||||||
|
// Coverage callback: void __on_source_line(i32 line, i32 col)
|
||||||
|
// Registered unconditionally — production hooks don't import it,
|
||||||
|
// so it's harmless. Instrumented hooks call it at each DWARF
|
||||||
|
// source location to record line:col coverage hits.
|
||||||
|
{
|
||||||
|
static WasmEdge_ValType paramsOSL[] = {
|
||||||
|
WasmEdge_ValType_I32, WasmEdge_ValType_I32};
|
||||||
|
static auto* ftOSL =
|
||||||
|
WasmEdge_FunctionTypeCreate(paramsOSL, 2, nullptr, 0);
|
||||||
|
auto* hfOSL = WasmEdge_FunctionInstanceCreate(
|
||||||
|
ftOSL, hook::onSourceLine, (void*)(&ctx), 0);
|
||||||
|
static auto nameOSL =
|
||||||
|
WasmEdge_StringCreateByCString("__on_source_line");
|
||||||
|
WasmEdge_ModuleInstanceAddFunction(importObj, nameOSL, hfOSL);
|
||||||
|
}
|
||||||
|
|
||||||
WasmEdge_TableInstanceContext* hostTable =
|
WasmEdge_TableInstanceContext* hostTable =
|
||||||
WasmEdge_TableInstanceCreate(tableType);
|
WasmEdge_TableInstanceCreate(tableType);
|
||||||
WasmEdge_ModuleInstanceAddTable(importObj, tableName, hostTable);
|
WasmEdge_ModuleInstanceAddTable(importObj, tableName, hostTable);
|
||||||
|
|||||||
@@ -1267,7 +1267,7 @@ DEFINE_HOOK_FUNCTION(
|
|||||||
if (NOT_IN_BOUNDS(read_ptr, read_len, memory_length))
|
if (NOT_IN_BOUNDS(read_ptr, read_len, memory_length))
|
||||||
return OUT_OF_BOUNDS;
|
return OUT_OF_BOUNDS;
|
||||||
|
|
||||||
if (!j.trace())
|
if (!jh.trace())
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (read_len > 128)
|
if (read_len > 128)
|
||||||
@@ -1281,16 +1281,16 @@ DEFINE_HOOK_FUNCTION(
|
|||||||
|
|
||||||
if (read_len > 0)
|
if (read_len > 0)
|
||||||
{
|
{
|
||||||
j.trace() << "HookTrace[" << HC_ACC() << "]: "
|
JLOG(jh.trace()) << "HookTrace[" << HC_ACC() << "]: "
|
||||||
<< std::string_view(
|
<< std::string_view(
|
||||||
(const char*)memory + read_ptr, read_len)
|
(const char*)memory + read_ptr, read_len)
|
||||||
<< ": " << number;
|
<< ": " << number;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
j.trace() << "HookTrace[" << HC_ACC() << "]: " << number;
|
JLOG(jh.trace()) << "HookTrace[" << HC_ACC() << "]: " << number;
|
||||||
return 0;
|
return 0;
|
||||||
HOOK_TEARDOWN();
|
HOOK_TEARDOWN();
|
||||||
}
|
}
|
||||||
@@ -1310,7 +1310,7 @@ DEFINE_HOOK_FUNCTION(
|
|||||||
NOT_IN_BOUNDS(dread_ptr, dread_len, memory_length))
|
NOT_IN_BOUNDS(dread_ptr, dread_len, memory_length))
|
||||||
return OUT_OF_BOUNDS;
|
return OUT_OF_BOUNDS;
|
||||||
|
|
||||||
if (!j.trace())
|
if (!jh.trace())
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (mread_len > 128)
|
if (mread_len > 128)
|
||||||
@@ -1370,8 +1370,8 @@ DEFINE_HOOK_FUNCTION(
|
|||||||
|
|
||||||
if (out_len > 0)
|
if (out_len > 0)
|
||||||
{
|
{
|
||||||
j.trace() << "HookTrace[" << HC_ACC() << "]: "
|
JLOG(jh.trace()) << "HookTrace[" << HC_ACC() << "]: "
|
||||||
<< std::string_view((const char*)output_storage, out_len);
|
<< std::string_view((const char*)output_storage, out_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -3547,7 +3547,7 @@ DEFINE_HOOK_FUNCTION(
|
|||||||
if (NOT_IN_BOUNDS(read_ptr, read_len, memory_length))
|
if (NOT_IN_BOUNDS(read_ptr, read_len, memory_length))
|
||||||
return OUT_OF_BOUNDS;
|
return OUT_OF_BOUNDS;
|
||||||
|
|
||||||
if (!j.trace())
|
if (!jh.trace())
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (read_len > 128)
|
if (read_len > 128)
|
||||||
@@ -3560,12 +3560,12 @@ DEFINE_HOOK_FUNCTION(
|
|||||||
|
|
||||||
if (float1 == 0)
|
if (float1 == 0)
|
||||||
{
|
{
|
||||||
j.trace() << "HookTrace[" << HC_ACC() << "]: "
|
JLOG(jh.trace()) << "HookTrace[" << HC_ACC() << "]: "
|
||||||
<< (read_len == 0
|
<< (read_len == 0
|
||||||
? ""
|
? ""
|
||||||
: std::string_view(
|
: std::string_view(
|
||||||
(const char*)memory + read_ptr, read_len))
|
(const char*)memory + read_ptr, read_len))
|
||||||
<< ": Float 0*10^(0) <ZERO>";
|
<< ": Float 0*10^(0) <ZERO>";
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3575,20 +3575,22 @@ DEFINE_HOOK_FUNCTION(
|
|||||||
if (man < minMantissa || man > maxMantissa || exp < minExponent ||
|
if (man < minMantissa || man > maxMantissa || exp < minExponent ||
|
||||||
exp > maxExponent)
|
exp > maxExponent)
|
||||||
{
|
{
|
||||||
j.trace() << "HookTrace[" << HC_ACC() << "]:"
|
JLOG(jh.trace()) << "HookTrace[" << HC_ACC() << "]:"
|
||||||
<< (read_len == 0
|
<< (read_len == 0
|
||||||
? ""
|
? ""
|
||||||
: std::string_view(
|
: std::string_view(
|
||||||
(const char*)memory + read_ptr, read_len))
|
(const char*)memory + read_ptr, read_len))
|
||||||
<< ": Float <INVALID>";
|
<< ": Float <INVALID>";
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
j.trace() << "HookTrace[" << HC_ACC() << "]:"
|
JLOG(jh.trace()) << "HookTrace[" << HC_ACC() << "]:"
|
||||||
<< (read_len == 0 ? ""
|
<< (read_len == 0
|
||||||
: std::string_view(
|
? ""
|
||||||
(const char*)memory + read_ptr, read_len))
|
: std::string_view(
|
||||||
<< ": Float " << (neg ? "-" : "") << man << "*10^(" << exp << ")";
|
(const char*)memory + read_ptr, read_len))
|
||||||
|
<< ": Float " << (neg ? "-" : "") << man << "*10^(" << exp
|
||||||
|
<< ")";
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
HOOK_TEARDOWN();
|
HOOK_TEARDOWN();
|
||||||
|
|||||||
@@ -534,7 +534,7 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto result = validateGuards(
|
auto result = validateGuards(
|
||||||
hook, // wasm to verify
|
hook,
|
||||||
logger,
|
logger,
|
||||||
hsacc,
|
hsacc,
|
||||||
hook_api::getImportWhitelist(ctx.rules),
|
hook_api::getImportWhitelist(ctx.rules),
|
||||||
|
|||||||
Reference in New Issue
Block a user