mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-24 07:30:30 +00:00
418 lines
12 KiB
C++
418 lines
12 KiB
C++
#include <xrpl/basics/Log.h>
|
|
#include <xrpl/basics/base_uint.h>
|
|
#include <xrpl/beast/utility/Journal.h>
|
|
#include <xrpl/beast/utility/Zero.h>
|
|
#include <xrpl/beast/utility/instrumentation.h>
|
|
#include <xrpl/ledger/PaymentSandbox.h>
|
|
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
|
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
|
#include <xrpl/protocol/AccountID.h>
|
|
#include <xrpl/protocol/IOUAmount.h>
|
|
#include <xrpl/protocol/Indexes.h>
|
|
#include <xrpl/protocol/Issue.h>
|
|
#include <xrpl/protocol/MPTIssue.h>
|
|
#include <xrpl/protocol/Quality.h>
|
|
#include <xrpl/protocol/STAmount.h>
|
|
#include <xrpl/protocol/TER.h>
|
|
#include <xrpl/protocol/UintTypes.h>
|
|
#include <xrpl/protocol/XRPAmount.h>
|
|
#include <xrpl/tx/paths/detail/AmountSpec.h>
|
|
#include <xrpl/tx/paths/detail/EitherAmount.h>
|
|
#include <xrpl/tx/paths/detail/StepChecks.h>
|
|
#include <xrpl/tx/paths/detail/Steps.h>
|
|
|
|
#include <boost/container/flat_set.hpp>
|
|
|
|
#include <cstdint>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
namespace xrpl {
|
|
|
|
template <class TDerived>
|
|
class XRPEndpointStep : public StepImp<XRPAmount, XRPAmount, XRPEndpointStep<TDerived>>
|
|
{
|
|
private:
|
|
AccountID acc_;
|
|
bool const isLast_;
|
|
beast::Journal const j_;
|
|
|
|
// Since this step will always be an endpoint in a strand
|
|
// (either the first or last step) the same cache is used
|
|
// for cachedIn and cachedOut and only one will ever be used
|
|
std::optional<XRPAmount> cache_;
|
|
|
|
[[nodiscard]] std::optional<EitherAmount>
|
|
cached() const
|
|
{
|
|
if (!cache_)
|
|
return std::nullopt;
|
|
return EitherAmount(*cache_);
|
|
}
|
|
|
|
XRPEndpointStep(StrandContext const& ctx, AccountID const& acc)
|
|
: acc_(acc), isLast_(ctx.isLast), j_(ctx.j)
|
|
{
|
|
}
|
|
|
|
public:
|
|
[[nodiscard]] AccountID const&
|
|
acc() const
|
|
{
|
|
return acc_;
|
|
}
|
|
|
|
[[nodiscard]] std::optional<std::pair<AccountID, AccountID>>
|
|
directStepAccts() const override
|
|
{
|
|
if (isLast_)
|
|
return std::make_pair(xrpAccount(), acc_);
|
|
return std::make_pair(acc_, xrpAccount());
|
|
}
|
|
|
|
[[nodiscard]] std::optional<EitherAmount>
|
|
cachedIn() const override
|
|
{
|
|
return cached();
|
|
}
|
|
|
|
[[nodiscard]] std::optional<EitherAmount>
|
|
cachedOut() const override
|
|
{
|
|
return cached();
|
|
}
|
|
|
|
[[nodiscard]] DebtDirection
|
|
debtDirection(ReadView const& sb, StrandDirection dir) const override
|
|
{
|
|
return DebtDirection::Issues;
|
|
}
|
|
|
|
[[nodiscard]] std::pair<std::optional<Quality>, DebtDirection>
|
|
qualityUpperBound(ReadView const& v, DebtDirection prevStepDir) const override;
|
|
|
|
std::pair<XRPAmount, XRPAmount>
|
|
revImp(
|
|
PaymentSandbox& sb,
|
|
ApplyView& afView,
|
|
boost::container::flat_set<uint256>& ofrsToRm,
|
|
XRPAmount const& out);
|
|
|
|
std::pair<XRPAmount, XRPAmount>
|
|
fwdImp(
|
|
PaymentSandbox& sb,
|
|
ApplyView& afView,
|
|
boost::container::flat_set<uint256>& ofrsToRm,
|
|
XRPAmount const& in);
|
|
|
|
std::pair<bool, EitherAmount>
|
|
validFwd(PaymentSandbox& sb, ApplyView& afView, EitherAmount const& in) override;
|
|
|
|
// Check for errors and violations of frozen constraints.
|
|
[[nodiscard]] TER
|
|
check(StrandContext const& ctx) const;
|
|
|
|
protected:
|
|
XRPAmount
|
|
xrpLiquidImpl(ReadView& sb, std::int32_t reserveReduction) const
|
|
{
|
|
return xrpl::xrpLiquid(sb, acc_, reserveReduction, j_);
|
|
}
|
|
|
|
std::string
|
|
logStringImpl(char const* name) const
|
|
{
|
|
std::ostringstream ostr;
|
|
ostr << name << ": "
|
|
<< "\nAcc: " << acc_;
|
|
return ostr.str();
|
|
}
|
|
|
|
private:
|
|
template <class P>
|
|
friend bool
|
|
operator==(XRPEndpointStep<P> const& lhs, XRPEndpointStep<P> const& rhs);
|
|
|
|
friend bool
|
|
operator!=(XRPEndpointStep const& lhs, XRPEndpointStep const& rhs)
|
|
{
|
|
return !(lhs == rhs);
|
|
}
|
|
|
|
[[nodiscard]] bool
|
|
equal(Step const& rhs) const override
|
|
{
|
|
if (auto ds = dynamic_cast<XRPEndpointStep const*>(&rhs))
|
|
{
|
|
return *this == *ds;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
friend TDerived;
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// Flow is used in two different circumstances for transferring funds:
|
|
// o Payments, and
|
|
// o Offer crossing.
|
|
// The rules for handling funds in these two cases are almost, but not
|
|
// quite, the same.
|
|
|
|
// Payment XRPEndpointStep class (not offer crossing).
|
|
class XRPEndpointPaymentStep : public XRPEndpointStep<XRPEndpointPaymentStep>
|
|
{
|
|
public:
|
|
XRPEndpointPaymentStep(StrandContext const& ctx, AccountID const& acc)
|
|
: XRPEndpointStep<XRPEndpointPaymentStep>(ctx, acc)
|
|
{
|
|
}
|
|
|
|
XRPAmount
|
|
xrpLiquid(ReadView& sb) const
|
|
{
|
|
return xrpLiquidImpl(sb, 0);
|
|
;
|
|
}
|
|
|
|
[[nodiscard]] std::string
|
|
logString() const override
|
|
{
|
|
return logStringImpl("XRPEndpointPaymentStep");
|
|
}
|
|
};
|
|
|
|
// Offer crossing XRPEndpointStep class (not a payment).
|
|
class XRPEndpointOfferCrossingStep : public XRPEndpointStep<XRPEndpointOfferCrossingStep>
|
|
{
|
|
private:
|
|
// For historical reasons, offer crossing is allowed to dig further
|
|
// into the XRP reserve than an ordinary payment. (I believe it's
|
|
// because the trust line was created after the XRP was removed.)
|
|
// Return how much the reserve should be reduced.
|
|
//
|
|
// Note that reduced reserve only happens if the trust line or MPT does not
|
|
// currently exist.
|
|
static std::int32_t
|
|
computeReserveReduction(StrandContext const& ctx, AccountID const& acc)
|
|
{
|
|
if (ctx.isFirst)
|
|
{
|
|
return ctx.strandDeliver.visit(
|
|
[&](Issue const& issue) {
|
|
if (!ctx.view.exists(keylet::line(acc, issue)))
|
|
return -1;
|
|
return 0;
|
|
},
|
|
[&](MPTIssue const& issue) {
|
|
if (!ctx.view.exists(keylet::mptoken(issue.getMptID(), acc)))
|
|
return -1;
|
|
return 0;
|
|
});
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
public:
|
|
XRPEndpointOfferCrossingStep(StrandContext const& ctx, AccountID const& acc)
|
|
: XRPEndpointStep<XRPEndpointOfferCrossingStep>(ctx, acc)
|
|
, reserveReduction_(computeReserveReduction(ctx, acc))
|
|
{
|
|
}
|
|
|
|
XRPAmount
|
|
xrpLiquid(ReadView& sb) const
|
|
{
|
|
return xrpLiquidImpl(sb, reserveReduction_);
|
|
}
|
|
|
|
[[nodiscard]] std::string
|
|
logString() const override
|
|
{
|
|
return logStringImpl("XRPEndpointOfferCrossingStep");
|
|
}
|
|
|
|
private:
|
|
std::int32_t const reserveReduction_;
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
template <class TDerived>
|
|
inline bool
|
|
operator==(XRPEndpointStep<TDerived> const& lhs, XRPEndpointStep<TDerived> const& rhs)
|
|
{
|
|
return lhs.acc_ == rhs.acc_ && lhs.isLast_ == rhs.isLast_;
|
|
}
|
|
|
|
template <class TDerived>
|
|
std::pair<std::optional<Quality>, DebtDirection>
|
|
XRPEndpointStep<TDerived>::qualityUpperBound(ReadView const& v, DebtDirection prevStepDir) const
|
|
{
|
|
return {Quality{STAmount::kURateOne}, this->debtDirection(v, StrandDirection::Forward)};
|
|
}
|
|
|
|
template <class TDerived>
|
|
std::pair<XRPAmount, XRPAmount>
|
|
XRPEndpointStep<TDerived>::revImp(
|
|
PaymentSandbox& sb,
|
|
ApplyView& afView,
|
|
boost::container::flat_set<uint256>& ofrsToRm,
|
|
XRPAmount const& out)
|
|
{
|
|
auto const balance = static_cast<TDerived const*>(this)->xrpLiquid(sb);
|
|
|
|
auto const result = isLast_ ? out : std::min(balance, out);
|
|
|
|
auto& sender = isLast_ ? xrpAccount() : acc_;
|
|
auto& receiver = isLast_ ? acc_ : xrpAccount();
|
|
auto ter = accountSend(sb, sender, receiver, toSTAmount(result), j_);
|
|
if (!isTesSuccess(ter))
|
|
return {XRPAmount{beast::kZero}, XRPAmount{beast::kZero}};
|
|
|
|
cache_.emplace(result);
|
|
return {result, result};
|
|
}
|
|
|
|
template <class TDerived>
|
|
std::pair<XRPAmount, XRPAmount>
|
|
XRPEndpointStep<TDerived>::fwdImp(
|
|
PaymentSandbox& sb,
|
|
ApplyView& afView,
|
|
boost::container::flat_set<uint256>& ofrsToRm,
|
|
XRPAmount const& in)
|
|
{
|
|
XRPL_ASSERT(cache_, "xrpl::XRPEndpointStep::fwdImp : cache is set");
|
|
auto const balance = static_cast<TDerived const*>(this)->xrpLiquid(sb);
|
|
|
|
auto const result = isLast_ ? in : std::min(balance, in);
|
|
|
|
auto& sender = isLast_ ? xrpAccount() : acc_;
|
|
auto& receiver = isLast_ ? acc_ : xrpAccount();
|
|
auto ter = accountSend(sb, sender, receiver, toSTAmount(result), j_);
|
|
if (!isTesSuccess(ter))
|
|
return {XRPAmount{beast::kZero}, XRPAmount{beast::kZero}};
|
|
|
|
cache_.emplace(result);
|
|
return {result, result};
|
|
}
|
|
|
|
template <class TDerived>
|
|
std::pair<bool, EitherAmount>
|
|
XRPEndpointStep<TDerived>::validFwd(PaymentSandbox& sb, ApplyView& afView, EitherAmount const& in)
|
|
{
|
|
if (!cache_)
|
|
{
|
|
JLOG(j_.error()) << "Expected valid cache in validFwd";
|
|
return {false, EitherAmount(XRPAmount(beast::kZero))};
|
|
}
|
|
|
|
XRPL_ASSERT(in.holds<XRPAmount>(), "xrpl::XRPEndpointStep::validFwd : input is XRP");
|
|
|
|
auto const& xrpIn = in.get<XRPAmount>();
|
|
auto const balance = static_cast<TDerived const*>(this)->xrpLiquid(sb);
|
|
|
|
if (!isLast_ && balance < xrpIn)
|
|
{
|
|
JLOG(j_.warn()) << "XRPEndpointStep: Strand re-execute check failed."
|
|
<< " Insufficient balance: " << to_string(balance)
|
|
<< " Requested: " << to_string(xrpIn);
|
|
return {false, EitherAmount(balance)};
|
|
}
|
|
|
|
if (xrpIn != *cache_)
|
|
{
|
|
JLOG(j_.warn()) << "XRPEndpointStep: Strand re-execute check failed."
|
|
<< " ExpectedIn: " << to_string(*cache_)
|
|
<< " CachedIn: " << to_string(xrpIn);
|
|
}
|
|
return {true, in};
|
|
}
|
|
|
|
template <class TDerived>
|
|
TER
|
|
XRPEndpointStep<TDerived>::check(StrandContext const& ctx) const
|
|
{
|
|
if (!acc_)
|
|
{
|
|
JLOG(j_.debug()) << "XRPEndpointStep: specified bad account.";
|
|
return temBAD_PATH;
|
|
}
|
|
|
|
auto sleAcc = ctx.view.read(keylet::account(acc_));
|
|
if (!sleAcc)
|
|
{
|
|
JLOG(j_.warn()) << "XRPEndpointStep: can't send or receive XRP from "
|
|
"non-existent account: "
|
|
<< acc_;
|
|
return terNO_ACCOUNT;
|
|
}
|
|
|
|
if (!ctx.isFirst && !ctx.isLast)
|
|
{
|
|
return temBAD_PATH;
|
|
}
|
|
|
|
auto& src = isLast_ ? xrpAccount() : acc_;
|
|
auto& dst = isLast_ ? acc_ : xrpAccount();
|
|
auto ter = checkFreeze(ctx.view, src, dst, xrpCurrency());
|
|
if (!isTesSuccess(ter))
|
|
return ter;
|
|
|
|
auto const issuesIndex = isLast_ ? 0 : 1;
|
|
if (!ctx.seenDirectAssets[issuesIndex].insert(xrpIssue()).second)
|
|
{
|
|
JLOG(j_.debug()) << "XRPEndpointStep: loop detected: Index: " << ctx.strandSize << ' '
|
|
<< *this;
|
|
return temBAD_PATH_LOOP;
|
|
}
|
|
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
namespace test {
|
|
// Needed for testing
|
|
bool
|
|
xrpEndpointStepEqual(Step const& step, AccountID const& acc)
|
|
{
|
|
if (auto xs = dynamic_cast<XRPEndpointStep<XRPEndpointPaymentStep> const*>(&step))
|
|
{
|
|
return xs->acc() == acc;
|
|
}
|
|
return false;
|
|
}
|
|
} // namespace test
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
std::pair<TER, std::unique_ptr<Step>>
|
|
makeXrpEndpointStep(StrandContext const& ctx, AccountID const& acc)
|
|
{
|
|
TER ter = tefINTERNAL;
|
|
std::unique_ptr<Step> r;
|
|
if (ctx.offerCrossing != OfferCrossing::No)
|
|
{
|
|
auto offerCrossingStep = std::make_unique<XRPEndpointOfferCrossingStep>(ctx, acc);
|
|
ter = offerCrossingStep->check(ctx);
|
|
r = std::move(offerCrossingStep);
|
|
}
|
|
else // payment
|
|
{
|
|
auto paymentStep = std::make_unique<XRPEndpointPaymentStep>(ctx, acc);
|
|
ter = paymentStep->check(ctx);
|
|
r = std::move(paymentStep);
|
|
}
|
|
if (!isTesSuccess(ter))
|
|
return {ter, nullptr};
|
|
|
|
return {tesSUCCESS, std::move(r)};
|
|
}
|
|
|
|
} // namespace xrpl
|