diff --git a/src/OrderBook.cpp b/src/OrderBook.cpp new file mode 100644 index 0000000000..e3e41f4a10 --- /dev/null +++ b/src/OrderBook.cpp @@ -0,0 +1,19 @@ +#include "OrderBook.h" +#include "Ledger.h" + +OrderBook::pointer OrderBook::newOrderBook(SerializedLedgerEntry::pointer ledgerEntry) +{ + if(ledgerEntry->getType() != ltOFFER) return( OrderBook::pointer()); + + return( OrderBook::pointer(new OrderBook(ledgerEntry))); +} + +OrderBook::OrderBook(SerializedLedgerEntry::pointer ledgerEntry) +{ + mCurrencyIn=ledgerEntry->getIValueFieldAmount(sfTakerGets).getCurrency(); + mCurrencyOut=ledgerEntry->getIValueFieldAmount(sfTakerPays).getCurrency(); + mIssuerIn=ledgerEntry->getIValueFieldAccount(sfGetsIssuer).getAccountID(); + mIssuerOut=ledgerEntry->getIValueFieldAccount(sfPaysIssuer).getAccountID(); + + mBookBase=Ledger::getBookBase(mCurrencyOut,mIssuerOut,mCurrencyIn,mIssuerIn); +} \ No newline at end of file diff --git a/src/OrderBook.h b/src/OrderBook.h new file mode 100644 index 0000000000..1d0262b278 --- /dev/null +++ b/src/OrderBook.h @@ -0,0 +1,33 @@ +#include "SerializedLedger.h" +#include +/* + Encapsulates the SLE for an orderbook +*/ +class OrderBook +{ + uint256 mBookBase; + + uint160 mCurrencyIn; + uint160 mCurrencyOut; + uint160 mIssuerIn; + uint160 mIssuerOut; + + //SerializedLedgerEntry::pointer mLedgerEntry; + OrderBook(SerializedLedgerEntry::pointer ledgerEntry); // For accounts in a ledger +public: + typedef boost::shared_ptr pointer; + + // returns NULL if ledgerEntry doesn't point to an orderbook + static OrderBook::pointer newOrderBook(SerializedLedgerEntry::pointer ledgerEntry); + + uint256& getBookBase(){ return(mBookBase); } + uint160& getCurrencyIn(){ return(mCurrencyIn); } + uint160& getCurrencyOut(){ return(mCurrencyOut); } + uint160& getIssuerIn(){ return(mIssuerIn); } + uint160& getIssuerOut(){ return(mIssuerOut); } + + // looks through the best offers to see how much it would cost to take the given amount + STAmount& getTakePrice(STAmount& takeAmount); + + +}; \ No newline at end of file diff --git a/src/OrderBookDB.cpp b/src/OrderBookDB.cpp new file mode 100644 index 0000000000..4f49f01338 --- /dev/null +++ b/src/OrderBookDB.cpp @@ -0,0 +1,56 @@ +#include "OrderBookDB.h" +#include "Log.h" +#include + + +// 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()) + { + SLE::pointer entry=ledger->getSLE(currentIndex); + + OrderBook::pointer book=OrderBook::newOrderBook(entry); + if(book) + { + if( mKnownMap.find(book->getBookBase()) != mKnownMap.end() ) + { + mKnownMap[book->getBookBase()]=true; + + if(!book->getCurrencyIn()) + { // XNS + mXNSOrders.push_back(book); + }else + { + mIssuerMap[book->getIssuerIn()].push_back(book); + } + } + } + + currentIndex=ledger->getNextLedgerIndex(currentIndex); + } +} + +// 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 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() ) + { + BOOST_FOREACH(OrderBook::pointer book, mIssuerMap[issuerID]) + { + if(book->getCurrencyIn()==currencyID) + { + bookRet.push_back(book); + } + } + } +} \ No newline at end of file diff --git a/src/OrderBookDB.h b/src/OrderBookDB.h new file mode 100644 index 0000000000..c8e598d186 --- /dev/null +++ b/src/OrderBookDB.h @@ -0,0 +1,30 @@ +#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 +*/ + +class OrderBookDB +{ + std::vector mEmptyVector; + std::vector mXNSOrders; + std::map > mIssuerMap; + + std::map mKnownMap; + +public: + OrderBookDB(Ledger::pointer ledger); + + // return list of all orderbooks that want XNS + std::vector& getXNSInBooks(){ return mXNSOrders; } + // 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); + +}; \ No newline at end of file diff --git a/src/Pathfinder.cpp b/src/Pathfinder.cpp new file mode 100644 index 0000000000..c1c5d86b30 --- /dev/null +++ b/src/Pathfinder.cpp @@ -0,0 +1,202 @@ +#include "Pathfinder.h" +#include "Application.h" +#include "RippleLines.h" +#include "Log.h" +#include + +/* +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: + getXNSOffers(); + + // return list of all orderbooks that want XNS + // return list of all orderbooks that want IssuerID + // return list of all orderbooks that want this issuerID and currencyID +*/ + +/* +Test sending to XNS +Test XNS to XNS +Test offer in middle +Test XNS 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 + + + +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.getElementCount()mPath.getElementCount()) return(true); + if(first->mPath.getElementCount()>second->mPath.getElementCount()) 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: +} + + +Pathfinder::Pathfinder(NewcoinAddress& srcAccountID, NewcoinAddress& dstAccountID, uint160& srcCurrencyID, STAmount dstAmount) : + mSrcAccountID(srcAccountID.getAccountID()) , mDstAccountID(dstAccountID.getAccountID()), mSrcCurrencyID(srcCurrencyID) , mDstAmount(dstAmount), mOrderBook(theApp->getMasterLedger().getCurrentLedger()) +{ + mLedger=theApp->getMasterLedger().getCurrentLedger(); +} + +bool Pathfinder::findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet) +{ + if(mLedger) + { + PathOption::pointer head(new PathOption(mSrcAccountID,mSrcCurrencyID,mDstAmount.getCurrency())); + addOptions(head); + + for(int n=0; n tempPaths=mBuildingPaths; + mBuildingPaths.clear(); + BOOST_FOREACH(PathOption::pointer path,tempPaths) + { + addOptions(path); + } + if(checkComplete(retPathSet)) return(true); + } + + } + + return(false); +} + +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 XNS +// every offer that wants XNS +// 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 XNS + BOOST_FOREACH(OrderBook::pointer book,mOrderBook.getXNSInBooks()) + { + 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); + } +} + + diff --git a/src/Pathfinder.h b/src/Pathfinder.h new file mode 100644 index 0000000000..5a7a4345a1 --- /dev/null +++ b/src/Pathfinder.h @@ -0,0 +1,52 @@ +#include "SerializedTypes.h" +#include "NewcoinAddress.h" +#include "OrderBookDB.h" +#include + +/* this is a very simple implementation. This can be made way better. +We are simply flooding from the start. And doing an exhaustive search of all paths under maxSearchSteps. An easy improvement would be to flood from both directions +*/ +class PathOption +{ +public: + typedef boost::shared_ptr pointer; + + STPath mPath; + bool mCorrectCurrency; // for the sorting + uint160 mCurrencyID; // what currency we currently have at the end of the path + uint160 mCurrentAccount; // what account is at the end of the path + int mTotalCost; // in send currency + STAmount mMinWidth; // in dest currency + float mQuality; + + PathOption(uint160& srcAccount,uint160& srcCurrencyID,const uint160& dstCurrencyID); + PathOption(PathOption::pointer other); +}; + +class Pathfinder +{ + uint160 mSrcAccountID; + uint160 mDstAccountID; + STAmount mDstAmount; + uint160 mSrcCurrencyID; + + OrderBookDB mOrderBook; + Ledger::pointer mLedger; + + + std::list mBuildingPaths; + std::list mCompletePaths; + + void addOptions(PathOption::pointer tail); + + // returns true if any building paths are now complete? + bool checkComplete(STPathSet& retPathSet); + + void addPathOption(PathOption::pointer pathOption); + +public: + Pathfinder(NewcoinAddress& srcAccountID, NewcoinAddress& dstAccountID, uint160& srcCurrencyID, STAmount dstAmount); + + // returns false if there is no path. otherwise fills out retPath + bool findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet); +}; \ No newline at end of file