mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-20 02:55:50 +00:00
Many fixes for path finding.
This commit is contained in:
@@ -129,12 +129,13 @@ bool Pathfinder::bDefaultPath(const STPath& spPath)
|
|||||||
return false;
|
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)
|
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();
|
mLedger = theApp->getLedgerMaster().getCurrentLedger();
|
||||||
mSrcAmount = STAmount(uSrcCurrencyID, uSrcIssuerID, 1, 0, true); // -1/uSrcIssuerID/uSrcIssuerID
|
mSrcAmount = STAmount(uSrcCurrencyID, uSrcIssuerID, 1, 0, true); // -1/uSrcIssuerID/uSrcIssuerID
|
||||||
@@ -155,14 +156,14 @@ Pathfinder::Pathfinder(const RippleAddress& uSrcAccountID, const RippleAddress&
|
|||||||
if (tesSUCCESS == psDefault->terStatus)
|
if (tesSUCCESS == psDefault->terStatus)
|
||||||
{
|
{
|
||||||
// The default path works, remember it.
|
// The default path works, remember it.
|
||||||
cLog(lsDEBUG) << "Pathfinder: reference path: " << psDefault->getJson();
|
cLog(lsDEBUG) << "Pathfinder: default path: " << psDefault->getJson();
|
||||||
|
|
||||||
mPsDefault = psDefault;
|
mPsDefault = psDefault;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// The default path doesn't work.
|
// The default path doesn't work.
|
||||||
cLog(lsDEBUG) << "Pathfinder: reference path: NONE: " << transToken(psDefault->terStatus);
|
cLog(lsDEBUG) << "Pathfinder: default path: NONE: " << transToken(psDefault->terStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,15 +200,29 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax
|
|||||||
std::vector<STPath> vspResults;
|
std::vector<STPath> vspResults;
|
||||||
std::queue<STPath> qspExplore; // Path stubs to explore.
|
std::queue<STPath> qspExplore; // Path stubs to explore.
|
||||||
|
|
||||||
// The end is our cursor, start at the source.
|
STPath spSeed;
|
||||||
STPathElement speEnd(mSrcAccountID, mSrcCurrencyID, uint160()); // XXX Might add source issuer.
|
bool bForcedIssuer = !!mSrcCurrencyID && mSrcIssuerID != mSrcAccountID; // Source forced an issuer.
|
||||||
|
|
||||||
|
// The end is the cursor, start at the source account.
|
||||||
|
STPathElement speEnd(mSrcAccountID,
|
||||||
|
mSrcCurrencyID,
|
||||||
|
!!mSrcCurrencyID
|
||||||
|
? mSrcAccountID // Non-XRP, start with self as issuer.
|
||||||
|
: ACCOUNT_XRP);
|
||||||
|
|
||||||
// Build a path of one element: the source.
|
// Build a path of one element: the source.
|
||||||
STPath path;
|
spSeed.addElement(speEnd);
|
||||||
|
|
||||||
path.addElement(speEnd);
|
if (bForcedIssuer)
|
||||||
|
{
|
||||||
|
// Add forced source issuer to seed, via issuer's account.
|
||||||
|
STPathElement speIssuer(mSrcIssuerID, mSrcCurrencyID, mSrcIssuerID);
|
||||||
|
|
||||||
// Push the source as a path of one element to explore.
|
spSeed.addElement(speEnd);
|
||||||
qspExplore.push(path);
|
}
|
||||||
|
|
||||||
|
// Push the seed path to explore.
|
||||||
|
qspExplore.push(spSeed);
|
||||||
|
|
||||||
while (qspExplore.size()) { // Have paths to explore?
|
while (qspExplore.size()) { // Have paths to explore?
|
||||||
STPath spPath = qspExplore.front();
|
STPath spPath = qspExplore.front();
|
||||||
@@ -221,9 +236,15 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax
|
|||||||
{
|
{
|
||||||
// Done, cursor produces XRP and dest wants XRP.
|
// Done, cursor produces XRP and dest wants XRP.
|
||||||
|
|
||||||
// Remove implied first.
|
// Remove implied source.
|
||||||
spPath.mPath.erase(spPath.mPath.begin());
|
spPath.mPath.erase(spPath.mPath.begin());
|
||||||
|
|
||||||
|
if (bForcedIssuer)
|
||||||
|
{
|
||||||
|
// Remove implied source issuer.
|
||||||
|
spPath.mPath.erase(spPath.mPath.begin());
|
||||||
|
}
|
||||||
|
|
||||||
if (spPath.size())
|
if (spPath.size())
|
||||||
{
|
{
|
||||||
// There is an actual path element.
|
// There is an actual path element.
|
||||||
@@ -252,12 +273,12 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax
|
|||||||
// YYY Allows going through self. Is this wanted?
|
// YYY Allows going through self. Is this wanted?
|
||||||
if (speEnd.mAccountID == mDstAccountID // Tail is destination account.
|
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 accpets own.
|
&& ( speEnd.mIssuerID == mDstAccountID // Dest always accepts own issuer.
|
||||||
|| mDstAmount.getIssuer() == mDstAccountID // Any issuer is good.
|
|| mDstAmount.getIssuer() == mDstAccountID // Any issuer is good.
|
||||||
|| speEnd.mIssuerID == mDstAmount.getIssuer())) // The desired issuer.
|
|| mDstAmount.getIssuer() == speEnd.mIssuerID)) // The desired issuer.
|
||||||
{
|
{
|
||||||
// Found a path to the destination.
|
// Done, found a path to the destination.
|
||||||
// Done, cursor on the dest account with correct currency and issuer.
|
// Cursor on the dest account with correct currency and issuer.
|
||||||
|
|
||||||
if (bDefaultPath(spPath)) {
|
if (bDefaultPath(spPath)) {
|
||||||
cLog(lsDEBUG) << "findPaths: dropping: default path: " << spPath.getJson(0);
|
cLog(lsDEBUG) << "findPaths: dropping: default path: " << spPath.getJson(0);
|
||||||
@@ -266,9 +287,15 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Remove implied first and last nodes.
|
// Remove implied nodes.
|
||||||
|
|
||||||
spPath.mPath.erase(spPath.mPath.begin());
|
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);
|
spPath.mPath.erase(spPath.mPath.begin() + spPath.mPath.size()-1);
|
||||||
|
|
||||||
vspResults.push_back(spPath); // Potential result.
|
vspResults.push_back(spPath); // Potential result.
|
||||||
@@ -293,57 +320,68 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax
|
|||||||
|
|
||||||
cLog(lsDEBUG)
|
cLog(lsDEBUG)
|
||||||
<< boost::str(boost::format("findPaths: dropping: path would exceed max steps"));
|
<< boost::str(boost::format("findPaths: dropping: path would exceed max steps"));
|
||||||
|
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
else if (!speEnd.mCurrencyID)
|
else if (!speEnd.mCurrencyID)
|
||||||
{
|
{
|
||||||
// Cursor 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())
|
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);
|
STPath spNew(spPath);
|
||||||
// New end is an order book with the currency and issuer.
|
STPathElement speBook(ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut());
|
||||||
STPathElement new_ele(uint160(), book->getCurrencyOut(), book->getIssuerOut());
|
STPathElement speAccount(book->getIssuerOut(), book->getCurrencyOut(), book->getIssuerOut());
|
||||||
|
|
||||||
new_path.mPath.push_back(new_ele);
|
spNew.mPath.push_back(speBook); // Add the order book.
|
||||||
|
spNew.mPath.push_back(speAccount); // Add the account and currency
|
||||||
|
|
||||||
cLog(lsDEBUG) <<
|
cLog(lsDEBUG) <<
|
||||||
boost::str(boost::format("findPaths: XRP input -> %s/%s")
|
boost::str(boost::format("findPaths: XRP -> %s/%s")
|
||||||
% STAmount::createHumanCurrency(new_ele.mCurrencyID)
|
% STAmount::createHumanCurrency(speBook.mCurrencyID)
|
||||||
% RippleAddress::createHumanAccountID(new_ele.mIssuerID));
|
% RippleAddress::createHumanAccountID(speBook.mIssuerID));
|
||||||
|
|
||||||
qspExplore.push(new_path);
|
qspExplore.push(spNew);
|
||||||
|
|
||||||
bContinued = true;
|
bContinued = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tLog(!bContinued, lsDEBUG)
|
tLog(!bContinued, lsDEBUG)
|
||||||
<< boost::str(boost::format("findPaths: XRP input -> dead end"));
|
<< boost::str(boost::format("findPaths: XRP -> dead end"));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Last element is for non-XRP, continue by adding ripple lines and order books.
|
// Last element is for non-XRP, continue by adding ripple lines and order books.
|
||||||
|
|
||||||
// Create new paths for each outbound account not already in the path.
|
// Create new paths for each outbound account not already in the path.
|
||||||
AccountItems rippleLines(speEnd.mIssuerID, mLedger, AccountItem::pointer(new RippleState()));
|
AccountItems rippleLines(speEnd.mAccountID, mLedger, AccountItem::pointer(new RippleState()));
|
||||||
SLE::pointer sleSrc = lesActive.entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(speEnd.mIssuerID));
|
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);
|
bool bRequireAuth = isSetBit(sleSrc->getFieldU32(sfFlags), lsfRequireAuth);
|
||||||
|
|
||||||
BOOST_FOREACH(AccountItem::ref item, rippleLines.getItems())
|
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.
|
// Peer is in path already. Ignore it to avoid a loop.
|
||||||
cLog(lsDEBUG) <<
|
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)
|
% RippleAddress::createHumanAccountID(speEnd.mAccountID)
|
||||||
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
|
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
|
||||||
% RippleAddress::createHumanAccountID(rspEntry->getAccountIDPeer().getAccountID())
|
% RippleAddress::createHumanAccountID(uPeerID)
|
||||||
% STAmount::createHumanCurrency(speEnd.mCurrencyID));
|
% STAmount::createHumanCurrency(speEnd.mCurrencyID));
|
||||||
}
|
}
|
||||||
else if (!rspEntry->getBalance().isPositive() // No IOUs to send.
|
else if (!rspEntry->getBalance().isPositive() // No IOUs to send.
|
||||||
@@ -353,31 +391,29 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax
|
|||||||
{
|
{
|
||||||
// Path has no credit left. Ignore it.
|
// Path has no credit left. Ignore it.
|
||||||
cLog(lsDEBUG) <<
|
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)
|
% RippleAddress::createHumanAccountID(speEnd.mAccountID)
|
||||||
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
|
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
|
||||||
% RippleAddress::createHumanAccountID(rspEntry->getAccountIDPeer().getAccountID())
|
% RippleAddress::createHumanAccountID(uPeerID)
|
||||||
% STAmount::createHumanCurrency(speEnd.mCurrencyID));
|
% STAmount::createHumanCurrency(speEnd.mCurrencyID));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Can transmit IOUs.
|
// Can transmit IOUs and account to the path.
|
||||||
STPath new_path(spPath);
|
STPath spNew(spPath);
|
||||||
STPathElement new_ele(rspEntry->getAccountIDPeer().getAccountID(),
|
STPathElement speNew(uPeerID, speEnd.mCurrencyID, uPeerID);
|
||||||
speEnd.mCurrencyID,
|
|
||||||
rspEntry->getAccountIDPeer().getAccountID());
|
spNew.mPath.push_back(speNew);
|
||||||
|
qspExplore.push(spNew);
|
||||||
|
|
||||||
|
bContinued = true;
|
||||||
|
|
||||||
cLog(lsDEBUG) <<
|
cLog(lsDEBUG) <<
|
||||||
boost::str(boost::format("findPaths: push explore: %s/%s --> %s/%s")
|
boost::str(boost::format("findPaths: push explore: %s/%s -> %s/%s")
|
||||||
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
|
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
|
||||||
% RippleAddress::createHumanAccountID(speEnd.mAccountID)
|
% RippleAddress::createHumanAccountID(speEnd.mAccountID)
|
||||||
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
|
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
|
||||||
% RippleAddress::createHumanAccountID(rspEntry->getAccountIDPeer().getAccountID()));
|
% RippleAddress::createHumanAccountID(uPeerID));
|
||||||
|
|
||||||
new_path.mPath.push_back(new_ele);
|
|
||||||
qspExplore.push(new_path);
|
|
||||||
|
|
||||||
bContinued = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,27 +422,30 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax
|
|||||||
|
|
||||||
mOrderBook.getBooks(speEnd.mIssuerID, speEnd.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);
|
if (!spPath.hasSeen(ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut()))
|
||||||
STPathElement new_ele(uint160(), 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) <<
|
spNew.mPath.push_back(speBook);
|
||||||
boost::str(boost::format("findPaths: %s/%s :: %s/%s")
|
qspExplore.push(spNew);
|
||||||
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
|
|
||||||
% RippleAddress::createHumanAccountID(speEnd.mIssuerID)
|
|
||||||
% STAmount::createHumanCurrency(book->getCurrencyOut())
|
|
||||||
% RippleAddress::createHumanAccountID(book->getIssuerOut()));
|
|
||||||
|
|
||||||
new_path.mPath.push_back(new_ele);
|
bContinued = true;
|
||||||
|
|
||||||
qspExplore.push(new_path);
|
cLog(lsDEBUG) <<
|
||||||
|
boost::str(boost::format("findPaths: push book: %s/%s -> %s/%s")
|
||||||
bContinued = true;
|
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
|
||||||
|
% RippleAddress::createHumanAccountID(speEnd.mIssuerID)
|
||||||
|
% STAmount::createHumanCurrency(book->getCurrencyOut())
|
||||||
|
% RippleAddress::createHumanAccountID(book->getIssuerOut()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tLog(!bContinued, lsDEBUG)
|
tLog(!bContinued, lsDEBUG)
|
||||||
<< boost::str(boost::format("findPaths: non-XRP input - dead end"));
|
<< boost::str(boost::format("findPaths: dropping: non-XRP -> dead end"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ require('../src/js/config').load(require('./config'));
|
|||||||
buster.testRunner.timeout = 5000;
|
buster.testRunner.timeout = 5000;
|
||||||
|
|
||||||
buster.testCase("Basic Path finding", {
|
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(),
|
||||||
|
// 'setUp' : testutils.build_setup({ verbose: true }),
|
||||||
|
// 'setUp' : testutils.build_setup({ verbose: true, no_server: true }),
|
||||||
'tearDown' : testutils.build_teardown(),
|
'tearDown' : testutils.build_teardown(),
|
||||||
|
|
||||||
"no direct path, no intermediary -> no alternatives" :
|
"no direct path, no intermediary -> no alternatives" :
|
||||||
|
|||||||
Reference in New Issue
Block a user