PoC Smart Escrows (#5340)

* wasmedge in unittest

* add WashVM.h and cpp

* accountID comparison (vector<u8>) working

* json decode tx and ledger object with two buffers working

* wasm return a buffer working

* add a failure test case to P2P3

* host function return ledger sqn

* instruction gas and host function gas

* basics

* add scaffold

* add amendment check

* working PoC

* get test working

* fix clang-format

* prototype #2

* p2p3

* [WIP] P4

* P5

* add calculateBaseFee

* add FinishFunction preflight checks (+ tests)

* additional reserve for sfFinishFunction

* higher fees for EscrowFinish

* rename amendment to SmartEscrow

* make fee voting changes, add basic tests

* clean up

* clean up

* clean up

* more cleanup

* add subscribe tests

* add more tests

* undo formatting

* undo formatting

* remove bad comment

* more debugging statements

* fix clang-format

* fix rebase issues

* fix more rebase issues

* more rebase fixes

* add source code for wasm

* respond to comments

* add const

---------

Co-authored-by: Peng Wang <pwang200@gmail.com>
This commit is contained in:
Mayukha Vadari
2025-03-20 14:08:06 -04:00
committed by GitHub
parent 5bf3a308d5
commit b6a95f9970
33 changed files with 6339 additions and 40 deletions

View File

@@ -103,6 +103,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

@@ -1248,6 +1248,30 @@
# 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_compute_limit = 2000000 # 2 million 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

@@ -80,7 +80,7 @@ namespace detail {
// Feature.cpp. Because it's only used to reserve storage, and determine how
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
// the actual number of amendments. A LogicError on startup will verify this.
static constexpr std::size_t numFeatures = 88;
static constexpr std::size_t numFeatures = 89;
/** Amendments that this server supports and the default voting behavior.
Whether they are enabled depends on the Rules defined in the validated

View File

@@ -34,6 +34,9 @@ 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)
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

@@ -344,6 +344,7 @@ enum TECcodes : TERUnderlyingType {
tecARRAY_TOO_LARGE = 191,
tecLOCKED = 192,
tecBAD_CREDENTIALS = 193,
tecWASM_REJECTED = 194,
};
//------------------------------------------------------------------------------

View File

@@ -29,7 +29,7 @@
// If you add an amendment here, then do not forget to increment `numFeatures`
// in include/xrpl/protocol/Feature.h.
// Check flags in Credential transactions
XRPL_FEATURE(SmartEscrow, Supported::yes, VoteBehavior::DefaultNo)
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,11 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({
{sfBaseFeeDrops, soeOPTIONAL},
{sfReserveBaseDrops, soeOPTIONAL},
{sfReserveIncrementDrops, soeOPTIONAL},
// New fields
{sfExtensionComputeLimit, soeOPTIONAL},
{sfExtensionSizeLimit, soeOPTIONAL},
{sfPreviousTxnID, soeOPTIONAL},
{sfPreviousTxnLgrSeq, soeOPTIONAL},
}))
@@ -344,6 +349,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,8 @@ 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)
// 64-bit integers (common)
TYPED_SFIELD(sfIndexNext, UINT64, 1)
@@ -223,7 +225,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 +267,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. */
@@ -488,6 +490,8 @@ TRANSACTION(ttFEE, 101, SetFee, ({
{sfBaseFeeDrops, soeOPTIONAL},
{sfReserveBaseDrops, soeOPTIONAL},
{sfReserveIncrementDrops, soeOPTIONAL},
{sfExtensionComputeLimit, soeOPTIONAL},
{sfExtensionSizeLimit, soeOPTIONAL},
}))
/** This system-generated transaction type is used to update the network's negative UNL

View File

@@ -265,6 +265,8 @@ 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(fail_hard); // in: Sign, Submit
JSS(failed); // out: InboundLedger
JSS(feature); // in: Feature

View File

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

View File

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

View File

@@ -1519,7 +1519,7 @@ struct Escrow_test : public beast::unit_test::suite
Account const alice{"alice"};
Account const bob{"bob"};
Account const carol{"carol"};
Account const dillon{"dillon "};
Account const dillon{"dillon"};
Account const zelda{"zelda"};
const char credType[] = "abcde";
@@ -1656,6 +1656,370 @@ struct Escrow_test : public beast::unit_test::suite
}
}
void
testFinishFunctionPreflight()
{
testcase("Test preflight checks involving FinishFunction");
using namespace jtx;
using namespace std::chrono;
Account const alice{"alice"};
Account const carol{"carol"};
// Tests whether the ledger index is >= 5
// #[no_mangle]
// pub fn ready() -> bool {
// unsafe { host_lib::get_ledger_sqn() >= 5}
// }
static auto wasmHex =
"0061736d010000000105016000017f021b0108686f73745f6c69620e6765745f6c"
"65646765725f73716e0000030201000405017001010105030100100619037f0141"
"8080c0000b7f00418080c0000b7f00418080c0000b072d04066d656d6f72790200"
"05726561647900010a5f5f646174615f656e6403010b5f5f686561705f62617365"
"03020a0d010b0010808080800041044a0b006e046e616d65000e0d7761736d5f6c"
"69622e7761736d01430200395f5a4e387761736d5f6c696238686f73745f6c6962"
"31346765745f6c65646765725f73716e3137686663383539386237646539633036"
"64624501057265616479071201000f5f5f737461636b5f706f696e746572005509"
"70726f64756365727302086c616e6775616765010452757374000c70726f636573"
"7365642d62790105727573746325312e38332e302d6e696768746c792028633266"
"37346333663920323032342d30392d30392900490f7461726765745f6665617475"
"726573042b0a6d756c746976616c75652b0f6d757461626c652d676c6f62616c73"
"2b0f7265666572656e63652d74797065732b087369676e2d657874";
{
// featureSmartEscrow disabled
Env env(*this, supported_amendments() - featureSmartEscrow);
env.fund(XRP(5000), alice, carol);
XRPAmount const txnFees = env.current()->fees().base + 1000;
auto escrowCreate = escrow(alice, carol, XRP(1000));
env(escrowCreate,
finish_function(wasmHex),
cancel_time(env.now() + 100s),
fee(txnFees),
ter(temDISABLED));
env.close();
}
Env env(*this);
XRPAmount const txnFees = env.current()->fees().base + 1000;
// create escrow
env.fund(XRP(5000), alice, carol);
auto escrowCreate = escrow(alice, carol, XRP(1000));
// Success situations
{
// FinishFunction + CancelAfter
env(escrowCreate,
finish_function(wasmHex),
cancel_time(env.now() + 100s),
fee(txnFees));
env.close();
}
{
// FinishFunction + Condition + CancelAfter
env(escrowCreate,
finish_function(wasmHex),
cancel_time(env.now() + 100s),
condition(cb1),
fee(txnFees));
env.close();
}
{
// FinishFunction + FinishAfter + CancelAfter
env(escrowCreate,
finish_function(wasmHex),
cancel_time(env.now() + 100s),
finish_time(env.now() + 2s),
fee(txnFees));
env.close();
}
{
// FinishFunction + FinishAfter + Condition + CancelAfter
env(escrowCreate,
finish_function(wasmHex),
cancel_time(env.now() + 100s),
condition(cb1),
finish_time(env.now() + 2s),
fee(txnFees));
env.close();
}
// Failure situations (i.e. all other combinations)
{
// only FinishFunction
env(escrowCreate,
finish_function(wasmHex),
fee(txnFees),
ter(temBAD_EXPIRATION));
env.close();
}
{
// FinishFunction + FinishAfter
env(escrowCreate,
finish_function(wasmHex),
finish_time(env.now() + 2s),
fee(txnFees),
ter(temBAD_EXPIRATION));
env.close();
}
{
// FinishFunction + Condition
env(escrowCreate,
finish_function(wasmHex),
condition(cb1),
fee(txnFees),
ter(temBAD_EXPIRATION));
env.close();
}
{
// FinishFunction + FinishAfter + Condition
env(escrowCreate,
finish_function(wasmHex),
condition(cb1),
finish_time(env.now() + 2s),
fee(txnFees),
ter(temBAD_EXPIRATION));
env.close();
}
{
// FinishFunction 0 length
env(escrowCreate,
finish_function(""),
cancel_time(env.now() + 100s),
fee(txnFees),
ter(temMALFORMED));
env.close();
}
// {
// // FinishFunction > max length
// std::string longWasmHex = "00";
// // TODO: fix to use the config setting
// // TODO: make this test more efficient
// // uncomment when that's done
// for (int i = 0; i < 4294967295; i++)
// {
// longWasmHex += "11";
// }
// env(escrowCreate,
// finish_function(longWasmHex),
// cancel_time(env.now() + 100s),
// fee(txnFees),
// ter(temMALFORMED));
// env.close();
// }
}
void
testFinishFunction()
{
testcase("PoC escrow function");
using namespace jtx;
using namespace std::chrono;
Account const alice{"alice"};
Account const carol{"carol"};
// Tests whether the ledger index is >= 5
// #[no_mangle]
// pub fn ready() -> bool {
// unsafe { host_lib::get_ledger_sqn() >= 5}
// }
static auto wasmHex =
"0061736d010000000105016000017f021b0108686f73745f6c69620e6765745f6c"
"65646765725f73716e0000030201000405017001010105030100100619037f0141"
"8080c0000b7f00418080c0000b7f00418080c0000b072d04066d656d6f72790200"
"05726561647900010a5f5f646174615f656e6403010b5f5f686561705f62617365"
"03020a0d010b0010808080800041044a0b006e046e616d65000e0d7761736d5f6c"
"69622e7761736d01430200395f5a4e387761736d5f6c696238686f73745f6c6962"
"31346765745f6c65646765725f73716e3137686663383539386237646539633036"
"64624501057265616479071201000f5f5f737461636b5f706f696e746572005509"
"70726f64756365727302086c616e6775616765010452757374000c70726f636573"
"7365642d62790105727573746325312e38332e302d6e696768746c792028633266"
"37346333663920323032342d30392d30392900490f7461726765745f6665617475"
"726573042b0a6d756c746976616c75652b0f6d757461626c652d676c6f62616c73"
"2b0f7265666572656e63652d74797065732b087369676e2d657874";
{
// basic FinishFunction situation
Env env(*this);
// create escrow
env.fund(XRP(5000), alice, carol);
auto const seq = env.seq(alice);
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
auto escrowCreate = escrow(alice, carol, XRP(1000));
XRPAmount txnFees = env.current()->fees().base + 1000;
env(escrowCreate,
finish_function(wasmHex),
cancel_time(env.now() + 100s),
fee(txnFees));
env.close();
if (BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2))
{
env.require(balance(alice, XRP(4000) - txnFees));
env.require(balance(carol, XRP(5000)));
env(finish(carol, alice, seq),
fee(txnFees),
ter(tecWASM_REJECTED));
env(finish(alice, alice, seq),
fee(txnFees),
ter(tecWASM_REJECTED));
env(finish(alice, alice, seq),
fee(txnFees),
ter(tecWASM_REJECTED));
env(finish(carol, alice, seq),
fee(txnFees),
ter(tecWASM_REJECTED));
env(finish(carol, alice, seq),
fee(txnFees),
ter(tecWASM_REJECTED));
env.close();
env(finish(alice, alice, seq), fee(txnFees), ter(tesSUCCESS));
env.close();
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
}
}
{
// FinishFunction + Condition
Env env(*this);
// create escrow
env.fund(XRP(5000), alice, carol);
auto const seq = env.seq(alice);
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
auto escrowCreate = escrow(alice, carol, XRP(1000));
XRPAmount txnFees = env.current()->fees().base + 1000;
env(escrowCreate,
finish_function(wasmHex),
condition(cb1),
cancel_time(env.now() + 100s),
fee(txnFees));
env.close();
if (BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2))
{
env.require(balance(alice, XRP(4000) - txnFees));
env.require(balance(carol, XRP(5000)));
// no fulfillment provided, function fails
env(finish(carol, alice, seq),
fee(txnFees),
ter(tecCRYPTOCONDITION_ERROR));
// fulfillment provided, function fails
env(finish(carol, alice, seq),
condition(cb1),
fulfillment(fb1),
fee(txnFees),
ter(tecWASM_REJECTED));
env.close();
// no fulfillment provided, function succeeds
env(finish(alice, alice, seq),
fee(txnFees),
ter(tecCRYPTOCONDITION_ERROR));
// wrong fulfillment provided, function succeeds
env(finish(alice, alice, seq),
condition(cb1),
fulfillment(fb2),
fee(txnFees),
ter(tecCRYPTOCONDITION_ERROR));
// fulfillment provided, function succeeds, tx succeeds
env(finish(alice, alice, seq),
condition(cb1),
fulfillment(fb1),
fee(txnFees),
ter(tesSUCCESS));
env.close();
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
}
}
{
// FinishFunction + FinishAfter
Env env(*this);
// create escrow
env.fund(XRP(5000), alice, carol);
auto const seq = env.seq(alice);
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
auto escrowCreate = escrow(alice, carol, XRP(1000));
XRPAmount txnFees = env.current()->fees().base + 1000;
auto const ts = env.now() + 97s;
env(escrowCreate,
finish_function(wasmHex),
finish_time(ts),
cancel_time(env.now() + 1000s),
fee(txnFees));
env.close();
if (BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2))
{
env.require(balance(alice, XRP(4000) - txnFees));
env.require(balance(carol, XRP(5000)));
// finish time hasn't passed, function fails
env(finish(carol, alice, seq),
fee(txnFees + 1),
ter(tecNO_PERMISSION));
env.close();
// finish time hasn't passed, function succeeds
for (; env.now() < ts; env.close())
env(finish(carol, alice, seq),
fee(txnFees + 2),
ter(tecNO_PERMISSION));
env(finish(carol, alice, seq),
fee(txnFees + 1),
ter(tesSUCCESS));
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
}
}
{
// FinishFunction + FinishAfter #2
Env env(*this);
// create escrow
env.fund(XRP(5000), alice, carol);
auto const seq = env.seq(alice);
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
auto escrowCreate = escrow(alice, carol, XRP(1000));
XRPAmount txnFees = env.current()->fees().base + 1000;
env(escrowCreate,
finish_function(wasmHex),
finish_time(env.now() + 2s),
cancel_time(env.now() + 100s),
fee(txnFees));
// Don't close the ledger here
if (BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2))
{
env.require(balance(alice, XRP(4000) - txnFees));
env.require(balance(carol, XRP(5000)));
// finish time hasn't passed, function fails
env(finish(carol, alice, seq),
fee(txnFees),
ter(tecNO_PERMISSION));
env.close();
// finish time has passed, function fails
env(finish(carol, alice, seq),
fee(txnFees),
ter(tecWASM_REJECTED));
env.close();
// finish time has passed, function succeeds, tx succeeds
env(finish(carol, alice, seq), fee(txnFees), ter(tesSUCCESS));
env.close();
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
}
}
}
void
run() override
{
@@ -1671,6 +2035,8 @@ struct Escrow_test : public beast::unit_test::suite
testConsequences();
testEscrowWithTickets();
testCredentials();
testFinishFunctionPreflight();
testFinishFunction();
}
};

View File

@@ -37,36 +37,56 @@ 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);
}
{
Section config;
config.append(
{"reference_fee = 50",
"account_reserve = 1234567",
"owner_reserve = 1234"});
"owner_reserve = 1234",
"extension_compute_limit = 100",
"extension_size_limit = 200"});
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);
}
{
Section config;
config.append(
{"reference_fee = blah",
"account_reserve = yada",
"owner_reserve = foo"});
"owner_reserve = foo",
"extension_compute_limit = bar",
"extension_size_limit = baz"});
// 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);
}
{
Section config;
config.append(
{"reference_fee = -50",
"account_reserve = -1234567",
"owner_reserve = -1234"});
"owner_reserve = -1234",
"extension_compute_limit = -100",
"extension_size_limit = -200"});
// Illegal values are ignored, and the defaults left unchanged
auto setup = setup_FeeVote(config);
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
@@ -74,6 +94,11 @@ 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));
}
{
const auto big64 = std::to_string(
@@ -84,12 +109,20 @@ 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});
// 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);
}
}

View File

@@ -48,6 +48,11 @@ struct PseudoTx_test : public beast::unit_test::suite
obj[sfReserveIncrement] = 0;
obj[sfReferenceFeeUnits] = 0;
}
if (rules.enabled(featureSmartEscrow))
{
obj[sfExtensionComputeLimit] = 0;
obj[sfExtensionSizeLimit] = 0;
}
}));
res.emplace_back(STTx(ttAMENDMENT, [&](auto& obj) {
@@ -114,9 +119,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();
}

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

File diff suppressed because it is too large Load Diff

View File

@@ -343,6 +343,33 @@ 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_;
}
};
/* Payment Channel */
/******************************************************************************/

View File

@@ -36,6 +36,8 @@ setupConfigForUnitTests(Config& cfg)
cfg.FEES.reference_fee = 10;
cfg.FEES.account_reserve = XRP(200).value().xrp().drops();
cfg.FEES.owner_reserve = XRP(50).value().xrp().drops();
cfg.FEES.extension_compute_limit = 4294967295;
cfg.FEES.extension_size_limit = 4294967295;
// The Beta API (currently v2) is always available to tests
cfg.BETA_RPC_API = true;

View File

@@ -515,6 +515,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;
};
@@ -1298,14 +1314,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

@@ -230,6 +230,14 @@ 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;
}
rawInsert(sle);
}
@@ -621,6 +629,7 @@ Ledger::setup()
{
bool oldFees = false;
bool newFees = false;
bool extensionFees = false;
{
auto const baseFee = sle->at(~sfBaseFee);
auto const reserveBase = sle->at(~sfReserveBase);
@@ -638,6 +647,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) {
@@ -654,12 +664,32 @@ Ledger::setup()
assign(fees_.increment, reserveIncrementXRP);
newFees = baseFeeXRP || reserveBaseXRP || reserveIncrementXRP;
}
{
auto const extensionComputeLimit =
sle->at(~sfExtensionComputeLimit);
auto const extensionSizeLimit = sle->at(~sfExtensionSizeLimit);
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);
extensionFees = extensionComputeLimit || extensionSizeLimit;
}
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&)
@@ -678,15 +708,17 @@ 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);
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;
}
std::shared_ptr<SLE>

View File

@@ -29,10 +29,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_;
@@ -67,8 +67,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;
@@ -191,6 +192,32 @@ 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);
}
}
void
@@ -213,11 +240,19 @@ 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);
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())
@@ -246,7 +281,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))
{
@@ -277,6 +312,29 @@ 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);
}
}
// choose our positions
// TODO: Use structured binding once LLVM 16 is the minimum supported
@@ -285,11 +343,14 @@ 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 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)
{
JLOG(journal_.warn())
<< "We are voting for a fee change: " << baseFee.first << "/"
@@ -317,6 +378,11 @@ FeeVoteImpl::doVoting(
incReserveVote.current());
obj[sfReferenceFeeUnits] = Config::FEE_UNITS_DEPRECATED;
}
if (rules.enabled(featureSmartEscrow))
{
obj[sfExtensionComputeLimit] = extensionCompute.first;
obj[sfExtensionSizeLimit] = extensionSize.first;
}
});
uint256 txID = feeTx.getTransactionID();

View File

@@ -2262,6 +2262,15 @@ 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;
// NOTE Use MultiApiJson to publish two slightly different JSON objects
// for consumers supporting different API versions
MultiApiJson multiObj{jvObj};
@@ -2711,12 +2720,21 @@ 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::close_time] = Json::Value::UInt(
lpClosed->info().closeTime.time_since_epoch().count());
}
@@ -2726,6 +2744,12 @@ 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;
}
if (auto const closeOffset = app_.timeKeeper().closeOffset();
std::abs(closeOffset.count()) >= 60)
@@ -2911,6 +2935,13 @@ 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::txn_count] = Json::UInt(alpAccepted->size());
@@ -3947,6 +3978,12 @@ 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;
}
}
if ((mMode >= OperatingMode::SYNCING) && !isNeedNetworkLedger())

View File

@@ -0,0 +1,592 @@
//------------------------------------------------------------------------------
/*
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>
// WasmVM::WasmVM(beast::Journal j)
// : j_(j)
//{
// }
namespace ripple {
Expected<bool, TER>
runEscrowWasm(
std::vector<uint8_t> const& wasmCode,
std::string const& funcName,
int32_t input)
{
WasmEdge_VMContext* VMCxt = WasmEdge_VMCreate(NULL, NULL);
WasmEdge_Value Params[1] = {WasmEdge_ValueGenI32(input)};
WasmEdge_Value Returns[1];
WasmEdge_String FuncName = WasmEdge_StringCreateByCString(funcName.c_str());
WasmEdge_Result Res = WasmEdge_VMRunWasmFromBuffer(
VMCxt,
wasmCode.data(),
wasmCode.size(),
FuncName,
Params,
1,
Returns,
1);
bool ok = WasmEdge_ResultOK(Res);
bool re = false;
if (ok)
{
auto result = WasmEdge_ValueGetI32(Returns[0]);
// printf("Get the result: %d\n", result);
if (result != 0)
re = true;
}
else
{
printf("Error message: %s\n", WasmEdge_ResultGetMessage(Res));
}
WasmEdge_VMDelete(VMCxt);
WasmEdge_StringDelete(FuncName);
if (ok)
return re;
else
return Unexpected<TER>(tecFAILED_PROCESSING);
}
Expected<bool, TER>
runEscrowWasm(
std::vector<uint8_t> const& wasmCode,
std::string const& funcName,
std::vector<uint8_t> const& accountID)
{
auto dataLen = (int32_t)accountID.size();
// printf("accountID size: %d\n", dataLen);
WasmEdge_VMContext* VMCxt = WasmEdge_VMCreate(NULL, NULL);
WasmEdge_Value allocParams[1] = {WasmEdge_ValueGenI32(dataLen)};
WasmEdge_Value allocReturns[1];
WasmEdge_String allocFunc = WasmEdge_StringCreateByCString("allocate");
WasmEdge_Result allocRes = WasmEdge_VMRunWasmFromBuffer(
VMCxt,
wasmCode.data(),
wasmCode.size(),
allocFunc,
allocParams,
1,
allocReturns,
1);
bool ok = WasmEdge_ResultOK(allocRes);
bool re = false;
if (ok)
{
auto pointer = WasmEdge_ValueGetI32(allocReturns[0]);
// printf("Alloc pointer: %d\n", pointer);
const WasmEdge_ModuleInstanceContext* m =
WasmEdge_VMGetActiveModule(VMCxt);
WasmEdge_String mName = WasmEdge_StringCreateByCString("memory");
WasmEdge_MemoryInstanceContext* mi =
WasmEdge_ModuleInstanceFindMemory(m, mName);
WasmEdge_Result setRes = WasmEdge_MemoryInstanceSetData(
mi, accountID.data(), pointer, dataLen);
ok = WasmEdge_ResultOK(setRes);
if (ok)
{
// printf("Set data ok\n");
WasmEdge_Value params[2] = {
WasmEdge_ValueGenI32(pointer), WasmEdge_ValueGenI32(dataLen)};
WasmEdge_Value returns[1];
WasmEdge_String func =
WasmEdge_StringCreateByCString(funcName.c_str());
WasmEdge_Result funcRes =
WasmEdge_VMExecute(VMCxt, func, params, 2, returns, 1);
ok = WasmEdge_ResultOK(funcRes);
if (ok)
{
// printf("func ok\n");
re = (WasmEdge_ValueGetI32(returns[0]) == 1);
}
else
{
printf(
"Func message: %s\n", WasmEdge_ResultGetMessage(funcRes));
}
}
else
{
printf(
"Set error message: %s\n", WasmEdge_ResultGetMessage(setRes));
}
}
else
{
printf(
"Alloc error message: %s\n", WasmEdge_ResultGetMessage(allocRes));
}
WasmEdge_VMDelete(VMCxt);
// TODO free everything
// WasmEdge_StringDelete(FuncName);
if (ok)
{
// printf("runEscrowWasm ok, result %d\n", re);
return re;
}
else
return Unexpected<TER>(tecFAILED_PROCESSING);
}
Expected<bool, TER>
runEscrowWasm(
std::vector<uint8_t> const& wasmCode,
std::string const& funcName,
std::vector<uint8_t> const& escrow_tx_json_data,
std::vector<uint8_t> const& escrow_lo_json_data)
{
WasmEdge_VMContext* VMCxt = WasmEdge_VMCreate(NULL, NULL);
WasmEdge_Result loadRes =
WasmEdge_VMLoadWasmFromBuffer(VMCxt, wasmCode.data(), wasmCode.size());
if (!WasmEdge_ResultOK(loadRes))
{
printf("load error\n");
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);
}
auto wasmAlloc = [VMCxt](std::vector<uint8_t> const& data) -> int32_t {
auto dataLen = (int32_t)data.size();
WasmEdge_Value allocParams[1] = {WasmEdge_ValueGenI32(dataLen)};
WasmEdge_Value allocReturns[1];
WasmEdge_String allocFunc = WasmEdge_StringCreateByCString("allocate");
WasmEdge_Result allocRes = WasmEdge_VMExecute(
VMCxt, allocFunc, allocParams, 1, allocReturns, 1);
if (WasmEdge_ResultOK(allocRes))
{
auto pointer = WasmEdge_ValueGetI32(allocReturns[0]);
// printf("alloc ptr %d, len %d\n", pointer, dataLen);
const WasmEdge_ModuleInstanceContext* m =
WasmEdge_VMGetActiveModule(VMCxt);
WasmEdge_String mName = WasmEdge_StringCreateByCString("memory");
WasmEdge_MemoryInstanceContext* mi =
WasmEdge_ModuleInstanceFindMemory(m, mName);
WasmEdge_Result setRes = WasmEdge_MemoryInstanceSetData(
mi, data.data(), pointer, dataLen);
if (WasmEdge_ResultOK(setRes))
{
return pointer;
}
}
return 0;
};
auto tx_ptr = wasmAlloc(escrow_tx_json_data);
auto lo_ptr = wasmAlloc(escrow_lo_json_data);
if (tx_ptr == 0 || lo_ptr == 0)
{
printf("data error\n");
return Unexpected<TER>(tecFAILED_PROCESSING);
}
auto txLen = (int32_t)escrow_tx_json_data.size();
auto loLen = (int32_t)escrow_lo_json_data.size();
WasmEdge_Value params[4] = {
WasmEdge_ValueGenI32(tx_ptr),
WasmEdge_ValueGenI32(txLen),
WasmEdge_ValueGenI32(lo_ptr),
WasmEdge_ValueGenI32(loLen)};
WasmEdge_Value returns[1];
WasmEdge_String func = WasmEdge_StringCreateByCString(funcName.c_str());
WasmEdge_Result funcRes =
WasmEdge_VMExecute(VMCxt, func, params, 4, returns, 1);
if (WasmEdge_ResultOK(funcRes))
{
// printf("func ok\n");
return WasmEdge_ValueGetI32(returns[0]) == 1;
}
else
{
printf("Func message: %s\n", WasmEdge_ResultGetMessage(funcRes));
return Unexpected<TER>(tecFAILED_PROCESSING);
}
}
Expected<std::pair<bool, std::string>, TER>
runEscrowWasmP4(
std::vector<uint8_t> const& wasmCode,
std::string const& funcName,
std::vector<uint8_t> const& escrow_tx_json_data,
std::vector<uint8_t> const& escrow_lo_json_data)
{
WasmEdge_VMContext* VMCxt = WasmEdge_VMCreate(NULL, NULL);
WasmEdge_Result loadRes =
WasmEdge_VMLoadWasmFromBuffer(VMCxt, wasmCode.data(), wasmCode.size());
if (!WasmEdge_ResultOK(loadRes))
{
printf("load error\n");
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);
}
auto wasmAlloc = [VMCxt](std::vector<uint8_t> const& data) -> int32_t {
auto dataLen = (int32_t)data.size();
WasmEdge_Value allocParams[1] = {WasmEdge_ValueGenI32(dataLen)};
WasmEdge_Value allocReturns[1];
WasmEdge_String allocFunc = WasmEdge_StringCreateByCString("allocate");
WasmEdge_Result allocRes = WasmEdge_VMExecute(
VMCxt, allocFunc, allocParams, 1, allocReturns, 1);
if (WasmEdge_ResultOK(allocRes))
{
auto pointer = WasmEdge_ValueGetI32(allocReturns[0]);
// printf("alloc ptr %d, len %d\n", pointer, dataLen);
const WasmEdge_ModuleInstanceContext* m =
WasmEdge_VMGetActiveModule(VMCxt);
WasmEdge_String mName = WasmEdge_StringCreateByCString("memory");
WasmEdge_MemoryInstanceContext* mi =
WasmEdge_ModuleInstanceFindMemory(m, mName);
WasmEdge_Result setRes = WasmEdge_MemoryInstanceSetData(
mi, data.data(), pointer, dataLen);
if (WasmEdge_ResultOK(setRes))
{
return pointer;
}
}
return 0;
};
auto tx_ptr = wasmAlloc(escrow_tx_json_data);
auto lo_ptr = wasmAlloc(escrow_lo_json_data);
if (tx_ptr == 0 || lo_ptr == 0)
{
printf("data error\n");
return Unexpected<TER>(tecFAILED_PROCESSING);
}
auto txLen = (int32_t)escrow_tx_json_data.size();
auto loLen = (int32_t)escrow_lo_json_data.size();
WasmEdge_Value params[4] = {
WasmEdge_ValueGenI32(tx_ptr),
WasmEdge_ValueGenI32(txLen),
WasmEdge_ValueGenI32(lo_ptr),
WasmEdge_ValueGenI32(loLen)};
WasmEdge_Value returns[1];
WasmEdge_String func = WasmEdge_StringCreateByCString(funcName.c_str());
WasmEdge_Result funcRes =
WasmEdge_VMExecute(VMCxt, func, params, 4, returns, 1);
if (WasmEdge_ResultOK(funcRes))
{
auto pointer = WasmEdge_ValueGetI32(returns[0]);
const WasmEdge_ModuleInstanceContext* m =
WasmEdge_VMGetActiveModule(VMCxt);
WasmEdge_String mName = WasmEdge_StringCreateByCString("memory");
WasmEdge_MemoryInstanceContext* mi =
WasmEdge_ModuleInstanceFindMemory(m, mName);
uint8_t buff[9];
WasmEdge_Result getRes =
WasmEdge_MemoryInstanceGetData(mi, buff, pointer, 9);
if (!WasmEdge_ResultOK(getRes))
{
printf(
"re mem get message: %s\n", WasmEdge_ResultGetMessage(getRes));
return Unexpected<TER>(tecFAILED_PROCESSING);
}
auto flag = buff[0];
auto leToInt32 = [](const uint8_t* d) -> uint32_t {
uint32_t r = 0;
for (int i = 0; i < 4; ++i)
{
r |= static_cast<uint32_t>(d[i]) << (i * 8);
// printf("leToInt32 %d\n", r);
}
return r;
};
auto ret_pointer =
leToInt32(reinterpret_cast<const uint8_t*>(&buff[1]));
auto ret_len = leToInt32(reinterpret_cast<const uint8_t*>(&buff[5]));
// printf("re flag %d, ptr %d, len %d\n", flag, ret_pointer,
// ret_len);
std::vector<uint8_t> buff2(ret_len);
getRes = WasmEdge_MemoryInstanceGetData(
mi, buff2.data(), ret_pointer, ret_len);
if (!WasmEdge_ResultOK(getRes))
{
printf(
"re 2 mem get message: %s\n",
WasmEdge_ResultGetMessage(getRes));
return Unexpected<TER>(tecFAILED_PROCESSING);
}
std::string newData(buff2.begin(), buff2.end());
// free
WasmEdge_String freeFunc = WasmEdge_StringCreateByCString("deallocate");
WasmEdge_Value freeParams[2] = {
WasmEdge_ValueGenI32(ret_pointer), WasmEdge_ValueGenI32(ret_len)};
WasmEdge_Value freeReturns[0];
WasmEdge_VMExecute(VMCxt, freeFunc, freeParams, 2, freeReturns, 0);
// free pointer too, with len = 9 too
freeParams[0] = WasmEdge_ValueGenI32(pointer);
freeParams[1] = WasmEdge_ValueGenI32(9);
WasmEdge_VMExecute(VMCxt, freeFunc, freeParams, 2, freeReturns, 0);
return std::pair<bool, std::string>(flag == 1, newData);
}
else
{
printf("Func message: %s\n", WasmEdge_ResultGetMessage(funcRes));
return Unexpected<TER>(tecFAILED_PROCESSING);
}
}
WasmEdge_Result
get_ledger_sqn(
void* data,
const WasmEdge_CallingFrameContext*,
const WasmEdge_Value* In,
WasmEdge_Value* Out)
{
Out[0] =
WasmEdge_ValueGenI32(((LedgerDataProvider*)data)->get_ledger_sqn());
return WasmEdge_Result_Success;
}
Expected<bool, TER>
runEscrowWasm(
std::vector<uint8_t> const& wasmCode,
std::string const& funcName,
LedgerDataProvider* ledgerDataProvider)
{
WasmEdge_VMContext* VMCxt = WasmEdge_VMCreate(NULL, NULL);
{ // register host function
WasmEdge_ValType ReturnList[1] = {WasmEdge_ValTypeGenI32()};
WasmEdge_FunctionTypeContext* HostFType =
WasmEdge_FunctionTypeCreate(NULL, 0, ReturnList, 1);
WasmEdge_FunctionInstanceContext* HostFunc =
WasmEdge_FunctionInstanceCreate(
HostFType, get_ledger_sqn, ledgerDataProvider, 0);
WasmEdge_FunctionTypeDelete(HostFType);
WasmEdge_String HostName = WasmEdge_StringCreateByCString("host_lib");
WasmEdge_ModuleInstanceContext* HostMod =
WasmEdge_ModuleInstanceCreate(HostName);
WasmEdge_StringDelete(HostName);
WasmEdge_String HostFuncName =
WasmEdge_StringCreateByCString("get_ledger_sqn");
WasmEdge_ModuleInstanceAddFunction(HostMod, HostFuncName, HostFunc);
WasmEdge_StringDelete(HostFuncName);
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\n");
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);
bool re = false;
if (ok)
{
auto result = WasmEdge_ValueGetI32(funcReturns[0]);
if (result != 0)
re = true;
}
else
{
printf("Error message: %s\n", WasmEdge_ResultGetMessage(funcRes));
}
WasmEdge_VMDelete(VMCxt);
WasmEdge_StringDelete(func);
if (ok)
return re;
else
return Unexpected<TER>(tecFAILED_PROCESSING);
}
WasmEdge_Result
constInt(
void* data,
const WasmEdge_CallingFrameContext*,
const WasmEdge_Value* In,
WasmEdge_Value* Out)
{
Out[0] = WasmEdge_ValueGenI32(5);
return WasmEdge_Result_Success;
}
Expected<EscrowResultP6, TER>
runEscrowWasm(
std::vector<uint8_t> const& wasmCode,
std::string const& funcName,
int32_t gasLimit,
int32_t input)
{
WasmEdge_ConfigureContext* conf = WasmEdge_ConfigureCreate();
WasmEdge_ConfigureStatisticsSetInstructionCounting(conf, true);
WasmEdge_ConfigureStatisticsSetCostMeasuring(conf, true);
WasmEdge_VMContext* VMCxt = WasmEdge_VMCreate(conf, NULL);
WasmEdge_StatisticsContext* StatCxt =
WasmEdge_VMGetStatisticsContext(VMCxt);
WasmEdge_StatisticsSetCostLimit(StatCxt, gasLimit);
{ // register host function
WasmEdge_ValType ReturnList[1] = {WasmEdge_ValTypeGenI32()};
WasmEdge_FunctionTypeContext* HostFType =
WasmEdge_FunctionTypeCreate(NULL, 0, ReturnList, 1);
WasmEdge_FunctionInstanceContext* HostFunc =
WasmEdge_FunctionInstanceCreate(HostFType, constInt, nullptr, 100);
WasmEdge_FunctionTypeDelete(HostFType);
WasmEdge_String HostName = WasmEdge_StringCreateByCString("host_lib");
WasmEdge_ModuleInstanceContext* HostMod =
WasmEdge_ModuleInstanceCreate(HostName);
WasmEdge_StringDelete(HostName);
WasmEdge_String HostFuncName =
WasmEdge_StringCreateByCString("constInt");
WasmEdge_ModuleInstanceAddFunction(HostMod, HostFuncName, HostFunc);
WasmEdge_StringDelete(HostFuncName);
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\n");
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_String func = WasmEdge_StringCreateByCString(funcName.c_str());
WasmEdge_Value Params[1] = {WasmEdge_ValueGenI32(input)};
WasmEdge_Result funcRes =
WasmEdge_VMExecute(VMCxt, func, Params, 1, NULL, 0);
bool ok = WasmEdge_ResultOK(funcRes);
EscrowResultP6 re;
if (ok)
{
auto sc = WasmEdge_VMGetStatisticsContext(VMCxt);
re.cost = WasmEdge_StatisticsGetTotalCost(sc);
// WasmEdge_StatisticsGetTotalCost, WasmEdge_StatisticsGetInstrCount
re.result = true;
}
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

View File

@@ -0,0 +1,97 @@
//------------------------------------------------------------------------------
/*
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_WASMVM_H_INLCUDED
#define RIPPLE_APP_MISC_WASMVM_H_INLCUDED
#include <xrpl/basics/Expected.h>
// #include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/TER.h>
#include <wasmedge/wasmedge.h>
namespace ripple {
Expected<bool, TER>
runEscrowWasm(
std::vector<uint8_t> const& wasmCode,
std::string const& funcName,
int32_t input);
Expected<bool, TER>
runEscrowWasm(
std::vector<uint8_t> const& wasmCode,
std::string const& funcName,
std::vector<uint8_t> const& accountID);
Expected<bool, TER>
runEscrowWasm(
std::vector<uint8_t> const& wasmCode,
std::string const& funcName,
std::vector<uint8_t> const& escrow_tx_json_data,
std::vector<uint8_t> const& escrow_lo_json_data);
Expected<std::pair<bool, std::string>, TER>
runEscrowWasmP4(
std::vector<uint8_t> const& wasmCode,
std::string const& funcName,
std::vector<uint8_t> const& escrow_tx_json_data,
std::vector<uint8_t> const& escrow_lo_json_data);
struct LedgerDataProvider
{
virtual int32_t
get_ledger_sqn()
{
return 1;
}
virtual ~LedgerDataProvider() = default;
};
Expected<bool, TER>
runEscrowWasm(
std::vector<uint8_t> const& wasmCode,
std::string const& funcName,
LedgerDataProvider* ledgerDataProvider);
struct EscrowResultP6
{
bool result;
int32_t cost;
};
Expected<EscrowResultP6, TER>
runEscrowWasm(
std::vector<uint8_t> const& wasmCode,
std::string const& funcName,
int32_t gasLimit,
int32_t input);
} // namespace ripple
#endif // RIPPLE_APP_MISC_WASMVM_H_INLCUDED
// class WasmVM final
//{
// public:
// explicit WasmVM(beast::Journal j);
// ~WasmVM() = default;
//
// private:
// beast::Journal j_;
// };

View File

@@ -128,6 +128,18 @@ 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))
return temMALFORMED;
}
else
{
if (ctx.tx.isFieldPresent(sfExtensionComputeLimit) ||
ctx.tx.isFieldPresent(sfExtensionSizeLimit))
return temDISABLED;
}
return tesSUCCESS;
case ttAMENDMENT:
case ttUNL_MODIFY:
@@ -375,6 +387,11 @@ 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);
}
view().update(feeObject);

View File

@@ -21,6 +21,7 @@
#include <xrpld/app/misc/CredentialHelpers.h>
#include <xrpld/app/misc/HashRouter.h>
#include <xrpld/app/misc/WasmVM.h>
#include <xrpld/conditions/Condition.h>
#include <xrpld/conditions/Fulfillment.h>
#include <xrpld/ledger/ApplyView.h>
@@ -97,9 +98,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;
@@ -122,14 +142,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])
@@ -154,6 +183,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);
}
@@ -254,6 +296,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);
@@ -277,8 +321,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;
@@ -323,7 +372,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.
@@ -367,6 +419,7 @@ EscrowFinish::calculateBaseFee(ReadView const& view, STTx const& tx)
{
extraFee += view.fees().base * (32 + (fb->size() / 16));
}
// TODO: make this fee increase based on the extra compute run
return Transactor::calculateBaseFee(view, tx) + extraFee;
}
@@ -384,6 +437,22 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
return tesSUCCESS;
}
struct EscrowLedgerDataProvider : public LedgerDataProvider
{
ApplyView& view_;
public:
EscrowLedgerDataProvider(ApplyView& view) : view_(view)
{
}
int32_t
get_ledger_sqn() override
{
return (int32_t)view_.seq();
}
};
TER
EscrowFinish::doApply()
{
@@ -392,6 +461,13 @@ EscrowFinish::doApply()
if (!slep)
return 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
// a finish only succeed strictly after the cancel time.
@@ -401,11 +477,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
{
@@ -413,13 +495,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
@@ -469,17 +573,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");
auto const escrowTx =
ctx_.tx.getJson(JsonOptions::none).toStyledString();
auto const escrowObj =
slep->getJson(JsonOptions::none).toStyledString();
std::vector<uint8_t> escrowTxData(escrowTx.begin(), escrowTx.end());
std::vector<uint8_t> escrowObjData(escrowObj.begin(), escrowObj.end());
EscrowLedgerDataProvider ledgerDataProvider(ctx_.view());
auto re = runEscrowWasm(wasm, funcName, &ledgerDataProvider);
JLOG(j_.trace()) << "Escrow WASM ran";
if (re.has_value())
{
auto reValue = re.value();
JLOG(j_.debug()) << "WASM Success: " + std::to_string(reValue);
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];
@@ -512,7 +655,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
@@ -594,7 +741,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

@@ -80,6 +80,12 @@ 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};
/* (Remember to update the example cfg files when changing any of these
* values.) */
};

View File

@@ -1102,6 +1102,10 @@ 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;
}
return setup;
}