20 #ifndef RIPPLE_APP_PATHS_IMPL_STRANDFLOW_H_INCLUDED
21 #define RIPPLE_APP_PATHS_IMPL_STRANDFLOW_H_INCLUDED
23 #include <ripple/app/misc/AMMHelpers.h>
24 #include <ripple/app/paths/AMMContext.h>
25 #include <ripple/app/paths/Credit.h>
26 #include <ripple/app/paths/Flow.h>
27 #include <ripple/app/paths/impl/AmountSpec.h>
28 #include <ripple/app/paths/impl/FlatSets.h>
29 #include <ripple/app/paths/impl/FlowDebugInfo.h>
30 #include <ripple/app/paths/impl/Steps.h>
31 #include <ripple/basics/IOUAmount.h>
32 #include <ripple/basics/Log.h>
33 #include <ripple/basics/XRPAmount.h>
34 #include <ripple/protocol/Feature.h>
36 #include <boost/container/flat_set.hpp>
46 template <
class TInAmt,
class TOutAmt>
50 TInAmt
in = beast::zero;
51 TOutAmt
out = beast::zero;
70 boost::container::flat_set<uint256> ofrsToRm_,
84 boost::container::flat_set<uint256> ofrsToRm_)
103 template <
class TInAmt,
class TOutAmt>
104 StrandResult<TInAmt, TOutAmt>
107 Strand
const& strand,
115 JLOG(j.
warn()) <<
"Empty strand passed to Liquidity";
119 boost::container::flat_set<uint256> ofrsToRm;
121 if (isDirectXrpToXrp<TInAmt, TOutAmt>(strand))
123 return Result{strand, std::move(ofrsToRm)};
139 for (
auto i = s; i--;)
141 auto r = strand[i]->rev(*sb, *afView, ofrsToRm, stepOut);
142 if (strand[i]->isZero(r.second))
144 JLOG(j.
trace()) <<
"Strand found dry in rev";
145 return Result{strand, std::move(ofrsToRm)};
148 if (i == 0 && maxIn && *maxIn < get<TInAmt>(r.first))
160 if (strand[i]->isZero(r.second))
162 JLOG(j.
trace()) <<
"First step found dry";
163 return Result{strand, std::move(ofrsToRm)};
165 if (get<TInAmt>(r.first) != *maxIn)
171 <<
"Re-executed limiting step failed. r.first: "
175 return Result{strand, std::move(ofrsToRm)};
178 else if (!strand[i]->equalOut(r.second, stepOut))
188 r = strand[i]->rev(*sb, *afView, ofrsToRm, stepOut);
191 if (strand[i]->isZero(r.second))
195 JLOG(j.
trace()) <<
"Limiting step found dry";
196 return Result{strand, std::move(ofrsToRm)};
198 if (!strand[i]->equalOut(r.second, stepOut))
205 <<
"Re-executed limiting step failed. r.second: "
206 << r.second <<
" stepOut: " << stepOut;
208 JLOG(j.
fatal()) <<
"Re-executed limiting step failed";
211 return Result{strand, std::move(ofrsToRm)};
222 for (
auto i = limitingStep + 1; i < s; ++i)
224 auto const r = strand[i]->fwd(*sb, *afView, ofrsToRm, stepIn);
225 if (strand[i]->isZero(r.second))
229 JLOG(j.
trace()) <<
"Non-limiting step found dry";
230 return Result{strand, std::move(ofrsToRm)};
232 if (!strand[i]->equalIn(r.first, stepIn))
239 <<
"Re-executed forward pass failed. r.first: "
240 << r.first <<
" stepIn: " << stepIn;
242 JLOG(j.
fatal()) <<
"Re-executed forward pass failed";
245 return Result{strand, std::move(ofrsToRm)};
251 auto const strandIn = *strand.front()->cachedIn();
252 auto const strandOut = *strand.back()->cachedOut();
261 for (
auto i = 0; i < s; ++i)
265 strand[i]->validFwd(checkSB, checkAfView, stepIn);
269 <<
"Strand re-execute check failed. Step: " << i;
283 get<TInAmt>(strandIn),
284 get<TOutAmt>(strandOut),
289 catch (FlowException
const&)
291 return Result{strand, std::move(ofrsToRm)};
296 template <
class TInAmt,
class TOutAmt>
299 TInAmt in = beast::zero;
300 TOutAmt out = beast::zero;
302 boost::container::flat_set<uint256> removableOffers;
305 FlowResult() =
default;
310 PaymentSandbox&& sandbox_,
311 boost::container::flat_set<uint256> ofrsToRm)
314 , sandbox(
std::move(sandbox_))
315 , removableOffers(
std::move(ofrsToRm))
320 FlowResult(
TER ter_, boost::container::flat_set<uint256> ofrsToRm)
321 : removableOffers(
std::move(ofrsToRm)), ter(ter_)
329 boost::container::flat_set<uint256> ofrsToRm)
330 :
in(in_),
out(out_), removableOffers(
std::move(ofrsToRm)), ter(ter_)
338 qualityUpperBound(ReadView
const& v, Strand
const& strand)
343 for (
auto const& step : strand)
345 if (
std::tie(stepQ, dir) = step->qualityUpperBound(v, dir); stepQ)
363 template <
typename TOutAmt>
367 Strand
const& strand,
368 TOutAmt
const& remainingOut,
369 Quality
const& limitQuality)
374 for (
auto const& step : strand)
376 if (
std::tie(stepQualityFunc, dir) = step->getQualityFunc(v, dir);
380 qf = stepQualityFunc;
382 qf->combine(*stepQualityFunc);
389 if (!qf || qf->isConst())
392 auto const out = [&]() {
393 if (
auto const out = qf->outFromAvgQ(limitQuality); !
out)
395 else if constexpr (std::is_same_v<TOutAmt, XRPAmount>)
396 return XRPAmount{*
out};
397 else if constexpr (std::is_same_v<TOutAmt, IOUAmount>)
398 return IOUAmount{*
out};
401 remainingOut.issue(),
out->mantissa(),
out->exponent()};
404 if (withinRelativeDistance(out, remainingOut, Number(1, -9)))
431 for (
auto& strand : strands)
447 if (next_.
size() > 1)
449 for (Strand
const* strand : next_)
456 if (
auto const qual = qualityUpperBound(v, *strand))
458 if (limitQuality && *qual < *limitQuality)
476 [](
auto const& lhs,
auto const& rhs) {
478 return std::get<Quality>(lhs) > std::get<Quality>(rhs);
481 next_.reserve(strandQuals.
size());
482 for (
auto const& sq : strandQuals)
484 next_.push_back(std::get<Strand const*>(sq));
494 if (i >= cur_.
size())
503 push(Strand
const* s)
510 pushRemainingCurToNext(
size_t i)
512 if (i >= cur_.
size())
526 if (i >= next_.size())
528 next_.erase(next_.begin() + i);
553 template <
class TInAmt,
class TOutAmt>
554 FlowResult<TInAmt, TOutAmt>
558 TOutAmt
const& outReq,
574 Strand
const& strand;
581 Strand
const& strand_,
582 Quality
const& quality_)
601 TInAmt
const sendMaxInit =
602 sendMaxST ? toAmount<TInAmt>(*sendMaxST) : TInAmt{beast::zero};
604 (sendMaxST && sendMaxInit >= beast::zero)
611 TOutAmt remainingOut(outReq);
616 ActiveStrands activeStrands(strands);
621 boost::container::flat_multiset<TInAmt> savedIns;
622 savedIns.reserve(maxTries);
623 boost::container::flat_multiset<TOutAmt> savedOuts;
624 savedOuts.reserve(maxTries);
626 auto sum = [](
auto const& col) {
629 return TResult{beast::zero};
635 boost::container::flat_set<uint256> ofrsToRmOnFail;
637 while (remainingOut > beast::zero &&
638 (!remainingIn || *remainingIn > beast::zero))
641 if (curTry >= maxTries)
646 activeStrands.activateNext(sb, limitQuality);
651 auto const limitRemainingOut = [&]() {
652 if (activeStrands.size() == 1 && limitQuality)
653 if (
auto const strand = activeStrands.get(0))
654 return limitOut(sb, *strand, remainingOut, *limitQuality);
657 auto const adjustedRemOut = limitRemainingOut != remainingOut;
659 boost::container::flat_set<uint256> ofrsToRm;
662 flowDebugInfo->newLiquidityPass();
668 for (
size_t strandIndex = 0, sie = activeStrands.size();
672 Strand
const* strand = activeStrands.get(strandIndex);
682 if (offerCrossing && limitQuality)
684 auto const strandQ = qualityUpperBound(sb, *strand);
685 if (!strandQ || *strandQ < *limitQuality)
688 auto f = flow<TInAmt, TOutAmt>(
689 sb, *strand, remainingIn, limitRemainingOut, j);
694 offersConsidered += f.ofrsUsed;
696 if (!f.success || f.out == beast::zero)
700 flowDebugInfo->pushLiquiditySrc(
704 f.out <= remainingOut && f.sandbox &&
705 (!remainingIn || f.in <= *remainingIn));
707 Quality
const q(f.out, f.in);
710 <<
"New flow iter (iter, in, out): " << curTry - 1 <<
" "
716 if (limitQuality && q < *limitQuality &&
718 !withinRelativeDistance(q, *limitQuality,
Number(1, -7))))
721 <<
"Path rejected by limitQuality"
722 <<
" limit: " << *limitQuality <<
" path q: " << q;
730 activeStrands.push(strand);
731 best.
emplace(f.in, f.out, std::move(*f.sandbox), *strand, q);
732 activeStrands.pushRemainingCurToNext(strandIndex + 1);
736 activeStrands.push(strand);
738 if (!best || best->quality < q ||
739 (best->quality == q && best->out < f.out))
750 markInactiveOnUse = activeStrands.size() - 1;
754 markInactiveOnUse.
reset();
757 best.
emplace(f.in, f.out, std::move(*f.sandbox), *strand, q);
761 bool const shouldBreak = [&] {
763 return !best || offersConsidered >= maxOffersToConsider;
769 if (markInactiveOnUse)
771 activeStrands.removeIndex(*markInactiveOnUse);
772 markInactiveOnUse.
reset();
774 savedIns.insert(best->in);
775 savedOuts.insert(best->out);
776 remainingOut = outReq -
sum(savedOuts);
778 remainingIn = *sendMax -
sum(savedIns);
781 flowDebugInfo->pushPass(
784 activeStrands.size());
788 <<
" remainingOut: " <<
to_string(remainingOut);
795 JLOG(j.
trace()) <<
"All strands dry.";
800 if (!ofrsToRm.empty())
803 for (
auto const& o : ofrsToRm)
814 auto const actualOut =
sum(savedOuts);
815 auto const actualIn =
sum(savedIns);
833 if (actualOut != outReq)
835 if (actualOut > outReq)
849 if (!offerCrossing ||
850 (fillOrKillEnabled && offerCrossing != OfferCrossing::sell))
855 std::move(ofrsToRmOnFail)};
857 else if (actualOut == beast::zero)
864 (!fillOrKillEnabled || offerCrossing == OfferCrossing::sell)))
874 if (remainingIn && *remainingIn != beast::zero)
879 std::move(ofrsToRmOnFail)};
882 return {actualIn, actualOut, std::move(sb), std::move(ofrsToRmOnFail)};