mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-27 23:38:08 +00:00
320 lines
12 KiB
C++
320 lines
12 KiB
C++
#include <xrpl/tx/apply.h>
|
|
|
|
#include <xrpl/basics/Log.h>
|
|
#include <xrpl/basics/base_uint.h>
|
|
#include <xrpl/beast/utility/Journal.h>
|
|
#include <xrpl/beast/utility/instrumentation.h>
|
|
#include <xrpl/core/HashRouter.h>
|
|
#include <xrpl/core/ServiceRegistry.h>
|
|
#include <xrpl/ledger/ApplyView.h>
|
|
#include <xrpl/ledger/OpenView.h>
|
|
#include <xrpl/protocol/Feature.h>
|
|
#include <xrpl/protocol/Rules.h>
|
|
#include <xrpl/protocol/SField.h>
|
|
#include <xrpl/protocol/STObject.h>
|
|
#include <xrpl/protocol/STTx.h>
|
|
#include <xrpl/protocol/TER.h>
|
|
#include <xrpl/protocol/TxFlags.h>
|
|
#include <xrpl/protocol/TxFormats.h>
|
|
#include <xrpl/tx/applySteps.h>
|
|
|
|
#include <exception>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
namespace xrpl {
|
|
|
|
// This file owns HashRouterFlags::PRIVATE1-4 and PRIVATE7-8 in HashRouter.h.
|
|
// These are the first four; the other two are below.
|
|
constexpr HashRouterFlags kSfSigbad = HashRouterFlags::PRIVATE1; // Signature is bad
|
|
constexpr HashRouterFlags kSfSiggood = HashRouterFlags::PRIVATE2; // Signature is good
|
|
constexpr HashRouterFlags kSfLocalbad = HashRouterFlags::PRIVATE3; // Local checks failed
|
|
constexpr HashRouterFlags kSfLocalgood = HashRouterFlags::PRIVATE4; // Local checks passed
|
|
|
|
// Before fixCleanup3_4_0, a signature in an alternate role field, such as
|
|
// sfSponsorSignature, covered the same bytes as the top level signature. Which
|
|
// bytes a role signature must cover therefore depends on whether the fix is
|
|
// enabled, but the four flags above record only the verdict, not the rules that
|
|
// produced it. A verdict reached under one prefix would otherwise be reused
|
|
// under the other.
|
|
//
|
|
// The two flags below hold the verdict for the pre-fix prefixes, so the pre-fix
|
|
// and post-fix verdicts occupy separate slots and neither is ever read in the
|
|
// other's era. Nothing is cleared when the amendment activates: setFlags only
|
|
// sets bits, so a stale pre-fix verdict simply stops being read and ages out
|
|
// with the rest of the routing table.
|
|
//
|
|
// This is not one switchover at a single instant. The era is chosen per call
|
|
// from the rules passed in, and callers do not agree on the rules: relay and
|
|
// submit verify against the validated rules, which lag the open ledger rules
|
|
// that preflight2 verifies against. At the amendment's flag ledger the same
|
|
// transaction can therefore be checked under both prefixes, on the same node,
|
|
// at the same time.
|
|
//
|
|
// Remove these two flags, and oldPrefixSig below, when Cleanup3_4_0 is retired
|
|
// in features.macro.
|
|
constexpr HashRouterFlags kSfSigbadOldPrefix = HashRouterFlags::PRIVATE7;
|
|
constexpr HashRouterFlags kSfSiggoodOldPrefix = HashRouterFlags::PRIVATE8;
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
std::pair<Validity, std::string>
|
|
checkValidity(HashRouter& router, STTx const& tx, Rules const& rules)
|
|
{
|
|
auto const id = tx.getTransactionID();
|
|
auto const flags = router.getFlags(id);
|
|
|
|
// Batch inner transactions are never independently valid: they are applied
|
|
// within their batch, not through checkValidity. Reaching here means one was
|
|
// relayed or submitted on its own, so mark it bad regardless of the
|
|
// amendment (like PeerImp and NetworkOPs).
|
|
if (tx.isFlag(tfInnerBatchTxn))
|
|
{
|
|
router.setFlags(id, kSfSigbad);
|
|
return {Validity::SigBad, "Batch inner transactions are never considered validly signed."};
|
|
}
|
|
|
|
// Pick the cache slot for this call's era; see kSfSiggoodOldPrefix above.
|
|
// Only a transaction that carries a role signature, and only while the fix
|
|
// is disabled, uses the separate slot. Every other transaction, and every
|
|
// transaction once the fix is enabled, uses the ordinary flags and verifies
|
|
// exactly once, so there is no steady state cost.
|
|
//
|
|
// Both directions matter. A good verdict from before the fix must not let a
|
|
// signature moved between roles survive the amendment, and a bad verdict
|
|
// from before the fix must not condemn a transaction that the new prefixes
|
|
// accept.
|
|
//
|
|
// Whether a transaction carries a role signature is fixed for its ID: the
|
|
// fields are kNotSigning, so they are excluded from the signed bytes, but
|
|
// they are still covered by the transaction ID. Repeat calls for one ID
|
|
// therefore always agree on which slot pair to use.
|
|
bool const oldPrefixSig = !rules.enabled(fixCleanup3_4_0) &&
|
|
(tx.isFieldPresent(sfSponsorSignature) || tx.isFieldPresent(sfCounterpartySignature));
|
|
auto const sigbadFlag = oldPrefixSig ? kSfSigbadOldPrefix : kSfSigbad;
|
|
auto const siggoodFlag = oldPrefixSig ? kSfSiggoodOldPrefix : kSfSiggood;
|
|
|
|
if (any(flags & sigbadFlag))
|
|
{
|
|
// Signature is known bad
|
|
return {Validity::SigBad, "Transaction has bad signature."};
|
|
}
|
|
|
|
if (!any(flags & siggoodFlag))
|
|
{
|
|
auto const sigVerify = tx.checkSign(rules);
|
|
if (!sigVerify)
|
|
{
|
|
router.setFlags(id, sigbadFlag);
|
|
return {Validity::SigBad, sigVerify.error()};
|
|
}
|
|
router.setFlags(id, siggoodFlag);
|
|
}
|
|
|
|
// Signature is now known good
|
|
if (any(flags & kSfLocalbad))
|
|
{
|
|
// ...but the local checks
|
|
// are known bad.
|
|
return {Validity::SigGoodOnly, "Local checks failed."};
|
|
}
|
|
|
|
if (any(flags & kSfLocalgood))
|
|
{
|
|
// ...and the local checks
|
|
// are known good.
|
|
return {Validity::Valid, ""};
|
|
}
|
|
|
|
// Do the local checks
|
|
std::string reason;
|
|
if (!passesLocalChecks(tx, reason))
|
|
{
|
|
router.setFlags(id, kSfLocalbad);
|
|
return {Validity::SigGoodOnly, reason};
|
|
}
|
|
router.setFlags(id, kSfLocalgood);
|
|
return {Validity::Valid, ""};
|
|
}
|
|
|
|
void
|
|
forceValidity(HashRouter& router, uint256 const& txid, Validity validity)
|
|
{
|
|
// Callers reach here when they deliberately skip signature verification,
|
|
// such as a cluster peer that trusts its neighbor's checks, or a
|
|
// configuration that turns signature checks off. Nothing was verified, so
|
|
// there is no prefix era to record. Mark both of checkValidity's signature
|
|
// slots good: otherwise the forced verdict is ignored for a role-signature
|
|
// transaction until fixCleanup3_4_0 is enabled, and the signature the
|
|
// caller meant to skip gets verified after all. Marking both cannot leak a
|
|
// verdict across eras, because no verdict was reached, and this is the only
|
|
// place the distinction can be recorded: kSfSiggood alone does not say
|
|
// whether checkValidity verified a post-fix signature or a caller forced
|
|
// the result. An already cached bad verdict still wins, since checkValidity
|
|
// tests its bad flag first. Drop kSfSiggoodOldPrefix when Cleanup3_4_0 is
|
|
// retired.
|
|
HashRouterFlags flags = HashRouterFlags::UNDEFINED;
|
|
switch (validity)
|
|
{
|
|
case Validity::Valid:
|
|
flags |= kSfLocalgood;
|
|
[[fallthrough]];
|
|
case Validity::SigGoodOnly:
|
|
flags |= kSfSiggood | kSfSiggoodOldPrefix;
|
|
[[fallthrough]];
|
|
case Validity::SigBad:
|
|
// would be silly to call directly
|
|
break;
|
|
}
|
|
if (any(flags))
|
|
router.setFlags(txid, flags);
|
|
}
|
|
|
|
template <typename PreflightChecks>
|
|
ApplyResult
|
|
apply(ServiceRegistry& registry, OpenView& view, PreflightChecks&& preflightChecks)
|
|
{
|
|
return doApply(preclaim(preflightChecks(), registry, view), registry, view);
|
|
}
|
|
|
|
ApplyResult
|
|
apply(ServiceRegistry& registry, OpenView& view, STTx const& tx, ApplyFlags flags, beast::Journal j)
|
|
{
|
|
return apply(
|
|
registry, view, [&]() mutable { return preflight(registry, view.rules(), tx, flags, j); });
|
|
}
|
|
|
|
ApplyResult
|
|
apply(
|
|
ServiceRegistry& registry,
|
|
OpenView& view,
|
|
uint256 const& parentBatchId,
|
|
STTx const& tx,
|
|
ApplyFlags flags,
|
|
beast::Journal j)
|
|
{
|
|
return apply(registry, view, [&]() mutable {
|
|
return preflight(registry, view.rules(), parentBatchId, tx, flags, j);
|
|
});
|
|
}
|
|
|
|
static bool
|
|
applyBatchTransactions(
|
|
ServiceRegistry& registry,
|
|
OpenView& batchView,
|
|
STTx const& batchTxn,
|
|
beast::Journal j)
|
|
{
|
|
XRPL_ASSERT(
|
|
batchTxn.getTxnType() == ttBATCH && !batchTxn.getFieldArray(sfRawTransactions).empty(),
|
|
"Batch transaction missing sfRawTransactions");
|
|
|
|
auto const parentBatchId = batchTxn.getTransactionID();
|
|
auto const mode = batchTxn.getFlags();
|
|
|
|
auto applyOneTransaction = [®istry, &j, &parentBatchId, &batchView](STTx const& tx) {
|
|
OpenView perTxBatchView(kBatchView, batchView);
|
|
|
|
auto const ret = apply(registry, perTxBatchView, parentBatchId, tx, TapBatch, j);
|
|
XRPL_ASSERT(
|
|
ret.applied == (isTesSuccess(ret.ter) || isTecClaim(ret.ter)),
|
|
"Inner transaction should not be applied");
|
|
|
|
JLOG(j.debug()) << "BatchTrace[" << parentBatchId << "]: " << tx.getTransactionID() << " "
|
|
<< (ret.applied ? "applied" : "failure") << ": " << transToken(ret.ter);
|
|
|
|
// If the transaction should be applied push its changes to the
|
|
// whole-batch view.
|
|
// NOTE: each inner tx is individually capped at kOversizeMetaDataCap;
|
|
// there is no aggregate cap here. Bounded by kMaxBatchTxCount * cap,
|
|
// which standalone txns can already produce in one ledger.
|
|
if (ret.applied && (isTesSuccess(ret.ter) || isTecClaim(ret.ter)))
|
|
perTxBatchView.apply(batchView);
|
|
|
|
return ret;
|
|
};
|
|
|
|
int applied = 0;
|
|
|
|
for (auto const& stx : batchTxn.getBatchTransactions())
|
|
{
|
|
auto const result = applyOneTransaction(*stx);
|
|
XRPL_ASSERT(
|
|
result.applied == (isTesSuccess(result.ter) || isTecClaim(result.ter)),
|
|
"Outer Batch failure, inner transaction should not be applied");
|
|
|
|
if (result.applied)
|
|
++applied;
|
|
|
|
if (!isTesSuccess(result.ter))
|
|
{
|
|
if ((mode & tfAllOrNothing) != 0u)
|
|
return false;
|
|
|
|
if ((mode & tfUntilFailure) != 0u)
|
|
break;
|
|
}
|
|
else if ((mode & tfOnlyOne) != 0u)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return applied != 0;
|
|
}
|
|
|
|
ApplyTransactionResult
|
|
applyTransaction(
|
|
ServiceRegistry& registry,
|
|
OpenView& view,
|
|
STTx const& txn,
|
|
bool retryAssured,
|
|
ApplyFlags flags,
|
|
beast::Journal j)
|
|
{
|
|
// Returns false if the transaction has need not be retried.
|
|
if (retryAssured)
|
|
flags = flags | TapRetry;
|
|
|
|
JLOG(j.debug()) << "TXN " << txn.getTransactionID() << (retryAssured ? "/retry" : "/final");
|
|
|
|
try
|
|
{
|
|
auto const result = apply(registry, view, txn, flags, j);
|
|
|
|
if (result.applied)
|
|
{
|
|
JLOG(j.debug()) << "Transaction applied: " << transToken(result.ter);
|
|
|
|
// The batch transaction was just applied; now we need to apply
|
|
// its inner transactions as necessary.
|
|
if (isTesSuccess(result.ter) && txn.getTxnType() == ttBATCH)
|
|
{
|
|
OpenView wholeBatchView(kBatchView, view);
|
|
|
|
if (applyBatchTransactions(registry, wholeBatchView, txn, j))
|
|
wholeBatchView.apply(view);
|
|
}
|
|
|
|
return ApplyTransactionResult::Success;
|
|
}
|
|
|
|
if (isTefFailure(result.ter) || isTemMalformed(result.ter) || isTelLocal(result.ter))
|
|
{
|
|
// failure
|
|
JLOG(j.debug()) << "Transaction failure: " << transHuman(result.ter);
|
|
return ApplyTransactionResult::Fail;
|
|
}
|
|
|
|
JLOG(j.debug()) << "Transaction retry: " << transHuman(result.ter);
|
|
return ApplyTransactionResult::Retry;
|
|
}
|
|
catch (std::exception const& ex)
|
|
{
|
|
JLOG(j.warn()) << "Throws: " << ex.what();
|
|
return ApplyTransactionResult::Fail;
|
|
}
|
|
}
|
|
|
|
} // namespace xrpl
|