Convert all of an asset (RIPD-655)

This commit is contained in:
Miguel Portilla
2015-07-29 15:29:18 -04:00
committed by Scott Schurr
parent 1842878c40
commit 3d777f3f5d
24 changed files with 838 additions and 612 deletions

View File

@@ -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 ()

View File

@@ -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.

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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_;

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -335,7 +335,7 @@ TER RippleCalc::rippleCalculate ()
actualAmountIn_ += pathState->inPass();
actualAmountOut_ += pathState->outPass();
if (pathState->allLiquidityConsumed() || multiQuality)
if (multiQuality)
{
++iDry;
pathState->setQuality(0);

View File

@@ -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,

View File

@@ -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;

View File

@@ -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

View File

@@ -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();
}
};

View File

@@ -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);

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View File

@@ -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
{

View File

@@ -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)
{

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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();

View File

@@ -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 (

View File

@@ -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);
}
//------------------------------------------------------------------------------