Merge branch 'master' of github.com:jedmccaleb/NewCoin

This commit is contained in:
JoelKatz
2013-02-04 16:56:37 -08:00
10 changed files with 500 additions and 154 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)
{

View File

@@ -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
{

View File

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

View File

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

View File

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

View File

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

View File

@@ -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