// This interface is deprecated. Json::Value doRipplePathFind (RPC::Context& context) { RPC::LegacyPathFind lpf (context.role == Role::ADMIN); if (!lpf.isOk ()) return rpcError (rpcTOO_BUSY); context.loadType = Resource::feeHighBurdenRPC; RippleAddress raSrc; RippleAddress raDst; STAmount saDstAmount; Ledger::pointer lpLedger; Json::Value jvResult; if (getConfig().RUN_STANDALONE || context.params.isMember(jss::ledger) || context.params.isMember(jss::ledger_index) || context.params.isMember(jss::ledger_hash)) { // The caller specified a ledger jvResult = RPC::lookupLedger ( context.params, lpLedger, context.netOps); if (!lpLedger) return jvResult; } if (!context.params.isMember (jss::source_account)) { jvResult = rpcError (rpcSRC_ACT_MISSING); } else if (!context.params[jss::source_account].isString () || !raSrc.setAccountID ( context.params[jss::source_account].asString ())) { jvResult = rpcError (rpcSRC_ACT_MALFORMED); } else if (!context.params.isMember (jss::destination_account)) { jvResult = rpcError (rpcDST_ACT_MISSING); } else if (!context.params[jss::destination_account].isString () || !raDst.setAccountID ( context.params[jss::destination_account].asString ())) { jvResult = rpcError (rpcDST_ACT_MALFORMED); } else if ( // Parse saDstAmount. !context.params.isMember (jss::destination_amount) || ! amountFromJsonNoThrow(saDstAmount, context.params[jss::destination_amount]) || saDstAmount <= zero || (!isXRP(saDstAmount.getCurrency ()) && (!saDstAmount.getIssuer () || noAccount() == saDstAmount.getIssuer ()))) { WriteLog (lsINFO, RPCHandler) << "Bad destination_amount."; jvResult = rpcError (rpcINVALID_PARAMS); } else if ( // Checks on source_currencies. context.params.isMember (jss::source_currencies) && (!context.params[jss::source_currencies].isArray () || !context.params[jss::source_currencies].size ()) // Don't allow empty currencies. ) { WriteLog (lsINFO, RPCHandler) << "Bad source_currencies."; jvResult = rpcError (rpcINVALID_PARAMS); } else { context.loadType = Resource::feeHighBurdenRPC; RippleLineCache::pointer cache; if (lpLedger) { // The caller specified a ledger lpLedger = std::make_shared<Ledger> (std::ref (*lpLedger), false); cache = std::make_shared<RippleLineCache>(lpLedger); } else { // The closed ledger is recent and any nodes made resident // have the best chance to persist lpLedger = context.netOps.getClosedLedger(); cache = getApp().getPathRequests().getLineCache(lpLedger, false); } Json::Value jvSrcCurrencies; if (context.params.isMember (jss::source_currencies)) { jvSrcCurrencies = context.params[jss::source_currencies]; } else { jvSrcCurrencies = buildSrcCurrencies(raSrc, cache); } // Fill in currencies destination will accept Json::Value jvDestCur (Json::arrayValue); // TODO(tom): this could be optimized the same way that // PathRequest::doUpdate() is - if we don't obsolete this code first. auto usDestCurrID = accountDestCurrencies (raDst, cache, true); for (auto const& uCurrency: usDestCurrID) jvDestCur.append (to_string (uCurrency)); jvResult[jss::destination_currencies] = jvDestCur; jvResult[jss::destination_account] = raDst.humanAccountID (); int level = getConfig().PATH_SEARCH_OLD; if ((getConfig().PATH_SEARCH_MAX > level) && !getApp().getFeeTrack().isLoadedLocal()) { ++level; } if (context.params.isMember(jss::search_depth) && context.params[jss::search_depth].isIntegral()) { int rLev = context.params[jss::search_depth].asInt (); if ((rLev < level) || (context.role == Role::ADMIN)) level = rLev; } auto contextPaths = context.params.isMember(jss::paths) ? boost::optional<Json::Value>(context.params[jss::paths]) : boost::optional<Json::Value>(boost::none); auto pathFindResult = ripplePathFind(cache, raSrc, raDst, saDstAmount, lpLedger, jvSrcCurrencies, contextPaths, level); if (!pathFindResult.first) return pathFindResult.second; // Each alternative differs by source currency. jvResult[jss::alternatives] = pathFindResult.second; } WriteLog (lsDEBUG, RPCHandler) << boost::str (boost::format ("ripple_path_find< %s") % jvResult); return jvResult; }
// This interface is deprecated. Json::Value doRipplePathFind (RPC::Context& context) { RPC::LegacyPathFind lpf (context.role == Role::ADMIN); if (!lpf.isOk ()) return rpcError (rpcTOO_BUSY); context.loadType = Resource::feeHighBurdenRPC; RippleAddress raSrc; RippleAddress raDst; STAmount saDstAmount; Ledger::pointer lpLedger; Json::Value jvResult; if (getConfig().RUN_STANDALONE || context.params.isMember(jss::ledger) || context.params.isMember(jss::ledger_index) || context.params.isMember(jss::ledger_hash)) { // The caller specified a ledger jvResult = RPC::lookupLedger ( context.params, lpLedger, context.netOps); if (!lpLedger) return jvResult; } if (!context.params.isMember ("source_account")) { jvResult = rpcError (rpcSRC_ACT_MISSING); } else if (!context.params["source_account"].isString () || !raSrc.setAccountID ( context.params["source_account"].asString ())) { jvResult = rpcError (rpcSRC_ACT_MALFORMED); } else if (!context.params.isMember ("destination_account")) { jvResult = rpcError (rpcDST_ACT_MISSING); } else if (!context.params["destination_account"].isString () || !raDst.setAccountID ( context.params["destination_account"].asString ())) { jvResult = rpcError (rpcDST_ACT_MALFORMED); } else if ( // Parse saDstAmount. !context.params.isMember ("destination_amount") || ! amountFromJsonNoThrow(saDstAmount, context.params["destination_amount"]) || saDstAmount <= zero || (!isXRP(saDstAmount.getCurrency ()) && (!saDstAmount.getIssuer () || noAccount() == saDstAmount.getIssuer ()))) { WriteLog (lsINFO, RPCHandler) << "Bad destination_amount."; jvResult = rpcError (rpcINVALID_PARAMS); } else if ( // Checks on source_currencies. context.params.isMember ("source_currencies") && (!context.params["source_currencies"].isArray () || !context.params["source_currencies"].size ()) // Don't allow empty currencies. ) { WriteLog (lsINFO, RPCHandler) << "Bad source_currencies."; jvResult = rpcError (rpcINVALID_PARAMS); } else { context.loadType = Resource::feeHighBurdenRPC; RippleLineCache::pointer cache; if (lpLedger) { // The caller specified a ledger lpLedger = std::make_shared<Ledger> (std::ref (*lpLedger), false); cache = std::make_shared<RippleLineCache>(lpLedger); } else { // The closed ledger is recent and any nodes made resident // have the best chance to persist lpLedger = context.netOps.getClosedLedger(); cache = getApp().getPathRequests().getLineCache(lpLedger, false); } Json::Value jvSrcCurrencies; if (context.params.isMember ("source_currencies")) { jvSrcCurrencies = context.params["source_currencies"]; } else { auto currencies = accountSourceCurrencies (raSrc, cache, true); jvSrcCurrencies = Json::Value (Json::arrayValue); for (auto const& uCurrency: currencies) { Json::Value jvCurrency (Json::objectValue); jvCurrency["currency"] = to_string(uCurrency); jvSrcCurrencies.append (jvCurrency); } } // Fill in currencies destination will accept Json::Value jvDestCur (Json::arrayValue); // TODO(tom): this could be optimized the same way that // PathRequest::doUpdate() is - if we don't obsolete this code first. auto usDestCurrID = accountDestCurrencies (raDst, cache, true); for (auto const& uCurrency: usDestCurrID) jvDestCur.append (to_string (uCurrency)); jvResult["destination_currencies"] = jvDestCur; jvResult["destination_account"] = raDst.humanAccountID (); Json::Value jvArray (Json::arrayValue); int level = getConfig().PATH_SEARCH_OLD; if ((getConfig().PATH_SEARCH_MAX > level) && !getApp().getFeeTrack().isLoadedLocal()) { ++level; } if (context.params.isMember("search_depth") && context.params["search_depth"].isIntegral()) { int rLev = context.params["search_depth"].asInt (); if ((rLev < level) || (context.role == Role::ADMIN)) level = rLev; } FindPaths fp ( cache, raSrc.getAccountID(), raDst.getAccountID(), saDstAmount, level, 4); // max paths for (unsigned int i = 0; i != jvSrcCurrencies.size (); ++i) { Json::Value jvSource = jvSrcCurrencies[i]; Currency uSrcCurrencyID; Account uSrcIssuerID; if (!jvSource.isObject ()) return rpcError (rpcINVALID_PARAMS); // Parse mandatory currency. if (!jvSource.isMember ("currency") || !to_currency ( uSrcCurrencyID, jvSource["currency"].asString ())) { WriteLog (lsINFO, RPCHandler) << "Bad currency."; return rpcError (rpcSRC_CUR_MALFORMED); } if (uSrcCurrencyID.isNonZero ()) uSrcIssuerID = raSrc.getAccountID (); // Parse optional issuer. if (jvSource.isMember ("issuer") && ((!jvSource["issuer"].isString () || !to_issuer (uSrcIssuerID, jvSource["issuer"].asString ())) || (uSrcIssuerID.isZero () != uSrcCurrencyID.isZero ()) || (noAccount() == uSrcIssuerID))) { WriteLog (lsINFO, RPCHandler) << "Bad issuer."; return rpcError (rpcSRC_ISR_MALFORMED); } STPathSet spsComputed; if (context.params.isMember("paths")) { Json::Value pathSet = Json::objectValue; pathSet["Paths"] = context.params["paths"]; STParsedJSONObject paths ("pathSet", pathSet); if (paths.object.get() == nullptr) return paths.error; else { spsComputed = paths.object.get()->getFieldPathSet (sfPaths); WriteLog (lsTRACE, RPCHandler) << "ripple_path_find: Paths: " << spsComputed.getJson (0); } } STPath fullLiquidityPath; auto valid = fp.findPathsForIssue ( {uSrcCurrencyID, uSrcIssuerID}, spsComputed, fullLiquidityPath); if (!valid) { WriteLog (lsWARNING, RPCHandler) << "ripple_path_find: No paths found."; } else { auto& issuer = isXRP (uSrcIssuerID) ? isXRP (uSrcCurrencyID) ? // Default to source account. xrpAccount() : Account (raSrc.getAccountID ()) : uSrcIssuerID; // Use specifed issuer. STAmount saMaxAmount ({uSrcCurrencyID, issuer}, 1); saMaxAmount.negate (); LedgerEntrySet lesSandbox (lpLedger, tapNONE); auto rc = path::RippleCalc::rippleCalculate ( lesSandbox, saMaxAmount, // --> Amount to send is unlimited // to get an estimate. saDstAmount, // --> Amount to deliver. raDst.getAccountID (), // --> Account to deliver to. raSrc.getAccountID (), // --> Account sending from. spsComputed); // --> Path set. WriteLog (lsWARNING, RPCHandler) << "ripple_path_find:" << " saMaxAmount=" << saMaxAmount << " saDstAmount=" << saDstAmount << " saMaxAmountAct=" << rc.actualAmountIn << " saDstAmountAct=" << rc.actualAmountOut; if (fullLiquidityPath.size() > 0 && (rc.result() == terNO_LINE || rc.result() == tecPATH_PARTIAL)) { WriteLog (lsDEBUG, PathRequest) << "Trying with an extra path element"; spsComputed.push_back (fullLiquidityPath); lesSandbox.clear (); rc = path::RippleCalc::rippleCalculate ( lesSandbox, saMaxAmount, // --> Amount to send is unlimited // to get an estimate. saDstAmount, // --> Amount to deliver. raDst.getAccountID (), // --> Account to deliver to. raSrc.getAccountID (), // --> Account sending from. spsComputed); // --> Path set. WriteLog (lsDEBUG, PathRequest) << "Extra path element gives " << transHuman (rc.result ()); } if (rc.result () == tesSUCCESS) { Json::Value jvEntry (Json::objectValue); STPathSet spsCanonical; // Reuse the expanded as it would need to be calcuated // anyway to produce the canonical. (At least unless we // make a direct canonical.) jvEntry["source_amount"] = rc.actualAmountIn.getJson (0); jvEntry["paths_canonical"] = Json::arrayValue; jvEntry["paths_computed"] = spsComputed.getJson (0); jvArray.append (jvEntry); } else { std::string strToken; std::string strHuman; transResultInfo (rc.result (), strToken, strHuman); WriteLog (lsDEBUG, RPCHandler) << "ripple_path_find: " << strToken << " " << strHuman << " " << spsComputed.getJson (0); } } } // Each alternative differs by source currency. jvResult["alternatives"] = jvArray; } WriteLog (lsDEBUG, RPCHandler) << boost::str (boost::format ("ripple_path_find< %s") % jvResult); return jvResult; }
// This interface is deprecated. Json::Value doRipplePathFind (RPC::Context& context) { if (context.app.config().PATH_SEARCH_MAX == 0) return rpcError (rpcNOT_SUPPORTED); context.loadType = Resource::feeHighBurdenRPC; std::shared_ptr <ReadView const> lpLedger; Json::Value jvResult; if (! context.app.config().standalone() && ! context.params.isMember(jss::ledger) && ! context.params.isMember(jss::ledger_index) && ! context.params.isMember(jss::ledger_hash)) { // No ledger specified, use pathfinding defaults // and dispatch to pathfinding engine if (context.app.getLedgerMaster().getValidatedLedgerAge() > RPC::Tuning::maxValidatedLedgerAge) { return rpcError (rpcNO_NETWORK); } PathRequest::pointer request; lpLedger = context.ledgerMaster.getClosedLedger(); // It doesn't look like there's much odd happening here, but you should // be aware this code runs in a JobQueue::Coro, which is a coroutine. // And we may be flipping around between threads. Here's an overview: // // 1. We're running doRipplePathFind() due to a call to // ripple_path_find. doRipplePathFind() is currently running // inside of a JobQueue::Coro using a JobQueue thread. // // 2. doRipplePathFind's call to makeLegacyPathRequest() enqueues the // path-finding request. That request will (probably) run at some // indeterminate future time on a (probably different) JobQueue // thread. // // 3. As a continuation from that path-finding JobQueue thread, the // coroutine we're currently running in (!) is posted to the // JobQueue. Because it is a continuation, that post won't // happen until the path-finding request completes. // // 4. Once the continuation is enqueued, and we have reason to think // the path-finding job is likely to run, then the coroutine we're // running in yield()s. That means it surrenders its thread in // the JobQueue. The coroutine is suspended, but ready to run, // because it is kept resident by a shared_ptr in the // path-finding continuation. // // 5. If all goes well then path-finding runs on a JobQueue thread // and executes its continuation. The continuation posts this // same coroutine (!) to the JobQueue. // // 6. When the JobQueue calls this coroutine, this coroutine resumes // from the line below the coro->yield() and returns the // path-finding result. // // With so many moving parts, what could go wrong? // // Just in terms of the JobQueue refusing to add jobs at shutdown // there are two specific things that can go wrong. // // 1. The path-finding Job queued by makeLegacyPathRequest() might be // rejected (because we're shutting down). // // Fortunately this problem can be addressed by looking at the // return value of makeLegacyPathRequest(). If // makeLegacyPathRequest() cannot get a thread to run the path-find // on, then it returns an empty request. // // 2. The path-finding job might run, but the Coro::post() might be // rejected by the JobQueue (because we're shutting down). // // We handle this case by resuming (not posting) the Coro. // By resuming the Coro, we allow the Coro to run to completion // on the current thread instead of requiring that it run on a // new thread from the JobQueue. // // Both of these failure modes are hard to recreate in a unit test // because they are so dependent on inter-thread timing. However // the failure modes can be observed by synchronously (inside the // rippled source code) shutting down the application. The code to // do so looks like this: // // context.app.signalStop(); // while (! context.app.getJobQueue().jobCounter().joined()) { } // // The first line starts the process of shutting down the app. // The second line waits until no more jobs can be added to the // JobQueue before letting the thread continue. // // May 2017 jvResult = context.app.getPathRequests().makeLegacyPathRequest ( request, [&context] () { // Copying the shared_ptr keeps the coroutine alive up // through the return. Otherwise the storage under the // captured reference could evaporate when we return from // coroCopy->resume(). This is not strictly necessary, but // will make maintenance easier. std::shared_ptr<JobQueue::Coro> coroCopy {context.coro}; if (!coroCopy->post()) { // The post() failed, so we won't get a thread to let // the Coro finish. We'll call Coro::resume() so the // Coro can finish on our thread. Otherwise the // application will hang on shutdown. coroCopy->resume(); } }, context.consumer, lpLedger, context.params); if (request) { context.coro->yield(); jvResult = request->doStatus (context.params); } return jvResult; } // The caller specified a ledger jvResult = RPC::lookupLedger (lpLedger, context); if (! lpLedger) return jvResult; RPC::LegacyPathFind lpf (isUnlimited (context.role), context.app); if (! lpf.isOk ()) return rpcError (rpcTOO_BUSY); auto result = context.app.getPathRequests().doLegacyPathRequest ( context.consumer, lpLedger, context.params); for (auto &fieldName : jvResult.getMemberNames ()) result[fieldName] = std::move (jvResult[fieldName]); return result; }