mirror of
https://github.com/Xahau/xahaud.git
synced 2026-03-19 19:12:22 +00:00
Compare commits
3 Commits
gas-hook
...
backport-l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
823d41775a | ||
|
|
5c1d7d9ae9 | ||
|
|
70d4d3ba81 |
4
.github/workflows/levelization.yml
vendored
4
.github/workflows/levelization.yml
vendored
@@ -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.
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -53,6 +53,9 @@ Builds/levelization/results/paths.txt
|
||||
Builds/levelization/results/includes/
|
||||
Builds/levelization/results/includedby/
|
||||
|
||||
# Python
|
||||
__pycache__
|
||||
|
||||
# Ignore tmp directory.
|
||||
tmp
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
283
Builds/levelization/levelization.py
Executable file
283
Builds/levelization/levelization.py
Executable 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()
|
||||
@@ -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
|
||||
@@ -72,10 +72,6 @@
|
||||
#define sfLockCount ((2U << 16U) + 49U)
|
||||
#define sfFirstNFTokenSequence ((2U << 16U) + 50U)
|
||||
#define sfOracleDocumentID ((2U << 16U) + 51U)
|
||||
#define sfHookCallbackGas ((2U << 16U) + 89U)
|
||||
#define sfHookWeakGas ((2U << 16U) + 90U)
|
||||
#define sfHookInstructionCost ((2U << 16U) + 91U)
|
||||
#define sfHookGas ((2U << 16U) + 92U)
|
||||
#define sfStartTime ((2U << 16U) + 93U)
|
||||
#define sfRepeatCount ((2U << 16U) + 94U)
|
||||
#define sfDelaySeconds ((2U << 16U) + 95U)
|
||||
|
||||
@@ -266,8 +266,6 @@ enum hook_log_code : uint16_t {
|
||||
CUSTOM_SECTION_DISALLOWED =
|
||||
86, // the wasm contained a custom section (id=0)
|
||||
INTERNAL_ERROR = 87, // an internal error described by the log text
|
||||
MEMORY_PAGE_LIMIT =
|
||||
88, // memory import declares min or max exceeding the page limit
|
||||
// RH NOTE: only HookSet msgs got log codes, possibly all Hook log lines
|
||||
// should get a code?
|
||||
};
|
||||
@@ -393,7 +391,6 @@ enum ExitType : uint8_t {
|
||||
WASM_ERROR = 1,
|
||||
ROLLBACK = 2,
|
||||
ACCEPT = 3,
|
||||
GAS_INSUFFICIENT = 4,
|
||||
};
|
||||
|
||||
const uint16_t max_state_modifications = 256;
|
||||
@@ -403,8 +400,6 @@ const uint8_t max_emit = 255;
|
||||
const uint8_t max_params = 16;
|
||||
const double fee_base_multiplier = 1.1f;
|
||||
|
||||
const uint8_t max_memory_pages = 8;
|
||||
|
||||
using APIWhitelist = std::map<std::string, std::vector<uint8_t>>;
|
||||
|
||||
// RH NOTE: Find descriptions of api functions in ./impl/applyHook.cpp and
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2024 XRPL-Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_APP_HOOK_GASVALIDATOR_H_INCLUDED
|
||||
#define RIPPLE_APP_HOOK_GASVALIDATOR_H_INCLUDED
|
||||
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Forward declaration
|
||||
using GuardLog =
|
||||
std::optional<std::reference_wrapper<std::basic_ostream<char>>>;
|
||||
|
||||
namespace hook {
|
||||
|
||||
/**
|
||||
* @brief Validate WASM host functions for Gas-type hooks
|
||||
*
|
||||
* Validates that a WASM binary only imports allowed host functions
|
||||
* and does not import the _g (guard) function, which is only for
|
||||
* Guard-type hooks.
|
||||
*
|
||||
* @param wasm The WASM binary to validate
|
||||
* @param guardLog Logging function for validation errors
|
||||
* @param guardLogAccStr Account string for logging
|
||||
* @return bool if validation succeeds,
|
||||
* @return true if contains cbak function
|
||||
* @return false if otherwise
|
||||
* @return error message if validation fails
|
||||
*/
|
||||
ripple::Expected<bool, std::string>
|
||||
validateWasmHostFunctionsForGas(
|
||||
std::vector<uint8_t> const& wasm,
|
||||
ripple::Rules const& rules,
|
||||
beast::Journal const& j);
|
||||
|
||||
} // namespace hook
|
||||
|
||||
#endif // RIPPLE_APP_HOOK_GASVALIDATOR_H_INCLUDED
|
||||
@@ -80,7 +80,7 @@ namespace detail {
|
||||
// Feature.cpp. Because it's only used to reserve storage, and determine how
|
||||
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
|
||||
// the actual number of amendments. A LogicError on startup will verify this.
|
||||
static constexpr std::size_t numFeatures = 114;
|
||||
static constexpr std::size_t numFeatures = 113;
|
||||
|
||||
/** Amendments that this server supports and the default voting behavior.
|
||||
Whether they are enabled depends on the Rules defined in the validated
|
||||
|
||||
@@ -362,8 +362,6 @@ enum TECcodes : TERUnderlyingType {
|
||||
tecARRAY_TOO_LARGE = 197,
|
||||
tecLOCKED = 198,
|
||||
tecBAD_CREDENTIALS = 199,
|
||||
tecHOOK_INSUFFICIENT_GAS = 200,
|
||||
tecHOOK_INVALID = 201,
|
||||
tecLAST_POSSIBLE_ENTRY = 255,
|
||||
};
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||
// in include/xrpl/protocol/Feature.h.
|
||||
|
||||
XRPL_FEATURE(HookGas, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(HookAPISerializedType240, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(PermissionedDomains, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(DynamicNFT, Supported::no, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -103,12 +103,10 @@ LEDGER_ENTRY(ltHOOK_DEFINITION, 'D', HookDefinition, hook_definition, ({
|
||||
{sfCreateCode, soeREQUIRED},
|
||||
{sfHookSetTxnID, soeREQUIRED},
|
||||
{sfReferenceCount, soeREQUIRED},
|
||||
{sfFee, soeOPTIONAL},
|
||||
{sfFee, soeREQUIRED},
|
||||
{sfHookCallbackFee, soeOPTIONAL},
|
||||
{sfPreviousTxnID, soeOPTIONAL},
|
||||
{sfPreviousTxnLgrSeq, soeOPTIONAL},
|
||||
{sfHookCallbackGas, soeOPTIONAL},
|
||||
{sfHookWeakGas, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** A ledger object containing a hook-emitted transaction from a previous hook execution.
|
||||
|
||||
@@ -115,10 +115,6 @@ TYPED_SFIELD(sfLockCount, UINT32, 49)
|
||||
TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50)
|
||||
TYPED_SFIELD(sfOracleDocumentID, UINT32, 51)
|
||||
|
||||
TYPED_SFIELD(sfHookCallbackGas, UINT32, 89)
|
||||
TYPED_SFIELD(sfHookWeakGas, UINT32, 90)
|
||||
TYPED_SFIELD(sfHookInstructionCost, UINT32, 91)
|
||||
TYPED_SFIELD(sfHookGas, UINT32, 92)
|
||||
TYPED_SFIELD(sfStartTime, UINT32, 93)
|
||||
TYPED_SFIELD(sfRepeatCount, UINT32, 94)
|
||||
TYPED_SFIELD(sfDelaySeconds, UINT32, 95)
|
||||
|
||||
@@ -87,8 +87,6 @@ JSS(HookParameterName); // field
|
||||
JSS(HookParameterValue); // field
|
||||
JSS(HookParameter); // field
|
||||
JSS(HookGrant); // field
|
||||
JSS(HookCallbackGas); // field
|
||||
JSS(HookWeakGas); // field
|
||||
JSS(isSerialized); // out: RPC server_definitions
|
||||
// matches definitions.json format
|
||||
JSS(isSigningField); // out: RPC server_definitions
|
||||
|
||||
@@ -78,10 +78,7 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfHookExecutionIndex, soeREQUIRED},
|
||||
{sfHookStateChangeCount, soeREQUIRED},
|
||||
{sfHookEmitCount, soeREQUIRED},
|
||||
{sfFlags, soeOPTIONAL},
|
||||
{sfHookInstructionCost, soeOPTIONAL},
|
||||
{sfHookWeakGas, soeOPTIONAL},
|
||||
{sfHookCallbackGas, soeOPTIONAL}});
|
||||
{sfFlags, soeOPTIONAL}});
|
||||
|
||||
add(sfHookEmission.jsonName,
|
||||
sfHookEmission.getCode(),
|
||||
@@ -101,7 +98,7 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfHookCanEmit, soeOPTIONAL},
|
||||
{sfHookApiVersion, soeREQUIRED},
|
||||
{sfFlags, soeREQUIRED},
|
||||
{sfFee, soeOPTIONAL}});
|
||||
{sfFee, soeREQUIRED}});
|
||||
|
||||
add(sfHook.jsonName,
|
||||
sfHook.getCode(),
|
||||
@@ -115,8 +112,6 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfHookOnOutgoing, soeOPTIONAL},
|
||||
{sfHookCanEmit, soeOPTIONAL},
|
||||
{sfHookApiVersion, soeOPTIONAL},
|
||||
{sfHookCallbackGas, soeOPTIONAL},
|
||||
{sfHookWeakGas, soeOPTIONAL},
|
||||
{sfFlags, soeOPTIONAL}});
|
||||
|
||||
add(sfHookGrant.jsonName,
|
||||
|
||||
@@ -124,8 +124,6 @@ transResults()
|
||||
MAKE_ERROR(tecARRAY_TOO_LARGE, "Array is too large."),
|
||||
MAKE_ERROR(tecLOCKED, "Fund is locked."),
|
||||
MAKE_ERROR(tecBAD_CREDENTIALS, "Bad credentials."),
|
||||
MAKE_ERROR(tecHOOK_INSUFFICIENT_GAS, "Insufficient hook gas to complete the transaction."),
|
||||
MAKE_ERROR(tecHOOK_INVALID, "Invalid hook."),
|
||||
|
||||
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
|
||||
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
|
||||
|
||||
@@ -48,7 +48,6 @@ TxFormats::TxFormats()
|
||||
{sfFirstLedgerSequence, soeOPTIONAL},
|
||||
{sfNetworkID, soeOPTIONAL},
|
||||
{sfHookParameters, soeOPTIONAL},
|
||||
{sfHookGas, soeOPTIONAL},
|
||||
};
|
||||
|
||||
#pragma push_macro("UNWRAP")
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -41,28 +41,17 @@ echo '
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
std::map<std::string, std::vector<uint8_t>> wasm = {' > $OUTPUT_FILE
|
||||
# Counter file for sharing between subshells
|
||||
COUNTER_FILE=$(mktemp)
|
||||
echo "0" > $COUNTER_FILE
|
||||
trap "rm -f $COUNTER_FILE" EXIT
|
||||
|
||||
# Process both [test.hook] and [test.hook.gas] blocks
|
||||
process_block() {
|
||||
local tag_pattern="$1" # regex pattern: "hook" or "hook\.gas"
|
||||
local tag_output="$2" # output string: "hook" or "hook.gas"
|
||||
local skip_cleaner="$3" # "0" for no skip, "1" for skip
|
||||
|
||||
cat $INPUT_FILE | tr '\n' '\f' |
|
||||
grep -Po "R\"\[test\.${tag_pattern}\](.*?)\[test\.${tag_pattern}\]\"" |
|
||||
sed -E "s/R\"\[test\.${tag_pattern}\]\(//g" |
|
||||
sed -E "s/\)\[test\.${tag_pattern}\]\"[\f \t]*/\/*end*\//g" |
|
||||
COUNTER="0"
|
||||
cat $INPUT_FILE | tr '\n' '\f' |
|
||||
grep -Po 'R"\[test\.hook\](.*?)\[test\.hook\]"' |
|
||||
sed -E 's/R"\[test\.hook\]\(//g' |
|
||||
sed -E 's/\)\[test\.hook\]"[\f \t]*/\/*end*\//g' |
|
||||
while read -r line
|
||||
do
|
||||
COUNTER=$(cat $COUNTER_FILE)
|
||||
echo "/* ==== WASM: $COUNTER ==== */" >> $OUTPUT_FILE
|
||||
echo -n "{ R\"[test.${tag_output}](" >> $OUTPUT_FILE
|
||||
echo -n '{ R"[test.hook](' >> $OUTPUT_FILE
|
||||
cat <<< "$line" | sed -E 's/.{7}$//g' | tr -d '\n' | tr '\f' '\n' >> $OUTPUT_FILE
|
||||
echo ")[test.${tag_output}]\"," >> $OUTPUT_FILE
|
||||
echo ')[test.hook]",' >> $OUTPUT_FILE
|
||||
echo "{" >> $OUTPUT_FILE
|
||||
WAT=`grep -Eo '\(module' <<< $line | wc -l`
|
||||
if [ "$WAT" -eq "0" ]
|
||||
@@ -80,19 +69,10 @@ process_block() {
|
||||
echo "$line"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$skip_cleaner" -eq "1" ]
|
||||
then
|
||||
# Skip hook-cleaner for [test.hook.gas]
|
||||
wasmcc -x c /dev/stdin -o /dev/stdout -O2 -Wl,--allow-undefined -Wno-int-conversion -Wno-pointer-sign -Wno-return-type <<< "`tr '\f' '\n' <<< $line`" |
|
||||
xxd -p -u -c 10 |
|
||||
sed -E 's/../0x&U,/g' | sed -E 's/^/ /g' >> $OUTPUT_FILE
|
||||
else
|
||||
# Run hook-cleaner for [test.hook]
|
||||
wasmcc -x c /dev/stdin -o /dev/stdout -O2 -Wl,--allow-undefined -Wno-int-conversion -Wno-pointer-sign -Wno-return-type <<< "`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
|
||||
fi
|
||||
wasmcc -x c /dev/stdin -o /dev/stdout -O2 -Wl,--allow-undefined <<< "`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
|
||||
else
|
||||
wat2wasm - -o /dev/stdout <<< "`tr '\f' '\n' <<< $(sed -E 's/.{7}$//g' <<< $line)`" |
|
||||
xxd -p -u -c 10 |
|
||||
@@ -105,15 +85,8 @@ process_block() {
|
||||
fi
|
||||
echo '}},' >> $OUTPUT_FILE
|
||||
echo >> $OUTPUT_FILE
|
||||
echo $((COUNTER + 1)) > $COUNTER_FILE
|
||||
COUNTER=`echo $COUNTER + 1 | bc`
|
||||
done
|
||||
}
|
||||
|
||||
# Process [test.hook] blocks (with hook-cleaner)
|
||||
process_block "hook" "hook" "0"
|
||||
|
||||
# Process [test.hook.gas] blocks (without hook-cleaner)
|
||||
process_block "hook\.gas" "hook.gas" "1"
|
||||
echo '};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,9 +44,6 @@ hso(std::vector<uint8_t> const& wasmBytes, void (*f)(Json::Value& jv) = 0);
|
||||
Json::Value
|
||||
hso(std::string const& wasmHex, void (*f)(Json::Value& jv) = 0);
|
||||
|
||||
Json::Value
|
||||
hso(uint256 const& hookHash, void (*f)(Json::Value& jv) = 0);
|
||||
|
||||
Json::Value
|
||||
hso_delete(void (*f)(Json::Value& jv) = 0);
|
||||
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 XRPL Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TEST_JTX_HOOKGAS_H_INCLUDED
|
||||
#define RIPPLE_TEST_JTX_HOOKGAS_H_INCLUDED
|
||||
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/tags.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
|
||||
/** Set the HookGas on a JTx. */
|
||||
class hookgas
|
||||
{
|
||||
private:
|
||||
std::uint32_t gas_;
|
||||
|
||||
public:
|
||||
hookgas(std::uint32_t gas) : gas_{gas}
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const;
|
||||
};
|
||||
|
||||
/** Set the HookCallbackGas on a JTx. */
|
||||
class cbakgas
|
||||
{
|
||||
private:
|
||||
std::uint32_t gas_;
|
||||
|
||||
public:
|
||||
cbakgas(std::uint32_t gas) : gas_{gas}
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const;
|
||||
};
|
||||
|
||||
/** Set the HookWeakGas on a JTx. */
|
||||
class weakgas
|
||||
{
|
||||
private:
|
||||
std::uint32_t gas_;
|
||||
|
||||
public:
|
||||
weakgas(std::uint32_t gas) : gas_{gas}
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const;
|
||||
};
|
||||
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -104,16 +104,6 @@ hso(std::string const& wasmHex, void (*f)(Json::Value& jv))
|
||||
return jv;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
hso(uint256 const& hookHash, void (*f)(Json::Value& jv))
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::HookHash] = to_string(hookHash);
|
||||
if (f)
|
||||
f(jv);
|
||||
return jv;
|
||||
}
|
||||
|
||||
// Helper function to create HookContext with external stateMap
|
||||
hook::HookContext
|
||||
makeStubHookContext(
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 XRPL Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <test/jtx/hookgas.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
|
||||
void
|
||||
hookgas::operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt[sfHookGas.jsonName] = gas_;
|
||||
}
|
||||
|
||||
void
|
||||
cbakgas::operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt[sfHookCallbackGas.jsonName] = gas_;
|
||||
}
|
||||
|
||||
void
|
||||
weakgas::operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt[sfHookWeakGas.jsonName] = gas_;
|
||||
}
|
||||
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -124,9 +124,7 @@ apply(
|
||||
uint32_t wasmParam,
|
||||
uint8_t hookChainPosition,
|
||||
// result of apply() if this is weak exec
|
||||
std::shared_ptr<STObject const> const& provisionalMeta,
|
||||
uint16_t hookApiVersion,
|
||||
uint32_t hookGas);
|
||||
std::shared_ptr<STObject const> const& provisionalMeta);
|
||||
|
||||
struct HookContext;
|
||||
|
||||
@@ -164,7 +162,6 @@ struct HookResult
|
||||
std::string exitReason{""};
|
||||
int64_t exitCode{-1};
|
||||
uint64_t instructionCount{0};
|
||||
uint64_t instructionCost{0};
|
||||
bool hasCallback = false; // true iff this hook wasm has a cbak function
|
||||
bool isCallback =
|
||||
false; // true iff this hook execution is a callback in action
|
||||
@@ -177,8 +174,6 @@ struct HookResult
|
||||
false; // hook_again allows strong pre-apply to nominate
|
||||
// additional weak post-apply execution
|
||||
std::shared_ptr<STObject const> provisionalMeta;
|
||||
uint16_t hookApiVersion = 0; // 0 = Guard-type, 1 = Gas-type
|
||||
uint32_t hookGas; // Gas limit for Gas-type hooks
|
||||
};
|
||||
|
||||
class HookExecutor;
|
||||
@@ -330,18 +325,12 @@ public:
|
||||
WasmEdge_ConfigureContext* conf = NULL;
|
||||
WasmEdge_VMContext* ctx = NULL;
|
||||
|
||||
WasmEdgeVM(uint16_t hookApiVersion)
|
||||
WasmEdgeVM()
|
||||
{
|
||||
conf = WasmEdge_ConfigureCreate();
|
||||
if (!conf)
|
||||
return;
|
||||
WasmEdge_ConfigureStatisticsSetInstructionCounting(conf, true);
|
||||
if (hookApiVersion == 1)
|
||||
{
|
||||
WasmEdge_ConfigureStatisticsSetCostMeasuring(conf, true);
|
||||
WasmEdge_ConfigureSetMaxMemoryPage(
|
||||
conf, hook_api::max_memory_pages);
|
||||
}
|
||||
ctx = WasmEdge_VMCreate(conf, NULL);
|
||||
}
|
||||
|
||||
@@ -376,9 +365,9 @@ public:
|
||||
* Validate that a web assembly blob can be loaded by wasmedge
|
||||
*/
|
||||
static std::optional<std::string>
|
||||
validateWasm(const void* wasm, size_t len, uint16_t hookApiVersion)
|
||||
validateWasm(const void* wasm, size_t len)
|
||||
{
|
||||
WasmEdgeVM vm{hookApiVersion};
|
||||
WasmEdgeVM vm;
|
||||
|
||||
if (!vm.sane())
|
||||
return "Could not create WASMEDGE instance";
|
||||
@@ -423,7 +412,7 @@ public:
|
||||
|
||||
WasmEdge_LogOff();
|
||||
|
||||
WasmEdgeVM vm{hookCtx.result.hookApiVersion};
|
||||
WasmEdgeVM vm;
|
||||
|
||||
if (!vm.sane())
|
||||
{
|
||||
@@ -444,22 +433,6 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
// Set Gas limit for Gas-type hooks (HookApiVersion == 1)
|
||||
if (hookCtx.result.hookApiVersion == 1)
|
||||
{
|
||||
auto* statsCtx = WasmEdge_VMGetStatisticsContext(vm.ctx);
|
||||
if (statsCtx)
|
||||
{
|
||||
// Convert HookGas to cost limit count (1 Gas = 1 cost)
|
||||
uint32_t gasLimit = hookCtx.result.hookGas;
|
||||
WasmEdge_StatisticsSetCostLimit(statsCtx, gasLimit);
|
||||
|
||||
JLOG(j.trace())
|
||||
<< "HookInfo[" << HC_ACC() << "]: Set Gas limit to "
|
||||
<< gasLimit << " cost limit for Gas-type Hook";
|
||||
}
|
||||
}
|
||||
|
||||
WasmEdge_Value params[1] = {WasmEdge_ValueGenI32((int64_t)wasmParam)};
|
||||
WasmEdge_Value returns[1];
|
||||
|
||||
@@ -473,31 +446,16 @@ public:
|
||||
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);
|
||||
hookCtx.result.instructionCost =
|
||||
WasmEdge_StatisticsGetTotalCost(statsCtx);
|
||||
|
||||
if (auto err = getWasmError("WASM VM error", res); err)
|
||||
{
|
||||
JLOG(j.trace()) << "HookError[" << HC_ACC() << "]: " << *err;
|
||||
|
||||
// Check if error is due to Gas limit exceeded for Gas-type hooks
|
||||
if (hookCtx.result.hookApiVersion == 1 &&
|
||||
err->find("cost limit exceeded") != std::string::npos)
|
||||
{
|
||||
JLOG(j.trace()) << "HookError[" << HC_ACC()
|
||||
<< "]: Gas limit exceeded. Limit was "
|
||||
<< hookCtx.result.hookGas;
|
||||
hookCtx.result.exitType = hook_api::ExitType::GAS_INSUFFICIENT;
|
||||
}
|
||||
else
|
||||
{
|
||||
hookCtx.result.exitType = hook_api::ExitType::WASM_ERROR;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// RH NOTE: stack unwind will clean up WasmEdgeVM
|
||||
}
|
||||
|
||||
@@ -1,438 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2024 XRPL-Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/hook/GasValidator.h>
|
||||
#include <xrpl/hook/Guard.h>
|
||||
#include <xrpl/hook/Macro.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <wasmedge/wasmedge.h>
|
||||
|
||||
namespace hook {
|
||||
|
||||
Expected<bool, std::string>
|
||||
validateExportSection(
|
||||
WasmEdge_ASTModuleContext* astModule,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
// Get export count
|
||||
uint32_t exportCount = WasmEdge_ASTModuleListExportsLength(astModule);
|
||||
if (exportCount == 0)
|
||||
{
|
||||
return Unexpected("WASM must export at least hook API functions");
|
||||
}
|
||||
|
||||
// Get exports
|
||||
const WasmEdge_ExportTypeContext* exports[256];
|
||||
uint32_t actualExportCount = std::min(exportCount, 256u);
|
||||
actualExportCount =
|
||||
WasmEdge_ASTModuleListExports(astModule, exports, actualExportCount);
|
||||
|
||||
// Track if we found required hook() function
|
||||
bool foundHook = false;
|
||||
bool foundCbak = false;
|
||||
|
||||
// Check each export
|
||||
for (uint32_t i = 0; i < actualExportCount; i++)
|
||||
{
|
||||
WasmEdge_ExternalType const type =
|
||||
WasmEdge_ExportTypeGetExternalType(exports[i]);
|
||||
|
||||
// Validate memory exports for page limits
|
||||
if (type == WasmEdge_ExternalType_Memory)
|
||||
{
|
||||
const WasmEdge_MemoryTypeContext* memType =
|
||||
WasmEdge_ExportTypeGetMemoryType(astModule, exports[i]);
|
||||
|
||||
if (!memType)
|
||||
{
|
||||
JLOG(j.trace()) << "HookSet(" << hook::log::MEMORY_PAGE_LIMIT
|
||||
<< "): Exported memory has no type definition";
|
||||
return Unexpected("Exported memory has no type definition");
|
||||
}
|
||||
|
||||
WasmEdge_Limit limit = WasmEdge_MemoryTypeGetLimit(memType);
|
||||
|
||||
constexpr uint32_t kMaxMemoryPages = hook_api::max_memory_pages;
|
||||
|
||||
if (limit.Min > kMaxMemoryPages)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "HookSet(" << hook::log::MEMORY_PAGE_LIMIT
|
||||
<< "): Gas-type hook exported memory minimum "
|
||||
"pages ("
|
||||
<< limit.Min << ") exceeds limit of " << kMaxMemoryPages;
|
||||
return Unexpected(
|
||||
"Gas-type hook exported memory minimum pages "
|
||||
"exceed limit of 8");
|
||||
}
|
||||
|
||||
if (limit.HasMax && limit.Max > kMaxMemoryPages)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "HookSet(" << hook::log::MEMORY_PAGE_LIMIT
|
||||
<< "): Gas-type hook exported memory maximum "
|
||||
"pages ("
|
||||
<< limit.Max << ") exceeds limit of " << kMaxMemoryPages;
|
||||
return Unexpected(
|
||||
"Gas-type hook exported memory maximum pages "
|
||||
"exceed limit of 8");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only check function exports
|
||||
if (type != WasmEdge_ExternalType_Function)
|
||||
continue;
|
||||
|
||||
WasmEdge_String const name =
|
||||
WasmEdge_ExportTypeGetExternalName(exports[i]);
|
||||
std::string nameStr(name.Buf, name.Length);
|
||||
|
||||
if (nameStr.starts_with("__"))
|
||||
{
|
||||
// skip runtime support functions
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only allow hook() and cbak() exports
|
||||
if (nameStr != "hook" && nameStr != "cbak")
|
||||
{
|
||||
JLOG(j.trace()) << "HookSet(" << hook::log::EXPORT_MISSING
|
||||
<< "): Unauthorized export function '" << nameStr
|
||||
<< "'. Only 'hook' and 'cbak' are allowed";
|
||||
return Unexpected(
|
||||
"Unauthorized export function '" + nameStr +
|
||||
"'. Only 'hook' and 'cbak' are allowed");
|
||||
}
|
||||
|
||||
if (nameStr == "hook")
|
||||
foundHook = true;
|
||||
if (nameStr == "cbak")
|
||||
foundCbak = true;
|
||||
|
||||
// Get function type to validate signature
|
||||
WasmEdge_FunctionTypeContext const* functionType =
|
||||
WasmEdge_ExportTypeGetFunctionType(astModule, exports[i]);
|
||||
|
||||
// Validate parameter count (must be exactly 1)
|
||||
uint32_t paramCount =
|
||||
WasmEdge_FunctionTypeGetParametersLength(functionType);
|
||||
if (paramCount != 1)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "HookSet("
|
||||
<< (nameStr == "hook" ? hook::log::EXPORT_HOOK_FUNC
|
||||
: hook::log::EXPORT_CBAK_FUNC)
|
||||
<< "): Function '" << nameStr
|
||||
<< "' must have exactly 1 parameter, found " << paramCount;
|
||||
return Unexpected(
|
||||
"Function '" + nameStr +
|
||||
"' must have exactly 1 parameter of type uint32_t");
|
||||
}
|
||||
|
||||
// Validate parameter type (must be i32 / uint32_t)
|
||||
WasmEdge_ValType parameters[1];
|
||||
WasmEdge_FunctionTypeGetParameters(functionType, parameters, 1);
|
||||
if (parameters[0] != WasmEdge_ValType_I32)
|
||||
{
|
||||
JLOG(j.trace()) << "HookSet("
|
||||
<< (nameStr == "hook" ? hook::log::EXPORT_HOOK_FUNC
|
||||
: hook::log::EXPORT_CBAK_FUNC)
|
||||
<< "): Function '" << nameStr
|
||||
<< "' parameter must be uint32_t (i32), found type "
|
||||
<< parameters[0];
|
||||
return Unexpected(
|
||||
"Function '" + nameStr + "' parameter must be uint32_t (i32)");
|
||||
}
|
||||
|
||||
// Validate return type (must be i64 / uint64_t)
|
||||
uint32_t returnCount =
|
||||
WasmEdge_FunctionTypeGetReturnsLength(functionType);
|
||||
if (returnCount != 1)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "HookSet("
|
||||
<< (nameStr == "hook" ? hook::log::EXPORT_HOOK_FUNC
|
||||
: hook::log::EXPORT_CBAK_FUNC)
|
||||
<< "): Function '" << nameStr
|
||||
<< "' must return exactly 1 value, found " << returnCount;
|
||||
return Unexpected(
|
||||
"Function '" + nameStr +
|
||||
"' must return exactly 1 value of type uint64_t");
|
||||
}
|
||||
|
||||
WasmEdge_ValType returns[1];
|
||||
WasmEdge_FunctionTypeGetReturns(functionType, returns, 1);
|
||||
if (returns[0] != WasmEdge_ValType_I64)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "HookSet("
|
||||
<< (nameStr == "hook" ? hook::log::EXPORT_HOOK_FUNC
|
||||
: hook::log::EXPORT_CBAK_FUNC)
|
||||
<< "): Function '" << nameStr
|
||||
<< "' return type must be uint64_t (i64), found type "
|
||||
<< returns[0];
|
||||
return Unexpected(
|
||||
"Function '" + nameStr +
|
||||
"' return type must be uint64_t (i64)");
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure hook() function was exported (required)
|
||||
if (!foundHook)
|
||||
{
|
||||
JLOG(j.trace()) << "HookSet(" << hook::log::EXPORT_MISSING
|
||||
<< "): Required function 'hook' not found in exports";
|
||||
return Unexpected("Required function 'hook' not found in exports");
|
||||
}
|
||||
|
||||
return foundCbak;
|
||||
}
|
||||
|
||||
Expected<void, std::string>
|
||||
validateImportSection(
|
||||
WasmEdge_ASTModuleContext* astModule,
|
||||
Rules const& rules,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
// Get import count
|
||||
uint32_t importCount = WasmEdge_ASTModuleListImportsLength(astModule);
|
||||
|
||||
if (importCount == 0)
|
||||
{
|
||||
JLOG(j.trace()) << "HookSet(" << hook::log::IMPORTS_MISSING
|
||||
<< "): WASM must import at least hook API functions";
|
||||
return Unexpected("WASM must import at least hook API functions");
|
||||
}
|
||||
|
||||
// Get imports (max 256)
|
||||
const WasmEdge_ImportTypeContext* imports[256];
|
||||
uint32_t actualImportCount = std::min(importCount, 256u);
|
||||
actualImportCount =
|
||||
WasmEdge_ASTModuleListImports(astModule, imports, actualImportCount);
|
||||
|
||||
std::optional<std::string> error;
|
||||
|
||||
// Check each import
|
||||
for (uint32_t i = 0; i < actualImportCount; i++)
|
||||
{
|
||||
WasmEdge_String moduleName =
|
||||
WasmEdge_ImportTypeGetModuleName(imports[i]);
|
||||
WasmEdge_String externalName =
|
||||
WasmEdge_ImportTypeGetExternalName(imports[i]);
|
||||
WasmEdge_ExternalType extType =
|
||||
WasmEdge_ImportTypeGetExternalType(imports[i]);
|
||||
|
||||
// Only check function imports
|
||||
if (extType != WasmEdge_ExternalType_Function)
|
||||
continue;
|
||||
|
||||
// Convert WasmEdge_String to std::string for comparison
|
||||
std::string modName(moduleName.Buf, moduleName.Length);
|
||||
std::string extName(externalName.Buf, externalName.Length);
|
||||
|
||||
// Check module name is "env"
|
||||
if (modName != "env")
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "HookSet(" << hook::log::IMPORT_MODULE_ENV
|
||||
<< "): Import module must be 'env', found: " << modName;
|
||||
return Unexpected("Import module must be 'env', found: " + modName);
|
||||
}
|
||||
|
||||
// Check for forbidden _g function (guard function)
|
||||
if (extName == "_g")
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "HookSet(" << hook::log::IMPORT_ILLEGAL
|
||||
<< "): Gas-type hooks cannot import _g (guard) function";
|
||||
return Unexpected(
|
||||
"Gas-type hooks cannot import _g (guard) function");
|
||||
}
|
||||
|
||||
// Determine which whitelist contains the function and get expected
|
||||
// signature
|
||||
std::vector<uint8_t> const* expectedSig = nullptr;
|
||||
auto importWhitelist = hook_api::getImportWhitelist(rules);
|
||||
|
||||
auto baseIt = importWhitelist.find(extName);
|
||||
if (baseIt != importWhitelist.end())
|
||||
expectedSig = &baseIt->second;
|
||||
|
||||
// Function not in any whitelist
|
||||
if (!expectedSig)
|
||||
{
|
||||
JLOG(j.trace()) << "HookSet(" << hook::log::IMPORT_ILLEGAL
|
||||
<< "): Import not in whitelist: " << extName;
|
||||
return Unexpected("Import not in whitelist: " + extName);
|
||||
}
|
||||
|
||||
// Get function type for signature validation
|
||||
WasmEdge_FunctionTypeContext const* functionType =
|
||||
WasmEdge_ImportTypeGetFunctionType(astModule, imports[i]);
|
||||
|
||||
if (!functionType)
|
||||
{
|
||||
JLOG(j.trace()) << "HookSet(" << hook::log::FUNC_TYPELESS
|
||||
<< "): Import function '" << extName
|
||||
<< "' has no function type definition";
|
||||
return Unexpected(
|
||||
"Import function '" + extName +
|
||||
"' has no function type definition");
|
||||
}
|
||||
|
||||
// Validate return type
|
||||
// expectedSig[0] is the return type
|
||||
uint32_t returnCount =
|
||||
WasmEdge_FunctionTypeGetReturnsLength(functionType);
|
||||
|
||||
if (returnCount != 1)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "HookSet(" << hook::log::FUNC_RETURN_COUNT
|
||||
<< "): Import function '" << extName
|
||||
<< "' must return exactly 1 value, found " << returnCount;
|
||||
return Unexpected(
|
||||
"Import function '" + extName +
|
||||
"' must return exactly 1 value");
|
||||
}
|
||||
|
||||
WasmEdge_ValType actualReturnType;
|
||||
WasmEdge_FunctionTypeGetReturns(functionType, &actualReturnType, 1);
|
||||
|
||||
if (actualReturnType != (*expectedSig)[0])
|
||||
{
|
||||
JLOG(j.trace()) << "HookSet(" << hook::log::FUNC_RETURN_INVALID
|
||||
<< "): Import function '" << extName
|
||||
<< "' has incorrect return type. Expected "
|
||||
<< static_cast<int>((*expectedSig)[0]) << ", found "
|
||||
<< static_cast<int>(actualReturnType);
|
||||
return Unexpected(
|
||||
"Import function '" + extName + "' has incorrect return type");
|
||||
}
|
||||
|
||||
// Validate parameter count and types
|
||||
// expectedSig[1..N] are the parameter types
|
||||
uint32_t expectedParamCount =
|
||||
expectedSig->size() > 0 ? expectedSig->size() - 1 : 0;
|
||||
uint32_t actualParamCount =
|
||||
WasmEdge_FunctionTypeGetParametersLength(functionType);
|
||||
|
||||
if (actualParamCount != expectedParamCount)
|
||||
{
|
||||
JLOG(j.trace()) << "HookSet(" << hook::log::FUNC_PARAM_INVALID
|
||||
<< "): Import function '" << extName << "' has "
|
||||
<< actualParamCount << " parameters, expected "
|
||||
<< expectedParamCount;
|
||||
return Unexpected(
|
||||
"Import function '" + extName +
|
||||
"' has incorrect parameter count");
|
||||
}
|
||||
|
||||
// Validate each parameter type
|
||||
if (actualParamCount > 0)
|
||||
{
|
||||
std::vector<WasmEdge_ValType> actualParams(actualParamCount);
|
||||
WasmEdge_FunctionTypeGetParameters(
|
||||
functionType, actualParams.data(), actualParamCount);
|
||||
|
||||
for (uint32_t p = 0; p < actualParamCount; p++)
|
||||
{
|
||||
uint8_t expectedParamType = (*expectedSig)[1 + p];
|
||||
if (actualParams[p] != expectedParamType)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "HookSet(" << hook::log::FUNC_PARAM_INVALID
|
||||
<< "): Import function '" << extName << "' parameter "
|
||||
<< p << " has incorrect type. Expected "
|
||||
<< static_cast<int>(expectedParamType) << ", found "
|
||||
<< static_cast<int>(actualParams[p]);
|
||||
return Unexpected(
|
||||
"Import function '" + extName +
|
||||
"' has incorrect parameter types");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Expected<bool, std::string>
|
||||
validateWasmHostFunctionsForGas(
|
||||
std::vector<uint8_t> const& wasm,
|
||||
Rules const& rules,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
// Create WasmEdge Loader
|
||||
WasmEdge_LoaderContext* loader = WasmEdge_LoaderCreate(NULL);
|
||||
if (!loader)
|
||||
{
|
||||
return Unexpected("Failed to create WasmEdge Loader");
|
||||
}
|
||||
|
||||
// Parse WASM binary
|
||||
WasmEdge_ASTModuleContext* astModule = NULL;
|
||||
WasmEdge_Result res = WasmEdge_LoaderParseFromBuffer(
|
||||
loader, &astModule, wasm.data(), wasm.size());
|
||||
|
||||
if (!WasmEdge_ResultOK(res))
|
||||
{
|
||||
WasmEdge_LoaderDelete(loader);
|
||||
const char* msg = WasmEdge_ResultGetMessage(res);
|
||||
return Unexpected(
|
||||
std::string("Failed to parse WASM: ") +
|
||||
(msg ? msg : "unknown error"));
|
||||
}
|
||||
|
||||
bool foundCbak = false;
|
||||
|
||||
//
|
||||
// check export section
|
||||
//
|
||||
auto resultExport = validateExportSection(astModule, j);
|
||||
if (!resultExport)
|
||||
{
|
||||
WasmEdge_ASTModuleDelete(astModule);
|
||||
WasmEdge_LoaderDelete(loader);
|
||||
return Unexpected(resultExport.error());
|
||||
}
|
||||
foundCbak = resultExport.value();
|
||||
|
||||
//
|
||||
// check import section
|
||||
//
|
||||
if (auto result = validateImportSection(astModule, rules, j); !result)
|
||||
{
|
||||
WasmEdge_ASTModuleDelete(astModule);
|
||||
WasmEdge_LoaderDelete(loader);
|
||||
return Unexpected(result.error());
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
WasmEdge_ASTModuleDelete(astModule);
|
||||
WasmEdge_LoaderDelete(loader);
|
||||
|
||||
return foundCbak;
|
||||
}
|
||||
|
||||
} // namespace hook
|
||||
@@ -1197,9 +1197,7 @@ hook::apply(
|
||||
bool isStrong,
|
||||
uint32_t wasmParam,
|
||||
uint8_t hookChainPosition,
|
||||
std::shared_ptr<STObject const> const& provisionalMeta,
|
||||
uint16_t hookApiVersion,
|
||||
uint32_t hookGas)
|
||||
std::shared_ptr<STObject const> const& provisionalMeta)
|
||||
{
|
||||
HookContext hookCtx = {
|
||||
.applyCtx = applyCtx,
|
||||
@@ -1230,9 +1228,7 @@ hook::apply(
|
||||
.wasmParam = wasmParam,
|
||||
.hookChainPosition = hookChainPosition,
|
||||
.foreignStateSetDisabled = false,
|
||||
.provisionalMeta = provisionalMeta,
|
||||
.hookApiVersion = hookApiVersion,
|
||||
.hookGas = hookGas},
|
||||
.provisionalMeta = provisionalMeta},
|
||||
.emitFailure = isCallback && wasmParam & 1
|
||||
? std::optional<ripple::STObject>(
|
||||
(*(applyCtx.view().peek(keylet::emittedTxn(
|
||||
@@ -1247,14 +1243,10 @@ hook::apply(
|
||||
executor.executeWasm(
|
||||
wasm.data(), (size_t)wasm.size(), isCallback, wasmParam, j);
|
||||
|
||||
auto const& exitType = hookCtx.result.exitType;
|
||||
auto const& exitTypeStr = exitType == ExitType::ROLLBACK ? "ROLLBACK"
|
||||
: exitType == ExitType::ACCEPT ? "ACCEPT"
|
||||
: exitType == ExitType::GAS_INSUFFICIENT ? "GAS_INSUFFICIENT"
|
||||
: exitType == ExitType::WASM_ERROR ? "WASM_ERROR"
|
||||
: "UNSET";
|
||||
|
||||
JLOG(j.trace()) << "HookInfo[" << HC_ACC() << "]: " << exitTypeStr
|
||||
JLOG(j.trace()) << "HookInfo[" << HC_ACC() << "]: "
|
||||
<< (hookCtx.result.exitType == hook_api::ExitType::ROLLBACK
|
||||
? "ROLLBACK"
|
||||
: "ACCEPT")
|
||||
<< " RS: '" << hookCtx.result.exitReason.c_str()
|
||||
<< "' RC: " << hookCtx.result.exitCode;
|
||||
|
||||
@@ -1743,9 +1735,6 @@ hook::finalizeHookResult(
|
||||
ripple::Slice{
|
||||
hookResult.exitReason.data(), hookResult.exitReason.size()});
|
||||
meta.setFieldU64(sfHookInstructionCount, hookResult.instructionCount);
|
||||
if (hookResult.hookApiVersion == 1)
|
||||
meta.setFieldU32(sfHookInstructionCost, hookResult.instructionCost);
|
||||
|
||||
meta.setFieldU16(
|
||||
sfHookEmitCount,
|
||||
emission_txnid.size()); // this will never wrap, hard limit
|
||||
|
||||
@@ -639,7 +639,7 @@ Change::activateXahauGenesis()
|
||||
|
||||
std::optional<std::string> result2 =
|
||||
hook::HookExecutor::validateWasm(
|
||||
wasmBytes.data(), (size_t)wasmBytes.size(), 0);
|
||||
wasmBytes.data(), (size_t)wasmBytes.size());
|
||||
|
||||
if (result2)
|
||||
{
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include <xrpld/ledger/ApplyView.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/hook/Enum.h>
|
||||
#include <xrpl/hook/GasValidator.h>
|
||||
#include <xrpl/hook/Guard.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -231,9 +230,7 @@ SetHook::inferOperation(STObject const& hookSetObj)
|
||||
hookSetObj.isFieldPresent(sfHookOnIncoming))) &&
|
||||
!hookSetObj.isFieldPresent(sfHookCanEmit) &&
|
||||
!hookSetObj.isFieldPresent(sfHookApiVersion) &&
|
||||
!hookSetObj.isFieldPresent(sfFlags) &&
|
||||
!hookSetObj.isFieldPresent(sfHookCallbackGas) &&
|
||||
!hookSetObj.isFieldPresent(sfHookWeakGas))
|
||||
!hookSetObj.isFieldPresent(sfFlags))
|
||||
return hsoNOOP;
|
||||
|
||||
uint32_t flags = hookSetObj.isFieldPresent(sfFlags)
|
||||
@@ -270,8 +267,6 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
hookSetObj.isFieldPresent(sfHookOnIncoming) ||
|
||||
hookSetObj.isFieldPresent(sfHookCanEmit) ||
|
||||
hookSetObj.isFieldPresent(sfHookApiVersion) ||
|
||||
hookSetObj.isFieldPresent(sfHookCallbackGas) ||
|
||||
hookSetObj.isFieldPresent(sfHookWeakGas) ||
|
||||
!hookSetObj.isFieldPresent(sfFlags) ||
|
||||
!hookSetObj.isFieldPresent(sfHookNamespace))
|
||||
{
|
||||
@@ -305,8 +300,6 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
hookSetObj.isFieldPresent(sfHookCanEmit) ||
|
||||
hookSetObj.isFieldPresent(sfHookApiVersion) ||
|
||||
hookSetObj.isFieldPresent(sfHookNamespace) ||
|
||||
hookSetObj.isFieldPresent(sfHookCallbackGas) ||
|
||||
hookSetObj.isFieldPresent(sfHookWeakGas) ||
|
||||
!hookSetObj.isFieldPresent(sfFlags))
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
@@ -409,8 +402,6 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
// namespace may be valid, if the user so chooses
|
||||
// hookon may be present if the user so chooses
|
||||
// flags may be present if the user so chooses
|
||||
// hookweakgas may be present if the user so chooses
|
||||
// hookcallbackgas may be present if the user so chooses
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -450,8 +441,7 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
}
|
||||
|
||||
auto version = hookSetObj.getFieldU16(sfHookApiVersion);
|
||||
|
||||
if (!ctx.rules.enabled(featureHookGas) && version != 0)
|
||||
if (version != 0)
|
||||
{
|
||||
// we currently only accept api version 0
|
||||
JLOG(ctx.j.trace())
|
||||
@@ -461,92 +451,6 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
return false;
|
||||
}
|
||||
|
||||
// allow only version=0 and version=1
|
||||
if (version != 0 && version != 1)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << ::hook::log::API_INVALID << ")["
|
||||
<< HS_ACC()
|
||||
<< "]: Malformed transaction: SetHook "
|
||||
"sfHook->sfHookApiVersion invalid. (Must be 0 or 1).";
|
||||
return false;
|
||||
}
|
||||
|
||||
// validate sfHookCallbackGas
|
||||
auto hasHookCallbackGas = false;
|
||||
if (hookSetObj.isFieldPresent(sfHookCallbackGas))
|
||||
{
|
||||
if (version != 1)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::HOOK_INVALID_FIELD << ")["
|
||||
<< HS_ACC()
|
||||
<< "]: Malformed transaction: SetHook "
|
||||
"sfHookCallbackGas is "
|
||||
"not allowed in version "
|
||||
<< version << ".";
|
||||
return false;
|
||||
}
|
||||
if (hookSetObj.getFieldU32(sfHookCallbackGas) == 0)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::HOOK_INVALID_FIELD << ")["
|
||||
<< HS_ACC()
|
||||
<< "]: Malformed transaction: SetHook "
|
||||
"sfHookCallbackGas must be greater than 0.";
|
||||
return false;
|
||||
}
|
||||
hasHookCallbackGas = true;
|
||||
}
|
||||
|
||||
// validate sfHookWeakGas
|
||||
if (hookSetObj.isFieldPresent(sfHookWeakGas))
|
||||
{
|
||||
if (version != 1)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::HOOK_INVALID_FIELD << ")["
|
||||
<< HS_ACC()
|
||||
<< "]: Malformed transaction: SetHook sfHookWeakGas is "
|
||||
"not allowed in version "
|
||||
<< version << ".";
|
||||
return false;
|
||||
}
|
||||
if (hookSetObj.getFieldU32(sfHookWeakGas) == 0)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::HOOK_INVALID_FIELD << ")["
|
||||
<< HS_ACC()
|
||||
<< "]: Malformed transaction: SetHook sfHookWeakGas "
|
||||
"must be greater than 0.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(flags & hsfCOLLECT))
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::HOOK_INVALID_FIELD << ")["
|
||||
<< HS_ACC()
|
||||
<< "]: Malformed transaction: SetHook with "
|
||||
"sfHookWeakGas must be used with hsfCOLLECT "
|
||||
"flag.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (version == 1)
|
||||
{
|
||||
if (flags & hsfCOLLECT)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::HOOK_INVALID_FIELD << ")["
|
||||
<< HS_ACC()
|
||||
<< "]: Malformed transaction: SetHook with "
|
||||
"sfHookWeakGas must be used with hsfCOLLECT "
|
||||
"flag.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// validate sfHookOn
|
||||
if (!hookSetObj.isFieldPresent(sfHookOn))
|
||||
{
|
||||
@@ -612,7 +516,6 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
return {};
|
||||
|
||||
Blob hook = hookSetObj.getFieldVL(sfCreateCode);
|
||||
auto version = hookSetObj.getFieldU16(sfHookApiVersion);
|
||||
|
||||
// RH NOTE: validateGuards has a generic non-rippled specific
|
||||
// interface so it can be used in other projects (i.e. tooling).
|
||||
@@ -630,97 +533,46 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
hsacc = ss.str();
|
||||
}
|
||||
|
||||
uint64_t maxInstrCountHook = 0;
|
||||
uint64_t maxInstrCountCbak = 0;
|
||||
auto result = validateGuards(
|
||||
hook, // wasm to verify
|
||||
logger,
|
||||
hsacc,
|
||||
hook_api::getImportWhitelist(ctx.rules),
|
||||
hook_api::getGuardRulesVersion(ctx.rules));
|
||||
|
||||
if (version == 0) // Guard type
|
||||
if (ctx.j.trace())
|
||||
{
|
||||
auto result = validateGuards(
|
||||
hook, // wasm to verify
|
||||
logger,
|
||||
hsacc,
|
||||
hook_api::getImportWhitelist(ctx.rules),
|
||||
hook_api::getGuardRulesVersion(ctx.rules));
|
||||
// clunky but to get the stream to accept the output
|
||||
// correctly we will split on new line and feed each line
|
||||
// one by one into the trace stream beast::Journal should be
|
||||
// updated to inherit from basic_ostream<char> then this
|
||||
// wouldn't be necessary.
|
||||
|
||||
if (ctx.j.trace())
|
||||
// is this a needless copy or does the compiler do copy
|
||||
// elision here?
|
||||
std::string s = loggerStream.str();
|
||||
|
||||
char* data = s.data();
|
||||
size_t len = s.size();
|
||||
|
||||
char* last = data;
|
||||
size_t i = 0;
|
||||
for (; i < len; ++i)
|
||||
{
|
||||
// clunky but to get the stream to accept the output
|
||||
// correctly we will split on new line and feed each
|
||||
// line one by one into the trace stream beast::Journal
|
||||
// should be updated to inherit from basic_ostream<char>
|
||||
// then this wouldn't be necessary.
|
||||
|
||||
// is this a needless copy or does the compiler do copy
|
||||
// elision here?
|
||||
std::string s = loggerStream.str();
|
||||
|
||||
char* data = s.data();
|
||||
size_t len = s.size();
|
||||
|
||||
char* last = data;
|
||||
size_t i = 0;
|
||||
for (; i < len; ++i)
|
||||
if (data[i] == '\n')
|
||||
{
|
||||
if (data[i] == '\n')
|
||||
{
|
||||
data[i] = '\0';
|
||||
ctx.j.trace() << last;
|
||||
last = data + i;
|
||||
}
|
||||
}
|
||||
|
||||
if (last < data + i)
|
||||
data[i] = '\0';
|
||||
ctx.j.trace() << last;
|
||||
last = data + i;
|
||||
}
|
||||
}
|
||||
|
||||
if (!result)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::WASM_BAD_MAGIC << ")["
|
||||
<< HS_ACC()
|
||||
<< "]: Malformed transaction: SetHook "
|
||||
"sfCreateCode failed validation.";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::tie(maxInstrCountHook, maxInstrCountCbak) = *result;
|
||||
if (last < data + i)
|
||||
ctx.j.trace() << last;
|
||||
}
|
||||
else if (version == 1) // Gas type
|
||||
{
|
||||
// validate with GasValidator
|
||||
auto validationResult =
|
||||
hook::validateWasmHostFunctionsForGas(
|
||||
hook, ctx.rules, ctx.j);
|
||||
|
||||
if (!validationResult)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::IMPORT_ILLEGAL << ")["
|
||||
<< HS_ACC()
|
||||
<< "]: Malformed transaction: Gas-type Hook "
|
||||
"validation failed: "
|
||||
<< validationResult.error();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const hasCbak = validationResult.value();
|
||||
if ((!hasCbak && hasHookCallbackGas) ||
|
||||
(hasCbak && !hasHookCallbackGas))
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::HOOK_INVALID_FIELD
|
||||
<< ")[" << HS_ACC()
|
||||
<< "]: Malformed transaction: Gas-type Hook must "
|
||||
"contain either sfHookCallbackGas if it "
|
||||
"contains cbak function";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Gas type: maxInstrCount is not pre-calculated (use Gas
|
||||
// limit at runtime)
|
||||
maxInstrCountHook = 0;
|
||||
maxInstrCountCbak = 0;
|
||||
}
|
||||
if (!result)
|
||||
return false;
|
||||
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::WASM_SMOKE_TEST << ")["
|
||||
@@ -730,7 +582,7 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
|
||||
std::optional<std::string> result2 =
|
||||
hook::HookExecutor::validateWasm(
|
||||
hook.data(), (size_t)hook.size(), version);
|
||||
hook.data(), (size_t)hook.size());
|
||||
|
||||
if (result2)
|
||||
{
|
||||
@@ -742,7 +594,7 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
return false;
|
||||
}
|
||||
|
||||
return std::make_pair(maxInstrCountHook, maxInstrCountCbak);
|
||||
return *result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -931,11 +783,6 @@ SetHook::preflight(PreflightContext const& ctx)
|
||||
hookSetObj.isFieldPresent(sfHookCanEmit))
|
||||
return temDISABLED;
|
||||
|
||||
if (!ctx.rules.enabled(featureHookGas) &&
|
||||
(hookSetObj.isFieldPresent(sfHookCallbackGas) ||
|
||||
hookSetObj.isFieldPresent(sfHookWeakGas)))
|
||||
return temDISABLED;
|
||||
|
||||
for (auto const& hookSetElement : hookSetObj)
|
||||
{
|
||||
auto const& name = hookSetElement.getFName();
|
||||
@@ -945,8 +792,7 @@ SetHook::preflight(PreflightContext const& ctx)
|
||||
name != sfHookOn && name != sfHookOnOutgoing &&
|
||||
name != sfHookOnIncoming && name != sfHookGrants &&
|
||||
name != sfHookApiVersion && name != sfFlags &&
|
||||
name != sfHookCanEmit && name != sfHookCallbackGas &&
|
||||
name != sfHookWeakGas)
|
||||
name != sfHookCanEmit)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::HOOK_INVALID_FIELD << ")["
|
||||
@@ -1409,46 +1255,6 @@ struct KeyletComparator
|
||||
}
|
||||
};
|
||||
|
||||
TER
|
||||
validateGasHook(
|
||||
STObject const& hook,
|
||||
std::shared_ptr<STLedgerEntry> const& defSLE)
|
||||
{
|
||||
auto const version = defSLE->getFieldU16(sfHookApiVersion);
|
||||
if (version == 1)
|
||||
{
|
||||
// Gas Hook
|
||||
if (!defSLE->isFieldPresent(sfHookCallbackGas) &&
|
||||
hook.isFieldPresent(sfHookCallbackGas))
|
||||
return tecHOOK_INVALID;
|
||||
|
||||
auto const flags = hook.getFlags();
|
||||
auto const hasCollectFlag = flags & hsfCOLLECT;
|
||||
|
||||
if (hasCollectFlag)
|
||||
{
|
||||
// ltHook or ltHookDefinition must have sfHookWeakGas
|
||||
if (!hook.isFieldPresent(sfHookWeakGas) &&
|
||||
!defSLE->isFieldPresent(sfHookWeakGas))
|
||||
return tecHOOK_INVALID;
|
||||
}
|
||||
else
|
||||
{
|
||||
// ltHook must not have sfHookWeakGas
|
||||
if (hook.isFieldPresent(sfHookWeakGas))
|
||||
return tecHOOK_INVALID;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Guard Hook
|
||||
if (hook.isFieldPresent(sfHookCallbackGas) ||
|
||||
hook.isFieldPresent(sfHookWeakGas))
|
||||
return tecHOOK_INVALID;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
SetHook::setHook()
|
||||
{
|
||||
@@ -1542,14 +1348,6 @@ SetHook::setHook()
|
||||
std::optional<uint256> newHookCanEmit;
|
||||
std::optional<uint256> defHookCanEmit;
|
||||
|
||||
std::optional<uint32_t> oldHookWeakGas;
|
||||
std::optional<uint32_t> newHookWeakGas;
|
||||
std::optional<uint32_t> defHookWeakGas;
|
||||
|
||||
std::optional<uint32_t> oldHookCallbackGas;
|
||||
std::optional<uint32_t> newHookCallbackGas;
|
||||
std::optional<uint32_t> defHookCallbackGas;
|
||||
|
||||
// when hsoCREATE is invoked it populates this variable in case the hook
|
||||
// definition already exists and the operation falls through into a
|
||||
// hsoINSTALL operation instead
|
||||
@@ -1623,23 +1421,6 @@ SetHook::setHook()
|
||||
oldHookCanEmit = oldHook->get().getFieldH256(sfHookCanEmit);
|
||||
else if (defHookCanEmit)
|
||||
oldHookCanEmit = *defHookCanEmit;
|
||||
|
||||
if (oldDefSLE && oldDefSLE->isFieldPresent(sfHookWeakGas))
|
||||
defHookWeakGas = oldDefSLE->getFieldU32(sfHookWeakGas);
|
||||
|
||||
if (oldHook && oldHook->get().isFieldPresent(sfHookWeakGas))
|
||||
oldHookWeakGas = oldHook->get().getFieldU32(sfHookWeakGas);
|
||||
else if (defHookWeakGas)
|
||||
oldHookWeakGas = *defHookWeakGas;
|
||||
|
||||
if (oldDefSLE && oldDefSLE->isFieldPresent(sfHookCallbackGas))
|
||||
defHookCallbackGas = oldDefSLE->getFieldU32(sfHookCallbackGas);
|
||||
|
||||
if (oldHook && oldHook->get().isFieldPresent(sfHookCallbackGas))
|
||||
oldHookCallbackGas =
|
||||
oldHook->get().getFieldU32(sfHookCallbackGas);
|
||||
else if (defHookCallbackGas)
|
||||
oldHookCallbackGas = *defHookCallbackGas;
|
||||
}
|
||||
|
||||
// in preparation for three way merge populate fields if they are
|
||||
@@ -1672,13 +1453,6 @@ SetHook::setHook()
|
||||
newNamespace = hookSetObj->get().getFieldH256(sfHookNamespace);
|
||||
newDirKeylet = keylet::hookStateDir(account_, *newNamespace);
|
||||
}
|
||||
|
||||
if (hookSetObj->get().isFieldPresent(sfHookCallbackGas))
|
||||
newHookCallbackGas =
|
||||
hookSetObj->get().getFieldU32(sfHookCallbackGas);
|
||||
|
||||
if (hookSetObj->get().isFieldPresent(sfHookWeakGas))
|
||||
newHookWeakGas = hookSetObj->get().getFieldU32(sfHookWeakGas);
|
||||
}
|
||||
|
||||
// users may destroy a namespace in any operation except NOOP and
|
||||
@@ -1864,40 +1638,6 @@ SetHook::setHook()
|
||||
newHook.setFieldH256(sfHookCanEmit, *newHookCanEmit);
|
||||
}
|
||||
|
||||
auto const defVersion =
|
||||
oldDefSLE->getFieldU16(sfHookApiVersion);
|
||||
|
||||
if (defVersion == 0 && (newHookCallbackGas || newHookWeakGas))
|
||||
return tecHOOK_INVALID;
|
||||
|
||||
if (!defHookCallbackGas.has_value() && newHookCallbackGas)
|
||||
return tecHOOK_INVALID;
|
||||
|
||||
if (newHookWeakGas)
|
||||
{
|
||||
if (defHookWeakGas.has_value() &&
|
||||
*defHookWeakGas == *newHookWeakGas)
|
||||
{
|
||||
if (newHook.isFieldPresent(sfHookWeakGas))
|
||||
newHook.makeFieldAbsent(sfHookWeakGas);
|
||||
}
|
||||
else
|
||||
newHook.setFieldU32(sfHookWeakGas, *newHookWeakGas);
|
||||
}
|
||||
|
||||
if (newHookCallbackGas)
|
||||
{
|
||||
if (defHookCallbackGas.has_value() &&
|
||||
*defHookCallbackGas == *newHookCallbackGas)
|
||||
{
|
||||
if (newHook.isFieldPresent(sfHookCallbackGas))
|
||||
newHook.makeFieldAbsent(sfHookCallbackGas);
|
||||
}
|
||||
else
|
||||
newHook.setFieldU32(
|
||||
sfHookCallbackGas, *newHookCallbackGas);
|
||||
}
|
||||
|
||||
// parameters
|
||||
if (hookSetObj->get().isFieldPresent(sfHookParameters) &&
|
||||
hookSetObj->get().getFieldArray(sfHookParameters).empty())
|
||||
@@ -1942,10 +1682,6 @@ SetHook::setHook()
|
||||
if (flags)
|
||||
newHook.setFieldU32(sfFlags, *flags);
|
||||
|
||||
TER result = validateGasHook(newHook, oldDefSLE);
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
|
||||
newHooks.push_back(std::move(newHook));
|
||||
continue;
|
||||
}
|
||||
@@ -2080,24 +1816,10 @@ SetHook::setHook()
|
||||
newHookDef->setFieldH256(
|
||||
sfHookSetTxnID, ctx.tx.getTransactionID());
|
||||
newHookDef->setFieldU64(sfReferenceCount, 1);
|
||||
|
||||
// Set HookCallbackGas if present in hookSetObj
|
||||
if (hookSetObj->get().isFieldPresent(sfHookCallbackGas))
|
||||
newHookDef->setFieldU32(
|
||||
sfHookCallbackGas,
|
||||
hookSetObj->get().getFieldU32(sfHookCallbackGas));
|
||||
|
||||
// Set HookWeakGas if present in hookSetObj
|
||||
if (hookSetObj->get().isFieldPresent(sfHookWeakGas))
|
||||
newHookDef->setFieldU32(
|
||||
sfHookWeakGas,
|
||||
hookSetObj->get().getFieldU32(sfHookWeakGas));
|
||||
|
||||
if (hookSetObj->get().getFieldU16(sfHookApiVersion) != 1)
|
||||
newHookDef->setFieldAmount(
|
||||
sfFee,
|
||||
XRPAmount{
|
||||
hook::computeExecutionFee(maxInstrCountHook)});
|
||||
newHookDef->setFieldAmount(
|
||||
sfFee,
|
||||
XRPAmount{
|
||||
hook::computeExecutionFee(maxInstrCountHook)});
|
||||
if (maxInstrCountCbak > 0)
|
||||
newHookDef->setFieldAmount(
|
||||
sfHookCallbackFee,
|
||||
@@ -2119,38 +1841,6 @@ SetHook::setHook()
|
||||
|
||||
slesToInsert.emplace(keylet, newHookDef);
|
||||
newHook.setFieldH256(sfHookHash, *createHookHash);
|
||||
|
||||
// Set HookCallbackGas in Hook object only if different from
|
||||
// Definition
|
||||
if (hookSetObj->get().isFieldPresent(sfHookCallbackGas))
|
||||
{
|
||||
uint32_t objGas =
|
||||
hookSetObj->get().getFieldU32(sfHookCallbackGas);
|
||||
if (!newHookDef->isFieldPresent(sfHookCallbackGas) ||
|
||||
newHookDef->getFieldU32(sfHookCallbackGas) !=
|
||||
objGas)
|
||||
{
|
||||
newHook.setFieldU32(sfHookCallbackGas, objGas);
|
||||
}
|
||||
}
|
||||
|
||||
// Set HookWeakGas in Hook object only if different from
|
||||
// Definition
|
||||
if (hookSetObj->get().isFieldPresent(sfHookWeakGas))
|
||||
{
|
||||
uint32_t objGas =
|
||||
hookSetObj->get().getFieldU32(sfHookWeakGas);
|
||||
if (!newHookDef->isFieldPresent(sfHookWeakGas) ||
|
||||
newHookDef->getFieldU32(sfHookWeakGas) != objGas)
|
||||
{
|
||||
newHook.setFieldU32(sfHookWeakGas, objGas);
|
||||
}
|
||||
}
|
||||
|
||||
TER result = validateGasHook(newHook, newHookDef);
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
|
||||
newHooks.push_back(std::move(newHook));
|
||||
continue;
|
||||
}
|
||||
@@ -2260,28 +1950,6 @@ SetHook::setHook()
|
||||
*defHookCanEmit == *newHookCanEmit))
|
||||
newHook.setFieldH256(sfHookCanEmit, *newHookCanEmit);
|
||||
|
||||
auto const defVersion =
|
||||
newDefSLE->getFieldU16(sfHookApiVersion);
|
||||
|
||||
if (defVersion == 0 && (newHookCallbackGas || newHookWeakGas))
|
||||
return tecHOOK_INVALID;
|
||||
|
||||
if (!defHookCallbackGas.has_value() && newHookCallbackGas)
|
||||
return tecHOOK_INVALID;
|
||||
|
||||
if (newHookCallbackGas &&
|
||||
!(defHookCallbackGas.has_value() &&
|
||||
*defHookCallbackGas == *newHookCallbackGas))
|
||||
newHook.setFieldU32(sfHookCallbackGas, *newHookCallbackGas);
|
||||
|
||||
if ((!flags || !(*flags & hsfCOLLECT)) && newHookWeakGas)
|
||||
return tecHOOK_INVALID;
|
||||
|
||||
if (newHookWeakGas &&
|
||||
!(defHookWeakGas.has_value() &&
|
||||
*defHookWeakGas == *newHookWeakGas))
|
||||
newHook.setFieldU32(sfHookWeakGas, *newHookWeakGas);
|
||||
|
||||
// parameters
|
||||
TER result = updateHookParameters(
|
||||
ctx,
|
||||
@@ -2305,10 +1973,6 @@ SetHook::setHook()
|
||||
if (flags)
|
||||
newHook.setFieldU32(sfFlags, newFlags);
|
||||
|
||||
result = validateGasHook(newHook, newDefSLE);
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
|
||||
newHooks.push_back(std::move(newHook));
|
||||
|
||||
slesToUpdate.emplace(*newDefKeylet, newDefSLE);
|
||||
@@ -2326,8 +1990,6 @@ SetHook::setHook()
|
||||
}
|
||||
}
|
||||
|
||||
JLOG(ctx.j.warn()) << "HookSet: setHook after for loops";
|
||||
|
||||
int reserveDelta = 0;
|
||||
{
|
||||
// compute owner counts before modifying anything on ledger
|
||||
@@ -2337,8 +1999,7 @@ SetHook::setHook()
|
||||
// sfParameters: 1 reserve PER entry
|
||||
// sfGrants are: 1 reserve PER entry
|
||||
// sfHookHash, sfHookNamespace, sfHookOn, sfHookOnOutgoing,
|
||||
// sfHookOnIncoming, sfHookCanEmit, sfHookApiVersion, sfHookCallbackGas,
|
||||
// sfHookWeakGas, sfFlags: free
|
||||
// sfHookOnIncoming, sfHookCanEmit sfHookApiVersion, sfFlags: free
|
||||
|
||||
// sfHookDefinition is not reserved because it is an unowned object,
|
||||
// rather the uploader is billed via fee according to the following:
|
||||
@@ -2485,7 +2146,6 @@ SetHook::setHook()
|
||||
view().update(accountSLE);
|
||||
}
|
||||
|
||||
JLOG(ctx.j.warn()) << "HookSet: setHook end";
|
||||
return nsDeleteResult;
|
||||
} // namespace ripple
|
||||
|
||||
|
||||
@@ -101,11 +101,6 @@ preflight1(PreflightContext const& ctx)
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfHookGas) && !ctx.rules.enabled(featureHookGas))
|
||||
{
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
auto const ret = preflight0(ctx);
|
||||
if (!isTesSuccess(ret))
|
||||
return ret;
|
||||
@@ -239,12 +234,6 @@ Transactor::Transactor(ApplyContext& ctx)
|
||||
{
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
calculateHookGas(uint32_t gas)
|
||||
{
|
||||
return XRPAmount{gas};
|
||||
}
|
||||
|
||||
// RH NOTE: this only computes one chain at a time, so if there is a receiving
|
||||
// side to a txn then it must seperately be computed by a second call here
|
||||
XRPAmount
|
||||
@@ -260,7 +249,6 @@ Transactor::calculateHookChainFee(
|
||||
return XRPAmount{0};
|
||||
|
||||
XRPAmount fee{0};
|
||||
uint32_t gasTypeHookCount = 0; // Gas type hook counter
|
||||
|
||||
auto const& hooks = hookSLE->getFieldArray(sfHooks);
|
||||
|
||||
@@ -295,56 +283,18 @@ Transactor::calculateHookChainFee(
|
||||
if (hook::canHook(tx.getTxnType(), hookOn) &&
|
||||
(!collectCallsOnly || (flags & hook::hsfCOLLECT)))
|
||||
{
|
||||
// get HookApiVersion
|
||||
uint16_t apiVersion = hookDef->getFieldU16(sfHookApiVersion);
|
||||
XRPAmount const toAdd{hookDef->getFieldAmount(sfFee).xrp().drops()};
|
||||
|
||||
if (apiVersion == 0) // Guard type
|
||||
{
|
||||
// existing logic: read HookDefinition's sfFee
|
||||
XRPAmount const toAdd{
|
||||
hookDef->getFieldAmount(sfFee).xrp().drops()};
|
||||
|
||||
// this overflow should never happen, if somehow it does
|
||||
// fee is set to the largest possible valid xrp value to force
|
||||
// fail the transaction
|
||||
if (fee + toAdd < fee)
|
||||
fee = XRPAmount{INITIAL_XRP.drops()};
|
||||
else
|
||||
fee += toAdd;
|
||||
}
|
||||
else if (apiVersion == 1) // Gas type
|
||||
{
|
||||
if (!collectCallsOnly)
|
||||
{
|
||||
// Gas type: only count
|
||||
gasTypeHookCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const weakFee = hookObj.isFieldPresent(sfHookWeakGas)
|
||||
? hookObj.getFieldU32(sfHookWeakGas)
|
||||
: hookDef->getFieldU32(sfHookWeakGas);
|
||||
XRPAmount const toAdd = calculateHookGas(weakFee);
|
||||
if (fee + toAdd < fee)
|
||||
fee = XRPAmount{INITIAL_XRP.drops()};
|
||||
else
|
||||
fee += toAdd;
|
||||
}
|
||||
}
|
||||
// this overflow should never happen, if somehow it does
|
||||
// fee is set to the largest possible valid xrp value to force
|
||||
// fail the transaction
|
||||
if (fee + toAdd < fee)
|
||||
fee = XRPAmount{INITIAL_XRP.drops()};
|
||||
else
|
||||
fee += toAdd;
|
||||
}
|
||||
}
|
||||
|
||||
// Additional cost for Gas type: baseFee * 100 /Hook = 10*100 drops/Hook
|
||||
if (gasTypeHookCount > 0)
|
||||
{
|
||||
auto const baseGasFee = view.fees().base * 100;
|
||||
XRPAmount const gasTypeFee{gasTypeHookCount * baseGasFee};
|
||||
if (fee + gasTypeFee < fee)
|
||||
fee = XRPAmount{INITIAL_XRP.drops()}; // overflow
|
||||
else
|
||||
fee += gasTypeFee;
|
||||
}
|
||||
|
||||
return fee;
|
||||
}
|
||||
|
||||
@@ -407,51 +357,6 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
hookExecutionFee += toAdd;
|
||||
}
|
||||
|
||||
if (const auto hookSLE =
|
||||
view.read(keylet::hook(tx.getAccountID(sfAccount))))
|
||||
{
|
||||
const auto& hooks = hookSLE->getFieldArray(sfHooks);
|
||||
for (auto const& hookObj : hooks)
|
||||
{
|
||||
if (hookObj.isFieldPresent(sfHookHash))
|
||||
{
|
||||
if (hookObj.getFieldH256(sfHookHash) !=
|
||||
callbackHookHash)
|
||||
continue;
|
||||
|
||||
uint32_t callbackGas = 0;
|
||||
|
||||
// Priority 1: Check HookObject
|
||||
if (hookObj.isFieldPresent(sfHookCallbackGas))
|
||||
{
|
||||
callbackGas =
|
||||
hookObj.getFieldU32(sfHookCallbackGas);
|
||||
}
|
||||
// Priority 2: Check HookDefinition
|
||||
else if (
|
||||
hookDef &&
|
||||
hookDef->isFieldPresent(sfHookCallbackGas))
|
||||
{
|
||||
callbackGas =
|
||||
hookDef->getFieldU32(sfHookCallbackGas);
|
||||
}
|
||||
// Priority 3: Default to 0 (implicit)
|
||||
|
||||
if (callbackGas > 0)
|
||||
{
|
||||
XRPAmount const toAdd =
|
||||
calculateHookGas(callbackGas);
|
||||
if (hookExecutionFee + toAdd < hookExecutionFee)
|
||||
hookExecutionFee =
|
||||
XRPAmount{INITIAL_XRP.drops()};
|
||||
else
|
||||
hookExecutionFee += toAdd;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
XRPL_ASSERT(
|
||||
emitDetails.isFieldPresent(sfEmitBurden),
|
||||
"Transactor::calculateBaseFee : emit burden not present");
|
||||
@@ -471,10 +376,6 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
if (canRollback)
|
||||
hookExecutionFee += calculateHookChainFee(
|
||||
view, tx, keylet::hook(tshAcc), false);
|
||||
|
||||
if (view.rules().enabled(featureHookGas) &&
|
||||
tx.isFieldPresent(sfHookGas))
|
||||
hookExecutionFee += calculateHookGas(tx.getFieldU32(sfHookGas));
|
||||
}
|
||||
|
||||
XRPAmount accumulator = baseFee;
|
||||
@@ -1395,15 +1296,6 @@ Transactor::executeHookChain(
|
||||
std::map<uint256, std::map<std::vector<uint8_t>, std::vector<uint8_t>>>
|
||||
hookParamOverrides{};
|
||||
|
||||
// Initialize Gas pool for Gas-type hooks
|
||||
uint32_t gasPool = 0;
|
||||
if (ctx_.tx.isFieldPresent(sfHookGas))
|
||||
{
|
||||
gasPool = ctx_.tx.getFieldU32(sfHookGas);
|
||||
JLOG(j_.trace()) << "HookChain: Initialized Gas pool with " << gasPool
|
||||
<< " instructions";
|
||||
}
|
||||
|
||||
auto const& hooks = hookSLE->getFieldArray(sfHooks);
|
||||
uint8_t hook_no = 0;
|
||||
|
||||
@@ -1468,30 +1360,7 @@ Transactor::executeHookChain(
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
bool hasCallback = hookDef->isFieldPresent(sfHookCallbackFee) ||
|
||||
hookDef->isFieldPresent(sfHookCallbackGas);
|
||||
|
||||
// Extract HookApiVersion for Gas-type hooks
|
||||
uint16_t hookApiVersion = hookDef->isFieldPresent(sfHookApiVersion)
|
||||
? hookDef->getFieldU16(sfHookApiVersion)
|
||||
: 0;
|
||||
|
||||
// Prepare Gas limit for this hook execution
|
||||
uint32_t hookGas = 0;
|
||||
if (hookApiVersion == 1)
|
||||
{
|
||||
if (!strong) // WeakTSH execution
|
||||
{
|
||||
hookGas = hookObj.isFieldPresent(sfHookWeakGas)
|
||||
? hookObj.getFieldU32(sfHookWeakGas)
|
||||
: hookDef->getFieldU32(sfHookWeakGas);
|
||||
}
|
||||
else // Strong execution
|
||||
{
|
||||
// Pass remaining Gas pool to this hook
|
||||
hookGas = gasPool;
|
||||
}
|
||||
}
|
||||
bool hasCallback = hookDef->isFieldPresent(sfHookCallbackFee);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -1511,35 +1380,12 @@ Transactor::executeHookChain(
|
||||
strong,
|
||||
(strong ? 0 : 1UL), // 0 = strong, 1 = weak
|
||||
hook_no - 1,
|
||||
provisionalMeta,
|
||||
hookApiVersion,
|
||||
hookGas));
|
||||
provisionalMeta));
|
||||
|
||||
executedHookCount_++;
|
||||
|
||||
hook::HookResult& hookResult = results.back();
|
||||
|
||||
// Track Gas consumption for Gas-type hooks
|
||||
if (hookApiVersion == 1)
|
||||
{
|
||||
uint64_t consumed = hookResult.instructionCost;
|
||||
|
||||
JLOG(j_.trace()) << "HookChain: Hook consumed " << consumed
|
||||
<< " instructions. Pool before: " << gasPool;
|
||||
|
||||
if (consumed >= gasPool)
|
||||
{
|
||||
JLOG(j_.trace()) << "HookError: Gas pool exhausted. "
|
||||
<< "Hook tried to consume " << consumed
|
||||
<< " but only " << gasPool << " remained.";
|
||||
return tecHOOK_INSUFFICIENT_GAS;
|
||||
}
|
||||
|
||||
gasPool -= consumed;
|
||||
|
||||
JLOG(j_.trace()) << "HookChain: Pool after: " << gasPool;
|
||||
}
|
||||
|
||||
if (hookResult.exitType != hook_api::ExitType::ACCEPT)
|
||||
{
|
||||
if (results.back().exitType == hook_api::ExitType::WASM_ERROR)
|
||||
@@ -1619,8 +1465,7 @@ Transactor::doHookCallback(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hookDef->isFieldPresent(sfHookCallbackFee) &&
|
||||
!hookDef->isFieldPresent(sfHookCallbackGas))
|
||||
if (!hookDef->isFieldPresent(sfHookCallbackFee))
|
||||
{
|
||||
JLOG(j_.trace()) << "HookInfo[" << callbackAccountID
|
||||
<< "]: Callback specified by emitted txn "
|
||||
@@ -1677,24 +1522,6 @@ Transactor::doHookCallback(
|
||||
{
|
||||
hook::HookStateMap stateMap;
|
||||
|
||||
// Extract HookApiVersion for callback
|
||||
uint16_t hookApiVersion = hookDef->getFieldU16(sfHookApiVersion);
|
||||
|
||||
// Get callback gas with fallback priority:
|
||||
// 1. HookObject's HookCallbackGas
|
||||
// 2. HookDefinition's HookCallbackGas
|
||||
// 3. Transaction's HookGas (for backward compatibility)
|
||||
uint32_t hookGas = 0;
|
||||
|
||||
if (hookObj.isFieldPresent(sfHookCallbackGas))
|
||||
{
|
||||
hookGas = hookObj.getFieldU32(sfHookCallbackGas);
|
||||
}
|
||||
else if (hookDef->isFieldPresent(sfHookCallbackGas))
|
||||
{
|
||||
hookGas = hookDef->getFieldU32(sfHookCallbackGas);
|
||||
}
|
||||
|
||||
hook::HookResult callbackResult = hook::apply(
|
||||
hookDef->getFieldH256(sfHookSetTxnID),
|
||||
callbackHookHash,
|
||||
@@ -1714,9 +1541,7 @@ Transactor::doHookCallback(
|
||||
? 1UL
|
||||
: 0UL,
|
||||
hook_no - 1,
|
||||
provisionalMeta,
|
||||
hookApiVersion,
|
||||
hookGas);
|
||||
provisionalMeta);
|
||||
|
||||
executedHookCount_++;
|
||||
|
||||
@@ -2005,19 +1830,6 @@ Transactor::doAgainAsWeak(
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract HookApiVersion for aaw execution
|
||||
uint16_t hookApiVersion = hookDef->getFieldU16(sfHookApiVersion);
|
||||
|
||||
// Extract HookGas for Gas-type hooks
|
||||
uint32_t hookGasWeak = 0;
|
||||
if (hookApiVersion == 1)
|
||||
{
|
||||
if (hookObj.isFieldPresent(sfHookWeakGas))
|
||||
hookGasWeak = hookObj.getFieldU32(sfHookWeakGas);
|
||||
else if (hookDef->isFieldPresent(sfHookWeakGas))
|
||||
hookGasWeak = hookDef->getFieldU32(sfHookWeakGas);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
hook::HookResult aawResult = hook::apply(
|
||||
@@ -2031,15 +1843,12 @@ Transactor::doAgainAsWeak(
|
||||
stateMap,
|
||||
ctx_,
|
||||
hookAccountID,
|
||||
hookDef->isFieldPresent(sfHookCallbackFee) ||
|
||||
hookDef->isFieldPresent(sfHookCallbackGas),
|
||||
hookDef->isFieldPresent(sfHookCallbackFee),
|
||||
false,
|
||||
false,
|
||||
2UL, // param 2 = aaw
|
||||
hook_no - 1,
|
||||
provisionalMeta,
|
||||
hookApiVersion,
|
||||
hookGasWeak);
|
||||
provisionalMeta);
|
||||
|
||||
executedHookCount_++;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user