From 0bab6a9fecb3d825a2a824661f4c87ef3c43783a Mon Sep 17 00:00:00 2001 From: JoelKatz Date: Tue, 7 Jan 2014 16:32:36 -0800 Subject: [PATCH] Add directory and order book iterators This should fix the crossed order book bug. * Change OfferCreate::takeOffers to use new iterators * Change NetworkOps::getBookPage to use new iterators * If we find an offer in the book but not the ledger, deindex it --- Builds/VisualStudio2012/RippleD.vcxproj | 14 ++ .../VisualStudio2012/RippleD.vcxproj.filters | 12 + .../ledger/DirectoryEntryIterator.cpp | 107 +++++++++ .../ledger/DirectoryEntryIterator.h | 88 +++++++ src/ripple_app/ledger/LedgerEntrySet.cpp | 35 ++- src/ripple_app/ledger/LedgerEntrySet.h | 4 +- src/ripple_app/ledger/OrderBookIterator.cpp | 227 ++++++++++++++++++ src/ripple_app/ledger/OrderBookIterator.h | 203 ++++++++++++++++ src/ripple_app/misc/NetworkOPs.cpp | 199 ++++++--------- src/ripple_app/paths/RippleCalc.cpp | 5 +- src/ripple_app/ripple_app.h | 2 + src/ripple_app/ripple_app_pt6.cpp | 2 + src/ripple_app/tx/OfferCancelTransactor.cpp | 2 +- src/ripple_app/tx/OfferCreateTransactor.cpp | 141 +++++------ src/ripple_app/tx/OfferCreateTransactor.h | 3 +- 15 files changed, 823 insertions(+), 221 deletions(-) create mode 100644 src/ripple_app/ledger/DirectoryEntryIterator.cpp create mode 100644 src/ripple_app/ledger/DirectoryEntryIterator.h create mode 100644 src/ripple_app/ledger/OrderBookIterator.cpp create mode 100644 src/ripple_app/ledger/OrderBookIterator.h diff --git a/Builds/VisualStudio2012/RippleD.vcxproj b/Builds/VisualStudio2012/RippleD.vcxproj index 95afb1de8..f9eb20ed7 100644 --- a/Builds/VisualStudio2012/RippleD.vcxproj +++ b/Builds/VisualStudio2012/RippleD.vcxproj @@ -844,6 +844,12 @@ true true + + true + true + true + true + true true @@ -916,6 +922,12 @@ true true + + true + true + true + true + true true @@ -2426,6 +2438,7 @@ + @@ -2439,6 +2452,7 @@ + diff --git a/Builds/VisualStudio2012/RippleD.vcxproj.filters b/Builds/VisualStudio2012/RippleD.vcxproj.filters index f4919da43..5376a23a7 100644 --- a/Builds/VisualStudio2012/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2012/RippleD.vcxproj.filters @@ -2922,6 +2922,18 @@ [2] Old Ripple\ripple_core\nodestore\impl + + [2] Old Ripple\ripple_app\ledger + + + [2] Old Ripple\ripple_app\ledger + + + [2] Old Ripple\ripple_app\ledger + + + [2] Old Ripple\ripple_app\ledger + [2] Old Ripple\ripple_app\peers diff --git a/src/ripple_app/ledger/DirectoryEntryIterator.cpp b/src/ripple_app/ledger/DirectoryEntryIterator.cpp new file mode 100644 index 000000000..04a4c304a --- /dev/null +++ b/src/ripple_app/ledger/DirectoryEntryIterator.cpp @@ -0,0 +1,107 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +/** Get the current ledger entry +*/ +SLE::pointer DirectoryEntryIterator::getEntry (LedgerEntrySet& les, LedgerEntryType type) +{ + return les.entryCache (type, mEntryIndex); +} + +/** Position the iterator at the first entry +*/ +bool DirectoryEntryIterator::firstEntry (LedgerEntrySet& les) +{ + WriteLog (lsTRACE, Ledger) << "DirectoryEntryIterator::firstEntry(" << mRootIndex.GetHex() << ")"; + mEntry = 0; + mDirIndex = mRootIndex; + + return nextEntry (les); +} + +/** Advance the iterator to the next entry +*/ +bool DirectoryEntryIterator::nextEntry (LedgerEntrySet& les) +{ + + if (!mDirNode || mDirNode->getIndex() != mDirIndex) + { + WriteLog (lsTRACE, Ledger) << "DirectoryEntryIterator::nextEntry(" << mRootIndex.GetHex() << ") need dir node"; + // Are we already at the end + if (mDirIndex.isZero()) + { + WriteLog (lsTRACE, Ledger) << "DirectoryEntryIterator::nextEntry(" << mRootIndex.GetHex() << ") at end"; + return false; + } + + // Fetch the current directory + mDirNode = les.entryCache (ltDIR_NODE, mRootIndex); + if (!mDirNode) + { + WriteLog (lsTRACE, Ledger) << "DirectoryEntryIterator::nextEntry(" << mRootIndex.GetHex() << ") no dir node"; + mEntryIndex.zero(); + return false; + } + } + + if (!les.dirNext (mRootIndex, mDirNode, mEntry, mEntryIndex)) + { + mDirIndex.zero(); + mDirNode.reset(); + WriteLog (lsTRACE, Ledger) << "DirectoryEntryIterator::nextEntry(" << mRootIndex.GetHex() << ") now at end"; + return false; + } + + WriteLog (lsTRACE, Ledger) << "DirectoryEntryIterator::nextEntry(" << mRootIndex.GetHex() << ") now at " << mEntry; + return true; +} + +bool DirectoryEntryIterator::addJson (Json::Value& j) const +{ + if (mDirNode && (mEntry != 0)) + { + j["dir_root"] = mRootIndex.GetHex(); + j["dir_index"] = mDirIndex.GetHex(); + j["dir_entry"] = static_cast (mEntry); + return true; + } + return false; +} + +bool DirectoryEntryIterator::setJson (Json::Value const& j, LedgerEntrySet& les) +{ + if (!j.isMember("dir_root") || !j.isMember("dir_index") || !j.isMember("dir_entry")) + return false; +#if 0 // WRITEME + Json::Value const& dirRoot = j["dir_root"]; + Json::Value const& dirIndex = j["dir_index"]; + Json::Value const& dirEntry = j["dir_entry"]; + + assert(false); // CAUTION: This function is incomplete + + mEntry = j["dir_entry"].asUInt (); + + if (!mDirIndex.SetHex(j["dir_index"].asString())) + return false; +#endif + + return true; +} + +// vim:ts=4 diff --git a/src/ripple_app/ledger/DirectoryEntryIterator.h b/src/ripple_app/ledger/DirectoryEntryIterator.h new file mode 100644 index 000000000..48450584a --- /dev/null +++ b/src/ripple_app/ledger/DirectoryEntryIterator.h @@ -0,0 +1,88 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_DIRECTORYENTRYITERATOR_H_INCLUDED +#define RIPPLE_DIRECTORYENTRYITERATOR_H_INCLUDED + +/** An iterator that walks the ledger entries in a single directory +*/ +class DirectoryEntryIterator +{ + +public: + + DirectoryEntryIterator () : mEntry(0) + { ; } + + DirectoryEntryIterator (uint256 const& index) : mRootIndex(index), mEntry(0) + { ; } + + /** Construct from a reference to the root directory + */ + DirectoryEntryIterator (SLE::ref directory) : mEntry (0), mDirNode (directory) + { + if (mDirNode) + { + mDirIndex = mDirNode->getIndex(); + mRootIndex = mDirNode->getIndex(); + } + } + + /** Get the SLE this iterator currently references + */ + SLE::pointer getEntry (LedgerEntrySet& les, LedgerEntryType type); + + /** Make this iterator point to the first offer + */ + bool firstEntry (LedgerEntrySet&); + + /** Make this iterator point to the next offer + */ + bool nextEntry (LedgerEntrySet&); + + /** Add this iterator's position to a JSON object + */ + bool addJson (Json::Value&) const; + + /** Set this iterator's position from a JSON object + */ + bool setJson (Json::Value const&, LedgerEntrySet& les); + + uint256 const& getEntryLedgerIndex () const + { + return mEntryIndex; + } + + uint256 const& getDirectory () const + { + return mDirIndex; + } + +private: + + uint256 mRootIndex; // ledger index of the root directory + uint256 mDirIndex; // ledger index of the current directory + unsigned int mEntry; // entry index we are on (0 means first is next) + uint256 mEntryIndex; // ledger index of the current entry + SLE::pointer mDirNode; // SLE for the entry we are on +}; + +#endif + +// vim:ts=4 diff --git a/src/ripple_app/ledger/LedgerEntrySet.cpp b/src/ripple_app/ledger/LedgerEntrySet.cpp index 68f7fe6a4..7c02cf6dc 100644 --- a/src/ripple_app/ledger/LedgerEntrySet.cpp +++ b/src/ripple_app/ledger/LedgerEntrySet.cpp @@ -278,11 +278,6 @@ bool LedgerEntrySet::hasChanges () return false; } -bool LedgerEntrySet::intersect (const LedgerEntrySet& lesLeft, const LedgerEntrySet& lesRight) -{ - return true; // XXX Needs implementation -} - Json::Value LedgerEntrySet::getJson (int) const { Json::Value ret (Json::objectValue); @@ -751,7 +746,7 @@ TER LedgerEntrySet::dirDelete ( const bool bSoft) // --> True, uNodeDir is not hard and fast (pass uNodeDir=0). { uint64 uNodeCur = uNodeDir; - SLE::pointer sleNode = entryCache (ltDIR_NODE, uNodeCur ? Ledger::getDirNodeIndex (uRootIndex, uNodeCur) : uRootIndex); + SLE::pointer sleNode = entryCache (ltDIR_NODE, Ledger::getDirNodeIndex (uRootIndex, uNodeCur)); if (!sleNode) { @@ -882,11 +877,11 @@ TER LedgerEntrySet::dirDelete ( { // Not root and not last node. Can delete node. - SLE::pointer slePrevious = entryCache (ltDIR_NODE, uNodePrevious ? Ledger::getDirNodeIndex (uRootIndex, uNodePrevious) : uRootIndex); + SLE::pointer slePrevious = entryCache (ltDIR_NODE, Ledger::getDirNodeIndex (uRootIndex, uNodePrevious)); assert (slePrevious); - SLE::pointer sleNext = entryCache (ltDIR_NODE, uNodeNext ? Ledger::getDirNodeIndex (uRootIndex, uNodeNext) : uRootIndex); + SLE::pointer sleNext = entryCache (ltDIR_NODE, Ledger::getDirNodeIndex (uRootIndex, uNodeNext)); assert (slePrevious); assert (sleNext); @@ -1069,19 +1064,22 @@ void LedgerEntrySet::ownerCountAdjust (const uint160& uOwnerID, int iAmount, SLE } } -TER LedgerEntrySet::offerDelete (SLE::ref sleOffer, uint256 const& uOfferIndex, const uint160& uOwnerID) +TER LedgerEntrySet::offerDelete (SLE::pointer sleOffer) { - bool bOwnerNode = sleOffer->isFieldPresent (sfOwnerNode); // Detect legacy dirs. - uint64 uOwnerNode = sleOffer->getFieldU64 (sfOwnerNode); - TER terResult = dirDelete (false, uOwnerNode, Ledger::getOwnerDirIndex (uOwnerID), uOfferIndex, false, !bOwnerNode); + + uint256 offerIndex = sleOffer->getIndex (); + uint160 uOwnerID = sleOffer->getFieldAccount160 (sfAccount); + bool bOwnerNode = sleOffer->isFieldPresent (sfOwnerNode); // Detect legacy dirs. + uint64 uOwnerNode = sleOffer->getFieldU64 (sfOwnerNode); + uint256 uDirectory = sleOffer->getFieldH256 (sfBookDirectory); + uint64 uBookNode = sleOffer->getFieldU64 (sfBookNode); + + TER terResult = dirDelete (false, uOwnerNode, Ledger::getOwnerDirIndex (uOwnerID), offerIndex, false, !bOwnerNode); + TER terResult2 = dirDelete (false, uBookNode, uDirectory, offerIndex, true, false); + if (tesSUCCESS == terResult) ownerCountAdjust (uOwnerID, -1); - // Offer delete is always hard. Always have hints. - uint256 uDirectory = sleOffer->getFieldH256 (sfBookDirectory); - uint64 uBookNode = sleOffer->getFieldU64 (sfBookNode); - TER terResult2 = dirDelete ( false, uBookNode, uDirectory, uOfferIndex, true, false); - entryDelete (sleOffer); return (terResult == tesSUCCESS) ? terResult2 : terResult; @@ -1094,8 +1092,7 @@ TER LedgerEntrySet::offerDelete (uint256 const& uOfferIndex) if (!sleOffer) return tesSUCCESS; - const uint160 uOwnerID = sleOffer->getFieldAccount160 (sfAccount); - return offerDelete (sleOffer, uOfferIndex, uOwnerID); + return offerDelete (sleOffer); } // Returns amount owed by uToAccountID to uFromAccountID. diff --git a/src/ripple_app/ledger/LedgerEntrySet.h b/src/ripple_app/ledger/LedgerEntrySet.h index c490e7038..ea203e643 100644 --- a/src/ripple_app/ledger/LedgerEntrySet.h +++ b/src/ripple_app/ledger/LedgerEntrySet.h @@ -182,7 +182,7 @@ public: // Offer functions. TER offerDelete (uint256 const & uOfferIndex); - TER offerDelete (SLE::ref sleOffer, uint256 const & uOfferIndex, const uint160 & uOwnerID); + TER offerDelete (SLE::pointer sleOffer); // Balance functions. uint32 rippleTransferRate (const uint160 & uIssuerID); @@ -246,8 +246,6 @@ public: return mEntries.end (); } - static bool intersect (const LedgerEntrySet & lesLeft, const LedgerEntrySet & lesRight); - private: Ledger::pointer mLedger; std::map mEntries; // cannot be unordered! diff --git a/src/ripple_app/ledger/OrderBookIterator.cpp b/src/ripple_app/ledger/OrderBookIterator.cpp new file mode 100644 index 000000000..384d957c5 --- /dev/null +++ b/src/ripple_app/ledger/OrderBookIterator.cpp @@ -0,0 +1,227 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +/** Iterate through the directories in an order book +*/ +BookDirIterator::BookDirIterator(uint160 const& uInCurrency, uint160 const& uInIssuer, + uint160 const& uOutCurrency, uint160 const& uOutIssuer) +{ + mBase = Ledger::getBookBase(uInCurrency, uInIssuer, uOutCurrency, uOutIssuer); + mEnd = Ledger::getQualityNext(mBase); + mIndex = mBase; +} + +bool BookDirIterator::nextDirectory (LedgerEntrySet& les) +{ + WriteLog (lsTRACE, Ledger) << "BookDirectoryIterator:: nextDirectory"; + + // Are we already at the end? + if (mIndex.isZero ()) + return false; + + // Get the ledger index of the next directory + mIndex = les.getNextLedgerIndex (mIndex, mEnd); + + if (mIndex.isZero ()) + { + // We ran off the end of the book + WriteLog (lsTRACE, Ledger) << "BookDirectoryIterator:: no next ledger index"; + return false; + } + assert (mIndex < mEnd); + + WriteLog (lsTRACE, Ledger) << "BookDirectoryIterator:: index " << mIndex.GetHex(); + + // Retrieve the SLE from the LES + mOfferDir = les.entryCache (ltDIR_NODE, mIndex); + assert (mOfferDir); + + return !!mOfferDir; +} + +bool BookDirIterator::firstDirectory (LedgerEntrySet& les) +{ + WriteLog (lsTRACE, Ledger) << "BookDirIterator(" << mBase.GetHex() << ") firstDirectory"; + + /** Jump to the beginning + */ + mIndex = mBase; + + return nextDirectory (les); +} + +/** The LES may have changed. Repoint to the current directory if it still exists, + Otherwise, go to the next one. +*/ +bool BookDirIterator::resync (LedgerEntrySet& les) +{ + if (mIndex.isZero ()) + mIndex = mBase; + else if (mIndex != mBase) + --mIndex; + + return nextDirectory (les); +} + +DirectoryEntryIterator BookDirIterator::getOfferIterator () const +{ + WriteLog (lsTRACE, Ledger) << "BookDirIterator(" << mBase.GetHex() << ") get offer iterator"; + return DirectoryEntryIterator (mOfferDir); +} + +uint64 BookDirIterator::getRate () const +{ + return Ledger::getQuality(mIndex); +} + +bool BookDirIterator::addJson (Json::Value& jv) const +{ + if (! (*this)) + return false; + + jv["book_index"] = mIndex.GetHex(); + return true; +} + +bool BookDirIterator::setJson(Json::Value const& jv) +{ + if (!jv.isMember("book_index")) + return false; + const Json::Value& bi = jv["book_index"]; + if (!bi.isString ()) + return false; + mIndex.SetHexExact(bi.asString()); + return true; +} + +bool OrderBookIterator::addJson (Json::Value& jv) const +{ + return mOfferIterator.addJson(jv) && mDirectoryIterator.addJson(jv); +} + +bool OrderBookIterator::setJson (Json::Value const& jv) +{ + return mDirectoryIterator.setJson (jv) && mOfferIterator.setJson (jv, mEntrySet); +} + +STAmount OrderBookIterator::getCurrentRate () const +{ + return mDirectoryIterator.getCurrentRate(); +} + +uint64 OrderBookIterator::getCurrentQuality () const +{ + return mDirectoryIterator.getCurrentQuality(); +} + +uint256 OrderBookIterator::getCurrentDirectory () const +{ + return mOfferIterator.getDirectory (); +} + +uint256 OrderBookIterator::getCurrentIndex () const +{ + return mOfferIterator.getEntryLedgerIndex(); +} + +/** Retrieve the offer the iterator points to +*/ +SLE::pointer OrderBookIterator::getCurrentOffer () +{ + return mOfferIterator.getEntry (mEntrySet, ltOFFER); +} + +/** Go to the first offer in the first directory +*/ +bool OrderBookIterator::firstOffer () +{ + WriteLog (lsTRACE, Ledger) << "OrderBookIterator: first offer"; + // Go to first directory in order book + if (!mDirectoryIterator.firstDirectory (mEntrySet)) + { + WriteLog (lsTRACE, Ledger) << "OrderBookIterator: no first directory"; + return false; + } + mOfferIterator = mDirectoryIterator.getOfferIterator (); + + // Take the next offer + return nextOffer(); +} + +/** Go to the next offer, possibly changing directories +*/ +bool OrderBookIterator::nextOffer () +{ + WriteLog (lsTRACE, Ledger) << "OrderBookIterator: next offer"; + do + { + + // Is there a next offer in the current directory + if (mOfferIterator.nextEntry (mEntrySet)) + { + WriteLog (lsTRACE, Ledger) << "OrderBookIterator: there is a next offer in this directory"; + return true; + } + + // Is there a next directory + + if (!mDirectoryIterator.nextDirectory (mEntrySet)) + { + WriteLog (lsTRACE, Ledger) << "OrderBookIterator: there is no next directory"; + return false; + } + WriteLog (lsTRACE, Ledger) << "OrderBookIterator: going to next directory"; + + // Set to before its first offer + mOfferIterator = mDirectoryIterator.getOfferIterator (); + } + while (1); +} + +/** Rewind to the beginning of this directory, then take the next offer +*/ +bool OrderBookIterator::rewind () +{ + if (!mDirectoryIterator.resync (mEntrySet)) + return false; + + mOfferIterator = mDirectoryIterator.getOfferIterator (); + return nextOffer (); +} + +/** Go to before the first offer in the next directory +*/ +bool OrderBookIterator::nextDir () +{ + if (!mDirectoryIterator.nextDirectory (mEntrySet)) + return false; + + mOfferIterator = mDirectoryIterator.getOfferIterator (); + + return true; +} + +/** Advance to the next offer in this directory +*/ +bool OrderBookIterator::nextOfferInDir () +{ + return mOfferIterator.nextEntry (mEntrySet); +} + +// vim:ts=4 diff --git a/src/ripple_app/ledger/OrderBookIterator.h b/src/ripple_app/ledger/OrderBookIterator.h new file mode 100644 index 000000000..aedd14e0e --- /dev/null +++ b/src/ripple_app/ledger/OrderBookIterator.h @@ -0,0 +1,203 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_ORDERBOOKITERATOR_H_INCLUDED +#define RIPPLE_ORDERBOOKITERATOR_H_INCLUDED + +/** An iterator that walks the directories in a book +*/ +class BookDirIterator +{ + +public: + + BookDirIterator () + { ; } + + BookDirIterator ( + uint160 const& uInCurrency, uint160 const& uInIssuer, + uint160 const& uOutCurrency, uint160 const& uOutIssuer); + + uint256 const& getBookBase () const + { + return mBase; + } + + uint256 const& getBookEnd () const + { + return mEnd; + } + + uint256 const& getCurrentIndex() const + { + return mIndex; + } + + void setCurrentIndex(uint256 const& index) + { + mIndex = index; + } + + /** Get the current exchange rate + */ + STAmount getCurrentRate () const + { + return STAmount::setRate (getCurrentQuality()); + } + + /** Get the current quality + */ + uint64 getCurrentQuality () const + { + return Ledger::getQuality(mIndex); + } + + /** Make this iterator refer to the next book + */ + bool nextDirectory (LedgerEntrySet&); + + /** Make this iterator refer to the first book + */ + bool firstDirectory (LedgerEntrySet&); + + /** The LES may have changed + Resync the iterator + */ + bool resync (LedgerEntrySet&); + + /** Get an iterator to the offers in this directory + */ + DirectoryEntryIterator getOfferIterator () const; + + uint64 getRate () const; + + bool addJson (Json::Value&) const; + + bool setJson (Json::Value const&); + + // Does this iterator currently point to a valid directory + operator bool () const + { + return mOfferDir && (mOfferDir->getIndex() == mIndex); + } + +private: + + uint256 mBase; // The first index a directory in the book can have + uint256 mEnd; // The first index a directory in the book cannot have + uint256 mIndex; // The index we are currently on + SLE::pointer mOfferDir; // The directory page we are currently on +}; + +/** An iterator that walks the offers in a book + CAUTION: The LedgerEntrySet must remain valid for the life of the iterator +*/ +class OrderBookIterator +{ +public: + + OrderBookIterator ( + LedgerEntrySet& set, + uint160 const& uInCurrency, + uint160 const& uInIssuer, + uint160 const& uOutCurrency, + uint160 const& uOutIssuer) : + mEntrySet (set), + mDirectoryIterator (uInCurrency, uInIssuer, uOutCurrency, uOutIssuer) + { ; } + + bool addJson (Json::Value&) const; + + bool setJson (Json::Value const&); + + STAmount getCurrentRate () const; + + uint64 getCurrentQuality () const; + + uint256 getCurrentIndex () const; + + uint256 getCurrentDirectory () const; + + SLE::pointer getCurrentOffer (); + + /** Position the iterator at the first offer in the first directory. + Returns whether there is an offer to point to. + */ + bool firstOffer (); + + /** Position the iterator at the next offer, going to the next directory if needed + Returns whether there is a next offer. + */ + bool nextOffer (); + + /** Position the iterator at the first offer in the next directory. + Returns whether there is a next directory to point to. + */ + bool nextDir (); + + /** Position the iterator at the first offer in the current directory. + Returns whether there is an offer in the directory. + */ + bool firstOfferInDir (); + + /** Position the iterator at the next offer in the current directory. + Returns whether there is a next offer in the directory. + */ + bool nextOfferInDir (); + + /** Position the iterator at the first offer at the current quality. + If none, position the iterator at the first offer at the next quality. + This rather odd semantic is required by the payment engine. + */ + bool rewind (); + + LedgerEntrySet& peekEntrySet () + { + return mEntrySet; + } + + BookDirIterator const& peekDirIterator () const + { + return mDirectoryIterator; + } + + DirectoryEntryIterator const& peekDirectoryEntryIterator () const + { + return mOfferIterator; + } + + BookDirIterator& peekDirIterator () + { + return mDirectoryIterator; + } + + DirectoryEntryIterator& peekDirectoryEntryIterator () + { + return mOfferIterator; + } + +private: + LedgerEntrySet& mEntrySet; + BookDirIterator mDirectoryIterator; + DirectoryEntryIterator mOfferIterator; +}; + +#endif + +// vim:ts=4 diff --git a/src/ripple_app/misc/NetworkOPs.cpp b/src/ripple_app/misc/NetworkOPs.cpp index 598f8493c..97b2c0317 100644 --- a/src/ripple_app/misc/NetworkOPs.cpp +++ b/src/ripple_app/misc/NetworkOPs.cpp @@ -2782,25 +2782,9 @@ void NetworkOPsImp::getBookPage (Ledger::pointer lpLedger, const uint160& uTaker Json::Value& jvOffers = (jvResult["offers"] = Json::Value (Json::arrayValue)); std::map umBalance; - const uint256 uBookBase = Ledger::getBookBase (uTakerPaysCurrencyID, uTakerPaysIssuerID, uTakerGetsCurrencyID, uTakerGetsIssuerID); - const uint256 uBookEnd = Ledger::getQualityNext (uBookBase); - uint256 uTipIndex = uBookBase; - - m_journal.trace << boost::str (boost::format ("getBookPage: uTakerPaysCurrencyID=%s uTakerPaysIssuerID=%s") % STAmount::createHumanCurrency (uTakerPaysCurrencyID) % RippleAddress::createHumanAccountID (uTakerPaysIssuerID)); - m_journal.trace << boost::str (boost::format ("getBookPage: uTakerGetsCurrencyID=%s uTakerGetsIssuerID=%s") % STAmount::createHumanCurrency (uTakerGetsCurrencyID) % RippleAddress::createHumanAccountID (uTakerGetsIssuerID)); - m_journal.trace << boost::str (boost::format ("getBookPage: uBookBase=%s") % uBookBase); - m_journal.trace << boost::str (boost::format ("getBookPage: uBookEnd=%s") % uBookEnd); - m_journal.trace << boost::str (boost::format ("getBookPage: uTipIndex=%s") % uTipIndex); LedgerEntrySet lesActive (lpLedger, tapNONE, true); - - bool bDone = false; - bool bDirectAdvance = true; - - SLE::pointer sleOfferDir; - uint256 uOfferIndex; - unsigned int uBookEntry; - STAmount saDirRate; + OrderBookIterator obIterator (lesActive, uTakerPaysCurrencyID, uTakerPaysIssuerID, uTakerGetsCurrencyID, uTakerGetsIssuerID); unsigned int iLeft = iLimit; @@ -2809,146 +2793,107 @@ void NetworkOPsImp::getBookPage (Ledger::pointer lpLedger, const uint160& uTaker uint32 uTransferRate = lesActive.rippleTransferRate (uTakerGetsIssuerID); - while (!bDone && (iLeft > 0)) + while ((iLeft > 0) && obIterator.nextOffer ()) { - if (bDirectAdvance) + + SLE::pointer sleOffer = obIterator.getCurrentOffer(); + if (sleOffer) { - bDirectAdvance = false; + const uint160 uOfferOwnerID = sleOffer->getFieldAccount160 (sfAccount); + const STAmount& saTakerGets = sleOffer->getFieldAmount (sfTakerGets); + const STAmount& saTakerPays = sleOffer->getFieldAmount (sfTakerPays); + STAmount saDirRate = obIterator.getCurrentRate (); + STAmount saOwnerFunds; - m_journal.trace << "getBookPage: bDirectAdvance"; - - sleOfferDir = lesActive.entryCache (ltDIR_NODE, lpLedger->getNextLedgerIndex (uTipIndex, uBookEnd)); - - if (!sleOfferDir) + if (uTakerGetsIssuerID == uOfferOwnerID) { - m_journal.trace << "getBookPage: bDone"; - bDone = true; + // If offer is selling issuer's own IOUs, it is fully funded. + saOwnerFunds = saTakerGets; } else { - uTipIndex = sleOfferDir->getIndex (); - saDirRate = STAmount::setRate (Ledger::getQuality (uTipIndex)); + std::map::const_iterator umBalanceEntry = umBalance.find (uOfferOwnerID); - lesActive.dirFirst (uTipIndex, sleOfferDir, uBookEntry, uOfferIndex); - - m_journal.trace << boost::str (boost::format ("getBookPage: uTipIndex=%s") % uTipIndex); - m_journal.trace << boost::str (boost::format ("getBookPage: uOfferIndex=%s") % uOfferIndex); - } - } - - if (!bDone) - { - SLE::pointer sleOffer = lesActive.entryCache (ltOFFER, uOfferIndex); - - if (sleOffer) - { - const uint160 uOfferOwnerID = sleOffer->getFieldAccount160 (sfAccount); - const STAmount& saTakerGets = sleOffer->getFieldAmount (sfTakerGets); - const STAmount& saTakerPays = sleOffer->getFieldAmount (sfTakerPays); - STAmount saOwnerFunds; - - if (uTakerGetsIssuerID == uOfferOwnerID) + if (umBalanceEntry != umBalance.end ()) { - // If offer is selling issuer's own IOUs, it is fully funded. - saOwnerFunds = saTakerGets; + // Found in running balance table. + + saOwnerFunds = umBalanceEntry->second; + // m_journal.info << boost::str(boost::format("getBookPage: saOwnerFunds=%s (cached)") % saOwnerFunds.getFullText()); } else { - std::map::const_iterator umBalanceEntry = umBalance.find (uOfferOwnerID); + // Did not find balance in table. - if (umBalanceEntry != umBalance.end ()) + saOwnerFunds = lesActive.accountHolds (uOfferOwnerID, uTakerGetsCurrencyID, uTakerGetsIssuerID); + + // m_journal.info << boost::str(boost::format("getBookPage: saOwnerFunds=%s (new)") % saOwnerFunds.getFullText()); + if (saOwnerFunds.isNegative ()) { - // Found in running balance table. + // Treat negative funds as zero. - saOwnerFunds = umBalanceEntry->second; - // m_journal.info << boost::str(boost::format("getBookPage: saOwnerFunds=%s (cached)") % saOwnerFunds.getFullText()); - } - else - { - // Did not find balance in table. - - saOwnerFunds = lesActive.accountHolds (uOfferOwnerID, uTakerGetsCurrencyID, uTakerGetsIssuerID); - - // m_journal.info << boost::str(boost::format("getBookPage: saOwnerFunds=%s (new)") % saOwnerFunds.getFullText()); - if (saOwnerFunds.isNegative ()) - { - // Treat negative funds as zero. - - saOwnerFunds.zero (); - } + saOwnerFunds.zero (); } } + } - Json::Value jvOffer = sleOffer->getJson (0); + Json::Value jvOffer = sleOffer->getJson (0); - STAmount saTakerGetsFunded; - STAmount saOwnerFundsLimit; - uint32 uOfferRate; + STAmount saTakerGetsFunded; + STAmount saOwnerFundsLimit; + uint32 uOfferRate; - if (uTransferRate != QUALITY_ONE // Have a tranfer fee. - && uTakerID != uTakerGetsIssuerID // Not taking offers of own IOUs. - && uTakerGetsIssuerID != uOfferOwnerID) // Offer owner not issuing ownfunds - { - // Need to charge a transfer fee to offer owner. - uOfferRate = uTransferRate; - saOwnerFundsLimit = STAmount::divide (saOwnerFunds, STAmount (CURRENCY_ONE, ACCOUNT_ONE, uOfferRate, -9)); - } - else - { - uOfferRate = QUALITY_ONE; - saOwnerFundsLimit = saOwnerFunds; - } - - if (saOwnerFundsLimit >= saTakerGets) - { - // Sufficient funds no shenanigans. - saTakerGetsFunded = saTakerGets; - } - else - { - // m_journal.info << boost::str(boost::format("getBookPage: saTakerGets=%s") % saTakerGets.getFullText()); - // m_journal.info << boost::str(boost::format("getBookPage: saTakerPays=%s") % saTakerPays.getFullText()); - // m_journal.info << boost::str(boost::format("getBookPage: saOwnerFunds=%s") % saOwnerFunds.getFullText()); - // m_journal.info << boost::str(boost::format("getBookPage: saDirRate=%s") % saDirRate.getText()); - // m_journal.info << boost::str(boost::format("getBookPage: multiply=%s") % STAmount::multiply(saTakerGetsFunded, saDirRate).getFullText()); - // m_journal.info << boost::str(boost::format("getBookPage: multiply=%s") % STAmount::multiply(saTakerGetsFunded, saDirRate, saTakerPays).getFullText()); - - // Only provide, if not fully funded. - - saTakerGetsFunded = saOwnerFundsLimit; - - saTakerGetsFunded.setJson (jvOffer["taker_gets_funded"]); - std::min (saTakerPays, STAmount::multiply (saTakerGetsFunded, saDirRate, saTakerPays)).setJson (jvOffer["taker_pays_funded"]); - } - - STAmount saOwnerPays = (QUALITY_ONE == uOfferRate) - ? saTakerGetsFunded - : std::min (saOwnerFunds, STAmount::multiply (saTakerGetsFunded, STAmount (CURRENCY_ONE, ACCOUNT_ONE, uOfferRate, -9))); - - umBalance[uOfferOwnerID] = saOwnerFunds - saOwnerPays; - - if (!saOwnerFunds.isZero () || uOfferOwnerID == uTakerID) - { - // Only provide funded offers and offers of the taker. - Json::Value& jvOf = jvOffers.append (jvOffer); - jvOf["quality"] = saDirRate.getText (); - --iLeft; - } + if (uTransferRate != QUALITY_ONE // Have a tranfer fee. + && uTakerID != uTakerGetsIssuerID // Not taking offers of own IOUs. + && uTakerGetsIssuerID != uOfferOwnerID) // Offer owner not issuing ownfunds + { + // Need to charge a transfer fee to offer owner. + uOfferRate = uTransferRate; + saOwnerFundsLimit = STAmount::divide (saOwnerFunds, STAmount (CURRENCY_ONE, ACCOUNT_ONE, uOfferRate, -9)); } else { - m_journal.warning << "Missing offer"; - } + uOfferRate = QUALITY_ONE; + saOwnerFundsLimit = saOwnerFunds; + } - if (!lesActive.dirNext (uTipIndex, sleOfferDir, uBookEntry, uOfferIndex)) + if (saOwnerFundsLimit >= saTakerGets) { - bDirectAdvance = true; + // Sufficient funds no shenanigans. + saTakerGetsFunded = saTakerGets; } else { - m_journal.trace << boost::str (boost::format ("getBookPage: uOfferIndex=%s") % uOfferIndex); + // m_journal.info << boost::str(boost::format("getBookPage: saTakerGets=%s") % saTakerGets.getFullText()); + // m_journal.info << boost::str(boost::format("getBookPage: saTakerPays=%s") % saTakerPays.getFullText()); + // m_journal.info << boost::str(boost::format("getBookPage: saOwnerFunds=%s") % saOwnerFunds.getFullText()); + // m_journal.info << boost::str(boost::format("getBookPage: saDirRate=%s") % saDirRate.getText()); + // m_journal.info << boost::str(boost::format("getBookPage: multiply=%s") % STAmount::multiply(saTakerGetsFunded, saDirRate).getFullText()); + // m_journal.info << boost::str(boost::format("getBookPage: multiply=%s") % STAmount::multiply(saTakerGetsFunded, saDirRate, saTakerPays).getFullText()); + + // Only provide, if not fully funded. + + saTakerGetsFunded = saOwnerFundsLimit; + + saTakerGetsFunded.setJson (jvOffer["taker_gets_funded"]); + std::min (saTakerPays, STAmount::multiply (saTakerGetsFunded, saDirRate, saTakerPays)).setJson (jvOffer["taker_pays_funded"]); } + + STAmount saOwnerPays = (QUALITY_ONE == uOfferRate) + ? saTakerGetsFunded + : std::min (saOwnerFunds, STAmount::multiply (saTakerGetsFunded, STAmount (CURRENCY_ONE, ACCOUNT_ONE, uOfferRate, -9))); + + umBalance[uOfferOwnerID] = saOwnerFunds - saOwnerPays; + + if (!saOwnerFunds.isZero () || uOfferOwnerID == uTakerID) + { + // Only provide funded offers and offers of the taker. + Json::Value& jvOf = jvOffers.append (jvOffer); + jvOf["quality"] = saDirRate.getText (); + --iLeft; + } + } } diff --git a/src/ripple_app/paths/RippleCalc.cpp b/src/ripple_app/paths/RippleCalc.cpp index c4c90bff8..859ad7651 100644 --- a/src/ripple_app/paths/RippleCalc.cpp +++ b/src/ripple_app/paths/RippleCalc.cpp @@ -197,9 +197,10 @@ TER RippleCalc::calcNodeAdvance ( { // Got a new offer. sleOffer = lesActive.entryCache (ltOFFER, uOfferIndex); + if (!sleOffer) { - WriteLog (lsWARNING, RippleCalc) << "Missing offer in directory, " << uOfferIndex; + WriteLog (lsWARNING, RippleCalc) << "Missing offer in directory"; bEntryAdvance = true; } else @@ -335,7 +336,7 @@ TER RippleCalc::calcNodeAdvance ( bFundsDirty = false; bEntryAdvance = false; } - } + } } while (tesSUCCESS == terResult && (bEntryAdvance || bDirectAdvance)); diff --git a/src/ripple_app/ripple_app.h b/src/ripple_app/ripple_app.h index f40630e82..da8af341b 100644 --- a/src/ripple_app/ripple_app.h +++ b/src/ripple_app/ripple_app.h @@ -111,6 +111,8 @@ namespace ripple { #include "ledger/AcceptedLedgerTx.h" #include "ledger/AcceptedLedger.h" #include "ledger/LedgerEntrySet.h" +#include "ledger/DirectoryEntryIterator.h" +#include "ledger/OrderBookIterator.h" #include "tx/TransactionEngine.h" #include "misc/CanonicalTXSet.h" #include "ledger/LedgerHolder.h" diff --git a/src/ripple_app/ripple_app_pt6.cpp b/src/ripple_app/ripple_app_pt6.cpp index 5837b4562..f0d57e1d3 100644 --- a/src/ripple_app/ripple_app_pt6.cpp +++ b/src/ripple_app/ripple_app_pt6.cpp @@ -26,6 +26,8 @@ namespace ripple #include "ledger/LedgerEntrySet.cpp" #include "ledger/AcceptedLedger.cpp" +#include "ledger/DirectoryEntryIterator.cpp" +#include "ledger/OrderBookIterator.cpp" #include "consensus/DisputedTx.cpp" #include "misc/HashRouter.cpp" #include "misc/Offer.cpp" diff --git a/src/ripple_app/tx/OfferCancelTransactor.cpp b/src/ripple_app/tx/OfferCancelTransactor.cpp index b3142bb0b..f565c6456 100644 --- a/src/ripple_app/tx/OfferCancelTransactor.cpp +++ b/src/ripple_app/tx/OfferCancelTransactor.cpp @@ -51,7 +51,7 @@ TER OfferCancelTransactor::doApply () { WriteLog (lsDEBUG, OfferCancelTransactor) << "OfferCancel: uOfferSequence=" << uOfferSequence; - terResult = mEngine->getNodes ().offerDelete (sleOffer, uOfferIndex, mTxnAccountID); + terResult = mEngine->getNodes ().offerDelete (sleOffer); } else { diff --git a/src/ripple_app/tx/OfferCreateTransactor.cpp b/src/ripple_app/tx/OfferCreateTransactor.cpp index 1c78673e6..1ca1448e3 100644 --- a/src/ripple_app/tx/OfferCreateTransactor.cpp +++ b/src/ripple_app/tx/OfferCreateTransactor.cpp @@ -22,7 +22,6 @@ SETUP_LOG (OfferCreateTransactor) // Make sure an offer is still valid. If not, mark it unfunded. bool OfferCreateTransactor::bValidOffer ( SLE::ref sleOffer, - uint256 const& uOfferIndex, const uint160& uOfferOwnerID, const STAmount& saOfferPays, const STAmount& saOfferGets, @@ -39,7 +38,7 @@ bool OfferCreateTransactor::bValidOffer ( // Offer is expired. Expired offers are considered unfunded. Delete it. WriteLog (lsINFO, OfferCreateTransactor) << "bValidOffer: encountered expired offer"; - usOfferUnfundedFound.insert (uOfferIndex); + usOfferUnfundedFound.insert (sleOffer->getIndex()); bValid = false; } @@ -48,7 +47,7 @@ bool OfferCreateTransactor::bValidOffer ( // Would take own offer. Consider old offer expired. Delete it. WriteLog (lsINFO, OfferCreateTransactor) << "bValidOffer: encountered taker's own old offer"; - usOfferUnfundedFound.insert (uOfferIndex); + usOfferUnfundedFound.insert (sleOffer->getIndex()); bValid = false; } @@ -58,7 +57,7 @@ bool OfferCreateTransactor::bValidOffer ( WriteLog (lsWARNING, OfferCreateTransactor) << boost::str (boost::format ("bValidOffer: BAD OFFER: saOfferPays=%s saOfferGets=%s") % saOfferPays % saOfferGets); - usOfferUnfundedFound.insert (uOfferIndex); + usOfferUnfundedFound.insert (sleOffer->getIndex()); bValid = false; } @@ -78,12 +77,12 @@ bool OfferCreateTransactor::bValidOffer ( if (account != usAccountTouched.end ()) { // Previously touched account. - usOfferUnfundedBecame.insert (uOfferIndex); // Delete unfunded offer on success. + usOfferUnfundedBecame.insert (sleOffer->getIndex()); // Delete unfunded offer on success. } else { // Never touched source account. - usOfferUnfundedFound.insert (uOfferIndex); // Delete found unfunded offer when possible. + usOfferUnfundedFound.insert (sleOffer->getIndex()); // Delete found unfunded offer when possible. } bValid = false; @@ -130,8 +129,6 @@ TER OfferCreateTransactor::takeOffers ( WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: bSell: " << bSell << ": against book: " << uBookBase.ToString (); LedgerEntrySet& lesActive = mEngine->getNodes (); - uint256 uTipIndex = uBookBase; - const uint256 uBookEnd = Ledger::getQualityNext (uBookBase); const uint64 uTakeQuality = STAmount::getRate (saTakerGets, saTakerPays); STAmount saTakerRate = STAmount::setRate (uTakeQuality); const uint160 uTakerPaysAccountID = saTakerPays.getIssuer (); @@ -145,57 +142,35 @@ TER OfferCreateTransactor::takeOffers ( saTakerGot = STAmount (saTakerGets.getCurrency (), saTakerGets.getIssuer ()); bUnfunded = false; - while (temUNCERTAIN == terResult) + OrderBookIterator bookIterator (lesActive, + saTakerPays.getCurrency(), saTakerPays.getIssuer(), + saTakerGets.getCurrency(), saTakerGets.getIssuer()); + + while ((temUNCERTAIN == terResult) && bookIterator.nextOffer()) { - SLE::pointer sleOfferDir; - uint64 uTipQuality = 0; STAmount saTakerFunds = lesActive.accountFunds (uTakerAccountID, saTakerPays); STAmount saSubTakerPays = saTakerPays - saTakerPaid; // How much more to spend. STAmount saSubTakerGets = saTakerGets - saTakerGot; // How much more is wanted. + uint64 uTipQuality = bookIterator.getCurrentQuality(); - // Figure out next offer to take, if needed. - if (saTakerFunds.isPositive () // Taker has funds available. - && saSubTakerPays.isPositive () - && saSubTakerGets.isPositive ()) + if (!saTakerFunds.isPositive ()) { - sleOfferDir = mEngine->entryCache (ltDIR_NODE, lesActive.getNextLedgerIndex (uTipIndex, uBookEnd)); - - if (sleOfferDir) - { - uTipIndex = sleOfferDir->getIndex (); - uTipQuality = Ledger::getQuality (uTipIndex); - - WriteLog (lsDEBUG, OfferCreateTransactor) << boost::str (boost::format ("takeOffers: possible counter offer found: uTipQuality=%d uTipIndex=%s") - % uTipQuality - % uTipIndex.ToString ()); - - } - else - { - WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: counter offer book is empty: " - << uTipIndex.ToString () - << " ... " - << uBookEnd.ToString (); - } + // Taker is out of funds. Don't create the offer. + bUnfunded = true; + terResult = tesSUCCESS; } - - if (!saTakerFunds.isPositive ()) // Taker has no funds. + else if (!saSubTakerPays.isPositive() || !saSubTakerGets.isPositive()) { - // Done. Ran out of funds on previous round. As fees aren't calculated directly in this routine, funds are checked here. - WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: done: taker unfunded."; - - bUnfunded = true; // Don't create an order. - terResult = tesSUCCESS; + // Offer is completely consumed + terResult = tesSUCCESS; } - else if (!sleOfferDir // No offer directory to take. - || uTakeQuality < uTipQuality // No offers of sufficient quality available. + else if ((uTakeQuality < uTipQuality) || (bPassive && uTakeQuality == uTipQuality)) { - // Done. - STAmount saTipRate = sleOfferDir ? STAmount::setRate (uTipQuality) : saTakerRate; + // Offer does not cross this offer + STAmount saTipRate = STAmount::setRate (uTipQuality); - WriteLog (lsDEBUG, OfferCreateTransactor) << boost::str (boost::format ("takeOffers: done: dir=%d uTakeQuality=%d %c uTipQuality=%d saTakerRate=%s %c saTipRate=%s bPassive=%d") - % !!sleOfferDir + WriteLog (lsDEBUG, OfferCreateTransactor) << boost::str (boost::format ("takeOffers: done: uTakeQuality=%d %c uTipQuality=%d saTakerRate=%s %c saTipRate=%s bPassive=%d") % uTakeQuality % (uTakeQuality == uTipQuality ? '=' @@ -216,22 +191,22 @@ TER OfferCreateTransactor::takeOffers ( } else { - // Have an offer directory to consider. - WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: considering dir: " << sleOfferDir->getJson (0); + // We have a crossing offer to consider. - SLE::pointer sleBookNode; - unsigned int uBookEntry; - uint256 uOfferIndex; + SLE::pointer sleOffer = bookIterator.getCurrentOffer (); - lesActive.dirFirst (uTipIndex, sleBookNode, uBookEntry, uOfferIndex); - - SLE::pointer sleOffer = mEngine->entryCache (ltOFFER, uOfferIndex); - - if (sleOffer) + if (!sleOffer) + { // offer is in directory but not in ledger + uint256 offerIndex = bookIterator.getCurrentIndex (); + WriteLog (lsWARNING, OfferCreateTransactor) << "takeOffers: offer not found : " << offerIndex; + usMissingOffers.insert (missingOffer_t ( + bookIterator.getCurrentIndex (), bookIterator.getCurrentDirectory ())); + } + else { WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: considering offer : " << sleOffer->getJson (0); - const uint160 uOfferOwnerID = sleOffer->getFieldAccount160 (sfAccount); + const uint160& uOfferOwnerID = sleOffer->getFieldAccount160 (sfAccount); STAmount saOfferPays = sleOffer->getFieldAmount (sfTakerGets); STAmount saOfferGets = sleOffer->getFieldAmount (sfTakerPays); @@ -239,7 +214,7 @@ TER OfferCreateTransactor::takeOffers ( bool bValid; bValid = bValidOffer ( - sleOffer, uOfferIndex, uOfferOwnerID, saOfferPays, saOfferGets, + sleOffer, uOfferOwnerID, saOfferPays, saOfferGets, uTakerAccountID, usOfferUnfundedFound, usOfferUnfundedBecame, usAccountTouched, saOfferFunds); @@ -298,7 +273,7 @@ TER OfferCreateTransactor::takeOffers ( // Offer now fully claimed or now unfunded. WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: Offer claimed: Delete."; - usOfferUnfundedBecame.insert (uOfferIndex); // Delete unfunded offer on success. + usOfferUnfundedBecame.insert (sleOffer->getIndex()); // Delete unfunded offer on success. // Offer owner's account is no longer pristine. usAccountTouched.insert (uOfferOwnerID); @@ -370,14 +345,12 @@ TER OfferCreateTransactor::takeOffers ( } } } - else - { - WriteLog (lsWARNING, OfferCreateTransactor) << "missing offer"; - // WRITEME: Remove the missing offer from the directory - } } } + if (temUNCERTAIN == terResult) + terResult = tesSUCCESS; + WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: " << transToken (terResult); if (tesSUCCESS == terResult) @@ -522,7 +495,7 @@ TER OfferCreateTransactor::doApply () { WriteLog (lsWARNING, OfferCreateTransactor) << "OfferCreate: uCancelSequence=" << uCancelSequence; - terResult = mEngine->getNodes ().offerDelete (sleCancel, uCancelIndex, mTxnAccountID); + terResult = mEngine->getNodes ().offerDelete (sleCancel); } else { @@ -627,8 +600,8 @@ TER OfferCreateTransactor::doApply () lesActive.swapWith (lesCheckpoint); // Restore with just fees paid. } else if ( - !saTakerPays // Wants nothing more. - || !saTakerGets // Offering nothing more. + !saTakerPays.isPositive() // Wants nothing more. + || !saTakerGets.isPositive() // Offering nothing more. || bImmediateOrCancel // Do not persist. || !lesActive.accountFunds (mTxnAccountID, saTakerGets).isPositive () // Not funded. || bUnfunded) // Consider unfunded. @@ -729,6 +702,8 @@ TER OfferCreateTransactor::doApply () // On storing meta data, delete offers that were found unfunded to prevent encountering them in future. if (tesSUCCESS == terResult) { + + // Go through the list of unfunded offers and remove them BOOST_FOREACH (uint256 const & uOfferIndex, usOfferUnfundedFound) { @@ -739,6 +714,36 @@ TER OfferCreateTransactor::doApply () if (tesSUCCESS != terResult) break; } + + // Go through the list of offers not found and remove them from the order book + BOOST_FOREACH (missingOffer_t const& indexes, usMissingOffers) + { + SLE::pointer sleDirectory = lesActive.entryCache (ltDIR_NODE, indexes.second); + if (sleDirectory) + { + STVector256 svIndexes = sleDirectory->getFieldV256 (sfIndexes); + std::vector& vuiIndexes = svIndexes.peekValue(); + std::vector::iterator it = std::find (vuiIndexes.begin(), vuiIndexes.end (), indexes.first); + if (it != vuiIndexes.end ()) + { + vuiIndexes.erase (it); + sleDirectory->setFieldV256 (sfIndexes, svIndexes); + lesActive.entryModify (sleDirectory); + WriteLog (lsWARNING, OfferCreateTransactor) << "takeOffers: offer " << indexes.first << + " removed from directory " << indexes.second; + } + else + { + WriteLog (lsINFO, OfferCreateTransactor) << "takeOffers: offer " << indexes.first << + " not found in directory " << indexes.second; + } + } + else + { + WriteLog (lsWARNING, OfferCreateTransactor) << "takeOffers: directory " << indexes.second << + " not found for offer " << indexes.first; + } + } } CondLog (tesSUCCESS != terResult, lsINFO, OfferCreateTransactor) << boost::str (boost::format ("OfferCreate: final terResult=%s") % transToken (terResult)); diff --git a/src/ripple_app/tx/OfferCreateTransactor.h b/src/ripple_app/tx/OfferCreateTransactor.h index dccf0a2c7..ec418c35e 100644 --- a/src/ripple_app/tx/OfferCreateTransactor.h +++ b/src/ripple_app/tx/OfferCreateTransactor.h @@ -29,7 +29,6 @@ public: private: bool bValidOffer ( SLE::ref sleOfferDir, - uint256 const& uOffer, const uint160& uOfferOwnerID, const STAmount& saOfferPays, const STAmount& saOfferGets, @@ -54,6 +53,8 @@ private: boost::unordered_set usOfferUnfundedFound; // Offers found unfunded. + typedef std::pair missingOffer_t; + boost::unordered_set usMissingOffers; }; #endif