Files
rippled/src/libxrpl/tx/ApplyContext.cpp
Denis Angell 42cbecb9e4 feature
Co-authored-by: Mayukha Vadari <mvadari@gmail.com>
2026-04-06 10:42:02 -04:00

151 lines
4.2 KiB
C++

#include <xrpl/tx/ApplyContext.h>
//
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/json/to_string.h>
#include <xrpl/tx/invariants/InvariantCheck.h>
namespace xrpl {
ApplyContext::ApplyContext(
ServiceRegistry& registry_,
OpenView& base,
std::optional<uint256 const> const& parentBatchId,
STTx const& tx_,
TER preclaimResult_,
XRPAmount baseFee_,
ApplyFlags flags,
beast::Journal journal_)
: registry(registry_)
, tx(tx_)
, preclaimResult(preclaimResult_)
, baseFee(baseFee_)
, journal(journal_)
, base_(base)
, flags_(flags)
, parentBatchId_(parentBatchId)
{
XRPL_ASSERT(
parentBatchId.has_value() == ((flags_ & tapBATCH) == tapBATCH),
"Parent Batch ID should be set if batch apply flag is set");
view_.emplace(&base_.view(), flags_);
}
void
ApplyContext::discard()
{
base_.discard();
view_.emplace(&base_.view(), flags_);
}
void
ApplyContext::finalize()
{
base_.commit();
view_.emplace(&base_.view(), flags_);
}
std::optional<TxMeta>
ApplyContext::apply(TER ter)
{
if (wasmReturnCode_.has_value())
{
view_->setWasmReturnCode(*wasmReturnCode_);
}
view_->setGasUsed(gasUsed_);
return view_->apply(
base_.view(), tx, ter, parentBatchId_, (flags_ & tapDRY_RUN) != 0u, journal);
}
std::size_t
ApplyContext::size()
{
return view_->size();
}
void
ApplyContext::visit(
std::function<void(
uint256 const&,
bool,
std::shared_ptr<SLE const> const&,
std::shared_ptr<SLE const> const&)> const& func)
{
view_->visit(base_.view(), func);
}
TER
ApplyContext::failInvariantCheck(TER const result)
{
// If we already failed invariant checks before and we are now attempting to
// only charge a fee, and even that fails the invariant checks something is
// very wrong. We switch to tefINVARIANT_FAILED, which does NOT get included
// in a ledger.
return (result == tecINVARIANT_FAILED || result == tefINVARIANT_FAILED)
? TER{tefINVARIANT_FAILED}
: TER{tecINVARIANT_FAILED};
}
template <std::size_t... Is>
TER
ApplyContext::checkInvariantsHelper(
TER const result,
XRPAmount const fee,
std::index_sequence<Is...>)
{
try
{
auto checkers = getInvariantChecks();
// call each check's per-entry method
visit([&checkers](
uint256 const& index,
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) {
(..., std::get<Is>(checkers).visitEntry(isDelete, before, after));
});
// Note: do not replace this logic with a `...&&` fold expression.
// The fold expression will only run until the first check fails (it
// short-circuits). While the logic is still correct, the log
// message won't be. Every failed invariant should write to the log,
// not just the first one.
std::array<bool, sizeof...(Is)> const finalizers{
{std::get<Is>(checkers).finalize(tx, result, fee, *view_, journal)...}};
// call each check's finalizer to see that it passes
if (!std::all_of(finalizers.cbegin(), finalizers.cend(), [](auto const& b) { return b; }))
{
JLOG(journal.fatal()) << "Transaction has failed one or more invariants: "
<< to_string(tx.getJson(JsonOptions::none));
return failInvariantCheck(result);
}
}
catch (std::exception const& ex)
{
JLOG(journal.fatal()) << "Transaction caused an exception in an invariant"
<< ", ex: " << ex.what()
<< ", tx: " << to_string(tx.getJson(JsonOptions::none));
return failInvariantCheck(result);
}
return result;
}
TER
ApplyContext::checkInvariants(TER const result, XRPAmount const fee)
{
XRPL_ASSERT(
isTesSuccess(result) || isTecClaim(result),
"xrpl::ApplyContext::checkInvariants : is tesSUCCESS or tecCLAIM");
return checkInvariantsHelper(
result, fee, std::make_index_sequence<std::tuple_size<InvariantChecks>::value>{});
}
} // namespace xrpl