From 7abfd354f8478db10f2b53e84ddfe3d859aa8952 Mon Sep 17 00:00:00 2001 From: JoelKatz Date: Mon, 19 Aug 2013 19:52:26 -0700 Subject: [PATCH] New improved Pathfinding engine --- modules/ripple_app/ledger/OrderBookDB.cpp | 14 +- modules/ripple_app/ledger/OrderBookDB.h | 5 +- .../ledger/ripple_LedgerEntrySet.cpp | 2 +- .../ripple_app/main/ripple_Application.cpp | 1 + .../ripple_app/paths/ripple_PathRequest.cpp | 48 +- modules/ripple_app/paths/ripple_PathRequest.h | 16 +- modules/ripple_app/paths/ripple_PathState.cpp | 4 +- .../ripple_app/paths/ripple_Pathfinder.cpp | 1163 +++++++++-------- modules/ripple_app/paths/ripple_Pathfinder.h | 54 +- modules/ripple_app/peers/ripple_Peer.cpp | 18 +- modules/ripple_app/rpc/RPCHandler.cpp | 7 +- .../ripple_core/functional/ripple_Config.cpp | 16 +- .../ripple_core/functional/ripple_Config.h | 11 +- .../functional/ripple_ConfigSections.h | 5 +- .../protocol/ripple_SerializedTypes.h | 28 +- rippled-example.cfg | 21 +- 16 files changed, 793 insertions(+), 620 deletions(-) diff --git a/modules/ripple_app/ledger/OrderBookDB.cpp b/modules/ripple_app/ledger/OrderBookDB.cpp index f414867551..3c702441a1 100644 --- a/modules/ripple_app/ledger/OrderBookDB.cpp +++ b/modules/ripple_app/ledger/OrderBookDB.cpp @@ -34,10 +34,12 @@ void OrderBookDB::setup (Ledger::ref ledger) mDestMap.clear (); mSourceMap.clear (); + mXRPBooks.clear (); WriteLog (lsDEBUG, OrderBookDB) << "OrderBookDB>"; // walk through the entire ledger looking for orderbook entries + int books = 0; uint256 currentIndex = ledger->getFirstLedgerIndex (); while (currentIndex.isNonZero ()) @@ -62,13 +64,16 @@ void OrderBookDB::setup (Ledger::ref ledger) mSourceMap[currencyIssuer_ct (ci, ii)].push_back (book); mDestMap[currencyIssuer_ct (co, io)].push_back (book); + if (co.isZero()) + mXRPBooks.insert(currencyIssuer_ct (ci, ii)); + ++books; } } currentIndex = ledger->getNextLedgerIndex (currentIndex); } - WriteLog (lsDEBUG, OrderBookDB) << "OrderBookDB<"; + WriteLog (lsDEBUG, OrderBookDB) << "OrderBookDB< " << books << " books found"; } // return list of all orderbooks that want this issuerID and currencyID @@ -85,6 +90,13 @@ void OrderBookDB::getBooksByTakerPays (const uint160& issuerID, const uint160& c bookRet.clear (); } +bool OrderBookDB::isBookToXRP(const uint160& issuerID, const uint160& currencyID) +{ + ScopedLockType sl (mLock, __FILE__, __LINE__); + + return mXRPBooks.count(currencyIssuer_ct(currencyID, issuerID)) > 0; +} + // return list of all orderbooks that give this issuerID and currencyID void OrderBookDB::getBooksByTakerGets (const uint160& issuerID, const uint160& currencyID, std::vector& bookRet) diff --git a/modules/ripple_app/ledger/OrderBookDB.h b/modules/ripple_app/ledger/OrderBookDB.h index 7787e2685b..b6fd7a4474 100644 --- a/modules/ripple_app/ledger/OrderBookDB.h +++ b/modules/ripple_app/ledger/OrderBookDB.h @@ -50,6 +50,8 @@ public: void getBooksByTakerGets (const uint160& issuerID, const uint160& currencyID, std::vector& bookRet); + bool isBookToXRP (const uint160& issuerID, const uint160& currencyID); + BookListeners::pointer getBookListeners (const uint160& currencyPays, const uint160& currencyGets, const uint160& issuerPays, const uint160& issuerGets); @@ -61,11 +63,12 @@ public: private: boost::unordered_map< currencyIssuer_t, std::vector > mSourceMap; // by ci/ii + boost::unordered_map< currencyIssuer_t, std::vector > mDestMap; // by co/io + boost::unordered_set< currencyIssuer_t > mXRPBooks; // does an order book to XRP exist typedef RippleRecursiveMutex LockType; typedef LockType::ScopedLockType ScopedLockType; LockType mLock; - boost::unordered_map< currencyIssuer_t, std::vector > mDestMap; // by co/io // issuerPays, issuerGets, currencyPays, currencyGets std::map > > > mListeners; diff --git a/modules/ripple_app/ledger/ripple_LedgerEntrySet.cpp b/modules/ripple_app/ledger/ripple_LedgerEntrySet.cpp index 70276fc559..7ce418e67a 100644 --- a/modules/ripple_app/ledger/ripple_LedgerEntrySet.cpp +++ b/modules/ripple_app/ledger/ripple_LedgerEntrySet.cpp @@ -1142,7 +1142,7 @@ uint32 LedgerEntrySet::rippleTransferRate (const uint160& uIssuerID) ? sleAccount->getFieldU32 (sfTransferRate) : QUALITY_ONE; - WriteLog (lsDEBUG, LedgerEntrySet) << boost::str (boost::format ("rippleTransferRate: uIssuerID=%s account_exists=%d transfer_rate=%f") + WriteLog (lsTRACE, LedgerEntrySet) << boost::str (boost::format ("rippleTransferRate: uIssuerID=%s account_exists=%d transfer_rate=%f") % RippleAddress::createHumanAccountID (uIssuerID) % !!sleAccount % (uQuality / 1000000000.0)); diff --git a/modules/ripple_app/main/ripple_Application.cpp b/modules/ripple_app/main/ripple_Application.cpp index c84bc3152b..0c782f789f 100644 --- a/modules/ripple_app/main/ripple_Application.cpp +++ b/modules/ripple_app/main/ripple_Application.cpp @@ -456,6 +456,7 @@ public: updateTables (); mFeatures->addInitialFeatures (); + Pathfinder::initPathTable (); if (getConfig ().START_UP == Config::FRESH) { diff --git a/modules/ripple_app/paths/ripple_PathRequest.cpp b/modules/ripple_app/paths/ripple_PathRequest.cpp index 2289733692..6d6f187c85 100644 --- a/modules/ripple_app/paths/ripple_PathRequest.cpp +++ b/modules/ripple_app/paths/ripple_PathRequest.cpp @@ -16,6 +16,8 @@ PathRequest::PathRequest (const boost::shared_ptr& subscriber) , jvStatus (Json::objectValue) , bValid (false) , bNew (true) + , iLastLevel (0) + , bLastSuccess (false) { } @@ -266,6 +268,43 @@ bool PathRequest::doUpdate (RippleLineCache::ref cache, bool fast) Json::Value jvArray = Json::arrayValue; + int iLevel = iLastLevel; + bool loaded = getApp().getFeeTrack().isLoadedLocal(); + + if (iLevel == 0) + { // first pass + if (loaded) + iLevel = getConfig().PATH_SEARCH_FAST; + else if (!fast) + iLevel = getConfig().PATH_SEARCH_OLD; + else if (getConfig().PATH_SEARCH < getConfig().PATH_SEARCH_MAX) + iLevel = getConfig().PATH_SEARCH + 1; // start with an extra boost + 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 (!loaded && (iLevel < getConfig().PATH_SEARCH)) + ++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; + } + + bool found = false; + BOOST_FOREACH (const currIssuer_t & currIssuer, sourceCurrencies) { { @@ -273,12 +312,12 @@ bool PathRequest::doUpdate (RippleLineCache::ref cache, bool fast) WriteLog (lsDEBUG, PathRequest) << "Trying to find paths: " << test.getFullText (); } bool valid; - STPathSet spsPaths; + STPathSet& spsPaths = mContext[currIssuer]; Pathfinder pf (cache, raSrcAccount, raDstAccount, currIssuer.first, currIssuer.second, saDstAmount, valid); CondLog (!valid, lsINFO, PathRequest) << "PF request not valid"; - if (valid && pf.findPaths (getConfig ().PATH_SEARCH_SIZE - (fast ? 1 : 0), 3, spsPaths)) + if (valid && pf.findPaths (iLevel, 4, spsPaths)) { LedgerEntrySet lesSandbox (cache->getLedger (), tapNONE); std::vector vpsExpanded; @@ -298,6 +337,7 @@ bool PathRequest::doUpdate (RippleLineCache::ref cache, bool fast) Json::Value jvEntry (Json::objectValue); jvEntry["source_amount"] = saMaxAmountAct.getJson (0); jvEntry["paths_computed"] = spsPaths.getJson (0); + found = true; jvArray.append (jvEntry); } else @@ -310,6 +350,10 @@ bool PathRequest::doUpdate (RippleLineCache::ref cache, bool fast) WriteLog (lsINFO, PathRequest) << "No paths found"; } } + + iLastLevel = iLevel; + bLastSuccess = found; + jvStatus["alternatives"] = jvArray; return true; } diff --git a/modules/ripple_app/paths/ripple_PathRequest.h b/modules/ripple_app/paths/ripple_PathRequest.h index 1575bed472..45f224f486 100644 --- a/modules/ripple_app/paths/ripple_PathRequest.h +++ b/modules/ripple_app/paths/ripple_PathRequest.h @@ -20,7 +20,7 @@ class RippleLineCache; class PathRequest : public boost::enable_shared_from_this { public: - typedef boost::weak_ptr wptr; + typedef boost::weak_ptr wptr; typedef boost::shared_ptr pointer; typedef const pointer& ref; typedef const wptr& wref; @@ -56,15 +56,19 @@ private: Json::Value jvStatus; // Last result // Client request parameters - RippleAddress raSrcAccount; - RippleAddress raDstAccount; - STAmount saDstAmount; - std::set sciSourceCurrencies; - std::vector vjvBridges; + RippleAddress raSrcAccount; + RippleAddress raDstAccount; + STAmount saDstAmount; + std::set sciSourceCurrencies; + std::vector vjvBridges; + std::map mContext; bool bValid; bool bNew; + int iLastLevel; + bool bLastSuccess; + // Track all requests static std::set sRequests; diff --git a/modules/ripple_app/paths/ripple_PathState.cpp b/modules/ripple_app/paths/ripple_PathState.cpp index 5b3a4cf056..b25166198b 100644 --- a/modules/ripple_app/paths/ripple_PathState.cpp +++ b/modules/ripple_app/paths/ripple_PathState.cpp @@ -391,7 +391,7 @@ void PathState::setExpanded ( const uint160 uOutIssuerID = saOutReq.getIssuer (); const uint160 uSenderIssuerID = !!uMaxCurrencyID ? uSenderID : ACCOUNT_XRP; // Sender is always issuer for non-XRP. - WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded> %s") % spSourcePath.getJson (0)); + WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("setExpanded> %s") % spSourcePath.getJson (0)); lesEntries = lesSource.duplicate (); @@ -467,7 +467,7 @@ void PathState::setExpanded ( { if (tesSUCCESS == terStatus) { - WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("setExpanded: element in path:")); + WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("setExpanded: element in path:")); terStatus = pushNode (speElement.getNodeType (), speElement.getAccountID (), speElement.getCurrency (), speElement.getIssuerID ()); } } diff --git a/modules/ripple_app/paths/ripple_Pathfinder.cpp b/modules/ripple_app/paths/ripple_Pathfinder.cpp index dc798110ec..8e3d74f1f9 100644 --- a/modules/ripple_app/paths/ripple_Pathfinder.cpp +++ b/modules/ripple_app/paths/ripple_Pathfinder.cpp @@ -1,3 +1,4 @@ + //------------------------------------------------------------------------------ /* Copyright (c) 2011-2013, OpenCoin, Inc. @@ -64,56 +65,6 @@ static bool bQualityCmp (const path_LQ_t& a, const path_LQ_t& b) 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) { @@ -126,20 +77,6 @@ static bool candCmp (uint32 seq, const candidate_t& first, const candidate_t& se 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) @@ -162,59 +99,22 @@ Pathfinder::Pathfinder (RippleLineCache::ref cache, bValid = true; + // FIXME: This is not right getApp().getOrderBookDB ().setup (mLedger); m_loadEvent = getApp().getJobQueue ().getLoadEvent (jtPATH_FIND, "FindPath"); - // Construct the default path for later comparison. + 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)); - 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. +bool Pathfinder::findPaths (int iLevel, const unsigned int iMaxPaths, STPathSet& pathsOut) +{ // pathsOut contains only non-default paths without source or destiation +// On input, pathsOut contains any paths you want to ensure are included if still good WriteLog (lsTRACE, Pathfinder) << boost::str (boost::format ("findPaths> mSrcAccountID=%s mDstAccountID=%s mDstAmount=%s mSrcCurrencyID=%s mSrcIssuerID=%s") % RippleAddress::createHumanAccountID (mSrcAccountID) @@ -231,518 +131,231 @@ bool Pathfinder::findPaths (const unsigned int iMaxSteps, const unsigned int iMa return false; } - LedgerEntrySet lesActive (mLedger, tapNONE); - boost::unordered_map aiMap; - - SLE::pointer sleSrc = lesActive.entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (mSrcAccountID)); + bool bSrcXrp = mSrcCurrencyID.isZero(); + bool bDstXrp = mDstAmount.getCurrency().isZero(); + SLE::pointer sleSrc = mLedger->getSLEi(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")); + SLE::pointer sleDest = mLedger->getSLEi(Ledger::getAccountRootIndex(mDstAccountID)); + if (!sleDest && (!bDstXrp || (mDstAmount < mLedger->getReserve(0)))) return false; + + PaymentType paymentType; + if (bSrcXrp && bDstXrp) + { // XRP -> XRP + + WriteLog (lsDEBUG, Pathfinder) << "XRP to XRP payment"; + paymentType = pt_XRP_to_XRP; + + } + else if (bSrcXrp) + { // XRP -> non-XRP + + WriteLog (lsDEBUG, Pathfinder) << "XRP to non-XRP payment"; + paymentType = pt_XRP_to_nonXRP; + + } + else if (bDstXrp) + { // non-XRP -> XRP + + WriteLog (lsDEBUG, Pathfinder) << "non-XRP to XRP payment"; + paymentType = pt_nonXRP_to_XRP; + + } + else if (mSrcCurrencyID == mDstAmount.getCurrency()) + { // non-XRP -> non-XRP - Same currency + + WriteLog (lsDEBUG, Pathfinder) << "non-XRP to non-XRP - same currency"; + paymentType = pt_nonXRP_to_same; + + } + else + { // non-XRP to non-XRP - Different currency + + WriteLog (lsDEBUG, Pathfinder) << "non-XRP to non-XRP - cross currency"; + paymentType = pt_nonXRP_to_nonXRP; + } - 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) + BOOST_FOREACH(CostedPath_t const& costedPath, mPathTable[paymentType]) { - // Add forced source issuer to seed, via issuer's account. - STPathElement speIssuer (mSrcIssuerID, mSrcCurrencyID, mSrcIssuerID); - - spSeed.addElement (speEnd); + if (costedPath.first <= iLevel) + { + getPaths(costedPath.second); + } } - // Push the seed path to explore. - qspExplore.push (spSeed); + WriteLog (lsDEBUG, Pathfinder) << mCompletePaths.size() << " complete paths found"; - while (qspExplore.size ()) // Have paths to explore? + BOOST_FOREACH(const STPath& path, pathsOut) + { // make sure no paths were lost + bool found = false; + BOOST_FOREACH(const STPath& ePath, mCompletePaths) + { + if (ePath == path) + { + found = true; + break; + } + } + if (!found) + mCompletePaths.addPath(path); + } + + WriteLog (lsDEBUG, Pathfinder) << mCompletePaths.size() << " paths to filter"; + + if (mCompletePaths.size() > iMaxPaths) + pathsOut = filterPaths(iMaxPaths); + else + pathsOut = mCompletePaths; + + return true; // Even if we find no paths, default paths may work, and we don't check them currently +} + +STPathSet Pathfinder::filterPaths(int iMaxPaths) +{ + if (mCompletePaths.size() <= iMaxPaths) + return mCompletePaths; + + STAmount remaining = mDstAmount; + + // must subtract liquidity in default path from remaining amount + try { - STPath spPath = qspExplore.front (); + STAmount saMaxAmountAct, saDstAmountAct; + std::vector vpsExpanded; + LedgerEntrySet lesSandbox (mLedger, tapNONE); - qspExplore.pop (); // Pop the first path from the queue. + 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); - speEnd = spPath.mPath.back (); // Get the last node from the path. - - if (!speEnd.mCurrencyID // Tail output is XRP. - && !mDstAmount.getCurrency ()) // Which is dst currency. + if (tesSUCCESS == result) { - // 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")); + WriteLog (lsDEBUG, Pathfinder) << "Default path contributes: " << saDstAmountAct; + remaining -= saDstAmountAct; } else { - // Last element is for non-XRP, continue by adding ripple lines and order books. + WriteLog (lsDEBUG, Pathfinder) << "Default path fails: " << transToken (result); + } + } + catch (...) + { + WriteLog (lsDEBUG, Pathfinder) << "Default path causes exception"; + } - // Create new paths for each outbound account not already in the path. + std::vector vMap; - SLE::pointer sleEnd = lesActive.entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (speEnd.mAccountID)); + // Build map of quality to entry. + for (int i = mCompletePaths.size (); i--;) + { + STAmount saMaxAmountAct; + STAmount saDstAmountAct; + std::vector vpsExpanded; + STPathSet spsPaths; + STPath& spCurrent = mCompletePaths[i]; - CondLog (!sleEnd, lsDEBUG, Pathfinder) - << boost::str (boost::format ("findPaths: tail: %s/%s : ") - % RippleAddress::createHumanAccountID (speEnd.mAccountID) - % RippleAddress::createHumanAccountID (speEnd.mIssuerID)); + spsPaths.addPath (spCurrent); // Just checking the current path. - 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 (); + TER terResult; - AccountItems& rippleLines (mRLCache->getRippleLines (speEnd.mAccountID)); + try + { + LedgerEntrySet lesSandbox (mLedger, tapNONE); - std::vector< std::pair > candidates; - candidates.reserve (rippleLines.getItems ().size ()); + 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 (); - BOOST_FOREACH (AccountItem::ref item, rippleLines.getItems ()) - { - RippleState* rspEntry = (RippleState*) item.get (); - const uint160& uPeerID = rspEntry->getAccountIDPeer (); + terResult = tefEXCEPTION; + } - 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 (tesSUCCESS == terResult) + { + uint64 uQuality = STAmount::getRate (saDstAmountAct, saMaxAmountAct); - if (out != 0) - candidates.push_back (std::make_pair (out, uPeerID)); - else - WriteLog(lsTRACE, Pathfinder) << "findPaths: " << RippleAddress::createHumanAccountID(uPeerID) << " has no paths out"; - } - } + WriteLog (lsDEBUG, Pathfinder) + << boost::str (boost::format ("findPaths: quality: %d: %s") + % uQuality + % spCurrent.getJson (0)); - 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")); + 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)); } } - unsigned int iLimit = std::min (iMaxPaths, (unsigned int) vspResults.size ()); + STPathSet spsDst; - // Only filter, sort, and limit if have non-default paths. - if (iLimit) + if (vMap.size()) { - std::vector vMap; + std::sort (vMap.begin (), vMap.end (), bQualityCmp); // Lower is better and should be first. - // Build map of quality to entry. - for (int i = vspResults.size (); i--;) + + for (int i = 0, iPathsLeft = iMaxPaths; (iPathsLeft > 0) && (i < vMap.size ()); ++i) { - STAmount saMaxAmountAct; - STAmount saDstAmountAct; - std::vector vpsExpanded; - STPathSet spsPaths; - STPath& spCurrent = vspResults[i]; + path_LQ_t& lqt = vMap[i]; - spsPaths.addPath (spCurrent); // Just checking the current path. - - TER terResult; - - try + if ((iPathsLeft != 1) || (lqt.get<2> () >= remaining)) { - 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)); + // last path must fill + --iPathsLeft; + remaining -= lqt.get<2> (); + spsDst.addPath (mCompletePaths[lqt.get<3> ()]); } else - { - WriteLog (lsDEBUG, Pathfinder) - << boost::str (boost::format ("findPaths: dropping: %s: %s") - % transToken (terResult) - % spCurrent.getJson (0)); - } + WriteLog (lsDEBUG, Pathfinder) << "Skipping a non-filling path: " << mCompletePaths[lqt.get<3> ()].getJson (0); } - if (vMap.size ()) + if (remaining.isPositive ()) + { + WriteLog (lsINFO, Pathfinder) << "Paths could not send " << remaining << " of " << mDstAmount; + } + else { - 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")); - } + } + 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; + return spsDst; } boost::unordered_set usAccountSourceCurrencies (const RippleAddress& raAccountID, Ledger::ref lrLedger, @@ -801,7 +414,13 @@ boost::unordered_set usAccountDestCurrencies (const RippleAddress& raAc bool Pathfinder::matchesOrigin (const uint160& currency, const uint160& issuer) { - return (currency == mSrcCurrencyID) && (issuer == mSrcIssuerID); + if (currency != mSrcCurrencyID) + return false; + + if (currency.isZero()) + return true; + + return (issuer == mSrcIssuerID) || (issuer == mSrcAccountID); } int Pathfinder::getPathsOut (const uint160& currencyID, const uint160& accountID, @@ -842,4 +461,394 @@ int Pathfinder::getPathsOut (const uint160& currencyID, const uint160& accountID return count; } +void Pathfinder::addLink( + const STPathSet& currentPaths, // The paths to build from + STPathSet& incompletePaths, // The set of partial paths we add to + int addFlags) +{ + WriteLog (lsDEBUG, Pathfinder) << "addLink< on " << currentPaths.size() << " source(s), flags=" << addFlags; + BOOST_FOREACH(const STPath& path, currentPaths) + { + addLink(path, incompletePaths, addFlags); + } +} + +STPathSet& Pathfinder::getPaths(PathType_t const& type, bool addComplete) +{ + std::map< PathType_t, STPathSet >::iterator it = mPaths.find(type); + + // We already have these paths + if (it != mPaths.end()) + return it->second; + + // The type is empty + if (type.empty()) + return mPaths[type]; + + NodeType toAdd = type.back(); + PathType_t pathType(type); + pathType.pop_back(); + + STPathSet pathsIn = getPaths(pathType, false); + STPathSet& pathsOut = mPaths[type]; + + WriteLog (lsDEBUG, Pathfinder) + << "getPaths< adding onto '" + << pathTypeToString(pathType) << "' to get '" + << pathTypeToString(type) << "'"; + + int cp = mCompletePaths.size(); + + switch (toAdd) + { + + case nt_SOURCE: + + { // source is an empty path + assert(pathsOut.isEmpty()); + pathsOut.addPath(STPath()); + } + break; + + case nt_ACCOUNTS: + addLink(pathsIn, pathsOut, afADD_ACCOUNTS); + break; + + case nt_BOOKS: + addLink(pathsIn, pathsOut, afADD_BOOKS); + break; + + case nt_XRP_BOOK: + addLink(pathsIn, pathsOut, afADD_BOOKS | afOB_XRP); + break; + + case nt_DEST_BOOK: + addLink(pathsIn, pathsOut, afADD_BOOKS | afOB_LAST); + break; + + case nt_DESTINATION: + // FIXME: What if a different issuer was specified on the destination amount + addLink(pathsIn, pathsOut, afADD_ACCOUNTS | afAC_LAST); + break; + + } + + CondLog (mCompletePaths.size() != cp, lsDEBUG, Pathfinder) + << (mCompletePaths.size() - cp) + << " complete paths added"; + WriteLog (lsDEBUG, Pathfinder) << "getPaths> " << pathsOut.size() << " partial paths found"; + return pathsOut; +} + +void Pathfinder::addLink( + const STPath& currentPath, // The path to build from + STPathSet& incompletePaths, // The set of partial paths we add to + int addFlags) +{ + STPathElement const& pathEnd = currentPath.isEmpty() ? mSource : currentPath.mPath.back (); + uint160 const& uEndCurrency = pathEnd.mCurrencyID; + uint160 const& uEndIssuer = pathEnd.mIssuerID; + uint160 const& uEndAccount = pathEnd.mAccountID; + bool const bOnXRP = uEndCurrency.isZero(); + + WriteLog (lsTRACE, Pathfinder) << "addLink< flags=" << addFlags << " onXRP=" << bOnXRP; + WriteLog (lsTRACE, Pathfinder) << currentPath.getJson(0); + + if (addFlags & afADD_ACCOUNTS) + { // add accounts + if (bOnXRP) + { + if (mDstAmount.isNative() && !currentPath.isEmpty()) + { // non-default path to XRP destination + WriteLog (lsTRACE, Pathfinder) << "complete path found ax: " << currentPath.getJson(0); + mCompletePaths.addUniquePath(currentPath); + } + } + else + { // search for accounts to add + SLE::pointer sleEnd = mLedger->getSLEi(Ledger::getAccountRootIndex(uEndAccount)); + if (sleEnd) + { + bool const bRequireAuth = isSetBit(sleEnd->getFieldU32(sfFlags), lsfRequireAuth); + bool const bIsEndCurrency = (uEndCurrency == mDstAmount.getCurrency()); + + AccountItems& rippleLines(mRLCache->getRippleLines(uEndAccount)); + + std::vector< std::pair > candidates; + candidates.reserve(rippleLines.getItems().size()); + + BOOST_FOREACH(AccountItem::ref item, rippleLines.getItems()) + { + RippleState const& rspEntry = * reinterpret_cast(item.get()); + uint160 const& acctID = rspEntry.getAccountIDPeer(); + + if ((uEndCurrency == rspEntry.getLimit().getCurrency()) && + !currentPath.hasSeen(acctID, uEndCurrency, acctID)) + { // path is for correct currency and has not been seen + if (!rspEntry.getBalance().isPositive() + && (!rspEntry.getLimitPeer() + || -rspEntry.getBalance() >= rspEntry.getLimitPeer() + || (bRequireAuth && !rspEntry.getAuth()))) + { + // path has no credit + } + else if (acctID == mDstAccountID) + { // destination is always worth trying + if (uEndCurrency == mDstAmount.getCurrency()) + { // this is a complete path + if (!currentPath.isEmpty()) + { + WriteLog (lsTRACE, Pathfinder) << "complete path found ae: " << currentPath.getJson(0); + mCompletePaths.addUniquePath(currentPath); + } + } + else if ((addFlags & afAC_LAST) == 0) + { // this is a high-priority candidate + candidates.push_back(std::make_pair(100000, acctID)); + } + } + else if (acctID == mSrcAccountID) + { + // going back to the source is bad + } + else if ((addFlags & afAC_LAST) == 0) + { // save this candidate + int out = getPathsOut(uEndCurrency, acctID, bIsEndCurrency, mDstAccountID); + if (out) + candidates.push_back(std::make_pair(out, acctID)); + } + } + } + + 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) && (uEndAccount != mSrcAccountID)) // allow more paths from source + count = 10; + else if (count > 50) + count = 50; + + std::vector< std::pair >::const_iterator it = candidates.begin(); + while (count-- != 0) + { // Add accounts to incompletePaths + incompletePaths.assembleAdd(currentPath, STPathElement(STPathElement::typeAccount, it->second, uEndCurrency, it->second)); + ++it; + } + } + + } + else + { + WriteLog(lsWARNING, Pathfinder) << "Path ends on non-existent issuer"; + } + } + } + if (addFlags & afADD_BOOKS) + { // add order books + if (addFlags & afOB_XRP) + { // to XRP only + if (!bOnXRP && getApp().getOrderBookDB().isBookToXRP(uEndIssuer, uEndCurrency)) + { + incompletePaths.assembleAdd(currentPath, STPathElement(STPathElement::typeCurrency, ACCOUNT_XRP, CURRENCY_XRP, ACCOUNT_XRP)); + } + } + else + { + bool bDestOnly = (addFlags & afOB_LAST) != 0; + std::vector books; + getApp().getOrderBookDB().getBooksByTakerPays(uEndIssuer, uEndCurrency, books); + WriteLog (lsTRACE, Pathfinder) << books.size() << " books found from this currency/issuer"; + BOOST_FOREACH(OrderBook::ref book, books) + { + if (!currentPath.hasSeen (ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut()) && + !matchesOrigin(book->getCurrencyOut(), book->getIssuerOut()) && + (!bDestOnly || (book->getCurrencyOut() == mDstAmount.getCurrency()))) + { + STPath newPath(currentPath); + + if (book->getCurrencyOut().isZero()) + { // to XRP + + // add the order book itself + newPath.addElement(STPathElement(STPathElement::typeCurrency, ACCOUNT_XRP, CURRENCY_XRP, ACCOUNT_XRP)); + + if (mDstAmount.getCurrency().isZero()) + { // destination is XRP, add account and path is complete + WriteLog (lsTRACE, Pathfinder) << "complete path found bx: " << currentPath.getJson(0); + mCompletePaths.addUniquePath(newPath); + } + else + incompletePaths.addPath(newPath); + } + else + { + // add the order book itself + newPath.addElement(STPathElement(STPathElement::typeCurrency | STPathElement::typeIssuer, + ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut())); + + if ((book->getIssuerOut() == mDstAccountID) && book->getCurrencyOut() == mDstAmount.getCurrency()) + { // with the destination account, this path is complete + WriteLog (lsTRACE, Pathfinder) << "complete path found ba: " << currentPath.getJson(0); + mCompletePaths.addUniquePath(newPath); + } + else + { // add issuer's account, path still incomplete + incompletePaths.assembleAdd(newPath, + STPathElement(STPathElement::typeAccount, + book->getIssuerOut(), book->getCurrencyOut(), book->getIssuerOut())); + } + } + + } + } + } + } +} + +std::map Pathfinder::mPathTable; + +Pathfinder::PathType_t Pathfinder::makePath(char const *string) +{ + PathType_t ret; + + while (true) + { + switch (*string++) + { + case 's': // source + ret.push_back(nt_SOURCE); + break; + + case 'a': // accounts + ret.push_back(nt_ACCOUNTS); + break; + + case 'b': // books + ret.push_back(nt_BOOKS); + break; + + case 'x': // xrp book + ret.push_back(nt_XRP_BOOK); + break; + + case 'f': // book to final currency + ret.push_back(nt_DEST_BOOK); + break; + + case 'd': // destination (with account, if required and not already present) + ret.push_back(nt_DESTINATION); + break; + + case 0: + return ret; + } + } +} + +std::string Pathfinder::pathTypeToString(PathType_t const& type) +{ + std::string ret; + + BOOST_FOREACH(NodeType const& node, type) + { + switch (node) + { + case nt_SOURCE: + ret.append("s"); + break; + case nt_ACCOUNTS: + ret.append("a"); + break; + case nt_BOOKS: + ret.append("b"); + break; + case nt_XRP_BOOK: + ret.append("x"); + break; + case nt_DEST_BOOK: + ret.append("f"); + break; + case nt_DESTINATION: + ret.append("d"); + break; + } + } + + return ret; +} + +// Costs: +// 0 = minimum to make some payments possible +// 1 = include trivial paths to make common cases work +// 4 = normal fast search level +// 7 = normal slow search level +// 10 = most agressive + +void Pathfinder::initPathTable() +{ // CAUTION: Do not include rules that build default paths + { // XRP to XRP + CostedPathList_t& list = mPathTable[pt_XRP_to_XRP]; + + list.push_back(CostedPath_t(8, makePath("sbxd"))); // source -> book -> book_to_XRP -> destination + list.push_back(CostedPath_t(9, makePath("sbaxd"))); // source -> book -> gateway -> to_XRP ->destination + } + + { // XRP to non-XRP + CostedPathList_t& list = mPathTable[pt_XRP_to_nonXRP]; + + list.push_back(CostedPath_t(0, makePath("sfd"))); // source -> book -> gateway + list.push_back(CostedPath_t(3, makePath("sfad"))); // source -> book -> account -> destination + list.push_back(CostedPath_t(5, makePath("sfaad"))); // source -> book -> account -> account -> destination + list.push_back(CostedPath_t(6, makePath("sbfd"))); // source -> book -> book -> destination + list.push_back(CostedPath_t(8, makePath("sbafd"))); // source -> book -> account -> book -> destination + list.push_back(CostedPath_t(9, makePath("sbfad"))); // source -> book -> book -> account -> destination + list.push_back(CostedPath_t(10, makePath("sbafad"))); + } + + { // non-XRP to XRP + CostedPathList_t& list = mPathTable[pt_nonXRP_to_XRP]; + + list.push_back(CostedPath_t(0, makePath("sxd"))); // gateway buys XRP + list.push_back(CostedPath_t(1, makePath("saxd"))); // source -> gateway -> book(XRP) -> dest + list.push_back(CostedPath_t(6, makePath("saaxd"))); + list.push_back(CostedPath_t(7, makePath("sbxd"))); + list.push_back(CostedPath_t(8, makePath("sabxd"))); + list.push_back(CostedPath_t(9, makePath("sabaxd"))); + } + + { // non-XRP to non-XRP (same currency) + CostedPathList_t& list = mPathTable[pt_nonXRP_to_same]; + + list.push_back(CostedPath_t(1, makePath("sad"))); // source -> gateway -> destination + list.push_back(CostedPath_t(1, makePath("sfd"))); // source -> book -> destination + list.push_back(CostedPath_t(4, makePath("safd"))); // source -> gateway -> book -> destination + list.push_back(CostedPath_t(4, makePath("sfad"))); + list.push_back(CostedPath_t(5, makePath("saad"))); + list.push_back(CostedPath_t(5, makePath("sxfd"))); + list.push_back(CostedPath_t(6, makePath("sxfad"))); + list.push_back(CostedPath_t(6, makePath("safad"))); + list.push_back(CostedPath_t(6, makePath("saxfd"))); // source -> gateway -> book to XRP -> book -> destination + list.push_back(CostedPath_t(6, makePath("saxfad"))); + list.push_back(CostedPath_t(8, makePath("saaad"))); + } + + { // non-XRP to non-XRP (different currency) + CostedPathList_t& list = mPathTable[pt_nonXRP_to_nonXRP]; + + list.push_back(CostedPath_t(1, makePath("sfad"))); + list.push_back(CostedPath_t(1, makePath("safd"))); + list.push_back(CostedPath_t(3, makePath("safad"))); + list.push_back(CostedPath_t(4, makePath("sxfd"))); + list.push_back(CostedPath_t(5, makePath("saxfd"))); + list.push_back(CostedPath_t(6, makePath("saxfad"))); + list.push_back(CostedPath_t(7, makePath("saafd"))); + list.push_back(CostedPath_t(8, makePath("saafad"))); + list.push_back(CostedPath_t(9, makePath("safaad"))); + } + +} + // vim:ts=4 diff --git a/modules/ripple_app/paths/ripple_Pathfinder.h b/modules/ripple_app/paths/ripple_Pathfinder.h index 9a5f03ba89..46372b3160 100644 --- a/modules/ripple_app/paths/ripple_Pathfinder.h +++ b/modules/ripple_app/paths/ripple_Pathfinder.h @@ -47,24 +47,54 @@ public: const RippleAddress& srcAccountID, const RippleAddress& dstAccountID, const uint160& srcCurrencyID, const uint160& srcIssuerID, const STAmount& dstAmount, bool& bValid); - bool findPaths (const unsigned int iMaxSteps, const unsigned int iMaxPaths, STPathSet& spsDst); - - bool bDefaultPath (const STPath& spPath); + static void initPathTable(); + bool findPaths (int iLevel, const unsigned int iMaxPaths, STPathSet& spsDst); private: - // void addOptions(PathOption::pointer tail); + + enum PaymentType + { + pt_XRP_to_XRP, + pt_XRP_to_nonXRP, + pt_nonXRP_to_XRP, + pt_nonXRP_to_same, + pt_nonXRP_to_nonXRP + }; + + enum NodeType + { + nt_SOURCE, // The source account with an issuer account, if required + nt_ACCOUNTS, // Accounts that connect from this source/currency + nt_BOOKS, // Order books that connect to this currency + nt_XRP_BOOK, // The order book from this currency to XRP + nt_DEST_BOOK, // The order book to the destination currency/issuer + nt_DESTINATION // The destination account only + }; + + typedef std::vector PathType_t; + typedef std::pair CostedPath_t; + typedef std::vector CostedPathList_t; // returns true if any building paths are now complete? bool checkComplete (STPathSet& retPathSet); - // void addPathOption(PathOption::pointer pathOption); + static std::string pathTypeToString(PathType_t const&); bool matchesOrigin (const uint160& currency, const uint160& issuer); int getPathsOut (const uint160& currency, const uint160& accountID, bool isDestCurrency, const uint160& dest); -private: + void addLink(const STPath& currentPath, STPathSet& incompletePaths, int addFlags); + void addLink(const STPathSet& currentPaths, STPathSet& incompletePaths, int addFlags); + STPathSet& getPaths(const PathType_t& type, bool addComplete = true); + STPathSet filterPaths(int iMaxPaths); + + // Our main table of paths + + static std::map mPathTable; + static PathType_t makePath(char const*); + uint160 mSrcAccountID; uint160 mDstAccountID; STAmount mDstAmount; @@ -73,15 +103,21 @@ private: STAmount mSrcAmount; Ledger::pointer mLedger; - PathState::pointer mPsDefault; LoadEvent::pointer m_loadEvent; RippleLineCache::pointer mRLCache; + STPathElement mSource; + STPathSet mCompletePaths; + std::map< PathType_t, STPathSet > mPaths; + boost::unordered_map mRLMap; boost::unordered_map, int> mPOMap; - // std::list mBuildingPaths; - // std::list mCompletePaths; + static const uint32 afADD_ACCOUNTS = 0x001; // Add ripple paths + static const uint32 afADD_BOOKS = 0x002; // Add order books + static const uint32 afOB_XRP = 0x010; // Add order book to XRP only + static const uint32 afOB_LAST = 0x040; // Must link to destination currency + static const uint32 afAC_LAST = 0x080; // Destination account only }; boost::unordered_set usAccountDestCurrencies (const RippleAddress& raAccountID, Ledger::ref lrLedger, diff --git a/modules/ripple_app/peers/ripple_Peer.cpp b/modules/ripple_app/peers/ripple_Peer.cpp index 0a2b3ef863..5c25c61655 100644 --- a/modules/ripple_app/peers/ripple_Peer.cpp +++ b/modules/ripple_app/peers/ripple_Peer.cpp @@ -1250,14 +1250,7 @@ void PeerImp::recvTransaction (protocol::TMTransaction& packet, Application::Sco return; } - if (getApp().getMasterTransaction().fetch(txID, true)) - { - WriteLog (lsDEBUG, Peer) << "Peer " << getDisplayName() << " send old TX " << txID; - applyLoadCharge (LT_InvalidRequest); - return; - } - - WriteLog (lsDEBUG, Peer) << "Got new transaction from peer " << getDisplayName () << " : " << txID; + WriteLog (lsDEBUG, Peer) << "Got transaction from peer " << getDisplayName () << " : " << txID; if (mCluster) flags |= SF_TRUSTED | SF_SIGGOOD; @@ -1943,6 +1936,7 @@ void PeerImp::recvGetLedger (protocol::TMGetLedger& packet, Application::ScopedL uint256 txHash; memcpy (txHash.begin (), packet.ledgerhash ().data (), 32); map = getApp().getOPs ().getTXMap (txHash); + masterLockHolder.unlock(); if (!map) { @@ -1982,6 +1976,12 @@ void PeerImp::recvGetLedger (protocol::TMGetLedger& packet, Application::ScopedL } else { + if (getApp().getFeeTrack().isLoadedLocal() && !mCluster) + { + WriteLog (lsDEBUG, Peer) << "Too busy to fetch ledger data"; + return; + } + // Figure out what ledger they want WriteLog (lsTRACE, Peer) << "Received request for ledger data " << getIP (); Ledger::pointer ledger; @@ -2280,7 +2280,7 @@ void PeerImp::recvLedger (const boost::shared_ptr& packe } bool PeerImp::hasLedger (uint256 const& hash, uint32 seq) const -{ +{ // FIXME: mRecentLedgers needs some kind of synchronization if ((seq != 0) && (seq >= mMinLedger) && (seq <= mMaxLedger)) return true; diff --git a/modules/ripple_app/rpc/RPCHandler.cpp b/modules/ripple_app/rpc/RPCHandler.cpp index 206d4fd36b..084ea36a18 100644 --- a/modules/ripple_app/rpc/RPCHandler.cpp +++ b/modules/ripple_app/rpc/RPCHandler.cpp @@ -159,7 +159,7 @@ Json::Value RPCHandler::transactionSign (Json::Value params, bool bSubmit, bool Pathfinder pf (cache, raSrcAddressID, dstAccountID, saSendMax.getCurrency (), saSendMax.getIssuer (), saSend, bValid); - if (!bValid || !pf.findPaths (getConfig ().PATH_SEARCH_SIZE, 3, spsPaths)) + if (!bValid || !pf.findPaths (getConfig ().PATH_SEARCH_OLD, 5, spsPaths)) { WriteLog (lsDEBUG, RPCHandler) << "transactionSign: build_path: No paths found."; @@ -1513,7 +1513,10 @@ Json::Value RPCHandler::doRipplePathFind (Json::Value params, LoadType* loadType bool bValid; Pathfinder pf (cache, raSrc, raDst, uSrcCurrencyID, uSrcIssuerID, saDstAmount, bValid); - if (!bValid || !pf.findPaths (getConfig ().PATH_SEARCH_SIZE, 3, spsComputed)) + int level = getConfig().PATH_SEARCH_OLD; + if ((getConfig().PATH_SEARCH_MAX > level) && getApp().getFeeTrack().isLoadedLocal()) + ++level; + if (!bValid || !pf.findPaths (level, 4, spsComputed)) { WriteLog (lsWARNING, RPCHandler) << "ripple_path_find: No paths found."; } diff --git a/modules/ripple_core/functional/ripple_Config.cpp b/modules/ripple_core/functional/ripple_Config.cpp index 9797aa37df..4305547162 100644 --- a/modules/ripple_core/functional/ripple_Config.cpp +++ b/modules/ripple_core/functional/ripple_Config.cpp @@ -72,7 +72,11 @@ Config::Config () LEDGER_HISTORY = 256; - PATH_SEARCH_SIZE = DEFAULT_PATH_SEARCH_SIZE; + PATH_SEARCH_OLD = DEFAULT_PATH_SEARCH_OLD; + PATH_SEARCH = DEFAULT_PATH_SEARCH; + PATH_SEARCH_FAST = DEFAULT_PATH_SEARCH_FAST; + PATH_SEARCH_MAX = DEFAULT_PATH_SEARCH_MAX; + ACCOUNT_PROBE_MAX = 10; VALIDATORS_SITE = DEFAULT_VALIDATORS_SITE; @@ -502,8 +506,14 @@ void Config::load () LEDGER_HISTORY = lexicalCastThrow (strTemp); } - if (SectionSingleB (secConfig, SECTION_PATH_SEARCH_SIZE, strTemp)) - PATH_SEARCH_SIZE = lexicalCastThrow (strTemp); + if (SectionSingleB (secConfig, SECTION_PATH_SEARCH_OLD, strTemp)) + PATH_SEARCH_OLD = lexicalCastThrow (strTemp); + if (SectionSingleB (secConfig, SECTION_PATH_SEARCH, strTemp)) + PATH_SEARCH = lexicalCastThrow (strTemp); + if (SectionSingleB (secConfig, SECTION_PATH_SEARCH_FAST, strTemp)) + PATH_SEARCH_FAST = lexicalCastThrow (strTemp); + if (SectionSingleB (secConfig, SECTION_PATH_SEARCH_MAX, strTemp)) + PATH_SEARCH_MAX = lexicalCastThrow (strTemp); if (SectionSingleB (secConfig, SECTION_ACCOUNT_PROBE_MAX, strTemp)) ACCOUNT_PROBE_MAX = lexicalCastThrow (strTemp); diff --git a/modules/ripple_core/functional/ripple_Config.h b/modules/ripple_core/functional/ripple_Config.h index b683e6d9ee..6825e3c942 100644 --- a/modules/ripple_core/functional/ripple_Config.h +++ b/modules/ripple_core/functional/ripple_Config.h @@ -44,8 +44,10 @@ const int SYSTEM_WEBSOCKET_PUBLIC_PORT = 6563; // XXX Going away. // Might connect with fewer for testing. #define DEFAULT_PEER_CONNECT_LOW_WATER 10 -// Grows exponentially worse. -#define DEFAULT_PATH_SEARCH_SIZE 4 +#define DEFAULT_PATH_SEARCH_OLD 7 +#define DEFAULT_PATH_SEARCH 7 +#define DEFAULT_PATH_SEARCH_FAST 2 +#define DEFAULT_PATH_SEARCH_MAX 10 enum SizedItemName { @@ -291,7 +293,10 @@ public: //---------------------------------------------------------------------------- // Path searching - int PATH_SEARCH_SIZE; + int PATH_SEARCH_OLD; + int PATH_SEARCH; + int PATH_SEARCH_FAST; + int PATH_SEARCH_MAX; // Validation RippleAddress VALIDATION_SEED, VALIDATION_PUB, VALIDATION_PRIV; diff --git a/modules/ripple_core/functional/ripple_ConfigSections.h b/modules/ripple_core/functional/ripple_ConfigSections.h index 1743604042..76b56d8904 100644 --- a/modules/ripple_core/functional/ripple_ConfigSections.h +++ b/modules/ripple_core/functional/ripple_ConfigSections.h @@ -37,7 +37,10 @@ struct ConfigSection #define SECTION_NETWORK_QUORUM "network_quorum" #define SECTION_NODE_SEED "node_seed" #define SECTION_NODE_SIZE "node_size" -#define SECTION_PATH_SEARCH_SIZE "path_search_size" +#define SECTION_PATH_SEARCH_OLD "path_search_old" +#define SECTION_PATH_SEARCH "path_search" +#define SECTION_PATH_SEARCH_FAST "path_search_fast" +#define SECTION_PATH_SEARCH_MAX "path_search_max" #define SECTION_PEER_CONNECT_LOW_WATER "peer_connect_low_water" #define SECTION_PEER_IP "peer_ip" #define SECTION_PEER_PORT "peer_port" diff --git a/modules/ripple_data/protocol/ripple_SerializedTypes.h b/modules/ripple_data/protocol/ripple_SerializedTypes.h index 0f02444c37..4e8ed75517 100644 --- a/modules/ripple_data/protocol/ripple_SerializedTypes.h +++ b/modules/ripple_data/protocol/ripple_SerializedTypes.h @@ -1164,6 +1164,12 @@ public: ; } + STPathElement () + : mType (0) + { + ; + } + int getNodeType () const { return mType; @@ -1193,8 +1199,8 @@ public: bool operator== (const STPathElement& t) const { - return mType == t.mType && mAccountID == t.mAccountID && mCurrencyID == t.mCurrencyID && - mIssuerID == t.mIssuerID; + return ((mType & typeAccount) == (t.mType & typeAccount)) && + (mAccountID == t.mAccountID) && (mCurrencyID == t.mCurrencyID) && (mIssuerID == t.mIssuerID); } private: @@ -1368,6 +1374,15 @@ public: { value.push_back (e); } + void addUniquePath (const STPath& e) + { + BOOST_FOREACH(const STPath& p, value) + { + if (p == e) + return; + } + value.push_back (e); + } bool assembleAdd(STPath const& base, STPathElement const& tail) { // assemble base+tail and add it to the set if it's not a duplicate @@ -1398,6 +1413,15 @@ public: void printDebug (); + STPath& operator[](size_t n) + { + return value[n]; + } + STPath const& operator[](size_t n) const + { + return value[n]; + } + std::vector::iterator begin () { return value.begin (); diff --git a/rippled-example.cfg b/rippled-example.cfg index 265f07ad43..59427e7e91 100644 --- a/rippled-example.cfg +++ b/rippled-example.cfg @@ -443,7 +443,7 @@ # #------------------------------------------------------------------------------- # -# 5. Ripple Protcol +# 5. Ripple Protocol # #------------------ # @@ -554,6 +554,25 @@ # # # +# [path_search] +# When searching for paths, the default search aggressiveness. This can take +# exponentially more resources as the size is increased. +# +# The default is: 7 +# +# [path_search_fast] +# [path_search_max] +# When seaching for paths, the minimum and maximum search aggressiveness. +# +# The default for 'path_search_fast' is 2. The default for 'path_search_max' is 10. +# +# [path_search_old] +# +# For clients that use the legacy path finding interfaces, the search +# agressiveness to use. The default is 7. +# +# +# #------------------------------------------------------------------------------- # # 6. HTTPS Client