20#include <xrpld/app/paths/detail/Steps.h>
21#include <xrpld/ledger/ReadView.h>
22#include <xrpl/basics/contract.h>
23#include <xrpl/json/json_writer.h>
24#include <xrpl/protocol/IOUAmount.h>
25#include <xrpl/protocol/XRPAmount.h>
35 double const ratTol = 0.001;
51 double const diff = std::abs(a - b);
52 auto const r = diff /
std::max(std::abs(a), std::abs(b));
59 return expected == actual;
75 Issue const& curIssue)
99 <<
"Found offer/account payment step. Aborting payment strand.";
100 UNREACHABLE(
"ripple::toStep : offer/account payment payment strand");
107 "ripple::toStep : currency or issuer");
117 JLOG(j.info()) <<
"Found xrp/xrp offer payment step";
121 XRPL_ASSERT(e2->
isOffer(),
"ripple::toStep : is offer");
123 if (
isXRP(outCurrency))
137 Issue const& deliver,
141 bool ownerPaysTransferFee,
150 if ((sendMaxIssue && sendMaxIssue->account ==
noAccount()) ||
155 for (
auto const& pe : path)
157 auto const t = pe.getNodeType();
166 if (hasAccount && (hasIssuer || hasCurrency))
169 if (hasIssuer &&
isXRP(pe.getIssuerID()))
172 if (hasAccount &&
isXRP(pe.getAccountID()))
175 if (hasCurrency && hasIssuer &&
176 isXRP(pe.getCurrency()) !=
isXRP(pe.getIssuerID()))
179 if (hasIssuer && (pe.getIssuerID() ==
noAccount()))
182 if (hasAccount && (pe.getAccountID() ==
noAccount()))
186 Issue curIssue = [&] {
187 auto const& currency =
188 sendMaxIssue ? sendMaxIssue->currency : deliver.
currency;
191 return Issue{currency, src};
201 normPath.
reserve(4 + path.size());
206 if (sendMaxIssue && sendMaxIssue->account != src &&
207 (path.empty() || !path[0].isAccount() ||
208 path[0].getAccountID() != sendMaxIssue->account))
211 sendMaxIssue->account, std::nullopt, std::nullopt);
214 for (
auto const& i : path)
231 if (!((normPath.
back().isAccount() &&
232 normPath.
back().getAccountID() == deliver.
account) ||
238 if (!normPath.
back().isAccount() ||
239 normPath.
back().getAccountID() != dst)
245 if (normPath.
size() < 2)
248 auto const strandSrc = normPath.
front().getAccountID();
249 auto const strandDst = normPath.
back().getAccountID();
253 result.reserve(2 * normPath.
size());
262 boost::container::flat_set<Issue> seenBookOuts;
263 seenDirectIssues[0].reserve(normPath.
size());
264 seenDirectIssues[1].reserve(normPath.
size());
265 seenBookOuts.reserve(normPath.
size());
266 auto ctx = [&](
bool isLast =
false) {
275 ownerPaysTransferFee,
294 auto cur = &normPath[i];
295 auto const next = &normPath[i + 1];
297 if (cur->isAccount())
298 curIssue.
account = cur->getAccountID();
299 else if (cur->hasIssuer())
300 curIssue.
account = cur->getIssuerID();
302 if (cur->hasCurrency())
304 curIssue.
currency = cur->getCurrency();
309 if (cur->isAccount() && next->isAccount())
312 curIssue.
account != cur->getAccountID() &&
313 curIssue.
account != next->getAccountID())
315 JLOG(j.
trace()) <<
"Inserting implied account";
322 return {msr.first, Strand{}};
323 result.push_back(std::move(msr.second));
332 else if (cur->isAccount() && next->isOffer())
334 if (curIssue.
account != cur->getAccountID())
336 JLOG(j.
trace()) <<
"Inserting implied account before offer";
343 return {msr.first, Strand{}};
344 result.push_back(std::move(msr.second));
353 else if (cur->isOffer() && next->isAccount())
355 if (curIssue.
account != next->getAccountID() &&
356 !
isXRP(next->getAccountID()))
360 if (i != normPath.
size() - 2)
368 return {msr.first, Strand{}};
369 result.push_back(std::move(msr.second));
374 JLOG(j.
trace()) <<
"Inserting implied account after offer";
378 next->getAccountID(),
381 return {msr.first, Strand{}};
382 result.push_back(std::move(msr.second));
388 if (!next->isOffer() && next->hasCurrency() &&
389 next->getCurrency() != curIssue.
currency)
392 UNREACHABLE(
"ripple::toStrand : offer currency mismatch");
397 ctx( i == normPath.
size() - 2), cur, next, curIssue);
399 result.emplace_back(std::move(s.second));
402 JLOG(j.
debug()) <<
"toStep failed: " << s.first;
403 return {s.first, Strand{}};
407 auto checkStrand = [&]() ->
bool {
409 if (
auto r = s.directStepAccts())
411 if (
auto const r = s.bookStepBook())
413 Throw<FlowException>(
414 tefEXCEPTION,
"Step should be either a direct or book step");
421 sendMaxIssue ? sendMaxIssue->currency : deliver.
currency;
424 return Issue{currency, src};
427 for (
auto const& s : result)
429 auto const accts = stepAccts(*s);
430 if (accts.first != curAcc)
433 if (
auto const b = s->bookStepBook())
441 curIss.account = accts.second;
444 curAcc = accts.second;
448 if (curIss.currency != deliver.
currency)
450 if (curIss.account != deliver.
account && curIss.account != dst)
457 JLOG(j.
warn()) <<
"Flow check strand failed";
458 UNREACHABLE(
"ripple::toStrand : invalid strand");
470 Issue const& deliver,
475 bool ownerPaysTransferFee,
483 auto insert = [&](Strand s) {
484 bool const hasStrand =
501 ownerPaysTransferFee,
505 auto const ter = sp.first;
506 auto& strand = sp.second;
510 JLOG(j.
trace()) <<
"failed to add default path";
516 else if (strand.empty())
518 JLOG(j.
trace()) <<
"toStrand failed";
519 Throw<FlowException>(
524 insert(std::move(strand));
527 else if (paths.
empty())
529 JLOG(j.
debug()) <<
"Flow: Invalid transaction: No paths and direct "
530 "ripple not allowed.";
535 for (
auto const& p : paths)
545 ownerPaysTransferFee,
550 auto& strand = sp.second;
555 JLOG(j.
trace()) <<
"failed to add path: ter: " << ter
560 else if (strand.empty())
562 JLOG(j.
trace()) <<
"toStrand failed";
563 Throw<FlowException>(
568 insert(std::move(strand));
573 return {lastFailTer, std::move(result)};
585 Issue const& strandDeliver_,
588 bool ownerPaysTransferFee_,
591 std::array<boost::container::flat_set<Issue>, 2>& seenDirectIssues_,
592 boost::container::flat_set<Issue>& seenBookOuts_,
596 , strandSrc(strandSrc_)
597 , strandDst(strandDst_)
598 , strandDeliver(strandDeliver_)
599 , limitQuality(limitQuality_)
600 , isFirst(strand_.empty())
602 , ownerPaysTransferFee(ownerPaysTransferFee_)
603 , offerCrossing(offerCrossing_)
605 , strandSize(strand_.size())
606 , prevStep(!strand_.empty() ? strand_.back().
get() : nullptr)
607 , seenDirectIssues(seenDirectIssues_)
608 , seenBookOuts(seenBookOuts_)
609 , ammContext(ammContext_)
614template <
class InAmt,
class OutAmt>
625 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