mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-05 16:57:56 +00:00
Convert all of an asset (RIPD-655)
This commit is contained in:
committed by
Scott Schurr
parent
1842878c40
commit
3d777f3f5d
@@ -30,31 +30,32 @@ public:
|
||||
AccountID const& srcAccount,
|
||||
AccountID const& dstAccount,
|
||||
STAmount const& dstAmount,
|
||||
boost::optional<STAmount> const& srcAmount,
|
||||
int searchLevel,
|
||||
unsigned int maxPaths)
|
||||
: cache_ (cache),
|
||||
srcAccount_ (srcAccount),
|
||||
dstAccount_ (dstAccount),
|
||||
dstAmount_ (dstAmount),
|
||||
srcAmount_ (srcAmount),
|
||||
searchLevel_ (searchLevel),
|
||||
maxPaths_ (maxPaths)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
bool findPathsForIssue (
|
||||
boost::optional<STPathSet>
|
||||
findPathsForIssue (
|
||||
Issue const& issue,
|
||||
STPathSet& pathsInOut,
|
||||
STPathSet const& paths,
|
||||
STPath& fullLiquidityPath)
|
||||
{
|
||||
if (auto& pathfinder = getPathFinder (issue.currency))
|
||||
{
|
||||
pathsInOut = pathfinder->getBestPaths (
|
||||
maxPaths_, fullLiquidityPath, pathsInOut, issue.account);
|
||||
return true;
|
||||
return pathfinder->getBestPaths (maxPaths_,
|
||||
fullLiquidityPath, paths, issue.account);
|
||||
}
|
||||
assert (false);
|
||||
return false;
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -64,6 +65,7 @@ private:
|
||||
AccountID const srcAccount_;
|
||||
AccountID const dstAccount_;
|
||||
STAmount const dstAmount_;
|
||||
boost::optional<STAmount> const srcAmount_;
|
||||
int const searchLevel_;
|
||||
unsigned int const maxPaths_;
|
||||
|
||||
@@ -73,7 +75,8 @@ private:
|
||||
if (i != currencyMap_.end ())
|
||||
return i->second;
|
||||
auto pathfinder = std::make_unique<Pathfinder> (
|
||||
cache_, srcAccount_, dstAccount_, currency, dstAmount_);
|
||||
cache_, srcAccount_, dstAccount_, currency,
|
||||
boost::none, dstAmount_, srcAmount_);
|
||||
if (pathfinder->findPaths (searchLevel_))
|
||||
pathfinder->computePathRanks (maxPaths_);
|
||||
else
|
||||
@@ -90,24 +93,28 @@ FindPaths::FindPaths (
|
||||
AccountID const& srcAccount,
|
||||
AccountID const& dstAccount,
|
||||
STAmount const& dstAmount,
|
||||
boost::optional<STAmount> const& srcAmount,
|
||||
int level,
|
||||
unsigned int maxPaths)
|
||||
: impl_ (std::make_unique<Impl> (
|
||||
cache, srcAccount, dstAccount, dstAmount, level, maxPaths))
|
||||
cache, srcAccount, dstAccount,
|
||||
dstAmount, srcAmount, level, maxPaths))
|
||||
{
|
||||
}
|
||||
|
||||
FindPaths::~FindPaths() = default;
|
||||
|
||||
bool FindPaths::findPathsForIssue (
|
||||
boost::optional<STPathSet>
|
||||
FindPaths::findPathsForIssue (
|
||||
Issue const& issue,
|
||||
STPathSet& pathsInOut,
|
||||
STPathSet const& paths,
|
||||
STPath& fullLiquidityPath)
|
||||
{
|
||||
return impl_->findPathsForIssue (issue, pathsInOut, fullLiquidityPath);
|
||||
return impl_->findPathsForIssue (issue, paths, fullLiquidityPath);
|
||||
}
|
||||
|
||||
bool findPathsForOneIssuer (
|
||||
boost::optional<STPathSet>
|
||||
findPathsForOneIssuer (
|
||||
RippleLineCache::ref cache,
|
||||
AccountID const& srcAccount,
|
||||
AccountID const& dstAccount,
|
||||
@@ -115,7 +122,7 @@ bool findPathsForOneIssuer (
|
||||
STAmount const& dstAmount,
|
||||
int searchLevel,
|
||||
unsigned int const maxPaths,
|
||||
STPathSet& pathsInOut,
|
||||
STPathSet const& paths,
|
||||
STPath& fullLiquidityPath)
|
||||
{
|
||||
Pathfinder pf (
|
||||
@@ -124,14 +131,15 @@ bool findPathsForOneIssuer (
|
||||
dstAccount,
|
||||
srcIssue.currency,
|
||||
srcIssue.account,
|
||||
dstAmount);
|
||||
dstAmount,
|
||||
boost::none);
|
||||
|
||||
if (!pf.findPaths (searchLevel))
|
||||
return false;
|
||||
if (! pf.findPaths(searchLevel))
|
||||
return boost::none;
|
||||
|
||||
pf.computePathRanks (maxPaths);
|
||||
pathsInOut = pf.getBestPaths(maxPaths, fullLiquidityPath, pathsInOut, srcIssue.account);
|
||||
return true;
|
||||
return pf.getBestPaths(maxPaths, fullLiquidityPath,
|
||||
paths, srcIssue.account);
|
||||
}
|
||||
|
||||
void initializePathfinding ()
|
||||
|
||||
@@ -32,6 +32,7 @@ public:
|
||||
AccountID const& srcAccount,
|
||||
AccountID const& dstAccount,
|
||||
STAmount const& dstAmount,
|
||||
boost::optional<STAmount> const& srcAmount,
|
||||
/** searchLevel is the maximum search level allowed in an output path.
|
||||
*/
|
||||
int searchLevel,
|
||||
@@ -40,15 +41,15 @@ public:
|
||||
unsigned int const maxPaths);
|
||||
~FindPaths();
|
||||
|
||||
bool findPathsForIssue (
|
||||
/** The return value will have any additional paths found. Only
|
||||
non-default paths without source or destination will be added. */
|
||||
boost::optional<STPathSet>
|
||||
findPathsForIssue (
|
||||
Issue const& issue,
|
||||
|
||||
/** On input, pathsInOut contains any paths you want to ensure are
|
||||
included if still good.
|
||||
|
||||
On output, pathsInOut will have any additional paths found. Only
|
||||
non-default paths without source or destination will be added. */
|
||||
STPathSet& pathsInOut,
|
||||
/** Contains any paths you want to ensure are
|
||||
included if still good. */
|
||||
STPathSet const& paths,
|
||||
|
||||
/** On input, fullLiquidityPath must be an empty STPath.
|
||||
|
||||
@@ -61,7 +62,10 @@ private:
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
bool findPathsForOneIssuer (
|
||||
/** The return value will have any additional paths found. Only
|
||||
non-default paths without source or destination will be added. */
|
||||
boost::optional<STPathSet>
|
||||
findPathsForOneIssuer (
|
||||
RippleLineCache::ref cache,
|
||||
AccountID const& srcAccount,
|
||||
AccountID const& dstAccount,
|
||||
@@ -75,12 +79,10 @@ bool findPathsForOneIssuer (
|
||||
pathsOut. */
|
||||
unsigned int const maxPaths,
|
||||
|
||||
/** On input, pathsInOut contains any paths you want to ensure are included if
|
||||
/** Contains any paths you want to ensure are included if
|
||||
still good.
|
||||
|
||||
On output, pathsInOut will have any additional paths found. Only
|
||||
non-default paths without source or destination will be added. */
|
||||
STPathSet& pathsInOut,
|
||||
*/
|
||||
STPathSet const& paths,
|
||||
|
||||
/** On input, fullLiquidityPath must be an empty STPath.
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/app/paths/AccountCurrencies.h>
|
||||
#include <ripple/app/paths/FindPaths.h>
|
||||
#include <ripple/app/paths/RippleCalc.h>
|
||||
#include <ripple/app/paths/PathRequest.h>
|
||||
#include <ripple/app/paths/PathRequests.h>
|
||||
@@ -46,7 +45,6 @@ PathRequest::PathRequest (
|
||||
, mOwner (owner)
|
||||
, wpSubscriber (subscriber)
|
||||
, jvStatus (Json::objectValue)
|
||||
, bValid (false)
|
||||
, mLastIndex (0)
|
||||
, mInProgress (false)
|
||||
, iLastLevel (0)
|
||||
@@ -67,7 +65,6 @@ PathRequest::PathRequest (
|
||||
, mOwner (owner)
|
||||
, fCompletion (completion)
|
||||
, jvStatus (Json::objectValue)
|
||||
, bValid (false)
|
||||
, mLastIndex (0)
|
||||
, mInProgress (false)
|
||||
, iLastLevel (0)
|
||||
@@ -114,12 +111,6 @@ PathRequest::~PathRequest()
|
||||
" total:" << get_milli_diff(ptCreated) << "ms";
|
||||
}
|
||||
|
||||
bool PathRequest::isValid ()
|
||||
{
|
||||
ScopedLockType sl (mLock);
|
||||
return bValid;
|
||||
}
|
||||
|
||||
bool PathRequest::isNew ()
|
||||
{
|
||||
ScopedLockType sl (mIndexLock);
|
||||
@@ -175,45 +166,47 @@ void PathRequest::updateComplete ()
|
||||
bool PathRequest::isValid (RippleLineCache::ref crCache)
|
||||
{
|
||||
ScopedLockType sl (mLock);
|
||||
bValid = raSrcAccount && raDstAccount &&
|
||||
saDstAmount > zero;
|
||||
auto const& lrLedger = crCache->getLedger ();
|
||||
if (! raSrcAccount || ! raDstAccount)
|
||||
return false;
|
||||
|
||||
if (bValid)
|
||||
if (! convert_all_ && (saSendMax || saDstAmount <= zero))
|
||||
{
|
||||
// If send max specified, dst amt must be -1.
|
||||
jvStatus = rpcError(rpcDST_AMT_MALFORMED);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! crCache->getLedger()->exists(
|
||||
keylet::account(*raSrcAccount)))
|
||||
{
|
||||
// no source account
|
||||
bValid = false;
|
||||
// Source account does not exist.
|
||||
jvStatus = rpcError (rpcSRC_ACT_NOT_FOUND);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bValid)
|
||||
{
|
||||
auto const& lrLedger = crCache->getLedger();
|
||||
auto const sleDest = crCache->getLedger()->read(
|
||||
keylet::account(*raDstAccount));
|
||||
|
||||
Json::Value& jvDestCur =
|
||||
(jvStatus[jss::destination_currencies] = Json::arrayValue);
|
||||
|
||||
if (!sleDest)
|
||||
if (! sleDest)
|
||||
{
|
||||
// no destination account
|
||||
jvDestCur.append (Json::Value ("XRP"));
|
||||
|
||||
if (!saDstAmount.native ())
|
||||
jvDestCur.append (Json::Value (systemCurrencyCode()));
|
||||
if (! saDstAmount.native ())
|
||||
{
|
||||
// only XRP can be send to a non-existent account
|
||||
bValid = false;
|
||||
// Only XRP can be send to a non-existent account.
|
||||
jvStatus = rpcError (rpcACT_NOT_FOUND);
|
||||
return false;
|
||||
}
|
||||
else if (saDstAmount < STAmount (lrLedger->fees().accountReserve (0)))
|
||||
|
||||
if (! convert_all_ && saDstAmount <
|
||||
STAmount (lrLedger->fees().accountReserve (0)))
|
||||
{
|
||||
// payment must meet reserve
|
||||
bValid = false;
|
||||
// Payment must meet reserve.
|
||||
jvStatus = rpcError (rpcDST_AMT_MALFORMED);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -222,23 +215,17 @@ bool PathRequest::isValid (RippleLineCache::ref crCache)
|
||||
sleDest->getFlags() & lsfDisallowXRP);
|
||||
|
||||
auto usDestCurrID = accountDestCurrencies (
|
||||
*raDstAccount, crCache, !disallowXRP);
|
||||
*raDstAccount, crCache, ! disallowXRP);
|
||||
|
||||
for (auto const& currency : usDestCurrID)
|
||||
jvDestCur.append (to_string (currency));
|
||||
|
||||
jvStatus["destination_tag"] =
|
||||
(sleDest->getFlags () & lsfRequireDestTag)
|
||||
!= 0;
|
||||
}
|
||||
jvStatus[jss::destination_tag] =
|
||||
(sleDest->getFlags() & lsfRequireDestTag);
|
||||
}
|
||||
|
||||
if (bValid)
|
||||
{
|
||||
jvStatus[jss::ledger_hash] = to_string (lrLedger->info().hash);
|
||||
jvStatus[jss::ledger_index] = lrLedger->seq();
|
||||
}
|
||||
return bValid;
|
||||
return true;
|
||||
}
|
||||
|
||||
Json::Value PathRequest::doCreate (
|
||||
@@ -246,27 +233,22 @@ Json::Value PathRequest::doCreate (
|
||||
Json::Value const& value,
|
||||
bool& valid)
|
||||
{
|
||||
|
||||
Json::Value status;
|
||||
|
||||
if (parseJson (value, true) != PFR_PJ_INVALID)
|
||||
if (parseJson (value) != PFR_PJ_INVALID)
|
||||
{
|
||||
bValid = isValid (cache);
|
||||
|
||||
if (bValid)
|
||||
status = doUpdate (cache, true);
|
||||
else
|
||||
status = jvStatus;
|
||||
valid = isValid (cache);
|
||||
status = valid ? doUpdate(cache, true) : jvStatus;
|
||||
}
|
||||
else
|
||||
{
|
||||
bValid = false;
|
||||
valid = false;
|
||||
status = jvStatus;
|
||||
}
|
||||
|
||||
if (m_journal.debug)
|
||||
{
|
||||
if (bValid)
|
||||
if (valid)
|
||||
{
|
||||
m_journal.debug << iIdentifier
|
||||
<< " valid: " << toBase58(*raSrcAccount);
|
||||
@@ -279,16 +261,29 @@ Json::Value PathRequest::doCreate (
|
||||
}
|
||||
}
|
||||
|
||||
valid = bValid;
|
||||
return status;
|
||||
}
|
||||
|
||||
int PathRequest::parseJson (Json::Value const& jvParams, bool complete)
|
||||
int PathRequest::parseJson (Json::Value const& jvParams)
|
||||
{
|
||||
int ret = PFR_PJ_NOCHANGE;
|
||||
|
||||
if (jvParams.isMember (jss::source_account))
|
||||
if (! jvParams.isMember(jss::source_account))
|
||||
{
|
||||
jvStatus = rpcError(rpcSRC_ACT_MISSING);
|
||||
return PFR_PJ_INVALID;
|
||||
}
|
||||
|
||||
if (! jvParams.isMember(jss::destination_account))
|
||||
{
|
||||
jvStatus = rpcError(rpcDST_ACT_MISSING);
|
||||
return PFR_PJ_INVALID;
|
||||
}
|
||||
|
||||
if (! jvParams.isMember(jss::destination_amount))
|
||||
{
|
||||
jvStatus = rpcError(rpcDST_AMT_MISSING);
|
||||
return PFR_PJ_INVALID;
|
||||
}
|
||||
|
||||
raSrcAccount = parseBase58<AccountID>(
|
||||
jvParams[jss::source_account].asString());
|
||||
if (! raSrcAccount)
|
||||
@@ -296,15 +291,7 @@ int PathRequest::parseJson (Json::Value const& jvParams, bool complete)
|
||||
jvStatus = rpcError (rpcSRC_ACT_MALFORMED);
|
||||
return PFR_PJ_INVALID;
|
||||
}
|
||||
}
|
||||
else if (complete)
|
||||
{
|
||||
jvStatus = rpcError (rpcSRC_ACT_MISSING);
|
||||
return PFR_PJ_INVALID;
|
||||
}
|
||||
|
||||
if (jvParams.isMember (jss::destination_account))
|
||||
{
|
||||
raDstAccount = parseBase58<AccountID>(
|
||||
jvParams[jss::destination_account].asString());
|
||||
if (! raDstAccount)
|
||||
@@ -312,32 +299,49 @@ int PathRequest::parseJson (Json::Value const& jvParams, bool complete)
|
||||
jvStatus = rpcError (rpcDST_ACT_MALFORMED);
|
||||
return PFR_PJ_INVALID;
|
||||
}
|
||||
}
|
||||
else if (complete)
|
||||
{
|
||||
jvStatus = rpcError (rpcDST_ACT_MISSING);
|
||||
return PFR_PJ_INVALID;
|
||||
}
|
||||
|
||||
if (jvParams.isMember (jss::destination_amount))
|
||||
{
|
||||
if (! amountFromJsonNoThrow (
|
||||
saDstAmount, jvParams[jss::destination_amount]) ||
|
||||
(saDstAmount.getCurrency ().isZero () &&
|
||||
saDstAmount.getIssuer ().isNonZero ()) ||
|
||||
(saDstAmount.getCurrency () == badCurrency ()) ||
|
||||
saDstAmount <= zero)
|
||||
saDstAmount, jvParams[jss::destination_amount]))
|
||||
{
|
||||
jvStatus = rpcError (rpcDST_AMT_MALFORMED);
|
||||
return PFR_PJ_INVALID;
|
||||
}
|
||||
}
|
||||
else if (complete)
|
||||
|
||||
convert_all_ =
|
||||
saDstAmount == STAmount(saDstAmount.issue(), 1u, 0, true);
|
||||
|
||||
if ((saDstAmount.getCurrency ().isZero () &&
|
||||
saDstAmount.getIssuer ().isNonZero ()) ||
|
||||
(saDstAmount.getCurrency () == badCurrency ()) ||
|
||||
(! convert_all_ && saDstAmount <= zero))
|
||||
{
|
||||
jvStatus = rpcError (rpcDST_ACT_MISSING);
|
||||
jvStatus = rpcError (rpcDST_AMT_MALFORMED);
|
||||
return PFR_PJ_INVALID;
|
||||
}
|
||||
|
||||
if (jvParams.isMember(jss::send_max))
|
||||
{
|
||||
// Send_max requires destination amount to be -1.
|
||||
if (! convert_all_)
|
||||
{
|
||||
jvStatus = rpcError(rpcDST_AMT_MALFORMED);
|
||||
return PFR_PJ_INVALID;
|
||||
}
|
||||
|
||||
saSendMax.emplace();
|
||||
if (! amountFromJsonNoThrow(
|
||||
*saSendMax, jvParams[jss::send_max]) ||
|
||||
(saSendMax->getCurrency().isZero() &&
|
||||
saSendMax->getIssuer().isNonZero()) ||
|
||||
(saSendMax->getCurrency() == badCurrency()) ||
|
||||
(*saSendMax <= zero &&
|
||||
*saSendMax != STAmount(saSendMax->issue(), 1u, 0, true)))
|
||||
{
|
||||
jvStatus = rpcError(rpcSENDMAX_MALFORMED);
|
||||
return PFR_PJ_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
if (jvParams.isMember (jss::source_currencies))
|
||||
{
|
||||
Json::Value const& jvSrcCur = jvParams[jss::source_currencies];
|
||||
@@ -356,8 +360,9 @@ int PathRequest::parseJson (Json::Value const& jvParams, bool complete)
|
||||
Currency uCur;
|
||||
AccountID uIss;
|
||||
|
||||
if (!jvCur.isObject() || !jvCur.isMember (jss::currency) ||
|
||||
!to_currency (uCur, jvCur[jss::currency].asString ()))
|
||||
if (! jvCur.isObject() ||
|
||||
! jvCur.isMember (jss::currency) ||
|
||||
! to_currency (uCur, jvCur[jss::currency].asString ()))
|
||||
{
|
||||
jvStatus = rpcError (rpcSRC_CUR_MALFORMED);
|
||||
return PFR_PJ_INVALID;
|
||||
@@ -376,19 +381,46 @@ int PathRequest::parseJson (Json::Value const& jvParams, bool complete)
|
||||
}
|
||||
|
||||
if (uCur.isNonZero() && uIss.isZero())
|
||||
{
|
||||
uIss = *raSrcAccount;
|
||||
|
||||
if (saSendMax)
|
||||
{
|
||||
// If the currencies don't match, ignore the source currency.
|
||||
if (uCur == saSendMax->getCurrency())
|
||||
{
|
||||
// If neither is the source and they are not equal, then the
|
||||
// source issuer is illegal.
|
||||
if (uIss != *raSrcAccount &&
|
||||
saSendMax->getIssuer() != *raSrcAccount &&
|
||||
uIss != saSendMax->getIssuer())
|
||||
{
|
||||
jvStatus = rpcError (rpcSRC_ISR_MALFORMED);
|
||||
return PFR_PJ_INVALID;
|
||||
}
|
||||
|
||||
sciSourceCurrencies.insert ({uCur, uIss});
|
||||
// If both are the source, use the source.
|
||||
// Otherwise, use the one that's not the source.
|
||||
if (uIss != *raSrcAccount)
|
||||
sciSourceCurrencies.insert({uCur, uIss});
|
||||
else if (saSendMax->getIssuer() != *raSrcAccount)
|
||||
sciSourceCurrencies.insert({uCur, saSendMax->getIssuer()});
|
||||
else
|
||||
sciSourceCurrencies.insert({uCur, *raSrcAccount});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sciSourceCurrencies.insert({uCur, uIss});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (jvParams.isMember ("id"))
|
||||
jvId = jvParams["id"];
|
||||
|
||||
return ret;
|
||||
return PFR_PJ_NOCHANGE;
|
||||
}
|
||||
|
||||
Json::Value PathRequest::doClose (Json::Value const&)
|
||||
{
|
||||
m_journal.debug << iIdentifier << " closed";
|
||||
@@ -408,6 +440,94 @@ void PathRequest::resetLevel (int l)
|
||||
iLastLevel = l;
|
||||
}
|
||||
|
||||
bool
|
||||
PathRequest::findPaths (
|
||||
RippleLineCache::ref cache,
|
||||
FindPaths& fp, Issue const& issue,
|
||||
Json::Value& jvArray)
|
||||
{
|
||||
STPath fullLiquidityPath;
|
||||
auto result = fp.findPathsForIssue(issue,
|
||||
mContext[issue], fullLiquidityPath);
|
||||
if (! result)
|
||||
{
|
||||
m_journal.debug << iIdentifier << " No paths found";
|
||||
return false;
|
||||
}
|
||||
mContext[issue] = *result;
|
||||
|
||||
boost::optional<PaymentSandbox> sandbox;
|
||||
sandbox.emplace(&*cache->getLedger(), tapNONE);
|
||||
|
||||
auto& sourceAccount = ! isXRP(issue.account)
|
||||
? issue.account
|
||||
: isXRP(issue.currency)
|
||||
? xrpAccount()
|
||||
: *raSrcAccount;
|
||||
|
||||
STAmount saMaxAmount = saSendMax.value_or(
|
||||
STAmount({issue.currency, sourceAccount}, 1u, 0, true));
|
||||
|
||||
m_journal.debug << iIdentifier
|
||||
<< " Paths found, calling rippleCalc";
|
||||
|
||||
auto rc = path::RippleCalc::rippleCalculate(
|
||||
*sandbox, saMaxAmount,
|
||||
convert_all_ ? STAmount(saDstAmount.issue(), STAmount::cMaxValue,
|
||||
STAmount::cMaxOffset) : saDstAmount,
|
||||
*raDstAccount, *raSrcAccount, *result);
|
||||
|
||||
if (! convert_all_ &&
|
||||
! fullLiquidityPath.empty() &&
|
||||
(rc.result() == terNO_LINE || rc.result() == tecPATH_PARTIAL))
|
||||
{
|
||||
m_journal.debug << iIdentifier
|
||||
<< " Trying with an extra path element";
|
||||
result->push_back(fullLiquidityPath);
|
||||
sandbox.emplace(&*cache->getLedger(), tapNONE);
|
||||
|
||||
rc = path::RippleCalc::rippleCalculate(
|
||||
*sandbox, saMaxAmount, saDstAmount,
|
||||
*raDstAccount, *raSrcAccount, *result);
|
||||
if (rc.result() != tesSUCCESS)
|
||||
{
|
||||
m_journal.warning << iIdentifier
|
||||
<< " Failed with covering path "
|
||||
<< transHuman(rc.result());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_journal.debug << iIdentifier
|
||||
<< " Extra path element gives "
|
||||
<< transHuman(rc.result());
|
||||
}
|
||||
}
|
||||
|
||||
if (rc.result () == tesSUCCESS)
|
||||
{
|
||||
Json::Value jvEntry (Json::objectValue);
|
||||
rc.actualAmountIn.setIssuer (sourceAccount);
|
||||
jvEntry[jss::source_amount] = rc.actualAmountIn.getJson (0);
|
||||
jvEntry[jss::paths_computed] = result->getJson(0);
|
||||
|
||||
if (convert_all_)
|
||||
jvEntry[jss::destination_amount] = rc.actualAmountOut.getJson(0);
|
||||
|
||||
if (hasCompletion ())
|
||||
{
|
||||
// Old ripple_path_find API requires this
|
||||
jvEntry[jss::paths_canonical] = Json::arrayValue;
|
||||
}
|
||||
|
||||
jvArray.append (jvEntry);
|
||||
return true;
|
||||
}
|
||||
|
||||
m_journal.debug << iIdentifier << " rippleCalc returns "
|
||||
<< transHuman (rc.result ());
|
||||
return false;
|
||||
}
|
||||
|
||||
Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast)
|
||||
{
|
||||
m_journal.debug << iIdentifier << " update " << (fast ? "fast" : "normal");
|
||||
@@ -454,8 +574,6 @@ Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast)
|
||||
if (jvId)
|
||||
jvStatus["id"] = jvId;
|
||||
|
||||
Json::Value jvArray = Json::arrayValue;
|
||||
|
||||
int iLevel = iLastLevel;
|
||||
bool loaded = getApp().getFeeTrack().isLoadedLocal();
|
||||
|
||||
@@ -493,108 +611,24 @@ Json::Value PathRequest::doUpdate (RippleLineCache::ref cache, bool fast)
|
||||
m_journal.debug << iIdentifier << " processing at level " << iLevel;
|
||||
|
||||
bool found = false;
|
||||
|
||||
Json::Value jvArray = Json::arrayValue;
|
||||
FindPaths fp (
|
||||
cache,
|
||||
*raSrcAccount,
|
||||
*raDstAccount,
|
||||
saDstAmount,
|
||||
convert_all_ ? STAmount(saDstAmount.issue(), STAmount::cMaxValue,
|
||||
STAmount::cMaxOffset) : saDstAmount,
|
||||
saSendMax,
|
||||
iLevel,
|
||||
4); // iMaxPaths
|
||||
for (auto const& currIssuer: sourceCurrencies)
|
||||
for (auto const& i: sourceCurrencies)
|
||||
{
|
||||
{
|
||||
STAmount test (currIssuer, 1);
|
||||
if (m_journal.debug)
|
||||
{
|
||||
m_journal.debug
|
||||
JLOG(m_journal.debug)
|
||||
<< iIdentifier
|
||||
<< " Trying to find paths: " << test.getFullText ();
|
||||
}
|
||||
}
|
||||
STPathSet& spsPaths = mContext[currIssuer];
|
||||
STPath fullLiquidityPath;
|
||||
auto valid = fp.findPathsForIssue (
|
||||
currIssuer,
|
||||
spsPaths,
|
||||
fullLiquidityPath);
|
||||
CondLog (!valid, lsDEBUG, PathRequest)
|
||||
<< iIdentifier << " PF request not valid";
|
||||
|
||||
if (valid)
|
||||
{
|
||||
boost::optional<PaymentSandbox> sandbox;
|
||||
sandbox.emplace(&*cache->getLedger(), tapNONE);
|
||||
|
||||
auto& sourceAccount = !isXRP (currIssuer.account)
|
||||
? currIssuer.account
|
||||
: isXRP (currIssuer.currency)
|
||||
? xrpAccount()
|
||||
: *raSrcAccount;
|
||||
STAmount saMaxAmount ({currIssuer.currency, sourceAccount}, 1);
|
||||
|
||||
saMaxAmount.negate ();
|
||||
m_journal.debug << iIdentifier
|
||||
<< " Paths found, calling rippleCalc";
|
||||
auto rc = path::RippleCalc::rippleCalculate (
|
||||
*sandbox,
|
||||
saMaxAmount,
|
||||
saDstAmount,
|
||||
*raDstAccount,
|
||||
*raSrcAccount,
|
||||
spsPaths);
|
||||
|
||||
if (!fullLiquidityPath.empty() &&
|
||||
(rc.result () == terNO_LINE || rc.result () == tecPATH_PARTIAL))
|
||||
{
|
||||
m_journal.debug
|
||||
<< iIdentifier << " Trying with an extra path element";
|
||||
spsPaths.push_back (fullLiquidityPath);
|
||||
sandbox.emplace(&*cache->getLedger(), tapNONE);
|
||||
rc = path::RippleCalc::rippleCalculate (
|
||||
*sandbox,
|
||||
saMaxAmount,
|
||||
saDstAmount,
|
||||
*raDstAccount,
|
||||
*raSrcAccount,
|
||||
spsPaths);
|
||||
if (rc.result () != tesSUCCESS)
|
||||
m_journal.warning
|
||||
<< iIdentifier << " Failed with covering path "
|
||||
<< transHuman (rc.result ());
|
||||
else
|
||||
m_journal.debug
|
||||
<< iIdentifier << " Extra path element gives "
|
||||
<< transHuman (rc.result ());
|
||||
}
|
||||
|
||||
if (rc.result () == tesSUCCESS)
|
||||
{
|
||||
Json::Value jvEntry (Json::objectValue);
|
||||
rc.actualAmountIn.setIssuer (sourceAccount);
|
||||
|
||||
jvEntry[jss::source_amount] = rc.actualAmountIn.getJson (0);
|
||||
jvEntry[jss::paths_computed] = spsPaths.getJson (0);
|
||||
|
||||
if (hasCompletion ())
|
||||
{
|
||||
// Old ripple_path_find API requires this
|
||||
jvEntry[jss::paths_canonical] = Json::arrayValue;
|
||||
}
|
||||
|
||||
<< " Trying to find paths: "
|
||||
<< STAmount(i, 1).getFullText();
|
||||
if (findPaths(cache, fp, i, jvArray))
|
||||
found = true;
|
||||
jvArray.append (jvEntry);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_journal.debug << iIdentifier << " rippleCalc returns "
|
||||
<< transHuman (rc.result ());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_journal.debug << iIdentifier << " No paths found";
|
||||
}
|
||||
}
|
||||
|
||||
iLastLevel = iLevel;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#define RIPPLE_APP_PATHS_PATHREQUEST_H_INCLUDED
|
||||
|
||||
#include <ripple/app/ledger/Ledger.h>
|
||||
#include <ripple/app/paths/FindPaths.h>
|
||||
#include <ripple/app/paths/RippleLineCache.h>
|
||||
#include <ripple/json/json_value.h>
|
||||
#include <ripple/net/InfoSub.h>
|
||||
@@ -70,7 +71,6 @@ public:
|
||||
|
||||
~PathRequest ();
|
||||
|
||||
bool isValid ();
|
||||
bool isNew ();
|
||||
bool needsUpdate (bool newOnly, LedgerIndex index);
|
||||
void updateComplete ();
|
||||
@@ -92,7 +92,13 @@ private:
|
||||
bool isValid (RippleLineCache::ref crCache);
|
||||
void setValid ();
|
||||
void resetLevel (int level);
|
||||
int parseJson (Json::Value const&, bool complete);
|
||||
|
||||
bool
|
||||
findPaths (RippleLineCache::ref,
|
||||
FindPaths&, Issue const&,
|
||||
Json::Value& jvArray);
|
||||
|
||||
int parseJson (Json::Value const&);
|
||||
|
||||
beast::Journal m_journal;
|
||||
|
||||
@@ -112,11 +118,12 @@ private:
|
||||
boost::optional<AccountID> raSrcAccount;
|
||||
boost::optional<AccountID> raDstAccount;
|
||||
STAmount saDstAmount;
|
||||
boost::optional<STAmount> saSendMax;
|
||||
|
||||
std::set<Issue> sciSourceCurrencies;
|
||||
std::map<Issue, STPathSet> mContext;
|
||||
|
||||
bool bValid;
|
||||
bool convert_all_;
|
||||
|
||||
LockType mIndexLock;
|
||||
LedgerIndex mLastIndex;
|
||||
|
||||
@@ -36,7 +36,6 @@ class RippleCalc; // for logging
|
||||
|
||||
void PathState::clear()
|
||||
{
|
||||
allLiquidityConsumed_ = false;
|
||||
saInPass = saInReq.zeroed();
|
||||
saOutPass = saOutReq.zeroed();
|
||||
unfundedOffers_.clear ();
|
||||
@@ -68,7 +67,7 @@ void PathState::reset(STAmount const& in, STAmount const& out)
|
||||
<< " saOutAct=" << outAct()
|
||||
<< " saOutReq=" << outReq();
|
||||
|
||||
assert (outAct() < outReq());
|
||||
assert(outAct() < outReq());
|
||||
assert (nodes().size () >= 2);
|
||||
}
|
||||
|
||||
@@ -323,7 +322,8 @@ TER PathState::pushNode (
|
||||
node.issue_.currency);
|
||||
STAmount saLimit;
|
||||
|
||||
if (saOwed <= zero) {
|
||||
if (saOwed <= zero)
|
||||
{
|
||||
saLimit = creditLimit (view(),
|
||||
node.account_,
|
||||
backNode.account_,
|
||||
@@ -361,7 +361,7 @@ TER PathState::pushNode (
|
||||
else
|
||||
node.issue_.account = backNode.issue_.account;
|
||||
|
||||
node.saRateMax = saZero;
|
||||
node.saRateMax = STAmount::saZero;
|
||||
node.saRevDeliver = STAmount (node.issue_);
|
||||
node.saFwdDeliver = node.saRevDeliver;
|
||||
|
||||
|
||||
@@ -91,9 +91,6 @@ class PathState : public CountedObject <PathState>
|
||||
std::uint64_t quality() const { return uQuality; }
|
||||
void setQuality (std::uint64_t q) { uQuality = q; }
|
||||
|
||||
bool allLiquidityConsumed() const { return allLiquidityConsumed_; }
|
||||
void consumeAllLiquidity () { allLiquidityConsumed_ = true; }
|
||||
|
||||
void setIndex (int i) { mIndex = i; }
|
||||
int index() const { return mIndex; }
|
||||
|
||||
@@ -153,9 +150,6 @@ private:
|
||||
STAmount saOutAct; // --> Amount actually sent so far.
|
||||
STAmount saOutPass; // <-- Amount actually sent.
|
||||
|
||||
// If true, all liquidity on this path has been consumed.
|
||||
bool allLiquidityConsumed_ = false;
|
||||
|
||||
TER terStatus;
|
||||
|
||||
path::Node::List nodes_;
|
||||
|
||||
@@ -66,32 +66,6 @@ namespace ripple {
|
||||
|
||||
namespace {
|
||||
|
||||
// We sort possible paths by:
|
||||
// cost of path
|
||||
// length of path
|
||||
// width of path
|
||||
|
||||
// Compare two PathRanks. A better PathRank is lower, so the best are sorted to
|
||||
// the beginning.
|
||||
bool comparePathRank (
|
||||
Pathfinder::PathRank const& a, Pathfinder::PathRank const& b)
|
||||
{
|
||||
// 1) Higher quality (lower cost) is better
|
||||
if (a.quality != b.quality)
|
||||
return a.quality < b.quality;
|
||||
|
||||
// 2) More liquidity (higher volume) is better
|
||||
if (a.liquidity != b.liquidity)
|
||||
return a.liquidity > b.liquidity;
|
||||
|
||||
// 3) Shorter paths are better
|
||||
if (a.length != b.length)
|
||||
return a.length < b.length;
|
||||
|
||||
// 4) Tie breaker
|
||||
return a.index > b.index;
|
||||
}
|
||||
|
||||
struct AccountCandidate
|
||||
{
|
||||
int priority;
|
||||
@@ -172,8 +146,9 @@ Pathfinder::Pathfinder (
|
||||
AccountID const& uSrcAccount,
|
||||
AccountID const& uDstAccount,
|
||||
Currency const& uSrcCurrency,
|
||||
AccountID const& uSrcIssuer,
|
||||
STAmount const& saDstAmount)
|
||||
boost::optional<AccountID> const& uSrcIssuer,
|
||||
STAmount const& saDstAmount,
|
||||
boost::optional<STAmount> const& srcAmount)
|
||||
: mSrcAccount (uSrcAccount),
|
||||
mDstAccount (uDstAccount),
|
||||
mEffectiveDst (isXRP(saDstAmount.getIssuer ()) ?
|
||||
@@ -181,33 +156,15 @@ Pathfinder::Pathfinder (
|
||||
mDstAmount (saDstAmount),
|
||||
mSrcCurrency (uSrcCurrency),
|
||||
mSrcIssuer (uSrcIssuer),
|
||||
mSrcAmount ({uSrcCurrency, uSrcIssuer}, 1u, 0, true),
|
||||
mLedger (cache->getLedger ()),
|
||||
mRLCache (cache)
|
||||
{
|
||||
assert (isXRP(uSrcCurrency) == isXRP(uSrcIssuer));
|
||||
}
|
||||
|
||||
Pathfinder::Pathfinder (
|
||||
RippleLineCache::ref cache,
|
||||
AccountID const& uSrcAccount,
|
||||
AccountID const& uDstAccount,
|
||||
Currency const& uSrcCurrency,
|
||||
STAmount const& saDstAmount)
|
||||
: mSrcAccount (uSrcAccount),
|
||||
mDstAccount (uDstAccount),
|
||||
mEffectiveDst (isXRP(saDstAmount.getIssuer ()) ?
|
||||
uDstAccount : saDstAmount.getIssuer ()),
|
||||
mDstAmount (saDstAmount),
|
||||
mSrcCurrency (uSrcCurrency),
|
||||
mSrcAmount (
|
||||
{
|
||||
uSrcCurrency,
|
||||
isXRP (uSrcCurrency) ? xrpAccount () : uSrcAccount
|
||||
}, 1u, 0, true),
|
||||
mSrcAmount (srcAmount.value_or(STAmount({uSrcCurrency,
|
||||
uSrcIssuer.value_or(isXRP(uSrcCurrency) ?
|
||||
xrpAccount() : uSrcAccount)}, 1u, 0, true))),
|
||||
convert_all_ (mDstAmount ==
|
||||
STAmount(mDstAmount.issue(), STAmount::cMaxValue, STAmount::cMaxOffset)),
|
||||
mLedger (cache->getLedger ()),
|
||||
mRLCache (cache)
|
||||
{
|
||||
assert (! uSrcIssuer || isXRP(uSrcCurrency) == isXRP(uSrcIssuer.get()));
|
||||
}
|
||||
|
||||
Pathfinder::~Pathfinder()
|
||||
@@ -383,6 +340,9 @@ TER Pathfinder::getPathLiquidity (
|
||||
try
|
||||
{
|
||||
// Compute a path that provides at least the minimum liquidity.
|
||||
if (convert_all_)
|
||||
rcInput.partialPaymentAllowed = true;
|
||||
|
||||
auto rc = path::RippleCalc::rippleCalculate (
|
||||
sandbox,
|
||||
mSrcAmount,
|
||||
@@ -391,7 +351,6 @@ TER Pathfinder::getPathLiquidity (
|
||||
mSrcAccount,
|
||||
pathSet,
|
||||
&rcInput);
|
||||
|
||||
// If we can't get even the minimum liquidity requested, we're done.
|
||||
if (rc.result () != tesSUCCESS)
|
||||
return rc.result ();
|
||||
@@ -399,6 +358,8 @@ TER Pathfinder::getPathLiquidity (
|
||||
qualityOut = getRate (rc.actualAmountOut, rc.actualAmountIn);
|
||||
amountOut = rc.actualAmountOut;
|
||||
|
||||
if (! convert_all_)
|
||||
{
|
||||
// Now try to compute the remaining liquidity.
|
||||
rcInput.partialPaymentAllowed = true;
|
||||
rc = path::RippleCalc::rippleCalculate (
|
||||
@@ -413,6 +374,7 @@ TER Pathfinder::getPathLiquidity (
|
||||
// If we found further liquidity, add it into the result.
|
||||
if (rc.result () == tesSUCCESS)
|
||||
amountOut += rc.actualAmountOut;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -438,7 +400,10 @@ STAmount smallestUsefulAmount (STAmount const& amount, int maxPaths)
|
||||
|
||||
void Pathfinder::computePathRanks (int maxPaths)
|
||||
{
|
||||
mRemainingAmount = mDstAmount;
|
||||
mRemainingAmount = convert_all_ ?
|
||||
STAmount(mDstAmount.issue(), STAmount::cMaxValue,
|
||||
STAmount::cMaxOffset)
|
||||
: mDstAmount;
|
||||
|
||||
// Must subtract liquidity in default path from remaining amount.
|
||||
try
|
||||
@@ -450,7 +415,7 @@ void Pathfinder::computePathRanks (int maxPaths)
|
||||
auto rc = path::RippleCalc::rippleCalculate (
|
||||
sandbox,
|
||||
mSrcAmount,
|
||||
mDstAmount,
|
||||
mRemainingAmount,
|
||||
mDstAccount,
|
||||
mSrcAccount,
|
||||
STPathSet(),
|
||||
@@ -514,29 +479,37 @@ void Pathfinder::rankPaths (
|
||||
STPathSet const& paths,
|
||||
std::vector <PathRank>& rankedPaths)
|
||||
{
|
||||
|
||||
rankedPaths.clear ();
|
||||
rankedPaths.reserve (paths.size());
|
||||
|
||||
STAmount saMinDstAmount;
|
||||
if (convert_all_)
|
||||
{
|
||||
// On convert_all_ partialPaymentAllowed will be set to true
|
||||
// and requiring a huge amount will find the highest liquidity.
|
||||
saMinDstAmount = STAmount(mDstAmount.issue(),
|
||||
STAmount::cMaxValue, STAmount::cMaxOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ignore paths that move only very small amounts.
|
||||
auto saMinDstAmount = smallestUsefulAmount (mDstAmount, maxPaths);
|
||||
saMinDstAmount = smallestUsefulAmount(mDstAmount, maxPaths);
|
||||
}
|
||||
|
||||
for (int i = 0; i < paths.size (); ++i)
|
||||
{
|
||||
auto const& currentPath = paths[i];
|
||||
if (! currentPath.empty())
|
||||
{
|
||||
STAmount liquidity;
|
||||
uint64_t uQuality;
|
||||
|
||||
if (currentPath.empty ())
|
||||
continue;
|
||||
|
||||
auto const resultCode = getPathLiquidity (
|
||||
currentPath, saMinDstAmount, liquidity, uQuality);
|
||||
|
||||
if (resultCode != tesSUCCESS)
|
||||
{
|
||||
WriteLog (lsDEBUG, Pathfinder) <<
|
||||
"findPaths: dropping : " << transToken (resultCode) <<
|
||||
"findPaths: dropping : " <<
|
||||
transToken (resultCode) <<
|
||||
": " << currentPath.getJson (0);
|
||||
}
|
||||
else
|
||||
@@ -545,16 +518,42 @@ void Pathfinder::rankPaths (
|
||||
"findPaths: quality: " << uQuality <<
|
||||
": " << currentPath.getJson (0);
|
||||
|
||||
rankedPaths.push_back ({uQuality, currentPath.size (), liquidity, i});
|
||||
rankedPaths.push_back ({uQuality,
|
||||
currentPath.size (), liquidity, i});
|
||||
}
|
||||
}
|
||||
std::sort (rankedPaths.begin (), rankedPaths.end (), comparePathRank);
|
||||
}
|
||||
|
||||
// Sort paths by:
|
||||
// cost of path (when considering quality)
|
||||
// width of path
|
||||
// length of path
|
||||
// A better PathRank is lower, best are sorted to the beginning.
|
||||
std::sort(rankedPaths.begin(), rankedPaths.end(),
|
||||
[&](Pathfinder::PathRank const& a, Pathfinder::PathRank const& b)
|
||||
{
|
||||
// 1) Higher quality (lower cost) is better
|
||||
if (! convert_all_ && a.quality != b.quality)
|
||||
return a.quality < b.quality;
|
||||
|
||||
// 2) More liquidity (higher volume) is better
|
||||
if (a.liquidity != b.liquidity)
|
||||
return a.liquidity > b.liquidity;
|
||||
|
||||
// 3) Shorter paths are better
|
||||
if (a.length != b.length)
|
||||
return a.length < b.length;
|
||||
|
||||
// 4) Tie breaker
|
||||
return a.index > b.index;
|
||||
});
|
||||
}
|
||||
|
||||
STPathSet Pathfinder::getBestPaths (
|
||||
STPathSet
|
||||
Pathfinder::getBestPaths (
|
||||
int maxPaths,
|
||||
STPath& fullLiquidityPath,
|
||||
STPathSet& extraPaths,
|
||||
STPathSet const& extraPaths,
|
||||
AccountID const& srcIssuer)
|
||||
{
|
||||
WriteLog (lsDEBUG, Pathfinder) << "findPaths: " <<
|
||||
@@ -607,7 +606,7 @@ STPathSet Pathfinder::getBestPaths (
|
||||
|
||||
auto& pathRank = usePath ? *pathsIterator : *extraPathsIterator;
|
||||
|
||||
auto& path = usePath ? mCompletePaths[pathRank.index] :
|
||||
auto const& path = usePath ? mCompletePaths[pathRank.index] :
|
||||
extraPaths[pathRank.index];
|
||||
|
||||
if (useExtraPath)
|
||||
@@ -631,13 +630,12 @@ STPathSet Pathfinder::getBestPaths (
|
||||
if (! issuerIsSender && usePath)
|
||||
{
|
||||
// Need to make sure path matches issuer constraints
|
||||
|
||||
if (path.front ().getAccountID() != srcIssuer)
|
||||
continue;
|
||||
if (isDefaultPath (path))
|
||||
if (isDefaultPath(path) ||
|
||||
path.front().getAccountID() != srcIssuer)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
startsWithIssuer = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,23 +37,15 @@ namespace ripple {
|
||||
class Pathfinder
|
||||
{
|
||||
public:
|
||||
/** Construct a pathfinder with an issuer.*/
|
||||
Pathfinder (
|
||||
RippleLineCache::ref cache,
|
||||
AccountID const& srcAccount,
|
||||
AccountID const& dstAccount,
|
||||
Currency const& uSrcCurrency,
|
||||
AccountID const& uSrcIssuer,
|
||||
STAmount const& dstAmount);
|
||||
|
||||
/** Construct a pathfinder without an issuer.*/
|
||||
Pathfinder (
|
||||
RippleLineCache::ref cache,
|
||||
AccountID const& srcAccount,
|
||||
AccountID const& dstAccount,
|
||||
Currency const& uSrcCurrency,
|
||||
STAmount const& dstAmount);
|
||||
|
||||
boost::optional<AccountID> const& uSrcIssuer,
|
||||
STAmount const& dstAmount,
|
||||
boost::optional<STAmount> const& srcAmount);
|
||||
~Pathfinder();
|
||||
|
||||
static void initPathTable ();
|
||||
@@ -68,10 +60,11 @@ public:
|
||||
On return, if fullLiquidityPath is not empty, then it contains the best
|
||||
additional single path which can consume all the liquidity.
|
||||
*/
|
||||
STPathSet getBestPaths (
|
||||
STPathSet
|
||||
getBestPaths (
|
||||
int maxPaths,
|
||||
STPath& fullLiquidityPath,
|
||||
STPathSet& extraPaths,
|
||||
STPathSet const& extraPaths,
|
||||
AccountID const& srcIssuer);
|
||||
|
||||
enum NodeType
|
||||
@@ -184,6 +177,7 @@ private:
|
||||
/** The amount remaining from mSrcAccount after the default liquidity has
|
||||
been removed. */
|
||||
STAmount mRemainingAmount;
|
||||
bool convert_all_;
|
||||
|
||||
std::shared_ptr <ReadView const> mLedger;
|
||||
LoadEvent::pointer m_loadEvent;
|
||||
|
||||
@@ -335,7 +335,7 @@ TER RippleCalc::rippleCalculate ()
|
||||
actualAmountIn_ += pathState->inPass();
|
||||
actualAmountOut_ += pathState->outPass();
|
||||
|
||||
if (pathState->allLiquidityConsumed() || multiQuality)
|
||||
if (multiQuality)
|
||||
{
|
||||
++iDry;
|
||||
pathState->setQuality(0);
|
||||
|
||||
@@ -83,7 +83,7 @@ TER PathCursor::deliverNodeForward (
|
||||
bool noFee = isXRP (previousNode().issue_)
|
||||
|| uInAccountID == previousNode().issue_.account
|
||||
|| node().offerOwnerAccount_ == previousNode().issue_.account;
|
||||
const STAmount saInFeeRate = noFee ? saOne
|
||||
const STAmount saInFeeRate = noFee ? STAmount::saOne
|
||||
: previousNode().transferRate_; // Transfer rate of issuer.
|
||||
|
||||
// First calculate assuming no output fees: saInPassAct,
|
||||
|
||||
@@ -95,7 +95,7 @@ TER PathCursor::deliverNodeReverseImpl (
|
||||
// Issuer sending or receiving.
|
||||
|
||||
const STAmount saOutFeeRate = hasFee
|
||||
? saOne // No fee.
|
||||
? STAmount::saOne // No fee.
|
||||
: node().transferRate_; // Transfer rate of issuer.
|
||||
|
||||
WriteLog (lsTRACE, RippleCalc)
|
||||
@@ -342,7 +342,7 @@ TER PathCursor::deliverNodeReverseImpl (
|
||||
<< " saOutAct=" << saOutAct
|
||||
<< " saOutReq=" << saOutReq;
|
||||
|
||||
assert (saOutAct <= saOutReq);
|
||||
assert(saOutAct <= saOutReq);
|
||||
|
||||
if (resultCode == tesSUCCESS && !saOutAct)
|
||||
resultCode = tecPATH_DRY;
|
||||
|
||||
@@ -462,17 +462,11 @@ TER PathCursor::reverseLiquidityForAccount () const
|
||||
|
||||
if (saCurWantedReq <= zero)
|
||||
{
|
||||
// TEMPORARY emergency fix
|
||||
//
|
||||
// TODO(tom): why can't saCurWantedReq be -1 if you want to
|
||||
// compute maximum liquidity? This might be unimplemented
|
||||
// functionality. TODO(tom): should the same check appear in
|
||||
// other paths or even be pulled up?
|
||||
assert(false);
|
||||
WriteLog (lsFATAL, RippleCalc) << "CurWantReq was not positive";
|
||||
return tefEXCEPTION;
|
||||
}
|
||||
|
||||
assert (saCurWantedReq > zero); // FIXME: We got one of these
|
||||
// The previous node is an offer; we are receiving our own currency.
|
||||
|
||||
// The previous order book's entries might hold our issuances; might
|
||||
|
||||
@@ -18,86 +18,205 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/test/jtx.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/json/json_reader.h>
|
||||
#include <ripple/json/to_string.h>
|
||||
#include <ripple/protocol/JsonFields.h>
|
||||
#include <ripple/protocol/STParsedJSON.h>
|
||||
#include <ripple/protocol/TxFlags.h>
|
||||
#include <ripple/rpc/RipplePathFind.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/test/jtx.h>
|
||||
#include <beast/unit_test/suite.h>
|
||||
#include <ripple/app/paths/AccountCurrencies.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
using namespace jtx;
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace detail {
|
||||
|
||||
void
|
||||
stpath_append_one (STPath& st,
|
||||
jtx::Account const& account)
|
||||
{
|
||||
st.push_back(STPathElement({
|
||||
account.id(), boost::none, boost::none }));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
std::enable_if_t<
|
||||
std::is_constructible<jtx::Account, T>::value>
|
||||
stpath_append_one (STPath& st,
|
||||
T const& t)
|
||||
{
|
||||
stpath_append_one(st, jtx::Account{ t });
|
||||
}
|
||||
|
||||
void
|
||||
stpath_append_one (STPath& st,
|
||||
jtx::IOU const& iou)
|
||||
{
|
||||
st.push_back(STPathElement({
|
||||
iou.account.id(), iou.currency, boost::none }));
|
||||
}
|
||||
|
||||
void
|
||||
stpath_append_one (STPath& st,
|
||||
jtx::BookSpec const& book)
|
||||
{
|
||||
st.push_back(STPathElement({
|
||||
boost::none, book.currency, book.account }));
|
||||
}
|
||||
|
||||
inline
|
||||
void
|
||||
stpath_append (STPath& st)
|
||||
{
|
||||
}
|
||||
|
||||
template <class T, class... Args>
|
||||
void
|
||||
stpath_append (STPath& st,
|
||||
T const& t, Args const&... args)
|
||||
{
|
||||
stpath_append_one(st, t);
|
||||
stpath_append(st, args...);
|
||||
}
|
||||
|
||||
inline
|
||||
void
|
||||
stpathset_append (STPathSet& st)
|
||||
{
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
void
|
||||
stpathset_append(STPathSet& st,
|
||||
STPath const& p, Args const&... args)
|
||||
{
|
||||
st.push_back(p);
|
||||
stpathset_append(st, args...);
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
template <class... Args>
|
||||
STPath
|
||||
stpath (Args const&... args)
|
||||
{
|
||||
STPath st;
|
||||
detail::stpath_append(st, args...);
|
||||
return st;
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
bool
|
||||
same (STPathSet const& st1, Args const&... args)
|
||||
{
|
||||
STPathSet st2;
|
||||
detail::stpathset_append(st2, args...);
|
||||
if (st1.size() != st2.size())
|
||||
return false;
|
||||
|
||||
for (auto const& p : st2)
|
||||
{
|
||||
if (std::find(st1.begin(), st1.end(), p) == st1.end())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
equal(STAmount const& sa1, STAmount const& sa2)
|
||||
{
|
||||
return sa1 == sa2 &&
|
||||
sa1.issue().account == sa2.issue().account;
|
||||
}
|
||||
|
||||
std::tuple <STPathSet, STAmount, STAmount>
|
||||
find_paths(std::shared_ptr<ReadView const> const& view,
|
||||
jtx::Account const& src, jtx::Account const& dst,
|
||||
STAmount const& saDstAmount,
|
||||
boost::optional<STAmount> const& saSendMax = boost::none)
|
||||
{
|
||||
static int const level = 8;
|
||||
auto cache = std::make_shared<RippleLineCache>(view);
|
||||
auto currencies = accountSourceCurrencies(src, cache, true);
|
||||
auto jvSrcCurrencies = Json::Value(Json::arrayValue);
|
||||
for (auto const& c : currencies)
|
||||
{
|
||||
Json::Value jvCurrency = Json::objectValue;
|
||||
jvCurrency[jss::currency] = to_string(c);
|
||||
jvSrcCurrencies.append(jvCurrency);
|
||||
}
|
||||
|
||||
auto result = ripplePathFind(cache, src.id(), dst.id(),
|
||||
saDstAmount, jvSrcCurrencies, boost::none, level, saSendMax,
|
||||
saDstAmount == STAmount(saDstAmount.issue(), 1u, 0, true));
|
||||
if (! result.first)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Path_test::findPath: ripplePathFind find failed");
|
||||
}
|
||||
|
||||
auto const& jv = result.second[0u];
|
||||
Json::Value paths;
|
||||
paths["Paths"] = jv["paths_computed"];
|
||||
STParsedJSONObject stp("generic", paths);
|
||||
|
||||
STAmount sa;
|
||||
if (jv.isMember(jss::source_amount))
|
||||
sa = amountFromJson(sfGeneric, jv[jss::source_amount]);
|
||||
|
||||
STAmount da;
|
||||
if (jv.isMember(jss::destination_amount))
|
||||
da = amountFromJson(sfGeneric, jv[jss::destination_amount]);
|
||||
|
||||
return std::make_tuple(
|
||||
std::move(stp.object->getFieldPathSet(sfPaths)),
|
||||
std::move(sa), std::move(da));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class Path_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
Json::Value
|
||||
findPath (std::shared_ptr<ReadView const> const& view,
|
||||
Account const& src, Account const& dest,
|
||||
std::vector<Issue> const& srcIssues,
|
||||
STAmount const& saDstAmount)
|
||||
{
|
||||
auto jvSrcCurrencies = Json::Value(Json::arrayValue);
|
||||
for (auto const& i : srcIssues)
|
||||
{
|
||||
STAmount const a = STAmount(i, 0);
|
||||
jvSrcCurrencies.append(a.getJson(0));
|
||||
}
|
||||
|
||||
int const level = 8;
|
||||
auto result = ripplePathFind(
|
||||
std::make_shared<RippleLineCache>(view),
|
||||
src.id(), dest.id(), saDstAmount,
|
||||
jvSrcCurrencies, boost::none, level);
|
||||
if(!result.first)
|
||||
throw std::runtime_error(
|
||||
"Path_test::findPath: ripplePathFind find failed");
|
||||
|
||||
return result.second;
|
||||
}
|
||||
|
||||
void
|
||||
test_no_direct_path_no_intermediary_no_alternatives()
|
||||
no_direct_path_no_intermediary_no_alternatives()
|
||||
{
|
||||
using namespace jtx;
|
||||
testcase("no direct path no intermediary no alternatives");
|
||||
Env env(*this);
|
||||
env.fund(XRP(10000), "alice", "bob");
|
||||
|
||||
auto const alternatives = findPath(env.open(), "alice", "bob",
|
||||
{Account("alice")["USD"]}, Account("bob")["USD"](5));
|
||||
expect(alternatives.size() == 0);
|
||||
auto const result = find_paths(env.open(),
|
||||
"alice", "bob", Account("bob")["USD"](5));
|
||||
expect(std::get<0>(result).empty());
|
||||
}
|
||||
|
||||
void
|
||||
test_direct_path_no_intermediary()
|
||||
direct_path_no_intermediary()
|
||||
{
|
||||
using namespace jtx;
|
||||
testcase("direct path no intermediary");
|
||||
Env env(*this);
|
||||
env.fund(XRP(10000), "alice", "bob");
|
||||
env.trust(Account("alice")["USD"](700), "bob");
|
||||
|
||||
auto const alternatives = findPath(env.open(), "alice", "bob",
|
||||
{Account("alice")["USD"]}, Account("bob")["USD"](5));
|
||||
Json::Value jv;
|
||||
Json::Reader().parse(R"([{
|
||||
"paths_canonical" : [],
|
||||
"paths_computed" : [],
|
||||
"source_amount" :
|
||||
{
|
||||
"currency" : "USD",
|
||||
"issuer" : "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn",
|
||||
"value" : "5"
|
||||
}
|
||||
}])", jv);
|
||||
expect(jv == alternatives);
|
||||
STPathSet st;
|
||||
STAmount sa;
|
||||
std::tie(st, sa, std::ignore) = find_paths(env.open(),
|
||||
"alice", "bob", Account("bob")["USD"](5));
|
||||
expect(st.empty());
|
||||
expect(equal(sa, Account("alice")["USD"](5)));
|
||||
}
|
||||
|
||||
void
|
||||
test_payment_auto_path_find()
|
||||
payment_auto_path_find()
|
||||
{
|
||||
using namespace jtx;
|
||||
testcase("payment auto path find");
|
||||
Env env(*this);
|
||||
auto const gw = Account("gateway");
|
||||
@@ -114,8 +233,9 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
test_path_find()
|
||||
path_find()
|
||||
{
|
||||
using namespace jtx;
|
||||
testcase("path find");
|
||||
Env env(*this);
|
||||
auto const gw = Account("gateway");
|
||||
@@ -126,52 +246,68 @@ public:
|
||||
env(pay(gw, "alice", USD(70)));
|
||||
env(pay(gw, "bob", USD(50)));
|
||||
|
||||
auto const alternatives = findPath(env.open(), "alice", "bob",
|
||||
{USD}, Account("bob")["USD"](5));
|
||||
Json::Value jv;
|
||||
Json::Reader().parse(R"([{
|
||||
"paths_canonical" : [],
|
||||
"paths_computed" : [],
|
||||
"source_amount" :
|
||||
{
|
||||
"currency" : "USD",
|
||||
"issuer" : "r9QxhA9RghPZBbUchA9HkrmLKaWvkLXU29",
|
||||
"value" : "5"
|
||||
}
|
||||
}])", jv);
|
||||
expect(jv == alternatives);
|
||||
STPathSet st;
|
||||
STAmount sa;
|
||||
std::tie(st, sa, std::ignore) = find_paths(env.open(),
|
||||
"alice", "bob", Account("bob")["USD"](5));
|
||||
expect(same(st, stpath("gateway")));
|
||||
expect(equal(sa, Account("alice")["USD"](5)));
|
||||
}
|
||||
|
||||
void
|
||||
test_path_find_consume_all()
|
||||
path_find_consume_all()
|
||||
{
|
||||
using namespace jtx;
|
||||
testcase("path find consume all");
|
||||
|
||||
{
|
||||
Env env(*this);
|
||||
env.fund(XRP(10000), "alice", "bob", "carol",
|
||||
"dan", "edward");
|
||||
env.trust(Account("alice")["USD"](10), "bob");
|
||||
env.trust(Account("bob")["USD"](10), "carol");
|
||||
env.trust(Account("carol")["USD"](10), "edward");
|
||||
env.trust(Account("alice")["USD"](100), "dan");
|
||||
env.trust(Account("dan")["USD"](100), "edward");
|
||||
|
||||
STPathSet st;
|
||||
STAmount sa;
|
||||
STAmount da;
|
||||
std::tie(st, sa, da) = find_paths(env.open(),
|
||||
"alice", "edward", Account("edward")["USD"](-1));
|
||||
expect(same(st, stpath("dan"), stpath("bob", "carol")));
|
||||
expect(equal(sa, Account("alice")["USD"](110)));
|
||||
expect(equal(da, Account("edward")["USD"](110)));
|
||||
}
|
||||
|
||||
{
|
||||
Env env(*this);
|
||||
auto const gw = Account("gateway");
|
||||
auto const USD = gw["USD"];
|
||||
env.fund(XRP(10000), "alice", "bob", gw);
|
||||
env.trust(Account("alice")["USD"](600), gw);
|
||||
env.trust(USD(700), "bob");
|
||||
env.fund(XRP(10000), "alice", "bob", "carol", gw);
|
||||
env.trust(USD(100), "bob", "carol");
|
||||
env(pay(gw, "carol", USD(100)));
|
||||
env(offer("carol", XRP(100), USD(100)));
|
||||
|
||||
auto const alternatives = findPath(env.open(), "alice", "bob",
|
||||
{USD}, Account("bob")["USD"](1));
|
||||
Json::Value jv;
|
||||
Json::Reader().parse(R"([{
|
||||
"paths_canonical" : [],
|
||||
"paths_computed" : [],
|
||||
"source_amount" :
|
||||
{
|
||||
"currency" : "USD",
|
||||
"issuer" : "r9QxhA9RghPZBbUchA9HkrmLKaWvkLXU29",
|
||||
"value" : "1"
|
||||
STPathSet st;
|
||||
STAmount sa;
|
||||
STAmount da;
|
||||
std::tie(st, sa, da) = find_paths(env.open(),
|
||||
"alice", "bob", Account("bob")["AUD"](-1),
|
||||
boost::optional<STAmount>(XRP(100000000)));
|
||||
expect(st.empty());
|
||||
std::tie(st, sa, da) = find_paths(env.open(),
|
||||
"alice", "bob", Account("bob")["USD"](-1),
|
||||
boost::optional<STAmount>(XRP(100000000)));
|
||||
expect(sa == XRP(100));
|
||||
expect(equal(da, Account("bob")["USD"](100)));
|
||||
}
|
||||
}])", jv);
|
||||
expect(jv == alternatives);
|
||||
}
|
||||
|
||||
void
|
||||
test_alternative_path_consume_both()
|
||||
alternative_path_consume_both()
|
||||
{
|
||||
using namespace jtx;
|
||||
testcase("alternative path consume both");
|
||||
Env env(*this);
|
||||
auto const gw = Account("gateway");
|
||||
@@ -198,8 +334,9 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
test_alternative_paths_consume_best_transfer()
|
||||
alternative_paths_consume_best_transfer()
|
||||
{
|
||||
using namespace jtx;
|
||||
testcase("alternative paths consume best transfer");
|
||||
Env env(*this);
|
||||
auto const gw = Account("gateway");
|
||||
@@ -226,8 +363,9 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
test_alternative_paths_consume_best_transfer_first()
|
||||
alternative_paths_consume_best_transfer_first()
|
||||
{
|
||||
using namespace jtx;
|
||||
testcase("alternative paths - consume best transfer first");
|
||||
Env env(*this);
|
||||
auto const gw = Account("gateway");
|
||||
@@ -256,8 +394,9 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
test_alternative_paths_limit_returned_paths_to_best_quality()
|
||||
alternative_paths_limit_returned_paths_to_best_quality()
|
||||
{
|
||||
using namespace jtx;
|
||||
testcase("alternative paths - limit returned paths to best quality");
|
||||
Env env(*this);
|
||||
auto const gw = Account("gateway");
|
||||
@@ -276,25 +415,19 @@ public:
|
||||
env(pay("carol", "alice", Account("carol")["USD"](100)));
|
||||
env(pay(gw, "alice", USD(100)));
|
||||
|
||||
auto const alternatives = findPath(env.open(), "alice", "bob",
|
||||
{USD}, Account("bob")["USD"](5));
|
||||
Json::Value jv;
|
||||
Json::Reader().parse(R"([{
|
||||
"paths_canonical" : [],
|
||||
"paths_computed" : [],
|
||||
"source_amount" :
|
||||
{
|
||||
"currency" : "USD",
|
||||
"issuer" : "r9QxhA9RghPZBbUchA9HkrmLKaWvkLXU29",
|
||||
"value" : "5"
|
||||
}
|
||||
}])", jv);
|
||||
expect(jv == alternatives);
|
||||
STPathSet st;
|
||||
STAmount sa;
|
||||
std::tie(st, sa, std::ignore) = find_paths(env.open(),
|
||||
"alice", "bob", Account("bob")["USD"](5));
|
||||
expect(same(st, stpath("gateway"), stpath("gateway2"),
|
||||
stpath("dan"), stpath("carol")));
|
||||
expect(equal(sa, Account("alice")["USD"](5)));
|
||||
}
|
||||
|
||||
void
|
||||
test_issues_path_negative_issue()
|
||||
issues_path_negative_issue()
|
||||
{
|
||||
using namespace jtx;
|
||||
testcase("path negative: Issue #5");
|
||||
Env env(*this);
|
||||
env.fund(XRP(10000), "alice", "bob", "carol", "dan");
|
||||
@@ -305,16 +438,16 @@ public:
|
||||
env.require(balance("bob", Account("carol")["USD"](-75)));
|
||||
env.require(balance("carol", Account("bob")["USD"](75)));
|
||||
|
||||
auto alternatives = findPath(env.open(), "alice", "bob",
|
||||
{Account("alice")["USD"]}, Account("bob")["USD"](25));
|
||||
expect(alternatives.size() == 0);
|
||||
auto result = find_paths(env.open(),
|
||||
"alice", "bob", Account("bob")["USD"](25));
|
||||
expect(std::get<0>(result).empty());
|
||||
|
||||
env(pay("alice", "bob", Account("alice")["USD"](25)),
|
||||
ter(tecPATH_DRY));
|
||||
|
||||
alternatives = findPath(env.open(), "alice", "bob",
|
||||
{Account("alice")["USD"]}, Account("alice")["USD"](25));
|
||||
expect(alternatives.size() == 0);
|
||||
result = find_paths(env.open(),
|
||||
"alice", "bob", Account("alice")["USD"](25));
|
||||
expect(std::get<0>(result).empty());
|
||||
|
||||
env.require(balance("alice", Account("bob")["USD"](0)));
|
||||
env.require(balance("alice", Account("dan")["USD"](0)));
|
||||
@@ -332,8 +465,9 @@ public:
|
||||
// alice --> carol --> dan --> bob
|
||||
// Balance of 100 USD Bob - Balance of 37 USD -> Rod
|
||||
void
|
||||
test_issues_path_negative_ripple_client_issue_23_smaller()
|
||||
issues_path_negative_ripple_client_issue_23_smaller()
|
||||
{
|
||||
using namespace jtx;
|
||||
testcase("path negative: ripple-client issue #23: smaller");
|
||||
Env env(*this);
|
||||
env.fund(XRP(10000), "alice", "bob", "carol", "dan");
|
||||
@@ -350,8 +484,9 @@ public:
|
||||
// alice -120 USD-> edward -25 USD-> bob
|
||||
// alice -25 USD-> carol -75 USD -> dan -100 USD-> bob
|
||||
void
|
||||
test_issues_path_negative_ripple_client_issue_23_larger()
|
||||
issues_path_negative_ripple_client_issue_23_larger()
|
||||
{
|
||||
using namespace jtx;
|
||||
testcase("path negative: ripple-client issue #23: larger");
|
||||
Env env(*this);
|
||||
env.fund(XRP(10000), "alice", "bob", "carol", "dan", "edward");
|
||||
@@ -376,64 +511,49 @@ public:
|
||||
// bob will hold gateway AUD
|
||||
// alice pays bob gateway AUD using XRP
|
||||
void
|
||||
test_via_offers_via_gateway()
|
||||
via_offers_via_gateway()
|
||||
{
|
||||
using namespace jtx;
|
||||
testcase("via gateway");
|
||||
Env env(*this);
|
||||
auto const gw = Account("gateway");
|
||||
auto const AUD = gw["AUD"];
|
||||
env.fund(XRP(10000), "alice", "bob", "carol", gw);
|
||||
env(rate(gw, 1.1));
|
||||
env.trust(AUD(100), "bob");
|
||||
env.trust(AUD(100), "carol");
|
||||
env.trust(AUD(100), "bob", "carol");
|
||||
env(pay(gw, "carol", AUD(50)));
|
||||
env(offer("carol", XRP(50), AUD(50)));
|
||||
env(pay("alice", "bob", AUD(10)), sendmax(XRP(100)), paths(XRP));
|
||||
env.require(balance("bob", AUD(10)));
|
||||
env.require(balance("carol", AUD(39)));
|
||||
|
||||
auto const alternatives = findPath(env.open(), "alice", "bob",
|
||||
{Account("alice")["USD"]}, Account("bob")["USD"](25));
|
||||
expect(alternatives.size() == 0);
|
||||
auto const result = find_paths(env.open(),
|
||||
"alice", "bob", Account("bob")["USD"](25));
|
||||
expect(std::get<0>(result).empty());
|
||||
}
|
||||
|
||||
void
|
||||
test_indirect_paths_path_find()
|
||||
indirect_paths_path_find()
|
||||
{
|
||||
using namespace jtx;
|
||||
testcase("path find");
|
||||
Env env(*this);
|
||||
env.fund(XRP(10000), "alice", "bob", "carol");
|
||||
env.trust(Account("alice")["USD"](1000), "bob");
|
||||
env.trust(Account("bob")["USD"](1000), "carol");
|
||||
|
||||
auto const alternatives = findPath(env.open(), "alice", "carol",
|
||||
{Account("alice")["USD"]}, Account("carol")["USD"](5));
|
||||
Json::Value jv;
|
||||
Json::Reader().parse(R"([{
|
||||
"paths_canonical" : [],
|
||||
"paths_computed" :
|
||||
[
|
||||
[
|
||||
{
|
||||
"account" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
|
||||
"type" : 1,
|
||||
"type_hex" : "0000000000000001"
|
||||
}
|
||||
]
|
||||
],
|
||||
"source_amount" :
|
||||
{
|
||||
"currency" : "USD",
|
||||
"issuer" : "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn",
|
||||
"value" : "5"
|
||||
}
|
||||
}])", jv);
|
||||
expect(jv == alternatives);
|
||||
STPathSet st;
|
||||
STAmount sa;
|
||||
std::tie(st, sa, std::ignore) = find_paths(env.open(),
|
||||
"alice", "carol", Account("carol")["USD"](5));
|
||||
expect(same(st, stpath("bob")));
|
||||
expect(equal(sa, Account("alice")["USD"](5)));
|
||||
}
|
||||
|
||||
void
|
||||
test_quality_paths_quality_set_and_test()
|
||||
quality_paths_quality_set_and_test()
|
||||
{
|
||||
using namespace jtx;
|
||||
testcase("quality set and test");
|
||||
Env env(*this);
|
||||
env.fund(XRP(10000), "alice", "bob");
|
||||
@@ -473,8 +593,9 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
test_trust_auto_clear_trust_normal_clear()
|
||||
trust_auto_clear_trust_normal_clear()
|
||||
{
|
||||
using namespace jtx;
|
||||
testcase("trust normal clear");
|
||||
Env env(*this);
|
||||
env.fund(XRP(10000), "alice", "bob");
|
||||
@@ -516,8 +637,9 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
test_trust_auto_clear_trust_auto_clear()
|
||||
trust_auto_clear_trust_auto_clear()
|
||||
{
|
||||
using namespace jtx;
|
||||
testcase("trust auto clear");
|
||||
Env env(*this);
|
||||
env.fund(XRP(10000), "alice", "bob");
|
||||
@@ -564,23 +686,23 @@ public:
|
||||
void
|
||||
run()
|
||||
{
|
||||
test_no_direct_path_no_intermediary_no_alternatives();
|
||||
test_direct_path_no_intermediary();
|
||||
test_payment_auto_path_find();
|
||||
test_path_find();
|
||||
test_path_find_consume_all();
|
||||
test_alternative_path_consume_both();
|
||||
test_alternative_paths_consume_best_transfer();
|
||||
test_alternative_paths_consume_best_transfer_first();
|
||||
test_alternative_paths_limit_returned_paths_to_best_quality();
|
||||
test_issues_path_negative_issue();
|
||||
test_issues_path_negative_ripple_client_issue_23_smaller();
|
||||
test_issues_path_negative_ripple_client_issue_23_larger();
|
||||
test_via_offers_via_gateway();
|
||||
test_indirect_paths_path_find();
|
||||
test_quality_paths_quality_set_and_test();
|
||||
test_trust_auto_clear_trust_normal_clear();
|
||||
test_trust_auto_clear_trust_auto_clear();
|
||||
no_direct_path_no_intermediary_no_alternatives();
|
||||
direct_path_no_intermediary();
|
||||
payment_auto_path_find();
|
||||
path_find();
|
||||
path_find_consume_all();
|
||||
alternative_path_consume_both();
|
||||
alternative_paths_consume_best_transfer();
|
||||
alternative_paths_consume_best_transfer_first();
|
||||
alternative_paths_limit_returned_paths_to_best_quality();
|
||||
issues_path_negative_issue();
|
||||
issues_path_negative_ripple_client_issue_23_smaller();
|
||||
issues_path_negative_ripple_client_issue_23_larger();
|
||||
via_offers_via_gateway();
|
||||
indirect_paths_path_find();
|
||||
quality_paths_quality_set_and_test();
|
||||
trust_auto_clear_trust_normal_clear();
|
||||
trust_auto_clear_trust_auto_clear();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ void DeferredCredits::credit (AccountID const& sender,
|
||||
using std::get;
|
||||
|
||||
assert (sender != receiver);
|
||||
assert (!amount.negative ());
|
||||
assert (!amount.negative());
|
||||
|
||||
auto const k = makeKey (sender, receiver, amount.getCurrency ());
|
||||
auto i = map_.find (k);
|
||||
|
||||
@@ -90,6 +90,7 @@ enum error_code_i
|
||||
rpcDST_ACT_MALFORMED,
|
||||
rpcDST_ACT_MISSING,
|
||||
rpcDST_AMT_MALFORMED,
|
||||
rpcDST_AMT_MISSING,
|
||||
rpcDST_ISR_MALFORMED,
|
||||
rpcGETS_ACT_MALFORMED,
|
||||
rpcGETS_AMT_MALFORMED,
|
||||
@@ -101,6 +102,7 @@ enum error_code_i
|
||||
rpcPORT_MALFORMED,
|
||||
rpcPUBLIC_MALFORMED,
|
||||
rpcSIGN_FOR_MALFORMED,
|
||||
rpcSENDMAX_MALFORMED,
|
||||
rpcSRC_ACT_MALFORMED,
|
||||
rpcSRC_ACT_MISSING,
|
||||
rpcSRC_ACT_NOT_FOUND,
|
||||
|
||||
@@ -131,6 +131,7 @@ JSS ( descending ); // in: AccountTx*
|
||||
JSS ( destination_account ); // in: PathRequest, RipplePathFind
|
||||
JSS ( destination_amount ); // in: PathRequest, RipplePathFind
|
||||
JSS ( destination_currencies ); // in: PathRequest, RipplePathFind
|
||||
JSS ( destination_tag ); // in: PathRequest
|
||||
JSS ( dir_entry ); // out: DirectoryEntryIterator
|
||||
JSS ( dir_index ); // out: DirectoryEntryIterator
|
||||
JSS ( dir_root ); // out: DirectoryEntryIterator
|
||||
@@ -311,6 +312,7 @@ JSS ( secret ); // in: TransactionSign, WalletSeed,
|
||||
JSS ( seed ); // in: WalletAccounts, out: WalletSeed
|
||||
JSS ( seed_hex ); // in: WalletPropose, TransactionSign
|
||||
JSS ( send_currencies ); // out: AccountCurrencies
|
||||
JSS ( send_max ); // in: PathRequest, RipplePathFind
|
||||
JSS ( seq ); // in: LedgerEntry;
|
||||
// out: NetworkOPs, RPCSub, AccountOffers
|
||||
JSS ( seqNum ); // out: LedgerToJson
|
||||
|
||||
@@ -71,6 +71,9 @@ public:
|
||||
|
||||
static std::uint64_t const uRateOne;
|
||||
|
||||
static STAmount const saZero;
|
||||
static STAmount const saOne;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
STAmount(SerialIter& sit, SField const& name);
|
||||
|
||||
@@ -382,10 +385,6 @@ inline bool isXRP(STAmount const& amount)
|
||||
return isXRP (amount.issue().currency);
|
||||
}
|
||||
|
||||
// VFALCO TODO Make static member accessors for these in STAmount
|
||||
extern const STAmount saZero;
|
||||
extern const STAmount saOne;
|
||||
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
|
||||
@@ -318,6 +318,12 @@ public:
|
||||
return value[n];
|
||||
}
|
||||
|
||||
std::vector<STPath>::reference
|
||||
operator[] (std::vector<STPath>::size_type n)
|
||||
{
|
||||
return value[n];
|
||||
}
|
||||
|
||||
std::vector<STPath>::const_iterator
|
||||
begin () const
|
||||
{
|
||||
|
||||
@@ -39,9 +39,6 @@ static const std::uint64_t tenTo14 = 100000000000000ull;
|
||||
static const std::uint64_t tenTo14m1 = tenTo14 - 1;
|
||||
static const std::uint64_t tenTo17 = tenTo14 * 1000;
|
||||
|
||||
STAmount const saZero (noIssue(), 0);
|
||||
STAmount const saOne (noIssue(), 1);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
static
|
||||
std::int64_t
|
||||
@@ -340,6 +337,9 @@ STAmount operator- (STAmount const& v1, STAmount const& v2)
|
||||
|
||||
std::uint64_t const STAmount::uRateOne = getRate (STAmount (1), STAmount (1));
|
||||
|
||||
STAmount const STAmount::saZero (noIssue (), 0u);
|
||||
STAmount const STAmount::saOne (noIssue (), 1u);
|
||||
|
||||
void
|
||||
STAmount::setIssue (Issue const& issue)
|
||||
{
|
||||
|
||||
@@ -31,7 +31,8 @@ ripplePathFind (RippleLineCache::pointer const& cache,
|
||||
STAmount const& saDstAmount,
|
||||
Json::Value const& jvSrcCurrencies,
|
||||
boost::optional<Json::Value> const& contextPaths,
|
||||
int const& level);
|
||||
int const& level, boost::optional<STAmount> saSendMax,
|
||||
bool convert_all);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -144,7 +144,8 @@ Json::Value doRipplePathFind (RPC::Context& context)
|
||||
// Parse saDstAmount.
|
||||
!context.params.isMember (jss::destination_amount)
|
||||
|| ! amountFromJsonNoThrow(saDstAmount, context.params[jss::destination_amount])
|
||||
|| saDstAmount <= zero
|
||||
|| (saDstAmount <= zero && saDstAmount !=
|
||||
STAmount(saDstAmount.issue(), 1u, 0, true))
|
||||
|| (!isXRP(saDstAmount.getCurrency ())
|
||||
&& (!saDstAmount.getIssuer () ||
|
||||
noAccount() == saDstAmount.getIssuer ())))
|
||||
@@ -219,11 +220,34 @@ Json::Value doRipplePathFind (RPC::Context& context)
|
||||
level = rLev;
|
||||
}
|
||||
|
||||
auto const convert_all =
|
||||
saDstAmount == STAmount(saDstAmount.issue(), 1u, 0, true);
|
||||
|
||||
boost::optional<STAmount> saSendMax;
|
||||
if (context.params.isMember(jss::send_max))
|
||||
{
|
||||
// Send_max requires destination amount to be -1.
|
||||
if (! convert_all)
|
||||
return rpcError(rpcDST_AMT_MALFORMED);
|
||||
|
||||
saSendMax.emplace();
|
||||
if (!amountFromJsonNoThrow(
|
||||
*saSendMax, context.params[jss::send_max]) ||
|
||||
(saSendMax->getCurrency().isZero() &&
|
||||
saSendMax->getIssuer().isNonZero()) ||
|
||||
(saSendMax->getCurrency() == badCurrency()) ||
|
||||
(*saSendMax <= zero &&
|
||||
*saSendMax != STAmount(saSendMax->issue(), 1u, 0, true)))
|
||||
{
|
||||
return rpcError(rpcSENDMAX_MALFORMED);
|
||||
}
|
||||
}
|
||||
|
||||
auto contextPaths = context.params.isMember(jss::paths) ?
|
||||
boost::optional<Json::Value>(context.params[jss::paths]) :
|
||||
boost::optional<Json::Value>(boost::none);
|
||||
auto pathFindResult = ripplePathFind(cache, raSrc, raDst, saDstAmount,
|
||||
jvSrcCurrencies, contextPaths, level);
|
||||
jvSrcCurrencies, contextPaths, level, saSendMax, convert_all);
|
||||
if (!pathFindResult.first)
|
||||
return pathFindResult.second;
|
||||
|
||||
@@ -241,13 +265,18 @@ std::pair<bool, Json::Value>
|
||||
ripplePathFind (RippleLineCache::pointer const& cache,
|
||||
AccountID const& raSrc, AccountID const& raDst,
|
||||
STAmount const& saDstAmount, Json::Value const& jvSrcCurrencies,
|
||||
boost::optional<Json::Value> const& contextPaths, int const& level)
|
||||
boost::optional<Json::Value> const& contextPaths,
|
||||
int const& level, boost::optional<STAmount> saSendMax,
|
||||
bool convert_all)
|
||||
{
|
||||
auto const sa = STAmount(saDstAmount.issue(),
|
||||
STAmount::cMaxValue, STAmount::cMaxOffset);
|
||||
FindPaths fp(
|
||||
cache,
|
||||
raSrc,
|
||||
raDst,
|
||||
saDstAmount,
|
||||
convert_all ? sa : saDstAmount,
|
||||
saSendMax,
|
||||
level,
|
||||
4); // max paths
|
||||
|
||||
@@ -269,7 +298,6 @@ ripplePathFind (RippleLineCache::pointer const& cache,
|
||||
uSrcCurrencyID, jvSource[jss::currency].asString()))
|
||||
{
|
||||
WriteLog(lsINFO, RPCHandler) << "Bad currency.";
|
||||
|
||||
return std::make_pair(false, rpcError(rpcSRC_CUR_MALFORMED));
|
||||
}
|
||||
|
||||
@@ -287,6 +315,30 @@ ripplePathFind (RippleLineCache::pointer const& cache,
|
||||
return std::make_pair(false, rpcError(rpcSRC_ISR_MALFORMED));
|
||||
}
|
||||
|
||||
auto issue = Issue(uSrcCurrencyID, uSrcIssuerID);
|
||||
if (saSendMax)
|
||||
{
|
||||
// If the currencies don't match, ignore the source currency.
|
||||
if (uSrcCurrencyID != saSendMax->getCurrency())
|
||||
continue;
|
||||
|
||||
// If neither is the source and they are not equal, then the
|
||||
// source issuer is illegal.
|
||||
if (uSrcIssuerID != raSrc && saSendMax->getIssuer() != raSrc &&
|
||||
uSrcIssuerID != saSendMax->getIssuer())
|
||||
{
|
||||
return std::make_pair(false, rpcError(rpcSRC_ISR_MALFORMED));
|
||||
}
|
||||
|
||||
// If both are the source, use the source.
|
||||
// Otherwise, use the one that's not the source.
|
||||
if (uSrcIssuerID == raSrc)
|
||||
{
|
||||
issue.account = saSendMax->getIssuer() != raSrc ?
|
||||
saSendMax->getIssuer() : raSrc;
|
||||
}
|
||||
}
|
||||
|
||||
STPathSet spsComputed;
|
||||
if (contextPaths)
|
||||
{
|
||||
@@ -304,11 +356,11 @@ ripplePathFind (RippleLineCache::pointer const& cache,
|
||||
}
|
||||
|
||||
STPath fullLiquidityPath;
|
||||
auto valid = fp.findPathsForIssue(
|
||||
{ uSrcCurrencyID, uSrcIssuerID },
|
||||
auto result = fp.findPathsForIssue(
|
||||
issue,
|
||||
spsComputed,
|
||||
fullLiquidityPath);
|
||||
if (!valid)
|
||||
if (! result)
|
||||
{
|
||||
WriteLog(lsWARNING, RPCHandler)
|
||||
<< "ripple_path_find: No paths found.";
|
||||
@@ -321,22 +373,26 @@ ripplePathFind (RippleLineCache::pointer const& cache,
|
||||
xrpAccount() :
|
||||
(raSrc)
|
||||
: uSrcIssuerID; // Use specifed issuer.
|
||||
|
||||
STAmount saMaxAmount({ uSrcCurrencyID, issuer }, 1);
|
||||
saMaxAmount.negate();
|
||||
STAmount saMaxAmount = saSendMax.value_or(
|
||||
STAmount({uSrcCurrencyID, issuer}, 1u, 0, true));
|
||||
|
||||
boost::optional<PaymentSandbox> sandbox;
|
||||
sandbox.emplace(&*cache->getLedger(), tapNONE);
|
||||
assert(sandbox->open());
|
||||
|
||||
path::RippleCalc::Input rcInput;
|
||||
if (convert_all)
|
||||
rcInput.partialPaymentAllowed = true;
|
||||
|
||||
auto rc = path::RippleCalc::rippleCalculate(
|
||||
*sandbox,
|
||||
saMaxAmount, // --> Amount to send is unlimited
|
||||
// to get an estimate.
|
||||
saDstAmount, // --> Amount to deliver.
|
||||
convert_all ? sa : saDstAmount, // --> Amount to deliver.
|
||||
raDst, // --> Account to deliver to.
|
||||
raSrc, // --> Account sending from.
|
||||
spsComputed); // --> Path set.
|
||||
*result, // --> Path set.
|
||||
&rcInput);
|
||||
|
||||
WriteLog(lsWARNING, RPCHandler)
|
||||
<< "ripple_path_find:"
|
||||
@@ -345,13 +401,13 @@ ripplePathFind (RippleLineCache::pointer const& cache,
|
||||
<< " saMaxAmountAct=" << rc.actualAmountIn
|
||||
<< " saDstAmountAct=" << rc.actualAmountOut;
|
||||
|
||||
if (fullLiquidityPath.size() > 0 &&
|
||||
if (! convert_all &&
|
||||
! fullLiquidityPath.empty() &&
|
||||
(rc.result() == terNO_LINE || rc.result() == tecPATH_PARTIAL))
|
||||
{
|
||||
WriteLog(lsDEBUG, PathRequest)
|
||||
<< "Trying with an extra path element";
|
||||
|
||||
spsComputed.push_back(fullLiquidityPath);
|
||||
result->push_back(fullLiquidityPath);
|
||||
sandbox.emplace(&*cache->getLedger(), tapNONE);
|
||||
assert(sandbox->open());
|
||||
rc = path::RippleCalc::rippleCalculate(
|
||||
@@ -361,7 +417,7 @@ ripplePathFind (RippleLineCache::pointer const& cache,
|
||||
saDstAmount, // --> Amount to deliver.
|
||||
raDst, // --> Account to deliver to.
|
||||
raSrc, // --> Account sending from.
|
||||
spsComputed); // --> Path set.
|
||||
*result); // --> Path set.
|
||||
WriteLog(lsDEBUG, PathRequest)
|
||||
<< "Extra path element gives "
|
||||
<< transHuman(rc.result());
|
||||
@@ -379,7 +435,10 @@ ripplePathFind (RippleLineCache::pointer const& cache,
|
||||
|
||||
jvEntry[jss::source_amount] = rc.actualAmountIn.getJson(0);
|
||||
jvEntry[jss::paths_canonical] = Json::arrayValue;
|
||||
jvEntry[jss::paths_computed] = spsComputed.getJson(0);
|
||||
jvEntry[jss::paths_computed] = result->getJson(0);
|
||||
|
||||
if (convert_all)
|
||||
jvEntry[jss::destination_amount] = rc.actualAmountOut.getJson(0);
|
||||
|
||||
jvArray.append(jvEntry);
|
||||
}
|
||||
@@ -399,7 +458,7 @@ ripplePathFind (RippleLineCache::pointer const& cache,
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(true, jvArray);
|
||||
return std::make_pair(true, std::move(jvArray));
|
||||
}
|
||||
|
||||
} // ripple
|
||||
|
||||
@@ -140,21 +140,26 @@ void TxnSignApiFacade::processTransaction (
|
||||
netOPs_->processTransaction(transaction, bAdmin, bLocal, failType);
|
||||
}
|
||||
|
||||
bool TxnSignApiFacade::findPathsForOneIssuer (
|
||||
boost::optional<STPathSet>
|
||||
TxnSignApiFacade::findPathsForOneIssuer (
|
||||
AccountID const& dstAccountID,
|
||||
Issue const& srcIssue,
|
||||
STAmount const& dstAmount,
|
||||
int searchLevel,
|
||||
unsigned int const maxPaths,
|
||||
STPathSet& pathsOut,
|
||||
STPathSet const& paths,
|
||||
STPath& fullLiquidityPath) const
|
||||
{
|
||||
if (!ledger_) // Unit testing.
|
||||
// Note that unit tests don't (yet) need pathsOut or fullLiquidityPath.
|
||||
return true;
|
||||
if (! ledger_) // Unit testing.
|
||||
{
|
||||
// Note that unit tests don't (yet) need paths or fullLiquidityPath.
|
||||
boost::optional<STPathSet> result;
|
||||
result.emplace();
|
||||
return result;
|
||||
}
|
||||
|
||||
auto cache = std::make_shared<RippleLineCache> (ledger_);
|
||||
return ripple::findPathsForOneIssuer (
|
||||
return ripple::findPathsForOneIssuer(
|
||||
cache,
|
||||
accountID_,
|
||||
dstAccountID,
|
||||
@@ -162,7 +167,7 @@ bool TxnSignApiFacade::findPathsForOneIssuer (
|
||||
dstAmount,
|
||||
searchLevel,
|
||||
maxPaths,
|
||||
pathsOut,
|
||||
paths,
|
||||
fullLiquidityPath);
|
||||
}
|
||||
|
||||
@@ -413,18 +418,17 @@ static Json::Value checkPayment(
|
||||
if (!lpf.isOk ())
|
||||
return rpcError (rpcTOO_BUSY);
|
||||
|
||||
STPathSet spsPaths;
|
||||
STPath fullLiquidityPath;
|
||||
bool valid = apiFacade.findPathsForOneIssuer (
|
||||
auto result = apiFacade.findPathsForOneIssuer (
|
||||
*dstAccountID,
|
||||
sendMax.issue (),
|
||||
amount,
|
||||
getConfig ().PATH_SEARCH_OLD,
|
||||
4, // iMaxPaths
|
||||
spsPaths,
|
||||
{},
|
||||
fullLiquidityPath);
|
||||
|
||||
if (!valid)
|
||||
if (! result)
|
||||
{
|
||||
WriteLog (lsDEBUG, RPCHandler)
|
||||
<< "transactionSign: build_path: No paths found.";
|
||||
@@ -432,10 +436,10 @@ static Json::Value checkPayment(
|
||||
}
|
||||
WriteLog (lsDEBUG, RPCHandler)
|
||||
<< "transactionSign: build_path: "
|
||||
<< spsPaths.getJson (0);
|
||||
<< result->getJson (0);
|
||||
|
||||
if (!spsPaths.empty ())
|
||||
tx_json[jss::Paths] = spsPaths.getJson (0);
|
||||
if (! result->empty ())
|
||||
tx_json[jss::Paths] = result->getJson (0);
|
||||
}
|
||||
}
|
||||
return Json::Value();
|
||||
|
||||
@@ -70,13 +70,14 @@ public:
|
||||
|
||||
std::uint32_t getSeq () const;
|
||||
|
||||
bool findPathsForOneIssuer (
|
||||
boost::optional<STPathSet>
|
||||
findPathsForOneIssuer (
|
||||
AccountID const& dstAccountID,
|
||||
Issue const& srcIssue,
|
||||
STAmount const& dstAmount,
|
||||
int searchLevel,
|
||||
unsigned int const maxPaths,
|
||||
STPathSet& pathsOut,
|
||||
STPathSet const& paths,
|
||||
STPath& fullLiquidityPath) const;
|
||||
|
||||
void processTransaction (
|
||||
|
||||
@@ -37,16 +37,15 @@ paths::operator()(Env const& env, JTx& jt) const
|
||||
auto const amount = amountFromJson(
|
||||
sfAmount, jv[jss::Amount]);
|
||||
STPath fp;
|
||||
STPathSet ps;
|
||||
auto const found = findPathsForOneIssuer(
|
||||
std::make_shared<RippleLineCache>(
|
||||
env.open()), from, to,
|
||||
in_, amount,
|
||||
depth_, limit_, ps, fp);
|
||||
depth_, limit_, {}, fp);
|
||||
// VFALCO TODO API to allow caller to examine the STPathSet
|
||||
// VFALCO isDefault should be renamed to empty()
|
||||
if (found && ! ps.isDefault())
|
||||
jv[jss::Paths] = ps.getJson(0);
|
||||
if (found && ! found->isDefault())
|
||||
jv[jss::Paths] = found->getJson(0);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user