20 #include <ripple/app/paths/impl/Steps.h>
21 #include <ripple/basics/IOUAmount.h>
22 #include <ripple/basics/XRPAmount.h>
23 #include <ripple/basics/contract.h>
24 #include <ripple/json/json_writer.h>
25 #include <ripple/ledger/ReadView.h>
26 #include <ripple/protocol/Feature.h>
38 double const ratTol = 0.001;
54 double const diff = std::abs(a - b);
55 auto const r = diff /
std::max(std::abs(a), std::abs(b));
62 return expected == actual;
78 Issue const& curIssue)
102 <<
"Found offer/account payment step. Aborting payment strand.";
119 JLOG(j.info()) <<
"Found xrp/xrp offer payment step";
125 if (
isXRP(outCurrency))
139 Issue const& deliver,
143 bool ownerPaysTransferFee,
152 if ((sendMaxIssue && sendMaxIssue->account ==
noAccount()) ||
157 for (
auto const& pe : path)
159 auto const t = pe.getNodeType();
168 if (hasAccount && (hasIssuer || hasCurrency))
171 if (hasIssuer &&
isXRP(pe.getIssuerID()))
174 if (hasAccount &&
isXRP(pe.getAccountID()))
177 if (hasCurrency && hasIssuer &&
178 isXRP(pe.getCurrency()) !=
isXRP(pe.getIssuerID()))
181 if (hasIssuer && (pe.getIssuerID() ==
noAccount()))
184 if (hasAccount && (pe.getAccountID() ==
noAccount()))
188 Issue curIssue = [&] {
189 auto const& currency =
190 sendMaxIssue ? sendMaxIssue->currency : deliver.
currency;
193 return Issue{currency, src};
203 normPath.
reserve(4 + path.size());
208 if (sendMaxIssue && sendMaxIssue->account != src &&
209 (path.empty() || !path[0].isAccount() ||
210 path[0].getAccountID() != sendMaxIssue->account))
213 sendMaxIssue->account, std::nullopt, std::nullopt);
216 for (
auto const& i : path)
247 if (normPath.
size() < 2)
255 result.reserve(2 * normPath.
size());
264 boost::container::flat_set<Issue> seenBookOuts;
265 seenDirectIssues[0].reserve(normPath.
size());
266 seenDirectIssues[1].reserve(normPath.
size());
267 seenBookOuts.reserve(normPath.
size());
268 auto ctx = [&](
bool isLast =
false) {
277 ownerPaysTransferFee,
296 auto cur = &normPath[i];
297 auto const next = &normPath[i + 1];
299 if (cur->isAccount())
300 curIssue.
account = cur->getAccountID();
301 else if (cur->hasIssuer())
302 curIssue.
account = cur->getIssuerID();
304 if (cur->hasCurrency())
306 curIssue.
currency = cur->getCurrency();
311 if (cur->isAccount() && next->isAccount())
314 curIssue.
account != cur->getAccountID() &&
315 curIssue.
account != next->getAccountID())
317 JLOG(j.
trace()) <<
"Inserting implied account";
324 return {msr.first, Strand{}};
325 result.push_back(std::move(msr.second));
334 else if (cur->isAccount() && next->isOffer())
336 if (curIssue.
account != cur->getAccountID())
338 JLOG(j.
trace()) <<
"Inserting implied account before offer";
345 return {msr.first, Strand{}};
346 result.push_back(std::move(msr.second));
355 else if (cur->isOffer() && next->isAccount())
357 if (curIssue.
account != next->getAccountID() &&
358 !
isXRP(next->getAccountID()))
362 if (i != normPath.
size() - 2)
370 return {msr.first, Strand{}};
371 result.push_back(std::move(msr.second));
376 JLOG(j.
trace()) <<
"Inserting implied account after offer";
380 next->getAccountID(),
383 return {msr.first, Strand{}};
384 result.push_back(std::move(msr.second));
390 if (!next->isOffer() && next->hasCurrency() &&
391 next->getCurrency() != curIssue.
currency)
399 ctx( i == normPath.
size() - 2), cur, next, curIssue);
401 result.emplace_back(std::move(s.second));
404 JLOG(j.
debug()) <<
"toStep failed: " << s.first;
405 return {s.first, Strand{}};
409 auto checkStrand = [&]() ->
bool {
411 if (
auto r = s.directStepAccts())
413 if (
auto const r = s.bookStepBook())
415 Throw<FlowException>(
416 tefEXCEPTION,
"Step should be either a direct or book step");
423 sendMaxIssue ? sendMaxIssue->currency : deliver.
currency;
426 return Issue{currency, src};
429 for (
auto const& s : result)
431 auto const accts = stepAccts(*s);
432 if (accts.first != curAcc)
435 if (
auto const b = s->bookStepBook())
443 curIss.account = accts.second;
446 curAcc = accts.second;
450 if (curIss.currency != deliver.
currency)
452 if (curIss.account != deliver.
account && curIss.account != dst)
459 JLOG(j.
warn()) <<
"Flow check strand failed";
472 Issue const& deliver,
477 bool ownerPaysTransferFee,
485 auto insert = [&](Strand s) {
486 bool const hasStrand =
503 ownerPaysTransferFee,
507 auto const ter = sp.first;
508 auto& strand = sp.second;
512 JLOG(j.
trace()) <<
"failed to add default path";
518 else if (strand.empty())
520 JLOG(j.
trace()) <<
"toStrand failed";
521 Throw<FlowException>(
526 insert(std::move(strand));
529 else if (paths.
empty())
531 JLOG(j.
debug()) <<
"Flow: Invalid transaction: No paths and direct "
532 "ripple not allowed.";
537 for (
auto const& p : paths)
547 ownerPaysTransferFee,
552 auto& strand = sp.second;
557 JLOG(j.
trace()) <<
"failed to add path: ter: " << ter
562 else if (strand.empty())
564 JLOG(j.
trace()) <<
"toStrand failed";
565 Throw<FlowException>(
570 insert(std::move(strand));
575 return {lastFailTer, std::move(result)};
587 Issue const& strandDeliver_,
590 bool ownerPaysTransferFee_,
593 std::array<boost::container::flat_set<Issue>, 2>& seenDirectIssues_,
594 boost::container::flat_set<Issue>& seenBookOuts_,
598 , strandSrc(strandSrc_)
599 , strandDst(strandDst_)
600 , strandDeliver(strandDeliver_)
601 , limitQuality(limitQuality_)
602 , isFirst(strand_.empty())
604 , ownerPaysTransferFee(ownerPaysTransferFee_)
605 , offerCrossing(offerCrossing_)
607 , strandSize(strand_.size())
608 , prevStep(!strand_.empty() ? strand_.back().
get() : nullptr)
609 , seenDirectIssues(seenDirectIssues_)
610 , seenBookOuts(seenBookOuts_)
611 , ammContext(ammContext_)
616 template <
class InAmt,
class OutAmt>
627 return (strand.size() == 2);
Context needed to build Strand Steps and for error checking.
int exponent() const noexcept
A currency issued by an account.
bool isConsistent(Book const &book)
Stream trace() const
Severity stream access functions.
std::pair< TER, std::unique_ptr< Step > > make_BookStepXI(StrandContext const &ctx, Issue const &out)
std::pair< TER, std::unique_ptr< Step > > make_BookStepII(StrandContext const &ctx, Issue const &in, Issue const &out)
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.
Currency const & getCurrency() const
static bool isDefaultPath(STPath const &path)
bool checkNear(IOUAmount const &expected, IOUAmount const &actual)
template bool isDirectXrpToXrp< IOUAmount, IOUAmount >(Strand const &strand)
std::pair< TER, std::unique_ptr< Step > > make_DirectStepI(StrandContext const &ctx, AccountID const &src, AccountID const &dst, Currency const &c)
Floating point representation of amounts with high dynamic range.
Integers of any length that is a multiple of 32-bits.
static std::pair< TER, std::unique_ptr< Step > > toStep(StrandContext const &ctx, STPathElement const *e1, STPathElement const *e2, Issue const &curIssue)
std::vector< STPath >::size_type size() const
Maintains AMM info per overall payment engine execution and individual iteration.
std::pair< TER, std::unique_ptr< Step > > make_XRPEndpointStep(StrandContext const &ctx, AccountID const &acc)
A step in a payment path.
AccountID const & xrpAccount()
Compute AccountID from public key.
bool isXRP(AccountID const &c)
const bool isLast
true if Step is last in Strand
A generic endpoint for log messages.
bool isDirectXrpToXrp(Strand const &strand)
AccountID const & getIssuerID() const
bool isDirectXrpToXrp< XRPAmount, XRPAmount >(Strand const &strand)
T emplace_back(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
std::int64_t mantissa() const noexcept
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.
constexpr Number abs(Number 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, beast::Journal j)
Create a Strand for each specified path (including the default path, if indicated)
const bool isFirst
true if Step is first in Strand
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
template bool isDirectXrpToXrp< IOUAmount, XRPAmount >(Strand const &strand)
std::pair< TER, std::unique_ptr< Step > > make_BookStepIX(StrandContext const &ctx, Issue const &in)
template bool isDirectXrpToXrp< XRPAmount, IOUAmount >(Strand const &strand)
AccountID const & noAccount()
A placeholder for empty accounts.
Currency const & xrpCurrency()
XRP currency.
bool isTemMalformed(TER x)
T & get(EitherAmount &amt)
AccountID const & getAccountID() const