From ed68a53f6c903795fecc17b53053e8cd848dabac Mon Sep 17 00:00:00 2001 From: tequ Date: Sat, 6 Sep 2025 03:59:19 +0900 Subject: [PATCH] otxn Hook APIs --- src/ripple/app/hook/HookAPI.h | 30 +++++- src/ripple/app/hook/impl/HookAPI.cpp | 138 +++++++++++++++++++++++++ src/ripple/app/hook/impl/applyHook.cpp | 116 ++++++--------------- 3 files changed, 196 insertions(+), 88 deletions(-) diff --git a/src/ripple/app/hook/HookAPI.h b/src/ripple/app/hook/HookAPI.h index fd6f8b921..593b686d2 100644 --- a/src/ripple/app/hook/HookAPI.h +++ b/src/ripple/app/hook/HookAPI.h @@ -6,12 +6,16 @@ #include #include +#include +#include #include namespace hook { using namespace ripple; using HookReturnCode = hook_api::hook_return_code; +using Bytes = std::vector; + struct HookContext; // defined in applyHook.h class HookAPI @@ -111,11 +115,21 @@ public: uint32_t otxn_generation() const; - // otxn_field - // otxn_id - // otxn_type - // otxn_slot - // otxn_param + + Expected + otxn_field(uint32_t field_id) const; + + Expected + otxn_id(uint32_t flags) const; + + TxType + otxn_type() const; + + Expected + otxn_slot(uint32_t slot_into) const; + + Expected + otxn_param(Bytes param_name) const; /// hook APIs // hook_account @@ -162,6 +176,12 @@ public: private: HookContext& hookCtx; + inline int32_t + no_free_slots() const; + + inline std::optional + get_free_slot() const; + inline Expected float_multiply_internal_parts( uint64_t man1, diff --git a/src/ripple/app/hook/impl/HookAPI.cpp b/src/ripple/app/hook/impl/HookAPI.cpp index 5b9823e6c..719251375 100644 --- a/src/ripple/app/hook/impl/HookAPI.cpp +++ b/src/ripple/app/hook/impl/HookAPI.cpp @@ -294,6 +294,108 @@ const int64_t float_one_internal = using namespace ripple; using namespace hook_float; +Expected +HookAPI::otxn_field(uint32_t field_id) const +{ + SField const& fieldType = ripple::SField::getField(field_id); + + if (fieldType == sfInvalid) + return Unexpected(INVALID_FIELD); + + if (!hookCtx.applyCtx.tx.isFieldPresent(fieldType)) + return Unexpected(DOESNT_EXIST); + + auto const& field = hookCtx.emitFailure + ? hookCtx.emitFailure->getField(fieldType) + : const_cast(hookCtx.applyCtx.tx).getField(fieldType); + + return &field; +} + +Expected +HookAPI::otxn_id(uint32_t flags) const +{ + auto const& txID = + (hookCtx.emitFailure && !flags + ? hookCtx.applyCtx.tx.getFieldH256(sfTransactionHash) + : hookCtx.applyCtx.tx.getTransactionID()); + + return txID; +} + +TxType +HookAPI::otxn_type() const +{ + if (hookCtx.emitFailure) + return safe_cast( + hookCtx.emitFailure->getFieldU16(sfTransactionType)); + + return hookCtx.applyCtx.tx.getTxnType(); +} + +Expected +HookAPI::otxn_slot(uint32_t slot_into) const +{ + if (slot_into > hook_api::max_slots) + return Unexpected(INVALID_ARGUMENT); + + // check if we can emplace the object to a slot + if (slot_into == 0 && no_free_slots()) + return Unexpected(NO_FREE_SLOTS); + + if (slot_into == 0) + { + if (auto found = get_free_slot(); found) + slot_into = *found; + else + return Unexpected(NO_FREE_SLOTS); + } + + auto const& st_tx = std::make_shared( + hookCtx.emitFailure ? *(hookCtx.emitFailure) + : const_cast(hookCtx.applyCtx.tx) + .downcast()); + + hookCtx.slot[slot_into] = hook::SlotEntry{.storage = st_tx, .entry = 0}; + + hookCtx.slot[slot_into].entry = &(*hookCtx.slot[slot_into].storage); + + return slot_into; +} + +Expected +HookAPI::otxn_param(Bytes param_name) const +{ + if (param_name.size() < 1) + return Unexpected(TOO_SMALL); + + if (param_name.size() > 32) + return Unexpected(TOO_BIG); + + if (!hookCtx.applyCtx.tx.isFieldPresent(sfHookParameters)) + return Unexpected(DOESNT_EXIST); + + auto const& params = hookCtx.applyCtx.tx.getFieldArray(sfHookParameters); + + for (auto const& param : params) + { + if (!param.isFieldPresent(sfHookParameterName) || + param.getFieldVL(sfHookParameterName) != param_name) + continue; + + if (!param.isFieldPresent(sfHookParameterValue)) + return Unexpected(DOESNT_EXIST); + + auto const& val = param.getFieldVL(sfHookParameterValue); + if (val.empty()) + return Unexpected(DOESNT_EXIST); + + return val; + } + + return Unexpected(DOESNT_EXIST); +} + Expected, HookReturnCode> HookAPI::emit(Slice txBlob) { @@ -992,6 +1094,42 @@ HookAPI::otxn_generation() const // private +inline int32_t +HookAPI::no_free_slots() const +{ + return hook_api::max_slots - hookCtx.slot.size() <= 0; +} + +inline std::optional +HookAPI::get_free_slot() const +{ + // allocate a slot + int32_t slot_into = 0; + if (hookCtx.slot_free.size() > 0) + { + slot_into = hookCtx.slot_free.front(); + hookCtx.slot_free.pop(); + return slot_into; + } + + // no slots were available in the queue so increment slot counter until we + // find a free slot usually this will be the next available but the hook + // developer may have allocated any slot ahead of when the counter gets + // there + do + { + slot_into = ++hookCtx.slot_counter; + } while (hookCtx.slot.find(slot_into) != hookCtx.slot.end() && + // this condition should always be met, if for some reason, somehow + // it is not then we will return the final slot every time. + hookCtx.slot_counter <= hook_api::max_slots); + + if (hookCtx.slot_counter > hook_api::max_slots) + return {}; + + return slot_into; +} + inline Expected HookAPI::float_multiply_internal_parts( uint64_t man1, diff --git a/src/ripple/app/hook/impl/applyHook.cpp b/src/ripple/app/hook/impl/applyHook.cpp index adedb2135..a5668992b 100644 --- a/src/ripple/app/hook/impl/applyHook.cpp +++ b/src/ripple/app/hook/impl/applyHook.cpp @@ -767,6 +767,8 @@ normalize_xfl(T& man, int32_t& exp, bool neg = false) } // namespace hook_float using namespace hook_float; +using hook::Bytes; + inline int32_t no_free_slots(hook::HookContext& hookCtx) { @@ -2214,10 +2216,12 @@ DEFINE_HOOK_FUNCTION( HOOK_SETUP(); // populates memory_ctx, memory, memory_length, applyCtx, // hookCtx on current stack - auto const& txID = - (hookCtx.emitFailure && !flags - ? applyCtx.tx.getFieldH256(sfTransactionHash) - : applyCtx.tx.getTransactionID()); + hook::HookAPI api(hookCtx); + auto const result = api.otxn_id(flags); + if (!result) + return result.error(); + + auto const& txID = result.value(); if (txID.size() > write_len) return TOO_SMALL; @@ -2243,11 +2247,8 @@ DEFINE_HOOK_FUNCNARG(int64_t, otxn_type) HOOK_SETUP(); // populates memory_ctx, memory, memory_length, applyCtx, // hookCtx on current stack - if (hookCtx.emitFailure) - return safe_cast( - hookCtx.emitFailure->getFieldU16(sfTransactionType)); - - return applyCtx.tx.getTxnType(); + hook::HookAPI api(hookCtx); + return api.otxn_type(); HOOK_TEARDOWN(); } @@ -2257,31 +2258,12 @@ DEFINE_HOOK_FUNCTION(int64_t, otxn_slot, uint32_t slot_into) HOOK_SETUP(); // populates memory_ctx, memory, memory_length, applyCtx, // hookCtx on current stack - if (slot_into > hook_api::max_slots) - return INVALID_ARGUMENT; + hook::HookAPI api(hookCtx); + auto const result = api.otxn_slot(slot_into); + if (!result) + return result.error(); - // check if we can emplace the object to a slot - if (slot_into == 0 && no_free_slots(hookCtx)) - return NO_FREE_SLOTS; - - if (slot_into == 0) - { - if (auto found = get_free_slot(hookCtx); found) - slot_into = *found; - else - return NO_FREE_SLOTS; - } - - auto const& st_tx = std::make_shared( - hookCtx.emitFailure ? *(hookCtx.emitFailure) - : const_cast(applyCtx.tx) - .downcast()); - - hookCtx.slot[slot_into] = hook::SlotEntry{.storage = st_tx, .entry = 0}; - - hookCtx.slot[slot_into].entry = &(*hookCtx.slot[slot_into].storage); - - return slot_into; + return result.value(); HOOK_TEARDOWN(); } @@ -2378,27 +2360,22 @@ DEFINE_HOOK_FUNCTION( else if NOT_IN_BOUNDS (write_ptr, write_len, memory_length) return OUT_OF_BOUNDS; - SField const& fieldType = ripple::SField::getField(field_id); + hook::HookAPI api(hookCtx); + auto const result = api.otxn_field(field_id); + if (!result) + return result.error(); - if (fieldType == sfInvalid) - return INVALID_FIELD; - - if (!applyCtx.tx.isFieldPresent(fieldType)) - return DOESNT_EXIST; - - auto const& field = hookCtx.emitFailure - ? hookCtx.emitFailure->getField(fieldType) - : const_cast(applyCtx.tx).getField(fieldType); + auto const& field = result.value(); Serializer s; - field.add(s); + field->add(s); WRITE_WASM_MEMORY_OR_RETURN_AS_INT64( write_ptr, write_len, s.getDataPtr(), s.getDataLength(), - field.getSType() == STI_ACCOUNT); + field->getSType() == STI_ACCOUNT); HOOK_TEARDOWN(); } @@ -5031,46 +5008,19 @@ DEFINE_HOOK_FUNCTION( if (NOT_IN_BOUNDS(write_ptr, write_len, memory_length)) return OUT_OF_BOUNDS; - if (read_len < 1) + Bytes paramName{read_ptr + memory, read_ptr + read_len + memory}; + + hook::HookAPI api(hookCtx); + auto const result = api.otxn_param(paramName); + if (!result) + return result.error(); + auto const& val = result.value(); + + if (val.size() > write_len) return TOO_SMALL; - if (read_len > 32) - return TOO_BIG; - - if (!applyCtx.tx.isFieldPresent(sfHookParameters)) - return DOESNT_EXIST; - - std::vector paramName{ - read_ptr + memory, read_ptr + read_len + memory}; - - auto const& params = applyCtx.tx.getFieldArray(sfHookParameters); - - for (auto const& param : params) - { - if (!param.isFieldPresent(sfHookParameterName) || - param.getFieldVL(sfHookParameterName) != paramName) - continue; - - if (!param.isFieldPresent(sfHookParameterValue)) - return DOESNT_EXIST; - - auto const& val = param.getFieldVL(sfHookParameterValue); - if (val.empty()) - return DOESNT_EXIST; - - if (val.size() > write_len) - return TOO_SMALL; - - WRITE_WASM_MEMORY_AND_RETURN( - write_ptr, - write_len, - val.data(), - val.size(), - memory, - memory_length); - } - - return DOESNT_EXIST; + WRITE_WASM_MEMORY_AND_RETURN( + write_ptr, write_len, val.data(), val.size(), memory, memory_length); HOOK_TEARDOWN(); }