Make pathfinder return best quality paths.

This commit is contained in:
Arthur Britto
2012-12-12 15:41:47 -08:00
parent dd95a4b25e
commit 895a176611
2 changed files with 77 additions and 22 deletions

View File

@@ -75,6 +75,7 @@ PathOption::PathOption(PathOption::pointer other)
} }
#endif #endif
// Lower numbers have better quality. Sort higher quality first.
static bool bQualityCmp(std::pair<uint32, unsigned int> a, std::pair<uint32, unsigned int> b) static bool bQualityCmp(std::pair<uint32, unsigned int> a, std::pair<uint32, unsigned int> b)
{ {
return a.first < b.first; return a.first < b.first;
@@ -163,6 +164,11 @@ Pathfinder::Pathfinder(const RippleAddress& uSrcAccountID, const RippleAddress&
// When generating a path set blindly, don't allow the empty path, it is implied by default. // When generating a path set blindly, don't allow the empty path, it is implied by default.
// When generating a path set for estimates, allow an empty path instead of no paths to indicate a path exists. The caller will // When generating a path set for estimates, allow an empty path instead of no paths to indicate a path exists. The caller will
// need to strip the empty path when submitting the transaction. // need to strip the empty path when submitting the transaction.
//
// Assumes rippling (not XRP to XRP)
//
// Leaves to the caller figuring out overall liquidity.
// Optimization opportunity: For some simple cases, this routine has figured out the overall liquidity.
bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMaxPaths, STPathSet& spsDst) bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMaxPaths, STPathSet& spsDst)
{ {
bool bFound = false; // True, iff found a path. bool bFound = false; // True, iff found a path.
@@ -177,6 +183,7 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax
if (mLedger) if (mLedger)
{ {
LedgerEntrySet lesActive(mLedger);
std::vector<STPath> vspResults; std::vector<STPath> vspResults;
std::queue<STPath> qspExplore; std::queue<STPath> qspExplore;
@@ -192,24 +199,23 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax
while (qspExplore.size()) { while (qspExplore.size()) {
STPath spPath = qspExplore.front(); STPath spPath = qspExplore.front();
qspExplore.pop(); // Pop the first path from the queue. qspExplore.pop(); // Pop the first path from the queue.
speEnd = spPath.mPath.back(); // Get the last node from the path. speEnd = spPath.mPath.back(); // Get the last node from the path.
// Done, if dest wants XRP and last element produces XRP. // Done, if dest wants XRP and last element produces XRP.
if (!speEnd.mCurrencyID // Tail output is XRP. if (!speEnd.mCurrencyID // Tail output is XRP.
&& !mDstAmount.getCurrency()) { // Which is dst currency. && !mDstAmount.getCurrency()) // Which is dst currency.
{
// Remove implied first. // Remove implied first.
spPath.mPath.erase(spPath.mPath.begin()); spPath.mPath.erase(spPath.mPath.begin());
if (spPath.size()) if (spPath.size())
{ {
// There is an actual path element. // There is an actual path element.
cLog(lsDEBUG) << "findPaths: adding: " << spPath.getJson(0);
vspResults.push_back(spPath); // Potential result. vspResults.push_back(spPath); // Potential result.
cLog(lsDEBUG) << "findPaths: adding: " << spPath.getJson(0);
} }
else else
{ {
@@ -222,7 +228,8 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax
// Done, if dest wants non-XRP and last element is dest. // Done, if dest wants non-XRP and last element is dest.
// 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.
{
// Found a path to the destination. // Found a path to the destination.
if (bDefaultPath(spPath)) { if (bDefaultPath(spPath)) {
@@ -237,7 +244,7 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax
spPath.mPath.erase(spPath.mPath.begin()); 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.
cLog(lsDEBUG) << "findPaths: adding: " << spPath.getJson(0); cLog(lsDEBUG) << "findPaths: adding: " << spPath.getJson(0);
} }
@@ -358,35 +365,82 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax
unsigned int iLimit = std::min(iMaxPaths, (unsigned int) vspResults.size()); unsigned int iLimit = std::min(iMaxPaths, (unsigned int) vspResults.size());
// Only filter, sort, and limit if have non-default paths.
if (iLimit) if (iLimit)
{ {
std::vector< std::pair<uint32, unsigned int> > vMap; std::vector< std::pair<uint64, unsigned int> > vMap;
// Build map of quality to entry. // Build map of quality to entry.
for (int i = vspResults.size(); i--;) for (int i = vspResults.size(); i--;)
{ {
uint32 uQuality = 1; 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 = 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.
if (tesSUCCESS == terResult)
{
uint64 uQuality = STAmount::getRate(saDstAmountAct, saMaxAmountAct);
cLog(lsDEBUG)
<< boost::str(boost::format("findPaths: quality: %d: %s")
% uQuality
% spCurrent.getJson(0));
if (uQuality)
vMap.push_back(std::make_pair(uQuality, i)); vMap.push_back(std::make_pair(uQuality, i));
}
else
{
cLog(lsDEBUG)
<< boost::str(boost::format("findPaths: dropping: %s: %s")
% transToken(terResult)
% spCurrent.getJson(0));
}
} }
std::sort(vMap.begin(), vMap.end(), bQualityCmp); if (vMap.size())
// Output best quality entries.
for (int i = 0; i != iLimit; ++i)
{ {
spsDst.addPath(vspResults[vMap[i].second]); 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"));
} }
bFound = true;
cLog(lsWARNING) << boost::str(boost::format("findPaths: RESULTS: %s") % spsDst.getJson(0));
} }
} }
else else
{ {
cLog(lsWARNING) << boost::str(boost::format("findPaths: no ledger")); cLog(lsDEBUG) << boost::str(boost::format("findPaths: no ledger"));
} }
return bFound; return bFound;

View File

@@ -2327,6 +2327,7 @@ void RippleCalc::pathNext(PathState::ref psrCur, const int iPaths, const LedgerE
} }
} }
// <-- TER: Only returns tepPATH_PARTIAL if !bPartialPayment.
TER RippleCalc::rippleCalc( TER RippleCalc::rippleCalc(
// Compute paths vs this ledger entry set. Up to caller to actually apply to ledger. // Compute paths vs this ledger entry set. Up to caller to actually apply to ledger.
LedgerEntrySet& lesActive, // <-> --> = Fee already applied to src balance. LedgerEntrySet& lesActive, // <-> --> = Fee already applied to src balance.