mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-19 18:45:52 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -344,6 +344,7 @@ enum TECcodes : TERUnderlyingType {
|
||||
tecARRAY_TOO_LARGE = 191,
|
||||
tecLOCKED = 192,
|
||||
tecBAD_CREDENTIALS = 193,
|
||||
tecWASM_REJECTED = 194,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -66,6 +66,9 @@ STValidation::validationFormat()
|
||||
{sfBaseFeeDrops, soeOPTIONAL},
|
||||
{sfReserveBaseDrops, soeOPTIONAL},
|
||||
{sfReserveIncrementDrops, soeOPTIONAL},
|
||||
// featureSmartEscrow
|
||||
{sfExtensionComputeLimit, soeOPTIONAL},
|
||||
{sfExtensionSizeLimit, soeOPTIONAL},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
@@ -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."),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
4752
src/test/app/Wasm_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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 */
|
||||
/******************************************************************************/
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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())
|
||||
|
||||
592
src/xrpld/app/misc/WasmVM.cpp
Normal file
592
src/xrpld/app/misc/WasmVM.cpp
Normal 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
|
||||
97
src/xrpld/app/misc/WasmVM.h
Normal file
97
src/xrpld/app/misc/WasmVM.h
Normal 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_;
|
||||
// };
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.) */
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user