mirror of
https://github.com/Xahau/xahaud.git
synced 2026-04-26 14:07:45 +00:00
Compare commits
22 Commits
sync-2.6.0
...
gas-hook
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
119b0221c7 | ||
|
|
aa6101409d | ||
|
|
6e3e518bdc | ||
|
|
b46613fd74 | ||
|
|
8aef5c10f7 | ||
|
|
5885be9f8a | ||
|
|
41b87c8749 | ||
|
|
3a4ca5560a | ||
|
|
563a902a5d | ||
|
|
4491894a90 | ||
|
|
efea057fad | ||
|
|
7a9de3a205 | ||
|
|
9bdaa58d9b | ||
|
|
4d6d944831 | ||
|
|
cc3a63ad31 | ||
|
|
0ff199501f | ||
|
|
d8d848eb70 | ||
|
|
ca08b61a78 | ||
|
|
1101f99afd | ||
|
|
a9b956bbd9 | ||
|
|
44ba7a01aa | ||
|
|
47beef302c |
@@ -72,6 +72,10 @@
|
||||
#define sfLockCount ((2U << 16U) + 49U)
|
||||
#define sfFirstNFTokenSequence ((2U << 16U) + 50U)
|
||||
#define sfOracleDocumentID ((2U << 16U) + 51U)
|
||||
#define sfHookCallbackGas ((2U << 16U) + 89U)
|
||||
#define sfHookWeakGas ((2U << 16U) + 90U)
|
||||
#define sfHookInstructionCost ((2U << 16U) + 91U)
|
||||
#define sfHookGas ((2U << 16U) + 92U)
|
||||
#define sfStartTime ((2U << 16U) + 93U)
|
||||
#define sfRepeatCount ((2U << 16U) + 94U)
|
||||
#define sfDelaySeconds ((2U << 16U) + 95U)
|
||||
@@ -105,6 +109,7 @@
|
||||
#define sfMPTAmount ((3U << 16U) + 26U)
|
||||
#define sfIssuerNode ((3U << 16U) + 27U)
|
||||
#define sfSubjectNode ((3U << 16U) + 28U)
|
||||
#define sfHookGasPrice ((3U << 16U) + 96U)
|
||||
#define sfTouchCount ((3U << 16U) + 97U)
|
||||
#define sfAccountIndex ((3U << 16U) + 98U)
|
||||
#define sfAccountCount ((3U << 16U) + 99U)
|
||||
|
||||
@@ -266,6 +266,8 @@ enum hook_log_code : uint16_t {
|
||||
CUSTOM_SECTION_DISALLOWED =
|
||||
86, // the wasm contained a custom section (id=0)
|
||||
INTERNAL_ERROR = 87, // an internal error described by the log text
|
||||
MEMORY_PAGE_LIMIT =
|
||||
88, // memory import declares min or max exceeding the page limit
|
||||
// RH NOTE: only HookSet msgs got log codes, possibly all Hook log lines
|
||||
// should get a code?
|
||||
};
|
||||
@@ -391,6 +393,7 @@ enum ExitType : uint8_t {
|
||||
WASM_ERROR = 1,
|
||||
ROLLBACK = 2,
|
||||
ACCEPT = 3,
|
||||
GAS_INSUFFICIENT = 4,
|
||||
};
|
||||
|
||||
const uint16_t max_state_modifications = 256;
|
||||
@@ -400,6 +403,8 @@ const uint8_t max_emit = 255;
|
||||
const uint8_t max_params = 16;
|
||||
const double fee_base_multiplier = 1.1f;
|
||||
|
||||
const uint8_t max_memory_pages = 8;
|
||||
|
||||
using APIWhitelist = std::map<std::string, std::vector<uint8_t>>;
|
||||
|
||||
// RH NOTE: Find descriptions of api functions in ./impl/applyHook.cpp and
|
||||
@@ -411,7 +416,9 @@ getImportWhitelist(Rules const& rules)
|
||||
APIWhitelist whitelist;
|
||||
|
||||
#pragma push_macro("HOOK_API_DEFINITION")
|
||||
#pragma push_macro("HOOK_API_COST")
|
||||
#undef HOOK_API_DEFINITION
|
||||
#undef HOOK_API_COST
|
||||
|
||||
#define int64_t 0x7EU
|
||||
#define int32_t 0x7FU
|
||||
@@ -424,15 +431,18 @@ getImportWhitelist(Rules const& rules)
|
||||
if (AMENDMENT == uint256{} || rules.enabled(AMENDMENT)) \
|
||||
whitelist[#FUNCTION_NAME] = { \
|
||||
RETURN_TYPE, HOOK_WRAP_PARAMS PARAMS_TUPLE};
|
||||
#define HOOK_API_COST(FUNCTION_NAME, cost, amendment)
|
||||
|
||||
#include "hook_api.macro"
|
||||
|
||||
#undef HOOK_API_DEFINITION
|
||||
#undef HOOK_API_COST
|
||||
#undef HOOK_WRAP_PARAMS
|
||||
#undef int64_t
|
||||
#undef int32_t
|
||||
#undef uint32_t
|
||||
#pragma pop_macro("HOOK_API_DEFINITION")
|
||||
#pragma pop_macro("HOOK_API_COST")
|
||||
|
||||
return whitelist;
|
||||
}
|
||||
|
||||
61
include/xrpl/hook/GasValidator.h
Normal file
61
include/xrpl/hook/GasValidator.h
Normal file
@@ -0,0 +1,61 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2024 XRPL-Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_APP_HOOK_GASVALIDATOR_H_INCLUDED
|
||||
#define RIPPLE_APP_HOOK_GASVALIDATOR_H_INCLUDED
|
||||
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Forward declaration
|
||||
using GuardLog =
|
||||
std::optional<std::reference_wrapper<std::basic_ostream<char>>>;
|
||||
|
||||
namespace hook {
|
||||
|
||||
/**
|
||||
* @brief Validate WASM host functions for Gas-type hooks
|
||||
*
|
||||
* Validates that a WASM binary only imports allowed host functions
|
||||
* and does not import the _g (guard) function, which is only for
|
||||
* Guard-type hooks.
|
||||
*
|
||||
* @param wasm The WASM binary to validate
|
||||
* @param guardLog Logging function for validation errors
|
||||
* @param guardLogAccStr Account string for logging
|
||||
* @return bool if validation succeeds,
|
||||
* @return true if contains cbak function
|
||||
* @return false if otherwise
|
||||
* @return error message if validation fails
|
||||
*/
|
||||
ripple::Expected<bool, std::string>
|
||||
validateWasmHostFunctionsForGas(
|
||||
std::vector<uint8_t> const& wasm,
|
||||
ripple::Rules const& rules,
|
||||
beast::Journal const& j);
|
||||
|
||||
} // namespace hook
|
||||
|
||||
#endif // RIPPLE_APP_HOOK_GASVALIDATOR_H_INCLUDED
|
||||
@@ -2,373 +2,448 @@
|
||||
HOOK_API_DEFINITION(
|
||||
int32_t, _g, (uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(_g, 0, uint256{})
|
||||
|
||||
// int64_t accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, accept, (uint32_t, uint32_t, int64_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(accept, 0, uint256{})
|
||||
|
||||
// int64_t rollback(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, rollback, (uint32_t, uint32_t, int64_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(rollback, 0, uint256{})
|
||||
|
||||
// int64_t util_raddr(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, util_raddr, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(util_raddr, 0, uint256{})
|
||||
|
||||
// int64_t util_accid(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, util_accid, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(util_accid, 0, uint256{})
|
||||
|
||||
// int64_t util_verify(uint32_t dread_ptr, uint32_t dread_len, uint32_t sread_ptr, uint32_t sread_len, uint32_t kread_ptr, uint32_t kread_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, util_verify, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(util_verify, 0, uint256{})
|
||||
|
||||
// int64_t util_sha512h(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, util_sha512h, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(util_sha512h, 0, uint256{})
|
||||
|
||||
// int64_t util_keylet(uint32_t write_ptr, uint32_t write_len, uint32_t keylet_type, uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t e, uint32_t f);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, util_keylet, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(util_keylet, 0, uint256{})
|
||||
|
||||
// int64_t sto_validate(uint32_t tread_ptr, uint32_t tread_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, sto_validate, (uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(sto_validate, 0, uint256{})
|
||||
|
||||
// int64_t sto_subfield(uint32_t read_ptr, uint32_t read_len, uint32_t field_id);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, sto_subfield, (uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(sto_subfield, 0, uint256{})
|
||||
|
||||
// int64_t sto_subarray(uint32_t read_ptr, uint32_t read_len, uint32_t array_id);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, sto_subarray, (uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(sto_subarray, 0, uint256{})
|
||||
|
||||
// int64_t sto_emplace(uint32_t write_ptr, uint32_t write_len, uint32_t sread_ptr, uint32_t sread_len, uint32_t fread_ptr, uint32_t fread_len, uint32_t field_id);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, sto_emplace, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(sto_emplace, 0, uint256{})
|
||||
|
||||
// int64_t sto_erase(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len, uint32_t field_id);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, sto_erase, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(sto_erase, 0, uint256{})
|
||||
|
||||
// int64_t etxn_burden();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, etxn_burden, (),
|
||||
uint256{})
|
||||
HOOK_API_COST(etxn_burden, 0, uint256{})
|
||||
|
||||
// int64_t etxn_details(uint32_t write_ptr, uint32_t write_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, etxn_details, (uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(etxn_details, 0, uint256{})
|
||||
|
||||
// int64_t etxn_fee_base(uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, etxn_fee_base, (uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(etxn_fee_base, 0, uint256{})
|
||||
|
||||
// int64_t etxn_reserve(uint32_t count);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, etxn_reserve, (uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(etxn_reserve, 0, uint256{})
|
||||
|
||||
// int64_t etxn_generation();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, etxn_generation, (),
|
||||
uint256{})
|
||||
HOOK_API_COST(etxn_generation, 0, uint256{})
|
||||
|
||||
// int64_t etxn_nonce(uint32_t write_ptr, uint32_t write_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, etxn_nonce, (uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(etxn_nonce, 0, uint256{})
|
||||
|
||||
// int64_t emit(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, emit, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(emit, 0, uint256{})
|
||||
|
||||
// int64_t float_set(int32_t exponent, int64_t mantissa);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_set, (int32_t, int64_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(float_set, 0, uint256{})
|
||||
|
||||
// int64_t float_multiply(int64_t float1, int64_t float2);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_multiply, (int64_t, int64_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(float_multiply, 0, uint256{})
|
||||
|
||||
// int64_t float_mulratio(int64_t float1, uint32_t round_up, uint32_t numerator, uint32_t denominator);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_mulratio, (int64_t, uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(float_mulratio, 0, uint256{})
|
||||
|
||||
// int64_t float_negate(int64_t float1);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_negate, (int64_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(float_negate, 0, uint256{})
|
||||
|
||||
// int64_t float_compare(int64_t float1, int64_t float2, uint32_t mode);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_compare, (int64_t, int64_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(float_compare, 0, uint256{})
|
||||
|
||||
// int64_t float_sum(int64_t float1, int64_t float2);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_sum, (int64_t, int64_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(float_sum, 0, uint256{})
|
||||
|
||||
// int64_t float_sto(uint32_t write_ptr, uint32_t write_len, uint32_t cread_ptr, uint32_t cread_len, uint32_t iread_ptr, uint32_t iread_len, int64_t float1, uint32_t field_code);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_sto, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, int64_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(float_sto, 0, uint256{})
|
||||
|
||||
// int64_t float_sto_set(uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_sto_set, (uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(float_sto_set, 0, uint256{})
|
||||
|
||||
// int64_t float_invert(int64_t float1);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_invert, (int64_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(float_invert, 0, uint256{})
|
||||
|
||||
// int64_t float_divide(int64_t float1, int64_t float2);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_divide, (int64_t, int64_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(float_divide, 0, uint256{})
|
||||
|
||||
// int64_t float_one();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_one, (),
|
||||
uint256{})
|
||||
HOOK_API_COST(float_one, 0, uint256{})
|
||||
|
||||
// int64_t float_mantissa(int64_t float1);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_mantissa, (int64_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(float_mantissa, 0, uint256{})
|
||||
|
||||
// int64_t float_sign(int64_t float1);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_sign, (int64_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(float_sign, 0, uint256{})
|
||||
|
||||
// int64_t float_int(int64_t float1, uint32_t decimal_places, uint32_t abs);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_int, (int64_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(float_int, 0, uint256{})
|
||||
|
||||
// int64_t float_log(int64_t float1);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_log, (int64_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(float_log, 0, uint256{})
|
||||
|
||||
// int64_t float_root(int64_t float1, uint32_t n);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, float_root, (int64_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(float_root, 0, uint256{})
|
||||
|
||||
// int64_t fee_base();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, fee_base, (),
|
||||
uint256{})
|
||||
HOOK_API_COST(fee_base, 0, uint256{})
|
||||
|
||||
// int64_t ledger_seq();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, ledger_seq, (),
|
||||
uint256{})
|
||||
HOOK_API_COST(ledger_seq, 0, uint256{})
|
||||
|
||||
// int64_t ledger_last_time();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, ledger_last_time, (),
|
||||
uint256{})
|
||||
HOOK_API_COST(ledger_last_time, 0, uint256{})
|
||||
|
||||
// int64_t ledger_last_hash(uint32_t write_ptr, uint32_t write_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, ledger_last_hash, (uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(ledger_last_hash, 0, uint256{})
|
||||
|
||||
// int64_t ledger_nonce(uint32_t write_ptr, uint32_t write_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, ledger_nonce, (uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(ledger_nonce, 0, uint256{})
|
||||
|
||||
// int64_t ledger_keylet(uint32_t write_ptr, uint32_t write_len, uint32_t lread_ptr, uint32_t lread_len, uint32_t hread_ptr, uint32_t hread_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, ledger_keylet, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(ledger_keylet, 0, uint256{})
|
||||
|
||||
// int64_t hook_account(uint32_t write_ptr, uint32_t write_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, hook_account, (uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(hook_account, 0, uint256{})
|
||||
|
||||
// int64_t hook_hash(uint32_t write_ptr, uint32_t write_len, int32_t hook_no);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, hook_hash, (uint32_t, uint32_t, int32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(hook_hash, 0, uint256{})
|
||||
|
||||
// int64_t hook_param_set(uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len, uint32_t hread_ptr, uint32_t hread_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, hook_param_set, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(hook_param_set, 0, uint256{})
|
||||
|
||||
// int64_t hook_param(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, hook_param, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(hook_param, 0, uint256{})
|
||||
|
||||
// int64_t hook_again();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, hook_again, (),
|
||||
uint256{})
|
||||
HOOK_API_COST(hook_again, 0, uint256{})
|
||||
|
||||
// int64_t hook_skip(uint32_t read_ptr, uint32_t read_len, uint32_t flags);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, hook_skip, (uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(hook_skip, 0, uint256{})
|
||||
|
||||
// int64_t hook_pos();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, hook_pos, (),
|
||||
uint256{})
|
||||
HOOK_API_COST(hook_pos, 0, uint256{})
|
||||
|
||||
// int64_t slot(uint32_t write_ptr, uint32_t write_len, uint32_t slot);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot, (uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(slot, 0, uint256{})
|
||||
|
||||
// int64_t slot_clear(uint32_t slot);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot_clear, (uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(slot_clear, 0, uint256{})
|
||||
|
||||
// int64_t slot_count(uint32_t slot);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot_count, (uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(slot_count, 0, uint256{})
|
||||
|
||||
// int64_t slot_set(uint32_t read_ptr, uint32_t read_len, uint32_t slot);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot_set, (uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(slot_set, 0, uint256{})
|
||||
|
||||
// int64_t slot_size(uint32_t slot);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot_size, (uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(slot_size, 0, uint256{})
|
||||
|
||||
// int64_t slot_subarray(uint32_t parent_slot, uint32_t array_id, uint32_t new_slot);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot_subarray, (uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(slot_subarray, 0, uint256{})
|
||||
|
||||
// int64_t slot_subfield(uint32_t parent_slot, uint32_t field_id, uint32_t new_slot);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot_subfield, (uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(slot_subfield, 0, uint256{})
|
||||
|
||||
// int64_t slot_type(uint32_t slot_no, uint32_t flags);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot_type, (uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(slot_type, 0, uint256{})
|
||||
|
||||
// int64_t slot_float(uint32_t slot_no);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, slot_float, (uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(slot_float, 0, uint256{})
|
||||
|
||||
// int64_t state_set(uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, state_set, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(state_set, 0, uint256{})
|
||||
|
||||
// int64_t state_foreign_set(uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len, uint32_t nread_ptr, uint32_t nread_len, uint32_t aread_ptr, uint32_t aread_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, state_foreign_set, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(state_foreign_set, 0, uint256{})
|
||||
|
||||
// int64_t state(uint32_t write_ptr, uint32_t write_len, uint32_t kread_ptr, uint32_t kread_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, state, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(state, 0, uint256{})
|
||||
|
||||
// int64_t state_foreign(uint32_t write_ptr, uint32_t write_len, uint32_t kread_ptr, uint32_t kread_len, uint32_t nread_ptr, uint32_t nread_len, uint32_t aread_ptr, uint32_t aread_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, state_foreign, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(state_foreign, 0, uint256{})
|
||||
|
||||
// int64_t trace(uint32_t mread_ptr, uint32_t mread_len, uint32_t dread_ptr, uint32_t dread_len, uint32_t as_hex);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, trace, (uint32_t, uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(trace, 0, uint256{})
|
||||
|
||||
// int64_t trace_num(uint32_t read_ptr, uint32_t read_len, int64_t number);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, trace_num, (uint32_t, uint32_t, int64_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(trace_num, 0, uint256{})
|
||||
|
||||
// int64_t trace_float(uint32_t read_ptr, uint32_t read_len, int64_t float1);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, trace_float, (uint32_t, uint32_t, int64_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(trace_float, 0, uint256{})
|
||||
|
||||
// int64_t otxn_burden();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, otxn_burden, (),
|
||||
uint256{})
|
||||
HOOK_API_COST(otxn_burden, 0, uint256{})
|
||||
|
||||
// int64_t otxn_field(uint32_t write_ptr, uint32_t write_len, uint32_t field_id);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, otxn_field, (uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(otxn_field, 0, uint256{})
|
||||
|
||||
// int64_t otxn_generation();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, otxn_generation, (),
|
||||
uint256{})
|
||||
HOOK_API_COST(otxn_generation, 0, uint256{})
|
||||
|
||||
// int64_t otxn_id(uint32_t write_ptr, uint32_t write_len, uint32_t flags);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, otxn_id, (uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(otxn_id, 0, uint256{})
|
||||
|
||||
// int64_t otxn_type();
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, otxn_type, (),
|
||||
uint256{})
|
||||
HOOK_API_COST(otxn_type, 0, uint256{})
|
||||
|
||||
// int64_t otxn_slot(uint32_t slot_no);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, otxn_slot, (uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(otxn_slot, 0, uint256{})
|
||||
|
||||
// int64_t otxn_param(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, otxn_param, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(otxn_param, 0, uint256{})
|
||||
|
||||
// int64_t meta_slot(uint32_t slot_no);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, meta_slot, (uint32_t),
|
||||
uint256{})
|
||||
HOOK_API_COST(meta_slot, 0, uint256{})
|
||||
|
||||
// int64_t xpop_slot(uint32_t slot_no_tx, uint32_t slot_no_meta);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, xpop_slot, (uint32_t, uint32_t),
|
||||
featureHooksUpdate1)
|
||||
HOOK_API_COST(xpop_slot, 0, uint256{})
|
||||
|
||||
// int64_t prepare(uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
|
||||
HOOK_API_DEFINITION(
|
||||
int64_t, prepare, (uint32_t, uint32_t, uint32_t, uint32_t),
|
||||
featureHooksUpdate2)
|
||||
HOOK_API_COST(prepare, 0, uint256{})
|
||||
|
||||
@@ -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 = 113;
|
||||
static constexpr std::size_t numFeatures = 114;
|
||||
|
||||
/** Amendments that this server supports and the default voting behavior.
|
||||
Whether they are enabled depends on the Rules defined in the validated
|
||||
|
||||
@@ -34,6 +34,8 @@ struct Fees
|
||||
XRPAmount base{0}; // Reference tx cost (drops)
|
||||
XRPAmount reserve{0}; // Reserve base (drops)
|
||||
XRPAmount increment{0}; // Reserve increment (drops)
|
||||
std::uint64_t hookGasPrice{
|
||||
0}; // Gas price for gas-type hooks (micro-drops per gas unit)
|
||||
|
||||
explicit Fees() = default;
|
||||
Fees(Fees const&) = default;
|
||||
|
||||
@@ -362,6 +362,8 @@ enum TECcodes : TERUnderlyingType {
|
||||
tecARRAY_TOO_LARGE = 197,
|
||||
tecLOCKED = 198,
|
||||
tecBAD_CREDENTIALS = 199,
|
||||
tecHOOK_INSUFFICIENT_GAS = 200,
|
||||
tecHOOK_INVALID = 201,
|
||||
tecLAST_POSSIBLE_ENTRY = 255,
|
||||
};
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||
// in include/xrpl/protocol/Feature.h.
|
||||
|
||||
XRPL_FEATURE(HookGas, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(HookAPISerializedType240, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(PermissionedDomains, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(DynamicNFT, Supported::no, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -103,10 +103,12 @@ LEDGER_ENTRY(ltHOOK_DEFINITION, 'D', HookDefinition, hook_definition, ({
|
||||
{sfCreateCode, soeREQUIRED},
|
||||
{sfHookSetTxnID, soeREQUIRED},
|
||||
{sfReferenceCount, soeREQUIRED},
|
||||
{sfFee, soeREQUIRED},
|
||||
{sfFee, soeOPTIONAL},
|
||||
{sfHookCallbackFee, soeOPTIONAL},
|
||||
{sfPreviousTxnID, soeOPTIONAL},
|
||||
{sfPreviousTxnLgrSeq, soeOPTIONAL},
|
||||
{sfHookCallbackGas, soeOPTIONAL},
|
||||
{sfHookWeakGas, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** A ledger object containing a hook-emitted transaction from a previous hook execution.
|
||||
@@ -414,6 +416,7 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({
|
||||
{sfBaseFeeDrops, soeOPTIONAL},
|
||||
{sfReserveBaseDrops, soeOPTIONAL},
|
||||
{sfReserveIncrementDrops, soeOPTIONAL},
|
||||
{sfHookGasPrice, soeOPTIONAL},
|
||||
{sfXahauActivationLgrSeq, soeOPTIONAL},
|
||||
{sfAccountCount, soeOPTIONAL},
|
||||
{sfNetworkID, soeOPTIONAL},
|
||||
|
||||
@@ -115,6 +115,10 @@ TYPED_SFIELD(sfLockCount, UINT32, 49)
|
||||
TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50)
|
||||
TYPED_SFIELD(sfOracleDocumentID, UINT32, 51)
|
||||
|
||||
TYPED_SFIELD(sfHookCallbackGas, UINT32, 89)
|
||||
TYPED_SFIELD(sfHookWeakGas, UINT32, 90)
|
||||
TYPED_SFIELD(sfHookInstructionCost, UINT32, 91)
|
||||
TYPED_SFIELD(sfHookGas, UINT32, 92)
|
||||
TYPED_SFIELD(sfStartTime, UINT32, 93)
|
||||
TYPED_SFIELD(sfRepeatCount, UINT32, 94)
|
||||
TYPED_SFIELD(sfDelaySeconds, UINT32, 95)
|
||||
@@ -153,6 +157,7 @@ TYPED_SFIELD(sfOutstandingAmount, UINT64, 25, SField::sMD_BaseTen|SFie
|
||||
TYPED_SFIELD(sfMPTAmount, UINT64, 26, SField::sMD_BaseTen|SField::sMD_Default)
|
||||
TYPED_SFIELD(sfIssuerNode, UINT64, 27)
|
||||
TYPED_SFIELD(sfSubjectNode, UINT64, 28)
|
||||
TYPED_SFIELD(sfHookGasPrice, UINT64, 96)
|
||||
TYPED_SFIELD(sfTouchCount, UINT64, 97)
|
||||
TYPED_SFIELD(sfAccountIndex, UINT64, 98)
|
||||
TYPED_SFIELD(sfAccountCount, UINT64, 99)
|
||||
|
||||
@@ -583,6 +583,7 @@ TRANSACTION(ttFEE, 101, SetFee, ({
|
||||
{sfBaseFeeDrops, soeOPTIONAL},
|
||||
{sfReserveBaseDrops, soeOPTIONAL},
|
||||
{sfReserveIncrementDrops, soeOPTIONAL},
|
||||
{sfHookGasPrice, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This system-generated transaction type is used to update the network's negative UNL
|
||||
|
||||
@@ -87,6 +87,8 @@ JSS(HookParameterName); // field
|
||||
JSS(HookParameterValue); // field
|
||||
JSS(HookParameter); // field
|
||||
JSS(HookGrant); // field
|
||||
JSS(HookCallbackGas); // field
|
||||
JSS(HookWeakGas); // field
|
||||
JSS(isSerialized); // out: RPC server_definitions
|
||||
// matches definitions.json format
|
||||
JSS(isSigningField); // out: RPC server_definitions
|
||||
|
||||
@@ -78,7 +78,10 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfHookExecutionIndex, soeREQUIRED},
|
||||
{sfHookStateChangeCount, soeREQUIRED},
|
||||
{sfHookEmitCount, soeREQUIRED},
|
||||
{sfFlags, soeOPTIONAL}});
|
||||
{sfFlags, soeOPTIONAL},
|
||||
{sfHookInstructionCost, soeOPTIONAL},
|
||||
{sfHookWeakGas, soeOPTIONAL},
|
||||
{sfHookCallbackGas, soeOPTIONAL}});
|
||||
|
||||
add(sfHookEmission.jsonName,
|
||||
sfHookEmission.getCode(),
|
||||
@@ -98,7 +101,7 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfHookCanEmit, soeOPTIONAL},
|
||||
{sfHookApiVersion, soeREQUIRED},
|
||||
{sfFlags, soeREQUIRED},
|
||||
{sfFee, soeREQUIRED}});
|
||||
{sfFee, soeOPTIONAL}});
|
||||
|
||||
add(sfHook.jsonName,
|
||||
sfHook.getCode(),
|
||||
@@ -112,6 +115,8 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfHookOnOutgoing, soeOPTIONAL},
|
||||
{sfHookCanEmit, soeOPTIONAL},
|
||||
{sfHookApiVersion, soeOPTIONAL},
|
||||
{sfHookCallbackGas, soeOPTIONAL},
|
||||
{sfHookWeakGas, soeOPTIONAL},
|
||||
{sfFlags, soeOPTIONAL}});
|
||||
|
||||
add(sfHookGrant.jsonName,
|
||||
|
||||
@@ -66,6 +66,8 @@ STValidation::validationFormat()
|
||||
{sfBaseFeeDrops, soeOPTIONAL},
|
||||
{sfReserveBaseDrops, soeOPTIONAL},
|
||||
{sfReserveIncrementDrops, soeOPTIONAL},
|
||||
// featureHookGas
|
||||
{sfHookGasPrice, soeOPTIONAL},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
@@ -124,6 +124,8 @@ 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_INSUFFICIENT_GAS, "Insufficient hook gas to complete the transaction."),
|
||||
MAKE_ERROR(tecHOOK_INVALID, "Invalid hook."),
|
||||
|
||||
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
|
||||
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
|
||||
|
||||
@@ -48,6 +48,7 @@ TxFormats::TxFormats()
|
||||
{sfFirstLedgerSequence, soeOPTIONAL},
|
||||
{sfNetworkID, soeOPTIONAL},
|
||||
{sfHookParameters, soeOPTIONAL},
|
||||
{sfHookGas, soeOPTIONAL},
|
||||
};
|
||||
|
||||
#pragma push_macro("UNWRAP")
|
||||
|
||||
@@ -29,6 +29,7 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
void
|
||||
testSetup()
|
||||
{
|
||||
testcase("setup");
|
||||
FeeSetup const defaultSetup;
|
||||
{
|
||||
// defaults
|
||||
@@ -37,36 +38,42 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
|
||||
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
|
||||
BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve);
|
||||
BEAST_EXPECT(setup.hook_gas_price == defaultSetup.hook_gas_price);
|
||||
}
|
||||
{
|
||||
Section config;
|
||||
config.append(
|
||||
{"reference_fee = 50",
|
||||
"account_reserve = 1234567",
|
||||
"owner_reserve = 1234"});
|
||||
"owner_reserve = 1234",
|
||||
"hook_gas_price = 2000000"});
|
||||
auto setup = setup_FeeVote(config);
|
||||
BEAST_EXPECT(setup.reference_fee == 50);
|
||||
BEAST_EXPECT(setup.account_reserve == 1234567);
|
||||
BEAST_EXPECT(setup.owner_reserve == 1234);
|
||||
BEAST_EXPECT(setup.hook_gas_price == 2'000'000);
|
||||
}
|
||||
{
|
||||
Section config;
|
||||
config.append(
|
||||
{"reference_fee = blah",
|
||||
"account_reserve = yada",
|
||||
"owner_reserve = foo"});
|
||||
"owner_reserve = foo",
|
||||
"hook_gas_price =invalid"});
|
||||
// Illegal values are ignored, and the defaults left unchanged
|
||||
auto setup = setup_FeeVote(config);
|
||||
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
|
||||
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
|
||||
BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve);
|
||||
BEAST_EXPECT(setup.hook_gas_price == defaultSetup.hook_gas_price);
|
||||
}
|
||||
{
|
||||
Section config;
|
||||
config.append(
|
||||
{"reference_fee = -50",
|
||||
"account_reserve = -1234567",
|
||||
"owner_reserve = -1234"});
|
||||
"owner_reserve = -1234",
|
||||
"hook_gas_price = -2000000"});
|
||||
// Illegal values are ignored, and the defaults left unchanged
|
||||
auto setup = setup_FeeVote(config);
|
||||
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
|
||||
@@ -74,22 +81,30 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
setup.account_reserve == static_cast<std::uint32_t>(-1234567));
|
||||
BEAST_EXPECT(
|
||||
setup.owner_reserve == static_cast<std::uint32_t>(-1234));
|
||||
BEAST_EXPECT(
|
||||
setup.hook_gas_price == static_cast<std::int64_t>(-2000000));
|
||||
}
|
||||
{
|
||||
const auto big64 = std::to_string(
|
||||
const auto big64xrp = std::to_string(
|
||||
static_cast<std::uint64_t>(
|
||||
std::numeric_limits<XRPAmount::value_type>::max()) +
|
||||
1);
|
||||
const auto big64 = std::to_string(
|
||||
static_cast<std::uint64_t>(
|
||||
std::numeric_limits<std::int64_t>::max()) +
|
||||
1);
|
||||
Section config;
|
||||
config.append(
|
||||
{"reference_fee = " + big64,
|
||||
"account_reserve = " + big64,
|
||||
"owner_reserve = " + big64});
|
||||
{"reference_fee = " + big64xrp,
|
||||
"account_reserve = " + big64xrp,
|
||||
"owner_reserve = " + big64xrp,
|
||||
"hook_gas_price =" + big64});
|
||||
// Illegal values are ignored, and the defaults left unchanged
|
||||
auto setup = setup_FeeVote(config);
|
||||
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
|
||||
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
|
||||
BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve);
|
||||
BEAST_EXPECT(setup.hook_gas_price == defaultSetup.hook_gas_price);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
260
src/test/app/HookGasPrice_test.cpp
Normal file
260
src/test/app/HookGasPrice_test.cpp
Normal file
@@ -0,0 +1,260 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 XRPL Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <test/jtx.h>
|
||||
#include <xrpld/app/misc/FeeVote.h>
|
||||
#include <xrpld/app/tx/detail/Transactor.h>
|
||||
#include <xrpld/core/Config.h>
|
||||
#include <xrpl/basics/BasicConfig.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Fees.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
class HookGasPrice_test : public beast::unit_test::suite
|
||||
{
|
||||
// Advance to flag ledger so fee voting sets sfHookGasPrice
|
||||
void
|
||||
advanceToFlagLedger(jtx::Env& env)
|
||||
{
|
||||
auto const seq = env.current()->info().seq;
|
||||
for (auto i = seq; i <= 256; ++i)
|
||||
env.close();
|
||||
env.close();
|
||||
}
|
||||
|
||||
void
|
||||
testHookGasPriceGenesis(FeatureBitset features)
|
||||
{
|
||||
testcase("GasPrice genesis");
|
||||
using namespace jtx;
|
||||
|
||||
// Test Env passes empty amendments to genesis, so sfHookGasPrice
|
||||
// is NOT set in the genesis FeeSettings SLE.
|
||||
{
|
||||
Env env{*this, features};
|
||||
auto const sle = env.le(keylet::fees());
|
||||
if (!BEAST_EXPECT(sle))
|
||||
return;
|
||||
BEAST_EXPECT(!sle->isFieldPresent(sfHookGasPrice));
|
||||
}
|
||||
|
||||
// Without featureHookGas - also no sfHookGasPrice
|
||||
{
|
||||
Env env{*this, features - featureHookGas};
|
||||
auto const sle = env.le(keylet::fees());
|
||||
if (!BEAST_EXPECT(sle))
|
||||
return;
|
||||
BEAST_EXPECT(!sle->isFieldPresent(sfHookGasPrice));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testHookGasPriceFeeVoting(FeatureBitset features)
|
||||
{
|
||||
testcase("GasPrice fee voting");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(10000), Account{"alice"});
|
||||
env.close();
|
||||
|
||||
// Before flag ledger: sfHookGasPrice not in SLE
|
||||
{
|
||||
auto const sle = env.le(keylet::fees());
|
||||
if (!BEAST_EXPECT(sle))
|
||||
return;
|
||||
BEAST_EXPECT(!sle->isFieldPresent(sfHookGasPrice));
|
||||
}
|
||||
|
||||
// Advance to flag ledger to trigger fee voting
|
||||
advanceToFlagLedger(env);
|
||||
|
||||
// After fee voting with featureHookGas enabled:
|
||||
// sfHookGasPrice should be set in SLE via ttFEE pseudo-transaction
|
||||
{
|
||||
auto const sle = env.le(keylet::fees());
|
||||
if (!BEAST_EXPECT(sle))
|
||||
return;
|
||||
BEAST_EXPECT(sle->isFieldPresent(sfHookGasPrice));
|
||||
if (sle->isFieldPresent(sfHookGasPrice))
|
||||
BEAST_EXPECT(sle->getFieldU64(sfHookGasPrice) == 1'000'000);
|
||||
}
|
||||
BEAST_EXPECT(
|
||||
env.current()->fees().hookGasPrice == XRPAmount{1'000'000});
|
||||
}
|
||||
|
||||
void
|
||||
testHookGasPriceVotingUpdate(FeatureBitset features)
|
||||
{
|
||||
testcase("GasPrice voting update");
|
||||
using namespace jtx;
|
||||
|
||||
// Create env with custom gas_price = 2,000,000 via voting config
|
||||
auto cfg = envconfig();
|
||||
auto& votingSection = cfg->section("voting");
|
||||
votingSection.set("hook_gas_price", "2000000");
|
||||
// A validation_seed is required for fee voting to work
|
||||
cfg->section("validation_seed").legacy("shUwVw52ofnCUX5m7kPTKzJdr4HEH");
|
||||
|
||||
Env env{*this, std::move(cfg), features};
|
||||
env.fund(XRP(10000), Account{"alice"});
|
||||
env.close();
|
||||
|
||||
// Before flag ledger: sfHookGasPrice not in SLE
|
||||
{
|
||||
auto const sle = env.le(keylet::fees());
|
||||
if (!BEAST_EXPECT(sle))
|
||||
return;
|
||||
BEAST_EXPECT(!sle->isFieldPresent(sfHookGasPrice));
|
||||
}
|
||||
|
||||
// Advance to flag ledger to trigger fee voting
|
||||
advanceToFlagLedger(env);
|
||||
|
||||
// After voting: sfHookGasPrice should be updated to 2,000,000
|
||||
{
|
||||
auto const sle = env.le(keylet::fees());
|
||||
if (!BEAST_EXPECT(sle))
|
||||
return;
|
||||
BEAST_EXPECT(sle->isFieldPresent(sfHookGasPrice));
|
||||
if (sle->isFieldPresent(sfHookGasPrice))
|
||||
BEAST_EXPECT(sle->getFieldU64(sfHookGasPrice) == 2'000'000);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testHookGasPriceFeeCalculation(FeatureBitset features)
|
||||
{
|
||||
testcase("GasPrice fee calculation");
|
||||
using namespace jtx;
|
||||
|
||||
{
|
||||
// With default gasPrice = 1,000,000:
|
||||
auto cfg = envconfig();
|
||||
auto& votingSection = cfg->section("voting");
|
||||
votingSection.set("hook_gas_price", "1000000");
|
||||
// A validation_seed is required for fee voting to work
|
||||
cfg->section("validation_seed")
|
||||
.legacy("shUwVw52ofnCUX5m7kPTKzJdr4HEH");
|
||||
|
||||
Env env{*this, std::move(cfg), features};
|
||||
env.fund(XRP(10000), Account{"alice"});
|
||||
env.close();
|
||||
|
||||
// Advance to flag ledger so gasPrice is set via fee voting
|
||||
advanceToFlagLedger(env);
|
||||
|
||||
auto const& fees = env.current()->fees();
|
||||
BEAST_EXPECT(fees.hookGasPrice == XRPAmount{1'000'000});
|
||||
|
||||
// Verify the gas price calculation formula:
|
||||
// fee = gasCount * gasPrice / 1,000,000
|
||||
|
||||
// With default gasPrice = 1,000,000:
|
||||
// 1,000,000 gas => 1,000,000 * 1,000,000 / 1,000,000 = 1,000,000
|
||||
// 500,000 gas => 500,000 drops
|
||||
// 0 gas => 0 drops
|
||||
for (auto gasCount : {1'000'000, 500'000, 0})
|
||||
{
|
||||
auto gasFee = Transactor::calculateHookGas(gasCount, fees);
|
||||
BEAST_EXPECT(gasFee == XRPAmount{gasCount});
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// With gasPrice = 100,000:
|
||||
auto cfg = envconfig();
|
||||
auto& votingSection = cfg->section("voting");
|
||||
votingSection.set("hook_gas_price", "100000");
|
||||
// A validation_seed is required for fee voting to work
|
||||
cfg->section("validation_seed")
|
||||
.legacy("shUwVw52ofnCUX5m7kPTKzJdr4HEH");
|
||||
|
||||
Env env{*this, std::move(cfg), features};
|
||||
env.fund(XRP(10000), Account{"alice"});
|
||||
env.close();
|
||||
|
||||
// Advance to flag ledger so gasPrice is set via fee voting
|
||||
advanceToFlagLedger(env);
|
||||
|
||||
auto const& fees = env.current()->fees();
|
||||
BEAST_EXPECT(fees.hookGasPrice == XRPAmount{100'000});
|
||||
|
||||
// Verify the gas price calculation formula:
|
||||
// fee = gasCount * gasPrice / 1,000,000
|
||||
|
||||
// With default gasPrice = 1,000,000:
|
||||
// 1,000,000 gas => 1,000,000 * 100,000 / 1,000,000 = 100,000
|
||||
// 500,000 gas => 50,000 drops
|
||||
// 0 gas => 0 drops
|
||||
for (auto gasCount : {1'000'000, 500'000, 0})
|
||||
{
|
||||
auto gasFee = Transactor::calculateHookGas(gasCount, fees);
|
||||
BEAST_EXPECT(gasFee == XRPAmount{gasCount / 10});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testHookGasPriceDisabled(FeatureBitset features)
|
||||
{
|
||||
testcase("GasPrice disabled");
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this, features - featureHookGas};
|
||||
env.fund(XRP(10000), Account{"alice"});
|
||||
env.close();
|
||||
|
||||
// sfHookGasPrice not in SLE when amendment disabled
|
||||
auto const sle = env.le(keylet::fees());
|
||||
if (!BEAST_EXPECT(sle))
|
||||
return;
|
||||
BEAST_EXPECT(!sle->isFieldPresent(sfHookGasPrice));
|
||||
|
||||
// Advance past flag ledger - sfHookGasPrice should still not be set
|
||||
advanceToFlagLedger(env);
|
||||
|
||||
{
|
||||
auto const sle2 = env.le(keylet::fees());
|
||||
if (!BEAST_EXPECT(sle2))
|
||||
return;
|
||||
BEAST_EXPECT(!sle2->isFieldPresent(sfHookGasPrice));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
using namespace jtx;
|
||||
auto const sa = supported_amendments();
|
||||
testHookGasPriceGenesis(sa);
|
||||
testHookGasPriceFeeVoting(sa);
|
||||
testHookGasPriceVotingUpdate(sa);
|
||||
testHookGasPriceFeeCalculation(sa);
|
||||
testHookGasPriceDisabled(sa);
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(HookGasPrice, app, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -41,17 +41,28 @@ echo '
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
std::map<std::string, std::vector<uint8_t>> wasm = {' > $OUTPUT_FILE
|
||||
COUNTER="0"
|
||||
cat $INPUT_FILE | tr '\n' '\f' |
|
||||
grep -Po 'R"\[test\.hook\](.*?)\[test\.hook\]"' |
|
||||
sed -E 's/R"\[test\.hook\]\(//g' |
|
||||
sed -E 's/\)\[test\.hook\]"[\f \t]*/\/*end*\//g' |
|
||||
# Counter file for sharing between subshells
|
||||
COUNTER_FILE=$(mktemp)
|
||||
echo "0" > $COUNTER_FILE
|
||||
trap "rm -f $COUNTER_FILE" EXIT
|
||||
|
||||
# Process both [test.hook] and [test.hook.gas] blocks
|
||||
process_block() {
|
||||
local tag_pattern="$1" # regex pattern: "hook" or "hook\.gas"
|
||||
local tag_output="$2" # output string: "hook" or "hook.gas"
|
||||
local skip_cleaner="$3" # "0" for no skip, "1" for skip
|
||||
|
||||
cat $INPUT_FILE | tr '\n' '\f' |
|
||||
grep -Po "R\"\[test\.${tag_pattern}\](.*?)\[test\.${tag_pattern}\]\"" |
|
||||
sed -E "s/R\"\[test\.${tag_pattern}\]\(//g" |
|
||||
sed -E "s/\)\[test\.${tag_pattern}\]\"[\f \t]*/\/*end*\//g" |
|
||||
while read -r line
|
||||
do
|
||||
COUNTER=$(cat $COUNTER_FILE)
|
||||
echo "/* ==== WASM: $COUNTER ==== */" >> $OUTPUT_FILE
|
||||
echo -n '{ R"[test.hook](' >> $OUTPUT_FILE
|
||||
echo -n "{ R\"[test.${tag_output}](" >> $OUTPUT_FILE
|
||||
cat <<< "$line" | sed -E 's/.{7}$//g' | tr -d '\n' | tr '\f' '\n' >> $OUTPUT_FILE
|
||||
echo ')[test.hook]",' >> $OUTPUT_FILE
|
||||
echo ")[test.${tag_output}]\"," >> $OUTPUT_FILE
|
||||
echo "{" >> $OUTPUT_FILE
|
||||
WAT=`grep -Eo '\(module' <<< $line | wc -l`
|
||||
if [ "$WAT" -eq "0" ]
|
||||
@@ -69,10 +80,19 @@ cat $INPUT_FILE | tr '\n' '\f' |
|
||||
echo "$line"
|
||||
exit 1
|
||||
fi
|
||||
wasmcc -x c /dev/stdin -o /dev/stdout -O2 -Wl,--allow-undefined <<< "`tr '\f' '\n' <<< $line`" |
|
||||
hook-cleaner - - 2>/dev/null |
|
||||
xxd -p -u -c 10 |
|
||||
sed -E 's/../0x&U,/g' | sed -E 's/^/ /g' >> $OUTPUT_FILE
|
||||
if [ "$skip_cleaner" -eq "1" ]
|
||||
then
|
||||
# Skip hook-cleaner for [test.hook.gas]
|
||||
wasmcc -x c /dev/stdin -o /dev/stdout -O2 -Wl,--allow-undefined -Wno-int-conversion -Wno-pointer-sign -Wno-return-type <<< "`tr '\f' '\n' <<< $line`" |
|
||||
xxd -p -u -c 10 |
|
||||
sed -E 's/../0x&U,/g' | sed -E 's/^/ /g' >> $OUTPUT_FILE
|
||||
else
|
||||
# Run hook-cleaner for [test.hook]
|
||||
wasmcc -x c /dev/stdin -o /dev/stdout -O2 -Wl,--allow-undefined -Wno-int-conversion -Wno-pointer-sign -Wno-return-type <<< "`tr '\f' '\n' <<< $line`" |
|
||||
hook-cleaner - - 2>/dev/null |
|
||||
xxd -p -u -c 10 |
|
||||
sed -E 's/../0x&U,/g' | sed -E 's/^/ /g' >> $OUTPUT_FILE
|
||||
fi
|
||||
else
|
||||
wat2wasm - -o /dev/stdout <<< "`tr '\f' '\n' <<< $(sed -E 's/.{7}$//g' <<< $line)`" |
|
||||
xxd -p -u -c 10 |
|
||||
@@ -85,8 +105,15 @@ cat $INPUT_FILE | tr '\n' '\f' |
|
||||
fi
|
||||
echo '}},' >> $OUTPUT_FILE
|
||||
echo >> $OUTPUT_FILE
|
||||
COUNTER=`echo $COUNTER + 1 | bc`
|
||||
echo $((COUNTER + 1)) > $COUNTER_FILE
|
||||
done
|
||||
}
|
||||
|
||||
# Process [test.hook] blocks (with hook-cleaner)
|
||||
process_block "hook" "hook" "0"
|
||||
|
||||
# Process [test.hook.gas] blocks (without hook-cleaner)
|
||||
process_block "hook\.gas" "hook.gas" "1"
|
||||
echo '};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,9 @@ hso(std::vector<uint8_t> const& wasmBytes, void (*f)(Json::Value& jv) = 0);
|
||||
Json::Value
|
||||
hso(std::string const& wasmHex, void (*f)(Json::Value& jv) = 0);
|
||||
|
||||
Json::Value
|
||||
hso(uint256 const& hookHash, void (*f)(Json::Value& jv) = 0);
|
||||
|
||||
Json::Value
|
||||
hso_delete(void (*f)(Json::Value& jv) = 0);
|
||||
|
||||
|
||||
80
src/test/jtx/hookgas.h
Normal file
80
src/test/jtx/hookgas.h
Normal file
@@ -0,0 +1,80 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 XRPL Labs
|
||||
|
||||
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_TEST_JTX_HOOKGAS_H_INCLUDED
|
||||
#define RIPPLE_TEST_JTX_HOOKGAS_H_INCLUDED
|
||||
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/tags.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
|
||||
/** Set the HookGas on a JTx. */
|
||||
class hookgas
|
||||
{
|
||||
private:
|
||||
std::uint32_t gas_;
|
||||
|
||||
public:
|
||||
hookgas(std::uint32_t gas) : gas_{gas}
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const;
|
||||
};
|
||||
|
||||
/** Set the HookCallbackGas on a JTx. */
|
||||
class cbakgas
|
||||
{
|
||||
private:
|
||||
std::uint32_t gas_;
|
||||
|
||||
public:
|
||||
cbakgas(std::uint32_t gas) : gas_{gas}
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const;
|
||||
};
|
||||
|
||||
/** Set the HookWeakGas on a JTx. */
|
||||
class weakgas
|
||||
{
|
||||
private:
|
||||
std::uint32_t gas_;
|
||||
|
||||
public:
|
||||
weakgas(std::uint32_t gas) : gas_{gas}
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const;
|
||||
};
|
||||
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -104,6 +104,16 @@ hso(std::string const& wasmHex, void (*f)(Json::Value& jv))
|
||||
return jv;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
hso(uint256 const& hookHash, void (*f)(Json::Value& jv))
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::HookHash] = to_string(hookHash);
|
||||
if (f)
|
||||
f(jv);
|
||||
return jv;
|
||||
}
|
||||
|
||||
// Helper function to create HookContext with external stateMap
|
||||
hook::HookContext
|
||||
makeStubHookContext(
|
||||
|
||||
47
src/test/jtx/impl/hookgas.cpp
Normal file
47
src/test/jtx/impl/hookgas.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 XRPL Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <test/jtx/hookgas.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
|
||||
void
|
||||
hookgas::operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt[sfHookGas.jsonName] = gas_;
|
||||
}
|
||||
|
||||
void
|
||||
cbakgas::operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt[sfHookCallbackGas.jsonName] = gas_;
|
||||
}
|
||||
|
||||
void
|
||||
weakgas::operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt[sfHookWeakGas.jsonName] = gas_;
|
||||
}
|
||||
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -63,18 +63,23 @@ namespace hook_api {
|
||||
fprintf
|
||||
|
||||
#pragma push_macro("HOOK_API_DEFINITION")
|
||||
#pragma push_macro("HOOK_API_COST")
|
||||
#undef HOOK_API_DEFINITION
|
||||
#undef HOOK_API_COST
|
||||
|
||||
#define HOOK_WRAP_PARAMS(...) __VA_ARGS__
|
||||
#define HOOK_API_DEFINITION(RETURN_TYPE, FUNCTION_NAME, PARAMS_TUPLE, ...) \
|
||||
DECLARE_HOOK_FUNCTION( \
|
||||
RETURN_TYPE, FUNCTION_NAME, HOOK_WRAP_PARAMS PARAMS_TUPLE);
|
||||
#define HOOK_API_COST(FUNCTION_NAME, cost, amendment)
|
||||
|
||||
#include <xrpl/hook/hook_api.macro>
|
||||
|
||||
#undef HOOK_API_DEFINITION
|
||||
#undef HOOK_API_COST
|
||||
#undef HOOK_WRAP_PARAMS
|
||||
#pragma pop_macro("HOOK_API_DEFINITION")
|
||||
#pragma pop_macro("HOOK_API_COST")
|
||||
|
||||
} /* end namespace hook_api */
|
||||
|
||||
@@ -124,7 +129,9 @@ apply(
|
||||
uint32_t wasmParam,
|
||||
uint8_t hookChainPosition,
|
||||
// result of apply() if this is weak exec
|
||||
std::shared_ptr<STObject const> const& provisionalMeta);
|
||||
std::shared_ptr<STObject const> const& provisionalMeta,
|
||||
uint16_t hookApiVersion,
|
||||
uint32_t hookGas);
|
||||
|
||||
struct HookContext;
|
||||
|
||||
@@ -162,6 +169,7 @@ struct HookResult
|
||||
std::string exitReason{""};
|
||||
int64_t exitCode{-1};
|
||||
uint64_t instructionCount{0};
|
||||
uint64_t instructionCost{0};
|
||||
bool hasCallback = false; // true iff this hook wasm has a cbak function
|
||||
bool isCallback =
|
||||
false; // true iff this hook execution is a callback in action
|
||||
@@ -174,6 +182,8 @@ struct HookResult
|
||||
false; // hook_again allows strong pre-apply to nominate
|
||||
// additional weak post-apply execution
|
||||
std::shared_ptr<STObject const> provisionalMeta;
|
||||
uint16_t hookApiVersion = 0; // 0 = Guard-type, 1 = Gas-type
|
||||
uint32_t hookGas; // Gas limit for Gas-type hooks
|
||||
};
|
||||
|
||||
class HookExecutor;
|
||||
@@ -270,14 +280,14 @@ gatherHookParameters(
|
||||
beast::Journal const& j_);
|
||||
|
||||
// RH TODO: call destruct for these on rippled shutdown
|
||||
#define ADD_HOOK_FUNCTION(F, ctx) \
|
||||
#define ADD_HOOK_FUNCTION(F, ctx, cost) \
|
||||
{ \
|
||||
WasmEdge_FunctionInstanceContext* hf = \
|
||||
WasmEdge_FunctionInstanceCreate( \
|
||||
hook_api::WasmFunctionType##F, \
|
||||
hook_api::WasmFunction##F, \
|
||||
(void*)(&ctx), \
|
||||
0); \
|
||||
cost); \
|
||||
WasmEdge_ModuleInstanceAddFunction( \
|
||||
importObj, hook_api::WasmFunctionName##F, hf); \
|
||||
}
|
||||
@@ -325,12 +335,18 @@ public:
|
||||
WasmEdge_ConfigureContext* conf = NULL;
|
||||
WasmEdge_VMContext* ctx = NULL;
|
||||
|
||||
WasmEdgeVM()
|
||||
WasmEdgeVM(uint16_t hookApiVersion)
|
||||
{
|
||||
conf = WasmEdge_ConfigureCreate();
|
||||
if (!conf)
|
||||
return;
|
||||
WasmEdge_ConfigureStatisticsSetInstructionCounting(conf, true);
|
||||
if (hookApiVersion == 1)
|
||||
{
|
||||
WasmEdge_ConfigureStatisticsSetCostMeasuring(conf, true);
|
||||
WasmEdge_ConfigureSetMaxMemoryPage(
|
||||
conf, hook_api::max_memory_pages);
|
||||
}
|
||||
ctx = WasmEdge_VMCreate(conf, NULL);
|
||||
}
|
||||
|
||||
@@ -365,9 +381,9 @@ public:
|
||||
* Validate that a web assembly blob can be loaded by wasmedge
|
||||
*/
|
||||
static std::optional<std::string>
|
||||
validateWasm(const void* wasm, size_t len)
|
||||
validateWasm(const void* wasm, size_t len, uint16_t hookApiVersion)
|
||||
{
|
||||
WasmEdgeVM vm;
|
||||
WasmEdgeVM vm{hookApiVersion};
|
||||
|
||||
if (!vm.sane())
|
||||
return "Could not create WASMEDGE instance";
|
||||
@@ -412,7 +428,7 @@ public:
|
||||
|
||||
WasmEdge_LogOff();
|
||||
|
||||
WasmEdgeVM vm;
|
||||
WasmEdgeVM vm{hookCtx.result.hookApiVersion};
|
||||
|
||||
if (!vm.sane())
|
||||
{
|
||||
@@ -433,6 +449,22 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
// Set Gas limit for Gas-type hooks (HookApiVersion == 1)
|
||||
if (hookCtx.result.hookApiVersion == 1)
|
||||
{
|
||||
auto* statsCtx = WasmEdge_VMGetStatisticsContext(vm.ctx);
|
||||
if (statsCtx)
|
||||
{
|
||||
// Convert HookGas to cost limit count (1 Gas = 1 cost)
|
||||
uint32_t gasLimit = hookCtx.result.hookGas;
|
||||
WasmEdge_StatisticsSetCostLimit(statsCtx, gasLimit);
|
||||
|
||||
JLOG(j.trace())
|
||||
<< "HookInfo[" << HC_ACC() << "]: Set Gas limit to "
|
||||
<< gasLimit << " cost limit for Gas-type Hook";
|
||||
}
|
||||
}
|
||||
|
||||
WasmEdge_Value params[1] = {WasmEdge_ValueGenI32((int64_t)wasmParam)};
|
||||
WasmEdge_Value returns[1];
|
||||
|
||||
@@ -446,16 +478,31 @@ public:
|
||||
returns,
|
||||
1);
|
||||
|
||||
if (auto err = getWasmError("WASM VM error", res); err)
|
||||
{
|
||||
JLOG(j.warn()) << "HookError[" << HC_ACC() << "]: " << *err;
|
||||
hookCtx.result.exitType = hook_api::ExitType::WASM_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
auto* statsCtx = WasmEdge_VMGetStatisticsContext(vm.ctx);
|
||||
hookCtx.result.instructionCount =
|
||||
WasmEdge_StatisticsGetInstrCount(statsCtx);
|
||||
hookCtx.result.instructionCost =
|
||||
WasmEdge_StatisticsGetTotalCost(statsCtx);
|
||||
|
||||
if (auto err = getWasmError("WASM VM error", res); err)
|
||||
{
|
||||
JLOG(j.trace()) << "HookError[" << HC_ACC() << "]: " << *err;
|
||||
|
||||
// Check if error is due to Gas limit exceeded for Gas-type hooks
|
||||
if (hookCtx.result.hookApiVersion == 1 &&
|
||||
err->find("cost limit exceeded") != std::string::npos)
|
||||
{
|
||||
JLOG(j.trace()) << "HookError[" << HC_ACC()
|
||||
<< "]: Gas limit exceeded. Limit was "
|
||||
<< hookCtx.result.hookGas;
|
||||
hookCtx.result.exitType = hook_api::ExitType::GAS_INSUFFICIENT;
|
||||
}
|
||||
else
|
||||
{
|
||||
hookCtx.result.exitType = hook_api::ExitType::WASM_ERROR;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// RH NOTE: stack unwind will clean up WasmEdgeVM
|
||||
}
|
||||
@@ -468,17 +515,40 @@ public:
|
||||
WasmEdge_LogSetDebugLevel();
|
||||
|
||||
#pragma push_macro("HOOK_API_DEFINITION")
|
||||
#pragma push_macro("HOOK_API_COST")
|
||||
|
||||
// Access rules for amendment-based cost switching
|
||||
auto const& rules_ = ctx.applyCtx.view().rules();
|
||||
|
||||
// Phase 1: Declare per-function cost variables, initialized to 0
|
||||
#undef HOOK_API_DEFINITION
|
||||
|
||||
#undef HOOK_API_COST
|
||||
#define HOOK_WRAP_PARAMS(...) __VA_ARGS__
|
||||
#define HOOK_API_DEFINITION(RETURN_TYPE, FUNCTION_NAME, PARAMS_TUPLE, ...) \
|
||||
ADD_HOOK_FUNCTION(FUNCTION_NAME, ctx);
|
||||
#define HOOK_API_DEFINITION(RT, FN, ...) uint64_t cost_##FN = 0;
|
||||
#define HOOK_API_COST(...)
|
||||
#include <xrpl/hook/hook_api.macro>
|
||||
|
||||
// Phase 2: Set costs; amendment-gated entries override base costs
|
||||
#undef HOOK_API_DEFINITION
|
||||
#undef HOOK_API_COST
|
||||
#define HOOK_API_DEFINITION(...)
|
||||
#define HOOK_API_COST(FN, cost, AM) \
|
||||
if ((AM) == uint256{} || rules_.enabled(AM)) \
|
||||
cost_##FN = (cost);
|
||||
#include <xrpl/hook/hook_api.macro>
|
||||
|
||||
// Phase 3: Register functions with WasmEdge using computed costs
|
||||
#undef HOOK_API_DEFINITION
|
||||
#undef HOOK_API_COST
|
||||
#define HOOK_API_DEFINITION(RT, FN, ...) ADD_HOOK_FUNCTION(FN, ctx, cost_##FN);
|
||||
#define HOOK_API_COST(...)
|
||||
#include <xrpl/hook/hook_api.macro>
|
||||
|
||||
#undef HOOK_API_DEFINITION
|
||||
#undef HOOK_API_COST
|
||||
#undef HOOK_WRAP_PARAMS
|
||||
#pragma pop_macro("HOOK_API_DEFINITION")
|
||||
#pragma pop_macro("HOOK_API_COST")
|
||||
|
||||
WasmEdge_TableInstanceContext* hostTable =
|
||||
WasmEdge_TableInstanceCreate(tableType);
|
||||
|
||||
444
src/xrpld/app/hook/detail/GasValidator.cpp
Normal file
444
src/xrpld/app/hook/detail/GasValidator.cpp
Normal file
@@ -0,0 +1,444 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2024 XRPL-Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/hook/GasValidator.h>
|
||||
#include <xrpl/hook/Guard.h>
|
||||
#include <xrpl/hook/Macro.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <wasmedge/wasmedge.h>
|
||||
|
||||
namespace hook {
|
||||
|
||||
Expected<bool, std::string>
|
||||
validateExportSection(
|
||||
WasmEdge_ASTModuleContext* astModule,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
// Get export count
|
||||
uint32_t exportCount = WasmEdge_ASTModuleListExportsLength(astModule);
|
||||
if (exportCount == 0)
|
||||
{
|
||||
return Unexpected("WASM must export at least hook API functions");
|
||||
}
|
||||
|
||||
// Get exports
|
||||
const WasmEdge_ExportTypeContext* exports[256];
|
||||
uint32_t actualExportCount = std::min(exportCount, 256u);
|
||||
actualExportCount =
|
||||
WasmEdge_ASTModuleListExports(astModule, exports, actualExportCount);
|
||||
|
||||
// Track if we found required hook() function
|
||||
bool foundHook = false;
|
||||
bool foundCbak = false;
|
||||
|
||||
// Check each export
|
||||
for (uint32_t i = 0; i < actualExportCount; i++)
|
||||
{
|
||||
WasmEdge_ExternalType const type =
|
||||
WasmEdge_ExportTypeGetExternalType(exports[i]);
|
||||
|
||||
// Validate memory exports for page limits
|
||||
if (type == WasmEdge_ExternalType_Memory)
|
||||
{
|
||||
const WasmEdge_MemoryTypeContext* memType =
|
||||
WasmEdge_ExportTypeGetMemoryType(astModule, exports[i]);
|
||||
|
||||
if (!memType)
|
||||
{
|
||||
JLOG(j.trace()) << "HookSet(" << hook::log::MEMORY_PAGE_LIMIT
|
||||
<< "): Exported memory has no type definition";
|
||||
return Unexpected("Exported memory has no type definition");
|
||||
}
|
||||
|
||||
WasmEdge_Limit limit = WasmEdge_MemoryTypeGetLimit(memType);
|
||||
|
||||
constexpr uint32_t kMaxMemoryPages = hook_api::max_memory_pages;
|
||||
|
||||
if (limit.Min > kMaxMemoryPages)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "HookSet(" << hook::log::MEMORY_PAGE_LIMIT
|
||||
<< "): Gas-type hook exported memory minimum "
|
||||
"pages ("
|
||||
<< limit.Min << ") exceeds limit of " << kMaxMemoryPages;
|
||||
return Unexpected(
|
||||
"Gas-type hook exported memory minimum pages "
|
||||
"exceed limit of 8");
|
||||
}
|
||||
|
||||
if (limit.HasMax && limit.Max > kMaxMemoryPages)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "HookSet(" << hook::log::MEMORY_PAGE_LIMIT
|
||||
<< "): Gas-type hook exported memory maximum "
|
||||
"pages ("
|
||||
<< limit.Max << ") exceeds limit of " << kMaxMemoryPages;
|
||||
return Unexpected(
|
||||
"Gas-type hook exported memory maximum pages "
|
||||
"exceed limit of 8");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only check function exports
|
||||
if (type != WasmEdge_ExternalType_Function)
|
||||
continue;
|
||||
|
||||
WasmEdge_String const name =
|
||||
WasmEdge_ExportTypeGetExternalName(exports[i]);
|
||||
std::string nameStr(name.Buf, name.Length);
|
||||
|
||||
if (nameStr.starts_with("__"))
|
||||
{
|
||||
// skip runtime support functions
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only allow hook() and cbak() exports
|
||||
if (nameStr != "hook" && nameStr != "cbak")
|
||||
{
|
||||
JLOG(j.trace()) << "HookSet(" << hook::log::EXPORT_MISSING
|
||||
<< "): Unauthorized export function '" << nameStr
|
||||
<< "'. Only 'hook' and 'cbak' are allowed";
|
||||
return Unexpected(
|
||||
"Unauthorized export function '" + nameStr +
|
||||
"'. Only 'hook' and 'cbak' are allowed");
|
||||
}
|
||||
|
||||
if (nameStr == "hook")
|
||||
foundHook = true;
|
||||
if (nameStr == "cbak")
|
||||
foundCbak = true;
|
||||
|
||||
// Get function type to validate signature
|
||||
WasmEdge_FunctionTypeContext const* functionType =
|
||||
WasmEdge_ExportTypeGetFunctionType(astModule, exports[i]);
|
||||
|
||||
// Validate parameter count (must be exactly 1)
|
||||
uint32_t paramCount =
|
||||
WasmEdge_FunctionTypeGetParametersLength(functionType);
|
||||
if (paramCount != 1)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "HookSet("
|
||||
<< (nameStr == "hook" ? hook::log::EXPORT_HOOK_FUNC
|
||||
: hook::log::EXPORT_CBAK_FUNC)
|
||||
<< "): Function '" << nameStr
|
||||
<< "' must have exactly 1 parameter, found " << paramCount;
|
||||
return Unexpected(
|
||||
"Function '" + nameStr +
|
||||
"' must have exactly 1 parameter of type uint32_t");
|
||||
}
|
||||
|
||||
// Validate parameter type (must be i32 / uint32_t)
|
||||
WasmEdge_ValType parameters[1];
|
||||
WasmEdge_FunctionTypeGetParameters(functionType, parameters, 1);
|
||||
if (parameters[0] != WasmEdge_ValType_I32)
|
||||
{
|
||||
JLOG(j.trace()) << "HookSet("
|
||||
<< (nameStr == "hook" ? hook::log::EXPORT_HOOK_FUNC
|
||||
: hook::log::EXPORT_CBAK_FUNC)
|
||||
<< "): Function '" << nameStr
|
||||
<< "' parameter must be uint32_t (i32), found type "
|
||||
<< parameters[0];
|
||||
return Unexpected(
|
||||
"Function '" + nameStr + "' parameter must be uint32_t (i32)");
|
||||
}
|
||||
|
||||
// Validate return type (must be i64 / uint64_t)
|
||||
uint32_t returnCount =
|
||||
WasmEdge_FunctionTypeGetReturnsLength(functionType);
|
||||
if (returnCount != 1)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "HookSet("
|
||||
<< (nameStr == "hook" ? hook::log::EXPORT_HOOK_FUNC
|
||||
: hook::log::EXPORT_CBAK_FUNC)
|
||||
<< "): Function '" << nameStr
|
||||
<< "' must return exactly 1 value, found " << returnCount;
|
||||
return Unexpected(
|
||||
"Function '" + nameStr +
|
||||
"' must return exactly 1 value of type uint64_t");
|
||||
}
|
||||
|
||||
WasmEdge_ValType returns[1];
|
||||
WasmEdge_FunctionTypeGetReturns(functionType, returns, 1);
|
||||
if (returns[0] != WasmEdge_ValType_I64)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "HookSet("
|
||||
<< (nameStr == "hook" ? hook::log::EXPORT_HOOK_FUNC
|
||||
: hook::log::EXPORT_CBAK_FUNC)
|
||||
<< "): Function '" << nameStr
|
||||
<< "' return type must be uint64_t (i64), found type "
|
||||
<< returns[0];
|
||||
return Unexpected(
|
||||
"Function '" + nameStr +
|
||||
"' return type must be uint64_t (i64)");
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure hook() function was exported (required)
|
||||
if (!foundHook)
|
||||
{
|
||||
JLOG(j.trace()) << "HookSet(" << hook::log::EXPORT_MISSING
|
||||
<< "): Required function 'hook' not found in exports";
|
||||
return Unexpected("Required function 'hook' not found in exports");
|
||||
}
|
||||
|
||||
return foundCbak;
|
||||
}
|
||||
|
||||
Expected<void, std::string>
|
||||
validateImportSection(
|
||||
WasmEdge_ASTModuleContext* astModule,
|
||||
Rules const& rules,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
// Get import count
|
||||
uint32_t importCount = WasmEdge_ASTModuleListImportsLength(astModule);
|
||||
|
||||
if (importCount == 0)
|
||||
{
|
||||
JLOG(j.trace()) << "HookSet(" << hook::log::IMPORTS_MISSING
|
||||
<< "): WASM must import at least hook API functions";
|
||||
return Unexpected("WASM must import at least hook API functions");
|
||||
}
|
||||
|
||||
// Get imports (max 256)
|
||||
const WasmEdge_ImportTypeContext* imports[256];
|
||||
uint32_t actualImportCount = std::min(importCount, 256u);
|
||||
actualImportCount =
|
||||
WasmEdge_ASTModuleListImports(astModule, imports, actualImportCount);
|
||||
|
||||
std::optional<std::string> error;
|
||||
|
||||
// Check each import
|
||||
for (uint32_t i = 0; i < actualImportCount; i++)
|
||||
{
|
||||
WasmEdge_String moduleName =
|
||||
WasmEdge_ImportTypeGetModuleName(imports[i]);
|
||||
WasmEdge_String externalName =
|
||||
WasmEdge_ImportTypeGetExternalName(imports[i]);
|
||||
WasmEdge_ExternalType extType =
|
||||
WasmEdge_ImportTypeGetExternalType(imports[i]);
|
||||
|
||||
// Reject non-function imports (memory, global, table)
|
||||
if (extType != WasmEdge_ExternalType_Function)
|
||||
{
|
||||
JLOG(j.trace()) << "HookSet(" << hook::log::IMPORT_ILLEGAL
|
||||
<< "): Gas-type hooks cannot have non-function "
|
||||
"imports";
|
||||
return Unexpected(
|
||||
"Gas-type hooks cannot have non-function imports");
|
||||
}
|
||||
|
||||
// Convert WasmEdge_String to std::string for comparison
|
||||
std::string modName(moduleName.Buf, moduleName.Length);
|
||||
std::string extName(externalName.Buf, externalName.Length);
|
||||
|
||||
// Check module name is "env"
|
||||
if (modName != "env")
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "HookSet(" << hook::log::IMPORT_MODULE_ENV
|
||||
<< "): Import module must be 'env', found: " << modName;
|
||||
return Unexpected("Import module must be 'env', found: " + modName);
|
||||
}
|
||||
|
||||
// Check for forbidden _g function (guard function)
|
||||
if (extName == "_g")
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "HookSet(" << hook::log::IMPORT_ILLEGAL
|
||||
<< "): Gas-type hooks cannot import _g (guard) function";
|
||||
return Unexpected(
|
||||
"Gas-type hooks cannot import _g (guard) function");
|
||||
}
|
||||
|
||||
// Determine which whitelist contains the function and get expected
|
||||
// signature
|
||||
std::vector<uint8_t> const* expectedSig = nullptr;
|
||||
auto importWhitelist = hook_api::getImportWhitelist(rules);
|
||||
|
||||
auto baseIt = importWhitelist.find(extName);
|
||||
if (baseIt != importWhitelist.end())
|
||||
expectedSig = &baseIt->second;
|
||||
|
||||
// Function not in any whitelist
|
||||
if (!expectedSig)
|
||||
{
|
||||
JLOG(j.trace()) << "HookSet(" << hook::log::IMPORT_ILLEGAL
|
||||
<< "): Import not in whitelist: " << extName;
|
||||
return Unexpected("Import not in whitelist: " + extName);
|
||||
}
|
||||
|
||||
// Get function type for signature validation
|
||||
WasmEdge_FunctionTypeContext const* functionType =
|
||||
WasmEdge_ImportTypeGetFunctionType(astModule, imports[i]);
|
||||
|
||||
if (!functionType)
|
||||
{
|
||||
JLOG(j.trace()) << "HookSet(" << hook::log::FUNC_TYPELESS
|
||||
<< "): Import function '" << extName
|
||||
<< "' has no function type definition";
|
||||
return Unexpected(
|
||||
"Import function '" + extName +
|
||||
"' has no function type definition");
|
||||
}
|
||||
|
||||
// Validate return type
|
||||
// expectedSig[0] is the return type
|
||||
uint32_t returnCount =
|
||||
WasmEdge_FunctionTypeGetReturnsLength(functionType);
|
||||
|
||||
if (returnCount != 1)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "HookSet(" << hook::log::FUNC_RETURN_COUNT
|
||||
<< "): Import function '" << extName
|
||||
<< "' must return exactly 1 value, found " << returnCount;
|
||||
return Unexpected(
|
||||
"Import function '" + extName +
|
||||
"' must return exactly 1 value");
|
||||
}
|
||||
|
||||
WasmEdge_ValType actualReturnType;
|
||||
WasmEdge_FunctionTypeGetReturns(functionType, &actualReturnType, 1);
|
||||
|
||||
if (actualReturnType != (*expectedSig)[0])
|
||||
{
|
||||
JLOG(j.trace()) << "HookSet(" << hook::log::FUNC_RETURN_INVALID
|
||||
<< "): Import function '" << extName
|
||||
<< "' has incorrect return type. Expected "
|
||||
<< static_cast<int>((*expectedSig)[0]) << ", found "
|
||||
<< static_cast<int>(actualReturnType);
|
||||
return Unexpected(
|
||||
"Import function '" + extName + "' has incorrect return type");
|
||||
}
|
||||
|
||||
// Validate parameter count and types
|
||||
// expectedSig[1..N] are the parameter types
|
||||
uint32_t expectedParamCount =
|
||||
expectedSig->size() > 0 ? expectedSig->size() - 1 : 0;
|
||||
uint32_t actualParamCount =
|
||||
WasmEdge_FunctionTypeGetParametersLength(functionType);
|
||||
|
||||
if (actualParamCount != expectedParamCount)
|
||||
{
|
||||
JLOG(j.trace()) << "HookSet(" << hook::log::FUNC_PARAM_INVALID
|
||||
<< "): Import function '" << extName << "' has "
|
||||
<< actualParamCount << " parameters, expected "
|
||||
<< expectedParamCount;
|
||||
return Unexpected(
|
||||
"Import function '" + extName +
|
||||
"' has incorrect parameter count");
|
||||
}
|
||||
|
||||
// Validate each parameter type
|
||||
if (actualParamCount > 0)
|
||||
{
|
||||
std::vector<WasmEdge_ValType> actualParams(actualParamCount);
|
||||
WasmEdge_FunctionTypeGetParameters(
|
||||
functionType, actualParams.data(), actualParamCount);
|
||||
|
||||
for (uint32_t p = 0; p < actualParamCount; p++)
|
||||
{
|
||||
uint8_t expectedParamType = (*expectedSig)[1 + p];
|
||||
if (actualParams[p] != expectedParamType)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "HookSet(" << hook::log::FUNC_PARAM_INVALID
|
||||
<< "): Import function '" << extName << "' parameter "
|
||||
<< p << " has incorrect type. Expected "
|
||||
<< static_cast<int>(expectedParamType) << ", found "
|
||||
<< static_cast<int>(actualParams[p]);
|
||||
return Unexpected(
|
||||
"Import function '" + extName +
|
||||
"' has incorrect parameter types");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Expected<bool, std::string>
|
||||
validateWasmHostFunctionsForGas(
|
||||
std::vector<uint8_t> const& wasm,
|
||||
Rules const& rules,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
// Create WasmEdge Loader
|
||||
WasmEdge_LoaderContext* loader = WasmEdge_LoaderCreate(NULL);
|
||||
if (!loader)
|
||||
{
|
||||
return Unexpected("Failed to create WasmEdge Loader");
|
||||
}
|
||||
|
||||
// Parse WASM binary
|
||||
WasmEdge_ASTModuleContext* astModule = NULL;
|
||||
WasmEdge_Result res = WasmEdge_LoaderParseFromBuffer(
|
||||
loader, &astModule, wasm.data(), wasm.size());
|
||||
|
||||
if (!WasmEdge_ResultOK(res))
|
||||
{
|
||||
WasmEdge_LoaderDelete(loader);
|
||||
const char* msg = WasmEdge_ResultGetMessage(res);
|
||||
return Unexpected(
|
||||
std::string("Failed to parse WASM: ") +
|
||||
(msg ? msg : "unknown error"));
|
||||
}
|
||||
|
||||
bool foundCbak = false;
|
||||
|
||||
//
|
||||
// check export section
|
||||
//
|
||||
auto resultExport = validateExportSection(astModule, j);
|
||||
if (!resultExport)
|
||||
{
|
||||
WasmEdge_ASTModuleDelete(astModule);
|
||||
WasmEdge_LoaderDelete(loader);
|
||||
return Unexpected(resultExport.error());
|
||||
}
|
||||
foundCbak = resultExport.value();
|
||||
|
||||
//
|
||||
// check import section
|
||||
//
|
||||
if (auto result = validateImportSection(astModule, rules, j); !result)
|
||||
{
|
||||
WasmEdge_ASTModuleDelete(astModule);
|
||||
WasmEdge_LoaderDelete(loader);
|
||||
return Unexpected(result.error());
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
WasmEdge_ASTModuleDelete(astModule);
|
||||
WasmEdge_LoaderDelete(loader);
|
||||
|
||||
return foundCbak;
|
||||
}
|
||||
|
||||
} // namespace hook
|
||||
@@ -1197,7 +1197,9 @@ hook::apply(
|
||||
bool isStrong,
|
||||
uint32_t wasmParam,
|
||||
uint8_t hookChainPosition,
|
||||
std::shared_ptr<STObject const> const& provisionalMeta)
|
||||
std::shared_ptr<STObject const> const& provisionalMeta,
|
||||
uint16_t hookApiVersion,
|
||||
uint32_t hookGas)
|
||||
{
|
||||
HookContext hookCtx = {
|
||||
.applyCtx = applyCtx,
|
||||
@@ -1228,7 +1230,9 @@ hook::apply(
|
||||
.wasmParam = wasmParam,
|
||||
.hookChainPosition = hookChainPosition,
|
||||
.foreignStateSetDisabled = false,
|
||||
.provisionalMeta = provisionalMeta},
|
||||
.provisionalMeta = provisionalMeta,
|
||||
.hookApiVersion = hookApiVersion,
|
||||
.hookGas = hookGas},
|
||||
.emitFailure = isCallback && wasmParam & 1
|
||||
? std::optional<ripple::STObject>(
|
||||
(*(applyCtx.view().peek(keylet::emittedTxn(
|
||||
@@ -1243,10 +1247,14 @@ hook::apply(
|
||||
executor.executeWasm(
|
||||
wasm.data(), (size_t)wasm.size(), isCallback, wasmParam, j);
|
||||
|
||||
JLOG(j.trace()) << "HookInfo[" << HC_ACC() << "]: "
|
||||
<< (hookCtx.result.exitType == hook_api::ExitType::ROLLBACK
|
||||
? "ROLLBACK"
|
||||
: "ACCEPT")
|
||||
auto const& exitType = hookCtx.result.exitType;
|
||||
auto const& exitTypeStr = exitType == ExitType::ROLLBACK ? "ROLLBACK"
|
||||
: exitType == ExitType::ACCEPT ? "ACCEPT"
|
||||
: exitType == ExitType::GAS_INSUFFICIENT ? "GAS_INSUFFICIENT"
|
||||
: exitType == ExitType::WASM_ERROR ? "WASM_ERROR"
|
||||
: "UNSET";
|
||||
|
||||
JLOG(j.trace()) << "HookInfo[" << HC_ACC() << "]: " << exitTypeStr
|
||||
<< " RS: '" << hookCtx.result.exitReason.c_str()
|
||||
<< "' RC: " << hookCtx.result.exitCode;
|
||||
|
||||
@@ -1735,6 +1743,9 @@ hook::finalizeHookResult(
|
||||
ripple::Slice{
|
||||
hookResult.exitReason.data(), hookResult.exitReason.size()});
|
||||
meta.setFieldU64(sfHookInstructionCount, hookResult.instructionCount);
|
||||
if (hookResult.hookApiVersion == 1)
|
||||
meta.setFieldU32(sfHookInstructionCost, hookResult.instructionCost);
|
||||
|
||||
meta.setFieldU16(
|
||||
sfHookEmitCount,
|
||||
emission_txnid.size()); // this will never wrap, hard limit
|
||||
|
||||
@@ -244,6 +244,12 @@ Ledger::Ledger(
|
||||
sle->at(sfReserveIncrement) = *f;
|
||||
sle->at(sfReferenceFeeUnits) = Config::FEE_UNITS_DEPRECATED;
|
||||
}
|
||||
if (std::find(amendments.begin(), amendments.end(), featureHookGas) !=
|
||||
amendments.end())
|
||||
{
|
||||
if (auto const f = config.FEES.hook_gas_price)
|
||||
sle->at(sfHookGasPrice) = f;
|
||||
}
|
||||
rawInsert(sle);
|
||||
}
|
||||
|
||||
@@ -695,12 +701,22 @@ Ledger::setup()
|
||||
assign(fees_.increment, reserveIncrementXRP);
|
||||
newFees = baseFeeXRP || reserveBaseXRP || reserveIncrementXRP;
|
||||
}
|
||||
// Read GasPrice
|
||||
bool hookFees = false;
|
||||
if (auto const gp = sle->at(~sfHookGasPrice))
|
||||
{
|
||||
fees_.hookGasPrice = *gp;
|
||||
hookFees = true;
|
||||
}
|
||||
if (oldFees && newFees)
|
||||
// Should be all of one or the other, but not both
|
||||
ret = false;
|
||||
if (!rules_.enabled(featureXRPFees) && newFees)
|
||||
// Can't populate the new fees before the amendment is enabled
|
||||
ret = false;
|
||||
if (!rules_.enabled(featureHookGas) && hookFees)
|
||||
// Can't populate hook gas price before the amendment is enabled
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
catch (SHAMapMissingNode const&)
|
||||
@@ -720,7 +736,8 @@ void
|
||||
Ledger::defaultFees(Config const& config)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
fees_.base == 0 && fees_.reserve == 0 && fees_.increment == 0,
|
||||
fees_.base == 0 && fees_.reserve == 0 && fees_.increment == 0 &&
|
||||
fees_.hookGasPrice == 0,
|
||||
"ripple::Ledger::defaultFees : zero fees");
|
||||
if (fees_.base == 0)
|
||||
fees_.base = config.FEES.reference_fee;
|
||||
@@ -728,6 +745,8 @@ Ledger::defaultFees(Config const& config)
|
||||
fees_.reserve = config.FEES.account_reserve;
|
||||
if (fees_.increment == 0)
|
||||
fees_.increment = config.FEES.owner_reserve;
|
||||
if (fees_.hookGasPrice == 0)
|
||||
fees_.hookGasPrice = config.FEES.hook_gas_price;
|
||||
}
|
||||
|
||||
std::shared_ptr<SLE>
|
||||
|
||||
@@ -29,10 +29,10 @@ namespace ripple {
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename value_type>
|
||||
class VotableValue
|
||||
{
|
||||
private:
|
||||
using value_type = XRPAmount;
|
||||
value_type const current_; // The current setting
|
||||
value_type const target_; // The setting we want
|
||||
std::map<value_type, int> voteMap_;
|
||||
@@ -67,8 +67,9 @@ public:
|
||||
getVotes() const;
|
||||
};
|
||||
|
||||
auto
|
||||
VotableValue::getVotes() const -> std::pair<value_type, bool>
|
||||
template <typename value_type>
|
||||
std::pair<value_type, bool>
|
||||
VotableValue<value_type>::getVotes() const
|
||||
{
|
||||
value_type ourVote = current_;
|
||||
int weight = 0;
|
||||
@@ -191,6 +192,18 @@ FeeVoteImpl::doValidation(
|
||||
"reserve increment",
|
||||
sfReserveIncrement);
|
||||
}
|
||||
|
||||
// GasPrice voting (always uses UINT64, independent of featureXRPFees)
|
||||
if (rules.enabled(featureHookGas))
|
||||
{
|
||||
if (lastFees.hookGasPrice != target_.hook_gas_price)
|
||||
{
|
||||
JLOG(journal_.info())
|
||||
<< "Voting for hook gas price of " << target_.hook_gas_price;
|
||||
if (auto const f = target_.hook_gas_price)
|
||||
v[sfHookGasPrice] = f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -213,11 +226,14 @@ FeeVoteImpl::doVoting(
|
||||
detail::VotableValue incReserveVote(
|
||||
lastClosedLedger->fees().increment, target_.owner_reserve);
|
||||
|
||||
detail::VotableValue hookGasPriceVote(
|
||||
lastClosedLedger->fees().hookGasPrice, target_.hook_gas_price);
|
||||
|
||||
auto const& rules = lastClosedLedger->rules();
|
||||
if (rules.enabled(featureXRPFees))
|
||||
{
|
||||
auto doVote = [](std::shared_ptr<STValidation> const& val,
|
||||
detail::VotableValue& value,
|
||||
detail::VotableValue<XRPAmount>& value,
|
||||
SF_AMOUNT const& xrpField) {
|
||||
if (auto const field = ~val->at(~xrpField);
|
||||
field && field->native())
|
||||
@@ -246,7 +262,7 @@ FeeVoteImpl::doVoting(
|
||||
else
|
||||
{
|
||||
auto doVote = [](std::shared_ptr<STValidation> const& val,
|
||||
detail::VotableValue& value,
|
||||
detail::VotableValue<XRPAmount>& value,
|
||||
auto const& valueField) {
|
||||
if (auto const field = val->at(~valueField))
|
||||
{
|
||||
@@ -278,6 +294,33 @@ FeeVoteImpl::doVoting(
|
||||
}
|
||||
}
|
||||
|
||||
// GasPrice voting (UINT64 field, independent of featureXRPFees)
|
||||
if (rules.enabled(featureHookGas))
|
||||
{
|
||||
auto doVote = [](std::shared_ptr<STValidation> const& val,
|
||||
detail::VotableValue<std::uint64_t>& value,
|
||||
SF_UINT64 const& xrpField) {
|
||||
if (auto const field = val->at(~xrpField))
|
||||
{
|
||||
auto const vote = *field;
|
||||
if (vote <= std::numeric_limits<std::uint64_t>::max())
|
||||
value.addVote(vote);
|
||||
else
|
||||
value.noVote();
|
||||
}
|
||||
else
|
||||
{
|
||||
value.noVote();
|
||||
}
|
||||
};
|
||||
for (auto const& val : set)
|
||||
{
|
||||
if (!val->isTrusted())
|
||||
continue;
|
||||
doVote(val, hookGasPriceVote, sfHookGasPrice);
|
||||
}
|
||||
}
|
||||
|
||||
// choose our positions
|
||||
// TODO: Use structured binding once LLVM 16 is the minimum supported
|
||||
// version. See also: https://github.com/llvm/llvm-project/issues/48582
|
||||
@@ -285,11 +328,13 @@ FeeVoteImpl::doVoting(
|
||||
auto const baseFee = baseFeeVote.getVotes();
|
||||
auto const baseReserve = baseReserveVote.getVotes();
|
||||
auto const incReserve = incReserveVote.getVotes();
|
||||
auto const hookGasPrice = hookGasPriceVote.getVotes();
|
||||
|
||||
auto const seq = lastClosedLedger->info().seq + 1;
|
||||
|
||||
// add transactions to our position
|
||||
if (baseFee.second || baseReserve.second || incReserve.second)
|
||||
if (baseFee.second || baseReserve.second || incReserve.second ||
|
||||
(rules.enabled(featureHookGas) && hookGasPrice.second))
|
||||
{
|
||||
JLOG(journal_.warn())
|
||||
<< "We are voting for a fee change: " << baseFee.first << "/"
|
||||
@@ -317,6 +362,10 @@ FeeVoteImpl::doVoting(
|
||||
incReserveVote.current());
|
||||
obj[sfReferenceFeeUnits] = Config::FEE_UNITS_DEPRECATED;
|
||||
}
|
||||
if (rules.enabled(featureHookGas))
|
||||
{
|
||||
obj[sfHookGasPrice] = hookGasPrice.first;
|
||||
}
|
||||
});
|
||||
|
||||
uint256 txID = feeTx.getTransactionID();
|
||||
|
||||
@@ -152,6 +152,18 @@ Change::preclaim(PreclaimContext const& ctx)
|
||||
ctx.tx.isFieldPresent(sfReserveIncrementDrops))
|
||||
return temDISABLED;
|
||||
}
|
||||
// sfHookGasPrice: required when featureHookGas is enabled,
|
||||
// forbidden when disabled
|
||||
if (ctx.view.rules().enabled(featureHookGas))
|
||||
{
|
||||
if (!ctx.tx.isFieldPresent(sfHookGasPrice))
|
||||
return temMALFORMED;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ctx.tx.isFieldPresent(sfHookGasPrice))
|
||||
return temDISABLED;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
case ttAMENDMENT:
|
||||
case ttUNL_MODIFY:
|
||||
@@ -639,7 +651,7 @@ Change::activateXahauGenesis()
|
||||
|
||||
std::optional<std::string> result2 =
|
||||
hook::HookExecutor::validateWasm(
|
||||
wasmBytes.data(), (size_t)wasmBytes.size());
|
||||
wasmBytes.data(), (size_t)wasmBytes.size(), 0);
|
||||
|
||||
if (result2)
|
||||
{
|
||||
@@ -1022,6 +1034,8 @@ Change::applyFee()
|
||||
set(feeObject, ctx_.tx, sfBaseFeeDrops);
|
||||
set(feeObject, ctx_.tx, sfReserveBaseDrops);
|
||||
set(feeObject, ctx_.tx, sfReserveIncrementDrops);
|
||||
if (ctx_.tx.isFieldPresent(sfHookGasPrice))
|
||||
set(feeObject, ctx_.tx, sfHookGasPrice);
|
||||
// Ensure the old fields are removed
|
||||
feeObject->makeFieldAbsent(sfBaseFee);
|
||||
feeObject->makeFieldAbsent(sfReferenceFeeUnits);
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <xrpld/ledger/ApplyView.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/hook/Enum.h>
|
||||
#include <xrpl/hook/GasValidator.h>
|
||||
#include <xrpl/hook/Guard.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -230,7 +231,9 @@ SetHook::inferOperation(STObject const& hookSetObj)
|
||||
hookSetObj.isFieldPresent(sfHookOnIncoming))) &&
|
||||
!hookSetObj.isFieldPresent(sfHookCanEmit) &&
|
||||
!hookSetObj.isFieldPresent(sfHookApiVersion) &&
|
||||
!hookSetObj.isFieldPresent(sfFlags))
|
||||
!hookSetObj.isFieldPresent(sfFlags) &&
|
||||
!hookSetObj.isFieldPresent(sfHookCallbackGas) &&
|
||||
!hookSetObj.isFieldPresent(sfHookWeakGas))
|
||||
return hsoNOOP;
|
||||
|
||||
uint32_t flags = hookSetObj.isFieldPresent(sfFlags)
|
||||
@@ -267,6 +270,8 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
hookSetObj.isFieldPresent(sfHookOnIncoming) ||
|
||||
hookSetObj.isFieldPresent(sfHookCanEmit) ||
|
||||
hookSetObj.isFieldPresent(sfHookApiVersion) ||
|
||||
hookSetObj.isFieldPresent(sfHookCallbackGas) ||
|
||||
hookSetObj.isFieldPresent(sfHookWeakGas) ||
|
||||
!hookSetObj.isFieldPresent(sfFlags) ||
|
||||
!hookSetObj.isFieldPresent(sfHookNamespace))
|
||||
{
|
||||
@@ -300,6 +305,8 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
hookSetObj.isFieldPresent(sfHookCanEmit) ||
|
||||
hookSetObj.isFieldPresent(sfHookApiVersion) ||
|
||||
hookSetObj.isFieldPresent(sfHookNamespace) ||
|
||||
hookSetObj.isFieldPresent(sfHookCallbackGas) ||
|
||||
hookSetObj.isFieldPresent(sfHookWeakGas) ||
|
||||
!hookSetObj.isFieldPresent(sfFlags))
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
@@ -402,6 +409,8 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
// 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
|
||||
// hookweakgas may be present if the user so chooses
|
||||
// hookcallbackgas may be present if the user so chooses
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -441,7 +450,8 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
}
|
||||
|
||||
auto version = hookSetObj.getFieldU16(sfHookApiVersion);
|
||||
if (version != 0)
|
||||
|
||||
if (!ctx.rules.enabled(featureHookGas) && version != 0)
|
||||
{
|
||||
// we currently only accept api version 0
|
||||
JLOG(ctx.j.trace())
|
||||
@@ -451,6 +461,92 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
return false;
|
||||
}
|
||||
|
||||
// allow only version=0 and version=1
|
||||
if (version != 0 && version != 1)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << ::hook::log::API_INVALID << ")["
|
||||
<< HS_ACC()
|
||||
<< "]: Malformed transaction: SetHook "
|
||||
"sfHook->sfHookApiVersion invalid. (Must be 0 or 1).";
|
||||
return false;
|
||||
}
|
||||
|
||||
// validate sfHookCallbackGas
|
||||
auto hasHookCallbackGas = false;
|
||||
if (hookSetObj.isFieldPresent(sfHookCallbackGas))
|
||||
{
|
||||
if (version != 1)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::HOOK_INVALID_FIELD << ")["
|
||||
<< HS_ACC()
|
||||
<< "]: Malformed transaction: SetHook "
|
||||
"sfHookCallbackGas is "
|
||||
"not allowed in version "
|
||||
<< version << ".";
|
||||
return false;
|
||||
}
|
||||
if (hookSetObj.getFieldU32(sfHookCallbackGas) == 0)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::HOOK_INVALID_FIELD << ")["
|
||||
<< HS_ACC()
|
||||
<< "]: Malformed transaction: SetHook "
|
||||
"sfHookCallbackGas must be greater than 0.";
|
||||
return false;
|
||||
}
|
||||
hasHookCallbackGas = true;
|
||||
}
|
||||
|
||||
// validate sfHookWeakGas
|
||||
if (hookSetObj.isFieldPresent(sfHookWeakGas))
|
||||
{
|
||||
if (version != 1)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::HOOK_INVALID_FIELD << ")["
|
||||
<< HS_ACC()
|
||||
<< "]: Malformed transaction: SetHook sfHookWeakGas is "
|
||||
"not allowed in version "
|
||||
<< version << ".";
|
||||
return false;
|
||||
}
|
||||
if (hookSetObj.getFieldU32(sfHookWeakGas) == 0)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::HOOK_INVALID_FIELD << ")["
|
||||
<< HS_ACC()
|
||||
<< "]: Malformed transaction: SetHook sfHookWeakGas "
|
||||
"must be greater than 0.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(flags & hsfCOLLECT))
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::HOOK_INVALID_FIELD << ")["
|
||||
<< HS_ACC()
|
||||
<< "]: Malformed transaction: SetHook with "
|
||||
"sfHookWeakGas must be used with hsfCOLLECT "
|
||||
"flag.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (version == 1)
|
||||
{
|
||||
if (flags & hsfCOLLECT)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::HOOK_INVALID_FIELD << ")["
|
||||
<< HS_ACC()
|
||||
<< "]: Malformed transaction: SetHook with "
|
||||
"hsfCOLLECT flag requires sfHookWeakGas to be "
|
||||
"present.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// validate sfHookOn
|
||||
if (!hookSetObj.isFieldPresent(sfHookOn))
|
||||
{
|
||||
@@ -516,6 +612,7 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
return {};
|
||||
|
||||
Blob hook = hookSetObj.getFieldVL(sfCreateCode);
|
||||
auto version = hookSetObj.getFieldU16(sfHookApiVersion);
|
||||
|
||||
// RH NOTE: validateGuards has a generic non-rippled specific
|
||||
// interface so it can be used in other projects (i.e. tooling).
|
||||
@@ -533,46 +630,97 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
hsacc = ss.str();
|
||||
}
|
||||
|
||||
auto result = validateGuards(
|
||||
hook, // wasm to verify
|
||||
logger,
|
||||
hsacc,
|
||||
hook_api::getImportWhitelist(ctx.rules),
|
||||
hook_api::getGuardRulesVersion(ctx.rules));
|
||||
uint64_t maxInstrCountHook = 0;
|
||||
uint64_t maxInstrCountCbak = 0;
|
||||
|
||||
if (ctx.j.trace())
|
||||
if (version == 0) // Guard type
|
||||
{
|
||||
// clunky but to get the stream to accept the output
|
||||
// correctly we will split on new line and feed each line
|
||||
// one by one into the trace stream beast::Journal should be
|
||||
// updated to inherit from basic_ostream<char> then this
|
||||
// wouldn't be necessary.
|
||||
auto result = validateGuards(
|
||||
hook, // wasm to verify
|
||||
logger,
|
||||
hsacc,
|
||||
hook_api::getImportWhitelist(ctx.rules),
|
||||
hook_api::getGuardRulesVersion(ctx.rules));
|
||||
|
||||
// is this a needless copy or does the compiler do copy
|
||||
// elision here?
|
||||
std::string s = loggerStream.str();
|
||||
|
||||
char* data = s.data();
|
||||
size_t len = s.size();
|
||||
|
||||
char* last = data;
|
||||
size_t i = 0;
|
||||
for (; i < len; ++i)
|
||||
if (ctx.j.trace())
|
||||
{
|
||||
if (data[i] == '\n')
|
||||
// clunky but to get the stream to accept the output
|
||||
// correctly we will split on new line and feed each
|
||||
// line one by one into the trace stream beast::Journal
|
||||
// should be updated to inherit from basic_ostream<char>
|
||||
// then this wouldn't be necessary.
|
||||
|
||||
// is this a needless copy or does the compiler do copy
|
||||
// elision here?
|
||||
std::string s = loggerStream.str();
|
||||
|
||||
char* data = s.data();
|
||||
size_t len = s.size();
|
||||
|
||||
char* last = data;
|
||||
size_t i = 0;
|
||||
for (; i < len; ++i)
|
||||
{
|
||||
data[i] = '\0';
|
||||
ctx.j.trace() << last;
|
||||
last = data + i;
|
||||
if (data[i] == '\n')
|
||||
{
|
||||
data[i] = '\0';
|
||||
ctx.j.trace() << last;
|
||||
last = data + i;
|
||||
}
|
||||
}
|
||||
|
||||
if (last < data + i)
|
||||
ctx.j.trace() << last;
|
||||
}
|
||||
|
||||
if (last < data + i)
|
||||
ctx.j.trace() << last;
|
||||
}
|
||||
if (!result)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::WASM_BAD_MAGIC << ")["
|
||||
<< HS_ACC()
|
||||
<< "]: Malformed transaction: SetHook "
|
||||
"sfCreateCode failed validation.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!result)
|
||||
return false;
|
||||
std::tie(maxInstrCountHook, maxInstrCountCbak) = *result;
|
||||
}
|
||||
else if (version == 1) // Gas type
|
||||
{
|
||||
// validate with GasValidator
|
||||
auto validationResult =
|
||||
hook::validateWasmHostFunctionsForGas(
|
||||
hook, ctx.rules, ctx.j);
|
||||
|
||||
if (!validationResult)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::IMPORT_ILLEGAL << ")["
|
||||
<< HS_ACC()
|
||||
<< "]: Malformed transaction: Gas-type Hook "
|
||||
"validation failed: "
|
||||
<< validationResult.error();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const hasCbak = validationResult.value();
|
||||
if ((!hasCbak && hasHookCallbackGas) ||
|
||||
(hasCbak && !hasHookCallbackGas))
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::HOOK_INVALID_FIELD
|
||||
<< ")[" << HS_ACC()
|
||||
<< "]: Malformed transaction: Gas-type Hook must "
|
||||
"contain either sfHookCallbackGas if it "
|
||||
"contains cbak function";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Gas type: maxInstrCount is not pre-calculated (use Gas
|
||||
// limit at runtime)
|
||||
maxInstrCountHook = 0;
|
||||
maxInstrCountCbak = 0;
|
||||
}
|
||||
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::WASM_SMOKE_TEST << ")["
|
||||
@@ -582,7 +730,7 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
|
||||
std::optional<std::string> result2 =
|
||||
hook::HookExecutor::validateWasm(
|
||||
hook.data(), (size_t)hook.size());
|
||||
hook.data(), (size_t)hook.size(), version);
|
||||
|
||||
if (result2)
|
||||
{
|
||||
@@ -594,7 +742,7 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
return false;
|
||||
}
|
||||
|
||||
return *result;
|
||||
return std::make_pair(maxInstrCountHook, maxInstrCountCbak);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -783,6 +931,11 @@ SetHook::preflight(PreflightContext const& ctx)
|
||||
hookSetObj.isFieldPresent(sfHookCanEmit))
|
||||
return temDISABLED;
|
||||
|
||||
if (!ctx.rules.enabled(featureHookGas) &&
|
||||
(hookSetObj.isFieldPresent(sfHookCallbackGas) ||
|
||||
hookSetObj.isFieldPresent(sfHookWeakGas)))
|
||||
return temDISABLED;
|
||||
|
||||
for (auto const& hookSetElement : hookSetObj)
|
||||
{
|
||||
auto const& name = hookSetElement.getFName();
|
||||
@@ -792,7 +945,8 @@ SetHook::preflight(PreflightContext const& ctx)
|
||||
name != sfHookOn && name != sfHookOnOutgoing &&
|
||||
name != sfHookOnIncoming && name != sfHookGrants &&
|
||||
name != sfHookApiVersion && name != sfFlags &&
|
||||
name != sfHookCanEmit)
|
||||
name != sfHookCanEmit && name != sfHookCallbackGas &&
|
||||
name != sfHookWeakGas)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::HOOK_INVALID_FIELD << ")["
|
||||
@@ -1255,6 +1409,46 @@ struct KeyletComparator
|
||||
}
|
||||
};
|
||||
|
||||
TER
|
||||
validateGasHook(
|
||||
STObject const& hook,
|
||||
std::shared_ptr<STLedgerEntry> const& defSLE)
|
||||
{
|
||||
auto const version = defSLE->getFieldU16(sfHookApiVersion);
|
||||
if (version == 1)
|
||||
{
|
||||
// Gas Hook
|
||||
if (!defSLE->isFieldPresent(sfHookCallbackGas) &&
|
||||
hook.isFieldPresent(sfHookCallbackGas))
|
||||
return tecHOOK_INVALID;
|
||||
|
||||
auto const flags = hook.getFlags();
|
||||
auto const hasCollectFlag = flags & hsfCOLLECT;
|
||||
|
||||
if (hasCollectFlag)
|
||||
{
|
||||
// ltHook or ltHookDefinition must have sfHookWeakGas
|
||||
if (!hook.isFieldPresent(sfHookWeakGas) &&
|
||||
!defSLE->isFieldPresent(sfHookWeakGas))
|
||||
return tecHOOK_INVALID;
|
||||
}
|
||||
else
|
||||
{
|
||||
// ltHook must not have sfHookWeakGas
|
||||
if (hook.isFieldPresent(sfHookWeakGas))
|
||||
return tecHOOK_INVALID;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Guard Hook
|
||||
if (hook.isFieldPresent(sfHookCallbackGas) ||
|
||||
hook.isFieldPresent(sfHookWeakGas))
|
||||
return tecHOOK_INVALID;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
SetHook::setHook()
|
||||
{
|
||||
@@ -1348,6 +1542,14 @@ SetHook::setHook()
|
||||
std::optional<uint256> newHookCanEmit;
|
||||
std::optional<uint256> defHookCanEmit;
|
||||
|
||||
std::optional<uint32_t> oldHookWeakGas;
|
||||
std::optional<uint32_t> newHookWeakGas;
|
||||
std::optional<uint32_t> defHookWeakGas;
|
||||
|
||||
std::optional<uint32_t> oldHookCallbackGas;
|
||||
std::optional<uint32_t> newHookCallbackGas;
|
||||
std::optional<uint32_t> defHookCallbackGas;
|
||||
|
||||
// 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
|
||||
@@ -1421,6 +1623,23 @@ SetHook::setHook()
|
||||
oldHookCanEmit = oldHook->get().getFieldH256(sfHookCanEmit);
|
||||
else if (defHookCanEmit)
|
||||
oldHookCanEmit = *defHookCanEmit;
|
||||
|
||||
if (oldDefSLE && oldDefSLE->isFieldPresent(sfHookWeakGas))
|
||||
defHookWeakGas = oldDefSLE->getFieldU32(sfHookWeakGas);
|
||||
|
||||
if (oldHook && oldHook->get().isFieldPresent(sfHookWeakGas))
|
||||
oldHookWeakGas = oldHook->get().getFieldU32(sfHookWeakGas);
|
||||
else if (defHookWeakGas)
|
||||
oldHookWeakGas = *defHookWeakGas;
|
||||
|
||||
if (oldDefSLE && oldDefSLE->isFieldPresent(sfHookCallbackGas))
|
||||
defHookCallbackGas = oldDefSLE->getFieldU32(sfHookCallbackGas);
|
||||
|
||||
if (oldHook && oldHook->get().isFieldPresent(sfHookCallbackGas))
|
||||
oldHookCallbackGas =
|
||||
oldHook->get().getFieldU32(sfHookCallbackGas);
|
||||
else if (defHookCallbackGas)
|
||||
oldHookCallbackGas = *defHookCallbackGas;
|
||||
}
|
||||
|
||||
// in preparation for three way merge populate fields if they are
|
||||
@@ -1453,6 +1672,13 @@ SetHook::setHook()
|
||||
newNamespace = hookSetObj->get().getFieldH256(sfHookNamespace);
|
||||
newDirKeylet = keylet::hookStateDir(account_, *newNamespace);
|
||||
}
|
||||
|
||||
if (hookSetObj->get().isFieldPresent(sfHookCallbackGas))
|
||||
newHookCallbackGas =
|
||||
hookSetObj->get().getFieldU32(sfHookCallbackGas);
|
||||
|
||||
if (hookSetObj->get().isFieldPresent(sfHookWeakGas))
|
||||
newHookWeakGas = hookSetObj->get().getFieldU32(sfHookWeakGas);
|
||||
}
|
||||
|
||||
// users may destroy a namespace in any operation except NOOP and
|
||||
@@ -1638,6 +1864,40 @@ SetHook::setHook()
|
||||
newHook.setFieldH256(sfHookCanEmit, *newHookCanEmit);
|
||||
}
|
||||
|
||||
auto const defVersion =
|
||||
oldDefSLE->getFieldU16(sfHookApiVersion);
|
||||
|
||||
if (defVersion == 0 && (newHookCallbackGas || newHookWeakGas))
|
||||
return tecHOOK_INVALID;
|
||||
|
||||
if (!defHookCallbackGas.has_value() && newHookCallbackGas)
|
||||
return tecHOOK_INVALID;
|
||||
|
||||
if (newHookWeakGas)
|
||||
{
|
||||
if (defHookWeakGas.has_value() &&
|
||||
*defHookWeakGas == *newHookWeakGas)
|
||||
{
|
||||
if (newHook.isFieldPresent(sfHookWeakGas))
|
||||
newHook.makeFieldAbsent(sfHookWeakGas);
|
||||
}
|
||||
else
|
||||
newHook.setFieldU32(sfHookWeakGas, *newHookWeakGas);
|
||||
}
|
||||
|
||||
if (newHookCallbackGas)
|
||||
{
|
||||
if (defHookCallbackGas.has_value() &&
|
||||
*defHookCallbackGas == *newHookCallbackGas)
|
||||
{
|
||||
if (newHook.isFieldPresent(sfHookCallbackGas))
|
||||
newHook.makeFieldAbsent(sfHookCallbackGas);
|
||||
}
|
||||
else
|
||||
newHook.setFieldU32(
|
||||
sfHookCallbackGas, *newHookCallbackGas);
|
||||
}
|
||||
|
||||
// parameters
|
||||
if (hookSetObj->get().isFieldPresent(sfHookParameters) &&
|
||||
hookSetObj->get().getFieldArray(sfHookParameters).empty())
|
||||
@@ -1682,6 +1942,10 @@ SetHook::setHook()
|
||||
if (flags)
|
||||
newHook.setFieldU32(sfFlags, *flags);
|
||||
|
||||
TER result = validateGasHook(newHook, oldDefSLE);
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
|
||||
newHooks.push_back(std::move(newHook));
|
||||
continue;
|
||||
}
|
||||
@@ -1816,10 +2080,24 @@ SetHook::setHook()
|
||||
newHookDef->setFieldH256(
|
||||
sfHookSetTxnID, ctx.tx.getTransactionID());
|
||||
newHookDef->setFieldU64(sfReferenceCount, 1);
|
||||
newHookDef->setFieldAmount(
|
||||
sfFee,
|
||||
XRPAmount{
|
||||
hook::computeExecutionFee(maxInstrCountHook)});
|
||||
|
||||
// Set HookCallbackGas if present in hookSetObj
|
||||
if (hookSetObj->get().isFieldPresent(sfHookCallbackGas))
|
||||
newHookDef->setFieldU32(
|
||||
sfHookCallbackGas,
|
||||
hookSetObj->get().getFieldU32(sfHookCallbackGas));
|
||||
|
||||
// Set HookWeakGas if present in hookSetObj
|
||||
if (hookSetObj->get().isFieldPresent(sfHookWeakGas))
|
||||
newHookDef->setFieldU32(
|
||||
sfHookWeakGas,
|
||||
hookSetObj->get().getFieldU32(sfHookWeakGas));
|
||||
|
||||
if (hookSetObj->get().getFieldU16(sfHookApiVersion) != 1)
|
||||
newHookDef->setFieldAmount(
|
||||
sfFee,
|
||||
XRPAmount{
|
||||
hook::computeExecutionFee(maxInstrCountHook)});
|
||||
if (maxInstrCountCbak > 0)
|
||||
newHookDef->setFieldAmount(
|
||||
sfHookCallbackFee,
|
||||
@@ -1841,6 +2119,38 @@ SetHook::setHook()
|
||||
|
||||
slesToInsert.emplace(keylet, newHookDef);
|
||||
newHook.setFieldH256(sfHookHash, *createHookHash);
|
||||
|
||||
// Set HookCallbackGas in Hook object only if different from
|
||||
// Definition
|
||||
if (hookSetObj->get().isFieldPresent(sfHookCallbackGas))
|
||||
{
|
||||
uint32_t objGas =
|
||||
hookSetObj->get().getFieldU32(sfHookCallbackGas);
|
||||
if (!newHookDef->isFieldPresent(sfHookCallbackGas) ||
|
||||
newHookDef->getFieldU32(sfHookCallbackGas) !=
|
||||
objGas)
|
||||
{
|
||||
newHook.setFieldU32(sfHookCallbackGas, objGas);
|
||||
}
|
||||
}
|
||||
|
||||
// Set HookWeakGas in Hook object only if different from
|
||||
// Definition
|
||||
if (hookSetObj->get().isFieldPresent(sfHookWeakGas))
|
||||
{
|
||||
uint32_t objGas =
|
||||
hookSetObj->get().getFieldU32(sfHookWeakGas);
|
||||
if (!newHookDef->isFieldPresent(sfHookWeakGas) ||
|
||||
newHookDef->getFieldU32(sfHookWeakGas) != objGas)
|
||||
{
|
||||
newHook.setFieldU32(sfHookWeakGas, objGas);
|
||||
}
|
||||
}
|
||||
|
||||
TER result = validateGasHook(newHook, newHookDef);
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
|
||||
newHooks.push_back(std::move(newHook));
|
||||
continue;
|
||||
}
|
||||
@@ -1912,6 +2222,14 @@ SetHook::setHook()
|
||||
defHookOnOutgoing =
|
||||
newDefSLE->getFieldH256(sfHookOnOutgoing);
|
||||
|
||||
// refresh gas fields from the new definition
|
||||
if (newDefSLE->isFieldPresent(sfHookCallbackGas))
|
||||
defHookCallbackGas =
|
||||
newDefSLE->getFieldU32(sfHookCallbackGas);
|
||||
|
||||
if (newDefSLE->isFieldPresent(sfHookWeakGas))
|
||||
defHookWeakGas = newDefSLE->getFieldU32(sfHookWeakGas);
|
||||
|
||||
// set the hookon field if it differs from definition
|
||||
if (newHookOn)
|
||||
{
|
||||
@@ -1950,6 +2268,28 @@ SetHook::setHook()
|
||||
*defHookCanEmit == *newHookCanEmit))
|
||||
newHook.setFieldH256(sfHookCanEmit, *newHookCanEmit);
|
||||
|
||||
auto const defVersion =
|
||||
newDefSLE->getFieldU16(sfHookApiVersion);
|
||||
|
||||
if (defVersion == 0 && (newHookCallbackGas || newHookWeakGas))
|
||||
return tecHOOK_INVALID;
|
||||
|
||||
if (!defHookCallbackGas.has_value() && newHookCallbackGas)
|
||||
return tecHOOK_INVALID;
|
||||
|
||||
if (newHookCallbackGas &&
|
||||
!(defHookCallbackGas.has_value() &&
|
||||
*defHookCallbackGas == *newHookCallbackGas))
|
||||
newHook.setFieldU32(sfHookCallbackGas, *newHookCallbackGas);
|
||||
|
||||
if ((!flags || !(*flags & hsfCOLLECT)) && newHookWeakGas)
|
||||
return tecHOOK_INVALID;
|
||||
|
||||
if (newHookWeakGas &&
|
||||
!(defHookWeakGas.has_value() &&
|
||||
*defHookWeakGas == *newHookWeakGas))
|
||||
newHook.setFieldU32(sfHookWeakGas, *newHookWeakGas);
|
||||
|
||||
// parameters
|
||||
TER result = updateHookParameters(
|
||||
ctx,
|
||||
@@ -1973,6 +2313,10 @@ SetHook::setHook()
|
||||
if (flags)
|
||||
newHook.setFieldU32(sfFlags, newFlags);
|
||||
|
||||
result = validateGasHook(newHook, newDefSLE);
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
|
||||
newHooks.push_back(std::move(newHook));
|
||||
|
||||
slesToUpdate.emplace(*newDefKeylet, newDefSLE);
|
||||
@@ -1990,6 +2334,8 @@ SetHook::setHook()
|
||||
}
|
||||
}
|
||||
|
||||
JLOG(ctx.j.warn()) << "HookSet: setHook after for loops";
|
||||
|
||||
int reserveDelta = 0;
|
||||
{
|
||||
// compute owner counts before modifying anything on ledger
|
||||
@@ -1999,7 +2345,8 @@ SetHook::setHook()
|
||||
// sfParameters: 1 reserve PER entry
|
||||
// sfGrants are: 1 reserve PER entry
|
||||
// sfHookHash, sfHookNamespace, sfHookOn, sfHookOnOutgoing,
|
||||
// sfHookOnIncoming, sfHookCanEmit sfHookApiVersion, sfFlags: free
|
||||
// sfHookOnIncoming, sfHookCanEmit, sfHookApiVersion, sfHookCallbackGas,
|
||||
// sfHookWeakGas, sfFlags: free
|
||||
|
||||
// sfHookDefinition is not reserved because it is an unowned object,
|
||||
// rather the uploader is billed via fee according to the following:
|
||||
@@ -2146,6 +2493,7 @@ SetHook::setHook()
|
||||
view().update(accountSLE);
|
||||
}
|
||||
|
||||
JLOG(ctx.j.warn()) << "HookSet: setHook end";
|
||||
return nsDeleteResult;
|
||||
} // namespace ripple
|
||||
|
||||
|
||||
@@ -101,6 +101,11 @@ preflight1(PreflightContext const& ctx)
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfHookGas) && !ctx.rules.enabled(featureHookGas))
|
||||
{
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
auto const ret = preflight0(ctx);
|
||||
if (!isTesSuccess(ret))
|
||||
return ret;
|
||||
@@ -234,6 +239,21 @@ Transactor::Transactor(ApplyContext& ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static constexpr uint64_t GAS_PRICE_MICRO_DROPS = 1'000'000;
|
||||
|
||||
XRPAmount
|
||||
Transactor::calculateHookGas(uint32_t gasCount, Fees const& fees)
|
||||
{
|
||||
uint64_t const gasPrice =
|
||||
fees.hookGasPrice > 0 ? fees.hookGasPrice : GAS_PRICE_MICRO_DROPS;
|
||||
uint64_t gasCountU64 = static_cast<uint64_t>(gasCount);
|
||||
if (gasPrice > 0 &&
|
||||
gasCountU64 > std::numeric_limits<uint64_t>::max() / gasPrice)
|
||||
return XRPAmount{INITIAL_XRP.drops()};
|
||||
return XRPAmount{static_cast<XRPAmount::value_type>(
|
||||
(gasCountU64 * gasPrice) / GAS_PRICE_MICRO_DROPS)};
|
||||
}
|
||||
|
||||
// RH NOTE: this only computes one chain at a time, so if there is a receiving
|
||||
// side to a txn then it must seperately be computed by a second call here
|
||||
XRPAmount
|
||||
@@ -249,6 +269,7 @@ Transactor::calculateHookChainFee(
|
||||
return XRPAmount{0};
|
||||
|
||||
XRPAmount fee{0};
|
||||
uint32_t gasTypeHookCount = 0; // Gas type hook counter
|
||||
|
||||
auto const& hooks = hookSLE->getFieldArray(sfHooks);
|
||||
|
||||
@@ -283,18 +304,57 @@ Transactor::calculateHookChainFee(
|
||||
if (hook::canHook(tx.getTxnType(), hookOn) &&
|
||||
(!collectCallsOnly || (flags & hook::hsfCOLLECT)))
|
||||
{
|
||||
XRPAmount const toAdd{hookDef->getFieldAmount(sfFee).xrp().drops()};
|
||||
// get HookApiVersion
|
||||
uint16_t apiVersion = hookDef->getFieldU16(sfHookApiVersion);
|
||||
|
||||
// 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)
|
||||
fee = XRPAmount{INITIAL_XRP.drops()};
|
||||
else
|
||||
fee += toAdd;
|
||||
if (apiVersion == 0) // Guard type
|
||||
{
|
||||
// existing logic: read HookDefinition's sfFee
|
||||
XRPAmount const toAdd{
|
||||
hookDef->getFieldAmount(sfFee).xrp().drops()};
|
||||
|
||||
// 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)
|
||||
fee = XRPAmount{INITIAL_XRP.drops()};
|
||||
else
|
||||
fee += toAdd;
|
||||
}
|
||||
else if (apiVersion == 1) // Gas type
|
||||
{
|
||||
if (!collectCallsOnly)
|
||||
{
|
||||
// Gas type: only count
|
||||
gasTypeHookCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const weakFee = hookObj.isFieldPresent(sfHookWeakGas)
|
||||
? hookObj.getFieldU32(sfHookWeakGas)
|
||||
: hookDef->getFieldU32(sfHookWeakGas);
|
||||
XRPAmount const toAdd =
|
||||
calculateHookGas(weakFee, view.fees());
|
||||
if (fee + toAdd < fee)
|
||||
fee = XRPAmount{INITIAL_XRP.drops()};
|
||||
else
|
||||
fee += toAdd;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Additional cost for Gas type: baseFee * 100 /Hook = 10*100 drops/Hook
|
||||
if (gasTypeHookCount > 0)
|
||||
{
|
||||
auto const baseGasFee = view.fees().base * 100;
|
||||
XRPAmount const gasTypeFee{gasTypeHookCount * baseGasFee};
|
||||
if (fee + gasTypeFee < fee)
|
||||
fee = XRPAmount{INITIAL_XRP.drops()}; // overflow
|
||||
else
|
||||
fee += gasTypeFee;
|
||||
}
|
||||
|
||||
return fee;
|
||||
}
|
||||
|
||||
@@ -357,6 +417,51 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
hookExecutionFee += toAdd;
|
||||
}
|
||||
|
||||
if (const auto hookSLE =
|
||||
view.read(keylet::hook(tx.getAccountID(sfAccount))))
|
||||
{
|
||||
const auto& hooks = hookSLE->getFieldArray(sfHooks);
|
||||
for (auto const& hookObj : hooks)
|
||||
{
|
||||
if (hookObj.isFieldPresent(sfHookHash))
|
||||
{
|
||||
if (hookObj.getFieldH256(sfHookHash) !=
|
||||
callbackHookHash)
|
||||
continue;
|
||||
|
||||
uint32_t callbackGas = 0;
|
||||
|
||||
// Priority 1: Check HookObject
|
||||
if (hookObj.isFieldPresent(sfHookCallbackGas))
|
||||
{
|
||||
callbackGas =
|
||||
hookObj.getFieldU32(sfHookCallbackGas);
|
||||
}
|
||||
// Priority 2: Check HookDefinition
|
||||
else if (
|
||||
hookDef &&
|
||||
hookDef->isFieldPresent(sfHookCallbackGas))
|
||||
{
|
||||
callbackGas =
|
||||
hookDef->getFieldU32(sfHookCallbackGas);
|
||||
}
|
||||
// Priority 3: Default to 0 (implicit)
|
||||
|
||||
if (callbackGas > 0)
|
||||
{
|
||||
XRPAmount const toAdd =
|
||||
calculateHookGas(callbackGas, view.fees());
|
||||
if (hookExecutionFee + toAdd < hookExecutionFee)
|
||||
hookExecutionFee =
|
||||
XRPAmount{INITIAL_XRP.drops()};
|
||||
else
|
||||
hookExecutionFee += toAdd;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
XRPL_ASSERT(
|
||||
emitDetails.isFieldPresent(sfEmitBurden),
|
||||
"Transactor::calculateBaseFee : emit burden not present");
|
||||
@@ -376,6 +481,11 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
if (canRollback)
|
||||
hookExecutionFee += calculateHookChainFee(
|
||||
view, tx, keylet::hook(tshAcc), false);
|
||||
|
||||
if (view.rules().enabled(featureHookGas) &&
|
||||
tx.isFieldPresent(sfHookGas))
|
||||
hookExecutionFee +=
|
||||
calculateHookGas(tx.getFieldU32(sfHookGas), view.fees());
|
||||
}
|
||||
|
||||
XRPAmount accumulator = baseFee;
|
||||
@@ -1290,7 +1400,8 @@ Transactor::executeHookChain(
|
||||
ripple::AccountID const& account,
|
||||
bool strong,
|
||||
bool isOutgoing,
|
||||
std::shared_ptr<STObject const> const& provisionalMeta)
|
||||
std::shared_ptr<STObject const> const& provisionalMeta,
|
||||
uint32_t& gasPool)
|
||||
{
|
||||
std::set<uint256> hookSkips;
|
||||
std::map<uint256, std::map<std::vector<uint8_t>, std::vector<uint8_t>>>
|
||||
@@ -1360,7 +1471,30 @@ Transactor::executeHookChain(
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
bool hasCallback = hookDef->isFieldPresent(sfHookCallbackFee);
|
||||
bool hasCallback = hookDef->isFieldPresent(sfHookCallbackFee) ||
|
||||
hookDef->isFieldPresent(sfHookCallbackGas);
|
||||
|
||||
// Extract HookApiVersion for Gas-type hooks
|
||||
uint16_t hookApiVersion = hookDef->isFieldPresent(sfHookApiVersion)
|
||||
? hookDef->getFieldU16(sfHookApiVersion)
|
||||
: 0;
|
||||
|
||||
// Prepare Gas limit for this hook execution
|
||||
uint32_t hookGas = 0;
|
||||
if (hookApiVersion == 1)
|
||||
{
|
||||
if (!strong) // WeakTSH execution
|
||||
{
|
||||
hookGas = hookObj.isFieldPresent(sfHookWeakGas)
|
||||
? hookObj.getFieldU32(sfHookWeakGas)
|
||||
: hookDef->getFieldU32(sfHookWeakGas);
|
||||
}
|
||||
else // Strong execution
|
||||
{
|
||||
// Pass remaining Gas pool to this hook
|
||||
hookGas = gasPool;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -1380,12 +1514,38 @@ Transactor::executeHookChain(
|
||||
strong,
|
||||
(strong ? 0 : 1UL), // 0 = strong, 1 = weak
|
||||
hook_no - 1,
|
||||
provisionalMeta));
|
||||
provisionalMeta,
|
||||
hookApiVersion,
|
||||
hookGas));
|
||||
|
||||
executedHookCount_++;
|
||||
|
||||
hook::HookResult& hookResult = results.back();
|
||||
|
||||
// Track Gas consumption for Gas-type hooks
|
||||
if (hookApiVersion == 1)
|
||||
{
|
||||
uint64_t consumed = hookResult.instructionCost;
|
||||
|
||||
JLOG(j_.trace()) << "HookChain: Hook consumed " << consumed
|
||||
<< " instructions. Pool before: " << gasPool;
|
||||
|
||||
if (consumed >= gasPool)
|
||||
{
|
||||
JLOG(j_.trace()) << "HookError: Gas pool exhausted. "
|
||||
<< "Hook tried to consume " << consumed
|
||||
<< " but only " << gasPool << " remained.";
|
||||
return tecHOOK_INSUFFICIENT_GAS;
|
||||
}
|
||||
|
||||
gasPool -= consumed;
|
||||
|
||||
JLOG(j_.trace()) << "HookChain: Pool after: " << gasPool;
|
||||
}
|
||||
|
||||
if (hookResult.exitType == hook_api::ExitType::GAS_INSUFFICIENT)
|
||||
return tecHOOK_INSUFFICIENT_GAS;
|
||||
|
||||
if (hookResult.exitType != hook_api::ExitType::ACCEPT)
|
||||
{
|
||||
if (results.back().exitType == hook_api::ExitType::WASM_ERROR)
|
||||
@@ -1465,7 +1625,8 @@ Transactor::doHookCallback(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hookDef->isFieldPresent(sfHookCallbackFee))
|
||||
if (!hookDef->isFieldPresent(sfHookCallbackFee) &&
|
||||
!hookDef->isFieldPresent(sfHookCallbackGas))
|
||||
{
|
||||
JLOG(j_.trace()) << "HookInfo[" << callbackAccountID
|
||||
<< "]: Callback specified by emitted txn "
|
||||
@@ -1522,6 +1683,24 @@ Transactor::doHookCallback(
|
||||
{
|
||||
hook::HookStateMap stateMap;
|
||||
|
||||
// Extract HookApiVersion for callback
|
||||
uint16_t hookApiVersion = hookDef->getFieldU16(sfHookApiVersion);
|
||||
|
||||
// Get callback gas with fallback priority:
|
||||
// 1. HookObject's HookCallbackGas
|
||||
// 2. HookDefinition's HookCallbackGas
|
||||
// 3. Transaction's HookGas (for backward compatibility)
|
||||
uint32_t hookGas = 0;
|
||||
|
||||
if (hookObj.isFieldPresent(sfHookCallbackGas))
|
||||
{
|
||||
hookGas = hookObj.getFieldU32(sfHookCallbackGas);
|
||||
}
|
||||
else if (hookDef->isFieldPresent(sfHookCallbackGas))
|
||||
{
|
||||
hookGas = hookDef->getFieldU32(sfHookCallbackGas);
|
||||
}
|
||||
|
||||
hook::HookResult callbackResult = hook::apply(
|
||||
hookDef->getFieldH256(sfHookSetTxnID),
|
||||
callbackHookHash,
|
||||
@@ -1541,7 +1720,9 @@ Transactor::doHookCallback(
|
||||
? 1UL
|
||||
: 0UL,
|
||||
hook_no - 1,
|
||||
provisionalMeta);
|
||||
provisionalMeta,
|
||||
hookApiVersion,
|
||||
hookGas);
|
||||
|
||||
executedHookCount_++;
|
||||
|
||||
@@ -1609,7 +1790,8 @@ Transactor::doTSH(
|
||||
std::vector<std::pair<AccountID, bool>> tsh,
|
||||
hook::HookStateMap& stateMap,
|
||||
std::vector<hook::HookResult>& results,
|
||||
std::shared_ptr<STObject const> const& provisionalMeta)
|
||||
std::shared_ptr<STObject const> const& provisionalMeta,
|
||||
uint32_t& gasPool)
|
||||
{
|
||||
auto& view = ctx_.view();
|
||||
|
||||
@@ -1759,7 +1941,8 @@ Transactor::doTSH(
|
||||
tshAccountID,
|
||||
strong,
|
||||
false,
|
||||
provisionalMeta);
|
||||
provisionalMeta,
|
||||
gasPool);
|
||||
|
||||
if (canRollback && (!isTesSuccess(tshResult)))
|
||||
return tshResult;
|
||||
@@ -1830,6 +2013,19 @@ Transactor::doAgainAsWeak(
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract HookApiVersion for aaw execution
|
||||
uint16_t hookApiVersion = hookDef->getFieldU16(sfHookApiVersion);
|
||||
|
||||
// Extract HookGas for Gas-type hooks
|
||||
uint32_t hookGasWeak = 0;
|
||||
if (hookApiVersion == 1)
|
||||
{
|
||||
if (hookObj.isFieldPresent(sfHookWeakGas))
|
||||
hookGasWeak = hookObj.getFieldU32(sfHookWeakGas);
|
||||
else if (hookDef->isFieldPresent(sfHookWeakGas))
|
||||
hookGasWeak = hookDef->getFieldU32(sfHookWeakGas);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
hook::HookResult aawResult = hook::apply(
|
||||
@@ -1843,12 +2039,15 @@ Transactor::doAgainAsWeak(
|
||||
stateMap,
|
||||
ctx_,
|
||||
hookAccountID,
|
||||
hookDef->isFieldPresent(sfHookCallbackFee),
|
||||
hookDef->isFieldPresent(sfHookCallbackFee) ||
|
||||
hookDef->isFieldPresent(sfHookCallbackGas),
|
||||
false,
|
||||
false,
|
||||
2UL, // param 2 = aaw
|
||||
hook_no - 1,
|
||||
provisionalMeta);
|
||||
provisionalMeta,
|
||||
hookApiVersion,
|
||||
hookGasWeak);
|
||||
|
||||
executedHookCount_++;
|
||||
|
||||
@@ -1946,6 +2145,15 @@ Transactor::operator()()
|
||||
auto const& accountID = ctx_.tx.getAccountID(sfAccount);
|
||||
std::vector<hook::HookResult> hookResults;
|
||||
|
||||
// Initialize Gas pool once for strong TSH hook chains
|
||||
uint32_t gasPool = 0;
|
||||
if (ctx_.tx.isFieldPresent(sfHookGas))
|
||||
{
|
||||
gasPool = ctx_.tx.getFieldU32(sfHookGas);
|
||||
JLOG(j_.trace()) << "HookChain: Initialized Gas pool with "
|
||||
<< gasPool << " instructions";
|
||||
}
|
||||
|
||||
auto const& hooksOriginator = view().read(keylet::hook(accountID));
|
||||
|
||||
// First check if the Sending account has any hooks that can be fired
|
||||
@@ -1958,7 +2166,8 @@ Transactor::operator()()
|
||||
accountID,
|
||||
true,
|
||||
true,
|
||||
{});
|
||||
{},
|
||||
gasPool);
|
||||
|
||||
if (isTesSuccess(result))
|
||||
{
|
||||
@@ -1967,7 +2176,7 @@ Transactor::operator()()
|
||||
// (who have the right to rollback the txn), any weak TSH will be
|
||||
// executed after doApply has been successful (callback as well)
|
||||
|
||||
result = doTSH(true, tsh, stateMap, hookResults, {});
|
||||
result = doTSH(true, tsh, stateMap, hookResults, {}, gasPool);
|
||||
}
|
||||
|
||||
// write state if all chains executed successfully
|
||||
@@ -2263,7 +2472,8 @@ Transactor::operator()()
|
||||
tsh = hook::getTransactionalStakeHolders(ctx_.tx, ctx_.view());
|
||||
}
|
||||
|
||||
doTSH(false, tsh, stateMap, weakResults, proMeta);
|
||||
uint32_t weakGasPool = 0;
|
||||
doTSH(false, tsh, stateMap, weakResults, proMeta, weakGasPool);
|
||||
|
||||
// execute any hooks that nominated for 'again as weak'
|
||||
for (auto const& [accID, hookHashes] : aawMap)
|
||||
|
||||
@@ -174,6 +174,9 @@ public:
|
||||
|
||||
// Hooks
|
||||
|
||||
static XRPAmount
|
||||
calculateHookGas(uint32_t gasCount, Fees const& fees);
|
||||
|
||||
static XRPAmount
|
||||
calculateHookChainFee(
|
||||
ReadView const& view,
|
||||
@@ -192,7 +195,8 @@ protected:
|
||||
std::vector<std::pair<AccountID, bool>> tsh,
|
||||
hook::HookStateMap& stateMap,
|
||||
std::vector<hook::HookResult>& result,
|
||||
std::shared_ptr<STObject const> const& provisionalMeta);
|
||||
std::shared_ptr<STObject const> const& provisionalMeta,
|
||||
uint32_t& gasPool);
|
||||
|
||||
// Execute a hook "Again As Weak" is a feature that allows
|
||||
// a hook that which is being executed pre-application of the otxn
|
||||
@@ -213,7 +217,8 @@ protected:
|
||||
ripple::AccountID const& account,
|
||||
bool strong,
|
||||
bool isOutgoing,
|
||||
std::shared_ptr<STObject const> const& provisionalMeta);
|
||||
std::shared_ptr<STObject const> const& provisionalMeta,
|
||||
uint32_t& gasPool);
|
||||
|
||||
void
|
||||
addWeakTSHFromBalanceChanges(detail::ApplyViewBase const& pv);
|
||||
|
||||
@@ -82,6 +82,9 @@ struct FeeSetup
|
||||
/** The per-owned item reserve requirement in drops. */
|
||||
XRPAmount owner_reserve{2 * DROPS_PER_XRP};
|
||||
|
||||
/** The gas price in micro-drops per gas unit. */
|
||||
std::uint64_t hook_gas_price{1'000'000};
|
||||
|
||||
/* (Remember to update the example cfg files when changing any of these
|
||||
* values.) */
|
||||
};
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include <boost/regex.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
@@ -1143,6 +1144,11 @@ setup_FeeVote(Section const& section)
|
||||
if (set(temp, "owner_reserve", section))
|
||||
setup.owner_reserve = temp;
|
||||
}
|
||||
{
|
||||
std::int64_t temp;
|
||||
if (set(temp, "hook_gas_price", section))
|
||||
setup.hook_gas_price = temp;
|
||||
}
|
||||
return setup;
|
||||
}
|
||||
} // namespace ripple
|
||||
|
||||
Reference in New Issue
Block a user