From 3f4a06df9fbe5e672ea9a3204fdb099732293971 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Sat, 2 Feb 2013 19:07:28 -0800 Subject: [PATCH 1/9] Fix pays vs get for order book base generation. --- src/cpp/ripple/OrderBook.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/cpp/ripple/OrderBook.cpp b/src/cpp/ripple/OrderBook.cpp index 2cf716257..d140c8ce7 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 From c9b954dd7ce139649335d2422e3a543774b35448 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Sat, 2 Feb 2013 19:08:26 -0800 Subject: [PATCH 2/9] Fix order book scaning for path finding. --- src/cpp/ripple/OrderBookDB.cpp | 55 ++++++++++++++++++++++------------ src/cpp/ripple/OrderBookDB.h | 11 +++---- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/src/cpp/ripple/OrderBookDB.cpp b/src/cpp/ripple/OrderBookDB.cpp index 192cab703..70d02744d 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 ded3562ae..de6ab9668 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 From 1b27d2bc10389d6f0934301b2e7eaa3b39971d57 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Sat, 2 Feb 2013 19:10:01 -0800 Subject: [PATCH 3/9] Try to improve consumed and multi-quality handling for ripple calc. --- src/cpp/ripple/RippleCalc.cpp | 18 ++++++++++-------- src/cpp/ripple/RippleCalc.h | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/cpp/ripple/RippleCalc.cpp b/src/cpp/ripple/RippleCalc.cpp index 2d85bea14..bb2fc3b32 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 2935061be..c08af730e 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); From 2c957df3cca6bad6b5a724098eeae40c4a6cfc2a Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Sat, 2 Feb 2013 19:10:36 -0800 Subject: [PATCH 4/9] Catch throws in PaymentTransactor. --- src/cpp/ripple/PaymentTransactor.cpp | 41 +++++++++++++++++----------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/cpp/ripple/PaymentTransactor.cpp b/src/cpp/ripple/PaymentTransactor.cpp index 6a6ffc8d7..0f72167c7 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 { From dc89b3eaf94f529329122eff8358e37fd31251d0 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Sat, 2 Feb 2013 19:11:20 -0800 Subject: [PATCH 5/9] Fixes for pathfinding. --- src/cpp/ripple/Pathfinder.cpp | 129 +++++++++++++++++++++------------- 1 file changed, 82 insertions(+), 47 deletions(-) diff --git a/src/cpp/ripple/Pathfinder.cpp b/src/cpp/ripple/Pathfinder.cpp index a7f03cc1c..3526a8bf8 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; @@ -144,17 +145,23 @@ 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) { + // The default path works, remember it. cLog(lsDEBUG) << "Pathfinder: reference path: " << psDefault->getJson(); + mPsDefault = psDefault; } else { + // The default path doesn't work. cLog(lsDEBUG) << "Pathfinder: reference path: NONE: " << transToken(psDefault->terStatus); } } @@ -190,35 +197,37 @@ 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. - STPathElement speEnd(mSrcAccountID, - mSrcCurrencyID, - uint160()); // XXX Might add source issuer. + // The end is our cursor, start at the source. + STPathElement speEnd(mSrcAccountID, mSrcCurrencyID, uint160()); // XXX Might add source issuer. + // Build a path of one element: the source. STPath path; - path.addElement(speEnd); // Add the source. + path.addElement(speEnd); + // Push the source as a path of one element to explore. qspExplore.push(path); - while (qspExplore.size()) { + 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. { + // Done, cursor produces XRP and dest wants XRP. + // Remove implied first. 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,15 +239,28 @@ 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 accpets own. + || mDstAmount.getIssuer() == mDstAccountID // Any issuer is good. + || speEnd.mIssuerID == mDstAmount.getIssuer())) // The desired issuer. { // Found a path to the destination. + // Done, 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; } @@ -251,24 +273,30 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax 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")); } else if (!speEnd.mCurrencyID) { - // Last element is for XRP, continue with qualifying books. + // Cursor is for XRP, continue with qualifying books. BOOST_FOREACH(OrderBook::ref book, mOrderBook.getXRPInBooks()) { // XXX Don't allow looping through same order books. @@ -276,16 +304,15 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax //if (!path.hasSeen(line->getAccountIDPeer().getAccountID())) { STPath new_path(spPath); + // New end is an order book with the currency and issuer. STPathElement new_ele(uint160(), book->getCurrencyOut(), book->getIssuerOut()); new_path.mPath.push_back(new_ele); - new_path.mCurrencyID = book->getCurrencyOut(); - new_path.mCurrentAccount = book->getCurrencyOut(); 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 input -> %s/%s") + % STAmount::createHumanCurrency(new_ele.mCurrencyID) + % RippleAddress::createHumanAccountID(new_ele.mIssuerID)); qspExplore.push(new_path); @@ -294,15 +321,15 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax } tLog(!bContinued, lsDEBUG) - << boost::str(boost::format("findPaths: XRP input - dead end")); + << boost::str(boost::format("findPaths: XRP input -> dead end")); } else { // Last element is for non-XRP, continue by adding ripple lines and order books. // 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)); + AccountItems rippleLines(speEnd.mIssuerID, mLedger, AccountItem::pointer(new RippleState())); + SLE::pointer sleSrc = lesActive.entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(speEnd.mIssuerID)); bool bRequireAuth = isSetBit(sleSrc->getFieldU32(sfFlags), lsfRequireAuth); BOOST_FOREACH(AccountItem::ref item, rippleLines.getItems()) @@ -338,14 +365,14 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax STPath new_path(spPath); STPathElement new_ele(rspEntry->getAccountIDPeer().getAccountID(), speEnd.mCurrencyID, - uint160()); + rspEntry->getAccountIDPeer().getAccountID()); cLog(lsDEBUG) << - boost::str(boost::format("findPaths: %s/%s --> %s/%s") + 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(rspEntry->getAccountIDPeer().getAccountID()) - % STAmount::createHumanCurrency(speEnd.mCurrencyID)); + % RippleAddress::createHumanAccountID(rspEntry->getAccountIDPeer().getAccountID())); new_path.mPath.push_back(new_ele); qspExplore.push(new_path); @@ -357,7 +384,7 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax // 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) { @@ -367,13 +394,11 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax cLog(lsDEBUG) << boost::str(boost::format("findPaths: %s/%s :: %s/%s") % STAmount::createHumanCurrency(speEnd.mCurrencyID) - % RippleAddress::createHumanAccountID(speEnd.mAccountID) + % RippleAddress::createHumanAccountID(speEnd.mIssuerID) % STAmount::createHumanCurrency(book->getCurrencyOut()) % RippleAddress::createHumanAccountID(book->getIssuerOut())); new_path.mPath.push_back(new_ele); - new_path.mCurrentAccount=book->getIssuerOut(); - new_path.mCurrencyID=book->getCurrencyOut(); qspExplore.push(new_path); @@ -403,20 +428,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) { From f1ffac8757e18533d81d1afd0925a4c89e9c4d5a Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Sat, 2 Feb 2013 19:11:35 -0800 Subject: [PATCH 6/9] Comment out unused variables. --- src/cpp/ripple/SerializedTypes.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cpp/ripple/SerializedTypes.h b/src/cpp/ripple/SerializedTypes.h index fd0c2fc6f..2ca0d7421 100644 --- a/src/cpp/ripple/SerializedTypes.h +++ b/src/cpp/ripple/SerializedTypes.h @@ -628,8 +628,8 @@ public: // 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(); } From db83d90f059e116658ddfb1ac37894336adf02f5 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Sat, 2 Feb 2013 19:16:16 -0800 Subject: [PATCH 7/9] UT: Add tests for rippling from XRP. --- test/path-test.js | 239 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) diff --git a/test/path-test.js b/test/path-test.js index 481c8caea..ff6ac8a18 100644 --- a/test/path-test.js +++ b/test/path-test.js @@ -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 From acff2f45f10a3585fc272800fe6b4b2d48bcc5da Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Mon, 4 Feb 2013 15:39:53 -0800 Subject: [PATCH 8/9] Make hasSeen consider all elements of a path element. --- src/cpp/ripple/SerializedTypes.cpp | 12 ++++++++---- src/cpp/ripple/SerializedTypes.h | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/cpp/ripple/SerializedTypes.cpp b/src/cpp/ripple/SerializedTypes.cpp index 11ed9794b..46e599b4f 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 2ca0d7421..0a05fa225 100644 --- a/src/cpp/ripple/SerializedTypes.h +++ b/src/cpp/ripple/SerializedTypes.h @@ -623,7 +623,7 @@ 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; From 83e8e7c62439e39d7dee29955a79b7854eca9e1f Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Mon, 4 Feb 2013 15:40:37 -0800 Subject: [PATCH 9/9] Many fixes for path finding. --- src/cpp/ripple/Pathfinder.cpp | 169 +++++++++++++++++++++------------- test/path-test.js | 4 +- 2 files changed, 106 insertions(+), 67 deletions(-) diff --git a/src/cpp/ripple/Pathfinder.cpp b/src/cpp/ripple/Pathfinder.cpp index 3526a8bf8..659de5690 100644 --- a/src/cpp/ripple/Pathfinder.cpp +++ b/src/cpp/ripple/Pathfinder.cpp @@ -129,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 @@ -155,14 +156,14 @@ Pathfinder::Pathfinder(const RippleAddress& uSrcAccountID, const RippleAddress& if (tesSUCCESS == psDefault->terStatus) { // The default path works, remember it. - cLog(lsDEBUG) << "Pathfinder: reference path: " << psDefault->getJson(); + cLog(lsDEBUG) << "Pathfinder: default path: " << psDefault->getJson(); mPsDefault = psDefault; } else { // The default path doesn't work. - cLog(lsDEBUG) << "Pathfinder: reference path: NONE: " << transToken(psDefault->terStatus); + cLog(lsDEBUG) << "Pathfinder: default path: NONE: " << transToken(psDefault->terStatus); } } } @@ -199,15 +200,29 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax std::vector vspResults; std::queue qspExplore; // Path stubs to explore. - // The end is our cursor, start at the source. - STPathElement speEnd(mSrcAccountID, mSrcCurrencyID, uint160()); // XXX Might add source issuer. + 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, + !!mSrcCurrencyID + ? mSrcAccountID // Non-XRP, start with self as issuer. + : ACCOUNT_XRP); + // Build a path of one element: the source. - STPath path; + spSeed.addElement(speEnd); - path.addElement(speEnd); + if (bForcedIssuer) + { + // Add forced source issuer to seed, via issuer's account. + STPathElement speIssuer(mSrcIssuerID, mSrcCurrencyID, mSrcIssuerID); - // Push the source as a path of one element to explore. - qspExplore.push(path); + spSeed.addElement(speEnd); + } + + // Push the seed path to explore. + qspExplore.push(spSeed); while (qspExplore.size()) { // Have paths to explore? STPath spPath = qspExplore.front(); @@ -221,9 +236,15 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax { // Done, cursor produces XRP and dest wants XRP. - // Remove implied first. + // 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. @@ -252,12 +273,12 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax // 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.mIssuerID == mDstAccountID // Dest always accpets own. + && ( speEnd.mIssuerID == mDstAccountID // Dest always accepts own issuer. || mDstAmount.getIssuer() == mDstAccountID // Any issuer is good. - || speEnd.mIssuerID == mDstAmount.getIssuer())) // The desired issuer. + || mDstAmount.getIssuer() == speEnd.mIssuerID)) // The desired issuer. { - // Found a path to the destination. - // Done, cursor on the dest account with correct currency and issuer. + // Done, found a path to the destination. + // Cursor on the dest account with correct currency and issuer. if (bDefaultPath(spPath)) { cLog(lsDEBUG) << "findPaths: dropping: default path: " << spPath.getJson(0); @@ -266,9 +287,15 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax } 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. @@ -293,57 +320,68 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax cLog(lsDEBUG) << boost::str(boost::format("findPaths: dropping: path would exceed max steps")); + + continue; } else if (!speEnd.mCurrencyID) { - // Cursor 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); - // New end is an order book with the currency and issuer. - 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); + 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_ele.mCurrencyID) - % RippleAddress::createHumanAccountID(new_ele.mIssuerID)); + 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 { // Last element is for non-XRP, continue by adding ripple lines and order books. // Create new paths for each outbound account not already in the path. - AccountItems rippleLines(speEnd.mIssuerID, mLedger, AccountItem::pointer(new RippleState())); - SLE::pointer sleSrc = lesActive.entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(speEnd.mIssuerID)); + 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. @@ -353,31 +391,29 @@ 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, - rspEntry->getAccountIDPeer().getAccountID()); + // Can transmit IOUs and account to the path. + STPath spNew(spPath); + STPathElement speNew(uPeerID, speEnd.mCurrencyID, uPeerID); + + spNew.mPath.push_back(speNew); + qspExplore.push(spNew); + + bContinued = true; cLog(lsDEBUG) << - boost::str(boost::format("findPaths: push explore: %s/%s --> %s/%s") + 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(rspEntry->getAccountIDPeer().getAccountID())); - - new_path.mPath.push_back(new_ele); - qspExplore.push(new_path); - - bContinued = true; + % RippleAddress::createHumanAccountID(uPeerID)); } } @@ -386,27 +422,30 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax 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.mIssuerID) - % STAmount::createHumanCurrency(book->getCurrencyOut()) - % RippleAddress::createHumanAccountID(book->getIssuerOut())); + spNew.mPath.push_back(speBook); + qspExplore.push(spNew); - new_path.mPath.push_back(new_ele); + 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")); } } diff --git a/test/path-test.js b/test/path-test.js index ff6ac8a18..4c021ce56 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" :