20#include <xrpld/app/ledger/OrderBookDB.h>
21#include <xrpld/app/main/Application.h>
22#include <xrpld/app/paths/Pathfinder.h>
23#include <xrpld/app/paths/RippleCalc.h>
24#include <xrpld/app/paths/RippleLineCache.h>
25#include <xrpld/app/paths/detail/PathfinderUtils.h>
26#include <xrpld/core/JobQueue.h>
27#include <xrpld/ledger/PaymentSandbox.h>
29#include <xrpl/basics/Log.h>
30#include <xrpl/basics/join.h>
31#include <xrpl/json/to_string.h>
73constexpr std::size_t PATHFINDER_MAX_COMPLETE_PATHS = 1000;
75struct AccountCandidate
80 static int const highPriority = 10000;
84compareAccountCandidate(
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);
117static PathTable mPathTable;
124 for (
auto const& node : type)
155smallestUsefulAmount(STAmount
const& amount,
int maxPaths)
157 return divide(amount, STAmount(maxPaths + 2), amount.issue());
171 : mSrcAccount(uSrcAccount)
172 , mDstAccount(uDstAccount)
174 isXRP(saDstAmount.getIssuer()) ? uDstAccount
175 : saDstAmount.getIssuer())
176 , mDstAmount(saDstAmount)
177 , mSrcCurrency(uSrcCurrency)
178 , mSrcIssuer(uSrcIssuer)
179 , mSrcAmount(srcAmount.value_or(
STAmount(
189 , mLedger(cache->getLedger())
192 , j_(app.journal(
"Pathfinder"))
196 "ripple::Pathfinder::Pathfinder : valid inputs");
204 JLOG(
j_.
trace()) <<
"findPaths start";
208 JLOG(
j_.
debug()) <<
"Destination amount was zero.";
220 JLOG(
j_.
debug()) <<
"Tried to send to same issuer";
237 auto issuer = currencyIsXRP ?
AccountID() : account;
241 JLOG(
j_.
trace()) <<
"findPaths>"
246 <<
" mSrcIssuer=" << issuerString;
250 JLOG(
j_.
debug()) <<
"findPaths< no ledger";
260 JLOG(
j_.
debug()) <<
"invalid source account";
267 JLOG(
j_.
debug()) <<
"Non-existent gateway";
277 JLOG(
j_.
debug()) <<
"New account not being funded in XRP ";
285 <<
"New account not getting enough funding: " <<
mDstAmount
294 if (bSrcXrp && bDstXrp)
297 JLOG(
j_.
debug()) <<
"XRP to XRP payment";
303 JLOG(
j_.
debug()) <<
"XRP to non-XRP payment";
309 JLOG(
j_.
debug()) <<
"non-XRP to XRP payment";
315 JLOG(
j_.
debug()) <<
"non-XRP to non-XRP - same currency";
321 JLOG(
j_.
debug()) <<
"non-XRP to non-XRP - cross currency";
326 for (
auto const& costedPath : mPathTable[paymentType])
328 if (continueCallback && !continueCallback())
331 if (costedPath.searchLevel <= searchLevel)
333 JLOG(
j_.
trace()) <<
"findPaths trying payment type " << paymentType;
354 uint64_t& qualityOut)
const
384 qualityOut =
getRate(rc.actualAmountOut, rc.actualAmountIn);
385 amountOut = rc.actualAmountOut;
394 mDstAmount - amountOut,
404 amountOut += rc.actualAmountOut;
411 JLOG(j_.
info()) <<
"checkpath: exception (" << e.
what() <<
") "
445 <<
"Default path contributes: " << rc.actualAmountIn;
451 <<
"Default path fails: " <<
transToken(rc.result());
456 JLOG(
j_.
debug()) <<
"Default path causes exception";
478 return path.size() == 1;
488 for (
auto it = path.begin() + 1; it != path.end(); ++it)
503 JLOG(
j_.
trace()) <<
"rankPaths with " << paths.
size() <<
" candidates, and "
504 << maxPaths <<
" maximum";
508 auto const saMinDstAmount = [&]() ->
STAmount {
512 return smallestUsefulAmount(
mDstAmount, maxPaths);
520 for (
int i = 0; i < paths.
size(); ++i)
522 if (continueCallback && !continueCallback())
524 auto const& currentPath = paths[i];
525 if (!currentPath.empty())
530 currentPath, saMinDstAmount, liquidity, uQuality);
534 <<
"findPaths: dropping : " <<
transToken(resultCode)
539 JLOG(
j_.
debug()) <<
"findPaths: quality: " << uQuality <<
": "
543 {uQuality, currentPath.size(), liquidity, i});
558 if (!convert_all_ && a.quality != b.quality)
559 return a.quality < b.quality;
562 if (a.liquidity != b.liquidity)
563 return a.liquidity > b.liquidity;
566 if (a.length != b.length)
567 return a.length < b.length;
570 return a.index > b.index;
577 STPath& fullLiquidityPath,
583 << extraPaths.
size() <<
" extras";
589 fullLiquidityPath.
empty(),
590 "ripple::Pathfinder::getBestPaths : first empty path result");
591 bool const issuerIsSender =
595 rankPaths(maxPaths, extraPaths, extraPathRanks, continueCallback);
605 auto extraPathsIterator = extraPathRanks.
begin();
608 extraPathsIterator != extraPathRanks.
end())
610 if (continueCallback && !continueCallback())
612 bool usePath =
false;
613 bool useExtraPath =
false;
617 else if (extraPathsIterator == extraPathRanks.
end())
619 else if (extraPathsIterator->quality < pathsIterator->quality)
621 else if (extraPathsIterator->quality > pathsIterator->quality)
623 else if (extraPathsIterator->liquidity > pathsIterator->liquidity)
625 else if (extraPathsIterator->liquidity < pathsIterator->liquidity)
634 auto& pathRank = usePath ? *pathsIterator : *extraPathsIterator;
637 : extraPaths[pathRank.index];
640 ++extraPathsIterator;
645 auto iPathsLeft = maxPaths - bestPaths.
size();
646 if (!(iPathsLeft > 0 || fullLiquidityPath.
empty()))
651 UNREACHABLE(
"ripple::Pathfinder::getBestPaths : path not found");
655 bool startsWithIssuer =
false;
657 if (!issuerIsSender && usePath)
660 if (
isDefaultPath(path) || path.front().getAccountID() != srcIssuer)
665 startsWithIssuer =
true;
668 if (iPathsLeft > 1 ||
669 (iPathsLeft > 0 && pathRank.liquidity >= remaining))
673 remaining -= pathRank.liquidity;
677 iPathsLeft == 0 && pathRank.liquidity >=
mDstAmount &&
678 fullLiquidityPath.
empty())
681 fullLiquidityPath = (startsWithIssuer ?
removeIssuer(path) : path);
682 JLOG(
j_.
debug()) <<
"Found extra full path: "
687 JLOG(
j_.
debug()) <<
"Skipping a non-filling path: "
692 if (remaining > beast::zero)
695 fullLiquidityPath.
empty(),
696 "ripple::Pathfinder::getBestPaths : second empty path result");
697 JLOG(
j_.
info()) <<
"Paths could not send " << remaining <<
" of "
702 JLOG(
j_.
debug()) <<
"findPaths: RESULTS: "
716 return matchingCurrency && matchingAccount;
728 Issue const issue(currency, account);
741 int aFlags = sleAccount->getFieldU32(sfFlags);
751 if (
auto const lines =
mRLCache->getRippleLines(account, direction))
753 for (
auto const& rspEntry : *lines)
755 if (currency != rspEntry.getLimit().getCurrency())
759 rspEntry.getBalance() <= beast::zero &&
760 (!rspEntry.getLimitPeer() ||
761 -rspEntry.getBalance() >= rspEntry.getLimitPeer() ||
762 (bAuthRequired && !rspEntry.getAuth())))
766 isDstCurrency && dstAccount == rspEntry.getAccountIDPeer())
770 else if (rspEntry.getNoRipplePeer())
774 else if (rspEntry.getFreezePeer())
796 JLOG(
j_.
debug()) <<
"addLink< on " << currentPaths.
size()
797 <<
" source(s), flags=" << addFlags;
798 for (
auto const& path : currentPaths)
800 if (continueCallback && !continueCallback())
802 addLink(path, incompletePaths, addFlags, continueCallback);
811 JLOG(
j_.
debug()) <<
"addPathsForType "
814 auto it =
mPaths.find(pathType);
819 if (pathType.
empty())
821 if (continueCallback && !continueCallback())
833 JLOG(
j_.
debug()) <<
"getPaths< adding onto '"
834 << pathTypeToString(parentPathType) <<
"' to get '"
835 << pathTypeToString(pathType) <<
"'";
840 auto nodeType = pathType.
back();
847 "ripple::Pathfinder::addPathsForType : empty paths");
890 <<
" complete paths added";
893 JLOG(
j_.
debug()) <<
"getPaths> " << pathsOut.
size()
894 <<
" partial paths found";
910 return sleRipple && (sleRipple->getFieldU32(sfFlags) & flag);
919 if (currentPath.
empty())
930 auto const& fromAccount = (currentPath.
size() == 1)
932 : (currentPath.
end() - 2)->getAccountID();
942 for (
auto const& p : pathSet)
952 STPath const& currentPath,
958 auto const& uEndCurrency = pathEnd.getCurrency();
959 auto const& uEndIssuer = pathEnd.getIssuerID();
960 auto const& uEndAccount = pathEnd.getAccountID();
961 bool const bOnXRP = uEndCurrency.isZero();
968 JLOG(
j_.
trace()) <<
"addLink< flags=" << addFlags <<
" onXRP=" << bOnXRP
979 JLOG(
j_.
trace()) <<
"complete path found ax: "
991 bool const bRequireAuth(
993 bool const bIsEndCurrency(
996 bool const bDestOnly(addFlags &
afAC_LAST);
998 if (
auto const lines =
mRLCache->getRippleLines(
1003 auto& rippleLines = *lines;
1005 AccountCandidates candidates;
1006 candidates.reserve(rippleLines.size());
1008 for (
auto const& rs : rippleLines)
1010 if (continueCallback && !continueCallback())
1012 auto const& acct = rs.getAccountIDPeer();
1015 if (hasEffectiveDestination && (acct ==
mDstAccount))
1023 if (bDestOnly && !bToDestination)
1028 if ((uEndCurrency == rs.getLimit().getCurrency()) &&
1029 !currentPath.
hasSeen(acct, uEndCurrency, acct))
1033 if (rs.getBalance() <= beast::zero &&
1034 (!rs.getLimitPeer() ||
1035 -rs.getBalance() >= rs.getLimitPeer() ||
1036 (bRequireAuth && !rs.getAuth())))
1040 else if (bIsNoRippleOut && rs.getNoRipple())
1044 else if (bToDestination)
1050 if (!currentPath.
empty())
1053 <<
"complete path found ae: "
1060 else if (!bDestOnly)
1063 candidates.push_back(
1064 {AccountCandidate::highPriority, acct});
1082 candidates.push_back({
out, acct});
1087 if (!candidates.empty())
1093 compareAccountCandidate,
1095 std::placeholders::_1,
1096 std::placeholders::_2));
1098 int count = candidates.size();
1102 else if (count > 50)
1105 auto it = candidates.begin();
1106 while (count-- != 0)
1108 if (continueCallback && !continueCallback())
1117 currentPath, pathElement);
1125 JLOG(
j_.
warn()) <<
"Path ends on non-existent issuer";
1137 {uEndCurrency, uEndIssuer},
mDomain))
1144 incompletePaths.
assembleAdd(currentPath, pathElement);
1149 bool bDestOnly = (addFlags &
afOB_LAST) != 0;
1151 {uEndCurrency, uEndIssuer},
mDomain);
1153 << books.size() <<
" books found from this currency/issuer";
1155 for (
auto const& book : books)
1157 if (continueCallback && !continueCallback())
1160 xrpAccount(), book.out.currency, book.out.account) &&
1165 STPath newPath(currentPath);
1167 if (book.out.currency.isZero())
1182 <<
"complete path found bx: "
1189 else if (!currentPath.
hasSeen(
1196 if ((newPath.
size() >= 2) &&
1197 (newPath.
back().isAccount()) &&
1198 (newPath[newPath.
size() - 2].isOffer()))
1219 if (hasEffectiveDestination &&
1231 <<
"complete path found ba: "
1256makePath(
char const*
string)
1299 auto& list = mPathTable[type];
1300 XRPL_ASSERT(list.empty(),
"ripple::fillPaths : empty paths");
1301 for (
auto& cost : costs)
1302 list.push_back({cost.cost, makePath(cost.path)});
Stream trace() const
Severity stream access functions.
virtual OrderBookDB & getOrderBookDB()=0
virtual JobQueue & getJobQueue()=0
A currency issued by an account.
std::unique_ptr< LoadEvent > makeLoadEvent(JobType t, std::string const &name)
Return a scoped LoadEvent.
int getBookSize(Issue const &, std::optional< Domain > const &domain=std::nullopt)
std::vector< Book > getBooksByTakerPays(Issue const &, std::optional< Domain > const &domain=std::nullopt)
bool isBookToXRP(Issue const &, std::optional< Domain > domain=std::nullopt)
bool issueMatchesOrigin(Issue const &)
Pathfinder(std::shared_ptr< RippleLineCache > const &cache, AccountID const &srcAccount, AccountID const &dstAccount, Currency const &uSrcCurrency, std::optional< AccountID > const &uSrcIssuer, STAmount const &dstAmount, std::optional< STAmount > const &srcAmount, std::optional< uint256 > const &domain, Application &app)
Construct a pathfinder without an issuer.
void rankPaths(int maxPaths, STPathSet const &paths, std::vector< PathRank > &rankedPaths, std::function< bool(void)> const &continueCallback)
bool findPaths(int searchLevel, std::function< bool(void)> const &continueCallback={})
std::optional< AccountID > mSrcIssuer
std::unique_ptr< LoadEvent > m_loadEvent
std::shared_ptr< RippleLineCache > mRLCache
TER getPathLiquidity(STPath const &path, STAmount const &minDstAmount, STAmount &amountOut, uint64_t &qualityOut) const
std::map< PathType, STPathSet > mPaths
static std::uint32_t const afADD_ACCOUNTS
void computePathRanks(int maxPaths, std::function< bool(void)> const &continueCallback={})
Compute the rankings of the paths.
STAmount mRemainingAmount
The amount remaining from mSrcAccount after the default liquidity has been removed.
bool isNoRippleOut(STPath const ¤tPath)
void addLinks(STPathSet const ¤tPaths, STPathSet &incompletePaths, int addFlags, std::function< bool(void)> const &continueCallback)
static std::uint32_t const afADD_BOOKS
int getPathsOut(Currency const ¤cy, AccountID const &account, LineDirection direction, bool isDestCurrency, AccountID const &dest, std::function< bool(void)> const &continueCallback)
bool isNoRipple(AccountID const &fromAccount, AccountID const &toAccount, Currency const ¤cy)
STPathSet & addPathsForType(PathType const &type, std::function< bool(void)> const &continueCallback)
static std::uint32_t const afOB_XRP
static void initPathTable()
hash_map< Issue, int > mPathsOutCountMap
std::vector< NodeType > PathType
std::vector< PathRank > mPathRanks
std::optional< uint256 > mDomain
void addLink(STPath const ¤tPath, STPathSet &incompletePaths, int addFlags, std::function< bool(void)> const &continueCallback)
STPathSet getBestPaths(int maxPaths, STPath &fullLiquidityPath, STPathSet const &extraPaths, AccountID const &srcIssuer, std::function< bool(void)> const &continueCallback={})
static std::uint32_t const afAC_LAST
std::shared_ptr< ReadView const > mLedger
static std::uint32_t const afOB_LAST
A wrapper which makes credits unavailable to balances.
Currency const & getCurrency() const
std::string getFullText() const override
bool native() const noexcept
Currency const & getCurrency() const
AccountID const & getAccountID() const
void push_back(STPath const &e)
bool assembleAdd(STPath const &base, STPathElement const &tail)
Json::Value getJson(JsonOptions) const override
std::vector< STPath >::size_type size() const
std::vector< STPathElement >::const_iterator end() const
Json::Value getJson(JsonOptions) const
void push_back(STPathElement const &e)
bool hasSeen(AccountID const &account, Currency const ¤cy, AccountID const &issuer) const
std::vector< STPathElement >::size_type size() const
std::vector< STPathElement >::const_reference back() const
void emplace_back(Args &&... args)
static Output rippleCalculate(PaymentSandbox &view, STAmount const &saMaxAmountReq, STAmount const &saDstAmountReq, AccountID const &uDstAccountID, AccountID const &uSrcAccountID, STPathSet const &spsPaths, std::optional< uint256 > const &domainID, Logs &l, Input const *const pInputs=nullptr)
Keylet line(AccountID const &id0, AccountID const &id1, Currency const ¤cy) noexcept
The index of a trust line for a given currency.
Keylet account(AccountID const &id) noexcept
AccountID root.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
base_uint< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
STAmount divide(STAmount const &amount, Rate const &rate)
STAmount convertAmount(STAmount const &amt, bool all)
bool isXRP(AccountID const &c)
AccountID const & xrpAccount()
Compute AccountID from public key.
bool convertAllCheck(STAmount const &a)
static bool isDefaultPath(STPath const &path)
std::uint64_t getRate(STAmount const &offerOut, STAmount const &offerIn)
static STPath removeIssuer(STPath const &path)
std::string transToken(TER code)
Currency const & xrpCurrency()
XRP currency.
std::string to_string(base_uint< Bits, Tag > const &a)
STAmount largestAmount(STAmount const &amt)
void addUniquePath(STPathSet &pathSet, STPath const &path)
LineDirection
Describes how an account was found in a path, and how to find the next set of paths.