diff --git a/src/cpp/ripple/Pathfinder.cpp b/src/cpp/ripple/Pathfinder.cpp index 4607079f3a..d79aac74c2 100644 --- a/src/cpp/ripple/Pathfinder.cpp +++ b/src/cpp/ripple/Pathfinder.cpp @@ -84,7 +84,7 @@ Pathfinder::Pathfinder(RippleAddress& srcAccountID, RippleAddress& dstAccountID, // Returns a single path, if possible. // --> maxSearchSteps: unused // --> maxPay: unused -bool Pathfinder::findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet) +bool Pathfinder::findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet, bool bAllowEmpty) { if (mLedger) { std::queue pqueue; @@ -107,16 +107,28 @@ bool Pathfinder::findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet // Determine if path is solved. // Done, if dest wants XRP and last element produces XRP. - // Done, if dest wants non-XRP and last element is dest. + if (!ele.mCurrencyID // Tail output is XRP + && !mDstAmount.getCurrency()) { - if (!ele.mCurrencyID) { + // Remove implied first. + path.mPath.erase(path.mPath.begin()); + + // Return the path. + retPathSet.addPath(path); + + cLog(lsDEBUG) << "findPaths: adding: " << path.getJson(0); + + return true; } - if (ele.mAccountID == mDstAccountID) { + + // Done, if dest wants non-XRP and last element is dest. + // YYY Allows going through self. Is this wanted? + if (ele.mAccountID == mDstAccountID // Tail is destination + && ele.mCurrencyID == mDstAmount.getCurrency()) { // With correct output currency. // Found a path to the destination. - if (2 == path.mPath.size()) { + if (!bAllowEmpty && 2 == path.mPath.size()) { // Empty path is default. Drop it. - // XXX Don't drop empty path - we still want an estimate. cLog(lsDEBUG) << "findPaths: dropping empty path."; continue; } @@ -128,27 +140,42 @@ bool Pathfinder::findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet // Return the path. retPathSet.addPath(path); + cLog(lsDEBUG) << "findPaths: adding: " << path.getJson(0); + return true; } + bool bContinued = false; + if (!ele.mCurrencyID) { // Last element is for XRP continue with qualifying books. - BOOST_FOREACH(OrderBook::pointer book, mOrderBook.getXRPInBooks()) { + // XXX Don't allow looping through same order books. + //if (!path.hasSeen(line->getAccountIDPeer().getAccountID())) { STPath new_path(path); 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(); + 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)); pqueue.push(new_path); + + bContinued = true; } } + tLog(!bContinued, lsDEBUG) + << boost::str(boost::format("findPaths: XRP input - dead end")); + } else { // Last element is for non-XRP continue by adding ripple lines and order books. @@ -164,8 +191,17 @@ bool Pathfinder::findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet ele.mCurrencyID, uint160()); + cLog(lsDEBUG) << + boost::str(boost::format("findPaths: %s/%s --> %s/%s") + % RippleAddress::createHumanAccountID(ele.mAccountID) + % STAmount::createHumanCurrency(ele.mCurrencyID) + % RippleAddress::createHumanAccountID(line->getAccountIDPeer().getAccountID()) + % STAmount::createHumanCurrency(ele.mCurrencyID)); + new_path.mPath.push_back(new_ele); pqueue.push(new_path); + + bContinued = true; } } @@ -179,17 +215,31 @@ bool Pathfinder::findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet STPath new_path(path); STPathElement new_ele(uint160(), book->getCurrencyOut(), book->getIssuerOut()); + cLog(lsDEBUG) << + boost::str(boost::format("findPaths: %s/%s :: %s/%s") + % STAmount::createHumanCurrency(ele.mCurrencyID) + % RippleAddress::createHumanAccountID(ele.mAccountID) + % 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(); pqueue.push(new_path); + + bContinued = true; } } - // enumerate all adjacent nodes, construct a new path and push it into the queue - } // While - } // if there is a ledger + tLog(!bContinued, lsDEBUG) + << boost::str(boost::format("findPaths: non-XRP input - dead end")); + } + } + else + { + cLog(lsWARNING) << boost::str(boost::format("findPaths: no ledger")); + } return false; } diff --git a/src/cpp/ripple/Pathfinder.h b/src/cpp/ripple/Pathfinder.h index 97f8789b09..8e91842d2a 100644 --- a/src/cpp/ripple/Pathfinder.h +++ b/src/cpp/ripple/Pathfinder.h @@ -38,20 +38,20 @@ class Pathfinder OrderBookDB mOrderBook; Ledger::pointer mLedger; - std::list mBuildingPaths; - std::list mCompletePaths; +// std::list mBuildingPaths; +// std::list mCompletePaths; - void addOptions(PathOption::pointer tail); +// void addOptions(PathOption::pointer tail); // returns true if any building paths are now complete? bool checkComplete(STPathSet& retPathSet); - void addPathOption(PathOption::pointer pathOption); +// void addPathOption(PathOption::pointer pathOption); public: Pathfinder(RippleAddress& srcAccountID, RippleAddress& dstAccountID, uint160& srcCurrencyID, STAmount dstAmount); // returns false if there is no path. otherwise fills out retPath - bool findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet); + bool findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet, bool bAllowEmpty); }; // vim:ts=4 diff --git a/src/cpp/ripple/PaymentTransactor.cpp b/src/cpp/ripple/PaymentTransactor.cpp index e2e6bceed4..5dfb73155f 100644 --- a/src/cpp/ripple/PaymentTransactor.cpp +++ b/src/cpp/ripple/PaymentTransactor.cpp @@ -119,19 +119,20 @@ TER PaymentTransactor::doApply() STAmount saDstAmountAct; terResult = isSetBit(mParams, tapOPEN_LEDGER) && spsPaths.getPathCount() > RIPPLE_PATHS_MAX - ? telBAD_PATH_COUNT + ? telBAD_PATH_COUNT // Too many paths for proposed ledger. : RippleCalc::rippleCalc( - mEngine->getNodes(), - saMaxAmountAct, - saDstAmountAct, - saMaxAmount, - saDstAmount, - uDstAccountID, - mTxnAccountID, - spsPaths, - bPartialPayment, - bLimitQuality, - bNoRippleDirect); + mEngine->getNodes(), + saMaxAmountAct, + saDstAmountAct, + saMaxAmount, + saDstAmount, + uDstAccountID, + mTxnAccountID, + spsPaths, + bPartialPayment, + bLimitQuality, + bNoRippleDirect, // Always compute for finalizing ledger. + false); // Not standalone, delete unfundeds. } else { @@ -174,4 +175,6 @@ TER PaymentTransactor::doApply() } return terResult; -} \ No newline at end of file +} + +// vim:ts=4 diff --git a/src/cpp/ripple/RPCHandler.cpp b/src/cpp/ripple/RPCHandler.cpp index 26682006ec..4bc417f196 100644 --- a/src/cpp/ripple/RPCHandler.cpp +++ b/src/cpp/ripple/RPCHandler.cpp @@ -9,6 +9,7 @@ #include "RippleLines.h" #include "Wallet.h" #include "RippleAddress.h" +#include "RippleCalc.h" #include "AccountState.h" #include "NicknameState.h" #include "InstanceCounter.h" @@ -699,7 +700,7 @@ Json::Value RPCHandler::doRipplePathFind(const Json::Value& jvRequest) Json::Value jvResult(Json::objectValue); RippleAddress raSrc; RippleAddress raDst; - STAmount saDst; + STAmount saDstAmount; if ( // Parse raSrc. @@ -720,9 +721,9 @@ Json::Value RPCHandler::doRipplePathFind(const Json::Value& jvRequest) jvResult = rpcError(rpcINVALID_PARAMS); } else if ( - // Parse saDst. + // Parse saDstAmount. !jvRequest.isMember("destination_amount") - || !saDst.bSetJson(jvRequest["destination_amount"])) + || !saDstAmount.bSetJson(jvRequest["destination_amount"])) { cLog(lsINFO) << "Bad destination_amount."; jvResult = rpcError(rpcINVALID_PARAMS); @@ -739,9 +740,12 @@ Json::Value RPCHandler::doRipplePathFind(const Json::Value& jvRequest) } else { - Json::Value jvSrcCurrencies = jvRequest.isMember("source_currencies"); + Json::Value jvSrcCurrencies = jvRequest["source_currencies"]; Json::Value jvArray(Json::arrayValue); + Ledger::pointer lpCurrent = mNetOps->getCurrentLedger(); + LedgerEntrySet lesSnapshot(lpCurrent); + for (unsigned int i=0; i != jvSrcCurrencies.size(); ++i) { Json::Value jvSource = jvSrcCurrencies[i]; uint160 srcCurrencyID; @@ -760,16 +764,80 @@ Json::Value RPCHandler::doRipplePathFind(const Json::Value& jvRequest) STPathSet spsPaths; // XXX Need to add support for srcIssuerID. - Pathfinder pf(raSrc, raDst, srcCurrencyID, saDst); + Pathfinder pf(raSrc, raDst, srcCurrencyID, saDstAmount); - if (!spsPaths.isEmpty()) + pf.findPaths(5, 1, spsPaths, true); + + if (spsPaths.isEmpty()) + { + cLog(lsDEBUG) << "ripple_path_find: No paths found."; + } + else { // XXX Also need to check liquidity. - jvSource.append(spsPaths.getJson(0)); + STAmount saMaxAmountAct; + STAmount saDstAmountAct; + STAmount saMaxAmount(srcCurrencyID, + !!srcIssuerID + ? srcIssuerID + : !!srcCurrencyID + ? raSrc.getAccountID() + : ACCOUNT_XRP, + 1); + saMaxAmount.negate(); + + TER terResult = + RippleCalc::rippleCalc( + lesSnapshot, + saMaxAmountAct, + saDstAmountAct, + saMaxAmount, // --> -1/xxx/yyy unlimited + saDstAmount, // --> Amount to deliver. + raDst.getAccountID(), // --> Account to deliver to. + raSrc.getAccountID(), // --> Account sending from. + spsPaths, // --> Path set. + false, // --> bPartialPayment - XXX might allow sometimes. + // Must achive delivery goal. + false, // --> bLimitQuality - XXX might allow sometimes. + // Average quality is wanted for normal payments. + // XXX TRUE till direct path representation resolved. + true, // --> bNoRippleDirect - XXX might allow sometimes. + // XXX No reason not to take the direct, unless set is merely direct. + true); //--> Stand alone mode, don't delete unfundeds. + + cLog(lsDEBUG) + << boost::str(boost::format("ripple_path_find: saMaxAmount=%s saDstAmount=%s saMaxAmountAct=%s saDstAmountAct=%s") + % saMaxAmount + % saDstAmount + % saMaxAmountAct + % saDstAmountAct); + + if (tesSUCCESS == terResult) + { + Json::Value jvEntry(Json::objectValue); + + jvEntry["source_amount"] = saMaxAmountAct.getJson(0); + jvEntry["paths"] = spsPaths.getJson(0); + + jvArray.append(jvEntry); + } + else + { + std::string strToken; + std::string strHuman; + + transResultInfo(terResult, strToken, strHuman); + + cLog(lsDEBUG) + << boost::str(boost::format("ripple_path_find: %s %s %s") + % strToken + % strHuman + % spsPaths.getJson(0)); + } } } - jvResult["results"] = jvArray; + jvResult["alternatives"] = jvArray; } return jvResult; @@ -872,7 +940,7 @@ Json::Value RPCHandler::handleJSONSubmit(const Json::Value& jvRequest) Pathfinder pf(srcAddress, dstAccountID, srcCurrencyID, dstAmount); - pf.findPaths(5, 1, spsPaths); + pf.findPaths(5, 1, spsPaths, false); if (!spsPaths.isEmpty()) { @@ -1067,7 +1135,7 @@ Json::Value RPCHandler::doTxHistory(const Json::Value& params) obj["index"]=startIndex; std::string sql = - str(boost::format("SELECT * FROM Transactions ORDER BY LedgerSeq desc LIMIT %u,20") + boost::str(boost::format("SELECT * FROM Transactions ORDER BY LedgerSeq desc LIMIT %u,20") % startIndex); { diff --git a/src/cpp/ripple/RippleCalc.cpp b/src/cpp/ripple/RippleCalc.cpp index 24508b8633..022b25a85a 100644 --- a/src/cpp/ripple/RippleCalc.cpp +++ b/src/cpp/ripple/RippleCalc.cpp @@ -2126,7 +2126,6 @@ void RippleCalc::pathNext(PathState::ref pspCur, const int iPaths, const LedgerE } } -// XXX Stand alone calculation not implemented, does not calculate required input. TER RippleCalc::rippleCalc( LedgerEntrySet& lesActive, // <-> --> = Fee applied to src balance. STAmount& saMaxAmountAct, // <-- The computed input amount. @@ -2138,7 +2137,8 @@ TER RippleCalc::rippleCalc( const STPathSet& spsPaths, const bool bPartialPayment, const bool bLimitQuality, - const bool bNoRippleDirect + const bool bNoRippleDirect, + const bool bStandAlone // True, not to delete unfundeds. ) { RippleCalc rc(lesActive); @@ -2240,8 +2240,9 @@ cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Build path: %d: add: %d s terResult = temUNCERTAIN; } - STAmount saInAct = STAmount(saMaxAmountReq.getCurrency(), saMaxAmountReq.getIssuer()); - STAmount saOutAct = STAmount(saDstAmountReq.getCurrency(), saDstAmountReq.getIssuer()); + saMaxAmountAct = STAmount(saMaxAmountReq.getCurrency(), saMaxAmountReq.getIssuer()); + saDstAmountAct = STAmount(saDstAmountReq.getCurrency(), saDstAmountReq.getIssuer()); + const LedgerEntrySet lesBase = lesActive; // Checkpoint with just fees paid. const uint64 uQualityLimit = bLimitQuality ? STAmount::getRate(saDstAmountReq, saMaxAmountReq) : 0; // When processing, don't want to complicate directory walking with deletion. @@ -2259,8 +2260,8 @@ int iPass = 0; { if (pspCur->uQuality) { - pspCur->saInAct = saInAct; // Update to current amount processed. - pspCur->saOutAct = saOutAct; + pspCur->saInAct = saMaxAmountAct; // Update to current amount processed. + pspCur->saOutAct = saDstAmountAct; rc.pathNext(pspCur, vpsPaths.size(), lesCheckpoint, lesActive); // Compute increment. @@ -2314,8 +2315,8 @@ cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Summary: %d quality:%d be // Record best pass' LedgerEntrySet to build off of and potentially return. lesActive.swapWith(pspBest->lesEntries); - saInAct += pspBest->saInPass; - saOutAct += pspBest->saOutPass; + saMaxAmountAct += pspBest->saInPass; + saDstAmountAct += pspBest->saOutPass; if (pspBest->bConsumed) { @@ -2323,13 +2324,13 @@ cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Summary: %d quality:%d be pspBest->uQuality = 0; } - if (saOutAct == saDstAmountReq) + if (saDstAmountAct == saDstAmountReq) { // Done. Delivered requested amount. terResult = tesSUCCESS; } - else if (saInAct != saMaxAmountReq && iDry != vpsPaths.size()) + else if (saMaxAmountAct != saMaxAmountReq && iDry != vpsPaths.size()) { // Have not met requested amount or max send, try to do more. Prepare for next pass. @@ -2359,7 +2360,7 @@ cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Summary: %d quality:%d be lesActive = lesBase; // Revert to just fees charged. } // Partial payment ok. - else if (!saOutAct) + else if (!saDstAmountAct) { // No payment at all. terResult = tepPATH_DRY; @@ -2371,22 +2372,25 @@ cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Summary: %d quality:%d be } } - if (tesSUCCESS == terResult) - { - // Delete became unfunded offers. - BOOST_FOREACH(const uint256& uOfferIndex, vuUnfundedBecame) - { - if (tesSUCCESS == terResult) - terResult = lesActive.offerDelete(uOfferIndex); - } - } + if (!bStandAlone) + { + if (tesSUCCESS == terResult) + { + // Delete became unfunded offers. + BOOST_FOREACH(const uint256& uOfferIndex, vuUnfundedBecame) + { + if (tesSUCCESS == terResult) + terResult = lesActive.offerDelete(uOfferIndex); + } + } - // Delete found unfunded offers. - BOOST_FOREACH(const uint256& uOfferIndex, rc.musUnfundedFound) - { - if (tesSUCCESS == terResult) - terResult = lesActive.offerDelete(uOfferIndex); - } + // Delete found unfunded offers. + BOOST_FOREACH(const uint256& uOfferIndex, rc.musUnfundedFound) + { + if (tesSUCCESS == terResult) + terResult = lesActive.offerDelete(uOfferIndex); + } + } return terResult; } diff --git a/src/cpp/ripple/RippleCalc.h b/src/cpp/ripple/RippleCalc.h index 887c5c5577..c04c7bd44e 100644 --- a/src/cpp/ripple/RippleCalc.h +++ b/src/cpp/ripple/RippleCalc.h @@ -34,7 +34,7 @@ protected: // For offers: - STAmount saRateMax; // XXX Should rate be sticky for forward too? + STAmount saRateMax; // Directory uint256 uDirectTip; // Current directory. @@ -186,7 +186,8 @@ public: const STPathSet& spsPaths, const bool bPartialPayment, const bool bLimitQuality, - const bool bNoRippleDirect + const bool bNoRippleDirect, + const bool bStandAlone ); };