diff --git a/src/ripple/app/main/Application.cpp b/src/ripple/app/main/Application.cpp index ccb9749ed2..f791acb679 100644 --- a/src/ripple/app/main/Application.cpp +++ b/src/ripple/app/main/Application.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -652,7 +653,7 @@ public: updateTables (); m_amendmentTable->addInitial(); - Pathfinder::initPathTable (); + initializePathfinding (); m_ledgerMaster->setMinValidations (getConfig ().VALIDATION_QUORUM); diff --git a/src/ripple/app/paths/AccountCurrencies.cpp b/src/ripple/app/paths/AccountCurrencies.cpp new file mode 100644 index 0000000000..139dabf113 --- /dev/null +++ b/src/ripple/app/paths/AccountCurrencies.cpp @@ -0,0 +1,93 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include + +namespace ripple { + +CurrencySet accountSourceCurrencies ( + RippleAddress const& raAccountID, + RippleLineCache::ref lrCache, + bool includeXRP) +{ + CurrencySet currencies; + + // YYY Only bother if they are above reserve + if (includeXRP) + currencies.insert (xrpCurrency()); + + // 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 (); + + // 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; +} + +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; +} + +} // ripple diff --git a/src/ripple/app/paths/AccountCurrencies.h b/src/ripple/app/paths/AccountCurrencies.h new file mode 100644 index 0000000000..627c52adb0 --- /dev/null +++ b/src/ripple/app/paths/AccountCurrencies.h @@ -0,0 +1,37 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLED_RIPPLE_APP_PATHS_ACCOUNTCURRENCIES_H +#define RIPPLED_RIPPLE_APP_PATHS_ACCOUNTCURRENCIES_H + +namespace ripple { + +CurrencySet accountDestCurrencies + (RippleAddress const& raAccountID, + RippleLineCache::ref cache, + bool includeXRP); + +CurrencySet accountSourceCurrencies + (RippleAddress const& raAccountID, + RippleLineCache::ref lrLedger, + bool includeXRP); + +} // ripple + +#endif diff --git a/src/ripple/app/paths/Credit.cpp b/src/ripple/app/paths/Credit.cpp new file mode 100644 index 0000000000..270244aa7d --- /dev/null +++ b/src/ripple/app/paths/Credit.cpp @@ -0,0 +1,73 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include + +namespace ripple { + +STAmount creditLimit ( + LedgerEntrySet& ledger, + Account const& account, + Account const& issuer, + Currency const& currency) +{ + STAmount result ({currency, account}); + + auto sleRippleState = ledger.entryCache (ltRIPPLE_STATE, + Ledger::getRippleStateIndex (account, issuer, currency)); + + if (sleRippleState) + { + result = sleRippleState->getFieldAmount ( + account < issuer ? sfLowLimit : sfHighLimit); + + result.setIssuer (account); + } + + assert (result.getIssuer () == account); + assert (result.getCurrency () == currency); + return result; +} + +STAmount creditBalance ( + LedgerEntrySet& ledger, + Account const& account, + Account const& issuer, + Currency const& currency) +{ + STAmount result ({currency, account}); + + auto sleRippleState = ledger.entryCache (ltRIPPLE_STATE, + Ledger::getRippleStateIndex (account, issuer, currency)); + + if (sleRippleState) + { + result = sleRippleState->getFieldAmount (sfBalance); + if (account < issuer) + result.negate (); + + result.setIssuer (account); + } + + assert (result.getIssuer () == account); + assert (result.getCurrency () == currency); + return result; +} + +} // ripple diff --git a/src/ripple/app/paths/Credit.h b/src/ripple/app/paths/Credit.h new file mode 100644 index 0000000000..65b67f3655 --- /dev/null +++ b/src/ripple/app/paths/Credit.h @@ -0,0 +1,52 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLED_RIPPLE_APP_PATHS_CREDIT_H +#define RIPPLED_RIPPLE_APP_PATHS_CREDIT_H + +namespace ripple { + +/** Calculate the maximum amount of IOUs that an account can hold + @param ledger the ledger to check against. + @param account the account of interest. + @param issuer the issuer of the IOU. + @param currency the IOU to check. + @return The maximum amount that can be held. +*/ +STAmount creditLimit ( + LedgerEntrySet& ledger, + Account const& account, + Account const& issuer, + Currency const& currency); + +/** Returns the amount of IOUs issued by issuer that are held by an account + @param ledger the ledger to check against. + @param account the account of interest. + @param issuer the issuer of the IOU. + @param currency the IOU to check. +*/ +STAmount creditBalance ( + LedgerEntrySet& ledger, + Account const& account, + Account const& issuer, + Currency const& currency); + +} // ripple + +#endif diff --git a/src/ripple/app/paths/FindPaths.cpp b/src/ripple/app/paths/FindPaths.cpp new file mode 100644 index 0000000000..d0d99f421b --- /dev/null +++ b/src/ripple/app/paths/FindPaths.cpp @@ -0,0 +1,58 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include + +namespace ripple { + +bool findPathsForOneIssuer ( + RippleLineCache::ref cache, + Account const& srcAccount, + Account const& dstAccount, + Issue const& srcIssue, + STAmount const& dstAmount, + int searchLevel, + unsigned int const maxPaths, + STPathSet& pathsOut, + STPath& fullLiquidityPath) +{ + Pathfinder pf ( + cache, + srcAccount, + dstAccount, + srcIssue.currency, + srcIssue.account, + dstAmount); + + if (!pf.findPaths (searchLevel)) + return false; + + // Yes, ensurePathsAreComplete is called BEFORE we compute the paths... + pf.ensurePathsAreComplete (pathsOut); + pathsOut = pf.getBestPaths(maxPaths, fullLiquidityPath); + return true; +} + +void initializePathfinding () +{ + Pathfinder::initPathTable (); +} + +} // ripple diff --git a/src/ripple/app/paths/FindPaths.h b/src/ripple/app/paths/FindPaths.h new file mode 100644 index 0000000000..84afaa0619 --- /dev/null +++ b/src/ripple/app/paths/FindPaths.h @@ -0,0 +1,56 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLED_RIPPLE_APP_PATHS_FINDPATHS_H +#define RIPPLED_RIPPLE_APP_PATHS_FINDPATHS_H + +namespace ripple { + +bool findPathsForOneIssuer ( + RippleLineCache::ref cache, + Account const& srcAccount, + Account const& dstAccount, + Issue const& srcIssue, + STAmount const& dstAmount, + + /** searchLevel is the maximum search level allowed in an output path. */ + int searchLevel, + + /** maxPaths is the maximum number of paths that can be returned in + pathsOut. */ + unsigned int const maxPaths, + + /** On input, pathsOut contains any paths you want to ensure are included if + still good. + + On output, pathsOut will have any additional paths found. Only + non-default paths without source or destination will be added. */ + STPathSet& pathsOut, + + /** On input, fullLiquidityPath must be an empty STPath. + + On output, if fullLiquidityPath is non-empty, it contains one extra path + that can move the entire liquidity requested. */ + STPath& fullLiquidityPath); + +void initializePathfinding (); + +} // ripple + +#endif diff --git a/src/ripple/app/paths/PathRequest.cpp b/src/ripple/app/paths/PathRequest.cpp index a8c8c8db0b..4302d77450 100644 --- a/src/ripple/app/paths/PathRequest.cpp +++ b/src/ripple/app/paths/PathRequest.cpp @@ -17,27 +17,31 @@ */ //============================================================================== -#include +#include +#include #include #include +#include #include #include namespace ripple { PathRequest::PathRequest ( -const std::shared_ptr& subscriber, int id, PathRequests& owner, + const std::shared_ptr& subscriber, + int id, + PathRequests& owner, beast::Journal journal) - : m_journal (journal) - , mOwner (owner) - , wpSubscriber (subscriber) - , jvStatus (Json::objectValue) - , bValid (false) - , mLastIndex (0) - , mInProgress (false) - , iLastLevel (0) - , bLastSuccess (false) - , iIdentifier (id) + : m_journal (journal) + , mOwner (owner) + , wpSubscriber (subscriber) + , jvStatus (Json::objectValue) + , bValid (false) + , mLastIndex (0) + , mInProgress (false) + , iLastLevel (0) + , bLastSuccess (false) + , iIdentifier (id) { if (m_journal.debug) m_journal.debug << iIdentifier << " created"; @@ -45,7 +49,8 @@ const std::shared_ptr& subscriber, int id, PathRequests& owner, } static std::string const get_milli_diff ( - boost::posix_time::ptime const& after, boost::posix_time::ptime + boost::posix_time::ptime const& after, + boost::posix_time::ptime const& before) { return beast::lexicalCastThrow ( @@ -174,7 +179,7 @@ bool PathRequest::isValid (RippleLineCache::ref crCache) bool const disallowXRP ( asDst->peekSLE ().getFlags() & lsfDisallowXRP); - auto usDestCurrID = usAccountDestCurrencies ( + auto usDestCurrID = accountDestCurrencies ( raDstAccount, crCache, !disallowXRP); for (auto const& currency : usDestCurrID) @@ -223,11 +228,15 @@ Json::Value PathRequest::doCreate ( { if (bValid) { - m_journal.debug << iIdentifier << " valid: " << raSrcAccount.humanAccountID (); - m_journal.debug << iIdentifier << " Deliver: " << saDstAmount.getFullText (); + m_journal.debug << iIdentifier + << " valid: " << raSrcAccount.humanAccountID (); + m_journal.debug << iIdentifier + << " Deliver: " << saDstAmount.getFullText (); } else + { m_journal.debug << iIdentifier << " invalid"; + } } valid = bValid; @@ -268,10 +277,12 @@ int PathRequest::parseJson (Json::Value const& jvParams, bool complete) if (jvParams.isMember ("destination_amount")) { - if (! amountFromJsonNoThrow (saDstAmount, jvParams["destination_amount"]) || - (saDstAmount.getCurrency ().isZero () && saDstAmount.getIssuer ().isNonZero ()) || - (saDstAmount.getCurrency () == badCurrency()) || - saDstAmount <= zero) + if (! amountFromJsonNoThrow ( + saDstAmount, jvParams["destination_amount"]) || + (saDstAmount.getCurrency ().isZero () && + saDstAmount.getIssuer ().isNonZero ()) || + (saDstAmount.getCurrency () == badCurrency ()) || + saDstAmount <= zero) { jvStatus = rpcError (rpcDST_AMT_MALFORMED); return PFR_PJ_INVALID; @@ -363,16 +374,16 @@ Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast) if (sourceCurrencies.empty ()) { auto usCurrencies = - usAccountSourceCurrencies (raSrcAccount, cache, true); + accountSourceCurrencies (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())); + sourceCurrencies.insert ({c, xrpAccount()}); else - sourceCurrencies.insert (std::make_pair (c, raSrcAccount.getAccountID ())); + sourceCurrencies.insert ({c, raSrcAccount.getAccountID ()}); } } } @@ -390,26 +401,30 @@ Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast) bool loaded = getApp().getFeeTrack().isLoadedLocal(); if (iLevel == 0) - { // first pass + { + // 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 + { + // leaving fast pathfinding iLevel = getConfig().PATH_SEARCH; if (loaded && (iLevel > getConfig().PATH_SEARCH_FAST)) --iLevel; } else if (bLastSuccess) - { // decrement, if possible + { + // decrement, if possible if (iLevel > getConfig().PATH_SEARCH || (loaded && (iLevel > getConfig().PATH_SEARCH_FAST))) --iLevel; } else - { // adjust as needed + { + // adjust as needed if (!loaded && (iLevel < getConfig().PATH_SEARCH_MAX)) ++iLevel; if (loaded && (iLevel > getConfig().PATH_SEARCH_FAST)) @@ -423,7 +438,7 @@ Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast) for (auto const& currIssuer: sourceCurrencies) { { - STAmount test ({currIssuer.first, currIssuer.second}, 1); + STAmount test (currIssuer, 1); if (m_journal.debug) { m_journal.debug @@ -431,26 +446,34 @@ Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast) << " Trying to find paths: " << test.getFullText (); } } - bool valid; STPathSet& spsPaths = mContext[currIssuer]; - Pathfinder pf (cache, raSrcAccount, raDstAccount, - currIssuer.first, currIssuer.second, saDstAmount, valid); + STPath fullLiquidityPath; + auto valid = findPathsForOneIssuer ( + cache, + raSrcAccount.getAccountID(), + raDstAccount.getAccountID(), + currIssuer, + saDstAmount, + iLevel, + 4, // iMaxPaths + spsPaths, + fullLiquidityPath); CondLog (!valid, lsDEBUG, PathRequest) << iIdentifier << " PF request not valid"; - STPath extraPath; - if (valid && pf.findPaths (iLevel, 4, spsPaths, extraPath)) + if (valid) { LedgerEntrySet lesSandbox (cache->getLedger (), tapNONE); - auto& account = currIssuer.second.isNonZero () - ? Account(currIssuer.second) - : isXRP (currIssuer.first) + auto& account = !isXRP (currIssuer.account) + ? currIssuer.account + : isXRP (currIssuer.currency) ? xrpAccount() : raSrcAccount.getAccountID (); - STAmount saMaxAmount ({currIssuer.first, account}, 1); + STAmount saMaxAmount ({currIssuer.currency, account}, 1); saMaxAmount.negate (); - m_journal.debug << iIdentifier << " Paths found, calling rippleCalc"; + m_journal.debug << iIdentifier + << " Paths found, calling rippleCalc"; auto rc = path::RippleCalc::rippleCalculate ( lesSandbox, saMaxAmount, @@ -459,12 +482,12 @@ Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast) raSrcAccount.getAccountID (), spsPaths); - if (!extraPath.empty() && + if (!fullLiquidityPath.empty() && (rc.result () == terNO_LINE || rc.result () == tecPATH_PARTIAL)) { m_journal.debug << iIdentifier << " Trying with an extra path element"; - spsPaths.push_back (extraPath); + spsPaths.push_back (fullLiquidityPath); rc = path::RippleCalc::rippleCalculate (lesSandbox, saMaxAmount, saDstAmount, @@ -484,8 +507,8 @@ Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast) if (rc.result () == tesSUCCESS) { Json::Value jvEntry (Json::objectValue); - jvEntry["source_amount"] = rc.actualAmountIn.getJson (0); - jvEntry["paths_computed"] = spsPaths.getJson (0); + jvEntry["source_amount"] = rc.actualAmountIn.getJson (0); + jvEntry["paths_computed"] = spsPaths.getJson (0); found = true; jvArray.append (jvEntry); } diff --git a/src/ripple/app/paths/PathRequest.h b/src/ripple/app/paths/PathRequest.h index b02e3503bd..9b68199683 100644 --- a/src/ripple/app/paths/PathRequest.h +++ b/src/ripple/app/paths/PathRequest.h @@ -45,13 +45,13 @@ public: typedef const pointer& ref; typedef const wptr& wref; - // TODO(tom): Use Issue instead! - typedef std::pair CurrencyIssuer; - public: // VFALCO TODO Break the cyclic dependency on InfoSub - PathRequest (std::shared_ptr const& subscriber, - int id, PathRequests&, beast::Journal journal); + PathRequest ( + std::shared_ptr const& subscriber, + int id, + PathRequests&, + beast::Journal journal); ~PathRequest (); @@ -97,8 +97,8 @@ private: RippleAddress raDstAccount; STAmount saDstAmount; - std::set sciSourceCurrencies; - std::map mContext; + std::set sciSourceCurrencies; + std::map mContext; bool bValid; diff --git a/src/ripple/app/paths/PathState.cpp b/src/ripple/app/paths/PathState.cpp index 38496052a9..8a94d9e97f 100644 --- a/src/ripple/app/paths/PathState.cpp +++ b/src/ripple/app/paths/PathState.cpp @@ -17,6 +17,8 @@ */ //============================================================================== +#include + namespace ripple { // OPTIMIZE: When calculating path increment, note if increment consumes all @@ -314,13 +316,13 @@ TER PathState::pushNode ( if (resultCode == tesSUCCESS) { - STAmount saOwed = credit_balance (lesEntries, + STAmount saOwed = creditBalance (lesEntries, node.account_, backNode.account_, node.issue_.currency); STAmount saLimit; if (saOwed <= zero) { - saLimit = credit_limit (lesEntries, + saLimit = creditLimit (lesEntries, node.account_, backNode.account_, node.issue_.currency); diff --git a/src/ripple/app/paths/Pathfinder.cpp b/src/ripple/app/paths/Pathfinder.cpp index 7df0ea5922..237b2d3ee2 100644 --- a/src/ripple/app/paths/Pathfinder.cpp +++ b/src/ripple/app/paths/Pathfinder.cpp @@ -18,6 +18,7 @@ //============================================================================== #include +#include #include @@ -43,8 +44,8 @@ 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 + // return list of all orderbooks that want Issuer + // return list of all orderbooks that want this issuer and currency */ /* @@ -55,181 +56,276 @@ Test XRP to USD Test USD to EUR */ -// we sort the options by: +namespace { + +// We sort possible paths by: // cost of path // length of path // width of path -// correct currency at the end +// correct currency at the end. -// quality, length, liquidity, index -typedef std::tuple path_LQ_t; +struct PathRank +{ + std::uint64_t quality; + std::uint64_t length; + STAmount liquidity; + int index; +}; -// Lower numbers have better quality. Sort higher quality first. -static bool bQualityCmp (path_LQ_t const& a, path_LQ_t const& b) +// Compare two PathRanks. A better PathRank is lower, so the best are sorted to +// the beginning. +bool comparePathRank (PathRank const& a, PathRank const& b) { // 1) Higher quality (lower cost) is better - if (std::get<0> (a) != std::get<0> (b)) - return std::get<0> (a) < std::get<0> (b); + if (a.quality != b.quality) + return a.quality < b.quality; // 2) More liquidity (higher volume) is better - if (std::get<2> (a) != std::get<2> (b)) - return std::get<2> (a) > std::get<2> (b); + if (a.liquidity != b.liquidity) + return a.liquidity > b.liquidity; // 3) Shorter paths are better - if (std::get<1> (a) != std::get<1> (b)) - return std::get<1> (a) < std::get<1> (b); + if (a.length != b.length) + return a.length < b.length; // 4) Tie breaker - return std::get<3> (a) > std::get<3> (b); + return a.index > b.index; } -typedef std::pair AccountCandidate; -typedef std::vector AccountCandidates; +struct AccountCandidate +{ + int priority; + Account account; -static bool candCmp ( + static const int highPriority = 10000; +}; + +bool compareAccountCandidate ( std::uint32_t seq, AccountCandidate const& first, AccountCandidate const& second) { - if (first.first < second.first) + if (first.priority < second.priority) return false; - if (first.first > second.first) + if (first.account > second.account) return true; - return (first.first ^ seq) < (second.first ^ seq); + return (first.priority ^ seq) < (second.priority ^ seq); } +typedef std::vector AccountCandidates; + +struct CostedPath +{ + int searchLevel; + Pathfinder::PathType type; +}; + +typedef std::vector CostedPathList; + +typedef std::map PathTable; + +struct PathCost { + int cost; + char const* path; +}; +typedef std::vector PathCostList; + +static PathTable mPathTable; + +std::string pathTypeToString (Pathfinder::PathType const& type) +{ + std::string ret; + + for (auto const& node : type) + { + switch (node) + { + case Pathfinder::nt_SOURCE: + ret.append("s"); + break; + case Pathfinder::nt_ACCOUNTS: + ret.append("a"); + break; + case Pathfinder::nt_BOOKS: + ret.append("b"); + break; + case Pathfinder::nt_XRP_BOOK: + ret.append("x"); + break; + case Pathfinder::nt_DEST_BOOK: + ret.append("f"); + break; + case Pathfinder::nt_DESTINATION: + ret.append("d"); + break; + } + } + + return ret; +} + +} // namespace + Pathfinder::Pathfinder ( RippleLineCache::ref cache, - RippleAddress const& uSrcAccountID, - RippleAddress const& uDstAccountID, - Currency const& uSrcCurrencyID, - Account const& uSrcIssuerID, - STAmount const& saDstAmount, - bool& bValid) - : mSrcAccountID (uSrcAccountID.getAccountID ()), - mDstAccountID (uDstAccountID.getAccountID ()), + Account const& uSrcAccount, + Account const& uDstAccount, + Currency const& uSrcCurrency, + Account const& uSrcIssuer, + STAmount const& saDstAmount) + : mSrcAccount (uSrcAccount), + mDstAccount (uDstAccount), mDstAmount (saDstAmount), - mSrcCurrencyID (uSrcCurrencyID), - mSrcIssuerID (uSrcIssuerID), - mSrcAmount ({uSrcCurrencyID, uSrcIssuerID}, 1u, 0, true), - mLedger (cache->getLedger ()), mRLCache (cache) + mSrcCurrency (uSrcCurrency), + mSrcIssuer (uSrcIssuer), + mSrcAmount ({mSrcCurrency, mSrcIssuer}, 1u, 0, true), + mLedger (cache->getLedger ()), + mRLCache (cache) { - - if ((mSrcAccountID == mDstAccountID && - mSrcCurrencyID == mDstAmount.getCurrency ()) || mDstAmount == zero) - { - // 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() ? Account() : - (bIssuer ? mSrcIssuerID : mSrcAccountID)); } -bool Pathfinder::findPaths ( - int iLevel, const unsigned int iMaxPaths, - STPathSet& pathsOut, STPath& extraPath) +bool Pathfinder::findPaths (int searchLevel) { - // pathsOut contains only non-default paths without source or - // destination. On input, pathsOut contains any paths you want to ensure are - // included if still good. + if (mDstAmount == zero) + { + // No need to send zero money. + WriteLog (lsDEBUG, Pathfinder) << "Destination amount was zero."; + mLedger.reset (); + return false; + // TODO(tom): why do we reset the ledger just in this case and the one + // below - why don't we do it each time we return false? + } + + if (mSrcAccount == mDstAccount && + mSrcCurrency == mDstAmount.getCurrency ()) + { + // No need to send to same account with same currency. + WriteLog (lsDEBUG, Pathfinder) << "Tried to send to same issuer"; + mLedger.reset (); + return false; + } + + m_loadEvent = getApp ().getJobQueue ().getLoadEvent ( + jtPATH_FIND, "FindPath"); + auto currencyIsXRP = isXRP (mSrcCurrency); + auto issuerIsXRP = isXRP (mSrcIssuer); + + bool useIssuerAccount = !currencyIsXRP && !issuerIsXRP; + auto& account = useIssuerAccount ? mSrcIssuer : mSrcAccount; + auto issuer = currencyIsXRP ? Account() : account; + mSource = STPathElement (account, mSrcCurrency, issuer); WriteLog (lsTRACE, Pathfinder) << "findPaths>" - << " mSrcAccountID=" << mSrcAccountID - << " mDstAccountID=" << mDstAccountID + << " mSrcAccount=" << mSrcAccount + << " mDstAccount=" << mDstAccount << " mDstAmount=" << mDstAmount.getFullText () - << " mSrcCurrencyID=" << mSrcCurrencyID - << " mSrcIssuerID=" << mSrcIssuerID; + << " mSrcCurrency=" << mSrcCurrency + << " mSrcIssuer=" << mSrcIssuer; if (!mLedger) { WriteLog (lsDEBUG, Pathfinder) << "findPaths< no ledger"; - return false; } - bool bSrcXrp = mSrcCurrencyID.isZero(); - bool bDstXrp = mDstAmount.getCurrency().isZero(); + bool bSrcXrp = isXRP (mSrcCurrency); + bool bDstXrp = isXRP (mDstAmount.getCurrency()); - auto sleSrc = mLedger->getSLEi(Ledger::getAccountRootIndex(mSrcAccountID)); - if (!sleSrc) + if (!mLedger->getSLEi (Ledger::getAccountRootIndex (mSrcAccount))) + { + // We can't even start without a source account. + WriteLog (lsDEBUG, Pathfinder) << "invalid source account"; return false; + } - auto sleDest = mLedger->getSLEi(Ledger::getAccountRootIndex(mDstAccountID)); - if (!sleDest && (!bDstXrp || (mDstAmount < mLedger->getReserve(0)))) - return false; + if (!mLedger->getSLEi (Ledger::getAccountRootIndex (mDstAccount))) + { + // Can't find the destination account - we must be funding a new + // account. + if (!bDstXrp) + { + WriteLog (lsDEBUG, Pathfinder) + << "New account not being funded in XRP "; + return false; + } + auto reserve = mLedger->getReserve (0); + if (mDstAmount < reserve) + { + WriteLog (lsDEBUG, Pathfinder) + << "New account not getting enough funding: " + << mDstAmount << " < " << reserve; + return false; + } + } + + // Now compute the payment type from the types of the source and destination + // currencies. PaymentType paymentType; if (bSrcXrp && bDstXrp) - { // XRP -> XRP - + { + // XRP -> XRP WriteLog (lsDEBUG, Pathfinder) << "XRP to XRP payment"; paymentType = pt_XRP_to_XRP; - } else if (bSrcXrp) - { // XRP -> non-XRP - + { + // XRP -> non-XRP WriteLog (lsDEBUG, Pathfinder) << "XRP to non-XRP payment"; paymentType = pt_XRP_to_nonXRP; - } else if (bDstXrp) - { // non-XRP -> XRP - + { + // 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 - + else if (mSrcCurrency == 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 - + { + // non-XRP to non-XRP - Different currency WriteLog (lsDEBUG, Pathfinder) << "non-XRP to non-XRP - cross currency"; paymentType = pt_nonXRP_to_nonXRP; - } + // Now iterate over all paths for that paymentType. for (auto const& costedPath : mPathTable[paymentType]) { - if (costedPath.first <= iLevel) - { - getPaths(costedPath.second); - if (mCompletePaths.size () > PATHFINDER_MAX_COMPLETE_PATHS) - break; - } + // Only use paths with at most the current search level. + if (costedPath.searchLevel <= searchLevel) + { + addPathsForType (costedPath.type); + + // TODO(tom): we might be missing other good paths with this + // arbitrary cut off. + if (mCompletePaths.size () > PATHFINDER_MAX_COMPLETE_PATHS) + break; + } } WriteLog (lsDEBUG, Pathfinder) - << mCompletePaths.size() << " complete paths found"; + << mCompletePaths.size () << " complete paths found"; + // Even if we find no paths, default paths may work, and we don't check them + // currently. + return true; +} + +void Pathfinder::ensurePathsAreComplete (STPathSet& pathsOut) +{ + // Add any result paths that aren't in mCompletePaths. + // TODO(tom): this is also quadratic in the size of the paths. + // TODO(tom): how could a path possibly not be in mCompletePaths? for (auto const& path : pathsOut) - { // make sure no paths were lost + { + // make sure no paths were lost bool found = false; if (!path.empty ()) { @@ -241,72 +337,67 @@ bool Pathfinder::findPaths ( break; } } + + // TODO(tom): this asssert never triggers. We should probably + // remove this whole loop, which might be expensive. + assert (found); if (!found) mCompletePaths.push_back (path); } } WriteLog (lsDEBUG, Pathfinder) - << mCompletePaths.size() << " paths to filter"; - - if (mCompletePaths.size() > iMaxPaths) - pathsOut = filterPaths(iMaxPaths, extraPath); - else - pathsOut = mCompletePaths; - - // Even if we find no paths, default paths may work, and we don't check them - // currently. - return true; + << mCompletePaths.size () << " paths to filter"; } -// Check the specified path -// Returning the initial quality and liquidity -TER Pathfinder::checkPath ( - STPath const& path, // The path to check - STAmount const& minDstAmount, // The minimum output this path must - // deliver to be worth keeping - STAmount& amountOut, // The returned liquidity - uint64_t& qualityOut) const // The returned initial quality +TER Pathfinder::getPathLiquidity ( + STPath const& path, // IN: The path to check. + STAmount const& minDstAmount, // IN: The minimum output this path must + // deliver to be worth keeping. + STAmount& amountOut, // OUT: The actual liquidity along the path. + uint64_t& qualityOut) const // OUT: The returned initial quality { STPathSet pathSet; pathSet.push_back (path); - // We only want to look at this path path::RippleCalc::Input rcInput; rcInput.defaultPathsAllowed = false; - LedgerEntrySet scratchPad (mLedger, tapNONE); + LedgerEntrySet lesSandbox (mLedger, tapNONE); try { - // Try to move minimum amount to sanity-check - // path and compute initial quality + // Compute a path that provides at least the minimum liquidity. auto rc = path::RippleCalc::rippleCalculate ( - scratchPad, mSrcAmount, minDstAmount, - mDstAccountID, mSrcAccountID, - pathSet, &rcInput); + lesSandbox, + mSrcAmount, + minDstAmount, + mDstAccount, + mSrcAccount, + pathSet, + &rcInput); - if (rc.result() != tesSUCCESS) - { - // Path has trivial/no liquidity - return rc.result(); - } + // If we can't get even the minimum liquidity requested, we're done. + if (rc.result () != tesSUCCESS) + return rc.result (); - qualityOut = getRate - (rc.actualAmountOut, rc.actualAmountIn); + qualityOut = getRate (rc.actualAmountOut, rc.actualAmountIn); amountOut = rc.actualAmountOut; - // Try to complete as much of the payment - // as possible to assess path liquidity + // Now try to compute the remaining liquidity. rcInput.partialPaymentAllowed = true; rc = path::RippleCalc::rippleCalculate ( - scratchPad, mSrcAmount, mDstAmount - amountOut, - mDstAccountID, mSrcAccountID, pathSet, &rcInput); - if (rc.result() == tesSUCCESS) - { - // Report total liquidity to caller + lesSandbox, + mSrcAmount, + mDstAmount - amountOut, + mDstAccount, + mSrcAccount, + pathSet, + &rcInput); + + // If we found further liquidity, add it into the result. + if (rc.result () == tesSUCCESS) amountOut += rc.actualAmountOut; - } return tesSUCCESS; } @@ -319,14 +410,16 @@ TER Pathfinder::checkPath ( } } -STPathSet Pathfinder::filterPaths(int iMaxPaths, STPath& extraPath) +STPathSet Pathfinder::getBestPaths (int maxPaths, STPath& fullLiquidityPath) { - if (mCompletePaths.size() <= iMaxPaths) + assert (fullLiquidityPath.empty ()); + + if (mCompletePaths.size () <= maxPaths) return mCompletePaths; STAmount remaining = mDstAmount; - // Must subtract liquidity in default path from remaining amount + // Must subtract liquidity in default path from remaining amount. try { LedgerEntrySet lesSandbox (mLedger, tapNONE); @@ -337,8 +430,8 @@ STPathSet Pathfinder::filterPaths(int iMaxPaths, STPath& extraPath) lesSandbox, mSrcAmount, mDstAmount, - mDstAccountID, - mSrcAccountID, + mDstAccount, + mSrcAccount, STPathSet(), &rcInput); @@ -359,21 +452,20 @@ STPathSet Pathfinder::filterPaths(int iMaxPaths, STPath& extraPath) WriteLog (lsDEBUG, Pathfinder) << "Default path causes exception"; } - std::vector vMap; + // Ignore paths that move only very small amounts. + // TODO(tom): the logic of "very small" is pretty arbitrary here. + auto saMinDstAmount = divide ( + mDstAmount, STAmount (maxPaths + 2), mDstAmount); - // Ignore paths that move only very small amounts - auto saMinDstAmount = divide( - mDstAmount, STAmount(iMaxPaths + 2), mDstAmount); - - // Build map of quality to entry. - for (int i = 0; i < mCompletePaths.size(); ++i) + // Get the PathRank for each path. + std::vector pathRanks; + for (int i = 0; i < mCompletePaths.size (); ++i) { auto const& currentPath = mCompletePaths[i]; - - STAmount actualOut; + STAmount liquidity; uint64_t uQuality; - auto const resultCode = checkPath - (currentPath, saMinDstAmount, actualOut, uQuality); + auto const resultCode = getPathLiquidity ( + currentPath, saMinDstAmount, liquidity, uQuality); if (resultCode != tesSUCCESS) { @@ -387,54 +479,59 @@ STPathSet Pathfinder::filterPaths(int iMaxPaths, STPath& extraPath) "findPaths: quality: " << uQuality << ": " << currentPath.getJson (0); - vMap.push_back (path_LQ_t ( - uQuality, currentPath.size (), actualOut, i)); + pathRanks.push_back ({uQuality, currentPath.size (), liquidity, i}); } } - STPathSet spsDst; - - if (vMap.size()) + STPathSet bestPaths; + if (pathRanks.size ()) { - // Lower is better and should be first. - std::sort (vMap.begin (), vMap.end (), bQualityCmp); + std::sort (pathRanks.begin (), pathRanks.end (), comparePathRank); - for (int i = 0, iPathsLeft = iMaxPaths; - (iPathsLeft > 0 || extraPath.empty()) && i < vMap.size (); ++i) + // The best PathRanks are now at the start. Pull off enough of them to + // fill bestPaths, then look through the rest for the best individual + // path that can satisfy the entire liquidity - if one exists. + for (auto& pathRank: pathRanks) { - path_LQ_t& lqt = vMap[i]; + auto iPathsLeft = maxPaths - bestPaths.size (); + if (!(iPathsLeft > 0 || fullLiquidityPath.empty ())) + break; if (iPathsLeft > 1 || - (iPathsLeft > 0 && std::get<2> (lqt) >= remaining)) + (iPathsLeft > 0 && pathRank.liquidity >= remaining)) { // last path must fill --iPathsLeft; - remaining -= std::get<2> (lqt); - spsDst.push_back (mCompletePaths[std::get<3> (lqt)]); + remaining -= pathRank.liquidity; + bestPaths.push_back (mCompletePaths[pathRank.index]); } - else if (iPathsLeft == 0 && std::get<2>(lqt) >= mDstAmount && - extraPath.empty()) + else if (iPathsLeft == 0 && + pathRank.liquidity >= mDstAmount && + fullLiquidityPath.empty ()) { - // found an extra path that can move the whole amount - extraPath = mCompletePaths[std::get<3>(lqt)]; + // We found an extra path that can move the whole amount. + fullLiquidityPath = mCompletePaths[pathRank.index]; WriteLog (lsDEBUG, Pathfinder) << - "Found extra full path: " << extraPath.getJson(0); + "Found extra full path: " << fullLiquidityPath.getJson (0); } else + { WriteLog (lsDEBUG, Pathfinder) << "Skipping a non-filling path: " << - mCompletePaths[std::get<3> (lqt)].getJson (0); + mCompletePaths[pathRank.index].getJson (0); + } } if (remaining > zero) { + assert (fullLiquidityPath.empty ()); WriteLog (lsINFO, Pathfinder) << "Paths could not send " << remaining << " of " << mDstAmount; } else { WriteLog (lsDEBUG, Pathfinder) << - "findPaths: RESULTS: " << spsDst.getJson (0); + "findPaths: RESULTS: " << bestPaths.getJson (0); } } else @@ -443,82 +540,24 @@ STPathSet Pathfinder::filterPaths(int iMaxPaths, STPath& extraPath) "findPaths: RESULTS: non-defaults filtered away"; } - return spsDst; + return bestPaths; } -CurrencySet usAccountSourceCurrencies ( - RippleAddress const& raAccountID, RippleLineCache::ref lrCache, - bool includeXRP) +bool Pathfinder::issueMatchesOrigin (Issue const& issue) { - CurrencySet usCurrencies; - - // YYY Only bother if they are above reserve - if (includeXRP) - usCurrencies.insert (xrpCurrency()); - - // List of ripple lines. - auto& rippleLines (lrCache->getRippleLines (raAccountID.getAccountID ())); - - for (auto const& item : rippleLines) - { - auto rspEntry = (RippleState*) item.get (); - auto& saBalance = rspEntry->getBalance (); - - // Filter out non - if (saBalance > zero - // Have IOUs to send. - || (rspEntry->getLimitPeer () - // Peer extends credit. - && ((-saBalance) < rspEntry->getLimitPeer ()))) // Credit left. - { - usCurrencies.insert (saBalance.getCurrency ()); - } - } - - usCurrencies.erase (badCurrency()); - return usCurrencies; -} - -CurrencySet usAccountDestCurrencies ( - RippleAddress const& raAccountID, - RippleLineCache::ref lrCache, - bool includeXRP) -{ - CurrencySet usCurrencies; - - if (includeXRP) - usCurrencies.insert (xrpCurrency()); - // Even if account doesn't exist - - // List of ripple lines. - auto& rippleLines (lrCache->getRippleLines (raAccountID.getAccountID ())); - - for (auto const& item : rippleLines) - { - RippleState* rspEntry = (RippleState*) item.get (); - STAmount const& saBalance = rspEntry->getBalance (); - - if (saBalance < rspEntry->getLimit ()) // Can take more - usCurrencies.insert (saBalance.getCurrency ()); - } - - usCurrencies.erase (badCurrency()); - return usCurrencies; -} - -bool Pathfinder::matchesOrigin (Issue const& issue) -{ - return issue.currency == mSrcCurrencyID && - (isXRP (issue) || - issue.account == mSrcIssuerID || - issue.account == mSrcAccountID); + return issue.currency == mSrcCurrency && + (isXRP (issue.currency) || + issue.account == mSrcIssuer || + issue.account == mSrcAccount); } int Pathfinder::getPathsOut ( - Currency const& currencyID, Account const& accountID, - bool isDstCurrency, Account const& dstAccount) + Currency const& currency, + Account const& account, + bool isDstCurrency, + Account const& dstAccount) { - Issue const issue (currencyID, accountID); + Issue const issue (currency, account); auto it = mPathsOutCountMap.emplace (issue, 0); @@ -526,12 +565,13 @@ int Pathfinder::getPathsOut ( if (!it.second) return it.first->second; - auto sleAccount = mLedger->getSLEi (Ledger::getAccountRootIndex (accountID)); + auto sleAccount + = mLedger->getSLEi (Ledger::getAccountRootIndex (account)); if (!sleAccount) return 0; - int aFlags = sleAccount->getFieldU32(sfFlags); + int aFlags = sleAccount->getFieldU32 (sfFlags); bool const bAuthRequired = (aFlags & lsfRequireAuth) != 0; bool const bFrozen = ((aFlags & lsfGlobalFreeze) != 0) && mLedger->enforceFreeze (); @@ -540,13 +580,13 @@ int Pathfinder::getPathsOut ( if (!bFrozen) { - count = getApp().getOrderBookDB().getBookSize(issue); + count = getApp ().getOrderBookDB ().getBookSize (issue); - for (auto const& item : mRLCache->getRippleLines (accountID)) + for (auto const& item : mRLCache->getRippleLines (account)) { RippleState* rspEntry = (RippleState*) item.get (); - if (currencyID != rspEntry->getLimit ().getCurrency ()) + if (currency != rspEntry->getLimit ().getCurrency ()) { } else if (rspEntry->getBalance () <= zero && @@ -555,8 +595,11 @@ int Pathfinder::getPathsOut ( || (bAuthRequired && !rspEntry->getAuth ()))) { } - else if (isDstCurrency && (dstAccount == rspEntry->getAccountIDPeer ())) + else if (isDstCurrency && + dstAccount == rspEntry->getAccountIDPeer ()) + { count += 10000; // count a path to the destination extra + } else if (rspEntry->getNoRipplePeer ()) { // This probably isn't a useful path out @@ -566,14 +609,16 @@ int Pathfinder::getPathsOut ( // Not a useful path out } else + { ++count; + } } } it.first->second = count; return count; } -void Pathfinder::addLink( +void Pathfinder::addLinks ( STPathSet const& currentPaths, // The paths to build from STPathSet& incompletePaths, // The set of partial paths we add to int addFlags) @@ -582,241 +627,259 @@ void Pathfinder::addLink( << "addLink< on " << currentPaths.size() << " source(s), flags=" << addFlags; for (auto const& path: currentPaths) - addLink(path, incompletePaths, addFlags); + addLink (path, incompletePaths, addFlags); } -STPathSet& Pathfinder::getPaths(PathType_t const& type, bool addComplete) +STPathSet& Pathfinder::addPathsForType (PathType const& pathType) { - auto it = mPaths.find(type); - - // We already have these paths - if (it != mPaths.end()) + // See if the set of paths for this type already exists. + auto it = mPaths.find (pathType); + if (it != mPaths.end ()) return it->second; - // The type is empty - if (type.empty()) - return mPaths[type]; + // Otherwise, if the type has no nodes, return the empty path. + if (pathType.empty ()) + return mPaths[pathType]; - NodeType toAdd = type.back(); - PathType_t pathType(type); - pathType.pop_back(); + // Otherwise, get the paths for the parent PathType by calling + // addPathsForType recursively. + PathType parentPathType = pathType; + parentPathType.pop_back (); - STPathSet pathsIn = getPaths(pathType, false); - STPathSet& pathsOut = mPaths[type]; + STPathSet const& parentPaths = addPathsForType (parentPathType); + STPathSet& pathsOut = mPaths[pathType]; WriteLog (lsDEBUG, Pathfinder) << "getPaths< adding onto '" - << pathTypeToString(pathType) << "' to get '" - << pathTypeToString(type) << "'"; + << pathTypeToString (parentPathType) << "' to get '" + << pathTypeToString (pathType) << "'"; - int cp = mCompletePaths.size(); + int initialSize = mCompletePaths.size (); - switch (toAdd) + // Add the last NodeType to the lists. + auto nodeType = pathType.back (); + switch (nodeType) { case nt_SOURCE: - // source is an empty path - assert(pathsOut.empty()); - pathsOut.push_back (STPath()); + // Source must always be at the start, so pathsOut has to be empty. + assert (pathsOut.empty ()); + pathsOut.push_back (STPath ()); break; case nt_ACCOUNTS: - addLink(pathsIn, pathsOut, afADD_ACCOUNTS); + addLinks (parentPaths, pathsOut, afADD_ACCOUNTS); break; case nt_BOOKS: - addLink(pathsIn, pathsOut, afADD_BOOKS); + addLinks (parentPaths, pathsOut, afADD_BOOKS); break; case nt_XRP_BOOK: - addLink(pathsIn, pathsOut, afADD_BOOKS | afOB_XRP); + addLinks (parentPaths, pathsOut, afADD_BOOKS | afOB_XRP); break; case nt_DEST_BOOK: - addLink(pathsIn, pathsOut, afADD_BOOKS | afOB_LAST); + addLinks (parentPaths, 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); + // TODO(tom): what does this even mean? Should it be a JIRA? + addLinks (parentPaths, pathsOut, afADD_ACCOUNTS | afAC_LAST); break; } - CondLog (mCompletePaths.size() != cp, lsDEBUG, Pathfinder) - << (mCompletePaths.size() - cp) + CondLog (mCompletePaths.size () != initialSize, lsDEBUG, Pathfinder) + << (mCompletePaths.size () - initialSize) << " complete paths added"; WriteLog (lsDEBUG, Pathfinder) - << "getPaths> " << pathsOut.size() << " partial paths found"; + << "getPaths> " << pathsOut.size () << " partial paths found"; return pathsOut; } bool Pathfinder::isNoRipple ( - Account const& setByID, Account const& setOnID, Currency const& currencyID) + Account const& fromAccount, + Account const& toAccount, + Currency const& currency) { SLE::pointer sleRipple = mLedger->getSLEi ( - Ledger::getRippleStateIndex (setByID, setOnID, currencyID)); + Ledger::getRippleStateIndex (toAccount, fromAccount, currency)); - auto const flag ((setByID > setOnID) ? lsfHighNoRipple : lsfLowNoRipple); + auto const flag ((toAccount > fromAccount) + ? lsfHighNoRipple : lsfLowNoRipple); return sleRipple && (sleRipple->getFieldU32 (sfFlags) & flag); } -// Does this path end on an account-to-account link whose last account -// has set no ripple on the link? +// Does this path end on an account-to-account link whose last account has +// set "no ripple" on the link? bool Pathfinder::isNoRippleOut (STPath const& currentPath) { - // Must have at least one link - if (currentPath.size() == 0) + // Must have at least one link. + if (currentPath.empty ()) return false; - // Last link must be an account - STPathElement const& endElement = *(currentPath.end() - 1); - if (!(endElement.getNodeType() & STPathElement::typeAccount)) + // Last link must be an account. + STPathElement const& endElement = currentPath.back (); + if (!(endElement.getNodeType () & STPathElement::typeAccount)) return false; // What account are we leaving? - auto const& fromAccount = (currentPath.size() == 1) - ? mSrcAccountID - : (currentPath.end() - 2)->getAccountID (); - - return isNoRipple ( - endElement.getAccountID (), fromAccount, endElement.getCurrency ()); + // TODO(tom): clarify what's going on here when we only have one item in the + // path. + // TODO(tom): why aren't we checking that the previous node is also an + // account? + auto const& fromAccount = (currentPath.size () == 1) + ? mSrcAccount + : (currentPath.end () - 2)->getAccountID (); + auto const& toAccount = endElement.getAccountID (); + return isNoRipple (fromAccount, toAccount, endElement.getCurrency ()); } -void Pathfinder::addLink( +void addUniquePath (STPathSet& pathSet, STPath const& path) +{ + // TODO(tom): building an STPathSet this way is quadratic in the size + // of the STPathSet! + for (auto const& p : pathSet) + { + if (p == path) + return; + } + pathSet.push_back (path); +} + +void Pathfinder::addLink ( const STPath& currentPath, // The path to build from STPathSet& incompletePaths, // The set of partial paths we add to int addFlags) { - auto const& pathEnd = currentPath.empty() - ? mSource - : currentPath.back (); - auto const& uEndCurrency = pathEnd.getCurrency (); - auto const& uEndIssuer = pathEnd.getIssuerID (); - auto const& uEndAccount = pathEnd.getAccountID (); - bool const bOnXRP = uEndCurrency.isZero(); + auto const& pathEnd = currentPath.empty() ? mSource : currentPath.back (); + auto const& uEndCurrency = pathEnd.getCurrency (); + auto const& uEndIssuer = pathEnd.getIssuerID (); + auto const& uEndAccount = pathEnd.getAccountID (); + bool const bOnXRP = uEndCurrency.isZero (); WriteLog (lsTRACE, Pathfinder) << "addLink< flags=" << addFlags << " onXRP=" << bOnXRP; - WriteLog (lsTRACE, Pathfinder) << currentPath.getJson(0); - - auto add_unique_path = [](STPathSet& path_set, STPath const& path) - { - for (auto const& p : path_set) - { - if (p == path) - return; - } - path_set.push_back (path); - }; + WriteLog (lsTRACE, Pathfinder) << currentPath.getJson (0); if (addFlags & afADD_ACCOUNTS) - { // add accounts + { + // add accounts if (bOnXRP) { - if (mDstAmount.isNative() && !currentPath.empty()) + if (mDstAmount.isNative () && !currentPath.empty ()) { // non-default path to XRP destination WriteLog (lsTRACE, Pathfinder) << "complete path found ax: " << currentPath.getJson(0); - add_unique_path (mCompletePaths, currentPath); + addUniquePath (mCompletePaths, currentPath); } } else - { // search for accounts to add + { + // search for accounts to add auto sleEnd = mLedger->getSLEi( - Ledger::getAccountRootIndex(uEndAccount)); + Ledger::getAccountRootIndex (uEndAccount)); if (sleEnd) { bool const bRequireAuth ( - sleEnd->getFieldU32(sfFlags) & lsfRequireAuth); + sleEnd->getFieldU32 (sfFlags) & lsfRequireAuth); bool const bIsEndCurrency ( - uEndCurrency == mDstAmount.getCurrency()); + uEndCurrency == mDstAmount.getCurrency ()); bool const bIsNoRippleOut ( isNoRippleOut (currentPath)); bool const bDestOnly ( addFlags & afAC_LAST); - auto& rippleLines (mRLCache->getRippleLines(uEndAccount)); + auto& rippleLines (mRLCache->getRippleLines (uEndAccount)); AccountCandidates candidates; - candidates.reserve(rippleLines.size()); + candidates.reserve (rippleLines.size ()); - for(auto const& item : rippleLines) + for (auto const& item : rippleLines) { - auto* rs = dynamic_cast (item.get()); + auto* rs = dynamic_cast (item.get ()); if (!rs) { WriteLog (lsERROR, Pathfinder) << "Couldn't decipher RippleState"; continue; } - auto const& acctID = rs->getAccountIDPeer(); - bool const bToDestination = acctID == mDstAccountID; + auto const& acct = rs->getAccountIDPeer (); + bool const bToDestination = acct == mDstAccount; if (bDestOnly && !bToDestination) { continue; } - if ((uEndCurrency == rs->getLimit().getCurrency()) && - !currentPath.hasSeen(acctID, uEndCurrency, acctID)) + if ((uEndCurrency == rs->getLimit ().getCurrency ()) && + !currentPath.hasSeen (acct, uEndCurrency, acct)) { // path is for correct currency and has not been seen - if (rs->getBalance() <= zero - && (!rs->getLimitPeer() - || -rs->getBalance() >= rs->getLimitPeer() - || (bRequireAuth && !rs->getAuth()))) + if (rs->getBalance () <= zero + && (!rs->getLimitPeer () + || -rs->getBalance () >= rs->getLimitPeer () + || (bRequireAuth && !rs->getAuth ()))) { // path has no credit } - else if (bIsNoRippleOut && rs->getNoRipple()) + else if (bIsNoRippleOut && rs->getNoRipple ()) { // Can't leave on this path } else if (bToDestination) { // destination is always worth trying - if (uEndCurrency == mDstAmount.getCurrency()) + if (uEndCurrency == mDstAmount.getCurrency ()) { // this is a complete path - if (!currentPath.empty()) + if (!currentPath.empty ()) { WriteLog (lsTRACE, Pathfinder) << "complete path found ae: " - << currentPath.getJson(0); - add_unique_path (mCompletePaths, currentPath); + << currentPath.getJson (0); + addUniquePath + (mCompletePaths, currentPath); } } else if (!bDestOnly) { // this is a high-priority candidate - candidates.push_back(std::make_pair(100000, acctID)); + candidates.push_back ( + {AccountCandidate::highPriority, acct}); } } - else if (acctID == mSrcAccountID) + else if (acct == mSrcAccount) { // going back to the source is bad } else { // save this candidate - int out = getPathsOut(uEndCurrency, acctID, bIsEndCurrency, mDstAccountID); + int out = getPathsOut ( + uEndCurrency, + acct, + bIsEndCurrency, + mDstAccount); if (out) - candidates.push_back(std::make_pair(out, acctID)); + candidates.push_back ({out, acct}); } } } if (!candidates.empty()) { - std::sort (candidates.begin(), candidates.end(), - std::bind(candCmp, mLedger->getLedgerSeq(), + std::sort (candidates.begin (), candidates.end (), + std::bind(compareAccountCandidate, + mLedger->getLedgerSeq (), std::placeholders::_1, std::placeholders::_2)); - int count = candidates.size(); + int count = candidates.size (); // allow more paths from source - if ((count > 10) && (uEndAccount != mSrcAccountID)) + if ((count > 10) && (uEndAccount != mSrcAccount)) count = 10; else if (count > 50) count = 50; @@ -825,11 +888,12 @@ void Pathfinder::addLink( while (count-- != 0) { // Add accounts to incompletePaths - incompletePaths.assembleAdd( - currentPath, - STPathElement(STPathElement::typeAccount, - it->second, uEndCurrency, - it->second)); + STPathElement pathElement ( + STPathElement::typeAccount, + it->account, + uEndCurrency, + it->account); + incompletePaths.assembleAdd (currentPath, pathElement); ++it; } } @@ -837,28 +901,32 @@ void Pathfinder::addLink( } else { - WriteLog(lsWARNING, Pathfinder) + WriteLog (lsWARNING, Pathfinder) << "Path ends on non-existent issuer"; } } } if (addFlags & afADD_BOOKS) - { // add order books + { + // add order books if (addFlags & afOB_XRP) - { // to XRP only - if (!bOnXRP && getApp().getOrderBookDB().isBookToXRP ( + { + // to XRP only + if (!bOnXRP && getApp ().getOrderBookDB ().isBookToXRP ( {uEndCurrency, uEndIssuer})) { STPathElement pathElement( STPathElement::typeCurrency, - xrpAccount(), xrpCurrency(), xrpAccount()); - incompletePaths.assembleAdd(currentPath, pathElement); + xrpAccount (), + xrpCurrency (), + xrpAccount ()); + incompletePaths.assembleAdd (currentPath, pathElement); } } else { bool bDestOnly = (addFlags & afOB_LAST) != 0; - auto books = getApp().getOrderBookDB().getBooksByTakerPays( + auto books = getApp ().getOrderBookDB ().getBooksByTakerPays( {uEndCurrency, uEndIssuer}); WriteLog (lsTRACE, Pathfinder) << books.size() << " books found from this currency/issuer"; @@ -867,59 +935,67 @@ void Pathfinder::addLink( { if (!currentPath.hasSeen ( xrpAccount(), - book->getCurrencyOut(), - book->getIssuerOut()) && - !matchesOrigin (book->book().out) && + book->getCurrencyOut (), + book->getIssuerOut ()) && + !issueMatchesOrigin (book->book ().out) && (!bDestOnly || - (book->getCurrencyOut() == mDstAmount.getCurrency()))) + (book->getCurrencyOut () == mDstAmount.getCurrency ()))) { - STPath newPath(currentPath); + STPath newPath (currentPath); if (book->getCurrencyOut().isZero()) { // to XRP // add the order book itself - newPath.emplace_back (STPathElement::typeCurrency, - xrpAccount(), xrpCurrency(), xrpAccount()); + newPath.emplace_back ( + STPathElement::typeCurrency, + xrpAccount (), + xrpCurrency (), + xrpAccount ()); - if (mDstAmount.getCurrency().isZero()) + if (mDstAmount.getCurrency ().isZero ()) { // destination is XRP, add account and path is // complete WriteLog (lsTRACE, Pathfinder) << "complete path found bx: " << currentPath.getJson(0); - add_unique_path (mCompletePaths, newPath); + addUniquePath (mCompletePaths, newPath); } else incompletePaths.push_back (newPath); } else if (!currentPath.hasSeen( - book->getIssuerOut(), - book->getCurrencyOut(), - book->getIssuerOut())) + book->getIssuerOut (), + book->getCurrencyOut (), + book->getIssuerOut ())) { // Don't want the book if we've already seen the issuer // add the order book itself - newPath.emplace_back( - STPathElement::typeCurrency | STPathElement::typeIssuer, - xrpAccount(), book->getCurrencyOut(), - book->getIssuerOut()); + newPath.emplace_back ( + STPathElement::typeCurrency | + STPathElement::typeIssuer, + xrpAccount (), + book->getCurrencyOut (), + book->getIssuerOut ()); - if (book->getIssuerOut() == mDstAccountID && - book->getCurrencyOut() == mDstAmount.getCurrency()) - { // with the destination account, this path is complete + if (book->getIssuerOut () == mDstAccount && + book->getCurrencyOut () == mDstAmount.getCurrency()) + { + // with the destination account, this path is + // complete WriteLog (lsTRACE, Pathfinder) << "complete path found ba: " << currentPath.getJson(0); - add_unique_path (mCompletePaths, newPath); + addUniquePath (mCompletePaths, newPath); } else - { // add issuer's account, path still incomplete + { + // add issuer's account, path still incomplete incompletePaths.assembleAdd(newPath, - STPathElement(STPathElement::typeAccount, - book->getIssuerOut(), - book->getCurrencyOut(), - book->getIssuerOut())); + STPathElement (STPathElement::typeAccount, + book->getIssuerOut (), + book->getCurrencyOut (), + book->getIssuerOut ())); } } @@ -929,40 +1005,40 @@ void Pathfinder::addLink( } } -Pathfinder::PathTable Pathfinder::mPathTable; +namespace { -Pathfinder::PathType_t Pathfinder::makePath(char const *string) +Pathfinder::PathType makePath (char const *string) { - PathType_t ret; + Pathfinder::PathType ret; while (true) { switch (*string++) { case 's': // source - ret.push_back(nt_SOURCE); + ret.push_back (Pathfinder::nt_SOURCE); break; case 'a': // accounts - ret.push_back(nt_ACCOUNTS); + ret.push_back (Pathfinder::nt_ACCOUNTS); break; case 'b': // books - ret.push_back(nt_BOOKS); + ret.push_back (Pathfinder::nt_BOOKS); break; case 'x': // xrp book - ret.push_back(nt_XRP_BOOK); + ret.push_back (Pathfinder::nt_XRP_BOOK); break; case 'f': // book to final currency - ret.push_back(nt_DEST_BOOK); + ret.push_back (Pathfinder::nt_DEST_BOOK); break; case 'd': // Destination (with account, if required and not already // present). - ret.push_back(nt_DESTINATION); + ret.push_back (Pathfinder::nt_DESTINATION); break; case 0: @@ -971,46 +1047,17 @@ Pathfinder::PathType_t Pathfinder::makePath(char const *string) } } -std::string Pathfinder::pathTypeToString(PathType_t const& type) -{ - std::string ret; - - for (auto 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; -} - -void Pathfinder::fillPaths(PaymentType type, PathCostList const& costs) +void fillPaths (Pathfinder::PaymentType type, PathCostList const& costs) { auto& list = mPathTable[type]; assert (list.empty()); for (auto& cost: costs) - list.push_back ({cost.first, makePath(cost.second)}); + list.push_back ({cost.cost, makePath (cost.path)}); } +} // namespace + + // Costs: // 0 = minimum to make some payments possible // 1 = include trivial paths to make common cases work @@ -1018,7 +1065,7 @@ void Pathfinder::fillPaths(PaymentType type, PathCostList const& costs) // 7 = normal slow search level // 10 = most agressive -void Pathfinder::initPathTable() +void Pathfinder::initPathTable () { // CAUTION: Do not include rules that build default paths fillPaths( @@ -1081,51 +1128,4 @@ void Pathfinder::initPathTable() }); } -STAmount -credit_limit ( - LedgerEntrySet& ledger, Account const& account, - Account const& issuer, Currency const& currency) -{ - STAmount saLimit ({currency, account}); - - auto sleRippleState = ledger.entryCache (ltRIPPLE_STATE, - Ledger::getRippleStateIndex (account, issuer, currency)); - - if (sleRippleState) - { - saLimit = sleRippleState->getFieldAmount ( - account < issuer ? sfLowLimit : sfHighLimit); - saLimit.setIssuer (account); - } - - assert (saLimit.getIssuer () == account); - assert (saLimit.getCurrency () == currency); - return saLimit; -} - -STAmount -credit_balance ( - LedgerEntrySet& ledger, Account const& account, - Account const& issuer, Currency const& currency) -{ - STAmount saBalance ({currency, account}); - - auto sleRippleState = ledger.entryCache (ltRIPPLE_STATE, - Ledger::getRippleStateIndex (account, issuer, currency)); - - if (sleRippleState) - { - saBalance = sleRippleState->getFieldAmount (sfBalance); - - if (account < issuer) - saBalance.negate (); - - saBalance.setIssuer (account); - } - - assert (saBalance.getIssuer () == account); - assert (saBalance.getCurrency () == currency); - return saBalance; -} - } // ripple diff --git a/src/ripple/app/paths/Pathfinder.h b/src/ripple/app/paths/Pathfinder.h index da90320fea..f4c821c113 100644 --- a/src/ripple/app/paths/Pathfinder.h +++ b/src/ripple/app/paths/Pathfinder.h @@ -33,112 +33,126 @@ class Pathfinder public: Pathfinder ( RippleLineCache::ref cache, - RippleAddress const& srcAccountID, - RippleAddress const& dstAccountID, - Currency const& srcCurrencyID, - Account const& srcIssuerID, - STAmount const& dstAmount, - bool& bValid); + Account const& srcAccount, + Account const& dstAccount, + Currency const& uSrcCurrency, + Account const& uSrcIssuer, + STAmount const& dstAmount); - static void initPathTable(); + static void initPathTable (); - bool findPaths ( - int iLevel, - unsigned int const iMaxPaths, - STPathSet& spsDst, - STPath& spExtraPath); + bool findPaths (int searchLevel); -private: + void ensurePathsAreComplete (STPathSet&); + + /* Get the best paths, up to maxPaths in number, from mCompletePaths. + + On return, if fullLiquidityPath is not empty, then it contains the best + additional single path which can consume all the liquidity. + */ + STPathSet getBestPaths (int maxPaths, STPath& fullLiquidityPath); + + enum NodeType + { + nt_SOURCE, // The source account: with an issuer account, if needed. + 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. + }; + + // The PathType is a list of the NodeTypes for a path. + using PathType = std::vector ; + + // PaymentType represents the types of the source and destination currencies + // in a path request. enum PaymentType { pt_XRP_to_XRP, pt_XRP_to_nonXRP, pt_nonXRP_to_XRP, - pt_nonXRP_to_same, - pt_nonXRP_to_nonXRP + pt_nonXRP_to_same, // Destination currency is the same as source. + pt_nonXRP_to_nonXRP // Destination currency is NOT the same as source. }; - 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 - }; +private: + /* + Call graph of methoids - typedef std::vector PathType_t; - typedef std::pair CostedPath_t; - typedef std::vector CostedPathList_t; + findPaths: + addPathsForType: + addLinks: + addLink: + getPathsOut + issueMatchesOrigin + isNoRippleOut: + isNoRipple - typedef std::pair PathCost; - typedef std::vector PathCostList; + ensurePathsAreComplete - typedef std::map PathTable; + getBestPaths: + rippleCalculate + getPathLiquidity: + rippleCalculate + */ - /** Fill a CostedPathList_t from its description. */ - static void fillPaths(PaymentType type, - PathCostList const& costs); + // Add all paths of one type to mCompletePaths. + STPathSet& addPathsForType (PathType const& type); - /** @return true if any building paths are now complete. */ - bool checkComplete (STPathSet& retPathSet); - - static std::string pathTypeToString(PathType_t const&); - - bool matchesOrigin (Issue const&); + bool issueMatchesOrigin (Issue const&); int getPathsOut ( Currency const& currency, - Account const& accountID, + Account const& account, bool isDestCurrency, Account const& dest); - void addLink( + void addLink ( STPath const& currentPath, STPathSet& incompletePaths, int addFlags); - void addLink( + // Call addLink() for each path in currentPaths. + void addLinks ( STPathSet const& currentPaths, STPathSet& incompletePaths, int addFlags); - STPathSet& getPaths(PathType_t const& type, - bool addComplete = true); - - STPathSet filterPaths(int iMaxPaths, - STPath& extraPath); - - TER checkPath (STPath const& path, STAmount const& minDestAmount, - STAmount& amount, uint64_t& quality) const; + // Compute the liquidity for a path. Return tesSUCCESS if it has has enough + // liquidity to be worth keeping, otherwise an error. + TER getPathLiquidity ( + STPath const& path, // IN: The path to check. + STAmount const& minDstAmount, // IN: The minimum output this path must + // deliver to be worth keeping. + STAmount& amountOut, // OUT: The actual liquidity on the path. + uint64_t& qualityOut) const; // OUT: The returned initial quality + // Does this path end on an account-to-account link whose last account has + // set the "no ripple" flag on the link? bool isNoRippleOut (STPath const& currentPath); + + // Is the "no ripple" flag set from one account to another? bool isNoRipple ( - Account const& setByID, - Account const& setOnID, - Currency const& currencyID); + Account const& fromAccount, + Account const& toAccount, + Currency const& currency); - // Our main table of paths + Account mSrcAccount; + Account mDstAccount; + STAmount mDstAmount; + Currency mSrcCurrency; + Account mSrcIssuer; + STAmount mSrcAmount; - static PathTable mPathTable; - static PathType_t makePath(char const*); - - Account mSrcAccountID; - Account mDstAccountID; - STAmount mDstAmount; - Currency mSrcCurrencyID; - Account mSrcIssuerID; - STAmount mSrcAmount; - - Ledger::pointer mLedger; - LoadEvent::pointer m_loadEvent; - RippleLineCache::pointer mRLCache; + Ledger::pointer mLedger; + LoadEvent::pointer m_loadEvent; + RippleLineCache::pointer mRLCache; STPathElement mSource; STPathSet mCompletePaths; - std::map mPaths; + std::map mPaths; hash_map mPathsOutCountMap; @@ -146,51 +160,18 @@ private: static std::uint32_t const afADD_ACCOUNTS = 0x001; // Add order books - static std::uint32_t const afADD_BOOKS = 0x002; + static std::uint32_t const afADD_BOOKS = 0x002; // Add order book to XRP only - static std::uint32_t const afOB_XRP = 0x010; + static std::uint32_t const afOB_XRP = 0x010; // Must link to destination currency - static std::uint32_t const afOB_LAST = 0x040; + static std::uint32_t const afOB_LAST = 0x040; // Destination account only - static std::uint32_t const afAC_LAST = 0x080; + static std::uint32_t const afAC_LAST = 0x080; }; -CurrencySet usAccountDestCurrencies - (RippleAddress const& raAccountID, - RippleLineCache::ref cache, - bool includeXRP); - -CurrencySet usAccountSourceCurrencies - (RippleAddress const& raAccountID, - RippleLineCache::ref lrLedger, - bool includeXRP); - -/** Calculate the maximum amount of IOUs that an account can hold - @param ledger the ledger to check against. - @param account the account of interest. - @param issuer the issuer of the IOU. - @param currency the IOU to check. - @return The maximum amount that can be held. -*/ -STAmount -credit_limit ( - LedgerEntrySet& ledger, Account const& account, - Account const& issuer, Currency const& currency); - -/** Returns the amount of IOUs issued by issuer that are held by an account - @param ledger the ledger to check against. - @param account the account of interest. - @param issuer the issuer of the IOU. - @param currency the IOU to check. -*/ -STAmount -credit_balance ( - LedgerEntrySet& ledger, Account const& account, - Account const& issuer, Currency const& currency); - } // ripple #endif diff --git a/src/ripple/app/paths/cursor/ReverseLiquidityForAccount.cpp b/src/ripple/app/paths/cursor/ReverseLiquidityForAccount.cpp index 7d5aff7a1e..27ae54dda7 100644 --- a/src/ripple/app/paths/cursor/ReverseLiquidityForAccount.cpp +++ b/src/ripple/app/paths/cursor/ReverseLiquidityForAccount.cpp @@ -18,6 +18,7 @@ //============================================================================== #include +#include #include namespace ripple { @@ -46,8 +47,8 @@ TER PathCursor::reverseLiquidityForAccount () const auto const isFinalNode = (nodeIndex_ == lastNodeIndex); // 0 quality means none has yet been determined. - std::uint64_t uRateMax = 0; - + std::uint64_t uRateMax = 0 +; // Current is allowed to redeem to next. const bool previousNodeIsAccount = !nodeIndex_ || previousNode().isAccount(); @@ -80,7 +81,7 @@ TER PathCursor::reverseLiquidityForAccount () const // For previousNodeIsAccount: // Previous account is already owed. const STAmount saPrvOwed = (previousNodeIsAccount && nodeIndex_ != 0) - ? credit_balance (ledger(), + ? creditBalance (ledger(), node().account_, previousAccountID, node().issue_.currency) @@ -88,7 +89,7 @@ TER PathCursor::reverseLiquidityForAccount () const // The limit amount that the previous account may owe. const STAmount saPrvLimit = (previousNodeIsAccount && nodeIndex_ != 0) - ? credit_limit (ledger(), + ? creditLimit (ledger(), node().account_, previousAccountID, node().issue_.currency) @@ -96,7 +97,7 @@ TER PathCursor::reverseLiquidityForAccount () const // Next account is owed. const STAmount saNxtOwed = (nextNodeIsAccount && nodeIndex_ != lastNodeIndex) - ? credit_balance (ledger(), + ? creditBalance (ledger(), node().account_, nextAccountID, node().issue_.currency) diff --git a/src/ripple/rpc/handlers/RipplePathFind.cpp b/src/ripple/rpc/handlers/RipplePathFind.cpp index 8531bae8af..a06e08ded5 100644 --- a/src/ripple/rpc/handlers/RipplePathFind.cpp +++ b/src/ripple/rpc/handlers/RipplePathFind.cpp @@ -17,6 +17,8 @@ */ //============================================================================== +#include +#include #include namespace ripple { @@ -119,11 +121,10 @@ Json::Value doRipplePathFind (RPC::Context& context) } else { - auto usCurrencies = usAccountSourceCurrencies (raSrc, cache, true); - + auto currencies = accountSourceCurrencies (raSrc, cache, true); jvSrcCurrencies = Json::Value (Json::arrayValue); - for (auto const& uCurrency: usCurrencies) + for (auto const& uCurrency: currencies) { Json::Value jvCurrency (Json::objectValue); jvCurrency["currency"] = to_string(uCurrency); @@ -134,7 +135,7 @@ Json::Value doRipplePathFind (RPC::Context& context) // Fill in currencies destination will accept Json::Value jvDestCur (Json::arrayValue); - auto usDestCurrID = usAccountDestCurrencies (raDst, cache, true); + auto usDestCurrID = accountDestCurrencies (raDst, cache, true); for (auto const& uCurrency: usDestCurrID) jvDestCur.append (to_string (uCurrency)); @@ -177,11 +178,6 @@ Json::Value doRipplePathFind (RPC::Context& context) return rpcError (rpcSRC_ISR_MALFORMED); } - STPathSet spsComputed; - bool bValid; - Pathfinder pf (cache, raSrc, raDst, uSrcCurrencyID, - uSrcIssuerID, saDstAmount, bValid); - int level = getConfig().PATH_SEARCH_OLD; if ((getConfig().PATH_SEARCH_MAX > level) && !getApp().getFeeTrack().isLoadedLocal()) @@ -196,6 +192,7 @@ Json::Value doRipplePathFind (RPC::Context& context) level = rLev; } + STPathSet spsComputed; if (context.params_.isMember("paths")) { STParsedJSONObject paths ("paths", context.params_["paths"]); @@ -205,8 +202,18 @@ Json::Value doRipplePathFind (RPC::Context& context) spsComputed = paths.object.get()->downcast (); } - STPath extraPath; - if (!bValid || !pf.findPaths (level, 4, spsComputed, extraPath)) + STPath fullLiquidityPath; + auto valid = findPathsForOneIssuer( + cache, + raSrc.getAccountID(), + raDst.getAccountID(), + {uSrcCurrencyID, uSrcIssuerID}, + saDstAmount, + level, + 4, // iMaxPaths + spsComputed, + fullLiquidityPath); + if (!valid) { WriteLog (lsWARNING, RPCHandler) << "ripple_path_find: No paths found."; @@ -241,13 +248,13 @@ Json::Value doRipplePathFind (RPC::Context& context) << " saMaxAmountAct=" << rc.actualAmountIn << " saDstAmountAct=" << rc.actualAmountOut; - if (extraPath.size() > 0 && + if (fullLiquidityPath.size() > 0 && (rc.result() == terNO_LINE || rc.result() == tecPATH_PARTIAL)) { WriteLog (lsDEBUG, PathRequest) << "Trying with an extra path element"; - spsComputed.push_back (extraPath); + spsComputed.push_back (fullLiquidityPath); lesSandbox.clear (); rc = path::RippleCalc::rippleCalculate ( lesSandbox, diff --git a/src/ripple/rpc/impl/TransactionSign.cpp b/src/ripple/rpc/impl/TransactionSign.cpp index 10c50c767b..2576105c10 100644 --- a/src/ripple/rpc/impl/TransactionSign.cpp +++ b/src/ripple/rpc/impl/TransactionSign.cpp @@ -17,6 +17,7 @@ */ //============================================================================== +#include #include #include #include @@ -152,22 +153,20 @@ static Json::Value signPayment( if (!lpf.isOk ()) return rpcError (rpcTOO_BUSY); - bool bValid; auto cache = std::make_shared (lSnapshot); - Pathfinder pf ( + STPath fullLiquidityPath; + auto valid = findPathsForOneIssuer ( cache, - raSrcAddressID, - dstAccountID, - saSendMax.getCurrency (), - saSendMax.getIssuer (), - amount, bValid); + raSrcAddressID.getAccountID(), + dstAccountID.getAccountID(), + saSendMax.issue (), + amount, + getConfig ().PATH_SEARCH_OLD, + 4, // iMaxPaths + spsPaths, + fullLiquidityPath); - STPath extraPath; - if (!bValid || - !pf.findPaths (getConfig ().PATH_SEARCH_OLD, - 4, - spsPaths, - extraPath)) + if (!valid) { WriteLog (lsDEBUG, RPCHandler) << "transactionSign: build_path: No paths found."; diff --git a/src/ripple/unity/app.h b/src/ripple/unity/app.h index 7aae729f86..5b2312dad9 100644 --- a/src/ripple/unity/app.h +++ b/src/ripple/unity/app.h @@ -117,7 +117,5 @@ #include #include #include -#include - #endif diff --git a/src/ripple/unity/app6.cpp b/src/ripple/unity/app6.cpp index 68ea6c89bf..59db29b8f6 100644 --- a/src/ripple/unity/app6.cpp +++ b/src/ripple/unity/app6.cpp @@ -29,5 +29,8 @@ #include #include #include +#include +#include +#include #include #include