20#include <xrpld/app/paths/detail/Steps.h>
21#include <xrpld/ledger/ReadView.h>
22#include <xrpl/basics/contract.h>
23#include <xrpl/beast/utility/instrumentation.h>
24#include <xrpl/json/json_writer.h>
25#include <xrpl/protocol/Feature.h>
26#include <xrpl/protocol/IOUAmount.h>
27#include <xrpl/protocol/XRPAmount.h>
39 double const ratTol = 0.001;
55 double const diff = std::abs(a - b);
56 auto const r = diff /
std::max(std::abs(a), std::abs(b));
63 return expected == actual;
79 Issue const& curIssue)
103 <<
"Found offer/account payment step. Aborting payment strand.";
104 UNREACHABLE(
"ripple::toStep : offer/account payment payment strand");
111 "ripple::toStep : currency or issuer");
121 JLOG(j.info()) <<
"Found xrp/xrp offer payment step";
125 XRPL_ASSERT(e2->
isOffer(),
"ripple::toStep : is offer");
127 if (
isXRP(outCurrency))
141 Issue const& deliver,
145 bool ownerPaysTransferFee,
154 if ((sendMaxIssue && sendMaxIssue->account ==
noAccount()) ||
159 for (
auto const& pe : path)
161 auto const t = pe.getNodeType();
170 if (hasAccount && (hasIssuer || hasCurrency))
173 if (hasIssuer &&
isXRP(pe.getIssuerID()))
176 if (hasAccount &&
isXRP(pe.getAccountID()))
179 if (hasCurrency && hasIssuer &&
180 isXRP(pe.getCurrency()) !=
isXRP(pe.getIssuerID()))
183 if (hasIssuer && (pe.getIssuerID() ==
noAccount()))
186 if (hasAccount && (pe.getAccountID() ==
noAccount()))
190 Issue curIssue = [&] {
191 auto const& currency =
192 sendMaxIssue ? sendMaxIssue->currency : deliver.
currency;
195 return Issue{currency, src};
205 normPath.
reserve(4 + path.size());
210 if (sendMaxIssue && sendMaxIssue->account != src &&
211 (path.empty() || !path[0].isAccount() ||
212 path[0].getAccountID() != sendMaxIssue->account))
215 sendMaxIssue->account, std::nullopt, std::nullopt);
218 for (
auto const& i : path)
235 if (!((normPath.
back().isAccount() &&
236 normPath.
back().getAccountID() == deliver.
account) ||
242 if (!normPath.
back().isAccount() ||
243 normPath.
back().getAccountID() != dst)
249 if (normPath.
size() < 2)
252 auto const strandSrc = normPath.
front().getAccountID();
253 auto const strandDst = normPath.
back().getAccountID();
257 result.reserve(2 * normPath.
size());
266 boost::container::flat_set<Issue> seenBookOuts;
267 seenDirectIssues[0].reserve(normPath.
size());
268 seenDirectIssues[1].reserve(normPath.
size());
269 seenBookOuts.reserve(normPath.
size());
270 auto ctx = [&](
bool isLast =
false) {
279 ownerPaysTransferFee,
298 auto cur = &normPath[i];
299 auto const next = &normPath[i + 1];
301 if (cur->isAccount())
302 curIssue.
account = cur->getAccountID();
303 else if (cur->hasIssuer())
304 curIssue.
account = cur->getIssuerID();
306 if (cur->hasCurrency())
308 curIssue.
currency = cur->getCurrency();
313 if (cur->isAccount() && next->isAccount())
316 curIssue.
account != cur->getAccountID() &&
317 curIssue.
account != next->getAccountID())
319 JLOG(j.
trace()) <<
"Inserting implied account";
326 return {msr.first, Strand{}};
327 result.push_back(std::move(msr.second));
336 else if (cur->isAccount() && next->isOffer())
338 if (curIssue.
account != cur->getAccountID())
340 JLOG(j.
trace()) <<
"Inserting implied account before offer";
347 return {msr.first, Strand{}};
348 result.push_back(std::move(msr.second));
357 else if (cur->isOffer() && next->isAccount())
359 if (curIssue.
account != next->getAccountID() &&
360 !
isXRP(next->getAccountID()))
364 if (i != normPath.
size() - 2)
372 return {msr.first, Strand{}};
373 result.push_back(std::move(msr.second));
378 JLOG(j.
trace()) <<
"Inserting implied account after offer";
382 next->getAccountID(),
385 return {msr.first, Strand{}};
386 result.push_back(std::move(msr.second));
392 if (!next->isOffer() && next->hasCurrency() &&
393 next->getCurrency() != curIssue.
currency)
396 UNREACHABLE(
"ripple::toStrand : offer currency mismatch");
401 ctx( i == normPath.
size() - 2), cur, next, curIssue);
403 result.emplace_back(std::move(s.second));
406 JLOG(j.
debug()) <<
"toStep failed: " << s.first;
407 return {s.first, Strand{}};
411 auto checkStrand = [&]() ->
bool {
413 if (
auto r = s.directStepAccts())
415 if (
auto const r = s.bookStepBook())
417 Throw<FlowException>(
418 tefEXCEPTION,
"Step should be either a direct or book step");
425 sendMaxIssue ? sendMaxIssue->currency : deliver.
currency;
428 return Issue{currency, src};
431 for (
auto const& s : result)
433 auto const accts = stepAccts(*s);
434 if (accts.first != curAcc)
437 if (
auto const b = s->bookStepBook())
445 curIss.account = accts.second;
448 curAcc = accts.second;
452 if (curIss.currency != deliver.
currency)
454 if (curIss.account != deliver.
account && curIss.account != dst)
461 JLOG(j.
warn()) <<
"Flow check strand failed";
462 UNREACHABLE(
"ripple::toStrand : invalid strand");
474 Issue const& deliver,
479 bool ownerPaysTransferFee,
487 auto insert = [&](Strand s) {
488 bool const hasStrand =
505 ownerPaysTransferFee,
509 auto const ter = sp.first;
510 auto& strand = sp.second;
514 JLOG(j.
trace()) <<
"failed to add default path";
520 else if (strand.empty())
522 JLOG(j.
trace()) <<
"toStrand failed";
523 Throw<FlowException>(
528 insert(std::move(strand));
531 else if (paths.
empty())
533 JLOG(j.
debug()) <<
"Flow: Invalid transaction: No paths and direct "
534 "ripple not allowed.";
539 for (
auto const& p : paths)
549 ownerPaysTransferFee,
554 auto& strand = sp.second;
559 JLOG(j.
trace()) <<
"failed to add path: ter: " << ter
564 else if (strand.empty())
566 JLOG(j.
trace()) <<
"toStrand failed";
567 Throw<FlowException>(
572 insert(std::move(strand));
577 return {lastFailTer, std::move(result)};
589 Issue const& strandDeliver_,
592 bool ownerPaysTransferFee_,
595 std::array<boost::container::flat_set<Issue>, 2>& seenDirectIssues_,
596 boost::container::flat_set<Issue>& seenBookOuts_,
600 , strandSrc(strandSrc_)
601 , strandDst(strandDst_)
602 , strandDeliver(strandDeliver_)
603 , limitQuality(limitQuality_)
604 , isFirst(strand_.empty())
606 , ownerPaysTransferFee(ownerPaysTransferFee_)
607 , offerCrossing(offerCrossing_)
609 , strandSize(strand_.size())
610 , prevStep(!strand_.empty() ? strand_.back().
get() : nullptr)
611 , seenDirectIssues(seenDirectIssues_)
612 , seenBookOuts(seenBookOuts_)
613 , ammContext(ammContext_)
618template <
class InAmt,
class OutAmt>
629 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