//------------------------------------------------------------------------------ /* Copyright (c) 2011-2013, OpenCoin, Inc. */ //============================================================================== SETUP_LOG (Pathfinder) /* we just need to find a succession of the highest quality paths there until we find enough width Don't do branching within each path We have a list of paths we are working on but how do we compare the ones that are terminating in a different currency? Loops TODO: what is a good way to come up with multiple paths? Maybe just change the sort criteria? first a low cost one and then a fat short one? OrderDB: getXRPOffers(); // return list of all orderbooks that want XRP // return list of all orderbooks that want IssuerID // return list of all orderbooks that want this issuerID and currencyID */ /* Test sending to XRP Test XRP to XRP Test offer in middle Test XRP to USD Test USD to EUR */ // we sort the options by: // cost of path // length of path // width of path // correct currency at the end // quality, length, liquidity, index typedef boost::tuple path_LQ_t; // Lower numbers have better quality. Sort higher quality first. static bool bQualityCmp (const path_LQ_t& a, const path_LQ_t& b) { // 1) Higher quality (lower cost) is better if (a.get<0> () != b.get<0> ()) return a.get<0> () < b.get<0> (); // 2) More liquidity (higher volume) is better if (a.get<2> () != b.get<2> ()) return a.get<2> () > b.get<2> (); // 3) Shorter paths are better if (a.get<1> () != b.get<1> ()) return a.get<1> () < b.get<1> (); // 4) Tie breaker return a.get<3> () > b.get<3> (); } // Return true, if path is a default path with an element. // A path is a default path if it is implied via src, dst, send, and sendmax. bool Pathfinder::bDefaultPath (const STPath& spPath) { if (2 >= spPath.mPath.size ()) { // Empty path is a default. Don't need to add it to return set. WriteLog (lsTRACE, Pathfinder) << "findPaths: empty path: direct"; return true; } if (!mPsDefault) { // No default path. // There might not be a direct credit line or there may be no implied nodes // in send and sendmax. return false; // Didn't generate a default path. So can't match. } PathState::pointer pspCurrent = boost::make_shared (mDstAmount, mSrcAmount); if (pspCurrent) { bool bDefault; LedgerEntrySet lesActive (mLedger, tapNONE); WriteLog (lsTRACE, Pathfinder) << boost::str (boost::format ("bDefaultPath> mSrcAmount=%s mDstAmount=%s") % mSrcAmount.getFullText () % mDstAmount.getFullText ()); // Expand the current path. pspCurrent->setExpanded (lesActive, spPath, mDstAccountID, mSrcAccountID); // XXX Need to report or act on errors returned in pspCurrent->terStatus. // Determine if expanded current path is the default. // When path is a default (implied). Don't need to add it to return set. bDefault = pspCurrent->vpnNodes == mPsDefault->vpnNodes; WriteLog (lsTRACE, Pathfinder) << "bDefaultPath: expanded path: " << pspCurrent->getJson (); WriteLog (lsTRACE, Pathfinder) << "bDefaultPath: source path: " << spPath.getJson (0); WriteLog (lsTRACE, Pathfinder) << "bDefaultPath: default path: " << mPsDefault->getJson (); return bDefault; } return false; } typedef std::pair candidate_t; static bool candCmp (uint32 seq, const candidate_t& first, const candidate_t& second) { if (first.first < second.first) return false; if (first.first > second.first) return true; return (first.first ^ seq) < (second.first ^ seq); } static int getEffectiveLength (const STPath& spPath) { // don't count exchanges to non-XRP currencies twice (only count the forced issuer account node) int length = 0; for (std::vector::const_iterator it = spPath.begin (); it != spPath.end (); ++it) { if (it->isAccount () || it->getCurrency ().isZero ()) ++length; } return length; } 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; getApp().getOrderBookDB ().setup (mLedger); m_loadEvent = getApp().getJobQueue ().getLoadEvent (jtPATH_FIND, "FindPath"); // Construct the default path for later comparison. PathState::pointer psDefault = boost::make_shared (mDstAmount, mSrcAmount); if (psDefault) { // Build the default path. // Later, reject anything that expands to the default path as the default is sufficient. LedgerEntrySet lesActive (mLedger, tapNONE); WriteLog (lsTRACE, Pathfinder) << boost::str (boost::format ("Pathfinder> mSrcAmount=%s mDstAmount=%s") % mSrcAmount.getFullText () % mDstAmount.getFullText ()); psDefault->setExpanded (lesActive, STPath (), mDstAccountID, mSrcAccountID); if (tesSUCCESS == psDefault->terStatus) { // The default path works, remember it. WriteLog (lsTRACE, Pathfinder) << "Pathfinder: default path: " << psDefault->getJson (); mPsDefault = psDefault; } else { // The default path doesn't work. WriteLog (lsTRACE, Pathfinder) << "Pathfinder: default path: NONE: " << transToken (psDefault->terStatus); } } } // If possible, returns a single path. // --> iMaxSteps: Maximum nodes in paths to return. // --> iMaxPaths: Maximum number of paths to return. // <-- retPathSet: founds paths not including default paths. // Returns true if found paths. // // When generating a path set blindly, don't allow the empty path, it is implied by default. // When generating a path set for estimates, allow an empty path instead of no paths to indicate a path exists. The caller will // need to strip the empty path when submitting the transaction. // // Assumes rippling (not XRP to XRP) // // Leaves to the caller figuring out overall liquidity. // Optimization opportunity: For some simple cases, this routine has figured out the overall liquidity. bool Pathfinder::findPaths (const unsigned int iMaxSteps, const unsigned int iMaxPaths, STPathSet& spsDst) { bool bFound = false; // True, iff found a path. WriteLog (lsTRACE, Pathfinder) << boost::str (boost::format ("findPaths> mSrcAccountID=%s mDstAccountID=%s mDstAmount=%s mSrcCurrencyID=%s mSrcIssuerID=%s") % RippleAddress::createHumanAccountID (mSrcAccountID) % RippleAddress::createHumanAccountID (mDstAccountID) % mDstAmount.getFullText () % STAmount::createHumanCurrency (mSrcCurrencyID) % RippleAddress::createHumanAccountID (mSrcIssuerID) ); if (!mLedger) { WriteLog (lsDEBUG, Pathfinder) << "findPaths< no ledger"; return false; } LedgerEntrySet lesActive (mLedger, tapNONE); boost::unordered_map aiMap; SLE::pointer sleSrc = lesActive.entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (mSrcAccountID)); if (!sleSrc) { WriteLog (lsDEBUG, Pathfinder) << boost::str (boost::format ("findPaths< no source")); return false; } SLE::pointer sleDst = lesActive.entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (mDstAccountID)); if (!sleDst) { WriteLog (lsDEBUG, Pathfinder) << boost::str (boost::format ("findPaths< no dest")); return false; } std::vector vspResults; std::queue qspExplore; // Path stubs to explore. STPath spSeed; bool bForcedIssuer = !!mSrcCurrencyID && mSrcIssuerID != mSrcAccountID; // Source forced an issuer. // The end is the cursor, start at the source account. STPathElement speEnd (mSrcAccountID, mSrcCurrencyID, !!mSrcCurrencyID ? mSrcAccountID // Non-XRP, start with self as issuer. : ACCOUNT_XRP); // Build a path of one element: the source. spSeed.addElement (speEnd); if (bForcedIssuer) { // Add forced source issuer to seed, via issuer's account. STPathElement speIssuer (mSrcIssuerID, mSrcCurrencyID, mSrcIssuerID); spSeed.addElement (speEnd); } // Push the seed path to explore. qspExplore.push (spSeed); while (qspExplore.size ()) // Have paths to explore? { STPath spPath = qspExplore.front (); qspExplore.pop (); // Pop the first path from the queue. speEnd = spPath.mPath.back (); // Get the last node from the path. if (!speEnd.mCurrencyID // Tail output is XRP. && !mDstAmount.getCurrency ()) // Which is dst currency. { // Done, cursor produces XRP and dest wants XRP. // Remove implied source. spPath.mPath.erase (spPath.mPath.begin ()); if (bForcedIssuer) { // Remove implied source issuer. spPath.mPath.erase (spPath.mPath.begin ()); } if (spPath.size ()) { // There is an actual path element. WriteLog (lsTRACE, Pathfinder) << "findPaths: adding path: " << spPath.getJson (0); vspResults.push_back (spPath); // Potential result. } else { WriteLog (lsWARNING, Pathfinder) << "findPaths: empty path: XRP->XRP"; } continue; } if (ShouldLog (lsTRACE, Pathfinder)) { WriteLog (lsTRACE, Pathfinder) << boost::str (boost::format ("findPaths: spe: %s/%s: %s amt: %s") % RippleAddress::createHumanAccountID (speEnd.mAccountID) % RippleAddress::createHumanAccountID (speEnd.mIssuerID) % RippleAddress::createHumanAccountID (mDstAccountID) % RippleAddress::createHumanAccountID (mDstAmount.getIssuer ())); WriteLog (lsTRACE, Pathfinder) << "findPaths: finish? account: " << (speEnd.mAccountID == mDstAccountID); WriteLog (lsTRACE, Pathfinder) << "findPaths: finish? currency: " << (speEnd.mCurrencyID == mDstAmount.getCurrency ()); WriteLog (lsTRACE, Pathfinder) << "findPaths: finish? issuer: " << RippleAddress::createHumanAccountID (speEnd.mIssuerID) << " / " << RippleAddress::createHumanAccountID (mDstAmount.getIssuer ()) << " / " << RippleAddress::createHumanAccountID (mDstAccountID); WriteLog (lsTRACE, Pathfinder) << "findPaths: finish? issuer is desired: " << (speEnd.mIssuerID == mDstAmount.getIssuer ()); } // YYY Allows going through self. Is this wanted? if (speEnd.mAccountID == mDstAccountID // Tail is destination account. && speEnd.mCurrencyID == mDstAmount.getCurrency () // With correct output currency. && ( speEnd.mIssuerID == mDstAccountID // Dest always accepts own issuer. || mDstAmount.getIssuer () == mDstAccountID // Any issuer is good. || mDstAmount.getIssuer () == speEnd.mIssuerID)) // The desired issuer. { // Done, found a path to the destination. // Cursor on the dest account with correct currency and issuer. if (bDefaultPath (spPath)) { WriteLog (lsTRACE, Pathfinder) << "findPaths: dropping: default path: " << spPath.getJson (0); bFound = true; } else { // Remove implied nodes. spPath.mPath.erase (spPath.mPath.begin ()); if (bForcedIssuer) { // Remove implied source issuer. spPath.mPath.erase (spPath.mPath.begin ()); } spPath.mPath.erase (spPath.mPath.begin () + spPath.mPath.size () - 1); vspResults.push_back (spPath); // Potential result. WriteLog (lsDEBUG, Pathfinder) << "findPaths: adding path: " << spPath.getJson (0); } continue; } bool bContinued = false; // True, if wasn't a dead end. WriteLog (lsTRACE, Pathfinder) << boost::str (boost::format ("findPaths: cursor: %s - %s/%s") % RippleAddress::createHumanAccountID (speEnd.mAccountID) % STAmount::createHumanCurrency (speEnd.mCurrencyID) % RippleAddress::createHumanAccountID (speEnd.mIssuerID)); int length = getEffectiveLength (spPath.mPath); if (length >= iMaxSteps) { // Path is at maximum size. Don't want to add more. WriteLog (lsTRACE, Pathfinder) << boost::str (boost::format ("findPaths: dropping: path would exceed max steps")); continue; } bool isLast = (length == (iMaxSteps - 1)); if (!speEnd.mCurrencyID) { // Cursor is for XRP, continue with qualifying books: XRP -> non-XRP std::vector xrpBooks; getApp().getOrderBookDB ().getBooksByTakerPays (ACCOUNT_XRP, CURRENCY_XRP, xrpBooks); BOOST_FOREACH (OrderBook::ref book, xrpBooks) { // New end is an order book with the currency and issuer. if (!spPath.hasSeen (ACCOUNT_XRP, book->getCurrencyOut (), book->getIssuerOut ()) && !matchesOrigin (book->getCurrencyOut (), book->getIssuerOut ()) && (!isLast || (book->getCurrencyOut () == mDstAmount.getCurrency () && book->getIssuerOut () == mDstAccountID))) { // Not a order book already in path. STPath spNew (spPath); STPathElement speBook (ACCOUNT_XRP, book->getCurrencyOut (), book->getIssuerOut ()); STPathElement speAccount (book->getIssuerOut (), book->getCurrencyOut (), book->getIssuerOut ()); spNew.mPath.push_back (speBook); // Add the order book. spNew.mPath.push_back (speAccount); // Add the account and currency WriteLog (lsDEBUG, Pathfinder) << boost::str (boost::format ("findPaths: XRP -> %s/%s") // % STAmount::createHumanCurrency(book->getCurrencyOut()) // % RippleAddress::createHumanAccountID(book->getIssuerOut()) % STAmount::createHumanCurrency (speBook.mCurrencyID) % RippleAddress::createHumanAccountID (speBook.mIssuerID)); qspExplore.push (spNew); bContinued = true; } } CondLog (!bContinued, lsDEBUG, Pathfinder) << boost::str (boost::format ("findPaths: XRP -> dead end")); } else { // Last element is for non-XRP, continue by adding ripple lines and order books. // Create new paths for each outbound account not already in the path. SLE::pointer sleEnd = lesActive.entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (speEnd.mAccountID)); CondLog (!sleEnd, lsDEBUG, Pathfinder) << boost::str (boost::format ("findPaths: tail: %s/%s : ") % RippleAddress::createHumanAccountID (speEnd.mAccountID) % RippleAddress::createHumanAccountID (speEnd.mIssuerID)); if (sleEnd) { // On a non-XRP account: // True, the cursor requires the next node to be authorized. bool bRequireAuth = isSetBit (sleEnd->getFieldU32 (sfFlags), lsfRequireAuth); bool dstCurrency = speEnd.mCurrencyID == mDstAmount.getCurrency (); AccountItems& rippleLines (mRLCache->getRippleLines (speEnd.mAccountID)); std::vector< std::pair > candidates; candidates.reserve (rippleLines.getItems ().size ()); BOOST_FOREACH (AccountItem::ref item, rippleLines.getItems ()) { RippleState* rspEntry = (RippleState*) item.get (); const uint160& uPeerID = rspEntry->getAccountIDPeer (); if (speEnd.mCurrencyID != rspEntry->getLimit ().getCurrency ()) { // wrong currency nothing (); } else if (spPath.hasSeen (uPeerID, speEnd.mCurrencyID, uPeerID) || ((uPeerID == mSrcAccountID) && (uPeerID != mDstAccountID))) { // Peer is in path already. Ignore it to avoid a loop. WriteLog (lsTRACE, Pathfinder) << boost::str (boost::format ("findPaths: SEEN: %s/%s -> %s/%s") % RippleAddress::createHumanAccountID (speEnd.mAccountID) % STAmount::createHumanCurrency (speEnd.mCurrencyID) % RippleAddress::createHumanAccountID (uPeerID) % STAmount::createHumanCurrency (speEnd.mCurrencyID)); } else if (isLast && (!dstCurrency || (uPeerID != mDstAccountID))) { nothing (); } else if (!rspEntry->getBalance ().isPositive () // No IOUs to send. && (!rspEntry->getLimitPeer () // Peer does not extend credit. || -rspEntry->getBalance () >= rspEntry->getLimitPeer () // No credit left. || (bRequireAuth && !rspEntry->getAuth ()))) // Not authorized to hold credit. { // Path has no credit left. Ignore it. WriteLog (lsTRACE, Pathfinder) << boost::str (boost::format ("findPaths: No credit: %s/%s -> %s/%s balance=%s limit=%s") % RippleAddress::createHumanAccountID (speEnd.mAccountID) % STAmount::createHumanCurrency (speEnd.mCurrencyID) % RippleAddress::createHumanAccountID (uPeerID) % STAmount::createHumanCurrency (speEnd.mCurrencyID) % rspEntry->getBalance ().getFullText () % rspEntry->getLimitPeer ().getFullText () ); } else if (dstCurrency && (uPeerID == mDstAccountID)) { // never skip the destination node candidates.push_back (std::make_pair (1000000, uPeerID)); } else { // save this candidate int out = getPathsOut (speEnd.mCurrencyID, uPeerID, dstCurrency, mDstAccountID); if (out != 0) candidates.push_back (std::make_pair (out, uPeerID)); else WriteLog(lsTRACE, Pathfinder) << "findPaths: " << RippleAddress::createHumanAccountID(uPeerID) << " has no paths out"; } } if (!candidates.empty ()) { std::sort (candidates.begin (), candidates.end (), BIND_TYPE (candCmp, mLedger->getLedgerSeq (), P_1, P_2)); int count = candidates.size (); if ((count > 10) && (speEnd.mAccountID != mSrcAccountID)) // try more paths from source count = 10; else if (count > 50) count = 50; std::vector< std::pair >::iterator it = candidates.begin (); while (count-- != 0) { STPath spNew (spPath); STPathElement speNew (it->second, speEnd.mCurrencyID, it->second); spNew.mPath.push_back (speNew); qspExplore.push (spNew); bContinued = true; WriteLog (lsTRACE, Pathfinder) << boost::str (boost::format ("findPaths: push explore: %s/%s -> %s/%s") % STAmount::createHumanCurrency (speEnd.mCurrencyID) % RippleAddress::createHumanAccountID (speEnd.mAccountID) % STAmount::createHumanCurrency (speEnd.mCurrencyID) % RippleAddress::createHumanAccountID (it->second)); ++it; } } } // XXX Flip argument order to norm. (currency, issuer) std::vector books; getApp().getOrderBookDB ().getBooksByTakerPays (speEnd.mIssuerID, speEnd.mCurrencyID, books); BOOST_FOREACH (OrderBook::ref book, books) { if (!spPath.hasSeen (ACCOUNT_XRP, book->getCurrencyOut (), book->getIssuerOut ()) && !matchesOrigin (book->getCurrencyOut (), book->getIssuerOut ()) && (!isLast || (book->getCurrencyOut () == mDstAmount.getCurrency () && book->getIssuerOut () == mDstAccountID))) { // A book we haven't seen before. Add it. STPath spNew (spPath); STPathElement speBook (ACCOUNT_XRP, book->getCurrencyOut (), book->getIssuerOut (), book->getCurrencyIn () != book->getCurrencyOut ()); spNew.mPath.push_back (speBook); // Add the order book. if (!!book->getCurrencyOut ()) { // For non-XRP out, don't end on the book, add the issuing account. STPathElement speAccount (book->getIssuerOut (), book->getCurrencyOut (), book->getIssuerOut ()); spNew.mPath.push_back (speAccount); // Add the account and currency } qspExplore.push (spNew); bContinued = true; WriteLog (lsTRACE, Pathfinder) << boost::str (boost::format ("findPaths: push book: %s/%s -> %s/%s") % STAmount::createHumanCurrency (speEnd.mCurrencyID) % RippleAddress::createHumanAccountID (speEnd.mIssuerID) % STAmount::createHumanCurrency (book->getCurrencyOut ()) % RippleAddress::createHumanAccountID (book->getIssuerOut ())); } } CondLog (!bContinued, lsTRACE, Pathfinder) << boost::str (boost::format ("findPaths: dropping: non-XRP -> dead end")); } } unsigned int iLimit = std::min (iMaxPaths, (unsigned int) vspResults.size ()); // Only filter, sort, and limit if have non-default paths. if (iLimit) { std::vector vMap; // Build map of quality to entry. for (int i = vspResults.size (); i--;) { STAmount saMaxAmountAct; STAmount saDstAmountAct; std::vector vpsExpanded; STPathSet spsPaths; STPath& spCurrent = vspResults[i]; spsPaths.addPath (spCurrent); // Just checking the current path. TER terResult; try { LedgerEntrySet lesSandbox (lesActive.duplicate ()); terResult = RippleCalc::rippleCalc ( lesSandbox, saMaxAmountAct, saDstAmountAct, vpsExpanded, mSrcAmount, // --> amount to send max. mDstAmount, // --> amount to deliver. mDstAccountID, mSrcAccountID, spsPaths, true, // --> bPartialPayment: Allow, it might contribute. false, // --> bLimitQuality: Assume normal transaction. true, // --> bNoRippleDirect: Providing the only path. true); // --> bStandAlone: Don't need to delete unfundeds. } catch (const std::exception& e) { WriteLog (lsINFO, Pathfinder) << "findPaths: Caught throw: " << e.what (); terResult = tefEXCEPTION; } if (tesSUCCESS == terResult) { uint64 uQuality = STAmount::getRate (saDstAmountAct, saMaxAmountAct); WriteLog (lsDEBUG, Pathfinder) << boost::str (boost::format ("findPaths: quality: %d: %s") % uQuality % spCurrent.getJson (0)); vMap.push_back (path_LQ_t (uQuality, spCurrent.mPath.size (), saDstAmountAct, i)); } else { WriteLog (lsDEBUG, Pathfinder) << boost::str (boost::format ("findPaths: dropping: %s: %s") % transToken (terResult) % spCurrent.getJson (0)); } } if (vMap.size ()) { std::sort (vMap.begin (), vMap.end (), bQualityCmp); // Lower is better and should be first. STAmount remaining = mDstAmount; if (bFound) { // must subtract liquidity in default path from remaining amount try { STAmount saMaxAmountAct, saDstAmountAct; std::vector vpsExpanded; LedgerEntrySet lesSandbox (lesActive.duplicate ()); TER result = RippleCalc::rippleCalc ( lesSandbox, saMaxAmountAct, saDstAmountAct, vpsExpanded, mSrcAmount, mDstAmount, mDstAccountID, mSrcAccountID, STPathSet (), true, // allow partial payment false, false, // don't suppress default paths, that's the point true); if (tesSUCCESS == result) { WriteLog (lsDEBUG, Pathfinder) << "Default path contributes: " << saDstAmountAct; remaining -= saDstAmountAct; } else { WriteLog (lsDEBUG, Pathfinder) << "Default path fails: " << transToken (result); } } catch (...) { WriteLog (lsDEBUG, Pathfinder) << "Default path causes exception"; } } for (int i = 0, iPathsLeft = iMaxPaths; (iPathsLeft > 0) && (i < vMap.size ()); ++i) { path_LQ_t& lqt = vMap[i]; if ((iPathsLeft != 1) || (lqt.get<2> () >= remaining)) { // last path must fill --iPathsLeft; remaining -= lqt.get<2> (); spsDst.addPath (vspResults[lqt.get<3> ()]); } else WriteLog (lsDEBUG, Pathfinder) << "Skipping a non-filling path: " << vspResults[lqt.get<3> ()].getJson (0); } if (remaining.isPositive ()) { bFound = false; WriteLog (lsINFO, Pathfinder) << "Paths could not send " << remaining << " of " << mDstAmount; } else bFound = true; WriteLog (lsDEBUG, Pathfinder) << boost::str (boost::format ("findPaths: RESULTS: %s") % spsDst.getJson (0)); } else { WriteLog (lsDEBUG, Pathfinder) << boost::str (boost::format ("findPaths: RESULTS: non-defaults filtered away")); } } WriteLog (lsDEBUG, Pathfinder) << boost::str (boost::format ("findPaths< bFound=%d") % bFound); return bFound; } boost::unordered_set usAccountSourceCurrencies (const RippleAddress& raAccountID, Ledger::ref lrLedger, bool includeXRP) { boost::unordered_set usCurrencies; // YYY Only bother if they are above reserve if (includeXRP) usCurrencies.insert (uint160 (CURRENCY_XRP)); // List of ripple lines. AccountItems rippleLines (raAccountID.getAccountID (), lrLedger, AccountItem::pointer (new RippleState ())); BOOST_FOREACH (AccountItem::ref item, rippleLines.getItems ()) { RippleState* rspEntry = (RippleState*) item.get (); const STAmount& saBalance = rspEntry->getBalance (); // Filter out non if (saBalance.isPositive () // Have IOUs to send. || (rspEntry->getLimitPeer () // Peer extends credit. && ((-saBalance) < rspEntry->getLimitPeer ()))) // Credit left. { usCurrencies.insert (saBalance.getCurrency ()); } } usCurrencies.erase (CURRENCY_BAD); return usCurrencies; } boost::unordered_set usAccountDestCurrencies (const RippleAddress& raAccountID, Ledger::ref lrLedger, bool includeXRP) { boost::unordered_set usCurrencies; if (includeXRP) usCurrencies.insert (uint160 (CURRENCY_XRP)); // Even if account doesn't exist // List of ripple lines. AccountItems rippleLines (raAccountID.getAccountID (), lrLedger, AccountItem::pointer (new RippleState ())); BOOST_FOREACH (AccountItem::ref item, rippleLines.getItems ()) { RippleState* rspEntry = (RippleState*) item.get (); const STAmount& saBalance = rspEntry->getBalance (); if (saBalance < rspEntry->getLimit ()) // Can take more usCurrencies.insert (saBalance.getCurrency ()); } usCurrencies.erase (CURRENCY_BAD); return usCurrencies; } bool Pathfinder::matchesOrigin (const uint160& currency, const uint160& issuer) { return (currency == mSrcCurrencyID) && (issuer == mSrcIssuerID); } int Pathfinder::getPathsOut (const uint160& currencyID, const uint160& accountID, bool isDstCurrency, const uint160& dstAccount) { #ifdef C11X std::pair accountCurrency (currencyID, accountID); #else std::pair accountCurrency (currencyID, accountID); #endif boost::unordered_map, int>::iterator it = mPOMap.find (accountCurrency); if (it != mPOMap.end ()) return it->second; int aFlags = mLedger->getSLEi(Ledger::getAccountRootIndex(accountID))->getFieldU32(sfFlags); bool const bAuthRequired = (aFlags & lsfRequireAuth) != 0; int count = 0; AccountItems& rippleLines (mRLCache->getRippleLines (accountID)); BOOST_FOREACH (AccountItem::ref item, rippleLines.getItems ()) { RippleState* rspEntry = (RippleState*) item.get (); if (currencyID != rspEntry->getLimit ().getCurrency ()) nothing (); else if (!rspEntry->getBalance ().isPositive () && (!rspEntry->getLimitPeer () || -rspEntry->getBalance () >= rspEntry->getLimitPeer () || (bAuthRequired && !rspEntry->getAuth ()))) nothing (); else if (isDstCurrency && (dstAccount == rspEntry->getAccountIDPeer ())) count += 10000; // count a path to the destination extra else ++count; } mPOMap[accountCurrency] = count; return count; } // vim:ts=4