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";
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 if (!pathSet.contains(path))
966 {
967 pathSet.pushBack(path);
968 }
969}
970
971void
973 STPath const& currentPath, // The path to build from
974 STPathSet& incompletePaths, // The set of partial paths we add to
975 int addFlags,
976 std::function<bool(void)> const& continueCallback)
977{
978 auto const& pathEnd = currentPath.empty() ? source_ : currentPath.back();
979 auto const& uEndPathAsset = pathEnd.getPathAsset();
980 auto const& uEndIssuer = pathEnd.getIssuerID();
981 auto const& uEndAccount = pathEnd.getAccountID();
982 bool const bOnXRP = isXRP(uEndPathAsset);
983
984 // Does pathfinding really need to get this to
985 // a gateway (the issuer of the destination amount)
986 // rather than the ultimate destination?
987 bool const hasEffectiveDestination = effectiveDst_ != dstAccount_;
988
989 JLOG(j_.trace()) << "addLink< flags=" << addFlags << " onXRP=" << bOnXRP
990 << " completePaths size=" << completePaths_.size();
991 JLOG(j_.trace()) << currentPath.getJson(JsonOptions::Values::None);
992
993 if ((addFlags & kAfAddAccounts) != 0u)
994 {
995 // add accounts
996 if (bOnXRP)
997 {
998 if (dstAmount_.native() && !currentPath.empty())
999 { // non-default path to XRP destination
1000 JLOG(j_.trace()) << "complete path found ax: "
1001 << currentPath.getJson(JsonOptions::Values::None);
1002 addUniquePath(completePaths_, currentPath);
1003 }
1004 }
1005 else
1006 {
1007 // search for accounts to add
1008 auto const sleEnd = ledger_->read(keylet::account(uEndAccount));
1009
1010 if (sleEnd)
1011 {
1012 bool const bRequireAuth(sleEnd->isFlag(lsfRequireAuth));
1013 bool const bIsEndAsset(uEndPathAsset == dstAmount_.asset());
1014 bool const bIsNoRippleOut(isNoRippleOut(currentPath));
1015 bool const bDestOnly((addFlags & kAfAcLast) != 0u);
1016
1017 AccountCandidates candidates;
1018
1019 auto forAssets = [&]<typename AssetType>(AssetType const& assets) {
1020 candidates.reserve(assets.size());
1021
1022 static constexpr bool kIsLine =
1024 static constexpr bool kIsMpt =
1026
1027 for (auto const& asset : assets)
1028 {
1029 if (continueCallback && !continueCallback())
1030 return;
1031 auto const& acct = [&]() constexpr {
1032 if constexpr (kIsLine)
1033 return asset.getAccountIDPeer();
1034 // Unlike trustline, MPT is not bidirectional
1035 if constexpr (kIsMpt)
1036 return getMPTIssuer(asset);
1037 }();
1038 auto const direction = [&]() constexpr -> LineDirection {
1039 if constexpr (kIsLine)
1040 return asset.getDirectionPeer();
1041 // incoming for MPT since MPT doesn't support
1042 // rippling (see LineDirection comments)
1044 }();
1045
1046 if (hasEffectiveDestination && (acct == dstAccount_))
1047 {
1048 // We skipped the gateway
1049 continue;
1050 }
1051
1052 bool const bToDestination = acct == effectiveDst_;
1053
1054 if (bDestOnly && !bToDestination)
1055 {
1056 continue;
1057 }
1058
1059 auto const correctAsset = [&]() {
1060 if constexpr (kIsLine)
1061 {
1062 return uEndPathAsset.get<Currency>() ==
1063 asset.getLimit().template get<Issue>().currency;
1064 }
1065 if constexpr (kIsMpt)
1066 {
1067 return uEndPathAsset.get<MPTID>() == asset.getMptID();
1068 }
1069 }();
1070 auto checkAsset = [&]() {
1071 if constexpr (kIsLine)
1072 {
1073 return (
1074 (asset.getBalance() <= beast::kZero &&
1075 (!asset.getLimitPeer() ||
1076 -asset.getBalance() >= asset.getLimitPeer() ||
1077 (bRequireAuth && !asset.getAuth()))) ||
1078 (bIsNoRippleOut && asset.getNoRipple()));
1079 }
1080 if constexpr (kIsMpt)
1081 {
1082 return asset.isZeroBalance() || asset.isMaxedOut() ||
1083 requireAuth(*ledger_, MPTIssue{asset}, acct);
1084 }
1085 };
1086
1087 if (correctAsset && !currentPath.hasSeen(acct, uEndPathAsset, acct))
1088 {
1089 // path is for correct currency and has not been
1090 // seen
1091 if (checkAsset())
1092 {
1093 // Can't leave on this path
1094 continue;
1095 }
1096 if (bToDestination)
1097 {
1098 // destination is always worth trying
1099 if (uEndPathAsset == dstAmount_.asset())
1100 {
1101 // this is a complete path
1102 if (!currentPath.empty())
1103 {
1104 JLOG(j_.trace())
1105 << "complete path found ae: "
1106 << currentPath.getJson(JsonOptions::Values::None);
1107 addUniquePath(completePaths_, currentPath);
1108 }
1109 }
1110 else if (!bDestOnly)
1111 {
1112 // this is a high-priority candidate
1113 candidates.push_back({AccountCandidate::kHighPriority, acct});
1114 }
1115 }
1116 else if (acct == srcAccount_)
1117 {
1118 // going back to the source is bad
1119 }
1120 else
1121 {
1122 // save this candidate
1123 int const out = getPathsOut(
1124 uEndPathAsset,
1125 acct,
1126 direction,
1127 bIsEndAsset,
1129 continueCallback);
1130 if (out != 0)
1131 candidates.push_back({out, acct});
1132 }
1133 }
1134 }
1135 };
1136
1137 uEndPathAsset.visit(
1138 [&](Currency const&) {
1139 if (auto const lines = rLCache_->getRippleLines(
1140 uEndAccount,
1142 {
1143 forAssets(*lines);
1144 }
1145 },
1146 [&](MPTID const&) {
1147 if (auto const mpts = rLCache_->getMPTs(uEndAccount))
1148 {
1149 forAssets(*mpts);
1150 }
1151 });
1152
1153 if (!candidates.empty())
1154 {
1156 candidates,
1157 [seq = ledger_->seq()](
1158 AccountCandidate const& first, AccountCandidate const& second) {
1159 return compareAccountCandidate(seq, first, second);
1160 });
1161
1162 int count = candidates.size();
1163 // allow more paths from source
1164 if ((count > 10) && (uEndAccount != srcAccount_))
1165 {
1166 count = 10;
1167 }
1168 else if (count > 50)
1169 {
1170 count = 50;
1171 }
1172
1173 auto it = candidates.begin();
1174 while (count-- != 0)
1175 {
1176 if (continueCallback && !continueCallback())
1177 return;
1178 // Add accounts to incompletePaths
1179 STPathElement const pathElement(
1180 STPathElement::TypeAccount, it->account, uEndPathAsset, it->account);
1181 incompletePaths.assembleAdd(currentPath, pathElement);
1182 ++it;
1183 }
1184 }
1185 }
1186 else
1187 {
1188 JLOG(j_.warn()) << "Path ends on non-existent issuer";
1189 }
1190 }
1191 }
1192 if ((addFlags & kAfAddBooks) != 0u)
1193 {
1194 // add order books
1195 if ((addFlags & kAfObXrp) != 0u)
1196 {
1197 // to XRP only
1198 if (!bOnXRP &&
1199 app_.getOrderBookDB().isBookToXRP(
1200 assetFromPathAsset(uEndPathAsset, uEndIssuer), domain_))
1201 {
1202 STPathElement const pathElement(
1204 incompletePaths.assembleAdd(currentPath, pathElement);
1205 }
1206 }
1207 else
1208 {
1209 bool const bDestOnly = (addFlags & kAfObLast) != 0;
1210 auto books = app_.getOrderBookDB().getBooksByTakerPays(
1211 assetFromPathAsset(uEndPathAsset, uEndIssuer), domain_);
1212 JLOG(j_.trace()) << books.size() << " books found from this currency/issuer";
1213
1214 for (auto const& book : books)
1215 {
1216 if (continueCallback && !continueCallback())
1217 return;
1218 if (!currentPath.hasSeen(xrpAccount(), book.out, book.out.getIssuer()) &&
1219 !issueMatchesOrigin(book.out) &&
1220 (!bDestOnly || equalTokens(book.out, dstAmount_.asset())))
1221 {
1222 STPath newPath(currentPath);
1223
1224 if (isXRP(book.out))
1225 { // to XRP
1226
1227 // add the order book itself
1228 newPath.emplaceBack(
1230
1231 if (isXRP(dstAmount_.asset()))
1232 {
1233 // destination is XRP, add account and path is
1234 // complete
1235 JLOG(j_.trace()) << "complete path found bx: "
1236 << currentPath.getJson(JsonOptions::Values::None);
1237 addUniquePath(completePaths_, newPath);
1238 }
1239 else
1240 {
1241 incompletePaths.pushBack(newPath);
1242 }
1243 }
1244 else if (!currentPath.hasSeen(
1245 book.out.getIssuer(), book.out, book.out.getIssuer()))
1246 {
1247 auto const assetType = book.out.holds<Issue>() ? STPathElement::TypeCurrency
1249 // Don't want the book if we've already seen the issuer
1250 // book -> account -> book
1251 if ((newPath.size() >= 2) && (newPath.back().isAccount()) &&
1252 (newPath[newPath.size() - 2].isOffer()))
1253 {
1254 // replace the redundant account with the order book
1255 newPath[newPath.size() - 1] = STPathElement(
1256 assetType | STPathElement::TypeIssuer,
1257 xrpAccount(),
1258 book.out,
1259 book.out.getIssuer());
1260 }
1261 else
1262 {
1263 // add the order book
1264 newPath.emplaceBack(
1265 assetType | STPathElement::TypeIssuer,
1266 xrpAccount(),
1267 book.out,
1268 book.out.getIssuer());
1269 }
1270
1271 if (hasEffectiveDestination && book.out.getIssuer() == dstAccount_ &&
1272 equalTokens(book.out, dstAmount_.asset()))
1273 {
1274 // We skipped a required issuer
1275 }
1276 else if (
1277 book.out.getIssuer() == effectiveDst_ &&
1278 equalTokens(book.out, dstAmount_.asset()))
1279 { // with the destination account, this path is
1280 // complete
1281 JLOG(j_.trace()) << "complete path found ba: "
1282 << currentPath.getJson(JsonOptions::Values::None);
1283 addUniquePath(completePaths_, newPath);
1284 }
1285 else
1286 {
1287 // add issuer's account, path still incomplete
1288 incompletePaths.assembleAdd(
1289 newPath,
1292 book.out.getIssuer(),
1293 book.out,
1294 book.out.getIssuer()));
1295 }
1296 }
1297 }
1298 }
1299 }
1300 }
1301}
1302
1303namespace {
1304
1306makePath(char const* string)
1307{
1309
1310 while (true)
1311 {
1312 // NOLINTNEXTLINE(bugprone-switch-missing-default-case)
1313 switch (*string++)
1314 {
1315 case 's': // source
1317 break;
1318
1319 case 'a': // accounts
1321 break;
1322
1323 case 'b': // books
1325 break;
1326
1327 case 'x': // xrp book
1329 break;
1330
1331 case 'f': // book to final currency
1333 break;
1334
1335 case 'd':
1336 // Destination (with account, if required and not already
1337 // present).
1339 break;
1340
1341 case 0:
1342 return ret;
1343 }
1344 }
1345}
1346
1347void
1348fillPaths(Pathfinder::PaymentType type, PathCostList const& costs)
1349{
1350 auto& list = gPathTable[type];
1351 XRPL_ASSERT(list.empty(), "xrpl::fillPaths : empty paths");
1352 for (auto& cost : costs)
1353 list.push_back({.searchLevel = cost.cost, .type = makePath(cost.path)});
1354}
1355
1356} // namespace
1357
1358// Costs:
1359// 0 = minimum to make some payments possible
1360// 1 = include trivial paths to make common cases work
1361// 4 = normal fast search level
1362// 7 = normal slow search level
1363// 10 = most aggressive
1364
1365void
1367{
1368 // CAUTION: Do not include rules that build default paths
1369
1370 gPathTable.clear();
1371 fillPaths(PaymentType::XrpToXrp, {});
1372 /* cspell: disable */
1373
1374 fillPaths(
1376 {{.cost = 1, .path = "sfd"}, // source -> book -> gateway
1377 {.cost = 3, .path = "sfad"}, // source -> book -> account -> destination
1378 {.cost = 5, .path = "sfaad"}, // source -> book -> account -> account -> destination
1379 {.cost = 6, .path = "sbfd"}, // source -> book -> book -> destination
1380 {.cost = 8, .path = "sbafd"}, // source -> book -> account -> book -> destination
1381 {.cost = 9, .path = "sbfad"}, // source -> book -> book -> account -> destination
1382 {.cost = 10, .path = "sbafad"}});
1383
1384 fillPaths(
1386 {{.cost = 1, .path = "sxd"}, // gateway buys XRP
1387 {.cost = 2, .path = "saxd"}, // source -> gateway -> book(XRP) -> dest
1388 {.cost = 6, .path = "saaxd"},
1389 {.cost = 7, .path = "sbxd"},
1390 {.cost = 8, .path = "sabxd"},
1391 {.cost = 9, .path = "sabaxd"}});
1392
1393 // non-XRP to non-XRP (same currency)
1394 fillPaths(
1396 {
1397 {.cost = 1, .path = "sad"}, // source -> gateway -> destination
1398 {.cost = 1, .path = "sfd"}, // source -> book -> destination
1399 {.cost = 4, .path = "safd"}, // source -> gateway -> book -> destination
1400 {.cost = 4, .path = "sfad"},
1401 {.cost = 5, .path = "saad"},
1402 {.cost = 5, .path = "sbfd"},
1403 {.cost = 6, .path = "sxfad"},
1404 {.cost = 6, .path = "safad"},
1405 {.cost = 6, .path = "saxfd"}, // source -> gateway -> book to XRP -> book ->
1406 // destination
1407 {.cost = 6, .path = "saxfad"},
1408 {.cost = 6, .path = "sabfd"}, // source -> gateway -> book -> book -> destination
1409 {.cost = 7, .path = "saaad"},
1410 });
1411
1412 // non-XRP to non-XRP (different currency)
1413 fillPaths(
1415 {
1416 {.cost = 1, .path = "sfad"},
1417 {.cost = 1, .path = "safd"},
1418 {.cost = 3, .path = "safad"},
1419 {.cost = 4, .path = "sxfd"},
1420 {.cost = 5, .path = "saxfd"},
1421 {.cost = 5, .path = "sxfad"},
1422 {.cost = 5, .path = "sbfd"},
1423 {.cost = 6, .path = "saxfad"},
1424 {.cost = 6, .path = "sabfd"},
1425 {.cost = 7, .path = "saafd"},
1426 {.cost = 8, .path = "saafad"},
1427 {.cost = 9, .path = "safaad"},
1428 });
1429 /* cspell: enable */
1430}
1431
1432} // 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:97
constexpr bool holds() const
Definition PathAsset.h:76
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
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:214
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
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
std::optional< uint256 > domain_
Definition Pathfinder.h:203
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)
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
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
auto getNodeType() const
Definition STPathSet.h:340
AccountID const & getAccountID() const
Definition STPathSet.h:396
Currency const & getCurrency() const
Definition STPathSet.h:408
bool hasCurrency() const
Definition STPathSet.h:370
bool assembleAdd(STPath const &base, STPathElement const &tail)
std::vector< STPath >::size_type size() const
Definition STPathSet.h:537
json::Value getJson(JsonOptions) const override
void pushBack(STPath const &e)
Definition STPathSet.h:549
bool empty() const
Definition STPathSet.h:543
bool contains(STPath const &path) const
Definition STPathSet.h:564
std::vector< STPathElement >::size_type size() const
Definition STPathSet.h:439
bool hasSeen(AccountID const &account, PathAsset const &asset, AccountID const &issuer) const
bool empty() const
Definition STPathSet.h:445
void pushBack(STPathElement const &e)
Definition STPathSet.h:451
std::vector< STPathElement >::const_iterator end() const
Definition STPathSet.h:470
void emplaceBack(Args &&... args)
Definition STPathSet.h:458
std::vector< STPathElement >::const_reference back() const
Definition STPathSet.h:482
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:198
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:253
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:164
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:666
static STPath removeIssuer(STPath const &path)
AccountID getMPTIssuer(MPTID const &mptid)
Definition MPTIssue.h:95
std::string transToken(TER code)
Definition TER.cpp:251
Currency const & xrpCurrency()
XRP currency.
Definition UintTypes.cpp:99
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
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:422
bool convertAllCheck(STAmount const &a)
void addUniquePath(STPathSet &pathSet, STPath const &path)
@ 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:676
TERSubset< CanCvtToTER > TER
Definition TER.h:647
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:245
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)