From fde853d3884d0097282098ad95f92f1a0e65a6bf Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Sat, 28 Jul 2012 17:35:51 -0700 Subject: [PATCH 1/7] Revise STPathSet. --- src/SerializedTypes.cpp | 122 ++++++++++++++++++++++++---------------- src/SerializedTypes.h | 83 +++++++++++++++++++++------ 2 files changed, 142 insertions(+), 63 deletions(-) diff --git a/src/SerializedTypes.cpp b/src/SerializedTypes.cpp index bd76f5c84..fbd861c44 100644 --- a/src/SerializedTypes.cpp +++ b/src/SerializedTypes.cpp @@ -294,34 +294,54 @@ STPathSet* STPathSet::construct(SerializerIterator& s, const char *name) { std::vector paths; std::vector path; + do { - switch(s.get8()) + int iType = s.get8(); + + if (iType == STPathElement::typeEnd || iType == STPathElement::typeBoundary) { - case STPathElement::typeEnd: - if (path.empty()) - { - if (!paths.empty()) - throw std::runtime_error("empty last path"); - } - else paths.push_back(path); + if (path.empty()) + throw std::runtime_error("empty path"); + + paths.push_back(path); + path.clear(); + + if (iType == STPathElement::typeEnd) + { return new STPathSet(name, paths); + } + } + else if (iType & STPathElement::typeStrayBits) + { + throw std::runtime_error("bad path element"); + } + else + { + bool bAccount = !!(iType & STPathElement::typeAccount); + bool bOffer = !!(iType & STPathElement::typeOffer); + bool bCurrency = !!(iType & STPathElement::typeCurrency); + bool bIssuer = !!(iType & STPathElement::typeIssuer); - case STPathElement::typeBoundary: - if (path.empty()) - throw std::runtime_error("empty path"); - paths.push_back(path); - path.clear(); + if (!bAccount && !bOffer) + { + throw std::runtime_error("bad path element"); + } - case STPathElement::typeAccount: - path.push_back(STPathElement(STPathElement::typeAccount, s.get160())); - break; + uint160 uAccountID; + uint160 uCurrency; + uint160 uIssuerID; - case STPathElement::typeOffer: - path.push_back(STPathElement(STPathElement::typeOffer, s.get160())); - break; + if (bAccount) + uAccountID = s.get160(); - default: throw std::runtime_error("Unknown path element"); + if (bCurrency) + uCurrency = s.get160(); + + if (bIssuer) + uIssuerID = s.get160(); + + path.push_back(STPathElement(uAccountID, uCurrency, uIssuerID)); } } while(1); } @@ -329,8 +349,10 @@ STPathSet* STPathSet::construct(SerializerIterator& s, const char *name) int STPathSet::getLength() const { int ret = 0; + for (std::vector::const_iterator it = value.begin(), end = value.end(); it != end; ++it) ret += it->getSerializeSize(); + return (ret != 0) ? ret : (ret + 1); } @@ -340,30 +362,22 @@ Json::Value STPath::getJson(int) const BOOST_FOREACH(std::vector::const_iterator::value_type it, mPath) { - switch (it.getNodeType()) - { - case STPathElement::typeAccount: - { - Json::Value elem(Json::objectValue); + Json::Value elem(Json::objectValue); + int iType = it.getNodeType(); - elem["account"] = NewcoinAddress::createHumanAccountID(it.getNode()); + elem["type"] = it.getNodeType(); + elem["type_hex"] = strHex(it.getNodeType()); - ret.append(elem); - break; - } + if (iType & STPathElement::typeAccount) + elem["account"] = NewcoinAddress::createHumanAccountID(it.getAccountID()); - case STPathElement::typeOffer: - { - Json::Value elem(Json::objectValue); + if (iType & STPathElement::typeCurrency) + elem["currency"] = STAmount::createHumanCurrency(it.getCurrency()); - elem["offer"] = it.getNode().GetHex(); + if (iType & STPathElement::typeIssuer) + elem["issuer"] = NewcoinAddress::createHumanAccountID(it.getIssuerID()); - ret.append(elem); - break; - } - - default: throw std::runtime_error("Unknown path element"); - } + ret.append(elem); } return ret; @@ -379,12 +393,13 @@ Json::Value STPathSet::getJson(int options) const return ret; } +#if 0 std::string STPath::getText() const { std::string ret("["); bool first = true; - BOOST_FOREACH(std::vector::const_iterator::value_type it, mPath) + BOOST_FOREACH(const STPathElement& it, mPath) { if (!first) ret += ", "; switch (it.getNodeType()) @@ -410,7 +425,9 @@ std::string STPath::getText() const return ret + "]"; } +#endif +#if 0 std::string STPathSet::getText() const { std::string ret("{"); @@ -427,23 +444,34 @@ std::string STPathSet::getText() const } return ret + "}"; } +#endif void STPathSet::add(Serializer& s) const { - bool firstPath = true; + bool bFirst = true; - BOOST_FOREACH(std::vector::const_iterator::value_type pit, value) + BOOST_FOREACH(const STPath& spPath, value) { - if (!firstPath) + if (!bFirst) { s.add8(STPathElement::typeBoundary); - firstPath = false; + bFirst = false; } - for (std::vector::const_iterator eit = pit.begin(), eend = pit.end(); eit != eend; ++eit) + BOOST_FOREACH(const STPathElement& speElement, spPath) { - s.add8(eit->getNodeType()); - s.add160(eit->getNode()); + int iType = speElement.getNodeType(); + + s.add8(iType); + + if (iType && STPathElement::typeAccount) + s.add160(speElement.getAccountID()); + + if (iType && STPathElement::typeCurrency) + s.add160(speElement.getCurrency()); + + if (iType && STPathElement::typeIssuer) + s.add160(speElement.getIssuerID()); } } s.add8(STPathElement::typeEnd); diff --git a/src/SerializedTypes.h b/src/SerializedTypes.h index b1ce80eed..89c3a89dd 100644 --- a/src/SerializedTypes.h +++ b/src/SerializedTypes.h @@ -518,26 +518,40 @@ public: class STPathElement { public: - static const int typeEnd = 0x00; - static const int typeAccount = 0x01; // Rippling through an account - static const int typeOffer = 0x02; // Claiming an offer - static const int typeBoundary = 0xFF; // boundary between alternate paths + enum { + typeEnd = 0x00, + typeAccount = 0x01, // Rippling through an account + typeOffer = 0x02, // Claiming an offer + typeCurrency = 0x10, // Currency follows + typeIssuer = 0x20, // Issuer follows + typeBoundary = 0xFF, // boundary between alternate paths + typeStrayBits = 0xCC, // Bits that must be zero. + }; protected: - int mType; - uint160 mNode; + int mType; + uint160 mAccountID; + uint160 mCurrency; + uint160 mIssuerID; public: - STPathElement(int type, const uint160& node) : mType(type), mNode(node) { ; } - int getNodeType() const { return mType; } - bool isAccount() const { return mType == typeAccount; } - bool isOffer() const { return mType == typeOffer; } + STPathElement(const uint160& uAccountID, const uint160& uCurrency, const uint160& uIssuerID) + : mAccountID(uAccountID), mCurrency(uCurrency), mIssuerID(uIssuerID) + { + mType = + (uAccountID.isZero() ? STPathElement::typeOffer : STPathElement::typeAccount) + | (uCurrency.isZero() ? 0 : STPathElement::typeCurrency) + | (uIssuerID.isZero() ? 0 : STPathElement::typeIssuer); + } + + int getNodeType() const { return mType; } + bool isOffer() const { return mAccountID.isZero(); } + bool isAccount() const { return !isOffer(); } // Nodes are either an account ID or a offer prefix. Offer prefixs denote a class of offers. - const uint160& getNode() const { return mNode; } - - void setType(int type) { mType = type; } - void setNode(const uint160& n) { mNode = n; } + const uint160& getAccountID() const { return mAccountID; } + const uint160& getCurrency() const { return mCurrency; } + const uint160& getIssuerID() const { return mIssuerID; } }; class STPath @@ -556,12 +570,49 @@ public: void addElement(const STPathElement& e) { mPath.push_back(e); } void clear() { mPath.clear(); } int getSerializeSize() const { return 1 + mPath.size() * 21; } - std::string getText() const; +// std::string getText() const; Json::Value getJson(int) const; + + std::vector::iterator begin() { return mPath.begin(); } + std::vector::iterator end() { return mPath.end(); } std::vector::const_iterator begin() const { return mPath.begin(); } std::vector::const_iterator end() const { return mPath.end(); } }; +inline std::vector::iterator range_begin(STPath & x) +{ + return x.begin(); +} + +inline std::vector::iterator range_end(STPath & x) +{ + return x.end(); +} + +inline std::vector::const_iterator range_begin(const STPath& x) +{ + return x.begin(); +} + +inline std::vector::const_iterator range_end(const STPath& x) +{ + return x.end(); +} + +namespace boost +{ + template<> + struct range_mutable_iterator< STPath > + { + typedef std::vector::iterator type; + }; + + template<> + struct range_const_iterator< STPath > + { + typedef std::vector::const_iterator type; + }; +} class STPathSet : public SerializedType { // A set of zero or more payment paths protected: @@ -580,7 +631,7 @@ public: { return std::auto_ptr(construct(sit, name)); } int getLength() const; - std::string getText() const; +// std::string getText() const; void add(Serializer& s) const; virtual Json::Value getJson(int) const; From 4f8ada17c710ee812cb53e180f7527dfb0bbdb0a Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Sat, 28 Jul 2012 17:36:08 -0700 Subject: [PATCH 2/7] Work towards ripple. --- src/LedgerEntrySet.cpp | 7 +- src/LedgerEntrySet.h | 6 +- src/SerializedTypes.cpp | 6 +- src/TransactionEngine.cpp | 144 +++++++++++++++++++++++++------------- src/TransactionEngine.h | 77 +++++++++++++------- 5 files changed, 162 insertions(+), 78 deletions(-) diff --git a/src/LedgerEntrySet.cpp b/src/LedgerEntrySet.cpp index e325b47de..ad65ddd9f 100644 --- a/src/LedgerEntrySet.cpp +++ b/src/LedgerEntrySet.cpp @@ -15,7 +15,7 @@ void LedgerEntrySet::clear() mSet.clear(); } -LedgerEntrySet LedgerEntrySet::duplicate() +LedgerEntrySet LedgerEntrySet::duplicate() const { return LedgerEntrySet(mEntries, mSet, mSeq + 1); } @@ -180,4 +180,9 @@ void LedgerEntrySet::entryDelete(SLE::pointer& sle) } } +bool LedgerEntrySet::intersect(const LedgerEntrySet& lesLeft, const LedgerEntrySet& lesRight) +{ + return true; // XXX Needs implementation +} + // vim:ts=4 diff --git a/src/LedgerEntrySet.h b/src/LedgerEntrySet.h index 01312e0b5..885281a01 100644 --- a/src/LedgerEntrySet.h +++ b/src/LedgerEntrySet.h @@ -34,14 +34,14 @@ protected: TransactionMetaSet mSet; int mSeq; - LedgerEntrySet(const boost::unordered_map &e, TransactionMetaSet& s, int m) : + LedgerEntrySet(const boost::unordered_map &e, const TransactionMetaSet& s, int m) : mEntries(e), mSet(s), mSeq(m) { ; } public: LedgerEntrySet() : mSeq(0) { ; } // set functions - LedgerEntrySet duplicate(); // Make a duplicate of this set + LedgerEntrySet duplicate() const; // Make a duplicate of this set void setTo(LedgerEntrySet&); // Set this set to have the same contents as another void swapWith(LedgerEntrySet&); // Swap the contents of two sets @@ -64,6 +64,8 @@ public: boost::unordered_map::const_iterator end() const { return mEntries.end(); } boost::unordered_map::iterator begin() { return mEntries.begin(); } boost::unordered_map::iterator end() { return mEntries.end(); } + + static bool intersect(const LedgerEntrySet& lesLeft, const LedgerEntrySet& lesRight); }; #endif diff --git a/src/SerializedTypes.cpp b/src/SerializedTypes.cpp index fbd861c44..a341225d1 100644 --- a/src/SerializedTypes.cpp +++ b/src/SerializedTypes.cpp @@ -464,13 +464,13 @@ void STPathSet::add(Serializer& s) const s.add8(iType); - if (iType && STPathElement::typeAccount) + if (iType & STPathElement::typeAccount) s.add160(speElement.getAccountID()); - if (iType && STPathElement::typeCurrency) + if (iType & STPathElement::typeCurrency) s.add160(speElement.getCurrency()); - if (iType && STPathElement::typeIssuer) + if (iType & STPathElement::typeIssuer) s.add160(speElement.getIssuerID()); } } diff --git a/src/TransactionEngine.cpp b/src/TransactionEngine.cpp index 69dde7cb9..5fb1fc94d 100644 --- a/src/TransactionEngine.cpp +++ b/src/TransactionEngine.cpp @@ -2161,51 +2161,67 @@ bool calcPaymentForward(std::vector& pnNodes) } #endif -// Ensure sort order is complelely deterministic. -class PathStateCompare +bool PathState::less(const PathState::pointer& lhs, const PathState::pointer& rhs) { -public: - bool operator()(const PathState::pointer& lhs, const PathState::pointer& rhs) - { - // Return true, iff lhs has less priority than rhs. + // Return true, iff lhs has less priority than rhs. - if (lhs->uQuality != rhs->uQuality) - return lhs->uQuality > rhs->uQuality; // Bigger is worse. + if (lhs->uQuality != rhs->uQuality) + return lhs->uQuality > rhs->uQuality; // Bigger is worse. - // Best quanity is second rank. - if (lhs->saOut != rhs->saOut) - return lhs->saOut < rhs->saOut; // Smaller is worse. + // Best quanity is second rank. + if (lhs->saOut != rhs->saOut) + return lhs->saOut < rhs->saOut; // Smaller is worse. - // Path index is third rank. - return lhs->mIndex > rhs->mIndex; // Bigger is worse. - } -}; - -PathState::pointer TransactionEngine::pathCreate(const STPath& spPath) -{ - return PathState::pointer(); + // Path index is third rank. + return lhs->mIndex > rhs->mIndex; // Bigger is worse. } -// Calcuate the next increment of a path. +PathState::PathState( + int iIndex, + const LedgerEntrySet& lesSource, + const STPath& spSourcePath, + uint160 uReceiverID, + uint160 uSenderID, + STAmount saSend, + STAmount saSendMax, + bool bPartialPayment + ) + : mIndex(iIndex), uQuality(0), bDirty(true) +{ + lesEntries = lesSource.duplicate(); + + paymentNode pnFirst; + paymentNode pnLast; + + pnLast.bPartialPayment = bPartialPayment; + pnLast.uAccount = uReceiverID; + pnLast.saWanted = saSend; + + pnFirst.uAccount = uSenderID; + pnFirst.saWanted = saSendMax; + + vpnNodes.push_back(pnFirst); + vpnNodes.push_back(pnLast); +} + +// Calculate the next increment of a path. void TransactionEngine::pathNext(PathState::pointer pspCur) { - } // Apply an increment of the path, then calculate the next increment. void TransactionEngine::pathApply(PathState::pointer pspCur) { - - pathNext(pspCur); } // XXX Need to audit for things like setting accountID not having memory. TransactionEngineResult TransactionEngine::doPayment(const SerializedTransaction& txn) { // Ripple if source or destination is non-native or if there are paths. - uint32 txFlags = txn.getFlags(); - bool bCreate = !!(txFlags & tfCreateAccount); - bool bNoRippleDirect = !!(txFlags & tfNoRippleDirect); + uint32 uTxFlags = txn.getFlags(); + bool bCreate = !!(uTxFlags & tfCreateAccount); + bool bNoRippleDirect = !!(uTxFlags & tfNoRippleDirect); + bool bPartialPayment = !!(uTxFlags & tfPartialPayment); bool bPaths = txn.getITFieldPresent(sfPaths); bool bMax = txn.getITFieldPresent(sfSendMax); uint160 uDstAccountID = txn.getITFieldAccount(sfDestination); @@ -2385,7 +2401,6 @@ TransactionEngineResult TransactionEngine::doPayment(const SerializedTransaction STPathSet spsPaths = txn.getITFieldPathSet(sfPaths); - // XXX If we are parsing for determing forwarding check maximum path count. if (!spsPaths.isEmpty()) { Log(lsINFO) << "doPayment: Invalid transaction: No paths."; @@ -2398,45 +2413,80 @@ TransactionEngineResult TransactionEngine::doPayment(const SerializedTransaction } // Incrementally search paths. - std::priority_queue, PathStateCompare> pqPaths; -#if 0 - BOOST_FOREACH(std::vector::const_iterator::value_type spPath, spsPaths) - { - PathState::pointer pspCur = pathCreate(spPath); + std::vector vpsPaths; - pqPaths.push(pspCur); + BOOST_FOREACH(const STPath& spPath, spsPaths) + { + vpsPaths.push_back(PathState::createPathState( + vpsPaths.size(), + mNodes, + spPath, + uDstAccountID, + mTxnAccountID, + saDstAmount, + saMaxAmount, + bPartialPayment + )); } -#endif + TransactionEngineResult terResult; STAmount saPaid; STAmount saWanted; - uint32 uFlags = txn.getFlags(); // XXX Redundant. terResult = tenUNKNOWN; while (tenUNKNOWN == terResult) { - if (!pqPaths.empty()) + PathState::pointer pspBest; + + // Find the best path. + BOOST_FOREACH(PathState::pointer pspCur, vpsPaths) { - // Have a path to contribute. - PathState::pointer pspCur = pqPaths.top(); + if (pspCur->bDirty) + { + pspCur->bDirty = false; + pspCur->lesEntries = mNodes.duplicate(); - pqPaths.pop(); + // XXX Compute increment + pathNext(pspCur); + } - pathApply(pspCur); + if (!pspBest || (pspCur->uQuality && PathState::less(pspBest, pspCur))) + pspBest = pspCur; + } + if (!pspBest) + { + // + // Apply path. + // + + // Install changes for path. + mNodes.swapWith(pspBest->lesEntries); + + // Mark that path as dirty. + pspBest->bDirty = true; + + // Mark as dirty any other path that intersected. + BOOST_FOREACH(PathState::pointer& pspOther, vpsPaths) + { + // Look for intersection of best and the others. + // - Will forget the intersection applied. + // - Anything left will not interfere with it. + // - Will remember the non-intersection non-applied for future consideration. + if (!pspOther->bDirty + && pspOther->uQuality + && LedgerEntrySet::intersect(pspBest->lesEntries, pspOther->lesEntries)) + pspOther->bDirty = true; + } + + // Figure out if done. if (tenUNKNOWN == terResult && saPaid == saWanted) { terResult = terSUCCESS; } - - if (tenUNKNOWN == terResult && pspCur->uQuality) - { - // Current path still has something to contribute. - pqPaths.push(pspCur); - } } // Ran out of paths. - else if ((!uFlags & tfPartialPayment)) + else if (!bPartialPayment) { // Partial payment not allowed. terResult = terPATH_PARTIAL; // XXX No effect, except unfunded and charge fee. diff --git a/src/TransactionEngine.h b/src/TransactionEngine.h index 82a651181..f470e98ab 100644 --- a/src/TransactionEngine.h +++ b/src/TransactionEngine.h @@ -105,14 +105,60 @@ class PathState public: typedef boost::shared_ptr pointer; - int mIndex; - uint64 uQuality; // 0 = none. - STAmount saIn; - STAmount saOut; + typedef struct { + bool bPartialPayment; // --> + uint16 uFlags; // --> from path - PathState(int iIndex) : mIndex(iIndex) { ; }; + uint160 uAccount; // --> recieving/sending account - static PathState::pointer createPathState(int iIndex) { return boost::make_shared(iIndex); }; + STAmount saWanted; // --> What this node wants from upstream. + + // Maybe this should just be a bool: + // STAmount saIOURedeemMax; // --> Max amount of IOUs to redeem downstream. + // Maybe this should just be a bool: + // STAmount saIOUIssueMax; // --> Max Amount of IOUs to issue downstream. + + STAmount saIOURedeem; // <-- What this node will redeem downstream. + STAmount saIOUIssue; // <-- What this node will issue downstream. + STAmount saSend; // <-- Stamps this node will send downstream. + + STAmount saRecieve; // Amount stamps to receive. + + } paymentNode; + + std::vector vpnNodes; + LedgerEntrySet lesEntries; + + int mIndex; + uint64 uQuality; // 0 = none. + STAmount saIn; + STAmount saOut; + bool bDirty; // Path not computed. + + PathState( + int iIndex, + const LedgerEntrySet& lesSource, + const STPath& spSourcePath, + uint160 uReceiverID, + uint160 uSenderID, + STAmount saSend, + STAmount saSendMax, + bool bPartialPayment + ); + + static PathState::pointer createPathState( + int iIndex, + const LedgerEntrySet& lesSource, + const STPath& spSourcePath, + uint160 uReceiverID, + uint160 uSenderID, + STAmount saSend, + STAmount saSendMax, + bool bPartialPayment + ) + { return boost::make_shared(iIndex, lesSource, spSourcePath, uReceiverID, uSenderID, saSend, saSendMax, bPartialPayment); }; + + static bool less(const PathState::pointer& lhs, const PathState::pointer& rhs); }; // One instance per ledger. @@ -137,25 +183,6 @@ private: bool dirNext(const uint256& uRootIndex, SLE::pointer& sleNode, unsigned int& uDirEntry, uint256& uEntryIndex); #ifdef WORK_IN_PROGRESS - typedef struct { - uint16 uFlags; // --> from path - - STAccount saAccount; // --> recieving/sending account - - STAmount saWanted; // --> What this node wants from upstream. - - // Maybe this should just be a bool: - STAmount saIOURedeemMax; // --> Max amount of IOUs to redeem downstream. - // Maybe this should just be a bool: - STAmount saIOUIssueMax; // --> Max Amount of IOUs to issue downstream. - - STAmount saIOURedeem; // <-- What this node will redeem downstream. - STAmount saIOUIssue; // <-- What this node will issue downstream. - STAmount saSend; // <-- Stamps this node will send downstream. - - STAmount saRecieve; // Amount stamps to receive. - - } paymentNode; typedef struct { std::vector vpnNodes; From 794fe66008d0e15d394e1457258b449ac1d3cd88 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Wed, 1 Aug 2012 12:44:22 -0700 Subject: [PATCH 3/7] Work towards ripple. --- src/SerializedTypes.cpp | 8 +- src/SerializedTypes.h | 15 +- src/TransactionEngine.cpp | 287 ++++++++++++++++++++++++++++++-------- src/TransactionEngine.h | 46 +++--- 4 files changed, 261 insertions(+), 95 deletions(-) diff --git a/src/SerializedTypes.cpp b/src/SerializedTypes.cpp index a341225d1..311f004ed 100644 --- a/src/SerializedTypes.cpp +++ b/src/SerializedTypes.cpp @@ -318,16 +318,10 @@ STPathSet* STPathSet::construct(SerializerIterator& s, const char *name) } else { - bool bAccount = !!(iType & STPathElement::typeAccount); - bool bOffer = !!(iType & STPathElement::typeOffer); + bool bAccount = !!(iType & STPathElement::typeAccount); bool bCurrency = !!(iType & STPathElement::typeCurrency); bool bIssuer = !!(iType & STPathElement::typeIssuer); - if (!bAccount && !bOffer) - { - throw std::runtime_error("bad path element"); - } - uint160 uAccountID; uint160 uCurrency; uint160 uIssuerID; diff --git a/src/SerializedTypes.h b/src/SerializedTypes.h index 89c3a89dd..c3ba0f047 100644 --- a/src/SerializedTypes.h +++ b/src/SerializedTypes.h @@ -520,12 +520,13 @@ class STPathElement public: enum { typeEnd = 0x00, - typeAccount = 0x01, // Rippling through an account - typeOffer = 0x02, // Claiming an offer - typeCurrency = 0x10, // Currency follows - typeIssuer = 0x20, // Issuer follows - typeBoundary = 0xFF, // boundary between alternate paths - typeStrayBits = 0xCC, // Bits that must be zero. + typeAccount = 0x01, // Rippling through an account (vs taking an offer). + typeRedeem = 0x04, // Redeem IOUs. + typeIssue = 0x08, // Issue IOUs. + typeCurrency = 0x10, // Currency follows. + typeIssuer = 0x20, // Issuer follows. + typeBoundary = 0xFF, // Boundary between alternate paths. + typeStrayBits = 0xC0, // Bits that must be zero. }; protected: @@ -539,7 +540,7 @@ public: : mAccountID(uAccountID), mCurrency(uCurrency), mIssuerID(uIssuerID) { mType = - (uAccountID.isZero() ? STPathElement::typeOffer : STPathElement::typeAccount) + (uAccountID.isZero() ? 0 : STPathElement::typeAccount) | (uCurrency.isZero() ? 0 : STPathElement::typeCurrency) | (uIssuerID.isZero() ? 0 : STPathElement::typeIssuer); } diff --git a/src/TransactionEngine.cpp b/src/TransactionEngine.cpp index 5fb1fc94d..dd3d3a8c8 100644 --- a/src/TransactionEngine.cpp +++ b/src/TransactionEngine.cpp @@ -2017,6 +2017,7 @@ void TransactionEngine::calcNodeOfferReverse( } } } +#endif // From the destination work towards the source calculating how much must be asked for. // --> bAllowPartial: If false, fail if can't meet requirements. @@ -2028,64 +2029,31 @@ void TransactionEngine::calcNodeOfferReverse( // <-> [0]saWanted.mAmount : --> limit, <-- actual // XXX Disallow looping. // XXX With multiple path and due to offers, must consider consumed. -bool calcPaymentReverse(std::vector& pnNodes, bool bAllowPartial) +bool TransactionEngine::calcPathReverse(PathState::pointer pspCur) { + bool bSent = true; +#if 0 TransactionEngineResult terResult = tenUNKNOWN; - uIndex = pnNodes.size(); + unsigned int uIndex = pspCur->vpnNodes.size() - 1; - while (tenUNKNOWN == terResult && uIndex--) + while (bSent && tenUNKNOWN == terResult && uIndex--) { // Calculate (1) sending by fullfilling next wants and (2) setting current wants. + paymentNode& prvPN = uIndex ? pnNodes[uIndex-1] : pnNodes[0]; paymentNode& curPN = pnNodes[uIndex]; - paymentNode& prvPN = pnNodes[uIndex-1]; paymentNode& nxtPN = pnNodes[uIndex+1]; + bool bRedeem = !!(curPN.uFlags & STPathElement::typeRedeem); + bool bIssue = !!(curPN.uFlags & STPathElement::typeIssue); + bool bOffer = !!(curPN.uFlags & STPathElement::typeOffer); - if (!(uFlags & (PF_REDEEM|PF_ISSUE))) - { - // Redeem IOUs - terResult = tenBAD_PATH; - } - else if (curPN.saWanted.isZero()) + if (curPN.saWanted.isZero()) { // Must want something. terResult = tenBAD_AMOUNT; } - else if (curPN->uFlags & PF_ACCOUNT) - { - // Account node. - // Rippling through this accounts balances. - // No currency change. - // Issuer change possible. - - SLE::pointer sleRippleCur = ; - SLE::pointer sleRippleNxt = ; - STAmount saBalanceCur = ; - - if ((uFlags & PF_REDEEM) && saBalanceCur.isPositive()) - { - // Redeem IOUs - - // XXX - curPN.saWanted += ___; - - bSent = true; - } - - if ((uFlags & PF_ISSUE) // Allowed to issue. - && !saWantedNxt.isZero() // Need to issue. - && !saBalanceCur.isPositive()) // Can issue. - { - // Issue IOUs - - // XXX - curPN.saWanted += ___; - bSent = true; - } - - } - else if (curPN->uFlags & PF_OFFER) + else if (bOffer) { // Offer node. // Ripple or transfering from previous node through this offer to next node. @@ -2125,13 +2093,91 @@ bool calcPaymentReverse(std::vector& pnNodes, bool bAllowPartial) // Save sent amount } } + // Account node. + else if (!bIssue && !bOffer) + { + terResult = tenBAD_PATH; + } else { - assert(false); - } + // Rippling through this accounts balances. + // No currency change. + // Issuer change possible. + // Figuring out want current account should redeem and send. - if (tenUNKNOWN == terResult == curPN.saWanted.isZero()) - terResult = terZERO; // Path contributes nothing. + SLE::pointer sleRippleCur = ; + SLE::pointer sleRippleNxt = ; + STAmount saBalance = peekBalance(curPN.uAccountID, nxtPN.uAccountID); + STAmount saLimit = peekLimit(curPN.uAccountID, nxtPN.uAccountID); + STAmount saWanted = nxtPN.saWanted; + + curPN.saWanted.zero(); + + if (bRedeem && saBalance.isPositive()) + { + // Allowed to redeem and have IOUs. + curPN.saIOURedeem = min(saBalance, saWanted); + curPN.saWanted = curPN.saIOURedeem; // XXX Assumes we want 1::1 + saWanted -= curPN.saIOURedeem; + } + else + { + curPN.saIOURedeem.zero(); + } + + if (bIssue // Allowed to issue. + && !saWanted.isZero() // Need to issue. + && !saBalance.isPositive()) // Can issue. (Can't if must redeem). + { + curPN.saIOUIssue = min(saLimit+saBalance, saWanted); + curPN.saWanted += curPN.saIOUIssue; // XXX Assumes we want 1::1 + } + else + { + curPN.saIOUIssue.zero(); + } + + bSent = !curPN.saIOURedeem.isZero() || !curPN.saIOUIssue.isZero(); + } + } +#endif + return bSent; +} + +// Cur is the driver and will be filled exactly. +void TransactionEngine::calcNodeFwd(const uint32 uQualityIn, const uint32 uQualityOut, + const STAmount& saPrvReq, const STAmount& saCurReq, + STAmount& saPrvAct, STAmount& saCurAct) +{ + if (uQualityIn >= uQualityOut) + { + // No fee. + STAmount saTransfer = MIN(saPrvReq-saPrvAct, saCurReq-saCurAct); + + saPrvAct += saTransfer; + saCurAct += saTransfer; + } + else + { + // Fee. + STAmount saPrv = saPrvReq-saPrvAct; + STAmount saCur = saCurReq-saCurAct; + STAmount saCurIn = STAmount::divide(STAmount::multiply(saCur, uQualityIn, uint160(1)), uQualityOut, uint160(1)); + + if (saCurIn >= saPrv) + { + // All of cur. Some amount of prv. + saCurAct = saCurReq; + saPrvAct += saCurIn; + } + else + { + // A part of cur. All of prv. + STAmount saCurOut = STAmount::divide(STAmount::multiply(saPrv, uQualityOut, uint160(1)), uQualityIn, uint160(1)); + + saCurAct += saCurOut; + saPrvAct = saPrvReq; + } } } @@ -2142,24 +2188,130 @@ bool calcPaymentReverse(std::vector& pnNodes, bool bAllowPartial) // --> [all]saWanted.IOURedeem // --> [all]saWanted.IOUIssue // --> [all]saAccount -bool calcPaymentForward(std::vector& pnNodes) +// XXX This path becomes dirty if saSendMaxFirst must be changed. +void TransactionEngine::calcPathForward(PathState::pointer pspCur) { - cur = src; +#if 0 + TransactionEngineResult terResult = tenUNKNOWN; + unsigned int uIndex = 0; - if (!cur->saSend.isZero()) - { - // Sending stamps - always final step. - assert(!cur->next); - nxt->saReceive = cur->saSend; - bDone = true; - } - else - { - // Rippling. + unsigned int uEnd = pspCur->vpnNodes.size(); + unsigned int uLast = uEnd - 1; + while (tenUNKNOWN == terResult && uIndex != uEnd) + { + // Calculate (1) sending by fullfilling next wants and (2) setting current wants. + + paymentNode& prvPN = uIndex ? pnNodes[uIndex-1] : pnNodes[0]; + paymentNode& curPN = pnNodes[uIndex]; + + // XXX Assume rippling. + + if (!uIndex) + { + // First node, calculate amount to send. + STAmount& saCurRedeemReq = curPN.saRevRedeem; + STAmount& saCurRedeemAct = curPN.saFwdRedeem; + STAmount& saCurIssueReq = curPN.saRevIssue; + STAmount& saCurIssueAct = curPN.saFwdIssue; + + STAmount& saCurSendMaxReq = curPN.saSendMax; + STAmount saCurSendMaxAct; + + if (saCurRedeemReq) + { + // Redeem requested. + saCurRedeemAct = MIN(saCurRedeemAct, saCurSendMaxReq); + saCurSendMaxAct = saCurRedeemAct; + } + + if (saCurIssueReq && saCurSendMaxReq != saCurRedeemAct) + { + // Issue requested and not over budget. + saCurIssueAct = MIN(saCurSendMaxReq-saCurRedeemAct, saCurIssueReq); + // saCurSendMaxAct += saCurIssueReq; // Not needed. + } + } + else if (uIndex == uLast) + { + // Last node. Accept all funds. Calculate amount actually to credit. + uint32 uQualityIn = peekQualityIn(curPN.uAccountID, prvPN.uAccountID); + STAmount& saPrvRedeemReq = prvPN.saFwdRedeem; + STAmount& saPrvIssueReq = prvPN.saFwdIssue; + STAmount saPrvIssueAct = uQualityIn >= QUALITY_ONE + ? saPrvIssueReq // No fee. + : multiply(saPrvIssueReq, uQualityIn, _); // Fee. + STAmount& saCurReceive = curPN.saReceive; + + // Amount to credit. + saCurReceive = saPrvRedeemReq+saPrvIssueAct; + + // Actually receive. + rippleCredit(curPN.uAccountID, prvPN.uAccountID, saPrvRedeemReq + saPrvIssueReq); + } + else + { + // Handle middle node. + // The previous nodes tells want it wants to push through to current. + // The current node know what it woud push through next. + // Determine for the current node: + // - Output to next node minus fees. + // Perform balance adjustment with previous. + // All of previous output is consumed. + + STAmount saSrcRedeem; // To start, redeeming none. + STAmount saSrcIssue; // To start, issuing none. + STAmount saDstRedeem; + + // Have funds in previous node to transfer. + uint32 uQualityIn = peekQualityIn(curPN.uAccountID, prvPN.uAccountID); + uint32 uQualityOut = peekQualityOut(curPN.uAccountID, nxtPN.uAccountID); + + STAmount& saPrvRedeemReq = prvPN.saFwdRedeem; + STAmount saPrvRedeemAct; + STAmount& saCurRedeemReq = curPN.saRevRedeem; + STAmount& saCurRedeemAct = curPN.saFwdRedeem; + STAmount& saPrvIssueReq = prvPN.saFwdIssue; + STAmount saPrvIssueAct; + + // Previous redeem part 1: redeem -> redeem + if (saPrvRedeemReq != saPrvRedeemAct) // Previous wants to redeem. To next must be ok. + { + // Rate : 1.0 : quality out + calcNodeFwd(QUALITY_ONE, uQualityOut, saPrvRedeemReq, saCurRedeemReq, saPrvRedeemAct, saCurRedeemAct); + } + + // 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 more to redeem to next. + { + // Rate : 1.0 : 1.0 + transfer_rate + calcNodeFwd(QUALITY_ONE, QUALITY_ONE+peekTransfer(curPN.uAccountID), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct); + } + + // 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 + calcNodeFwd(uQualityIn, uQualityOut, saPrvIssueReq, saCurRedeemReq, saPrvIssueAct, saCurRedeemAct); + } + + // Previous issue part 2 : issue -> issue + if (saPrvIssueReq != saPrvIssueAct) // Previous wants to issue. To next must be ok. + { + // Rate: quality in : 1.0 + calcNodeFwd(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurIssueReq, saPrvIssueAct, saCurIssueAct); + } + + // Adjust prv --> cur balance : take all inbound + rippleCredit(cur, prv, saPrvRedeemReq + saPrvIssueReq); + } } -} #endif +} bool PathState::less(const PathState::pointer& lhs, const PathState::pointer& rhs) { @@ -2207,6 +2359,19 @@ PathState::PathState( // Calculate the next increment of a path. void TransactionEngine::pathNext(PathState::pointer pspCur) { + // The next state is what is available in preference order. + // This is calculated when referenced accounts changed. + + if (calcPathReverse(pspCur)) + { + calcPathForward(pspCur); + } + else + { + // Mark path as inactive. + pspCur->uQuality = 0; + pspCur->bDirty = false; + } } // Apply an increment of the path, then calculate the next increment. diff --git a/src/TransactionEngine.h b/src/TransactionEngine.h index f470e98ab..144dc7988 100644 --- a/src/TransactionEngine.h +++ b/src/TransactionEngine.h @@ -99,32 +99,33 @@ enum TransactionEngineParams tepMETADATA = 5, // put metadata in tree, not transaction }; +typedef struct { + bool bPartialPayment; // --> + uint16 uFlags; // --> from path + + uint160 uAccount; // --> recieving/sending account + + STAmount saSendMax; // --> First node: most to send. + STAmount saRecieve; // <-- Last node: Value received (minus fees) from upstream. + + STAmount saRevRedeem; // <-- Computed amount node needs at most redeem. + STAmount saRevIssue; // <-- Computed amount node ____ + STAmount saCurRedeem; // <-- Amount node will redeem to next. + STAmount saCurIssue; // <-- Amount node will issue to next. + + STAmount saWanted; // <-- What this node wants from upstream. + + STAmount saSend; // <-- Stamps this node will send downstream. + + STAmount saXNSRecieve; // Amount stamps to receive. +} paymentNode; + // Hold a path state under incremental application. class PathState { public: typedef boost::shared_ptr pointer; - typedef struct { - bool bPartialPayment; // --> - uint16 uFlags; // --> from path - - uint160 uAccount; // --> recieving/sending account - - STAmount saWanted; // --> What this node wants from upstream. - - // Maybe this should just be a bool: - // STAmount saIOURedeemMax; // --> Max amount of IOUs to redeem downstream. - // Maybe this should just be a bool: - // STAmount saIOUIssueMax; // --> Max Amount of IOUs to issue downstream. - - STAmount saIOURedeem; // <-- What this node will redeem downstream. - STAmount saIOUIssue; // <-- What this node will issue downstream. - STAmount saSend; // <-- Stamps this node will send downstream. - - STAmount saRecieve; // Amount stamps to receive. - - } paymentNode; std::vector vpnNodes; LedgerEntrySet lesEntries; @@ -229,6 +230,11 @@ protected: PathState::pointer pathCreate(const STPath& spPath); void pathApply(PathState::pointer pspCur); void pathNext(PathState::pointer pspCur); + void calcNodeFwd(const uint32 uQualityIn, const uint32 uQualityOut, + const STAmount& saPrvReq, const STAmount& saCurReq, + STAmount& saPrvAct, STAmount& saCurAct); + bool calcPathReverse(PathState::pointer pspCur); + void calcPathForward(PathState::pointer pspCur); void txnWrite(); From 42bd29c212f6869992387ade887e0ea401aef8ee Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Thu, 2 Aug 2012 19:16:24 -0700 Subject: [PATCH 4/7] Work towards ripple. --- src/SerializedTypes.h | 1 + src/TransactionEngine.cpp | 491 ++++++++++++++++++++++++++------------ src/TransactionEngine.h | 71 +++--- 3 files changed, 375 insertions(+), 188 deletions(-) diff --git a/src/SerializedTypes.h b/src/SerializedTypes.h index 21a3ca8f6..392966fcb 100644 --- a/src/SerializedTypes.h +++ b/src/SerializedTypes.h @@ -614,6 +614,7 @@ namespace boost typedef std::vector::const_iterator type; }; } + class STPathSet : public SerializedType { // A set of zero or more payment paths protected: diff --git a/src/TransactionEngine.cpp b/src/TransactionEngine.cpp index 50d70ea75..25a0cef54 100644 --- a/src/TransactionEngine.cpp +++ b/src/TransactionEngine.cpp @@ -21,6 +21,11 @@ #define DIR_NODE_MAX 2 #define RIPPLE_PATHS_MAX 3 +#define QUALITY_ONE 100000000 // 10e9 +#define CURRENCY_ONE uint160(1) + +// static STAmount saOne(CURRENCY_ONE, 1, 0); + bool transResultInfo(TransactionEngineResult terCode, std::string& strToken, std::string& strHuman) { static struct { @@ -35,6 +40,7 @@ bool transResultInfo(TransactionEngineResult terCode, std::string& strToken, std { tenBAD_GEN_AUTH, "tenBAD_GEN_AUTH", "Not authorized to claim generator." }, { tenBAD_ISSUER, "tenBAD_ISSUER", "Malformed." }, { tenBAD_OFFER, "tenBAD_OFFER", "Malformed." }, + { tenBAD_PATH, "tenBAD_PATH", "Malformed: path." }, { tenBAD_PATH_COUNT, "tenBAD_PATH_COUNT", "Malformed: too many paths." }, { tenBAD_PUBLISH, "tenBAD_PUBLISH", "Malformed: bad publish." }, { tenBAD_RIPPLE, "tenBAD_RIPPLE", "Ledger prevents ripple from succeeding." }, @@ -99,12 +105,92 @@ bool transResultInfo(TransactionEngineResult terCode, std::string& strToken, std return iIndex >= 0; } -// Return how much of uIssuerID's uCurrency IOUs that uAccountID holds. May be negative. +STAmount TransactionEngine::rippleBalance(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID) +{ + STAmount saBalance; + SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uToAccountID, uFromAccountID, uCurrencyID)); + + assert(sleRippleState); + if (sleRippleState) + { + saBalance = sleRippleState->getIValueFieldAmount(sfBalance); + if (uToAccountID < uFromAccountID) + saBalance.negate(); + } + + return saBalance; + +} + +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); + } + + return saLimit; + +} + +uint32 TransactionEngine::rippleTransfer(const uint160& uIssuerID) +{ + SLE::pointer sleAccount = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uIssuerID)); + + return sleAccount->getIFieldPresent(sfTransferRate) + ? sleAccount->getIFieldU32(sfTransferRate) + : QUALITY_ONE; +} + +// XXX Might not need this, might store in nodes on calc reverse. +uint32 TransactionEngine::rippleQualityIn(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID) +{ + uint32 uQualityIn; + + SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uToAccountID, uFromAccountID, uCurrencyID)); + + if (sleRippleState) + { + uQualityIn = sleRippleState->getIFieldU32(uToAccountID < uFromAccountID ? sfLowQualityIn : sfHighQualityIn); + } + else + { + assert(false); + uQualityIn = QUALITY_ONE; + } + + return uQualityIn; +} + +uint32 TransactionEngine::rippleQualityOut(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID) +{ + uint32 uQualityOut; + + SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uToAccountID, uFromAccountID, uCurrencyID)); + + if (sleRippleState) + { + uQualityOut = sleRippleState->getIFieldU32(uToAccountID < uFromAccountID ? sfLowQualityOut : sfHighQualityOut); + } + else + { + assert(false); + uQualityOut = QUALITY_ONE; + } + + return uQualityOut; +} + +// 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& uCurrency, const uint160& 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, uCurrency)); + SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uAccountID, uIssuerID, uCurrencyID)); if (sleRippleState) { @@ -117,12 +203,12 @@ STAmount TransactionEngine::rippleHolds(const uint160& uAccountID, const uint160 return saBalance; } -// <-- saAmount: amount of uCurrency held by uAccountID. May be negative. -STAmount TransactionEngine::accountHolds(const uint160& uAccountID, const uint160& uCurrency, const uint160& uIssuerID) +// <-- 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 (uCurrency.isZero()) + if (uCurrencyID.isZero()) { SLE::pointer sleAccount = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uAccountID)); @@ -132,12 +218,12 @@ STAmount TransactionEngine::accountHolds(const uint160& uAccountID, const uint16 } else { - saAmount = rippleHolds(uAccountID, uCurrency, uIssuerID); + saAmount = rippleHolds(uAccountID, uCurrencyID, uIssuerID); Log(lsINFO) << "accountHolds: " << saAmount.getFullText() << " : " - << STAmount::createHumanCurrency(uCurrency) + << STAmount::createHumanCurrency(uCurrencyID) << "/" << NewcoinAddress::createHumanAccountID(uIssuerID); } @@ -190,16 +276,11 @@ STAmount TransactionEngine::rippleTransit(const uint160& uSenderID, const uint16 if (uSenderID != uIssuerID && uReceiverID != uIssuerID) { - SLE::pointer sleIssuerAccount = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uIssuerID)); - uint32 uTransitRate; + uint32 uTransitRate = rippleTransfer(uIssuerID); - if (sleIssuerAccount->getIFieldPresent(sfTransferRate)) - uTransitRate = sleIssuerAccount->getIFieldU32(sfTransferRate); - - if (uTransitRate) + if (QUALITY_ONE != uTransitRate) { - - STAmount saTransitRate(uint160(1), uTransitRate, -9); + STAmount saTransitRate(CURRENCY_ONE, uTransitRate, -9); saTransitFee = STAmount::multiply(saAmount, saTransitRate, saAmount.getCurrency()); } @@ -208,6 +289,50 @@ STAmount TransactionEngine::rippleTransit(const uint160& uSenderID, const uint16 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) +{ + uint160 uIssuerID = saAmount.getIssuer(); + + assert(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. @@ -219,42 +344,7 @@ STAmount TransactionEngine::rippleSend(const uint160& uSenderID, const uint160& if (uSenderID == uIssuerID || uReceiverID == uIssuerID) { // Direct send: redeeming IOUs and/or sending own IOUs. - - bool bFlipped = uSenderID > uReceiverID; - uint256 uIndex = Ledger::getRippleStateIndex(uSenderID, uReceiverID, saAmount.getCurrency()); - SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, uIndex); - - if (!sleRippleState) - { - Log(lsINFO) << "rippleSend: 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); - } + rippleCredit(uSenderID, uReceiverID, saAmount); saActual = saAmount; } @@ -268,8 +358,8 @@ STAmount TransactionEngine::rippleSend(const uint160& uSenderID, const uint160& saActual.setIssuer(uIssuerID); // XXX Make sure this done in + above. - rippleSend(uIssuerID, uReceiverID, saAmount); - rippleSend(uSenderID, uIssuerID, saActual); + rippleCredit(uIssuerID, uReceiverID, saAmount); + rippleCredit(uSenderID, uIssuerID, saActual); } return saActual; @@ -1405,12 +1495,12 @@ TransactionEngineResult TransactionEngine::doCreditSet(const SerializedTransacti uint32 uQualityIn = bQualityIn ? txn.getITFieldU32(sfQualityIn) : 0; bool bQualityOut = txn.getITFieldPresent(sfQualityOut); uint32 uQualityOut = bQualityIn ? txn.getITFieldU32(sfQualityOut) : 0; - uint160 uCurrency = saLimitAmount.getCurrency(); - STAmount saBalance(uCurrency); + uint160 uCurrencyID = saLimitAmount.getCurrency(); + STAmount saBalance(uCurrencyID); bool bAddIndex = false; bool bDelIndex = false; - SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(mTxnAccountID, uDstAccountID, uCurrency)); + SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(mTxnAccountID, uDstAccountID, uCurrencyID)); if (sleRippleState) { // A line exists in one or more directions. @@ -1489,10 +1579,10 @@ TransactionEngineResult TransactionEngine::doCreditSet(const SerializedTransacti else { // Create a new ripple line. - STAmount saZero(uCurrency); + STAmount saZero(uCurrencyID); bAddIndex = true; - sleRippleState = entryCreate(ltRIPPLE_STATE, Ledger::getRippleStateIndex(mTxnAccountID, uDstAccountID, uCurrency)); + sleRippleState = entryCreate(ltRIPPLE_STATE, Ledger::getRippleStateIndex(mTxnAccountID, uDstAccountID, uCurrencyID)); Log(lsINFO) << "doCreditSet: Creating ripple line: " << sleRippleState->getIndex().ToString(); @@ -2024,7 +2114,7 @@ void TransactionEngine::calcNodeOfferReverse( // From the destination work towards the source calculating how much must be asked for. // --> bAllowPartial: If false, fail if can't meet requirements. -// <-- bSuccess: true=success, false=insufficient funds. +// <-- bSuccess: true=success, false=insufficient funds / liqudity. // <-> pnNodes: // --> [end]saWanted.mAmount // --> [all]saWanted.mCurrency @@ -2034,29 +2124,70 @@ void TransactionEngine::calcNodeOfferReverse( // XXX With multiple path and due to offers, must consider consumed. bool TransactionEngine::calcPathReverse(PathState::pointer pspCur) { - bool bSent = true; -#if 0 TransactionEngineResult terResult = tenUNKNOWN; - unsigned int uIndex = pspCur->vpnNodes.size() - 1; + unsigned int uLast = pspCur->vpnNodes.size() - 1; + unsigned int uIndex = pspCur->vpnNodes.size(); - while (bSent && tenUNKNOWN == terResult && uIndex--) + while (tenUNKNOWN == terResult && uIndex--) { - // Calculate (1) sending by fullfilling next wants and (2) setting current wants. + // Calculate: + // (1) saPrvRedeem & saPrvIssue from saCurRevRedeem & saCurRevIssue. + // (2) saCurWanted by summing saRevRedeem & saRevIssue. <-- XXX might not need this as it can be infered. + // XXX We might not need to do 0. - paymentNode& prvPN = uIndex ? pnNodes[uIndex-1] : pnNodes[0]; - paymentNode& curPN = pnNodes[uIndex]; - paymentNode& nxtPN = pnNodes[uIndex+1]; - bool bRedeem = !!(curPN.uFlags & STPathElement::typeRedeem); - bool bIssue = !!(curPN.uFlags & STPathElement::typeIssue); - bool bOffer = !!(curPN.uFlags & STPathElement::typeOffer); + paymentNode& prvPN = uIndex ? pspCur->vpnNodes[uIndex-1] : pspCur->vpnNodes[0]; + paymentNode& curPN = pspCur->vpnNodes[uIndex]; + paymentNode& nxtPN = uIndex == uLast ? pspCur->vpnNodes[uLast] : pspCur->vpnNodes[uIndex+1]; + bool bAccount = !!(curPN.uFlags & STPathElement::typeAccount); + bool bRedeem = !!(curPN.uFlags & STPathElement::typeRedeem); + bool bIssue = !!(curPN.uFlags & STPathElement::typeIssue); + uint160& uPrvAccountID = prvPN.uAccountID; + uint160& uCurAccountID = curPN.uAccountID; + uint160& uNxtAccountID = nxtPN.uAccountID; + uint160& uCurrencyID = curPN.uCurrencyID; - if (curPN.saWanted.isZero()) + if (uIndex == uLast) { - // Must want something. - terResult = tenBAD_AMOUNT; + // saCurWanted set by caller. + uint32 uQualityIn = rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID); + STAmount saPrvBalance = rippleBalance(uCurAccountID, uPrvAccountID, uCurrencyID); + STAmount saPrvLimit = rippleLimit(uCurAccountID, uPrvAccountID, uCurAccountID); + + STAmount saPrvRedeemReq = saPrvBalance.isNative() ? -saPrvBalance : STAmount(uCurrencyID, 0); + STAmount& saPrvRedeemAct = prvPN.saRevRedeem; // What previous should redeem with cur. + + STAmount& saPrvIssueAct = prvPN.saRevIssue; + STAmount saPrvIssueReq = saPrvBalance.isPositive() ? saPrvLimit - saPrvBalance : saPrvLimit; + + STAmount saCurWantedReq = pspCur->saOutReq; + STAmount saCurWantedAct; + + // Calculate redeem + if (bRedeem + && saPrvRedeemReq) // Previous has IOUs to redeem. + { + // Redeem at 1:1 + saCurWantedAct = MIN(saPrvRedeemReq, saCurWantedReq); + saPrvRedeemAct = saCurWantedAct; + } + + // Calculate issuing. + if (bIssue + && saCurWantedReq != saCurWantedAct // Need more. + && saPrvIssueReq) // Will accept IOUs. + { + // Rate: quality in : 1.0 + calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurWantedReq, saPrvIssueAct, saCurWantedAct); + } + + if (!saCurWantedAct) + { + // Must have processed something. + terResult = tenBAD_AMOUNT; + } } - else if (bOffer) + else if (!bAccount) { // Offer node. // Ripple or transfering from previous node through this offer to next node. @@ -2065,12 +2196,12 @@ bool TransactionEngine::calcPathReverse(PathState::pointer pspCur) // We limit what this node sends by this nodes redeem and issue max. // This allows path lists to be strictly redeem. // XXX Make sure offer book was not previously mentioned. - - uint160 uPrvCurrency = curPN->uFlags & PF_WANTED_CURRENCY +#if 0 + uint160 uPrvCurrency = curPN.uFlags & PF_WANTED_CURRENCY ? curPN->saWanted.getCurrency() : saSendMax.getCurrency(); - uint160 uPrvIssuer = curPN->uFlags & PF_WANTED_ISSUER + uint160 uPrvIssuer = curPN.uFlags & PF_WANTED_ISSUER ? curPN->saWanted.getIssuer() : saSendMax.getIssuer(); @@ -2095,67 +2226,114 @@ bool TransactionEngine::calcPathReverse(PathState::pointer pspCur) // Save sent amount } +#endif } - // Account node. - else if (!bIssue && !bOffer) + // Ripple through account. + else if (!bIssue && !bRedeem) { - terResult = tenBAD_PATH; + // XXX This might be checked sooner. + terResult = tenBAD_PATH; // Must be able to redeem and issue. + } + else if (!uIndex) + { + // Done. No previous. We know what we would like to redeem and issue. + nothing(); } else { // Rippling through this accounts balances. // No currency change. - // Issuer change possible. - // Figuring out want current account should redeem and send. + // Given saCurRedeem & saCurIssue figure out saPrvRedeem & saPrvIssue. - SLE::pointer sleRippleCur = ; - SLE::pointer sleRippleNxt = ; - STAmount saBalance = peekBalance(curPN.uAccountID, nxtPN.uAccountID); - STAmount saLimit = peekLimit(curPN.uAccountID, nxtPN.uAccountID); - STAmount saWanted = nxtPN.saWanted; + uint32 uQualityIn = rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID); + uint32 uQualityOut = rippleQualityOut(uCurAccountID, uNxtAccountID, uCurrencyID); + STAmount saPrvBalance = rippleBalance(uCurAccountID, uPrvAccountID, uCurrencyID); + STAmount saPrvLimit = rippleLimit(uCurAccountID, uPrvAccountID, uCurrencyID); - curPN.saWanted.zero(); + STAmount saPrvRedeemReq = saPrvBalance.isNegative() ? -saPrvBalance : STAmount(uCurrencyID, 0); + STAmount& saPrvRedeemAct = prvPN.saRevRedeem; - if (bRedeem && saBalance.isPositive()) + STAmount saPrvIssueReq = saPrvLimit - saPrvBalance; + STAmount& saPrvIssueAct = prvPN.saRevIssue; + + const STAmount& saCurRedeemReq = curPN.saRevRedeem; + STAmount saCurRedeemAct; + + const STAmount& saCurIssueReq = curPN.saRevIssue; + STAmount saCurIssueAct; // Track progess. + + + // Previous redeem part 1: redeem -> redeem + if (bRedeem + && saCurRedeemReq // Next wants us to redeem. + && saPrvBalance.isNegative()) // Previous has IOUs to redeem. { - // Allowed to redeem and have IOUs. - curPN.saIOURedeem = min(saBalance, saWanted); - curPN.saWanted = curPN.saIOURedeem; // XXX Assumes we want 1::1 - saWanted -= curPN.saIOURedeem; - } - else - { - curPN.saIOURedeem.zero(); + // Rate : 1.0 : quality out + + calcNodeRipple(QUALITY_ONE, uQualityOut, saPrvRedeemReq, saCurRedeemReq, saPrvRedeemAct, saCurRedeemAct); } - if (bIssue // Allowed to issue. - && !saWanted.isZero() // Need to issue. - && !saBalance.isPositive()) // Can issue. (Can't if must redeem). + // Previous redeem part 2: redeem -> issue. + if (bIssue + && saCurRedeemReq != saCurRedeemAct // Can only if issue if more can not be redeemed. + && saPrvBalance.isNegative() // Previous still has IOUs. + && saCurIssueReq) // Need some issued. { - curPN.saIOUIssue = min(saLimit+saBalance, saWanted); - curPN.saWanted += curPN.saIOUIssue; // XXX Assumes we want 1::1 - } - else - { - curPN.saIOUIssue.zero(); + // Rate : 1.0 : transfer_rate + calcNodeRipple(QUALITY_ONE, rippleTransfer(uCurAccountID), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct); } - bSent = !curPN.saIOURedeem.isZero() || !curPN.saIOUIssue.isZero(); + // Previous issue part 1: issue -> redeem + if (bRedeem + && saCurRedeemReq != saCurRedeemAct // Can only redeem if more to be redeemed. + && !saPrvBalance.isNegative()) // Previous has no IOUs. + { + // Rate: quality in : quality out + calcNodeRipple(uQualityIn, uQualityOut, saPrvIssueReq, saCurRedeemReq, saPrvIssueAct, saCurRedeemAct); + } + + // Previous issue part 2 : issue -> issue + if (bIssue + && saCurRedeemReq != saCurRedeemAct // Can only if issue if more can not be redeemed. + && !saPrvBalance.isNegative() // Previous has no IOUs. + && saCurIssueReq != saCurIssueAct) // Need some issued. + { + // Rate: quality in : 1.0 + calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurIssueReq, saPrvIssueAct, saCurIssueAct); + } + + if (!saCurRedeemAct && !saCurIssueAct) + { + // Must want something. + terResult = tenBAD_AMOUNT; + } } } -#endif - return bSent; + + return tenUNKNOWN == terResult; } // Cur is the driver and will be filled exactly. -void TransactionEngine::calcNodeFwd(const uint32 uQualityIn, const uint32 uQualityOut, - const STAmount& saPrvReq, const STAmount& saCurReq, - STAmount& saPrvAct, STAmount& saCurAct) +// uQualityIn -> uQualityOut +// saPrvReq -> saCurReq +// sqPrvAct -> saCurAct +// 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. +void TransactionEngine::calcNodeRipple( + const uint32 uQualityIn, + const uint32 uQualityOut, + const STAmount& saPrvReq, // --> in limit including fees + const STAmount& saCurReq, // --> out limit (driver) + STAmount& saPrvAct, // <-> in limit including achieved + STAmount& saCurAct) // <-> out limit achieved. { + STAmount saPrv = saPrvReq-saPrvAct; + STAmount saCur = saCurReq-saCurAct; + if (uQualityIn >= uQualityOut) { // No fee. - STAmount saTransfer = MIN(saPrvReq-saPrvAct, saCurReq-saCurAct); + STAmount saTransfer = MIN(saPrv, saCur); saPrvAct += saTransfer; saCurAct += saTransfer; @@ -2163,9 +2341,7 @@ void TransactionEngine::calcNodeFwd(const uint32 uQualityIn, const uint32 uQuali else { // Fee. - STAmount saPrv = saPrvReq-saPrvAct; - STAmount saCur = saCurReq-saCurAct; - STAmount saCurIn = STAmount::divide(STAmount::multiply(saCur, uQualityIn, uint160(1)), uQualityOut, uint160(1)); + STAmount saCurIn = STAmount::divide(STAmount::multiply(saCur, uQualityOut, CURRENCY_ONE), uQualityIn, CURRENCY_ONE); if (saCurIn >= saPrv) { @@ -2175,8 +2351,8 @@ void TransactionEngine::calcNodeFwd(const uint32 uQualityIn, const uint32 uQuali } else { - // A part of cur. All of prv. - STAmount saCurOut = STAmount::divide(STAmount::multiply(saPrv, uQualityOut, uint160(1)), uQualityIn, uint160(1)); + // A part of cur. All of prv. (cur as driver) + STAmount saCurOut = STAmount::divide(STAmount::multiply(saPrv, uQualityIn, CURRENCY_ONE), uQualityOut, CURRENCY_ONE); saCurAct += saCurOut; saPrvAct = saPrvReq; @@ -2191,22 +2367,20 @@ void TransactionEngine::calcNodeFwd(const uint32 uQualityIn, const uint32 uQuali // --> [all]saWanted.IOURedeem // --> [all]saWanted.IOUIssue // --> [all]saAccount -// XXX This path becomes dirty if saSendMaxFirst must be changed. void TransactionEngine::calcPathForward(PathState::pointer pspCur) { -#if 0 - TransactionEngineResult terResult = tenUNKNOWN; unsigned int uIndex = 0; unsigned int uEnd = pspCur->vpnNodes.size(); unsigned int uLast = uEnd - 1; - while (tenUNKNOWN == terResult && uIndex != uEnd) + while (uIndex != uEnd) { // Calculate (1) sending by fullfilling next wants and (2) setting current wants. - paymentNode& prvPN = uIndex ? pnNodes[uIndex-1] : pnNodes[0]; - paymentNode& curPN = pnNodes[uIndex]; + paymentNode& prvPN = uIndex ? pspCur->vpnNodes[uIndex-1] : pspCur->vpnNodes[0]; + paymentNode& curPN = pspCur->vpnNodes[uIndex]; + paymentNode& nxtPN = uIndex == uLast ? pspCur->vpnNodes[uLast] : pspCur->vpnNodes[uIndex+1]; // XXX Assume rippling. @@ -2218,8 +2392,8 @@ void TransactionEngine::calcPathForward(PathState::pointer pspCur) STAmount& saCurIssueReq = curPN.saRevIssue; STAmount& saCurIssueAct = curPN.saFwdIssue; - STAmount& saCurSendMaxReq = curPN.saSendMax; - STAmount saCurSendMaxAct; + STAmount& saCurSendMaxReq = pspCur->saInReq; + STAmount& saCurSendMaxAct = pspCur->saInAct; if (saCurRedeemReq) { @@ -2238,50 +2412,55 @@ void TransactionEngine::calcPathForward(PathState::pointer pspCur) else if (uIndex == uLast) { // Last node. Accept all funds. Calculate amount actually to credit. - uint32 uQualityIn = peekQualityIn(curPN.uAccountID, prvPN.uAccountID); + uint32 uQualityIn = rippleQualityIn(curPN.uAccountID, prvPN.uAccountID, curPN.uCurrencyID); STAmount& saPrvRedeemReq = prvPN.saFwdRedeem; STAmount& saPrvIssueReq = prvPN.saFwdIssue; STAmount saPrvIssueAct = uQualityIn >= QUALITY_ONE - ? saPrvIssueReq // No fee. - : multiply(saPrvIssueReq, uQualityIn, _); // Fee. - STAmount& saCurReceive = curPN.saReceive; + ? saPrvIssueReq // No fee. + : STAmount::multiply(saPrvIssueReq, uQualityIn, curPN.uCurrencyID); // Fee. + STAmount& saCurReceive = pspCur->saOutAct; // Amount to credit. saCurReceive = saPrvRedeemReq+saPrvIssueAct; // Actually receive. - rippleCredit(curPN.uAccountID, prvPN.uAccountID, saPrvRedeemReq + saPrvIssueReq); + rippleCredit(curPN.uAccountID, prvPN.uAccountID, saCurReceive); } else { // Handle middle node. // The previous nodes tells want it wants to push through to current. - // The current node know what it woud push through next. + // The current node know what it would push through next. // Determine for the current node: // - Output to next node minus fees. // Perform balance adjustment with previous. // All of previous output is consumed. - STAmount saSrcRedeem; // To start, redeeming none. - STAmount saSrcIssue; // To start, issuing none. - STAmount saDstRedeem; + uint160& uCurrencyID = curPN.uCurrencyID; - // Have funds in previous node to transfer. - uint32 uQualityIn = peekQualityIn(curPN.uAccountID, prvPN.uAccountID); - uint32 uQualityOut = peekQualityOut(curPN.uAccountID, nxtPN.uAccountID); + uint160& uPrvAccountID = prvPN.uAccountID; + uint160& uCurAccountID = curPN.uAccountID; + uint160& uNxtAccountID = nxtPN.uAccountID; STAmount& saPrvRedeemReq = prvPN.saFwdRedeem; STAmount saPrvRedeemAct; - STAmount& saCurRedeemReq = curPN.saRevRedeem; - STAmount& saCurRedeemAct = curPN.saFwdRedeem; STAmount& saPrvIssueReq = prvPN.saFwdIssue; STAmount saPrvIssueAct; + STAmount& saCurRedeemReq = curPN.saRevRedeem; + STAmount& saCurRedeemAct = curPN.saFwdRedeem; + STAmount& saCurIssueReq = curPN.saRevIssue; + STAmount& saCurIssueAct = curPN.saFwdIssue; + + // Have funds in previous node to transfer. + uint32 uQualityIn = rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID); + uint32 uQualityOut = rippleQualityOut(uCurAccountID, uNxtAccountID, uCurrencyID); + // Previous redeem part 1: redeem -> redeem if (saPrvRedeemReq != saPrvRedeemAct) // Previous wants to redeem. To next must be ok. { // Rate : 1.0 : quality out - calcNodeFwd(QUALITY_ONE, uQualityOut, saPrvRedeemReq, saCurRedeemReq, saPrvRedeemAct, saCurRedeemAct); + calcNodeRipple(QUALITY_ONE, uQualityOut, saPrvRedeemReq, saCurRedeemReq, saPrvRedeemAct, saCurRedeemAct); } // Previous redeem part 2: redeem -> issue. @@ -2290,8 +2469,8 @@ void TransactionEngine::calcPathForward(PathState::pointer pspCur) if (saPrvRedeemReq != saPrvRedeemAct // Previous still wants to redeem. && saCurRedeemReq == saCurRedeemAct) // Current has more to redeem to next. { - // Rate : 1.0 : 1.0 + transfer_rate - calcNodeFwd(QUALITY_ONE, QUALITY_ONE+peekTransfer(curPN.uAccountID), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct); + // Rate : 1.0 : transfer_rate + calcNodeRipple(QUALITY_ONE, rippleTransfer(uCurAccountID), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct); } // Previous issue part 1: issue -> redeem @@ -2299,21 +2478,21 @@ void TransactionEngine::calcPathForward(PathState::pointer pspCur) && saCurRedeemReq == saCurRedeemAct) // Current has more to redeem to next. { // Rate: quality in : quality out - calcNodeFwd(uQualityIn, uQualityOut, saPrvIssueReq, saCurRedeemReq, saPrvIssueAct, saCurRedeemAct); + calcNodeRipple(uQualityIn, uQualityOut, saPrvIssueReq, saCurRedeemReq, saPrvIssueAct, saCurRedeemAct); } // Previous issue part 2 : issue -> issue if (saPrvIssueReq != saPrvIssueAct) // Previous wants to issue. To next must be ok. { // Rate: quality in : 1.0 - calcNodeFwd(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurIssueReq, saPrvIssueAct, saCurIssueAct); + calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurIssueReq, saPrvIssueAct, saCurIssueAct); } // Adjust prv --> cur balance : take all inbound - rippleCredit(cur, prv, saPrvRedeemReq + saPrvIssueReq); + // XXX Currency must be in amount. + rippleCredit(uCurAccountID, uPrvAccountID, saPrvRedeemReq + saPrvIssueReq); } } -#endif } bool PathState::less(const PathState::pointer& lhs, const PathState::pointer& rhs) @@ -2324,8 +2503,8 @@ bool PathState::less(const PathState::pointer& lhs, const PathState::pointer& rh return lhs->uQuality > rhs->uQuality; // Bigger is worse. // Best quanity is second rank. - if (lhs->saOut != rhs->saOut) - return lhs->saOut < rhs->saOut; // Smaller is worse. + 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. @@ -2345,15 +2524,17 @@ PathState::PathState( { lesEntries = lesSource.duplicate(); + saOutReq = saSend; + saInReq = saSendMax; + paymentNode pnFirst; paymentNode pnLast; - pnLast.bPartialPayment = bPartialPayment; - pnLast.uAccount = uReceiverID; - pnLast.saWanted = saSend; + pnLast.uAccountID = uReceiverID; + pnLast.uCurrencyID = saOutReq.getCurrency(); - pnFirst.uAccount = uSenderID; - pnFirst.saWanted = saSendMax; + pnFirst.uAccountID = uSenderID; + pnFirst.uCurrencyID = saSendMax.getCurrency(); vpnNodes.push_back(pnFirst); vpnNodes.push_back(pnLast); diff --git a/src/TransactionEngine.h b/src/TransactionEngine.h index 6143468d1..e246895cc 100644 --- a/src/TransactionEngine.h +++ b/src/TransactionEngine.h @@ -27,6 +27,7 @@ enum TransactionEngineResult tenBAD_GEN_AUTH, tenBAD_ISSUER, tenBAD_OFFER, + tenBAD_PATH, tenBAD_PATH_COUNT, tenBAD_PUBLISH, tenBAD_SET_ID, @@ -100,24 +101,19 @@ enum TransactionEngineParams }; typedef struct { - bool bPartialPayment; // --> uint16 uFlags; // --> from path - uint160 uAccount; // --> recieving/sending account + uint160 uAccountID; // --> recieving/sending account + uint160 uCurrencyID; // --> currency to recieve - STAmount saSendMax; // --> First node: most to send. - STAmount saRecieve; // <-- Last node: Value received (minus fees) from upstream. + // Computed by Reverse. + STAmount saRevRedeem; // <-- Amount to redeem to next. + STAmount saRevIssue; // <-- Amount to issue to next limited by credit and outstanding IOUs. + // ? STAmount saSend; // <-- Stamps this node will send downstream. - STAmount saRevRedeem; // <-- Computed amount node needs at most redeem. - STAmount saRevIssue; // <-- Computed amount node ____ - STAmount saCurRedeem; // <-- Amount node will redeem to next. - STAmount saCurIssue; // <-- Amount node will issue to next. - - STAmount saWanted; // <-- What this node wants from upstream. - - STAmount saSend; // <-- Stamps this node will send downstream. - - STAmount saXNSRecieve; // Amount stamps to receive. + // Computed by forward. + STAmount saFwdRedeem; // <-- Amount node will redeem to next. + STAmount saFwdIssue; // <-- Amount node will issue to next. } paymentNode; // Hold a path state under incremental application. @@ -132,8 +128,10 @@ public: int mIndex; uint64 uQuality; // 0 = none. - STAmount saIn; - STAmount saOut; + 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). bool bDirty; // Path not computed. PathState( @@ -204,39 +202,46 @@ private: STAmount& saTakerGot); protected: - Ledger::pointer mLedger; - uint64 mLedgerParentCloseTime; + Ledger::pointer mLedger; + uint64 mLedgerParentCloseTime; - uint160 mTxnAccountID; - SLE::pointer mTxnAccount; + uint160 mTxnAccountID; + SLE::pointer mTxnAccount; boost::unordered_set mUnfunded; // Indexes that were found unfunded. - SLE::pointer entryCreate(LedgerEntryType letType, const uint256& uIndex); - SLE::pointer entryCache(LedgerEntryType letType, const uint256& uIndex); - void entryDelete(SLE::pointer sleEntry, bool unfunded = false); - void entryModify(SLE::pointer sleEntry); + SLE::pointer entryCreate(LedgerEntryType letType, const uint256& uIndex); + SLE::pointer entryCache(LedgerEntryType letType, const uint256& uIndex); + void entryDelete(SLE::pointer sleEntry, bool unfunded = false); + void entryModify(SLE::pointer sleEntry); - void entryReset(); + void entryReset(); - STAmount rippleHolds(const uint160& uAccountID, const uint160& uCurrency, const uint160& uIssuerID); - STAmount rippleTransit(const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID, const STAmount& saAmount); - STAmount rippleSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount); + uint32 rippleTransfer(const uint160& uIssuerID); + STAmount rippleBalance(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); + uint32 rippleQualityOut(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID); - STAmount accountHolds(const uint160& uAccountID, const uint160& uCurrency, const uint160& uIssuerID); - STAmount accountSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount); - STAmount accountFunds(const uint160& uAccountID, const STAmount& saDefault); + STAmount rippleHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID); + STAmount rippleTransit(const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID, const STAmount& saAmount); + void rippleCredit(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount); + STAmount rippleSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount); + + STAmount accountHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID); + STAmount 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 pathApply(PathState::pointer pspCur); void pathNext(PathState::pointer pspCur); - void calcNodeFwd(const uint32 uQualityIn, const uint32 uQualityOut, + void calcNodeRipple(const uint32 uQualityIn, const uint32 uQualityOut, const STAmount& saPrvReq, const STAmount& saCurReq, STAmount& saPrvAct, STAmount& saCurAct); bool calcPathReverse(PathState::pointer pspCur); void calcPathForward(PathState::pointer pspCur); - void txnWrite(); + void txnWrite(); TransactionEngineResult offerDelete(const SLE::pointer& sleOffer, const uint256& uOfferIndex, const uint160& uOwnerID); From 1a6a6231cc040c8ee372156530796aff80830ac0 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Fri, 3 Aug 2012 14:57:52 -0700 Subject: [PATCH 5/7] Add path expansion to transaction engine. --- src/SerializedTypes.cpp | 4 +- src/SerializedTypes.h | 2 +- src/TransactionEngine.cpp | 97 +++++++++++++++++++++++++++++++++++---- src/TransactionEngine.h | 12 +++-- 4 files changed, 98 insertions(+), 17 deletions(-) diff --git a/src/SerializedTypes.cpp b/src/SerializedTypes.cpp index 1a5bdd3bf..df263827e 100644 --- a/src/SerializedTypes.cpp +++ b/src/SerializedTypes.cpp @@ -318,13 +318,13 @@ STPathSet* STPathSet::construct(SerializerIterator& s, const char *name) return new STPathSet(name, paths); } } - else if (iType & STPathElement::typeStrayBits) + else if (iType & ~STPathElement::typeValidBits) { throw std::runtime_error("bad path element"); } else { - bool bAccount = !!(iType & STPathElement::typeAccount); + bool bAccount = !!(iType & STPathElement::typeAccount); bool bCurrency = !!(iType & STPathElement::typeCurrency); bool bIssuer = !!(iType & STPathElement::typeIssuer); diff --git a/src/SerializedTypes.h b/src/SerializedTypes.h index 392966fcb..48a7069da 100644 --- a/src/SerializedTypes.h +++ b/src/SerializedTypes.h @@ -526,7 +526,7 @@ public: typeCurrency = 0x10, // Currency follows. typeIssuer = 0x20, // Issuer follows. typeBoundary = 0xFF, // Boundary between alternate paths. - typeStrayBits = 0xC0, // Bits that must be zero. + typeValidBits = 0x3E, // Bits that may be non-zero. }; protected: diff --git a/src/TransactionEngine.cpp b/src/TransactionEngine.cpp index 25a0cef54..f2b4575b9 100644 --- a/src/TransactionEngine.cpp +++ b/src/TransactionEngine.cpp @@ -22,7 +22,8 @@ #define RIPPLE_PATHS_MAX 3 #define QUALITY_ONE 100000000 // 10e9 -#define CURRENCY_ONE uint160(1) +#define CURRENCY_ONE uint160(1) // Used as a place holder +#define ACCOUNT_ONE uint160(1) // Used as a place holder // static STAmount saOne(CURRENCY_ONE, 1, 0); @@ -2510,6 +2511,85 @@ bool PathState::less(const PathState::pointer& lhs, const PathState::pointer& rh return lhs->mIndex > rhs->mIndex; // Bigger is worse. } +// <-- bValid: true, if node is valid. +bool PathState::pushNode(int iType, uint160 uAccountID, uint160 uCurrencyID, uint160 uIssuerID) +{ + paymentNode pnCur; + bool bFirst = vpnNodes.empty(); + const paymentNode& pnPrv = bFirst ? paymentNode() : vpnNodes.back(); + bool bAccount = !!(iType & STPathElement::typeAccount); + bool bCurrency = !!(iType & STPathElement::typeCurrency); + bool bIssuer = !!(iType & STPathElement::typeIssuer); + bool bRedeem = !!(iType & STPathElement::typeRedeem); + bool bIssue = !!(iType & STPathElement::typeIssue); + bool bValid = true; + + pnCur.uFlags = iType; + + if (iType & ~STPathElement::typeValidBits) + { + bValid = false; + } + else if (bAccount) + { + if (bRedeem || bIssue) + { + // Account link + + pnCur.uAccountID = uAccountID; + pnCur.uCurrencyID = bCurrency ? uCurrencyID : pnPrv.uCurrencyID; + pnCur.uIssuerID = bIssuer ? uIssuerID : uAccountID; + + // An intermediate node may be implied. + + // An offer may be implied + if (uCurrencyID != pnPrv.uCurrencyID) + { + // Implied preceeding offer. + + bValid = pushNode( + 0, + ACCOUNT_ONE, + CURRENCY_ONE, // Inherit from previous + ACCOUNT_ONE); // Inherit from previous + } + else if (uIssuerID != pnPrv.uIssuerID) + { + // Implied preceeding account. + + bValid = pushNode( + STPathElement::typeAccount + | STPathElement::typeRedeem + | STPathElement::typeIssue, + uIssuerID, + CURRENCY_ONE, // Inherit from previous + ACCOUNT_ONE); // Default same as account. + } + } + else + { + bValid = false; + } + } + else + { + // Offer link + if (bRedeem || bIssue) + { + bValid = false; + } + else + { + pnCur.uCurrencyID = bCurrency ? uCurrencyID : pnPrv.uCurrencyID; + pnCur.uIssuerID = bIssuer ? uIssuerID : uAccountID; + } + } + + vpnNodes.push_back(pnCur); + + return bValid; +} + PathState::PathState( int iIndex, const LedgerEntrySet& lesSource, @@ -2527,17 +2607,14 @@ PathState::PathState( saOutReq = saSend; saInReq = saSendMax; - paymentNode pnFirst; - paymentNode pnLast; + pushNode(STPathElement::typeAccount, uSenderID, saSendMax.getCurrency(), saSendMax.getIssuer()); - pnLast.uAccountID = uReceiverID; - pnLast.uCurrencyID = saOutReq.getCurrency(); + BOOST_FOREACH(const STPathElement& speElement, spSourcePath) + { + pushNode(speElement.getNodeType(), speElement.getAccountID(), speElement.getCurrency(), speElement.getIssuerID()); + } - pnFirst.uAccountID = uSenderID; - pnFirst.uCurrencyID = saSendMax.getCurrency(); - - vpnNodes.push_back(pnFirst); - vpnNodes.push_back(pnLast); + pushNode(STPathElement::typeAccount, uReceiverID, saOutReq.getCurrency(), saOutReq.getIssuer()); } // Calculate the next increment of a path. diff --git a/src/TransactionEngine.h b/src/TransactionEngine.h index e246895cc..0f3aed767 100644 --- a/src/TransactionEngine.h +++ b/src/TransactionEngine.h @@ -101,10 +101,12 @@ enum TransactionEngineParams }; typedef struct { - uint16 uFlags; // --> from path + uint16 uFlags; // --> From path. - uint160 uAccountID; // --> recieving/sending account - uint160 uCurrencyID; // --> currency to recieve + uint160 uAccountID; // --> Recieving/sending account. + uint160 uCurrencyID; // --> Currency to recieve. + // --- For offer's next has currency out. + uint160 uIssuerID; // --> Currency's issuer // Computed by Reverse. STAmount saRevRedeem; // <-- Amount to redeem to next. @@ -119,10 +121,12 @@ typedef struct { // Hold a path state under incremental application. class PathState { +protected: + bool pushNode(int iType, uint160 uAccountID, uint160 uCurrencyID, uint160 uIssuerID); + public: typedef boost::shared_ptr pointer; - std::vector vpnNodes; LedgerEntrySet lesEntries; From 622066f7e362992324b3a6bbddc3a59f14ac9840 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Fri, 10 Aug 2012 12:58:59 -0700 Subject: [PATCH 6/7] Work towards ripple backend. --- src/TransactionEngine.cpp | 1361 +++++++++++++++++++++++-------------- src/TransactionEngine.h | 18 +- 2 files changed, 881 insertions(+), 498 deletions(-) diff --git a/src/TransactionEngine.cpp b/src/TransactionEngine.cpp index f2b4575b9..0f22432f1 100644 --- a/src/TransactionEngine.cpp +++ b/src/TransactionEngine.cpp @@ -22,10 +22,12 @@ #define RIPPLE_PATHS_MAX 3 #define QUALITY_ONE 100000000 // 10e9 +#define CURRENCY_XNS uint160(0) #define CURRENCY_ONE uint160(1) // Used as a place holder +#define ACCOUNT_XNS uint160(0) #define ACCOUNT_ONE uint160(1) // Used as a place holder -// static STAmount saOne(CURRENCY_ONE, 1, 0); +static STAmount saOne(CURRENCY_ONE, 1, 0); bool transResultInfo(TransactionEngineResult terCode, std::string& strToken, std::string& strHuman) { @@ -271,7 +273,7 @@ STAmount TransactionEngine::accountFunds(const uint160& uAccountID, const STAmou } // Calculate transit fee. -STAmount TransactionEngine::rippleTransit(const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID, const STAmount& saAmount) +STAmount TransactionEngine::rippleTransfer(const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID, const STAmount& saAmount) { STAmount saTransitFee; @@ -353,7 +355,7 @@ STAmount TransactionEngine::rippleSend(const uint160& uSenderID, const uint160& { // Sending 3rd party IOUs: transit. - STAmount saTransitFee = rippleTransit(uSenderID, uReceiverID, uIssuerID, saAmount); + STAmount saTransitFee = rippleTransfer(uSenderID, uReceiverID, uIssuerID, saAmount); saActual = saTransitFee.isZero() ? saAmount : saAmount+saTransitFee; @@ -684,10 +686,11 @@ TransactionEngineResult TransactionEngine::dirDelete( return terSUCCESS; } +// Return the first entry and advance uDirEntry. // <-- true, if had a next entry. bool TransactionEngine::dirFirst( const uint256& uRootIndex, // --> Root of directory. - SLE::pointer& sleNode, // <-> current node + SLE::pointer& sleNode, // <-- current node unsigned int& uDirEntry, // <-- next entry uint256& uEntryIndex) // <-- The entry, if available. Otherwise, zero. { @@ -699,6 +702,7 @@ bool TransactionEngine::dirFirst( return TransactionEngine::dirNext(uRootIndex, sleNode, uDirEntry, uEntryIndex); } +// Return the current entry and advance uDirEntry. // <-- true, if had a next entry. bool TransactionEngine::dirNext( const uint256& uRootIndex, // --> Root of directory @@ -1712,7 +1716,7 @@ TransactionEngineResult TransactionEngine::doPasswordSet(const SerializedTransac return terResult; } -#ifdef WORK_IN_PROGRESS +#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. @@ -1748,8 +1752,7 @@ TransactionEngineResult calcOfferFill(paymentNode& pnSrc, paymentNode& pnDst, bo else { // Ripple funds. - { - prv->saSend = min(prv->account->saBalance(), cur->saWanted); + // Redeem to limit. terResult = calcOfferFill( accountHolds(pnSrc.saAccount, pnDst.saWanted.getCurrency(), pnDst.saWanted.getIssuer()), @@ -1778,7 +1781,9 @@ TransactionEngineResult calcOfferFill(paymentNode& pnSrc, paymentNode& pnDst, bo return terResult; } +#endif +#if 0 // Get the next offer limited by funding. // - Stop when becomes unfunded. void TransactionEngine::calcOfferBridgeNext( @@ -1859,7 +1864,7 @@ void TransactionEngine::calcOfferBridgeNext( // Offer fully funded. // Account transfering funds in to offer always pays inbound fees. - // + saOfferIn = saOfferGets; // XXX Add in fees? saOfferOut = saOfferPays; @@ -1886,15 +1891,413 @@ void TransactionEngine::calcOfferBridgeNext( } while (bNext); } +#endif +// <-- bSuccess: false= no transfer +bool TransactionEngine::calcNodeOfferRev( + unsigned int uIndex, // 0 < uIndex < uLast-1 + PathState::pointer pspCur, + bool bMultiQuality + ) +{ + bool bSuccess = false; + + paymentNode& prvPN = pspCur->vpnNodes[uIndex-1]; + paymentNode& curPN = pspCur->vpnNodes[uIndex]; + paymentNode& nxtPN = pspCur->vpnNodes[uIndex+1]; + + uint160& uPrvCurrencyID = prvPN.uCurrencyID; + uint160& uPrvIssuerID = prvPN.uIssuerID; + uint160& uCurCurrencyID = curPN.uCurrencyID; + uint160& uCurIssuerID = curPN.uIssuerID; + uint160& uNxtCurrencyID = nxtPN.uCurrencyID; + uint160& uNxtIssuerID = nxtPN.uIssuerID; + uint160& uNxtAccountID = nxtPN.uAccountID; + + STAmount saTransferRate = STAmount(CURRENCY_ONE, rippleTransfer(uCurIssuerID), -9); + + uint256 uDirectTip = Ledger::getBookBase(uPrvCurrencyID, uPrvIssuerID, uCurCurrencyID, uCurIssuerID); + uint256 uDirectEnd = Ledger::getQualityNext(uDirectTip); + bool bAdvance = !entryCache(ltDIR_NODE, uDirectTip); + + STAmount& saPrvDlvReq = prvPN.saRevDeliver; + STAmount saPrvDlvAct; + + STAmount& saCurDlvReq = curPN.saRevDeliver; // Reverse driver. + STAmount saCurDlvAct; + + while (!!uDirectTip // Have a quality. + && saCurDlvAct != saCurDlvReq) + { + // Get next quality. + if (bAdvance) + { + uDirectTip = mLedger->getNextLedgerIndex(uDirectTip, uDirectEnd); + } + else + { + bAdvance = true; + } + + if (!!uDirectTip) + { + // Do a directory. + // - Drive on computing saCurDlvAct to derive saPrvDlvAct. + SLE::pointer sleDirectDir = entryCache(ltDIR_NODE, uDirectTip); + STAmount saOfrRate = STAmount::setRate(Ledger::getQuality(uDirectTip)); // For correct ratio + unsigned int uEntry = 0; + uint256 uCurIndex; + + while (saCurDlvReq != saCurDlvAct // Have not met request. + && dirNext(uDirectTip, sleDirectDir, uEntry, uCurIndex)) + { + SLE::pointer sleCurOfr = entryCache(ltOFFER, uCurIndex); + uint160 uCurOfrAccountID = sleCurOfr->getIValueFieldAccount(sfAccount).getAccountID(); + STAmount saCurOfrOutReq = sleCurOfr->getIValueFieldAmount(sfTakerGets); + STAmount saCurOfrIn = sleCurOfr->getIValueFieldAmount(sfTakerPays); + // XXX Move issuer into STAmount + if (sleCurOfr->getIFieldPresent(sfGetsIssuer)) + saCurOfrOutReq.setIssuer(sleCurOfr->getIValueFieldAccount(sfGetsIssuer).getAccountID()); + + if (sleCurOfr->getIFieldPresent(sfPaysIssuer)) + saCurOfrIn.setIssuer(sleCurOfr->getIValueFieldAccount(sfPaysIssuer).getAccountID()); + + STAmount saCurOfrFunds = accountFunds(uCurOfrAccountID, saCurOfrOutReq); // Funds left. + + // XXX Offer is also unfunded if funding source previously mentioned. + if (!saCurOfrFunds) + { + // Offer is unfunded. + Log(lsINFO) << "calcNodeOffer: encountered unfunded offer"; + + // XXX Mark unfunded. + } + else if (sleCurOfr->getIFieldPresent(sfExpiration) && sleCurOfr->getIFieldU32(sfExpiration) <= mLedger->getParentCloseTimeNC()) + { + // Offer is expired. + Log(lsINFO) << "calcNodeOffer: encountered expired offer"; + + // XXX Mark unfunded. + } + else if (!!uNxtAccountID) + { + // Next is an account. + + STAmount saFeeRate = uCurOfrAccountID == uCurIssuerID || uNxtAccountID == uCurIssuerID + ? saOne + : saTransferRate; + bool bFee = saFeeRate != saOne; + + STAmount saOutBase = MIN(saCurOfrOutReq, saCurDlvReq-saCurDlvAct); // Limit offer out by needed. + STAmount saOutCost = MIN( + bFee + ? STAmount::multiply(saOutBase, saFeeRate, uCurCurrencyID) + : saOutBase, + saCurOfrFunds); // Limit cost by fees & funds. + STAmount saOutDlvAct = bFee + ? STAmount::divide(saOutCost, saFeeRate, uCurCurrencyID) + : saOutCost; // Out amount after fees. + STAmount saInDlvAct = STAmount::multiply(saOutDlvAct, saOfrRate, uPrvCurrencyID); // Compute input w/o fees required. + + saCurDlvAct += saOutDlvAct; // Portion of driver served. + saPrvDlvAct += saInDlvAct; // Portion needed in previous. + } + else + { + // Next is an offer. + + uint256 uNxtTip = Ledger::getBookBase(uCurCurrencyID, uCurIssuerID, uNxtCurrencyID, uNxtIssuerID); + uint256 uNxtEnd = Ledger::getQualityNext(uNxtTip); + bool bNxtAdvance = !entryCache(ltDIR_NODE, uNxtTip); + + while (!!uNxtTip // Have a quality. + && saCurDlvAct != saCurDlvReq) // Have more to do. + { + if (bNxtAdvance) + { + uNxtTip = mLedger->getNextLedgerIndex(uNxtTip, uNxtEnd); + } + else + { + bNxtAdvance = true; + } + + if (!!uNxtTip) + { + // Do a directory. + // - Drive on computing saCurDlvAct to derive saPrvDlvAct. + SLE::pointer sleNxtDir = entryCache(ltDIR_NODE, uNxtTip); +// ??? STAmount saOfrRate = STAmount::setRate(STAmount::getQuality(uNxtTip)); // For correct ratio + unsigned int uEntry = 0; + uint256 uNxtIndex; + + while (saCurDlvReq != saCurDlvAct // Have not met request. + && dirNext(uNxtTip, sleNxtDir, uEntry, uNxtIndex)) + { + // YYY This could combine offers with the same fee before doing math. + SLE::pointer sleNxtOfr = entryCache(ltOFFER, uNxtIndex); + uint160 uNxtOfrAccountID = sleNxtOfr->getIValueFieldAccount(sfAccount).getAccountID(); + STAmount saNxtOfrIn = sleNxtOfr->getIValueFieldAmount(sfTakerPays); + // XXX Move issuer into STAmount + if (sleNxtOfr->getIFieldPresent(sfPaysIssuer)) + saNxtOfrIn.setIssuer(sleCurOfr->getIValueFieldAccount(sfPaysIssuer).getAccountID()); + + STAmount saFeeRate = uCurOfrAccountID == uCurIssuerID || uNxtOfrAccountID == uCurIssuerID + ? saOne + : saTransferRate; + bool bFee = saFeeRate != saOne; + + STAmount saOutBase = MIN(saCurOfrOutReq, saCurDlvReq-saCurDlvAct); // Limit offer out by needed. + saOutBase = MIN(saOutBase, saNxtOfrIn); // Limit offer out by supplying offer. + STAmount saOutCost = MIN( + bFee + ? STAmount::multiply(saOutBase, saFeeRate, uCurCurrencyID) + : saOutBase, + saCurOfrFunds); // Limit cost by fees & funds. + STAmount saOutDlvAct = bFee + ? STAmount::divide(saOutCost, saFeeRate, uCurCurrencyID) + : saOutCost; // Out amount after fees. + STAmount saInDlvAct = STAmount::multiply(saOutDlvAct, saOfrRate, uPrvCurrencyID); // Compute input w/o fees required. + + saCurDlvAct += saOutDlvAct; // Portion of driver served. + saPrvDlvAct += saInDlvAct; // Portion needed in previous. + } + } + + // Do another nxt directory iff bMultiQuality + if (!bMultiQuality) + uNxtTip = 0; + } + } + } + } + + // Do another cur directory iff bMultiQuality + if (!bMultiQuality) + uDirectTip = 0; + } + + if (saPrvDlvAct) + { + saPrvDlvReq = saPrvDlvAct; // Adjust request. + bSuccess = true; + } + + return bSuccess; +} + +bool TransactionEngine::calcNodeOfferFwd( + unsigned int uIndex, // 0 < uIndex < uLast-1 + PathState::pointer pspCur, + bool bMultiQuality + ) +{ + bool bSuccess = false; + + paymentNode& prvPN = pspCur->vpnNodes[uIndex-1]; + paymentNode& curPN = pspCur->vpnNodes[uIndex]; + paymentNode& nxtPN = pspCur->vpnNodes[uIndex+1]; + + uint160& uPrvCurrencyID = prvPN.uCurrencyID; + uint160& uPrvIssuerID = prvPN.uIssuerID; + uint160& uCurCurrencyID = curPN.uCurrencyID; + uint160& uCurIssuerID = curPN.uIssuerID; + uint160& uNxtCurrencyID = nxtPN.uCurrencyID; + uint160& uNxtIssuerID = nxtPN.uIssuerID; + + uint160& uNxtAccountID = nxtPN.uAccountID; + STAmount saTransferRate = STAmount(CURRENCY_ONE, rippleTransfer(uCurIssuerID), -9); + + uint256 uDirectTip = Ledger::getBookBase(uPrvCurrencyID, uPrvIssuerID, uCurCurrencyID, uCurIssuerID); + uint256 uDirectEnd = Ledger::getQualityNext(uDirectTip); + bool bAdvance = !entryCache(ltDIR_NODE, uDirectTip); + + STAmount& saPrvDlvReq = prvPN.saFwdDeliver; // Forward driver. + STAmount saPrvDlvAct; + + STAmount& saCurDlvReq = curPN.saFwdDeliver; + STAmount saCurDlvAct; + + while (!!uDirectTip // Have a quality. + && saPrvDlvAct != saPrvDlvReq) + { + // Get next quality. + if (bAdvance) + { + uDirectTip = mLedger->getNextLedgerIndex(uDirectTip, uDirectEnd); + } + else + { + bAdvance = true; + } + + if (!!uDirectTip) + { + // Do a directory. + // - Drive on computing saPrvDlvAct to derive saCurDlvAct. + SLE::pointer sleDirectDir = entryCache(ltDIR_NODE, uDirectTip); + STAmount saOfrRate = STAmount::setRate(Ledger::getQuality(uDirectTip)); // For correct ratio + unsigned int uEntry = 0; + uint256 uCurIndex; + + while (saPrvDlvReq != saPrvDlvAct // Have not met request. + && dirNext(uDirectTip, sleDirectDir, uEntry, uCurIndex)) + { + SLE::pointer sleCurOfr = entryCache(ltOFFER, uCurIndex); + uint160 uCurOfrAccountID = sleCurOfr->getIValueFieldAccount(sfAccount).getAccountID(); + STAmount saCurOfrOutReq = sleCurOfr->getIValueFieldAmount(sfTakerGets); + STAmount saCurOfrInReq = sleCurOfr->getIValueFieldAmount(sfTakerPays); + // XXX Move issuer into STAmount + if (sleCurOfr->getIFieldPresent(sfGetsIssuer)) + saCurOfrOutReq.setIssuer(sleCurOfr->getIValueFieldAccount(sfGetsIssuer).getAccountID()); + + if (sleCurOfr->getIFieldPresent(sfPaysIssuer)) + saCurOfrInReq.setIssuer(sleCurOfr->getIValueFieldAccount(sfPaysIssuer).getAccountID()); + STAmount saCurOfrInAct; + STAmount saCurOfrFunds = accountFunds(uCurOfrAccountID, saCurOfrOutReq); // Funds left. + + saCurOfrInReq = MIN(saCurOfrInReq, saPrvDlvReq-saPrvDlvAct); + + if (!!uNxtAccountID) + { + // Next is an account. + + STAmount saFeeRate = uCurOfrAccountID == uCurIssuerID || uNxtAccountID == uCurIssuerID + ? saOne + : saTransferRate; + bool bFee = saFeeRate != saOne; + + STAmount saOutPass = STAmount::divide(saCurOfrInReq, saOfrRate, uCurCurrencyID); + STAmount saOutBase = MIN(saCurOfrOutReq, saOutPass); // Limit offer out by needed. + STAmount saOutCost = MIN( + bFee + ? STAmount::multiply(saOutBase, saFeeRate, uCurCurrencyID) + : saOutBase, + saCurOfrFunds); // Limit cost by fees & funds. + STAmount saOutDlvAct = bFee + ? STAmount::divide(saOutCost, saFeeRate, uCurCurrencyID) + : saOutCost; // Out amount after fees. + STAmount saInDlvAct = STAmount::multiply(saOutDlvAct, saOfrRate, uCurCurrencyID); // Compute input w/o fees required. + + saCurDlvAct += saOutDlvAct; // Portion of driver served. + saPrvDlvAct += saInDlvAct; // Portion needed in previous. + } + else + { + // Next is an offer. + + uint256 uNxtTip = Ledger::getBookBase(uCurCurrencyID, uCurIssuerID, uNxtCurrencyID, uNxtIssuerID); + uint256 uNxtEnd = Ledger::getQualityNext(uNxtTip); + bool bNxtAdvance = !entryCache(ltDIR_NODE, uNxtTip); + + while (!!uNxtTip // Have a quality. + && saPrvDlvAct != saPrvDlvReq) // Have more to do. + { + if (bNxtAdvance) + { + uNxtTip = mLedger->getNextLedgerIndex(uNxtTip, uNxtEnd); + } + else + { + bNxtAdvance = true; + } + + if (!!uNxtTip) + { + // Do a directory. + // - Drive on computing saCurDlvAct to derive saPrvDlvAct. + SLE::pointer sleNxtDir = entryCache(ltDIR_NODE, uNxtTip); +// ??? STAmount saOfrRate = STAmount::setRate(STAmount::getQuality(uNxtTip)); // For correct ratio + unsigned int uEntry = 0; + uint256 uNxtIndex; + + while (saPrvDlvReq != saPrvDlvAct // Have not met request. + && dirNext(uNxtTip, sleNxtDir, uEntry, uNxtIndex)) + { + // YYY This could combine offers with the same fee before doing math. + SLE::pointer sleNxtOfr = entryCache(ltOFFER, uNxtIndex); + uint160 uNxtOfrAccountID = sleNxtOfr->getIValueFieldAccount(sfAccount).getAccountID(); + STAmount saNxtOfrIn = sleNxtOfr->getIValueFieldAmount(sfTakerPays); + // XXX Move issuer into STAmount + if (sleNxtOfr->getIFieldPresent(sfPaysIssuer)) + saNxtOfrIn.setIssuer(sleCurOfr->getIValueFieldAccount(sfPaysIssuer).getAccountID()); + + STAmount saFeeRate = uCurOfrAccountID == uCurIssuerID || uNxtOfrAccountID == uCurIssuerID + ? saOne + : saTransferRate; + bool bFee = saFeeRate != saOne; + + STAmount saInBase = saCurOfrInReq-saCurOfrInAct; + STAmount saOutPass = STAmount::divide(saInBase, saOfrRate, uCurCurrencyID); + STAmount saOutBase = MIN(saCurOfrOutReq, saOutPass); // Limit offer out by needed. + saOutBase = MIN(saOutBase, saNxtOfrIn); // Limit offer out by supplying offer. + STAmount saOutCost = MIN( + bFee + ? STAmount::multiply(saOutBase, saFeeRate, uCurCurrencyID) + : saOutBase, + saCurOfrFunds); // Limit cost by fees & funds. + STAmount saOutDlvAct = bFee + ? STAmount::divide(saOutCost, saFeeRate, uCurCurrencyID) + : saOutCost; // Out amount after fees. + STAmount saInDlvAct = STAmount::multiply(saOutDlvAct, saOfrRate, uPrvCurrencyID); // Compute input w/o fees required. + + saCurOfrInAct += saOutDlvAct; // Portion of driver served. + saPrvDlvAct += saOutDlvAct; // Portion needed in previous. + saCurDlvAct += saInDlvAct; // Portion of driver served. + } + } + + // Do another nxt directory iff bMultiQuality + if (!bMultiQuality) + uNxtTip = 0; + } + } + } + } + + // Do another cur directory iff bMultiQuality + if (!bMultiQuality) + uDirectTip = 0; + } + + if (saCurDlvAct) + { + saCurDlvReq = saCurDlvAct; // Adjust request. + bSuccess = true; + } + + return bSuccess; +} + +#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. -void TransactionEngine::calcNodeOfferReverse( - const uint160& uPayCurrency, - const uint160& uPayIssuerID, - const STAmount& saWanted, // Driver +// 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 @@ -1902,11 +2305,13 @@ void TransactionEngine::calcNodeOfferReverse( { TransactionEngineResult terResult = tenUNKNOWN; + // 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. @@ -1927,397 +2332,177 @@ void TransactionEngine::calcNodeOfferReverse( unsigned int uOutEntry; saPay.zero(); - saPay.setCurrency(uPayCurrency); - saPay.setIssuer(uPayIssuerID); + saPay.setCurrency(uPrvCurrencyID); + saPay.setIssuer(uPrvIssuerID); saNeed = saWanted; - if (!saWanted.isNative() && !uTakerCurrency.isZero()) + if (!uCurCurrencyID && !uPrvCurrencyID) { - // Bridging - uInTip = Ledger::getBookBase(uPayCurrency, uPayIssuerID, uint160(0), uint160(0)); + // Bridging: Neither currency is XNS. + uInTip = Ledger::getBookBase(uPrvCurrencyID, uPrvIssuerID, CURRENCY_XNS, ACCOUNT_XNS); uInEnd = Ledger::getQualityNext(uInTip); - uOutTip = Ledger::getBookBase(uint160(0), uint160(0), saWanted.getCurrency(), saWanted.getIssuer()); + uOutTip = Ledger::getBookBase(CURRENCY_XNS, ACCOUNT_XNS, uCurCurrencyID, uCurIssuerID); uOutEnd = Ledger::getQualityNext(uInTip); } - while (tenUNKNOWN == terResult) + // 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)) { - if (saNeed == saWanted) + // 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) { - // Got all. - saGot = saWanted; - terResult = terSUCCESS; - } - else - { - // Calculate next tips, if needed. - - if (bDirectNext) - { - // Find next direct offer. - uDirectTip = mLedger->getNextLedgerIndex(uDirectTip, uDirectEnd); - if (!!uDirectTip) - { - sleDirectDir = entryCache(ltDIR_NODE, uDirectTip); - - // XXX Need to calculate the real quality: including fees. - uDirectQuality = STAmount::getQualityNext(uDirectTip); - } - - bDirectNext = false; - } - - 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; - } + // Get the next funded offer. + offerBridgeNext(uInIndex, uInEnd, uInEntry, saInIn, saInOut); // Get offer limited by funding. + bInNext = false; + } - if (bOutNext) - { + 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 = terSUCCESS; - } - - if (terSUCCESS != 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; - } - } - } + offerNext(uOutIndex, uOutEnd, uOutEntry, saOutIn, saOutOut); + bOutNext = false; } - } -} -#endif -// From the destination work towards the source calculating how much must be asked for. -// --> bAllowPartial: If false, fail if can't meet requirements. -// <-- bSuccess: true=success, false=insufficient funds / liqudity. -// <-> pnNodes: -// --> [end]saWanted.mAmount -// --> [all]saWanted.mCurrency -// --> [all]saAccount -// <-> [0]saWanted.mAmount : --> limit, <-- actual -// XXX Disallow looping. -// XXX With multiple path and due to offers, must consider consumed. -bool TransactionEngine::calcPathReverse(PathState::pointer pspCur) -{ - TransactionEngineResult terResult = tenUNKNOWN; - - unsigned int uLast = pspCur->vpnNodes.size() - 1; - unsigned int uIndex = pspCur->vpnNodes.size(); - - while (tenUNKNOWN == terResult && uIndex--) - { - // Calculate: - // (1) saPrvRedeem & saPrvIssue from saCurRevRedeem & saCurRevIssue. - // (2) saCurWanted by summing saRevRedeem & saRevIssue. <-- XXX might not need this as it can be infered. - // XXX We might not need to do 0. - - paymentNode& prvPN = uIndex ? pspCur->vpnNodes[uIndex-1] : pspCur->vpnNodes[0]; - paymentNode& curPN = pspCur->vpnNodes[uIndex]; - paymentNode& nxtPN = uIndex == uLast ? pspCur->vpnNodes[uLast] : pspCur->vpnNodes[uIndex+1]; - bool bAccount = !!(curPN.uFlags & STPathElement::typeAccount); - bool bRedeem = !!(curPN.uFlags & STPathElement::typeRedeem); - bool bIssue = !!(curPN.uFlags & STPathElement::typeIssue); - uint160& uPrvAccountID = prvPN.uAccountID; - uint160& uCurAccountID = curPN.uAccountID; - uint160& uNxtAccountID = nxtPN.uAccountID; - uint160& uCurrencyID = curPN.uCurrencyID; - - if (uIndex == uLast) + if (!uInIndex || !uOutIndex) { - // saCurWanted set by caller. - uint32 uQualityIn = rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID); - STAmount saPrvBalance = rippleBalance(uCurAccountID, uPrvAccountID, uCurrencyID); - STAmount saPrvLimit = rippleLimit(uCurAccountID, uPrvAccountID, uCurAccountID); - - STAmount saPrvRedeemReq = saPrvBalance.isNative() ? -saPrvBalance : STAmount(uCurrencyID, 0); - STAmount& saPrvRedeemAct = prvPN.saRevRedeem; // What previous should redeem with cur. - - STAmount& saPrvIssueAct = prvPN.saRevIssue; - STAmount saPrvIssueReq = saPrvBalance.isPositive() ? saPrvLimit - saPrvBalance : saPrvLimit; - - STAmount saCurWantedReq = pspCur->saOutReq; - STAmount saCurWantedAct; - - // Calculate redeem - if (bRedeem - && saPrvRedeemReq) // Previous has IOUs to redeem. - { - // Redeem at 1:1 - saCurWantedAct = MIN(saPrvRedeemReq, saCurWantedReq); - saPrvRedeemAct = saCurWantedAct; - } - - // Calculate issuing. - if (bIssue - && saCurWantedReq != saCurWantedAct // Need more. - && saPrvIssueReq) // Will accept IOUs. - { - // Rate: quality in : 1.0 - calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurWantedReq, saPrvIssueAct, saCurWantedAct); - } - - if (!saCurWantedAct) - { - // Must have processed something. - terResult = tenBAD_AMOUNT; - } - } - else if (!bAccount) - { - // Offer node. - // Ripple or transfering from previous node through this offer to next node. - // Current node has a credit line with next node. - // Next node will receive either its own IOUs or this nodes IOUs. - // We limit what this node sends by this nodes redeem and issue max. - // This allows path lists to be strictly redeem. - // XXX Make sure offer book was not previously mentioned. -#if 0 - uint160 uPrvCurrency = curPN.uFlags & PF_WANTED_CURRENCY - ? curPN->saWanted.getCurrency() - : saSendMax.getCurrency(); - - uint160 uPrvIssuer = curPN.uFlags & PF_WANTED_ISSUER - ? curPN->saWanted.getIssuer() - : saSendMax.getIssuer(); - - calcNodeOfferReverse( - uTakerCurrency, - uTakerIssuer, - nxtPN->saWanted, // Driver. - - uTakerPaid, - uTakerGot, - uOwnerPaid, - uOwnerGot, - ); - - if (uOwnerPaid.isZero()) - { - terResult = terZERO; // Path contributes nothing. - } - else - { - // Update wanted. - - // Save sent amount - } -#endif - } - // Ripple through account. - else if (!bIssue && !bRedeem) - { - // XXX This might be checked sooner. - terResult = tenBAD_PATH; // Must be able to redeem and issue. - } - else if (!uIndex) - { - // Done. No previous. We know what we would like to redeem and issue. - nothing(); + bBridge = false; // No more offers to bridge. } else { - // Rippling through this accounts balances. - // No currency change. - // Given saCurRedeem & saCurIssue figure out saPrvRedeem & saPrvIssue. + // Have bridge in and out entries. + // Calculate bridge rate. Out offer pay ripple fee. In offer fee is added to in cost. - uint32 uQualityIn = rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID); - uint32 uQualityOut = rippleQualityOut(uCurAccountID, uNxtAccountID, uCurrencyID); - STAmount saPrvBalance = rippleBalance(uCurAccountID, uPrvAccountID, uCurrencyID); - STAmount saPrvLimit = rippleLimit(uCurAccountID, uPrvAccountID, uCurrencyID); + saBridgeOut.zero(); - STAmount saPrvRedeemReq = saPrvBalance.isNegative() ? -saPrvBalance : STAmount(uCurrencyID, 0); - STAmount& saPrvRedeemAct = prvPN.saRevRedeem; - - STAmount saPrvIssueReq = saPrvLimit - saPrvBalance; - STAmount& saPrvIssueAct = prvPN.saRevIssue; - - const STAmount& saCurRedeemReq = curPN.saRevRedeem; - STAmount saCurRedeemAct; - - const STAmount& saCurIssueReq = curPN.saRevIssue; - STAmount saCurIssueAct; // Track progess. - - - // Previous redeem part 1: redeem -> redeem - if (bRedeem - && saCurRedeemReq // Next wants us to redeem. - && saPrvBalance.isNegative()) // Previous has IOUs to redeem. + if (saInOut < saOutIn) { - // Rate : 1.0 : quality out + // Limit by in. - calcNodeRipple(QUALITY_ONE, uQualityOut, saPrvRedeemReq, saCurRedeemReq, saPrvRedeemAct, saCurRedeemAct); + // 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. } - // Previous redeem part 2: redeem -> issue. - if (bIssue - && saCurRedeemReq != saCurRedeemAct // Can only if issue if more can not be redeemed. - && saPrvBalance.isNegative() // Previous still has IOUs. - && saCurIssueReq) // Need some issued. - { - // Rate : 1.0 : transfer_rate - calcNodeRipple(QUALITY_ONE, rippleTransfer(uCurAccountID), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct); - } - - // Previous issue part 1: issue -> redeem - if (bRedeem - && saCurRedeemReq != saCurRedeemAct // Can only redeem if more to be redeemed. - && !saPrvBalance.isNegative()) // Previous has no IOUs. - { - // Rate: quality in : quality out - calcNodeRipple(uQualityIn, uQualityOut, saPrvIssueReq, saCurRedeemReq, saPrvIssueAct, saCurRedeemAct); - } - - // Previous issue part 2 : issue -> issue - if (bIssue - && saCurRedeemReq != saCurRedeemAct // Can only if issue if more can not be redeemed. - && !saPrvBalance.isNegative() // Previous has no IOUs. - && saCurIssueReq != saCurIssueAct) // Need some issued. - { - // Rate: quality in : 1.0 - calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurIssueReq, saPrvIssueAct, saCurIssueAct); - } - - if (!saCurRedeemAct && !saCurIssueAct) - { - // Must want something. - terResult = tenBAD_AMOUNT; - } + uBridgeQuality = STAmount::getRate(saBridgeIn, saBridgeOut); // Inclusive of fees. } } - return tenUNKNOWN == terResult; + 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 = terSUCCESS; + } + + if (terSUCCESS != 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 // 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 reciver. // 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. void TransactionEngine::calcNodeRipple( @@ -2361,145 +2546,301 @@ void TransactionEngine::calcNodeRipple( } } -// From the source work toward the destination calculate how much is transfered at each step and finally. -// <-> pnNodes: -// --> [0]saWanted.mAmount -// --> [all]saWanted.saSend -// --> [all]saWanted.IOURedeem -// --> [all]saWanted.IOUIssue -// --> [all]saAccount -void TransactionEngine::calcPathForward(PathState::pointer pspCur) +// Calculate saPrvRedeemReq, saPrvIssueReq, saPrvDeliver; +bool TransactionEngine::calcNodeAccountRev(unsigned int uIndex, PathState::pointer pspCur, bool bMultiQuality) { - unsigned int uIndex = 0; + bool bSuccess = true; + unsigned int uLast = pspCur->vpnNodes.size() - 1; - unsigned int uEnd = pspCur->vpnNodes.size(); - unsigned int uLast = uEnd - 1; + paymentNode& prvPN = uIndex ? pspCur->vpnNodes[uIndex-1] : pspCur->vpnNodes[0]; + paymentNode& curPN = pspCur->vpnNodes[uIndex]; + paymentNode& nxtPN = uIndex == uLast ? pspCur->vpnNodes[uLast] : pspCur->vpnNodes[uIndex+1]; - while (uIndex != uEnd) + bool bRedeem = !!(curPN.uFlags & STPathElement::typeRedeem); + bool bIssue = !!(curPN.uFlags & STPathElement::typeIssue); + bool bPrvAccount = !!(prvPN.uFlags & STPathElement::typeAccount); + + uint160& uPrvAccountID = prvPN.uAccountID; + uint160& uCurAccountID = curPN.uAccountID; + uint160& uNxtAccountID = nxtPN.uAccountID; + + uint160& uCurIssuerID = curPN.uIssuerID; + + uint160& uCurrencyID = curPN.uCurrencyID; + + if (uIndex == uLast) { - // Calculate (1) sending by fullfilling next wants and (2) setting current wants. + STAmount saCurWantedReq = pspCur->saOutReq; + STAmount saCurWantedAct; - paymentNode& prvPN = uIndex ? pspCur->vpnNodes[uIndex-1] : pspCur->vpnNodes[0]; - paymentNode& curPN = pspCur->vpnNodes[uIndex]; - paymentNode& nxtPN = uIndex == uLast ? pspCur->vpnNodes[uLast] : pspCur->vpnNodes[uIndex+1]; - - // XXX Assume rippling. - - if (!uIndex) + // Calculate deliver + if (bPrvAccount) { - // First node, calculate amount to send. - STAmount& saCurRedeemReq = curPN.saRevRedeem; - STAmount& saCurRedeemAct = curPN.saFwdRedeem; - STAmount& saCurIssueReq = curPN.saRevIssue; - STAmount& saCurIssueAct = curPN.saFwdIssue; + // Previous is an account node. + uint32 uQualityIn = rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID); + STAmount saPrvBalance = rippleBalance(uCurAccountID, uPrvAccountID, uCurrencyID); + STAmount saPrvLimit = rippleLimit(uCurAccountID, uPrvAccountID, uCurAccountID); - STAmount& saCurSendMaxReq = pspCur->saInReq; - STAmount& saCurSendMaxAct = pspCur->saInAct; + STAmount saPrvRedeemReq = saPrvBalance.isNative() ? -saPrvBalance : STAmount(uCurrencyID, 0); + STAmount& saPrvRedeemAct = prvPN.saRevRedeem; // What previous should redeem with cur. - if (saCurRedeemReq) + STAmount saPrvIssueReq = saPrvBalance.isPositive() ? saPrvLimit - saPrvBalance : saPrvLimit; + STAmount& saPrvIssueAct = prvPN.saRevIssue; + + // Calculate redeem + if (bRedeem + && saPrvRedeemReq) // Previous has IOUs to redeem. { - // Redeem requested. - saCurRedeemAct = MIN(saCurRedeemAct, saCurSendMaxReq); - saCurSendMaxAct = saCurRedeemAct; + // Redeem at 1:1 + saCurWantedAct = MIN(saPrvRedeemReq, saCurWantedReq); + saPrvRedeemAct = saCurWantedAct; } - if (saCurIssueReq && saCurSendMaxReq != saCurRedeemAct) + // Calculate issuing. + if (bIssue + && saCurWantedReq != saCurWantedAct // Need more. + && saPrvIssueReq) // Will accept IOUs. { - // Issue requested and not over budget. - saCurIssueAct = MIN(saCurSendMaxReq-saCurRedeemAct, saCurIssueReq); - // saCurSendMaxAct += saCurIssueReq; // Not needed. + // Rate: quality in : 1.0 + calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurWantedReq, saPrvIssueAct, saCurWantedAct); } - } - else if (uIndex == uLast) - { - // Last node. Accept all funds. Calculate amount actually to credit. - uint32 uQualityIn = rippleQualityIn(curPN.uAccountID, prvPN.uAccountID, curPN.uCurrencyID); - STAmount& saPrvRedeemReq = prvPN.saFwdRedeem; - STAmount& saPrvIssueReq = prvPN.saFwdIssue; - STAmount saPrvIssueAct = uQualityIn >= QUALITY_ONE - ? saPrvIssueReq // No fee. - : STAmount::multiply(saPrvIssueReq, uQualityIn, curPN.uCurrencyID); // Fee. - STAmount& saCurReceive = pspCur->saOutAct; - // Amount to credit. - saCurReceive = saPrvRedeemReq+saPrvIssueAct; - - // Actually receive. - rippleCredit(curPN.uAccountID, prvPN.uAccountID, saCurReceive); + if (!saCurWantedAct) + { + // Must have processed something. + // terResult = tenBAD_AMOUNT; + bSuccess = false; + } } else { - // Handle middle node. - // The previous nodes tells want it wants to push through to current. - // The current node know what it would push through next. - // Determine for the current node: - // - Output to next node minus fees. - // Perform balance adjustment with previous. - // All of previous output is consumed. + // Previous is an offer node. + // Rate: quality in : 1.0 + uint32 uQualityIn = rippleQualityIn(uCurAccountID, uCurIssuerID, uCurrencyID); + STAmount saPrvBalance = rippleBalance(uCurAccountID, uCurIssuerID, uCurrencyID); + STAmount saPrvLimit = rippleLimit(uCurAccountID, uCurIssuerID, uCurAccountID); + STAmount saPrvDeliverReq = saPrvBalance.isPositive() ? saPrvLimit - saPrvBalance : saPrvLimit; + STAmount& saPrvDeliverAct = prvPN.saRevDeliver; - uint160& uCurrencyID = curPN.uCurrencyID; + calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvDeliverReq, saCurWantedReq, saPrvDeliverAct, saCurWantedAct); + } - uint160& uPrvAccountID = prvPN.uAccountID; - uint160& uCurAccountID = curPN.uAccountID; - uint160& uNxtAccountID = nxtPN.uAccountID; - - STAmount& saPrvRedeemReq = prvPN.saFwdRedeem; - STAmount saPrvRedeemAct; - STAmount& saPrvIssueReq = prvPN.saFwdIssue; - STAmount saPrvIssueAct; - - STAmount& saCurRedeemReq = curPN.saRevRedeem; - STAmount& saCurRedeemAct = curPN.saFwdRedeem; - STAmount& saCurIssueReq = curPN.saRevIssue; - STAmount& saCurIssueAct = curPN.saFwdIssue; - - // Have funds in previous node to transfer. - uint32 uQualityIn = rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID); - uint32 uQualityOut = rippleQualityOut(uCurAccountID, uNxtAccountID, uCurrencyID); - - // 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); - } - - // 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 more to redeem to next. - { - // Rate : 1.0 : transfer_rate - calcNodeRipple(QUALITY_ONE, rippleTransfer(uCurAccountID), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct); - } - - // 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); - } - - // 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); - } - - // Adjust prv --> cur balance : take all inbound - // XXX Currency must be in amount. - rippleCredit(uCurAccountID, uPrvAccountID, saPrvRedeemReq + saPrvIssueReq); + if (!saCurWantedAct) + { + // Must have processed something. + // terResult = tenBAD_AMOUNT; + bSuccess = false; } } + else if (bPrvAccount) + { + // Rippling through this accounts balances. + // No currency change. + // Given saCurRedeem & saCurIssue figure out saPrvRedeem & saPrvIssue. + + uint32 uQualityIn = rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID); + uint32 uQualityOut = rippleQualityOut(uCurAccountID, uNxtAccountID, uCurrencyID); + STAmount saPrvBalance = rippleBalance(uCurAccountID, uPrvAccountID, uCurrencyID); + STAmount saPrvLimit = rippleLimit(uCurAccountID, uPrvAccountID, uCurrencyID); + + STAmount saPrvRedeemReq = saPrvBalance.isNegative() ? -saPrvBalance : STAmount(uCurrencyID, 0); + STAmount& saPrvRedeemAct = prvPN.saRevRedeem; + + STAmount saPrvIssueReq = saPrvLimit - saPrvBalance; + STAmount& saPrvIssueAct = prvPN.saRevIssue; + + const STAmount& saCurRedeemReq = curPN.saRevRedeem; + STAmount saCurRedeemAct; + + const STAmount& saCurIssueReq = curPN.saRevIssue; + STAmount saCurIssueAct; // Track progess. + + + // Previous redeem part 1: redeem -> redeem + if (bRedeem // Allowed to redeem. + && saCurRedeemReq // Next wants us to redeem. + && saPrvBalance.isNegative()) // Previous has IOUs to redeem. + { + // Rate : 1.0 : quality out + calcNodeRipple(QUALITY_ONE, uQualityOut, saPrvRedeemReq, saCurRedeemReq, saPrvRedeemAct, saCurRedeemAct); + } + + // Previous redeem part 2: redeem -> issue. + if (bIssue // Allowed to issue. + && saCurRedeemReq != saCurRedeemAct // Can only if issue if more can not be redeemed. + && saPrvBalance.isNegative() // Previous still has IOUs. + && saCurIssueReq) // Need some issued. + { + // Rate : 1.0 : transfer_rate + calcNodeRipple(QUALITY_ONE, rippleTransfer(uCurAccountID), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct); + } + + // Previous issue part 1: issue -> redeem + if (bRedeem // Allowed to redeem. + && saCurRedeemReq != saCurRedeemAct // Can only redeem if more to be redeemed. + && !saPrvBalance.isNegative()) // Previous has no IOUs. + { + // Rate: quality in : quality out + calcNodeRipple(uQualityIn, uQualityOut, saPrvIssueReq, saCurRedeemReq, saPrvIssueAct, saCurRedeemAct); + } + + // Previous issue part 2 : issue -> issue + if (bIssue // Allowed to issue. + && saCurRedeemReq != saCurRedeemAct // Can only if issue if more can not be redeemed. + && !saPrvBalance.isNegative() // Previous has no IOUs. + && saCurIssueReq != saCurIssueAct) // Need some issued. + { + // Rate: quality in : 1.0 + calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurIssueReq, saPrvIssueAct, saCurIssueAct); + } + + if (!saCurRedeemAct && !saCurIssueAct) + { + // Must want something. + // terResult = tenBAD_AMOUNT; + bSuccess = false; + } + } + else + { + // Previous is a offer. + + } + + return bSuccess; } -bool PathState::less(const PathState::pointer& lhs, const PathState::pointer& rhs) +bool TransactionEngine::calcNodeAccountFwd(unsigned int uIndex, PathState::pointer pspCur, bool bMultiQuality) { - // Return true, iff lhs has less priority than rhs. + bool bSuccess = true; + unsigned int uLast = pspCur->vpnNodes.size() - 1; + // Ripple through account. + if (!uIndex) + { + // First node, calculate amount to send. + // XXX Use stamp/ripple balance + paymentNode& curPN = pspCur->vpnNodes[uIndex]; + + STAmount& saCurRedeemReq = curPN.saRevRedeem; + STAmount& saCurRedeemAct = curPN.saFwdRedeem; + STAmount& saCurIssueReq = curPN.saRevIssue; + STAmount& saCurIssueAct = curPN.saFwdIssue; + + STAmount& saCurSendMaxReq = pspCur->saInReq; + STAmount& saCurSendMaxAct = pspCur->saInAct; + + if (saCurRedeemReq) + { + // Redeem requested. + saCurRedeemAct = MIN(saCurRedeemAct, saCurSendMaxReq); + saCurSendMaxAct = saCurRedeemAct; + } + + if (saCurIssueReq && saCurSendMaxReq != saCurRedeemAct) + { + // Issue requested and not over budget. + saCurIssueAct = MIN(saCurSendMaxReq-saCurRedeemAct, saCurIssueReq); + // saCurSendMaxAct += saCurIssueReq; // Not needed. + } + } + else if (uIndex == uLast) + { + // Last node. Accept all funds. Calculate amount actually to credit. + paymentNode& prvPN = pspCur->vpnNodes[uIndex-1]; + paymentNode& curPN = pspCur->vpnNodes[uIndex]; + + uint32 uQualityIn = rippleQualityIn(curPN.uAccountID, prvPN.uAccountID, curPN.uCurrencyID); + STAmount& saPrvRedeemReq = prvPN.saFwdRedeem; + STAmount& saPrvIssueReq = prvPN.saFwdIssue; + STAmount saPrvIssueAct = uQualityIn >= QUALITY_ONE + ? saPrvIssueReq // No fee. + : STAmount::multiply(saPrvIssueReq, uQualityIn, curPN.uCurrencyID); // Fee. + STAmount& saCurReceive = pspCur->saOutAct; + + // Amount to credit. + saCurReceive = saPrvRedeemReq+saPrvIssueAct; + + // Actually receive. + rippleCredit(curPN.uAccountID, prvPN.uAccountID, saCurReceive); + } + else + { + // Handle middle node. + // The previous nodes tells want it wants to push through to current. + // The current node know what it would push through next. + // Determine for the current node: + // - Output to next node minus fees. + // Perform balance adjustment with previous. + // All of previous output is consumed. + + paymentNode& prvPN = pspCur->vpnNodes[uIndex-1]; + paymentNode& curPN = pspCur->vpnNodes[uIndex]; + paymentNode& nxtPN = pspCur->vpnNodes[uIndex+1]; + + uint160& uCurrencyID = curPN.uCurrencyID; + + uint160& uPrvAccountID = prvPN.uAccountID; + uint160& uCurAccountID = curPN.uAccountID; + uint160& uNxtAccountID = nxtPN.uAccountID; + + STAmount& saPrvRedeemReq = prvPN.saFwdRedeem; + STAmount saPrvRedeemAct; + STAmount& saPrvIssueReq = prvPN.saFwdIssue; + STAmount saPrvIssueAct; + + STAmount& saCurRedeemReq = curPN.saRevRedeem; + STAmount& saCurRedeemAct = curPN.saFwdRedeem; + STAmount& saCurIssueReq = curPN.saRevIssue; + STAmount& saCurIssueAct = curPN.saFwdIssue; + + // Have funds in previous node to transfer. + uint32 uQualityIn = rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID); + uint32 uQualityOut = rippleQualityOut(uCurAccountID, uNxtAccountID, uCurrencyID); + + // 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); + } + + // 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 more to redeem to next. + { + // Rate : 1.0 : transfer_rate + calcNodeRipple(QUALITY_ONE, rippleTransfer(uCurAccountID), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct); + } + + // 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); + } + + // 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); + } + + // Adjust prv --> cur balance : take all inbound + // XXX Currency must be in amount. + rippleCredit(uCurAccountID, uPrvAccountID, saPrvRedeemReq + saPrvIssueReq); + } + + return bSuccess; +} + +// 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. @@ -2590,6 +2931,7 @@ bool PathState::pushNode(int iType, uint160 uAccountID, uint160 uCurrencyID, uin return bValid; } +// XXX Disallow loops in ripple paths PathState::PathState( int iIndex, const LedgerEntrySet& lesSource, @@ -2617,17 +2959,53 @@ PathState::PathState( pushNode(STPathElement::typeAccount, uReceiverID, saOutReq.getCurrency(), saOutReq.getIssuer()); } +// Calculate a node and its previous nodes. +// From the destination work towards the source calculating how much must be asked for. +// --> bAllowPartial: If false, fail if can't meet requirements. +// <-- bSuccess: true=success, false=insufficient funds / liqudity. +// <-> pnNodes: +// --> [end]saWanted.mAmount +// --> [all]saWanted.mCurrency +// --> [all]saAccount +// <-> [0]saWanted.mAmount : --> limit, <-- actual +// XXX Disallow looping. +bool TransactionEngine::calcNode(unsigned int uIndex, PathState::pointer pspCur, bool bMultiQuality) +{ + paymentNode& curPN = pspCur->vpnNodes[uIndex]; + bool bCurAccount = !!(curPN.uFlags & STPathElement::typeAccount); + bool bSuccess; + + // Do current node reverse. + bSuccess = bCurAccount + ? calcNodeAccountRev(uIndex, pspCur, bMultiQuality) + : calcNodeOfferRev(uIndex, pspCur, bMultiQuality); + + // Do previous. + if (bSuccess && uIndex) + { + bSuccess = calcNode(uIndex-1, pspCur, bMultiQuality); + } + + // Do current node forward. + if (bSuccess) + { + bSuccess = bCurAccount + ? calcNodeAccountFwd(uIndex, pspCur, bMultiQuality) + : calcNodeOfferFwd(uIndex, pspCur, bMultiQuality); + } + + return bSuccess; +} + // Calculate the next increment of a path. -void TransactionEngine::pathNext(PathState::pointer pspCur) +void TransactionEngine::pathNext(PathState::pointer pspCur, int iPaths) { // The next state is what is available in preference order. // This is calculated when referenced accounts changed. - if (calcPathReverse(pspCur)) - { - calcPathForward(pspCur); - } - else + unsigned int uLast = pspCur->vpnNodes.size() - 1; + + if (!calcNode(uLast, pspCur, iPaths == 1)) { // Mark path as inactive. pspCur->uQuality = 0; @@ -2740,7 +3118,6 @@ TransactionEngineResult TransactionEngine::doPayment(const SerializedTransaction // // Ripple payment // - // XXX Disallow loops in ripple paths // Try direct ripple first. if (!bNoRippleDirect && mTxnAccountID != uDstAccountID && uSrcCurrency == uDstCurrency) @@ -2873,10 +3250,10 @@ TransactionEngineResult TransactionEngine::doPayment(const SerializedTransaction pspCur->lesEntries = mNodes.duplicate(); // XXX Compute increment - pathNext(pspCur); + pathNext(pspCur, vpsPaths.size()); } - if (!pspBest || (pspCur->uQuality && PathState::less(pspBest, pspCur))) + if (!pspBest || (pspCur->uQuality && PathState::lessPriority(pspBest, pspCur))) pspBest = pspCur; } diff --git a/src/TransactionEngine.h b/src/TransactionEngine.h index 0f3aed767..832f09e5e 100644 --- a/src/TransactionEngine.h +++ b/src/TransactionEngine.h @@ -111,11 +111,14 @@ typedef struct { // Computed by Reverse. STAmount saRevRedeem; // <-- Amount to redeem to next. STAmount saRevIssue; // <-- Amount to issue to next limited by credit and outstanding IOUs. - // ? STAmount saSend; // <-- Stamps this node will send downstream. + // 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. } paymentNode; // Hold a path state under incremental application. @@ -161,7 +164,7 @@ public: ) { return boost::make_shared(iIndex, lesSource, spSourcePath, uReceiverID, uSenderID, saSend, saSendMax, bPartialPayment); }; - static bool less(const PathState::pointer& lhs, const PathState::pointer& rhs); + static bool lessPriority(const PathState::pointer& lhs, const PathState::pointer& rhs); }; // One instance per ledger. @@ -228,7 +231,7 @@ protected: uint32 rippleQualityOut(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID); STAmount rippleHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID); - STAmount rippleTransit(const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID, const STAmount& saAmount); + STAmount rippleTransfer(const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID, const STAmount& saAmount); void rippleCredit(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount); STAmount rippleSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount); @@ -238,12 +241,15 @@ protected: PathState::pointer pathCreate(const STPath& spPath); void pathApply(PathState::pointer pspCur); - void pathNext(PathState::pointer pspCur); + void pathNext(PathState::pointer pspCur, int iPaths); + bool calcNode(unsigned int uIndex, PathState::pointer pspCur, bool bMultiQuality); + bool calcNodeOfferRev(unsigned int uIndex, PathState::pointer pspCur, bool bMultiQuality); + bool calcNodeOfferFwd(unsigned int uIndex, PathState::pointer pspCur, bool bMultiQuality); + bool calcNodeAccountRev(unsigned int uIndex, PathState::pointer pspCur, bool bMultiQuality); + bool calcNodeAccountFwd(unsigned int uIndex, PathState::pointer pspCur, bool bMultiQuality); void calcNodeRipple(const uint32 uQualityIn, const uint32 uQualityOut, const STAmount& saPrvReq, const STAmount& saCurReq, STAmount& saPrvAct, STAmount& saCurAct); - bool calcPathReverse(PathState::pointer pspCur); - void calcPathForward(PathState::pointer pspCur); void txnWrite(); From 05304cc1fcbf51f161019f89990601b4ca9240c2 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Sat, 11 Aug 2012 14:39:58 -0700 Subject: [PATCH 7/7] More work towards ripple backend. --- src/TransactionEngine.cpp | 670 +++++++++++++++++++++++++------------- 1 file changed, 447 insertions(+), 223 deletions(-) diff --git a/src/TransactionEngine.cpp b/src/TransactionEngine.cpp index 92a6ddec9..d95b0bb19 100644 --- a/src/TransactionEngine.cpp +++ b/src/TransactionEngine.cpp @@ -27,6 +27,7 @@ #define ACCOUNT_XNS uint160(0) #define ACCOUNT_ONE uint160(1) // Used as a place holder +static STAmount saZero(CURRENCY_ONE, 0, 0); static STAmount saOne(CURRENCY_ONE, 1, 0); bool transResultInfo(TransactionEngineResult terCode, std::string& strToken, std::string& strHuman) @@ -152,18 +153,24 @@ uint32 TransactionEngine::rippleTransfer(const uint160& uIssuerID) // XXX Might not need this, might store in nodes on calc reverse. uint32 TransactionEngine::rippleQualityIn(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID) { - uint32 uQualityIn; + uint32 uQualityIn = QUALITY_ONE; - SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uToAccountID, uFromAccountID, uCurrencyID)); - - if (sleRippleState) + if (uToAccountID == uFromAccountID) { - uQualityIn = sleRippleState->getIFieldU32(uToAccountID < uFromAccountID ? sfLowQualityIn : sfHighQualityIn); + nothing(); } else { - assert(false); - uQualityIn = QUALITY_ONE; + SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uToAccountID, uFromAccountID, uCurrencyID)); + + if (sleRippleState) + { + uQualityIn = sleRippleState->getIFieldU32(uToAccountID < uFromAccountID ? sfLowQualityIn : sfHighQualityIn); + } + else + { + assert(false); + } } return uQualityIn; @@ -171,18 +178,24 @@ uint32 TransactionEngine::rippleQualityIn(const uint160& uToAccountID, const uin uint32 TransactionEngine::rippleQualityOut(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID) { - uint32 uQualityOut; + uint32 uQualityOut = QUALITY_ONE; - SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uToAccountID, uFromAccountID, uCurrencyID)); - - if (sleRippleState) + if (uToAccountID == uFromAccountID) { - uQualityOut = sleRippleState->getIFieldU32(uToAccountID < uFromAccountID ? sfLowQualityOut : sfHighQualityOut); + nothing(); } else { - assert(false); - uQualityOut = QUALITY_ONE; + SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uToAccountID, uFromAccountID, uCurrencyID)); + + if (sleRippleState) + { + uQualityOut = sleRippleState->getIFieldU32(uToAccountID < uFromAccountID ? sfLowQualityOut : sfHighQualityOut); + } + else + { + assert(false); + } } return uQualityOut; @@ -1894,6 +1907,7 @@ void TransactionEngine::calcOfferBridgeNext( #endif // <-- bSuccess: false= no transfer +// XXX Make sure missing ripple path is addressed cleanly. bool TransactionEngine::calcNodeOfferRev( unsigned int uIndex, // 0 < uIndex < uLast-1 PathState::pointer pspCur, @@ -2508,18 +2522,19 @@ void TransactionEngine::calcNodeOffer( void TransactionEngine::calcNodeRipple( const uint32 uQualityIn, const uint32 uQualityOut, - const STAmount& saPrvReq, // --> in limit including fees + 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. { - STAmount saPrv = saPrvReq-saPrvAct; - STAmount saCur = saCurReq-saCurAct; + bool bPrvUnlimited = saPrvReq.isNegative(); + STAmount saPrv = bPrvUnlimited ? saZero : saPrvReq-saPrvAct; + STAmount saCur = saCurReq-saCurAct; if (uQualityIn >= uQualityOut) { // No fee. - STAmount saTransfer = MIN(saPrv, saCur); + STAmount saTransfer = bPrvUnlimited ? saCur : MIN(saPrv, saCur); saPrvAct += saTransfer; saCurAct += saTransfer; @@ -2529,7 +2544,7 @@ void TransactionEngine::calcNodeRipple( // Fee. STAmount saCurIn = STAmount::divide(STAmount::multiply(saCur, uQualityOut, CURRENCY_ONE), uQualityIn, CURRENCY_ONE); - if (saCurIn >= saPrv) + if (bPrvUnlimited || saCurIn >= saPrv) { // All of cur. Some amount of prv. saCurAct = saCurReq; @@ -2552,40 +2567,61 @@ bool TransactionEngine::calcNodeAccountRev(unsigned int uIndex, PathState::point bool bSuccess = true; unsigned int uLast = pspCur->vpnNodes.size() - 1; - paymentNode& prvPN = uIndex ? pspCur->vpnNodes[uIndex-1] : pspCur->vpnNodes[0]; + paymentNode& prvPN = pspCur->vpnNodes[uIndex ? uIndex-1 : 0]; paymentNode& curPN = pspCur->vpnNodes[uIndex]; - paymentNode& nxtPN = uIndex == uLast ? pspCur->vpnNodes[uLast] : pspCur->vpnNodes[uIndex+1]; + paymentNode& nxtPN = pspCur->vpnNodes[uIndex == uLast ? uLast : uIndex+1]; bool bRedeem = !!(curPN.uFlags & STPathElement::typeRedeem); + bool bPrvRedeem = !!(prvPN.uFlags & STPathElement::typeRedeem); bool bIssue = !!(curPN.uFlags & STPathElement::typeIssue); + bool bPrvIssue = !!(prvPN.uFlags & STPathElement::typeIssue); bool bPrvAccount = !!(prvPN.uFlags & STPathElement::typeAccount); + bool bNxtAccount = !!(nxtPN.uFlags & STPathElement::typeAccount); uint160& uPrvAccountID = prvPN.uAccountID; uint160& uCurAccountID = curPN.uAccountID; - uint160& uNxtAccountID = nxtPN.uAccountID; - - uint160& uCurIssuerID = curPN.uIssuerID; + uint160& uNxtAccountID = bNxtAccount ? nxtPN.uAccountID : uCurAccountID; // Offers are always issue. uint160& uCurrencyID = curPN.uCurrencyID; - if (uIndex == uLast) + uint32 uQualityIn = rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID); + uint32 uQualityOut = rippleQualityOut(uCurAccountID, uNxtAccountID, uCurrencyID); + + // For bPrvAccount + STAmount saPrvBalance = bPrvAccount ? rippleBalance(uCurAccountID, uPrvAccountID, uCurrencyID) : saZero; + STAmount saPrvLimit = bPrvAccount ? rippleLimit(uCurAccountID, uPrvAccountID, uCurrencyID) : saZero; + + STAmount saPrvRedeemReq = bPrvRedeem && saPrvBalance.isNegative() ? -saPrvBalance : STAmount(uCurrencyID, 0); + STAmount& saPrvRedeemAct = prvPN.saRevRedeem; + + STAmount saPrvIssueReq = bPrvIssue && saPrvLimit - saPrvBalance; + STAmount& saPrvIssueAct = prvPN.saRevIssue; + + // For !bPrvAccount + STAmount saPrvDeliverReq = STAmount(uCurrencyID, -1); // Unlimited. + STAmount& saPrvDeliverAct = prvPN.saRevDeliver; + + // For bNxtAccount + const STAmount& saCurRedeemReq = curPN.saRevRedeem; + STAmount saCurRedeemAct; + + const STAmount& saCurIssueReq = curPN.saRevIssue; + STAmount saCurIssueAct; // Track progress. + + // For !bNxtAccount + const STAmount& saCurDeliverReq = curPN.saRevDeliver; + STAmount saCurDeliverAct; + + // For uIndex == uLast + const STAmount& saCurWantedReq = pspCur->saOutReq; // XXX Credit limits? + // STAmount saPrvDeliverReq = saPrvBalance.isPositive() ? saPrvLimit - saPrvBalance : saPrvLimit; + STAmount saCurWantedAct; + + if (bPrvAccount && bNxtAccount) { - STAmount saCurWantedReq = pspCur->saOutReq; - STAmount saCurWantedAct; - - // Calculate deliver - if (bPrvAccount) + if (uIndex == uLast) { - // Previous is an account node. - uint32 uQualityIn = rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID); - STAmount saPrvBalance = rippleBalance(uCurAccountID, uPrvAccountID, uCurrencyID); - STAmount saPrvLimit = rippleLimit(uCurAccountID, uPrvAccountID, uCurAccountID); - - STAmount saPrvRedeemReq = saPrvBalance.isNative() ? -saPrvBalance : STAmount(uCurrencyID, 0); - STAmount& saPrvRedeemAct = prvPN.saRevRedeem; // What previous should redeem with cur. - - STAmount saPrvIssueReq = saPrvBalance.isPositive() ? saPrvLimit - saPrvBalance : saPrvLimit; - STAmount& saPrvIssueAct = prvPN.saRevIssue; + // account --> ACCOUNT --> $ // Calculate redeem if (bRedeem @@ -2614,225 +2650,384 @@ bool TransactionEngine::calcNodeAccountRev(unsigned int uIndex, PathState::point } else { - // Previous is an offer node. - // Rate: quality in : 1.0 - uint32 uQualityIn = rippleQualityIn(uCurAccountID, uCurIssuerID, uCurrencyID); - STAmount saPrvBalance = rippleBalance(uCurAccountID, uCurIssuerID, uCurrencyID); - STAmount saPrvLimit = rippleLimit(uCurAccountID, uCurIssuerID, uCurAccountID); - STAmount saPrvDeliverReq = saPrvBalance.isPositive() ? saPrvLimit - saPrvBalance : saPrvLimit; - STAmount& saPrvDeliverAct = prvPN.saRevDeliver; + // account --> ACCOUNT --> account - calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvDeliverReq, saCurWantedReq, saPrvDeliverAct, saCurWantedAct); - } + // redeem (part 1) -> redeem + if (bPrvRedeem + && bRedeem // Allowed to redeem. + && saCurRedeemReq // Next wants us to redeem. + && saPrvBalance.isNegative()) // Previous has IOUs to redeem. + { + // Rate : 1.0 : quality out + calcNodeRipple(QUALITY_ONE, uQualityOut, saPrvRedeemReq, saCurRedeemReq, saPrvRedeemAct, saCurRedeemAct); + } - if (!saCurWantedAct) - { - // Must have processed something. - // terResult = tenBAD_AMOUNT; - bSuccess = false; + // redeem (part 2) -> issue. + if (bPrvRedeem + && bIssue // Allowed to issue. + && saCurRedeemReq != saCurRedeemAct // Can only if issue if more can not be redeemed. + && saPrvBalance.isNegative() // Previous still has IOUs. + && saCurIssueReq) // Need some issued. + { + // Rate : 1.0 : transfer_rate + calcNodeRipple(QUALITY_ONE, rippleTransfer(uCurAccountID), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct); + } + + // issue (part 1)-> redeem + if (bPrvIssue + && bRedeem // Allowed to redeem. + && saCurRedeemReq != saCurRedeemAct // Can only redeem if more to be redeemed. + && !saPrvBalance.isNegative()) // Previous has no IOUs. + { + // Rate: quality in : quality out + calcNodeRipple(uQualityIn, uQualityOut, saPrvIssueReq, saCurRedeemReq, saPrvIssueAct, saCurRedeemAct); + } + + // issue (part 2) -> issue + if (bPrvIssue + && bIssue // Allowed to issue. + && saCurRedeemReq != saCurRedeemAct // Can only if issue if more can not be redeemed. + && !saPrvBalance.isNegative() // Previous has no IOUs. + && saCurIssueReq != saCurIssueAct) // Need some issued. + { + // Rate: quality in : 1.0 + calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurIssueReq, saPrvIssueAct, saCurIssueAct); + } + + if (!saCurRedeemAct && !saCurIssueAct) + { + // Must want something. + // terResult = tenBAD_AMOUNT; + bSuccess = false; + } } } - else if (bPrvAccount) + else if (bPrvAccount && !bNxtAccount) { - // Rippling through this accounts balances. - // No currency change. - // Given saCurRedeem & saCurIssue figure out saPrvRedeem & saPrvIssue. + // account --> ACCOUNT --> offer + // Note: deliver is always issue as ACCOUNT is the issuer for the offer input. - uint32 uQualityIn = rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID); - uint32 uQualityOut = rippleQualityOut(uCurAccountID, uNxtAccountID, uCurrencyID); - STAmount saPrvBalance = rippleBalance(uCurAccountID, uPrvAccountID, uCurrencyID); - STAmount saPrvLimit = rippleLimit(uCurAccountID, uPrvAccountID, uCurrencyID); - - STAmount saPrvRedeemReq = saPrvBalance.isNegative() ? -saPrvBalance : STAmount(uCurrencyID, 0); - STAmount& saPrvRedeemAct = prvPN.saRevRedeem; - - STAmount saPrvIssueReq = saPrvLimit - saPrvBalance; - STAmount& saPrvIssueAct = prvPN.saRevIssue; - - const STAmount& saCurRedeemReq = curPN.saRevRedeem; - STAmount saCurRedeemAct; - - const STAmount& saCurIssueReq = curPN.saRevIssue; - STAmount saCurIssueAct; // Track progess. - - - // Previous redeem part 1: redeem -> redeem - if (bRedeem // Allowed to redeem. - && saCurRedeemReq // Next wants us to redeem. - && saPrvBalance.isNegative()) // Previous has IOUs to redeem. - { - // Rate : 1.0 : quality out - calcNodeRipple(QUALITY_ONE, uQualityOut, saPrvRedeemReq, saCurRedeemReq, saPrvRedeemAct, saCurRedeemAct); - } - - // Previous redeem part 2: redeem -> issue. - if (bIssue // Allowed to issue. - && saCurRedeemReq != saCurRedeemAct // Can only if issue if more can not be redeemed. - && saPrvBalance.isNegative() // Previous still has IOUs. - && saCurIssueReq) // Need some issued. + // redeem -> deliver/issue. + if (bPrvRedeem + && bIssue // Allowed to issue. + && saPrvBalance.isNegative() // Previous redeeming: Previous still has IOUs. + && saCurDeliverReq) // Need some issued. { // Rate : 1.0 : transfer_rate - calcNodeRipple(QUALITY_ONE, rippleTransfer(uCurAccountID), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct); + calcNodeRipple(QUALITY_ONE, rippleTransfer(uCurAccountID), saPrvRedeemReq, saCurDeliverReq, saPrvRedeemAct, saCurDeliverAct); } - // Previous issue part 1: issue -> redeem - if (bRedeem // Allowed to redeem. - && saCurRedeemReq != saCurRedeemAct // Can only redeem if more to be redeemed. - && !saPrvBalance.isNegative()) // Previous has no IOUs. - { - // Rate: quality in : quality out - calcNodeRipple(uQualityIn, uQualityOut, saPrvIssueReq, saCurRedeemReq, saPrvIssueAct, saCurRedeemAct); - } - - // Previous issue part 2 : issue -> issue - if (bIssue // Allowed to issue. - && saCurRedeemReq != saCurRedeemAct // Can only if issue if more can not be redeemed. - && !saPrvBalance.isNegative() // Previous has no IOUs. - && saCurIssueReq != saCurIssueAct) // Need some issued. + // issue -> deliver/issue + if (bPrvIssue + && bIssue // Allowed to issue. + && !saPrvBalance.isNegative() // Previous issuing: Previous has no IOUs. + && saCurDeliverReq != saCurDeliverAct) // Need some issued. { // Rate: quality in : 1.0 - calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurIssueReq, saPrvIssueAct, saCurIssueAct); + calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurDeliverReq, saPrvIssueAct, saCurDeliverAct); } - if (!saCurRedeemAct && !saCurIssueAct) + if (!saCurDeliverAct) { // Must want something. // terResult = tenBAD_AMOUNT; bSuccess = false; } } + else if (!bPrvAccount && bNxtAccount) + { + if (uIndex == uLast) + { + // offer --> ACCOUNT --> $ + + // Rate: quality in : 1.0 + calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvDeliverReq, saCurWantedReq, saPrvDeliverAct, saCurWantedAct); + + if (!saCurWantedAct) + { + // Must have processed something. + // terResult = tenBAD_AMOUNT; + bSuccess = false; + } + } + else + { + // offer --> ACCOUNT --> account + // Note: offer is always deliver/redeeming as account is issuer. + + // deliver -> redeem + if (bRedeem // Allowed to redeem. + && saCurRedeemReq // Next wants us to redeem. + && saPrvBalance.isNegative()) // Previous has IOUs to redeem. + { + // Rate : 1.0 : quality out + calcNodeRipple(QUALITY_ONE, uQualityOut, saPrvDeliverReq, saCurRedeemReq, saPrvDeliverAct, saCurRedeemAct); + } + + // deliver -> issue. + if (bIssue // Allowed to issue. + && saCurRedeemReq != saCurRedeemAct // Can only if issue if more can not be redeemed. + && saPrvBalance.isNegative() // Previous still has IOUs. + && saCurIssueReq) // Need some issued. + { + // Rate : 1.0 : transfer_rate + calcNodeRipple(QUALITY_ONE, rippleTransfer(uCurAccountID), saPrvDeliverReq, saCurIssueReq, saPrvDeliverAct, saCurIssueAct); + } + + if (!saCurDeliverAct && !saCurIssueAct) + { + // Must want something. + // terResult = tenBAD_AMOUNT; + bSuccess = false; + } + } + } else { - // Previous is a offer. + // offer --> ACCOUNT --> offer + // deliver/redeem -> deliver/issue. + if (bIssue // Allowed to issue. + && saCurDeliverReq != saCurDeliverAct) // Can only if issue if more can not be redeemed. + { + // Rate : 1.0 : transfer_rate + calcNodeRipple(QUALITY_ONE, rippleTransfer(uCurAccountID), saPrvDeliverReq, saCurDeliverReq, saPrvDeliverAct, saCurDeliverAct); + } + if (!saCurDeliverAct) + { + // Must want something. + // terResult = tenBAD_AMOUNT; + bSuccess = false; + } } + // XXX Need a more nuanced return: temporary fail vs perm? return bSuccess; } +// The previous node: specifies what to push through to current. +// - All of previous output is consumed. +// The current node: specify what to push through to next. +// - Output to next node minus fees. +// Perform balance adjustment with previous. bool TransactionEngine::calcNodeAccountFwd(unsigned int uIndex, PathState::pointer pspCur, bool bMultiQuality) { - bool bSuccess = true; - unsigned int uLast = pspCur->vpnNodes.size() - 1; + bool bSuccess = true; + unsigned int uLast = pspCur->vpnNodes.size() - 1; + + paymentNode& prvPN = pspCur->vpnNodes[uIndex ? uIndex-1 : 0]; + paymentNode& curPN = pspCur->vpnNodes[uIndex]; + paymentNode& nxtPN = pspCur->vpnNodes[uIndex == uLast ? uLast : uIndex+1]; + + bool bRedeem = !!(curPN.uFlags & STPathElement::typeRedeem); + bool bIssue = !!(curPN.uFlags & STPathElement::typeIssue); + bool bPrvAccount = !!(prvPN.uFlags & STPathElement::typeAccount); + bool bNxtAccount = !!(nxtPN.uFlags & STPathElement::typeAccount); + + uint160& uPrvAccountID = prvPN.uAccountID; + uint160& uCurAccountID = curPN.uAccountID; + uint160& uNxtAccountID = bNxtAccount ? nxtPN.uAccountID : uCurAccountID; // Offers are always issue. + + uint160& uCurrencyID = curPN.uCurrencyID; + + uint32 uQualityIn = rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID); + uint32 uQualityOut = rippleQualityOut(uCurAccountID, uNxtAccountID, uCurrencyID); + + // For bNxtAccount + const STAmount& saPrvRedeemReq = prvPN.saFwdRedeem; + STAmount saPrvRedeemAct; + + const STAmount& saPrvIssueReq = prvPN.saFwdIssue; + STAmount saPrvIssueAct; + + // For !bPrvAccount + const STAmount& saPrvDeliverReq = prvPN.saRevDeliver; + STAmount saPrvDeliverAct; + + // For bNxtAccount + const STAmount& saCurRedeemReq = curPN.saRevRedeem; + STAmount& saCurRedeemAct = curPN.saFwdRedeem; + + const STAmount& saCurIssueReq = curPN.saRevIssue; + STAmount& saCurIssueAct = curPN.saFwdIssue; + + // For !bNxtAccount + const STAmount& saCurDeliverReq = curPN.saRevDeliver; + STAmount& saCurDeliverAct = curPN.saFwdDeliver; + + STAmount& saCurReceive = pspCur->saOutAct; // Ripple through account. - if (!uIndex) + + if (bPrvAccount && bNxtAccount) { - // First node, calculate amount to send. - // XXX Use stamp/ripple balance - paymentNode& curPN = pspCur->vpnNodes[uIndex]; - - STAmount& saCurRedeemReq = curPN.saRevRedeem; - STAmount& saCurRedeemAct = curPN.saFwdRedeem; - STAmount& saCurIssueReq = curPN.saRevIssue; - STAmount& saCurIssueAct = curPN.saFwdIssue; - - STAmount& saCurSendMaxReq = pspCur->saInReq; - STAmount& saCurSendMaxAct = pspCur->saInAct; - - if (saCurRedeemReq) + if (!uIndex) { - // Redeem requested. - saCurRedeemAct = MIN(saCurRedeemAct, saCurSendMaxReq); - saCurSendMaxAct = saCurRedeemAct; + // ^ --> ACCOUNT --> account + + // First node, calculate amount to send. + // XXX Use stamp/ripple balance + paymentNode& curPN = pspCur->vpnNodes[uIndex]; + + STAmount& saCurRedeemReq = curPN.saRevRedeem; + STAmount& saCurRedeemAct = curPN.saFwdRedeem; + STAmount& saCurIssueReq = curPN.saRevIssue; + STAmount& saCurIssueAct = curPN.saFwdIssue; + + STAmount& saCurSendMaxReq = pspCur->saInReq; + STAmount& saCurSendMaxAct = pspCur->saInAct; + + if (saCurRedeemReq) + { + // Redeem requested. + saCurRedeemAct = MIN(saCurRedeemAct, saCurSendMaxReq); + saCurSendMaxAct = saCurRedeemAct; + } + + if (saCurIssueReq && saCurSendMaxReq != saCurRedeemAct) + { + // Issue requested and not over budget. + saCurIssueAct = MIN(saCurSendMaxReq-saCurRedeemAct, saCurIssueReq); + // saCurSendMaxAct += saCurIssueReq; // Not needed. + } } - - if (saCurIssueReq && saCurSendMaxReq != saCurRedeemAct) + else if (uIndex == uLast) { - // Issue requested and not over budget. - saCurIssueAct = MIN(saCurSendMaxReq-saCurRedeemAct, saCurIssueReq); - // saCurSendMaxAct += saCurIssueReq; // Not needed. + // account --> ACCOUNT --> $ + + // Last node. Accept all funds. Calculate amount actually to credit. + + STAmount saIssueCrd = uQualityIn >= QUALITY_ONE + ? saPrvIssueReq // No fee. + : STAmount::multiply(saPrvIssueReq, uQualityIn, uCurrencyID); // Fee. + + // Amount to credit. + saCurReceive = saPrvRedeemReq+saIssueCrd; + + // Actually receive. + rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq+saPrvIssueReq); + } + else + { + // account --> ACCOUNT --> account + + // Previous redeem part 1: redeem -> redeem + if (bRedeem // Can redeem. + && saPrvRedeemReq != saPrvRedeemAct) // Previous wants to redeem. To next must be ok. + { + // Rate : 1.0 : quality out + calcNodeRipple(QUALITY_ONE, uQualityOut, saPrvRedeemReq, saCurRedeemReq, saPrvRedeemAct, saCurRedeemAct); + } + + // 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 (bIssue // Can issue. + && 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, rippleTransfer(uCurAccountID), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct); + } + + // Previous issue part 1: issue -> redeem + if (bRedeem // Can redeem. + && 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); + } + + // Previous issue part 2 : issue -> issue + if (bIssue // Can issue. + && saPrvIssueReq != saPrvIssueAct) // Previous wants to issue. To next must be ok. + { + // Rate: quality in : 1.0 + calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurIssueReq, saPrvIssueAct, saCurIssueAct); + } + + // Adjust prv --> cur balance : take all inbound + // XXX Currency must be in amount. + rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq + saPrvIssueReq); } } - else if (uIndex == uLast) + else if (bPrvAccount && !bNxtAccount) { - // Last node. Accept all funds. Calculate amount actually to credit. - paymentNode& prvPN = pspCur->vpnNodes[uIndex-1]; - paymentNode& curPN = pspCur->vpnNodes[uIndex]; + // account --> ACCOUNT --> offer - uint32 uQualityIn = rippleQualityIn(curPN.uAccountID, prvPN.uAccountID, curPN.uCurrencyID); - STAmount& saPrvRedeemReq = prvPN.saFwdRedeem; - STAmount& saPrvIssueReq = prvPN.saFwdIssue; - STAmount saPrvIssueAct = uQualityIn >= QUALITY_ONE - ? saPrvIssueReq // No fee. - : STAmount::multiply(saPrvIssueReq, uQualityIn, curPN.uCurrencyID); // Fee. - STAmount& saCurReceive = pspCur->saOutAct; - - // Amount to credit. - saCurReceive = saPrvRedeemReq+saPrvIssueAct; - - // Actually receive. - rippleCredit(curPN.uAccountID, prvPN.uAccountID, saCurReceive); - } - else - { - // Handle middle node. - // The previous nodes tells want it wants to push through to current. - // The current node know what it would push through next. - // Determine for the current node: - // - Output to next node minus fees. - // Perform balance adjustment with previous. - // All of previous output is consumed. - - paymentNode& prvPN = pspCur->vpnNodes[uIndex-1]; - paymentNode& curPN = pspCur->vpnNodes[uIndex]; - paymentNode& nxtPN = pspCur->vpnNodes[uIndex+1]; - - uint160& uCurrencyID = curPN.uCurrencyID; - - uint160& uPrvAccountID = prvPN.uAccountID; - uint160& uCurAccountID = curPN.uAccountID; - uint160& uNxtAccountID = nxtPN.uAccountID; - - STAmount& saPrvRedeemReq = prvPN.saFwdRedeem; - STAmount saPrvRedeemAct; - STAmount& saPrvIssueReq = prvPN.saFwdIssue; - STAmount saPrvIssueAct; - - STAmount& saCurRedeemReq = curPN.saRevRedeem; - STAmount& saCurRedeemAct = curPN.saFwdRedeem; - STAmount& saCurIssueReq = curPN.saRevIssue; - STAmount& saCurIssueAct = curPN.saFwdIssue; - - // Have funds in previous node to transfer. - uint32 uQualityIn = rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID); - uint32 uQualityOut = rippleQualityOut(uCurAccountID, uNxtAccountID, uCurrencyID); - - // 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); - } - - // Previous redeem part 2: redeem -> issue. + // 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 more to redeem to next. + if (saPrvRedeemReq) // Previous wants to redeem. { // Rate : 1.0 : transfer_rate - calcNodeRipple(QUALITY_ONE, rippleTransfer(uCurAccountID), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct); + calcNodeRipple(QUALITY_ONE, rippleTransfer(uCurAccountID), saPrvRedeemReq, saCurDeliverReq, saPrvRedeemAct, saCurDeliverAct); } - // 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); - } - - // Previous issue part 2 : issue -> issue - if (saPrvIssueReq != saPrvIssueAct) // Previous wants to issue. To next must be ok. + // 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, saCurIssueReq, saPrvIssueAct, saCurIssueAct); + calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurDeliverReq, saPrvIssueAct, saCurDeliverAct); } // Adjust prv --> cur balance : take all inbound // XXX Currency must be in amount. - rippleCredit(uCurAccountID, uPrvAccountID, saPrvRedeemReq + saPrvIssueReq); + rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq + saPrvIssueReq); + } + else if (!bPrvAccount && bNxtAccount) + { + if (uIndex == uLast) + { + // offer --> ACCOUNT --> $ + + // Amount to credit. + saCurReceive = saPrvDeliverAct; + + // No income balance adjustments necessary. The paying side inside the offer paid to this account. + } + else + { + // offer --> ACCOUNT --> account + + // deliver -> redeem + if (bRedeem // Allowed to redeem. + && saPrvDeliverReq) // Previous wants to deliver. + { + // Rate : 1.0 : quality out + calcNodeRipple(QUALITY_ONE, uQualityOut, saPrvDeliverReq, saCurRedeemReq, saPrvDeliverAct, saCurRedeemAct); + } + + // deliver -> issue + // Wants to redeem and current would and can issue. + if (bIssue // Allowed to issue. + && 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, rippleTransfer(uCurAccountID), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct); + } + + // 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. + if (bIssue // Allowed to issue. + && saPrvDeliverReq // Previous wants to deliver + && saCurIssueReq) // Current wants issue. + { + // Rate : 1.0 : transfer_rate + calcNodeRipple(QUALITY_ONE, rippleTransfer(uCurAccountID), saPrvDeliverReq, saCurDeliverReq, saPrvDeliverAct, saCurDeliverAct); + } + + // No income balance adjustments necessary. The paying side inside the offer paid and the next link will receive. } return bSuccess; @@ -2852,7 +3047,8 @@ bool PathState::lessPriority(const PathState::pointer& lhs, const PathState::poi return lhs->mIndex > rhs->mIndex; // Bigger is worse. } -// <-- bValid: true, if node is valid. +// Add a node and insert any implied nodes. +// <-- bValid: true, if node is valid. false, if node is malformed. bool PathState::pushNode(int iType, uint160 uAccountID, uint160 uCurrencyID, uint160 uIssuerID) { paymentNode pnCur; @@ -2883,29 +3079,32 @@ bool PathState::pushNode(int iType, uint160 uAccountID, uint160 uCurrencyID, uin // An intermediate node may be implied. - // An offer may be implied if (uCurrencyID != pnPrv.uCurrencyID) { // Implied preceeding offer. bValid = pushNode( - 0, - ACCOUNT_ONE, - CURRENCY_ONE, // Inherit from previous - ACCOUNT_ONE); // Inherit from previous + 0, + ACCOUNT_ONE, + CURRENCY_ONE, // Inherit from previous + ACCOUNT_ONE); // Inherit from previous } - else if (uIssuerID != pnPrv.uIssuerID) + + if (bValid && uIssuerID != pnPrv.uIssuerID) { // Implied preceeding account. bValid = pushNode( - STPathElement::typeAccount - | STPathElement::typeRedeem - | STPathElement::typeIssue, - uIssuerID, - CURRENCY_ONE, // Inherit from previous - ACCOUNT_ONE); // Default same as account. + STPathElement::typeAccount + | STPathElement::typeRedeem + | STPathElement::typeIssue, + uIssuerID, + CURRENCY_ONE, // Inherit from previous + ACCOUNT_ONE); // Default same as account. } + + if (bValid) + vpnNodes.push_back(pnCur); } else { @@ -2921,13 +3120,38 @@ bool PathState::pushNode(int iType, uint160 uAccountID, uint160 uCurrencyID, uin } else { + pnCur.uAccountID = uAccountID; pnCur.uCurrencyID = bCurrency ? uCurrencyID : pnPrv.uCurrencyID; - pnCur.uIssuerID = bIssuer ? uIssuerID : uAccountID; + pnCur.uIssuerID = bIssuer ? uIssuerID : pnCur.uAccountID; + + if (!!pnPrv.uAccountID // Previous is an account. + && pnPrv.uAccountID != pnCur.uIssuerID) // Account is not issuer. + { + // Implied preceeding account. + + bValid = pushNode( + STPathElement::typeAccount + | STPathElement::typeIssue, + uIssuerID, + CURRENCY_ONE, // Inherit from previous + ACCOUNT_ONE); // Default same as account. + } + else if (bValid) + { + // Verify that previous account is allowed to issue. + const paymentNode& pnLst = vpnNodes.back(); + bool bLstAccount = !!(pnLst.uFlags & STPathElement::typeAccount); + bool bLstIssue = !!(pnLst.uFlags & STPathElement::typeIssue); + + if (bLstAccount && !bLstIssue) + bValid = false; // Malformed path. + } + + if (bValid) + vpnNodes.push_back(pnCur); } } - vpnNodes.push_back(pnCur); - return bValid; }