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