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;
29 double const diff = std::abs(a - b);
30 auto const r = diff /
std::max(std::abs(a), std::abs(b));
37 return expected == actual;
70 JLOG(j.error()) <<
"Found offer/account payment step. Aborting payment strand.";
71 UNREACHABLE(
"xrpl::toStep : offer/account payment payment strand");
78 "xrpl::toStep : currency or issuer");
84 JLOG(j.info()) <<
"Found xrp/xrp offer payment step";
88 XRPL_ASSERT(e2->
isOffer(),
"xrpl::toStep : is offer");
90 if (
isXRP(outCurrency))
104 Issue const& deliver,
108 bool ownerPaysTransferFee,
121 for (
auto const& pe : path)
123 auto const t = pe.getNodeType();
132 if (hasAccount && (hasIssuer || hasCurrency))
135 if (hasIssuer &&
isXRP(pe.getIssuerID()))
138 if (hasAccount &&
isXRP(pe.getAccountID()))
141 if (hasCurrency && hasIssuer &&
isXRP(pe.getCurrency()) !=
isXRP(pe.getIssuerID()))
144 if (hasIssuer && (pe.getIssuerID() ==
noAccount()))
147 if (hasAccount && (pe.getAccountID() ==
noAccount()))
151 Issue curIssue = [&] {
152 auto const& currency = sendMaxIssue ? sendMaxIssue->currency : deliver.
currency;
155 return Issue{currency, src};
167 if (sendMaxIssue && sendMaxIssue->account != src &&
168 (path.
empty() || !path[0].isAccount() || path[0].getAccountID() != sendMaxIssue->account))
173 for (
auto const& i : path)
187 if (!((normPath.
back().isAccount() && normPath.
back().getAccountID() == deliver.
account) ||
193 if (!normPath.
back().isAccount() || normPath.
back().getAccountID() != dst)
199 if (normPath.
size() < 2)
202 auto const strandSrc = normPath.
front().getAccountID();
203 auto const strandDst = normPath.
back().getAccountID();
207 result.reserve(2 * normPath.
size());
216 boost::container::flat_set<Issue> seenBookOuts;
217 seenDirectIssues[0].reserve(normPath.
size());
218 seenDirectIssues[1].reserve(normPath.
size());
219 seenBookOuts.reserve(normPath.
size());
220 auto ctx = [&](
bool isLast =
false) {
229 ownerPaysTransferFee,
249 auto cur = &normPath[i];
250 auto const next = &normPath[i + 1];
252 if (cur->isAccount())
253 curIssue.
account = cur->getAccountID();
254 else if (cur->hasIssuer())
255 curIssue.
account = cur->getIssuerID();
257 if (cur->hasCurrency())
259 curIssue.
currency = cur->getCurrency();
264 if (cur->isAccount() && next->isAccount())
267 curIssue.
account != next->getAccountID())
269 JLOG(j.
trace()) <<
"Inserting implied account";
272 return {msr.first, Strand{}};
273 result.push_back(std::move(msr.second));
278 else if (cur->isAccount() && next->isOffer())
280 if (curIssue.
account != cur->getAccountID())
282 JLOG(j.
trace()) <<
"Inserting implied account before offer";
285 return {msr.first, Strand{}};
286 result.push_back(std::move(msr.second));
291 else if (cur->isOffer() && next->isAccount())
293 if (curIssue.
account != next->getAccountID() && !
isXRP(next->getAccountID()))
297 if (i != normPath.
size() - 2)
304 return {msr.first, Strand{}};
305 result.push_back(std::move(msr.second));
310 JLOG(j.
trace()) <<
"Inserting implied account after offer";
313 return {msr.first, Strand{}};
314 result.push_back(std::move(msr.second));
320 if (!next->isOffer() && next->hasCurrency() && next->getCurrency() != curIssue.
currency)
324 UNREACHABLE(
"xrpl::toStrand : offer currency mismatch");
329 auto s =
toStep(ctx( i == normPath.
size() - 2), cur, next, curIssue);
331 result.emplace_back(std::move(s.second));
334 JLOG(j.
debug()) <<
"toStep failed: " << s.first;
335 return {s.first, Strand{}};
339 auto checkStrand = [&]() ->
bool {
341 if (
auto r = s.directStepAccts())
343 if (
auto const r = s.bookStepBook())
345 Throw<FlowException>(
tefEXCEPTION,
"Step should be either a direct or book step");
351 auto& currency = sendMaxIssue ? sendMaxIssue->currency : deliver.
currency;
354 return Issue{currency, src};
357 for (
auto const& s : result)
359 auto const accts = stepAccts(*s);
360 if (accts.first != curAcc)
363 if (
auto const b = s->bookStepBook())
371 curIss.account = accts.second;
374 curAcc = accts.second;
378 if (curIss.currency != deliver.
currency)
380 if (curIss.account != deliver.
account && curIss.account != dst)
388 JLOG(j.
warn()) <<
"Flow check strand failed";
389 UNREACHABLE(
"xrpl::toStrand : invalid strand");
402 Issue const& deliver,
407 bool ownerPaysTransferFee,
416 auto insert = [&](Strand s) {
433 ownerPaysTransferFee,
438 auto const ter = sp.first;
439 auto& strand = sp.second;
443 JLOG(j.
trace()) <<
"failed to add default path";
449 else if (strand.empty())
451 JLOG(j.
trace()) <<
"toStrand failed";
452 Throw<FlowException>(
tefEXCEPTION,
"toStrand returned tes & empty strand");
456 insert(std::move(strand));
459 else if (paths.
empty())
461 JLOG(j.
debug()) <<
"Flow: Invalid transaction: No paths and direct "
462 "ripple not allowed.";
467 for (
auto const& p : paths)
477 ownerPaysTransferFee,
483 auto& strand = sp.second;
492 else if (strand.empty())
494 JLOG(j.
trace()) <<
"toStrand failed";
495 Throw<FlowException>(
tefEXCEPTION,
"toStrand returned tes & empty strand");
499 insert(std::move(strand));
504 return {lastFailTer, std::move(result)};
516 Issue const& strandDeliver_,
519 bool ownerPaysTransferFee_,
522 std::array<boost::container::flat_set<Issue>, 2>& seenDirectIssues_,
523 boost::container::flat_set<Issue>& seenBookOuts_,
528 , strandSrc(strandSrc_)
529 , strandDst(strandDst_)
530 , strandDeliver(strandDeliver_)
531 , limitQuality(limitQuality_)
532 , isFirst(strand_.empty())
534 , ownerPaysTransferFee(ownerPaysTransferFee_)
535 , offerCrossing(offerCrossing_)
537 , strandSize(strand_.size())
538 , prevStep(!strand_.empty() ? strand_.back().
get() : nullptr)
539 , seenDirectIssues(seenDirectIssues_)
540 , seenBookOuts(seenBookOuts_)
541 , ammContext(ammContext_)
542 , domainID(domainID_)
547template <
class InAmt,
class OutAmt>
558 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.
mantissa_type mantissa() const noexcept
exponent_type exponent() const noexcept
A currency issued by an account.
AccountID const & getAccountID() const
Currency const & getCurrency() const
AccountID const & getIssuerID() const
std::vector< STPath >::size_type size() const
std::vector< STPathElement >::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.
bool isConsistent(Book const &book)
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
bool isDirectXrpToXrp(Strand const &strand)
bool isXRP(AccountID const &c)
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)
T get(Section const §ion, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
bool isDirectXrpToXrp< XRPAmount, XRPAmount >(Strand const &strand)
std::pair< TER, std::unique_ptr< Step > > make_XRPEndpointStep(StrandContext const &ctx, AccountID const &acc)
std::pair< TER, std::unique_ptr< Step > > make_BookStepXI(StrandContext const &ctx, Issue const &out)
template bool isDirectXrpToXrp< IOUAmount, IOUAmount >(Strand const &strand)
Currency const & xrpCurrency()
XRP currency.
static bool isDefaultPath(STPath const &path)
static std::pair< TER, std::unique_ptr< Step > > toStep(StrandContext const &ctx, STPathElement const *e1, STPathElement const *e2, Issue const &curIssue)
std::pair< TER, std::unique_ptr< Step > > make_DirectStepI(StrandContext const &ctx, AccountID const &src, AccountID const &dst, Currency const &c)
std::pair< TER, std::unique_ptr< Step > > make_BookStepIX(StrandContext const &ctx, Issue const &in)
bool checkNear(IOUAmount const &expected, IOUAmount const &actual)
constexpr Number abs(Number x) noexcept
AccountID const & noAccount()
A placeholder for empty accounts.
template bool isDirectXrpToXrp< XRPAmount, IOUAmount >(Strand const &strand)
std::pair< TER, std::unique_ptr< Step > > make_BookStepII(StrandContext const &ctx, Issue const &in, Issue const &out)
AccountID const & xrpAccount()
Compute AccountID from public key.
static bool isXRPAccount(STPathElement const &pe)
template bool isDirectXrpToXrp< IOUAmount, XRPAmount >(Strand const &strand)
bool isTemMalformed(TER x) noexcept
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.
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_, std::optional< uint256 > const &domainID, 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