feat: Add fee vote maxes (#7346)

This PR adds hard-cap maxes for the size and compute limits, currently set at twice the defaults.
This commit is contained in:
Mayukha Vadari
2026-06-04 16:25:52 -04:00
committed by GitHub
parent 1f9675d22f
commit 68f0dcae53
5 changed files with 128 additions and 25 deletions

View File

@@ -2,6 +2,8 @@
#include <xrpl/protocol/XRPAmount.h>
#include <cstdint>
namespace xrpl {
// Deprecated constant for backwards compatibility with pre-XRPFees amendment.
@@ -11,6 +13,10 @@ inline constexpr std::uint32_t kFeeUnitsDeprecated = 10;
// Number of micro-drops in one drop.
constexpr std::uint32_t microDropsPerDrop{1'000'000};
/** Maximum Feature Extension fee settings. */
inline constexpr std::uint32_t kMaxExtensionComputeLimit{2'000'000};
inline constexpr std::uint32_t kMaxExtensionSizeLimit{200'000};
/** Reflects the fee settings for a particular ledger.
The fees are always the same for any transactions applied

View File

@@ -9,6 +9,7 @@
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/AmendmentTable.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Fees.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/PublicKey.h>
@@ -129,6 +130,9 @@ Change::preclaim(PreclaimContext const& ctx)
!ctx.tx.isFieldPresent(sfExtensionSizeLimit) ||
!ctx.tx.isFieldPresent(sfGasPrice))
return temMALFORMED;
if (ctx.tx[sfExtensionComputeLimit] > kMaxExtensionComputeLimit ||
ctx.tx[sfExtensionSizeLimit] > kMaxExtensionSizeLimit)
return temBAD_FEE;
}
else
{

View File

@@ -11,6 +11,7 @@
#include <xrpl/ledger/Ledger.h>
#include <xrpl/ledger/OpenView.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Fees.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/KeyType.h>
#include <xrpl/protocol/PublicKey.h>
@@ -305,16 +306,14 @@ class FeeVote_test : public beast::unit_test::Suite
"extension_compute_limit = -100",
"extension_size_limit = -200",
"gas_price = -300"});
// Negative values wrap to large positive uint32_t values.
// reference_fee is uint64_t and has bounds checking, so it keeps
// the default.
// Illegal values are ignored, and the defaults left unchanged
// Negative extension limit values wrap past their maximum and are
// ignored. Other uint32_t fields keep the existing behavior.
auto setup = setupFeeVote(config);
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
BEAST_EXPECT(setup.account_reserve == static_cast<std::uint32_t>(-1234567));
BEAST_EXPECT(setup.owner_reserve == static_cast<std::uint32_t>(-1234));
BEAST_EXPECT(setup.extension_compute_limit == static_cast<std::uint32_t>(-100));
BEAST_EXPECT(setup.extension_size_limit == static_cast<std::uint32_t>(-200));
BEAST_EXPECT(setup.extension_compute_limit == defaultSetup.extension_compute_limit);
BEAST_EXPECT(setup.extension_size_limit == defaultSetup.extension_size_limit);
BEAST_EXPECT(setup.gas_price == static_cast<std::uint32_t>(-300));
}
{
@@ -337,6 +336,24 @@ class FeeVote_test : public beast::unit_test::Suite
BEAST_EXPECT(setup.extension_size_limit == defaultSetup.extension_size_limit);
BEAST_EXPECT(setup.gas_price == defaultSetup.gas_price);
}
{
Section config;
config.append(
{"extension_compute_limit = " + std::to_string(kMaxExtensionComputeLimit + 1),
"extension_size_limit = " + std::to_string(kMaxExtensionSizeLimit + 1)});
auto const setup = setupFeeVote(config);
BEAST_EXPECT(setup.extension_compute_limit == defaultSetup.extension_compute_limit);
BEAST_EXPECT(setup.extension_size_limit == defaultSetup.extension_size_limit);
}
{
Section config;
config.append(
{"extension_compute_limit = " + std::to_string(kMaxExtensionComputeLimit),
"extension_size_limit = " + std::to_string(kMaxExtensionSizeLimit)});
auto const setup = setupFeeVote(config);
BEAST_EXPECT(setup.extension_compute_limit == kMaxExtensionComputeLimit);
BEAST_EXPECT(setup.extension_size_limit == kMaxExtensionSizeLimit);
}
}
void
@@ -433,6 +450,40 @@ class FeeVote_test : public beast::unit_test::Suite
BEAST_EXPECT(verifyFeeObject(ledger, ledger->rules(), fields));
}
// Test that Smart Escrow limits reject values above their maximums.
{
jtx::Env env(*this, jtx::testableAmendments());
auto ledger = std::make_shared<Ledger>(
kCreateGenesis,
Rules{env.app().config().features},
env.app().config().FEES.toFees(),
std::vector<uint256>{},
env.app().getNodeFamily());
ledger = std::make_shared<Ledger>(*ledger, env.app().getTimeKeeper().closeTime());
auto testBadFields = [&](FeeSettingsFields const& fields) {
auto feeTx = createFeeTx(ledger->rules(), ledger->seq(), fields);
OpenView accum(ledger.get());
BEAST_EXPECT(!isTesSuccess(applyFeeAndTestResult(env, accum, feeTx)));
};
testBadFields(
{.baseFeeDrops = XRPAmount{10},
.reserveBaseDrops = XRPAmount{200000},
.reserveIncrementDrops = XRPAmount{50000},
.extensionComputeLimit = kMaxExtensionComputeLimit + 1,
.extensionSizeLimit = kMaxExtensionSizeLimit,
.gasPrice = 300});
testBadFields(
{.baseFeeDrops = XRPAmount{10},
.reserveBaseDrops = XRPAmount{200000},
.reserveIncrementDrops = XRPAmount{50000},
.extensionComputeLimit = kMaxExtensionComputeLimit,
.extensionSizeLimit = kMaxExtensionSizeLimit + 1,
.gasPrice = 300});
}
// Test that the Smart Escrow fields are rejected if the
// feature is disabled
{
@@ -1022,6 +1073,21 @@ class FeeVote_test : public beast::unit_test::Suite
checkFeeTx(setup, feeTx, ledger);
}
{
FeeSetup setup;
setup.reference_fee = 42;
setup.account_reserve = 1234567;
setup.owner_reserve = 7654321;
setup.extension_compute_limit = kMaxExtensionComputeLimit + 1;
setup.extension_size_limit = kMaxExtensionSizeLimit + 1;
setup.gas_price = 300;
auto const [feeTx, ledger] = createFeeTxFromVoting(setup);
setup.extension_compute_limit = ledger->fees().extensionComputeLimit;
setup.extension_size_limit = ledger->fees().extensionSizeLimit;
checkFeeTx(setup, feeTx, ledger);
}
}
void

View File

@@ -174,16 +174,22 @@ FeeVoteImpl::doValidation(Fees const& lastFees, Rules const& rules, STValidation
}
if (rules.enabled(featureSmartEscrow))
{
vote(
lastFees.extensionComputeLimit,
target_.extension_compute_limit,
"extension compute limit",
sfExtensionComputeLimit);
vote(
lastFees.extensionSizeLimit,
target_.extension_size_limit,
"extension size limit",
sfExtensionSizeLimit);
if (target_.extension_compute_limit <= kMaxExtensionComputeLimit)
{
vote(
lastFees.extensionComputeLimit,
target_.extension_compute_limit,
"extension compute limit",
sfExtensionComputeLimit);
}
if (target_.extension_size_limit <= kMaxExtensionSizeLimit)
{
vote(
lastFees.extensionSizeLimit,
target_.extension_size_limit,
"extension size limit",
sfExtensionSizeLimit);
}
vote(lastFees.gasPrice, target_.gas_price, "gas price", sfGasPrice);
}
}
@@ -205,11 +211,23 @@ FeeVoteImpl::doVoting(
detail::VotableValue incReserveVote(lastClosedLedger->fees().increment, target_.owner_reserve);
auto validOrCurrent = [](std::uint32_t target, std::uint32_t max, std::uint32_t current) {
return target <= max ? target : current;
};
detail::VotableValue extensionComputeVote(
lastClosedLedger->fees().extensionComputeLimit, target_.extension_compute_limit);
lastClosedLedger->fees().extensionComputeLimit,
validOrCurrent(
target_.extension_compute_limit,
kMaxExtensionComputeLimit,
lastClosedLedger->fees().extensionComputeLimit));
detail::VotableValue extensionSizeVote(
lastClosedLedger->fees().extensionSizeLimit, target_.extension_size_limit);
lastClosedLedger->fees().extensionSizeLimit,
validOrCurrent(
target_.extension_size_limit,
kMaxExtensionSizeLimit,
lastClosedLedger->fees().extensionSizeLimit));
detail::VotableValue gasPriceVote(lastClosedLedger->fees().gasPrice, target_.gas_price);
@@ -287,10 +305,18 @@ FeeVoteImpl::doVoting(
{
auto doVote = [](std::shared_ptr<STValidation> const& val,
detail::VotableValue<std::uint32_t>& value,
SF_UINT32 const& extensionField) {
SF_UINT32 const& extensionField,
std::uint32_t maxValue) {
if (auto const field = ~val->at(~extensionField); field)
{
value.addVote(field.value());
if (field.value() <= maxValue)
{
value.addVote(field.value());
}
else
{
value.noVote();
}
}
else
{
@@ -302,9 +328,9 @@ FeeVoteImpl::doVoting(
{
if (!val->isTrusted())
continue;
doVote(val, extensionComputeVote, sfExtensionComputeLimit);
doVote(val, extensionSizeVote, sfExtensionSizeLimit);
doVote(val, gasPriceVote, sfGasPrice);
doVote(val, extensionComputeVote, sfExtensionComputeLimit, kMaxExtensionComputeLimit);
doVote(val, extensionSizeVote, sfExtensionSizeLimit, kMaxExtensionSizeLimit);
doVote(val, gasPriceVote, sfGasPrice, std::numeric_limits<std::uint32_t>::max());
}
}

View File

@@ -13,6 +13,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/net/HTTPClient.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Fees.h>
#include <xrpl/protocol/SystemParameters.h>
#include <xrpl/rdb/DBInit.h>
#include <xrpl/rdb/DatabaseCon.h>
@@ -1194,9 +1195,9 @@ setupFeeVote(Section const& section)
setup.account_reserve = temp;
if (set(temp, "owner_reserve", section))
setup.owner_reserve = temp;
if (set(temp, "extension_compute_limit", section))
if (set(temp, "extension_compute_limit", section) && temp <= kMaxExtensionComputeLimit)
setup.extension_compute_limit = temp;
if (set(temp, "extension_size_limit", section))
if (set(temp, "extension_size_limit", section) && temp <= kMaxExtensionSizeLimit)
setup.extension_size_limit = temp;
if (set(temp, "gas_price", section))
setup.gas_price = temp;