pathfinding

This commit is contained in:
jed
2012-08-23 14:01:39 -07:00
parent cedb9d08fa
commit 27490e1f0b
6 changed files with 392 additions and 0 deletions

19
src/OrderBook.cpp Normal file
View File

@@ -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);
}

33
src/OrderBook.h Normal file
View File

@@ -0,0 +1,33 @@
#include "SerializedLedger.h"
#include <boost/shared_ptr.hpp>
/*
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<OrderBook> 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);
};

56
src/OrderBookDB.cpp Normal file
View File

@@ -0,0 +1,56 @@
#include "OrderBookDB.h"
#include "Log.h"
#include <boost/foreach.hpp>
// 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<OrderBook::pointer>& 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<OrderBook::pointer>& bookRet)
{
if( mIssuerMap.find(issuerID) == mIssuerMap.end() )
{
BOOST_FOREACH(OrderBook::pointer book, mIssuerMap[issuerID])
{
if(book->getCurrencyIn()==currencyID)
{
bookRet.push_back(book);
}
}
}
}

30
src/OrderBookDB.h Normal file
View File

@@ -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<OrderBook::pointer> mEmptyVector;
std::vector<OrderBook::pointer> mXNSOrders;
std::map<uint160, std::vector<OrderBook::pointer> > mIssuerMap;
std::map<uint256, bool > mKnownMap;
public:
OrderBookDB(Ledger::pointer ledger);
// return list of all orderbooks that want XNS
std::vector<OrderBook::pointer>& getXNSInBooks(){ return mXNSOrders; }
// return list of all orderbooks that want IssuerID
std::vector<OrderBook::pointer>& 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<OrderBook::pointer>& bookRet);
// returns the best rate we can find
float getPrice(uint160& currencyIn,uint160& currencyOut);
};

202
src/Pathfinder.cpp Normal file
View File

@@ -0,0 +1,202 @@
#include "Pathfinder.h"
#include "Application.h"
#include "RippleLines.h"
#include "Log.h"
#include <boost/foreach.hpp>
/*
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->mTotalCost<second->mTotalCost) 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()<second->mPath.getElementCount()) return(true);
if(first->mPath.getElementCount()>second->mPath.getElementCount()) return(false);
if(first->mMinWidth<second->mMinWidth) 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<maxSearchSteps; n++)
{
std::list<PathOption::pointer> 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<OrderBook::pointer> 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);
}
}

52
src/Pathfinder.h Normal file
View File

@@ -0,0 +1,52 @@
#include "SerializedTypes.h"
#include "NewcoinAddress.h"
#include "OrderBookDB.h"
#include <boost/shared_ptr.hpp>
/* 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<PathOption> 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<PathOption::pointer> mBuildingPaths;
std::list<PathOption::pointer> 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);
};