This commit is contained in:
Richard Holland
2025-10-14 11:16:49 +11:00
parent d3d5c757fe
commit 1d6066127c
21 changed files with 263 additions and 109 deletions

View File

@@ -444,6 +444,7 @@ target_sources (rippled PRIVATE
src/ripple/app/tx/impl/CreateCheck.cpp
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/DeleteAccount.cpp
src/ripple/app/tx/impl/DepositPreauth.cpp
src/ripple/app/tx/impl/Escrow.cpp
@@ -461,6 +462,7 @@ 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

@@ -75,6 +75,11 @@ getTransactionalStakeHolders(STTx const& tx, ReadView const& rv)
switch (tt)
{
case ttCRON: {
ADD_TSH(tx.getAccountID(sfOwner), tshWEAK);
break;
}
case ttREMIT: {
if (destAcc)
ADD_TSH(*destAcc, tshSTRONG);
@@ -1051,8 +1056,8 @@ hook::getHookCanEmit(
(hookObj.isFieldPresent(sfHookCanEmit)
? hookObj.getFieldH256(sfHookCanEmit)
: hookDef->isFieldPresent(sfHookCanEmit)
? hookDef->getFieldH256(sfHookCanEmit)
: defaultHookCanEmit);
? hookDef->getFieldH256(sfHookCanEmit)
: defaultHookCanEmit);
return hookCanEmit;
}
@@ -2938,10 +2943,10 @@ DEFINE_HOOK_FUNCTION(
ripple::Keylet kl = keylet_type == keylet_code::CHILD
? ripple::keylet::child(id)
: keylet_type == keylet_code::EMITTED_TXN
? ripple::keylet::emittedTxn(id)
: keylet_type == keylet_code::HOOK_DEFINITION
? ripple::keylet::hookDefinition(id)
: ripple::keylet::unchecked(id);
? ripple::keylet::emittedTxn(id)
: keylet_type == keylet_code::HOOK_DEFINITION
? ripple::keylet::hookDefinition(id)
: ripple::keylet::unchecked(id);
return serialize_keylet(kl, memory, write_ptr, write_len);
}
@@ -2970,10 +2975,10 @@ DEFINE_HOOK_FUNCTION(
ripple::Keylet kl = keylet_type == keylet_code::HOOK
? ripple::keylet::hook(id)
: keylet_type == keylet_code::SIGNERS
? ripple::keylet::signers(id)
: keylet_type == keylet_code::OWNER_DIR
? ripple::keylet::ownerDir(id)
: ripple::keylet::account(id);
? ripple::keylet::signers(id)
: keylet_type == keylet_code::OWNER_DIR
? ripple::keylet::ownerDir(id)
: ripple::keylet::account(id);
return serialize_keylet(kl, memory, write_ptr, write_len);
}
@@ -3013,10 +3018,10 @@ DEFINE_HOOK_FUNCTION(
ripple::Keylet kl = keylet_type == keylet_code::CHECK
? ripple::keylet::check(id, seq)
: keylet_type == keylet_code::ESCROW
? ripple::keylet::escrow(id, seq)
: keylet_type == keylet_code::NFT_OFFER
? ripple::keylet::nftoffer(id, seq)
: ripple::keylet::offer(id, seq);
? ripple::keylet::escrow(id, seq)
: keylet_type == keylet_code::NFT_OFFER
? ripple::keylet::nftoffer(id, seq)
: ripple::keylet::offer(id, seq);
return serialize_keylet(kl, memory, write_ptr, write_len);
}
@@ -3134,13 +3139,11 @@ DEFINE_HOOK_FUNCTION(
WRITE_WASM_MEMORY_AND_RETURN(
write_ptr,
write_len,
keylet_type == keylet_code::AMENDMENTS
? cAmendments.data()
: keylet_type == keylet_code::FEES
? cFees.data()
: keylet_type == keylet_code::NEGATIVE_UNL
? cNegativeUNL.data()
: cEmittedDir.data(),
keylet_type == keylet_code::AMENDMENTS ? cAmendments.data()
: keylet_type == keylet_code::FEES ? cFees.data()
: keylet_type == keylet_code::NEGATIVE_UNL
? cNegativeUNL.data()
: cEmittedDir.data(),
34,
memory,
memory_length);
@@ -6001,9 +6004,9 @@ DEFINE_HOOK_FUNCTION(
size_t free_count = hook_api::max_slots - hookCtx.slot.size();
size_t needed_count = slot_into_tx == 0 && slot_into_meta == 0
? 2
: slot_into_tx != 0 && slot_into_meta != 0 ? 0 : 1;
size_t needed_count = slot_into_tx == 0 && slot_into_meta == 0 ? 2
: slot_into_tx != 0 && slot_into_meta != 0 ? 0
: 1;
if (free_count < needed_count)
return NO_FREE_SLOTS;

View File

@@ -1477,6 +1477,63 @@ TxQ::accept(Application& app, OpenView& view)
}
}
// Inject cron transactions, if any
if (view.rules().enabled(featureCron))
{
uint32_t currentTime =
view.parentCloseTime().time_since_epoch().count();
uint256 klStart = keylet::cron(0, AccountID(beast::zero)).key;
uint256 const klEnd =
keylet::cron(currentTime + 1, AccountID(beast::zero)).key;
std::set<AccountID> cronAccs;
auto counter = 0;
while (++counter < 128 && klStart < klEnd)
{
std::optional<uint256 const> next = view.succ(klStart, klEnd);
if (!next.has_value())
break;
Keylet kl{ltANY, *next};
if (view.exists(kl))
{
auto sle = view.read(kl);
if (safe_cast<TxType>(sle->getFieldU16(sfLedgerEntryType)) ==
ltCRON)
{
// valid cron object, add it to the list
cronAccs.emplace(sle->getAccountID(sfOwner));
}
}
klStart = *next;
}
auto const seq = view.info().seq;
// insert Cron pseudos for each of the accs we need to ping
for (AccountID const& id : cronAccs)
{
STTx cronTx(ttCRON, [=](auto& obj) {
obj[sfAccount] = AccountID();
obj[sfLedgerSequence] = seq;
obj[sfOwner] = id;
});
uint256 txID = cronTx.getTransactionID();
auto s = std::make_shared<ripple::Serializer>();
cronTx.add(*s);
app.getHashRouter().setFlags(txID, SF_PRIVATE2);
app.getHashRouter().setFlags(txID, SF_EMITTED);
view.rawTxInsert(txID, std::move(s), nullptr);
ledgerChanged = true;
}
}
// Inject emitted transactions if any
if (view.rules().enabled(featureHooks))
do

View File

@@ -36,7 +36,6 @@ Cron::makeTxConsequences(PreflightContext const& ctx)
return TxConsequences{ctx.tx, TxConsequences::normal};
}
NotTEC
Cron::preflight(PreflightContext const& ctx)
{
@@ -76,10 +75,9 @@ Cron::preflight(PreflightContext const& ctx)
return temBAD_SEQUENCE;
}
return tesSUCCESS;
return tesSUCCESS;
}
TER
Cron::preclaim(PreclaimContext const& ctx)
{
@@ -112,7 +110,7 @@ Cron::doApply()
}
uint256 ptr = sle->getFieldH256(sfCron);
Keylet klOld {ltCRON, ptr};
Keylet klOld{ltCRON, ptr};
auto sleCron = view.peek(klOld);
if (!sleCron)
{
@@ -122,7 +120,7 @@ Cron::doApply()
uint32_t delay = sleCron->getFieldU32(sfDelaySeconds);
uint32_t recur = sleCron->getFieldU32(sfRepeatCount);
uint32_t currentTime = view.parentCloseTime().time_since_epoch().count();
// do all this sanity checking before we modify the ledger...
@@ -134,11 +132,12 @@ Cron::doApply()
// if there are further crons to do then a new one is created at the next
// time point
if (!view.dirRemove(keylet::ownerDir(id), (*sleCron)[sfOwnerNode], klOld, false))
if (!view.dirRemove(
keylet::ownerDir(id), (*sleCron)[sfOwnerNode], klOld, false))
return tefBAD_LEDGER;
view.erase(sleCron);
if (recur == 0)
{
// already at last execution, stop here
@@ -151,9 +150,11 @@ Cron::doApply()
// more executions remain, so create a new object
Keylet klCron = keylet::cron(afterTime, id);
// insert into owner dir, we don't need to check reserve because we've just deleted an object
auto const page = view.dirInsert(keylet::ownerDir(id), klCron, describeOwnerDir(id));
// insert into owner dir, we don't need to check reserve because we've just
// deleted an object
auto const page =
view.dirInsert(keylet::ownerDir(id), klCron, describeOwnerDir(id));
if (!page)
return tecDIR_FULL;
@@ -164,7 +165,7 @@ Cron::doApply()
sleCron->setAccountID(sfOwner, id);
sle->setFieldH256(sfCron, klCron.key);
view.insert(sleCron);
view.update(sle);

View File

@@ -50,7 +50,6 @@ public:
TER
doApply() override;
};
} // namespace ripple

View File

@@ -36,7 +36,6 @@ SetCron::makeTxConsequences(PreflightContext const& ctx)
return TxConsequences{ctx.tx, TxConsequences::normal};
}
NotTEC
SetCron::preflight(PreflightContext const& ctx)
{
@@ -60,17 +59,17 @@ SetCron::preflight(PreflightContext const& ctx)
// D- - Set Cron (once off) with Delay only (repat implicitly 0)
// -R - Invalid
// -- - Clear any existing cron (succeeds even if there isn't one)
if (tx.isFieldPresent(sfRepeatCount) && !tx.isFieldPresent(sfDelaySeconds))
{
JLOG(j.warn()) << "SetCron: DelaySeconds must also be specified when RepeatCount is present.";
JLOG(j.warn()) << "SetCron: DelaySeconds must also be specified when "
"RepeatCount is present.";
return temMALFORMED;
}
return preflight2(ctx);
}
TER
SetCron::preclaim(PreclaimContext const& ctx)
{
@@ -103,17 +102,19 @@ SetCron::preclaim(PreclaimContext const& ctx)
auto delay = ctx.tx.getFieldU32(sfDelaySeconds);
if (delay > 1209600UL /* 14 days in seconds */)
{
JLOG(j.debug()) << "SetCron: DelaySeconds was too high. (max 14 days in seconds).";
JLOG(j.debug())
<< "SetCron: DelaySeconds was too high. (max 14 days in seconds).";
return tecDELAY_OR_REPEAT_COUNT_TOO_LARGE;
}
if (!hasRepeat)
return tesSUCCESS;
auto recur = ctx.tx.getFieldU32(sfRepeatCount);
if (recur > 256)
{
JLOG(j.debug()) << "SetCron: RepeatCount too high. Limit is 256. Issue new SetCron to increase.";
JLOG(j.debug()) << "SetCron: RepeatCount too high. Limit is 256. Issue "
"new SetCron to increase.";
return tecDELAY_OR_REPEAT_COUNT_TOO_LARGE;
}
@@ -131,10 +132,11 @@ SetCron::doApply()
if (isDelete && tx.isFieldPresent(sfRepeatCount))
return tefINTERNAL;
// delay can be zero, in which case the cron will usually execute next ledger.
uint32_t delay {0};
uint32_t recur {0};
// delay can be zero, in which case the cron will usually execute next
// ledger.
uint32_t delay{0};
uint32_t recur{0};
if (!isDelete)
{
delay = tx.getFieldU32(sfDelaySeconds);
@@ -150,7 +152,6 @@ SetCron::doApply()
if (afterTime < currentTime)
return tefINTERNAL;
AccountID const& id = tx.getAccountID(sfAccount);
auto sle = view.peek(keylet::account(id));
if (!sle)
@@ -161,7 +162,7 @@ SetCron::doApply()
if (sle->isFieldPresent(sfCron))
{
Keylet klOld {ltCRON, sle->getFieldH256(sfCron)};
Keylet klOld{ltCRON, sle->getFieldH256(sfCron)};
auto sleCron = view.peek(klOld);
if (!sleCron)
@@ -169,14 +170,16 @@ SetCron::doApply()
JLOG(j_.warn()) << "SetCron: Cron object didn't exist.";
return tefBAD_LEDGER;
}
if (safe_cast<TxType>(sleCron->getFieldU16(sfLedgerEntryType)) != ltCRON)
if (safe_cast<TxType>(sleCron->getFieldU16(sfLedgerEntryType)) !=
ltCRON)
{
JLOG(j_.warn()) << "SetCron: sfCron pointed to non-cron object!!";
return tefBAD_LEDGER;
}
if (!view.dirRemove(keylet::ownerDir(id), (*sleCron)[sfOwnerNode], klOld, false))
if (!view.dirRemove(
keylet::ownerDir(id), (*sleCron)[sfOwnerNode], klOld, false))
{
JLOG(j_.warn()) << "SetCron: Ownerdir bad. " << id;
return tefBAD_LEDGER;
@@ -187,15 +190,16 @@ SetCron::doApply()
sle->makeFieldAbsent(sfCron);
}
// if the operation is a delete (no delay or recur specified then stop here.)
// if the operation is a delete (no delay or recur specified then stop
// here.)
if (isDelete)
{
view.update(sle);
return tesSUCCESS;
}
// execution to here means we're creating a new Cron object and adding it to the
// user's owner dir
// execution to here means we're creating a new Cron object and adding it to
// the user's owner dir
Keylet klCron = keylet::cron(afterTime, id);
@@ -211,26 +215,26 @@ SetCron::doApply()
if (afterFee > mPriorBalance || afterFee < reserve)
return tecINSUFFICIENT_RESERVE;
// add to owner dir
auto const page = view.dirInsert(keylet::ownerDir(id), klCron, describeOwnerDir(id));
auto const page =
view.dirInsert(keylet::ownerDir(id), klCron, describeOwnerDir(id));
if (!page)
return tecDIR_FULL;
adjustOwnerCount(view, sle, 1, j_);
}
std::shared_ptr<SLE> sleCron = alreadyExists
? view.peek(klCron)
: std::make_shared<SLE>(klCron);
// set the fields
std::shared_ptr<SLE> sleCron =
alreadyExists ? view.peek(klCron) : std::make_shared<SLE>(klCron);
// set the fields
sleCron->setFieldU32(sfDelaySeconds, delay);
sleCron->setFieldU32(sfRepeatCount, recur);
sleCron->setAccountID(sfOwner, id);
sle->setFieldH256(sfCron, klCron.key);
view.update(sle);
if (alreadyExists)

View File

@@ -50,7 +50,6 @@ public:
TER
doApply() override;
};
} // namespace ripple

View File

@@ -28,6 +28,7 @@
#include <ripple/app/tx/impl/CreateCheck.h>
#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/DeleteAccount.h>
#include <ripple/app/tx/impl/DepositPreauth.h>
#include <ripple/app/tx/impl/Escrow.h>
@@ -43,6 +44,7 @@
#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>
@@ -181,6 +183,8 @@ invoke_preflight(PreflightContext const& ctx)
case ttURITOKEN_CREATE_SELL_OFFER:
case ttURITOKEN_CANCEL_SELL_OFFER:
return invoke_preflight_helper<URIToken>(ctx);
case ttCRON_SET:
return invoke_preflight_helper<SetCron>(ctx);
default:
assert(false);
return {temUNKNOWN, TxConsequences{temUNKNOWN}};
@@ -306,6 +310,8 @@ invoke_preclaim(PreclaimContext const& ctx)
case ttURITOKEN_CREATE_SELL_OFFER:
case ttURITOKEN_CANCEL_SELL_OFFER:
return invoke_preclaim<URIToken>(ctx);
case ttCRON_SET:
return invoke_preclaim<SetCron>(ctx);
default:
assert(false);
return temUNKNOWN;
@@ -393,6 +399,8 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
case ttURITOKEN_CREATE_SELL_OFFER:
case ttURITOKEN_CANCEL_SELL_OFFER:
return URIToken::calculateBaseFee(view, tx);
case ttCRON_SET:
return SetCron::calculateBaseFee(view, tx);
default:
return XRPAmount{0};
}
@@ -586,6 +594,10 @@ invoke_apply(ApplyContext& ctx)
URIToken p(ctx);
return p();
}
case ttCRON_SET: {
SetCron p(ctx);
return p();
}
default:
assert(false);
return {temUNKNOWN, false};

View File

@@ -74,7 +74,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 = 85;
static constexpr std::size_t numFeatures = 86;
/** Amendments that this server supports and the default voting behavior.
Whether they are enabled depends on the Rules defined in the validated
@@ -373,6 +373,7 @@ extern uint256 const fixProvisionalDoubleThreading;
extern uint256 const featureClawback;
extern uint256 const featureDeepFreeze;
extern uint256 const featureIOUIssuerWeakTSH;
extern uint256 const featureCron;
} // namespace ripple

View File

@@ -297,6 +297,9 @@ import_vlseq(PublicKey const& key) noexcept;
Keylet
uritoken(AccountID const& issuer, Blob const& uri);
Keylet
cron(uint32_t timestamp, AccountID const& id);
} // namespace keylet
// Everything below is deprecated and should be removed in favor of keylets:

View File

@@ -58,6 +58,12 @@ enum LedgerEntryType : std::uint16_t
*/
ltACCOUNT_ROOT = 0x0061,
/** A ledger object representing a scheduled cron execution on an account.
\sa keylet::cron
*/
ltCRON = 0x0041,
/** A ledger object which contains a list of object identifiers.
\sa keylet::page, keylet::quality, keylet::book, keylet::next and

View File

@@ -410,6 +410,8 @@ extern SF_UINT32 const sfRewardLgrLast;
extern SF_UINT32 const sfFirstNFTokenSequence;
extern SF_UINT32 const sfImportSequence;
extern SF_UINT32 const sfXahauActivationLgrSeq;
extern SF_UINT32 const sfDelaySeconds;
extern SF_UINT32 const sfRepeatCount;
// 64-bit integers (common)
extern SF_UINT64 const sfIndexNext;
@@ -486,6 +488,7 @@ extern SF_UINT256 const sfURITokenID;
extern SF_UINT256 const sfGovernanceFlags;
extern SF_UINT256 const sfGovernanceMarks;
extern SF_UINT256 const sfEmittedTxnID;
extern SF_UINT256 const sfCron;
// currency amount (common)
extern SF_AMOUNT const sfAmount;

View File

@@ -343,6 +343,7 @@ enum TECcodes : TERUnderlyingType {
tecINSUF_RESERVE_SELLER = 187,
tecIMMUTABLE = 188,
tecTOO_MANY_REMARKS = 189,
tecDELAY_OR_REPEAT_COUNT_TOO_LARGE = 200,
tecLAST_POSSIBLE_ENTRY = 255,
};

View File

@@ -149,6 +149,12 @@ enum TxType : std::uint16_t
ttURITOKEN_CREATE_SELL_OFFER = 48,
ttURITOKEN_CANCEL_SELL_OFFER = 49,
/* A pseudo-txn alarm signal for invoking a hook, emitted by validators after alarm set conditions are met */
ttCRON = 92,
/* Sechedule an alarm for later */
ttCRON_SET = 93,
/* A note attaching transactor that allows the owner or issuer (on a object by object basis) to attach remarks */
ttREMARKS_SET = 94,

View File

@@ -479,6 +479,7 @@ REGISTER_FEATURE(Clawback, Supported::yes, VoteBehavior::De
REGISTER_FIX (fixProvisionalDoubleThreading, Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FEATURE(IOUIssuerWeakTSH, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FEATURE(Cron, Supported::yes, VoteBehavior::DefaultNo);
// The following amendments are obsolete, but must remain supported
// because they could potentially get enabled.

View File

@@ -72,6 +72,7 @@ enum class LedgerNameSpace : std::uint16_t {
URI_TOKEN = 'U',
IMPORT_VLSEQ = 'I',
UNL_REPORT = 'R',
CRON = 'A',
// No longer used or supported. Left here to reserve the space
// to avoid accidental reuse.
@@ -443,6 +444,28 @@ uritoken(AccountID const& issuer, Blob const& uri)
LedgerNameSpace::URI_TOKEN, issuer, Slice{uri.data(), uri.size()})};
}
Keylet
cron(uint32_t timestamp, AccountID const& id)
{
static const uint256 ns = indexHash(LedgerNameSpace::CRON);
uint8_t h[32];
// first 8 bytes are the namespacing
std::memcpy(h, ns.data(), 8);
// next 4 bytes are the timestamp in BE
h[8] = static_cast<uint8_t>((timestamp >> 24) & 0xFFU);
h[9] = static_cast<uint8_t>((timestamp >> 16) & 0xFFU);
h[10] = static_cast<uint8_t>((timestamp >> 8) & 0xFFU);
h[11] = static_cast<uint8_t>((timestamp >> 0) & 0xFFU);
// final 20 bytes are account ID
std::memcpy(h + 12, id.data(), 20);
return {ltCRON, uint256::fromVoid(h)};
}
} // namespace keylet
} // namespace ripple

View File

@@ -68,6 +68,7 @@ LedgerFormats::LedgerFormats()
{sfGovernanceMarks, soeOPTIONAL},
{sfAccountIndex, soeOPTIONAL},
{sfTouchCount, soeOPTIONAL},
{sfCron, soeOPTIONAL},
},
commonFields);
@@ -366,6 +367,15 @@ LedgerFormats::LedgerFormats()
},
commonFields);
add(jss::Cron,
ltCRON,
{
{sfOwner, soeREQUIRED},
{sfDelaySeconds, soeREQUIRED},
{sfRepeatCount, soeREQUIRED}
},
commonFields);
// clang-format on
}

View File

@@ -157,6 +157,8 @@ CONSTRUCT_TYPED_SFIELD(sfLockCount, "LockCount", UINT32,
CONSTRUCT_TYPED_SFIELD(sfFirstNFTokenSequence, "FirstNFTokenSequence", UINT32, 50);
CONSTRUCT_TYPED_SFIELD(sfRepeatCount, "RepeatCount", UINT32, 94);
CONSTRUCT_TYPED_SFIELD(sfDelaySeconds, "DelaySeconds", UINT32, 95);
CONSTRUCT_TYPED_SFIELD(sfXahauActivationLgrSeq, "XahauActivationLgrSeq",UINT32, 96);
CONSTRUCT_TYPED_SFIELD(sfImportSequence, "ImportSequence", UINT32, 97);
CONSTRUCT_TYPED_SFIELD(sfRewardTime, "RewardTime", UINT32, 98);
@@ -239,6 +241,7 @@ CONSTRUCT_TYPED_SFIELD(sfGovernanceFlags, "GovernanceFlags", UINT256,
CONSTRUCT_TYPED_SFIELD(sfGovernanceMarks, "GovernanceMarks", UINT256, 98);
CONSTRUCT_TYPED_SFIELD(sfEmittedTxnID, "EmittedTxnID", UINT256, 97);
CONSTRUCT_TYPED_SFIELD(sfHookCanEmit, "HookCanEmit", UINT256, 96);
CONSTRUCT_TYPED_SFIELD(sfCron, "Cron", UINT256, 95);
// currency amount (common)
CONSTRUCT_TYPED_SFIELD(sfAmount, "Amount", AMOUNT, 1);

View File

@@ -615,7 +615,7 @@ isPseudoTx(STObject const& tx)
auto tt = safe_cast<TxType>(*t);
return tt == ttAMENDMENT || tt == ttFEE || tt == ttUNL_MODIFY ||
tt == ttEMIT_FAILURE || tt == ttUNL_REPORT;
tt == ttEMIT_FAILURE || tt == ttUNL_REPORT || tt == ttCRON;
}
} // namespace ripple

View File

@@ -472,6 +472,22 @@ TxFormats::TxFormats()
{sfTicketSequence, soeOPTIONAL},
},
commonFields);
add(jss::Cron,
ttCRON,
{
{sfOwner, soeREQUIRED},
{sfLedgerSequence, soeREQUIRED},
},
commonFields);
add(jss::SetCron,
ttCRON_SET,
{
{sfDelaySeconds, soeOPTIONAL},
{sfRepeatCount, soeOPTIONAL},
},
commonFields);
}
TxFormats const&

View File

@@ -50,15 +50,17 @@ JSS(AccountSet); // transaction type.
JSS(Amendments); // ledger type.
JSS(Amount); // in: TransactionSign; field.
JSS(Authorize); // field
JSS(Alarm);
JSS(Blob);
JSS(Check); // ledger type.
JSS(CheckCancel); // transaction type.
JSS(CheckCash); // transaction type.
JSS(CheckCreate); // transaction type.
JSS(ClaimReward); // transaction type.
JSS(Clawback); // transaction type.
JSS(ClearFlag); // field.
JSS(CreateCode); // field.
JSS(Check); // ledger type.
JSS(CheckCancel); // transaction type.
JSS(CheckCash); // transaction type.
JSS(CheckCreate); // transaction type.
JSS(ClaimReward); // transaction type.
JSS(Clawback); // transaction type.
JSS(ClearFlag); // field.
JSS(CreateCode); // field.
JSS(Cron);
JSS(DeliverMin); // in: TransactionSign
JSS(DepositPreauth); // transaction and ledger type.
JSS(Destination); // in: TransactionSign; field.
@@ -95,40 +97,42 @@ JSS(isSigningField); // out: RPC server_definitions
JSS(isVLEncoded); // out: RPC server_definitions
JSS(Import);
JSS(ImportVLSequence);
JSS(Invalid); //
JSS(Invoke); // transaction type
JSS(InvoiceID); // field
JSS(LastLedgerSequence); // in: TransactionSign; field
JSS(LedgerHashes); // ledger type.
JSS(LimitAmount); // field.
JSS(NetworkID); // field.
JSS(NFTokenBurn); // transaction type.
JSS(NFTokenMint); // transaction type.
JSS(NFTokenOffer); // ledger type.
JSS(NFTokenAcceptOffer); // transaction type.
JSS(NFTokenCancelOffer); // transaction type.
JSS(NFTokenCreateOffer); // transaction type.
JSS(NFTokenPage); // ledger type.
JSS(Offer); // ledger type.
JSS(OfferCancel); // transaction type.
JSS(OfferCreate); // transaction type.
JSS(OfferSequence); // field.
JSS(Paths); // in/out: TransactionSign
JSS(PayChannel); // ledger type.
JSS(Payment); // transaction type.
JSS(PaymentChannelClaim); // transaction type.
JSS(PaymentChannelCreate); // transaction type.
JSS(PaymentChannelFund); // transaction type.
JSS(Remit); // transaction type.
JSS(RippleState); // ledger type.
JSS(SLE_hit_rate); // out: GetCounts.
JSS(SetFee); // transaction type.
JSS(SetRemarks); // transaction type
JSS(UNLModify); // transaction type.
JSS(UNLReport); // transaction type.
JSS(SettleDelay); // in: TransactionSign
JSS(SendMax); // in: TransactionSign
JSS(Sequence); // in/out: TransactionSign; field.
JSS(Invalid); //
JSS(Invoke); // transaction type
JSS(InvoiceID); // field
JSS(LastLedgerSequence); // in: TransactionSign; field
JSS(LedgerHashes); // ledger type.
JSS(LimitAmount); // field.
JSS(NetworkID); // field.
JSS(NFTokenBurn); // transaction type.
JSS(NFTokenMint); // transaction type.
JSS(NFTokenOffer); // ledger type.
JSS(NFTokenAcceptOffer); // transaction type.
JSS(NFTokenCancelOffer); // transaction type.
JSS(NFTokenCreateOffer); // transaction type.
JSS(NFTokenPage); // ledger type.
JSS(Offer); // ledger type.
JSS(OfferCancel); // transaction type.
JSS(OfferCreate); // transaction type.
JSS(OfferSequence); // field.
JSS(Paths); // in/out: TransactionSign
JSS(PayChannel); // ledger type.
JSS(Payment); // transaction type.
JSS(PaymentChannelClaim); // transaction type.
JSS(PaymentChannelCreate); // transaction type.
JSS(PaymentChannelFund); // transaction type.
JSS(Remit); // transaction type.
JSS(RippleState); // ledger type.
JSS(SLE_hit_rate); // out: GetCounts.
JSS(SetFee); // transaction type.
JSS(SetRemarks); // transaction type
JSS(UNLModify); // transaction type.
JSS(UNLReport); // transaction type.
JSS(SettleDelay); // in: TransactionSign
JSS(SendMax); // in: TransactionSign
JSS(Sequence); // in/out: TransactionSign; field.
JSS(SetAlarm);
JSS(SetCron);
JSS(SetFlag); // field.
JSS(SetRegularKey); // transaction type.
JSS(SetHook); // transaction type.