From 27e4e4b510c4641cd7fd4b7b386a5601cbc97a3a Mon Sep 17 00:00:00 2001 From: tequ Date: Mon, 29 Sep 2025 12:18:01 +0900 Subject: [PATCH] add slot APIs --- src/ripple/app/hook/HookAPI.h | 37 ++- src/ripple/app/hook/impl/HookAPI.cpp | 318 ++++++++++++++++++++++++- src/ripple/app/hook/impl/applyHook.cpp | 274 ++++----------------- 3 files changed, 381 insertions(+), 248 deletions(-) diff --git a/src/ripple/app/hook/HookAPI.h b/src/ripple/app/hook/HookAPI.h index 1bd1f854d..e507f9c3a 100644 --- a/src/ripple/app/hook/HookAPI.h +++ b/src/ripple/app/hook/HookAPI.h @@ -206,15 +206,33 @@ public: Bytes& data) const; /// slot APIs - // slot - // slot_clear - // slot_count - // slot_set - // slot_size - // slot_subarray - // slot_subfield + Expected + slot(uint32_t slot_no) const; + + Expected + slot_clear(uint32_t slot_no) const; + + Expected + slot_count(uint32_t slot_no) const; + + Expected + slot_set(Bytes const& data, uint32_t slot_no) const; + + Expected + slot_size(uint32_t slot_no) const; + + Expected + slot_subarray(uint32_t parent_slot, uint32_t array_id, uint32_t new_slot) + const; + + Expected + slot_subfield(uint32_t parent_slot, uint32_t field_id, uint32_t new_slot) + const; + // slot_type - // slot_float + + Expected + slot_float(uint32_t slot_no) const; /// trace APIs // trace @@ -256,6 +274,9 @@ private: inline Expected double_to_xfl(double x) const; + std::optional + unserialize_keylet(Bytes const& data) const; + // update the state cache inline std::optional< std::reference_wrapper const>> diff --git a/src/ripple/app/hook/impl/HookAPI.cpp b/src/ripple/app/hook/impl/HookAPI.cpp index fdafff74f..d35bfc3cc 100644 --- a/src/ripple/app/hook/impl/HookAPI.cpp +++ b/src/ripple/app/hook/impl/HookAPI.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -1646,15 +1647,303 @@ HookAPI::state_foreign_set( } /// slot APIs -// slot -// slot_clear -// slot_count -// slot_set -// slot_size -// slot_subarray -// slot_subfield + +Expected +HookAPI::slot(uint32_t slot_no) const +{ + if (hookCtx.slot.find(slot_no) == hookCtx.slot.end()) + return Unexpected(DOESNT_EXIST); + + if (hookCtx.slot[slot_no].entry == 0) + return Unexpected(INTERNAL_ERROR); + + return hookCtx.slot[slot_no].entry; +} + +Expected +HookAPI::slot_clear(uint32_t slot_no) const +{ + if (hookCtx.slot.find(slot_no) == hookCtx.slot.end()) + return Unexpected(DOESNT_EXIST); + + hookCtx.slot.erase(slot_no); + hookCtx.slot_free.push(slot_no); + return 1; +} + +Expected +HookAPI::slot_count(uint32_t slot_no) const +{ + if (hookCtx.slot.find(slot_no) == hookCtx.slot.end()) + return Unexpected(DOESNT_EXIST); + + if (hookCtx.slot[slot_no].entry == 0) + return Unexpected(INTERNAL_ERROR); + + if (hookCtx.slot[slot_no].entry->getSType() != STI_ARRAY) + return Unexpected(NOT_AN_ARRAY); + + return hookCtx.slot[slot_no].entry->downcast().size(); +} + +Expected +HookAPI::slot_set(Bytes const& data, uint32_t slot_no) const +{ + if ((data.size() != 32 && data.size() != 34) || + slot_no > hook_api::max_slots) + return Unexpected(INVALID_ARGUMENT); + + if (slot_no == 0 && no_free_slots()) + return Unexpected(NO_FREE_SLOTS); + + std::optional> slot_value = + std::nullopt; + + if (data.size() == 34) + { + std::optional kl = unserialize_keylet(data); + if (!kl) + return Unexpected(DOESNT_EXIST); + + if (kl->key == beast::zero) + return Unexpected(DOESNT_EXIST); + + auto const sle = hookCtx.applyCtx.view().read(*kl); + if (!sle) + return Unexpected(DOESNT_EXIST); + + slot_value = sle; + } + else if (data.size() == 32) + { + uint256 hash = ripple::base_uint<256>::fromVoid(data.data()); + + ripple::error_code_i ec{ripple::error_code_i::rpcUNKNOWN}; + + auto hTx = hookCtx.applyCtx.app.getMasterTransaction().fetch(hash, ec); + + if (auto const* p = std::get_if, + std::shared_ptr>>(&hTx)) + slot_value = p->first->getSTransaction(); + else + return Unexpected(DOESNT_EXIST); + } + else + return Unexpected(INVALID_ARGUMENT); + + if (!slot_value.has_value()) + return Unexpected(DOESNT_EXIST); + + if (slot_no == 0) + { + if (auto found = get_free_slot(); found) + slot_no = *found; + else + return Unexpected(NO_FREE_SLOTS); + } + + hookCtx.slot[slot_no] = hook::SlotEntry{.storage = *slot_value, .entry = 0}; + hookCtx.slot[slot_no].entry = &(*hookCtx.slot[slot_no].storage); + + return slot_no; +} + +Expected +HookAPI::slot_size(uint32_t slot_no) const +{ + if (hookCtx.slot.find(slot_no) == hookCtx.slot.end()) + return Unexpected(DOESNT_EXIST); + + if (hookCtx.slot[slot_no].entry == 0) + return Unexpected(INTERNAL_ERROR); + + // RH TODO: this is a very expensive way of computing size, cache it + Serializer s; + hookCtx.slot[slot_no].entry->add(s); + return s.getDataLength(); +} + +Expected +HookAPI::slot_subarray( + uint32_t parent_slot, + uint32_t array_id, + uint32_t new_slot) const +{ + if (hookCtx.slot.find(parent_slot) == hookCtx.slot.end()) + return Unexpected(DOESNT_EXIST); + + if (hookCtx.slot[parent_slot].entry == 0) + return Unexpected(INTERNAL_ERROR); + + if (hookCtx.slot[parent_slot].entry->getSType() != STI_ARRAY) + return Unexpected(NOT_AN_ARRAY); + + if (new_slot == 0 && no_free_slots()) + return Unexpected(NO_FREE_SLOTS); + + if (new_slot > hook_api::max_slots) + return Unexpected(INVALID_ARGUMENT); + + bool copied = false; + try + { + ripple::STArray& parent_obj = + const_cast(*hookCtx.slot[parent_slot].entry) + .downcast(); + + if (parent_obj.size() <= array_id) + return Unexpected(DOESNT_EXIST); + + if (new_slot == 0) + { + if (auto found = get_free_slot(); found) + new_slot = *found; + else + return Unexpected(NO_FREE_SLOTS); + } + + // copy + if (new_slot != parent_slot) + { + copied = true; + hookCtx.slot[new_slot] = hookCtx.slot[parent_slot]; + } + hookCtx.slot[new_slot].entry = &(parent_obj[array_id]); + return new_slot; + } + catch (const std::bad_cast& e) + { + if (copied) + { + hookCtx.slot.erase(new_slot); + hookCtx.slot_free.push(new_slot); + } + return Unexpected(NOT_AN_ARRAY); + } + + return new_slot; +} + +Expected +HookAPI::slot_subfield( + uint32_t parent_slot, + uint32_t field_id, + uint32_t new_slot) const +{ + if (hookCtx.slot.find(parent_slot) == hookCtx.slot.end()) + return Unexpected(DOESNT_EXIST); + + if (new_slot == 0 && no_free_slots()) + return Unexpected(NO_FREE_SLOTS); + + if (new_slot > hook_api::max_slots) + return Unexpected(INVALID_ARGUMENT); + + SField const& fieldCode = ripple::SField::getField(field_id); + + if (fieldCode == sfInvalid) + return Unexpected(INVALID_FIELD); + + if (hookCtx.slot[parent_slot].entry == 0) + return Unexpected(INTERNAL_ERROR); + + bool copied = false; + + try + { + ripple::STObject& parent_obj = + const_cast(*hookCtx.slot[parent_slot].entry) + .downcast(); + + if (!parent_obj.isFieldPresent(fieldCode)) + return Unexpected(DOESNT_EXIST); + + if (new_slot == 0) + { + if (auto found = get_free_slot(); found) + new_slot = *found; + else + return Unexpected(NO_FREE_SLOTS); + } + + // copy + if (new_slot != parent_slot) + { + copied = true; + hookCtx.slot[new_slot] = hookCtx.slot[parent_slot]; + } + + hookCtx.slot[new_slot].entry = &(parent_obj.getField(fieldCode)); + return new_slot; + } + catch (const std::bad_cast& e) + { + if (copied) + { + hookCtx.slot.erase(new_slot); + hookCtx.slot_free.push(new_slot); + } + return Unexpected(NOT_AN_OBJECT); + } +} + // slot_type -// slot_float + +Expected +HookAPI::slot_float(uint32_t slot_no) const +{ + if (hookCtx.slot.find(slot_no) == hookCtx.slot.end()) + return Unexpected(DOESNT_EXIST); + + if (hookCtx.slot[slot_no].entry == 0) + return Unexpected(INTERNAL_ERROR); + + try + { + ripple::STAmount& st_amt = + const_cast(*hookCtx.slot[slot_no].entry) + .downcast(); + + int64_t normalized = 0; + if (st_amt.native()) + { + ripple::XRPAmount amt = st_amt.xrp(); + int64_t drops = amt.drops(); + int32_t exp = -6; + // normalize + auto const ret = hook_float::normalize_xfl(drops, exp); + if (!ret) + { + if (ret.error() == EXPONENT_UNDERSIZED) + return 0; + return Unexpected(ret.error()); + } + normalized = ret.value(); + } + else + { + ripple::IOUAmount amt = st_amt.iou(); + auto const ret = make_float(amt); + if (!ret) + { + if (ret.error() == EXPONENT_UNDERSIZED) + return 0; + return Unexpected(ret.error()); + } + normalized = ret.value(); + } + + if (normalized == EXPONENT_UNDERSIZED) + /* exponent undersized (underflow) */ + return 0; // return 0 in this case + return normalized; + } + catch (const std::bad_cast& e) + { + return Unexpected(NOT_AN_AMOUNT); + } +} /// trace APIs // trace @@ -1887,6 +2176,19 @@ HookAPI::double_to_xfl(double x) const return ret; } +std::optional +HookAPI::unserialize_keylet(Bytes const& data) const +{ + if (data.size() != 34) + return {}; + + uint16_t ktype = ((uint16_t)data[0] << 8) + ((uint16_t)data[1]); + + return ripple::Keylet{ + static_cast(ktype), + ripple::uint256::fromVoid(data.data() + 2)}; +} + inline std::optional< std::reference_wrapper const>> HookAPI::lookup_state_cache( diff --git a/src/ripple/app/hook/impl/applyHook.cpp b/src/ripple/app/hook/impl/applyHook.cpp index f935fee28..f5acb508e 100644 --- a/src/ripple/app/hook/impl/applyHook.cpp +++ b/src/ripple/app/hook/impl/applyHook.cpp @@ -1,7 +1,6 @@ #include #include #include -#include #include #include #include @@ -2136,21 +2135,20 @@ DEFINE_HOOK_FUNCTION( return TOO_SMALL; } - if (hookCtx.slot.find(slot_no) == hookCtx.slot.end()) - return DOESNT_EXIST; - - if (hookCtx.slot[slot_no].entry == 0) - return INTERNAL_ERROR; + hook::HookAPI api(hookCtx); + auto const result = api.slot(slot_no); + if (!result) + return result.error(); Serializer s; - hookCtx.slot[slot_no].entry->add(s); + (*result)->add(s); WRITE_WASM_MEMORY_OR_RETURN_AS_INT64( write_ptr, write_len, s.getDataPtr(), s.getDataLength(), - hookCtx.slot[slot_no].entry->getSType() == STI_ACCOUNT); + (*result)->getSType() == STI_ACCOUNT); HOOK_TEARDOWN(); } @@ -2160,13 +2158,12 @@ DEFINE_HOOK_FUNCTION(int64_t, slot_clear, uint32_t slot_no) HOOK_SETUP(); // populates memory_ctx, memory, memory_length, applyCtx, // hookCtx on current stack - if (hookCtx.slot.find(slot_no) == hookCtx.slot.end()) - return DOESNT_EXIST; + hook::HookAPI api(hookCtx); + auto const result = api.slot_clear(slot_no); + if (!result) + return result.error(); - hookCtx.slot.erase(slot_no); - hookCtx.slot_free.push(slot_no); - - return 1; + return result.value(); HOOK_TEARDOWN(); } @@ -2176,16 +2173,12 @@ DEFINE_HOOK_FUNCTION(int64_t, slot_count, uint32_t slot_no) HOOK_SETUP(); // populates memory_ctx, memory, memory_length, applyCtx, // hookCtx on current stack - if (hookCtx.slot.find(slot_no) == hookCtx.slot.end()) - return DOESNT_EXIST; + hook::HookAPI api(hookCtx); + auto const result = api.slot_count(slot_no); + if (!result) + return result.error(); - if (hookCtx.slot[slot_no].entry == 0) - return INTERNAL_ERROR; - - if (hookCtx.slot[slot_no].entry->getSType() != STI_ARRAY) - return NOT_AN_ARRAY; - - return hookCtx.slot[slot_no].entry->downcast().size(); + return result.value(); HOOK_TEARDOWN(); } @@ -2203,68 +2196,13 @@ DEFINE_HOOK_FUNCTION( if (NOT_IN_BOUNDS(read_ptr, read_len, memory_length)) return OUT_OF_BOUNDS; - if ((read_len != 32 && read_len != 34) || slot_into > hook_api::max_slots) - return INVALID_ARGUMENT; + hook::HookAPI api(hookCtx); + Bytes data{memory + read_ptr, memory + read_ptr + read_len}; + auto const result = api.slot_set(data, 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; - - std::vector slot_key{ - memory + read_ptr, memory + read_ptr + read_len}; - std::optional> slot_value = - std::nullopt; - - if (read_len == 34) - { - std::optional kl = - unserialize_keylet(memory + read_ptr, read_len); - if (!kl) - return DOESNT_EXIST; - - if (kl->key == beast::zero) - return DOESNT_EXIST; - - auto const sle = applyCtx.view().read(*kl); - if (!sle) - return DOESNT_EXIST; - - slot_value = sle; - } - else if (read_len == 32) - { - uint256 hash = ripple::base_uint<256>::fromVoid(memory + read_ptr); - - ripple::error_code_i ec{ripple::error_code_i::rpcUNKNOWN}; - - auto hTx = applyCtx.app.getMasterTransaction().fetch(hash, ec); - - if (auto const* p = std::get_if, - std::shared_ptr>>(&hTx)) - slot_value = p->first->getSTransaction(); - else - return DOESNT_EXIST; - } - else - return DOESNT_EXIST; - - if (!slot_value.has_value()) - return DOESNT_EXIST; - - if (slot_into == 0) - { - if (auto found = get_free_slot(hookCtx); found) - slot_into = *found; - else - return NO_FREE_SLOTS; - } - - hookCtx.slot[slot_into] = - hook::SlotEntry{.storage = *slot_value, .entry = 0}; - hookCtx.slot[slot_into].entry = &(*hookCtx.slot[slot_into].storage); - - return slot_into; + return result.value(); HOOK_TEARDOWN(); } @@ -2274,16 +2212,12 @@ DEFINE_HOOK_FUNCTION(int64_t, slot_size, uint32_t slot_no) HOOK_SETUP(); // populates memory_ctx, memory, memory_length, applyCtx, // hookCtx on current stack - if (hookCtx.slot.find(slot_no) == hookCtx.slot.end()) - return DOESNT_EXIST; + hook::HookAPI api(hookCtx); + auto const result = api.slot_size(slot_no); + if (!result) + return result.error(); - if (hookCtx.slot[slot_no].entry == 0) - return INTERNAL_ERROR; - - // RH TODO: this is a very expensive way of computing size, cache it - Serializer s; - hookCtx.slot[slot_no].entry->add(s); - return s.getDataLength(); + return result.value(); HOOK_TEARDOWN(); } @@ -2298,57 +2232,12 @@ DEFINE_HOOK_FUNCTION( HOOK_SETUP(); // populates memory_ctx, memory, memory_length, applyCtx, // hookCtx on current stack - if (hookCtx.slot.find(parent_slot) == hookCtx.slot.end()) - return DOESNT_EXIST; + hook::HookAPI api(hookCtx); + auto const result = api.slot_subarray(parent_slot, array_id, new_slot); + if (!result) + return result.error(); - if (hookCtx.slot[parent_slot].entry == 0) - return INTERNAL_ERROR; - - if (hookCtx.slot[parent_slot].entry->getSType() != STI_ARRAY) - return NOT_AN_ARRAY; - - if (new_slot == 0 && no_free_slots(hookCtx)) - return NO_FREE_SLOTS; - - if (new_slot > hook_api::max_slots) - return INVALID_ARGUMENT; - - bool copied = false; - try - { - ripple::STArray& parent_obj = - const_cast(*hookCtx.slot[parent_slot].entry) - .downcast(); - - if (parent_obj.size() <= array_id) - return DOESNT_EXIST; - - if (new_slot == 0) - { - if (auto found = get_free_slot(hookCtx); found) - new_slot = *found; - else - return NO_FREE_SLOTS; - } - - // copy - if (new_slot != parent_slot) - { - copied = true; - hookCtx.slot[new_slot] = hookCtx.slot[parent_slot]; - } - hookCtx.slot[new_slot].entry = &(parent_obj[array_id]); - return new_slot; - } - catch (const std::bad_cast& e) - { - if (copied) - { - hookCtx.slot.erase(new_slot); - hookCtx.slot_free.push(new_slot); - } - return NOT_AN_ARRAY; - } + return result.value(); HOOK_TEARDOWN(); } @@ -2363,61 +2252,12 @@ DEFINE_HOOK_FUNCTION( HOOK_SETUP(); // populates memory_ctx, memory, memory_length, applyCtx, // hookCtx on current stack - if (hookCtx.slot.find(parent_slot) == hookCtx.slot.end()) - return DOESNT_EXIST; + hook::HookAPI api(hookCtx); + auto const result = api.slot_subfield(parent_slot, field_id, new_slot); + if (!result) + return result.error(); - if (new_slot == 0 && no_free_slots(hookCtx)) - return NO_FREE_SLOTS; - - if (new_slot > hook_api::max_slots) - return INVALID_ARGUMENT; - - SField const& fieldCode = ripple::SField::getField(field_id); - - if (fieldCode == sfInvalid) - return INVALID_FIELD; - - if (hookCtx.slot[parent_slot].entry == 0) - return INTERNAL_ERROR; - - bool copied = false; - - try - { - ripple::STObject& parent_obj = - const_cast(*hookCtx.slot[parent_slot].entry) - .downcast(); - - if (!parent_obj.isFieldPresent(fieldCode)) - return DOESNT_EXIST; - - if (new_slot == 0) - { - if (auto found = get_free_slot(hookCtx); found) - new_slot = *found; - else - return NO_FREE_SLOTS; - } - - // copy - if (new_slot != parent_slot) - { - copied = true; - hookCtx.slot[new_slot] = hookCtx.slot[parent_slot]; - } - - hookCtx.slot[new_slot].entry = &(parent_obj.getField(fieldCode)); - return new_slot; - } - catch (const std::bad_cast& e) - { - if (copied) - { - hookCtx.slot.erase(new_slot); - hookCtx.slot_free.push(new_slot); - } - return NOT_AN_OBJECT; - } + return result.value(); HOOK_TEARDOWN(); } @@ -2465,42 +2305,12 @@ DEFINE_HOOK_FUNCTION(int64_t, slot_float, uint32_t slot_no) HOOK_SETUP(); // populates memory_ctx, memory, memory_length, applyCtx, // hookCtx on current stack - if (hookCtx.slot.find(slot_no) == hookCtx.slot.end()) - return DOESNT_EXIST; + hook::HookAPI api(hookCtx); + auto const result = api.slot_float(slot_no); + if (!result) + return result.error(); - if (hookCtx.slot[slot_no].entry == 0) - return INTERNAL_ERROR; - - try - { - ripple::STAmount& st_amt = - const_cast(*hookCtx.slot[slot_no].entry) - .downcast(); - - int64_t normalized = 0; - if (st_amt.native()) - { - ripple::XRPAmount amt = st_amt.xrp(); - int64_t drops = amt.drops(); - int32_t exp = -6; - // normalize - normalized = hook_float::normalize_xfl(drops, exp); - } - else - { - ripple::IOUAmount amt = st_amt.iou(); - normalized = make_float(amt); - } - - if (normalized == - EXPONENT_UNDERSIZED /* exponent undersized (underflow) */) - return 0; // return 0 in this case - return normalized; - } - catch (const std::bad_cast& e) - { - return NOT_AN_AMOUNT; - } + return result.value(); HOOK_TEARDOWN(); }