20 #ifndef RIPPLE_APP_PATHS_IMPL_STRANDFLOW_H_INCLUDED
21 #define RIPPLE_APP_PATHS_IMPL_STRANDFLOW_H_INCLUDED
23 #include <ripple/app/paths/Credit.h>
24 #include <ripple/app/paths/Flow.h>
25 #include <ripple/app/paths/impl/AmountSpec.h>
26 #include <ripple/app/paths/impl/FlatSets.h>
27 #include <ripple/app/paths/impl/FlowDebugInfo.h>
28 #include <ripple/app/paths/impl/Steps.h>
29 #include <ripple/basics/IOUAmount.h>
30 #include <ripple/basics/Log.h>
31 #include <ripple/basics/XRPAmount.h>
32 #include <ripple/protocol/Feature.h>
34 #include <boost/container/flat_set.hpp>
44 template <
class TInAmt,
class TOutAmt>
48 TInAmt
in = beast::zero;
49 TOutAmt
out = beast::zero;
68 boost::container::flat_set<uint256> ofrsToRm_,
82 boost::container::flat_set<uint256> ofrsToRm_)
101 template <
class TInAmt,
class TOutAmt>
102 StrandResult<TInAmt, TOutAmt>
105 Strand
const& strand,
113 JLOG(j.
warn()) <<
"Empty strand passed to Liquidity";
117 boost::container::flat_set<uint256> ofrsToRm;
119 if (isDirectXrpToXrp<TInAmt, TOutAmt>(strand))
121 return Result{strand, std::move(ofrsToRm)};
137 for (
auto i = s; i--;)
139 auto r = strand[i]->rev(*sb, *afView, ofrsToRm, stepOut);
140 if (strand[i]->isZero(r.second))
142 JLOG(j.
trace()) <<
"Strand found dry in rev";
143 return Result{strand, std::move(ofrsToRm)};
146 if (i == 0 && maxIn && *maxIn < get<TInAmt>(r.first))
158 if (strand[i]->isZero(r.second))
160 JLOG(j.
trace()) <<
"First step found dry";
161 return Result{strand, std::move(ofrsToRm)};
163 if (get<TInAmt>(r.first) != *maxIn)
169 <<
"Re-executed limiting step failed. r.first: "
173 return Result{strand, std::move(ofrsToRm)};
176 else if (!strand[i]->equalOut(r.second, stepOut))
186 r = strand[i]->rev(*sb, *afView, ofrsToRm, stepOut);
189 if (strand[i]->isZero(r.second))
193 JLOG(j.
trace()) <<
"Limiting step found dry";
194 return Result{strand, std::move(ofrsToRm)};
196 if (!strand[i]->equalOut(r.second, stepOut))
203 <<
"Re-executed limiting step failed. r.second: "
204 << r.second <<
" stepOut: " << stepOut;
206 JLOG(j.
fatal()) <<
"Re-executed limiting step failed";
209 return Result{strand, std::move(ofrsToRm)};
220 for (
auto i = limitingStep + 1; i < s; ++i)
222 auto const r = strand[i]->fwd(*sb, *afView, ofrsToRm, stepIn);
223 if (strand[i]->isZero(r.second))
227 JLOG(j.
trace()) <<
"Non-limiting step found dry";
228 return Result{strand, std::move(ofrsToRm)};
230 if (!strand[i]->equalIn(r.first, stepIn))
237 <<
"Re-executed forward pass failed. r.first: "
238 << r.first <<
" stepIn: " << stepIn;
240 JLOG(j.
fatal()) <<
"Re-executed forward pass failed";
243 return Result{strand, std::move(ofrsToRm)};
249 auto const strandIn = *strand.front()->cachedIn();
250 auto const strandOut = *strand.back()->cachedOut();
259 for (
auto i = 0; i < s; ++i)
263 strand[i]->validFwd(checkSB, checkAfView, stepIn);
267 <<
"Strand re-execute check failed. Step: " << i;
281 get<TInAmt>(strandIn),
282 get<TOutAmt>(strandOut),
287 catch (FlowException
const&)
289 return Result{strand, std::move(ofrsToRm)};
294 template <
class TInAmt,
class TOutAmt>
297 TInAmt in = beast::zero;
298 TOutAmt out = beast::zero;
300 boost::container::flat_set<uint256> removableOffers;
303 FlowResult() =
default;
308 PaymentSandbox&& sandbox_,
309 boost::container::flat_set<uint256> ofrsToRm)
312 , sandbox(
std::move(sandbox_))
313 , removableOffers(
std::move(ofrsToRm))
318 FlowResult(
TER ter_, boost::container::flat_set<uint256> ofrsToRm)
319 : removableOffers(
std::move(ofrsToRm)), ter(ter_)
327 boost::container::flat_set<uint256> ofrsToRm)
328 :
in(in_),
out(out_), removableOffers(
std::move(ofrsToRm)), ter(ter_)
336 qualityUpperBound(ReadView
const& v, Strand
const& strand)
341 for (
auto const& step : strand)
343 if (
std::tie(stepQ, dir) = step->qualityUpperBound(v, dir); stepQ)
373 for (
auto& strand : strands)
389 if (next_.
size() > 1)
391 for (Strand
const* strand : next_)
398 if (
auto const qual = qualityUpperBound(v, *strand))
400 if (limitQuality && *qual < *limitQuality)
418 [](
auto const& lhs,
auto const& rhs) {
420 return std::get<Quality>(lhs) > std::get<Quality>(rhs);
423 next_.reserve(strandQuals.
size());
424 for (
auto const& sq : strandQuals)
426 next_.push_back(std::get<Strand const*>(sq));
436 if (i >= cur_.
size())
445 push(Strand
const* s)
452 pushRemainingCurToNext(
size_t i)
454 if (i >= cur_.
size())
468 if (i >= next_.size())
470 next_.erase(next_.begin() + i);
494 template <
class TInAmt,
class TOutAmt>
495 FlowResult<TInAmt, TOutAmt>
499 TOutAmt
const& outReq,
514 Strand
const& strand;
521 Strand
const& strand_,
522 Quality
const& quality_)
541 TInAmt
const sendMaxInit =
542 sendMaxST ? toAmount<TInAmt>(*sendMaxST) : TInAmt{beast::zero};
544 (sendMaxST && sendMaxInit >= beast::zero)
551 TOutAmt remainingOut(outReq);
556 ActiveStrands activeStrands(strands);
561 boost::container::flat_multiset<TInAmt> savedIns;
562 savedIns.reserve(maxTries);
563 boost::container::flat_multiset<TOutAmt> savedOuts;
564 savedOuts.reserve(maxTries);
566 auto sum = [](
auto const& col) {
569 return TResult{beast::zero};
575 boost::container::flat_set<uint256> ofrsToRmOnFail;
577 while (remainingOut > beast::zero &&
578 (!remainingIn || *remainingIn > beast::zero))
581 if (curTry >= maxTries)
586 activeStrands.activateNext(sb, limitQuality);
588 boost::container::flat_set<uint256> ofrsToRm;
591 flowDebugInfo->newLiquidityPass();
597 for (
size_t strandIndex = 0, sie = activeStrands.size();
601 Strand
const* strand = activeStrands.get(strandIndex);
607 if (offerCrossing && limitQuality)
609 auto const strandQ = qualityUpperBound(sb, *strand);
610 if (!strandQ || *strandQ < *limitQuality)
613 auto f = flow<TInAmt, TOutAmt>(
614 sb, *strand, remainingIn, remainingOut, j);
619 offersConsidered += f.ofrsUsed;
621 if (!f.success || f.out == beast::zero)
625 flowDebugInfo->pushLiquiditySrc(
629 f.out <= remainingOut && f.sandbox &&
630 (!remainingIn || f.in <= *remainingIn));
632 Quality
const q(f.out, f.in);
635 <<
"New flow iter (iter, in, out): " << curTry - 1 <<
" "
638 if (limitQuality && q < *limitQuality)
641 <<
"Path rejected by limitQuality"
642 <<
" limit: " << *limitQuality <<
" path q: " << q;
650 activeStrands.push(strand);
651 best.
emplace(f.in, f.out, std::move(*f.sandbox), *strand, q);
652 activeStrands.pushRemainingCurToNext(strandIndex + 1);
656 activeStrands.push(strand);
658 if (!best || best->quality < q ||
659 (best->quality == q && best->out < f.out))
670 markInactiveOnUse = activeStrands.size() - 1;
674 markInactiveOnUse.
reset();
677 best.
emplace(f.in, f.out, std::move(*f.sandbox), *strand, q);
681 bool const shouldBreak = [&] {
683 return !best || offersConsidered >= maxOffersToConsider;
689 if (markInactiveOnUse)
691 activeStrands.removeIndex(*markInactiveOnUse);
692 markInactiveOnUse.
reset();
694 savedIns.insert(best->in);
695 savedOuts.insert(best->out);
696 remainingOut = outReq -
sum(savedOuts);
698 remainingIn = *sendMax -
sum(savedIns);
701 flowDebugInfo->pushPass(
704 activeStrands.size());
708 <<
" remainingOut: " <<
to_string(remainingOut);
714 JLOG(j.
trace()) <<
"All strands dry.";
719 if (!ofrsToRm.empty())
722 for (
auto const& o : ofrsToRm)
733 auto const actualOut =
sum(savedOuts);
734 auto const actualIn =
sum(savedIns);
739 if (actualOut != outReq)
741 if (actualOut > outReq)
755 std::move(ofrsToRmOnFail)};
757 else if (actualOut == beast::zero)
762 if (offerCrossing && !partialPayment)
768 if (remainingIn && *remainingIn != beast::zero)
773 std::move(ofrsToRmOnFail)};
776 return {actualIn, actualOut, std::move(sb), std::move(ofrsToRmOnFail)};