diff --git a/src/ripple/app/hook/Enum.h b/src/ripple/app/hook/Enum.h index 2acf37183..c38736890 100644 --- a/src/ripple/app/hook/Enum.h +++ b/src/ripple/app/hook/Enum.h @@ -1,3 +1,5 @@ +#include +#include #include #include #include @@ -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 @@ -43,8 +47,12 @@ maxHookParameterValueSize(void) } inline uint32_t -maxHookStateDataSize(void) +maxHookStateDataSize(Rules const& rules) { + if (rules.enabled(featureExtendedHookState)) + { + return 2048U; + } return 256U; } diff --git a/src/ripple/app/hook/applyHook.h b/src/ripple/app/hook/applyHook.h index c81514fe4..e30a577ca 100644 --- a/src/ripple/app/hook/applyHook.h +++ b/src/ripple/app/hook/applyHook.h @@ -461,7 +461,9 @@ apply( struct HookContext; uint32_t -computeHookStateOwnerCount(uint32_t hookStateCount); +computeHookStateOwnerCount(Blob hookStateData); +uint32_t +computeHookStateOwnerCount(Slice hookStateData); int64_t computeExecutionFee(uint64_t instructionCount); diff --git a/src/ripple/app/hook/impl/applyHook.cpp b/src/ripple/app/hook/impl/applyHook.cpp index abd7ef136..83a000162 100644 --- a/src/ripple/app/hook/impl/applyHook.cpp +++ b/src/ripple/app/hook/impl/applyHook.cpp @@ -853,9 +853,15 @@ parseCurrency(uint8_t* cu_ptr, uint32_t cu_len) } uint32_t -hook::computeHookStateOwnerCount(uint32_t hookStateCount) +hook::computeHookStateOwnerCount(Blob hookStateData) { - return hookStateCount; + return std::floor((hookStateData.size() - 1) / 256) + 1; +} + +uint32_t +hook::computeHookStateOwnerCount(Slice hookStateData) +{ + return std::floor((hookStateData.size() - 1) / 256) + 1; } inline int64_t @@ -1047,14 +1053,14 @@ 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 newHookStateCount = sleAccount->getFieldU32(sfHookStateCount); + uint32_t oldHookStateCount = newHookStateCount; auto hookState = view.peek(hookStateKeylet); @@ -1082,16 +1088,22 @@ hook::setHookState( view.erase(hookState); // adjust state object count - if (stateCount > 0) - --stateCount; // guard this because in the "impossible" event it is - // already 0 we'll wrap back to int_max - + Blob deletedStateData = hookState->getFieldVL(sfHookStateData); + uint32_t deleteOwnerCount = + computeHookStateOwnerCount(deletedStateData); + if (newHookStateCount >= deleteOwnerCount) + { + newHookStateCount -= + deleteOwnerCount; // 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 (newHookStateCount < oldHookStateCount) + adjustOwnerCount(view, sleAccount, -deleteOwnerCount, j); - sleAccount->setFieldU32(sfHookStateCount, stateCount); + sleAccount->setFieldU32(sfHookStateCount, newHookStateCount); if (nsDestroyed) hook::removeHookNamespaceEntry(*sleAccount, ns); @@ -1116,30 +1128,54 @@ hook::setHookState( if (createNew) { - ++stateCount; + uint32_t createdOwnerCount = computeHookStateOwnerCount(data); + newHookStateCount += createdOwnerCount; - if (computeHookStateOwnerCount(stateCount) > oldStateReserve) + if (newHookStateCount > oldHookStateCount) { // the hook used its allocated allotment of state entries for its // previous ownercount increment ownercount and give it another // allotment - ++ownerCount; + ownerCount += createdOwnerCount; XRPAmount const newReserve{view.fees().accountReserve(ownerCount)}; if (STAmount((*sleAccount)[sfBalance]).xrp() < newReserve) return tecINSUFFICIENT_RESERVE; - adjustOwnerCount(view, sleAccount, 1, j); + adjustOwnerCount(view, sleAccount, createdOwnerCount, j); } // update state count - sleAccount->setFieldU32(sfHookStateCount, stateCount); + sleAccount->setFieldU32(sfHookStateCount, newHookStateCount); view.update(sleAccount); // create an entry hookState = std::make_shared(hookStateKeylet); } + else + { + // on Update + uint32_t oldOwnerCount = + computeHookStateOwnerCount((*hookState)[sfHookStateData]); + uint32_t newOwnerCount = computeHookStateOwnerCount(data); + + uint32_t changedOwnerCount = newOwnerCount - oldOwnerCount; + newHookStateCount += changedOwnerCount; + + if (changedOwnerCount != 0) + { + ownerCount += changedOwnerCount; + XRPAmount const newReserve{view.fees().accountReserve(ownerCount)}; + if (STAmount((*sleAccount)[sfBalance]).xrp() < newReserve) + return tecINSUFFICIENT_RESERVE; + + adjustOwnerCount(view, sleAccount, changedOwnerCount, j); + + sleAccount->setFieldU32(sfHookStateCount, newHookStateCount); + view.update(sleAccount); + } + } hookState->setFieldVL(sfHookStateData, data); hookState->setFieldH256(sfHookStateKey, key); @@ -1634,7 +1670,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; diff --git a/src/ripple/app/tx/impl/SetHook.cpp b/src/ripple/app/tx/impl/SetHook.cpp index aac02753c..8fc87866f 100644 --- a/src/ripple/app/tx/impl/SetHook.cpp +++ b/src/ripple/app/tx/impl/SetHook.cpp @@ -877,6 +877,7 @@ SetHook::destroyNamespace( } while (cdirNext(view, dirKeylet.key, sleDirNode, uDirEntry, dirEntry)); + uint32_t toDeleteOwnerCount = 0; // delete it! for (auto const& itemKey : toDelete) { @@ -892,6 +893,9 @@ SetHook::destroyNamespace( continue; } + toDeleteOwnerCount += + hook::computeHookStateOwnerCount((*sleItem)[sfHookStateData]); + auto const hint = (*sleItem)[sfOwnerNode]; if (!view.dirRemove(dirKeylet, hint, itemKey, false)) { @@ -905,7 +909,7 @@ SetHook::destroyNamespace( view.erase(sleItem); } - uint32_t stateCount = oldStateCount - toDelete.size(); + uint32_t stateCount = oldStateCount - toDeleteOwnerCount; if (stateCount > oldStateCount) { JLOG(ctx.j.fatal()) << "HookSet(" << hook::log::NSDELETE_COUNT << ")[" @@ -921,7 +925,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); diff --git a/src/ripple/protocol/Feature.h b/src/ripple/protocol/Feature.h index 715f5dac6..574b71df0 100644 --- a/src/ripple/protocol/Feature.h +++ b/src/ripple/protocol/Feature.h @@ -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 = 75; +static constexpr std::size_t numFeatures = 76; /** Amendments that this server supports and the default voting behavior. Whether they are enabled depends on the Rules defined in the validated @@ -363,7 +363,7 @@ extern uint256 const fixPageCap; extern uint256 const fix240911; extern uint256 const fixFloatDivide; extern uint256 const fixReduceImport; - +extern uint256 const featureExtendedHookState; } // namespace ripple #endif diff --git a/src/ripple/protocol/impl/Feature.cpp b/src/ripple/protocol/impl/Feature.cpp index 1c7fc931b..8b6613bb9 100644 --- a/src/ripple/protocol/impl/Feature.cpp +++ b/src/ripple/protocol/impl/Feature.cpp @@ -469,6 +469,7 @@ REGISTER_FIX (fixPageCap, Supported::yes, VoteBehavior::De REGISTER_FIX (fix240911, Supported::yes, VoteBehavior::DefaultYes); REGISTER_FIX (fixFloatDivide, Supported::yes, VoteBehavior::DefaultYes); REGISTER_FIX (fixReduceImport, 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.