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>
32#include <xrpl/basics/Log.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>
46template <
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_)
103template <
class TInAmt,
class TOutAmt>
104StrandResult<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)
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)};
182 else if (!strand[i]->equalOut(r.second, stepOut))
192 r = strand[i]->rev(*sb, *afView, ofrsToRm, stepOut);
195 if (strand[i]->isZero(r.second))
199 JLOG(j.
trace()) <<
"Limiting step found dry";
200 return Result{strand, std::move(ofrsToRm)};
202 if (!strand[i]->equalOut(r.second, stepOut))
210 <<
"Re-executed limiting step failed. r.second: "
211 << r.second <<
" stepOut: " << stepOut;
213 JLOG(j.
fatal()) <<
"Re-executed limiting step failed";
216 "ripple::flow : limiting step re-executing the "
217 "limiting step failed");
218 return Result{strand, std::move(ofrsToRm)};
230 for (
auto i = limitingStep + 1; i < s; ++i)
232 auto const r = strand[i]->fwd(*sb, *afView, ofrsToRm, stepIn);
233 if (strand[i]->isZero(r.second))
237 JLOG(j.
trace()) <<
"Non-limiting step found dry";
238 return Result{strand, std::move(ofrsToRm)};
240 if (!strand[i]->equalIn(r.first, stepIn))
248 <<
"Re-executed forward pass failed. r.first: "
249 << r.first <<
" stepIn: " << stepIn;
251 JLOG(j.
fatal()) <<
"Re-executed forward pass failed";
254 "ripple::flow : non-limiting step re-executing the "
255 "forward pass failed");
256 return Result{strand, std::move(ofrsToRm)};
263 auto const strandIn = *strand.front()->cachedIn();
264 auto const strandOut = *strand.back()->cachedOut();
273 for (
auto i = 0; i < s; ++i)
277 strand[i]->validFwd(checkSB, checkAfView, stepIn);
281 <<
"Strand re-execute check failed. Step: " << i;
295 get<TInAmt>(strandIn),
296 get<TOutAmt>(strandOut),
301 catch (FlowException
const&)
303 return Result{strand, std::move(ofrsToRm)};
308template <
class TInAmt,
class TOutAmt>
311 TInAmt in = beast::zero;
312 TOutAmt out = beast::zero;
314 boost::container::flat_set<uint256> removableOffers;
315 TER ter = temUNKNOWN;
317 FlowResult() =
default;
322 PaymentSandbox&& sandbox_,
323 boost::container::flat_set<uint256> ofrsToRm)
326 , sandbox(
std::move(sandbox_))
327 , removableOffers(
std::move(ofrsToRm))
332 FlowResult(TER ter_, boost::container::flat_set<uint256> ofrsToRm)
333 : removableOffers(
std::
move(ofrsToRm)), ter(ter_)
341 boost::container::flat_set<uint256> ofrsToRm)
342 :
in(in_),
out(out_), removableOffers(
std::
move(ofrsToRm)), ter(ter_)
350qualityUpperBound(ReadView
const& v, Strand
const& strand)
355 for (
auto const& step : strand)
357 if (
std::tie(stepQ, dir) = step->qualityUpperBound(v, dir); stepQ)
375template <
typename TOutAmt>
379 Strand
const& strand,
380 TOutAmt
const& remainingOut,
381 Quality
const& limitQuality)
386 for (
auto const& step : strand)
388 if (
std::tie(stepQualityFunc, dir) = step->getQualityFunc(v, dir);
392 qf = stepQualityFunc;
394 qf->combine(*stepQualityFunc);
401 if (!qf || qf->isConst())
404 auto const out = [&]() {
405 if (
auto const out = qf->outFromAvgQ(limitQuality); !
out)
408 return XRPAmount{*
out};
410 return IOUAmount{*
out};
413 remainingOut.issue(),
out->mantissa(),
out->exponent()};
443 for (
auto& strand : strands)
444 next_.push_back(&strand);
455 if (v.rules().enabled(featureFlowSortStrands) && !next_.
empty())
459 if (next_.
size() > 1)
461 for (Strand
const* strand : next_)
468 if (
auto const qual = qualityUpperBound(v, *strand))
470 if (limitQuality && *qual < *limitQuality)
488 [](
auto const& lhs,
auto const& rhs) {
490 return std::get<Quality>(lhs) > std::get<Quality>(rhs);
494 for (
auto const& sq : strandQuals)
506 if (i >= cur_.
size())
509 UNREACHABLE(
"ripple::ActiveStrands::get : input out of range");
517 push(Strand
const* s)
524 pushRemainingCurToNext(
size_t i)
526 if (i >= cur_.
size())
540 if (i >= next_.
size())
567template <
class TInAmt,
class TOutAmt>
568FlowResult<TInAmt, TOutAmt>
572 TOutAmt
const& outReq,
588 Strand
const& strand;
595 Strand
const& strand_,
596 Quality
const& quality_)
615 TInAmt
const sendMaxInit =
616 sendMaxST ? toAmount<TInAmt>(*sendMaxST) : TInAmt{beast::zero};
618 (sendMaxST && sendMaxInit >= beast::zero)
625 TOutAmt remainingOut(outReq);
630 ActiveStrands activeStrands(strands);
635 boost::container::flat_multiset<TInAmt> savedIns;
636 savedIns.reserve(maxTries);
637 boost::container::flat_multiset<TOutAmt> savedOuts;
638 savedOuts.reserve(maxTries);
640 auto sum = [](
auto const& col) {
643 return TResult{beast::zero};
649 boost::container::flat_set<uint256> ofrsToRmOnFail;
651 while (remainingOut > beast::zero &&
652 (!remainingIn || *remainingIn > beast::zero))
655 if (curTry >= maxTries)
660 activeStrands.activateNext(sb, limitQuality);
665 auto const limitRemainingOut = [&]() {
666 if (activeStrands.size() == 1 && limitQuality)
667 if (
auto const strand = activeStrands.get(0))
668 return limitOut(sb, *strand, remainingOut, *limitQuality);
671 auto const adjustedRemOut = limitRemainingOut != remainingOut;
673 boost::container::flat_set<uint256> ofrsToRm;
676 flowDebugInfo->newLiquidityPass();
682 for (
size_t strandIndex = 0, sie = activeStrands.size();
686 Strand
const* strand = activeStrands.get(strandIndex);
696 if (offerCrossing && limitQuality)
698 auto const strandQ = qualityUpperBound(sb, *strand);
699 if (!strandQ || *strandQ < *limitQuality)
702 auto f = flow<TInAmt, TOutAmt>(
703 sb, *strand, remainingIn, limitRemainingOut, j);
708 offersConsidered += f.ofrsUsed;
710 if (!f.success || f.out == beast::zero)
714 flowDebugInfo->pushLiquiditySrc(
718 f.out <= remainingOut && f.sandbox &&
719 (!remainingIn || f.in <= *remainingIn),
720 "ripple::flow : remaining constraints");
722 Quality
const q(f.out, f.in);
725 <<
"New flow iter (iter, in, out): " << curTry - 1 <<
" "
731 if (limitQuality && q < *limitQuality &&
736 <<
"Path rejected by limitQuality"
737 <<
" limit: " << *limitQuality <<
" path q: " << q;
743 XRPL_ASSERT(!best,
"ripple::flow : best is unset");
745 activeStrands.push(strand);
746 best.
emplace(f.in, f.out, std::move(*f.sandbox), *strand, q);
747 activeStrands.pushRemainingCurToNext(strandIndex + 1);
751 activeStrands.push(strand);
753 if (!best || best->quality < q ||
754 (best->quality == q && best->out < f.out))
765 markInactiveOnUse = activeStrands.size() - 1;
769 markInactiveOnUse.
reset();
772 best.
emplace(f.in, f.out, std::move(*f.sandbox), *strand, q);
776 bool const shouldBreak = [&] {
778 return !best || offersConsidered >= maxOffersToConsider;
784 if (markInactiveOnUse)
786 activeStrands.removeIndex(*markInactiveOnUse);
787 markInactiveOnUse.
reset();
789 savedIns.insert(best->in);
790 savedOuts.insert(best->out);
791 remainingOut = outReq -
sum(savedOuts);
793 remainingIn = *sendMax -
sum(savedIns);
796 flowDebugInfo->pushPass(
799 activeStrands.size());
803 <<
" remainingOut: " <<
to_string(remainingOut);
810 JLOG(j.
trace()) <<
"All strands dry.";
815 if (!ofrsToRm.empty())
818 for (
auto const& o : ofrsToRm)
829 auto const actualOut =
sum(savedOuts);
830 auto const actualIn =
sum(savedIns);
846 bool const fillOrKillEnabled = baseView.
rules().
enabled(fixFillOrKill);
848 if (actualOut != outReq)
850 if (actualOut > outReq)
870 if (!offerCrossing ||
876 std::move(ofrsToRmOnFail)};
878 else if (actualOut == beast::zero)
894 XRPL_ASSERT(remainingIn,
"ripple::flow : nonzero remainingIn");
895 if (remainingIn && *remainingIn != beast::zero)
900 std::move(ofrsToRmOnFail)};
903 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(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.
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.
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.
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
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.