mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-21 19:45:53 +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(nudb REQUIRED)
|
||||||
find_package(date REQUIRED)
|
find_package(date REQUIRED)
|
||||||
find_package(xxHash REQUIRED)
|
find_package(xxHash REQUIRED)
|
||||||
|
find_package(wasmedge REQUIRED)
|
||||||
|
|
||||||
target_link_libraries(ripple_libs INTERFACE
|
target_link_libraries(ripple_libs INTERFACE
|
||||||
ed25519::ed25519
|
ed25519::ed25519
|
||||||
|
|||||||
@@ -1248,6 +1248,30 @@
|
|||||||
# Example:
|
# Example:
|
||||||
# owner_reserve = 2000000 # 2 XRP
|
# 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
|
# 9. Misc Settings
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ target_link_libraries(xrpl.imports.main
|
|||||||
xrpl.libpb
|
xrpl.libpb
|
||||||
xxHash::xxhash
|
xxHash::xxhash
|
||||||
$<$<BOOL:${voidstar}>:antithesis-sdk-cpp>
|
$<$<BOOL:${voidstar}>:antithesis-sdk-cpp>
|
||||||
|
wasmedge::wasmedge
|
||||||
)
|
)
|
||||||
|
|
||||||
include(add_module)
|
include(add_module)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from conan import ConanFile
|
|||||||
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
|
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
class Xrpl(ConanFile):
|
class Xrpl(ConanFile):
|
||||||
name = 'xrpl'
|
name = 'xrpl'
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ class Xrpl(ConanFile):
|
|||||||
'soci/4.0.3',
|
'soci/4.0.3',
|
||||||
'xxhash/0.8.2',
|
'xxhash/0.8.2',
|
||||||
'zlib/1.3.1',
|
'zlib/1.3.1',
|
||||||
|
'wasmedge/0.14.1',
|
||||||
]
|
]
|
||||||
|
|
||||||
tool_requires = [
|
tool_requires = [
|
||||||
@@ -125,6 +127,7 @@ class Xrpl(ConanFile):
|
|||||||
self.folders.generators = 'build/generators'
|
self.folders.generators = 'build/generators'
|
||||||
|
|
||||||
generators = 'CMakeDeps'
|
generators = 'CMakeDeps'
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
tc = CMakeToolchain(self)
|
tc = CMakeToolchain(self)
|
||||||
tc.variables['tests'] = self.options.tests
|
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
|
// 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
|
// 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.
|
// 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.
|
/** Amendments that this server supports and the default voting behavior.
|
||||||
Whether they are enabled depends on the Rules defined in the validated
|
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 base{0}; // Reference tx cost (drops)
|
||||||
XRPAmount reserve{0}; // Reserve base (drops)
|
XRPAmount reserve{0}; // Reserve base (drops)
|
||||||
XRPAmount increment{0}; // Reserve increment (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;
|
explicit Fees() = default;
|
||||||
Fees(Fees const&) = default;
|
Fees(Fees const&) = default;
|
||||||
|
|||||||
@@ -230,6 +230,12 @@ page(Keylet const& root, std::uint64_t index = 0) noexcept
|
|||||||
Keylet
|
Keylet
|
||||||
escrow(AccountID const& src, std::uint32_t seq) noexcept;
|
escrow(AccountID const& src, std::uint32_t seq) noexcept;
|
||||||
|
|
||||||
|
inline Keylet
|
||||||
|
escrow(uint256 const& key) noexcept
|
||||||
|
{
|
||||||
|
return {ltESCROW, key};
|
||||||
|
}
|
||||||
|
|
||||||
/** A PaymentChannel */
|
/** A PaymentChannel */
|
||||||
Keylet
|
Keylet
|
||||||
payChan(AccountID const& src, AccountID const& dst, std::uint32_t seq) noexcept;
|
payChan(AccountID const& src, AccountID const& dst, std::uint32_t seq) noexcept;
|
||||||
|
|||||||
@@ -344,6 +344,7 @@ enum TECcodes : TERUnderlyingType {
|
|||||||
tecARRAY_TOO_LARGE = 191,
|
tecARRAY_TOO_LARGE = 191,
|
||||||
tecLOCKED = 192,
|
tecLOCKED = 192,
|
||||||
tecBAD_CREDENTIALS = 193,
|
tecBAD_CREDENTIALS = 193,
|
||||||
|
tecWASM_REJECTED = 194,
|
||||||
};
|
};
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||||
// in include/xrpl/protocol/Feature.h.
|
// 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 (InvalidTxFlags, Supported::yes, VoteBehavior::DefaultNo)
|
||||||
XRPL_FIX (FrozenLPTokenTransfer, Supported::yes, VoteBehavior::DefaultNo)
|
XRPL_FIX (FrozenLPTokenTransfer, Supported::yes, VoteBehavior::DefaultNo)
|
||||||
XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo)
|
XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo)
|
||||||
|
|||||||
@@ -315,6 +315,11 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({
|
|||||||
{sfBaseFeeDrops, soeOPTIONAL},
|
{sfBaseFeeDrops, soeOPTIONAL},
|
||||||
{sfReserveBaseDrops, soeOPTIONAL},
|
{sfReserveBaseDrops, soeOPTIONAL},
|
||||||
{sfReserveIncrementDrops, soeOPTIONAL},
|
{sfReserveIncrementDrops, soeOPTIONAL},
|
||||||
|
|
||||||
|
// New fields
|
||||||
|
{sfExtensionComputeLimit, soeOPTIONAL},
|
||||||
|
{sfExtensionSizeLimit, soeOPTIONAL},
|
||||||
|
|
||||||
{sfPreviousTxnID, soeOPTIONAL},
|
{sfPreviousTxnID, soeOPTIONAL},
|
||||||
{sfPreviousTxnLgrSeq, soeOPTIONAL},
|
{sfPreviousTxnLgrSeq, soeOPTIONAL},
|
||||||
}))
|
}))
|
||||||
@@ -344,6 +349,8 @@ LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({
|
|||||||
{sfCondition, soeOPTIONAL},
|
{sfCondition, soeOPTIONAL},
|
||||||
{sfCancelAfter, soeOPTIONAL},
|
{sfCancelAfter, soeOPTIONAL},
|
||||||
{sfFinishAfter, soeOPTIONAL},
|
{sfFinishAfter, soeOPTIONAL},
|
||||||
|
{sfFinishFunction, soeOPTIONAL},
|
||||||
|
{sfData, soeOPTIONAL},
|
||||||
{sfSourceTag, soeOPTIONAL},
|
{sfSourceTag, soeOPTIONAL},
|
||||||
{sfDestinationTag, soeOPTIONAL},
|
{sfDestinationTag, soeOPTIONAL},
|
||||||
{sfOwnerNode, soeREQUIRED},
|
{sfOwnerNode, soeREQUIRED},
|
||||||
|
|||||||
@@ -112,6 +112,8 @@ TYPED_SFIELD(sfEmitGeneration, UINT32, 46)
|
|||||||
TYPED_SFIELD(sfVoteWeight, UINT32, 48)
|
TYPED_SFIELD(sfVoteWeight, UINT32, 48)
|
||||||
TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50)
|
TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50)
|
||||||
TYPED_SFIELD(sfOracleDocumentID, UINT32, 51)
|
TYPED_SFIELD(sfOracleDocumentID, UINT32, 51)
|
||||||
|
TYPED_SFIELD(sfExtensionComputeLimit, UINT32, 52)
|
||||||
|
TYPED_SFIELD(sfExtensionSizeLimit, UINT32, 53)
|
||||||
|
|
||||||
// 64-bit integers (common)
|
// 64-bit integers (common)
|
||||||
TYPED_SFIELD(sfIndexNext, UINT64, 1)
|
TYPED_SFIELD(sfIndexNext, UINT64, 1)
|
||||||
@@ -223,7 +225,7 @@ TYPED_SFIELD(sfBaseFeeDrops, AMOUNT, 22)
|
|||||||
TYPED_SFIELD(sfReserveBaseDrops, AMOUNT, 23)
|
TYPED_SFIELD(sfReserveBaseDrops, AMOUNT, 23)
|
||||||
TYPED_SFIELD(sfReserveIncrementDrops, AMOUNT, 24)
|
TYPED_SFIELD(sfReserveIncrementDrops, AMOUNT, 24)
|
||||||
|
|
||||||
// currency amount (AMM)
|
// currency amount (more)
|
||||||
TYPED_SFIELD(sfLPTokenOut, AMOUNT, 25)
|
TYPED_SFIELD(sfLPTokenOut, AMOUNT, 25)
|
||||||
TYPED_SFIELD(sfLPTokenIn, AMOUNT, 26)
|
TYPED_SFIELD(sfLPTokenIn, AMOUNT, 26)
|
||||||
TYPED_SFIELD(sfEPrice, AMOUNT, 27)
|
TYPED_SFIELD(sfEPrice, AMOUNT, 27)
|
||||||
@@ -265,6 +267,7 @@ TYPED_SFIELD(sfAssetClass, VL, 28)
|
|||||||
TYPED_SFIELD(sfProvider, VL, 29)
|
TYPED_SFIELD(sfProvider, VL, 29)
|
||||||
TYPED_SFIELD(sfMPTokenMetadata, VL, 30)
|
TYPED_SFIELD(sfMPTokenMetadata, VL, 30)
|
||||||
TYPED_SFIELD(sfCredentialType, VL, 31)
|
TYPED_SFIELD(sfCredentialType, VL, 31)
|
||||||
|
TYPED_SFIELD(sfFinishFunction, VL, 32)
|
||||||
|
|
||||||
// account (common)
|
// account (common)
|
||||||
TYPED_SFIELD(sfAccount, ACCOUNT, 1)
|
TYPED_SFIELD(sfAccount, ACCOUNT, 1)
|
||||||
|
|||||||
@@ -42,12 +42,14 @@ TRANSACTION(ttPAYMENT, 0, Payment, ({
|
|||||||
|
|
||||||
/** This transaction type creates an escrow object. */
|
/** This transaction type creates an escrow object. */
|
||||||
TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, ({
|
TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, ({
|
||||||
{sfDestination, soeREQUIRED},
|
|
||||||
{sfAmount, soeREQUIRED},
|
{sfAmount, soeREQUIRED},
|
||||||
|
{sfDestination, soeREQUIRED},
|
||||||
|
{sfDestinationTag, soeOPTIONAL},
|
||||||
{sfCondition, soeOPTIONAL},
|
{sfCondition, soeOPTIONAL},
|
||||||
{sfCancelAfter, soeOPTIONAL},
|
{sfCancelAfter, soeOPTIONAL},
|
||||||
{sfFinishAfter, soeOPTIONAL},
|
{sfFinishAfter, soeOPTIONAL},
|
||||||
{sfDestinationTag, soeOPTIONAL},
|
{sfFinishFunction, soeOPTIONAL},
|
||||||
|
{sfData, soeOPTIONAL},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type completes an existing escrow. */
|
/** This transaction type completes an existing escrow. */
|
||||||
@@ -488,6 +490,8 @@ TRANSACTION(ttFEE, 101, SetFee, ({
|
|||||||
{sfBaseFeeDrops, soeOPTIONAL},
|
{sfBaseFeeDrops, soeOPTIONAL},
|
||||||
{sfReserveBaseDrops, soeOPTIONAL},
|
{sfReserveBaseDrops, soeOPTIONAL},
|
||||||
{sfReserveIncrementDrops, soeOPTIONAL},
|
{sfReserveIncrementDrops, soeOPTIONAL},
|
||||||
|
{sfExtensionComputeLimit, soeOPTIONAL},
|
||||||
|
{sfExtensionSizeLimit, soeOPTIONAL},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/** This system-generated transaction type is used to update the network's negative UNL
|
/** 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(expected_ledger_size); // out: TxQ
|
||||||
JSS(expiration); // out: AccountOffers, AccountChannels,
|
JSS(expiration); // out: AccountOffers, AccountChannels,
|
||||||
// ValidatorList, amm_info
|
// ValidatorList, amm_info
|
||||||
|
JSS(extension_compute); // out: NetworkOps
|
||||||
|
JSS(extension_size); // out: NetworkOps
|
||||||
JSS(fail_hard); // in: Sign, Submit
|
JSS(fail_hard); // in: Sign, Submit
|
||||||
JSS(failed); // out: InboundLedger
|
JSS(failed); // out: InboundLedger
|
||||||
JSS(feature); // in: Feature
|
JSS(feature); // in: Feature
|
||||||
|
|||||||
@@ -66,6 +66,9 @@ STValidation::validationFormat()
|
|||||||
{sfBaseFeeDrops, soeOPTIONAL},
|
{sfBaseFeeDrops, soeOPTIONAL},
|
||||||
{sfReserveBaseDrops, soeOPTIONAL},
|
{sfReserveBaseDrops, soeOPTIONAL},
|
||||||
{sfReserveIncrementDrops, soeOPTIONAL},
|
{sfReserveIncrementDrops, soeOPTIONAL},
|
||||||
|
// featureSmartEscrow
|
||||||
|
{sfExtensionComputeLimit, soeOPTIONAL},
|
||||||
|
{sfExtensionSizeLimit, soeOPTIONAL},
|
||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ transResults()
|
|||||||
MAKE_ERROR(tecARRAY_TOO_LARGE, "Array is too large."),
|
MAKE_ERROR(tecARRAY_TOO_LARGE, "Array is too large."),
|
||||||
MAKE_ERROR(tecLOCKED, "Fund is locked."),
|
MAKE_ERROR(tecLOCKED, "Fund is locked."),
|
||||||
MAKE_ERROR(tecBAD_CREDENTIALS, "Bad credentials."),
|
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(tefALREADY, "The exact transaction was already in this ledger."),
|
||||||
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
|
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 alice{"alice"};
|
||||||
Account const bob{"bob"};
|
Account const bob{"bob"};
|
||||||
Account const carol{"carol"};
|
Account const carol{"carol"};
|
||||||
Account const dillon{"dillon "};
|
Account const dillon{"dillon"};
|
||||||
Account const zelda{"zelda"};
|
Account const zelda{"zelda"};
|
||||||
|
|
||||||
const char credType[] = "abcde";
|
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
|
void
|
||||||
run() override
|
run() override
|
||||||
{
|
{
|
||||||
@@ -1671,6 +2035,8 @@ struct Escrow_test : public beast::unit_test::suite
|
|||||||
testConsequences();
|
testConsequences();
|
||||||
testEscrowWithTickets();
|
testEscrowWithTickets();
|
||||||
testCredentials();
|
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.reference_fee == defaultSetup.reference_fee);
|
||||||
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
|
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
|
||||||
BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_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;
|
Section config;
|
||||||
config.append(
|
config.append(
|
||||||
{"reference_fee = 50",
|
{"reference_fee = 50",
|
||||||
"account_reserve = 1234567",
|
"account_reserve = 1234567",
|
||||||
"owner_reserve = 1234"});
|
"owner_reserve = 1234",
|
||||||
|
"extension_compute_limit = 100",
|
||||||
|
"extension_size_limit = 200"});
|
||||||
auto setup = setup_FeeVote(config);
|
auto setup = setup_FeeVote(config);
|
||||||
BEAST_EXPECT(setup.reference_fee == 50);
|
BEAST_EXPECT(setup.reference_fee == 50);
|
||||||
BEAST_EXPECT(setup.account_reserve == 1234567);
|
BEAST_EXPECT(setup.account_reserve == 1234567);
|
||||||
BEAST_EXPECT(setup.owner_reserve == 1234);
|
BEAST_EXPECT(setup.owner_reserve == 1234);
|
||||||
|
BEAST_EXPECT(setup.extension_compute_limit == 100);
|
||||||
|
BEAST_EXPECT(setup.extension_size_limit == 200);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
Section config;
|
Section config;
|
||||||
config.append(
|
config.append(
|
||||||
{"reference_fee = blah",
|
{"reference_fee = blah",
|
||||||
"account_reserve = yada",
|
"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
|
// Illegal values are ignored, and the defaults left unchanged
|
||||||
auto setup = setup_FeeVote(config);
|
auto setup = setup_FeeVote(config);
|
||||||
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
|
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
|
||||||
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
|
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
|
||||||
BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_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;
|
Section config;
|
||||||
config.append(
|
config.append(
|
||||||
{"reference_fee = -50",
|
{"reference_fee = -50",
|
||||||
"account_reserve = -1234567",
|
"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
|
// Illegal values are ignored, and the defaults left unchanged
|
||||||
auto setup = setup_FeeVote(config);
|
auto setup = setup_FeeVote(config);
|
||||||
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
|
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));
|
setup.account_reserve == static_cast<std::uint32_t>(-1234567));
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
setup.owner_reserve == static_cast<std::uint32_t>(-1234));
|
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(
|
const auto big64 = std::to_string(
|
||||||
@@ -84,12 +109,20 @@ class FeeVote_test : public beast::unit_test::suite
|
|||||||
config.append(
|
config.append(
|
||||||
{"reference_fee = " + big64,
|
{"reference_fee = " + big64,
|
||||||
"account_reserve = " + 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
|
// Illegal values are ignored, and the defaults left unchanged
|
||||||
auto setup = setup_FeeVote(config);
|
auto setup = setup_FeeVote(config);
|
||||||
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
|
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
|
||||||
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
|
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
|
||||||
BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_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[sfReserveIncrement] = 0;
|
||||||
obj[sfReferenceFeeUnits] = 0;
|
obj[sfReferenceFeeUnits] = 0;
|
||||||
}
|
}
|
||||||
|
if (rules.enabled(featureSmartEscrow))
|
||||||
|
{
|
||||||
|
obj[sfExtensionComputeLimit] = 0;
|
||||||
|
obj[sfExtensionSizeLimit] = 0;
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
res.emplace_back(STTx(ttAMENDMENT, [&](auto& obj) {
|
res.emplace_back(STTx(ttAMENDMENT, [&](auto& obj) {
|
||||||
@@ -114,9 +119,10 @@ struct PseudoTx_test : public beast::unit_test::suite
|
|||||||
{
|
{
|
||||||
using namespace test::jtx;
|
using namespace test::jtx;
|
||||||
FeatureBitset const all{supported_amendments()};
|
FeatureBitset const all{supported_amendments()};
|
||||||
FeatureBitset const xrpFees{featureXRPFees};
|
|
||||||
|
|
||||||
|
testPrevented(all - featureXRPFees - featureSmartEscrow);
|
||||||
testPrevented(all - featureXRPFees);
|
testPrevented(all - featureXRPFees);
|
||||||
|
testPrevented(all - featureSmartEscrow);
|
||||||
testPrevented(all);
|
testPrevented(all);
|
||||||
testAllowed();
|
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 */
|
/* Payment Channel */
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ setupConfigForUnitTests(Config& cfg)
|
|||||||
cfg.FEES.reference_fee = 10;
|
cfg.FEES.reference_fee = 10;
|
||||||
cfg.FEES.account_reserve = XRP(200).value().xrp().drops();
|
cfg.FEES.account_reserve = XRP(200).value().xrp().drops();
|
||||||
cfg.FEES.owner_reserve = XRP(50).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
|
// The Beta API (currently v2) is always available to tests
|
||||||
cfg.BETA_RPC_API = true;
|
cfg.BETA_RPC_API = true;
|
||||||
|
|||||||
@@ -515,6 +515,22 @@ public:
|
|||||||
if (jv.isMember(jss::reserve_inc) != isFlagLedger)
|
if (jv.isMember(jss::reserve_inc) != isFlagLedger)
|
||||||
return false;
|
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;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1298,14 +1314,14 @@ public:
|
|||||||
{
|
{
|
||||||
using namespace test::jtx;
|
using namespace test::jtx;
|
||||||
FeatureBitset const all{supported_amendments()};
|
FeatureBitset const all{supported_amendments()};
|
||||||
FeatureBitset const xrpFees{featureXRPFees};
|
|
||||||
|
|
||||||
testServer();
|
testServer();
|
||||||
testLedger();
|
testLedger();
|
||||||
testTransactions_APIv1();
|
testTransactions_APIv1();
|
||||||
testTransactions_APIv2();
|
testTransactions_APIv2();
|
||||||
testManifests();
|
testManifests();
|
||||||
testValidations(all - xrpFees);
|
testValidations(all - featureXRPFees - featureSmartEscrow);
|
||||||
|
testValidations(all - featureSmartEscrow);
|
||||||
testValidations(all);
|
testValidations(all);
|
||||||
testSubErrors(true);
|
testSubErrors(true);
|
||||||
testSubErrors(false);
|
testSubErrors(false);
|
||||||
|
|||||||
@@ -230,6 +230,14 @@ Ledger::Ledger(
|
|||||||
sle->at(sfReserveIncrement) = *f;
|
sle->at(sfReserveIncrement) = *f;
|
||||||
sle->at(sfReferenceFeeUnits) = Config::FEE_UNITS_DEPRECATED;
|
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);
|
rawInsert(sle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -621,6 +629,7 @@ Ledger::setup()
|
|||||||
{
|
{
|
||||||
bool oldFees = false;
|
bool oldFees = false;
|
||||||
bool newFees = false;
|
bool newFees = false;
|
||||||
|
bool extensionFees = false;
|
||||||
{
|
{
|
||||||
auto const baseFee = sle->at(~sfBaseFee);
|
auto const baseFee = sle->at(~sfBaseFee);
|
||||||
auto const reserveBase = sle->at(~sfReserveBase);
|
auto const reserveBase = sle->at(~sfReserveBase);
|
||||||
@@ -638,6 +647,7 @@ Ledger::setup()
|
|||||||
auto const reserveBaseXRP = sle->at(~sfReserveBaseDrops);
|
auto const reserveBaseXRP = sle->at(~sfReserveBaseDrops);
|
||||||
auto const reserveIncrementXRP =
|
auto const reserveIncrementXRP =
|
||||||
sle->at(~sfReserveIncrementDrops);
|
sle->at(~sfReserveIncrementDrops);
|
||||||
|
|
||||||
auto assign = [&ret](
|
auto assign = [&ret](
|
||||||
XRPAmount& dest,
|
XRPAmount& dest,
|
||||||
std::optional<STAmount> const& src) {
|
std::optional<STAmount> const& src) {
|
||||||
@@ -654,12 +664,32 @@ Ledger::setup()
|
|||||||
assign(fees_.increment, reserveIncrementXRP);
|
assign(fees_.increment, reserveIncrementXRP);
|
||||||
newFees = baseFeeXRP || reserveBaseXRP || 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)
|
if (oldFees && newFees)
|
||||||
// Should be all of one or the other, but not both
|
// Should be all of one or the other, but not both
|
||||||
ret = false;
|
ret = false;
|
||||||
if (!rules_.enabled(featureXRPFees) && newFees)
|
if (!rules_.enabled(featureXRPFees) && newFees)
|
||||||
// Can't populate the new fees before the amendment is enabled
|
// Can't populate the new fees before the amendment is enabled
|
||||||
ret = false;
|
ret = false;
|
||||||
|
if (!rules_.enabled(featureSmartEscrow) && extensionFees)
|
||||||
|
// Can't populate the extension fees before the amendment is
|
||||||
|
// enabled
|
||||||
|
ret = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (SHAMapMissingNode const&)
|
catch (SHAMapMissingNode const&)
|
||||||
@@ -678,15 +708,17 @@ Ledger::setup()
|
|||||||
void
|
void
|
||||||
Ledger::defaultFees(Config const& config)
|
Ledger::defaultFees(Config const& config)
|
||||||
{
|
{
|
||||||
XRPL_ASSERT(
|
assert(
|
||||||
fees_.base == 0 && fees_.reserve == 0 && fees_.increment == 0,
|
fees_.base == 0 && fees_.reserve == 0 && fees_.increment == 0 &&
|
||||||
"ripple::Ledger::defaultFees : zero fees");
|
fees_.extensionComputeLimit == 0 && fees_.extensionSizeLimit == 0);
|
||||||
if (fees_.base == 0)
|
if (fees_.base == 0)
|
||||||
fees_.base = config.FEES.reference_fee;
|
fees_.base = config.FEES.reference_fee;
|
||||||
if (fees_.reserve == 0)
|
if (fees_.reserve == 0)
|
||||||
fees_.reserve = config.FEES.account_reserve;
|
fees_.reserve = config.FEES.account_reserve;
|
||||||
if (fees_.increment == 0)
|
if (fees_.extensionComputeLimit == 0)
|
||||||
fees_.increment = config.FEES.owner_reserve;
|
fees_.extensionComputeLimit = config.FEES.extension_compute_limit;
|
||||||
|
if (fees_.extensionSizeLimit == 0)
|
||||||
|
fees_.extensionSizeLimit = config.FEES.extension_size_limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<SLE>
|
std::shared_ptr<SLE>
|
||||||
|
|||||||
@@ -29,10 +29,10 @@ namespace ripple {
|
|||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
|
template <typename value_type>
|
||||||
class VotableValue
|
class VotableValue
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
using value_type = XRPAmount;
|
|
||||||
value_type const current_; // The current setting
|
value_type const current_; // The current setting
|
||||||
value_type const target_; // The setting we want
|
value_type const target_; // The setting we want
|
||||||
std::map<value_type, int> voteMap_;
|
std::map<value_type, int> voteMap_;
|
||||||
@@ -67,8 +67,9 @@ public:
|
|||||||
getVotes() const;
|
getVotes() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto
|
template <typename value_type>
|
||||||
VotableValue::getVotes() const -> std::pair<value_type, bool>
|
std::pair<value_type, bool>
|
||||||
|
VotableValue<value_type>::getVotes() const
|
||||||
{
|
{
|
||||||
value_type ourVote = current_;
|
value_type ourVote = current_;
|
||||||
int weight = 0;
|
int weight = 0;
|
||||||
@@ -191,6 +192,32 @@ FeeVoteImpl::doValidation(
|
|||||||
"reserve increment",
|
"reserve increment",
|
||||||
sfReserveIncrement);
|
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
|
void
|
||||||
@@ -213,11 +240,19 @@ FeeVoteImpl::doVoting(
|
|||||||
detail::VotableValue incReserveVote(
|
detail::VotableValue incReserveVote(
|
||||||
lastClosedLedger->fees().increment, target_.owner_reserve);
|
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();
|
auto const& rules = lastClosedLedger->rules();
|
||||||
if (rules.enabled(featureXRPFees))
|
if (rules.enabled(featureXRPFees))
|
||||||
{
|
{
|
||||||
auto doVote = [](std::shared_ptr<STValidation> const& val,
|
auto doVote = [](std::shared_ptr<STValidation> const& val,
|
||||||
detail::VotableValue& value,
|
detail::VotableValue<XRPAmount>& value,
|
||||||
SF_AMOUNT const& xrpField) {
|
SF_AMOUNT const& xrpField) {
|
||||||
if (auto const field = ~val->at(~xrpField);
|
if (auto const field = ~val->at(~xrpField);
|
||||||
field && field->native())
|
field && field->native())
|
||||||
@@ -246,7 +281,7 @@ FeeVoteImpl::doVoting(
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto doVote = [](std::shared_ptr<STValidation> const& val,
|
auto doVote = [](std::shared_ptr<STValidation> const& val,
|
||||||
detail::VotableValue& value,
|
detail::VotableValue<XRPAmount>& value,
|
||||||
auto const& valueField) {
|
auto const& valueField) {
|
||||||
if (auto const field = val->at(~valueField))
|
if (auto const field = val->at(~valueField))
|
||||||
{
|
{
|
||||||
@@ -277,6 +312,29 @@ FeeVoteImpl::doVoting(
|
|||||||
doVote(val, incReserveVote, sfReserveIncrement);
|
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
|
// choose our positions
|
||||||
// TODO: Use structured binding once LLVM 16 is the minimum supported
|
// TODO: Use structured binding once LLVM 16 is the minimum supported
|
||||||
@@ -285,11 +343,14 @@ FeeVoteImpl::doVoting(
|
|||||||
auto const baseFee = baseFeeVote.getVotes();
|
auto const baseFee = baseFeeVote.getVotes();
|
||||||
auto const baseReserve = baseReserveVote.getVotes();
|
auto const baseReserve = baseReserveVote.getVotes();
|
||||||
auto const incReserve = incReserveVote.getVotes();
|
auto const incReserve = incReserveVote.getVotes();
|
||||||
|
auto const extensionCompute = extensionComputeVote.getVotes();
|
||||||
|
auto const extensionSize = extensionSizeVote.getVotes();
|
||||||
|
|
||||||
auto const seq = lastClosedLedger->info().seq + 1;
|
auto const seq = lastClosedLedger->info().seq + 1;
|
||||||
|
|
||||||
// add transactions to our position
|
// 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())
|
JLOG(journal_.warn())
|
||||||
<< "We are voting for a fee change: " << baseFee.first << "/"
|
<< "We are voting for a fee change: " << baseFee.first << "/"
|
||||||
@@ -317,6 +378,11 @@ FeeVoteImpl::doVoting(
|
|||||||
incReserveVote.current());
|
incReserveVote.current());
|
||||||
obj[sfReferenceFeeUnits] = Config::FEE_UNITS_DEPRECATED;
|
obj[sfReferenceFeeUnits] = Config::FEE_UNITS_DEPRECATED;
|
||||||
}
|
}
|
||||||
|
if (rules.enabled(featureSmartEscrow))
|
||||||
|
{
|
||||||
|
obj[sfExtensionComputeLimit] = extensionCompute.first;
|
||||||
|
obj[sfExtensionSizeLimit] = extensionSize.first;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
uint256 txID = feeTx.getTransactionID();
|
uint256 txID = feeTx.getTransactionID();
|
||||||
|
|||||||
@@ -2262,6 +2262,15 @@ NetworkOPsImp::pubValidation(std::shared_ptr<STValidation> const& val)
|
|||||||
reserveIncXRP && reserveIncXRP->native())
|
reserveIncXRP && reserveIncXRP->native())
|
||||||
jvObj[jss::reserve_inc] = reserveIncXRP->xrp().jsonClipped();
|
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
|
// NOTE Use MultiApiJson to publish two slightly different JSON objects
|
||||||
// for consumers supporting different API versions
|
// for consumers supporting different API versions
|
||||||
MultiApiJson multiObj{jvObj};
|
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::seq] = Json::UInt(lpClosed->info().seq);
|
||||||
l[jss::hash] = to_string(lpClosed->info().hash);
|
l[jss::hash] = to_string(lpClosed->info().hash);
|
||||||
|
|
||||||
|
bool const smartEscrowEnabled =
|
||||||
|
m_ledgerMaster.getValidatedLedger()->rules().enabled(
|
||||||
|
featureSmartEscrow);
|
||||||
if (!human)
|
if (!human)
|
||||||
{
|
{
|
||||||
l[jss::base_fee] = baseFee.jsonClipped();
|
l[jss::base_fee] = baseFee.jsonClipped();
|
||||||
l[jss::reserve_base] =
|
l[jss::reserve_base] =
|
||||||
lpClosed->fees().accountReserve(0).jsonClipped();
|
lpClosed->fees().accountReserve(0).jsonClipped();
|
||||||
l[jss::reserve_inc] = lpClosed->fees().increment.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(
|
l[jss::close_time] = Json::Value::UInt(
|
||||||
lpClosed->info().closeTime.time_since_epoch().count());
|
lpClosed->info().closeTime.time_since_epoch().count());
|
||||||
}
|
}
|
||||||
@@ -2726,6 +2744,12 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters)
|
|||||||
l[jss::reserve_base_xrp] =
|
l[jss::reserve_base_xrp] =
|
||||||
lpClosed->fees().accountReserve(0).decimalXRP();
|
lpClosed->fees().accountReserve(0).decimalXRP();
|
||||||
l[jss::reserve_inc_xrp] = lpClosed->fees().increment.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();
|
if (auto const closeOffset = app_.timeKeeper().closeOffset();
|
||||||
std::abs(closeOffset.count()) >= 60)
|
std::abs(closeOffset.count()) >= 60)
|
||||||
@@ -2911,6 +2935,13 @@ NetworkOPsImp::pubLedger(std::shared_ptr<ReadView const> const& lpAccepted)
|
|||||||
lpAccepted->fees().accountReserve(0).jsonClipped();
|
lpAccepted->fees().accountReserve(0).jsonClipped();
|
||||||
jvObj[jss::reserve_inc] =
|
jvObj[jss::reserve_inc] =
|
||||||
lpAccepted->fees().increment.jsonClipped();
|
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());
|
jvObj[jss::txn_count] = Json::UInt(alpAccepted->size());
|
||||||
|
|
||||||
@@ -3947,6 +3978,12 @@ NetworkOPsImp::subLedger(InfoSub::ref isrListener, Json::Value& jvResult)
|
|||||||
jvResult[jss::reserve_base] =
|
jvResult[jss::reserve_base] =
|
||||||
lpClosed->fees().accountReserve(0).jsonClipped();
|
lpClosed->fees().accountReserve(0).jsonClipped();
|
||||||
jvResult[jss::reserve_inc] = lpClosed->fees().increment.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())
|
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))
|
ctx.tx.isFieldPresent(sfReserveIncrementDrops))
|
||||||
return temDISABLED;
|
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;
|
return tesSUCCESS;
|
||||||
case ttAMENDMENT:
|
case ttAMENDMENT:
|
||||||
case ttUNL_MODIFY:
|
case ttUNL_MODIFY:
|
||||||
@@ -375,6 +387,11 @@ Change::applyFee()
|
|||||||
set(feeObject, ctx_.tx, sfReserveBase);
|
set(feeObject, ctx_.tx, sfReserveBase);
|
||||||
set(feeObject, ctx_.tx, sfReserveIncrement);
|
set(feeObject, ctx_.tx, sfReserveIncrement);
|
||||||
}
|
}
|
||||||
|
if (view().rules().enabled(featureSmartEscrow))
|
||||||
|
{
|
||||||
|
set(feeObject, ctx_.tx, sfExtensionComputeLimit);
|
||||||
|
set(feeObject, ctx_.tx, sfExtensionSizeLimit);
|
||||||
|
}
|
||||||
|
|
||||||
view().update(feeObject);
|
view().update(feeObject);
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
#include <xrpld/app/misc/CredentialHelpers.h>
|
#include <xrpld/app/misc/CredentialHelpers.h>
|
||||||
#include <xrpld/app/misc/HashRouter.h>
|
#include <xrpld/app/misc/HashRouter.h>
|
||||||
|
#include <xrpld/app/misc/WasmVM.h>
|
||||||
#include <xrpld/conditions/Condition.h>
|
#include <xrpld/conditions/Condition.h>
|
||||||
#include <xrpld/conditions/Fulfillment.h>
|
#include <xrpld/conditions/Fulfillment.h>
|
||||||
#include <xrpld/ledger/ApplyView.h>
|
#include <xrpld/ledger/ApplyView.h>
|
||||||
@@ -97,9 +98,28 @@ EscrowCreate::makeTxConsequences(PreflightContext const& ctx)
|
|||||||
return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()};
|
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
|
NotTEC
|
||||||
EscrowCreate::preflight(PreflightContext const& ctx)
|
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)
|
if (ctx.rules.enabled(fix1543) && ctx.tx.getFlags() & tfUniversalMask)
|
||||||
return temINVALID_FLAG;
|
return temINVALID_FLAG;
|
||||||
|
|
||||||
@@ -122,14 +142,23 @@ EscrowCreate::preflight(PreflightContext const& ctx)
|
|||||||
ctx.tx[sfCancelAfter] <= ctx.tx[sfFinishAfter])
|
ctx.tx[sfCancelAfter] <= ctx.tx[sfFinishAfter])
|
||||||
return temBAD_EXPIRATION;
|
return temBAD_EXPIRATION;
|
||||||
|
|
||||||
|
if (ctx.tx.isFieldPresent(sfFinishFunction) &&
|
||||||
|
!ctx.tx.isFieldPresent(sfCancelAfter))
|
||||||
|
return temBAD_EXPIRATION;
|
||||||
|
|
||||||
if (ctx.rules.enabled(fix1571))
|
if (ctx.rules.enabled(fix1571))
|
||||||
{
|
{
|
||||||
// In the absence of a FinishAfter, the escrow can be finished
|
// In the absence of a FinishAfter, the escrow can be finished
|
||||||
// immediately, which can be confusing. When creating an escrow,
|
// immediately, which can be confusing. When creating an escrow,
|
||||||
// we want to ensure that either a FinishAfter time is explicitly
|
// we want to ensure that either a FinishAfter time is explicitly
|
||||||
// specified or a completion condition is attached.
|
// 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;
|
return temMALFORMED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto const cb = ctx.tx[~sfCondition])
|
if (auto const cb = ctx.tx[~sfCondition])
|
||||||
@@ -154,6 +183,19 @@ EscrowCreate::preflight(PreflightContext const& ctx)
|
|||||||
return temDISABLED;
|
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);
|
return preflight2(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,6 +296,8 @@ EscrowCreate::doApply()
|
|||||||
(*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
|
(*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
|
||||||
(*slep)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter];
|
(*slep)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter];
|
||||||
(*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
|
(*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
|
||||||
|
(*slep)[~sfFinishFunction] = ctx_.tx[~sfFinishFunction];
|
||||||
|
(*slep)[~sfData] = ctx_.tx[~sfData];
|
||||||
|
|
||||||
ctx_.view().insert(slep);
|
ctx_.view().insert(slep);
|
||||||
|
|
||||||
@@ -277,8 +321,13 @@ EscrowCreate::doApply()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Deduct owner's balance, increment owner count
|
// Deduct owner's balance, increment owner count
|
||||||
|
// TODO: determine actual reserve based on FinishFunction size
|
||||||
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
|
(*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);
|
ctx_.view().update(sle);
|
||||||
|
|
||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
@@ -323,7 +372,10 @@ EscrowFinish::preflight(PreflightContext const& ctx)
|
|||||||
// If you specify a condition, then you must also specify
|
// If you specify a condition, then you must also specify
|
||||||
// a fulfillment.
|
// a fulfillment.
|
||||||
if (static_cast<bool>(cb) != static_cast<bool>(fb))
|
if (static_cast<bool>(cb) != static_cast<bool>(fb))
|
||||||
|
{
|
||||||
|
JLOG(ctx.j.debug()) << "Condition != Fulfillment";
|
||||||
return temMALFORMED;
|
return temMALFORMED;
|
||||||
|
}
|
||||||
|
|
||||||
// Verify the transaction signature. If it doesn't work
|
// Verify the transaction signature. If it doesn't work
|
||||||
// then don't do any more 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));
|
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;
|
return Transactor::calculateBaseFee(view, tx) + extraFee;
|
||||||
}
|
}
|
||||||
@@ -384,6 +437,22 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
|
|||||||
return tesSUCCESS;
|
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
|
TER
|
||||||
EscrowFinish::doApply()
|
EscrowFinish::doApply()
|
||||||
{
|
{
|
||||||
@@ -392,6 +461,13 @@ EscrowFinish::doApply()
|
|||||||
if (!slep)
|
if (!slep)
|
||||||
return tecNO_TARGET;
|
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
|
// 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
|
// to that time. fix1571 corrects a logic error in the check that would make
|
||||||
// a finish only succeed strictly after the cancel time.
|
// a finish only succeed strictly after the cancel time.
|
||||||
@@ -401,11 +477,17 @@ EscrowFinish::doApply()
|
|||||||
|
|
||||||
// Too soon: can't execute before the finish time
|
// Too soon: can't execute before the finish time
|
||||||
if ((*slep)[~sfFinishAfter] && !after(now, (*slep)[sfFinishAfter]))
|
if ((*slep)[~sfFinishAfter] && !after(now, (*slep)[sfFinishAfter]))
|
||||||
|
{
|
||||||
|
JLOG(j_.debug()) << "Too soon";
|
||||||
return tecNO_PERMISSION;
|
return tecNO_PERMISSION;
|
||||||
|
}
|
||||||
|
|
||||||
// Too late: can't execute after the cancel time
|
// Too late: can't execute after the cancel time
|
||||||
if ((*slep)[~sfCancelAfter] && after(now, (*slep)[sfCancelAfter]))
|
if ((*slep)[~sfCancelAfter] && after(now, (*slep)[sfCancelAfter]))
|
||||||
|
{
|
||||||
|
JLOG(j_.debug()) << "Too late";
|
||||||
return tecNO_PERMISSION;
|
return tecNO_PERMISSION;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -413,13 +495,35 @@ EscrowFinish::doApply()
|
|||||||
if ((*slep)[~sfFinishAfter] &&
|
if ((*slep)[~sfFinishAfter] &&
|
||||||
ctx_.view().info().parentCloseTime.time_since_epoch().count() <=
|
ctx_.view().info().parentCloseTime.time_since_epoch().count() <=
|
||||||
(*slep)[sfFinishAfter])
|
(*slep)[sfFinishAfter])
|
||||||
|
{
|
||||||
|
JLOG(j_.debug()) << "Too soon?";
|
||||||
return tecNO_PERMISSION;
|
return tecNO_PERMISSION;
|
||||||
|
}
|
||||||
|
|
||||||
// Too late?
|
// Too late?
|
||||||
if ((*slep)[~sfCancelAfter] &&
|
if ((*slep)[~sfCancelAfter] &&
|
||||||
ctx_.view().info().parentCloseTime.time_since_epoch().count() <=
|
ctx_.view().info().parentCloseTime.time_since_epoch().count() <=
|
||||||
(*slep)[sfCancelAfter])
|
(*slep)[sfCancelAfter])
|
||||||
|
{
|
||||||
|
JLOG(j_.debug()) << "Too late?";
|
||||||
return tecNO_PERMISSION;
|
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
|
// Check cryptocondition fulfillment
|
||||||
@@ -469,17 +573,56 @@ EscrowFinish::doApply()
|
|||||||
return tecCRYPTOCONDITION_ERROR;
|
return tecCRYPTOCONDITION_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Escrow payments cannot be used to fund accounts.
|
if (!ctx_.view().rules().enabled(featureSmartEscrow))
|
||||||
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 (auto err = verifyDepositPreauth(ctx_, account_, destID, sled);
|
// NOTE: Escrow payments cannot be used to fund accounts.
|
||||||
!isTesSuccess(err))
|
if (!sled)
|
||||||
return err;
|
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];
|
AccountID const account = (*slep)[sfAccount];
|
||||||
@@ -512,7 +655,11 @@ EscrowFinish::doApply()
|
|||||||
|
|
||||||
// Adjust source owner count
|
// Adjust source owner count
|
||||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
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);
|
ctx_.view().update(sle);
|
||||||
|
|
||||||
// Remove escrow from ledger
|
// Remove escrow from ledger
|
||||||
@@ -594,7 +741,11 @@ EscrowCancel::doApply()
|
|||||||
// Transfer amount back to owner, decrement owner count
|
// Transfer amount back to owner, decrement owner count
|
||||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
auto const sle = ctx_.view().peek(keylet::account(account));
|
||||||
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount];
|
(*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);
|
ctx_.view().update(sle);
|
||||||
|
|
||||||
// Remove escrow from ledger
|
// Remove escrow from ledger
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ public:
|
|||||||
static TxConsequences
|
static TxConsequences
|
||||||
makeTxConsequences(PreflightContext const& ctx);
|
makeTxConsequences(PreflightContext const& ctx);
|
||||||
|
|
||||||
|
static XRPAmount
|
||||||
|
calculateBaseFee(ReadView const& view, STTx const& tx);
|
||||||
|
|
||||||
static NotTEC
|
static NotTEC
|
||||||
preflight(PreflightContext const& ctx);
|
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
|
static void
|
||||||
removeDeletedTrustLines(
|
removeDeletedTrustLines(
|
||||||
ApplyView& view,
|
ApplyView& view,
|
||||||
@@ -973,6 +989,7 @@ Transactor::operator()()
|
|||||||
else if (
|
else if (
|
||||||
(result == tecOVERSIZE) || (result == tecKILLED) ||
|
(result == tecOVERSIZE) || (result == tecKILLED) ||
|
||||||
(result == tecINCOMPLETE) || (result == tecEXPIRED) ||
|
(result == tecINCOMPLETE) || (result == tecEXPIRED) ||
|
||||||
|
(result == tecWASM_REJECTED) ||
|
||||||
(isTecClaimHardFail(result, view().flags())))
|
(isTecClaimHardFail(result, view().flags())))
|
||||||
{
|
{
|
||||||
JLOG(j_.trace()) << "reapplying because of " << transToken(result);
|
JLOG(j_.trace()) << "reapplying because of " << transToken(result);
|
||||||
@@ -985,13 +1002,16 @@ Transactor::operator()()
|
|||||||
std::vector<uint256> removedTrustLines;
|
std::vector<uint256> removedTrustLines;
|
||||||
std::vector<uint256> expiredNFTokenOffers;
|
std::vector<uint256> expiredNFTokenOffers;
|
||||||
std::vector<uint256> expiredCredentials;
|
std::vector<uint256> expiredCredentials;
|
||||||
|
std::vector<std::pair<uint256, Blob>> modifiedWasmObjects;
|
||||||
|
|
||||||
bool const doOffers =
|
bool const doOffers =
|
||||||
((result == tecOVERSIZE) || (result == tecKILLED));
|
((result == tecOVERSIZE) || (result == tecKILLED));
|
||||||
bool const doLines = (result == tecINCOMPLETE);
|
bool const doLines = (result == tecINCOMPLETE);
|
||||||
bool const doNFTokenOffers = (result == tecEXPIRED);
|
bool const doNFTokenOffers = (result == tecEXPIRED);
|
||||||
bool const doCredentials = (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,
|
ctx_.visit([doOffers,
|
||||||
&removedOffers,
|
&removedOffers,
|
||||||
@@ -1000,7 +1020,9 @@ Transactor::operator()()
|
|||||||
doNFTokenOffers,
|
doNFTokenOffers,
|
||||||
&expiredNFTokenOffers,
|
&expiredNFTokenOffers,
|
||||||
doCredentials,
|
doCredentials,
|
||||||
&expiredCredentials](
|
&expiredCredentials,
|
||||||
|
doWasmData,
|
||||||
|
&modifiedWasmObjects](
|
||||||
uint256 const& index,
|
uint256 const& index,
|
||||||
bool isDelete,
|
bool isDelete,
|
||||||
std::shared_ptr<SLE const> const& before,
|
std::shared_ptr<SLE const> const& before,
|
||||||
@@ -1035,6 +1057,13 @@ Transactor::operator()()
|
|||||||
(before->getType() == ltCREDENTIAL))
|
(before->getType() == ltCREDENTIAL))
|
||||||
expiredCredentials.push_back(index);
|
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(
|
removeExpiredCredentials(
|
||||||
view(), expiredCredentials, ctx_.app.journal("View"));
|
view(), expiredCredentials, ctx_.app.journal("View"));
|
||||||
|
|
||||||
|
if (result == tecWASM_REJECTED)
|
||||||
|
modifyWasmDataFields(
|
||||||
|
view(), modifiedWasmObjects, ctx_.app.journal("View"));
|
||||||
|
|
||||||
applied = isTecClaim(result);
|
applied = isTecClaim(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,12 @@ struct FeeSetup
|
|||||||
/** The per-owned item reserve requirement in drops. */
|
/** The per-owned item reserve requirement in drops. */
|
||||||
XRPAmount owner_reserve{2 * DROPS_PER_XRP};
|
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
|
/* (Remember to update the example cfg files when changing any of these
|
||||||
* values.) */
|
* values.) */
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1102,6 +1102,10 @@ setup_FeeVote(Section const& section)
|
|||||||
setup.account_reserve = temp;
|
setup.account_reserve = temp;
|
||||||
if (set(temp, "owner_reserve", section))
|
if (set(temp, "owner_reserve", section))
|
||||||
setup.owner_reserve = temp;
|
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;
|
return setup;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user