xrpld
Loading...
Searching...
No Matches
Pathfinder.cpp
1#include <xrpld/rpc/detail/Pathfinder.h>
2
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>
7
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> // IWYU pragma: keep
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>
32
33#include <algorithm>
34#include <cstddef>
35#include <cstdint>
36#include <exception>
37#include <functional>
38#include <map>
39#include <memory>
40#include <optional>
41#include <ostream>
42#include <string>
43#include <vector>
44
45namespace xrpl {
48{
49 return os << static_cast<int>(t);
50}
53{
54 return os << static_cast<int>(t);
55}
56} // namespace xrpl
57
58/*
59
60Core Pathfinding Engine
61
62The pathfinding request is identified by category, XRP to XRP, XRP to
63non-XRP, non-XRP to XRP, same currency non-XRP to non-XRP, cross-currency
64non-XRP to non-XRP. For each category, there is a table of paths that the
65pathfinder searches for. Complete paths are collected.
66
67Each complete path is then rated and sorted. Paths with no or trivial
68liquidity are dropped. Otherwise, paths are sorted based on quality,
69liquidity, and path length.
70
71Path slots are filled in quality (ratio of out to in) order, with the
72exception that the last path must have enough liquidity to complete the
73payment (assuming no liquidity overlap). In addition, if no selected path
74is capable of providing enough liquidity to complete the payment by itself,
75an extra "covering" path is returned.
76
77The selected paths are then tested to determine if they can complete the
78payment and, if so, at what cost. If they fail and a covering path was
79found, the test is repeated with the covering path. If this succeeds, the
80final paths and the estimated cost are returned.
81
82The engine permits the search depth to be selected and the paths table
83includes the depth at which each path type is found. A search depth of zero
84causes no searching to be done. Extra paths can also be injected, and this
85should be used to preserve previously-found paths across invocations for the
86same path request (particularly if the search depth may change).
87
88*/
89
90namespace xrpl {
91
92namespace {
93
94// This is an arbitrary cutoff, and it might cause us to miss other
95// good paths with this arbitrary cut off.
96constexpr std::size_t kPathfinderMaxCompletePaths = 1000;
97
98struct AccountCandidate
99{
100 int priority;
101 AccountID account;
102
103 static int const kHighPriority = 10000;
104};
105
106bool
107compareAccountCandidate(
108 std::uint32_t seq,
109 AccountCandidate const& first,
110 AccountCandidate const& second)
111{
112 // Primary sort key: priority descending
113 if (first.priority != second.priority)
114 return first.priority > second.priority;
115
116 // Secondary sort key: account descending
117 if (first.account != second.account)
118 return first.account > second.account;
119
120 // Tertiary sort key (tie-breaker): (priority ^ seq) ascending
121 // Note: The primary and secondary keys are equal here.
122 return (first.priority ^ seq) < (second.priority ^ seq);
123}
124
125using AccountCandidates = std::vector<AccountCandidate>;
126
127struct CostedPath
128{
129 int searchLevel;
131};
132
133using CostedPathList = std::vector<CostedPath>;
134
135using PathTable = std::map<Pathfinder::PaymentType, CostedPathList>;
136
137struct PathCost
138{
139 int cost;
140 char const* path;
141};
142using PathCostList = std::vector<PathCost>;
143
144PathTable gPathTable;
145
146std::string
147pathTypeToString(Pathfinder::PathType const& type)
148{
149 std::string ret;
150
151 for (auto const& node : type)
152 {
153 switch (node)
154 {
156 ret.append("s");
157 break;
159 ret.append("a");
160 break;
162 ret.append("b");
163 break;
165 ret.append("x");
166 break;
168 ret.append("f");
169 break;
171 ret.append("d");
172 break;
173 }
174 }
175
176 return ret;
177}
178
179// Return the smallest amount of useful liquidity for a given amount, and the
180// total number of paths we have to evaluate.
182smallestUsefulAmount(STAmount const& amount, int maxPaths)
183{
184 return divide(amount, STAmount(maxPaths + 2), amount.asset());
185}
186
188amountFromPathAsset(
189 PathAsset const& pathAsset,
190 std::optional<AccountID> const& srcIssuer,
191 AccountID const& srcAccount)
192{
193 return pathAsset.visit(
194 [&](Currency const& currency) {
195 auto const& account = srcIssuer.value_or(isXRP(currency) ? xrpAccount() : srcAccount);
196 return STAmount(Issue{currency, account}, 1u, 0, true);
197 },
198 [](MPTID const& mpt) { return STAmount(mpt, 1u, 0, true); });
199}
200
201Asset
202assetFromPathAsset(PathAsset const& pathAsset, AccountID const& account)
203{
204 return pathAsset.visit(
205 [&](Currency const& currency) { return Asset{Issue{currency, account}}; },
206 [](MPTID const& mpt) { return Asset{mpt}; });
207}
208
209} // namespace
210
212 std::shared_ptr<AssetCache> const& cache,
213 AccountID const& uSrcAccount,
214 AccountID const& uDstAccount,
215 PathAsset const& uSrcPathAsset,
216 std::optional<AccountID> const& uSrcIssuer,
217 STAmount const& saDstAmount,
218 std::optional<STAmount> const& srcAmount,
219 std::optional<UInt256> const& domain,
220 Application& app)
221 : srcAccount_(uSrcAccount)
222 , dstAccount_(uDstAccount)
223 , effectiveDst_(isXRP(saDstAmount.getIssuer()) ? uDstAccount : saDstAmount.getIssuer())
224 , dstAmount_(saDstAmount)
225 , srcPathAsset_(uSrcPathAsset)
226 , srcIssuer_(uSrcIssuer)
227 , srcAmount_(srcAmount.value_or(amountFromPathAsset(uSrcPathAsset, uSrcIssuer, uSrcAccount)))
229 , domain_(domain)
230 , ledger_(cache->getLedger())
231 , rLCache_(cache)
232 , app_(app)
233 , j_(app.getJournal("Pathfinder"))
234{
235 XRPL_ASSERT(
236 !uSrcIssuer || uSrcPathAsset.isXRP() == isXRP(uSrcIssuer.value()),
237 "xrpl::Pathfinder::Pathfinder : valid inputs");
238}
239
240bool
241Pathfinder::findPaths(int searchLevel, std::function<bool(void)> const& continueCallback)
242{
243 JLOG(j_.trace()) << "findPaths start";
245 {
246 // No need to send zero money.
247 JLOG(j_.debug()) << "Destination amount was zero.";
248 ledger_.reset();
249 return false;
250
251 // TODO(tom): why do we reset the ledger just in this case and the one
252 // below - why don't we do it each time we return false?
253 }
254
256 srcPathAsset_ == dstAmount_.asset())
257 {
258 // No need to send to same account with same currency.
259 JLOG(j_.debug()) << "Tried to send to same issuer";
260 ledger_.reset();
261 return false;
262 }
263
265 {
266 // Default path might work, but any path would loop
267 return true;
268 }
269
270 loadEvent_ = app_.getJobQueue().makeLoadEvent(JtPathFind, "FindPath");
271 auto currencyIsXRP = isXRP(srcPathAsset_);
272
273 bool const useIssuerAccount = srcIssuer_ && !currencyIsXRP && !isXRP(*srcIssuer_);
274 auto& account = useIssuerAccount ? *srcIssuer_ : srcAccount_;
275 auto issuer = currencyIsXRP ? AccountID() : account;
276 source_ = STPathElement(account, srcPathAsset_, issuer);
277 auto issuerString = srcIssuer_ ? to_string(*srcIssuer_) : std::string("none");
278 JLOG(j_.trace()) << "findPaths>"
279 << " srcAccount_=" << srcAccount_ << " dstAccount_=" << dstAccount_
280 << " dstAmount_=" << dstAmount_.getFullText()
281 << " srcPathAsset_=" << srcPathAsset_ << " srcIssuer_=" << issuerString;
282
283 if (!ledger_)
284 {
285 JLOG(j_.debug()) << "findPaths< no ledger";
286 return false;
287 }
288
289 bool const bSrcXrp = isXRP(srcPathAsset_);
290 bool const bDstXrp = isXRP(dstAmount_.asset());
291
292 if (!ledger_->exists(keylet::account(srcAccount_)))
293 {
294 // We can't even start without a source account.
295 JLOG(j_.debug()) << "invalid source account";
296 return false;
297 }
298
300 {
301 JLOG(j_.debug()) << "Non-existent gateway";
302 return false;
303 }
304
305 if (!ledger_->exists(keylet::account(dstAccount_)))
306 {
307 // Can't find the destination account - we must be funding a new
308 // account.
309 if (!bDstXrp)
310 {
311 JLOG(j_.debug()) << "New account not being funded in XRP ";
312 return false;
313 }
314
315 auto const reserve = STAmount(ledger_->fees().reserve);
316 if (dstAmount_ < reserve)
317 {
318 JLOG(j_.debug()) << "New account not getting enough funding: " << dstAmount_ << " < "
319 << reserve;
320 return false;
321 }
322 }
323
324 // Now compute the payment type from the types of the source and destination
325 // currencies.
327 if (bSrcXrp && bDstXrp)
328 {
329 // XRP -> XRP
330 JLOG(j_.debug()) << "XRP to XRP payment";
331 paymentType = PaymentType::XrpToXrp;
332 }
333 else if (bSrcXrp)
334 {
335 // XRP -> non-XRP
336 JLOG(j_.debug()) << "XRP to non-XRP payment";
337 paymentType = PaymentType::XrpToNonXrp;
338 }
339 else if (bDstXrp)
340 {
341 // non-XRP -> XRP
342 JLOG(j_.debug()) << "non-XRP to XRP payment";
343 paymentType = PaymentType::NonXrpToXrp;
344 }
345 else if (srcPathAsset_ == dstAmount_.asset())
346 {
347 // non-XRP -> non-XRP - Same currency
348 JLOG(j_.debug()) << "non-XRP to non-XRP - same currency";
349 paymentType = PaymentType::NonXrpToSame;
350 }
351 else
352 {
353 // non-XRP to non-XRP - Different currency
354 JLOG(j_.debug()) << "non-XRP to non-XRP - cross currency";
355 paymentType = PaymentType::NonXrpToNonXrp;
356 }
357
358 // Now iterate over all paths for that paymentType.
359 for (auto const& costedPath : gPathTable[paymentType])
360 {
361 if (continueCallback && !continueCallback())
362 return false;
363 // Only use paths with at most the current search level.
364 if (costedPath.searchLevel <= searchLevel)
365 {
366 JLOG(j_.trace()) << "findPaths trying payment type " << paymentType;
367 addPathsForType(costedPath.type, continueCallback);
368
369 if (completePaths_.size() > kPathfinderMaxCompletePaths)
370 break;
371 }
372 }
373
374 JLOG(j_.debug()) << completePaths_.size() << " complete paths found";
375
376 // Even if we find no paths, default paths may work, and we don't check them
377 // currently.
378 return true;
379}
380
381TER
383 STPath const& path, // IN: The path to check.
384 STAmount const& minDstAmount, // IN: The minimum output this path must
385 // deliver to be worth keeping.
386 STAmount& amountOut, // OUT: The actual liquidity along the path.
387 uint64_t& qualityOut) const // OUT: The returned initial quality
388{
389 STPathSet pathSet;
390 pathSet.pushBack(path);
391
393 rcInput.defaultPathsAllowed = false;
394
395 PaymentSandbox sandbox(&*ledger_, TapNone);
396
397 try
398 {
399 // Compute a path that provides at least the minimum liquidity.
400 if (convertAll_)
401 rcInput.partialPaymentAllowed = true;
402
404 sandbox,
406 minDstAmount,
409 pathSet,
410 domain_,
411 app_,
412 &rcInput);
413 // If we can't get even the minimum liquidity requested, we're done.
414 if (!isTesSuccess(rc.result()))
415 return rc.result();
416
417 qualityOut = getRate(rc.actualAmountOut, rc.actualAmountIn);
418 amountOut = rc.actualAmountOut;
419
420 if (!convertAll_)
421 {
422 // Now try to compute the remaining liquidity.
423 rcInput.partialPaymentAllowed = true;
425 sandbox,
427 dstAmount_ - amountOut,
430 pathSet,
431 domain_,
432 app_,
433 &rcInput);
434
435 // If we found further liquidity, add it into the result.
436 if (rc.result() == tesSUCCESS)
437 amountOut += rc.actualAmountOut;
438 }
439
440 return tesSUCCESS;
441 }
442 catch (std::exception const& e)
443 {
444 JLOG(j_.info()) << "checkpath: exception (" << e.what() << ") "
446 return tefEXCEPTION;
447 }
448}
449
450void
451Pathfinder::computePathRanks(int maxPaths, std::function<bool(void)> const& continueCallback)
452{
454
455 // Must subtract liquidity in default path from remaining amount.
456 try
457 {
458 PaymentSandbox sandbox(&*ledger_, TapNone);
459
461 rcInput.partialPaymentAllowed = true;
463 sandbox,
468 STPathSet(),
469 domain_,
470 app_,
471 &rcInput);
472
473 if (rc.result() == tesSUCCESS)
474 {
475 JLOG(j_.debug()) << "Default path contributes: " << rc.actualAmountIn;
476 remainingAmount_ -= rc.actualAmountOut;
477 }
478 else
479 {
480 JLOG(j_.debug()) << "Default path fails: " << transToken(rc.result());
481 }
482 }
483 catch (std::exception const&)
484 {
485 JLOG(j_.debug()) << "Default path causes exception";
486 }
487
488 rankPaths(maxPaths, completePaths_, pathRanks_, continueCallback);
489}
490
491static bool
493{
494 // FIXME: default paths can consist of more than just an account:
495 //
496 // JoelKatz writes:
497 // So the test for whether a path is a default path is incorrect. I'm not
498 // sure it's worth the complexity of fixing though. If we are going to fix
499 // it, I'd suggest doing it this way:
500 //
501 // 1) Compute the default path, probably by using 'expandPath' to expand an
502 // empty path. 2) Chop off the source and destination nodes.
503 //
504 // 3) In the pathfinding loop, if the source issuer is not the sender,
505 // reject all paths that don't begin with the issuer's account node or match
506 // the path we built at step 2.
507 return path.size() == 1;
508}
509
510static STPath
512{
513 // This path starts with the issuer, which is already implied
514 // so remove the head node
515 STPath ret;
516
517 for (auto it = path.begin() + 1; it != path.end(); ++it)
518 ret.pushBack(*it);
519
520 return ret;
521}
522
523// For each useful path in the input path set,
524// create a ranking entry in the output vector of path ranks
525void
527 int maxPaths,
528 STPathSet const& paths,
529 std::vector<PathRank>& rankedPaths,
530 std::function<bool(void)> const& continueCallback)
531{
532 JLOG(j_.trace()) << "rankPaths with " << paths.size() << " candidates, and " << maxPaths
533 << " maximum";
534 rankedPaths.clear();
535 rankedPaths.reserve(paths.size());
536
537 auto const saMinDstAmount = [&]() -> STAmount {
538 if (!convertAll_)
539 {
540 // Ignore paths that move only very small amounts.
541 return smallestUsefulAmount(dstAmount_, maxPaths);
542 }
543
544 // On convert_all_ partialPaymentAllowed will be set to true
545 // and requiring a huge amount will find the highest liquidity.
547 }();
548
549 for (int i = 0; i < paths.size(); ++i)
550 {
551 if (continueCallback && !continueCallback())
552 return;
553 auto const& currentPath = paths[i];
554 if (!currentPath.empty())
555 {
556 STAmount liquidity;
557 uint64_t uQuality = 0;
558 auto const resultCode =
559 getPathLiquidity(currentPath, saMinDstAmount, liquidity, uQuality);
560 if (!isTesSuccess(resultCode))
561 {
562 JLOG(j_.debug()) << "findPaths: dropping : " << transToken(resultCode) << ": "
563 << currentPath.getJson(JsonOptions::Values::None);
564 }
565 else
566 {
567 JLOG(j_.debug()) << "findPaths: quality: " << uQuality << ": "
568 << currentPath.getJson(JsonOptions::Values::None);
569
570 rankedPaths.push_back(
571 {.quality = uQuality,
572 .length = currentPath.size(),
573 .liquidity = liquidity,
574 .index = i});
575 }
576 }
577 }
578
579 // Sort paths by:
580 // cost of path (when considering quality)
581 // width of path
582 // length of path
583 // A better PathRank is lower, best are sorted to the beginning.
585 rankedPaths, [&](Pathfinder::PathRank const& a, Pathfinder::PathRank const& b) {
586 // 1) Higher quality (lower cost) is better
587 if (!convertAll_ && a.quality != b.quality)
588 return a.quality < b.quality;
589
590 // 2) More liquidity (higher volume) is better
591 if (a.liquidity != b.liquidity)
592 return a.liquidity > b.liquidity;
593
594 // 3) Shorter paths are better
595 if (a.length != b.length)
596 return a.length < b.length;
597
598 // 4) Tie breaker
599 return a.index > b.index;
600 });
601}
602
605 int maxPaths,
606 STPath& fullLiquidityPath,
607 STPathSet const& extraPaths,
608 AccountID const& srcIssuer,
609 std::function<bool(void)> const& continueCallback)
610{
611 JLOG(j_.debug()) << "findPaths: " << completePaths_.size() << " paths and " << extraPaths.size()
612 << " extras";
613
614 if (completePaths_.empty() && extraPaths.empty())
615 return completePaths_;
616
617 XRPL_ASSERT(
618 fullLiquidityPath.empty(), "xrpl::Pathfinder::getBestPaths : first empty path result");
619 bool const issuerIsSender = isXRP(srcPathAsset_) || (srcIssuer == srcAccount_);
620
621 std::vector<PathRank> extraPathRanks;
622 rankPaths(maxPaths, extraPaths, extraPathRanks, continueCallback);
623
624 STPathSet bestPaths;
625
626 // The best PathRanks are now at the start. Pull off enough of them to
627 // fill bestPaths, then look through the rest for the best individual
628 // path that can satisfy the entire liquidity - if one exists.
629 STAmount remaining = remainingAmount_;
630
631 auto pathsIterator = pathRanks_.begin();
632 auto extraPathsIterator = extraPathRanks.begin();
633
634 while (pathsIterator != pathRanks_.end() || extraPathsIterator != extraPathRanks.end())
635 {
636 if (continueCallback && !continueCallback())
637 break;
638 bool usePath = false;
639 bool useExtraPath = false;
640
641 if (pathsIterator == pathRanks_.end())
642 {
643 useExtraPath = true;
644 }
645 else if (extraPathsIterator == extraPathRanks.end())
646 {
647 usePath = true;
648 }
649 else if (extraPathsIterator->quality != pathsIterator->quality)
650 {
651 // Prefer the lower (better) quality value
652 useExtraPath = extraPathsIterator->quality < pathsIterator->quality;
653 usePath = !useExtraPath;
654 }
655 else if (extraPathsIterator->liquidity != pathsIterator->liquidity)
656 {
657 // Equal quality: prefer the higher liquidity
658 useExtraPath = extraPathsIterator->liquidity > pathsIterator->liquidity;
659 usePath = !useExtraPath;
660 }
661 else
662 {
663 // Risk is high they have identical liquidity
664 useExtraPath = true;
665 usePath = true;
666 }
667
668 auto& pathRank = usePath ? *pathsIterator : *extraPathsIterator;
669
670 auto const& path = usePath ? completePaths_[pathRank.index] : extraPaths[pathRank.index];
671
672 if (useExtraPath)
673 ++extraPathsIterator;
674
675 if (usePath)
676 ++pathsIterator;
677
678 auto iPathsLeft = maxPaths - bestPaths.size();
679 if (iPathsLeft <= 0 && !fullLiquidityPath.empty())
680 break;
681
682 if (path.empty())
683 {
684 // LCOV_EXCL_START
685 UNREACHABLE("xrpl::Pathfinder::getBestPaths : path not found");
686 continue;
687 // LCOV_EXCL_STOP
688 }
689
690 bool startsWithIssuer = false;
691
692 if (!issuerIsSender && usePath)
693 {
694 // Need to make sure path matches issuer constraints
695 if (isDefaultPath(path) || path.front().getAccountID() != srcIssuer)
696 {
697 continue;
698 }
699
700 startsWithIssuer = true;
701 }
702
703 if (iPathsLeft > 1 || (iPathsLeft > 0 && pathRank.liquidity >= remaining))
704 // last path must fill
705 {
706 --iPathsLeft;
707 remaining -= pathRank.liquidity;
708 bestPaths.pushBack(startsWithIssuer ? removeIssuer(path) : path);
709 }
710 else if (iPathsLeft == 0 && pathRank.liquidity >= dstAmount_ && fullLiquidityPath.empty())
711 {
712 // We found an extra path that can move the whole amount.
713 fullLiquidityPath = (startsWithIssuer ? removeIssuer(path) : path);
714 JLOG(j_.debug()) << "Found extra full path: "
715 << fullLiquidityPath.getJson(JsonOptions::Values::None);
716 }
717 else
718 {
719 JLOG(j_.debug()) << "Skipping a non-filling path: "
721 }
722 }
723
724 if (remaining > beast::kZero)
725 {
726 XRPL_ASSERT(
727 fullLiquidityPath.empty(), "xrpl::Pathfinder::getBestPaths : second empty path result");
728 JLOG(j_.info()) << "Paths could not send " << remaining << " of " << dstAmount_;
729 }
730 else
731 {
732 JLOG(j_.debug()) << "findPaths: RESULTS: " << bestPaths.getJson(JsonOptions::Values::None);
733 }
734 return bestPaths;
735}
736
737bool
739{
740 bool const matchingAsset = (asset == srcPathAsset_);
741 bool const matchingAccount = isXRP(asset) || (srcIssuer_ && asset.getIssuer() == srcIssuer_) ||
742 asset.getIssuer() == srcAccount_;
743
744 return matchingAsset && matchingAccount;
745}
746
747int
749 PathAsset const& pathAsset,
750 AccountID const& account,
751 LineDirection direction,
752 bool isDstAsset,
753 AccountID const& dstAccount,
754 std::function<bool(void)> const& continueCallback)
755{
756 Asset const asset = assetFromPathAsset(pathAsset, account);
757
758 auto [it, inserted] = pathsOutCountMap_.emplace(asset, 0);
759
760 // If it was already present, return the stored number of paths
761 if (!inserted)
762 return it->second;
763
764 auto sleAccount = ledger_->read(keylet::account(account));
765
766 if (!sleAccount)
767 return 0;
768
769 auto const aFlags = sleAccount->getFieldU32(sfFlags);
770 bool const bAuthRequired = [&]() {
771 if (pathAsset.holds<Currency>())
772 return (aFlags & lsfRequireAuth) != 0;
773 return !isTesSuccess(requireAuth(*ledger_, asset.get<MPTIssue>(), account));
774 }();
775 bool const bFrozen = [&]() {
776 if (pathAsset.holds<Currency>())
777 return (aFlags & lsfGlobalFreeze) != 0;
778 return isGlobalFrozen(*ledger_, asset.get<MPTIssue>());
779 }();
780
781 int count = 0;
782
783 if (!bFrozen)
784 {
785 count = app_.getOrderBookDB().getBookSize(asset, domain_);
786
787 asset.visit(
788 [&](Issue const&) {
789 if (auto const lines = rLCache_->getRippleLines(account, direction))
790 {
791 for (auto const& rspEntry : *lines)
792 {
793 if (pathAsset.get<Currency>() != rspEntry.getLimit().get<Issue>().currency)
794 continue;
795 if (rspEntry.getBalance() <= beast::kZero &&
796 (!rspEntry.getLimitPeer() ||
797 -rspEntry.getBalance() >= rspEntry.getLimitPeer() ||
798 (bAuthRequired && !rspEntry.getAuth())))
799 continue;
800 if (isDstAsset && dstAccount == rspEntry.getAccountIDPeer())
801 {
802 count += 10000; // count a path to the destination extra
803 continue;
804 }
805 if (rspEntry.getNoRipplePeer())
806 continue; // This probably isn't a useful path out
807 if (rspEntry.getFreezePeer())
808 continue; // Not a useful path out
809 ++count;
810 }
811 }
812 },
813 [&](MPTIssue const&) {
814 if (auto const mpts = rLCache_->getMPTs(account))
815 {
816 for (auto const& mpt : *mpts)
817 {
818 if (pathAsset.get<MPTID>() != mpt.getMptID() || !mpt.canSend(account) ||
819 bAuthRequired)
820 continue;
821 if (isDstAsset && dstAccount == getMPTIssuer(mpt))
822 {
823 count += 10000;
824 continue;
825 }
826 if (isIndividualFrozen(*ledger_, account, MPTIssue{mpt.getMptID()}))
827 continue;
828 ++count;
829 }
830 }
831 });
832 }
833 it->second = count;
834 return count;
835}
836
837void
839 STPathSet const& currentPaths, // The paths to build from
840 STPathSet& incompletePaths, // The set of partial paths we add to
841 int addFlags,
842 std::function<bool(void)> const& continueCallback)
843{
844 JLOG(j_.debug()) << "addLink< on " << currentPaths.size() << " source(s), flags=" << addFlags;
845 for (auto const& path : currentPaths)
846 {
847 if (continueCallback && !continueCallback())
848 return;
849 addLink(path, incompletePaths, addFlags, continueCallback);
850 }
851}
852
855 PathType const& pathType,
856 std::function<bool(void)> const& continueCallback)
857{
858 JLOG(j_.debug()) << "addPathsForType " << CollectionAndDelimiter(pathType, ", ");
859 // See if the set of paths for this type already exists.
860 auto it = paths_.find(pathType);
861 if (it != paths_.end())
862 return it->second;
863
864 // Otherwise, if the type has no nodes, return the empty path.
865 if (pathType.empty() || (continueCallback && !continueCallback()))
866 {
867 static auto const kEmptyPath = PathType{};
868 return paths_.try_emplace(kEmptyPath, STPathSet::DeduplicationTag{}).first->second;
869 }
870
871 // Otherwise, get the paths for the parent PathType by calling
872 // addPathsForType recursively.
873 PathType parentPathType = pathType;
874 parentPathType.pop_back();
875
876 STPathSet const& parentPaths = addPathsForType(parentPathType, continueCallback);
877 STPathSet& pathsOut = paths_.try_emplace(pathType, STPathSet::DeduplicationTag{}).first->second;
878
879 JLOG(j_.debug()) << "getPaths< adding onto '" << pathTypeToString(parentPathType)
880 << "' to get '" << pathTypeToString(pathType) << "'";
881
882 int const initialSize = completePaths_.size();
883
884 // Add the last NodeType to the lists.
885 auto nodeType = pathType.back();
886 switch (nodeType)
887 {
888 case NodeType::Source:
889 // Source must always be at the start, so pathsOut has to be empty.
890 XRPL_ASSERT(pathsOut.empty(), "xrpl::Pathfinder::addPathsForType : empty paths");
891 pathsOut.pushBack(STPath());
892 break;
893
895 addLinks(parentPaths, pathsOut, kAfAddAccounts, continueCallback);
896 break;
897
898 case NodeType::Books:
899 addLinks(parentPaths, pathsOut, kAfAddBooks, continueCallback);
900 break;
901
903 addLinks(parentPaths, pathsOut, kAfAddBooks | kAfObXrp, continueCallback);
904 break;
905
907 addLinks(parentPaths, pathsOut, kAfAddBooks | kAfObLast, continueCallback);
908 break;
909
911 // FIXME: What if a different issuer was specified on the
912 // destination amount?
913 // TODO(tom): what does this even mean? Should it be a JIRA?
914 addLinks(parentPaths, pathsOut, kAfAddAccounts | kAfAcLast, continueCallback);
915 break;
916 }
917
918 if (completePaths_.size() != initialSize)
919 {
920 JLOG(j_.debug()) << (completePaths_.size() - initialSize) << " complete paths added";
921 }
922
923 JLOG(j_.debug()) << "getPaths> " << pathsOut.size() << " partial paths found";
924 return pathsOut;
925}
926
927bool
929 AccountID const& fromAccount,
930 AccountID const& toAccount,
931 Currency const& currency)
932{
933 auto sleRipple = ledger_->read(keylet::trustLine(toAccount, fromAccount, currency));
934
935 auto const flag((toAccount > fromAccount) ? lsfHighNoRipple : lsfLowNoRipple);
936
937 return sleRipple && sleRipple->isFlag(flag);
938}
939
940// Does this path end on an account-to-account link whose last account has
941// set "no ripple" on the link?
942bool
944{
945 // Must have at least one link.
946 if (currentPath.empty())
947 return false;
948
949 // Last link must be an account.
950 STPathElement const& endElement = currentPath.back();
951 if ((endElement.getNodeType() & STPathElement::TypeAccount) == 0u)
952 return false;
953
954 // If there's only one item in the path, return true if that item specifies
955 // no ripple on the output. A path with no ripple on its output can't be
956 // followed by a link with no ripple on its input.
957 auto const& fromAccount =
958 (currentPath.size() == 1) ? srcAccount_ : (currentPath.end() - 2)->getAccountID();
959 auto const& toAccount = endElement.getAccountID();
960 return endElement.hasCurrency() && isNoRipple(fromAccount, toAccount, endElement.getCurrency());
961}
962
963void
965 STPath const& currentPath, // The path to build from
966 STPathSet& incompletePaths, // The set of partial paths we add to
967 int addFlags,
968 std::function<bool(void)> const& continueCallback)
969{
970 auto const& pathEnd = currentPath.empty() ? source_ : currentPath.back();
971 auto const& uEndPathAsset = pathEnd.getPathAsset();
972 auto const& uEndIssuer = pathEnd.getIssuerID();
973 auto const& uEndAccount = pathEnd.getAccountID();
974 bool const bOnXRP = isXRP(uEndPathAsset);
975
976 // Does pathfinding really need to get this to
977 // a gateway (the issuer of the destination amount)
978 // rather than the ultimate destination?
979 bool const hasEffectiveDestination = effectiveDst_ != dstAccount_;
980
981 JLOG(j_.trace()) << "addLink< flags=" << addFlags << " onXRP=" << bOnXRP
982 << " completePaths size=" << completePaths_.size();
983 JLOG(j_.trace()) << currentPath.getJson(JsonOptions::Values::None);
984
985 if ((addFlags & kAfAddAccounts) != 0u)
986 {
987 // add accounts
988 if (bOnXRP)
989 {
990 if (dstAmount_.native() && !currentPath.empty())
991 { // non-default path to XRP destination
992 JLOG(j_.trace()) << "complete path found ax: "
993 << currentPath.getJson(JsonOptions::Values::None);
994 completePaths_.pushBack(currentPath);
995 }
996 }
997 else
998 {
999 // search for accounts to add
1000 auto const sleEnd = ledger_->read(keylet::account(uEndAccount));
1001
1002 if (sleEnd)
1003 {
1004 bool const bRequireAuth(sleEnd->isFlag(lsfRequireAuth));
1005 bool const bIsEndAsset(uEndPathAsset == dstAmount_.asset());
1006 bool const bIsNoRippleOut(isNoRippleOut(currentPath));
1007 bool const bDestOnly((addFlags & kAfAcLast) != 0u);
1008
1009 AccountCandidates candidates;
1010
1011 auto forAssets = [&]<typename AssetType>(AssetType const& assets) {
1012 candidates.reserve(assets.size());
1013
1014 static constexpr bool kIsLine =
1016 static constexpr bool kIsMpt =
1018
1019 for (auto const& asset : assets)
1020 {
1021 if (continueCallback && !continueCallback())
1022 return;
1023 auto const& acct = [&]() constexpr {
1024 if constexpr (kIsLine)
1025 return asset.getAccountIDPeer();
1026 // Unlike trustline, MPT is not bidirectional
1027 if constexpr (kIsMpt)
1028 return getMPTIssuer(asset);
1029 }();
1030 auto const direction = [&]() constexpr -> LineDirection {
1031 if constexpr (kIsLine)
1032 return asset.getDirectionPeer();
1033 // incoming for MPT since MPT doesn't support
1034 // rippling (see LineDirection comments)
1036 }();
1037
1038 if (hasEffectiveDestination && (acct == dstAccount_))
1039 {
1040 // We skipped the gateway
1041 continue;
1042 }
1043
1044 bool const bToDestination = acct == effectiveDst_;
1045
1046 if (bDestOnly && !bToDestination)
1047 {
1048 continue;
1049 }
1050
1051 auto const correctAsset = [&]() {
1052 if constexpr (kIsLine)
1053 {
1054 return uEndPathAsset.get<Currency>() ==
1055 asset.getLimit().template get<Issue>().currency;
1056 }
1057 if constexpr (kIsMpt)
1058 {
1059 return uEndPathAsset.get<MPTID>() == asset.getMptID();
1060 }
1061 }();
1062 auto checkAsset = [&]() {
1063 if constexpr (kIsLine)
1064 {
1065 return (
1066 (asset.getBalance() <= beast::kZero &&
1067 (!asset.getLimitPeer() ||
1068 -asset.getBalance() >= asset.getLimitPeer() ||
1069 (bRequireAuth && !asset.getAuth()))) ||
1070 (bIsNoRippleOut && asset.getNoRipple()));
1071 }
1072 if constexpr (kIsMpt)
1073 {
1074 // `asset` came from uEndAccount's cached MPTs.
1075 // `acct` is the next issuer hop, not the
1076 // account whose balance is being tested.
1077 return !asset.canSend(uEndAccount) ||
1078 requireAuth(*ledger_, MPTIssue{asset}, acct);
1079 }
1080 };
1081
1082 if (correctAsset && !currentPath.hasSeen(acct, uEndPathAsset, acct))
1083 {
1084 // path is for correct currency and has not been
1085 // seen
1086 if (checkAsset())
1087 {
1088 // Can't leave on this path
1089 continue;
1090 }
1091 if (bToDestination)
1092 {
1093 // destination is always worth trying
1094 if (uEndPathAsset == dstAmount_.asset())
1095 {
1096 // this is a complete path
1097 if (!currentPath.empty())
1098 {
1099 JLOG(j_.trace())
1100 << "complete path found ae: "
1101 << currentPath.getJson(JsonOptions::Values::None);
1102 completePaths_.pushBack(currentPath);
1103 }
1104 }
1105 else if (!bDestOnly)
1106 {
1107 // this is a high-priority candidate
1108 candidates.push_back({AccountCandidate::kHighPriority, acct});
1109 }
1110 }
1111 else if (acct == srcAccount_)
1112 {
1113 // going back to the source is bad
1114 }
1115 else
1116 {
1117 // save this candidate
1118 int const out = getPathsOut(
1119 uEndPathAsset,
1120 acct,
1121 direction,
1122 bIsEndAsset,
1124 continueCallback);
1125 if (out != 0)
1126 candidates.push_back({out, acct});
1127 }
1128 }
1129 }
1130 };
1131
1132 uEndPathAsset.visit(
1133 [&](Currency const&) {
1134 if (auto const lines = rLCache_->getRippleLines(
1135 uEndAccount,
1137 {
1138 forAssets(*lines);
1139 }
1140 },
1141 [&](MPTID const&) {
1142 if (auto const mpts = rLCache_->getMPTs(uEndAccount))
1143 {
1144 forAssets(*mpts);
1145 }
1146 });
1147
1148 if (!candidates.empty())
1149 {
1151 candidates,
1152 [seq = ledger_->seq()](
1153 AccountCandidate const& first, AccountCandidate const& second) {
1154 return compareAccountCandidate(seq, first, second);
1155 });
1156
1157 int count = candidates.size();
1158 // allow more paths from source
1159 if ((count > 10) && (uEndAccount != srcAccount_))
1160 {
1161 count = 10;
1162 }
1163 else if (count > 50)
1164 {
1165 count = 50;
1166 }
1167
1168 auto it = candidates.begin();
1169 while (count-- != 0)
1170 {
1171 if (continueCallback && !continueCallback())
1172 return;
1173 // Add accounts to incompletePaths
1174 STPathElement const pathElement(
1175 STPathElement::TypeAccount, it->account, uEndPathAsset, it->account);
1176 incompletePaths.assembleAdd(currentPath, pathElement);
1177 ++it;
1178 }
1179 }
1180 }
1181 else
1182 {
1183 JLOG(j_.warn()) << "Path ends on non-existent issuer";
1184 }
1185 }
1186 }
1187 if ((addFlags & kAfAddBooks) != 0u)
1188 {
1189 // add order books
1190 if ((addFlags & kAfObXrp) != 0u)
1191 {
1192 // to XRP only
1193 if (!bOnXRP &&
1194 app_.getOrderBookDB().isBookToXRP(
1195 assetFromPathAsset(uEndPathAsset, uEndIssuer), domain_))
1196 {
1197 STPathElement const pathElement(
1199 incompletePaths.assembleAdd(currentPath, pathElement);
1200 }
1201 }
1202 else
1203 {
1204 bool const bDestOnly = (addFlags & kAfObLast) != 0;
1205 auto books = app_.getOrderBookDB().getBooksByTakerPays(
1206 assetFromPathAsset(uEndPathAsset, uEndIssuer), domain_);
1207 JLOG(j_.trace()) << books.size() << " books found from this currency/issuer";
1208
1209 for (auto const& book : books)
1210 {
1211 if (continueCallback && !continueCallback())
1212 return;
1213 if (!currentPath.hasSeen(xrpAccount(), book.out, book.out.getIssuer()) &&
1214 !issueMatchesOrigin(book.out) &&
1215 (!bDestOnly || equalTokens(book.out, dstAmount_.asset())))
1216 {
1217 STPath newPath(currentPath);
1218
1219 if (isXRP(book.out))
1220 { // to XRP
1221
1222 // add the order book itself
1223 newPath.emplaceBack(
1225
1226 if (isXRP(dstAmount_.asset()))
1227 {
1228 // destination is XRP, add account and path is
1229 // complete
1230 JLOG(j_.trace()) << "complete path found bx: "
1231 << currentPath.getJson(JsonOptions::Values::None);
1232 completePaths_.pushBack(newPath);
1233 }
1234 else
1235 {
1236 [[maybe_unused]] auto result = incompletePaths.pushBack(newPath);
1237 XRPL_ASSERT(result, "xrpl::Pathfinder::addLink : unique path");
1238 }
1239 }
1240 else if (!currentPath.hasSeen(
1241 book.out.getIssuer(), book.out, book.out.getIssuer()))
1242 {
1243 auto const assetType = book.out.holds<Issue>() ? STPathElement::TypeCurrency
1245 // Don't want the book if we've already seen the issuer
1246 // book -> account -> book
1247 if ((newPath.size() >= 2) && (newPath.back().isAccount()) &&
1248 (newPath[newPath.size() - 2].isOffer()))
1249 {
1250 // replace the redundant account with the order book
1251 newPath[newPath.size() - 1] = STPathElement(
1252 assetType | STPathElement::TypeIssuer,
1253 xrpAccount(),
1254 book.out,
1255 book.out.getIssuer());
1256 }
1257 else
1258 {
1259 // add the order book
1260 newPath.emplaceBack(
1261 assetType | STPathElement::TypeIssuer,
1262 xrpAccount(),
1263 book.out,
1264 book.out.getIssuer());
1265 }
1266
1267 if (hasEffectiveDestination && book.out.getIssuer() == dstAccount_ &&
1268 equalTokens(book.out, dstAmount_.asset()))
1269 {
1270 // We skipped a required issuer
1271 }
1272 else if (
1273 book.out.getIssuer() == effectiveDst_ &&
1274 equalTokens(book.out, dstAmount_.asset()))
1275 { // with the destination account, this path is
1276 // complete
1277 JLOG(j_.trace()) << "complete path found ba: "
1278 << currentPath.getJson(JsonOptions::Values::None);
1279 completePaths_.pushBack(newPath);
1280 }
1281 else
1282 {
1283 // add issuer's account, path still incomplete
1284 incompletePaths.assembleAdd(
1285 newPath,
1288 book.out.getIssuer(),
1289 book.out,
1290 book.out.getIssuer()));
1291 }
1292 }
1293 }
1294 }
1295 }
1296 }
1297}
1298
1299namespace {
1300
1302makePath(char const* string)
1303{
1305
1306 while (true)
1307 {
1308 // NOLINTNEXTLINE(bugprone-switch-missing-default-case)
1309 switch (*string++)
1310 {
1311 case 's': // source
1313 break;
1314
1315 case 'a': // accounts
1317 break;
1318
1319 case 'b': // books
1321 break;
1322
1323 case 'x': // xrp book
1325 break;
1326
1327 case 'f': // book to final currency
1329 break;
1330
1331 case 'd':
1332 // Destination (with account, if required and not already
1333 // present).
1335 break;
1336
1337 case 0:
1338 return ret;
1339 }
1340 }
1341}
1342
1343void
1344fillPaths(Pathfinder::PaymentType type, PathCostList const& costs)
1345{
1346 auto& list = gPathTable[type];
1347 XRPL_ASSERT(list.empty(), "xrpl::fillPaths : empty paths");
1348 for (auto& cost : costs)
1349 list.push_back({.searchLevel = cost.cost, .type = makePath(cost.path)});
1350}
1351
1352} // namespace
1353
1354// Costs:
1355// 0 = minimum to make some payments possible
1356// 1 = include trivial paths to make common cases work
1357// 4 = normal fast search level
1358// 7 = normal slow search level
1359// 10 = most aggressive
1360
1361void
1363{
1364 // CAUTION: Do not include rules that build default paths
1365
1366 gPathTable.clear();
1367 fillPaths(PaymentType::XrpToXrp, {});
1368 /* cspell: disable */
1369
1370 fillPaths(
1372 {{.cost = 1, .path = "sfd"}, // source -> book -> gateway
1373 {.cost = 3, .path = "sfad"}, // source -> book -> account -> destination
1374 {.cost = 5, .path = "sfaad"}, // source -> book -> account -> account -> destination
1375 {.cost = 6, .path = "sbfd"}, // source -> book -> book -> destination
1376 {.cost = 8, .path = "sbafd"}, // source -> book -> account -> book -> destination
1377 {.cost = 9, .path = "sbfad"}, // source -> book -> book -> account -> destination
1378 {.cost = 10, .path = "sbafad"}});
1379
1380 fillPaths(
1382 {{.cost = 1, .path = "sxd"}, // gateway buys XRP
1383 {.cost = 2, .path = "saxd"}, // source -> gateway -> book(XRP) -> dest
1384 {.cost = 6, .path = "saaxd"},
1385 {.cost = 7, .path = "sbxd"},
1386 {.cost = 8, .path = "sabxd"},
1387 {.cost = 9, .path = "sabaxd"}});
1388
1389 // non-XRP to non-XRP (same currency)
1390 fillPaths(
1392 {
1393 {.cost = 1, .path = "sad"}, // source -> gateway -> destination
1394 {.cost = 1, .path = "sfd"}, // source -> book -> destination
1395 {.cost = 4, .path = "safd"}, // source -> gateway -> book -> destination
1396 {.cost = 4, .path = "sfad"},
1397 {.cost = 5, .path = "saad"},
1398 {.cost = 5, .path = "sbfd"},
1399 {.cost = 6, .path = "sxfad"},
1400 {.cost = 6, .path = "safad"},
1401 {.cost = 6, .path = "saxfd"}, // source -> gateway -> book to XRP -> book ->
1402 // destination
1403 {.cost = 6, .path = "saxfad"},
1404 {.cost = 6, .path = "sabfd"}, // source -> gateway -> book -> book -> destination
1405 {.cost = 7, .path = "saaad"},
1406 });
1407
1408 // non-XRP to non-XRP (different currency)
1409 fillPaths(
1411 {
1412 {.cost = 1, .path = "sfad"},
1413 {.cost = 1, .path = "safd"},
1414 {.cost = 3, .path = "safad"},
1415 {.cost = 4, .path = "sxfd"},
1416 {.cost = 5, .path = "saxfd"},
1417 {.cost = 5, .path = "sxfad"},
1418 {.cost = 5, .path = "sbfd"},
1419 {.cost = 6, .path = "saxfad"},
1420 {.cost = 6, .path = "sabfd"},
1421 {.cost = 7, .path = "saafd"},
1422 {.cost = 8, .path = "saafad"},
1423 {.cost = 9, .path = "safaad"},
1424 });
1425 /* cspell: enable */
1426}
1427
1428} // namespace xrpl
T append(T... args)
T back(T... args)
T begin(T... args)
constexpr auto visit(Visitors &&... visitors) const -> decltype(auto)
Definition Asset.h:117
constexpr TIss const & get() const
AccountID const & getIssuer() const
Definition Asset.cpp:21
A currency issued by an account.
Definition Issue.h:18
Currency currency
Definition Issue.h:20
constexpr bool isXRP() const
Definition PathAsset.h:99
constexpr bool holds() const
Definition PathAsset.h:78
T const & get() const
STAmount dstAmount_
Definition Pathfinder.h:193
static std::uint32_t const kAfObLast
Definition Pathfinder.h:229
void addLinks(STPathSet const &currentPaths, 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
bool issueMatchesOrigin(Asset const &)
STAmount srcAmount_
Definition Pathfinder.h:196
static std::uint32_t const kAfAddAccounts
Definition Pathfinder.h:220
AccountID dstAccount_
Definition Pathfinder.h:191
STPathSet getBestPaths(int maxPaths, STPath &fullLiquidityPath, STPathSet const &extraPaths, AccountID const &srcIssuer, std::function< bool(void)> const &continueCallback={})
void addLink(STPath const &currentPath, STPathSet &incompletePaths, int addFlags, std::function< bool(void)> const &continueCallback)
PathAsset srcPathAsset_
Definition Pathfinder.h:194
std::vector< NodeType > PathType
Definition Pathfinder.h:92
std::unique_ptr< LoadEvent > loadEvent_
Definition Pathfinder.h:206
HashMap< Asset, int > pathsOutCountMap_
Definition Pathfinder.h:214
AccountID srcAccount_
Definition Pathfinder.h:190
static std::uint32_t const kAfObXrp
Definition Pathfinder.h:226
std::optional< AccountID > srcIssuer_
Definition Pathfinder.h:195
std::map< PathType, STPathSet > paths_
Definition Pathfinder.h:212
static std::uint32_t const kAfAcLast
Definition Pathfinder.h:232
std::shared_ptr< AssetCache > rLCache_
Definition Pathfinder.h:207
STPathSet completePaths_
Definition Pathfinder.h:210
STPathSet & addPathsForType(PathType const &type, std::function< bool(void)> const &continueCallback)
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.
STPathElement source_
Definition Pathfinder.h:209
bool isNoRipple(AccountID const &fromAccount, AccountID const &toAccount, Currency const &currency)
bool findPaths(int searchLevel, std::function< bool(void)> const &continueCallback={})
Application & app_
Definition Pathfinder.h:216
bool isNoRippleOut(STPath const &currentPath)
beast::Journal const j_
Definition Pathfinder.h:217
std::optional< UInt256 > domain_
Definition Pathfinder.h:203
static void initPathTable()
STAmount remainingAmount_
The amount remaining from srcAccount_ after the default liquidity has been removed.
Definition Pathfinder.h:201
static std::uint32_t const kAfAddBooks
Definition Pathfinder.h:223
AccountID effectiveDst_
Definition Pathfinder.h:192
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_
Definition Pathfinder.h:211
std::shared_ptr< ReadView const > ledger_
Definition Pathfinder.h:205
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
Definition STAmount.h:496
std::uint32_t getNodeType() const
Definition STPathSet.h:427
AccountID const & getAccountID() const
Definition STPathSet.h:483
Currency const & getCurrency() const
Definition STPathSet.h:495
bool hasCurrency() const
Definition STPathSet.h:457
bool assembleAdd(STPath const &base, STPathElement const &tail)
assembleAdd adds a path to the set by combining a base path and a tail element.
std::vector< STPath >::size_type size() const
Definition STPathSet.h:624
json::Value getJson(JsonOptions) const override
bool pushBack(STPath const &e)
pushBack adds a path to the set.
Definition STPathSet.h:669
bool empty() const
Definition STPathSet.h:630
std::vector< STPathElement >::size_type size() const
Definition STPathSet.h:526
bool hasSeen(AccountID const &account, PathAsset const &asset, AccountID const &issuer) const
bool empty() const
Definition STPathSet.h:532
void pushBack(STPathElement const &e)
Definition STPathSet.h:538
std::vector< STPathElement >::const_iterator end() const
Definition STPathSet.h:557
void emplaceBack(Args &&... args)
Definition STPathSet.h:545
std::vector< STPathElement >::const_reference back() const
Definition STPathSet.h:569
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 &registry, Input const *const pInputs=nullptr)
T clear(T... args)
T empty(T... args)
T end(T... args)
T is_same_v
constexpr Zero kZero
Definition Zero.h:30
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:220
Keylet trustLine(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:275
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
STAmount divide(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:69
STAmount convertAmount(STAmount const &amt, bool all)
bool isXRP(AccountID const &c)
Definition AccountID.h:84
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 &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
STAmount largestAmount(STAmount const &amt)
@ tefEXCEPTION
Definition TER.h:167
BaseUInt< 160, detail::CurrencyTag > Currency
Currency is a hash representing a specific currency.
Definition UintTypes.h:42
std::ostream & operator<<(std::ostream &out, BaseUInt< Bits, Tag > const &u)
Definition base_uint.h:672
static STPath removeIssuer(STPath const &path)
AccountID getMPTIssuer(MPTID const &mptid)
Definition MPTIssue.h:95
std::string transToken(TER code)
Definition TER.cpp:257
Currency const & xrpCurrency()
XRP currency.
Definition UintTypes.cpp:99
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:657
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.
Definition TrustLine.h:27
@ JtPathFind
Definition Job.h:70
BaseUInt< 192 > MPTID
MPTID is a 192-bit value representing MPT Issuance ID, which is a concatenation of a 32-bit sequence ...
Definition UintTypes.h:54
std::uint64_t getRate(STAmount const &offerOut, STAmount const &offerIn)
Definition STAmount.cpp:423
bool convertAllCheck(STAmount const &a)
@ TapNone
Definition ApplyView.h:28
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
bool isTesSuccess(TER x) noexcept
Definition TER.h:683
TERSubset< CanCvtToTER > TER
Definition TER.h:654
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.
@ tesSUCCESS
Definition TER.h:250
constexpr bool equalTokens(Asset const &lhs, Asset const &rhs)
Definition Asset.h:286
T pop_back(T... args)
T push_back(T... args)
T reserve(T... args)
T sort(T... args)
T value(T... args)
T value_or(T... args)
T what(T... args)