20#include <xrpld/app/paths/detail/Steps.h>
21#include <xrpld/ledger/ReadView.h>
23#include <xrpl/basics/contract.h>
24#include <xrpl/json/json_writer.h>
25#include <xrpl/protocol/IOUAmount.h>
26#include <xrpl/protocol/XRPAmount.h>
36 double const ratTol = 0.001;
52 double const diff = std::abs(a - b);
53 auto const r = diff /
std::max(std::abs(a), std::abs(b));
60 return expected == actual;
76 Issue const& curIssue)
100 <<
"Found offer/account payment step. Aborting payment strand.";
101 UNREACHABLE(
"ripple::toStep : offer/account payment payment strand");
108 "ripple::toStep : currency or issuer");
118 JLOG(j.info()) <<
"Found xrp/xrp offer payment step";
122 XRPL_ASSERT(e2->
isOffer(),
"ripple::toStep : is offer");
124 if (
isXRP(outCurrency))
138 Issue const& deliver,
142 bool ownerPaysTransferFee,
151 if ((sendMaxIssue && sendMaxIssue->account ==
noAccount()) ||
156 for (
auto const& pe : path)
158 auto const t = pe.getNodeType();
167 if (hasAccount && (hasIssuer || hasCurrency))
170 if (hasIssuer &&
isXRP(pe.getIssuerID()))
173 if (hasAccount &&
isXRP(pe.getAccountID()))
176 if (hasCurrency && hasIssuer &&
177 isXRP(pe.getCurrency()) !=
isXRP(pe.getIssuerID()))
180 if (hasIssuer && (pe.getIssuerID() ==
noAccount()))
183 if (hasAccount && (pe.getAccountID() ==
noAccount()))
187 Issue curIssue = [&] {
188 auto const& currency =
189 sendMaxIssue ? sendMaxIssue->currency : deliver.
currency;
192 return Issue{currency, src};
202 normPath.
reserve(4 + path.size());
207 if (sendMaxIssue && sendMaxIssue->account != src &&
208 (path.empty() || !path[0].isAccount() ||
209 path[0].getAccountID() != sendMaxIssue->account))
212 sendMaxIssue->account, std::nullopt, std::nullopt);
215 for (
auto const& i : path)
232 if (!((normPath.
back().isAccount() &&
233 normPath.
back().getAccountID() == deliver.
account) ||
239 if (!normPath.
back().isAccount() ||
240 normPath.
back().getAccountID() != dst)
246 if (normPath.
size() < 2)
249 auto const strandSrc = normPath.
front().getAccountID();
250 auto const strandDst = normPath.
back().getAccountID();
254 result.reserve(2 * normPath.
size());
263 boost::container::flat_set<Issue> seenBookOuts;
264 seenDirectIssues[0].reserve(normPath.
size());
265 seenDirectIssues[1].reserve(normPath.
size());
266 seenBookOuts.reserve(normPath.
size());
267 auto ctx = [&](
bool isLast =
false) {
276 ownerPaysTransferFee,
295 auto cur = &normPath[i];
296 auto const next = &normPath[i + 1];
298 if (cur->isAccount())
299 curIssue.
account = cur->getAccountID();
300 else if (cur->hasIssuer())
301 curIssue.
account = cur->getIssuerID();
303 if (cur->hasCurrency())
305 curIssue.
currency = cur->getCurrency();
310 if (cur->isAccount() && next->isAccount())
313 curIssue.
account != cur->getAccountID() &&
314 curIssue.
account != next->getAccountID())
316 JLOG(j.
trace()) <<
"Inserting implied account";
323 return {msr.first, Strand{}};
324 result.push_back(std::move(msr.second));
333 else if (cur->isAccount() && next->isOffer())
335 if (curIssue.
account != cur->getAccountID())
337 JLOG(j.
trace()) <<
"Inserting implied account before offer";
344 return {msr.first, Strand{}};
345 result.push_back(std::move(msr.second));
354 else if (cur->isOffer() && next->isAccount())
356 if (curIssue.
account != next->getAccountID() &&
357 !
isXRP(next->getAccountID()))
361 if (i != normPath.
size() - 2)
369 return {msr.first, Strand{}};
370 result.push_back(std::move(msr.second));
375 JLOG(j.
trace()) <<
"Inserting implied account after offer";
379 next->getAccountID(),
382 return {msr.first, Strand{}};
383 result.push_back(std::move(msr.second));
389 if (!next->isOffer() && next->hasCurrency() &&
390 next->getCurrency() != curIssue.
currency)
393 UNREACHABLE(
"ripple::toStrand : offer currency mismatch");
398 ctx( i == normPath.
size() - 2), cur, next, curIssue);
400 result.emplace_back(std::move(s.second));
403 JLOG(j.
debug()) <<
"toStep failed: " << s.first;
404 return {s.first, Strand{}};
408 auto checkStrand = [&]() ->
bool {
410 if (
auto r = s.directStepAccts())
412 if (
auto const r = s.bookStepBook())
414 Throw<FlowException>(
415 tefEXCEPTION,
"Step should be either a direct or book step");
422 sendMaxIssue ? sendMaxIssue->currency : deliver.
currency;
425 return Issue{currency, src};
428 for (
auto const& s : result)
430 auto const accts = stepAccts(*s);
431 if (accts.first != curAcc)
434 if (
auto const b = s->bookStepBook())
442 curIss.account = accts.second;
445 curAcc = accts.second;
449 if (curIss.currency != deliver.
currency)
451 if (curIss.account != deliver.
account && curIss.account != dst)
458 JLOG(j.
warn()) <<
"Flow check strand failed";
459 UNREACHABLE(
"ripple::toStrand : invalid strand");
471 Issue const& deliver,
476 bool ownerPaysTransferFee,
484 auto insert = [&](Strand s) {
485 bool const hasStrand =
502 ownerPaysTransferFee,
506 auto const ter = sp.first;
507 auto& strand = sp.second;
511 JLOG(j.
trace()) <<
"failed to add default path";
517 else if (strand.empty())
519 JLOG(j.
trace()) <<
"toStrand failed";
520 Throw<FlowException>(
525 insert(std::move(strand));
528 else if (paths.
empty())
530 JLOG(j.
debug()) <<
"Flow: Invalid transaction: No paths and direct "
531 "ripple not allowed.";
536 for (
auto const& p : paths)
546 ownerPaysTransferFee,
551 auto& strand = sp.second;
556 JLOG(j.
trace()) <<
"failed to add path: ter: " << ter
561 else if (strand.empty())
563 JLOG(j.
trace()) <<
"toStrand failed";
564 Throw<FlowException>(
569 insert(std::move(strand));
574 return {lastFailTer, std::move(result)};
586 Issue const& strandDeliver_,
589 bool ownerPaysTransferFee_,
592 std::array<boost::container::flat_set<Issue>, 2>& seenDirectIssues_,
593 boost::container::flat_set<Issue>& seenBookOuts_,
597 , strandSrc(strandSrc_)
598 , strandDst(strandDst_)
599 , strandDeliver(strandDeliver_)
600 , limitQuality(limitQuality_)
601 , isFirst(strand_.empty())
603 , ownerPaysTransferFee(ownerPaysTransferFee_)
604 , offerCrossing(offerCrossing_)
606 , strandSize(strand_.size())
607 , prevStep(!strand_.empty() ? strand_.back().
get() : nullptr)
608 , seenDirectIssues(seenDirectIssues_)
609 , seenBookOuts(seenBookOuts_)
610 , ammContext(ammContext_)
615template <
class InAmt,
class OutAmt>
626 return (strand.size() == 2);
A generic endpoint for log messages.
Stream trace() const
Severity stream access functions.
Maintains AMM info per overall payment engine execution and individual iteration.
Floating point representation of amounts with high dynamic range.
int exponent() const noexcept
std::int64_t mantissa() const noexcept
A currency issued by an account.
Currency const & getCurrency() const
AccountID const & getAccountID() const
AccountID const & getIssuerID() const
std::vector< STPath >::size_type size() const
A step in a payment path.
T emplace_back(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
AccountID const & noAccount()
A placeholder for empty accounts.
static std::pair< TER, std::unique_ptr< Step > > toStep(StrandContext const &ctx, STPathElement const *e1, STPathElement const *e2, Issue const &curIssue)
bool isConsistent(Book const &book)
bool isXRP(AccountID const &c)
AccountID const & xrpAccount()
Compute AccountID from public key.
std::pair< TER, std::unique_ptr< Step > > make_XRPEndpointStep(StrandContext const &ctx, AccountID const &acc)
std::pair< TER, Strand > toStrand(ReadView const &view, AccountID const &src, AccountID const &dst, Issue const &deliver, std::optional< Quality > const &limitQuality, std::optional< Issue > const &sendMaxIssue, STPath const &path, bool ownerPaysTransferFee, OfferCrossing offerCrossing, AMMContext &ammContext, beast::Journal j)
Create a Strand for the specified path.
std::pair< TER, std::vector< Strand > > toStrands(ReadView const &view, AccountID const &src, AccountID const &dst, Issue const &deliver, std::optional< Quality > const &limitQuality, std::optional< Issue > const &sendMax, STPathSet const &paths, bool addDefaultPath, bool ownerPaysTransferFee, OfferCrossing offerCrossing, AMMContext &ammContext, beast::Journal j)
Create a Strand for each specified path (including the default path, if indicated)
template bool isDirectXrpToXrp< IOUAmount, IOUAmount >(Strand const &strand)
static bool isDefaultPath(STPath const &path)
template bool isDirectXrpToXrp< IOUAmount, XRPAmount >(Strand const &strand)
std::pair< TER, std::unique_ptr< Step > > make_BookStepXI(StrandContext const &ctx, Issue const &out)
bool isTemMalformed(TER x)
std::pair< TER, std::unique_ptr< Step > > make_BookStepIX(StrandContext const &ctx, Issue const &in)
template bool isDirectXrpToXrp< XRPAmount, IOUAmount >(Strand const &strand)
Currency const & xrpCurrency()
XRP currency.
bool checkNear(IOUAmount const &expected, IOUAmount const &actual)
std::pair< TER, std::unique_ptr< Step > > make_BookStepII(StrandContext const &ctx, Issue const &in, Issue const &out)
std::pair< TER, std::unique_ptr< Step > > make_DirectStepI(StrandContext const &ctx, AccountID const &src, AccountID const &dst, Currency const &c)
bool isDirectXrpToXrp(Strand const &strand)
T get(Section const §ion, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
static bool isXRPAccount(STPathElement const &pe)
bool isDirectXrpToXrp< XRPAmount, XRPAmount >(Strand const &strand)
constexpr Number abs(Number x) noexcept
Context needed to build Strand Steps and for error checking.
StrandContext(ReadView const &view_, std::vector< std::unique_ptr< Step > > const &strand_, AccountID const &strandSrc_, AccountID const &strandDst_, Issue const &strandDeliver_, std::optional< Quality > const &limitQuality_, bool isLast_, bool ownerPaysTransferFee_, OfferCrossing offerCrossing_, bool isDefaultPath_, std::array< boost::container::flat_set< Issue >, 2 > &seenDirectIssues_, boost::container::flat_set< Issue > &seenBookOuts_, AMMContext &ammContext_, beast::Journal j_)
StrandContext constructor.
bool const isFirst
true if Step is first in Strand
bool const isLast
true if Step is last in Strand