diff --git a/src/ripple/app/main/Main.cpp b/src/ripple/app/main/Main.cpp index c08836e79e7..64b6464a4c6 100644 --- a/src/ripple/app/main/Main.cpp +++ b/src/ripple/app/main/Main.cpp @@ -135,6 +135,7 @@ printHelp(const po::options_description& desc) "[strict]\n" " account_tx accountID [ledger_min [ledger_max [limit " "[offset]]]] [binary] [count] [descending]\n" + " book_changes []\n" " book_offers [ " "[ [ []]]]]\n" " can_delete [||now|always|never]\n" diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index 9203a83ad41..6ff644ba846 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -62,6 +62,7 @@ #include #include #include +#include #include #include #include @@ -502,6 +503,11 @@ class NetworkOPsImp final : public NetworkOPs bool unsubLedger(std::uint64_t uListener) override; + bool + subBookChanges(InfoSub::ref ispListener) override; + bool + unsubBookChanges(std::uint64_t uListener) override; + bool subServer(InfoSub::ref ispListener, Json::Value& jvResult, bool admin) override; @@ -743,9 +749,10 @@ class NetworkOPsImp final : public NetworkOPs sValidations, // Received validations. sPeerStatus, // Peer status changes. sConsensusPhase, // Consensus phase + sBookChanges, // Per-ledger order book changes - sLastEntry = sConsensusPhase // as this name implies, any new entry - // must be ADDED ABOVE this one + sLastEntry = sBookChanges // as this name implies, any new entry + // must be ADDED ABOVE this one }; std::array mStreamMaps; @@ -2898,6 +2905,24 @@ NetworkOPsImp::pubLedger(std::shared_ptr const& lpAccepted) } } + if (!mStreamMaps[sBookChanges].empty()) + { + Json::Value jvObj = ripple::RPC::computeBookChanges(lpAccepted); + + auto it = mStreamMaps[sBookChanges].begin(); + while (it != mStreamMaps[sBookChanges].end()) + { + InfoSub::pointer p = it->second.lock(); + if (p) + { + p->send(jvObj, true); + ++it; + } + else + it = mStreamMaps[sBookChanges].erase(it); + } + } + { static bool firstTime = true; if (firstTime) @@ -3876,6 +3901,16 @@ NetworkOPsImp::subLedger(InfoSub::ref isrListener, Json::Value& jvResult) .second; } +// <-- bool: true=added, false=already there +bool +NetworkOPsImp::subBookChanges(InfoSub::ref isrListener) +{ + std::lock_guard sl(mSubLock); + return mStreamMaps[sBookChanges] + .emplace(isrListener->getSeq(), isrListener) + .second; +} + // <-- bool: true=erased, false=was not there bool NetworkOPsImp::unsubLedger(std::uint64_t uSeq) @@ -3884,6 +3919,14 @@ NetworkOPsImp::unsubLedger(std::uint64_t uSeq) return mStreamMaps[sLedger].erase(uSeq); } +// <-- bool: true=erased, false=was not there +bool +NetworkOPsImp::unsubBookChanges(std::uint64_t uSeq) +{ + std::lock_guard sl(mSubLock); + return mStreamMaps[sBookChanges].erase(uSeq); +} + // <-- bool: true=added, false=already there bool NetworkOPsImp::subManifests(InfoSub::ref isrListener) diff --git a/src/ripple/net/InfoSub.h b/src/ripple/net/InfoSub.h index 3c170669bab..fb44e23b720 100644 --- a/src/ripple/net/InfoSub.h +++ b/src/ripple/net/InfoSub.h @@ -128,6 +128,11 @@ class InfoSub : public CountedObject virtual bool unsubLedger(std::uint64_t uListener) = 0; + virtual bool + subBookChanges(ref ispListener) = 0; + virtual bool + unsubBookChanges(std::uint64_t uListener) = 0; + virtual bool subManifests(ref ispListener) = 0; virtual bool diff --git a/src/ripple/net/impl/RPCCall.cpp b/src/ripple/net/impl/RPCCall.cpp index 820f25ddfc2..334ca8693e3 100644 --- a/src/ripple/net/impl/RPCCall.cpp +++ b/src/ripple/net/impl/RPCCall.cpp @@ -1242,6 +1242,7 @@ class RPCParser {"account_objects", &RPCParser::parseAccountItems, 1, 5}, {"account_offers", &RPCParser::parseAccountItems, 1, 4}, {"account_tx", &RPCParser::parseAccountTransactions, 1, 8}, + {"book_changes", &RPCParser::parseLedgerId, 1, 1}, {"book_offers", &RPCParser::parseBookOffers, 2, 7}, {"can_delete", &RPCParser::parseCanDelete, 0, 1}, {"channel_authorize", &RPCParser::parseChannelAuthorize, 3, 4}, diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index 0dc413e6d20..1c5bf8463b0 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -166,11 +166,13 @@ JSS(build_path); // in: TransactionSign JSS(build_version); // out: NetworkOPs JSS(cancel_after); // out: AccountChannels JSS(can_delete); // out: CanDelete +JSS(changes); // out: BookChanges JSS(channel_id); // out: AccountChannels JSS(channels); // out: AccountChannels JSS(check); // in: AccountObjects JSS(check_nodes); // in: LedgerCleaner JSS(clear); // in/out: FetchInfo +JSS(close); // out: BookChanges JSS(close_flags); // out: LedgerToJson JSS(close_time); // in: Application, out: NetworkOPs, // RCLCxPeerPos, LedgerToJson @@ -193,6 +195,8 @@ JSS(converge_time_s); // out: NetworkOPs JSS(cookie); // out: NetworkOPs JSS(count); // in: AccountTx*, ValidatorList JSS(counters); // in/out: retrieve counters +JSS(currency_a); // out: BookChanges +JSS(currency_b); // out: BookChanges JSS(currentShard); // out: NodeToShardStatus JSS(currentShardIndex); // out: NodeToShardStatus JSS(currency); // in: paths/PathRequest, STAmount @@ -282,6 +286,7 @@ JSS(hashes); // in: AccountObjects JSS(have_header); // out: InboundLedger JSS(have_state); // out: InboundLedger JSS(have_transactions); // out: InboundLedger +JSS(high); // out: BookChanges JSS(highest_sequence); // out: AccountInfo JSS(highest_ticket); // out: AccountInfo JSS(historical_perminute); // historical_perminute. @@ -363,6 +368,7 @@ JSS(load_fee); // out: LoadFeeTrackImp, NetworkOPs JSS(local); // out: resource/Logic.h JSS(local_txs); // out: GetCounts JSS(local_static_keys); // out: ValidatorList +JSS(low); // out: BookChanges JSS(lowest_sequence); // out: AccountInfo JSS(lowest_ticket); // out: AccountInfo JSS(majority); // out: RPC feature @@ -639,6 +645,8 @@ JSS(validator_sites); // out: ValidatorSites JSS(value); // out: STAmount JSS(version); // out: RPCVersion JSS(vetoed); // out: AmendmentTableImpl +JSS(volume_a); // out: BookChanges +JSS(volume_b); // out: BookChanges JSS(vote); // in: Feature JSS(warning); // rpc: JSS(warnings); // out: server_info, server_state diff --git a/src/ripple/rpc/BookChanges.h b/src/ripple/rpc/BookChanges.h new file mode 100644 index 00000000000..11f8e704127 --- /dev/null +++ b/src/ripple/rpc/BookChanges.h @@ -0,0 +1,213 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2019 Ripple Labs Inc. + + 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_RPC_BOOKCHANGES_H_INCLUDED +#define RIPPLE_RPC_BOOKCAHNGES_H_INCLUDED + +namespace Json { +class Value; +} + +namespace ripple { + +class ReadView; +class Transaction; +class TxMeta; +class STTx; + +namespace RPC { + +template +Json::Value +computeBookChanges(std::shared_ptr const& lpAccepted) +{ + std::map< + std::string, + std::tuple< + STAmount, // side A volume + STAmount, // side B volume + STAmount, // high rate + STAmount, // low rate + STAmount, // open rate + STAmount // close rate + >> + tally; + + for (auto& tx : lpAccepted->txs) + { + if (!tx.first || !tx.second || + !tx.first->isFieldPresent(sfTransactionType)) + continue; + + std::optional offerCancel; + uint16_t tt = tx.first->getFieldU16(sfTransactionType); + switch (tt) + { + case ttOFFER_CANCEL: + case ttOFFER_CREATE: { + if (tx.first->isFieldPresent(sfOfferSequence)) + offerCancel = tx.first->getFieldU32(sfOfferSequence); + break; + } + // in future if any other ways emerge to cancel an offer + // this switch makes them easy to add + default: + break; + } + + for (auto const& node : tx.second->getFieldArray(sfAffectedNodes)) + { + SField const& metaType = node.getFName(); + uint16_t nodeType = node.getFieldU16(sfLedgerEntryType); + + // we only care about ltOFFER objects being modified or + // deleted + if (nodeType != ltOFFER || metaType == sfCreatedNode) + continue; + + // if either FF or PF are missing we can't compute + // but generally these are cancelled rather than crossed + // so skipping them is consistent + if (!node.isFieldPresent(sfFinalFields) || + !node.isFieldPresent(sfPreviousFields)) + continue; + + STObject& finalFields = (const_cast(node)) + .getField(sfFinalFields) + .downcast(); + + STObject& previousFields = (const_cast(node)) + .getField(sfPreviousFields) + .downcast(); + + // defensive case that should never be hit + if (!finalFields.isFieldPresent(sfTakerGets) || + !finalFields.isFieldPresent(sfTakerPays) || + !previousFields.isFieldPresent(sfTakerGets) || + !previousFields.isFieldPresent(sfTakerPays)) + continue; + + // filter out any offers deleted by explicit offer cancels + if (metaType == sfDeletedNode && offerCancel && + finalFields.getFieldU32(sfSequence) == *offerCancel) + continue; + + // compute the difference in gets and pays actually + // affected onto the offer + STAmount deltaGets = finalFields.getFieldAmount(sfTakerGets) - + previousFields.getFieldAmount(sfTakerGets); + STAmount deltaPays = finalFields.getFieldAmount(sfTakerPays) - + previousFields.getFieldAmount(sfTakerPays); + + std::string g{to_string(deltaGets.issue())}; + std::string p{to_string(deltaPays.issue())}; + + bool const noswap = + isXRP(deltaGets) ? true : (isXRP(deltaPays) ? false : (g < p)); + + STAmount first = noswap ? deltaGets : deltaPays; + STAmount second = noswap ? deltaPays : deltaGets; + + // defensively programmed, should (probably) never happen + if (second == beast::zero) + continue; + + STAmount rate = divide(first, second, noIssue()); + + if (first < beast::zero) + first = -first; + + if (second < beast::zero) + second = -second; + + std::stringstream ss; + if (noswap) + ss << g << "|" << p; + else + ss << p << "|" << g; + + std::string key{ss.str()}; + + if (tally.find(key) == tally.end()) + tally[key] = { + first, // side A vol + second, // side B vol + rate, // high + rate, // low + rate, // open + rate // close + }; + else + { + // increment volume + auto& entry = tally[key]; + + std::get<0>(entry) += first; // side A vol + std::get<1>(entry) += second; // side B vol + + if (std::get<2>(entry) < rate) // high + std::get<2>(entry) = rate; + + if (std::get<3>(entry) > rate) // low + std::get<3>(entry) = rate; + + std::get<5>(entry) = rate; // close + } + } + } + + Json::Value jvObj(Json::objectValue); + jvObj[jss::type] = "bookChanges"; + jvObj[jss::ledger_index] = lpAccepted->info().seq; + jvObj[jss::ledger_hash] = to_string(lpAccepted->info().hash); + jvObj[jss::ledger_time] = Json::Value::UInt( + lpAccepted->info().closeTime.time_since_epoch().count()); + + jvObj[jss::changes] = Json::arrayValue; + + for (auto const& entry : tally) + { + Json::Value& inner = jvObj[jss::changes].append(Json::objectValue); + + STAmount volA = std::get<0>(entry.second); + STAmount volB = std::get<1>(entry.second); + + inner[jss::currency_a] = + (isXRP(volA) ? "XRP_drops" : to_string(volA.issue())); + inner[jss::currency_b] = + (isXRP(volB) ? "XRP_drops" : to_string(volB.issue())); + + inner[jss::volume_a] = + (isXRP(volA) ? to_string(volA.xrp()) : to_string(volA.iou())); + inner[jss::volume_b] = + (isXRP(volB) ? to_string(volB.xrp()) : to_string(volB.iou())); + + inner[jss::high] = to_string(std::get<2>(entry.second).iou()); + inner[jss::low] = to_string(std::get<3>(entry.second).iou()); + inner[jss::open] = to_string(std::get<4>(entry.second).iou()); + inner[jss::close] = to_string(std::get<5>(entry.second).iou()); + } + + return jvObj; +} + +} // namespace RPC +} // namespace ripple + +#endif diff --git a/src/ripple/rpc/handlers/BookOffers.cpp b/src/ripple/rpc/handlers/BookOffers.cpp index a42a0de9924..e85b6029ba6 100644 --- a/src/ripple/rpc/handlers/BookOffers.cpp +++ b/src/ripple/rpc/handlers/BookOffers.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -200,4 +201,16 @@ doBookOffers(RPC::JsonContext& context) return jvResult; } +Json::Value +doBookChanges(RPC::JsonContext& context) +{ + auto res = RPC::getLedgerByContext(context); + + if (std::holds_alternative(res)) + return std::get(res); + + return RPC::computeBookChanges( + std::get>(res)); +} + } // namespace ripple diff --git a/src/ripple/rpc/handlers/Handlers.h b/src/ripple/rpc/handlers/Handlers.h index 1bb3be05654..3c00899d734 100644 --- a/src/ripple/rpc/handlers/Handlers.h +++ b/src/ripple/rpc/handlers/Handlers.h @@ -43,6 +43,8 @@ doAccountTxJson(RPC::JsonContext&); Json::Value doBookOffers(RPC::JsonContext&); Json::Value +doBookChanges(RPC::JsonContext&); +Json::Value doBlackList(RPC::JsonContext&); Json::Value doCanDelete(RPC::JsonContext&); diff --git a/src/ripple/rpc/handlers/LedgerRequest.cpp b/src/ripple/rpc/handlers/LedgerRequest.cpp index 3fe238e9212..88d26176ddc 100644 --- a/src/ripple/rpc/handlers/LedgerRequest.cpp +++ b/src/ripple/rpc/handlers/LedgerRequest.cpp @@ -26,7 +26,9 @@ #include #include #include +#include #include +#include namespace ripple { @@ -37,122 +39,17 @@ namespace ripple { Json::Value doLedgerRequest(RPC::JsonContext& context) { - if (context.app.config().reporting()) - return rpcError(rpcREPORTING_UNSUPPORTED); + auto res = getLedgerByContext(context); - auto const hasHash = context.params.isMember(jss::ledger_hash); - auto const hasIndex = context.params.isMember(jss::ledger_index); - std::uint32_t ledgerIndex = 0; + if (std::holds_alternative(res)) + return std::get(res); - auto& ledgerMaster = context.app.getLedgerMaster(); - LedgerHash ledgerHash; + auto const& ledger = std::get>(res); - if ((hasHash && hasIndex) || !(hasHash || hasIndex)) - { - return RPC::make_param_error( - "Exactly one of ledger_hash and ledger_index can be set."); - } - - context.loadType = Resource::feeHighBurdenRPC; - - if (hasHash) - { - auto const& jsonHash = context.params[jss::ledger_hash]; - if (!jsonHash.isString() || !ledgerHash.parseHex(jsonHash.asString())) - return RPC::invalid_field_error(jss::ledger_hash); - } - else - { - auto const& jsonIndex = context.params[jss::ledger_index]; - if (!jsonIndex.isInt()) - return RPC::invalid_field_error(jss::ledger_index); - - // We need a validated ledger to get the hash from the sequence - if (ledgerMaster.getValidatedLedgerAge() > - RPC::Tuning::maxValidatedLedgerAge) - { - if (context.apiVersion == 1) - return rpcError(rpcNO_CURRENT); - return rpcError(rpcNOT_SYNCED); - } - - ledgerIndex = jsonIndex.asInt(); - auto ledger = ledgerMaster.getValidatedLedger(); - - if (ledgerIndex >= ledger->info().seq) - return RPC::make_param_error("Ledger index too large"); - if (ledgerIndex <= 0) - return RPC::make_param_error("Ledger index too small"); - - auto const j = context.app.journal("RPCHandler"); - // Try to get the hash of the desired ledger from the validated ledger - auto neededHash = hashOfSeq(*ledger, ledgerIndex, j); - if (!neededHash) - { - // Find a ledger more likely to have the hash of the desired ledger - auto const refIndex = getCandidateLedger(ledgerIndex); - auto refHash = hashOfSeq(*ledger, refIndex, j); - assert(refHash); - - ledger = ledgerMaster.getLedgerByHash(*refHash); - if (!ledger) - { - // We don't have the ledger we need to figure out which ledger - // they want. Try to get it. - - if (auto il = context.app.getInboundLedgers().acquire( - *refHash, refIndex, InboundLedger::Reason::GENERIC)) - { - Json::Value jvResult = RPC::make_error( - rpcLGR_NOT_FOUND, - "acquiring ledger containing requested index"); - jvResult[jss::acquiring] = - getJson(LedgerFill(*il, &context)); - return jvResult; - } - - if (auto il = context.app.getInboundLedgers().find(*refHash)) - { - Json::Value jvResult = RPC::make_error( - rpcLGR_NOT_FOUND, - "acquiring ledger containing requested index"); - jvResult[jss::acquiring] = il->getJson(0); - return jvResult; - } - - // Likely the app is shutting down - return Json::Value(); - } - - neededHash = hashOfSeq(*ledger, ledgerIndex, j); - } - assert(neededHash); - ledgerHash = neededHash ? *neededHash : beast::zero; // kludge - } - - // Try to get the desired ledger - // Verify all nodes even if we think we have it - auto ledger = context.app.getInboundLedgers().acquire( - ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC); - - // In standalone mode, accept the ledger from the ledger cache - if (!ledger && context.app.config().standalone()) - ledger = ledgerMaster.getLedgerByHash(ledgerHash); - - if (ledger) - { - // We already had the entire ledger verified/acquired - Json::Value jvResult; - jvResult[jss::ledger_index] = ledger->info().seq; - addJson(jvResult, {*ledger, &context, 0}); - return jvResult; - } - - if (auto il = context.app.getInboundLedgers().find(ledgerHash)) - return il->getJson(0); - - return RPC::make_error( - rpcNOT_READY, "findCreate failed to return an inbound ledger"); + Json::Value jvResult; + jvResult[jss::ledger_index] = ledger->info().seq; + addJson(jvResult, {*ledger, &context, 0}); + return jvResult; } } // namespace ripple diff --git a/src/ripple/rpc/handlers/Subscribe.cpp b/src/ripple/rpc/handlers/Subscribe.cpp index a3c1d1e1c21..f17aa62b626 100644 --- a/src/ripple/rpc/handlers/Subscribe.cpp +++ b/src/ripple/rpc/handlers/Subscribe.cpp @@ -136,6 +136,10 @@ doSubscribe(RPC::JsonContext& context) { context.netOps.subLedger(ispSub, jvResult); } + else if (streamName == "book_changes") + { + context.netOps.subBookChanges(ispSub); + } else if (streamName == "manifests") { context.netOps.subManifests(ispSub); diff --git a/src/ripple/rpc/impl/Handler.cpp b/src/ripple/rpc/impl/Handler.cpp index 15f2ea8f856..17a15eed31b 100644 --- a/src/ripple/rpc/impl/Handler.cpp +++ b/src/ripple/rpc/impl/Handler.cpp @@ -72,6 +72,7 @@ Handler const handlerArray[]{ {"account_offers", byRef(&doAccountOffers), Role::USER, NO_CONDITION}, {"account_tx", byRef(&doAccountTxJson), Role::USER, NO_CONDITION}, {"blacklist", byRef(&doBlackList), Role::ADMIN, NO_CONDITION}, + {"book_changes", byRef(&doBookChanges), Role::USER, NO_CONDITION}, {"book_offers", byRef(&doBookOffers), Role::USER, NO_CONDITION}, {"can_delete", byRef(&doCanDelete), Role::ADMIN, NO_CONDITION}, {"channel_authorize", byRef(&doChannelAuthorize), Role::USER, NO_CONDITION}, diff --git a/src/ripple/rpc/impl/RPCHandler.cpp b/src/ripple/rpc/impl/RPCHandler.cpp index b04a6f0ed2a..c7984f8309c 100644 --- a/src/ripple/rpc/impl/RPCHandler.cpp +++ b/src/ripple/rpc/impl/RPCHandler.cpp @@ -17,7 +17,9 @@ */ //============================================================================== +#include #include +#include #include #include #include @@ -30,14 +32,17 @@ #include #include #include +#include #include #include +#include #include #include #include #include #include #include +#include namespace ripple { namespace RPC { diff --git a/src/ripple/rpc/impl/RPCHelpers.cpp b/src/ripple/rpc/impl/RPCHelpers.cpp index 6958ce9d9bb..77255a5e6f6 100644 --- a/src/ripple/rpc/impl/RPCHelpers.cpp +++ b/src/ripple/rpc/impl/RPCHelpers.cpp @@ -18,6 +18,7 @@ //============================================================================== #include +#include #include #include #include @@ -31,6 +32,7 @@ #include #include +#include #include namespace ripple { @@ -956,5 +958,119 @@ getAPIVersionNumber(Json::Value const& jv, bool betaEnabled) return requestedVersion.asUInt(); } +std::variant, Json::Value> +getLedgerByContext(RPC::JsonContext& context) +{ + if (context.app.config().reporting()) + return rpcError(rpcREPORTING_UNSUPPORTED); + + auto const hasHash = context.params.isMember(jss::ledger_hash); + auto const hasIndex = context.params.isMember(jss::ledger_index); + std::uint32_t ledgerIndex = 0; + + auto& ledgerMaster = context.app.getLedgerMaster(); + LedgerHash ledgerHash; + + if ((hasHash && hasIndex) || !(hasHash || hasIndex)) + { + return RPC::make_param_error( + "Exactly one of ledger_hash and ledger_index can be set."); + } + + context.loadType = Resource::feeHighBurdenRPC; + + if (hasHash) + { + auto const& jsonHash = context.params[jss::ledger_hash]; + if (!jsonHash.isString() || !ledgerHash.parseHex(jsonHash.asString())) + return RPC::invalid_field_error(jss::ledger_hash); + } + else + { + auto const& jsonIndex = context.params[jss::ledger_index]; + if (!jsonIndex.isInt()) + return RPC::invalid_field_error(jss::ledger_index); + + // We need a validated ledger to get the hash from the sequence + if (ledgerMaster.getValidatedLedgerAge() > + RPC::Tuning::maxValidatedLedgerAge) + { + if (context.apiVersion == 1) + return rpcError(rpcNO_CURRENT); + return rpcError(rpcNOT_SYNCED); + } + + ledgerIndex = jsonIndex.asInt(); + auto ledger = ledgerMaster.getValidatedLedger(); + + if (ledgerIndex >= ledger->info().seq) + return RPC::make_param_error("Ledger index too large"); + if (ledgerIndex <= 0) + return RPC::make_param_error("Ledger index too small"); + + auto const j = context.app.journal("RPCHandler"); + // Try to get the hash of the desired ledger from the validated ledger + auto neededHash = hashOfSeq(*ledger, ledgerIndex, j); + if (!neededHash) + { + // Find a ledger more likely to have the hash of the desired ledger + auto const refIndex = getCandidateLedger(ledgerIndex); + auto refHash = hashOfSeq(*ledger, refIndex, j); + assert(refHash); + + ledger = ledgerMaster.getLedgerByHash(*refHash); + if (!ledger) + { + // We don't have the ledger we need to figure out which ledger + // they want. Try to get it. + + if (auto il = context.app.getInboundLedgers().acquire( + *refHash, refIndex, InboundLedger::Reason::GENERIC)) + { + Json::Value jvResult = RPC::make_error( + rpcLGR_NOT_FOUND, + "acquiring ledger containing requested index"); + jvResult[jss::acquiring] = + getJson(LedgerFill(*il, &context)); + return jvResult; + } + + if (auto il = context.app.getInboundLedgers().find(*refHash)) + { + Json::Value jvResult = RPC::make_error( + rpcLGR_NOT_FOUND, + "acquiring ledger containing requested index"); + jvResult[jss::acquiring] = il->getJson(0); + return jvResult; + } + + // Likely the app is shutting down + return Json::Value(); + } + + neededHash = hashOfSeq(*ledger, ledgerIndex, j); + } + assert(neededHash); + ledgerHash = neededHash ? *neededHash : beast::zero; // kludge + } + + // Try to get the desired ledger + // Verify all nodes even if we think we have it + auto ledger = context.app.getInboundLedgers().acquire( + ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC); + + // In standalone mode, accept the ledger from the ledger cache + if (!ledger && context.app.config().standalone()) + ledger = ledgerMaster.getLedgerByHash(ledgerHash); + + if (ledger) + return ledger; + + if (auto il = context.app.getInboundLedgers().find(ledgerHash)) + return il->getJson(0); + + return RPC::make_error( + rpcNOT_READY, "findCreate failed to return an inbound ledger"); +} } // namespace RPC } // namespace ripple diff --git a/src/ripple/rpc/impl/RPCHelpers.h b/src/ripple/rpc/impl/RPCHelpers.h index e3d44c2e730..2aa62f3474a 100644 --- a/src/ripple/rpc/impl/RPCHelpers.h +++ b/src/ripple/rpc/impl/RPCHelpers.h @@ -31,6 +31,7 @@ #include #include #include +#include namespace Json { class Value; @@ -287,6 +288,11 @@ chooseLedgerEntryType(Json::Value const& params); unsigned int getAPIVersionNumber(const Json::Value& value, bool betaEnabled); +/** Return a ledger based on ledger_hash or ledger_index, + or an RPC error */ +std::variant, Json::Value> +getLedgerByContext(RPC::JsonContext& context); + } // namespace RPC } // namespace ripple