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: "
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)
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 =
973 STPath const& currentPath,
979 auto const& uEndPathAsset = pathEnd.getPathAsset();
980 auto const& uEndIssuer = pathEnd.getIssuerID();
981 auto const& uEndAccount = pathEnd.getAccountID();
982 bool const bOnXRP =
isXRP(uEndPathAsset);
989 JLOG(
j_.trace()) <<
"addLink< flags=" << addFlags <<
" onXRP=" << bOnXRP
1000 JLOG(
j_.trace()) <<
"complete path found ax: "
1012 bool const bRequireAuth(sleEnd->isFlag(lsfRequireAuth));
1013 bool const bIsEndAsset(uEndPathAsset ==
dstAmount_.asset());
1015 bool const bDestOnly((addFlags &
kAfAcLast) != 0u);
1017 AccountCandidates candidates;
1020 candidates.reserve(assets.size());
1022 static constexpr bool kIsLine =
1024 static constexpr bool kIsMpt =
1027 for (
auto const& asset : assets)
1029 if (continueCallback && !continueCallback())
1031 auto const& acct = [&]()
constexpr {
1032 if constexpr (kIsLine)
1033 return asset.getAccountIDPeer();
1035 if constexpr (kIsMpt)
1039 if constexpr (kIsLine)
1040 return asset.getDirectionPeer();
1046 if (hasEffectiveDestination && (acct ==
dstAccount_))
1054 if (bDestOnly && !bToDestination)
1059 auto const correctAsset = [&]() {
1060 if constexpr (kIsLine)
1062 return uEndPathAsset.get<
Currency>() ==
1063 asset.getLimit().template
get<Issue>().currency;
1065 if constexpr (kIsMpt)
1067 return uEndPathAsset.get<
MPTID>() == asset.getMptID();
1070 auto checkAsset = [&]() {
1071 if constexpr (kIsLine)
1075 (!asset.getLimitPeer() ||
1076 -asset.getBalance() >= asset.getLimitPeer() ||
1077 (bRequireAuth && !asset.getAuth()))) ||
1078 (bIsNoRippleOut && asset.getNoRipple()));
1080 if constexpr (kIsMpt)
1082 return asset.isZeroBalance() || asset.isMaxedOut() ||
1087 if (correctAsset && !currentPath.
hasSeen(acct, uEndPathAsset, acct))
1102 if (!currentPath.
empty())
1105 <<
"complete path found ae: "
1110 else if (!bDestOnly)
1113 candidates.push_back({AccountCandidate::kHighPriority, acct});
1131 candidates.push_back({out, acct});
1137 uEndPathAsset.visit(
1139 if (
auto const lines =
rLCache_->getRippleLines(
1147 if (
auto const mpts =
rLCache_->getMPTs(uEndAccount))
1153 if (!candidates.empty())
1158 AccountCandidate
const& first, AccountCandidate
const& second) {
1159 return compareAccountCandidate(seq, first, second);
1162 int count = candidates.size();
1168 else if (count > 50)
1173 auto it = candidates.begin();
1174 while (count-- != 0)
1176 if (continueCallback && !continueCallback())
1181 incompletePaths.
assembleAdd(currentPath, pathElement);
1188 JLOG(
j_.warn()) <<
"Path ends on non-existent issuer";
1199 app_.getOrderBookDB().isBookToXRP(
1200 assetFromPathAsset(uEndPathAsset, uEndIssuer),
domain_))
1204 incompletePaths.
assembleAdd(currentPath, pathElement);
1209 bool const bDestOnly = (addFlags &
kAfObLast) != 0;
1210 auto books =
app_.getOrderBookDB().getBooksByTakerPays(
1211 assetFromPathAsset(uEndPathAsset, uEndIssuer),
domain_);
1212 JLOG(
j_.trace()) << books.size() <<
" books found from this currency/issuer";
1214 for (
auto const& book : books)
1216 if (continueCallback && !continueCallback())
1222 STPath newPath(currentPath);
1224 if (
isXRP(book.out))
1235 JLOG(
j_.trace()) <<
"complete path found bx: "
1244 else if (!currentPath.
hasSeen(
1245 book.out.getIssuer(), book.out, book.out.getIssuer()))
1251 if ((newPath.
size() >= 2) && (newPath.
back().isAccount()) &&
1252 (newPath[newPath.
size() - 2].isOffer()))
1259 book.out.getIssuer());
1268 book.out.getIssuer());
1271 if (hasEffectiveDestination && book.out.getIssuer() ==
dstAccount_ &&
1281 JLOG(
j_.trace()) <<
"complete path found ba: "
1292 book.out.getIssuer(),
1294 book.out.getIssuer()));
1306makePath(
char const*
string)
1350 auto& list = gPathTable[type];
1351 XRPL_ASSERT(list.empty(),
"xrpl::fillPaths : empty paths");
1352 for (
auto& cost : costs)
1353 list.push_back({.searchLevel = cost.cost, .type = makePath(cost.path)});
1376 {{.cost = 1, .path =
"sfd"},
1377 {.cost = 3, .path =
"sfad"},
1378 {.cost = 5, .path =
"sfaad"},
1379 {.cost = 6, .path =
"sbfd"},
1380 {.cost = 8, .path =
"sbafd"},
1381 {.cost = 9, .path =
"sbfad"},
1382 {.cost = 10, .path =
"sbafad"}});
1386 {{.cost = 1, .path =
"sxd"},
1387 {.cost = 2, .path =
"saxd"},
1388 {.cost = 6, .path =
"saaxd"},
1389 {.cost = 7, .path =
"sbxd"},
1390 {.cost = 8, .path =
"sabxd"},
1391 {.cost = 9, .path =
"sabaxd"}});
1397 {.cost = 1, .path =
"sad"},
1398 {.cost = 1, .path =
"sfd"},
1399 {.cost = 4, .path =
"safd"},
1400 {.cost = 4, .path =
"sfad"},
1401 {.cost = 5, .path =
"saad"},
1402 {.cost = 5, .path =
"sbfd"},
1403 {.cost = 6, .path =
"sxfad"},
1404 {.cost = 6, .path =
"safad"},
1405 {.cost = 6, .path =
"saxfd"},
1407 {.cost = 6, .path =
"saxfad"},
1408 {.cost = 6, .path =
"sabfd"},
1409 {.cost = 7, .path =
"saaad"},
1416 {.cost = 1, .path =
"sfad"},
1417 {.cost = 1, .path =
"safd"},
1418 {.cost = 3, .path =
"safad"},
1419 {.cost = 4, .path =
"sxfd"},
1420 {.cost = 5, .path =
"saxfd"},
1421 {.cost = 5, .path =
"sxfad"},
1422 {.cost = 5, .path =
"sbfd"},
1423 {.cost = 6, .path =
"saxfad"},
1424 {.cost = 6, .path =
"sabfd"},
1425 {.cost = 7, .path =
"saafd"},
1426 {.cost = 8, .path =
"saafad"},
1427 {.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)
bool contains(STPath const &path) const
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)
Returns true if account's MPToken for mptIssue carries the individual-lock flag (lsfMPTLocked).
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)