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:
Tom Ritchford
2014-10-27 14:45:58 -04:00
parent 6904e66384
commit bb44bdd047
7 changed files with 356 additions and 105 deletions

View File

@@ -22,6 +22,89 @@
namespace ripple { 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 ( bool findPathsForOneIssuer (
RippleLineCache::ref cache, RippleLineCache::ref cache,
Account const& srcAccount, Account const& srcAccount,
@@ -44,9 +127,9 @@ bool findPathsForOneIssuer (
if (!pf.findPaths (searchLevel)) if (!pf.findPaths (searchLevel))
return false; return false;
// Yes, ensurePathsAreComplete is called BEFORE we compute the paths... pf.addPathsFromPreviousPathfinding (pathsOut);
pf.ensurePathsAreComplete (pathsOut); pf.computePathRanks (maxPaths);
pathsOut = pf.getBestPaths(maxPaths, fullLiquidityPath); pathsOut = pf.getBestPaths(maxPaths, fullLiquidityPath, srcIssue.account);
return true; return true;
} }

View File

@@ -22,6 +22,43 @@
namespace ripple { 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 ( bool findPathsForOneIssuer (
RippleLineCache::ref cache, RippleLineCache::ref cache,
Account const& srcAccount, Account const& srcAccount,

View File

@@ -331,6 +331,11 @@ int PathRequest::parseJson (Json::Value const& jvParams, bool complete)
return PFR_PJ_INVALID; return PFR_PJ_INVALID;
} }
if (uCur.isNonZero() && uIss.isZero())
{
uIss = raSrcAccount.getAccountID();
}
sciSourceCurrencies.insert ({uCur, uIss}); sciSourceCurrencies.insert ({uCur, uIss});
} }
} }
@@ -435,6 +440,13 @@ Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast)
bool found = false; bool found = false;
FindPaths fp (
cache,
raSrcAccount.getAccountID (),
raDstAccount.getAccountID (),
saDstAmount,
iLevel,
4); // iMaxPaths
for (auto const& currIssuer: sourceCurrencies) for (auto const& currIssuer: sourceCurrencies)
{ {
{ {
@@ -448,14 +460,8 @@ Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast)
} }
STPathSet& spsPaths = mContext[currIssuer]; STPathSet& spsPaths = mContext[currIssuer];
STPath fullLiquidityPath; STPath fullLiquidityPath;
auto valid = findPathsForOneIssuer ( auto valid = fp.findPathsForIssue (
cache,
raSrcAccount.getAccountID(),
raDstAccount.getAccountID(),
currIssuer, currIssuer,
saDstAmount,
iLevel,
4, // iMaxPaths
spsPaths, spsPaths,
fullLiquidityPath); fullLiquidityPath);
CondLog (!valid, lsDEBUG, PathRequest) CondLog (!valid, lsDEBUG, PathRequest)
@@ -464,12 +470,12 @@ Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast)
if (valid) if (valid)
{ {
LedgerEntrySet lesSandbox (cache->getLedger (), tapNONE); LedgerEntrySet lesSandbox (cache->getLedger (), tapNONE);
auto& account = !isXRP (currIssuer.account) auto& sourceAccount = !isXRP (currIssuer.account)
? currIssuer.account ? currIssuer.account
: isXRP (currIssuer.currency) : isXRP (currIssuer.currency)
? xrpAccount() ? xrpAccount()
: raSrcAccount.getAccountID (); : raSrcAccount.getAccountID ();
STAmount saMaxAmount ({currIssuer.currency, account}, 1); STAmount saMaxAmount ({currIssuer.currency, sourceAccount}, 1);
saMaxAmount.negate (); saMaxAmount.negate ();
m_journal.debug << iIdentifier m_journal.debug << iIdentifier
@@ -488,7 +494,9 @@ Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast)
m_journal.debug m_journal.debug
<< iIdentifier << " Trying with an extra path element"; << iIdentifier << " Trying with an extra path element";
spsPaths.push_back (fullLiquidityPath); spsPaths.push_back (fullLiquidityPath);
rc = path::RippleCalc::rippleCalculate (lesSandbox, lesSandbox.clear();
rc = path::RippleCalc::rippleCalculate (
lesSandbox,
saMaxAmount, saMaxAmount,
saDstAmount, saDstAmount,
raDstAccount.getAccountID (), raDstAccount.getAccountID (),
@@ -507,6 +515,8 @@ Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast)
if (rc.result () == tesSUCCESS) if (rc.result () == tesSUCCESS)
{ {
Json::Value jvEntry (Json::objectValue); Json::Value jvEntry (Json::objectValue);
rc.actualAmountIn.setIssuer (sourceAccount);
jvEntry["source_amount"] = rc.actualAmountIn.getJson (0); jvEntry["source_amount"] = rc.actualAmountIn.getJson (0);
jvEntry["paths_computed"] = spsPaths.getJson (0); jvEntry["paths_computed"] = spsPaths.getJson (0);
found = true; found = true;

View File

@@ -64,17 +64,10 @@ namespace {
// width of path // width of path
// correct currency at the end. // 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 // Compare two PathRanks. A better PathRank is lower, so the best are sorted to
// the beginning. // 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 // 1) Higher quality (lower cost) is better
if (a.quality != b.quality) if (a.quality != b.quality)
@@ -179,12 +172,32 @@ Pathfinder::Pathfinder (
mDstAmount (saDstAmount), mDstAmount (saDstAmount),
mSrcCurrency (uSrcCurrency), mSrcCurrency (uSrcCurrency),
mSrcIssuer (uSrcIssuer), mSrcIssuer (uSrcIssuer),
mSrcAmount ({mSrcCurrency, mSrcIssuer}, 1u, 0, true), mSrcAmount ({uSrcCurrency, uSrcIssuer}, 1u, 0, true),
mLedger (cache->getLedger ()), mLedger (cache->getLedger ()),
mRLCache (cache) 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) bool Pathfinder::findPaths (int searchLevel)
{ {
if (mDstAmount == zero) if (mDstAmount == zero)
@@ -210,19 +223,21 @@ bool Pathfinder::findPaths (int searchLevel)
m_loadEvent = getApp ().getJobQueue ().getLoadEvent ( m_loadEvent = getApp ().getJobQueue ().getLoadEvent (
jtPATH_FIND, "FindPath"); jtPATH_FIND, "FindPath");
auto currencyIsXRP = isXRP (mSrcCurrency); auto currencyIsXRP = isXRP (mSrcCurrency);
auto issuerIsXRP = isXRP (mSrcIssuer);
bool useIssuerAccount = !currencyIsXRP && !issuerIsXRP; bool useIssuerAccount
auto& account = useIssuerAccount ? mSrcIssuer : mSrcAccount; = mSrcIssuer && !currencyIsXRP && !isXRP (*mSrcIssuer);
auto& account = useIssuerAccount ? *mSrcIssuer : mSrcAccount;
auto issuer = currencyIsXRP ? Account() : account; auto issuer = currencyIsXRP ? Account() : account;
mSource = STPathElement (account, mSrcCurrency, issuer); mSource = STPathElement (account, mSrcCurrency, issuer);
auto issuerString = mSrcIssuer
? to_string (*mSrcIssuer) : std::string ("none");
WriteLog (lsTRACE, Pathfinder) WriteLog (lsTRACE, Pathfinder)
<< "findPaths>" << "findPaths>"
<< " mSrcAccount=" << mSrcAccount << " mSrcAccount=" << mSrcAccount
<< " mDstAccount=" << mDstAccount << " mDstAccount=" << mDstAccount
<< " mDstAmount=" << mDstAmount.getFullText () << " mDstAmount=" << mDstAmount.getFullText ()
<< " mSrcCurrency=" << mSrcCurrency << " mSrcCurrency=" << mSrcCurrency
<< " mSrcIssuer=" << mSrcIssuer; << " mSrcIssuer=" << issuerString;
if (!mLedger) if (!mLedger)
{ {
@@ -318,11 +333,10 @@ bool Pathfinder::findPaths (int searchLevel)
return true; return true;
} }
void Pathfinder::ensurePathsAreComplete (STPathSet& pathsOut) void Pathfinder::addPathsFromPreviousPathfinding (STPathSet& pathsOut)
{ {
// Add any result paths that aren't in mCompletePaths. // Add any result paths that aren't in mCompletePaths.
// TODO(tom): this is also quadratic in the size of the paths. // 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) for (auto const& path : pathsOut)
{ {
// make sure no paths were lost // make sure no paths were lost
@@ -338,9 +352,7 @@ void Pathfinder::ensurePathsAreComplete (STPathSet& pathsOut)
} }
} }
// TODO(tom): this asssert never triggers. We should probably // TODO(tom): write a test that exercises this code path.
// remove this whole loop, which might be expensive.
assert (found);
if (!found) if (!found)
mCompletePaths.push_back (path); 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) if (mCompletePaths.size () <= maxPaths)
return mCompletePaths; return;
STAmount remaining = mDstAmount; mRemainingAmount = mDstAmount;
// Must subtract liquidity in default path from remaining amount. // Must subtract liquidity in default path from remaining amount.
try try
@@ -439,7 +460,7 @@ STPathSet Pathfinder::getBestPaths (int maxPaths, STPath& fullLiquidityPath)
{ {
WriteLog (lsDEBUG, Pathfinder) WriteLog (lsDEBUG, Pathfinder)
<< "Default path contributes: " << rc.actualAmountIn; << "Default path contributes: " << rc.actualAmountIn;
remaining -= rc.actualAmountOut; mRemainingAmount -= rc.actualAmountOut;
} }
else else
{ {
@@ -453,12 +474,9 @@ STPathSet Pathfinder::getBestPaths (int maxPaths, STPath& fullLiquidityPath)
} }
// Ignore paths that move only very small amounts. // Ignore paths that move only very small amounts.
// TODO(tom): the logic of "very small" is pretty arbitrary here. auto saMinDstAmount = smallestUsefulAmount (mDstAmount, maxPaths);
auto saMinDstAmount = divide (
mDstAmount, STAmount (maxPaths + 2), mDstAmount);
// Get the PathRank for each path. // Get the PathRank for each path.
std::vector<PathRank> pathRanks;
for (int i = 0; i < mCompletePaths.size (); ++i) for (int i = 0; i < mCompletePaths.size (); ++i)
{ {
auto const& currentPath = mCompletePaths[i]; auto const& currentPath = mCompletePaths[i];
@@ -479,46 +497,104 @@ STPathSet Pathfinder::getBestPaths (int maxPaths, STPath& fullLiquidityPath)
"findPaths: quality: " << uQuality << "findPaths: quality: " << uQuality <<
": " << currentPath.getJson (0); ": " << 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);
}
STPathSet bestPaths; static bool isDefaultPath (STPath const& path)
if (pathRanks.size ())
{ {
std::sort (pathRanks.begin (), pathRanks.end (), comparePathRank); // 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;
// The best PathRanks are now at the start. Pull off enough of them to // 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 // fill bestPaths, then look through the rest for the best individual
// path that can satisfy the entire liquidity - if one exists. // path that can satisfy the entire liquidity - if one exists.
for (auto& pathRank: pathRanks) STAmount remaining = mRemainingAmount;
for (auto& pathRank: mPathRanks)
{ {
auto iPathsLeft = maxPaths - bestPaths.size (); auto iPathsLeft = maxPaths - bestPaths.size ();
if (!(iPathsLeft > 0 || fullLiquidityPath.empty ())) if (!(iPathsLeft > 0 || fullLiquidityPath.empty ()))
break; break;
auto& path = mCompletePaths[pathRank.index];
assert (!path.empty ());
if (path.empty ())
continue;
bool startsWithIssuer = false;
if (! issuerIsSender)
{
if (path.front ().getAccountID() != srcIssuer)
continue;
if (isDefaultPath (path))
{
continue;
}
startsWithIssuer = true;
}
if (iPathsLeft > 1 || if (iPathsLeft > 1 ||
(iPathsLeft > 0 && pathRank.liquidity >= remaining)) (iPathsLeft > 0 && pathRank.liquidity >= remaining))
{
// last path must fill // last path must fill
{
--iPathsLeft; --iPathsLeft;
remaining -= pathRank.liquidity; remaining -= pathRank.liquidity;
bestPaths.push_back (mCompletePaths[pathRank.index]); bestPaths.push_back (startsWithIssuer ? removeIssuer (path) : path);
} }
else if (iPathsLeft == 0 && else if (iPathsLeft == 0 &&
pathRank.liquidity >= mDstAmount && pathRank.liquidity >= mDstAmount &&
fullLiquidityPath.empty ()) fullLiquidityPath.empty ())
{ {
// We found an extra path that can move the whole amount. // We found an extra path that can move the whole amount.
fullLiquidityPath = mCompletePaths[pathRank.index]; fullLiquidityPath = (startsWithIssuer ? removeIssuer (path) : path);
WriteLog (lsDEBUG, Pathfinder) << WriteLog (lsDEBUG, Pathfinder) <<
"Found extra full path: " << fullLiquidityPath.getJson (0); "Found extra full path: " << fullLiquidityPath.getJson (0);
} }
else else
{ {
WriteLog (lsDEBUG, Pathfinder) << WriteLog (lsDEBUG, Pathfinder) <<
"Skipping a non-filling path: " << "Skipping a non-filling path: " << path.getJson (0);
mCompletePaths[pathRank.index].getJson (0);
} }
} }
@@ -533,22 +609,18 @@ STPathSet Pathfinder::getBestPaths (int maxPaths, STPath& fullLiquidityPath)
WriteLog (lsDEBUG, Pathfinder) << WriteLog (lsDEBUG, Pathfinder) <<
"findPaths: RESULTS: " << bestPaths.getJson (0); "findPaths: RESULTS: " << bestPaths.getJson (0);
} }
}
else
{
WriteLog (lsDEBUG, Pathfinder) <<
"findPaths: RESULTS: non-defaults filtered away";
}
return bestPaths; return bestPaths;
} }
bool Pathfinder::issueMatchesOrigin (Issue const& issue) bool Pathfinder::issueMatchesOrigin (Issue const& issue)
{ {
return issue.currency == mSrcCurrency && bool matchingCurrency = (issue.currency == mSrcCurrency);
(isXRP (issue.currency) || bool matchingAccount =
issue.account == mSrcIssuer || isXRP (issue.currency) ||
issue.account == mSrcAccount); (mSrcIssuer && issue.account == mSrcIssuer) ||
issue.account == mSrcAccount;
return matchingCurrency && matchingAccount;
} }
int Pathfinder::getPathsOut ( int Pathfinder::getPathsOut (
@@ -725,11 +797,9 @@ bool Pathfinder::isNoRippleOut (STPath const& currentPath)
if (!(endElement.getNodeType () & STPathElement::typeAccount)) if (!(endElement.getNodeType () & STPathElement::typeAccount))
return false; return false;
// What account are we leaving? // If there's only one item in the path, return true if that item specifies
// TODO(tom): clarify what's going on here when we only have one item in the // no ripple on the output. A path with no ripple on its output can't be
// path. // followed by a link with no ripple on its input.
// TODO(tom): why aren't we checking that the previous node is also an
// account?
auto const& fromAccount = (currentPath.size () == 1) auto const& fromAccount = (currentPath.size () == 1)
? mSrcAccount ? mSrcAccount
: (currentPath.end () - 2)->getAccountID (); : (currentPath.end () - 2)->getAccountID ();

View File

@@ -31,6 +31,7 @@ namespace ripple {
class Pathfinder class Pathfinder
{ {
public: public:
/** Construct a pathfinder with an issuer.*/
Pathfinder ( Pathfinder (
RippleLineCache::ref cache, RippleLineCache::ref cache,
Account const& srcAccount, Account const& srcAccount,
@@ -39,18 +40,35 @@ public:
Account const& uSrcIssuer, Account const& uSrcIssuer,
STAmount const& dstAmount); 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 (); static void initPathTable ();
bool findPaths (int searchLevel); 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. /* Get the best paths, up to maxPaths in number, from mCompletePaths.
On return, if fullLiquidityPath is not empty, then it contains the best On return, if fullLiquidityPath is not empty, then it contains the best
additional single path which can consume all the liquidity. 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 enum NodeType
{ {
@@ -76,9 +94,17 @@ public:
pt_nonXRP_to_nonXRP // Destination currency is NOT the same as source. 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: private:
/* /*
Call graph of methoids Call graph of Pathfinder methods.
findPaths: findPaths:
addPathsForType: addPathsForType:
@@ -89,12 +115,14 @@ private:
isNoRippleOut: isNoRippleOut:
isNoRipple isNoRipple
ensurePathsAreComplete addPathsFromPreviousPathfinding
getBestPaths: computePathRanks:
rippleCalculate rippleCalculate
getPathLiquidity: getPathLiquidity:
rippleCalculate rippleCalculate
getBestPaths
*/ */
@@ -143,8 +171,11 @@ private:
Account mDstAccount; Account mDstAccount;
STAmount mDstAmount; STAmount mDstAmount;
Currency mSrcCurrency; Currency mSrcCurrency;
Account mSrcIssuer; boost::optional<Account> mSrcIssuer;
STAmount mSrcAmount; STAmount mSrcAmount;
/** The amount remaining from mSrcAccount after the default liquidity has
been removed. */
STAmount mRemainingAmount;
Ledger::pointer mLedger; Ledger::pointer mLedger;
LoadEvent::pointer m_loadEvent; LoadEvent::pointer m_loadEvent;
@@ -152,6 +183,7 @@ private:
STPathElement mSource; STPathElement mSource;
STPathSet mCompletePaths; STPathSet mCompletePaths;
std::vector<PathRank> mPathRanks;
std::map<PathType, STPathSet> mPaths; std::map<PathType, STPathSet> mPaths;
hash_map<Issue, int> mPathsOutCountMap; hash_map<Issue, int> mPathsOutCountMap;

View File

@@ -135,6 +135,8 @@ Json::Value doRipplePathFind (RPC::Context& context)
// Fill in currencies destination will accept // Fill in currencies destination will accept
Json::Value jvDestCur (Json::arrayValue); 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); auto usDestCurrID = accountDestCurrencies (raDst, cache, true);
for (auto const& uCurrency: usDestCurrID) for (auto const& uCurrency: usDestCurrID)
jvDestCur.append (to_string (uCurrency)); jvDestCur.append (to_string (uCurrency));
@@ -144,6 +146,29 @@ Json::Value doRipplePathFind (RPC::Context& context)
Json::Value jvArray (Json::arrayValue); 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) for (unsigned int i = 0; i != jvSrcCurrencies.size (); ++i)
{ {
Json::Value jvSource = jvSrcCurrencies[i]; Json::Value jvSource = jvSrcCurrencies[i];
@@ -203,14 +228,8 @@ Json::Value doRipplePathFind (RPC::Context& context)
} }
STPath fullLiquidityPath; STPath fullLiquidityPath;
auto valid = findPathsForOneIssuer( auto valid = fp.findPathsForIssue (
cache,
raSrc.getAccountID(),
raDst.getAccountID(),
{uSrcCurrencyID, uSrcIssuerID}, {uSrcCurrencyID, uSrcIssuerID},
saDstAmount,
level,
4, // iMaxPaths
spsComputed, spsComputed,
fullLiquidityPath); fullLiquidityPath);
if (!valid) if (!valid)

View File

@@ -126,7 +126,6 @@ static Json::Value signPayment(
&& params.isMember ("build_path")) && params.isMember ("build_path"))
{ {
// Need a ripple path. // Need a ripple path.
STPathSet spsPaths;
Currency uSrcCurrencyID; Currency uSrcCurrencyID;
Account uSrcIssuerID; Account uSrcIssuerID;
@@ -154,6 +153,7 @@ static Json::Value signPayment(
return rpcError (rpcTOO_BUSY); return rpcError (rpcTOO_BUSY);
auto cache = std::make_shared<RippleLineCache> (lSnapshot); auto cache = std::make_shared<RippleLineCache> (lSnapshot);
STPathSet spsPaths;
STPath fullLiquidityPath; STPath fullLiquidityPath;
auto valid = findPathsForOneIssuer ( auto valid = findPathsForOneIssuer (
cache, cache,