Merge remote-tracking branch 'upstream/ripple/smart-escrow' into develop2

This commit is contained in:
Mayukha Vadari
2025-04-28 17:14:28 -04:00
39 changed files with 5363 additions and 49 deletions

View File

@@ -115,6 +115,7 @@ endif()
find_package(nudb REQUIRED)
find_package(date REQUIRED)
find_package(xxHash REQUIRED)
find_package(wasmedge REQUIRED)
target_link_libraries(ripple_libs INTERFACE
ed25519::ed25519

View File

@@ -1249,6 +1249,39 @@
# Example:
# owner_reserve = 2000000 # 2 XRP
#
# extension_compute_limit = <gas>
#
# The extension compute limit is the maximum amount of gas that can be
# consumed by a single transaction. The gas limit is used to prevent
# transactions from consuming too many resources.
#
# If this parameter is unspecified, rippled will use an internal
# default. Don't change this without understanding the consequences.
#
# Example:
# extension_compute_limit = 2000000 # 2 million gas
#
# extension_size_limit = <bytes>
#
# The extension size limit is the maximum size of a WASM extension in
# bytes. The size limit is used to prevent extensions from consuming
# too many resources.
#
# If this parameter is unspecified, rippled will use an internal
# default. Don't change this without understanding the consequences.
#
# Example:
# extension_size_limit = 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

@@ -65,6 +65,7 @@ target_link_libraries(xrpl.imports.main
xrpl.libpb
xxHash::xxhash
$<$<BOOL:${voidstar}>:antithesis-sdk-cpp>
wasmedge::wasmedge
)
include(add_module)

View File

@@ -2,6 +2,7 @@ from conan import ConanFile
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
import re
class Xrpl(ConanFile):
name = 'xrpl'
@@ -32,6 +33,7 @@ class Xrpl(ConanFile):
'soci/4.0.3',
'xxhash/0.8.2',
'zlib/1.3.1',
'wasmedge/0.14.1',
]
tool_requires = [
@@ -125,6 +127,7 @@ class Xrpl(ConanFile):
self.folders.generators = 'build/generators'
generators = 'CMakeDeps'
def generate(self):
tc = CMakeToolchain(self)
tc.variables['tests'] = self.options.tests

View File

@@ -451,8 +451,8 @@ mulDivU(Source1 value, Dest mul, Source2 div)
}
using namespace boost::multiprecision;
uint128_t product;
using uint128 = boost::multiprecision::uint128_t;
uint128 product;
product = multiply(
product,
static_cast<std::uint64_t>(value.value()),

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
@@ -34,6 +36,10 @@ struct Fees
XRPAmount base{0}; // Reference tx cost (drops)
XRPAmount reserve{0}; // Reserve base (drops)
XRPAmount increment{0}; // Reserve increment (drops)
std::uint32_t extensionComputeLimit{
0}; // Extension compute limit (instructions)
std::uint32_t extensionSizeLimit{0}; // Extension size limit (bytes)
std::uint32_t gasPrice{0}; // price of WASM gas (micro-drops)
explicit Fees() = default;
Fees(Fees const&) = default;

View File

@@ -230,6 +230,12 @@ page(Keylet const& root, std::uint64_t index = 0) noexcept
Keylet
escrow(AccountID const& src, std::uint32_t seq) noexcept;
inline Keylet
escrow(uint256 const& key) noexcept
{
return {ltESCROW, key};
}
/** A PaymentChannel */
Keylet
payChan(AccountID const& src, AccountID const& dst, std::uint32_t seq) noexcept;

View File

@@ -152,11 +152,12 @@ mulRatio(
bool roundUp)
{
using namespace boost::multiprecision;
using int128 = boost::multiprecision::int128_t;
if (!den)
Throw<std::runtime_error>("division by zero");
int128_t const amt128(amt.value());
int128 const amt128(amt.value());
auto const neg = amt.value() < 0;
auto const m = amt128 * num;
auto r = m / den;

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,
};
//------------------------------------------------------------------------------
@@ -344,6 +346,7 @@ enum TECcodes : TERUnderlyingType {
tecARRAY_TOO_LARGE = 191,
tecLOCKED = 192,
tecBAD_CREDENTIALS = 193,
tecWASM_REJECTED = 194,
};
//------------------------------------------------------------------------------

View File

@@ -286,11 +286,12 @@ mulRatio(
bool roundUp)
{
using namespace boost::multiprecision;
using int128 = boost::multiprecision::int128_t;
if (!den)
Throw<std::runtime_error>("division by zero");
int128_t const amt128(amt.drops());
int128 const amt128(amt.drops());
auto const neg = amt.drops() < 0;
auto const m = amt128 * num;
auto r = m / den;

View File

@@ -32,8 +32,8 @@
// If you add an amendment here, then do not forget to increment `numFeatures`
// in include/xrpl/protocol/Feature.h.
XRPL_FEATURE(SmartEscrow, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (PayChanCancelAfter, Supported::yes, VoteBehavior::DefaultNo)
// Check flags in Credential transactions
XRPL_FIX (InvalidTxFlags, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (FrozenLPTokenTransfer, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo)

View File

@@ -315,6 +315,12 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({
{sfBaseFeeDrops, soeOPTIONAL},
{sfReserveBaseDrops, soeOPTIONAL},
{sfReserveIncrementDrops, soeOPTIONAL},
// New fields
{sfExtensionComputeLimit, soeOPTIONAL},
{sfExtensionSizeLimit, soeOPTIONAL},
{sfGasPrice, soeOPTIONAL},
{sfPreviousTxnID, soeOPTIONAL},
{sfPreviousTxnLgrSeq, soeOPTIONAL},
}))
@@ -344,6 +350,8 @@ LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({
{sfCondition, soeOPTIONAL},
{sfCancelAfter, soeOPTIONAL},
{sfFinishAfter, soeOPTIONAL},
{sfFinishFunction, soeOPTIONAL},
{sfData, soeOPTIONAL},
{sfSourceTag, soeOPTIONAL},
{sfDestinationTag, soeOPTIONAL},
{sfOwnerNode, soeREQUIRED},

View File

@@ -112,6 +112,10 @@ TYPED_SFIELD(sfEmitGeneration, UINT32, 46)
TYPED_SFIELD(sfVoteWeight, UINT32, 48)
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)
@@ -223,7 +227,7 @@ TYPED_SFIELD(sfBaseFeeDrops, AMOUNT, 22)
TYPED_SFIELD(sfReserveBaseDrops, AMOUNT, 23)
TYPED_SFIELD(sfReserveIncrementDrops, AMOUNT, 24)
// currency amount (AMM)
// currency amount (more)
TYPED_SFIELD(sfLPTokenOut, AMOUNT, 25)
TYPED_SFIELD(sfLPTokenIn, AMOUNT, 26)
TYPED_SFIELD(sfEPrice, AMOUNT, 27)
@@ -265,6 +269,7 @@ TYPED_SFIELD(sfAssetClass, VL, 28)
TYPED_SFIELD(sfProvider, VL, 29)
TYPED_SFIELD(sfMPTokenMetadata, VL, 30)
TYPED_SFIELD(sfCredentialType, VL, 31)
TYPED_SFIELD(sfFinishFunction, VL, 32)
// account (common)
TYPED_SFIELD(sfAccount, ACCOUNT, 1)

View File

@@ -42,12 +42,14 @@ TRANSACTION(ttPAYMENT, 0, Payment, ({
/** This transaction type creates an escrow object. */
TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, ({
{sfDestination, soeREQUIRED},
{sfAmount, soeREQUIRED},
{sfDestination, soeREQUIRED},
{sfDestinationTag, soeOPTIONAL},
{sfCondition, soeOPTIONAL},
{sfCancelAfter, soeOPTIONAL},
{sfFinishAfter, soeOPTIONAL},
{sfDestinationTag, soeOPTIONAL},
{sfFinishFunction, soeOPTIONAL},
{sfData, soeOPTIONAL},
}))
/** This transaction type completes an existing escrow. */
@@ -57,6 +59,7 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, ({
{sfFulfillment, soeOPTIONAL},
{sfCondition, soeOPTIONAL},
{sfCredentialIDs, soeOPTIONAL},
{sfComputationAllowance, soeOPTIONAL},
}))
@@ -488,6 +491,9 @@ TRANSACTION(ttFEE, 101, SetFee, ({
{sfBaseFeeDrops, soeOPTIONAL},
{sfReserveBaseDrops, soeOPTIONAL},
{sfReserveIncrementDrops, soeOPTIONAL},
{sfExtensionComputeLimit, soeOPTIONAL},
{sfExtensionSizeLimit, soeOPTIONAL},
{sfGasPrice, soeOPTIONAL},
}))
/** This system-generated transaction type is used to update the network's negative UNL

View File

@@ -265,6 +265,9 @@ JSS(expected_date_UTC); // out: any (warnings)
JSS(expected_ledger_size); // out: TxQ
JSS(expiration); // out: AccountOffers, AccountChannels,
// ValidatorList, amm_info
JSS(extension_compute); // out: NetworkOps
JSS(extension_size); // out: NetworkOps
JSS(gas_price); // out: NetworkOps
JSS(fail_hard); // in: Sign, Submit
JSS(failed); // out: InboundLedger
JSS(feature); // in: Feature

View File

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

View File

@@ -123,6 +123,7 @@ transResults()
MAKE_ERROR(tecARRAY_TOO_LARGE, "Array is too large."),
MAKE_ERROR(tecLOCKED, "Fund is locked."),
MAKE_ERROR(tecBAD_CREDENTIALS, "Bad credentials."),
MAKE_ERROR(tecWASM_REJECTED, "The custom WASM code that was run rejected your transaction."),
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
@@ -146,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."),

File diff suppressed because it is too large Load Diff

View File

@@ -37,36 +37,62 @@ class FeeVote_test : public beast::unit_test::suite
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve);
BEAST_EXPECT(
setup.extension_compute_limit ==
defaultSetup.extension_compute_limit);
BEAST_EXPECT(
setup.extension_size_limit ==
defaultSetup.extension_size_limit);
BEAST_EXPECT(setup.gas_price == defaultSetup.gas_price);
}
{
Section config;
config.append(
{"reference_fee = 50",
"account_reserve = 1234567",
"owner_reserve = 1234"});
"owner_reserve = 1234",
"extension_compute_limit = 100",
"extension_size_limit = 200",
" gas_price = 300"});
auto setup = setup_FeeVote(config);
BEAST_EXPECT(setup.reference_fee == 50);
BEAST_EXPECT(setup.account_reserve == 1234567);
BEAST_EXPECT(setup.owner_reserve == 1234);
BEAST_EXPECT(setup.extension_compute_limit == 100);
BEAST_EXPECT(setup.extension_size_limit == 200);
BEAST_EXPECT(setup.gas_price == 300);
}
{
Section config;
config.append(
{"reference_fee = blah",
"account_reserve = yada",
"owner_reserve = foo"});
"owner_reserve = foo",
"extension_compute_limit = bar",
"extension_size_limit = baz",
"gas_price = qux"});
// Illegal values are ignored, and the defaults left unchanged
auto setup = setup_FeeVote(config);
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve);
BEAST_EXPECT(
setup.extension_compute_limit ==
defaultSetup.extension_compute_limit);
BEAST_EXPECT(
setup.extension_size_limit ==
defaultSetup.extension_size_limit);
BEAST_EXPECT(setup.gas_price == defaultSetup.gas_price);
}
{
Section config;
config.append(
{"reference_fee = -50",
"account_reserve = -1234567",
"owner_reserve = -1234"});
"owner_reserve = -1234",
"extension_compute_limit = -100",
"extension_size_limit = -200",
"gas_price = -300"});
// Illegal values are ignored, and the defaults left unchanged
auto setup = setup_FeeVote(config);
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
@@ -74,6 +100,12 @@ class FeeVote_test : public beast::unit_test::suite
setup.account_reserve == static_cast<std::uint32_t>(-1234567));
BEAST_EXPECT(
setup.owner_reserve == static_cast<std::uint32_t>(-1234));
BEAST_EXPECT(
setup.extension_compute_limit ==
static_cast<std::uint32_t>(-100));
BEAST_EXPECT(
setup.extension_size_limit == static_cast<std::uint32_t>(-200));
BEAST_EXPECT(setup.gas_price == static_cast<std::uint32_t>(-300));
}
{
const auto big64 = std::to_string(
@@ -84,12 +116,22 @@ class FeeVote_test : public beast::unit_test::suite
config.append(
{"reference_fee = " + big64,
"account_reserve = " + big64,
"owner_reserve = " + big64});
"owner_reserve = " + big64,
"extension_compute_limit = " + big64,
"extension_size_limit = " + big64,
"gas_price = " + big64});
// Illegal values are ignored, and the defaults left unchanged
auto setup = setup_FeeVote(config);
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve);
BEAST_EXPECT(
setup.extension_compute_limit ==
defaultSetup.extension_compute_limit);
BEAST_EXPECT(
setup.extension_size_limit ==
defaultSetup.extension_size_limit);
BEAST_EXPECT(setup.gas_price == defaultSetup.gas_price);
}
}

View File

@@ -50,6 +50,12 @@ struct PseudoTx_test : public beast::unit_test::suite
obj[sfReserveIncrement] = 0;
obj[sfReferenceFeeUnits] = 0;
}
if (rules.enabled(featureSmartEscrow))
{
obj[sfExtensionComputeLimit] = 0;
obj[sfExtensionSizeLimit] = 0;
obj[sfGasPrice] = 0;
}
}));
res.emplace_back(STTx(ttAMENDMENT, [&](auto& obj) {
@@ -116,9 +122,10 @@ struct PseudoTx_test : public beast::unit_test::suite
{
using namespace test::jtx;
FeatureBitset const all{supported_amendments()};
FeatureBitset const xrpFees{featureXRPFees};
testPrevented(all - featureXRPFees - featureSmartEscrow);
testPrevented(all - featureXRPFees);
testPrevented(all - featureSmartEscrow);
testPrevented(all);
testAllowed();
}

2165
src/test/app/Wasm_test.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -345,6 +345,76 @@ public:
}
};
struct finish_function
{
private:
std::string value_;
public:
explicit finish_function(std::string func) : value_(func)
{
}
explicit finish_function(Slice const& func) : value_(strHex(func))
{
}
template <size_t N>
explicit finish_function(std::array<std::uint8_t, N> const& f)
: finish_function(makeSlice(f))
{
}
void
operator()(Env&, JTx& jt) const
{
jt.jv[sfFinishFunction.jsonName] = value_;
}
};
struct data
{
private:
std::string value_;
public:
explicit data(std::string func) : value_(func)
{
}
explicit data(Slice const& func) : value_(strHex(func))
{
}
template <size_t N>
explicit data(std::array<std::uint8_t, N> const& f) : data(makeSlice(f))
{
}
void
operator()(Env&, JTx& jt) const
{
jt.jv[sfData.jsonName] = value_;
}
};
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,9 +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 = UNIT_TEST_REFERENCE_FEE;
cfg.FEES.account_reserve = XRP(200).value().xrp().drops();
cfg.FEES.owner_reserve = XRP(50).value().xrp().drops();
cfg.FEES.extension_compute_limit = 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

@@ -522,6 +522,22 @@ public:
if (jv.isMember(jss::reserve_inc) != isFlagLedger)
return false;
if (env.closed()->rules().enabled(featureSmartEscrow))
{
if (jv.isMember(jss::extension_compute) != isFlagLedger)
return false;
if (jv.isMember(jss::extension_size) != isFlagLedger)
return false;
}
else
{
if (jv.isMember(jss::extension_compute))
return false;
if (jv.isMember(jss::extension_size))
return false;
}
return true;
};
@@ -1305,14 +1321,14 @@ public:
{
using namespace test::jtx;
FeatureBitset const all{supported_amendments()};
FeatureBitset const xrpFees{featureXRPFees};
testServer();
testLedger();
testTransactions_APIv1();
testTransactions_APIv2();
testManifests();
testValidations(all - xrpFees);
testValidations(all - featureXRPFees - featureSmartEscrow);
testValidations(all - featureSmartEscrow);
testValidations(all);
testSubErrors(true);
testSubErrors(false);

View File

@@ -221,6 +221,15 @@ Ledger::Ledger(
sle->at(sfReserveIncrement) = *f;
sle->at(sfReferenceFeeUnits) = Config::FEE_UNITS_DEPRECATED;
}
if (std::find(
amendments.begin(), amendments.end(), featureSmartEscrow) !=
amendments.end())
{
sle->at(sfExtensionComputeLimit) =
config.FEES.extension_compute_limit;
sle->at(sfExtensionSizeLimit) = config.FEES.extension_size_limit;
sle->at(sfGasPrice) = config.FEES.gas_price;
}
rawInsert(sle);
}
@@ -612,6 +621,7 @@ Ledger::setup()
{
bool oldFees = false;
bool newFees = false;
bool extensionFees = false;
{
auto const baseFee = sle->at(~sfBaseFee);
auto const reserveBase = sle->at(~sfReserveBase);
@@ -629,6 +639,7 @@ Ledger::setup()
auto const reserveBaseXRP = sle->at(~sfReserveBaseDrops);
auto const reserveIncrementXRP =
sle->at(~sfReserveIncrementDrops);
auto assign = [&ret](
XRPAmount& dest,
std::optional<STAmount> const& src) {
@@ -645,12 +656,35 @@ Ledger::setup()
assign(fees_.increment, reserveIncrementXRP);
newFees = baseFeeXRP || reserveBaseXRP || reserveIncrementXRP;
}
{
auto const extensionComputeLimit =
sle->at(~sfExtensionComputeLimit);
auto const extensionSizeLimit = sle->at(~sfExtensionSizeLimit);
auto const gasPrice = sle->at(~sfGasPrice);
auto assign = [](std::uint32_t& dest,
std::optional<std::uint32_t> const& src) {
if (src)
{
dest = src.value();
}
};
assign(fees_.extensionComputeLimit, extensionComputeLimit);
assign(fees_.extensionSizeLimit, extensionSizeLimit);
assign(fees_.gasPrice, gasPrice);
extensionFees =
extensionComputeLimit || extensionSizeLimit || gasPrice;
}
if (oldFees && newFees)
// Should be all of one or the other, but not both
ret = false;
if (!rules_.enabled(featureXRPFees) && newFees)
// Can't populate the new fees before the amendment is enabled
ret = false;
if (!rules_.enabled(featureSmartEscrow) && extensionFees)
// Can't populate the extension fees before the amendment is
// enabled
ret = false;
}
}
catch (SHAMapMissingNode const&)
@@ -669,15 +703,20 @@ Ledger::setup()
void
Ledger::defaultFees(Config const& config)
{
XRPL_ASSERT(
fees_.base == 0 && fees_.reserve == 0 && fees_.increment == 0,
"ripple::Ledger::defaultFees : zero fees");
assert(
fees_.base == 0 && fees_.reserve == 0 && fees_.increment == 0 &&
fees_.extensionComputeLimit == 0 && fees_.extensionSizeLimit == 0 &&
fees_.gasPrice == 0);
if (fees_.base == 0)
fees_.base = config.FEES.reference_fee;
if (fees_.reserve == 0)
fees_.reserve = config.FEES.account_reserve;
if (fees_.increment == 0)
fees_.increment = config.FEES.owner_reserve;
if (fees_.extensionComputeLimit == 0)
fees_.extensionComputeLimit = config.FEES.extension_compute_limit;
if (fees_.extensionSizeLimit == 0)
fees_.extensionSizeLimit = config.FEES.extension_size_limit;
if (fees_.gasPrice == 0)
fees_.gasPrice = config.FEES.gas_price;
}
std::shared_ptr<SLE>

View File

@@ -17,8 +17,8 @@
*/
//==============================================================================
#ifndef RIPPLE_APP_MISC_AMMUTILS_H_INLCUDED
#define RIPPLE_APP_MISC_AMMUTILS_H_INLCUDED
#ifndef RIPPLE_APP_MISC_AMMUTILS_H_INCLUDED
#define RIPPLE_APP_MISC_AMMUTILS_H_INCLUDED
#include <xrpld/ledger/View.h>
@@ -127,4 +127,4 @@ isOnlyLiquidityProvider(
} // namespace ripple
#endif // RIPPLE_APP_MISC_AMMUTILS_H_INLCUDED
#endif // RIPPLE_APP_MISC_AMMUTILS_H_INCLUDED

View File

@@ -28,10 +28,10 @@ namespace ripple {
namespace detail {
template <typename value_type>
class VotableValue
{
private:
using value_type = XRPAmount;
value_type const current_; // The current setting
value_type const target_; // The setting we want
std::map<value_type, int> voteMap_;
@@ -66,8 +66,9 @@ public:
getVotes() const;
};
auto
VotableValue::getVotes() const -> std::pair<value_type, bool>
template <typename value_type>
std::pair<value_type, bool>
VotableValue<value_type>::getVotes() const
{
value_type ourVote = current_;
int weight = 0;
@@ -190,6 +191,33 @@ FeeVoteImpl::doValidation(
"reserve increment",
sfReserveIncrement);
}
if (rules.enabled(featureSmartEscrow))
{
auto vote = [&v, this](
auto const current,
std::uint32_t target,
const char* name,
auto const& sfield) {
if (current != target)
{
JLOG(journal_.info())
<< "Voting for " << name << " of " << target;
v[sfield] = target;
}
};
vote(
lastFees.extensionComputeLimit,
target_.extension_compute_limit,
"extension compute limit",
sfExtensionComputeLimit);
vote(
lastFees.extensionSizeLimit,
target_.extension_size_limit,
"extension size limit",
sfExtensionSizeLimit);
vote(lastFees.gasPrice, target_.gas_price, "gas price", sfGasPrice);
}
}
void
@@ -212,11 +240,22 @@ FeeVoteImpl::doVoting(
detail::VotableValue incReserveVote(
lastClosedLedger->fees().increment, target_.owner_reserve);
detail::VotableValue extensionComputeVote(
lastClosedLedger->fees().extensionComputeLimit,
target_.extension_compute_limit);
detail::VotableValue extensionSizeVote(
lastClosedLedger->fees().extensionSizeLimit,
target_.extension_size_limit);
detail::VotableValue gasPriceVote(
lastClosedLedger->fees().gasPrice, target_.gas_price);
auto const& rules = lastClosedLedger->rules();
if (rules.enabled(featureXRPFees))
{
auto doVote = [](std::shared_ptr<STValidation> const& val,
detail::VotableValue& value,
detail::VotableValue<XRPAmount>& value,
SF_AMOUNT const& xrpField) {
if (auto const field = ~val->at(~xrpField);
field && field->native())
@@ -245,7 +284,7 @@ FeeVoteImpl::doVoting(
else
{
auto doVote = [](std::shared_ptr<STValidation> const& val,
detail::VotableValue& value,
detail::VotableValue<XRPAmount>& value,
auto const& valueField) {
if (auto const field = val->at(~valueField))
{
@@ -276,6 +315,30 @@ FeeVoteImpl::doVoting(
doVote(val, incReserveVote, sfReserveIncrement);
}
}
if (rules.enabled(featureSmartEscrow))
{
auto doVote = [](std::shared_ptr<STValidation> const& val,
detail::VotableValue<std::uint32_t>& value,
SF_UINT32 const& extensionField) {
if (auto const field = ~val->at(~extensionField); field)
{
value.addVote(field.value());
}
else
{
value.noVote();
}
};
for (auto const& val : set)
{
if (!val->isTrusted())
continue;
doVote(val, extensionComputeVote, sfExtensionComputeLimit);
doVote(val, extensionSizeVote, sfExtensionSizeLimit);
doVote(val, gasPriceVote, sfGasPrice);
}
}
// choose our positions
// TODO: Use structured binding once LLVM 16 is the minimum supported
@@ -284,11 +347,15 @@ FeeVoteImpl::doVoting(
auto const baseFee = baseFeeVote.getVotes();
auto const baseReserve = baseReserveVote.getVotes();
auto const incReserve = incReserveVote.getVotes();
auto const extensionCompute = extensionComputeVote.getVotes();
auto const extensionSize = extensionSizeVote.getVotes();
auto const gasPrice = gasPriceVote.getVotes();
auto const seq = lastClosedLedger->info().seq + 1;
// add transactions to our position
if (baseFee.second || baseReserve.second || incReserve.second)
if (baseFee.second || baseReserve.second || incReserve.second ||
extensionCompute.second || extensionSize.second || gasPrice.second)
{
JLOG(journal_.warn())
<< "We are voting for a fee change: " << baseFee.first << "/"
@@ -316,6 +383,12 @@ FeeVoteImpl::doVoting(
incReserveVote.current());
obj[sfReferenceFeeUnits] = Config::FEE_UNITS_DEPRECATED;
}
if (rules.enabled(featureSmartEscrow))
{
obj[sfExtensionComputeLimit] = extensionCompute.first;
obj[sfExtensionSizeLimit] = extensionSize.first;
obj[sfGasPrice] = gasPrice.first;
}
});
uint256 txID = feeTx.getTransactionID();

View File

@@ -2290,6 +2290,18 @@ NetworkOPsImp::pubValidation(std::shared_ptr<STValidation> const& val)
reserveIncXRP && reserveIncXRP->native())
jvObj[jss::reserve_inc] = reserveIncXRP->xrp().jsonClipped();
if (auto const extensionComputeLimit =
~val->at(~sfExtensionComputeLimit);
extensionComputeLimit)
jvObj[jss::extension_compute] = *extensionComputeLimit;
if (auto const extensionSizeLimit = ~val->at(~sfExtensionSizeLimit);
extensionSizeLimit)
jvObj[jss::extension_size] = *extensionSizeLimit;
if (auto const gasPrice = ~val->at(~sfGasPrice); gasPrice)
jvObj[jss::gas_price] = *gasPrice;
// NOTE Use MultiApiJson to publish two slightly different JSON objects
// for consumers supporting different API versions
MultiApiJson multiObj{jvObj};
@@ -2739,12 +2751,22 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters)
l[jss::seq] = Json::UInt(lpClosed->info().seq);
l[jss::hash] = to_string(lpClosed->info().hash);
bool const smartEscrowEnabled =
m_ledgerMaster.getValidatedLedger()->rules().enabled(
featureSmartEscrow);
if (!human)
{
l[jss::base_fee] = baseFee.jsonClipped();
l[jss::reserve_base] =
lpClosed->fees().accountReserve(0).jsonClipped();
l[jss::reserve_inc] = lpClosed->fees().increment.jsonClipped();
if (smartEscrowEnabled)
{
l[jss::extension_compute] =
lpClosed->fees().extensionComputeLimit;
l[jss::extension_size] = lpClosed->fees().extensionSizeLimit;
l[jss::gas_price] = lpClosed->fees().gasPrice;
}
l[jss::close_time] = Json::Value::UInt(
lpClosed->info().closeTime.time_since_epoch().count());
}
@@ -2754,6 +2776,13 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters)
l[jss::reserve_base_xrp] =
lpClosed->fees().accountReserve(0).decimalXRP();
l[jss::reserve_inc_xrp] = lpClosed->fees().increment.decimalXRP();
if (smartEscrowEnabled)
{
l[jss::extension_compute] =
lpClosed->fees().extensionComputeLimit;
l[jss::extension_size] = lpClosed->fees().extensionSizeLimit;
l[jss::gas_price] = lpClosed->fees().gasPrice;
}
if (auto const closeOffset = app_.timeKeeper().closeOffset();
std::abs(closeOffset.count()) >= 60)
@@ -2939,6 +2968,14 @@ NetworkOPsImp::pubLedger(std::shared_ptr<ReadView const> const& lpAccepted)
lpAccepted->fees().accountReserve(0).jsonClipped();
jvObj[jss::reserve_inc] =
lpAccepted->fees().increment.jsonClipped();
if (lpAccepted->rules().enabled(featureSmartEscrow))
{
jvObj[jss::extension_compute] =
lpAccepted->fees().extensionComputeLimit;
jvObj[jss::extension_size] =
lpAccepted->fees().extensionSizeLimit;
jvObj[jss::gas_price] = lpAccepted->fees().gasPrice;
}
jvObj[jss::txn_count] = Json::UInt(alpAccepted->size());
@@ -3989,6 +4026,13 @@ NetworkOPsImp::subLedger(InfoSub::ref isrListener, Json::Value& jvResult)
jvResult[jss::reserve_base] =
lpClosed->fees().accountReserve(0).jsonClipped();
jvResult[jss::reserve_inc] = lpClosed->fees().increment.jsonClipped();
if (lpClosed->rules().enabled(featureSmartEscrow))
{
jvResult[jss::extension_compute] =
lpClosed->fees().extensionComputeLimit;
jvResult[jss::extension_size] = lpClosed->fees().extensionSizeLimit;
jvResult[jss::gas_price] = lpClosed->fees().gasPrice;
}
}
if ((mMode >= OperatingMode::SYNCING) && !isNeedNetworkLedger())

View File

@@ -0,0 +1,118 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpld/app/misc/WasmHostFuncImpl.h>
#include <xrpl/protocol/digest.h>
namespace ripple {
int32_t
WasmHostFunctionsImpl::getLedgerSqn()
{
return ctx.view().seq();
}
int32_t
WasmHostFunctionsImpl::getParentLedgerTime()
{
return ctx.view().parentCloseTime().time_since_epoch().count(); // TODO try
}
// TODO remove json code after deciding encoding scheme
std::optional<Bytes>
WasmHostFunctionsImpl::getTxField(const std::string& fname)
{
auto js = ctx.tx.getJson(JsonOptions::none);
if (js.isMember(fname))
{
auto s = js.get(fname, Json::Value::null).asString();
return Bytes{s.begin(), s.end()};
}
else
return std::nullopt;
}
std::optional<Bytes>
WasmHostFunctionsImpl::getLedgerEntryField(
int32_t type,
Bytes const& kdata,
const std::string& fname)
{
auto kl = [&]() -> std::optional<ripple::Keylet> {
if (type == ltACCOUNT_ROOT)
{
std::string s(kdata.begin(), kdata.end());
auto const account = parseBase58<AccountID>(s);
if (account)
{
return keylet::account(account.value());
}
}
return std::nullopt;
}();
if (!kl || !ctx.view().exists(kl.value()))
return std::nullopt;
auto js = ctx.view().read(kl.value())->getJson(JsonOptions::none);
if (js.isMember(fname))
{
auto s = js.get(fname, Json::Value::null).asString();
return Bytes{s.begin(), s.end()};
}
else
return std::nullopt;
}
std::optional<Bytes>
WasmHostFunctionsImpl::getCurrentLedgerEntryField(const std::string& fname)
{
if (!ctx.view().exists(leKey))
return std::nullopt;
auto js = ctx.view().read(leKey)->getJson(JsonOptions::none);
if (js.isMember(fname))
{
auto s = js.get(fname, Json::Value::null).asString();
return Bytes{s.begin(), s.end()};
}
else
return std::nullopt;
}
bool
WasmHostFunctionsImpl::updateData(const Bytes& data)
{
if (!ctx.view().exists(leKey))
return false;
auto sle = ctx.view().peek(leKey);
sle->setFieldVL(sfData, data);
ctx.view().update(sle);
return true;
}
Hash
WasmHostFunctionsImpl::computeSha512HalfHash(const Bytes& data)
{
auto const hash = sha512Half(data);
return uint256::fromVoid(hash.data());
}
} // namespace ripple

View File

@@ -0,0 +1,69 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2023 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_APP_MISC_WASMHOSTFUNCIMPL_H_INLCUDED
#define RIPPLE_APP_MISC_WASMHOSTFUNCIMPL_H_INLCUDED
#include <xrpl/basics/Expected.h>
#include <xrpl/protocol/TER.h>
#include "xrpl/basics/base_uint.h"
#include "xrpld/app/misc/WasmVM.h"
#include "xrpld/app/tx/detail/ApplyContext.h"
#include <wasmedge/wasmedge.h>
namespace ripple {
class WasmHostFunctionsImpl : public HostFunctions
{
public:
WasmHostFunctionsImpl(ApplyContext& ctx, Keylet leKey)
: ctx(ctx), leKey(leKey)
{
}
int32_t
getLedgerSqn() override;
int32_t
getParentLedgerTime() override;
std::optional<Bytes>
getTxField(std::string const& fname) override;
std::optional<Bytes>
getLedgerEntryField(
int32_t type,
Bytes const& kdata,
std::string const& fname) override;
std::optional<Bytes>
getCurrentLedgerEntryField(std::string const& fname) override;
bool
updateData(Bytes const& data) override;
Hash
computeSha512HalfHash(Bytes const& data) override;
private:
ApplyContext& ctx;
Keylet leKey;
};
} // namespace ripple
#endif // RIPPLE_APP_MISC_WASMHOSTFUNCIMPL_H_INLCUDED

View File

@@ -0,0 +1,505 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2020 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpld/app/misc/WasmVM.h>
#include "xrpl/protocol/AccountID.h"
#include "xrpl/protocol/LedgerFormats.h"
namespace ripple {
WasmEdge_Result
getLedgerSqn(
void* data,
const WasmEdge_CallingFrameContext*,
const WasmEdge_Value*,
WasmEdge_Value* out)
{
out[0] = WasmEdge_ValueGenI32(((HostFunctions*)data)->getLedgerSqn());
return WasmEdge_Result_Success;
}
WasmEdge_Result
getParentLedgerTime(
void* data,
const WasmEdge_CallingFrameContext*,
const WasmEdge_Value*,
WasmEdge_Value* out)
{
out[0] =
WasmEdge_ValueGenI32(((HostFunctions*)data)->getParentLedgerTime());
return WasmEdge_Result_Success;
}
Expected<Bytes, WasmEdge_Result>
getParameterData(
const WasmEdge_CallingFrameContext* fm,
const WasmEdge_Value* in,
size_t index)
{
auto fnameOffset = (uint32_t)WasmEdge_ValueGetI32(in[index]);
auto fnameLen = (uint32_t)WasmEdge_ValueGetI32(in[index + 1]);
Bytes fname(fnameLen, char{0});
WasmEdge_MemoryInstanceContext* mem =
WasmEdge_CallingFrameGetMemoryInstance(fm, 0);
WasmEdge_Result Res = WasmEdge_MemoryInstanceGetData(
mem, (uint8_t*)(fname.data()), fnameOffset, fnameLen);
if (WasmEdge_ResultOK(Res))
{
return fname;
}
else
{
return Unexpected<WasmEdge_Result>(Res);
}
}
Expected<std::string, WasmEdge_Result>
getFieldName(
const WasmEdge_CallingFrameContext* fm,
const WasmEdge_Value* in,
size_t index)
{
auto dataRes = getParameterData(fm, in, index);
if (dataRes)
{
return std::string(dataRes.value().begin(), dataRes->end());
}
else
{
return Unexpected<WasmEdge_Result>(dataRes.error());
}
}
Expected<WasmEdge_Value, WasmEdge_Result>
setData(const WasmEdge_CallingFrameContext* fm, Bytes const& data)
{
auto alloc = [fm](int32_t dataLen) -> int32_t {
WasmEdge_String allocFunc = WasmEdge_StringCreateByCString("allocate");
auto mod = WasmEdge_CallingFrameGetModuleInstance(fm);
WasmEdge_FunctionInstanceContext* func =
WasmEdge_ModuleInstanceFindFunction(mod, allocFunc);
WasmEdge_Value allocParams[1] = {
WasmEdge_ValueGenI32(dataLen)}; // 4 for prepend the data size
WasmEdge_Value allocReturns[1];
auto executor = WasmEdge_CallingFrameGetExecutor(fm);
auto res = WasmEdge_ExecutorInvoke(
executor, func, allocParams, 1, allocReturns, 1);
if (WasmEdge_ResultOK(res))
{
return WasmEdge_ValueGetI32(allocReturns[0]);
}
else
{
return 0;
}
};
auto dataLen = (int32_t)data.size();
auto dataPtr = alloc(dataLen);
auto retPtr = alloc(8);
if (dataPtr && retPtr)
{
auto mem = WasmEdge_CallingFrameGetMemoryInstance(fm, 0);
auto res =
WasmEdge_MemoryInstanceSetData(mem, data.data(), dataPtr, dataLen);
if (WasmEdge_ResultOK(res))
{
unsigned char intBuf[8]; // little-endian
for (size_t i = 0; i < 4; ++i)
{
intBuf[i] = (dataPtr >> (i * 8)) & 0xFF;
}
for (size_t i = 0; i < 4; ++i)
{
intBuf[i + 4] = (dataLen >> (i * 8)) & 0xFF;
}
res = WasmEdge_MemoryInstanceSetData(mem, intBuf, retPtr, 8);
if (WasmEdge_ResultOK(res))
{
return WasmEdge_ValueGenI32(retPtr);
}
}
}
return Unexpected<WasmEdge_Result>(WasmEdge_Result_Fail);
}
WasmEdge_Result
getTxField(
void* data,
const WasmEdge_CallingFrameContext* fm,
const WasmEdge_Value* in,
WasmEdge_Value* out)
{
auto fname = getFieldName(fm, in, 0);
if (!fname)
return fname.error();
auto fieldData = ((HostFunctions*)data)->getTxField(fname.value());
if (!fieldData)
return WasmEdge_Result_Fail;
auto pointer = setData(fm, fieldData.value());
if (!pointer)
return pointer.error();
out[0] = pointer.value();
// out[1] = WasmEdge_ValueGenI32((int)fieldData.value().size());
return WasmEdge_Result_Success;
}
WasmEdge_Result
getLedgerEntryField(
void* data,
const WasmEdge_CallingFrameContext* fm,
const WasmEdge_Value* in,
WasmEdge_Value* out)
{
auto type = WasmEdge_ValueGetI32(in[0]);
auto lkData = getParameterData(fm, in, 1);
if (!lkData)
return lkData.error();
auto fname = getFieldName(fm, in, 3);
if (!fname)
return fname.error();
auto fieldData =
((HostFunctions*)data)
->getLedgerEntryField(type, lkData.value(), fname.value());
if (!fieldData)
return WasmEdge_Result_Fail;
auto pointer = setData(fm, fieldData.value());
if (!pointer)
return pointer.error();
out[0] = pointer.value();
// out[1] = WasmEdge_ValueGenI32((int)fieldData.value().size());
return WasmEdge_Result_Success;
}
WasmEdge_Result
getCurrentLedgerEntryField(
void* data,
const WasmEdge_CallingFrameContext* fm,
const WasmEdge_Value* in,
WasmEdge_Value* out)
{
auto fname = getFieldName(fm, in, 0);
if (!fname)
return fname.error();
auto fieldData =
((HostFunctions*)data)->getCurrentLedgerEntryField(fname.value());
if (!fieldData)
return WasmEdge_Result_Fail;
auto pointer = setData(fm, fieldData.value());
if (!pointer)
return pointer.error();
out[0] = pointer.value();
// out[1] = WasmEdge_ValueGenI32((int)fieldData.value().size());
return WasmEdge_Result_Success;
}
WasmEdge_Result
updateData(
void* data,
const WasmEdge_CallingFrameContext* fm,
const WasmEdge_Value* in,
WasmEdge_Value* out)
{
auto fname = getParameterData(fm, in, 0);
if (!fname)
return fname.error();
if (((HostFunctions*)data)->updateData(fname.value()))
return WasmEdge_Result_Success;
else
return WasmEdge_Result_Fail;
}
WasmEdge_Result
computeSha512HalfHash(
void* data,
const WasmEdge_CallingFrameContext* fm,
const WasmEdge_Value* in,
WasmEdge_Value* out)
{
auto fname = getParameterData(fm, in, 0);
if (!fname)
return fname.error();
auto hres = ((HostFunctions*)data)->computeSha512HalfHash(fname.value());
Bytes digest{hres.begin(), hres.end()};
auto pointer = setData(fm, digest);
if (!pointer)
return pointer.error();
out[0] = pointer.value();
// out[1] = WasmEdge_ValueGenI32(32);
return WasmEdge_Result_Success;
}
WasmEdge_Result
print(
void* data,
const WasmEdge_CallingFrameContext* fm,
const WasmEdge_Value* in,
WasmEdge_Value* out)
{
auto f = getParameterData(fm, in, 0);
if (!f)
return f.error();
std::string s(f.value().begin(), f.value().end());
std::cout << s << std::endl;
return WasmEdge_Result_Success;
}
Expected<EscrowResult, TER>
runEscrowWasm(
std::vector<uint8_t> const& wasmCode,
std::string const& funcName,
HostFunctions* hfs,
uint64_t gasLimit)
{
// WasmEdge_LogOff();
// TODO deletes
// create VM and set cost limit
WasmEdge_ConfigureContext* conf = WasmEdge_ConfigureCreate();
WasmEdge_ConfigureStatisticsSetInstructionCounting(conf, true);
WasmEdge_ConfigureStatisticsSetCostMeasuring(conf, true);
WasmEdge_ConfigureSetMaxMemoryPage(conf, MAX_PAGES);
WasmEdge_VMContext* VMCxt = WasmEdge_VMCreate(conf, NULL);
WasmEdge_StatisticsContext* StatCxt =
WasmEdge_VMGetStatisticsContext(VMCxt);
WasmEdge_StatisticsSetCostLimit(StatCxt, gasLimit);
{ // register host function
// module
WasmEdge_String libName = WasmEdge_StringCreateByCString("host_lib");
WasmEdge_ModuleInstanceContext* hostMod =
WasmEdge_ModuleInstanceCreate(libName);
WasmEdge_StringDelete(libName);
// getLedgerSqn
{
WasmEdge_ValType returnList[1] = {WasmEdge_ValTypeGenI32()};
WasmEdge_FunctionTypeContext* hostFuncType =
WasmEdge_FunctionTypeCreate(NULL, 0, returnList, 1);
WasmEdge_FunctionInstanceContext* hostFunc =
WasmEdge_FunctionInstanceCreate(
hostFuncType, getLedgerSqn, hfs, 100);
// WasmEdge_FunctionTypeDelete(hostFuncType);
WasmEdge_String fName =
WasmEdge_StringCreateByCString("getLedgerSqn");
WasmEdge_ModuleInstanceAddFunction(hostMod, fName, hostFunc);
// WasmEdge_StringDelete(fName);
// WasmEdge_FunctionInstanceDelete(hostFunc);
}
// getParentLedgerTime
{
WasmEdge_ValType returnList[1] = {WasmEdge_ValTypeGenI32()};
WasmEdge_FunctionTypeContext* hostFuncType =
WasmEdge_FunctionTypeCreate(NULL, 0, returnList, 1);
WasmEdge_FunctionInstanceContext* hostFunc =
WasmEdge_FunctionInstanceCreate(
hostFuncType, getParentLedgerTime, hfs, 100);
// WasmEdge_FunctionTypeDelete(hostFuncType);
WasmEdge_String fName =
WasmEdge_StringCreateByCString("getParentLedgerTime");
WasmEdge_ModuleInstanceAddFunction(hostMod, fName, hostFunc);
// WasmEdge_StringDelete(fName);
// WasmEdge_FunctionInstanceDelete(hostFunc);
}
// getTxField
{
WasmEdge_ValType inputList[2] = {
WasmEdge_ValTypeGenI32(), WasmEdge_ValTypeGenI32()};
WasmEdge_ValType returnList[1] = {WasmEdge_ValTypeGenI32()};
WasmEdge_FunctionTypeContext* hostFuncType =
WasmEdge_FunctionTypeCreate(inputList, 2, returnList, 1);
WasmEdge_FunctionInstanceContext* hostFunc =
WasmEdge_FunctionInstanceCreate(
hostFuncType, getTxField, hfs, 100);
// WasmEdge_FunctionTypeDelete(hostFuncType);
// WasmEdge_FunctionInstanceDelete(hostFunc);
WasmEdge_String fName =
WasmEdge_StringCreateByCString("getTxField");
WasmEdge_ModuleInstanceAddFunction(hostMod, fName, hostFunc);
// WasmEdge_StringDelete(fName);
}
// getLedgerEntryField
{
WasmEdge_ValType inputList[5] = {
WasmEdge_ValTypeGenI32(),
WasmEdge_ValTypeGenI32(),
WasmEdge_ValTypeGenI32(),
WasmEdge_ValTypeGenI32(),
WasmEdge_ValTypeGenI32()};
WasmEdge_ValType returnList[1] = {WasmEdge_ValTypeGenI32()};
WasmEdge_FunctionTypeContext* hostFuncType =
WasmEdge_FunctionTypeCreate(inputList, 5, returnList, 1);
WasmEdge_FunctionInstanceContext* hostFunc =
WasmEdge_FunctionInstanceCreate(
hostFuncType, getLedgerEntryField, hfs, 100);
// WasmEdge_FunctionTypeDelete(hostFuncType);
// WasmEdge_FunctionInstanceDelete(hostFunc);
WasmEdge_String fName =
WasmEdge_StringCreateByCString("getLedgerEntryField");
WasmEdge_ModuleInstanceAddFunction(hostMod, fName, hostFunc);
// WasmEdge_StringDelete(fName);
}
// getCurrentLedgerEntryField
{
WasmEdge_ValType inputList[2] = {
WasmEdge_ValTypeGenI32(), WasmEdge_ValTypeGenI32()};
WasmEdge_ValType returnList[1] = {WasmEdge_ValTypeGenI32()};
WasmEdge_FunctionTypeContext* hostFuncType =
WasmEdge_FunctionTypeCreate(inputList, 2, returnList, 1);
WasmEdge_FunctionInstanceContext* hostFunc =
WasmEdge_FunctionInstanceCreate(
hostFuncType, getCurrentLedgerEntryField, hfs, 100);
// WasmEdge_FunctionTypeDelete(hostFuncType);
// WasmEdge_FunctionInstanceDelete(hostFunc);
WasmEdge_String fName =
WasmEdge_StringCreateByCString("getCurrentLedgerEntryField");
WasmEdge_ModuleInstanceAddFunction(hostMod, fName, hostFunc);
// WasmEdge_StringDelete(fName);
}
// updateData
{
WasmEdge_ValType inputList[2] = {
WasmEdge_ValTypeGenI32(), WasmEdge_ValTypeGenI32()};
WasmEdge_FunctionTypeContext* hostFuncType =
WasmEdge_FunctionTypeCreate(inputList, 2, NULL, 0);
WasmEdge_FunctionInstanceContext* hostFunc =
WasmEdge_FunctionInstanceCreate(
hostFuncType, updateData, hfs, 100);
// WasmEdge_FunctionTypeDelete(hostFuncType);
// WasmEdge_FunctionInstanceDelete(hostFunc);
WasmEdge_String fName =
WasmEdge_StringCreateByCString("updateData");
WasmEdge_ModuleInstanceAddFunction(hostMod, fName, hostFunc);
// WasmEdge_StringDelete(fName);
}
// computeSha512HalfHash
{
WasmEdge_ValType inputList[2] = {
WasmEdge_ValTypeGenI32(), WasmEdge_ValTypeGenI32()};
WasmEdge_ValType returnList[1] = {WasmEdge_ValTypeGenI32()};
WasmEdge_FunctionTypeContext* hostFuncType =
WasmEdge_FunctionTypeCreate(inputList, 2, returnList, 1);
WasmEdge_FunctionInstanceContext* hostFunc =
WasmEdge_FunctionInstanceCreate(
hostFuncType, computeSha512HalfHash, hfs, 100);
// WasmEdge_FunctionTypeDelete(hostFuncType);
// WasmEdge_FunctionInstanceDelete(hostFunc);
WasmEdge_String fName =
WasmEdge_StringCreateByCString("computeSha512HalfHash");
WasmEdge_ModuleInstanceAddFunction(hostMod, fName, hostFunc);
// WasmEdge_StringDelete(fName);
}
// print
{
WasmEdge_ValType inputList[2] = {
WasmEdge_ValTypeGenI32(), WasmEdge_ValTypeGenI32()};
WasmEdge_FunctionTypeContext* hostFuncType =
WasmEdge_FunctionTypeCreate(inputList, 2, NULL, 0);
WasmEdge_FunctionInstanceContext* hostFunc =
WasmEdge_FunctionInstanceCreate(hostFuncType, print, hfs, 100);
// WasmEdge_FunctionTypeDelete(hostFuncType);
// WasmEdge_FunctionInstanceDelete(hostFunc);
WasmEdge_String fName = WasmEdge_StringCreateByCString("print");
WasmEdge_ModuleInstanceAddFunction(hostMod, fName, hostFunc);
// WasmEdge_StringDelete(fName);
}
WasmEdge_Result regRe =
WasmEdge_VMRegisterModuleFromImport(VMCxt, hostMod);
if (!WasmEdge_ResultOK(regRe))
{
printf("host func reg error\n");
return Unexpected<TER>(tecFAILED_PROCESSING);
}
}
WasmEdge_Result loadRes =
WasmEdge_VMLoadWasmFromBuffer(VMCxt, wasmCode.data(), wasmCode.size());
if (!WasmEdge_ResultOK(loadRes))
{
printf("load error, %p, %d\n", wasmCode.data(), wasmCode.size());
return Unexpected<TER>(tecFAILED_PROCESSING);
}
WasmEdge_Result validateRes = WasmEdge_VMValidate(VMCxt);
if (!WasmEdge_ResultOK(validateRes))
{
printf("validate error\n");
return Unexpected<TER>(tecFAILED_PROCESSING);
}
WasmEdge_Result instantiateRes = WasmEdge_VMInstantiate(VMCxt);
if (!WasmEdge_ResultOK(instantiateRes))
{
printf("instantiate error\n");
return Unexpected<TER>(tecFAILED_PROCESSING);
}
WasmEdge_Value funcReturns[1];
WasmEdge_String func = WasmEdge_StringCreateByCString(funcName.c_str());
WasmEdge_Result funcRes =
WasmEdge_VMExecute(VMCxt, func, NULL, 0, funcReturns, 1);
bool ok = WasmEdge_ResultOK(funcRes);
EscrowResult re;
if (ok)
{
auto sc = WasmEdge_VMGetStatisticsContext(VMCxt);
re.cost = WasmEdge_StatisticsGetTotalCost(sc);
// WasmEdge_StatisticsGetTotalCost, WasmEdge_StatisticsGetInstrCount
auto result = WasmEdge_ValueGetI32(funcReturns[0]);
if (result != 0)
re.result = true;
else
re.result = false;
}
else
{
printf("Error message: %s\n", WasmEdge_ResultGetMessage(funcRes));
}
WasmEdge_VMDelete(VMCxt);
WasmEdge_StringDelete(func);
// delete other obj allocated
if (ok)
return re;
else
return Unexpected<TER>(tecFAILED_PROCESSING);
}
} // namespace ripple

100
src/xrpld/app/misc/WasmVM.h Normal file
View File

@@ -0,0 +1,100 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2025 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_APP_MISC_WASMVM_H_INLCUDED
#define RIPPLE_APP_MISC_WASMVM_H_INLCUDED
#include <xrpl/basics/Expected.h>
#include <xrpl/protocol/TER.h>
#include "xrpl/basics/base_uint.h"
#include <wasmedge/wasmedge.h>
namespace ripple {
const uint32_t MAX_PAGES = 128; // 8MB = 64KB*128
typedef std::vector<uint8_t> Bytes;
typedef ripple::uint256 Hash;
template <typename T>
struct WasmResult
{
T result;
uint64_t cost;
};
typedef WasmResult<bool> EscrowResult;
struct HostFunctions
{
virtual int32_t
getLedgerSqn()
{
return 1;
}
virtual int32_t
getParentLedgerTime()
{
return 1;
}
virtual std::optional<Bytes>
getTxField(std::string const& fname)
{
return Bytes{};
}
virtual std::optional<Bytes>
getLedgerEntryField(
int32_t type,
Bytes const& kdata,
std::string const& fname)
{
return Bytes{};
}
virtual std::optional<Bytes>
getCurrentLedgerEntryField(std::string const& fname)
{
return Bytes{};
}
virtual bool
updateData(Bytes const& data)
{
return true;
}
virtual Hash
computeSha512HalfHash(Bytes const& data)
{
return Hash{};
}
virtual ~HostFunctions() = default;
};
Expected<EscrowResult, TER>
runEscrowWasm(
std::vector<uint8_t> const& wasmCode,
std::string const& funcName,
HostFunctions* hfs,
uint64_t gasLimit);
} // namespace ripple
#endif // RIPPLE_APP_MISC_WASMVM_H_INLCUDED

View File

@@ -130,6 +130,20 @@ Change::preclaim(PreclaimContext const& ctx)
ctx.tx.isFieldPresent(sfReserveIncrementDrops))
return temDISABLED;
}
if (ctx.view.rules().enabled(featureSmartEscrow))
{
if (!ctx.tx.isFieldPresent(sfExtensionComputeLimit) ||
!ctx.tx.isFieldPresent(sfExtensionSizeLimit) ||
!ctx.tx.isFieldPresent(sfGasPrice))
return temMALFORMED;
}
else
{
if (ctx.tx.isFieldPresent(sfExtensionComputeLimit) ||
ctx.tx.isFieldPresent(sfExtensionSizeLimit) ||
ctx.tx.isFieldPresent(sfGasPrice))
return temDISABLED;
}
return tesSUCCESS;
case ttAMENDMENT:
case ttUNL_MODIFY:
@@ -377,6 +391,12 @@ Change::applyFee()
set(feeObject, ctx_.tx, sfReserveBase);
set(feeObject, ctx_.tx, sfReserveIncrement);
}
if (view().rules().enabled(featureSmartEscrow))
{
set(feeObject, ctx_.tx, sfExtensionComputeLimit);
set(feeObject, ctx_.tx, sfExtensionSizeLimit);
set(feeObject, ctx_.tx, sfGasPrice);
}
view().update(feeObject);

View File

@@ -19,6 +19,8 @@
#include <xrpld/app/misc/CredentialHelpers.h>
#include <xrpld/app/misc/HashRouter.h>
#include <xrpld/app/misc/WasmHostFuncImpl.h>
#include <xrpld/app/misc/WasmVM.h>
#include <xrpld/app/tx/detail/Escrow.h>
#include <xrpld/conditions/Condition.h>
#include <xrpld/conditions/Fulfillment.h>
@@ -32,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.
@@ -82,9 +86,28 @@ EscrowCreate::makeTxConsequences(PreflightContext const& ctx)
return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()};
}
XRPAmount
EscrowCreate::calculateBaseFee(ReadView const& view, STTx const& tx)
{
XRPAmount txnFees{Transactor::calculateBaseFee(view, tx)};
if (tx.isFieldPresent(sfFinishFunction))
{
// TODO: make this fee increase based on the extra compute run
txnFees += 1000;
}
return txnFees;
}
NotTEC
EscrowCreate::preflight(PreflightContext const& ctx)
{
if (ctx.tx.isFieldPresent(sfFinishFunction) &&
!ctx.rules.enabled(featureSmartEscrow))
{
JLOG(ctx.j.debug()) << "SmartEscrow not enabled";
return temDISABLED;
}
if (ctx.rules.enabled(fix1543) && ctx.tx.getFlags() & tfUniversalMask)
return temINVALID_FLAG;
@@ -107,14 +130,23 @@ EscrowCreate::preflight(PreflightContext const& ctx)
ctx.tx[sfCancelAfter] <= ctx.tx[sfFinishAfter])
return temBAD_EXPIRATION;
if (ctx.tx.isFieldPresent(sfFinishFunction) &&
!ctx.tx.isFieldPresent(sfCancelAfter))
return temBAD_EXPIRATION;
if (ctx.rules.enabled(fix1571))
{
// In the absence of a FinishAfter, the escrow can be finished
// immediately, which can be confusing. When creating an escrow,
// we want to ensure that either a FinishAfter time is explicitly
// specified or a completion condition is attached.
if (!ctx.tx[~sfFinishAfter] && !ctx.tx[~sfCondition])
if (!ctx.tx[~sfFinishAfter] && !ctx.tx[~sfCondition] &&
!ctx.tx[~sfFinishFunction])
{
JLOG(ctx.j.debug()) << "Must have at least one of FinishAfter, "
"Condition, or FinishFunction.";
return temMALFORMED;
}
}
if (auto const cb = ctx.tx[~sfCondition])
@@ -139,6 +171,19 @@ EscrowCreate::preflight(PreflightContext const& ctx)
return temDISABLED;
}
if (ctx.tx.isFieldPresent(sfFinishFunction))
{
auto const code = ctx.tx.getFieldVL(sfFinishFunction);
if (code.size() == 0 ||
code.size() > ctx.app.config().FEES.extension_size_limit)
{
JLOG(ctx.j.debug())
<< "EscrowCreate.FinishFunction bad size " << code.size();
return temMALFORMED;
}
// TODO: add check to ensure this is valid WASM code
}
return preflight2(ctx);
}
@@ -239,6 +284,8 @@ EscrowCreate::doApply()
(*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
(*slep)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter];
(*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
(*slep)[~sfFinishFunction] = ctx_.tx[~sfFinishFunction];
(*slep)[~sfData] = ctx_.tx[~sfData];
ctx_.view().insert(slep);
@@ -262,8 +309,13 @@ EscrowCreate::doApply()
}
// Deduct owner's balance, increment owner count
// TODO: determine actual reserve based on FinishFunction size
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal);
adjustOwnerCount(
ctx_.view(),
sle,
ctx_.tx.isFieldPresent(sfFinishFunction) ? 2 : 1,
ctx_.journal);
ctx_.view().update(sle);
return tesSUCCESS;
@@ -299,6 +351,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;
@@ -308,7 +367,10 @@ EscrowFinish::preflight(PreflightContext const& ctx)
// If you specify a condition, then you must also specify
// a fulfillment.
if (static_cast<bool>(cb) != static_cast<bool>(fb))
{
JLOG(ctx.j.debug()) << "Condition != Fulfillment";
return temMALFORMED;
}
// Verify the transaction signature. If it doesn't work
// then don't do any more work.
@@ -337,6 +399,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;
@@ -352,7 +424,10 @@ EscrowFinish::calculateBaseFee(ReadView const& view, STTx const& tx)
{
extraFee += view.fees().base * (32 + (fb->size() / 16));
}
if (auto const allowance = tx[~sfComputationAllowance]; allowance)
{
extraFee += (*allowance) * view.fees().gasPrice / MICRO_DROPS_PER_DROP;
}
return Transactor::calculateBaseFee(view, tx) + extraFee;
}
@@ -362,6 +437,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;
@@ -375,7 +478,15 @@ 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
// Destination validity (after SmartEscrow is enabled)
// Condition/Fulfillment
// Destination validity (before SmartEscrow is enabled)
// FinishFunction
// If a cancel time is present, a finish operation should only succeed prior
// to that time. fix1571 corrects a logic error in the check that would make
@@ -386,11 +497,17 @@ EscrowFinish::doApply()
// Too soon: can't execute before the finish time
if ((*slep)[~sfFinishAfter] && !after(now, (*slep)[sfFinishAfter]))
{
JLOG(j_.debug()) << "Too soon";
return tecNO_PERMISSION;
}
// Too late: can't execute after the cancel time
if ((*slep)[~sfCancelAfter] && after(now, (*slep)[sfCancelAfter]))
{
JLOG(j_.debug()) << "Too late";
return tecNO_PERMISSION;
}
}
else
{
@@ -398,13 +515,35 @@ EscrowFinish::doApply()
if ((*slep)[~sfFinishAfter] &&
ctx_.view().info().parentCloseTime.time_since_epoch().count() <=
(*slep)[sfFinishAfter])
{
JLOG(j_.debug()) << "Too soon?";
return tecNO_PERMISSION;
}
// Too late?
if ((*slep)[~sfCancelAfter] &&
ctx_.view().info().parentCloseTime.time_since_epoch().count() <=
(*slep)[sfCancelAfter])
{
JLOG(j_.debug()) << "Too late?";
return tecNO_PERMISSION;
}
}
AccountID const destID = (*slep)[sfDestination];
auto const sled = ctx_.view().peek(keylet::account(destID));
if (ctx_.view().rules().enabled(featureSmartEscrow))
{
// NOTE: Escrow payments cannot be used to fund accounts.
if (!sled)
return tecNO_DST;
if (ctx_.view().rules().enabled(featureDepositAuth))
{
if (auto err = verifyDepositPreauth(ctx_, account_, destID, sled);
!isTesSuccess(err))
return err;
}
}
// Check cryptocondition fulfillment
@@ -454,17 +593,56 @@ EscrowFinish::doApply()
return tecCRYPTOCONDITION_ERROR;
}
// NOTE: Escrow payments cannot be used to fund accounts.
AccountID const destID = (*slep)[sfDestination];
auto const sled = ctx_.view().peek(keylet::account(destID));
if (!sled)
return tecNO_DST;
if (ctx_.view().rules().enabled(featureDepositAuth))
if (!ctx_.view().rules().enabled(featureSmartEscrow))
{
if (auto err = verifyDepositPreauth(ctx_, account_, destID, sled);
!isTesSuccess(err))
return err;
// NOTE: Escrow payments cannot be used to fund accounts.
if (!sled)
return tecNO_DST;
if (ctx_.view().rules().enabled(featureDepositAuth))
{
if (auto err = verifyDepositPreauth(ctx_, account_, destID, sled);
!isTesSuccess(err))
return err;
}
}
// Execute custom release function
if ((*slep)[~sfFinishFunction])
{
JLOG(j_.trace())
<< "The escrow has a finish function, running WASM code...";
// WASM execution
auto const wasmStr = slep->getFieldVL(sfFinishFunction);
std::vector<uint8_t> wasm(wasmStr.begin(), wasmStr.end());
std::string funcName("ready");
WasmHostFunctionsImpl ledgerDataProvider(ctx_, k);
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())
{
auto reValue = re.value().result;
JLOG(j_.debug()) << "WASM Success: " + std::to_string(reValue)
<< ", cost: " << re.value().cost;
if (!reValue)
{
// ctx_.view().update(slep);
return tecWASM_REJECTED;
}
}
else
{
JLOG(j_.debug()) << "WASM Failure: " + transHuman(re.error());
return re.error();
}
}
AccountID const account = (*slep)[sfAccount];
@@ -497,7 +675,11 @@ EscrowFinish::doApply()
// Adjust source owner count
auto const sle = ctx_.view().peek(keylet::account(account));
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
adjustOwnerCount(
ctx_.view(),
sle,
slep->isFieldPresent(sfFinishFunction) ? -2 : -1,
ctx_.journal);
ctx_.view().update(sle);
// Remove escrow from ledger
@@ -579,7 +761,11 @@ EscrowCancel::doApply()
// Transfer amount back to owner, decrement owner count
auto const sle = ctx_.view().peek(keylet::account(account));
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount];
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
adjustOwnerCount(
ctx_.view(),
sle,
slep->isFieldPresent(sfFinishFunction) ? -2 : -1,
ctx_.journal);
ctx_.view().update(sle);
// Remove escrow from ledger

View File

@@ -36,6 +36,9 @@ public:
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static NotTEC
preflight(PreflightContext const& ctx);

View File

@@ -831,6 +831,22 @@ removeExpiredCredentials(
}
}
static void
modifyWasmDataFields(
ApplyView& view,
std::vector<std::pair<uint256, Blob>> const& wasmObjects,
beast::Journal viewJ)
{
for (auto const& [index, data] : wasmObjects)
{
if (auto const sle = view.peek(keylet::escrow(index)))
{
sle->setFieldVL(sfData, data);
view.update(sle);
}
}
}
static void
removeDeletedTrustLines(
ApplyView& view,
@@ -973,6 +989,7 @@ Transactor::operator()()
else if (
(result == tecOVERSIZE) || (result == tecKILLED) ||
(result == tecINCOMPLETE) || (result == tecEXPIRED) ||
(result == tecWASM_REJECTED) ||
(isTecClaimHardFail(result, view().flags())))
{
JLOG(j_.trace()) << "reapplying because of " << transToken(result);
@@ -985,13 +1002,16 @@ Transactor::operator()()
std::vector<uint256> removedTrustLines;
std::vector<uint256> expiredNFTokenOffers;
std::vector<uint256> expiredCredentials;
std::vector<std::pair<uint256, Blob>> modifiedWasmObjects;
bool const doOffers =
((result == tecOVERSIZE) || (result == tecKILLED));
bool const doLines = (result == tecINCOMPLETE);
bool const doNFTokenOffers = (result == tecEXPIRED);
bool const doCredentials = (result == tecEXPIRED);
if (doOffers || doLines || doNFTokenOffers || doCredentials)
bool const doWasmData = (result == tecWASM_REJECTED);
if (doOffers || doLines || doNFTokenOffers || doCredentials ||
doWasmData)
{
ctx_.visit([doOffers,
&removedOffers,
@@ -1000,7 +1020,9 @@ Transactor::operator()()
doNFTokenOffers,
&expiredNFTokenOffers,
doCredentials,
&expiredCredentials](
&expiredCredentials,
doWasmData,
&modifiedWasmObjects](
uint256 const& index,
bool isDelete,
std::shared_ptr<SLE const> const& before,
@@ -1035,6 +1057,13 @@ Transactor::operator()()
(before->getType() == ltCREDENTIAL))
expiredCredentials.push_back(index);
}
if (doWasmData && before && after &&
(before->getType() == ltESCROW))
{
modifiedWasmObjects.push_back(
std::make_pair(index, after->getFieldVL(sfData)));
}
});
}
@@ -1064,6 +1093,10 @@ Transactor::operator()()
removeExpiredCredentials(
view(), expiredCredentials, ctx_.app.journal("View"));
if (result == tecWASM_REJECTED)
modifyWasmDataFields(
view(), modifiedWasmObjects, ctx_.app.journal("View"));
applied = isTecClaim(result);
}

View File

@@ -73,6 +73,15 @@ struct FeeSetup
/** The per-owned item reserve requirement in drops. */
XRPAmount owner_reserve{2 * DROPS_PER_XRP};
/** The compute limit for Feature Extensions. */
std::uint32_t extension_compute_limit{4294967295};
/** 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

@@ -1104,6 +1104,12 @@ setup_FeeVote(Section const& section)
setup.account_reserve = temp;
if (set(temp, "owner_reserve", section))
setup.owner_reserve = temp;
if (set(temp, "extension_compute_limit", section))
setup.extension_compute_limit = temp;
if (set(temp, "extension_size_limit", section))
setup.extension_size_limit = temp;
if (set(temp, "gas_price", section))
setup.gas_price = temp;
}
return setup;
}

View File

@@ -17,7 +17,6 @@
*/
//==============================================================================
#include <xrpld/app/main/Application.h>
#include <xrpld/ledger/ReadView.h>
#include <xrpld/rpc/Context.h>
#include <xrpld/rpc/detail/RPCHelpers.h>