Refactor the structure of RippleCalc

* Split code into multiple files
* Rename and move things
This commit is contained in:
Tom Swirly
2014-05-09 15:53:27 -04:00
committed by Vinnie Falco
parent 4b3e629dfd
commit 3ca9646329
22 changed files with 3242 additions and 2741 deletions

View File

@@ -0,0 +1,111 @@
//------------------------------------------------------------------------------
/*
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 <tuple>
#include "CalcState.h"
#include "Calculators.h"
#include "RippleCalc.h"
#include "Tuning.h"
namespace ripple {
TER calcNodeFwd (
RippleCalc& rippleCalc,
const unsigned int nodeIndex, PathState& pathState,
const bool bMultiQuality)
{
auto const& node = pathState.vpnNodes[nodeIndex];
auto const nodeIsAccount = isAccount(node);
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeFwd> nodeIndex=" << nodeIndex;
TER errorCode = nodeIsAccount
? calcNodeAccountFwd (rippleCalc, nodeIndex, pathState, bMultiQuality)
: calcNodeOfferFwd (rippleCalc, nodeIndex, pathState, bMultiQuality);
if (errorCode == tesSUCCESS && nodeIndex + 1 != pathState.vpnNodes.size ())
errorCode = calcNodeFwd (rippleCalc, nodeIndex + 1, pathState, bMultiQuality);
if (errorCode == tesSUCCESS && !(pathState.saInPass && pathState.saOutPass))
errorCode = tecPATH_DRY;
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeFwd<"
<< " nodeIndex:" << nodeIndex
<< " errorCode:" << errorCode;
return errorCode;
}
// Calculate a node and its previous nodes.
//
// From the destination work in reverse towards the source calculating how much
// must be asked for.
//
// Then work forward, figuring out how much can actually be delivered.
// <-- errorCode: tesSUCCESS or tecPATH_DRY
// <-> pnNodes:
// --> [end]saWanted.mAmount
// --> [all]saWanted.mCurrency
// --> [all]saAccount
// <-> [0]saWanted.mAmount : --> limit, <-- actual
TER calcNodeRev (
RippleCalc& rippleCalc,
const unsigned int nodeIndex, PathState& pathState,
const bool bMultiQuality)
{
PathState::Node& node = pathState.vpnNodes[nodeIndex];
bool const nodeIsAccount
= is_bit_set (node.uFlags, STPathElement::typeAccount);
TER errorCode;
node.saTransferRate = STAmount::saFromRate (
rippleCalc.mActiveLedger.rippleTransferRate (node.uIssuerID));
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeRev>"
<< " nodeIndex=" << nodeIndex
<< " nodeIsAccount=" << nodeIsAccount
<< " uIssuerID=" << RippleAddress::createHumanAccountID (node.uIssuerID)
<< " saTransferRate=" << node.saTransferRate;
errorCode = nodeIsAccount
? calcNodeAccountRev (rippleCalc, nodeIndex, pathState, bMultiQuality)
: calcNodeOfferRev (rippleCalc, nodeIndex, pathState, bMultiQuality);
// Do previous.
if (errorCode != tesSUCCESS)
// Error, don't continue.
nothing ();
else if (nodeIndex)
// Continue in reverse. TODO(tom): remove unnecessary recursion.
errorCode = calcNodeRev (rippleCalc, nodeIndex - 1, pathState, bMultiQuality);
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeRev< "
<< "nodeIndex=" << nodeIndex
<< " errorCode=%s" << transToken (errorCode)
<< "/" << errorCode;
return errorCode;
}
} // ripple

View File

@@ -0,0 +1,462 @@
//------------------------------------------------------------------------------
/*
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 "Calculators.h"
#include "RippleCalc.h"
#include "Tuning.h"
namespace ripple {
// 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 calcNodeAccountFwd (
RippleCalc& rippleCalc,
const unsigned int nodeIndex, // 0 <= nodeIndex <= lastNodeIndex
PathState& pathState,
const bool bMultiQuality)
{
TER errorCode = tesSUCCESS;
const unsigned int lastNodeIndex = pathState.vpnNodes.size () - 1;
std::uint64_t uRateMax = 0;
auto& previousNode = pathState.vpnNodes[nodeIndex ? nodeIndex - 1 : 0];
auto& node = pathState.vpnNodes[nodeIndex];
auto& nextNode = pathState.vpnNodes[nodeIndex == lastNodeIndex ? lastNodeIndex : nodeIndex + 1];
const bool previousNodeIsAccount
= is_bit_set (previousNode.uFlags, STPathElement::typeAccount);
const bool nextNodeIsAccount
= is_bit_set (nextNode.uFlags, STPathElement::typeAccount);
const uint160& previousAccountID
= previousNodeIsAccount ? previousNode.uAccountID : node.uAccountID;
// Offers are always issue.
const uint160& nextAccountID
= nextNodeIsAccount ? nextNode.uAccountID : node.uAccountID;
std::uint32_t uQualityIn = nodeIndex
? rippleCalc.mActiveLedger.rippleQualityIn (
node.uAccountID, previousAccountID, node.uCurrencyID)
: QUALITY_ONE;
std::uint32_t uQualityOut = (nodeIndex == lastNodeIndex)
? rippleCalc.mActiveLedger.rippleQualityOut (
node.uAccountID, nextAccountID, node.uCurrencyID)
: QUALITY_ONE;
// 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 nextNodeIsAccount
STAmount saPrvRedeemAct (
previousNode.saFwdRedeem.getCurrency (),
previousNode.saFwdRedeem.getIssuer ());
STAmount saPrvIssueAct (
previousNode.saFwdIssue.getCurrency (),
previousNode.saFwdIssue.getIssuer ());
// For !previousNodeIsAccount
STAmount saPrvDeliverAct (
previousNode.saFwdDeliver.getCurrency (),
previousNode.saFwdDeliver.getIssuer ());
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAccountFwd> "
<< "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 (previousNodeIsAccount && nextNodeIsAccount)
{
// 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.saInReq >= zero)
{
// Limit by send max.
node.saFwdRedeem = std::min (
node.saFwdRedeem, pathState.saInReq - pathState.saInAct);
}
pathState.saInPass = node.saFwdRedeem;
node.saFwdIssue = node.saFwdRedeem == node.saRevRedeem
// Fully redeemed.
? node.saRevIssue : STAmount (node.saRevIssue);
if (!!node.saFwdIssue && pathState.saInReq >= zero)
{
// Limit by send max.
node.saFwdIssue = std::min (
node.saFwdIssue,
pathState.saInReq - pathState.saInAct - node.saFwdRedeem);
}
pathState.saInPass += node.saFwdIssue;
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAccountFwd: ^ --> ACCOUNT --> account :"
<< " saInReq=" << pathState.saInReq
<< " saInAct=" << pathState.saInAct
<< " node.saFwdRedeem:" << node.saFwdRedeem
<< " node.saRevIssue:" << node.saRevIssue
<< " node.saFwdIssue:" << node.saFwdIssue
<< " pathState.saInPass:" << pathState.saInPass;
}
else if (nodeIndex == lastNodeIndex)
{
// account --> ACCOUNT --> $
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAccountFwd: account --> ACCOUNT --> $ :"
<< " previousAccountID="
<< RippleAddress::createHumanAccountID (previousAccountID)
<< " node.uAccountID="
<< RippleAddress::createHumanAccountID (node.uAccountID)
<< " previousNode.saFwdRedeem:" << previousNode.saFwdRedeem
<< " previousNode.saFwdIssue:" << previousNode.saFwdIssue;
// Last node. Accept all funds. Calculate amount actually to credit.
STAmount& saCurReceive = pathState.saOutPass;
STAmount saIssueCrd = uQualityIn >= QUALITY_ONE
? previousNode.saFwdIssue // No fee.
: STAmount::mulRound (
previousNode.saFwdIssue,
STAmount (CURRENCY_ONE, ACCOUNT_ONE, uQualityIn, -9),
true); // Amount to credit.
// Amount to credit. Credit for less than received as a surcharge.
saCurReceive = previousNode.saFwdRedeem + saIssueCrd;
if (saCurReceive)
{
// Actually receive.
errorCode = rippleCalc.mActiveLedger.rippleCredit (
previousAccountID, node.uAccountID,
previousNode.saFwdRedeem + previousNode.saFwdIssue, false);
}
else
{
// After applying quality, total payment was microscopic.
errorCode = tecPATH_DRY;
}
}
else
{
// account --> ACCOUNT --> account
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAccountFwd: 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
calcNodeRipple (
rippleCalc,
QUALITY_ONE, uQualityOut, 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
calcNodeRipple (
rippleCalc,
uQualityIn, uQualityOut, 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
calcNodeRipple (
rippleCalc, QUALITY_ONE,
rippleCalc.mActiveLedger.rippleTransferRate (node.uAccountID),
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
calcNodeRipple (
rippleCalc,
uQualityIn, QUALITY_ONE, previousNode.saFwdIssue, node.saRevIssue,
saPrvIssueAct, node.saFwdIssue, uRateMax);
}
STAmount saProvide = node.saFwdRedeem + node.saFwdIssue;
// Adjust prv --> cur balance : take all inbound
errorCode = saProvide
? rippleCalc.mActiveLedger.rippleCredit (
previousAccountID, node.uAccountID,
previousNode.saFwdRedeem + previousNode.saFwdIssue, false)
: tecPATH_DRY;
}
}
else if (previousNodeIsAccount && !nextNodeIsAccount)
{
// 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.
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAccountFwd: 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?
calcNodeRipple (
rippleCalc, QUALITY_ONE,
rippleCalc.mActiveLedger.rippleTransferRate (node.uAccountID),
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
calcNodeRipple (
rippleCalc,
uQualityIn, QUALITY_ONE, previousNode.saFwdIssue, node.saRevDeliver,
saPrvIssueAct, node.saFwdDeliver, uRateMax);
}
// Adjust prv --> cur balance : take all inbound
errorCode = node.saFwdDeliver
? rippleCalc.mActiveLedger.rippleCredit (
previousAccountID, node.uAccountID,
previousNode.saFwdRedeem + previousNode.saFwdIssue, false)
: 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.saInReq >= zero)
{
// Limit by send max.
node.saFwdDeliver = std::min (node.saFwdDeliver,
pathState.saInReq - pathState.saInAct);
// Limit XRP by available. No limit for non-XRP as issuer.
if (node.uCurrencyID.isZero ())
node.saFwdDeliver = std::min (
node.saFwdDeliver,
rippleCalc.mActiveLedger.accountHolds (
node.uAccountID, CURRENCY_XRP, ACCOUNT_XRP));
}
// Record amount sent for pass.
pathState.saInPass = node.saFwdDeliver;
if (!node.saFwdDeliver)
{
errorCode = tecPATH_DRY;
}
else if (!!node.uCurrencyID)
{
// 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.
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAccountFwd: ^ --> ACCOUNT -- !XRP --> offer";
// As the issuer, would only issue.
// Don't need to actually deliver. As from delivering leave in
// the issuer as limbo.
nothing ();
}
else
{
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAccountFwd: ^ --> ACCOUNT -- XRP --> offer";
// Deliver XRP to limbo.
errorCode = rippleCalc.mActiveLedger.accountSend (
node.uAccountID, ACCOUNT_XRP, node.saFwdDeliver);
}
}
}
else if (!previousNodeIsAccount && nextNodeIsAccount)
{
if (nodeIndex == lastNodeIndex)
{
// offer --> ACCOUNT --> $
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAccountFwd: offer --> ACCOUNT --> $ : "
<< previousNode.saFwdDeliver;
STAmount& saCurReceive = pathState.saOutPass;
// Amount to credit.
saCurReceive = previousNode.saFwdDeliver;
// No income balance adjustments necessary. The paying side inside
// the offer paid to this account.
}
else
{
// offer --> ACCOUNT --> account
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAccountFwd: 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
calcNodeRipple (
rippleCalc,
QUALITY_ONE, uQualityOut, 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
calcNodeRipple (
rippleCalc, QUALITY_ONE,
rippleCalc.mActiveLedger.rippleTransferRate (node.uAccountID),
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)
errorCode = tecPATH_DRY;
}
}
else
{
// offer --> ACCOUNT --> offer
// deliver/redeem -> deliver/issue.
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAccountFwd: offer --> ACCOUNT --> offer";
node.saFwdDeliver.clear (node.saRevDeliver);
if (previousNode.saFwdDeliver
// Previous wants to deliver
&& node.saRevIssue)
// Current wants issue.
{
// Rate : 1.0 : transfer_rate
calcNodeRipple (
rippleCalc, QUALITY_ONE,
rippleCalc.mActiveLedger.rippleTransferRate (node.uAccountID),
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)
errorCode = tecPATH_DRY;
}
return errorCode;
}
} // ripple

View File

@@ -0,0 +1,500 @@
//------------------------------------------------------------------------------
/*
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 "Calculators.h"
#include "RippleCalc.h"
#include "Tuning.h"
namespace ripple {
// Calculate saPrvRedeemReq, saPrvIssueReq, saPrvDeliver from saCur, based on
// required deliverable, propagate redeem, issue, and deliver requests 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.
//
// No account balance adjustments as we don't know how much is going to actually
// be pushed through yet.
//
// <-- tesSUCCESS or tecPATH_DRY
TER calcNodeAccountRev (
RippleCalc& rippleCalc,
const unsigned int nodeIndex, PathState& pathState,
const bool bMultiQuality)
{
TER terResult = tesSUCCESS;
auto const lastNodeIndex = pathState.vpnNodes.size () - 1;
auto const isFinalNode = (nodeIndex == lastNodeIndex);
std::uint64_t uRateMax = 0;
auto& previousNode = pathState.vpnNodes[nodeIndex ? nodeIndex - 1 : 0];
auto& node = pathState.vpnNodes[nodeIndex];
auto& nextNode = pathState.vpnNodes[isFinalNode ? lastNodeIndex : nodeIndex + 1];
// Current is allowed to redeem to next.
const bool previousNodeIsAccount = !nodeIndex ||
is_bit_set (previousNode.uFlags, STPathElement::typeAccount);
const bool nextNodeIsAccount = isFinalNode ||
is_bit_set (nextNode.uFlags, STPathElement::typeAccount);
const uint160& previousAccountID = previousNodeIsAccount
? previousNode.uAccountID : node.uAccountID;
const uint160& nextAccountID = nextNodeIsAccount ? nextNode.uAccountID
: node.uAccountID; // Offers are always issue.
// XXX Don't look up quality for XRP
const std::uint32_t uQualityIn = nodeIndex
? rippleCalc.mActiveLedger.rippleQualityIn (
node.uAccountID, previousAccountID, node.uCurrencyID)
: QUALITY_ONE;
const std::uint32_t uQualityOut = (nodeIndex != lastNodeIndex)
? rippleCalc.mActiveLedger.rippleQualityOut (
node.uAccountID, nextAccountID, node.uCurrencyID)
: QUALITY_ONE;
// For previousNodeIsAccount:
// Previous account is owed.
const STAmount saPrvOwed = (previousNodeIsAccount && nodeIndex)
? rippleCalc.mActiveLedger.rippleOwed (
node.uAccountID, previousAccountID, node.uCurrencyID)
: STAmount (node.uCurrencyID, node.uAccountID);
// Previous account may owe.
const STAmount saPrvLimit = (previousNodeIsAccount && nodeIndex)
? rippleCalc.mActiveLedger.rippleLimit (
node.uAccountID, previousAccountID, node.uCurrencyID)
: STAmount (node.uCurrencyID, node.uAccountID);
// Next account is owed.
const STAmount saNxtOwed = (nextNodeIsAccount && nodeIndex != lastNodeIndex)
? rippleCalc.mActiveLedger.rippleOwed (
node.uAccountID, nextAccountID, node.uCurrencyID)
: STAmount (node.uCurrencyID, node.uAccountID);
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAccountRev>"
<< " nodeIndex=%d/%d" << nodeIndex << "/" << lastNodeIndex
<< " previousAccountID="
<< RippleAddress::createHumanAccountID (previousAccountID)
<< " node.uAccountID="
<< RippleAddress::createHumanAccountID (node.uAccountID)
<< " nextAccountID="
<< RippleAddress::createHumanAccountID (nextAccountID)
<< " uCurrencyID="
<< STAmount::createHumanCurrency (node.uCurrencyID)
<< " uQualityIn=" << uQualityIn
<< " uQualityOut=" << uQualityOut
<< " saPrvOwed=" << saPrvOwed
<< " saPrvLimit=" << saPrvLimit;
// Previous can redeem the owed IOUs it holds.
const STAmount saPrvRedeemReq = (saPrvOwed > zero)
? saPrvOwed
: STAmount (saPrvOwed.getCurrency (), saPrvOwed.getIssuer ());
STAmount& saPrvRedeemAct = previousNode.saRevRedeem;
// Previous can issue up to limit minus whatever portion of limit already
// used (not including redeemable amount).
const STAmount saPrvIssueReq = (saPrvOwed < zero)
? saPrvLimit + saPrvOwed : saPrvLimit;
STAmount& saPrvIssueAct = previousNode.saRevIssue;
// For !previousNodeIsAccount
auto deliverCurrency = previousNode.saRevDeliver.getCurrency ();
const STAmount saPrvDeliverReq (
deliverCurrency, previousNode.saRevDeliver.getIssuer (), -1);
// Unlimited.
STAmount& saPrvDeliverAct = previousNode.saRevDeliver;
// For nextNodeIsAccount
const STAmount& saCurRedeemReq = node.saRevRedeem;
STAmount saCurRedeemAct (
saCurRedeemReq.getCurrency (), saCurRedeemReq.getIssuer ());
const STAmount& saCurIssueReq = node.saRevIssue;
// Track progress.
STAmount saCurIssueAct (
saCurIssueReq.getCurrency (), saCurIssueReq.getIssuer ());
// For !nextNodeIsAccount
const STAmount& saCurDeliverReq = node.saRevDeliver;
STAmount saCurDeliverAct (
saCurDeliverReq.getCurrency (), saCurDeliverReq.getIssuer ());
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAccountRev:"
<< " saPrvRedeemReq:" << saPrvRedeemReq
<< " saPrvIssueReq:" << saPrvIssueReq
<< " saPrvDeliverAct:" << saPrvDeliverAct
<< " saPrvDeliverReq:" << saPrvDeliverReq
<< " saCurRedeemReq:" << saCurRedeemReq
<< " saCurIssueReq:" << saCurIssueReq
<< " saNxtOwed:" << saNxtOwed;
WriteLog (lsTRACE, RippleCalc) << pathState.getJson ();
// Current redeem req can't be more than IOUs on hand.
assert (!saCurRedeemReq || (-saNxtOwed) >= saCurRedeemReq);
assert (!saCurIssueReq // If not issuing, fine.
|| saNxtOwed >= zero
// saNxtOwed >= 0: Sender not holding next IOUs, saNxtOwed < 0:
// Sender holding next IOUs.
|| -saNxtOwed == saCurRedeemReq);
// If issue req, then redeem req must consume all owed.
if (!nodeIndex)
{
// ^ --> ACCOUNT --> account|offer
// Nothing to do, there is no previous to adjust.
nothing ();
}
else if (previousNodeIsAccount && nextNodeIsAccount)
{
if (isFinalNode)
{
// account --> ACCOUNT --> $
// Overall deliverable.
// If previous is an account, limit.
const STAmount saCurWantedReq = std::min (
pathState.saOutReq - pathState.saOutAct, saPrvLimit + saPrvOwed);
STAmount saCurWantedAct (
saCurWantedReq.getCurrency (), saCurWantedReq.getIssuer ());
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAccountRev: account --> ACCOUNT --> $ :"
<< " saCurWantedReq=" << saCurWantedReq;
// Calculate redeem
if (saPrvRedeemReq) // Previous has IOUs to redeem.
{
// Redeem at 1:1
saCurWantedAct = std::min (saPrvRedeemReq, saCurWantedReq);
saPrvRedeemAct = saCurWantedAct;
uRateMax = STAmount::uRateOne;
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAccountRev: Redeem at 1:1"
<< " saPrvRedeemReq=" << saPrvRedeemReq
<< " (available) saPrvRedeemAct=" << saPrvRedeemAct
<< " uRateMax="
<< STAmount::saFromRate (uRateMax).getText ();
}
else
{
saPrvRedeemAct.clear (saPrvRedeemReq);
}
// Calculate issuing.
saPrvIssueAct.clear (saPrvIssueReq);
if (saCurWantedReq != saCurWantedAct // Need more.
&& saPrvIssueReq) // Will accept IOUs from prevous.
{
// Rate: quality in : 1.0
// If we previously redeemed and this has a poorer rate, this
// won't be included the current increment.
calcNodeRipple (
rippleCalc,
uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurWantedReq,
saPrvIssueAct, saCurWantedAct, uRateMax);
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAccountRev: Issuing: Rate: quality in : 1.0"
<< " saPrvIssueAct:" << saPrvIssueAct
<< " saCurWantedAct:" << saCurWantedAct;
}
if (!saCurWantedAct)
{
// Must have processed something.
terResult = tecPATH_DRY;
}
}
else
{
// ^|account --> ACCOUNT --> account
saPrvRedeemAct.clear (saPrvRedeemReq);
saPrvIssueAct.clear (saPrvIssueReq);
// redeem (part 1) -> redeem
if (saCurRedeemReq // Next wants IOUs redeemed.
&& saPrvRedeemReq) // Previous has IOUs to redeem.
{
// Rate : 1.0 : quality out
calcNodeRipple (
rippleCalc,
QUALITY_ONE, uQualityOut, saPrvRedeemReq, saCurRedeemReq,
saPrvRedeemAct, saCurRedeemAct, uRateMax);
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAccountRev: Rate : 1.0 : quality out"
<< " saPrvRedeemAct:" << saPrvRedeemAct
<< " saCurRedeemAct:" << saCurRedeemAct;
}
// issue (part 1) -> redeem
if (saCurRedeemReq != saCurRedeemAct
// Next wants more IOUs redeemed.
&& saPrvRedeemAct == saPrvRedeemReq)
// Previous has no IOUs to redeem remaining.
{
// Rate: quality in : quality out
calcNodeRipple (
rippleCalc,
uQualityIn, uQualityOut, saPrvIssueReq, saCurRedeemReq,
saPrvIssueAct, saCurRedeemAct, uRateMax);
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAccountRev: Rate: quality in : quality out:"
<< " saPrvIssueAct:" << saPrvIssueAct
<< " saCurRedeemAct:" << saCurRedeemAct;
}
// redeem (part 2) -> issue.
if (saCurIssueReq // Next wants IOUs issued.
&& saCurRedeemAct == saCurRedeemReq
// Can only issue if completed redeeming.
&& saPrvRedeemAct != saPrvRedeemReq)
// Did not complete redeeming previous IOUs.
{
// Rate : 1.0 : transfer_rate
calcNodeRipple (
rippleCalc,
QUALITY_ONE,
rippleCalc.mActiveLedger.rippleTransferRate (node.uAccountID),
saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct,
saCurIssueAct, uRateMax);
WriteLog (lsDEBUG, RippleCalc)
<< "calcNodeAccountRev: Rate : 1.0 : transfer_rate:"
<< " saPrvRedeemAct:" << saPrvRedeemAct
<< " saCurIssueAct:" << saCurIssueAct;
}
// issue (part 2) -> issue
if (saCurIssueReq != saCurIssueAct
// Need wants more IOUs issued.
&& saCurRedeemAct == saCurRedeemReq
// Can only issue if completed redeeming.
&& saPrvRedeemReq == saPrvRedeemAct
// Previously redeemed all owed IOUs.
&& saPrvIssueReq)
// Previous can issue.
{
// Rate: quality in : 1.0
calcNodeRipple (
rippleCalc,
uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurIssueReq,
saPrvIssueAct, saCurIssueAct, uRateMax);
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAccountRev: Rate: quality in : 1.0:"
<< " saPrvIssueAct:" << saPrvIssueAct
<< " saCurIssueAct:" << saCurIssueAct;
}
if (!saCurRedeemAct && !saCurIssueAct)
{
// Did not make progress.
terResult = tecPATH_DRY;
}
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAccountRev: ^|account --> ACCOUNT --> account :"
<< " saCurRedeemReq:" << saCurRedeemReq
<< " saCurIssueReq:" << saCurIssueReq
<< " 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.
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAccountRev: account --> ACCOUNT --> offer";
saPrvRedeemAct.clear (saPrvRedeemReq);
saPrvIssueAct.clear (saPrvIssueReq);
// redeem -> deliver/issue.
if (saPrvOwed > zero // Previous has IOUs to redeem.
&& saCurDeliverReq) // Need some issued.
{
// Rate : 1.0 : transfer_rate
calcNodeRipple (
rippleCalc, QUALITY_ONE,
rippleCalc.mActiveLedger.rippleTransferRate (node.uAccountID),
saPrvRedeemReq, saCurDeliverReq, saPrvRedeemAct,
saCurDeliverAct, uRateMax);
}
// issue -> deliver/issue
if (saPrvRedeemReq == saPrvRedeemAct // Previously redeemed all owed.
&& saCurDeliverReq != saCurDeliverAct) // Still need some issued.
{
// Rate: quality in : 1.0
calcNodeRipple (
rippleCalc,
uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurDeliverReq,
saPrvIssueAct, saCurDeliverAct, uRateMax);
}
if (!saCurDeliverAct)
{
// Must want something.
terResult = tecPATH_DRY;
}
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAccountRev: "
<< " saCurDeliverReq:" << saCurDeliverReq
<< " saCurDeliverAct:" << saCurDeliverAct
<< " saPrvOwed:" << saPrvOwed;
}
else if (!previousNodeIsAccount && nextNodeIsAccount)
{
if (isFinalNode)
{
// offer --> ACCOUNT --> $
// Previous is an offer, no limit: redeem own IOUs.
const STAmount& saCurWantedReq = pathState.saOutReq - pathState.saOutAct;
STAmount saCurWantedAct (
saCurWantedReq.getCurrency (), saCurWantedReq.getIssuer ());
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAccountRev: offer --> ACCOUNT --> $ :"
<< " saCurWantedReq:" << saCurWantedReq
<< " saOutAct:" << pathState.saOutAct
<< " saOutReq:" << pathState.saOutReq;
if (saCurWantedReq <= zero)
{
// TEMPORARY emergency fix
WriteLog (lsFATAL, RippleCalc) << "CurWantReq was not positive";
return tefEXCEPTION;
}
assert (saCurWantedReq > zero); // FIXME: We got one of these
// TODO(tom): can only be a race condition if true!
// Rate: quality in : 1.0
calcNodeRipple (
rippleCalc,
uQualityIn, QUALITY_ONE, saPrvDeliverReq, saCurWantedReq,
saPrvDeliverAct, saCurWantedAct, uRateMax);
if (!saCurWantedAct)
{
// Must have processed something.
terResult = tecPATH_DRY;
}
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAccountRev:"
<< " saPrvDeliverAct:" << saPrvDeliverAct
<< " saPrvDeliverReq:" << saPrvDeliverReq
<< " saCurWantedAct:" << saCurWantedAct
<< " saCurWantedReq:" << saCurWantedReq;
}
else
{
// offer --> ACCOUNT --> account
// Note: offer is always delivering(redeeming) as account is issuer.
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAccountRev: offer --> ACCOUNT --> account :"
<< " saCurRedeemReq:" << saCurRedeemReq
<< " saCurIssueReq:" << saCurIssueReq;
// deliver -> redeem
if (saCurRedeemReq) // Next wants us to redeem.
{
// Rate : 1.0 : quality out
calcNodeRipple (
rippleCalc,
QUALITY_ONE, uQualityOut, saPrvDeliverReq, saCurRedeemReq,
saPrvDeliverAct, saCurRedeemAct, uRateMax);
}
// deliver -> issue.
if (saCurRedeemReq == saCurRedeemAct
// Can only issue if previously redeemed all.
&& saCurIssueReq)
// Need some issued.
{
// Rate : 1.0 : transfer_rate
calcNodeRipple (
rippleCalc,
QUALITY_ONE,
rippleCalc.mActiveLedger.rippleTransferRate (node.uAccountID),
saPrvDeliverReq, saCurIssueReq, saPrvDeliverAct,
saCurIssueAct, uRateMax);
}
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAccountRev:"
<< " saCurRedeemAct:" << saCurRedeemAct
<< " saCurRedeemReq:" << saCurRedeemReq
<< " saPrvDeliverAct:" << saPrvDeliverAct
<< " saCurIssueReq:" << saCurIssueReq;
if (!saPrvDeliverAct)
{
// Must want something.
terResult = tecPATH_DRY;
}
}
}
else
{
// offer --> ACCOUNT --> offer
// deliver/redeem -> deliver/issue.
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAccountRev: offer --> ACCOUNT --> offer";
// Rate : 1.0 : transfer_rate
calcNodeRipple (
rippleCalc,
QUALITY_ONE,
rippleCalc.mActiveLedger.rippleTransferRate (node.uAccountID),
saPrvDeliverReq, saCurDeliverReq, saPrvDeliverAct,
saCurDeliverAct, uRateMax);
if (!saCurDeliverAct)
{
// Must want something.
terResult = tecPATH_DRY;
}
}
return terResult;
}
} // ripple

View File

@@ -0,0 +1,427 @@
//------------------------------------------------------------------------------
/*
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 "Calculators.h"
#include "RippleCalc.h"
#include "Tuning.h"
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.
//
// VFALCO TODO Update the comment for this function, the argument list no
// resembles the comment
//
// Provide a better explanation for what this function does.
// 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 calcNodeAdvance (
RippleCalc& rippleCalc,
const unsigned int nodeIndex,
PathState& pathState,
const bool bMultiQuality,
const bool bReverse)
{
auto& previousNode = pathState.vpnNodes[nodeIndex - 1];
auto& node = pathState.vpnNodes[nodeIndex];
uint256& uDirectTip = node.uDirectTip;
uint256& uDirectEnd = node.uDirectEnd;
bool& bDirectAdvance = node.bDirectAdvance;
bool& bDirectRestart = node.bDirectRestart;
SLE::pointer& sleDirectDir = node.sleDirectDir;
STAmount& saOfrRate = node.saOfrRate;
bool& bEntryAdvance = node.bEntryAdvance;
unsigned int& uEntry = node.uEntry;
uint256& uOfferIndex = node.uOfferIndex;
SLE::pointer& sleOffer = node.sleOffer;
uint160& uOfrOwnerID = node.uOfrOwnerID;
STAmount& saOfferFunds = node.saOfferFunds;
STAmount& saTakerPays = node.saTakerPays;
STAmount& saTakerGets = node.saTakerGets;
bool& bFundsDirty = node.bFundsDirty;
TER errorCode = tesSUCCESS;
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAdvance: TakerPays:"
<< saTakerPays << " TakerGets:" << saTakerGets;
int loopCount = 0;
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)
{
WriteLog (lsWARNING, RippleCalc) << "Loop count exceeded";
return tefEXCEPTION;
}
bool bDirectDirDirty = false;
if (!uDirectTip)
{
// Need to initialize current node.
uDirectTip = Ledger::getBookBase (
previousNode.uCurrencyID, previousNode.uIssuerID,
node.uCurrencyID,
node.uIssuerID);
uDirectEnd = Ledger::getQualityNext (uDirectTip);
sleDirectDir = rippleCalc.mActiveLedger.entryCache (ltDIR_NODE, uDirectTip);
// Associated vars are dirty, if found it.
bDirectDirDirty = !!sleDirectDir;
// Advance, if didn't find it. Normal not to be unable to lookup
// firstdirectory. Maybe even skip this lookup.
bDirectAdvance = !sleDirectDir;
bDirectRestart = false;
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAdvance: Initialize node:"
<< " uDirectTip=" << uDirectTip
<<" uDirectEnd=" << uDirectEnd
<< " bDirectAdvance=" << bDirectAdvance;
}
if (bDirectAdvance || bDirectRestart)
{
// Get next quality.
if (bDirectAdvance)
{
uDirectTip = rippleCalc.mActiveLedger.getNextLedgerIndex (
uDirectTip, uDirectEnd);
}
bDirectDirDirty = true;
bDirectAdvance = false;
bDirectRestart = false;
if (!!uDirectTip)
{
// Have another quality directory.
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAdvance: Quality advance: uDirectTip="
<< uDirectTip;
sleDirectDir = rippleCalc.mActiveLedger.entryCache (ltDIR_NODE, uDirectTip);
}
else if (bReverse)
{
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAdvance: No more offers.";
uOfferIndex = 0;
break;
}
else
{
// No more offers. Should be done rather than fall off end of
// book.
WriteLog (lsWARNING, RippleCalc)
<< "calcNodeAdvance: Unreachable: "
<< "Fell off end of order book.";
// FIXME: why?
return rippleCalc.mOpenLedger ? telFAILED_PROCESSING :
tecFAILED_PROCESSING;
}
}
if (bDirectDirDirty)
{
saOfrRate = STAmount::setRate (Ledger::getQuality (uDirectTip));
// For correct ratio
uEntry = 0;
bEntryAdvance = true;
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAdvance: directory dirty: saOfrRate="
<< saOfrRate;
}
if (!bEntryAdvance)
{
if (bFundsDirty)
{
// We were called again probably merely to update structure
// variables.
saTakerPays = sleOffer->getFieldAmount (sfTakerPays);
saTakerGets = sleOffer->getFieldAmount (sfTakerGets);
saOfferFunds = rippleCalc.mActiveLedger.accountFunds (
uOfrOwnerID, saTakerGets);
// Funds left.
bFundsDirty = false;
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAdvance: funds dirty: saOfrRate="
<< saOfrRate;
}
else
{
WriteLog (lsTRACE, RippleCalc) << "calcNodeAdvance: as is";
}
}
else if (!rippleCalc.mActiveLedger.dirNext (
uDirectTip, sleDirectDir, uEntry, uOfferIndex))
{
// Failed to find an entry in directory.
// Do another cur directory iff bMultiQuality
if (bMultiQuality)
{
// We are allowed to process multiple qualities if this is the
// only path.
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAdvance: next quality";
bDirectAdvance = true; // Process next quality.
}
else if (!bReverse)
{
WriteLog (lsWARNING, RippleCalc)
<< "calcNodeAdvance: unreachable: ran out of offers";
return rippleCalc.mOpenLedger ? telFAILED_PROCESSING :
tecFAILED_PROCESSING;
// TEMPORARY
}
else
{
// Ran off end of offers.
bEntryAdvance = false; // Done.
uOfferIndex = 0; // Report nore more entries.
}
}
else
{
// Got a new offer.
sleOffer = rippleCalc.mActiveLedger.entryCache (ltOFFER, uOfferIndex);
if (!sleOffer)
{
WriteLog (lsWARNING, RippleCalc) <<
"Missing offer in directory";
bEntryAdvance = true;
}
else
{
uOfrOwnerID = sleOffer->getFieldAccount160 (sfAccount);
saTakerPays = sleOffer->getFieldAmount (sfTakerPays);
saTakerGets = sleOffer->getFieldAmount (sfTakerGets);
const AccountCurrencyIssuer asLine (
uOfrOwnerID, node.uCurrencyID, node.uIssuerID);
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAdvance: uOfrOwnerID="
<< RippleAddress::createHumanAccountID (uOfrOwnerID)
<< " saTakerPays=" << saTakerPays
<< " saTakerGets=" << saTakerGets
<< " uOfferIndex=" << uOfferIndex;
if (sleOffer->isFieldPresent (sfExpiration) &&
(sleOffer->getFieldU32 (sfExpiration) <=
rippleCalc.mActiveLedger.getLedger ()->getParentCloseTimeNC ()))
{
// Offer is expired.
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAdvance: expired offer";
rippleCalc.mUnfundedOffers.insert(uOfferIndex);
continue;
}
else if (saTakerPays <= zero || saTakerGets <= zero)
{
// Offer has bad amounts. Offers should never have a bad
// amounts.
if (bReverse)
{
// Past internal error, offer had bad amounts.
WriteLog (lsWARNING, RippleCalc)
<< "calcNodeAdvance: PAST INTERNAL ERROR:"
<< " OFFER NON-POSITIVE:"
<< " saTakerPays=" << saTakerPays
<< " saTakerGets=%s" << saTakerGets;
// Mark offer for always deletion.
rippleCalc.mUnfundedOffers.insert (uOfferIndex);
}
else if (rippleCalc.mUnfundedOffers.find (uOfferIndex) !=
rippleCalc.mUnfundedOffers.end ())
{
// Past internal error, offer was found failed to place
// this in mUnfundedOffers.
// Just skip it. It will be deleted.
WriteLog (lsDEBUG, RippleCalc)
<< "calcNodeAdvance: PAST INTERNAL ERROR:"
<< " OFFER NON-POSITIVE:"
<< " saTakerPays=" << saTakerPays
<< " saTakerGets=" << saTakerGets;
}
else
{
// Reverse should have previously put bad offer in list.
// An internal error previously left a bad offer.
WriteLog (lsWARNING, RippleCalc)
<< "calcNodeAdvance: INTERNAL ERROR:"
<<" OFFER NON-POSITIVE:"
<< " saTakerPays=" << saTakerPays
<< " saTakerGets=" << saTakerGets;
// Don't process at all, things are in an unexpected
// state for this transactions.
errorCode = 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.umForward.find (asLine);
const bool bFoundForward = itForward != pathState.umForward.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 &&
uOfrOwnerID != node.uIssuerID)
{
// Temporarily unfunded. Another node uses this source,
// ignore in this offer.
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAdvance: temporarily unfunded offer"
<< " (forward)";
continue;
}
// This is overly strict. For contributions to past. We should
// only count source if actually used.
auto itReverse = pathState.umReverse.find (asLine);
bool bFoundReverse = itReverse != pathState.umReverse.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 &&
uOfrOwnerID != node.uIssuerID)
{
// Temporarily unfunded. Another node uses this source,
// ignore in this offer.
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAdvance: 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 (asLine);
bool bFoundPast = (itPast != rippleCalc.mumSource.end ());
// Only the current node is allowed to use the source.
saOfferFunds = rippleCalc.mActiveLedger.accountFunds
(uOfrOwnerID, saTakerGets); // Funds held.
if (saOfferFunds <= zero)
{
// Offer is unfunded.
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAdvance: 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.mUnfundedOffers.insert (uOfferIndex);
}
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.
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAdvance: remember="
<< RippleAddress::createHumanAccountID (uOfrOwnerID)
<< "/"
<< STAmount::createHumanCurrency (node.uCurrencyID)
<< "/"
<< RippleAddress::createHumanAccountID (node.uIssuerID);
pathState.umReverse.insert (std::make_pair (asLine, nodeIndex));
}
bFundsDirty = false;
bEntryAdvance = false;
}
}
}
while (errorCode == tesSUCCESS && (bEntryAdvance || bDirectAdvance));
if (errorCode == tesSUCCESS)
{
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeAdvance: uOfferIndex=" << uOfferIndex;
}
else
{
WriteLog (lsDEBUG, RippleCalc)
<< "calcNodeAdvance: errorCode=" << transToken (errorCode);
}
return errorCode;
}
} // ripple

View File

@@ -0,0 +1,385 @@
//------------------------------------------------------------------------------
/*
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 "Calculators.h"
#include "RippleCalc.h"
#include "Tuning.h"
namespace ripple {
// For current offer, get input from deliver/limbo and output to next account or
// deliver for next offers.
//
// <-- node.saFwdDeliver: For calcNodeAccountFwd to know how much went through
// --> node.saRevDeliver: Do not exceed.
TER calcNodeDeliverFwd (
RippleCalc& rippleCalc,
const unsigned int nodeIndex, // 0 < nodeIndex < lastNodeIndex
PathState& pathState,
const bool bMultiQuality,
const uint160& uInAccountID, // --> Input owner's account.
const STAmount& saInReq, // --> Amount to deliver.
STAmount& saInAct, // <-- Amount delivered, this invokation.
STAmount& saInFees) // <-- Fees charged, this invokation.
{
TER errorCode = tesSUCCESS;
PathState::Node& previousNode = pathState.vpnNodes[nodeIndex - 1];
PathState::Node& node = pathState.vpnNodes[nodeIndex];
PathState::Node& nextNode = pathState.vpnNodes[nodeIndex + 1];
const uint160& nextAccountID = nextNode.uAccountID;
const uint160& uCurCurrencyID = node.uCurrencyID;
const uint160& uCurIssuerID = node.uIssuerID;
uint256 const& uOfferIndex = node.uOfferIndex;
const uint160& uPrvCurrencyID = previousNode.uCurrencyID;
const uint160& uPrvIssuerID = previousNode.uIssuerID;
const STAmount& saInTransRate = previousNode.saTransferRate;
const STAmount& saCurDeliverMax = node.saRevDeliver;
// Don't deliver more than wanted.
STAmount& saCurDeliverAct = node.saFwdDeliver;
// Zeroed in reverse pass.
uint256& uDirectTip = node.uDirectTip;
bool& bDirectRestart = node.bDirectRestart;
if (bMultiQuality)
uDirectTip = 0; // Restart book searching.
else
bDirectRestart = true; // Restart at same quality.
saInAct.clear (saInReq);
saInFees.clear (saInReq);
int loopCount = 0;
// XXX Perhaps make sure do not exceed saCurDeliverMax as another way to
// stop?
while (errorCode == tesSUCCESS && saInAct + saInFees < saInReq)
{
// Did not spend all inbound deliver funds.
if (++loopCount > CALC_NODE_DELIVER_MAX_LOOPS)
{
WriteLog (lsWARNING, RippleCalc)
<< "calcNodeDeliverFwd: max loops cndf";
return rippleCalc.mOpenLedger ? telFAILED_PROCESSING : tecFAILED_PROCESSING;
}
// Determine values for pass to adjust saInAct, saInFees, and
// saCurDeliverAct.
errorCode = calcNodeAdvance (
rippleCalc,
nodeIndex, pathState, bMultiQuality || saInAct == zero, false);
// If needed, advance to next funded offer.
if (errorCode != tesSUCCESS)
{
nothing ();
}
else if (!uOfferIndex)
{
WriteLog (lsWARNING, RippleCalc)
<< "calcNodeDeliverFwd: INTERNAL ERROR: Ran out of offers.";
return rippleCalc.mOpenLedger ? telFAILED_PROCESSING : tecFAILED_PROCESSING;
}
else if (errorCode == tesSUCCESS)
{
// Doesn't charge input. Input funds are in limbo.
bool& bEntryAdvance = node.bEntryAdvance;
STAmount& saOfrRate = node.saOfrRate;
uint256& uOfferIndex = node.uOfferIndex;
SLE::pointer& sleOffer = node.sleOffer;
const uint160& uOfrOwnerID = node.uOfrOwnerID;
bool& bFundsDirty = node.bFundsDirty;
STAmount& saOfferFunds = node.saOfferFunds;
STAmount& saTakerPays = node.saTakerPays;
STAmount& saTakerGets = node.saTakerGets;
const STAmount saInFeeRate
= !uPrvCurrencyID // XRP.
|| uInAccountID == uPrvIssuerID // Sender is issuer.
|| uOfrOwnerID == uPrvIssuerID // Reciever is issuer.
? saOne // No fee.
: saInTransRate; // Transfer rate of issuer.
// First calculate assuming no output fees: saInPassAct,
// saInPassFees, saOutPassAct.
// Offer maximum out - limited by funds with out fees.
STAmount saOutFunded = std::min (saOfferFunds, saTakerGets);
// Offer maximum out - limit by most to deliver.
STAmount saOutPassFunded = std::min (
saOutFunded, saCurDeliverMax - saCurDeliverAct);
// Offer maximum in - Limited by by payout.
STAmount saInFunded = STAmount::mulRound (
saOutPassFunded, saOfrRate, saTakerPays, true);
// Offer maximum in with fees.
STAmount saInTotal = STAmount::mulRound (
saInFunded, saInFeeRate, true);
STAmount saInRemaining = saInReq - saInAct - saInFees;
if (saInRemaining < zero)
saInRemaining.clear();
// In limited by remaining.
STAmount saInSum = std::min (saInTotal, saInRemaining);
// In without fees.
STAmount saInPassAct = std::min (
saTakerPays, STAmount::divRound (saInSum, saInFeeRate, true));
// Out limited by in remaining.
auto outPass = STAmount::divRound (
saInPassAct, saOfrRate, saTakerGets, 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;
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeDeliverFwd:"
<< " nodeIndex=" << nodeIndex
<< " saOutFunded=" << saOutFunded
<< " saOutPassFunded=" << saOutPassFunded
<< " saOfferFunds=" << saOfferFunds
<< " saTakerGets=" << 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 (!saTakerPays || saInSum <= zero)
{
WriteLog (lsDEBUG, RippleCalc)
<< "calcNodeDeliverFwd: Microscopic offer unfunded.";
// After math offer is effectively unfunded.
pathState.vUnfundedBecame.push_back (uOfferIndex);
bEntryAdvance = true;
continue;
}
else if (!saInFunded)
{
// Previous check should catch this.
WriteLog (lsWARNING, RippleCalc)
<< "calcNodeDeliverFwd: UNREACHABLE REACHED";
// After math offer is effectively unfunded.
pathState.vUnfundedBecame.push_back (uOfferIndex);
bEntryAdvance = true;
continue;
}
else if (!!nextAccountID)
{
// ? --> 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;
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeDeliverFwd: ? --> OFFER --> account:"
<< " uOfrOwnerID="
<< RippleAddress::createHumanAccountID (uOfrOwnerID)
<< " nextAccountID="
<< RippleAddress::createHumanAccountID (nextAccountID)
<< " saOutPassAct=" << saOutPassAct
<< " saOutFunded=%s" << saOutFunded;
// Output: Debit offer owner, send XRP or non-XPR to next
// account.
errorCode = rippleCalc.mActiveLedger.accountSend (
uOfrOwnerID, nextAccountID, saOutPassAct);
if (errorCode != 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.
errorCode = calcNodeDeliverFwd (
rippleCalc,
nodeIndex + 1,
pathState,
bMultiQuality,
uOfrOwnerID, // --> Current holder.
saOutPassMax, // --> Amount available.
saOutPassAct, // <-- Amount delivered.
saOutPassFees); // <-- Fees charged.
if (errorCode != 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 = STAmount::mulRound (
saOutPassAct, saOfrRate, saInReq, true);
saInPassAct = std::min (saTakerPays, inPassAct);
auto inPassFees = STAmount::mulRound (
saInPassAct, saInFeeRate, true);
saInPassFees = std::min (saInPassFeesMax, inPassFees);
}
// Do outbound debiting.
// Send to issuer/limbo total amount including fees (issuer gets
// fees).
auto id = !!uCurCurrencyID ? uCurIssuerID : ACCOUNT_XRP;
auto outPassTotal = saOutPassAct + saOutPassFees;
rippleCalc.mActiveLedger.accountSend (uOfrOwnerID, id, outPassTotal);
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeDeliverFwd: ? --> OFFER --> offer:"
<< " saOutPassAct=" << saOutPassAct
<< " saOutPassFees=" << saOutPassFees;
}
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeDeliverFwd: "
<< " nodeIndex=" << nodeIndex
<< " saTakerGets=" << saTakerGets
<< " saTakerPays=" << saTakerPays
<< " saInPassAct=" << saInPassAct
<< " saInPassFees=" << saInPassFees
<< " saOutPassAct=" << saOutPassAct
<< " saOutFunded=" << saOutFunded;
// Funds were spent.
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 (!uPrvCurrencyID // Always credit XRP from limbo.
|| uInAccountID != uOfrOwnerID) // Never send non-XRP to the
// same account.
{
auto id = !!uPrvCurrencyID ? uInAccountID : ACCOUNT_XRP;
errorCode = rippleCalc.mActiveLedger.accountSend (
id, uOfrOwnerID, saInPassAct);
if (errorCode != tesSUCCESS)
break;
}
// Adjust offer.
//
// Fees are considered paid from a seperate budget and are not named
// in the offer.
STAmount saTakerGetsNew = saTakerGets - saOutPassAct;
STAmount saTakerPaysNew = saTakerPays - saInPassAct;
if (saTakerPaysNew < zero || saTakerGetsNew < zero)
{
WriteLog (lsWARNING, RippleCalc)
<< "calcNodeDeliverFwd: NEGATIVE:"
<< " saTakerPaysNew=" << saTakerPaysNew
<< " saTakerGetsNew=" << saTakerGetsNew;
// If mOpenLedger, then ledger is not final, can vote no.
errorCode = rippleCalc.mOpenLedger
? telFAILED_PROCESSING : tecFAILED_PROCESSING;
break;
}
sleOffer->setFieldAmount (sfTakerGets, saTakerGetsNew);
sleOffer->setFieldAmount (sfTakerPays, saTakerPaysNew);
rippleCalc.mActiveLedger.entryModify (sleOffer);
if (saOutPassAct == saOutFunded || saTakerGetsNew == zero)
{
// Offer became unfunded.
WriteLog (lsWARNING, RippleCalc)
<< "calcNodeDeliverFwd: unfunded:"
<< " saOutPassAct=" << saOutPassAct
<< " saOutFunded=" << saOutFunded;
pathState.vUnfundedBecame.push_back (uOfferIndex);
bEntryAdvance = true;
}
else
{
CondLog (saOutPassAct >= saOutFunded, lsWARNING, RippleCalc)
<< "calcNodeDeliverFwd: TOO MUCH:"
<< " saOutPassAct=" << saOutPassAct
<< " saOutFunded=" << saOutFunded;
assert (saOutPassAct < saOutFunded);
}
saInAct += saInPassAct;
saInFees += saInPassFees;
// Adjust amount available to next node.
saCurDeliverAct = std::min (saCurDeliverMax,
saCurDeliverAct + saOutPassAct);
}
}
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeDeliverFwd<"
<< " nodeIndex=" << nodeIndex
<< " saInAct=" << saInAct
<< " saInFees=" << saInFees;
return errorCode;
}
} // ripple

View File

@@ -0,0 +1,382 @@
//------------------------------------------------------------------------------
/*
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 "Calculators.h"
#include "RippleCalc.h"
#include "Tuning.h"
namespace ripple {
// At the right most node of a list of consecutive offer nodes, given the amount
// requested to be delivered, push toward node 0 the amount requested for
// previous nodes to know how much to deliver.
//
// 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.
TER calcNodeDeliverRev (
RippleCalc& rippleCalc,
const unsigned int nodeIndex,
PathState& pathState,
const bool bMultiQuality, // True, if not constrained to the same
// or better quality.
const uint160& uOutAccountID, // --> Output owner's account.
const STAmount& saOutReq, // --> Funds requested to be
// delivered for an increment.
STAmount& saOutAct) // <-- Funds actually delivered for an
// increment.
{
TER errorCode = tesSUCCESS;
PathState::Node& previousNode = pathState.vpnNodes[nodeIndex - 1];
PathState::Node& node = pathState.vpnNodes[nodeIndex];
const uint160& uCurIssuerID = node.uIssuerID;
const uint160& uPrvAccountID = previousNode.uAccountID;
const STAmount& saTransferRate = node.saTransferRate;
// Transfer rate of the TakerGets issuer.
STAmount& saPrvDlvReq = previousNode.saRevDeliver;
// Accumulation of what the previous node must deliver.
uint256& uDirectTip = node.uDirectTip;
bool& bDirectRestart = node.bDirectRestart;
if (bMultiQuality)
uDirectTip = 0; // Restart book searching.
else
bDirectRestart = true; // Restart at same quality.
// YYY 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);
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeDeliverRev>"
<< " saOutAct=" << saOutAct
<< " saOutReq=" << saOutReq
<< " saPrvDlvReq=" << saPrvDlvReq;
assert (!!saOutReq);
int loopCount = 0;
// While we did not deliver as much as requested:
while (saOutAct < saOutReq)
{
if (++loopCount > CALC_NODE_DELIVER_MAX_LOOPS)
{
WriteLog (lsFATAL, RippleCalc) << "loop count exceeded";
return rippleCalc.mOpenLedger ? telFAILED_PROCESSING : tecFAILED_PROCESSING;
}
bool& bEntryAdvance = node.bEntryAdvance;
STAmount& saOfrRate = node.saOfrRate;
uint256& uOfferIndex = node.uOfferIndex;
SLE::pointer& sleOffer = node.sleOffer;
const uint160& uOfrOwnerID = node.uOfrOwnerID;
bool& bFundsDirty = node.bFundsDirty;
STAmount& saOfferFunds = node.saOfferFunds;
STAmount& saTakerPays = node.saTakerPays;
STAmount& saTakerGets = node.saTakerGets;
STAmount& saRateMax = node.saRateMax;
errorCode = calcNodeAdvance (
rippleCalc,
nodeIndex, pathState, bMultiQuality || saOutAct == zero, true);
// If needed, advance to next funded offer.
if (errorCode != tesSUCCESS || !uOfferIndex)
{
// Error or out of offers.
break;
}
auto const hasFee = uOfrOwnerID == uCurIssuerID
|| uOutAccountID == uCurIssuerID; // Issuer sending or receiving.
const STAmount saOutFeeRate = hasFee
? saOne // No fee.
: saTransferRate; // Transfer rate of issuer.
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeDeliverRev:"
<< " uOfrOwnerID="
<< RippleAddress::createHumanAccountID (uOfrOwnerID)
<< " uOutAccountID="
<< RippleAddress::createHumanAccountID (uOutAccountID)
<< " uCurIssuerID="
<< RippleAddress::createHumanAccountID (uCurIssuerID)
<< " saTransferRate=" << saTransferRate
<< " saOutFeeRate=" << saOutFeeRate;
if (bMultiQuality)
{
// In multi-quality mode, ignore rate.
}
else if (!saRateMax)
{
// Set initial rate.
saRateMax = saOutFeeRate;
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeDeliverRev: Set initial rate:"
<< " saRateMax=" << saRateMax
<< " saOutFeeRate=" << saOutFeeRate;
}
else if (saOutFeeRate > saRateMax)
{
// Offer exceeds initial rate.
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeDeliverRev: Offer exceeds initial rate:"
<< " saRateMax=" << saRateMax
<< " saOutFeeRate=" << saOutFeeRate;
break; // Done. Don't bother looking for smaller saTransferRates.
}
else if (saOutFeeRate < 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.
saRateMax = saOutFeeRate;
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeDeliverRev: Reducing rate:"
<< " saRateMax=" << saRateMax;
}
// Amount that goes to the taker.
STAmount saOutPassReq = std::min (
std::min (saOfferFunds, 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 = STAmount::mulRound (
saOutPassAct, saOutFeeRate, false);
// Offer out with fees.
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeDeliverRev:"
<< " saOutReq=" << saOutReq
<< " saOutAct=" << saOutAct
<< " saTakerGets=" << saTakerGets
<< " saOutPassAct=" << saOutPassAct
<< " saOutPlusFees=" << saOutPlusFees
<< " saOfferFunds=" << saOfferFunds;
if (saOutPlusFees > saOfferFunds)
{
// Offer owner can not cover all fees, compute saOutPassAct based on
// saOfferFunds.
saOutPlusFees = saOfferFunds;
// Round up: prefer liquidity rather than microscopic fees. But,
// limit by requested.
auto fee = STAmount::divRound (saOutPlusFees, saOutFeeRate, true);
saOutPassAct = std::min (saOutPassReq, fee);
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeDeliverRev: Total exceeds fees:"
<< " saOutPassAct=" << saOutPassAct
<< " saOutPlusFees=" << saOutPlusFees
<< " saOfferFunds=" << saOfferFunds;
}
// Compute portion of input needed to cover actual output.
auto outputFee = STAmount::mulRound (
saOutPassAct, saOfrRate, saTakerPays, true);
STAmount saInPassReq = std::min (saTakerPays, outputFee);
STAmount saInPassAct;
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeDeliverRev:"
<< " outputFee=" << outputFee
<< " saInPassReq=" << saInPassReq
<< " saOfrRate=" << saOfrRate
<< " saOutPassAct=" << saOutPassAct
<< " saOutPlusFees=" << saOutPlusFees;
if (!saInPassReq) // FIXME: This is bogus
{
// After rounding did not want anything.
WriteLog (lsDEBUG, RippleCalc)
<< "calcNodeDeliverRev: micro offer is unfunded.";
bEntryAdvance = true;
continue;
}
// Find out input amount actually available at current rate.
else if (!!uPrvAccountID)
{
// 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;
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeDeliverRev: account --> OFFER --> ? :"
<< " saInPassAct=" << saInPassAct;
}
else
{
// offer --> OFFER --> ?
// Compute in previous offer node how much could come in.
errorCode = calcNodeDeliverRev (
rippleCalc,
nodeIndex - 1,
pathState,
bMultiQuality,
uOfrOwnerID,
saInPassReq,
saInPassAct);
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeDeliverRev: offer --> OFFER --> ? :"
<< " saInPassAct=" << saInPassAct;
}
if (errorCode != tesSUCCESS)
break;
if (saInPassAct < saInPassReq)
{
// Adjust output to conform to limited input.
auto outputRequirements = STAmount::divRound (
saInPassAct, saOfrRate, saTakerGets, true);
saOutPassAct = std::min (saOutPassReq, outputRequirements);
auto outputFees = STAmount::mulRound (
saOutPassAct, saOutFeeRate, true);
saOutPlusFees = std::min (saOfferFunds, outputFees);
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeDeliverRev: adjusted:"
<< " saOutPassAct=" << saOutPassAct
<< " saOutPlusFees=" << saOutPlusFees;
}
else
{
// TODO(tom): more logging here.
assert (saInPassAct == saInPassReq);
}
// Funds were spent.
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.
errorCode = rippleCalc.mActiveLedger.accountSend (
uOfrOwnerID, uCurIssuerID, saOutPassAct);
if (errorCode != tesSUCCESS)
break;
// Adjust offer
STAmount saTakerGetsNew = saTakerGets - saOutPassAct;
STAmount saTakerPaysNew = saTakerPays - saInPassAct;
if (saTakerPaysNew < zero || saTakerGetsNew < zero)
{
WriteLog (lsWARNING, RippleCalc)
<< "calcNodeDeliverRev: NEGATIVE:"
<< " saTakerPaysNew=" << saTakerPaysNew
<< " saTakerGetsNew=%s" << saTakerGetsNew;
// If mOpenLedger then ledger is not final, can vote no.
errorCode = rippleCalc.mOpenLedger ? telFAILED_PROCESSING : tecFAILED_PROCESSING;
break;
}
sleOffer->setFieldAmount (sfTakerGets, saTakerGetsNew);
sleOffer->setFieldAmount (sfTakerPays, saTakerPaysNew);
rippleCalc.mActiveLedger.entryModify (sleOffer);
if (saOutPassAct == saTakerGets)
{
// Offer became unfunded.
WriteLog (lsDEBUG, RippleCalc)
<< "calcNodeDeliverRev: offer became unfunded.";
bEntryAdvance = true; // XXX When don't we want to set advance?
}
else
{
assert (saOutPassAct < saTakerGets);
}
saOutAct += saOutPassAct;
// Accumulate what is to be delivered from previous node.
saPrvDlvReq += saInPassAct;
}
CondLog (saOutAct > saOutReq, lsWARNING, RippleCalc)
<< "calcNodeDeliverRev: TOO MUCH:"
<< " saOutAct=" << saOutAct
<< " saOutReq=" << saOutReq;
assert (saOutAct <= saOutReq);
// XXX Perhaps need to check if partial is okay to relax this?
if (errorCode == tesSUCCESS && !saOutAct)
errorCode = tecPATH_DRY;
// Unable to meet request, consider path dry.
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeDeliverRev<"
<< " saOutAct=" << saOutAct
<< " saOutReq=" << saOutReq
<< " saPrvDlvReq=" << saPrvDlvReq;
return errorCode;
}
} // ripple

View File

@@ -0,0 +1,118 @@
//------------------------------------------------------------------------------
/*
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 "Calculators.h"
#include "RippleCalc.h"
#include "Tuning.h"
namespace ripple {
// 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.
TER calcNodeOfferFwd (
RippleCalc& rippleCalc,
const unsigned int nodeIndex,
PathState& pathState,
const bool bMultiQuality
)
{
TER errorCode;
PathState::Node& previousNode = pathState.vpnNodes [nodeIndex - 1];
if (!!previousNode.uAccountID)
{
// Previous is an account node, resolve its deliver.
STAmount saInAct;
STAmount saInFees;
errorCode = calcNodeDeliverFwd (
rippleCalc,
nodeIndex,
pathState,
bMultiQuality,
previousNode.uAccountID,
previousNode.saFwdDeliver, // Previous is sending this much.
saInAct,
saInFees);
assert (errorCode != tesSUCCESS ||
previousNode.saFwdDeliver == saInAct + saInFees);
}
else
{
// Previous is an offer. Deliver has already been resolved.
errorCode = tesSUCCESS;
}
return errorCode;
}
// Called to drive from the last offer node in a chain.
TER calcNodeOfferRev (
RippleCalc& rippleCalc,
const unsigned int nodeIndex,
PathState& pathState,
const bool bMultiQuality)
{
TER errorCode;
PathState::Node& node = pathState.vpnNodes [nodeIndex];
PathState::Node& nextNode = pathState.vpnNodes [nodeIndex + 1];
if (!!nextNode.uAccountID)
{
// Next is an account node, resolve current offer node's deliver.
STAmount saDeliverAct;
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeOfferRev: OFFER --> account:"
<< " nodeIndex=" << nodeIndex
<< " saRevDeliver=" << node.saRevDeliver;
errorCode = calcNodeDeliverRev (
rippleCalc,
nodeIndex,
pathState,
bMultiQuality,
nextNode.uAccountID,
// The next node wants the current node to deliver this much:
node.saRevDeliver,
saDeliverAct);
}
else
{
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeOfferRev: OFFER --> offer: nodeIndex=" << nodeIndex;
// Next is an offer. Deliver has already been resolved.
errorCode = tesSUCCESS;
}
return errorCode;
}
} // ripple

View File

@@ -0,0 +1,175 @@
//------------------------------------------------------------------------------
/*
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 "Calculators.h"
#include "RippleCalc.h"
#include "Tuning.h"
namespace ripple {
// 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.
//
// This routine works backwards:
// - cur is the driver: it calculates previous wants based on previous credit
// limits and current wants.
//
// This routine works forwards:
// - prv is the driver: 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 calcNodeRipple (
RippleCalc& rippleCalc,
const std::uint32_t uQualityIn,
const std::uint32_t uQualityOut,
const STAmount& saPrvReq, // --> in limit including fees, <0 = unlimited
const STAmount& saCurReq, // --> out limit (driver)
STAmount& saPrvAct, // <-> in limit including achieved so far: <-- <= -->
STAmount& saCurAct, // <-> out limit including achieved : <-- <= -->
std::uint64_t& uRateMax)
{
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeRipple>"
<< " uQualityIn=" << uQualityIn
<< " uQualityOut=" << uQualityOut
<< " saPrvReq=" << saPrvReq
<< " saCurReq=" << saCurReq
<< " saPrvAct=" << saPrvAct
<< " saCurAct=" << saCurAct;
assert (saCurReq > zero); // FIXME: saCurReq was zero
assert (saPrvReq.getCurrency () == saCurReq.getCurrency ());
assert (saPrvReq.getCurrency () == saPrvAct.getCurrency ());
assert (saPrvReq.getIssuer () == saPrvAct.getIssuer ());
const bool bPrvUnlimited = saPrvReq < zero;
const STAmount saPrv = bPrvUnlimited ? STAmount (saPrvReq)
: saPrvReq - saPrvAct;
const STAmount saCur = saCurReq - saCurAct;
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeRipple: "
<< " bPrvUnlimited=" << bPrvUnlimited
<< " saPrv=" << saPrv
<< " saCur=" << saCur;
if (uQualityIn >= uQualityOut)
{
// No fee.
WriteLog (lsTRACE, RippleCalc) << "calcNodeRipple: No fees";
// Only process if we are not worsing previously processed.
if (!uRateMax || STAmount::uRateOne <= uRateMax)
{
// Limit amount to transfer if need.
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.
saPrvAct += saTransfer;
saCurAct += saTransfer;
// If no rate limit, set rate limit to avoid combining with
// something with a worse rate.
if (!uRateMax)
uRateMax = STAmount::uRateOne;
}
}
else
{
// Fee.
WriteLog (lsTRACE, RippleCalc) << "calcNodeRipple: Fee";
std::uint64_t uRate = STAmount::getRate (
STAmount (uQualityOut), STAmount (uQualityIn));
if (!uRateMax || uRate <= uRateMax)
{
const uint160 uCurrencyID = saCur.getCurrency ();
const uint160 uCurIssuerID = saCur.getIssuer ();
// TODO(tom): what's this?
auto someFee = STAmount::mulRound (
saCur, uQualityOut, uCurrencyID, uCurIssuerID, true);
STAmount saCurIn = STAmount::divRound (
someFee, uQualityIn, uCurrencyID, uCurIssuerID, true);
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeRipple:"
<< " bPrvUnlimited=" << bPrvUnlimited
<< " saPrv=" << saPrv
<< " saCurIn=" << saCurIn;
if (bPrvUnlimited || saCurIn <= saPrv)
{
// All of cur. Some amount of prv.
saCurAct += saCur;
saPrvAct += saCurIn;
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeRipple:3c:"
<< " saCurReq=" << saCurReq
<< " saPrvAct=" << saPrvAct;
}
else
{
// TODO(tom): what's this?
auto someFee = STAmount::mulRound (
saPrv, uQualityIn, uCurrencyID, uCurIssuerID, true);
// A part of cur. All of prv. (cur as driver)
STAmount saCurOut = STAmount::divRound (
someFee, uQualityOut, uCurrencyID, uCurIssuerID, true);
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeRipple:4: saCurReq=" << saCurReq;
saCurAct += saCurOut;
saPrvAct = saPrvReq;
}
if (!uRateMax)
uRateMax = uRate;
}
}
WriteLog (lsTRACE, RippleCalc)
<< "calcNodeRipple<"
<< " uQualityIn=" << uQualityIn
<< " uQualityOut=" << uQualityOut
<< " saPrvReq=" << saPrvReq
<< " saCurReq=" << saCurReq
<< " saPrvAct=" << saPrvAct
<< " saCurAct=" << saCurAct;
}
} // ripple

View File

@@ -0,0 +1,111 @@
//------------------------------------------------------------------------------
/*
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_CALCSTATE_H
#define RIPPLE_CALCSTATE_H
namespace ripple {
typedef TER ErrorCode;
class CalcState {
public:
CalcState(
unsigned int nodeIndex, PathState& state, LedgerEntrySet& ledger, bool quality)
: nodeIndex_(nodeIndex),
pathState_(state),
ledger_(ledger),
quality_(quality)
{}
enum Direction { BACKWARD, FORWARD };
TER calc(Direction);
TER calcAccount(Direction);
TER calcOffer(Direction);
TER calcDeliver(Direction);
TER calcAdvance(Direction);
void nextPath(LedgerEntrySet const& checkpoint) const;
private:
enum NodeCursor { FIRST, PREVIOUS, CURRENT, NEXT, LAST };
unsigned int index(NodeCursor cursor = CURRENT) const
{
switch (cursor) {
case FIRST:
return 0;
case PREVIOUS:
return nodeIndex_ ? nodeIndex_ - 1 : 0;
case CURRENT:
return nodeIndex_;
default:
break;
}
unsigned int size = pathState_.vpnNodes.size ();
auto last = size ? size - 1 : 0;
switch (cursor) {
case NEXT:
return std::min(nodeIndex_ + 1, last);
case LAST:
return last;
default:
bassert(false);
}
}
PathState::Node& node(NodeCursor cursor = CURRENT)
{
return pathState_.vpnNodes[index(cursor)];
}
CalcState state(NodeCursor cursor = CURRENT)
{
return CalcState(index(cursor), pathState_, ledger_, quality_);
}
LedgerEntrySet& ledger()
{
return ledger_;
}
bool quality() const
{
return quality_;
}
private:
unsigned int const nodeIndex_;
PathState& pathState_;
LedgerEntrySet& ledger_;
bool const quality_;
};
inline bool isAccount(PathState::Node const& node)
{
return is_bit_set (node.uFlags, STPathElement::typeAccount);
}
inline STAmount copyCurrencyAndIssuer(const STAmount& a)
{
return STAmount(a.getCurrency(), a.getIssuer());
}
} // ripple
#endif

View File

@@ -0,0 +1,109 @@
//------------------------------------------------------------------------------
/*
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_PATHS_CALCULATORS_H
#define RIPPLE_PATHS_CALCULATORS_H
namespace ripple {
// TODO(vfalco) What's the difference between a RippleState versus PathState?
//
struct RippleCalc
{
LedgerEntrySet& mActiveLedger;
bool mOpenLedger;
// First time working in reverse a funding source was mentioned. Source may
// only be used there.
//
// Map of currency, issuer to node index.
AccountCurrencyIssuerToNodeIndex mumSource;
// If the transaction fails to meet some constraint, still need to delete
// unfunded offers.
//
// Offers that were found unfunded.
unordered_set<uint256> mUnfundedOffers;
RippleCalc (LedgerEntrySet& activeLedger, const bool bOpenLedger)
: mActiveLedger (activeLedger), mOpenLedger (bOpenLedger)
{
}
};
void pathNext (
RippleCalc&,
PathState& pathState, const bool bMultiQuality,
const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent);
TER calcNodeRev (
RippleCalc&,
unsigned int nodeIndex, PathState& pathState, bool bMultiQuality);
TER calcNodeFwd (
RippleCalc&,
unsigned int nodeIndex, PathState& pathState, bool bMultiQuality);
TER calcNodeOfferRev (
RippleCalc&,
unsigned int nodeIndex, PathState& pathState, bool bMultiQuality);
TER calcNodeOfferFwd (
RippleCalc&,
unsigned int nodeIndex, PathState& pathState, bool bMultiQuality);
TER calcNodeAccountRev (
RippleCalc&,
unsigned int nodeIndex, PathState& pathState, bool bMultiQuality);
TER calcNodeAccountFwd (
RippleCalc&,
unsigned int nodeIndex, PathState& pathState, bool bMultiQuality);
TER calcNodeAdvance (
RippleCalc&,
unsigned int nodeIndex, PathState& pathState, bool bMultiQuality,
bool bReverse);
TER calcNodeDeliverRev (
RippleCalc&,
const unsigned int nodeIndex,
PathState& pathState,
const bool bMultiQuality,
const uint160& uOutAccountID,
const STAmount& saOutReq,
STAmount& saOutAct);
TER calcNodeDeliverFwd (
RippleCalc&,
const unsigned int nodeIndex,
PathState& pathState,
const bool bMultiQuality,
const uint160& uInAccountID,
const STAmount& saInReq,
STAmount& saInAct,
STAmount& saInFees);
void calcNodeRipple (
RippleCalc&,
const std::uint32_t uQualityIn, const std::uint32_t uQualityOut,
const STAmount& saPrvReq, const STAmount& saCurReq,
STAmount& saPrvAct, STAmount& saCurAct, std::uint64_t& uRateMax);
void setCanonical (
STPathSet& spsDst, const std::vector<PathState::pointer>& vpsExpanded,
bool bKeepDefault);
} // ripple
#endif

View File

@@ -0,0 +1,107 @@
//------------------------------------------------------------------------------
/*
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 "Calculators.h"
#include "CalcState.h"
#include "RippleCalc.h"
#include "Tuning.h"
namespace ripple {
// 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
void pathNext (
RippleCalc& rippleCalc,
PathState& pathState, const bool bMultiQuality,
const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent)
{
// The next state is what is available in preference order.
// This is calculated when referenced accounts changed.
const unsigned int lastNodeIndex = pathState.vpnNodes.size () - 1;
pathState.bConsumed = false;
// YYY This clearing should only be needed for nice logging.
pathState.saInPass = STAmount (
pathState.saInReq.getCurrency (), pathState.saInReq.getIssuer ());
pathState.saOutPass = STAmount (
pathState.saOutReq.getCurrency (), pathState.saOutReq.getIssuer ());
pathState.vUnfundedBecame.clear ();
pathState.umReverse.clear ();
WriteLog (lsTRACE, RippleCalc)
<< "pathNext: Path In: " << pathState.getJson ();
assert (pathState.vpnNodes.size () >= 2);
lesCurrent = lesCheckpoint.duplicate (); // Restore from checkpoint.
for (unsigned int uIndex = pathState.vpnNodes.size (); uIndex--;)
{
auto& node = pathState.vpnNodes[uIndex];
node.saRevRedeem.clear ();
node.saRevIssue.clear ();
node.saRevDeliver.clear ();
node.saFwdDeliver.clear ();
}
pathState.terStatus = calcNodeRev (rippleCalc, lastNodeIndex, pathState, bMultiQuality);
WriteLog (lsTRACE, RippleCalc)
<< "pathNext: Path after reverse: " << pathState.getJson ();
if (tesSUCCESS == pathState.terStatus)
{
// Do forward.
lesCurrent = lesCheckpoint.duplicate (); // Restore from checkpoint.
pathState.terStatus = calcNodeFwd (rippleCalc, 0, pathState, bMultiQuality);
}
if (tesSUCCESS == pathState.terStatus)
{
CondLog (!pathState.saInPass || !pathState.saOutPass, lsDEBUG, RippleCalc)
<< "pathNext: Error calcNodeFwd reported success for nothing:"
<< " saOutPass=" << pathState.saOutPass
<< " saInPass=" << pathState.saInPass;
if (!pathState.saOutPass || !pathState.saInPass)
throw std::runtime_error ("Made no progress.");
// Calculate relative quality.
pathState.uQuality = STAmount::getRate (
pathState.saOutPass, pathState.saInPass);
WriteLog (lsTRACE, RippleCalc)
<< "pathNext: Path after forward: " << pathState.getJson ();
}
else
{
pathState.uQuality = 0;
}
}
} // ripple

View File

@@ -17,6 +17,10 @@
*/
//==============================================================================
#include <tuple>
#include "Calculators.h"
namespace ripple {
SETUP_LOG (PathRequest)
@@ -422,25 +426,25 @@ Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast)
(currIssuer.first.isZero () ? ACCOUNT_XRP : raSrcAccount.getAccountID ()), 1);
saMaxAmount.negate ();
m_journal.debug << iIdentifier << " Paths found, calling rippleCalc";
TER terResult = RippleCalc::rippleCalc (lesSandbox, saMaxAmountAct, saDstAmountAct,
TER errorCode = rippleCalculate (lesSandbox, saMaxAmountAct, saDstAmountAct,
vpsExpanded, saMaxAmount, saDstAmount,
raDstAccount.getAccountID (), raSrcAccount.getAccountID (),
spsPaths, false, false, false, true);
if ((extraPath.size() > 0) && ((terResult == terNO_LINE) || (terResult == tecPATH_PARTIAL)))
if ((extraPath.size() > 0) && ((errorCode == terNO_LINE) || (errorCode == tecPATH_PARTIAL)))
{
m_journal.debug << iIdentifier << " Trying with an extra path element";
spsPaths.addPath(extraPath);
vpsExpanded.clear ();
terResult = RippleCalc::rippleCalc (lesSandbox, saMaxAmountAct, saDstAmountAct,
errorCode = rippleCalculate (lesSandbox, saMaxAmountAct, saDstAmountAct,
vpsExpanded, saMaxAmount, saDstAmount,
raDstAccount.getAccountID (), raSrcAccount.getAccountID (),
spsPaths, false, false, false, true);
m_journal.debug << iIdentifier << " Extra path element gives " << transHuman (terResult);
m_journal.debug << iIdentifier << " Extra path element gives " << transHuman (errorCode);
}
if (terResult == tesSUCCESS)
if (errorCode == tesSUCCESS)
{
Json::Value jvEntry (Json::objectValue);
jvEntry["source_amount"] = saMaxAmountAct.getJson (0);
@@ -450,7 +454,7 @@ Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast)
}
else
{
m_journal.debug << iIdentifier << " rippleCalc returns " << transHuman (terResult);
m_journal.debug << iIdentifier << " rippleCalc returns " << transHuman (errorCode);
}
}
else

View File

@@ -26,12 +26,12 @@ namespace ripple {
// all liquidity is used.
//
class RippleCalc; // for logging
struct RippleCalc; // for logging
std::size_t hash_value (const aciSource& asValue)
std::size_t hash_value (const AccountCurrencyIssuer& asValue)
{
std::size_t const seed = 0;
return beast::hardened_hash<aciSource>{seed}(asValue);
return beast::hardened_hash<AccountCurrencyIssuer>{seed}(asValue);
}
// Compare the non-calculated fields.
@@ -126,19 +126,19 @@ TER PathState::pushImply (
const uint160& uCurrencyID, // --> Delivering this currency.
const uint160& uIssuerID) // --> Delivering this issuer.
{
const Node& pnPrv = vpnNodes.back ();
TER terResult = tesSUCCESS;
const Node& previousNode = vpnNodes.back ();
TER errorCode = tesSUCCESS;
WriteLog (lsTRACE, RippleCalc) << "pushImply>" <<
" " << RippleAddress::createHumanAccountID (uAccountID) <<
" " << STAmount::createHumanCurrency (uCurrencyID) <<
" " << RippleAddress::createHumanAccountID (uIssuerID);
if (pnPrv.uCurrencyID != uCurrencyID)
if (previousNode.uCurrencyID != uCurrencyID)
{
// Currency is different, need to convert via an offer.
terResult = pushNode ( // Offer.
errorCode = pushNode ( // Offer.
!!uCurrencyID
? STPathElement::typeCurrency | STPathElement::typeIssuer
: STPathElement::typeCurrency,
@@ -150,37 +150,37 @@ TER PathState::pushImply (
const Node& pnBck = vpnNodes.back ();
// For ripple, non-XRP, ensure the issuer is on at least one side of the transaction.
if (tesSUCCESS == terResult
&& !!uCurrencyID // Not XRP.
&& (pnBck.uAccountID != uIssuerID // Previous is not issuing own IOUs.
&& uAccountID != uIssuerID)) // Current is not receiving own IOUs.
if (errorCode == tesSUCCESS
&& !!uCurrencyID // Not XRP.
&& (pnBck.uAccountID != uIssuerID // Previous is not issuing own IOUs.
&& uAccountID != uIssuerID)) // Current is not receiving own IOUs.
{
// Need to ripple through uIssuerID's account.
terResult = pushNode (
errorCode = pushNode (
STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer,
uIssuerID, // Intermediate account is the needed issuer.
uCurrencyID,
uIssuerID);
}
WriteLog (lsTRACE, RippleCalc) << "pushImply< : " << transToken (terResult);
WriteLog (lsTRACE, RippleCalc) << "pushImply< : " << transToken (errorCode);
return terResult;
return errorCode;
}
// Append a node and insert before it any implied nodes.
// Offers may go back to back.
// <-- terResult: tesSUCCESS, temBAD_PATH, terNO_ACCOUNT, terNO_AUTH, terNO_LINE, tecPATH_DRY
// <-- errorCode: tesSUCCESS, temBAD_PATH, terNO_ACCOUNT, terNO_AUTH, terNO_LINE, tecPATH_DRY
TER PathState::pushNode (
const int iType,
const uint160& uAccountID,
const uint160& uCurrencyID,
const uint160& uIssuerID)
{
Node pnCur;
Node node;
const bool bFirst = vpnNodes.empty ();
const Node& pnPrv = bFirst ? Node () : vpnNodes.back ();
const Node& previousNode = bFirst ? Node () : vpnNodes.back ();
// true, iff node is a ripple account. false, iff node is an offer node.
const bool bAccount = is_bit_set (iType, STPathElement::typeAccount);
// true, iff currency supplied.
@@ -188,7 +188,7 @@ TER PathState::pushNode (
const bool bCurrency = is_bit_set (iType, STPathElement::typeCurrency);
// Issuer is specified for the output of the current node.
const bool bIssuer = is_bit_set (iType, STPathElement::typeIssuer);
TER terResult = tesSUCCESS;
TER errorCode = tesSUCCESS;
WriteLog (lsTRACE, RippleCalc) << "pushNode> " <<
iType <<
@@ -196,47 +196,47 @@ TER PathState::pushNode (
" " << (bCurrency ? STAmount::createHumanCurrency (uCurrencyID) : "-") <<
"/" << (bIssuer ? RippleAddress::createHumanAccountID (uIssuerID) : "-");
pnCur.uFlags = iType;
pnCur.uCurrencyID = bCurrency ? uCurrencyID : pnPrv.uCurrencyID;
node.uFlags = iType;
node.uCurrencyID = bCurrency ? uCurrencyID : previousNode.uCurrencyID;
if (iType & ~STPathElement::typeValidBits)
{
WriteLog (lsDEBUG, RippleCalc) << "pushNode: bad bits.";
terResult = temBAD_PATH;
errorCode = temBAD_PATH;
}
else if (bIssuer && !pnCur.uCurrencyID)
else if (bIssuer && !node.uCurrencyID)
{
WriteLog (lsDEBUG, RippleCalc) << "pushNode: issuer specified for XRP.";
terResult = temBAD_PATH;
errorCode = temBAD_PATH;
}
else if (bIssuer && !uIssuerID)
{
WriteLog (lsDEBUG, RippleCalc) << "pushNode: specified bad issuer.";
terResult = temBAD_PATH;
errorCode = temBAD_PATH;
}
else if (!bAccount && !bCurrency && !bIssuer)
{
WriteLog (lsDEBUG, RippleCalc) << "pushNode: offer must specify at least currency or issuer.";
terResult = temBAD_PATH;
errorCode = temBAD_PATH;
}
else if (bAccount)
{
// Account link
pnCur.uAccountID = uAccountID;
pnCur.uIssuerID = bIssuer
node.uAccountID = uAccountID;
node.uIssuerID = bIssuer
? uIssuerID
: !!pnCur.uCurrencyID
: !!node.uCurrencyID
? uAccountID
: ACCOUNT_XRP;
pnCur.saRevRedeem = STAmount (pnCur.uCurrencyID, uAccountID);
pnCur.saRevIssue = STAmount (pnCur.uCurrencyID, uAccountID);
pnCur.saRevDeliver = STAmount (pnCur.uCurrencyID, pnCur.uIssuerID);
pnCur.saFwdDeliver = pnCur.saRevDeliver;
node.saRevRedeem = STAmount (node.uCurrencyID, uAccountID);
node.saRevIssue = STAmount (node.uCurrencyID, uAccountID);
node.saRevDeliver = STAmount (node.uCurrencyID, node.uIssuerID);
node.saFwdDeliver = node.saRevDeliver;
if (bFirst)
{
@@ -248,62 +248,62 @@ TER PathState::pushNode (
{
WriteLog (lsDEBUG, RippleCalc) << "pushNode: specified bad account.";
terResult = temBAD_PATH;
errorCode = temBAD_PATH;
}
else
{
// Add required intermediate nodes to deliver to current account.
WriteLog (lsTRACE, RippleCalc) << "pushNode: imply for account.";
terResult = pushImply (
pnCur.uAccountID, // Current account.
pnCur.uCurrencyID, // Wanted currency.
!!pnCur.uCurrencyID ? uAccountID : ACCOUNT_XRP); // Account as wanted issuer.
errorCode = pushImply (
node.uAccountID, // Current account.
node.uCurrencyID, // Wanted currency.
!!node.uCurrencyID ? uAccountID : ACCOUNT_XRP); // Account as wanted issuer.
// Note: pnPrv may no longer be the immediately previous node.
// Note: previousNode may no longer be the immediately previous node.
}
if (tesSUCCESS == terResult && !vpnNodes.empty ())
if (errorCode == tesSUCCESS && !vpnNodes.empty ())
{
const Node& pnBck = vpnNodes.back ();
bool bBckAccount = is_bit_set (pnBck.uFlags, STPathElement::typeAccount);
if (bBckAccount)
{
SLE::pointer sleRippleState = lesEntries.entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex (pnBck.uAccountID, pnCur.uAccountID, pnPrv.uCurrencyID));
SLE::pointer sleRippleState = lesEntries.entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex (pnBck.uAccountID, node.uAccountID, previousNode.uCurrencyID));
if (!sleRippleState)
{
WriteLog (lsTRACE, RippleCalc) << "pushNode: No credit line between "
<< RippleAddress::createHumanAccountID (pnBck.uAccountID)
<< " and "
<< RippleAddress::createHumanAccountID (pnCur.uAccountID)
<< RippleAddress::createHumanAccountID (node.uAccountID)
<< " for "
<< STAmount::createHumanCurrency (pnCur.uCurrencyID)
<< STAmount::createHumanCurrency (node.uCurrencyID)
<< "." ;
WriteLog (lsTRACE, RippleCalc) << getJson ();
terResult = terNO_LINE;
errorCode = terNO_LINE;
}
else
{
WriteLog (lsTRACE, RippleCalc) << "pushNode: Credit line found between "
<< RippleAddress::createHumanAccountID (pnBck.uAccountID)
<< " and "
<< RippleAddress::createHumanAccountID (pnCur.uAccountID)
<< RippleAddress::createHumanAccountID (node.uAccountID)
<< " for "
<< STAmount::createHumanCurrency (pnCur.uCurrencyID)
<< STAmount::createHumanCurrency (node.uCurrencyID)
<< "." ;
SLE::pointer sleBck = lesEntries.entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (pnBck.uAccountID));
bool bHigh = pnBck.uAccountID > pnCur.uAccountID;
bool bHigh = pnBck.uAccountID > node.uAccountID;
if (!sleBck)
{
WriteLog (lsWARNING, RippleCalc) << "pushNode: delay: can't receive IOUs from non-existent issuer: " << RippleAddress::createHumanAccountID (pnBck.uAccountID);
terResult = terNO_ACCOUNT;
errorCode = terNO_ACCOUNT;
}
else if ((is_bit_set (sleBck->getFieldU32 (sfFlags), lsfRequireAuth)
&& !is_bit_set (sleRippleState->getFieldU32 (sfFlags), (bHigh ? lsfHighAuth : lsfLowAuth)))
@@ -311,76 +311,76 @@ TER PathState::pushNode (
{
WriteLog (lsWARNING, RippleCalc) << "pushNode: delay: can't receive IOUs from issuer without auth.";
terResult = terNO_AUTH;
errorCode = terNO_AUTH;
}
if (tesSUCCESS == terResult)
if (errorCode == tesSUCCESS)
{
STAmount saOwed = lesEntries.rippleOwed (pnCur.uAccountID, pnBck.uAccountID, pnCur.uCurrencyID);
STAmount saOwed = lesEntries.rippleOwed (node.uAccountID, pnBck.uAccountID, node.uCurrencyID);
STAmount saLimit;
if (saOwed <= zero
&& -saOwed >= (saLimit = lesEntries.rippleLimit (pnCur.uAccountID, pnBck.uAccountID, pnCur.uCurrencyID)))
&& -saOwed >= (saLimit = lesEntries.rippleLimit (node.uAccountID, pnBck.uAccountID, node.uCurrencyID)))
{
WriteLog (lsWARNING, RippleCalc) <<
"pushNode: dry:" <<
" saOwed=" << saOwed <<
" saLimit=" << saLimit;
terResult = tecPATH_DRY;
errorCode = tecPATH_DRY;
}
}
}
}
}
if (tesSUCCESS == terResult)
if (errorCode == tesSUCCESS)
{
vpnNodes.push_back (pnCur);
vpnNodes.push_back (node);
}
}
else
{
// Offer link
// Offers bridge a change in currency & issuer or just a change in issuer.
pnCur.uIssuerID = bIssuer
node.uIssuerID = bIssuer
? uIssuerID
: !!pnCur.uCurrencyID
? !!pnPrv.uIssuerID
? pnPrv.uIssuerID // Default to previous issuer
: pnPrv.uAccountID // Or previous account if no previous issuer.
: !!node.uCurrencyID
? !!previousNode.uIssuerID
? previousNode.uIssuerID // Default to previous issuer
: previousNode.uAccountID // Or previous account if no previous issuer.
: ACCOUNT_XRP;
pnCur.saRateMax = saZero;
pnCur.saRevDeliver = STAmount (pnCur.uCurrencyID, pnCur.uIssuerID);
pnCur.saFwdDeliver = pnCur.saRevDeliver;
node.saRateMax = saZero;
node.saRevDeliver = STAmount (node.uCurrencyID, node.uIssuerID);
node.saFwdDeliver = node.saRevDeliver;
if (!!pnCur.uCurrencyID != !!pnCur.uIssuerID)
if (!!node.uCurrencyID != !!node.uIssuerID)
{
WriteLog (lsDEBUG, RippleCalc) << "pushNode: currency is inconsistent with issuer.";
terResult = temBAD_PATH;
errorCode = temBAD_PATH;
}
else if (!!pnPrv.uAccountID)
else if (!!previousNode.uAccountID)
{
// Previous is an account.
WriteLog (lsTRACE, RippleCalc) << "pushNode: imply for offer.";
// Insert intermediary issuer account if needed.
terResult = pushImply (
errorCode = pushImply (
ACCOUNT_XRP, // Rippling, but offers don't have an account.
pnPrv.uCurrencyID,
pnPrv.uIssuerID);
previousNode.uCurrencyID,
previousNode.uIssuerID);
}
if (tesSUCCESS == terResult)
if (errorCode == tesSUCCESS)
{
vpnNodes.push_back (pnCur);
vpnNodes.push_back (node);
}
}
WriteLog (lsTRACE, RippleCalc) << "pushNode< : " << transToken (terResult);
WriteLog (lsTRACE, RippleCalc) << "pushNode< : " << transToken (errorCode);
return terResult;
return errorCode;
}
// Set to an expanded path.
@@ -440,7 +440,7 @@ void PathState::setExpanded (
const uint160 uNxtCurrencyID = spSourcePath.size ()
? spSourcePath.getElement (0).getCurrency () // Use next node.
: uOutCurrencyID; // Use send.
const uint160 uNxtAccountID = spSourcePath.size ()
const uint160 nextAccountID = spSourcePath.size ()
? spSourcePath.getElement (0).getAccountID ()
: !!uOutCurrencyID
? uOutIssuerID == uReceiverID
@@ -452,12 +452,12 @@ void PathState::setExpanded (
" uMaxIssuerID=" << RippleAddress::createHumanAccountID (uMaxIssuerID) <<
" uSenderIssuerID=" << RippleAddress::createHumanAccountID (uSenderIssuerID) <<
" uNxtCurrencyID=" << STAmount::createHumanCurrency (uNxtCurrencyID) <<
" uNxtAccountID=" << RippleAddress::createHumanAccountID (uNxtAccountID);
" nextAccountID=" << RippleAddress::createHumanAccountID (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 != uNxtAccountID) // Next is not implied issuer
|| uMaxIssuerID != nextAccountID) // Next is not implied issuer
{
WriteLog (lsDEBUG, RippleCalc) << "setExpanded: sender implied:" <<
" account=" << RippleAddress::createHumanAccountID (uMaxIssuerID) <<
@@ -481,18 +481,18 @@ void PathState::setExpanded (
{
WriteLog (lsTRACE, RippleCalc) << "setExpanded: element in path";
terStatus = pushNode (
speElement.getNodeType (), speElement.getAccountID (),
speElement.getNodeType (), speElement.getAccountID (),
speElement.getCurrency (), speElement.getIssuerID ());
}
}
const Node& pnPrv = vpnNodes.back ();
const Node& previousNode = vpnNodes.back ();
if (tesSUCCESS == terStatus
&& !!uOutCurrencyID // Next is not XRP
&& uOutIssuerID != uReceiverID // Out issuer is not receiver
&& (pnPrv.uCurrencyID != uOutCurrencyID // Previous will be an offer.
|| pnPrv.uAccountID != uOutIssuerID)) // Need the implied issuer.
&& (previousNode.uCurrencyID != uOutCurrencyID // Previous will be an offer.
|| previousNode.uAccountID != uOutIssuerID)) // Need the implied issuer.
{
// Add implied account.
WriteLog (lsDEBUG, RippleCalc) << "setExpanded: receiver implied:" <<
@@ -530,11 +530,13 @@ void PathState::setExpanded (
const unsigned int uNodes = vpnNodes.size ();
for (unsigned int uNode = 0; tesSUCCESS == terStatus && uNode != uNodes; ++uNode)
for (unsigned int nodeIndex = 0; tesSUCCESS == terStatus && nodeIndex != uNodes; ++nodeIndex)
{
const Node& pnCur = vpnNodes[uNode];
const Node& node = vpnNodes[nodeIndex];
if (!umForward.insert (std::make_pair (std::make_tuple (pnCur.uAccountID, pnCur.uCurrencyID, pnCur.uIssuerID), uNode)).second)
AccountCurrencyIssuer aci(
node.uAccountID, node.uCurrencyID, node.uIssuerID);
if (!umForward.insert (std::make_pair (aci, nodeIndex)).second)
{
// Failed to insert. Have a loop.
WriteLog (lsDEBUG, RippleCalc) <<
@@ -589,7 +591,7 @@ void PathState::setCanonical (
const uint160 uOutCurrencyID = saOutAct.getCurrency ();
const uint160 uOutIssuerID = saOutAct.getIssuer ();
unsigned int uNode = 0;
unsigned int nodeIndex = 0;
unsigned int uEnd = psExpanded.vpnNodes.size (); // The node, indexed by 0, not to include.
@@ -600,7 +602,7 @@ void PathState::setCanonical (
uint160 uIssuerID = uMaxIssuerID;
// Node 0 is a composite of the sending account and saInAct.
++uNode; // skip node 0
++nodeIndex; // skip node 0
// Last node is implied: Always skip last node
--uEnd; // skip last node
@@ -609,21 +611,21 @@ void PathState::setCanonical (
// - currency is always the same as vpnNodes[0].
#if 1
if (uNode != uEnd && uMaxIssuerID != uAccountID)
if (nodeIndex != uEnd && uMaxIssuerID != uAccountID)
{
// saInAct issuer is not the sender. This forces an implied node.
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: in diff: uNode=%d uEnd=%d") % uNode % uEnd);
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: in diff: nodeIndex=%d uEnd=%d") % nodeIndex % uEnd);
// skip node 1
uIssuerID = psExpanded.vpnNodes[uNode].uIssuerID;
uIssuerID = psExpanded.vpnNodes[nodeIndex].uIssuerID;
++uNode;
++nodeIndex;
}
#else
if (uNode != uEnd)
if (nodeIndex != uEnd)
{
// Have another node
bool bKeep = false;
@@ -656,17 +658,17 @@ void PathState::setCanonical (
if (bKeep)
{
uCurrencyID = psExpanded.vpnNodes[uNode].uCurrencyID;
uIssuerID = psExpanded.vpnNodes[uNode].uIssuerID;
++uNode; // Keep it.
uCurrencyID = psExpanded.vpnNodes[nodeIndex].uCurrencyID;
uIssuerID = psExpanded.vpnNodes[nodeIndex].uIssuerID;
++nodeIndex; // Keep it.
}
}
#endif
if (uNode != uEnd && !!uOutCurrencyID && uOutIssuerID != uDstAccountID)
if (nodeIndex != uEnd && !!uOutCurrencyID && uOutIssuerID != uDstAccountID)
{
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: out diff: uNode=%d uEnd=%d") % uNode % uEnd);
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: out diff: nodeIndex=%d uEnd=%d") % nodeIndex % uEnd);
// The next to last node is saOutAct if an issuer different from receiver is supplied.
// The next to last node can be implied.
@@ -675,33 +677,33 @@ void PathState::setCanonical (
const Node& pnEnd = psExpanded.vpnNodes[uEnd];
if (uNode != uEnd
if (nodeIndex != uEnd
&& !pnEnd.uAccountID && pnEnd.uCurrencyID == uOutCurrencyID && pnEnd.uIssuerID == uOutIssuerID)
{
// The current end node is an offer converting to saOutAct's currency and issuer and can be implied.
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: out offer: uNode=%d uEnd=%d") % uNode % uEnd);
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: out offer: nodeIndex=%d uEnd=%d") % nodeIndex % uEnd);
--uEnd;
}
// Do not include uEnd.
for (; uNode != uEnd; ++uNode)
for (; nodeIndex != uEnd; ++nodeIndex)
{
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: loop: uNode=%d uEnd=%d") % uNode % uEnd);
const Node& pnPrv = psExpanded.vpnNodes[uNode - 1];
const Node& pnCur = psExpanded.vpnNodes[uNode];
const Node& pnNxt = psExpanded.vpnNodes[uNode + 1];
// WriteLog (lsDEBUG, RippleCalc) << boost::str(boost::format("setCanonical: loop: nodeIndex=%d uEnd=%d") % nodeIndex % uEnd);
const Node& previousNode = psExpanded.vpnNodes[nodeIndex - 1];
const Node& node = psExpanded.vpnNodes[nodeIndex];
const Node& nextNode = psExpanded.vpnNodes[nodeIndex + 1];
const bool bCurAccount = is_bit_set (pnCur.uFlags, STPathElement::typeAccount);
const bool nodeIsAccount = is_bit_set (node.uFlags, STPathElement::typeAccount);
bool bSkip = false;
if (bCurAccount)
if (nodeIsAccount)
{
// Currently at an account.
// Output is non-XRP and issuer is account.
if (!!pnCur.uCurrencyID && pnCur.uIssuerID == pnCur.uAccountID)
if (!!node.uCurrencyID && node.uIssuerID == node.uAccountID)
{
// Account issues itself.
// XXX Not good enough. Previous account must mention it.
@@ -712,11 +714,11 @@ void PathState::setCanonical (
else
{
// Currently at an offer.
const bool bPrvAccount = is_bit_set (pnPrv.uFlags, STPathElement::typeAccount);
const bool bNxtAccount = is_bit_set (pnNxt.uFlags, STPathElement::typeAccount);
const bool bPrvAccount = is_bit_set (previousNode.uFlags, STPathElement::typeAccount);
const bool bNxtAccount = is_bit_set (nextNode.uFlags, STPathElement::typeAccount);
if (bPrvAccount && bNxtAccount // Offer surrounded by accounts.
&& pnPrv.uCurrencyID != pnNxt.uCurrencyID)
&& previousNode.uCurrencyID != nextNode.uCurrencyID)
{
// Offer can be implied by currency change.
// XXX What about issuer?
@@ -730,26 +732,25 @@ void PathState::setCanonical (
// Copy node
Node pnNew;
bool bSetAccount = bCurAccount;
bool bSetCurrency = uCurrencyID != pnCur.uCurrencyID;
bool bSetCurrency = (uCurrencyID != node.uCurrencyID);
// XXX What if we need the next account because we want to skip it?
bool bSetIssuer = !uCurrencyID && uIssuerID != pnCur.uIssuerID;
bool bSetIssuer = !uCurrencyID && (uIssuerID != node.uIssuerID);
pnNew.uFlags = (bSetAccount ? STPathElement::typeAccount : 0)
pnNew.uFlags = (nodeIsAccount ? STPathElement::typeAccount : 0)
| (bSetCurrency ? STPathElement::typeCurrency : 0)
| (bSetIssuer ? STPathElement::typeIssuer : 0);
if (bSetAccount)
pnNew.uAccountID = pnCur.uAccountID;
if (nodeIsAccount)
pnNew.uAccountID = node.uAccountID;
if (bSetCurrency)
{
pnNew.uCurrencyID = pnCur.uCurrencyID;
pnNew.uCurrencyID = node.uCurrencyID;
uCurrencyID = pnNew.uCurrencyID;
}
if (bSetIssuer)
pnNew.uIssuerID = pnCur.uIssuerID;
pnNew.uIssuerID = node.uIssuerID;
// XXX ^^^ What about setting uIssuerID?

View File

@@ -23,11 +23,13 @@
namespace ripple {
// account id, currency id, issuer id :: node
typedef std::tuple <uint160, uint160, uint160> aciSource;
typedef ripple::unordered_map <aciSource, unsigned int> curIssuerNode; // Map of currency, issuer to node index.
typedef ripple::unordered_map <aciSource, unsigned int>::const_iterator curIssuerNodeConstIterator;
typedef std::tuple <uint160, uint160, uint160> AccountCurrencyIssuer;
extern std::size_t hash_value (const aciSource& asValue);
// Map of currency, issuer to node index.
typedef ripple::unordered_map <AccountCurrencyIssuer, unsigned int>
AccountCurrencyIssuerToNodeIndex;
extern std::size_t hash_value (const AccountCurrencyIssuer& asValue);
// Holds a path state under incremental application.
class PathState : public CountedObject <PathState>
@@ -89,11 +91,10 @@ public:
STAmount saTakerGets;
};
public:
typedef boost::shared_ptr<PathState> pointer;
typedef const boost::shared_ptr<PathState>& ref;
public:
typedef std::shared_ptr<PathState> pointer;
typedef const std::shared_ptr<PathState>& ref;
PathState* setIndex (const int iIndex)
{
mIndex = iIndex;
@@ -131,27 +132,12 @@ public:
void checkNoRipple (uint160 const& destinationAccountID, uint160 const& sourceAccountID);
void checkNoRipple (uint160 const&, uint160 const&, uint160 const&, uint160 const&);
void setCanonical (
const PathState& psExpanded
);
void setCanonical (const PathState& psExpanded);
Json::Value getJson () const;
#if 0
static PathState::pointer createCanonical (
PathState& ref pspExpanded
)
{
PathState::pointer pspNew = boost::make_shared<PathState> (pspExpanded->saOutAct, pspExpanded->saInAct);
pspNew->setCanonical (pspExpanded);
return pspNew;
}
#endif
static bool lessPriority (PathState& lhs, PathState& rhs);
public:
TER terStatus;
std::vector<Node> vpnNodes;
@@ -160,11 +146,11 @@ public:
// First time scanning foward, as part of path contruction, a funding source was mentioned for accounts. Source may only be
// used there.
curIssuerNode umForward; // Map of currency, issuer to node index.
AccountCurrencyIssuerToNodeIndex umForward;
// First time working in reverse a funding source was used.
// Source may only be used there if not mentioned by an account.
curIssuerNode umReverse; // Map of currency, issuer to node index.
AccountCurrencyIssuerToNodeIndex umReverse;
LedgerEntrySet lesEntries;

View File

@@ -20,6 +20,8 @@
#include <tuple>
#include "Calculators.h"
namespace ripple {
SETUP_LOG (Pathfinder)
@@ -244,7 +246,7 @@ STPathSet Pathfinder::filterPaths(int iMaxPaths, STPath& extraPath)
std::vector<PathState::pointer> vpsExpanded;
LedgerEntrySet lesSandbox (mLedger, tapNONE);
TER result = RippleCalc::rippleCalc (
TER result = rippleCalculate (
lesSandbox,
saMaxAmountAct,
saDstAmountAct,
@@ -290,13 +292,13 @@ STPathSet Pathfinder::filterPaths(int iMaxPaths, STPath& extraPath)
spsPaths.addPath (spCurrent); // Just checking the current path.
TER terResult;
TER errorCode;
try
{
LedgerEntrySet lesSandbox (mLedger, tapNONE);
terResult = RippleCalc::rippleCalc (
errorCode = rippleCalculate (
lesSandbox,
saMaxAmountAct, // --> computed input
saDstAmountAct, // --> computed output
@@ -315,13 +317,13 @@ STPathSet Pathfinder::filterPaths(int iMaxPaths, STPath& extraPath)
{
WriteLog (lsINFO, Pathfinder) << "findPaths: Caught throw: " << e.what ();
terResult = tefEXCEPTION;
errorCode = tefEXCEPTION;
}
if (tesSUCCESS != terResult)
if (errorCode != tesSUCCESS)
{
WriteLog (lsDEBUG, Pathfinder) <<
"findPaths: dropping: " << transToken (terResult) <<
"findPaths: dropping: " << transToken (errorCode) <<
": " << spCurrent.getJson (0);
}
else if (saDstAmountAct < saMinDstAmount)

File diff suppressed because it is too large Load Diff

View File

@@ -28,100 +28,24 @@ namespace ripple {
input required to produce a given output along a specified path.
*/
// TODO(vfalco) What's the difference between a RippleState versus PathState?
//
class RippleCalc
{
private:
// First time working in reverse a funding source was mentioned. Source may
// only be used there.
//
// Map of currency, issuer to node index.
curIssuerNode mumSource;
// If the transaction fails to meet some constraint, still need to delete
// unfunded offers.
//
// Offers that were found unfunded.
unordered_set<uint256> mUnfundedOffers;
void pathNext (
PathState::ref psrCur, const bool bMultiQuality,
const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent);
TER calcNode (
const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeRev (
const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeFwd (
const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeOfferRev (
const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeOfferFwd (
const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeAccountRev (
const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeAccountFwd (
const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
TER calcNodeAdvance (
const unsigned int uNode, PathState& psCur, const bool bMultiQuality,
const bool bReverse);
TER calcNodeDeliverRev (
const unsigned int uNode,
PathState& psCur,
const bool bMultiQuality,
const uint160& uOutAccountID,
const STAmount& saOutReq,
STAmount& saOutAct);
TER calcNodeDeliverFwd (
const unsigned int uNode,
PathState& psCur,
const bool bMultiQuality,
const uint160& uInAccountID,
const STAmount& saInReq,
STAmount& saInAct,
STAmount& saInFees);
void calcNodeRipple (
const std::uint32_t uQualityIn, const std::uint32_t uQualityOut,
const STAmount& saPrvReq, const STAmount& saCurReq,
STAmount& saPrvAct, STAmount& saCurAct, std::uint64_t& uRateMax);
RippleCalc (LedgerEntrySet& activeLedger, const bool bOpenLedger)
: mActiveLedger (activeLedger), mOpenLedger (bOpenLedger)
{
}
public:
static TER rippleCalc (
LedgerEntrySet& lesActive,
STAmount& saMaxAmountAct,
STAmount& saDstAmountAct,
std::vector<PathState::pointer>& vpsExpanded,
const STAmount& saDstAmountReq,
const STAmount& saMaxAmountReq,
const uint160& uDstAccountID,
const uint160& uSrcAccountID,
const STPathSet& spsPaths,
const bool bPartialPayment,
const bool bLimitQuality,
const bool bNoRippleDirect,
// --> True, not to affect accounts.
const bool bStandAlone,
// --> What kind of errors to return.
const bool bOpenLedger = true
);
static void setCanonical (
STPathSet& spsDst, const std::vector<PathState::pointer>& vpsExpanded,
bool bKeepDefault);
private:
LedgerEntrySet& mActiveLedger;
bool mOpenLedger;
};
TER rippleCalculate (
LedgerEntrySet& lesActive,
STAmount& saMaxAmountAct,
STAmount& saDstAmountAct,
std::vector<PathState::pointer>& vpsExpanded,
const STAmount& saDstAmountReq,
const STAmount& saMaxAmountReq,
const uint160& uDstAccountID,
const uint160& uSrcAccountID,
const STPathSet& spsPaths,
const bool bPartialPayment,
const bool bLimitQuality,
const bool bNoRippleDirect,
// --> True, not to affect accounts.
const bool bStandAlone,
// --> What kind of errors to return.
const bool bOpenLedger = true
);
} // ripple

View File

@@ -0,0 +1,31 @@
//------------------------------------------------------------------------------
/*
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_TUNING_H
#define RIPPLE_APP_PATHS_TUNING_H
namespace ripple {
// VFALCO TODO Why 40? is the number 40 part of protocol?
const int CALC_NODE_DELIVER_MAX_LOOPS = 40;
const int NODE_ADVANCE_MAX_LOOPS = 100;
} // ripple
#endif

View File

@@ -243,7 +243,7 @@ TER PaymentTransactor::doApply ()
terResult = openLedger && tooManyPaths
? telBAD_PATH_COUNT // Too many paths for proposed ledger.
: RippleCalc::rippleCalc (
: rippleCalculate (
mEngine->view (),
maxSourceAmountAct,
saDstAmountAct,

View File

@@ -22,7 +22,9 @@
namespace ripple {
// This interface is deprecated.
Json::Value RPCHandler::doRipplePathFind (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
Json::Value RPCHandler::doRipplePathFind (
Json::Value params, Resource::Charge& loadType,
Application::ScopedLockType& masterLockHolder)
{
masterLockHolder.unlock ();
@@ -217,7 +219,7 @@ Json::Value RPCHandler::doRipplePathFind (Json::Value params, Resource::Charge&
LedgerEntrySet lesSandbox (lpLedger, tapNONE);
TER terResult =
RippleCalc::rippleCalc (
rippleCalculate (
lesSandbox,
saMaxAmountAct, // <--
saDstAmountAct, // <--
@@ -249,7 +251,7 @@ Json::Value RPCHandler::doRipplePathFind (Json::Value params, Resource::Charge&
spsComputed.addPath(extraPath);
vpsExpanded.clear ();
lesSandbox.clear ();
terResult = RippleCalc::rippleCalc (lesSandbox, saMaxAmountAct, saDstAmountAct,
terResult = rippleCalculate (lesSandbox, saMaxAmountAct, saDstAmountAct,
vpsExpanded, saMaxAmount, saDstAmount,
raDst.getAccountID (), raSrc.getAccountID (),
spsComputed, false, false, false, true);