#include "Pathfinder.h" #include "Application.h" #include "AccountItems.h" #include "Log.h" #include SETUP_LOG(); /* JED: V IIII we just need to find a succession of the highest quality paths there until we find enough width Don't do branching within each path We have a list of paths we are working on but how do we compare the ones that are terminating in a different currency? Loops TODO: what is a good way to come up with multiple paths? Maybe just change the sort criteria? first a low cost one and then a fat short one? OrderDB: getXRPOffers(); // return list of all orderbooks that want XRP // return list of all orderbooks that want IssuerID // return list of all orderbooks that want this issuerID and currencyID */ /* Test sending to XRP Test XRP to XRP Test offer in middle Test XRP to USD Test USD to EUR */ // we sort the options by: // cost of path // length of path // width of path // correct currency at the end #if 0 bool sortPathOptions(PathOption::pointer first, PathOption::pointer second) { if (first->mTotalCostmTotalCost) return(true); if (first->mTotalCost>second->mTotalCost) return(false); if (first->mCorrectCurrency && !second->mCorrectCurrency) return(true); if (!first->mCorrectCurrency && second->mCorrectCurrency) return(false); if (first->mPath.size()mPath.size()) return(true); if (first->mPath.size()>second->mPath.size()) return(false); if (first->mMinWidthmMinWidth) return true; return false; } PathOption::PathOption(uint160& srcAccount,uint160& srcCurrencyID,const uint160& dstCurrencyID) { mCurrentAccount=srcAccount; mCurrencyID=srcCurrencyID; mCorrectCurrency=(srcCurrencyID==dstCurrencyID); mQuality=0; mMinWidth=STAmount(dstCurrencyID,99999,80); // this will get lowered when we convert back to the correct currency } PathOption::PathOption(PathOption::pointer other) { // TODO: } #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. // Pathfinder::Pathfinder(const RippleAddress& srcAccountID, const RippleAddress& dstAccountID, const uint160& srcCurrencyID, const uint160& srcIssuerID, const STAmount& dstAmount) : mSrcAccountID(srcAccountID.getAccountID()), mDstAccountID(dstAccountID.getAccountID()), mDstAmount(dstAmount), mSrcCurrencyID(srcCurrencyID), mSrcIssuerID(srcIssuerID), mOrderBook(theApp->getLedgerMaster().getCurrentLedger()) { mLedger=theApp->getLedgerMaster().getCurrentLedger(); } // 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 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, mSrcCurrencyID, uint160()); // XXX Might add source issuer. STPath path; path.addElement(ele); // Add the source. pqueue.push(path); while (pqueue.size()) { STPath path = pqueue.front(); pqueue.pop(); // Pop the first path from the queue. 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()) { // Which is dst currency. // Remove implied first. path.mPath.erase(path.mPath.begin()); if (path.size()) { // There is an actual path element. 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 account. && ele.mCurrencyID == mDstAmount.getCurrency()) { // With correct output currency. // Found a path to the destination. 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. path.mPath.erase(path.mPath.begin()); path.mPath.erase(path.mPath.begin() + path.mPath.size()-1); // 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(); 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. // Create new paths for each outbound account not already in the path. AccountItems rippleLines(ele.mAccountID, AccountItem::pointer(new RippleState())); BOOST_FOREACH(AccountItem::pointer item, rippleLines.getItems()) { RippleState* line=(RippleState*)item.get(); if (!path.hasSeen(line->getAccountIDPeer().getAccountID())) { STPath new_path(path); STPathElement new_ele(line->getAccountIDPeer().getAccountID(), 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; } } // Every book that wants the source currency. std::vector books; mOrderBook.getBooks(path.mCurrentAccount, path.mCurrencyID, books); BOOST_FOREACH(OrderBook::pointer book,books) { 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; } } tLog(!bContinued, lsDEBUG) << boost::str(boost::format("findPaths: non-XRP input - dead end")); } } else { cLog(lsWARNING) << boost::str(boost::format("findPaths: no ledger")); } return bFound; } #if 0 bool Pathfinder::checkComplete(STPathSet& retPathSet) { if (mCompletePaths.size()) { // TODO: look through these and pick the most promising int count=0; BOOST_FOREACH(PathOption::pointer pathOption,mCompletePaths) { retPathSet.addPath(pathOption->mPath); count++; if(count>2) return(true); } return(true); } return(false); } // get all the options from this accountID // if source is XRP // every offer that wants XRP // else // every ripple line that starts with the source currency // every offer that we can take that wants the source currency void Pathfinder::addOptions(PathOption::pointer tail) { if (!tail->mCurrencyID) { // source XRP BOOST_FOREACH(OrderBook::pointer book, mOrderBook.getXRPInBooks()) { PathOption::pointer pathOption(new PathOption(tail)); STPathElement ele(uint160(), book->getCurrencyOut(), book->getIssuerOut()); pathOption->mPath.addElement(ele); pathOption->mCurrentAccount=book->getIssuerOut(); pathOption->mCurrencyID=book->getCurrencyOut(); addPathOption(pathOption); } } else { // ripple RippleLines rippleLines(tail->mCurrentAccount); BOOST_FOREACH(RippleState::pointer line,rippleLines.getLines()) { // TODO: make sure we can move in the correct direction STAmount balance=line->getBalance(); if(balance.getCurrency()==tail->mCurrencyID) { // we have a ripple line from the tail to somewhere else PathOption::pointer pathOption(new PathOption(tail)); STPathElement ele(line->getAccountIDPeer().getAccountID(), uint160(), uint160()); pathOption->mPath.addElement(ele); pathOption->mCurrentAccount=line->getAccountIDPeer().getAccountID(); addPathOption(pathOption); } } // every offer that wants the source currency std::vector books; mOrderBook.getBooks(tail->mCurrentAccount, tail->mCurrencyID, books); BOOST_FOREACH(OrderBook::pointer book,books) { PathOption::pointer pathOption(new PathOption(tail)); STPathElement ele(uint160(), book->getCurrencyOut(), book->getIssuerOut()); pathOption->mPath.addElement(ele); pathOption->mCurrentAccount=book->getIssuerOut(); pathOption->mCurrencyID=book->getCurrencyOut(); addPathOption(pathOption); } } } void Pathfinder::addPathOption(PathOption::pointer pathOption) { if(pathOption->mCurrencyID==mDstAmount.getCurrency()) { pathOption->mCorrectCurrency=true; if(pathOption->mCurrentAccount==mDstAccountID) { // this path is complete mCompletePaths.push_back(pathOption); }else mBuildingPaths.push_back(pathOption); } else { pathOption->mCorrectCurrency=false; mBuildingPaths.push_back(pathOption); } } #endif // vim:ts=4