host function error path refactor (#7639)

This commit is contained in:
pwang200
2026-07-02 13:35:46 -04:00
committed by GitHub
parent 9767d86de4
commit 8022fc33cf
12 changed files with 225 additions and 158 deletions

View File

@@ -360,6 +360,7 @@ enum TECcodes : TERUnderlyingType {
tecLIMIT_EXCEEDED = 195,
tecPSEUDO_ACCOUNT = 196,
tecPRECISION_LOSS = 197,
tecOUT_OF_GAS = 200,
};
//------------------------------------------------------------------------------

View File

@@ -113,398 +113,398 @@ public:
virtual Expected<std::uint32_t, HostFunctionError>
getLedgerSqn() const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<std::uint32_t, HostFunctionError>
getParentLedgerTime() const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Hash, HostFunctionError>
getParentLedgerHash() const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<uint32_t, HostFunctionError>
getBaseFee() const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
isAmendmentEnabled(uint256 const& amendmentId) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
isAmendmentEnabled(std::string_view const& amendmentName) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
cacheLedgerObj(uint256 const& objId, int32_t cacheIdx)
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
getTxField(SField const& fname) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
getCurrentLedgerObjField(SField const& fname) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
getLedgerObjField(int32_t cacheIdx, SField const& fname) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
getTxNestedField(FieldLocator const& locator) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
getCurrentLedgerObjNestedField(FieldLocator const& locator) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
getLedgerObjNestedField(int32_t cacheIdx, FieldLocator const& locator) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
getTxArrayLen(SField const& fname) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
getCurrentLedgerObjArrayLen(SField const& fname) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
getTxNestedArrayLen(FieldLocator const& locator) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
getCurrentLedgerObjNestedArrayLen(FieldLocator const& locator) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
getLedgerObjNestedArrayLen(int32_t cacheIdx, FieldLocator const& locator) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
updateData(Slice const& data)
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
checkSignature(Slice const& message, Slice const& signature, Slice const& pubkey) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Hash, HostFunctionError>
computeSha512HalfHash(Slice const& data) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
accountKeylet(AccountID const& account) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
ammKeylet(Asset const& issue1, Asset const& issue2) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
checkKeylet(AccountID const& account, std::uint32_t seq) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
credentialKeylet(AccountID const& subject, AccountID const& issuer, Slice const& credentialType)
const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
didKeylet(AccountID const& account) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
delegateKeylet(AccountID const& account, AccountID const& authorize) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
depositPreauthKeylet(AccountID const& account, AccountID const& authorize) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
escrowKeylet(AccountID const& account, std::uint32_t seq) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
lineKeylet(AccountID const& account1, AccountID const& account2, Currency const& currency) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
mptIssuanceKeylet(AccountID const& issuer, std::uint32_t seq) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
mptokenKeylet(MPTID const& mptid, AccountID const& holder) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
nftOfferKeylet(AccountID const& account, std::uint32_t seq) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
offerKeylet(AccountID const& account, std::uint32_t seq) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
oracleKeylet(AccountID const& account, std::uint32_t docId) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
paychanKeylet(AccountID const& account, AccountID const& destination, std::uint32_t seq) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
permissionedDomainKeylet(AccountID const& account, std::uint32_t seq) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
signersKeylet(AccountID const& account) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
ticketKeylet(AccountID const& account, std::uint32_t seq) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
vaultKeylet(AccountID const& account, std::uint32_t seq) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
getNFT(AccountID const& account, uint256 const& nftId) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
getNFTIssuer(uint256 const& nftId) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<std::uint32_t, HostFunctionError>
getNFTTaxon(uint256 const& nftId) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
getNFTFlags(uint256 const& nftId) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
getNFTTransferFee(uint256 const& nftId) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<std::uint32_t, HostFunctionError>
getNFTSerial(uint256 const& nftId) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
trace(std::string_view const& msg, Slice const& data, bool asHex) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
traceNum(std::string_view const& msg, int64_t data) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
traceAccount(std::string_view const& msg, AccountID const& account) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
traceFloat(std::string_view const& msg, Slice const& data) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
traceAmount(std::string_view const& msg, STAmount const& amount) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
floatFromInt(int64_t x, int32_t mode) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
floatFromUint(uint64_t x, int32_t mode) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
floatFromSTAmount(STAmount const& x, int32_t mode) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
floatFromSTNumber(STNumber const& x, int32_t mode) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int64_t, HostFunctionError>
floatToInt(Slice const& x, int32_t mode) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<FloatPair, HostFunctionError>
floatToMantExp(Slice const& x) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
floatFromMantExp(int64_t mantissa, int32_t exponent, int32_t mode) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<int32_t, HostFunctionError>
floatCompare(Slice const& x, Slice const& y) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
floatAdd(Slice const& x, Slice const& y, int32_t mode) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
floatSubtract(Slice const& x, Slice const& y, int32_t mode) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
floatMultiply(Slice const& x, Slice const& y, int32_t mode) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
floatDivide(Slice const& x, Slice const& y, int32_t mode) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
floatRoot(Slice const& x, int32_t n, int32_t mode) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual Expected<Bytes, HostFunctionError>
floatPower(Slice const& x, int32_t n, int32_t mode) const
{
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
virtual ~HostFunctions() = default;

View File

@@ -2,10 +2,12 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h>
#include <xrpl/protocol/TER.h>
#include <functional>
#include <optional>
#include <stdexcept>
#include <string_view>
#include <vector>
namespace xrpl {
@@ -14,8 +16,20 @@ using Bytes = std::vector<std::uint8_t>;
using Hash = xrpl::uint256;
using FloatPair = std::pair<int64_t, int32_t>;
// Error signals that cross the wasm boundary as trap messages (the C API has no
// trap code). WasmiEngine::call maps them to TER: hfErrInternal -> tecINTERNAL,
// hfErrOutOfGas / wasmi's OutOfFuel -> tecOUT_OF_GAS, anything else ->
// tecFAILED_PROCESSING.
//
// Matched as substrings, not by equality: the C API returns the Rust Debug form
// of the error, e.g. `Error { kind: Message("HfInternal") }` or
// `Error { kind: TrapCode(OutOfFuel) }`.
std::string_view inline constexpr hfErrInternal = "HfInternal";
std::string_view inline constexpr hfErrOutOfGas = "HfOutOfGas";
std::string_view inline constexpr wasmiTrapOutOfFuel = "OutOfFuel";
enum class HostFunctionError : int32_t {
Internal = -1,
Unimplemented = -1,
FieldNotFound = -2,
BufferTooSmall = -3,
NoArray = -4,
@@ -25,7 +39,7 @@ enum class HostFunctionError : int32_t {
SlotsFull = -8,
EmptySlot = -9,
LedgerObjNotFound = -10,
Decoding = -11,
OutOfTransferLimit = -11,
DataFieldTooLarge = -12,
PointerOutOfBounds = -13,
NoMemExported = -14,
@@ -35,9 +49,6 @@ enum class HostFunctionError : int32_t {
IndexOutOfBounds = -18,
FloatInputMalformed = -19,
FloatComputationError = -20,
NoRuntime = -21,
OutOfGas = -22,
OutOfTransferLimit = -23,
};
enum class WasmTypes { WtI32, WtI64 };
@@ -61,6 +72,15 @@ struct WasmResult
};
using EscrowResult = WasmResult<int32_t>;
// Engine error when wasm does not run to completion. `cost` is the gas consumed
// when meaningful (tecOUT_OF_GAS / tecFAILED_PROCESSING; caller writes it to tx
// metadata); std::nullopt for tecINTERNAL and malformed input (no gas reported).
struct WasmTER
{
TER ter;
std::optional<int64_t> cost;
};
class FieldLocator
{
int32_t const* ptr_ = nullptr;

View File

@@ -40,7 +40,7 @@ public:
static WasmEngine&
instance();
Expected<WasmResult<int32_t>, TER>
Expected<WasmResult<int32_t>, WasmTER>
run(Bytes const& wasmCode,
HostFunctions& hfs,
int64_t gasLimit,
@@ -71,7 +71,7 @@ public:
ImportVec
createWasmImport(HostFunctions& hfs);
Expected<EscrowResult, TER>
Expected<EscrowResult, WasmTER>
runEscrowWasm(
Bytes const& wasmCode,
HostFunctions& hfs,

View File

@@ -6,6 +6,8 @@
#include <wasm.h>
#include <wasmi.h>
#include <optional>
namespace xrpl {
template <class T, void (*Create)(T*, size_t), void (*Destroy)(T*)>
@@ -120,7 +122,10 @@ using WasmImporttypeVec = WasmVec<
struct WasmiResult
{
WasmValVec r;
bool f{false}; // failure flag
// Set iff the call trapped. Holds the TER the trap was classified into
// (tecINTERNAL / tecOUT_OF_GAS / tecFAILED_PROCESSING); see
// WasmiEngine::call. std::nullopt means the call returned normally.
std::optional<TER> ter;
WasmiResult(unsigned n = 0) : r(n)
{
@@ -304,7 +309,7 @@ public:
static EnginePtr
init();
Expected<WasmResult<int32_t>, TER>
Expected<WasmResult<int32_t>, WasmTER>
run(Bytes const& wasmCode,
HostFunctions& hfs,
int64_t gas,
@@ -355,7 +360,7 @@ private:
return moduleWrap_ ? moduleWrap_->getMem() : Wmem();
}
Expected<WasmResult<int32_t>, TER>
Expected<WasmResult<int32_t>, WasmTER>
runHlp(
Bytes const& wasmCode,
HostFunctions& hfs,

View File

@@ -106,6 +106,7 @@ transResults()
MAKE_ERROR(tecLIMIT_EXCEEDED, "Limit exceeded."),
MAKE_ERROR(tecPSEUDO_ACCOUNT, "This operation is not allowed against a pseudo-account."),
MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."),
MAKE_ERROR(tecOUT_OF_GAS, "The WASM code ran out of gas during execution."),
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),

View File

@@ -1,5 +1,6 @@
#include <xrpl/basics/Expected.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Indexes.h>
@@ -13,6 +14,8 @@
#include <xrpl/tx/wasm/WasmCommon.h>
#include <cstdint>
#include <stdexcept>
#include <string>
#include <utility>
#include <variant>
@@ -121,7 +124,9 @@ getAnyFieldData(FieldValue const& variantObj)
if (uint256 const* const* u = std::get_if<uint256 const*>(&variantObj))
return Bytes((*u)->begin(), (*u)->end());
return Unexpected(HostFunctionError::Internal); // LCOV_EXCL_LINE
// Unreachable: the variant only holds the two alternatives above. If not,
// it's an xrpld bug -> tecINTERNAL (thrown, caught by HostFuncMain_wrap).
Throw<std::runtime_error>(std::string(hfErrInternal)); // LCOV_EXCL_LINE
}
static inline bool

View File

@@ -3,6 +3,7 @@
#include <xrpl/basics/Expected.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Issue.h>
@@ -31,6 +32,8 @@
#include <exception>
#include <functional>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
@@ -43,7 +46,10 @@ using SFieldCRef = std::reference_wrapper<SField const>;
constexpr int64_t unalignedGas = 50;
static inline Expected<std::int64_t, HostFunctionError>
// Charge `delta` gas; returns the remaining gas. Out-of-gas throws hfErrOutOfGas
// (-> tecOUT_OF_GAS); a failed setGas is an xrpld bug, throws hfErrInternal
// (-> tecINTERNAL). HostFuncMain_wrap turns both into traps.
static inline std::int64_t
checkGas(WasmRuntimeWrapper& rt, int64_t delta)
{
int64_t const gas = rt.getGas();
@@ -53,36 +59,16 @@ checkGas(WasmRuntimeWrapper& rt, int64_t delta)
int64_t const x = gas >= delta ? gas - delta : 0;
if (rt.setGas(x) < 0)
return Unexpected(HostFunctionError::Internal); // LCOV_EXCL_LINE
Throw<std::runtime_error>(std::string(hfErrInternal)); // LCOV_EXCL_LINE
if (gas < delta)
return Unexpected(HostFunctionError::OutOfGas);
Throw<std::runtime_error>(std::string(hfErrOutOfGas));
return x;
}
static inline Expected<std::int64_t, wasm_trap_t*>
checkGas(WasmRuntimeWrapper& rt, WasmImportFunc const& impFunc)
{
auto g = checkGas(rt, impFunc.gas);
if (!g)
{
if (g.error() == HostFunctionError::OutOfGas)
{
wasm_trap_t* const trap = // NOLINT
reinterpret_cast<wasm_trap_t*>(WasmEngine::instance().newTrap("hf out of gas"));
return Unexpected(trap);
}
wasm_trap_t* trap = reinterpret_cast<wasm_trap_t*>( // NOLINT
WasmEngine::instance().newTrap("can't set gas")); // LCOV_EXCL_LINE
return Unexpected(trap); // LCOV_EXCL_LINE
}
return *g;
}
// Transfer limit is a separate soft budget: exceeding it is a normal guest-facing
// return code, not a trap. Only a failed setTransferLimit (an xrpld bug) throws.
static inline Expected<std::int64_t, HostFunctionError>
checkTransfer(WasmRuntimeWrapper& rt, int64_t delta)
{
@@ -90,7 +76,7 @@ checkTransfer(WasmRuntimeWrapper& rt, int64_t delta)
int64_t const x = transLimit >= delta ? transLimit - delta : 0;
if (rt.setTransferLimit(x) < 0)
return Unexpected(HostFunctionError::Internal); // LCOV_EXCL_LINE
Throw<std::runtime_error>(std::string(hfErrInternal)); // LCOV_EXCL_LINE
if (transLimit < delta)
return Unexpected(HostFunctionError::OutOfTransferLimit);
@@ -98,37 +84,27 @@ checkTransfer(WasmRuntimeWrapper& rt, int64_t delta)
return x;
}
static Expected<std::tuple<HostFunctions&, WasmImportFunc const&>, wasm_trap_t*>
// On any failure here a C++ exception is thrown; HostFuncMain_wrap's catch-all
// turns it into tecINTERNAL. These conditions are all xrpld-side invariants.
static std::tuple<HostFunctions&, WasmImportFunc const&>
mainCheck(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results)
{
if (env == nullptr)
{
wasm_trap_t* trap = reinterpret_cast<wasm_trap_t*>( // NOLINT
WasmEngine::instance().newTrap("no environment")); // LCOV_EXCL_LINE
return Unexpected(trap); // LCOV_EXCL_LINE
}
Throw<std::runtime_error>(std::string(hfErrInternal)); // LCOV_EXCL_LINE
if (params == nullptr)
{
wasm_trap_t* trap = reinterpret_cast<wasm_trap_t*>( // NOLINT
WasmEngine::instance().newTrap("no params")); // LCOV_EXCL_LINE
return Unexpected(trap); // LCOV_EXCL_LINE
}
Throw<std::runtime_error>(std::string(hfErrInternal)); // LCOV_EXCL_LINE
if (results == nullptr)
{
wasm_trap_t* trap = reinterpret_cast<wasm_trap_t*>( // NOLINT
WasmEngine::instance().newTrap("no results")); // LCOV_EXCL_LINE
return Unexpected(trap); // LCOV_EXCL_LINE
}
Throw<std::runtime_error>(std::string(hfErrInternal)); // LCOV_EXCL_LINE
WasmUserData const* udata = reinterpret_cast<WasmUserData*>(env);
HostFunctions& hf = udata->first;
WasmRuntimeWrapper& rt = hf.getRT();
WasmImportFunc const& impFunc = udata->second;
if (auto g = checkGas(rt, impFunc); !g)
return Unexpected(g.error()); // LCOV_EXCL_LINE
// Charge the per-call gas. Throws (and terminates) if out of gas.
checkGas(rt, impFunc.gas);
return std::tie(hf, impFunc);
}
@@ -387,9 +363,10 @@ getDataLocator(WasmRuntimeWrapper& runtime, wasm_val_vec_t const* params, int32_
if ((p & (alignof(int32_t) - 1)) != 0u)
{ // unaligned
// Use gas and transfer limit for copying
if (auto g = checkGas(runtime, unalignedGas); !g)
return Unexpected(g.error());
// Use gas and transfer limit for copying. checkGas throws (and
// terminates execution) if out of gas; checkTransfer keeps returning a
// guest-facing code when the transfer limit is exceeded.
checkGas(runtime, unalignedGas);
if (auto t = checkTransfer(runtime, slice->size()); !t)
return Unexpected(t.error());
@@ -435,7 +412,7 @@ returnResult(
if constexpr (std::is_same_v<T, Bytes>)
{
if (index < 0 || index + 1 >= params->size)
return hfResult(results, HostFunctionError::Internal); // LCOV_EXCL_LINE
Throw<std::runtime_error>(std::string(hfErrInternal)); // LCOV_EXCL_LINE
auto const dataResult = setData(
runtime,
@@ -448,7 +425,7 @@ returnResult(
else if constexpr (std::is_same_v<T, Hash>)
{
if (index < 0 || index + 1 >= params->size)
return hfResult(results, HostFunctionError::Internal); // LCOV_EXCL_LINE
Throw<std::runtime_error>(std::string(hfErrInternal)); // LCOV_EXCL_LINE
auto const dataResult = setData(
runtime,
@@ -465,7 +442,7 @@ returnResult(
else if constexpr (std::is_same_v<T, std::uint32_t>)
{
if (index < 0 || index + 1 >= params->size)
return hfResult(results, HostFunctionError::Internal); // LCOV_EXCL_LINE
Throw<std::runtime_error>(std::string(hfErrInternal)); // LCOV_EXCL_LINE
auto const resultValue = adjustWasmEndianess(res.value());
auto const dataResult = setData(
@@ -479,7 +456,7 @@ returnResult(
else if constexpr (std::is_same_v<T, int64_t>)
{
if (index < 0 || index + 1 >= params->size)
return hfResult(results, HostFunctionError::Internal); // LCOV_EXCL_LINE
Throw<std::runtime_error>(std::string(hfErrInternal)); // LCOV_EXCL_LINE
auto const resultValue = adjustWasmEndianess(res.value());
auto const dataResult = setData(
@@ -493,7 +470,7 @@ returnResult(
else if constexpr (std::is_same_v<T, FloatPair>)
{
if (index < 0 || index + 3 >= params->size)
return hfResult(results, HostFunctionError::Internal); // LCOV_EXCL_LINE
Throw<std::runtime_error>(std::string(hfErrInternal)); // LCOV_EXCL_LINE
auto const mantissa = adjustWasmEndianess(res->first);
auto const r1 = setData(
@@ -533,11 +510,7 @@ HostFuncMain_wrap(WASM_CB_PARAMS_LIST)
try
{
auto const mc = mainCheck(env, params, results);
if (!mc)
return mc.error();
auto& [hf, impFunc] = *mc;
auto [hf, impFunc] = mainCheck(env, params, results);
hfName = impFunc.name;
auto* fWrap = reinterpret_cast<wasmSecondaryCbFuncType*>(impFunc.wrap);
return fWrap(hf, params, results);
@@ -547,8 +520,11 @@ HostFuncMain_wrap(WASM_CB_PARAMS_LIST)
#ifdef DEBUG_OUTPUT
std::cerr << "Hostfunction " << hfName << " exception: " << e.what() << std::endl;
#endif
// Normalize to the two boundary signals: explicit out-of-gas, else any
// exception (including stray ones from helpers) is an internal fault.
bool const oog = std::string_view(e.what()) == hfErrOutOfGas;
wasm_trap_t* trap = reinterpret_cast<wasm_trap_t*>( // NOLINT
WasmEngine::instance().newTrap(e.what())); // LCOV_EXCL_LINE
WasmEngine::instance().newTrap(std::string(oog ? hfErrOutOfGas : hfErrInternal)));
return trap;
}
catch (...)
@@ -556,12 +532,12 @@ HostFuncMain_wrap(WASM_CB_PARAMS_LIST)
#ifdef DEBUG_OUTPUT
std::cerr << "Hostfunction " << hfName << " unknown exception." << std::endl;
#endif
wasm_trap_t* trap = reinterpret_cast<wasm_trap_t*>( // NOLINT
WasmEngine::instance().newTrap("Unknown exception")); // LCOV_EXCL_LINE
wasm_trap_t* trap = reinterpret_cast<wasm_trap_t*>( // NOLINT
WasmEngine::instance().newTrap(std::string(hfErrInternal))); // LCOV_EXCL_LINE
return trap;
}
return nullptr;
return nullptr; // LCOV_EXCL_LINE
}
//----------------------------------------------------------------------------------------------------------------------

View File

@@ -115,7 +115,7 @@ createWasmImport(HostFunctions& hfs)
return i;
}
Expected<EscrowResult, TER>
Expected<EscrowResult, WasmTER>
runEscrowWasm(
Bytes const& wasmCode,
HostFunctions& hfs,
@@ -133,9 +133,12 @@ runEscrowWasm(
if (!ret)
{
#ifdef DEBUG_OUTPUT
std::cout << ", error: " << ret.error() << std::endl;
std::cout << ", error: " << ret.error().ter << std::endl;
#endif
return Unexpected<TER>(ret.error());
// Carries the TER (tecOUT_OF_GAS / tecFAILED_PROCESSING / tecINTERNAL /
// temBAD_AMOUNT) and, when meaningful, the gas consumed. The caller is
// responsible for writing that gas to tx metadata.
return Unexpected(ret.error());
}
#ifdef DEBUG_OUTPUT
@@ -174,7 +177,7 @@ WasmEngine::instance()
return e;
}
Expected<WasmResult<int32_t>, TER>
Expected<WasmResult<int32_t>, WasmTER>
WasmEngine::run(
Bytes const& wasmCode,
HostFunctions& hfs,

View File

@@ -22,6 +22,7 @@
#include <limits>
#include <memory>
#include <mutex>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
@@ -78,6 +79,26 @@ printWasmError(std::string_view msg, wasm_trap_t* trap, beast::Journal jlog)
}
// LCOV_EXCL_STOP
// Extract a trap's message into a std::string (the only signal the C API gives
// for classification; see the trap-signal constants in WasmCommon.h). Does not
// take ownership of `trap`.
std::string
trapMessage(wasm_trap_t* trap)
{
if (trap == nullptr)
return {}; // LCOV_EXCL_LINE
wasm_byte_vec_t msg WASM_EMPTY_VEC;
wasm_trap_message(trap, &msg);
std::string out;
if (msg.size != 0u)
{
// wasm_trap_message NUL-terminates, so drop the trailing NUL.
out.assign(msg.data, msg.size - 1);
wasm_byte_vec_delete(&msg);
}
return out;
}
} // namespace
class WasmiRuntimeWrapper : public WasmRuntimeWrapper
@@ -679,7 +700,24 @@ WasmiEngine::call(FuncInfo const& f, std::vector<wasm_val_t>& in)
if (trap)
{
ret.f = true;
// Classify the trap into a TER by matching tokens as substrings of the
// message (see the trap-signal constants in WasmCommon.h for why).
std::string const msg = trapMessage(trap);
auto const has = [&msg](std::string_view token) {
return msg.find(token) != std::string::npos;
};
if (has(hfErrInternal))
{
ret.ter = tecINTERNAL;
}
else if (has(hfErrOutOfGas) || has(wasmiTrapOutOfFuel))
{
ret.ter = tecOUT_OF_GAS;
}
else
{
ret.ter = tecFAILED_PROCESSING;
}
printWasmError("failure to call func", trap, j_);
}
@@ -719,7 +757,7 @@ checkImports(ImportVec const& imports, HostFunctions* hfs)
}
}
Expected<WasmResult<int32_t>, TER>
Expected<WasmResult<int32_t>, WasmTER>
WasmiEngine::run(
Bytes const& wasmCode,
HostFunctions& hfs,
@@ -730,7 +768,7 @@ WasmiEngine::run(
beast::Journal j)
{
if (gas <= 0)
return Unexpected<TER>(temBAD_AMOUNT);
return Unexpected(WasmTER{.ter = temBAD_AMOUNT, .cost = std::nullopt});
try
{
@@ -747,10 +785,12 @@ WasmiEngine::run(
printWasmError(std::string("exception: unknown"), nullptr, j);
}
// LCOV_EXCL_STOP
return Unexpected<TER>(tecFAILED_PROCESSING);
// An exception escaping the engine is an xrpld-side fault -> tecINTERNAL,
// no gas. Genuine wasm faults don't throw; they surface as traps in runHlp.
return Unexpected(WasmTER{.ter = tecINTERNAL, .cost = std::nullopt});
}
Expected<WasmResult<int32_t>, TER>
Expected<WasmResult<int32_t>, WasmTER>
WasmiEngine::runHlp(
Bytes const& wasmCode,
HostFunctions& hfs,
@@ -793,8 +833,24 @@ WasmiEngine::runHlp(
auto const res = call<1>(f, p);
if (res.f)
Throw<std::runtime_error>("<" + std::string(funcName) + "> failure");
if (gas == -1)
gas = std::numeric_limits<decltype(gas)>::max();
if (res.ter.has_value())
{
// call() already classified the trap (see WasmiEngine::call).
// tecINTERNAL is an xrpld-side bug: report no gas.
if (*res.ter == tecINTERNAL)
return Unexpected(WasmTER{.ter = tecINTERNAL, .cost = std::nullopt});
// Out-of-gas / wasm faults report gas (caller writes it to metadata).
// Force fuel to 0 on out-of-gas so cost is the full limit (wasmi leaves
// nonzero leftover fuel on its own out-of-fuel trap).
if (*res.ter == tecOUT_OF_GAS)
iw.setGas(0);
return Unexpected(WasmTER{.ter = *res.ter, .cost = gas - moduleWrap_->getGas()});
}
if (res.r.empty())
{
@@ -809,8 +865,6 @@ WasmiEngine::runHlp(
"> return type mismatch, ret: " + std::to_string(static_cast<int>(res.r[0].kind)));
}
if (gas == -1)
gas = std::numeric_limits<decltype(gas)>::max();
WasmResult<int32_t> const ret{.result = res.r[0].of.i32, .cost = gas - moduleWrap_->getGas()};
// #ifdef DEBUG_OUTPUT

View File

@@ -135,7 +135,7 @@ public:
return Bytes{s.begin(), s.end()};
}
return Unexpected(HostFunctionError::Internal);
return Unexpected(HostFunctionError::Unimplemented);
}
Expected<Bytes, HostFunctionError>

View File

@@ -53,13 +53,13 @@ struct Wasm_test : public beast::unit_test::Suite
{
void
checkResult(
Expected<WasmResult<int32_t>, TER> re,
Expected<WasmResult<int32_t>, WasmTER> re,
int32_t expectedResult,
int64_t expectedCost,
std::source_location const location = std::source_location::current())
{
auto const lineStr = " (" + std::to_string(location.line()) + ")";
if (BEAST_EXPECTS(re.has_value(), transToken(re.error()) + lineStr))
if (BEAST_EXPECTS(re.has_value(), transToken(re.error().ter) + lineStr))
{
BEAST_EXPECTS(re->result == expectedResult, std::to_string(re->result) + lineStr);
BEAST_EXPECTS(re->cost == expectedCost, std::to_string(re->cost) + lineStr);
@@ -258,8 +258,10 @@ struct Wasm_test : public beast::unit_test::Suite
if (BEAST_EXPECT(!re))
{
// Running out of gas now terminates with tecOUT_OF_GAS (was
// previously collapsed into tecFAILED_PROCESSING).
BEAST_EXPECTS(
re.error() == tecFAILED_PROCESSING, std::to_string(TERtoInt(re.error())));
re.error().ter == tecOUT_OF_GAS, std::to_string(TERtoInt(re.error().ter)));
}
env.close();
@@ -286,7 +288,7 @@ struct Wasm_test : public beast::unit_test::Suite
TestHostFunctions hfs(env);
auto re = runEscrowWasm(allHFWasm, hfs, -1, escrowFunctionName, {});
BEAST_EXPECT(!re.has_value());
BEAST_EXPECT(re.error() == temBAD_AMOUNT);
BEAST_EXPECT(re.error().ter == temBAD_AMOUNT);
}
{
@@ -294,7 +296,7 @@ struct Wasm_test : public beast::unit_test::Suite
TestHostFunctions hfs(env);
auto re = runEscrowWasm(allHFWasm, hfs, 0, escrowFunctionName, {});
BEAST_EXPECT(!re.has_value());
BEAST_EXPECT(re.error() == temBAD_AMOUNT);
BEAST_EXPECT(re.error().ter == temBAD_AMOUNT);
}
{
@@ -379,7 +381,7 @@ struct Wasm_test : public beast::unit_test::Suite
auto& engine = WasmEngine::instance();
auto re = engine.run(badAlignWasm, hfs, 1'000'000, "test", {}, imports, env.journal);
if (BEAST_EXPECTS(re, transToken(re.error())))
if (BEAST_EXPECTS(re, transToken(re.error().ter)))
{
BEAST_EXPECTS(re->result == 0x47308594, std::to_string(re->result));
}