diff --git a/cfg/rippled-example.cfg b/cfg/rippled-example.cfg index 7affa212ab..6da6841052 100644 --- a/cfg/rippled-example.cfg +++ b/cfg/rippled-example.cfg @@ -1271,8 +1271,17 @@ # default. Don't change this without understanding the consequences. # # Example: -# extension_compute_limit = 2000000 # 2 million gas +# extension_size_limit = 2000000 # 2 mb # +# gas_price = +# +# 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 = 2000000 # 2 drops per gas #------------------------------------------------------------------------------- # # 9. Misc Settings diff --git a/include/xrpl/protocol/Fees.h b/include/xrpl/protocol/Fees.h index a89bd4e486..1a1ecacb36 100644 --- a/include/xrpl/protocol/Fees.h +++ b/include/xrpl/protocol/Fees.h @@ -24,6 +24,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 @@ -37,6 +39,7 @@ struct Fees 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/TER.h b/include/xrpl/protocol/TER.h index 4f544963f0..caf05daf76 100644 --- a/include/xrpl/protocol/TER.h +++ b/include/xrpl/protocol/TER.h @@ -185,6 +185,8 @@ enum TEFcodes : TERUnderlyingType { tefNO_TICKET, tefNFTOKEN_IS_NOT_TRANSFERABLE, tefINVALID_LEDGER_FIX_TYPE, + tefNO_WASM, + tefWASM_FIELD_NOT_INCLUDED, }; //------------------------------------------------------------------------------ diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index 2d4a3453ea..08849f0fa6 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -319,6 +319,7 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({ // New 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 42a706c4de..65a4bfcbc1 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -114,6 +114,8 @@ TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50) TYPED_SFIELD(sfOracleDocumentID, UINT32, 51) TYPED_SFIELD(sfExtensionComputeLimit, UINT32, 52) TYPED_SFIELD(sfExtensionSizeLimit, UINT32, 53) +TYPED_SFIELD(sfGasPrice, UINT32, 54) +TYPED_SFIELD(sfComputationAllowance, UINT32, 55) // 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 19c31d439b..f9d70ea6db 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -59,6 +59,7 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, ({ {sfFulfillment, soeOPTIONAL}, {sfCondition, soeOPTIONAL}, {sfCredentialIDs, soeOPTIONAL}, + {sfComputationAllowance, soeOPTIONAL}, })) @@ -490,8 +491,9 @@ TRANSACTION(ttFEE, 101, SetFee, ({ {sfBaseFeeDrops, soeOPTIONAL}, {sfReserveBaseDrops, soeOPTIONAL}, {sfReserveIncrementDrops, soeOPTIONAL}, - {sfExtensionComputeLimit, soeOPTIONAL}, - {sfExtensionSizeLimit, soeOPTIONAL}, + {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 fa34cf800b..96aefb0788 100644 --- a/include/xrpl/protocol/jss.h +++ b/include/xrpl/protocol/jss.h @@ -267,6 +267,7 @@ 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 diff --git a/src/libxrpl/protocol/STValidation.cpp b/src/libxrpl/protocol/STValidation.cpp index 948a47f0ac..3a9d16445e 100644 --- a/src/libxrpl/protocol/STValidation.cpp +++ b/src/libxrpl/protocol/STValidation.cpp @@ -81,6 +81,7 @@ STValidation::validationFormat() // featureSmartEscrow {sfExtensionComputeLimit, soeOPTIONAL}, {sfExtensionSizeLimit, soeOPTIONAL}, + {sfGasPrice, soeOPTIONAL}, }; // clang-format on diff --git a/src/libxrpl/protocol/TER.cpp b/src/libxrpl/protocol/TER.cpp index 3766f33c54..6a2387035b 100644 --- a/src/libxrpl/protocol/TER.cpp +++ b/src/libxrpl/protocol/TER.cpp @@ -147,6 +147,8 @@ transResults() MAKE_ERROR(tefNO_TICKET, "Ticket is not in ledger."), MAKE_ERROR(tefNFTOKEN_IS_NOT_TRANSFERABLE, "The specified NFToken is not transferable."), MAKE_ERROR(tefINVALID_LEDGER_FIX_TYPE, "The LedgerFixType field has an invalid value."), + MAKE_ERROR(tefNO_WASM, "There is no WASM code to run, but a WASM-specific field was included."), + MAKE_ERROR(tefWASM_FIELD_NOT_INCLUDED, "WASM code requires a field to be included that was not included."), MAKE_ERROR(telLOCAL_ERROR, "Local failure."), MAKE_ERROR(telBAD_DOMAIN, "Domain too long."), diff --git a/src/test/app/Escrow_test.cpp b/src/test/app/Escrow_test.cpp index 6c0e32179e..6b25da6363 100644 --- a/src/test/app/Escrow_test.cpp +++ b/src/test/app/Escrow_test.cpp @@ -1659,7 +1659,7 @@ struct Escrow_test : public beast::unit_test::suite } void - testFinishFunctionPreflight() + testCreateFinishFunctionPreflight() { testcase("Test preflight checks involving FinishFunction"); @@ -1703,12 +1703,37 @@ struct Escrow_test : public beast::unit_test::suite env.close(); } - Env env(*this); + { + // FinishFunction > max length + Env env(*this, envconfig([](std::unique_ptr cfg) { + cfg->FEES.extension_size_limit = 10; // 10 bytes + return cfg; + })); + XRPAmount const txnFees = env.current()->fees().base + 1000; + // create escrow + env.fund(XRP(5000), alice, carol); + + auto escrowCreate = escrow(alice, carol, XRP(500)); + + // 11-byte string + std::string longWasmHex = "00112233445566778899AA"; + env(escrowCreate, + finish_function(longWasmHex), + cancel_time(env.now() + 100s), + fee(txnFees), + ter(temMALFORMED)); + env.close(); + } + + Env env(*this, envconfig([](std::unique_ptr cfg) { + cfg->START_UP = Config::FRESH; + return cfg; + })); XRPAmount const txnFees = env.current()->fees().base + 1000; // create escrow env.fund(XRP(5000), alice, carol); - auto escrowCreate = escrow(alice, carol, XRP(1000)); + auto escrowCreate = escrow(alice, carol, XRP(500)); // Success situations { @@ -1794,23 +1819,132 @@ struct Escrow_test : public beast::unit_test::suite ter(temMALFORMED)); env.close(); } - // { - // // FinishFunction > max length - // std::string longWasmHex = "00"; - // // TODO: fix to use the config setting - // // TODO: make this test more efficient - // // uncomment when that's done - // for (int i = 0; i < 4294967295; i++) - // { - // longWasmHex += "11"; - // } - // env(escrowCreate, - // finish_function(longWasmHex), - // cancel_time(env.now() + 100s), - // fee(txnFees), - // ter(temMALFORMED)); - // env.close(); + } + + void + testFinishWasmFailures() + { + testcase("EscrowFinish Smart Escrow failures"); + + using namespace jtx; + using namespace std::chrono; + + Account const alice{"alice"}; + Account const carol{"carol"}; + + // Tests whether the ledger index is >= 5 + // #[no_mangle] + // pub fn ready() -> bool { + // unsafe { host_lib::getLedgerSqn() >= 5} // } + static auto wasmHex = + "0061736d010000000105016000017f02190108686f73745f6c69620c6765744c65" + "6467657253716e0000030201000405017001010105030100100619037f01418080" + "c0000b7f00418080c0000b7f00418080c0000b072d04066d656d6f727902000572" + "6561647900010a5f5f646174615f656e6403010b5f5f686561705f626173650302" + "0a0d010b0010808080800041044a0b006c046e616d65000e0d7761736d5f6c6962" + "2e7761736d01410200375f5a4e387761736d5f6c696238686f73745f6c69623132" + "6765744c656467657253716e313768303033306666356636376562356638314501" + "057265616479071201000f5f5f737461636b5f706f696e74657200550970726f64" + "756365727302086c616e6775616765010452757374000c70726f6365737365642d" + "62790105727573746325312e38332e302d6e696768746c79202863326637346333" + "663920323032342d30392d30392900490f7461726765745f666561747572657304" + "2b0a6d756c746976616c75652b0f6d757461626c652d676c6f62616c732b0f7265" + "666572656e63652d74797065732b087369676e2d657874"; + + { + // featureSmartEscrow disabled + Env env(*this, supported_amendments() - featureSmartEscrow); + env.fund(XRP(5000), alice, carol); + XRPAmount const txnFees = env.current()->fees().base + 1000; + env(finish(carol, alice, 1), + fee(txnFees), + comp_allowance(110), + ter(temDISABLED)); + env.close(); + } + + { + // ComputationAllowance > max compute limit + Env env(*this, envconfig([](std::unique_ptr cfg) { + cfg->FEES.extension_compute_limit = 1'000; // in gas + return cfg; + })); + env.fund(XRP(5000), alice, carol); + // Run past the flag ledger so that a Fee change vote occurs and + // updates FeeSettings. (It also activates all supported + // amendments.) + for (auto i = env.current()->seq(); i <= 257; ++i) + env.close(); + + auto const allowance = 1'001; + env(finish(carol, alice, 1), + fee(env.current()->fees().base + allowance), + comp_allowance(allowance), + ter(temBAD_LIMIT)); + } + + Env env(*this); + + // Run past the flag ledger so that a Fee change vote occurs and + // updates FeeSettings. (It also activates all supported + // amendments.) + for (auto i = env.current()->seq(); i <= 257; ++i) + env.close(); + + XRPAmount const txnFees = env.current()->fees().base + 1000; + env.fund(XRP(5000), alice, carol); + + // create escrow + auto const seq = env.seq(alice); + env(escrow(alice, carol, XRP(500)), + finish_function(wasmHex), + cancel_time(env.now() + 100s), + fee(txnFees)); + env.close(); + + { + // no ComputationAllowance field + env(finish(carol, alice, seq), ter(tefWASM_FIELD_NOT_INCLUDED)); + } + + { + // not enough fees + // This function takes 110 gas + // In testing, 1 gas costs 1 drop + auto const finishFee = env.current()->fees().base + 109; + env(finish(carol, alice, seq), + fee(finishFee), + comp_allowance(110), + ter(telINSUF_FEE_P)); + } + + { + // not enough gas + // This function takes 110 gas + // In testing, 1 gas costs 1 drop + auto const finishFee = env.current()->fees().base + 108; + env(finish(carol, alice, seq), + fee(finishFee), + comp_allowance(108), + ter(tecFAILED_PROCESSING)); + } + + { + // ComputationAllowance field included w/no FinishFunction on + // escrow + auto const seq2 = env.seq(alice); + env(escrow(alice, carol, XRP(500)), + finish_time(env.now() + 10s), + cancel_time(env.now() + 100s)); + env.close(); + + auto const allowance = 100; + env(finish(carol, alice, seq2), + fee(env.current()->fees().base + allowance), + comp_allowance(allowance), + ter(tefNO_WASM)); + } } void @@ -1865,22 +1999,30 @@ struct Escrow_test : public beast::unit_test::suite env.require(balance(carol, XRP(5000))); env(finish(carol, alice, seq), + comp_allowance(110), fee(txnFees), ter(tecWASM_REJECTED)); env(finish(alice, alice, seq), + comp_allowance(110), fee(txnFees), ter(tecWASM_REJECTED)); env(finish(alice, alice, seq), + comp_allowance(110), fee(txnFees), ter(tecWASM_REJECTED)); env(finish(carol, alice, seq), + comp_allowance(110), fee(txnFees), ter(tecWASM_REJECTED)); env(finish(carol, alice, seq), + comp_allowance(110), fee(txnFees), ter(tecWASM_REJECTED)); env.close(); - env(finish(alice, alice, seq), fee(txnFees), ter(tesSUCCESS)); + env(finish(alice, alice, seq), + fee(txnFees), + comp_allowance(110), + ter(tesSUCCESS)); env.close(); BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0); @@ -1910,29 +2052,34 @@ struct Escrow_test : public beast::unit_test::suite // no fulfillment provided, function fails env(finish(carol, alice, seq), + comp_allowance(110), fee(txnFees), ter(tecCRYPTOCONDITION_ERROR)); // fulfillment provided, function fails env(finish(carol, alice, seq), condition(cb1), fulfillment(fb1), + comp_allowance(110), fee(txnFees), ter(tecWASM_REJECTED)); env.close(); // no fulfillment provided, function succeeds env(finish(alice, alice, seq), + comp_allowance(110), fee(txnFees), ter(tecCRYPTOCONDITION_ERROR)); // wrong fulfillment provided, function succeeds env(finish(alice, alice, seq), condition(cb1), fulfillment(fb2), + comp_allowance(110), fee(txnFees), ter(tecCRYPTOCONDITION_ERROR)); // fulfillment provided, function succeeds, tx succeeds env(finish(alice, alice, seq), condition(cb1), fulfillment(fb1), + comp_allowance(110), fee(txnFees), ter(tesSUCCESS)); env.close(); @@ -1965,16 +2112,19 @@ struct Escrow_test : public beast::unit_test::suite // finish time hasn't passed, function fails env(finish(carol, alice, seq), + comp_allowance(110), fee(txnFees + 1), ter(tecNO_PERMISSION)); env.close(); // finish time hasn't passed, function succeeds for (; env.now() < ts; env.close()) env(finish(carol, alice, seq), + comp_allowance(110), fee(txnFees + 2), ter(tecNO_PERMISSION)); env(finish(carol, alice, seq), + comp_allowance(110), fee(txnFees + 1), ter(tesSUCCESS)); BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0); @@ -2004,17 +2154,22 @@ struct Escrow_test : public beast::unit_test::suite // finish time hasn't passed, function fails env(finish(carol, alice, seq), + comp_allowance(110), fee(txnFees), ter(tecNO_PERMISSION)); env.close(); // finish time has passed, function fails env(finish(carol, alice, seq), + comp_allowance(110), fee(txnFees), ter(tecWASM_REJECTED)); env.close(); // finish time has passed, function succeeds, tx succeeds - env(finish(carol, alice, seq), fee(txnFees), ter(tesSUCCESS)); + env(finish(carol, alice, seq), + comp_allowance(110), + fee(txnFees), + ter(tesSUCCESS)); env.close(); BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0); @@ -3175,19 +3330,24 @@ struct Escrow_test : public beast::unit_test::suite env.require(balance(alice, XRP(4000) - txnFees)); env.require(balance(carol, XRP(5000))); + auto const allowance = 40'000; + // FinishAfter time hasn't passed env(finish(carol, alice, seq), + comp_allowance(allowance), fee(txnFees), ter(tecNO_PERMISSION)); env.close(); // tx sender not escrow creator (alice) env(finish(carol, alice, seq), + comp_allowance(allowance), fee(txnFees), ter(tecWASM_REJECTED)); // destination balance is too high env(finish(carol, alice, seq), + comp_allowance(allowance), fee(txnFees), ter(tecWASM_REJECTED)); @@ -3199,11 +3359,15 @@ struct Escrow_test : public beast::unit_test::suite // tx sender not escrow creator (alice) env(finish(carol, alice, seq), + comp_allowance(allowance), fee(txnFees), ter(tecWASM_REJECTED)); env.close(); - env(finish(alice, alice, seq), fee(txnFees), ter(tesSUCCESS)); + env(finish(alice, alice, seq), + comp_allowance(allowance), + fee(txnFees), + ter(tesSUCCESS)); env.close(); BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0); @@ -3226,7 +3390,8 @@ struct Escrow_test : public beast::unit_test::suite testConsequences(); testEscrowWithTickets(); testCredentials(); - testFinishFunctionPreflight(); + testCreateFinishFunctionPreflight(); + testFinishWasmFailures(); testFinishFunction(); testAllHostFunctions(); } diff --git a/src/test/app/FeeVote_test.cpp b/src/test/app/FeeVote_test.cpp index 73bb539a22..8e333b27ea 100644 --- a/src/test/app/FeeVote_test.cpp +++ b/src/test/app/FeeVote_test.cpp @@ -43,6 +43,7 @@ class FeeVote_test : public beast::unit_test::suite BEAST_EXPECT( setup.extension_size_limit == defaultSetup.extension_size_limit); + BEAST_EXPECT(setup.gas_price == defaultSetup.gas_price); } { Section config; @@ -51,13 +52,15 @@ class FeeVote_test : public beast::unit_test::suite "account_reserve = 1234567", "owner_reserve = 1234", "extension_compute_limit = 100", - "extension_size_limit = 200"}); + "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; @@ -66,7 +69,8 @@ class FeeVote_test : public beast::unit_test::suite "account_reserve = yada", "owner_reserve = foo", "extension_compute_limit = bar", - "extension_size_limit = baz"}); + "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); @@ -78,6 +82,7 @@ class FeeVote_test : public beast::unit_test::suite BEAST_EXPECT( setup.extension_size_limit == defaultSetup.extension_size_limit); + BEAST_EXPECT(setup.gas_price == defaultSetup.gas_price); } { Section config; @@ -86,7 +91,8 @@ class FeeVote_test : public beast::unit_test::suite "account_reserve = -1234567", "owner_reserve = -1234", "extension_compute_limit = -100", - "extension_size_limit = -200"}); + "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); @@ -99,6 +105,7 @@ class FeeVote_test : public beast::unit_test::suite static_cast(-100)); BEAST_EXPECT( setup.extension_size_limit == static_cast(-200)); + BEAST_EXPECT(setup.gas_price == static_cast(-300)); } { const auto big64 = std::to_string( @@ -111,7 +118,8 @@ class FeeVote_test : public beast::unit_test::suite "account_reserve = " + big64, "owner_reserve = " + big64, "extension_compute_limit = " + big64, - "extension_size_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); @@ -123,6 +131,7 @@ class FeeVote_test : public beast::unit_test::suite BEAST_EXPECT( setup.extension_size_limit == defaultSetup.extension_size_limit); + BEAST_EXPECT(setup.gas_price == defaultSetup.gas_price); } } diff --git a/src/test/app/PseudoTx_test.cpp b/src/test/app/PseudoTx_test.cpp index 1101f56c69..0ba7b0e5ba 100644 --- a/src/test/app/PseudoTx_test.cpp +++ b/src/test/app/PseudoTx_test.cpp @@ -54,6 +54,7 @@ struct PseudoTx_test : public beast::unit_test::suite { obj[sfExtensionComputeLimit] = 0; obj[sfExtensionSizeLimit] = 0; + obj[sfGasPrice] = 0; } })); diff --git a/src/test/jtx/TestHelpers.h b/src/test/jtx/TestHelpers.h index 8a5e30a001..b77b00b00c 100644 --- a/src/test/jtx/TestHelpers.h +++ b/src/test/jtx/TestHelpers.h @@ -398,6 +398,23 @@ public: } }; +struct comp_allowance +{ +private: + std::uint32_t value_; + +public: + explicit comp_allowance(std::uint32_t const& value) : value_(value) + { + } + + void + operator()(Env&, JTx& jt) const + { + jt.jv[sfComputationAllowance.jsonName] = value_; + } +}; + /* Payment Channel */ /******************************************************************************/ diff --git a/src/test/jtx/impl/envconfig.cpp b/src/test/jtx/impl/envconfig.cpp index b94c79ccb8..ab0969c6cb 100644 --- a/src/test/jtx/impl/envconfig.cpp +++ b/src/test/jtx/impl/envconfig.cpp @@ -33,11 +33,14 @@ setupConfigForUnitTests(Config& cfg) using namespace jtx; // Default fees to old values, so tests don't have to worry about changes in // Config.h + // NOTE: For new `FEES` fields, you need to wait for the first flag ledger + // to close for the values to be activated. cfg.FEES.reference_fee = 10; cfg.FEES.account_reserve = XRP(200).value().xrp().drops(); cfg.FEES.owner_reserve = XRP(50).value().xrp().drops(); cfg.FEES.extension_compute_limit = 4294967295; cfg.FEES.extension_size_limit = 4294967295; + cfg.FEES.gas_price = 1000000; // The Beta API (currently v2) is always available to tests cfg.BETA_RPC_API = true; diff --git a/src/xrpld/app/ledger/Ledger.cpp b/src/xrpld/app/ledger/Ledger.cpp index d3c59fbeb6..d9a0dce151 100644 --- a/src/xrpld/app/ledger/Ledger.cpp +++ b/src/xrpld/app/ledger/Ledger.cpp @@ -228,6 +228,7 @@ Ledger::Ledger( 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); } @@ -659,6 +660,7 @@ Ledger::setup() 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) { @@ -669,7 +671,9 @@ Ledger::setup() }; assign(fees_.extensionComputeLimit, extensionComputeLimit); assign(fees_.extensionSizeLimit, extensionSizeLimit); - extensionFees = extensionComputeLimit || extensionSizeLimit; + assign(fees_.gasPrice, gasPrice); + extensionFees = + extensionComputeLimit || extensionSizeLimit || gasPrice; } if (oldFees && newFees) // Should be all of one or the other, but not both @@ -701,7 +705,8 @@ Ledger::defaultFees(Config const& config) { assert( fees_.base == 0 && fees_.reserve == 0 && fees_.increment == 0 && - fees_.extensionComputeLimit == 0 && fees_.extensionSizeLimit == 0); + fees_.extensionComputeLimit == 0 && fees_.extensionSizeLimit == 0 && + fees_.gasPrice == 0); if (fees_.base == 0) fees_.base = config.FEES.reference_fee; if (fees_.reserve == 0) @@ -710,6 +715,8 @@ Ledger::defaultFees(Config const& config) 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 e312ab042f..6c6e2f0865 100644 --- a/src/xrpld/app/misc/FeeVoteImpl.cpp +++ b/src/xrpld/app/misc/FeeVoteImpl.cpp @@ -216,6 +216,7 @@ FeeVoteImpl::doValidation( target_.extension_size_limit, "extension size limit", sfExtensionSizeLimit); + vote(lastFees.gasPrice, target_.gas_price, "gas price", sfGasPrice); } } @@ -247,6 +248,9 @@ FeeVoteImpl::doVoting( 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)) { @@ -332,6 +336,7 @@ FeeVoteImpl::doVoting( continue; doVote(val, extensionComputeVote, sfExtensionComputeLimit); doVote(val, extensionSizeVote, sfExtensionSizeLimit); + doVote(val, gasPriceVote, sfGasPrice); } } @@ -344,12 +349,13 @@ FeeVoteImpl::doVoting( 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 || - extensionCompute.second || extensionSize.second) + extensionCompute.second || extensionSize.second || gasPrice.second) { JLOG(journal_.warn()) << "We are voting for a fee change: " << baseFee.first << "/" @@ -381,6 +387,7 @@ FeeVoteImpl::doVoting( { obj[sfExtensionComputeLimit] = extensionCompute.first; obj[sfExtensionSizeLimit] = extensionSize.first; + obj[sfGasPrice] = gasPrice.first; } }); diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index b721c80bfd..049a47bc23 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -2295,6 +2295,9 @@ NetworkOPsImp::pubValidation(std::shared_ptr const& val) 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}; @@ -2758,6 +2761,7 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters) 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()); @@ -2773,6 +2777,7 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters) 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(); @@ -2965,6 +2970,7 @@ NetworkOPsImp::pubLedger(std::shared_ptr const& lpAccepted) 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()); @@ -4007,6 +4013,7 @@ NetworkOPsImp::subLedger(InfoSub::ref isrListener, Json::Value& jvResult) jvResult[jss::extension_compute] = lpClosed->fees().extensionComputeLimit; jvResult[jss::extension_size] = lpClosed->fees().extensionSizeLimit; + jvResult[jss::gas_price] = lpClosed->fees().gasPrice; } } diff --git a/src/xrpld/app/tx/detail/Change.cpp b/src/xrpld/app/tx/detail/Change.cpp index e40e61ff94..7d84e003fb 100644 --- a/src/xrpld/app/tx/detail/Change.cpp +++ b/src/xrpld/app/tx/detail/Change.cpp @@ -133,13 +133,15 @@ Change::preclaim(PreclaimContext const& ctx) if (ctx.view.rules().enabled(featureSmartEscrow)) { if (!ctx.tx.isFieldPresent(sfExtensionComputeLimit) || - !ctx.tx.isFieldPresent(sfExtensionSizeLimit)) + !ctx.tx.isFieldPresent(sfExtensionSizeLimit) || + !ctx.tx.isFieldPresent(sfGasPrice)) return temMALFORMED; } else { if (ctx.tx.isFieldPresent(sfExtensionComputeLimit) || - ctx.tx.isFieldPresent(sfExtensionSizeLimit)) + ctx.tx.isFieldPresent(sfExtensionSizeLimit) || + ctx.tx.isFieldPresent(sfGasPrice)) return temDISABLED; } return tesSUCCESS; @@ -393,6 +395,7 @@ Change::applyFee() { set(feeObject, ctx_.tx, sfExtensionComputeLimit); set(feeObject, ctx_.tx, sfExtensionSizeLimit); + set(feeObject, ctx_.tx, sfGasPrice); } view().update(feeObject); diff --git a/src/xrpld/app/tx/detail/Escrow.cpp b/src/xrpld/app/tx/detail/Escrow.cpp index 055fbc1499..ab91274326 100644 --- a/src/xrpld/app/tx/detail/Escrow.cpp +++ b/src/xrpld/app/tx/detail/Escrow.cpp @@ -34,6 +34,8 @@ #include #include +#include + // During an EscrowFinish, the transaction must specify both // a condition and a fulfillment. We track whether that // fulfillment matches and validates the condition. @@ -361,6 +363,13 @@ EscrowFinish::preflight(PreflightContext const& ctx) !ctx.rules.enabled(featureCredentials)) return temDISABLED; + if (ctx.tx.isFieldPresent(sfComputationAllowance) && + !ctx.rules.enabled(featureSmartEscrow)) + { + JLOG(ctx.j.debug()) << "SmartEscrow not enabled"; + return temDISABLED; + } + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; @@ -402,6 +411,16 @@ EscrowFinish::preflight(PreflightContext const& ctx) } } + if (auto const allowance = ctx.tx[~sfComputationAllowance]; allowance) + { + if (*allowance > ctx.app.config().FEES.extension_compute_limit) + { + JLOG(ctx.j.debug()) + << "ComputationAllowance too large: " << *allowance; + return temBAD_LIMIT; + } + } + if (auto const err = credentials::checkFields(ctx); !isTesSuccess(err)) return err; @@ -417,8 +436,10 @@ EscrowFinish::calculateBaseFee(ReadView const& view, STTx const& tx) { extraFee += view.fees().base * (32 + (fb->size() / 16)); } - // TODO: make this fee increase based on the extra compute run - + if (auto const allowance = tx[~sfComputationAllowance]; allowance) + { + extraFee += (*allowance) * view.fees().gasPrice / MICRO_DROPS_PER_DROP; + } return Transactor::calculateBaseFee(view, tx) + extraFee; } @@ -428,6 +449,34 @@ EscrowFinish::preclaim(PreclaimContext const& ctx) if (!ctx.view.rules().enabled(featureCredentials)) return Transactor::preclaim(ctx); + if (ctx.view.rules().enabled(featureSmartEscrow)) + { + // this check is done in doApply before this amendment is enabled + auto const k = keylet::escrow(ctx.tx[sfOwner], ctx.tx[sfOfferSequence]); + auto const slep = ctx.view.read(k); + if (!slep) + return tecNO_TARGET; + + if (slep->isFieldPresent(sfFinishFunction)) + { + if (!ctx.tx.isFieldPresent(sfComputationAllowance)) + { + JLOG(ctx.j.debug()) + << "FinishFunction requires ComputationAllowance"; + return tefWASM_FIELD_NOT_INCLUDED; + } + } + else + { + if (ctx.tx.isFieldPresent(sfComputationAllowance)) + { + JLOG(ctx.j.debug()) << "FinishFunction not present, " + "ComputationAllowance present"; + return tefNO_WASM; + } + } + } + if (auto const err = credentials::valid(ctx, ctx.tx[sfAccount]); !isTesSuccess(err)) return err; @@ -441,7 +490,8 @@ EscrowFinish::doApply() auto const k = keylet::escrow(ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]); auto const slep = ctx_.view().peek(k); if (!slep) - return tecNO_TARGET; + return ctx_.view().rules().enabled(featureSmartEscrow) ? tecINTERNAL + : tecNO_TARGET; // Order of processing the release conditions (in order of performance): // FinishAfter/CancelAfter @@ -579,17 +629,15 @@ EscrowFinish::doApply() std::vector wasm(wasmStr.begin(), wasmStr.end()); std::string funcName("ready"); - auto const escrowTx = - ctx_.tx.getJson(JsonOptions::none).toStyledString(); - auto const escrowObj = - slep->getJson(JsonOptions::none).toStyledString(); - std::vector escrowTxData(escrowTx.begin(), escrowTx.end()); - std::vector escrowObjData(escrowObj.begin(), escrowObj.end()); - WasmHostFunctionsImpl ledgerDataProvider(ctx_, k); - std::uint32_t gasLimit = ctx_.app.config().FEES.extension_compute_limit; - auto re = runEscrowWasm(wasm, funcName, &ledgerDataProvider, gasLimit); + if (!ctx_.tx.isFieldPresent(sfComputationAllowance)) + { + // already checked above, this check is just in case + return tecINTERNAL; + } + std::uint32_t allowance = ctx_.tx[sfComputationAllowance]; + auto re = runEscrowWasm(wasm, funcName, &ledgerDataProvider, allowance); JLOG(j_.trace()) << "Escrow WASM ran"; if (re.has_value()) { diff --git a/src/xrpld/core/Config.h b/src/xrpld/core/Config.h index 8e29ecdcf4..4b297c8610 100644 --- a/src/xrpld/core/Config.h +++ b/src/xrpld/core/Config.h @@ -79,6 +79,9 @@ struct FeeSetup /** The WASM size limit for Feature Extensions. */ std::uint32_t extension_size_limit{4294967295}; + /** The price of 1 WASM gas, in micro-drops. */ + std::uint32_t gas_price{1000000}; + /* (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 fb4a4aab21..97d6d5fd9d 100644 --- a/src/xrpld/core/detail/Config.cpp +++ b/src/xrpld/core/detail/Config.cpp @@ -1108,6 +1108,8 @@ setup_FeeVote(Section const& 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; }