Set up fees for WASM processing (#5393)

* set up fields

* throw error if allowance is too high

* votable gas price

* fix comments

* hook everything together

* make test less flaky (hopefully)

* fix other tests

* fix some tests

* fix tests

* clean up

* add more tests

* uncomment other tests

* respond to comments

* fix build

* respond to comments
This commit is contained in:
Mayukha Vadari
2025-04-24 08:47:13 -04:00
committed by GitHub
parent 177cdaf550
commit f37d52d8e9
21 changed files with 342 additions and 47 deletions

View File

@@ -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 = <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 = 2000000 # 2 drops per gas
#-------------------------------------------------------------------------------
#
# 9. Misc Settings

View File

@@ -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;

View File

@@ -185,6 +185,8 @@ enum TEFcodes : TERUnderlyingType {
tefNO_TICKET,
tefNFTOKEN_IS_NOT_TRANSFERABLE,
tefINVALID_LEDGER_FIX_TYPE,
tefNO_WASM,
tefWASM_FIELD_NOT_INCLUDED,
};
//------------------------------------------------------------------------------

View File

@@ -319,6 +319,7 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({
// New fields
{sfExtensionComputeLimit, soeOPTIONAL},
{sfExtensionSizeLimit, soeOPTIONAL},
{sfGasPrice, soeOPTIONAL},
{sfPreviousTxnID, soeOPTIONAL},
{sfPreviousTxnLgrSeq, soeOPTIONAL},

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -81,6 +81,7 @@ STValidation::validationFormat()
// featureSmartEscrow
{sfExtensionComputeLimit, soeOPTIONAL},
{sfExtensionSizeLimit, soeOPTIONAL},
{sfGasPrice, soeOPTIONAL},
};
// clang-format on

View File

@@ -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."),

View File

@@ -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<Config> 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<Config> 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<Config> 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();
}

View File

@@ -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<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));
}
{
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);
}
}

View File

@@ -54,6 +54,7 @@ struct PseudoTx_test : public beast::unit_test::suite
{
obj[sfExtensionComputeLimit] = 0;
obj[sfExtensionSizeLimit] = 0;
obj[sfGasPrice] = 0;
}
}));

View File

@@ -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 */
/******************************************************************************/

View File

@@ -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;

View File

@@ -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<std::uint32_t> 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<SLE>

View File

@@ -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;
}
});

View File

@@ -2295,6 +2295,9 @@ NetworkOPsImp::pubValidation(std::shared_ptr<STValidation> 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<ReadView const> 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;
}
}

View File

@@ -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);

View File

@@ -34,6 +34,8 @@
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/XRPAmount.h>
#include <algorithm>
// 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<uint8_t> 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<uint8_t> escrowTxData(escrowTx.begin(), escrowTx.end());
std::vector<uint8_t> 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())
{

View File

@@ -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.) */
};

View File

@@ -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;
}