increase size of hook state to 256 bytes, parameters too, change the reserve requirement to 1:1 for hook state, allow weak execution on rollback if hook_again was specified, add further state_set tests

This commit is contained in:
Richard Holland
2022-12-11 13:03:08 +00:00
parent 90949b8a59
commit e4aa7db5cb
7 changed files with 159 additions and 22 deletions

View File

@@ -136,12 +136,6 @@
WasmEdge_String hook_api::WasmFunctionName##F = WasmEdge_StringCreateByCString(#F);\
R hook_api::F(hook::HookContext& hookCtx, WasmEdge_MemoryInstanceContext& memoryCtx)
#define COMPUTE_HOOK_DATA_OWNER_COUNT(state_count)\
(std::ceil( (double)state_count/(double)5.0 ))
#define HOOK_SETUP()\
[[maybe_unused]] ApplyContext& applyCtx = hookCtx.applyCtx;\
[[maybe_unused]] auto& view = applyCtx.view();\

View File

@@ -262,6 +262,9 @@ namespace hook
uint32_t maxHookChainLength(void);
uint32_t computeHookStateOwnerCount(uint32_t hookStateCount);
int64_t computeExecutionFee(uint64_t instructionCount);
int64_t computeCreationFee(uint64_t byteCount);

View File

@@ -556,6 +556,13 @@ get_free_slot(hook::HookContext& hookCtx)
return slot_into;
}
uint32_t
hook::
computeHookStateOwnerCount(uint32_t hookStateCount)
{
return hookStateCount;
}
inline int64_t
serialize_keylet(
ripple::Keylet& kl,
@@ -590,7 +597,7 @@ unserialize_keylet(uint8_t* ptr, uint32_t len)
// RH TODO: this is used by sethook to determine the value stored in ltHOOK
// replace this with votable value
uint32_t hook::maxHookStateDataSize(void) {
return 128;
return 256U;
}
uint32_t hook::maxHookWasmSize(void)
@@ -604,7 +611,7 @@ uint32_t hook::maxHookParameterKeySize(void)
}
uint32_t hook::maxHookParameterValueSize(void)
{
return 128;
return 256;
}
bool hook::isEmittedTxn(ripple::STTx const& tx)
@@ -775,7 +782,7 @@ hook::setHookState(
auto hookStateDirKeylet = ripple::keylet::hookStateDir(acc, ns);
uint32_t stateCount = sleAccount->getFieldU32(sfHookStateCount);
uint32_t oldStateReserve = COMPUTE_HOOK_DATA_OWNER_COUNT(stateCount);
uint32_t oldStateReserve = computeHookStateOwnerCount(stateCount);
auto hookState = view.peek(hookStateKeylet);
@@ -806,7 +813,7 @@ hook::setHookState(
--stateCount; // guard this because in the "impossible" event it is already 0 we'll wrap back to int_max
// if removing this state entry would destroy the allotment then reduce the owner count
if (COMPUTE_HOOK_DATA_OWNER_COUNT(stateCount) < oldStateReserve)
if (computeHookStateOwnerCount(stateCount) < oldStateReserve)
adjustOwnerCount(view, sleAccount, -1, j);
sleAccount->setFieldU32(sfHookStateCount, stateCount);
@@ -838,7 +845,7 @@ hook::setHookState(
++stateCount;
if (COMPUTE_HOOK_DATA_OWNER_COUNT(stateCount) > oldStateReserve)
if (computeHookStateOwnerCount(stateCount) > oldStateReserve)
{
// the hook used its allocated allotment of state entries for its previous ownercount
// increment ownercount and give it another allotment

View File

@@ -1538,7 +1538,7 @@ Transactor::operator()()
for (auto& hookResult: hookResults)
{
hook::finalizeHookResult(hookResult, ctx_, isTesSuccess(result));
if (isTesSuccess(result) && hookResult.executeAgainAsWeak)
if (hookResult.executeAgainAsWeak)
{
if (aawMap.find(hookResult.account) == aawMap.end())
aawMap[hookResult.account] = {hookResult.hookHash};

View File

@@ -300,11 +300,11 @@ public:
params[0U][jss::HookParameter][jss::HookParameterName] =
strHex(std::string(32, 'a'));
params[0U][jss::HookParameter][jss::HookParameterValue] =
strHex(std::string(129, 'a'));
strHex(std::string(257, 'a'));
iv[jss::HookParameters] = params;
jv[jss::Hooks][0U][jss::Hook] = iv;
env(jv,
M("HSO must must not contain parameter values longer than 128 "
M("HSO must must not contain parameter values longer than 256 "
"bytes"),
HSFEE,
ter(temMALFORMED));
@@ -5227,8 +5227,18 @@ public:
auto const bob = Account{"bob"};
auto const alice = Account{"alice"};
auto const cho = Account{"cho"};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
env.fund(XRP(10000), cho);
// install a rollback hook on cho
env(ripple::test::jtx::hook(cho, {{hso(rollback_wasm, overrideFlag)}}, 0),
M("set state_set rollback"),
HSFEE);
env.close();
auto const aliceid = Account("alice").id();
@@ -5241,6 +5251,8 @@ public:
auto const state1 = env.le(ripple::keylet::hookState(aliceid, beast::zero, beast::zero));
BEAST_REQUIRE(!state1);
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 0);
}
// first hook will set two state objects with different keys and data on alice
@@ -5300,7 +5312,7 @@ public:
ASSERT(state_set(0,1000000, 0, 32) == OUT_OF_BOUNDS);
ASSERT(state_set(1000000, 0, 0, 32) == OUT_OF_BOUNDS);
ASSERT(state_set(0, 129, 0, 32) == TOO_BIG);
ASSERT(state_set(0, 257, 0, 32) == TOO_BIG);
}
@@ -5345,8 +5357,21 @@ public:
M("set state_set 1"),
HSFEE);
env.close();
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
// invoke the hook with cho (rollback after alice's hooks have executed)
env(pay(alice, cho, XRP(1)), M("test state_set 1 rollback"), fee(XRP(1)), ter(tecHOOK_REJECTED));
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
// invoke the hook
auto const nsdir = env.le(nsdirkl);
BEAST_EXPECT(!nsdir);
auto const state1 = env.le(ripple::keylet::hookState(aliceid, beast::zero, beast::zero));
BEAST_EXPECT(!state1);
// invoke the hook from bob to alice, this will work
env(pay(bob, alice, XRP(1)), M("test state_set 1"), fee(XRP(1)));
env.close();
}
@@ -5354,6 +5379,9 @@ public:
// check that the state object and namespace exists
{
// owner count should be 1 hook + 2 state objects == 3
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 3);
auto const nsdir = env.le(nsdirkl);
BEAST_REQUIRE(!!nsdir);
@@ -5517,6 +5545,8 @@ public:
HSFEE);
env.close();
// two hooks + two state objects = 4
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 4);
// this hook will be installed on bob, and it will verify the newly updated state
// is also available on his side. caution must be taken because bob's hooks will execute
@@ -5587,6 +5617,11 @@ public:
M("set state_set 3"),
HSFEE);
env.close();
// invoke the hook with cho (rollback after alice's hooks have executed)
env(pay(alice, cho, XRP(1)), M("test state_set 3 rollback"), fee(XRP(1)), ter(tecHOOK_REJECTED));
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 4);
// invoke the hook
env(pay(alice, bob, XRP(1)), M("test state_set 3"), fee(XRP(1)));
@@ -5597,6 +5632,10 @@ public:
// check that the updates have been made
{
// two hooks + one state == 3
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 3);
BEAST_EXPECT((*env.le("bob"))[sfOwnerCount] == 1);
auto const nsdir = env.le(nsdirkl);
BEAST_REQUIRE(!!nsdir);
@@ -5633,16 +5672,92 @@ public:
}
// create a hook state inside the weak side of an execution, while the strong side is rolled back
{
TestHook hook = wasm[R"[test.hook](
#include <stdint.h>
extern int32_t _g (uint32_t id, uint32_t maxiter);
#define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1)
extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t state_set (
uint32_t read_ptr,
uint32_t read_len,
uint32_t kread_ptr,
uint32_t kread_len
);
extern int64_t hook_again(void);
#define ASSERT(x)\
if (!(x))\
rollback((uint32_t)#x, sizeof(#x), __LINE__);
#define SBUF(x) (uint32_t)(x), sizeof(x)
int64_t hook(uint32_t reserved )
{
_g(1,1);
hook_again();
// create state
{
uint8_t data[16] =
{
0xFFU,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,2
};
uint8_t ff = 0xFFU;
ASSERT(state_set(SBUF(data), &ff, 1) == sizeof(data));
}
accept(0,0,0);
}
)[test.hook]"];
// install the hook on alice, deleting the other hook
env(ripple::test::jtx::hook(alice, {{
{hso(hook, overrideFlag)},
{},
{},
{hso_delete()}}}, 0),
M("set state_set 4"),
HSFEE);
env.close();
// invoke from alice to cho, this will cause a rollback, however the hook state should still be updated
// because the hook specified hook_again, and in the second weak execution the hook is allowed to
// set state
env(pay(alice, cho, XRP(1)), M("test state_set 4 rollback"), fee(XRP(1)), ter(tecHOOK_REJECTED));
uint8_t key[32] =
{
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0xFFU
};
auto const state = env.le(ripple::keylet::hookState(aliceid, uint256::fromVoid(key), beast::zero));
BEAST_EXPECT(state);
// one hook + two state objects == 3
// BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 3);
}
// todo:
// check state not set after unsuccessful chain execution
// check state can be set on emit callback
// check state can be set on weak execution
// check state can be set on weak execution after strong execution
// check namespacing provides for non-collision of same key
// check state persistance between hook install and uninstall
// check reserve - cant make new state object if reserve insufficient
// try creating many new state objects
// check namespacing provides for non-collision of same key
}

View File

@@ -34,6 +34,9 @@ hook(Account const& account, std::optional<std::vector<Json::Value>> hooks, std:
Json::Value
hso(std::vector<uint8_t> const& wasmBytes, void (*f)(Json::Value& jv) = 0);
Json::Value
hso_delete(void (*f)(Json::Value& jv) = 0);
std::string uint64_hex(uint64_t x);
} // namespace jtx

View File

@@ -21,6 +21,7 @@
#include <ripple/protocol/jss.h>
#include <stdexcept>
#include <test/jtx/hook.h>
#include <ripple/app/hook/Enum.h>
namespace ripple {
namespace test {
@@ -65,9 +66,23 @@ std::string uint64_hex(uint64_t x)
nibble(x >> 4U) + nibble(x >> 0U);
}
Json::Value
hso_delete(void (*f)(Json::Value& jv))
{
Json::Value jv;
jv[jss::CreateCode] = "";
jv[jss::Flags] = hsfOVERRIDE;
if (f)
f(jv);
return jv;
}
Json::Value
hso(std::vector<uint8_t> const& wasmBytes, void (*f)(Json::Value& jv))
{
if (wasmBytes.size() == 0)
throw std::runtime_error("empty hook wasm passed to hso()");
@@ -78,12 +93,12 @@ hso(std::vector<uint8_t> const& wasmBytes, void (*f)(Json::Value& jv))
jv[jss::HookNamespace] = to_string(uint256{beast::zero});
jv[jss::HookApiVersion] = Json::Value{0};
}
if (f)
if (f)
f(jv);
return jv;
}
} // namespace jtx