From 650dc3d479137284f89959b50a300c92fe3f53d1 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Fri, 7 Nov 2025 14:16:06 +0100 Subject: [PATCH] `exitWith` --- src/test/app/ContractHostFuncImpl_test.cpp | 1 - src/xrpld/app/tx/detail/ContractCall.cpp | 7 ++-- src/xrpld/app/wasm/ContractContext.h | 2 +- src/xrpld/app/wasm/ContractHostFuncImpl.h | 3 ++ src/xrpld/app/wasm/HostFunc.h | 6 ++++ src/xrpld/app/wasm/HostFuncWrapper.h | 8 +++++ .../app/wasm/detail/ContractHostFuncImpl.cpp | 10 ++++++ src/xrpld/app/wasm/detail/HostFuncWrapper.cpp | 32 +++++++++++++++++++ src/xrpld/app/wasm/detail/WasmVM.cpp | 1 + 9 files changed, 66 insertions(+), 4 deletions(-) diff --git a/src/test/app/ContractHostFuncImpl_test.cpp b/src/test/app/ContractHostFuncImpl_test.cpp index 533beb5423..99b93d21f1 100644 --- a/src/test/app/ContractHostFuncImpl_test.cpp +++ b/src/test/app/ContractHostFuncImpl_test.cpp @@ -91,7 +91,6 @@ struct ContractHostFuncImpl_test : public beast::unit_test::suite .nextSequence = nextSequence, .otxnAccount = otxn.id(), .otxnId = txId, - .exitType = ripple::ExitType::ROLLBACK, .exitCode = -1, .dataMap = dataMap, .eventMap = eventMap, diff --git a/src/xrpld/app/tx/detail/ContractCall.cpp b/src/xrpld/app/tx/detail/ContractCall.cpp index 351cdb60b5..dc2862a759 100644 --- a/src/xrpld/app/tx/detail/ContractCall.cpp +++ b/src/xrpld/app/tx/detail/ContractCall.cpp @@ -264,7 +264,7 @@ ContractCall::doApply() .nextSequence = caSle->getFieldU32(sfSequence), .otxnAccount = account_, .otxnId = ctx_.tx.getTransactionID(), - .exitType = ripple::ExitType::ROLLBACK, + .exitReason = "", .exitCode = -1, .dataMap = dataMap, .eventMap = eventMap, @@ -306,8 +306,9 @@ ContractCall::doApply() auto ret = re.value().result; if (ret < 0) { - JLOG(j_.error()) << "Contract Failure: " << ret; + JLOG(j_.error()) << "WASM Execution Failed: " << contractCtx.result.exitReason; ctx_.setWasmReturnCode(ret); + // ctx_.setWasmReturnStr(contractCtx.result.exitReason); return tecWASM_REJECTED; } @@ -325,6 +326,7 @@ ContractCall::doApply() } ctx_.setWasmReturnCode(ret); + // ctx_.setWasmReturnStr(contractCtx.result.exitReason); ctx_.setEmittedTxns(contractCtx.result.emittedTxns); return tesSUCCESS; } @@ -333,6 +335,7 @@ ContractCall::doApply() JLOG(j_.error()) << "WASM Failure: " + transHuman(re.error()); auto const errorCode = TERtoInt(re.error()); ctx_.setWasmReturnCode(errorCode); + // ctx_.setWasmReturnStr(contractCtx.result.exitReason); return re.error(); } return tesSUCCESS; diff --git a/src/xrpld/app/wasm/ContractContext.h b/src/xrpld/app/wasm/ContractContext.h index 852d16af75..19b760d63b 100644 --- a/src/xrpld/app/wasm/ContractContext.h +++ b/src/xrpld/app/wasm/ContractContext.h @@ -86,7 +86,7 @@ struct ContractResult ripple::AccountID const otxnAccount; // AccountID for the originating transaction ripple::uint256 const otxnId; // ID for the originating transaction - ripple::ExitType exitType = ripple::ExitType::ROLLBACK; + std::string exitReason{""}; int64_t exitCode{-1}; ContractDataMap dataMap; ContractEventMap eventMap; diff --git a/src/xrpld/app/wasm/ContractHostFuncImpl.h b/src/xrpld/app/wasm/ContractHostFuncImpl.h index 55029c813f..b8f4753828 100644 --- a/src/xrpld/app/wasm/ContractHostFuncImpl.h +++ b/src/xrpld/app/wasm/ContractHostFuncImpl.h @@ -118,6 +118,9 @@ public: Expected emitEvent(std::string_view const& eventName, STJson const& eventData) override; + + Expected + exitWith(int32_t code, std::string_view const& msg) override; }; } // namespace ripple diff --git a/src/xrpld/app/wasm/HostFunc.h b/src/xrpld/app/wasm/HostFunc.h index 544535d024..97b37b4c18 100644 --- a/src/xrpld/app/wasm/HostFunc.h +++ b/src/xrpld/app/wasm/HostFunc.h @@ -643,6 +643,12 @@ struct HostFunctions return Unexpected(HostFunctionError::INTERNAL); } + virtual Expected + exitWith(int32_t code, std::string_view const& msg) + { + return Unexpected(HostFunctionError::INTERNAL); + } + virtual ~HostFunctions() = default; // LCOV_EXCL_STOP }; diff --git a/src/xrpld/app/wasm/HostFuncWrapper.h b/src/xrpld/app/wasm/HostFuncWrapper.h index 91011379fd..fa73ca19e9 100644 --- a/src/xrpld/app/wasm/HostFuncWrapper.h +++ b/src/xrpld/app/wasm/HostFuncWrapper.h @@ -714,4 +714,12 @@ emitEvent_wrap( wasm_val_vec_t const* params, wasm_val_vec_t* results); +using exitWith_proto = + int32_t(int32_t, uint8_t const*, int32_t); +wasm_trap_t* +exitWith_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + } // namespace ripple diff --git a/src/xrpld/app/wasm/detail/ContractHostFuncImpl.cpp b/src/xrpld/app/wasm/detail/ContractHostFuncImpl.cpp index c875bf8cfe..cc3f002509 100644 --- a/src/xrpld/app/wasm/detail/ContractHostFuncImpl.cpp +++ b/src/xrpld/app/wasm/detail/ContractHostFuncImpl.cpp @@ -1102,4 +1102,14 @@ ContractHostFunctionsImpl::emitEvent( } } +Expected +ContractHostFunctionsImpl::exitWith( + int32_t code, + std::string_view const& msg) +{ + contractCtx.result.exitReason = std::string(msg); + contractCtx.result.exitCode = code; + return code; +} + } // namespace ripple diff --git a/src/xrpld/app/wasm/detail/HostFuncWrapper.cpp b/src/xrpld/app/wasm/detail/HostFuncWrapper.cpp index 025d0697ff..900a79d54d 100644 --- a/src/xrpld/app/wasm/detail/HostFuncWrapper.cpp +++ b/src/xrpld/app/wasm/detail/HostFuncWrapper.cpp @@ -2562,6 +2562,38 @@ emitEvent_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) rt, params, results, hf->emitEvent(*name, *parsed), index); } +wasm_trap_t* +exitWith_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + auto* hf = reinterpret_cast(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const code = getDataInt32(rt, params, index); + if (!code) + { + return hfResult(results, code.error()); + } + + if (params->data[2].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const msg = getDataString(rt, params, index); + if (!msg) + { + return hfResult(results, msg.error()); + } + + // Call exitWith to store the exit code and reason + [[maybe_unused]] auto result = hf->exitWith(*code, *msg); + + // ALWAYS return a trap to halt execution + // This ensures WASM stops here + return static_cast(WasmEngine::instance().newTrap(*msg)); +} + // LCOV_EXCL_START namespace test { diff --git a/src/xrpld/app/wasm/detail/WasmVM.cpp b/src/xrpld/app/wasm/detail/WasmVM.cpp index 95ec4563e6..67a1c5ccb3 100644 --- a/src/xrpld/app/wasm/detail/WasmVM.cpp +++ b/src/xrpld/app/wasm/detail/WasmVM.cpp @@ -123,6 +123,7 @@ setCommonHostFunctions(HostFunctions* hfs, std::vector& i) WASM_IMPORT_FUNC2(i, emitBuiltTxn, "emit_built_txn", hfs, 2'000); WASM_IMPORT_FUNC2(i, emitTxn, "emit_txn", hfs, 2'000); WASM_IMPORT_FUNC2(i, emitEvent, "emit_event", hfs, 70); + WASM_IMPORT_FUNC2(i, exitWith, "exit_with", hfs, 70); // clang-format on }