Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
afd52e1
`prepare` hook api
tequdev Dec 18, 2024
65307c3
featureHooksUpdate2
tequdev Dec 18, 2024
625f505
Merge branch 'dev' into prepare-hook-api
tequdev Feb 12, 2025
2058c3f
fix rulesVersion for guard.h
tequdev Feb 12, 2025
a6bd54c
fix
tequdev Feb 14, 2025
65dffa3
add bounds check
tequdev Feb 14, 2025
7b1f3a8
add prepare test
tequdev Feb 14, 2025
eba6d1a
clang-format
tequdev Feb 14, 2025
708499b
fix
tequdev Feb 14, 2025
7b9d5f3
refactor Guard.h
tequdev Feb 15, 2025
87c70e3
Merge branch 'dev' into prepare-hook-api
tequdev Feb 27, 2025
d3d8ca9
Merge branch 'dev' into prepare-hook-api
tequdev Mar 4, 2025
877f457
Merge branch 'dev' into prepare-hook-api
tequdev May 9, 2025
6e922b5
Merge branch 'dev' into prepare-hook-api
tequdev May 15, 2025
22c6e8d
Merge branch 'dev' into prepare-hook-api
RichardAH Jun 18, 2025
774ee87
Merge branch 'dev' into prepare-hook-api
tequdev Jul 3, 2025
8cca931
Merge branch 'dev' into prepare-hook-api
tequdev Jul 10, 2025
66d2a64
fix bad merge
tequdev Jul 14, 2025
cc37a96
Refactor: Whitelisted Hook APIs
tequdev Oct 22, 2025
6f83efe
Merge branch 'dev' into refactor-enum-whitelist-api
tequdev Oct 23, 2025
4269ef7
additional refactor
tequdev Oct 30, 2025
9c0feaf
fix
tequdev Oct 30, 2025
24e18c9
Refactor2: Hook APIs Amendment Guards
tequdev Oct 30, 2025
adebc3c
refactor: rulesVersion
tequdev Oct 30, 2025
c89967e
bad merge
tequdev Oct 30, 2025
df81b7a
Merge branch 'dev' into refactor-enum-whitelist-api
tequdev Oct 30, 2025
96417f3
Merge commit 'df81b7ae5d28176aaa93b181aeff5b8a12195633' into refactor…
tequdev Oct 31, 2025
fce6ced
Refactor3: Consolidate the Hook API definitions from Enum.h and Apply…
tequdev Oct 31, 2025
e92b993
add `xpop_slot` test before featureHooksUpdate1
tequdev Oct 31, 2025
2e6e64b
Merge branch 'refactor-whitelist-api-2' into refactor-whitelist-api-3
tequdev Oct 31, 2025
65af101
Merge branch 'dev' into refactor-enum-whitelist-api
tequdev Nov 3, 2025
047b713
update generate_extern.sh
tequdev Nov 4, 2025
0955ab2
fix workflow
tequdev Nov 4, 2025
cdf3008
fix script
tequdev Nov 4, 2025
5e60bdf
fix
tequdev Nov 5, 2025
bfb037d
Merge branch 'refactor-whitelist-api-2' into refactor-whitelist-api-3
tequdev Nov 5, 2025
158def4
Merge branch 'dev' into refactor-enum-whitelist-api
tequdev Nov 7, 2025
6288a6e
Merge branch 'dev' into refactor-enum-whitelist-api
tequdev Nov 20, 2025
7c96052
remove unused DECLARE_HOOK_FUNCNARG macro
tequdev Nov 20, 2025
4c0d3ab
fix DECLARE_HOOK_FUNCTION macro
tequdev Nov 20, 2025
2fc0795
refactor DEFINE_HOOK_FUNCNARG
tequdev Nov 20, 2025
6dedbf9
fix
tequdev Nov 20, 2025
c898eda
Merge branch 'dev' into refactor-enum-whitelist-api
tequdev Nov 25, 2025
ee3285b
Merge branch 'dev' into refactor-enum-whitelist-api
tequdev Nov 27, 2025
c1c367d
Merge branch 'dev' into refactor-enum-whitelist-api
tequdev Dec 1, 2025
6561311
Merge remote-tracking branch 'upstream/dev' into prepare-hook-api
tequdev Dec 2, 2025
10a4cfb
update extern.h
tequdev Dec 2, 2025
6f11085
Merge remote-tracking branch 'origin/refactor-enum-whitelist-api' int…
tequdev Dec 3, 2025
64f17b8
Merge branch 'refactor-whitelist-api-2' into refactor-whitelist-api-3
tequdev Dec 3, 2025
dee1d88
fix log message
tequdev Dec 3, 2025
4d9f766
Merge remote-tracking branch 'upstream/dev' into refactor-whitelist-a…
tequdev Dec 10, 2025
9a7e856
Merge branch 'refactor-whitelist-api-2' into refactor-whitelist-api-3
tequdev Dec 10, 2025
8a9eb1f
Fix to use macros only top of Enum.h
tequdev Dec 11, 2025
379fb0f
format hook_api.macro
tequdev Dec 12, 2025
f036976
Merge branch 'refactor-whitelist-api-2' into refactor-whitelist-api-3
tequdev Dec 12, 2025
7d6a3f2
Merge branch 'dev' into refactor-whitelist-api-2
tequdev Dec 16, 2025
d604cf7
Merge branch 'dev' into refactor-whitelist-api-2
tequdev Dec 17, 2025
7debf8d
Merge branch 'refactor-whitelist-api-2' into refactor-whitelist-api-3
tequdev Dec 17, 2025
e9b6d09
Merge branch 'dev' into prepare-hook-api
tequdev Dec 17, 2025
c0f7da0
Merge branch 'dev' into prepare-hook-api
tequdev Dec 24, 2025
0902358
Merge branch 'dev' into refactor-whitelist-api-3
tequdev Dec 24, 2025
b90767d
Merge remote-tracking branch 'origin/refactor-whitelist-api-3' into p…
tequdev Dec 24, 2025
1bf1a5c
Update extern.h
tequdev Dec 24, 2025
87b81bb
Merge branch 'dev' into prepare-hook-api
RichardAH Jan 28, 2026
cef84a2
Merge branch 'dev' into refactor-whitelist-api-3
tequdev Jan 29, 2026
1e5c442
Merge branch 'dev' into prepare-hook-api
tequdev Feb 9, 2026
87c387a
Merge branch 'refactor-whitelist-api-3' into prepare-hook-api
tequdev Feb 9, 2026
a7a7d9b
Merge remote-tracking branch 'upstream/dev' into prepare-hook-api
tequdev Feb 16, 2026
2ca0cfe
bad merge
tequdev Feb 16, 2026
66fbfb2
Add prepare unittest
tequdev Feb 16, 2026
916595d
nit
tequdev Feb 16, 2026
fcc392e
Merge branch 'dev' into prepare-hook-api
RichardAH Feb 17, 2026
6e80d39
update amendment definition for guard checker
tequdev Feb 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions hook/extern.h
Original file line number Diff line number Diff line change
Expand Up @@ -329,5 +329,12 @@ meta_slot(uint32_t slot_no);
extern int64_t
xpop_slot(uint32_t slot_no_tx, uint32_t slot_no_meta);

extern int64_t
prepare(
uint32_t write_ptr,
uint32_t write_len,
uint32_t read_ptr,
uint32_t read_len);

#define HOOK_EXTERN
#endif // HOOK_EXTERN
1 change: 1 addition & 0 deletions src/ripple/app/hook/Enum.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// Override uint256, Feature and Rules for guard checker build
#define uint256 std::string
#define featureHooksUpdate1 "1"
#define featureHooksUpdate2 "1"
#define fix20250131 "1"
namespace hook_api {
struct Rules
Expand Down
3 changes: 3 additions & 0 deletions src/ripple/app/hook/HookAPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ class HookAPI
// sto_erase(): same as sto_emplace with field_object = nullopt

/// etxn APIs
Expected<Bytes, HookReturnCode>
prepare(Slice const& txBlob) const;

Expected<std::shared_ptr<Transaction>, HookReturnCode>
emit(Slice const& txBlob) const;

Expand Down
5 changes: 5 additions & 0 deletions src/ripple/app/hook/hook_api.macro
Original file line number Diff line number Diff line change
Expand Up @@ -367,3 +367,8 @@ HOOK_API_DEFINITION(
HOOK_API_DEFINITION(
int64_t, xpop_slot, (uint32_t, uint32_t),
featureHooksUpdate1)

// int64_t prepare(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
HOOK_API_DEFINITION(
int64_t, prepare, (uint32_t, uint32_t, uint32_t, uint32_t),
featureHooksUpdate2)
117 changes: 117 additions & 0 deletions src/ripple/app/hook/impl/HookAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <ripple/app/ledger/OpenLedger.h>
#include <ripple/app/ledger/TransactionMaster.h>
#include <ripple/app/tx/impl/Import.h>
#include <ripple/protocol/STParsedJSON.h>
#include <cfenv>

namespace hook {
Expand Down Expand Up @@ -625,6 +626,122 @@ HookAPI::sto_emplace(
// sto_erase

/// etxn APIs
Expected<Bytes, HookReturnCode>
HookAPI::prepare(Slice const& txBlob) const
{
auto& applyCtx = hookCtx.applyCtx;
auto j = applyCtx.app.journal("View");

if (hookCtx.expected_etxn_count < 0)
return Unexpected(PREREQUISITE_NOT_MET);

Json::Value json;

// std::shared_ptr<STObject const> stpTrans;
try
{
SerialIter sitTrans{txBlob};
json =
STObject(std::ref(sitTrans), sfGeneric).getJson(JsonOptions::none);
}
catch (std::exception& e)
{
JLOG(j.trace()) << "HookInfo[" << HC_ACC() << "]: prepare Failed "
<< e.what() << "\n";
return Unexpected(INVALID_ARGUMENT);
}

// add a dummy fee
json[jss::Fee] = "0";

// force key to empty
json[jss::SigningPubKey] =
"000000000000000000000000000000000000000000000000000000000000000000";

// force sequence to 0
json[jss::Sequence] = Json::Value(0u);

std::string raddr = encodeBase58Token(
TokenType::AccountID, hookCtx.result.account.data(), 20);

json[jss::Account] = raddr;

uint32_t seq = applyCtx.view().info().seq;
if (!json.isMember(jss::FirstLedgerSequence))
json[jss::FirstLedgerSequence] = Json::Value(seq + 1);

if (!json.isMember(jss::LastLedgerSequence))
json[jss::LastLedgerSequence] = Json::Value(seq + 5);

uint8_t details[512];
if (!json.isMember(jss::EmitDetails))
{
auto ret = etxn_details(details);
if (!ret || ret.value() < 2)
return Unexpected(INTERNAL_ERROR);

// truncate the head and tail (emit details object markers)
Slice s(
reinterpret_cast<void const*>(details + 1),
(size_t)(ret.value() - 2));

// std::cout << "emitdets: " << strHex(s) << "\n";
try
{
SerialIter sit{s};
STObject st{sit, sfEmitDetails};
json[jss::EmitDetails] = st.getJson(JsonOptions::none);
}
catch (std::exception const& ex)
{
JLOG(j.warn()) << "HookInfo[" << HC_ACC() << "]: Exception in "
<< __func__ << ": " << ex.what();
return Unexpected(INTERNAL_ERROR);
}
}

// {
// const std::string flat = Json::FastWriter().write(json);
// std::cout << "intermediate: `" << flat << "`\n";
// }

Blob tx_blob;
{
STParsedJSONObject parsed(std::string(jss::tx_json), json);
if (!parsed.object.has_value())
return Unexpected(INVALID_ARGUMENT);

STObject& obj = *(parsed.object);

// serialize it
Serializer s;
obj.add(s);
tx_blob = s.getData();
}

// run it through the fee estimate, this doubles as a txn sanity check
auto fee = etxn_fee_base(Slice(tx_blob.data(), tx_blob.size()));
if (!fee)
return Unexpected(INVALID_ARGUMENT);

json[jss::Fee] = to_string(fee.value());

{
STParsedJSONObject parsed(std::string(jss::tx_json), json);
if (!parsed.object.has_value())
return Unexpected(INVALID_ARGUMENT);

STObject& obj = *(parsed.object);

// serialize it
Serializer s;
obj.add(s);
tx_blob = s.getData();
}

return tx_blob;
}

Expected<std::shared_ptr<Transaction>, HookReturnCode>
HookAPI::emit(Slice const& txBlob) const
{
Expand Down
37 changes: 37 additions & 0 deletions src/ripple/app/hook/impl/applyHook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2575,6 +2575,43 @@ DEFINE_HOOK_FUNCTION(
HOOK_TEARDOWN();
}

DEFINE_HOOK_FUNCTION(
int64_t,
prepare,
uint32_t write_ptr,
uint32_t write_len,
uint32_t read_ptr,
uint32_t read_len)
{
HOOK_SETUP(); // populates memory_ctx, memory, memory_length, applyCtx,
// hookCtx on current stack

if (NOT_IN_BOUNDS(read_ptr, read_len, memory_length))
return OUT_OF_BOUNDS;

if (NOT_IN_BOUNDS(write_ptr, write_len, memory_length))
return OUT_OF_BOUNDS;

ripple::Slice txBlob{
reinterpret_cast<const void*>(memory + read_ptr), read_len};

auto const res = api.prepare(txBlob);
if (!res)
return res.error();

auto tx_blob = res.value();

WRITE_WASM_MEMORY_AND_RETURN(
write_ptr,
tx_blob.size(),
tx_blob.data(),
tx_blob.size(),
memory,
memory_length);

HOOK_TEARDOWN();
}

/* Emit a transaction from this hook. Transaction must be in STObject form,
* fully formed and valid. XRPLD does not modify transactions it only checks
* them for validity. */
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 = 90;
static constexpr std::size_t numFeatures = 91;

/** 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 @@ -378,6 +378,7 @@ extern uint256 const fixInvalidTxFlags;
extern uint256 const featureExtendedHookState;
extern uint256 const fixCronStacking;
extern uint256 const fixHookAPI20251128;
extern uint256 const featureHooksUpdate2;
} // namespace ripple

#endif
1 change: 1 addition & 0 deletions src/ripple/protocol/impl/Feature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,7 @@ REGISTER_FIX (fixInvalidTxFlags, Supported::yes, VoteBehavior::De
REGISTER_FEATURE(ExtendedHookState, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FIX (fixCronStacking, Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX (fixHookAPI20251128, Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(HooksUpdate2, Supported::yes, VoteBehavior::DefaultNo);

// The following amendments are obsolete, but must remain supported
// because they could potentially get enabled.
Expand Down
2 changes: 2 additions & 0 deletions src/ripple/protocol/jss.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ JSS(Invalid); //
JSS(Invoke); // transaction type
JSS(InvoiceID); // field
JSS(LastLedgerSequence); // in: TransactionSign; field
JSS(FirstLedgerSequence); // in: TransactionSign; field
JSS(LedgerHashes); // ledger type.
JSS(LimitAmount); // field.
JSS(NetworkID); // field.
Expand Down Expand Up @@ -140,6 +141,7 @@ JSS(HookState); // ledger type.
JSS(HookStateData); // field.
JSS(HookStateKey); // field.
JSS(EmittedTxn); // ledger type.
JSS(EmitDetails); // field.
JSS(SignerList); // ledger type.
JSS(SignerListSet); // transaction type.
JSS(SigningPubKey); // field.
Expand Down
81 changes: 81 additions & 0 deletions src/test/app/HookAPI_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
//==============================================================================
#include <ripple/app/hook/HookAPI.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/beast/unit_test/suite.hpp>
#include <ripple/json/json_writer.h>
#include <ripple/protocol/SField.h>
#include <ripple/protocol/STAccount.h>
#include <limits>
#include <test/app/Import_json.h>
Expand Down Expand Up @@ -75,6 +77,84 @@ class HookAPI_test : public beast::unit_test::suite
BEAST_EXPECT(true);
}

void
test_prepare(FeatureBitset features)
{
testcase("Test prepare");
using namespace jtx;

auto const alice = Account{"alice"};
auto const bob = Account{"bob"};

using namespace hook_api;
Env env{*this, features};

STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
OpenView ov{*env.current()};
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);

STTx const emitInvokeTx = STTx(ttINVOKE, [&](STObject& obj) {});

{
// PREREQUISITE_NOT_MET
auto hookCtx =
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
auto& api = hookCtx.api();
auto tx = emitInvokeTx;
Serializer s = tx.getSerializer();
s.add8(0); // invalid value
auto const result = api.prepare(s.slice());
BEAST_EXPECT(result.error() == PREREQUISITE_NOT_MET);
}

{
// Invalid txn
auto hookCtx = makeStubHookContext(
applyCtx,
alice.id(),
alice.id(),
{
.expected_etxn_count = 1,
});
auto& api = hookCtx.api();
auto tx = emitInvokeTx;
Serializer s = tx.getSerializer();
s.add8(0); // invalid value
auto const result = api.prepare(s.slice());
BEAST_EXPECT(result.error() == INVALID_ARGUMENT);
}

{
// success
auto hookCtx = makeStubHookContext(
applyCtx,
alice.id(),
alice.id(),
{
.expected_etxn_count = 1,
});
auto& api = hookCtx.api();
auto tx = emitInvokeTx;
Serializer s = tx.getSerializer();
auto const result = api.prepare(s.slice());
BEAST_EXPECT(result.has_value());

SerialIter sit(Slice(result.value().data(), result.value().size()));
STObject st(sit, sfGeneric);
BEAST_EXPECT(st.getFieldAmount(sfFee) > XRPAmount(0));
BEAST_EXPECT(st.getFieldU32(sfSequence) == 0);
BEAST_EXPECT(st.getAccountID(sfAccount) == alice.id());
auto const seq = applyCtx.view().info().seq;
BEAST_EXPECT(st.getFieldU32(sfFirstLedgerSequence) == seq + 1);
BEAST_EXPECT(st.getFieldU32(sfLastLedgerSequence) == seq + 5);
BEAST_EXPECT(st.isFieldPresent(sfEmitDetails));

auto const result2 =
api.emit(Slice(result.value().data(), result.value().size()));
BEAST_EXPECT(result2.has_value());
}
}

void
test_emit(FeatureBitset features)
{
Expand Down Expand Up @@ -4441,6 +4521,7 @@ class HookAPI_test : public beast::unit_test::suite
test_rollback(features);
testGuards(features);

test_prepare(features);
test_emit(features);
test_etxn_burden(features);
test_etxn_generation(features);
Expand Down
Loading
Loading