mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-27 09:00:32 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -185,6 +185,8 @@ enum TEFcodes : TERUnderlyingType {
|
||||
tefNO_TICKET,
|
||||
tefNFTOKEN_IS_NOT_TRANSFERABLE,
|
||||
tefINVALID_LEDGER_FIX_TYPE,
|
||||
tefNO_WASM,
|
||||
tefWASM_FIELD_NOT_INCLUDED,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -319,6 +319,7 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({
|
||||
// New fields
|
||||
{sfExtensionComputeLimit, soeOPTIONAL},
|
||||
{sfExtensionSizeLimit, soeOPTIONAL},
|
||||
{sfGasPrice, soeOPTIONAL},
|
||||
|
||||
{sfPreviousTxnID, soeOPTIONAL},
|
||||
{sfPreviousTxnLgrSeq, soeOPTIONAL},
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -81,6 +81,7 @@ STValidation::validationFormat()
|
||||
// featureSmartEscrow
|
||||
{sfExtensionComputeLimit, soeOPTIONAL},
|
||||
{sfExtensionSizeLimit, soeOPTIONAL},
|
||||
{sfGasPrice, soeOPTIONAL},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
@@ -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."),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ struct PseudoTx_test : public beast::unit_test::suite
|
||||
{
|
||||
obj[sfExtensionComputeLimit] = 0;
|
||||
obj[sfExtensionSizeLimit] = 0;
|
||||
obj[sfGasPrice] = 0;
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
@@ -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 */
|
||||
/******************************************************************************/
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -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.) */
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user