diff --git a/cfg/xrpld-example.cfg b/cfg/xrpld-example.cfg index 995d4e65ffc..8db260c08b7 100644 --- a/cfg/xrpld-example.cfg +++ b/cfg/xrpld-example.cfg @@ -1272,6 +1272,39 @@ # Example: # owner_reserve = 2000000 # 2 XRP # +# extension_compute_limit = +# +# 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 = +# +# 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 = +# +# 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 diff --git a/include/xrpl/protocol/Fees.h b/include/xrpl/protocol/Fees.h index 84c8ffe81fc..8157fdf31ea 100644 --- a/include/xrpl/protocol/Fees.h +++ b/include/xrpl/protocol/Fees.h @@ -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; diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index a40d524c709..33e36b26126 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -16,7 +16,8 @@ // 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::no, VoteBehavior::DefaultNo) XRPL_FEATURE(LendingProtocol, Supported::yes, VoteBehavior::DefaultNo) @@ -33,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::no, 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) diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index 7955dcb66c7..d30fab0cc64 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -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}, })) diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro index 712cf568af5..d51c6ddee84 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -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) diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro index b696a1d1c2c..e0b3e67878d 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -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 diff --git a/include/xrpl/protocol/jss.h b/include/xrpl/protocol/jss.h index 0264f625eb2..05942353dd4 100644 --- a/include/xrpl/protocol/jss.h +++ b/include/xrpl/protocol/jss.h @@ -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 diff --git a/src/libxrpl/protocol/STValidation.cpp b/src/libxrpl/protocol/STValidation.cpp index f6f89d43e9a..7362a45d847 100644 --- a/src/libxrpl/protocol/STValidation.cpp +++ b/src/libxrpl/protocol/STValidation.cpp @@ -58,6 +58,10 @@ STValidation::validationFormat() {sfBaseFeeDrops, soeOPTIONAL}, {sfReserveBaseDrops, soeOPTIONAL}, {sfReserveIncrementDrops, soeOPTIONAL}, + // featureSmartEscrow + {sfExtensionComputeLimit, soeOPTIONAL}, + {sfExtensionSizeLimit, soeOPTIONAL}, + {sfGasPrice, soeOPTIONAL}, }; // clang-format on diff --git a/src/libxrpl/tx/transactors/Change.cpp b/src/libxrpl/tx/transactors/Change.cpp index 315aeb89bdf..cc5c17ecfe7 100644 --- a/src/libxrpl/tx/transactors/Change.cpp +++ b/src/libxrpl/tx/transactors/Change.cpp @@ -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: @@ -264,6 +278,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); diff --git a/src/test/app/FeeVote_test.cpp b/src/test/app/FeeVote_test.cpp index 360def1abfa..48f94943d8c 100644 --- a/src/test/app/FeeVote_test.cpp +++ b/src/test/app/FeeVote_test.cpp @@ -12,6 +12,8 @@ #include #include +#include + namespace xrpl { namespace test { @@ -24,10 +26,17 @@ struct FeeSettingsFields std::optional baseFeeDrops = std::nullopt; std::optional reserveBaseDrops = std::nullopt; std::optional reserveIncrementDrops = std::nullopt; + std::optional extensionComputeLimit = std::nullopt; + std::optional extensionSizeLimit = std::nullopt; + std::optional 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()); @@ -55,6 +64,15 @@ createFeeTx(Rules const& rules, std::uint32_t seq, FeeSettingsFields const& fiel obj.setFieldU32( sfReferenceFeeUnits, fields.referenceFeeUnits ? *fields.referenceFeeUnits : 0); } + if (rules.enabled(featureSmartEscrow) || forceAllFields) + { + obj.setFieldU32( + 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); } @@ -103,6 +121,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) @@ -110,11 +134,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 @@ -165,6 +189,22 @@ 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; } @@ -187,6 +227,7 @@ class FeeVote_test : public beast::unit_test::suite void testSetup() { + testcase("FeeVote setup"); FeeSetup const defaultSetup; { // defaults @@ -195,35 +236,65 @@ 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"}); + {"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"}); + {"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"}); + {"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. // 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 == static_cast(-1234567)); BEAST_EXPECT(setup.owner_reserve == static_cast(-1234)); + BEAST_EXPECT(setup.extension_compute_limit == static_cast(-100)); + BEAST_EXPECT(setup.extension_size_limit == static_cast(-200)); + BEAST_EXPECT(setup.gas_price == static_cast(-300)); } { auto const big64 = std::to_string( @@ -232,12 +303,18 @@ 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); } } @@ -248,7 +325,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( create_genesis, env.app().config(), @@ -268,7 +345,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 @@ -277,7 +354,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( create_genesis, env.app().config(), @@ -295,12 +372,69 @@ 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( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + // Create the next ledger to apply transaction to + ledger = std::make_shared(*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( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + // Create the next ledger to apply transaction to + ledger = std::make_shared(*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 @@ -309,7 +443,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( create_genesis, env.app().config(), @@ -322,15 +456,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( create_genesis, env.app().config(), @@ -343,11 +477,32 @@ 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( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + // Create the next ledger to apply transaction to + ledger = std::make_shared(*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))); } } @@ -356,7 +511,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( create_genesis, env.app().config(), std::vector{}, env.app().getNodeFamily()); @@ -382,7 +537,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))); } } @@ -391,7 +546,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( create_genesis, env.app().config(), std::vector{}, env.app().getNodeFamily()); @@ -405,7 +560,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); } @@ -422,7 +577,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); } @@ -435,7 +590,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( create_genesis, env.app().config(), std::vector{}, env.app().getNodeFamily()); @@ -454,7 +609,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 @@ -462,7 +617,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( create_genesis, env.app().config(), std::vector{}, env.app().getNodeFamily()); @@ -476,7 +631,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); } @@ -491,7 +646,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); } @@ -504,7 +659,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( create_genesis, env.app().config(), std::vector{}, env.app().getNodeFamily()); @@ -522,7 +677,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 @@ -539,7 +694,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( @@ -568,7 +723,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( @@ -607,7 +762,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}); @@ -678,6 +833,138 @@ class FeeVote_test : public beast::unit_test::suite 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> { + auto feeVote = make_FeeVote(setup, env.app().journal("FeeVote")); + auto ledger = std::make_shared( + create_genesis, + env.app().config(), + std::vector{}, + 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, env.app().timeKeeper().closeTime()); + } + BEAST_EXPECT(ledger->isFlagLedger()); + + // Create some mock validations with fee votes + std::vector> validations; + + for (int i = 0; i < 5; i++) + { + auto sec = randomSecretKey(); + auto pub = derivePublicKey(KeyType::secp256k1, sec); + + auto val = std::make_shared( + 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(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 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 { @@ -691,6 +978,7 @@ class FeeVote_test : public beast::unit_test::suite testSingleInvalidTransaction(); testDoValidation(); testDoVoting(); + testDoVotingSmartEscrow(); } }; diff --git a/src/test/app/PseudoTx_test.cpp b/src/test/app/PseudoTx_test.cpp index aa16df8e9c4..e61430903bc 100644 --- a/src/test/app/PseudoTx_test.cpp +++ b/src/test/app/PseudoTx_test.cpp @@ -32,6 +32,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) { @@ -96,7 +102,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(); } diff --git a/src/test/jtx/impl/envconfig.cpp b/src/test/jtx/impl/envconfig.cpp index e31e687c3d6..6c27e4f5c53 100644 --- a/src/test/jtx/impl/envconfig.cpp +++ b/src/test/jtx/impl/envconfig.cpp @@ -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; diff --git a/src/test/rpc/Subscribe_test.cpp b/src/test/rpc/Subscribe_test.cpp index 414bceefd77..7717f86b419 100644 --- a/src/test/rpc/Subscribe_test.cpp +++ b/src/test/rpc/Subscribe_test.cpp @@ -477,6 +477,22 @@ class Subscribe_test : public beast::unit_test::suite 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; }; @@ -1475,7 +1491,8 @@ class Subscribe_test : public beast::unit_test::suite testTransactions_APIv1(); testTransactions_APIv2(); testManifests(); - testValidations(all - xrpFees); + testValidations(all - featureXRPFees - featureSmartEscrow); + testValidations(all - featureSmartEscrow); testValidations(all); testSubErrors(true); testSubErrors(false); diff --git a/src/xrpld/app/ledger/Ledger.cpp b/src/xrpld/app/ledger/Ledger.cpp index bde5ca010a4..dfb90fa5c2e 100644 --- a/src/xrpld/app/ledger/Ledger.cpp +++ b/src/xrpld/app/ledger/Ledger.cpp @@ -196,6 +196,12 @@ 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); } @@ -577,6 +583,7 @@ Ledger::setup() { bool oldFees = false; bool newFees = false; + bool extensionFees = false; { auto const baseFee = sle->at(~sfBaseFee); auto const reserveBase = sle->at(~sfReserveBase); @@ -593,6 +600,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 const& src) { if (src) { @@ -607,12 +615,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 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&) @@ -632,7 +660,9 @@ void Ledger::defaultFees(Config const& config) { XRPL_ASSERT( - fees_.base == 0 && fees_.reserve == 0 && fees_.increment == 0, + 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; @@ -640,6 +670,13 @@ Ledger::defaultFees(Config const& config) 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 diff --git a/src/xrpld/app/misc/FeeVoteImpl.cpp b/src/xrpld/app/misc/FeeVoteImpl.cpp index 5cb203115ab..0c2eaadaf8e 100644 --- a/src/xrpld/app/misc/FeeVoteImpl.cpp +++ b/src/xrpld/app/misc/FeeVoteImpl.cpp @@ -9,10 +9,10 @@ namespace xrpl { namespace detail { +template class VotableValue { private: - using value_type = XRPAmount; value_type const current_; // The current setting value_type const target_; // The setting we want std::map voteMap_; @@ -46,8 +46,9 @@ class VotableValue getVotes() const; }; -auto -VotableValue::getVotes() const -> std::pair +template +std::pair +VotableValue::getVotes() const { value_type ourVote = current_; int weight = 0; @@ -101,17 +102,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( @@ -124,12 +124,12 @@ FeeVoteImpl::doValidation(Fees const& lastFees, Rules const& rules, STValidation { auto to32 = [](XRPAmount target) { return target.dropsAs(); }; auto to64 = [](XRPAmount target) { return target.dropsAs(); }; - 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; @@ -139,15 +139,30 @@ 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( + 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); + } } void @@ -167,11 +182,19 @@ 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 const& val, - detail::VotableValue& value, + detail::VotableValue& value, SF_AMOUNT const& xrpField) { if (auto const field = ~val->at(~xrpField); field && field->native()) { @@ -199,7 +222,7 @@ FeeVoteImpl::doVoting( else { auto doVote = [](std::shared_ptr const& val, - detail::VotableValue& value, + detail::VotableValue& value, auto const& valueField) { if (auto const field = val->at(~valueField)) { @@ -229,6 +252,30 @@ FeeVoteImpl::doVoting( doVote(val, incReserveVote, sfReserveIncrement); } } + if (rules.enabled(featureSmartEscrow)) + { + auto doVote = [](std::shared_ptr const& val, + detail::VotableValue& 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 @@ -237,11 +284,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; @@ -266,6 +317,12 @@ FeeVoteImpl::doVoting( incReserve.first.dropsAs(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(); diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index 8178611c61d..191faf86f92 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -2320,6 +2320,16 @@ NetworkOPsImp::pubValidation(std::shared_ptr 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}; @@ -2743,11 +2753,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()); } @@ -2756,6 +2773,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 = registry_.timeKeeper().closeOffset(); std::abs(closeOffset.count()) >= 60) @@ -2941,6 +2964,12 @@ NetworkOPsImp::pubLedger(std::shared_ptr 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()); @@ -3288,8 +3317,8 @@ 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()) { @@ -3949,6 +3978,12 @@ NetworkOPsImp::subLedger(InfoSub::ref isrListener, Json::Value& jvResult) jvResult[jss::reserve_base] = lpClosed->fees().reserve.jsonClipped(); jvResult[jss::reserve_inc] = lpClosed->fees().increment.jsonClipped(); jvResult[jss::network_id] = registry_.getNetworkIDService().getNetworkID(); + 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()) diff --git a/src/xrpld/core/Config.h b/src/xrpld/core/Config.h index c40d13c83ac..db61904d06a 100644 --- a/src/xrpld/core/Config.h +++ b/src/xrpld/core/Config.h @@ -55,6 +55,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.) */ }; diff --git a/src/xrpld/core/detail/Config.cpp b/src/xrpld/core/detail/Config.cpp index d1937fa83ec..716825a40f4 100644 --- a/src/xrpld/core/detail/Config.cpp +++ b/src/xrpld/core/detail/Config.cpp @@ -1062,6 +1062,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; }