test(hooks): pin export reservation budget

This commit is contained in:
Nicholas Dudfield
2026-06-26 21:31:34 +07:00
parent a78f09a0fe
commit 72f73ccbad
5 changed files with 80 additions and 2 deletions

View File

@@ -222,7 +222,6 @@ TYPED_SFIELD(sfHookCanEmit, UINT256, 96)
TYPED_SFIELD(sfEmittedTxnID, UINT256, 97)
TYPED_SFIELD(sfGovernanceMarks, UINT256, 98)
TYPED_SFIELD(sfGovernanceFlags, UINT256, 99)
TYPED_SFIELD(sfEntropyDigest, UINT256, 100)
// number (common)
TYPED_SFIELD(sfNumber, NUMBER, 1)

View File

@@ -623,5 +623,4 @@ TRANSACTION(ttCONSENSUS_ENTROPY, 105, ConsensusEntropy, ({
{sfDigest, soeREQUIRED},
{sfEntropyCount, soeREQUIRED},
{sfEntropyTier, soeREQUIRED},
{sfBlob, soeOPTIONAL},
}))

View File

@@ -948,6 +948,83 @@ public:
}
}
void
test_xport_reserve(FeatureBitset features)
{
testcase("Test xport_reserve");
using namespace jtx;
using namespace hook_api;
auto const alice = Account{"alice"};
Env env{*this, features};
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
OpenView ov{*env.current()};
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
{
// ALREADY_SET
StubHookContext stubCtx{.expected_export_count = 1};
auto hookCtx =
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
auto& api = hookCtx.api();
auto const result = api.xport_reserve(2);
BEAST_EXPECT(result.error() == ALREADY_SET);
}
{
// TOO_SMALL
auto hookCtx =
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
auto& api = hookCtx.api();
auto const result = api.xport_reserve(0);
BEAST_EXPECT(result.error() == TOO_SMALL);
}
{
// TOO_BIG
auto hookCtx =
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
auto& api = hookCtx.api();
auto const result = api.xport_reserve(hook_api::max_export + 1);
BEAST_EXPECT(result.error() == TOO_BIG);
}
{
// SUCCESS
auto hookCtx =
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
auto& api = hookCtx.api();
auto const result = api.xport_reserve(2);
BEAST_EXPECT(result.has_value());
BEAST_EXPECT(hookCtx.expected_export_count == 2);
BEAST_EXPECT(hookCtx.expected_etxn_count == 2);
}
{
// xport_reserve composes with an earlier emit reservation.
auto hookCtx =
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
auto& api = hookCtx.api();
BEAST_EXPECT(api.etxn_reserve(1).has_value());
BEAST_EXPECT(api.xport_reserve(2).has_value());
BEAST_EXPECT(hookCtx.expected_export_count == 2);
BEAST_EXPECT(hookCtx.expected_etxn_count == 3);
}
{
// xport_reserve consumes the shared emitted-txn reservation slot,
// so a later etxn_reserve cannot reset it.
auto hookCtx =
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
auto& api = hookCtx.api();
BEAST_EXPECT(api.xport_reserve(1).has_value());
BEAST_EXPECT(api.etxn_reserve(1).error() == ALREADY_SET);
BEAST_EXPECT(hookCtx.expected_export_count == 1);
BEAST_EXPECT(hookCtx.expected_etxn_count == 1);
}
}
void
test_fee_base(FeatureBitset features)
{
@@ -4855,6 +4932,7 @@ public:
test_etxn_fee_base(features);
test_etxn_nonce(features);
test_etxn_reserve(features);
test_xport_reserve(features);
test_fee_base(features);
test_otxn_field(features);

View File

@@ -91,6 +91,7 @@ struct StubHookContext
uint16_t emit_nonce_counter{0};
uint16_t ledger_nonce_counter{0};
int64_t expected_etxn_count{-1};
int64_t expected_export_count{-1};
std::map<ripple::uint256, bool> nonce_used{};
uint32_t generation = 0;
uint64_t burden = 0;

View File

@@ -111,6 +111,7 @@ makeStubHookContext(
.emit_nonce_counter = stubHookContext.emit_nonce_counter,
.ledger_nonce_counter = stubHookContext.ledger_nonce_counter,
.expected_etxn_count = stubHookContext.expected_etxn_count,
.expected_export_count = stubHookContext.expected_export_count,
.nonce_used = stubHookContext.nonce_used,
.generation = stubHookContext.generation,
.burden = stubHookContext.burden,