mirror of
https://github.com/Xahau/xahaud.git
synced 2026-01-15 04:05:15 +00:00
Compare commits
3 Commits
batch
...
emit-atomi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
692be6b783 | ||
|
|
bd7fc84dbc | ||
|
|
1a35c83048 |
@@ -349,6 +349,12 @@ const uint8_t max_emit = 255;
|
||||
const uint8_t max_params = 16;
|
||||
const double fee_base_multiplier = 1.1f;
|
||||
|
||||
enum GuardRules : uint64_t {
|
||||
HooksUpdate1 = 0x0001U,
|
||||
Fix20250131 = 0x0002U,
|
||||
AtomicEmit = 0x0004U,
|
||||
};
|
||||
|
||||
// RH NOTE: Find descriptions of api functions in ./impl/applyHook.cpp and
|
||||
// hookapi.h (include for hooks) this is a map of the api name to its return
|
||||
// code (vec[0] and its parameters vec[>0]) as wasm type codes
|
||||
@@ -434,5 +440,8 @@ static const std::map<std::string, std::vector<uint8_t>> import_whitelist{
|
||||
// featureHooks1
|
||||
static const std::map<std::string, std::vector<uint8_t>> import_whitelist_1{
|
||||
{"xpop_slot", {0x7EU, 0x7FU, 0x7FU}}};
|
||||
// featureAtomicEmit
|
||||
static const std::map<std::string, std::vector<uint8_t>> import_whitelist_2{
|
||||
{"emit_atomic", {0x7EU, 0x7FU, 0x7FU, 0x7FU, 0x7FU}}};
|
||||
}; // namespace hook_api
|
||||
#endif
|
||||
|
||||
@@ -634,7 +634,7 @@ check_guard(
|
||||
}
|
||||
else if (fc_type == 10) // memory.copy
|
||||
{
|
||||
if (rulesVersion & 0x02U)
|
||||
if (rulesVersion & hook_api::GuardRules::Fix20250131)
|
||||
GUARD_ERROR("Memory.copy instruction is not allowed.");
|
||||
|
||||
REQUIRE(2);
|
||||
@@ -642,7 +642,7 @@ check_guard(
|
||||
}
|
||||
else if (fc_type == 11) // memory.fill
|
||||
{
|
||||
if (rulesVersion & 0x02U)
|
||||
if (rulesVersion & hook_api::GuardRules::Fix20250131)
|
||||
GUARD_ERROR("Memory.fill instruction is not allowed.");
|
||||
|
||||
ADVANCE(1);
|
||||
@@ -1028,12 +1028,19 @@ validateGuards(
|
||||
hook_api::import_whitelist.find(import_name) ==
|
||||
hook_api::import_whitelist.end())
|
||||
{
|
||||
if (rulesVersion > 0 &&
|
||||
if (rulesVersion & hook_api::GuardRules::HooksUpdate1 &&
|
||||
hook_api::import_whitelist_1.find(import_name) !=
|
||||
hook_api::import_whitelist_1.end())
|
||||
{
|
||||
// PASS, this is a version 1 api
|
||||
}
|
||||
else if (
|
||||
rulesVersion & hook_api::GuardRules::AtomicEmit &&
|
||||
hook_api::import_whitelist_2.find(import_name) !=
|
||||
hook_api::import_whitelist_2.end())
|
||||
{
|
||||
// PASS, this is a version 2 api
|
||||
}
|
||||
else
|
||||
{
|
||||
GUARDLOG(hook::log::IMPORT_ILLEGAL)
|
||||
@@ -1258,12 +1265,20 @@ validateGuards(
|
||||
{
|
||||
for (auto const& [import_idx, api_name] : usage->second)
|
||||
{
|
||||
auto const& api_signature =
|
||||
hook_api::import_whitelist.find(api_name) !=
|
||||
hook_api::import_whitelist.end()
|
||||
? hook_api::import_whitelist.find(api_name)->second
|
||||
: hook_api::import_whitelist_1.find(api_name)
|
||||
->second;
|
||||
auto findInWhitelist = [&](auto const& whitelist) {
|
||||
auto it = whitelist.find(api_name);
|
||||
return it != whitelist.end() ? &it->second
|
||||
: nullptr;
|
||||
};
|
||||
|
||||
auto const* sig =
|
||||
findInWhitelist(hook_api::import_whitelist);
|
||||
if (!sig)
|
||||
sig = findInWhitelist(hook_api::import_whitelist_1);
|
||||
if (!sig)
|
||||
sig = findInWhitelist(hook_api::import_whitelist_2);
|
||||
|
||||
auto const& api_signature = *sig;
|
||||
|
||||
if (!first_signature)
|
||||
{
|
||||
|
||||
@@ -79,7 +79,10 @@ main(int argc, char** argv)
|
||||
|
||||
close(fd);
|
||||
|
||||
auto result = validateGuards(hook, std::cout, "", 3);
|
||||
auto const allRules = hook_api::GuardRules::HooksUpdate1 +
|
||||
hook_api::GuardRules::Fix20250131 + hook_api::GuardRules::AtomicEmit;
|
||||
|
||||
auto result = validateGuards(hook, std::cout, "", allRules);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace detail {
|
||||
// Feature.cpp. Because it's only used to reserve storage, and determine how
|
||||
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
|
||||
// the actual number of amendments. A LogicError on startup will verify this.
|
||||
static constexpr std::size_t numFeatures = 106;
|
||||
static constexpr std::size_t numFeatures = 107;
|
||||
|
||||
/** Amendments that this server supports and the default voting behavior.
|
||||
Whether they are enabled depends on the Rules defined in the validated
|
||||
|
||||
@@ -361,6 +361,7 @@ enum TECcodes : TERUnderlyingType {
|
||||
tecARRAY_TOO_LARGE = 196,
|
||||
tecLOCKED = 197,
|
||||
tecBAD_CREDENTIALS = 198,
|
||||
tecHOOK_EMIT_FAILED = 199,
|
||||
tecLAST_POSSIBLE_ENTRY = 255,
|
||||
};
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||
// in include/xrpl/protocol/Feature.h.
|
||||
|
||||
XRPL_FEATURE(AtomicEmit, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(PermissionedDomains, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(DynamicNFT, Supported::no, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -91,6 +91,7 @@ LEDGER_ENTRY(ltHOOK_DEFINITION, 'D', HookDefinition, hook_definition, ({
|
||||
{sfHookCallbackFee, soeOPTIONAL},
|
||||
{sfPreviousTxnID, soeOPTIONAL},
|
||||
{sfPreviousTxnLgrSeq, soeOPTIONAL},
|
||||
{sfHookAtomicEmitFee, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** A ledger object containing a hook-emitted transaction from a previous hook execution.
|
||||
|
||||
@@ -251,6 +251,7 @@ TYPED_SFIELD(sfPrice, AMOUNT, 28)
|
||||
TYPED_SFIELD(sfSignatureReward, AMOUNT, 29)
|
||||
TYPED_SFIELD(sfMinAccountCreateAmount, AMOUNT, 30)
|
||||
TYPED_SFIELD(sfLPTokenBalance, AMOUNT, 31)
|
||||
TYPED_SFIELD(sfHookAtomicEmitFee, AMOUNT, 32)
|
||||
|
||||
// variable length (common)
|
||||
TYPED_SFIELD(sfPublicKey, VL, 1)
|
||||
|
||||
@@ -96,7 +96,8 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfHookCanEmit, soeOPTIONAL},
|
||||
{sfHookApiVersion, soeREQUIRED},
|
||||
{sfFlags, soeREQUIRED},
|
||||
{sfFee, soeREQUIRED}});
|
||||
{sfFee, soeREQUIRED},
|
||||
{sfHookAtomicEmitFee, soeOPTIONAL}});
|
||||
|
||||
add(sfHook.jsonName,
|
||||
sfHook.getCode(),
|
||||
@@ -108,7 +109,8 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfHookOn, soeOPTIONAL},
|
||||
{sfHookCanEmit, soeOPTIONAL},
|
||||
{sfHookApiVersion, soeOPTIONAL},
|
||||
{sfFlags, soeOPTIONAL}});
|
||||
{sfFlags, soeOPTIONAL},
|
||||
{sfHookAtomicEmitFee, soeOPTIONAL}});
|
||||
|
||||
add(sfHookGrant.jsonName,
|
||||
sfHookGrant.getCode(),
|
||||
|
||||
@@ -123,6 +123,7 @@ transResults()
|
||||
MAKE_ERROR(tecARRAY_TOO_LARGE, "Array is too large."),
|
||||
MAKE_ERROR(tecLOCKED, "Fund is locked."),
|
||||
MAKE_ERROR(tecBAD_CREDENTIALS, "Bad credentials."),
|
||||
MAKE_ERROR(tecHOOK_EMIT_FAILED, "Failed to emit transactions from Hook."),
|
||||
|
||||
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
|
||||
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
|
||||
|
||||
@@ -2933,6 +2933,457 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
Json::Value
|
||||
getLastLedger(jtx::Env& env)
|
||||
{
|
||||
Json::Value params;
|
||||
params[jss::ledger_index] = env.closed()->seq();
|
||||
params[jss::transactions] = true;
|
||||
params[jss::expand] = true;
|
||||
return env.rpc("json", "ledger", to_string(params));
|
||||
}
|
||||
|
||||
void
|
||||
test_emit_atomic(FeatureBitset features)
|
||||
{
|
||||
testcase("Test emit_atomic");
|
||||
using namespace jtx;
|
||||
// Env env{*this, features};
|
||||
Env env{
|
||||
*this, envconfig(), features, nullptr, beast::severities::kInfo};
|
||||
|
||||
auto const alice = Account{"alice"};
|
||||
auto const bob = Account{"bob"};
|
||||
auto const charlie = Account{"charlie"};
|
||||
auto const nonActive = Account{"nonActive"};
|
||||
env.fund(XRP(10000), alice);
|
||||
env.fund(XRP(10000), bob);
|
||||
env.fund(XRP(10000), charlie);
|
||||
|
||||
TestHook hook = wasm[R"[test.hook](
|
||||
#include <stdint.h>
|
||||
extern int32_t _g(uint32_t, uint32_t);
|
||||
extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
extern int64_t emit_atomic (uint32_t, uint32_t, uint32_t, uint32_t);
|
||||
extern int64_t etxn_reserve(uint32_t);
|
||||
extern int64_t otxn_param(uint32_t, uint32_t, uint32_t, uint32_t);
|
||||
extern int64_t hook_account(uint32_t, uint32_t);
|
||||
extern int64_t otxn_field (
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t field_id
|
||||
);
|
||||
#define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1)
|
||||
#define OUT_OF_BOUNDS (-1)
|
||||
#define ttPAYMENT 0
|
||||
#define tfCANONICAL 0x80000000UL
|
||||
#define amAMOUNT 1U
|
||||
#define amFEE 8U
|
||||
#define atACCOUNT 1U
|
||||
#define DOESNT_EXIST (-5)
|
||||
#define atDESTINATION 3U
|
||||
#define SBUF(x) (uint32_t)x,sizeof(x)
|
||||
|
||||
#define PREREQUISITE_NOT_MET -9
|
||||
#define ENCODE_DROPS_SIZE 9
|
||||
#define ENCODE_DROPS(buf_out, drops, amount_type ) \
|
||||
{\
|
||||
uint8_t uat = amount_type; \
|
||||
uint64_t udrops = drops; \
|
||||
buf_out[0] = 0x60U +(uat & 0x0FU ); \
|
||||
buf_out[1] = 0b01000000 + (( udrops >> 56 ) & 0b00111111 ); \
|
||||
buf_out[2] = (udrops >> 48) & 0xFFU; \
|
||||
buf_out[3] = (udrops >> 40) & 0xFFU; \
|
||||
buf_out[4] = (udrops >> 32) & 0xFFU; \
|
||||
buf_out[5] = (udrops >> 24) & 0xFFU; \
|
||||
buf_out[6] = (udrops >> 16) & 0xFFU; \
|
||||
buf_out[7] = (udrops >> 8) & 0xFFU; \
|
||||
buf_out[8] = (udrops >> 0) & 0xFFU; \
|
||||
buf_out += ENCODE_DROPS_SIZE; \
|
||||
}
|
||||
|
||||
#define _06_XX_ENCODE_DROPS(buf_out, drops, amount_type )\
|
||||
ENCODE_DROPS(buf_out, drops, amount_type );
|
||||
|
||||
#define ENCODE_DROPS_AMOUNT(buf_out, drops )\
|
||||
ENCODE_DROPS(buf_out, drops, amAMOUNT );
|
||||
#define _06_01_ENCODE_DROPS_AMOUNT(buf_out, drops )\
|
||||
ENCODE_DROPS_AMOUNT(buf_out, drops );
|
||||
|
||||
#define ENCODE_DROPS_FEE(buf_out, drops )\
|
||||
ENCODE_DROPS(buf_out, drops, amFEE );
|
||||
#define _06_08_ENCODE_DROPS_FEE(buf_out, drops )\
|
||||
ENCODE_DROPS_FEE(buf_out, drops );
|
||||
|
||||
#define ENCODE_TT_SIZE 3
|
||||
#define ENCODE_TT(buf_out, tt )\
|
||||
{\
|
||||
uint8_t utt = tt;\
|
||||
buf_out[0] = 0x12U;\
|
||||
buf_out[1] =(utt >> 8 ) & 0xFFU;\
|
||||
buf_out[2] =(utt >> 0 ) & 0xFFU;\
|
||||
buf_out += ENCODE_TT_SIZE; \
|
||||
}
|
||||
#define _01_02_ENCODE_TT(buf_out, tt)\
|
||||
ENCODE_TT(buf_out, tt);
|
||||
|
||||
|
||||
#define ENCODE_ACCOUNT_SIZE 22
|
||||
#define ENCODE_ACCOUNT(buf_out, account_id, account_type)\
|
||||
{\
|
||||
uint8_t uat = account_type;\
|
||||
buf_out[0] = 0x80U + uat;\
|
||||
buf_out[1] = 0x14U;\
|
||||
*(uint64_t*)(buf_out + 2) = *(uint64_t*)(account_id + 0);\
|
||||
*(uint64_t*)(buf_out + 10) = *(uint64_t*)(account_id + 8);\
|
||||
*(uint32_t*)(buf_out + 18) = *(uint32_t*)(account_id + 16);\
|
||||
buf_out += ENCODE_ACCOUNT_SIZE;\
|
||||
}
|
||||
#define _08_XX_ENCODE_ACCOUNT(buf_out, account_id, account_type)\
|
||||
ENCODE_ACCOUNT(buf_out, account_id, account_type);
|
||||
|
||||
#define ENCODE_ACCOUNT_SRC_SIZE 22
|
||||
#define ENCODE_ACCOUNT_SRC(buf_out, account_id)\
|
||||
ENCODE_ACCOUNT(buf_out, account_id, atACCOUNT);
|
||||
#define _08_01_ENCODE_ACCOUNT_SRC(buf_out, account_id)\
|
||||
ENCODE_ACCOUNT_SRC(buf_out, account_id);
|
||||
|
||||
#define ENCODE_ACCOUNT_DST_SIZE 22
|
||||
#define ENCODE_ACCOUNT_DST(buf_out, account_id)\
|
||||
ENCODE_ACCOUNT(buf_out, account_id, atDESTINATION);
|
||||
#define _08_03_ENCODE_ACCOUNT_DST(buf_out, account_id)\
|
||||
ENCODE_ACCOUNT_DST(buf_out, account_id);
|
||||
|
||||
#define ENCODE_ACCOUNT_OWNER_SIZE 22
|
||||
#define ENCODE_ACCOUNT_OWNER(buf_out, account_id) \
|
||||
ENCODE_ACCOUNT(buf_out, account_id, atOWNER);
|
||||
#define _08_02_ENCODE_ACCOUNT_OWNER(buf_out, account_id) \
|
||||
ENCODE_ACCOUNT_OWNER(buf_out, account_id);
|
||||
|
||||
#define ENCODE_UINT32_COMMON_SIZE 5U
|
||||
#define ENCODE_UINT32_COMMON(buf_out, i, field)\
|
||||
{\
|
||||
uint32_t ui = i; \
|
||||
uint8_t uf = field; \
|
||||
buf_out[0] = 0x20U +(uf & 0x0FU); \
|
||||
buf_out[1] =(ui >> 24 ) & 0xFFU; \
|
||||
buf_out[2] =(ui >> 16 ) & 0xFFU; \
|
||||
buf_out[3] =(ui >> 8 ) & 0xFFU; \
|
||||
buf_out[4] =(ui >> 0 ) & 0xFFU; \
|
||||
buf_out += ENCODE_UINT32_COMMON_SIZE; \
|
||||
}
|
||||
#define _02_XX_ENCODE_UINT32_COMMON(buf_out, i, field)\
|
||||
ENCODE_UINT32_COMMON(buf_out, i, field)\
|
||||
|
||||
#define ENCODE_UINT32_UNCOMMON_SIZE 6U
|
||||
#define ENCODE_UINT32_UNCOMMON(buf_out, i, field)\
|
||||
{\
|
||||
uint32_t ui = i; \
|
||||
uint8_t uf = field; \
|
||||
buf_out[0] = 0x20U; \
|
||||
buf_out[1] = uf; \
|
||||
buf_out[2] =(ui >> 24 ) & 0xFFU; \
|
||||
buf_out[3] =(ui >> 16 ) & 0xFFU; \
|
||||
buf_out[4] =(ui >> 8 ) & 0xFFU; \
|
||||
buf_out[5] =(ui >> 0 ) & 0xFFU; \
|
||||
buf_out += ENCODE_UINT32_UNCOMMON_SIZE; \
|
||||
}
|
||||
#define _02_XX_ENCODE_UINT32_UNCOMMON(buf_out, i, field)\
|
||||
ENCODE_UINT32_UNCOMMON(buf_out, i, field)\
|
||||
|
||||
#define ENCODE_LLS_SIZE 6U
|
||||
#define ENCODE_LLS(buf_out, lls )\
|
||||
ENCODE_UINT32_UNCOMMON(buf_out, lls, 0x1B );
|
||||
#define _02_27_ENCODE_LLS(buf_out, lls )\
|
||||
ENCODE_LLS(buf_out, lls );
|
||||
|
||||
#define ENCODE_FLS_SIZE 6U
|
||||
#define ENCODE_FLS(buf_out, fls )\
|
||||
ENCODE_UINT32_UNCOMMON(buf_out, fls, 0x1A );
|
||||
#define _02_26_ENCODE_FLS(buf_out, fls )\
|
||||
ENCODE_FLS(buf_out, fls );
|
||||
|
||||
#define ENCODE_TAG_SRC_SIZE 5
|
||||
#define ENCODE_TAG_SRC(buf_out, tag )\
|
||||
ENCODE_UINT32_COMMON(buf_out, tag, 0x3U );
|
||||
#define _02_03_ENCODE_TAG_SRC(buf_out, tag )\
|
||||
ENCODE_TAG_SRC(buf_out, tag );
|
||||
|
||||
#define ENCODE_TAG_DST_SIZE 5
|
||||
#define ENCODE_TAG_DST(buf_out, tag )\
|
||||
ENCODE_UINT32_COMMON(buf_out, tag, 0xEU );
|
||||
#define _02_14_ENCODE_TAG_DST(buf_out, tag )\
|
||||
ENCODE_TAG_DST(buf_out, tag );
|
||||
|
||||
#define ENCODE_SEQUENCE_SIZE 5
|
||||
#define ENCODE_SEQUENCE(buf_out, sequence )\
|
||||
ENCODE_UINT32_COMMON(buf_out, sequence, 0x4U );
|
||||
#define _02_04_ENCODE_SEQUENCE(buf_out, sequence )\
|
||||
ENCODE_SEQUENCE(buf_out, sequence );
|
||||
|
||||
#define ENCODE_FLAGS_SIZE 5
|
||||
#define ENCODE_FLAGS(buf_out, tag )\
|
||||
ENCODE_UINT32_COMMON(buf_out, tag, 0x2U );
|
||||
#define _02_02_ENCODE_FLAGS(buf_out, tag )\
|
||||
ENCODE_FLAGS(buf_out, tag );
|
||||
|
||||
#define ENCODE_SIGNING_PUBKEY_SIZE 35
|
||||
#define ENCODE_SIGNING_PUBKEY(buf_out, pkey )\
|
||||
{\
|
||||
buf_out[0] = 0x73U;\
|
||||
buf_out[1] = 0x21U;\
|
||||
*(uint64_t*)(buf_out + 2) = *(uint64_t*)(pkey + 0);\
|
||||
*(uint64_t*)(buf_out + 10) = *(uint64_t*)(pkey + 8);\
|
||||
*(uint64_t*)(buf_out + 18) = *(uint64_t*)(pkey + 16);\
|
||||
*(uint64_t*)(buf_out + 26) = *(uint64_t*)(pkey + 24);\
|
||||
buf[34] = pkey[32];\
|
||||
buf_out += ENCODE_SIGNING_PUBKEY_SIZE;\
|
||||
}
|
||||
|
||||
#define _07_03_ENCODE_SIGNING_PUBKEY(buf_out, pkey )\
|
||||
ENCODE_SIGNING_PUBKEY(buf_out, pkey );
|
||||
|
||||
#define ENCODE_SIGNING_PUBKEY_NULL_SIZE 35
|
||||
#define ENCODE_SIGNING_PUBKEY_NULL(buf_out )\
|
||||
{\
|
||||
buf_out[0] = 0x73U;\
|
||||
buf_out[1] = 0x21U;\
|
||||
*(uint64_t*)(buf_out+2) = 0;\
|
||||
*(uint64_t*)(buf_out+10) = 0;\
|
||||
*(uint64_t*)(buf_out+18) = 0;\
|
||||
*(uint64_t*)(buf_out+25) = 0;\
|
||||
buf_out += ENCODE_SIGNING_PUBKEY_NULL_SIZE;\
|
||||
}
|
||||
|
||||
#define _07_03_ENCODE_SIGNING_PUBKEY_NULL(buf_out )\
|
||||
ENCODE_SIGNING_PUBKEY_NULL(buf_out );
|
||||
|
||||
extern int64_t etxn_fee_base (
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len
|
||||
);
|
||||
extern int64_t etxn_details (
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len
|
||||
);
|
||||
extern int64_t ledger_seq (void);
|
||||
|
||||
#define PREPARE_PAYMENT_SIMPLE_SIZE 248U
|
||||
#define PREPARE_PAYMENT_SIMPLE(buf_out_master, drops_amount_raw, to_address, dest_tag_raw, src_tag_raw)\
|
||||
{\
|
||||
uint8_t* buf_out = buf_out_master;\
|
||||
uint8_t acc[20];\
|
||||
uint64_t drops_amount = (drops_amount_raw);\
|
||||
uint32_t dest_tag = (dest_tag_raw);\
|
||||
uint32_t src_tag = (src_tag_raw);\
|
||||
uint32_t cls = (uint32_t)ledger_seq();\
|
||||
hook_account(SBUF(acc));\
|
||||
_01_02_ENCODE_TT (buf_out, ttPAYMENT ); /* uint16 | size 3 */ \
|
||||
_02_02_ENCODE_FLAGS (buf_out, tfCANONICAL ); /* uint32 | size 5 */ \
|
||||
_02_03_ENCODE_TAG_SRC (buf_out, src_tag ); /* uint32 | size 5 */ \
|
||||
_02_04_ENCODE_SEQUENCE (buf_out, 3 ); /* uint32 | size 5 */ \
|
||||
_02_14_ENCODE_TAG_DST (buf_out, dest_tag ); /* uint32 | size 5 */ \
|
||||
_02_26_ENCODE_FLS (buf_out, cls + 0 ); /* uint32 | size 6 */ \
|
||||
_02_27_ENCODE_LLS (buf_out, cls + 0 ); /* uint32 | size 6 */ \
|
||||
_06_01_ENCODE_DROPS_AMOUNT (buf_out, drops_amount ); /* amount | size 9 */ \
|
||||
uint8_t* fee_ptr = buf_out;\
|
||||
_06_08_ENCODE_DROPS_FEE (buf_out, 0 ); /* amount | size 9 */ \
|
||||
_07_03_ENCODE_SIGNING_PUBKEY_NULL (buf_out ); /* pk | size 35 */ \
|
||||
_08_01_ENCODE_ACCOUNT_SRC (buf_out, acc ); /* account | size 22 */ \
|
||||
_08_03_ENCODE_ACCOUNT_DST (buf_out, to_address ); /* account | size 22 */ \
|
||||
int64_t edlen = etxn_details((uint32_t)buf_out, PREPARE_PAYMENT_SIMPLE_SIZE); /* emitdet | size 1?? */ \
|
||||
int64_t fee = etxn_fee_base(buf_out_master, PREPARE_PAYMENT_SIMPLE_SIZE); \
|
||||
_06_08_ENCODE_DROPS_FEE (fee_ptr, fee ); \
|
||||
}
|
||||
|
||||
#define UINT16_FROM_BUF(buf)\
|
||||
(((uint64_t)((buf)[0]) << 8U) +\
|
||||
((uint64_t)((buf)[1]) << 0U))
|
||||
|
||||
#define BUFFER_EQUAL_32(buf1, buf2)\
|
||||
(\
|
||||
*(((uint64_t*)(buf1)) + 0) == *(((uint64_t*)(buf2)) + 0) &&\
|
||||
*(((uint64_t*)(buf1)) + 1) == *(((uint64_t*)(buf2)) + 1) &&\
|
||||
*(((uint64_t*)(buf1)) + 2) == *(((uint64_t*)(buf2)) + 2) &&\
|
||||
*(((uint64_t*)(buf1)) + 3) == *(((uint64_t*)(buf2)) + 3) &&\
|
||||
*(((uint64_t*)(buf1)) + 4) == *(((uint64_t*)(buf2)) + 4) &&\
|
||||
*(((uint64_t*)(buf1)) + 5) == *(((uint64_t*)(buf2)) + 5) &&\
|
||||
*(((uint64_t*)(buf1)) + 6) == *(((uint64_t*)(buf2)) + 6) &&\
|
||||
*(((uint64_t*)(buf1)) + 7) == *(((uint64_t*)(buf2)) + 7))
|
||||
|
||||
#define ASSERT(x)\
|
||||
if (!(x))\
|
||||
rollback((uint32_t)#x,sizeof(#x),__LINE__)
|
||||
|
||||
#define sfDestination ((8U << 16U) + 3U)
|
||||
|
||||
extern int64_t etxn_generation(void);
|
||||
extern int64_t otxn_generation(void);
|
||||
extern int64_t otxn_burden(void);
|
||||
extern int64_t etxn_burden(void);
|
||||
|
||||
// int64_t cbak(uint32_t r)
|
||||
// {
|
||||
// // on callback we emit 2 more txns
|
||||
// uint8_t bob[20];
|
||||
// ASSERT(otxn_field(SBUF(bob), sfDestination) == 20);
|
||||
|
||||
// ASSERT(otxn_generation() + 1 == etxn_generation());
|
||||
|
||||
// ASSERT(etxn_burden() == PREREQUISITE_NOT_MET);
|
||||
|
||||
// ASSERT(etxn_reserve(2) == 2);
|
||||
|
||||
// ASSERT(otxn_burden() > 0);
|
||||
// ASSERT(etxn_burden() == otxn_burden() * 2);
|
||||
|
||||
// uint8_t tx[PREPARE_PAYMENT_SIMPLE_SIZE];
|
||||
// PREPARE_PAYMENT_SIMPLE(tx, 1000, bob, 0, 0);
|
||||
|
||||
// uint8_t hash1[32];
|
||||
// ASSERT(emit_atomic(SBUF(hash1), SBUF(tx)) == 32);
|
||||
|
||||
// ASSERT(etxn_details(tx + 132, 138) == 138);
|
||||
// uint8_t hash2[32];
|
||||
// ASSERT(emit_atomic(SBUF(hash2), SBUF(tx)) == 32);
|
||||
|
||||
// ASSERT(!BUFFER_EQUAL_32(hash1, hash2));
|
||||
|
||||
// return accept(0,0,0);
|
||||
// }
|
||||
|
||||
int64_t hook(uint32_t r)
|
||||
{
|
||||
_g(1,1);
|
||||
|
||||
etxn_reserve(1);
|
||||
|
||||
// bounds checks
|
||||
ASSERT(emit_atomic(1000000, 32, 0, 32) == OUT_OF_BOUNDS);
|
||||
ASSERT(emit_atomic(0,1000000, 0, 32) == OUT_OF_BOUNDS);
|
||||
ASSERT(emit_atomic(0,32, 1000000, 32) == OUT_OF_BOUNDS);
|
||||
ASSERT(emit_atomic(0,32, 0, 1000000) == OUT_OF_BOUNDS);
|
||||
|
||||
ASSERT(otxn_generation() == 0);
|
||||
ASSERT(otxn_burden == 1);
|
||||
|
||||
uint8_t bob[20];
|
||||
ASSERT(otxn_param(SBUF(bob), "bob", 3) == 20);
|
||||
|
||||
uint8_t tx[PREPARE_PAYMENT_SIMPLE_SIZE];
|
||||
PREPARE_PAYMENT_SIMPLE(tx, 1000, bob, 0, 0);
|
||||
|
||||
uint8_t hash[32];
|
||||
ASSERT(emit_atomic(SBUF(hash), SBUF(tx)) == 32);
|
||||
|
||||
return accept(0,0,0);
|
||||
}
|
||||
)[test.hook]"];
|
||||
|
||||
auto hookObj = hso(hook, overrideFlag);
|
||||
// hookObj[jss::HookOn] =
|
||||
// "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFFFFFFFFFBFFF"
|
||||
// "FF"; // Invoke
|
||||
hookObj[sfHookAtomicEmitFee.jsonName] = "1000";
|
||||
env(ripple::test::jtx::hook(charlie, {{hookObj}}, 0),
|
||||
M("set emit_atomic"),
|
||||
HSFEE);
|
||||
env.close();
|
||||
|
||||
// Json::Value invoke;
|
||||
// invoke[jss::TransactionType] = "Invoke";
|
||||
// invoke[jss::Account] = alice.human();
|
||||
// invoke[jss::Destination] = charlie.human();
|
||||
|
||||
Json::Value invoke;
|
||||
invoke[jss::TransactionType] = "Payment";
|
||||
invoke[jss::Account] = alice.human();
|
||||
invoke[jss::Destination] = charlie.human();
|
||||
invoke[jss::Amount] = "1234";
|
||||
|
||||
Json::Value params{Json::arrayValue};
|
||||
params[0U][jss::HookParameter][jss::HookParameterName] =
|
||||
strHex(std::string("bob"));
|
||||
params[0U][jss::HookParameter][jss::HookParameterValue] =
|
||||
strHex(nonActive.id());
|
||||
|
||||
invoke[jss::HookParameters] = params;
|
||||
|
||||
env(invoke, M("test emit_atomic"), fee(XRP(1)), ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
bool const fixV2 = env.current()->rules().enabled(fixXahauV2);
|
||||
|
||||
std::optional<uint256> emithash;
|
||||
{
|
||||
auto meta = env.meta(); // meta can close
|
||||
|
||||
printf(
|
||||
"txs: %s\n",
|
||||
getLastLedger(env)[jss::result][jss::ledger][jss::transactions]
|
||||
.toStyledString()
|
||||
.c_str());
|
||||
|
||||
// ensure hook execution occured
|
||||
BEAST_REQUIRE(meta);
|
||||
BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions));
|
||||
|
||||
auto const hookEmissions = meta->getFieldArray(sfHookEmissions);
|
||||
BEAST_EXPECT(
|
||||
hookEmissions[0u].isFieldPresent(sfEmitNonce) == fixV2 ? true
|
||||
: false);
|
||||
BEAST_EXPECT(
|
||||
hookEmissions[0u].getAccountID(sfHookAccount) == alice.id());
|
||||
|
||||
auto const hookExecutions = meta->getFieldArray(sfHookExecutions);
|
||||
BEAST_REQUIRE(hookExecutions.size() == 1);
|
||||
|
||||
// ensure there was one emitted txn
|
||||
BEAST_EXPECT(hookExecutions[0].getFieldU16(sfHookEmitCount) == 1);
|
||||
|
||||
BEAST_REQUIRE(meta->isFieldPresent(sfAffectedNodes));
|
||||
|
||||
BEAST_REQUIRE(meta->getFieldArray(sfAffectedNodes).size() == 3);
|
||||
|
||||
for (auto const& node : meta->getFieldArray(sfAffectedNodes))
|
||||
{
|
||||
SField const& metaType = node.getFName();
|
||||
uint16_t nodeType = node.getFieldU16(sfLedgerEntryType);
|
||||
if (metaType == sfCreatedNode && nodeType == ltEMITTED_TXN)
|
||||
{
|
||||
BEAST_REQUIRE(node.isFieldPresent(sfNewFields));
|
||||
|
||||
auto const& nf = const_cast<ripple::STObject&>(node)
|
||||
.getField(sfNewFields)
|
||||
.downcast<STObject>();
|
||||
|
||||
auto const& et = const_cast<ripple::STObject&>(nf)
|
||||
.getField(sfEmittedTxn)
|
||||
.downcast<STObject>();
|
||||
|
||||
auto const& em = const_cast<ripple::STObject&>(et)
|
||||
.getField(sfEmitDetails)
|
||||
.downcast<STObject>();
|
||||
|
||||
BEAST_EXPECT(em.getFieldU32(sfEmitGeneration) == 1);
|
||||
BEAST_EXPECT(em.getFieldU64(sfEmitBurden) == 1);
|
||||
|
||||
Blob txBlob = et.getSerializer().getData();
|
||||
auto const tx = std::make_unique<STTx>(
|
||||
Slice{txBlob.data(), txBlob.size()});
|
||||
emithash = tx->getTransactionID();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BEAST_REQUIRE(emithash);
|
||||
BEAST_EXPECT(
|
||||
emithash == hookEmissions[0u].getFieldH256(sfEmittedTxnID));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
test_etxn_details(FeatureBitset features)
|
||||
{
|
||||
@@ -12734,117 +13185,118 @@ public:
|
||||
void
|
||||
testWithFeatures(FeatureBitset features)
|
||||
{
|
||||
testHooksOwnerDir(features);
|
||||
testHooksDisabled(features);
|
||||
testTxStructure(features);
|
||||
testInferHookSetOperation();
|
||||
testParams(features);
|
||||
testGrants(features);
|
||||
testHookCanEmit(features);
|
||||
// testHooksOwnerDir(features);
|
||||
// testHooksDisabled(features);
|
||||
// testTxStructure(features);
|
||||
// testInferHookSetOperation();
|
||||
// testParams(features);
|
||||
// testGrants(features);
|
||||
// testHookCanEmit(features);
|
||||
|
||||
testDelete(features);
|
||||
testInstall(features);
|
||||
testCreate(features);
|
||||
testWithTickets(features);
|
||||
// testDelete(features);
|
||||
// testInstall(features);
|
||||
// testCreate(features);
|
||||
// testWithTickets(features);
|
||||
|
||||
testUpdate(features);
|
||||
// testUpdate(features);
|
||||
|
||||
testNSDelete(features);
|
||||
testNSDeletePartial(features);
|
||||
testPageCap(features);
|
||||
// testNSDelete(features);
|
||||
// testNSDeletePartial(features);
|
||||
// testPageCap(features);
|
||||
|
||||
testFillCopy(features);
|
||||
// testFillCopy(features);
|
||||
|
||||
testWasm(features);
|
||||
test_accept(features);
|
||||
test_rollback(features);
|
||||
// testWasm(features);
|
||||
// test_accept(features);
|
||||
// test_rollback(features);
|
||||
|
||||
testGuards(features);
|
||||
// testGuards(features);
|
||||
|
||||
test_emit(features); //
|
||||
// test_etxn_burden(features); // tested above
|
||||
// test_etxn_generation(features); // tested above
|
||||
// test_otxn_burden(features); // tested above
|
||||
// test_otxn_generation(features); // tested above
|
||||
test_etxn_details(features); //
|
||||
test_etxn_fee_base(features); //
|
||||
test_etxn_nonce(features); //
|
||||
test_etxn_reserve(features); //
|
||||
test_fee_base(features); //
|
||||
// test_emit(features); //
|
||||
test_emit_atomic(features); //
|
||||
// // test_etxn_burden(features); // tested above
|
||||
// // test_etxn_generation(features); // tested above
|
||||
// // test_otxn_burden(features); // tested above
|
||||
// // test_otxn_generation(features); // tested above
|
||||
// test_etxn_details(features); //
|
||||
// test_etxn_fee_base(features); //
|
||||
// test_etxn_nonce(features); //
|
||||
// test_etxn_reserve(features); //
|
||||
// test_fee_base(features); //
|
||||
|
||||
test_otxn_field(features); //
|
||||
// test_otxn_field(features); //
|
||||
|
||||
test_ledger_keylet(features); //
|
||||
// test_ledger_keylet(features); //
|
||||
|
||||
test_float_compare(features); //
|
||||
test_float_divide(features); //
|
||||
test_float_int(features); //
|
||||
test_float_invert(features); //
|
||||
test_float_log(features); //
|
||||
test_float_mantissa(features); //
|
||||
test_float_mulratio(features); //
|
||||
test_float_multiply(features); //
|
||||
test_float_negate(features); //
|
||||
test_float_one(features); //
|
||||
test_float_root(features); //
|
||||
test_float_set(features); //
|
||||
test_float_sign(features); //
|
||||
test_float_sto(features); //
|
||||
test_float_sto_set(features); //
|
||||
test_float_sum(features); //
|
||||
// test_float_compare(features); //
|
||||
// test_float_divide(features); //
|
||||
// test_float_int(features); //
|
||||
// test_float_invert(features); //
|
||||
// test_float_log(features); //
|
||||
// test_float_mantissa(features); //
|
||||
// test_float_mulratio(features); //
|
||||
// test_float_multiply(features); //
|
||||
// test_float_negate(features); //
|
||||
// test_float_one(features); //
|
||||
// test_float_root(features); //
|
||||
// test_float_set(features); //
|
||||
// test_float_sign(features); //
|
||||
// test_float_sto(features); //
|
||||
// test_float_sto_set(features); //
|
||||
// test_float_sum(features); //
|
||||
|
||||
test_hook_account(features); //
|
||||
test_hook_again(features); //
|
||||
test_hook_hash(features); //
|
||||
test_hook_param(features); //
|
||||
test_hook_param_set(features); //
|
||||
test_hook_pos(features); //
|
||||
test_hook_skip(features); //
|
||||
// test_hook_account(features); //
|
||||
// test_hook_again(features); //
|
||||
// test_hook_hash(features); //
|
||||
// test_hook_param(features); //
|
||||
// test_hook_param_set(features); //
|
||||
// test_hook_pos(features); //
|
||||
// test_hook_skip(features); //
|
||||
|
||||
test_ledger_last_hash(features); //
|
||||
test_ledger_last_time(features); //
|
||||
test_ledger_nonce(features); //
|
||||
test_ledger_seq(features); //
|
||||
// test_ledger_last_hash(features); //
|
||||
// test_ledger_last_time(features); //
|
||||
// test_ledger_nonce(features); //
|
||||
// test_ledger_seq(features); //
|
||||
|
||||
test_meta_slot(features); //
|
||||
test_xpop_slot(features); //
|
||||
// test_meta_slot(features); //
|
||||
// test_xpop_slot(features); //
|
||||
|
||||
test_otxn_id(features); //
|
||||
test_otxn_slot(features); //
|
||||
test_otxn_type(features); //
|
||||
test_otxn_param(features); //
|
||||
// test_otxn_id(features); //
|
||||
// test_otxn_slot(features); //
|
||||
// test_otxn_type(features); //
|
||||
// test_otxn_param(features); //
|
||||
|
||||
test_slot(features); //
|
||||
test_slot_clear(features); //
|
||||
test_slot_count(features); //
|
||||
test_slot_float(features); //
|
||||
test_slot_set(features); //
|
||||
test_slot_size(features); //
|
||||
test_slot_subarray(features); //
|
||||
test_slot_subfield(features); //
|
||||
test_slot_type(features); //
|
||||
// test_slot(features); //
|
||||
// test_slot_clear(features); //
|
||||
// test_slot_count(features); //
|
||||
// test_slot_float(features); //
|
||||
// test_slot_set(features); //
|
||||
// test_slot_size(features); //
|
||||
// test_slot_subarray(features); //
|
||||
// test_slot_subfield(features); //
|
||||
// test_slot_type(features); //
|
||||
|
||||
test_state(features); //
|
||||
test_state_foreign(features); //
|
||||
test_state_foreign_set(features); //
|
||||
test_state_foreign_set_max(features); //
|
||||
test_state_set(features); //
|
||||
// test_state(features); //
|
||||
// test_state_foreign(features); //
|
||||
// test_state_foreign_set(features); //
|
||||
// test_state_foreign_set_max(features); //
|
||||
// test_state_set(features); //
|
||||
|
||||
test_sto_emplace(features); //
|
||||
test_sto_erase(features); //
|
||||
test_sto_subarray(features); //
|
||||
test_sto_subfield(features); //
|
||||
test_sto_validate(features); //
|
||||
// test_sto_emplace(features); //
|
||||
// test_sto_erase(features); //
|
||||
// test_sto_subarray(features); //
|
||||
// test_sto_subfield(features); //
|
||||
// test_sto_validate(features); //
|
||||
|
||||
test_trace(features); //
|
||||
test_trace_float(features); //
|
||||
test_trace_num(features); //
|
||||
// test_trace(features); //
|
||||
// test_trace_float(features); //
|
||||
// test_trace_num(features); //
|
||||
|
||||
test_util_accid(features); //
|
||||
test_util_keylet(features); //
|
||||
test_util_raddr(features); //
|
||||
test_util_sha512h(features); //
|
||||
test_util_verify(features); //
|
||||
// test_util_accid(features); //
|
||||
// test_util_keylet(features); //
|
||||
// test_util_raddr(features); //
|
||||
// test_util_sha512h(features); //
|
||||
// test_util_verify(features); //
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -13031,11 +13483,11 @@ SETHOOK_TEST(4, false)
|
||||
SETHOOK_TEST(5, true)
|
||||
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(SetHook0, app, ripple, 2);
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(SetHook1, app, ripple, 2);
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(SetHook2, app, ripple, 2);
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(SetHook3, app, ripple, 2);
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(SetHook4, app, ripple, 2);
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(SetHook5, app, ripple, 2);
|
||||
// BEAST_DEFINE_TESTSUITE_PRIO(SetHook1, app, ripple, 2);
|
||||
// BEAST_DEFINE_TESTSUITE_PRIO(SetHook2, app, ripple, 2);
|
||||
// BEAST_DEFINE_TESTSUITE_PRIO(SetHook3, app, ripple, 2);
|
||||
// BEAST_DEFINE_TESTSUITE_PRIO(SetHook4, app, ripple, 2);
|
||||
// BEAST_DEFINE_TESTSUITE_PRIO(SetHook5, app, ripple, 2);
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
#undef M
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -177,6 +177,13 @@ DECLARE_HOOK_FUNCTION(
|
||||
uint32_t write_len,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len);
|
||||
DECLARE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
emit_atomic,
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len);
|
||||
|
||||
DECLARE_HOOK_FUNCTION(int64_t, float_set, int32_t exponent, int64_t mantissa);
|
||||
DECLARE_HOOK_FUNCTION(int64_t, float_multiply, int64_t float1, int64_t float2);
|
||||
@@ -454,6 +461,7 @@ apply(
|
||||
std::map<std::vector<uint8_t>, std::vector<uint8_t>>> const&
|
||||
hookParamOverrides,
|
||||
HookStateMap& stateMap,
|
||||
XRPAmount& atomicEmitFeeRemaining,
|
||||
ripple::ApplyContext& applyCtx,
|
||||
ripple::AccountID const& account, /* the account the hook is INSTALLED ON
|
||||
not always the otxn account */
|
||||
@@ -489,7 +497,10 @@ struct HookResult
|
||||
|
||||
std::queue<std::shared_ptr<ripple::Transaction>>
|
||||
emittedTxn{}; // etx stored here until accept/rollback
|
||||
std::queue<std::shared_ptr<ripple::Transaction>>
|
||||
emittedAtomicTxn{}; // etx stored here until accept/rollback
|
||||
HookStateMap& stateMap;
|
||||
XRPAmount& atomicEmitFeeRemaining;
|
||||
uint16_t changedStateCount = 0;
|
||||
std::map<
|
||||
ripple::uint256, // hook hash
|
||||
@@ -581,6 +592,14 @@ finalizeHookResult(
|
||||
ripple::ApplyContext&,
|
||||
bool doEmit);
|
||||
|
||||
bool
|
||||
emitAtomicTransactions(
|
||||
ripple::Application& app,
|
||||
ripple::OpenView& view,
|
||||
ripple::uint256 const& parentTxnId,
|
||||
ripple::STArray const& hookEmissions,
|
||||
beast::Journal j);
|
||||
|
||||
// write state map to ledger
|
||||
ripple::TER
|
||||
finalizeHookState(
|
||||
@@ -813,6 +832,7 @@ public:
|
||||
ADD_HOOK_FUNCTION(util_keylet, ctx);
|
||||
|
||||
ADD_HOOK_FUNCTION(emit, ctx);
|
||||
ADD_HOOK_FUNCTION(emit_atomic, ctx);
|
||||
ADD_HOOK_FUNCTION(etxn_burden, ctx);
|
||||
ADD_HOOK_FUNCTION(etxn_fee_base, ctx);
|
||||
ADD_HOOK_FUNCTION(etxn_details, ctx);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <xrpld/app/misc/NetworkOPs.h>
|
||||
#include <xrpld/app/misc/Transaction.h>
|
||||
#include <xrpld/app/misc/TxQ.h>
|
||||
#include <xrpld/app/tx/apply.h>
|
||||
#include <xrpld/app/tx/detail/Import.h>
|
||||
#include <xrpld/app/tx/detail/NFTokenUtils.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
@@ -1300,6 +1301,7 @@ hook::apply(
|
||||
std::map<std::vector<uint8_t>, std::vector<uint8_t>>> const&
|
||||
hookParamOverrides,
|
||||
HookStateMap& stateMap,
|
||||
XRPAmount& atomicEmitFeeRemaining,
|
||||
ApplyContext& applyCtx,
|
||||
ripple::AccountID const& account, /* the account the hook is INSTALLED ON
|
||||
not always the otxn account */
|
||||
@@ -1324,6 +1326,7 @@ hook::apply(
|
||||
.otxnAccount = applyCtx.tx.getAccountID(sfAccount),
|
||||
.hookNamespace = hookNamespace,
|
||||
.stateMap = stateMap,
|
||||
.atomicEmitFeeRemaining = atomicEmitFeeRemaining,
|
||||
.changedStateCount = 0,
|
||||
.hookParamOverrides = hookParamOverrides,
|
||||
.hookParams = hookParams,
|
||||
@@ -2018,10 +2021,9 @@ hook::finalizeHookResult(
|
||||
|
||||
if (doEmit)
|
||||
{
|
||||
DBG_PRINTF("emitted txn count: %d\n", hookResult.emittedTxn.size());
|
||||
for (; hookResult.emittedTxn.size() > 0; hookResult.emittedTxn.pop())
|
||||
{
|
||||
auto& tpTrans = hookResult.emittedTxn.front();
|
||||
auto const insertEmittedTxn =
|
||||
[&](std::shared_ptr<ripple::Transaction> tpTrans,
|
||||
bool atomic) -> TER {
|
||||
auto& id = tpTrans->getID();
|
||||
JLOG(j.trace()) << "HookEmit[" << HR_ACC() << "]: " << id;
|
||||
|
||||
@@ -2049,7 +2051,7 @@ hook::finalizeHookResult(
|
||||
ptr->add(s);
|
||||
SerialIter sit(s.slice());
|
||||
|
||||
sleEmitted->emplace_back(ripple::STObject(sit, sfEmittedTxn));
|
||||
sleEmitted->set(ripple::STObject(sit, sfEmittedTxn));
|
||||
auto page = applyCtx.view().dirInsert(
|
||||
keylet::emittedDir(), emittedId, [&](SLE::ref sle) {
|
||||
(*sle)[sfFlags] = lsfEmittedDir;
|
||||
@@ -2058,6 +2060,7 @@ hook::finalizeHookResult(
|
||||
if (page)
|
||||
{
|
||||
(*sleEmitted)[sfOwnerNode] = *page;
|
||||
(*sleEmitted)[sfFlags] = atomic ? 1 : 0;
|
||||
applyCtx.view().insert(sleEmitted);
|
||||
}
|
||||
else
|
||||
@@ -2069,6 +2072,24 @@ hook::finalizeHookResult(
|
||||
return tecDIR_FULL;
|
||||
}
|
||||
}
|
||||
return tesSUCCESS;
|
||||
};
|
||||
|
||||
DBG_PRINTF("emitted txn count: %d\n", hookResult.emittedTxn.size());
|
||||
for (; hookResult.emittedTxn.size() > 0; hookResult.emittedTxn.pop())
|
||||
{
|
||||
auto& tpTrans = hookResult.emittedTxn.front();
|
||||
insertEmittedTxn(tpTrans, false);
|
||||
}
|
||||
|
||||
DBG_PRINTF(
|
||||
"emitted atomic txn count: %d\n",
|
||||
hookResult.emittedAtomicTxn.size());
|
||||
for (; hookResult.emittedAtomicTxn.size() > 0;
|
||||
hookResult.emittedAtomicTxn.pop())
|
||||
{
|
||||
auto& tpTrans = hookResult.emittedAtomicTxn.front();
|
||||
insertEmittedTxn(tpTrans, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2134,6 +2155,57 @@ hook::finalizeHookResult(
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
bool
|
||||
hook::emitAtomicTransactions(
|
||||
ripple::Application& app,
|
||||
ripple::OpenView& view,
|
||||
ripple::uint256 const& parentTxnId,
|
||||
ripple::STArray const& hookEmissions,
|
||||
beast::Journal j_)
|
||||
{
|
||||
for (auto const& emission : hookEmissions)
|
||||
{
|
||||
auto const& etxnId = emission.getFieldH256(sfEmittedTxnID);
|
||||
auto const& keylet = keylet::emittedTxn(etxnId);
|
||||
|
||||
auto sleItem = view.read(keylet);
|
||||
if (!sleItem)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
LedgerEntryType const nodeType{
|
||||
safe_cast<LedgerEntryType>((*sleItem)[sfLedgerEntryType])};
|
||||
|
||||
if (nodeType != ltEMITTED_TXN)
|
||||
{
|
||||
JLOG(j_.warn())
|
||||
<< "EmittedTxn processing: emitted directory contained "
|
||||
"non ltEMITTED_TXN type";
|
||||
// RH TODO: if this ever happens the entry should be
|
||||
// gracefully removed (somehow)
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const& emitted = const_cast<ripple::STLedgerEntry&>(*sleItem)
|
||||
.getField(sfEmittedTxn)
|
||||
.downcast<STObject>();
|
||||
|
||||
auto s = std::make_shared<ripple::Serializer>();
|
||||
emitted.add(*s);
|
||||
SerialIter sitTrans(s->slice());
|
||||
|
||||
auto const& stpTrans = std::make_shared<STTx const>(std::ref(sitTrans));
|
||||
|
||||
auto const result = ripple::apply(
|
||||
app, view, parentTxnId, *stpTrans, tapATOMIC_EMIT, j_);
|
||||
|
||||
if (!result.applied || !isTesSuccess(result.ter))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Retrieve the state into write_ptr identified by the key in kread_ptr */
|
||||
DEFINE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
@@ -3354,7 +3426,9 @@ DEFINE_HOOK_FUNCTION(
|
||||
if (hookCtx.expected_etxn_count < 0)
|
||||
return PREREQUISITE_NOT_MET;
|
||||
|
||||
if (hookCtx.result.emittedTxn.size() >= hookCtx.expected_etxn_count)
|
||||
if (hookCtx.result.emittedTxn.size() +
|
||||
hookCtx.result.emittedAtomicTxn.size() >=
|
||||
hookCtx.expected_etxn_count)
|
||||
return TOO_MANY_EMITTED_TXN;
|
||||
|
||||
ripple::Blob blob{memory + read_ptr, memory + read_ptr + read_len};
|
||||
@@ -3687,6 +3761,386 @@ DEFINE_HOOK_FUNCTION(
|
||||
HOOK_TEARDOWN();
|
||||
}
|
||||
|
||||
/* Emit a transaction from this hook. Transaction must be in STObject form,
|
||||
* fully formed and valid. XRPLD does not modify transactions it only checks
|
||||
* them for validity. */
|
||||
DEFINE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
emit_atomic,
|
||||
uint32_t write_ptr,
|
||||
uint32_t write_len,
|
||||
uint32_t read_ptr,
|
||||
uint32_t read_len)
|
||||
{
|
||||
HOOK_SETUP(); // populates memory_ctx, memory, memory_length, applyCtx,
|
||||
// hookCtx on current stack
|
||||
|
||||
if (NOT_IN_BOUNDS(read_ptr, read_len, memory_length))
|
||||
return OUT_OF_BOUNDS;
|
||||
|
||||
if (NOT_IN_BOUNDS(write_ptr, write_len, memory_length))
|
||||
return OUT_OF_BOUNDS;
|
||||
|
||||
if (write_len < 32)
|
||||
return TOO_SMALL;
|
||||
|
||||
auto& app = hookCtx.applyCtx.app;
|
||||
|
||||
// Doesn't allow to call Callbackable Hook
|
||||
if (hookCtx.result.hasCallback || hookCtx.result.isCallback)
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: emit_atomic: Not allowed in Callbackable Hook.";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
// Doesn't allow to call in Weak Execution
|
||||
if (!hookCtx.result.isStrong)
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: emit_atomic: Not allowed in Weak Execution.";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
if (hookCtx.expected_etxn_count < 0)
|
||||
return PREREQUISITE_NOT_MET;
|
||||
|
||||
if (hookCtx.result.emittedTxn.size() +
|
||||
hookCtx.result.emittedAtomicTxn.size() >=
|
||||
hookCtx.expected_etxn_count)
|
||||
return TOO_MANY_EMITTED_TXN;
|
||||
|
||||
ripple::Blob blob{memory + read_ptr, memory + read_ptr + read_len};
|
||||
std::shared_ptr<STTx const> stpTrans;
|
||||
try
|
||||
{
|
||||
stpTrans = std::make_shared<STTx const>(
|
||||
SerialIter{memory + read_ptr, read_len});
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC() << "]: Failed " << e.what()
|
||||
<< "\n";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
if (isPseudoTx(*stpTrans))
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: Attempted to emit pseudo txn.";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
ripple::TxType txType = stpTrans->getTxnType();
|
||||
|
||||
ripple::uint256 const& hookCanEmit = hookCtx.result.hookCanEmit;
|
||||
if (!hook::canEmit(txType, hookCanEmit))
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: Hook cannot emit this txn.";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
// check the emitted txn is valid
|
||||
/* Emitted TXN rules
|
||||
* 0. Account must match the hook account
|
||||
* 1. Sequence: 0
|
||||
* 2. PubSigningKey: 000000000000000
|
||||
* 3. sfEmitDetails present and valid
|
||||
* 4. No sfTxnSignature
|
||||
* 5. LastLedgerSeq == current ledger
|
||||
* 6. FirstLedgerSeq == current ledger
|
||||
* 7. Fee must be correctly high
|
||||
* 8. The generation cannot be higher than 10
|
||||
*/
|
||||
|
||||
// rule 0: account must match the hook account
|
||||
if (!stpTrans->isFieldPresent(sfAccount) ||
|
||||
stpTrans->getAccountID(sfAccount) != hookCtx.result.account)
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: sfAccount does not match hook account";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
// rule 1: sfSequence must be present and 0
|
||||
// if (!stpTrans->isFieldPresent(sfSequence) ||
|
||||
// stpTrans->getFieldU32(sfSequence) != 0)
|
||||
// {
|
||||
// JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
// << "]: sfSequence missing or non-zero";
|
||||
// return EMISSION_FAILURE;
|
||||
// }
|
||||
|
||||
// rule 2: sfSigningPubKey must be present and 00...00
|
||||
if (!stpTrans->isFieldPresent(sfSigningPubKey))
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: sfSigningPubKey missing";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
auto const pk = stpTrans->getSigningPubKey();
|
||||
if (pk.size() != 33 && pk.size() != 0)
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: sfSigningPubKey present but wrong size"
|
||||
<< " expecting 33 bytes";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
for (int i = 0; i < pk.size(); ++i)
|
||||
if (pk[i] != 0)
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: sfSigningPubKey present but non-zero.";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
// rule 2.a: no signers
|
||||
if (stpTrans->isFieldPresent(sfSigners))
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: sfSigners not allowed in emitted txns.";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
// rule 2.b: ticketseq cannot be used
|
||||
if (stpTrans->isFieldPresent(sfTicketSequence))
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: sfTicketSequence not allowed in emitted txns.";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
// rule 2.c sfAccountTxnID not allowed
|
||||
if (stpTrans->isFieldPresent(sfAccountTxnID))
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: sfAccountTxnID not allowed in emitted txns.";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
// rule 3: sfEmitDetails must be present and valid
|
||||
if (!stpTrans->isFieldPresent(sfEmitDetails))
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: sfEmitDetails missing.";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
auto const& emitDetails = const_cast<ripple::STTx&>(*stpTrans)
|
||||
.getField(sfEmitDetails)
|
||||
.downcast<STObject>();
|
||||
|
||||
if (!emitDetails.isFieldPresent(sfEmitGeneration) ||
|
||||
!emitDetails.isFieldPresent(sfEmitBurden) ||
|
||||
!emitDetails.isFieldPresent(sfEmitParentTxnID) ||
|
||||
!emitDetails.isFieldPresent(sfEmitNonce) ||
|
||||
!emitDetails.isFieldPresent(sfEmitHookHash))
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: sfEmitDetails malformed.";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
// rule 8: emit generation cannot exceed 10
|
||||
if (emitDetails.getFieldU32(sfEmitGeneration) >= 10)
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: sfEmitGeneration was 10 or more.";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
uint32_t gen = emitDetails.getFieldU32(sfEmitGeneration);
|
||||
uint64_t bur = emitDetails.getFieldU64(sfEmitBurden);
|
||||
ripple::uint256 const& pTxnID = emitDetails.getFieldH256(sfEmitParentTxnID);
|
||||
ripple::uint256 const& nonce = emitDetails.getFieldH256(sfEmitNonce);
|
||||
|
||||
if (emitDetails.isFieldPresent(sfEmitCallback))
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: callback not supported yet in emit_atomic.";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
auto const& hash = emitDetails.getFieldH256(sfEmitHookHash);
|
||||
|
||||
uint32_t gen_proper = etxn_generation(hookCtx, frameCtx);
|
||||
|
||||
if (gen != gen_proper)
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: sfEmitGeneration provided in EmitDetails "
|
||||
<< "not correct (" << gen << ") "
|
||||
<< "should be " << gen_proper;
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
uint64_t bur_proper = etxn_burden(hookCtx, frameCtx);
|
||||
if (bur != bur_proper)
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: sfEmitBurden provided in EmitDetails "
|
||||
<< "was not correct (" << bur << ") "
|
||||
<< "should be " << bur_proper;
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
if (pTxnID != applyCtx.tx.getTransactionID())
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: sfEmitParentTxnID provided in EmitDetails "
|
||||
<< "was not correct";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
if (hookCtx.nonce_used.find(nonce) == hookCtx.nonce_used.end())
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: sfEmitNonce provided in EmitDetails "
|
||||
<< "was not generated by nonce api";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
if (hash != hookCtx.result.hookHash)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "HookEmit[" << HC_ACC()
|
||||
<< "]: sfEmitHookHash must be the hash of the emitting hook";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
// rule 4: sfTxnSignature must be absent
|
||||
if (stpTrans->isFieldPresent(sfTxnSignature))
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: sfTxnSignature is present but should not be";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
// rule 5: LastLedgerSeq must be present and after current ledger
|
||||
if (!stpTrans->isFieldPresent(sfLastLedgerSequence))
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: sfLastLedgerSequence missing";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
uint32_t tx_lls = stpTrans->getFieldU32(sfLastLedgerSequence);
|
||||
uint32_t ledgerSeq = view.info().seq;
|
||||
|
||||
if (tx_lls != ledgerSeq)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "HookEmit[" << HC_ACC()
|
||||
<< "]: sfLastLedgerSequence cannot be equal to current seq";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
// rule 6
|
||||
if (!stpTrans->isFieldPresent(sfFirstLedgerSequence) ||
|
||||
stpTrans->getFieldU32(sfFirstLedgerSequence) != ledgerSeq)
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: sfFirstLedgerSequence must be present and "
|
||||
<< "= LastLedgerSequence";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
// rule 7 check the emitted txn pays the appropriate fee
|
||||
int64_t minfee = etxn_fee_base(hookCtx, frameCtx, read_ptr, read_len);
|
||||
|
||||
if (minfee < 0)
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: Fee could not be calculated";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
if (!stpTrans->isFieldPresent(sfFee))
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: Fee missing from emitted tx";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
int64_t fee = stpTrans->getFieldAmount(sfFee).xrp().drops();
|
||||
if (fee < minfee)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "HookEmit[" << HC_ACC()
|
||||
<< "]: Fee on emitted txn is less than the minimum required fee";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
if (hookCtx.result.atomicEmitFeeRemaining < XRPAmount(fee))
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: Fee on emitted txn is greater than the atomic "
|
||||
"emit fee remaining";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
std::string reason;
|
||||
auto tpTrans = std::make_shared<Transaction>(stpTrans, reason, app);
|
||||
if (tpTrans->getStatus() != NEW)
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: tpTrans->getStatus() != NEW";
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
// preflight the transaction
|
||||
auto preflightResult = ripple::preflight(
|
||||
applyCtx.app,
|
||||
applyCtx.view().rules(),
|
||||
*stpTrans,
|
||||
ripple::ApplyFlags::tapPREFLIGHT_EMIT,
|
||||
j);
|
||||
|
||||
if (!isTesSuccess(preflightResult.ter))
|
||||
{
|
||||
JLOG(j.trace()) << "HookEmit[" << HC_ACC()
|
||||
<< "]: Transaction preflight failure: "
|
||||
<< preflightResult.ter;
|
||||
return EMISSION_FAILURE;
|
||||
}
|
||||
|
||||
auto const& txID = tpTrans->getID();
|
||||
|
||||
if (txID.size() > write_len)
|
||||
return TOO_SMALL;
|
||||
|
||||
if (NOT_IN_BOUNDS(write_ptr, txID.size(), memory_length))
|
||||
return OUT_OF_BOUNDS;
|
||||
|
||||
auto const write_txid = [&]() -> int64_t {
|
||||
WRITE_WASM_MEMORY_AND_RETURN(
|
||||
write_ptr,
|
||||
txID.size(),
|
||||
txID.data(),
|
||||
txID.size(),
|
||||
memory,
|
||||
memory_length);
|
||||
};
|
||||
|
||||
int64_t result = write_txid();
|
||||
|
||||
if (result == 32)
|
||||
{
|
||||
hookCtx.result.atomicEmitFeeRemaining -= XRPAmount(fee);
|
||||
XRPL_ASSERT(
|
||||
hookCtx.result.atomicEmitFeeRemaining >= XRPAmount(0),
|
||||
"atomicEmitFeeRemaining is negative");
|
||||
|
||||
hookCtx.result.emittedAtomicTxn.push(tpTrans);
|
||||
}
|
||||
|
||||
return result;
|
||||
HOOK_TEARDOWN();
|
||||
}
|
||||
|
||||
// When implemented will return the hash of the current hook
|
||||
DEFINE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
|
||||
@@ -129,6 +129,15 @@ apply(
|
||||
ApplyFlags flags,
|
||||
beast::Journal journal);
|
||||
|
||||
ApplyResult
|
||||
apply(
|
||||
Application& app,
|
||||
OpenView& view,
|
||||
uint256 const& parentBatchId,
|
||||
STTx const& tx,
|
||||
ApplyFlags flags,
|
||||
beast::Journal journal);
|
||||
|
||||
/** Enum class for return value from `applyTransaction`
|
||||
|
||||
@see applyTransaction
|
||||
|
||||
@@ -47,22 +47,31 @@ ApplyContext::ApplyContext(
|
||||
, parentBatchId_(parentBatchId)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
parentBatchId.has_value() == ((flags_ & tapBATCH) == tapBATCH),
|
||||
"Parent Batch ID should be set if batch apply flag is set");
|
||||
view_.emplace(&base_, flags_);
|
||||
parentBatchId.has_value() ==
|
||||
((flags_ & (tapBATCH | tapATOMIC_EMIT)) > 0),
|
||||
"Parent Batch ID should be set if batch or AtomicEmit flag is set");
|
||||
view_.emplace(&base_.view(), flags_);
|
||||
}
|
||||
|
||||
void
|
||||
ApplyContext::discard()
|
||||
{
|
||||
view_.emplace(&base_, flags_);
|
||||
base_.discard();
|
||||
view_.emplace(&base_.view(), flags_);
|
||||
}
|
||||
|
||||
void
|
||||
ApplyContext::finalize()
|
||||
{
|
||||
base_.commit();
|
||||
view_.emplace(&base_.view(), flags_);
|
||||
}
|
||||
|
||||
std::optional<TxMeta>
|
||||
ApplyContext::apply(TER ter)
|
||||
{
|
||||
return view_->apply(
|
||||
base_, tx, ter, parentBatchId_, flags_ & tapDRY_RUN, journal);
|
||||
base_.view(), tx, ter, parentBatchId_, flags_ & tapDRY_RUN, journal);
|
||||
}
|
||||
|
||||
std::size_t
|
||||
@@ -78,7 +87,7 @@ ApplyContext::visit(std::function<void(
|
||||
std::shared_ptr<SLE const> const&,
|
||||
std::shared_ptr<SLE const> const&)> const& func)
|
||||
{
|
||||
view_->visit(base_, func);
|
||||
view_->visit(base_.view(), func);
|
||||
}
|
||||
|
||||
TER
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/core/Config.h>
|
||||
#include <xrpld/ledger/ApplyViewImpl.h>
|
||||
#include <xrpld/ledger/OpenViewSandbox.h>
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
@@ -64,7 +66,8 @@ public:
|
||||
journal)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
(flags & tapBATCH) == 0, "Batch apply flag should not be set");
|
||||
(flags & (tapBATCH | tapATOMIC_EMIT)) == 0,
|
||||
"Batch or AtomicEmit flag should not be set");
|
||||
}
|
||||
|
||||
Application& app;
|
||||
@@ -73,6 +76,12 @@ public:
|
||||
XRPAmount const baseFee;
|
||||
beast::Journal const journal;
|
||||
|
||||
OpenView&
|
||||
openView()
|
||||
{
|
||||
return base_.view();
|
||||
}
|
||||
|
||||
ApplyView&
|
||||
view()
|
||||
{
|
||||
@@ -109,6 +118,10 @@ public:
|
||||
void
|
||||
discard();
|
||||
|
||||
/** Finalize changes. */
|
||||
void
|
||||
finalize();
|
||||
|
||||
/** Apply the transaction result to the base. */
|
||||
std::optional<TxMeta> apply(TER);
|
||||
|
||||
@@ -134,7 +147,7 @@ public:
|
||||
generateProvisionalMeta()
|
||||
{
|
||||
return view_->generateProvisionalMeta(
|
||||
base_, tx, parentBatchId_, journal);
|
||||
base_.view(), tx, parentBatchId_, journal);
|
||||
}
|
||||
|
||||
/** Applies all invariant checkers one by one.
|
||||
@@ -169,7 +182,7 @@ private:
|
||||
XRPAmount const fee,
|
||||
std::index_sequence<Is...>);
|
||||
|
||||
OpenView& base_;
|
||||
OpenViewSandbox base_;
|
||||
ApplyFlags flags_;
|
||||
std::optional<ApplyViewImpl> view_;
|
||||
|
||||
|
||||
@@ -585,12 +585,21 @@ Change::activateXahauGenesis()
|
||||
for (auto const& [hookOn, wasmBytes, params] : genesis_hooks)
|
||||
{
|
||||
std::ostringstream loggerStream;
|
||||
auto result = validateGuards(
|
||||
auto rulesVersion =
|
||||
(ctx_.view().rules().enabled(featureHooksUpdate1)
|
||||
? hook_api::GuardRules::HooksUpdate1
|
||||
: 0U) +
|
||||
(ctx_.view().rules().enabled(fix20250131)
|
||||
? hook_api::GuardRules::Fix20250131
|
||||
: 0U) +
|
||||
(ctx_.view().rules().enabled(featureAtomicEmit)
|
||||
? hook_api::GuardRules::AtomicEmit
|
||||
: 0U);
|
||||
auto const result = validateGuards(
|
||||
wasmBytes, // wasm to verify
|
||||
loggerStream,
|
||||
"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
|
||||
(ctx_.view().rules().enabled(featureHooksUpdate1) ? 1 : 0) +
|
||||
(ctx_.view().rules().enabled(fix20250131) ? 2 : 0));
|
||||
rulesVersion);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
|
||||
@@ -227,7 +227,8 @@ SetHook::inferOperation(STObject const& hookSetObj)
|
||||
!hookSetObj.isFieldPresent(sfHookOn) &&
|
||||
!hookSetObj.isFieldPresent(sfHookCanEmit) &&
|
||||
!hookSetObj.isFieldPresent(sfHookApiVersion) &&
|
||||
!hookSetObj.isFieldPresent(sfFlags))
|
||||
!hookSetObj.isFieldPresent(sfFlags) &&
|
||||
!hookSetObj.isFieldPresent(sfHookAtomicEmitFee))
|
||||
return hsoNOOP;
|
||||
|
||||
uint32_t flags = hookSetObj.isFieldPresent(sfFlags)
|
||||
@@ -263,7 +264,8 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
hookSetObj.isFieldPresent(sfHookCanEmit) ||
|
||||
hookSetObj.isFieldPresent(sfHookApiVersion) ||
|
||||
!hookSetObj.isFieldPresent(sfFlags) ||
|
||||
!hookSetObj.isFieldPresent(sfHookNamespace))
|
||||
!hookSetObj.isFieldPresent(sfHookNamespace) ||
|
||||
!hookSetObj.isFieldPresent(sfHookAtomicEmitFee))
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "HookSet(" << hook::log::NSDELETE_FIELD
|
||||
<< ")[" << HS_ACC()
|
||||
@@ -293,7 +295,8 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
hookSetObj.isFieldPresent(sfHookCanEmit) ||
|
||||
hookSetObj.isFieldPresent(sfHookApiVersion) ||
|
||||
hookSetObj.isFieldPresent(sfHookNamespace) ||
|
||||
!hookSetObj.isFieldPresent(sfFlags))
|
||||
!hookSetObj.isFieldPresent(sfFlags) ||
|
||||
!hookSetObj.isFieldPresent(sfHookAtomicEmitFee))
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::DELETE_FIELD << ")[" << HS_ACC()
|
||||
@@ -392,6 +395,18 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hookSetObj.isFieldPresent(sfHookAtomicEmitFee))
|
||||
{
|
||||
auto const fee = hookSetObj.getFieldAmount(sfHookAtomicEmitFee);
|
||||
if (!isXRP(fee))
|
||||
return false;
|
||||
|
||||
// TODO: Update maxFee
|
||||
XRPAmount maxFee{1'000'000};
|
||||
if (fee < beast::zero || fee > maxFee)
|
||||
return false;
|
||||
}
|
||||
|
||||
// namespace may be valid, if the user so chooses
|
||||
// hookon may be present if the user so chooses
|
||||
// flags may be present if the user so chooses
|
||||
@@ -462,6 +477,19 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
// pass
|
||||
}
|
||||
|
||||
// validate sfHookAtomicEmitFee
|
||||
if (hookSetObj.isFieldPresent(sfHookAtomicEmitFee))
|
||||
{
|
||||
auto const fee = hookSetObj.getFieldAmount(sfHookAtomicEmitFee);
|
||||
if (!isXRP(fee))
|
||||
return false;
|
||||
|
||||
// TODO: Update maxFee
|
||||
XRPAmount maxFee{1'000'000};
|
||||
if (fee < beast::zero || fee > maxFee)
|
||||
return false;
|
||||
}
|
||||
|
||||
// finally validate web assembly byte code
|
||||
{
|
||||
if (!hookSetObj.isFieldPresent(sfCreateCode))
|
||||
@@ -485,12 +513,21 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
hsacc = ss.str();
|
||||
}
|
||||
|
||||
auto rulesVersion = (ctx.rules.enabled(featureHooksUpdate1)
|
||||
? hook_api::GuardRules::HooksUpdate1
|
||||
: 0U) +
|
||||
(ctx.rules.enabled(fix20250131)
|
||||
? hook_api::GuardRules::Fix20250131
|
||||
: 0U) +
|
||||
(ctx.rules.enabled(featureAtomicEmit)
|
||||
? hook_api::GuardRules::AtomicEmit
|
||||
: 0U);
|
||||
|
||||
auto result = validateGuards(
|
||||
hook, // wasm to verify
|
||||
logger,
|
||||
hsacc,
|
||||
(ctx.rules.enabled(featureHooksUpdate1) ? 1 : 0) +
|
||||
(ctx.rules.enabled(fix20250131) ? 2 : 0));
|
||||
rulesVersion);
|
||||
|
||||
if (ctx.j.trace())
|
||||
{
|
||||
@@ -728,6 +765,10 @@ SetHook::preflight(PreflightContext const& ctx)
|
||||
hookSetObj.isFieldPresent(sfHookCanEmit))
|
||||
return temDISABLED;
|
||||
|
||||
if (!ctx.rules.enabled(featureAtomicEmit) &&
|
||||
hookSetObj.isFieldPresent(sfHookAtomicEmitFee))
|
||||
return temDISABLED;
|
||||
|
||||
for (auto const& hookSetElement : hookSetObj)
|
||||
{
|
||||
auto const& name = hookSetElement.getFName();
|
||||
@@ -736,7 +777,7 @@ SetHook::preflight(PreflightContext const& ctx)
|
||||
name != sfHookNamespace && name != sfHookParameters &&
|
||||
name != sfHookOn && name != sfHookGrants &&
|
||||
name != sfHookApiVersion && name != sfFlags &&
|
||||
name != sfHookCanEmit)
|
||||
name != sfHookCanEmit && name != sfHookAtomicEmitFee)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::HOOK_INVALID_FIELD << ")["
|
||||
@@ -1242,6 +1283,10 @@ SetHook::setHook()
|
||||
std::optional<uint256> newHookCanEmit;
|
||||
std::optional<uint256> defHookCanEmit;
|
||||
|
||||
std::optional<STAmount> oldHookAtomicEmitFee;
|
||||
std::optional<STAmount> newHookAtomicEmitFee;
|
||||
std::optional<STAmount> defHookAtomicEmitFee;
|
||||
|
||||
// when hsoCREATE is invoked it populates this variable in case the hook
|
||||
// definition already exists and the operation falls through into a
|
||||
// hsoINSTALL operation instead
|
||||
@@ -1310,6 +1355,16 @@ SetHook::setHook()
|
||||
oldHookCanEmit = oldHook->get().getFieldH256(sfHookCanEmit);
|
||||
else if (defHookCanEmit)
|
||||
oldHookCanEmit = *defHookCanEmit;
|
||||
|
||||
if (oldDefSLE && oldDefSLE->isFieldPresent(sfHookAtomicEmitFee))
|
||||
defHookAtomicEmitFee =
|
||||
oldDefSLE->getFieldAmount(sfHookAtomicEmitFee);
|
||||
|
||||
if (oldHook && oldHook->get().isFieldPresent(sfHookAtomicEmitFee))
|
||||
oldHookAtomicEmitFee =
|
||||
oldHook->get().getFieldAmount(sfHookAtomicEmitFee);
|
||||
else if (defHookAtomicEmitFee)
|
||||
oldHookAtomicEmitFee = *defHookAtomicEmitFee;
|
||||
}
|
||||
|
||||
// in preparation for three way merge populate fields if they are
|
||||
@@ -1329,6 +1384,10 @@ SetHook::setHook()
|
||||
if (hookSetObj->get().isFieldPresent(sfHookCanEmit))
|
||||
newHookCanEmit = hookSetObj->get().getFieldH256(sfHookCanEmit);
|
||||
|
||||
if (hookSetObj->get().isFieldPresent(sfHookAtomicEmitFee))
|
||||
newHookAtomicEmitFee =
|
||||
hookSetObj->get().getFieldAmount(sfHookAtomicEmitFee);
|
||||
|
||||
if (hookSetObj->get().isFieldPresent(sfHookNamespace))
|
||||
{
|
||||
newNamespace = hookSetObj->get().getFieldH256(sfHookNamespace);
|
||||
@@ -1485,6 +1544,17 @@ SetHook::setHook()
|
||||
newHook.setFieldH256(sfHookCanEmit, *newHookCanEmit);
|
||||
}
|
||||
|
||||
// set the hookAtomeicFee field if it differs from definition
|
||||
if (newHookAtomicEmitFee)
|
||||
{
|
||||
if (defHookAtomicEmitFee.has_value() &&
|
||||
*defHookAtomicEmitFee == *newHookAtomicEmitFee)
|
||||
{
|
||||
if (newHook.isFieldPresent(sfHookAtomicEmitFee))
|
||||
newHook.makeFieldAbsent(sfHookAtomicEmitFee);
|
||||
}
|
||||
}
|
||||
|
||||
// parameters
|
||||
if (hookSetObj->get().isFieldPresent(sfHookParameters) &&
|
||||
hookSetObj->get().getFieldArray(sfHookParameters).empty())
|
||||
@@ -1660,6 +1730,11 @@ SetHook::setHook()
|
||||
sfHookCallbackFee,
|
||||
XRPAmount{
|
||||
hook::computeExecutionFee(maxInstrCountCbak)});
|
||||
if (hookSetObj->get().isFieldPresent(sfHookAtomicEmitFee))
|
||||
newHookDef->setFieldAmount(
|
||||
sfHookAtomicEmitFee,
|
||||
hookSetObj->get().getFieldAmount(
|
||||
sfHookAtomicEmitFee));
|
||||
|
||||
if (flags)
|
||||
newHookDef->setFieldU32(sfFlags, newFlags);
|
||||
|
||||
@@ -289,15 +289,23 @@ Transactor::calculateHookChainFee(
|
||||
if (hook::canHook(tx.getTxnType(), hookOn) &&
|
||||
(!collectCallsOnly || (flags & hook::hsfCOLLECT)))
|
||||
{
|
||||
XRPAmount const toAdd{hookDef->getFieldAmount(sfFee).xrp().drops()};
|
||||
XRPAmount const toAddFee{
|
||||
hookDef->getFieldAmount(sfFee).xrp().drops()};
|
||||
|
||||
XRPAmount const toAddAtomicEmitFee{
|
||||
hookObj.isFieldPresent(sfHookAtomicEmitFee)
|
||||
? hookObj.getFieldAmount(sfHookAtomicEmitFee).xrp().drops()
|
||||
: hookDef->isFieldPresent(sfHookAtomicEmitFee)
|
||||
? hookDef->getFieldAmount(sfHookAtomicEmitFee).xrp().drops()
|
||||
: 0};
|
||||
|
||||
// this overflow should never happen, if somehow it does
|
||||
// fee is set to the largest possible valid xrp value to force
|
||||
// fail the transaction
|
||||
if (fee + toAdd < fee)
|
||||
if (fee + toAddFee + toAddAtomicEmitFee < fee)
|
||||
fee = XRPAmount{INITIAL_XRP.drops()};
|
||||
else
|
||||
fee += toAdd;
|
||||
fee += toAddFee + toAddAtomicEmitFee;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -493,7 +501,7 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
|
||||
|
||||
if (feePaid < feeDue)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
JLOG(ctx.j.fatal())
|
||||
<< "Insufficient fee paid: " << to_string(feePaid) << "/"
|
||||
<< to_string(feeDue);
|
||||
return telINSUF_FEE_P;
|
||||
@@ -1368,6 +1376,111 @@ Transactor::reset(XRPAmount fee)
|
||||
return {ter, fee};
|
||||
}
|
||||
|
||||
std::pair<TER, XRPAmount>
|
||||
Transactor::checkInvariants(TER result, XRPAmount fee)
|
||||
{
|
||||
// Check invariants: if `tecINVARIANT_FAILED` is not returned, we can
|
||||
// proceed to apply the tx
|
||||
result = ctx_.checkInvariants(result, fee);
|
||||
|
||||
if (result == tecINVARIANT_FAILED)
|
||||
{
|
||||
// if invariants checking failed again, reset the context and
|
||||
// attempt to only claim a fee.
|
||||
auto const resetResult = reset(fee);
|
||||
if (!isTesSuccess(resetResult.first))
|
||||
result = resetResult.first;
|
||||
|
||||
fee = resetResult.second;
|
||||
|
||||
// Check invariants again to ensure the fee claiming doesn't
|
||||
// violate invariants.
|
||||
if (isTesSuccess(result) || isTecClaim(result))
|
||||
result = ctx_.checkInvariants(result, fee);
|
||||
}
|
||||
|
||||
return {result, fee};
|
||||
}
|
||||
|
||||
void
|
||||
Transactor::balanceRewards(TER result)
|
||||
{
|
||||
TxMeta metaRaw = ctx_.generateProvisionalMeta();
|
||||
metaRaw.setResult(result, 0);
|
||||
STObject const meta = metaRaw.getAsObject();
|
||||
|
||||
uint32_t lgrCur = view().seq();
|
||||
|
||||
bool const has240819 = view().rules().enabled(fix240819);
|
||||
bool const has240911 = view().rules().enabled(fix240911);
|
||||
|
||||
auto const& sfRewardFields =
|
||||
*(ripple::SField::knownCodeToField.at(917511 - has240819));
|
||||
|
||||
// iterate all affected balances
|
||||
for (auto const& node : meta.getFieldArray(sfAffectedNodes))
|
||||
{
|
||||
SField const& metaType = node.getFName();
|
||||
uint16_t nodeType = node.getFieldU16(sfLedgerEntryType);
|
||||
|
||||
// we only care about ltACCOUNT_ROOT objects being modified or
|
||||
// created
|
||||
if (nodeType != ltACCOUNT_ROOT || metaType == sfDeletedNode)
|
||||
continue;
|
||||
|
||||
if (!node.isFieldPresent(sfRewardFields) ||
|
||||
!node.isFieldPresent(sfLedgerIndex))
|
||||
continue;
|
||||
|
||||
auto sle = view().peek(
|
||||
Keylet{ltACCOUNT_ROOT, node.getFieldH256(sfLedgerIndex)});
|
||||
|
||||
if (!sle)
|
||||
continue;
|
||||
|
||||
if (!sle->isFieldPresent(sfRewardLgrFirst) ||
|
||||
!sle->isFieldPresent(sfRewardLgrLast) ||
|
||||
!sle->isFieldPresent(sfRewardAccumulator))
|
||||
continue;
|
||||
|
||||
STObject& finalFields = (const_cast<STObject&>(node))
|
||||
.getField(sfRewardFields)
|
||||
.downcast<STObject>();
|
||||
|
||||
if (!finalFields.isFieldPresent(sfBalance))
|
||||
continue;
|
||||
|
||||
uint64_t bal =
|
||||
finalFields.getFieldAmount(sfBalance).xrp().drops() / 1'000'000;
|
||||
|
||||
if (bal == 0)
|
||||
continue;
|
||||
|
||||
uint32_t lgrLast = sle->getFieldU32(sfRewardLgrLast);
|
||||
|
||||
uint32_t lgrElapsed = lgrCur - lgrLast;
|
||||
|
||||
// overflow safety
|
||||
if (!has240911 &&
|
||||
(lgrElapsed > lgrCur || lgrElapsed > lgrLast || lgrElapsed == 0))
|
||||
continue;
|
||||
if (has240911 && (lgrElapsed > lgrCur || lgrElapsed == 0))
|
||||
continue;
|
||||
|
||||
uint64_t accum = sle->getFieldU64(sfRewardAccumulator);
|
||||
uint64_t accumNew = accum + bal * ((uint64_t)lgrElapsed);
|
||||
|
||||
// check for overflow
|
||||
if (accumNew < accum)
|
||||
continue;
|
||||
|
||||
sle->setFieldU64(sfRewardAccumulator, accumNew);
|
||||
sle->setFieldU32(sfRewardLgrLast, lgrCur);
|
||||
|
||||
view().update(sle);
|
||||
}
|
||||
}
|
||||
|
||||
TER
|
||||
Transactor::executeHookChain(
|
||||
std::shared_ptr<ripple::STLedgerEntry const> const& hookSLE,
|
||||
@@ -1419,6 +1532,13 @@ Transactor::executeHookChain(
|
||||
|
||||
uint256 hookCanEmit = hook::getHookCanEmit(hookObj, hookDef);
|
||||
|
||||
XRPAmount atomicEmitFeeRemaining =
|
||||
hookObj.isFieldPresent(sfHookAtomicEmitFee)
|
||||
? hookObj.getFieldAmount(sfHookAtomicEmitFee).xrp()
|
||||
: hookDef->isFieldPresent(sfHookAtomicEmitFee)
|
||||
? hookDef->getFieldAmount(sfHookAtomicEmitFee).xrp()
|
||||
: XRPAmount(0);
|
||||
|
||||
uint32_t flags =
|
||||
(hookObj.isFieldPresent(sfFlags) ? hookObj.getFieldU32(sfFlags)
|
||||
: hookDef->getFieldU32(sfFlags));
|
||||
@@ -1460,6 +1580,7 @@ Transactor::executeHookChain(
|
||||
parameters,
|
||||
hookParamOverrides,
|
||||
stateMap,
|
||||
atomicEmitFeeRemaining,
|
||||
ctx_,
|
||||
account,
|
||||
hasCallback,
|
||||
@@ -1587,6 +1708,8 @@ Transactor::doHookCallback(
|
||||
|
||||
uint256 hookCanEmit = hook::getHookCanEmit(hookObj, hookDef);
|
||||
|
||||
XRPAmount atomicEmitFeeRemaining = XRPAmount(0);
|
||||
|
||||
// fetch the namespace either from the hook object of, if absent, the
|
||||
// hook def
|
||||
uint256 const& ns =
|
||||
@@ -1618,6 +1741,7 @@ Transactor::doHookCallback(
|
||||
parameters,
|
||||
{},
|
||||
stateMap,
|
||||
atomicEmitFeeRemaining,
|
||||
ctx_,
|
||||
callbackAccountID,
|
||||
true,
|
||||
@@ -1867,6 +1991,8 @@ Transactor::doAgainAsWeak(
|
||||
|
||||
uint256 hookCanEmit = hook::getHookCanEmit(hookObj, hookDef);
|
||||
|
||||
XRPAmount atomicEmitFeeRemaining{0};
|
||||
|
||||
// fetch the namespace either from the hook object of, if absent, the
|
||||
// hook def
|
||||
uint256 const& ns =
|
||||
@@ -1893,6 +2019,7 @@ Transactor::doAgainAsWeak(
|
||||
parameters,
|
||||
{},
|
||||
stateMap,
|
||||
atomicEmitFeeRemaining,
|
||||
ctx_,
|
||||
hookAccountID,
|
||||
hookDef->isFieldPresent(sfHookCallbackFee),
|
||||
@@ -2164,25 +2291,10 @@ Transactor::operator()()
|
||||
|
||||
if (applied)
|
||||
{
|
||||
// Check invariants: if `tecINVARIANT_FAILED` is not returned, we can
|
||||
// proceed to apply the tx
|
||||
result = ctx_.checkInvariants(result, fee);
|
||||
auto const invariantsResult = checkInvariants(result, fee);
|
||||
|
||||
if (result == tecINVARIANT_FAILED)
|
||||
{
|
||||
// if invariants checking failed again, reset the context and
|
||||
// attempt to only claim a fee.
|
||||
auto const resetResult = reset(fee);
|
||||
if (!isTesSuccess(resetResult.first))
|
||||
result = resetResult.first;
|
||||
|
||||
fee = resetResult.second;
|
||||
|
||||
// Check invariants again to ensure the fee claiming doesn't
|
||||
// violate invariants.
|
||||
if (isTesSuccess(result) || isTecClaim(result))
|
||||
result = ctx_.checkInvariants(result, fee);
|
||||
}
|
||||
result = invariantsResult.first;
|
||||
fee = invariantsResult.second;
|
||||
|
||||
// We ran through the invariant checker, which can, in some cases,
|
||||
// return a tef error code. Don't apply the transaction in that case.
|
||||
@@ -2192,81 +2304,7 @@ Transactor::operator()()
|
||||
|
||||
if (applied && view().rules().enabled(featureBalanceRewards))
|
||||
{
|
||||
TxMeta metaRaw = ctx_.generateProvisionalMeta();
|
||||
metaRaw.setResult(result, 0);
|
||||
STObject const meta = metaRaw.getAsObject();
|
||||
|
||||
uint32_t lgrCur = view().seq();
|
||||
|
||||
bool const has240819 = view().rules().enabled(fix240819);
|
||||
bool const has240911 = view().rules().enabled(fix240911);
|
||||
|
||||
auto const& sfRewardFields =
|
||||
*(ripple::SField::knownCodeToField.at(917511 - has240819));
|
||||
|
||||
// iterate all affected balances
|
||||
for (auto const& node : meta.getFieldArray(sfAffectedNodes))
|
||||
{
|
||||
SField const& metaType = node.getFName();
|
||||
uint16_t nodeType = node.getFieldU16(sfLedgerEntryType);
|
||||
|
||||
// we only care about ltACCOUNT_ROOT objects being modified or
|
||||
// created
|
||||
if (nodeType != ltACCOUNT_ROOT || metaType == sfDeletedNode)
|
||||
continue;
|
||||
|
||||
if (!node.isFieldPresent(sfRewardFields) ||
|
||||
!node.isFieldPresent(sfLedgerIndex))
|
||||
continue;
|
||||
|
||||
auto sle = view().peek(
|
||||
Keylet{ltACCOUNT_ROOT, node.getFieldH256(sfLedgerIndex)});
|
||||
|
||||
if (!sle)
|
||||
continue;
|
||||
|
||||
if (!sle->isFieldPresent(sfRewardLgrFirst) ||
|
||||
!sle->isFieldPresent(sfRewardLgrLast) ||
|
||||
!sle->isFieldPresent(sfRewardAccumulator))
|
||||
continue;
|
||||
|
||||
STObject& finalFields = (const_cast<STObject&>(node))
|
||||
.getField(sfRewardFields)
|
||||
.downcast<STObject>();
|
||||
|
||||
if (!finalFields.isFieldPresent(sfBalance))
|
||||
continue;
|
||||
|
||||
uint64_t bal =
|
||||
finalFields.getFieldAmount(sfBalance).xrp().drops() / 1'000'000;
|
||||
|
||||
if (bal == 0)
|
||||
continue;
|
||||
|
||||
uint32_t lgrLast = sle->getFieldU32(sfRewardLgrLast);
|
||||
|
||||
uint32_t lgrElapsed = lgrCur - lgrLast;
|
||||
|
||||
// overflow safety
|
||||
if (!has240911 &&
|
||||
(lgrElapsed > lgrCur || lgrElapsed > lgrLast ||
|
||||
lgrElapsed == 0))
|
||||
continue;
|
||||
if (has240911 && (lgrElapsed > lgrCur || lgrElapsed == 0))
|
||||
continue;
|
||||
|
||||
uint64_t accum = sle->getFieldU64(sfRewardAccumulator);
|
||||
uint64_t accumNew = accum + bal * ((uint64_t)lgrElapsed);
|
||||
|
||||
// check for overflow
|
||||
if (accumNew < accum)
|
||||
continue;
|
||||
|
||||
sle->setFieldU64(sfRewardAccumulator, accumNew);
|
||||
sle->setFieldU32(sfRewardLgrLast, lgrCur);
|
||||
|
||||
view().update(sle);
|
||||
}
|
||||
balanceRewards(result);
|
||||
}
|
||||
|
||||
// Post-application (Weak TSH/AAW) Hooks are executed here.
|
||||
@@ -2352,6 +2390,53 @@ Transactor::operator()()
|
||||
applied = false;
|
||||
}
|
||||
|
||||
if (metadata && metadata->hasHookEmissions())
|
||||
{
|
||||
OpenView emittedTxnsView(batch_view, ctx_.openView());
|
||||
bool const emitResult = hook::emitAtomicTransactions(
|
||||
ctx_.app,
|
||||
emittedTxnsView,
|
||||
metadata->getTxID(),
|
||||
metadata->getHookEmissions(),
|
||||
j_);
|
||||
printf("emitResult: %d\n", emitResult);
|
||||
if (emitResult)
|
||||
{
|
||||
emittedTxnsView.apply(ctx_.openView());
|
||||
}
|
||||
else
|
||||
{
|
||||
// reset context
|
||||
result = tecHOOK_EMIT_FAILED;
|
||||
auto const resetResult = reset(fee);
|
||||
if (!isTesSuccess(resetResult.first))
|
||||
result = resetResult.first;
|
||||
fee = resetResult.second;
|
||||
|
||||
// InvariantCheck
|
||||
auto const invariantsResult = checkInvariants(result, fee);
|
||||
result = invariantsResult.first;
|
||||
fee = invariantsResult.second;
|
||||
|
||||
// BalanceRewards
|
||||
balanceRewards(result);
|
||||
// Add Hooks metadata (use metadata)
|
||||
|
||||
// apply
|
||||
metadata = ctx_.apply(result);
|
||||
}
|
||||
}
|
||||
|
||||
ctx_.finalize();
|
||||
|
||||
if (ctx_.flags() & tapATOMIC_EMIT && !isTesSuccess(result))
|
||||
{
|
||||
JLOG(j_.trace()) << "HookEmit[]: Atomic emit failed: "
|
||||
<< transToken(result);
|
||||
JLOG(j_.trace()) << "HookEmit[]: Atomic emit failed: "
|
||||
<< ctx_.tx.getFullText();
|
||||
}
|
||||
|
||||
JLOG(j_.trace()) << (applied ? "applied " : "not applied ")
|
||||
<< transToken(result);
|
||||
|
||||
|
||||
@@ -57,7 +57,8 @@ public:
|
||||
, j(j_)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
(flags_ & tapBATCH) == tapBATCH, "Batch apply flag should be set");
|
||||
(flags_ & (tapBATCH | tapATOMIC_EMIT)) > 0,
|
||||
"Batch or AtomicEmit flag should be set");
|
||||
}
|
||||
|
||||
PreflightContext(
|
||||
@@ -69,7 +70,8 @@ public:
|
||||
: app(app_), tx(tx_), rules(rules_), flags(flags_), j(j_)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
(flags_ & tapBATCH) == 0, "Batch apply flag should not be set");
|
||||
(flags_ & (tapBATCH | tapATOMIC_EMIT)) == 0,
|
||||
"Batch or AtomicEmit flag should not be set");
|
||||
}
|
||||
|
||||
PreflightContext&
|
||||
@@ -105,8 +107,9 @@ public:
|
||||
, j(j_)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
parentBatchId.has_value() == ((flags_ & tapBATCH) == tapBATCH),
|
||||
"Parent Batch ID should be set if batch apply flag is set");
|
||||
parentBatchId.has_value() ==
|
||||
((flags_ & (tapBATCH | tapATOMIC_EMIT)) > 0),
|
||||
"Parent Batch ID should be set if batch or AtomicEmit flag is set");
|
||||
}
|
||||
|
||||
PreclaimContext(
|
||||
@@ -126,7 +129,8 @@ public:
|
||||
j_)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
(flags_ & tapBATCH) == 0, "Batch apply flag should not be set");
|
||||
(flags_ & (tapBATCH | tapATOMIC_EMIT)) == 0,
|
||||
"Batch or AtomicEmit flag should not be set");
|
||||
}
|
||||
|
||||
PreclaimContext&
|
||||
@@ -312,6 +316,12 @@ private:
|
||||
std::pair<TER, XRPAmount>
|
||||
reset(XRPAmount fee);
|
||||
|
||||
std::pair<TER, XRPAmount>
|
||||
checkInvariants(TER result, XRPAmount fee);
|
||||
|
||||
void
|
||||
balanceRewards(TER result);
|
||||
|
||||
TER
|
||||
consumeSeqProxy(SLE::pointer const& sleAccount);
|
||||
TER
|
||||
|
||||
@@ -49,7 +49,10 @@ enum ApplyFlags : std::uint32_t {
|
||||
|
||||
// Transaction shouldn't be applied
|
||||
// Signatures shouldn't be checked
|
||||
tapDRY_RUN = 0x1000
|
||||
tapDRY_RUN = 0x1000,
|
||||
|
||||
// Transaction is executing as part of a atomic emit
|
||||
tapATOMIC_EMIT = 0x2000,
|
||||
};
|
||||
|
||||
constexpr ApplyFlags
|
||||
|
||||
105
src/xrpld/ledger/OpenViewSandbox.h
Normal file
105
src/xrpld/ledger/OpenViewSandbox.h
Normal file
@@ -0,0 +1,105 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_LEDGER_OPENVIEWSANDBOX_H_INCLUDED
|
||||
#define RIPPLE_LEDGER_OPENVIEWSANDBOX_H_INCLUDED
|
||||
|
||||
#include <xrpld/ledger/OpenView.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class OpenViewSandbox
|
||||
{
|
||||
private:
|
||||
OpenView& parent_;
|
||||
std::unique_ptr<OpenView> sandbox_;
|
||||
|
||||
public:
|
||||
using key_type = ReadView::key_type;
|
||||
|
||||
OpenViewSandbox(OpenView& parent)
|
||||
: parent_(parent)
|
||||
, sandbox_(std::make_unique<OpenView>(batch_view, parent))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
rawErase(std::shared_ptr<SLE> const& sle)
|
||||
{
|
||||
sandbox_->rawErase(sle);
|
||||
}
|
||||
|
||||
void
|
||||
rawInsert(std::shared_ptr<SLE> const& sle)
|
||||
{
|
||||
sandbox_->rawInsert(sle);
|
||||
}
|
||||
|
||||
void
|
||||
rawReplace(std::shared_ptr<SLE> const& sle)
|
||||
{
|
||||
sandbox_->rawReplace(sle);
|
||||
}
|
||||
|
||||
void
|
||||
rawDestroyXRP(XRPAmount const& fee)
|
||||
{
|
||||
sandbox_->rawDestroyXRP(fee);
|
||||
}
|
||||
|
||||
void
|
||||
rawTxInsert(
|
||||
key_type const& key,
|
||||
std::shared_ptr<Serializer const> const& txn,
|
||||
std::shared_ptr<Serializer const> const& metaData)
|
||||
{
|
||||
sandbox_->rawTxInsert(key, txn, metaData);
|
||||
}
|
||||
|
||||
void
|
||||
commit()
|
||||
{
|
||||
sandbox_->apply(parent_);
|
||||
sandbox_ = std::make_unique<OpenView>(batch_view, parent_);
|
||||
}
|
||||
|
||||
void
|
||||
discard()
|
||||
{
|
||||
sandbox_ = std::make_unique<OpenView>(batch_view, parent_);
|
||||
}
|
||||
|
||||
OpenView const&
|
||||
view() const
|
||||
{
|
||||
return *sandbox_;
|
||||
}
|
||||
|
||||
OpenView&
|
||||
view()
|
||||
{
|
||||
return *sandbox_;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user