mirror of
https://github.com/XRPLF/rippled.git
synced 2026-02-14 02:42:32 +00:00
Compare commits
44 Commits
ripple/was
...
ripple/se/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fe508cb92 | ||
|
|
eb2d44d442 | ||
|
|
cd46b5d999 | ||
|
|
13707dda05 | ||
|
|
f625fb993a | ||
|
|
69c61b2235 | ||
|
|
6ae0e860ff | ||
|
|
c077e7f073 | ||
|
|
4621e4eda3 | ||
|
|
981ac7abf4 | ||
|
|
43c80edaf4 | ||
|
|
384b3608d7 | ||
|
|
e1513570df | ||
|
|
f0d0739528 | ||
|
|
103379836a | ||
|
|
397bc8781e | ||
|
|
8bb8c2e38b | ||
|
|
36ecd3b52b | ||
|
|
9d1f51b01a | ||
|
|
827ecc6e3a | ||
|
|
27ac30208d | ||
|
|
e40a4df777 | ||
|
|
95d78a8600 | ||
|
|
578413859c | ||
|
|
3195eb16b2 | ||
|
|
eed280d169 | ||
|
|
aebec5378c | ||
|
|
67e2c1b563 | ||
|
|
209ee25c32 | ||
|
|
af6beb1d7c | ||
|
|
ce19c13059 | ||
|
|
43caa1ef29 | ||
|
|
fe601308e7 | ||
|
|
51f1be7f5b | ||
|
|
f57b855d74 | ||
|
|
86525d8583 | ||
|
|
51ee06429b | ||
|
|
ca85d09f02 | ||
|
|
e59f5f3b01 | ||
|
|
c8b06e7de1 | ||
|
|
c3fd52c177 | ||
|
|
737fab5471 | ||
|
|
5a6c4e8ae0 | ||
|
|
7420f47658 |
@@ -1272,6 +1272,39 @@
|
||||
# Example:
|
||||
# owner_reserve = 2000000 # 2 XRP
|
||||
#
|
||||
# extension_compute_limit = <gas>
|
||||
#
|
||||
# The extension compute 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:
|
||||
# extension_compute_limit = 1000000 # 1 million gas
|
||||
#
|
||||
# extension_size_limit = <bytes>
|
||||
#
|
||||
# The extension 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:
|
||||
# extension_size_limit = 100000 # 100 kb
|
||||
#
|
||||
# gas_price = <bytes>
|
||||
#
|
||||
# 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
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
constexpr std::uint32_t MICRO_DROPS_PER_DROP{1'000'000};
|
||||
|
||||
/** Reflects the fee settings for a particular ledger.
|
||||
|
||||
The fees are always the same for any transactions applied
|
||||
@@ -11,9 +13,12 @@ namespace xrpl {
|
||||
*/
|
||||
struct Fees
|
||||
{
|
||||
XRPAmount base{0}; // Reference tx cost (drops)
|
||||
XRPAmount reserve{0}; // Reserve base (drops)
|
||||
XRPAmount increment{0}; // Reserve increment (drops)
|
||||
XRPAmount base{0}; // Reference tx cost (drops)
|
||||
XRPAmount reserve{0}; // Reserve base (drops)
|
||||
XRPAmount increment{0}; // Reserve increment (drops)
|
||||
std::uint32_t extensionComputeLimit{0}; // Extension compute limit (instructions)
|
||||
std::uint32_t extensionSizeLimit{0}; // Extension size limit (bytes)
|
||||
std::uint32_t gasPrice{0}; // price of WASM gas (micro-drops)
|
||||
|
||||
explicit Fees() = default;
|
||||
Fees(Fees const&) = default;
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
|
||||
// Add new amendments to the top of this list.
|
||||
// Keep it sorted in reverse chronological order.
|
||||
XRPL_FIX (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo)
|
||||
|
||||
XRPL_FEATURE(SmartEscrow, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (ExpiredNFTokenOfferRemoval, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (BatchInnerSigs, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(LendingProtocol, Supported::yes, VoteBehavior::DefaultNo)
|
||||
@@ -32,9 +34,8 @@ XRPL_FIX (EnforceNFTokenTrustlineV2, Supported::yes, VoteBehavior::DefaultNo
|
||||
XRPL_FIX (AMMv1_3, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(PermissionedDEX, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(SingleAssetVault, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(SingleAssetVault, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (PayChanCancelAfter, Supported::yes, VoteBehavior::DefaultNo)
|
||||
// Check flags in Credential transactions
|
||||
XRPL_FIX (InvalidTxFlags, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (FrozenLPTokenTransfer, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -302,6 +302,11 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({
|
||||
{sfBaseFeeDrops, soeOPTIONAL},
|
||||
{sfReserveBaseDrops, soeOPTIONAL},
|
||||
{sfReserveIncrementDrops, soeOPTIONAL},
|
||||
// Smart Escrow fields
|
||||
{sfExtensionComputeLimit, soeOPTIONAL},
|
||||
{sfExtensionSizeLimit, soeOPTIONAL},
|
||||
{sfGasPrice, soeOPTIONAL},
|
||||
|
||||
{sfPreviousTxnID, soeOPTIONAL},
|
||||
{sfPreviousTxnLgrSeq, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
@@ -114,6 +114,9 @@ TYPED_SFIELD(sfInterestRate, UINT32, 65) // 1/10 basis points (bi
|
||||
TYPED_SFIELD(sfLateInterestRate, UINT32, 66) // 1/10 basis points (bips)
|
||||
TYPED_SFIELD(sfCloseInterestRate, UINT32, 67) // 1/10 basis points (bips)
|
||||
TYPED_SFIELD(sfOverpaymentInterestRate, UINT32, 68) // 1/10 basis points (bips)
|
||||
TYPED_SFIELD(sfExtensionComputeLimit, UINT32, 69)
|
||||
TYPED_SFIELD(sfExtensionSizeLimit, UINT32, 70)
|
||||
TYPED_SFIELD(sfGasPrice, UINT32, 71)
|
||||
|
||||
// 64-bit integers (common)
|
||||
TYPED_SFIELD(sfIndexNext, UINT64, 1)
|
||||
|
||||
@@ -1092,6 +1092,10 @@ TRANSACTION(ttFEE, 101, SetFee,
|
||||
{sfBaseFeeDrops, soeOPTIONAL},
|
||||
{sfReserveBaseDrops, soeOPTIONAL},
|
||||
{sfReserveIncrementDrops, soeOPTIONAL},
|
||||
// Smart Escrow fields
|
||||
{sfExtensionComputeLimit, soeOPTIONAL},
|
||||
{sfExtensionSizeLimit, soeOPTIONAL},
|
||||
{sfGasPrice, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This system-generated transaction type is used to update the network's negative UNL
|
||||
|
||||
@@ -254,6 +254,9 @@ JSS(expected_date_UTC); // out: any (warnings)
|
||||
JSS(expected_ledger_size); // out: TxQ
|
||||
JSS(expiration); // out: AccountOffers, AccountChannels,
|
||||
// ValidatorList, amm_info
|
||||
JSS(extension_compute); // out: NetworkOps
|
||||
JSS(extension_size); // out: NetworkOps
|
||||
JSS(gas_price); // out: NetworkOps
|
||||
JSS(fail_hard); // in: Sign, Submit
|
||||
JSS(failed); // out: InboundLedger
|
||||
JSS(feature); // in: Feature
|
||||
@@ -708,11 +711,11 @@ JSS(write_load); // out: GetCounts
|
||||
#pragma push_macro("LEDGER_ENTRY_DUPLICATE")
|
||||
#undef LEDGER_ENTRY_DUPLICATE
|
||||
|
||||
#define LEDGER_ENTRY(tag, value, name, rpcName, ...) \
|
||||
JSS(name); \
|
||||
#define LEDGER_ENTRY(tag, value, name, rpcName, fields) \
|
||||
JSS(name); \
|
||||
JSS(rpcName);
|
||||
|
||||
#define LEDGER_ENTRY_DUPLICATE(tag, value, name, rpcName, ...) JSS(rpcName);
|
||||
#define LEDGER_ENTRY_DUPLICATE(tag, value, name, rpcName, fields) JSS(rpcName);
|
||||
|
||||
#include <xrpl/protocol/detail/ledger_entries.macro>
|
||||
|
||||
|
||||
@@ -58,6 +58,10 @@ STValidation::validationFormat()
|
||||
{sfBaseFeeDrops, soeOPTIONAL},
|
||||
{sfReserveBaseDrops, soeOPTIONAL},
|
||||
{sfReserveIncrementDrops, soeOPTIONAL},
|
||||
// featureSmartEscrow
|
||||
{sfExtensionComputeLimit, soeOPTIONAL},
|
||||
{sfExtensionSizeLimit, soeOPTIONAL},
|
||||
{sfGasPrice, soeOPTIONAL},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
|
||||
#include <source_location>
|
||||
|
||||
namespace xrpl {
|
||||
namespace test {
|
||||
|
||||
@@ -24,10 +26,13 @@ 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> extensionComputeLimit = std::nullopt;
|
||||
std::optional<std::uint32_t> extensionSizeLimit = 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());
|
||||
@@ -49,6 +54,12 @@ createFeeTx(Rules const& rules, std::uint32_t seq, FeeSettingsFields const& fiel
|
||||
obj.setFieldU32(sfReserveIncrement, fields.reserveIncrement ? *fields.reserveIncrement : 0);
|
||||
obj.setFieldU32(sfReferenceFeeUnits, fields.referenceFeeUnits ? *fields.referenceFeeUnits : 0);
|
||||
}
|
||||
if (rules.enabled(featureSmartEscrow) || forceAllFields)
|
||||
{
|
||||
obj.setFieldU32(sfExtensionComputeLimit, fields.extensionComputeLimit ? *fields.extensionComputeLimit : 0);
|
||||
obj.setFieldU32(sfExtensionSizeLimit, fields.extensionSizeLimit ? *fields.extensionSizeLimit : 0);
|
||||
obj.setFieldU32(sfGasPrice, fields.gasPrice ? *fields.gasPrice : 0);
|
||||
}
|
||||
};
|
||||
return STTx(ttFEE, fill);
|
||||
}
|
||||
@@ -97,6 +108,12 @@ createInvalidFeeTx(
|
||||
obj.setFieldU32(sfReserveIncrement, 50000);
|
||||
obj.setFieldU32(sfReferenceFeeUnits, 10);
|
||||
}
|
||||
if (rules.enabled(featureSmartEscrow))
|
||||
{
|
||||
obj.setFieldU32(sfExtensionComputeLimit, 100 + uniqueValue);
|
||||
obj.setFieldU32(sfExtensionSizeLimit, 200 + uniqueValue);
|
||||
obj.setFieldU32(sfGasPrice, 300 + uniqueValue);
|
||||
}
|
||||
}
|
||||
// If missingRequiredFields is true, we don't add the required fields
|
||||
// (default behavior)
|
||||
@@ -104,11 +121,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 res.ter == tesSUCCESS;
|
||||
return res.ter;
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -153,6 +170,21 @@ verifyFeeObject(std::shared_ptr<Ledger const> const& ledger, Rules const& rules,
|
||||
if (!checkEquality(sfReferenceFeeUnits, expected.referenceFeeUnits))
|
||||
return false;
|
||||
}
|
||||
if (rules.enabled(featureSmartEscrow))
|
||||
{
|
||||
if (!checkEquality(sfExtensionComputeLimit, expected.extensionComputeLimit.value_or(0)))
|
||||
return false;
|
||||
if (!checkEquality(sfExtensionSizeLimit, expected.extensionSizeLimit.value_or(0)))
|
||||
return false;
|
||||
if (!checkEquality(sfGasPrice, expected.gasPrice.value_or(0)))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (feeObject->isFieldPresent(sfExtensionComputeLimit) || feeObject->isFieldPresent(sfExtensionSizeLimit) ||
|
||||
feeObject->isFieldPresent(sfGasPrice))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -175,6 +207,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
void
|
||||
testSetup()
|
||||
{
|
||||
testcase("FeeVote setup");
|
||||
FeeSetup const defaultSetup;
|
||||
{
|
||||
// defaults
|
||||
@@ -183,43 +216,84 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
|
||||
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
|
||||
BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve);
|
||||
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 == defaultSetup.gas_price);
|
||||
}
|
||||
{
|
||||
Section config;
|
||||
config.append({"reference_fee = 50", "account_reserve = 1234567", "owner_reserve = 1234"});
|
||||
config.append(
|
||||
{"reference_fee = 50",
|
||||
"account_reserve = 1234567",
|
||||
"owner_reserve = 1234",
|
||||
"extension_compute_limit = 100",
|
||||
"extension_size_limit = 200",
|
||||
" gas_price = 300"});
|
||||
auto setup = setup_FeeVote(config);
|
||||
BEAST_EXPECT(setup.reference_fee == 50);
|
||||
BEAST_EXPECT(setup.account_reserve == 1234567);
|
||||
BEAST_EXPECT(setup.owner_reserve == 1234);
|
||||
BEAST_EXPECT(setup.extension_compute_limit == 100);
|
||||
BEAST_EXPECT(setup.extension_size_limit == 200);
|
||||
BEAST_EXPECT(setup.gas_price == 300);
|
||||
}
|
||||
{
|
||||
Section config;
|
||||
config.append({"reference_fee = blah", "account_reserve = yada", "owner_reserve = foo"});
|
||||
config.append(
|
||||
{"reference_fee = blah",
|
||||
"account_reserve = yada",
|
||||
"owner_reserve = foo",
|
||||
"extension_compute_limit = bar",
|
||||
"extension_size_limit = baz",
|
||||
"gas_price = qux"});
|
||||
// Illegal values are ignored, and the defaults left unchanged
|
||||
auto setup = setup_FeeVote(config);
|
||||
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
|
||||
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
|
||||
BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve);
|
||||
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 == defaultSetup.gas_price);
|
||||
}
|
||||
{
|
||||
Section config;
|
||||
config.append({"reference_fee = -50", "account_reserve = -1234567", "owner_reserve = -1234"});
|
||||
// Illegal values are ignored, and the defaults left unchanged
|
||||
config.append(
|
||||
{"reference_fee = -50",
|
||||
"account_reserve = -1234567",
|
||||
"owner_reserve = -1234",
|
||||
"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.
|
||||
auto setup = setup_FeeVote(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.gas_price == static_cast<std::uint32_t>(-300));
|
||||
}
|
||||
{
|
||||
auto const big64 =
|
||||
std::to_string(static_cast<std::uint64_t>(std::numeric_limits<XRPAmount::value_type>::max()) + 1);
|
||||
Section config;
|
||||
config.append({"reference_fee = " + big64, "account_reserve = " + big64, "owner_reserve = " + big64});
|
||||
config.append(
|
||||
{"reference_fee = " + big64,
|
||||
"account_reserve = " + big64,
|
||||
"owner_reserve = " + big64,
|
||||
"extension_compute_limit = " + big64,
|
||||
"extension_size_limit = " + big64,
|
||||
"gas_price = " + big64});
|
||||
// Illegal values are ignored, and the defaults left unchanged
|
||||
auto setup = setup_FeeVote(config);
|
||||
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
|
||||
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
|
||||
BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve);
|
||||
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 == defaultSetup.gas_price);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,7 +304,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
|
||||
// Test with XRPFees disabled (legacy format)
|
||||
{
|
||||
jtx::Env env(*this, jtx::testable_amendments() - featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testable_amendments() - featureXRPFees - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), std::vector<uint256>{}, env.app().getNodeFamily());
|
||||
|
||||
@@ -244,7 +318,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
|
||||
@@ -253,7 +327,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
|
||||
// Test with XRPFees enabled (new format)
|
||||
{
|
||||
jtx::Env env(*this, jtx::testable_amendments() | featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testable_amendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), std::vector<uint256>{}, env.app().getNodeFamily());
|
||||
|
||||
@@ -268,12 +342,63 @@ 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::testable_amendments());
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), std::vector<uint256>{}, env.app().getNodeFamily());
|
||||
|
||||
// Create the next ledger to apply transaction to
|
||||
ledger = std::make_shared<Ledger>(*ledger, env.app().timeKeeper().closeTime());
|
||||
|
||||
FeeSettingsFields fields{
|
||||
.baseFeeDrops = XRPAmount{10},
|
||||
.reserveBaseDrops = XRPAmount{200000},
|
||||
.reserveIncrementDrops = XRPAmount{50000},
|
||||
.extensionComputeLimit = 100,
|
||||
.extensionSizeLimit = 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 the Smart Escrow fields are rejected if the
|
||||
// feature is disabled
|
||||
{
|
||||
jtx::Env env(*this, jtx::testable_amendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), std::vector<uint256>{}, env.app().getNodeFamily());
|
||||
|
||||
// Create the next ledger to apply transaction to
|
||||
ledger = std::make_shared<Ledger>(*ledger, env.app().timeKeeper().closeTime());
|
||||
|
||||
FeeSettingsFields fields{
|
||||
.baseFeeDrops = XRPAmount{10},
|
||||
.reserveBaseDrops = XRPAmount{200000},
|
||||
.reserveIncrementDrops = XRPAmount{50000},
|
||||
.extensionComputeLimit = 100,
|
||||
.extensionSizeLimit = 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
|
||||
@@ -282,7 +407,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
testcase("Fee Transaction Validation");
|
||||
|
||||
{
|
||||
jtx::Env env(*this, jtx::testable_amendments() - featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testable_amendments() - featureXRPFees - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), std::vector<uint256>{}, env.app().getNodeFamily());
|
||||
|
||||
@@ -292,15 +417,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::testable_amendments() | featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testable_amendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), std::vector<uint256>{}, env.app().getNodeFamily());
|
||||
|
||||
@@ -310,11 +435,29 @@ 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::testable_amendments() | featureXRPFees | featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), std::vector<uint256>{}, env.app().getNodeFamily());
|
||||
|
||||
// Create the next ledger to apply transaction to
|
||||
ledger = std::make_shared<Ledger>(*ledger, env.app().timeKeeper().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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,7 +466,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
{
|
||||
testcase("Pseudo Transaction Properties");
|
||||
|
||||
jtx::Env env(*this, jtx::testable_amendments());
|
||||
jtx::Env env(*this, jtx::testable_amendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), std::vector<uint256>{}, env.app().getNodeFamily());
|
||||
|
||||
@@ -349,7 +492,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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,7 +501,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
{
|
||||
testcase("Multiple Fee Updates");
|
||||
|
||||
jtx::Env env(*this, jtx::testable_amendments() | featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testable_amendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), std::vector<uint256>{}, env.app().getNodeFamily());
|
||||
|
||||
@@ -372,7 +515,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);
|
||||
}
|
||||
|
||||
@@ -389,7 +532,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);
|
||||
}
|
||||
|
||||
@@ -402,7 +545,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
{
|
||||
testcase("Wrong Ledger Sequence");
|
||||
|
||||
jtx::Env env(*this, jtx::testable_amendments() | featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testable_amendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), std::vector<uint256>{}, env.app().getNodeFamily());
|
||||
|
||||
@@ -421,7 +564,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
|
||||
@@ -429,7 +572,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
{
|
||||
testcase("Partial Field Updates");
|
||||
|
||||
jtx::Env env(*this, jtx::testable_amendments() | featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testable_amendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), std::vector<uint256>{}, env.app().getNodeFamily());
|
||||
|
||||
@@ -443,7 +586,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);
|
||||
}
|
||||
|
||||
@@ -457,7 +600,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);
|
||||
}
|
||||
|
||||
@@ -470,7 +613,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
{
|
||||
testcase("Single Invalid Transaction");
|
||||
|
||||
jtx::Env env(*this, jtx::testable_amendments() | featureXRPFees);
|
||||
jtx::Env env(*this, jtx::testable_amendments() - featureSmartEscrow);
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), std::vector<uint256>{}, env.app().getNodeFamily());
|
||||
|
||||
@@ -488,7 +631,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
|
||||
@@ -505,7 +648,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
|
||||
// Test with XRPFees enabled
|
||||
{
|
||||
Env env(*this, testable_amendments() | featureXRPFees);
|
||||
Env env(*this, testable_amendments() - featureSmartEscrow);
|
||||
auto feeVote = make_FeeVote(setup, env.app().journal("FeeVote"));
|
||||
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
@@ -531,7 +674,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
|
||||
// Test with XRPFees disabled (legacy format)
|
||||
{
|
||||
Env env(*this, testable_amendments() - featureXRPFees);
|
||||
Env env(*this, testable_amendments() - featureXRPFees - featureSmartEscrow);
|
||||
auto feeVote = make_FeeVote(setup, env.app().journal("FeeVote"));
|
||||
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
@@ -567,7 +710,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
setup.account_reserve = 1234567;
|
||||
setup.owner_reserve = 7654321;
|
||||
|
||||
Env env(*this, testable_amendments() | featureXRPFees);
|
||||
Env env(*this, testable_amendments() - featureSmartEscrow);
|
||||
|
||||
// establish what the current fees are
|
||||
BEAST_EXPECT(env.current()->fees().base == XRPAmount{UNIT_TEST_REFERENCE_FEE});
|
||||
@@ -637,6 +780,127 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(feeTx.getFieldAmount(sfReserveIncrementDrops) == XRPAmount{setup.owner_reserve});
|
||||
}
|
||||
|
||||
void
|
||||
testDoVotingSmartEscrow()
|
||||
{
|
||||
testcase("doVoting with Smart Escrow");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this, testable_amendments() | 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().extensionComputeLimit == 0);
|
||||
BEAST_EXPECT(env.current()->fees().extensionSizeLimit == 0);
|
||||
BEAST_EXPECT(env.current()->fees().gasPrice == 0);
|
||||
|
||||
auto const createFeeTxFromVoting = [&](FeeSetup const& setup) -> std::pair<STTx, std::shared_ptr<Ledger>> {
|
||||
auto feeVote = make_FeeVote(setup, env.app().journal("FeeVote"));
|
||||
auto ledger = std::make_shared<Ledger>(
|
||||
create_genesis, env.app().config(), 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().timeKeeper().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().timeKeeper().now(), pub, sec, calcNodeID(pub), [&](STValidation& v) {
|
||||
v.setFieldU32(sfLedgerSequence, ledger->seq());
|
||||
// Vote for different fees than current
|
||||
v.setFieldAmount(sfBaseFeeDrops, XRPAmount{setup.reference_fee});
|
||||
v.setFieldAmount(sfReserveBaseDrops, XRPAmount{setup.account_reserve});
|
||||
v.setFieldAmount(sfReserveIncrementDrops, XRPAmount{setup.owner_reserve});
|
||||
v.setFieldU32(sfExtensionComputeLimit, setup.extension_compute_limit);
|
||||
v.setFieldU32(sfExtensionSizeLimit, setup.extension_size_limit);
|
||||
v.setFieldU32(sfGasPrice, setup.gas_price);
|
||||
});
|
||||
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.reference_fee}, line);
|
||||
BEAST_EXPECTS(feeTx.getFieldAmount(sfReserveBaseDrops) == XRPAmount{setup.account_reserve}, line);
|
||||
BEAST_EXPECTS(feeTx.getFieldAmount(sfReserveIncrementDrops) == XRPAmount{setup.owner_reserve}, line);
|
||||
BEAST_EXPECTS(feeTx.getFieldU32(sfExtensionComputeLimit) == setup.extension_compute_limit, line);
|
||||
BEAST_EXPECTS(feeTx.getFieldU32(sfExtensionSizeLimit) == setup.extension_size_limit, line);
|
||||
BEAST_EXPECTS(feeTx.getFieldU32(sfGasPrice) == setup.gas_price, line);
|
||||
};
|
||||
|
||||
{
|
||||
FeeSetup setup;
|
||||
setup.reference_fee = 42;
|
||||
setup.account_reserve = 1234567;
|
||||
setup.owner_reserve = 7654321;
|
||||
setup.extension_compute_limit = 100;
|
||||
setup.extension_size_limit = 200;
|
||||
setup.gas_price = 300;
|
||||
auto const [feeTx, ledger] = createFeeTxFromVoting(setup);
|
||||
|
||||
checkFeeTx(setup, feeTx, ledger);
|
||||
}
|
||||
|
||||
{
|
||||
FeeSetup setup;
|
||||
setup.reference_fee = 42;
|
||||
setup.account_reserve = 1234567;
|
||||
setup.owner_reserve = 7654321;
|
||||
setup.extension_compute_limit = 0;
|
||||
setup.extension_size_limit = 0;
|
||||
setup.gas_price = 300;
|
||||
auto const [feeTx, ledger] = createFeeTxFromVoting(setup);
|
||||
|
||||
checkFeeTx(setup, feeTx, ledger);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
@@ -650,6 +914,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
testSingleInvalidTransaction();
|
||||
testDoValidation();
|
||||
testDoVoting();
|
||||
testDoVotingSmartEscrow();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -33,6 +33,12 @@ struct PseudoTx_test : public beast::unit_test::suite
|
||||
obj[sfReserveIncrement] = 0;
|
||||
obj[sfReferenceFeeUnits] = 0;
|
||||
}
|
||||
if (rules.enabled(featureSmartEscrow))
|
||||
{
|
||||
obj[sfExtensionComputeLimit] = 0;
|
||||
obj[sfExtensionSizeLimit] = 0;
|
||||
obj[sfGasPrice] = 0;
|
||||
}
|
||||
}));
|
||||
|
||||
res.emplace_back(STTx(ttAMENDMENT, [&](auto& obj) {
|
||||
@@ -97,7 +103,9 @@ struct PseudoTx_test : public beast::unit_test::suite
|
||||
FeatureBitset const all{testable_amendments()};
|
||||
FeatureBitset const xrpFees{featureXRPFees};
|
||||
|
||||
testPrevented(all - featureXRPFees - featureSmartEscrow);
|
||||
testPrevented(all - featureXRPFees);
|
||||
testPrevented(all - featureSmartEscrow);
|
||||
testPrevented(all);
|
||||
testAllowed();
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@ setupConfigForUnitTests(Config& cfg)
|
||||
cfg.FEES.reference_fee = UNIT_TEST_REFERENCE_FEE;
|
||||
cfg.FEES.account_reserve = XRP(200).value().xrp().drops();
|
||||
cfg.FEES.owner_reserve = XRP(50).value().xrp().drops();
|
||||
cfg.FEES.extension_compute_limit = 1'000'000;
|
||||
cfg.FEES.extension_size_limit = 1'000'000;
|
||||
cfg.FEES.gas_price = 1'000;
|
||||
|
||||
// The Beta API (currently v2) is always available to tests
|
||||
cfg.BETA_RPC_API = true;
|
||||
|
||||
@@ -467,6 +467,22 @@ public:
|
||||
if (jv.isMember(jss::reserve_inc) != isFlagLedger)
|
||||
return false;
|
||||
|
||||
if (env.closed()->rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
if (jv.isMember(jss::extension_compute) != isFlagLedger)
|
||||
return false;
|
||||
|
||||
if (jv.isMember(jss::extension_size) != isFlagLedger)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (jv.isMember(jss::extension_compute))
|
||||
return false;
|
||||
|
||||
if (jv.isMember(jss::extension_size))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -1444,7 +1460,8 @@ public:
|
||||
testTransactions_APIv1();
|
||||
testTransactions_APIv2();
|
||||
testManifests();
|
||||
testValidations(all - xrpFees);
|
||||
testValidations(all - featureXRPFees - featureSmartEscrow);
|
||||
testValidations(all - featureSmartEscrow);
|
||||
testValidations(all);
|
||||
testSubErrors(true);
|
||||
testSubErrors(false);
|
||||
|
||||
@@ -191,6 +191,12 @@ Ledger::Ledger(create_genesis_t, Config const& config, std::vector<uint256> cons
|
||||
sle->at(sfReserveIncrement) = *f;
|
||||
sle->at(sfReferenceFeeUnits) = Config::FEE_UNITS_DEPRECATED;
|
||||
}
|
||||
if (std::find(amendments.begin(), amendments.end(), featureSmartEscrow) != amendments.end())
|
||||
{
|
||||
sle->at(sfExtensionComputeLimit) = config.FEES.extension_compute_limit;
|
||||
sle->at(sfExtensionSizeLimit) = config.FEES.extension_size_limit;
|
||||
sle->at(sfGasPrice) = config.FEES.gas_price;
|
||||
}
|
||||
rawInsert(sle);
|
||||
}
|
||||
|
||||
@@ -561,6 +567,7 @@ Ledger::setup()
|
||||
{
|
||||
bool oldFees = false;
|
||||
bool newFees = false;
|
||||
bool extensionFees = false;
|
||||
{
|
||||
auto const baseFee = sle->at(~sfBaseFee);
|
||||
auto const reserveBase = sle->at(~sfReserveBase);
|
||||
@@ -577,6 +584,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)
|
||||
{
|
||||
@@ -591,12 +599,32 @@ Ledger::setup()
|
||||
assign(fees_.increment, reserveIncrementXRP);
|
||||
newFees = baseFeeXRP || reserveBaseXRP || reserveIncrementXRP;
|
||||
}
|
||||
{
|
||||
auto const extensionComputeLimit = sle->at(~sfExtensionComputeLimit);
|
||||
auto const extensionSizeLimit = sle->at(~sfExtensionSizeLimit);
|
||||
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_.extensionComputeLimit, extensionComputeLimit);
|
||||
assign(fees_.extensionSizeLimit, extensionSizeLimit);
|
||||
assign(fees_.gasPrice, gasPrice);
|
||||
extensionFees = extensionComputeLimit || extensionSizeLimit || gasPrice;
|
||||
}
|
||||
if (oldFees && newFees)
|
||||
// Should be all of one or the other, but not both
|
||||
ret = false;
|
||||
if (!rules_.enabled(featureXRPFees) && newFees)
|
||||
// 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&)
|
||||
@@ -615,13 +643,23 @@ Ledger::setup()
|
||||
void
|
||||
Ledger::defaultFees(Config const& config)
|
||||
{
|
||||
XRPL_ASSERT(fees_.base == 0 && fees_.reserve == 0 && fees_.increment == 0, "xrpl::Ledger::defaultFees : zero fees");
|
||||
XRPL_ASSERT(
|
||||
fees_.base == 0 && fees_.reserve == 0 && fees_.increment == 0 && fees_.extensionComputeLimit == 0 &&
|
||||
fees_.extensionSizeLimit == 0 && fees_.gasPrice == 0,
|
||||
"xrpl::Ledger::defaultFees : zero fees");
|
||||
if (fees_.base == 0)
|
||||
fees_.base = config.FEES.reference_fee;
|
||||
if (fees_.reserve == 0)
|
||||
fees_.reserve = config.FEES.account_reserve;
|
||||
if (fees_.increment == 0)
|
||||
fees_.increment = config.FEES.owner_reserve;
|
||||
|
||||
if (fees_.extensionComputeLimit == 0)
|
||||
fees_.extensionComputeLimit = config.FEES.extension_compute_limit;
|
||||
if (fees_.extensionSizeLimit == 0)
|
||||
fees_.extensionSizeLimit = config.FEES.extension_size_limit;
|
||||
if (fees_.gasPrice == 0)
|
||||
fees_.gasPrice = config.FEES.gas_price;
|
||||
}
|
||||
|
||||
std::shared_ptr<SLE>
|
||||
|
||||
@@ -9,10 +9,10 @@ namespace xrpl {
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename value_type>
|
||||
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_;
|
||||
@@ -46,8 +46,9 @@ public:
|
||||
getVotes() const;
|
||||
};
|
||||
|
||||
auto
|
||||
VotableValue::getVotes() const -> std::pair<value_type, bool>
|
||||
template <typename value_type>
|
||||
std::pair<value_type, bool>
|
||||
VotableValue<value_type>::getVotes() const
|
||||
{
|
||||
value_type ourVote = current_;
|
||||
int weight = 0;
|
||||
@@ -99,16 +100,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_.reference_fee, "base fee", sfBaseFeeDrops);
|
||||
vote(lastFees.reserve, target_.account_reserve, "base reserve", sfReserveBaseDrops);
|
||||
vote(lastFees.increment, target_.owner_reserve, "reserve increment", sfReserveIncrementDrops);
|
||||
@@ -117,12 +118,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;
|
||||
@@ -132,9 +133,19 @@ FeeVoteImpl::doValidation(Fees const& lastFees, Rules const& rules, STValidation
|
||||
}
|
||||
};
|
||||
|
||||
vote(lastFees.base, target_.reference_fee, to64, "base fee", sfBaseFee);
|
||||
vote(lastFees.reserve, target_.account_reserve, to32, "base reserve", sfReserveBase);
|
||||
vote(lastFees.increment, target_.owner_reserve, to32, "reserve increment", sfReserveIncrement);
|
||||
voteAndConvert(lastFees.base, target_.reference_fee, to64, "base fee", sfBaseFee);
|
||||
voteAndConvert(lastFees.reserve, target_.account_reserve, to32, "base reserve", sfReserveBase);
|
||||
voteAndConvert(lastFees.increment, target_.owner_reserve, to32, "reserve increment", sfReserveIncrement);
|
||||
}
|
||||
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);
|
||||
vote(lastFees.gasPrice, target_.gas_price, "gas price", sfGasPrice);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,24 +165,32 @@ FeeVoteImpl::doVoting(
|
||||
|
||||
detail::VotableValue incReserveVote(lastClosedLedger->fees().increment, target_.owner_reserve);
|
||||
|
||||
detail::VotableValue extensionComputeVote(
|
||||
lastClosedLedger->fees().extensionComputeLimit, target_.extension_compute_limit);
|
||||
|
||||
detail::VotableValue extensionSizeVote(lastClosedLedger->fees().extensionSizeLimit, target_.extension_size_limit);
|
||||
|
||||
detail::VotableValue gasPriceVote(lastClosedLedger->fees().gasPrice, target_.gas_price);
|
||||
|
||||
auto const& rules = lastClosedLedger->rules();
|
||||
if (rules.enabled(featureXRPFees))
|
||||
{
|
||||
auto doVote =
|
||||
[](std::shared_ptr<STValidation> const& val, detail::VotableValue& value, SF_AMOUNT const& xrpField) {
|
||||
if (auto const field = ~val->at(~xrpField); field && field->native())
|
||||
{
|
||||
auto const vote = field->xrp();
|
||||
if (isLegalAmountSigned(vote))
|
||||
value.addVote(vote);
|
||||
else
|
||||
value.noVote();
|
||||
}
|
||||
auto doVote = [](std::shared_ptr<STValidation> const& val,
|
||||
detail::VotableValue<XRPAmount>& value,
|
||||
SF_AMOUNT const& xrpField) {
|
||||
if (auto const field = ~val->at(~xrpField); field && field->native())
|
||||
{
|
||||
auto const vote = field->xrp();
|
||||
if (isLegalAmountSigned(vote))
|
||||
value.addVote(vote);
|
||||
else
|
||||
{
|
||||
value.noVote();
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
value.noVote();
|
||||
}
|
||||
};
|
||||
|
||||
for (auto const& val : set)
|
||||
{
|
||||
@@ -184,26 +203,27 @@ FeeVoteImpl::doVoting(
|
||||
}
|
||||
else
|
||||
{
|
||||
auto doVote =
|
||||
[](std::shared_ptr<STValidation> const& val, detail::VotableValue& value, auto const& valueField) {
|
||||
if (auto const field = val->at(~valueField))
|
||||
{
|
||||
using XRPType = XRPAmount::value_type;
|
||||
auto const vote = *field;
|
||||
if (vote <= std::numeric_limits<XRPType>::max() &&
|
||||
isLegalAmountSigned(XRPAmount{unsafe_cast<XRPType>(vote)}))
|
||||
value.addVote(XRPAmount{unsafe_cast<XRPType>(vote)});
|
||||
else
|
||||
// Invalid amounts will be treated as if they're
|
||||
// not provided. Don't throw because this value is
|
||||
// provided by an external entity.
|
||||
value.noVote();
|
||||
}
|
||||
auto doVote = [](std::shared_ptr<STValidation> const& val,
|
||||
detail::VotableValue<XRPAmount>& value,
|
||||
auto const& valueField) {
|
||||
if (auto const field = val->at(~valueField))
|
||||
{
|
||||
using XRPType = XRPAmount::value_type;
|
||||
auto const vote = *field;
|
||||
if (vote <= std::numeric_limits<XRPType>::max() &&
|
||||
isLegalAmountSigned(XRPAmount{unsafe_cast<XRPType>(vote)}))
|
||||
value.addVote(XRPAmount{unsafe_cast<XRPType>(vote)});
|
||||
else
|
||||
{
|
||||
// Invalid amounts will be treated as if they're
|
||||
// not provided. Don't throw because this value is
|
||||
// provided by an external entity.
|
||||
value.noVote();
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
value.noVote();
|
||||
}
|
||||
};
|
||||
|
||||
for (auto const& val : set)
|
||||
{
|
||||
@@ -214,6 +234,30 @@ 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& extensionField) {
|
||||
if (auto const field = ~val->at(~extensionField); field)
|
||||
{
|
||||
value.addVote(field.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
value.noVote();
|
||||
}
|
||||
};
|
||||
|
||||
for (auto const& val : set)
|
||||
{
|
||||
if (!val->isTrusted())
|
||||
continue;
|
||||
doVote(val, extensionComputeVote, sfExtensionComputeLimit);
|
||||
doVote(val, extensionSizeVote, sfExtensionSizeLimit);
|
||||
doVote(val, gasPriceVote, sfGasPrice);
|
||||
}
|
||||
}
|
||||
|
||||
// choose our positions
|
||||
// TODO: Use structured binding once LLVM 16 is the minimum supported
|
||||
@@ -222,11 +266,15 @@ FeeVoteImpl::doVoting(
|
||||
auto const baseFee = baseFeeVote.getVotes();
|
||||
auto const baseReserve = baseReserveVote.getVotes();
|
||||
auto const incReserve = incReserveVote.getVotes();
|
||||
auto const extensionCompute = extensionComputeVote.getVotes();
|
||||
auto const extensionSize = extensionSizeVote.getVotes();
|
||||
auto const gasPrice = gasPriceVote.getVotes();
|
||||
|
||||
auto const seq = lastClosedLedger->header().seq + 1;
|
||||
|
||||
// add transactions to our position
|
||||
if (baseFee.second || baseReserve.second || incReserve.second)
|
||||
if (baseFee.second || baseReserve.second || incReserve.second || extensionCompute.second || extensionSize.second ||
|
||||
gasPrice.second)
|
||||
{
|
||||
JLOG(journal_.warn()) << "We are voting for a fee change: " << baseFee.first << "/" << baseReserve.first << "/"
|
||||
<< incReserve.first;
|
||||
@@ -249,6 +297,12 @@ FeeVoteImpl::doVoting(
|
||||
obj[sfReserveIncrement] = incReserve.first.dropsAs<std::uint32_t>(incReserveVote.current());
|
||||
obj[sfReferenceFeeUnits] = Config::FEE_UNITS_DEPRECATED;
|
||||
}
|
||||
if (rules.enabled(featureSmartEscrow))
|
||||
{
|
||||
obj[sfExtensionComputeLimit] = extensionCompute.first;
|
||||
obj[sfExtensionSizeLimit] = extensionSize.first;
|
||||
obj[sfGasPrice] = gasPrice.first;
|
||||
}
|
||||
});
|
||||
|
||||
uint256 txID = feeTx.getTransactionID();
|
||||
|
||||
@@ -2243,6 +2243,15 @@ NetworkOPsImp::pubValidation(std::shared_ptr<STValidation> const& val)
|
||||
if (auto const reserveIncXRP = ~val->at(~sfReserveIncrementDrops); reserveIncXRP && reserveIncXRP->native())
|
||||
jvObj[jss::reserve_inc] = reserveIncXRP->xrp().jsonClipped();
|
||||
|
||||
if (auto const extensionComputeLimit = ~val->at(~sfExtensionComputeLimit); extensionComputeLimit)
|
||||
jvObj[jss::extension_compute] = *extensionComputeLimit;
|
||||
|
||||
if (auto const extensionSizeLimit = ~val->at(~sfExtensionSizeLimit); extensionSizeLimit)
|
||||
jvObj[jss::extension_size] = *extensionSizeLimit;
|
||||
|
||||
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};
|
||||
@@ -2656,11 +2665,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::extension_compute] = lpClosed->fees().extensionComputeLimit;
|
||||
l[jss::extension_size] = lpClosed->fees().extensionSizeLimit;
|
||||
l[jss::gas_price] = lpClosed->fees().gasPrice;
|
||||
}
|
||||
l[jss::close_time] = Json::Value::UInt(lpClosed->header().closeTime.time_since_epoch().count());
|
||||
}
|
||||
else
|
||||
@@ -2668,6 +2684,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::extension_compute] = lpClosed->fees().extensionComputeLimit;
|
||||
l[jss::extension_size] = lpClosed->fees().extensionSizeLimit;
|
||||
l[jss::gas_price] = lpClosed->fees().gasPrice;
|
||||
}
|
||||
|
||||
if (auto const closeOffset = app_.timeKeeper().closeOffset(); std::abs(closeOffset.count()) >= 60)
|
||||
l[jss::close_time_offset] = static_cast<std::uint32_t>(closeOffset.count());
|
||||
@@ -2844,6 +2866,12 @@ NetworkOPsImp::pubLedger(std::shared_ptr<ReadView const> const& lpAccepted)
|
||||
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::extension_compute] = lpAccepted->fees().extensionComputeLimit;
|
||||
jvObj[jss::extension_size] = lpAccepted->fees().extensionSizeLimit;
|
||||
jvObj[jss::gas_price] = lpAccepted->fees().gasPrice;
|
||||
}
|
||||
|
||||
jvObj[jss::txn_count] = Json::UInt(alpAccepted->size());
|
||||
|
||||
@@ -3186,8 +3214,7 @@ NetworkOPsImp::pubAccountTransaction(
|
||||
}
|
||||
}
|
||||
|
||||
JLOG(m_journal.trace()) << "pubAccountTransaction: "
|
||||
<< "proposed=" << iProposed << ", accepted=" << iAccepted;
|
||||
JLOG(m_journal.trace()) << "pubAccountTransaction: " << "proposed=" << iProposed << ", accepted=" << iAccepted;
|
||||
|
||||
if (!notify.empty() || !accountHistoryNotify.empty())
|
||||
{
|
||||
@@ -3810,6 +3837,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] = app_.config().NETWORK_ID;
|
||||
if (lpClosed->rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
jvResult[jss::extension_compute] = lpClosed->fees().extensionComputeLimit;
|
||||
jvResult[jss::extension_size] = lpClosed->fees().extensionSizeLimit;
|
||||
jvResult[jss::gas_price] = lpClosed->fees().gasPrice;
|
||||
}
|
||||
}
|
||||
|
||||
if ((mMode >= OperatingMode::SYNCING) && !isNeedNetworkLedger())
|
||||
|
||||
@@ -100,6 +100,18 @@ Change::preclaim(PreclaimContext const& ctx)
|
||||
ctx.tx.isFieldPresent(sfReserveIncrementDrops))
|
||||
return temDISABLED;
|
||||
}
|
||||
if (ctx.view.rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
if (!ctx.tx.isFieldPresent(sfExtensionComputeLimit) || !ctx.tx.isFieldPresent(sfExtensionSizeLimit) ||
|
||||
!ctx.tx.isFieldPresent(sfGasPrice))
|
||||
return temMALFORMED;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ctx.tx.isFieldPresent(sfExtensionComputeLimit) || ctx.tx.isFieldPresent(sfExtensionSizeLimit) ||
|
||||
ctx.tx.isFieldPresent(sfGasPrice))
|
||||
return temDISABLED;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
case ttAMENDMENT:
|
||||
case ttUNL_MODIFY:
|
||||
@@ -256,6 +268,12 @@ Change::applyFee()
|
||||
set(feeObject, ctx_.tx, sfReserveBase);
|
||||
set(feeObject, ctx_.tx, sfReserveIncrement);
|
||||
}
|
||||
if (view().rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
set(feeObject, ctx_.tx, sfExtensionComputeLimit);
|
||||
set(feeObject, ctx_.tx, sfExtensionSizeLimit);
|
||||
set(feeObject, ctx_.tx, sfGasPrice);
|
||||
}
|
||||
|
||||
view().update(feeObject);
|
||||
|
||||
|
||||
@@ -53,6 +53,15 @@ struct FeeSetup
|
||||
/** The per-owned item reserve requirement in drops. */
|
||||
XRPAmount owner_reserve{2 * DROPS_PER_XRP};
|
||||
|
||||
/** The compute limit for Feature Extensions. */
|
||||
std::uint32_t extension_compute_limit{1'000'000};
|
||||
|
||||
/** The WASM size limit for Feature Extensions. */
|
||||
std::uint32_t extension_size_limit{100'000};
|
||||
|
||||
/** The price of 1 WASM gas, in micro-drops. */
|
||||
std::uint32_t gas_price{1'000'000};
|
||||
|
||||
/* (Remember to update the example cfg files when changing any of these
|
||||
* values.) */
|
||||
};
|
||||
|
||||
@@ -1035,6 +1035,12 @@ setup_FeeVote(Section const& section)
|
||||
setup.account_reserve = temp;
|
||||
if (set(temp, "owner_reserve", section))
|
||||
setup.owner_reserve = temp;
|
||||
if (set(temp, "extension_compute_limit", section))
|
||||
setup.extension_compute_limit = temp;
|
||||
if (set(temp, "extension_size_limit", section))
|
||||
setup.extension_size_limit = temp;
|
||||
if (set(temp, "gas_price", section))
|
||||
setup.gas_price = temp;
|
||||
}
|
||||
return setup;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user