Remove old payment code

This commit is contained in:
seelabs
2020-01-07 09:28:13 -08:00
committed by Manoj doshi
parent 9d3626fec5
commit 81326a6d08
39 changed files with 176 additions and 5366 deletions

View File

@@ -457,25 +457,12 @@ else ()
src/ripple/app/paths/AccountCurrencies.cpp
src/ripple/app/paths/Credit.cpp
src/ripple/app/paths/Flow.cpp
src/ripple/app/paths/Node.cpp
src/ripple/app/paths/PathRequest.cpp
src/ripple/app/paths/PathRequests.cpp
src/ripple/app/paths/PathState.cpp
src/ripple/app/paths/Pathfinder.cpp
src/ripple/app/paths/RippleCalc.cpp
src/ripple/app/paths/RippleLineCache.cpp
src/ripple/app/paths/RippleState.cpp
src/ripple/app/paths/cursor/AdvanceNode.cpp
src/ripple/app/paths/cursor/DeliverNodeForward.cpp
src/ripple/app/paths/cursor/DeliverNodeReverse.cpp
src/ripple/app/paths/cursor/EffectiveRate.cpp
src/ripple/app/paths/cursor/ForwardLiquidity.cpp
src/ripple/app/paths/cursor/ForwardLiquidityForAccount.cpp
src/ripple/app/paths/cursor/Liquidity.cpp
src/ripple/app/paths/cursor/NextIncrement.cpp
src/ripple/app/paths/cursor/ReverseLiquidity.cpp
src/ripple/app/paths/cursor/ReverseLiquidityForAccount.cpp
src/ripple/app/paths/cursor/RippleLiquidity.cpp
src/ripple/app/paths/impl/BookStep.cpp
src/ripple/app/paths/impl/DirectStep.cpp
src/ripple/app/paths/impl/PaySteps.cpp

View File

@@ -1,98 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/app/paths/Node.h>
#include <ripple/app/paths/PathState.h>
#include <ripple/protocol/jss.h>
namespace ripple {
namespace path {
// Compare the non-calculated fields.
bool Node::operator== (const Node& other) const
{
return other.uFlags == uFlags
&& other.account_ == account_
&& other.issue_ == issue_;
}
// This is for debugging not end users. Output names can be changed without
// warning.
Json::Value Node::getJson () const
{
Json::Value jvNode (Json::objectValue);
Json::Value jvFlags (Json::arrayValue);
jvNode[jss::type] = uFlags;
bool const hasCurrency = !isXRP (issue_.currency);
bool const hasAccount = !isXRP (account_);
bool const hasIssuer = !isXRP (issue_.account);
if (isAccount() || hasAccount)
jvFlags.append (!isAccount() == hasAccount ? "account" : "-account");
if (uFlags & STPathElement::typeCurrency || hasCurrency)
{
jvFlags.append ((uFlags & STPathElement::typeCurrency) && hasCurrency
? "currency"
: "-currency");
}
if (uFlags & STPathElement::typeIssuer || hasIssuer)
{
jvFlags.append ((uFlags & STPathElement::typeIssuer) && hasIssuer
? "issuer"
: "-issuer");
}
jvNode["flags"] = jvFlags;
if (!isXRP (account_))
jvNode[jss::account] = to_string (account_);
if (!isXRP (issue_.currency))
jvNode[jss::currency] = to_string (issue_.currency);
if (!isXRP (issue_.account))
jvNode[jss::issuer] = to_string (issue_.account);
if (saRevRedeem)
jvNode["rev_redeem"] = saRevRedeem.getFullText ();
if (saRevIssue)
jvNode["rev_issue"] = saRevIssue.getFullText ();
if (saRevDeliver)
jvNode["rev_deliver"] = saRevDeliver.getFullText ();
if (saFwdRedeem)
jvNode["fwd_redeem"] = saFwdRedeem.getFullText ();
if (saFwdIssue)
jvNode["fwd_issue"] = saFwdIssue.getFullText ();
if (saFwdDeliver)
jvNode["fwd_deliver"] = saFwdDeliver.getFullText ();
return jvNode;
}
} // path
} // ripple

View File

@@ -1,114 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_APP_PATHS_NODE_H_INCLUDED
#define RIPPLE_APP_PATHS_NODE_H_INCLUDED
#include <ripple/app/paths/NodeDirectory.h>
#include <ripple/app/paths/Types.h>
#include <ripple/protocol/Rate.h>
#include <ripple/protocol/UintTypes.h>
#include <boost/optional.hpp>
namespace ripple {
namespace path {
struct Node
{
explicit Node() = default;
using List = std::vector<Node>;
inline bool isAccount() const
{
return (uFlags & STPathElement::typeAccount);
}
Json::Value getJson () const;
bool operator == (Node const&) const;
std::uint16_t uFlags; // --> From path.
AccountID account_; // --> Accounts: Receiving/sending account.
Issue issue_; // --> Accounts: Receive and send, Offers: send.
// --- For offer's next has currency out.
boost::optional<Rate> transferRate_; // Transfer rate for issuer.
// Computed by Reverse.
STAmount saRevRedeem; // <-- Amount to redeem to next.
STAmount saRevIssue; // <-- Amount to issue to next, limited by
// credit and outstanding IOUs. Issue
// isn't used by offers.
STAmount saRevDeliver; // <-- Amount to deliver to next regardless of
// fee.
// Computed by forward.
STAmount saFwdRedeem; // <-- Amount node will redeem to next.
STAmount saFwdIssue; // <-- Amount node will issue to next.
// Issue isn't used by offers.
STAmount saFwdDeliver; // <-- Amount to deliver to next regardless of
// fee.
// For offers:
boost::optional<Rate> rateMax;
// The nodes are partitioned into a buckets called "directories".
//
// Each "directory" contains nodes with exactly the same "quality" (meaning
// the conversion rate between one corrency and the next).
//
// The "directories" are ordered in "increasing" "quality" value, which
// means that the first "directory" has the "best" (i.e. numerically least)
// "quality".
// https://ripple.com/wiki/Ledger_Format#Prioritizing_a_continuous_key_space
NodeDirectory directory;
STAmount saOfrRate; // For correct ratio.
// PaymentNode
bool bEntryAdvance; // Need to advance entry.
unsigned int uEntry;
uint256 offerIndex_;
SLE::pointer sleOffer;
AccountID offerOwnerAccount_;
// Do we need to refresh saOfferFunds, saTakerPays, & saTakerGets?
bool bFundsDirty;
STAmount saOfferFunds;
STAmount saTakerPays;
STAmount saTakerGets;
/** Clear input and output amounts. */
void clear()
{
saRevRedeem.clear ();
saRevIssue.clear ();
saRevDeliver.clear ();
saFwdDeliver.clear ();
}
};
} // path
} // ripple
#endif

View File

@@ -1,861 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/app/paths/Credit.h>
#include <ripple/app/paths/PathState.h>
#include <ripple/basics/Log.h>
#include <ripple/json/to_string.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/jss.h>
#include <boost/lexical_cast.hpp>
namespace ripple {
// OPTIMIZE: When calculating path increment, note if increment consumes all
// liquidity. No need to revisit path in the future if all liquidity is used.
//
class RippleCalc; // for logging
void PathState::clear()
{
saInPass = saInReq.zeroed();
saOutPass = saOutReq.zeroed();
unfundedOffers_.clear ();
umReverse.clear ();
for (auto& node: nodes_)
node.clear();
}
void PathState::reset(STAmount const& in, STAmount const& out)
{
clear();
// Update to current amount processed.
saInAct = in;
saOutAct = out;
if (inReq() > beast::zero && inAct() >= inReq())
{
JLOG (j_.warn())
<< "rippleCalc: DONE:"
<< " inAct()=" << inAct()
<< " inReq()=" << inReq();
}
assert (inReq() < beast::zero || inAct() < inReq());
// Error if done.
if (outAct() >= outReq())
{
JLOG (j_.warn())
<< "rippleCalc: ALREADY DONE:"
<< " saOutAct=" << outAct()
<< " saOutReq=" << outReq();
}
assert(outAct() < outReq());
assert (nodes().size () >= 2);
}
// Return true, iff lhs has less priority than rhs.
bool PathState::lessPriority (PathState const& lhs, PathState const& rhs)
{
// First rank is quality.
if (lhs.uQuality != rhs.uQuality)
return lhs.uQuality > rhs.uQuality; // Bigger is worse.
// Second rank is best quantity.
if (lhs.saOutPass != rhs.saOutPass)
return lhs.saOutPass < rhs.saOutPass; // Smaller is worse.
// Third rank is path index.
return lhs.mIndex > rhs.mIndex; // Bigger is worse.
}
// Make sure last path node delivers to account_: currency from.issue_.account.
//
// If the unadded next node as specified by arguments would not work as is, then
// add the necessary nodes so it would work.
// PRECONDITION: the PathState must be non-empty.
//
// Rules:
// - Currencies must be converted via an offer.
// - A node names its output.
// - A ripple nodes output issuer must be the node's account or the next node's
// account.
// - Offers can only go directly to another offer if the currency and issuer are
// an exact match.
// - Real issuers must be specified for non-XRP.
TER PathState::pushImpliedNodes (
AccountID const& account, // --> Delivering to this account.
Currency const& currency, // --> Delivering this currency.
AccountID const& issuer) // --> Delivering this issuer.
{
TER resultCode = tesSUCCESS;
JLOG (j_.trace()) << "pushImpliedNodes>" <<
" " << account <<
" " << currency <<
" " << issuer;
if (nodes_.back ().issue_.currency != currency)
{
// Currency is different, need to convert via an offer from an order
// book. xrpAccount() does double duty as signaling "this is an order
// book".
// Corresponds to "Implies an offer directory" in the diagram, currently
// at http://goo.gl/Uj3HAB.
auto type = isXRP(currency) ? STPathElement::typeCurrency
: STPathElement::typeCurrency | STPathElement::typeIssuer;
// The offer's output is what is now wanted.
// xrpAccount() is a placeholder for offers.
resultCode = pushNode (type, xrpAccount(), currency, issuer);
}
// For ripple, non-XRP, ensure the issuer is on at least one side of the
// transaction.
if (resultCode == tesSUCCESS
&& !isXRP(currency)
&& nodes_.back ().account_ != issuer
// Previous is not issuing own IOUs.
&& account != issuer)
// Current is not receiving own IOUs.
{
// Need to ripple through issuer's account.
// Case "Implies an another node: (pushImpliedNodes)" in the document.
// Intermediate account is the needed issuer.
resultCode = pushNode (
STPathElement::typeAll, issuer, currency, issuer);
}
JLOG (j_.trace())
<< "pushImpliedNodes< : " << transToken (resultCode);
return resultCode;
}
// Append a node, then create and insert before it any implied nodes. Order
// book nodes may go back to back.
//
// For each non-matching pair of IssuedCurrency, there's an order book.
//
// <-- resultCode: tesSUCCESS, temBAD_PATH, terNO_ACCOUNT, terNO_AUTH,
// terNO_LINE, tecPATH_DRY
TER PathState::pushNode (
const int iType,
AccountID const& account, // If not specified, means an order book.
Currency const& currency, // If not specified, default to previous.
AccountID const& issuer) // If not specified, default to previous.
{
path::Node node;
const bool pathIsEmpty = nodes_.empty ();
// TODO(tom): if pathIsEmpty, we probably don't need to do ANYTHING below.
// Indeed, we might just not even call pushNode in the first place!
auto const& backNodeOuter = pathIsEmpty ? path::Node () : nodes_.back ();
// true, iff node is a ripple account. false, iff node is an offer node.
const bool hasAccount = (iType & STPathElement::typeAccount);
// Is currency specified for the output of the current node?
const bool hasCurrency = (iType & STPathElement::typeCurrency);
// Issuer is specified for the output of the current node.
const bool hasIssuer = (iType & STPathElement::typeIssuer);
TER resultCode = tesSUCCESS;
JLOG (j_.trace())
<< "pushNode> " << iType << ": "
<< (hasAccount ? to_string(account) : std::string("-")) << " "
<< (hasCurrency ? to_string(currency) : std::string("-")) << "/"
<< (hasIssuer ? to_string(issuer) : std::string("-")) << "/";
node.uFlags = iType;
node.issue_.currency = hasCurrency ?
currency : backNodeOuter.issue_.currency;
// TODO(tom): we can probably just return immediately whenever we hit an
// error in these next pages.
if (iType & ~STPathElement::typeAll)
{
// Of course, this could never happen.
JLOG (j_.debug()) << "pushNode: bad bits.";
resultCode = temBAD_PATH;
}
else if (hasIssuer && isXRP (node.issue_))
{
JLOG (j_.debug()) << "pushNode: issuer specified for XRP.";
resultCode = temBAD_PATH;
}
else if (hasIssuer && !issuer)
{
JLOG (j_.debug()) << "pushNode: specified bad issuer.";
resultCode = temBAD_PATH;
}
else if (!hasAccount && !hasCurrency && !hasIssuer)
{
// You can't default everything to the previous node as you would make
// no progress.
JLOG (j_.debug())
<< "pushNode: offer must specify at least currency or issuer.";
resultCode = temBAD_PATH;
}
else if (hasAccount)
{
// Account link
node.account_ = account;
node.issue_.account = hasIssuer ? issuer :
(isXRP (node.issue_) ? xrpAccount() : account);
// Zero value - for accounts.
node.saRevRedeem = STAmount ({node.issue_.currency, account});
node.saRevIssue = node.saRevRedeem;
// For order books only - zero currency with the issuer ID.
node.saRevDeliver = STAmount (node.issue_);
node.saFwdDeliver = node.saRevDeliver;
if (pathIsEmpty)
{
// The first node is always correct as is.
}
else if (!account)
{
JLOG (j_.debug())
<< "pushNode: specified bad account.";
resultCode = temBAD_PATH;
}
else
{
// Add required intermediate nodes to deliver to current account.
JLOG (j_.trace())
<< "pushNode: imply for account.";
resultCode = pushImpliedNodes (
node.account_,
node.issue_.currency,
isXRP(node.issue_.currency) ? xrpAccount() : account);
// Note: backNodeOuter may no longer be the immediately previous node.
}
if (resultCode == tesSUCCESS && !nodes_.empty ())
{
auto const& backNode = nodes_.back ();
if (backNode.isAccount())
{
auto sleRippleState = view().peek(
keylet::line(backNode.account_, node.account_, backNode.issue_.currency));
// A "RippleState" means a balance betweeen two accounts for a
// specific currency.
if (!sleRippleState)
{
JLOG (j_.trace())
<< "pushNode: No credit line between "
<< backNode.account_ << " and " << node.account_
<< " for " << node.issue_.currency << "." ;
JLOG (j_.trace()) << getJson ();
resultCode = terNO_LINE;
}
else
{
JLOG (j_.trace())
<< "pushNode: Credit line found between "
<< backNode.account_ << " and " << node.account_
<< " for " << node.issue_.currency << "." ;
auto sleBck = view().peek (
keylet::account(backNode.account_));
// Is the source account the highest numbered account ID?
bool bHigh = backNode.account_ > node.account_;
if (!sleBck)
{
JLOG (j_.warn())
<< "pushNode: delay: can't receive IOUs from "
<< "non-existent issuer: " << backNode.account_;
resultCode = terNO_ACCOUNT;
}
else if ((sleBck->getFieldU32 (sfFlags) & lsfRequireAuth) &&
!(sleRippleState->getFieldU32 (sfFlags) &
(bHigh ? lsfHighAuth : lsfLowAuth)) &&
sleRippleState->getFieldAmount(sfBalance) == beast::zero)
{
JLOG (j_.warn())
<< "pushNode: delay: can't receive IOUs from "
<< "issuer without auth.";
resultCode = terNO_AUTH;
}
if (resultCode == tesSUCCESS)
{
STAmount saOwed = creditBalance (view(),
node.account_, backNode.account_,
node.issue_.currency);
STAmount saLimit;
if (saOwed <= beast::zero)
{
saLimit = creditLimit (view(),
node.account_,
backNode.account_,
node.issue_.currency);
if (-saOwed >= saLimit)
{
JLOG (j_.debug()) <<
"pushNode: dry:" <<
" saOwed=" << saOwed <<
" saLimit=" << saLimit;
resultCode = tecPATH_DRY;
}
}
}
}
}
}
if (resultCode == tesSUCCESS)
nodes_.push_back (node);
}
else
{
// Offer link.
//
// Offers bridge a change in currency and issuer, or just a change in
// issuer.
if (hasIssuer)
node.issue_.account = issuer;
else if (isXRP (node.issue_.currency))
node.issue_.account = xrpAccount();
else if (isXRP (backNodeOuter.issue_.account))
node.issue_.account = backNodeOuter.account_;
else
node.issue_.account = backNodeOuter.issue_.account;
node.saRevDeliver = STAmount (node.issue_);
node.saFwdDeliver = node.saRevDeliver;
if (!isConsistent (node.issue_))
{
JLOG (j_.debug())
<< "pushNode: currency is inconsistent with issuer.";
resultCode = temBAD_PATH;
}
else if (backNodeOuter.issue_ == node.issue_)
{
JLOG (j_.debug()) <<
"pushNode: bad path: offer to same currency and issuer";
resultCode = temBAD_PATH;
}
else {
JLOG (j_.trace()) << "pushNode: imply for offer.";
// Insert intermediary issuer account if needed.
resultCode = pushImpliedNodes (
xrpAccount(), // Rippling, but offers don't have an account.
backNodeOuter.issue_.currency,
backNodeOuter.issue_.account);
}
if (resultCode == tesSUCCESS)
nodes_.push_back (node);
}
JLOG (j_.trace()) << "pushNode< : " << transToken (resultCode);
return resultCode;
}
// Set this object to be an expanded path from spSourcePath - take the implied
// nodes and makes them explicit. It also sanitizes the path.
//
// There are only two types of nodes: account nodes and order books nodes.
//
// You can infer some nodes automatically. If you're paying me bitstamp USD,
// then there must be an intermediate bitstamp node.
//
// If you have accounts A and B, and they're delivery currency issued by C, then
// there must be a node with account C in the middle.
//
// If you're paying USD and getting bitcoins, there has to be an order book in
// between.
//
// terStatus = tesSUCCESS, temBAD_PATH, terNO_LINE, terNO_ACCOUNT, terNO_AUTH,
// or temBAD_PATH_LOOP
TER PathState::expandPath (
STPath const& spSourcePath,
AccountID const& uReceiverID,
AccountID const& uSenderID)
{
uQuality = 1; // Mark path as active.
Currency const& uMaxCurrencyID = saInReq.getCurrency ();
AccountID const& uMaxIssuerID = saInReq.getIssuer ();
Currency const& currencyOutID = saOutReq.getCurrency ();
AccountID const& issuerOutID = saOutReq.getIssuer ();
AccountID const& uSenderIssuerID
= isXRP(uMaxCurrencyID) ? xrpAccount() : uSenderID;
// Sender is always issuer for non-XRP.
JLOG (j_.trace())
<< "expandPath> " << spSourcePath.getJson (JsonOptions::none);
terStatus = tesSUCCESS;
// XRP with issuer is malformed.
if ((isXRP (uMaxCurrencyID) && !isXRP (uMaxIssuerID))
|| (isXRP (currencyOutID) && !isXRP (issuerOutID)))
{
JLOG (j_.debug())
<< "expandPath> issuer with XRP";
terStatus = temBAD_PATH;
}
// Push sending node.
// For non-XRP, issuer is always sending account.
// - Trying to expand, not-compact.
// - Every issuer will be traversed through.
if (terStatus == tesSUCCESS)
{
terStatus = pushNode (
!isXRP(uMaxCurrencyID)
? STPathElement::typeAccount | STPathElement::typeCurrency |
STPathElement::typeIssuer
: STPathElement::typeAccount | STPathElement::typeCurrency,
uSenderID,
uMaxCurrencyID, // Max specifies the currency.
uSenderIssuerID);
}
JLOG (j_.debug())
<< "expandPath: pushed:"
<< " account=" << uSenderID
<< " currency=" << uMaxCurrencyID
<< " issuer=" << uSenderIssuerID;
// Issuer was not same as sender.
if (tesSUCCESS == terStatus && uMaxIssuerID != uSenderIssuerID)
{
// May have an implied account node.
// - If it was XRP, then issuers would have matched.
// Figure out next node properties for implied node.
const auto uNxtCurrencyID = spSourcePath.size ()
? Currency(spSourcePath.front ().getCurrency ())
// Use next node.
: currencyOutID;
// Use send.
// TODO(tom): complexify this next logic further in case someone
// understands it.
const auto nextAccountID = spSourcePath.size ()
? AccountID(spSourcePath. front ().getAccountID ())
: !isXRP(currencyOutID)
? (issuerOutID == uReceiverID)
? AccountID(uReceiverID)
: AccountID(issuerOutID) // Use implied node.
: xrpAccount();
JLOG (j_.debug())
<< "expandPath: implied check:"
<< " uMaxIssuerID=" << uMaxIssuerID
<< " uSenderIssuerID=" << uSenderIssuerID
<< " uNxtCurrencyID=" << uNxtCurrencyID
<< " nextAccountID=" << nextAccountID;
// Can't just use push implied, because it can't compensate for next
// account.
if (!uNxtCurrencyID
// Next is XRP, offer next. Must go through issuer.
|| uMaxCurrencyID != uNxtCurrencyID
// Next is different currency, offer next...
|| uMaxIssuerID != nextAccountID)
// Next is not implied issuer
{
JLOG (j_.debug())
<< "expandPath: sender implied:"
<< " account=" << uMaxIssuerID
<< " currency=" << uMaxCurrencyID
<< " issuer=" << uMaxIssuerID;
// Add account implied by SendMax.
terStatus = pushNode (
!isXRP(uMaxCurrencyID)
? STPathElement::typeAccount | STPathElement::typeCurrency |
STPathElement::typeIssuer
: STPathElement::typeAccount | STPathElement::typeCurrency,
uMaxIssuerID,
uMaxCurrencyID,
uMaxIssuerID);
}
}
for (auto & speElement: spSourcePath)
{
if (terStatus == tesSUCCESS)
{
JLOG (j_.trace()) << "expandPath: element in path";
terStatus = pushNode (
speElement.getNodeType (), speElement.getAccountID (),
speElement.getCurrency (), speElement.getIssuerID ());
}
}
if (terStatus == tesSUCCESS
&& !isXRP(currencyOutID) // Next is not XRP
&& issuerOutID != uReceiverID) // Out issuer is not receiver
{
assert (!nodes_.empty ());
auto const& backNode = nodes_.back ();
if (backNode.issue_.currency != currencyOutID // Previous will be offer
|| backNode.account_ != issuerOutID) // Need implied issuer
{
// Add implied account.
JLOG (j_.debug())
<< "expandPath: receiver implied:"
<< " account=" << issuerOutID
<< " currency=" << currencyOutID
<< " issuer=" << issuerOutID;
terStatus = pushNode (
!isXRP(currencyOutID)
? STPathElement::typeAccount | STPathElement::typeCurrency |
STPathElement::typeIssuer
: STPathElement::typeAccount | STPathElement::typeCurrency,
issuerOutID,
currencyOutID,
issuerOutID);
}
}
if (terStatus == tesSUCCESS)
{
// Create receiver node.
// Last node is always an account.
terStatus = pushNode (
!isXRP(currencyOutID)
? STPathElement::typeAccount | STPathElement::typeCurrency |
STPathElement::typeIssuer
: STPathElement::typeAccount | STPathElement::typeCurrency,
uReceiverID, // Receive to output
currencyOutID, // Desired currency
uReceiverID);
}
if (terStatus == tesSUCCESS)
{
// Look for first mention of source in nodes and detect loops.
// Note: The output is not allowed to be a source.
unsigned int index = 0;
for (auto& node: nodes_)
{
AccountIssue accountIssue (node.account_, node.issue_);
if (!umForward.insert ({accountIssue, index++}).second)
{
// Failed to insert. Have a loop.
JLOG (j_.debug()) <<
"expandPath: loop detected: " << getJson ();
terStatus = temBAD_PATH_LOOP;
break;
}
}
}
JLOG (j_.trace())
<< "expandPath:"
<< " in=" << uMaxCurrencyID
<< "/" << uMaxIssuerID
<< " out=" << currencyOutID
<< "/" << issuerOutID
<< ": " << getJson ();
return terStatus;
}
/** Check if an expanded path violates freeze rules */
void PathState::checkFreeze()
{
assert (nodes_.size() >= 2);
// A path with no intermediaries -- pure issue/redeem
// cannot be frozen.
if (nodes_.size() == 2)
return;
SLE::pointer sle;
for (std::size_t i = 0; i < (nodes_.size() - 1); ++i)
{
// Check each order book for a global freeze
if (nodes_[i].uFlags & STPathElement::typeIssuer)
{
sle = view().peek (keylet::account(nodes_[i].issue_.account));
if (sle && sle->isFlag (lsfGlobalFreeze))
{
terStatus = terNO_LINE;
return;
}
}
// Check each account change to make sure funds can leave
if (nodes_[i].uFlags & STPathElement::typeAccount)
{
Currency const& currencyID = nodes_[i].issue_.currency;
AccountID const& inAccount = nodes_[i].account_;
AccountID const& outAccount = nodes_[i+1].account_;
if (inAccount != outAccount)
{
sle = view().peek (keylet::account(outAccount));
if (sle && sle->isFlag (lsfGlobalFreeze))
{
terStatus = terNO_LINE;
return;
}
sle = view().peek (keylet::line(inAccount,
outAccount, currencyID));
if (sle && sle->isFlag (
(outAccount > inAccount) ? lsfHighFreeze : lsfLowFreeze))
{
terStatus = terNO_LINE;
return;
}
}
}
}
}
/** Check if a sequence of three accounts violates the no ripple constrains
[first] -> [second] -> [third]
Disallowed if 'second' set no ripple on [first]->[second] and
[second]->[third]
*/
TER PathState::checkNoRipple (
AccountID const& firstAccount,
AccountID const& secondAccount,
// This is the account whose constraints we are checking
AccountID const& thirdAccount,
Currency const& currency)
{
// fetch the ripple lines into and out of this node
SLE::pointer sleIn = view().peek (
keylet::line(firstAccount, secondAccount, currency));
SLE::pointer sleOut = view().peek (
keylet::line(secondAccount, thirdAccount, currency));
if (!sleIn || !sleOut)
{
terStatus = terNO_LINE;
}
else if (
sleIn->getFieldU32 (sfFlags) &
((secondAccount > firstAccount) ? lsfHighNoRipple : lsfLowNoRipple) &&
sleOut->getFieldU32 (sfFlags) &
((secondAccount > thirdAccount) ? lsfHighNoRipple : lsfLowNoRipple))
{
JLOG (j_.info())
<< "Path violates noRipple constraint between "
<< firstAccount << ", "
<< secondAccount << " and "
<< thirdAccount;
terStatus = terNO_RIPPLE;
}
return terStatus;
}
// Check a fully-expanded path to make sure it doesn't violate no-Ripple
// settings.
TER PathState::checkNoRipple (
AccountID const& uDstAccountID,
AccountID const& uSrcAccountID)
{
// There must be at least one node for there to be two consecutive ripple
// lines.
if (nodes_.size() == 0)
return terStatus;
if (nodes_.size() == 1)
{
// There's just one link in the path
// We only need to check source-node-dest
if (nodes_[0].isAccount() &&
(nodes_[0].account_ != uSrcAccountID) &&
(nodes_[0].account_ != uDstAccountID))
{
if (saInReq.getCurrency() != saOutReq.getCurrency())
{
terStatus = terNO_LINE;
}
else
{
terStatus = checkNoRipple (
uSrcAccountID, nodes_[0].account_, uDstAccountID,
nodes_[0].issue_.currency);
}
}
return terStatus;
}
// Check source <-> first <-> second
if (nodes_[0].isAccount() &&
nodes_[1].isAccount() &&
(nodes_[0].account_ != uSrcAccountID))
{
if ((nodes_[0].issue_.currency != nodes_[1].issue_.currency))
{
terStatus = terNO_LINE;
return terStatus;
}
else
{
terStatus = checkNoRipple (
uSrcAccountID, nodes_[0].account_, nodes_[1].account_,
nodes_[0].issue_.currency);
if (terStatus != tesSUCCESS)
return terStatus;
}
}
// Check second_from_last <-> last <-> destination
size_t s = nodes_.size() - 2;
if (nodes_[s].isAccount() &&
nodes_[s + 1].isAccount() &&
(uDstAccountID != nodes_[s+1].account_))
{
if ((nodes_[s].issue_.currency != nodes_[s+1].issue_.currency))
{
terStatus = terNO_LINE;
return terStatus;
}
else
{
terStatus = checkNoRipple (
nodes_[s].account_, nodes_[s+1].account_,
uDstAccountID, nodes_[s].issue_.currency);
if (tesSUCCESS != terStatus)
return terStatus;
}
}
// Loop through all nodes that have a prior node and successor nodes
// These are the nodes whose no ripple constraints could be violated
for (int i = 1; i < nodes_.size() - 1; ++i)
{
if (nodes_[i - 1].isAccount() &&
nodes_[i].isAccount() &&
nodes_[i + 1].isAccount())
{ // Two consecutive account-to-account links
auto const& currencyID = nodes_[i].issue_.currency;
if ((nodes_[i-1].issue_.currency != currencyID) ||
(nodes_[i+1].issue_.currency != currencyID))
{
terStatus = temBAD_PATH;
return terStatus;
}
terStatus = checkNoRipple (
nodes_[i-1].account_, nodes_[i].account_, nodes_[i+1].account_,
currencyID);
if (terStatus != tesSUCCESS)
return terStatus;
}
if (!nodes_[i - 1].isAccount() &&
nodes_[i].isAccount() &&
nodes_[i + 1].isAccount() &&
nodes_[i -1].issue_.account != nodes_[i].account_)
{ // offer -> account -> account
auto const& currencyID = nodes_[i].issue_.currency;
terStatus = checkNoRipple (
nodes_[i-1].issue_.account, nodes_[i].account_, nodes_[i+1].account_,
currencyID);
if (terStatus != tesSUCCESS)
return terStatus;
}
}
return tesSUCCESS;
}
// This is for debugging not end users. Output names can be changed without
// warning.
Json::Value PathState::getJson () const
{
Json::Value jvPathState (Json::objectValue);
Json::Value jvNodes (Json::arrayValue);
for (auto const &pnNode: nodes_)
jvNodes.append (pnNode.getJson ());
jvPathState[jss::status] = terStatus;
jvPathState[jss::index] = mIndex;
jvPathState[jss::nodes] = jvNodes;
if (saInReq)
jvPathState["in_req"] = saInReq.getJson (JsonOptions::none);
if (saInAct)
jvPathState["in_act"] = saInAct.getJson (JsonOptions::none);
if (saInPass)
jvPathState["in_pass"] = saInPass.getJson (JsonOptions::none);
if (saOutReq)
jvPathState["out_req"] = saOutReq.getJson (JsonOptions::none);
if (saOutAct)
jvPathState["out_act"] = saOutAct.getJson (JsonOptions::none);
if (saOutPass)
jvPathState["out_pass"] = saOutPass.getJson (JsonOptions::none);
if (uQuality)
jvPathState["uQuality"] = boost::lexical_cast<std::string>(uQuality);
return jvPathState;
}
} // ripple

View File

@@ -1,177 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_APP_PATHS_PATHSTATE_H_INCLUDED
#define RIPPLE_APP_PATHS_PATHSTATE_H_INCLUDED
#include <ripple/app/paths/Node.h>
#include <ripple/app/paths/Types.h>
#include <ripple/ledger/PaymentSandbox.h>
#include <boost/optional.hpp>
namespace ripple {
// Holds a single path state under incremental application.
class PathState : public CountedObject <PathState>
{
public:
using OfferIndexList = std::vector<uint256>;
using Ptr = std::shared_ptr<PathState>;
using List = std::vector<Ptr>;
PathState (PaymentSandbox const& parent,
STAmount const& saSend,
STAmount const& saSendMax,
beast::Journal j)
: mIndex (0)
, uQuality (0)
, saInReq (saSendMax)
, saOutReq (saSend)
, j_ (j)
{
view_.emplace(&parent);
}
void reset(STAmount const& in, STAmount const& out);
TER expandPath (
STPath const& spSourcePath,
AccountID const& uReceiverID,
AccountID const& uSenderID
);
path::Node::List& nodes() { return nodes_; }
STAmount const& inPass() const { return saInPass; }
STAmount const& outPass() const { return saOutPass; }
STAmount const& outReq() const { return saOutReq; }
STAmount const& inAct() const { return saInAct; }
STAmount const& outAct() const { return saOutAct; }
STAmount const& inReq() const { return saInReq; }
void setInPass(STAmount const& sa)
{
saInPass = sa;
}
void setOutPass(STAmount const& sa)
{
saOutPass = sa;
}
AccountIssueToNodeIndex const& forward() { return umForward; }
AccountIssueToNodeIndex const& reverse() { return umReverse; }
void insertReverse (AccountIssue const& ai, path::NodeIndex i)
{
umReverse.insert({ai, i});
}
static char const* getCountedObjectName () { return "PathState"; }
OfferIndexList& unfundedOffers() { return unfundedOffers_; }
void setStatus(TER status) { terStatus = status; }
TER status() const { return terStatus; }
std::uint64_t quality() const { return uQuality; }
void setQuality (std::uint64_t q) { uQuality = q; }
void setIndex (int i) { mIndex = i; }
int index() const { return mIndex; }
TER checkNoRipple (AccountID const& destinationAccountID,
AccountID const& sourceAccountID);
void checkFreeze ();
static bool lessPriority (PathState const& lhs, PathState const& rhs);
PaymentSandbox&
view()
{
return *view_;
}
void resetView (PaymentSandbox const& view)
{
view_.emplace(&view);
}
bool isDry() const
{
return !(saInPass && saOutPass);
}
private:
TER checkNoRipple (
AccountID const&, AccountID const&, AccountID const&, Currency const&);
/** Clear path structures, and clear each node. */
void clear();
TER pushNode (
int const iType,
AccountID const& account,
Currency const& currency,
AccountID const& issuer);
TER pushImpliedNodes (
AccountID const& account,
Currency const& currency,
AccountID const& issuer);
Json::Value getJson () const;
private:
boost::optional<PaymentSandbox> view_;
int mIndex; // Index/rank amoung siblings.
std::uint64_t uQuality; // 0 = no quality/liquity left.
STAmount const& saInReq; // --> Max amount to spend by sender.
STAmount saInAct; // --> Amount spent by sender so far.
STAmount saInPass; // <-- Amount spent by sender.
STAmount const& saOutReq; // --> Amount to send.
STAmount saOutAct; // --> Amount actually sent so far.
STAmount saOutPass; // <-- Amount actually sent.
TER terStatus;
path::Node::List nodes_;
// When processing, don't want to complicate directory walking with
// deletion. Offers that became unfunded or were completely consumed go
// here and are deleted at the end.
OfferIndexList unfundedOffers_;
// First time scanning foward, as part of path construction, a funding
// source was mentioned for accounts. Source may only be used there.
AccountIssueToNodeIndex umForward;
// First time working in reverse a funding source was used.
// Source may only be used there if not mentioned by an account.
AccountIssueToNodeIndex umReverse;
beast::Journal const j_;
};
} // ripple
#endif

View File

@@ -20,7 +20,6 @@
#include <ripple/app/paths/Flow.h>
#include <ripple/app/paths/RippleCalc.h>
#include <ripple/app/paths/Tuning.h>
#include <ripple/app/paths/cursor/PathCursor.h>
#include <ripple/app/paths/impl/FlowDebugInfo.h>
#include <ripple/basics/Log.h>
#include <ripple/ledger/View.h>
@@ -29,19 +28,8 @@
namespace ripple {
namespace path {
static
TER
deleteOffers (ApplyView& view,
boost::container::flat_set<uint256> const& offers, beast::Journal j)
{
for (auto& e: offers)
if (TER r = offerDelete (view,
view.peek(keylet::offer(e)), j))
return r;
return tesSUCCESS;
}
RippleCalc::Output RippleCalc::rippleCalculate (
RippleCalc::Output
RippleCalc::rippleCalculate(
PaymentSandbox& view,
// Compute paths using this ledger entry set. Up to caller to actually
@@ -51,7 +39,7 @@ RippleCalc::Output RippleCalc::rippleCalculate (
// XRP: xrpAccount()
// non-XRP: uSrcAccountID (for any issuer) or another account with
// trust node.
STAmount const& saMaxAmountReq, // --> -1 = no limit.
STAmount const& saMaxAmountReq, // --> -1 = no limit.
// Issuer:
// XRP: xrpAccount()
@@ -68,521 +56,84 @@ RippleCalc::Output RippleCalc::rippleCalculate (
Logs& l,
Input const* const pInputs)
{
// call flow v1 and v2 so results may be compared
bool const compareFlowV1V2 =
view.rules ().enabled (featureCompareFlowV1V2);
Output flowOut;
PaymentSandbox flowSB(&view);
auto j = l.journal("Flow");
bool const useFlowV1Output =
!view.rules().enabled(featureFlow);
bool const callFlowV1 = useFlowV1Output || compareFlowV1V2;
bool const callFlowV2 = !useFlowV1Output || compareFlowV1V2;
Output flowV1Out;
PaymentSandbox flowV1SB (&view);
auto const inNative = saMaxAmountReq.native();
auto const outNative = saDstAmountReq.native();
detail::FlowDebugInfo flowV1FlowDebugInfo (inNative, outNative);
if (callFlowV1)
if (!view.rules().enabled(featureFlow))
{
auto const timeIt = flowV1FlowDebugInfo.timeBlock ("main");
RippleCalc rc (
flowV1SB,
saMaxAmountReq,
saDstAmountReq,
uDstAccountID,
uSrcAccountID,
spsPaths,
l);
if (pInputs != nullptr)
{
rc.inputFlags = *pInputs;
}
auto result = rc.rippleCalculate (compareFlowV1V2 ? &flowV1FlowDebugInfo : nullptr);
flowV1Out.setResult (result);
flowV1Out.actualAmountIn = rc.actualAmountIn_;
flowV1Out.actualAmountOut = rc.actualAmountOut_;
if (result != tesSUCCESS && !rc.permanentlyUnfundedOffers_.empty ())
flowV1Out.removableOffers = std::move (rc.permanentlyUnfundedOffers_);
// The new payment engine was enabled several years ago. New transaction
// should never use the old rules. Assume this is a replay
j.fatal()
<< "Old payment rules are required for this transaction. Assuming "
"this is a replay and running with the new rules.";
}
Output flowV2Out;
PaymentSandbox flowV2SB (&view);
detail::FlowDebugInfo flowV2FlowDebugInfo (inNative, outNative);
auto j = l.journal ("Flow");
if (callFlowV2)
{
bool defaultPaths = true;
bool partialPayment = false;
boost::optional<Quality> limitQuality;
boost::optional<STAmount> sendMax;
bool const defaultPaths =
!pInputs ? true : pInputs->defaultPathsAllowed;
if (pInputs)
{
defaultPaths = pInputs->defaultPathsAllowed;
partialPayment = pInputs->partialPaymentAllowed;
if (pInputs->limitQuality && saMaxAmountReq > beast::zero)
limitQuality.emplace (
Amounts (saMaxAmountReq, saDstAmountReq));
}
bool const partialPayment =
!pInputs ? false : pInputs->partialPaymentAllowed;
if (saMaxAmountReq >= beast::zero ||
saMaxAmountReq.getCurrency () != saDstAmountReq.getCurrency () ||
saMaxAmountReq.getIssuer () != uSrcAccountID)
{
sendMax.emplace (saMaxAmountReq);
}
auto const limitQuality = [&]() -> boost::optional<Quality> {
if (pInputs && pInputs->limitQuality &&
saMaxAmountReq > beast::zero)
return Quality{Amounts(saMaxAmountReq, saDstAmountReq)};
return boost::none;
}();
auto const sendMax = [&]() -> boost::optional<STAmount> {
if (saMaxAmountReq >= beast::zero ||
saMaxAmountReq.getCurrency() != saDstAmountReq.getCurrency() ||
saMaxAmountReq.getIssuer() != uSrcAccountID)
{
return saMaxAmountReq;
}
return boost::none;
}();
bool const ownerPaysTransferFee =
view.rules().enabled(featureOwnerPaysFee);
try
{
bool const ownerPaysTransferFee =
view.rules ().enabled (featureOwnerPaysFee);
auto const timeIt = flowV2FlowDebugInfo.timeBlock ("main");
flowV2Out = flow (flowV2SB, saDstAmountReq, uSrcAccountID,
uDstAccountID, spsPaths, defaultPaths, partialPayment,
ownerPaysTransferFee, /* offerCrossing */ false, limitQuality, sendMax, j,
compareFlowV1V2 ? &flowV2FlowDebugInfo : nullptr);
flowOut = flow(
flowSB,
saDstAmountReq,
uSrcAccountID,
uDstAccountID,
spsPaths,
defaultPaths,
partialPayment,
ownerPaysTransferFee,
/* offerCrossing */ false,
limitQuality,
sendMax,
j,
nullptr);
}
catch (std::exception& e)
{
JLOG (j.error()) << "Exception from flow: " << e.what ();
if (!useFlowV1Output)
{
// return a tec so the tx is stored
path::RippleCalc::Output exceptResult;
exceptResult.setResult(tecINTERNAL);
return exceptResult;
}
JLOG(j.error()) << "Exception from flow: " << e.what();
// return a tec so the tx is stored
path::RippleCalc::Output exceptResult;
exceptResult.setResult(tecINTERNAL);
return exceptResult;
}
}
if (j.debug())
{
using BalanceDiffs = detail::BalanceDiffs;
auto logResult = [&](std::string const& algoName,
Output const& result,
detail::FlowDebugInfo const& flowDebugInfo,
boost::optional<BalanceDiffs> const& balanceDiffs,
bool outputPassInfo,
bool outputBalanceDiffs) {
j.debug () << "RippleCalc Result> " <<
" actualIn: " << result.actualAmountIn <<
", actualOut: " << result.actualAmountOut <<
", result: " << result.result () <<
", dstAmtReq: " << saDstAmountReq <<
", sendMax: " << saMaxAmountReq <<
(compareFlowV1V2 ? ", " + flowDebugInfo.to_string (outputPassInfo): "") <<
(outputBalanceDiffs && balanceDiffs
? ", " + detail::balanceDiffsToString(balanceDiffs) : "") <<
", algo: " << algoName;
};
bool outputPassInfo = false;
bool outputBalanceDiffs = false;
boost::optional<BalanceDiffs> bdV1, bdV2;
if (compareFlowV1V2)
{
auto const v1r = flowV1Out.result ();
auto const v2r = flowV2Out.result ();
if (v1r != v2r ||
(((v1r == tesSUCCESS) || (v1r == tecPATH_PARTIAL)) &&
((flowV1Out.actualAmountIn !=
flowV2Out.actualAmountIn) ||
(flowV1Out.actualAmountOut !=
flowV2Out.actualAmountOut))))
{
outputPassInfo = true;
}
bdV1 = detail::balanceDiffs (flowV1SB, view);
bdV2 = detail::balanceDiffs (flowV2SB, view);
outputBalanceDiffs = bdV1 != bdV2;
}
j.debug() << "RippleCalc Result> "
<< " actualIn: " << flowOut.actualAmountIn
<< ", actualOut: " << flowOut.actualAmountOut
<< ", result: " << flowOut.result()
<< ", dstAmtReq: " << saDstAmountReq
<< ", sendMax: " << saMaxAmountReq;
if (callFlowV1)
{
logResult ("V1", flowV1Out, flowV1FlowDebugInfo, bdV1,
outputPassInfo, outputBalanceDiffs);
}
if (callFlowV2)
{
logResult ("V2", flowV2Out, flowV2FlowDebugInfo, bdV2,
outputPassInfo, outputBalanceDiffs);
}
}
JLOG (j.trace()) << "Using old flow: " << useFlowV1Output;
if (!useFlowV1Output)
{
flowV2SB.apply (view);
return flowV2Out;
}
flowV1SB.apply (view);
return flowV1Out;
flowSB.apply(view);
return flowOut;
}
bool RippleCalc::addPathState(STPath const& path, TER& resultCode)
{
auto pathState = std::make_shared<PathState> (
view, saDstAmountReq_, saMaxAmountReq_, j_);
if (!pathState)
{
resultCode = temUNKNOWN;
return false;
}
pathState->expandPath (
path,
uDstAccountID_,
uSrcAccountID_);
if (pathState->status() == tesSUCCESS)
pathState->checkNoRipple (uDstAccountID_, uSrcAccountID_);
if (pathState->status() == tesSUCCESS)
pathState->checkFreeze ();
pathState->setIndex (pathStateList_.size ());
JLOG (j_.debug())
<< "rippleCalc: Build direct:"
<< " status: " << transToken (pathState->status());
// Return if malformed.
if (isTemMalformed (pathState->status()))
{
resultCode = pathState->status();
return false;
}
if (pathState->status () == tesSUCCESS)
{
resultCode = pathState->status();
pathStateList_.push_back (pathState);
}
else if (pathState->status () != terNO_LINE)
{
resultCode = pathState->status();
}
return true;
}
// OPTIMIZE: When calculating path increment, note if increment consumes all
// liquidity. No need to revisit path in the future if all liquidity is used.
// <-- TER: Only returns tepPATH_PARTIAL if partialPaymentAllowed.
TER RippleCalc::rippleCalculate (detail::FlowDebugInfo* flowDebugInfo)
{
JLOG (j_.trace())
<< "rippleCalc>"
<< " saMaxAmountReq_:" << saMaxAmountReq_
<< " saDstAmountReq_:" << saDstAmountReq_;
TER resultCode = temUNCERTAIN;
permanentlyUnfundedOffers_.clear ();
mumSource_.clear ();
// YYY Might do basic checks on src and dst validity as per doPayment.
// Incrementally search paths.
if (inputFlags.defaultPathsAllowed)
{
if (!addPathState (STPath(), resultCode))
return resultCode;
}
else if (spsPaths_.empty ())
{
JLOG (j_.debug())
<< "rippleCalc: Invalid transaction:"
<< "No paths and direct ripple not allowed.";
return temRIPPLE_EMPTY;
}
// Build a default path. Use saDstAmountReq_ and saMaxAmountReq_ to imply
// nodes.
// XXX Might also make a XRP bridge by default.
JLOG (j_.trace())
<< "rippleCalc: Paths in set: " << spsPaths_.size ();
// Now expand the path state.
for (auto const& spPath: spsPaths_)
{
if (!addPathState (spPath, resultCode))
return resultCode;
}
if (resultCode != tesSUCCESS)
return (resultCode == temUNCERTAIN) ? terNO_LINE : resultCode;
resultCode = temUNCERTAIN;
actualAmountIn_ = saMaxAmountReq_.zeroed();
actualAmountOut_ = saDstAmountReq_.zeroed();
// When processing, we don't want to complicate directory walking with
// deletion.
const std::uint64_t uQualityLimit = inputFlags.limitQuality ?
getRate (saDstAmountReq_, saMaxAmountReq_) : 0;
// Offers that became unfunded.
boost::container::flat_set<uint256> unfundedOffersFromBestPaths;
int iPass = 0;
while (resultCode == temUNCERTAIN)
{
int iBest = -1;
int iDry = 0;
// True, if ever computed multi-quality.
bool multiQuality = false;
if (flowDebugInfo) flowDebugInfo->newLiquidityPass();
// Find the best path.
for (auto pathState : pathStateList_)
{
if (pathState->quality())
// Only do active paths.
{
// If computing the only non-dry path, and not limiting quality,
// compute multi-quality.
multiQuality = !inputFlags.limitQuality &&
((pathStateList_.size() - iDry) == 1);
// Update to current amount processed.
pathState->reset (actualAmountIn_, actualAmountOut_);
// Error if done, output met.
PathCursor pc(*this, *pathState, multiQuality, j_);
pc.nextIncrement ();
// Compute increment.
JLOG (j_.debug())
<< "rippleCalc: AFTER:"
<< " mIndex=" << pathState->index()
<< " uQuality=" << pathState->quality()
<< " rate=" << amountFromQuality (pathState->quality());
if (flowDebugInfo)
flowDebugInfo->pushLiquiditySrc (
toEitherAmount (pathState->inPass ()),
toEitherAmount (pathState->outPass ()));
if (!pathState->quality())
{
// Path was dry.
++iDry;
}
else if (pathState->outPass() == beast::zero)
{
// Path is not dry, but moved no funds
// This should never happen. Consider the path dry
JLOG (j_.warn())
<< "rippleCalc: Non-dry path moves no funds";
assert (false);
pathState->setQuality (0);
++iDry;
}
else
{
if (!pathState->inPass() || !pathState->outPass())
{
JLOG (j_.debug())
<< "rippleCalc: better:"
<< " uQuality="
<< amountFromQuality (pathState->quality())
<< " inPass()=" << pathState->inPass()
<< " saOutPass=" << pathState->outPass();
}
assert (pathState->inPass() && pathState->outPass());
JLOG (j_.debug())
<< "Old flow iter (iter, in, out): "
<< iPass << " "
<< pathState->inPass() << " "
<< pathState->outPass();
if ((!inputFlags.limitQuality ||
pathState->quality() <= uQualityLimit)
// Quality is not limited or increment has allowed
// quality.
&& (iBest < 0
// Best is not yet set.
|| PathState::lessPriority (
*pathStateList_[iBest], *pathState)))
// Current is better than set.
{
JLOG (j_.debug())
<< "rippleCalc: better:"
<< " mIndex=" << pathState->index()
<< " uQuality=" << pathState->quality()
<< " rate="
<< amountFromQuality (pathState->quality())
<< " inPass()=" << pathState->inPass()
<< " saOutPass=" << pathState->outPass();
iBest = pathState->index ();
}
}
}
}
++iPass;
if (auto stream = j_.debug())
{
stream
<< "rippleCalc: Summary:"
<< " Pass: " << iPass
<< " Dry: " << iDry
<< " Paths: " << pathStateList_.size ();
for (auto pathState: pathStateList_)
{
stream
<< "rippleCalc: "
<< "Summary: " << pathState->index()
<< " rate: "
<< amountFromQuality (pathState->quality())
<< " quality:" << pathState->quality()
<< " best: " << (iBest == pathState->index ());
}
}
if (iBest >= 0)
{
// Apply best path.
auto pathState = pathStateList_[iBest];
if (flowDebugInfo)
flowDebugInfo->pushPass (toEitherAmount (pathState->inPass ()),
toEitherAmount (pathState->outPass ()),
pathStateList_.size () - iDry);
JLOG (j_.debug ())
<< "rippleCalc: best:"
<< " uQuality=" << amountFromQuality (pathState->quality ())
<< " inPass()=" << pathState->inPass ()
<< " saOutPass=" << pathState->outPass () << " iBest=" << iBest;
// Record best pass' offers that became unfunded for deletion on
// success.
unfundedOffersFromBestPaths.insert (
pathState->unfundedOffers().begin (),
pathState->unfundedOffers().end ());
// Apply best pass' view
pathState->view().apply(view);
actualAmountIn_ += pathState->inPass();
actualAmountOut_ += pathState->outPass();
JLOG (j_.trace())
<< "rippleCalc: best:"
<< " uQuality="
<< amountFromQuality (pathState->quality())
<< " inPass()=" << pathState->inPass()
<< " saOutPass=" << pathState->outPass()
<< " actualIn=" << actualAmountIn_
<< " actualOut=" << actualAmountOut_
<< " iBest=" << iBest;
if (multiQuality)
{
++iDry;
pathState->setQuality(0);
}
if (actualAmountOut_ == saDstAmountReq_)
{
// Done. Delivered requested amount.
resultCode = tesSUCCESS;
}
else if (actualAmountOut_ > saDstAmountReq_)
{
JLOG (j_.fatal())
<< "rippleCalc: TOO MUCH:"
<< " actualAmountOut_:" << actualAmountOut_
<< " saDstAmountReq_:" << saDstAmountReq_;
return tefEXCEPTION; // TEMPORARY
assert (false);
}
else if (actualAmountIn_ != saMaxAmountReq_ &&
iDry != pathStateList_.size ())
{
// Have not met requested amount or max send, try to do
// more. Prepare for next pass.
//
// Merge best pass' umReverse.
mumSource_.insert (
pathState->reverse().begin (), pathState->reverse().end ());
if (iPass >= PAYMENT_MAX_LOOPS)
{
// This payment is taking too many passes
JLOG (j_.error())
<< "rippleCalc: pass limit";
resultCode = telFAILED_PROCESSING;
}
}
else if (!inputFlags.partialPaymentAllowed)
{
// Have sent maximum allowed. Partial payment not allowed.
resultCode = tecPATH_PARTIAL;
}
else
{
// Have sent maximum allowed. Partial payment allowed. Success.
resultCode = tesSUCCESS;
}
}
// Not done and ran out of paths.
else if (!inputFlags.partialPaymentAllowed)
{
// Partial payment not allowed.
resultCode = tecPATH_PARTIAL;
}
// Partial payment ok.
else if (!actualAmountOut_)
{
// No payment at all.
resultCode = tecPATH_DRY;
}
else
{
// Don't apply any payment increments
resultCode = tesSUCCESS;
}
}
if (resultCode == tesSUCCESS)
{
auto viewJ = logs_.journal ("View");
resultCode = deleteOffers(view, unfundedOffersFromBestPaths, viewJ);
if (resultCode == tesSUCCESS)
resultCode = deleteOffers(view, permanentlyUnfundedOffers_, viewJ);
}
// If isOpenLedger, then ledger is not final, can vote no.
if (resultCode == telFAILED_PROCESSING && !inputFlags.isLedgerOpen)
return tecFAILED_PROCESSING;
return resultCode;
}
} // path
} // ripple
} // namespace path
} // namespace ripple

View File

@@ -20,9 +20,8 @@
#ifndef RIPPLE_APP_PATHS_RIPPLECALC_H_INCLUDED
#define RIPPLE_APP_PATHS_RIPPLECALC_H_INCLUDED
#include <ripple/ledger/PaymentSandbox.h>
#include <ripple/app/paths/PathState.h>
#include <ripple/basics/Log.h>
#include <ripple/ledger/PaymentSandbox.h>
#include <ripple/protocol/STAmount.h>
#include <ripple/protocol/TER.h>
@@ -69,23 +68,24 @@ public:
// could have been removed but were not because the payment fails. It is
// useful for offer crossing, which does remove the offers.
boost::container::flat_set<uint256> removableOffers;
private:
TER calculationResult_ = temUNKNOWN;
public:
TER result () const
TER
result() const
{
return calculationResult_;
}
void setResult (TER const value)
void
setResult(TER const value)
{
calculationResult_ = value;
}
};
static
Output
static Output
rippleCalculate(
PaymentSandbox& view,
@@ -96,7 +96,7 @@ public:
// XRP: xrpAccount()
// non-XRP: uSrcAccountID (for any issuer) or another account with
// trust node.
STAmount const& saMaxAmountReq, // --> -1 = no limit.
STAmount const& saMaxAmountReq, // --> -1 = no limit.
// Issuer:
// XRP: xrpAccount()
@@ -121,63 +121,9 @@ public:
//
// Offers that were found unfunded.
boost::container::flat_set<uint256> permanentlyUnfundedOffers_;
// First time working in reverse a funding source was mentioned. Source may
// only be used there.
// Map of currency, issuer to node index.
AccountIssueToNodeIndex mumSource_;
beast::Journal const j_;
Logs& logs_;
private:
RippleCalc (
PaymentSandbox& view_,
STAmount const& saMaxAmountReq, // --> -1 = no limit.
STAmount const& saDstAmountReq,
AccountID const& uDstAccountID,
AccountID const& uSrcAccountID,
STPathSet const& spsPaths,
Logs& l)
: view (view_),
j_ (l.journal ("RippleCalc")),
logs_ (l),
saDstAmountReq_(saDstAmountReq),
saMaxAmountReq_(saMaxAmountReq),
uDstAccountID_(uDstAccountID),
uSrcAccountID_(uSrcAccountID),
spsPaths_(spsPaths)
{
}
/** Compute liquidity through these path sets. */
TER rippleCalculate (detail::FlowDebugInfo* flowDebugInfo=nullptr);
/** Add a single PathState. Returns true on success.*/
bool addPathState(STPath const&, TER&);
STAmount const& saDstAmountReq_;
STAmount const& saMaxAmountReq_;
AccountID const& uDstAccountID_;
AccountID const& uSrcAccountID_;
STPathSet const& spsPaths_;
// The computed input amount.
STAmount actualAmountIn_;
// The computed output amount.
STAmount actualAmountOut_;
// Expanded path with all the actual nodes in it.
// A path starts with the source account, ends with the destination account
// and goes through other acounts or order books.
PathState::List pathStateList_;
Input inputFlags;
};
} // path
} // ripple
} // namespace path
} // namespace ripple
#endif

View File

@@ -1,399 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/app/paths/cursor/RippleLiquidity.h>
#include <ripple/basics/Log.h>
#include <ripple/ledger/View.h>
namespace ripple {
namespace path {
TER PathCursor::advanceNode (STAmount const& amount, bool reverse, bool callerHasLiquidity) const
{
bool const multi =
(multiQuality_ || (!callerHasLiquidity && amount == beast::zero));
// If the multiQuality_ is unchanged, use the PathCursor we're using now.
if (multi == multiQuality_)
return advanceNode (reverse);
// Otherwise, use a new PathCursor with the new multiQuality_.
PathCursor withMultiQuality {rippleCalc_, pathState_, multi, j_, nodeIndex_};
return withMultiQuality.advanceNode (reverse);
}
// OPTIMIZE: When calculating path increment, note if increment consumes all
// liquidity. No need to revisit path in the future if all liquidity is used.
//
TER PathCursor::advanceNode (bool const bReverse) const
{
TER resultCode = tesSUCCESS;
// Taker is the active party against an offer in the ledger - the entity
// that is taking advantage of an offer in the order book.
JLOG (j_.trace())
<< "advanceNode: TakerPays:"
<< node().saTakerPays << " TakerGets:" << node().saTakerGets;
int loopCount = 0;
auto viewJ = rippleCalc_.logs_.journal ("View");
do
{
// VFALCO NOTE Why not use a for() loop?
// VFALCO TODO The limit on loop iterations puts an
// upper limit on the number of different quality
// levels (ratio of pay:get) that will be considered for one path.
// Changing this value has repercusssions on validation and consensus.
//
if (++loopCount > NODE_ADVANCE_MAX_LOOPS)
{
JLOG (j_.warn()) << "Loop count exceeded";
return tefEXCEPTION;
}
bool bDirectDirDirty = node().directory.initialize (
{ previousNode().issue_, node().issue_},
view());
if (auto advance = node().directory.advance (view()))
{
bDirectDirDirty = true;
if (advance == NodeDirectory::NEW_QUALITY)
{
// We didn't run off the end of this order book and found
// another quality directory.
JLOG (j_.trace())
<< "advanceNode: Quality advance: node.directory.current="
<< node().directory.current;
}
else if (bReverse)
{
JLOG (j_.trace())
<< "advanceNode: No more offers.";
node().offerIndex_ = beast::zero;
break;
}
else
{
// No more offers. Should be done rather than fall off end of
// book.
JLOG (j_.warn())
<< "advanceNode: Unreachable: "
<< "Fell off end of order book.";
// FIXME: why?
return telFAILED_PROCESSING;
}
}
if (bDirectDirDirty)
{
// Our quality changed since last iteration.
// Use the rate from the directory.
node().saOfrRate = amountFromQuality (
getQuality (node().directory.current));
// For correct ratio
node().uEntry = 0;
node().bEntryAdvance = true;
JLOG (j_.trace())
<< "advanceNode: directory dirty: node.saOfrRate="
<< node().saOfrRate;
}
if (!node().bEntryAdvance)
{
if (node().bFundsDirty)
{
// We were called again probably merely to update structure
// variables.
node().saTakerPays
= node().sleOffer->getFieldAmount (sfTakerPays);
node().saTakerGets
= node().sleOffer->getFieldAmount (sfTakerGets);
// Funds left.
node().saOfferFunds = accountFunds(view(),
node().offerOwnerAccount_,
node().saTakerGets,
fhZERO_IF_FROZEN, viewJ);
node().bFundsDirty = false;
JLOG (j_.trace())
<< "advanceNode: funds dirty: node().saOfrRate="
<< node().saOfrRate;
}
else
{
JLOG (j_.trace()) << "advanceNode: as is";
}
}
else if (!dirNext (view(),
node().directory.current,
node().directory.ledgerEntry,
node().uEntry,
node().offerIndex_, viewJ))
// This is the only place that offerIndex_ changes.
{
// Failed to find an entry in directory.
// Do another cur directory iff multiQuality_
if (multiQuality_)
{
// We are allowed to process multiple qualities if this is the
// only path.
JLOG (j_.trace())
<< "advanceNode: next quality";
node().directory.advanceNeeded = true; // Process next quality.
}
else if (!bReverse)
{
// We didn't run dry going backwards - why are we running dry
// going forwards - this should be impossible!
// TODO(tom): these warnings occur in production! They
// shouldn't.
JLOG (j_.warn())
<< "advanceNode: unreachable: ran out of offers";
return telFAILED_PROCESSING;
}
else
{
// Ran off end of offers.
node().bEntryAdvance = false; // Done.
node().offerIndex_ = beast::zero; // Report no more entries.
}
}
else
{
// Got a new offer.
node().sleOffer = view().peek (keylet::offer(node().offerIndex_));
if (!node().sleOffer)
{
// Corrupt directory that points to an entry that doesn't exist.
// This has happened in production.
JLOG (j_.warn()) <<
"Missing offer in directory";
node().bEntryAdvance = true;
}
else
{
node().offerOwnerAccount_
= node().sleOffer->getAccountID (sfAccount);
node().saTakerPays
= node().sleOffer->getFieldAmount (sfTakerPays);
node().saTakerGets
= node().sleOffer->getFieldAmount (sfTakerGets);
AccountIssue const accountIssue (
node().offerOwnerAccount_, node().issue_);
JLOG (j_.trace())
<< "advanceNode: offerOwnerAccount_="
<< to_string (node().offerOwnerAccount_)
<< " node.saTakerPays=" << node().saTakerPays
<< " node.saTakerGets=" << node().saTakerGets
<< " node.offerIndex_=" << node().offerIndex_;
if (node().sleOffer->isFieldPresent (sfExpiration) &&
(node().sleOffer->getFieldU32 (sfExpiration) <=
view().parentCloseTime().time_since_epoch().count()))
{
// Offer is expired.
JLOG (j_.trace())
<< "advanceNode: expired offer";
rippleCalc_.permanentlyUnfundedOffers_.insert(
node().offerIndex_);
continue;
}
if (node().saTakerPays <= beast::zero || node().saTakerGets <= beast::zero)
{
// Offer has bad amounts. Offers should never have a bad
// amounts.
auto const index = node().offerIndex_;
if (bReverse)
{
// Past internal error, offer had bad amounts.
// This has occurred in production.
JLOG (j_.warn())
<< "advanceNode: PAST INTERNAL ERROR"
<< " REVERSE: OFFER NON-POSITIVE:"
<< " node.saTakerPays=" << node().saTakerPays
<< " node.saTakerGets=" << node().saTakerGets;
// Mark offer for always deletion.
rippleCalc_.permanentlyUnfundedOffers_.insert (
node().offerIndex_);
}
else if (rippleCalc_.permanentlyUnfundedOffers_.find (index)
!= rippleCalc_.permanentlyUnfundedOffers_.end ())
{
// Past internal error, offer was found failed to place
// this in permanentlyUnfundedOffers_.
// Just skip it. It will be deleted.
JLOG (j_.debug())
<< "advanceNode: PAST INTERNAL ERROR "
<< " FORWARD CONFIRM: OFFER NON-POSITIVE:"
<< " node.saTakerPays=" << node().saTakerPays
<< " node.saTakerGets=" << node().saTakerGets;
}
else
{
// Reverse should have previously put bad offer in list.
// An internal error previously left a bad offer.
JLOG (j_.warn())
<< "advanceNode: INTERNAL ERROR"
<<" FORWARD NEWLY FOUND: OFFER NON-POSITIVE:"
<< " node.saTakerPays=" << node().saTakerPays
<< " node.saTakerGets=" << node().saTakerGets;
// Don't process at all, things are in an unexpected
// state for this transactions.
resultCode = tefEXCEPTION;
}
continue;
}
// Allowed to access source from this node?
//
// XXX This can get called multiple times for same source in a
// row, caching result would be nice.
//
// XXX Going forward could we fund something with a worse
// quality which was previously skipped? Might need to check
// quality.
auto itForward = pathState_.forward().find (accountIssue);
const bool bFoundForward =
itForward != pathState_.forward().end ();
// Only allow a source to be used once, in the first node
// encountered from initial path scan. This prevents
// conflicting uses of the same balance when going reverse vs
// forward.
if (bFoundForward &&
itForward->second != nodeIndex_ &&
node().offerOwnerAccount_ != node().issue_.account)
{
// Temporarily unfunded. Another node uses this source,
// ignore in this offer.
JLOG (j_.trace())
<< "advanceNode: temporarily unfunded offer"
<< " (forward)";
continue;
}
// This is overly strict. For contributions to past. We should
// only count source if actually used.
auto itReverse = pathState_.reverse().find (accountIssue);
bool bFoundReverse = itReverse != pathState_.reverse().end ();
// For this quality increment, only allow a source to be used
// from a single node, in the first node encountered from
// applying offers in reverse.
if (bFoundReverse &&
itReverse->second != nodeIndex_ &&
node().offerOwnerAccount_ != node().issue_.account)
{
// Temporarily unfunded. Another node uses this source,
// ignore in this offer.
JLOG (j_.trace())
<< "advanceNode: temporarily unfunded offer"
<<" (reverse)";
continue;
}
// Determine if used in past.
// We only need to know if it might need to be marked unfunded.
auto itPast = rippleCalc_.mumSource_.find (accountIssue);
bool bFoundPast = (itPast != rippleCalc_.mumSource_.end ());
// Only the current node is allowed to use the source.
node().saOfferFunds = accountFunds(view(),
node().offerOwnerAccount_,
node().saTakerGets,
fhZERO_IF_FROZEN, viewJ);
// Funds held.
if (node().saOfferFunds <= beast::zero)
{
// Offer is unfunded.
JLOG (j_.trace())
<< "advanceNode: unfunded offer";
if (bReverse && !bFoundReverse && !bFoundPast)
{
// Never mentioned before, clearly just: found unfunded.
// That is, even if this offer fails due to fill or kill
// still do deletions.
// Mark offer for always deletion.
rippleCalc_.permanentlyUnfundedOffers_.insert (node().offerIndex_);
}
else
{
// Moving forward, don't need to insert again
// Or, already found it.
}
// YYY Could verify offer is correct place for unfundeds.
continue;
}
if (bReverse // Need to remember reverse mention.
&& !bFoundPast // Not mentioned in previous passes.
&& !bFoundReverse) // New to pass.
{
// Consider source mentioned by current path state.
JLOG (j_.trace())
<< "advanceNode: remember="
<< node().offerOwnerAccount_
<< "/"
<< node().issue_;
pathState_.insertReverse (accountIssue, nodeIndex_);
}
node().bFundsDirty = false;
node().bEntryAdvance = false;
}
}
}
while (resultCode == tesSUCCESS &&
(node().bEntryAdvance || node().directory.advanceNeeded));
if (resultCode == tesSUCCESS)
{
JLOG (j_.trace())
<< "advanceNode: node.offerIndex_=" << node().offerIndex_;
}
else
{
JLOG (j_.debug())
<< "advanceNode: resultCode=" << transToken (resultCode);
}
return resultCode;
}
} // path
} // ripple

View File

@@ -1,370 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/app/paths/cursor/EffectiveRate.h>
#include <ripple/app/paths/cursor/RippleLiquidity.h>
#include <ripple/basics/Log.h>
namespace ripple {
namespace path {
// For current offer, get input from deliver/limbo and output to next account or
// deliver for next offers.
//
// <-- node.saFwdDeliver: For forwardLiquidityForAccount to know
// how much went through
// --> node.saRevDeliver: Do not exceed.
TER PathCursor::deliverNodeForward (
AccountID const& uInAccountID, // --> Input owner's account.
STAmount const& saInReq, // --> Amount to deliver.
STAmount& saInAct, // <-- Amount delivered, this invocation.
STAmount& saInFees, // <-- Fees charged, this invocation.
bool callerHasLiquidity) const
{
TER resultCode = tesSUCCESS;
// Don't deliver more than wanted.
// Zeroed in reverse pass.
node().directory.restart(multiQuality_);
saInAct.clear (saInReq);
saInFees.clear (saInReq);
int loopCount = 0;
auto viewJ = rippleCalc_.logs_.journal ("View");
// XXX Perhaps make sure do not exceed node().saRevDeliver as another way to
// stop?
while (resultCode == tesSUCCESS && saInAct + saInFees < saInReq)
{
// Did not spend all inbound deliver funds.
if (++loopCount >
(multiQuality_ ?
CALC_NODE_DELIVER_MAX_LOOPS_MQ :
CALC_NODE_DELIVER_MAX_LOOPS))
{
JLOG (j_.warn())
<< "deliverNodeForward: max loops cndf";
return telFAILED_PROCESSING;
}
// Determine values for pass to adjust saInAct, saInFees, and
// node().saFwdDeliver.
advanceNode (saInAct, false, callerHasLiquidity);
// If needed, advance to next funded offer.
if (resultCode != tesSUCCESS)
{
}
else if (!node().offerIndex_)
{
JLOG (j_.warn())
<< "deliverNodeForward: INTERNAL ERROR: Ran out of offers.";
return telFAILED_PROCESSING;
}
else if (resultCode == tesSUCCESS)
{
auto const xferRate = effectiveRate (
previousNode().issue_,
uInAccountID,
node().offerOwnerAccount_,
previousNode().transferRate_);
// First calculate assuming no output fees: saInPassAct,
// saInPassFees, saOutPassAct.
// Offer maximum out - limited by funds with out fees.
auto saOutFunded = std::min (
node().saOfferFunds, node().saTakerGets);
// Offer maximum out - limit by most to deliver.
auto saOutPassFunded = std::min (
saOutFunded,
node().saRevDeliver - node().saFwdDeliver);
// Offer maximum in - Limited by by payout.
auto saInFunded = mulRound (
saOutPassFunded,
node().saOfrRate,
node().saTakerPays.issue (),
true);
// Offer maximum in with fees.
auto saInTotal = multiplyRound (
saInFunded, xferRate, true);
auto saInRemaining = saInReq - saInAct - saInFees;
if (saInRemaining < beast::zero)
saInRemaining.clear();
// In limited by remaining.
auto saInSum = std::min (saInTotal, saInRemaining);
// In without fees.
auto saInPassAct = std::min (
node().saTakerPays,
divideRound (saInSum, xferRate, true));
// Out limited by in remaining.
auto outPass = divRound (
saInPassAct, node().saOfrRate, node().saTakerGets.issue (), true);
STAmount saOutPassMax = std::min (saOutPassFunded, outPass);
STAmount saInPassFeesMax = saInSum - saInPassAct;
// Will be determined by next node().
STAmount saOutPassAct;
// Will be determined by adjusted saInPassAct.
STAmount saInPassFees;
JLOG (j_.trace())
<< "deliverNodeForward:"
<< " nodeIndex_=" << nodeIndex_
<< " saOutFunded=" << saOutFunded
<< " saOutPassFunded=" << saOutPassFunded
<< " node().saOfferFunds=" << node().saOfferFunds
<< " node().saTakerGets=" << node().saTakerGets
<< " saInReq=" << saInReq
<< " saInAct=" << saInAct
<< " saInFees=" << saInFees
<< " saInFunded=" << saInFunded
<< " saInTotal=" << saInTotal
<< " saInSum=" << saInSum
<< " saInPassAct=" << saInPassAct
<< " saOutPassMax=" << saOutPassMax;
// FIXME: We remove an offer if WE didn't want anything out of it?
if (!node().saTakerPays || saInSum <= beast::zero)
{
JLOG (j_.debug())
<< "deliverNodeForward: Microscopic offer unfunded.";
// After math offer is effectively unfunded.
pathState_.unfundedOffers().push_back (node().offerIndex_);
node().bEntryAdvance = true;
continue;
}
if (!saInFunded)
{
// Previous check should catch this.
JLOG (j_.warn())
<< "deliverNodeForward: UNREACHABLE REACHED";
// After math offer is effectively unfunded.
pathState_.unfundedOffers().push_back (node().offerIndex_);
node().bEntryAdvance = true;
continue;
}
if (!isXRP(nextNode().account_))
{
// ? --> OFFER --> account
// Input fees: vary based upon the consumed offer's owner.
// Output fees: none as XRP or the destination account is the
// issuer.
saOutPassAct = saOutPassMax;
saInPassFees = saInPassFeesMax;
JLOG (j_.trace())
<< "deliverNodeForward: ? --> OFFER --> account:"
<< " offerOwnerAccount_="
<< node().offerOwnerAccount_
<< " nextNode().account_="
<< nextNode().account_
<< " saOutPassAct=" << saOutPassAct
<< " saOutFunded=" << saOutFunded;
// Output: Debit offer owner, send XRP or non-XPR to next
// account.
resultCode = accountSend(view(),
node().offerOwnerAccount_,
nextNode().account_,
saOutPassAct, viewJ);
if (resultCode != tesSUCCESS)
break;
}
else
{
// ? --> OFFER --> offer
//
// Offer to offer means current order book's output currency and
// issuer match next order book's input current and issuer.
//
// Output fees: possible if issuer has fees and is not on either
// side.
STAmount saOutPassFees;
// Output fees vary as the next nodes offer owners may vary.
// Therefore, immediately push through output for current offer.
resultCode = increment().deliverNodeForward (
node().offerOwnerAccount_, // --> Current holder.
saOutPassMax, // --> Amount available.
saOutPassAct, // <-- Amount delivered.
saOutPassFees, // <-- Fees charged.
saInAct > beast::zero);
if (resultCode != tesSUCCESS)
break;
if (saOutPassAct == saOutPassMax)
{
// No fees and entire output amount.
saInPassFees = saInPassFeesMax;
}
else
{
// Fraction of output amount.
// Output fees are paid by offer owner and not passed to
// previous.
assert (saOutPassAct < saOutPassMax);
auto inPassAct = mulRound (
saOutPassAct, node().saOfrRate, saInReq.issue (), true);
saInPassAct = std::min (node().saTakerPays, inPassAct);
auto inPassFees = multiplyRound (
saInPassAct, xferRate, true);
saInPassFees = std::min (saInPassFeesMax, inPassFees);
}
// Do outbound debiting.
// Send to issuer/limbo total amount including fees (issuer gets
// fees).
auto const& id = isXRP(node().issue_) ?
xrpAccount() : node().issue_.account;
auto outPassTotal = saOutPassAct + saOutPassFees;
accountSend(view(),
node().offerOwnerAccount_,
id,
outPassTotal,
viewJ);
JLOG (j_.trace())
<< "deliverNodeForward: ? --> OFFER --> offer:"
<< " saOutPassAct=" << saOutPassAct
<< " saOutPassFees=" << saOutPassFees;
}
JLOG (j_.trace())
<< "deliverNodeForward: "
<< " nodeIndex_=" << nodeIndex_
<< " node().saTakerGets=" << node().saTakerGets
<< " node().saTakerPays=" << node().saTakerPays
<< " saInPassAct=" << saInPassAct
<< " saInPassFees=" << saInPassFees
<< " saOutPassAct=" << saOutPassAct
<< " saOutFunded=" << saOutFunded;
// Funds were spent.
node().bFundsDirty = true;
// Do inbound crediting.
//
// Credit offer owner from in issuer/limbo (input transfer fees left
// with owner). Don't attempt to have someone credit themselves, it
// is redundant.
if (isXRP (previousNode().issue_.currency)
|| uInAccountID != node().offerOwnerAccount_)
{
auto id = !isXRP(previousNode().issue_.currency) ?
uInAccountID : xrpAccount();
resultCode = accountSend(view(),
id,
node().offerOwnerAccount_,
saInPassAct,
viewJ);
if (resultCode != tesSUCCESS)
break;
}
// Adjust offer.
//
// Fees are considered paid from a seperate budget and are not named
// in the offer.
STAmount saTakerGetsNew = node().saTakerGets - saOutPassAct;
STAmount saTakerPaysNew = node().saTakerPays - saInPassAct;
if (saTakerPaysNew < beast::zero || saTakerGetsNew < beast::zero)
{
JLOG (j_.warn())
<< "deliverNodeForward: NEGATIVE:"
<< " saTakerPaysNew=" << saTakerPaysNew
<< " saTakerGetsNew=" << saTakerGetsNew;
resultCode = telFAILED_PROCESSING;
break;
}
node().sleOffer->setFieldAmount (sfTakerGets, saTakerGetsNew);
node().sleOffer->setFieldAmount (sfTakerPays, saTakerPaysNew);
view().update (node().sleOffer);
if (saOutPassAct == saOutFunded || saTakerGetsNew == beast::zero)
{
// Offer became unfunded.
JLOG (j_.debug())
<< "deliverNodeForward: unfunded:"
<< " saOutPassAct=" << saOutPassAct
<< " saOutFunded=" << saOutFunded;
pathState_.unfundedOffers().push_back (node().offerIndex_);
node().bEntryAdvance = true;
}
else
{
if (saOutPassAct >= saOutFunded)
{
JLOG (j_.warn())
<< "deliverNodeForward: TOO MUCH:"
<< " saOutPassAct=" << saOutPassAct
<< " saOutFunded=" << saOutFunded;
}
assert (saOutPassAct < saOutFunded);
}
saInAct += saInPassAct;
saInFees += saInPassFees;
// Adjust amount available to next node().
node().saFwdDeliver = std::min (node().saRevDeliver,
node().saFwdDeliver + saOutPassAct);
}
}
JLOG (j_.trace())
<< "deliverNodeForward<"
<< " nodeIndex_=" << nodeIndex_
<< " saInAct=" << saInAct
<< " saInFees=" << saInFees;
return resultCode;
}
} // path
} // ripple

View File

@@ -1,371 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/app/paths/cursor/EffectiveRate.h>
#include <ripple/app/paths/cursor/RippleLiquidity.h>
#include <ripple/basics/Log.h>
namespace ripple {
namespace path {
// At the right most node of a list of consecutive offer nodes, given the amount
// requested to be delivered, push towards the left nodes the amount requested
// for the right nodes so we can compute how much to deliver from the source.
//
// Between offer nodes, the fee charged may vary. Therefore, process one
// inbound offer at a time. Propagate the inbound offer's requirements to the
// previous node. The previous node adjusts the amount output and the amount
// spent on fees. Continue processing until the request is satisified as long
// as the rate does not increase past the initial rate.
// To deliver from an order book, when computing
TER PathCursor::deliverNodeReverseImpl (
AccountID const& uOutAccountID, // --> Output owner's account.
STAmount const& saOutReq, // --> Funds requested to be
// delivered for an increment.
STAmount& saOutAct, // <-- Funds actually delivered for an
// increment
bool callerHasLiquidity
) const
{
TER resultCode = tesSUCCESS;
// Accumulation of what the previous node must deliver.
// Possible optimization: Note this gets zeroed on each increment, ideally
// only on first increment, then it could be a limit on the forward pass.
saOutAct.clear (saOutReq);
JLOG (j_.trace())
<< "deliverNodeReverse>"
<< " saOutAct=" << saOutAct
<< " saOutReq=" << saOutReq
<< " saPrvDlvReq=" << previousNode().saRevDeliver;
assert (saOutReq != beast::zero);
int loopCount = 0;
auto viewJ = rippleCalc_.logs_.journal ("View");
// While we did not deliver as much as requested:
while (saOutAct < saOutReq)
{
if (++loopCount >
(multiQuality_ ?
CALC_NODE_DELIVER_MAX_LOOPS_MQ :
CALC_NODE_DELIVER_MAX_LOOPS))
{
JLOG (j_.warn()) << "loop count exceeded";
return telFAILED_PROCESSING;
}
resultCode = advanceNode (saOutAct, true, callerHasLiquidity);
// If needed, advance to next funded offer.
if (resultCode != tesSUCCESS || !node().offerIndex_)
// Error or out of offers.
break;
auto const xferRate = effectiveRate (
node().issue_,
uOutAccountID,
node().offerOwnerAccount_,
node().transferRate_);
JLOG (j_.trace())
<< "deliverNodeReverse:"
<< " offerOwnerAccount_=" << node().offerOwnerAccount_
<< " uOutAccountID=" << uOutAccountID
<< " node().issue_.account=" << node().issue_.account
<< " xferRate=" << xferRate;
// Only use rate when not in multi-quality mode
if (!multiQuality_)
{
if (!node().rateMax)
{
// Set initial rate.
JLOG (j_.trace())
<< "Set initial rate";
node().rateMax = xferRate;
}
else if (xferRate > node().rateMax)
{
// Offer exceeds initial rate.
JLOG (j_.trace())
<< "Offer exceeds initial rate: " << *node().rateMax;
break; // Done. Don't bother looking for smaller transferRates.
}
else if (xferRate < node().rateMax)
{
// Reducing rate. Additional offers will only
// be considered for this increment if they
// are at least this good.
//
// At this point, the overall rate is reducing,
// while the overall rate is not xferRate, it
// would be wrong to add anything with a rate
// above xferRate.
//
// The rate would be reduced if the current
// offer was from the issuer and the previous
// offer wasn't.
JLOG (j_.trace())
<< "Reducing rate: " << *node().rateMax;
node().rateMax = xferRate;
}
}
// Amount that goes to the taker.
STAmount saOutPassReq = std::min (
std::min (node().saOfferFunds, node().saTakerGets),
saOutReq - saOutAct);
// Maximum out - assuming no out fees.
STAmount saOutPassAct = saOutPassReq;
// Amount charged to the offer owner.
//
// The fee goes to issuer. The fee is paid by offer owner and not passed
// as a cost to taker.
//
// Round down: prefer liquidity rather than microscopic fees.
STAmount saOutPlusFees = multiplyRound (
saOutPassAct, xferRate, false);
// Offer out with fees.
JLOG (j_.trace())
<< "deliverNodeReverse:"
<< " saOutReq=" << saOutReq
<< " saOutAct=" << saOutAct
<< " node().saTakerGets=" << node().saTakerGets
<< " saOutPassAct=" << saOutPassAct
<< " saOutPlusFees=" << saOutPlusFees
<< " node().saOfferFunds=" << node().saOfferFunds;
if (saOutPlusFees > node().saOfferFunds)
{
// Offer owner can not cover all fees, compute saOutPassAct based on
// node().saOfferFunds.
saOutPlusFees = node().saOfferFunds;
// Round up: prefer liquidity rather than microscopic fees. But,
// limit by requested.
auto fee = divideRound (saOutPlusFees, xferRate, true);
saOutPassAct = std::min (saOutPassReq, fee);
JLOG (j_.trace())
<< "deliverNodeReverse: Total exceeds fees:"
<< " saOutPassAct=" << saOutPassAct
<< " saOutPlusFees=" << saOutPlusFees
<< " node().saOfferFunds=" << node().saOfferFunds;
}
// Compute portion of input needed to cover actual output.
auto outputFee = mulRound (
saOutPassAct, node().saOfrRate, node().saTakerPays.issue (), true);
STAmount saInPassReq = std::min (node().saTakerPays, outputFee);
STAmount saInPassAct;
JLOG (j_.trace())
<< "deliverNodeReverse:"
<< " outputFee=" << outputFee
<< " saInPassReq=" << saInPassReq
<< " node().saOfrRate=" << node().saOfrRate
<< " saOutPassAct=" << saOutPassAct
<< " saOutPlusFees=" << saOutPlusFees;
if (!saInPassReq) // FIXME: This is bogus
{
// After rounding did not want anything.
JLOG (j_.debug())
<< "deliverNodeReverse: micro offer is unfunded.";
node().bEntryAdvance = true;
continue;
}
// Find out input amount actually available at current rate.
else if (!isXRP(previousNode().account_))
{
// account --> OFFER --> ?
// Due to node expansion, previous is guaranteed to be the issuer.
//
// Previous is the issuer and receiver is an offer, so no fee or
// quality.
//
// Previous is the issuer and has unlimited funds.
//
// Offer owner is obtaining IOUs via an offer, so credit line limits
// are ignored. As limits are ignored, don't need to adjust
// previous account's balance.
saInPassAct = saInPassReq;
JLOG (j_.trace())
<< "deliverNodeReverse: account --> OFFER --> ? :"
<< " saInPassAct=" << saInPassAct;
}
else
{
// offer --> OFFER --> ?
// Compute in previous offer node how much could come in.
// TODO(tom): Fix nasty recursion here!
resultCode = increment(-1).deliverNodeReverseImpl(
node().offerOwnerAccount_,
saInPassReq,
saInPassAct,
saOutAct > beast::zero);
// The recursive call is dry this time, but we have liquidity
// from previous calls
if (resultCode == tecPATH_DRY && saOutAct > beast::zero)
{
resultCode = tesSUCCESS;
break;
}
JLOG (j_.trace())
<< "deliverNodeReverse: offer --> OFFER --> ? :"
<< " saInPassAct=" << saInPassAct;
}
if (resultCode != tesSUCCESS)
break;
if (saInPassAct < saInPassReq)
{
// Adjust output to conform to limited input.
auto outputRequirements = divRound (saInPassAct, node ().saOfrRate,
node ().saTakerGets.issue (), true);
saOutPassAct = std::min (saOutPassReq, outputRequirements);
auto outputFees = multiplyRound (saOutPassAct, xferRate, true);
saOutPlusFees = std::min (node().saOfferFunds, outputFees);
JLOG (j_.trace())
<< "deliverNodeReverse: adjusted:"
<< " saOutPassAct=" << saOutPassAct
<< " saOutPlusFees=" << saOutPlusFees;
}
else
{
// TODO(tom): more logging here.
assert (saInPassAct == saInPassReq);
}
// Funds were spent.
node().bFundsDirty = true;
// Want to deduct output to limit calculations while computing reverse.
// Don't actually need to send.
//
// Sending could be complicated: could fund a previous offer not yet
// visited. However, these deductions and adjustments are tenative.
//
// Must reset balances when going forward to perform actual transfers.
resultCode = accountSend(view(),
node().offerOwnerAccount_, node().issue_.account, saOutPassAct, viewJ);
if (resultCode != tesSUCCESS)
break;
// Adjust offer
STAmount saTakerGetsNew = node().saTakerGets - saOutPassAct;
STAmount saTakerPaysNew = node().saTakerPays - saInPassAct;
if (saTakerPaysNew < beast::zero || saTakerGetsNew < beast::zero)
{
JLOG (j_.warn())
<< "deliverNodeReverse: NEGATIVE:"
<< " node().saTakerPaysNew=" << saTakerPaysNew
<< " node().saTakerGetsNew=" << saTakerGetsNew;
resultCode = telFAILED_PROCESSING;
break;
}
node().sleOffer->setFieldAmount (sfTakerGets, saTakerGetsNew);
node().sleOffer->setFieldAmount (sfTakerPays, saTakerPaysNew);
view().update (node().sleOffer);
if (saOutPassAct == node().saTakerGets)
{
// Offer became unfunded.
JLOG (j_.debug())
<< "deliverNodeReverse: offer became unfunded.";
node().bEntryAdvance = true;
// XXX When don't we want to set advance?
}
else
{
assert (saOutPassAct < node().saTakerGets);
}
saOutAct += saOutPassAct;
// Accumulate what is to be delivered from previous node.
previousNode().saRevDeliver += saInPassAct;
}
if (saOutAct > saOutReq)
{
JLOG (j_.warn())
<< "deliverNodeReverse: TOO MUCH:"
<< " saOutAct=" << saOutAct
<< " saOutReq=" << saOutReq;
}
assert(saOutAct <= saOutReq);
if (resultCode == tesSUCCESS && !saOutAct)
resultCode = tecPATH_DRY;
// Unable to meet request, consider path dry.
// Design invariant: if nothing was actually delivered, return tecPATH_DRY.
JLOG (j_.trace())
<< "deliverNodeReverse<"
<< " saOutAct=" << saOutAct
<< " saOutReq=" << saOutReq
<< " saPrvDlvReq=" << previousNode().saRevDeliver;
return resultCode;
}
TER PathCursor::deliverNodeReverse (
AccountID const& uOutAccountID, // --> Output owner's account.
STAmount const& saOutReq, // --> Funds requested to be
// delivered for an increment.
STAmount& saOutAct // <-- Funds actually delivered for an
// increment
) const
{
for (int i = nodeIndex_; i >= 0 && !node (i).isAccount(); --i)
node (i).directory.restart (multiQuality_);
return deliverNodeReverseImpl(uOutAccountID, saOutReq, saOutAct, /*callerHasLiquidity*/false);
}
} // path
} // ripple

View File

@@ -1,48 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/app/paths/cursor/EffectiveRate.h>
#include <ripple/basics/contract.h>
namespace ripple {
namespace path {
Rate
effectiveRate(
Issue const& issue,
AccountID const& account1,
AccountID const& account2,
boost::optional<Rate> const& rate)
{
// 1:1 transfer rate for XRP
if (isXRP (issue))
return parityRate;
if (!rate)
LogicError ("No transfer rate set for node.");
// 1:1 transfer rate if either of the accounts is the issuer
if (issue.account == account1 || issue.account == account2)
return parityRate;
return rate.get();
}
} // path
} // ripple

View File

@@ -1,41 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_APP_PATHS_CURSOR_EFFECTIVERATE_H_INCLUDED
#define RIPPLE_APP_PATHS_CURSOR_EFFECTIVERATE_H_INCLUDED
#include <ripple/protocol/AccountID.h>
#include <ripple/protocol/Issue.h>
#include <ripple/protocol/Rate.h>
#include <boost/optional.hpp>
namespace ripple {
namespace path {
Rate
effectiveRate(
Issue const& issue,
AccountID const& account1,
AccountID const& account2,
boost::optional<Rate> const& rate);
} // path
} // ripple
#endif

View File

@@ -1,64 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/app/paths/cursor/PathCursor.h>
#include <ripple/basics/Log.h>
#include <tuple>
namespace ripple {
namespace path {
TER PathCursor::forwardLiquidity () const
{
if (node().isAccount())
return forwardLiquidityForAccount ();
// Otherwise, node is an offer.
if (previousNode().account_ == beast::zero)
return tesSUCCESS;
// Previous is an account node, resolve its deliver.
STAmount saInAct;
STAmount saInFees;
auto resultCode = deliverNodeForward (
previousNode().account_,
previousNode().saFwdDeliver, // Previous is sending this much.
saInAct,
saInFees,
false);
assert (resultCode != tesSUCCESS ||
previousNode().saFwdDeliver == saInAct + saInFees);
return resultCode;
}
} // path
} // ripple
// Original comments:
// Called to drive the from the first offer node in a chain.
//
// - Offer input is in issuer/limbo.
// - Current offers consumed.
// - Current offer owners debited.
// - Transfer fees credited to issuer.
// - Payout to issuer or limbo.
// - Deliver is set without transfer fees.

View File

@@ -1,501 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/app/paths/cursor/RippleLiquidity.h>
#include <ripple/basics/Log.h>
#include <ripple/protocol/Quality.h>
namespace ripple {
namespace path {
// The reverse pass has been narrowing by credit available and inflating by fees
// as it worked backwards. Now, for the current account node, take the actual
// amount from previous and adjust forward balances.
//
// Perform balance adjustments between previous and current node.
// - The previous node: specifies what to push through to current.
// - All of previous output is consumed.
//
// Then, compute current node's output for next node.
// - Current node: specify what to push through to next.
// - Output to next node is computed as input minus quality or transfer fee.
// - If next node is an offer and output is non-XRP then we are the issuer and
// do not need to push funds.
// - If next node is an offer and output is XRP then we need to deliver funds to
// limbo.
TER PathCursor::forwardLiquidityForAccount () const
{
TER resultCode = tesSUCCESS;
auto const lastNodeIndex = pathState_.nodes().size () - 1;
auto viewJ = rippleCalc_.logs_.journal ("View");
std::uint64_t uRateMax = 0;
AccountID const& previousAccountID =
previousNode().isAccount() ? previousNode().account_ :
node().account_;
// Offers are always issue.
AccountID const& nextAccountID =
nextNode().isAccount() ? nextNode().account_ : node().account_;
auto const qualityIn = nodeIndex_
? quality_in (view(),
node().account_,
previousAccountID,
node().issue_.currency)
: parityRate;
auto const qualityOut = (nodeIndex_ == lastNodeIndex)
? quality_out (view(),
node().account_,
nextAccountID,
node().issue_.currency)
: parityRate;
// When looking backward (prv) for req we care about what we just
// calculated: use fwd.
// When looking forward (cur) for req we care about what was desired: use
// rev.
// For nextNode().isAccount()
auto saPrvRedeemAct = previousNode().saFwdRedeem.zeroed();
auto saPrvIssueAct = previousNode().saFwdIssue.zeroed();
// For !previousNode().isAccount()
auto saPrvDeliverAct = previousNode().saFwdDeliver.zeroed ();
JLOG (j_.trace())
<< "forwardLiquidityForAccount> "
<< "nodeIndex_=" << nodeIndex_ << "/" << lastNodeIndex
<< " previousNode.saFwdRedeem:" << previousNode().saFwdRedeem
<< " saPrvIssueReq:" << previousNode().saFwdIssue
<< " previousNode.saFwdDeliver:" << previousNode().saFwdDeliver
<< " node.saRevRedeem:" << node().saRevRedeem
<< " node.saRevIssue:" << node().saRevIssue
<< " node.saRevDeliver:" << node().saRevDeliver;
// Ripple through account.
if (previousNode().isAccount() && nextNode().isAccount())
{
// Next is an account, must be rippling.
if (!nodeIndex_)
{
// ^ --> ACCOUNT --> account
// For the first node, calculate amount to ripple based on what is
// available.
node().saFwdRedeem = node().saRevRedeem;
if (pathState_.inReq() >= beast::zero)
{
// Limit by send max.
node().saFwdRedeem = std::min (
node().saFwdRedeem, pathState_.inReq() - pathState_.inAct());
}
pathState_.setInPass (node().saFwdRedeem);
node().saFwdIssue = node().saFwdRedeem == node().saRevRedeem
// Fully redeemed.
? node().saRevIssue : STAmount (node().saRevIssue);
if (node().saFwdIssue && pathState_.inReq() >= beast::zero)
{
// Limit by send max.
node().saFwdIssue = std::min (
node().saFwdIssue,
pathState_.inReq() - pathState_.inAct() - node().saFwdRedeem);
}
pathState_.setInPass (pathState_.inPass() + node().saFwdIssue);
JLOG (j_.trace())
<< "forwardLiquidityForAccount: ^ --> "
<< "ACCOUNT --> account :"
<< " saInReq=" << pathState_.inReq()
<< " saInAct=" << pathState_.inAct()
<< " node.saFwdRedeem:" << node().saFwdRedeem
<< " node.saRevIssue:" << node().saRevIssue
<< " node.saFwdIssue:" << node().saFwdIssue
<< " pathState_.saInPass:" << pathState_.inPass();
}
else if (nodeIndex_ == lastNodeIndex)
{
// account --> ACCOUNT --> $
JLOG (j_.trace())
<< "forwardLiquidityForAccount: account --> "
<< "ACCOUNT --> $ :"
<< " previousAccountID="
<< to_string (previousAccountID)
<< " node.account_="
<< to_string (node().account_)
<< " previousNode.saFwdRedeem:" << previousNode().saFwdRedeem
<< " previousNode.saFwdIssue:" << previousNode().saFwdIssue;
// Last node. Accept all funds. Calculate amount actually to credit.
auto& saCurReceive = pathState_.outPass();
STAmount saIssueCrd = qualityIn >= parityRate
? previousNode().saFwdIssue // No fee.
: multiplyRound (
previousNode().saFwdIssue,
qualityIn,
true); // Amount to credit.
// Amount to credit. Credit for less than received as a surcharge.
pathState_.setOutPass (previousNode().saFwdRedeem + saIssueCrd);
if (saCurReceive)
{
// Actually receive.
resultCode = rippleCredit(view(),
previousAccountID,
node().account_,
previousNode().saFwdRedeem + previousNode().saFwdIssue,
false, viewJ);
}
else
{
// After applying quality, total payment was microscopic.
resultCode = tecPATH_DRY;
}
}
else
{
// account --> ACCOUNT --> account
JLOG (j_.trace())
<< "forwardLiquidityForAccount: account --> "
<< "ACCOUNT --> account";
node().saFwdRedeem.clear (node().saRevRedeem);
node().saFwdIssue.clear (node().saRevIssue);
// Previous redeem part 1: redeem -> redeem
if (previousNode().saFwdRedeem && node().saRevRedeem)
// Previous wants to redeem.
{
// Rate : 1.0 : quality out
rippleLiquidity (
rippleCalc_,
parityRate,
qualityOut,
previousNode().saFwdRedeem,
node().saRevRedeem,
saPrvRedeemAct,
node().saFwdRedeem,
uRateMax);
}
// Previous issue part 1: issue -> redeem
if (previousNode().saFwdIssue != saPrvIssueAct
// Previous wants to issue.
&& node().saRevRedeem != node().saFwdRedeem)
// Current has more to redeem to next.
{
// Rate: quality in : quality out
rippleLiquidity (
rippleCalc_,
qualityIn,
qualityOut,
previousNode().saFwdIssue,
node().saRevRedeem,
saPrvIssueAct,
node().saFwdRedeem,
uRateMax);
}
// Previous redeem part 2: redeem -> issue.
if (previousNode().saFwdRedeem != saPrvRedeemAct
// Previous still wants to redeem.
&& node().saRevRedeem == node().saFwdRedeem
// Current redeeming is done can issue.
&& node().saRevIssue)
// Current wants to issue.
{
// Rate : 1.0 : transfer_rate
rippleLiquidity (
rippleCalc_,
parityRate,
transferRate (view(), node().account_),
previousNode().saFwdRedeem,
node().saRevIssue,
saPrvRedeemAct,
node().saFwdIssue,
uRateMax);
}
// Previous issue part 2 : issue -> issue
if (previousNode().saFwdIssue != saPrvIssueAct
// Previous wants to issue.
&& node().saRevRedeem == node().saFwdRedeem
// Current redeeming is done can issue.
&& node().saRevIssue)
// Current wants to issue.
{
// Rate: quality in : 1.0
rippleLiquidity (
rippleCalc_,
qualityIn,
parityRate,
previousNode().saFwdIssue,
node().saRevIssue,
saPrvIssueAct,
node().saFwdIssue,
uRateMax);
}
STAmount saProvide = node().saFwdRedeem + node().saFwdIssue;
// Adjust prv --> cur balance : take all inbound
resultCode = saProvide
? rippleCredit(view(),
previousAccountID,
node().account_,
previousNode().saFwdRedeem + previousNode().saFwdIssue,
false, viewJ)
: tecPATH_DRY;
}
}
else if (previousNode().isAccount() && !nextNode().isAccount())
{
// Current account is issuer to next offer.
// Determine deliver to offer amount.
// Don't adjust outbound balances- keep funds with issuer as limbo.
// If issuer hold's an offer owners inbound IOUs, there is no fee and
// redeem/issue will transparently happen.
if (nodeIndex_)
{
// Non-XRP, current node is the issuer.
JLOG (j_.trace())
<< "forwardLiquidityForAccount: account --> "
<< "ACCOUNT --> offer";
node().saFwdDeliver.clear (node().saRevDeliver);
// redeem -> issue/deliver.
// Previous wants to redeem.
// Current is issuing to an offer so leave funds in account as
// "limbo".
if (previousNode().saFwdRedeem)
// Previous wants to redeem.
{
// Rate : 1.0 : transfer_rate
// XXX Is having the transfer rate here correct?
rippleLiquidity (
rippleCalc_,
parityRate,
transferRate (view(), node().account_),
previousNode().saFwdRedeem,
node().saRevDeliver,
saPrvRedeemAct,
node().saFwdDeliver,
uRateMax);
}
// issue -> issue/deliver
if (previousNode().saFwdRedeem == saPrvRedeemAct
// Previous done redeeming: Previous has no IOUs.
&& previousNode().saFwdIssue)
// Previous wants to issue. To next must be ok.
{
// Rate: quality in : 1.0
rippleLiquidity (
rippleCalc_,
qualityIn,
parityRate,
previousNode().saFwdIssue,
node().saRevDeliver,
saPrvIssueAct,
node().saFwdDeliver,
uRateMax);
}
// Adjust prv --> cur balance : take all inbound
resultCode = node().saFwdDeliver
? rippleCredit(view(),
previousAccountID, node().account_,
previousNode().saFwdRedeem + previousNode().saFwdIssue,
false, viewJ)
: tecPATH_DRY; // Didn't actually deliver anything.
}
else
{
// Delivering amount requested from downstream.
node().saFwdDeliver = node().saRevDeliver;
// If limited, then limit by send max and available.
if (pathState_.inReq() >= beast::zero)
{
// Limit by send max.
node().saFwdDeliver = std::min (
node().saFwdDeliver, pathState_.inReq() - pathState_.inAct());
// Limit XRP by available. No limit for non-XRP as issuer.
if (isXRP (node().issue_))
node().saFwdDeliver = std::min (
node().saFwdDeliver,
accountHolds(view(),
node().account_,
xrpCurrency(),
xrpAccount(),
fhIGNORE_FREEZE, viewJ)); // XRP can't be frozen
}
// Record amount sent for pass.
pathState_.setInPass (node().saFwdDeliver);
if (!node().saFwdDeliver)
{
resultCode = tecPATH_DRY;
}
else if (!isXRP (node().issue_))
{
// Non-XRP, current node is the issuer.
// We could be delivering to multiple accounts, so we don't know
// which ripple balance will be adjusted. Assume just issuing.
JLOG (j_.trace())
<< "forwardLiquidityForAccount: ^ --> "
<< "ACCOUNT -- !XRP --> offer";
// As the issuer, would only issue.
// Don't need to actually deliver. As from delivering leave in
// the issuer as limbo.
}
else
{
JLOG (j_.trace())
<< "forwardLiquidityForAccount: ^ --> "
<< "ACCOUNT -- XRP --> offer";
// Deliver XRP to limbo.
resultCode = accountSend(view(),
node().account_, xrpAccount(), node().saFwdDeliver, viewJ);
}
}
}
else if (!previousNode().isAccount() && nextNode().isAccount())
{
if (nodeIndex_ == lastNodeIndex)
{
// offer --> ACCOUNT --> $
JLOG (j_.trace())
<< "forwardLiquidityForAccount: offer --> "
<< "ACCOUNT --> $ : "
<< previousNode().saFwdDeliver;
// Amount to credit.
pathState_.setOutPass (previousNode().saFwdDeliver);
// No income balance adjustments necessary. The paying side inside
// the offer paid to this account.
}
else
{
// offer --> ACCOUNT --> account
JLOG (j_.trace())
<< "forwardLiquidityForAccount: offer --> "
<< "ACCOUNT --> account";
node().saFwdRedeem.clear (node().saRevRedeem);
node().saFwdIssue.clear (node().saRevIssue);
// deliver -> redeem
if (previousNode().saFwdDeliver && node().saRevRedeem)
// Previous wants to deliver and can current redeem.
{
// Rate : 1.0 : quality out
rippleLiquidity (
rippleCalc_,
parityRate,
qualityOut,
previousNode().saFwdDeliver,
node().saRevRedeem,
saPrvDeliverAct,
node().saFwdRedeem,
uRateMax);
}
// deliver -> issue
// Wants to redeem and current would and can issue.
if (previousNode().saFwdDeliver != saPrvDeliverAct
// Previous still wants to deliver.
&& node().saRevRedeem == node().saFwdRedeem
// Current has more to redeem to next.
&& node().saRevIssue)
// Current wants issue.
{
// Rate : 1.0 : transfer_rate
rippleLiquidity (
rippleCalc_,
parityRate,
transferRate (view(), node().account_),
previousNode().saFwdDeliver,
node().saRevIssue,
saPrvDeliverAct,
node().saFwdIssue,
uRateMax);
}
// No income balance adjustments necessary. The paying side inside
// the offer paid and the next link will receive.
STAmount saProvide = node().saFwdRedeem + node().saFwdIssue;
if (!saProvide)
resultCode = tecPATH_DRY;
}
}
else
{
// offer --> ACCOUNT --> offer
// deliver/redeem -> deliver/issue.
JLOG (j_.trace())
<< "forwardLiquidityForAccount: offer --> ACCOUNT --> offer";
node().saFwdDeliver.clear (node().saRevDeliver);
if (previousNode().saFwdDeliver && node().saRevDeliver)
{
// Rate : 1.0 : transfer_rate
rippleLiquidity (
rippleCalc_,
parityRate,
transferRate (view(), node().account_),
previousNode().saFwdDeliver,
node().saRevDeliver,
saPrvDeliverAct,
node().saFwdDeliver,
uRateMax);
}
// No income balance adjustments necessary. The paying side inside the
// offer paid and the next link will receive.
if (!node().saFwdDeliver)
resultCode = tecPATH_DRY;
}
return resultCode;
}
} // path
} // ripple

View File

@@ -1,87 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/app/paths/cursor/RippleLiquidity.h>
#include <ripple/basics/Log.h>
#include <tuple>
namespace ripple {
namespace path {
TER PathCursor::liquidity () const
{
TER resultCode = tecPATH_DRY;
PathCursor pc = *this;
pathState_.resetView (rippleCalc_.view);
for (pc.nodeIndex_ = pc.nodeSize(); pc.nodeIndex_--; )
{
JLOG (j_.trace())
<< "reverseLiquidity>"
<< " nodeIndex=" << pc.nodeIndex_
<< ".issue_.account=" << to_string (pc.node().issue_.account);
resultCode = pc.reverseLiquidity();
if (!pc.node().transferRate_)
return tefINTERNAL;
JLOG (j_.trace())
<< "reverseLiquidity< "
<< "nodeIndex=" << pc.nodeIndex_
<< " resultCode=" << transToken (resultCode)
<< " transferRate_=" << *pc.node().transferRate_
<< ": " << resultCode;
if (resultCode != tesSUCCESS)
break;
}
// VFALCO-FIXME this generates errors
// JLOG (j_.trace())
// << "nextIncrement: Path after reverse: " << pathState_.getJson ();
if (resultCode != tesSUCCESS)
return resultCode;
pathState_.resetView (rippleCalc_.view);
for (pc.nodeIndex_ = 0; pc.nodeIndex_ < pc.nodeSize(); ++pc.nodeIndex_)
{
JLOG (j_.trace())
<< "forwardLiquidity> nodeIndex=" << nodeIndex_;
resultCode = pc.forwardLiquidity();
if (resultCode != tesSUCCESS)
return resultCode;
JLOG (j_.trace())
<< "forwardLiquidity<"
<< " nodeIndex:" << pc.nodeIndex_
<< " resultCode:" << resultCode;
if (pathState_.isDry())
resultCode = tecPATH_DRY;
}
return resultCode;
}
} // path
} // ripple

View File

@@ -1,67 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/app/paths/cursor/RippleLiquidity.h>
#include <ripple/basics/contract.h>
#include <ripple/basics/Log.h>
namespace ripple {
namespace path {
// Calculate the next increment of a path.
//
// The increment is what can satisfy a portion or all of the requested output at
// the best quality.
//
// <-- pathState.uQuality
//
// This is the wrapper that restores a checkpointed version of the ledger so we
// can write all over it without consequence.
void PathCursor::nextIncrement () const
{
// The next state is what is available in preference order.
// This is calculated when referenced accounts changed.
auto status = liquidity();
if (status == tesSUCCESS)
{
if (pathState_.isDry())
{
JLOG (j_.debug())
<< "nextIncrement: success on dry path:"
<< " outPass=" << pathState_.outPass()
<< " inPass=" << pathState_.inPass();
Throw<std::runtime_error> ("Made no progress.");
}
// Calculate relative quality.
pathState_.setQuality(getRate (
pathState_.outPass(), pathState_.inPass()));
}
else
{
pathState_.setQuality(0);
}
pathState_.setStatus (status);
}
} // path
} // ripple

View File

@@ -1,151 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_APP_PATHS_CURSOR_PATHCURSOR_H_INCLUDED
#define RIPPLE_APP_PATHS_CURSOR_PATHCURSOR_H_INCLUDED
#include <ripple/app/paths/RippleCalc.h>
namespace ripple {
namespace path {
// The next section contains methods that compute the liquidity along a path,
// either backward or forward.
//
// We need to do these computations twice - once backward to figure out the
// maximum possible liqiudity along a path, and then forward to compute the
// actual liquidity of the paths we actually chose.
//
// Some of these routines use recursion to loop over all nodes in a path.
// TODO(tom): replace this recursion with a loop.
class PathCursor
{
public:
PathCursor(
RippleCalc& rippleCalc,
PathState& pathState,
bool multiQuality,
beast::Journal j,
NodeIndex nodeIndex = 0)
: rippleCalc_(rippleCalc),
pathState_(pathState),
multiQuality_(multiQuality),
nodeIndex_(restrict(nodeIndex)),
j_ (j)
{
}
void nextIncrement() const;
private:
PathCursor(PathCursor const&) = default;
PathCursor increment(int delta = 1) const
{
return {rippleCalc_, pathState_, multiQuality_, j_, nodeIndex_ + delta};
}
TER liquidity() const;
TER reverseLiquidity () const;
TER forwardLiquidity () const;
TER forwardLiquidityForAccount () const;
TER reverseLiquidityForOffer () const;
TER forwardLiquidityForOffer () const;
TER reverseLiquidityForAccount () const;
// To send money out of an account.
/** advanceNode advances through offers in an order book.
If needed, advance to next funded offer.
- Automatically advances to first offer.
--> bEntryAdvance: true, to advance to next entry. false, recalculate.
<-- uOfferIndex : 0=end of list.
*/
TER advanceNode (bool reverse) const;
TER advanceNode (STAmount const& amount, bool reverse, bool callerHasLiquidity) const;
// To deliver from an order book, when computing
TER deliverNodeReverse (
AccountID const& uOutAccountID,
STAmount const& saOutReq,
STAmount& saOutAct) const;
// To deliver from an order book, when computing
TER deliverNodeReverseImpl (
AccountID const& uOutAccountID,
STAmount const& saOutReq,
STAmount& saOutAct,
bool callerHasLiquidity) const;
TER deliverNodeForward (
AccountID const& uInAccountID,
STAmount const& saInReq,
STAmount& saInAct,
STAmount& saInFees,
bool callerHasLiquidity) const;
// VFALCO TODO Rename this to view()
PaymentSandbox&
view() const
{
return pathState_.view();
}
NodeIndex nodeSize() const
{
return pathState_.nodes().size();
}
NodeIndex restrict(NodeIndex i) const
{
return std::min (i, nodeSize() - 1);
}
Node& node(NodeIndex i) const
{
return pathState_.nodes()[i];
}
Node& node() const
{
return node (nodeIndex_);
}
Node& previousNode() const
{
return node (restrict (nodeIndex_ - 1));
}
Node& nextNode() const
{
return node (restrict (nodeIndex_ + 1));
}
RippleCalc& rippleCalc_;
PathState& pathState_;
bool multiQuality_;
NodeIndex nodeIndex_;
beast::Journal const j_;
};
} // path
} // ripple
#endif

View File

@@ -1,92 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/app/paths/cursor/PathCursor.h>
#include <ripple/basics/Log.h>
#include <ripple/ledger/View.h>
#include <tuple>
namespace ripple {
namespace path {
// Calculate a node and its previous nodes. The eventual goal is to determine1
// how much input currency we need in the forward direction to satisfy the
// output.
//
// From the destination work in reverse towards the source calculating how much
// must be asked for. As we move backwards, individual nodes may further limit
// the amount of liquidity available.
//
// This is just a controlling loop that sets things up and then hands the work
// off to either reverseLiquidityForAccount or
// reverseLiquidityForOffer.
//
// Later on the result of this will be used to work forward, figuring out how
// much can actually be delivered.
//
// <-- resultCode: tesSUCCESS or tecPATH_DRY
// <-> pnNodes:
// --> [end]saWanted.mAmount
// --> [all]saWanted.mCurrency
// --> [all]saAccount
// <-> [0]saWanted.mAmount : --> limit, <-- actual
TER PathCursor::reverseLiquidity () const
{
// Every account has a transfer rate for its issuances.
// TOMOVE: The account charges
// a fee when third parties transfer that account's own issuances.
// Cache the output transfer rate for this node.
node().transferRate_ = transferRate (view(), node().issue_.account);
if (node().isAccount ())
return reverseLiquidityForAccount ();
// Otherwise the node is an Offer.
if (isXRP (nextNode().account_))
{
JLOG (j_.trace())
<< "reverseLiquidityForOffer: "
<< "OFFER --> offer: nodeIndex_=" << nodeIndex_;
return tesSUCCESS;
// This control structure ensures deliverNodeReverse is only called for the
// rightmost offer in a chain of offers - which means that
// deliverNodeReverse has to take all of those offers into consideration.
}
// Next is an account node, resolve current offer node's deliver.
STAmount saDeliverAct;
JLOG (j_.trace())
<< "reverseLiquidityForOffer: OFFER --> account:"
<< " nodeIndex_=" << nodeIndex_
<< " saRevDeliver=" << node().saRevDeliver;
// The next node wants the current node to deliver this much:
return deliverNodeReverse (
nextNode().account_,
node().saRevDeliver,
saDeliverAct);
}
} // path
} // ripple

View File

@@ -1,599 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/app/paths/Credit.h>
#include <ripple/app/paths/cursor/RippleLiquidity.h>
#include <ripple/basics/Log.h>
#include <ripple/protocol/Quality.h>
namespace ripple {
namespace path {
// Calculate saPrvRedeemReq, saPrvIssueReq, saPrvDeliver from saCur, based on
// required deliverable, propagate redeem, issue (for accounts) and deliver
// requests (for order books) to the previous node.
//
// Inflate amount requested by required fees.
// Reedems are limited based on IOUs previous has on hand.
// Issues are limited based on credit limits and amount owed.
//
// Currency cannot be XRP because we are rippling.
//
// No permanent account balance adjustments as we don't know how much is going
// to actually be pushed through yet - changes are only in the scratch pad
// ledger.
//
// <-- tesSUCCESS or tecPATH_DRY
TER PathCursor::reverseLiquidityForAccount () const
{
TER terResult = tesSUCCESS;
auto const lastNodeIndex = nodeSize () - 1;
auto const isFinalNode = (nodeIndex_ == lastNodeIndex);
// 0 quality means none has yet been determined.
std::uint64_t uRateMax = 0;
// Current is allowed to redeem to next.
const bool previousNodeIsAccount = !nodeIndex_ ||
previousNode().isAccount();
const bool nextNodeIsAccount = isFinalNode || nextNode().isAccount();
AccountID const& previousAccountID = previousNodeIsAccount
? previousNode().account_ : node().account_;
AccountID const& nextAccountID = nextNodeIsAccount ? nextNode().account_
: node().account_; // Offers are always issue.
// This is the quality from from the previous node to this one.
auto const qualityIn
= (nodeIndex_ != 0)
? quality_in (view(),
node().account_,
previousAccountID,
node().issue_.currency)
: parityRate;
// And this is the quality from the next one to this one.
auto const qualityOut
= (nodeIndex_ != lastNodeIndex)
? quality_out (view(),
node().account_,
nextAccountID,
node().issue_.currency)
: parityRate;
// For previousNodeIsAccount:
// Previous account is already owed.
const STAmount saPrvOwed = (previousNodeIsAccount && nodeIndex_ != 0)
? creditBalance (view(),
node().account_,
previousAccountID,
node().issue_.currency)
: STAmount (node().issue_);
// The limit amount that the previous account may owe.
const STAmount saPrvLimit = (previousNodeIsAccount && nodeIndex_ != 0)
? creditLimit (view(),
node().account_,
previousAccountID,
node().issue_.currency)
: STAmount (node().issue_);
// Next account is owed.
const STAmount saNxtOwed = (nextNodeIsAccount && nodeIndex_ != lastNodeIndex)
? creditBalance (view(),
node().account_,
nextAccountID,
node().issue_.currency)
: STAmount (node().issue_);
JLOG (j_.trace())
<< "reverseLiquidityForAccount>"
<< " nodeIndex_=" << nodeIndex_ << "/" << lastNodeIndex
<< " previousAccountID=" << previousAccountID
<< " node.account_=" << node().account_
<< " nextAccountID=" << nextAccountID
<< " currency=" << node().issue_.currency
<< " qualityIn=" << qualityIn
<< " qualityOut=" << qualityOut
<< " saPrvOwed=" << saPrvOwed
<< " saPrvLimit=" << saPrvLimit;
// Requests are computed to be the maximum flow possible.
// Previous can redeem the owed IOUs it holds.
const STAmount saPrvRedeemReq = (saPrvOwed > beast::zero)
? saPrvOwed
: STAmount (saPrvOwed.issue ());
// Previous can issue up to limit minus whatever portion of limit already
// used (not including redeemable amount) - another "maximum flow".
const STAmount saPrvIssueReq = (saPrvOwed < beast::zero)
? saPrvLimit + saPrvOwed : saPrvLimit;
// Precompute these values in case we have an order book.
auto deliverCurrency = previousNode().saRevDeliver.getCurrency ();
const STAmount saPrvDeliverReq (
{deliverCurrency, previousNode().saRevDeliver.getIssuer ()}, -1);
// -1 means unlimited delivery.
// Set to zero, because we're trying to hit the previous node.
auto saCurRedeemAct = node().saRevRedeem.zeroed();
// Track the amount we actually redeem.
auto saCurIssueAct = node().saRevIssue.zeroed();
// For !nextNodeIsAccount
auto saCurDeliverAct = node().saRevDeliver.zeroed();
JLOG (j_.trace())
<< "reverseLiquidityForAccount:"
<< " saPrvRedeemReq:" << saPrvRedeemReq
<< " saPrvIssueReq:" << saPrvIssueReq
<< " previousNode.saRevDeliver:" << previousNode().saRevDeliver
<< " saPrvDeliverReq:" << saPrvDeliverReq
<< " node.saRevRedeem:" << node().saRevRedeem
<< " node.saRevIssue:" << node().saRevIssue
<< " saNxtOwed:" << saNxtOwed;
// VFALCO-FIXME this generates errors
//JLOG (j_.trace()) << pathState_.getJson ();
// Current redeem req can't be more than IOUs on hand.
assert (!node().saRevRedeem || -saNxtOwed >= node().saRevRedeem);
assert (!node().saRevIssue // If not issuing, fine.
|| saNxtOwed >= beast::zero
// saNxtOwed >= 0: Sender not holding next IOUs, saNxtOwed < 0:
// Sender holding next IOUs.
|| -saNxtOwed == node().saRevRedeem);
// If issue req, then redeem req must consume all owed.
if (nodeIndex_ == 0)
{
// ^ --> ACCOUNT --> account|offer
// Nothing to do, there is no previous to adjust.
//
// TODO(tom): we could have skipped all that setup and just left
// or even just never call this whole routine on nodeIndex_ = 0!
}
// The next four cases correspond to the table at the bottom of this Wiki
// page section: https://ripple.com/wiki/Transit_Fees#Implementation
else if (previousNodeIsAccount && nextNodeIsAccount)
{
if (isFinalNode)
{
// account --> ACCOUNT --> $
// Overall deliverable.
const STAmount saCurWantedReq = std::min (
pathState_.outReq() - pathState_.outAct(),
saPrvLimit + saPrvOwed);
auto saCurWantedAct = saCurWantedReq.zeroed ();
JLOG (j_.trace())
<< "reverseLiquidityForAccount: account --> "
<< "ACCOUNT --> $ :"
<< " saCurWantedReq=" << saCurWantedReq;
// Calculate redeem
if (saPrvRedeemReq) // Previous has IOUs to redeem.
{
// Redeem your own IOUs at 1:1
saCurWantedAct = std::min (saPrvRedeemReq, saCurWantedReq);
previousNode().saRevRedeem = saCurWantedAct;
uRateMax = STAmount::uRateOne;
JLOG (j_.trace())
<< "reverseLiquidityForAccount: Redeem at 1:1"
<< " saPrvRedeemReq=" << saPrvRedeemReq
<< " (available) previousNode.saRevRedeem="
<< previousNode().saRevRedeem
<< " uRateMax="
<< amountFromQuality (uRateMax).getText ();
}
else
{
previousNode().saRevRedeem.clear (saPrvRedeemReq);
}
// Calculate issuing.
previousNode().saRevIssue.clear (saPrvIssueReq);
if (saCurWantedReq != saCurWantedAct // Need more.
&& saPrvIssueReq) // Will accept IOUs from previous.
{
// Rate: quality in : 1.0
// If we previously redeemed and this has a poorer rate, this
// won't be included the current increment.
rippleLiquidity (
rippleCalc_,
qualityIn,
parityRate,
saPrvIssueReq,
saCurWantedReq,
previousNode().saRevIssue,
saCurWantedAct,
uRateMax);
JLOG (j_.trace())
<< "reverseLiquidityForAccount: Issuing: Rate: "
<< "quality in : 1.0"
<< " previousNode.saRevIssue:" << previousNode().saRevIssue
<< " saCurWantedAct:" << saCurWantedAct;
}
if (!saCurWantedAct)
{
// Must have processed something.
terResult = tecPATH_DRY;
}
}
else
{
// Not final node.
// account --> ACCOUNT --> account
previousNode().saRevRedeem.clear (saPrvRedeemReq);
previousNode().saRevIssue.clear (saPrvIssueReq);
// redeem (part 1) -> redeem
if (node().saRevRedeem
// Next wants IOUs redeemed from current account.
&& saPrvRedeemReq)
// Previous has IOUs to redeem to the current account.
{
// TODO(tom): add English.
// Rate : 1.0 : quality out - we must accept our own IOUs
// as 1:1.
rippleLiquidity (
rippleCalc_,
parityRate,
qualityOut,
saPrvRedeemReq,
node().saRevRedeem,
previousNode().saRevRedeem,
saCurRedeemAct,
uRateMax);
JLOG (j_.trace())
<< "reverseLiquidityForAccount: "
<< "Rate : 1.0 : quality out"
<< " previousNode.saRevRedeem:" << previousNode().saRevRedeem
<< " saCurRedeemAct:" << saCurRedeemAct;
}
// issue (part 1) -> redeem
if (node().saRevRedeem != saCurRedeemAct
// The current node has more IOUs to redeem.
&& previousNode().saRevRedeem == saPrvRedeemReq)
// The previous node has no IOUs to redeem remaining, so issues.
{
// Rate: quality in : quality out
rippleLiquidity (
rippleCalc_,
qualityIn,
qualityOut,
saPrvIssueReq,
node().saRevRedeem,
previousNode().saRevIssue,
saCurRedeemAct,
uRateMax);
JLOG (j_.trace())
<< "reverseLiquidityForAccount: "
<< "Rate: quality in : quality out:"
<< " previousNode.saRevIssue:" << previousNode().saRevIssue
<< " saCurRedeemAct:" << saCurRedeemAct;
}
// redeem (part 2) -> issue.
if (node().saRevIssue // Next wants IOUs issued.
// TODO(tom): this condition seems redundant.
&& saCurRedeemAct == node().saRevRedeem
// Can only issue if completed redeeming.
&& previousNode().saRevRedeem != saPrvRedeemReq)
// Did not complete redeeming previous IOUs.
{
// Rate : 1.0 : transfer_rate
rippleLiquidity (
rippleCalc_,
parityRate,
transferRate (view(), node().account_),
saPrvRedeemReq,
node().saRevIssue,
previousNode().saRevRedeem,
saCurIssueAct,
uRateMax);
JLOG (j_.debug())
<< "reverseLiquidityForAccount: "
<< "Rate : 1.0 : transfer_rate:"
<< " previousNode.saRevRedeem:" << previousNode().saRevRedeem
<< " saCurIssueAct:" << saCurIssueAct;
}
// issue (part 2) -> issue
if (node().saRevIssue != saCurIssueAct
// Need wants more IOUs issued.
&& saCurRedeemAct == node().saRevRedeem
// Can only issue if completed redeeming.
&& saPrvRedeemReq == previousNode().saRevRedeem
// Previously redeemed all owed IOUs.
&& saPrvIssueReq)
// Previous can issue.
{
// Rate: quality in : 1.0
rippleLiquidity (
rippleCalc_,
qualityIn,
parityRate,
saPrvIssueReq,
node().saRevIssue,
previousNode().saRevIssue,
saCurIssueAct,
uRateMax);
JLOG (j_.trace())
<< "reverseLiquidityForAccount: "
<< "Rate: quality in : 1.0:"
<< " previousNode.saRevIssue:" << previousNode().saRevIssue
<< " saCurIssueAct:" << saCurIssueAct;
}
if (!saCurRedeemAct && !saCurIssueAct)
{
// Did not make progress.
terResult = tecPATH_DRY;
}
JLOG (j_.trace())
<< "reverseLiquidityForAccount: "
<< "^|account --> ACCOUNT --> account :"
<< " node.saRevRedeem:" << node().saRevRedeem
<< " node.saRevIssue:" << node().saRevIssue
<< " saPrvOwed:" << saPrvOwed
<< " saCurRedeemAct:" << saCurRedeemAct
<< " saCurIssueAct:" << saCurIssueAct;
}
}
else if (previousNodeIsAccount && !nextNodeIsAccount)
{
// account --> ACCOUNT --> offer
// Note: deliver is always issue as ACCOUNT is the issuer for the offer
// input.
JLOG (j_.trace())
<< "reverseLiquidityForAccount: "
<< "account --> ACCOUNT --> offer";
previousNode().saRevRedeem.clear (saPrvRedeemReq);
previousNode().saRevIssue.clear (saPrvIssueReq);
// We have three cases: the nxt offer can be owned by current account,
// previous account or some third party account.
//
// Also, the current account may or may not have a redeemable balance
// with the account for the next offer, so we don't yet know if we're
// redeeming or issuing.
//
// TODO(tom): Make sure deliver was cleared, or check actual is zero.
// redeem -> deliver/issue.
if (saPrvOwed > beast::zero // Previous has IOUs to redeem.
&& node().saRevDeliver) // Need some issued.
{
// Rate : 1.0 : transfer_rate
rippleLiquidity (
rippleCalc_,
parityRate,
transferRate (view(), node().account_),
saPrvRedeemReq,
node().saRevDeliver,
previousNode().saRevRedeem,
saCurDeliverAct,
uRateMax);
}
// issue -> deliver/issue
if (saPrvRedeemReq == previousNode().saRevRedeem
// Previously redeemed all owed.
&& node().saRevDeliver != saCurDeliverAct) // Still need some issued.
{
// Rate: quality in : 1.0
rippleLiquidity (
rippleCalc_,
qualityIn,
parityRate,
saPrvIssueReq,
node().saRevDeliver,
previousNode().saRevIssue,
saCurDeliverAct,
uRateMax);
}
if (!saCurDeliverAct)
{
// Must want something.
terResult = tecPATH_DRY;
}
JLOG (j_.trace())
<< "reverseLiquidityForAccount: "
<< " node.saRevDeliver:" << node().saRevDeliver
<< " saCurDeliverAct:" << saCurDeliverAct
<< " saPrvOwed:" << saPrvOwed;
}
else if (!previousNodeIsAccount && nextNodeIsAccount)
{
if (isFinalNode)
{
// offer --> ACCOUNT --> $
// Previous is an offer, no limit: redeem own IOUs.
//
// This is the final node; we can't look to the right to get values;
// we have to go up to get the out value for the entire path state.
STAmount const& saCurWantedReq =
pathState_.outReq() - pathState_.outAct();
STAmount saCurWantedAct = saCurWantedReq.zeroed();
JLOG (j_.trace())
<< "reverseLiquidityForAccount: "
<< "offer --> ACCOUNT --> $ :"
<< " saCurWantedReq:" << saCurWantedReq
<< " saOutAct:" << pathState_.outAct()
<< " saOutReq:" << pathState_.outReq();
if (saCurWantedReq <= beast::zero)
{
assert(false);
JLOG (j_.fatal()) << "CurWantReq was not positive";
return tefEXCEPTION;
}
// The previous node is an offer; we are receiving our own currency.
// The previous order book's entries might hold our issuances; might
// not hold our issuances; might be our own offer.
//
// Assume the worst case, the case which costs the most to go
// through, which is that it is not our own offer or our own
// issuances. Later on the forward pass we may be able to do
// better.
//
// TODO: this comment applies generally to this section - move it up
// to a document.
// Rate: quality in : 1.0
rippleLiquidity (
rippleCalc_,
qualityIn,
parityRate,
saPrvDeliverReq,
saCurWantedReq,
previousNode().saRevDeliver,
saCurWantedAct,
uRateMax);
if (!saCurWantedAct)
{
// Must have processed something.
terResult = tecPATH_DRY;
}
JLOG (j_.trace())
<< "reverseLiquidityForAccount:"
<< " previousNode().saRevDeliver:" << previousNode().saRevDeliver
<< " saPrvDeliverReq:" << saPrvDeliverReq
<< " saCurWantedAct:" << saCurWantedAct
<< " saCurWantedReq:" << saCurWantedReq;
}
else
{
// offer --> ACCOUNT --> account
// Note: offer is always delivering(redeeming) as account is issuer.
JLOG (j_.trace())
<< "reverseLiquidityForAccount: "
<< "offer --> ACCOUNT --> account :"
<< " node.saRevRedeem:" << node().saRevRedeem
<< " node.saRevIssue:" << node().saRevIssue;
// deliver -> redeem
// TODO(tom): now we have more checking in nodeRipple, these checks
// might be redundant.
if (node().saRevRedeem) // Next wants us to redeem.
{
// cur holds IOUs from the account to the right, the nxt
// account. If someone is making the current account get rid of
// the nxt account's IOUs, then charge the input for quality
// out.
//
// Rate : 1.0 : quality out
rippleLiquidity (
rippleCalc_,
parityRate,
qualityOut,
saPrvDeliverReq,
node().saRevRedeem,
previousNode().saRevDeliver,
saCurRedeemAct,
uRateMax);
}
// deliver -> issue.
if (node().saRevRedeem == saCurRedeemAct
// Can only issue if previously redeemed all.
&& node().saRevIssue)
// Need some issued.
{
// Rate : 1.0 : transfer_rate
rippleLiquidity (
rippleCalc_,
parityRate,
transferRate (view(), node().account_),
saPrvDeliverReq,
node().saRevIssue,
previousNode().saRevDeliver,
saCurIssueAct,
uRateMax);
}
JLOG (j_.trace())
<< "reverseLiquidityForAccount:"
<< " saCurRedeemAct:" << saCurRedeemAct
<< " node.saRevRedeem:" << node().saRevRedeem
<< " previousNode.saRevDeliver:" << previousNode().saRevDeliver
<< " node.saRevIssue:" << node().saRevIssue;
if (!previousNode().saRevDeliver)
{
// Must want something.
terResult = tecPATH_DRY;
}
}
}
else
{
// offer --> ACCOUNT --> offer
// deliver/redeem -> deliver/issue.
JLOG (j_.trace())
<< "reverseLiquidityForAccount: offer --> ACCOUNT --> offer";
// Rate : 1.0 : transfer_rate
rippleLiquidity (
rippleCalc_,
parityRate,
transferRate (view(), node().account_),
saPrvDeliverReq,
node().saRevDeliver,
previousNode().saRevDeliver,
saCurDeliverAct,
uRateMax);
if (!saCurDeliverAct)
{
// Must want something.
terResult = tecPATH_DRY;
}
}
return terResult;
}
} // path
} // ripple

View File

@@ -1,250 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/protocol/Quality.h>
#include <ripple/app/paths/cursor/RippleLiquidity.h>
#include <ripple/basics/Log.h>
namespace ripple {
namespace path {
// Compute how much might flow for the node for the pass. Does not actually
// adjust balances.
//
// uQualityIn -> uQualityOut
// saPrvReq -> saCurReq
// sqPrvAct -> saCurAct
//
// This is a minimizing routine: moving in reverse it propagates the send limit
// to the sender, moving forward it propagates the actual send toward the
// receiver.
//
// When this routine works backwards, saCurReq is the driving variable: it
// calculates previous wants based on previous credit limits and current wants.
//
// When this routine works forwards, saPrvReq is the driving variable: it
// calculates current deliver based on previous delivery limits and current
// wants.
//
// This routine is called one or two times for a node in a pass. If called once,
// it will work and set a rate. If called again, the new work must not worsen
// the previous rate.
void rippleLiquidity (
RippleCalc& rippleCalc,
Rate const& qualityIn,
Rate const& qualityOut,
STAmount const& saPrvReq, // --> in limit including fees, <0 = unlimited
STAmount const& saCurReq, // --> out limit
STAmount& saPrvAct, // <-> in limit including achieved so far: <-- <= -->
STAmount& saCurAct, // <-> out limit including achieved so far: <-- <= -->
std::uint64_t& uRateMax)
{
JLOG (rippleCalc.j_.trace())
<< "rippleLiquidity>"
<< " qualityIn=" << qualityIn
<< " qualityOut=" << qualityOut
<< " saPrvReq=" << saPrvReq
<< " saCurReq=" << saCurReq
<< " saPrvAct=" << saPrvAct
<< " saCurAct=" << saCurAct;
// saCurAct was once beast::zero in a production server.
assert (saCurReq != beast::zero);
assert (saCurReq > beast::zero);
assert (saPrvReq.getCurrency () == saCurReq.getCurrency ());
assert (saPrvReq.getCurrency () == saPrvAct.getCurrency ());
assert (saPrvReq.getIssuer () == saPrvAct.getIssuer ());
const bool bPrvUnlimited = (saPrvReq < beast::zero); // -1 means unlimited.
// Unlimited stays unlimited - don't do calculations.
// How much could possibly flow through the previous node?
const STAmount saPrv = bPrvUnlimited ? saPrvReq : saPrvReq - saPrvAct;
// How much could possibly flow through the current node?
const STAmount saCur = saCurReq - saCurAct;
JLOG (rippleCalc.j_.trace())
<< "rippleLiquidity: "
<< " bPrvUnlimited=" << bPrvUnlimited
<< " saPrv=" << saPrv
<< " saCur=" << saCur;
// If nothing can flow, we might as well not do any work.
if (saPrv == beast::zero || saCur == beast::zero)
return;
if (qualityIn >= qualityOut)
{
// You're getting better quality than you asked for, so no fee.
JLOG (rippleCalc.j_.trace()) << "rippleLiquidity: No fees";
// Only process if the current rate, 1:1, is not worse than the previous
// rate, uRateMax - otherwise there is no flow.
if (!uRateMax || STAmount::uRateOne <= uRateMax)
{
// Limit amount to transfer if need - the minimum of amount being
// paid and the amount that's wanted.
STAmount saTransfer = bPrvUnlimited ? saCur
: std::min (saPrv, saCur);
// In reverse, we want to propagate the limited cur to prv and set
// actual cur.
//
// In forward, we want to propagate the limited prv to cur and set
// actual prv.
//
// This is the actual flow.
saPrvAct += saTransfer;
saCurAct += saTransfer;
// If no rate limit, set rate limit to avoid combining with
// something with a worse rate.
if (uRateMax == 0)
uRateMax = STAmount::uRateOne;
}
}
else
{
// If the quality is worse than the previous
JLOG (rippleCalc.j_.trace()) << "rippleLiquidity: Fee";
std::uint64_t const uRate = getRate (
STAmount (qualityOut.value),
STAmount (qualityIn.value));
// If the next rate is at least as good as the current rate, process.
if (!uRateMax || uRate <= uRateMax)
{
// current actual = current request * (quality out / quality in).
auto n = multiplyRound (saCur, qualityOut, true);
// True means "round up" to get best flow.
STAmount saCurIn = divideRound (n, qualityIn, true);
JLOG (rippleCalc.j_.trace())
<< "rippleLiquidity:"
<< " bPrvUnlimited=" << bPrvUnlimited
<< " saPrv=" << saPrv
<< " saCurIn=" << saCurIn;
if (bPrvUnlimited || saCurIn <= saPrv)
{
// All of current. Some amount of previous.
saCurAct += saCur;
saPrvAct += saCurIn;
JLOG (rippleCalc.j_.trace())
<< "rippleLiquidity:3c:"
<< " saCurReq=" << saCurReq
<< " saPrvAct=" << saPrvAct;
}
else
{
// There wasn't enough money to start with - so given the
// limited input, how much could we deliver?
// current actual = previous request
// * (quality in / quality out).
// This is inverted compared to the code above because we're
// going the other way
auto numerator = multiplyRound (saPrv,
qualityIn, saCur.issue(), true);
// A part of current. All of previous. (Cur is the driver
// variable.)
STAmount saCurOut = divideRound (numerator,
qualityOut, saCur.issue(), true);
JLOG (rippleCalc.j_.trace())
<< "rippleLiquidity:4: saCurReq=" << saCurReq;
saCurAct += saCurOut;
saPrvAct = saPrvReq;
}
if (!uRateMax)
uRateMax = uRate;
}
}
JLOG (rippleCalc.j_.trace())
<< "rippleLiquidity<"
<< " qualityIn=" << qualityIn
<< " qualityOut=" << qualityOut
<< " saPrvReq=" << saPrvReq
<< " saCurReq=" << saCurReq
<< " saPrvAct=" << saPrvAct
<< " saCurAct=" << saCurAct;
}
static
Rate
rippleQuality (
ReadView const& view,
AccountID const& destination,
AccountID const& source,
Currency const& currency,
SField const& sfLow,
SField const& sfHigh)
{
if (destination == source)
return parityRate;
auto const& sfField = destination < source ? sfLow : sfHigh;
auto const sle = view.read(
keylet::line(destination, source, currency));
if (!sle || !sle->isFieldPresent (sfField))
return parityRate;
auto quality = sle->getFieldU32 (sfField);
// Avoid divide by zero. NIKB CHECKME: if we
// allow zero qualities now, then we shouldn't.
if (quality == 0)
quality = 1;
return Rate{ quality };
}
Rate
quality_in (
ReadView const& view,
AccountID const& uToAccountID,
AccountID const& uFromAccountID,
Currency const& currency)
{
return rippleQuality (view, uToAccountID, uFromAccountID, currency,
sfLowQualityIn, sfHighQualityIn);
}
Rate
quality_out (
ReadView const& view,
AccountID const& uToAccountID,
AccountID const& uFromAccountID,
Currency const& currency)
{
return rippleQuality (view, uToAccountID, uFromAccountID, currency,
sfLowQualityOut, sfHighQualityOut);
}
} // path
} // ripple

View File

@@ -1,59 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_APP_PATHS_CURSOR_RIPPLELIQUIDITY_H_INCLUDED
#define RIPPLE_APP_PATHS_CURSOR_RIPPLELIQUIDITY_H_INCLUDED
#include <ripple/app/paths/cursor/PathCursor.h>
#include <ripple/app/paths/RippleCalc.h>
#include <ripple/app/paths/Tuning.h>
#include <ripple/ledger/View.h>
#include <ripple/protocol/Rate.h>
namespace ripple {
namespace path {
void rippleLiquidity (
RippleCalc&,
Rate const& qualityIn,
Rate const& qualityOut,
STAmount const& saPrvReq,
STAmount const& saCurReq,
STAmount& saPrvAct,
STAmount& saCurAct,
std::uint64_t& uRateMax);
Rate
quality_in (
ReadView const& view,
AccountID const& uToAccountID,
AccountID const& uFromAccountID,
Currency const& currency);
Rate
quality_out (
ReadView const& view,
AccountID const& uToAccountID,
AccountID const& uFromAccountID,
Currency const& currency);
} // path
} // ripple
#endif

View File

@@ -299,7 +299,7 @@ rippleCredit (ApplyView& view,
const STAmount & saAmount, bool bCheckIssuer,
beast::Journal j);
// [[nodiscard]] // nodiscard commented out so DeliverNodeForward.cpp compiles.
[[nodiscard]]
TER
accountSend (ApplyView& view,
AccountID const& from,

View File

@@ -78,7 +78,7 @@ class FeatureCollections
"OwnerPaysFee",
"CompareFlowV1V2",
"PayChan",
"Flow",
"Flow", // Unconditionally supported.
"CompareTakerFlowCross",
"FlowCross",
"CryptoConditions",

View File

@@ -177,7 +177,7 @@ JSS ( converge_time_s ); // out: NetworkOPs
JSS ( count ); // in: AccountTx*, ValidatorList
JSS ( counters ); // in/out: retrieve counters
JSS ( currency ); // in: paths/PathRequest, STAmount
// out: paths/Node, STPathSet, STAmount,
// out: STPathSet, STAmount,
// AccountLines
JSS ( current ); // out: OwnerInfo
JSS ( current_activities );
@@ -236,7 +236,7 @@ JSS ( fetch_pack ); // out: NetworkOPs
JSS ( first ); // out: rpc/Version
JSS ( finished );
JSS ( fix_txns ); // in: LedgerCleaner
JSS ( flags ); // out: paths/Node, AccountOffers,
JSS ( flags ); // out: AccountOffers,
// NetworkOPs
JSS ( forward ); // in: AccountTx
JSS ( freeze ); // out: AccountLines
@@ -262,7 +262,7 @@ JSS ( ident ); // in: AccountCurrencies, AccountInfo,
JSS ( inLedger ); // out: tx/Transaction
JSS ( inbound ); // out: PeerImp
JSS ( index ); // in: LedgerEntry, DownloadShard
// out: PathState, STLedgerEntry,
// out: STLedgerEntry,
// LedgerEntry, TxHistory, LedgerData
// field
JSS ( info ); // out: ServerInfo, ConsensusInfo, FetchInfo
@@ -273,7 +273,7 @@ JSS ( io_latency_ms ); // out: NetworkOPs
JSS ( ip ); // in: Connect, out: OverlayImpl
JSS ( issuer ); // in: RipplePathFind, Subscribe,
// Unsubscribe, BookOffers
// out: paths/Node, STPathSet, STAmount
// out: STPathSet, STAmount
JSS ( job );
JSS ( job_queue );
JSS ( jobs );
@@ -373,7 +373,6 @@ JSS ( node_reads_hit ); // out: GetCounts
JSS ( node_reads_total ); // out: GetCounts
JSS ( node_writes ); // out: GetCounts
JSS ( node_written_bytes ); // out: GetCounts
JSS ( nodes ); // out: PathState
JSS ( obligations ); // out: GatewayBalances
JSS ( offer ); // in: LedgerEntry
JSS ( offers ); // out: NetworkOPs, AccountOffers, Subscribe
@@ -532,7 +531,7 @@ JSS ( txn_count ); // out: NetworkOPs
JSS ( txs ); // out: TxHistory
JSS ( type ); // in: AccountObjects
// out: NetworkOPs
// paths/Node.cpp, OverlayImpl, Logic
// OverlayImpl, Logic
JSS ( type_hex ); // out: STPathSet
JSS ( unl ); // out: UnlList
JSS ( unlimited); // out: Connection.h

View File

@@ -22,10 +22,8 @@
#include <ripple/app/paths/AccountCurrencies.cpp>
#include <ripple/app/paths/Credit.cpp>
#include <ripple/app/paths/Pathfinder.cpp>
#include <ripple/app/paths/Node.cpp>
#include <ripple/app/paths/PathRequest.cpp>
#include <ripple/app/paths/PathRequests.cpp>
#include <ripple/app/paths/PathState.cpp>
#include <ripple/app/paths/RippleCalc.cpp>
#include <ripple/app/paths/RippleLineCache.cpp>
#include <ripple/app/paths/Flow.cpp>
@@ -33,15 +31,3 @@
#include <ripple/app/paths/impl/DirectStep.cpp>
#include <ripple/app/paths/impl/BookStep.cpp>
#include <ripple/app/paths/impl/XRPEndpointStep.cpp>
#include <ripple/app/paths/cursor/AdvanceNode.cpp>
#include <ripple/app/paths/cursor/DeliverNodeForward.cpp>
#include <ripple/app/paths/cursor/DeliverNodeReverse.cpp>
#include <ripple/app/paths/cursor/EffectiveRate.cpp>
#include <ripple/app/paths/cursor/ForwardLiquidity.cpp>
#include <ripple/app/paths/cursor/ForwardLiquidityForAccount.cpp>
#include <ripple/app/paths/cursor/Liquidity.cpp>
#include <ripple/app/paths/cursor/NextIncrement.cpp>
#include <ripple/app/paths/cursor/ReverseLiquidity.cpp>
#include <ripple/app/paths/cursor/ReverseLiquidityForAccount.cpp>
#include <ripple/app/paths/cursor/RippleLiquidity.cpp>

View File

@@ -528,10 +528,9 @@ public:
};
using namespace jtx;
auto const sa = supported_amendments();
testAll(sa - featureFlow - fix1373 - featureFlowCross);
testAll(sa - fix1373 - featureFlowCross);
testAll(sa - featureFlowCross);
testAll(sa );
testAll(sa - fix1373 - featureFlowCross);
testAll(sa - featureFlowCross);
testAll(sa );
}
};

View File

@@ -112,9 +112,8 @@ public:
{
using namespace jtx;
auto const sa = supported_amendments();
test_convert_all_of_an_asset(sa - featureFlow - fix1373 - featureFlowCross);
test_convert_all_of_an_asset(sa - fix1373 - featureFlowCross);
test_convert_all_of_an_asset(sa - featureFlowCross);
test_convert_all_of_an_asset(sa - featureFlowCross);
test_convert_all_of_an_asset(sa);
}
};

View File

@@ -146,9 +146,8 @@ public:
{
using namespace test::jtx;
auto const sa = supported_amendments();
testXRPDiscrepancy (sa - featureFlow - fix1373 - featureFlowCross);
testXRPDiscrepancy (sa - fix1373 - featureFlowCross);
testXRPDiscrepancy (sa - featureFlowCross);
testXRPDiscrepancy (sa - fix1373 - featureFlowCross);
testXRPDiscrepancy (sa - featureFlowCross);
testXRPDiscrepancy (sa);
}
};

View File

@@ -212,9 +212,6 @@ struct Flow_test : public beast::unit_test::suite
for (auto bobDanQIn : {80, 100, 120})
for (auto bobAliceQOut : {80, 100, 120})
{
if (!features[featureFlow] && bobDanQIn < 100 &&
bobAliceQOut < 100)
continue; // Bug in flow v1
Env env(*this, features);
env.fund(XRP(10000), alice, bob, carol, dan);
env(trust(bob, USDD(100)), qualityInPercent(bobDanQIn));
@@ -728,20 +725,21 @@ struct Flow_test : public beast::unit_test::suite
Account const bob ("bob");
Account const carol ("carol");
Env env (*this, FeatureBitset{});
{
Env env (*this, supported_amendments());
env.fund (XRP(10000), alice, bob, carol, gw);
env.fund (XRP(10000), alice, bob, carol, gw);
env.trust (USD(100), alice, bob, carol);
env (pay (gw, bob, USD (100)));
env (offer (bob, XRP (50), USD (50)));
env (offer (bob, XRP (100), USD (50)));
env.trust (USD(100), alice, bob, carol);
env (pay (gw, bob, USD (100)));
env (offer (bob, XRP (50), USD (50)));
env (offer (bob, XRP (100), USD (50)));
env (pay (alice, carol, USD (100)), path (~USD), sendmax (XRP (100)),
txflags (tfNoRippleDirect | tfPartialPayment | tfLimitQuality),
ter (tesSUCCESS));
env (pay (alice, carol, USD (100)), path (~USD), sendmax (XRP (100)),
txflags (tfNoRippleDirect | tfPartialPayment | tfLimitQuality));
env.require (balance (carol, USD (50)));
env.require (balance (carol, USD (50)));
}
}
// Helper function that returns the reserve on an account based on
@@ -1189,9 +1187,6 @@ struct Flow_test : public beast::unit_test::suite
testLineQuality(features);
testFalseDry(features);
// Only do the rest of the tests if featureFlow is enabled.
if (!features[featureFlow])
return;
testDirectStep(features);
testBookStep(features);
testDirectStep(features | ownerPaysFee);
@@ -1214,9 +1209,8 @@ struct Flow_test : public beast::unit_test::suite
using namespace jtx;
auto const sa = supported_amendments();
testWithFeats(sa - featureFlow - fix1373 - featureFlowCross);
testWithFeats(sa - fix1373 - featureFlowCross);
testWithFeats(sa - featureFlowCross);
testWithFeats(sa - fix1373 - featureFlowCross);
testWithFeats(sa - featureFlowCross);
testWithFeats(sa);
testEmptyStrand(sa);
}
@@ -1228,22 +1222,19 @@ struct Flow_manual_test : public Flow_test
{
using namespace jtx;
auto const all = supported_amendments();
FeatureBitset const flow{featureFlow};
FeatureBitset const f1373{fix1373};
FeatureBitset const flowCross{featureFlowCross};
FeatureBitset const f1513{fix1513};
testWithFeats(all - flow - f1373 - flowCross - f1513);
testWithFeats(all - flow - f1373 - flowCross );
testWithFeats(all - f1373 - flowCross - f1513);
testWithFeats(all - f1373 - flowCross );
testWithFeats(all - flowCross - f1513);
testWithFeats(all - flowCross );
testWithFeats(all - f1513);
testWithFeats(all );
testWithFeats(all - f1373 - flowCross - f1513);
testWithFeats(all - f1373 - flowCross );
testWithFeats(all - flowCross - f1513);
testWithFeats(all - flowCross );
testWithFeats(all - f1513);
testWithFeats(all );
testEmptyStrand(all - f1513);
testEmptyStrand(all );
testEmptyStrand(all - f1513);
testEmptyStrand(all );
}
};

View File

@@ -545,9 +545,8 @@ public:
};
using namespace test::jtx;
auto const sa = supported_amendments();
testAll(sa - featureFlow - fix1373 - featureFlowCross);
testAll(sa - fix1373 - featureFlowCross);
testAll(sa - featureFlowCross);
testAll(sa - fix1373 - featureFlowCross);
testAll(sa - featureFlowCross);
testAll(sa);
}
};

View File

@@ -263,7 +263,10 @@ public:
env (pay (gw, carol, EUR (100)));
// Create more offers than the loop max count in DeliverNodeReverse
for (int i=0;i<101;++i)
// Note: the DeliverNodeReverse code has been removed; however since
// this is a regression test the original test is being left as-is for
// now.
for (int i = 0; i < 101; ++i)
env (offer (carol, USD (1), EUR (2)));
env (pay (alice, bob, EUR (epsilon)), path (~EUR), sendmax (USD (100)));
@@ -3351,13 +3354,9 @@ public:
env (trust (ann, D_BUX(100)));
env.close();
// Determine which TEC code we expect.
TER const tecExpect =
features[featureFlow] ? TER {temBAD_PATH} : TER {tecPATH_DRY};
// This payment caused the assert.
env (pay (ann, ann, D_BUX(30)),
path (A_BUX, D_BUX), sendmax (A_BUX(30)), ter (tecExpect));
path (A_BUX, D_BUX), sendmax (A_BUX(30)), ter (temBAD_PATH));
env.close();
env.require (balance (ann, A_BUX(none)));
@@ -4617,28 +4616,21 @@ class Offer_manual_test : public Offer_test
{
using namespace jtx;
FeatureBitset const all{supported_amendments()};
FeatureBitset const flow{featureFlow};
FeatureBitset const f1373{fix1373};
FeatureBitset const flowCross{featureFlowCross};
FeatureBitset const f1513{fix1513};
FeatureBitset const takerDryOffer{fixTakerDryOfferRemoval};
testAll(all - flow - f1373 - flowCross - f1513);
testAll(all - flow - f1373 - flowCross );
testAll(all - flow - f1373 - f1513);
testAll(all - flow - f1373 );
testAll(all - f1373 - flowCross - f1513);
testAll(all - f1373 - flowCross );
testAll(all - f1373 - f1513);
testAll(all - f1373 );
testAll(all - flowCross - f1513);
testAll(all - flowCross );
testAll(all - f1513);
testAll(all );
testAll(all - f1373 - flowCross - f1513);
testAll(all - f1373 - flowCross );
testAll(all - f1373 - f1513);
testAll(all - f1373 );
testAll(all - flowCross - f1513);
testAll(all - flowCross );
testAll(all - f1513);
testAll(all );
testAll(all - flow - f1373 - flowCross - takerDryOffer);
testAll(all - flow - f1373 - takerDryOffer);
testAll(all - f1373 - flowCross - takerDryOffer);
testAll(all - f1373 - flowCross - takerDryOffer);
}
};

View File

@@ -1031,84 +1031,6 @@ public:
BEAST_EXPECT(same(st, stpath(G3, IPE(xrpIssue()))));
}
void path_find_03()
{
testcase("Path Find: CNY");
using namespace jtx;
Env env{*this,
supported_amendments().reset(featureFlow)};
Account A1 {"A1"};
Account A2 {"A2"};
Account A3 {"A3"};
Account SRC {"SRC"};
Account GATEWAY_DST {"GATEWAY_DST"};
Account MONEY_MAKER_1 {"MONEY_MAKER_1"};
Account MONEY_MAKER_2 {"MONEY_MAKER_2"};
env.fund(XRP(4999.999898), SRC);
env.fund(XRP(10846.168060), GATEWAY_DST);
env.fund(XRP(4291.430036), MONEY_MAKER_1);
env.fund(XRP(106839.375770), MONEY_MAKER_2);
env.fund(XRP(1240.997150), A1);
env.fund(XRP(14115.046893), A2);
env.fund(XRP(512087.883181), A3);
env.close();
env.trust(MONEY_MAKER_1["CNY"](1001), MONEY_MAKER_2);
env.trust(GATEWAY_DST["CNY"](1001), MONEY_MAKER_2);
env.trust(MONEY_MAKER_1["CNY"](1000000), A1);
env.trust(MONEY_MAKER_1["BTC"](10000), A1);
env.trust(GATEWAY_DST["USD"](1000), A1);
env.trust(GATEWAY_DST["CNY"](1000), A1);
env.trust(MONEY_MAKER_1["CNY"](3000), A2);
env.trust(GATEWAY_DST["CNY"](3000), A2);
env.trust(MONEY_MAKER_1["CNY"](10000), A3);
env.trust(GATEWAY_DST["CNY"](10000), A3);
env.close();
env(pay(MONEY_MAKER_1, MONEY_MAKER_2,
STAmount{ MONEY_MAKER_1["CNY"].issue(), UINT64_C(3599), -13}));
env(pay(GATEWAY_DST, MONEY_MAKER_2,
GATEWAY_DST["CNY"](137.6852546843001)));
env(pay(MONEY_MAKER_1, A1,
STAmount{ MONEY_MAKER_1["CNY"].issue(), UINT64_C(119761), -13}));
env(pay(GATEWAY_DST, A1, GATEWAY_DST["CNY"](33.047994)));
env(pay(MONEY_MAKER_1, A2, MONEY_MAKER_1["CNY"](209.3081873019994)));
env(pay(GATEWAY_DST, A2, GATEWAY_DST["CNY"](694.6251706504019)));
env(pay(MONEY_MAKER_1, A3, MONEY_MAKER_1["CNY"](23.617050013581)));
env(pay(GATEWAY_DST, A3, GATEWAY_DST["CNY"](70.999614649799)));
env.close();
env(offer(MONEY_MAKER_2, XRP(1), GATEWAY_DST["CNY"](1)));
env(offer(MONEY_MAKER_2, GATEWAY_DST["CNY"](1), XRP(1)));
env(offer(MONEY_MAKER_2, GATEWAY_DST["CNY"](318000), XRP(53000)));
env(offer(MONEY_MAKER_2, XRP(209), MONEY_MAKER_2["CNY"](4.18)));
env(offer(MONEY_MAKER_2, MONEY_MAKER_1["CNY"](990000), XRP(10000)));
env(offer(MONEY_MAKER_2, MONEY_MAKER_1["CNY"](9990000), XRP(10000)));
env(offer(MONEY_MAKER_2, GATEWAY_DST["CNY"](8870000), XRP(10000)));
env(offer(MONEY_MAKER_2, XRP(232), MONEY_MAKER_2["CNY"](5.568)));
env(offer(A2, XRP(2000), MONEY_MAKER_1["CNY"](66.8)));
env(offer(A2, XRP(1200), GATEWAY_DST["CNY"](42)));
env(offer(A2, MONEY_MAKER_1["CNY"](43.2), XRP(900)));
env(offer(A3, MONEY_MAKER_1["CNY"](2240), XRP(50000)));
STPathSet st;
STAmount sa, da;
auto const& send_amt = GATEWAY_DST["CNY"](10.1);
std::tie(st, sa, da) = find_paths(env, SRC, GATEWAY_DST, send_amt,
boost::none, xrpCurrency());
BEAST_EXPECT(equal(da, send_amt));
BEAST_EXPECT(equal(sa, XRP(288.571429)));
BEAST_EXPECT(same(st,
stpath(IPE(MONEY_MAKER_1["CNY"]), MONEY_MAKER_1, A3),
stpath(IPE(MONEY_MAKER_1["CNY"]), MONEY_MAKER_1, MONEY_MAKER_2),
stpath(IPE(MONEY_MAKER_1["CNY"]), MONEY_MAKER_1, A2),
stpath(IPE(MONEY_MAKER_1["CNY"]), MONEY_MAKER_1, A1)
));
}
void path_find_04()
{
testcase("Path Find: Bitstamp and SnapSwap, liquidity with no offers");
@@ -1393,7 +1315,6 @@ public:
path_find_01();
path_find_02();
path_find_03();
path_find_04();
path_find_05();
path_find_06();

View File

@@ -622,237 +622,6 @@ struct ExistingElementPool
}
};
struct PayStrandAllPairs_test : public beast::unit_test::suite
{
// Test every combination of element type pairs on a path
void
testAllPairs(FeatureBitset features)
{
testcase("All pairs");
using namespace jtx;
using RippleCalc = ::ripple::path::RippleCalc;
ExistingElementPool eep;
Env env(*this, features);
eep.setupEnv(env, /*numAcc*/ 9, /*numCur*/ 6, boost::none);
env.close();
auto const src = eep.getAvailAccount();
auto const dst = eep.getAvailAccount();
RippleCalc::Input inputs;
inputs.defaultPathsAllowed = false;
auto callback = [&](
STAmount const& sendMax,
STAmount const& deliver,
std::vector<STPathElement> const& p) {
std::array<PaymentSandbox, 2> sbs{
{PaymentSandbox{env.current().get(), tapNONE},
PaymentSandbox{env.current().get(), tapNONE}}};
std::array<RippleCalc::Output, 2> rcOutputs;
// pay with both env1 and env2
// check all result and account balances match
// save results so can see if run out of funds or somesuch
STPathSet paths;
paths.emplace_back(p);
for (auto i = 0; i < 2; ++i)
{
if (i == 0)
env.app().config().features.insert(featureFlow);
else
env.app().config().features.erase(featureFlow);
try
{
rcOutputs[i] = RippleCalc::rippleCalculate(
sbs[i],
sendMax,
deliver,
dst,
src,
paths,
env.app().logs(),
&inputs);
}
catch (...)
{
this->fail();
}
}
// check combinations of src and dst currencies (inc xrp)
// Check the results
auto const terMatch = [&] {
if (rcOutputs[0].result() == rcOutputs[1].result())
return true;
// handle some know error code mismatches
if (p.empty() ||
!(rcOutputs[0].result() == temBAD_PATH ||
rcOutputs[0].result() == temBAD_PATH_LOOP))
return false;
if (rcOutputs[1].result() == temBAD_PATH)
return true;
if (rcOutputs[1].result() == terNO_LINE)
return true;
for (auto const& pe : p)
{
auto const t = pe.getNodeType();
if ((t & STPathElement::typeAccount) &&
t != STPathElement::typeAccount)
{
return true;
}
}
// xrp followed by offer that doesn't specify both currency and
// issuer (and currency is not xrp, if specifyed)
if (isXRP(sendMax) &&
!(p[0].hasCurrency() && isXRP(p[0].getCurrency())) &&
!(p[0].hasCurrency() && p[0].hasIssuer()))
{
return true;
}
for (size_t i = 0; i < p.size() - 1; ++i)
{
auto const tCur = p[i].getNodeType();
auto const tNext = p[i + 1].getNodeType();
if ((tCur & STPathElement::typeCurrency) &&
isXRP(p[i].getCurrency()) &&
(tNext & STPathElement::typeAccount) &&
!isXRP(p[i + 1].getAccountID()))
{
return true;
}
}
return false;
}();
this->BEAST_EXPECT(
terMatch && (rcOutputs[0].result() == tesSUCCESS ||
rcOutputs[0].result() == temBAD_PATH ||
rcOutputs[0].result() == temBAD_PATH_LOOP));
if (terMatch && rcOutputs[0].result() == tesSUCCESS)
this->BEAST_EXPECT(eep.checkBalances(sbs[0], sbs[1]));
};
std::vector<STPathElement> prefix;
std::vector<STPathElement> suffix;
for (auto const srcAmtIsXRP : {false, true})
{
for (auto const dstAmtIsXRP : {false, true})
{
for (auto const hasPrefix : {false, true})
{
ExistingElementPool::StateGuard esg{eep};
prefix.clear();
suffix.clear();
STAmount const sendMax{
srcAmtIsXRP ? xrpIssue() : Issue{eep.getAvailCurrency(),
eep.getAvailAccount()},
-1, // (-1 == no limit)
0};
STAmount const deliver{
dstAmtIsXRP ? xrpIssue() : Issue{eep.getAvailCurrency(),
eep.getAvailAccount()},
1,
0};
if (hasPrefix)
{
for(auto const e0IsAccount : {false, true})
{
for (auto const e1IsAccount : {false, true})
{
ExistingElementPool::StateGuard presg{eep};
prefix.clear();
auto pushElement =
[&prefix, &eep](bool isAccount) mutable {
if (isAccount)
prefix.emplace_back(
eep.getAvailAccount().id(),
boost::none,
boost::none);
else
prefix.emplace_back(
boost::none,
eep.getAvailCurrency(),
eep.getAvailAccount().id());
};
pushElement(e0IsAccount);
pushElement(e1IsAccount);
boost::optional<AccountID> existingAcc;
boost::optional<Currency> existingCur;
boost::optional<AccountID> existingIss;
if (e0IsAccount)
{
existingAcc = prefix[0].getAccountID();
}
else
{
existingIss = prefix[0].getIssuerID();
existingCur = prefix[0].getCurrency();
}
if (e1IsAccount)
{
if (!existingAcc)
existingAcc = prefix[1].getAccountID();
}
else
{
if (!existingIss)
existingIss = prefix[1].getIssuerID();
if (!existingCur)
existingCur = prefix[1].getCurrency();
}
eep.for_each_element_pair(
sendMax,
deliver,
prefix,
suffix,
existingAcc,
existingCur,
existingIss,
callback);
}
}
}
else
{
eep.for_each_element_pair(
sendMax,
deliver,
prefix,
suffix,
/*existingAcc*/ boost::none,
/*existingCur*/ boost::none,
/*existingIss*/ boost::none,
callback);
}
}
}
}
}
void
run() override
{
auto const sa = jtx::supported_amendments();
testAllPairs(sa - featureFlowCross);
testAllPairs(sa);
}
};
BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(PayStrandAllPairs, app, ripple, 12);
struct PayStrand_test : public beast::unit_test::suite
{
void
@@ -1379,7 +1148,7 @@ struct PayStrand_test : public beast::unit_test::suite
env(offer(bob, USD(100), XRP(100)), txflags(tfPassive));
auto const expectedResult = [&] () -> TER {
if (features[featureFlow] && !features[fix1373])
if (!features[fix1373])
return tesSUCCESS;
return temBAD_PATH_LOOP;
}();
@@ -1480,13 +1249,12 @@ struct PayStrand_test : public beast::unit_test::suite
testToStrand(sa - featureFlowCross);
testToStrand(sa);
testRIPD1373(sa - featureFlow - fix1373 - featureFlowCross);
testRIPD1373(sa - featureFlowCross);
testRIPD1373(sa - fix1373 - featureFlowCross);
testRIPD1373(sa - featureFlowCross);
testRIPD1373(sa);
testLoop(sa - featureFlow - fix1373 - featureFlowCross);
testLoop(sa - fix1373 - featureFlowCross);
testLoop(sa - featureFlowCross);
testLoop(sa - fix1373 - featureFlowCross);
testLoop(sa - featureFlowCross);
testLoop(sa);
testNoAccount(sa);

View File

@@ -70,7 +70,6 @@ struct SetAuth_test : public beast::unit_test::suite
{
using namespace jtx;
auto const sa = supported_amendments();
testAuth(sa - featureFlow - fix1373 - featureFlowCross);
testAuth(sa - fix1373 - featureFlowCross);
testAuth(sa - featureFlowCross);
testAuth(sa);

View File

@@ -510,9 +510,8 @@ public:
using namespace test::jtx;
auto const sa = supported_amendments();
testWithFeatures(sa - featureFlow - fix1373 - featureFlowCross);
testWithFeatures(sa - fix1373 - featureFlowCross);
testWithFeatures(sa -featureFlowCross);
testWithFeatures(sa - fix1373 - featureFlowCross);
testWithFeatures(sa -featureFlowCross);
testWithFeatures(sa);
}
};

View File

@@ -95,8 +95,8 @@ struct BookDirs_test : public beast::unit_test::suite
{
using namespace jtx;
auto const sa = supported_amendments();
test_bookdir(sa - featureFlow - fix1373 - featureFlowCross);
test_bookdir(sa - featureFlowCross);
test_bookdir(sa - fix1373 - featureFlowCross);
test_bookdir(sa - featureFlowCross);
test_bookdir(sa);
}
};

View File

@@ -127,13 +127,17 @@ class PaymentSandbox_test : public beast::unit_test::suite
auto const iss = USD_gw1.issue ();
auto const startingAmount = accountHolds (
av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
accountSend (av, gw1, alice, toCredit, j);
{
auto r = accountSend (av, gw1, alice, toCredit, j);
BEAST_EXPECT(r == tesSUCCESS);
}
BEAST_EXPECT(accountHolds (av, alice, iss.currency, iss.account,
fhIGNORE_FREEZE, j) ==
startingAmount + toCredit);
accountSend (av, alice, gw1, toDebit, j);
{
auto r = accountSend(av, alice, gw1, toDebit, j);
BEAST_EXPECT(r == tesSUCCESS);
}
BEAST_EXPECT(accountHolds (av, alice, iss.currency, iss.account,
fhIGNORE_FREEZE, j) ==
startingAmount + toCredit - toDebit);
@@ -167,12 +171,18 @@ class PaymentSandbox_test : public beast::unit_test::suite
auto const startingAmount = accountHolds (
pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
accountSend (pv, gw1, alice, toCredit, j);
{
auto r = accountSend (pv, gw1, alice, toCredit, j);
BEAST_EXPECT(r == tesSUCCESS);
}
BEAST_EXPECT(accountHolds (pv, alice, iss.currency, iss.account,
fhIGNORE_FREEZE, j) ==
startingAmount);
accountSend (pv, alice, gw1, toDebit, j);
{
auto r = accountSend (pv, alice, gw1, toDebit, j);
BEAST_EXPECT(r == tesSUCCESS);
}
BEAST_EXPECT(accountHolds (pv, alice, iss.currency, iss.account,
fhIGNORE_FREEZE, j) ==
startingAmount - toDebit);
@@ -232,7 +242,10 @@ class PaymentSandbox_test : public beast::unit_test::suite
auto const startingAmount = accountHolds (
pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
accountSend (pv, gw1, alice, toCredit, j);
{
auto r = accountSend (pv, gw1, alice, toCredit, j);
BEAST_EXPECT(r == tesSUCCESS);
}
BEAST_EXPECT(accountHolds (pv, alice, iss.currency, iss.account,
fhIGNORE_FREEZE, j) ==
startingAmount);
@@ -242,13 +255,19 @@ class PaymentSandbox_test : public beast::unit_test::suite
BEAST_EXPECT(accountHolds (pv2, alice, iss.currency, iss.account,
fhIGNORE_FREEZE, j) ==
startingAmount);
accountSend (pv2, gw1, alice, toCredit, j);
{
auto r = accountSend (pv2, gw1, alice, toCredit, j);
BEAST_EXPECT(r == tesSUCCESS);
}
BEAST_EXPECT(accountHolds (pv2, alice, iss.currency, iss.account,
fhIGNORE_FREEZE, j) ==
startingAmount);
}
accountSend (pv, alice, gw1, toDebit, j);
{
auto r = accountSend (pv, alice, gw1, toDebit, j);
BEAST_EXPECT(r == tesSUCCESS);
}
BEAST_EXPECT(accountHolds (pv, alice, iss.currency, iss.account,
fhIGNORE_FREEZE, j) ==
startingAmount - toDebit);
@@ -313,8 +332,14 @@ class PaymentSandbox_test : public beast::unit_test::suite
// to drop below the reserve. Make sure her funds are zero (there was a bug that
// caused her funds to become negative).
accountSend (sb, xrpAccount (), alice, XRP(100), env.journal);
accountSend (sb, alice, xrpAccount (), XRP(100), env.journal);
{
auto r = accountSend (sb, xrpAccount (), alice, XRP(100), env.journal);
BEAST_EXPECT(r == tesSUCCESS);
}
{
auto r = accountSend (sb, alice, xrpAccount (), XRP(100), env.journal);
BEAST_EXPECT(r == tesSUCCESS);
}
BEAST_EXPECT(
accountFundsXRP (sb, alice, env.journal) == beast::zero);
}
@@ -362,8 +387,8 @@ public:
};
using namespace jtx;
auto const sa = supported_amendments();
testAll(sa - featureFlow - fix1373 - featureFlowCross);
testAll(sa - featureFlowCross);
testAll(sa - fix1373 - featureFlowCross);
testAll(sa - featureFlowCross);
testAll(sa);
}
};

View File

@@ -154,8 +154,8 @@ public:
{
using namespace jtx;
auto const sa = supported_amendments();
testGWB(sa - featureFlow - fix1373 - featureFlowCross);
testGWB(sa - featureFlowCross);
testGWB(sa - fix1373 - featureFlowCross);
testGWB(sa - featureFlowCross);
testGWB(sa);
}
};

View File

@@ -269,9 +269,8 @@ public:
};
using namespace jtx;
auto const sa = supported_amendments();
withFeatsTests(sa - featureFlow - fix1373 - featureFlowCross);
withFeatsTests(sa - fix1373 - featureFlowCross);
withFeatsTests(sa - featureFlowCross);
withFeatsTests(sa - fix1373 - featureFlowCross);
withFeatsTests(sa - featureFlowCross);
withFeatsTests(sa);
}
};