From 2a10013dfc9fd55e52c2d9b4a4aeafa5e884af7f Mon Sep 17 00:00:00 2001 From: tequ Date: Fri, 24 Oct 2025 16:05:14 +0900 Subject: [PATCH] Support 'cron' with ledger_entry RPC (#608) --- src/ripple/protocol/impl/Indexes.cpp | 2 +- src/ripple/protocol/jss.h | 1 + src/ripple/rpc/handlers/LedgerEntry.cpp | 30 +++++++++ src/test/rpc/LedgerRPC_test.cpp | 83 +++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/src/ripple/protocol/impl/Indexes.cpp b/src/ripple/protocol/impl/Indexes.cpp index d8f802fe2..c09070574 100644 --- a/src/ripple/protocol/impl/Indexes.cpp +++ b/src/ripple/protocol/impl/Indexes.cpp @@ -72,7 +72,7 @@ enum class LedgerNameSpace : std::uint16_t { URI_TOKEN = 'U', IMPORT_VLSEQ = 'I', UNL_REPORT = 'R', - CRON = 'A', + CRON = 'L', // No longer used or supported. Left here to reserve the space // to avoid accidental reuse. diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index 5cba9fb3c..316037268 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -256,6 +256,7 @@ JSS(coins); JSS(children); JSS(ctid); // in/out: Tx RPC JSS(cres); +JSS(cron); JSS(currency_a); // out: BookChanges JSS(currency_b); // out: BookChanges JSS(currentShard); // out: NodeToShardStatus diff --git a/src/ripple/rpc/handlers/LedgerEntry.cpp b/src/ripple/rpc/handlers/LedgerEntry.cpp index aa5ffb02a..bf861333a 100644 --- a/src/ripple/rpc/handlers/LedgerEntry.cpp +++ b/src/ripple/rpc/handlers/LedgerEntry.cpp @@ -506,6 +506,36 @@ doLedgerEntry(RPC::JsonContext& context) jvResult[jss::error] = "malformedRequest"; } } + else if (context.params.isMember(jss::cron)) + { + expectedType = ltCRON; + if (!context.params[jss::cron].isObject()) + { + if (!uNodeIndex.parseHex(context.params[jss::cron].asString())) + { + uNodeIndex = beast::zero; + jvResult[jss::error] = "malformedRequest"; + } + } + else if ( + !context.params[jss::cron].isMember(jss::owner) || + !context.params[jss::cron].isMember(jss::time)) + { + jvResult[jss::error] = "malformedRequest"; + } + else + { + auto const id = parseBase58( + context.params[jss::cron][jss::owner].asString()); + if (!id) + jvResult[jss::error] = "malformedAddress"; + else + uNodeIndex = + keylet::cron( + context.params[jss::cron][jss::time].asUInt(), *id) + .key; + } + } else { if (context.params.isMember("params") && diff --git a/src/test/rpc/LedgerRPC_test.cpp b/src/test/rpc/LedgerRPC_test.cpp index e6f766916..6c0819377 100644 --- a/src/test/rpc/LedgerRPC_test.cpp +++ b/src/test/rpc/LedgerRPC_test.cpp @@ -1839,6 +1839,88 @@ public: } } + void + testLedgerEntryCron() + { + testcase("ledger_entry Request Cron"); + using namespace test::jtx; + + Env env{*this}; + + Account const alice{"alice"}; + env.fund(XRP(10000), alice); + env.close(); + + auto const startTime = + env.current()->parentCloseTime().time_since_epoch().count() + 100; + env(cron::set(alice), + cron::startTime(startTime), + cron::delay(100), + cron::repeat(200), + fee(XRP(1)), + ter(tesSUCCESS)); + env.close(); + + std::string const ledgerHash{to_string(env.closed()->info().hash)}; + + uint256 const cronIndex{keylet::cron(startTime, alice).key}; + { + // Request the cron using its index. + Json::Value jvParams; + jvParams[jss::cron] = to_string(cronIndex); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][sfOwner.jsonName] == alice.human()); + BEAST_EXPECT(jrr[jss::node][sfStartTime.jsonName] == startTime); + BEAST_EXPECT(jrr[jss::node][sfDelaySeconds.jsonName] == 100); + BEAST_EXPECT(jrr[jss::node][sfRepeatCount.jsonName] == 200); + } + { + // Request the cron using its owner and time. + Json::Value jvParams; + jvParams[jss::cron] = Json::objectValue; + jvParams[jss::cron][jss::owner] = alice.human(); + jvParams[jss::cron][jss::time] = startTime; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::node][sfOwner.jsonName] == alice.human()); + BEAST_EXPECT(jrr[jss::node][sfStartTime.jsonName] == startTime); + BEAST_EXPECT(jrr[jss::node][sfDelaySeconds.jsonName] == 100); + BEAST_EXPECT(jrr[jss::node][sfRepeatCount.jsonName] == 200); + } + { + // Malformed uritoken object. Missing owner member. + Json::Value jvParams; + jvParams[jss::cron] = Json::objectValue; + jvParams[jss::cron][jss::time] = startTime; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Malformed uritoken object. Missing time member. + Json::Value jvParams; + jvParams[jss::cron] = Json::objectValue; + jvParams[jss::cron][jss::owner] = alice.human(); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Request an index that is not a uritoken. + Json::Value jvParams; + jvParams[jss::cron] = ledgerHash; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "entryNotFound", ""); + } + } + void testLedgerEntryUnknownOption() { @@ -2365,6 +2447,7 @@ public: testLedgerEntryTicket(); testLedgerEntryURIToken(); testLedgerEntryImportVLSeq(); + testLedgerEntryCron(); testLedgerEntryUnknownOption(); testLookupLedger(); testNoQueue();