Merge branch 'dev' into hook-api-unittest

This commit is contained in:
tequ
2025-11-12 16:19:34 +09:00
13 changed files with 804 additions and 687 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

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

@@ -2197,17 +2197,6 @@ DEFINE_HOOK_FUNCTION(
if (write_len < 34)
return TOO_SMALL;
bool const v1 = applyCtx.view().rules().enabled(featureHooksUpdate1);
if (keylet_type == 0)
return INVALID_ARGUMENT;
auto const last =
v1 ? keylet_code::LAST_KLTYPE_V1 : keylet_code::LAST_KLTYPE_V0;
if (keylet_type > last)
return INVALID_ARGUMENT;
try
{
switch (keylet_type)
@@ -2308,7 +2297,8 @@ DEFINE_HOOK_FUNCTION(
return serialize_keylet(kl, memory, write_ptr, write_len);
}
// keylets that take 20 byte account id, and 4 byte uint
// keylets that take 20 byte account id, and (4 byte uint for 32
// byte hash)
case keylet_code::OFFER:
case keylet_code::CHECK:
case keylet_code::ESCROW:
@@ -2351,6 +2341,33 @@ DEFINE_HOOK_FUNCTION(
return serialize_keylet(kl, memory, write_ptr, write_len);
}
// keylets that take 20 byte account id, and 4 byte uint
case keylet_code::CRON: {
if (!applyCtx.view().rules().enabled(featureCron))
return INVALID_ARGUMENT;
if (a == 0 || b == 0)
return INVALID_ARGUMENT;
if (e != 0 || f != 0 || d != 0)
return INVALID_ARGUMENT;
uint32_t read_ptr = a, read_len = b;
if (NOT_IN_BOUNDS(read_ptr, read_len, memory_length))
return OUT_OF_BOUNDS;
if (read_len != 20)
return INVALID_ARGUMENT;
ripple::AccountID id = AccountID::fromVoid(memory + read_ptr);
uint32_t seq = c;
ripple::Keylet kl = ripple::keylet::cron(seq, id);
return serialize_keylet(kl, memory, write_ptr, write_len);
}
// keylets that take a 32 byte uint and an 8byte uint64
case keylet_code::PAGE: {
if (a == 0 || b == 0)
@@ -2398,6 +2415,9 @@ DEFINE_HOOK_FUNCTION(
}
case keylet_code::HOOK_STATE_DIR: {
if (!applyCtx.view().rules().enabled(featureHooksUpdate1))
return INVALID_ARGUMENT;
if (a == 0 || b == 0 || c == 0 || d == 0)
return INVALID_ARGUMENT;
@@ -2572,7 +2592,7 @@ DEFINE_HOOK_FUNCTION(
return INTERNAL_ERROR;
}
return NO_SUCH_KEYLET;
return INVALID_ARGUMENT;
HOOK_TEARDOWN();
}

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

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

@@ -11396,6 +11396,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 +11459,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 +11904,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]"];

File diff suppressed because it is too large Load Diff

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;