rippled
Pathfinder.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2012, 2013 Ripple Labs Inc.
5 
6  Permission to use, copy, modify, and/or distribute this software for any
7  purpose with or without fee is hereby granted, provided that the above
8  copyright notice and this permission notice appear in all copies.
9 
10  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 //==============================================================================
19 
20 #include <ripple/app/ledger/OrderBookDB.h>
21 #include <ripple/app/main/Application.h>
22 #include <ripple/app/paths/Pathfinder.h>
23 #include <ripple/app/paths/RippleCalc.h>
24 #include <ripple/app/paths/RippleLineCache.h>
25 #include <ripple/app/paths/impl/PathfinderUtils.h>
26 #include <ripple/basics/Log.h>
27 #include <ripple/basics/join.h>
28 #include <ripple/core/Config.h>
29 #include <ripple/core/JobQueue.h>
30 #include <ripple/json/to_string.h>
31 #include <ripple/ledger/PaymentSandbox.h>
32 
33 #include <tuple>
34 
35 /*
36 
37 Core Pathfinding Engine
38 
39 The pathfinding request is identified by category, XRP to XRP, XRP to
40 non-XRP, non-XRP to XRP, same currency non-XRP to non-XRP, cross-currency
41 non-XRP to non-XRP. For each category, there is a table of paths that the
42 pathfinder searches for. Complete paths are collected.
43 
44 Each complete path is then rated and sorted. Paths with no or trivial
45 liquidity are dropped. Otherwise, paths are sorted based on quality,
46 liquidity, and path length.
47 
48 Path slots are filled in quality (ratio of out to in) order, with the
49 exception that the last path must have enough liquidity to complete the
50 payment (assuming no liquidity overlap). In addition, if no selected path
51 is capable of providing enough liquidity to complete the payment by itself,
52 an extra "covering" path is returned.
53 
54 The selected paths are then tested to determine if they can complete the
55 payment and, if so, at what cost. If they fail and a covering path was
56 found, the test is repeated with the covering path. If this succeeds, the
57 final paths and the estimated cost are returned.
58 
59 The engine permits the search depth to be selected and the paths table
60 includes the depth at which each path type is found. A search depth of zero
61 causes no searching to be done. Extra paths can also be injected, and this
62 should be used to preserve previously-found paths across invokations for the
63 same path request (particularly if the search depth may change).
64 
65 */
66 
67 namespace ripple {
68 
69 namespace {
70 
71 // This is an arbitrary cutoff, and it might cause us to miss other
72 // good paths with this arbitrary cut off.
73 constexpr std::size_t PATHFINDER_MAX_COMPLETE_PATHS = 1000;
74 
75 struct AccountCandidate
76 {
77  int priority;
78  AccountID account;
79 
80  static const int highPriority = 10000;
81 };
82 
83 bool
84 compareAccountCandidate(
85  std::uint32_t seq,
86  AccountCandidate const& first,
87  AccountCandidate const& second)
88 {
89  if (first.priority < second.priority)
90  return false;
91 
92  if (first.account > second.account)
93  return true;
94 
95  return (first.priority ^ seq) < (second.priority ^ seq);
96 }
97 
98 using AccountCandidates = std::vector<AccountCandidate>;
99 
100 struct CostedPath
101 {
102  int searchLevel;
104 };
105 
106 using CostedPathList = std::vector<CostedPath>;
107 
109 
110 struct PathCost
111 {
112  int cost;
113  char const* path;
114 };
115 using PathCostList = std::vector<PathCost>;
116 
117 static PathTable mPathTable;
118 
120 pathTypeToString(Pathfinder::PathType const& type)
121 {
122  std::string ret;
123 
124  for (auto const& node : type)
125  {
126  switch (node)
127  {
129  ret.append("s");
130  break;
132  ret.append("a");
133  break;
135  ret.append("b");
136  break;
138  ret.append("x");
139  break;
141  ret.append("f");
142  break;
144  ret.append("d");
145  break;
146  }
147  }
148 
149  return ret;
150 }
151 
152 // Return the smallest amount of useful liquidity for a given amount, and the
153 // total number of paths we have to evaluate.
154 STAmount
155 smallestUsefulAmount(STAmount const& amount, int maxPaths)
156 {
157  return divide(amount, STAmount(maxPaths + 2), amount.issue());
158 }
159 } // namespace
160 
163  AccountID const& uSrcAccount,
164  AccountID const& uDstAccount,
165  Currency const& uSrcCurrency,
166  std::optional<AccountID> const& uSrcIssuer,
167  STAmount const& saDstAmount,
168  std::optional<STAmount> const& srcAmount,
169  Application& app)
170  : mSrcAccount(uSrcAccount)
171  , mDstAccount(uDstAccount)
172  , mEffectiveDst(
173  isXRP(saDstAmount.getIssuer()) ? uDstAccount
174  : saDstAmount.getIssuer())
175  , mDstAmount(saDstAmount)
176  , mSrcCurrency(uSrcCurrency)
177  , mSrcIssuer(uSrcIssuer)
178  , mSrcAmount(srcAmount.value_or(STAmount(
179  {uSrcCurrency,
180  uSrcIssuer.value_or(
181  isXRP(uSrcCurrency) ? xrpAccount() : uSrcAccount)},
182  1u,
183  0,
184  true)))
185  , convert_all_(convertAllCheck(mDstAmount))
186  , mLedger(cache->getLedger())
187  , mRLCache(cache)
188  , app_(app)
189  , j_(app.journal("Pathfinder"))
190 {
191  assert(!uSrcIssuer || isXRP(uSrcCurrency) == isXRP(uSrcIssuer.value()));
192 }
193 
194 bool
196  int searchLevel,
197  std::function<bool(void)> const& continueCallback)
198 {
199  JLOG(j_.trace()) << "findPaths start";
200  if (mDstAmount == beast::zero)
201  {
202  // No need to send zero money.
203  JLOG(j_.debug()) << "Destination amount was zero.";
204  mLedger.reset();
205  return false;
206 
207  // TODO(tom): why do we reset the ledger just in this case and the one
208  // below - why don't we do it each time we return false?
209  }
210 
213  {
214  // No need to send to same account with same currency.
215  JLOG(j_.debug()) << "Tried to send to same issuer";
216  mLedger.reset();
217  return false;
218  }
219 
220  if (mSrcAccount == mEffectiveDst &&
222  {
223  // Default path might work, but any path would loop
224  return true;
225  }
226 
228  auto currencyIsXRP = isXRP(mSrcCurrency);
229 
230  bool useIssuerAccount = mSrcIssuer && !currencyIsXRP && !isXRP(*mSrcIssuer);
231  auto& account = useIssuerAccount ? *mSrcIssuer : mSrcAccount;
232  auto issuer = currencyIsXRP ? AccountID() : account;
233  mSource = STPathElement(account, mSrcCurrency, issuer);
234  auto issuerString =
236  JLOG(j_.trace()) << "findPaths>"
237  << " mSrcAccount=" << mSrcAccount
238  << " mDstAccount=" << mDstAccount
239  << " mDstAmount=" << mDstAmount.getFullText()
240  << " mSrcCurrency=" << mSrcCurrency
241  << " mSrcIssuer=" << issuerString;
242 
243  if (!mLedger)
244  {
245  JLOG(j_.debug()) << "findPaths< no ledger";
246  return false;
247  }
248 
249  bool bSrcXrp = isXRP(mSrcCurrency);
250  bool bDstXrp = isXRP(mDstAmount.getCurrency());
251 
252  if (!mLedger->exists(keylet::account(mSrcAccount)))
253  {
254  // We can't even start without a source account.
255  JLOG(j_.debug()) << "invalid source account";
256  return false;
257  }
258 
259  if ((mEffectiveDst != mDstAccount) &&
261  {
262  JLOG(j_.debug()) << "Non-existent gateway";
263  return false;
264  }
265 
266  if (!mLedger->exists(keylet::account(mDstAccount)))
267  {
268  // Can't find the destination account - we must be funding a new
269  // account.
270  if (!bDstXrp)
271  {
272  JLOG(j_.debug()) << "New account not being funded in XRP ";
273  return false;
274  }
275 
276  auto const reserve = STAmount(mLedger->fees().accountReserve(0));
277  if (mDstAmount < reserve)
278  {
279  JLOG(j_.debug())
280  << "New account not getting enough funding: " << mDstAmount
281  << " < " << reserve;
282  return false;
283  }
284  }
285 
286  // Now compute the payment type from the types of the source and destination
287  // currencies.
288  PaymentType paymentType;
289  if (bSrcXrp && bDstXrp)
290  {
291  // XRP -> XRP
292  JLOG(j_.debug()) << "XRP to XRP payment";
293  paymentType = pt_XRP_to_XRP;
294  }
295  else if (bSrcXrp)
296  {
297  // XRP -> non-XRP
298  JLOG(j_.debug()) << "XRP to non-XRP payment";
299  paymentType = pt_XRP_to_nonXRP;
300  }
301  else if (bDstXrp)
302  {
303  // non-XRP -> XRP
304  JLOG(j_.debug()) << "non-XRP to XRP payment";
305  paymentType = pt_nonXRP_to_XRP;
306  }
307  else if (mSrcCurrency == mDstAmount.getCurrency())
308  {
309  // non-XRP -> non-XRP - Same currency
310  JLOG(j_.debug()) << "non-XRP to non-XRP - same currency";
311  paymentType = pt_nonXRP_to_same;
312  }
313  else
314  {
315  // non-XRP to non-XRP - Different currency
316  JLOG(j_.debug()) << "non-XRP to non-XRP - cross currency";
317  paymentType = pt_nonXRP_to_nonXRP;
318  }
319 
320  // Now iterate over all paths for that paymentType.
321  for (auto const& costedPath : mPathTable[paymentType])
322  {
323  if (continueCallback && !continueCallback())
324  return false;
325  // Only use paths with at most the current search level.
326  if (costedPath.searchLevel <= searchLevel)
327  {
328  JLOG(j_.trace()) << "findPaths trying payment type " << paymentType;
329  addPathsForType(costedPath.type, continueCallback);
330 
331  if (mCompletePaths.size() > PATHFINDER_MAX_COMPLETE_PATHS)
332  break;
333  }
334  }
335 
336  JLOG(j_.debug()) << mCompletePaths.size() << " complete paths found";
337 
338  // Even if we find no paths, default paths may work, and we don't check them
339  // currently.
340  return true;
341 }
342 
343 TER
345  STPath const& path, // IN: The path to check.
346  STAmount const& minDstAmount, // IN: The minimum output this path must
347  // deliver to be worth keeping.
348  STAmount& amountOut, // OUT: The actual liquidity along the path.
349  uint64_t& qualityOut) const // OUT: The returned initial quality
350 {
351  STPathSet pathSet;
352  pathSet.push_back(path);
353 
354  path::RippleCalc::Input rcInput;
355  rcInput.defaultPathsAllowed = false;
356 
357  PaymentSandbox sandbox(&*mLedger, tapNONE);
358 
359  try
360  {
361  // Compute a path that provides at least the minimum liquidity.
362  if (convert_all_)
363  rcInput.partialPaymentAllowed = true;
364 
366  sandbox,
367  mSrcAmount,
368  minDstAmount,
369  mDstAccount,
370  mSrcAccount,
371  pathSet,
372  app_.logs(),
373  &rcInput);
374  // If we can't get even the minimum liquidity requested, we're done.
375  if (rc.result() != tesSUCCESS)
376  return rc.result();
377 
378  qualityOut = getRate(rc.actualAmountOut, rc.actualAmountIn);
379  amountOut = rc.actualAmountOut;
380 
381  if (!convert_all_)
382  {
383  // Now try to compute the remaining liquidity.
384  rcInput.partialPaymentAllowed = true;
386  sandbox,
387  mSrcAmount,
388  mDstAmount - amountOut,
389  mDstAccount,
390  mSrcAccount,
391  pathSet,
392  app_.logs(),
393  &rcInput);
394 
395  // If we found further liquidity, add it into the result.
396  if (rc.result() == tesSUCCESS)
397  amountOut += rc.actualAmountOut;
398  }
399 
400  return tesSUCCESS;
401  }
402  catch (std::exception const& e)
403  {
404  JLOG(j_.info()) << "checkpath: exception (" << e.what() << ") "
405  << path.getJson(JsonOptions::none);
406  return tefEXCEPTION;
407  }
408 }
409 
410 void
412  int maxPaths,
413  std::function<bool(void)> const& continueCallback)
414 {
416 
417  // Must subtract liquidity in default path from remaining amount.
418  try
419  {
420  PaymentSandbox sandbox(&*mLedger, tapNONE);
421 
422  path::RippleCalc::Input rcInput;
423  rcInput.partialPaymentAllowed = true;
425  sandbox,
426  mSrcAmount,
428  mDstAccount,
429  mSrcAccount,
430  STPathSet(),
431  app_.logs(),
432  &rcInput);
433 
434  if (rc.result() == tesSUCCESS)
435  {
436  JLOG(j_.debug())
437  << "Default path contributes: " << rc.actualAmountIn;
438  mRemainingAmount -= rc.actualAmountOut;
439  }
440  else
441  {
442  JLOG(j_.debug())
443  << "Default path fails: " << transToken(rc.result());
444  }
445  }
446  catch (std::exception const&)
447  {
448  JLOG(j_.debug()) << "Default path causes exception";
449  }
450 
451  rankPaths(maxPaths, mCompletePaths, mPathRanks, continueCallback);
452 }
453 
454 static bool
455 isDefaultPath(STPath const& path)
456 {
457  // FIXME: default paths can consist of more than just an account:
458  //
459  // JoelKatz writes:
460  // So the test for whether a path is a default path is incorrect. I'm not
461  // sure it's worth the complexity of fixing though. If we are going to fix
462  // it, I'd suggest doing it this way:
463  //
464  // 1) Compute the default path, probably by using 'expandPath' to expand an
465  // empty path. 2) Chop off the source and destination nodes.
466  //
467  // 3) In the pathfinding loop, if the source issuer is not the sender,
468  // reject all paths that don't begin with the issuer's account node or match
469  // the path we built at step 2.
470  return path.size() == 1;
471 }
472 
473 static STPath
474 removeIssuer(STPath const& path)
475 {
476  // This path starts with the issuer, which is already implied
477  // so remove the head node
478  STPath ret;
479 
480  for (auto it = path.begin() + 1; it != path.end(); ++it)
481  ret.push_back(*it);
482 
483  return ret;
484 }
485 
486 // For each useful path in the input path set,
487 // create a ranking entry in the output vector of path ranks
488 void
490  int maxPaths,
491  STPathSet const& paths,
492  std::vector<PathRank>& rankedPaths,
493  std::function<bool(void)> const& continueCallback)
494 {
495  JLOG(j_.trace()) << "rankPaths with " << paths.size() << " candidates, and "
496  << maxPaths << " maximum";
497  rankedPaths.clear();
498  rankedPaths.reserve(paths.size());
499 
500  auto const saMinDstAmount = [&]() -> STAmount {
501  if (!convert_all_)
502  {
503  // Ignore paths that move only very small amounts.
504  return smallestUsefulAmount(mDstAmount, maxPaths);
505  }
506 
507  // On convert_all_ partialPaymentAllowed will be set to true
508  // and requiring a huge amount will find the highest liquidity.
509  return largestAmount(mDstAmount);
510  }();
511 
512  for (int i = 0; i < paths.size(); ++i)
513  {
514  if (continueCallback && !continueCallback())
515  return;
516  auto const& currentPath = paths[i];
517  if (!currentPath.empty())
518  {
519  STAmount liquidity;
520  uint64_t uQuality;
521  auto const resultCode = getPathLiquidity(
522  currentPath, saMinDstAmount, liquidity, uQuality);
523  if (resultCode != tesSUCCESS)
524  {
525  JLOG(j_.debug())
526  << "findPaths: dropping : " << transToken(resultCode)
527  << ": " << currentPath.getJson(JsonOptions::none);
528  }
529  else
530  {
531  JLOG(j_.debug()) << "findPaths: quality: " << uQuality << ": "
532  << currentPath.getJson(JsonOptions::none);
533 
534  rankedPaths.push_back(
535  {uQuality, currentPath.size(), liquidity, i});
536  }
537  }
538  }
539 
540  // Sort paths by:
541  // cost of path (when considering quality)
542  // width of path
543  // length of path
544  // A better PathRank is lower, best are sorted to the beginning.
545  std::sort(
546  rankedPaths.begin(),
547  rankedPaths.end(),
548  [&](Pathfinder::PathRank const& a, Pathfinder::PathRank const& b) {
549  // 1) Higher quality (lower cost) is better
550  if (!convert_all_ && a.quality != b.quality)
551  return a.quality < b.quality;
552 
553  // 2) More liquidity (higher volume) is better
554  if (a.liquidity != b.liquidity)
555  return a.liquidity > b.liquidity;
556 
557  // 3) Shorter paths are better
558  if (a.length != b.length)
559  return a.length < b.length;
560 
561  // 4) Tie breaker
562  return a.index > b.index;
563  });
564 }
565 
566 STPathSet
568  int maxPaths,
569  STPath& fullLiquidityPath,
570  STPathSet const& extraPaths,
571  AccountID const& srcIssuer,
572  std::function<bool(void)> const& continueCallback)
573 {
574  JLOG(j_.debug()) << "findPaths: " << mCompletePaths.size() << " paths and "
575  << extraPaths.size() << " extras";
576 
577  if (mCompletePaths.empty() && extraPaths.empty())
578  return mCompletePaths;
579 
580  assert(fullLiquidityPath.empty());
581  const bool issuerIsSender =
582  isXRP(mSrcCurrency) || (srcIssuer == mSrcAccount);
583 
584  std::vector<PathRank> extraPathRanks;
585  rankPaths(maxPaths, extraPaths, extraPathRanks, continueCallback);
586 
587  STPathSet bestPaths;
588 
589  // The best PathRanks are now at the start. Pull off enough of them to
590  // fill bestPaths, then look through the rest for the best individual
591  // path that can satisfy the entire liquidity - if one exists.
592  STAmount remaining = mRemainingAmount;
593 
594  auto pathsIterator = mPathRanks.begin();
595  auto extraPathsIterator = extraPathRanks.begin();
596 
597  while (pathsIterator != mPathRanks.end() ||
598  extraPathsIterator != extraPathRanks.end())
599  {
600  if (continueCallback && !continueCallback())
601  break;
602  bool usePath = false;
603  bool useExtraPath = false;
604 
605  if (pathsIterator == mPathRanks.end())
606  useExtraPath = true;
607  else if (extraPathsIterator == extraPathRanks.end())
608  usePath = true;
609  else if (extraPathsIterator->quality < pathsIterator->quality)
610  useExtraPath = true;
611  else if (extraPathsIterator->quality > pathsIterator->quality)
612  usePath = true;
613  else if (extraPathsIterator->liquidity > pathsIterator->liquidity)
614  useExtraPath = true;
615  else if (extraPathsIterator->liquidity < pathsIterator->liquidity)
616  usePath = true;
617  else
618  {
619  // Risk is high they have identical liquidity
620  useExtraPath = true;
621  usePath = true;
622  }
623 
624  auto& pathRank = usePath ? *pathsIterator : *extraPathsIterator;
625 
626  auto const& path = usePath ? mCompletePaths[pathRank.index]
627  : extraPaths[pathRank.index];
628 
629  if (useExtraPath)
630  ++extraPathsIterator;
631 
632  if (usePath)
633  ++pathsIterator;
634 
635  auto iPathsLeft = maxPaths - bestPaths.size();
636  if (!(iPathsLeft > 0 || fullLiquidityPath.empty()))
637  break;
638 
639  if (path.empty())
640  {
641  assert(false);
642  continue;
643  }
644 
645  bool startsWithIssuer = false;
646 
647  if (!issuerIsSender && usePath)
648  {
649  // Need to make sure path matches issuer constraints
650  if (isDefaultPath(path) || path.front().getAccountID() != srcIssuer)
651  {
652  continue;
653  }
654 
655  startsWithIssuer = true;
656  }
657 
658  if (iPathsLeft > 1 ||
659  (iPathsLeft > 0 && pathRank.liquidity >= remaining))
660  // last path must fill
661  {
662  --iPathsLeft;
663  remaining -= pathRank.liquidity;
664  bestPaths.push_back(startsWithIssuer ? removeIssuer(path) : path);
665  }
666  else if (
667  iPathsLeft == 0 && pathRank.liquidity >= mDstAmount &&
668  fullLiquidityPath.empty())
669  {
670  // We found an extra path that can move the whole amount.
671  fullLiquidityPath = (startsWithIssuer ? removeIssuer(path) : path);
672  JLOG(j_.debug()) << "Found extra full path: "
673  << fullLiquidityPath.getJson(JsonOptions::none);
674  }
675  else
676  {
677  JLOG(j_.debug()) << "Skipping a non-filling path: "
678  << path.getJson(JsonOptions::none);
679  }
680  }
681 
682  if (remaining > beast::zero)
683  {
684  assert(fullLiquidityPath.empty());
685  JLOG(j_.info()) << "Paths could not send " << remaining << " of "
686  << mDstAmount;
687  }
688  else
689  {
690  JLOG(j_.debug()) << "findPaths: RESULTS: "
691  << bestPaths.getJson(JsonOptions::none);
692  }
693  return bestPaths;
694 }
695 
696 bool
698 {
699  bool matchingCurrency = (issue.currency == mSrcCurrency);
700  bool matchingAccount = isXRP(issue.currency) ||
701  (mSrcIssuer && issue.account == mSrcIssuer) ||
702  issue.account == mSrcAccount;
703 
704  return matchingCurrency && matchingAccount;
705 }
706 
707 int
709  Currency const& currency,
710  AccountID const& account,
711  bool isDstCurrency,
712  AccountID const& dstAccount,
713  std::function<bool(void)> const& continueCallback)
714 {
715  Issue const issue(currency, account);
716 
717  auto [it, inserted] = mPathsOutCountMap.emplace(issue, 0);
718 
719  // If it was already present, return the stored number of paths
720  if (!inserted)
721  return it->second;
722 
723  auto sleAccount = mLedger->read(keylet::account(account));
724 
725  if (!sleAccount)
726  return 0;
727 
728  int aFlags = sleAccount->getFieldU32(sfFlags);
729  bool const bAuthRequired = (aFlags & lsfRequireAuth) != 0;
730  bool const bFrozen = ((aFlags & lsfGlobalFreeze) != 0);
731 
732  int count = 0;
733 
734  if (!bFrozen)
735  {
736  count = app_.getOrderBookDB().getBookSize(issue);
737 
738  for (auto const& rspEntry : mRLCache->getRippleLines(account))
739  {
740  if (currency != rspEntry.getLimit().getCurrency())
741  {
742  }
743  else if (
744  rspEntry.getBalance() <= beast::zero &&
745  (!rspEntry.getLimitPeer() ||
746  -rspEntry.getBalance() >= rspEntry.getLimitPeer() ||
747  (bAuthRequired && !rspEntry.getAuth())))
748  {
749  }
750  else if (isDstCurrency && dstAccount == rspEntry.getAccountIDPeer())
751  {
752  count += 10000; // count a path to the destination extra
753  }
754  else if (rspEntry.getNoRipplePeer())
755  {
756  // This probably isn't a useful path out
757  }
758  else if (rspEntry.getFreezePeer())
759  {
760  // Not a useful path out
761  }
762  else
763  {
764  ++count;
765  }
766  }
767  }
768  it->second = count;
769  return count;
770 }
771 
772 void
774  STPathSet const& currentPaths, // The paths to build from
775  STPathSet& incompletePaths, // The set of partial paths we add to
776  int addFlags,
777  std::function<bool(void)> const& continueCallback)
778 {
779  JLOG(j_.debug()) << "addLink< on " << currentPaths.size()
780  << " source(s), flags=" << addFlags;
781  for (auto const& path : currentPaths)
782  {
783  if (continueCallback && !continueCallback())
784  return;
785  addLink(path, incompletePaths, addFlags, continueCallback);
786  }
787 }
788 
789 STPathSet&
791  PathType const& pathType,
792  std::function<bool(void)> const& continueCallback)
793 {
794  JLOG(j_.warn()) << "addPathsForType "
795  << CollectionAndDelimiter(pathType, ", ");
796  // See if the set of paths for this type already exists.
797  auto it = mPaths.find(pathType);
798  if (it != mPaths.end())
799  return it->second;
800 
801  // Otherwise, if the type has no nodes, return the empty path.
802  if (pathType.empty())
803  return mPaths[pathType];
804  if (continueCallback && !continueCallback())
805  return mPaths[{}];
806 
807  // Otherwise, get the paths for the parent PathType by calling
808  // addPathsForType recursively.
809  PathType parentPathType = pathType;
810  parentPathType.pop_back();
811 
812  STPathSet const& parentPaths =
813  addPathsForType(parentPathType, continueCallback);
814  STPathSet& pathsOut = mPaths[pathType];
815 
816  JLOG(j_.debug()) << "getPaths< adding onto '"
817  << pathTypeToString(parentPathType) << "' to get '"
818  << pathTypeToString(pathType) << "'";
819 
820  int initialSize = mCompletePaths.size();
821 
822  // Add the last NodeType to the lists.
823  auto nodeType = pathType.back();
824  switch (nodeType)
825  {
826  case nt_SOURCE:
827  // Source must always be at the start, so pathsOut has to be empty.
828  assert(pathsOut.empty());
829  pathsOut.push_back(STPath());
830  break;
831 
832  case nt_ACCOUNTS:
833  addLinks(parentPaths, pathsOut, afADD_ACCOUNTS, continueCallback);
834  break;
835 
836  case nt_BOOKS:
837  addLinks(parentPaths, pathsOut, afADD_BOOKS, continueCallback);
838  break;
839 
840  case nt_XRP_BOOK:
841  addLinks(
842  parentPaths,
843  pathsOut,
845  continueCallback);
846  break;
847 
848  case nt_DEST_BOOK:
849  addLinks(
850  parentPaths,
851  pathsOut,
853  continueCallback);
854  break;
855 
856  case nt_DESTINATION:
857  // FIXME: What if a different issuer was specified on the
858  // destination amount?
859  // TODO(tom): what does this even mean? Should it be a JIRA?
860  addLinks(
861  parentPaths,
862  pathsOut,
864  continueCallback);
865  break;
866  }
867 
868  if (mCompletePaths.size() != initialSize)
869  {
870  JLOG(j_.debug()) << (mCompletePaths.size() - initialSize)
871  << " complete paths added";
872  }
873 
874  JLOG(j_.debug()) << "getPaths> " << pathsOut.size()
875  << " partial paths found";
876  return pathsOut;
877 }
878 
879 bool
881  AccountID const& fromAccount,
882  AccountID const& toAccount,
883  Currency const& currency)
884 {
885  auto sleRipple =
886  mLedger->read(keylet::line(toAccount, fromAccount, currency));
887 
888  auto const flag(
889  (toAccount > fromAccount) ? lsfHighNoRipple : lsfLowNoRipple);
890 
891  return sleRipple && (sleRipple->getFieldU32(sfFlags) & flag);
892 }
893 
894 // Does this path end on an account-to-account link whose last account has
895 // set "no ripple" on the link?
896 bool
898 {
899  // Must have at least one link.
900  if (currentPath.empty())
901  return false;
902 
903  // Last link must be an account.
904  STPathElement const& endElement = currentPath.back();
905  if (!(endElement.getNodeType() & STPathElement::typeAccount))
906  return false;
907 
908  // If there's only one item in the path, return true if that item specifies
909  // no ripple on the output. A path with no ripple on its output can't be
910  // followed by a link with no ripple on its input.
911  auto const& fromAccount = (currentPath.size() == 1)
912  ? mSrcAccount
913  : (currentPath.end() - 2)->getAccountID();
914  auto const& toAccount = endElement.getAccountID();
915  return isNoRipple(fromAccount, toAccount, endElement.getCurrency());
916 }
917 
918 void
919 addUniquePath(STPathSet& pathSet, STPath const& path)
920 {
921  // TODO(tom): building an STPathSet this way is quadratic in the size
922  // of the STPathSet!
923  for (auto const& p : pathSet)
924  {
925  if (p == path)
926  return;
927  }
928  pathSet.push_back(path);
929 }
930 
931 void
933  const STPath& currentPath, // The path to build from
934  STPathSet& incompletePaths, // The set of partial paths we add to
935  int addFlags,
936  std::function<bool(void)> const& continueCallback)
937 {
938  auto const& pathEnd = currentPath.empty() ? mSource : currentPath.back();
939  auto const& uEndCurrency = pathEnd.getCurrency();
940  auto const& uEndIssuer = pathEnd.getIssuerID();
941  auto const& uEndAccount = pathEnd.getAccountID();
942  bool const bOnXRP = uEndCurrency.isZero();
943 
944  // Does pathfinding really need to get this to
945  // a gateway (the issuer of the destination amount)
946  // rather than the ultimate destination?
947  bool const hasEffectiveDestination = mEffectiveDst != mDstAccount;
948 
949  JLOG(j_.trace()) << "addLink< flags=" << addFlags << " onXRP=" << bOnXRP
950  << " completePaths size=" << mCompletePaths.size();
951  JLOG(j_.trace()) << currentPath.getJson(JsonOptions::none);
952 
953  if (addFlags & afADD_ACCOUNTS)
954  {
955  // add accounts
956  if (bOnXRP)
957  {
958  if (mDstAmount.native() && !currentPath.empty())
959  { // non-default path to XRP destination
960  JLOG(j_.trace()) << "complete path found ax: "
961  << currentPath.getJson(JsonOptions::none);
962  addUniquePath(mCompletePaths, currentPath);
963  }
964  }
965  else
966  {
967  // search for accounts to add
968  auto const sleEnd = mLedger->read(keylet::account(uEndAccount));
969 
970  if (sleEnd)
971  {
972  bool const bRequireAuth(
973  sleEnd->getFieldU32(sfFlags) & lsfRequireAuth);
974  bool const bIsEndCurrency(
975  uEndCurrency == mDstAmount.getCurrency());
976  bool const bIsNoRippleOut(isNoRippleOut(currentPath));
977  bool const bDestOnly(addFlags & afAC_LAST);
978 
979  auto& rippleLines(mRLCache->getRippleLines(uEndAccount));
980 
981  AccountCandidates candidates;
982  candidates.reserve(rippleLines.size());
983 
984  for (auto const& rs : rippleLines)
985  {
986  if (continueCallback && !continueCallback())
987  return;
988  auto const& acct = rs.getAccountIDPeer();
989 
990  if (hasEffectiveDestination && (acct == mDstAccount))
991  {
992  // We skipped the gateway
993  continue;
994  }
995 
996  bool bToDestination = acct == mEffectiveDst;
997 
998  if (bDestOnly && !bToDestination)
999  {
1000  continue;
1001  }
1002 
1003  if ((uEndCurrency == rs.getLimit().getCurrency()) &&
1004  !currentPath.hasSeen(acct, uEndCurrency, acct))
1005  {
1006  // path is for correct currency and has not been seen
1007  if (rs.getBalance() <= beast::zero &&
1008  (!rs.getLimitPeer() ||
1009  -rs.getBalance() >= rs.getLimitPeer() ||
1010  (bRequireAuth && !rs.getAuth())))
1011  {
1012  // path has no credit
1013  }
1014  else if (bIsNoRippleOut && rs.getNoRipple())
1015  {
1016  // Can't leave on this path
1017  }
1018  else if (bToDestination)
1019  {
1020  // destination is always worth trying
1021  if (uEndCurrency == mDstAmount.getCurrency())
1022  {
1023  // this is a complete path
1024  if (!currentPath.empty())
1025  {
1026  JLOG(j_.trace())
1027  << "complete path found ae: "
1028  << currentPath.getJson(
1030  addUniquePath(mCompletePaths, currentPath);
1031  }
1032  }
1033  else if (!bDestOnly)
1034  {
1035  // this is a high-priority candidate
1036  candidates.push_back(
1037  {AccountCandidate::highPriority, acct});
1038  }
1039  }
1040  else if (acct == mSrcAccount)
1041  {
1042  // going back to the source is bad
1043  }
1044  else
1045  {
1046  // save this candidate
1047  int out = getPathsOut(
1048  uEndCurrency,
1049  acct,
1050  bIsEndCurrency,
1051  mEffectiveDst,
1052  continueCallback);
1053  if (out)
1054  candidates.push_back({out, acct});
1055  }
1056  }
1057  }
1058 
1059  if (!candidates.empty())
1060  {
1061  std::sort(
1062  candidates.begin(),
1063  candidates.end(),
1064  std::bind(
1065  compareAccountCandidate,
1066  mLedger->seq(),
1067  std::placeholders::_1,
1068  std::placeholders::_2));
1069 
1070  int count = candidates.size();
1071  // allow more paths from source
1072  if ((count > 10) && (uEndAccount != mSrcAccount))
1073  count = 10;
1074  else if (count > 50)
1075  count = 50;
1076 
1077  auto it = candidates.begin();
1078  while (count-- != 0)
1079  {
1080  if (continueCallback && !continueCallback())
1081  return;
1082  // Add accounts to incompletePaths
1083  STPathElement pathElement(
1085  it->account,
1086  uEndCurrency,
1087  it->account);
1088  incompletePaths.assembleAdd(currentPath, pathElement);
1089  ++it;
1090  }
1091  }
1092  }
1093  else
1094  {
1095  JLOG(j_.warn()) << "Path ends on non-existent issuer";
1096  }
1097  }
1098  }
1099  if (addFlags & afADD_BOOKS)
1100  {
1101  // add order books
1102  if (addFlags & afOB_XRP)
1103  {
1104  // to XRP only
1105  if (!bOnXRP &&
1106  app_.getOrderBookDB().isBookToXRP({uEndCurrency, uEndIssuer}))
1107  {
1108  STPathElement pathElement(
1110  xrpAccount(),
1111  xrpCurrency(),
1112  xrpAccount());
1113  incompletePaths.assembleAdd(currentPath, pathElement);
1114  }
1115  }
1116  else
1117  {
1118  bool bDestOnly = (addFlags & afOB_LAST) != 0;
1119  auto books = app_.getOrderBookDB().getBooksByTakerPays(
1120  {uEndCurrency, uEndIssuer});
1121  JLOG(j_.trace())
1122  << books.size() << " books found from this currency/issuer";
1123 
1124  for (auto const& book : books)
1125  {
1126  if (continueCallback && !continueCallback())
1127  return;
1128  if (!currentPath.hasSeen(
1129  xrpAccount(), book.out.currency, book.out.account) &&
1130  !issueMatchesOrigin(book.out) &&
1131  (!bDestOnly ||
1132  (book.out.currency == mDstAmount.getCurrency())))
1133  {
1134  STPath newPath(currentPath);
1135 
1136  if (book.out.currency.isZero())
1137  { // to XRP
1138 
1139  // add the order book itself
1140  newPath.emplace_back(
1142  xrpAccount(),
1143  xrpCurrency(),
1144  xrpAccount());
1145 
1146  if (mDstAmount.getCurrency().isZero())
1147  {
1148  // destination is XRP, add account and path is
1149  // complete
1150  JLOG(j_.trace())
1151  << "complete path found bx: "
1152  << currentPath.getJson(JsonOptions::none);
1153  addUniquePath(mCompletePaths, newPath);
1154  }
1155  else
1156  incompletePaths.push_back(newPath);
1157  }
1158  else if (!currentPath.hasSeen(
1159  book.out.account,
1160  book.out.currency,
1161  book.out.account))
1162  {
1163  // Don't want the book if we've already seen the issuer
1164  // book -> account -> book
1165  if ((newPath.size() >= 2) &&
1166  (newPath.back().isAccount()) &&
1167  (newPath[newPath.size() - 2].isOffer()))
1168  {
1169  // replace the redundant account with the order book
1170  newPath[newPath.size() - 1] = STPathElement(
1173  xrpAccount(),
1174  book.out.currency,
1175  book.out.account);
1176  }
1177  else
1178  {
1179  // add the order book
1180  newPath.emplace_back(
1183  xrpAccount(),
1184  book.out.currency,
1185  book.out.account);
1186  }
1187 
1188  if (hasEffectiveDestination &&
1189  book.out.account == mDstAccount &&
1190  book.out.currency == mDstAmount.getCurrency())
1191  {
1192  // We skipped a required issuer
1193  }
1194  else if (
1195  book.out.account == mEffectiveDst &&
1196  book.out.currency == mDstAmount.getCurrency())
1197  { // with the destination account, this path is
1198  // complete
1199  JLOG(j_.trace())
1200  << "complete path found ba: "
1201  << currentPath.getJson(JsonOptions::none);
1202  addUniquePath(mCompletePaths, newPath);
1203  }
1204  else
1205  {
1206  // add issuer's account, path still incomplete
1207  incompletePaths.assembleAdd(
1208  newPath,
1209  STPathElement(
1211  book.out.account,
1212  book.out.currency,
1213  book.out.account));
1214  }
1215  }
1216  }
1217  }
1218  }
1219  }
1220 }
1221 
1222 namespace {
1223 
1225 makePath(char const* string)
1226 {
1228 
1229  while (true)
1230  {
1231  switch (*string++)
1232  {
1233  case 's': // source
1235  break;
1236 
1237  case 'a': // accounts
1239  break;
1240 
1241  case 'b': // books
1243  break;
1244 
1245  case 'x': // xrp book
1247  break;
1248 
1249  case 'f': // book to final currency
1251  break;
1252 
1253  case 'd':
1254  // Destination (with account, if required and not already
1255  // present).
1257  break;
1258 
1259  case 0:
1260  return ret;
1261  }
1262  }
1263 }
1264 
1265 void
1266 fillPaths(Pathfinder::PaymentType type, PathCostList const& costs)
1267 {
1268  auto& list = mPathTable[type];
1269  assert(list.empty());
1270  for (auto& cost : costs)
1271  list.push_back({cost.cost, makePath(cost.path)});
1272 }
1273 
1274 } // namespace
1275 
1276 // Costs:
1277 // 0 = minimum to make some payments possible
1278 // 1 = include trivial paths to make common cases work
1279 // 4 = normal fast search level
1280 // 7 = normal slow search level
1281 // 10 = most agressive
1282 
1283 void
1285 {
1286  // CAUTION: Do not include rules that build default paths
1287 
1288  mPathTable.clear();
1289  fillPaths(pt_XRP_to_XRP, {});
1290 
1291  fillPaths(
1293  {{1, "sfd"}, // source -> book -> gateway
1294  {3, "sfad"}, // source -> book -> account -> destination
1295  {5, "sfaad"}, // source -> book -> account -> account -> destination
1296  {6, "sbfd"}, // source -> book -> book -> destination
1297  {8, "sbafd"}, // source -> book -> account -> book -> destination
1298  {9, "sbfad"}, // source -> book -> book -> account -> destination
1299  {10, "sbafad"}});
1300 
1301  fillPaths(
1303  {{1, "sxd"}, // gateway buys XRP
1304  {2, "saxd"}, // source -> gateway -> book(XRP) -> dest
1305  {6, "saaxd"},
1306  {7, "sbxd"},
1307  {8, "sabxd"},
1308  {9, "sabaxd"}});
1309 
1310  // non-XRP to non-XRP (same currency)
1311  fillPaths(
1313  {
1314  {1, "sad"}, // source -> gateway -> destination
1315  {1, "sfd"}, // source -> book -> destination
1316  {4, "safd"}, // source -> gateway -> book -> destination
1317  {4, "sfad"},
1318  {5, "saad"},
1319  {5, "sbfd"},
1320  {6, "sxfad"},
1321  {6, "safad"},
1322  {6, "saxfd"}, // source -> gateway -> book to XRP -> book ->
1323  // destination
1324  {6, "saxfad"},
1325  {6, "sabfd"}, // source -> gateway -> book -> book -> destination
1326  {7, "saaad"},
1327  });
1328 
1329  // non-XRP to non-XRP (different currency)
1330  fillPaths(
1332  {
1333  {1, "sfad"},
1334  {1, "safd"},
1335  {3, "safad"},
1336  {4, "sxfd"},
1337  {5, "saxfd"},
1338  {5, "sxfad"},
1339  {5, "sbfd"},
1340  {6, "saxfad"},
1341  {6, "sabfd"},
1342  {7, "saafd"},
1343  {8, "saafad"},
1344  {9, "safaad"},
1345  });
1346 }
1347 
1348 } // namespace ripple
ripple::Pathfinder::addPathsForType
STPathSet & addPathsForType(PathType const &type, std::function< bool(void)> const &continueCallback)
Definition: Pathfinder.cpp:790
ripple::Pathfinder::pt_nonXRP_to_nonXRP
@ pt_nonXRP_to_nonXRP
Definition: Pathfinder.h:102
ripple::Pathfinder::mSrcCurrency
Currency mSrcCurrency
Definition: Pathfinder.h:199
ripple::Application
Definition: Application.h:115
ripple::Pathfinder::mSrcAccount
AccountID mSrcAccount
Definition: Pathfinder.h:195
ripple::Application::getOrderBookDB
virtual OrderBookDB & getOrderBookDB()=0
ripple::Pathfinder::PathType
std::vector< NodeType > PathType
Definition: Pathfinder.h:93
ripple::STPath::push_back
void push_back(STPathElement const &e)
Definition: STPathSet.h:405
std::bind
T bind(T... args)
ripple::Pathfinder::addLink
void addLink(STPath const &currentPath, STPathSet &incompletePaths, int addFlags, std::function< bool(void)> const &continueCallback)
Definition: Pathfinder.cpp:932
ripple::Issue
A currency issued by an account.
Definition: Issue.h:34
ripple::lsfGlobalFreeze
@ lsfGlobalFreeze
Definition: LedgerFormats.h:231
std::string
STL class.
std::shared_ptr
STL class.
ripple::Pathfinder::afADD_ACCOUNTS
static const std::uint32_t afADD_ACCOUNTS
Definition: Pathfinder.h:222
std::exception
STL class.
beast::Journal::trace
Stream trace() const
Severity stream access functions.
Definition: Journal.h:309
ripple::jtPATH_FIND
@ jtPATH_FIND
Definition: Job.h:85
ripple::PaymentSandbox
A wrapper which makes credits unavailable to balances.
Definition: PaymentSandbox.h:112
ripple::removeIssuer
static STPath removeIssuer(STPath const &path)
Definition: Pathfinder.cpp:474
ripple::Pathfinder::addLinks
void addLinks(STPathSet const &currentPaths, STPathSet &incompletePaths, int addFlags, std::function< bool(void)> const &continueCallback)
Definition: Pathfinder.cpp:773
std::vector::reserve
T reserve(T... args)
ripple::STPath::size
std::vector< STPathElement >::size_type size() const
Definition: STPathSet.h:393
ripple::lsfLowNoRipple
@ lsfLowNoRipple
Definition: LedgerFormats.h:245
ripple::Pathfinder::computePathRanks
void computePathRanks(int maxPaths, std::function< bool(void)> const &continueCallback={})
Compute the rankings of the paths.
Definition: Pathfinder.cpp:411
std::vector
STL class.
ripple::Pathfinder::afOB_LAST
static const std::uint32_t afOB_LAST
Definition: Pathfinder.h:231
ripple::STPathElement::getCurrency
Currency const & getCurrency() const
Definition: STPathSet.h:361
std::optional::value_or
T value_or(T... args)
ripple::path::RippleCalc::Input
Definition: RippleCalc.h:46
ripple::isDefaultPath
static bool isDefaultPath(STPath const &path)
Definition: Pathfinder.cpp:455
ripple::Pathfinder::nt_XRP_BOOK
@ nt_XRP_BOOK
Definition: Pathfinder.h:87
ripple::Pathfinder::mPaths
std::map< PathType, STPathSet > mPaths
Definition: Pathfinder.h:214
ripple::Issue::currency
Currency currency
Definition: Issue.h:37
beast::Journal::warn
Stream warn() const
Definition: Journal.h:327
ripple::transToken
std::string transToken(TER code)
Definition: TER.cpp:207
ripple::Pathfinder::mDstAmount
STAmount mDstAmount
Definition: Pathfinder.h:198
ripple::Pathfinder::app_
Application & app_
Definition: Pathfinder.h:218
tuple
ripple::Pathfinder::initPathTable
static void initPathTable()
Definition: Pathfinder.cpp:1284
ripple::Pathfinder::findPaths
bool findPaths(int searchLevel, std::function< bool(void)> const &continueCallback={})
Definition: Pathfinder.cpp:195
std::vector::back
T back(T... args)
std::function
ripple::tapNONE
@ tapNONE
Definition: ApplyView.h:30
std::sort
T sort(T... args)
ripple::STPathElement::typeCurrency
@ typeCurrency
Definition: STPathSet.h:49
ripple::STPathSet
Definition: STPathSet.h:176
ripple::Pathfinder::pt_XRP_to_XRP
@ pt_XRP_to_XRP
Definition: Pathfinder.h:98
ripple::Pathfinder::isNoRipple
bool isNoRipple(AccountID const &fromAccount, AccountID const &toAccount, Currency const &currency)
Definition: Pathfinder.cpp:880
ripple::Pathfinder::getPathLiquidity
TER getPathLiquidity(STPath const &path, STAmount const &minDstAmount, STAmount &amountOut, uint64_t &qualityOut) const
Definition: Pathfinder.cpp:344
ripple::STPathSet::empty
bool empty() const
Definition: STPathSet.h:503
ripple::STPathElement::typeIssuer
@ typeIssuer
Definition: STPathSet.h:50
std::vector::clear
T clear(T... args)
ripple::Pathfinder::afAC_LAST
static const std::uint32_t afAC_LAST
Definition: Pathfinder.h:234
std::vector::push_back
T push_back(T... args)
ripple::getRate
std::uint64_t getRate(STAmount const &offerOut, STAmount const &offerIn)
Definition: STAmount.cpp:454
ripple::divide
STAmount divide(STAmount const &amount, Rate const &rate)
Definition: Rate2.cpp:86
ripple::OrderBookDB::isBookToXRP
bool isBookToXRP(Issue const &)
Definition: OrderBookDB.cpp:195
ripple::base_uint
Integers of any length that is a multiple of 32-bits.
Definition: base_uint.h:75
ripple::convertAllCheck
bool convertAllCheck(STAmount const &a)
Definition: PathfinderUtils.h:46
ripple::STAmount::getFullText
std::string getFullText() const override
Definition: STAmount.cpp:507
ripple::CollectionAndDelimiter
Definition: join.h:39
ripple::lsfRequireAuth
@ lsfRequireAuth
Definition: LedgerFormats.h:226
ripple::Pathfinder::mSrcAmount
STAmount mSrcAmount
Definition: Pathfinder.h:201
ripple::OrderBookDB::getBookSize
int getBookSize(Issue const &)
Definition: OrderBookDB.cpp:186
ripple::QualityDirection::out
@ out
ripple::STPathElement::getNodeType
auto getNodeType() const
Definition: STPathSet.h:317
ripple::path::RippleCalc::Input::defaultPathsAllowed
bool defaultPathsAllowed
Definition: RippleCalc.h:51
ripple::Pathfinder::nt_BOOKS
@ nt_BOOKS
Definition: Pathfinder.h:86
ripple::base_uint::isZero
bool isZero() const
Definition: base_uint.h:525
ripple::STPathSet::size
std::vector< STPath >::size_type size() const
Definition: STPathSet.h:497
ripple::keylet::account
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:133
ripple::Pathfinder::mSrcIssuer
std::optional< AccountID > mSrcIssuer
Definition: Pathfinder.h:200
ripple::JsonOptions::none
@ none
ripple::TERSubset< CanCvtToTER >
ripple::STPath::back
std::vector< STPathElement >::const_reference back() const
Definition: STPathSet.h:436
ripple::Application::getJobQueue
virtual JobQueue & getJobQueue()=0
ripple::Pathfinder::mLedger
std::shared_ptr< ReadView const > mLedger
Definition: Pathfinder.h:207
ripple::STAmount
Definition: STAmount.h:44
ripple::STPath::emplace_back
void emplace_back(Args &&... args)
Definition: STPathSet.h:412
ripple::xrpAccount
AccountID const & xrpAccount()
Compute AccountID from public key.
Definition: AccountID.cpp:90
beast::Journal::info
Stream info() const
Definition: Journal.h:321
ripple::Pathfinder::mRLCache
std::shared_ptr< RippleLineCache > mRLCache
Definition: Pathfinder.h:209
ripple::Application::logs
virtual Logs & logs()=0
ripple::isXRP
bool isXRP(AccountID const &c)
Definition: AccountID.h:89
ripple::Pathfinder::Pathfinder
Pathfinder(std::shared_ptr< RippleLineCache > const &cache, AccountID const &srcAccount, AccountID const &dstAccount, Currency const &uSrcCurrency, std::optional< AccountID > const &uSrcIssuer, STAmount const &dstAmount, std::optional< STAmount > const &srcAmount, Application &app)
Construct a pathfinder without an issuer.
Definition: Pathfinder.cpp:161
std::uint32_t
ripple::Pathfinder::m_loadEvent
std::unique_ptr< LoadEvent > m_loadEvent
Definition: Pathfinder.h:208
ripple::Pathfinder::mRemainingAmount
STAmount mRemainingAmount
The amount remaining from mSrcAccount after the default liquidity has been removed.
Definition: Pathfinder.h:204
ripple::keylet::line
Keylet line(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition: Indexes.cpp:193
std::map
STL class.
std::vector::pop_back
T pop_back(T... args)
ripple::Pathfinder::nt_DESTINATION
@ nt_DESTINATION
Definition: Pathfinder.h:89
ripple::Pathfinder::mEffectiveDst
AccountID mEffectiveDst
Definition: Pathfinder.h:197
std::string::append
T append(T... args)
ripple::lsfHighNoRipple
@ lsfHighNoRipple
Definition: LedgerFormats.h:246
ripple::Pathfinder::mDstAccount
AccountID mDstAccount
Definition: Pathfinder.h:196
ripple::Pathfinder::mSource
STPathElement mSource
Definition: Pathfinder.h:211
ripple::Pathfinder::mCompletePaths
STPathSet mCompletePaths
Definition: Pathfinder.h:212
ripple::STPath::getJson
Json::Value getJson(JsonOptions) const
Definition: STPathSet.cpp:166
ripple::Pathfinder::getBestPaths
STPathSet getBestPaths(int maxPaths, STPath &fullLiquidityPath, STPathSet const &extraPaths, AccountID const &srcIssuer, std::function< bool(void)> const &continueCallback={})
Definition: Pathfinder.cpp:567
ripple::Pathfinder::pt_XRP_to_nonXRP
@ pt_XRP_to_nonXRP
Definition: Pathfinder.h:99
ripple::STPath::hasSeen
bool hasSeen(AccountID const &account, Currency const &currency, AccountID const &issuer) const
Definition: STPathSet.cpp:151
ripple::Pathfinder::getPathsOut
int getPathsOut(Currency const &currency, AccountID const &account, bool isDestCurrency, AccountID const &dest, std::function< bool(void)> const &continueCallback)
Definition: Pathfinder.cpp:708
ripple::STAmount::native
bool native() const noexcept
Definition: STAmount.h:321
ripple::STPath::empty
bool empty() const
Definition: STPathSet.h:399
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::addUniquePath
void addUniquePath(STPathSet &pathSet, STPath const &path)
Definition: Pathfinder.cpp:919
ripple::Pathfinder::afOB_XRP
static const std::uint32_t afOB_XRP
Definition: Pathfinder.h:228
ripple::Pathfinder::issueMatchesOrigin
bool issueMatchesOrigin(Issue const &)
Definition: Pathfinder.cpp:697
ripple::STPathElement
Definition: STPathSet.h:34
std::vector::begin
T begin(T... args)
ripple::sfFlags
const SF_UINT32 sfFlags
ripple::Pathfinder::j_
const beast::Journal j_
Definition: Pathfinder.h:219
ripple::STPath::end
std::vector< STPathElement >::const_iterator end() const
Definition: STPathSet.h:424
ripple::STPathSet::getJson
Json::Value getJson(JsonOptions) const override
Definition: STPathSet.cpp:193
ripple::Pathfinder::pt_nonXRP_to_same
@ pt_nonXRP_to_same
Definition: Pathfinder.h:101
ripple::path::RippleCalc::rippleCalculate
static Output rippleCalculate(PaymentSandbox &view, STAmount const &saMaxAmountReq, STAmount const &saDstAmountReq, AccountID const &uDstAccountID, AccountID const &uSrcAccountID, STPathSet const &spsPaths, Logs &l, Input const *const pInputs=nullptr)
Definition: RippleCalc.cpp:31
ripple::STPathElement::typeAccount
@ typeAccount
Definition: STPathSet.h:47
ripple::Pathfinder::afADD_BOOKS
static const std::uint32_t afADD_BOOKS
Definition: Pathfinder.h:225
ripple::path::RippleCalc::Input::partialPaymentAllowed
bool partialPaymentAllowed
Definition: RippleCalc.h:50
ripple::Pathfinder::pt_nonXRP_to_XRP
@ pt_nonXRP_to_XRP
Definition: Pathfinder.h:100
std::vector::empty
T empty(T... args)
ripple::Pathfinder::isNoRippleOut
bool isNoRippleOut(STPath const &currentPath)
Definition: Pathfinder.cpp:897
std::optional
beast::Journal::debug
Stream debug() const
Definition: Journal.h:315
ripple::Pathfinder::mPathsOutCountMap
hash_map< Issue, int > mPathsOutCountMap
Definition: Pathfinder.h:216
std::size_t
ripple::to_string
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
Definition: app/misc/impl/Manifest.cpp:41
ripple::Pathfinder::nt_ACCOUNTS
@ nt_ACCOUNTS
Definition: Pathfinder.h:85
std::vector::end
T end(T... args)
ripple::Pathfinder::nt_DEST_BOOK
@ nt_DEST_BOOK
Definition: Pathfinder.h:88
ripple::tefEXCEPTION
@ tefEXCEPTION
Definition: TER.h:151
ripple::OrderBookDB::getBooksByTakerPays
std::vector< Book > getBooksByTakerPays(Issue const &)
Definition: OrderBookDB.cpp:166
ripple::Pathfinder::PathRank
Definition: Pathfinder.h:105
ripple::Pathfinder::mPathRanks
std::vector< PathRank > mPathRanks
Definition: Pathfinder.h:213
ripple::JobQueue::makeLoadEvent
std::unique_ptr< LoadEvent > makeLoadEvent(JobType t, std::string const &name)
Return a scoped LoadEvent.
Definition: JobQueue.cpp:165
ripple::Pathfinder::PaymentType
PaymentType
Definition: Pathfinder.h:97
ripple::Pathfinder::rankPaths
void rankPaths(int maxPaths, STPathSet const &paths, std::vector< PathRank > &rankedPaths, std::function< bool(void)> const &continueCallback)
Definition: Pathfinder.cpp:489
ripple::STPathSet::assembleAdd
bool assembleAdd(STPath const &base, STPathElement const &tail)
Definition: STPathSet.cpp:117
ripple::STPath
Definition: STPathSet.h:118
ripple::largestAmount
STAmount largestAmount(STAmount const &amt)
Definition: PathfinderUtils.h:28
ripple::Pathfinder::nt_SOURCE
@ nt_SOURCE
Definition: Pathfinder.h:84
ripple::STAmount::getCurrency
Currency const & getCurrency() const
Definition: STAmount.h:345
ripple::tesSUCCESS
@ tesSUCCESS
Definition: TER.h:219
ripple::Pathfinder::convert_all_
bool convert_all_
Definition: Pathfinder.h:205
ripple::xrpCurrency
Currency const & xrpCurrency()
XRP currency.
Definition: UintTypes.cpp:121
ripple::AccountID
base_uint< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition: AccountID.h:47
ripple::Issue::account
AccountID account
Definition: Issue.h:38
std::exception::what
T what(T... args)
ripple::convertAmount
STAmount convertAmount(STAmount const &amt, bool all)
Definition: PathfinderUtils.h:37
ripple::STPathSet::push_back
void push_back(STPath const &e)
Definition: STPathSet.h:509
ripple::STPathElement::getAccountID
AccountID const & getAccountID() const
Definition: STPathSet.h:355