mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-29 23:45:51 +00:00
Remove old payment code
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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,
|
||||
|
||||
@@ -78,7 +78,7 @@ class FeatureCollections
|
||||
"OwnerPaysFee",
|
||||
"CompareFlowV1V2",
|
||||
"PayChan",
|
||||
"Flow",
|
||||
"Flow", // Unconditionally supported.
|
||||
"CompareTakerFlowCross",
|
||||
"FlowCross",
|
||||
"CryptoConditions",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user