mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Merge remote-tracking branch 'upstream/ripple/smart-escrow' into develop2
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -65,6 +65,7 @@ target_link_libraries(xrpl.imports.main
|
||||
xrpl.libpb
|
||||
xxHash::xxhash
|
||||
$<$<BOOL:${voidstar}>:antithesis-sdk-cpp>
|
||||
wasmedge::wasmedge
|
||||
)
|
||||
|
||||
include(add_module)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -78,6 +78,10 @@ STValidation::validationFormat()
|
||||
{sfBaseFeeDrops, soeOPTIONAL},
|
||||
{sfReserveBaseDrops, soeOPTIONAL},
|
||||
{sfReserveIncrementDrops, soeOPTIONAL},
|
||||
// featureSmartEscrow
|
||||
{sfExtensionComputeLimit, soeOPTIONAL},
|
||||
{sfExtensionSizeLimit, soeOPTIONAL},
|
||||
{sfGasPrice, soeOPTIONAL},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
2165
src/test/app/Wasm_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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 */
|
||||
/******************************************************************************/
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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())
|
||||
|
||||
118
src/xrpld/app/misc/WasmHostFuncImpl.cpp
Normal file
118
src/xrpld/app/misc/WasmHostFuncImpl.cpp
Normal 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
|
||||
69
src/xrpld/app/misc/WasmHostFuncImpl.h
Normal file
69
src/xrpld/app/misc/WasmHostFuncImpl.h
Normal 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
|
||||
505
src/xrpld/app/misc/WasmVM.cpp
Normal file
505
src/xrpld/app/misc/WasmVM.cpp
Normal 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
100
src/xrpld/app/misc/WasmVM.h
Normal 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
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.) */
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user