20#ifndef RIPPLE_APP_PATHS_IMPL_STRANDFLOW_H_INCLUDED
21#define RIPPLE_APP_PATHS_IMPL_STRANDFLOW_H_INCLUDED
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>
37#include <boost/container/flat_set.hpp>
47template <
class TInAmt,
class TOutAmt>
51 TInAmt
in = beast::zero;
52 TOutAmt
out = beast::zero;
71 boost::container::flat_set<uint256> ofrsToRm_,
85 boost::container::flat_set<uint256> ofrsToRm_)
104template <
class TInAmt,
class TOutAmt>
105StrandResult<TInAmt, TOutAmt>
108 Strand
const& strand,
116 JLOG(j.
warn()) <<
"Empty strand passed to Liquidity";
120 boost::container::flat_set<uint256> ofrsToRm;
122 if (isDirectXrpToXrp<TInAmt, TOutAmt>(strand))
124 return Result{strand, std::move(ofrsToRm)};
140 for (
auto i = s; i--;)
142 auto r = strand[i]->rev(*sb, *afView, ofrsToRm, stepOut);
143 if (strand[i]->isZero(r.second))
145 JLOG(j.
trace()) <<
"Strand found dry in rev";
146 return Result{strand, std::move(ofrsToRm)};
149 if (i == 0 && maxIn && *maxIn < get<TInAmt>(r.first))
161 if (strand[i]->isZero(r.second))
163 JLOG(j.
trace()) <<
"First step found dry";
164 return Result{strand, std::move(ofrsToRm)};
166 if (get<TInAmt>(r.first) != *maxIn)
172 <<
"Re-executed limiting step failed. r.first: "
176 "ripple::flow : first step re-executing the "
177 "limiting step failed");
178 return Result{strand, std::move(ofrsToRm)};
181 else if (!strand[i]->equalOut(r.second, stepOut))
191 r = strand[i]->rev(*sb, *afView, ofrsToRm, stepOut);
194 if (strand[i]->isZero(r.second))
198 JLOG(j.
trace()) <<
"Limiting step found dry";
199 return Result{strand, std::move(ofrsToRm)};
201 if (!strand[i]->equalOut(r.second, stepOut))
208 <<
"Re-executed limiting step failed. r.second: "
209 << r.second <<
" stepOut: " << stepOut;
211 JLOG(j.
fatal()) <<
"Re-executed limiting step failed";
214 "ripple::flow : limiting step re-executing the "
215 "limiting step failed");
216 return Result{strand, std::move(ofrsToRm)};
227 for (
auto i = limitingStep + 1; i < s; ++i)
229 auto const r = strand[i]->fwd(*sb, *afView, ofrsToRm, stepIn);
230 if (strand[i]->isZero(r.second))
234 JLOG(j.
trace()) <<
"Non-limiting step found dry";
235 return Result{strand, std::move(ofrsToRm)};
237 if (!strand[i]->equalIn(r.first, stepIn))
244 <<
"Re-executed forward pass failed. r.first: "
245 << r.first <<
" stepIn: " << stepIn;
247 JLOG(j.
fatal()) <<
"Re-executed forward pass failed";
250 "ripple::flow : non-limiting step re-executing the "
251 "forward pass failed");
252 return Result{strand, std::move(ofrsToRm)};
258 auto const strandIn = *strand.front()->cachedIn();
259 auto const strandOut = *strand.back()->cachedOut();
268 for (
auto i = 0; i < s; ++i)
272 strand[i]->validFwd(checkSB, checkAfView, stepIn);
276 <<
"Strand re-execute check failed. Step: " << i;
290 get<TInAmt>(strandIn),
291 get<TOutAmt>(strandOut),
296 catch (FlowException
const&)
298 return Result{strand, std::move(ofrsToRm)};
303template <
class TInAmt,
class TOutAmt>
306 TInAmt in = beast::zero;
307 TOutAmt out = beast::zero;
309 boost::container::flat_set<uint256> removableOffers;
310 TER ter = temUNKNOWN;
312 FlowResult() =
default;
317 PaymentSandbox&& sandbox_,
318 boost::container::flat_set<uint256> ofrsToRm)
321 , sandbox(
std::move(sandbox_))
322 , removableOffers(
std::move(ofrsToRm))
327 FlowResult(TER ter_, boost::container::flat_set<uint256> ofrsToRm)
328 : removableOffers(
std::move(ofrsToRm)), ter(ter_)
336 boost::container::flat_set<uint256> ofrsToRm)
337 :
in(in_),
out(out_), removableOffers(
std::move(ofrsToRm)), ter(ter_)
345qualityUpperBound(ReadView
const& v, Strand
const& strand)
350 for (
auto const& step : strand)
352 if (
std::tie(stepQ, dir) = step->qualityUpperBound(v, dir); stepQ)
370template <
typename TOutAmt>
374 Strand
const& strand,
375 TOutAmt
const& remainingOut,
376 Quality
const& limitQuality)
381 for (
auto const& step : strand)
383 if (
std::tie(stepQualityFunc, dir) = step->getQualityFunc(v, dir);
387 qf = stepQualityFunc;
389 qf->combine(*stepQualityFunc);
396 if (!qf || qf->isConst())
399 auto const out = [&]() {
400 if (
auto const out = qf->outFromAvgQ(limitQuality); !
out)
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};
408 remainingOut.issue(),
out->mantissa(),
out->exponent()};
438 for (
auto& strand : strands)
450 if (v.rules().enabled(featureFlowSortStrands) && !next_.
empty())
454 if (next_.
size() > 1)
456 for (Strand
const* strand : next_)
463 if (
auto const qual = qualityUpperBound(v, *strand))
465 if (limitQuality && *qual < *limitQuality)
483 [](
auto const& lhs,
auto const& rhs) {
485 return std::get<Quality>(lhs) > std::get<Quality>(rhs);
488 next_.reserve(strandQuals.
size());
489 for (
auto const& sq : strandQuals)
491 next_.push_back(std::get<Strand const*>(sq));
501 if (i >= cur_.
size())
503 UNREACHABLE(
"ripple::ActiveStrands::get : input out of range");
510 push(Strand
const* s)
517 pushRemainingCurToNext(
size_t i)
519 if (i >= cur_.
size())
533 if (i >= next_.size())
535 next_.erase(next_.begin() + i);
560template <
class TInAmt,
class TOutAmt>
561FlowResult<TInAmt, TOutAmt>
565 TOutAmt
const& outReq,
581 Strand
const& strand;
588 Strand
const& strand_,
589 Quality
const& quality_)
608 TInAmt
const sendMaxInit =
609 sendMaxST ? toAmount<TInAmt>(*sendMaxST) : TInAmt{beast::zero};
611 (sendMaxST && sendMaxInit >= beast::zero)
618 TOutAmt remainingOut(outReq);
623 ActiveStrands activeStrands(strands);
628 boost::container::flat_multiset<TInAmt> savedIns;
629 savedIns.reserve(maxTries);
630 boost::container::flat_multiset<TOutAmt> savedOuts;
631 savedOuts.reserve(maxTries);
633 auto sum = [](
auto const& col) {
636 return TResult{beast::zero};
642 boost::container::flat_set<uint256> ofrsToRmOnFail;
644 while (remainingOut > beast::zero &&
645 (!remainingIn || *remainingIn > beast::zero))
648 if (curTry >= maxTries)
653 activeStrands.activateNext(sb, 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);
664 auto const adjustedRemOut = limitRemainingOut != remainingOut;
666 boost::container::flat_set<uint256> ofrsToRm;
669 flowDebugInfo->newLiquidityPass();
675 for (
size_t strandIndex = 0, sie = activeStrands.size();
679 Strand
const* strand = activeStrands.get(strandIndex);
689 if (offerCrossing && limitQuality)
691 auto const strandQ = qualityUpperBound(sb, *strand);
692 if (!strandQ || *strandQ < *limitQuality)
695 auto f = flow<TInAmt, TOutAmt>(
696 sb, *strand, remainingIn, limitRemainingOut, j);
701 offersConsidered += f.ofrsUsed;
703 if (!f.success || f.out == beast::zero)
707 flowDebugInfo->pushLiquiditySrc(
711 f.out <= remainingOut && f.sandbox &&
712 (!remainingIn || f.in <= *remainingIn),
713 "ripple::flow : remaining constraints");
715 Quality
const q(f.out, f.in);
718 <<
"New flow iter (iter, in, out): " << curTry - 1 <<
" "
724 if (limitQuality && q < *limitQuality &&
729 <<
"Path rejected by limitQuality"
730 <<
" limit: " << *limitQuality <<
" path q: " << q;
736 XRPL_ASSERT(!best,
"ripple::flow : best is unset");
738 activeStrands.push(strand);
739 best.
emplace(f.in, f.out, std::move(*f.sandbox), *strand, q);
740 activeStrands.pushRemainingCurToNext(strandIndex + 1);
744 activeStrands.push(strand);
746 if (!best || best->quality < q ||
747 (best->quality == q && best->out < f.out))
758 markInactiveOnUse = activeStrands.size() - 1;
762 markInactiveOnUse.
reset();
765 best.
emplace(f.in, f.out, std::move(*f.sandbox), *strand, q);
769 bool const shouldBreak = [&] {
771 return !best || offersConsidered >= maxOffersToConsider;
777 if (markInactiveOnUse)
779 activeStrands.removeIndex(*markInactiveOnUse);
780 markInactiveOnUse.
reset();
782 savedIns.insert(best->in);
783 savedOuts.insert(best->out);
784 remainingOut = outReq -
sum(savedOuts);
786 remainingIn = *sendMax -
sum(savedIns);
789 flowDebugInfo->pushPass(
792 activeStrands.size());
796 <<
" remainingOut: " <<
to_string(remainingOut);
803 JLOG(j.
trace()) <<
"All strands dry.";
808 if (!ofrsToRm.empty())
811 for (
auto const& o : ofrsToRm)
822 auto const actualOut =
sum(savedOuts);
823 auto const actualIn =
sum(savedIns);
839 bool const fillOrKillEnabled = baseView.
rules().
enabled(fixFillOrKill);
841 if (actualOut != outReq)
843 if (actualOut > outReq)
863 if (!offerCrossing ||
869 std::move(ofrsToRmOnFail)};
871 else if (actualOut == beast::zero)
887 XRPL_ASSERT(remainingIn,
"ripple::flow : nonzero remainingIn");
888 if (remainingIn && *remainingIn != beast::zero)
893 std::move(ofrsToRmOnFail)};
896 return {actualIn, actualOut, std::move(sb), std::move(ofrsToRmOnFail)};
A generic endpoint for log messages.
Stream trace() const
Severity stream access functions.
Maintains AMM info per overall payment engine execution and individual iteration.
void setMultiPath(bool fs)
void clear()
Strand execution may fail.
A wrapper which makes credits unavailable to balances.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
static std::uint64_t const uRateOne
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 make_optional(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.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
static auto sum(TCollection const &col)
boost::outcome_v2::result< T, std::error_code > Result
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.
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Quality composed_quality(Quality const &lhs, Quality const &rhs)
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.
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)
std::string to_string(base_uint< Bits, Tag > const &a)
T get(Section const §ion, 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.
Result of flow() execution of a single Strand.
bool inactive
Strand should not considered as a further source of liquidity (dry)
bool success
Strand succeeded.
std::optional< PaymentSandbox > sandbox
Resulting Sandbox state.
TOutAmt out
Currency amount out.
StrandResult(Strand const &strand, TInAmt const &in_, TOutAmt const &out_, PaymentSandbox &&sandbox_, boost::container::flat_set< uint256 > ofrsToRm_, bool inactive_)
boost::container::flat_set< uint256 > ofrsToRm
Offers to remove.
TInAmt in
Currency amount in.
StrandResult(Strand const &strand, boost::container::flat_set< uint256 > ofrsToRm_)
StrandResult()=default
Strand result constructor.