From b7f3baee15434483de8c1bb5f3cfa2a42f07cfcb Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Sun, 9 Sep 2012 19:54:46 -0700 Subject: [PATCH] Split up TransactionEngine, part 1. --- src/LedgerEntrySet.cpp | 362 +++++ src/LedgerEntrySet.h | 26 +- src/RippleCalc.cpp | 2400 +++++++++++++++++++++++++++++++ src/RippleCalc.h | 190 +++ src/SerializedTypes.cpp | 3 + src/SerializedTypes.h | 3 + src/TransactionEngine.cpp | 2809 +------------------------------------ src/TransactionEngine.h | 186 +-- 8 files changed, 3027 insertions(+), 2952 deletions(-) create mode 100644 src/RippleCalc.cpp create mode 100644 src/RippleCalc.h diff --git a/src/LedgerEntrySet.cpp b/src/LedgerEntrySet.cpp index c5747b85b0..3c5adc432a 100644 --- a/src/LedgerEntrySet.cpp +++ b/src/LedgerEntrySet.cpp @@ -756,4 +756,366 @@ Log(lsINFO) << boost::str(boost::format("dirNext: uDirEntry=%d uEntryIndex=%s") return true; } +TER LedgerEntrySet::offerDelete(const SLE::pointer& sleOffer, const uint256& uOfferIndex, const uint160& uOwnerID) +{ + uint64 uOwnerNode = sleOffer->getIFieldU64(sfOwnerNode); + TER terResult = dirDelete(false, uOwnerNode, Ledger::getOwnerDirIndex(uOwnerID), uOfferIndex, false); + + if (tesSUCCESS == terResult) + { + uint256 uDirectory = sleOffer->getIFieldH256(sfBookDirectory); + uint64 uBookNode = sleOffer->getIFieldU64(sfBookNode); + + terResult = dirDelete(false, uBookNode, uDirectory, uOfferIndex, true); + } + + entryDelete(sleOffer); + + return terResult; +} + +TER LedgerEntrySet::offerDelete(const uint256& uOfferIndex) +{ + SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); + const uint160 uOwnerID = sleOffer->getIValueFieldAccount(sfAccount).getAccountID(); + + return offerDelete(sleOffer, uOfferIndex, uOwnerID); +} + +// Returns amount owed by uToAccountID to uFromAccountID. +// <-- $owed/uCurrencyID/uToAccountID: positive: uFromAccountID holds IOUs., negative: uFromAccountID owes IOUs. +STAmount LedgerEntrySet::rippleOwed(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID) +{ + STAmount saBalance; + SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uToAccountID, uFromAccountID, uCurrencyID)); + + if (sleRippleState) + { + saBalance = sleRippleState->getIValueFieldAmount(sfBalance); + if (uToAccountID < uFromAccountID) + saBalance.negate(); + saBalance.setIssuer(uToAccountID); + } + else + { + Log(lsINFO) << "rippleOwed: No credit line between " + << NewcoinAddress::createHumanAccountID(uFromAccountID) + << " and " + << NewcoinAddress::createHumanAccountID(uToAccountID) + << " for " + << STAmount::createHumanCurrency(uCurrencyID) + << "." ; + + assert(false); + } + + return saBalance; +} + +// Maximum amount of IOUs uToAccountID will hold from uFromAccountID. +// <-- $amount/uCurrencyID/uToAccountID. +STAmount LedgerEntrySet::rippleLimit(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID) +{ + STAmount saLimit; + SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uToAccountID, uFromAccountID, uCurrencyID)); + + assert(sleRippleState); + if (sleRippleState) + { + saLimit = sleRippleState->getIValueFieldAmount(uToAccountID < uFromAccountID ? sfLowLimit : sfHighLimit); + saLimit.setIssuer(uToAccountID); + } + + return saLimit; + +} + +uint32 LedgerEntrySet::rippleTransferRate(const uint160& uIssuerID) +{ + SLE::pointer sleAccount = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uIssuerID)); + + uint32 uQuality = sleAccount && sleAccount->getIFieldPresent(sfTransferRate) + ? sleAccount->getIFieldU32(sfTransferRate) + : QUALITY_ONE; + + Log(lsINFO) << boost::str(boost::format("rippleTransferRate: uIssuerID=%s account_exists=%d transfer_rate=%f") + % NewcoinAddress::createHumanAccountID(uIssuerID) + % !!sleAccount + % (uQuality/1000000000.0)); + + assert(sleAccount); + + return uQuality; +} + +// XXX Might not need this, might store in nodes on calc reverse. +uint32 LedgerEntrySet::rippleQualityIn(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID, const SOE_Field sfLow, const SOE_Field sfHigh) +{ + uint32 uQuality = QUALITY_ONE; + SLE::pointer sleRippleState; + + if (uToAccountID == uFromAccountID) + { + nothing(); + } + else + { + sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uToAccountID, uFromAccountID, uCurrencyID)); + + if (sleRippleState) + { + SOE_Field sfField = uToAccountID < uFromAccountID ? sfLow: sfHigh; + + uQuality = sleRippleState->getIFieldPresent(sfField) + ? sleRippleState->getIFieldU32(sfField) + : QUALITY_ONE; + + if (!uQuality) + uQuality = 1; // Avoid divide by zero. + } + } + + Log(lsINFO) << boost::str(boost::format("rippleQuality: %s uToAccountID=%s uFromAccountID=%s uCurrencyID=%s bLine=%d uQuality=%f") + % (sfLow == sfLowQualityIn ? "in" : "out") + % NewcoinAddress::createHumanAccountID(uToAccountID) + % NewcoinAddress::createHumanAccountID(uFromAccountID) + % STAmount::createHumanCurrency(uCurrencyID) + % !!sleRippleState + % (uQuality/1000000000.0)); + + assert(uToAccountID == uFromAccountID || !!sleRippleState); + + return uQuality; +} + +// Return how much of uIssuerID's uCurrencyID IOUs that uAccountID holds. May be negative. +// <-- IOU's uAccountID has of uIssuerID +STAmount LedgerEntrySet::rippleHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID) +{ + STAmount saBalance; + SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uAccountID, uIssuerID, uCurrencyID)); + + if (sleRippleState) + { + saBalance = sleRippleState->getIValueFieldAmount(sfBalance); + + if (uAccountID > uIssuerID) + saBalance.negate(); // Put balance in uAccountID terms. + } + + return saBalance; +} + +// <-- saAmount: amount of uCurrencyID held by uAccountID. May be negative. +STAmount LedgerEntrySet::accountHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID) +{ + STAmount saAmount; + + if (!uCurrencyID) + { + SLE::pointer sleAccount = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uAccountID)); + + saAmount = sleAccount->getIValueFieldAmount(sfBalance); + + Log(lsINFO) << "accountHolds: stamps: " << saAmount.getText(); + } + else + { + saAmount = rippleHolds(uAccountID, uCurrencyID, uIssuerID); + + Log(lsINFO) << "accountHolds: " + << saAmount.getFullText() + << " : " + << STAmount::createHumanCurrency(uCurrencyID) + << "/" + << NewcoinAddress::createHumanAccountID(uIssuerID); + } + + return saAmount; +} + +// Returns the funds available for uAccountID for a currency/issuer. +// Use when you need a default for rippling uAccountID's currency. +// --> saDefault/currency/issuer +// <-- saFunds: Funds available. May be negative. +// If the issuer is the same as uAccountID, funds are unlimited, use result is saDefault. +STAmount LedgerEntrySet::accountFunds(const uint160& uAccountID, const STAmount& saDefault) +{ + STAmount saFunds; + + Log(lsINFO) << "accountFunds: uAccountID=" + << NewcoinAddress::createHumanAccountID(uAccountID); + Log(lsINFO) << "accountFunds: saDefault.isNative()=" << saDefault.isNative(); + Log(lsINFO) << "accountFunds: saDefault.getIssuer()=" + << NewcoinAddress::createHumanAccountID(saDefault.getIssuer()); + + if (!saDefault.isNative() && saDefault.getIssuer() == uAccountID) + { + saFunds = saDefault; + + Log(lsINFO) << "accountFunds: offer funds: ripple self-funded: " << saFunds.getText(); + } + else + { + saFunds = accountHolds(uAccountID, saDefault.getCurrency(), saDefault.getIssuer()); + + Log(lsINFO) << "accountFunds: offer funds: uAccountID =" + << NewcoinAddress::createHumanAccountID(uAccountID) + << " : " + << saFunds.getText() + << "/" + << saDefault.getHumanCurrency() + << "/" + << NewcoinAddress::createHumanAccountID(saDefault.getIssuer()); + } + + return saFunds; +} + +// Calculate transit fee. +STAmount LedgerEntrySet::rippleTransferFee(const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID, const STAmount& saAmount) +{ + STAmount saTransitFee; + + if (uSenderID != uIssuerID && uReceiverID != uIssuerID) + { + uint32 uTransitRate = rippleTransferRate(uIssuerID); + + if (QUALITY_ONE != uTransitRate) + { + STAmount saTransitRate(CURRENCY_ONE, uTransitRate, -9); + + saTransitFee = STAmount::multiply(saAmount, saTransitRate, saAmount.getCurrency(), saAmount.getIssuer()); + } + } + + return saTransitFee; +} + +// Direct send w/o fees: redeeming IOUs and/or sending own IOUs. +void LedgerEntrySet::rippleCredit(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount, bool bCheckIssuer) +{ + uint160 uIssuerID = saAmount.getIssuer(); + + assert(!bCheckIssuer || uSenderID == uIssuerID || uReceiverID == uIssuerID); + + bool bFlipped = uSenderID > uReceiverID; + uint256 uIndex = Ledger::getRippleStateIndex(uSenderID, uReceiverID, saAmount.getCurrency()); + SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, uIndex); + + if (!sleRippleState) + { + Log(lsINFO) << "rippleCredit: Creating ripple line: " << uIndex.ToString(); + + STAmount saBalance = saAmount; + + sleRippleState = entryCreate(ltRIPPLE_STATE, uIndex); + + if (!bFlipped) + saBalance.negate(); + + sleRippleState->setIFieldAmount(sfBalance, saBalance); + sleRippleState->setIFieldAccount(bFlipped ? sfHighID : sfLowID, uSenderID); + sleRippleState->setIFieldAccount(bFlipped ? sfLowID : sfHighID, uReceiverID); + } + else + { + STAmount saBalance = sleRippleState->getIValueFieldAmount(sfBalance); + + if (!bFlipped) + saBalance.negate(); // Put balance in low terms. + + saBalance += saAmount; + + if (!bFlipped) + saBalance.negate(); + + sleRippleState->setIFieldAmount(sfBalance, saBalance); + + entryModify(sleRippleState); + } +} + +// Send regardless of limits. +// --> saAmount: Amount/currency/issuer for receiver to get. +// <-- saActual: Amount actually sent. Sender pay's fees. +STAmount LedgerEntrySet::rippleSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount) +{ + STAmount saActual; + const uint160 uIssuerID = saAmount.getIssuer(); + + assert(!!uSenderID && !!uReceiverID); + + if (uSenderID == uIssuerID || uReceiverID == uIssuerID) + { + // Direct send: redeeming IOUs and/or sending own IOUs. + rippleCredit(uSenderID, uReceiverID, saAmount); + + saActual = saAmount; + } + else + { + // Sending 3rd party IOUs: transit. + + STAmount saTransitFee = rippleTransferFee(uSenderID, uReceiverID, uIssuerID, saAmount); + + saActual = !saTransitFee ? saAmount : saAmount+saTransitFee; + + saActual.setIssuer(uIssuerID); // XXX Make sure this done in + above. + + rippleCredit(uIssuerID, uReceiverID, saAmount); + rippleCredit(uSenderID, uIssuerID, saActual); + } + + return saActual; +} + +void LedgerEntrySet::accountSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount) +{ + assert(!saAmount.isNegative()); + + if (!saAmount) + { + nothing(); + } + else if (saAmount.isNative()) + { + SLE::pointer sleSender = !!uSenderID + ? entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uSenderID)) + : SLE::pointer(); + SLE::pointer sleReceiver = !!uReceiverID + ? entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uReceiverID)) + : SLE::pointer(); + + Log(lsINFO) << boost::str(boost::format("accountSend> %s (%s) -> %s (%s) : %s") + % NewcoinAddress::createHumanAccountID(uSenderID) + % (sleSender ? (sleSender->getIValueFieldAmount(sfBalance)).getFullText() : "-") + % NewcoinAddress::createHumanAccountID(uReceiverID) + % (sleReceiver ? (sleReceiver->getIValueFieldAmount(sfBalance)).getFullText() : "-") + % saAmount.getFullText()); + + if (sleSender) + { + sleSender->setIFieldAmount(sfBalance, sleSender->getIValueFieldAmount(sfBalance) - saAmount); + entryModify(sleSender); + } + + if (sleReceiver) + { + sleReceiver->setIFieldAmount(sfBalance, sleReceiver->getIValueFieldAmount(sfBalance) + saAmount); + entryModify(sleReceiver); + } + + Log(lsINFO) << boost::str(boost::format("accountSend< %s (%s) -> %s (%s) : %s") + % NewcoinAddress::createHumanAccountID(uSenderID) + % (sleSender ? (sleSender->getIValueFieldAmount(sfBalance)).getFullText() : "-") + % NewcoinAddress::createHumanAccountID(uReceiverID) + % (sleReceiver ? (sleReceiver->getIValueFieldAmount(sfBalance)).getFullText() : "-") + % saAmount.getFullText()); + } + else + { + rippleSend(uSenderID, uReceiverID, saAmount); + } +} // vim:ts=4 diff --git a/src/LedgerEntrySet.h b/src/LedgerEntrySet.h index 256275b00e..6abef7cab3 100644 --- a/src/LedgerEntrySet.h +++ b/src/LedgerEntrySet.h @@ -17,7 +17,6 @@ enum LedgerEntryAction taaCREATE, // Newly created. }; - class LedgerEntrySetEntry { public: @@ -68,6 +67,9 @@ public: void init(Ledger::ref ledger, const uint256& transactionID, uint32 ledgerID); void clear(); + Ledger::pointer& getLedger() { return mLedger; } + const Ledger::pointer& getLedgerRef() const { return mLedger; } + // basic entry functions SLE::pointer getEntry(const uint256& index, LedgerEntryAction&); LedgerEntryAction hasEntry(const uint256& index) const; @@ -80,7 +82,7 @@ public: SLE::pointer entryCreate(LedgerEntryType letType, const uint256& uIndex); SLE::pointer entryCache(LedgerEntryType letType, const uint256& uIndex); - // Utility entry functions. + // Directory functions. TER dirAdd( uint64& uNodeDir, // Node of entry. const uint256& uRootIndex, @@ -96,6 +98,26 @@ public: bool dirFirst(const uint256& uRootIndex, SLE::pointer& sleNode, unsigned int& uDirEntry, uint256& uEntryIndex); bool dirNext(const uint256& uRootIndex, SLE::pointer& sleNode, unsigned int& uDirEntry, uint256& uEntryIndex); + // Offer functions. + TER offerDelete(const uint256& uOfferIndex); + TER offerDelete(const SLE::pointer& sleOffer, const uint256& uOfferIndex, const uint160& uOwnerID); + + // Balance functions. + uint32 rippleTransferRate(const uint160& uIssuerID); + STAmount rippleOwed(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID); + STAmount rippleLimit(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID); + uint32 rippleQualityIn(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID, const SOE_Field sfLow=sfLowQualityIn, const SOE_Field sfHigh=sfHighQualityIn); + uint32 rippleQualityOut(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID) + { return rippleQualityIn(uToAccountID, uFromAccountID, uCurrencyID, sfLowQualityOut, sfHighQualityOut); } + + STAmount rippleHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID); + STAmount rippleTransferFee(const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID, const STAmount& saAmount); + void rippleCredit(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount, bool bCheckIssuer=true); + STAmount rippleSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount); + + STAmount accountHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID); + void accountSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount); + STAmount accountFunds(const uint160& uAccountID, const STAmount& saDefault); Json::Value getJson(int) const; void calcRawMeta(Serializer&, Ledger::ref originalLedger); diff --git a/src/RippleCalc.cpp b/src/RippleCalc.cpp new file mode 100644 index 0000000000..7b88303bc9 --- /dev/null +++ b/src/RippleCalc.cpp @@ -0,0 +1,2400 @@ + +#include +#include +#include + +#include "RippleCalc.h" +#include "Log.h" + +#include "../json/writer.h" + +std::size_t hash_value(const aciSource& asValue) +{ + std::size_t seed = 0; + + asValue.get<0>().hash_combine(seed); + asValue.get<1>().hash_combine(seed); + asValue.get<2>().hash_combine(seed); + + return seed; +} + +// If needed, advance to next funded offer. +// - Automatically advances to first offer. +// - Set bEntryAdvance to advance to next entry. +// <-- uOfferIndex : 0=end of list. +TER RippleCalc::calcNodeAdvance( + const unsigned int uIndex, // 0 < uIndex < uLast + const PathState::pointer& pspCur, + const bool bMultiQuality, + const bool bReverse) +{ + PaymentNode& pnPrv = pspCur->vpnNodes[uIndex-1]; + PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + + 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; + + do + { + bool bDirectDirDirty = false; + + if (!uDirectEnd) + { + // Need to initialize current node. + + uDirectTip = Ledger::getBookBase(uPrvCurrencyID, uPrvIssuerID, uCurCurrencyID, uCurIssuerID); + uDirectEnd = Ledger::getQualityNext(uDirectTip); + sleDirectDir = lesActive.entryCache(ltDIR_NODE, uDirectTip); + bDirectAdvance = !sleDirectDir; + bDirectDirDirty = true; + + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: Initialize node: uDirectTip=%s uDirectEnd=%s bDirectAdvance=%d") % uDirectTip % uDirectEnd % bDirectAdvance); + } + + if (bDirectAdvance) + { + // Get next quality. + uDirectTip = lesActive.getLedger()->getNextLedgerIndex(uDirectTip, uDirectEnd); + bDirectDirDirty = true; + bDirectAdvance = false; + + if (!!uDirectTip) + { + // Have another quality directory. + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: Quality advance: uDirectTip=%s") % uDirectTip); + + sleDirectDir = lesActive.entryCache(ltDIR_NODE, uDirectTip); + } + else if (bReverse) + { + Log(lsINFO) << "calcNodeAdvance: No more offers."; + + uOfferIndex = 0; + break; + } + else + { + // No more offers. Should be done rather than fall off end of book. + Log(lsINFO) << "calcNodeAdvance: Unreachable: Fell off end of order book."; + assert(false); + + terResult = tefEXCEPTION; + } + } + + if (bDirectDirDirty) + { + saOfrRate = STAmount::setRate(Ledger::getQuality(uDirectTip)); // For correct ratio + uEntry = 0; + bEntryAdvance = true; + + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: directory dirty: saOfrRate=%s") % saOfrRate); + } + + if (!bEntryAdvance) + { + if (bFundsDirty) + { + saTakerPays = sleOffer->getIValueFieldAmount(sfTakerPays); + saTakerGets = sleOffer->getIValueFieldAmount(sfTakerGets); + + saOfferFunds = lesActive.accountFunds(uOfrOwnerID, saTakerGets); // Funds left. + bFundsDirty = false; + + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: directory dirty: saOfrRate=%s") % saOfrRate); + } + else + { + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: as is")); + nothing(); + } + } + else if (!lesActive.dirNext(uDirectTip, sleDirectDir, uEntry, uOfferIndex)) + { + // Failed to find an entry in directory. + + uOfferIndex = 0; + + // Do another cur directory iff bMultiQuality + if (bMultiQuality) + { + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: next quality")); + bDirectAdvance = true; + } + else if (!bReverse) + { + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: unreachable: ran out of offers")); + assert(false); // Can't run out of offers in forward direction. + terResult = tefEXCEPTION; + } + } + else + { + // Got a new offer. + sleOffer = lesActive.entryCache(ltOFFER, uOfferIndex); + uOfrOwnerID = sleOffer->getIValueFieldAccount(sfAccount).getAccountID(); + + const aciSource asLine = boost::make_tuple(uOfrOwnerID, uCurCurrencyID, uCurIssuerID); + + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: uOfrOwnerID=%s") % NewcoinAddress::createHumanAccountID(uOfrOwnerID)); + + if (sleOffer->getIFieldPresent(sfExpiration) && sleOffer->getIFieldU32(sfExpiration) <= lesActive.getLedger()->getParentCloseTimeNC()) + { + // Offer is expired. + Log(lsINFO) << "calcNodeAdvance: expired offer"; + + assert(musUnfundedFound.find(uOfferIndex) != musUnfundedFound.end()); // Verify reverse found it too. + bEntryAdvance = true; + 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. + curIssuerNodeConstIterator itForward = pspCur->umForward.find(asLine); + const bool bFoundForward = itForward != pspCur->umForward.end(); + + if (bFoundForward && itForward->second != uIndex) + { + // Temporarily unfunded. Another node uses this source, ignore in this offer. + Log(lsINFO) << "calcNodeAdvance: temporarily unfunded offer (forward)"; + + bEntryAdvance = true; + continue; + } + + curIssuerNodeConstIterator itPast = mumSource.find(asLine); + bool bFoundPast = itPast != mumSource.end(); + + if (bFoundPast && itPast->second != uIndex) + { + // Temporarily unfunded. Another node uses this source, ignore in this offer. + Log(lsINFO) << "calcNodeAdvance: temporarily unfunded offer (past)"; + + bEntryAdvance = true; + continue; + } + + curIssuerNodeConstIterator itReverse = pspCur->umReverse.find(asLine); + bool bFoundReverse = itReverse != pspCur->umReverse.end(); + + if (bFoundReverse && itReverse->second != uIndex) + { + // Temporarily unfunded. Another node uses this source, ignore in this offer. + Log(lsINFO) << "calcNodeAdvance: temporarily unfunded offer (reverse)"; + + bEntryAdvance = true; + continue; + } + + saTakerPays = sleOffer->getIValueFieldAmount(sfTakerPays); + saTakerGets = sleOffer->getIValueFieldAmount(sfTakerGets); + + saOfferFunds = lesActive.accountFunds(uOfrOwnerID, saTakerGets); // Funds left. + + if (!saOfferFunds.isPositive()) + { + // Offer is unfunded. + Log(lsINFO) << "calcNodeAdvance: unfunded offer"; + + if (bReverse && !bFoundReverse && !bFoundPast) + { + // Never mentioned before: found unfunded. + musUnfundedFound.insert(uOfferIndex); // Mark offer for always deletion. + } + + // YYY Could verify offer is correct place for unfundeds. + bEntryAdvance = true; + continue; + } + + if (bReverse // Need to remember reverse mention. + && !bFoundPast // Not mentioned in previous passes. + && !bFoundReverse) // Not mentioned for pass. + { + // Consider source mentioned by current path state. + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: remember=%s/%s/%s") + % NewcoinAddress::createHumanAccountID(uOfrOwnerID) + % STAmount::createHumanCurrency(uCurCurrencyID) + % NewcoinAddress::createHumanAccountID(uCurIssuerID)); + + pspCur->umReverse.insert(std::make_pair(asLine, uIndex)); + } + + bFundsDirty = false; + bEntryAdvance = false; + } + } + while (tesSUCCESS == terResult && (bEntryAdvance || bDirectAdvance)); + + if (tesSUCCESS == terResult) + { + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: uOfferIndex=%s") % uOfferIndex); + } + else + { + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: terResult=%s") % transToken(terResult)); + } + + return terResult; +} + +// 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 process till request is satisified while we the rate does not increase past the initial rate. +TER RippleCalc::calcNodeDeliverRev( + const unsigned int uIndex, // 0 < uIndex < uLast + const PathState::pointer& pspCur, + const bool bMultiQuality, + const uint160& uOutAccountID, // --> Output owner's account. + const STAmount& saOutReq, // --> Funds wanted. + STAmount& saOutAct) // <-- Funds delivered. +{ + TER terResult = tesSUCCESS; + + PaymentNode& pnPrv = pspCur->vpnNodes[uIndex-1]; + PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + + const uint160& uCurIssuerID = pnCur.uIssuerID; + const uint160& uPrvAccountID = pnPrv.uAccountID; + const STAmount& saTransferRate = pnCur.saTransferRate; + + STAmount& saPrvDlvReq = pnPrv.saRevDeliver; // To be adjusted. + + saOutAct = 0; + + while (saOutAct != saOutReq) // Did not deliver limit. + { + 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(uIndex, pspCur, 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 receiving or sending. + ? saOne // No fee. + : saTransferRate; // Transfer rate of issuer. + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: uOfrOwnerID=%s uOutAccountID=%s uCurIssuerID=%s saTransferRate=%s saOutFeeRate=%s") + % NewcoinAddress::createHumanAccountID(uOfrOwnerID) + % NewcoinAddress::createHumanAccountID(uOutAccountID) + % NewcoinAddress::createHumanAccountID(uCurIssuerID) + % saTransferRate.getFullText() + % saOutFeeRate.getFullText()); + + if (!saRateMax) + { + // Set initial rate. + saRateMax = saOutFeeRate; + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: Set initial rate: saRateMax=%s saOutFeeRate=%s") + % saRateMax + % saOutFeeRate); + } + else if (saRateMax < saOutFeeRate) + { + // Offer exceeds initial rate. + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: Offer exceeds initial rate: saRateMax=%s saOutFeeRate=%s") + % saRateMax + % saOutFeeRate); + + nothing(); + break; + } + else if (saOutFeeRate < saRateMax) + { + // Reducing rate. + + saRateMax = saOutFeeRate; + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: Reducing rate: saRateMax=%s") + % saRateMax); + } + + STAmount saOutPass = std::min(std::min(saOfferFunds, saTakerGets), saOutReq-saOutAct); // Offer maximum out - assuming no out fees. + STAmount saOutPlusFees = STAmount::multiply(saOutPass, saOutFeeRate); // Offer out with fees. + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: saOutReq=%s saOutAct=%s saTakerGets=%s saOutPass=%s saOutPlusFees=%s saOfferFunds=%s") + % saOutReq + % saOutAct + % saTakerGets + % saOutPass + % saOutPlusFees + % saOfferFunds); + + if (saOutPlusFees > saOfferFunds) + { + // Offer owner can not cover all fees, compute saOutPass based on saOfferFunds. + + saOutPlusFees = saOfferFunds; + saOutPass = STAmount::divide(saOutPlusFees, saOutFeeRate); + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: Total exceeds fees: saOutPass=%s saOutPlusFees=%s saOfferFunds=%s") + % saOutPass + % saOutPlusFees + % saOfferFunds); + } + + // Compute portion of input needed to cover output. + + STAmount saInPassReq = STAmount::multiply(saOutPass, saOfrRate, saTakerPays); + STAmount saInPassAct; + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: saInPassReq=%s saOfrRate=%s saOutPass=%s saOutPlusFees=%s") + % saInPassReq + % saOfrRate + % saOutPass + % saOutPlusFees); + + // Find out input amount actually available at current rate. + if (!!uPrvAccountID) + { + // account --> OFFER --> ? + // 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; + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: account --> OFFER --> ? : saInPassAct=%s") + % saPrvDlvReq); + } + else + { + // offer --> OFFER --> ? + + terResult = calcNodeDeliverRev( + uIndex-1, + pspCur, + bMultiQuality, + uOfrOwnerID, + saInPassReq, + saInPassAct); + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: offer --> OFFER --> ? : saInPassAct=%s") + % saInPassAct); + } + + if (tesSUCCESS != terResult) + break; + + if (saInPassAct != saInPassReq) + { + // Adjust output to conform to limited input. + saOutPass = STAmount::divide(saInPassAct, saOfrRate, saTakerGets); + saOutPlusFees = STAmount::multiply(saOutPass, saOutFeeRate); + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: adjusted: saOutPass=%s saOutPlusFees=%s") + % saOutPass + % saOutPlusFees); + } + + // Funds were spent. + bFundsDirty = true; + + // Deduct output, don't actually need to send. + lesActive.accountSend(uOfrOwnerID, uCurIssuerID, saOutPass); + + // Adjust offer + sleOffer->setIFieldAmount(sfTakerGets, saTakerGets - saOutPass); + sleOffer->setIFieldAmount(sfTakerPays, saTakerPays - saInPassAct); + + lesActive.entryModify(sleOffer); + + if (saOutPass == saTakerGets) + { + // Offer became unfunded. + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: offer became unfunded.")); + + bEntryAdvance = true; + } + + saOutAct += saOutPass; + saPrvDlvReq += saInPassAct; + } + + if (!saOutAct) + terResult = tepPATH_DRY; + + return terResult; +} + +// Deliver maximum amount of funds from previous node. +// Goal: Make progress consuming the offer. +TER RippleCalc::calcNodeDeliverFwd( + const unsigned int uIndex, // 0 < uIndex < uLast + const PathState::pointer& pspCur, + const bool bMultiQuality, + const uint160& uInAccountID, // --> Input owner's account. + const STAmount& saInFunds, // --> Funds available for delivery and fees. + const STAmount& saInReq, // --> Limit to deliver. + STAmount& saInAct, // <-- Amount delivered. + STAmount& saInFees) // <-- Fees charged. +{ + TER terResult = tesSUCCESS; + + PaymentNode& pnPrv = pspCur->vpnNodes[uIndex-1]; + PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + PaymentNode& pnNxt = pspCur->vpnNodes[uIndex+1]; + + const uint160& uNxtAccountID = pnNxt.uAccountID; + const uint160& uCurIssuerID = pnCur.uIssuerID; + const uint160& uPrvIssuerID = pnPrv.uIssuerID; + const STAmount& saTransferRate = pnPrv.saTransferRate; + + saInAct = 0; + saInFees = 0; + + while (tesSUCCESS == terResult + && saInAct != saInReq // Did not deliver limit. + && saInAct + saInFees != saInFunds) // Did not deliver all funds. + { + terResult = calcNodeAdvance(uIndex, pspCur, bMultiQuality, false); // If needed, advance to next funded offer. + + if (tesSUCCESS == terResult) + { + 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 = uInAccountID == uPrvIssuerID || uOfrOwnerID == uPrvIssuerID // Issuer receiving or sending. + ? saOne // No fee. + : saTransferRate; // Transfer rate of issuer. + + // + // First calculate assuming no output fees. + // XXX Make sure derived in does not exceed actual saTakerPays due to rounding. + + STAmount saOutFunded = std::max(saOfferFunds, saTakerGets); // Offer maximum out - There are no out fees. + STAmount saInFunded = STAmount::multiply(saOutFunded, saOfrRate, saInReq); // Offer maximum in - Limited by by payout. + STAmount saInTotal = STAmount::multiply(saInFunded, saTransferRate); // Offer maximum in with fees. + STAmount saInSum = std::min(saInTotal, saInFunds-saInAct-saInFees); // In limited by saInFunds. + STAmount saInPassAct = STAmount::divide(saInSum, saInFeeRate); // In without fees. + STAmount saOutPassMax = STAmount::divide(saInPassAct, saOfrRate, saOutFunded); // Out. + + STAmount saInPassFees; + STAmount saOutPassAct; + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: saOutFunded=%s saInFunded=%s saInTotal=%s saInSum=%s saInPassAct=%s saOutPassMax=%s") + % saOutFunded + % saInFunded + % saInTotal + % saInSum + % saInPassAct + % saOutPassMax); + + if (!!uNxtAccountID) + { + // ? --> OFFER --> account + // Input fees: vary based upon the consumed offer's owner. + // Output fees: none as the destination account is the issuer. + + // XXX This doesn't claim input. + // XXX Assumes input is in limbo. XXX Check. + + // Debit offer owner. + lesActive.accountSend(uOfrOwnerID, uCurIssuerID, saOutPassMax); + + saOutPassAct = saOutPassMax; + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: ? --> OFFER --> account: saOutPassAct=%s") + % saOutPassAct); + } + else + { + // ? --> OFFER --> offer + STAmount saOutPassFees; + + terResult = RippleCalc::calcNodeDeliverFwd( + uIndex+1, + pspCur, + bMultiQuality, + uOfrOwnerID, + saOutPassMax, + saOutPassMax, + saOutPassAct, // <-- Amount delivered. + saOutPassFees); // <-- Fees charged. + + if (tesSUCCESS != terResult) + break; + + // Offer maximum in limited by next payout. + saInPassAct = STAmount::multiply(saOutPassAct, saOfrRate); + saInPassFees = STAmount::multiply(saInFunded, saInFeeRate)-saInPassAct; + } + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: saTakerGets=%s saTakerPays=%s saInPassAct=%s saOutPassAct=%s") + % saTakerGets.getFullText() + % saTakerPays.getFullText() + % saInPassAct.getFullText() + % saOutPassAct.getFullText()); + + // Funds were spent. + bFundsDirty = true; + + // Credit issuer transfer fees. + lesActive.accountSend(uInAccountID, uOfrOwnerID, saInPassFees); + + // Credit offer owner from offer. + lesActive.accountSend(uInAccountID, uOfrOwnerID, saInPassAct); + + // Adjust offer + sleOffer->setIFieldAmount(sfTakerGets, saTakerGets - saOutPassAct); + sleOffer->setIFieldAmount(sfTakerPays, saTakerPays - saInPassAct); + + lesActive.entryModify(sleOffer); + + if (saOutPassAct == saTakerGets) + { + // Offer became unfunded. + pspCur->vUnfundedBecame.push_back(uOfferIndex); + bEntryAdvance = true; + } + + saInAct += saInPassAct; + saInFees += saInPassFees; + } + } + + return terResult; +} + +// Called to drive from the last offer node in a chain. +TER RippleCalc::calcNodeOfferRev( + const unsigned int uIndex, // 0 < uIndex < uLast + const PathState::pointer& pspCur, + const bool bMultiQuality) +{ + TER terResult; + + PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + PaymentNode& pnNxt = pspCur->vpnNodes[uIndex+1]; + + if (!!pnNxt.uAccountID) + { + // Next is an account node, resolve current offer node's deliver. + STAmount saDeliverAct; + + terResult = calcNodeDeliverRev( + uIndex, + pspCur, + bMultiQuality, + + pnNxt.uAccountID, + pnCur.saRevDeliver, + saDeliverAct); + } + else + { + // 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 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 uIndex, // 0 < uIndex < uLast + const PathState::pointer& pspCur, + const bool bMultiQuality + ) +{ + TER terResult; + PaymentNode& pnPrv = pspCur->vpnNodes[uIndex-1]; + + if (!!pnPrv.uAccountID) + { + // Previous is an account node, resolve its deliver. + STAmount saInAct; + STAmount saInFees; + + terResult = calcNodeDeliverFwd( + uIndex, + pspCur, + bMultiQuality, + pnPrv.uAccountID, + pnPrv.saFwdDeliver, + pnPrv.saFwdDeliver, + saInAct, + saInFees); + + assert(tesSUCCESS != terResult || pnPrv.saFwdDeliver == saInAct+saInFees); + } + else + { + // Previous is an offer. Deliver has already been resolved. + terResult = tesSUCCESS; + } + + return terResult; + +} + +// Cur is the driver and will be filled exactly. +// 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 as it calculates previous wants based on previous credit limits and current wants. +// This routine works forwards as it calculates current deliver based on previous delivery limits and current wants. +// XXX Deal with uQualityIn or uQualityOut = 0 +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 + STAmount& saCurAct, // <-> out limit achieved. + uint64& uRateMax) +{ + Log(lsINFO) << boost::str(boost::format("calcNodeRipple> uQualityIn=%d uQualityOut=%d saPrvReq=%s saCurReq=%s saPrvAct=%s saCurAct=%s") + % uQualityIn + % uQualityOut + % saPrvReq.getFullText() + % saCurReq.getFullText() + % saPrvAct.getFullText() + % saCurAct.getFullText()); + + assert(saPrvReq.getCurrency() == saCurReq.getCurrency()); + + const bool bPrvUnlimited = saPrvReq.isNegative(); + const STAmount saPrv = bPrvUnlimited ? STAmount(saPrvReq) : saPrvReq-saPrvAct; + const STAmount saCur = saCurReq-saCurAct; + +#if 0 + Log(lsINFO) << boost::str(boost::format("calcNodeRipple: bPrvUnlimited=%d saPrv=%s saCur=%s") + % bPrvUnlimited + % saPrv.getFullText() + % saCur.getFullText()); +#endif + + if (uQualityIn >= uQualityOut) + { + // No fee. + Log(lsINFO) << boost::str(boost::format("calcNodeRipple: No fees")); + + if (!uRateMax || STAmount::uRateOne <= uRateMax) + { + STAmount saTransfer = bPrvUnlimited ? saCur : std::min(saPrv, saCur); + + saPrvAct += saTransfer; + saCurAct += saTransfer; + + if (!uRateMax) + uRateMax = STAmount::uRateOne; + } + } + else + { + // Fee. + Log(lsINFO) << boost::str(boost::format("calcNodeRipple: Fee")); + + uint64 uRate = STAmount::getRate(STAmount(uQualityIn), STAmount(uQualityOut)); + + if (!uRateMax || uRate <= uRateMax) + { + const uint160 uCurrencyID = saCur.getCurrency(); + const uint160 uCurIssuerID = saCur.getIssuer(); + const uint160 uPrvIssuerID = saPrv.getIssuer(); + + STAmount saCurIn = STAmount::divide(STAmount::multiply(saCur, uQualityOut, uCurrencyID, uCurIssuerID), uQualityIn, uCurrencyID, uCurIssuerID); + + Log(lsINFO) << boost::str(boost::format("calcNodeRipple: bPrvUnlimited=%d saPrv=%s saCurIn=%s") % bPrvUnlimited % saPrv.getFullText() % saCurIn.getFullText()); + if (bPrvUnlimited || saCurIn <= saPrv) + { + // All of cur. Some amount of prv. + saCurAct += saCur; + saPrvAct += saCurIn; + Log(lsINFO) << boost::str(boost::format("calcNodeRipple:3c: saCurReq=%s saPrvAct=%s") % saCurReq.getFullText() % saPrvAct.getFullText()); + } + else + { + // A part of cur. All of prv. (cur as driver) + STAmount saCurOut = STAmount::divide(STAmount::multiply(saPrv, uQualityIn, uCurrencyID, uCurIssuerID), uQualityOut, uCurrencyID, uCurIssuerID); + Log(lsINFO) << boost::str(boost::format("calcNodeRipple:4: saCurReq=%s") % saCurReq.getFullText()); + + saCurAct += saCurOut; + saPrvAct = saPrvReq; + + if (!uRateMax) + uRateMax = uRate; + } + } + } + + Log(lsINFO) << boost::str(boost::format("calcNodeRipple< uQualityIn=%d uQualityOut=%d saPrvReq=%s saCurReq=%s saPrvAct=%s saCurAct=%s") + % uQualityIn + % uQualityOut + % saPrvReq.getFullText() + % saCurReq.getFullText() + % saPrvAct.getFullText() + % saCurAct.getFullText()); +} + +// Calculate saPrvRedeemReq, saPrvIssueReq, saPrvDeliver from saCur... +// <-- tesSUCCESS or tepPATH_DRY +TER RippleCalc::calcNodeAccountRev(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality) +{ + TER terResult = tesSUCCESS; + const unsigned int uLast = pspCur->vpnNodes.size() - 1; + + uint64 uRateMax = 0; + + PaymentNode& pnPrv = pspCur->vpnNodes[uIndex ? uIndex-1 : 0]; + PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + PaymentNode& pnNxt = pspCur->vpnNodes[uIndex == uLast ? uLast : uIndex+1]; + + // Current is allowed to redeem to next. + const bool bPrvAccount = !uIndex || isSetBit(pnPrv.uFlags, STPathElement::typeAccount); + const bool bNxtAccount = uIndex == 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; + + const uint32 uQualityIn = uIndex ? lesActive.rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID) : QUALITY_ONE; + const uint32 uQualityOut = uIndex != uLast ? lesActive.rippleQualityOut(uCurAccountID, uNxtAccountID, uCurrencyID) : QUALITY_ONE; + + // For bPrvAccount + const STAmount saPrvOwed = bPrvAccount && uIndex // Previous account is owed. + ? lesActive.rippleOwed(uCurAccountID, uPrvAccountID, uCurrencyID) + : STAmount(uCurrencyID, uCurAccountID); + + const STAmount saPrvLimit = bPrvAccount && uIndex // Previous account may owe. + ? lesActive.rippleLimit(uCurAccountID, uPrvAccountID, uCurrencyID) + : STAmount(uCurrencyID, uCurAccountID); + + const STAmount saNxtOwed = bNxtAccount && uIndex != uLast // Next account is owed. + ? lesActive.rippleOwed(uCurAccountID, uNxtAccountID, uCurrencyID) + : STAmount(uCurrencyID, uCurAccountID); + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev> uIndex=%d/%d uPrvAccountID=%s uCurAccountID=%s uNxtAccountID=%s uCurrencyID=%s uQualityIn=%d uQualityOut=%d saPrvOwed=%s saPrvLimit=%s") + % uIndex + % uLast + % NewcoinAddress::createHumanAccountID(uPrvAccountID) + % NewcoinAddress::createHumanAccountID(uCurAccountID) + % NewcoinAddress::createHumanAccountID(uNxtAccountID) + % STAmount::createHumanCurrency(uCurrencyID) + % uQualityIn + % uQualityOut + % saPrvOwed.getFullText() + % saPrvLimit.getFullText()); + + // Previous can redeem the owed IOUs it holds. + const STAmount saPrvRedeemReq = saPrvOwed.isPositive() ? saPrvOwed : STAmount(uCurrencyID, 0); + 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::saFromSigned(uCurrencyID, uCurAccountID, -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()); + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: saPrvRedeemReq=%s saPrvIssueReq=%s saCurRedeemReq=%s saNxtOwed=%s") + % saPrvRedeemReq.getFullText() + % saPrvIssueReq.getFullText() + % saCurRedeemReq.getFullText() + % saNxtOwed.getFullText()); + + Log(lsINFO) << pspCur->getJson(); + + assert(!saCurRedeemReq || (-saNxtOwed) >= saCurRedeemReq); // Current redeem req can't be more than IOUs on hand. + assert(!saCurIssueReq || !saNxtOwed.isPositive() || saNxtOwed == saCurRedeemReq); // If issue req, then redeem req must consume all owed. + + if (bPrvAccount && bNxtAccount) + { + if (!uIndex) + { + // ^ --> ACCOUNT --> account|offer + // Nothing to do, there is no previous to adjust. + nothing(); + } + else if (uIndex == uLast) + { + // account --> ACCOUNT --> $ + // Overall deliverable. + const STAmount& saCurWantedReq = bPrvAccount + ? std::min(pspCur->saOutReq, saPrvLimit+saPrvOwed) // If previous is an account, limit. + : pspCur->saOutReq; // Previous is an offer, no limit: redeem own IOUs. + STAmount saCurWantedAct(saCurWantedReq.getCurrency(), saCurWantedReq.getIssuer()); + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: account --> ACCOUNT --> $ : saCurWantedReq=%s") + % saCurWantedReq.getFullText()); + + // Calculate redeem + if (saPrvRedeemReq) // Previous has IOUs to redeem. + { + // Redeem at 1:1 + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Redeem at 1:1")); + + saCurWantedAct = std::min(saPrvRedeemReq, saCurWantedReq); + saPrvRedeemAct = saCurWantedAct; + + uRateMax = STAmount::uRateOne; + } + + // Calculate issuing. + if (saCurWantedReq != saCurWantedAct // Need more. + && saPrvIssueReq) // Will accept IOUs from prevous. + { + // Rate: quality in : 1.0 + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: 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); + } + + if (!saCurWantedAct) + { + // Must have processed something. + terResult = tepPATH_DRY; + } + } + else + { + // ^|account --> ACCOUNT --> account + + // redeem (part 1) -> redeem + if (saCurRedeemReq // Next wants IOUs redeemed. + && saPrvRedeemReq) // Previous has IOUs to redeem. + { + // Rate : 1.0 : quality out + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Rate : 1.0 : quality out")); + + calcNodeRipple(QUALITY_ONE, uQualityOut, saPrvRedeemReq, saCurRedeemReq, saPrvRedeemAct, saCurRedeemAct, uRateMax); + } + + // 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 + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Rate: quality in : quality out")); + + calcNodeRipple(uQualityIn, uQualityOut, saPrvIssueReq, saCurRedeemReq, saPrvIssueAct, saCurRedeemAct, uRateMax); + } + + // 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 + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Rate : 1.0 : transfer_rate")); + + calcNodeRipple(QUALITY_ONE, lesActive.rippleTransferRate(uCurAccountID), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct, uRateMax); + } + + // 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. + { + // Rate: quality in : 1.0 + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Rate: quality in : 1.0")); + + calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurIssueReq, saPrvIssueAct, saCurIssueAct, uRateMax); + } + + if (!saCurRedeemAct && !saCurIssueAct) + { + // Must want something. + terResult = tepPATH_DRY; + } + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: ^|account --> ACCOUNT --> account : saCurRedeemReq=%s saCurIssueReq=%s saPrvOwed=%s saCurRedeemAct=%s saCurIssueAct=%s") + % saCurRedeemReq.getFullText() + % saCurIssueReq.getFullText() + % saPrvOwed.getFullText() + % saCurRedeemAct.getFullText() + % saCurIssueAct.getFullText()); + } + } + else if (bPrvAccount && !bNxtAccount) + { + // account --> ACCOUNT --> offer + // Note: deliver is always issue as ACCOUNT is the issuer for the offer input. + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: account --> ACCOUNT --> offer")); + + // 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 = tepPATH_DRY; + } + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: saCurDeliverReq=%s saCurDeliverAct=%s saPrvOwed=%s") + % saCurDeliverReq.getFullText() + % saCurDeliverAct.getFullText() + % saPrvOwed.getFullText()); + } + else if (!bPrvAccount && bNxtAccount) + { + if (uIndex == uLast) + { + // offer --> ACCOUNT --> $ + const STAmount& saCurWantedReq = bPrvAccount + ? std::min(pspCur->saOutReq, saPrvLimit+saPrvOwed) // If previous is an account, limit. + : pspCur->saOutReq; // Previous is an offer, no limit: redeem own IOUs. + STAmount saCurWantedAct(saCurWantedReq.getCurrency(), saCurWantedReq.getIssuer()); + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: offer --> ACCOUNT --> $ : saCurWantedReq=%s") + % saCurWantedReq.getFullText()); + + // Rate: quality in : 1.0 + calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvDeliverReq, saCurWantedReq, saPrvDeliverAct, saCurWantedAct, uRateMax); + + if (!saCurWantedAct) + { + // Must have processed something. + terResult = tepPATH_DRY; + } + } + else + { + // offer --> ACCOUNT --> account + // Note: offer is always delivering(redeeming) as account is issuer. + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: offer --> ACCOUNT --> account")); + + // 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); + } + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: saCurRedeemReq=%s saCurIssueAct=%s saCurIssueReq=%s saPrvDeliverAct=%s") + % saCurRedeemReq.getFullText() + % saCurRedeemAct.getFullText() + % saCurIssueReq.getFullText() + % saPrvDeliverAct.getFullText()); + + if (!saPrvDeliverAct) + { + // Must want something. + terResult = tepPATH_DRY; + } + } + } + else + { + // offer --> ACCOUNT --> offer + // deliver/redeem -> deliver/issue. + Log(lsINFO) << 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 = tepPATH_DRY; + } + } + + return terResult; +} + +// Perfrom 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 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. +TER RippleCalc::calcNodeAccountFwd( + const unsigned int uIndex, // 0 <= uIndex <= uLast + const PathState::pointer& pspCur, + const bool bMultiQuality) +{ + TER terResult = tesSUCCESS; + const unsigned int uLast = pspCur->vpnNodes.size() - 1; + + uint64 uRateMax = 0; + + PaymentNode& pnPrv = pspCur->vpnNodes[uIndex ? uIndex-1 : 0]; + PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + PaymentNode& pnNxt = pspCur->vpnNodes[uIndex == uLast ? uLast : uIndex+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& uCurrencyID = pnCur.uCurrencyID; + + uint32 uQualityIn = uIndex ? lesActive.rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID) : QUALITY_ONE; + uint32 uQualityOut = uIndex == uLast ? lesActive.rippleQualityOut(uCurAccountID, uNxtAccountID, uCurrencyID) : QUALITY_ONE; + + // 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.saRevDeliver; + 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; + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd> uIndex=%d/%d saPrvRedeemReq=%s saPrvIssueReq=%s saPrvDeliverReq=%s saCurRedeemReq=%s saCurIssueReq=%s saCurDeliverReq=%s") + % uIndex + % uLast + % saPrvRedeemReq.getFullText() + % saPrvIssueReq.getFullText() + % saPrvDeliverReq.getFullText() + % saCurRedeemReq.getFullText() + % saCurIssueReq.getFullText() + % saCurDeliverReq.getFullText()); + + // Ripple through account. + + if (bPrvAccount && bNxtAccount) + { + if (!uIndex) + { + // ^ --> ACCOUNT --> account + + // First node, calculate amount to send. + // XXX Use stamp/ripple balance + PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + + const STAmount& saCurRedeemReq = pnCur.saRevRedeem; + STAmount& saCurRedeemAct = pnCur.saFwdRedeem; + const STAmount& saCurIssueReq = pnCur.saRevIssue; + STAmount& saCurIssueAct = pnCur.saFwdIssue; + + const STAmount& saCurSendMaxReq = pspCur->saInReq; // Negative for no limit, doing a calculation. + STAmount& saCurSendMaxAct = pspCur->saInAct; // Report to user how much this sends. + + if (saCurRedeemReq) + { + // Redeem requested. + saCurRedeemAct = saCurRedeemReq.isNegative() + ? saCurRedeemReq + : std::min(saCurRedeemReq, saCurSendMaxReq); + } + else + { + saCurRedeemAct = STAmount(saCurRedeemReq); + } + saCurSendMaxAct = saCurRedeemAct; + + if (saCurIssueReq && (saCurSendMaxReq.isNegative() || saCurSendMaxReq != saCurRedeemAct)) + { + // Issue requested and not over budget. + saCurIssueAct = saCurSendMaxReq.isNegative() + ? saCurIssueReq + : std::min(saCurSendMaxReq-saCurRedeemAct, saCurIssueReq); + } + else + { + saCurIssueAct = STAmount(saCurIssueReq); + } + saCurSendMaxAct += saCurIssueAct; + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: ^ --> ACCOUNT --> account : saCurSendMaxReq=%s saCurRedeemAct=%s saCurIssueReq=%s saCurIssueAct=%s") + % saCurSendMaxReq.getFullText() + % saCurRedeemAct.getFullText() + % saCurIssueReq.getFullText() + % saCurIssueAct.getFullText()); + } + else if (uIndex == uLast) + { + // account --> ACCOUNT --> $ + Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: account --> ACCOUNT --> $ : uPrvAccountID=%s uCurAccountID=%s saPrvRedeemReq=%s saPrvIssueReq=%s") + % NewcoinAddress::createHumanAccountID(uPrvAccountID) + % NewcoinAddress::createHumanAccountID(uCurAccountID) + % saPrvRedeemReq.getFullText() + % saPrvIssueReq.getFullText()); + + // Last node. Accept all funds. Calculate amount actually to credit. + + STAmount& saCurReceive = pspCur->saOutAct; + + STAmount saIssueCrd = uQualityIn >= QUALITY_ONE + ? saPrvIssueReq // No fee. + : STAmount::multiply(saPrvIssueReq, uQualityIn, uCurrencyID, saPrvIssueReq.getIssuer()); // Fee. + + // Amount to credit. + saCurReceive = saPrvRedeemReq+saIssueCrd; + + // Actually receive. + lesActive.rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq+saPrvIssueReq, false); + } + else + { + // account --> ACCOUNT --> account + Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: account --> ACCOUNT --> account")); + + // Previous redeem part 1: redeem -> redeem + if (saPrvRedeemReq != saPrvRedeemAct) // Previous wants to redeem. To next must be ok. + { + // 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. + // wants to redeem and current would and can issue. + // If redeeming cur to next is done, this implies can issue. + if (saPrvRedeemReq != saPrvRedeemAct // Previous still wants to redeem. + && saCurRedeemReq == saCurRedeemAct // Current has no more to redeem to next. + && saCurIssueReq) + { + // 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. To next must be ok. + { + // Rate: quality in : 1.0 + calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurIssueReq, saPrvIssueAct, saCurIssueAct, uRateMax); + } + + // Adjust prv --> cur balance : take all inbound + // XXX Currency must be in amount. + lesActive.rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq + saPrvIssueReq, false); + } + } + else if (bPrvAccount && !bNxtAccount) + { + // account --> ACCOUNT --> offer + Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: account --> ACCOUNT --> offer")); + + // redeem -> issue. + // wants to redeem and current would and can issue. + // If redeeming cur to next is done, this implies can issue. + if (saPrvRedeemReq) // Previous wants to redeem. + { + // Rate : 1.0 : transfer_rate + calcNodeRipple(QUALITY_ONE, lesActive.rippleTransferRate(uCurAccountID), saPrvRedeemReq, saCurDeliverReq, saPrvRedeemAct, saCurDeliverAct, uRateMax); + } + + // issue -> issue + 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 + // XXX Currency must be in amount. + lesActive.rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq + saPrvIssueReq, false); + } + else if (!bPrvAccount && bNxtAccount) + { + if (uIndex == uLast) + { + // offer --> ACCOUNT --> $ + Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: offer --> ACCOUNT --> $")); + + STAmount& saCurReceive = pspCur->saOutAct; + + // Amount to credit. + saCurReceive = saPrvDeliverAct; + + // No income balance adjustments necessary. The paying side inside the offer paid to this account. + } + else + { + // offer --> ACCOUNT --> account + Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: offer --> ACCOUNT --> account")); + + // deliver -> redeem + if (saPrvDeliverReq) // Previous wants to deliver. + { + // 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. + } + } + else + { + // offer --> ACCOUNT --> offer + // deliver/redeem -> deliver/issue. + Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: offer --> ACCOUNT --> offer")); + + 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. + } + + return terResult; +} + +// Return true, iff lhs has less priority than rhs. +bool PathState::lessPriority(const PathState::pointer& lhs, const PathState::pointer& rhs) +{ + if (lhs->uQuality != rhs->uQuality) + return lhs->uQuality > rhs->uQuality; // Bigger is worse. + + // Best quanity is second rank. + if (lhs->saOutAct != rhs->saOutAct) + return lhs->saOutAct < rhs->saOutAct; // Smaller is worse. + + // Path index is third rank. + return lhs->mIndex > rhs->mIndex; // Bigger is worse. +} + +// Make sure the path delivers to uAccountID: uCurrencyID from uIssuerID. +// +// Rules: +// - Currencies must be converted via an offer. +// - A node names it's output. +// - A ripple nodes output issuer must be the node's account or the next node's account. +// - Offers can only go directly to another offer if the currency and issuer are an exact match. +TER PathState::pushImply( + const uint160& uAccountID, // --> Delivering to this account. + const uint160& uCurrencyID, // --> Delivering this currency. + const uint160& uIssuerID) // --> Delivering this issuer. +{ + const PaymentNode& pnPrv = vpnNodes.back(); + TER terResult = tesSUCCESS; + + Log(lsINFO) << "pushImply> " + << NewcoinAddress::createHumanAccountID(uAccountID) + << " " << STAmount::createHumanCurrency(uCurrencyID) + << " " << NewcoinAddress::createHumanAccountID(uIssuerID); + + if (pnPrv.uCurrencyID != uCurrencyID) + { + // Currency is different, need to convert via an offer. + + terResult = pushNode( + STPathElement::typeCurrency // Offer. + | STPathElement::typeIssuer, + ACCOUNT_ONE, // Placeholder for offers. + uCurrencyID, // The offer's output is what is now wanted. + uIssuerID); + + } + + // For ripple, non-stamps, ensure the issuer is on at least one side of the transaction. + if (tesSUCCESS == terResult + && !!uCurrencyID // Not stamps. + && (pnPrv.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( + STPathElement::typeAccount, + uIssuerID, // Intermediate account is the needed issuer. + uCurrencyID, + uIssuerID); + } + + Log(lsINFO) << "pushImply< " << terResult; + + return terResult; +} + +// Append a node and insert before it any implied nodes. +// <-- terResult: tesSUCCESS, temBAD_PATH, terNO_LINE +TER PathState::pushNode( + const int iType, + const uint160& uAccountID, + const uint160& uCurrencyID, + const uint160& uIssuerID) +{ + Log(lsINFO) << "pushNode> " + << NewcoinAddress::createHumanAccountID(uAccountID) + << " " << STAmount::createHumanCurrency(uCurrencyID) + << "/" << NewcoinAddress::createHumanAccountID(uIssuerID); + PaymentNode pnCur; + const bool bFirst = vpnNodes.empty(); + const PaymentNode& pnPrv = bFirst ? PaymentNode() : vpnNodes.back(); + // true, iff node is a ripple account. false, iff node is an offer node. + const bool bAccount = isSetBit(iType, STPathElement::typeAccount); + // true, iff currency supplied. + // Currency is specified for the output of the current node. + const bool bCurrency = isSetBit(iType, STPathElement::typeCurrency); + // Issuer is specified for the output of the current node. + const bool bIssuer = isSetBit(iType, STPathElement::typeIssuer); + TER terResult = tesSUCCESS; + + pnCur.uFlags = iType; + + if (iType & ~STPathElement::typeValidBits) + { + Log(lsINFO) << "pushNode: bad bits."; + + terResult = temBAD_PATH; + } + else if (bAccount) + { + // Account link + + pnCur.uAccountID = uAccountID; + pnCur.uCurrencyID = bCurrency ? uCurrencyID : pnPrv.uCurrencyID; + pnCur.uIssuerID = bIssuer ? uIssuerID : uAccountID; + pnCur.saRevRedeem = STAmount(uCurrencyID, uAccountID); + pnCur.saRevIssue = STAmount(uCurrencyID, uAccountID); + + if (!bFirst) + { + // Add required intermediate nodes to deliver to current account. + terResult = pushImply( + pnCur.uAccountID, // Current account. + pnCur.uCurrencyID, // Wanted currency. + !!pnCur.uCurrencyID ? uAccountID : ACCOUNT_XNS); // Account as issuer. + } + + if (tesSUCCESS == terResult && !vpnNodes.empty()) + { + const PaymentNode& pnBck = vpnNodes.back(); + bool bBckAccount = isSetBit(pnBck.uFlags, STPathElement::typeAccount); + + if (bBckAccount) + { + SLE::pointer sleRippleState = mLedger->getSLE(Ledger::getRippleStateIndex(pnBck.uAccountID, pnCur.uAccountID, pnPrv.uCurrencyID)); + + if (!sleRippleState) + { + Log(lsINFO) << "pushNode: No credit line between " + << NewcoinAddress::createHumanAccountID(pnBck.uAccountID) + << " and " + << NewcoinAddress::createHumanAccountID(pnCur.uAccountID) + << " for " + << STAmount::createHumanCurrency(pnPrv.uCurrencyID) + << "." ; + + Log(lsINFO) << getJson(); + + terResult = terNO_LINE; + } + else + { + Log(lsINFO) << "pushNode: Credit line found between " + << NewcoinAddress::createHumanAccountID(pnBck.uAccountID) + << " and " + << NewcoinAddress::createHumanAccountID(pnCur.uAccountID) + << " for " + << STAmount::createHumanCurrency(pnPrv.uCurrencyID) + << "." ; + } + } + } + + if (tesSUCCESS == terResult) + vpnNodes.push_back(pnCur); + } + else + { + // Offer link + // Offers bridge a change in currency & issuer or just a change in issuer. + pnCur.uCurrencyID = bCurrency ? uCurrencyID : pnPrv.uCurrencyID; + pnCur.uIssuerID = bIssuer ? uIssuerID : pnCur.uAccountID; + pnCur.saRateMax = saZero; + + if (!!pnPrv.uAccountID) + { + // Previous is an account. + + // Insert intermediary issuer account if needed. + terResult = pushImply( + !!pnPrv.uCurrencyID + ? ACCOUNT_ONE // Rippling, but offer's don't have an account. + : ACCOUNT_XNS, + pnPrv.uCurrencyID, + pnPrv.uIssuerID); + } + + if (tesSUCCESS == terResult) + { + vpnNodes.push_back(pnCur); + } + } + Log(lsINFO) << "pushNode< " << terResult; + + return terResult; +} + +PathState::PathState( + const int iIndex, + const LedgerEntrySet& lesSource, + const STPath& spSourcePath, + const uint160& uReceiverID, + const uint160& uSenderID, + const STAmount& saSend, + const STAmount& saSendMax + ) + : mLedger(lesSource.getLedgerRef()), mIndex(iIndex), uQuality(0) +{ + const uint160 uInCurrencyID = saSendMax.getCurrency(); + const uint160 uOutCurrencyID = saSend.getCurrency(); + const uint160 uInIssuerID = !!uInCurrencyID ? saSendMax.getIssuer() : ACCOUNT_XNS; + const uint160 uOutIssuerID = !!uOutCurrencyID ? saSend.getIssuer() : ACCOUNT_XNS; + + lesEntries = lesSource.duplicate(); + + saInReq = saSendMax; + saOutReq = saSend; + + // Push sending node. + terStatus = pushNode( + STPathElement::typeAccount + | STPathElement::typeCurrency + | STPathElement::typeIssuer, + uSenderID, + uInCurrencyID, + uInIssuerID); + + BOOST_FOREACH(const STPathElement& speElement, spSourcePath) + { + if (tesSUCCESS == terStatus) + terStatus = pushNode(speElement.getNodeType(), speElement.getAccountID(), speElement.getCurrency(), speElement.getIssuerID()); + } + + if (tesSUCCESS == terStatus) + { + // Create receiver node. + + terStatus = pushImply(uReceiverID, uOutCurrencyID, uOutIssuerID); + if (tesSUCCESS == terStatus) + { + terStatus = pushNode( + STPathElement::typeAccount // Last node is always an account. + | STPathElement::typeCurrency + | STPathElement::typeIssuer, + uReceiverID, // Receive to output + uOutCurrencyID, // Desired currency + uOutIssuerID); + } + } + + if (tesSUCCESS == terStatus) + { + // Look for first mention of source in nodes and detect loops. + // Note: The output is not allowed to be a source. + + const unsigned int uNodes = vpnNodes.size(); + + for (unsigned int uIndex = 0; tesSUCCESS == terStatus && uIndex != uNodes; ++uIndex) + { + const PaymentNode& pnCur = vpnNodes[uIndex]; + + if (!!pnCur.uAccountID) + { + // Source is a ripple line + nothing(); + } + else if (!umForward.insert(std::make_pair(boost::make_tuple(pnCur.uAccountID, pnCur.uCurrencyID, pnCur.uIssuerID), uIndex)).second) + { + // Failed to insert. Have a loop. + Log(lsINFO) << boost::str(boost::format("PathState: loop detected: %s") + % getJson()); + + terStatus = temBAD_PATH_LOOP; + } + } + } + + Log(lsINFO) << boost::str(boost::format("PathState: in=%s/%s out=%s/%s %s") + % STAmount::createHumanCurrency(uInCurrencyID) + % NewcoinAddress::createHumanAccountID(uInIssuerID) + % STAmount::createHumanCurrency(uOutCurrencyID) + % NewcoinAddress::createHumanAccountID(uOutIssuerID) + % getJson()); +} + +Json::Value PathState::getJson() const +{ + Json::Value jvPathState(Json::objectValue); + Json::Value jvNodes(Json::arrayValue); + + BOOST_FOREACH(const PaymentNode& pnNode, vpnNodes) + { + Json::Value jvNode(Json::objectValue); + + Json::Value jvFlags(Json::arrayValue); + + if (pnNode.uFlags & STPathElement::typeAccount) + jvFlags.append("account"); + + jvNode["flags"] = jvFlags; + + if (pnNode.uFlags & STPathElement::typeAccount) + jvNode["account"] = NewcoinAddress::createHumanAccountID(pnNode.uAccountID); + + if (!!pnNode.uCurrencyID) + jvNode["currency"] = STAmount::createHumanCurrency(pnNode.uCurrencyID); + + if (!!pnNode.uIssuerID) + jvNode["issuer"] = NewcoinAddress::createHumanAccountID(pnNode.uIssuerID); + + // if (pnNode.saRevRedeem) + jvNode["rev_redeem"] = pnNode.saRevRedeem.getFullText(); + + // if (pnNode.saRevIssue) + jvNode["rev_issue"] = pnNode.saRevIssue.getFullText(); + + // if (pnNode.saRevDeliver) + jvNode["rev_deliver"] = pnNode.saRevDeliver.getFullText(); + + // if (pnNode.saFwdRedeem) + jvNode["fwd_redeem"] = pnNode.saFwdRedeem.getFullText(); + + // if (pnNode.saFwdIssue) + jvNode["fwd_issue"] = pnNode.saFwdIssue.getFullText(); + + // if (pnNode.saFwdDeliver) + jvNode["fwd_deliver"] = pnNode.saFwdDeliver.getFullText(); + + jvNodes.append(jvNode); + } + + jvPathState["status"] = terStatus; + jvPathState["index"] = mIndex; + jvPathState["nodes"] = jvNodes; + + if (saInReq) + jvPathState["in_req"] = saInReq.getJson(0); + + if (saInAct) + jvPathState["in_act"] = saInAct.getJson(0); + + if (saOutReq) + jvPathState["out_req"] = saOutReq.getJson(0); + + if (saOutAct) + jvPathState["out_act"] = saOutAct.getJson(0); + + if (uQuality) + jvPathState["uQuality"] = Json::Value::UInt(uQuality); + + return jvPathState; +} + +TER RippleCalc::calcNodeFwd(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality) +{ + const PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + const bool bCurAccount = isSetBit(pnCur.uFlags, STPathElement::typeAccount); + + Log(lsINFO) << boost::str(boost::format("calcNodeFwd> uIndex=%d") % uIndex); + + TER terResult = bCurAccount + ? calcNodeAccountFwd(uIndex, pspCur, bMultiQuality) + : calcNodeOfferFwd(uIndex, pspCur, bMultiQuality); + + if (tesSUCCESS == terResult && uIndex + 1 != pspCur->vpnNodes.size()) + { + terResult = calcNodeFwd(uIndex+1, pspCur, bMultiQuality); + } + + Log(lsINFO) << boost::str(boost::format("calcNodeFwd< uIndex=%d terResult=%d") % uIndex % 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 tepPATH_DRY +// <-> pnNodes: +// --> [end]saWanted.mAmount +// --> [all]saWanted.mCurrency +// --> [all]saAccount +// <-> [0]saWanted.mAmount : --> limit, <-- actual +TER RippleCalc::calcNodeRev(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality) +{ + PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + const bool 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)); + + Log(lsINFO) << boost::str(boost::format("calcNodeRev> uIndex=%d uIssuerID=%s saTransferRate=%s") + % uIndex + % NewcoinAddress::createHumanAccountID(uCurIssuerID) + % saTransferRate.getFullText()); + + terResult = bCurAccount + ? calcNodeAccountRev(uIndex, pspCur, bMultiQuality) + : calcNodeOfferRev(uIndex, pspCur, bMultiQuality); + + // Do previous. + if (tesSUCCESS != terResult) + { + // Error, don't continue. + nothing(); + } + else if (uIndex) + { + // Continue in reverse. + + terResult = calcNodeRev(uIndex-1, pspCur, bMultiQuality); + } + + Log(lsINFO) << boost::str(boost::format("calcNodeRev< uIndex=%d terResult=%s/%d") % uIndex % 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. +// <-- pspCur->uQuality +void RippleCalc::pathNext(const PathState::pointer& pspCur, const int iPaths, const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent) +{ + // The next state is what is available in preference order. + // This is calculated when referenced accounts changed. + const bool bMultiQuality = iPaths == 1; + const unsigned int uLast = pspCur->vpnNodes.size() - 1; + + Log(lsINFO) << "Path In: " << pspCur->getJson(); + + assert(pspCur->vpnNodes.size() >= 2); + + pspCur->vUnfundedBecame.clear(); + pspCur->umReverse.clear(); + + lesCurrent = lesCheckpoint; // Restore from checkpoint. + lesCurrent.bumpSeq(); // Begin ledger varance. + + pspCur->terStatus = calcNodeRev(uLast, pspCur, bMultiQuality); + + Log(lsINFO) << "Path after reverse: " << pspCur->getJson(); + + if (tesSUCCESS == pspCur->terStatus) + { + // Do forward. + lesCurrent = lesCheckpoint; // Restore from checkpoint. + lesCurrent.bumpSeq(); // Begin ledger varance. + + pspCur->terStatus = calcNodeFwd(0, pspCur, bMultiQuality); + + pspCur->uQuality = tesSUCCESS == pspCur->terStatus + ? STAmount::getRate(pspCur->saOutAct, pspCur->saInAct) // Calculate relative quality. + : 0; // Mark path as inactive. + + Log(lsINFO) << "Path after forward: " << pspCur->getJson(); + } +} + +// XXX Stand alone calculation not implemented, does not calculate required input. +TER RippleCalc::rippleCalc( + LedgerEntrySet& lesActive, // <-> --> = Fee applied to src balance. + STAmount& saMaxAmountAct, // <-- The computed input amount. + STAmount& saDstAmountAct, // <-- The computed output amount. + const STAmount& saMaxAmountReq, // --> -1 = no limit. + const STAmount& saDstAmountReq, + const uint160& uDstAccountID, + const uint160& uSrcAccountID, + const STPathSet& spsPaths, + const bool bPartialPayment, + const bool bLimitQuality, + const bool bNoRippleDirect + ) +{ + RippleCalc rc(lesActive); + + TER terResult = temUNCERTAIN; + + // YYY Might do basic checks on src and dst validity as per doPayment. + + if (bNoRippleDirect && spsPaths.isEmpty()) + { + Log(lsINFO) << "doPayment: Invalid transaction: No paths and direct ripple not allowed."; + + return temRIPPLE_EMPTY; + } + + // Incrementally search paths. + std::vector vpsPaths; + + if (!bNoRippleDirect) + { + // Direct path. + // XXX Might also make a stamp bridge by default. + Log(lsINFO) << "doPayment: Build direct:"; + + PathState::pointer pspDirect = PathState::createPathState( + vpsPaths.size(), + lesActive, + STPath(), + uDstAccountID, + uSrcAccountID, + saDstAmountReq, + saMaxAmountReq); + + if (pspDirect) + { + // Return if malformed. + if (pspDirect->terStatus >= temMALFORMED && pspDirect->terStatus < tefFAILURE) + return pspDirect->terStatus; + + if (tesSUCCESS == pspDirect->terStatus) + { + // Had a success. + terResult = tesSUCCESS; + + vpsPaths.push_back(pspDirect); + } + } + } + + Log(lsINFO) << "doPayment: Paths in set: " << spsPaths.getPathCount(); + + BOOST_FOREACH(const STPath& spPath, spsPaths) + { + Log(lsINFO) << "doPayment: Build path:"; + + PathState::pointer pspExpanded = PathState::createPathState( + vpsPaths.size(), + lesActive, + spPath, + uDstAccountID, + uSrcAccountID, + saDstAmountReq, + saMaxAmountReq); + + if (pspExpanded) + { + // Return if malformed. + if (pspExpanded->terStatus >= temMALFORMED && pspExpanded->terStatus < tefFAILURE) + return pspExpanded->terStatus; + + if (tesSUCCESS == pspExpanded->terStatus) + { + // Had a success. + terResult = tesSUCCESS; + } + + vpsPaths.push_back(pspExpanded); + } + } + + if (vpsPaths.empty()) + { + return tefEXCEPTION; + } + else if (tesSUCCESS != terResult) + { + // No path successes. + + return vpsPaths[0]->terStatus; + } + else + { + terResult = temUNCERTAIN; + } + + STAmount saPaid; + STAmount saWanted; + 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 vuUnfundedBecame; // Offers that became unfunded. + + while (temUNCERTAIN == terResult) + { + PathState::pointer pspBest; + const LedgerEntrySet lesCheckpoint = lesActive; + + // Find the best path. + BOOST_FOREACH(PathState::pointer& pspCur, vpsPaths) + { + rc.pathNext(pspCur, vpsPaths.size(), lesCheckpoint, lesActive); // Compute increment. + + if ((!bLimitQuality || pspCur->uQuality <= uQualityLimit) // Quality is not limted or increment has allowed quality. + || !pspBest // Best is not yet set. + || (pspCur->uQuality && PathState::lessPriority(pspBest, pspCur))) // Current is better than set. + { + lesActive.swapWith(pspCur->lesEntries); // For the path, save ledger state. + pspBest = pspCur; + } + } + + if (pspBest) + { + // Apply best path. + + // 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. + lesActive.swapWith(pspBest->lesEntries); + + // Figure out if done. + if (temUNCERTAIN == terResult && saPaid == saWanted) + { + terResult = tesSUCCESS; + } + else + { + // Prepare for next pass. + + // Merge best pass' umReverse. + rc.mumSource.insert(pspBest->umReverse.begin(), pspBest->umReverse.end()); + } + } + // Not done and ran out of paths. + else if (!bPartialPayment) + { + // Partial payment not allowed. + terResult = tepPATH_PARTIAL; + lesActive = lesBase; // Revert to just fees charged. + } + // Partial payment ok. + else if (!saPaid) + { + // No payment at all. + terResult = tepPATH_DRY; + lesActive = lesBase; // Revert to just fees charged. + } + else + { + terResult = tesSUCCESS; + } + } + + if (tesSUCCESS == terResult) + { + // Delete became unfunded offers. + BOOST_FOREACH(const uint256& uOfferIndex, vuUnfundedBecame) + { + if (tesSUCCESS == terResult) + terResult = lesActive.offerDelete(uOfferIndex); + } + } + + // Delete found unfunded offers. + BOOST_FOREACH(const uint256& uOfferIndex, rc.musUnfundedFound) + { + if (tesSUCCESS == terResult) + terResult = lesActive.offerDelete(uOfferIndex); + } + + return terResult; +} + +#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(PaymentNode& pnSrc, PaymentNode& pnDst, bool bAllowPartial) +{ + TER terResult; + + if (pnDst.saWanted.isNative()) + { + // Transfer stamps. + + 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( + const uint256& uBookRoot, // --> Which order book to look in. + const uint256& 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->getIValueFieldAccount(sfAccount).getAccountID(); + STAmount saOfferPays = sleOffer->getIValueFieldAmount(sfTakerGets); + STAmount saOfferGets = sleOffer->getIValueFieldAmount(sfTakerPays); + + if (sleOffer->getIFieldPresent(sfExpiration) && sleOffer->getIFieldU32(sfExpiration) <= mLedger->getParentCloseTimeNC()) + { + // Offer is expired. + Log(lsINFO) << "calcOfferFirst: encountered expired offer"; + } + 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 stamps. + + 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. + Log(lsINFO) << "calcOfferFirst: offer unfunded: delete"; + } + else if (saOfferFunds >= saOfferPays) + { + // Offer fully funded. + + // Account transfering 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 stamps, then also calculates vs stamp 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 reedem. +// 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 offer's limbo + const uint160& uPrvCurrencyID, + const uint160& uPrvIssuerID, + const uint160& uCurCurrencyID, + const uint160& uCurIssuerID, + + const STAmount& uPrvRedeemReq, // --> In limit. + STAmount& uPrvRedeemAct, // <-> In limit achived. + const STAmount& uCurRedeemReq, // --> Out limit. Driver when uCurIssuerID == uNxtIssuerID (offer would redeem to next) + STAmount& uCurRedeemAct, // <-> Out limit achived. + + const STAmount& uCurIssueReq, // --> In limit. + STAmount& uCurIssueAct, // <-> In limit achived. + const STAmount& uCurIssueReq, // --> Out limit. Driver when uCurIssueReq != uNxtIssuerID (offer would effectively issue or transfer to next) + STAmount& uCurIssueAct, // <-> Out limit achived. + + STAmount& saPay, + STAmount& saGot + ) const +{ + TER terResult = temUNKNOWN; + + // Direct: not bridging via XNS + 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 XNS + 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 XNS. + uInTip = Ledger::getBookBase(uPrvCurrencyID, uPrvIssuerID, CURRENCY_XNS, ACCOUNT_XNS); + uInEnd = Ledger::getQualityNext(uInTip); + uOutTip = Ledger::getBookBase(CURRENCY_XNS, ACCOUNT_XNS, 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 transfering, 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 offer's 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 diff --git a/src/RippleCalc.h b/src/RippleCalc.h new file mode 100644 index 0000000000..673393b5d5 --- /dev/null +++ b/src/RippleCalc.h @@ -0,0 +1,190 @@ +#ifndef __RIPPLE_CALC__ +#define __RIPPLE_CALC__ + +#include + +#include "LedgerEntrySet.h" + +class PaymentNode { +protected: + friend class RippleCalc; + friend class PathState; + + uint16 uFlags; // --> From path. + + uint160 uAccountID; // --> Accounts: Recieving/sending account. + uint160 uCurrencyID; // --> Accounts: Receive and send, Offers: send. + // --- For offer's next has currency out. + uint160 uIssuerID; // --> Currency's issuer + + STAmount saTransferRate; // Transfer rate for uIssuerID. + + // Computed by Reverse. + STAmount saRevRedeem; // <-- Amount to redeem to next. + STAmount saRevIssue; // <-- Amount to issue to next limited by credit and outstanding IOUs. + // Issue isn't used by offers. + STAmount saRevDeliver; // <-- Amount to deliver to next regardless of fee. + + // Computed by forward. + STAmount saFwdRedeem; // <-- Amount node will redeem to next. + STAmount saFwdIssue; // <-- Amount node will issue to next. + // Issue isn't used by offers. + STAmount saFwdDeliver; // <-- Amount to deliver to next regardless of fee. + + // For offers: + + STAmount saRateMax; // XXX Should rate be sticky for forward too? + + // Directory + uint256 uDirectTip; // Current directory. + uint256 uDirectEnd; // Next order book. + bool bDirectAdvance; // Need to advance directory. + SLE::pointer sleDirectDir; + STAmount saOfrRate; // For correct ratio. + + // Node + bool bEntryAdvance; // Need to advance entry. + unsigned int uEntry; + uint256 uOfferIndex; + SLE::pointer sleOffer; + uint160 uOfrOwnerID; + bool bFundsDirty; // Need to refresh saOfferFunds, saTakerPays, & saTakerGets. + STAmount saOfferFunds; + STAmount saTakerPays; + STAmount saTakerGets; +}; + +// account id, currency id, issuer id :: node +typedef boost::tuple aciSource; +typedef boost::unordered_map curIssuerNode; // Map of currency, issuer to node index. +typedef boost::unordered_map::const_iterator curIssuerNodeConstIterator; + +extern std::size_t hash_value(const aciSource& asValue); + +// Holds a path state under incremental application. +class PathState +{ +protected: + const Ledger::pointer& mLedger; + + TER pushNode(const int iType, const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID); + TER pushImply(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID); + +public: + typedef boost::shared_ptr pointer; + + TER terStatus; + std::vector vpnNodes; + + // When processing, don't want to complicate directory walking with deletion. + std::vector vUnfundedBecame; // Offers that became unfunded or were completely consumed. + + // 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. + + // 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. + + LedgerEntrySet lesEntries; + + int mIndex; + uint64 uQuality; // 0 = none. + STAmount saInReq; // Max amount to spend by sender + STAmount saInAct; // Amount spent by sender (calc output) + STAmount saOutReq; // Amount to send (calc input) + STAmount saOutAct; // Amount actually sent (calc output). + + PathState( + const int iIndex, + const LedgerEntrySet& lesSource, + const STPath& spSourcePath, + const uint160& uReceiverID, + const uint160& uSenderID, + const STAmount& saSend, + const STAmount& saSendMax + ); + + Json::Value getJson() const; + + static PathState::pointer createPathState( + const int iIndex, + const LedgerEntrySet& lesSource, + const STPath& spSourcePath, + const uint160& uReceiverID, + const uint160& uSenderID, + const STAmount& saSend, + const STAmount& saSendMax + ) + { + return boost::make_shared(iIndex, lesSource, spSourcePath, uReceiverID, uSenderID, saSend, saSendMax); + } + + static bool lessPriority(const PathState::pointer& lhs, const PathState::pointer& rhs); +}; + +class RippleCalc +{ +protected: + LedgerEntrySet& lesActive; + +public: + // First time working in reverse a funding source was mentioned. Source may only be used there. + curIssuerNode mumSource; // Map of currency, issuer to node index. + + // If the transaction fails to meet some constraint, still need to delete unfunded offers. + boost::unordered_set musUnfundedFound; // Offers that were found unfunded. + + PathState::pointer pathCreate(const STPath& spPath); + void pathNext(const PathState::pointer& pspCur, const int iPaths, const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent); + TER calcNode(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); + TER calcNodeRev(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); + TER calcNodeFwd(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); + TER calcNodeOfferRev(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); + TER calcNodeOfferFwd(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); + TER calcNodeAccountRev(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); + TER calcNodeAccountFwd(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); + TER calcNodeAdvance(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality, const bool bReverse); + TER calcNodeDeliverRev( + const unsigned int uIndex, + const PathState::pointer& pspCur, + const bool bMultiQuality, + const uint160& uOutAccountID, + const STAmount& saOutReq, + STAmount& saOutAct); + + TER calcNodeDeliverFwd( + const unsigned int uIndex, + const PathState::pointer& pspCur, + const bool bMultiQuality, + const uint160& uInAccountID, + const STAmount& saInFunds, + const STAmount& saInReq, + STAmount& saInAct, + STAmount& saInFees); + + void calcNodeRipple(const uint32 uQualityIn, const uint32 uQualityOut, + const STAmount& saPrvReq, const STAmount& saCurReq, + STAmount& saPrvAct, STAmount& saCurAct, + uint64& uRateMax); + + RippleCalc(LedgerEntrySet& lesNodes) : lesActive(lesNodes) { ; } + + static TER rippleCalc( + LedgerEntrySet& lesActive, + STAmount& saMaxAmountAct, + STAmount& saDstAmountAct, + const STAmount& saDstAmountReq, + const STAmount& saMaxAmountReq, + const uint160& uDstAccountID, + const uint160& uSrcAccountID, + const STPathSet& spsPaths, + const bool bPartialPayment, + const bool bLimitQuality, + const bool bNoRippleDirect + ); +}; + +#endif +// vim:ts=4 diff --git a/src/SerializedTypes.cpp b/src/SerializedTypes.cpp index 4684345496..3bd54dc34a 100644 --- a/src/SerializedTypes.cpp +++ b/src/SerializedTypes.cpp @@ -9,6 +9,9 @@ #include "NewcoinAddress.h" #include "utils.h" +STAmount saZero(CURRENCY_ONE, ACCOUNT_ONE, 0); +STAmount saOne(CURRENCY_ONE, ACCOUNT_ONE, 1); + std::string SerializedType::getFullText() const { std::string ret; diff --git a/src/SerializedTypes.h b/src/SerializedTypes.h index 17e79af3c1..3a6417986e 100644 --- a/src/SerializedTypes.h +++ b/src/SerializedTypes.h @@ -395,6 +395,9 @@ public: Json::Value getJson(int) const; }; +extern STAmount saZero; +extern STAmount saOne; + class STHash128 : public SerializedType { protected: diff --git a/src/TransactionEngine.cpp b/src/TransactionEngine.cpp index f30ef3f4d6..526716ec81 100644 --- a/src/TransactionEngine.cpp +++ b/src/TransactionEngine.cpp @@ -17,387 +17,10 @@ #include "utils.h" #include "Interpreter.h" #include "Contract.h" +#include "RippleCalc.h" #define RIPPLE_PATHS_MAX 3 -static STAmount saZero(CURRENCY_ONE, ACCOUNT_ONE, 0); -static STAmount saOne(CURRENCY_ONE, ACCOUNT_ONE, 1); - -std::size_t hash_value(const aciSource& asValue) -{ - std::size_t seed = 0; - - asValue.get<0>().hash_combine(seed); - asValue.get<1>().hash_combine(seed); - asValue.get<2>().hash_combine(seed); - - return seed; -} - -// Returns amount owed by uToAccountID to uFromAccountID. -// <-- $owed/uCurrencyID/uToAccountID: positive: uFromAccountID holds IOUs., negative: uFromAccountID owes IOUs. -STAmount TransactionEngine::rippleOwed(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID) -{ - STAmount saBalance; - SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uToAccountID, uFromAccountID, uCurrencyID)); - - if (sleRippleState) - { - saBalance = sleRippleState->getIValueFieldAmount(sfBalance); - if (uToAccountID < uFromAccountID) - saBalance.negate(); - saBalance.setIssuer(uToAccountID); - } - else - { - Log(lsINFO) << "rippleOwed: No credit line between " - << NewcoinAddress::createHumanAccountID(uFromAccountID) - << " and " - << NewcoinAddress::createHumanAccountID(uToAccountID) - << " for " - << STAmount::createHumanCurrency(uCurrencyID) - << "." ; - - assert(false); - } - - return saBalance; - -} - -// Maximum amount of IOUs uToAccountID will hold from uFromAccountID. -// <-- $amount/uCurrencyID/uToAccountID. -STAmount TransactionEngine::rippleLimit(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID) -{ - STAmount saLimit; - SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uToAccountID, uFromAccountID, uCurrencyID)); - - assert(sleRippleState); - if (sleRippleState) - { - saLimit = sleRippleState->getIValueFieldAmount(uToAccountID < uFromAccountID ? sfLowLimit : sfHighLimit); - saLimit.setIssuer(uToAccountID); - } - - return saLimit; - -} - -uint32 TransactionEngine::rippleTransferRate(const uint160& uIssuerID) -{ - SLE::pointer sleAccount = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uIssuerID)); - - uint32 uQuality = sleAccount && sleAccount->getIFieldPresent(sfTransferRate) - ? sleAccount->getIFieldU32(sfTransferRate) - : QUALITY_ONE; - - Log(lsINFO) << boost::str(boost::format("rippleTransferRate: uIssuerID=%s account_exists=%d transfer_rate=%f") - % NewcoinAddress::createHumanAccountID(uIssuerID) - % !!sleAccount - % (uQuality/1000000000.0)); - - assert(sleAccount); - - return uQuality; -} - -// XXX Might not need this, might store in nodes on calc reverse. -uint32 TransactionEngine::rippleQualityIn(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID, const SOE_Field sfLow, const SOE_Field sfHigh) -{ - uint32 uQuality = QUALITY_ONE; - SLE::pointer sleRippleState; - - if (uToAccountID == uFromAccountID) - { - nothing(); - } - else - { - sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uToAccountID, uFromAccountID, uCurrencyID)); - - if (sleRippleState) - { - SOE_Field sfField = uToAccountID < uFromAccountID ? sfLow: sfHigh; - - uQuality = sleRippleState->getIFieldPresent(sfField) - ? sleRippleState->getIFieldU32(sfField) - : QUALITY_ONE; - - if (!uQuality) - uQuality = 1; // Avoid divide by zero. - } - } - - Log(lsINFO) << boost::str(boost::format("rippleQuality: %s uToAccountID=%s uFromAccountID=%s uCurrencyID=%s bLine=%d uQuality=%f") - % (sfLow == sfLowQualityIn ? "in" : "out") - % NewcoinAddress::createHumanAccountID(uToAccountID) - % NewcoinAddress::createHumanAccountID(uFromAccountID) - % STAmount::createHumanCurrency(uCurrencyID) - % !!sleRippleState - % (uQuality/1000000000.0)); - - assert(uToAccountID == uFromAccountID || !!sleRippleState); - - return uQuality; -} - -// Return how much of uIssuerID's uCurrencyID IOUs that uAccountID holds. May be negative. -// <-- IOU's uAccountID has of uIssuerID -STAmount TransactionEngine::rippleHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID) -{ - STAmount saBalance; - SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uAccountID, uIssuerID, uCurrencyID)); - - if (sleRippleState) - { - saBalance = sleRippleState->getIValueFieldAmount(sfBalance); - - if (uAccountID > uIssuerID) - saBalance.negate(); // Put balance in uAccountID terms. - } - - return saBalance; -} - -// <-- saAmount: amount of uCurrencyID held by uAccountID. May be negative. -STAmount TransactionEngine::accountHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID) -{ - STAmount saAmount; - - if (!uCurrencyID) - { - SLE::pointer sleAccount = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uAccountID)); - - saAmount = sleAccount->getIValueFieldAmount(sfBalance); - - Log(lsINFO) << "accountHolds: stamps: " << saAmount.getText(); - } - else - { - saAmount = rippleHolds(uAccountID, uCurrencyID, uIssuerID); - - Log(lsINFO) << "accountHolds: " - << saAmount.getFullText() - << " : " - << STAmount::createHumanCurrency(uCurrencyID) - << "/" - << NewcoinAddress::createHumanAccountID(uIssuerID); - } - - return saAmount; -} - -// Returns the funds available for uAccountID for a currency/issuer. -// Use when you need a default for rippling uAccountID's currency. -// --> saDefault/currency/issuer -// <-- saFunds: Funds available. May be negative. -// If the issuer is the same as uAccountID, funds are unlimited, use result is saDefault. -STAmount TransactionEngine::accountFunds(const uint160& uAccountID, const STAmount& saDefault) -{ - STAmount saFunds; - - Log(lsINFO) << "accountFunds: uAccountID=" - << NewcoinAddress::createHumanAccountID(uAccountID); - Log(lsINFO) << "accountFunds: saDefault.isNative()=" << saDefault.isNative(); - Log(lsINFO) << "accountFunds: saDefault.getIssuer()=" - << NewcoinAddress::createHumanAccountID(saDefault.getIssuer()); - - if (!saDefault.isNative() && saDefault.getIssuer() == uAccountID) - { - saFunds = saDefault; - - Log(lsINFO) << "accountFunds: offer funds: ripple self-funded: " << saFunds.getText(); - } - else - { - saFunds = accountHolds(uAccountID, saDefault.getCurrency(), saDefault.getIssuer()); - - Log(lsINFO) << "accountFunds: offer funds: uAccountID =" - << NewcoinAddress::createHumanAccountID(uAccountID) - << " : " - << saFunds.getText() - << "/" - << saDefault.getHumanCurrency() - << "/" - << NewcoinAddress::createHumanAccountID(saDefault.getIssuer()); - } - - return saFunds; -} - -// Calculate transit fee. -STAmount TransactionEngine::rippleTransferFee(const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID, const STAmount& saAmount) -{ - STAmount saTransitFee; - - if (uSenderID != uIssuerID && uReceiverID != uIssuerID) - { - uint32 uTransitRate = rippleTransferRate(uIssuerID); - - if (QUALITY_ONE != uTransitRate) - { - STAmount saTransitRate(CURRENCY_ONE, uTransitRate, -9); - - saTransitFee = STAmount::multiply(saAmount, saTransitRate, saAmount.getCurrency(), saAmount.getIssuer()); - } - } - - return saTransitFee; -} - -// Direct send w/o fees: redeeming IOUs and/or sending own IOUs. -void TransactionEngine::rippleCredit(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount, bool bCheckIssuer) -{ - uint160 uIssuerID = saAmount.getIssuer(); - - assert(!bCheckIssuer || uSenderID == uIssuerID || uReceiverID == uIssuerID); - - bool bFlipped = uSenderID > uReceiverID; - uint256 uIndex = Ledger::getRippleStateIndex(uSenderID, uReceiverID, saAmount.getCurrency()); - SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, uIndex); - - if (!sleRippleState) - { - Log(lsINFO) << "rippleCredit: Creating ripple line: " << uIndex.ToString(); - - STAmount saBalance = saAmount; - - sleRippleState = entryCreate(ltRIPPLE_STATE, uIndex); - - if (!bFlipped) - saBalance.negate(); - - sleRippleState->setIFieldAmount(sfBalance, saBalance); - sleRippleState->setIFieldAccount(bFlipped ? sfHighID : sfLowID, uSenderID); - sleRippleState->setIFieldAccount(bFlipped ? sfLowID : sfHighID, uReceiverID); - } - else - { - STAmount saBalance = sleRippleState->getIValueFieldAmount(sfBalance); - - if (!bFlipped) - saBalance.negate(); // Put balance in low terms. - - saBalance += saAmount; - - if (!bFlipped) - saBalance.negate(); - - sleRippleState->setIFieldAmount(sfBalance, saBalance); - - entryModify(sleRippleState); - } -} - -// Send regardless of limits. -// --> saAmount: Amount/currency/issuer for receiver to get. -// <-- saActual: Amount actually sent. Sender pay's fees. -STAmount TransactionEngine::rippleSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount) -{ - STAmount saActual; - const uint160 uIssuerID = saAmount.getIssuer(); - - assert(!!uSenderID && !!uReceiverID); - - if (uSenderID == uIssuerID || uReceiverID == uIssuerID) - { - // Direct send: redeeming IOUs and/or sending own IOUs. - rippleCredit(uSenderID, uReceiverID, saAmount); - - saActual = saAmount; - } - else - { - // Sending 3rd party IOUs: transit. - - STAmount saTransitFee = rippleTransferFee(uSenderID, uReceiverID, uIssuerID, saAmount); - - saActual = !saTransitFee ? saAmount : saAmount+saTransitFee; - - saActual.setIssuer(uIssuerID); // XXX Make sure this done in + above. - - rippleCredit(uIssuerID, uReceiverID, saAmount); - rippleCredit(uSenderID, uIssuerID, saActual); - } - - return saActual; -} - -void TransactionEngine::accountSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount) -{ - assert(!saAmount.isNegative()); - - if (!saAmount) - { - nothing(); - } - else if (saAmount.isNative()) - { - SLE::pointer sleSender = !!uSenderID - ? entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uSenderID)) - : SLE::pointer(); - SLE::pointer sleReceiver = !!uReceiverID - ? entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uReceiverID)) - : SLE::pointer(); - - Log(lsINFO) << boost::str(boost::format("accountSend> %s (%s) -> %s (%s) : %s") - % NewcoinAddress::createHumanAccountID(uSenderID) - % (sleSender ? (sleSender->getIValueFieldAmount(sfBalance)).getFullText() : "-") - % NewcoinAddress::createHumanAccountID(uReceiverID) - % (sleReceiver ? (sleReceiver->getIValueFieldAmount(sfBalance)).getFullText() : "-") - % saAmount.getFullText()); - - if (sleSender) - { - sleSender->setIFieldAmount(sfBalance, sleSender->getIValueFieldAmount(sfBalance) - saAmount); - entryModify(sleSender); - } - - if (sleReceiver) - { - sleReceiver->setIFieldAmount(sfBalance, sleReceiver->getIValueFieldAmount(sfBalance) + saAmount); - entryModify(sleReceiver); - } - - Log(lsINFO) << boost::str(boost::format("accountSend< %s (%s) -> %s (%s) : %s") - % NewcoinAddress::createHumanAccountID(uSenderID) - % (sleSender ? (sleSender->getIValueFieldAmount(sfBalance)).getFullText() : "-") - % NewcoinAddress::createHumanAccountID(uReceiverID) - % (sleReceiver ? (sleReceiver->getIValueFieldAmount(sfBalance)).getFullText() : "-") - % saAmount.getFullText()); - } - else - { - rippleSend(uSenderID, uReceiverID, saAmount); - } -} - -TER TransactionEngine::offerDelete(const SLE::pointer& sleOffer, const uint256& uOfferIndex, const uint160& uOwnerID) -{ - uint64 uOwnerNode = sleOffer->getIFieldU64(sfOwnerNode); - TER terResult = mNodes.dirDelete(false, uOwnerNode, Ledger::getOwnerDirIndex(uOwnerID), uOfferIndex, false); - - if (tesSUCCESS == terResult) - { - uint256 uDirectory = sleOffer->getIFieldH256(sfBookDirectory); - uint64 uBookNode = sleOffer->getIFieldU64(sfBookNode); - - terResult = mNodes.dirDelete(false, uBookNode, uDirectory, uOfferIndex, true); - } - - entryDelete(sleOffer); - - return terResult; -} - -TER TransactionEngine::offerDelete(const uint256& uOfferIndex) -{ - SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); - const uint160 uOwnerID = sleOffer->getIValueFieldAccount(sfAccount).getAccountID(); - - return offerDelete(sleOffer, uOfferIndex, uOwnerID); -} - // Set the authorized public key for an account. May also set the generator map. TER TransactionEngine::setAuthorized(const SerializedTransaction& txn, bool bMustSetGenerator) { @@ -859,7 +482,7 @@ TER TransactionEngine::applyTransaction(const SerializedTransaction& txn, Transa break; case ttPAYMENT: - terResult = doPayment(txn); + terResult = doPayment(txn, params); break; case ttWALLET_ADD: @@ -910,7 +533,6 @@ TER TransactionEngine::applyTransaction(const SerializedTransaction& txn, Transa mTxnAccount = SLE::pointer(); mNodes.clear(); - musUnfundedFound.clear(); if (!isSetBit(params, tapOPEN_LEDGER) && (isTemMalformed(terResult) || isTefFailure(terResult))) @@ -922,7 +544,6 @@ TER TransactionEngine::applyTransaction(const SerializedTransaction& txn, Transa } - TER TransactionEngine::doAccountSet(const SerializedTransaction& txn) { Log(lsINFO) << "doAccountSet>"; @@ -1320,1791 +941,9 @@ TER TransactionEngine::doPasswordSet(const SerializedTransaction& txn) return terResult; } -// If needed, advance to next funded offer. -// - Automatically advances to first offer. -// - Set bEntryAdvance to advance to next entry. -// <-- uOfferIndex : 0=end of list. -TER TransactionEngine::calcNodeAdvance( - const unsigned int uIndex, // 0 < uIndex < uLast - const PathState::pointer& pspCur, - const bool bMultiQuality, - const bool bReverse) -{ - PaymentNode& pnPrv = pspCur->vpnNodes[uIndex-1]; - PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; - - 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; - - do - { - bool bDirectDirDirty = false; - - if (!uDirectEnd) - { - // Need to initialize current node. - - uDirectTip = Ledger::getBookBase(uPrvCurrencyID, uPrvIssuerID, uCurCurrencyID, uCurIssuerID); - uDirectEnd = Ledger::getQualityNext(uDirectTip); - sleDirectDir = entryCache(ltDIR_NODE, uDirectTip); - bDirectAdvance = !sleDirectDir; - bDirectDirDirty = true; - - Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: Initialize node: uDirectTip=%s uDirectEnd=%s bDirectAdvance=%d") % uDirectTip % uDirectEnd % bDirectAdvance); - } - - if (bDirectAdvance) - { - // Get next quality. - uDirectTip = mLedger->getNextLedgerIndex(uDirectTip, uDirectEnd); - bDirectDirDirty = true; - bDirectAdvance = false; - - if (!!uDirectTip) - { - // Have another quality directory. - Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: Quality advance: uDirectTip=%s") % uDirectTip); - - sleDirectDir = entryCache(ltDIR_NODE, uDirectTip); - } - else if (bReverse) - { - Log(lsINFO) << "calcNodeAdvance: No more offers."; - - uOfferIndex = 0; - break; - } - else - { - // No more offers. Should be done rather than fall off end of book. - Log(lsINFO) << "calcNodeAdvance: Unreachable: Fell off end of order book."; - assert(false); - - terResult = tefEXCEPTION; - } - } - - if (bDirectDirDirty) - { - saOfrRate = STAmount::setRate(Ledger::getQuality(uDirectTip)); // For correct ratio - uEntry = 0; - bEntryAdvance = true; - - Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: directory dirty: saOfrRate=%s") % saOfrRate); - } - - if (!bEntryAdvance) - { - if (bFundsDirty) - { - saTakerPays = sleOffer->getIValueFieldAmount(sfTakerPays); - saTakerGets = sleOffer->getIValueFieldAmount(sfTakerGets); - - saOfferFunds = accountFunds(uOfrOwnerID, saTakerGets); // Funds left. - bFundsDirty = false; - - Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: directory dirty: saOfrRate=%s") % saOfrRate); - } - else - { - Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: as is")); - nothing(); - } - } - else if (!mNodes.dirNext(uDirectTip, sleDirectDir, uEntry, uOfferIndex)) - { - // Failed to find an entry in directory. - - uOfferIndex = 0; - - // Do another cur directory iff bMultiQuality - if (bMultiQuality) - { - Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: next quality")); - bDirectAdvance = true; - } - else if (!bReverse) - { - Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: unreachable: ran out of offers")); - assert(false); // Can't run out of offers in forward direction. - terResult = tefEXCEPTION; - } - } - else - { - // Got a new offer. - sleOffer = entryCache(ltOFFER, uOfferIndex); - uOfrOwnerID = sleOffer->getIValueFieldAccount(sfAccount).getAccountID(); - - const aciSource asLine = boost::make_tuple(uOfrOwnerID, uCurCurrencyID, uCurIssuerID); - - Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: uOfrOwnerID=%s") % NewcoinAddress::createHumanAccountID(uOfrOwnerID)); - - if (sleOffer->getIFieldPresent(sfExpiration) && sleOffer->getIFieldU32(sfExpiration) <= mLedger->getParentCloseTimeNC()) - { - // Offer is expired. - Log(lsINFO) << "calcNodeAdvance: expired offer"; - - assert(musUnfundedFound.find(uOfferIndex) != musUnfundedFound.end()); // Verify reverse found it too. - bEntryAdvance = true; - 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. - curIssuerNodeConstIterator itForward = pspCur->umForward.find(asLine); - const bool bFoundForward = itForward != pspCur->umForward.end(); - - if (bFoundForward && itForward->second != uIndex) - { - // Temporarily unfunded. Another node uses this source, ignore in this offer. - Log(lsINFO) << "calcNodeAdvance: temporarily unfunded offer (forward)"; - - bEntryAdvance = true; - continue; - } - - curIssuerNodeConstIterator itPast = mumSource.find(asLine); - bool bFoundPast = itPast != mumSource.end(); - - if (bFoundPast && itPast->second != uIndex) - { - // Temporarily unfunded. Another node uses this source, ignore in this offer. - Log(lsINFO) << "calcNodeAdvance: temporarily unfunded offer (past)"; - - bEntryAdvance = true; - continue; - } - - curIssuerNodeConstIterator itReverse = pspCur->umReverse.find(asLine); - bool bFoundReverse = itReverse != pspCur->umReverse.end(); - - if (bFoundReverse && itReverse->second != uIndex) - { - // Temporarily unfunded. Another node uses this source, ignore in this offer. - Log(lsINFO) << "calcNodeAdvance: temporarily unfunded offer (reverse)"; - - bEntryAdvance = true; - continue; - } - - saTakerPays = sleOffer->getIValueFieldAmount(sfTakerPays); - saTakerGets = sleOffer->getIValueFieldAmount(sfTakerGets); - - saOfferFunds = accountFunds(uOfrOwnerID, saTakerGets); // Funds left. - - if (!saOfferFunds.isPositive()) - { - // Offer is unfunded. - Log(lsINFO) << "calcNodeAdvance: unfunded offer"; - - if (bReverse && !bFoundReverse && !bFoundPast) - { - // Never mentioned before: found unfunded. - musUnfundedFound.insert(uOfferIndex); // Mark offer for always deletion. - } - - // YYY Could verify offer is correct place for unfundeds. - bEntryAdvance = true; - continue; - } - - if (bReverse // Need to remember reverse mention. - && !bFoundPast // Not mentioned in previous passes. - && !bFoundReverse) // Not mentioned for pass. - { - // Consider source mentioned by current path state. - Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: remember=%s/%s/%s") - % NewcoinAddress::createHumanAccountID(uOfrOwnerID) - % STAmount::createHumanCurrency(uCurCurrencyID) - % NewcoinAddress::createHumanAccountID(uCurIssuerID)); - - pspCur->umReverse.insert(std::make_pair(asLine, uIndex)); - } - - bFundsDirty = false; - bEntryAdvance = false; - } - } - while (tesSUCCESS == terResult && (bEntryAdvance || bDirectAdvance)); - - if (tesSUCCESS == terResult) - { - Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: uOfferIndex=%s") % uOfferIndex); - } - else - { - Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: terResult=%s") % transToken(terResult)); - } - - return terResult; -} - -// 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 process till request is satisified while we the rate does not increase past the initial rate. -TER TransactionEngine::calcNodeDeliverRev( - const unsigned int uIndex, // 0 < uIndex < uLast - const PathState::pointer& pspCur, - const bool bMultiQuality, - const uint160& uOutAccountID, // --> Output owner's account. - const STAmount& saOutReq, // --> Funds wanted. - STAmount& saOutAct) // <-- Funds delivered. -{ - TER terResult = tesSUCCESS; - - PaymentNode& pnPrv = pspCur->vpnNodes[uIndex-1]; - PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; - - const uint160& uCurIssuerID = pnCur.uIssuerID; - const uint160& uPrvAccountID = pnPrv.uAccountID; - const STAmount& saTransferRate = pnCur.saTransferRate; - - STAmount& saPrvDlvReq = pnPrv.saRevDeliver; // To be adjusted. - - saOutAct = 0; - - while (saOutAct != saOutReq) // Did not deliver limit. - { - 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(uIndex, pspCur, 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 receiving or sending. - ? saOne // No fee. - : saTransferRate; // Transfer rate of issuer. - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: uOfrOwnerID=%s uOutAccountID=%s uCurIssuerID=%s saTransferRate=%s saOutFeeRate=%s") - % NewcoinAddress::createHumanAccountID(uOfrOwnerID) - % NewcoinAddress::createHumanAccountID(uOutAccountID) - % NewcoinAddress::createHumanAccountID(uCurIssuerID) - % saTransferRate.getFullText() - % saOutFeeRate.getFullText()); - - if (!saRateMax) - { - // Set initial rate. - saRateMax = saOutFeeRate; - - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: Set initial rate: saRateMax=%s saOutFeeRate=%s") - % saRateMax - % saOutFeeRate); - } - else if (saRateMax < saOutFeeRate) - { - // Offer exceeds initial rate. - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: Offer exceeds initial rate: saRateMax=%s saOutFeeRate=%s") - % saRateMax - % saOutFeeRate); - - nothing(); - break; - } - else if (saOutFeeRate < saRateMax) - { - // Reducing rate. - - saRateMax = saOutFeeRate; - - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: Reducing rate: saRateMax=%s") - % saRateMax); - } - - STAmount saOutPass = std::min(std::min(saOfferFunds, saTakerGets), saOutReq-saOutAct); // Offer maximum out - assuming no out fees. - STAmount saOutPlusFees = STAmount::multiply(saOutPass, saOutFeeRate); // Offer out with fees. - - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: saOutReq=%s saOutAct=%s saTakerGets=%s saOutPass=%s saOutPlusFees=%s saOfferFunds=%s") - % saOutReq - % saOutAct - % saTakerGets - % saOutPass - % saOutPlusFees - % saOfferFunds); - - if (saOutPlusFees > saOfferFunds) - { - // Offer owner can not cover all fees, compute saOutPass based on saOfferFunds. - - saOutPlusFees = saOfferFunds; - saOutPass = STAmount::divide(saOutPlusFees, saOutFeeRate); - - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: Total exceeds fees: saOutPass=%s saOutPlusFees=%s saOfferFunds=%s") - % saOutPass - % saOutPlusFees - % saOfferFunds); - } - - // Compute portion of input needed to cover output. - - STAmount saInPassReq = STAmount::multiply(saOutPass, saOfrRate, saTakerPays); - STAmount saInPassAct; - - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: saInPassReq=%s saOfrRate=%s saOutPass=%s saOutPlusFees=%s") - % saInPassReq - % saOfrRate - % saOutPass - % saOutPlusFees); - - // Find out input amount actually available at current rate. - if (!!uPrvAccountID) - { - // account --> OFFER --> ? - // 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; - - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: account --> OFFER --> ? : saInPassAct=%s") - % saPrvDlvReq); - } - else - { - // offer --> OFFER --> ? - - terResult = calcNodeDeliverRev( - uIndex-1, - pspCur, - bMultiQuality, - uOfrOwnerID, - saInPassReq, - saInPassAct); - - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: offer --> OFFER --> ? : saInPassAct=%s") - % saInPassAct); - } - - if (tesSUCCESS != terResult) - break; - - if (saInPassAct != saInPassReq) - { - // Adjust output to conform to limited input. - saOutPass = STAmount::divide(saInPassAct, saOfrRate, saTakerGets); - saOutPlusFees = STAmount::multiply(saOutPass, saOutFeeRate); - - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: adjusted: saOutPass=%s saOutPlusFees=%s") - % saOutPass - % saOutPlusFees); - } - - // Funds were spent. - bFundsDirty = true; - - // Deduct output, don't actually need to send. - accountSend(uOfrOwnerID, uCurIssuerID, saOutPass); - - // Adjust offer - sleOffer->setIFieldAmount(sfTakerGets, saTakerGets - saOutPass); - sleOffer->setIFieldAmount(sfTakerPays, saTakerPays - saInPassAct); - - entryModify(sleOffer); - - if (saOutPass == saTakerGets) - { - // Offer became unfunded. - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: offer became unfunded.")); - - bEntryAdvance = true; - } - - saOutAct += saOutPass; - saPrvDlvReq += saInPassAct; - } - - if (!saOutAct) - terResult = tepPATH_DRY; - - return terResult; -} - -// Deliver maximum amount of funds from previous node. -// Goal: Make progress consuming the offer. -TER TransactionEngine::calcNodeDeliverFwd( - const unsigned int uIndex, // 0 < uIndex < uLast - const PathState::pointer& pspCur, - const bool bMultiQuality, - const uint160& uInAccountID, // --> Input owner's account. - const STAmount& saInFunds, // --> Funds available for delivery and fees. - const STAmount& saInReq, // --> Limit to deliver. - STAmount& saInAct, // <-- Amount delivered. - STAmount& saInFees) // <-- Fees charged. -{ - TER terResult = tesSUCCESS; - - PaymentNode& pnPrv = pspCur->vpnNodes[uIndex-1]; - PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; - PaymentNode& pnNxt = pspCur->vpnNodes[uIndex+1]; - - const uint160& uNxtAccountID = pnNxt.uAccountID; - const uint160& uCurIssuerID = pnCur.uIssuerID; - const uint160& uPrvIssuerID = pnPrv.uIssuerID; - const STAmount& saTransferRate = pnPrv.saTransferRate; - - saInAct = 0; - saInFees = 0; - - while (tesSUCCESS == terResult - && saInAct != saInReq // Did not deliver limit. - && saInAct + saInFees != saInFunds) // Did not deliver all funds. - { - terResult = calcNodeAdvance(uIndex, pspCur, bMultiQuality, false); // If needed, advance to next funded offer. - - if (tesSUCCESS == terResult) - { - 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 = uInAccountID == uPrvIssuerID || uOfrOwnerID == uPrvIssuerID // Issuer receiving or sending. - ? saOne // No fee. - : saTransferRate; // Transfer rate of issuer. - - // - // First calculate assuming no output fees. - // XXX Make sure derived in does not exceed actual saTakerPays due to rounding. - - STAmount saOutFunded = std::max(saOfferFunds, saTakerGets); // Offer maximum out - There are no out fees. - STAmount saInFunded = STAmount::multiply(saOutFunded, saOfrRate, saInReq); // Offer maximum in - Limited by by payout. - STAmount saInTotal = STAmount::multiply(saInFunded, saTransferRate); // Offer maximum in with fees. - STAmount saInSum = std::min(saInTotal, saInFunds-saInAct-saInFees); // In limited by saInFunds. - STAmount saInPassAct = STAmount::divide(saInSum, saInFeeRate); // In without fees. - STAmount saOutPassMax = STAmount::divide(saInPassAct, saOfrRate, saOutFunded); // Out. - - STAmount saInPassFees; - STAmount saOutPassAct; - - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: saOutFunded=%s saInFunded=%s saInTotal=%s saInSum=%s saInPassAct=%s saOutPassMax=%s") - % saOutFunded - % saInFunded - % saInTotal - % saInSum - % saInPassAct - % saOutPassMax); - - if (!!uNxtAccountID) - { - // ? --> OFFER --> account - // Input fees: vary based upon the consumed offer's owner. - // Output fees: none as the destination account is the issuer. - - // XXX This doesn't claim input. - // XXX Assumes input is in limbo. XXX Check. - - // Debit offer owner. - accountSend(uOfrOwnerID, uCurIssuerID, saOutPassMax); - - saOutPassAct = saOutPassMax; - - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: ? --> OFFER --> account: saOutPassAct=%s") - % saOutPassAct); - } - else - { - // ? --> OFFER --> offer - STAmount saOutPassFees; - - terResult = TransactionEngine::calcNodeDeliverFwd( - uIndex+1, - pspCur, - bMultiQuality, - uOfrOwnerID, - saOutPassMax, - saOutPassMax, - saOutPassAct, // <-- Amount delivered. - saOutPassFees); // <-- Fees charged. - - if (tesSUCCESS != terResult) - break; - - // Offer maximum in limited by next payout. - saInPassAct = STAmount::multiply(saOutPassAct, saOfrRate); - saInPassFees = STAmount::multiply(saInFunded, saInFeeRate)-saInPassAct; - } - - Log(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: saTakerGets=%s saTakerPays=%s saInPassAct=%s saOutPassAct=%s") - % saTakerGets.getFullText() - % saTakerPays.getFullText() - % saInPassAct.getFullText() - % saOutPassAct.getFullText()); - - // Funds were spent. - bFundsDirty = true; - - // Credit issuer transfer fees. - accountSend(uInAccountID, uOfrOwnerID, saInPassFees); - - // Credit offer owner from offer. - accountSend(uInAccountID, uOfrOwnerID, saInPassAct); - - // Adjust offer - sleOffer->setIFieldAmount(sfTakerGets, saTakerGets - saOutPassAct); - sleOffer->setIFieldAmount(sfTakerPays, saTakerPays - saInPassAct); - - entryModify(sleOffer); - - if (saOutPassAct == saTakerGets) - { - // Offer became unfunded. - pspCur->vUnfundedBecame.push_back(uOfferIndex); - bEntryAdvance = true; - } - - saInAct += saInPassAct; - saInFees += saInPassFees; - } - } - - return terResult; -} - -// Called to drive from the last offer node in a chain. -TER TransactionEngine::calcNodeOfferRev( - const unsigned int uIndex, // 0 < uIndex < uLast - const PathState::pointer& pspCur, - const bool bMultiQuality) -{ - TER terResult; - - PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; - PaymentNode& pnNxt = pspCur->vpnNodes[uIndex+1]; - - if (!!pnNxt.uAccountID) - { - // Next is an account node, resolve current offer node's deliver. - STAmount saDeliverAct; - - terResult = calcNodeDeliverRev( - uIndex, - pspCur, - bMultiQuality, - - pnNxt.uAccountID, - pnCur.saRevDeliver, - saDeliverAct); - } - else - { - // 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 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 TransactionEngine::calcNodeOfferFwd( - const unsigned int uIndex, // 0 < uIndex < uLast - const PathState::pointer& pspCur, - const bool bMultiQuality - ) -{ - TER terResult; - PaymentNode& pnPrv = pspCur->vpnNodes[uIndex-1]; - - if (!!pnPrv.uAccountID) - { - // Previous is an account node, resolve its deliver. - STAmount saInAct; - STAmount saInFees; - - terResult = calcNodeDeliverFwd( - uIndex, - pspCur, - bMultiQuality, - pnPrv.uAccountID, - pnPrv.saFwdDeliver, - pnPrv.saFwdDeliver, - saInAct, - saInFees); - - assert(tesSUCCESS != terResult || pnPrv.saFwdDeliver == saInAct+saInFees); - } - else - { - // Previous is an offer. Deliver has already been resolved. - terResult = tesSUCCESS; - } - - return terResult; - -} - -// Cur is the driver and will be filled exactly. -// 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 as it calculates previous wants based on previous credit limits and current wants. -// This routine works forwards as it calculates current deliver based on previous delivery limits and current wants. -// XXX Deal with uQualityIn or uQualityOut = 0 -void TransactionEngine::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 - STAmount& saCurAct, // <-> out limit achieved. - uint64& uRateMax) -{ - Log(lsINFO) << boost::str(boost::format("calcNodeRipple> uQualityIn=%d uQualityOut=%d saPrvReq=%s saCurReq=%s saPrvAct=%s saCurAct=%s") - % uQualityIn - % uQualityOut - % saPrvReq.getFullText() - % saCurReq.getFullText() - % saPrvAct.getFullText() - % saCurAct.getFullText()); - - assert(saPrvReq.getCurrency() == saCurReq.getCurrency()); - - const bool bPrvUnlimited = saPrvReq.isNegative(); - const STAmount saPrv = bPrvUnlimited ? STAmount(saPrvReq) : saPrvReq-saPrvAct; - const STAmount saCur = saCurReq-saCurAct; - -#if 0 - Log(lsINFO) << boost::str(boost::format("calcNodeRipple: bPrvUnlimited=%d saPrv=%s saCur=%s") - % bPrvUnlimited - % saPrv.getFullText() - % saCur.getFullText()); -#endif - - if (uQualityIn >= uQualityOut) - { - // No fee. - Log(lsINFO) << boost::str(boost::format("calcNodeRipple: No fees")); - - if (!uRateMax || STAmount::uRateOne <= uRateMax) - { - STAmount saTransfer = bPrvUnlimited ? saCur : std::min(saPrv, saCur); - - saPrvAct += saTransfer; - saCurAct += saTransfer; - - if (!uRateMax) - uRateMax = STAmount::uRateOne; - } - } - else - { - // Fee. - Log(lsINFO) << boost::str(boost::format("calcNodeRipple: Fee")); - - uint64 uRate = STAmount::getRate(STAmount(uQualityIn), STAmount(uQualityOut)); - - if (!uRateMax || uRate <= uRateMax) - { - const uint160 uCurrencyID = saCur.getCurrency(); - const uint160 uCurIssuerID = saCur.getIssuer(); - const uint160 uPrvIssuerID = saPrv.getIssuer(); - - STAmount saCurIn = STAmount::divide(STAmount::multiply(saCur, uQualityOut, uCurrencyID, uCurIssuerID), uQualityIn, uCurrencyID, uCurIssuerID); - - Log(lsINFO) << boost::str(boost::format("calcNodeRipple: bPrvUnlimited=%d saPrv=%s saCurIn=%s") % bPrvUnlimited % saPrv.getFullText() % saCurIn.getFullText()); - if (bPrvUnlimited || saCurIn <= saPrv) - { - // All of cur. Some amount of prv. - saCurAct += saCur; - saPrvAct += saCurIn; - Log(lsINFO) << boost::str(boost::format("calcNodeRipple:3c: saCurReq=%s saPrvAct=%s") % saCurReq.getFullText() % saPrvAct.getFullText()); - } - else - { - // A part of cur. All of prv. (cur as driver) - STAmount saCurOut = STAmount::divide(STAmount::multiply(saPrv, uQualityIn, uCurrencyID, uCurIssuerID), uQualityOut, uCurrencyID, uCurIssuerID); - Log(lsINFO) << boost::str(boost::format("calcNodeRipple:4: saCurReq=%s") % saCurReq.getFullText()); - - saCurAct += saCurOut; - saPrvAct = saPrvReq; - - if (!uRateMax) - uRateMax = uRate; - } - } - } - - Log(lsINFO) << boost::str(boost::format("calcNodeRipple< uQualityIn=%d uQualityOut=%d saPrvReq=%s saCurReq=%s saPrvAct=%s saCurAct=%s") - % uQualityIn - % uQualityOut - % saPrvReq.getFullText() - % saCurReq.getFullText() - % saPrvAct.getFullText() - % saCurAct.getFullText()); -} - -// Calculate saPrvRedeemReq, saPrvIssueReq, saPrvDeliver from saCur... -// <-- tesSUCCESS or tepPATH_DRY -TER TransactionEngine::calcNodeAccountRev(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality) -{ - TER terResult = tesSUCCESS; - const unsigned int uLast = pspCur->vpnNodes.size() - 1; - - uint64 uRateMax = 0; - - PaymentNode& pnPrv = pspCur->vpnNodes[uIndex ? uIndex-1 : 0]; - PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; - PaymentNode& pnNxt = pspCur->vpnNodes[uIndex == uLast ? uLast : uIndex+1]; - - // Current is allowed to redeem to next. - const bool bPrvAccount = !uIndex || isSetBit(pnPrv.uFlags, STPathElement::typeAccount); - const bool bNxtAccount = uIndex == 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; - - const uint32 uQualityIn = uIndex ? rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID) : QUALITY_ONE; - const uint32 uQualityOut = uIndex != uLast ? rippleQualityOut(uCurAccountID, uNxtAccountID, uCurrencyID) : QUALITY_ONE; - - // For bPrvAccount - const STAmount saPrvOwed = bPrvAccount && uIndex // Previous account is owed. - ? rippleOwed(uCurAccountID, uPrvAccountID, uCurrencyID) - : STAmount(uCurrencyID, uCurAccountID); - - const STAmount saPrvLimit = bPrvAccount && uIndex // Previous account may owe. - ? rippleLimit(uCurAccountID, uPrvAccountID, uCurrencyID) - : STAmount(uCurrencyID, uCurAccountID); - - const STAmount saNxtOwed = bNxtAccount && uIndex != uLast // Next account is owed. - ? rippleOwed(uCurAccountID, uNxtAccountID, uCurrencyID) - : STAmount(uCurrencyID, uCurAccountID); - - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev> uIndex=%d/%d uPrvAccountID=%s uCurAccountID=%s uNxtAccountID=%s uCurrencyID=%s uQualityIn=%d uQualityOut=%d saPrvOwed=%s saPrvLimit=%s") - % uIndex - % uLast - % NewcoinAddress::createHumanAccountID(uPrvAccountID) - % NewcoinAddress::createHumanAccountID(uCurAccountID) - % NewcoinAddress::createHumanAccountID(uNxtAccountID) - % STAmount::createHumanCurrency(uCurrencyID) - % uQualityIn - % uQualityOut - % saPrvOwed.getFullText() - % saPrvLimit.getFullText()); - - // Previous can redeem the owed IOUs it holds. - const STAmount saPrvRedeemReq = saPrvOwed.isPositive() ? saPrvOwed : STAmount(uCurrencyID, 0); - 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::saFromSigned(uCurrencyID, uCurAccountID, -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()); - - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: saPrvRedeemReq=%s saPrvIssueReq=%s saCurRedeemReq=%s saNxtOwed=%s") - % saPrvRedeemReq.getFullText() - % saPrvIssueReq.getFullText() - % saCurRedeemReq.getFullText() - % saNxtOwed.getFullText()); - - Log(lsINFO) << pspCur->getJson(); - - assert(!saCurRedeemReq || (-saNxtOwed) >= saCurRedeemReq); // Current redeem req can't be more than IOUs on hand. - assert(!saCurIssueReq || !saNxtOwed.isPositive() || saNxtOwed == saCurRedeemReq); // If issue req, then redeem req must consume all owed. - - if (bPrvAccount && bNxtAccount) - { - if (!uIndex) - { - // ^ --> ACCOUNT --> account|offer - // Nothing to do, there is no previous to adjust. - nothing(); - } - else if (uIndex == uLast) - { - // account --> ACCOUNT --> $ - // Overall deliverable. - const STAmount& saCurWantedReq = bPrvAccount - ? std::min(pspCur->saOutReq, saPrvLimit+saPrvOwed) // If previous is an account, limit. - : pspCur->saOutReq; // Previous is an offer, no limit: redeem own IOUs. - STAmount saCurWantedAct(saCurWantedReq.getCurrency(), saCurWantedReq.getIssuer()); - - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: account --> ACCOUNT --> $ : saCurWantedReq=%s") - % saCurWantedReq.getFullText()); - - // Calculate redeem - if (saPrvRedeemReq) // Previous has IOUs to redeem. - { - // Redeem at 1:1 - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Redeem at 1:1")); - - saCurWantedAct = std::min(saPrvRedeemReq, saCurWantedReq); - saPrvRedeemAct = saCurWantedAct; - - uRateMax = STAmount::uRateOne; - } - - // Calculate issuing. - if (saCurWantedReq != saCurWantedAct // Need more. - && saPrvIssueReq) // Will accept IOUs from prevous. - { - // Rate: quality in : 1.0 - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: 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); - } - - if (!saCurWantedAct) - { - // Must have processed something. - terResult = tepPATH_DRY; - } - } - else - { - // ^|account --> ACCOUNT --> account - - // redeem (part 1) -> redeem - if (saCurRedeemReq // Next wants IOUs redeemed. - && saPrvRedeemReq) // Previous has IOUs to redeem. - { - // Rate : 1.0 : quality out - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Rate : 1.0 : quality out")); - - calcNodeRipple(QUALITY_ONE, uQualityOut, saPrvRedeemReq, saCurRedeemReq, saPrvRedeemAct, saCurRedeemAct, uRateMax); - } - - // 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 - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Rate: quality in : quality out")); - - calcNodeRipple(uQualityIn, uQualityOut, saPrvIssueReq, saCurRedeemReq, saPrvIssueAct, saCurRedeemAct, uRateMax); - } - - // 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 - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Rate : 1.0 : transfer_rate")); - - calcNodeRipple(QUALITY_ONE, rippleTransferRate(uCurAccountID), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct, uRateMax); - } - - // 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. - { - // Rate: quality in : 1.0 - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Rate: quality in : 1.0")); - - calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurIssueReq, saPrvIssueAct, saCurIssueAct, uRateMax); - } - - if (!saCurRedeemAct && !saCurIssueAct) - { - // Must want something. - terResult = tepPATH_DRY; - } - - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: ^|account --> ACCOUNT --> account : saCurRedeemReq=%s saCurIssueReq=%s saPrvOwed=%s saCurRedeemAct=%s saCurIssueAct=%s") - % saCurRedeemReq.getFullText() - % saCurIssueReq.getFullText() - % saPrvOwed.getFullText() - % saCurRedeemAct.getFullText() - % saCurIssueAct.getFullText()); - } - } - else if (bPrvAccount && !bNxtAccount) - { - // account --> ACCOUNT --> offer - // Note: deliver is always issue as ACCOUNT is the issuer for the offer input. - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: account --> ACCOUNT --> offer")); - - // redeem -> deliver/issue. - if (saPrvOwed.isPositive() // Previous has IOUs to redeem. - && saCurDeliverReq) // Need some issued. - { - // Rate : 1.0 : transfer_rate - calcNodeRipple(QUALITY_ONE, 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 = tepPATH_DRY; - } - - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: saCurDeliverReq=%s saCurDeliverAct=%s saPrvOwed=%s") - % saCurDeliverReq.getFullText() - % saCurDeliverAct.getFullText() - % saPrvOwed.getFullText()); - } - else if (!bPrvAccount && bNxtAccount) - { - if (uIndex == uLast) - { - // offer --> ACCOUNT --> $ - const STAmount& saCurWantedReq = bPrvAccount - ? std::min(pspCur->saOutReq, saPrvLimit+saPrvOwed) // If previous is an account, limit. - : pspCur->saOutReq; // Previous is an offer, no limit: redeem own IOUs. - STAmount saCurWantedAct(saCurWantedReq.getCurrency(), saCurWantedReq.getIssuer()); - - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: offer --> ACCOUNT --> $ : saCurWantedReq=%s") - % saCurWantedReq.getFullText()); - - // Rate: quality in : 1.0 - calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvDeliverReq, saCurWantedReq, saPrvDeliverAct, saCurWantedAct, uRateMax); - - if (!saCurWantedAct) - { - // Must have processed something. - terResult = tepPATH_DRY; - } - } - else - { - // offer --> ACCOUNT --> account - // Note: offer is always delivering(redeeming) as account is issuer. - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: offer --> ACCOUNT --> account")); - - // 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, rippleTransferRate(uCurAccountID), saPrvDeliverReq, saCurIssueReq, saPrvDeliverAct, saCurIssueAct, uRateMax); - } - - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: saCurRedeemReq=%s saCurIssueAct=%s saCurIssueReq=%s saPrvDeliverAct=%s") - % saCurRedeemReq.getFullText() - % saCurRedeemAct.getFullText() - % saCurIssueReq.getFullText() - % saPrvDeliverAct.getFullText()); - - if (!saPrvDeliverAct) - { - // Must want something. - terResult = tepPATH_DRY; - } - } - } - else - { - // offer --> ACCOUNT --> offer - // deliver/redeem -> deliver/issue. - Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: offer --> ACCOUNT --> offer")); - - // Rate : 1.0 : transfer_rate - calcNodeRipple(QUALITY_ONE, rippleTransferRate(uCurAccountID), saPrvDeliverReq, saCurDeliverReq, saPrvDeliverAct, saCurDeliverAct, uRateMax); - - if (!saCurDeliverAct) - { - // Must want something. - terResult = tepPATH_DRY; - } - } - - return terResult; -} - -// Perfrom 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 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. -TER TransactionEngine::calcNodeAccountFwd( - const unsigned int uIndex, // 0 <= uIndex <= uLast - const PathState::pointer& pspCur, - const bool bMultiQuality) -{ - TER terResult = tesSUCCESS; - const unsigned int uLast = pspCur->vpnNodes.size() - 1; - - uint64 uRateMax = 0; - - PaymentNode& pnPrv = pspCur->vpnNodes[uIndex ? uIndex-1 : 0]; - PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; - PaymentNode& pnNxt = pspCur->vpnNodes[uIndex == uLast ? uLast : uIndex+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& uCurrencyID = pnCur.uCurrencyID; - - uint32 uQualityIn = uIndex ? rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID) : QUALITY_ONE; - uint32 uQualityOut = uIndex == uLast ? rippleQualityOut(uCurAccountID, uNxtAccountID, uCurrencyID) : QUALITY_ONE; - - // 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.saRevDeliver; - 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; - - Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd> uIndex=%d/%d saPrvRedeemReq=%s saPrvIssueReq=%s saPrvDeliverReq=%s saCurRedeemReq=%s saCurIssueReq=%s saCurDeliverReq=%s") - % uIndex - % uLast - % saPrvRedeemReq.getFullText() - % saPrvIssueReq.getFullText() - % saPrvDeliverReq.getFullText() - % saCurRedeemReq.getFullText() - % saCurIssueReq.getFullText() - % saCurDeliverReq.getFullText()); - - // Ripple through account. - - if (bPrvAccount && bNxtAccount) - { - if (!uIndex) - { - // ^ --> ACCOUNT --> account - - // First node, calculate amount to send. - // XXX Use stamp/ripple balance - PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; - - const STAmount& saCurRedeemReq = pnCur.saRevRedeem; - STAmount& saCurRedeemAct = pnCur.saFwdRedeem; - const STAmount& saCurIssueReq = pnCur.saRevIssue; - STAmount& saCurIssueAct = pnCur.saFwdIssue; - - const STAmount& saCurSendMaxReq = pspCur->saInReq; // Negative for no limit, doing a calculation. - STAmount& saCurSendMaxAct = pspCur->saInAct; // Report to user how much this sends. - - if (saCurRedeemReq) - { - // Redeem requested. - saCurRedeemAct = saCurRedeemReq.isNegative() - ? saCurRedeemReq - : std::min(saCurRedeemReq, saCurSendMaxReq); - } - else - { - saCurRedeemAct = STAmount(saCurRedeemReq); - } - saCurSendMaxAct = saCurRedeemAct; - - if (saCurIssueReq && (saCurSendMaxReq.isNegative() || saCurSendMaxReq != saCurRedeemAct)) - { - // Issue requested and not over budget. - saCurIssueAct = saCurSendMaxReq.isNegative() - ? saCurIssueReq - : std::min(saCurSendMaxReq-saCurRedeemAct, saCurIssueReq); - } - else - { - saCurIssueAct = STAmount(saCurIssueReq); - } - saCurSendMaxAct += saCurIssueAct; - - Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: ^ --> ACCOUNT --> account : saCurSendMaxReq=%s saCurRedeemAct=%s saCurIssueReq=%s saCurIssueAct=%s") - % saCurSendMaxReq.getFullText() - % saCurRedeemAct.getFullText() - % saCurIssueReq.getFullText() - % saCurIssueAct.getFullText()); - } - else if (uIndex == uLast) - { - // account --> ACCOUNT --> $ - Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: account --> ACCOUNT --> $ : uPrvAccountID=%s uCurAccountID=%s saPrvRedeemReq=%s saPrvIssueReq=%s") - % NewcoinAddress::createHumanAccountID(uPrvAccountID) - % NewcoinAddress::createHumanAccountID(uCurAccountID) - % saPrvRedeemReq.getFullText() - % saPrvIssueReq.getFullText()); - - // Last node. Accept all funds. Calculate amount actually to credit. - - STAmount& saCurReceive = pspCur->saOutAct; - - STAmount saIssueCrd = uQualityIn >= QUALITY_ONE - ? saPrvIssueReq // No fee. - : STAmount::multiply(saPrvIssueReq, uQualityIn, uCurrencyID, saPrvIssueReq.getIssuer()); // Fee. - - // Amount to credit. - saCurReceive = saPrvRedeemReq+saIssueCrd; - - // Actually receive. - rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq+saPrvIssueReq, false); - } - else - { - // account --> ACCOUNT --> account - Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: account --> ACCOUNT --> account")); - - // Previous redeem part 1: redeem -> redeem - if (saPrvRedeemReq != saPrvRedeemAct) // Previous wants to redeem. To next must be ok. - { - // 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. - // wants to redeem and current would and can issue. - // If redeeming cur to next is done, this implies can issue. - if (saPrvRedeemReq != saPrvRedeemAct // Previous still wants to redeem. - && saCurRedeemReq == saCurRedeemAct // Current has no more to redeem to next. - && saCurIssueReq) - { - // Rate : 1.0 : transfer_rate - calcNodeRipple(QUALITY_ONE, rippleTransferRate(uCurAccountID), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct, uRateMax); - } - - // Previous issue part 2 : issue -> issue - if (saPrvIssueReq != saPrvIssueAct) // Previous wants to issue. To next must be ok. - { - // Rate: quality in : 1.0 - calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurIssueReq, saPrvIssueAct, saCurIssueAct, uRateMax); - } - - // Adjust prv --> cur balance : take all inbound - // XXX Currency must be in amount. - rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq + saPrvIssueReq, false); - } - } - else if (bPrvAccount && !bNxtAccount) - { - // account --> ACCOUNT --> offer - Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: account --> ACCOUNT --> offer")); - - // redeem -> issue. - // wants to redeem and current would and can issue. - // If redeeming cur to next is done, this implies can issue. - if (saPrvRedeemReq) // Previous wants to redeem. - { - // Rate : 1.0 : transfer_rate - calcNodeRipple(QUALITY_ONE, rippleTransferRate(uCurAccountID), saPrvRedeemReq, saCurDeliverReq, saPrvRedeemAct, saCurDeliverAct, uRateMax); - } - - // issue -> issue - 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 - // XXX Currency must be in amount. - rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq + saPrvIssueReq, false); - } - else if (!bPrvAccount && bNxtAccount) - { - if (uIndex == uLast) - { - // offer --> ACCOUNT --> $ - Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: offer --> ACCOUNT --> $")); - - STAmount& saCurReceive = pspCur->saOutAct; - - // Amount to credit. - saCurReceive = saPrvDeliverAct; - - // No income balance adjustments necessary. The paying side inside the offer paid to this account. - } - else - { - // offer --> ACCOUNT --> account - Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: offer --> ACCOUNT --> account")); - - // deliver -> redeem - if (saPrvDeliverReq) // Previous wants to deliver. - { - // 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, 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. - } - } - else - { - // offer --> ACCOUNT --> offer - // deliver/redeem -> deliver/issue. - Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: offer --> ACCOUNT --> offer")); - - if (saPrvDeliverReq // Previous wants to deliver - && saCurIssueReq) // Current wants issue. - { - // Rate : 1.0 : transfer_rate - calcNodeRipple(QUALITY_ONE, 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. - } - - return terResult; -} - -// Return true, iff lhs has less priority than rhs. -bool PathState::lessPriority(const PathState::pointer& lhs, const PathState::pointer& rhs) -{ - if (lhs->uQuality != rhs->uQuality) - return lhs->uQuality > rhs->uQuality; // Bigger is worse. - - // Best quanity is second rank. - if (lhs->saOutAct != rhs->saOutAct) - return lhs->saOutAct < rhs->saOutAct; // Smaller is worse. - - // Path index is third rank. - return lhs->mIndex > rhs->mIndex; // Bigger is worse. -} - -// Make sure the path delivers to uAccountID: uCurrencyID from uIssuerID. -// -// Rules: -// - Currencies must be converted via an offer. -// - A node names it's output. -// - A ripple nodes output issuer must be the node's account or the next node's account. -// - Offers can only go directly to another offer if the currency and issuer are an exact match. -TER PathState::pushImply( - const uint160& uAccountID, // --> Delivering to this account. - const uint160& uCurrencyID, // --> Delivering this currency. - const uint160& uIssuerID) // --> Delivering this issuer. -{ - const PaymentNode& pnPrv = vpnNodes.back(); - TER terResult = tesSUCCESS; - - Log(lsINFO) << "pushImply> " - << NewcoinAddress::createHumanAccountID(uAccountID) - << " " << STAmount::createHumanCurrency(uCurrencyID) - << " " << NewcoinAddress::createHumanAccountID(uIssuerID); - - if (pnPrv.uCurrencyID != uCurrencyID) - { - // Currency is different, need to convert via an offer. - - terResult = pushNode( - STPathElement::typeCurrency // Offer. - | STPathElement::typeIssuer, - ACCOUNT_ONE, // Placeholder for offers. - uCurrencyID, // The offer's output is what is now wanted. - uIssuerID); - - } - - // For ripple, non-stamps, ensure the issuer is on at least one side of the transaction. - if (tesSUCCESS == terResult - && !!uCurrencyID // Not stamps. - && (pnPrv.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( - STPathElement::typeAccount, - uIssuerID, // Intermediate account is the needed issuer. - uCurrencyID, - uIssuerID); - } - - Log(lsINFO) << "pushImply< " << terResult; - - return terResult; -} - -// Append a node and insert before it any implied nodes. -// <-- terResult: tesSUCCESS, temBAD_PATH, terNO_LINE -TER PathState::pushNode( - const int iType, - const uint160& uAccountID, - const uint160& uCurrencyID, - const uint160& uIssuerID) -{ - Log(lsINFO) << "pushNode> " - << NewcoinAddress::createHumanAccountID(uAccountID) - << " " << STAmount::createHumanCurrency(uCurrencyID) - << "/" << NewcoinAddress::createHumanAccountID(uIssuerID); - PaymentNode pnCur; - const bool bFirst = vpnNodes.empty(); - const PaymentNode& pnPrv = bFirst ? PaymentNode() : vpnNodes.back(); - // true, iff node is a ripple account. false, iff node is an offer node. - const bool bAccount = isSetBit(iType, STPathElement::typeAccount); - // true, iff currency supplied. - // Currency is specified for the output of the current node. - const bool bCurrency = isSetBit(iType, STPathElement::typeCurrency); - // Issuer is specified for the output of the current node. - const bool bIssuer = isSetBit(iType, STPathElement::typeIssuer); - TER terResult = tesSUCCESS; - - pnCur.uFlags = iType; - - if (iType & ~STPathElement::typeValidBits) - { - Log(lsINFO) << "pushNode: bad bits."; - - terResult = temBAD_PATH; - } - else if (bAccount) - { - // Account link - - pnCur.uAccountID = uAccountID; - pnCur.uCurrencyID = bCurrency ? uCurrencyID : pnPrv.uCurrencyID; - pnCur.uIssuerID = bIssuer ? uIssuerID : uAccountID; - pnCur.saRevRedeem = STAmount(uCurrencyID, uAccountID); - pnCur.saRevIssue = STAmount(uCurrencyID, uAccountID); - - if (!bFirst) - { - // Add required intermediate nodes to deliver to current account. - terResult = pushImply( - pnCur.uAccountID, // Current account. - pnCur.uCurrencyID, // Wanted currency. - !!pnCur.uCurrencyID ? uAccountID : ACCOUNT_XNS); // Account as issuer. - } - - if (tesSUCCESS == terResult && !vpnNodes.empty()) - { - const PaymentNode& pnBck = vpnNodes.back(); - bool bBckAccount = isSetBit(pnBck.uFlags, STPathElement::typeAccount); - - if (bBckAccount) - { - SLE::pointer sleRippleState = mLedger->getSLE(Ledger::getRippleStateIndex(pnBck.uAccountID, pnCur.uAccountID, pnPrv.uCurrencyID)); - - if (!sleRippleState) - { - Log(lsINFO) << "pushNode: No credit line between " - << NewcoinAddress::createHumanAccountID(pnBck.uAccountID) - << " and " - << NewcoinAddress::createHumanAccountID(pnCur.uAccountID) - << " for " - << STAmount::createHumanCurrency(pnPrv.uCurrencyID) - << "." ; - - Log(lsINFO) << getJson(); - - terResult = terNO_LINE; - } - else - { - Log(lsINFO) << "pushNode: Credit line found between " - << NewcoinAddress::createHumanAccountID(pnBck.uAccountID) - << " and " - << NewcoinAddress::createHumanAccountID(pnCur.uAccountID) - << " for " - << STAmount::createHumanCurrency(pnPrv.uCurrencyID) - << "." ; - } - } - } - - if (tesSUCCESS == terResult) - vpnNodes.push_back(pnCur); - } - else - { - // Offer link - // Offers bridge a change in currency & issuer or just a change in issuer. - pnCur.uCurrencyID = bCurrency ? uCurrencyID : pnPrv.uCurrencyID; - pnCur.uIssuerID = bIssuer ? uIssuerID : pnCur.uAccountID; - pnCur.saRateMax = saZero; - - if (!!pnPrv.uAccountID) - { - // Previous is an account. - - // Insert intermediary issuer account if needed. - terResult = pushImply( - !!pnPrv.uCurrencyID - ? ACCOUNT_ONE // Rippling, but offer's don't have an account. - : ACCOUNT_XNS, - pnPrv.uCurrencyID, - pnPrv.uIssuerID); - } - - if (tesSUCCESS == terResult) - { - vpnNodes.push_back(pnCur); - } - } - Log(lsINFO) << "pushNode< " << terResult; - - return terResult; -} - -PathState::PathState( - Ledger::ref lpLedger, - const int iIndex, - const LedgerEntrySet& lesSource, - const STPath& spSourcePath, - const uint160& uReceiverID, - const uint160& uSenderID, - const STAmount& saSend, - const STAmount& saSendMax - ) - : mLedger(lpLedger), mIndex(iIndex), uQuality(0) -{ - const uint160 uInCurrencyID = saSendMax.getCurrency(); - const uint160 uOutCurrencyID = saSend.getCurrency(); - const uint160 uInIssuerID = !!uInCurrencyID ? saSendMax.getIssuer() : ACCOUNT_XNS; - const uint160 uOutIssuerID = !!uOutCurrencyID ? saSend.getIssuer() : ACCOUNT_XNS; - - lesEntries = lesSource.duplicate(); - - saInReq = saSendMax; - saOutReq = saSend; - - // Push sending node. - terStatus = pushNode( - STPathElement::typeAccount - | STPathElement::typeCurrency - | STPathElement::typeIssuer, - uSenderID, - uInCurrencyID, - uInIssuerID); - - BOOST_FOREACH(const STPathElement& speElement, spSourcePath) - { - if (tesSUCCESS == terStatus) - terStatus = pushNode(speElement.getNodeType(), speElement.getAccountID(), speElement.getCurrency(), speElement.getIssuerID()); - } - - if (tesSUCCESS == terStatus) - { - // Create receiver node. - - terStatus = pushImply(uReceiverID, uOutCurrencyID, uOutIssuerID); - if (tesSUCCESS == terStatus) - { - terStatus = pushNode( - STPathElement::typeAccount // Last node is always an account. - | STPathElement::typeCurrency - | STPathElement::typeIssuer, - uReceiverID, // Receive to output - uOutCurrencyID, // Desired currency - uOutIssuerID); - } - } - - if (tesSUCCESS == terStatus) - { - // Look for first mention of source in nodes and detect loops. - // Note: The output is not allowed to be a source. - - const unsigned int uNodes = vpnNodes.size(); - - for (unsigned int uIndex = 0; tesSUCCESS == terStatus && uIndex != uNodes; ++uIndex) - { - const PaymentNode& pnCur = vpnNodes[uIndex]; - - if (!!pnCur.uAccountID) - { - // Source is a ripple line - nothing(); - } - else if (!umForward.insert(std::make_pair(boost::make_tuple(pnCur.uAccountID, pnCur.uCurrencyID, pnCur.uIssuerID), uIndex)).second) - { - // Failed to insert. Have a loop. - Log(lsINFO) << boost::str(boost::format("PathState: loop detected: %s") - % getJson()); - - terStatus = temBAD_PATH_LOOP; - } - } - } - - Log(lsINFO) << boost::str(boost::format("PathState: in=%s/%s out=%s/%s %s") - % STAmount::createHumanCurrency(uInCurrencyID) - % NewcoinAddress::createHumanAccountID(uInIssuerID) - % STAmount::createHumanCurrency(uOutCurrencyID) - % NewcoinAddress::createHumanAccountID(uOutIssuerID) - % getJson()); -} - -Json::Value PathState::getJson() const -{ - Json::Value jvPathState(Json::objectValue); - Json::Value jvNodes(Json::arrayValue); - - BOOST_FOREACH(const PaymentNode& pnNode, vpnNodes) - { - Json::Value jvNode(Json::objectValue); - - Json::Value jvFlags(Json::arrayValue); - - if (pnNode.uFlags & STPathElement::typeAccount) - jvFlags.append("account"); - - jvNode["flags"] = jvFlags; - - if (pnNode.uFlags & STPathElement::typeAccount) - jvNode["account"] = NewcoinAddress::createHumanAccountID(pnNode.uAccountID); - - if (!!pnNode.uCurrencyID) - jvNode["currency"] = STAmount::createHumanCurrency(pnNode.uCurrencyID); - - if (!!pnNode.uIssuerID) - jvNode["issuer"] = NewcoinAddress::createHumanAccountID(pnNode.uIssuerID); - - // if (pnNode.saRevRedeem) - jvNode["rev_redeem"] = pnNode.saRevRedeem.getFullText(); - - // if (pnNode.saRevIssue) - jvNode["rev_issue"] = pnNode.saRevIssue.getFullText(); - - // if (pnNode.saRevDeliver) - jvNode["rev_deliver"] = pnNode.saRevDeliver.getFullText(); - - // if (pnNode.saFwdRedeem) - jvNode["fwd_redeem"] = pnNode.saFwdRedeem.getFullText(); - - // if (pnNode.saFwdIssue) - jvNode["fwd_issue"] = pnNode.saFwdIssue.getFullText(); - - // if (pnNode.saFwdDeliver) - jvNode["fwd_deliver"] = pnNode.saFwdDeliver.getFullText(); - - jvNodes.append(jvNode); - } - - jvPathState["status"] = terStatus; - jvPathState["index"] = mIndex; - jvPathState["nodes"] = jvNodes; - - if (saInReq) - jvPathState["in_req"] = saInReq.getJson(0); - - if (saInAct) - jvPathState["in_act"] = saInAct.getJson(0); - - if (saOutReq) - jvPathState["out_req"] = saOutReq.getJson(0); - - if (saOutAct) - jvPathState["out_act"] = saOutAct.getJson(0); - - if (uQuality) - jvPathState["uQuality"] = Json::Value::UInt(uQuality); - - return jvPathState; -} - -TER TransactionEngine::calcNodeFwd(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality) -{ - const PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; - const bool bCurAccount = isSetBit(pnCur.uFlags, STPathElement::typeAccount); - - Log(lsINFO) << boost::str(boost::format("calcNodeFwd> uIndex=%d") % uIndex); - - TER terResult = bCurAccount - ? calcNodeAccountFwd(uIndex, pspCur, bMultiQuality) - : calcNodeOfferFwd(uIndex, pspCur, bMultiQuality); - - if (tesSUCCESS == terResult && uIndex + 1 != pspCur->vpnNodes.size()) - { - terResult = calcNodeFwd(uIndex+1, pspCur, bMultiQuality); - } - - Log(lsINFO) << boost::str(boost::format("calcNodeFwd< uIndex=%d terResult=%d") % uIndex % 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 tepPATH_DRY -// <-> pnNodes: -// --> [end]saWanted.mAmount -// --> [all]saWanted.mCurrency -// --> [all]saAccount -// <-> [0]saWanted.mAmount : --> limit, <-- actual -TER TransactionEngine::calcNodeRev(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality) -{ - PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; - const bool bCurAccount = isSetBit(pnCur.uFlags, STPathElement::typeAccount); - TER terResult; - - // Do current node reverse. - const uint160& uCurIssuerID = pnCur.uIssuerID; - STAmount& saTransferRate = pnCur.saTransferRate; - - saTransferRate = STAmount::saFromRate(rippleTransferRate(uCurIssuerID)); - - Log(lsINFO) << boost::str(boost::format("calcNodeRev> uIndex=%d uIssuerID=%s saTransferRate=%s") - % uIndex - % NewcoinAddress::createHumanAccountID(uCurIssuerID) - % saTransferRate.getFullText()); - - terResult = bCurAccount - ? calcNodeAccountRev(uIndex, pspCur, bMultiQuality) - : calcNodeOfferRev(uIndex, pspCur, bMultiQuality); - - // Do previous. - if (tesSUCCESS != terResult) - { - // Error, don't continue. - nothing(); - } - else if (uIndex) - { - // Continue in reverse. - - terResult = calcNodeRev(uIndex-1, pspCur, bMultiQuality); - } - - Log(lsINFO) << boost::str(boost::format("calcNodeRev< uIndex=%d terResult=%s/%d") % uIndex % 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. -// <-- pspCur->uQuality -void TransactionEngine::pathNext(const PathState::pointer& pspCur, const int iPaths, const LedgerEntrySet& lesCheckpoint) -{ - // The next state is what is available in preference order. - // This is calculated when referenced accounts changed. - const bool bMultiQuality = iPaths == 1; - const unsigned int uLast = pspCur->vpnNodes.size() - 1; - - Log(lsINFO) << "Path In: " << pspCur->getJson(); - - assert(pspCur->vpnNodes.size() >= 2); - - pspCur->vUnfundedBecame.clear(); - pspCur->umReverse.clear(); - - mNodes = lesCheckpoint; // Restore from checkpoint. - mNodes.bumpSeq(); // Begin ledger varance. - - pspCur->terStatus = calcNodeRev(uLast, pspCur, bMultiQuality); - - Log(lsINFO) << "Path after reverse: " << pspCur->getJson(); - - if (tesSUCCESS == pspCur->terStatus) - { - // Do forward. - mNodes = lesCheckpoint; // Restore from checkpoint. - mNodes.bumpSeq(); // Begin ledger varance. - - pspCur->terStatus = calcNodeFwd(0, pspCur, bMultiQuality); - - pspCur->uQuality = tesSUCCESS == pspCur->terStatus - ? STAmount::getRate(pspCur->saOutAct, pspCur->saInAct) // Calculate relative quality. - : 0; // Mark path as inactive. - - Log(lsINFO) << "Path after forward: " << pspCur->getJson(); - } -} // XXX Need to audit for things like setting accountID not having memory. -TER TransactionEngine::doPayment(const SerializedTransaction& txn) +TER TransactionEngine::doPayment(const SerializedTransaction& txn, const TransactionEngineParams params) { // Ripple if source or destination is non-native or if there are paths. const uint32 uTxFlags = txn.getFlags(); @@ -3184,12 +1023,37 @@ TER TransactionEngine::doPayment(const SerializedTransaction& txn) entryModify(sleDst); } + TER terResult; // XXX Should bMax be sufficient to imply ripple? - const bool bRipple = bPaths || bMax || !saDstAmount.isNative(); + const bool bRipple = bPaths || bMax || !saDstAmount.isNative(); - if (!bRipple) + if (bRipple) + { + // Ripple payment + + STPathSet spsPaths = txn.getITFieldPathSet(sfPaths); + STAmount saMaxAmountAct; + STAmount saDstAmountAct; + + terResult = isSetBit(params, tapOPEN_LEDGER) && spsPaths.getPathCount() > RIPPLE_PATHS_MAX + ? telBAD_PATH_COUNT + : RippleCalc::rippleCalc( + mNodes, + saMaxAmountAct, + saDstAmountAct, + saMaxAmount, + saDstAmount, + uDstAccountID, + mTxnAccountID, + spsPaths, + bPartialPayment, + bLimitQuality, + bNoRippleDirect); + } + else { // Direct XNS payment. + STAmount saSrcXNSBalance = mTxnAccount->getIValueFieldAmount(sfBalance); if (saSrcXNSBalance < saDstAmount) @@ -3197,202 +1061,17 @@ TER TransactionEngine::doPayment(const SerializedTransaction& txn) // Transaction might succeed, if applied in a different order. Log(lsINFO) << "doPayment: Delay transaction: Insufficent funds."; - return terUNFUNDED; - } - - mTxnAccount->setIFieldAmount(sfBalance, saSrcXNSBalance - saDstAmount); - sleDst->setIFieldAmount(sfBalance, sleDst->getIValueFieldAmount(sfBalance) + saDstAmount); - - return tesSUCCESS; - } - - // - // Ripple payment - // - - STPathSet spsPaths = txn.getITFieldPathSet(sfPaths); - - if (bNoRippleDirect && spsPaths.isEmpty()) - { - Log(lsINFO) << "doPayment: Invalid transaction: No paths and direct ripple not allowed."; - - return temRIPPLE_EMPTY; - } - - // XXX Skip check if final processing. - if (spsPaths.getPathCount() > RIPPLE_PATHS_MAX) - { - return telBAD_PATH_COUNT; - } - - // Incrementally search paths. - std::vector vpsPaths; - - TER terResult = temUNCERTAIN; - - if (!bNoRippleDirect) - { - // Direct path. - // XXX Might also make a stamp bridge by default. - Log(lsINFO) << "doPayment: Build direct:"; - - PathState::pointer pspDirect = PathState::createPathState( - mLedger, - vpsPaths.size(), - mNodes, - STPath(), - uDstAccountID, - mTxnAccountID, - saDstAmount, - saMaxAmount); - - if (pspDirect) - { - // Return if malformed. - if (pspDirect->terStatus >= temMALFORMED && pspDirect->terStatus < tefFAILURE) - return pspDirect->terStatus; - - if (tesSUCCESS == pspDirect->terStatus) - { - // Had a success. - terResult = tesSUCCESS; - - vpsPaths.push_back(pspDirect); - } - } - } - - Log(lsINFO) << "doPayment: Paths in set: " << spsPaths.getPathCount(); - - BOOST_FOREACH(const STPath& spPath, spsPaths) - { - Log(lsINFO) << "doPayment: Build path:"; - - PathState::pointer pspExpanded = PathState::createPathState( - mLedger, - vpsPaths.size(), - mNodes, - spPath, - uDstAccountID, - mTxnAccountID, - saDstAmount, - saMaxAmount); - - if (pspExpanded) - { - // Return if malformed. - if (pspExpanded->terStatus >= temMALFORMED && pspExpanded->terStatus < tefFAILURE) - return pspExpanded->terStatus; - - if (tesSUCCESS == pspExpanded->terStatus) - { - // Had a success. - terResult = tesSUCCESS; - } - - vpsPaths.push_back(pspExpanded); - } - } - - if (vpsPaths.empty()) - { - return tefEXCEPTION; - } - else if (tesSUCCESS != terResult) - { - // No path successes. - - return vpsPaths[0]->terStatus; - } - else - { - terResult = temUNCERTAIN; - } - - STAmount saPaid; - STAmount saWanted; - const LedgerEntrySet lesBase = mNodes; // Checkpoint with just fees paid. - const uint64 uQualityLimit = bLimitQuality ? STAmount::getRate(saDstAmount, saMaxAmount) : 0; - - while (temUNCERTAIN == terResult) - { - PathState::pointer pspBest; - const LedgerEntrySet lesCheckpoint = mNodes; - - // Find the best path. - BOOST_FOREACH(PathState::pointer& pspCur, vpsPaths) - { - pathNext(pspCur, vpsPaths.size(), lesCheckpoint); // Compute increment. - - if ((!bLimitQuality || pspCur->uQuality <= uQualityLimit) // Quality is not limted or increment has allowed quality. - || !pspBest // Best is not yet set. - || (pspCur->uQuality && PathState::lessPriority(pspBest, pspCur))) // Current is better than set. - { - mNodes.swapWith(pspCur->lesEntries); // For the path, save ledger state. - pspBest = pspCur; - } - } - - if (pspBest) - { - // Apply best path. - - // Record best pass' offers that became unfunded for deletion on success. - mvUnfundedBecame.insert(mvUnfundedBecame.end(), pspBest->vUnfundedBecame.begin(), pspBest->vUnfundedBecame.end()); - - // Record best pass' LedgerEntrySet to build off of and potentially return. - mNodes.swapWith(pspBest->lesEntries); - - // Figure out if done. - if (temUNCERTAIN == terResult && saPaid == saWanted) - { - terResult = tesSUCCESS; - } - else - { - // Prepare for next pass. - - // Merge best pass' umReverse. - mumSource.insert(pspBest->umReverse.begin(), pspBest->umReverse.end()); - } - } - // Not done and ran out of paths. - else if (!bPartialPayment) - { - // Partial payment not allowed. - terResult = tepPATH_PARTIAL; - mNodes = lesBase; // Revert to just fees charged. - } - // Partial payment ok. - else if (!saPaid) - { - // No payment at all. - terResult = tepPATH_DRY; - mNodes = lesBase; // Revert to just fees charged. + terResult = terUNFUNDED; } else { + mTxnAccount->setIFieldAmount(sfBalance, saSrcXNSBalance - saDstAmount); + sleDst->setIFieldAmount(sfBalance, sleDst->getIValueFieldAmount(sfBalance) + saDstAmount); + terResult = tesSUCCESS; } } - if (tesSUCCESS == terResult) - { - // Delete became unfunded offers. - BOOST_FOREACH(const uint256& uOfferIndex, mvUnfundedBecame) - { - if (tesSUCCESS == terResult) - terResult = offerDelete(uOfferIndex); - } - } - - // Delete found unfunded offers. - BOOST_FOREACH(const uint256& uOfferIndex, musUnfundedFound) - { - if (tesSUCCESS == terResult) - terResult = offerDelete(uOfferIndex); - } - std::string strToken; std::string strHuman; @@ -3580,8 +1259,8 @@ TER TransactionEngine::takeOffers( Log(lsINFO) << "takeOffers: saOfferPays=" << saOfferPays.getFullText(); - STAmount saOfferFunds = accountFunds(uOfferOwnerID, saOfferPays); - STAmount saTakerFunds = accountFunds(uTakerAccountID, saTakerPays); + STAmount saOfferFunds = mNodes.accountFunds(uOfferOwnerID, saOfferPays); + STAmount saTakerFunds = mNodes.accountFunds(uTakerAccountID, saTakerPays); SLE::pointer sleOfferAccount; // Owner of offer. if (!saOfferFunds.isPositive()) @@ -3660,14 +1339,14 @@ TER TransactionEngine::takeOffers( // Offer owner pays taker. saSubTakerGot.setIssuer(uTakerGetsAccountID); // XXX Move this earlier? - accountSend(uOfferOwnerID, uTakerAccountID, saSubTakerGot); + mNodes.accountSend(uOfferOwnerID, uTakerAccountID, saSubTakerGot); saTakerGot += saSubTakerGot; // Taker pays offer owner. saSubTakerPaid.setIssuer(uTakerPaysAccountID); - accountSend(uTakerAccountID, uOfferOwnerID, saSubTakerPaid); + mNodes.accountSend(uTakerAccountID, uOfferOwnerID, saSubTakerPaid); saTakerPaid += saSubTakerPaid; } @@ -3680,7 +1359,7 @@ TER TransactionEngine::takeOffers( { BOOST_FOREACH(const uint256& uOfferIndex, usOfferUnfundedFound) { - terResult = offerDelete(uOfferIndex); + terResult = mNodes.offerDelete(uOfferIndex); if (tesSUCCESS != terResult) break; } @@ -3691,7 +1370,7 @@ TER TransactionEngine::takeOffers( // On success, delete offers that became unfunded. BOOST_FOREACH(const uint256& uOfferIndex, usOfferUnfundedBecame) { - terResult = offerDelete(uOfferIndex); + terResult = mNodes.offerDelete(uOfferIndex); if (tesSUCCESS != terResult) break; } @@ -3766,7 +1445,7 @@ Log(lsWARNING) << "doOfferCreate: saTakerGets=" << saTakerGets.getFullText(); terResult = temBAD_ISSUER; } - else if (!accountFunds(mTxnAccountID, saTakerGets).isPositive()) + else if (!mNodes.accountFunds(mTxnAccountID, saTakerGets).isPositive()) { Log(lsWARNING) << "doOfferCreate: delay: Offers must be at least partially funded."; @@ -3827,7 +1506,7 @@ Log(lsWARNING) << "doOfferCreate: saTakerGets=" << saTakerGets.getFullText(); Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerGets=" << saTakerGets.getFullText(); Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerGets=" << NewcoinAddress::createHumanAccountID(saTakerGets.getIssuer()); Log(lsWARNING) << "doOfferCreate: takeOffers: mTxnAccountID=" << NewcoinAddress::createHumanAccountID(mTxnAccountID); - Log(lsWARNING) << "doOfferCreate: takeOffers: funds=" << accountFunds(mTxnAccountID, saTakerGets).getFullText(); + Log(lsWARNING) << "doOfferCreate: takeOffers: funds=" << mNodes.accountFunds(mTxnAccountID, saTakerGets).getFullText(); // Log(lsWARNING) << "doOfferCreate: takeOffers: uPaysIssuerID=" << NewcoinAddress::createHumanAccountID(uPaysIssuerID); // Log(lsWARNING) << "doOfferCreate: takeOffers: uGetsIssuerID=" << NewcoinAddress::createHumanAccountID(uGetsIssuerID); @@ -3835,7 +1514,7 @@ Log(lsWARNING) << "doOfferCreate: saTakerGets=" << saTakerGets.getFullText(); if (tesSUCCESS == terResult && saTakerPays // Still wanting something. && saTakerGets // Still offering something. - && accountFunds(mTxnAccountID, saTakerGets).isPositive()) // Still funded. + && mNodes.accountFunds(mTxnAccountID, saTakerGets).isPositive()) // Still funded. { // We need to place the remainder of the offer into its order book. @@ -3898,7 +1577,7 @@ TER TransactionEngine::doOfferCancel(const SerializedTransaction& txn) { Log(lsWARNING) << "doOfferCancel: uSequence=" << uSequence; - terResult = offerDelete(sleOffer, uOfferIndex, mTxnAccountID); + terResult = mNodes.offerDelete(sleOffer, uOfferIndex, mTxnAccountID); } else { @@ -3913,406 +1592,6 @@ TER TransactionEngine::doOfferCancel(const SerializedTransaction& txn) return terResult; } -#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(PaymentNode& pnSrc, PaymentNode& pnDst, bool bAllowPartial) -{ - TER terResult; - - if (pnDst.saWanted.isNative()) - { - // Transfer stamps. - - 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( - const uint256& uBookRoot, // --> Which order book to look in. - const uint256& 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->getIValueFieldAccount(sfAccount).getAccountID(); - STAmount saOfferPays = sleOffer->getIValueFieldAmount(sfTakerGets); - STAmount saOfferGets = sleOffer->getIValueFieldAmount(sfTakerPays); - - if (sleOffer->getIFieldPresent(sfExpiration) && sleOffer->getIFieldU32(sfExpiration) <= mLedger->getParentCloseTimeNC()) - { - // Offer is expired. - Log(lsINFO) << "calcOfferFirst: encountered expired offer"; - } - 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 stamps. - - 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. - Log(lsINFO) << "calcOfferFirst: offer unfunded: delete"; - } - else if (saOfferFunds >= saOfferPays) - { - // Offer fully funded. - - // Account transfering 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 stamps, then also calculates vs stamp 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 reedem. -// 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 offer's limbo - const uint160& uPrvCurrencyID, - const uint160& uPrvIssuerID, - const uint160& uCurCurrencyID, - const uint160& uCurIssuerID, - - const STAmount& uPrvRedeemReq, // --> In limit. - STAmount& uPrvRedeemAct, // <-> In limit achived. - const STAmount& uCurRedeemReq, // --> Out limit. Driver when uCurIssuerID == uNxtIssuerID (offer would redeem to next) - STAmount& uCurRedeemAct, // <-> Out limit achived. - - const STAmount& uCurIssueReq, // --> In limit. - STAmount& uCurIssueAct, // <-> In limit achived. - const STAmount& uCurIssueReq, // --> Out limit. Driver when uCurIssueReq != uNxtIssuerID (offer would effectively issue or transfer to next) - STAmount& uCurIssueAct, // <-> Out limit achived. - - STAmount& saPay, - STAmount& saGot - ) const -{ - TER terResult = temUNKNOWN; - - // Direct: not bridging via XNS - 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 XNS - 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 XNS. - uInTip = Ledger::getBookBase(uPrvCurrencyID, uPrvIssuerID, CURRENCY_XNS, ACCOUNT_XNS); - uInEnd = Ledger::getQualityNext(uInTip); - uOutTip = Ledger::getBookBase(CURRENCY_XNS, ACCOUNT_XNS, 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 transfering, 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 offer's 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 - - TER TransactionEngine::doContractAdd(const SerializedTransaction& txn) { Log(lsWARNING) << "doContractAdd> " << txn.getJson(0); diff --git a/src/TransactionEngine.h b/src/TransactionEngine.h index b57b76b89e..70d464b7ae 100644 --- a/src/TransactionEngine.h +++ b/src/TransactionEngine.h @@ -1,8 +1,6 @@ #ifndef __TRANSACTIONENGINE__ #define __TRANSACTIONENGINE__ -#include -#include #include #include @@ -29,127 +27,6 @@ enum TransactionEngineParams // Transaction can be retried, soft failures allowed }; -class PaymentNode { -protected: - friend class TransactionEngine; - friend class PathState; - - uint16 uFlags; // --> From path. - - uint160 uAccountID; // --> Accounts: Recieving/sending account. - uint160 uCurrencyID; // --> Accounts: Receive and send, Offers: send. - // --- For offer's next has currency out. - uint160 uIssuerID; // --> Currency's issuer - - STAmount saTransferRate; // Transfer rate for uIssuerID. - - // Computed by Reverse. - STAmount saRevRedeem; // <-- Amount to redeem to next. - STAmount saRevIssue; // <-- Amount to issue to next limited by credit and outstanding IOUs. - // Issue isn't used by offers. - STAmount saRevDeliver; // <-- Amount to deliver to next regardless of fee. - - // Computed by forward. - STAmount saFwdRedeem; // <-- Amount node will redeem to next. - STAmount saFwdIssue; // <-- Amount node will issue to next. - // Issue isn't used by offers. - STAmount saFwdDeliver; // <-- Amount to deliver to next regardless of fee. - - // For offers: - - STAmount saRateMax; // XXX Should rate be sticky for forward too? - - // Directory - uint256 uDirectTip; // Current directory. - uint256 uDirectEnd; // Next order book. - bool bDirectAdvance; // Need to advance directory. - SLE::pointer sleDirectDir; - STAmount saOfrRate; // For correct ratio. - - // Node - bool bEntryAdvance; // Need to advance entry. - unsigned int uEntry; - uint256 uOfferIndex; - SLE::pointer sleOffer; - uint160 uOfrOwnerID; - bool bFundsDirty; // Need to refresh saOfferFunds, saTakerPays, & saTakerGets. - STAmount saOfferFunds; - STAmount saTakerPays; - STAmount saTakerGets; -}; - -// account id, currency id, issuer id :: node -typedef boost::tuple aciSource; -typedef boost::unordered_map curIssuerNode; // Map of currency, issuer to node index. -typedef boost::unordered_map::const_iterator curIssuerNodeConstIterator; - -extern std::size_t hash_value(const aciSource& asValue); - -// Holds a path state under incremental application. -class PathState -{ -protected: - Ledger::pointer mLedger; - - TER pushNode(const int iType, const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID); - TER pushImply(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID); - -public: - typedef boost::shared_ptr pointer; - - TER terStatus; - std::vector vpnNodes; - - // When processing, don't want to complicate directory walking with deletion. - std::vector vUnfundedBecame; // Offers that became unfunded or were completely consumed. - - // 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. - - // 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. - - LedgerEntrySet lesEntries; - - int mIndex; - uint64 uQuality; // 0 = none. - STAmount saInReq; // Max amount to spend by sender - STAmount saInAct; // Amount spent by sender (calc output) - STAmount saOutReq; // Amount to send (calc input) - STAmount saOutAct; // Amount actually sent (calc output). - - PathState( - Ledger::ref lpLedger, - const int iIndex, - const LedgerEntrySet& lesSource, - const STPath& spSourcePath, - const uint160& uReceiverID, - const uint160& uSenderID, - const STAmount& saSend, - const STAmount& saSendMax - ); - - Json::Value getJson() const; - - static PathState::pointer createPathState( - Ledger::ref lpLedger, - const int iIndex, - const LedgerEntrySet& lesSource, - const STPath& spSourcePath, - const uint160& uReceiverID, - const uint160& uSenderID, - const STAmount& saSend, - const STAmount& saSendMax - ) - { - return boost::make_shared(lpLedger, iIndex, lesSource, spSourcePath, uReceiverID, uSenderID, saSend, saSendMax); - } - - static bool lessPriority(const PathState::pointer& lhs, const PathState::pointer& rhs); -}; - // One instance per ledger. // Only one transaction applied at a time. class TransactionEngine @@ -175,72 +52,11 @@ protected: uint160 mTxnAccountID; SLE::pointer mTxnAccount; - // First time working in reverse a funding source was mentioned. Source may only be used there. - curIssuerNode mumSource; // Map of currency, issuer to node index. - - // When processing, don't want to complicate directory walking with deletion. - std::vector mvUnfundedBecame; // Offers that became unfunded. - - // If the transaction fails to meet some constraint, still need to delete unfunded offers. - boost::unordered_set musUnfundedFound; // Offers that were found unfunded. - SLE::pointer entryCreate(LedgerEntryType type, const uint256& index) { return mNodes.entryCreate(type, index); } SLE::pointer entryCache(LedgerEntryType type, const uint256& index) { return mNodes.entryCache(type, index); } void entryDelete(SLE::ref sleEntry) { mNodes.entryDelete(sleEntry); } void entryModify(SLE::ref sleEntry) { mNodes.entryModify(sleEntry); } - TER offerDelete(const uint256& uOfferIndex); - TER offerDelete(const SLE::pointer& sleOffer, const uint256& uOfferIndex, const uint160& uOwnerID); - - uint32 rippleTransferRate(const uint160& uIssuerID); - STAmount rippleOwed(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID); - STAmount rippleLimit(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID); - uint32 rippleQualityIn(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID, const SOE_Field sfLow=sfLowQualityIn, const SOE_Field sfHigh=sfHighQualityIn); - uint32 rippleQualityOut(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID) - { return rippleQualityIn(uToAccountID, uFromAccountID, uCurrencyID, sfLowQualityOut, sfHighQualityOut); } - - STAmount rippleHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID); - STAmount rippleTransferFee(const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID, const STAmount& saAmount); - void rippleCredit(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount, bool bCheckIssuer=true); - STAmount rippleSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount); - - STAmount accountHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID); - void accountSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount); - STAmount accountFunds(const uint160& uAccountID, const STAmount& saDefault); - - PathState::pointer pathCreate(const STPath& spPath); - void pathNext(const PathState::pointer& pspCur, const int iPaths, const LedgerEntrySet& lesCheckpoint); - TER calcNode(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); - TER calcNodeRev(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); - TER calcNodeFwd(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); - TER calcNodeOfferRev(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); - TER calcNodeOfferFwd(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); - TER calcNodeAccountRev(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); - TER calcNodeAccountFwd(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); - TER calcNodeAdvance(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality, const bool bReverse); - TER calcNodeDeliverRev( - const unsigned int uIndex, - const PathState::pointer& pspCur, - const bool bMultiQuality, - const uint160& uOutAccountID, - const STAmount& saOutReq, - STAmount& saOutAct); - - TER calcNodeDeliverFwd( - const unsigned int uIndex, - const PathState::pointer& pspCur, - const bool bMultiQuality, - const uint160& uInAccountID, - const STAmount& saInFunds, - const STAmount& saInReq, - STAmount& saInAct, - STAmount& saInFees); - - void calcNodeRipple(const uint32 uQualityIn, const uint32 uQualityOut, - const STAmount& saPrvReq, const STAmount& saCurReq, - STAmount& saPrvAct, STAmount& saCurAct, - uint64& uRateMax); - void txnWrite(); TER doAccountSet(const SerializedTransaction& txn); @@ -252,7 +68,7 @@ protected: TER doNicknameSet(const SerializedTransaction& txn); TER doPasswordFund(const SerializedTransaction& txn); TER doPasswordSet(const SerializedTransaction& txn); - TER doPayment(const SerializedTransaction& txn); + TER doPayment(const SerializedTransaction& txn, const TransactionEngineParams params); TER doWalletAdd(const SerializedTransaction& txn); TER doContractAdd(const SerializedTransaction& txn); TER doContractRemove(const SerializedTransaction& txn);