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  LineDirection direction,
712  bool isDstCurrency,
713  AccountID const& dstAccount,
714  std::function<bool(void)> const& continueCallback)
715 {
716  Issue const issue(currency, account);
717 
718  auto [it, inserted] = mPathsOutCountMap.emplace(issue, 0);
719 
720  // If it was already present, return the stored number of paths
721  if (!inserted)
722  return it->second;
723 
724  auto sleAccount = mLedger->read(keylet::account(account));
725 
726  if (!sleAccount)
727  return 0;
728 
729  int aFlags = sleAccount->getFieldU32(sfFlags);
730  bool const bAuthRequired = (aFlags & lsfRequireAuth) != 0;
731  bool const bFrozen = ((aFlags & lsfGlobalFreeze) != 0);
732 
733  int count = 0;
734 
735  if (!bFrozen)
736  {
737  count = app_.getOrderBookDB().getBookSize(issue);
738 
739  if (auto const lines = mRLCache->getRippleLines(account, direction))
740  {
741  for (auto const& rspEntry : *lines)
742  {
743  if (currency != rspEntry.getLimit().getCurrency())
744  {
745  }
746  else if (
747  rspEntry.getBalance() <= beast::zero &&
748  (!rspEntry.getLimitPeer() ||
749  -rspEntry.getBalance() >= rspEntry.getLimitPeer() ||
750  (bAuthRequired && !rspEntry.getAuth())))
751  {
752  }
753  else if (
754  isDstCurrency && dstAccount == rspEntry.getAccountIDPeer())
755  {
756  count += 10000; // count a path to the destination extra
757  }
758  else if (rspEntry.getNoRipplePeer())
759  {
760  // This probably isn't a useful path out
761  }
762  else if (rspEntry.getFreezePeer())
763  {
764  // Not a useful path out
765  }
766  else
767  {
768  ++count;
769  }
770  }
771  }
772  }
773  it->second = count;
774  return count;
775 }
776 
777 void
779  STPathSet const& currentPaths, // The paths to build from
780  STPathSet& incompletePaths, // The set of partial paths we add to
781  int addFlags,
782  std::function<bool(void)> const& continueCallback)
783 {
784  JLOG(j_.debug()) << "addLink< on " << currentPaths.size()
785  << " source(s), flags=" << addFlags;
786  for (auto const& path : currentPaths)
787  {
788  if (continueCallback && !continueCallback())
789  return;
790  addLink(path, incompletePaths, addFlags, continueCallback);
791  }
792 }
793 
794 STPathSet&
796  PathType const& pathType,
797  std::function<bool(void)> const& continueCallback)
798 {
799  JLOG(j_.debug()) << "addPathsForType "
800  << CollectionAndDelimiter(pathType, ", ");
801  // See if the set of paths for this type already exists.
802  auto it = mPaths.find(pathType);
803  if (it != mPaths.end())
804  return it->second;
805 
806  // Otherwise, if the type has no nodes, return the empty path.
807  if (pathType.empty())
808  return mPaths[pathType];
809  if (continueCallback && !continueCallback())
810  return mPaths[{}];
811 
812  // Otherwise, get the paths for the parent PathType by calling
813  // addPathsForType recursively.
814  PathType parentPathType = pathType;
815  parentPathType.pop_back();
816 
817  STPathSet const& parentPaths =
818  addPathsForType(parentPathType, continueCallback);
819  STPathSet& pathsOut = mPaths[pathType];
820 
821  JLOG(j_.debug()) << "getPaths< adding onto '"
822  << pathTypeToString(parentPathType) << "' to get '"
823  << pathTypeToString(pathType) << "'";
824 
825  int initialSize = mCompletePaths.size();
826 
827  // Add the last NodeType to the lists.
828  auto nodeType = pathType.back();
829  switch (nodeType)
830  {
831  case nt_SOURCE:
832  // Source must always be at the start, so pathsOut has to be empty.
833  assert(pathsOut.empty());
834  pathsOut.push_back(STPath());
835  break;
836 
837  case nt_ACCOUNTS:
838  addLinks(parentPaths, pathsOut, afADD_ACCOUNTS, continueCallback);
839  break;
840 
841  case nt_BOOKS:
842  addLinks(parentPaths, pathsOut, afADD_BOOKS, continueCallback);
843  break;
844 
845  case nt_XRP_BOOK:
846  addLinks(
847  parentPaths,
848  pathsOut,
850  continueCallback);
851  break;
852 
853  case nt_DEST_BOOK:
854  addLinks(
855  parentPaths,
856  pathsOut,
858  continueCallback);
859  break;
860 
861  case nt_DESTINATION:
862  // FIXME: What if a different issuer was specified on the
863  // destination amount?
864  // TODO(tom): what does this even mean? Should it be a JIRA?
865  addLinks(
866  parentPaths,
867  pathsOut,
869  continueCallback);
870  break;
871  }
872 
873  if (mCompletePaths.size() != initialSize)
874  {
875  JLOG(j_.debug()) << (mCompletePaths.size() - initialSize)
876  << " complete paths added";
877  }
878 
879  JLOG(j_.debug()) << "getPaths> " << pathsOut.size()
880  << " partial paths found";
881  return pathsOut;
882 }
883 
884 bool
886  AccountID const& fromAccount,
887  AccountID const& toAccount,
888  Currency const& currency)
889 {
890  auto sleRipple =
891  mLedger->read(keylet::line(toAccount, fromAccount, currency));
892 
893  auto const flag(
894  (toAccount > fromAccount) ? lsfHighNoRipple : lsfLowNoRipple);
895 
896  return sleRipple && (sleRipple->getFieldU32(sfFlags) & flag);
897 }
898 
899 // Does this path end on an account-to-account link whose last account has
900 // set "no ripple" on the link?
901 bool
903 {
904  // Must have at least one link.
905  if (currentPath.empty())
906  return false;
907 
908  // Last link must be an account.
909  STPathElement const& endElement = currentPath.back();
910  if (!(endElement.getNodeType() & STPathElement::typeAccount))
911  return false;
912 
913  // If there's only one item in the path, return true if that item specifies
914  // no ripple on the output. A path with no ripple on its output can't be
915  // followed by a link with no ripple on its input.
916  auto const& fromAccount = (currentPath.size() == 1)
917  ? mSrcAccount
918  : (currentPath.end() - 2)->getAccountID();
919  auto const& toAccount = endElement.getAccountID();
920  return isNoRipple(fromAccount, toAccount, endElement.getCurrency());
921 }
922 
923 void
924 addUniquePath(STPathSet& pathSet, STPath const& path)
925 {
926  // TODO(tom): building an STPathSet this way is quadratic in the size
927  // of the STPathSet!
928  for (auto const& p : pathSet)
929  {
930  if (p == path)
931  return;
932  }
933  pathSet.push_back(path);
934 }
935 
936 void
938  const STPath& currentPath, // The path to build from
939  STPathSet& incompletePaths, // The set of partial paths we add to
940  int addFlags,
941  std::function<bool(void)> const& continueCallback)
942 {
943  auto const& pathEnd = currentPath.empty() ? mSource : currentPath.back();
944  auto const& uEndCurrency = pathEnd.getCurrency();
945  auto const& uEndIssuer = pathEnd.getIssuerID();
946  auto const& uEndAccount = pathEnd.getAccountID();
947  bool const bOnXRP = uEndCurrency.isZero();
948 
949  // Does pathfinding really need to get this to
950  // a gateway (the issuer of the destination amount)
951  // rather than the ultimate destination?
952  bool const hasEffectiveDestination = mEffectiveDst != mDstAccount;
953 
954  JLOG(j_.trace()) << "addLink< flags=" << addFlags << " onXRP=" << bOnXRP
955  << " completePaths size=" << mCompletePaths.size();
956  JLOG(j_.trace()) << currentPath.getJson(JsonOptions::none);
957 
958  if (addFlags & afADD_ACCOUNTS)
959  {
960  // add accounts
961  if (bOnXRP)
962  {
963  if (mDstAmount.native() && !currentPath.empty())
964  { // non-default path to XRP destination
965  JLOG(j_.trace()) << "complete path found ax: "
966  << currentPath.getJson(JsonOptions::none);
967  addUniquePath(mCompletePaths, currentPath);
968  }
969  }
970  else
971  {
972  // search for accounts to add
973  auto const sleEnd = mLedger->read(keylet::account(uEndAccount));
974 
975  if (sleEnd)
976  {
977  bool const bRequireAuth(
978  sleEnd->getFieldU32(sfFlags) & lsfRequireAuth);
979  bool const bIsEndCurrency(
980  uEndCurrency == mDstAmount.getCurrency());
981  bool const bIsNoRippleOut(isNoRippleOut(currentPath));
982  bool const bDestOnly(addFlags & afAC_LAST);
983 
984  if (auto const lines = mRLCache->getRippleLines(
985  uEndAccount,
986  bIsNoRippleOut ? LineDirection::incoming
988  {
989  auto& rippleLines = *lines;
990 
991  AccountCandidates candidates;
992  candidates.reserve(rippleLines.size());
993 
994  for (auto const& rs : rippleLines)
995  {
996  if (continueCallback && !continueCallback())
997  return;
998  auto const& acct = rs.getAccountIDPeer();
999  LineDirection const direction = rs.getDirectionPeer();
1000 
1001  if (hasEffectiveDestination && (acct == mDstAccount))
1002  {
1003  // We skipped the gateway
1004  continue;
1005  }
1006 
1007  bool bToDestination = acct == mEffectiveDst;
1008 
1009  if (bDestOnly && !bToDestination)
1010  {
1011  continue;
1012  }
1013 
1014  if ((uEndCurrency == rs.getLimit().getCurrency()) &&
1015  !currentPath.hasSeen(acct, uEndCurrency, acct))
1016  {
1017  // path is for correct currency and has not been
1018  // seen
1019  if (rs.getBalance() <= beast::zero &&
1020  (!rs.getLimitPeer() ||
1021  -rs.getBalance() >= rs.getLimitPeer() ||
1022  (bRequireAuth && !rs.getAuth())))
1023  {
1024  // path has no credit
1025  }
1026  else if (bIsNoRippleOut && rs.getNoRipple())
1027  {
1028  // Can't leave on this path
1029  }
1030  else if (bToDestination)
1031  {
1032  // destination is always worth trying
1033  if (uEndCurrency == mDstAmount.getCurrency())
1034  {
1035  // this is a complete path
1036  if (!currentPath.empty())
1037  {
1038  JLOG(j_.trace())
1039  << "complete path found ae: "
1040  << currentPath.getJson(
1042  addUniquePath(
1043  mCompletePaths, currentPath);
1044  }
1045  }
1046  else if (!bDestOnly)
1047  {
1048  // this is a high-priority candidate
1049  candidates.push_back(
1050  {AccountCandidate::highPriority, acct});
1051  }
1052  }
1053  else if (acct == mSrcAccount)
1054  {
1055  // going back to the source is bad
1056  }
1057  else
1058  {
1059  // save this candidate
1060  int out = getPathsOut(
1061  uEndCurrency,
1062  acct,
1063  direction,
1064  bIsEndCurrency,
1065  mEffectiveDst,
1066  continueCallback);
1067  if (out)
1068  candidates.push_back({out, acct});
1069  }
1070  }
1071  }
1072 
1073  if (!candidates.empty())
1074  {
1075  std::sort(
1076  candidates.begin(),
1077  candidates.end(),
1078  std::bind(
1079  compareAccountCandidate,
1080  mLedger->seq(),
1081  std::placeholders::_1,
1082  std::placeholders::_2));
1083 
1084  int count = candidates.size();
1085  // allow more paths from source
1086  if ((count > 10) && (uEndAccount != mSrcAccount))
1087  count = 10;
1088  else if (count > 50)
1089  count = 50;
1090 
1091  auto it = candidates.begin();
1092  while (count-- != 0)
1093  {
1094  if (continueCallback && !continueCallback())
1095  return;
1096  // Add accounts to incompletePaths
1097  STPathElement pathElement(
1099  it->account,
1100  uEndCurrency,
1101  it->account);
1102  incompletePaths.assembleAdd(
1103  currentPath, pathElement);
1104  ++it;
1105  }
1106  }
1107  }
1108  }
1109  else
1110  {
1111  JLOG(j_.warn()) << "Path ends on non-existent issuer";
1112  }
1113  }
1114  }
1115  if (addFlags & afADD_BOOKS)
1116  {
1117  // add order books
1118  if (addFlags & afOB_XRP)
1119  {
1120  // to XRP only
1121  if (!bOnXRP &&
1122  app_.getOrderBookDB().isBookToXRP({uEndCurrency, uEndIssuer}))
1123  {
1124  STPathElement pathElement(
1126  xrpAccount(),
1127  xrpCurrency(),
1128  xrpAccount());
1129  incompletePaths.assembleAdd(currentPath, pathElement);
1130  }
1131  }
1132  else
1133  {
1134  bool bDestOnly = (addFlags & afOB_LAST) != 0;
1135  auto books = app_.getOrderBookDB().getBooksByTakerPays(
1136  {uEndCurrency, uEndIssuer});
1137  JLOG(j_.trace())
1138  << books.size() << " books found from this currency/issuer";
1139 
1140  for (auto const& book : books)
1141  {
1142  if (continueCallback && !continueCallback())
1143  return;
1144  if (!currentPath.hasSeen(
1145  xrpAccount(), book.out.currency, book.out.account) &&
1146  !issueMatchesOrigin(book.out) &&
1147  (!bDestOnly ||
1148  (book.out.currency == mDstAmount.getCurrency())))
1149  {
1150  STPath newPath(currentPath);
1151 
1152  if (book.out.currency.isZero())
1153  { // to XRP
1154 
1155  // add the order book itself
1156  newPath.emplace_back(
1158  xrpAccount(),
1159  xrpCurrency(),
1160  xrpAccount());
1161 
1162  if (mDstAmount.getCurrency().isZero())
1163  {
1164  // destination is XRP, add account and path is
1165  // complete
1166  JLOG(j_.trace())
1167  << "complete path found bx: "
1168  << currentPath.getJson(JsonOptions::none);
1169  addUniquePath(mCompletePaths, newPath);
1170  }
1171  else
1172  incompletePaths.push_back(newPath);
1173  }
1174  else if (!currentPath.hasSeen(
1175  book.out.account,
1176  book.out.currency,
1177  book.out.account))
1178  {
1179  // Don't want the book if we've already seen the issuer
1180  // book -> account -> book
1181  if ((newPath.size() >= 2) &&
1182  (newPath.back().isAccount()) &&
1183  (newPath[newPath.size() - 2].isOffer()))
1184  {
1185  // replace the redundant account with the order book
1186  newPath[newPath.size() - 1] = STPathElement(
1189  xrpAccount(),
1190  book.out.currency,
1191  book.out.account);
1192  }
1193  else
1194  {
1195  // add the order book
1196  newPath.emplace_back(
1199  xrpAccount(),
1200  book.out.currency,
1201  book.out.account);
1202  }
1203 
1204  if (hasEffectiveDestination &&
1205  book.out.account == mDstAccount &&
1206  book.out.currency == mDstAmount.getCurrency())
1207  {
1208  // We skipped a required issuer
1209  }
1210  else if (
1211  book.out.account == mEffectiveDst &&
1212  book.out.currency == mDstAmount.getCurrency())
1213  { // with the destination account, this path is
1214  // complete
1215  JLOG(j_.trace())
1216  << "complete path found ba: "
1217  << currentPath.getJson(JsonOptions::none);
1218  addUniquePath(mCompletePaths, newPath);
1219  }
1220  else
1221  {
1222  // add issuer's account, path still incomplete
1223  incompletePaths.assembleAdd(
1224  newPath,
1225  STPathElement(
1227  book.out.account,
1228  book.out.currency,
1229  book.out.account));
1230  }
1231  }
1232  }
1233  }
1234  }
1235  }
1236 }
1237 
1238 namespace {
1239 
1241 makePath(char const* string)
1242 {
1244 
1245  while (true)
1246  {
1247  switch (*string++)
1248  {
1249  case 's': // source
1251  break;
1252 
1253  case 'a': // accounts
1255  break;
1256 
1257  case 'b': // books
1259  break;
1260 
1261  case 'x': // xrp book
1263  break;
1264 
1265  case 'f': // book to final currency
1267  break;
1268 
1269  case 'd':
1270  // Destination (with account, if required and not already
1271  // present).
1273  break;
1274 
1275  case 0:
1276  return ret;
1277  }
1278  }
1279 }
1280 
1281 void
1282 fillPaths(Pathfinder::PaymentType type, PathCostList const& costs)
1283 {
1284  auto& list = mPathTable[type];
1285  assert(list.empty());
1286  for (auto& cost : costs)
1287  list.push_back({cost.cost, makePath(cost.path)});
1288 }
1289 
1290 } // namespace
1291 
1292 // Costs:
1293 // 0 = minimum to make some payments possible
1294 // 1 = include trivial paths to make common cases work
1295 // 4 = normal fast search level
1296 // 7 = normal slow search level
1297 // 10 = most agressive
1298 
1299 void
1301 {
1302  // CAUTION: Do not include rules that build default paths
1303 
1304  mPathTable.clear();
1305  fillPaths(pt_XRP_to_XRP, {});
1306 
1307  fillPaths(
1309  {{1, "sfd"}, // source -> book -> gateway
1310  {3, "sfad"}, // source -> book -> account -> destination
1311  {5, "sfaad"}, // source -> book -> account -> account -> destination
1312  {6, "sbfd"}, // source -> book -> book -> destination
1313  {8, "sbafd"}, // source -> book -> account -> book -> destination
1314  {9, "sbfad"}, // source -> book -> book -> account -> destination
1315  {10, "sbafad"}});
1316 
1317  fillPaths(
1319  {{1, "sxd"}, // gateway buys XRP
1320  {2, "saxd"}, // source -> gateway -> book(XRP) -> dest
1321  {6, "saaxd"},
1322  {7, "sbxd"},
1323  {8, "sabxd"},
1324  {9, "sabaxd"}});
1325 
1326  // non-XRP to non-XRP (same currency)
1327  fillPaths(
1329  {
1330  {1, "sad"}, // source -> gateway -> destination
1331  {1, "sfd"}, // source -> book -> destination
1332  {4, "safd"}, // source -> gateway -> book -> destination
1333  {4, "sfad"},
1334  {5, "saad"},
1335  {5, "sbfd"},
1336  {6, "sxfad"},
1337  {6, "safad"},
1338  {6, "saxfd"}, // source -> gateway -> book to XRP -> book ->
1339  // destination
1340  {6, "saxfad"},
1341  {6, "sabfd"}, // source -> gateway -> book -> book -> destination
1342  {7, "saaad"},
1343  });
1344 
1345  // non-XRP to non-XRP (different currency)
1346  fillPaths(
1348  {
1349  {1, "sfad"},
1350  {1, "safd"},
1351  {3, "safad"},
1352  {4, "sxfd"},
1353  {5, "saxfd"},
1354  {5, "sxfad"},
1355  {5, "sbfd"},
1356  {6, "saxfad"},
1357  {6, "sabfd"},
1358  {7, "saafd"},
1359  {8, "saafad"},
1360  {9, "safaad"},
1361  });
1362 }
1363 
1364 } // namespace ripple
ripple::Pathfinder::addPathsForType
STPathSet & addPathsForType(PathType const &type, std::function< bool(void)> const &continueCallback)
Definition: Pathfinder.cpp:795
ripple::Pathfinder::pt_nonXRP_to_nonXRP
@ pt_nonXRP_to_nonXRP
Definition: Pathfinder.h:102
ripple::Pathfinder::mSrcCurrency
Currency mSrcCurrency
Definition: Pathfinder.h:200
ripple::Application
Definition: Application.h:115
ripple::Pathfinder::mSrcAccount
AccountID mSrcAccount
Definition: Pathfinder.h:196
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:937
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:223
ripple::LineDirection
LineDirection
Describes how an account was found in a path, and how to find the next set of paths.
Definition: TrustLine.h:41
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:778
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:256
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:232
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:215
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:210
ripple::Pathfinder::mDstAmount
STAmount mDstAmount
Definition: Pathfinder.h:199
ripple::Pathfinder::app_
Application & app_
Definition: Pathfinder.h:219
tuple
ripple::Pathfinder::initPathTable
static void initPathTable()
Definition: Pathfinder.cpp:1300
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::Pathfinder::getPathsOut
int getPathsOut(Currency const &currency, AccountID const &account, LineDirection direction, bool isDestCurrency, AccountID const &dest, std::function< bool(void)> const &continueCallback)
Definition: Pathfinder.cpp:708
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:885
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:235
std::vector::push_back
T push_back(T... args)
ripple::getRate
std::uint64_t getRate(STAmount const &offerOut, STAmount const &offerIn)
Definition: STAmount.cpp:495
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:82
ripple::convertAllCheck
bool convertAllCheck(STAmount const &a)
Definition: PathfinderUtils.h:46
ripple::STAmount::getFullText
std::string getFullText() const override
Definition: STAmount.cpp:548
ripple::CollectionAndDelimiter
Definition: join.h:39
ripple::lsfRequireAuth
@ lsfRequireAuth
Definition: LedgerFormats.h:226
ripple::Pathfinder::mSrcAmount
STAmount mSrcAmount
Definition: Pathfinder.h:202
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:532
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:201
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::LineDirection::outgoing
@ outgoing
ripple::Pathfinder::mLedger
std::shared_ptr< ReadView const > mLedger
Definition: Pathfinder.h:208
ripple::STAmount
Definition: STAmount.h:45
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:168
beast::Journal::info
Stream info() const
Definition: Journal.h:321
ripple::Pathfinder::mRLCache
std::shared_ptr< RippleLineCache > mRLCache
Definition: Pathfinder.h:210
ripple::Application::logs
virtual Logs & logs()=0
ripple::LineDirection::incoming
@ incoming
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:209
ripple::Pathfinder::mRemainingAmount
STAmount mRemainingAmount
The amount remaining from mSrcAccount after the default liquidity has been removed.
Definition: Pathfinder.h:205
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:198
std::string::append
T append(T... args)
ripple::lsfHighNoRipple
@ lsfHighNoRipple
Definition: LedgerFormats.h:257
ripple::Pathfinder::mDstAccount
AccountID mDstAccount
Definition: Pathfinder.h:197
ripple::Pathfinder::mSource
STPathElement mSource
Definition: Pathfinder.h:212
ripple::Pathfinder::mCompletePaths
STPathSet mCompletePaths
Definition: Pathfinder.h:213
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::STAmount::native
bool native() const noexcept
Definition: STAmount.h:329
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:924
ripple::Pathfinder::afOB_XRP
static const std::uint32_t afOB_XRP
Definition: Pathfinder.h:229
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:220
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:226
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:902
std::optional
beast::Journal::debug
Stream debug() const
Definition: Journal.h:315
ripple::Pathfinder::mPathsOutCountMap
hash_map< Issue, int > mPathsOutCountMap
Definition: Pathfinder.h:217
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:154
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:214
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:353
ripple::tesSUCCESS
@ tesSUCCESS
Definition: TER.h:222
ripple::Pathfinder::convert_all_
bool convert_all_
Definition: Pathfinder.h:206
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