Step 3: Automatically switch precision in the transaction engine

- Default Number outside of transaction processing to be "large" so RPC
  will work.
This commit is contained in:
Ed Hennis
2025-11-15 02:55:48 -05:00
parent 595a5ee220
commit 3451d15e12
4 changed files with 86 additions and 47 deletions

View File

@@ -36,9 +36,8 @@ struct make_unsigned<ripple::numberint128>
namespace ripple {
thread_local Number::rounding_mode Number::mode_ = Number::to_nearest;
// TODO: Once the Rules switching is implemented, default to largeRange
thread_local std::reference_wrapper<MantissaRange const> Number::range_ =
smallRange; // largeRange;
largeRange;
Number::rounding_mode
Number::getround()

View File

@@ -1,10 +1,12 @@
#include <xrpl/protocol/Rules.h>
//
#include <xrpl/basics/LocalValue.h>
#include <xrpl/basics/Number.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/hardened_hash.h>
#include <xrpl/beast/hash/uhash.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/STVector256.h>
#include <memory>
@@ -33,6 +35,15 @@ getCurrentTransactionRules()
void
setCurrentTransactionRules(std::optional<Rules> r)
{
// Make global changes associated with the rules before the value is moved.
// Push the appropriate setting, instead of having the class pull every time
// the value is needed. That could get expensive fast.
bool enableLargeNumbers = !r ||
(r->enabled(featureSingleAssetVault) /*||
r->enabled(featureLendingProtocol)*/);
Number::setMantissaScale(
enableLargeNumbers ? MantissaRange::large : MantissaRange::small);
*getCurrentTransactionRulesRef() = std::move(r);
}

View File

@@ -1115,6 +1115,10 @@ Transactor::operator()()
{
JLOG(j_.trace()) << "apply: " << ctx_.tx.getTransactionID();
// These global updates really should have been for every Transaction
// step: preflight, preclaim, and doApply. And even calculateBaseFee. See
// with_txn_type().
//
// raii classes for the current ledger rules.
// fixUniversalNumber predate the rulesGuard and should be replaced.
NumberSO stNumberSO{view().rules().enabled(fixUniversalNumber)};
@@ -1131,7 +1135,7 @@ Transactor::operator()()
{
// LCOV_EXCL_START
JLOG(j_.fatal()) << "Transaction serdes mismatch";
JLOG(j_.info()) << to_string(ctx_.tx.getJson(JsonOptions::none));
JLOG(j_.fatal()) << ctx_.tx.getJson(JsonOptions::none);
JLOG(j_.fatal()) << s2.getJson(JsonOptions::none);
UNREACHABLE(
"ripple::Transactor::operator() : transaction serdes mismatch");

View File

@@ -34,8 +34,31 @@ struct UnknownTxnType : std::exception
// throw an "UnknownTxnType" exception on error
template <class F>
auto
with_txn_type(TxType txnType, F&& f)
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;
if (rules.enabled(featureSingleAssetVault) /*|| rules.enabled(featureLendingProtocol)*/)
{
// raii classes for the current ledger rules.
// fixUniversalNumber predate the rulesGuard and should be replaced.
stNumberSO.emplace(rules.enabled(fixUniversalNumber));
rulesGuard.emplace(rules);
}
switch (txnType)
{
#pragma push_macro("TRANSACTION")
@@ -99,7 +122,7 @@ invoke_preflight(PreflightContext const& ctx)
{
try
{
return with_txn_type(ctx.tx.getTxnType(), [&]<typename T>() {
return with_txn_type(ctx.rules, ctx.tx.getTxnType(), [&]<typename T>() {
auto const tec = Transactor::invokePreflight<T>(ctx);
return std::make_pair(
tec,
@@ -126,50 +149,51 @@ invoke_preclaim(PreclaimContext const& ctx)
{
// use name hiding to accomplish compile-time polymorphism of static
// class functions for Transactor and derived classes.
return with_txn_type(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.
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 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 (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::checkPriorTxAndLastLedger(ctx))
return result;
if (NotTEC const result =
T::checkPermission(ctx.view, ctx.tx))
return result;
if (NotTEC const result =
T::checkPermission(ctx.view, ctx.tx))
return result;
if (NotTEC const result = T::checkSign(ctx))
return result;
if (NotTEC const result = T::checkSign(ctx))
return result;
return tesSUCCESS;
}())
return preSigResult;
return tesSUCCESS;
}())
return preSigResult;
if (TER const result =
T::checkFee(ctx, calculateBaseFee(ctx.view, ctx.tx)))
return result;
}
if (TER const result = T::checkFee(
ctx, calculateBaseFee(ctx.view, ctx.tx)))
return result;
}
return T::preclaim(ctx);
});
return T::preclaim(ctx);
});
}
catch (UnknownTxnType const& e)
{
@@ -204,7 +228,7 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
{
try
{
return with_txn_type(tx.getTxnType(), [&]<typename T>() {
return with_txn_type(view.rules(), tx.getTxnType(), [&]<typename T>() {
return T::calculateBaseFee(view, tx);
});
}
@@ -264,10 +288,11 @@ invoke_apply(ApplyContext& ctx)
{
try
{
return with_txn_type(ctx.tx.getTxnType(), [&]<typename T>() {
T p(ctx);
return p();
});
return with_txn_type(
ctx.view().rules(), ctx.tx.getTxnType(), [&]<typename T>() {
T p(ctx);
return p();
});
}
catch (UnknownTxnType const& e)
{