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_(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";
244 if (dstAmount_ == beast::kZero)
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.isZeroBalance() ||
819 mpt.isMaxedOut() || 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())
866 return paths_[pathType];
867 if (continueCallback && !continueCallback())
868 return paths_[{}];
869
870 // Otherwise, get the paths for the parent PathType by calling
871 // addPathsForType recursively.
872 PathType parentPathType = pathType;
873 parentPathType.pop_back();
874
875 STPathSet const& parentPaths = addPathsForType(parentPathType, continueCallback);
876 STPathSet& pathsOut = paths_[pathType];
877
878 JLOG(j_.debug()) << "getPaths< adding onto '" << pathTypeToString(parentPathType)
879 << "' to get '" << pathTypeToString(pathType) << "'";
880
881 int const initialSize = completePaths_.size();
882
883 // Add the last NodeType to the lists.
884 auto nodeType = pathType.back();
885 switch (nodeType)
886 {
887 case NodeType::Source:
888 // Source must always be at the start, so pathsOut has to be empty.
889 XRPL_ASSERT(pathsOut.empty(), "xrpl::Pathfinder::addPathsForType : empty paths");
890 pathsOut.pushBack(STPath());
891 break;
892
894 addLinks(parentPaths, pathsOut, kAfAddAccounts, continueCallback);
895 break;
896
897 case NodeType::Books:
898 addLinks(parentPaths, pathsOut, kAfAddBooks, continueCallback);
899 break;
900
902 addLinks(parentPaths, pathsOut, kAfAddBooks | kAfObXrp, continueCallback);
903 break;
904
906 addLinks(parentPaths, pathsOut, kAfAddBooks | kAfObLast, continueCallback);
907 break;
908
910 // FIXME: What if a different issuer was specified on the
911 // destination amount?
912 // TODO(tom): what does this even mean? Should it be a JIRA?
913 addLinks(parentPaths, pathsOut, kAfAddAccounts | kAfAcLast, continueCallback);
914 break;
915 }
916
917 if (completePaths_.size() != initialSize)
918 {
919 JLOG(j_.debug()) << (completePaths_.size() - initialSize) << " complete paths added";
920 }
921
922 JLOG(j_.debug()) << "getPaths> " << pathsOut.size() << " partial paths found";
923 return pathsOut;
924}
925
926bool
928 AccountID const& fromAccount,
929 AccountID const& toAccount,
930 Currency const& currency)
931{
932 auto sleRipple = ledger_->read(keylet::trustLine(toAccount, fromAccount, currency));
933
934 auto const flag((toAccount > fromAccount) ? lsfHighNoRipple : lsfLowNoRipple);
935
936 return sleRipple && sleRipple->isFlag(flag);
937}
938
939// Does this path end on an account-to-account link whose last account has
940// set "no ripple" on the link?
941bool
943{
944 // Must have at least one link.
945 if (currentPath.empty())
946 return false;
947
948 // Last link must be an account.
949 STPathElement const& endElement = currentPath.back();
950 if ((endElement.getNodeType() & STPathElement::TypeAccount) == 0u)
951 return false;
952
953 // If there's only one item in the path, return true if that item specifies
954 // no ripple on the output. A path with no ripple on its output can't be
955 // followed by a link with no ripple on its input.
956 auto const& fromAccount =
957 (currentPath.size() == 1) ? srcAccount_ : (currentPath.end() - 2)->getAccountID();
958 auto const& toAccount = endElement.getAccountID();
959 return endElement.hasCurrency() && isNoRipple(fromAccount, toAccount, endElement.getCurrency());
960}
961
962void
964{
965 // TODO(tom): building an STPathSet this way is quadratic in the size
966 // of the STPathSet!
967 for (auto const& p : pathSet)
968 {
969 if (p == path)
970 return;
971 }
972 pathSet.pushBack(path);
973}
974
975void
977 STPath const& currentPath, // The path to build from
978 STPathSet& incompletePaths, // The set of partial paths we add to
979 int addFlags,
980 std::function<bool(void)> const& continueCallback)
981{
982 auto const& pathEnd = currentPath.empty() ? source_ : currentPath.back();
983 auto const& uEndPathAsset = pathEnd.getPathAsset();
984 auto const& uEndIssuer = pathEnd.getIssuerID();
985 auto const& uEndAccount = pathEnd.getAccountID();
986 bool const bOnXRP = isXRP(uEndPathAsset);
987
988 // Does pathfinding really need to get this to
989 // a gateway (the issuer of the destination amount)
990 // rather than the ultimate destination?
991 bool const hasEffectiveDestination = effectiveDst_ != dstAccount_;
992
993 JLOG(j_.trace()) << "addLink< flags=" << addFlags << " onXRP=" << bOnXRP
994 << " completePaths size=" << completePaths_.size();
995 JLOG(j_.trace()) << currentPath.getJson(JsonOptions::Values::None);
996
997 if ((addFlags & kAfAddAccounts) != 0u)
998 {
999 // add accounts
1000 if (bOnXRP)
1001 {
1002 if (dstAmount_.native() && !currentPath.empty())
1003 { // non-default path to XRP destination
1004 JLOG(j_.trace()) << "complete path found ax: "
1005 << currentPath.getJson(JsonOptions::Values::None);
1006 addUniquePath(completePaths_, currentPath);
1007 }
1008 }
1009 else
1010 {
1011 // search for accounts to add
1012 auto const sleEnd = ledger_->read(keylet::account(uEndAccount));
1013
1014 if (sleEnd)
1015 {
1016 bool const bRequireAuth(sleEnd->isFlag(lsfRequireAuth));
1017 bool const bIsEndAsset(uEndPathAsset == dstAmount_.asset());
1018 bool const bIsNoRippleOut(isNoRippleOut(currentPath));
1019 bool const bDestOnly((addFlags & kAfAcLast) != 0u);
1020
1021 AccountCandidates candidates;
1022
1023 auto forAssets = [&]<typename AssetType>(AssetType const& assets) {
1024 candidates.reserve(assets.size());
1025
1026 static constexpr bool kIsLine =
1028 static constexpr bool kIsMpt =
1030
1031 for (auto const& asset : assets)
1032 {
1033 if (continueCallback && !continueCallback())
1034 return;
1035 auto const& acct = [&]() constexpr {
1036 if constexpr (kIsLine)
1037 return asset.getAccountIDPeer();
1038 // Unlike trustline, MPT is not bidirectional
1039 if constexpr (kIsMpt)
1040 return getMPTIssuer(asset);
1041 }();
1042 auto const direction = [&]() constexpr -> LineDirection {
1043 if constexpr (kIsLine)
1044 return asset.getDirectionPeer();
1045 // incoming for MPT since MPT doesn't support
1046 // rippling (see LineDirection comments)
1048 }();
1049
1050 if (hasEffectiveDestination && (acct == dstAccount_))
1051 {
1052 // We skipped the gateway
1053 continue;
1054 }
1055
1056 bool const bToDestination = acct == effectiveDst_;
1057
1058 if (bDestOnly && !bToDestination)
1059 {
1060 continue;
1061 }
1062
1063 auto const correctAsset = [&]() {
1064 if constexpr (kIsLine)
1065 {
1066 return uEndPathAsset.get<Currency>() ==
1067 asset.getLimit().template get<Issue>().currency;
1068 }
1069 if constexpr (kIsMpt)
1070 {
1071 return uEndPathAsset.get<MPTID>() == asset.getMptID();
1072 }
1073 }();
1074 auto checkAsset = [&]() {
1075 if constexpr (kIsLine)
1076 {
1077 return (
1078 (asset.getBalance() <= beast::kZero &&
1079 (!asset.getLimitPeer() ||
1080 -asset.getBalance() >= asset.getLimitPeer() ||
1081 (bRequireAuth && !asset.getAuth()))) ||
1082 (bIsNoRippleOut && asset.getNoRipple()));
1083 }
1084 if constexpr (kIsMpt)
1085 {
1086 return asset.isZeroBalance() || asset.isMaxedOut() ||
1087 requireAuth(*ledger_, MPTIssue{asset}, acct);
1088 }
1089 };
1090
1091 if (correctAsset && !currentPath.hasSeen(acct, uEndPathAsset, acct))
1092 {
1093 // path is for correct currency and has not been
1094 // seen
1095 if (checkAsset())
1096 {
1097 // Can't leave on this path
1098 continue;
1099 }
1100 if (bToDestination)
1101 {
1102 // destination is always worth trying
1103 if (uEndPathAsset == dstAmount_.asset())
1104 {
1105 // this is a complete path
1106 if (!currentPath.empty())
1107 {
1108 JLOG(j_.trace())
1109 << "complete path found ae: "
1110 << currentPath.getJson(JsonOptions::Values::None);
1111 addUniquePath(completePaths_, currentPath);
1112 }
1113 }
1114 else if (!bDestOnly)
1115 {
1116 // this is a high-priority candidate
1117 candidates.push_back({AccountCandidate::kHighPriority, acct});
1118 }
1119 }
1120 else if (acct == srcAccount_)
1121 {
1122 // going back to the source is bad
1123 }
1124 else
1125 {
1126 // save this candidate
1127 int const out = getPathsOut(
1128 uEndPathAsset,
1129 acct,
1130 direction,
1131 bIsEndAsset,
1133 continueCallback);
1134 if (out != 0)
1135 candidates.push_back({out, acct});
1136 }
1137 }
1138 }
1139 };
1140
1141 uEndPathAsset.visit(
1142 [&](Currency const&) {
1143 if (auto const lines = rLCache_->getRippleLines(
1144 uEndAccount,
1146 {
1147 forAssets(*lines);
1148 }
1149 },
1150 [&](MPTID const&) {
1151 if (auto const mpts = rLCache_->getMPTs(uEndAccount))
1152 {
1153 forAssets(*mpts);
1154 }
1155 });
1156
1157 if (!candidates.empty())
1158 {
1160 candidates,
1161 std::bind(
1162 compareAccountCandidate,
1163 ledger_->seq(),
1164 std::placeholders::_1,
1165 std::placeholders::_2));
1166
1167 int count = candidates.size();
1168 // allow more paths from source
1169 if ((count > 10) && (uEndAccount != srcAccount_))
1170 {
1171 count = 10;
1172 }
1173 else if (count > 50)
1174 {
1175 count = 50;
1176 }
1177
1178 auto it = candidates.begin();
1179 while (count-- != 0)
1180 {
1181 if (continueCallback && !continueCallback())
1182 return;
1183 // Add accounts to incompletePaths
1184 STPathElement const pathElement(
1185 STPathElement::TypeAccount, it->account, uEndPathAsset, it->account);
1186 incompletePaths.assembleAdd(currentPath, pathElement);
1187 ++it;
1188 }
1189 }
1190 }
1191 else
1192 {
1193 JLOG(j_.warn()) << "Path ends on non-existent issuer";
1194 }
1195 }
1196 }
1197 if ((addFlags & kAfAddBooks) != 0u)
1198 {
1199 // add order books
1200 if ((addFlags & kAfObXrp) != 0u)
1201 {
1202 // to XRP only
1203 if (!bOnXRP &&
1204 app_.getOrderBookDB().isBookToXRP(
1205 assetFromPathAsset(uEndPathAsset, uEndIssuer), domain_))
1206 {
1207 STPathElement const pathElement(
1209 incompletePaths.assembleAdd(currentPath, pathElement);
1210 }
1211 }
1212 else
1213 {
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";
1218
1219 for (auto const& book : books)
1220 {
1221 if (continueCallback && !continueCallback())
1222 return;
1223 if (!currentPath.hasSeen(xrpAccount(), book.out, book.out.getIssuer()) &&
1224 !issueMatchesOrigin(book.out) &&
1225 (!bDestOnly || equalTokens(book.out, dstAmount_.asset())))
1226 {
1227 STPath newPath(currentPath);
1228
1229 if (isXRP(book.out))
1230 { // to XRP
1231
1232 // add the order book itself
1233 newPath.emplaceBack(
1235
1236 if (isXRP(dstAmount_.asset()))
1237 {
1238 // destination is XRP, add account and path is
1239 // complete
1240 JLOG(j_.trace()) << "complete path found bx: "
1241 << currentPath.getJson(JsonOptions::Values::None);
1242 addUniquePath(completePaths_, newPath);
1243 }
1244 else
1245 {
1246 incompletePaths.pushBack(newPath);
1247 }
1248 }
1249 else if (!currentPath.hasSeen(
1250 book.out.getIssuer(), book.out, book.out.getIssuer()))
1251 {
1252 auto const assetType = book.out.holds<Issue>() ? STPathElement::TypeCurrency
1254 // Don't want the book if we've already seen the issuer
1255 // book -> account -> book
1256 if ((newPath.size() >= 2) && (newPath.back().isAccount()) &&
1257 (newPath[newPath.size() - 2].isOffer()))
1258 {
1259 // replace the redundant account with the order book
1260 newPath[newPath.size() - 1] = STPathElement(
1261 assetType | STPathElement::TypeIssuer,
1262 xrpAccount(),
1263 book.out,
1264 book.out.getIssuer());
1265 }
1266 else
1267 {
1268 // add the order book
1269 newPath.emplaceBack(
1270 assetType | STPathElement::TypeIssuer,
1271 xrpAccount(),
1272 book.out,
1273 book.out.getIssuer());
1274 }
1275
1276 if (hasEffectiveDestination && book.out.getIssuer() == dstAccount_ &&
1277 equalTokens(book.out, dstAmount_.asset()))
1278 {
1279 // We skipped a required issuer
1280 }
1281 else if (
1282 book.out.getIssuer() == effectiveDst_ &&
1283 equalTokens(book.out, dstAmount_.asset()))
1284 { // with the destination account, this path is
1285 // complete
1286 JLOG(j_.trace()) << "complete path found ba: "
1287 << currentPath.getJson(JsonOptions::Values::None);
1288 addUniquePath(completePaths_, newPath);
1289 }
1290 else
1291 {
1292 // add issuer's account, path still incomplete
1293 incompletePaths.assembleAdd(
1294 newPath,
1297 book.out.getIssuer(),
1298 book.out,
1299 book.out.getIssuer()));
1300 }
1301 }
1302 }
1303 }
1304 }
1305 }
1306}
1307
1308namespace {
1309
1311makePath(char const* string)
1312{
1314
1315 while (true)
1316 {
1317 // NOLINTNEXTLINE(bugprone-switch-missing-default-case)
1318 switch (*string++)
1319 {
1320 case 's': // source
1322 break;
1323
1324 case 'a': // accounts
1326 break;
1327
1328 case 'b': // books
1330 break;
1331
1332 case 'x': // xrp book
1334 break;
1335
1336 case 'f': // book to final currency
1338 break;
1339
1340 case 'd':
1341 // Destination (with account, if required and not already
1342 // present).
1344 break;
1345
1346 case 0:
1347 return ret;
1348 }
1349 }
1350}
1351
1352void
1353fillPaths(Pathfinder::PaymentType type, PathCostList const& costs)
1354{
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)});
1359}
1360
1361} // namespace
1362
1363// Costs:
1364// 0 = minimum to make some payments possible
1365// 1 = include trivial paths to make common cases work
1366// 4 = normal fast search level
1367// 7 = normal slow search level
1368// 10 = most aggressive
1369
1370void
1372{
1373 // CAUTION: Do not include rules that build default paths
1374
1375 gPathTable.clear();
1376 fillPaths(PaymentType::XrpToXrp, {});
1377 /* cspell: disable */
1378
1379 fillPaths(
1381 {{.cost = 1, .path = "sfd"}, // source -> book -> gateway
1382 {.cost = 3, .path = "sfad"}, // source -> book -> account -> destination
1383 {.cost = 5, .path = "sfaad"}, // source -> book -> account -> account -> destination
1384 {.cost = 6, .path = "sbfd"}, // source -> book -> book -> destination
1385 {.cost = 8, .path = "sbafd"}, // source -> book -> account -> book -> destination
1386 {.cost = 9, .path = "sbfad"}, // source -> book -> book -> account -> destination
1387 {.cost = 10, .path = "sbafad"}});
1388
1389 fillPaths(
1391 {{.cost = 1, .path = "sxd"}, // gateway buys XRP
1392 {.cost = 2, .path = "saxd"}, // source -> gateway -> book(XRP) -> dest
1393 {.cost = 6, .path = "saaxd"},
1394 {.cost = 7, .path = "sbxd"},
1395 {.cost = 8, .path = "sabxd"},
1396 {.cost = 9, .path = "sabaxd"}});
1397
1398 // non-XRP to non-XRP (same currency)
1399 fillPaths(
1401 {
1402 {.cost = 1, .path = "sad"}, // source -> gateway -> destination
1403 {.cost = 1, .path = "sfd"}, // source -> book -> destination
1404 {.cost = 4, .path = "safd"}, // source -> gateway -> book -> destination
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"}, // source -> gateway -> book to XRP -> book ->
1411 // destination
1412 {.cost = 6, .path = "saxfad"},
1413 {.cost = 6, .path = "sabfd"}, // source -> gateway -> book -> book -> destination
1414 {.cost = 7, .path = "saaad"},
1415 });
1416
1417 // non-XRP to non-XRP (different currency)
1418 fillPaths(
1420 {
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"},
1433 });
1434 /* cspell: enable */
1435}
1436
1437} // namespace xrpl
T append(T... args)
T back(T... args)
T begin(T... args)
T bind(T... args)
constexpr auto visit(Visitors &&... visitors) const -> decltype(auto)
Definition Asset.h:107
constexpr TIss const & get() const
AccountID const & getIssuer() const
Definition Asset.cpp:21
A currency issued by an account.
Definition Issue.h:13
Currency currency
Definition Issue.h:15
constexpr bool isXRP() const
Definition PathAsset.h:90
constexpr bool holds() const
Definition PathAsset.h:69
T const & get() const
STAmount dstAmount_
Definition Pathfinder.h:174
static std::uint32_t const kAfObLast
Definition Pathfinder.h:208
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
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_
Definition Pathfinder.h:193
bool issueMatchesOrigin(Asset const &)
STAmount srcAmount_
Definition Pathfinder.h:177
static std::uint32_t const kAfAddAccounts
Definition Pathfinder.h:199
AccountID dstAccount_
Definition Pathfinder.h:172
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:175
std::vector< NodeType > PathType
Definition Pathfinder.h:73
std::unique_ptr< LoadEvent > loadEvent_
Definition Pathfinder.h:185
AccountID srcAccount_
Definition Pathfinder.h:171
static std::uint32_t const kAfObXrp
Definition Pathfinder.h:205
std::optional< AccountID > srcIssuer_
Definition Pathfinder.h:176
std::map< PathType, STPathSet > paths_
Definition Pathfinder.h:191
std::optional< uint256 > domain_
Definition Pathfinder.h:182
static std::uint32_t const kAfAcLast
Definition Pathfinder.h:211
std::shared_ptr< AssetCache > rLCache_
Definition Pathfinder.h:186
STPathSet completePaths_
Definition Pathfinder.h:189
STPathSet & addPathsForType(PathType const &type, std::function< bool(void)> const &continueCallback)
STPathElement source_
Definition Pathfinder.h:188
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:195
bool isNoRippleOut(STPath const &currentPath)
beast::Journal const j_
Definition Pathfinder.h:196
static void initPathTable()
STAmount remainingAmount_
The amount remaining from srcAccount_ after the default liquidity has been removed.
Definition Pathfinder.h:180
static std::uint32_t const kAfAddBooks
Definition Pathfinder.h:202
AccountID effectiveDst_
Definition Pathfinder.h:173
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:190
std::shared_ptr< ReadView const > ledger_
Definition Pathfinder.h:184
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:478
auto getNodeType() const
Definition STPathSet.h:319
AccountID const & getAccountID() const
Definition STPathSet.h:375
Currency const & getCurrency() const
Definition STPathSet.h:387
bool hasCurrency() const
Definition STPathSet.h:349
bool assembleAdd(STPath const &base, STPathElement const &tail)
std::vector< STPath >::size_type size() const
Definition STPathSet.h:528
json::Value getJson(JsonOptions) const override
void pushBack(STPath const &e)
Definition STPathSet.h:540
bool empty() const
Definition STPathSet.h:534
std::vector< STPathElement >::size_type size() const
Definition STPathSet.h:424
bool hasSeen(AccountID const &account, PathAsset const &asset, AccountID const &issuer) const
bool empty() const
Definition STPathSet.h:430
void pushBack(STPathElement const &e)
Definition STPathSet.h:436
std::vector< STPathElement >::const_iterator end() const
Definition STPathSet.h:455
void emplaceBack(Args &&... args)
Definition STPathSet.h:443
std::vector< STPathElement >::const_reference back() const
Definition STPathSet.h:467
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
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:186
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:241
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:70
bool isIndividualFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptIssue)
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:162
BaseUInt< 160, detail::CurrencyTag > Currency
Currency is a hash representing a specific currency.
Definition UintTypes.h:36
std::ostream & operator<<(std::ostream &out, BaseUInt< Bits, Tag > const &u)
Definition base_uint.h:648
static STPath removeIssuer(STPath const &path)
AccountID getMPTIssuer(MPTID const &mptid)
Definition MPTIssue.h:84
std::string transToken(TER code)
Definition TER.cpp:247
Currency const & xrpCurrency()
XRP currency.
Definition UintTypes.cpp:99
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:633
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:21
@ JtPathFind
Definition Job.h:65
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:44
std::uint64_t getRate(STAmount const &offerOut, STAmount const &offerIn)
Definition STAmount.cpp:422
bool convertAllCheck(STAmount const &a)
void addUniquePath(STPathSet &pathSet, STPath const &path)
@ TapNone
Definition ApplyView.h:13
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:28
bool isTesSuccess(TER x) noexcept
Definition TER.h:663
TERSubset< CanCvtToTER > TER
Definition TER.h:634
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:240
constexpr bool equalTokens(Asset const &lhs, Asset const &rhs)
Definition Asset.h:275
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)