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