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