diff --git a/src/test/rpc/LedgerRPC_test.cpp b/src/test/rpc/LedgerRPC_test.cpp index 70e4ffbe8dc..bdfed510108 100644 --- a/src/test/rpc/LedgerRPC_test.cpp +++ b/src/test/rpc/LedgerRPC_test.cpp @@ -1413,6 +1413,122 @@ class LedgerRPC_test : public beast::unit_test::suite } } + void + testLedgerEntrySubscription() + { + testcase("ledger_entry Request Subscription"); + using namespace test::jtx; + Env env{*this}; + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(10000), alice, bob); + env.close(); + + using namespace std::chrono_literals; + env(subscription::create(alice, bob, XRP(10), 100s)); + env.close(); + + std::string const ledgerHash{to_string(env.closed()->info().hash)}; + std::string subIndex; + { + // Request the subscription using account, destination and sequence. + Json::Value jvParams; + jvParams[jss::subscription] = Json::objectValue; + jvParams[jss::subscription][jss::account] = alice.human(); + jvParams[jss::subscription][jss::destination] = bob.human(); + jvParams[jss::subscription][jss::seq] = env.seq(alice) - 1; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][jss::Amount] == XRP(10).value().getText()); + subIndex = jrr[jss::index].asString(); + } + { + // Request the subscription by index. + Json::Value jvParams; + jvParams[jss::subscription] = subIndex; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][jss::Amount] == XRP(10).value().getText()); + } + { + // Malformed account entry. + Json::Value jvParams; + jvParams[jss::subscription] = Json::objectValue; + + std::string const badAddress = makeBadAddress(alice.human()); + jvParams[jss::subscription][jss::account] = badAddress; + jvParams[jss::subscription][jss::destination] = bob.human(); + jvParams[jss::subscription][jss::seq] = env.seq(alice) - 1; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedAddress", ""); + } + { + // Missing account. + Json::Value jvParams; + jvParams[jss::subscription] = Json::objectValue; + jvParams[jss::subscription][jss::destination] = bob.human(); + jvParams[jss::subscription][jss::seq] = env.seq(alice) - 1; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Malformed destination entry. + Json::Value jvParams; + jvParams[jss::subscription] = Json::objectValue; + + std::string const badAddress = makeBadAddress(alice.human()); + jvParams[jss::subscription][jss::account] = alice.human(); + jvParams[jss::subscription][jss::destination] = badAddress; + jvParams[jss::subscription][jss::seq] = env.seq(alice) - 1; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedAddress", ""); + } + { + // Missing destination. + Json::Value jvParams; + jvParams[jss::subscription] = Json::objectValue; + jvParams[jss::subscription][jss::account] = alice.human(); + jvParams[jss::subscription][jss::seq] = env.seq(alice) - 1; + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Missing sequence. + Json::Value jvParams; + jvParams[jss::subscription] = Json::objectValue; + jvParams[jss::subscription][jss::account] = alice.human(); + jvParams[jss::subscription][jss::destination] = bob.human(); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + { + // Non-integer sequence. + Json::Value jvParams; + jvParams[jss::subscription] = Json::objectValue; + jvParams[jss::subscription][jss::account] = alice.human(); + jvParams[jss::subscription][jss::destination] = bob.human(); + jvParams[jss::subscription][jss::seq] = + std::to_string(env.seq(alice) - 1); + jvParams[jss::ledger_hash] = ledgerHash; + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + checkErrorValue(jrr, "malformedRequest", ""); + } + } + void testLedgerEntryTicket() { @@ -2380,6 +2496,7 @@ class LedgerRPC_test : public beast::unit_test::suite testLedgerEntryOffer(); testLedgerEntryPayChan(); testLedgerEntryRippleState(); + testLedgerEntrySubscription(); testLedgerEntryTicket(); testLookupLedger(); testNoQueue(); diff --git a/src/xrpld/rpc/detail/RPCHelpers.cpp b/src/xrpld/rpc/detail/RPCHelpers.cpp index fa66fecfbba..d5fa7c97568 100644 --- a/src/xrpld/rpc/detail/RPCHelpers.cpp +++ b/src/xrpld/rpc/detail/RPCHelpers.cpp @@ -915,7 +915,7 @@ chooseLedgerEntryType(Json::Value const& params) std::pair result{RPC::Status::OK, ltANY}; if (params.isMember(jss::type)) { - static constexpr std::array, 22> + static constexpr std::array, 23> types{ {{jss::account, ltACCOUNT_ROOT}, {jss::amendments, ltAMENDMENTS}, @@ -936,6 +936,7 @@ chooseLedgerEntryType(Json::Value const& params) {jss::payment_channel, ltPAYCHAN}, {jss::signer_list, ltSIGNER_LIST}, {jss::state, ltRIPPLE_STATE}, + {jss::subscription, ltSUBSCRIPTION}, {jss::ticket, ltTICKET}, {jss::xchain_owned_claim_id, ltXCHAIN_OWNED_CLAIM_ID}, {jss::xchain_owned_create_account_claim_id, diff --git a/src/xrpld/rpc/handlers/LedgerEntry.cpp b/src/xrpld/rpc/handlers/LedgerEntry.cpp index 6cc85aa6a4c..f26d2f17645 100644 --- a/src/xrpld/rpc/handlers/LedgerEntry.cpp +++ b/src/xrpld/rpc/handlers/LedgerEntry.cpp @@ -656,39 +656,34 @@ doLedgerEntry(RPC::JsonContext& context) jvResult[jss::error] = "malformedRequest"; } } - // else if ( - // !context.params[jss::oracle].isMember( - // jss::oracle_document_id) || - // !context.params[jss::oracle].isMember(jss::account)) - // { - // jvResult[jss::error] = "malformedRequest"; - // } - // else - // { - // uNodeIndex = beast::zero; - // auto const& oracle = context.params[jss::oracle]; - // auto const documentID = [&]() -> std::optional - // { - // auto const& id = oracle[jss::oracle_document_id]; - // if (id.isUInt() || (id.isInt() && id.asInt() >= 0)) - // return std::make_optional(id.asUInt()); - // else if (id.isString()) - // { - // std::uint32_t v; - // if (beast::lexicalCastChecked(v, id.asString())) - // return std::make_optional(v); - // } - // return std::nullopt; - // }(); - // auto const account = - // parseBase58(oracle[jss::account].asString()); - // if (!account || account->isZero()) - // jvResult[jss::error] = "malformedAddress"; - // else if (!documentID) - // jvResult[jss::error] = "malformedDocumentID"; - // else - // uNodeIndex = keylet::oracle(*account, *documentID).key; - // } + else if ( + !context.params[jss::subscription].isMember(jss::account) || + !context.params[jss::subscription].isMember(jss::destination) || + !context.params[jss::subscription].isMember(jss::seq) || + !context.params[jss::subscription][jss::seq].isIntegral()) + { + jvResult[jss::error] = "malformedRequest"; + } + else + { + uNodeIndex = beast::zero; + auto const& subscription = context.params[jss::subscription]; + auto const account = parseBase58( + subscription[jss::account].asString()); + auto const destination = parseBase58( + subscription[jss::destination].asString()); + if (!account || account->isZero()) + jvResult[jss::error] = "malformedAddress"; + else if (!destination || destination->isZero()) + jvResult[jss::error] = "malformedAddress"; + else + uNodeIndex = keylet::subscription( + *account, + *destination, + context.params[jss::subscription][jss::seq] + .asUInt()) + .key; + } } else {