mirror of
				https://github.com/Xahau/xahaud.git
				synced 2025-11-04 02:35:48 +00:00 
			
		
		
		
	@@ -457,6 +457,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
 | 
			
		||||
@@ -474,6 +475,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
 | 
			
		||||
@@ -734,6 +736,7 @@ if (tests)
 | 
			
		||||
    src/test/app/BaseFee_test.cpp
 | 
			
		||||
    src/test/app/Check_test.cpp
 | 
			
		||||
    src/test/app/ClaimReward_test.cpp
 | 
			
		||||
    src/test/app/Cron_test.cpp
 | 
			
		||||
    src/test/app/Clawback_test.cpp
 | 
			
		||||
    src/test/app/CrossingLimits_test.cpp
 | 
			
		||||
    src/test/app/DeliverMin_test.cpp
 | 
			
		||||
@@ -898,6 +901,7 @@ if (tests)
 | 
			
		||||
    src/test/jtx/impl/amount.cpp
 | 
			
		||||
    src/test/jtx/impl/balance.cpp
 | 
			
		||||
    src/test/jtx/impl/check.cpp
 | 
			
		||||
    src/test/jtx/impl/cron.cpp
 | 
			
		||||
    src/test/jtx/impl/delivermin.cpp
 | 
			
		||||
    src/test/jtx/impl/deposit.cpp
 | 
			
		||||
    src/test/jtx/impl/envconfig.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);
 | 
			
		||||
 
 | 
			
		||||
@@ -1477,6 +1477,64 @@ 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;
 | 
			
		||||
        // include max 128 cron txns in the ledger
 | 
			
		||||
        while (++counter < 129 && 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<LedgerEntryType>(
 | 
			
		||||
                        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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										188
									
								
								src/ripple/app/tx/impl/Cron.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								src/ripple/app/tx/impl/Cron.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,188 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of rippled: https://github.com/ripple/rippled
 | 
			
		||||
    Copyright (c) 2024 XRPL-Labs
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and/or distribute this software for any
 | 
			
		||||
    purpose  with  or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL ,  DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#include <ripple/app/tx/impl/Cron.h>
 | 
			
		||||
#include <ripple/basics/Log.h>
 | 
			
		||||
#include <ripple/core/Config.h>
 | 
			
		||||
#include <ripple/ledger/View.h>
 | 
			
		||||
#include <ripple/protocol/Feature.h>
 | 
			
		||||
#include <ripple/protocol/Indexes.h>
 | 
			
		||||
#include <ripple/protocol/PublicKey.h>
 | 
			
		||||
#include <ripple/protocol/Quality.h>
 | 
			
		||||
#include <ripple/protocol/TxFlags.h>
 | 
			
		||||
#include <ripple/protocol/st.h>
 | 
			
		||||
 | 
			
		||||
namespace ripple {
 | 
			
		||||
 | 
			
		||||
TxConsequences
 | 
			
		||||
Cron::makeTxConsequences(PreflightContext const& ctx)
 | 
			
		||||
{
 | 
			
		||||
    return TxConsequences{ctx.tx, TxConsequences::normal};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
NotTEC
 | 
			
		||||
Cron::preflight(PreflightContext const& ctx)
 | 
			
		||||
{
 | 
			
		||||
    if (!ctx.rules.enabled(featureCron))
 | 
			
		||||
        return temDISABLED;
 | 
			
		||||
 | 
			
		||||
    auto const ret = preflight0(ctx);
 | 
			
		||||
    if (!isTesSuccess(ret))
 | 
			
		||||
        return ret;
 | 
			
		||||
 | 
			
		||||
    auto account = ctx.tx.getAccountID(sfAccount);
 | 
			
		||||
    if (account != beast::zero)
 | 
			
		||||
    {
 | 
			
		||||
        JLOG(ctx.j.warn()) << "Cron: Bad source id";
 | 
			
		||||
        return temBAD_SRC_ACCOUNT;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // No point in going any further if the transaction fee is malformed.
 | 
			
		||||
    auto const fee = ctx.tx.getFieldAmount(sfFee);
 | 
			
		||||
    if (!fee.native() || fee != beast::zero)
 | 
			
		||||
    {
 | 
			
		||||
        JLOG(ctx.j.warn()) << "Cron: invalid fee";
 | 
			
		||||
        return temBAD_FEE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!ctx.tx.getSigningPubKey().empty() || !ctx.tx.getSignature().empty() ||
 | 
			
		||||
        ctx.tx.isFieldPresent(sfSigners))
 | 
			
		||||
    {
 | 
			
		||||
        JLOG(ctx.j.warn()) << "Cron: Bad signature";
 | 
			
		||||
        return temBAD_SIGNATURE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (ctx.tx.getFieldU32(sfSequence) != 0 ||
 | 
			
		||||
        ctx.tx.isFieldPresent(sfPreviousTxnID))
 | 
			
		||||
    {
 | 
			
		||||
        JLOG(ctx.j.warn()) << "Cron: Bad sequence";
 | 
			
		||||
        return temBAD_SEQUENCE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return tesSUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TER
 | 
			
		||||
Cron::preclaim(PreclaimContext const& ctx)
 | 
			
		||||
{
 | 
			
		||||
    if (!ctx.view.rules().enabled(featureCron))
 | 
			
		||||
        return temDISABLED;
 | 
			
		||||
 | 
			
		||||
    return tesSUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TER
 | 
			
		||||
Cron::doApply()
 | 
			
		||||
{
 | 
			
		||||
    auto& view = ctx_.view();
 | 
			
		||||
    auto const& tx = ctx_.tx;
 | 
			
		||||
 | 
			
		||||
    AccountID const& id = tx.getAccountID(sfOwner);
 | 
			
		||||
 | 
			
		||||
    auto sle = view.peek(keylet::account(id));
 | 
			
		||||
    if (!sle)
 | 
			
		||||
    {
 | 
			
		||||
        // should never happen... but might in a race condition with acc delete
 | 
			
		||||
        JLOG(j_.warn()) << "Cron: sfOwner account missing. " << id;
 | 
			
		||||
        return tesSUCCESS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!sle->isFieldPresent(sfCron))
 | 
			
		||||
    {
 | 
			
		||||
        JLOG(j_.warn()) << "Cron: sfCron missing from account " << id;
 | 
			
		||||
        return tefINTERNAL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    uint256 ptr = sle->getFieldH256(sfCron);
 | 
			
		||||
    Keylet klOld{ltCRON, ptr};
 | 
			
		||||
    auto sleCron = view.peek(klOld);
 | 
			
		||||
    if (!sleCron)
 | 
			
		||||
    {
 | 
			
		||||
        JLOG(j_.warn()) << "Cron: Cron object missing for account " << id;
 | 
			
		||||
        return tesSUCCESS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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...
 | 
			
		||||
    uint32_t afterTime = currentTime + delay;
 | 
			
		||||
    if (afterTime < currentTime)
 | 
			
		||||
        return tefINTERNAL;
 | 
			
		||||
 | 
			
		||||
    // in all circumstances the Cron object is deleted...
 | 
			
		||||
    // 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))
 | 
			
		||||
        return tefBAD_LEDGER;
 | 
			
		||||
 | 
			
		||||
    view.erase(sleCron);
 | 
			
		||||
 | 
			
		||||
    if (recur == 0)
 | 
			
		||||
    {
 | 
			
		||||
        // already at last execution, stop here
 | 
			
		||||
        adjustOwnerCount(view, sle, -1, j_);
 | 
			
		||||
        sle->makeFieldAbsent(sfCron);
 | 
			
		||||
        view.update(sle);
 | 
			
		||||
        return tesSUCCESS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 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));
 | 
			
		||||
    if (!page)
 | 
			
		||||
        return tecDIR_FULL;
 | 
			
		||||
 | 
			
		||||
    sleCron = std::make_shared<SLE>(klCron);
 | 
			
		||||
 | 
			
		||||
    sleCron->setFieldU64(sfOwnerNode, *page);
 | 
			
		||||
    sleCron->setFieldU32(sfDelaySeconds, delay);
 | 
			
		||||
    sleCron->setFieldU32(sfRepeatCount, recur - 1);
 | 
			
		||||
    sleCron->setAccountID(sfOwner, id);
 | 
			
		||||
 | 
			
		||||
    sle->setFieldH256(sfCron, klCron.key);
 | 
			
		||||
 | 
			
		||||
    view.insert(sleCron);
 | 
			
		||||
    view.update(sle);
 | 
			
		||||
 | 
			
		||||
    return tesSUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
Cron::preCompute()
 | 
			
		||||
{
 | 
			
		||||
    assert(account_ == beast::zero);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
XRPAmount
 | 
			
		||||
Cron::calculateBaseFee(ReadView const& view, STTx const& tx)
 | 
			
		||||
{
 | 
			
		||||
    return XRPAmount{0};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace ripple
 | 
			
		||||
							
								
								
									
										60
									
								
								src/ripple/app/tx/impl/Cron.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/ripple/app/tx/impl/Cron.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of rippled: https://github.com/ripple/rippled
 | 
			
		||||
    Copyright (c) 2024 XRPL-Labs
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and/or distribute this software for any
 | 
			
		||||
    purpose  with  or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL ,  DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#ifndef RIPPLE_TX_CRON_H_INCLUDED
 | 
			
		||||
#define RIPPLE_TX_CRON_H_INCLUDED
 | 
			
		||||
 | 
			
		||||
#include <ripple/app/tx/impl/Transactor.h>
 | 
			
		||||
#include <ripple/basics/Log.h>
 | 
			
		||||
#include <ripple/core/Config.h>
 | 
			
		||||
#include <ripple/protocol/Indexes.h>
 | 
			
		||||
 | 
			
		||||
namespace ripple {
 | 
			
		||||
 | 
			
		||||
class Cron : public Transactor
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
 | 
			
		||||
 | 
			
		||||
    explicit Cron(ApplyContext& ctx) : Transactor(ctx)
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static XRPAmount
 | 
			
		||||
    calculateBaseFee(ReadView const& view, STTx const& tx);
 | 
			
		||||
 | 
			
		||||
    static TxConsequences
 | 
			
		||||
    makeTxConsequences(PreflightContext const& ctx);
 | 
			
		||||
 | 
			
		||||
    static NotTEC
 | 
			
		||||
    preflight(PreflightContext const& ctx);
 | 
			
		||||
 | 
			
		||||
    static TER
 | 
			
		||||
    preclaim(PreclaimContext const&);
 | 
			
		||||
 | 
			
		||||
    TER
 | 
			
		||||
    doApply() override;
 | 
			
		||||
 | 
			
		||||
    void
 | 
			
		||||
    preCompute() override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace ripple
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
@@ -491,6 +491,7 @@ LedgerEntryTypesMatch::visitEntry(
 | 
			
		||||
            case ltNFTOKEN_PAGE:
 | 
			
		||||
            case ltNFTOKEN_OFFER:
 | 
			
		||||
            case ltURI_TOKEN:
 | 
			
		||||
            case ltCRON:
 | 
			
		||||
            case ltIMPORT_VLSEQ:
 | 
			
		||||
            case ltUNL_REPORT:
 | 
			
		||||
                break;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										255
									
								
								src/ripple/app/tx/impl/SetCron.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										255
									
								
								src/ripple/app/tx/impl/SetCron.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,255 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of rippled: https://github.com/ripple/rippled
 | 
			
		||||
    Copyright (c) 2024 XRPL-Labs
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and/or distribute this software for any
 | 
			
		||||
    purpose  with  or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL ,  DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#include <ripple/app/tx/impl/SetCron.h>
 | 
			
		||||
#include <ripple/basics/Log.h>
 | 
			
		||||
#include <ripple/ledger/View.h>
 | 
			
		||||
#include <ripple/protocol/Feature.h>
 | 
			
		||||
#include <ripple/protocol/Indexes.h>
 | 
			
		||||
#include <ripple/protocol/TxFlags.h>
 | 
			
		||||
#include <ripple/protocol/st.h>
 | 
			
		||||
 | 
			
		||||
namespace ripple {
 | 
			
		||||
 | 
			
		||||
TxConsequences
 | 
			
		||||
SetCron::makeTxConsequences(PreflightContext const& ctx)
 | 
			
		||||
{
 | 
			
		||||
    return TxConsequences{ctx.tx, TxConsequences::normal};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
NotTEC
 | 
			
		||||
SetCron::preflight(PreflightContext const& ctx)
 | 
			
		||||
{
 | 
			
		||||
    if (!ctx.rules.enabled(featureCron))
 | 
			
		||||
        return temDISABLED;
 | 
			
		||||
 | 
			
		||||
    if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
 | 
			
		||||
        return ret;
 | 
			
		||||
 | 
			
		||||
    auto& tx = ctx.tx;
 | 
			
		||||
    auto& j = ctx.j;
 | 
			
		||||
 | 
			
		||||
    if (tx.getFlags() & tfCronSetMask)
 | 
			
		||||
    {
 | 
			
		||||
        JLOG(j.warn()) << "SetCron: Invalid flags set.";
 | 
			
		||||
        return temINVALID_FLAG;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // DelaySeconds (D), RepeatCount (R)
 | 
			
		||||
    // DR - Set Cron with Delay and Repeat
 | 
			
		||||
    // D- - Set Cron (once off) with Delay only (repat implicitly 0)
 | 
			
		||||
    // -R - Invalid
 | 
			
		||||
    // -- - Clear any existing cron (succeeds even if there isn't one) / with
 | 
			
		||||
    // tfCronUnset flag set
 | 
			
		||||
 | 
			
		||||
    bool const hasDelay = tx.isFieldPresent(sfDelaySeconds);
 | 
			
		||||
    bool const hasRepeat = tx.isFieldPresent(sfRepeatCount);
 | 
			
		||||
 | 
			
		||||
    if (tx.isFlag(tfCronUnset))
 | 
			
		||||
    {
 | 
			
		||||
        if (hasDelay || hasRepeat)
 | 
			
		||||
        {
 | 
			
		||||
            JLOG(j.debug()) << "SetCron: tfCronUnset flag cannot be used with "
 | 
			
		||||
                               "DelaySeconds or RepeatCount.";
 | 
			
		||||
            return temMALFORMED;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        if (!hasDelay)
 | 
			
		||||
        {
 | 
			
		||||
            JLOG(j.debug()) << "SetCron: DelaySeconds must be "
 | 
			
		||||
                               "specified to create a cron.";
 | 
			
		||||
            return temMALFORMED;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // check delay is not too high
 | 
			
		||||
        auto delay = tx.getFieldU32(sfDelaySeconds);
 | 
			
		||||
        if (delay > 31536000UL /* 365 days in seconds */)
 | 
			
		||||
        {
 | 
			
		||||
            JLOG(j.debug()) << "SetCron: DelaySeconds was too high. (max 365 "
 | 
			
		||||
                               "days in seconds).";
 | 
			
		||||
            return temMALFORMED;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // check repeat is not too high
 | 
			
		||||
        if (hasRepeat)
 | 
			
		||||
        {
 | 
			
		||||
            auto recur = tx.getFieldU32(sfRepeatCount);
 | 
			
		||||
            if (recur > 256)
 | 
			
		||||
            {
 | 
			
		||||
                JLOG(j.debug())
 | 
			
		||||
                    << "SetCron: RepeatCount too high. Limit is 256. Issue "
 | 
			
		||||
                       "new SetCron to increase.";
 | 
			
		||||
                return temMALFORMED;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return preflight2(ctx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TER
 | 
			
		||||
SetCron::preclaim(PreclaimContext const& ctx)
 | 
			
		||||
{
 | 
			
		||||
    return tesSUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TER
 | 
			
		||||
SetCron::doApply()
 | 
			
		||||
{
 | 
			
		||||
    auto& view = ctx_.view();
 | 
			
		||||
    auto const& tx = ctx_.tx;
 | 
			
		||||
 | 
			
		||||
    bool const isDelete = tx.isFlag(tfCronUnset);
 | 
			
		||||
 | 
			
		||||
    // delay can be zero, in which case the cron will usually execute next
 | 
			
		||||
    // ledger.
 | 
			
		||||
    uint32_t delay{0};
 | 
			
		||||
    uint32_t recur{0};
 | 
			
		||||
 | 
			
		||||
    if (!isDelete)
 | 
			
		||||
    {
 | 
			
		||||
        if (tx.isFieldPresent(sfDelaySeconds))
 | 
			
		||||
            delay = tx.getFieldU32(sfDelaySeconds);
 | 
			
		||||
        if (tx.isFieldPresent(sfRepeatCount))
 | 
			
		||||
            recur = tx.getFieldU32(sfRepeatCount);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    uint32_t currentTime = view.parentCloseTime().time_since_epoch().count();
 | 
			
		||||
 | 
			
		||||
    // do all this sanity checking before we modify the ledger...
 | 
			
		||||
    // even for a delete operation this will fall through without incident
 | 
			
		||||
 | 
			
		||||
    uint32_t afterTime = currentTime + delay;
 | 
			
		||||
    if (afterTime < currentTime)
 | 
			
		||||
        return tefINTERNAL;
 | 
			
		||||
 | 
			
		||||
    AccountID const& id = tx.getAccountID(sfAccount);
 | 
			
		||||
    auto sle = view.peek(keylet::account(id));
 | 
			
		||||
    if (!sle)
 | 
			
		||||
        return tefINTERNAL;
 | 
			
		||||
 | 
			
		||||
    // in all cases whatsoever, this transaction will delete an existing
 | 
			
		||||
    // old cron object and return the owner reserve to the owner.
 | 
			
		||||
 | 
			
		||||
    if (sle->isFieldPresent(sfCron))
 | 
			
		||||
    {
 | 
			
		||||
        Keylet klOld{ltCRON, sle->getFieldH256(sfCron)};
 | 
			
		||||
 | 
			
		||||
        auto sleCron = view.peek(klOld);
 | 
			
		||||
        if (!sleCron)
 | 
			
		||||
        {
 | 
			
		||||
            JLOG(j_.warn()) << "SetCron: 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!!";
 | 
			
		||||
            return tefBAD_LEDGER;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!view.dirRemove(
 | 
			
		||||
                keylet::ownerDir(id), (*sleCron)[sfOwnerNode], klOld, false))
 | 
			
		||||
        {
 | 
			
		||||
            JLOG(j_.warn()) << "SetCron: Ownerdir bad. " << id;
 | 
			
		||||
            return tefBAD_LEDGER;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        view.erase(sleCron);
 | 
			
		||||
        adjustOwnerCount(view, sle, -1, j_);
 | 
			
		||||
        sle->makeFieldAbsent(sfCron);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 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
 | 
			
		||||
 | 
			
		||||
    Keylet klCron = keylet::cron(afterTime, id);
 | 
			
		||||
 | 
			
		||||
    std::shared_ptr<SLE> sleCron = std::make_shared<SLE>(klCron);
 | 
			
		||||
 | 
			
		||||
    STAmount const reserve{
 | 
			
		||||
        view.fees().accountReserve(sle->getFieldU32(sfOwnerCount) + 1)};
 | 
			
		||||
 | 
			
		||||
    STAmount const afterFee =
 | 
			
		||||
        mPriorBalance - ctx_.tx.getFieldAmount(sfFee).xrp();
 | 
			
		||||
 | 
			
		||||
    if (afterFee > mPriorBalance || afterFee < reserve)
 | 
			
		||||
        return tecINSUFFICIENT_RESERVE;
 | 
			
		||||
 | 
			
		||||
    // add to owner dir
 | 
			
		||||
    auto const page =
 | 
			
		||||
        view.dirInsert(keylet::ownerDir(id), klCron, describeOwnerDir(id));
 | 
			
		||||
    if (!page)
 | 
			
		||||
        return tecDIR_FULL;
 | 
			
		||||
 | 
			
		||||
    sleCron->setFieldU64(sfOwnerNode, *page);
 | 
			
		||||
 | 
			
		||||
    adjustOwnerCount(view, sle, 1, j_);
 | 
			
		||||
 | 
			
		||||
    // set the fields
 | 
			
		||||
    sleCron->setFieldU32(sfDelaySeconds, delay);
 | 
			
		||||
    sleCron->setFieldU32(sfRepeatCount, recur);
 | 
			
		||||
    sleCron->setAccountID(sfOwner, id);
 | 
			
		||||
 | 
			
		||||
    sle->setFieldH256(sfCron, klCron.key);
 | 
			
		||||
 | 
			
		||||
    view.update(sle);
 | 
			
		||||
 | 
			
		||||
    view.insert(sleCron);
 | 
			
		||||
 | 
			
		||||
    return tesSUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
XRPAmount
 | 
			
		||||
SetCron::calculateBaseFee(ReadView const& view, STTx const& tx)
 | 
			
		||||
{
 | 
			
		||||
    auto const baseFee = Transactor::calculateBaseFee(view, tx);
 | 
			
		||||
 | 
			
		||||
    auto const hasRepeat = tx.isFieldPresent(sfRepeatCount);
 | 
			
		||||
 | 
			
		||||
    if (tx.isFlag(tfCronUnset))
 | 
			
		||||
        // delete cron
 | 
			
		||||
        return baseFee;
 | 
			
		||||
 | 
			
		||||
    // factor a cost based on the total number of txns expected
 | 
			
		||||
    // for RepeatCount of 0 we have this txn (SetCron) 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 =
 | 
			
		||||
        hasRepeat ? tx.getFieldU32(sfRepeatCount) + 1 : 1;
 | 
			
		||||
    auto const additionalFee = baseFee * additionalExpectedExecutions;
 | 
			
		||||
 | 
			
		||||
    if (baseFee + additionalFee < baseFee)
 | 
			
		||||
        return baseFee;
 | 
			
		||||
 | 
			
		||||
    return baseFee + additionalFee;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace ripple
 | 
			
		||||
							
								
								
									
										56
									
								
								src/ripple/app/tx/impl/SetCron.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/ripple/app/tx/impl/SetCron.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of rippled: https://github.com/ripple/rippled
 | 
			
		||||
    Copyright (c) 2024 XRPL-Labs
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and/or distribute this software for any
 | 
			
		||||
    purpose  with  or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL ,  DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#ifndef RIPPLE_TX_SETCRON_H_INCLUDED
 | 
			
		||||
#define RIPPLE_TX_SETCRON_H_INCLUDED
 | 
			
		||||
 | 
			
		||||
#include <ripple/app/tx/impl/Transactor.h>
 | 
			
		||||
#include <ripple/basics/Log.h>
 | 
			
		||||
#include <ripple/protocol/Indexes.h>
 | 
			
		||||
 | 
			
		||||
namespace ripple {
 | 
			
		||||
 | 
			
		||||
class SetCron : public Transactor
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
 | 
			
		||||
 | 
			
		||||
    explicit SetCron(ApplyContext& ctx) : Transactor(ctx)
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static XRPAmount
 | 
			
		||||
    calculateBaseFee(ReadView const& view, STTx const& tx);
 | 
			
		||||
 | 
			
		||||
    static TxConsequences
 | 
			
		||||
    makeTxConsequences(PreflightContext const& ctx);
 | 
			
		||||
 | 
			
		||||
    static NotTEC
 | 
			
		||||
    preflight(PreflightContext const& ctx);
 | 
			
		||||
 | 
			
		||||
    static TER
 | 
			
		||||
    preclaim(PreclaimContext const&);
 | 
			
		||||
 | 
			
		||||
    TER
 | 
			
		||||
    doApply() override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace ripple
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
@@ -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,10 @@ 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);
 | 
			
		||||
        case ttCRON:
 | 
			
		||||
            return invoke_preflight_helper<Cron>(ctx);
 | 
			
		||||
        default:
 | 
			
		||||
            assert(false);
 | 
			
		||||
            return {temUNKNOWN, TxConsequences{temUNKNOWN}};
 | 
			
		||||
@@ -306,6 +312,10 @@ 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);
 | 
			
		||||
        case ttCRON:
 | 
			
		||||
            return invoke_preclaim<Cron>(ctx);
 | 
			
		||||
        default:
 | 
			
		||||
            assert(false);
 | 
			
		||||
            return temUNKNOWN;
 | 
			
		||||
@@ -393,6 +403,10 @@ 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);
 | 
			
		||||
        case ttCRON:
 | 
			
		||||
            return Cron::calculateBaseFee(view, tx);
 | 
			
		||||
        default:
 | 
			
		||||
            return XRPAmount{0};
 | 
			
		||||
    }
 | 
			
		||||
@@ -586,6 +600,14 @@ invoke_apply(ApplyContext& ctx)
 | 
			
		||||
            URIToken p(ctx);
 | 
			
		||||
            return p();
 | 
			
		||||
        }
 | 
			
		||||
        case ttCRON_SET: {
 | 
			
		||||
            SetCron p(ctx);
 | 
			
		||||
            return p();
 | 
			
		||||
        }
 | 
			
		||||
        case ttCRON: {
 | 
			
		||||
            Cron p(ctx);
 | 
			
		||||
            return p();
 | 
			
		||||
        }
 | 
			
		||||
        default:
 | 
			
		||||
            assert(false);
 | 
			
		||||
            return {temUNKNOWN, false};
 | 
			
		||||
 
 | 
			
		||||
@@ -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 = 86;
 | 
			
		||||
static constexpr std::size_t numFeatures = 87;
 | 
			
		||||
 | 
			
		||||
/** 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;
 | 
			
		||||
extern uint256 const fixInvalidTxFlags;
 | 
			
		||||
 | 
			
		||||
}  // namespace ripple
 | 
			
		||||
 
 | 
			
		||||
@@ -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:
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -200,6 +200,12 @@ constexpr std::uint32_t const tfImmutable = 1;
 | 
			
		||||
// Clawback flags:
 | 
			
		||||
constexpr std::uint32_t const tfClawbackMask = ~tfUniversal;
 | 
			
		||||
 | 
			
		||||
// CronSet Flags:
 | 
			
		||||
enum CronSetFlags : uint32_t {
 | 
			
		||||
    tfCronUnset = 0x00000001,
 | 
			
		||||
};
 | 
			
		||||
constexpr std::uint32_t const tfCronSetMask = ~(tfUniversal | tfCronUnset);
 | 
			
		||||
 | 
			
		||||
// clang-format on
 | 
			
		||||
 | 
			
		||||
}  // namespace ripple
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
REGISTER_FIX    (fixInvalidTxFlags,             Supported::yes, VoteBehavior::DefaultYes);
 | 
			
		||||
 | 
			
		||||
// The following amendments are obsolete, but must remain supported
 | 
			
		||||
 
 | 
			
		||||
@@ -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,51 @@ uritoken(AccountID const& issuer, Blob const& uri)
 | 
			
		||||
            LedgerNameSpace::URI_TOKEN, issuer, Slice{uri.data(), uri.size()})};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Constructs an ordered CRON keylet (32 bytes):
 | 
			
		||||
//   [8-byte namespace][4-byte timestamp (big-endian, seconds)][20-byte
 | 
			
		||||
//   AccountID]
 | 
			
		||||
//
 | 
			
		||||
// Properties
 | 
			
		||||
// - Namespacing: first 8 bytes are the most-significant bytes of
 | 
			
		||||
// indexHash(LedgerNameSpace::CRON).
 | 
			
		||||
// - Uniqueness (per ts,acct): exactly ONE cron per (timestamp, AccountID).
 | 
			
		||||
// Insert is upsert (last-write-wins).
 | 
			
		||||
//   This is fine because we only ever allow one cron per account at a time—if
 | 
			
		||||
//   the same (ts,acct) is written, it’s simply an update to that single entry
 | 
			
		||||
//   (idempotent; no duplicate leaves).
 | 
			
		||||
// - Iteration order: chronological by timestamp (BE), then by raw AccountID
 | 
			
		||||
// bytes.
 | 
			
		||||
//   NOTE: raw AccountID ordering may bias priority; consider hashing AccountID
 | 
			
		||||
//   for uniform per-timestamp spread.
 | 
			
		||||
// - Expected accidental prefix collisions (foreign objects sharing the 8-byte
 | 
			
		||||
// namespace): n / 2^64,
 | 
			
		||||
//   assuming uniform high-64-bit distribution of other objects.
 | 
			
		||||
//   Examples: 100M → ~5.4e-12, 1B → ~5.4e-11, 10B → ~5.4e-10, 100B → ~5.4e-9
 | 
			
		||||
//   (negligible).
 | 
			
		||||
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);
 | 
			
		||||
 | 
			
		||||
    const uint256 accHash = indexHash(LedgerNameSpace::CRON, timestamp, id);
 | 
			
		||||
 | 
			
		||||
    // final 20 bytes are account ID
 | 
			
		||||
    std::memcpy(h + 12, accHash.cdata(), 20);
 | 
			
		||||
 | 
			
		||||
    return {ltCRON, uint256::fromVoid(h)};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace keylet
 | 
			
		||||
 | 
			
		||||
}  // namespace ripple
 | 
			
		||||
 
 | 
			
		||||
@@ -68,6 +68,7 @@ LedgerFormats::LedgerFormats()
 | 
			
		||||
            {sfGovernanceMarks,      soeOPTIONAL},
 | 
			
		||||
            {sfAccountIndex,         soeOPTIONAL},
 | 
			
		||||
            {sfTouchCount,           soeOPTIONAL},
 | 
			
		||||
            {sfCron,                 soeOPTIONAL},
 | 
			
		||||
        },
 | 
			
		||||
        commonFields);
 | 
			
		||||
 | 
			
		||||
@@ -366,6 +367,16 @@ LedgerFormats::LedgerFormats()
 | 
			
		||||
        },
 | 
			
		||||
        commonFields);
 | 
			
		||||
 | 
			
		||||
    add(jss::Cron,
 | 
			
		||||
        ltCRON,
 | 
			
		||||
        {
 | 
			
		||||
            {sfOwner,                soeREQUIRED},
 | 
			
		||||
            {sfDelaySeconds,         soeREQUIRED},
 | 
			
		||||
            {sfRepeatCount,          soeREQUIRED},
 | 
			
		||||
            {sfOwnerNode,            soeREQUIRED},
 | 
			
		||||
        },
 | 
			
		||||
        commonFields);
 | 
			
		||||
 | 
			
		||||
    // clang-format on
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -472,6 +472,22 @@ TxFormats::TxFormats()
 | 
			
		||||
            {sfTicketSequence, soeOPTIONAL},
 | 
			
		||||
        },
 | 
			
		||||
        commonFields);
 | 
			
		||||
 | 
			
		||||
    add(jss::Cron,
 | 
			
		||||
        ttCRON,
 | 
			
		||||
        {
 | 
			
		||||
            {sfOwner, soeREQUIRED},
 | 
			
		||||
            {sfLedgerSequence, soeREQUIRED},
 | 
			
		||||
        },
 | 
			
		||||
        commonFields);
 | 
			
		||||
 | 
			
		||||
    add(jss::CronSet,
 | 
			
		||||
        ttCRON_SET,
 | 
			
		||||
        {
 | 
			
		||||
            {sfDelaySeconds, soeOPTIONAL},
 | 
			
		||||
            {sfRepeatCount, soeOPTIONAL},
 | 
			
		||||
        },
 | 
			
		||||
        commonFields);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TxFormats const&
 | 
			
		||||
 
 | 
			
		||||
@@ -51,14 +51,16 @@ JSS(Amendments);     // ledger type.
 | 
			
		||||
JSS(Amount);         // in: TransactionSign; field.
 | 
			
		||||
JSS(Authorize);      // field
 | 
			
		||||
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(CronSet);
 | 
			
		||||
JSS(DeliverMin);         // in: TransactionSign
 | 
			
		||||
JSS(DepositPreauth);     // transaction and ledger type.
 | 
			
		||||
JSS(Destination);        // in: TransactionSign; field.
 | 
			
		||||
 
 | 
			
		||||
@@ -423,6 +423,7 @@ private:
 | 
			
		||||
        addFlagsToJson<NFTokenMintFlags>(ret, "NFTokenMint");
 | 
			
		||||
        addFlagsToJson<NFTokenCreateOfferFlags>(ret, "NFTokenCreateOffer");
 | 
			
		||||
        addFlagsToJson<ClaimRewardFlags>(ret, "ClaimReward");
 | 
			
		||||
        addFlagsToJson<CronSetFlags>(ret, "CronSet");
 | 
			
		||||
        struct FlagData
 | 
			
		||||
        {
 | 
			
		||||
            std::string name;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										461
									
								
								src/test/app/Cron_test.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										461
									
								
								src/test/app/Cron_test.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,461 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of rippled: https://github.com/ripple/rippled
 | 
			
		||||
    Copyright (c) 2025 XRPL Labs
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and/or distribute this software for any
 | 
			
		||||
    purpose  with  or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL ,  DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#include <ripple/app/ledger/LedgerMaster.h>
 | 
			
		||||
#include <ripple/protocol/Feature.h>
 | 
			
		||||
#include <ripple/protocol/jss.h>
 | 
			
		||||
#include <test/jtx.h>
 | 
			
		||||
 | 
			
		||||
namespace ripple {
 | 
			
		||||
namespace test {
 | 
			
		||||
struct Cron_test : public beast::unit_test::suite
 | 
			
		||||
{
 | 
			
		||||
    void
 | 
			
		||||
    testEnabled(FeatureBitset features)
 | 
			
		||||
    {
 | 
			
		||||
        testcase("enabled");
 | 
			
		||||
        using namespace jtx;
 | 
			
		||||
        using namespace std::literals::chrono_literals;
 | 
			
		||||
 | 
			
		||||
        // setup env
 | 
			
		||||
        auto const alice = Account("alice");
 | 
			
		||||
        auto const issuer = Account("issuer");
 | 
			
		||||
 | 
			
		||||
        for (bool const withCron : {false, true})
 | 
			
		||||
        {
 | 
			
		||||
            auto const amend = withCron ? features : features - featureCron;
 | 
			
		||||
            Env env{*this, amend};
 | 
			
		||||
 | 
			
		||||
            env.fund(XRP(1000), alice, issuer);
 | 
			
		||||
            env.close();
 | 
			
		||||
 | 
			
		||||
            auto const expectResult =
 | 
			
		||||
                withCron ? ter(tesSUCCESS) : ter(temDISABLED);
 | 
			
		||||
 | 
			
		||||
            auto tx = cron::set(alice);
 | 
			
		||||
            // CLAIM
 | 
			
		||||
            env(cron::set(alice), cron::delay(100), fee(XRP(1)), expectResult);
 | 
			
		||||
            env.close();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void
 | 
			
		||||
    testFee(FeatureBitset features)
 | 
			
		||||
    {
 | 
			
		||||
        testcase("fee");
 | 
			
		||||
        using namespace jtx;
 | 
			
		||||
        using namespace std::literals::chrono_literals;
 | 
			
		||||
 | 
			
		||||
        auto const alice = Account("alice");
 | 
			
		||||
        Env env{*this, features | featureCron};
 | 
			
		||||
 | 
			
		||||
        auto const baseFee = env.current()->fees().base;
 | 
			
		||||
 | 
			
		||||
        env.fund(XRP(1000), alice);
 | 
			
		||||
        env.close();
 | 
			
		||||
 | 
			
		||||
        // create with RepeatCount
 | 
			
		||||
        auto expected = baseFee * 2 + baseFee * 256;
 | 
			
		||||
        env(cron::set(alice),
 | 
			
		||||
            cron::delay(356 * 24 * 60 * 60),
 | 
			
		||||
            cron::repeat(256),
 | 
			
		||||
            fee(expected - 1),
 | 
			
		||||
            ter(telINSUF_FEE_P));
 | 
			
		||||
        env.close();
 | 
			
		||||
 | 
			
		||||
        env(cron::set(alice),
 | 
			
		||||
            cron::delay(356 * 24 * 60 * 60),
 | 
			
		||||
            cron::repeat(256),
 | 
			
		||||
            fee(expected),
 | 
			
		||||
            ter(tesSUCCESS));
 | 
			
		||||
        env.close();
 | 
			
		||||
 | 
			
		||||
        // create with no RepeatCount
 | 
			
		||||
        expected = baseFee * 2;
 | 
			
		||||
        env(cron::set(alice),
 | 
			
		||||
            cron::delay(356 * 24 * 60 * 60),
 | 
			
		||||
            fee(expected - 1),
 | 
			
		||||
            ter(telINSUF_FEE_P));
 | 
			
		||||
        env.close();
 | 
			
		||||
 | 
			
		||||
        env(cron::set(alice),
 | 
			
		||||
            cron::delay(356 * 24 * 60 * 60),
 | 
			
		||||
            fee(expected),
 | 
			
		||||
            ter(tesSUCCESS));
 | 
			
		||||
        env.close();
 | 
			
		||||
 | 
			
		||||
        // delete
 | 
			
		||||
        expected = baseFee;
 | 
			
		||||
        env(cron::set(alice),
 | 
			
		||||
            txflags(tfCronUnset),
 | 
			
		||||
            fee(expected - 1),
 | 
			
		||||
            ter(telINSUF_FEE_P));
 | 
			
		||||
        env.close();
 | 
			
		||||
 | 
			
		||||
        env(cron::set(alice),
 | 
			
		||||
            txflags(tfCronUnset),
 | 
			
		||||
            fee(expected),
 | 
			
		||||
            ter(tesSUCCESS));
 | 
			
		||||
        env.close();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void
 | 
			
		||||
    testInvalidPreflight(FeatureBitset features)
 | 
			
		||||
    {
 | 
			
		||||
        testcase("invalid preflight");
 | 
			
		||||
        using namespace test::jtx;
 | 
			
		||||
        using namespace std::literals;
 | 
			
		||||
 | 
			
		||||
        auto const alice = Account("alice");
 | 
			
		||||
 | 
			
		||||
        test::jtx::Env env{
 | 
			
		||||
            *this, network::makeNetworkConfig(21337), features | featureCron};
 | 
			
		||||
 | 
			
		||||
        env.fund(XRP(1000), alice);
 | 
			
		||||
        env.close();
 | 
			
		||||
 | 
			
		||||
        //----------------------------------------------------------------------
 | 
			
		||||
        // preflight
 | 
			
		||||
 | 
			
		||||
        // temINVALID_FLAG
 | 
			
		||||
        {
 | 
			
		||||
            env(cron::set(alice), txflags(tfClose), ter(temINVALID_FLAG));
 | 
			
		||||
            env(cron::set(alice),
 | 
			
		||||
                txflags(tfUniversalMask),
 | 
			
		||||
                ter(temINVALID_FLAG));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // temMALFORMED
 | 
			
		||||
        {
 | 
			
		||||
            // Invalid both DelaySeconds and RepeatCount are not specified
 | 
			
		||||
            env(cron::set(alice), ter(temMALFORMED));
 | 
			
		||||
 | 
			
		||||
            // Invalid DelaySeconds and RepeatCount combination
 | 
			
		||||
            // (only RepeatCount specified)
 | 
			
		||||
            env(cron::set(alice), cron::repeat(256), ter(temMALFORMED));
 | 
			
		||||
 | 
			
		||||
            // Invalid DelaySeconds
 | 
			
		||||
            env(cron::set(alice),
 | 
			
		||||
                cron::delay(365 * 24 * 60 * 60 + 1),
 | 
			
		||||
                cron::repeat(256),
 | 
			
		||||
                ter(temMALFORMED));
 | 
			
		||||
 | 
			
		||||
            // Invalid RepeatCount
 | 
			
		||||
            env(cron::set(alice),
 | 
			
		||||
                cron::delay(365 * 24 * 60 * 60),
 | 
			
		||||
                cron::repeat(257),
 | 
			
		||||
                ter(temMALFORMED));
 | 
			
		||||
 | 
			
		||||
            // Invalid tfCronUnset flag
 | 
			
		||||
            env(cron::set(alice),
 | 
			
		||||
                cron::delay(365 * 24 * 60 * 60),
 | 
			
		||||
                txflags(tfCronUnset),
 | 
			
		||||
                ter(temMALFORMED));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void
 | 
			
		||||
    testInvalidPreclaim(FeatureBitset features)
 | 
			
		||||
    {
 | 
			
		||||
        testcase("invalid preclaim");
 | 
			
		||||
        using namespace test::jtx;
 | 
			
		||||
        using namespace std::literals;
 | 
			
		||||
 | 
			
		||||
        auto const alice = Account("alice");
 | 
			
		||||
        Env env{*this, features | featureCron};
 | 
			
		||||
 | 
			
		||||
        // there is no check in preclaim
 | 
			
		||||
        BEAST_EXPECT(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void
 | 
			
		||||
    testDoApply(FeatureBitset features)
 | 
			
		||||
    {
 | 
			
		||||
        testcase("doApply");
 | 
			
		||||
        using namespace jtx;
 | 
			
		||||
        using namespace std::literals::chrono_literals;
 | 
			
		||||
        auto const alice = Account("alice");
 | 
			
		||||
        Env env{*this, features | featureCron};
 | 
			
		||||
 | 
			
		||||
        env.fund(XRP(1000), alice);
 | 
			
		||||
        env.close();
 | 
			
		||||
 | 
			
		||||
        auto const aliceOwnerCount = ownerCount(env, alice);
 | 
			
		||||
 | 
			
		||||
        // create cron
 | 
			
		||||
        env(cron::set(alice),
 | 
			
		||||
            cron::delay(356 * 24 * 60 * 60),
 | 
			
		||||
            cron::repeat(256),
 | 
			
		||||
            fee(XRP(1)),
 | 
			
		||||
            ter(tesSUCCESS));
 | 
			
		||||
        env.close();
 | 
			
		||||
 | 
			
		||||
        // increment owner count
 | 
			
		||||
        BEAST_EXPECT(ownerCount(env, alice) == aliceOwnerCount + 1);
 | 
			
		||||
 | 
			
		||||
        auto const accSle = env.le(keylet::account(alice.id()));
 | 
			
		||||
        BEAST_EXPECT(accSle);
 | 
			
		||||
        BEAST_EXPECT(accSle->isFieldPresent(sfCron));
 | 
			
		||||
 | 
			
		||||
        auto const cronKey = keylet::child(accSle->getFieldH256(sfCron));
 | 
			
		||||
        auto const cronSle = env.le(cronKey);
 | 
			
		||||
        BEAST_EXPECT(cronSle);
 | 
			
		||||
        BEAST_EXPECT(
 | 
			
		||||
            cronSle->getFieldU32(sfDelaySeconds) == 356 * 24 * 60 * 60);
 | 
			
		||||
        BEAST_EXPECT(cronSle->getFieldU32(sfRepeatCount) == 256);
 | 
			
		||||
 | 
			
		||||
        // update cron
 | 
			
		||||
        env(cron::set(alice),
 | 
			
		||||
            cron::delay(100),
 | 
			
		||||
            cron::repeat(10),
 | 
			
		||||
            fee(XRP(1)),
 | 
			
		||||
            ter(tesSUCCESS));
 | 
			
		||||
        env.close();
 | 
			
		||||
 | 
			
		||||
        // owner count does not change
 | 
			
		||||
        BEAST_EXPECT(ownerCount(env, alice) == aliceOwnerCount + 1);
 | 
			
		||||
 | 
			
		||||
        auto const accSle2 = env.le(keylet::account(alice.id()));
 | 
			
		||||
        BEAST_EXPECT(accSle2);
 | 
			
		||||
        BEAST_EXPECT(accSle2->isFieldPresent(sfCron));
 | 
			
		||||
 | 
			
		||||
        // old cron sle is deleted
 | 
			
		||||
        BEAST_EXPECT(!env.le(cronKey));
 | 
			
		||||
 | 
			
		||||
        auto const cronKey2 = keylet::child(accSle2->getFieldH256(sfCron));
 | 
			
		||||
        auto const cronSle2 = env.le(cronKey2);
 | 
			
		||||
        BEAST_EXPECT(cronSle2);
 | 
			
		||||
        BEAST_EXPECT(cronSle2->getFieldU32(sfDelaySeconds) == 100);
 | 
			
		||||
        BEAST_EXPECT(cronSle2->getFieldU32(sfRepeatCount) == 10);
 | 
			
		||||
 | 
			
		||||
        // delete cron
 | 
			
		||||
        env(cron::set(alice),
 | 
			
		||||
            fee(XRP(1)),
 | 
			
		||||
            txflags(tfCronUnset),
 | 
			
		||||
            ter(tesSUCCESS));
 | 
			
		||||
        env.close();
 | 
			
		||||
 | 
			
		||||
        // owner count decremented
 | 
			
		||||
        BEAST_EXPECT(ownerCount(env, alice) == aliceOwnerCount);
 | 
			
		||||
 | 
			
		||||
        auto const accSle3 = env.le(keylet::account(alice.id()));
 | 
			
		||||
        BEAST_EXPECT(accSle3);
 | 
			
		||||
        BEAST_EXPECT(!accSle3->isFieldPresent(sfCron));
 | 
			
		||||
 | 
			
		||||
        // old cron sle is deleted
 | 
			
		||||
        BEAST_EXPECT(!env.le(cronKey2));
 | 
			
		||||
 | 
			
		||||
        // delete cron without object will succeed
 | 
			
		||||
        env(cron::set(alice),
 | 
			
		||||
            fee(XRP(1)),
 | 
			
		||||
            txflags(tfCronUnset),
 | 
			
		||||
            ter(tesSUCCESS));
 | 
			
		||||
        env.close();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void
 | 
			
		||||
    testCronExecution(FeatureBitset features)
 | 
			
		||||
    {
 | 
			
		||||
        testcase("cron execution");
 | 
			
		||||
        using namespace jtx;
 | 
			
		||||
        using namespace std::literals::chrono_literals;
 | 
			
		||||
        auto const alice = Account("alice");
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            // test ttCron execution and repeatCount
 | 
			
		||||
            Env env{*this, features | featureCron};
 | 
			
		||||
 | 
			
		||||
            env.fund(XRP(1000), alice);
 | 
			
		||||
            env.close();
 | 
			
		||||
 | 
			
		||||
            auto baseTime = env.timeKeeper().now().time_since_epoch().count();
 | 
			
		||||
 | 
			
		||||
            auto repeatCount = 10;
 | 
			
		||||
 | 
			
		||||
            env(cron::set(alice),
 | 
			
		||||
                cron::delay(100),
 | 
			
		||||
                cron::repeat(repeatCount),
 | 
			
		||||
                fee(XRP(1)));
 | 
			
		||||
            env.close(10s);
 | 
			
		||||
 | 
			
		||||
            auto lastCronKeylet =
 | 
			
		||||
                keylet::child(env.le(alice)->getFieldH256(sfCron));
 | 
			
		||||
 | 
			
		||||
            while (repeatCount >= 0)
 | 
			
		||||
            {
 | 
			
		||||
                // close ledger until 100 seconds has passed
 | 
			
		||||
                while (env.timeKeeper().now().time_since_epoch().count() -
 | 
			
		||||
                           baseTime <
 | 
			
		||||
                       100)
 | 
			
		||||
                {
 | 
			
		||||
                    env.close(10s);
 | 
			
		||||
                    auto txns = env.closed()->txs;
 | 
			
		||||
                    auto size = std::distance(txns.begin(), txns.end());
 | 
			
		||||
                    BEAST_EXPECT(size == 0);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // close after 100 seconds passed
 | 
			
		||||
                env.close();
 | 
			
		||||
 | 
			
		||||
                auto txns = env.closed()->txs;
 | 
			
		||||
                auto size = std::distance(txns.begin(), txns.end());
 | 
			
		||||
                BEAST_EXPECT(size == 1);
 | 
			
		||||
                for (auto it = txns.begin(); it != txns.end(); ++it)
 | 
			
		||||
                {
 | 
			
		||||
                    auto const& tx = *it->first;
 | 
			
		||||
                    // check pseudo txn format
 | 
			
		||||
                    BEAST_EXPECT(tx.getTxnType() == ttCRON);
 | 
			
		||||
                    BEAST_EXPECT(tx.getAccountID(sfAccount) == AccountID());
 | 
			
		||||
                    BEAST_EXPECT(tx.getAccountID(sfOwner) == alice.id());
 | 
			
		||||
                    BEAST_EXPECT(
 | 
			
		||||
                        tx.getFieldU32(sfLedgerSequence) ==
 | 
			
		||||
                        env.closed()->info().seq);
 | 
			
		||||
                    BEAST_EXPECT(tx.getFieldAmount(sfFee) == XRP(0));
 | 
			
		||||
                    BEAST_EXPECT(tx.getFieldVL(sfSigningPubKey).size() == 0);
 | 
			
		||||
 | 
			
		||||
                    // check old Cron object is deleted
 | 
			
		||||
                    BEAST_EXPECT(!env.le(lastCronKeylet));
 | 
			
		||||
 | 
			
		||||
                    if (repeatCount > 0)
 | 
			
		||||
                    {
 | 
			
		||||
                        // check new Cron object
 | 
			
		||||
                        auto const cronKeylet =
 | 
			
		||||
                            keylet::child(env.le(alice)->getFieldH256(sfCron));
 | 
			
		||||
                        auto const cronSle = env.le(cronKeylet);
 | 
			
		||||
                        BEAST_EXPECT(cronSle);
 | 
			
		||||
                        BEAST_EXPECT(
 | 
			
		||||
                            cronSle->getFieldU32(sfDelaySeconds) == 100);
 | 
			
		||||
                        BEAST_EXPECT(
 | 
			
		||||
                            cronSle->getFieldU32(sfRepeatCount) ==
 | 
			
		||||
                            --repeatCount);
 | 
			
		||||
                        BEAST_EXPECT(
 | 
			
		||||
                            cronSle->getAccountID(sfOwner) == alice.id());
 | 
			
		||||
 | 
			
		||||
                        // set new base time
 | 
			
		||||
                        baseTime =
 | 
			
		||||
                            env.timeKeeper().now().time_since_epoch().count();
 | 
			
		||||
                        lastCronKeylet = cronKeylet;
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        // after all executions, the cron object should be
 | 
			
		||||
                        // deleted
 | 
			
		||||
                        BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfCron));
 | 
			
		||||
                        BEAST_EXPECT(!env.le(lastCronKeylet));
 | 
			
		||||
                        --repeatCount;  // decrement for break double loop
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            // test ttCron limit in a ledger
 | 
			
		||||
            Env env{*this, features | featureCron};
 | 
			
		||||
            std::vector<Account> accounts;
 | 
			
		||||
            accounts.reserve(300);
 | 
			
		||||
            for (int i = 0; i < 300; ++i)
 | 
			
		||||
            {
 | 
			
		||||
                auto const& account = accounts.emplace_back(
 | 
			
		||||
                    Account("account_" + std::to_string(i)));
 | 
			
		||||
                accounts.emplace_back(account);
 | 
			
		||||
                env.fund(XRP(10000), account);
 | 
			
		||||
            }
 | 
			
		||||
            env.close();
 | 
			
		||||
 | 
			
		||||
            for (auto const& account : accounts)
 | 
			
		||||
            {
 | 
			
		||||
                env(cron::set(account), cron::delay(0), fee(XRP(1)));
 | 
			
		||||
            }
 | 
			
		||||
            env.close();
 | 
			
		||||
 | 
			
		||||
            // proceed ledger
 | 
			
		||||
            env.close();
 | 
			
		||||
            {
 | 
			
		||||
                auto const txns = env.closed()->txs;
 | 
			
		||||
                auto size = std::distance(txns.begin(), txns.end());
 | 
			
		||||
                BEAST_EXPECT(size == 128);
 | 
			
		||||
                for (auto it = txns.begin(); it != txns.end(); ++it)
 | 
			
		||||
                {
 | 
			
		||||
                    auto const& tx = *it->first;
 | 
			
		||||
                    BEAST_EXPECT(tx.getTxnType() == ttCRON);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // proceed ledger
 | 
			
		||||
            env.close();
 | 
			
		||||
            {
 | 
			
		||||
                auto const txns = env.closed()->txs;
 | 
			
		||||
                auto size = std::distance(txns.begin(), txns.end());
 | 
			
		||||
                BEAST_EXPECT(size == 128);
 | 
			
		||||
                for (auto it = txns.begin(); it != txns.end(); ++it)
 | 
			
		||||
                {
 | 
			
		||||
                    auto const& tx = *it->first;
 | 
			
		||||
                    BEAST_EXPECT(tx.getTxnType() == ttCRON);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // proceed ledger
 | 
			
		||||
            env.close();
 | 
			
		||||
            {
 | 
			
		||||
                auto const txns = env.closed()->txs;
 | 
			
		||||
                auto size = std::distance(txns.begin(), txns.end());
 | 
			
		||||
                BEAST_EXPECT(size == 44);
 | 
			
		||||
                for (auto it = txns.begin(); it != txns.end(); ++it)
 | 
			
		||||
                {
 | 
			
		||||
                    auto const& tx = *it->first;
 | 
			
		||||
                    BEAST_EXPECT(tx.getTxnType() == ttCRON);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // proceed ledger
 | 
			
		||||
            env.close();
 | 
			
		||||
            {
 | 
			
		||||
                auto const txns = env.closed()->txs;
 | 
			
		||||
                auto size = std::distance(txns.begin(), txns.end());
 | 
			
		||||
                BEAST_EXPECT(size == 0);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void
 | 
			
		||||
    testWithFeats(FeatureBitset features)
 | 
			
		||||
    {
 | 
			
		||||
        testEnabled(features);
 | 
			
		||||
        testFee(features);
 | 
			
		||||
        testInvalidPreflight(features);
 | 
			
		||||
        testInvalidPreclaim(features);
 | 
			
		||||
        testDoApply(features);
 | 
			
		||||
 | 
			
		||||
        testCronExecution(features);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    void
 | 
			
		||||
    run() override
 | 
			
		||||
    {
 | 
			
		||||
        using namespace test::jtx;
 | 
			
		||||
        auto const sa = supported_amendments();
 | 
			
		||||
        testWithFeats(sa);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
BEAST_DEFINE_TESTSUITE(Cron, app, ripple);
 | 
			
		||||
 | 
			
		||||
}  // namespace test
 | 
			
		||||
}  // namespace ripple
 | 
			
		||||
@@ -22,6 +22,7 @@
 | 
			
		||||
#include <ripple/app/misc/HashRouter.h>
 | 
			
		||||
#include <ripple/app/misc/TxQ.h>
 | 
			
		||||
#include <ripple/app/tx/apply.h>
 | 
			
		||||
#include <ripple/basics/StringUtilities.h>
 | 
			
		||||
#include <ripple/protocol/Feature.h>
 | 
			
		||||
#include <ripple/protocol/PayChan.h>
 | 
			
		||||
#include <ripple/protocol/jss.h>
 | 
			
		||||
@@ -748,10 +749,22 @@ private:
 | 
			
		||||
        jtx::Env& env,
 | 
			
		||||
        int const& expected,
 | 
			
		||||
        uint64_t const& lineno)
 | 
			
		||||
    {
 | 
			
		||||
        auto const hashStr =
 | 
			
		||||
            env.tx()->getJson(JsonOptions::none)[jss::hash].asString();
 | 
			
		||||
        uint256 const txHash = uint256::fromVoid(strUnHex(hashStr)->data());
 | 
			
		||||
        testTSHStrongWeak(env, txHash, expected, lineno);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void
 | 
			
		||||
    testTSHStrongWeak(
 | 
			
		||||
        jtx::Env& env,
 | 
			
		||||
        uint256 const& txHash,
 | 
			
		||||
        int const& expected,
 | 
			
		||||
        uint64_t const& lineno)
 | 
			
		||||
    {
 | 
			
		||||
        Json::Value params;
 | 
			
		||||
        params[jss::transaction] =
 | 
			
		||||
            env.tx()->getJson(JsonOptions::none)[jss::hash];
 | 
			
		||||
        params[jss::transaction] = strHex(txHash);
 | 
			
		||||
        auto const jrr = env.rpc("json", "tx", to_string(params));
 | 
			
		||||
        auto const meta = jrr[jss::result][jss::meta];
 | 
			
		||||
        validateTSHStrongWeak(meta, expected, lineno);
 | 
			
		||||
@@ -6251,6 +6264,103 @@ private:
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // CronSet
 | 
			
		||||
    // | otxn | tsh | cset |
 | 
			
		||||
    // |   A  |  A  |  S   |
 | 
			
		||||
    void
 | 
			
		||||
    testCronSetTSH(FeatureBitset features)
 | 
			
		||||
    {
 | 
			
		||||
        testcase("cron set tsh");
 | 
			
		||||
 | 
			
		||||
        using namespace test::jtx;
 | 
			
		||||
        using namespace std::literals;
 | 
			
		||||
 | 
			
		||||
        // otxn: account
 | 
			
		||||
        // tsh account
 | 
			
		||||
        // w/s: strong
 | 
			
		||||
        for (bool const testStrong : {true, false})
 | 
			
		||||
        {
 | 
			
		||||
            test::jtx::Env env{
 | 
			
		||||
                *this,
 | 
			
		||||
                network::makeNetworkConfig(21337, "10", "1000000", "200000"),
 | 
			
		||||
                features};
 | 
			
		||||
 | 
			
		||||
            auto const account = Account("alice");
 | 
			
		||||
            env.fund(XRP(1000), account);
 | 
			
		||||
            env.close();
 | 
			
		||||
 | 
			
		||||
            if (!testStrong)
 | 
			
		||||
                addWeakTSH(env, account);
 | 
			
		||||
 | 
			
		||||
            // set tsh hook
 | 
			
		||||
            setTSHHook(env, account, testStrong);
 | 
			
		||||
 | 
			
		||||
            // cron set
 | 
			
		||||
            env(cron::set(account),
 | 
			
		||||
                cron::delay(100),
 | 
			
		||||
                cron::repeat(1),
 | 
			
		||||
                fee(XRP(1)),
 | 
			
		||||
                ter(tesSUCCESS));
 | 
			
		||||
            env.close();
 | 
			
		||||
 | 
			
		||||
            // verify tsh hook triggered
 | 
			
		||||
            testTSHStrongWeak(env, tshSTRONG, __LINE__);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // | otxn | tsh | cron |
 | 
			
		||||
    // |   -  |  O  |  W   |
 | 
			
		||||
    void
 | 
			
		||||
    testCronTSH(FeatureBitset features)
 | 
			
		||||
    {
 | 
			
		||||
        testcase("cron tsh");
 | 
			
		||||
 | 
			
		||||
        using namespace test::jtx;
 | 
			
		||||
        using namespace std::literals;
 | 
			
		||||
 | 
			
		||||
        // otxn: -
 | 
			
		||||
        // tsh owner
 | 
			
		||||
        // w/s: weak
 | 
			
		||||
        for (bool const testStrong : {true, false})
 | 
			
		||||
        {
 | 
			
		||||
            test::jtx::Env env{
 | 
			
		||||
                *this,
 | 
			
		||||
                network::makeNetworkConfig(21337, "10", "1000000", "200000"),
 | 
			
		||||
                features};
 | 
			
		||||
 | 
			
		||||
            auto const account = Account("alice");
 | 
			
		||||
            env.fund(XRP(1000), account);
 | 
			
		||||
            env.close();
 | 
			
		||||
 | 
			
		||||
            // cron set
 | 
			
		||||
            env(cron::set(account),
 | 
			
		||||
                cron::delay(100),
 | 
			
		||||
                cron::repeat(1),
 | 
			
		||||
                fee(XRP(1)),
 | 
			
		||||
                ter(tesSUCCESS));
 | 
			
		||||
            env.close();
 | 
			
		||||
 | 
			
		||||
            if (!testStrong)
 | 
			
		||||
                addWeakTSH(env, account);
 | 
			
		||||
 | 
			
		||||
            // set tsh hook
 | 
			
		||||
            setTSHHook(env, account, testStrong);
 | 
			
		||||
 | 
			
		||||
            // proceed ledger
 | 
			
		||||
            env.close(100s);
 | 
			
		||||
 | 
			
		||||
            // close ledger
 | 
			
		||||
            env.close();
 | 
			
		||||
 | 
			
		||||
            // verify tsh hook triggered
 | 
			
		||||
            auto const expected = testStrong ? tshNONE : tshWEAK;
 | 
			
		||||
            auto const txs = env.closed()->txs;
 | 
			
		||||
            BEAST_EXPECT(std::distance(txs.begin(), txs.end()) == 1);
 | 
			
		||||
            auto const tx = txs.begin()->first;
 | 
			
		||||
            BEAST_EXPECT(tx->getTxnType() == ttCRON);
 | 
			
		||||
            testTSHStrongWeak(env, tx->getTransactionID(), expected, __LINE__);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    void
 | 
			
		||||
    testEmissionOrdering(FeatureBitset features)
 | 
			
		||||
    {
 | 
			
		||||
@@ -6398,6 +6508,8 @@ private:
 | 
			
		||||
        testURITokenCancelSellOfferTSH(features);
 | 
			
		||||
        testURITokenCreateSellOfferTSH(features);
 | 
			
		||||
        testRemitTSH(features);
 | 
			
		||||
        testCronSetTSH(features);
 | 
			
		||||
        testCronTSH(features);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,7 @@
 | 
			
		||||
#include <test/jtx/amount.h>
 | 
			
		||||
#include <test/jtx/balance.h>
 | 
			
		||||
#include <test/jtx/check.h>
 | 
			
		||||
#include <test/jtx/cron.h>
 | 
			
		||||
#include <test/jtx/delivermin.h>
 | 
			
		||||
#include <test/jtx/deposit.h>
 | 
			
		||||
#include <test/jtx/escrow.h>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										74
									
								
								src/test/jtx/cron.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/test/jtx/cron.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of rippled: https://github.com/ripple/rippled
 | 
			
		||||
    Copyright (c) 2025 XRPL Labs
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and/or distribute this software for any
 | 
			
		||||
    purpose  with  or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL ,  DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#ifndef RIPPLE_TEST_JTX_CRON_H_INCLUDED
 | 
			
		||||
#define RIPPLE_TEST_JTX_CRON_H_INCLUDED
 | 
			
		||||
 | 
			
		||||
#include <test/jtx/Account.h>
 | 
			
		||||
#include <test/jtx/Env.h>
 | 
			
		||||
 | 
			
		||||
namespace ripple {
 | 
			
		||||
namespace test {
 | 
			
		||||
namespace jtx {
 | 
			
		||||
 | 
			
		||||
/** Cron operations. */
 | 
			
		||||
namespace cron {
 | 
			
		||||
 | 
			
		||||
/** Set a cron. */
 | 
			
		||||
Json::Value
 | 
			
		||||
set(jtx::Account const& account);
 | 
			
		||||
 | 
			
		||||
/** Sets the optional DelaySeconds on a JTx. */
 | 
			
		||||
class delay
 | 
			
		||||
{
 | 
			
		||||
private:
 | 
			
		||||
    uint32_t delay_;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    explicit delay(uint32_t delay) : delay_(delay)
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void
 | 
			
		||||
    operator()(Env&, JTx& jtx) const;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** Sets the optional RepeatCount on a JTx. */
 | 
			
		||||
class repeat
 | 
			
		||||
{
 | 
			
		||||
private:
 | 
			
		||||
    uint32_t repeat_;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    explicit repeat(uint32_t repeat) : repeat_(repeat)
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void
 | 
			
		||||
    operator()(Env&, JTx& jtx) const;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace cron
 | 
			
		||||
 | 
			
		||||
}  // namespace jtx
 | 
			
		||||
 | 
			
		||||
}  // namespace test
 | 
			
		||||
}  // namespace ripple
 | 
			
		||||
 | 
			
		||||
#endif  // RIPPLE_TEST_JTX_CRON_H_INCLUDED
 | 
			
		||||
							
								
								
									
										56
									
								
								src/test/jtx/impl/cron.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/test/jtx/impl/cron.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
/*
 | 
			
		||||
    This file is part of rippled: https://github.com/ripple/rippled
 | 
			
		||||
    Copyright (c) 2025 XRPL Labs
 | 
			
		||||
 | 
			
		||||
    Permission to use, copy, modify, and/or distribute this software for any
 | 
			
		||||
    purpose  with  or without fee is hereby granted, provided that the above
 | 
			
		||||
    copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
 | 
			
		||||
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
    ANY  SPECIAL ,  DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
*/
 | 
			
		||||
//==============================================================================
 | 
			
		||||
 | 
			
		||||
#include <ripple/protocol/jss.h>
 | 
			
		||||
#include <test/jtx/cron.h>
 | 
			
		||||
 | 
			
		||||
namespace ripple {
 | 
			
		||||
namespace test {
 | 
			
		||||
namespace jtx {
 | 
			
		||||
 | 
			
		||||
namespace cron {
 | 
			
		||||
 | 
			
		||||
// Set a cron.
 | 
			
		||||
Json::Value
 | 
			
		||||
set(jtx::Account const& account)
 | 
			
		||||
{
 | 
			
		||||
    using namespace jtx;
 | 
			
		||||
    Json::Value jv;
 | 
			
		||||
    jv[jss::TransactionType] = jss::CronSet;
 | 
			
		||||
    jv[jss::Account] = account.human();
 | 
			
		||||
    return jv;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
delay::operator()(Env& env, JTx& jt) const
 | 
			
		||||
{
 | 
			
		||||
    jt.jv[sfDelaySeconds.jsonName] = delay_;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void
 | 
			
		||||
repeat::operator()(Env& env, JTx& jt) const
 | 
			
		||||
{
 | 
			
		||||
    jt.jv[sfRepeatCount.jsonName] = repeat_;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace cron
 | 
			
		||||
 | 
			
		||||
}  // namespace jtx
 | 
			
		||||
}  // namespace test
 | 
			
		||||
}  // namespace ripple
 | 
			
		||||
		Reference in New Issue
	
	Block a user