rippled
StrandFlow.h
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 #ifndef RIPPLE_APP_PATHS_IMPL_STRANDFLOW_H_INCLUDED
21 #define RIPPLE_APP_PATHS_IMPL_STRANDFLOW_H_INCLUDED
22 
23 #include <ripple/app/misc/AMMHelpers.h>
24 #include <ripple/app/paths/AMMContext.h>
25 #include <ripple/app/paths/Credit.h>
26 #include <ripple/app/paths/Flow.h>
27 #include <ripple/app/paths/impl/AmountSpec.h>
28 #include <ripple/app/paths/impl/FlatSets.h>
29 #include <ripple/app/paths/impl/FlowDebugInfo.h>
30 #include <ripple/app/paths/impl/Steps.h>
31 #include <ripple/basics/IOUAmount.h>
32 #include <ripple/basics/Log.h>
33 #include <ripple/basics/XRPAmount.h>
34 #include <ripple/protocol/Feature.h>
35 
36 #include <boost/container/flat_set.hpp>
37 
38 #include <algorithm>
39 #include <iterator>
40 #include <numeric>
41 #include <sstream>
42 
43 namespace ripple {
44 
46 template <class TInAmt, class TOutAmt>
48 {
49  bool success;
50  TInAmt in = beast::zero;
51  TOutAmt out = beast::zero;
53  boost::container::flat_set<uint256> ofrsToRm;
54  // Num offers consumed or partially consumed (includes expired and unfunded
55  // offers)
57  // strand can be inactive if there is no more liquidity or too many offers
58  // have been consumed
59  bool inactive = false;
60 
63  StrandResult() = default;
64 
66  Strand const& strand,
67  TInAmt const& in_,
68  TOutAmt const& out_,
69  PaymentSandbox&& sandbox_,
70  boost::container::flat_set<uint256> ofrsToRm_,
71  bool inactive_)
72  : success(true)
73  , in(in_)
74  , out(out_)
75  , sandbox(std::move(sandbox_))
76  , ofrsToRm(std::move(ofrsToRm_))
77  , ofrsUsed(offersUsed(strand))
78  , inactive(inactive_)
79  {
80  }
81 
83  Strand const& strand,
84  boost::container::flat_set<uint256> ofrsToRm_)
85  : success(false)
86  , ofrsToRm(std::move(ofrsToRm_))
87  , ofrsUsed(offersUsed(strand))
88  {
89  }
90 };
91 
103 template <class TInAmt, class TOutAmt>
104 StrandResult<TInAmt, TOutAmt>
106  PaymentSandbox const& baseView,
107  Strand const& strand,
108  std::optional<TInAmt> const& maxIn,
109  TOutAmt const& out,
110  beast::Journal j)
111 {
113  if (strand.empty())
114  {
115  JLOG(j.warn()) << "Empty strand passed to Liquidity";
116  return {};
117  }
118 
119  boost::container::flat_set<uint256> ofrsToRm;
120 
121  if (isDirectXrpToXrp<TInAmt, TOutAmt>(strand))
122  {
123  return Result{strand, std::move(ofrsToRm)};
124  }
125 
126  try
127  {
128  std::size_t const s = strand.size();
129 
130  std::size_t limitingStep = strand.size();
131  std::optional<PaymentSandbox> sb(&baseView);
132  // The "all funds" view determines if an offer becomes unfunded or is
133  // found unfunded
134  // These are the account balances before the strand executes
135  std::optional<PaymentSandbox> afView(&baseView);
137  {
138  EitherAmount stepOut(out);
139  for (auto i = s; i--;)
140  {
141  auto r = strand[i]->rev(*sb, *afView, ofrsToRm, stepOut);
142  if (strand[i]->isZero(r.second))
143  {
144  JLOG(j.trace()) << "Strand found dry in rev";
145  return Result{strand, std::move(ofrsToRm)};
146  }
147 
148  if (i == 0 && maxIn && *maxIn < get<TInAmt>(r.first))
149  {
150  // limiting - exceeded maxIn
151  // Throw out previous results
152  sb.emplace(&baseView);
153  limitingStep = i;
154 
155  // re-execute the limiting step
156  r = strand[i]->fwd(
157  *sb, *afView, ofrsToRm, EitherAmount(*maxIn));
158  limitStepOut = r.second;
159 
160  if (strand[i]->isZero(r.second))
161  {
162  JLOG(j.trace()) << "First step found dry";
163  return Result{strand, std::move(ofrsToRm)};
164  }
165  if (get<TInAmt>(r.first) != *maxIn)
166  {
167  // Something is very wrong
168  // throwing out the sandbox can only increase liquidity
169  // yet the limiting is still limiting
170  JLOG(j.fatal())
171  << "Re-executed limiting step failed. r.first: "
172  << to_string(get<TInAmt>(r.first))
173  << " maxIn: " << to_string(*maxIn);
174  assert(0);
175  return Result{strand, std::move(ofrsToRm)};
176  }
177  }
178  else if (!strand[i]->equalOut(r.second, stepOut))
179  {
180  // limiting
181  // Throw out previous results
182  sb.emplace(&baseView);
183  afView.emplace(&baseView);
184  limitingStep = i;
185 
186  // re-execute the limiting step
187  stepOut = r.second;
188  r = strand[i]->rev(*sb, *afView, ofrsToRm, stepOut);
189  limitStepOut = r.second;
190 
191  if (strand[i]->isZero(r.second))
192  {
193  // A tiny input amount can cause this step to output
194  // zero. I.e. 10^-80 IOU into an IOU -> XRP offer.
195  JLOG(j.trace()) << "Limiting step found dry";
196  return Result{strand, std::move(ofrsToRm)};
197  }
198  if (!strand[i]->equalOut(r.second, stepOut))
199  {
200  // Something is very wrong
201  // throwing out the sandbox can only increase liquidity
202  // yet the limiting is still limiting
203 #ifndef NDEBUG
204  JLOG(j.fatal())
205  << "Re-executed limiting step failed. r.second: "
206  << r.second << " stepOut: " << stepOut;
207 #else
208  JLOG(j.fatal()) << "Re-executed limiting step failed";
209 #endif
210  assert(0);
211  return Result{strand, std::move(ofrsToRm)};
212  }
213  }
214 
215  // prev node needs to produce what this node wants to consume
216  stepOut = r.first;
217  }
218  }
219 
220  {
221  EitherAmount stepIn(limitStepOut);
222  for (auto i = limitingStep + 1; i < s; ++i)
223  {
224  auto const r = strand[i]->fwd(*sb, *afView, ofrsToRm, stepIn);
225  if (strand[i]->isZero(r.second))
226  {
227  // A tiny input amount can cause this step to output zero.
228  // I.e. 10^-80 IOU into an IOU -> XRP offer.
229  JLOG(j.trace()) << "Non-limiting step found dry";
230  return Result{strand, std::move(ofrsToRm)};
231  }
232  if (!strand[i]->equalIn(r.first, stepIn))
233  {
234  // The limits should already have been found, so executing a
235  // strand forward from the limiting step should not find a
236  // new limit
237 #ifndef NDEBUG
238  JLOG(j.fatal())
239  << "Re-executed forward pass failed. r.first: "
240  << r.first << " stepIn: " << stepIn;
241 #else
242  JLOG(j.fatal()) << "Re-executed forward pass failed";
243 #endif
244  assert(0);
245  return Result{strand, std::move(ofrsToRm)};
246  }
247  stepIn = r.second;
248  }
249  }
250 
251  auto const strandIn = *strand.front()->cachedIn();
252  auto const strandOut = *strand.back()->cachedOut();
253 
254 #ifndef NDEBUG
255  {
256  // Check that the strand will execute as intended
257  // Re-executing the strand will change the cached values
258  PaymentSandbox checkSB(&baseView);
259  PaymentSandbox checkAfView(&baseView);
260  EitherAmount stepIn(*strand[0]->cachedIn());
261  for (auto i = 0; i < s; ++i)
262  {
263  bool valid;
264  std::tie(valid, stepIn) =
265  strand[i]->validFwd(checkSB, checkAfView, stepIn);
266  if (!valid)
267  {
268  JLOG(j.warn())
269  << "Strand re-execute check failed. Step: " << i;
270  break;
271  }
272  }
273  }
274 #endif
275 
276  bool const inactive = std::any_of(
277  strand.begin(),
278  strand.end(),
279  [](std::unique_ptr<Step> const& step) { return step->inactive(); });
280 
281  return Result(
282  strand,
283  get<TInAmt>(strandIn),
284  get<TOutAmt>(strandOut),
285  std::move(*sb),
286  std::move(ofrsToRm),
287  inactive);
288  }
289  catch (FlowException const&)
290  {
291  return Result{strand, std::move(ofrsToRm)};
292  }
293 }
294 
296 template <class TInAmt, class TOutAmt>
297 struct FlowResult
298 {
299  TInAmt in = beast::zero;
300  TOutAmt out = beast::zero;
302  boost::container::flat_set<uint256> removableOffers;
303  TER ter = temUNKNOWN;
304 
305  FlowResult() = default;
306 
307  FlowResult(
308  TInAmt const& in_,
309  TOutAmt const& out_,
310  PaymentSandbox&& sandbox_,
311  boost::container::flat_set<uint256> ofrsToRm)
312  : in(in_)
313  , out(out_)
314  , sandbox(std::move(sandbox_))
315  , removableOffers(std::move(ofrsToRm))
316  , ter(tesSUCCESS)
317  {
318  }
319 
320  FlowResult(TER ter_, boost::container::flat_set<uint256> ofrsToRm)
321  : removableOffers(std::move(ofrsToRm)), ter(ter_)
322  {
323  }
324 
325  FlowResult(
326  TER ter_,
327  TInAmt const& in_,
328  TOutAmt const& out_,
329  boost::container::flat_set<uint256> ofrsToRm)
330  : in(in_), out(out_), removableOffers(std::move(ofrsToRm)), ter(ter_)
331  {
332  }
333 };
335 
338 qualityUpperBound(ReadView const& v, Strand const& strand)
339 {
340  Quality q{STAmount::uRateOne};
343  for (auto const& step : strand)
344  {
345  if (std::tie(stepQ, dir) = step->qualityUpperBound(v, dir); stepQ)
346  q = composed_quality(q, *stepQ);
347  else
348  return std::nullopt;
349  }
350  return q;
351 };
353 
355 
363 template <typename TOutAmt>
364 inline TOutAmt
365 limitOut(
366  ReadView const& v,
367  Strand const& strand,
368  TOutAmt const& remainingOut,
369  Quality const& limitQuality)
370 {
371  std::optional<QualityFunction> stepQualityFunc;
374  for (auto const& step : strand)
375  {
376  if (std::tie(stepQualityFunc, dir) = step->getQualityFunc(v, dir);
377  stepQualityFunc)
378  {
379  if (!qf)
380  qf = stepQualityFunc;
381  else
382  qf->combine(*stepQualityFunc);
383  }
384  else
385  return remainingOut;
386  }
387 
388  // QualityFunction is constant
389  if (!qf || qf->isConst())
390  return remainingOut;
391 
392  auto const out = [&]() {
393  if (auto const out = qf->outFromAvgQ(limitQuality); !out)
394  return remainingOut;
395  else if constexpr (std::is_same_v<TOutAmt, XRPAmount>)
396  return XRPAmount{*out};
397  else if constexpr (std::is_same_v<TOutAmt, IOUAmount>)
398  return IOUAmount{*out};
399  else
400  return STAmount{
401  remainingOut.issue(), out->mantissa(), out->exponent()};
402  }();
403  // A tiny difference could be due to the round off
404  if (withinRelativeDistance(out, remainingOut, Number(1, -9)))
405  return remainingOut;
406  return std::min(out, remainingOut);
407 };
409 
411 /* Track the non-dry strands
412 
413  flow will search the non-dry strands (stored in `cur_`) for the best
414  available liquidity If flow doesn't use all the liquidity of a strand, that
415  strand is added to `next_`. The strands in `next_` are searched after the
416  current best liquidity is used.
417  */
418 class ActiveStrands
419 {
420 private:
421  // Strands to be explored for liquidity
423  // Strands that may be explored for liquidity on the next iteration
425 
426 public:
427  ActiveStrands(std::vector<Strand> const& strands)
428  {
429  cur_.reserve(strands.size());
430  next_.reserve(strands.size());
431  for (auto& strand : strands)
432  next_.push_back(&strand);
433  }
434 
435  // Start a new iteration in the search for liquidity
436  // Set the current strands to the strands in `next_`
437  void
438  activateNext(ReadView const& v, std::optional<Quality> const& limitQuality)
439  {
440  // add the strands in `next_` to `cur_`, sorted by theoretical quality.
441  // Best quality first.
442  cur_.clear();
443  if (v.rules().enabled(featureFlowSortStrands) && !next_.empty())
444  {
446  strandQuals.reserve(next_.size());
447  if (next_.size() > 1) // no need to sort one strand
448  {
449  for (Strand const* strand : next_)
450  {
451  if (!strand)
452  {
453  // should not happen
454  continue;
455  }
456  if (auto const qual = qualityUpperBound(v, *strand))
457  {
458  if (limitQuality && *qual < *limitQuality)
459  {
460  // If a strand's quality is ever over limitQuality
461  // it is no longer part of the candidate set. Note
462  // that when transfer fees are charged, and an
463  // account goes from redeeming to issuing then
464  // strand quality _can_ increase; However, this is
465  // an unusual corner case.
466  continue;
467  }
468  strandQuals.push_back({*qual, strand});
469  }
470  }
471  // must stable sort for deterministic order across different c++
472  // standard library implementations
474  strandQuals.begin(),
475  strandQuals.end(),
476  [](auto const& lhs, auto const& rhs) {
477  // higher qualities first
478  return std::get<Quality>(lhs) > std::get<Quality>(rhs);
479  });
480  next_.clear();
481  next_.reserve(strandQuals.size());
482  for (auto const& sq : strandQuals)
483  {
484  next_.push_back(std::get<Strand const*>(sq));
485  }
486  }
487  }
488  std::swap(cur_, next_);
489  }
490 
491  Strand const*
492  get(size_t i) const
493  {
494  if (i >= cur_.size())
495  {
496  assert(0);
497  return nullptr;
498  }
499  return cur_[i];
500  }
501 
502  void
503  push(Strand const* s)
504  {
505  next_.push_back(s);
506  }
507 
508  // Push the strands from index i to the end of cur_ to next_
509  void
510  pushRemainingCurToNext(size_t i)
511  {
512  if (i >= cur_.size())
513  return;
514  next_.insert(next_.end(), std::next(cur_.begin(), i), cur_.end());
515  }
516 
517  auto
518  size() const
519  {
520  return cur_.size();
521  }
522 
523  void
524  removeIndex(std::size_t i)
525  {
526  if (i >= next_.size())
527  return;
528  next_.erase(next_.begin() + i);
529  }
530 };
532 
553 template <class TInAmt, class TOutAmt>
554 FlowResult<TInAmt, TOutAmt>
556  PaymentSandbox const& baseView,
557  std::vector<Strand> const& strands,
558  TOutAmt const& outReq,
559  bool partialPayment,
560  OfferCrossing offerCrossing,
561  std::optional<Quality> const& limitQuality,
562  std::optional<STAmount> const& sendMaxST,
563  beast::Journal j,
564  AMMContext& ammContext,
565  path::detail::FlowDebugInfo* flowDebugInfo = nullptr)
566 {
567  // Used to track the strand that offers the best quality (output/input
568  // ratio)
569  struct BestStrand
570  {
571  TInAmt in;
572  TOutAmt out;
573  PaymentSandbox sb;
574  Strand const& strand;
575  Quality quality;
576 
577  BestStrand(
578  TInAmt const& in_,
579  TOutAmt const& out_,
580  PaymentSandbox&& sb_,
581  Strand const& strand_,
582  Quality const& quality_)
583  : in(in_)
584  , out(out_)
585  , sb(std::move(sb_))
586  , strand(strand_)
587  , quality(quality_)
588  {
589  }
590  };
591 
592  std::size_t const maxTries = 1000;
593  std::size_t curTry = 0;
594  std::uint32_t maxOffersToConsider = 1500;
595  std::uint32_t offersConsidered = 0;
596 
597  // There is a bug in gcc that incorrectly warns about using uninitialized
598  // values if `remainingIn` is initialized through a copy constructor. We can
599  // get similar warnings for `sendMax` if it is initialized in the most
600  // natural way. Using `make_optional`, allows us to work around this bug.
601  TInAmt const sendMaxInit =
602  sendMaxST ? toAmount<TInAmt>(*sendMaxST) : TInAmt{beast::zero};
603  std::optional<TInAmt> const sendMax =
604  (sendMaxST && sendMaxInit >= beast::zero)
605  ? std::make_optional(sendMaxInit)
606  : std::nullopt;
607  std::optional<TInAmt> remainingIn =
608  !!sendMax ? std::make_optional(sendMaxInit) : std::nullopt;
609  // std::optional<TInAmt> remainingIn{sendMax};
610 
611  TOutAmt remainingOut(outReq);
612 
613  PaymentSandbox sb(&baseView);
614 
615  // non-dry strands
616  ActiveStrands activeStrands(strands);
617 
618  // Keeping a running sum of the amount in the order they are processed
619  // will not give the best precision. Keep a collection so they may be summed
620  // from smallest to largest
621  boost::container::flat_multiset<TInAmt> savedIns;
622  savedIns.reserve(maxTries);
623  boost::container::flat_multiset<TOutAmt> savedOuts;
624  savedOuts.reserve(maxTries);
625 
626  auto sum = [](auto const& col) {
627  using TResult = std::decay_t<decltype(*col.begin())>;
628  if (col.empty())
629  return TResult{beast::zero};
630  return std::accumulate(col.begin() + 1, col.end(), *col.begin());
631  };
632 
633  // These offers only need to be removed if the payment is not
634  // successful
635  boost::container::flat_set<uint256> ofrsToRmOnFail;
636 
637  while (remainingOut > beast::zero &&
638  (!remainingIn || *remainingIn > beast::zero))
639  {
640  ++curTry;
641  if (curTry >= maxTries)
642  {
643  return {telFAILED_PROCESSING, std::move(ofrsToRmOnFail)};
644  }
645 
646  activeStrands.activateNext(sb, limitQuality);
647 
648  ammContext.setMultiPath(activeStrands.size() > 1);
649 
650  // Limit only if one strand and limitQuality
651  auto const limitRemainingOut = [&]() {
652  if (activeStrands.size() == 1 && limitQuality)
653  if (auto const strand = activeStrands.get(0))
654  return limitOut(sb, *strand, remainingOut, *limitQuality);
655  return remainingOut;
656  }();
657  auto const adjustedRemOut = limitRemainingOut != remainingOut;
658 
659  boost::container::flat_set<uint256> ofrsToRm;
661  if (flowDebugInfo)
662  flowDebugInfo->newLiquidityPass();
663  // Index of strand to mark as inactive (remove from the active list) if
664  // the liquidity is used. This is used for strands that consume too many
665  // offers Constructed as `false,0` to workaround a gcc warning about
666  // uninitialized variables
667  std::optional<std::size_t> markInactiveOnUse;
668  for (size_t strandIndex = 0, sie = activeStrands.size();
669  strandIndex != sie;
670  ++strandIndex)
671  {
672  Strand const* strand = activeStrands.get(strandIndex);
673  if (!strand)
674  {
675  // should not happen
676  continue;
677  }
678  // Clear AMM liquidity used flag. The flag might still be set if
679  // the previous strand execution failed. It has to be reset
680  // since this strand might not have AMM liquidity.
681  ammContext.clear();
682  if (offerCrossing && limitQuality)
683  {
684  auto const strandQ = qualityUpperBound(sb, *strand);
685  if (!strandQ || *strandQ < *limitQuality)
686  continue;
687  }
688  auto f = flow<TInAmt, TOutAmt>(
689  sb, *strand, remainingIn, limitRemainingOut, j);
690 
691  // rm bad offers even if the strand fails
692  SetUnion(ofrsToRm, f.ofrsToRm);
693 
694  offersConsidered += f.ofrsUsed;
695 
696  if (!f.success || f.out == beast::zero)
697  continue;
698 
699  if (flowDebugInfo)
700  flowDebugInfo->pushLiquiditySrc(
701  EitherAmount(f.in), EitherAmount(f.out));
702 
703  assert(
704  f.out <= remainingOut && f.sandbox &&
705  (!remainingIn || f.in <= *remainingIn));
706 
707  Quality const q(f.out, f.in);
708 
709  JLOG(j.trace())
710  << "New flow iter (iter, in, out): " << curTry - 1 << " "
711  << to_string(f.in) << " " << to_string(f.out);
712 
713  // limitOut() finds output to generate exact requested
714  // limitQuality. But the actual limit quality might be slightly
715  // off due to the round off.
716  if (limitQuality && q < *limitQuality &&
717  (!adjustedRemOut ||
718  !withinRelativeDistance(q, *limitQuality, Number(1, -7))))
719  {
720  JLOG(j.trace())
721  << "Path rejected by limitQuality"
722  << " limit: " << *limitQuality << " path q: " << q;
723  continue;
724  }
725 
726  if (baseView.rules().enabled(featureFlowSortStrands))
727  {
728  assert(!best);
729  if (!f.inactive)
730  activeStrands.push(strand);
731  best.emplace(f.in, f.out, std::move(*f.sandbox), *strand, q);
732  activeStrands.pushRemainingCurToNext(strandIndex + 1);
733  break;
734  }
735 
736  activeStrands.push(strand);
737 
738  if (!best || best->quality < q ||
739  (best->quality == q && best->out < f.out))
740  {
741  // If this strand is inactive (because it consumed too many
742  // offers) and ends up having the best quality, remove it
743  // from the activeStrands. If it doesn't end up having the
744  // best quality, keep it active.
745 
746  if (f.inactive)
747  {
748  // This should be `nextSize`, not `size`. This issue is
749  // fixed in featureFlowSortStrands.
750  markInactiveOnUse = activeStrands.size() - 1;
751  }
752  else
753  {
754  markInactiveOnUse.reset();
755  }
756 
757  best.emplace(f.in, f.out, std::move(*f.sandbox), *strand, q);
758  }
759  }
760 
761  bool const shouldBreak = [&] {
762  if (baseView.rules().enabled(featureFlowSortStrands))
763  return !best || offersConsidered >= maxOffersToConsider;
764  return !best;
765  }();
766 
767  if (best)
768  {
769  if (markInactiveOnUse)
770  {
771  activeStrands.removeIndex(*markInactiveOnUse);
772  markInactiveOnUse.reset();
773  }
774  savedIns.insert(best->in);
775  savedOuts.insert(best->out);
776  remainingOut = outReq - sum(savedOuts);
777  if (sendMax)
778  remainingIn = *sendMax - sum(savedIns);
779 
780  if (flowDebugInfo)
781  flowDebugInfo->pushPass(
782  EitherAmount(best->in),
783  EitherAmount(best->out),
784  activeStrands.size());
785 
786  JLOG(j.trace()) << "Best path: in: " << to_string(best->in)
787  << " out: " << to_string(best->out)
788  << " remainingOut: " << to_string(remainingOut);
789 
790  best->sb.apply(sb);
791  ammContext.update();
792  }
793  else
794  {
795  JLOG(j.trace()) << "All strands dry.";
796  }
797 
798  best.reset(); // view in best must be destroyed before modifying base
799  // view
800  if (!ofrsToRm.empty())
801  {
802  SetUnion(ofrsToRmOnFail, ofrsToRm);
803  for (auto const& o : ofrsToRm)
804  {
805  if (auto ok = sb.peek(keylet::offer(o)))
806  offerDelete(sb, ok, j);
807  }
808  }
809 
810  if (shouldBreak)
811  break;
812  }
813 
814  auto const actualOut = sum(savedOuts);
815  auto const actualIn = sum(savedIns);
816 
817  JLOG(j.trace()) << "Total flow: in: " << to_string(actualIn)
818  << " out: " << to_string(actualOut);
819 
820  /* flowCross doesn't handle offer crossing with tfFillOrKill flag correctly.
821  * 1. If tfFillOrKill is set then the owner must receive the full
822  * TakerPays. We reverse pays and gets because during crossing
823  * we are taking, therefore the owner must deliver the full TakerPays and
824  * the entire TakerGets doesn't have to be spent.
825  * Pre-fixFillOrKill amendment code fails if the entire TakerGets
826  * is not spent. fixFillOrKill addresses this issue.
827  * 2. If tfSell is also set then the owner must spend the entire TakerGets
828  * even if it means obtaining more than TakerPays. Since the pays and gets
829  * are reversed, the owner must send the entire TakerGets.
830  */
831  bool const fillOrKillEnabled = baseView.rules().enabled(fixFillOrKill);
832 
833  if (actualOut != outReq)
834  {
835  if (actualOut > outReq)
836  {
837  assert(0);
838  return {tefEXCEPTION, std::move(ofrsToRmOnFail)};
839  }
840  if (!partialPayment)
841  {
842  // If we're offerCrossing a !partialPayment, then we're
843  // handling tfFillOrKill.
844  // Pre-fixFillOrKill amendment:
845  // That case is handled below; not here.
846  // fixFillOrKill amendment:
847  // That case is handled here if tfSell is also not set; i.e,
848  // case 1.
849  if (!offerCrossing ||
850  (fillOrKillEnabled && offerCrossing != OfferCrossing::sell))
851  return {
853  actualIn,
854  actualOut,
855  std::move(ofrsToRmOnFail)};
856  }
857  else if (actualOut == beast::zero)
858  {
859  return {tecPATH_DRY, std::move(ofrsToRmOnFail)};
860  }
861  }
862  if (offerCrossing &&
863  (!partialPayment &&
864  (!fillOrKillEnabled || offerCrossing == OfferCrossing::sell)))
865  {
866  // If we're offer crossing and partialPayment is *not* true, then
867  // we're handling a FillOrKill offer. In this case remainingIn must
868  // be zero (all funds must be consumed) or else we kill the offer.
869  // Pre-fixFillOrKill amendment:
870  // Handles both cases 1. and 2.
871  // fixFillOrKill amendment:
872  // Handles 2. 1. is handled above and falls through for tfSell.
873  assert(remainingIn);
874  if (remainingIn && *remainingIn != beast::zero)
875  return {
877  actualIn,
878  actualOut,
879  std::move(ofrsToRmOnFail)};
880  }
881 
882  return {actualIn, actualOut, std::move(sb), std::move(ofrsToRmOnFail)};
883 }
884 
885 } // namespace ripple
886 
887 #endif
beast::Journal::fatal
Stream fatal() const
Definition: Journal.h:338
sstream
ripple::OfferCrossing
OfferCrossing
Definition: Steps.h:42
ripple::Rules::enabled
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:94
beast::Journal::trace
Stream trace() const
Severity stream access functions.
Definition: Journal.h:308
ripple::composed_quality
Quality composed_quality(Quality const &lhs, Quality const &rhs)
Definition: Quality.cpp:124
ripple::StrandResult
Result of flow() execution of a single Strand.
Definition: StrandFlow.h:47
ripple::DebtDirection
DebtDirection
Definition: Steps.h:39
ripple::PaymentSandbox
A wrapper which makes credits unavailable to balances.
Definition: PaymentSandbox.h:112
ripple::path::detail::FlowDebugInfo
Definition: FlowDebugInfo.h:38
ripple::StrandResult::ofrsUsed
std::uint32_t ofrsUsed
Definition: StrandFlow.h:56
std::vector::reserve
T reserve(T... args)
ripple::AMMContext::update
void update()
Definition: AMMContext.h:82
std::vector
STL class.
std::vector::size
T size(T... args)
std::make_optional
T make_optional(T... args)
iterator
ripple::keylet::offer
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition: Indexes.cpp:232
std::optional::emplace
T emplace(T... args)
beast::Journal::warn
Stream warn() const
Definition: Journal.h:326
ripple::StrandResult::out
TOutAmt out
Currency amount out.
Definition: StrandFlow.h:51
ripple::QualityDirection::in
@ in
std::any_of
T any_of(T... args)
std::optional::reset
T reset(T... args)
algorithm
ripple::AMMContext::clear
void clear()
Strand execution may fail.
Definition: AMMContext.h:111
std::vector::clear
T clear(T... args)
std::tie
T tie(T... args)
std::vector::push_back
T push_back(T... args)
ripple::StrandResult::ofrsToRm
boost::container::flat_set< uint256 > ofrsToRm
Offers to remove.
Definition: StrandFlow.h:53
ripple::flow
path::RippleCalc::Output flow(PaymentSandbox &sb, STAmount const &deliver, AccountID const &src, AccountID const &dst, STPathSet const &paths, bool defaultPaths, bool partialPayment, bool ownerPaysTransferFee, OfferCrossing offerCrossing, std::optional< Quality > const &limitQuality, std::optional< STAmount > const &sendMax, beast::Journal j, path::detail::FlowDebugInfo *flowDebugInfo)
Make a payment from the src account to the dst account.
Definition: Flow.cpp:59
ripple::StrandResult::in
TInAmt in
Currency amount in.
Definition: StrandFlow.h:50
ripple::telFAILED_PROCESSING
@ telFAILED_PROCESSING
Definition: TER.h:56
ripple::QualityDirection::out
@ out
ripple::Number
Definition: Number.h:36
ripple::AMMContext
Maintains AMM info per overall payment engine execution and individual iteration.
Definition: AMMContext.h:35
ripple::offerDelete
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition: View.cpp:910
ripple::TER
TERSubset< CanCvtToTER > TER
Definition: TER.h:614
ripple::StrandResult::StrandResult
StrandResult(Strand const &strand, boost::container::flat_set< uint256 > ofrsToRm_)
Definition: StrandFlow.h:82
ripple::limitStepOut
static void limitStepOut(Offer const &offer, TAmounts< TIn, TOut > &ofrAmt, TAmounts< TIn, TOut > &stpAmt, TOut &ownerGives, std::uint32_t transferRateIn, std::uint32_t transferRateOut, TOut const &limit, Rules const &rules)
Definition: BookStep.cpp:609
std::accumulate
T accumulate(T... args)
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:58
std::uint32_t
ripple::temUNKNOWN
@ temUNKNOWN
Definition: TER.h:123
ripple::tecPATH_PARTIAL
@ tecPATH_PARTIAL
Definition: TER.h:265
ripple::featureFlowSortStrands
const uint256 featureFlowSortStrands
std::swap
T swap(T... args)
std::min
T min(T... args)
std::decay_t
ripple::DebtDirection::issues
@ issues
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::StrandResult::success
bool success
Strand succeeded.
Definition: StrandFlow.h:49
ripple::Result
boost::outcome_v2::result< T, std::error_code > Result
Definition: b58_utils.h:37
std::vector::begin
T begin(T... args)
std
STL namespace.
ripple::EitherAmount
Definition: AmountSpec.h:59
ripple::tecPATH_DRY
@ tecPATH_DRY
Definition: TER.h:277
std::vector::empty
T empty(T... args)
ripple::SetUnion
void SetUnion(boost::container::flat_set< T > &dst, boost::container::flat_set< T > const &src)
Given two flat sets dst and src, compute dst = dst union src.
Definition: FlatSets.h:35
std::optional
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:38
ripple::detail::ApplyViewBase::peek
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
Definition: ApplyViewBase.cpp:128
ripple::detail::ApplyViewBase::rules
Rules const & rules() const override
Returns the tx processing rules.
Definition: ApplyViewBase.cpp:52
std::vector::end
T end(T... args)
ripple::StrandResult::StrandResult
StrandResult()=default
Strand result constructor.
ripple::tefEXCEPTION
@ tefEXCEPTION
Definition: TER.h:169
numeric
ripple::fixFillOrKill
const uint256 fixFillOrKill
std::stable_sort
T stable_sort(T... args)
std::unique_ptr
STL class.
ripple::sum
static auto sum(TCollection const &col)
Definition: BookStep.cpp:884
ripple::StrandResult::sandbox
std::optional< PaymentSandbox > sandbox
Resulting Sandbox state.
Definition: StrandFlow.h:52
ripple::StrandResult::inactive
bool inactive
Strand should not considered as a further source of liquidity (dry)
Definition: StrandFlow.h:59
ripple::tesSUCCESS
@ tesSUCCESS
Definition: TER.h:238
ripple::STAmount::uRateOne
static const std::uint64_t uRateOne
Definition: STAmount.h:76
ripple::get
T & get(EitherAmount &amt)
Definition: AmountSpec.h:118
ripple::AMMContext::setMultiPath
void setMultiPath(bool fs)
Definition: AMMContext.h:70
std::next
T next(T... args)
ripple::StrandResult::StrandResult
StrandResult(Strand const &strand, TInAmt const &in_, TOutAmt const &out_, PaymentSandbox &&sandbox_, boost::container::flat_set< uint256 > ofrsToRm_, bool inactive_)
Definition: StrandFlow.h:65