featureAtomicEmit

This commit is contained in:
tequ
2025-09-03 18:04:04 +09:00
parent 1a35c83048
commit bd7fc84dbc
19 changed files with 1796 additions and 306 deletions

View File

@@ -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

View File

@@ -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,20 @@ validateGuards(
hook_api::import_whitelist.find(import_name) ==
hook_api::import_whitelist.end())
{
if (rulesVersion > 0 &&
printf("import_name: %s\n", import_name.c_str());
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 +1266,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)
{

View File

@@ -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)
{

View File

@@ -80,7 +80,7 @@ namespace detail {
// Feature.cpp. Because it's only used to reserve storage, and determine how
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
// the actual number of amendments. A LogicError on startup will verify this.
static constexpr std::size_t numFeatures = 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

View File

@@ -361,6 +361,7 @@ enum TECcodes : TERUnderlyingType {
tecARRAY_TOO_LARGE = 196,
tecLOCKED = 197,
tecBAD_CREDENTIALS = 198,
tecHOOK_EMIT_FAILED = 199,
tecLAST_POSSIBLE_ENTRY = 255,
};

View File

@@ -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)

View File

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

View File

@@ -2933,6 +2933,456 @@ 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
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 +13184,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 +13482,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

View File

@@ -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);
@@ -489,6 +496,8 @@ 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;
uint16_t changedStateCount = 0;
std::map<
@@ -581,6 +590,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 +830,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);

View File

@@ -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>
@@ -2018,10 +2019,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 +2049,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 +2058,7 @@ hook::finalizeHookResult(
if (page)
{
(*sleEmitted)[sfOwnerNode] = *page;
(*sleEmitted)[sfFlags] = atomic ? 1 : 0;
applyCtx.view().insert(sleEmitted);
}
else
@@ -2069,6 +2070,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 +2153,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 +3424,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 +3759,355 @@ 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;
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;
}
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.emittedAtomicTxn.push(tpTrans);
return result;
HOOK_TEARDOWN();
}
// When implemented will return the hash of the current hook
DEFINE_HOOK_FUNCTION(
int64_t,

View File

@@ -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

View File

@@ -47,8 +47,9 @@ ApplyContext::ApplyContext(
, parentBatchId_(parentBatchId)
{
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");
view_.emplace(&base_.view(), flags_);
}

View File

@@ -66,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;

View File

@@ -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)
{

View File

@@ -485,12 +485,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())
{

View File

@@ -1368,6 +1368,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,
@@ -2164,25 +2269,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 +2282,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 +2368,43 @@ 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();
JLOG(j_.trace()) << (applied ? "applied " : "not applied ")

View File

@@ -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

View File

@@ -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