add slot APIs

This commit is contained in:
tequ
2025-09-29 12:18:01 +09:00
parent 962fdbceb6
commit 27e4e4b510
3 changed files with 381 additions and 248 deletions

View File

@@ -206,15 +206,33 @@ public:
Bytes& data) const;
/// slot APIs
// slot
// slot_clear
// slot_count
// slot_set
// slot_size
// slot_subarray
// slot_subfield
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;
// slot_type
// slot_float
Expected<uint64_t, HookReturnCode>
slot_float(uint32_t slot_no) const;
/// trace APIs
// trace
@@ -256,6 +274,9 @@ private:
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>>

View File

@@ -3,6 +3,7 @@
#include <ripple/app/hook/HookAPI.h>
#include <ripple/app/hook/applyHook.h>
#include <ripple/app/ledger/OpenLedger.h>
#include <ripple/app/ledger/TransactionMaster.h>
#include <ripple/app/misc/Transaction.h>
#include <ripple/app/tx/apply.h>
#include <ripple/app/tx/impl/ApplyContext.h>
@@ -1646,15 +1647,303 @@ HookAPI::state_foreign_set(
}
/// slot APIs
// slot
// slot_clear
// slot_count
// slot_set
// slot_size
// slot_subarray
// slot_subfield
Expected<const STBase*, HookReturnCode>
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<uint64_t, HookReturnCode>
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<uint64_t, HookReturnCode>
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<ripple::STArray>().size();
}
Expected<uint32_t, HookReturnCode>
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<std::shared_ptr<const ripple::STObject>> slot_value =
std::nullopt;
if (data.size() == 34)
{
std::optional<ripple::Keylet> 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::pair<
std::shared_ptr<ripple::Transaction>,
std::shared_ptr<ripple::TxMeta>>>(&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<uint64_t, HookReturnCode>
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<uint32_t, HookReturnCode>
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<ripple::STBase&>(*hookCtx.slot[parent_slot].entry)
.downcast<ripple::STArray>();
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<uint32_t, HookReturnCode>
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<ripple::STBase&>(*hookCtx.slot[parent_slot].entry)
.downcast<ripple::STObject>();
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<uint64_t, HookReturnCode>
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<ripple::STBase&>(*hookCtx.slot[slot_no].entry)
.downcast<ripple::STAmount>();
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<ripple::Keylet>
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<LedgerEntryType>(ktype),
ripple::uint256::fromVoid(data.data() + 2)};
}
inline std::optional<
std::reference_wrapper<std::pair<bool, ripple::Blob> const>>
HookAPI::lookup_state_cache(

View File

@@ -1,7 +1,6 @@
#include <ripple/app/hook/HookAPI.h>
#include <ripple/app/hook/applyHook.h>
#include <ripple/app/ledger/OpenLedger.h>
#include <ripple/app/ledger/TransactionMaster.h>
#include <ripple/app/misc/HashRouter.h>
#include <ripple/app/misc/NetworkOPs.h>
#include <ripple/app/misc/Transaction.h>
@@ -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<ripple::STArray>().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<uint8_t> slot_key{
memory + read_ptr, memory + read_ptr + read_len};
std::optional<std::shared_ptr<const ripple::STObject>> slot_value =
std::nullopt;
if (read_len == 34)
{
std::optional<ripple::Keylet> 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::pair<
std::shared_ptr<ripple::Transaction>,
std::shared_ptr<ripple::TxMeta>>>(&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<ripple::STBase&>(*hookCtx.slot[parent_slot].entry)
.downcast<ripple::STArray>();
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<ripple::STBase&>(*hookCtx.slot[parent_slot].entry)
.downcast<ripple::STObject>();
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<ripple::STBase&>(*hookCtx.slot[slot_no].entry)
.downcast<ripple::STAmount>();
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();
}