Skip to content

ExtendedHookState #406

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: dev
Choose a base branch
from
10 changes: 9 additions & 1 deletion src/ripple/app/hook/Enum.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Rules.h>
#include <map>
#include <set>
#include <string>
Expand Down Expand Up @@ -28,6 +30,8 @@ enum HookEmissionFlags : uint16_t {
};
} // namespace ripple

using namespace ripple;

namespace hook {
// RH TODO: put these somewhere better, and allow rules to be fed in
inline uint32_t
Expand All @@ -43,8 +47,12 @@ maxHookParameterValueSize(void)
}

inline uint32_t
maxHookStateDataSize(void)
maxHookStateDataSize(Rules const& rules)
{
if (rules.enabled(featureExtendedHookState))
{
return 2048U;
}
return 256U;
}

Expand Down
7 changes: 6 additions & 1 deletion src/ripple/app/hook/applyHook.h
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,12 @@ apply(
struct HookContext;

uint32_t
computeHookStateOwnerCount(uint32_t hookStateCount);
computeHookStateCount(uint32_t hookStateCount);

uint32_t
computeHookStateReserves(Blob hookStateData);
uint32_t
computeHookStateReserves(Slice hookStateData);

int64_t
computeExecutionFee(uint64_t instructionCount);
Expand Down
94 changes: 78 additions & 16 deletions src/ripple/app/hook/impl/applyHook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -853,11 +853,27 @@ parseCurrency(uint8_t* cu_ptr, uint32_t cu_len)
}

uint32_t
hook::computeHookStateOwnerCount(uint32_t hookStateCount)
hook::computeHookStateCount(uint32_t hookStateCount)
{
return hookStateCount;
}

uint32_t
hook::computeHookStateReserves(Blob hookStateData)
{
if (hookStateData.size() == 0)
return 1;
return std::floor((hookStateData.size() - 1) / 256) + 1;
}

uint32_t
hook::computeHookStateReserves(Slice hookStateData)
{
if (hookStateData.size() == 0)
return 1;
return std::floor((hookStateData.size() - 1) / 256) + 1;
}

inline int64_t
serialize_keylet(
ripple::Keylet& kl,
Expand Down Expand Up @@ -1047,19 +1063,24 @@ hook::setHookState(
return tefINTERNAL;

// if the blob is too large don't set it
if (data.size() > hook::maxHookStateDataSize())
if (data.size() > hook::maxHookStateDataSize(view.rules()))
return temHOOK_DATA_TOO_LARGE;

auto hookStateKeylet = ripple::keylet::hookState(acc, key, ns);
auto hookStateDirKeylet = ripple::keylet::hookStateDir(acc, ns);

uint32_t stateCount = sleAccount->getFieldU32(sfHookStateCount);
uint32_t oldStateReserve = computeHookStateOwnerCount(stateCount);
uint32_t oldStateCount = stateCount;

auto hookState = view.peek(hookStateKeylet);

bool createNew = !hookState;

int32_t newHookStateReserves = computeHookStateReserves(data);
int32_t oldHookStateReserves = createNew
? 0
: computeHookStateReserves(hookState->getFieldVL(sfHookStateData));

// if the blob is nil then delete the entry if it exists
if (data.empty())
{
Expand All @@ -1085,13 +1106,15 @@ hook::setHookState(
if (stateCount > 0)
--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 (computeHookStateOwnerCount(stateCount) < oldStateReserve)
adjustOwnerCount(view, sleAccount, -1, j);
if (stateCount < oldStateCount)
adjustOwnerCount(view, sleAccount, -oldHookStateReserves, j);

sleAccount->setFieldU32(sfHookStateCount, stateCount);
if (view.rules().enabled(featureExtendedHookState) && stateCount == 0)
sleAccount->makeFieldAbsent(sfHookStateCount);
else
sleAccount->setFieldU32(sfHookStateCount, stateCount);

if (nsDestroyed)
hook::removeHookNamespaceEntry(*sleAccount, ns);
Expand All @@ -1118,19 +1141,19 @@ hook::setHookState(
{
++stateCount;

if (computeHookStateOwnerCount(stateCount) > oldStateReserve)
if (stateCount > oldStateCount)
{
// the hook used its allocated allotment of state entries for its
// previous ownercount increment ownercount and give it another
// allotment

++ownerCount;
ownerCount += newHookStateReserves;
XRPAmount const newReserve{view.fees().accountReserve(ownerCount)};

if (STAmount((*sleAccount)[sfBalance]).xrp() < newReserve)
return tecINSUFFICIENT_RESERVE;

adjustOwnerCount(view, sleAccount, 1, j);
adjustOwnerCount(view, sleAccount, newHookStateReserves, j);
}

// update state count
Expand All @@ -1140,6 +1163,27 @@ hook::setHookState(
// create an entry
hookState = std::make_shared<SLE>(hookStateKeylet);
}
else
{
// on Update
uint32_t changedOwnerCount =
newHookStateReserves - oldHookStateReserves;

if (changedOwnerCount != 0)
{
ownerCount += changedOwnerCount;
XRPAmount const newReserve{view.fees().accountReserve(ownerCount)};
if (changedOwnerCount > 0 &&
STAmount((*sleAccount)[sfBalance]).xrp() < newReserve)
// only check if increasing reserve
return tecINSUFFICIENT_RESERVE;

adjustOwnerCount(view, sleAccount, changedOwnerCount, j);

sleAccount->setFieldU32(sfHookStateCount, stateCount);
view.update(sleAccount);
}
}

hookState->setFieldVL(sfHookStateData, data);
hookState->setFieldH256(sfHookStateKey, key);
Expand Down Expand Up @@ -1451,6 +1495,7 @@ set_state_cache(

if (stateMap.find(acc) == stateMap.end())
{
// new Account Key
// if this is the first time this account has been interacted with
// we will compute how many available reserve positions there are
auto const& fees = hookCtx.applyCtx.view().fees();
Expand All @@ -1472,7 +1517,8 @@ set_state_cache(

availableForReserves /= increment;

if (availableForReserves < 1 && modified)
if (availableForReserves < hook::computeHookStateReserves(data) &&
modified)
return RESERVE_INSUFFICIENT;

int64_t namespaceCount = accSLE->isFieldPresent(sfHookNamespaces)
Expand All @@ -1492,7 +1538,7 @@ set_state_cache(
stateMap.modified_entry_count++;

stateMap[acc] = {
availableForReserves - 1,
availableForReserves - hook::computeHookStateReserves(data),
namespaceCount,
{{ns, {{key, {modified, data}}}}}};
return 1;
Expand All @@ -1501,10 +1547,12 @@ set_state_cache(
auto& availableForReserves = std::get<0>(stateMap[acc]);
auto& namespaceCount = std::get<1>(stateMap[acc]);
auto& stateMapAcc = std::get<2>(stateMap[acc]);
bool const canReserveNew = availableForReserves > 0;
bool const canReserveNew =
availableForReserves >= hook::computeHookStateReserves(data);

if (stateMapAcc.find(ns) == stateMapAcc.end())
{
// new Namespace Key
if (modified)
{
if (!canReserveNew)
Expand All @@ -1522,7 +1570,7 @@ set_state_cache(
namespaceCount++;
}

availableForReserves--;
availableForReserves -= hook::computeHookStateReserves(data);
stateMap.modified_entry_count++;
}

Expand All @@ -1534,11 +1582,12 @@ set_state_cache(
auto& stateMapNs = stateMapAcc[ns];
if (stateMapNs.find(key) == stateMapNs.end())
{
// new State Key
if (modified)
{
if (!canReserveNew)
return RESERVE_INSUFFICIENT;
availableForReserves--;
availableForReserves -= hook::computeHookStateReserves(data);
stateMap.modified_entry_count++;
}

Expand All @@ -1547,11 +1596,24 @@ set_state_cache(
return 1;
}

// existing State Key
auto const newReserve = hook::computeHookStateReserves(data);
auto const oldReserve =
hook::computeHookStateReserves(stateMapNs[key].second);
bool const canReserveUpdate = newReserve <= oldReserve ||
availableForReserves >= newReserve - oldReserve;

if (modified)
{
if (!canReserveUpdate)
return RESERVE_INSUFFICIENT;

if (!stateMapNs[key].first)
hookCtx.result.changedStateCount++;

if (view.rules().enabled(featureExtendedHookState))
availableForReserves -= newReserve - oldReserve;

stateMap.modified_entry_count++;
stateMapNs[key].first = true;
}
Expand Down Expand Up @@ -1635,7 +1697,7 @@ DEFINE_HOOK_FUNCTION(
(aread_len && NOT_IN_BOUNDS(aread_ptr, aread_len, memory_length)))
return OUT_OF_BOUNDS;

uint32_t maxSize = hook::maxHookStateDataSize();
uint32_t maxSize = hook::maxHookStateDataSize(view.rules());
if (read_len > maxSize)
return TOO_BIG;

Expand Down
13 changes: 9 additions & 4 deletions src/ripple/app/tx/impl/SetHook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,7 @@ SetHook::destroyNamespace(

} while (cdirNext(view, dirKeylet.key, sleDirNode, uDirEntry, dirEntry));

uint32_t toDeleteOwnerCount = 0;
// delete it!
for (auto const& itemKey : toDelete)
{
Expand All @@ -893,6 +894,9 @@ SetHook::destroyNamespace(
continue;
}

toDeleteOwnerCount +=
hook::computeHookStateReserves((*sleItem)[sfHookStateData]);

auto const hint = (*sleItem)[sfOwnerNode];
if (!view.dirRemove(dirKeylet, hint, itemKey, false))
{
Expand Down Expand Up @@ -922,7 +926,7 @@ SetHook::destroyNamespace(
sleAccount->setFieldU32(sfHookStateCount, stateCount);

if (ctx.rules.enabled(fixNSDelete))
adjustOwnerCount(view, sleAccount, -toDelete.size(), ctx.j);
adjustOwnerCount(view, sleAccount, -toDeleteOwnerCount, ctx.j);

if (!partialDelete && sleAccount->isFieldPresent(sfHookNamespaces))
hook::removeHookNamespaceEntry(*sleAccount, ns);
Expand Down Expand Up @@ -1315,13 +1319,14 @@ SetHook::setHook()
}
else if (op == hsoNSDELETE && newDirKeylet)
{
printf("Marking a namespace for destruction.... NSDELETE\n");
JLOG(ctx.j.trace())
<< "Marking a namespace for destruction.... NSDELETE";
namespacesToDestroy.emplace(*newNamespace);
}
else if (oldDirKeylet)
{
printf(
"Marking a namespace for destruction.... non-NSDELETE\n");
JLOG(ctx.j.trace())
<< "Marking a namespace for destruction.... non-NSDELETE";
namespacesToDestroy.emplace(*oldNamespace);
}
else
Expand Down
3 changes: 2 additions & 1 deletion src/ripple/protocol/Feature.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ namespace detail {
// Feature.cpp. Because it's only used to reserve storage, and determine how
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
// the actual number of amendments. A LogicError on startup will verify this.
static constexpr std::size_t numFeatures = 78;
static constexpr std::size_t numFeatures = 79;

/** Amendments that this server supports and the default voting behavior.
Whether they are enabled depends on the Rules defined in the validated
Expand Down Expand Up @@ -366,6 +366,7 @@ extern uint256 const featureTouch;
extern uint256 const fixReduceImport;
extern uint256 const fixXahauV3;
extern uint256 const fix20250131;
extern uint256 const featureExtendedHookState;

} // namespace ripple

Expand Down
1 change: 1 addition & 0 deletions src/ripple/protocol/impl/Feature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,7 @@ REGISTER_FEATURE(Touch, Supported::yes, VoteBehavior::De
REGISTER_FIX (fixReduceImport, Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX (fixXahauV3, Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX (fix20250131, Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(ExtendedHookState, Supported::yes, VoteBehavior::DefaultNo);

// The following amendments are obsolete, but must remain supported
// because they could potentially get enabled.
Expand Down
Loading
Loading