From e729aa11eb5a14d9aa3e7bca35aae0593a2e165c Mon Sep 17 00:00:00 2001 From: Nicholas Dudfield Date: Tue, 28 Apr 2026 07:47:20 +0700 Subject: [PATCH] fix(hooks): preserve finalization semantics Keep hook result/state finalization non-fatal while enforcing the hook-export backlog cap through the transaction-level ApplyContext guard. This avoids resetting non-success tec metadata and preserves hook_again weak execution behavior. --- src/xrpld/app/hook/detail/applyHook.cpp | 24 +++--------------------- src/xrpld/app/tx/detail/Transactor.cpp | 16 ++++------------ 2 files changed, 7 insertions(+), 33 deletions(-) diff --git a/src/xrpld/app/hook/detail/applyHook.cpp b/src/xrpld/app/hook/detail/applyHook.cpp index 3822490f7..e7d00ec24 100644 --- a/src/xrpld/app/hook/detail/applyHook.cpp +++ b/src/xrpld/app/hook/detail/applyHook.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -1662,8 +1661,6 @@ hook::finalizeHookResult( std::vector> emission_txnid; std::vector exported_txnid; - std::size_t pendingExportEmissions = - ExportLedgerOps::pendingExportEmissionCount(applyCtx.view()); if (doEmit) { @@ -1678,29 +1675,12 @@ hook::finalizeHookResult( std::shared_ptr ptr = tpTrans->getSTransaction(); - bool const isExportEmission = - ExportLedgerOps::isPendingExportWorkTxn(*ptr); auto emittedId = keylet::emittedTxn(id); auto sleEmitted = applyCtx.view().peek(emittedId); if (!sleEmitted) { - if (isExportEmission) - { - if (pendingExportEmissions >= - ExportLimits::maxPendingExports) - { - JLOG(j.warn()) - << "HookExport[" << HR_ACC() - << "]: export emission limit reached pending=" - << pendingExportEmissions - << " max=" << +ExportLimits::maxPendingExports; - return tecDIR_FULL; - } - ++pendingExportEmissions; - } - auto const& emitDetails = const_cast(*ptr) .getField(sfEmitDetails) .downcast(); @@ -1740,7 +1720,9 @@ hook::finalizeHookResult( // Exported txns now flow through the emitted txn path above // (xport() pushes a ttEXPORT wrapper onto emittedTxn). - // No separate processing needed here. + // The export backlog cap is enforced after hook finalization by + // ApplyContext::checkExportEmissionLimit(), so strong and weak hook + // emissions use the same fee-only reset path. } bool const fixV2 = applyCtx.view().rules().enabled(fixXahauV2); diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index a9167a457..96ab76000 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -1972,12 +1972,7 @@ Transactor::operator()() // write state if all chains executed successfully if (isTesSuccess(result)) - { - if (auto const ter = hook::finalizeHookState( - stateMap, ctx_, ctx_.tx.getTransactionID()); - !isTesSuccess(ter)) - result = ter; - } + hook::finalizeHookState(stateMap, ctx_, ctx_.tx.getTransactionID()); // write hook results // this happens irrespective of whether final result was a tesSUCCESS @@ -1986,12 +1981,9 @@ Transactor::operator()() for (auto& hookResult : hookResults) { - if (auto const ter = hook::finalizeHookResult( - hookResult, ctx_, isTesSuccess(result)); - isTesSuccess(result) && !isTesSuccess(ter)) - result = ter; + hook::finalizeHookResult(hookResult, ctx_, isTesSuccess(result)); - if (isTesSuccess(result) && hookResult.executeAgainAsWeak) + if (hookResult.executeAgainAsWeak) { if (aawMap.find(hookResult.account) == aawMap.end()) aawMap[hookResult.account] = {hookResult.hookHash}; @@ -2287,7 +2279,7 @@ Transactor::operator()() result = tecOVERSIZE; } - if (applied) + if (applied && isTesSuccess(result)) { auto const limitResult = ctx_.checkExportEmissionLimit(result); if (!isTesSuccess(limitResult))