diff --git a/newcoin.vcxproj b/newcoin.vcxproj index 42abebc8cd..a3cc152b6d 100644 --- a/newcoin.vcxproj +++ b/newcoin.vcxproj @@ -157,6 +157,7 @@ + diff --git a/newcoin.vcxproj.filters b/newcoin.vcxproj.filters index 49dc813897..e1d94cc8fd 100644 --- a/newcoin.vcxproj.filters +++ b/newcoin.vcxproj.filters @@ -174,6 +174,9 @@ Source Files + + Source Files + Source Files diff --git a/ripple2010.vcxproj b/ripple2010.vcxproj index e044141116..08e43f0c50 100644 --- a/ripple2010.vcxproj +++ b/ripple2010.vcxproj @@ -145,6 +145,7 @@ + diff --git a/ripple2010.vcxproj.filters b/ripple2010.vcxproj.filters index dd67f928d2..6aacf1bf82 100644 --- a/ripple2010.vcxproj.filters +++ b/ripple2010.vcxproj.filters @@ -171,6 +171,9 @@ Source Files + + Source Files + Source Files diff --git a/src/cpp/ripple/Amount.cpp b/src/cpp/ripple/Amount.cpp index ee851aa7e3..2d0094475f 100644 --- a/src/cpp/ripple/Amount.cpp +++ b/src/cpp/ripple/Amount.cpp @@ -281,7 +281,7 @@ bool STAmount::setValue(const std::string& sAmount) try { - if ((smMatch[2].length() + smMatch[4].length()) > 25) + if ((smMatch[2].length() + smMatch[4].length()) > 32) { cLog(lsWARNING) << "Overlong number: " << sAmount; return false; diff --git a/src/cpp/ripple/CallRPC.cpp b/src/cpp/ripple/CallRPC.cpp index 5fba686ace..268cda5725 100644 --- a/src/cpp/ripple/CallRPC.cpp +++ b/src/cpp/ripple/CallRPC.cpp @@ -736,6 +736,7 @@ Json::Value RPCParser::parseCommand(std::string strMethod, Json::Value jvParams) #endif // Evented methods + { "path_find", &RPCParser::parseEvented, -1, -1 }, { "subscribe", &RPCParser::parseEvented, -1, -1 }, { "unsubscribe", &RPCParser::parseEvented, -1, -1 }, }; diff --git a/src/cpp/ripple/JobQueue.cpp b/src/cpp/ripple/JobQueue.cpp index e89b2184da..f6ce0bcf47 100644 --- a/src/cpp/ripple/JobQueue.cpp +++ b/src/cpp/ripple/JobQueue.cpp @@ -43,6 +43,7 @@ const char* Job::toString(JobType t) case jtPROOFWORK: return "proofOfWork"; case jtPROPOSAL_ut: return "untrustedProposal"; case jtLEDGER_DATA: return "ledgerData"; + case jtUPDATE_PF: return "updatePaths"; case jtCLIENT: return "clientCommand"; case jtTRANSACTION: return "transaction"; case jtPUBLEDGER: return "publishNewLedger"; diff --git a/src/cpp/ripple/JobQueue.h b/src/cpp/ripple/JobQueue.h index 14710d95f9..76d444b92c 100644 --- a/src/cpp/ripple/JobQueue.h +++ b/src/cpp/ripple/JobQueue.h @@ -28,16 +28,17 @@ enum JobType jtPROOFWORK = 4, // A proof of work demand from another server jtPROPOSAL_ut = 5, // A proposal from an untrusted source jtLEDGER_DATA = 6, // Received data for a ledger we're acquiring - jtCLIENT = 7, // A websocket command from the client - jtTRANSACTION = 8, // A transaction received from the network - jtPUBLEDGER = 9, // Publish a fully-accepted ledger - jtWAL = 10, // Write-ahead logging - jtVALIDATION_t = 11, // A validation from a trusted source - jtWRITE = 12, // Write out hashed objects - jtTRANSACTION_l = 13, // A local transaction - jtPROPOSAL_t = 14, // A proposal from a trusted source - jtADMIN = 15, // An administrative operation - jtDEATH = 16, // job of death, used internally + jtUPDATE_PF = 7, // Update pathfinding requests + jtCLIENT = 8, // A websocket command from the client + jtTRANSACTION = 9, // A transaction received from the network + jtPUBLEDGER = 10, // Publish a fully-accepted ledger + jtWAL = 11, // Write-ahead logging + jtVALIDATION_t = 12, // A validation from a trusted source + jtWRITE = 13, // Write out hashed objects + jtTRANSACTION_l = 14, // A local transaction + jtPROPOSAL_t = 15, // A proposal from a trusted source + jtADMIN = 16, // An administrative operation + jtDEATH = 17, // job of death, used internally // special types not dispatched by the job pool jtPEER = 24, diff --git a/src/cpp/ripple/LedgerMaster.cpp b/src/cpp/ripple/LedgerMaster.cpp index 377650973a..97f04e1518 100644 --- a/src/cpp/ripple/LedgerMaster.cpp +++ b/src/cpp/ripple/LedgerMaster.cpp @@ -6,6 +6,7 @@ #include "Application.h" #include "RippleAddress.h" #include "Log.h" +#include "PFRequest.h" SETUP_LOG(); @@ -627,12 +628,20 @@ void LedgerMaster::tryPublish() mPubThread = true; theApp->getJobQueue().addJob(jtPUBLEDGER, "Ledger::pubThread", BIND_TYPE(&LedgerMaster::pubThread, this)); + mPathFindNewLedger = true; + if (!mPathFindThread) + { + mPathFindThread = true; + theApp->getJobQueue().addJob(jtUPDATE_PF, "updatePaths", + BIND_TYPE(&LedgerMaster::updatePaths, this)); + } } } void LedgerMaster::pubThread() { std::list ledgers; + bool published = false; while (1) { @@ -644,6 +653,12 @@ void LedgerMaster::pubThread() if (ledgers.empty()) { mPubThread = false; + if (published && !mPathFindThread) + { + mPathFindThread = true; + theApp->getJobQueue().addJob(jtUPDATE_PF, "updatePaths", + BIND_TYPE(&LedgerMaster::updatePaths, this)); + } return; } } @@ -653,8 +668,52 @@ void LedgerMaster::pubThread() cLog(lsDEBUG) << "Publishing ledger " << l->getLedgerSeq(); setFullLedger(l); // OPTIMIZEME: This is actually more work than we need to do theApp->getOPs().pubLedger(l); + published = true; } } } +void LedgerMaster::updatePaths() +{ + Ledger::pointer lastLedger; + do + { + bool newOnly = false; + + { + boost::recursive_mutex::scoped_lock ml(mLock); + if (mPathFindNewLedger || (lastLedger && (lastLedger.get() != mPubLedger.get()))) + lastLedger = mPubLedger; + else if (mPathFindNewRequest) + { + newOnly = true; + lastLedger = boost::make_shared(*mCurrentLedger, false); + } + else + { + mPathFindThread = false; + return; + } + lastLedger = mPubLedger; + mPathFindNewLedger = false; + mPathFindNewRequest = false; + } + + PFRequest::updateAll(lastLedger, newOnly); + + } while(1); +} + +void LedgerMaster::newPFRequest() +{ + boost::recursive_mutex::scoped_lock ml(mLock); + mPathFindNewRequest = true; + if (!mPathFindThread) + { + mPathFindThread = true; + theApp->getJobQueue().addJob(jtUPDATE_PF, "updatePaths", + BIND_TYPE(&LedgerMaster::updatePaths, this)); + } +} + // vim:ts=4 diff --git a/src/cpp/ripple/LedgerMaster.h b/src/cpp/ripple/LedgerMaster.h index 6c9d87122b..2d872bbdca 100644 --- a/src/cpp/ripple/LedgerMaster.h +++ b/src/cpp/ripple/LedgerMaster.h @@ -46,6 +46,10 @@ protected: std::list mPubLedgers; // List of ledgers to publish bool mPubThread; // Publish thread is running + bool mPathFindThread; // Pathfind thread is running + bool mPathFindNewLedger; + bool mPathFindNewRequest; + void applyFutureTransactions(uint32 ledgerIndex); bool isValidTransaction(Transaction::ref trans); bool isTransactionOnFutureList(Transaction::ref trans); @@ -54,11 +58,13 @@ protected: void asyncAccept(Ledger::pointer); void missingAcquireComplete(LedgerAcquire::pointer); void pubThread(); + void updatePaths(); public: LedgerMaster() : mHeldTransactions(uint256()), mMissingSeq(0), - mMinValidations(0), mLastValidateSeq(0), mPubThread(false) + mMinValidations(0), mLastValidateSeq(0), mPubThread(false), + mPathFindThread(false), mPathFindNewLedger(false), mPathFindNewRequest(false) { ; } uint32 getCurrentLedgerIndex(); @@ -155,6 +161,7 @@ public: void checkAccept(const uint256& hash); void checkAccept(const uint256& hash, uint32 seq); void tryPublish(); + void newPFRequest(); static bool shouldAcquire(uint32 currentLedgerID, uint32 ledgerHistory, uint32 targetLedger); }; diff --git a/src/cpp/ripple/NetworkOPs.h b/src/cpp/ripple/NetworkOPs.h index 6469d8ff5e..6db090981f 100644 --- a/src/cpp/ripple/NetworkOPs.h +++ b/src/cpp/ripple/NetworkOPs.h @@ -21,6 +21,7 @@ class Peer; class LedgerConsensus; +class PFRequest; DEFINE_INSTANCE(InfoSub); @@ -29,6 +30,7 @@ class InfoSub : public IS_INSTANCE(InfoSub) protected: boost::unordered_set mSubAccountInfo; boost::unordered_set mSubAccountTransaction; + boost::shared_ptr mPFRequest; boost::mutex mLockInfo; @@ -64,6 +66,21 @@ public: mSubAccountInfo.insert(addr); } + + void clearPFRequest() + { + mPFRequest.reset(); + } + + void setPFRequest(const boost::shared_ptr& req) + { + mPFRequest = req; + } + + const boost::shared_ptr& getPFRequest() + { + return mPFRequest; + } }; class NetworkOPs diff --git a/src/cpp/ripple/PFRequest.cpp b/src/cpp/ripple/PFRequest.cpp new file mode 100644 index 0000000000..cc1b827ee1 --- /dev/null +++ b/src/cpp/ripple/PFRequest.cpp @@ -0,0 +1,328 @@ +#include "PFRequest.h" + +#include "NetworkOPs.h" +#include "RPCErr.h" +#include "Ledger.h" +#include "Application.h" +#include "Pathfinder.h" +#include "RippleCalc.h" +#include "LedgerFormats.h" + +SETUP_LOG(); + +boost::recursive_mutex PFRequest::sLock; +std::set PFRequest::sRequests; + +PFRequest::PFRequest(const boost::shared_ptr& subscriber) : + wpSubscriber(subscriber), jvStatus(Json::objectValue), bValid(false), bNew(true) +{ + ; +} + +bool PFRequest::isValid() +{ + boost::recursive_mutex::scoped_lock sl(mLock); + return bValid; +} + +bool PFRequest::isNew() +{ + boost::recursive_mutex::scoped_lock sl(mLock); + return bNew; +} + +bool PFRequest::isValid(Ledger::ref lrLedger) +{ + boost::recursive_mutex::scoped_lock sl(mLock); + bValid = raSrcAccount.isSet() && raDstAccount.isSet() && saDstAmount.isPositive(); + if (bValid) + { + AccountState::pointer asSrc = theApp->getOPs().getAccountState(lrLedger, raSrcAccount); + if (!asSrc) + { // no source account + bValid = false; + jvStatus = rpcError(rpcSRC_ACT_NOT_FOUND); + } + else + { + AccountState::pointer asDst = theApp->getOPs().getAccountState(lrLedger, raDstAccount); + Json::Value jvDestCur; + if (!asDst) + { // no destination account + jvDestCur.append(Json::Value("XRP")); + if(!saDstAmount.isNative()) + { // only XRP can be send to a non-existent account + bValid = false; + jvStatus = rpcError(rpcACT_NOT_FOUND); + } + else if (saDstAmount < STAmount(lrLedger->getReserve(0))) + { // payment must meet reserve + bValid = false; + jvStatus = rpcError(rpcDST_AMT_MALFORMED); + } + } + else + { + boost::unordered_set usDestCurrID = usAccountDestCurrencies(raDstAccount, lrLedger, true); + BOOST_FOREACH(const uint160& uCurrency, usDestCurrID) + jvDestCur.append(STAmount::createHumanCurrency(uCurrency)); + jvStatus["destination_tag"] = (asDst->peekSLE().getFlags() & lsfRequireDestTag) != 0; + } + jvStatus["destination_currencies"] = jvDestCur; + } + } + jvStatus["ledger_hash"] = lrLedger->getHash().GetHex(); + jvStatus["ledger_index"] = lrLedger->getLedgerSeq(); + return bValid; +} + +Json::Value PFRequest::doCreate(Ledger::ref lrLedger, const Json::Value& value) +{ + assert(lrLedger->isClosed()); + + Json::Value status; + bool mValid; + + { + boost::recursive_mutex::scoped_lock sl(mLock); + if (parseJson(value, true) != PFR_PJ_INVALID) + { + mValid = isValid(lrLedger); + if (mValid) + { + RLCache::pointer cache = boost::make_shared(lrLedger); + doUpdate(cache, true); + } + } + else + mValid = false; + } + + if (mValid) + { + cLog(lsINFO) << "Request created: " << raSrcAccount.humanAccountID() << + " -> " << raDstAccount.humanAccountID(); + cLog(lsINFO) << "Deliver: " << saDstAmount.getFullText(); + + boost::recursive_mutex::scoped_lock sl(sLock); + sRequests.insert(shared_from_this()); + } + + return jvStatus; +} + +int PFRequest::parseJson(const Json::Value& jvParams, bool complete) +{ + int ret = PFR_PJ_NOCHANGE; + + if (jvParams.isMember("source_account")) + { + if (!raSrcAccount.setAccountID(jvParams["source_account"].asString())) + { + jvStatus = rpcError(rpcSRC_ACT_MALFORMED); + return PFR_PJ_INVALID; + } + } + else if (complete) + { + jvStatus = rpcError(rpcSRC_ACT_MISSING); + return PFR_PJ_INVALID; + } + + if (jvParams.isMember("destination_account")) + { + if (!raDstAccount.setAccountID(jvParams["destination_account"].asString())) + { + jvStatus = rpcError(rpcDST_ACT_MALFORMED); + return PFR_PJ_INVALID; + } + } + else if (complete) + { + jvStatus = rpcError(rpcDST_ACT_MISSING); + return PFR_PJ_INVALID; + } + + if (jvParams.isMember("destination_amount")) + { + if (!saDstAmount.bSetJson(jvParams["destination_amount"]) || + (saDstAmount.getCurrency().isZero() && saDstAmount.getIssuer().isNonZero()) || + (saDstAmount.getCurrency() == CURRENCY_BAD) || + !saDstAmount.isPositive()) + { + jvStatus = rpcError(rpcDST_AMT_MALFORMED); + return PFR_PJ_INVALID; + } + } + else if (complete) + { + jvStatus = rpcError(rpcDST_ACT_MISSING); + return PFR_PJ_INVALID; + } + + if (jvParams.isMember("source_currencies")) + { + const Json::Value& jvSrcCur = jvParams["source_currencies"]; + if (!jvSrcCur.isArray()) + { + jvStatus = rpcError(rpcSRC_CUR_MALFORMED); + return PFR_PJ_INVALID; + } + sciSourceCurrencies.clear(); + for (unsigned i = 0; i < jvSrcCur.size(); ++i) + { + const Json::Value& jvCur = jvSrcCur[i]; + uint160 uCur, uIss; + if (!jvCur.isMember("currency") || !STAmount::currencyFromString(uCur, jvCur["currency"].asString())) + { + jvStatus = rpcError(rpcSRC_CUR_MALFORMED); + return PFR_PJ_INVALID; + } + if (jvCur.isMember("issuer") && !STAmount::issuerFromString(uIss, jvCur["issuer"].asString())) + { + jvStatus = rpcError(rpcSRC_ISR_MALFORMED); + } + if (uCur.isZero() && uIss.isNonZero()) + { + jvStatus = rpcError(rpcSRC_CUR_MALFORMED); + return PFR_PJ_INVALID; + } + sciSourceCurrencies.insert(currIssuer_t(uCur, uIss)); + } + } + + return ret; +} +Json::Value PFRequest::doClose(const Json::Value&) +{ + boost::recursive_mutex::scoped_lock sl(mLock); + return jvStatus; +} + +Json::Value PFRequest::doStatus(const Json::Value&) +{ + boost::recursive_mutex::scoped_lock sl(mLock); + return jvStatus; +} + +bool PFRequest::doUpdate(RLCache::ref cache, bool fast) +{ + boost::recursive_mutex::scoped_lock sl(mLock); + jvStatus = Json::objectValue; + if (!isValid(cache->getLedger())) + return false; + if (!fast) + bNew = false; + + std::set sourceCurrencies(sciSourceCurrencies); + if (sourceCurrencies.empty()) + { + boost::unordered_set usCurrencies = + usAccountSourceCurrencies(raSrcAccount, cache->getLedger(), true); + bool sameAccount = raSrcAccount == raDstAccount; + BOOST_FOREACH(const uint160& c, usCurrencies) + { + if (!sameAccount || (c != saDstAmount.getCurrency())) + { + if (c.isZero()) + sourceCurrencies.insert(std::make_pair(c, ACCOUNT_XRP)); + else + sourceCurrencies.insert(std::make_pair(c, raSrcAccount.getAccountID())); + } + } + } + + jvStatus["source_account"] = raSrcAccount.humanAccountID(); + jvStatus["destination_account"] = raDstAccount.humanAccountID(); + + Json::Value jvArray = Json::arrayValue; + + BOOST_FOREACH(const currIssuer_t& currIssuer, sourceCurrencies) + { + { + STAmount test(currIssuer.first, currIssuer.second, 1); + cLog(lsDEBUG) << "Trying to find paths: " << test.getFullText(); + } + bool valid; + STPathSet spsPaths; + Pathfinder pf(cache, raSrcAccount, raDstAccount, + currIssuer.first, currIssuer.second, saDstAmount, valid); + tLog(!valid, lsINFO) << "PF request not valid"; + if (valid && pf.findPaths(theConfig.PATH_SEARCH_SIZE - (fast ? 0 : 1), 3, spsPaths)) + { + LedgerEntrySet lesSandbox(cache->getLedger(), tapNONE); + std::vector vpsExpanded; + STAmount saMaxAmountAct; + STAmount saDstAmountAct; + STAmount saMaxAmount(currIssuer.first, + currIssuer.second.isNonZero() ? currIssuer.second : + (currIssuer.first.isZero() ? ACCOUNT_XRP : raSrcAccount.getAccountID()), 1); + saMaxAmount.negate(); + cLog(lsDEBUG) << "Paths found, calling rippleCalc"; + TER terResult = RippleCalc::rippleCalc(lesSandbox, saMaxAmountAct, saDstAmountAct, + vpsExpanded, saMaxAmount, saDstAmount, raDstAccount.getAccountID(), raSrcAccount.getAccountID(), + spsPaths, false, false, false, true); + if (terResult == tesSUCCESS) + { + Json::Value jvEntry(Json::objectValue); + jvEntry["source_amount"] = saMaxAmountAct.getJson(0); + jvEntry["paths_computed"] = spsPaths.getJson(0); + jvArray.append(jvEntry); + } + else + { + cLog(lsINFO) << "rippleCalc returns " << transHuman(terResult); + } + } + else + { + cLog(lsINFO) << "No paths found"; + } + } + jvStatus["alternatives"] = jvArray; + return true; +} + +void PFRequest::updateAll(Ledger::ref ledger, bool newOnly) +{ + std::set requests; + + { + boost::recursive_mutex::scoped_lock sl(sLock); + requests = sRequests; + } + + if (requests.empty()) + return; + + RLCache::pointer cache = boost::make_shared(ledger); + + BOOST_FOREACH(wref wRequest, requests) + { + bool remove = true; + PFRequest::pointer pRequest = wRequest.lock(); + if (pRequest && (!newOnly || pRequest->isNew())) + { + InfoSub::pointer ipSub = pRequest->wpSubscriber.lock(); + if (ipSub) + { + Json::Value update; + { + boost::recursive_mutex::scoped_lock sl(pRequest->mLock); + pRequest->doUpdate(cache, false); + update = pRequest->jvStatus; + } + update["type"] = "path_find"; + ipSub->send(update, false); + remove = false; + } + } + if (remove) + { + boost::recursive_mutex::scoped_lock sl(sLock); + sRequests.erase(wRequest); + } + } +} + +// vim:ts=4 diff --git a/src/cpp/ripple/PFRequest.h b/src/cpp/ripple/PFRequest.h new file mode 100644 index 0000000000..402bb02cc0 --- /dev/null +++ b/src/cpp/ripple/PFRequest.h @@ -0,0 +1,84 @@ +#ifndef _PFREQUEST__H +#define _PFREQUEST__H + +#include +#include + +#include +#include +#include +#include + +#include "../json/value.h" + +#include "uint256.h" +#include "RippleAddress.h" +#include "SerializedTypes.h" +#include "Pathfinder.h" + +// A pathfinding request submitted by a client +// The request issuer must maintain a strong pointer + +class Ledger; +class InfoSub; +class STAmount; +class RLCache; + +// Return values from parseJson <0 = invalid, >0 = valid +#define PFR_PJ_INVALID -1 +#define PFR_PJ_NOCHANGE 0 +#define PFR_PJ_CHANGE 1 + +class PFRequest : public boost::enable_shared_from_this +{ +public: + typedef boost::weak_ptr wptr; + typedef boost::shared_ptr pointer; + typedef const pointer& ref; + typedef const wptr& wref; + typedef std::pair currIssuer_t; + + +protected: + boost::recursive_mutex mLock; + boost::weak_ptr wpSubscriber; // Who this request came from + Json::Value jvStatus; // Last result + + // Client request parameters + RippleAddress raSrcAccount; + RippleAddress raDstAccount; + STAmount saDstAmount; + std::set sciSourceCurrencies; + std::vector vjvBridges; + + bool bValid; + bool bNew; + + // Track all requests + static std::set sRequests; + static boost::recursive_mutex sLock; + + void setValid(); + int parseJson(const Json::Value&, bool complete); + +public: + + PFRequest(const boost::shared_ptr& subscriber); + + bool isValid(const boost::shared_ptr&); + bool isValid(); + bool isNew(); + Json::Value getStatus(); + + Json::Value doCreate(const boost::shared_ptr&, const Json::Value&); + Json::Value doClose(const Json::Value&); + Json::Value doStatus(const Json::Value&); + + bool doUpdate(const boost::shared_ptr&, bool fast); // update jvStatus + + static void updateAll(const boost::shared_ptr& ledger, bool newOnly); +}; + +#endif + +// vim:ts=4 diff --git a/src/cpp/ripple/Pathfinder.cpp b/src/cpp/ripple/Pathfinder.cpp index 9f3c604a8d..7101f9458a 100644 --- a/src/cpp/ripple/Pathfinder.cpp +++ b/src/cpp/ripple/Pathfinder.cpp @@ -138,7 +138,7 @@ static int getEffectiveLength(const STPath& spPath) return length; } -Pathfinder::Pathfinder(Ledger::ref ledger, +Pathfinder::Pathfinder(RLCache::ref cache, const RippleAddress& uSrcAccountID, const RippleAddress& uDstAccountID, const uint160& uSrcCurrencyID, const uint160& uSrcIssuerID, const STAmount& saDstAmount, bool& bValid) : mSrcAccountID(uSrcAccountID.getAccountID()), @@ -147,7 +147,7 @@ Pathfinder::Pathfinder(Ledger::ref ledger, mSrcCurrencyID(uSrcCurrencyID), mSrcIssuerID(uSrcIssuerID), mSrcAmount(uSrcCurrencyID, uSrcIssuerID, 1u, 0, true), - mLedger(ledger) + mLedger(cache->getLedger()), mRLCache(cache) { if (((mSrcAccountID == mDstAccountID) && (mSrcCurrencyID == mDstAmount.getCurrency())) || mDstAmount.isZero()) @@ -443,7 +443,7 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax bool bRequireAuth = isSetBit(sleEnd->getFieldU32(sfFlags), lsfRequireAuth); bool dstCurrency = speEnd.mCurrencyID == mDstAmount.getCurrency(); - AccountItems& rippleLines(getRippleLines(speEnd.mAccountID)); + AccountItems& rippleLines(mRLCache->getRippleLines(speEnd.mAccountID)); std::vector< std::pair > candidates; candidates.reserve(rippleLines.getItems().size()); @@ -776,8 +776,9 @@ boost::unordered_set usAccountDestCurrencies(const RippleAddress& raAcc return usCurrencies; } -AccountItems& Pathfinder::getRippleLines(const uint160& accountID) +AccountItems& RLCache::getRippleLines(const uint160& accountID) { + boost::mutex::scoped_lock sl(mLock); boost::unordered_map::iterator it = mRLMap.find(accountID); if (it == mRLMap.end()) it = mRLMap.insert(std::make_pair(accountID, boost::make_shared @@ -799,7 +800,7 @@ int Pathfinder::getPathsOut(const uint160& currencyID, const uint160& accountID, return it->second; int count = 0; - AccountItems& rippleLines(getRippleLines(accountID)); + AccountItems& rippleLines(mRLCache->getRippleLines(accountID)); BOOST_FOREACH(AccountItem::ref item, rippleLines.getItems()) { RippleState* rspEntry = (RippleState*) item.get(); diff --git a/src/cpp/ripple/Pathfinder.h b/src/cpp/ripple/Pathfinder.h index acc5e5a7d9..3ddbb486b1 100644 --- a/src/cpp/ripple/Pathfinder.h +++ b/src/cpp/ripple/Pathfinder.h @@ -35,6 +35,23 @@ public: }; #endif +class RLCache +{ +protected: + boost::mutex mLock; + Ledger::pointer mLedger; + boost::unordered_map mRLMap; + +public: + typedef boost::shared_ptr pointer; + typedef const pointer& ref; + + RLCache(Ledger::ref l) : mLedger(l) { ; } + Ledger::ref getLedger() { return mLedger; } + + AccountItems& getRippleLines(const uint160& accountID); +}; + class Pathfinder { uint160 mSrcAccountID; @@ -47,6 +64,7 @@ class Pathfinder Ledger::pointer mLedger; PathState::pointer mPsDefault; LoadEvent::pointer mLoadMonitor; + RLCache::pointer mRLCache; boost::unordered_map mRLMap; boost::unordered_map, int> mPOMap; @@ -63,13 +81,11 @@ class Pathfinder bool matchesOrigin(const uint160& currency, const uint160& issuer); - AccountItems& getRippleLines(const uint160& accountID); - int getPathsOut(const uint160& currency, const uint160& accountID, bool isAuthRequired, bool isDestCurrency, const uint160& dest); public: - Pathfinder(Ledger::ref ledger, + Pathfinder(RLCache::ref cache, const RippleAddress& srcAccountID, const RippleAddress& dstAccountID, const uint160& srcCurrencyID, const uint160& srcIssuerID, const STAmount& dstAmount, bool& bValid); diff --git a/src/cpp/ripple/RPCErr.cpp b/src/cpp/ripple/RPCErr.cpp index 01a6d5a026..566b420ce8 100644 --- a/src/cpp/ripple/RPCErr.cpp +++ b/src/cpp/ripple/RPCErr.cpp @@ -54,6 +54,7 @@ Json::Value rpcError(int iError, Json::Value jvResult) { 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." }, + { rpcNO_PF_REQUEST, "noPathRequest", "No pathfinding request in progress." }, { rpcNOT_STANDALONE, "notStandAlone", "Operation valid in debug mode only." }, { rpcNOT_SUPPORTED, "notSupported", "Operation not supported." }, { rpcPASSWD_CHANGED, "passwdChanged", "Wrong key, password changed." }, diff --git a/src/cpp/ripple/RPCErr.h b/src/cpp/ripple/RPCErr.h index 39a2747ae3..fbfc9549fe 100644 --- a/src/cpp/ripple/RPCErr.h +++ b/src/cpp/ripple/RPCErr.h @@ -42,6 +42,7 @@ enum { // Malformed command rpcINVALID_PARAMS, rpcUNKNOWN_COMMAND, + rpcNO_PF_REQUEST, // Bad parameter rpcACT_BITCOIN, diff --git a/src/cpp/ripple/RPCHandler.cpp b/src/cpp/ripple/RPCHandler.cpp index a24e49cc3f..6988ffa602 100644 --- a/src/cpp/ripple/RPCHandler.cpp +++ b/src/cpp/ripple/RPCHandler.cpp @@ -22,6 +22,7 @@ #include "NicknameState.h" #include "InstanceCounter.h" #include "Offer.h" +#include "PFRequest.h" SETUP_LOG(); @@ -180,7 +181,8 @@ Json::Value RPCHandler::transactionSign(Json::Value jvRequest, bool bSubmit) { ScopedUnlock su(theApp->getMasterLock()); bool bValid; - Pathfinder pf(lSnapshot, raSrcAddressID, dstAccountID, + RLCache::pointer cache = boost::make_shared(lSnapshot); + Pathfinder pf(cache, raSrcAddressID, dstAccountID, saSendMax.getCurrency(), saSendMax.getIssuer(), saSend, bValid); if (!bValid || !pf.findPaths(theConfig.PATH_SEARCH_SIZE, 3, spsPaths)) @@ -1167,6 +1169,49 @@ Json::Value RPCHandler::doRandom(Json::Value jvRequest, int& cost, ScopedLock& M } } +Json::Value RPCHandler::doPathFind(Json::Value jvRequest, int& cost, ScopedLock& MasterLockHolder) +{ + if (!jvRequest.isMember("subcommand") || !jvRequest["subcommand"].isString()) + return rpcError(rpcINVALID_PARAMS); + + if (!mInfoSub) + return rpcError(rpcNO_EVENTS); + + std::string sSubCommand = jvRequest["subcommand"].asString(); + + if (sSubCommand == "create") + { + mInfoSub->clearPFRequest(); + PFRequest::pointer request = boost::make_shared(mInfoSub); + Json::Value result = request->doCreate(mNetOps->getClosedLedger(), jvRequest); + if (request->isValid()) + { + mInfoSub->setPFRequest(request); + theApp->getLedgerMaster().newPFRequest(); + } + return result; + } + + if (sSubCommand == "close") + { + PFRequest::pointer request = mInfoSub->getPFRequest(); + if (!request) + return rpcError(rpcNO_PF_REQUEST); + mInfoSub->clearPFRequest(); + return request->doClose(jvRequest); + } + + if (sSubCommand == "status") + { + PFRequest::pointer request = mInfoSub->getPFRequest(); + if (!request) + return rpcNO_PF_REQUEST; + return request->doStatus(jvRequest); + } + + return rpcError(rpcINVALID_PARAMS); +} + // TODO: // - Add support for specifying non-endpoint issuer. // - Return fully expanded path with proof. @@ -1268,6 +1313,7 @@ Json::Value RPCHandler::doRipplePathFind(Json::Value jvRequest, int& cost, Scope jvResult["destination_account"] = raDst.humanAccountID(); Json::Value jvArray(Json::arrayValue); + RLCache::pointer cache = boost::make_shared(lSnapShot); for (unsigned int i=0; i != jvSrcCurrencies.size(); ++i) { Json::Value jvSource = jvSrcCurrencies[i]; @@ -1303,7 +1349,7 @@ Json::Value RPCHandler::doRipplePathFind(Json::Value jvRequest, int& cost, Scope STPathSet spsComputed; bool bValid; - Pathfinder pf(lSnapShot, raSrc, raDst, uSrcCurrencyID, uSrcIssuerID, saDstAmount, bValid); + Pathfinder pf(cache, raSrc, raDst, uSrcCurrencyID, uSrcIssuerID, saDstAmount, bValid); if (!bValid || !pf.findPaths(theConfig.PATH_SEARCH_SIZE, 3, spsComputed)) { @@ -3293,6 +3339,7 @@ Json::Value RPCHandler::doCommand(const Json::Value& jvRequest, int iRole, int & // { "nickname_info", &RPCHandler::doNicknameInfo, false, optCurrent }, { "owner_info", &RPCHandler::doOwnerInfo, false, optCurrent }, { "peers", &RPCHandler::doPeers, true, optNone }, + { "path_find", &RPCHandler::doPathFind, false, optCurrent }, { "ping", &RPCHandler::doPing, false, optNone }, // { "profile", &RPCHandler::doProfile, false, optCurrent }, { "random", &RPCHandler::doRandom, false, optNone }, diff --git a/src/cpp/ripple/RPCHandler.h b/src/cpp/ripple/RPCHandler.h index 710ed12127..f5b73c39be 100644 --- a/src/cpp/ripple/RPCHandler.h +++ b/src/cpp/ripple/RPCHandler.h @@ -69,6 +69,7 @@ class RPCHandler Json::Value doNicknameInfo(Json::Value params, int& cost, ScopedLock& mlh); Json::Value doOwnerInfo(Json::Value params, int& cost, ScopedLock& mlh); Json::Value doPeers(Json::Value params, int& cost, ScopedLock& mlh); + Json::Value doPathFind(Json::Value params, int& cost, ScopedLock& mlh); Json::Value doPing(Json::Value params, int& cost, ScopedLock& mlh); Json::Value doProfile(Json::Value params, int& cost, ScopedLock& mlh); Json::Value doRandom(Json::Value jvRequest, int& cost, ScopedLock& mlh);