mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-06 18:26:51 +00:00
441 lines
13 KiB
C++
441 lines
13 KiB
C++
#include <xrpl/tx/applySteps.h>
|
|
#pragma push_macro("TRANSACTION")
|
|
#undef TRANSACTION
|
|
|
|
// Do nothing
|
|
#define TRANSACTION(...)
|
|
#define TRANSACTION_INCLUDE 1
|
|
|
|
#include <xrpl/protocol/detail/transactions.macro>
|
|
|
|
#undef TRANSACTION
|
|
#pragma pop_macro("TRANSACTION")
|
|
|
|
// DO NOT INCLUDE TRANSACTOR HEADER FILES HERE.
|
|
// See the instructions at the top of transactions.macro instead.
|
|
|
|
#include <xrpl/core/ServiceRegistry.h>
|
|
#include <xrpl/protocol/TxFormats.h>
|
|
|
|
#include <stdexcept>
|
|
|
|
namespace xrpl {
|
|
|
|
namespace {
|
|
|
|
struct UnknownTxnType : std::exception
|
|
{
|
|
TxType txnType;
|
|
UnknownTxnType(TxType t) : txnType{t}
|
|
{
|
|
}
|
|
};
|
|
|
|
// Call a lambda with the concrete transaction type as a template parameter
|
|
// throw an "UnknownTxnType" exception on error
|
|
template <class F>
|
|
auto
|
|
with_txn_type(Rules const& rules, TxType txnType, F&& f)
|
|
{
|
|
// These global updates really should have been for every Transaction
|
|
// step: preflight, preclaim, calculateBaseFee, and doApply. Unfortunately,
|
|
// they were only included in doApply (via Transactor::operator()). That may
|
|
// have been sufficient when the changes were only related to operations
|
|
// that mutated data, but some features will now change how they read data,
|
|
// so these need to be more global.
|
|
//
|
|
// To prevent unintentional side effects on existing checks, they will be
|
|
// set for every operation only once SingleAssetVault (or later
|
|
// LendingProtocol) are enabled.
|
|
//
|
|
// See also Transactor::operator().
|
|
//
|
|
std::optional<NumberSO> stNumberSO;
|
|
std::optional<CurrentTransactionRulesGuard> rulesGuard;
|
|
std::optional<NumberMantissaScaleGuard> mantissaScaleGuard;
|
|
if (rules.enabled(featureSingleAssetVault) || rules.enabled(featureLendingProtocol))
|
|
{
|
|
// raii classes for the current ledger rules.
|
|
// fixUniversalNumber predates the rulesGuard and should be replaced.
|
|
stNumberSO.emplace(rules.enabled(fixUniversalNumber));
|
|
rulesGuard.emplace(rules);
|
|
}
|
|
else
|
|
{
|
|
// Without those features enabled, always use the old number rules.
|
|
mantissaScaleGuard.emplace(MantissaRange::small);
|
|
}
|
|
|
|
switch (txnType)
|
|
{
|
|
#pragma push_macro("TRANSACTION")
|
|
#undef TRANSACTION
|
|
|
|
#define TRANSACTION(tag, value, name, ...) \
|
|
case tag: \
|
|
return f.template operator()<name>();
|
|
|
|
#include <xrpl/protocol/detail/transactions.macro>
|
|
|
|
#undef TRANSACTION
|
|
#pragma pop_macro("TRANSACTION")
|
|
default:
|
|
throw UnknownTxnType(txnType);
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
// Templates so preflight does the right thing with T::ConsequencesFactory.
|
|
//
|
|
// This could be done more easily using if constexpr, but Visual Studio
|
|
// 2017 doesn't handle if constexpr correctly. So once we're no longer
|
|
// building with Visual Studio 2017 we can consider replacing the four
|
|
// templates with a single template function that uses if constexpr.
|
|
//
|
|
// For Transactor::Normal
|
|
//
|
|
|
|
// clang-format off
|
|
// Current formatter for rippled is based on clang-10, which does not handle `requires` clauses
|
|
template <class T>
|
|
requires(T::ConsequencesFactory == Transactor::Normal)
|
|
TxConsequences
|
|
consequences_helper(PreflightContext const& ctx)
|
|
{
|
|
return TxConsequences(ctx.tx);
|
|
};
|
|
|
|
// For Transactor::Blocker
|
|
template <class T>
|
|
requires(T::ConsequencesFactory == Transactor::Blocker)
|
|
TxConsequences
|
|
consequences_helper(PreflightContext const& ctx)
|
|
{
|
|
return TxConsequences(ctx.tx, TxConsequences::blocker);
|
|
};
|
|
|
|
// For Transactor::Custom
|
|
template <class T>
|
|
requires(T::ConsequencesFactory == Transactor::Custom)
|
|
TxConsequences
|
|
consequences_helper(PreflightContext const& ctx)
|
|
{
|
|
return T::makeTxConsequences(ctx);
|
|
};
|
|
// clang-format on
|
|
|
|
static std::pair<NotTEC, TxConsequences>
|
|
invoke_preflight(PreflightContext const& ctx)
|
|
{
|
|
try
|
|
{
|
|
return with_txn_type(ctx.rules, ctx.tx.getTxnType(), [&]<typename T>() {
|
|
auto const tec = Transactor::invokePreflight<T>(ctx);
|
|
return std::make_pair(
|
|
tec, isTesSuccess(tec) ? consequences_helper<T>(ctx) : TxConsequences{tec});
|
|
});
|
|
}
|
|
catch (UnknownTxnType const& e)
|
|
{
|
|
// Should never happen
|
|
// LCOV_EXCL_START
|
|
JLOG(ctx.j.fatal()) << "Unknown transaction type in preflight: " << e.txnType;
|
|
UNREACHABLE("xrpl::invoke_preflight : unknown transaction type");
|
|
return {temUNKNOWN, TxConsequences{temUNKNOWN}};
|
|
// LCOV_EXCL_STOP
|
|
}
|
|
}
|
|
|
|
static TER
|
|
invoke_preclaim(PreclaimContext const& ctx)
|
|
{
|
|
try
|
|
{
|
|
// use name hiding to accomplish compile-time polymorphism of static
|
|
// class functions for Transactor and derived classes.
|
|
return with_txn_type(ctx.view.rules(), ctx.tx.getTxnType(), [&]<typename T>() -> TER {
|
|
// preclaim functionality is divided into two sections:
|
|
// 1. Up to and including the signature check: returns NotTEC.
|
|
// All transaction checks before and including checkSign
|
|
// MUST return NotTEC, or something more restrictive.
|
|
// Allowing tec results in these steps risks theft or
|
|
// destruction of funds, as a fee will be charged before the
|
|
// signature is checked.
|
|
// 2. After the signature check: returns TER.
|
|
|
|
// If the transactor requires a valid account and the
|
|
// transaction doesn't list one, preflight will have already
|
|
// a flagged a failure.
|
|
auto const id = ctx.tx.getAccountID(sfAccount);
|
|
|
|
if (id != beast::zero)
|
|
{
|
|
if (NotTEC const preSigResult = [&]() -> NotTEC {
|
|
if (NotTEC const result = T::checkSeqProxy(ctx.view, ctx.tx, ctx.j))
|
|
return result;
|
|
|
|
if (NotTEC const result = T::checkPriorTxAndLastLedger(ctx))
|
|
return result;
|
|
|
|
if (NotTEC const result = T::checkPermission(ctx.view, ctx.tx))
|
|
return result;
|
|
|
|
if (NotTEC const result = T::checkSign(ctx))
|
|
return result;
|
|
|
|
return tesSUCCESS;
|
|
}())
|
|
return preSigResult;
|
|
|
|
if (TER const result = T::checkFee(ctx, calculateBaseFee(ctx.view, ctx.tx)))
|
|
return result;
|
|
}
|
|
|
|
return T::preclaim(ctx);
|
|
});
|
|
}
|
|
catch (UnknownTxnType const& e)
|
|
{
|
|
// Should never happen
|
|
// LCOV_EXCL_START
|
|
JLOG(ctx.j.fatal()) << "Unknown transaction type in preclaim: " << e.txnType;
|
|
UNREACHABLE("xrpl::invoke_preclaim : unknown transaction type");
|
|
return temUNKNOWN;
|
|
// LCOV_EXCL_STOP
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Calculates the base fee for a given transaction.
|
|
*
|
|
* This function determines the base fee required for the specified transaction
|
|
* by invoking the appropriate fee calculation logic based on the transaction
|
|
* type. It uses a type-dispatch mechanism to select the correct calculation
|
|
* method.
|
|
*
|
|
* @param view The ledger view to use for fee calculation.
|
|
* @param tx The transaction for which the base fee is to be calculated.
|
|
* @return The calculated base fee as an XRPAmount.
|
|
*
|
|
* @throws std::exception If an error occurs during fee calculation, including
|
|
* but not limited to unknown transaction types or internal errors, the function
|
|
* logs an error and returns an XRPAmount of zero.
|
|
*/
|
|
static XRPAmount
|
|
invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
|
|
{
|
|
try
|
|
{
|
|
return with_txn_type(view.rules(), tx.getTxnType(), [&]<typename T>() {
|
|
return T::calculateBaseFee(view, tx);
|
|
});
|
|
}
|
|
catch (UnknownTxnType const& e)
|
|
{
|
|
// LCOV_EXCL_START
|
|
UNREACHABLE("xrpl::invoke_calculateBaseFee : unknown transaction type");
|
|
return XRPAmount{0};
|
|
// LCOV_EXCL_STOP
|
|
}
|
|
}
|
|
|
|
TxConsequences::TxConsequences(NotTEC pfResult)
|
|
: isBlocker_(false)
|
|
, fee_(beast::zero)
|
|
, potentialSpend_(beast::zero)
|
|
, seqProx_(SeqProxy::sequence(0))
|
|
, sequencesConsumed_(0)
|
|
{
|
|
XRPL_ASSERT(
|
|
!isTesSuccess(pfResult), "xrpl::TxConsequences::TxConsequences : is not tesSUCCESS");
|
|
}
|
|
|
|
TxConsequences::TxConsequences(STTx const& tx)
|
|
: isBlocker_(false)
|
|
, fee_(tx[sfFee].native() && !tx[sfFee].negative() ? tx[sfFee].xrp() : beast::zero)
|
|
, potentialSpend_(beast::zero)
|
|
, seqProx_(tx.getSeqProxy())
|
|
, sequencesConsumed_(tx.getSeqProxy().isSeq() ? 1 : 0)
|
|
{
|
|
}
|
|
|
|
TxConsequences::TxConsequences(STTx const& tx, Category category) : TxConsequences(tx)
|
|
{
|
|
isBlocker_ = (category == blocker);
|
|
}
|
|
|
|
TxConsequences::TxConsequences(STTx const& tx, XRPAmount potentialSpend) : TxConsequences(tx)
|
|
{
|
|
potentialSpend_ = potentialSpend;
|
|
}
|
|
|
|
TxConsequences::TxConsequences(STTx const& tx, std::uint32_t sequencesConsumed) : TxConsequences(tx)
|
|
{
|
|
sequencesConsumed_ = sequencesConsumed;
|
|
}
|
|
|
|
static ApplyResult
|
|
invoke_apply(ApplyContext& ctx)
|
|
{
|
|
try
|
|
{
|
|
return with_txn_type(ctx.view().rules(), ctx.tx.getTxnType(), [&]<typename T>() {
|
|
T p(ctx);
|
|
return p();
|
|
});
|
|
}
|
|
catch (UnknownTxnType const& e)
|
|
{
|
|
// Should never happen
|
|
// LCOV_EXCL_START
|
|
JLOG(ctx.journal.fatal()) << "Unknown transaction type in apply: " << e.txnType;
|
|
UNREACHABLE("xrpl::invoke_apply : unknown transaction type");
|
|
return {temUNKNOWN, false};
|
|
// LCOV_EXCL_STOP
|
|
}
|
|
}
|
|
|
|
PreflightResult
|
|
preflight(
|
|
ServiceRegistry& registry,
|
|
Rules const& rules,
|
|
STTx const& tx,
|
|
ApplyFlags flags,
|
|
beast::Journal j)
|
|
{
|
|
PreflightContext const pfCtx(registry, tx, rules, flags, j);
|
|
try
|
|
{
|
|
return {pfCtx, invoke_preflight(pfCtx)};
|
|
}
|
|
catch (std::exception const& e)
|
|
{
|
|
JLOG(j.fatal()) << "apply (preflight): " << e.what();
|
|
return {pfCtx, {tefEXCEPTION, TxConsequences{tx}}};
|
|
}
|
|
}
|
|
|
|
PreflightResult
|
|
preflight(
|
|
ServiceRegistry& registry,
|
|
Rules const& rules,
|
|
uint256 const& parentBatchId,
|
|
STTx const& tx,
|
|
ApplyFlags flags,
|
|
beast::Journal j)
|
|
{
|
|
PreflightContext const pfCtx(registry, tx, parentBatchId, rules, flags, j);
|
|
try
|
|
{
|
|
return {pfCtx, invoke_preflight(pfCtx)};
|
|
}
|
|
catch (std::exception const& e)
|
|
{
|
|
JLOG(j.fatal()) << "apply (preflight): " << e.what();
|
|
return {pfCtx, {tefEXCEPTION, TxConsequences{tx}}};
|
|
}
|
|
}
|
|
|
|
PreclaimResult
|
|
preclaim(PreflightResult const& preflightResult, ServiceRegistry& registry, OpenView const& view)
|
|
{
|
|
std::optional<PreclaimContext const> ctx;
|
|
if (preflightResult.rules != view.rules())
|
|
{
|
|
auto secondFlight = [&]() {
|
|
if (preflightResult.parentBatchId)
|
|
return preflight(
|
|
registry,
|
|
view.rules(),
|
|
preflightResult.parentBatchId.value(),
|
|
preflightResult.tx,
|
|
preflightResult.flags,
|
|
preflightResult.j);
|
|
|
|
return preflight(
|
|
registry,
|
|
view.rules(),
|
|
preflightResult.tx,
|
|
preflightResult.flags,
|
|
preflightResult.j);
|
|
}();
|
|
|
|
ctx.emplace(
|
|
registry,
|
|
view,
|
|
secondFlight.ter,
|
|
secondFlight.tx,
|
|
secondFlight.flags,
|
|
secondFlight.parentBatchId,
|
|
secondFlight.j);
|
|
}
|
|
else
|
|
{
|
|
ctx.emplace(
|
|
registry,
|
|
view,
|
|
preflightResult.ter,
|
|
preflightResult.tx,
|
|
preflightResult.flags,
|
|
preflightResult.parentBatchId,
|
|
preflightResult.j);
|
|
}
|
|
|
|
try
|
|
{
|
|
if (ctx->preflightResult != tesSUCCESS)
|
|
return {*ctx, ctx->preflightResult};
|
|
return {*ctx, invoke_preclaim(*ctx)};
|
|
}
|
|
catch (std::exception const& e)
|
|
{
|
|
JLOG(ctx->j.fatal()) << "apply (preclaim): " << e.what();
|
|
return {*ctx, tefEXCEPTION};
|
|
}
|
|
}
|
|
|
|
XRPAmount
|
|
calculateBaseFee(ReadView const& view, STTx const& tx)
|
|
{
|
|
return invoke_calculateBaseFee(view, tx);
|
|
}
|
|
|
|
XRPAmount
|
|
calculateDefaultBaseFee(ReadView const& view, STTx const& tx)
|
|
{
|
|
return Transactor::calculateBaseFee(view, tx);
|
|
}
|
|
|
|
ApplyResult
|
|
doApply(PreclaimResult const& preclaimResult, ServiceRegistry& registry, OpenView& view)
|
|
{
|
|
if (preclaimResult.view.seq() != view.seq())
|
|
{
|
|
// Logic error from the caller. Don't have enough
|
|
// info to recover.
|
|
return {tefEXCEPTION, false};
|
|
}
|
|
try
|
|
{
|
|
if (!preclaimResult.likelyToClaimFee)
|
|
return {preclaimResult.ter, false};
|
|
ApplyContext ctx(
|
|
registry,
|
|
view,
|
|
preclaimResult.parentBatchId,
|
|
preclaimResult.tx,
|
|
preclaimResult.ter,
|
|
calculateBaseFee(view, preclaimResult.tx),
|
|
preclaimResult.flags,
|
|
preclaimResult.j);
|
|
return invoke_apply(ctx);
|
|
}
|
|
catch (std::exception const& e)
|
|
{
|
|
JLOG(preclaimResult.j.fatal()) << "apply: " << e.what();
|
|
return {tefEXCEPTION, false};
|
|
}
|
|
}
|
|
|
|
} // namespace xrpl
|