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/core/Config.h>
28 #include <ripple/core/JobQueue.h>
29 #include <ripple/json/to_string.h>
30 #include <ripple/ledger/PaymentSandbox.h>
72 constexpr
std::size_t PATHFINDER_MAX_COMPLETE_PATHS = 1000;
74 struct AccountCandidate
79 static const int highPriority = 10000;
83 compareAccountCandidate(
85 AccountCandidate
const& first,
86 AccountCandidate
const& second)
88 if (first.priority < second.priority)
91 if (first.account > second.account)
94 return (first.priority ^ seq) < (second.priority ^ seq);
116 static PathTable mPathTable;
123 for (
auto const& node : type)
154 smallestUsefulAmount(STAmount
const& amount,
int maxPaths)
156 return divide(amount, STAmount(maxPaths + 2), amount.issue());
169 : mSrcAccount(uSrcAccount)
170 , mDstAccount(uDstAccount)
172 isXRP(saDstAmount.getIssuer()) ? uDstAccount
173 : saDstAmount.getIssuer())
174 , mDstAmount(saDstAmount)
175 , mSrcCurrency(uSrcCurrency)
176 , mSrcIssuer(uSrcIssuer)
177 , mSrcAmount(srcAmount.value_or(
STAmount(
185 , mLedger(cache->getLedger())
188 , j_(app.journal(
"Pathfinder"))
190 assert(!uSrcIssuer ||
isXRP(uSrcCurrency) ==
isXRP(uSrcIssuer.value()));
199 JLOG(
j_.
debug()) <<
"Destination amount was zero.";
211 JLOG(
j_.
debug()) <<
"Tried to send to same issuer";
228 auto issuer = currencyIsXRP ?
AccountID() : account;
232 JLOG(
j_.
trace()) <<
"findPaths>"
237 <<
" mSrcIssuer=" << issuerString;
241 JLOG(
j_.
debug()) <<
"findPaths< no ledger";
251 JLOG(
j_.
debug()) <<
"invalid source account";
258 JLOG(
j_.
debug()) <<
"Non-existent gateway";
268 JLOG(
j_.
debug()) <<
"New account not being funded in XRP ";
276 <<
"New account not getting enough funding: " <<
mDstAmount
285 if (bSrcXrp && bDstXrp)
288 JLOG(
j_.
debug()) <<
"XRP to XRP payment";
294 JLOG(
j_.
debug()) <<
"XRP to non-XRP payment";
300 JLOG(
j_.
debug()) <<
"non-XRP to XRP payment";
306 JLOG(
j_.
debug()) <<
"non-XRP to non-XRP - same currency";
312 JLOG(
j_.
debug()) <<
"non-XRP to non-XRP - cross currency";
317 for (
auto const& costedPath : mPathTable[paymentType])
320 if (costedPath.searchLevel <= searchLevel)
342 uint64_t& qualityOut)
const
371 qualityOut =
getRate(rc.actualAmountOut, rc.actualAmountIn);
372 amountOut = rc.actualAmountOut;
381 mDstAmount - amountOut,
390 amountOut += rc.actualAmountOut;
397 JLOG(j_.
info()) <<
"checkpath: exception (" << e.
what() <<
") "
428 <<
"Default path contributes: " << rc.actualAmountIn;
434 <<
"Default path fails: " <<
transToken(rc.result());
439 JLOG(
j_.
debug()) <<
"Default path causes exception";
461 return path.size() == 1;
471 for (
auto it = path.begin() + 1; it != path.end(); ++it)
488 auto const saMinDstAmount = [&]() ->
STAmount {
492 return smallestUsefulAmount(
mDstAmount, maxPaths);
500 for (
int i = 0; i < paths.
size(); ++i)
502 auto const& currentPath = paths[i];
503 if (!currentPath.empty())
508 currentPath, saMinDstAmount, liquidity, uQuality);
512 <<
"findPaths: dropping : " <<
transToken(resultCode)
517 JLOG(
j_.
debug()) <<
"findPaths: quality: " << uQuality <<
": "
521 {uQuality, currentPath.size(), liquidity, i});
536 if (!convert_all_ && a.quality != b.quality)
537 return a.quality < b.quality;
540 if (a.liquidity != b.liquidity)
541 return a.liquidity > b.liquidity;
544 if (a.length != b.length)
545 return a.length < b.length;
548 return a.index > b.index;
555 STPath& fullLiquidityPath,
560 << extraPaths.
size() <<
" extras";
565 assert(fullLiquidityPath.
empty());
566 const bool issuerIsSender =
570 rankPaths(maxPaths, extraPaths, extraPathRanks);
580 auto extraPathsIterator = extraPathRanks.
begin();
583 extraPathsIterator != extraPathRanks.
end())
585 bool usePath =
false;
586 bool useExtraPath =
false;
590 else if (extraPathsIterator == extraPathRanks.
end())
592 else if (extraPathsIterator->quality < pathsIterator->quality)
594 else if (extraPathsIterator->quality > pathsIterator->quality)
596 else if (extraPathsIterator->liquidity > pathsIterator->liquidity)
598 else if (extraPathsIterator->liquidity < pathsIterator->liquidity)
607 auto& pathRank = usePath ? *pathsIterator : *extraPathsIterator;
610 : extraPaths[pathRank.index];
613 ++extraPathsIterator;
618 auto iPathsLeft = maxPaths - bestPaths.
size();
619 if (!(iPathsLeft > 0 || fullLiquidityPath.
empty()))
628 bool startsWithIssuer =
false;
630 if (!issuerIsSender && usePath)
633 if (
isDefaultPath(path) || path.front().getAccountID() != srcIssuer)
638 startsWithIssuer =
true;
641 if (iPathsLeft > 1 ||
642 (iPathsLeft > 0 && pathRank.liquidity >= remaining))
646 remaining -= pathRank.liquidity;
650 iPathsLeft == 0 && pathRank.liquidity >=
mDstAmount &&
651 fullLiquidityPath.
empty())
654 fullLiquidityPath = (startsWithIssuer ?
removeIssuer(path) : path);
655 JLOG(
j_.
debug()) <<
"Found extra full path: "
660 JLOG(
j_.
debug()) <<
"Skipping a non-filling path: "
665 if (remaining > beast::zero)
667 assert(fullLiquidityPath.
empty());
668 JLOG(
j_.
info()) <<
"Paths could not send " << remaining <<
" of "
673 JLOG(
j_.
debug()) <<
"findPaths: RESULTS: "
687 return matchingCurrency && matchingAccount;
697 Issue const issue(currency, account);
710 int aFlags = sleAccount->getFieldU32(
sfFlags);
720 for (
auto const& item :
mRLCache->getRippleLines(account))
731 (bAuthRequired && !rspEntry->
getAuth())))
763 JLOG(
j_.
debug()) <<
"addLink< on " << currentPaths.
size()
764 <<
" source(s), flags=" << addFlags;
765 for (
auto const& path : currentPaths)
766 addLink(path, incompletePaths, addFlags);
773 auto it =
mPaths.find(pathType);
778 if (pathType.
empty())
789 JLOG(
j_.
debug()) <<
"getPaths< adding onto '"
790 << pathTypeToString(parentPathType) <<
"' to get '"
791 << pathTypeToString(pathType) <<
"'";
796 auto nodeType = pathType.
back();
801 assert(pathsOut.
empty());
832 <<
" complete paths added";
835 JLOG(
j_.
debug()) <<
"getPaths> " << pathsOut.
size()
836 <<
" partial paths found";
852 return sleRipple && (sleRipple->getFieldU32(
sfFlags) & flag);
861 if (currentPath.
empty())
872 auto const& fromAccount = (currentPath.
size() == 1)
874 : (currentPath.
end() - 2)->getAccountID();
884 for (
auto const& p : pathSet)
889 pathSet.push_back(path);
894 const STPath& currentPath,
899 auto const& uEndCurrency = pathEnd.getCurrency();
900 auto const& uEndIssuer = pathEnd.getIssuerID();
901 auto const& uEndAccount = pathEnd.getAccountID();
902 bool const bOnXRP = uEndCurrency.isZero();
909 JLOG(
j_.
trace()) <<
"addLink< flags=" << addFlags <<
" onXRP=" << bOnXRP;
919 JLOG(
j_.
trace()) <<
"complete path found ax: "
931 bool const bRequireAuth(
933 bool const bIsEndCurrency(
936 bool const bDestOnly(addFlags &
afAC_LAST);
938 auto& rippleLines(
mRLCache->getRippleLines(uEndAccount));
940 AccountCandidates candidates;
941 candidates.reserve(rippleLines.size());
943 for (
auto const& item : rippleLines)
945 auto* rs =
dynamic_cast<RippleState const*
>(item.get());
948 JLOG(
j_.
error()) <<
"Couldn't decipher RippleState";
951 auto const& acct = rs->getAccountIDPeer();
953 if (hasEffectiveDestination && (acct ==
mDstAccount))
961 if (bDestOnly && !bToDestination)
966 if ((uEndCurrency == rs->getLimit().getCurrency()) &&
967 !currentPath.
hasSeen(acct, uEndCurrency, acct))
970 if (rs->getBalance() <= beast::zero &&
971 (!rs->getLimitPeer() ||
972 -rs->getBalance() >= rs->getLimitPeer() ||
973 (bRequireAuth && !rs->getAuth())))
977 else if (bIsNoRippleOut && rs->getNoRipple())
981 else if (bToDestination)
987 if (!currentPath.
empty())
990 <<
"complete path found ae: "
999 candidates.push_back(
1000 {AccountCandidate::highPriority, acct});
1016 candidates.push_back({
out, acct});
1021 if (!candidates.empty())
1027 compareAccountCandidate,
1029 std::placeholders::_1,
1030 std::placeholders::_2));
1032 int count = candidates.size();
1036 else if (count > 50)
1039 auto it = candidates.begin();
1040 while (count-- != 0)
1048 incompletePaths.
assembleAdd(currentPath, pathElement);
1055 JLOG(
j_.
warn()) <<
"Path ends on non-existent issuer";
1073 incompletePaths.
assembleAdd(currentPath, pathElement);
1078 bool bDestOnly = (addFlags &
afOB_LAST) != 0;
1080 {uEndCurrency, uEndIssuer});
1082 << books.size() <<
" books found from this currency/issuer";
1084 for (
auto const& book : books)
1088 book->getCurrencyOut(),
1089 book->getIssuerOut()) &&
1094 STPath newPath(currentPath);
1096 if (book->getCurrencyOut().isZero())
1111 <<
"complete path found bx: "
1118 else if (!currentPath.
hasSeen(
1119 book->getIssuerOut(),
1120 book->getCurrencyOut(),
1121 book->getIssuerOut()))
1125 if ((newPath.
size() >= 2) &&
1126 (newPath.
back().isAccount()) &&
1127 (newPath[newPath.
size() - 2].isOffer()))
1134 book->getCurrencyOut(),
1135 book->getIssuerOut());
1144 book->getCurrencyOut(),
1145 book->getIssuerOut());
1148 if (hasEffectiveDestination &&
1160 <<
"complete path found ba: "
1171 book->getIssuerOut(),
1172 book->getCurrencyOut(),
1173 book->getIssuerOut()));
1185 makePath(
char const*
string)
1228 auto& list = mPathTable[type];
1229 assert(list.empty());
1230 for (
auto& cost : costs)
1231 list.push_back({cost.cost, makePath(cost.path)});