mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
pathfinding
This commit is contained in:
19
src/OrderBook.cpp
Normal file
19
src/OrderBook.cpp
Normal 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
33
src/OrderBook.h
Normal 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
56
src/OrderBookDB.cpp
Normal 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
30
src/OrderBookDB.h
Normal 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
202
src/Pathfinder.cpp
Normal 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
52
src/Pathfinder.h
Normal 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);
|
||||
};
|
||||
Reference in New Issue
Block a user