Files
xahaud/src/ripple/app/paths/cursor/DeliverNodeReverse.cpp
seelabs 999701e384 Fix underflow rounding issue:
Very small payment could fail when STAmount::mulRound underflowed
and returned zero, when it should have rounded up to the smallest
representable value.
2015-12-01 11:02:27 -08:00

384 lines
14 KiB
C++

//------------------------------------------------------------------------------
/*
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 <BeastConfig.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
) const
{
TER resultCode = tesSUCCESS;
STAmountCalcSwitchovers amountCalcSwitchovers (
rippleCalc_.view.info ().parentCloseTime);
// 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 != 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_.warning) << "loop count exceeded";
return telFAILED_PROCESSING;
}
resultCode = advanceNode (saOutAct, true);
// If needed, advance to next funded offer.
if (resultCode != tesSUCCESS || !node().offerIndex_)
// Error or out of offers.
break;
auto const hasFee = node().offerOwnerAccount_ == node().issue_.account
|| uOutAccountID == node().issue_.account;
// Issuer sending or receiving.
const STAmount saOutFeeRate = hasFee
? STAmount::saOne // No fee.
: node().transferRate_; // Transfer rate of issuer.
JLOG (j_.trace)
<< "deliverNodeReverse:"
<< " offerOwnerAccount_="
<< node().offerOwnerAccount_
<< " uOutAccountID="
<< uOutAccountID
<< " node().issue_.account="
<< node().issue_.account
<< " node().transferRate_=" << node().transferRate_
<< " saOutFeeRate=" << saOutFeeRate;
if (multiQuality_)
{
// In multi-quality mode, ignore rate.
}
else if (!node().saRateMax)
{
// Set initial rate.
node().saRateMax = saOutFeeRate;
JLOG (j_.trace)
<< "deliverNodeReverse: Set initial rate:"
<< " node().saRateMax=" << node().saRateMax
<< " saOutFeeRate=" << saOutFeeRate;
}
else if (saOutFeeRate > node().saRateMax)
{
// Offer exceeds initial rate.
JLOG (j_.trace)
<< "deliverNodeReverse: Offer exceeds initial rate:"
<< " node().saRateMax=" << node().saRateMax
<< " saOutFeeRate=" << saOutFeeRate;
break; // Done. Don't bother looking for smaller transferRates.
}
else if (saOutFeeRate < node().saRateMax)
{
// Reducing rate. Additional offers will only 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 saOutFeeRate, it would be wrong to add anything with
// a rate above saOutFeeRate.
//
// The rate would be reduced if the current offer was from the
// issuer and the previous offer wasn't.
node().saRateMax = saOutFeeRate;
JLOG (j_.trace)
<< "deliverNodeReverse: Reducing rate:"
<< " node().saRateMax=" << node().saRateMax;
}
// 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 = mulRound (
saOutPassAct, saOutFeeRate, saOutPassAct.issue (), false,
amountCalcSwitchovers);
// 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 = divRound (saOutPlusFees, saOutFeeRate,
saOutPlusFees.issue (), true, amountCalcSwitchovers);
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,
amountCalcSwitchovers);
if (!amountCalcSwitchovers.enableUnderflowFix () && !outputFee)
{
JLOG (j_.fatal)
<< "underflow computing outputFee "
<< "saOutPassAct: " << saOutPassAct
<< " saOfrRate: " << node ().saOfrRate;
return telFAILED_PROCESSING;
}
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);
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,
amountCalcSwitchovers);
saOutPassAct = std::min (saOutPassReq, outputRequirements);
auto outputFees = mulRound (
saOutPassAct, saOutFeeRate, saOutPassAct.issue (), true,
amountCalcSwitchovers);
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 < zero || saTakerGetsNew < zero)
{
JLOG (j_.warning)
<< "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;
}
CondLog (saOutAct > saOutReq, lsWARNING, RippleCalc)
<< "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);
}
} // path
} // ripple