From 6d8afeca477933bfa11c5fe90f6c2d2b781e5328 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Fri, 30 Nov 2012 17:14:25 -0800 Subject: [PATCH] Work on path finder integration. --- src/cpp/ripple/Pathfinder.cpp | 60 ++++++++++++++++++++------ src/cpp/ripple/Pathfinder.h | 5 ++- src/cpp/ripple/RPCHandler.cpp | 80 +++++++++++++++++------------------ src/cpp/ripple/RPCHandler.h | 1 + src/cpp/ripple/RippleCalc.cpp | 3 +- 5 files changed, 93 insertions(+), 56 deletions(-) diff --git a/src/cpp/ripple/Pathfinder.cpp b/src/cpp/ripple/Pathfinder.cpp index 88387fb679..52d1b96f7d 100644 --- a/src/cpp/ripple/Pathfinder.cpp +++ b/src/cpp/ripple/Pathfinder.cpp @@ -75,6 +75,14 @@ PathOption::PathOption(PathOption::pointer other) } #endif +// Return true, if path is a default path with an element. +// XXX Could be determined via STAmount +bool Pathfinder::bDefaultPath(const STPath& spPath) +{ + return false; + // return spPath.size() == 3 && spPath.mPath[1].mType; +} + // // XXX Optionally, specifying a source and destination issuer might be nice. Especially, to convert between issuers. However, this // functionality is left to the future. @@ -88,11 +96,24 @@ Pathfinder::Pathfinder(const RippleAddress& srcAccountID, const RippleAddress& d // If possible, returns a single path. // --> maxSearchSteps: unused XXX // --> maxPay: unused XXX +// <-- retPathSet: founds paths not including default paths. +// Returns true if found paths. +// // When generating a path set blindly, don't allow the empty path, it is implied by default. // When generating a path set for estimates, allow an empty path instead of no paths to indicate a path exists. The caller will // need to strip the empty path when submitting the transaction. -bool Pathfinder::findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet, bool bAllowEmpty) +bool Pathfinder::findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet) { + bool bFound = false; + + cLog(lsDEBUG) << boost::str(boost::format("findPaths> mSrcAccountID=%s mDstAccountID=%s mDstAmount=%s mSrcCurrencyID=%s mSrcIssuerID=%s") + % RippleAddress::createHumanAccountID(mSrcAccountID) + % RippleAddress::createHumanAccountID(mDstAccountID) + % mDstAmount.getFullText() + % STAmount::createHumanCurrency(mSrcCurrencyID) + % RippleAddress::createHumanAccountID(mSrcIssuerID) + ); + if (mLedger) { std::queue pqueue; STPathElement ele(mSrcAccountID, @@ -112,30 +133,45 @@ bool Pathfinder::findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet ele = path.mPath.back(); // Get the last node from the path. // Done, if dest wants XRP and last element produces XRP. - if (!ele.mCurrencyID // Tail output is XRP - && !mDstAmount.getCurrency()) { + if (!ele.mCurrencyID // Tail output is XRP. + && !mDstAmount.getCurrency()) { // Which is dst currency. // Remove implied first. path.mPath.erase(path.mPath.begin()); - // Return the path. - retPathSet.addPath(path); + if (path.size()) + { + // There is an actual path element. - cLog(lsDEBUG) << "findPaths: adding: " << path.getJson(0); + retPathSet.addPath(path); // Return the path. + + cLog(lsDEBUG) << "findPaths: adding: " << path.getJson(0); + } + else + { + cLog(lsDEBUG) << "findPaths: empty path: XRP->XRP"; + } return true; } // 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 + if (ele.mAccountID == mDstAccountID // Tail is destination account. && ele.mCurrencyID == mDstAmount.getCurrency()) { // With correct output currency. // Found a path to the destination. - if (!bAllowEmpty && 2 == path.mPath.size()) { - // Empty path is default. Drop it. - cLog(lsDEBUG) << "findPaths: dropping empty path."; - continue; + if (2 == path.mPath.size()) { + // Empty path is a default. Don't need to add it to return set. + cLog(lsDEBUG) << "findPaths: empty path: direct"; + + return true; + } + else if (bDefaultPath(path)) { + // Path is a default (implied). Don't need to add it to return set. + cLog(lsDEBUG) << "findPaths: default path: indirect: " << path.getJson(0); + + return true; } // Remove implied first and last nodes. @@ -246,7 +282,7 @@ bool Pathfinder::findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet cLog(lsWARNING) << boost::str(boost::format("findPaths: no ledger")); } - return false; + return bFound; } #if 0 diff --git a/src/cpp/ripple/Pathfinder.h b/src/cpp/ripple/Pathfinder.h index 6537f01613..f72f334783 100644 --- a/src/cpp/ripple/Pathfinder.h +++ b/src/cpp/ripple/Pathfinder.h @@ -52,7 +52,8 @@ class Pathfinder public: Pathfinder(const RippleAddress& srcAccountID, const RippleAddress& dstAccountID, const uint160& srcCurrencyID, const uint160& srcIssuerID, const STAmount& dstAmount); - // returns false if there is no path. otherwise fills out retPath - bool findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet, bool bAllowEmpty); + bool findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet); + + bool bDefaultPath(const STPath& spPath); }; // vim:ts=4 diff --git a/src/cpp/ripple/RPCHandler.cpp b/src/cpp/ripple/RPCHandler.cpp index f1966c626f..99ce0e83d1 100644 --- a/src/cpp/ripple/RPCHandler.cpp +++ b/src/cpp/ripple/RPCHandler.cpp @@ -54,6 +54,7 @@ Json::Value RPCHandler::rpcError(int iError) { rpcNO_EVENTS, "noEvents", "Current transport does not support events." }, { rpcNO_GEN_DECRPYT, "noGenDectypt", "Password failed to decrypt master public generator." }, { rpcNO_NETWORK, "noNetwork", "Network not available." }, + { rpcNO_PATH, "noPath", "Unable to find a ripple path." }, { rpcNO_PERMISSION, "noPermission", "You don't have permission for this command." }, { rpcNOT_STANDALONE, "notStandAlone", "Operation valid in debug mode only." }, { rpcPASSWD_CHANGED, "passwdChanged", "Wrong key, password changed." }, @@ -784,9 +785,7 @@ Json::Value RPCHandler::doRipplePathFind(const Json::Value& jvRequest) Pathfinder pf(raSrc, raDst, uSrcCurrencyID, uSrcIssuerID, saDstAmount); - pf.findPaths(5, 1, spsComputed, true); - - if (spsComputed.isEmpty()) + if (!pf.findPaths(5, 1, spsComputed)) { cLog(lsDEBUG) << "ripple_path_find: No paths found."; } @@ -804,11 +803,7 @@ Json::Value RPCHandler::doRipplePathFind(const Json::Value& jvRequest) 1); saMaxAmount.negate(); - // Strip empty/default path. - if (1 == spsComputed.size() && !spsComputed.begin()->size()) - { - spsComputed.clear(); - } + cLog(lsDEBUG) << "ripple_path_find: PATHS: " << spsComputed.size(); TER terResult = RippleCalc::rippleCalc( @@ -955,51 +950,54 @@ Json::Value RPCHandler::handleJSONSubmit(const Json::Value& jvRequest) txJSON["Fee"] = (int) theConfig.FEE_ACCOUNT_CREATE; } - if (!txJSON.isMember("Paths") && jvRequest.isMember("build_path")) + if (!txJSON.isMember("Paths") && txJSON.isMember("Amount") && jvRequest.isMember("build_path")) { - if (txJSON["Amount"].isObject() || txJSON.isMember("SendMax")) - { // we need a ripple path - STPathSet spsPaths; - uint160 uSrcCurrencyID; - uint160 uSrcIssuerID; + // Need a ripple path. + STPathSet spsPaths; + uint160 uSrcCurrencyID; + uint160 uSrcIssuerID; - if (txJSON.isMember("SendMax") && txJSON["SendMax"].isMember("currency")) - { - STAmount::currencyFromString(uSrcCurrencyID, txJSON["SendMax"]["currency"].asString()); - } - else - { - uSrcCurrencyID = CURRENCY_XRP; - } + STAmount saSendMax; + STAmount saSend; - if (!!uSrcCurrencyID) - { - uSrcIssuerID = raSrcAddressID.getAccountID(); - } + if (!txJSON.isMember("Amount") // Amount required. + || !saSend.bSetJson(txJSON["Amount"])) // Must be valid. + return rpcError(rpcDST_AMT_MALFORMED); - STAmount dstAmount; + if (txJSON.isMember("SendMax")) + { + if (!saSendMax.bSetJson(txJSON["SendMax"])) + return rpcError(rpcINVALID_PARAMS); + } + else + { + // If no SendMax, default to Amount with sender as issuer. + saSendMax = saSend; + saSendMax.setIssuer(raSrcAddressID.getAccountID()); + } - if (!dstAmount.bSetJson(txJSON["Amount"])) - { - return rpcError(rpcDST_AMT_MALFORMED); - } + Pathfinder pf(raSrcAddressID, dstAccountID, saSendMax.getCurrency(), saSendMax.getIssuer(), saSend); - Pathfinder pf(raSrcAddressID, dstAccountID, uSrcCurrencyID, uSrcIssuerID, dstAmount); + if (!pf.findPaths(5, 1, spsPaths)) + { + cLog(lsDEBUG) << "payment: build_path: No paths found."; - pf.findPaths(5, 1, spsPaths, false); + return rpcError(rpcNO_PATH); + } + else + { + cLog(lsDEBUG) << "payment: build_path: " << spsPaths.getJson(0); + } - if (!spsPaths.isEmpty()) - { - txJSON["Paths"]=spsPaths.getJson(0); - if(txJSON.isMember("Flags")) txJSON["Flags"]=txJSON["Flags"].asUInt() | 2; - else txJSON["Flags"]=2; - } + if (!spsPaths.isEmpty()) + { + txJSON["Paths"]=spsPaths.getJson(0); } } } - if(!txJSON.isMember("Sequence")) txJSON["Sequence"]=asSrc->getSeq(); - if(!txJSON.isMember("Flags")) txJSON["Flags"]=0; + if (!txJSON.isMember("Sequence")) txJSON["Sequence"]=asSrc->getSeq(); + if (!txJSON.isMember("Flags")) txJSON["Flags"]=0; Ledger::pointer lpCurrent = mNetOps->getCurrentLedger(); SLE::pointer sleAccountRoot = mNetOps->getSLE(lpCurrent, Ledger::getAccountRootIndex(raSrcAddressID.getAccountID())); diff --git a/src/cpp/ripple/RPCHandler.h b/src/cpp/ripple/RPCHandler.h index d9911b3edc..2ac0e3f96c 100644 --- a/src/cpp/ripple/RPCHandler.h +++ b/src/cpp/ripple/RPCHandler.h @@ -121,6 +121,7 @@ public: rpcLGR_NOT_FOUND, rpcNICKNAME_MISSING, rpcNO_ACCOUNT, + rpcNO_PATH, rpcPASSWD_CHANGED, rpcSRC_MISSING, rpcSRC_UNCLAIMED, diff --git a/src/cpp/ripple/RippleCalc.cpp b/src/cpp/ripple/RippleCalc.cpp index f730ee3ca0..e554e13b1c 100644 --- a/src/cpp/ripple/RippleCalc.cpp +++ b/src/cpp/ripple/RippleCalc.cpp @@ -2328,9 +2328,10 @@ TER RippleCalc::rippleCalc( // Incrementally search paths. + // bNoRippleDirect is a slight misnomer, it really means make no ripple default path. if (!bNoRippleDirect) { - // Direct path. + // Build a default path. Use saDstAmountReq and saMaxAmountReq to imply nodes. // XXX Might also make a XRP bridge by default. PathState::pointer pspDirect = boost::make_shared(saDstAmountReq, saMaxAmountReq, lesActive.getLedgerRef());