mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Fix pathfinding with multiple issuers for one currency (RIPD-618).
* Allow pathfinding requests where the starting currency may have multiple issuers. * Cache paths over all issuers to avoid repeating work. * Clear the ledger checkpoint in one retry case. * Add an additional node at the front of paths when the starting issuer is not the source account.
This commit is contained in:
@@ -22,6 +22,89 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class FindPaths::Impl {
|
||||
public:
|
||||
Impl (
|
||||
RippleLineCache::ref cache,
|
||||
Account const& srcAccount,
|
||||
Account const& dstAccount,
|
||||
STAmount const& dstAmount,
|
||||
int searchLevel,
|
||||
unsigned int maxPaths)
|
||||
: cache_ (cache),
|
||||
srcAccount_ (srcAccount),
|
||||
dstAccount_ (dstAccount),
|
||||
dstAmount_ (dstAmount),
|
||||
searchLevel_ (searchLevel),
|
||||
maxPaths_ (maxPaths)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
bool findPathsForIssue (
|
||||
Issue const& issue,
|
||||
STPathSet& pathsOut,
|
||||
STPath& fullLiquidityPath)
|
||||
{
|
||||
if (auto& pathfinder = getPathFinder (issue.currency))
|
||||
{
|
||||
pathsOut = pathfinder->getBestPaths (
|
||||
maxPaths_, fullLiquidityPath, issue.account);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
hash_map<Currency, std::unique_ptr<Pathfinder>> currencyMap_;
|
||||
|
||||
RippleLineCache::ref cache_;
|
||||
Account const srcAccount_;
|
||||
Account const dstAccount_;
|
||||
STAmount const dstAmount_;
|
||||
int const searchLevel_;
|
||||
unsigned int const maxPaths_;
|
||||
|
||||
std::unique_ptr<Pathfinder> const& getPathFinder (Currency const& currency)
|
||||
{
|
||||
auto i = currencyMap_.find (currency);
|
||||
if (i != currencyMap_.end ())
|
||||
return i->second;
|
||||
auto pathfinder = std::make_unique<Pathfinder> (
|
||||
cache_, srcAccount_, dstAccount_, currency, dstAmount_);
|
||||
if (pathfinder->findPaths (searchLevel_))
|
||||
pathfinder->computePathRanks (maxPaths_);
|
||||
else
|
||||
pathfinder.reset (); // It's a bad request - clear it.
|
||||
return currencyMap_[currency] = std::move (pathfinder);
|
||||
|
||||
// TODO(tom): why doesn't this faster way compile?
|
||||
// return currencyMap_.insert (i, std::move (pathfinder)).second;
|
||||
}
|
||||
};
|
||||
|
||||
FindPaths::FindPaths (
|
||||
RippleLineCache::ref cache,
|
||||
Account const& srcAccount,
|
||||
Account const& dstAccount,
|
||||
STAmount const& dstAmount,
|
||||
int level,
|
||||
unsigned int maxPaths)
|
||||
: impl_ (std::make_unique<Impl> (
|
||||
cache, srcAccount, dstAccount, dstAmount, level, maxPaths))
|
||||
{
|
||||
}
|
||||
|
||||
FindPaths::~FindPaths() = default;
|
||||
|
||||
bool FindPaths::findPathsForIssue (
|
||||
Issue const& issue,
|
||||
STPathSet& pathsOut,
|
||||
STPath& fullLiquidityPath)
|
||||
{
|
||||
return impl_->findPathsForIssue (issue, pathsOut, fullLiquidityPath);
|
||||
}
|
||||
|
||||
bool findPathsForOneIssuer (
|
||||
RippleLineCache::ref cache,
|
||||
Account const& srcAccount,
|
||||
@@ -44,9 +127,9 @@ bool findPathsForOneIssuer (
|
||||
if (!pf.findPaths (searchLevel))
|
||||
return false;
|
||||
|
||||
// Yes, ensurePathsAreComplete is called BEFORE we compute the paths...
|
||||
pf.ensurePathsAreComplete (pathsOut);
|
||||
pathsOut = pf.getBestPaths(maxPaths, fullLiquidityPath);
|
||||
pf.addPathsFromPreviousPathfinding (pathsOut);
|
||||
pf.computePathRanks (maxPaths);
|
||||
pathsOut = pf.getBestPaths(maxPaths, fullLiquidityPath, srcIssue.account);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,43 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class FindPaths
|
||||
{
|
||||
public:
|
||||
FindPaths (
|
||||
RippleLineCache::ref cache,
|
||||
Account const& srcAccount,
|
||||
Account const& dstAccount,
|
||||
STAmount const& dstAmount,
|
||||
/** searchLevel is the maximum search level allowed in an output path.
|
||||
*/
|
||||
int searchLevel,
|
||||
/** maxPaths is the maximum number of paths that can be returned in
|
||||
pathsOut. */
|
||||
unsigned int const maxPaths);
|
||||
~FindPaths();
|
||||
|
||||
bool findPathsForIssue (
|
||||
Issue const& issue,
|
||||
|
||||
/** On input, pathsOut contains any paths you want to ensure are
|
||||
included if still good.
|
||||
|
||||
On output, pathsOut will have any additional paths found. Only
|
||||
non-default paths without source or destination will be added. */
|
||||
STPathSet& pathsOut,
|
||||
|
||||
/** On input, fullLiquidityPath must be an empty STPath.
|
||||
|
||||
On output, if fullLiquidityPath is non-empty, it contains one extra
|
||||
path that can move the entire liquidity requested. */
|
||||
STPath& fullLiquidityPath);
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
bool findPathsForOneIssuer (
|
||||
RippleLineCache::ref cache,
|
||||
Account const& srcAccount,
|
||||
|
||||
@@ -331,6 +331,11 @@ int PathRequest::parseJson (Json::Value const& jvParams, bool complete)
|
||||
return PFR_PJ_INVALID;
|
||||
}
|
||||
|
||||
if (uCur.isNonZero() && uIss.isZero())
|
||||
{
|
||||
uIss = raSrcAccount.getAccountID();
|
||||
}
|
||||
|
||||
sciSourceCurrencies.insert ({uCur, uIss});
|
||||
}
|
||||
}
|
||||
@@ -435,6 +440,13 @@ Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast)
|
||||
|
||||
bool found = false;
|
||||
|
||||
FindPaths fp (
|
||||
cache,
|
||||
raSrcAccount.getAccountID (),
|
||||
raDstAccount.getAccountID (),
|
||||
saDstAmount,
|
||||
iLevel,
|
||||
4); // iMaxPaths
|
||||
for (auto const& currIssuer: sourceCurrencies)
|
||||
{
|
||||
{
|
||||
@@ -448,14 +460,8 @@ Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast)
|
||||
}
|
||||
STPathSet& spsPaths = mContext[currIssuer];
|
||||
STPath fullLiquidityPath;
|
||||
auto valid = findPathsForOneIssuer (
|
||||
cache,
|
||||
raSrcAccount.getAccountID(),
|
||||
raDstAccount.getAccountID(),
|
||||
auto valid = fp.findPathsForIssue (
|
||||
currIssuer,
|
||||
saDstAmount,
|
||||
iLevel,
|
||||
4, // iMaxPaths
|
||||
spsPaths,
|
||||
fullLiquidityPath);
|
||||
CondLog (!valid, lsDEBUG, PathRequest)
|
||||
@@ -464,12 +470,12 @@ Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast)
|
||||
if (valid)
|
||||
{
|
||||
LedgerEntrySet lesSandbox (cache->getLedger (), tapNONE);
|
||||
auto& account = !isXRP (currIssuer.account)
|
||||
auto& sourceAccount = !isXRP (currIssuer.account)
|
||||
? currIssuer.account
|
||||
: isXRP (currIssuer.currency)
|
||||
? xrpAccount()
|
||||
: raSrcAccount.getAccountID ();
|
||||
STAmount saMaxAmount ({currIssuer.currency, account}, 1);
|
||||
STAmount saMaxAmount ({currIssuer.currency, sourceAccount}, 1);
|
||||
|
||||
saMaxAmount.negate ();
|
||||
m_journal.debug << iIdentifier
|
||||
@@ -488,7 +494,9 @@ Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast)
|
||||
m_journal.debug
|
||||
<< iIdentifier << " Trying with an extra path element";
|
||||
spsPaths.push_back (fullLiquidityPath);
|
||||
rc = path::RippleCalc::rippleCalculate (lesSandbox,
|
||||
lesSandbox.clear();
|
||||
rc = path::RippleCalc::rippleCalculate (
|
||||
lesSandbox,
|
||||
saMaxAmount,
|
||||
saDstAmount,
|
||||
raDstAccount.getAccountID (),
|
||||
@@ -507,6 +515,8 @@ Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast)
|
||||
if (rc.result () == tesSUCCESS)
|
||||
{
|
||||
Json::Value jvEntry (Json::objectValue);
|
||||
rc.actualAmountIn.setIssuer (sourceAccount);
|
||||
|
||||
jvEntry["source_amount"] = rc.actualAmountIn.getJson (0);
|
||||
jvEntry["paths_computed"] = spsPaths.getJson (0);
|
||||
found = true;
|
||||
|
||||
@@ -64,17 +64,10 @@ namespace {
|
||||
// width of path
|
||||
// correct currency at the end.
|
||||
|
||||
struct PathRank
|
||||
{
|
||||
std::uint64_t quality;
|
||||
std::uint64_t length;
|
||||
STAmount liquidity;
|
||||
int index;
|
||||
};
|
||||
|
||||
// Compare two PathRanks. A better PathRank is lower, so the best are sorted to
|
||||
// the beginning.
|
||||
bool comparePathRank (PathRank const& a, PathRank const& b)
|
||||
bool comparePathRank (
|
||||
Pathfinder::PathRank const& a, Pathfinder::PathRank const& b)
|
||||
{
|
||||
// 1) Higher quality (lower cost) is better
|
||||
if (a.quality != b.quality)
|
||||
@@ -179,12 +172,32 @@ Pathfinder::Pathfinder (
|
||||
mDstAmount (saDstAmount),
|
||||
mSrcCurrency (uSrcCurrency),
|
||||
mSrcIssuer (uSrcIssuer),
|
||||
mSrcAmount ({mSrcCurrency, mSrcIssuer}, 1u, 0, true),
|
||||
mSrcAmount ({uSrcCurrency, uSrcIssuer}, 1u, 0, true),
|
||||
mLedger (cache->getLedger ()),
|
||||
mRLCache (cache)
|
||||
{
|
||||
}
|
||||
|
||||
Pathfinder::Pathfinder (
|
||||
RippleLineCache::ref cache,
|
||||
Account const& uSrcAccount,
|
||||
Account const& uDstAccount,
|
||||
Currency const& uSrcCurrency,
|
||||
STAmount const& saDstAmount)
|
||||
: mSrcAccount (uSrcAccount),
|
||||
mDstAccount (uDstAccount),
|
||||
mDstAmount (saDstAmount),
|
||||
mSrcCurrency (uSrcCurrency),
|
||||
mSrcAmount ({uSrcCurrency, uSrcAccount}, 1u, 0, true),
|
||||
mLedger (cache->getLedger ()),
|
||||
mRLCache (cache)
|
||||
{
|
||||
}
|
||||
|
||||
Pathfinder::~Pathfinder()
|
||||
{
|
||||
}
|
||||
|
||||
bool Pathfinder::findPaths (int searchLevel)
|
||||
{
|
||||
if (mDstAmount == zero)
|
||||
@@ -210,19 +223,21 @@ bool Pathfinder::findPaths (int searchLevel)
|
||||
m_loadEvent = getApp ().getJobQueue ().getLoadEvent (
|
||||
jtPATH_FIND, "FindPath");
|
||||
auto currencyIsXRP = isXRP (mSrcCurrency);
|
||||
auto issuerIsXRP = isXRP (mSrcIssuer);
|
||||
|
||||
bool useIssuerAccount = !currencyIsXRP && !issuerIsXRP;
|
||||
auto& account = useIssuerAccount ? mSrcIssuer : mSrcAccount;
|
||||
bool useIssuerAccount
|
||||
= mSrcIssuer && !currencyIsXRP && !isXRP (*mSrcIssuer);
|
||||
auto& account = useIssuerAccount ? *mSrcIssuer : mSrcAccount;
|
||||
auto issuer = currencyIsXRP ? Account() : account;
|
||||
mSource = STPathElement (account, mSrcCurrency, issuer);
|
||||
auto issuerString = mSrcIssuer
|
||||
? to_string (*mSrcIssuer) : std::string ("none");
|
||||
WriteLog (lsTRACE, Pathfinder)
|
||||
<< "findPaths>"
|
||||
<< " mSrcAccount=" << mSrcAccount
|
||||
<< " mDstAccount=" << mDstAccount
|
||||
<< " mDstAmount=" << mDstAmount.getFullText ()
|
||||
<< " mSrcCurrency=" << mSrcCurrency
|
||||
<< " mSrcIssuer=" << mSrcIssuer;
|
||||
<< " mSrcIssuer=" << issuerString;
|
||||
|
||||
if (!mLedger)
|
||||
{
|
||||
@@ -318,11 +333,10 @@ bool Pathfinder::findPaths (int searchLevel)
|
||||
return true;
|
||||
}
|
||||
|
||||
void Pathfinder::ensurePathsAreComplete (STPathSet& pathsOut)
|
||||
void Pathfinder::addPathsFromPreviousPathfinding (STPathSet& pathsOut)
|
||||
{
|
||||
// Add any result paths that aren't in mCompletePaths.
|
||||
// TODO(tom): this is also quadratic in the size of the paths.
|
||||
// TODO(tom): how could a path possibly not be in mCompletePaths?
|
||||
for (auto const& path : pathsOut)
|
||||
{
|
||||
// make sure no paths were lost
|
||||
@@ -338,9 +352,7 @@ void Pathfinder::ensurePathsAreComplete (STPathSet& pathsOut)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(tom): this asssert never triggers. We should probably
|
||||
// remove this whole loop, which might be expensive.
|
||||
assert (found);
|
||||
// TODO(tom): write a test that exercises this code path.
|
||||
if (!found)
|
||||
mCompletePaths.push_back (path);
|
||||
}
|
||||
@@ -410,14 +422,23 @@ TER Pathfinder::getPathLiquidity (
|
||||
}
|
||||
}
|
||||
|
||||
STPathSet Pathfinder::getBestPaths (int maxPaths, STPath& fullLiquidityPath)
|
||||
namespace {
|
||||
|
||||
// Return the smallest amount of useful liquidity for a given amount, and the
|
||||
// total number of paths we have to evaluate.
|
||||
STAmount smallestUsefulAmount (STAmount const& amount, int maxPaths)
|
||||
{
|
||||
assert (fullLiquidityPath.empty ());
|
||||
return divide (amount, STAmount (maxPaths + 2), amount);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Pathfinder::computePathRanks (int maxPaths)
|
||||
{
|
||||
if (mCompletePaths.size () <= maxPaths)
|
||||
return mCompletePaths;
|
||||
return;
|
||||
|
||||
STAmount remaining = mDstAmount;
|
||||
mRemainingAmount = mDstAmount;
|
||||
|
||||
// Must subtract liquidity in default path from remaining amount.
|
||||
try
|
||||
@@ -439,7 +460,7 @@ STPathSet Pathfinder::getBestPaths (int maxPaths, STPath& fullLiquidityPath)
|
||||
{
|
||||
WriteLog (lsDEBUG, Pathfinder)
|
||||
<< "Default path contributes: " << rc.actualAmountIn;
|
||||
remaining -= rc.actualAmountOut;
|
||||
mRemainingAmount -= rc.actualAmountOut;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -453,12 +474,9 @@ STPathSet Pathfinder::getBestPaths (int maxPaths, STPath& fullLiquidityPath)
|
||||
}
|
||||
|
||||
// Ignore paths that move only very small amounts.
|
||||
// TODO(tom): the logic of "very small" is pretty arbitrary here.
|
||||
auto saMinDstAmount = divide (
|
||||
mDstAmount, STAmount (maxPaths + 2), mDstAmount);
|
||||
auto saMinDstAmount = smallestUsefulAmount (mDstAmount, maxPaths);
|
||||
|
||||
// Get the PathRank for each path.
|
||||
std::vector<PathRank> pathRanks;
|
||||
for (int i = 0; i < mCompletePaths.size (); ++i)
|
||||
{
|
||||
auto const& currentPath = mCompletePaths[i];
|
||||
@@ -479,76 +497,130 @@ STPathSet Pathfinder::getBestPaths (int maxPaths, STPath& fullLiquidityPath)
|
||||
"findPaths: quality: " << uQuality <<
|
||||
": " << currentPath.getJson (0);
|
||||
|
||||
pathRanks.push_back ({uQuality, currentPath.size (), liquidity, i});
|
||||
mPathRanks.push_back ({uQuality, currentPath.size (), liquidity, i});
|
||||
}
|
||||
}
|
||||
std::sort (mPathRanks.begin (), mPathRanks.end (), comparePathRank);
|
||||
}
|
||||
|
||||
static bool isDefaultPath (STPath const& path)
|
||||
{
|
||||
// TODO(tom): default paths can consist of more than just an account:
|
||||
// https://forum.ripple.com/viewtopic.php?f=2&t=8206&start=10#p57713
|
||||
//
|
||||
// JoelKatz writes:
|
||||
// So the test for whether a path is a default path is incorrect. I'm not
|
||||
// sure it's worth the complexity of fixing though. If we are going to fix
|
||||
// it, I'd suggest doing it this way:
|
||||
//
|
||||
// 1) Compute the default path, probably by using 'expandPath' to expand an
|
||||
// empty path. 2) Chop off the source and destination nodes.
|
||||
//
|
||||
// 3) In the pathfinding loop, if the source issuer is not the sender,
|
||||
// reject all paths that don't begin with the issuer's account node or match
|
||||
// the path we built at step 2.
|
||||
return path.size() == 1;
|
||||
}
|
||||
|
||||
static STPath removeIssuer (STPath const& path)
|
||||
{
|
||||
// This path starts with the issuer, which is already implied
|
||||
// so remove the head node
|
||||
STPath ret;
|
||||
|
||||
for (auto it = path.begin() + 1; it != path.end(); ++it)
|
||||
ret.push_back (*it);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
STPathSet Pathfinder::getBestPaths (
|
||||
int maxPaths,
|
||||
STPath& fullLiquidityPath,
|
||||
Account const& srcIssuer)
|
||||
{
|
||||
assert (fullLiquidityPath.empty ());
|
||||
const bool issuerIsSender = isXRP (mSrcCurrency) || (srcIssuer == mSrcAccount);
|
||||
|
||||
if (issuerIsSender && (mCompletePaths.size () <= maxPaths))
|
||||
return mCompletePaths;
|
||||
|
||||
STPathSet bestPaths;
|
||||
if (pathRanks.size ())
|
||||
|
||||
// The best PathRanks are now at the start. Pull off enough of them to
|
||||
// fill bestPaths, then look through the rest for the best individual
|
||||
// path that can satisfy the entire liquidity - if one exists.
|
||||
STAmount remaining = mRemainingAmount;
|
||||
for (auto& pathRank: mPathRanks)
|
||||
{
|
||||
std::sort (pathRanks.begin (), pathRanks.end (), comparePathRank);
|
||||
auto iPathsLeft = maxPaths - bestPaths.size ();
|
||||
if (!(iPathsLeft > 0 || fullLiquidityPath.empty ()))
|
||||
break;
|
||||
|
||||
// The best PathRanks are now at the start. Pull off enough of them to
|
||||
// fill bestPaths, then look through the rest for the best individual
|
||||
// path that can satisfy the entire liquidity - if one exists.
|
||||
for (auto& pathRank: pathRanks)
|
||||
auto& path = mCompletePaths[pathRank.index];
|
||||
assert (!path.empty ());
|
||||
if (path.empty ())
|
||||
continue;
|
||||
|
||||
bool startsWithIssuer = false;
|
||||
if (! issuerIsSender)
|
||||
{
|
||||
auto iPathsLeft = maxPaths - bestPaths.size ();
|
||||
if (!(iPathsLeft > 0 || fullLiquidityPath.empty ()))
|
||||
break;
|
||||
|
||||
if (iPathsLeft > 1 ||
|
||||
(iPathsLeft > 0 && pathRank.liquidity >= remaining))
|
||||
if (path.front ().getAccountID() != srcIssuer)
|
||||
continue;
|
||||
if (isDefaultPath (path))
|
||||
{
|
||||
// last path must fill
|
||||
--iPathsLeft;
|
||||
remaining -= pathRank.liquidity;
|
||||
bestPaths.push_back (mCompletePaths[pathRank.index]);
|
||||
}
|
||||
else if (iPathsLeft == 0 &&
|
||||
pathRank.liquidity >= mDstAmount &&
|
||||
fullLiquidityPath.empty ())
|
||||
{
|
||||
// We found an extra path that can move the whole amount.
|
||||
fullLiquidityPath = mCompletePaths[pathRank.index];
|
||||
WriteLog (lsDEBUG, Pathfinder) <<
|
||||
"Found extra full path: " << fullLiquidityPath.getJson (0);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLog (lsDEBUG, Pathfinder) <<
|
||||
"Skipping a non-filling path: " <<
|
||||
mCompletePaths[pathRank.index].getJson (0);
|
||||
continue;
|
||||
}
|
||||
startsWithIssuer = true;
|
||||
}
|
||||
|
||||
if (remaining > zero)
|
||||
if (iPathsLeft > 1 ||
|
||||
(iPathsLeft > 0 && pathRank.liquidity >= remaining))
|
||||
// last path must fill
|
||||
{
|
||||
assert (fullLiquidityPath.empty ());
|
||||
WriteLog (lsINFO, Pathfinder) <<
|
||||
"Paths could not send " << remaining << " of " << mDstAmount;
|
||||
--iPathsLeft;
|
||||
remaining -= pathRank.liquidity;
|
||||
bestPaths.push_back (startsWithIssuer ? removeIssuer (path) : path);
|
||||
}
|
||||
else if (iPathsLeft == 0 &&
|
||||
pathRank.liquidity >= mDstAmount &&
|
||||
fullLiquidityPath.empty ())
|
||||
{
|
||||
// We found an extra path that can move the whole amount.
|
||||
fullLiquidityPath = (startsWithIssuer ? removeIssuer (path) : path);
|
||||
WriteLog (lsDEBUG, Pathfinder) <<
|
||||
"Found extra full path: " << fullLiquidityPath.getJson (0);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLog (lsDEBUG, Pathfinder) <<
|
||||
"findPaths: RESULTS: " << bestPaths.getJson (0);
|
||||
"Skipping a non-filling path: " << path.getJson (0);
|
||||
}
|
||||
}
|
||||
|
||||
if (remaining > zero)
|
||||
{
|
||||
assert (fullLiquidityPath.empty ());
|
||||
WriteLog (lsINFO, Pathfinder) <<
|
||||
"Paths could not send " << remaining << " of " << mDstAmount;
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLog (lsDEBUG, Pathfinder) <<
|
||||
"findPaths: RESULTS: non-defaults filtered away";
|
||||
"findPaths: RESULTS: " << bestPaths.getJson (0);
|
||||
}
|
||||
|
||||
return bestPaths;
|
||||
}
|
||||
|
||||
bool Pathfinder::issueMatchesOrigin (Issue const& issue)
|
||||
{
|
||||
return issue.currency == mSrcCurrency &&
|
||||
(isXRP (issue.currency) ||
|
||||
issue.account == mSrcIssuer ||
|
||||
issue.account == mSrcAccount);
|
||||
bool matchingCurrency = (issue.currency == mSrcCurrency);
|
||||
bool matchingAccount =
|
||||
isXRP (issue.currency) ||
|
||||
(mSrcIssuer && issue.account == mSrcIssuer) ||
|
||||
issue.account == mSrcAccount;
|
||||
|
||||
return matchingCurrency && matchingAccount;
|
||||
}
|
||||
|
||||
int Pathfinder::getPathsOut (
|
||||
@@ -624,7 +696,7 @@ void Pathfinder::addLinks (
|
||||
int addFlags)
|
||||
{
|
||||
WriteLog (lsDEBUG, Pathfinder)
|
||||
<< "addLink< on " << currentPaths.size()
|
||||
<< "addLink< on " << currentPaths.size ()
|
||||
<< " source(s), flags=" << addFlags;
|
||||
for (auto const& path: currentPaths)
|
||||
addLink (path, incompletePaths, addFlags);
|
||||
@@ -725,11 +797,9 @@ bool Pathfinder::isNoRippleOut (STPath const& currentPath)
|
||||
if (!(endElement.getNodeType () & STPathElement::typeAccount))
|
||||
return false;
|
||||
|
||||
// What account are we leaving?
|
||||
// TODO(tom): clarify what's going on here when we only have one item in the
|
||||
// path.
|
||||
// TODO(tom): why aren't we checking that the previous node is also an
|
||||
// account?
|
||||
// If there's only one item in the path, return true if that item specifies
|
||||
// no ripple on the output. A path with no ripple on its output can't be
|
||||
// followed by a link with no ripple on its input.
|
||||
auto const& fromAccount = (currentPath.size () == 1)
|
||||
? mSrcAccount
|
||||
: (currentPath.end () - 2)->getAccountID ();
|
||||
@@ -929,7 +999,7 @@ void Pathfinder::addLink (
|
||||
auto books = getApp ().getOrderBookDB ().getBooksByTakerPays(
|
||||
{uEndCurrency, uEndIssuer});
|
||||
WriteLog (lsTRACE, Pathfinder)
|
||||
<< books.size() << " books found from this currency/issuer";
|
||||
<< books.size () << " books found from this currency/issuer";
|
||||
|
||||
for (auto const& book : books)
|
||||
{
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace ripple {
|
||||
class Pathfinder
|
||||
{
|
||||
public:
|
||||
/** Construct a pathfinder with an issuer.*/
|
||||
Pathfinder (
|
||||
RippleLineCache::ref cache,
|
||||
Account const& srcAccount,
|
||||
@@ -39,18 +40,35 @@ public:
|
||||
Account const& uSrcIssuer,
|
||||
STAmount const& dstAmount);
|
||||
|
||||
/** Construct a pathfinder without an issuer.*/
|
||||
Pathfinder (
|
||||
RippleLineCache::ref cache,
|
||||
Account const& srcAccount,
|
||||
Account const& dstAccount,
|
||||
Currency const& uSrcCurrency,
|
||||
STAmount const& dstAmount);
|
||||
|
||||
~Pathfinder();
|
||||
|
||||
static void initPathTable ();
|
||||
|
||||
bool findPaths (int searchLevel);
|
||||
|
||||
void ensurePathsAreComplete (STPathSet&);
|
||||
/** Make sure that all the input paths are included in mCompletePaths. */
|
||||
void addPathsFromPreviousPathfinding (STPathSet&);
|
||||
|
||||
/** Compute the rankings of the paths. */
|
||||
void computePathRanks (int maxPaths);
|
||||
|
||||
/* Get the best paths, up to maxPaths in number, from mCompletePaths.
|
||||
|
||||
On return, if fullLiquidityPath is not empty, then it contains the best
|
||||
additional single path which can consume all the liquidity.
|
||||
*/
|
||||
STPathSet getBestPaths (int maxPaths, STPath& fullLiquidityPath);
|
||||
STPathSet getBestPaths (
|
||||
int maxPaths,
|
||||
STPath& fullLiquidityPath,
|
||||
Account const& srcIssuer);
|
||||
|
||||
enum NodeType
|
||||
{
|
||||
@@ -76,9 +94,17 @@ public:
|
||||
pt_nonXRP_to_nonXRP // Destination currency is NOT the same as source.
|
||||
};
|
||||
|
||||
struct PathRank
|
||||
{
|
||||
std::uint64_t quality;
|
||||
std::uint64_t length;
|
||||
STAmount liquidity;
|
||||
int index;
|
||||
};
|
||||
|
||||
private:
|
||||
/*
|
||||
Call graph of methoids
|
||||
Call graph of Pathfinder methods.
|
||||
|
||||
findPaths:
|
||||
addPathsForType:
|
||||
@@ -89,12 +115,14 @@ private:
|
||||
isNoRippleOut:
|
||||
isNoRipple
|
||||
|
||||
ensurePathsAreComplete
|
||||
addPathsFromPreviousPathfinding
|
||||
|
||||
getBestPaths:
|
||||
computePathRanks:
|
||||
rippleCalculate
|
||||
getPathLiquidity:
|
||||
rippleCalculate
|
||||
|
||||
getBestPaths
|
||||
*/
|
||||
|
||||
|
||||
@@ -143,8 +171,11 @@ private:
|
||||
Account mDstAccount;
|
||||
STAmount mDstAmount;
|
||||
Currency mSrcCurrency;
|
||||
Account mSrcIssuer;
|
||||
boost::optional<Account> mSrcIssuer;
|
||||
STAmount mSrcAmount;
|
||||
/** The amount remaining from mSrcAccount after the default liquidity has
|
||||
been removed. */
|
||||
STAmount mRemainingAmount;
|
||||
|
||||
Ledger::pointer mLedger;
|
||||
LoadEvent::pointer m_loadEvent;
|
||||
@@ -152,6 +183,7 @@ private:
|
||||
|
||||
STPathElement mSource;
|
||||
STPathSet mCompletePaths;
|
||||
std::vector<PathRank> mPathRanks;
|
||||
std::map<PathType, STPathSet> mPaths;
|
||||
|
||||
hash_map<Issue, int> mPathsOutCountMap;
|
||||
|
||||
@@ -135,6 +135,8 @@ Json::Value doRipplePathFind (RPC::Context& context)
|
||||
// Fill in currencies destination will accept
|
||||
Json::Value jvDestCur (Json::arrayValue);
|
||||
|
||||
// TODO(tom): this could be optimized the same way that
|
||||
// PathRequest::doUpdate() is - if we don't obsolete this code first.
|
||||
auto usDestCurrID = accountDestCurrencies (raDst, cache, true);
|
||||
for (auto const& uCurrency: usDestCurrID)
|
||||
jvDestCur.append (to_string (uCurrency));
|
||||
@@ -144,6 +146,29 @@ Json::Value doRipplePathFind (RPC::Context& context)
|
||||
|
||||
Json::Value jvArray (Json::arrayValue);
|
||||
|
||||
int level = getConfig().PATH_SEARCH_OLD;
|
||||
if ((getConfig().PATH_SEARCH_MAX > level)
|
||||
&& !getApp().getFeeTrack().isLoadedLocal())
|
||||
{
|
||||
++level;
|
||||
}
|
||||
|
||||
if (context.params_.isMember("depth")
|
||||
&& context.params_["depth"].isIntegral())
|
||||
{
|
||||
int rLev = context.params_["search_depth"].asInt ();
|
||||
if ((rLev < level) || (context.role_ == Config::ADMIN))
|
||||
level = rLev;
|
||||
}
|
||||
|
||||
FindPaths fp (
|
||||
cache,
|
||||
raSrc.getAccountID(),
|
||||
raDst.getAccountID(),
|
||||
saDstAmount,
|
||||
level,
|
||||
4); // max paths
|
||||
|
||||
for (unsigned int i = 0; i != jvSrcCurrencies.size (); ++i)
|
||||
{
|
||||
Json::Value jvSource = jvSrcCurrencies[i];
|
||||
@@ -203,14 +228,8 @@ Json::Value doRipplePathFind (RPC::Context& context)
|
||||
}
|
||||
|
||||
STPath fullLiquidityPath;
|
||||
auto valid = findPathsForOneIssuer(
|
||||
cache,
|
||||
raSrc.getAccountID(),
|
||||
raDst.getAccountID(),
|
||||
auto valid = fp.findPathsForIssue (
|
||||
{uSrcCurrencyID, uSrcIssuerID},
|
||||
saDstAmount,
|
||||
level,
|
||||
4, // iMaxPaths
|
||||
spsComputed,
|
||||
fullLiquidityPath);
|
||||
if (!valid)
|
||||
|
||||
@@ -126,7 +126,6 @@ static Json::Value signPayment(
|
||||
&& params.isMember ("build_path"))
|
||||
{
|
||||
// Need a ripple path.
|
||||
STPathSet spsPaths;
|
||||
Currency uSrcCurrencyID;
|
||||
Account uSrcIssuerID;
|
||||
|
||||
@@ -154,6 +153,7 @@ static Json::Value signPayment(
|
||||
return rpcError (rpcTOO_BUSY);
|
||||
|
||||
auto cache = std::make_shared<RippleLineCache> (lSnapshot);
|
||||
STPathSet spsPaths;
|
||||
STPath fullLiquidityPath;
|
||||
auto valid = findPathsForOneIssuer (
|
||||
cache,
|
||||
|
||||
Reference in New Issue
Block a user