Named Hook (#718)

This commit is contained in:
tequ
2026-05-19 11:00:25 +09:00
committed by GitHub
parent 8422758d4d
commit 663ed4edb8
11 changed files with 792 additions and 12 deletions

View File

@@ -221,6 +221,7 @@
#define sfProvider ((7U << 16U) + 30U)
#define sfMPTokenMetadata ((7U << 16U) + 31U)
#define sfCredentialType ((7U << 16U) + 32U)
#define sfHookName ((7U << 16U) + 97U)
#define sfRemarkValue ((7U << 16U) + 98U)
#define sfRemarkName ((7U << 16U) + 99U)
#define sfAccount ((8U << 16U) + 1U)

View File

@@ -34,6 +34,7 @@
// If you add an amendment here, then do not forget to increment `numFeatures`
// in include/xrpl/protocol/Feature.h.
XRPL_FEATURE(NamedHooks, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(IOURewardClaim, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (IOULockedBalanceInvariant, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (ImportIssuer, Supported::yes, VoteBehavior::DefaultYes)

View File

@@ -293,6 +293,7 @@ TYPED_SFIELD(sfAssetClass, VL, 29)
TYPED_SFIELD(sfProvider, VL, 30)
TYPED_SFIELD(sfMPTokenMetadata, VL, 31)
TYPED_SFIELD(sfCredentialType, VL, 32)
TYPED_SFIELD(sfHookName, VL, 97)
TYPED_SFIELD(sfRemarkValue, VL, 98)
TYPED_SFIELD(sfRemarkName, VL, 99)

View File

@@ -76,6 +76,7 @@ JSS(Holder); // field.
JSS(HookApiVersion); // field
JSS(HookCanEmit); // field
JSS(HookHash); // field
JSS(HookName); // field
JSS(HookNamespace); // field
JSS(HookOn); // field
JSS(HookOnIncoming); // field

View File

@@ -99,6 +99,7 @@ InnerObjectFormats::InnerObjectFormats()
{sfHookOnOutgoing, soeOPTIONAL},
{sfHookCanEmit, soeOPTIONAL},
{sfHookApiVersion, soeOPTIONAL},
{sfHookName, soeOPTIONAL},
{sfFlags, soeOPTIONAL}});
add(sfHookGrant.jsonName,

View File

@@ -48,6 +48,7 @@ TxFormats::TxFormats()
{sfFirstLedgerSequence, soeOPTIONAL},
{sfNetworkID, soeOPTIONAL},
{sfHookParameters, soeOPTIONAL},
{sfHookName, soeOPTIONAL},
};
#pragma push_macro("UNWRAP")

View File

@@ -690,6 +690,8 @@ public:
bool const hasHookCanEmit =
env.current()->rules().enabled(featureHookCanEmit);
bool const hasNamedHooks =
env.current()->rules().enabled(featureNamedHooks);
auto const alice = Account{"alice"};
env.fund(XRP(10000), alice);
@@ -726,7 +728,8 @@ public:
}
// grants, parameters, hookon, hookonincoming, hookonoutgoing,
// hookcanemit, hookapiversion, hooknamespace keys must be absent
// hookcanemit, hookapiversion, hooknamespace, hookname keys must be
// absent
for (auto const& [key, value] : JSSMap{
{jss::HookGrants, Json::arrayValue},
{jss::HookParameters, Json::arrayValue},
@@ -743,11 +746,16 @@ public:
"000000000000000000000000000000000000000000000000000000000000"
"0000"},
{jss::HookApiVersion, "0"},
{jss::HookNamespace, to_string(uint256{beast::zero})}})
{jss::HookNamespace, to_string(uint256{beast::zero})},
{jss::HookName, strHex(std::string{"DEADBEEF"})},
})
{
if (!hasHookCanEmit && key == jss::HookCanEmit)
continue;
if (!hasNamedHooks && key == jss::HookName)
continue;
Json::Value iv;
iv[jss::CreateCode] = "";
iv[key] = value;
@@ -755,7 +763,7 @@ public:
env(jv,
M("Hook DELETE operation cannot include: grants, params, "
"hookon, HookOnIncoming, HookOnOutgoing, hookcanemit, "
"apiversion, namespace"),
"apiversion, namespace, hookname"),
HSFEE,
ter(temMALFORMED));
env.close();
@@ -894,6 +902,8 @@ public:
bool const fixNS = env.current()->rules().enabled(fixNSDelete);
bool const hasHookCanEmit =
env.current()->rules().enabled(featureHookCanEmit);
bool const hasNamedHooks =
env.current()->rules().enabled(featureNamedHooks);
auto const alice = Account{"alice"};
env.fund(XRP(10000), alice);
@@ -926,11 +936,15 @@ public:
"000000000000000000000000000000000000000000000000000000000000"
"0000"},
{jss::HookApiVersion, "0"},
{jss::HookName, strHex(std::string{"DEADBEEF"})},
})
{
if (!hasHookCanEmit && key == jss::HookCanEmit)
continue;
if (!hasNamedHooks && key == jss::HookName)
continue;
Json::Value iv;
iv[key] = value;
iv[jss::Flags] = hsfNSDELETE;
@@ -939,7 +953,7 @@ public:
env(jv,
M("Hook NSDELETE operation cannot include: grants, params, "
"hookon, hookonincoming, hookonoutgoing, hookcanemit, "
"apiversion"),
"apiversion, hookname"),
HSFEE,
ter(temMALFORMED));
env.close();
@@ -1684,6 +1698,340 @@ public:
}
}
void
testHookName(FeatureBitset features)
{
testcase("Test hook name");
using namespace jtx;
auto const alice = Account{"alice"};
auto const bob = Account{"bob"};
auto const charlie = Account{"charlie"};
auto const USD = alice["USD"];
Env env{*this, features};
env.fund(XRP(10000), alice, bob);
env.close();
// Invalid hook name (length=3,17, not utf-8)
for (auto const name : {
"414243", // ABC (length=3)
"4142434445464748494A4B4C4D4E4F5051", // ABCDEFGHIJKLMNOPQ
// (length=17)
"DEADBEEF", // not utf-8
})
{
auto jvh = hso(accept_wasm);
jvh[jss::HookName] = name;
env(ripple::test::jtx::hook(alice, {{jvh}}, 0),
M("Hook name must be between 8 and 32 hex characters and be a "
"valid UTF-8 string"),
HSFEE,
features[featureNamedHooks] ? ter(temMALFORMED)
: ter(temDISABLED));
auto jvi = invoke::invoke(alice);
jvi[jss::HookName] = name;
env(jvi,
M("Call named hook with the invalid hook name"),
HSFEE,
ter(temMALFORMED));
}
if (!features[featureNamedHooks])
return;
{
/// Create, Install, Update named Hook
Env env{*this, features};
env.fund(XRP(10000), alice, bob, charlie);
env.close();
// Create with hook name
auto jvh = hso(accept_wasm);
jvh[jss::HookName] = "41424344";
env(ripple::test::jtx::hook(alice, {{jvh}}, 0),
M("Create named hook"),
HSFEE);
env.close();
// Check hook definition
{
auto const hookDef =
env.le(keylet::hookDefinition(accept_hash));
BEAST_EXPECT(hookDef);
BEAST_EXPECT(!hookDef->isFieldPresent(sfHookName));
}
// Check Hook
{
auto const hooks = env.le(keylet::hook(alice));
BEAST_EXPECT(hooks && hooks->isFieldPresent(sfHooks));
auto const& hooksArray = hooks->getFieldArray(sfHooks);
BEAST_EXPECT(hooksArray.size() == 1);
BEAST_EXPECT(hooksArray[0].isFieldPresent(sfHookName));
BEAST_EXPECT(
strHex(hooksArray[0].getFieldVL(sfHookName)) == "41424344");
}
// install with hook name
jvh[jss::HookName] = "41424344";
env(ripple::test::jtx::hook(bob, {{jvh}}, 0),
M("Install named hook"),
HSFEE);
env.close();
// Check Hook
{
auto const hooks = env.le(keylet::hook(bob));
BEAST_EXPECT(hooks && hooks->isFieldPresent(sfHooks));
auto const& hooksArray = hooks->getFieldArray(sfHooks);
BEAST_EXPECT(hooksArray.size() == 1);
BEAST_EXPECT(hooksArray[0].isFieldPresent(sfHookName));
BEAST_EXPECT(
strHex(hooksArray[0].getFieldVL(sfHookName)) == "41424344");
}
// install without hook name
jvh.removeMember(jss::HookName);
env(ripple::test::jtx::hook(charlie, {{jvh}}, 0),
M("Install non-named hook"),
HSFEE);
env.close();
// Check Hook
{
auto const hooks = env.le(keylet::hook(charlie));
BEAST_EXPECT(hooks && hooks->isFieldPresent(sfHooks));
auto const& hooksArray = hooks->getFieldArray(sfHooks);
BEAST_EXPECT(hooksArray.size() == 1);
BEAST_EXPECT(!hooksArray[0].isFieldPresent(sfHookName));
}
// Update named hook to non-named hook
jvh[jss::HookName] = "";
jvh[jss::Flags] = hsfOVERRIDE;
env(ripple::test::jtx::hook(alice, {{jvh}}, 0),
M("Update named hook to non-named hook"),
HSFEE);
env.close();
// Check Hook
{
auto const hooks = env.le(keylet::hook(alice));
BEAST_EXPECT(hooks && hooks->isFieldPresent(sfHooks));
auto const& hooksArray = hooks->getFieldArray(sfHooks);
BEAST_EXPECT(hooksArray.size() == 1);
BEAST_EXPECT(!hooksArray[0].isFieldPresent(sfHookName));
}
// Update named hook to named hook
jvh[jss::HookName] = "4142434445";
jvh[jss::Flags] = hsfOVERRIDE;
env(ripple::test::jtx::hook(bob, {{jvh}}, 0),
M("Update non-named hook to named hook"),
HSFEE);
env.close();
// Check Hook
{
auto const hooks = env.le(keylet::hook(bob));
BEAST_EXPECT(hooks && hooks->isFieldPresent(sfHooks));
auto const& hooksArray = hooks->getFieldArray(sfHooks);
BEAST_EXPECT(hooksArray.size() == 1);
BEAST_EXPECT(hooksArray[0].isFieldPresent(sfHookName));
BEAST_EXPECT(
strHex(hooksArray[0].getFieldVL(sfHookName)) ==
"4142434445");
}
// Update non-named hook to named hook
jvh[jss::HookName] = "41424344";
jvh[jss::Flags] = hsfOVERRIDE;
env(ripple::test::jtx::hook(charlie, {{jvh}}, 0),
M("Update non-named hook to named hook"),
HSFEE);
env.close();
// Check Hook
{
auto const hooks = env.le(keylet::hook(charlie));
BEAST_EXPECT(hooks && hooks->isFieldPresent(sfHooks));
auto const& hooksArray = hooks->getFieldArray(sfHooks);
BEAST_EXPECT(hooksArray.size() == 1);
BEAST_EXPECT(hooksArray[0].isFieldPresent(sfHookName));
BEAST_EXPECT(
strHex(hooksArray[0].getFieldVL(sfHookName)) == "41424344");
}
}
// Install named hook
auto jvh = hso(accept_wasm);
jvh[jss::HookName] = "41424344";
jvh[jss::Flags] = hsfCOLLECT;
auto jvh2 = hso(accept2_wasm);
jvh2[jss::Flags] = hsfCOLLECT;
env(ripple::test::jtx::hook(alice, {{jvh, jvh2}}, 0),
M("Install named hook"),
HSFEE);
env.close();
//
// Test Strong
//
// Call named hook without specifying the hook name
{
auto jv = invoke::invoke(alice);
auto expectedFee =
calculateBaseFee(*env.current(), *env.jt(jv).stx);
BEAST_EXPECT(expectedFee == drops(19));
env(jv,
M("Call named hook without specifying the hook name"),
HSFEE);
env.close();
// execute only non-named hook
auto const hookExecutions =
env.meta()->getFieldArray(sfHookExecutions);
BEAST_EXPECT(hookExecutions.size() == 1);
BEAST_EXPECT(
hookExecutions[0].getFieldH256(sfHookHash) == accept2_hash);
}
// Call named hook with the wrong hook name
{
auto jv = invoke::invoke(alice);
jv[jss::HookName] = "41424345";
auto expectedFee =
calculateBaseFee(*env.current(), *env.jt(jv).stx);
BEAST_EXPECT(expectedFee == drops(19));
env(jv, M("Call named hook with the wrong hook name"), HSFEE);
env.close();
// execute only non-named hook
auto const hookExecutions =
env.meta()->getFieldArray(sfHookExecutions);
BEAST_EXPECT(hookExecutions.size() == 1);
BEAST_EXPECT(
hookExecutions[0].getFieldH256(sfHookHash) == accept2_hash);
}
// Call named hook with the correct hook name
{
auto jv = invoke::invoke(alice);
jv[jss::HookName] = "41424344";
auto expectedFee =
calculateBaseFee(*env.current(), *env.jt(jv).stx);
BEAST_EXPECT(expectedFee == drops(28));
env(jv, M("Call named hook with the correct hook name"), HSFEE);
env.close();
// execute both named and non-named hooks
auto const hookExecutions =
env.meta()->getFieldArray(sfHookExecutions);
BEAST_EXPECT(hookExecutions.size() == 2);
BEAST_EXPECT(
hookExecutions[0].getFieldH256(sfHookHash) == accept_hash);
BEAST_EXPECT(
hookExecutions[1].getFieldH256(sfHookHash) == accept2_hash);
}
//
// Test Weak
//
env(fset(alice, asfTshCollect), fee(XRP(1)));
env.close();
// Call named hook without specifying the hook name
{
auto jv = trust(bob, USD(1000));
env(jv,
M("Call named hook without specifying the hook name"),
HSFEE);
env.close();
// execute only non-named hook
auto const hookExecutions =
env.meta()->getFieldArray(sfHookExecutions);
BEAST_EXPECT(hookExecutions.size() == 1);
BEAST_EXPECT(
hookExecutions[0].getFieldH256(sfHookHash) == accept2_hash);
}
// Call named hook with the wrong hook name
{
auto jv = trust(bob, USD(1000));
jv[jss::HookName] = "41424345";
env(jv, M("Call named hook with the wrong hook name"), HSFEE);
env.close();
// execute only non-named hook
auto const hookExecutions =
env.meta()->getFieldArray(sfHookExecutions);
BEAST_EXPECT(hookExecutions.size() == 1);
BEAST_EXPECT(
hookExecutions[0].getFieldH256(sfHookHash) == accept2_hash);
}
// Call named hook with the correct hook name
{
auto jv = invoke::invoke(alice);
jv[jss::HookName] = "41424344";
env(jv, M("Call named hook with the correct hook name"), HSFEE);
env.close();
// execute both named and non-named hooks
auto const hookExecutions =
env.meta()->getFieldArray(sfHookExecutions);
BEAST_EXPECT(hookExecutions.size() == 2);
BEAST_EXPECT(
hookExecutions[0].getFieldH256(sfHookHash) == accept_hash);
BEAST_EXPECT(
hookExecutions[1].getFieldH256(sfHookHash) == accept2_hash);
}
env(fclear(alice, asfTshCollect), fee(XRP(1)));
env.close();
//
// Callback Execution
//
jvh = hso(emit_invoke_wasm);
jvh[jss::HookName] = "41424344";
jvh[jss::Flags] = hsfOVERRIDE;
env(ripple::test::jtx::hook(alice, {{jvh}}, 0),
M("Install named callback hook"),
HSFEE);
env.close();
// Call named hook without specifying the hook name
{
auto jv = invoke::invoke(alice);
env(jv,
M("Call named hook without specifying the hook name"),
HSFEE);
env.close();
// execute only non-named hook
BEAST_EXPECT(!env.meta()->isFieldPresent(sfHookEmissions));
}
// Call named hook with the wrong hook name
{
auto jv = invoke::invoke(alice);
jv[jss::HookName] = "41424345";
env(jv, M("Call named hook with the wrong hook name"), HSFEE);
env.close();
// execute only non-named hook
BEAST_EXPECT(!env.meta()->isFieldPresent(sfHookEmissions));
}
// Call named hook with the correct hook name
{
auto jv = invoke::invoke(alice);
jv[jss::HookName] = "41424344";
env(jv, M("Call named hook with the correct hook name"), HSFEE);
env.close();
// execute both named and non-named hooks
BEAST_EXPECT(env.meta()->isFieldPresent(sfHookEmissions));
auto const hookEmissions =
env.meta()->getFieldArray(sfHookEmissions)[0];
auto const etxn = hookEmissions.getFieldH256(sfEmittedTxnID);
env.close();
auto const tx = env.closed()->txRead(etxn);
BEAST_EXPECT(tx.first && tx.second);
BEAST_EXPECT(!tx.first->isFieldPresent(sfHookName));
// Callback transaction doesn't need to have hook name
BEAST_EXPECT(tx.second->isFieldPresent(sfHookExecutions));
}
}
void
testFillCopy(FeatureBitset features)
{
@@ -1741,6 +2089,8 @@ public:
bool const hasHookCanEmit =
env.current()->rules().enabled(featureHookCanEmit);
bool const hasNamedHooks =
env.current()->rules().enabled(featureNamedHooks);
auto const bob = Account{"bob"};
env.fund(XRP(10000), bob);
@@ -1789,6 +2139,8 @@ public:
iv[jss::HookCanEmit] =
"0000000000000000000000000000000000000000000000000000000000"
"000000";
if (hasNamedHooks)
iv[jss::HookName] = strHex(std::string{"DEADBEEF"});
jv[jss::Hooks][0U] = Json::Value{};
jv[jss::Hooks][0U][jss::Hook] = iv;
@@ -1811,6 +2163,8 @@ public:
iv[jss::HookCanEmit] =
"0000000000000000000000000000000000000000000000000000000000"
"000000";
if (hasNamedHooks)
iv[jss::HookName] = strHex(std::string{"DEADBEEF"});
jv[jss::Hooks][0U] = Json::Value{};
jv[jss::Hooks][0U][jss::Hook] = iv;
@@ -1834,6 +2188,8 @@ public:
iv[jss::HookCanEmit] =
"0000000000000000000000000000000000000000000000000000000000"
"000000";
if (hasNamedHooks)
iv[jss::HookName] = strHex(std::string{"DEADBEEF"});
jv[jss::Hooks][0U] = Json::Value{};
jv[jss::Hooks][0U][jss::Hook] = iv;
@@ -1854,6 +2210,8 @@ public:
iv[jss::HookCanEmit] =
"0000000000000000000000000000000000000000000000000000000000"
"000000";
if (hasNamedHooks)
iv[jss::HookName] = strHex(std::string{"DEADBEEF"});
jv[jss::Hooks][0U] = Json::Value{};
jv[jss::Hooks][0U][jss::Hook] = iv;
@@ -2002,6 +2360,8 @@ public:
bool const hasHookCanEmit =
env.current()->rules().enabled(featureHookCanEmit);
bool const hasNamedHooks =
env.current()->rules().enabled(featureNamedHooks);
auto const alice = Account{"alice"};
env.fund(XRP(10000), alice);
@@ -2028,6 +2388,8 @@ public:
iv[jss::HookCanEmit] =
"0000000000000000000000000000000000000000000000000000000000"
"000000";
if (hasNamedHooks)
iv[jss::HookName] = strHex(std::string{"DEADBEEF"});
iv[jss::HookParameters] = Json::Value{Json::arrayValue};
iv[jss::HookParameters][0U] = Json::Value{};
iv[jss::HookParameters][0U][jss::HookParameter] = Json::Value{};
@@ -2116,11 +2478,16 @@ public:
"CAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFE"
"CAFECAFE"},
{jss::HookParameters, params},
{jss::HookGrants, grants}})
{jss::HookGrants, grants},
{jss::HookName, strHex(std::string{"DEADBEEF"})},
})
{
if (!hasHookCanEmit && key == jss::HookCanEmit)
continue;
if (!hasNamedHooks && key == jss::HookName)
continue;
Json::Value iv;
iv[key] = value;
jv[jss::Hooks][0U] = Json::Value{};
@@ -2192,11 +2559,16 @@ public:
{jss::HookCanEmit,
"00000000000000000000000000000000000000000000000000000000"
"00000000"},
{jss::HookNamespace, to_string(uint256{beast::zero})}})
{jss::HookNamespace, to_string(uint256{beast::zero})},
{jss::HookName, strHex(std::string{"DEADBEEF"})},
})
{
if (key == jss::HookCanEmit && !hasHookCanEmit)
continue;
if (!hasNamedHooks && key == jss::HookName)
continue;
Json::Value iv;
iv[key] = value;
jv[jss::Hooks][0U] = Json::Value{};
@@ -2445,11 +2817,16 @@ public:
"CAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFE"
"CAFECAFE"},
{jss::HookParameters, params},
{jss::HookGrants, grants}})
{jss::HookGrants, grants},
{jss::HookName, strHex(std::string{"DEADBEEF"})},
})
{
if (key == jss::HookCanEmit && !hasHookCanEmit)
continue;
if (!hasNamedHooks && key == jss::HookName)
continue;
Json::Value iv;
iv[key] = value;
jv[jss::Hooks][0U] = Json::Value{};
@@ -14721,6 +15098,7 @@ public:
testPageCap(features);
testHookOnV2(features);
testHookName(features);
testFillCopy(features);
@@ -14824,7 +15202,7 @@ public:
using namespace test::jtx;
static FeatureBitset const all{supported_amendments()};
static std::array<FeatureBitset, 7> const feats{
static std::array<FeatureBitset, 8> const feats{
all,
all - fixXahauV2,
all - fixXahauV1 - fixXahauV2,
@@ -14834,6 +15212,7 @@ public:
featureHookCanEmit,
all - fixXahauV1 - fixXahauV2 - fixNSDelete - fixPageCap -
featureExtendedHookState,
all - featureNamedHooks,
};
if (BEAST_EXPECT(instance < feats.size()))
@@ -14985,6 +15364,98 @@ private:
)[test.hook]"];
HASH_WASM(accept2);
// This hook is used to test Callback
TestHook emit_invoke_wasm = // WASM: 7
wasm[
R"[test.hook](
#include <stdint.h>
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 emit (uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
extern int64_t hook_account(uint32_t write_ptr, uint32_t write_len);
extern int64_t etxn_reserve(uint32_t);
extern int64_t etxn_fee_base (uint32_t read_ptr, uint32_t read_len);
extern int64_t etxn_details (uint32_t write_ptr, uint32_t write_len);
extern int64_t ledger_seq (void);
#define SBUF(x) (uint32_t)x,sizeof(x)
// clang-format off
uint8_t txn[229] =
{
/* size, upto, field name */
/* 3, 0, tt = Invoke */ 0x12U, 0x00U, 0x63U,
/* 5, 3, flags */ 0x22U, 0x00U, 0x00U, 0x00U, 0x00U,
/* 5, 8, sequence */ 0x24U, 0x00U, 0x00U, 0x00U, 0x00U,
/* 6, 13, firstledgersequence */ 0x20U, 0x1AU, 0x00U, 0x00U, 0x00U, 0x00U,
/* 6, 19, lastledgersequence */ 0x20U, 0x1BU, 0x00U, 0x00U, 0x00U, 0x00U,
/* 9, 25, fee */ 0x68U, 0x40U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
/* 35, 34, signingpubkey */ 0x73U, 0x21U, 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,0,0,
/* 22, 69, account */ 0x81U, 0x14U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
/* 116, 91, emit details */
/* 0, 229, */
};
// clang-format on
// TX BUILDER
#define FLAGS_OUT (txn + 4U)
#define FLS_OUT (txn + 15U)
#define LLS_OUT (txn + 21U)
#define FEE_OUT (txn + 26U)
#define ACCOUNT_OUT (txn + 71U)
#define EMIT_OUT (txn + 91U)
#define FLIP_ENDIAN_32(value) \
(uint32_t)(((value & 0xFFU) << 24) | ((value & 0xFF00U) << 8) | \
((value & 0xFF0000U) >> 8) | ((value & 0xFF000000U) >> 24))
#define SET_UINT32(ptr, value) *((uint32_t *)(ptr)) = FLIP_ENDIAN_32(value);
#define SET_NATIVE_AMOUNT(ptr, amount) \
do { \
uint8_t *b = (ptr); \
*b++ = 0b01000000 + ((amount >> 56) & 0b00111111); \
*b++ = (amount >> 48) & 0xFFU; \
*b++ = (amount >> 40) & 0xFFU; \
*b++ = (amount >> 32) & 0xFFU; \
*b++ = (amount >> 24) & 0xFFU; \
*b++ = (amount >> 16) & 0xFFU; \
*b++ = (amount >> 8) & 0xFFU; \
*b++ = (amount >> 0) & 0xFFU; \
} while (0)
#define PREPARE_TXN() \
do { \
etxn_reserve(1); \
uint32_t fls = (uint32_t)ledger_seq() + 1; \
SET_UINT32(FLS_OUT, fls); \
SET_UINT32(LLS_OUT, fls + 4); \
hook_account(ACCOUNT_OUT, 20); \
etxn_details(EMIT_OUT, 138U); \
int64_t fee = etxn_fee_base(SBUF(txn)); \
SET_NATIVE_AMOUNT(FEE_OUT, fee); \
} while (0)
int64_t cbak(uint32_t r)
{
_g(1,1);
return accept(0,0,0);
}
int64_t hook(uint32_t reserved)
{
_g(1,1);
PREPARE_TXN();
uint8_t emithash[32];
int64_t emit_result = emit(SBUF(emithash), SBUF(txn));
if (emit_result < 0)
return rollback(SBUF("Emit failed."), __LINE__);
return accept(SBUF("Emit succeeded."), __LINE__);
}
)[test.hook]"];
HASH_WASM(emit_invoke);
};
#define SETHOOK_TEST(i, last) \
@@ -15002,7 +15473,8 @@ SETHOOK_TEST(2, false)
SETHOOK_TEST(3, false)
SETHOOK_TEST(4, false)
SETHOOK_TEST(5, false)
SETHOOK_TEST(6, true)
SETHOOK_TEST(6, false)
SETHOOK_TEST(7, true)
BEAST_DEFINE_TESTSUITE_PRIO(SetHook0, app, ripple, 2);
BEAST_DEFINE_TESTSUITE_PRIO(SetHook1, app, ripple, 2);
@@ -15011,6 +15483,7 @@ BEAST_DEFINE_TESTSUITE_PRIO(SetHook3, app, ripple, 2);
BEAST_DEFINE_TESTSUITE_PRIO(SetHook4, app, ripple, 2);
BEAST_DEFINE_TESTSUITE_PRIO(SetHook5, app, ripple, 2);
BEAST_DEFINE_TESTSUITE_PRIO(SetHook6, app, ripple, 2);
BEAST_DEFINE_TESTSUITE_PRIO(SetHook7, app, ripple, 2);
} // namespace test
} // namespace ripple
#undef M

View File

@@ -34110,6 +34110,193 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
0x0BU,
}},
/* ==== WASM: 100 ==== */
{R"[test.hook](
#include <stdint.h>
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 emit (uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
extern int64_t hook_account(uint32_t write_ptr, uint32_t write_len);
extern int64_t etxn_reserve(uint32_t);
extern int64_t etxn_fee_base (uint32_t read_ptr, uint32_t read_len);
extern int64_t etxn_details (uint32_t write_ptr, uint32_t write_len);
extern int64_t ledger_seq (void);
#define SBUF(x) (uint32_t)x,sizeof(x)
// clang-format off
uint8_t txn[229] =
{
/* size, upto, field name */
/* 3, 0, tt = Invoke */ 0x12U, 0x00U, 0x63U,
/* 5, 3, flags */ 0x22U, 0x00U, 0x00U, 0x00U, 0x00U,
/* 5, 8, sequence */ 0x24U, 0x00U, 0x00U, 0x00U, 0x00U,
/* 6, 13, firstledgersequence */ 0x20U, 0x1AU, 0x00U, 0x00U, 0x00U, 0x00U,
/* 6, 19, lastledgersequence */ 0x20U, 0x1BU, 0x00U, 0x00U, 0x00U, 0x00U,
/* 9, 25, fee */ 0x68U, 0x40U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
/* 35, 34, signingpubkey */ 0x73U, 0x21U, 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,0,0,
/* 22, 69, account */ 0x81U, 0x14U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
/* 116, 91, emit details */
/* 0, 229, */
};
// clang-format on
// TX BUILDER
#define FLAGS_OUT (txn + 4U)
#define FLS_OUT (txn + 15U)
#define LLS_OUT (txn + 21U)
#define FEE_OUT (txn + 26U)
#define ACCOUNT_OUT (txn + 71U)
#define EMIT_OUT (txn + 91U)
#define FLIP_ENDIAN_32(value) \
(uint32_t)(((value & 0xFFU) << 24) | ((value & 0xFF00U) << 8) | \
((value & 0xFF0000U) >> 8) | ((value & 0xFF000000U) >> 24))
#define SET_UINT32(ptr, value) *((uint32_t *)(ptr)) = FLIP_ENDIAN_32(value);
#define SET_NATIVE_AMOUNT(ptr, amount) \
do { \
uint8_t *b = (ptr); \
*b++ = 0b01000000 + ((amount >> 56) & 0b00111111); \
*b++ = (amount >> 48) & 0xFFU; \
*b++ = (amount >> 40) & 0xFFU; \
*b++ = (amount >> 32) & 0xFFU; \
*b++ = (amount >> 24) & 0xFFU; \
*b++ = (amount >> 16) & 0xFFU; \
*b++ = (amount >> 8) & 0xFFU; \
*b++ = (amount >> 0) & 0xFFU; \
} while (0)
#define PREPARE_TXN() \
do { \
etxn_reserve(1); \
uint32_t fls = (uint32_t)ledger_seq() + 1; \
SET_UINT32(FLS_OUT, fls); \
SET_UINT32(LLS_OUT, fls + 4); \
hook_account(ACCOUNT_OUT, 20); \
etxn_details(EMIT_OUT, 138U); \
int64_t fee = etxn_fee_base(SBUF(txn)); \
SET_NATIVE_AMOUNT(FEE_OUT, fee); \
} while (0)
int64_t cbak(uint32_t r)
{
_g(1,1);
return accept(0,0,0);
}
int64_t hook(uint32_t reserved)
{
_g(1,1);
PREPARE_TXN();
uint8_t emithash[32];
int64_t emit_result = emit(SBUF(emithash), SBUF(txn));
if (emit_result < 0)
return rollback(SBUF("Emit failed."), __LINE__);
return accept(SBUF("Emit succeeded."), __LINE__);
}
)[test.hook]",
{
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, 0x25U,
0x06U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x03U, 0x7FU,
0x7FU, 0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x60U,
0x00U, 0x01U, 0x7EU, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U,
0x04U, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x02U, 0x8FU, 0x01U,
0x09U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, 0x00U, 0x00U,
0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U,
0x74U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0CU, 0x65U, 0x74U,
0x78U, 0x6EU, 0x5FU, 0x72U, 0x65U, 0x73U, 0x65U, 0x72U, 0x76U, 0x65U,
0x00U, 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0AU, 0x6CU, 0x65U, 0x64U,
0x67U, 0x65U, 0x72U, 0x5FU, 0x73U, 0x65U, 0x71U, 0x00U, 0x03U, 0x03U,
0x65U, 0x6EU, 0x76U, 0x0CU, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x61U,
0x63U, 0x63U, 0x6FU, 0x75U, 0x6EU, 0x74U, 0x00U, 0x04U, 0x03U, 0x65U,
0x6EU, 0x76U, 0x0CU, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x64U, 0x65U,
0x74U, 0x61U, 0x69U, 0x6CU, 0x73U, 0x00U, 0x04U, 0x03U, 0x65U, 0x6EU,
0x76U, 0x0DU, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x66U, 0x65U, 0x65U,
0x5FU, 0x62U, 0x61U, 0x73U, 0x65U, 0x00U, 0x04U, 0x03U, 0x65U, 0x6EU,
0x76U, 0x04U, 0x65U, 0x6DU, 0x69U, 0x74U, 0x00U, 0x05U, 0x03U, 0x65U,
0x6EU, 0x76U, 0x08U, 0x72U, 0x6FU, 0x6CU, 0x6CU, 0x62U, 0x61U, 0x63U,
0x6BU, 0x00U, 0x01U, 0x03U, 0x03U, 0x02U, 0x02U, 0x02U, 0x05U, 0x03U,
0x01U, 0x00U, 0x02U, 0x06U, 0x27U, 0x06U, 0x7FU, 0x01U, 0x41U, 0x90U,
0x8AU, 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x82U, 0x0AU, 0x0BU, 0x7FU,
0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x90U, 0x8AU,
0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U,
0x41U, 0x80U, 0x08U, 0x0BU, 0x07U, 0x0FU, 0x02U, 0x04U, 0x63U, 0x62U,
0x61U, 0x6BU, 0x00U, 0x09U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U,
0x0AU, 0x0AU, 0xA8U, 0x83U, 0x00U, 0x02U, 0x99U, 0x80U, 0x00U, 0x00U,
0x41U, 0x01U, 0x41U, 0x01U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U,
0x1AU, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x00U, 0x10U, 0x81U, 0x80U,
0x80U, 0x80U, 0x00U, 0x0BU, 0x88U, 0x83U, 0x00U, 0x02U, 0x03U, 0x7FU,
0x01U, 0x7EU, 0x23U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x41U, 0x20U,
0x6BU, 0x22U, 0x01U, 0x24U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x41U,
0x01U, 0x41U, 0x01U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU,
0x41U, 0x01U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x41U,
0x00U, 0x10U, 0x83U, 0x80U, 0x80U, 0x80U, 0x00U, 0xA7U, 0x22U, 0x02U,
0x41U, 0x05U, 0x6AU, 0x22U, 0x03U, 0x41U, 0x18U, 0x74U, 0x20U, 0x03U,
0x41U, 0x08U, 0x74U, 0x41U, 0x80U, 0x80U, 0xFCU, 0x07U, 0x71U, 0x72U,
0x20U, 0x03U, 0x41U, 0x08U, 0x76U, 0x41U, 0x80U, 0xFEU, 0x03U, 0x71U,
0x20U, 0x03U, 0x41U, 0x18U, 0x76U, 0x72U, 0x72U, 0x36U, 0x02U, 0x95U,
0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x00U, 0x20U, 0x02U, 0x41U, 0x01U,
0x6AU, 0x22U, 0x03U, 0x41U, 0x18U, 0x74U, 0x20U, 0x03U, 0x41U, 0x08U,
0x74U, 0x41U, 0x80U, 0x80U, 0xFCU, 0x07U, 0x71U, 0x72U, 0x20U, 0x03U,
0x41U, 0x08U, 0x76U, 0x41U, 0x80U, 0xFEU, 0x03U, 0x71U, 0x20U, 0x03U,
0x41U, 0x18U, 0x76U, 0x72U, 0x72U, 0x36U, 0x02U, 0x8FU, 0x88U, 0x80U,
0x80U, 0x00U, 0x41U, 0xC7U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x14U,
0x10U, 0x84U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x41U, 0xDBU, 0x88U,
0x80U, 0x80U, 0x00U, 0x41U, 0x8AU, 0x01U, 0x10U, 0x85U, 0x80U, 0x80U,
0x80U, 0x00U, 0x1AU, 0x41U, 0x00U, 0x41U, 0x80U, 0x88U, 0x80U, 0x80U,
0x00U, 0x41U, 0xE5U, 0x01U, 0x10U, 0x86U, 0x80U, 0x80U, 0x80U, 0x00U,
0x22U, 0x04U, 0x3CU, 0x00U, 0xA1U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U,
0x00U, 0x20U, 0x04U, 0x42U, 0x08U, 0x88U, 0x3CU, 0x00U, 0xA0U, 0x88U,
0x80U, 0x80U, 0x00U, 0x41U, 0x00U, 0x20U, 0x04U, 0x42U, 0x10U, 0x88U,
0x3CU, 0x00U, 0x9FU, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x00U, 0x20U,
0x04U, 0x42U, 0x18U, 0x88U, 0x3CU, 0x00U, 0x9EU, 0x88U, 0x80U, 0x80U,
0x00U, 0x41U, 0x00U, 0x20U, 0x04U, 0x42U, 0x20U, 0x88U, 0x3CU, 0x00U,
0x9DU, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x00U, 0x20U, 0x04U, 0x42U,
0x28U, 0x88U, 0x3CU, 0x00U, 0x9CU, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U,
0x00U, 0x20U, 0x04U, 0x42U, 0x30U, 0x88U, 0x3CU, 0x00U, 0x9BU, 0x88U,
0x80U, 0x80U, 0x00U, 0x41U, 0x00U, 0x20U, 0x04U, 0x42U, 0x38U, 0x88U,
0xA7U, 0x41U, 0x3FU, 0x71U, 0x41U, 0xC0U, 0x00U, 0x72U, 0x3AU, 0x00U,
0x9AU, 0x88U, 0x80U, 0x80U, 0x00U, 0x02U, 0x40U, 0x02U, 0x40U, 0x20U,
0x01U, 0x41U, 0x20U, 0x41U, 0x80U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U,
0xE5U, 0x01U, 0x10U, 0x87U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x7FU,
0x55U, 0x0DU, 0x00U, 0x41U, 0xE5U, 0x89U, 0x80U, 0x80U, 0x00U, 0x41U,
0x0DU, 0x42U, 0xD3U, 0x00U, 0x10U, 0x88U, 0x80U, 0x80U, 0x80U, 0x00U,
0x21U, 0x04U, 0x0CU, 0x01U, 0x0BU, 0x41U, 0xF2U, 0x89U, 0x80U, 0x80U,
0x00U, 0x41U, 0x10U, 0x42U, 0xD4U, 0x00U, 0x10U, 0x81U, 0x80U, 0x80U,
0x80U, 0x00U, 0x21U, 0x04U, 0x0BU, 0x20U, 0x01U, 0x41U, 0x20U, 0x6AU,
0x24U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x20U, 0x04U, 0x0BU, 0x0BU,
0x90U, 0x02U, 0x02U, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0xE5U, 0x01U,
0x12U, 0x00U, 0x63U, 0x22U, 0x00U, 0x00U, 0x00U, 0x00U, 0x24U, 0x00U,
0x00U, 0x00U, 0x00U, 0x20U, 0x1AU, 0x00U, 0x00U, 0x00U, 0x00U, 0x20U,
0x1BU, 0x00U, 0x00U, 0x00U, 0x00U, 0x68U, 0x40U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x73U, 0x21U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x81U,
0x14U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x41U, 0xE5U, 0x09U, 0x0BU, 0x1DU, 0x45U, 0x6DU, 0x69U, 0x74U, 0x20U,
0x66U, 0x61U, 0x69U, 0x6CU, 0x65U, 0x64U, 0x2EU, 0x00U, 0x45U, 0x6DU,
0x69U, 0x74U, 0x20U, 0x73U, 0x75U, 0x63U, 0x63U, 0x65U, 0x65U, 0x64U,
0x65U, 0x64U, 0x2EU, 0x00U,
}},
};
}
} // namespace ripple

View File

@@ -23,6 +23,7 @@
#include <xrpld/app/ledger/Ledger.h>
#include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpld/app/ledger/OpenLedger.h>
#include <xrpld/app/tx/detail/URIToken.h>
#include <xrpld/ledger/ApplyView.h>
#include <xrpl/basics/Log.h>
#include <xrpl/hook/Enum.h>
@@ -230,6 +231,7 @@ SetHook::inferOperation(STObject const& hookSetObj)
hookSetObj.isFieldPresent(sfHookOnIncoming))) &&
!hookSetObj.isFieldPresent(sfHookCanEmit) &&
!hookSetObj.isFieldPresent(sfHookApiVersion) &&
!hookSetObj.isFieldPresent(sfHookName) &&
!hookSetObj.isFieldPresent(sfFlags))
return hsoNOOP;
@@ -267,6 +269,7 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
hookSetObj.isFieldPresent(sfHookOnIncoming) ||
hookSetObj.isFieldPresent(sfHookCanEmit) ||
hookSetObj.isFieldPresent(sfHookApiVersion) ||
hookSetObj.isFieldPresent(sfHookName) ||
!hookSetObj.isFieldPresent(sfFlags) ||
!hookSetObj.isFieldPresent(sfHookNamespace))
{
@@ -300,6 +303,7 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
hookSetObj.isFieldPresent(sfHookCanEmit) ||
hookSetObj.isFieldPresent(sfHookApiVersion) ||
hookSetObj.isFieldPresent(sfHookNamespace) ||
hookSetObj.isFieldPresent(sfHookName) ||
!hookSetObj.isFieldPresent(sfFlags))
{
JLOG(ctx.j.trace())
@@ -510,6 +514,14 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
// pass
}
// validate sfHookName
if (hookSetObj.isFieldPresent(sfHookName))
{
auto name = hookSetObj.getFieldVL(sfHookName);
if (!validateHookName(name, ctx.j))
return false;
}
// finally validate web assembly byte code
{
if (!hookSetObj.isFieldPresent(sfCreateCode))
@@ -609,6 +621,23 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
}
}
bool
SetHook::validateHookName(Blob const& name, beast::Journal const& j)
{
if (name.size() != 0 && (name.size() < 4 || 16 < name.size()))
{
JLOG(j.trace())
<< "sfHookName must be between 8 and 32 hex characters.";
return false;
}
if (!URIToken::validateUTF8(name))
{
JLOG(j.trace()) << "sfHookName must be a valid UTF-8 string.";
return false;
}
return true;
}
// Note that if fee calculation causes an overflow then INITIAL_XRP is returned
// as a way of ensuring that the txn cannot possibly meet the fee requirement.
XRPAmount
@@ -783,6 +812,10 @@ SetHook::preflight(PreflightContext const& ctx)
hookSetObj.isFieldPresent(sfHookCanEmit))
return temDISABLED;
if (!ctx.rules.enabled(featureNamedHooks) &&
hookSetObj.isFieldPresent(sfHookName))
return temDISABLED;
for (auto const& hookSetElement : hookSetObj)
{
auto const& name = hookSetElement.getFName();
@@ -792,7 +825,7 @@ SetHook::preflight(PreflightContext const& ctx)
name != sfHookOn && name != sfHookOnOutgoing &&
name != sfHookOnIncoming && name != sfHookGrants &&
name != sfHookApiVersion && name != sfFlags &&
name != sfHookCanEmit)
name != sfHookCanEmit && name != sfHookName)
{
JLOG(ctx.j.trace())
<< "HookSet(" << hook::log::HOOK_INVALID_FIELD << ")["
@@ -1348,6 +1381,8 @@ SetHook::setHook()
std::optional<uint256> newHookCanEmit;
std::optional<uint256> defHookCanEmit;
std::optional<Blob> newHookName;
// when hsoCREATE is invoked it populates this variable in case the hook
// definition already exists and the operation falls through into a
// hsoINSTALL operation instead
@@ -1416,7 +1451,6 @@ SetHook::setHook()
if (oldDefSLE && oldDefSLE->isFieldPresent(sfHookCanEmit))
defHookCanEmit = oldDefSLE->getFieldH256(sfHookCanEmit);
if (oldHook && oldHook->get().isFieldPresent(sfHookCanEmit))
oldHookCanEmit = oldHook->get().getFieldH256(sfHookCanEmit);
else if (defHookCanEmit)
@@ -1453,6 +1487,9 @@ SetHook::setHook()
newNamespace = hookSetObj->get().getFieldH256(sfHookNamespace);
newDirKeylet = keylet::hookStateDir(account_, *newNamespace);
}
if (hookSetObj->get().isFieldPresent(sfHookName))
newHookName = hookSetObj->get().getFieldVL(sfHookName);
}
// users may destroy a namespace in any operation except NOOP and
@@ -1574,6 +1611,9 @@ SetHook::setHook()
newHook.setFieldH256(
sfHookNamespace,
oldHook->get().getFieldH256(sfHookNamespace));
if (oldHook->get().isFieldPresent(sfHookName))
newHook.setFieldVL(
sfHookName, oldHook->get().getFieldVL(sfHookName));
// set the namespace if it differs from the definition namespace
if (newNamespace)
@@ -1638,6 +1678,20 @@ SetHook::setHook()
newHook.setFieldH256(sfHookCanEmit, *newHookCanEmit);
}
// set the hookname field on ltHook when it is explicitly
// provided
if (newHookName)
{
if (newHookName->size() == 0)
{
newHook.makeFieldAbsent(sfHookName);
}
else
{
newHook.setFieldVL(sfHookName, *newHookName);
}
}
// parameters
if (hookSetObj->get().isFieldPresent(sfHookParameters) &&
hookSetObj->get().getFieldArray(sfHookParameters).empty())
@@ -1839,6 +1893,12 @@ SetHook::setHook()
newHook.setFieldArray(sfHookGrants, grants);
}
if (hookSetObj->get().isFieldPresent(sfHookName) &&
hookSetObj->get().getFieldVL(sfHookName).size() > 0)
newHook.setFieldVL(
sfHookName,
hookSetObj->get().getFieldVL(sfHookName));
slesToInsert.emplace(keylet, newHookDef);
newHook.setFieldH256(sfHookHash, *createHookHash);
newHooks.push_back(std::move(newHook));
@@ -1950,6 +2010,11 @@ SetHook::setHook()
*defHookCanEmit == *newHookCanEmit))
newHook.setFieldH256(sfHookCanEmit, *newHookCanEmit);
// set the hookname field on ltHook when it is explicitly
// provided
if (newHookName && newHookName->size() > 0)
newHook.setFieldVL(sfHookName, *newHookName);
// parameters
TER result = updateHookParameters(
ctx,
@@ -1999,7 +2064,8 @@ SetHook::setHook()
// sfParameters: 1 reserve PER entry
// sfGrants are: 1 reserve PER entry
// sfHookHash, sfHookNamespace, sfHookOn, sfHookOnOutgoing,
// sfHookOnIncoming, sfHookCanEmit sfHookApiVersion, sfFlags: free
// sfHookOnIncoming, sfHookCanEmit sfHookApiVersion, sfFlags,
// sfHookName: free
// ltHookDefinition is not reserved because it is an unowned object,
// rather the uploader is billed via fee according to the following:

View File

@@ -91,6 +91,9 @@ public:
static HookSetValidation
validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj);
static bool
validateHookName(Blob const& name, beast::Journal const& j);
static uint32_t
computeHookReserve(STObject const& hookObj);

View File

@@ -24,6 +24,7 @@
#include <xrpld/app/misc/LoadFeeTrack.h>
#include <xrpld/app/tx/apply.h>
#include <xrpld/app/tx/detail/NFTokenUtils.h>
#include <xrpld/app/tx/detail/SetHook.h>
#include <xrpld/app/tx/detail/SignerEntries.h>
#include <xrpld/app/tx/detail/Transactor.h>
#include <xrpld/core/Config.h>
@@ -148,6 +149,16 @@ preflight1(PreflightContext const& ctx)
}
}
if (ctx.tx.isFieldPresent(sfHookName))
{
if (!ctx.rules.enabled(featureHooks) ||
!ctx.rules.enabled(featureNamedHooks))
return temMALFORMED;
if (!SetHook::validateHookName(ctx.tx.getFieldVL(sfHookName), ctx.j))
return temMALFORMED;
}
auto const spk = ctx.tx.getSigningPubKey();
if (!spk.empty() && !publicKeyType(makeSlice(spk)))
@@ -266,8 +277,24 @@ Transactor::calculateHookChainFee(
// at the same ledger the fee calculation for it can no longer occur
if (!hookDef)
{
// LCOV_EXCL_START
printf("calculateHookChainFee edge case\n");
continue;
// LCOV_EXCL_STOP
}
std::optional<Blob> requiredHookName;
if (hookObj.isFieldPresent(sfHookName) &&
hookObj.getFieldVL(sfHookName).size() > 0)
requiredHookName = hookObj.getFieldVL(sfHookName);
if (requiredHookName)
{
// need to specify same hook name in the transaction
if (!tx.isFieldPresent(sfHookName))
continue;
if (*requiredHookName != tx.getFieldVL(sfHookName))
continue;
}
uint32_t flags = 0;
@@ -1311,16 +1338,34 @@ Transactor::executeHookChain(
if (hookSkips.find(hookHash) != hookSkips.end())
{
// LCOV_EXCL_START
JLOG(j_.trace()) << "HookInfo: Skipping " << hookHash;
continue;
// LCOV_EXCL_STOP
}
auto const& hookDef =
ctx_.view().peek(keylet::hookDefinition(hookHash));
if (!hookDef)
{
// LCOV_EXCL_START
JLOG(j_.warn()) << "HookError[]: Failure: hook def missing (send)";
continue;
// LCOV_EXCL_STOP
}
std::optional<Blob> requiredHookName;
if (hookObj.isFieldPresent(sfHookName) &&
hookObj.getFieldVL(sfHookName).size() > 0)
requiredHookName = hookObj.getFieldVL(sfHookName);
if (requiredHookName)
{
// need to specify same hook name in the transaction
if (!ctx_.tx.isFieldPresent(sfHookName))
continue;
if (*requiredHookName != ctx_.tx.getFieldVL(sfHookName))
continue;
}
// check if the hook can fire