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