Compare commits

...

23 Commits

Author SHA1 Message Date
Mayukha Vadari
e40a4df777 Merge branch 'ripple/wasmi-host-functions' into ripple/se/fees 2025-11-25 03:49:06 +05:30
Mayukha Vadari
95d78a8600 Merge branch 'wasmi-host-functions' into ripple/se/fees 2025-11-25 03:26:19 +05:30
Mayukha Vadari
578413859c Merge branch 'ripple/wamr-host-functions' into ripple/se/fees 2025-11-04 17:52:00 -05:00
Mayukha Vadari
3195eb16b2 Merge branch 'wamr-host-functions' into ripple/se/fees 2025-11-04 14:50:31 -05:00
Mayukha Vadari
eed280d169 Merge branch 'wamr-host-functions' into ripple/se/fees 2025-11-04 13:37:09 -05:00
Mayukha Vadari
aebec5378c Merge branch 'wamr-host-functions' into ripple/se/fees 2025-10-24 18:01:17 -04:00
Mayukha Vadari
67e2c1b563 Merge branch 'wamr-host-functions' into ripple/se/fees 2025-10-24 16:06:02 -04:00
Mayukha Vadari
209ee25c32 Merge branch 'ripple/wamr-host-functions' into ripple/se/fees 2025-10-24 16:02:16 -04:00
Mayukha Vadari
af6beb1d7c fix ledger used for rules 2025-10-23 15:41:32 -04:00
Mayukha Vadari
ce19c13059 Merge branch 'ripple/wamr-host-functions' into ripple/se/fees 2025-10-23 15:38:37 -04:00
Mayukha Vadari
43caa1ef29 Merge branch 'ripple/wamr-host-functions' into ripple/se/fees 2025-10-20 11:53:56 -04:00
Mayukha Vadari
fe601308e7 Merge branch 'ripple/wamr-host-functions' into ripple/se/fees 2025-10-13 13:58:09 -04:00
Mayukha Vadari
51f1be7f5b Merge branch 'ripple/wamr-host-functions' into ripple/se/fees 2025-10-09 17:10:52 -04:00
Mayukha Vadari
f57b855d74 Merge branch 'ripple/wamr-host-functions' into ripple/se/fees 2025-10-06 17:01:17 -04:00
Mayukha Vadari
86525d8583 test: add tests for fee voting (#5747) 2025-10-06 16:32:04 -04:00
Mayukha Vadari
51ee06429b Merge branch 'ripple/wamr-host-functions' into ripple/se/fees 2025-10-02 14:35:36 -04:00
Mayukha Vadari
ca85d09f02 Merge branch 'ripple/wamr-host-functions' into ripple/se/fees 2025-09-30 14:43:24 -04:00
Mayukha Vadari
e59f5f3b01 fix tests 2025-09-26 17:09:53 -04:00
Mayukha Vadari
c8b06e7de1 Merge branch 'ripple/wamr-host-functions' into ripple/se/fees 2025-09-26 16:50:34 -04:00
Mayukha Vadari
c3fd52c177 Merge branch 'ripple/wamr-host-functions' into ripple/se/fees 2025-09-26 15:51:56 -04:00
Mayukha Vadari
737fab5471 fix issues 2025-09-22 23:55:57 -04:00
Mayukha Vadari
5a6c4e8ae0 Merge branch 'ripple/wamr-host-functions' into ripple/se/fees 2025-09-22 18:23:54 -04:00
Mayukha Vadari
7420f47658 SmartEscrow fee voting changes 2025-09-22 18:22:44 -04:00
18 changed files with 648 additions and 52 deletions

View File

@@ -1290,6 +1290,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, rippled 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, rippled 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, rippled will use an internal
# default. Don't change this without understanding the consequences.
#
# Example:
# gas_price = 1000000 # 1 drop per gas
#-------------------------------------------------------------------------------
#
# 9. Misc Settings

View File

@@ -5,6 +5,8 @@
namespace ripple {
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
@@ -15,6 +17,10 @@ struct Fees
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;

View File

@@ -14,6 +14,7 @@
// Add new amendments to the top of this list.
// Keep it sorted in reverse chronological order.
XRPL_FEATURE(SmartEscrow, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(PermissionDelegationV1_1, Supported::no, VoteBehavior::DefaultNo)
XRPL_FIX (DirectoryLimit, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (IncludeKeyletFields, Supported::yes, VoteBehavior::DefaultNo)
@@ -29,7 +30,6 @@ XRPL_FEATURE(PermissionedDEX, Supported::yes, VoteBehavior::DefaultNo
XRPL_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(SingleAssetVault, Supported::no, 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)

View File

@@ -301,6 +301,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},
}))

View File

@@ -96,6 +96,9 @@ TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50)
TYPED_SFIELD(sfOracleDocumentID, UINT32, 51)
TYPED_SFIELD(sfPermissionValue, UINT32, 52)
TYPED_SFIELD(sfMutableFlags, UINT32, 53)
TYPED_SFIELD(sfExtensionComputeLimit, UINT32, 54)
TYPED_SFIELD(sfExtensionSizeLimit, UINT32, 55)
TYPED_SFIELD(sfGasPrice, UINT32, 56)
// 64-bit integers (common)
TYPED_SFIELD(sfIndexNext, UINT64, 1)

View File

@@ -959,6 +959,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

View File

@@ -253,6 +253,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
@@ -704,11 +707,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>

View File

@@ -58,6 +58,10 @@ STValidation::validationFormat()
{sfBaseFeeDrops, soeOPTIONAL},
{sfReserveBaseDrops, soeOPTIONAL},
{sfReserveIncrementDrops, soeOPTIONAL},
// featureSmartEscrow
{sfExtensionComputeLimit, soeOPTIONAL},
{sfExtensionSizeLimit, soeOPTIONAL},
{sfGasPrice, soeOPTIONAL},
};
// clang-format on

View File

@@ -24,13 +24,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> 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)
FeeSettingsFields const& fields,
bool forceAllFields = false)
{
auto fill = [&](auto& obj) {
obj.setAccountID(sfAccount, AccountID());
@@ -64,6 +68,17 @@ createFeeTx(
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);
}
@@ -112,6 +127,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)
@@ -119,12 +140,12 @@ 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
@@ -180,6 +201,25 @@ verifyFeeObject(
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;
}
@@ -202,6 +242,7 @@ class FeeVote_test : public beast::unit_test::suite
void
testSetup()
{
testcase("FeeVote setup");
FeeSetup const defaultSetup;
{
// defaults
@@ -210,36 +251,62 @@ 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"});
"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"});
"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"});
"owner_reserve = -1234",
"extension_compute_limit = -100",
"extension_size_limit = -200",
"gas_price = -300"});
// Illegal values are ignored, and the defaults left unchanged
auto setup = setup_FeeVote(config);
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
@@ -247,6 +314,12 @@ class FeeVote_test : public beast::unit_test::suite
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(
@@ -257,12 +330,22 @@ class FeeVote_test : public beast::unit_test::suite
config.append(
{"reference_fee = " + big64,
"account_reserve = " + big64,
"owner_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);
}
}
@@ -273,7 +356,10 @@ 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(),
@@ -294,7 +380,8 @@ 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
@@ -303,7 +390,8 @@ 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(),
@@ -322,12 +410,76 @@ 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
@@ -336,7 +488,10 @@ 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(),
@@ -351,16 +506,19 @@ class FeeVote_test : public beast::unit_test::suite
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(),
@@ -375,12 +533,43 @@ class FeeVote_test : public beast::unit_test::suite
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)));
}
}
@@ -389,7 +578,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(),
@@ -419,7 +608,8 @@ 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)));
}
}
@@ -428,7 +618,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(),
@@ -446,7 +636,8 @@ 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);
}
@@ -464,7 +655,8 @@ 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);
}
@@ -477,7 +669,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(),
@@ -500,7 +692,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
@@ -508,7 +700,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(),
@@ -526,7 +718,8 @@ 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);
}
@@ -543,7 +736,8 @@ 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);
}
@@ -556,7 +750,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(),
@@ -579,7 +773,8 @@ 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
@@ -596,7 +791,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>(
@@ -631,7 +826,9 @@ 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>(
@@ -674,7 +871,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(
@@ -764,6 +961,128 @@ class FeeVote_test : public beast::unit_test::suite
XRPAmount{setup.owner_reserve});
}
void
testDoVotingSmartEscrow()
{
testcase("doVoting with Smart Escrow");
using namespace jtx;
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;
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 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);
auto const& feeTx = txs[0];
BEAST_EXPECT(feeTx.getTxnType() == ttFEE);
BEAST_EXPECT(feeTx.getAccountID(sfAccount) == AccountID());
BEAST_EXPECT(feeTx.getFieldU32(sfLedgerSequence) == ledger->seq() + 1);
BEAST_EXPECT(feeTx.isFieldPresent(sfBaseFeeDrops));
BEAST_EXPECT(feeTx.isFieldPresent(sfReserveBaseDrops));
BEAST_EXPECT(feeTx.isFieldPresent(sfReserveIncrementDrops));
// The legacy fields should NOT be present
BEAST_EXPECT(!feeTx.isFieldPresent(sfBaseFee));
BEAST_EXPECT(!feeTx.isFieldPresent(sfReserveBase));
BEAST_EXPECT(!feeTx.isFieldPresent(sfReserveIncrement));
BEAST_EXPECT(!feeTx.isFieldPresent(sfReferenceFeeUnits));
// Check the values
BEAST_EXPECT(
feeTx.getFieldAmount(sfBaseFeeDrops) ==
XRPAmount{setup.reference_fee});
BEAST_EXPECT(
feeTx.getFieldAmount(sfReserveBaseDrops) ==
XRPAmount{setup.account_reserve});
BEAST_EXPECT(
feeTx.getFieldAmount(sfReserveIncrementDrops) ==
XRPAmount{setup.owner_reserve});
BEAST_EXPECT(
feeTx.getFieldU32(sfExtensionComputeLimit) ==
setup.extension_compute_limit);
BEAST_EXPECT(
feeTx.getFieldU32(sfExtensionSizeLimit) ==
setup.extension_size_limit);
BEAST_EXPECT(feeTx.getFieldU32(sfGasPrice) == setup.gas_price);
}
void
run() override
{
@@ -777,6 +1096,7 @@ class FeeVote_test : public beast::unit_test::suite
testSingleInvalidTransaction();
testDoValidation();
testDoVoting();
testDoVotingSmartEscrow();
}
};

View File

@@ -38,14 +38,14 @@ public:
auto const jtx = env.jt(ticket::create(alice, 1), seq(1), fee(10));
auto transacionId = jtx.stx->getTransactionID();
auto transactionId = jtx.stx->getTransactionID();
env.app().getHashRouter().setFlags(
transacionId, HashRouterFlags::HELD);
transactionId, HashRouterFlags::HELD);
env(jtx, json(jss::Sequence, 1), ter(terNO_ACCOUNT));
env.app().getHashRouter().setFlags(
transacionId, HashRouterFlags::BAD);
transactionId, HashRouterFlags::BAD);
env.close();
}

View File

@@ -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) {
@@ -101,7 +107,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();
}

View File

@@ -515,6 +515,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;
};
@@ -1579,7 +1595,8 @@ public:
testTransactions_APIv1();
testTransactions_APIv2();
testManifests();
testValidations(all - xrpFees);
testValidations(all - featureXRPFees - featureSmartEscrow);
testValidations(all - featureSmartEscrow);
testValidations(all);
testSubErrors(true);
testSubErrors(false);

View File

@@ -202,6 +202,15 @@ Ledger::Ledger(
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);
}
@@ -595,6 +604,7 @@ Ledger::setup()
{
bool oldFees = false;
bool newFees = false;
bool extensionFees = false;
{
auto const baseFee = sle->at(~sfBaseFee);
auto const reserveBase = sle->at(~sfReserveBase);
@@ -612,6 +622,7 @@ Ledger::setup()
auto const reserveBaseXRP = sle->at(~sfReserveBaseDrops);
auto const reserveIncrementXRP =
sle->at(~sfReserveIncrementDrops);
auto assign = [&ret](
XRPAmount& dest,
std::optional<STAmount> const& src) {
@@ -628,12 +639,35 @@ 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&)
@@ -652,15 +686,23 @@ Ledger::setup()
void
Ledger::defaultFees(Config const& config)
{
XRPL_ASSERT(
fees_.base == 0 && fees_.reserve == 0 && fees_.increment == 0,
"ripple::Ledger::defaultFees : zero fees");
assert(
fees_.base == 0 && fees_.reserve == 0 && fees_.increment == 0 &&
fees_.extensionComputeLimit == 0 && fees_.extensionSizeLimit == 0 &&
fees_.gasPrice == 0);
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>

View File

@@ -9,10 +9,10 @@ namespace ripple {
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_;
@@ -47,8 +47,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;
@@ -171,6 +172,33 @@ FeeVoteImpl::doValidation(
"reserve increment",
sfReserveIncrement);
}
if (rules.enabled(featureSmartEscrow))
{
auto vote = [&v, this](
auto const current,
std::uint32_t target,
char const* name,
auto const& sfield) {
if (current != target)
{
JLOG(journal_.info())
<< "Voting for " << name << " of " << target;
v[sfield] = target;
}
};
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);
}
}
void
@@ -193,11 +221,22 @@ 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,
detail::VotableValue<XRPAmount>& value,
SF_AMOUNT const& xrpField) {
if (auto const field = ~val->at(~xrpField);
field && field->native())
@@ -226,7 +265,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))
{
@@ -257,6 +296,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
@@ -265,11 +328,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->info().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 << "/"
@@ -297,6 +364,12 @@ FeeVoteImpl::doVoting(
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();

View File

@@ -2464,6 +2464,18 @@ NetworkOPsImp::pubValidation(std::shared_ptr<STValidation> const& val)
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};
@@ -2913,11 +2925,20 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters)
l[jss::seq] = Json::UInt(lpClosed->info().seq);
l[jss::hash] = to_string(lpClosed->info().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->info().closeTime.time_since_epoch().count());
}
@@ -2926,6 +2947,13 @@ 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)
@@ -3117,6 +3145,14 @@ NetworkOPsImp::pubLedger(std::shared_ptr<ReadView const> const& lpAccepted)
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());
@@ -3487,8 +3523,8 @@ NetworkOPsImp::pubAccountTransaction(
}
JLOG(m_journal.trace())
<< "pubAccountTransaction: "
<< "proposed=" << iProposed << ", accepted=" << iAccepted;
<< "pubAccountTransaction: " << "proposed=" << iProposed
<< ", accepted=" << iAccepted;
if (!notify.empty() || !accountHistoryNotify.empty())
{
@@ -4189,12 +4225,19 @@ NetworkOPsImp::subLedger(InfoSub::ref isrListener, Json::Value& jvResult)
jvResult[jss::ledger_hash] = to_string(lpClosed->info().hash);
jvResult[jss::ledger_time] = Json::Value::UInt(
lpClosed->info().closeTime.time_since_epoch().count());
jvResult[jss::network_id] = app_.config().NETWORK_ID;
if (!lpClosed->rules().enabled(featureXRPFees))
jvResult[jss::fee_ref] = Config::FEE_UNITS_DEPRECATED;
jvResult[jss::fee_base] = lpClosed->fees().base.jsonClipped();
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())

View File

@@ -105,6 +105,20 @@ 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:
@@ -269,6 +283,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);

View File

@@ -54,6 +54,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.) */
};

View File

@@ -1122,6 +1122,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;
}