mirror of
https://github.com/Xahau/xahaud.git
synced 2026-07-22 22:50:08 +00:00
Compare commits
7 Commits
json-tx
...
fixhookmap
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87bbf5efc0 | ||
|
|
888d35b8dc | ||
|
|
7a0059ae80 | ||
|
|
d58cddf554 | ||
|
|
632df895d4 | ||
|
|
e9a2e71124 | ||
|
|
1b6da60399 |
@@ -34,6 +34,7 @@
|
|||||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||||
// in include/xrpl/protocol/Feature.h.
|
// in include/xrpl/protocol/Feature.h.
|
||||||
|
|
||||||
|
XRPL_FIX (HookMap, Supported::yes, VoteBehavior::DefaultYes)
|
||||||
XRPL_FIX (GuardDepth32, Supported::yes, VoteBehavior::DefaultNo)
|
XRPL_FIX (GuardDepth32, Supported::yes, VoteBehavior::DefaultNo)
|
||||||
XRPL_FEATURE(NamedHooks, Supported::yes, VoteBehavior::DefaultNo)
|
XRPL_FEATURE(NamedHooks, Supported::yes, VoteBehavior::DefaultNo)
|
||||||
XRPL_FEATURE(IOURewardClaim, Supported::yes, VoteBehavior::DefaultNo)
|
XRPL_FEATURE(IOURewardClaim, Supported::yes, VoteBehavior::DefaultNo)
|
||||||
|
|||||||
@@ -3632,8 +3632,9 @@ public:
|
|||||||
|
|
||||||
auto const alice = Account{"alice"};
|
auto const alice = Account{"alice"};
|
||||||
auto const bob = Account{"bob"};
|
auto const bob = Account{"bob"};
|
||||||
|
auto const claire = Account{"claire"};
|
||||||
Env env{*this, features};
|
Env env{*this, features};
|
||||||
env.fund(XRP(100000), alice, bob);
|
env.fund(XRP(100000), alice, bob, claire);
|
||||||
env.close();
|
env.close();
|
||||||
|
|
||||||
// Compute hook hash for the accept hook
|
// Compute hook hash for the accept hook
|
||||||
@@ -3773,6 +3774,84 @@ public:
|
|||||||
BEAST_EXPECT(result2.has_value());
|
BEAST_EXPECT(result2.has_value());
|
||||||
BEAST_EXPECT(result2.value() == newData.size());
|
BEAST_EXPECT(result2.value() == newData.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// fixHookMap: foreign state set without grant after state previous
|
||||||
|
// modified
|
||||||
|
|
||||||
|
HookStateMap stateMap;
|
||||||
|
auto hookCtx = makeStubHookContext(
|
||||||
|
applyCtx, alice.id(), bob.id(), {}, stateMap);
|
||||||
|
|
||||||
|
AccountID const aliceid = alice.id();
|
||||||
|
|
||||||
|
// Pre-populate stateMap
|
||||||
|
stateMap[alice.id()] = {
|
||||||
|
100, // availableForReserves
|
||||||
|
1, // namespaceCount
|
||||||
|
1, // hookStateScale
|
||||||
|
{}};
|
||||||
|
|
||||||
|
auto& api = hookCtx.api();
|
||||||
|
|
||||||
|
// setup a hook on alice, and on claire, no grants
|
||||||
|
env(hook(alice, {{hso(genesis::AcceptHook)}}, 0), fee(XRP(1)));
|
||||||
|
env(hook(claire, {{hso(genesis::AcceptHook)}}, 0), fee(XRP(1)));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// First modification
|
||||||
|
auto result1 =
|
||||||
|
api.state_foreign_set(testKey, testNs, aliceid, testData);
|
||||||
|
BEAST_EXPECT(result1.has_value());
|
||||||
|
|
||||||
|
// Second modification this time using bob as hookacc (should hit
|
||||||
|
// cache)
|
||||||
|
auto hookCtx2 = makeStubHookContext(
|
||||||
|
applyCtx, claire.id(), bob.id(), {}, stateMap);
|
||||||
|
|
||||||
|
// check the state entry is carried into the second context
|
||||||
|
|
||||||
|
// does the map contain the account?
|
||||||
|
BEAST_EXPECT(
|
||||||
|
hookCtx2.result.stateMap.find(aliceid) !=
|
||||||
|
hookCtx2.result.stateMap.end());
|
||||||
|
|
||||||
|
// the name space?
|
||||||
|
BEAST_EXPECT(
|
||||||
|
std::get<3>(hookCtx2.result.stateMap[aliceid]).find(testNs) !=
|
||||||
|
std::get<3>(hookCtx2.result.stateMap[aliceid]).end());
|
||||||
|
|
||||||
|
// the key entry?
|
||||||
|
BEAST_EXPECT(
|
||||||
|
std::get<3>(hookCtx2.result.stateMap[aliceid])[testNs].find(
|
||||||
|
testKey) !=
|
||||||
|
std::get<3>(hookCtx2.result.stateMap[aliceid])[testNs].end());
|
||||||
|
|
||||||
|
// is the entry marked as modified?
|
||||||
|
BEAST_EXPECT(
|
||||||
|
std::get<3>(hookCtx2.result.stateMap[aliceid])[testNs][testKey]
|
||||||
|
.first);
|
||||||
|
|
||||||
|
auto& api2 = hookCtx2.api();
|
||||||
|
Bytes newData{0x04, 0x05};
|
||||||
|
auto result2 =
|
||||||
|
api2.state_foreign_set(testKey, testNs, aliceid, newData);
|
||||||
|
|
||||||
|
if (features[fixHookMap])
|
||||||
|
{
|
||||||
|
// new behaviour: grant is missing, cannot write
|
||||||
|
BEAST_EXPECT(!result2.has_value());
|
||||||
|
BEAST_EXPECT(result2.error() == NOT_AUTHORIZED);
|
||||||
|
BEAST_EXPECT(hookCtx2.result.foreignStateSetDisabled);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// old behaviour: allow this illegal write due to the entry
|
||||||
|
// being modified previously in the map
|
||||||
|
BEAST_EXPECT(result2.has_value());
|
||||||
|
BEAST_EXPECT(result2.value() == newData.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -4832,6 +4911,7 @@ public:
|
|||||||
|
|
||||||
test_state(features);
|
test_state(features);
|
||||||
test_state_foreign(features);
|
test_state_foreign(features);
|
||||||
|
test_state_foreign_set(features - fixHookMap);
|
||||||
test_state_foreign_set(features);
|
test_state_foreign_set(features);
|
||||||
test_state_foreign_set_max(features);
|
test_state_foreign_set_max(features);
|
||||||
test_state_set(features);
|
test_state_set(features);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <wasmedge/wasmedge.h>
|
#include <wasmedge/wasmedge.h>
|
||||||
|
|
||||||
@@ -174,6 +175,8 @@ struct HookResult
|
|||||||
false; // hook_again allows strong pre-apply to nominate
|
false; // hook_again allows strong pre-apply to nominate
|
||||||
// additional weak post-apply execution
|
// additional weak post-apply execution
|
||||||
std::shared_ptr<STObject const> provisionalMeta;
|
std::shared_ptr<STObject const> provisionalMeta;
|
||||||
|
std::set<std::pair<AccountID, uint256 /* namespace */>>
|
||||||
|
foreignStateGrantCache; // add found grants here to avoid rechecking
|
||||||
};
|
};
|
||||||
|
|
||||||
class HookExecutor;
|
class HookExecutor;
|
||||||
|
|||||||
@@ -1920,88 +1920,114 @@ HookAPI::state_foreign_set(
|
|||||||
if (hookCtx.result.foreignStateSetDisabled)
|
if (hookCtx.result.foreignStateSetDisabled)
|
||||||
return Unexpected(PREVIOUS_FAILURE_PREVENTS_RETRY);
|
return Unexpected(PREVIOUS_FAILURE_PREVENTS_RETRY);
|
||||||
|
|
||||||
// first check if we've already modified this state
|
bool const hasFix = hookCtx.applyCtx.view().rules().enabled(fixHookMap);
|
||||||
auto cacheEntry = lookup_state_cache(account, 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 (auto ret = set_state_cache(account, ns, key, data, true);
|
|
||||||
!ret.has_value())
|
|
||||||
return Unexpected(ret.error());
|
|
||||||
|
|
||||||
return data.size();
|
if (!hasFix)
|
||||||
|
{
|
||||||
|
// first check if we've already modified this state
|
||||||
|
auto cacheEntry = lookup_state_cache(account, 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 (auto ret = set_state_cache(account, ns, key, data, true);
|
||||||
|
!ret.has_value())
|
||||||
|
return Unexpected(ret.error());
|
||||||
|
|
||||||
|
return data.size();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// check if we've used a grant to modify this state entry before, if not
|
||||||
// cache miss or cache was present but entry was not marked as previously
|
// look up possible grants
|
||||||
// modified therefore before continuing we need to check grants
|
if (!hasFix ||
|
||||||
auto const sle =
|
hookCtx.result.foreignStateGrantCache.find({account, ns}) ==
|
||||||
hookCtx.applyCtx.view().read(ripple::keylet::hook(account));
|
hookCtx.result.foreignStateGrantCache.end())
|
||||||
if (!sle)
|
|
||||||
return Unexpected(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
|
auto const sle =
|
||||||
if (!hookObj.isFieldPresent(sfHookHash))
|
hookCtx.applyCtx.view().read(ripple::keylet::hook(account));
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!hookObj.isFieldPresent(sfHookGrants))
|
if (!sle)
|
||||||
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)
|
if (hasFix)
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// fetch the hook definition
|
|
||||||
auto const def =
|
|
||||||
hookCtx.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;
|
hookCtx.result.foreignStateSetDisabled = true;
|
||||||
break;
|
return Unexpected(NOT_AUTHORIZED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Unexpected(INTERNAL_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (found_auth)
|
// RH TODO: test this code path more completely
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found_auth)
|
bool found_auth = false;
|
||||||
{
|
|
||||||
// hook only gets one attempt
|
// we do this by iterating the hooks installed on the foreign account
|
||||||
hookCtx.result.foreignStateSetDisabled = true;
|
// and in turn their grants and namespaces
|
||||||
return Unexpected(NOT_AUTHORIZED);
|
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 =
|
||||||
|
hookCtx.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 Unexpected(NOT_AUTHORIZED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the grant to the cache
|
||||||
|
hookCtx.result.foreignStateGrantCache.emplace(account, ns);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto ret = set_state_cache(account, ns, key, data, true);
|
if (auto ret = set_state_cache(account, ns, key, data, true);
|
||||||
|
|||||||
Reference in New Issue
Block a user