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; }
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; }