Files
rippled/modules/ripple_app/paths/ripple_RippleCalc.cpp
2013-07-23 18:47:02 -07:00

2671 lines
117 KiB
C++

//------------------------------------------------------------------------------
/*
Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================
// TODO:
// - Do automatic bridging via XRP.
//
// OPTIMIZE: When calculating path increment, note if increment consumes all liquidity. No need to revisit path in the future if
// all liquidity is used.
//
SETUP_LOG (RippleCalc)
// 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 RippleCalc::calcNodeAdvance (
const unsigned int uNode, // 0 < uNode < uLast
PathState& psCur,
const bool bMultiQuality,
const bool bReverse)
{
PathState::Node& pnPrv = psCur.vpnNodes[uNode - 1];
PathState::Node& pnCur = psCur.vpnNodes[uNode];
const uint160& uPrvCurrencyID = pnPrv.uCurrencyID;
const uint160& uPrvIssuerID = pnPrv.uIssuerID;
const uint160& uCurCurrencyID = pnCur.uCurrencyID;
const uint160& uCurIssuerID = pnCur.uIssuerID;
uint256& uDirectTip = pnCur.uDirectTip;
uint256& uDirectEnd = pnCur.uDirectEnd;
bool& bDirectAdvance = pnCur.bDirectAdvance;
SLE::pointer& sleDirectDir = pnCur.sleDirectDir;
STAmount& saOfrRate = pnCur.saOfrRate;
bool& bEntryAdvance = pnCur.bEntryAdvance;
unsigned int& uEntry = pnCur.uEntry;
uint256& uOfferIndex = pnCur.uOfferIndex;
SLE::pointer& sleOffer = pnCur.sleOffer;
uint160& uOfrOwnerID = pnCur.uOfrOwnerID;
STAmount& saOfferFunds = pnCur.saOfferFunds;
STAmount& saTakerPays = pnCur.saTakerPays;
STAmount& saTakerGets = pnCur.saTakerGets;
bool& bFundsDirty = pnCur.bFundsDirty;
TER terResult = tesSUCCESS;
WriteLog (lsDEBUG, RippleCalc) << "calcNodeAdvance: TakerPays:" << saTakerPays << " TakerGets:" << saTakerGets;
int loopCount = 0;
do
{
if (++loopCount > 20)
{
WriteLog (lsWARNING, RippleCalc) << "Loop count exceeded";
return tefEXCEPTION;
}
bool bDirectDirDirty = false;
if (!uDirectTip)
{
// Need to initialize current node.
uDirectTip = Ledger::getBookBase (uPrvCurrencyID, uPrvIssuerID, uCurCurrencyID, uCurIssuerID);
uDirectEnd = Ledger::getQualityNext (uDirectTip);
sleDirectDir = lesActive.entryCache (ltDIR_NODE, uDirectTip);
bDirectDirDirty = !!sleDirectDir; // Associated vars are dirty, if found it.
bDirectAdvance = !sleDirectDir; // Advance, if didn't find it. Normal not to be unable to lookup firstdirectory. Maybe even skip this lookup.
WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("calcNodeAdvance: Initialize node: uDirectTip=%s uDirectEnd=%s bDirectAdvance=%d") % uDirectTip % uDirectEnd % bDirectAdvance);
}
if (bDirectAdvance)
{
// Get next quality.
uDirectTip = lesActive.getNextLedgerIndex (uDirectTip, uDirectEnd);
bDirectDirDirty = true;
bDirectAdvance = false;
if (!!uDirectTip)
{
// Have another quality directory.
WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("calcNodeAdvance: Quality advance: uDirectTip=%s") % uDirectTip);
sleDirectDir = lesActive.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.";
return mOpenLedger ? telFAILED_PROCESSING : tecFAILED_PROCESSING; // FIXME
assert (false);
terResult = tefEXCEPTION;
}
}
if (bDirectDirDirty)
{
saOfrRate = STAmount::setRate (Ledger::getQuality (uDirectTip)); // For correct ratio
uEntry = 0;
bEntryAdvance = true;
WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("calcNodeAdvance: directory dirty: saOfrRate=%s") % saOfrRate);
}
if (!bEntryAdvance)
{
if (bFundsDirty)
{
// We were called again probably merely to update structure variables.
saTakerPays = sleOffer->getFieldAmount (sfTakerPays);
saTakerGets = sleOffer->getFieldAmount (sfTakerGets);
saOfferFunds = lesActive.accountFunds (uOfrOwnerID, saTakerGets); // Funds left.
bFundsDirty = false;
WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("calcNodeAdvance: funds dirty: saOfrRate=%s") % saOfrRate);
}
else
{
WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("calcNodeAdvance: as is"));
nothing ();
}
}
else if (!lesActive.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) << boost::str (boost::format ("calcNodeAdvance: next quality"));
bDirectAdvance = true; // Process next quality.
}
else if (!bReverse)
{
WriteLog (lsWARNING, RippleCalc) << boost::str (boost::format ("calcNodeAdvance: unreachable: ran out of offers"));
return mOpenLedger ? telFAILED_PROCESSING : tecFAILED_PROCESSING; // TEMPORARY
assert (false); // Can't run out of offers in forward direction.
terResult = tefEXCEPTION;
}
else
{
// Ran off end of offers.
bEntryAdvance = false; // Done.
uOfferIndex = 0; // Report nore more entries.
}
}
else
{
// Got a new offer.
sleOffer = lesActive.entryCache (ltOFFER, uOfferIndex);
uOfrOwnerID = sleOffer->getFieldAccount160 (sfAccount);
saTakerPays = sleOffer->getFieldAmount (sfTakerPays);
saTakerGets = sleOffer->getFieldAmount (sfTakerGets);
const aciSource asLine = boost::make_tuple (uOfrOwnerID, uCurCurrencyID, uCurIssuerID);
WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("calcNodeAdvance: uOfrOwnerID=%s saTakerPays=%s saTakerGets=%s uOfferIndex=%s")
% RippleAddress::createHumanAccountID (uOfrOwnerID)
% saTakerPays
% saTakerGets
% uOfferIndex);
if (sleOffer->isFieldPresent (sfExpiration) && sleOffer->getFieldU32 (sfExpiration) <= lesActive.getLedger ()->getParentCloseTimeNC ())
{
// Offer is expired.
WriteLog (lsTRACE, RippleCalc) << "calcNodeAdvance: expired offer";
if (bReverse)
musUnfundedFound.insert(uOfferIndex);
else // it will already be deleted
assert (musUnfundedFound.find (uOfferIndex) != musUnfundedFound.end ()); // Verify reverse found it too.
continue;
}
else if (!saTakerPays.isPositive () || !saTakerGets.isPositive ())
{
// Offer has bad amounts. Offers should never have a bad amounts.
if (bReverse)
{
// Past internal error, offer had bad amounts.
WriteLog (lsWARNING, RippleCalc) << boost::str (boost::format ("calcNodeAdvance: PAST INTERNAL ERROR: OFFER NON-POSITIVE: saTakerPays=%s saTakerGets=%s")
% saTakerPays % saTakerGets);
musUnfundedFound.insert (uOfferIndex); // Mark offer for always deletion.
continue;
}
else if (musUnfundedFound.find (uOfferIndex) != musUnfundedFound.end ())
{
// Past internal error, offer was found failed to place this in musUnfundedFound.
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeAdvance: PAST INTERNAL ERROR: OFFER NON-POSITIVE: saTakerPays=%s saTakerGets=%s")
% saTakerPays % saTakerGets);
// Just skip it. It will be deleted.
continue;
}
else
{
// Reverse should have previously put bad offer in list.
// An internal error previously left a bad offer.
WriteLog (lsWARNING, RippleCalc) << boost::str (boost::format ("calcNodeAdvance: INTERNAL ERROR: OFFER NON-POSITIVE: saTakerPays=%s saTakerGets=%s")
% saTakerPays % saTakerGets);
// Don't process at all, things are in an unexpected state for this transactions.
terResult = 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.
curIssuerNodeConstIterator itForward = psCur.umForward.find (asLine);
const bool bFoundForward = itForward != psCur.umForward.end ();
// Only a 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 != uNode)
{
// 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.
curIssuerNodeConstIterator itReverse = psCur.umReverse.find (asLine);
bool bFoundReverse = itReverse != psCur.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 != uNode)
{
// 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.
curIssuerNodeConstIterator itPast = mumSource.find (asLine);
bool bFoundPast = itPast != mumSource.end ();
// Only the current node is allowed to use the source.
saOfferFunds = lesActive.accountFunds (uOfrOwnerID, saTakerGets); // Funds held.
if (!saOfferFunds.isPositive ())
{
// 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.
musUnfundedFound.insert (uOfferIndex); // Mark offer for always deletion.
}
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) << boost::str (boost::format ("calcNodeAdvance: remember=%s/%s/%s")
% RippleAddress::createHumanAccountID (uOfrOwnerID)
% STAmount::createHumanCurrency (uCurCurrencyID)
% RippleAddress::createHumanAccountID (uCurIssuerID));
psCur.umReverse.insert (std::make_pair (asLine, uNode));
}
bFundsDirty = false;
bEntryAdvance = false;
}
}
while (tesSUCCESS == terResult && (bEntryAdvance || bDirectAdvance));
if (tesSUCCESS == terResult)
{
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeAdvance: uOfferIndex=%s") % uOfferIndex);
}
else
{
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeAdvance: terResult=%s") % transToken (terResult));
}
return terResult;
}
// 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 RippleCalc::calcNodeDeliverRev (
const unsigned int uNode, // 0 < uNode < uLast
PathState& psCur,
const bool bMultiQuality, // True, if not constrained to do 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 terResult = tesSUCCESS;
PathState::Node& pnPrv = psCur.vpnNodes[uNode - 1];
PathState::Node& pnCur = psCur.vpnNodes[uNode];
const uint160& uCurIssuerID = pnCur.uIssuerID;
const uint160& uPrvAccountID = pnPrv.uAccountID;
const STAmount& saTransferRate = pnCur.saTransferRate; // Transfer rate of the TakerGets issuer.
STAmount& saPrvDlvReq = pnPrv.saRevDeliver; // Accumulation of what the previous node must deliver.
uint256& uDirectTip = pnCur.uDirectTip;
uDirectTip = 0; // Restart book searching.
// YYY Note this gets zeroed on each increment, ideally only on first increment, then it could be a limit on the forward pass.
saOutAct.zero (saOutReq);
WriteLog (lsINFO, RippleCalc) << boost::str (boost::format ("calcNodeDeliverRev> saOutAct=%s saOutReq=%s saPrvDlvReq=%s")
% saOutAct
% saOutReq
% saPrvDlvReq);
assert (!!saOutReq);
int loopCount = 0;
while (saOutAct < saOutReq) // Did not deliver as much as requested.
{
// VFALCO TODO Why 40? Give this magic constant a name and document it
// NOTE is the number 40 part of protocol?
//
if (++loopCount > 40)
{
WriteLog (lsFATAL, RippleCalc) << "loop count exceeded";
return mOpenLedger ? telFAILED_PROCESSING : tecFAILED_PROCESSING;
}
bool& bEntryAdvance = pnCur.bEntryAdvance;
STAmount& saOfrRate = pnCur.saOfrRate;
uint256& uOfferIndex = pnCur.uOfferIndex;
SLE::pointer& sleOffer = pnCur.sleOffer;
const uint160& uOfrOwnerID = pnCur.uOfrOwnerID;
bool& bFundsDirty = pnCur.bFundsDirty;
STAmount& saOfferFunds = pnCur.saOfferFunds;
STAmount& saTakerPays = pnCur.saTakerPays;
STAmount& saTakerGets = pnCur.saTakerGets;
STAmount& saRateMax = pnCur.saRateMax;
terResult = calcNodeAdvance (uNode, psCur, bMultiQuality, true); // If needed, advance to next funded offer.
if (tesSUCCESS != terResult || !uOfferIndex)
{
// Error or out of offers.
break;
}
const STAmount saOutFeeRate = uOfrOwnerID == uCurIssuerID || uOutAccountID == uCurIssuerID // Issuer sending or receiving.
? saOne // No fee.
: saTransferRate; // Transfer rate of issuer.
WriteLog (lsINFO, RippleCalc) << boost::str (boost::format ("calcNodeDeliverRev: uOfrOwnerID=%s uOutAccountID=%s uCurIssuerID=%s saTransferRate=%s saOutFeeRate=%s")
% RippleAddress::createHumanAccountID (uOfrOwnerID)
% RippleAddress::createHumanAccountID (uOutAccountID)
% RippleAddress::createHumanAccountID (uCurIssuerID)
% saTransferRate
% saOutFeeRate);
if (bMultiQuality)
{
// In multi-quality mode, ignore rate.
nothing ();
}
else if (!saRateMax)
{
// Set initial rate.
saRateMax = saOutFeeRate;
WriteLog (lsINFO, RippleCalc) << boost::str (boost::format ("calcNodeDeliverRev: Set initial rate: saRateMax=%s saOutFeeRate=%s")
% saRateMax
% saOutFeeRate);
}
else if (saOutFeeRate > saRateMax)
{
// Offer exceeds initial rate.
WriteLog (lsINFO, RippleCalc) << boost::str (boost::format ("calcNodeDeliverRev: Offer exceeds initial rate: saRateMax=%s saOutFeeRate=%s")
% saRateMax
% 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
// anthing 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 (lsINFO, RippleCalc) << boost::str (boost::format ("calcNodeDeliverRev: Reducing rate: saRateMax=%s")
% 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 (lsINFO, RippleCalc) << boost::str (boost::format ("calcNodeDeliverRev: saOutReq=%s saOutAct=%s saTakerGets=%s saOutPassAct=%s saOutPlusFees=%s saOfferFunds=%s")
% saOutReq
% saOutAct
% saTakerGets
% saOutPassAct
% saOutPlusFees
% 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.
saOutPassAct = std::min (saOutPassReq, STAmount::divRound (saOutPlusFees, saOutFeeRate, true));
WriteLog (lsINFO, RippleCalc) << boost::str (boost::format ("calcNodeDeliverRev: Total exceeds fees: saOutPassAct=%s saOutPlusFees=%s saOfferFunds=%s")
% saOutPassAct
% saOutPlusFees
% saOfferFunds);
}
// Compute portion of input needed to cover actual output.
STAmount saInPassReq = STAmount::mulRound (saOutPassAct, saOfrRate, saTakerPays, true);
STAmount saInPassAct;
WriteLog (lsINFO, RippleCalc) << boost::str (boost::format ("calcNodeDeliverRev: saInPassReq=%s saOfrRate=%s saOutPassAct=%s saOutPlusFees=%s")
% saInPassReq
% saOfrRate
% saOutPassAct
% saOutPlusFees);
if (saInPassReq > saTakerPays)
saInPassReq = saTakerPays;
if (!saInPassReq)
{
// After rounding did not want anything.
WriteLog (lsINFO, RippleCalc) << boost::str (boost::format ("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 (lsINFO, RippleCalc) << boost::str (boost::format ("calcNodeDeliverRev: account --> OFFER --> ? : saInPassAct=%s")
% saInPassAct);
}
else
{
// offer --> OFFER --> ?
// Compute in previous offer node how much could come in.
terResult = calcNodeDeliverRev (
uNode - 1,
psCur,
bMultiQuality,
uOfrOwnerID,
saInPassReq,
saInPassAct);
WriteLog (lsINFO, RippleCalc) << boost::str (boost::format ("calcNodeDeliverRev: offer --> OFFER --> ? : saInPassAct=%s")
% saInPassAct);
}
if (tesSUCCESS != terResult)
break;
if (saInPassAct < saInPassReq)
{
// Adjust output to conform to limited input.
saOutPassAct = std::min (saOutPassReq, STAmount::divRound (saInPassAct, saOfrRate, saTakerGets, true));
saOutPlusFees = std::min (saOfferFunds, STAmount::mulRound (saOutPassAct, saOutFeeRate, true));
WriteLog (lsINFO, RippleCalc) << boost::str (boost::format ("calcNodeDeliverRev: adjusted: saOutPassAct=%s saOutPlusFees=%s")
% saOutPassAct
% saOutPlusFees);
}
else
{
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.
terResult = lesActive.accountSend (uOfrOwnerID, uCurIssuerID, saOutPassAct);
if (tesSUCCESS != terResult)
break;
// Adjust offer
STAmount saTakerGetsNew = saTakerGets - saOutPassAct;
STAmount saTakerPaysNew = saTakerPays - saInPassAct;
if (saTakerPaysNew.isNegative () || saTakerGetsNew.isNegative ())
{
WriteLog (lsWARNING, RippleCalc) << boost::str (boost::format ("calcNodeDeliverRev: NEGATIVE: saTakerPaysNew=%s saTakerGetsNew=%s")
% saTakerPaysNew % saTakerGetsNew);
terResult = mOpenLedger
? telFAILED_PROCESSING // Ledger is not final, can vote no.
: tecFAILED_PROCESSING;
break;
}
sleOffer->setFieldAmount (sfTakerGets, saTakerGetsNew);
sleOffer->setFieldAmount (sfTakerPays, saTakerPaysNew);
lesActive.entryModify (sleOffer);
if (saOutPassAct == saTakerGets)
{
// Offer became unfunded.
WriteLog (lsINFO, RippleCalc) << boost::str (boost::format ("calcNodeDeliverRev: offer became unfunded."));
bEntryAdvance = true; // XXX When don't we want to set advance?
}
else
{
assert (saOutPassAct < saTakerGets);
}
saOutAct += saOutPassAct;
saPrvDlvReq += saInPassAct; // Accumulate what is to be delivered from previous node.
}
CondLog (saOutAct > saOutReq, lsWARNING, RippleCalc)
<< boost::str (boost::format ("calcNodeDeliverRev: TOO MUCH: saOutAct=%s saOutReq=%s")
% saOutAct
% saOutReq);
assert (saOutAct <= saOutReq);
// XXX Perhaps need to check if partial is okay to relax this?
if (tesSUCCESS == terResult && !saOutAct)
terResult = tecPATH_DRY; // Unable to meet request, consider path dry.
WriteLog (lsINFO, RippleCalc) << boost::str (boost::format ("calcNodeDeliverRev< saOutAct=%s saOutReq=%s saPrvDlvReq=%s")
% saOutAct
% saOutReq
% saPrvDlvReq);
return terResult;
}
// For current offer, get input from deliver/limbo and output to next account or deliver for next offers.
// <-- pnCur.saFwdDeliver: For calcNodeAccountFwd to know how much went through
// --> pnCur.saRevDeliver: Do not exceed.
TER RippleCalc::calcNodeDeliverFwd (
const unsigned int uNode, // 0 < uNode < uLast
PathState& psCur,
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 terResult = tesSUCCESS;
PathState::Node& pnPrv = psCur.vpnNodes[uNode - 1];
PathState::Node& pnCur = psCur.vpnNodes[uNode];
PathState::Node& pnNxt = psCur.vpnNodes[uNode + 1];
const uint160& uNxtAccountID = pnNxt.uAccountID;
const uint160& uCurCurrencyID = pnCur.uCurrencyID;
const uint160& uCurIssuerID = pnCur.uIssuerID;
uint256 const& uOfferIndex = pnCur.uOfferIndex;
const uint160& uPrvCurrencyID = pnPrv.uCurrencyID;
const uint160& uPrvIssuerID = pnPrv.uIssuerID;
const STAmount& saInTransRate = pnPrv.saTransferRate;
const STAmount& saCurDeliverMax = pnCur.saRevDeliver; // Don't deliver more than wanted.
STAmount& saCurDeliverAct = pnCur.saFwdDeliver; // Zeroed in reverse pass.
uint256& uDirectTip = pnCur.uDirectTip;
uDirectTip = 0; // Restart book searching.
saInAct.zero (saInReq);
saInFees.zero (saInReq);
int loopCount = 0;
// XXX Perhaps make sure do not exceed saCurDeliverMax as another way to stop.
while (tesSUCCESS == terResult
&& saInAct + saInFees < saInReq) // Did not spend all inbound deliver funds.
{
// VFALCO TODO Why 40?
if (++loopCount > 40)
{
WriteLog (lsWARNING, RippleCalc) << "calcNodeDeliverFwd: max loops cndf";
return mOpenLedger ? telFAILED_PROCESSING : tecFAILED_PROCESSING;
}
// Determine values for pass to adjust saInAct, saInFees, and saCurDeliverAct
terResult = calcNodeAdvance (uNode, psCur, bMultiQuality, false); // If needed, advance to next funded offer.
if (tesSUCCESS != terResult)
{
nothing ();
}
else if (!uOfferIndex)
{
WriteLog (lsWARNING, RippleCalc) << "calcNodeDeliverFwd: INTERNAL ERROR: Ran out of offers.";
return mOpenLedger ? telFAILED_PROCESSING : tecFAILED_PROCESSING;
}
else if (tesSUCCESS == terResult)
{
// Doesn't charge input. Input funds are in limbo.
bool& bEntryAdvance = pnCur.bEntryAdvance;
STAmount& saOfrRate = pnCur.saOfrRate;
uint256& uOfferIndex = pnCur.uOfferIndex;
SLE::pointer& sleOffer = pnCur.sleOffer;
const uint160& uOfrOwnerID = pnCur.uOfrOwnerID;
bool& bFundsDirty = pnCur.bFundsDirty;
STAmount& saOfferFunds = pnCur.saOfferFunds;
STAmount& saTakerPays = pnCur.saTakerPays;
STAmount& saTakerGets = pnCur.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
STAmount saOutFunded = std::min (saOfferFunds, saTakerGets); // Offer maximum out - limited by funds with out fees.
STAmount saOutPassFunded = std::min (saOutFunded, saCurDeliverMax - saCurDeliverAct); // Offer maximum out - limit by most to deliver.
STAmount saInFunded = STAmount::mulRound (saOutPassFunded, saOfrRate, saTakerPays, true); // Offer maximum in - Limited by by payout.
STAmount saInTotal = STAmount::mulRound (saInFunded, saInFeeRate, true); // Offer maximum in with fees.
STAmount saInSum = std::min (saInTotal, saInReq - saInAct - saInFees); // In limited by remaining.
STAmount saInPassAct = std::min (saTakerPays, STAmount::divRound (saInSum, saInFeeRate, true)); // In without fees.
STAmount saOutPassMax = std::min (saOutPassFunded, STAmount::divRound (saInPassAct, saOfrRate, saTakerGets, true)); // Out limited by in remaining.
STAmount saInPassFeesMax = saInSum - saInPassAct;
STAmount saOutPassAct; // Will be determined by next node.
STAmount saInPassFees; // Will be determined by adjusted saInPassAct.
WriteLog (lsINFO, RippleCalc) << boost::str (boost::format ("calcNodeDeliverFwd: uNode=%d saOutFunded=%s saOutPassFunded=%s saOfferFunds=%s saTakerGets=%s saInReq=%s saInAct=%s saInFees=%s saInFunded=%s saInTotal=%s saInSum=%s saInPassAct=%s saOutPassMax=%s")
% uNode
% saOutFunded
% saOutPassFunded
% saOfferFunds
% saTakerGets
% saInReq
% saInAct
% saInFees
% saInFunded
% saInTotal
% saInSum
% saInPassAct
% saOutPassMax);
if (!saInSum)
{
WriteLog (lsINFO, RippleCalc) << "calcNodeDeliverFwd: Microscopic offer unfunded.";
// After math offer is effectively unfunded.
psCur.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.
psCur.vUnfundedBecame.push_back (uOfferIndex);
bEntryAdvance = true;
continue;
}
else if (!!uNxtAccountID)
{
// ? --> 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 (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeDeliverFwd: ? --> OFFER --> account: uOfrOwnerID=%s uNxtAccountID=%s saOutPassAct=%s saOutFunded=%s")
% RippleAddress::createHumanAccountID (uOfrOwnerID)
% RippleAddress::createHumanAccountID (uNxtAccountID)
% saOutPassAct
% saOutFunded);
// Output: Debit offer owner, send XRP or non-XPR to next account.
terResult = lesActive.accountSend (uOfrOwnerID, uNxtAccountID, saOutPassAct);
if (tesSUCCESS != terResult)
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.
terResult = RippleCalc::calcNodeDeliverFwd (
uNode + 1,
psCur,
bMultiQuality,
uOfrOwnerID, // --> Current holder.
saOutPassMax, // --> Amount available.
saOutPassAct, // <-- Amount delivered.
saOutPassFees); // <-- Fees charged.
if (tesSUCCESS != terResult)
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);
saInPassAct = std::min (saTakerPays, STAmount::mulRound (saOutPassAct, saOfrRate, saInReq, true));
saInPassFees = std::min (saInPassFeesMax, STAmount::mulRound (saInPassAct, saInFeeRate, true));
}
// Do outbound debiting.
// Send to issuer/limbo total amount including fees (issuer gets fees).
lesActive.accountSend (uOfrOwnerID, !!uCurCurrencyID ? uCurIssuerID : ACCOUNT_XRP, saOutPassAct + saOutPassFees);
WriteLog (lsINFO, RippleCalc) << boost::str (boost::format ("calcNodeDeliverFwd: ? --> OFFER --> offer: saOutPassAct=%s saOutPassFees=%s")
% saOutPassAct
% saOutPassFees);
}
WriteLog (lsINFO, RippleCalc) << boost::str (boost::format ("calcNodeDeliverFwd: uNode=%d saTakerGets=%s saTakerPays=%s saInPassAct=%s saInPassFees=%s saOutPassAct=%s saOutFunded=%s")
% uNode
% saTakerGets
% saTakerPays
% saInPassAct
% saInPassFees
% saOutPassAct
% 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.
{
terResult = lesActive.accountSend (!!uPrvCurrencyID ? uInAccountID : ACCOUNT_XRP, uOfrOwnerID, saInPassAct);
if (tesSUCCESS != terResult)
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.isNegative () || saTakerGetsNew.isNegative ())
{
WriteLog (lsWARNING, RippleCalc) << boost::str (boost::format ("calcNodeDeliverFwd: NEGATIVE: saTakerPaysNew=%s saTakerGetsNew=%s")
% saTakerPaysNew % saTakerGetsNew);
terResult = mOpenLedger
? telFAILED_PROCESSING // Ledger is not final, can vote no.
: tecFAILED_PROCESSING;
break;
}
sleOffer->setFieldAmount (sfTakerGets, saTakerGetsNew);
sleOffer->setFieldAmount (sfTakerPays, saTakerPaysNew);
lesActive.entryModify (sleOffer);
if (saOutPassAct == saOutFunded)
{
// Offer became unfunded.
WriteLog (lsWARNING, RippleCalc) << boost::str (boost::format ("calcNodeDeliverFwd: unfunded: saOutPassAct=%s saOutFunded=%s")
% saOutPassAct % saOutFunded);
psCur.vUnfundedBecame.push_back (uOfferIndex);
bEntryAdvance = true;
}
else
{
CondLog (saOutPassAct >= saOutFunded, lsWARNING, RippleCalc) << boost::str (boost::format ("calcNodeDeliverFwd: TOO MUCH: saOutPassAct=%s saOutFunded=%s")
% saOutPassAct % saOutFunded);
assert (saOutPassAct < saOutFunded);
}
saInAct += saInPassAct;
saInFees += saInPassFees;
// Adjust amount available to next node.
saCurDeliverAct = std::min (saCurDeliverMax, saCurDeliverAct + saOutPassAct);
}
}
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeDeliverFwd< uNode=%d saInAct=%s saInFees=%s")
% uNode
% saInAct
% saInFees);
return terResult;
}
// Called to drive from the last offer node in a chain.
TER RippleCalc::calcNodeOfferRev (
const unsigned int uNode, // 0 < uNode < uLast
PathState& psCur,
const bool bMultiQuality)
{
TER terResult;
PathState::Node& pnCur = psCur.vpnNodes [uNode];
PathState::Node& pnNxt = psCur.vpnNodes [uNode + 1];
if (!!pnNxt.uAccountID)
{
// Next is an account node, resolve current offer node's deliver.
STAmount saDeliverAct;
WriteLog (lsINFO, RippleCalc) << boost::str (boost::format ("calcNodeOfferRev: OFFER --> account: uNode=%d saRevDeliver=%s")
% uNode
% pnCur.saRevDeliver);
terResult = calcNodeDeliverRev (
uNode,
psCur,
bMultiQuality,
pnNxt.uAccountID,
pnCur.saRevDeliver, // The next node wants the current node to deliver this much.
saDeliverAct);
}
else
{
WriteLog (lsINFO, RippleCalc) << boost::str (boost::format ("calcNodeOfferRev: OFFER --> offer: uNode=%d")
% uNode);
// Next is an offer. Deliver has already been resolved.
terResult = tesSUCCESS;
}
return terResult;
}
// 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 RippleCalc::calcNodeOfferFwd (
const unsigned int uNode, // 0 < uNode < uLast
PathState& psCur,
const bool bMultiQuality
)
{
TER terResult;
PathState::Node& pnPrv = psCur.vpnNodes [uNode - 1];
if (!!pnPrv.uAccountID)
{
// Previous is an account node, resolve its deliver.
STAmount saInAct;
STAmount saInFees;
terResult = calcNodeDeliverFwd (
uNode,
psCur,
bMultiQuality,
pnPrv.uAccountID,
pnPrv.saFwdDeliver, // Previous is sending this much.
saInAct,
saInFees);
assert (tesSUCCESS != terResult || pnPrv.saFwdDeliver == saInAct + saInFees);
}
else
{
// Previous is an offer. Deliver has already been resolved.
terResult = tesSUCCESS;
}
return terResult;
}
// 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 RippleCalc::calcNodeRipple (
const uint32 uQualityIn,
const uint32 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 : <-- <= -->
uint64& uRateMax)
{
WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("calcNodeRipple> uQualityIn=%d uQualityOut=%d saPrvReq=%s saCurReq=%s saPrvAct=%s saCurAct=%s")
% uQualityIn
% uQualityOut
% saPrvReq
% saCurReq
% saPrvAct
% saCurAct);
assert (saCurReq.isPositive ()); // FIXME: saCurReq was zero
assert (saPrvReq.getCurrency () == saCurReq.getCurrency ());
assert (saPrvReq.getCurrency () == saPrvAct.getCurrency ());
assert (saPrvReq.getIssuer () == saPrvAct.getIssuer ());
const bool bPrvUnlimited = saPrvReq.isNegative ();
const STAmount saPrv = bPrvUnlimited ? STAmount (saPrvReq) : saPrvReq - saPrvAct;
const STAmount saCur = saCurReq - saCurAct;
#if 0
WriteLog (lsINFO, RippleCalc) << boost::str (boost::format ("calcNodeRipple: bPrvUnlimited=%d saPrv=%s saCur=%s")
% bPrvUnlimited
% saPrv
% saCur);
#endif
if (uQualityIn >= uQualityOut)
{
// No fee.
WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("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) << boost::str (boost::format ("calcNodeRipple: Fee"));
uint64 uRate = STAmount::getRate (STAmount (uQualityOut), STAmount (uQualityIn));
if (!uRateMax || uRate <= uRateMax)
{
const uint160 uCurrencyID = saCur.getCurrency ();
const uint160 uCurIssuerID = saCur.getIssuer ();
// const uint160 uPrvIssuerID = saPrv.getIssuer();
STAmount saCurIn = STAmount::divRound (STAmount::mulRound (saCur, uQualityOut, uCurrencyID, uCurIssuerID, true), uQualityIn, uCurrencyID, uCurIssuerID, true);
WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("calcNodeRipple: bPrvUnlimited=%d saPrv=%s saCurIn=%s") % bPrvUnlimited % saPrv % saCurIn);
if (bPrvUnlimited || saCurIn <= saPrv)
{
// All of cur. Some amount of prv.
saCurAct += saCur;
saPrvAct += saCurIn;
WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("calcNodeRipple:3c: saCurReq=%s saPrvAct=%s") % saCurReq % saPrvAct);
}
else
{
// A part of cur. All of prv. (cur as driver)
STAmount saCurOut = STAmount::divRound (STAmount::mulRound (saPrv, uQualityIn, uCurrencyID, uCurIssuerID, true), uQualityOut, uCurrencyID, uCurIssuerID, true);
WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("calcNodeRipple:4: saCurReq=%s") % saCurReq);
saCurAct += saCurOut;
saPrvAct = saPrvReq;
}
if (!uRateMax)
uRateMax = uRate;
}
}
WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("calcNodeRipple< uQualityIn=%d uQualityOut=%d saPrvReq=%s saCurReq=%s saPrvAct=%s saCurAct=%s")
% uQualityIn
% uQualityOut
% saPrvReq
% saCurReq
% saPrvAct
% saCurAct);
}
// 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 RippleCalc::calcNodeAccountRev (const unsigned int uNode, PathState& psCur, const bool bMultiQuality)
{
TER terResult = tesSUCCESS;
const unsigned int uLast = psCur.vpnNodes.size () - 1;
uint64 uRateMax = 0;
PathState::Node& pnPrv = psCur.vpnNodes[uNode ? uNode - 1 : 0];
PathState::Node& pnCur = psCur.vpnNodes[uNode];
PathState::Node& pnNxt = psCur.vpnNodes[uNode == uLast ? uLast : uNode + 1];
// Current is allowed to redeem to next.
const bool bPrvAccount = !uNode || isSetBit (pnPrv.uFlags, STPathElement::typeAccount);
const bool bNxtAccount = uNode == uLast || isSetBit (pnNxt.uFlags, STPathElement::typeAccount);
const uint160& uCurAccountID = pnCur.uAccountID;
const uint160& uPrvAccountID = bPrvAccount ? pnPrv.uAccountID : uCurAccountID;
const uint160& uNxtAccountID = bNxtAccount ? pnNxt.uAccountID : uCurAccountID; // Offers are always issue.
const uint160& uCurrencyID = pnCur.uCurrencyID;
// XXX Don't look up quality for XRP
const uint32 uQualityIn = uNode ? lesActive.rippleQualityIn (uCurAccountID, uPrvAccountID, uCurrencyID) : QUALITY_ONE;
const uint32 uQualityOut = uNode != uLast ? lesActive.rippleQualityOut (uCurAccountID, uNxtAccountID, uCurrencyID) : QUALITY_ONE;
// For bPrvAccount
const STAmount saPrvOwed = bPrvAccount && uNode // Previous account is owed.
? lesActive.rippleOwed (uCurAccountID, uPrvAccountID, uCurrencyID)
: STAmount (uCurrencyID, uCurAccountID);
const STAmount saPrvLimit = bPrvAccount && uNode // Previous account may owe.
? lesActive.rippleLimit (uCurAccountID, uPrvAccountID, uCurrencyID)
: STAmount (uCurrencyID, uCurAccountID);
const STAmount saNxtOwed = bNxtAccount && uNode != uLast // Next account is owed.
? lesActive.rippleOwed (uCurAccountID, uNxtAccountID, uCurrencyID)
: STAmount (uCurrencyID, uCurAccountID);
WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("calcNodeAccountRev> uNode=%d/%d uPrvAccountID=%s uCurAccountID=%s uNxtAccountID=%s uCurrencyID=%s uQualityIn=%d uQualityOut=%d saPrvOwed=%s saPrvLimit=%s")
% uNode
% uLast
% RippleAddress::createHumanAccountID (uPrvAccountID)
% RippleAddress::createHumanAccountID (uCurAccountID)
% RippleAddress::createHumanAccountID (uNxtAccountID)
% STAmount::createHumanCurrency (uCurrencyID)
% uQualityIn
% uQualityOut
% saPrvOwed
% saPrvLimit);
// Previous can redeem the owed IOUs it holds.
const STAmount saPrvRedeemReq = saPrvOwed.isPositive () ? saPrvOwed : STAmount (saPrvOwed.getCurrency (), saPrvOwed.getIssuer ());
STAmount& saPrvRedeemAct = pnPrv.saRevRedeem;
// Previous can issue up to limit minus whatever portion of limit already used (not including redeemable amount).
const STAmount saPrvIssueReq = saPrvOwed.isNegative () ? saPrvLimit + saPrvOwed : saPrvLimit;
STAmount& saPrvIssueAct = pnPrv.saRevIssue;
// For !bPrvAccount
const STAmount saPrvDeliverReq = STAmount (pnPrv.saRevDeliver.getCurrency (), pnPrv.saRevDeliver.getIssuer (), -1); // Unlimited.
STAmount& saPrvDeliverAct = pnPrv.saRevDeliver;
// For bNxtAccount
const STAmount& saCurRedeemReq = pnCur.saRevRedeem;
STAmount saCurRedeemAct (saCurRedeemReq.getCurrency (), saCurRedeemReq.getIssuer ());
const STAmount& saCurIssueReq = pnCur.saRevIssue;
STAmount saCurIssueAct (saCurIssueReq.getCurrency (), saCurIssueReq.getIssuer ()); // Track progress.
// For !bNxtAccount
const STAmount& saCurDeliverReq = pnCur.saRevDeliver;
STAmount saCurDeliverAct (saCurDeliverReq.getCurrency (), saCurDeliverReq.getIssuer ());
WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("calcNodeAccountRev: saPrvRedeemReq=%s saPrvIssueReq=%s saPrvDeliverAct=%s saPrvDeliverReq=%s saCurRedeemReq=%s saCurIssueReq=%s saNxtOwed=%s")
% saPrvRedeemReq
% saPrvIssueReq
% saPrvDeliverAct
% saPrvDeliverReq
% saCurRedeemReq
% saCurIssueReq
% saNxtOwed);
WriteLog (lsTRACE, RippleCalc) << psCur.getJson ();
assert (!saCurRedeemReq || (-saNxtOwed) >= saCurRedeemReq); // Current redeem req can't be more than IOUs on hand.
assert (!saCurIssueReq // If not issuing, fine.
|| !saNxtOwed.isNegative () // 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 (!uNode)
{
// ^ --> ACCOUNT --> account|offer
// Nothing to do, there is no previous to adjust.
nothing ();
}
else if (bPrvAccount && bNxtAccount)
{
if (uNode == uLast)
{
// account --> ACCOUNT --> $
// Overall deliverable.
const STAmount saCurWantedReq = std::min (psCur.saOutReq - psCur.saOutAct, saPrvLimit + saPrvOwed); // If previous is an account, limit.
STAmount saCurWantedAct (saCurWantedReq.getCurrency (), saCurWantedReq.getIssuer ());
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeAccountRev: account --> ACCOUNT --> $ : saCurWantedReq=%s")
% 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 (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeAccountRev: Redeem at 1:1 saPrvRedeemReq=%s (available) saPrvRedeemAct=%s uRateMax=%s")
% saPrvRedeemReq
% saPrvRedeemAct
% STAmount::saFromRate (uRateMax).getText ());
}
else
{
saPrvRedeemAct.zero (saPrvRedeemReq);
}
// Calculate issuing.
saPrvIssueAct.zero (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 (uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurWantedReq, saPrvIssueAct, saCurWantedAct, uRateMax);
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeAccountRev: Issuing: Rate: quality in : 1.0 saPrvIssueAct=%s saCurWantedAct=%s")
% saPrvIssueAct
% saCurWantedAct);
}
if (!saCurWantedAct)
{
// Must have processed something.
terResult = tecPATH_DRY;
}
}
else
{
// ^|account --> ACCOUNT --> account
saPrvRedeemAct.zero (saPrvRedeemReq);
saPrvIssueAct.zero (saPrvIssueReq);
// redeem (part 1) -> redeem
if (saCurRedeemReq // Next wants IOUs redeemed.
&& saPrvRedeemReq) // Previous has IOUs to redeem.
{
// Rate : 1.0 : quality out
calcNodeRipple (QUALITY_ONE, uQualityOut, saPrvRedeemReq, saCurRedeemReq, saPrvRedeemAct, saCurRedeemAct, uRateMax);
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeAccountRev: Rate : 1.0 : quality out saPrvRedeemAct=%s saCurRedeemAct=%s")
% saPrvRedeemAct
% 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 (uQualityIn, uQualityOut, saPrvIssueReq, saCurRedeemReq, saPrvIssueAct, saCurRedeemAct, uRateMax);
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeAccountRev: Rate: quality in : quality out: saPrvIssueAct=%s saCurRedeemAct=%s")
% saPrvIssueAct
% 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 (QUALITY_ONE, lesActive.rippleTransferRate (uCurAccountID), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct, uRateMax);
WriteLog (lsINFO, RippleCalc) << boost::str (boost::format ("calcNodeAccountRev: Rate : 1.0 : transfer_rate: saPrvRedeemAct=%s saCurIssueAct=%s")
% saPrvRedeemAct
% 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 (uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurIssueReq, saPrvIssueAct, saCurIssueAct, uRateMax);
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeAccountRev: Rate: quality in : 1.0: saPrvIssueAct=%s saCurIssueAct=%s")
% saPrvIssueAct
% saCurIssueAct);
}
if (!saCurRedeemAct && !saCurIssueAct)
{
// Did not make progress.
terResult = tecPATH_DRY;
}
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeAccountRev: ^|account --> ACCOUNT --> account : saCurRedeemReq=%s saCurIssueReq=%s saPrvOwed=%s saCurRedeemAct=%s saCurIssueAct=%s")
% saCurRedeemReq
% saCurIssueReq
% saPrvOwed
% saCurRedeemAct
% saCurIssueAct);
}
}
else if (bPrvAccount && !bNxtAccount)
{
// account --> ACCOUNT --> offer
// Note: deliver is always issue as ACCOUNT is the issuer for the offer input.
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeAccountRev: account --> ACCOUNT --> offer"));
saPrvRedeemAct.zero (saPrvRedeemReq);
saPrvIssueAct.zero (saPrvIssueReq);
// redeem -> deliver/issue.
if (saPrvOwed.isPositive () // Previous has IOUs to redeem.
&& saCurDeliverReq) // Need some issued.
{
// Rate : 1.0 : transfer_rate
calcNodeRipple (QUALITY_ONE, lesActive.rippleTransferRate (uCurAccountID), 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 (uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurDeliverReq, saPrvIssueAct, saCurDeliverAct, uRateMax);
}
if (!saCurDeliverAct)
{
// Must want something.
terResult = tecPATH_DRY;
}
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeAccountRev: saCurDeliverReq=%s saCurDeliverAct=%s saPrvOwed=%s")
% saCurDeliverReq
% saCurDeliverAct
% saPrvOwed);
}
else if (!bPrvAccount && bNxtAccount)
{
if (uNode == uLast)
{
// offer --> ACCOUNT --> $
const STAmount& saCurWantedReq = psCur.saOutReq - psCur.saOutAct; // Previous is an offer, no limit: redeem own IOUs.
STAmount saCurWantedAct (saCurWantedReq.getCurrency (), saCurWantedReq.getIssuer ());
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeAccountRev: offer --> ACCOUNT --> $ : saCurWantedReq=%s saOutAct=%s saOutReq=%s")
% saCurWantedReq
% psCur.saOutAct
% psCur.saOutReq);
if (!saCurWantedReq.isPositive ())
{
// TEMPORARY emergency fix
WriteLog (lsFATAL, RippleCalc) << "CurWantReq was not positive";
return tefEXCEPTION;
}
assert (saCurWantedReq.isPositive ()); // FIXME: We got one of these
// Rate: quality in : 1.0
calcNodeRipple (uQualityIn, QUALITY_ONE, saPrvDeliverReq, saCurWantedReq, saPrvDeliverAct, saCurWantedAct, uRateMax);
if (!saCurWantedAct)
{
// Must have processed something.
terResult = tecPATH_DRY;
}
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeAccountRev: saPrvDeliverAct=%s saPrvDeliverReq=%s saCurWantedAct=%s saCurWantedReq=%s")
% saPrvDeliverAct
% saPrvDeliverReq
% saCurWantedAct
% saCurWantedReq);
}
else
{
// offer --> ACCOUNT --> account
// Note: offer is always delivering(redeeming) as account is issuer.
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeAccountRev: offer --> ACCOUNT --> account : saCurRedeemReq=%s saCurIssueReq=%s")
% saCurRedeemReq % saCurIssueReq);
// deliver -> redeem
if (saCurRedeemReq) // Next wants us to redeem.
{
// Rate : 1.0 : quality out
calcNodeRipple (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 (QUALITY_ONE, lesActive.rippleTransferRate (uCurAccountID), saPrvDeliverReq, saCurIssueReq, saPrvDeliverAct, saCurIssueAct, uRateMax);
}
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeAccountRev: saCurIssueAct=%s saCurRedeemReq=%s saPrvDeliverAct=%s saCurIssueReq=%s")
% saCurRedeemAct
% saCurRedeemReq
% saPrvDeliverAct
% saCurIssueReq);
if (!saPrvDeliverAct)
{
// Must want something.
terResult = tecPATH_DRY;
}
}
}
else
{
// offer --> ACCOUNT --> offer
// deliver/redeem -> deliver/issue.
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeAccountRev: offer --> ACCOUNT --> offer"));
// Rate : 1.0 : transfer_rate
calcNodeRipple (QUALITY_ONE, lesActive.rippleTransferRate (uCurAccountID), saPrvDeliverReq, saCurDeliverReq, saPrvDeliverAct, saCurDeliverAct, uRateMax);
if (!saCurDeliverAct)
{
// Must want something.
terResult = tecPATH_DRY;
}
}
return terResult;
}
// 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 RippleCalc::calcNodeAccountFwd (
const unsigned int uNode, // 0 <= uNode <= uLast
PathState& psCur,
const bool bMultiQuality)
{
TER terResult = tesSUCCESS;
const unsigned int uLast = psCur.vpnNodes.size () - 1;
uint64 uRateMax = 0;
PathState::Node& pnPrv = psCur.vpnNodes[uNode ? uNode - 1 : 0];
PathState::Node& pnCur = psCur.vpnNodes[uNode];
PathState::Node& pnNxt = psCur.vpnNodes[uNode == uLast ? uLast : uNode + 1];
const bool bPrvAccount = isSetBit (pnPrv.uFlags, STPathElement::typeAccount);
const bool bNxtAccount = isSetBit (pnNxt.uFlags, STPathElement::typeAccount);
const uint160& uCurAccountID = pnCur.uAccountID;
const uint160& uPrvAccountID = bPrvAccount ? pnPrv.uAccountID : uCurAccountID;
const uint160& uNxtAccountID = bNxtAccount ? pnNxt.uAccountID : uCurAccountID; // Offers are always issue.
// const uint160& uCurIssuerID = pnCur.uIssuerID;
const uint160& uCurrencyID = pnCur.uCurrencyID;
uint32 uQualityIn = uNode ? lesActive.rippleQualityIn (uCurAccountID, uPrvAccountID, uCurrencyID) : QUALITY_ONE;
uint32 uQualityOut = uNode == uLast ? lesActive.rippleQualityOut (uCurAccountID, uNxtAccountID, 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 bNxtAccount
const STAmount& saPrvRedeemReq = pnPrv.saFwdRedeem;
STAmount saPrvRedeemAct (saPrvRedeemReq.getCurrency (), saPrvRedeemReq.getIssuer ());
const STAmount& saPrvIssueReq = pnPrv.saFwdIssue;
STAmount saPrvIssueAct (saPrvIssueReq.getCurrency (), saPrvIssueReq.getIssuer ());
// For !bPrvAccount
const STAmount& saPrvDeliverReq = pnPrv.saFwdDeliver;
STAmount saPrvDeliverAct (saPrvDeliverReq.getCurrency (), saPrvDeliverReq.getIssuer ());
// For bNxtAccount
const STAmount& saCurRedeemReq = pnCur.saRevRedeem;
STAmount& saCurRedeemAct = pnCur.saFwdRedeem;
const STAmount& saCurIssueReq = pnCur.saRevIssue;
STAmount& saCurIssueAct = pnCur.saFwdIssue;
// For !bNxtAccount
const STAmount& saCurDeliverReq = pnCur.saRevDeliver;
STAmount& saCurDeliverAct = pnCur.saFwdDeliver;
// For !uNode
STAmount& saCurSendMaxPass = psCur.saInPass; // Report how much pass sends.
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeAccountFwd> uNode=%d/%d saPrvRedeemReq=%s saPrvIssueReq=%s saPrvDeliverReq=%s saCurRedeemReq=%s saCurIssueReq=%s saCurDeliverReq=%s")
% uNode
% uLast
% saPrvRedeemReq
% saPrvIssueReq
% saPrvDeliverReq
% saCurRedeemReq
% saCurIssueReq
% saCurDeliverReq);
// Ripple through account.
if (bPrvAccount && bNxtAccount)
{
// Next is an account, must be rippling.
if (!uNode)
{
// ^ --> ACCOUNT --> account
// First node, calculate amount to ripple based on what is available.
saCurRedeemAct = saCurRedeemReq;
if (!psCur.saInReq.isNegative ())
{
// Limit by send max.
saCurRedeemAct = std::min (saCurRedeemAct, psCur.saInReq - psCur.saInAct);
}
saCurSendMaxPass = saCurRedeemAct;
saCurIssueAct = saCurRedeemAct == saCurRedeemReq // Fully redeemed.
? saCurIssueReq
: STAmount (saCurIssueReq);
if (!!saCurIssueAct && !psCur.saInReq.isNegative ())
{
// Limit by send max.
saCurIssueAct = std::min (saCurIssueAct, psCur.saInReq - psCur.saInAct - saCurRedeemAct);
}
saCurSendMaxPass += saCurIssueAct;
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeAccountFwd: ^ --> ACCOUNT --> account : saInReq=%s saInAct=%s saCurRedeemAct=%s saCurIssueReq=%s saCurIssueAct=%s saCurSendMaxPass=%s")
% psCur.saInReq
% psCur.saInAct
% saCurRedeemAct
% saCurIssueReq
% saCurIssueAct
% saCurSendMaxPass);
}
else if (uNode == uLast)
{
// account --> ACCOUNT --> $
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeAccountFwd: account --> ACCOUNT --> $ : uPrvAccountID=%s uCurAccountID=%s saPrvRedeemReq=%s saPrvIssueReq=%s")
% RippleAddress::createHumanAccountID (uPrvAccountID)
% RippleAddress::createHumanAccountID (uCurAccountID)
% saPrvRedeemReq
% saPrvIssueReq);
// Last node. Accept all funds. Calculate amount actually to credit.
STAmount& saCurReceive = psCur.saOutPass;
STAmount saIssueCrd = uQualityIn >= QUALITY_ONE
? saPrvIssueReq // No fee.
: STAmount::mulRound (saPrvIssueReq, STAmount (CURRENCY_ONE, ACCOUNT_ONE, uQualityIn, -9), true); // Amount to credit.
// Amount to credit. Credit for less than received as a surcharge.
saCurReceive = saPrvRedeemReq + saIssueCrd;
if (saCurReceive)
{
// Actually receive.
terResult = lesActive.rippleCredit (uPrvAccountID, uCurAccountID, saPrvRedeemReq + saPrvIssueReq, false);
}
else
{
// After applying quality, total payment was microscopic.
terResult = tecPATH_DRY;
}
}
else
{
// account --> ACCOUNT --> account
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeAccountFwd: account --> ACCOUNT --> account"));
saCurRedeemAct.zero (saCurRedeemReq);
saCurIssueAct.zero (saCurIssueReq);
// Previous redeem part 1: redeem -> redeem
if (saPrvRedeemReq && saCurRedeemReq) // Previous wants to redeem.
{
// Rate : 1.0 : quality out
calcNodeRipple (QUALITY_ONE, uQualityOut, saPrvRedeemReq, saCurRedeemReq, saPrvRedeemAct, saCurRedeemAct, uRateMax);
}
// Previous issue part 1: issue -> redeem
if (saPrvIssueReq != saPrvIssueAct // Previous wants to issue.
&& saCurRedeemReq != saCurRedeemAct) // Current has more to redeem to next.
{
// Rate: quality in : quality out
calcNodeRipple (uQualityIn, uQualityOut, saPrvIssueReq, saCurRedeemReq, saPrvIssueAct, saCurRedeemAct, uRateMax);
}
// Previous redeem part 2: redeem -> issue.
if (saPrvRedeemReq != saPrvRedeemAct // Previous still wants to redeem.
&& saCurRedeemReq == saCurRedeemAct // Current redeeming is done can issue.
&& saCurIssueReq) // Current wants to issue.
{
// Rate : 1.0 : transfer_rate
calcNodeRipple (QUALITY_ONE, lesActive.rippleTransferRate (uCurAccountID), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct, uRateMax);
}
// Previous issue part 2 : issue -> issue
if (saPrvIssueReq != saPrvIssueAct // Previous wants to issue.
&& saCurRedeemReq == saCurRedeemAct // Current redeeming is done can issue.
&& saCurIssueReq) // Current wants to issue.
{
// Rate: quality in : 1.0
calcNodeRipple (uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurIssueReq, saPrvIssueAct, saCurIssueAct, uRateMax);
}
STAmount saProvide = saCurRedeemAct + saCurIssueAct;
// Adjust prv --> cur balance : take all inbound
terResult = saProvide
? lesActive.rippleCredit (uPrvAccountID, uCurAccountID, saPrvRedeemReq + saPrvIssueReq, false)
: tecPATH_DRY;
}
}
else if (bPrvAccount && !bNxtAccount)
{
// 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 (uNode)
{
// Non-XRP, current node is the issuer.
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeAccountFwd: account --> ACCOUNT --> offer"));
saCurDeliverAct.zero (saCurDeliverReq);
// redeem -> issue/deliver.
// Previous wants to redeem.
// Current is issuing to an offer so leave funds in account as "limbo".
if (saPrvRedeemReq) // Previous wants to redeem.
{
// Rate : 1.0 : transfer_rate
// XXX Is having the transfer rate here correct?
calcNodeRipple (QUALITY_ONE, lesActive.rippleTransferRate (uCurAccountID), saPrvRedeemReq, saCurDeliverReq, saPrvRedeemAct, saCurDeliverAct, uRateMax);
}
// issue -> issue/deliver
if (saPrvRedeemReq == saPrvRedeemAct // Previous done redeeming: Previous has no IOUs.
&& saPrvIssueReq) // Previous wants to issue. To next must be ok.
{
// Rate: quality in : 1.0
calcNodeRipple (uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurDeliverReq, saPrvIssueAct, saCurDeliverAct, uRateMax);
}
// Adjust prv --> cur balance : take all inbound
terResult = saCurDeliverAct
? lesActive.rippleCredit (uPrvAccountID, uCurAccountID, saPrvRedeemReq + saPrvIssueReq, false)
: tecPATH_DRY; // Didn't actually deliver anything.
}
else
{
// Delivering amount requested from downstream.
saCurDeliverAct = saCurDeliverReq;
// If limited, then limit by send max and available.
if (!psCur.saInReq.isNegative ())
{
// Limit by send max.
saCurDeliverAct = std::min (saCurDeliverAct, psCur.saInReq - psCur.saInAct);
// Limit XRP by available. No limit for non-XRP as issuer.
if (uCurrencyID.isZero ())
saCurDeliverAct = std::min (saCurDeliverAct, lesActive.accountHolds (uCurAccountID, CURRENCY_XRP, ACCOUNT_XRP));
}
saCurSendMaxPass = saCurDeliverAct; // Record amount sent for pass.
if (!saCurDeliverAct)
{
terResult = tecPATH_DRY;
}
else if (!!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 (lsDEBUG, RippleCalc) << boost::str (boost::format ("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 (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeAccountFwd: ^ --> ACCOUNT -- XRP --> offer"));
// Deliver XRP to limbo.
terResult = lesActive.accountSend (uCurAccountID, ACCOUNT_XRP, saCurDeliverAct);
}
}
}
else if (!bPrvAccount && bNxtAccount)
{
if (uNode == uLast)
{
// offer --> ACCOUNT --> $
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeAccountFwd: offer --> ACCOUNT --> $ : %s") % saPrvDeliverReq);
STAmount& saCurReceive = psCur.saOutPass;
// Amount to credit.
saCurReceive = saPrvDeliverReq;
// No income balance adjustments necessary. The paying side inside the offer paid to this account.
}
else
{
// offer --> ACCOUNT --> account
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeAccountFwd: offer --> ACCOUNT --> account"));
saCurRedeemAct.zero (saCurRedeemReq);
saCurIssueAct.zero (saCurIssueReq);
// deliver -> redeem
if (saPrvDeliverReq && saCurRedeemReq) // Previous wants to deliver and can current redeem.
{
// Rate : 1.0 : quality out
calcNodeRipple (QUALITY_ONE, uQualityOut, saPrvDeliverReq, saCurRedeemReq, saPrvDeliverAct, saCurRedeemAct, uRateMax);
}
// deliver -> issue
// Wants to redeem and current would and can issue.
if (saPrvDeliverReq != saPrvDeliverAct // Previous still wants to deliver.
&& saCurRedeemReq == saCurRedeemAct // Current has more to redeem to next.
&& saCurIssueReq) // Current wants issue.
{
// Rate : 1.0 : transfer_rate
calcNodeRipple (QUALITY_ONE, lesActive.rippleTransferRate (uCurAccountID), saPrvDeliverReq, saCurIssueReq, saPrvDeliverAct, saCurIssueAct, uRateMax);
}
// No income balance adjustments necessary. The paying side inside the offer paid and the next link will receive.
STAmount saProvide = saCurRedeemAct + saCurIssueAct;
if (!saProvide)
terResult = tecPATH_DRY;
}
}
else
{
// offer --> ACCOUNT --> offer
// deliver/redeem -> deliver/issue.
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeAccountFwd: offer --> ACCOUNT --> offer"));
saCurDeliverAct.zero (saCurDeliverReq);
if (saPrvDeliverReq // Previous wants to deliver
&& saCurIssueReq) // Current wants issue.
{
// Rate : 1.0 : transfer_rate
calcNodeRipple (QUALITY_ONE, lesActive.rippleTransferRate (uCurAccountID), saPrvDeliverReq, saCurDeliverReq, saPrvDeliverAct, saCurDeliverAct, uRateMax);
}
// No income balance adjustments necessary. The paying side inside the offer paid and the next link will receive.
if (!saCurDeliverAct)
terResult = tecPATH_DRY;
}
return terResult;
}
TER RippleCalc::calcNodeFwd (const unsigned int uNode, PathState& psCur, const bool bMultiQuality)
{
const PathState::Node& pnCur = psCur.vpnNodes[uNode];
const bool bCurAccount = isSetBit (pnCur.uFlags, STPathElement::typeAccount);
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeFwd> uNode=%d") % uNode);
TER terResult = bCurAccount
? calcNodeAccountFwd (uNode, psCur, bMultiQuality)
: calcNodeOfferFwd (uNode, psCur, bMultiQuality);
if (tesSUCCESS == terResult && uNode + 1 != psCur.vpnNodes.size ())
{
terResult = calcNodeFwd (uNode + 1, psCur, bMultiQuality);
}
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeFwd< uNode=%d terResult=%d") % uNode % terResult);
return terResult;
}
// 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.
// <-- terResult: tesSUCCESS or tecPATH_DRY
// <-> pnNodes:
// --> [end]saWanted.mAmount
// --> [all]saWanted.mCurrency
// --> [all]saAccount
// <-> [0]saWanted.mAmount : --> limit, <-- actual
TER RippleCalc::calcNodeRev (const unsigned int uNode, PathState& psCur, const bool bMultiQuality)
{
PathState::Node& pnCur = psCur.vpnNodes[uNode];
bool const bCurAccount = isSetBit (pnCur.uFlags, STPathElement::typeAccount);
TER terResult;
// Do current node reverse.
const uint160& uCurIssuerID = pnCur.uIssuerID;
STAmount& saTransferRate = pnCur.saTransferRate;
saTransferRate = STAmount::saFromRate (lesActive.rippleTransferRate (uCurIssuerID));
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("calcNodeRev> uNode=%d bCurAccount=%d uIssuerID=%s saTransferRate=%s")
% uNode
% bCurAccount
% RippleAddress::createHumanAccountID (uCurIssuerID)
% saTransferRate);
terResult = bCurAccount
? calcNodeAccountRev (uNode, psCur, bMultiQuality)
: calcNodeOfferRev (uNode, psCur, bMultiQuality);
// Do previous.
if (tesSUCCESS != terResult)
{
// Error, don't continue.
nothing ();
}
else if (uNode)
{
// Continue in reverse.
terResult = calcNodeRev (uNode - 1, psCur, bMultiQuality);
}
WriteLog (lsINFO, RippleCalc) << boost::str (boost::format ("calcNodeRev< uNode=%d terResult=%s/%d") % uNode % transToken (terResult) % terResult);
return terResult;
}
// 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.
// <-- psCur.uQuality
void RippleCalc::pathNext (PathState::ref psrCur, 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 uLast = psrCur->vpnNodes.size () - 1;
psrCur->bConsumed = false;
// YYY This clearing should only be needed for nice logging.
psrCur->saInPass = STAmount (psrCur->saInReq.getCurrency (), psrCur->saInReq.getIssuer ());
psrCur->saOutPass = STAmount (psrCur->saOutReq.getCurrency (), psrCur->saOutReq.getIssuer ());
psrCur->vUnfundedBecame.clear ();
psrCur->umReverse.clear ();
WriteLog (lsDEBUG, RippleCalc) << "pathNext: Path In: " << psrCur->getJson ();
assert (psrCur->vpnNodes.size () >= 2);
lesCurrent = lesCheckpoint.duplicate (); // Restore from checkpoint.
for (unsigned int uIndex = psrCur->vpnNodes.size (); uIndex--;)
{
PathState::Node& pnCur = psrCur->vpnNodes[uIndex];
pnCur.saRevRedeem.zero ();
pnCur.saRevIssue.zero ();
pnCur.saRevDeliver.zero ();
pnCur.saFwdDeliver.zero ();
}
psrCur->terStatus = calcNodeRev (uLast, *psrCur, bMultiQuality);
WriteLog (lsDEBUG, RippleCalc) << "pathNext: Path after reverse: " << psrCur->getJson ();
if (tesSUCCESS == psrCur->terStatus)
{
// Do forward.
lesCurrent = lesCheckpoint.duplicate (); // Restore from checkpoint.
psrCur->terStatus = calcNodeFwd (0, *psrCur, bMultiQuality);
}
if (tesSUCCESS == psrCur->terStatus)
{
CondLog (!psrCur->saInPass || !psrCur->saOutPass, lsDEBUG, RippleCalc)
<< boost::str (boost::format ("pathNext: Error calcNodeFwd reported success for nothing: saOutPass=%s saInPass=%s")
% psrCur->saOutPass
% psrCur->saInPass);
if (!psrCur->saOutPass || !psrCur->saInPass)
throw std::runtime_error ("Made no progress.");
psrCur->uQuality = STAmount::getRate (psrCur->saOutPass, psrCur->saInPass); // Calculate relative quality.
WriteLog (lsDEBUG, RippleCalc) << "pathNext: Path after forward: " << psrCur->getJson ();
}
else
{
psrCur->uQuality = 0;
}
}
// <-- TER: Only returns tepPATH_PARTIAL if !bPartialPayment.
TER RippleCalc::rippleCalc (
// Compute paths vs this ledger entry set. Up to caller to actually apply to ledger.
LedgerEntrySet& lesActive, // <-> --> = Fee already applied to src balance.
STAmount& saMaxAmountAct, // <-- The computed input amount.
STAmount& saDstAmountAct, // <-- The computed output amount.
std::vector<PathState::pointer>& vpsExpanded,
// Issuer:
// XRP: ACCOUNT_XRP
// non-XRP: uSrcAccountID (for any issuer) or another account with trust node.
const STAmount& saMaxAmountReq, // --> -1 = no limit.
// Issuer:
// XRP: ACCOUNT_XRP
// non-XRP: uDstAccountID (for any issuer) or another account with trust node.
const STAmount& saDstAmountReq,
const uint160& uDstAccountID,
const uint160& uSrcAccountID,
const STPathSet& spsPaths,
const bool bPartialPayment,
const bool bLimitQuality,
const bool bNoRippleDirect,
const bool bStandAlone, // True, not to delete unfundeds.
const bool bOpenLedger
)
{
assert (lesActive.isValid ());
RippleCalc rc (lesActive, bOpenLedger);
WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("rippleCalc> saMaxAmountReq=%s saDstAmountReq=%s")
% saMaxAmountReq
% saDstAmountReq);
TER terResult = temUNCERTAIN;
// YYY Might do basic checks on src and dst validity as per doPayment.
if (bNoRippleDirect && spsPaths.isEmpty ())
{
WriteLog (lsINFO, RippleCalc) << "rippleCalc: Invalid transaction: No paths and direct ripple not allowed.";
return temRIPPLE_EMPTY;
}
// Incrementally search paths.
// bNoRippleDirect is a slight misnomer, it really means make no ripple default path.
if (!bNoRippleDirect)
{
// Build a default path. Use saDstAmountReq and saMaxAmountReq to imply nodes.
// XXX Might also make a XRP bridge by default.
PathState::pointer pspDirect = boost::make_shared<PathState> (saDstAmountReq, saMaxAmountReq);
if (!pspDirect)
return temUNKNOWN;
pspDirect->setExpanded (lesActive, STPath (), uDstAccountID, uSrcAccountID);
pspDirect->setIndex (vpsExpanded.size ());
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("rippleCalc: Build direct: status: %s")
% transToken (pspDirect->terStatus));
// Return if malformed.
if (isTemMalformed (pspDirect->terStatus))
return pspDirect->terStatus;
if (tesSUCCESS == pspDirect->terStatus)
{
// Had a success.
terResult = tesSUCCESS;
vpsExpanded.push_back (pspDirect);
}
else if (terNO_LINE != pspDirect->terStatus)
{
terResult = pspDirect->terStatus;
}
}
WriteLog (lsTRACE, RippleCalc) << "rippleCalc: Paths in set: " << spsPaths.size ();
int iIndex = 0;
BOOST_FOREACH (const STPath & spPath, spsPaths)
{
PathState::pointer pspExpanded = boost::make_shared<PathState> (saDstAmountReq, saMaxAmountReq);
if (!pspExpanded)
return temUNKNOWN;
WriteLog (lsTRACE, RippleCalc) << boost::str (boost::format ("rippleCalc: EXPAND: saDstAmountReq=%s saMaxAmountReq=%s uDstAccountID=%s uSrcAccountID=%s")
% saDstAmountReq
% saMaxAmountReq
% RippleAddress::createHumanAccountID (uDstAccountID)
% RippleAddress::createHumanAccountID (uSrcAccountID));
pspExpanded->setExpanded (lesActive, spPath, uDstAccountID, uSrcAccountID);
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("rippleCalc: Build path: %d: status: %s")
% ++iIndex
% transToken (pspExpanded->terStatus));
// Return, if the path specification was malformed.
if (isTemMalformed (pspExpanded->terStatus))
return pspExpanded->terStatus;
if (tesSUCCESS == pspExpanded->terStatus)
{
terResult = tesSUCCESS; // Had a success.
pspExpanded->setIndex (vpsExpanded.size ());
vpsExpanded.push_back (pspExpanded);
}
else if (terNO_LINE != pspExpanded->terStatus)
{
terResult = pspExpanded->terStatus;
}
}
if (tesSUCCESS != terResult)
{
return terResult == temUNCERTAIN ? terNO_LINE : terResult;
}
else
{
terResult = temUNCERTAIN;
}
saMaxAmountAct = STAmount (saMaxAmountReq.getCurrency (), saMaxAmountReq.getIssuer ());
saDstAmountAct = STAmount (saDstAmountReq.getCurrency (), saDstAmountReq.getIssuer ());
const LedgerEntrySet lesBase = lesActive; // Checkpoint with just fees paid.
const uint64 uQualityLimit = bLimitQuality ? STAmount::getRate (saDstAmountReq, saMaxAmountReq) : 0;
// When processing, don't want to complicate directory walking with deletion.
std::vector<uint256> vuUnfundedBecame; // Offers that became unfunded.
int iPass = 0;
while (temUNCERTAIN == terResult)
{
int iBest = -1;
const LedgerEntrySet lesCheckpoint = lesActive;
int iDry = 0;
bool bMultiQuality = false; // True, if ever computed multi-quality.
// Find the best path.
BOOST_FOREACH (PathState::ref pspCur, vpsExpanded)
{
if (pspCur->uQuality) // Only do active paths.
{
bMultiQuality = 1 == vpsExpanded.size () - iDry, // Computing the only non-dry path, compute multi-quality.
pspCur->saInAct = saMaxAmountAct; // Update to current amount processed.
pspCur->saOutAct = saDstAmountAct;
CondLog (pspCur->saInReq.isPositive () && pspCur->saInAct >= pspCur->saInReq, lsWARNING, RippleCalc)
<< boost::str (boost::format ("rippleCalc: DONE: saInAct=%s saInReq=%s")
% pspCur->saInAct
% pspCur->saInReq);
assert (pspCur->saInReq.isNegative () || pspCur->saInAct < pspCur->saInReq); // Error if done.
CondLog (pspCur->saOutAct >= pspCur->saOutReq, lsWARNING, RippleCalc)
<< boost::str (boost::format ("rippleCalc: ALREADY DONE: saOutAct=%s saOutReq=%s")
% pspCur->saOutAct
% pspCur->saOutReq);
assert (pspCur->saOutAct < pspCur->saOutReq); // Error if done, output met.
rc.pathNext (pspCur, bMultiQuality, lesCheckpoint, lesActive); // Compute increment.
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("rippleCalc: AFTER: mIndex=%d uQuality=%d rate=%s")
% pspCur->mIndex
% pspCur->uQuality
% STAmount::saFromRate (pspCur->uQuality));
if (!pspCur->uQuality)
{
// Path was dry.
++iDry;
}
else
{
CondLog (!pspCur->saInPass || !pspCur->saOutPass, lsDEBUG, RippleCalc)
<< boost::str (boost::format ("rippleCalc: better: uQuality=%s saInPass=%s saOutPass=%s")
% STAmount::saFromRate (pspCur->uQuality)
% pspCur->saInPass
% pspCur->saOutPass);
assert (!!pspCur->saInPass && !!pspCur->saOutPass);
if ((!bLimitQuality || pspCur->uQuality <= uQualityLimit) // Quality is not limted or increment has allowed quality.
&& (iBest < 0 // Best is not yet set.
|| PathState::lessPriority (*vpsExpanded[iBest], *pspCur))) // Current is better than set.
{
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("rippleCalc: better: mIndex=%d uQuality=%s rate=%s saInPass=%s saOutPass=%s")
% pspCur->mIndex
% pspCur->uQuality
% STAmount::saFromRate (pspCur->uQuality)
% pspCur->saInPass
% pspCur->saOutPass);
assert (lesActive.isValid ());
lesActive.swapWith (pspCur->lesEntries); // For the path, save ledger state.
lesActive.invalidate ();
iBest = pspCur->getIndex ();
}
}
}
}
if (ShouldLog (lsDEBUG, RippleCalc))
{
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("rippleCalc: Summary: Pass: %d Dry: %d Paths: %d") % ++iPass % iDry % vpsExpanded.size ());
BOOST_FOREACH (PathState::ref pspCur, vpsExpanded)
{
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("rippleCalc: Summary: %d rate: %s quality:%d best: %d consumed: %d")
% pspCur->mIndex
% STAmount::saFromRate (pspCur->uQuality)
% pspCur->uQuality
% (iBest == pspCur->getIndex ())
% pspCur->bConsumed);
}
}
if (iBest >= 0)
{
// Apply best path.
PathState::pointer pspBest = vpsExpanded[iBest];
WriteLog (lsDEBUG, RippleCalc) << boost::str (boost::format ("rippleCalc: best: uQuality=%s saInPass=%s saOutPass=%s")
% STAmount::saFromRate (pspBest->uQuality)
% pspBest->saInPass
% pspBest->saOutPass);
// Record best pass' offers that became unfunded for deletion on success.
vuUnfundedBecame.insert (vuUnfundedBecame.end (), pspBest->vUnfundedBecame.begin (), pspBest->vUnfundedBecame.end ());
// Record best pass' LedgerEntrySet to build off of and potentially return.
assert (pspBest->lesEntries.isValid ());
lesActive.swapWith (pspBest->lesEntries);
pspBest->lesEntries.invalidate ();
saMaxAmountAct += pspBest->saInPass;
saDstAmountAct += pspBest->saOutPass;
if (pspBest->bConsumed || bMultiQuality)
{
++iDry;
pspBest->uQuality = 0;
}
if (saDstAmountAct == saDstAmountReq)
{
// Done. Delivered requested amount.
terResult = tesSUCCESS;
}
else if (saDstAmountAct > saDstAmountReq)
{
WriteLog (lsFATAL, RippleCalc) << boost::str (boost::format ("rippleCalc: TOO MUCH: saDstAmountAct=%s saDstAmountReq=%s")
% saDstAmountAct
% saDstAmountReq);
return tefEXCEPTION; // TEMPORARY
assert (false);
}
else if (saMaxAmountAct != saMaxAmountReq && iDry != vpsExpanded.size ())
{
// Have not met requested amount or max send, try to do more. Prepare for next pass.
// Merge best pass' umReverse.
rc.mumSource.insert (pspBest->umReverse.begin (), pspBest->umReverse.end ());
}
else if (!bPartialPayment)
{
// Have sent maximum allowed. Partial payment not allowed.
terResult = tecPATH_PARTIAL;
}
else
{
// Have sent maximum allowed. Partial payment allowed. Success.
terResult = tesSUCCESS;
}
}
// Not done and ran out of paths.
else if (!bPartialPayment)
{
// Partial payment not allowed.
terResult = tecPATH_PARTIAL;
}
// Partial payment ok.
else if (!saDstAmountAct)
{
// No payment at all.
terResult = tecPATH_DRY;
}
else
{
terResult = tesSUCCESS;
}
}
if (!bStandAlone)
{
if (tesSUCCESS == terResult)
{
// Delete became unfunded offers.
BOOST_FOREACH (uint256 const & uOfferIndex, vuUnfundedBecame)
{
if (tesSUCCESS == terResult)
{
WriteLog (lsDEBUG, RippleCalc) << "Became unfunded " << uOfferIndex.GetHex ();
terResult = lesActive.offerDelete (uOfferIndex);
}
}
}
// Delete found unfunded offers.
BOOST_FOREACH (uint256 const & uOfferIndex, rc.musUnfundedFound)
{
if (tesSUCCESS == terResult)
{
WriteLog (lsDEBUG, RippleCalc) << "Delete unfunded " << uOfferIndex.GetHex ();
terResult = lesActive.offerDelete (uOfferIndex);
}
}
}
return terResult;
}
//
// Rough cut of automatic bridging.
//
#if 0
// XXX Need to adjust for fees.
// Find offers to satisfy pnDst.
// - Does not adjust any balances as there is at least a forward pass to come.
// --> pnDst.saWanted: currency and amount wanted
// --> pnSrc.saIOURedeem.mCurrency: use this before saIOUIssue, limit to use.
// --> pnSrc.saIOUIssue.mCurrency: use this after saIOURedeem, limit to use.
// <-- pnDst.saReceive
// <-- pnDst.saIOUForgive
// <-- pnDst.saIOUAccept
// <-- terResult : tesSUCCESS = no error and if !bAllowPartial complelely satisfied wanted.
// <-> usOffersDeleteAlways:
// <-> usOffersDeleteOnSuccess:
TER calcOfferFill (Node& pnSrc, Node& pnDst, bool bAllowPartial)
{
TER terResult;
if (pnDst.saWanted.isNative ())
{
// Transfer XRP.
STAmount saSrcFunds = pnSrc.saAccount->accountHolds (pnSrc.saAccount, uint160 (0), uint160 (0));
if (saSrcFunds && (bAllowPartial || saSrcFunds > pnDst.saWanted))
{
pnSrc.saSend = min (saSrcFunds, pnDst.saWanted);
pnDst.saReceive = pnSrc.saSend;
}
else
{
terResult = terINSUF_PATH;
}
}
else
{
// Ripple funds.
// Redeem to limit.
terResult = calcOfferFill (
accountHolds (pnSrc.saAccount, pnDst.saWanted.getCurrency (), pnDst.saWanted.getIssuer ()),
pnSrc.saIOURedeem,
pnDst.saIOUForgive,
bAllowPartial);
if (tesSUCCESS == terResult)
{
// Issue to wanted.
terResult = calcOfferFill (
pnDst.saWanted, // As much as wanted is available, limited by credit limit.
pnSrc.saIOUIssue,
pnDst.saIOUAccept,
bAllowPartial);
}
if (tesSUCCESS == terResult && !bAllowPartial)
{
STAmount saTotal = pnDst.saIOUForgive + pnSrc.saIOUAccept;
if (saTotal != saWanted)
terResult = terINSUF_PATH;
}
}
return terResult;
}
#endif
#if 0
// Get the next offer limited by funding.
// - Stop when becomes unfunded.
void TransactionEngine::calcOfferBridgeNext (
uint256 const& uBookRoot, // --> Which order book to look in.
uint256 const& uBookEnd, // --> Limit of how far to look.
uint256& uBookDirIndex, // <-> Current directory. <-- 0 = no offer available.
uint64& uBookDirNode, // <-> Which node. 0 = first.
unsigned int& uBookDirEntry, // <-> Entry in node. 0 = first.
STAmount& saOfferIn, // <-- How much to pay in, fee inclusive, to get saOfferOut out.
STAmount& saOfferOut // <-- How much offer pays out.
)
{
saOfferIn = 0; // XXX currency & issuer
saOfferOut = 0; // XXX currency & issuer
bool bDone = false;
while (!bDone)
{
uint256 uOfferIndex;
// Get uOfferIndex.
mNodes.dirNext (uBookRoot, uBookEnd, uBookDirIndex, uBookDirNode, uBookDirEntry, uOfferIndex);
SLE::pointer sleOffer = entryCache (ltOFFER, uOfferIndex);
uint160 uOfferOwnerID = sleOffer->getFieldAccount160 (sfAccount);
STAmount saOfferPays = sleOffer->getFieldAmount (sfTakerGets);
STAmount saOfferGets = sleOffer->getFieldAmount (sfTakerPays);
if (sleOffer->isFieldPresent (sfExpiration) && sleOffer->getFieldU32 (sfExpiration) <= mLedger->getParentCloseTimeNC ())
{
// Offer is expired.
WriteLog (lsINFO, RippleCalc) << "calcOfferFirst: encountered expired offer";
musUnfundedFound.insert(uOfferIndex);
}
else
{
STAmount saOfferFunds = accountFunds (uOfferOwnerID, saOfferPays);
// Outbound fees are paid by offer owner.
// XXX Calculate outbound fee rate.
if (saOfferPays.isNative ())
{
// No additional fees for XRP.
nothing ();
}
else if (saOfferPays.getIssuer () == uOfferOwnerID)
{
// Offerer is issue own IOUs.
// No fees at this exact point, XXX receiving node may charge a fee.
// XXX Make sure has a credit line with receiver, limit by credit line.
nothing ();
// XXX Broken - could be issuing or redeeming or both.
}
else
{
// Offer must be redeeming IOUs.
// No additional
// XXX Broken
}
if (!saOfferFunds.isPositive ())
{
// Offer is unfunded.
WriteLog (lsINFO, RippleCalc) << "calcOfferFirst: offer unfunded: delete";
}
else if (saOfferFunds >= saOfferPays)
{
// Offer fully funded.
// Account transferring funds in to offer always pays inbound fees.
saOfferIn = saOfferGets; // XXX Add in fees?
saOfferOut = saOfferPays;
bDone = true;
}
else
{
// Offer partially funded.
// saOfferIn/saOfferFunds = saOfferGets/saOfferPays
// XXX Round such that all saOffer funds are exhausted.
saOfferIn = (saOfferFunds * saOfferGets) / saOfferPays; // XXX Add in fees?
saOfferOut = saOfferFunds;
bDone = true;
}
}
if (!bDone)
{
// musUnfundedFound.insert(uOfferIndex);
}
}
while (bNext);
}
#endif
#if 0
// If either currency is not XRP, then also calculates vs XRP bridge.
// --> saWanted: Limit of how much is wanted out.
// <-- saPay: How much to pay into the offer.
// <-- saGot: How much to the offer pays out. Never more than saWanted.
// Given two value's enforce a minimum:
// - reverse: prv is maximum to pay in (including fee) - cur is what is wanted: generally, minimizing prv
// - forward: prv is actual amount to pay in (including fee) - cur is what is wanted: generally, minimizing cur
// Value in is may be rippled or credited from limbo. Value out is put in limbo.
// If next is an offer, the amount needed is in cur redeem.
// XXX What about account mentioned multiple times via offers?
void TransactionEngine::calcNodeOffer (
bool bForward,
bool bMultiQuality, // True, if this is the only active path: we can do multiple qualities in this pass.
const uint160& uPrvAccountID, // If 0, then funds from previous offers limbo
const uint160& uPrvCurrencyID,
const uint160& uPrvIssuerID,
const uint160& uCurCurrencyID,
const uint160& uCurIssuerID,
const STAmount& uPrvRedeemReq, // --> In limit.
STAmount& uPrvRedeemAct, // <-> In limit achieved.
const STAmount& uCurRedeemReq, // --> Out limit. Driver when uCurIssuerID == uNxtIssuerID (offer would redeem to next)
STAmount& uCurRedeemAct, // <-> Out limit achieved.
const STAmount& uCurIssueReq, // --> In limit.
STAmount& uCurIssueAct, // <-> In limit achieved.
const STAmount& uCurIssueReq, // --> Out limit. Driver when uCurIssueReq != uNxtIssuerID (offer would effectively issue or transfer to next)
STAmount& uCurIssueAct, // <-> Out limit achieved.
STAmount& saPay,
STAmount& saGot
) const
{
TER terResult = temUNKNOWN;
// Direct: not bridging via XRP
bool bDirectNext = true; // True, if need to load.
uint256 uDirectQuality;
uint256 uDirectTip = Ledger::getBookBase (uGetsCurrency, uGetsIssuerID, uPaysCurrency, uPaysIssuerID);
uint256 uDirectEnd = Ledger::getQualityNext (uDirectTip);
// Bridging: bridging via XRP
bool bBridge = true; // True, if bridging active. False, missing an offer.
uint256 uBridgeQuality;
STAmount saBridgeIn; // Amount available.
STAmount saBridgeOut;
bool bInNext = true; // True, if need to load.
STAmount saInIn; // Amount available. Consumed in loop. Limited by offer funding.
STAmount saInOut;
uint256 uInTip; // Current entry.
uint256 uInEnd;
unsigned int uInEntry;
bool bOutNext = true;
STAmount saOutIn;
STAmount saOutOut;
uint256 uOutTip;
uint256 uOutEnd;
unsigned int uOutEntry;
saPay.zero ();
saPay.setCurrency (uPrvCurrencyID);
saPay.setIssuer (uPrvIssuerID);
saNeed = saWanted;
if (!uCurCurrencyID && !uPrvCurrencyID)
{
// Bridging: Neither currency is XRP.
uInTip = Ledger::getBookBase (uPrvCurrencyID, uPrvIssuerID, CURRENCY_XRP, ACCOUNT_XRP);
uInEnd = Ledger::getQualityNext (uInTip);
uOutTip = Ledger::getBookBase (CURRENCY_XRP, ACCOUNT_XRP, uCurCurrencyID, uCurIssuerID);
uOutEnd = Ledger::getQualityNext (uInTip);
}
// Find our head offer.
bool bRedeeming = false;
bool bIssuing = false;
// The price varies as we change between issuing and transferring, so unless bMultiQuality, we must stick with a mode once it
// is determined.
if (bBridge && (bInNext || bOutNext))
{
// Bridging and need to calculate next bridge rate.
// A bridge can consist of multiple offers. As offers are consumed, the effective rate changes.
if (bInNext)
{
// sleInDir = entryCache(ltDIR_NODE, mLedger->getNextLedgerIndex(uInIndex, uInEnd));
// Get the next funded offer.
offerBridgeNext (uInIndex, uInEnd, uInEntry, saInIn, saInOut); // Get offer limited by funding.
bInNext = false;
}
if (bOutNext)
{
// sleOutDir = entryCache(ltDIR_NODE, mLedger->getNextLedgerIndex(uOutIndex, uOutEnd));
offerNext (uOutIndex, uOutEnd, uOutEntry, saOutIn, saOutOut);
bOutNext = false;
}
if (!uInIndex || !uOutIndex)
{
bBridge = false; // No more offers to bridge.
}
else
{
// Have bridge in and out entries.
// Calculate bridge rate. Out offer pay ripple fee. In offer fee is added to in cost.
saBridgeOut.zero ();
if (saInOut < saOutIn)
{
// Limit by in.
// XXX Need to include fees in saBridgeIn.
saBridgeIn = saInIn; // All of in
// Limit bridge out: saInOut/saBridgeOut = saOutIn/saOutOut
// Round such that we would take all of in offer, otherwise would have leftovers.
saBridgeOut = (saInOut * saOutOut) / saOutIn;
}
else if (saInOut > saOutIn)
{
// Limit by out, if at all.
// XXX Need to include fees in saBridgeIn.
// Limit bridge in:saInIn/saInOuts = aBridgeIn/saOutIn
// Round such that would take all of out offer.
saBridgeIn = (saInIn * saOutIn) / saInOuts;
saBridgeOut = saOutOut; // All of out.
}
else
{
// Entries match,
// XXX Need to include fees in saBridgeIn.
saBridgeIn = saInIn; // All of in
saBridgeOut = saOutOut; // All of out.
}
uBridgeQuality = STAmount::getRate (saBridgeIn, saBridgeOut); // Inclusive of fees.
}
}
if (bBridge)
{
bUseBridge = !uDirectTip || (uBridgeQuality < uDirectQuality)
}
else if (!!uDirectTip)
{
bUseBridge = false
}
else
{
// No more offers. Declare success, even if none returned.
saGot = saWanted - saNeed;
terResult = tesSUCCESS;
}
if (tesSUCCESS != terResult)
{
STAmount& saAvailIn = bUseBridge ? saBridgeIn : saDirectIn;
STAmount& saAvailOut = bUseBridge ? saBridgeOut : saDirectOut;
if (saAvailOut > saNeed)
{
// Consume part of offer. Done.
saNeed = 0;
saPay += (saNeed * saAvailIn) / saAvailOut; // Round up, prefer to pay more.
}
else
{
// Consume entire offer.
saNeed -= saAvailOut;
saPay += saAvailIn;
if (bUseBridge)
{
// Consume bridge out.
if (saOutOut == saAvailOut)
{
// Consume all.
saOutOut = 0;
saOutIn = 0;
bOutNext = true;
}
else
{
// Consume portion of bridge out, must be consuming all of bridge in.
// saOutIn/saOutOut = saSpent/saAvailOut
// Round?
saOutIn -= (saOutIn * saAvailOut) / saOutOut;
saOutOut -= saAvailOut;
}
// Consume bridge in.
if (saOutIn == saAvailIn)
{
// Consume all.
saInOut = 0;
saInIn = 0;
bInNext = true;
}
else
{
// Consume portion of bridge in, must be consuming all of bridge out.
// saInIn/saInOut = saAvailIn/saPay
// Round?
saInOut -= (saInOut * saAvailIn) / saInIn;
saInIn -= saAvailIn;
}
}
else
{
bDirectNext = true;
}
}
}
}
#endif
// vim:ts=4