js state, state_foreign, state_set, state_foreign_set, compiling untested

This commit is contained in:
Richard Holland
2024-05-14 21:09:37 +10:00
committed by RichardAH
parent 8e172e1846
commit 7b0bc67242
3 changed files with 319 additions and 103 deletions

View File

@@ -194,7 +194,7 @@
hook::HookContext& hookCtx, \
WasmEdge_CallingFrameContext const& frameCtx)
#define VAR_JSASSIGN(T, V) T& V = argv[_stack++]
#define VAR_JSASSIGN(T, V) if (_stack >= argc) returnJS(INVALID_ARGUMENT); T& V = argv[_stack++]
#define DEFINE_JS_FUNCNARG(R, F, ...)\
@@ -202,6 +202,7 @@ JSValue hook_api::JSFunction##F(JSContext *ctx, JSValueConst this_val,\
int argc, JSValueConst *argv)\
{
#define FORWARD_JS_FUNCTION_CALL(F, ac, av) hook_api::JSFunction##F(ctx, this_val, ac, av)
#define DEFINE_JS_FUNCTION(R, F, ...)\
JSValue hook_api::JSFunction##F(JSContext *ctx, JSValueConst this_val,\

View File

@@ -442,6 +442,12 @@ DECLARE_WASM_FUNCTION(
uint32_t read_len,
uint32_t kread_ptr,
uint32_t kread_len);
DECLARE_JS_FUNCTION(
JSValue,
state_set,
JSValue val,
JSValue key);
DECLARE_WASM_FUNCTION(
int64_t,
state_foreign_set,
@@ -453,6 +459,15 @@ DECLARE_WASM_FUNCTION(
uint32_t nread_len,
uint32_t aread_ptr,
uint32_t aread_len);
DECLARE_JS_FUNCTION(
JSValue,
state_foreign_set,
JSValue val,
JSValue key,
JSValue ns,
JSValue accid);
DECLARE_WASM_FUNCTION(
int64_t,
state,
@@ -460,6 +475,11 @@ DECLARE_WASM_FUNCTION(
uint32_t write_len,
uint32_t kread_ptr,
uint32_t kread_len);
DECLARE_JS_FUNCTION(
JSValue,
state,
JSValue key);
DECLARE_WASM_FUNCTION(
int64_t,
state_foreign,
@@ -471,6 +491,13 @@ DECLARE_WASM_FUNCTION(
uint32_t nread_len,
uint32_t aread_ptr,
uint32_t aread_len);
DECLARE_JS_FUNCTION(
JSValue,
state_foreign,
JSValue key,
JSValue ns,
JSValue accid);
DECLARE_WASM_FUNCTION(
int64_t,
trace,
@@ -1163,12 +1190,12 @@ public:
ADD_JS_FUNCTION(hook_skip, ctx);
ADD_JS_FUNCTION(hook_pos, ctx);
/*
ADD_JS_FUNCTION(state, ctx);
ADD_JS_FUNCTION(state_foreign, ctx);
ADD_JS_FUNCTION(state_set, ctx);
ADD_JS_FUNCTION(state_foreign_set, ctx);
/*
ADD_JS_FUNCTION(slot, ctx);
ADD_JS_FUNCTION(slot_clear, ctx);
ADD_JS_FUNCTION(slot_count, ctx);

View File

@@ -1710,7 +1710,7 @@ set_state_cache(
ripple::AccountID const& acc,
ripple::uint256 const& ns,
ripple::uint256 const& key,
ripple::Blob& data,
ripple::Blob const& data,
bool modified)
{
auto& stateMap = hookCtx.result.stateMap;
@@ -1853,6 +1853,139 @@ DEFINE_WASM_FUNCTION(
0,
0);
}
DEFINE_JS_FUNCTION(
JSValue,
state_set,
JSValue data,
JSValue key)
{
JS_HOOK_SETUP();
JSValueConst argv2[] = {
argv[0],
argv[1],
JS_UNDEFINED,
JS_UNDEFINED
};
return FORWARD_JS_FUNCTION_CALL(state_foreign_set, 4, argv2);
JS_HOOK_TEARDOWN();
}
inline
int64_t __state_foreign_set(
hook::HookContext& hookCtx, ApplyContext& applyCtx, beast::Journal& j,
Blob const& data, uint256 const& key, uint256 const& ns, AccountID const& acc)
{
int64_t aread_len = acc.size();
int64_t read_len = data.size();
// local modifications are always allowed
if (aread_len == 0 || acc == hookCtx.result.account)
{
if (int64_t ret = set_state_cache(hookCtx, acc, ns, key, data, true);
ret < 0)
return ret;
return read_len;
}
// execution to here means it's actually a foreign set
if (hookCtx.result.foreignStateSetDisabled)
return PREVIOUS_FAILURE_PREVENTS_RETRY;
// first check if we've already modified this state
auto cacheEntry = lookup_state_cache(hookCtx, acc, ns, key);
if (cacheEntry && cacheEntry->get().first)
{
// if a cache entry already exists and it has already been modified
// don't check grants again
if (int64_t ret = set_state_cache(hookCtx, acc, ns, key, data, true);
ret < 0)
return ret;
return read_len;
}
// cache miss or cache was present but entry was not marked as previously
// modified therefore before continuing we need to check grants
auto const sle = applyCtx.view().read(ripple::keylet::hook(acc));
if (!sle)
return INTERNAL_ERROR;
bool found_auth = false;
// we do this by iterating the hooks installed on the foreign account and in
// turn their grants and namespaces
auto const& hooks = sle->getFieldArray(sfHooks);
for (auto const& hookObj : hooks)
{
// skip blank entries
if (!hookObj.isFieldPresent(sfHookHash))
continue;
if (!hookObj.isFieldPresent(sfHookGrants))
continue;
auto const& hookGrants = hookObj.getFieldArray(sfHookGrants);
if (hookGrants.size() < 1)
continue;
// the grant allows the hook to modify the granter's namespace only
if (hookObj.isFieldPresent(sfHookNamespace))
{
if (hookObj.getFieldH256(sfHookNamespace) != ns)
continue;
}
else
{
// fetch the hook definition
auto const def = applyCtx.view().read(ripple::keylet::hookDefinition(
hookObj.getFieldH256(sfHookHash)));
if (!def) // should never happen except in a rare race condition
continue;
if (def->getFieldH256(sfHookNamespace) != ns)
continue;
}
// this is expensive search so we'll disallow after one failed attempt
for (auto const& hookGrantObj : hookGrants)
{
bool hasAuthorizedField = hookGrantObj.isFieldPresent(sfAuthorize);
if (hookGrantObj.getFieldH256(sfHookHash) ==
hookCtx.result.hookHash &&
(!hasAuthorizedField ||
hookGrantObj.getAccountID(sfAuthorize) ==
hookCtx.result.account))
{
found_auth = true;
break;
}
}
if (found_auth)
break;
}
if (!found_auth)
{
// hook only gets one attempt
hookCtx.result.foreignStateSetDisabled = true;
return NOT_AUTHORIZED;
}
if (int64_t ret = set_state_cache(hookCtx, acc, ns, key, data, true);
ret < 0)
return ret;
return read_len;
}
// update or create a hook state object
// read_ptr = data to set, kread_ptr = key
// RH NOTE passing 0 size causes a delete operation which is as-intended
@@ -1935,110 +2068,71 @@ DEFINE_WASM_FUNCTION(
ripple::Blob data{memory + read_ptr, memory + read_ptr + read_len};
// local modifications are always allowed
if (aread_len == 0 || acc == hookCtx.result.account)
{
if (int64_t ret = set_state_cache(hookCtx, acc, ns, *key, data, true);
ret < 0)
return ret;
return __state_foreign_set(hookCtx, applyCtx, j, data, *key, ns, acc);
return read_len;
}
// execution to here means it's actually a foreign set
if (hookCtx.result.foreignStateSetDisabled)
return PREVIOUS_FAILURE_PREVENTS_RETRY;
// first check if we've already modified this state
auto cacheEntry = lookup_state_cache(hookCtx, acc, ns, *key);
if (cacheEntry && cacheEntry->get().first)
{
// if a cache entry already exists and it has already been modified
// don't check grants again
if (int64_t ret = set_state_cache(hookCtx, acc, ns, *key, data, true);
ret < 0)
return ret;
return read_len;
}
// cache miss or cache was present but entry was not marked as previously
// modified therefore before continuing we need to check grants
auto const sle = view.read(ripple::keylet::hook(acc));
if (!sle)
return INTERNAL_ERROR;
bool found_auth = false;
// we do this by iterating the hooks installed on the foreign account and in
// turn their grants and namespaces
auto const& hooks = sle->getFieldArray(sfHooks);
for (auto const& hookObj : hooks)
{
// skip blank entries
if (!hookObj.isFieldPresent(sfHookHash))
continue;
if (!hookObj.isFieldPresent(sfHookGrants))
continue;
auto const& hookGrants = hookObj.getFieldArray(sfHookGrants);
if (hookGrants.size() < 1)
continue;
// the grant allows the hook to modify the granter's namespace only
if (hookObj.isFieldPresent(sfHookNamespace))
{
if (hookObj.getFieldH256(sfHookNamespace) != ns)
continue;
}
else
{
// fetch the hook definition
auto const def = view.read(ripple::keylet::hookDefinition(
hookObj.getFieldH256(sfHookHash)));
if (!def) // should never happen except in a rare race condition
continue;
if (def->getFieldH256(sfHookNamespace) != ns)
continue;
}
// this is expensive search so we'll disallow after one failed attempt
for (auto const& hookGrantObj : hookGrants)
{
bool hasAuthorizedField = hookGrantObj.isFieldPresent(sfAuthorize);
if (hookGrantObj.getFieldH256(sfHookHash) ==
hookCtx.result.hookHash &&
(!hasAuthorizedField ||
hookGrantObj.getAccountID(sfAuthorize) ==
hookCtx.result.account))
{
found_auth = true;
break;
}
}
if (found_auth)
break;
}
if (!found_auth)
{
// hook only gets one attempt
hookCtx.result.foreignStateSetDisabled = true;
return NOT_AUTHORIZED;
}
if (int64_t ret = set_state_cache(hookCtx, acc, ns, *key, data, true);
ret < 0)
return ret;
return read_len;
WASM_HOOK_TEARDOWN();
}
DEFINE_JS_FUNCTION(
JSValue,
state_foreign_set,
JSValue raw_val,
JSValue raw_key,
JSValue raw_ns,
JSValue raw_acc)
{
JS_HOOK_SETUP();
auto val = FromJSIntArrayOrHexString(ctx, raw_val, hook::maxHookStateDataSize());
auto key_in = FromJSIntArrayOrHexString(ctx, raw_key, 32);
auto ns_in = FromJSIntArrayOrHexString(ctx, raw_ns, 32);
auto acc_in = FromJSIntArrayOrHexString(ctx, raw_acc, 20);
if (!val.has_value() && !JS_IsUndefined(raw_val))
returnJS(INVALID_ARGUMENT);
if (!ns_in.has_value() && !JS_IsUndefined(raw_ns))
returnJS(INVALID_ARGUMENT);
if (!acc_in.has_value() && !JS_IsUndefined(raw_acc))
returnJS(INVALID_ARGUMENT);
// val may be populated and empty, this is a delete operation...
if (!key_in.has_value() || key_in->empty())
returnJS(INVALID_ARGUMENT);
if (ns_in.has_value() && ns_in->size() != 32)
returnJS(INVALID_ARGUMENT);
if (acc_in.has_value() && acc_in->size() != 20)
returnJS(INVALID_ARGUMENT);
uint256 ns = ns_in.has_value()
? uint256::fromVoid(ns_in->data())
: hookCtx.result.hookNamespace;
AccountID acc = acc_in.has_value()
? AccountID::fromVoid(acc_in->data())
: hookCtx.result.account;
auto key = make_state_key(
std::string_view{(const char*)(key_in->data()), key_in->size()});
auto const sleAccount = view.peek(hookCtx.result.accountKeylet);
if (!sleAccount)
returnJS(tefINTERNAL);
if (!key)
returnJS(INTERNAL_ERROR);
returnJS(__state_foreign_set(hookCtx, applyCtx, j, *val, *key, ns, acc));
JS_HOOK_TEARDOWN();
}
ripple::TER
hook::finalizeHookState(
HookStateMap const& stateMap,
@@ -2376,7 +2470,8 @@ DEFINE_WASM_FUNCTION(
uint256 ns = nread_len == 0
? hookCtx.result.hookNamespace
: ripple::base_uint<256>::fromVoid(memory + nread_ptr);
: ripple::base_uint<256>::fromVoid(memory + nread_ptr);
ripple::AccountID acc = is_foreign ? AccountID::fromVoid(memory + aread_ptr)
: hookCtx.result.account;
@@ -2418,6 +2513,99 @@ DEFINE_WASM_FUNCTION(
WASM_HOOK_TEARDOWN();
}
DEFINE_JS_FUNCTION(
JSValue,
state_foreign,
JSValue raw_key,
JSValue raw_ns,
JSValue raw_accid)
{
JS_HOOK_SETUP();
auto key_in = FromJSIntArrayOrHexString(ctx, raw_key, 32);
auto ns_in = FromJSIntArrayOrHexString(ctx, raw_ns, 32);
auto accid_in = FromJSIntArrayOrHexString(ctx, raw_accid, 20);
if (!key_in.has_value() || key_in->empty())
returnJS(INVALID_ARGUMENT);
// RH TODO: enhance this check to only allow undefined or false or array or hexstring
if (ns_in.has_value() && ns_in->size() != 32)
returnJS(INVALID_ARGUMENT);
if (accid_in.has_value() && accid_in->size() != 20)
returnJS(INVALID_ARGUMENT);
uint256 ns = ns_in.has_value()
? uint256::fromVoid(ns_in->data())
: hookCtx.result.hookNamespace;
AccountID acc = accid_in.has_value()
? AccountID::fromVoid(accid_in->data())
: hookCtx.result.account;
auto key = make_state_key(
std::string_view{(const char*)(key_in->data()), key_in->size()});
if (!key.has_value())
returnJS(INVALID_ARGUMENT);
// first check if the requested state was previously cached this session
auto cacheEntryLookup = lookup_state_cache(hookCtx, acc, ns, *key);
if (cacheEntryLookup)
{
auto const& cacheEntry = cacheEntryLookup->get();
auto out = ToJSIntArray(ctx, cacheEntry.second);
if (!out)
returnJS(INTERNAL_ERROR);
return *out;
}
auto hsSLE = view.peek(keylet::hookState(acc, *key, ns));
if (!hsSLE)
returnJS(DOESNT_EXIST);
Blob b = hsSLE->getFieldVL(sfHookStateData);
// it exists add it to cache and return it
if (set_state_cache(hookCtx, acc, ns, *key, b, false) < 0)
returnJS(INTERNAL_ERROR); // should never happen
auto out = ToJSIntArray(ctx, b);
if (!out)
returnJS(INTERNAL_ERROR);
return *out;
JS_HOOK_TEARDOWN();
}
/* Retrieve the state into write_ptr identified by the key in kread_ptr */
DEFINE_JS_FUNCTION(
JSValue,
state,
JSValue key)
{
JS_HOOK_SETUP();
JSValueConst argv2[] = {
argv[0],
JS_UNDEFINED,
JS_UNDEFINED
};
return FORWARD_JS_FUNCTION_CALL(state_foreign, 3, argv2);
JS_HOOK_TEARDOWN();
}
// Cause the originating transaction to go through, save state changes and emit
// emitted tx, exit hook
DEFINE_WASM_FUNCTION(