mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-29 23:45:51 +00:00
Merge branch 'master' of github.com:jedmccaleb/NewCoin
This commit is contained in:
@@ -13,11 +13,11 @@ OrderBook::OrderBook(SerializedLedgerEntry::pointer ledgerEntry)
|
||||
const STAmount saTakerGets = ledgerEntry->getFieldAmount(sfTakerGets);
|
||||
const STAmount saTakerPays = ledgerEntry->getFieldAmount(sfTakerPays);
|
||||
|
||||
mCurrencyIn = saTakerGets.getCurrency();
|
||||
mCurrencyOut = saTakerPays.getCurrency();
|
||||
mIssuerIn = saTakerGets.getIssuer();
|
||||
mIssuerOut = saTakerPays.getIssuer();
|
||||
mCurrencyIn = saTakerPays.getCurrency();
|
||||
mCurrencyOut = saTakerGets.getCurrency();
|
||||
mIssuerIn = saTakerPays.getIssuer();
|
||||
mIssuerOut = saTakerGets.getIssuer();
|
||||
|
||||
mBookBase=Ledger::getBookBase(mCurrencyOut,mIssuerOut,mCurrencyIn,mIssuerIn);
|
||||
mBookBase=Ledger::getBookBase(mCurrencyIn, mIssuerIn, mCurrencyOut, mIssuerOut);
|
||||
}
|
||||
// vim:ts=4
|
||||
|
||||
@@ -1,28 +1,42 @@
|
||||
#include "OrderBookDB.h"
|
||||
#include "Log.h"
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include "OrderBookDB.h"
|
||||
#include "Log.h"
|
||||
|
||||
SETUP_LOG();
|
||||
|
||||
// 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())
|
||||
uint256 currentIndex = ledger->getFirstLedgerIndex();
|
||||
|
||||
cLog(lsDEBUG) << "OrderBookDB>";
|
||||
|
||||
while (currentIndex.isNonZero())
|
||||
{
|
||||
SLE::pointer entry=ledger->getSLE(currentIndex);
|
||||
|
||||
OrderBook::pointer book=OrderBook::newOrderBook(entry);
|
||||
if(book)
|
||||
OrderBook::pointer book = OrderBook::newOrderBook(entry);
|
||||
if (book)
|
||||
{
|
||||
if( mKnownMap.find(book->getBookBase()) != mKnownMap.end() )
|
||||
{
|
||||
mKnownMap[book->getBookBase()]=true;
|
||||
cLog(lsDEBUG) << "OrderBookDB: found book";
|
||||
|
||||
if(!book->getCurrencyIn())
|
||||
{ // XRP
|
||||
if (mKnownMap.find(book->getBookBase()) == mKnownMap.end())
|
||||
{
|
||||
mKnownMap[book->getBookBase()] = true;
|
||||
|
||||
cLog(lsDEBUG) << "OrderBookDB: unknown book in: "
|
||||
<< STAmount::createHumanCurrency(book->getCurrencyIn())
|
||||
<< " -> "
|
||||
<< STAmount::createHumanCurrency(book->getCurrencyOut());
|
||||
|
||||
if (!book->getCurrencyIn())
|
||||
{
|
||||
// XRP
|
||||
mXRPOrders.push_back(book);
|
||||
}else
|
||||
}
|
||||
else
|
||||
{
|
||||
mIssuerMap[book->getIssuerIn()].push_back(book);
|
||||
}
|
||||
@@ -31,26 +45,29 @@ OrderBookDB::OrderBookDB(Ledger::pointer ledger)
|
||||
|
||||
currentIndex=ledger->getNextLedgerIndex(currentIndex);
|
||||
}
|
||||
|
||||
cLog(lsDEBUG) << "OrderBookDB<";
|
||||
}
|
||||
|
||||
// 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 mIssuerMap.find(issuerID) == mIssuerMap.end()
|
||||
? mEmptyVector
|
||||
: 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() )
|
||||
if (mIssuerMap.find(issuerID) == mIssuerMap.end())
|
||||
{
|
||||
BOOST_FOREACH(OrderBook::ref book, mIssuerMap[issuerID])
|
||||
{
|
||||
if(book->getCurrencyIn()==currencyID)
|
||||
{
|
||||
if (book->getCurrencyIn() == currencyID)
|
||||
bookRet.push_back(book);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// vim:ts=4
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#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
|
||||
*/
|
||||
//
|
||||
// XXX 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
|
||||
{
|
||||
@@ -19,14 +19,15 @@ public:
|
||||
|
||||
// return list of all orderbooks that want XRP
|
||||
std::vector<OrderBook::pointer>& getXRPInBooks(){ return mXRPOrders; }
|
||||
|
||||
// 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);
|
||||
|
||||
};
|
||||
|
||||
// vim:ts=4
|
||||
|
||||
@@ -113,13 +113,14 @@ bool Pathfinder::bDefaultPath(const STPath& spPath)
|
||||
bool bDefault;
|
||||
LedgerEntrySet lesActive(mLedger);
|
||||
|
||||
// Expand the current path.
|
||||
pspCurrent->setExpanded(lesActive, spPath, mDstAccountID, mSrcAccountID);
|
||||
|
||||
// Determine if expanded current path is the default.
|
||||
// When path is a default (implied). Don't need to add it to return set.
|
||||
bDefault = pspCurrent->vpnNodes == mPsDefault->vpnNodes;
|
||||
|
||||
cLog(lsDEBUG) << "findPaths: expanded path: " << pspCurrent->getJson();
|
||||
|
||||
// Path is a default (implied). Don't need to add it to return set.
|
||||
cLog(lsDEBUG) << "findPaths: default path: indirect: " << spPath.getJson(0);
|
||||
|
||||
return bDefault;
|
||||
@@ -128,12 +129,13 @@ bool Pathfinder::bDefaultPath(const STPath& spPath)
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// 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& uSrcAccountID, const RippleAddress& uDstAccountID, const uint160& uSrcCurrencyID, const uint160& uSrcIssuerID, const STAmount& saDstAmount)
|
||||
: mSrcAccountID(uSrcAccountID.getAccountID()), mDstAccountID(uDstAccountID.getAccountID()), mDstAmount(saDstAmount), mSrcCurrencyID(uSrcCurrencyID), mSrcIssuerID(uSrcIssuerID), mOrderBook(theApp->getLedgerMaster().getCurrentLedger())
|
||||
: mSrcAccountID(uSrcAccountID.getAccountID()),
|
||||
mDstAccountID(uDstAccountID.getAccountID()),
|
||||
mDstAmount(saDstAmount),
|
||||
mSrcCurrencyID(uSrcCurrencyID),
|
||||
mSrcIssuerID(uSrcIssuerID),
|
||||
mOrderBook(theApp->getLedgerMaster().getCurrentLedger())
|
||||
{
|
||||
mLedger = theApp->getLedgerMaster().getCurrentLedger();
|
||||
mSrcAmount = STAmount(uSrcCurrencyID, uSrcIssuerID, 1, 0, true); // -1/uSrcIssuerID/uSrcIssuerID
|
||||
@@ -144,18 +146,24 @@ Pathfinder::Pathfinder(const RippleAddress& uSrcAccountID, const RippleAddress&
|
||||
|
||||
if (psDefault)
|
||||
{
|
||||
// Build the default path.
|
||||
// Later, reject anything that expands to the default path as the default is sufficient.
|
||||
|
||||
LedgerEntrySet lesActive(mLedger);
|
||||
|
||||
psDefault->setExpanded(lesActive, STPath(), mDstAccountID, mSrcAccountID);
|
||||
|
||||
if (tesSUCCESS == psDefault->terStatus)
|
||||
{
|
||||
cLog(lsDEBUG) << "Pathfinder: reference path: " << psDefault->getJson();
|
||||
// The default path works, remember it.
|
||||
cLog(lsDEBUG) << "Pathfinder: default path: " << psDefault->getJson();
|
||||
|
||||
mPsDefault = psDefault;
|
||||
}
|
||||
else
|
||||
{
|
||||
cLog(lsDEBUG) << "Pathfinder: reference path: NONE: " << transToken(psDefault->terStatus);
|
||||
// The default path doesn't work.
|
||||
cLog(lsDEBUG) << "Pathfinder: default path: NONE: " << transToken(psDefault->terStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,35 +198,57 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax
|
||||
{
|
||||
LedgerEntrySet lesActive(mLedger);
|
||||
std::vector<STPath> vspResults;
|
||||
std::queue<STPath> qspExplore;
|
||||
std::queue<STPath> qspExplore; // Path stubs to explore.
|
||||
|
||||
STPath spSeed;
|
||||
bool bForcedIssuer = !!mSrcCurrencyID && mSrcIssuerID != mSrcAccountID; // Source forced an issuer.
|
||||
|
||||
// The end is the cursor, start at the source account.
|
||||
STPathElement speEnd(mSrcAccountID,
|
||||
mSrcCurrencyID,
|
||||
uint160()); // XXX Might add source issuer.
|
||||
STPath path;
|
||||
mSrcCurrencyID,
|
||||
!!mSrcCurrencyID
|
||||
? mSrcAccountID // Non-XRP, start with self as issuer.
|
||||
: ACCOUNT_XRP);
|
||||
|
||||
path.addElement(speEnd); // Add the source.
|
||||
// Build a path of one element: the source.
|
||||
spSeed.addElement(speEnd);
|
||||
|
||||
qspExplore.push(path);
|
||||
if (bForcedIssuer)
|
||||
{
|
||||
// Add forced source issuer to seed, via issuer's account.
|
||||
STPathElement speIssuer(mSrcIssuerID, mSrcCurrencyID, mSrcIssuerID);
|
||||
|
||||
while (qspExplore.size()) {
|
||||
spSeed.addElement(speEnd);
|
||||
}
|
||||
|
||||
// Push the seed path to explore.
|
||||
qspExplore.push(spSeed);
|
||||
|
||||
while (qspExplore.size()) { // Have paths to explore?
|
||||
STPath spPath = qspExplore.front();
|
||||
|
||||
qspExplore.pop(); // Pop the first path from the queue.
|
||||
|
||||
speEnd = spPath.mPath.back(); // Get the last node from the path.
|
||||
|
||||
// Done, if dest wants XRP and last element produces XRP.
|
||||
if (!speEnd.mCurrencyID // Tail output is XRP.
|
||||
&& !mDstAmount.getCurrency()) // Which is dst currency.
|
||||
{
|
||||
// Remove implied first.
|
||||
// Done, cursor produces XRP and dest wants XRP.
|
||||
|
||||
// Remove implied source.
|
||||
spPath.mPath.erase(spPath.mPath.begin());
|
||||
|
||||
if (bForcedIssuer)
|
||||
{
|
||||
// Remove implied source issuer.
|
||||
spPath.mPath.erase(spPath.mPath.begin());
|
||||
}
|
||||
|
||||
if (spPath.size())
|
||||
{
|
||||
// There is an actual path element.
|
||||
cLog(lsDEBUG) << "findPaths: adding: " << spPath.getJson(0);
|
||||
cLog(lsDEBUG) << "findPaths: adding path: " << spPath.getJson(0);
|
||||
|
||||
vspResults.push_back(spPath); // Potential result.
|
||||
}
|
||||
@@ -230,71 +260,100 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax
|
||||
continue;
|
||||
}
|
||||
|
||||
// Done, if dest wants non-XRP and last element is dest.
|
||||
cLog(lsDEBUG) << "findPaths: finish? account: " << (speEnd.mAccountID == mDstAccountID);
|
||||
cLog(lsDEBUG) << "findPaths: finish? currency: " << (speEnd.mCurrencyID == mDstAmount.getCurrency());
|
||||
cLog(lsDEBUG) << "findPaths: finish? issuer: "
|
||||
<< RippleAddress::createHumanAccountID(speEnd.mIssuerID)
|
||||
<< " / "
|
||||
<< RippleAddress::createHumanAccountID(mDstAmount.getIssuer())
|
||||
<< " / "
|
||||
<< RippleAddress::createHumanAccountID(mDstAccountID);
|
||||
cLog(lsDEBUG) << "findPaths: finish? issuer is desired: " << (speEnd.mIssuerID == mDstAmount.getIssuer());
|
||||
|
||||
// YYY Allows going through self. Is this wanted?
|
||||
if (speEnd.mAccountID == mDstAccountID // Tail is destination account.
|
||||
&& speEnd.mCurrencyID == mDstAmount.getCurrency()) // With correct output currency.
|
||||
&& speEnd.mCurrencyID == mDstAmount.getCurrency() // With correct output currency.
|
||||
&& ( speEnd.mIssuerID == mDstAccountID // Dest always accepts own issuer.
|
||||
|| mDstAmount.getIssuer() == mDstAccountID // Any issuer is good.
|
||||
|| mDstAmount.getIssuer() == speEnd.mIssuerID)) // The desired issuer.
|
||||
{
|
||||
// Found a path to the destination.
|
||||
// Done, found a path to the destination.
|
||||
// Cursor on the dest account with correct currency and issuer.
|
||||
|
||||
if (bDefaultPath(spPath)) {
|
||||
cLog(lsDEBUG) << "findPaths: default path: dropping: " << spPath.getJson(0);
|
||||
cLog(lsDEBUG) << "findPaths: dropping: default path: " << spPath.getJson(0);
|
||||
|
||||
bFound = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove implied first and last nodes.
|
||||
// Remove implied nodes.
|
||||
|
||||
spPath.mPath.erase(spPath.mPath.begin());
|
||||
|
||||
if (bForcedIssuer)
|
||||
{
|
||||
// Remove implied source issuer.
|
||||
spPath.mPath.erase(spPath.mPath.begin());
|
||||
}
|
||||
spPath.mPath.erase(spPath.mPath.begin() + spPath.mPath.size()-1);
|
||||
|
||||
vspResults.push_back(spPath); // Potential result.
|
||||
|
||||
cLog(lsDEBUG) << "findPaths: adding: " << spPath.getJson(0);
|
||||
cLog(lsDEBUG) << "findPaths: adding path: " << spPath.getJson(0);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
bool bContinued = false;
|
||||
bool bContinued = false; // True, if wasn't a dead end.
|
||||
|
||||
cLog(lsDEBUG) <<
|
||||
boost::str(boost::format("findPaths: cursor: %s - %s/%s")
|
||||
% RippleAddress::createHumanAccountID(speEnd.mAccountID)
|
||||
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
|
||||
% RippleAddress::createHumanAccountID(speEnd.mIssuerID));
|
||||
|
||||
if (spPath.mPath.size() == iMaxSteps)
|
||||
{
|
||||
// Path is at maximum size. Don't want to add more.
|
||||
|
||||
cLog(lsDEBUG)
|
||||
<< boost::str(boost::format("findPaths: path would exceed max steps: dropping"));
|
||||
<< boost::str(boost::format("findPaths: dropping: path would exceed max steps"));
|
||||
|
||||
continue;
|
||||
}
|
||||
else if (!speEnd.mCurrencyID)
|
||||
{
|
||||
// Last element is for XRP, continue with qualifying books.
|
||||
// Cursor is for XRP, continue with qualifying books: XRP -> non-XRP
|
||||
|
||||
BOOST_FOREACH(OrderBook::ref book, mOrderBook.getXRPInBooks())
|
||||
{
|
||||
// XXX Don't allow looping through same order books.
|
||||
// New end is an order book with the currency and issuer.
|
||||
|
||||
//if (!path.hasSeen(line->getAccountIDPeer().getAccountID()))
|
||||
// Don't allow looping through same order books.
|
||||
if (!spPath.hasSeen(ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut()))
|
||||
{
|
||||
STPath new_path(spPath);
|
||||
STPathElement new_ele(uint160(), book->getCurrencyOut(), book->getIssuerOut());
|
||||
STPath spNew(spPath);
|
||||
STPathElement speBook(ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut());
|
||||
STPathElement speAccount(book->getIssuerOut(), book->getCurrencyOut(), book->getIssuerOut());
|
||||
|
||||
new_path.mPath.push_back(new_ele);
|
||||
new_path.mCurrencyID = book->getCurrencyOut();
|
||||
new_path.mCurrentAccount = book->getCurrencyOut();
|
||||
spNew.mPath.push_back(speBook); // Add the order book.
|
||||
spNew.mPath.push_back(speAccount); // Add the account and currency
|
||||
|
||||
cLog(lsDEBUG) <<
|
||||
boost::str(boost::format("findPaths: XRP input - %s/%s")
|
||||
% STAmount::createHumanCurrency(new_path.mCurrencyID)
|
||||
% RippleAddress::createHumanAccountID(new_path.mCurrentAccount));
|
||||
boost::str(boost::format("findPaths: XRP -> %s/%s")
|
||||
% STAmount::createHumanCurrency(speBook.mCurrencyID)
|
||||
% RippleAddress::createHumanAccountID(speBook.mIssuerID));
|
||||
|
||||
qspExplore.push(new_path);
|
||||
qspExplore.push(spNew);
|
||||
|
||||
bContinued = true;
|
||||
}
|
||||
}
|
||||
|
||||
tLog(!bContinued, lsDEBUG)
|
||||
<< boost::str(boost::format("findPaths: XRP input - dead end"));
|
||||
<< boost::str(boost::format("findPaths: XRP -> dead end"));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -303,20 +362,26 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax
|
||||
// Create new paths for each outbound account not already in the path.
|
||||
AccountItems rippleLines(speEnd.mAccountID, mLedger, AccountItem::pointer(new RippleState()));
|
||||
SLE::pointer sleSrc = lesActive.entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(speEnd.mAccountID));
|
||||
|
||||
tLog(sleSrc, lsDEBUG)
|
||||
<< boost::str(boost::format("findPaths: account without root: %s")
|
||||
% RippleAddress::createHumanAccountID(speEnd.mAccountID));
|
||||
|
||||
bool bRequireAuth = isSetBit(sleSrc->getFieldU32(sfFlags), lsfRequireAuth);
|
||||
|
||||
BOOST_FOREACH(AccountItem::ref item, rippleLines.getItems())
|
||||
{
|
||||
RippleState* rspEntry = (RippleState*) item.get();
|
||||
RippleState* rspEntry = (RippleState*) item.get();
|
||||
const uint160 uPeerID = rspEntry->getAccountIDPeer().getAccountID();
|
||||
|
||||
if (spPath.hasSeen(rspEntry->getAccountIDPeer().getAccountID()))
|
||||
if (spPath.hasSeen(uPeerID, speEnd.mCurrencyID, uPeerID))
|
||||
{
|
||||
// Peer is in path already. Ignore it to avoid a loop.
|
||||
cLog(lsDEBUG) <<
|
||||
boost::str(boost::format("findPaths: SEEN: %s/%s --> %s/%s")
|
||||
boost::str(boost::format("findPaths: SEEN: %s/%s -> %s/%s")
|
||||
% RippleAddress::createHumanAccountID(speEnd.mAccountID)
|
||||
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
|
||||
% RippleAddress::createHumanAccountID(rspEntry->getAccountIDPeer().getAccountID())
|
||||
% RippleAddress::createHumanAccountID(uPeerID)
|
||||
% STAmount::createHumanCurrency(speEnd.mCurrencyID));
|
||||
}
|
||||
else if (!rspEntry->getBalance().isPositive() // No IOUs to send.
|
||||
@@ -326,62 +391,61 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax
|
||||
{
|
||||
// Path has no credit left. Ignore it.
|
||||
cLog(lsDEBUG) <<
|
||||
boost::str(boost::format("findPaths: No credit: %s/%s --> %s/%s")
|
||||
boost::str(boost::format("findPaths: No credit: %s/%s -> %s/%s")
|
||||
% RippleAddress::createHumanAccountID(speEnd.mAccountID)
|
||||
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
|
||||
% RippleAddress::createHumanAccountID(rspEntry->getAccountIDPeer().getAccountID())
|
||||
% RippleAddress::createHumanAccountID(uPeerID)
|
||||
% STAmount::createHumanCurrency(speEnd.mCurrencyID));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Can transmit IOUs.
|
||||
STPath new_path(spPath);
|
||||
STPathElement new_ele(rspEntry->getAccountIDPeer().getAccountID(),
|
||||
speEnd.mCurrencyID,
|
||||
uint160());
|
||||
// Can transmit IOUs and account to the path.
|
||||
STPath spNew(spPath);
|
||||
STPathElement speNew(uPeerID, speEnd.mCurrencyID, uPeerID);
|
||||
|
||||
cLog(lsDEBUG) <<
|
||||
boost::str(boost::format("findPaths: %s/%s --> %s/%s")
|
||||
% RippleAddress::createHumanAccountID(speEnd.mAccountID)
|
||||
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
|
||||
% RippleAddress::createHumanAccountID(rspEntry->getAccountIDPeer().getAccountID())
|
||||
% STAmount::createHumanCurrency(speEnd.mCurrencyID));
|
||||
|
||||
new_path.mPath.push_back(new_ele);
|
||||
qspExplore.push(new_path);
|
||||
spNew.mPath.push_back(speNew);
|
||||
qspExplore.push(spNew);
|
||||
|
||||
bContinued = true;
|
||||
|
||||
cLog(lsDEBUG) <<
|
||||
boost::str(boost::format("findPaths: push explore: %s/%s -> %s/%s")
|
||||
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
|
||||
% RippleAddress::createHumanAccountID(speEnd.mAccountID)
|
||||
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
|
||||
% RippleAddress::createHumanAccountID(uPeerID));
|
||||
}
|
||||
}
|
||||
|
||||
// Every book that wants the source currency.
|
||||
std::vector<OrderBook::pointer> books;
|
||||
|
||||
mOrderBook.getBooks(spPath.mCurrentAccount, spPath.mCurrencyID, books);
|
||||
mOrderBook.getBooks(speEnd.mIssuerID, speEnd.mCurrencyID, books);
|
||||
|
||||
BOOST_FOREACH(OrderBook::ref book,books)
|
||||
BOOST_FOREACH(OrderBook::ref book, books)
|
||||
{
|
||||
STPath new_path(spPath);
|
||||
STPathElement new_ele(uint160(), book->getCurrencyOut(), book->getIssuerOut());
|
||||
if (!spPath.hasSeen(ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut()))
|
||||
{
|
||||
// A book we haven't seen before. Add it.
|
||||
STPath spNew(spPath);
|
||||
STPathElement speBook(ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut());
|
||||
|
||||
cLog(lsDEBUG) <<
|
||||
boost::str(boost::format("findPaths: %s/%s :: %s/%s")
|
||||
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
|
||||
% RippleAddress::createHumanAccountID(speEnd.mAccountID)
|
||||
% STAmount::createHumanCurrency(book->getCurrencyOut())
|
||||
% RippleAddress::createHumanAccountID(book->getIssuerOut()));
|
||||
spNew.mPath.push_back(speBook);
|
||||
qspExplore.push(spNew);
|
||||
|
||||
new_path.mPath.push_back(new_ele);
|
||||
new_path.mCurrentAccount=book->getIssuerOut();
|
||||
new_path.mCurrencyID=book->getCurrencyOut();
|
||||
bContinued = true;
|
||||
|
||||
qspExplore.push(new_path);
|
||||
|
||||
bContinued = true;
|
||||
cLog(lsDEBUG) <<
|
||||
boost::str(boost::format("findPaths: push book: %s/%s -> %s/%s")
|
||||
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
|
||||
% RippleAddress::createHumanAccountID(speEnd.mIssuerID)
|
||||
% STAmount::createHumanCurrency(book->getCurrencyOut())
|
||||
% RippleAddress::createHumanAccountID(book->getIssuerOut()));
|
||||
}
|
||||
}
|
||||
|
||||
tLog(!bContinued, lsDEBUG)
|
||||
<< boost::str(boost::format("findPaths: non-XRP input - dead end"));
|
||||
<< boost::str(boost::format("findPaths: dropping: non-XRP -> dead end"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,20 +467,30 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax
|
||||
|
||||
spsPaths.addPath(spCurrent); // Just checking the current path.
|
||||
|
||||
TER terResult = RippleCalc::rippleCalc(
|
||||
lesActive,
|
||||
saMaxAmountAct,
|
||||
saDstAmountAct,
|
||||
vpsExpanded,
|
||||
mSrcAmount, // --> amount to send max.
|
||||
mDstAmount, // --> amount to deliver.
|
||||
mDstAccountID,
|
||||
mSrcAccountID,
|
||||
spsPaths,
|
||||
true, // --> bPartialPayment: Allow, it might contribute.
|
||||
false, // --> bLimitQuality: Assume normal transaction.
|
||||
true, // --> bNoRippleDirect: Providing the only path.
|
||||
true); // --> bStandAlone: Don't need to delete unfundeds.
|
||||
TER terResult;
|
||||
|
||||
try {
|
||||
terResult = RippleCalc::rippleCalc(
|
||||
lesActive,
|
||||
saMaxAmountAct,
|
||||
saDstAmountAct,
|
||||
vpsExpanded,
|
||||
mSrcAmount, // --> amount to send max.
|
||||
mDstAmount, // --> amount to deliver.
|
||||
mDstAccountID,
|
||||
mSrcAccountID,
|
||||
spsPaths,
|
||||
true, // --> bPartialPayment: Allow, it might contribute.
|
||||
false, // --> bLimitQuality: Assume normal transaction.
|
||||
true, // --> bNoRippleDirect: Providing the only path.
|
||||
true); // --> bStandAlone: Don't need to delete unfundeds.
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
cLog(lsINFO) << "findPaths: Caught throw: " << e.what();
|
||||
|
||||
terResult = tefEXCEPTION;
|
||||
}
|
||||
|
||||
if (tesSUCCESS == terResult)
|
||||
{
|
||||
|
||||
@@ -160,22 +160,31 @@ TER PaymentTransactor::doApply()
|
||||
STAmount saMaxAmountAct;
|
||||
STAmount saDstAmountAct;
|
||||
|
||||
terResult = isSetBit(mParams, tapOPEN_LEDGER) && spsPaths.size() > RIPPLE_PATHS_MAX
|
||||
? telBAD_PATH_COUNT // Too many paths for proposed ledger.
|
||||
: RippleCalc::rippleCalc(
|
||||
mEngine->getNodes(),
|
||||
saMaxAmountAct,
|
||||
saDstAmountAct,
|
||||
vpsExpanded,
|
||||
saMaxAmount,
|
||||
saDstAmount,
|
||||
uDstAccountID,
|
||||
mTxnAccountID,
|
||||
spsPaths,
|
||||
bPartialPayment,
|
||||
bLimitQuality,
|
||||
bNoRippleDirect, // Always compute for finalizing ledger.
|
||||
false); // Not standalone, delete unfundeds.
|
||||
try
|
||||
{
|
||||
terResult = isSetBit(mParams, tapOPEN_LEDGER) && spsPaths.size() > RIPPLE_PATHS_MAX
|
||||
? telBAD_PATH_COUNT // Too many paths for proposed ledger.
|
||||
: RippleCalc::rippleCalc(
|
||||
mEngine->getNodes(),
|
||||
saMaxAmountAct,
|
||||
saDstAmountAct,
|
||||
vpsExpanded,
|
||||
saMaxAmount,
|
||||
saDstAmount,
|
||||
uDstAccountID,
|
||||
mTxnAccountID,
|
||||
spsPaths,
|
||||
bPartialPayment,
|
||||
bLimitQuality,
|
||||
bNoRippleDirect, // Always compute for finalizing ledger.
|
||||
false); // Not standalone, delete unfundeds.
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
cLog(lsINFO) << "Payment: Caught throw: " << e.what();
|
||||
|
||||
terResult = tefEXCEPTION;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -967,7 +967,6 @@ TER RippleCalc::calcNodeDeliverRev(
|
||||
STAmount& saPrvDlvReq = pnPrv.saRevDeliver; // To be set.
|
||||
STAmount& saCurDlvFwd = pnCur.saFwdDeliver;
|
||||
|
||||
|
||||
uint256& uDirectTip = pnCur.uDirectTip;
|
||||
|
||||
uDirectTip = 0; // Restart book searching.
|
||||
@@ -1065,6 +1064,7 @@ TER RippleCalc::calcNodeDeliverRev(
|
||||
|
||||
// Compute portion of input needed to cover actual output.
|
||||
|
||||
// XXX This needs to round up!
|
||||
STAmount saInPassReq = STAmount::multiply(saOutPass, saOfrRate, saTakerPays);
|
||||
STAmount saInPassAct;
|
||||
|
||||
@@ -2281,11 +2281,10 @@ TER RippleCalc::calcNodeRev(const unsigned int uNode, PathState& psCur, const bo
|
||||
// Calculate the next increment of a path.
|
||||
// The increment is what can satisfy a portion or all of the requested output at the best quality.
|
||||
// <-- psCur.uQuality
|
||||
void RippleCalc::pathNext(PathState::ref psrCur, const int iPaths, const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent)
|
||||
void RippleCalc::pathNext(PathState::ref psrCur, const bool bMultiQuality, const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent)
|
||||
{
|
||||
// The next state is what is available in preference order.
|
||||
// This is calculated when referenced accounts changed.
|
||||
const bool bMultiQuality = iPaths == 1;
|
||||
const unsigned int uLast = psrCur->vpnNodes.size() - 1;
|
||||
|
||||
psrCur->bConsumed = false;
|
||||
@@ -2320,11 +2319,12 @@ void RippleCalc::pathNext(PathState::ref psrCur, const int iPaths, const LedgerE
|
||||
if (tesSUCCESS == psrCur->terStatus)
|
||||
{
|
||||
tLog(!psrCur->saInPass || !psrCur->saOutPass, lsDEBUG)
|
||||
<< boost::str(boost::format("pathNext: saOutPass=%s saInPass=%s")
|
||||
<< boost::str(boost::format("pathNext: Error calcNodeFwd reported success for nothing: saOutPass=%s saInPass=%s")
|
||||
% psrCur->saOutPass.getFullText()
|
||||
% psrCur->saInPass.getFullText());
|
||||
|
||||
assert(!!psrCur->saOutPass && !!psrCur->saInPass);
|
||||
if (!psrCur->saOutPass || !psrCur->saInPass)
|
||||
throw std::runtime_error("Made no progress.");
|
||||
|
||||
psrCur->uQuality = STAmount::getRate(psrCur->saOutPass, psrCur->saInPass); // Calculate relative quality.
|
||||
|
||||
@@ -2467,16 +2467,18 @@ int iPass = 0;
|
||||
int iBest = -1;
|
||||
const LedgerEntrySet lesCheckpoint = lesActive;
|
||||
int iDry = 0;
|
||||
bool bMultiQuality = false; // True, if ever computed multi-quality.
|
||||
|
||||
// Find the best path.
|
||||
BOOST_FOREACH(PathState::ref pspCur, vpsExpanded)
|
||||
{
|
||||
if (pspCur->uQuality)
|
||||
{
|
||||
pspCur->saInAct = saMaxAmountAct; // Update to current amount processed.
|
||||
bMultiQuality = 1 == vpsExpanded.size()-iDry, // Computing the only non-dry path, compute multi-quality.
|
||||
pspCur->saInAct = saMaxAmountAct; // Update to current amount processed.
|
||||
pspCur->saOutAct = saDstAmountAct;
|
||||
|
||||
rc.pathNext(pspCur, vpsExpanded.size(), lesCheckpoint, lesActive); // Compute increment.
|
||||
rc.pathNext(pspCur, bMultiQuality, lesCheckpoint, lesActive); // Compute increment.
|
||||
cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: AFTER: mIndex=%d uQuality=%d rate=%s")
|
||||
% pspCur->mIndex
|
||||
% pspCur->uQuality
|
||||
@@ -2543,7 +2545,7 @@ cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Summary: %d rate: %s qual
|
||||
saMaxAmountAct += pspBest->saInPass;
|
||||
saDstAmountAct += pspBest->saOutPass;
|
||||
|
||||
if (pspBest->bConsumed)
|
||||
if (pspBest->bConsumed || bMultiQuality)
|
||||
{
|
||||
++iDry;
|
||||
pspBest->uQuality = 0;
|
||||
|
||||
@@ -161,7 +161,7 @@ public:
|
||||
// If the transaction fails to meet some constraint, still need to delete unfunded offers.
|
||||
boost::unordered_set<uint256> musUnfundedFound; // Offers that were found unfunded.
|
||||
|
||||
void pathNext(PathState::ref psrCur, const int iPaths, const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent);
|
||||
void pathNext(PathState::ref psrCur, const bool bMultiQuality, const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent);
|
||||
TER calcNode(const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
|
||||
TER calcNodeRev(const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
|
||||
TER calcNodeFwd(const unsigned int uNode, PathState& psCur, const bool bMultiQuality);
|
||||
|
||||
@@ -415,16 +415,20 @@ bool STPathSet::isEquivalent(const SerializedType& t) const
|
||||
return v && (value == v->value);
|
||||
}
|
||||
|
||||
bool STPath::hasSeen(const uint160 &acct) {
|
||||
|
||||
for (int i = 0; i < mPath.size();i++) {
|
||||
bool STPath::hasSeen(const uint160 &uAccountId, const uint160& uCurrencyID, const uint160& uIssuerID)
|
||||
{
|
||||
for (int i = 0; i < mPath.size(); ++i) {
|
||||
STPathElement ele = getElement(i);
|
||||
if (ele.getAccountID() == acct)
|
||||
|
||||
if (ele.getAccountID() == uAccountId
|
||||
&& ele.getCurrency() == uCurrencyID
|
||||
&& ele.getIssuerID() == uIssuerID)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int STPath::getSerializeSize() const
|
||||
{
|
||||
int iBytes = 0;
|
||||
|
||||
@@ -623,13 +623,13 @@ public:
|
||||
const STPathElement& getElement(int offset) { return mPath[offset]; }
|
||||
void addElement(const STPathElement &e) { mPath.push_back(e); }
|
||||
void clear() { mPath.clear(); }
|
||||
bool hasSeen(const uint160 &acct);
|
||||
bool hasSeen(const uint160 &uAccountId, const uint160& uCurrencyID, const uint160& uIssuerID);
|
||||
int getSerializeSize() const;
|
||||
// std::string getText() const;
|
||||
Json::Value getJson(int) const;
|
||||
|
||||
uint160 mCurrencyID;
|
||||
uint160 mCurrentAccount; // what account is at the end of the path
|
||||
// uint160 mCurrencyID;
|
||||
// uint160 mCurrentAccount; // what account is at the end of the path
|
||||
|
||||
std::vector<STPathElement>::iterator begin() { return mPath.begin(); }
|
||||
std::vector<STPathElement>::iterator end() { return mPath.end(); }
|
||||
|
||||
@@ -12,9 +12,9 @@ require('../src/js/config').load(require('./config'));
|
||||
buster.testRunner.timeout = 5000;
|
||||
|
||||
buster.testCase("Basic Path finding", {
|
||||
// 'setUp' : testutils.build_setup({ verbose: true, no_server: true }),
|
||||
// 'setUp' : testutils.build_setup({ verbose: true }),
|
||||
'setUp' : testutils.build_setup(),
|
||||
// 'setUp' : testutils.build_setup({ verbose: true }),
|
||||
// 'setUp' : testutils.build_setup({ verbose: true, no_server: true }),
|
||||
'tearDown' : testutils.build_teardown(),
|
||||
|
||||
"no direct path, no intermediary -> no alternatives" :
|
||||
@@ -899,4 +899,243 @@ buster.testCase("Issues", {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
buster.testCase("Via offers", {
|
||||
'setUp' : testutils.build_setup(),
|
||||
// 'setUp' : testutils.build_setup({ verbose: true }),
|
||||
// 'setUp' : testutils.build_setup({ verbose: true, no_server: true }),
|
||||
'tearDown' : testutils.build_teardown(),
|
||||
|
||||
// XXX Triggers bad path expansion.
|
||||
"Via gateway" :
|
||||
// carol holds mtgoxAUD, sells mtgoxAUD for XRP
|
||||
// bob will hold mtgoxAUD
|
||||
// alice pays bob mtgoxAUD using XRP
|
||||
function (done) {
|
||||
var self = this;
|
||||
|
||||
async.waterfall([
|
||||
function (callback) {
|
||||
self.what = "Create accounts.";
|
||||
|
||||
testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "carol", "mtgox"], callback);
|
||||
},
|
||||
function (callback) {
|
||||
self.what = "Set transfer rate.";
|
||||
|
||||
self.remote.transaction()
|
||||
.account_set("mtgox")
|
||||
.transfer_rate(1005000000)
|
||||
.once('proposed', function (m) {
|
||||
// console.log("proposed: %s", JSON.stringify(m));
|
||||
callback(m.result !== 'tesSUCCESS');
|
||||
})
|
||||
.submit();
|
||||
},
|
||||
function (callback) {
|
||||
self.what = "Set credit limits.";
|
||||
|
||||
testutils.credit_limits(self.remote,
|
||||
{
|
||||
"bob" : [ "100/AUD/mtgox" ],
|
||||
"carol" : [ "100/AUD/mtgox" ],
|
||||
},
|
||||
callback);
|
||||
},
|
||||
function (callback) {
|
||||
self.what = "Distribute funds.";
|
||||
|
||||
testutils.payments(self.remote,
|
||||
{
|
||||
"mtgox" : "50/AUD/carol",
|
||||
},
|
||||
callback);
|
||||
},
|
||||
function (callback) {
|
||||
self.what = "Carol create offer.";
|
||||
|
||||
self.remote.transaction()
|
||||
.offer_create("carol", "50.0", "50/AUD/mtgox")
|
||||
.on('proposed', function (m) {
|
||||
// console.log("PROPOSED: offer_create: %s", JSON.stringify(m));
|
||||
callback(m.result !== 'tesSUCCESS');
|
||||
|
||||
seq_carol = m.tx_json.Sequence;
|
||||
})
|
||||
.submit();
|
||||
},
|
||||
function (callback) {
|
||||
self.what = "Alice sends bob 10/AUD/mtgox using XRP.";
|
||||
|
||||
// XXX Also try sending 10/AUX/bob
|
||||
self.remote.transaction()
|
||||
.payment("alice", "bob", "10/AUD/mtgox")
|
||||
.build_path(true)
|
||||
.send_max("100.0")
|
||||
.on('proposed', function (m) {
|
||||
// console.log("proposed: %s", JSON.stringify(m));
|
||||
|
||||
callback(m.result !== 'tesSUCCESS');
|
||||
})
|
||||
.submit();
|
||||
},
|
||||
function (callback) {
|
||||
self.what = "Verify balances.";
|
||||
|
||||
testutils.verify_balances(self.remote,
|
||||
{
|
||||
"bob" : "10/AUD/mtgox",
|
||||
"carol" : "39.95/AUD/mtgox",
|
||||
},
|
||||
callback);
|
||||
},
|
||||
// function (callback) {
|
||||
// self.what = "Display ledger";
|
||||
//
|
||||
// self.remote.request_ledger('current', true)
|
||||
// .on('success', function (m) {
|
||||
// console.log("Ledger: %s", JSON.stringify(m, undefined, 2));
|
||||
//
|
||||
// callback();
|
||||
// })
|
||||
// .request();
|
||||
// },
|
||||
// function (callback) {
|
||||
// self.what = "Find path from alice to bob";
|
||||
//
|
||||
// // 5. acct 1 sent a 25 usd iou to acct 2
|
||||
// self.remote.request_ripple_path_find("alice", "bob", "25/USD/bob",
|
||||
// [ { 'currency' : "USD" } ])
|
||||
// .on('success', function (m) {
|
||||
// // console.log("proposed: %s", JSON.stringify(m));
|
||||
//
|
||||
// // 0 alternatives.
|
||||
// buster.assert.equals(0, m.alternatives.length)
|
||||
//
|
||||
// callback();
|
||||
// })
|
||||
// .request();
|
||||
// },
|
||||
], function (error) {
|
||||
buster.refute(error, self.what);
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
"// Via gateway : FIX ME fails due to XRP rounding and not properly handling dry." :
|
||||
// carol holds mtgoxAUD, sells mtgoxAUD for XRP
|
||||
// bob will hold mtgoxAUD
|
||||
// alice pays bob mtgoxAUD using XRP
|
||||
function (done) {
|
||||
var self = this;
|
||||
|
||||
async.waterfall([
|
||||
function (callback) {
|
||||
self.what = "Create accounts.";
|
||||
|
||||
testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "carol", "mtgox"], callback);
|
||||
},
|
||||
function (callback) {
|
||||
self.what = "Set transfer rate.";
|
||||
|
||||
self.remote.transaction()
|
||||
.account_set("mtgox")
|
||||
.transfer_rate(1005000000)
|
||||
.once('proposed', function (m) {
|
||||
// console.log("proposed: %s", JSON.stringify(m));
|
||||
callback(m.result !== 'tesSUCCESS');
|
||||
})
|
||||
.submit();
|
||||
},
|
||||
function (callback) {
|
||||
self.what = "Set credit limits.";
|
||||
|
||||
testutils.credit_limits(self.remote,
|
||||
{
|
||||
"bob" : [ "100/AUD/mtgox" ],
|
||||
"carol" : [ "100/AUD/mtgox" ],
|
||||
},
|
||||
callback);
|
||||
},
|
||||
function (callback) {
|
||||
self.what = "Distribute funds.";
|
||||
|
||||
testutils.payments(self.remote,
|
||||
{
|
||||
"mtgox" : "50/AUD/carol",
|
||||
},
|
||||
callback);
|
||||
},
|
||||
function (callback) {
|
||||
self.what = "Carol create offer.";
|
||||
|
||||
self.remote.transaction()
|
||||
.offer_create("carol", "50", "50/AUD/mtgox")
|
||||
.on('proposed', function (m) {
|
||||
// console.log("PROPOSED: offer_create: %s", JSON.stringify(m));
|
||||
callback(m.result !== 'tesSUCCESS');
|
||||
|
||||
seq_carol = m.tx_json.Sequence;
|
||||
})
|
||||
.submit();
|
||||
},
|
||||
function (callback) {
|
||||
self.what = "Alice sends bob 10/AUD/mtgox using XRP.";
|
||||
|
||||
// XXX Also try sending 10/AUX/bob
|
||||
self.remote.transaction()
|
||||
.payment("alice", "bob", "10/AUD/mtgox")
|
||||
.build_path(true)
|
||||
.send_max("100")
|
||||
.on('proposed', function (m) {
|
||||
// console.log("proposed: %s", JSON.stringify(m));
|
||||
|
||||
callback(m.result !== 'tesSUCCESS');
|
||||
})
|
||||
.submit();
|
||||
},
|
||||
function (callback) {
|
||||
self.what = "Verify balances.";
|
||||
|
||||
testutils.verify_balances(self.remote,
|
||||
{
|
||||
"bob" : "10/AUD/mtgox",
|
||||
"carol" : "39.95/AUD/mtgox",
|
||||
},
|
||||
callback);
|
||||
},
|
||||
// function (callback) {
|
||||
// self.what = "Display ledger";
|
||||
//
|
||||
// self.remote.request_ledger('current', true)
|
||||
// .on('success', function (m) {
|
||||
// console.log("Ledger: %s", JSON.stringify(m, undefined, 2));
|
||||
//
|
||||
// callback();
|
||||
// })
|
||||
// .request();
|
||||
// },
|
||||
// function (callback) {
|
||||
// self.what = "Find path from alice to bob";
|
||||
//
|
||||
// // 5. acct 1 sent a 25 usd iou to acct 2
|
||||
// self.remote.request_ripple_path_find("alice", "bob", "25/USD/bob",
|
||||
// [ { 'currency' : "USD" } ])
|
||||
// .on('success', function (m) {
|
||||
// // console.log("proposed: %s", JSON.stringify(m));
|
||||
//
|
||||
// // 0 alternatives.
|
||||
// buster.assert.equals(0, m.alternatives.length)
|
||||
//
|
||||
// callback();
|
||||
// })
|
||||
// .request();
|
||||
// },
|
||||
], function (error) {
|
||||
buster.refute(error, self.what);
|
||||
done();
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
|
||||
Reference in New Issue
Block a user