1#include <xrpld/rpc/detail/Pathfinder.h>
3#include <xrpld/app/main/Application.h>
4#include <xrpld/rpc/detail/AssetCache.h>
5#include <xrpld/rpc/detail/PathfinderUtils.h>
6#include <xrpld/rpc/detail/TrustLine.h>
8#include <xrpl/basics/Log.h>
9#include <xrpl/basics/base_uint.h>
10#include <xrpl/basics/join.h>
11#include <xrpl/beast/utility/Zero.h>
12#include <xrpl/beast/utility/instrumentation.h>
13#include <xrpl/core/Job.h>
14#include <xrpl/core/JobQueue.h>
15#include <xrpl/json/to_string.h>
16#include <xrpl/ledger/ApplyView.h>
17#include <xrpl/ledger/OrderBookDB.h>
18#include <xrpl/ledger/PaymentSandbox.h>
19#include <xrpl/ledger/helpers/MPTokenHelpers.h>
20#include <xrpl/protocol/AccountID.h>
21#include <xrpl/protocol/Asset.h>
22#include <xrpl/protocol/Indexes.h>
23#include <xrpl/protocol/LedgerFormats.h>
24#include <xrpl/protocol/MPTIssue.h>
25#include <xrpl/protocol/PathAsset.h>
26#include <xrpl/protocol/SField.h>
27#include <xrpl/protocol/STAmount.h>
28#include <xrpl/protocol/STPathSet.h>
29#include <xrpl/protocol/TER.h>
30#include <xrpl/protocol/UintTypes.h>
31#include <xrpl/tx/paths/RippleCalc.h>
49 return os << static_cast<int>(t);
54 return os << static_cast<int>(t);
96constexpr std::size_t kPathfinderMaxCompletePaths = 1000;
98struct AccountCandidate
103 static int const kHighPriority = 10000;
107compareAccountCandidate(
109 AccountCandidate
const& first,
110 AccountCandidate
const& second)
113 if (first.priority != second.priority)
114 return first.priority > second.priority;
117 if (first.account != second.account)
118 return first.account > second.account;
122 return (first.priority ^ seq) < (second.priority ^ seq);
125using AccountCandidates = std::vector<AccountCandidate>;
133using CostedPathList = std::vector<CostedPath>;
135using PathTable = std::map<Pathfinder::PaymentType, CostedPathList>;
142using PathCostList = std::vector<PathCost>;
151 for (
auto const& node : type)
182smallestUsefulAmount(
STAmount const& amount,
int maxPaths)
190 std::optional<AccountID>
const& srcIssuer,
193 return pathAsset.visit(
198 [](
MPTID const& mpt) {
return STAmount(mpt, 1u, 0,
true); });
204 return pathAsset.
visit(
223 ,
effectiveDst_(
isXRP(saDstAmount.getIssuer()) ? uDstAccount : saDstAmount.getIssuer())
227 ,
srcAmount_(amountFromPathAsset(uSrcPathAsset, uSrcIssuer, uSrcAccount))
233 ,
j_(app.getJournal(
"Pathfinder"))
237 "xrpl::Pathfinder::Pathfinder : valid inputs");
243 JLOG(
j_.trace()) <<
"findPaths start";
247 JLOG(
j_.debug()) <<
"Destination amount was zero.";
259 JLOG(
j_.debug()) <<
"Tried to send to same issuer";
275 auto issuer = currencyIsXRP ?
AccountID() : account;
278 JLOG(
j_.trace()) <<
"findPaths>"
281 <<
" srcPathAsset_=" <<
srcPathAsset_ <<
" srcIssuer_=" << issuerString;
285 JLOG(
j_.debug()) <<
"findPaths< no ledger";
295 JLOG(
j_.debug()) <<
"invalid source account";
301 JLOG(
j_.debug()) <<
"Non-existent gateway";
311 JLOG(
j_.debug()) <<
"New account not being funded in XRP ";
318 JLOG(
j_.debug()) <<
"New account not getting enough funding: " <<
dstAmount_ <<
" < "
327 if (bSrcXrp && bDstXrp)
330 JLOG(
j_.debug()) <<
"XRP to XRP payment";
336 JLOG(
j_.debug()) <<
"XRP to non-XRP payment";
342 JLOG(
j_.debug()) <<
"non-XRP to XRP payment";
348 JLOG(
j_.debug()) <<
"non-XRP to non-XRP - same currency";
354 JLOG(
j_.debug()) <<
"non-XRP to non-XRP - cross currency";
359 for (
auto const& costedPath : gPathTable[paymentType])
361 if (continueCallback && !continueCallback())
364 if (costedPath.searchLevel <= searchLevel)
366 JLOG(
j_.trace()) <<
"findPaths trying payment type " << paymentType;
387 uint64_t& qualityOut)
const
417 qualityOut =
getRate(rc.actualAmountOut, rc.actualAmountIn);
418 amountOut = rc.actualAmountOut;
437 amountOut += rc.actualAmountOut;
444 JLOG(
j_.info()) <<
"checkpath: exception (" << e.
what() <<
") "
475 JLOG(
j_.debug()) <<
"Default path contributes: " << rc.actualAmountIn;
480 JLOG(
j_.debug()) <<
"Default path fails: " <<
transToken(rc.result());
485 JLOG(
j_.debug()) <<
"Default path causes exception";
507 return path.size() == 1;
517 for (
auto it =
path.begin() + 1; it !=
path.end(); ++it)
532 JLOG(
j_.trace()) <<
"rankPaths with " << paths.
size() <<
" candidates, and " << maxPaths
537 auto const saMinDstAmount = [&]() ->
STAmount {
541 return smallestUsefulAmount(
dstAmount_, maxPaths);
549 for (
int i = 0; i < paths.
size(); ++i)
551 if (continueCallback && !continueCallback())
553 auto const& currentPath = paths[i];
554 if (!currentPath.empty())
557 uint64_t uQuality = 0;
558 auto const resultCode =
562 JLOG(
j_.debug()) <<
"findPaths: dropping : " <<
transToken(resultCode) <<
": "
567 JLOG(
j_.debug()) <<
"findPaths: quality: " << uQuality <<
": "
571 {.quality = uQuality,
572 .length = currentPath.size(),
573 .liquidity = liquidity,
606 STPath& fullLiquidityPath,
611 JLOG(
j_.debug()) <<
"findPaths: " <<
completePaths_.size() <<
" paths and " << extraPaths.
size()
618 fullLiquidityPath.
empty(),
"xrpl::Pathfinder::getBestPaths : first empty path result");
622 rankPaths(maxPaths, extraPaths, extraPathRanks, continueCallback);
632 auto extraPathsIterator = extraPathRanks.
begin();
634 while (pathsIterator !=
pathRanks_.end() || extraPathsIterator != extraPathRanks.
end())
636 if (continueCallback && !continueCallback())
638 bool usePath =
false;
639 bool useExtraPath =
false;
645 else if (extraPathsIterator == extraPathRanks.
end())
649 else if (extraPathsIterator->quality != pathsIterator->quality)
652 useExtraPath = extraPathsIterator->quality < pathsIterator->quality;
653 usePath = !useExtraPath;
655 else if (extraPathsIterator->liquidity != pathsIterator->liquidity)
658 useExtraPath = extraPathsIterator->liquidity > pathsIterator->liquidity;
659 usePath = !useExtraPath;
668 auto& pathRank = usePath ? *pathsIterator : *extraPathsIterator;
670 auto const&
path = usePath ?
completePaths_[pathRank.index] : extraPaths[pathRank.index];
673 ++extraPathsIterator;
678 auto iPathsLeft = maxPaths - bestPaths.
size();
679 if (iPathsLeft <= 0 && !fullLiquidityPath.
empty())
685 UNREACHABLE(
"xrpl::Pathfinder::getBestPaths : path not found");
690 bool startsWithIssuer =
false;
692 if (!issuerIsSender && usePath)
700 startsWithIssuer =
true;
703 if (iPathsLeft > 1 || (iPathsLeft > 0 && pathRank.liquidity >= remaining))
707 remaining -= pathRank.liquidity;
710 else if (iPathsLeft == 0 && pathRank.liquidity >=
dstAmount_ && fullLiquidityPath.
empty())
714 JLOG(
j_.debug()) <<
"Found extra full path: "
719 JLOG(
j_.debug()) <<
"Skipping a non-filling path: "
724 if (remaining > beast::kZero)
727 fullLiquidityPath.
empty(),
"xrpl::Pathfinder::getBestPaths : second empty path result");
728 JLOG(
j_.info()) <<
"Paths could not send " << remaining <<
" of " <<
dstAmount_;
744 return matchingAsset && matchingAccount;
756 Asset const asset = assetFromPathAsset(pathAsset, account);
769 auto const aFlags = sleAccount->getFieldU32(sfFlags);
770 bool const bAuthRequired = [&]() {
772 return (aFlags & lsfRequireAuth) != 0;
775 bool const bFrozen = [&]() {
777 return (aFlags & lsfGlobalFreeze) != 0;
785 count =
app_.getOrderBookDB().getBookSize(asset,
domain_);
789 if (
auto const lines =
rLCache_->getRippleLines(account, direction))
791 for (
auto const& rspEntry : *lines)
795 if (rspEntry.getBalance() <= beast::kZero &&
796 (!rspEntry.getLimitPeer() ||
797 -rspEntry.getBalance() >= rspEntry.getLimitPeer() ||
798 (bAuthRequired && !rspEntry.getAuth())))
800 if (isDstAsset && dstAccount == rspEntry.getAccountIDPeer())
805 if (rspEntry.getNoRipplePeer())
807 if (rspEntry.getFreezePeer())
814 if (
auto const mpts =
rLCache_->getMPTs(account))
816 for (
auto const& mpt : *mpts)
818 if (pathAsset.
get<
MPTID>() != mpt.getMptID() || mpt.isZeroBalance() ||
819 mpt.isMaxedOut() || bAuthRequired)
844 JLOG(
j_.debug()) <<
"addLink< on " << currentPaths.
size() <<
" source(s), flags=" << addFlags;
845 for (
auto const&
path : currentPaths)
847 if (continueCallback && !continueCallback())
849 addLink(
path, incompletePaths, addFlags, continueCallback);
860 auto it =
paths_.find(pathType);
865 if (pathType.
empty())
867 if (continueCallback && !continueCallback())
878 JLOG(
j_.debug()) <<
"getPaths< adding onto '" << pathTypeToString(parentPathType)
879 <<
"' to get '" << pathTypeToString(pathType) <<
"'";
884 auto nodeType = pathType.
back();
889 XRPL_ASSERT(pathsOut.
empty(),
"xrpl::Pathfinder::addPathsForType : empty paths");
919 JLOG(
j_.debug()) << (
completePaths_.size() - initialSize) <<
" complete paths added";
922 JLOG(
j_.debug()) <<
"getPaths> " << pathsOut.
size() <<
" partial paths found";
934 auto const flag((toAccount > fromAccount) ? lsfHighNoRipple : lsfLowNoRipple);
936 return sleRipple && sleRipple->isFlag(flag);
945 if (currentPath.
empty())
956 auto const& fromAccount =
967 for (
auto const& p : pathSet)
977 STPath const& currentPath,
983 auto const& uEndPathAsset = pathEnd.getPathAsset();
984 auto const& uEndIssuer = pathEnd.getIssuerID();
985 auto const& uEndAccount = pathEnd.getAccountID();
986 bool const bOnXRP =
isXRP(uEndPathAsset);
993 JLOG(
j_.trace()) <<
"addLink< flags=" << addFlags <<
" onXRP=" << bOnXRP
1004 JLOG(
j_.trace()) <<
"complete path found ax: "
1016 bool const bRequireAuth(sleEnd->isFlag(lsfRequireAuth));
1017 bool const bIsEndAsset(uEndPathAsset ==
dstAmount_.asset());
1019 bool const bDestOnly((addFlags &
kAfAcLast) != 0u);
1021 AccountCandidates candidates;
1024 candidates.reserve(assets.size());
1026 static constexpr bool kIsLine =
1028 static constexpr bool kIsMpt =
1031 for (
auto const& asset : assets)
1033 if (continueCallback && !continueCallback())
1035 auto const& acct = [&]()
constexpr {
1036 if constexpr (kIsLine)
1037 return asset.getAccountIDPeer();
1039 if constexpr (kIsMpt)
1043 if constexpr (kIsLine)
1044 return asset.getDirectionPeer();
1050 if (hasEffectiveDestination && (acct ==
dstAccount_))
1058 if (bDestOnly && !bToDestination)
1063 auto const correctAsset = [&]() {
1064 if constexpr (kIsLine)
1066 return uEndPathAsset.get<
Currency>() ==
1067 asset.getLimit().template
get<Issue>().currency;
1069 if constexpr (kIsMpt)
1071 return uEndPathAsset.get<
MPTID>() == asset.getMptID();
1074 auto checkAsset = [&]() {
1075 if constexpr (kIsLine)
1078 (asset.getBalance() <= beast::kZero &&
1079 (!asset.getLimitPeer() ||
1080 -asset.getBalance() >= asset.getLimitPeer() ||
1081 (bRequireAuth && !asset.getAuth()))) ||
1082 (bIsNoRippleOut && asset.getNoRipple()));
1084 if constexpr (kIsMpt)
1086 return asset.isZeroBalance() || asset.isMaxedOut() ||
1091 if (correctAsset && !currentPath.
hasSeen(acct, uEndPathAsset, acct))
1106 if (!currentPath.
empty())
1109 <<
"complete path found ae: "
1114 else if (!bDestOnly)
1117 candidates.push_back({AccountCandidate::kHighPriority, acct});
1135 candidates.push_back({out, acct});
1141 uEndPathAsset.visit(
1143 if (
auto const lines =
rLCache_->getRippleLines(
1151 if (
auto const mpts =
rLCache_->getMPTs(uEndAccount))
1157 if (!candidates.empty())
1162 compareAccountCandidate,
1164 std::placeholders::_1,
1165 std::placeholders::_2));
1167 int count = candidates.size();
1173 else if (count > 50)
1178 auto it = candidates.begin();
1179 while (count-- != 0)
1181 if (continueCallback && !continueCallback())
1186 incompletePaths.
assembleAdd(currentPath, pathElement);
1193 JLOG(
j_.warn()) <<
"Path ends on non-existent issuer";
1204 app_.getOrderBookDB().isBookToXRP(
1205 assetFromPathAsset(uEndPathAsset, uEndIssuer),
domain_))
1209 incompletePaths.
assembleAdd(currentPath, pathElement);
1214 bool const bDestOnly = (addFlags &
kAfObLast) != 0;
1215 auto books =
app_.getOrderBookDB().getBooksByTakerPays(
1216 assetFromPathAsset(uEndPathAsset, uEndIssuer),
domain_);
1217 JLOG(
j_.trace()) << books.size() <<
" books found from this currency/issuer";
1219 for (
auto const& book : books)
1221 if (continueCallback && !continueCallback())
1227 STPath newPath(currentPath);
1229 if (
isXRP(book.out))
1240 JLOG(
j_.trace()) <<
"complete path found bx: "
1249 else if (!currentPath.
hasSeen(
1250 book.out.getIssuer(), book.out, book.out.getIssuer()))
1256 if ((newPath.
size() >= 2) && (newPath.
back().isAccount()) &&
1257 (newPath[newPath.
size() - 2].isOffer()))
1264 book.out.getIssuer());
1273 book.out.getIssuer());
1276 if (hasEffectiveDestination && book.out.getIssuer() ==
dstAccount_ &&
1286 JLOG(
j_.trace()) <<
"complete path found ba: "
1297 book.out.getIssuer(),
1299 book.out.getIssuer()));
1311makePath(
char const*
string)
1355 auto& list = gPathTable[type];
1356 XRPL_ASSERT(list.empty(),
"xrpl::fillPaths : empty paths");
1357 for (
auto& cost : costs)
1358 list.push_back({.searchLevel = cost.cost, .type = makePath(cost.path)});
1381 {{.cost = 1, .path =
"sfd"},
1382 {.cost = 3, .path =
"sfad"},
1383 {.cost = 5, .path =
"sfaad"},
1384 {.cost = 6, .path =
"sbfd"},
1385 {.cost = 8, .path =
"sbafd"},
1386 {.cost = 9, .path =
"sbfad"},
1387 {.cost = 10, .path =
"sbafad"}});
1391 {{.cost = 1, .path =
"sxd"},
1392 {.cost = 2, .path =
"saxd"},
1393 {.cost = 6, .path =
"saaxd"},
1394 {.cost = 7, .path =
"sbxd"},
1395 {.cost = 8, .path =
"sabxd"},
1396 {.cost = 9, .path =
"sabaxd"}});
1402 {.cost = 1, .path =
"sad"},
1403 {.cost = 1, .path =
"sfd"},
1404 {.cost = 4, .path =
"safd"},
1405 {.cost = 4, .path =
"sfad"},
1406 {.cost = 5, .path =
"saad"},
1407 {.cost = 5, .path =
"sbfd"},
1408 {.cost = 6, .path =
"sxfad"},
1409 {.cost = 6, .path =
"safad"},
1410 {.cost = 6, .path =
"saxfd"},
1412 {.cost = 6, .path =
"saxfad"},
1413 {.cost = 6, .path =
"sabfd"},
1414 {.cost = 7, .path =
"saaad"},
1421 {.cost = 1, .path =
"sfad"},
1422 {.cost = 1, .path =
"safd"},
1423 {.cost = 3, .path =
"safad"},
1424 {.cost = 4, .path =
"sxfd"},
1425 {.cost = 5, .path =
"saxfd"},
1426 {.cost = 5, .path =
"sxfad"},
1427 {.cost = 5, .path =
"sbfd"},
1428 {.cost = 6, .path =
"saxfad"},
1429 {.cost = 6, .path =
"sabfd"},
1430 {.cost = 7, .path =
"saafd"},
1431 {.cost = 8, .path =
"saafad"},
1432 {.cost = 9, .path =
"safaad"},
constexpr auto visit(Visitors &&... visitors) const -> decltype(auto)
constexpr TIss const & get() const
AccountID const & getIssuer() const
A currency issued by an account.
constexpr bool isXRP() const
constexpr bool holds() const
static std::uint32_t const kAfObLast
void addLinks(STPathSet const ¤tPaths, STPathSet &incompletePaths, int addFlags, std::function< bool(void)> const &continueCallback)
void rankPaths(int maxPaths, STPathSet const &paths, std::vector< PathRank > &rankedPaths, std::function< bool(void)> const &continueCallback)
TER getPathLiquidity(STPath const &path, STAmount const &minDstAmount, STAmount &amountOut, uint64_t &qualityOut) const
Pathfinder(std::shared_ptr< AssetCache > const &cache, AccountID const &srcAccount, AccountID const &dstAccount, PathAsset const &uSrcPathAsset, 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.
hash_map< Asset, int > pathsOutCountMap_
bool issueMatchesOrigin(Asset const &)
static std::uint32_t const kAfAddAccounts
STPathSet getBestPaths(int maxPaths, STPath &fullLiquidityPath, STPathSet const &extraPaths, AccountID const &srcIssuer, std::function< bool(void)> const &continueCallback={})
void addLink(STPath const ¤tPath, STPathSet &incompletePaths, int addFlags, std::function< bool(void)> const &continueCallback)
std::vector< NodeType > PathType
std::unique_ptr< LoadEvent > loadEvent_
static std::uint32_t const kAfObXrp
std::optional< AccountID > srcIssuer_
std::map< PathType, STPathSet > paths_
std::optional< uint256 > domain_
static std::uint32_t const kAfAcLast
std::shared_ptr< AssetCache > rLCache_
STPathSet & addPathsForType(PathType const &type, std::function< bool(void)> const &continueCallback)
bool isNoRipple(AccountID const &fromAccount, AccountID const &toAccount, Currency const ¤cy)
bool findPaths(int searchLevel, std::function< bool(void)> const &continueCallback={})
bool isNoRippleOut(STPath const ¤tPath)
static void initPathTable()
STAmount remainingAmount_
The amount remaining from srcAccount_ after the default liquidity has been removed.
static std::uint32_t const kAfAddBooks
int getPathsOut(PathAsset const &pathAsset, AccountID const &account, LineDirection direction, bool isDestPathAsset, AccountID const &dest, std::function< bool(void)> const &continueCallback)
std::vector< PathRank > pathRanks_
std::shared_ptr< ReadView const > ledger_
void computePathRanks(int maxPaths, std::function< bool(void)> const &continueCallback={})
Compute the rankings of the paths.
A wrapper which makes credits unavailable to balances.
Asset const & asset() const
AccountID const & getAccountID() const
Currency const & getCurrency() const
bool assembleAdd(STPath const &base, STPathElement const &tail)
std::vector< STPath >::size_type size() const
json::Value getJson(JsonOptions) const override
void pushBack(STPath const &e)
std::vector< STPathElement >::size_type size() const
bool hasSeen(AccountID const &account, PathAsset const &asset, AccountID const &issuer) const
void pushBack(STPathElement const &e)
std::vector< STPathElement >::const_iterator end() const
void emplaceBack(Args &&... args)
std::vector< STPathElement >::const_reference back() const
json::Value getJson(JsonOptions) const
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, ServiceRegistry ®istry, Input const *const pInputs=nullptr)
Keylet account(AccountID const &id) noexcept
AccountID root.
Keylet trustLine(AccountID const &id0, AccountID const &id1, Currency const ¤cy) noexcept
The index of a trust line for a given currency.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
STAmount divide(STAmount const &amount, Rate const &rate)
STAmount convertAmount(STAmount const &amt, bool all)
bool isXRP(AccountID const &c)
bool isIndividualFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptIssue)
T get(Section const §ion, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
STAmount largestAmount(STAmount const &amt)
BaseUInt< 160, detail::CurrencyTag > Currency
Currency is a hash representing a specific currency.
std::ostream & operator<<(std::ostream &out, BaseUInt< Bits, Tag > const &u)
static STPath removeIssuer(STPath const &path)
AccountID getMPTIssuer(MPTID const &mptid)
std::string transToken(TER code)
Currency const & xrpCurrency()
XRP currency.
std::string to_string(BaseUInt< Bits, Tag > const &a)
bool isGlobalFrozen(ReadView const &view, AccountID const &issuer)
Check if the issuer has the global freeze flag set.
static bool isDefaultPath(STPath const &path)
LineDirection
Describes how an account was found in a path, and how to find the next set of paths.
BaseUInt< 192 > MPTID
MPTID is a 192-bit value representing MPT Issuance ID, which is a concatenation of a 32-bit sequence ...
std::uint64_t getRate(STAmount const &offerOut, STAmount const &offerIn)
bool convertAllCheck(STAmount const &a)
void addUniquePath(STPathSet &pathSet, STPath const &path)
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
bool isTesSuccess(TER x) noexcept
TERSubset< CanCvtToTER > TER
TER requireAuth(ReadView const &view, MPTIssue const &mptIssue, AccountID const &account, AuthType authType=AuthType::Legacy, std::uint8_t depth=0)
Check if the account lacks required authorization for MPT.
AccountID const & xrpAccount()
Compute AccountID from public key.
constexpr bool equalTokens(Asset const &lhs, Asset const &rhs)