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