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/Tuning.h>
26 #include <ripple/basics/Log.h>
27 #include <ripple/core/Config.h>
28 #include <ripple/core/JobQueue.h>
29 #include <ripple/json/to_string.h>
30 #include <ripple/ledger/PaymentSandbox.h>
69 struct AccountCandidate
74 static const int highPriority = 10000;
78 compareAccountCandidate(
80 AccountCandidate
const& first,
81 AccountCandidate
const& second)
83 if (first.priority < second.priority)
86 if (first.account > second.account)
89 return (first.priority ^ seq) < (second.priority ^ seq);
111 static PathTable mPathTable;
118 for (
auto const& node : type)
153 boost::optional<AccountID>
const& uSrcIssuer,
155 boost::optional<STAmount>
const& srcAmount,
157 : mSrcAccount(uSrcAccount)
158 , mDstAccount(uDstAccount)
160 isXRP(saDstAmount.getIssuer()) ? uDstAccount
161 : saDstAmount.getIssuer())
162 , mDstAmount(saDstAmount)
163 , mSrcCurrency(uSrcCurrency)
164 , mSrcIssuer(uSrcIssuer)
165 , mSrcAmount(srcAmount.value_or(
STAmount(
178 , mLedger(cache->getLedger())
181 , j_(app.journal(
"Pathfinder"))
183 assert(!uSrcIssuer ||
isXRP(uSrcCurrency) ==
isXRP(uSrcIssuer.get()));
192 JLOG(
j_.
debug()) <<
"Destination amount was zero.";
204 JLOG(
j_.
debug()) <<
"Tried to send to same issuer";
221 auto issuer = currencyIsXRP ?
AccountID() : account;
225 JLOG(
j_.
trace()) <<
"findPaths>"
230 <<
" mSrcIssuer=" << issuerString;
234 JLOG(
j_.
debug()) <<
"findPaths< no ledger";
244 JLOG(
j_.
debug()) <<
"invalid source account";
251 JLOG(
j_.
debug()) <<
"Non-existent gateway";
261 JLOG(
j_.
debug()) <<
"New account not being funded in XRP ";
269 <<
"New account not getting enough funding: " <<
mDstAmount
278 if (bSrcXrp && bDstXrp)
281 JLOG(
j_.
debug()) <<
"XRP to XRP payment";
287 JLOG(
j_.
debug()) <<
"XRP to non-XRP payment";
293 JLOG(
j_.
debug()) <<
"non-XRP to XRP payment";
299 JLOG(
j_.
debug()) <<
"non-XRP to non-XRP - same currency";
305 JLOG(
j_.
debug()) <<
"non-XRP to non-XRP - cross currency";
310 for (
auto const& costedPath : mPathTable[paymentType])
313 if (costedPath.searchLevel <= searchLevel)
337 uint64_t& qualityOut)
const
366 qualityOut =
getRate(rc.actualAmountOut, rc.actualAmountIn);
367 amountOut = rc.actualAmountOut;
376 mDstAmount - amountOut,
385 amountOut += rc.actualAmountOut;
392 JLOG(j_.
info()) <<
"checkpath: exception (" << e.
what() <<
") "
403 smallestUsefulAmount(
STAmount const& amount,
int maxPaths)
438 <<
"Default path contributes: " << rc.actualAmountIn;
444 <<
"Default path fails: " <<
transToken(rc.result());
449 JLOG(
j_.
debug()) <<
"Default path causes exception";
472 return path.size() == 1;
482 for (
auto it = path.begin() + 1; it != path.end(); ++it)
510 saMinDstAmount = smallestUsefulAmount(
mDstAmount, maxPaths);
513 for (
int i = 0; i < paths.
size(); ++i)
515 auto const& currentPath = paths[i];
516 if (!currentPath.empty())
521 currentPath, saMinDstAmount, liquidity, uQuality);
525 <<
"findPaths: dropping : " <<
transToken(resultCode)
530 JLOG(
j_.
debug()) <<
"findPaths: quality: " << uQuality <<
": "
534 {uQuality, currentPath.size(), liquidity, i});
549 if (!convert_all_ && a.quality != b.quality)
550 return a.quality < b.quality;
553 if (a.liquidity != b.liquidity)
554 return a.liquidity > b.liquidity;
557 if (a.length != b.length)
558 return a.length < b.length;
561 return a.index > b.index;
568 STPath& fullLiquidityPath,
573 << extraPaths.
size() <<
" extras";
578 assert(fullLiquidityPath.
empty());
579 const bool issuerIsSender =
583 rankPaths(maxPaths, extraPaths, extraPathRanks);
593 auto extraPathsIterator = extraPathRanks.
begin();
596 extraPathsIterator != extraPathRanks.
end())
598 bool usePath =
false;
599 bool useExtraPath =
false;
603 else if (extraPathsIterator == extraPathRanks.
end())
605 else if (extraPathsIterator->quality < pathsIterator->quality)
607 else if (extraPathsIterator->quality > pathsIterator->quality)
609 else if (extraPathsIterator->liquidity > pathsIterator->liquidity)
611 else if (extraPathsIterator->liquidity < pathsIterator->liquidity)
620 auto& pathRank = usePath ? *pathsIterator : *extraPathsIterator;
623 : extraPaths[pathRank.index];
626 ++extraPathsIterator;
631 auto iPathsLeft = maxPaths - bestPaths.
size();
632 if (!(iPathsLeft > 0 || fullLiquidityPath.
empty()))
641 bool startsWithIssuer =
false;
643 if (!issuerIsSender && usePath)
646 if (
isDefaultPath(path) || path.front().getAccountID() != srcIssuer)
651 startsWithIssuer =
true;
654 if (iPathsLeft > 1 ||
655 (iPathsLeft > 0 && pathRank.liquidity >= remaining))
659 remaining -= pathRank.liquidity;
663 iPathsLeft == 0 && pathRank.liquidity >=
mDstAmount &&
664 fullLiquidityPath.
empty())
667 fullLiquidityPath = (startsWithIssuer ?
removeIssuer(path) : path);
668 JLOG(
j_.
debug()) <<
"Found extra full path: "
673 JLOG(
j_.
debug()) <<
"Skipping a non-filling path: "
678 if (remaining > beast::zero)
680 assert(fullLiquidityPath.
empty());
681 JLOG(
j_.
info()) <<
"Paths could not send " << remaining <<
" of "
686 JLOG(
j_.
debug()) <<
"findPaths: RESULTS: "
700 return matchingCurrency && matchingAccount;
710 Issue const issue(currency, account);
723 int aFlags = sleAccount->getFieldU32(
sfFlags);
733 for (
auto const& item :
mRLCache->getRippleLines(account))
744 (bAuthRequired && !rspEntry->
getAuth())))
776 JLOG(
j_.
debug()) <<
"addLink< on " << currentPaths.
size()
777 <<
" source(s), flags=" << addFlags;
778 for (
auto const& path : currentPaths)
779 addLink(path, incompletePaths, addFlags);
786 auto it =
mPaths.find(pathType);
791 if (pathType.
empty())
802 JLOG(
j_.
debug()) <<
"getPaths< adding onto '"
803 << pathTypeToString(parentPathType) <<
"' to get '"
804 << pathTypeToString(pathType) <<
"'";
809 auto nodeType = pathType.
back();
814 assert(pathsOut.
empty());
845 <<
" complete paths added";
848 JLOG(
j_.
debug()) <<
"getPaths> " << pathsOut.
size()
849 <<
" partial paths found";
865 return sleRipple && (sleRipple->getFieldU32(
sfFlags) & flag);
874 if (currentPath.
empty())
885 auto const& fromAccount = (currentPath.
size() == 1)
887 : (currentPath.
end() - 2)->getAccountID();
897 for (
auto const& p : pathSet)
902 pathSet.push_back(path);
907 const STPath& currentPath,
912 auto const& uEndCurrency = pathEnd.getCurrency();
913 auto const& uEndIssuer = pathEnd.getIssuerID();
914 auto const& uEndAccount = pathEnd.getAccountID();
915 bool const bOnXRP = uEndCurrency.isZero();
922 JLOG(
j_.
trace()) <<
"addLink< flags=" << addFlags <<
" onXRP=" << bOnXRP;
932 JLOG(
j_.
trace()) <<
"complete path found ax: "
944 bool const bRequireAuth(
946 bool const bIsEndCurrency(
949 bool const bDestOnly(addFlags &
afAC_LAST);
951 auto& rippleLines(
mRLCache->getRippleLines(uEndAccount));
953 AccountCandidates candidates;
954 candidates.reserve(rippleLines.size());
956 for (
auto const& item : rippleLines)
958 auto* rs =
dynamic_cast<RippleState const*
>(item.get());
961 JLOG(
j_.
error()) <<
"Couldn't decipher RippleState";
964 auto const& acct = rs->getAccountIDPeer();
966 if (hasEffectiveDestination && (acct ==
mDstAccount))
974 if (bDestOnly && !bToDestination)
979 if ((uEndCurrency == rs->getLimit().getCurrency()) &&
980 !currentPath.
hasSeen(acct, uEndCurrency, acct))
983 if (rs->getBalance() <= beast::zero &&
984 (!rs->getLimitPeer() ||
985 -rs->getBalance() >= rs->getLimitPeer() ||
986 (bRequireAuth && !rs->getAuth())))
990 else if (bIsNoRippleOut && rs->getNoRipple())
994 else if (bToDestination)
1000 if (!currentPath.
empty())
1003 <<
"complete path found ae: "
1009 else if (!bDestOnly)
1012 candidates.push_back(
1013 {AccountCandidate::highPriority, acct});
1029 candidates.push_back({
out, acct});
1034 if (!candidates.empty())
1040 compareAccountCandidate,
1042 std::placeholders::_1,
1043 std::placeholders::_2));
1045 int count = candidates.size();
1049 else if (count > 50)
1052 auto it = candidates.begin();
1053 while (count-- != 0)
1061 incompletePaths.
assembleAdd(currentPath, pathElement);
1068 JLOG(
j_.
warn()) <<
"Path ends on non-existent issuer";
1086 incompletePaths.
assembleAdd(currentPath, pathElement);
1091 bool bDestOnly = (addFlags &
afOB_LAST) != 0;
1093 {uEndCurrency, uEndIssuer});
1095 << books.size() <<
" books found from this currency/issuer";
1097 for (
auto const& book : books)
1101 book->getCurrencyOut(),
1102 book->getIssuerOut()) &&
1107 STPath newPath(currentPath);
1109 if (book->getCurrencyOut().isZero())
1124 <<
"complete path found bx: "
1131 else if (!currentPath.
hasSeen(
1132 book->getIssuerOut(),
1133 book->getCurrencyOut(),
1134 book->getIssuerOut()))
1138 if ((newPath.
size() >= 2) &&
1139 (newPath.
back().isAccount()) &&
1140 (newPath[newPath.
size() - 2].isOffer()))
1147 book->getCurrencyOut(),
1148 book->getIssuerOut());
1157 book->getCurrencyOut(),
1158 book->getIssuerOut());
1161 if (hasEffectiveDestination &&
1173 <<
"complete path found ba: "
1184 book->getIssuerOut(),
1185 book->getCurrencyOut(),
1186 book->getIssuerOut()));
1198 makePath(
char const*
string)
1241 auto& list = mPathTable[type];
1242 assert(list.empty());
1243 for (
auto& cost : costs)
1244 list.push_back({cost.cost, makePath(cost.path)});