Compare commits

...

13 Commits

Author SHA1 Message Date
tequ
04c2929883 Merge remote-tracking branch 'upstream/dev' into hook-helper-func 2026-04-27 14:17:42 +09:00
Alloy Networks
cd00ed72d8 change build instructions url 2026-04-24 11:12:28 +10:00
tequ
05a3e04f2d Fix BEAST_ENHANCED_LOGGING not working and restore original behavior 2026-04-24 11:11:40 +10:00
tequ
66f7294120 Test: hint build_test_hooks.sh when hook wasm is empty in hso() 2026-04-24 11:10:46 +10:00
Nicholas Dudfield
7f6ac75617 Revert "chore: use improved levelization script with threading and argparse"
This reverts commit 5c1d7d9ae9.
2026-04-24 11:09:19 +10:00
Nicholas Dudfield
4150f0383c chore: use improved levelization script with threading and argparse 2026-04-24 11:09:19 +10:00
Nicholas Dudfield
25123b370a chore: replace levelization shell script with python
Backport of XRPLF/rippled#6325. The python version runs ~80x faster.
2026-04-24 11:09:19 +10:00
tequ
63096d5fbc add test 2026-01-21 12:45:05 +09:00
tequ
2e128acdcf add execution test 2026-01-21 10:52:31 +09:00
tequ
043c60b62e Update new tests 2026-01-20 20:07:26 +09:00
tequ
5dd1198e4f Merge commit '5d9071695a616e1af378142e09649abc7d0e8afa' into hook-helper-func 2026-01-20 15:46:00 +09:00
tequ
5d9071695a Add tests for Hooks fee 2026-01-20 12:12:45 +09:00
tequ
ec6dc93834 Allow helper functions at Hooks 2026-01-19 16:44:23 +09:00
12 changed files with 2131 additions and 280 deletions

View File

@@ -10,7 +10,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Check levelization
run: Builds/levelization/levelization.sh
run: python Builds/levelization/levelization.py
- name: Check for differences
id: assert
run: |
@@ -40,7 +40,7 @@ jobs:
To fix it, you can do one of two things:
1. Download and apply the patch generated as an artifact of this
job to your repo, commit, and push.
2. Run './Builds/levelization/levelization.sh' in your repo,
2. Run 'python Builds/levelization/levelization.py' in your repo,
commit, and push.
See Builds/levelization/README.md for more info.

6
.gitignore vendored
View File

@@ -53,6 +53,9 @@ Builds/levelization/results/paths.txt
Builds/levelization/results/includes/
Builds/levelization/results/includedby/
# Python
__pycache__
# Ignore tmp directory.
tmp
@@ -126,3 +129,6 @@ generated
# Suggested in-tree build directory
/.build/
guard_checker
guard_checker.dSYM

View File

@@ -50,7 +50,7 @@ that `test` code should *never* be included in `ripple` code.)
## Validation
The [levelization.sh](levelization.sh) script takes no parameters,
The [levelization.py](levelization.py) script takes no parameters,
reads no environment variables, and can be run from any directory,
as long as it is in the expected location in the rippled repo.
It can be run at any time from within a checked out repo, and will
@@ -84,7 +84,7 @@ It generates many files of [results](results):
Github Actions workflow to test that levelization loops haven't
changed. Unfortunately, if changes are detected, it can't tell if
they are improvements or not, so if you have resolved any issues or
done anything else to improve levelization, run `levelization.sh`,
done anything else to improve levelization, run `levelization.py`,
and commit the updated results.
The `loops.txt` and `ordering.txt` files relate the modules
@@ -108,7 +108,7 @@ The committed files hide the detailed values intentionally, to
prevent false alarms and merging issues, and because it's easy to
get those details locally.
1. Run `levelization.sh`
1. Run `levelization.py`
2. Grep the modules in `paths.txt`.
* For example, if a cycle is found `A ~= B`, simply `grep -w
A Builds/levelization/results/paths.txt | grep -w B`

View File

@@ -0,0 +1,283 @@
#!/usr/bin/env python3
"""
Usage: levelization.py
This script takes no parameters, and can be called from any directory in the file system.
"""
import os
import re
import sys
from collections import defaultdict
from pathlib import Path
# Compile regex patterns once at module level
INCLUDE_PATTERN = re.compile(r"^\s*#include.*/.*\.h")
INCLUDE_PATH_PATTERN = re.compile(r'[<"]([^>"]+)[>"]')
def dictionary_sort_key(s):
"""
Create a sort key that mimics 'sort -d' (dictionary order).
Dictionary order only considers blanks and alphanumeric characters.
"""
return "".join(c for c in s if c.isalnum() or c.isspace())
def get_level(file_path):
"""
Extract the level from a file path (second and third directory components).
Equivalent to bash: cut -d/ -f 2,3
Examples:
src/ripple/app/main.cpp -> ripple.app
src/test/app/Import_test.cpp -> test.app
"""
parts = file_path.split("/")
if len(parts) >= 3:
level = f"{parts[1]}/{parts[2]}"
elif len(parts) >= 2:
level = f"{parts[1]}/toplevel"
else:
level = file_path
# If the "level" indicates a file, cut off the filename
if "." in level.split("/")[-1]:
# Use the "toplevel" label as a workaround for `sort`
# inconsistencies between different utility versions
level = level.rsplit("/", 1)[0] + "/toplevel"
return level.replace("/", ".")
def extract_include_level(include_line):
"""
Extract the include path from an #include directive.
Gets the first two directory components from the include path.
Equivalent to bash: cut -d/ -f 1,2
Examples:
#include <ripple/basics/base_uint.h> -> ripple.basics
#include "ripple/app/main/Application.h" -> ripple.app
"""
match = INCLUDE_PATH_PATTERN.search(include_line)
if not match:
return None
include_path = match.group(1)
parts = include_path.split("/")
if len(parts) >= 2:
include_level = f"{parts[0]}/{parts[1]}"
else:
include_level = include_path
# If the "includelevel" indicates a file, cut off the filename
if "." in include_level.split("/")[-1]:
include_level = include_level.rsplit("/", 1)[0] + "/toplevel"
return include_level.replace("/", ".")
def find_repository_directories(start_path, depth_limit=10):
"""
Find the repository root by looking for src or include folders.
Walks up the directory tree from the start path.
"""
current = start_path.resolve()
for _ in range(depth_limit):
src_path = current / "src"
include_path = current / "include"
has_src = src_path.exists()
has_include = include_path.exists()
if has_src or has_include:
dirs = []
if has_src:
dirs.append(src_path)
if has_include:
dirs.append(include_path)
return current, dirs
parent = current.parent
if parent == current:
break
current = parent
raise RuntimeError(
"Could not find repository root. "
"Expected to find a directory containing 'src' and/or 'include' folders."
)
def main():
script_dir = Path(__file__).parent.resolve()
os.chdir(script_dir)
# Clean up and create results directory.
results_dir = script_dir / "results"
if results_dir.exists():
import shutil
shutil.rmtree(results_dir)
results_dir.mkdir()
# Find the repository root.
try:
repo_root, scan_dirs = find_repository_directories(script_dir)
print(f"Found repository root: {repo_root}")
for scan_dir in scan_dirs:
print(f" Scanning: {scan_dir.relative_to(repo_root)}")
except RuntimeError as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
# Find all #include directives.
print("\nScanning for raw includes...")
raw_includes = []
rawincludes_file = results_dir / "rawincludes.txt"
with open(rawincludes_file, "w", buffering=8192) as raw_f:
for dir_path in scan_dirs:
for file_path in dir_path.rglob("*"):
if not file_path.is_file():
continue
try:
rel_path_str = str(file_path.relative_to(repo_root))
with open(
file_path, "r", encoding="utf-8", errors="ignore", buffering=8192
) as f:
for line in f:
if "#include" not in line or "boost" in line:
continue
if INCLUDE_PATTERN.match(line):
line_stripped = line.strip()
entry = f"{rel_path_str}:{line_stripped}\n"
print(entry, end="")
raw_f.write(entry)
raw_includes.append((rel_path_str, line_stripped))
except Exception as e:
print(f"Error reading {file_path}: {e}", file=sys.stderr)
# Build levelization paths and count directly.
print("Build levelization paths")
path_counts = defaultdict(int)
for file_path, include_line in raw_includes:
include_level = extract_include_level(include_line)
if not include_level:
continue
level = get_level(file_path)
if level != include_level:
path_counts[(level, include_level)] += 1
# Sort and deduplicate paths.
print("Sort and deduplicate paths")
sorted_items = sorted(
path_counts.items(),
key=lambda x: (dictionary_sort_key(x[0][0]), dictionary_sort_key(x[0][1])),
)
paths_file = results_dir / "paths.txt"
with open(paths_file, "w") as f:
for (level, include_level), count in sorted_items:
line = f"{count:7} {level} {include_level}\n"
print(line.rstrip())
f.write(line)
# Split into flat-file database.
print("Split into flat-file database")
includes_dir = results_dir / "includes"
includedby_dir = results_dir / "includedby"
includes_dir.mkdir()
includedby_dir.mkdir()
includes_data = defaultdict(list)
includedby_data = defaultdict(list)
for (level, include_level), count in sorted_items:
includes_data[level].append((include_level, count))
includedby_data[include_level].append((level, count))
for level in sorted(includes_data.keys(), key=dictionary_sort_key):
with open(includes_dir / level, "w") as f:
for include_level, count in includes_data[level]:
line = f"{include_level} {count}\n"
print(line.rstrip())
f.write(line)
for include_level in sorted(includedby_data.keys(), key=dictionary_sort_key):
with open(includedby_dir / include_level, "w") as f:
for level, count in includedby_data[include_level]:
line = f"{level} {count}\n"
print(line.rstrip())
f.write(line)
# Search for loops.
print("Search for loops")
loops_file = results_dir / "loops.txt"
ordering_file = results_dir / "ordering.txt"
# Pre-load all include files into memory for fast lookup.
includes_cache = {}
includes_lookup = {}
for include_file in sorted(includes_dir.iterdir(), key=lambda p: p.name):
if not include_file.is_file():
continue
includes_cache[include_file.name] = []
includes_lookup[include_file.name] = {}
with open(include_file, "r") as f:
for line in f:
parts = line.strip().split()
if len(parts) >= 2:
name, count = parts[0], int(parts[1])
includes_cache[include_file.name].append((name, count))
includes_lookup[include_file.name][name] = count
loops_found = set()
with open(loops_file, "w", buffering=8192) as loops_f, open(
ordering_file, "w", buffering=8192
) as ordering_f:
for source in sorted(includes_cache.keys()):
for include, include_freq in includes_cache[source]:
if include not in includes_lookup:
continue
source_freq = includes_lookup[include].get(source)
if source_freq is not None:
loop_key = tuple(sorted([source, include]))
if loop_key in loops_found:
continue
loops_found.add(loop_key)
loops_f.write(f"Loop: {source} {include}\n")
diff = include_freq - source_freq
if diff > 3:
loops_f.write(f" {source} > {include}\n\n")
elif diff < -3:
loops_f.write(f" {include} > {source}\n\n")
elif source_freq == include_freq:
loops_f.write(f" {include} == {source}\n\n")
else:
loops_f.write(f" {include} ~= {source}\n\n")
else:
ordering_f.write(f"{source} > {include}\n")
# Print results.
print("\nOrdering:")
with open(ordering_file, "r") as f:
print(f.read(), end="")
print("\nLoops:")
with open(loops_file, "r") as f:
print(f.read(), end="")
if __name__ == "__main__":
main()

View File

@@ -1,130 +0,0 @@
#!/bin/bash
# Usage: levelization.sh
# This script takes no parameters, reads no environment variables,
# and can be run from any directory, as long as it is in the expected
# location in the repo.
pushd $( dirname $0 )
if [ -v PS1 ]
then
# if the shell is interactive, clean up any flotsam before analyzing
git clean -ix
fi
# Ensure all sorting is ASCII-order consistently across platforms.
export LANG=C
rm -rfv results
mkdir results
includes="$( pwd )/results/rawincludes.txt"
pushd ../..
echo Raw includes:
grep -r '^[ ]*#include.*/.*\.h' include src | \
grep -v boost | tee ${includes}
popd
pushd results
oldifs=${IFS}
IFS=:
mkdir includes
mkdir includedby
echo Build levelization paths
exec 3< ${includes} # open rawincludes.txt for input
while read -r -u 3 file include
do
level=$( echo ${file} | cut -d/ -f 2,3 )
# If the "level" indicates a file, cut off the filename
if [[ "${level##*.}" != "${level}" ]]
then
# Use the "toplevel" label as a workaround for `sort`
# inconsistencies between different utility versions
level="$( dirname ${level} )/toplevel"
fi
level=$( echo ${level} | tr '/' '.' )
includelevel=$( echo ${include} | sed 's/.*["<]//; s/[">].*//' | \
cut -d/ -f 1,2 )
if [[ "${includelevel##*.}" != "${includelevel}" ]]
then
# Use the "toplevel" label as a workaround for `sort`
# inconsistencies between different utility versions
includelevel="$( dirname ${includelevel} )/toplevel"
fi
includelevel=$( echo ${includelevel} | tr '/' '.' )
if [[ "$level" != "$includelevel" ]]
then
echo $level $includelevel | tee -a paths.txt
fi
done
echo Sort and dedup paths
sort -ds paths.txt | uniq -c | tee sortedpaths.txt
mv sortedpaths.txt paths.txt
exec 3>&- #close fd 3
IFS=${oldifs}
unset oldifs
echo Split into flat-file database
exec 4<paths.txt # open paths.txt for input
while read -r -u 4 count level include
do
echo ${include} ${count} | tee -a includes/${level}
echo ${level} ${count} | tee -a includedby/${include}
done
exec 4>&- #close fd 4
loops="$( pwd )/loops.txt"
ordering="$( pwd )/ordering.txt"
pushd includes
echo Search for loops
# Redirect stdout to a file
exec 4>&1
exec 1>"${loops}"
for source in *
do
if [[ -f "$source" ]]
then
exec 5<"${source}" # open for input
while read -r -u 5 include includefreq
do
if [[ -f $include ]]
then
if grep -q -w $source $include
then
if grep -q -w "Loop: $include $source" "${loops}"
then
continue
fi
sourcefreq=$( grep -w $source $include | cut -d\ -f2 )
echo "Loop: $source $include"
# If the counts are close, indicate that the two modules are
# on the same level, though they shouldn't be
if [[ $(( $includefreq - $sourcefreq )) -gt 3 ]]
then
echo -e " $source > $include\n"
elif [[ $(( $sourcefreq - $includefreq )) -gt 3 ]]
then
echo -e " $include > $source\n"
elif [[ $sourcefreq -eq $includefreq ]]
then
echo -e " $include == $source\n"
else
echo -e " $include ~= $source\n"
fi
else
echo "$source > $include" >> "${ordering}"
fi
fi
done
exec 5>&- #close fd 5
fi
done
exec 1>&4 #close fd 1
exec 4>&- #close fd 4
cat "${ordering}"
cat "${loops}"
popd
popd
popd

View File

@@ -12,7 +12,7 @@ The server software that powers Xahau is called `xahaud` and is available in thi
### Build from Source
* [Read the build instructions in our documentation](https://xahau.network/infrastructure/building-xahau)
* [Read the build instructions in our documentation](https://xahau.network/docs/infrastructure/build-xahaud/)
* If you encounter any issues, please [open an issue](https://github.com/xahau/xahaud/issues)
## Highlights of Xahau

View File

@@ -68,6 +68,17 @@ target_link_libraries(xrpl.imports.main
$<$<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(target_link_modules)

View File

@@ -5,6 +5,7 @@
#include <memory>
#include <optional>
#include <ostream>
#include <set>
#include <stack>
#include <string>
#include <string_view>
@@ -282,7 +283,8 @@ check_guard(
* might have unforeseen consequences, without also rolling back further
* changes that are fine.
*/
uint64_t rulesVersion = 0
uint64_t rulesVersion = 0,
std::set<int>* out_callees = nullptr
)
{
@@ -492,17 +494,27 @@ check_guard(
{
REQUIRE(1);
uint64_t callee_idx = LEB();
// disallow calling of user defined functions inside a hook
// record user-defined function calls if tracking is enabled
if (callee_idx > last_import_idx)
{
GUARDLOG(hook::log::CALL_ILLEGAL)
<< "GuardCheck "
<< "Hook calls a function outside of the whitelisted "
"imports "
<< "codesec: " << codesec << " hook byte offset: " << i
<< "\n";
if (out_callees != nullptr)
{
// record the callee for call graph analysis
out_callees->insert(callee_idx);
}
else
{
// if not tracking, maintain original behavior: reject
GUARDLOG(hook::log::CALL_ILLEGAL)
<< "GuardCheck "
<< "Hook calls a function outside of the whitelisted "
"imports "
<< "codesec: " << codesec << " hook byte offset: " << i
<< "\n";
return {};
return {};
}
}
// enforce guard call limit
@@ -838,6 +850,42 @@ validateGuards(
*/
uint64_t rulesVersion = 0x00)
{
// Structure to track function call graph information
struct FunctionInfo
{
int func_idx;
std::set<int> callees; // functions this function calls
std::set<int> callers; // functions that call this function
bool has_loops; // whether this function contains loops
uint64_t local_wce; // local worst-case execution count
uint64_t total_wce; // total WCE including callees
bool wce_calculated; // whether total_wce has been computed
bool in_calculation; // for cycle detection in WCE calculation
FunctionInfo()
: func_idx(-1)
, has_loops(false)
, local_wce(0)
, total_wce(0)
, wce_calculated(false)
, in_calculation(false)
{
}
FunctionInfo(int idx, uint64_t local_wce_val, bool has_loops_val)
: func_idx(idx)
, has_loops(has_loops_val)
, local_wce(local_wce_val)
, total_wce(0)
, wce_calculated(false)
, in_calculation(false)
{
}
};
// Call graph: maps function index to its information
std::map<int, FunctionInfo> call_graph;
uint64_t byteCount = wasm.size();
// 63 bytes is the smallest possible valid hook wasm
@@ -1170,6 +1218,12 @@ validateGuards(
if (DEBUG_GUARD)
printf("Function map: func %d -> type %d\n", j, type_idx);
func_type_map[j] = type_idx;
// Step 4: Initialize FunctionInfo for each user-defined
// function func_idx starts from last_import_number + 1
int actual_func_idx = last_import_number + 1 + j;
call_graph[actual_func_idx] = FunctionInfo();
call_graph[actual_func_idx].func_idx = actual_func_idx;
}
}
@@ -1211,9 +1265,6 @@ validateGuards(
return {};
}
int64_t maxInstrCountHook = 0;
int64_t maxInstrCountCbak = 0;
// second pass... where we check all the guard function calls follow the
// guard rules minimal other validation in this pass because first pass
// caught most of it
@@ -1247,6 +1298,7 @@ validateGuards(
std::optional<
std::reference_wrapper<std::vector<uint8_t> const>>
first_signature;
bool helper_function = false;
if (auto const& usage = import_type_map.find(j);
usage != import_type_map.end())
{
@@ -1278,7 +1330,7 @@ validateGuards(
}
}
}
else if (j == hook_type_idx)
else if (j == hook_type_idx) // hook() or cbak() function type
{
// pass
}
@@ -1291,7 +1343,8 @@ validateGuards(
<< "Codesec: " << section_type << " "
<< "Local: " << j << " "
<< "Offset: " << i << "\n";
return {};
// return {};
helper_function = true;
}
int param_count = parseLeb128(wasm, i, &i);
@@ -1308,12 +1361,19 @@ validateGuards(
return {};
}
}
else if (helper_function)
{
// pass
}
else if (param_count != (*first_signature).get().size() - 1)
{
GUARDLOG(hook::log::FUNC_TYPE_INVALID)
<< "Malformed transaction. "
<< "Hook API: " << *first_name
<< " has the wrong number of parameters.\n";
<< " has the wrong number of parameters.\n"
<< "param_count: " << param_count << " "
<< "first_signature: "
<< (*first_signature).get().size() - 1 << "\n";
return {};
}
@@ -1360,6 +1420,10 @@ validateGuards(
return {};
}
}
else if (helper_function)
{
// pass
}
else if ((*first_signature).get()[k + 1] != param_type)
{
GUARDLOG(hook::log::FUNC_PARAM_INVALID)
@@ -1436,6 +1500,10 @@ validateGuards(
return {};
}
}
else if (helper_function)
{
// pass
}
else if ((*first_signature).get()[0] != result_type)
{
GUARDLOG(hook::log::FUNC_RETURN_INVALID)
@@ -1487,6 +1555,17 @@ validateGuards(
// execution to here means we are up to the actual expr for the
// codesec/function
// Step 5: Calculate actual function index and prepare callees
// tracking
int actual_func_idx = last_import_number + 1 + j;
std::set<int>* out_callees_ptr = nullptr;
// Only track callees if this function is in the call_graph
if (call_graph.find(actual_func_idx) != call_graph.end())
{
out_callees_ptr = &call_graph[actual_func_idx].callees;
}
auto valid = check_guard(
wasm,
j,
@@ -1496,33 +1575,188 @@ validateGuards(
last_import_number,
guardLog,
guardLogAccStr,
rulesVersion);
rulesVersion,
out_callees_ptr);
if (!valid)
return {};
if (hook_func_idx && *hook_func_idx == j)
maxInstrCountHook = *valid;
else if (cbak_func_idx && *cbak_func_idx == j)
maxInstrCountCbak = *valid;
else
// Step 5: Store local WCE and build bidirectional call
// relationships
if (call_graph.find(actual_func_idx) != call_graph.end())
{
if (DEBUG_GUARD)
printf(
"code section: %d not hook_func_idx: %d or "
"cbak_func_idx: %d\n",
j,
*hook_func_idx,
(cbak_func_idx ? *cbak_func_idx : -1));
// assert(false);
call_graph[actual_func_idx].local_wce = *valid;
// Build bidirectional relationships: for each callee, add
// this function as a caller
for (int callee_idx : call_graph[actual_func_idx].callees)
{
if (call_graph.find(callee_idx) != call_graph.end())
{
call_graph[callee_idx].callers.insert(
actual_func_idx);
}
}
}
// Note: We will calculate total WCE later after processing all
// functions
i = code_end;
}
}
i = next_section;
}
// execution to here means guards are installed correctly
// Step 6: Cycle detection using DFS
// Lambda function for DFS-based cycle detection
std::set<int> visited;
std::set<int> rec_stack;
std::function<bool(int)> detect_cycles_dfs = [&](int func_idx) -> bool {
if (rec_stack.find(func_idx) != rec_stack.end())
{
// Found a cycle: func_idx is already in the recursion stack
return true;
}
return std::pair<uint64_t, uint64_t>{maxInstrCountHook, maxInstrCountCbak};
if (visited.find(func_idx) != visited.end())
{
// Already visited and no cycle found from this node
return false;
}
visited.insert(func_idx);
rec_stack.insert(func_idx);
// Check all callees
if (call_graph.find(func_idx) != call_graph.end())
{
for (int callee_idx : call_graph[func_idx].callees)
{
if (detect_cycles_dfs(callee_idx))
{
return true;
}
}
}
rec_stack.erase(func_idx);
return false;
};
// Run cycle detection on all user-defined functions
for (const auto& [func_idx, func_info] : call_graph)
{
if (detect_cycles_dfs(func_idx))
{
GUARDLOG(hook::log::CALL_ILLEGAL)
<< "GuardCheck: Recursive function calls detected. "
<< "Hooks cannot contain recursive or mutually recursive "
"functions.\n";
return {};
}
}
// Step 7: Calculate total WCE for each function using bottom-up approach
// Lambda function for recursive WCE calculation with memoization
std::function<uint64_t(int)> calculate_function_wce =
[&](int func_idx) -> uint64_t {
// Check if function exists in call graph
if (call_graph.find(func_idx) == call_graph.end())
{
// This is an imported function, WCE = 0 (already accounted for)
return 0;
}
FunctionInfo& func_info = call_graph[func_idx];
// If already calculated, return cached result
if (func_info.wce_calculated)
{
return func_info.total_wce;
}
// Detect circular dependency in WCE calculation (should not happen
// after cycle detection)
if (func_info.in_calculation)
{
GUARDLOG(hook::log::CALL_ILLEGAL)
<< "GuardCheck: Internal error - circular dependency detected "
"during WCE calculation.\n";
return 0xFFFFFFFFU; // Return large value to trigger overflow error
}
func_info.in_calculation = true;
// Start with local WCE
uint64_t total = func_info.local_wce;
// Add WCE of all callees
for (int callee_idx : func_info.callees)
{
uint64_t callee_wce = calculate_function_wce(callee_idx);
// Check for overflow
if (total > 0xFFFFU || callee_wce > 0xFFFFU ||
(total + callee_wce) > 0xFFFFU)
{
func_info.in_calculation = false;
return 0xFFFFFFFFU; // Signal overflow
}
total += callee_wce;
}
func_info.total_wce = total;
func_info.wce_calculated = true;
func_info.in_calculation = false;
return total;
};
// Calculate WCE for hook and cbak functions
int64_t hook_wce_actual = 0;
int64_t cbak_wce_actual = 0;
if (hook_func_idx)
{
int actual_hook_idx = last_import_number + 1 + *hook_func_idx;
hook_wce_actual = calculate_function_wce(actual_hook_idx);
if (hook_wce_actual >= 0xFFFFU)
{
GUARDLOG(hook::log::INSTRUCTION_EXCESS)
<< "GuardCheck: hook() function exceeds maximum instruction "
"count (65535). "
<< "Total WCE including called functions: " << hook_wce_actual
<< "\n";
return {};
}
if (DEBUG_GUARD)
printf("hook() total WCE: %ld\n", hook_wce_actual);
}
if (cbak_func_idx)
{
int actual_cbak_idx = last_import_number + 1 + *cbak_func_idx;
cbak_wce_actual = calculate_function_wce(actual_cbak_idx);
if (cbak_wce_actual >= 0xFFFFU)
{
GUARDLOG(hook::log::INSTRUCTION_EXCESS)
<< "GuardCheck: cbak() function exceeds maximum instruction "
"count (65535). "
<< "Total WCE including called functions: " << cbak_wce_actual
<< "\n";
return {};
}
if (DEBUG_GUARD)
printf("cbak() total WCE: %ld\n", cbak_wce_actual);
}
// execution to here means guards are installed correctly and WCE is within
// limits
return std::pair<uint64_t, uint64_t>{hook_wce_actual, cbak_wce_actual};
}

View File

@@ -2971,6 +2971,809 @@ public:
}
}
void
testHelperFunctions(FeatureBitset features)
{
testcase("Test helper functions and recursion detection");
using namespace jtx;
Env env{*this, features};
auto const alice = Account{"alice"};
auto const bob = Account{"bob"};
env.fund(XRP(10000), alice, bob);
env.close();
// Test 1: Valid helper function without loops - should pass
{
/*
#include <stdint.h>
extern int32_t _g(uint32_t id, uint32_t maxiter);
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t
error_code);
extern int64_t hook_pos(void);
int64_t helper(int64_t n) { return n + hook_pos(); }
int64_t cbak(uint32_t reserved) {
_g(1, 1);
int64_t result = helper(34);
return accept(0, 0, result);
}
int64_t hook(uint32_t reserved) {
_g(1, 1);
int64_t result = helper(5);
return accept(0, 0, result);
}
*/
TestHook hook_wasm = wasm[R"[test.hook](
(module
(type (;0;) (func (param i32) (result i64)))
(type (;1;) (func (result i64)))
(type (;2;) (func (param i32 i32) (result i32)))
(type (;3;) (func (param i32 i32 i64) (result i64)))
(type (;4;) (func (param i64) (result i64)))
(import "env" "hook_pos" (func (;0;) (type 1)))
(import "env" "_g" (func (;1;) (type 2)))
(import "env" "accept" (func (;2;) (type 3)))
(func (;3;) (type 4) (param i64) (result i64)
call 0
local.get 0
i64.add)
(func (;4;) (type 0) (param i32) (result i64)
i32.const 1
i32.const 1
call 1
drop
i32.const 0
i32.const 0
i64.const 34
call 3
call 2)
(func (;5;) (type 0) (param i32) (result i64)
i32.const 1
i32.const 1
call 1
drop
i32.const 0
i32.const 0
i64.const 5
call 3
call 2)
(memory (;0;) 2)
(export "memory" (memory 0))
(export "cbak" (func 4))
(export "hook" (func 5)))
)[test.hook]"];
HASH_WASM(hook);
env(ripple::test::jtx::hook(
alice, {{hso(hook_wasm, overrideFlag)}}, 0),
M("Valid helper function without loops"),
HSFEE,
ter(tesSUCCESS));
env.close();
EXPECT_HOOK_FEE(hook, 14);
env(pay(bob, alice, XRP(1)), M("Test helper 1"), fee(XRP(1)));
env.close();
}
// Test 2: Helper function with guarded loop - should pass
{
/*
#include <stdint.h>
extern int32_t _g(uint32_t id, uint32_t maxiter);
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t
error_code); extern int64_t hook_pos(void);
int64_t helper(int64_t n) {
int64_t sum = 0;
for (int i = 0; i < 3; ++i) {
_g(2, 4);
sum += i * n;
}
return sum;
}
int64_t cbak(uint32_t reserved) {
_g(1, 1);
int64_t result = helper(2);
return accept(0, 0, result);
}
int64_t hook(uint32_t reserved) {
_g(1, 1);
int64_t result = helper(3);
return accept(0, 0, result);
}
*/
TestHook hook_wasm = wasm[R"[test.hook](
(module
(type (;0;) (func (param i32) (result i64)))
(type (;1;) (func (param i32 i32) (result i32)))
(type (;2;) (func (param i32 i32 i64) (result i64)))
(type (;3;) (func (param i64) (result i64)))
(import "env" "_g" (func (;0;) (type 1)))
(import "env" "accept" (func (;1;) (type 2)))
(func (;2;) (type 0) (param i32) (result i64)
i32.const 1
i32.const 1
call 0
drop
i32.const 0
i32.const 0
i64.const 3
call 3
call 1)
(func (;3;) (type 3) (param i64) (result i64)
i32.const 2
i32.const 4
call 0
drop
i32.const 2
i32.const 4
call 0
drop
i32.const 2
i32.const 4
call 0
drop
local.get 0
i64.const 3
i64.mul)
(func (;4;) (type 0) (param i32) (result i64)
i32.const 1
i32.const 1
call 0
drop
i32.const 0
i32.const 0
i64.const 2
call 3
call 1)
(memory (;0;) 2)
(export "memory" (memory 0))
(export "hook" (func 2))
(export "cbak" (func 4)))
)[test.hook]"];
HASH_WASM(hook);
env(ripple::test::jtx::hook(
alice, {{hso(hook_wasm, overrideFlag)}}, 0),
M("Helper function with guarded loop"),
HSFEE,
ter(tesSUCCESS));
env.close();
EXPECT_HOOK_FEE(hook, 26);
env(pay(bob, alice, XRP(1)), M("Test helper 2"), fee(XRP(1)));
env.close();
}
// Test 3: Direct recursion - should fail
{
/*
#include <stdint.h>
extern int32_t _g(uint32_t id, uint32_t maxiter);
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t
error_code);
extern int64_t hook_pos(void);
int64_t recursive_func(int64_t n) {
if (n <= 0)
return 0;
return n + recursive_func(n - hook_pos());
}
int64_t cbak(uint32_t reserved) {
_g(1, 1);
int64_t result = recursive_func(5);
return accept(0, 0, result);
}
int64_t hook(uint32_t reserved) {
_g(1, 1);
int64_t result = recursive_func(10);
return accept(0, 0, result);
}
*/
TestHook hook = wasm[R"[test.hook](
(module
(type (;0;) (func (param i32) (result i64)))
(type (;1;) (func (param i32 i32) (result i32)))
(type (;2;) (func (param i32 i32 i64) (result i64)))
(type (;3;) (func (result i64)))
(type (;4;) (func (param i64) (result i64)))
(import "env" "_g" (func $g (type 1)))
(import "env" "accept" (func $accept (type 2)))
(import "env" "hook_pos" (func $hook_pos (type 3)))
(func $recursive_func (type 4) (param $n i64) (result i64)
(if (result i64)
(i64.le_s (local.get $n) (i64.const 0))
(then
(i64.const 0)
)
(else
(i64.add
(local.get $n)
(call $recursive_func
(i64.sub (local.get $n) (call $hook_pos))
)
)
)
)
)
(func (;3;) (type 0) (param i32) (result i64) ;; cbak
i32.const 1
i32.const 1
call $g
drop
i32.const 0
i32.const 0
i64.const 5
call $recursive_func
call $accept
)
(func (;5;) (type 0) (param i32) (result i64) ;; hook
i32.const 1
i32.const 1
call $g
drop
i32.const 0
i32.const 0
i64.const 10
call $recursive_func
call $accept
)
(memory (;0;) 2)
(export "memory" (memory 0))
(export "cbak" (func 3))
(export "hook" (func 5)))
)[test.hook]"];
env(ripple::test::jtx::hook(alice, {{hso(hook)}}, 0),
M("Direct recursion should fail"),
HSFEE,
ter(temMALFORMED));
env.close();
}
// Test 4: Indirect recursion (A -> B -> A) - should fail
{
/*
#include <stdint.h>
extern int32_t _g(uint32_t id, uint32_t maxiter);
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t
error_code);
int64_t func_b(int64_t n);
int64_t func_a(int64_t n) {
if (n <= 0)
return 0;
return n + func_b(n - 1);
}
int64_t func_b(int64_t n) {
if (n <= 0)
return 0;
return n + func_a(n - 1);
}
int64_t cbak(uint32_t reserved) {
_g(1, 1);
int64_t result = func_a(5);
return accept(0, 0, result);
}
int64_t hook(uint32_t reserved) {
_g(1, 1);
int64_t result = func_a(10);
return accept(0, 0, result);
}
*/
TestHook hook = wasm[R"[test.hook](
(module
(import "env" "_g" (func $_g (param i32 i32) (result i32)))
(import "env" "accept" (func $accept (param i32 i32 i64) (result i64)))
(type $func_type (func (param i64) (result i64)))
(func $func_b (param $n i64) (result i64)
(if (result i64)
(i64.le_s (local.get $n) (i64.const 0))
(then
(i64.const 0)
)
(else
(i64.add
(local.get $n)
(call $func_a
(i64.sub (local.get $n) (i64.const 1))
)
)
)
)
)
(func $func_a (param $n i64) (result i64)
(if (result i64)
(i64.le_s (local.get $n) (i64.const 0))
(then
(i64.const 0)
)
(else
(i64.add
(local.get $n)
(call $func_b
(i64.sub (local.get $n) (i64.const 1))
)
)
)
)
)
(func $cbak (param $reserved i32) (result i64)
(local $result i64)
(drop (call $_g (i32.const 1) (i32.const 1)))
(local.set $result (call $func_a (i64.const 5)))
(call $accept (i32.const 0) (i32.const 0) (local.get $result))
)
(func $hook (param $reserved i32) (result i64)
(local $result i64)
(drop (call $_g (i32.const 1) (i32.const 1)))
(local.set $result (call $func_a (i64.const 10)))
(call $accept (i32.const 0) (i32.const 0) (local.get $result))
)
(export "cbak" (func $cbak))
(export "hook" (func $hook)))
)[test.hook]"];
env(ripple::test::jtx::hook(alice, {{hso(hook)}}, 0),
M("Indirect recursion should fail"),
HSFEE,
ter(temMALFORMED));
env.close();
}
// Test 5: Deep call chain (A -> B -> C -> D) - should pass if WCE is OK
{
/*
#include <stdint.h>
extern int32_t _g(uint32_t id, uint32_t maxiter);
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t
error_code);
extern int64_t hook_pos(void);
int64_t helper(int64_t n) { return n + hook_pos(); }
int64_t cbak(uint32_t reserved) {
_g(1, 1);
int64_t result = helper(34);
return accept(0, 0, result);
}
int64_t hook(uint32_t reserved) {
_g(1, 1);
int64_t result = helper(5);
return accept(0, 0, result);
}
*/
TestHook hook_wasm = wasm[R"[test.hook](
(module
(type (;0;) (func (param i32) (result i64)))
(type (;1;) (func (result i64)))
(type (;2;) (func (param i32 i32) (result i32)))
(type (;3;) (func (param i32 i32 i64) (result i64)))
(type (;4;) (func (param i64) (result i64)))
(import "env" "hook_pos" (func (;0;) (type 1)))
(import "env" "_g" (func (;1;) (type 2)))
(import "env" "accept" (func (;2;) (type 3)))
(func (;3;) (type 4) (param i64) (result i64)
call 0
local.get 0
i64.add)
(func (;4;) (type 0) (param i32) (result i64)
i32.const 1
i32.const 1
call 1
drop
i32.const 0
i32.const 0
i64.const 34
call 3
call 2)
(func (;5;) (type 0) (param i32) (result i64)
i32.const 1
i32.const 1
call 1
drop
i32.const 0
i32.const 0
i64.const 5
call 3
call 2)
(memory (;0;) 2)
(export "memory" (memory 0))
(export "cbak" (func 4))
(export "hook" (func 5)))
)[test.hook]"];
HASH_WASM(hook);
env(ripple::test::jtx::hook(
alice, {{hso(hook_wasm, overrideFlag)}}, 0),
M("Deep call chain without recursion"),
HSFEE,
ter(tesSUCCESS));
env.close();
EXPECT_HOOK_FEE(hook, 14);
env(pay(bob, alice, XRP(1)), M("Test helper 5"), fee(XRP(1)));
env.close();
}
// Test 6: Helper called multiple times - WCE should accumulate
{
/*
#include <stdint.h>
extern int32_t _g(uint32_t id, uint32_t maxiter);
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t
error_code);
int64_t expensive_helper() {
int64_t sum = 0;
for (int i = 0; i < 100; ++i) {
_g(2, 301);
sum += i;
}
return sum;
}
int64_t hook(uint32_t reserved) {
_g(1, 1);
int64_t result = 0;
result += expensive_helper();
result += expensive_helper();
result += expensive_helper();
return accept(0, 0, result);
}
*/
TestHook hook_wasm = wasm[R"[test.hook](
(module
(type (;0;) (func (param i32 i32) (result i32)))
(type (;1;) (func (param i32 i32 i64) (result i64)))
(type (;2;) (func (result i64)))
(type (;3;) (func (param i32) (result i64)))
(import "env" "_g" (func (;0;) (type 0)))
(import "env" "accept" (func (;1;) (type 1)))
(func (;2;) (type 2) (result i64)
(local i64)
i64.const 100
local.set 0
loop ;; label = @1
i32.const 2
i32.const 301
call 0
drop
local.get 0
i64.const 1
i64.sub
local.tee 0
i64.eqz
i32.eqz
br_if 0 (;@1;)
end
i64.const 4950)
(func (;3;) (type 3) (param i32) (result i64)
i32.const 1
i32.const 1
call 0
drop
i32.const 0
i32.const 0
call 2
call 2
i64.add
call 2
i64.add
call 1)
(memory (;0;) 2)
(export "memory" (memory 0))
(export "hook" (func 3)))
)[test.hook]"];
HASH_WASM(hook);
env(ripple::test::jtx::hook(
alice, {{hso(hook_wasm, overrideFlag)}}, 0),
M("Helper called multiple times"),
HSFEE,
ter(tesSUCCESS));
env.close();
EXPECT_HOOK_FEE(hook, 2727);
env(pay(bob, alice, XRP(1)), M("Test helper 6"), fee(XRP(1)));
env.close();
}
// Test 7: WCE overflow through many helpers - should fail
{
/*
#include <stdint.h>
extern int32_t _g(uint32_t id, uint32_t maxiter);
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t
error_code);
int64_t large_helper(int64_t n) {
int64_t sum = n;
for (int i = 0; i < 10000; ++i) {
_g(2, 10001);
sum += i;
}
return sum;
}
int64_t cbak(uint32_t reserved) {
_g(1, 1);
int64_t result = 10;
for (int i = 0; i < 10; ++i) {
_g(3, 11);
result += large_helper(10);
}
return accept(0, 0, result);
}
int64_t hook(uint32_t reserved) {
_g(1, 1);
int64_t result = 0;
for (int i = 0; i < 10; ++i) {
_g(3, 11);
result += large_helper(0);
}
return accept(0, 0, result);
}
*/
TestHook hook = wasm[R"[test.hook](
(module
(type (;0;) (func (param i32) (result i64)))
(type (;1;) (func (param i32 i32) (result i32)))
(type (;2;) (func (param i32 i32 i64) (result i64)))
(type (;3;) (func (param i64) (result i64)))
(import "env" "_g" (func (;0;) (type 1)))
(import "env" "accept" (func (;1;) (type 2)))
(func (;2;) (type 0) (param i32) (result i64)
(local i64)
i32.const 1
i32.const 1
call 0
drop
i32.const 10
local.set 0
i64.const 10
local.set 1
loop ;; label = @1
i32.const 3
i32.const 11
call 0
drop
i64.const 10
call 3
local.get 1
i64.add
local.set 1
local.get 0
i32.const 1
i32.sub
local.tee 0
br_if 0 (;@1;)
end
i32.const 0
i32.const 0
local.get 1
call 1)
(func (;3;) (type 3) (param i64) (result i64)
(local i64)
i64.const 10000
local.set 1
loop ;; label = @1
i32.const 2
i32.const 10001
call 0
drop
local.get 1
i64.const 1
i64.sub
local.tee 1
i64.eqz
i32.eqz
br_if 0 (;@1;)
end
local.get 0
i64.const 49995000
i64.add)
(func (;4;) (type 0) (param i32) (result i64)
(local i64)
i32.const 1
i32.const 1
call 0
drop
i32.const 10
local.set 0
loop ;; label = @1
i32.const 3
i32.const 11
call 0
drop
i64.const 0
call 3
local.get 1
i64.add
local.set 1
local.get 0
i32.const 1
i32.sub
local.tee 0
br_if 0 (;@1;)
end
i32.const 0
i32.const 0
local.get 1
call 1)
(memory (;0;) 2)
(export "memory" (memory 0))
(export "cbak" (func 2))
(export "hook" (func 4)))
)[test.hook]"];
env(ripple::test::jtx::hook(alice, {{hso(hook)}}, 0),
M("WCE overflow through helpers"),
HSFEE,
ter(temMALFORMED));
env.close();
}
// Test 8: guard inside guard
{
/*
#include <stdint.h>
extern int32_t _g(uint32_t id, uint32_t maxiter);
extern int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t
error_code);
int64_t helper(int64_t n) {
int64_t sum = n;
for (int i = 0; i < 100; ++i) {
_g(2, 1000);
sum += i;
}
return sum;
}
int64_t cbak(uint32_t reserved) {
_g(1, 1);
int64_t result = 10;
for (int i = 0; i < 10; ++i) {
_g(3, 11);
result += helper(10);
}
return accept(0, 0, result);
}
int64_t hook(uint32_t reserved) {
_g(1, 1);
int64_t result = 0;
for (int i = 0; i < 10; ++i) {
_g(3, 11);
result += helper(0);
}
return accept(0, 0, result);
}
*/
TestHook hook_wasm = wasm[R"[test.hook](
(module
(type (;0;) (func (param i32) (result i64)))
(type (;1;) (func (param i32 i32) (result i32)))
(type (;2;) (func (param i32 i32 i64) (result i64)))
(type (;3;) (func (param i64) (result i64)))
(import "env" "_g" (func (;0;) (type 1)))
(import "env" "accept" (func (;1;) (type 2)))
(func (;2;) (type 3) (param i64) (result i64)
(local i64)
i64.const 100
local.set 1
loop ;; label = @1
i32.const 2
i32.const 1000
call 0
drop
local.get 1
i64.const 1
i64.sub
local.tee 1
i64.eqz
i32.eqz
br_if 0 (;@1;)
end
local.get 0
i64.const 4950
i64.add)
(func (;3;) (type 0) (param i32) (result i64)
(local i64)
i32.const 1
i32.const 1
call 0
drop
i32.const 10
local.set 0
i64.const 10
local.set 1
loop ;; label = @1
i32.const 3
i32.const 11
call 0
drop
i64.const 10
call 2
local.get 1
i64.add
local.set 1
local.get 0
i32.const 1
i32.sub
local.tee 0
br_if 0 (;@1;)
end
i32.const 0
i32.const 0
local.get 1
call 1)
(func (;4;) (type 0) (param i32) (result i64)
(local i64)
i32.const 1
i32.const 1
call 0
drop
i32.const 10
local.set 0
loop ;; label = @1
i32.const 3
i32.const 11
call 0
drop
i64.const 0
call 2
local.get 1
i64.add
local.set 1
local.get 0
i32.const 1
i32.sub
local.tee 0
br_if 0 (;@1;)
end
i32.const 0
i32.const 0
local.get 1
call 1)
(memory (;0;) 2)
(export "memory" (memory 0))
(export "cbak" (func 3))
(export "hook" (func 4)))
)[test.hook]"];
HASH_WASM(hook);
env(ripple::test::jtx::hook(
alice, {{hso(hook_wasm, overrideFlag)}}, 0),
M("guard inside guard"),
HSFEE,
ter(tesSUCCESS));
EXPECT_HOOK_FEE(hook, 9151);
env(pay(bob, alice, XRP(1)), M("Test helper 8"), fee(XRP(1)));
env.close();
}
}
void
test_emit(FeatureBitset features)
{
@@ -14729,6 +15532,7 @@ public:
test_rollback(features);
testGuards(features);
testHelperFunctions(features);
test_emit(features); //
test_prepare(features);

File diff suppressed because it is too large Load Diff

View File

@@ -58,8 +58,21 @@ cat $INPUT_FILE | tr '\n' '\f' |
then
echo '#include "api.h"' > "$WASM_DIR/test-$COUNTER-gen.c"
tr '\f' '\n' <<< $line >> "$WASM_DIR/test-$COUNTER-gen.c"
DECLARED="`tr '\f' '\n' <<< $line | grep -E '(extern|define) ' | grep -Eo '[a-z\-\_]+ *\(' | grep -v 'sizeof' | sed -E 's/[^a-z\-\_]//g' | sort | uniq`"
USED="`tr '\f' '\n' <<< $line | grep -vE '(extern|define) ' | grep -Eo '[a-z\-\_]+\(' | grep -v 'sizeof' | sed -E 's/[^a-z\-\_]//g' | grep -vE '^(hook|cbak)' | sort | uniq`"
DECLARED="`tr '\f' '\n' <<< $line \
| grep -E '(extern|static|define) ' \
| grep -Eo '[a-z\-\_]+ *\(' \
| grep -v 'sizeof' \
| sed -E 's/[^a-z\-\_]//g' \
| grep -vE '^__attribute__$' \
| sort | uniq`"
USED="`tr '\f' '\n' <<< $line \
| grep -vE '(extern|static|define) ' \
| grep -Eo '[a-z\-\_]+\(' \
| grep -v 'sizeof' \
| sed -E 's/[^a-z\-\_]//g' \
| grep -vE '^(__attribute__|hook|cbak)$' \
| sort | uniq`"
ONCE="`echo $DECLARED $USED | tr ' ' '\n' | sort | uniq -c | grep '1 ' | sed -E 's/^ *1 //g'`"
FILTER="`echo $DECLARED | tr ' ' '|' | sed -E 's/\|$//g'`"
UNDECL="`echo $ONCE | grep -v -E $FILTER 2>/dev/null || echo ''`"
@@ -69,7 +82,7 @@ cat $INPUT_FILE | tr '\n' '\f' |
echo "$line"
exit 1
fi
wasmcc -x c /dev/stdin -o /dev/stdout -O2 -Wl,--allow-undefined <<< "`tr '\f' '\n' <<< $line`" |
wasmcc -x c /dev/stdin -o /dev/stdout -O2 -Wl,--allow-undefined,--export=hook,--export=cbak <<< "`tr '\f' '\n' <<< $line`" |
hook-cleaner - - 2>/dev/null |
xxd -p -u -c 10 |
sed -E 's/../0x&U,/g' | sed -E 's/^/ /g' >> $OUTPUT_FILE

View File

@@ -65,29 +65,16 @@ hso_delete(void (*f)(Json::Value& jv))
Json::Value
hso(std::vector<uint8_t> const& wasmBytes, void (*f)(Json::Value& jv))
{
if (wasmBytes.size() == 0)
throw std::runtime_error("empty hook wasm passed to hso()");
Json::Value jv;
jv[jss::CreateCode] = strHex(wasmBytes);
{
jv[jss::HookOn] =
"0000000000000000000000000000000000000000000000000000000000000000";
jv[jss::HookNamespace] = to_string(uint256{beast::zero});
jv[jss::HookApiVersion] = Json::Value{0};
}
if (f)
f(jv);
return jv;
return hso(strHex(wasmBytes), f);
}
Json::Value
hso(std::string const& wasmHex, void (*f)(Json::Value& jv))
{
if (wasmHex.size() == 0)
throw std::runtime_error("empty hook wasm passed to hso()");
throw std::runtime_error(
"empty hook wasm passed to hso(): run "
"src/test/app/build_test_hooks.sh to generate the hook wasm");
Json::Value jv;
jv[jss::CreateCode] = wasmHex;