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> 
   28#include <xrpl/basics/Log.h> 
   29#include <xrpl/basics/join.h> 
   30#include <xrpl/json/to_string.h> 
   31#include <xrpl/ledger/PaymentSandbox.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()))
 
  652            UNREACHABLE(
"ripple::Pathfinder::getBestPaths : path not found");
 
  657        bool startsWithIssuer = 
false;
 
  659        if (!issuerIsSender && usePath)
 
  662            if (
isDefaultPath(path) || path.front().getAccountID() != srcIssuer)
 
  667            startsWithIssuer = 
true;
 
  670        if (iPathsLeft > 1 ||
 
  671            (iPathsLeft > 0 && pathRank.liquidity >= remaining))
 
  675            remaining -= pathRank.liquidity;
 
  679            iPathsLeft == 0 && pathRank.liquidity >= 
mDstAmount &&
 
  680            fullLiquidityPath.
empty())
 
  683            fullLiquidityPath = (startsWithIssuer ? 
removeIssuer(path) : path);
 
  684            JLOG(
j_.
debug()) << 
"Found extra full path: " 
  689            JLOG(
j_.
debug()) << 
"Skipping a non-filling path: " 
  694    if (remaining > beast::zero)
 
  697            fullLiquidityPath.
empty(),
 
  698            "ripple::Pathfinder::getBestPaths : second empty path result");
 
  699        JLOG(
j_.
info()) << 
"Paths could not send " << remaining << 
" of " 
  704        JLOG(
j_.
debug()) << 
"findPaths: RESULTS: " 
 
  718    return matchingCurrency && matchingAccount;
 
 
  730    Issue const issue(currency, account);
 
  743    int aFlags = sleAccount->getFieldU32(sfFlags);
 
  753        if (
auto const lines = 
mRLCache->getRippleLines(account, direction))
 
  755            for (
auto const& rspEntry : *lines)
 
  757                if (currency != rspEntry.getLimit().getCurrency())
 
  761                    rspEntry.getBalance() <= beast::zero &&
 
  762                    (!rspEntry.getLimitPeer() ||
 
  763                     -rspEntry.getBalance() >= rspEntry.getLimitPeer() ||
 
  764                     (bAuthRequired && !rspEntry.getAuth())))
 
  768                    isDstCurrency && dstAccount == rspEntry.getAccountIDPeer())
 
  772                else if (rspEntry.getNoRipplePeer())
 
  776                else if (rspEntry.getFreezePeer())
 
 
  798    JLOG(
j_.
debug()) << 
"addLink< on " << currentPaths.
size()
 
  799                     << 
" source(s), flags=" << addFlags;
 
  800    for (
auto const& path : currentPaths)
 
  802        if (continueCallback && !continueCallback())
 
  804        addLink(path, incompletePaths, addFlags, continueCallback);
 
 
  813    JLOG(
j_.
debug()) << 
"addPathsForType " 
  816    auto it = 
mPaths.find(pathType);
 
  821    if (pathType.
empty())
 
  823    if (continueCallback && !continueCallback())
 
  835    JLOG(
j_.
debug()) << 
"getPaths< adding onto '" 
  836                     << pathTypeToString(parentPathType) << 
"' to get '" 
  837                     << pathTypeToString(pathType) << 
"'";
 
  842    auto nodeType = pathType.
back();
 
  849                "ripple::Pathfinder::addPathsForType : empty paths");
 
  892                         << 
" complete paths added";
 
  895    JLOG(
j_.
debug()) << 
"getPaths> " << pathsOut.
size()
 
  896                     << 
" partial paths found";
 
 
  912    return sleRipple && (sleRipple->getFieldU32(sfFlags) & flag);
 
 
  921    if (currentPath.
empty())
 
  932    auto const& fromAccount = (currentPath.
size() == 1)
 
  934        : (currentPath.
end() - 2)->getAccountID();
 
 
  944    for (
auto const& p : pathSet)
 
 
  954    STPath const& currentPath,   
 
  960    auto const& uEndCurrency = pathEnd.getCurrency();
 
  961    auto const& uEndIssuer = pathEnd.getIssuerID();
 
  962    auto const& uEndAccount = pathEnd.getAccountID();
 
  963    bool const bOnXRP = uEndCurrency.isZero();
 
  970    JLOG(
j_.
trace()) << 
"addLink< flags=" << addFlags << 
" onXRP=" << bOnXRP
 
  981                JLOG(
j_.
trace()) << 
"complete path found ax: " 
  993                bool const bRequireAuth(
 
  995                bool const bIsEndCurrency(
 
  998                bool const bDestOnly(addFlags & 
afAC_LAST);
 
 1000                if (
auto const lines = 
mRLCache->getRippleLines(
 
 1005                    auto& rippleLines = *lines;
 
 1007                    AccountCandidates candidates;
 
 1008                    candidates.reserve(rippleLines.size());
 
 1010                    for (
auto const& rs : rippleLines)
 
 1012                        if (continueCallback && !continueCallback())
 
 1014                        auto const& acct = rs.getAccountIDPeer();
 
 1017                        if (hasEffectiveDestination && (acct == 
mDstAccount))
 
 1025                        if (bDestOnly && !bToDestination)
 
 1030                        if ((uEndCurrency == rs.getLimit().getCurrency()) &&
 
 1031                            !currentPath.
hasSeen(acct, uEndCurrency, acct))
 
 1035                            if (rs.getBalance() <= beast::zero &&
 
 1036                                (!rs.getLimitPeer() ||
 
 1037                                 -rs.getBalance() >= rs.getLimitPeer() ||
 
 1038                                 (bRequireAuth && !rs.getAuth())))
 
 1042                            else if (bIsNoRippleOut && rs.getNoRipple())
 
 1046                            else if (bToDestination)
 
 1052                                    if (!currentPath.
empty())
 
 1055                                            << 
"complete path found ae: " 
 1062                                else if (!bDestOnly)
 
 1065                                    candidates.push_back(
 
 1066                                        {AccountCandidate::highPriority, acct});
 
 1084                                    candidates.push_back({
out, acct});
 
 1089                    if (!candidates.empty())
 
 1095                                compareAccountCandidate,
 
 1097                                std::placeholders::_1,
 
 1098                                std::placeholders::_2));
 
 1100                        int count = candidates.size();
 
 1104                        else if (count > 50)
 
 1107                        auto it = candidates.begin();
 
 1108                        while (count-- != 0)
 
 1110                            if (continueCallback && !continueCallback())
 
 1119                                currentPath, pathElement);
 
 1127                JLOG(
j_.
warn()) << 
"Path ends on non-existent issuer";
 
 1139                    {uEndCurrency, uEndIssuer}, 
mDomain))
 
 1146                incompletePaths.
assembleAdd(currentPath, pathElement);
 
 1151            bool bDestOnly = (addFlags & 
afOB_LAST) != 0;
 
 1153                {uEndCurrency, uEndIssuer}, 
mDomain);
 
 1155                << books.size() << 
" books found from this currency/issuer";
 
 1157            for (
auto const& book : books)
 
 1159                if (continueCallback && !continueCallback())
 
 1162                        xrpAccount(), book.out.currency, book.out.account) &&
 
 1167                    STPath newPath(currentPath);
 
 1169                    if (book.out.currency.isZero())
 
 1184                                << 
"complete path found bx: " 
 1191                    else if (!currentPath.
hasSeen(
 
 1198                        if ((newPath.
size() >= 2) &&
 
 1199                            (newPath.
back().isAccount()) &&
 
 1200                            (newPath[newPath.
size() - 2].isOffer()))
 
 1221                        if (hasEffectiveDestination &&
 
 1233                                << 
"complete path found ba: " 
 
 1258makePath(
char const* 
string)
 
 1301    auto& list = mPathTable[type];
 
 1302    XRPL_ASSERT(list.empty(), 
"ripple::fillPaths : empty paths");
 
 1303    for (
auto& cost : costs)
 
 1304        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.