Compare commits

..

5 Commits

Author SHA1 Message Date
tequ
8bcebdea42 Support 'cron' type for account_objects (#624) 2025-11-06 15:19:15 +10:00
Alloy Networks
4cc63c028a Change validators.txt to validators-xahau.txt (#619) 2025-11-01 15:26:56 +10:00
tequ
9ed20a4f1c Refactor: SetCron to CronSet (#609) 2025-10-27 14:38:40 +10:00
tequ
89ffc1969b Add Previous fields to ltCron (#611) 2025-10-27 14:36:57 +10:00
tequ
79fdafe638 Support Cron in util_keylet Hook API (#612) 2025-10-27 14:35:01 +10:00
19 changed files with 3494 additions and 5181 deletions

View File

@@ -458,6 +458,7 @@ target_sources (rippled PRIVATE
src/ripple/app/tx/impl/CreateOffer.cpp
src/ripple/app/tx/impl/CreateTicket.cpp
src/ripple/app/tx/impl/Cron.cpp
src/ripple/app/tx/impl/CronSet.cpp
src/ripple/app/tx/impl/DeleteAccount.cpp
src/ripple/app/tx/impl/DepositPreauth.cpp
src/ripple/app/tx/impl/Escrow.cpp
@@ -475,7 +476,6 @@ target_sources (rippled PRIVATE
src/ripple/app/tx/impl/Payment.cpp
src/ripple/app/tx/impl/Remit.cpp
src/ripple/app/tx/impl/SetAccount.cpp
src/ripple/app/tx/impl/SetCron.cpp
src/ripple/app/tx/impl/SetHook.cpp
src/ripple/app/tx/impl/SetRemarks.cpp
src/ripple/app/tx/impl/SetRegularKey.cpp
@@ -488,7 +488,6 @@ target_sources (rippled PRIVATE
src/ripple/app/tx/impl/apply.cpp
src/ripple/app/tx/impl/applySteps.cpp
src/ripple/app/hook/impl/applyHook.cpp
src/ripple/app/hook/impl/HookAPI.cpp
src/ripple/app/tx/impl/details/NFTokenUtils.cpp
#[===============================[
main sources:

View File

@@ -1769,7 +1769,7 @@ pool.ntp.org
# Unless an absolute path is specified, it will be considered relative to the
# folder in which the xahaud.cfg file is located.
[validators_file]
validators.txt
validators-xahau.txt
# Turn down default logging to save disk space in the long run.
# Valid values here are trace, debug, info, warning, error, and fatal

View File

@@ -37,6 +37,7 @@
#define KEYLET_NFT_OFFER 23
#define KEYLET_HOOK_DEFINITION 24
#define KEYLET_HOOK_STATE_DIR 25
#define KEYLET_CRON 26
#define COMPARE_EQUAL 1U
#define COMPARE_LESS 2U

View File

@@ -278,8 +278,7 @@ enum keylet_code : uint32_t {
NFT_OFFER = 23,
HOOK_DEFINITION = 24,
HOOK_STATE_DIR = 25,
LAST_KLTYPE_V0 = HOOK_DEFINITION,
LAST_KLTYPE_V1 = HOOK_STATE_DIR,
CRON = 26
};
}

View File

@@ -1,350 +0,0 @@
#include <ripple/app/hook/Enum.h>
#include <ripple/app/tx/impl/ApplyContext.h>
#include <ripple/basics/Blob.h>
#include <ripple/basics/Expected.h>
#include <ripple/basics/Slice.h>
#include <ripple/protocol/STTx.h>
#include <ripple/app/misc/Transaction.h>
#include <ripple/basics/base_uint.h>
#include <ripple/protocol/TxFormats.h>
#include <cstdint>
namespace hook {
using namespace ripple;
using HookReturnCode = hook_api::hook_return_code;
using Bytes = std::vector<std::uint8_t>;
struct HookContext; // defined in applyHook.h
class HookAPI
{
public:
explicit HookAPI(HookContext& ctx) : hookCtx(ctx)
{
}
/// control APIs
// _g
// accept
// rollback
/// util APIs
Expected<std::string, HookReturnCode>
util_raddr(Bytes const& accountID) const;
Expected<Bytes, HookReturnCode>
util_accid(std::string raddress) const;
Expected<bool, HookReturnCode>
util_verify(Slice const& data, Slice const& sig, Slice const& key) const;
uint256
util_sha512h(Slice const& data) const;
// util_keylet()
/// sto APIs
Expected<bool, HookReturnCode>
sto_validate(Bytes const& data) const;
Expected<std::pair<uint32_t, uint32_t>, HookReturnCode>
sto_subfield(Bytes const& data, uint32_t field_id) const;
Expected<std::pair<uint32_t, uint32_t>, HookReturnCode>
sto_subarray(Bytes const& data, uint32_t index_id) const;
Expected<Bytes, HookReturnCode>
sto_emplace(
Bytes const& source_object,
std::optional<Bytes> const& field_object,
uint32_t field_id) const;
// sto_erase(): same as sto_emplace with field_object = nullopt
/// etxn APIs
Expected<std::shared_ptr<Transaction>, HookReturnCode>
emit(Slice const& txBlob) const;
Expected<uint64_t, HookReturnCode>
etxn_burden() const;
Expected<uint64_t, HookReturnCode>
etxn_fee_base(Slice const& txBlob) const;
Expected<uint64_t, HookReturnCode>
etxn_details(uint8_t* out_ptr) const;
Expected<uint64_t, HookReturnCode>
etxn_reserve(uint64_t count) const;
uint32_t
etxn_generation() const;
Expected<uint256, HookReturnCode>
etxn_nonce() const;
/// float APIs
Expected<uint64_t, HookReturnCode>
float_set(int32_t exponent, int64_t mantissa) const;
Expected<uint64_t, HookReturnCode>
float_multiply(uint64_t float1, uint64_t float2) const;
Expected<uint64_t, HookReturnCode>
float_mulratio(
uint64_t float1,
uint32_t round_up,
uint32_t numerator,
uint32_t denominator) const;
uint64_t
float_negate(uint64_t float1) const;
Expected<uint64_t, HookReturnCode>
float_compare(uint64_t float1, uint64_t float2, uint32_t mode) const;
Expected<uint64_t, HookReturnCode>
float_sum(uint64_t float1, uint64_t float2) const;
Expected<Bytes, HookReturnCode>
float_sto(
std::optional<Currency> currency,
std::optional<AccountID> issuer,
uint64_t float1,
uint32_t field_code,
uint32_t write_len) const;
Expected<uint64_t, HookReturnCode>
float_sto_set(Bytes const& data) const;
Expected<uint64_t, HookReturnCode>
float_invert(uint64_t float1) const;
Expected<uint64_t, HookReturnCode>
float_divide(uint64_t float1, uint64_t float2) const;
uint64_t
float_one() const;
Expected<uint64_t, HookReturnCode>
float_mantissa(uint64_t float1) const;
uint64_t
float_sign(uint64_t float1) const;
Expected<uint64_t, HookReturnCode>
float_int(uint64_t float1, uint32_t decimal_places, uint32_t absolute)
const;
Expected<uint64_t, HookReturnCode>
float_log(uint64_t float1) const;
Expected<uint64_t, HookReturnCode>
float_root(uint64_t float1, uint32_t n) const;
/// otxn APIs
uint64_t
otxn_burden() const;
uint32_t
otxn_generation() const;
Expected<const STBase*, HookReturnCode>
otxn_field(uint32_t field_id) const;
Expected<uint256, HookReturnCode>
otxn_id(uint32_t flags) const;
TxType
otxn_type() const;
Expected<uint32_t, HookReturnCode>
otxn_slot(uint32_t slot_into) const;
Expected<Blob, HookReturnCode>
otxn_param(Bytes const& param_name) const;
/// hook APIs
AccountID
hook_account() const;
Expected<ripple::uint256, HookReturnCode>
hook_hash(int32_t hook_no) const;
Expected<int64_t, HookReturnCode>
hook_again() const;
Expected<Blob, HookReturnCode>
hook_param(Bytes const& paramName) const;
Expected<uint64_t, HookReturnCode>
hook_param_set(
uint256 const& hash,
Bytes const& paramName,
Bytes const& paramValue) const;
Expected<uint64_t, HookReturnCode>
hook_skip(uint256 const& hash, uint32_t flags) const;
uint8_t
hook_pos() const;
/// ledger APIs
uint64_t
fee_base() const;
uint32_t
ledger_seq() const;
uint256
ledger_last_hash() const;
uint64_t
ledger_last_time() const;
Expected<uint256, HookReturnCode>
ledger_nonce() const;
Expected<Keylet, HookReturnCode>
ledger_keylet(Keylet const& klLo, Keylet const& klHi) const;
/// state APIs
// state(): same as state_foreign with ns = 0 and account = hook_account()
Expected<Bytes, HookReturnCode>
state_foreign(
uint256 const& key,
uint256 const& ns,
AccountID const& account) const;
// state_set(): same as state_foreign_set with ns = 0 and account =
Expected<uint64_t, HookReturnCode>
state_foreign_set(
uint256 const& key,
uint256 const& ns,
AccountID const& account,
Bytes& data) const;
/// slot APIs
Expected<const STBase*, HookReturnCode>
slot(uint32_t slot_no) const;
Expected<uint64_t, HookReturnCode>
slot_clear(uint32_t slot_no) const;
Expected<uint64_t, HookReturnCode>
slot_count(uint32_t slot_no) const;
Expected<uint32_t, HookReturnCode>
slot_set(Bytes const& data, uint32_t slot_no) const;
Expected<uint64_t, HookReturnCode>
slot_size(uint32_t slot_no) const;
Expected<uint32_t, HookReturnCode>
slot_subarray(uint32_t parent_slot, uint32_t array_id, uint32_t new_slot)
const;
Expected<uint32_t, HookReturnCode>
slot_subfield(uint32_t parent_slot, uint32_t field_id, uint32_t new_slot)
const;
Expected<std::variant<STBase, STAmount>, HookReturnCode>
slot_type(uint32_t slot_no, uint32_t flags) const;
Expected<uint64_t, HookReturnCode>
slot_float(uint32_t slot_no) const;
/// trace APIs
// trace
// trace_num
// trace_float
Expected<uint32_t, HookReturnCode>
meta_slot(uint32_t slot_into) const;
Expected<std::pair<uint32_t, uint32_t>, HookReturnCode>
xpop_slot(uint32_t slot_into_tx, uint32_t slot_into_meta) const;
private:
HookContext& hookCtx;
inline int32_t
no_free_slots() const;
inline std::optional<int32_t>
get_free_slot() const;
inline Expected<uint64_t, HookReturnCode>
float_multiply_internal_parts(
uint64_t man1,
int32_t exp1,
bool neg1,
uint64_t man2,
int32_t exp2,
bool neg2) const;
inline Expected<uint64_t, HookReturnCode>
mulratio_internal(
int64_t& man1,
int32_t& exp1,
bool round_up,
uint32_t numerator,
uint32_t denominator) const;
inline Expected<uint64_t, HookReturnCode>
float_divide_internal(uint64_t float1, uint64_t float2) const;
inline Expected<uint64_t, HookReturnCode>
double_to_xfl(double x) const;
std::optional<ripple::Keylet>
unserialize_keylet(Bytes const& data) const;
// update the state cache
inline std::optional<
std::reference_wrapper<std::pair<bool, ripple::Blob> const>>
lookup_state_cache(
AccountID const& acc,
uint256 const& ns,
uint256 const& key) const;
// check the state cache
inline Expected<uint64_t, HookReturnCode>
set_state_cache(
AccountID const& acc,
uint256 const& ns,
uint256 const& key,
Bytes const& data,
bool modified) const;
// these are only used by get_stobject_length below
enum parse_error {
pe_unexpected_end = -1,
pe_unknown_type_early = -2, // detected early
pe_unknown_type_late = -3, // end of function
pe_excessive_nesting = -4,
pe_excessive_size = -5
};
inline Expected<
int32_t,
parse_error>
get_stobject_length(
unsigned char* start, // in - begin iterator
unsigned char* maxptr, // in - end iterator
int& type, // out - populated by serialized type code
int& field, // out - populated by serialized field code
int& payload_start, // out - the start of actual payload data for
// this type
int& payload_length, // out - the length of actual payload data for
// this type
int recursion_depth = 0) // used internally
const;
};
} // namespace hook

View File

@@ -477,6 +477,7 @@ struct HookResult
ripple::uint256 const hookHash;
ripple::uint256 const hookCanEmit;
ripple::Keylet const accountKeylet;
ripple::Keylet const ownerDirKeylet;
ripple::Keylet const hookKeylet;
ripple::AccountID const account;
ripple::AccountID const otxnAccount;
@@ -798,13 +799,12 @@ public:
ADD_HOOK_FUNCTION(util_accid, ctx);
ADD_HOOK_FUNCTION(util_verify, ctx);
ADD_HOOK_FUNCTION(util_sha512h, ctx);
ADD_HOOK_FUNCTION(util_keylet, ctx);
ADD_HOOK_FUNCTION(sto_validate, ctx);
ADD_HOOK_FUNCTION(sto_subfield, ctx);
ADD_HOOK_FUNCTION(sto_subarray, ctx);
ADD_HOOK_FUNCTION(sto_emplace, ctx);
ADD_HOOK_FUNCTION(sto_erase, ctx);
ADD_HOOK_FUNCTION(util_keylet, ctx);
ADD_HOOK_FUNCTION(emit, ctx);
ADD_HOOK_FUNCTION(etxn_burden, ctx);
@@ -843,11 +843,6 @@ public:
ADD_HOOK_FUNCTION(hook_account, ctx);
ADD_HOOK_FUNCTION(hook_hash, ctx);
ADD_HOOK_FUNCTION(hook_again, ctx);
ADD_HOOK_FUNCTION(hook_param, ctx);
ADD_HOOK_FUNCTION(hook_param_set, ctx);
ADD_HOOK_FUNCTION(hook_skip, ctx);
ADD_HOOK_FUNCTION(hook_pos, ctx);
ADD_HOOK_FUNCTION(fee_base, ctx);
ADD_HOOK_FUNCTION(ledger_seq, ctx);
ADD_HOOK_FUNCTION(ledger_last_hash, ctx);
@@ -855,6 +850,11 @@ public:
ADD_HOOK_FUNCTION(ledger_nonce, ctx);
ADD_HOOK_FUNCTION(ledger_keylet, ctx);
ADD_HOOK_FUNCTION(hook_param, ctx);
ADD_HOOK_FUNCTION(hook_param_set, ctx);
ADD_HOOK_FUNCTION(hook_skip, ctx);
ADD_HOOK_FUNCTION(hook_pos, ctx);
ADD_HOOK_FUNCTION(state, ctx);
ADD_HOOK_FUNCTION(state_foreign, ctx);
ADD_HOOK_FUNCTION(state_set, ctx);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -17,7 +17,7 @@
*/
//==============================================================================
#include <ripple/app/tx/impl/SetCron.h>
#include <ripple/app/tx/impl/CronSet.h>
#include <ripple/basics/Log.h>
#include <ripple/ledger/View.h>
#include <ripple/protocol/Feature.h>
@@ -28,13 +28,13 @@
namespace ripple {
TxConsequences
SetCron::makeTxConsequences(PreflightContext const& ctx)
CronSet::makeTxConsequences(PreflightContext const& ctx)
{
return TxConsequences{ctx.tx, TxConsequences::normal};
}
NotTEC
SetCron::preflight(PreflightContext const& ctx)
CronSet::preflight(PreflightContext const& ctx)
{
if (!ctx.rules.enabled(featureCron))
return temDISABLED;
@@ -47,7 +47,7 @@ SetCron::preflight(PreflightContext const& ctx)
if (tx.getFlags() & tfCronSetMask)
{
JLOG(j.warn()) << "SetCron: Invalid flags set.";
JLOG(j.warn()) << "CronSet: Invalid flags set.";
return temINVALID_FLAG;
}
@@ -69,7 +69,7 @@ SetCron::preflight(PreflightContext const& ctx)
// delete operation
if (hasDelay || hasRepeat || hasStartTime)
{
JLOG(j.debug()) << "SetCron: tfCronUnset flag cannot be used with "
JLOG(j.debug()) << "CronSet: tfCronUnset flag cannot be used with "
"DelaySeconds, RepeatCount or StartTime.";
return temMALFORMED;
}
@@ -81,7 +81,7 @@ SetCron::preflight(PreflightContext const& ctx)
if (!hasStartTime)
{
JLOG(j.debug())
<< "SetCron: StartTime is required. Use StartTime=0 for "
<< "CronSet: StartTime is required. Use StartTime=0 for "
"immediate execution, or specify a future timestamp.";
return temMALFORMED;
}
@@ -89,7 +89,7 @@ SetCron::preflight(PreflightContext const& ctx)
if ((!hasDelay && hasRepeat) || (hasDelay && !hasRepeat))
{
JLOG(j.debug())
<< "SetCron: DelaySeconds and RepeatCount must both be present "
<< "CronSet: DelaySeconds and RepeatCount must both be present "
"for recurring crons, or both absent for one-off crons.";
return temMALFORMED;
}
@@ -101,7 +101,7 @@ SetCron::preflight(PreflightContext const& ctx)
if (delay > 31536000UL /* 365 days in seconds */)
{
JLOG(j.debug())
<< "SetCron: DelaySeconds was too high. (max 365 "
<< "CronSet: DelaySeconds was too high. (max 365 "
"days in seconds).";
return temMALFORMED;
}
@@ -114,7 +114,7 @@ SetCron::preflight(PreflightContext const& ctx)
if (recur == 0)
{
JLOG(j.debug())
<< "SetCron: RepeatCount must be greater than 0."
<< "CronSet: RepeatCount must be greater than 0."
"For one-time execution, omit DelaySeconds and "
"RepeatCount.";
return temMALFORMED;
@@ -122,8 +122,8 @@ SetCron::preflight(PreflightContext const& ctx)
if (recur > 256)
{
JLOG(j.debug())
<< "SetCron: RepeatCount too high. Limit is 256. Issue "
"new SetCron to increase.";
<< "CronSet: RepeatCount too high. Limit is 256. Issue "
"new CronSet to increase.";
return temMALFORMED;
}
}
@@ -133,7 +133,7 @@ SetCron::preflight(PreflightContext const& ctx)
}
TER
SetCron::preclaim(PreclaimContext const& ctx)
CronSet::preclaim(PreclaimContext const& ctx)
{
if (ctx.tx.isFieldPresent(sfStartTime) &&
ctx.tx.getFieldU32(sfStartTime) != 0)
@@ -146,7 +146,7 @@ SetCron::preclaim(PreclaimContext const& ctx)
if (startTime < parentCloseTime)
{
JLOG(ctx.j.debug()) << "SetCron: StartTime must be in the future "
JLOG(ctx.j.debug()) << "CronSet: StartTime must be in the future "
"(or 0 for immediate execution)";
return tecEXPIRED;
}
@@ -154,7 +154,7 @@ SetCron::preclaim(PreclaimContext const& ctx)
if (startTime > ctx.view.parentCloseTime().time_since_epoch().count() +
365 * 24 * 60 * 60)
{
JLOG(ctx.j.debug()) << "SetCron: StartTime is too far in the "
JLOG(ctx.j.debug()) << "CronSet: StartTime is too far in the "
"future (max 365 days).";
return tecEXPIRED;
}
@@ -163,7 +163,7 @@ SetCron::preclaim(PreclaimContext const& ctx)
}
TER
SetCron::doApply()
CronSet::doApply()
{
auto& view = ctx_.view();
auto const& tx = ctx_.tx;
@@ -205,21 +205,21 @@ SetCron::doApply()
auto sleCron = view.peek(klOld);
if (!sleCron)
{
JLOG(j_.warn()) << "SetCron: Cron object didn't exist.";
JLOG(j_.warn()) << "CronSet: Cron object didn't exist.";
return tefBAD_LEDGER;
}
if (safe_cast<LedgerEntryType>(
sleCron->getFieldU16(sfLedgerEntryType)) != ltCRON)
{
JLOG(j_.warn()) << "SetCron: sfCron pointed to non-cron object!!";
JLOG(j_.warn()) << "CronSet: sfCron pointed to non-cron object!!";
return tefBAD_LEDGER;
}
if (!view.dirRemove(
keylet::ownerDir(id), (*sleCron)[sfOwnerNode], klOld, false))
{
JLOG(j_.warn()) << "SetCron: Ownerdir bad. " << id;
JLOG(j_.warn()) << "CronSet: Ownerdir bad. " << id;
return tefBAD_LEDGER;
}
@@ -278,7 +278,7 @@ SetCron::doApply()
}
XRPAmount
SetCron::calculateBaseFee(ReadView const& view, STTx const& tx)
CronSet::calculateBaseFee(ReadView const& view, STTx const& tx)
{
auto const baseFee = Transactor::calculateBaseFee(view, tx);
@@ -290,7 +290,7 @@ SetCron::calculateBaseFee(ReadView const& view, STTx const& tx)
tx.isFieldPresent(sfRepeatCount) ? tx.getFieldU32(sfRepeatCount) : 0;
// factor a cost based on the total number of txns expected
// for RepeatCount of 0 we have this txn (SetCron) and the
// for RepeatCount of 0 we have this txn (CronSet) and the
// single Cron txn (2). For a RepeatCount of 1 we have this txn,
// the first time the cron executes, and the second time (3).
uint32_t const additionalExpectedExecutions = 1 + repeatCount;

View File

@@ -17,8 +17,8 @@
*/
//==============================================================================
#ifndef RIPPLE_TX_SETCRON_H_INCLUDED
#define RIPPLE_TX_SETCRON_H_INCLUDED
#ifndef RIPPLE_TX_CRONSET_H_INCLUDED
#define RIPPLE_TX_CRONSET_H_INCLUDED
#include <ripple/app/tx/impl/Transactor.h>
#include <ripple/basics/Log.h>
@@ -26,12 +26,12 @@
namespace ripple {
class SetCron : public Transactor
class CronSet : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
explicit SetCron(ApplyContext& ctx) : Transactor(ctx)
explicit CronSet(ApplyContext& ctx) : Transactor(ctx)
{
}

View File

@@ -29,6 +29,7 @@
#include <ripple/app/tx/impl/CreateOffer.h>
#include <ripple/app/tx/impl/CreateTicket.h>
#include <ripple/app/tx/impl/Cron.h>
#include <ripple/app/tx/impl/CronSet.h>
#include <ripple/app/tx/impl/DeleteAccount.h>
#include <ripple/app/tx/impl/DepositPreauth.h>
#include <ripple/app/tx/impl/Escrow.h>
@@ -44,7 +45,6 @@
#include <ripple/app/tx/impl/Payment.h>
#include <ripple/app/tx/impl/Remit.h>
#include <ripple/app/tx/impl/SetAccount.h>
#include <ripple/app/tx/impl/SetCron.h>
#include <ripple/app/tx/impl/SetHook.h>
#include <ripple/app/tx/impl/SetRegularKey.h>
#include <ripple/app/tx/impl/SetRemarks.h>
@@ -184,7 +184,7 @@ invoke_preflight(PreflightContext const& ctx)
case ttURITOKEN_CANCEL_SELL_OFFER:
return invoke_preflight_helper<URIToken>(ctx);
case ttCRON_SET:
return invoke_preflight_helper<SetCron>(ctx);
return invoke_preflight_helper<CronSet>(ctx);
case ttCRON:
return invoke_preflight_helper<Cron>(ctx);
default:
@@ -313,7 +313,7 @@ invoke_preclaim(PreclaimContext const& ctx)
case ttURITOKEN_CANCEL_SELL_OFFER:
return invoke_preclaim<URIToken>(ctx);
case ttCRON_SET:
return invoke_preclaim<SetCron>(ctx);
return invoke_preclaim<CronSet>(ctx);
case ttCRON:
return invoke_preclaim<Cron>(ctx);
default:
@@ -404,7 +404,7 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
case ttURITOKEN_CANCEL_SELL_OFFER:
return URIToken::calculateBaseFee(view, tx);
case ttCRON_SET:
return SetCron::calculateBaseFee(view, tx);
return CronSet::calculateBaseFee(view, tx);
case ttCRON:
return Cron::calculateBaseFee(view, tx);
default:
@@ -601,7 +601,7 @@ invoke_apply(ApplyContext& ctx)
return p();
}
case ttCRON_SET: {
SetCron p(ctx);
CronSet p(ctx);
return p();
}
case ttCRON: {

View File

@@ -137,14 +137,14 @@ class [[nodiscard]] Expected
public:
template <typename U>
requires std::convertible_to<U, T> constexpr Expected(U && r)
: Base(boost::outcome_v2::success(T(std::forward<U>(r))))
: Base(T(std::forward<U>(r)))
{
}
template <typename U>
requires std::convertible_to<U, E> &&
(!std::is_reference_v<U>)constexpr Expected(Unexpected<U> e)
: Base(boost::outcome_v2::failure(E(std::move(e.value()))))
: Base(E(std::move(e.value())))
{
}
@@ -220,7 +220,7 @@ public:
template <typename U>
requires std::convertible_to<U, E> &&
(!std::is_reference_v<U>)constexpr Expected(Unexpected<U> e)
: Base(boost::outcome_v2::failure(E(std::move(e.value()))))
: Base(E(std::move(e.value())))
{
}

View File

@@ -376,6 +376,8 @@ LedgerFormats::LedgerFormats()
{sfDelaySeconds, soeREQUIRED},
{sfRepeatCount, soeREQUIRED},
{sfOwnerNode, soeREQUIRED},
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED}
},
commonFields);

View File

@@ -1106,30 +1106,32 @@ chooseLedgerEntryType(Json::Value const& params)
std::pair<RPC::Status, LedgerEntryType> result{RPC::Status::OK, ltANY};
if (params.isMember(jss::type))
{
static constexpr std::array<std::pair<char const*, LedgerEntryType>, 22>
types{
{{jss::account, ltACCOUNT_ROOT},
{jss::amendments, ltAMENDMENTS},
{jss::check, ltCHECK},
{jss::deposit_preauth, ltDEPOSIT_PREAUTH},
{jss::directory, ltDIR_NODE},
{jss::escrow, ltESCROW},
{jss::emitted_txn, ltEMITTED_TXN},
{jss::hook, ltHOOK},
{jss::hook_definition, ltHOOK_DEFINITION},
{jss::hook_state, ltHOOK_STATE},
{jss::fee, ltFEE_SETTINGS},
{jss::hashes, ltLEDGER_HASHES},
{jss::import_vlseq, ltIMPORT_VLSEQ},
{jss::offer, ltOFFER},
{jss::payment_channel, ltPAYCHAN},
{jss::uri_token, ltURI_TOKEN},
{jss::signer_list, ltSIGNER_LIST},
{jss::state, ltRIPPLE_STATE},
{jss::ticket, ltTICKET},
{jss::nft_offer, ltNFTOKEN_OFFER},
{jss::nft_page, ltNFTOKEN_PAGE},
{jss::unl_report, ltUNL_REPORT}}};
static constexpr std::array<std::pair<char const*, LedgerEntryType>, 23>
types{{
{jss::account, ltACCOUNT_ROOT},
{jss::amendments, ltAMENDMENTS},
{jss::check, ltCHECK},
{jss::deposit_preauth, ltDEPOSIT_PREAUTH},
{jss::directory, ltDIR_NODE},
{jss::escrow, ltESCROW},
{jss::emitted_txn, ltEMITTED_TXN},
{jss::hook, ltHOOK},
{jss::hook_definition, ltHOOK_DEFINITION},
{jss::hook_state, ltHOOK_STATE},
{jss::fee, ltFEE_SETTINGS},
{jss::hashes, ltLEDGER_HASHES},
{jss::import_vlseq, ltIMPORT_VLSEQ},
{jss::offer, ltOFFER},
{jss::payment_channel, ltPAYCHAN},
{jss::uri_token, ltURI_TOKEN},
{jss::signer_list, ltSIGNER_LIST},
{jss::state, ltRIPPLE_STATE},
{jss::ticket, ltTICKET},
{jss::nft_offer, ltNFTOKEN_OFFER},
{jss::nft_page, ltNFTOKEN_PAGE},
{jss::unl_report, ltUNL_REPORT},
{jss::cron, ltCRON},
}};
auto const& p = params[jss::type];
if (!p.isString())

View File

@@ -17,22 +17,11 @@
*/
//==============================================================================
#include <ripple/app/hook/Enum.h>
#include <ripple/app/hook/HookAPI.h>
#include <ripple/app/hook/Misc.h>
#include <ripple/app/hook/applyHook.h>
#include <ripple/app/ledger/LedgerMaster.h>
#include <ripple/app/misc/Transaction.h>
#include <ripple/app/tx/impl/ApplyContext.h>
#include <ripple/app/tx/impl/SetHook.h>
#include <ripple/basics/base_uint.h>
#include <ripple/json/json_reader.h>
#include <ripple/json/json_writer.h>
#include <ripple/ledger/OpenView.h>
#include <ripple/protocol/SField.h>
#include <ripple/protocol/STAccount.h>
#include <ripple/protocol/STArray.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/protocol/TxFormats.h>
#include <ripple/protocol/jss.h>
#include <test/app/Import_json.h>
#include <test/app/SetHook_wasm.h>
@@ -2581,212 +2570,15 @@ public:
}
}
ApplyContext
createApplyContext(jtx::Env& env, OpenView ov, STTx const& tx)
{
ApplyContext applyCtx{
env.app(),
ov,
tx,
tesSUCCESS,
env.current()->fees().base,
tapNONE,
env.journal};
return applyCtx;
}
// hook::HookContext
// createHookContext(
// AccountID const& hookAccount,
// AccountID const& otxnAccount,
// hook::HookContext ctx)
// {
// hook::HookContext hookCtx{
// .applyCtx = ctx.applyCtx,
// .result =
// {
// .hookSetTxnID = uint256(),
// .hookHash = uint256(),
// .hookCanEmit = uint256(),
// .accountKeylet = keylet::account(otxnAccount),
// .hookKeylet = keylet::hook(hookAccount),
// .account = otxnAccount,
// .otxnAccount = otxnAccount,
// .hookNamespace = uint256(),
// .stateMap = ctx.result.stateMap,
// .hookParamOverrides = {},
// .hookParams = {{}},
// .hookSkips = {uint256{}},
// },
// .module = nullptr};
// return hookCtx;
// }
void
test_emit(FeatureBitset features)
{
testcase("Test emit");
using namespace jtx;
Env env{*this, features};
auto const alice = Account{"alice"};
auto const bob = Account{"bob"};
{
using namespace hook_api;
Env env{*this, features};
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
OpenView ov{*env.current()};
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
STTx const emitInvokeTx = STTx(ttINVOKE, [&](STObject& obj) {
obj[sfAccount] = alice.id();
obj[sfSequence] = 0;
obj[sfSigningPubKey] = PublicKey();
obj[sfFirstLedgerSequence] = env.closed()->seq() + 1;
obj[sfLastLedgerSequence] = env.closed()->seq() + 5;
obj[sfFee] = env.closed()->fees().base;
auto& emitDetails = obj.peekFieldObject(sfEmitDetails);
emitDetails[sfEmitGeneration] = 1;
emitDetails[sfEmitBurden] = 1;
emitDetails[sfEmitParentTxnID] = invokeTx.getTransactionID();
emitDetails[sfEmitNonce] = uint256();
emitDetails[sfEmitHookHash] = uint256();
});
STTx const emitSetHookTx = STTx(ttHOOK_SET, [&](STObject& obj) {
obj[sfAccount] = alice.id();
obj[sfSequence] = 0;
obj[sfSigningPubKey] = PublicKey();
obj[sfFirstLedgerSequence] = env.closed()->seq() + 1;
obj[sfLastLedgerSequence] = env.closed()->seq() + 5;
obj[sfFee] = env.closed()->fees().base;
STObject hookobj(sfHook);
auto& hooks = obj.peekFieldArray(sfHooks);
hooks.emplace_back(std::move(hookobj));
auto& emitDetails = obj.peekFieldObject(sfEmitDetails);
emitDetails[sfEmitGeneration] = 1;
emitDetails[sfEmitBurden] = 1;
emitDetails[sfEmitParentTxnID] = invokeTx.getTransactionID();
emitDetails[sfEmitNonce] = uint256();
emitDetails[sfEmitHookHash] = uint256();
});
{
// PREREQUISITE_NOT_MET
auto hookCtx = makeStubHookContext(
applyCtx,
alice.id(),
alice.id(),
{.expected_etxn_count = -1});
hook::HookAPI api(hookCtx);
Serializer s;
emitInvokeTx.add(s);
BEAST_EXPECT(
api.emit(s.slice()).error() == PREREQUISITE_NOT_MET);
}
{
// TOO_MANY_EMITTED_TXN
std::string reason;
auto tx = std::make_shared<ripple::Transaction>(
std::make_shared<ripple::STTx const>(invokeTx),
reason,
env.app());
std::queue<std::shared_ptr<ripple::Transaction>> emittedTxn;
emittedTxn.push(tx);
auto hookCtx = makeStubHookContext(
applyCtx,
alice.id(),
alice.id(),
{
.expected_etxn_count = 1,
.result = {.emittedTxn = emittedTxn},
});
hook::HookAPI api(hookCtx);
Serializer s;
emitInvokeTx.add(s);
BEAST_EXPECT(
api.emit(s.slice()).error() == TOO_MANY_EMITTED_TXN);
}
// EMISSION_FAILURE
{
// Pseudo txn
auto hookCtx = makeStubHookContext(
applyCtx,
alice.id(),
alice.id(),
{
.expected_etxn_count = 1,
.nonce_used = {{uint256(0), true}},
});
hook::HookAPI api(hookCtx);
auto tx = emitInvokeTx;
tx.setFieldU16(sfTransactionType, ttFEE);
Serializer s;
tx.add(s);
BEAST_EXPECT(api.emit(s.slice()).error() == EMISSION_FAILURE);
}
{
// HookCanEmit (non-SetHook)
auto hookCtx = makeStubHookContext(
applyCtx,
alice.id(),
alice.id(),
{.expected_etxn_count = 1,
.nonce_used = {{uint256(0), true}},
.result = {
.hookCanEmit = UINT256_BIT[ttINVOKE],
}});
hook::HookAPI api(hookCtx);
auto tx = emitInvokeTx;
Serializer s;
tx.add(s);
BEAST_EXPECT(api.emit(s.slice()).error() == EMISSION_FAILURE);
}
{
// HookCanEmit (SetHook) Error
auto hookCtx = makeStubHookContext(
applyCtx,
alice.id(),
alice.id(),
{
.expected_etxn_count = 1,
.nonce_used = {{uint256(0), true}},
.result = {.hookCanEmit = uint256()},
});
hook::HookAPI api(hookCtx);
auto tx = emitSetHookTx;
Serializer s;
tx.add(s);
BEAST_EXPECT(api.emit(s.slice()).error() == EMISSION_FAILURE);
}
{
// HookCanEmit (SetHook) Success
auto hookCtx = makeStubHookContext(
applyCtx,
alice.id(),
alice.id(),
{
.expected_etxn_count = 1,
.nonce_used = {{uint256(0), true}},
.result = {.hookCanEmit = UINT256_BIT[ttHOOK_SET]},
});
hook::HookAPI api(hookCtx);
auto tx = emitSetHookTx;
Serializer s;
tx.add(s);
auto const result = api.emit(s.slice());
BEAST_EXPECT(result.has_value());
}
}
return;
Env env{*this, features};
env.fund(XRP(10000), alice);
env.fund(XRP(10000), bob);
@@ -3302,7 +3094,6 @@ public:
extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t etxn_details (uint32_t, uint32_t);
extern int64_t etxn_reserve(uint32_t);
extern int64_t hook_hash (uint32_t, uint32_t, int32_t);
#define TOO_SMALL -4
#define OUT_OF_BOUNDS -1
#define PREREQUISITE_NOT_MET -9
@@ -3325,45 +3116,6 @@ public:
etxn_reserve(1);
ASSERT(etxn_details((uint32_t)det, 116) == 116);
uint8_t expected1[49] = {
0xEDU, 0x20U, 0x2EU, 0x00U, 0x00U, 0x00U, 0x01U, 0x3DU, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x01U, 0x5BU, 0xB8U, 0x05U, 0xD6U,
0xC3U, 0x52U, 0xDFU, 0x7AU, 0x27U, 0x76U, 0x6DU, 0xC0U, 0x20U, 0x47U,
0xB7U, 0x64U, 0x22U, 0x5AU, 0xB7U, 0x5DU, 0xF3U, 0xFAU, 0x0DU, 0xE3U,
0xBDU, 0xC6U, 0x40U, 0xBAU, 0xD0U, 0x0AU, 0x66U, 0xEBU, 0x68U,
};
// 0x5CU
// EmitNonce 32bytes
uint8_t expected_emit_nonce[32] = {
0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU,
0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU,
0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU,
0xFFU, 0xFFU
};
// 0x5DU,
// EmitHookHash
uint8_t expected_hook_hash[32] = {
0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU,
0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU,
0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU,
0xFFU, 0xFFU,
};
// 0xE1U
// current hook hash
ASSERT(hook_hash((uint32_t)expected_hook_hash, 32, -1) == 32);
for (int i = 0; GUARD(49), i < sizeof(expected1); ++i)
ASSERT(det[i] == expected1[i]);
ASSERT(det[49] == 0x5CU);
// TODO: need to test this
// for (int i = 0; GUARD(32), i < sizeof(expected_emit_nonce); ++i)
// ASSERT(det[50 + i] == expected_emit_nonce[i]);
ASSERT(det[82] == 0x5DU);
for (int i = 0; GUARD(32), i < sizeof(expected_hook_hash); ++i)
ASSERT(det[83 + i] == expected_hook_hash[i]);
ASSERT(det[115] == 0xE1);
return accept(0,0,0);
}
)[test.hook]"];
@@ -3484,7 +3236,6 @@ public:
}
ASSERT(etxn_nonce((uint32_t)nonce, 116) == TOO_MANY_NONCES);
ASSERT(etxn_nonce((uint32_t)nonce, 31) == TOO_MANY_NONCES);
return accept(0,0,0);
}
@@ -11396,6 +11147,7 @@ public:
#define KEYLET_PAYCHAN 21
#define KEYLET_EMITTED_TXN 22
#define KEYLET_NFT_OFFER 23
#define KEYLET_CRON 26
#define ASSERT(x)\
if (!(x))\
rollback((uint32_t)#x, sizeof(#x), __LINE__);
@@ -11458,6 +11210,9 @@ public:
// Test min size
ASSERT(util_keylet((uint32_t)buf, 33, KEYLET_SKIP, 0,0,0,0,0,0) == TOO_SMALL);
// Invalid keylet type
ASSERT(util_keylet((uint32_t)buf, 34, 0, 0,0,0,0,0,0) == INVALID_ARGUMENT);
ASSERT(util_keylet((uint32_t)buf, 34, 0x99999999, 0,0,0,0,0,0) == INVALID_ARGUMENT);
// Test one of each type
ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_HOOK,
@@ -11900,6 +11655,17 @@ public:
0,0
)));
ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_CRON, SBUF(a), 1, 0, 0, 0)));
{
uint8_t ans[] =
{
0x00U,0x41U,0xF7U,0xB6U,0x45U,0x43U,0x61U,0x87U,0xCCU,0x61U,
0x00U,0x00U,0x00U,0x01U,0x0AU,0x45U,0x80U,0x75U,0x7CU,0xDAU,
0xD9U,0x16U,0x7EU,0xEEU,0xC1U,0x3CU,0x6CU,0x15U,0xD5U,0x17U,
0xE2U,0x72U,0x9EU,0xC8
};
ASSERT_KL_EQ(ans);
}
accept(0,0,0);
}
)[test.hook]"];
@@ -13684,7 +13450,7 @@ private:
)[test.hook]"];
HASH_WASM(accept2);
}; // namespace test
};
#define SETHOOK_TEST(i, last) \
class SetHook##i##_test : public SetHook0_test \

File diff suppressed because it is too large Load Diff

View File

@@ -20,13 +20,9 @@
#ifndef RIPPLE_TEST_JTX_HOOK_H_INCLUDED
#define RIPPLE_TEST_JTX_HOOK_H_INCLUDED
#include <ripple/app/hook/applyHook.h>
#include <ripple/json/json_value.h>
#include <cstdint>
#include <map>
#include <optional>
#include <test/jtx/Account.h>
#include <vector>
namespace ripple {
namespace test {
@@ -47,66 +43,6 @@ hso(std::string const& wasmHex, void (*f)(Json::Value& jv) = 0);
Json::Value
hso_delete(void (*f)(Json::Value& jv) = 0);
struct StubHookResult
{
ripple::uint256 const hookSetTxnID = ripple::uint256();
ripple::uint256 const hookHash = ripple::uint256();
ripple::uint256 const hookCanEmit = ripple::uint256();
ripple::uint256 const hookNamespace = ripple::uint256();
std::queue<std::shared_ptr<ripple::Transaction>> emittedTxn{};
std::optional<hook::HookStateMap> stateMap = std::nullopt;
uint16_t changedStateCount = 0;
std::map<
ripple::uint256, // hook hash
std::map<
std::vector<uint8_t>, // hook param name
std::vector<uint8_t> // hook param value
>>
hookParamOverrides = {};
std::optional<std::map<std::vector<uint8_t>, std::vector<uint8_t>>>
hookParams = std::nullopt;
std::set<ripple::uint256> hookSkips = {};
hook_api::ExitType exitType = hook_api::ExitType::ROLLBACK;
std::string exitReason{""};
int64_t exitCode{-1};
uint64_t instructionCount{0};
bool hasCallback = false;
bool isCallback = false;
bool isStrong = false;
uint32_t wasmParam = 0;
uint32_t overrideCount = 0;
uint8_t hookChainPosition = 0;
bool foreignStateSetDisabled = false;
bool executeAgainAsWeak = false;
std::shared_ptr<STObject const> provisionalMeta = nullptr;
};
struct StubHookContext
{
std::map<uint32_t, hook::SlotEntry> slot{};
std::queue<uint32_t> slot_free{};
uint32_t slot_counter{0};
uint16_t emit_nonce_counter{0};
uint16_t ledger_nonce_counter{0};
int64_t expected_etxn_count{-1};
std::map<ripple::uint256, bool> nonce_used{};
uint32_t generation = 0;
uint64_t burden = 0;
std::map<uint32_t, uint32_t> guard_map{};
StubHookResult result = {};
std::optional<ripple::STObject> emitFailure = std::nullopt;
const hook::HookExecutor* module = 0;
};
hook::HookContext
makeStubHookContext(
ripple::ApplyContext& applyCtx,
ripple::AccountID const& hookAccount,
ripple::AccountID const& otxnAccount,
StubHookContext const& stubHookContext);
} // namespace jtx
} // namespace test
} // namespace ripple

View File

@@ -18,9 +18,7 @@
//==============================================================================
#include <ripple/app/hook/Enum.h>
#include <ripple/app/hook/applyHook.h>
#include <ripple/basics/contract.h>
#include <ripple/protocol/Keylet.h>
#include <ripple/protocol/jss.h>
#include <stdexcept>
#include <test/jtx/hook.h>
@@ -104,63 +102,6 @@ hso(std::string const& wasmHex, void (*f)(Json::Value& jv))
return jv;
}
hook::HookContext
makeStubHookContext(
ripple::ApplyContext& applyCtx,
ripple::AccountID const& hookAccount,
ripple::AccountID const& otxnAccount,
StubHookContext const& stubHookContext)
{
auto& result = stubHookContext.result;
auto stateMap = result.stateMap.value_or(hook::HookStateMap{});
auto hookParams = result.hookParams.value_or(
std::map<std::vector<uint8_t>, std::vector<uint8_t>>{});
return hook::HookContext{
.applyCtx = applyCtx,
.slot = stubHookContext.slot,
.slot_free = stubHookContext.slot_free,
.slot_counter = stubHookContext.slot_counter,
.emit_nonce_counter = stubHookContext.emit_nonce_counter,
.ledger_nonce_counter = stubHookContext.ledger_nonce_counter,
.expected_etxn_count = stubHookContext.expected_etxn_count,
.nonce_used = stubHookContext.nonce_used,
.generation = stubHookContext.generation,
.burden = stubHookContext.burden,
.guard_map = stubHookContext.guard_map,
.result =
{
.hookSetTxnID = result.hookSetTxnID,
.hookHash = result.hookHash,
.hookCanEmit = result.hookCanEmit,
.accountKeylet = keylet::account(hookAccount),
.hookKeylet = keylet::hook(hookAccount),
.account = hookAccount,
.otxnAccount = otxnAccount,
.hookNamespace = result.hookNamespace,
.emittedTxn = result.emittedTxn,
.stateMap = stateMap,
.changedStateCount = result.changedStateCount,
.hookParamOverrides = result.hookParamOverrides,
.hookParams = hookParams,
.hookSkips = result.hookSkips,
.exitType = result.exitType,
.exitReason = result.exitReason,
.exitCode = result.exitCode,
.instructionCount = result.instructionCount,
.hasCallback = result.hasCallback,
.isCallback = result.isCallback,
.isStrong = result.isStrong,
.wasmParam = result.wasmParam,
.overrideCount = result.overrideCount,
.hookChainPosition = result.hookChainPosition,
.foreignStateSetDisabled = result.foreignStateSetDisabled,
.executeAgainAsWeak = result.executeAgainAsWeak,
.provisionalMeta = result.provisionalMeta,
},
.emitFailure = stubHookContext.emitFailure,
.module = nullptr};
}
} // namespace jtx
} // namespace test
} // namespace ripple

View File

@@ -781,6 +781,22 @@ public:
auto const& hook = resp[jss::result][jss::account_objects][0u];
BEAST_EXPECT(hook[sfAccount.jsonName] == gw.human());
}
{
// Create a Cron
env(cron::set(gw),
cron::startTime(env.now().time_since_epoch().count() + 100),
cron::delay(100),
cron::repeat(200),
fee(XRP(1)));
env.close();
}
{
// Find the cron.
Json::Value const resp = acct_objs(gw, jss::cron);
BEAST_EXPECT(acct_objs_is_size(resp, 1));
auto const& cron = resp[jss::result][jss::account_objects][0u];
BEAST_EXPECT(cron[sfOwner.jsonName] == gw.human());
}
{
// See how "deletion_blockers_only" handles gw's directory.
Json::Value params;