From e64692fc8bea256f4a95a22ea29c2d2352db4a6a Mon Sep 17 00:00:00 2001 From: tequ Date: Wed, 15 Oct 2025 20:45:47 +0900 Subject: [PATCH] add ttCron tests --- src/ripple/app/misc/impl/TxQ.cpp | 3 +- src/ripple/app/tx/impl/Cron.cpp | 6 + src/ripple/app/tx/impl/Cron.h | 3 + src/ripple/app/tx/impl/applySteps.cpp | 10 ++ src/test/app/Cron_test.cpp | 174 ++++++++++++++++++++++++-- 5 files changed, 187 insertions(+), 9 deletions(-) diff --git a/src/ripple/app/misc/impl/TxQ.cpp b/src/ripple/app/misc/impl/TxQ.cpp index ea4e5e7b2..dbd690a45 100644 --- a/src/ripple/app/misc/impl/TxQ.cpp +++ b/src/ripple/app/misc/impl/TxQ.cpp @@ -1489,7 +1489,8 @@ TxQ::accept(Application& app, OpenView& view) std::set cronAccs; auto counter = 0; - while (++counter < 128 && klStart < klEnd) + // include max 128 cron txns in the ledger + while (++counter < 129 && klStart < klEnd) { std::optional next = view.succ(klStart, klEnd); if (!next.has_value()) diff --git a/src/ripple/app/tx/impl/Cron.cpp b/src/ripple/app/tx/impl/Cron.cpp index e81b8322e..befcdd425 100644 --- a/src/ripple/app/tx/impl/Cron.cpp +++ b/src/ripple/app/tx/impl/Cron.cpp @@ -173,6 +173,12 @@ Cron::doApply() return tesSUCCESS; } +void +Cron::preCompute() +{ + assert(account_ == beast::zero); +} + XRPAmount Cron::calculateBaseFee(ReadView const& view, STTx const& tx) { diff --git a/src/ripple/app/tx/impl/Cron.h b/src/ripple/app/tx/impl/Cron.h index 893a63a51..9d0498c87 100644 --- a/src/ripple/app/tx/impl/Cron.h +++ b/src/ripple/app/tx/impl/Cron.h @@ -50,6 +50,9 @@ public: TER doApply() override; + + void + preCompute() override; }; } // namespace ripple diff --git a/src/ripple/app/tx/impl/applySteps.cpp b/src/ripple/app/tx/impl/applySteps.cpp index 2f6ea79d2..7e3d42cd8 100644 --- a/src/ripple/app/tx/impl/applySteps.cpp +++ b/src/ripple/app/tx/impl/applySteps.cpp @@ -185,6 +185,8 @@ invoke_preflight(PreflightContext const& ctx) return invoke_preflight_helper(ctx); case ttCRON_SET: return invoke_preflight_helper(ctx); + case ttCRON: + return invoke_preflight_helper(ctx); default: assert(false); return {temUNKNOWN, TxConsequences{temUNKNOWN}}; @@ -312,6 +314,8 @@ invoke_preclaim(PreclaimContext const& ctx) return invoke_preclaim(ctx); case ttCRON_SET: return invoke_preclaim(ctx); + case ttCRON: + return invoke_preclaim(ctx); default: assert(false); return temUNKNOWN; @@ -401,6 +405,8 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx) return URIToken::calculateBaseFee(view, tx); case ttCRON_SET: return SetCron::calculateBaseFee(view, tx); + case ttCRON: + return Cron::calculateBaseFee(view, tx); default: return XRPAmount{0}; } @@ -598,6 +604,10 @@ invoke_apply(ApplyContext& ctx) SetCron p(ctx); return p(); } + case ttCRON: { + Cron p(ctx); + return p(); + } default: assert(false); return {temUNKNOWN, false}; diff --git a/src/test/app/Cron_test.cpp b/src/test/app/Cron_test.cpp index 1f69a6292..8d99a39e0 100644 --- a/src/test/app/Cron_test.cpp +++ b/src/test/app/Cron_test.cpp @@ -20,11 +20,6 @@ #include #include #include -#include "ripple/protocol/Indexes.h" -#include "ripple/protocol/TER.h" -#include "ripple/protocol/TxFlags.h" -#include "test/jtx/TestHelpers.h" -#include "test/jtx/cron.h" #include namespace ripple { @@ -44,8 +39,6 @@ struct Cron_test : public beast::unit_test::suite for (bool const withCron : {false, true}) { - // If the BalanceRewards amendment is not enabled, you should not be - // able to claim rewards. auto const amend = withCron ? features : features - featureCron; Env env{*this, amend}; @@ -135,7 +128,6 @@ struct Cron_test : public beast::unit_test::suite // preflight // temINVALID_FLAG - // can have flag 1 set to opt-out of rewards { env(cron::set(alice), txflags(tfClose), ter(temINVALID_FLAG)); env(cron::set(alice), @@ -252,6 +244,170 @@ struct Cron_test : public beast::unit_test::suite BEAST_EXPECT(!env.le(cronKey2)); } + 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 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) { @@ -260,6 +416,8 @@ struct Cron_test : public beast::unit_test::suite testInvalidPreflight(features); testInvalidPreclaim(features); testDoApply(features); + + testCronExecution(features); } public: