CurrencySet accountDestCurrencies ( RippleAddress const& raAccountID, RippleLineCache::ref lrCache, bool includeXRP) { CurrencySet currencies; if (includeXRP) currencies.insert (xrpCurrency()); // Even if account doesn't exist // List of ripple lines. auto& rippleLines (lrCache->getRippleLines (raAccountID.getAccountID ())); for (auto const& item : rippleLines) { auto rspEntry = (RippleState*) item.get (); assert (rspEntry); if (!rspEntry) continue; auto& saBalance = rspEntry->getBalance (); if (saBalance < rspEntry->getLimit ()) // Can take more currencies.insert (saBalance.getCurrency ()); } currencies.erase (badCurrency()); return currencies; }
hash_set<Currency> accountDestCurrencies ( AccountID const& account, RippleLineCache::ref lrCache, bool includeXRP) { hash_set<Currency> currencies; if (includeXRP) { currencies.insert (xrpCurrency()); currencies.insert (vbcCurrency()); } // Even if account doesn't exist // List of ripple lines. auto& rippleLines = lrCache->getRippleLines (account); for (auto const& item : rippleLines) { auto rspEntry = (RippleState*) item.get (); assert (rspEntry); if (!rspEntry) continue; auto& saBalance = rspEntry->getBalance (); if (saBalance < rspEntry->getLimit ()) // Can take more currencies.insert (saBalance.getCurrency ()); } currencies.erase (badCurrency()); return currencies; }
Pathfinder::Pathfinder (RippleLineCache::ref cache, const RippleAddress& uSrcAccountID, const RippleAddress& uDstAccountID, const uint160& uSrcCurrencyID, const uint160& uSrcIssuerID, const STAmount& saDstAmount, bool& bValid) : mSrcAccountID (uSrcAccountID.getAccountID ()), mDstAccountID (uDstAccountID.getAccountID ()), mDstAmount (saDstAmount), mSrcCurrencyID (uSrcCurrencyID), mSrcIssuerID (uSrcIssuerID), mSrcAmount (uSrcCurrencyID, uSrcIssuerID, 1u, 0, true), mLedger (cache->getLedger ()), mRLCache (cache) { if (((mSrcAccountID == mDstAccountID) && (mSrcCurrencyID == mDstAmount.getCurrency ())) || mDstAmount.isZero ()) { // no need to send to same account with same currency, must send non-zero bValid = false; mLedger.reset (); return; } bValid = true; m_loadEvent = getApp().getJobQueue ().getLoadEvent (jtPATH_FIND, "FindPath"); bool bIssuer = mSrcCurrencyID.isNonZero() && mSrcIssuerID.isNonZero() && (mSrcIssuerID != mSrcAccountID); mSource = STPathElement( // Where does an empty path start? bIssuer ? mSrcIssuerID : mSrcAccountID, // On the source account or issuer account mSrcCurrencyID, // In the source currency mSrcCurrencyID.isZero() ? uint160() : (bIssuer ? mSrcIssuerID : mSrcAccountID)); }
hash_set<Currency> accountSourceCurrencies ( AccountID const& account, RippleLineCache::ref lrCache, bool includeXRP) { hash_set<Currency> currencies; // YYY Only bother if they are above reserve if (includeXRP) { currencies.insert (xrpCurrency()); currencies.insert (vbcCurrency()); } // List of ripple lines. auto& rippleLines = lrCache->getRippleLines (account); for (auto const& item : rippleLines) { auto rspEntry = (RippleState*) item.get (); assert (rspEntry); if (!rspEntry) continue; auto& saBalance = rspEntry->getBalance (); // Filter out non if (saBalance > zero // Have IOUs to send. || (rspEntry->getLimitPeer () // Peer extends credit. && ((-saBalance) < rspEntry->getLimitPeer ()))) // Credit left. { currencies.insert (saBalance.getCurrency ()); } } currencies.erase (badCurrency()); return currencies; }
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; }
bool PathRequest::isValid (RippleLineCache::ref crCache) { ScopedLockType sl (mLock); bValid = raSrcAccount.isSet () && raDstAccount.isSet () && saDstAmount > zero; Ledger::pointer lrLedger = crCache->getLedger (); if (bValid) { AccountState::pointer asSrc = getApp().getOPs ().getAccountState (crCache->getLedger(), raSrcAccount); if (!asSrc) { // no source account bValid = false; jvStatus = rpcError (rpcSRC_ACT_NOT_FOUND); } else { AccountState::pointer asDst = getApp().getOPs ().getAccountState (lrLedger, raDstAccount); Json::Value& jvDestCur = (jvStatus["destination_currencies"] = Json::arrayValue); if (!asDst) { // no destination account jvDestCur.append (Json::Value ("XRP")); if (!saDstAmount.isNative ()) { // only XRP can be send to a non-existent account bValid = false; jvStatus = rpcError (rpcACT_NOT_FOUND); } else if (saDstAmount < STAmount (lrLedger->getReserve (0))) { // payment must meet reserve bValid = false; jvStatus = rpcError (rpcDST_AMT_MALFORMED); } } else { bool const disallowXRP ( asDst->peekSLE ().getFlags() & lsfDisallowXRP); CurrencySet usDestCurrID = usAccountDestCurrencies (raDstAccount, crCache, !disallowXRP); for (auto const& currency : usDestCurrID) jvDestCur.append (to_string (currency)); jvStatus["destination_tag"] = (asDst->peekSLE ().getFlags () & lsfRequireDestTag) != 0; } } } if (bValid) { jvStatus["ledger_hash"] = to_string (lrLedger->getHash ()); jvStatus["ledger_index"] = lrLedger->getLedgerSeq (); } return bValid; }
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; }
bool PathRequest::isValid (RippleLineCache::ref crCache) { ScopedLockType sl (mLock); bValid = raSrcAccount && raDstAccount && saDstAmount > zero; auto const& lrLedger = crCache->getLedger (); if (bValid) { if (! crCache->getLedger()->exists( keylet::account(*raSrcAccount))) { // no source account bValid = false; jvStatus = rpcError (rpcSRC_ACT_NOT_FOUND); } } if (bValid) { auto const sleDest = crCache->getLedger()->read( keylet::account(*raDstAccount)); Json::Value& jvDestCur = (jvStatus[jss::destination_currencies] = Json::arrayValue); if (!sleDest) { // no destination account jvDestCur.append (Json::Value ("XRP")); if (!saDstAmount.native ()) { // only XRP can be send to a non-existent account bValid = false; jvStatus = rpcError (rpcACT_NOT_FOUND); } else if (saDstAmount < STAmount (lrLedger->fees().accountReserve (0))) { // payment must meet reserve bValid = false; jvStatus = rpcError (rpcDST_AMT_MALFORMED); } } else { bool const disallowXRP ( sleDest->getFlags() & lsfDisallowXRP); auto usDestCurrID = accountDestCurrencies ( *raDstAccount, crCache, !disallowXRP); for (auto const& currency : usDestCurrID) jvDestCur.append (to_string (currency)); jvStatus["destination_tag"] = (sleDest->getFlags () & lsfRequireDestTag) != 0; } } if (bValid) { jvStatus[jss::ledger_hash] = to_string (lrLedger->info().hash); jvStatus[jss::ledger_index] = lrLedger->seq(); } return bValid; }