void stpath_append_one (STPath& st, jtx::BookSpec const& book) { st.push_back(STPathElement({ boost::none, book.currency, book.account })); }
void stpath_append_one (STPath& st, jtx::IOU const& iou) { st.push_back(STPathElement({ iou.account.id(), iou.currency, boost::none })); }
// Set to an expanded path. // // terStatus = tesSUCCESS, temBAD_PATH, terNO_LINE, terNO_ACCOUNT, terNO_AUTH, or temBAD_PATH_LOOP void PathState::setExpanded ( const LedgerEntrySet& lesSource, const STPath& spSourcePath, const uint160& uReceiverID, const uint160& uSenderID ) { uQuality = 1; // Mark path as active. const uint160 uMaxCurrencyID = saInReq.getCurrency (); const uint160 uMaxIssuerID = saInReq.getIssuer (); const uint160 uOutCurrencyID = saOutReq.getCurrency (); const uint160 uOutIssuerID = saOutReq.getIssuer (); const uint160 uSenderIssuerID = !!uMaxCurrencyID ? uSenderID : ACCOUNT_STR; // Sender is always issuer for non-STR. WriteLog (lsTRACE, RippleCalc) << "setExpanded> " << spSourcePath.getJson (0); lesEntries = lesSource.duplicate (); terStatus = tesSUCCESS; // STR with issuer is malformed. if ((!uMaxCurrencyID && !!uMaxIssuerID) || (!uOutCurrencyID && !!uOutIssuerID)) terStatus = temBAD_PATH; // Push sending node. // For non-STR, issuer is always sending account. // - Trying to expand, not-compact. // - Every issuer will be traversed through. if (tesSUCCESS == terStatus) terStatus = pushNode ( !!uMaxCurrencyID ? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer : STPathElement::typeAccount | STPathElement::typeCurrency, uSenderID, uMaxCurrencyID, // Max specifies the currency. uSenderIssuerID); WriteLog (lsDEBUG, RippleCalc) << "setExpanded: pushed:" << " account=" << RippleAddress::createHumanAccountID (uSenderID) << " currency=" << STAmount::createHumanCurrency (uMaxCurrencyID) << " issuer=" << RippleAddress::createHumanAccountID (uSenderIssuerID); if (tesSUCCESS == terStatus && uMaxIssuerID != uSenderIssuerID) // Issuer was not same as sender. { // May have an implied account node. // - If it was STR, then issuers would have matched. // Figure out next node properties for implied node. const uint160 uNxtCurrencyID = spSourcePath.size () ? spSourcePath.getElement (0).getCurrency () // Use next node. : uOutCurrencyID; // Use send. const uint160 uNxtAccountID = spSourcePath.size () ? spSourcePath.getElement (0).getAccountID () : !!uOutCurrencyID ? uOutIssuerID == uReceiverID ? uReceiverID : uOutIssuerID // Use implied node. : ACCOUNT_STR; WriteLog (lsDEBUG, RippleCalc) << "setExpanded: implied check:" << " uMaxIssuerID=" << RippleAddress::createHumanAccountID (uMaxIssuerID) << " uSenderIssuerID=" << RippleAddress::createHumanAccountID (uSenderIssuerID) << " uNxtCurrencyID=" << STAmount::createHumanCurrency (uNxtCurrencyID) << " uNxtAccountID=" << RippleAddress::createHumanAccountID (uNxtAccountID); // Can't just use push implied, because it can't compensate for next account. if (!uNxtCurrencyID // Next is STR, offer next. Must go through issuer. || uMaxCurrencyID != uNxtCurrencyID // Next is different currency, offer next... || uMaxIssuerID != uNxtAccountID) // Next is not implied issuer { WriteLog (lsDEBUG, RippleCalc) << "setExpanded: sender implied:" << " account=" << RippleAddress::createHumanAccountID (uMaxIssuerID) << " currency=" << STAmount::createHumanCurrency (uMaxCurrencyID) << " issuer=" << RippleAddress::createHumanAccountID (uMaxIssuerID); // Add account implied by SendMax. terStatus = pushNode ( !!uMaxCurrencyID ? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer : STPathElement::typeAccount | STPathElement::typeCurrency, uMaxIssuerID, uMaxCurrencyID, uMaxIssuerID); } } BOOST_FOREACH (const STPathElement & speElement, spSourcePath) { if (tesSUCCESS == terStatus) { WriteLog (lsTRACE, RippleCalc) << "setExpanded: element in path"; terStatus = pushNode ( speElement.getNodeType (), speElement.getAccountID (), speElement.getCurrency (), speElement.getIssuerID ()); } } const Node& pnPrv = vpnNodes.back (); if (tesSUCCESS == terStatus && !!uOutCurrencyID // Next is not STR && uOutIssuerID != uReceiverID // Out issuer is not receiver && (pnPrv.uCurrencyID != uOutCurrencyID // Previous will be an offer. || pnPrv.uAccountID != uOutIssuerID)) // Need the implied issuer. { // Add implied account. WriteLog (lsDEBUG, RippleCalc) << "setExpanded: receiver implied:" << " account=" << RippleAddress::createHumanAccountID (uOutIssuerID) << " currency=" << STAmount::createHumanCurrency (uOutCurrencyID) << " issuer=" << RippleAddress::createHumanAccountID (uOutIssuerID); terStatus = pushNode ( !!uOutCurrencyID ? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer : STPathElement::typeAccount | STPathElement::typeCurrency, uOutIssuerID, uOutCurrencyID, uOutIssuerID); } if (tesSUCCESS == terStatus) { // Create receiver node. // Last node is always an account. terStatus = pushNode ( !!uOutCurrencyID ? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer : STPathElement::typeAccount | STPathElement::typeCurrency, uReceiverID, // Receive to output uOutCurrencyID, // Desired currency uReceiverID); } if (tesSUCCESS == terStatus) { // Look for first mention of source in nodes and detect loops. // Note: The output is not allowed to be a source. const unsigned int uNodes = vpnNodes.size (); for (unsigned int uNode = 0; tesSUCCESS == terStatus && uNode != uNodes; ++uNode) { const Node& pnCur = vpnNodes[uNode]; if (!umForward.insert (std::make_pair (std::make_tuple (pnCur.uAccountID, pnCur.uCurrencyID, pnCur.uIssuerID), uNode)).second) { // Failed to insert. Have a loop. WriteLog (lsDEBUG, RippleCalc) << "setExpanded: loop detected: " << getJson (); terStatus = temBAD_PATH_LOOP; } } } WriteLog (lsDEBUG, RippleCalc) << "setExpanded:" << " in=" << STAmount::createHumanCurrency (uMaxCurrencyID) << "/" << RippleAddress::createHumanAccountID (uMaxIssuerID) << " out=" << STAmount::createHumanCurrency (uOutCurrencyID) << "/" << RippleAddress::createHumanAccountID (uOutIssuerID) << ": " << getJson (); }
// Set this object to be an expanded path from spSourcePath - take the implied // nodes and makes them explicit. It also sanitizes the path. // // There are only two types of nodes: account nodes and order books nodes. // // You can infer some nodes automatically. If you're paying me bitstamp USD, // then there must be an intermediate bitstamp node. // // If you have accounts A and B, and they're delivery currency issued by C, then // there must be a node with account C in the middle. // // If you're paying USD and getting bitcoins, there has to be an order book in // between. // // terStatus = tesSUCCESS, temBAD_PATH, terNO_LINE, terNO_ACCOUNT, terNO_AUTH, // or temBAD_PATH_LOOP TER PathState::expandPath ( STPath const& spSourcePath, AccountID const& uReceiverID, AccountID const& uSenderID) { uQuality = 1; // Mark path as active. Currency const& uMaxCurrencyID = saInReq.getCurrency (); AccountID const& uMaxIssuerID = saInReq.getIssuer (); Currency const& currencyOutID = saOutReq.getCurrency (); AccountID const& issuerOutID = saOutReq.getIssuer (); AccountID const& uSenderIssuerID = isXRP(uMaxCurrencyID) ? xrpAccount() : uSenderID; // Sender is always issuer for non-XRP. JLOG (j_.trace) << "expandPath> " << spSourcePath.getJson (0); terStatus = tesSUCCESS; // XRP with issuer is malformed. if ((isXRP (uMaxCurrencyID) && !isXRP (uMaxIssuerID)) || (isXRP (currencyOutID) && !isXRP (issuerOutID))) { JLOG (j_.debug) << "expandPath> issuer with XRP"; terStatus = temBAD_PATH; } // Push sending node. // For non-XRP, issuer is always sending account. // - Trying to expand, not-compact. // - Every issuer will be traversed through. if (terStatus == tesSUCCESS) { terStatus = pushNode ( !isXRP(uMaxCurrencyID) ? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer : STPathElement::typeAccount | STPathElement::typeCurrency, uSenderID, uMaxCurrencyID, // Max specifies the currency. uSenderIssuerID); } JLOG (j_.debug) << "expandPath: pushed:" << " account=" << uSenderID << " currency=" << uMaxCurrencyID << " issuer=" << uSenderIssuerID; // Issuer was not same as sender. if (tesSUCCESS == terStatus && uMaxIssuerID != uSenderIssuerID) { // May have an implied account node. // - If it was XRP, then issuers would have matched. // Figure out next node properties for implied node. const auto uNxtCurrencyID = spSourcePath.size () ? Currency(spSourcePath.front ().getCurrency ()) // Use next node. : currencyOutID; // Use send. // TODO(tom): complexify this next logic further in case someone // understands it. const auto nextAccountID = spSourcePath.size () ? AccountID(spSourcePath. front ().getAccountID ()) : !isXRP(currencyOutID) ? (issuerOutID == uReceiverID) ? AccountID(uReceiverID) : AccountID(issuerOutID) // Use implied node. : xrpAccount(); JLOG (j_.debug) << "expandPath: implied check:" << " uMaxIssuerID=" << uMaxIssuerID << " uSenderIssuerID=" << uSenderIssuerID << " uNxtCurrencyID=" << uNxtCurrencyID << " nextAccountID=" << nextAccountID; // Can't just use push implied, because it can't compensate for next // account. if (!uNxtCurrencyID // Next is XRP, offer next. Must go through issuer. || uMaxCurrencyID != uNxtCurrencyID // Next is different currency, offer next... || uMaxIssuerID != nextAccountID) // Next is not implied issuer { JLOG (j_.debug) << "expandPath: sender implied:" << " account=" << uMaxIssuerID << " currency=" << uMaxCurrencyID << " issuer=" << uMaxIssuerID; // Add account implied by SendMax. terStatus = pushNode ( !isXRP(uMaxCurrencyID) ? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer : STPathElement::typeAccount | STPathElement::typeCurrency, uMaxIssuerID, uMaxCurrencyID, uMaxIssuerID); } } for (auto & speElement: spSourcePath) { if (terStatus == tesSUCCESS) { JLOG (j_.trace) << "expandPath: element in path"; terStatus = pushNode ( speElement.getNodeType (), speElement.getAccountID (), speElement.getCurrency (), speElement.getIssuerID ()); } } if (terStatus == tesSUCCESS && !isXRP(currencyOutID) // Next is not XRP && issuerOutID != uReceiverID) // Out issuer is not receiver { assert (!nodes_.empty ()); auto const& backNode = nodes_.back (); if (backNode.issue_.currency != currencyOutID // Previous will be offer || backNode.account_ != issuerOutID) // Need implied issuer { // Add implied account. JLOG (j_.debug) << "expandPath: receiver implied:" << " account=" << issuerOutID << " currency=" << currencyOutID << " issuer=" << issuerOutID; terStatus = pushNode ( !isXRP(currencyOutID) ? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer : STPathElement::typeAccount | STPathElement::typeCurrency, issuerOutID, currencyOutID, issuerOutID); } } if (terStatus == tesSUCCESS) { // Create receiver node. // Last node is always an account. terStatus = pushNode ( !isXRP(currencyOutID) ? STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer : STPathElement::typeAccount | STPathElement::typeCurrency, uReceiverID, // Receive to output currencyOutID, // Desired currency uReceiverID); } if (terStatus == tesSUCCESS) { // Look for first mention of source in nodes and detect loops. // Note: The output is not allowed to be a source. unsigned int index = 0; for (auto& node: nodes_) { AccountIssue accountIssue (node.account_, node.issue_); if (!umForward.insert ({accountIssue, index++}).second) { // Failed to insert. Have a loop. JLOG (j_.debug) << "expandPath: loop detected: " << getJson (); terStatus = temBAD_PATH_LOOP; break; } } } JLOG (j_.debug) << "expandPath:" << " in=" << uMaxCurrencyID << "/" << uMaxIssuerID << " out=" << currencyOutID << "/" << issuerOutID << ": " << getJson (); return terStatus; }
Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast) { m_journal.debug << iIdentifier << " update " << (fast ? "fast" : "normal"); ScopedLockType sl (mLock); if (!isValid (cache)) return jvStatus; jvStatus = Json::objectValue; auto sourceCurrencies = sciSourceCurrencies; if (sourceCurrencies.empty ()) { auto usCurrencies = usAccountSourceCurrencies (raSrcAccount, cache, true); bool sameAccount = raSrcAccount == raDstAccount; for (auto const& c: usCurrencies) { if (!sameAccount || (c != saDstAmount.getCurrency ())) { if (c.isZero ()) sourceCurrencies.insert (std::make_pair (c, xrpAccount())); else sourceCurrencies.insert (std::make_pair (c, raSrcAccount.getAccountID ())); } } } jvStatus["source_account"] = raSrcAccount.humanAccountID (); jvStatus["destination_account"] = raDstAccount.humanAccountID (); jvStatus["destination_amount"] = saDstAmount.getJson (0); if (!jvId.isNull ()) jvStatus["id"] = jvId; Json::Value jvArray = Json::arrayValue; int iLevel = iLastLevel; bool loaded = getApp().getFeeTrack().isLoadedLocal(); if (iLevel == 0) { // first pass if (loaded || fast) iLevel = getConfig().PATH_SEARCH_FAST; else iLevel = getConfig().PATH_SEARCH; } else if ((iLevel == getConfig().PATH_SEARCH_FAST) && !fast) { // leaving fast pathfinding iLevel = getConfig().PATH_SEARCH; if (loaded && (iLevel > getConfig().PATH_SEARCH_FAST)) --iLevel; } else if (bLastSuccess) { // decrement, if possible if (iLevel > getConfig().PATH_SEARCH || (loaded && (iLevel > getConfig().PATH_SEARCH_FAST))) --iLevel; } else { // adjust as needed if (!loaded && (iLevel < getConfig().PATH_SEARCH_MAX)) ++iLevel; if (loaded && (iLevel > getConfig().PATH_SEARCH_FAST)) --iLevel; } m_journal.debug << iIdentifier << " processing at level " << iLevel; bool found = false; for (auto const& currIssuer: sourceCurrencies) { { STAmount test ({currIssuer.first, currIssuer.second}, 1); if (m_journal.debug) { m_journal.debug << iIdentifier << " Trying to find paths: " << test.getFullText (); } } bool valid; STPathSet& spsPaths = mContext[currIssuer]; Pathfinder pf (cache, raSrcAccount, raDstAccount, currIssuer.first, currIssuer.second, saDstAmount, valid); CondLog (!valid, lsDEBUG, PathRequest) << iIdentifier << " PF request not valid"; STPath extraPath; if (valid && pf.findPaths (iLevel, 4, spsPaths, extraPath)) { LedgerEntrySet lesSandbox (cache->getLedger (), tapNONE); PathState::List pathStateList; STAmount saMaxAmountAct; STAmount saDstAmountAct; auto& account = currIssuer.second.isNonZero () ? Account(currIssuer.second) : isXRP (currIssuer.first) ? xrpAccount() : raSrcAccount.getAccountID (); STAmount saMaxAmount ({currIssuer.first, account}, 1); saMaxAmount.negate (); m_journal.debug << iIdentifier << " Paths found, calling rippleCalc"; TER resultCode = path::rippleCalculate ( lesSandbox, saMaxAmountAct, saDstAmountAct, pathStateList, saMaxAmount, saDstAmount, raDstAccount.getAccountID (), raSrcAccount.getAccountID (), spsPaths, false, false, false, true); if ((extraPath.size() > 0) && ((resultCode == terNO_LINE) || (resultCode == tecPATH_PARTIAL))) { m_journal.debug << iIdentifier << " Trying with an extra path element"; spsPaths.addPath(extraPath); pathStateList.clear (); resultCode = path::rippleCalculate ( lesSandbox, saMaxAmountAct, saDstAmountAct, pathStateList, saMaxAmount, saDstAmount, raDstAccount.getAccountID (), raSrcAccount.getAccountID (), spsPaths, false, false, false, true); m_journal.debug << iIdentifier << " Extra path element gives " << transHuman (resultCode); } if (resultCode == tesSUCCESS) { Json::Value jvEntry (Json::objectValue); jvEntry["source_amount"] = saMaxAmountAct.getJson (0); jvEntry["paths_computed"] = spsPaths.getJson (0); found = true; jvArray.append (jvEntry); } else { m_journal.debug << iIdentifier << " rippleCalc returns " << transHuman (resultCode); } } else { m_journal.debug << iIdentifier << " No paths found"; } } iLastLevel = iLevel; bLastSuccess = found; if (fast && ptQuickReply.is_not_a_date_time()) { ptQuickReply = boost::posix_time::microsec_clock::universal_time(); mOwner.reportFast ((ptQuickReply-ptCreated).total_milliseconds()); } else if (!fast && ptFullReply.is_not_a_date_time()) { ptFullReply = boost::posix_time::microsec_clock::universal_time(); mOwner.reportFull ((ptFullReply-ptCreated).total_milliseconds()); } jvStatus["alternatives"] = jvArray; return jvStatus; }
// 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; }
void stpath_append_one (STPath& st, STPathElement const& pe) { st.push_back(pe); }
bool PathRequest::findPaths (std::shared_ptr<RippleLineCache> const& cache, int const level, Json::Value& jvArray) { auto sourceCurrencies = sciSourceCurrencies; if (sourceCurrencies.empty ()) { auto currencies = accountSourceCurrencies(*raSrcAccount, cache, true); bool const sameAccount = *raSrcAccount == *raDstAccount; for (auto const& c : currencies) { if (! sameAccount || c != saDstAmount.getCurrency()) { if (sourceCurrencies.size() >= RPC::Tuning::max_auto_src_cur) return false; sourceCurrencies.insert( {c, c.isZero() ? xrpAccount() : *raSrcAccount}); } } } auto const dst_amount = convert_all_ ? STAmount(saDstAmount.issue(), STAmount::cMaxValue, STAmount::cMaxOffset) : saDstAmount; hash_map<Currency, std::unique_ptr<Pathfinder>> currency_map; for (auto const& issue : sourceCurrencies) { JLOG(m_journal.debug()) << iIdentifier << " Trying to find paths: " << STAmount(issue, 1).getFullText(); auto& pathfinder = getPathFinder(cache, currency_map, issue.currency, dst_amount, level); if (! pathfinder) { assert(false); JLOG(m_journal.debug()) << iIdentifier << " No paths found"; continue; } STPath fullLiquidityPath; auto ps = pathfinder->getBestPaths(max_paths_, fullLiquidityPath, mContext[issue], issue.account); mContext[issue] = ps; auto& sourceAccount = ! isXRP(issue.account) ? issue.account : isXRP(issue.currency) ? xrpAccount() : *raSrcAccount; STAmount saMaxAmount = saSendMax.value_or( STAmount({issue.currency, sourceAccount}, 1u, 0, true)); JLOG(m_journal.debug()) << iIdentifier << " Paths found, calling rippleCalc"; path::RippleCalc::Input rcInput; if (convert_all_) rcInput.partialPaymentAllowed = true; auto sandbox = std::make_unique<PaymentSandbox> (&*cache->getLedger(), tapNONE); auto rc = path::RippleCalc::rippleCalculate( *sandbox, saMaxAmount, // --> Amount to send is unlimited // to get an estimate. dst_amount, // --> Amount to deliver. *raDstAccount, // --> Account to deliver to. *raSrcAccount, // --> Account sending from. ps, // --> Path set. app_.logs(), app_.config(), &rcInput); if (! convert_all_ && ! fullLiquidityPath.empty() && (rc.result() == terNO_LINE || rc.result() == tecPATH_PARTIAL)) { JLOG(m_journal.debug()) << iIdentifier << " Trying with an extra path element"; ps.push_back(fullLiquidityPath); sandbox = std::make_unique<PaymentSandbox> (&*cache->getLedger(), tapNONE); rc = path::RippleCalc::rippleCalculate( *sandbox, saMaxAmount, // --> Amount to send is unlimited // to get an estimate. dst_amount, // --> Amount to deliver. *raDstAccount, // --> Account to deliver to. *raSrcAccount, // --> Account sending from. ps, // --> Path set. app_.logs(), app_.config()); if (rc.result() != tesSUCCESS) { JLOG(m_journal.warn()) << iIdentifier << " Failed with covering path " << transHuman(rc.result()); } else { JLOG(m_journal.debug()) << iIdentifier << " Extra path element gives " << transHuman(rc.result()); } } if (rc.result () == tesSUCCESS) { Json::Value jvEntry (Json::objectValue); rc.actualAmountIn.setIssuer (sourceAccount); jvEntry[jss::source_amount] = rc.actualAmountIn.getJson (0); jvEntry[jss::paths_computed] = ps.getJson(0); if (convert_all_) jvEntry[jss::destination_amount] = rc.actualAmountOut.getJson(0); if (hasCompletion ()) { // Old ripple_path_find API requires this jvEntry[jss::paths_canonical] = Json::arrayValue; } jvArray.append (jvEntry); } else { JLOG(m_journal.debug()) << iIdentifier << " rippleCalc returns " << transHuman(rc.result()); } } /* The resource fee is based on the number of source currencies used. The minimum cost is 50 and the maximum is 400. The cost increases after four source currencies, 50 - (4 * 4) = 34. */ int const size = sourceCurrencies.size(); consumer_.charge({boost::algorithm::clamp(size * size + 34, 50, 400), "path update"}); return true; }
bool STParsedJSON::parse (std::string const& json_name, Json::Value const& json, SField::ref inName, int depth, std::unique_ptr <STObject>& sub_object) { if (! json.isObject ()) { error = not_an_object (json_name); return false; } SField::ptr name (&inName); boost::ptr_vector<SerializedType> data; Json::Value::Members members (json.getMemberNames ()); for (Json::Value::Members::iterator it (members.begin ()); it != members.end (); ++it) { std::string const& fieldName = *it; Json::Value const& value = json [fieldName]; SField::ref field = SField::getField (fieldName); if (field == sfInvalid) { error = unknown_field (json_name, fieldName); return false; } switch (field.fieldType) { case STI_UINT8: try { if (value.isString ()) { // VFALCO TODO wtf? } else if (value.isInt ()) { if (value.asInt () < 0 || value.asInt () > 255) { error = out_of_range (json_name, fieldName); return false; } data.push_back (new STUInt8 (field, range_check_cast <unsigned char> ( value.asInt (), 0, 255))); } else if (value.isUInt ()) { if (value.asUInt () > 255) { error = out_of_range (json_name, fieldName); return false; } data.push_back (new STUInt8 (field, range_check_cast <unsigned char> ( value.asUInt (), 0, 255))); } else { error = bad_type (json_name, fieldName); return false; } } catch (...) { error = invalid_data (json_name, fieldName); return false; } break; case STI_UINT16: try { if (value.isString ()) { std::string strValue = value.asString (); if (! strValue.empty () && ((strValue[0] < '0') || (strValue[0] > '9'))) { if (field == sfTransactionType) { TxType const txType (TxFormats::getInstance()-> findTypeByName (strValue)); data.push_back (new STUInt16 (field, static_cast <std::uint16_t> (txType))); if (*name == sfGeneric) name = &sfTransaction; } else if (field == sfLedgerEntryType) { LedgerEntryType const type (LedgerFormats::getInstance()-> findTypeByName (strValue)); data.push_back (new STUInt16 (field, static_cast <std::uint16_t> (type))); if (*name == sfGeneric) name = &sfLedgerEntry; } else { error = invalid_data (json_name, fieldName); return false; } } else { data.push_back (new STUInt16 (field, beast::lexicalCastThrow <std::uint16_t> (strValue))); } } else if (value.isInt ()) { data.push_back (new STUInt16 (field, range_check_cast <std::uint16_t> ( value.asInt (), 0, 65535))); } else if (value.isUInt ()) { data.push_back (new STUInt16 (field, range_check_cast <std::uint16_t> ( value.asUInt (), 0, 65535))); } else { error = bad_type (json_name, fieldName); return false; } } catch (...) { error = invalid_data (json_name, fieldName); return false; } break; case STI_UINT32: try { if (value.isString ()) { data.push_back (new STUInt32 (field, beast::lexicalCastThrow <std::uint32_t> (value.asString ()))); } else if (value.isInt ()) { data.push_back (new STUInt32 (field, range_check_cast <std::uint32_t> (value.asInt (), 0u, 4294967295u))); } else if (value.isUInt ()) { data.push_back (new STUInt32 (field, static_cast <std::uint32_t> (value.asUInt ()))); } else { error = bad_type (json_name, fieldName); return false; } } catch (...) { error = invalid_data (json_name, fieldName); return false; } break; case STI_UINT64: try { if (value.isString ()) { data.push_back (new STUInt64 (field, uintFromHex (value.asString ()))); } else if (value.isInt ()) { data.push_back (new STUInt64 (field, range_check_cast<std::uint64_t> ( value.asInt (), 0, 18446744073709551615ull))); } else if (value.isUInt ()) { data.push_back (new STUInt64 (field, static_cast <std::uint64_t> (value.asUInt ()))); } else { error = bad_type (json_name, fieldName); return false; } } catch (...) { error = invalid_data (json_name, fieldName); return false; } break; case STI_HASH128: try { if (value.isString ()) { data.push_back (new STHash128 (field, value.asString ())); } else { error = bad_type (json_name, fieldName); return false; } } catch (...) { error = invalid_data (json_name, fieldName); return false; } break; case STI_HASH160: try { if (value.isString ()) { data.push_back (new STHash160 (field, value.asString ())); } else { error = bad_type (json_name, fieldName); return false; } } catch (...) { error = invalid_data (json_name, fieldName); return false; } break; case STI_HASH256: try { if (value.isString ()) { data.push_back (new STHash256 (field, value.asString ())); } else { error = bad_type (json_name, fieldName); return false; } } catch (...) { error = invalid_data (json_name, fieldName); return false; } break; case STI_VL: if (! value.isString ()) { error = bad_type (json_name, fieldName); return false; } try { std::pair<Blob, bool> ret(strUnHex (value.asString ())); if (!ret.second) throw std::invalid_argument ("invalid data"); data.push_back (new STVariableLength (field, ret.first)); } catch (...) { error = invalid_data (json_name, fieldName); return false; } break; case STI_AMOUNT: try { data.push_back (new STAmount (field, value)); } catch (...) { error = invalid_data (json_name, fieldName); return false; } break; case STI_VECTOR256: if (! value.isArray ()) { error = array_expected (json_name, fieldName); return false; } try { data.push_back (new STVector256 (field)); STVector256* tail (dynamic_cast <STVector256*> (&data.back ())); assert (tail); for (Json::UInt i = 0; !json.isValidIndex (i); ++i) { uint256 s; s.SetHex (json[i].asString ()); tail->addValue (s); } } catch (...) { error = invalid_data (json_name, fieldName); return false; } break; case STI_PATHSET: if (!value.isArray ()) { error = array_expected (json_name, fieldName); return false; } try { data.push_back (new STPathSet (field)); STPathSet* tail = dynamic_cast <STPathSet*> (&data.back ()); assert (tail); for (Json::UInt i = 0; value.isValidIndex (i); ++i) { STPath p; if (!value[i].isArray ()) { std::stringstream ss; ss << fieldName << "[" << i << "]"; error = array_expected (json_name, ss.str ()); return false; } for (Json::UInt j = 0; value[i].isValidIndex (j); ++j) { std::stringstream ss; ss << fieldName << "[" << i << "][" << j << "]"; std::string const element_name ( json_name + "." + ss.str()); // each element in this path has some combination of account, // currency, or issuer Json::Value pathEl = value[i][j]; if (!pathEl.isObject ()) { error = not_an_object (element_name); return false; } const Json::Value& account = pathEl["account"]; const Json::Value& currency = pathEl["currency"]; const Json::Value& issuer = pathEl["issuer"]; bool hasCurrency = false; uint160 uAccount, uCurrency, uIssuer; if (! account.isNull ()) { // human account id if (! account.isString ()) { error = string_expected (element_name, "account"); return false; } std::string const strValue (account.asString ()); if (value.size () == 40) // 160-bit hex account value uAccount.SetHex (strValue); { RippleAddress a; if (! a.setAccountID (strValue)) { error = invalid_data (element_name, "account"); return false; } uAccount = a.getAccountID (); } } if (!currency.isNull ()) { // human currency if (!currency.isString ()) { error = string_expected (element_name, "currency"); return false; } hasCurrency = true; if (currency.asString ().size () == 40) { uCurrency.SetHex (currency.asString ()); } else if (!STAmount::currencyFromString ( uCurrency, currency.asString ())) { error = invalid_data (element_name, "currency"); return false; } } if (!issuer.isNull ()) { // human account id if (!issuer.isString ()) { error = string_expected (element_name, "issuer"); return false; } if (issuer.asString ().size () == 40) { uIssuer.SetHex (issuer.asString ()); } else { RippleAddress a; if (!a.setAccountID (issuer.asString ())) { error = invalid_data (element_name, "issuer"); return false; } uIssuer = a.getAccountID (); } } p.addElement (STPathElement (uAccount, uCurrency, uIssuer, hasCurrency)); } tail->addPath (p); } } catch (...) { error = invalid_data (json_name, fieldName); return false; } break; case STI_ACCOUNT: { if (! value.isString ()) { error = bad_type (json_name, fieldName); return false; } std::string strValue = value.asString (); try { if (value.size () == 40) // 160-bit hex account value { uint160 v; v.SetHex (strValue); data.push_back (new STAccount (field, v)); } else { // ripple address RippleAddress a; if (!a.setAccountID (strValue)) { error = invalid_data (json_name, fieldName); return false; } data.push_back (new STAccount (field, a.getAccountID ())); } } catch (...) { error = invalid_data (json_name, fieldName); return false; } } break; case STI_OBJECT: case STI_TRANSACTION: case STI_LEDGERENTRY: case STI_VALIDATION: if (! value.isObject ()) { error = not_an_object (json_name, fieldName); return false; } if (depth > 64) { error = too_deep (json_name, fieldName); return false; } try { std::unique_ptr <STObject> sub_object_; bool const success (parse (json_name + "." + fieldName, value, field, depth + 1, sub_object_)); if (! success) return false; data.push_back (sub_object_.release ()); } catch (...) { error = invalid_data (json_name, fieldName); return false; } break; case STI_ARRAY: if (! value.isArray ()) { error = array_expected (json_name, fieldName); return false; } try { data.push_back (new STArray (field)); STArray* tail = dynamic_cast<STArray*> (&data.back ()); assert (tail); for (Json::UInt i = 0; value.isValidIndex (i); ++i) { bool const isObject (value[i].isObject()); bool const singleKey (isObject ? value [i].size() == 1 : true); if (!isObject || !singleKey) { std::stringstream ss; ss << json_name << "." << fieldName << "[" << i << "]"; error = singleton_expected (ss.str ()); return false; } // TODO: There doesn't seem to be a nice way to get just the // first/only key in an object without copying all keys into // a vector std::string const objectName (value[i].getMemberNames()[0]);; SField::ref nameField (SField::getField(objectName)); if (nameField == sfInvalid) { error = unknown_field (json_name, objectName); return false; } Json::Value const objectFields (value[i][objectName]); std::unique_ptr <STObject> sub_object_; { std::stringstream ss; ss << json_name << "." << fieldName << "[" << i << "]." << objectName; bool const success (parse (ss.str (), objectFields, nameField, depth + 1, sub_object_)); if (! success) return false; } tail->push_back (*sub_object_); } } catch (...) { error = invalid_data (json_name, fieldName); return false; } break; default: error = bad_type (json_name, fieldName); return false; } } sub_object.reset (new STObject (*name, data)); return true; }
std::pair<bool, Json::Value> ripplePathFind(RippleLineCache::pointer const& cache, RippleAddress const& raSrc, RippleAddress const& raDst, STAmount const& saDstAmount, Ledger::pointer const& lpLedger, Json::Value const& jvSrcCurrencies, boost::optional<Json::Value> const& contextPaths, int const& level) { FindPaths fp( cache, raSrc.getAccountID(), raDst.getAccountID(), saDstAmount, level, 4); // max paths Json::Value jvArray(Json::arrayValue); for (unsigned int i = 0; i != jvSrcCurrencies.size(); ++i) { Json::Value jvSource = jvSrcCurrencies[i]; Currency uSrcCurrencyID; Account uSrcIssuerID; if (!jvSource.isObject()) return std::make_pair(false, rpcError(rpcINVALID_PARAMS)); // Parse mandatory currency. if (!jvSource.isMember(jss::currency) || !to_currency( uSrcCurrencyID, jvSource[jss::currency].asString())) { WriteLog(lsINFO, RPCHandler) << "Bad currency."; return std::make_pair(false, rpcError(rpcSRC_CUR_MALFORMED)); } if (uSrcCurrencyID.isNonZero()) uSrcIssuerID = raSrc.getAccountID(); // Parse optional issuer. if (jvSource.isMember(jss::issuer) && ((!jvSource[jss::issuer].isString() || !to_issuer(uSrcIssuerID, jvSource[jss::issuer].asString())) || (uSrcIssuerID.isZero() != uSrcCurrencyID.isZero()) || (noAccount() == uSrcIssuerID))) { WriteLog(lsINFO, RPCHandler) << "Bad issuer."; return std::make_pair(false, rpcError(rpcSRC_ISR_MALFORMED)); } STPathSet spsComputed; if (contextPaths) { Json::Value pathSet = Json::objectValue; pathSet[jss::Paths] = contextPaths.get(); STParsedJSONObject paths("pathSet", pathSet); if (paths.object.get() == nullptr) return std::make_pair(false, 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[jss::source_amount] = rc.actualAmountIn.getJson(0); jvEntry[jss::paths_canonical] = Json::arrayValue; jvEntry[jss::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); } } } return std::make_pair(true, jvArray); }
Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast) { m_journal.debug << iIdentifier << " update " << (fast ? "fast" : "normal"); ScopedLockType sl (mLock); if (!isValid (cache)) return jvStatus; jvStatus = Json::objectValue; auto sourceCurrencies = sciSourceCurrencies; if (sourceCurrencies.empty ()) { auto usCurrencies = accountSourceCurrencies (*raSrcAccount, cache, true); bool sameAccount = *raSrcAccount == *raDstAccount; for (auto const& c: usCurrencies) { if (!sameAccount || (c != saDstAmount.getCurrency ())) { if (c.isZero ()) sourceCurrencies.insert ({c, xrpAccount()}); else sourceCurrencies.insert ({c, *raSrcAccount}); } } } if (hasCompletion ()) { // Old ripple_path_find API gives destination_currencies auto& destCurrencies = (jvStatus[jss::destination_currencies] = Json::arrayValue); auto usCurrencies = accountDestCurrencies (*raDstAccount, cache, true); for (auto const& c : usCurrencies) destCurrencies.append (to_string (c)); } jvStatus[jss::source_account] = getApp().accountIDCache().toBase58(*raSrcAccount); jvStatus[jss::destination_account] = getApp().accountIDCache().toBase58(*raDstAccount); jvStatus[jss::destination_amount] = saDstAmount.getJson (0); jvStatus[jss::full_reply] = ! fast; if (jvId) jvStatus["id"] = jvId; Json::Value jvArray = Json::arrayValue; int iLevel = iLastLevel; bool loaded = getApp().getFeeTrack().isLoadedLocal(); if (iLevel == 0) { // first pass if (loaded || fast) iLevel = getConfig().PATH_SEARCH_FAST; else iLevel = getConfig().PATH_SEARCH; } else if ((iLevel == getConfig().PATH_SEARCH_FAST) && !fast) { // leaving fast pathfinding iLevel = getConfig().PATH_SEARCH; if (loaded && (iLevel > getConfig().PATH_SEARCH_FAST)) --iLevel; } else if (bLastSuccess) { // decrement, if possible if (iLevel > getConfig().PATH_SEARCH || (loaded && (iLevel > getConfig().PATH_SEARCH_FAST))) --iLevel; } else { // adjust as needed if (!loaded && (iLevel < getConfig().PATH_SEARCH_MAX)) ++iLevel; if (loaded && (iLevel > getConfig().PATH_SEARCH_FAST)) --iLevel; } m_journal.debug << iIdentifier << " processing at level " << iLevel; bool found = false; FindPaths fp ( cache, *raSrcAccount, *raDstAccount, saDstAmount, iLevel, 4); // iMaxPaths for (auto const& currIssuer: sourceCurrencies) { { STAmount test (currIssuer, 1); if (m_journal.debug) { m_journal.debug << iIdentifier << " Trying to find paths: " << test.getFullText (); } } STPathSet& spsPaths = mContext[currIssuer]; STPath fullLiquidityPath; auto valid = fp.findPathsForIssue ( currIssuer, spsPaths, fullLiquidityPath); CondLog (!valid, lsDEBUG, PathRequest) << iIdentifier << " PF request not valid"; if (valid) { boost::optional<PaymentSandbox> sandbox; sandbox.emplace(&*cache->getLedger(), tapNONE); auto& sourceAccount = !isXRP (currIssuer.account) ? currIssuer.account : isXRP (currIssuer.currency) ? xrpAccount() : *raSrcAccount; STAmount saMaxAmount ({currIssuer.currency, sourceAccount}, 1); saMaxAmount.negate (); m_journal.debug << iIdentifier << " Paths found, calling rippleCalc"; auto rc = path::RippleCalc::rippleCalculate ( *sandbox, saMaxAmount, saDstAmount, *raDstAccount, *raSrcAccount, spsPaths); if (!fullLiquidityPath.empty() && (rc.result () == terNO_LINE || rc.result () == tecPATH_PARTIAL)) { m_journal.debug << iIdentifier << " Trying with an extra path element"; spsPaths.push_back (fullLiquidityPath); sandbox.emplace(&*cache->getLedger(), tapNONE); rc = path::RippleCalc::rippleCalculate ( *sandbox, saMaxAmount, saDstAmount, *raDstAccount, *raSrcAccount, spsPaths); if (rc.result () != tesSUCCESS) m_journal.warning << iIdentifier << " Failed with covering path " << transHuman (rc.result ()); else m_journal.debug << iIdentifier << " Extra path element gives " << transHuman (rc.result ()); } if (rc.result () == tesSUCCESS) { Json::Value jvEntry (Json::objectValue); rc.actualAmountIn.setIssuer (sourceAccount); jvEntry[jss::source_amount] = rc.actualAmountIn.getJson (0); jvEntry[jss::paths_computed] = spsPaths.getJson (0); if (hasCompletion ()) { // Old ripple_path_find API requires this jvEntry[jss::paths_canonical] = Json::arrayValue; } found = true; jvArray.append (jvEntry); } else { m_journal.debug << iIdentifier << " rippleCalc returns " << transHuman (rc.result ()); } } else { m_journal.debug << iIdentifier << " No paths found"; } } iLastLevel = iLevel; bLastSuccess = found; if (fast && ptQuickReply.is_not_a_date_time()) { ptQuickReply = boost::posix_time::microsec_clock::universal_time(); mOwner.reportFast ((ptQuickReply-ptCreated).total_milliseconds()); } else if (!fast && ptFullReply.is_not_a_date_time()) { ptFullReply = boost::posix_time::microsec_clock::universal_time(); mOwner.reportFull ((ptFullReply-ptCreated).total_milliseconds()); } jvStatus[jss::alternatives] = jvArray; return jvStatus; }