1#include <xrpld/app/paths/detail/Steps.h>
3#include <xrpl/basics/contract.h>
4#include <xrpl/json/json_writer.h>
5#include <xrpl/ledger/ReadView.h>
6#include <xrpl/protocol/IOUAmount.h>
7#include <xrpl/protocol/XRPAmount.h>
17 double const ratTol = 0.001;
33 double const diff = std::abs(a - b);
34 auto const r = diff /
std::max(std::abs(a), std::abs(b));
41 return expected == actual;
57 Issue const& curIssue)
82 <<
"Found offer/account payment step. Aborting payment strand.";
83 UNREACHABLE(
"ripple::toStep : offer/account payment payment strand");
91 "ripple::toStep : currency or issuer");
101 JLOG(j.info()) <<
"Found xrp/xrp offer payment step";
105 XRPL_ASSERT(e2->
isOffer(),
"ripple::toStep : is offer");
107 if (
isXRP(outCurrency))
121 Issue const& deliver,
125 bool ownerPaysTransferFee,
135 if ((sendMaxIssue && sendMaxIssue->account ==
noAccount()) ||
140 for (
auto const& pe : path)
142 auto const t = pe.getNodeType();
151 if (hasAccount && (hasIssuer || hasCurrency))
154 if (hasIssuer &&
isXRP(pe.getIssuerID()))
157 if (hasAccount &&
isXRP(pe.getAccountID()))
160 if (hasCurrency && hasIssuer &&
161 isXRP(pe.getCurrency()) !=
isXRP(pe.getIssuerID()))
164 if (hasIssuer && (pe.getIssuerID() ==
noAccount()))
167 if (hasAccount && (pe.getAccountID() ==
noAccount()))
171 Issue curIssue = [&] {
172 auto const& currency =
173 sendMaxIssue ? sendMaxIssue->currency : deliver.
currency;
176 return Issue{currency, src};
186 normPath.
reserve(4 + path.size());
191 if (sendMaxIssue && sendMaxIssue->account != src &&
192 (path.empty() || !path[0].isAccount() ||
193 path[0].getAccountID() != sendMaxIssue->account))
199 for (
auto const& i : path)
216 if (!((normPath.
back().isAccount() &&
217 normPath.
back().getAccountID() == deliver.
account) ||
223 if (!normPath.
back().isAccount() ||
224 normPath.
back().getAccountID() != dst)
230 if (normPath.
size() < 2)
233 auto const strandSrc = normPath.
front().getAccountID();
234 auto const strandDst = normPath.
back().getAccountID();
238 result.reserve(2 * normPath.
size());
247 boost::container::flat_set<Issue> seenBookOuts;
248 seenDirectIssues[0].reserve(normPath.
size());
249 seenDirectIssues[1].reserve(normPath.
size());
250 seenBookOuts.reserve(normPath.
size());
251 auto ctx = [&](
bool isLast =
false) {
260 ownerPaysTransferFee,
280 auto cur = &normPath[i];
281 auto const next = &normPath[i + 1];
283 if (cur->isAccount())
284 curIssue.
account = cur->getAccountID();
285 else if (cur->hasIssuer())
286 curIssue.
account = cur->getIssuerID();
288 if (cur->hasCurrency())
290 curIssue.
currency = cur->getCurrency();
295 if (cur->isAccount() && next->isAccount())
298 curIssue.
account != cur->getAccountID() &&
299 curIssue.
account != next->getAccountID())
301 JLOG(j.
trace()) <<
"Inserting implied account";
308 return {msr.first, Strand{}};
309 result.push_back(std::move(msr.second));
318 else if (cur->isAccount() && next->isOffer())
320 if (curIssue.
account != cur->getAccountID())
322 JLOG(j.
trace()) <<
"Inserting implied account before offer";
329 return {msr.first, Strand{}};
330 result.push_back(std::move(msr.second));
339 else if (cur->isOffer() && next->isAccount())
341 if (curIssue.
account != next->getAccountID() &&
342 !
isXRP(next->getAccountID()))
346 if (i != normPath.
size() - 2)
354 return {msr.first, Strand{}};
355 result.push_back(std::move(msr.second));
360 JLOG(j.
trace()) <<
"Inserting implied account after offer";
364 next->getAccountID(),
367 return {msr.first, Strand{}};
368 result.push_back(std::move(msr.second));
374 if (!next->isOffer() && next->hasCurrency() &&
375 next->getCurrency() != curIssue.
currency)
379 UNREACHABLE(
"ripple::toStrand : offer currency mismatch");
385 ctx( i == normPath.
size() - 2), cur, next, curIssue);
387 result.emplace_back(std::move(s.second));
390 JLOG(j.
debug()) <<
"toStep failed: " << s.first;
391 return {s.first, Strand{}};
395 auto checkStrand = [&]() ->
bool {
397 if (
auto r = s.directStepAccts())
399 if (
auto const r = s.bookStepBook())
401 Throw<FlowException>(
402 tefEXCEPTION,
"Step should be either a direct or book step");
409 sendMaxIssue ? sendMaxIssue->currency : deliver.
currency;
412 return Issue{currency, src};
415 for (
auto const& s : result)
417 auto const accts = stepAccts(*s);
418 if (accts.first != curAcc)
421 if (
auto const b = s->bookStepBook())
429 curIss.account = accts.second;
432 curAcc = accts.second;
436 if (curIss.currency != deliver.
currency)
438 if (curIss.account != deliver.
account && curIss.account != dst)
446 JLOG(j.
warn()) <<
"Flow check strand failed";
447 UNREACHABLE(
"ripple::toStrand : invalid strand");
460 Issue const& deliver,
465 bool ownerPaysTransferFee,
474 auto insert = [&](Strand s) {
475 bool const hasStrand =
492 ownerPaysTransferFee,
497 auto const ter = sp.first;
498 auto& strand = sp.second;
502 JLOG(j.
trace()) <<
"failed to add default path";
508 else if (strand.empty())
510 JLOG(j.
trace()) <<
"toStrand failed";
511 Throw<FlowException>(
516 insert(std::move(strand));
519 else if (paths.
empty())
521 JLOG(j.
debug()) <<
"Flow: Invalid transaction: No paths and direct "
522 "ripple not allowed.";
527 for (
auto const& p : paths)
537 ownerPaysTransferFee,
543 auto& strand = sp.second;
548 JLOG(j.
trace()) <<
"failed to add path: ter: " << ter
553 else if (strand.empty())
555 JLOG(j.
trace()) <<
"toStrand failed";
556 Throw<FlowException>(
561 insert(std::move(strand));
566 return {lastFailTer, std::move(result)};
578 Issue const& strandDeliver_,
581 bool ownerPaysTransferFee_,
584 std::array<boost::container::flat_set<Issue>, 2>& seenDirectIssues_,
585 boost::container::flat_set<Issue>& seenBookOuts_,
590 , strandSrc(strandSrc_)
591 , strandDst(strandDst_)
592 , strandDeliver(strandDeliver_)
593 , limitQuality(limitQuality_)
594 , isFirst(strand_.empty())
596 , ownerPaysTransferFee(ownerPaysTransferFee_)
597 , offerCrossing(offerCrossing_)
599 , strandSize(strand_.size())
600 , prevStep(!strand_.empty() ? strand_.back().
get() : nullptr)
601 , seenDirectIssues(seenDirectIssues_)
602 , seenBookOuts(seenBookOuts_)
603 , ammContext(ammContext_)
604 , domainID(domainID_)
609template <
class InAmt,
class OutAmt>
620 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)
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)
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.
bool isTemMalformed(TER x) noexcept
static bool isXRPAccount(STPathElement const &pe)
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, std::optional< uint256 > const &domainID, beast::Journal j)
Create a Strand for each specified path (including the default path, if indicated)
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, std::optional< uint256 > const &domainID, beast::Journal j)
Create a Strand for the specified path.
bool isDirectXrpToXrp< XRPAmount, XRPAmount >(Strand const &strand)
constexpr Number abs(Number x) noexcept
Context needed to build Strand Steps and for error checking.
bool const isFirst
true if Step is first in Strand
bool const isLast
true if Step is last in Strand
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_, std::optional< uint256 > const &domainID, beast::Journal j_)
StrandContext constructor.