mirror of
https://github.com/Xahau/xahaud.git
synced 2026-01-21 15:15:15 +00:00
Compare commits
6 Commits
hook-api-u
...
hook-helpe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63096d5fbc | ||
|
|
2e128acdcf | ||
|
|
043c60b62e | ||
|
|
5dd1198e4f | ||
|
|
5d9071695a | ||
|
|
ec6dc93834 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -121,3 +121,5 @@ CMakeUserPresets.json
|
||||
bld.rippled/
|
||||
|
||||
generated
|
||||
guard_checker
|
||||
guard_checker.dSYM
|
||||
|
||||
@@ -488,7 +488,6 @@ target_sources (rippled PRIVATE
|
||||
src/ripple/app/tx/impl/apply.cpp
|
||||
src/ripple/app/tx/impl/applySteps.cpp
|
||||
src/ripple/app/hook/impl/applyHook.cpp
|
||||
src/ripple/app/hook/impl/HookAPI.cpp
|
||||
src/ripple/app/tx/impl/details/NFTokenUtils.cpp
|
||||
#[===============================[
|
||||
main sources:
|
||||
@@ -750,7 +749,6 @@ if (tests)
|
||||
src/test/app/Freeze_test.cpp
|
||||
src/test/app/GenesisMint_test.cpp
|
||||
src/test/app/HashRouter_test.cpp
|
||||
src/test/app/HookAPI_test.cpp
|
||||
src/test/app/Import_test.cpp
|
||||
src/test/app/Invoke_test.cpp
|
||||
src/test/app/LedgerHistory_test.cpp
|
||||
|
||||
@@ -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
|
||||
@@ -837,6 +849,42 @@ validateGuards(
|
||||
*/
|
||||
uint64_t rulesVersion = 0)
|
||||
{
|
||||
// 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
|
||||
@@ -1176,6 +1224,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1217,9 +1271,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
|
||||
@@ -1253,6 +1304,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())
|
||||
{
|
||||
@@ -1288,7 +1340,7 @@ validateGuards(
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (j == hook_type_idx)
|
||||
else if (j == hook_type_idx) // hook() or cbak() function type
|
||||
{
|
||||
// pass
|
||||
}
|
||||
@@ -1301,7 +1353,8 @@ validateGuards(
|
||||
<< "Codesec: " << section_type << " "
|
||||
<< "Local: " << j << " "
|
||||
<< "Offset: " << i << "\n";
|
||||
return {};
|
||||
// return {};
|
||||
helper_function = true;
|
||||
}
|
||||
|
||||
int param_count = parseLeb128(wasm, i, &i);
|
||||
@@ -1318,12 +1371,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 {};
|
||||
}
|
||||
|
||||
@@ -1370,6 +1430,10 @@ validateGuards(
|
||||
return {};
|
||||
}
|
||||
}
|
||||
else if (helper_function)
|
||||
{
|
||||
// pass
|
||||
}
|
||||
else if ((*first_signature).get()[k + 1] != param_type)
|
||||
{
|
||||
GUARDLOG(hook::log::FUNC_PARAM_INVALID)
|
||||
@@ -1446,6 +1510,10 @@ validateGuards(
|
||||
return {};
|
||||
}
|
||||
}
|
||||
else if (helper_function)
|
||||
{
|
||||
// pass
|
||||
}
|
||||
else if ((*first_signature).get()[0] != result_type)
|
||||
{
|
||||
GUARDLOG(hook::log::FUNC_RETURN_INVALID)
|
||||
@@ -1497,6 +1565,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,
|
||||
@@ -1506,33 +1585,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};
|
||||
}
|
||||
|
||||
@@ -1,341 +0,0 @@
|
||||
#include <ripple/app/hook/Enum.h>
|
||||
#include <ripple/app/misc/Transaction.h>
|
||||
|
||||
namespace hook {
|
||||
using namespace ripple;
|
||||
using HookReturnCode = hook_api::hook_return_code;
|
||||
|
||||
using Bytes = std::vector<std::uint8_t>;
|
||||
|
||||
struct HookContext; // defined in applyHook.h
|
||||
|
||||
class HookAPI
|
||||
{
|
||||
public:
|
||||
explicit HookAPI(HookContext& ctx) : hookCtx(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
/// control APIs
|
||||
// _g
|
||||
// accept
|
||||
// rollback
|
||||
|
||||
/// util APIs
|
||||
Expected<std::string, HookReturnCode>
|
||||
util_raddr(Bytes const& accountID) const;
|
||||
|
||||
Expected<Bytes, HookReturnCode>
|
||||
util_accid(std::string raddress) const;
|
||||
|
||||
Expected<bool, HookReturnCode>
|
||||
util_verify(Slice const& data, Slice const& sig, Slice const& key) const;
|
||||
|
||||
uint256
|
||||
util_sha512h(Slice const& data) const;
|
||||
|
||||
// util_keylet()
|
||||
|
||||
/// sto APIs
|
||||
Expected<bool, HookReturnCode>
|
||||
sto_validate(Bytes const& data) const;
|
||||
|
||||
Expected<std::pair<uint32_t, uint32_t>, HookReturnCode>
|
||||
sto_subfield(Bytes const& data, uint32_t field_id) const;
|
||||
|
||||
Expected<std::pair<uint32_t, uint32_t>, HookReturnCode>
|
||||
sto_subarray(Bytes const& data, uint32_t index_id) const;
|
||||
|
||||
Expected<Bytes, HookReturnCode>
|
||||
sto_emplace(
|
||||
Bytes const& source_object,
|
||||
std::optional<Bytes> const& field_object,
|
||||
uint32_t field_id) const;
|
||||
|
||||
// sto_erase(): same as sto_emplace with field_object = nullopt
|
||||
|
||||
/// etxn APIs
|
||||
Expected<std::shared_ptr<Transaction>, HookReturnCode>
|
||||
emit(Slice const& txBlob) const;
|
||||
|
||||
Expected<uint64_t, HookReturnCode>
|
||||
etxn_burden() const;
|
||||
|
||||
Expected<uint64_t, HookReturnCode>
|
||||
etxn_fee_base(Slice const& txBlob) const;
|
||||
|
||||
Expected<uint64_t, HookReturnCode>
|
||||
etxn_details(uint8_t* out_ptr) const;
|
||||
|
||||
Expected<uint64_t, HookReturnCode>
|
||||
etxn_reserve(uint64_t count) const;
|
||||
|
||||
uint32_t
|
||||
etxn_generation() const;
|
||||
|
||||
Expected<uint256, HookReturnCode>
|
||||
etxn_nonce() const;
|
||||
|
||||
/// float APIs
|
||||
Expected<uint64_t, HookReturnCode>
|
||||
float_set(int32_t exponent, int64_t mantissa) const;
|
||||
|
||||
Expected<uint64_t, HookReturnCode>
|
||||
float_multiply(uint64_t float1, uint64_t float2) const;
|
||||
|
||||
Expected<uint64_t, HookReturnCode>
|
||||
float_mulratio(
|
||||
uint64_t float1,
|
||||
uint32_t round_up,
|
||||
uint32_t numerator,
|
||||
uint32_t denominator) const;
|
||||
|
||||
uint64_t
|
||||
float_negate(uint64_t float1) const;
|
||||
|
||||
Expected<uint64_t, HookReturnCode>
|
||||
float_compare(uint64_t float1, uint64_t float2, uint32_t mode) const;
|
||||
|
||||
Expected<uint64_t, HookReturnCode>
|
||||
float_sum(uint64_t float1, uint64_t float2) const;
|
||||
|
||||
Expected<Bytes, HookReturnCode>
|
||||
float_sto(
|
||||
std::optional<Currency> currency,
|
||||
std::optional<AccountID> issuer,
|
||||
uint64_t float1,
|
||||
uint32_t field_code,
|
||||
uint32_t write_len) const;
|
||||
|
||||
Expected<uint64_t, HookReturnCode>
|
||||
float_sto_set(Bytes const& data) const;
|
||||
|
||||
Expected<uint64_t, HookReturnCode>
|
||||
float_invert(uint64_t float1) const;
|
||||
|
||||
Expected<uint64_t, HookReturnCode>
|
||||
float_divide(uint64_t float1, uint64_t float2) const;
|
||||
|
||||
uint64_t
|
||||
float_one() const;
|
||||
|
||||
Expected<uint64_t, HookReturnCode>
|
||||
float_mantissa(uint64_t float1) const;
|
||||
|
||||
uint64_t
|
||||
float_sign(uint64_t float1) const;
|
||||
|
||||
Expected<uint64_t, HookReturnCode>
|
||||
float_int(uint64_t float1, uint32_t decimal_places, uint32_t absolute)
|
||||
const;
|
||||
|
||||
Expected<uint64_t, HookReturnCode>
|
||||
float_log(uint64_t float1) const;
|
||||
|
||||
Expected<uint64_t, HookReturnCode>
|
||||
float_root(uint64_t float1, uint32_t n) const;
|
||||
|
||||
/// otxn APIs
|
||||
uint64_t
|
||||
otxn_burden() const;
|
||||
|
||||
uint32_t
|
||||
otxn_generation() const;
|
||||
|
||||
Expected<const STBase*, HookReturnCode>
|
||||
otxn_field(uint32_t field_id) const;
|
||||
|
||||
Expected<uint256, HookReturnCode>
|
||||
otxn_id(uint32_t flags) const;
|
||||
|
||||
TxType
|
||||
otxn_type() const;
|
||||
|
||||
Expected<uint32_t, HookReturnCode>
|
||||
otxn_slot(uint32_t slot_into) const;
|
||||
|
||||
Expected<Blob, HookReturnCode>
|
||||
otxn_param(Bytes const& param_name) const;
|
||||
|
||||
/// hook APIs
|
||||
AccountID
|
||||
hook_account() const;
|
||||
|
||||
Expected<ripple::uint256, HookReturnCode>
|
||||
hook_hash(int32_t hook_no) const;
|
||||
|
||||
Expected<int64_t, HookReturnCode>
|
||||
hook_again() const;
|
||||
|
||||
Expected<Blob, HookReturnCode>
|
||||
hook_param(Bytes const& paramName) const;
|
||||
|
||||
Expected<uint64_t, HookReturnCode>
|
||||
hook_param_set(
|
||||
uint256 const& hash,
|
||||
Bytes const& paramName,
|
||||
Bytes const& paramValue) const;
|
||||
|
||||
Expected<uint64_t, HookReturnCode>
|
||||
hook_skip(uint256 const& hash, uint32_t flags) const;
|
||||
|
||||
uint8_t
|
||||
hook_pos() const;
|
||||
|
||||
/// ledger APIs
|
||||
uint64_t
|
||||
fee_base() const;
|
||||
|
||||
uint32_t
|
||||
ledger_seq() const;
|
||||
|
||||
uint256
|
||||
ledger_last_hash() const;
|
||||
|
||||
uint64_t
|
||||
ledger_last_time() const;
|
||||
|
||||
Expected<uint256, HookReturnCode>
|
||||
ledger_nonce() const;
|
||||
|
||||
Expected<Keylet, HookReturnCode>
|
||||
ledger_keylet(Keylet const& klLo, Keylet const& klHi) const;
|
||||
|
||||
/// state APIs
|
||||
|
||||
// state(): same as state_foreign with ns = 0 and account = hook_account()
|
||||
|
||||
Expected<Bytes, HookReturnCode>
|
||||
state_foreign(
|
||||
uint256 const& key,
|
||||
uint256 const& ns,
|
||||
AccountID const& account) const;
|
||||
|
||||
// state_set(): same as state_foreign_set with ns = 0 and account =
|
||||
|
||||
Expected<uint64_t, HookReturnCode>
|
||||
state_foreign_set(
|
||||
uint256 const& key,
|
||||
uint256 const& ns,
|
||||
AccountID const& account,
|
||||
Bytes& data) const;
|
||||
|
||||
/// slot APIs
|
||||
Expected<const STBase*, HookReturnCode>
|
||||
slot(uint32_t slot_no) const;
|
||||
|
||||
Expected<uint64_t, HookReturnCode>
|
||||
slot_clear(uint32_t slot_no) const;
|
||||
|
||||
Expected<uint64_t, HookReturnCode>
|
||||
slot_count(uint32_t slot_no) const;
|
||||
|
||||
Expected<uint32_t, HookReturnCode>
|
||||
slot_set(Bytes const& data, uint32_t slot_no) const;
|
||||
|
||||
Expected<uint64_t, HookReturnCode>
|
||||
slot_size(uint32_t slot_no) const;
|
||||
|
||||
Expected<uint32_t, HookReturnCode>
|
||||
slot_subarray(uint32_t parent_slot, uint32_t array_id, uint32_t new_slot)
|
||||
const;
|
||||
|
||||
Expected<uint32_t, HookReturnCode>
|
||||
slot_subfield(uint32_t parent_slot, uint32_t field_id, uint32_t new_slot)
|
||||
const;
|
||||
|
||||
Expected<std::variant<STBase, STAmount>, HookReturnCode>
|
||||
slot_type(uint32_t slot_no, uint32_t flags) const;
|
||||
|
||||
Expected<uint64_t, HookReturnCode>
|
||||
slot_float(uint32_t slot_no) const;
|
||||
|
||||
/// trace APIs
|
||||
// trace
|
||||
// trace_num
|
||||
// trace_float
|
||||
|
||||
Expected<uint32_t, HookReturnCode>
|
||||
meta_slot(uint32_t slot_into) const;
|
||||
|
||||
Expected<std::pair<uint32_t, uint32_t>, HookReturnCode>
|
||||
xpop_slot(uint32_t slot_into_tx, uint32_t slot_into_meta) const;
|
||||
|
||||
private:
|
||||
HookContext& hookCtx;
|
||||
|
||||
inline int32_t
|
||||
no_free_slots() const;
|
||||
|
||||
inline std::optional<int32_t>
|
||||
get_free_slot() const;
|
||||
|
||||
inline Expected<uint64_t, HookReturnCode>
|
||||
float_multiply_internal_parts(
|
||||
uint64_t man1,
|
||||
int32_t exp1,
|
||||
bool neg1,
|
||||
uint64_t man2,
|
||||
int32_t exp2,
|
||||
bool neg2) const;
|
||||
|
||||
inline Expected<uint64_t, HookReturnCode>
|
||||
mulratio_internal(
|
||||
int64_t& man1,
|
||||
int32_t& exp1,
|
||||
bool round_up,
|
||||
uint32_t numerator,
|
||||
uint32_t denominator) const;
|
||||
|
||||
inline Expected<uint64_t, HookReturnCode>
|
||||
float_divide_internal(uint64_t float1, uint64_t float2) const;
|
||||
|
||||
inline Expected<uint64_t, HookReturnCode>
|
||||
double_to_xfl(double x) const;
|
||||
|
||||
std::optional<ripple::Keylet>
|
||||
unserialize_keylet(Bytes const& data) const;
|
||||
|
||||
// update the state cache
|
||||
inline std::optional<
|
||||
std::reference_wrapper<std::pair<bool, ripple::Blob> const>>
|
||||
lookup_state_cache(
|
||||
AccountID const& acc,
|
||||
uint256 const& ns,
|
||||
uint256 const& key) const;
|
||||
|
||||
// check the state cache
|
||||
inline Expected<uint64_t, HookReturnCode>
|
||||
set_state_cache(
|
||||
AccountID const& acc,
|
||||
uint256 const& ns,
|
||||
uint256 const& key,
|
||||
Bytes const& data,
|
||||
bool modified) const;
|
||||
|
||||
// these are only used by get_stobject_length below
|
||||
enum parse_error {
|
||||
pe_unexpected_end = -1,
|
||||
pe_unknown_type_early = -2, // detected early
|
||||
pe_unknown_type_late = -3, // end of function
|
||||
pe_excessive_nesting = -4,
|
||||
pe_excessive_size = -5
|
||||
};
|
||||
|
||||
inline Expected<
|
||||
int32_t,
|
||||
parse_error>
|
||||
get_stobject_length(
|
||||
unsigned char* start, // in - begin iterator
|
||||
unsigned char* maxptr, // in - end iterator
|
||||
int& type, // out - populated by serialized type code
|
||||
int& field, // out - populated by serialized field code
|
||||
int& payload_start, // out - the start of actual payload data for
|
||||
// this type
|
||||
int& payload_length, // out - the length of actual payload data for
|
||||
// this type
|
||||
int recursion_depth = 0) // used internally
|
||||
const;
|
||||
};
|
||||
|
||||
} // namespace hook
|
||||
@@ -477,6 +477,7 @@ struct HookResult
|
||||
ripple::uint256 const hookHash;
|
||||
ripple::uint256 const hookCanEmit;
|
||||
ripple::Keylet const accountKeylet;
|
||||
ripple::Keylet const ownerDirKeylet;
|
||||
ripple::Keylet const hookKeylet;
|
||||
ripple::AccountID const account;
|
||||
ripple::AccountID const otxnAccount;
|
||||
@@ -798,13 +799,12 @@ public:
|
||||
ADD_HOOK_FUNCTION(util_accid, ctx);
|
||||
ADD_HOOK_FUNCTION(util_verify, ctx);
|
||||
ADD_HOOK_FUNCTION(util_sha512h, ctx);
|
||||
ADD_HOOK_FUNCTION(util_keylet, ctx);
|
||||
|
||||
ADD_HOOK_FUNCTION(sto_validate, ctx);
|
||||
ADD_HOOK_FUNCTION(sto_subfield, ctx);
|
||||
ADD_HOOK_FUNCTION(sto_subarray, ctx);
|
||||
ADD_HOOK_FUNCTION(sto_emplace, ctx);
|
||||
ADD_HOOK_FUNCTION(sto_erase, ctx);
|
||||
ADD_HOOK_FUNCTION(util_keylet, ctx);
|
||||
|
||||
ADD_HOOK_FUNCTION(emit, ctx);
|
||||
ADD_HOOK_FUNCTION(etxn_burden, ctx);
|
||||
@@ -843,11 +843,6 @@ public:
|
||||
ADD_HOOK_FUNCTION(hook_account, ctx);
|
||||
ADD_HOOK_FUNCTION(hook_hash, ctx);
|
||||
ADD_HOOK_FUNCTION(hook_again, ctx);
|
||||
ADD_HOOK_FUNCTION(hook_param, ctx);
|
||||
ADD_HOOK_FUNCTION(hook_param_set, ctx);
|
||||
ADD_HOOK_FUNCTION(hook_skip, ctx);
|
||||
ADD_HOOK_FUNCTION(hook_pos, ctx);
|
||||
|
||||
ADD_HOOK_FUNCTION(fee_base, ctx);
|
||||
ADD_HOOK_FUNCTION(ledger_seq, ctx);
|
||||
ADD_HOOK_FUNCTION(ledger_last_hash, ctx);
|
||||
@@ -855,6 +850,11 @@ public:
|
||||
ADD_HOOK_FUNCTION(ledger_nonce, ctx);
|
||||
ADD_HOOK_FUNCTION(ledger_keylet, ctx);
|
||||
|
||||
ADD_HOOK_FUNCTION(hook_param, ctx);
|
||||
ADD_HOOK_FUNCTION(hook_param_set, ctx);
|
||||
ADD_HOOK_FUNCTION(hook_skip, ctx);
|
||||
ADD_HOOK_FUNCTION(hook_pos, ctx);
|
||||
|
||||
ADD_HOOK_FUNCTION(state, ctx);
|
||||
ADD_HOOK_FUNCTION(state_foreign, ctx);
|
||||
ADD_HOOK_FUNCTION(state_set, ctx);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -137,14 +137,14 @@ class [[nodiscard]] Expected
|
||||
public:
|
||||
template <typename U>
|
||||
requires std::convertible_to<U, T> constexpr Expected(U && r)
|
||||
: Base(boost::outcome_v2::success(T(std::forward<U>(r))))
|
||||
: Base(T(std::forward<U>(r)))
|
||||
{
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
requires std::convertible_to<U, E> &&
|
||||
(!std::is_reference_v<U>)constexpr Expected(Unexpected<U> e)
|
||||
: Base(boost::outcome_v2::failure(E(std::move(e.value()))))
|
||||
: Base(E(std::move(e.value())))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ public:
|
||||
template <typename U>
|
||||
requires std::convertible_to<U, E> &&
|
||||
(!std::is_reference_v<U>)constexpr Expected(Unexpected<U> e)
|
||||
: Base(boost::outcome_v2::failure(E(std::move(e.value()))))
|
||||
: Base(E(std::move(e.value())))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -20,13 +20,9 @@
|
||||
#ifndef RIPPLE_TEST_JTX_HOOK_H_INCLUDED
|
||||
#define RIPPLE_TEST_JTX_HOOK_H_INCLUDED
|
||||
|
||||
#include <ripple/app/hook/applyHook.h>
|
||||
#include <ripple/json/json_value.h>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <test/jtx/Account.h>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
@@ -47,75 +43,6 @@ hso(std::string const& wasmHex, void (*f)(Json::Value& jv) = 0);
|
||||
Json::Value
|
||||
hso_delete(void (*f)(Json::Value& jv) = 0);
|
||||
|
||||
struct StubHookResult
|
||||
{
|
||||
ripple::uint256 const hookSetTxnID = ripple::uint256();
|
||||
ripple::uint256 const hookHash = ripple::uint256();
|
||||
ripple::uint256 const hookCanEmit = ripple::uint256();
|
||||
ripple::uint256 const hookNamespace = ripple::uint256();
|
||||
|
||||
std::queue<std::shared_ptr<ripple::Transaction>> emittedTxn{};
|
||||
std::optional<hook::HookStateMap> stateMap = std::nullopt;
|
||||
uint16_t changedStateCount = 0;
|
||||
std::map<
|
||||
ripple::uint256, // hook hash
|
||||
std::map<
|
||||
std::vector<uint8_t>, // hook param name
|
||||
std::vector<uint8_t> // hook param value
|
||||
>>
|
||||
hookParamOverrides = {};
|
||||
|
||||
std::optional<std::map<std::vector<uint8_t>, std::vector<uint8_t>>>
|
||||
hookParams = std::nullopt;
|
||||
std::set<ripple::uint256> hookSkips = {};
|
||||
hook_api::ExitType exitType = hook_api::ExitType::ROLLBACK;
|
||||
std::string exitReason{""};
|
||||
int64_t exitCode{-1};
|
||||
uint64_t instructionCount{0};
|
||||
bool hasCallback = false;
|
||||
bool isCallback = false;
|
||||
bool isStrong = false;
|
||||
uint32_t wasmParam = 0;
|
||||
uint32_t overrideCount = 0;
|
||||
uint8_t hookChainPosition = 0;
|
||||
bool foreignStateSetDisabled = false;
|
||||
bool executeAgainAsWeak = false;
|
||||
std::shared_ptr<STObject const> provisionalMeta = nullptr;
|
||||
};
|
||||
|
||||
struct StubHookContext
|
||||
{
|
||||
std::map<uint32_t, hook::SlotEntry> slot{};
|
||||
std::queue<uint32_t> slot_free{};
|
||||
uint32_t slot_counter{0};
|
||||
uint16_t emit_nonce_counter{0};
|
||||
uint16_t ledger_nonce_counter{0};
|
||||
int64_t expected_etxn_count{-1};
|
||||
std::map<ripple::uint256, bool> nonce_used{};
|
||||
uint32_t generation = 0;
|
||||
uint64_t burden = 0;
|
||||
std::map<uint32_t, uint32_t> guard_map{};
|
||||
StubHookResult result = {};
|
||||
std::optional<ripple::STObject> emitFailure = std::nullopt;
|
||||
const hook::HookExecutor* module = 0;
|
||||
};
|
||||
|
||||
// Overload that takes external stateMap to avoid dangling reference
|
||||
hook::HookContext
|
||||
makeStubHookContext(
|
||||
ripple::ApplyContext& applyCtx,
|
||||
ripple::AccountID const& hookAccount,
|
||||
ripple::AccountID const& otxnAccount,
|
||||
StubHookContext const& stubHookContext,
|
||||
hook::HookStateMap& stateMap);
|
||||
|
||||
hook::HookContext
|
||||
makeStubHookContext(
|
||||
ripple::ApplyContext& applyCtx,
|
||||
ripple::AccountID const& hookAccount,
|
||||
ripple::AccountID const& otxnAccount,
|
||||
StubHookContext const& stubHookContext);
|
||||
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
|
||||
@@ -18,9 +18,7 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/app/hook/Enum.h>
|
||||
#include <ripple/app/hook/applyHook.h>
|
||||
#include <ripple/basics/contract.h>
|
||||
#include <ripple/protocol/Keylet.h>
|
||||
#include <ripple/protocol/jss.h>
|
||||
#include <stdexcept>
|
||||
#include <test/jtx/hook.h>
|
||||
@@ -104,81 +102,6 @@ hso(std::string const& wasmHex, void (*f)(Json::Value& jv))
|
||||
return jv;
|
||||
}
|
||||
|
||||
// Helper function to create HookContext with external stateMap
|
||||
hook::HookContext
|
||||
makeStubHookContext(
|
||||
ripple::ApplyContext& applyCtx,
|
||||
ripple::AccountID const& hookAccount,
|
||||
ripple::AccountID const& otxnAccount,
|
||||
StubHookContext const& stubHookContext,
|
||||
hook::HookStateMap& stateMap)
|
||||
{
|
||||
auto& result = stubHookContext.result;
|
||||
auto hookParams = result.hookParams.value_or(
|
||||
std::map<std::vector<uint8_t>, std::vector<uint8_t>>{});
|
||||
return hook::HookContext{
|
||||
.applyCtx = applyCtx,
|
||||
.slot = stubHookContext.slot,
|
||||
.slot_free = stubHookContext.slot_free,
|
||||
.slot_counter = stubHookContext.slot_counter,
|
||||
.emit_nonce_counter = stubHookContext.emit_nonce_counter,
|
||||
.ledger_nonce_counter = stubHookContext.ledger_nonce_counter,
|
||||
.expected_etxn_count = stubHookContext.expected_etxn_count,
|
||||
.nonce_used = stubHookContext.nonce_used,
|
||||
.generation = stubHookContext.generation,
|
||||
.burden = stubHookContext.burden,
|
||||
.guard_map = stubHookContext.guard_map,
|
||||
.result =
|
||||
{
|
||||
.hookSetTxnID = result.hookSetTxnID,
|
||||
.hookHash = result.hookHash,
|
||||
.hookCanEmit = result.hookCanEmit,
|
||||
.accountKeylet = keylet::account(hookAccount),
|
||||
.hookKeylet = keylet::hook(hookAccount),
|
||||
.account = hookAccount,
|
||||
.otxnAccount = otxnAccount,
|
||||
.hookNamespace = result.hookNamespace,
|
||||
.emittedTxn = result.emittedTxn,
|
||||
.stateMap = stateMap,
|
||||
.changedStateCount = result.changedStateCount,
|
||||
.hookParamOverrides = result.hookParamOverrides,
|
||||
.hookParams = hookParams,
|
||||
.hookSkips = result.hookSkips,
|
||||
.exitType = result.exitType,
|
||||
.exitReason = result.exitReason,
|
||||
.exitCode = result.exitCode,
|
||||
.instructionCount = result.instructionCount,
|
||||
.hasCallback = result.hasCallback,
|
||||
.isCallback = result.isCallback,
|
||||
.isStrong = result.isStrong,
|
||||
.wasmParam = result.wasmParam,
|
||||
.overrideCount = result.overrideCount,
|
||||
.hookChainPosition = result.hookChainPosition,
|
||||
.foreignStateSetDisabled = result.foreignStateSetDisabled,
|
||||
.executeAgainAsWeak = result.executeAgainAsWeak,
|
||||
.provisionalMeta = result.provisionalMeta,
|
||||
},
|
||||
.emitFailure = stubHookContext.emitFailure,
|
||||
.module = nullptr};
|
||||
}
|
||||
|
||||
// Original function - WARNING: stateMap reference may become dangling
|
||||
// Only use when stateMap access is not needed after HookContext creation
|
||||
hook::HookContext
|
||||
makeStubHookContext(
|
||||
ripple::ApplyContext& applyCtx,
|
||||
ripple::AccountID const& hookAccount,
|
||||
ripple::AccountID const& otxnAccount,
|
||||
StubHookContext const& stubHookContext)
|
||||
{
|
||||
// Use thread_local to keep stateMap alive
|
||||
// Note: This is a workaround; each call resets the stateMap
|
||||
thread_local hook::HookStateMap stateMap;
|
||||
stateMap = stubHookContext.result.stateMap.value_or(hook::HookStateMap{});
|
||||
return makeStubHookContext(
|
||||
applyCtx, hookAccount, otxnAccount, stubHookContext, stateMap);
|
||||
}
|
||||
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
|
||||
Reference in New Issue
Block a user