diff --git a/src/cpp/ripple/OrderBook.cpp b/src/cpp/ripple/OrderBook.cpp index 2cf716257e..d140c8ce7d 100644 --- a/src/cpp/ripple/OrderBook.cpp +++ b/src/cpp/ripple/OrderBook.cpp @@ -13,11 +13,11 @@ OrderBook::OrderBook(SerializedLedgerEntry::pointer ledgerEntry) const STAmount saTakerGets = ledgerEntry->getFieldAmount(sfTakerGets); const STAmount saTakerPays = ledgerEntry->getFieldAmount(sfTakerPays); - mCurrencyIn = saTakerGets.getCurrency(); - mCurrencyOut = saTakerPays.getCurrency(); - mIssuerIn = saTakerGets.getIssuer(); - mIssuerOut = saTakerPays.getIssuer(); + mCurrencyIn = saTakerPays.getCurrency(); + mCurrencyOut = saTakerGets.getCurrency(); + mIssuerIn = saTakerPays.getIssuer(); + mIssuerOut = saTakerGets.getIssuer(); - mBookBase=Ledger::getBookBase(mCurrencyOut,mIssuerOut,mCurrencyIn,mIssuerIn); + mBookBase=Ledger::getBookBase(mCurrencyIn, mIssuerIn, mCurrencyOut, mIssuerOut); } // vim:ts=4 diff --git a/src/cpp/ripple/OrderBookDB.cpp b/src/cpp/ripple/OrderBookDB.cpp index 192cab703b..70d02744d2 100644 --- a/src/cpp/ripple/OrderBookDB.cpp +++ b/src/cpp/ripple/OrderBookDB.cpp @@ -1,28 +1,42 @@ -#include "OrderBookDB.h" -#include "Log.h" #include +#include "OrderBookDB.h" +#include "Log.h" + +SETUP_LOG(); // TODO: this would be way faster if we could just look under the order dirs OrderBookDB::OrderBookDB(Ledger::pointer ledger) { // walk through the entire ledger looking for orderbook entries - uint256 currentIndex=ledger->getFirstLedgerIndex(); - while(currentIndex.isNonZero()) + uint256 currentIndex = ledger->getFirstLedgerIndex(); + + cLog(lsDEBUG) << "OrderBookDB>"; + + while (currentIndex.isNonZero()) { SLE::pointer entry=ledger->getSLE(currentIndex); - OrderBook::pointer book=OrderBook::newOrderBook(entry); - if(book) + OrderBook::pointer book = OrderBook::newOrderBook(entry); + if (book) { - if( mKnownMap.find(book->getBookBase()) != mKnownMap.end() ) - { - mKnownMap[book->getBookBase()]=true; + cLog(lsDEBUG) << "OrderBookDB: found book"; - if(!book->getCurrencyIn()) - { // XRP + if (mKnownMap.find(book->getBookBase()) == mKnownMap.end()) + { + mKnownMap[book->getBookBase()] = true; + + cLog(lsDEBUG) << "OrderBookDB: unknown book in: " + << STAmount::createHumanCurrency(book->getCurrencyIn()) + << " -> " + << STAmount::createHumanCurrency(book->getCurrencyOut()); + + if (!book->getCurrencyIn()) + { + // XRP mXRPOrders.push_back(book); - }else + } + else { mIssuerMap[book->getIssuerIn()].push_back(book); } @@ -31,26 +45,29 @@ OrderBookDB::OrderBookDB(Ledger::pointer ledger) currentIndex=ledger->getNextLedgerIndex(currentIndex); } + + cLog(lsDEBUG) << "OrderBookDB<"; } // return list of all orderbooks that want IssuerID std::vector& OrderBookDB::getBooks(const uint160& issuerID) { - if( mIssuerMap.find(issuerID) == mIssuerMap.end() ) return mEmptyVector; - else return( mIssuerMap[issuerID]); + return mIssuerMap.find(issuerID) == mIssuerMap.end() + ? mEmptyVector + : mIssuerMap[issuerID]; } // return list of all orderbooks that want this issuerID and currencyID void OrderBookDB::getBooks(const uint160& issuerID, const uint160& currencyID, std::vector& bookRet) { - if( mIssuerMap.find(issuerID) == mIssuerMap.end() ) + if (mIssuerMap.find(issuerID) == mIssuerMap.end()) { BOOST_FOREACH(OrderBook::ref book, mIssuerMap[issuerID]) { - if(book->getCurrencyIn()==currencyID) - { + if (book->getCurrencyIn() == currencyID) bookRet.push_back(book); - } } } -} \ No newline at end of file +} + +// vim:ts=4 diff --git a/src/cpp/ripple/OrderBookDB.h b/src/cpp/ripple/OrderBookDB.h index ded3562ae4..de6ab96682 100644 --- a/src/cpp/ripple/OrderBookDB.h +++ b/src/cpp/ripple/OrderBookDB.h @@ -1,10 +1,10 @@ #include "Ledger.h" #include "OrderBook.h" -/* -we can eventually make this cached and just update it as transactions come in. -But for now it is probably faster to just generate it each time -*/ +// +// XXX Eventually make this cached and just update it as transactions come in. +// But, for now it is probably faster to just generate it each time. +// class OrderBookDB { @@ -19,14 +19,15 @@ public: // return list of all orderbooks that want XRP std::vector& getXRPInBooks(){ return mXRPOrders; } + // return list of all orderbooks that want IssuerID std::vector& getBooks(const uint160& issuerID); + // return list of all orderbooks that want this issuerID and currencyID void getBooks(const uint160& issuerID, const uint160& currencyID, std::vector& bookRet); // returns the best rate we can find float getPrice(uint160& currencyIn,uint160& currencyOut); - }; // vim:ts=4 diff --git a/src/cpp/ripple/Pathfinder.cpp b/src/cpp/ripple/Pathfinder.cpp index a7f03cc1cf..659de5690a 100644 --- a/src/cpp/ripple/Pathfinder.cpp +++ b/src/cpp/ripple/Pathfinder.cpp @@ -113,13 +113,14 @@ bool Pathfinder::bDefaultPath(const STPath& spPath) bool bDefault; LedgerEntrySet lesActive(mLedger); + // Expand the current path. pspCurrent->setExpanded(lesActive, spPath, mDstAccountID, mSrcAccountID); + // Determine if expanded current path is the default. + // When path is a default (implied). Don't need to add it to return set. bDefault = pspCurrent->vpnNodes == mPsDefault->vpnNodes; cLog(lsDEBUG) << "findPaths: expanded path: " << pspCurrent->getJson(); - - // Path is a default (implied). Don't need to add it to return set. cLog(lsDEBUG) << "findPaths: default path: indirect: " << spPath.getJson(0); return bDefault; @@ -128,12 +129,13 @@ bool Pathfinder::bDefaultPath(const STPath& spPath) return false; } -// -// XXX Optionally, specifying a source and destination issuer might be nice. Especially, to convert between issuers. However, this -// functionality is left to the future. -// Pathfinder::Pathfinder(const RippleAddress& uSrcAccountID, const RippleAddress& uDstAccountID, const uint160& uSrcCurrencyID, const uint160& uSrcIssuerID, const STAmount& saDstAmount) - : mSrcAccountID(uSrcAccountID.getAccountID()), mDstAccountID(uDstAccountID.getAccountID()), mDstAmount(saDstAmount), mSrcCurrencyID(uSrcCurrencyID), mSrcIssuerID(uSrcIssuerID), mOrderBook(theApp->getLedgerMaster().getCurrentLedger()) + : mSrcAccountID(uSrcAccountID.getAccountID()), + mDstAccountID(uDstAccountID.getAccountID()), + mDstAmount(saDstAmount), + mSrcCurrencyID(uSrcCurrencyID), + mSrcIssuerID(uSrcIssuerID), + mOrderBook(theApp->getLedgerMaster().getCurrentLedger()) { mLedger = theApp->getLedgerMaster().getCurrentLedger(); mSrcAmount = STAmount(uSrcCurrencyID, uSrcIssuerID, 1, 0, true); // -1/uSrcIssuerID/uSrcIssuerID @@ -144,18 +146,24 @@ Pathfinder::Pathfinder(const RippleAddress& uSrcAccountID, const RippleAddress& if (psDefault) { + // Build the default path. + // Later, reject anything that expands to the default path as the default is sufficient. + LedgerEntrySet lesActive(mLedger); psDefault->setExpanded(lesActive, STPath(), mDstAccountID, mSrcAccountID); if (tesSUCCESS == psDefault->terStatus) { - cLog(lsDEBUG) << "Pathfinder: reference path: " << psDefault->getJson(); + // The default path works, remember it. + cLog(lsDEBUG) << "Pathfinder: default path: " << psDefault->getJson(); + mPsDefault = psDefault; } else { - cLog(lsDEBUG) << "Pathfinder: reference path: NONE: " << transToken(psDefault->terStatus); + // The default path doesn't work. + cLog(lsDEBUG) << "Pathfinder: default path: NONE: " << transToken(psDefault->terStatus); } } } @@ -190,35 +198,57 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax { LedgerEntrySet lesActive(mLedger); std::vector vspResults; - std::queue qspExplore; + std::queue qspExplore; // Path stubs to explore. + STPath spSeed; + bool bForcedIssuer = !!mSrcCurrencyID && mSrcIssuerID != mSrcAccountID; // Source forced an issuer. + + // The end is the cursor, start at the source account. STPathElement speEnd(mSrcAccountID, - mSrcCurrencyID, - uint160()); // XXX Might add source issuer. - STPath path; + mSrcCurrencyID, + !!mSrcCurrencyID + ? mSrcAccountID // Non-XRP, start with self as issuer. + : ACCOUNT_XRP); - path.addElement(speEnd); // Add the source. + // Build a path of one element: the source. + spSeed.addElement(speEnd); - qspExplore.push(path); + if (bForcedIssuer) + { + // Add forced source issuer to seed, via issuer's account. + STPathElement speIssuer(mSrcIssuerID, mSrcCurrencyID, mSrcIssuerID); - while (qspExplore.size()) { + spSeed.addElement(speEnd); + } + + // Push the seed path to explore. + qspExplore.push(spSeed); + + while (qspExplore.size()) { // Have paths to explore? STPath spPath = qspExplore.front(); qspExplore.pop(); // Pop the first path from the queue. speEnd = spPath.mPath.back(); // Get the last node from the path. - // Done, if dest wants XRP and last element produces XRP. if (!speEnd.mCurrencyID // Tail output is XRP. && !mDstAmount.getCurrency()) // Which is dst currency. { - // Remove implied first. + // Done, cursor produces XRP and dest wants XRP. + + // Remove implied source. spPath.mPath.erase(spPath.mPath.begin()); + if (bForcedIssuer) + { + // Remove implied source issuer. + spPath.mPath.erase(spPath.mPath.begin()); + } + if (spPath.size()) { // There is an actual path element. - cLog(lsDEBUG) << "findPaths: adding: " << spPath.getJson(0); + cLog(lsDEBUG) << "findPaths: adding path: " << spPath.getJson(0); vspResults.push_back(spPath); // Potential result. } @@ -230,71 +260,100 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax continue; } - // Done, if dest wants non-XRP and last element is dest. + cLog(lsDEBUG) << "findPaths: finish? account: " << (speEnd.mAccountID == mDstAccountID); + cLog(lsDEBUG) << "findPaths: finish? currency: " << (speEnd.mCurrencyID == mDstAmount.getCurrency()); + cLog(lsDEBUG) << "findPaths: finish? issuer: " + << RippleAddress::createHumanAccountID(speEnd.mIssuerID) + << " / " + << RippleAddress::createHumanAccountID(mDstAmount.getIssuer()) + << " / " + << RippleAddress::createHumanAccountID(mDstAccountID); + cLog(lsDEBUG) << "findPaths: finish? issuer is desired: " << (speEnd.mIssuerID == mDstAmount.getIssuer()); + // YYY Allows going through self. Is this wanted? if (speEnd.mAccountID == mDstAccountID // Tail is destination account. - && speEnd.mCurrencyID == mDstAmount.getCurrency()) // With correct output currency. + && speEnd.mCurrencyID == mDstAmount.getCurrency() // With correct output currency. + && ( speEnd.mIssuerID == mDstAccountID // Dest always accepts own issuer. + || mDstAmount.getIssuer() == mDstAccountID // Any issuer is good. + || mDstAmount.getIssuer() == speEnd.mIssuerID)) // The desired issuer. { - // Found a path to the destination. + // Done, found a path to the destination. + // Cursor on the dest account with correct currency and issuer. if (bDefaultPath(spPath)) { - cLog(lsDEBUG) << "findPaths: default path: dropping: " << spPath.getJson(0); + cLog(lsDEBUG) << "findPaths: dropping: default path: " << spPath.getJson(0); bFound = true; } else { - // Remove implied first and last nodes. + // Remove implied nodes. spPath.mPath.erase(spPath.mPath.begin()); + + if (bForcedIssuer) + { + // Remove implied source issuer. + spPath.mPath.erase(spPath.mPath.begin()); + } spPath.mPath.erase(spPath.mPath.begin() + spPath.mPath.size()-1); vspResults.push_back(spPath); // Potential result. - cLog(lsDEBUG) << "findPaths: adding: " << spPath.getJson(0); + cLog(lsDEBUG) << "findPaths: adding path: " << spPath.getJson(0); } continue; } - bool bContinued = false; + bool bContinued = false; // True, if wasn't a dead end. + + cLog(lsDEBUG) << + boost::str(boost::format("findPaths: cursor: %s - %s/%s") + % RippleAddress::createHumanAccountID(speEnd.mAccountID) + % STAmount::createHumanCurrency(speEnd.mCurrencyID) + % RippleAddress::createHumanAccountID(speEnd.mIssuerID)); if (spPath.mPath.size() == iMaxSteps) { // Path is at maximum size. Don't want to add more. cLog(lsDEBUG) - << boost::str(boost::format("findPaths: path would exceed max steps: dropping")); + << boost::str(boost::format("findPaths: dropping: path would exceed max steps")); + + continue; } else if (!speEnd.mCurrencyID) { - // Last element is for XRP, continue with qualifying books. + // Cursor is for XRP, continue with qualifying books: XRP -> non-XRP + BOOST_FOREACH(OrderBook::ref book, mOrderBook.getXRPInBooks()) { - // XXX Don't allow looping through same order books. + // New end is an order book with the currency and issuer. - //if (!path.hasSeen(line->getAccountIDPeer().getAccountID())) + // Don't allow looping through same order books. + if (!spPath.hasSeen(ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut())) { - STPath new_path(spPath); - STPathElement new_ele(uint160(), book->getCurrencyOut(), book->getIssuerOut()); + STPath spNew(spPath); + STPathElement speBook(ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut()); + STPathElement speAccount(book->getIssuerOut(), book->getCurrencyOut(), book->getIssuerOut()); - new_path.mPath.push_back(new_ele); - new_path.mCurrencyID = book->getCurrencyOut(); - new_path.mCurrentAccount = book->getCurrencyOut(); + spNew.mPath.push_back(speBook); // Add the order book. + spNew.mPath.push_back(speAccount); // Add the account and currency cLog(lsDEBUG) << - boost::str(boost::format("findPaths: XRP input - %s/%s") - % STAmount::createHumanCurrency(new_path.mCurrencyID) - % RippleAddress::createHumanAccountID(new_path.mCurrentAccount)); + boost::str(boost::format("findPaths: XRP -> %s/%s") + % STAmount::createHumanCurrency(speBook.mCurrencyID) + % RippleAddress::createHumanAccountID(speBook.mIssuerID)); - qspExplore.push(new_path); + qspExplore.push(spNew); bContinued = true; } } tLog(!bContinued, lsDEBUG) - << boost::str(boost::format("findPaths: XRP input - dead end")); + << boost::str(boost::format("findPaths: XRP -> dead end")); } else { @@ -303,20 +362,26 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax // Create new paths for each outbound account not already in the path. AccountItems rippleLines(speEnd.mAccountID, mLedger, AccountItem::pointer(new RippleState())); SLE::pointer sleSrc = lesActive.entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(speEnd.mAccountID)); + + tLog(sleSrc, lsDEBUG) + << boost::str(boost::format("findPaths: account without root: %s") + % RippleAddress::createHumanAccountID(speEnd.mAccountID)); + bool bRequireAuth = isSetBit(sleSrc->getFieldU32(sfFlags), lsfRequireAuth); BOOST_FOREACH(AccountItem::ref item, rippleLines.getItems()) { - RippleState* rspEntry = (RippleState*) item.get(); + RippleState* rspEntry = (RippleState*) item.get(); + const uint160 uPeerID = rspEntry->getAccountIDPeer().getAccountID(); - if (spPath.hasSeen(rspEntry->getAccountIDPeer().getAccountID())) + if (spPath.hasSeen(uPeerID, speEnd.mCurrencyID, uPeerID)) { // Peer is in path already. Ignore it to avoid a loop. cLog(lsDEBUG) << - boost::str(boost::format("findPaths: SEEN: %s/%s --> %s/%s") + boost::str(boost::format("findPaths: SEEN: %s/%s -> %s/%s") % RippleAddress::createHumanAccountID(speEnd.mAccountID) % STAmount::createHumanCurrency(speEnd.mCurrencyID) - % RippleAddress::createHumanAccountID(rspEntry->getAccountIDPeer().getAccountID()) + % RippleAddress::createHumanAccountID(uPeerID) % STAmount::createHumanCurrency(speEnd.mCurrencyID)); } else if (!rspEntry->getBalance().isPositive() // No IOUs to send. @@ -326,62 +391,61 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax { // Path has no credit left. Ignore it. cLog(lsDEBUG) << - boost::str(boost::format("findPaths: No credit: %s/%s --> %s/%s") + boost::str(boost::format("findPaths: No credit: %s/%s -> %s/%s") % RippleAddress::createHumanAccountID(speEnd.mAccountID) % STAmount::createHumanCurrency(speEnd.mCurrencyID) - % RippleAddress::createHumanAccountID(rspEntry->getAccountIDPeer().getAccountID()) + % RippleAddress::createHumanAccountID(uPeerID) % STAmount::createHumanCurrency(speEnd.mCurrencyID)); } else { - // Can transmit IOUs. - STPath new_path(spPath); - STPathElement new_ele(rspEntry->getAccountIDPeer().getAccountID(), - speEnd.mCurrencyID, - uint160()); + // Can transmit IOUs and account to the path. + STPath spNew(spPath); + STPathElement speNew(uPeerID, speEnd.mCurrencyID, uPeerID); - cLog(lsDEBUG) << - boost::str(boost::format("findPaths: %s/%s --> %s/%s") - % RippleAddress::createHumanAccountID(speEnd.mAccountID) - % STAmount::createHumanCurrency(speEnd.mCurrencyID) - % RippleAddress::createHumanAccountID(rspEntry->getAccountIDPeer().getAccountID()) - % STAmount::createHumanCurrency(speEnd.mCurrencyID)); - - new_path.mPath.push_back(new_ele); - qspExplore.push(new_path); + spNew.mPath.push_back(speNew); + qspExplore.push(spNew); bContinued = true; + + cLog(lsDEBUG) << + boost::str(boost::format("findPaths: push explore: %s/%s -> %s/%s") + % STAmount::createHumanCurrency(speEnd.mCurrencyID) + % RippleAddress::createHumanAccountID(speEnd.mAccountID) + % STAmount::createHumanCurrency(speEnd.mCurrencyID) + % RippleAddress::createHumanAccountID(uPeerID)); } } // Every book that wants the source currency. std::vector books; - mOrderBook.getBooks(spPath.mCurrentAccount, spPath.mCurrencyID, books); + mOrderBook.getBooks(speEnd.mIssuerID, speEnd.mCurrencyID, books); - BOOST_FOREACH(OrderBook::ref book,books) + BOOST_FOREACH(OrderBook::ref book, books) { - STPath new_path(spPath); - STPathElement new_ele(uint160(), book->getCurrencyOut(), book->getIssuerOut()); + if (!spPath.hasSeen(ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut())) + { + // A book we haven't seen before. Add it. + STPath spNew(spPath); + STPathElement speBook(ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut()); - cLog(lsDEBUG) << - boost::str(boost::format("findPaths: %s/%s :: %s/%s") - % STAmount::createHumanCurrency(speEnd.mCurrencyID) - % RippleAddress::createHumanAccountID(speEnd.mAccountID) - % STAmount::createHumanCurrency(book->getCurrencyOut()) - % RippleAddress::createHumanAccountID(book->getIssuerOut())); + spNew.mPath.push_back(speBook); + qspExplore.push(spNew); - new_path.mPath.push_back(new_ele); - new_path.mCurrentAccount=book->getIssuerOut(); - new_path.mCurrencyID=book->getCurrencyOut(); + bContinued = true; - qspExplore.push(new_path); - - bContinued = true; + cLog(lsDEBUG) << + boost::str(boost::format("findPaths: push book: %s/%s -> %s/%s") + % STAmount::createHumanCurrency(speEnd.mCurrencyID) + % RippleAddress::createHumanAccountID(speEnd.mIssuerID) + % STAmount::createHumanCurrency(book->getCurrencyOut()) + % RippleAddress::createHumanAccountID(book->getIssuerOut())); + } } tLog(!bContinued, lsDEBUG) - << boost::str(boost::format("findPaths: non-XRP input - dead end")); + << boost::str(boost::format("findPaths: dropping: non-XRP -> dead end")); } } @@ -403,20 +467,30 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax spsPaths.addPath(spCurrent); // Just checking the current path. - TER terResult = RippleCalc::rippleCalc( - lesActive, - saMaxAmountAct, - saDstAmountAct, - vpsExpanded, - mSrcAmount, // --> amount to send max. - mDstAmount, // --> amount to deliver. - mDstAccountID, - mSrcAccountID, - spsPaths, - true, // --> bPartialPayment: Allow, it might contribute. - false, // --> bLimitQuality: Assume normal transaction. - true, // --> bNoRippleDirect: Providing the only path. - true); // --> bStandAlone: Don't need to delete unfundeds. + TER terResult; + + try { + terResult = RippleCalc::rippleCalc( + lesActive, + saMaxAmountAct, + saDstAmountAct, + vpsExpanded, + mSrcAmount, // --> amount to send max. + mDstAmount, // --> amount to deliver. + mDstAccountID, + mSrcAccountID, + spsPaths, + true, // --> bPartialPayment: Allow, it might contribute. + false, // --> bLimitQuality: Assume normal transaction. + true, // --> bNoRippleDirect: Providing the only path. + true); // --> bStandAlone: Don't need to delete unfundeds. + } + catch (const std::exception& e) + { + cLog(lsINFO) << "findPaths: Caught throw: " << e.what(); + + terResult = tefEXCEPTION; + } if (tesSUCCESS == terResult) { diff --git a/src/cpp/ripple/PaymentTransactor.cpp b/src/cpp/ripple/PaymentTransactor.cpp index 6a6ffc8d70..0f72167c73 100644 --- a/src/cpp/ripple/PaymentTransactor.cpp +++ b/src/cpp/ripple/PaymentTransactor.cpp @@ -160,22 +160,31 @@ TER PaymentTransactor::doApply() STAmount saMaxAmountAct; STAmount saDstAmountAct; - terResult = isSetBit(mParams, tapOPEN_LEDGER) && spsPaths.size() > RIPPLE_PATHS_MAX - ? telBAD_PATH_COUNT // Too many paths for proposed ledger. - : RippleCalc::rippleCalc( - mEngine->getNodes(), - saMaxAmountAct, - saDstAmountAct, - vpsExpanded, - saMaxAmount, - saDstAmount, - uDstAccountID, - mTxnAccountID, - spsPaths, - bPartialPayment, - bLimitQuality, - bNoRippleDirect, // Always compute for finalizing ledger. - false); // Not standalone, delete unfundeds. + try + { + terResult = isSetBit(mParams, tapOPEN_LEDGER) && spsPaths.size() > RIPPLE_PATHS_MAX + ? telBAD_PATH_COUNT // Too many paths for proposed ledger. + : RippleCalc::rippleCalc( + mEngine->getNodes(), + saMaxAmountAct, + saDstAmountAct, + vpsExpanded, + saMaxAmount, + saDstAmount, + uDstAccountID, + mTxnAccountID, + spsPaths, + bPartialPayment, + bLimitQuality, + bNoRippleDirect, // Always compute for finalizing ledger. + false); // Not standalone, delete unfundeds. + } + catch (const std::exception& e) + { + cLog(lsINFO) << "Payment: Caught throw: " << e.what(); + + terResult = tefEXCEPTION; + } } else { diff --git a/src/cpp/ripple/RippleCalc.cpp b/src/cpp/ripple/RippleCalc.cpp index 2d85bea144..bb2fc3b324 100644 --- a/src/cpp/ripple/RippleCalc.cpp +++ b/src/cpp/ripple/RippleCalc.cpp @@ -967,7 +967,6 @@ TER RippleCalc::calcNodeDeliverRev( STAmount& saPrvDlvReq = pnPrv.saRevDeliver; // To be set. STAmount& saCurDlvFwd = pnCur.saFwdDeliver; - uint256& uDirectTip = pnCur.uDirectTip; uDirectTip = 0; // Restart book searching. @@ -1065,6 +1064,7 @@ TER RippleCalc::calcNodeDeliverRev( // Compute portion of input needed to cover actual output. + // XXX This needs to round up! STAmount saInPassReq = STAmount::multiply(saOutPass, saOfrRate, saTakerPays); STAmount saInPassAct; @@ -2281,11 +2281,10 @@ TER RippleCalc::calcNodeRev(const unsigned int uNode, PathState& psCur, const bo // Calculate the next increment of a path. // The increment is what can satisfy a portion or all of the requested output at the best quality. // <-- psCur.uQuality -void RippleCalc::pathNext(PathState::ref psrCur, const int iPaths, const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent) +void RippleCalc::pathNext(PathState::ref psrCur, const bool bMultiQuality, const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent) { // The next state is what is available in preference order. // This is calculated when referenced accounts changed. - const bool bMultiQuality = iPaths == 1; const unsigned int uLast = psrCur->vpnNodes.size() - 1; psrCur->bConsumed = false; @@ -2320,11 +2319,12 @@ void RippleCalc::pathNext(PathState::ref psrCur, const int iPaths, const LedgerE if (tesSUCCESS == psrCur->terStatus) { tLog(!psrCur->saInPass || !psrCur->saOutPass, lsDEBUG) - << boost::str(boost::format("pathNext: saOutPass=%s saInPass=%s") + << boost::str(boost::format("pathNext: Error calcNodeFwd reported success for nothing: saOutPass=%s saInPass=%s") % psrCur->saOutPass.getFullText() % psrCur->saInPass.getFullText()); - assert(!!psrCur->saOutPass && !!psrCur->saInPass); + if (!psrCur->saOutPass || !psrCur->saInPass) + throw std::runtime_error("Made no progress."); psrCur->uQuality = STAmount::getRate(psrCur->saOutPass, psrCur->saInPass); // Calculate relative quality. @@ -2467,16 +2467,18 @@ int iPass = 0; int iBest = -1; const LedgerEntrySet lesCheckpoint = lesActive; int iDry = 0; + bool bMultiQuality = false; // True, if ever computed multi-quality. // Find the best path. BOOST_FOREACH(PathState::ref pspCur, vpsExpanded) { if (pspCur->uQuality) { - pspCur->saInAct = saMaxAmountAct; // Update to current amount processed. + bMultiQuality = 1 == vpsExpanded.size()-iDry, // Computing the only non-dry path, compute multi-quality. + pspCur->saInAct = saMaxAmountAct; // Update to current amount processed. pspCur->saOutAct = saDstAmountAct; - rc.pathNext(pspCur, vpsExpanded.size(), lesCheckpoint, lesActive); // Compute increment. + rc.pathNext(pspCur, bMultiQuality, lesCheckpoint, lesActive); // Compute increment. cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: AFTER: mIndex=%d uQuality=%d rate=%s") % pspCur->mIndex % pspCur->uQuality @@ -2543,7 +2545,7 @@ cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Summary: %d rate: %s qual saMaxAmountAct += pspBest->saInPass; saDstAmountAct += pspBest->saOutPass; - if (pspBest->bConsumed) + if (pspBest->bConsumed || bMultiQuality) { ++iDry; pspBest->uQuality = 0; diff --git a/src/cpp/ripple/RippleCalc.h b/src/cpp/ripple/RippleCalc.h index 2935061be4..c08af730e5 100644 --- a/src/cpp/ripple/RippleCalc.h +++ b/src/cpp/ripple/RippleCalc.h @@ -161,7 +161,7 @@ public: // If the transaction fails to meet some constraint, still need to delete unfunded offers. boost::unordered_set musUnfundedFound; // Offers that were found unfunded. - void pathNext(PathState::ref psrCur, const int iPaths, const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent); + void pathNext(PathState::ref psrCur, const bool bMultiQuality, const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent); TER calcNode(const unsigned int uNode, PathState& psCur, const bool bMultiQuality); TER calcNodeRev(const unsigned int uNode, PathState& psCur, const bool bMultiQuality); TER calcNodeFwd(const unsigned int uNode, PathState& psCur, const bool bMultiQuality); diff --git a/src/cpp/ripple/SerializedTypes.cpp b/src/cpp/ripple/SerializedTypes.cpp index 11ed9794b2..46e599b4ff 100644 --- a/src/cpp/ripple/SerializedTypes.cpp +++ b/src/cpp/ripple/SerializedTypes.cpp @@ -415,16 +415,20 @@ bool STPathSet::isEquivalent(const SerializedType& t) const return v && (value == v->value); } -bool STPath::hasSeen(const uint160 &acct) { - - for (int i = 0; i < mPath.size();i++) { +bool STPath::hasSeen(const uint160 &uAccountId, const uint160& uCurrencyID, const uint160& uIssuerID) +{ + for (int i = 0; i < mPath.size(); ++i) { STPathElement ele = getElement(i); - if (ele.getAccountID() == acct) + + if (ele.getAccountID() == uAccountId + && ele.getCurrency() == uCurrencyID + && ele.getIssuerID() == uIssuerID) return true; } return false; } + int STPath::getSerializeSize() const { int iBytes = 0; diff --git a/src/cpp/ripple/SerializedTypes.h b/src/cpp/ripple/SerializedTypes.h index fd0c2fc6fd..0a05fa225f 100644 --- a/src/cpp/ripple/SerializedTypes.h +++ b/src/cpp/ripple/SerializedTypes.h @@ -623,13 +623,13 @@ public: const STPathElement& getElement(int offset) { return mPath[offset]; } void addElement(const STPathElement &e) { mPath.push_back(e); } void clear() { mPath.clear(); } - bool hasSeen(const uint160 &acct); + bool hasSeen(const uint160 &uAccountId, const uint160& uCurrencyID, const uint160& uIssuerID); int getSerializeSize() const; // std::string getText() const; Json::Value getJson(int) const; - uint160 mCurrencyID; - uint160 mCurrentAccount; // what account is at the end of the path +// uint160 mCurrencyID; +// uint160 mCurrentAccount; // what account is at the end of the path std::vector::iterator begin() { return mPath.begin(); } std::vector::iterator end() { return mPath.end(); } diff --git a/test/path-test.js b/test/path-test.js index 481c8caea5..4c021ce568 100644 --- a/test/path-test.js +++ b/test/path-test.js @@ -12,9 +12,9 @@ require('../src/js/config').load(require('./config')); buster.testRunner.timeout = 5000; buster.testCase("Basic Path finding", { - // 'setUp' : testutils.build_setup({ verbose: true, no_server: true }), - // 'setUp' : testutils.build_setup({ verbose: true }), 'setUp' : testutils.build_setup(), + // 'setUp' : testutils.build_setup({ verbose: true }), + // 'setUp' : testutils.build_setup({ verbose: true, no_server: true }), 'tearDown' : testutils.build_teardown(), "no direct path, no intermediary -> no alternatives" : @@ -899,4 +899,243 @@ buster.testCase("Issues", { }); } }); + +buster.testCase("Via offers", { + 'setUp' : testutils.build_setup(), + // 'setUp' : testutils.build_setup({ verbose: true }), + // 'setUp' : testutils.build_setup({ verbose: true, no_server: true }), + 'tearDown' : testutils.build_teardown(), + + // XXX Triggers bad path expansion. + "Via gateway" : + // carol holds mtgoxAUD, sells mtgoxAUD for XRP + // bob will hold mtgoxAUD + // alice pays bob mtgoxAUD using XRP + function (done) { + var self = this; + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "carol", "mtgox"], callback); + }, + function (callback) { + self.what = "Set transfer rate."; + + self.remote.transaction() + .account_set("mtgox") + .transfer_rate(1005000000) + .once('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Set credit limits."; + + testutils.credit_limits(self.remote, + { + "bob" : [ "100/AUD/mtgox" ], + "carol" : [ "100/AUD/mtgox" ], + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "mtgox" : "50/AUD/carol", + }, + callback); + }, + function (callback) { + self.what = "Carol create offer."; + + self.remote.transaction() + .offer_create("carol", "50.0", "50/AUD/mtgox") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + + seq_carol = m.tx_json.Sequence; + }) + .submit(); + }, + function (callback) { + self.what = "Alice sends bob 10/AUD/mtgox using XRP."; + + // XXX Also try sending 10/AUX/bob + self.remote.transaction() + .payment("alice", "bob", "10/AUD/mtgox") + .build_path(true) + .send_max("100.0") + .on('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "bob" : "10/AUD/mtgox", + "carol" : "39.95/AUD/mtgox", + }, + callback); + }, +// function (callback) { +// self.what = "Display ledger"; +// +// self.remote.request_ledger('current', true) +// .on('success', function (m) { +// console.log("Ledger: %s", JSON.stringify(m, undefined, 2)); +// +// callback(); +// }) +// .request(); +// }, +// function (callback) { +// self.what = "Find path from alice to bob"; +// +// // 5. acct 1 sent a 25 usd iou to acct 2 +// self.remote.request_ripple_path_find("alice", "bob", "25/USD/bob", +// [ { 'currency' : "USD" } ]) +// .on('success', function (m) { +// // console.log("proposed: %s", JSON.stringify(m)); +// +// // 0 alternatives. +// buster.assert.equals(0, m.alternatives.length) +// +// callback(); +// }) +// .request(); +// }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, + + "// Via gateway : FIX ME fails due to XRP rounding and not properly handling dry." : + // carol holds mtgoxAUD, sells mtgoxAUD for XRP + // bob will hold mtgoxAUD + // alice pays bob mtgoxAUD using XRP + function (done) { + var self = this; + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "carol", "mtgox"], callback); + }, + function (callback) { + self.what = "Set transfer rate."; + + self.remote.transaction() + .account_set("mtgox") + .transfer_rate(1005000000) + .once('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Set credit limits."; + + testutils.credit_limits(self.remote, + { + "bob" : [ "100/AUD/mtgox" ], + "carol" : [ "100/AUD/mtgox" ], + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "mtgox" : "50/AUD/carol", + }, + callback); + }, + function (callback) { + self.what = "Carol create offer."; + + self.remote.transaction() + .offer_create("carol", "50", "50/AUD/mtgox") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + + seq_carol = m.tx_json.Sequence; + }) + .submit(); + }, + function (callback) { + self.what = "Alice sends bob 10/AUD/mtgox using XRP."; + + // XXX Also try sending 10/AUX/bob + self.remote.transaction() + .payment("alice", "bob", "10/AUD/mtgox") + .build_path(true) + .send_max("100") + .on('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "bob" : "10/AUD/mtgox", + "carol" : "39.95/AUD/mtgox", + }, + callback); + }, +// function (callback) { +// self.what = "Display ledger"; +// +// self.remote.request_ledger('current', true) +// .on('success', function (m) { +// console.log("Ledger: %s", JSON.stringify(m, undefined, 2)); +// +// callback(); +// }) +// .request(); +// }, +// function (callback) { +// self.what = "Find path from alice to bob"; +// +// // 5. acct 1 sent a 25 usd iou to acct 2 +// self.remote.request_ripple_path_find("alice", "bob", "25/USD/bob", +// [ { 'currency' : "USD" } ]) +// .on('success', function (m) { +// // console.log("proposed: %s", JSON.stringify(m)); +// +// // 0 alternatives. +// buster.assert.equals(0, m.alternatives.length) +// +// callback(); +// }) +// .request(); +// }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, +}); + // vim:sw=2:sts=2:ts=8:et