mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-23 21:50:20 +00:00
Compare commits
5 Commits
token-payc
...
mvadari/sm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4cca58490e | ||
|
|
a6cc20aa04 | ||
|
|
052782a3d4 | ||
|
|
e4476e5821 | ||
|
|
62d8d84a49 |
@@ -1360,6 +1360,39 @@
|
||||
# Example:
|
||||
# owner_reserve = 200000 # 0.2 XRP
|
||||
#
|
||||
# gas_limit = <gas>
|
||||
#
|
||||
# The gas limit is the maximum amount of gas that can be
|
||||
# consumed by a single transaction. The gas limit is used to prevent
|
||||
# transactions from consuming too many resources.
|
||||
#
|
||||
# If this parameter is unspecified, xrpld will use an internal
|
||||
# default. Don't change this without understanding the consequences.
|
||||
#
|
||||
# Example:
|
||||
# gas_limit = 1000000 # 1 million gas
|
||||
#
|
||||
# bytecode_size_limit = <bytes>
|
||||
#
|
||||
# The bytecode size limit is the maximum size of a WASM extension in
|
||||
# bytes. The size limit is used to prevent extensions from consuming
|
||||
# too many resources.
|
||||
#
|
||||
# If this parameter is unspecified, xrpld will use an internal
|
||||
# default. Don't change this without understanding the consequences.
|
||||
#
|
||||
# Example:
|
||||
# bytecode_size_limit = 100000 # 100 kb
|
||||
#
|
||||
# gas_price = <micro-drops>
|
||||
#
|
||||
# The gas price is the conversion between WASM gas and its price in drops.
|
||||
#
|
||||
# If this parameter is unspecified, xrpld will use an internal
|
||||
# default. Don't change this without understanding the consequences.
|
||||
#
|
||||
# Example:
|
||||
# gas_price = 1000000 # 1 drop per gas
|
||||
#-------------------------------------------------------------------------------
|
||||
#
|
||||
# 9. Misc Settings
|
||||
|
||||
@@ -94,6 +94,7 @@ struct Keys
|
||||
static constexpr auto kBbtOptions = "bbt_options";
|
||||
static constexpr auto kBgThreads = "bg_threads";
|
||||
static constexpr auto kBlockSize = "block_size";
|
||||
static constexpr auto kBytecodeSizeLimit = "bytecode_size_limit";
|
||||
static constexpr auto kCacheAge = "cache_age";
|
||||
static constexpr auto kCacheMb = "cache_mb";
|
||||
static constexpr auto kCacheSize = "cache_size";
|
||||
@@ -108,6 +109,8 @@ struct Keys
|
||||
static constexpr auto kFileSizeMult = "file_size_mult";
|
||||
static constexpr auto kFilterBits = "filter_bits";
|
||||
static constexpr auto kFilterFull = "filter_full";
|
||||
static constexpr auto kGasLimit = "gas_limit";
|
||||
static constexpr auto kGasPrice = "gas_price";
|
||||
static constexpr auto kHardSet = "hard_set";
|
||||
static constexpr auto kHighThreads = "high_threads";
|
||||
static constexpr auto kHoldTime = "hold_time";
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <xrpl/basics/TaggedCache.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/Fees.h>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
@@ -246,6 +247,9 @@ public:
|
||||
virtual DatabaseCon&
|
||||
getWalletDB() = 0;
|
||||
|
||||
[[nodiscard]] virtual Fees
|
||||
getFees() const = 0;
|
||||
|
||||
// Temporary: Get the underlying Application for functions that haven't
|
||||
// been migrated yet. This should be removed once all code is migrated.
|
||||
virtual Application&
|
||||
|
||||
@@ -10,6 +10,15 @@ namespace xrpl {
|
||||
// This was the reference fee units used in the old fee calculation.
|
||||
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 kMaxGasLimit{2'000'000};
|
||||
inline constexpr std::uint32_t kMaxBytecodeSizeLimit{200'000};
|
||||
|
||||
/**
|
||||
* Reflects the fee settings for a particular ledger.
|
||||
*
|
||||
@@ -33,6 +42,21 @@ struct Fees
|
||||
*/
|
||||
XRPAmount increment{0};
|
||||
|
||||
/**
|
||||
* @brief Gas limit for Feature Extensions (instructions).
|
||||
*/
|
||||
std::uint32_t gasLimit{0};
|
||||
|
||||
/**
|
||||
* @brief Bytecode size limit for Feature Extensions (bytes).
|
||||
*/
|
||||
std::uint32_t bytecodeSizeLimit{0};
|
||||
|
||||
/**
|
||||
* @brief Price of WASM gas (micro-drops).
|
||||
*/
|
||||
std::uint32_t gasPrice{0};
|
||||
|
||||
explicit Fees() = default;
|
||||
Fees(Fees const&) = default;
|
||||
Fees&
|
||||
|
||||
@@ -252,6 +252,9 @@ JSS(expected_date); // out: any (warnings)
|
||||
JSS(expected_date_UTC); // out: any (warnings)
|
||||
JSS(expected_ledger_size); // out: TxQ
|
||||
JSS(expiration); // out: AccountOffers, AccountChannels, ValidatorList, amm_info
|
||||
JSS(gas_limit); // out: NetworkOPs
|
||||
JSS(bytecode_size_limit); // out: NetworkOPs
|
||||
JSS(gas_price); // out: NetworkOPs
|
||||
JSS(fail_hard); // in: Sign, Submit
|
||||
JSS(failed); // out: InboundLedger
|
||||
JSS(feature); // in: Feature
|
||||
|
||||
@@ -198,6 +198,22 @@ Ledger::Ledger(
|
||||
sle->at(sfReserveIncrement) = *f;
|
||||
sle->at(sfReferenceFeeUnits) = kFeeUnitsDeprecated;
|
||||
}
|
||||
if (std::ranges::find(amendments, featureSmartEscrow) != amendments.end())
|
||||
{
|
||||
sle->at(sfGasLimit) = fees.gasLimit;
|
||||
sle->at(sfBytecodeSizeLimit) = fees.bytecodeSizeLimit;
|
||||
sle->at(sfGasPrice) = fees.gasPrice;
|
||||
}
|
||||
else
|
||||
{
|
||||
// This ledger does not carry the gas settings, so it must not
|
||||
// report them either. Otherwise a node reads its own config back
|
||||
// as though the network had agreed to it, and never votes for the
|
||||
// values it wants.
|
||||
fees_.gasLimit = 0;
|
||||
fees_.bytecodeSizeLimit = 0;
|
||||
fees_.gasPrice = 0;
|
||||
}
|
||||
rawInsert(sle);
|
||||
}
|
||||
|
||||
@@ -564,6 +580,7 @@ Ledger::setup()
|
||||
{
|
||||
bool oldFees = false;
|
||||
bool newFees = false;
|
||||
bool extensionFees = false;
|
||||
{
|
||||
auto const baseFee = sle->at(~sfBaseFee);
|
||||
auto const reserveBase = sle->at(~sfReserveBase);
|
||||
@@ -580,6 +597,7 @@ Ledger::setup()
|
||||
auto const baseFeeXRP = sle->at(~sfBaseFeeDrops);
|
||||
auto const reserveBaseXRP = sle->at(~sfReserveBaseDrops);
|
||||
auto const reserveIncrementXRP = sle->at(~sfReserveIncrementDrops);
|
||||
|
||||
auto assign = [&ret](XRPAmount& dest, std::optional<STAmount> const& src) {
|
||||
if (src)
|
||||
{
|
||||
@@ -598,6 +616,22 @@ Ledger::setup()
|
||||
assign(fees_.increment, reserveIncrementXRP);
|
||||
newFees = baseFeeXRP || reserveBaseXRP || reserveIncrementXRP;
|
||||
}
|
||||
{
|
||||
auto const gasLimit = sle->at(~sfGasLimit);
|
||||
auto const bytecodeSizeLimit = sle->at(~sfBytecodeSizeLimit);
|
||||
auto const gasPrice = sle->at(~sfGasPrice);
|
||||
|
||||
auto assign = [](std::uint32_t& dest, std::optional<std::uint32_t> const& src) {
|
||||
if (src)
|
||||
{
|
||||
dest = src.value();
|
||||
}
|
||||
};
|
||||
assign(fees_.gasLimit, gasLimit);
|
||||
assign(fees_.bytecodeSizeLimit, bytecodeSizeLimit);
|
||||
assign(fees_.gasPrice, gasPrice);
|
||||
extensionFees = gasLimit || bytecodeSizeLimit || gasPrice;
|
||||
}
|
||||
if (oldFees && newFees)
|
||||
{
|
||||
// Should be all of one or the other, but not both
|
||||
@@ -608,6 +642,12 @@ Ledger::setup()
|
||||
// Can't populate the new fees before the amendment is enabled
|
||||
ret = false;
|
||||
}
|
||||
if (!rules_.enabled(featureSmartEscrow) && extensionFees)
|
||||
{
|
||||
// Can't populate the extension fees before the amendment is
|
||||
// enabled
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (SHAMapMissingNode const&)
|
||||
|
||||
@@ -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>
|
||||
@@ -123,12 +124,22 @@ Change::preclaim(PreclaimContext const& ctx)
|
||||
ctx.tx.isFieldPresent(sfReserveIncrementDrops))
|
||||
return temDISABLED;
|
||||
}
|
||||
// The ttFEE transaction format defines these fields as optional,
|
||||
// but they are unconditionally forbidden until FeeVoteImpl is
|
||||
// updated to populate them (SmartEscrow behavioral port).
|
||||
if (ctx.tx.isFieldPresent(sfGasLimit) || ctx.tx.isFieldPresent(sfBytecodeSizeLimit) ||
|
||||
ctx.tx.isFieldPresent(sfGasPrice))
|
||||
return temDISABLED;
|
||||
if (ctx.view.rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
if (!ctx.tx.isFieldPresent(sfGasLimit) ||
|
||||
!ctx.tx.isFieldPresent(sfBytecodeSizeLimit) ||
|
||||
!ctx.tx.isFieldPresent(sfGasPrice))
|
||||
return temMALFORMED;
|
||||
if (ctx.tx[sfGasLimit] > kMaxGasLimit ||
|
||||
ctx.tx[sfBytecodeSizeLimit] > kMaxBytecodeSizeLimit)
|
||||
return temBAD_FEE;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ctx.tx.isFieldPresent(sfGasLimit) ||
|
||||
ctx.tx.isFieldPresent(sfBytecodeSizeLimit) || ctx.tx.isFieldPresent(sfGasPrice))
|
||||
return temDISABLED;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
case ttAMENDMENT:
|
||||
case ttUNL_MODIFY:
|
||||
@@ -290,6 +301,12 @@ Change::applyFee()
|
||||
set(feeObject, ctx_.tx, sfReserveBase);
|
||||
set(feeObject, ctx_.tx, sfReserveIncrement);
|
||||
}
|
||||
if (view().rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
set(feeObject, ctx_.tx, sfGasLimit);
|
||||
set(feeObject, ctx_.tx, sfBytecodeSizeLimit);
|
||||
set(feeObject, ctx_.tx, sfGasPrice);
|
||||
}
|
||||
|
||||
view().update(feeObject);
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -31,7 +32,9 @@
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <source_location>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
@@ -45,10 +48,17 @@ struct FeeSettingsFields
|
||||
std::optional<XRPAmount> baseFeeDrops = std::nullopt;
|
||||
std::optional<XRPAmount> reserveBaseDrops = std::nullopt;
|
||||
std::optional<XRPAmount> reserveIncrementDrops = std::nullopt;
|
||||
std::optional<std::uint32_t> gasLimit = std::nullopt;
|
||||
std::optional<std::uint32_t> bytecodeSizeLimit = std::nullopt;
|
||||
std::optional<std::uint32_t> gasPrice = std::nullopt;
|
||||
};
|
||||
|
||||
STTx
|
||||
createFeeTx(Rules const& rules, std::uint32_t seq, FeeSettingsFields const& fields)
|
||||
createFeeTx(
|
||||
Rules const& rules,
|
||||
std::uint32_t seq,
|
||||
FeeSettingsFields const& fields,
|
||||
bool forceAllFields = false)
|
||||
{
|
||||
auto fill = [&](auto& obj) {
|
||||
obj.setAccountID(sfAccount, AccountID());
|
||||
@@ -76,6 +86,13 @@ createFeeTx(Rules const& rules, std::uint32_t seq, FeeSettingsFields const& fiel
|
||||
obj.setFieldU32(
|
||||
sfReferenceFeeUnits, fields.referenceFeeUnits ? *fields.referenceFeeUnits : 0);
|
||||
}
|
||||
if (rules.enabled(featureSmartEscrow) || forceAllFields)
|
||||
{
|
||||
obj.setFieldU32(sfGasLimit, fields.gasLimit ? *fields.gasLimit : 0);
|
||||
obj.setFieldU32(
|
||||
sfBytecodeSizeLimit, fields.bytecodeSizeLimit ? *fields.bytecodeSizeLimit : 0);
|
||||
obj.setFieldU32(sfGasPrice, fields.gasPrice ? *fields.gasPrice : 0);
|
||||
}
|
||||
};
|
||||
return STTx(ttFEE, fill);
|
||||
}
|
||||
@@ -124,6 +141,12 @@ createInvalidFeeTx(
|
||||
obj.setFieldU32(sfReserveIncrement, 50000);
|
||||
obj.setFieldU32(sfReferenceFeeUnits, 10);
|
||||
}
|
||||
if (rules.enabled(featureSmartEscrow))
|
||||
{
|
||||
obj.setFieldU32(sfGasLimit, 100 + uniqueValue);
|
||||
obj.setFieldU32(sfBytecodeSizeLimit, 200 + uniqueValue);
|
||||
obj.setFieldU32(sfGasPrice, 300 + uniqueValue);
|
||||
}
|
||||
}
|
||||
// If missingRequiredFields is true, we don't add the required fields
|
||||
// (default behavior)
|
||||
@@ -131,11 +154,11 @@ createInvalidFeeTx(
|
||||
return STTx(ttFEE, fill);
|
||||
}
|
||||
|
||||
bool
|
||||
TER
|
||||
applyFeeAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx)
|
||||
{
|
||||
auto const res = apply(env.app(), view, tx, ApplyFlags::TapNone, env.journal);
|
||||
return isTesSuccess(res.ter);
|
||||
return res.ter;
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -186,6 +209,21 @@ verifyFeeObject(
|
||||
if (!checkEquality(sfReferenceFeeUnits, expected.referenceFeeUnits))
|
||||
return false;
|
||||
}
|
||||
if (rules.enabled(featureSmartEscrow))
|
||||
{
|
||||
if (!checkEquality(sfGasLimit, expected.gasLimit.value_or(0)))
|
||||
return false;
|
||||
if (!checkEquality(sfBytecodeSizeLimit, expected.bytecodeSizeLimit.value_or(0)))
|
||||
return false;
|
||||
if (!checkEquality(sfGasPrice, expected.gasPrice.value_or(0)))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (feeObject->isFieldPresent(sfGasLimit) ||
|
||||
feeObject->isFieldPresent(sfBytecodeSizeLimit) || feeObject->isFieldPresent(sfGasPrice))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -208,6 +246,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
void
|
||||
testSetup()
|
||||
{
|
||||
testcase("FeeVote setup");
|
||||
FeeSetup const defaultSetup;
|
||||
{
|
||||
// defaults
|
||||
@@ -216,35 +255,63 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
BEAST_EXPECT(setup.referenceFee == defaultSetup.referenceFee);
|
||||
BEAST_EXPECT(setup.accountReserve == defaultSetup.accountReserve);
|
||||
BEAST_EXPECT(setup.ownerReserve == defaultSetup.ownerReserve);
|
||||
BEAST_EXPECT(setup.gasLimit == defaultSetup.gasLimit);
|
||||
BEAST_EXPECT(setup.bytecodeSizeLimit == defaultSetup.bytecodeSizeLimit);
|
||||
BEAST_EXPECT(setup.gasPrice == defaultSetup.gasPrice);
|
||||
}
|
||||
{
|
||||
Section config;
|
||||
config.append(
|
||||
{"reference_fee = 50", "account_reserve = 1234567", "owner_reserve = 1234"});
|
||||
{"reference_fee = 50",
|
||||
"account_reserve = 1234567",
|
||||
"owner_reserve = 1234",
|
||||
"gas_limit = 100",
|
||||
"bytecode_size_limit = 200",
|
||||
"gas_price = 300"});
|
||||
auto setup = setupFeeVote(config);
|
||||
BEAST_EXPECT(setup.referenceFee == 50);
|
||||
BEAST_EXPECT(setup.accountReserve == 1234567);
|
||||
BEAST_EXPECT(setup.ownerReserve == 1234);
|
||||
BEAST_EXPECT(setup.gasLimit == 100);
|
||||
BEAST_EXPECT(setup.bytecodeSizeLimit == 200);
|
||||
BEAST_EXPECT(setup.gasPrice == 300);
|
||||
}
|
||||
{
|
||||
Section config;
|
||||
config.append(
|
||||
{"reference_fee = blah", "account_reserve = yada", "owner_reserve = foo"});
|
||||
{"reference_fee = blah",
|
||||
"account_reserve = yada",
|
||||
"owner_reserve = foo",
|
||||
"gas_limit = bar",
|
||||
"bytecode_size_limit = baz",
|
||||
"gas_price = qux"});
|
||||
// Illegal values are ignored, and the defaults left unchanged
|
||||
auto setup = setupFeeVote(config);
|
||||
BEAST_EXPECT(setup.referenceFee == defaultSetup.referenceFee);
|
||||
BEAST_EXPECT(setup.accountReserve == defaultSetup.accountReserve);
|
||||
BEAST_EXPECT(setup.ownerReserve == defaultSetup.ownerReserve);
|
||||
BEAST_EXPECT(setup.gasLimit == defaultSetup.gasLimit);
|
||||
BEAST_EXPECT(setup.bytecodeSizeLimit == defaultSetup.bytecodeSizeLimit);
|
||||
BEAST_EXPECT(setup.gasPrice == defaultSetup.gasPrice);
|
||||
}
|
||||
{
|
||||
Section config;
|
||||
config.append(
|
||||
{"reference_fee = -50", "account_reserve = -1234567", "owner_reserve = -1234"});
|
||||
// Illegal values are ignored, and the defaults left unchanged
|
||||
{"reference_fee = -50",
|
||||
"account_reserve = -1234567",
|
||||
"owner_reserve = -1234",
|
||||
"gas_limit = -100",
|
||||
"bytecode_size_limit = -200",
|
||||
"gas_price = -300"});
|
||||
// Negative gas/bytecode limit values wrap past their maximum and are
|
||||
// ignored. Other uint32_t fields keep the existing behavior.
|
||||
auto setup = setupFeeVote(config);
|
||||
BEAST_EXPECT(setup.referenceFee == defaultSetup.referenceFee);
|
||||
BEAST_EXPECT(setup.accountReserve == static_cast<std::uint32_t>(-1234567));
|
||||
BEAST_EXPECT(setup.ownerReserve == static_cast<std::uint32_t>(-1234));
|
||||
BEAST_EXPECT(setup.gasLimit == defaultSetup.gasLimit);
|
||||
BEAST_EXPECT(setup.bytecodeSizeLimit == defaultSetup.bytecodeSizeLimit);
|
||||
BEAST_EXPECT(setup.gasPrice == static_cast<std::uint32_t>(-300));
|
||||
}
|
||||
{
|
||||
auto const big64 = std::to_string(
|
||||
@@ -253,12 +320,36 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
config.append(
|
||||
{"reference_fee = " + big64,
|
||||
"account_reserve = " + big64,
|
||||
"owner_reserve = " + big64});
|
||||
"owner_reserve = " + big64,
|
||||
"gas_limit = " + big64,
|
||||
"bytecode_size_limit = " + big64,
|
||||
"gas_price = " + big64});
|
||||
// Illegal values are ignored, and the defaults left unchanged
|
||||
auto setup = setupFeeVote(config);
|
||||
BEAST_EXPECT(setup.referenceFee == defaultSetup.referenceFee);
|
||||
BEAST_EXPECT(setup.accountReserve == defaultSetup.accountReserve);
|
||||
BEAST_EXPECT(setup.ownerReserve == defaultSetup.ownerReserve);
|
||||
BEAST_EXPECT(setup.gasLimit == defaultSetup.gasLimit);
|
||||
BEAST_EXPECT(setup.bytecodeSizeLimit == defaultSetup.bytecodeSizeLimit);
|
||||
BEAST_EXPECT(setup.gasPrice == defaultSetup.gasPrice);
|
||||
}
|
||||
{
|
||||
Section config;
|
||||
config.append(
|
||||
{"gas_limit = " + std::to_string(kMaxGasLimit + 1),
|
||||
"bytecode_size_limit = " + std::to_string(kMaxBytecodeSizeLimit + 1)});
|
||||
auto const setup = setupFeeVote(config);
|
||||
BEAST_EXPECT(setup.gasLimit == defaultSetup.gasLimit);
|
||||
BEAST_EXPECT(setup.bytecodeSizeLimit == defaultSetup.bytecodeSizeLimit);
|
||||
}
|
||||
{
|
||||
Section config;
|
||||
config.append(
|
||||
{"gas_limit = " + std::to_string(kMaxGasLimit),
|
||||
"bytecode_size_limit = " + std::to_string(kMaxBytecodeSizeLimit)});
|
||||
auto const setup = setupFeeVote(config);
|
||||
BEAST_EXPECT(setup.gasLimit == kMaxGasLimit);
|
||||
BEAST_EXPECT(setup.bytecodeSizeLimit == kMaxBytecodeSizeLimit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +360,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
|
||||
// Test with XRPFees disabled (legacy format)
|
||||
{
|
||||
jtx::Env env(*this, jtx::testableAmendments() - featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testableAmendments() - featureXRPFees - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
@@ -290,7 +381,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
auto feeTx = createFeeTx(ledger->rules(), ledger->seq(), fields);
|
||||
|
||||
OpenView accum(ledger.get());
|
||||
BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx));
|
||||
BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, accum, feeTx)));
|
||||
accum.apply(*ledger);
|
||||
|
||||
// Verify fee object was created/updated correctly
|
||||
@@ -299,7 +390,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
|
||||
// Test with XRPFees enabled (new format)
|
||||
{
|
||||
jtx::Env env(*this, jtx::testableAmendments() | featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testableAmendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
@@ -318,12 +409,105 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
auto feeTx = createFeeTx(ledger->rules(), ledger->seq(), fields);
|
||||
|
||||
OpenView accum(ledger.get());
|
||||
BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx));
|
||||
BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, accum, feeTx)));
|
||||
accum.apply(*ledger);
|
||||
|
||||
// Verify fee object was created/updated correctly
|
||||
BEAST_EXPECT(verifyFeeObject(ledger, ledger->rules(), fields));
|
||||
}
|
||||
|
||||
// Test with both XRPFees and SmartEscrow enabled
|
||||
{
|
||||
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());
|
||||
|
||||
// Create the next ledger to apply transaction to
|
||||
ledger = std::make_shared<Ledger>(*ledger, env.app().getTimeKeeper().closeTime());
|
||||
|
||||
FeeSettingsFields const fields{
|
||||
.baseFeeDrops = XRPAmount{10},
|
||||
.reserveBaseDrops = XRPAmount{200000},
|
||||
.reserveIncrementDrops = XRPAmount{50000},
|
||||
.gasLimit = 100,
|
||||
.bytecodeSizeLimit = 200,
|
||||
.gasPrice = 300};
|
||||
// Test successful fee transaction with new fields
|
||||
auto feeTx = createFeeTx(ledger->rules(), ledger->seq(), fields);
|
||||
|
||||
OpenView accum(ledger.get());
|
||||
BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, accum, feeTx)));
|
||||
accum.apply(*ledger);
|
||||
|
||||
// Verify fee object was created/updated correctly
|
||||
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},
|
||||
.gasLimit = kMaxGasLimit + 1,
|
||||
.bytecodeSizeLimit = kMaxBytecodeSizeLimit,
|
||||
.gasPrice = 300});
|
||||
testBadFields(
|
||||
{.baseFeeDrops = XRPAmount{10},
|
||||
.reserveBaseDrops = XRPAmount{200000},
|
||||
.reserveIncrementDrops = XRPAmount{50000},
|
||||
.gasLimit = kMaxGasLimit,
|
||||
.bytecodeSizeLimit = kMaxBytecodeSizeLimit + 1,
|
||||
.gasPrice = 300});
|
||||
}
|
||||
|
||||
// Test that the Smart Escrow fields are rejected if the
|
||||
// feature is disabled
|
||||
{
|
||||
jtx::Env env(*this, jtx::testableAmendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
env.app().config().fees.toFees(),
|
||||
std::vector<uint256>{},
|
||||
env.app().getNodeFamily());
|
||||
|
||||
// Create the next ledger to apply transaction to
|
||||
ledger = std::make_shared<Ledger>(*ledger, env.app().getTimeKeeper().closeTime());
|
||||
|
||||
FeeSettingsFields const fields{
|
||||
.baseFeeDrops = XRPAmount{10},
|
||||
.reserveBaseDrops = XRPAmount{200000},
|
||||
.reserveIncrementDrops = XRPAmount{50000},
|
||||
.gasLimit = 100,
|
||||
.bytecodeSizeLimit = 200,
|
||||
.gasPrice = 300};
|
||||
// Test successful fee transaction with new fields
|
||||
auto feeTx = createFeeTx(ledger->rules(), ledger->seq(), fields, true);
|
||||
|
||||
OpenView accum(ledger.get());
|
||||
BEAST_EXPECT(!isTesSuccess(applyFeeAndTestResult(env, accum, feeTx)));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -332,7 +516,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
testcase("Fee Transaction Validation");
|
||||
|
||||
{
|
||||
jtx::Env env(*this, jtx::testableAmendments() - featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testableAmendments() - featureXRPFees - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
@@ -346,15 +530,15 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
// Test transaction with missing required legacy fields
|
||||
auto invalidTx = createInvalidFeeTx(ledger->rules(), ledger->seq(), true, false, 1);
|
||||
OpenView accum(ledger.get());
|
||||
BEAST_EXPECT(!applyFeeAndTestResult(env, accum, invalidTx));
|
||||
BEAST_EXPECT(!isTesSuccess(applyFeeAndTestResult(env, accum, invalidTx)));
|
||||
|
||||
// Test transaction with new format fields when XRPFees is disabled
|
||||
auto disallowedTx = createInvalidFeeTx(ledger->rules(), ledger->seq(), false, true, 2);
|
||||
BEAST_EXPECT(!applyFeeAndTestResult(env, accum, disallowedTx));
|
||||
BEAST_EXPECT(!isTesSuccess(applyFeeAndTestResult(env, accum, disallowedTx)));
|
||||
}
|
||||
|
||||
{
|
||||
jtx::Env env(*this, jtx::testableAmendments() | featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testableAmendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
@@ -368,11 +552,33 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
// Test transaction with missing required new fields
|
||||
auto invalidTx = createInvalidFeeTx(ledger->rules(), ledger->seq(), true, false, 3);
|
||||
OpenView accum(ledger.get());
|
||||
BEAST_EXPECT(!applyFeeAndTestResult(env, accum, invalidTx));
|
||||
BEAST_EXPECT(!isTesSuccess(applyFeeAndTestResult(env, accum, invalidTx)));
|
||||
|
||||
// Test transaction with legacy fields when XRPFees is enabled
|
||||
auto disallowedTx = createInvalidFeeTx(ledger->rules(), ledger->seq(), false, true, 4);
|
||||
BEAST_EXPECT(!applyFeeAndTestResult(env, accum, disallowedTx));
|
||||
BEAST_EXPECT(!isTesSuccess(applyFeeAndTestResult(env, accum, disallowedTx)));
|
||||
}
|
||||
|
||||
{
|
||||
jtx::Env env(*this, jtx::testableAmendments() | featureXRPFees | featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
env.app().config().fees.toFees(),
|
||||
std::vector<uint256>{},
|
||||
env.app().getNodeFamily());
|
||||
|
||||
// Create the next ledger to apply transaction to
|
||||
ledger = std::make_shared<Ledger>(*ledger, env.app().getTimeKeeper().closeTime());
|
||||
|
||||
// Test transaction with missing required new fields
|
||||
auto invalidTx = createInvalidFeeTx(ledger->rules(), ledger->seq(), true, false, 5);
|
||||
OpenView accum(ledger.get());
|
||||
BEAST_EXPECT(!isTesSuccess(applyFeeAndTestResult(env, accum, invalidTx)));
|
||||
|
||||
// Test transaction with legacy fields when XRPFees is enabled
|
||||
auto disallowedTx = createInvalidFeeTx(ledger->rules(), ledger->seq(), false, true, 6);
|
||||
BEAST_EXPECT(!isTesSuccess(applyFeeAndTestResult(env, accum, disallowedTx)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,7 +587,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
{
|
||||
testcase("Pseudo Transaction Properties");
|
||||
|
||||
jtx::Env env(*this, jtx::testableAmendments());
|
||||
jtx::Env env(*this, jtx::testableAmendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
@@ -411,7 +617,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
// But can be applied to a closed ledger
|
||||
{
|
||||
OpenView closedAccum(ledger.get());
|
||||
BEAST_EXPECT(applyFeeAndTestResult(env, closedAccum, feeTx));
|
||||
BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, closedAccum, feeTx)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,7 +626,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
{
|
||||
testcase("Multiple Fee Updates");
|
||||
|
||||
jtx::Env env(*this, jtx::testableAmendments() | featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testableAmendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
@@ -438,7 +644,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
|
||||
{
|
||||
OpenView accum(ledger.get());
|
||||
BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx1));
|
||||
BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, accum, feeTx1)));
|
||||
accum.apply(*ledger);
|
||||
}
|
||||
|
||||
@@ -455,7 +661,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
|
||||
{
|
||||
OpenView accum(ledger.get());
|
||||
BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx2));
|
||||
BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, accum, feeTx2)));
|
||||
accum.apply(*ledger);
|
||||
}
|
||||
|
||||
@@ -468,7 +674,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
{
|
||||
testcase("Wrong Ledger Sequence");
|
||||
|
||||
jtx::Env env(*this, jtx::testableAmendments() | featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testableAmendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
@@ -491,7 +697,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
// The transaction should still succeed as long as other fields are
|
||||
// valid
|
||||
// The ledger sequence field is only used for informational purposes
|
||||
BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx));
|
||||
BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, accum, feeTx)));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -499,7 +705,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
{
|
||||
testcase("Partial Field Updates");
|
||||
|
||||
jtx::Env env(*this, jtx::testableAmendments() | featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testableAmendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
@@ -517,7 +723,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
|
||||
{
|
||||
OpenView accum(ledger.get());
|
||||
BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx1));
|
||||
BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, accum, feeTx1)));
|
||||
accum.apply(*ledger);
|
||||
}
|
||||
|
||||
@@ -532,7 +738,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
|
||||
{
|
||||
OpenView accum(ledger.get());
|
||||
BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx2));
|
||||
BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, accum, feeTx2)));
|
||||
accum.apply(*ledger);
|
||||
}
|
||||
|
||||
@@ -545,7 +751,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
{
|
||||
testcase("Single Invalid Transaction");
|
||||
|
||||
jtx::Env env(*this, jtx::testableAmendments() | featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testableAmendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
@@ -567,7 +773,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
});
|
||||
|
||||
OpenView accum(ledger.get());
|
||||
BEAST_EXPECT(!applyFeeAndTestResult(env, accum, invalidTx));
|
||||
BEAST_EXPECT(!isTesSuccess(applyFeeAndTestResult(env, accum, invalidTx)));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -584,7 +790,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
|
||||
// Test with XRPFees enabled
|
||||
{
|
||||
Env env(*this, testableAmendments() | featureXRPFees);
|
||||
Env env(*this, testableAmendments() - featureSmartEscrow);
|
||||
auto feeVote = makeFeeVote(setup, env.app().getJournal("FeeVote"));
|
||||
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
@@ -614,7 +820,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
|
||||
// Test with XRPFees disabled (legacy format)
|
||||
{
|
||||
Env env(*this, testableAmendments() - featureXRPFees);
|
||||
Env env(*this, testableAmendments() - featureXRPFees - featureSmartEscrow);
|
||||
auto feeVote = makeFeeVote(setup, env.app().getJournal("FeeVote"));
|
||||
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
@@ -654,7 +860,7 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
setup.accountReserve = 1234567;
|
||||
setup.ownerReserve = 7654321;
|
||||
|
||||
Env env(*this, testableAmendments() | featureXRPFees);
|
||||
Env env(*this, testableAmendments() - featureSmartEscrow);
|
||||
|
||||
// establish what the current fees are
|
||||
BEAST_EXPECT(env.current()->fees().base == XRPAmount{UNIT_TEST_REFERENCE_FEE});
|
||||
@@ -729,6 +935,274 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
feeTx.getFieldAmount(sfReserveIncrementDrops) == XRPAmount{setup.ownerReserve});
|
||||
}
|
||||
|
||||
void
|
||||
testGenesisFeeSettings()
|
||||
{
|
||||
testcase("genesis FeeSettings carries the configured gas settings");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this, testableAmendments());
|
||||
auto const& cfg = env.app().config().fees;
|
||||
BEAST_EXPECT(cfg.gasLimit != 0 && cfg.bytecodeSizeLimit != 0 && cfg.gasPrice != 0);
|
||||
|
||||
auto const genesisWith = [&](std::vector<uint256> const& amendments) {
|
||||
return std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
cfg.toFees(),
|
||||
amendments,
|
||||
env.app().getNodeFamily());
|
||||
};
|
||||
|
||||
{
|
||||
// With the amendment, the operator's configuration becomes the
|
||||
// network's starting point.
|
||||
auto const ledger = genesisWith({featureSmartEscrow});
|
||||
auto const sle = ledger->read(keylet::feeSettings());
|
||||
if (BEAST_EXPECT(sle))
|
||||
{
|
||||
BEAST_EXPECT(sle->getFieldU32(sfGasLimit) == cfg.gasLimit);
|
||||
BEAST_EXPECT(sle->getFieldU32(sfBytecodeSizeLimit) == cfg.bytecodeSizeLimit);
|
||||
BEAST_EXPECT(sle->getFieldU32(sfGasPrice) == cfg.gasPrice);
|
||||
}
|
||||
BEAST_EXPECT(ledger->fees().gasLimit == cfg.gasLimit);
|
||||
BEAST_EXPECT(ledger->fees().bytecodeSizeLimit == cfg.bytecodeSizeLimit);
|
||||
BEAST_EXPECT(ledger->fees().gasPrice == cfg.gasPrice);
|
||||
}
|
||||
|
||||
{
|
||||
// Without it, the entry carries nothing and the ledger reports
|
||||
// nothing, so the node still has something to vote for.
|
||||
auto const ledger = genesisWith({});
|
||||
auto const sle = ledger->read(keylet::feeSettings());
|
||||
if (BEAST_EXPECT(sle))
|
||||
{
|
||||
BEAST_EXPECT(!sle->isFieldPresent(sfGasLimit));
|
||||
BEAST_EXPECT(!sle->isFieldPresent(sfBytecodeSizeLimit));
|
||||
BEAST_EXPECT(!sle->isFieldPresent(sfGasPrice));
|
||||
}
|
||||
BEAST_EXPECT(ledger->fees().gasLimit == 0);
|
||||
BEAST_EXPECT(ledger->fees().bytecodeSizeLimit == 0);
|
||||
BEAST_EXPECT(ledger->fees().gasPrice == 0);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testDoVotingNoChangePreSmartEscrow()
|
||||
{
|
||||
testcase("doVoting votes for nothing before Smart Escrow");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
// A ledger from before the amendment reports zero for all three gas
|
||||
// settings, while the config targets are non-zero by default. Those
|
||||
// three must not count as a change, or every node emits a SetFee on
|
||||
// every flag ledger for as long as the amendment is off.
|
||||
Env env(*this, testableAmendments() - featureSmartEscrow);
|
||||
|
||||
FeeSetup setup;
|
||||
setup.referenceFee = UNIT_TEST_REFERENCE_FEE;
|
||||
setup.accountReserve = 200'000'000;
|
||||
setup.ownerReserve = 50'000'000;
|
||||
BEAST_EXPECT(setup.gasLimit != 0);
|
||||
BEAST_EXPECT(setup.bytecodeSizeLimit != 0);
|
||||
BEAST_EXPECT(setup.gasPrice != 0);
|
||||
|
||||
// The three-argument Fees leaves the gas settings at zero, which is
|
||||
// what Ledger::setup() reads back from a pre-amendment FeeSettings.
|
||||
Fees const ledgerFees{setup.referenceFee, setup.accountReserve, setup.ownerReserve};
|
||||
|
||||
auto feeVote = makeFeeVote(setup, env.app().getJournal("FeeVote"));
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
ledgerFees,
|
||||
std::vector<uint256>{},
|
||||
env.app().getNodeFamily());
|
||||
|
||||
for (int i = 0; i < 256 - 1; ++i)
|
||||
{
|
||||
ledger = std::make_shared<Ledger>(*ledger, env.app().getTimeKeeper().closeTime());
|
||||
}
|
||||
BEAST_EXPECT(ledger->isFlagLedger());
|
||||
BEAST_EXPECT(ledger->fees().gasLimit == 0);
|
||||
|
||||
std::vector<std::shared_ptr<STValidation>> validations;
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
auto sec = randomSecretKey();
|
||||
auto pub = derivePublicKey(KeyType::Secp256k1, sec);
|
||||
|
||||
auto val = std::make_shared<STValidation>(
|
||||
env.app().getTimeKeeper().now(), pub, sec, calcNodeID(pub), [&](STValidation& v) {
|
||||
v.setFieldU32(sfLedgerSequence, ledger->seq());
|
||||
// Everyone is content with the fees as they stand.
|
||||
v.setFieldAmount(sfBaseFeeDrops, XRPAmount{setup.referenceFee});
|
||||
v.setFieldAmount(sfReserveBaseDrops, XRPAmount{setup.accountReserve});
|
||||
v.setFieldAmount(sfReserveIncrementDrops, XRPAmount{setup.ownerReserve});
|
||||
});
|
||||
if ((i % 2) != 0)
|
||||
val->setTrusted();
|
||||
validations.push_back(val);
|
||||
}
|
||||
|
||||
auto txSet = std::make_shared<SHAMap>(SHAMapType::TRANSACTION, env.app().getNodeFamily());
|
||||
feeVote->doVoting(ledger, validations, txSet);
|
||||
|
||||
BEAST_EXPECT(getTxs(txSet).empty());
|
||||
}
|
||||
|
||||
void
|
||||
testDoVotingSmartEscrow()
|
||||
{
|
||||
testcase("doVoting with Smart Escrow");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this, testableAmendments() | featureXRPFees | featureSmartEscrow);
|
||||
|
||||
// establish what the current fees are
|
||||
BEAST_EXPECT(env.current()->fees().base == XRPAmount{UNIT_TEST_REFERENCE_FEE});
|
||||
BEAST_EXPECT(env.current()->fees().reserve == XRPAmount{200'000'000});
|
||||
BEAST_EXPECT(env.current()->fees().increment == XRPAmount{50'000'000});
|
||||
BEAST_EXPECT(env.current()->fees().gasLimit == 0);
|
||||
BEAST_EXPECT(env.current()->fees().bytecodeSizeLimit == 0);
|
||||
BEAST_EXPECT(env.current()->fees().gasPrice == 0);
|
||||
|
||||
auto const createFeeTxFromVoting =
|
||||
[&](FeeSetup const& setup) -> std::pair<STTx, std::shared_ptr<Ledger>> {
|
||||
auto feeVote = makeFeeVote(setup, env.app().getJournal("FeeVote"));
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
kCreateGenesis,
|
||||
Rules{env.app().config().features},
|
||||
env.app().config().fees.toFees(),
|
||||
std::vector<uint256>{},
|
||||
env.app().getNodeFamily());
|
||||
|
||||
// doVoting requires a flag ledger (every 256th ledger)
|
||||
// We need to create a ledger at sequence 256 to make it a flag
|
||||
// ledger
|
||||
for (int i = 0; i < 256 - 1; ++i)
|
||||
{
|
||||
ledger = std::make_shared<Ledger>(*ledger, env.app().getTimeKeeper().closeTime());
|
||||
}
|
||||
BEAST_EXPECT(ledger->isFlagLedger());
|
||||
|
||||
// Create some mock validations with fee votes
|
||||
std::vector<std::shared_ptr<STValidation>> validations;
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
auto sec = randomSecretKey();
|
||||
auto pub = derivePublicKey(KeyType::Secp256k1, sec);
|
||||
|
||||
auto val = std::make_shared<STValidation>(
|
||||
env.app().getTimeKeeper().now(),
|
||||
pub,
|
||||
sec,
|
||||
calcNodeID(pub),
|
||||
[&](STValidation& v) {
|
||||
v.setFieldU32(sfLedgerSequence, ledger->seq());
|
||||
// Vote for different fees than current
|
||||
v.setFieldAmount(sfBaseFeeDrops, XRPAmount{setup.referenceFee});
|
||||
v.setFieldAmount(sfReserveBaseDrops, XRPAmount{setup.accountReserve});
|
||||
v.setFieldAmount(sfReserveIncrementDrops, XRPAmount{setup.ownerReserve});
|
||||
v.setFieldU32(sfGasLimit, setup.gasLimit);
|
||||
v.setFieldU32(sfBytecodeSizeLimit, setup.bytecodeSizeLimit);
|
||||
v.setFieldU32(sfGasPrice, setup.gasPrice);
|
||||
});
|
||||
if (i % 2)
|
||||
val->setTrusted();
|
||||
validations.push_back(val);
|
||||
}
|
||||
|
||||
auto txSet =
|
||||
std::make_shared<SHAMap>(SHAMapType::TRANSACTION, env.app().getNodeFamily());
|
||||
|
||||
// This should not throw since we have a flag ledger
|
||||
feeVote->doVoting(ledger, validations, txSet);
|
||||
|
||||
auto const txs = getTxs(txSet);
|
||||
BEAST_EXPECT(txs.size() == 1);
|
||||
return {txs[0], ledger};
|
||||
};
|
||||
|
||||
auto checkFeeTx = [&](FeeSetup const& setup,
|
||||
STTx const& feeTx,
|
||||
std::shared_ptr<Ledger> const& ledger,
|
||||
std::source_location const loc = std::source_location::current()) {
|
||||
auto const line = " (" + std::to_string(loc.line()) + ")";
|
||||
BEAST_EXPECTS(feeTx.getTxnType() == ttFEE, line);
|
||||
|
||||
BEAST_EXPECTS(feeTx.getAccountID(sfAccount) == AccountID(), line);
|
||||
BEAST_EXPECTS(feeTx.getFieldU32(sfLedgerSequence) == ledger->seq() + 1, line);
|
||||
|
||||
BEAST_EXPECTS(feeTx.isFieldPresent(sfBaseFeeDrops), line);
|
||||
BEAST_EXPECTS(feeTx.isFieldPresent(sfReserveBaseDrops), line);
|
||||
BEAST_EXPECTS(feeTx.isFieldPresent(sfReserveIncrementDrops), line);
|
||||
|
||||
// The legacy fields should NOT be present
|
||||
BEAST_EXPECTS(!feeTx.isFieldPresent(sfBaseFee), line);
|
||||
BEAST_EXPECTS(!feeTx.isFieldPresent(sfReserveBase), line);
|
||||
BEAST_EXPECTS(!feeTx.isFieldPresent(sfReserveIncrement), line);
|
||||
BEAST_EXPECTS(!feeTx.isFieldPresent(sfReferenceFeeUnits), line);
|
||||
|
||||
// Check the values
|
||||
BEAST_EXPECTS(
|
||||
feeTx.getFieldAmount(sfBaseFeeDrops) == XRPAmount{setup.referenceFee}, line);
|
||||
BEAST_EXPECTS(
|
||||
feeTx.getFieldAmount(sfReserveBaseDrops) == XRPAmount{setup.accountReserve}, line);
|
||||
BEAST_EXPECTS(
|
||||
feeTx.getFieldAmount(sfReserveIncrementDrops) == XRPAmount{setup.ownerReserve},
|
||||
line);
|
||||
BEAST_EXPECTS(feeTx.getFieldU32(sfGasLimit) == setup.gasLimit, line);
|
||||
BEAST_EXPECTS(feeTx.getFieldU32(sfBytecodeSizeLimit) == setup.bytecodeSizeLimit, line);
|
||||
BEAST_EXPECTS(feeTx.getFieldU32(sfGasPrice) == setup.gasPrice, line);
|
||||
};
|
||||
|
||||
{
|
||||
FeeSetup setup;
|
||||
setup.referenceFee = 42;
|
||||
setup.accountReserve = 1234567;
|
||||
setup.ownerReserve = 7654321;
|
||||
setup.gasLimit = 100;
|
||||
setup.bytecodeSizeLimit = 200;
|
||||
setup.gasPrice = 300;
|
||||
auto const [feeTx, ledger] = createFeeTxFromVoting(setup);
|
||||
|
||||
checkFeeTx(setup, feeTx, ledger);
|
||||
}
|
||||
|
||||
{
|
||||
FeeSetup setup;
|
||||
setup.referenceFee = 42;
|
||||
setup.accountReserve = 1234567;
|
||||
setup.ownerReserve = 7654321;
|
||||
setup.gasLimit = 0;
|
||||
setup.bytecodeSizeLimit = 0;
|
||||
setup.gasPrice = 300;
|
||||
auto const [feeTx, ledger] = createFeeTxFromVoting(setup);
|
||||
|
||||
checkFeeTx(setup, feeTx, ledger);
|
||||
}
|
||||
|
||||
{
|
||||
FeeSetup setup;
|
||||
setup.referenceFee = 42;
|
||||
setup.accountReserve = 1234567;
|
||||
setup.ownerReserve = 7654321;
|
||||
setup.gasLimit = kMaxGasLimit + 1;
|
||||
setup.bytecodeSizeLimit = kMaxBytecodeSizeLimit + 1;
|
||||
setup.gasPrice = 300;
|
||||
auto const [feeTx, ledger] = createFeeTxFromVoting(setup);
|
||||
|
||||
setup.gasLimit = ledger->fees().gasLimit;
|
||||
setup.bytecodeSizeLimit = ledger->fees().bytecodeSizeLimit;
|
||||
checkFeeTx(setup, feeTx, ledger);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
@@ -742,6 +1216,9 @@ class FeeVote_test : public beast::unit_test::Suite
|
||||
testSingleInvalidTransaction();
|
||||
testDoValidation();
|
||||
testDoVoting();
|
||||
testGenesisFeeSettings();
|
||||
testDoVotingNoChangePreSmartEscrow();
|
||||
testDoVotingSmartEscrow();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -43,6 +43,12 @@ struct PseudoTx_test : public beast::unit_test::Suite
|
||||
obj[sfReserveIncrement] = 0;
|
||||
obj[sfReferenceFeeUnits] = 0;
|
||||
}
|
||||
if (rules.enabled(featureSmartEscrow))
|
||||
{
|
||||
obj[sfGasLimit] = 0;
|
||||
obj[sfBytecodeSizeLimit] = 0;
|
||||
obj[sfGasPrice] = 0;
|
||||
}
|
||||
});
|
||||
|
||||
res.emplace_back(ttAMENDMENT, [&](auto& obj) {
|
||||
@@ -107,7 +113,9 @@ struct PseudoTx_test : public beast::unit_test::Suite
|
||||
FeatureBitset const all{testableAmendments()};
|
||||
FeatureBitset const xrpFees{featureXRPFees};
|
||||
|
||||
testPrevented(all - featureXRPFees - featureSmartEscrow);
|
||||
testPrevented(all - featureXRPFees);
|
||||
testPrevented(all - featureSmartEscrow);
|
||||
testPrevented(all);
|
||||
testAllowed();
|
||||
}
|
||||
|
||||
@@ -23,9 +23,14 @@ setupConfigForUnitTests(Config& cfg)
|
||||
using namespace jtx;
|
||||
// Default fees to old values, so tests don't have to worry about changes in
|
||||
// Config.h
|
||||
// NOTE: For new `fees` fields, you need to wait for the first flag ledger
|
||||
// to close for the values to be activated.
|
||||
cfg.fees.referenceFee = UNIT_TEST_REFERENCE_FEE;
|
||||
cfg.fees.accountReserve = XRP(200).value().xrp().drops();
|
||||
cfg.fees.ownerReserve = XRP(50).value().xrp().drops();
|
||||
cfg.fees.gasLimit = 1'000'000;
|
||||
cfg.fees.bytecodeSizeLimit = 100'000;
|
||||
cfg.fees.gasPrice = 1'000'000; // 1 drop = 1,000,000 micro-drops
|
||||
|
||||
// The Beta API (currently v2) is always available to tests
|
||||
cfg.betaRpcApi = true;
|
||||
|
||||
@@ -514,6 +514,28 @@ public:
|
||||
if (jv.isMember(jss::reserve_inc) != isFlagLedger)
|
||||
return false;
|
||||
|
||||
if (env.closed()->rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
if (jv.isMember(jss::gas_limit) != isFlagLedger)
|
||||
return false;
|
||||
|
||||
if (jv.isMember(jss::bytecode_size_limit) != isFlagLedger)
|
||||
return false;
|
||||
|
||||
if (jv.isMember(jss::gas_price) != isFlagLedger)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (jv.isMember(jss::gas_limit))
|
||||
return false;
|
||||
|
||||
if (jv.isMember(jss::bytecode_size_limit))
|
||||
return false;
|
||||
|
||||
if (jv.isMember(jss::gas_price))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -1976,7 +1998,8 @@ public:
|
||||
testTransactionsAPIv1();
|
||||
testTransactionsAPIv2();
|
||||
testManifests();
|
||||
testValidations(all - xrpFees);
|
||||
testValidations(all - featureXRPFees - featureSmartEscrow);
|
||||
testValidations(all - featureSmartEscrow);
|
||||
testValidations(all);
|
||||
testSubErrors(true);
|
||||
testSubErrors(false);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <xrpl/core/NetworkIDService.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/PendingSaves.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/server/LoadFeeTrack.h>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
@@ -71,11 +72,22 @@ private:
|
||||
*/
|
||||
class TestServiceRegistry : public ServiceRegistry
|
||||
{
|
||||
static Fees
|
||||
defaultFees()
|
||||
{
|
||||
Fees fees{XRPAmount{10}, XRPAmount{10 * kDropsPerXrp}, XRPAmount{2 * kDropsPerXrp}};
|
||||
fees.gasLimit = 1'000'000;
|
||||
fees.bytecodeSizeLimit = 100'000;
|
||||
fees.gasPrice = 1'000'000;
|
||||
return fees;
|
||||
}
|
||||
|
||||
TestLogs logs_{beast::Severity::Warning};
|
||||
boost::asio::io_context ioContext_;
|
||||
TestFamily family_{logs_.journal("TestFamily")};
|
||||
LoadFeeTrack feeTrack_{logs_.journal("LoadFeeTrack")};
|
||||
TestNetworkIDService networkIDService_;
|
||||
Fees fees_{defaultFees()};
|
||||
HashRouter hashRouter_{HashRouter::Setup{}, stopwatch()};
|
||||
NodeCache tempNodeCache_{
|
||||
"TempNodeCache",
|
||||
@@ -374,6 +386,12 @@ public:
|
||||
throw std::logic_error("TestServiceRegistry::getWalletDB() not implemented");
|
||||
}
|
||||
|
||||
Fees
|
||||
getFees() const override
|
||||
{
|
||||
return fees_;
|
||||
}
|
||||
|
||||
// Temporary: Get the underlying Application
|
||||
Application&
|
||||
getApp() override
|
||||
|
||||
@@ -826,6 +826,24 @@ public:
|
||||
return *walletDB_;
|
||||
}
|
||||
|
||||
Fees
|
||||
getFees() const override
|
||||
{
|
||||
XRPL_ASSERT(config_, "xrpl::ApplicationImp::getFees : non-null config");
|
||||
|
||||
auto const& f1(config_->fees);
|
||||
|
||||
Fees f2;
|
||||
f2.base = f1.referenceFee;
|
||||
f2.reserve = f1.accountReserve;
|
||||
f2.increment = f1.ownerReserve;
|
||||
f2.gasLimit = f1.gasLimit;
|
||||
f2.bytecodeSizeLimit = f1.bytecodeSizeLimit;
|
||||
f2.gasPrice = f1.gasPrice;
|
||||
|
||||
return f2;
|
||||
}
|
||||
|
||||
bool
|
||||
serverOkay(std::string& reason) override;
|
||||
|
||||
|
||||
@@ -33,23 +33,23 @@ namespace xrpl {
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename ValueType>
|
||||
class VotableValue
|
||||
{
|
||||
private:
|
||||
using value_type = XRPAmount;
|
||||
value_type const current_; // The current setting
|
||||
value_type const target_; // The setting we want
|
||||
std::map<value_type, int> voteMap_;
|
||||
ValueType const current_; // The current setting
|
||||
ValueType const target_; // The setting we want
|
||||
std::map<ValueType, int> voteMap_;
|
||||
|
||||
public:
|
||||
VotableValue(value_type current, value_type target) : current_(current), target_(target)
|
||||
VotableValue(ValueType current, ValueType target) : current_(current), target_(target)
|
||||
{
|
||||
// Add our vote
|
||||
++voteMap_[target_];
|
||||
}
|
||||
|
||||
void
|
||||
addVote(value_type vote)
|
||||
addVote(ValueType vote)
|
||||
{
|
||||
++voteMap_[vote];
|
||||
}
|
||||
@@ -60,20 +60,21 @@ public:
|
||||
addVote(current_);
|
||||
}
|
||||
|
||||
[[nodiscard]] value_type
|
||||
[[nodiscard]] ValueType
|
||||
current() const
|
||||
{
|
||||
return current_;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::pair<value_type, bool>
|
||||
[[nodiscard]] std::pair<ValueType, bool>
|
||||
getVotes() const;
|
||||
};
|
||||
|
||||
auto
|
||||
VotableValue::getVotes() const -> std::pair<value_type, bool>
|
||||
template <typename ValueType>
|
||||
std::pair<ValueType, bool>
|
||||
VotableValue<ValueType>::getVotes() const
|
||||
{
|
||||
value_type ourVote = current_;
|
||||
ValueType ourVote = current_;
|
||||
int weight = 0;
|
||||
for (auto const& [key, val] : voteMap_)
|
||||
{
|
||||
@@ -125,17 +126,16 @@ FeeVoteImpl::doValidation(Fees const& lastFees, Rules const& rules, STValidation
|
||||
// Values should always be in a valid range (because the voting process
|
||||
// will ignore out-of-range values) but if we detect such a case, we do
|
||||
// not send a value.
|
||||
auto vote = [&v, this](auto const current, auto target, char const* name, auto const& sfield) {
|
||||
if (current != target)
|
||||
{
|
||||
JLOG(journal_.info()) << "Voting for " << name << " of " << target;
|
||||
|
||||
v[sfield] = target;
|
||||
}
|
||||
};
|
||||
if (rules.enabled(featureXRPFees))
|
||||
{
|
||||
auto vote =
|
||||
[&v, this](auto const current, XRPAmount target, char const* name, auto const& sfield) {
|
||||
if (current != target)
|
||||
{
|
||||
JLOG(journal_.info()) << "Voting for " << name << " of " << target;
|
||||
|
||||
v[sfield] = target;
|
||||
}
|
||||
};
|
||||
vote(lastFees.base, target_.referenceFee, "base fee", sfBaseFeeDrops);
|
||||
vote(lastFees.reserve, target_.accountReserve, "base reserve", sfReserveBaseDrops);
|
||||
vote(
|
||||
@@ -145,12 +145,12 @@ FeeVoteImpl::doValidation(Fees const& lastFees, Rules const& rules, STValidation
|
||||
{
|
||||
auto to32 = [](XRPAmount target) { return target.dropsAs<std::uint32_t>(); };
|
||||
auto to64 = [](XRPAmount target) { return target.dropsAs<std::uint64_t>(); };
|
||||
auto vote = [&v, this](
|
||||
auto const current,
|
||||
XRPAmount target,
|
||||
auto const& convertCallback,
|
||||
char const* name,
|
||||
auto const& sfield) {
|
||||
auto voteAndConvert = [&v, this](
|
||||
auto const current,
|
||||
XRPAmount target,
|
||||
auto const& convertCallback,
|
||||
char const* name,
|
||||
auto const& sfield) {
|
||||
if (current != target)
|
||||
{
|
||||
JLOG(journal_.info()) << "Voting for " << name << " of " << target;
|
||||
@@ -160,15 +160,32 @@ FeeVoteImpl::doValidation(Fees const& lastFees, Rules const& rules, STValidation
|
||||
}
|
||||
};
|
||||
|
||||
vote(lastFees.base, target_.referenceFee, to64, "base fee", sfBaseFee);
|
||||
vote(lastFees.reserve, target_.accountReserve, to32, "base reserve", sfReserveBase);
|
||||
vote(
|
||||
voteAndConvert(lastFees.base, target_.referenceFee, to64, "base fee", sfBaseFee);
|
||||
voteAndConvert(
|
||||
lastFees.reserve, target_.accountReserve, to32, "base reserve", sfReserveBase);
|
||||
voteAndConvert(
|
||||
lastFees.increment,
|
||||
target_.ownerReserve,
|
||||
to32,
|
||||
"reserve increment",
|
||||
sfReserveIncrement);
|
||||
}
|
||||
if (rules.enabled(featureSmartEscrow))
|
||||
{
|
||||
if (target_.gasLimit <= kMaxGasLimit)
|
||||
{
|
||||
vote(lastFees.gasLimit, target_.gasLimit, "gas limit", sfGasLimit);
|
||||
}
|
||||
if (target_.bytecodeSizeLimit <= kMaxBytecodeSizeLimit)
|
||||
{
|
||||
vote(
|
||||
lastFees.bytecodeSizeLimit,
|
||||
target_.bytecodeSizeLimit,
|
||||
"bytecode size limit",
|
||||
sfBytecodeSizeLimit);
|
||||
}
|
||||
vote(lastFees.gasPrice, target_.gasPrice, "gas price", sfGasPrice);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -188,11 +205,28 @@ FeeVoteImpl::doVoting(
|
||||
|
||||
detail::VotableValue incReserveVote(lastClosedLedger->fees().increment, target_.ownerReserve);
|
||||
|
||||
auto validOrCurrent = [](std::uint32_t target, std::uint32_t max, std::uint32_t current) {
|
||||
return target <= max ? target : current;
|
||||
};
|
||||
|
||||
detail::VotableValue gasLimitVote(
|
||||
lastClosedLedger->fees().gasLimit,
|
||||
validOrCurrent(target_.gasLimit, kMaxGasLimit, lastClosedLedger->fees().gasLimit));
|
||||
|
||||
detail::VotableValue bytecodeSizeLimitVote(
|
||||
lastClosedLedger->fees().bytecodeSizeLimit,
|
||||
validOrCurrent(
|
||||
target_.bytecodeSizeLimit,
|
||||
kMaxBytecodeSizeLimit,
|
||||
lastClosedLedger->fees().bytecodeSizeLimit));
|
||||
|
||||
detail::VotableValue gasPriceVote(lastClosedLedger->fees().gasPrice, target_.gasPrice);
|
||||
|
||||
auto const& rules = lastClosedLedger->rules();
|
||||
if (rules.enabled(featureXRPFees))
|
||||
{
|
||||
auto doVote = [](std::shared_ptr<STValidation> const& val,
|
||||
detail::VotableValue& value,
|
||||
detail::VotableValue<XRPAmount>& value,
|
||||
SF_AMOUNT const& xrpField) {
|
||||
if (auto const field = ~val->at(~xrpField); field && field->native())
|
||||
{
|
||||
@@ -224,7 +258,7 @@ FeeVoteImpl::doVoting(
|
||||
else
|
||||
{
|
||||
auto doVote = [](std::shared_ptr<STValidation> const& val,
|
||||
detail::VotableValue& value,
|
||||
detail::VotableValue<XRPAmount>& value,
|
||||
auto const& valueField) {
|
||||
if (auto const field = val->at(~valueField))
|
||||
{
|
||||
@@ -258,16 +292,58 @@ FeeVoteImpl::doVoting(
|
||||
doVote(val, incReserveVote, sfReserveIncrement);
|
||||
}
|
||||
}
|
||||
if (rules.enabled(featureSmartEscrow))
|
||||
{
|
||||
auto doVote = [](std::shared_ptr<STValidation> const& val,
|
||||
detail::VotableValue<std::uint32_t>& value,
|
||||
SF_UINT32 const& sfield,
|
||||
std::uint32_t maxValue) {
|
||||
if (auto const field = ~val->at(~sfield); field)
|
||||
{
|
||||
if (field.value() <= maxValue)
|
||||
{
|
||||
value.addVote(field.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
value.noVote();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
value.noVote();
|
||||
}
|
||||
};
|
||||
|
||||
for (auto const& val : set)
|
||||
{
|
||||
if (!val->isTrusted())
|
||||
continue;
|
||||
doVote(val, gasLimitVote, sfGasLimit, kMaxGasLimit);
|
||||
doVote(val, bytecodeSizeLimitVote, sfBytecodeSizeLimit, kMaxBytecodeSizeLimit);
|
||||
doVote(val, gasPriceVote, sfGasPrice, std::numeric_limits<std::uint32_t>::max());
|
||||
}
|
||||
}
|
||||
|
||||
// choose our positions
|
||||
auto const [baseFee, baseFeeChanged] = baseFeeVote.getVotes();
|
||||
auto const [baseReserve, baseReserveChanged] = baseReserveVote.getVotes();
|
||||
auto const [incReserve, incReserveChanged] = incReserveVote.getVotes();
|
||||
auto const [gasLimit, gasLimitChanged] = gasLimitVote.getVotes();
|
||||
auto const [bytecodeSizeLimit, bytecodeSizeLimitChanged] = bytecodeSizeLimitVote.getVotes();
|
||||
auto const [gasPrice, gasPriceChanged] = gasPriceVote.getVotes();
|
||||
|
||||
auto const seq = lastClosedLedger->header().seq + 1;
|
||||
|
||||
// add transactions to our position
|
||||
if (baseFeeChanged || baseReserveChanged || incReserveChanged)
|
||||
//
|
||||
// The gas votes only count once the amendment is on. Before it is, the
|
||||
// ledger reports zero for all three while the config targets are
|
||||
// non-zero, so they would report a change on every flag ledger and have
|
||||
// us emit a SetFee that carries no gas fields and changes nothing.
|
||||
if (baseFeeChanged || baseReserveChanged || incReserveChanged ||
|
||||
(rules.enabled(featureSmartEscrow) &&
|
||||
(gasLimitChanged || bytecodeSizeLimitChanged || gasPriceChanged)))
|
||||
{
|
||||
JLOG(journal_.warn()) << "We are voting for a fee change: " << baseFee << "/" << baseReserve
|
||||
<< "/" << incReserve;
|
||||
@@ -291,6 +367,12 @@ FeeVoteImpl::doVoting(
|
||||
incReserve.dropsAs<std::uint32_t>(incReserveVote.current());
|
||||
obj[sfReferenceFeeUnits] = kFeeUnitsDeprecated;
|
||||
}
|
||||
if (rules.enabled(featureSmartEscrow))
|
||||
{
|
||||
obj[sfGasLimit] = gasLimit;
|
||||
obj[sfBytecodeSizeLimit] = bytecodeSizeLimit;
|
||||
obj[sfGasPrice] = gasPrice;
|
||||
}
|
||||
});
|
||||
|
||||
uint256 const txID = feeTx.getTransactionID();
|
||||
|
||||
@@ -2646,6 +2646,15 @@ NetworkOPsImp::pubValidation(std::shared_ptr<STValidation> const& val)
|
||||
reserveIncXRP && reserveIncXRP->native())
|
||||
jvObj[jss::reserve_inc] = reserveIncXRP->xrp().jsonClipped();
|
||||
|
||||
if (auto const gasLimit = ~val->at(~sfGasLimit); gasLimit)
|
||||
jvObj[jss::gas_limit] = *gasLimit;
|
||||
|
||||
if (auto const bytecodeSizeLimit = ~val->at(~sfBytecodeSizeLimit); bytecodeSizeLimit)
|
||||
jvObj[jss::bytecode_size_limit] = *bytecodeSizeLimit;
|
||||
|
||||
if (auto const gasPrice = ~val->at(~sfGasPrice); gasPrice)
|
||||
jvObj[jss::gas_price] = *gasPrice;
|
||||
|
||||
// NOTE Use MultiApiJson to publish two slightly different JSON objects
|
||||
// for consumers supporting different API versions
|
||||
MultiApiJson multiObj{jvObj};
|
||||
@@ -3101,11 +3110,18 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters)
|
||||
l[jss::seq] = json::UInt(lpClosed->header().seq);
|
||||
l[jss::hash] = to_string(lpClosed->header().hash);
|
||||
|
||||
bool const smartEscrowEnabled = lpClosed->rules().enabled(featureSmartEscrow);
|
||||
if (!human)
|
||||
{
|
||||
l[jss::base_fee] = baseFee.jsonClipped();
|
||||
l[jss::reserve_base] = lpClosed->fees().reserve.jsonClipped();
|
||||
l[jss::reserve_inc] = lpClosed->fees().increment.jsonClipped();
|
||||
if (smartEscrowEnabled)
|
||||
{
|
||||
l[jss::gas_limit] = lpClosed->fees().gasLimit;
|
||||
l[jss::bytecode_size_limit] = lpClosed->fees().bytecodeSizeLimit;
|
||||
l[jss::gas_price] = lpClosed->fees().gasPrice;
|
||||
}
|
||||
l[jss::close_time] =
|
||||
json::Value::UInt(lpClosed->header().closeTime.time_since_epoch().count());
|
||||
}
|
||||
@@ -3114,6 +3130,12 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters)
|
||||
l[jss::base_fee_xrp] = baseFee.decimalXRP();
|
||||
l[jss::reserve_base_xrp] = lpClosed->fees().reserve.decimalXRP();
|
||||
l[jss::reserve_inc_xrp] = lpClosed->fees().increment.decimalXRP();
|
||||
if (smartEscrowEnabled)
|
||||
{
|
||||
l[jss::gas_limit] = lpClosed->fees().gasLimit;
|
||||
l[jss::bytecode_size_limit] = lpClosed->fees().bytecodeSizeLimit;
|
||||
l[jss::gas_price] = lpClosed->fees().gasPrice;
|
||||
}
|
||||
|
||||
if (auto const closeOffset = registry_.get().getTimeKeeper().closeOffset();
|
||||
std::abs(closeOffset.count()) >= 60)
|
||||
@@ -3342,6 +3364,12 @@ NetworkOPsImp::publishLedgerStreams(
|
||||
jvObj[jss::fee_base] = lpAccepted->fees().base.jsonClipped();
|
||||
jvObj[jss::reserve_base] = lpAccepted->fees().reserve.jsonClipped();
|
||||
jvObj[jss::reserve_inc] = lpAccepted->fees().increment.jsonClipped();
|
||||
if (lpAccepted->rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
jvObj[jss::gas_limit] = lpAccepted->fees().gasLimit;
|
||||
jvObj[jss::bytecode_size_limit] = lpAccepted->fees().bytecodeSizeLimit;
|
||||
jvObj[jss::gas_price] = lpAccepted->fees().gasPrice;
|
||||
}
|
||||
|
||||
jvObj[jss::txn_count] = json::UInt(alpAccepted->size());
|
||||
|
||||
@@ -4540,6 +4568,12 @@ NetworkOPsImp::subLedger(InfoSub::ref isrListener, json::Value& jvResult)
|
||||
jvResult[jss::reserve_base] = lpClosed->fees().reserve.jsonClipped();
|
||||
jvResult[jss::reserve_inc] = lpClosed->fees().increment.jsonClipped();
|
||||
jvResult[jss::network_id] = registry_.get().getNetworkIDService().getNetworkID();
|
||||
if (lpClosed->rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
jvResult[jss::gas_limit] = lpClosed->fees().gasLimit;
|
||||
jvResult[jss::bytecode_size_limit] = lpClosed->fees().bytecodeSizeLimit;
|
||||
jvResult[jss::gas_price] = lpClosed->fees().gasPrice;
|
||||
}
|
||||
}
|
||||
|
||||
if ((mode_ >= OperatingMode::SYNCING) && !isNeedNetworkLedger())
|
||||
|
||||
@@ -66,6 +66,21 @@ struct FeeSetup
|
||||
*/
|
||||
XRPAmount ownerReserve{2 * kDropsPerXrp};
|
||||
|
||||
/**
|
||||
* The gas limit for Feature Extensions.
|
||||
*/
|
||||
std::uint32_t gasLimit{1'000'000};
|
||||
|
||||
/**
|
||||
* The bytecode size limit for Feature Extensions.
|
||||
*/
|
||||
std::uint32_t bytecodeSizeLimit{100'000};
|
||||
|
||||
/**
|
||||
* The price of 1 WASM gas, in micro-drops.
|
||||
*/
|
||||
std::uint32_t gasPrice{1'000'000};
|
||||
|
||||
/* (Remember to update the example cfg files when changing any of these
|
||||
* values.) */
|
||||
|
||||
@@ -75,7 +90,11 @@ struct FeeSetup
|
||||
[[nodiscard]] Fees
|
||||
toFees() const
|
||||
{
|
||||
return Fees{referenceFee, accountReserve, ownerReserve};
|
||||
Fees fees{referenceFee, accountReserve, ownerReserve};
|
||||
fees.gasLimit = gasLimit;
|
||||
fees.bytecodeSizeLimit = bytecodeSizeLimit;
|
||||
fees.gasPrice = gasPrice;
|
||||
return fees;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <xrpl/config/Constants.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>
|
||||
@@ -1253,6 +1254,12 @@ setupFeeVote(Section const& section)
|
||||
setup.accountReserve = temp;
|
||||
if (set(temp, Keys::kOwnerReserve, section))
|
||||
setup.ownerReserve = temp;
|
||||
if (set(temp, Keys::kGasLimit, section) && temp <= kMaxGasLimit)
|
||||
setup.gasLimit = temp;
|
||||
if (set(temp, Keys::kBytecodeSizeLimit, section) && temp <= kMaxBytecodeSizeLimit)
|
||||
setup.bytecodeSizeLimit = temp;
|
||||
if (set(temp, Keys::kGasPrice, section))
|
||||
setup.gasPrice = temp;
|
||||
}
|
||||
return setup;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user