From 1d6066127c243391a878cbfc7c03ba082f7ce1d8 Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Tue, 14 Oct 2025 11:16:49 +1100 Subject: [PATCH] whoops --- Builds/CMake/RippledCore.cmake | 2 + src/ripple/app/hook/impl/applyHook.cpp | 51 +++++++------ src/ripple/app/misc/impl/TxQ.cpp | 57 ++++++++++++++ src/ripple/app/tx/impl/Cron.cpp | 23 +++--- src/ripple/app/tx/impl/Cron.h | 1 - src/ripple/app/tx/impl/SetCron.cpp | 60 ++++++++------- src/ripple/app/tx/impl/SetCron.h | 1 - src/ripple/app/tx/impl/applySteps.cpp | 12 +++ src/ripple/protocol/Feature.h | 3 +- src/ripple/protocol/Indexes.h | 3 + src/ripple/protocol/LedgerFormats.h | 6 ++ src/ripple/protocol/SField.h | 3 + src/ripple/protocol/TER.h | 1 + src/ripple/protocol/TxFormats.h | 6 ++ src/ripple/protocol/impl/Feature.cpp | 1 + src/ripple/protocol/impl/Indexes.cpp | 23 ++++++ src/ripple/protocol/impl/LedgerFormats.cpp | 10 +++ src/ripple/protocol/impl/SField.cpp | 3 + src/ripple/protocol/impl/STTx.cpp | 2 +- src/ripple/protocol/impl/TxFormats.cpp | 16 ++++ src/ripple/protocol/jss.h | 88 +++++++++++----------- 21 files changed, 263 insertions(+), 109 deletions(-) diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 6b876997b..5a89dbd2f 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -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 diff --git a/src/ripple/app/hook/impl/applyHook.cpp b/src/ripple/app/hook/impl/applyHook.cpp index 5b5f28cf2..38502c8ed 100644 --- a/src/ripple/app/hook/impl/applyHook.cpp +++ b/src/ripple/app/hook/impl/applyHook.cpp @@ -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; diff --git a/src/ripple/app/misc/impl/TxQ.cpp b/src/ripple/app/misc/impl/TxQ.cpp index 72898ee7e..e78f30206 100644 --- a/src/ripple/app/misc/impl/TxQ.cpp +++ b/src/ripple/app/misc/impl/TxQ.cpp @@ -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 cronAccs; + + auto counter = 0; + while (++counter < 128 && klStart < klEnd) + { + std::optional 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(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(); + 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 diff --git a/src/ripple/app/tx/impl/Cron.cpp b/src/ripple/app/tx/impl/Cron.cpp index e86d92e5b..4e6e322a3 100644 --- a/src/ripple/app/tx/impl/Cron.cpp +++ b/src/ripple/app/tx/impl/Cron.cpp @@ -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); diff --git a/src/ripple/app/tx/impl/Cron.h b/src/ripple/app/tx/impl/Cron.h index 745c45361..893a63a51 100644 --- a/src/ripple/app/tx/impl/Cron.h +++ b/src/ripple/app/tx/impl/Cron.h @@ -50,7 +50,6 @@ public: TER doApply() override; - }; } // namespace ripple diff --git a/src/ripple/app/tx/impl/SetCron.cpp b/src/ripple/app/tx/impl/SetCron.cpp index 23531f69f..40d47b897 100644 --- a/src/ripple/app/tx/impl/SetCron.cpp +++ b/src/ripple/app/tx/impl/SetCron.cpp @@ -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(sleCron->getFieldU16(sfLedgerEntryType)) != ltCRON) + + if (safe_cast(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 sleCron = alreadyExists - ? view.peek(klCron) - : std::make_shared(klCron); - - // set the fields + std::shared_ptr sleCron = + alreadyExists ? view.peek(klCron) : std::make_shared(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) diff --git a/src/ripple/app/tx/impl/SetCron.h b/src/ripple/app/tx/impl/SetCron.h index 69988f3da..4a899fe57 100644 --- a/src/ripple/app/tx/impl/SetCron.h +++ b/src/ripple/app/tx/impl/SetCron.h @@ -50,7 +50,6 @@ public: TER doApply() override; - }; } // namespace ripple diff --git a/src/ripple/app/tx/impl/applySteps.cpp b/src/ripple/app/tx/impl/applySteps.cpp index aaf103153..2f6ea79d2 100644 --- a/src/ripple/app/tx/impl/applySteps.cpp +++ b/src/ripple/app/tx/impl/applySteps.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -181,6 +183,8 @@ invoke_preflight(PreflightContext const& ctx) case ttURITOKEN_CREATE_SELL_OFFER: case ttURITOKEN_CANCEL_SELL_OFFER: return invoke_preflight_helper(ctx); + case ttCRON_SET: + return invoke_preflight_helper(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(ctx); + case ttCRON_SET: + return invoke_preclaim(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}; diff --git a/src/ripple/protocol/Feature.h b/src/ripple/protocol/Feature.h index 6982b866c..844a5b536 100644 --- a/src/ripple/protocol/Feature.h +++ b/src/ripple/protocol/Feature.h @@ -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 diff --git a/src/ripple/protocol/Indexes.h b/src/ripple/protocol/Indexes.h index 2d8c32140..597a90414 100644 --- a/src/ripple/protocol/Indexes.h +++ b/src/ripple/protocol/Indexes.h @@ -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: diff --git a/src/ripple/protocol/LedgerFormats.h b/src/ripple/protocol/LedgerFormats.h index 0ecd94207..f87f7ae3f 100644 --- a/src/ripple/protocol/LedgerFormats.h +++ b/src/ripple/protocol/LedgerFormats.h @@ -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 diff --git a/src/ripple/protocol/SField.h b/src/ripple/protocol/SField.h index ae1c1007a..a75488310 100644 --- a/src/ripple/protocol/SField.h +++ b/src/ripple/protocol/SField.h @@ -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; diff --git a/src/ripple/protocol/TER.h b/src/ripple/protocol/TER.h index 31ad16a3c..76186e4e0 100644 --- a/src/ripple/protocol/TER.h +++ b/src/ripple/protocol/TER.h @@ -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, }; diff --git a/src/ripple/protocol/TxFormats.h b/src/ripple/protocol/TxFormats.h index e7fcc0d60..bea5905cb 100644 --- a/src/ripple/protocol/TxFormats.h +++ b/src/ripple/protocol/TxFormats.h @@ -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, diff --git a/src/ripple/protocol/impl/Feature.cpp b/src/ripple/protocol/impl/Feature.cpp index 9775b7d81..1642b55f4 100644 --- a/src/ripple/protocol/impl/Feature.cpp +++ b/src/ripple/protocol/impl/Feature.cpp @@ -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. diff --git a/src/ripple/protocol/impl/Indexes.cpp b/src/ripple/protocol/impl/Indexes.cpp index a03dc1ca2..a6bbc1eaa 100644 --- a/src/ripple/protocol/impl/Indexes.cpp +++ b/src/ripple/protocol/impl/Indexes.cpp @@ -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((timestamp >> 24) & 0xFFU); + h[9] = static_cast((timestamp >> 16) & 0xFFU); + h[10] = static_cast((timestamp >> 8) & 0xFFU); + h[11] = static_cast((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 diff --git a/src/ripple/protocol/impl/LedgerFormats.cpp b/src/ripple/protocol/impl/LedgerFormats.cpp index ef811667c..4b7864823 100644 --- a/src/ripple/protocol/impl/LedgerFormats.cpp +++ b/src/ripple/protocol/impl/LedgerFormats.cpp @@ -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 } diff --git a/src/ripple/protocol/impl/SField.cpp b/src/ripple/protocol/impl/SField.cpp index 6d205adbe..85b52ec2f 100644 --- a/src/ripple/protocol/impl/SField.cpp +++ b/src/ripple/protocol/impl/SField.cpp @@ -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); diff --git a/src/ripple/protocol/impl/STTx.cpp b/src/ripple/protocol/impl/STTx.cpp index cfa350381..2fbb19421 100644 --- a/src/ripple/protocol/impl/STTx.cpp +++ b/src/ripple/protocol/impl/STTx.cpp @@ -615,7 +615,7 @@ isPseudoTx(STObject const& tx) auto tt = safe_cast(*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 diff --git a/src/ripple/protocol/impl/TxFormats.cpp b/src/ripple/protocol/impl/TxFormats.cpp index 73bae26c3..7c3bae803 100644 --- a/src/ripple/protocol/impl/TxFormats.cpp +++ b/src/ripple/protocol/impl/TxFormats.cpp @@ -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& diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index 8bf510e2f..227fa88d3 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -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.