Compare commits

..

3 Commits

Author SHA1 Message Date
Nicholas Dudfield
a47c0c1397 test: drive the jtx reconnect through a fixture server 2026-09-28 11:59:57 +07:00
Nicholas Dudfield
b831d10b24 test: widen the jtx reconnect tests' timing margins
The first-request test slept 4 s against the server's 3 s deadline, leaving
1 s for the server's timer to fire on a loaded runner before the request
arrived; it now sleeps 8 s. The keep-alive test compared reconnects() to 0
across both requests, so a legitimate reconnect on a slow first request
failed it; it now checks the count is unchanged across the idle gap.
2026-09-28 11:38:46 +07:00
Nicholas Dudfield
7f505ea79c test: reconnect the jtx JSON-RPC client when the server closed it
The jtx Env opens its JSONRPCClient connection at construction, and the
server closes a loopback connection whose first request takes longer than
BaseHTTPPeer::timeoutSecondsLocal (3 s) to arrive. Test setup before the
first env.client().invoke() can take that long on a loaded runner, and the
client's blocking read then throws end_of_stream (Catalogue,
TransactionHistory, TransactionEntry on the Nix runners). #797 gave idle
keep-alives 30 s; the first request still gets 3 s.

invoke() now reconnects and sends the request once more when the write or
read fails because the server closed the connection, as upstream does
(XRPLF/rippled#7304, 20801d98ac, client half only).

AbstractClient::reconnects() counts those replacements so the keep-alive
test from #797 still proves the server kept its connection, and the new
first-request test proves the retry ran.

Fixes #839
2026-09-28 11:25:32 +07:00
3 changed files with 7 additions and 66 deletions

View File

@@ -36,7 +36,7 @@
XRPL_FIX (20261001, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(HookOnV2_1, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(OnChainManifests, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(OnChainManifests, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (HookMap, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FIX (GuardDepth32, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(NamedHooks, Supported::yes, VoteBehavior::DefaultNo)

View File

@@ -732,56 +732,6 @@ public:
else
BEAST_EXPECT(result.value() == baseFee + memoSize);
}
{
// fix20261001: the minimum fee is computed against the view
// being applied, not the node-local open ledger. Simulate that
// by inserting a hook definition (with a callback fee) into
// applyCtx.view() only -- it is never inserted into the open
// ledger the node otherwise reads.
uint256 const hookHash{1};
STTx const emittedTx = STTx(ttINVOKE, [&](STObject& obj) {
obj[sfAccount] = alice.id();
obj[sfSequence] = 0;
obj[sfSigningPubKey] = Slice{};
obj[sfFirstLedgerSequence] = ov.seq() + 1;
obj[sfLastLedgerSequence] = ov.seq() + 5;
obj[sfFee] = env.closed()->fees().base;
auto& emitDetails = obj.peekFieldObject(sfEmitDetails);
emitDetails[sfEmitGeneration] = 1;
emitDetails[sfEmitBurden] = 1;
emitDetails[sfEmitParentTxnID] = invokeTx.getTransactionID();
emitDetails[sfEmitNonce] = uint256();
emitDetails[sfEmitHookHash] = hookHash;
});
Serializer const emittedTxSerializer = emittedTx.getSerializer();
auto const baseline =
api.etxn_fee_base(emittedTxSerializer.slice());
BEAST_EXPECT(baseline.has_value());
auto hookDef =
std::make_shared<SLE>(keylet::hookDefinition(hookHash));
hookDef->setFieldH256(sfHookHash, hookHash);
hookDef->setFieldH256(sfHookNamespace, uint256());
hookDef->setFieldArray(sfHookParameters, STArray{});
hookDef->setFieldU16(sfHookApiVersion, 0);
hookDef->setFieldVL(sfCreateCode, std::vector<uint8_t>{});
hookDef->setFieldH256(sfHookSetTxnID, invokeTx.getTransactionID());
hookDef->setFieldU64(sfReferenceCount, 1);
hookDef->setFieldAmount(sfFee, XRPAmount{0});
hookDef->setFieldAmount(sfHookCallbackFee, XRPAmount{12345});
applyCtx.view().insert(hookDef);
auto const result = api.etxn_fee_base(emittedTxSerializer.slice());
BEAST_EXPECT(result.has_value());
if (env.closed()->rules().enabled(fix20261001))
BEAST_EXPECT(result.value() == baseline.value() + 12345);
else
BEAST_EXPECT(result.value() == baseline.value());
}
}
void
@@ -4768,7 +4718,6 @@ public:
test_otxn_generation(features);
test_etxn_details(features);
test_etxn_fee_base(features - fixHookAPI20251128);
test_etxn_fee_base(features - fix20261001);
test_etxn_fee_base(features);
test_etxn_nonce(features);
test_etxn_reserve(features);

View File

@@ -868,22 +868,14 @@ HookAPI::etxn_fee_base(ripple::Slice const& txBlob) const
std::unique_ptr<STTx const> stpTrans =
std::make_unique<STTx const>(std::ref(sitTrans));
// fix20261001: the minimum fee decides whether emit() accepts the
// txn, which is consensus-visible. Compute it against the view being
// applied, not the node-local open ledger (whose hook definitions can
// differ between nodes).
std::shared_ptr<ReadView const> hold;
ReadView const* feeView = &applyCtx.view();
if (!applyCtx.view().rules().enabled(fix20261001))
{
hold = applyCtx.app.openLedger().current();
feeView = hold.get();
}
if (!hookCtx.applyCtx.view().rules().enabled(fixHookAPI20251128))
return Transactor::calculateBaseFee(*feeView, *stpTrans).drops();
return Transactor::calculateBaseFee(
*(applyCtx.app.openLedger().current()), *stpTrans)
.drops();
return invoke_calculateBaseFee(*feeView, *stpTrans).drops();
return invoke_calculateBaseFee(
*(applyCtx.app.openLedger().current()), *stpTrans)
.drops();
}
catch (std::exception const& e)
{