20 #include <ripple/app/ledger/OrderBookDB.h>
21 #include <ripple/app/main/Application.h>
22 #include <ripple/app/paths/Pathfinder.h>
23 #include <ripple/app/paths/RippleCalc.h>
24 #include <ripple/app/paths/RippleLineCache.h>
25 #include <ripple/app/paths/impl/PathfinderUtils.h>
26 #include <ripple/basics/Log.h>
27 #include <ripple/basics/join.h>
28 #include <ripple/core/Config.h>
29 #include <ripple/core/JobQueue.h>
30 #include <ripple/json/to_string.h>
31 #include <ripple/ledger/PaymentSandbox.h>
73 constexpr
std::size_t PATHFINDER_MAX_COMPLETE_PATHS = 1000;
75 struct AccountCandidate
80 static const int highPriority = 10000;
84 compareAccountCandidate(
86 AccountCandidate
const& first,
87 AccountCandidate
const& second)
89 if (first.priority < second.priority)
92 if (first.account > second.account)
95 return (first.priority ^ seq) < (second.priority ^ seq);
117 static PathTable mPathTable;
124 for (
auto const& node : type)
155 smallestUsefulAmount(STAmount
const& amount,
int maxPaths)
157 return divide(amount, STAmount(maxPaths + 2), amount.issue());
170 : mSrcAccount(uSrcAccount)
171 , mDstAccount(uDstAccount)
173 isXRP(saDstAmount.getIssuer()) ? uDstAccount
174 : saDstAmount.getIssuer())
175 , mDstAmount(saDstAmount)
176 , mSrcCurrency(uSrcCurrency)
177 , mSrcIssuer(uSrcIssuer)
178 , mSrcAmount(srcAmount.value_or(
STAmount(
186 , mLedger(cache->getLedger())
189 , j_(app.journal(
"Pathfinder"))
191 assert(!uSrcIssuer ||
isXRP(uSrcCurrency) ==
isXRP(uSrcIssuer.value()));
199 JLOG(
j_.
trace()) <<
"findPaths start";
203 JLOG(
j_.
debug()) <<
"Destination amount was zero.";
215 JLOG(
j_.
debug()) <<
"Tried to send to same issuer";
232 auto issuer = currencyIsXRP ?
AccountID() : account;
236 JLOG(
j_.
trace()) <<
"findPaths>"
241 <<
" mSrcIssuer=" << issuerString;
245 JLOG(
j_.
debug()) <<
"findPaths< no ledger";
255 JLOG(
j_.
debug()) <<
"invalid source account";
262 JLOG(
j_.
debug()) <<
"Non-existent gateway";
272 JLOG(
j_.
debug()) <<
"New account not being funded in XRP ";
280 <<
"New account not getting enough funding: " <<
mDstAmount
289 if (bSrcXrp && bDstXrp)
292 JLOG(
j_.
debug()) <<
"XRP to XRP payment";
298 JLOG(
j_.
debug()) <<
"XRP to non-XRP payment";
304 JLOG(
j_.
debug()) <<
"non-XRP to XRP payment";
310 JLOG(
j_.
debug()) <<
"non-XRP to non-XRP - same currency";
316 JLOG(
j_.
debug()) <<
"non-XRP to non-XRP - cross currency";
321 for (
auto const& costedPath : mPathTable[paymentType])
323 if (continueCallback && !continueCallback())
326 if (costedPath.searchLevel <= searchLevel)
328 JLOG(
j_.
trace()) <<
"findPaths trying payment type " << paymentType;
349 uint64_t& qualityOut)
const
378 qualityOut =
getRate(rc.actualAmountOut, rc.actualAmountIn);
379 amountOut = rc.actualAmountOut;
388 mDstAmount - amountOut,
397 amountOut += rc.actualAmountOut;
404 JLOG(j_.
info()) <<
"checkpath: exception (" << e.
what() <<
") "
437 <<
"Default path contributes: " << rc.actualAmountIn;
443 <<
"Default path fails: " <<
transToken(rc.result());
448 JLOG(
j_.
debug()) <<
"Default path causes exception";
470 return path.size() == 1;
480 for (
auto it = path.begin() + 1; it != path.end(); ++it)
495 JLOG(
j_.
trace()) <<
"rankPaths with " << paths.
size() <<
" candidates, and "
496 << maxPaths <<
" maximum";
500 auto const saMinDstAmount = [&]() ->
STAmount {
504 return smallestUsefulAmount(
mDstAmount, maxPaths);
512 for (
int i = 0; i < paths.
size(); ++i)
514 if (continueCallback && !continueCallback())
516 auto const& currentPath = paths[i];
517 if (!currentPath.empty())
522 currentPath, saMinDstAmount, liquidity, uQuality);
526 <<
"findPaths: dropping : " <<
transToken(resultCode)
531 JLOG(
j_.
debug()) <<
"findPaths: quality: " << uQuality <<
": "
535 {uQuality, currentPath.size(), liquidity, i});
550 if (!convert_all_ && a.quality != b.quality)
551 return a.quality < b.quality;
554 if (a.liquidity != b.liquidity)
555 return a.liquidity > b.liquidity;
558 if (a.length != b.length)
559 return a.length < b.length;
562 return a.index > b.index;
569 STPath& fullLiquidityPath,
575 << extraPaths.
size() <<
" extras";
580 assert(fullLiquidityPath.
empty());
581 const bool issuerIsSender =
585 rankPaths(maxPaths, extraPaths, extraPathRanks, continueCallback);
595 auto extraPathsIterator = extraPathRanks.
begin();
598 extraPathsIterator != extraPathRanks.
end())
600 if (continueCallback && !continueCallback())
602 bool usePath =
false;
603 bool useExtraPath =
false;
607 else if (extraPathsIterator == extraPathRanks.
end())
609 else if (extraPathsIterator->quality < pathsIterator->quality)
611 else if (extraPathsIterator->quality > pathsIterator->quality)
613 else if (extraPathsIterator->liquidity > pathsIterator->liquidity)
615 else if (extraPathsIterator->liquidity < pathsIterator->liquidity)
624 auto& pathRank = usePath ? *pathsIterator : *extraPathsIterator;
627 : extraPaths[pathRank.index];
630 ++extraPathsIterator;
635 auto iPathsLeft = maxPaths - bestPaths.
size();
636 if (!(iPathsLeft > 0 || fullLiquidityPath.
empty()))
645 bool startsWithIssuer =
false;
647 if (!issuerIsSender && usePath)
650 if (
isDefaultPath(path) || path.front().getAccountID() != srcIssuer)
655 startsWithIssuer =
true;
658 if (iPathsLeft > 1 ||
659 (iPathsLeft > 0 && pathRank.liquidity >= remaining))
663 remaining -= pathRank.liquidity;
667 iPathsLeft == 0 && pathRank.liquidity >=
mDstAmount &&
668 fullLiquidityPath.
empty())
671 fullLiquidityPath = (startsWithIssuer ?
removeIssuer(path) : path);
672 JLOG(
j_.
debug()) <<
"Found extra full path: "
677 JLOG(
j_.
debug()) <<
"Skipping a non-filling path: "
682 if (remaining > beast::zero)
684 assert(fullLiquidityPath.
empty());
685 JLOG(
j_.
info()) <<
"Paths could not send " << remaining <<
" of "
690 JLOG(
j_.
debug()) <<
"findPaths: RESULTS: "
704 return matchingCurrency && matchingAccount;
716 Issue const issue(currency, account);
729 int aFlags = sleAccount->getFieldU32(
sfFlags);
739 if (
auto const lines =
mRLCache->getRippleLines(account, direction))
741 for (
auto const& rspEntry : *lines)
743 if (currency != rspEntry.getLimit().getCurrency())
747 rspEntry.getBalance() <= beast::zero &&
748 (!rspEntry.getLimitPeer() ||
749 -rspEntry.getBalance() >= rspEntry.getLimitPeer() ||
750 (bAuthRequired && !rspEntry.getAuth())))
754 isDstCurrency && dstAccount == rspEntry.getAccountIDPeer())
758 else if (rspEntry.getNoRipplePeer())
762 else if (rspEntry.getFreezePeer())
784 JLOG(
j_.
debug()) <<
"addLink< on " << currentPaths.
size()
785 <<
" source(s), flags=" << addFlags;
786 for (
auto const& path : currentPaths)
788 if (continueCallback && !continueCallback())
790 addLink(path, incompletePaths, addFlags, continueCallback);
799 JLOG(
j_.
debug()) <<
"addPathsForType "
802 auto it =
mPaths.find(pathType);
807 if (pathType.
empty())
809 if (continueCallback && !continueCallback())
821 JLOG(
j_.
debug()) <<
"getPaths< adding onto '"
822 << pathTypeToString(parentPathType) <<
"' to get '"
823 << pathTypeToString(pathType) <<
"'";
828 auto nodeType = pathType.
back();
833 assert(pathsOut.
empty());
876 <<
" complete paths added";
879 JLOG(
j_.
debug()) <<
"getPaths> " << pathsOut.
size()
880 <<
" partial paths found";
896 return sleRipple && (sleRipple->getFieldU32(
sfFlags) & flag);
905 if (currentPath.
empty())
916 auto const& fromAccount = (currentPath.
size() == 1)
918 : (currentPath.
end() - 2)->getAccountID();
928 for (
auto const& p : pathSet)
933 pathSet.push_back(path);
938 const STPath& currentPath,
944 auto const& uEndCurrency = pathEnd.getCurrency();
945 auto const& uEndIssuer = pathEnd.getIssuerID();
946 auto const& uEndAccount = pathEnd.getAccountID();
947 bool const bOnXRP = uEndCurrency.isZero();
954 JLOG(
j_.
trace()) <<
"addLink< flags=" << addFlags <<
" onXRP=" << bOnXRP
965 JLOG(
j_.
trace()) <<
"complete path found ax: "
977 bool const bRequireAuth(
979 bool const bIsEndCurrency(
982 bool const bDestOnly(addFlags &
afAC_LAST);
984 if (
auto const lines =
mRLCache->getRippleLines(
989 auto& rippleLines = *lines;
991 AccountCandidates candidates;
992 candidates.reserve(rippleLines.size());
994 for (
auto const& rs : rippleLines)
996 if (continueCallback && !continueCallback())
998 auto const& acct = rs.getAccountIDPeer();
1001 if (hasEffectiveDestination && (acct ==
mDstAccount))
1009 if (bDestOnly && !bToDestination)
1014 if ((uEndCurrency == rs.getLimit().getCurrency()) &&
1015 !currentPath.
hasSeen(acct, uEndCurrency, acct))
1019 if (rs.getBalance() <= beast::zero &&
1020 (!rs.getLimitPeer() ||
1021 -rs.getBalance() >= rs.getLimitPeer() ||
1022 (bRequireAuth && !rs.getAuth())))
1026 else if (bIsNoRippleOut && rs.getNoRipple())
1030 else if (bToDestination)
1036 if (!currentPath.
empty())
1039 <<
"complete path found ae: "
1046 else if (!bDestOnly)
1049 candidates.push_back(
1050 {AccountCandidate::highPriority, acct});
1068 candidates.push_back({
out, acct});
1073 if (!candidates.empty())
1079 compareAccountCandidate,
1081 std::placeholders::_1,
1082 std::placeholders::_2));
1084 int count = candidates.size();
1088 else if (count > 50)
1091 auto it = candidates.begin();
1092 while (count-- != 0)
1094 if (continueCallback && !continueCallback())
1103 currentPath, pathElement);
1111 JLOG(
j_.
warn()) <<
"Path ends on non-existent issuer";
1129 incompletePaths.
assembleAdd(currentPath, pathElement);
1134 bool bDestOnly = (addFlags &
afOB_LAST) != 0;
1136 {uEndCurrency, uEndIssuer});
1138 << books.size() <<
" books found from this currency/issuer";
1140 for (
auto const& book : books)
1142 if (continueCallback && !continueCallback())
1145 xrpAccount(), book.out.currency, book.out.account) &&
1150 STPath newPath(currentPath);
1152 if (book.out.currency.isZero())
1167 <<
"complete path found bx: "
1174 else if (!currentPath.
hasSeen(
1181 if ((newPath.
size() >= 2) &&
1182 (newPath.
back().isAccount()) &&
1183 (newPath[newPath.
size() - 2].isOffer()))
1204 if (hasEffectiveDestination &&
1216 <<
"complete path found ba: "
1241 makePath(
char const*
string)
1284 auto& list = mPathTable[type];
1285 assert(list.empty());
1286 for (
auto& cost : costs)
1287 list.push_back({cost.cost, makePath(cost.path)});