Pathfinding now verifies source and destination accounts exist.

This commit is contained in:
Arthur Britto
2013-02-12 16:05:03 -08:00
parent 61c84eba2e
commit 7e271ce509

View File

@@ -193,49 +193,123 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax
% RippleAddress::createHumanAccountID(mSrcIssuerID) % RippleAddress::createHumanAccountID(mSrcIssuerID)
); );
if (mLedger) if (!mLedger)
{ {
LedgerEntrySet lesActive(mLedger); cLog(lsDEBUG) << boost::str(boost::format("findPaths< no ledger"));
std::vector<STPath> vspResults;
std::queue<STPath> qspExplore; // Path stubs to explore.
STPath spSeed; return false;
bool bForcedIssuer = !!mSrcCurrencyID && mSrcIssuerID != mSrcAccountID; // Source forced an issuer. }
// The end is the cursor, start at the source account. LedgerEntrySet lesActive(mLedger);
STPathElement speEnd(mSrcAccountID,
mSrcCurrencyID, SLE::pointer sleSrc = lesActive.entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(mSrcAccountID));
!!mSrcCurrencyID if (!sleSrc)
? mSrcAccountID // Non-XRP, start with self as issuer. {
: ACCOUNT_XRP); cLog(lsDEBUG) << boost::str(boost::format("findPaths< no source"));
return false;
}
SLE::pointer sleDst = lesActive.entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(mDstAccountID));
if (!sleDst)
{
cLog(lsDEBUG) << boost::str(boost::format("findPaths< no dest"));
return false;
}
std::vector<STPath> vspResults;
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,
!!mSrcCurrencyID
? mSrcAccountID // Non-XRP, start with self as issuer.
: ACCOUNT_XRP);
// Build a path of one element: the source.
spSeed.addElement(speEnd);
if (bForcedIssuer)
{
// Add forced source issuer to seed, via issuer's account.
STPathElement speIssuer(mSrcIssuerID, mSrcCurrencyID, mSrcIssuerID);
// Build a path of one element: the source.
spSeed.addElement(speEnd); spSeed.addElement(speEnd);
}
if (bForcedIssuer) // 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.
if (!speEnd.mCurrencyID // Tail output is XRP.
&& !mDstAmount.getCurrency()) // Which is dst currency.
{ {
// Add forced source issuer to seed, via issuer's account. // Done, cursor produces XRP and dest wants XRP.
STPathElement speIssuer(mSrcIssuerID, mSrcCurrencyID, mSrcIssuerID);
spSeed.addElement(speEnd); // 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 path: " << spPath.getJson(0);
vspResults.push_back(spPath); // Potential result.
}
else
{
cLog(lsDEBUG) << "findPaths: empty path: XRP->XRP";
}
continue;
} }
// Push the seed path to explore. cLog(lsDEBUG) << "findPaths: finish? account: " << (speEnd.mAccountID == mDstAccountID);
qspExplore.push(spSeed); 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());
while (qspExplore.size()) { // Have paths to explore? // YYY Allows going through self. Is this wanted?
STPath spPath = qspExplore.front(); if (speEnd.mAccountID == mDstAccountID // Tail is destination account.
&& 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.
{
// Done, found a path to the destination.
// Cursor on the dest account with correct currency and issuer.
qspExplore.pop(); // Pop the first path from the queue. if (bDefaultPath(spPath)) {
cLog(lsDEBUG) << "findPaths: dropping: default path: " << spPath.getJson(0);
speEnd = spPath.mPath.back(); // Get the last node from the path. bFound = true;
}
if (!speEnd.mCurrencyID // Tail output is XRP. else
&& !mDstAmount.getCurrency()) // Which is dst currency.
{ {
// Done, cursor produces XRP and dest wants XRP. // Remove implied nodes.
// Remove implied source.
spPath.mPath.erase(spPath.mPath.begin()); spPath.mPath.erase(spPath.mPath.begin());
if (bForcedIssuer) if (bForcedIssuer)
@@ -243,299 +317,242 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax
// Remove implied source issuer. // Remove implied source issuer.
spPath.mPath.erase(spPath.mPath.begin()); spPath.mPath.erase(spPath.mPath.begin());
} }
spPath.mPath.erase(spPath.mPath.begin() + spPath.mPath.size()-1);
if (spPath.size()) vspResults.push_back(spPath); // Potential result.
{
// There is an actual path element.
cLog(lsDEBUG) << "findPaths: adding path: " << spPath.getJson(0);
vspResults.push_back(spPath); // Potential result. cLog(lsDEBUG) << "findPaths: adding path: " << spPath.getJson(0);
}
else
{
cLog(lsDEBUG) << "findPaths: empty path: XRP->XRP";
}
continue;
} }
cLog(lsDEBUG) << "findPaths: finish? account: " << (speEnd.mAccountID == mDstAccountID); continue;
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.mIssuerID == mDstAccountID // Dest always accepts own issuer.
|| mDstAmount.getIssuer() == mDstAccountID // Any issuer is good.
|| mDstAmount.getIssuer() == speEnd.mIssuerID)) // The desired issuer.
{
// Done, found a path to the destination.
// Cursor on the dest account with correct currency and issuer.
if (bDefaultPath(spPath)) {
cLog(lsDEBUG) << "findPaths: dropping: default path: " << spPath.getJson(0);
bFound = true;
}
else
{
// 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 path: " << spPath.getJson(0);
}
continue;
}
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: dropping: path would exceed max steps"));
continue;
}
else if (!speEnd.mCurrencyID)
{
// Cursor is for XRP, continue with qualifying books: XRP -> non-XRP
BOOST_FOREACH(OrderBook::ref book, theApp->getOrderBookDB().getXRPInBooks())
{
// New end is an order book with the currency and issuer.
// Don't allow looping through same order books.
if (!spPath.hasSeen(ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut()))
{
STPath spNew(spPath);
STPathElement speBook(ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut());
STPathElement speAccount(book->getIssuerOut(), book->getCurrencyOut(), book->getIssuerOut());
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 -> %s/%s")
% STAmount::createHumanCurrency(speBook.mCurrencyID)
% RippleAddress::createHumanAccountID(speBook.mIssuerID));
qspExplore.push(spNew);
bContinued = true;
}
}
tLog(!bContinued, lsDEBUG)
<< boost::str(boost::format("findPaths: XRP -> dead end"));
}
else
{
// 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.
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();
const uint160 uPeerID = 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")
% RippleAddress::createHumanAccountID(speEnd.mAccountID)
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
% RippleAddress::createHumanAccountID(uPeerID)
% STAmount::createHumanCurrency(speEnd.mCurrencyID));
}
else if (!rspEntry->getBalance().isPositive() // No IOUs to send.
&& (!rspEntry->getLimitPeer() // Peer does not extend credit.
|| *rspEntry->getBalance().negate() >= rspEntry->getLimitPeer() // No credit left.
|| (bRequireAuth && !rspEntry->getAuth()))) // Not authorized to hold credit.
{
// Path has no credit left. Ignore it.
cLog(lsDEBUG) <<
boost::str(boost::format("findPaths: No credit: %s/%s -> %s/%s")
% RippleAddress::createHumanAccountID(speEnd.mAccountID)
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
% RippleAddress::createHumanAccountID(uPeerID)
% STAmount::createHumanCurrency(speEnd.mCurrencyID));
}
else
{
// Can transmit IOUs and account to the path.
STPath spNew(spPath);
STPathElement speNew(uPeerID, speEnd.mCurrencyID, uPeerID);
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;
theApp->getOrderBookDB().getBooks(speEnd.mIssuerID, speEnd.mCurrencyID, books);
BOOST_FOREACH(OrderBook::ref book, books)
{
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());
spNew.mPath.push_back(speBook);
qspExplore.push(spNew);
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: dropping: non-XRP -> dead end"));
}
} }
unsigned int iLimit = std::min(iMaxPaths, (unsigned int) vspResults.size()); bool bContinued = false; // True, if wasn't a dead end.
// Only filter, sort, and limit if have non-default paths. cLog(lsDEBUG) <<
if (iLimit) 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)
{ {
std::vector< std::pair<uint64, unsigned int> > vMap; // Path is at maximum size. Don't want to add more.
// Build map of quality to entry. cLog(lsDEBUG)
for (int i = vspResults.size(); i--;) << boost::str(boost::format("findPaths: dropping: path would exceed max steps"));
continue;
}
else if (!speEnd.mCurrencyID)
{
// Cursor is for XRP, continue with qualifying books: XRP -> non-XRP
BOOST_FOREACH(OrderBook::ref book, theApp->getOrderBookDB().getXRPInBooks())
{ {
STAmount saMaxAmountAct; // New end is an order book with the currency and issuer.
STAmount saDstAmountAct;
std::vector<PathState::pointer> vpsExpanded;
STPathSet spsPaths;
STPath& spCurrent = vspResults[i];
spsPaths.addPath(spCurrent); // Just checking the current path. // Don't allow looping through same order books.
if (!spPath.hasSeen(ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut()))
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(); STPath spNew(spPath);
STPathElement speBook(ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut());
STPathElement speAccount(book->getIssuerOut(), book->getCurrencyOut(), book->getIssuerOut());
terResult = tefEXCEPTION; 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 -> %s/%s")
% STAmount::createHumanCurrency(speBook.mCurrencyID)
% RippleAddress::createHumanAccountID(speBook.mIssuerID));
qspExplore.push(spNew);
bContinued = true;
} }
}
if (tesSUCCESS == terResult) tLog(!bContinued, lsDEBUG)
<< boost::str(boost::format("findPaths: XRP -> dead end"));
}
else
{
// 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.
AccountItems rippleLines(speEnd.mAccountID, mLedger, AccountItem::pointer(new RippleState()));
SLE::pointer sleEnd = lesActive.entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(speEnd.mAccountID));
tLog(sleEnd, lsDEBUG)
<< boost::str(boost::format("findPaths: account without root: %s")
% RippleAddress::createHumanAccountID(speEnd.mAccountID));
bool bRequireAuth = isSetBit(sleEnd->getFieldU32(sfFlags), lsfRequireAuth);
BOOST_FOREACH(AccountItem::ref item, rippleLines.getItems())
{
RippleState* rspEntry = (RippleState*) item.get();
const uint160 uPeerID = rspEntry->getAccountIDPeer().getAccountID();
if (spPath.hasSeen(uPeerID, speEnd.mCurrencyID, uPeerID))
{ {
uint64 uQuality = STAmount::getRate(saDstAmountAct, saMaxAmountAct); // 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: quality: %d: %s") % RippleAddress::createHumanAccountID(speEnd.mAccountID)
% uQuality % STAmount::createHumanCurrency(speEnd.mCurrencyID)
% spCurrent.getJson(0)); % RippleAddress::createHumanAccountID(uPeerID)
% STAmount::createHumanCurrency(speEnd.mCurrencyID));
vMap.push_back(std::make_pair(uQuality, i)); }
else if (!rspEntry->getBalance().isPositive() // No IOUs to send.
&& (!rspEntry->getLimitPeer() // Peer does not extend credit.
|| *rspEntry->getBalance().negate() >= rspEntry->getLimitPeer() // No credit left.
|| (bRequireAuth && !rspEntry->getAuth()))) // Not authorized to hold credit.
{
// Path has no credit left. Ignore it.
cLog(lsDEBUG) <<
boost::str(boost::format("findPaths: No credit: %s/%s -> %s/%s")
% RippleAddress::createHumanAccountID(speEnd.mAccountID)
% STAmount::createHumanCurrency(speEnd.mCurrencyID)
% RippleAddress::createHumanAccountID(uPeerID)
% STAmount::createHumanCurrency(speEnd.mCurrencyID));
} }
else else
{ {
cLog(lsDEBUG) // Can transmit IOUs and account to the path.
<< boost::str(boost::format("findPaths: dropping: %s: %s") STPath spNew(spPath);
% transToken(terResult) STPathElement speNew(uPeerID, speEnd.mCurrencyID, uPeerID);
% spCurrent.getJson(0));
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));
} }
} }
if (vMap.size()) // Every book that wants the source currency.
std::vector<OrderBook::pointer> books;
theApp->getOrderBookDB().getBooks(speEnd.mIssuerID, speEnd.mCurrencyID, books);
BOOST_FOREACH(OrderBook::ref book, books)
{ {
iLimit = std::min(iMaxPaths, (unsigned int) vMap.size()); if (!spPath.hasSeen(ACCOUNT_XRP, book->getCurrencyOut(), book->getIssuerOut()))
bFound = true;
std::sort(vMap.begin(), vMap.end(), bQualityCmp); // Lower is better and should be first.
// Output best quality entries.
for (int i = 0; i != vMap.size(); ++i)
{ {
spsDst.addPath(vspResults[vMap[i].second]); // 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: RESULTS: %s") % spsDst.getJson(0)); spNew.mPath.push_back(speBook);
} qspExplore.push(spNew);
else
{ bContinued = true;
cLog(lsDEBUG) << boost::str(boost::format("findPaths: RESULTS: non-defaults filtered away"));
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: dropping: non-XRP -> dead end"));
} }
} }
else
unsigned int iLimit = std::min(iMaxPaths, (unsigned int) vspResults.size());
// Only filter, sort, and limit if have non-default paths.
if (iLimit)
{ {
cLog(lsDEBUG) << boost::str(boost::format("findPaths: no ledger")); std::vector< std::pair<uint64, unsigned int> > vMap;
// Build map of quality to entry.
for (int i = vspResults.size(); i--;)
{
STAmount saMaxAmountAct;
STAmount saDstAmountAct;
std::vector<PathState::pointer> vpsExpanded;
STPathSet spsPaths;
STPath& spCurrent = vspResults[i];
spsPaths.addPath(spCurrent); // Just checking the current path.
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)
{
uint64 uQuality = STAmount::getRate(saDstAmountAct, saMaxAmountAct);
cLog(lsDEBUG)
<< boost::str(boost::format("findPaths: quality: %d: %s")
% uQuality
% spCurrent.getJson(0));
vMap.push_back(std::make_pair(uQuality, i));
}
else
{
cLog(lsDEBUG)
<< boost::str(boost::format("findPaths: dropping: %s: %s")
% transToken(terResult)
% spCurrent.getJson(0));
}
}
if (vMap.size())
{
iLimit = std::min(iMaxPaths, (unsigned int) vMap.size());
bFound = true;
std::sort(vMap.begin(), vMap.end(), bQualityCmp); // Lower is better and should be first.
// Output best quality entries.
for (int i = 0; i != vMap.size(); ++i)
{
spsDst.addPath(vspResults[vMap[i].second]);
}
cLog(lsDEBUG) << boost::str(boost::format("findPaths: RESULTS: %s") % spsDst.getJson(0));
}
else
{
cLog(lsDEBUG) << boost::str(boost::format("findPaths: RESULTS: non-defaults filtered away"));
}
} }
cLog(lsDEBUG) << boost::str(boost::format("findPaths< bFound=%d") % bFound); cLog(lsDEBUG) << boost::str(boost::format("findPaths< bFound=%d") % bFound);