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

@@ -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,70 +166,66 @@ 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 (! crCache->getLedger()->exists(
keylet::account(*raSrcAccount)))
{
// no source account
bValid = false;
jvStatus = rpcError (rpcSRC_ACT_NOT_FOUND);
}
// If send max specified, dst amt must be -1.
jvStatus = rpcError(rpcDST_AMT_MALFORMED);
return false;
}
if (bValid)
if (! crCache->getLedger()->exists(
keylet::account(*raSrcAccount)))
{
auto const sleDest = crCache->getLedger()->read(
keylet::account(*raDstAccount));
Json::Value& jvDestCur =
(jvStatus[jss::destination_currencies] = Json::arrayValue);
if (!sleDest)
{
// no destination account
jvDestCur.append (Json::Value ("XRP"));
if (!saDstAmount.native ())
{
// only XRP can be send to a non-existent account
bValid = false;
jvStatus = rpcError (rpcACT_NOT_FOUND);
}
else if (saDstAmount < STAmount (lrLedger->fees().accountReserve (0)))
{
// payment must meet reserve
bValid = false;
jvStatus = rpcError (rpcDST_AMT_MALFORMED);
}
}
else
{
bool const disallowXRP (
sleDest->getFlags() & lsfDisallowXRP);
auto usDestCurrID = accountDestCurrencies (
*raDstAccount, crCache, !disallowXRP);
for (auto const& currency : usDestCurrID)
jvDestCur.append (to_string (currency));
jvStatus["destination_tag"] =
(sleDest->getFlags () & lsfRequireDestTag)
!= 0;
}
// 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)
{
jvStatus[jss::ledger_hash] = to_string (lrLedger->info().hash);
jvStatus[jss::ledger_index] = lrLedger->seq();
jvDestCur.append (Json::Value (systemCurrencyCode()));
if (! saDstAmount.native ())
{
// Only XRP can be send to a non-existent account.
jvStatus = rpcError (rpcACT_NOT_FOUND);
return false;
}
if (! convert_all_ && saDstAmount <
STAmount (lrLedger->fees().accountReserve (0)))
{
// Payment must meet reserve.
jvStatus = rpcError (rpcDST_AMT_MALFORMED);
return false;
}
}
return bValid;
else
{
bool const disallowXRP (
sleDest->getFlags() & lsfDisallowXRP);
auto usDestCurrID = accountDestCurrencies (
*raDstAccount, crCache, ! disallowXRP);
for (auto const& currency : usDestCurrID)
jvDestCur.append (to_string (currency));
jvStatus[jss::destination_tag] =
(sleDest->getFlags() & lsfRequireDestTag);
}
jvStatus[jss::ledger_hash] = to_string (lrLedger->info().hash);
jvStatus[jss::ledger_index] = lrLedger->seq();
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,63 +261,85 @@ 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))
{
raSrcAccount = parseBase58<AccountID>(
jvParams[jss::source_account].asString());
if (! raSrcAccount)
{
jvStatus = rpcError (rpcSRC_ACT_MALFORMED);
return PFR_PJ_INVALID;
}
}
else if (complete)
{
jvStatus = rpcError (rpcSRC_ACT_MISSING);
jvStatus = rpcError(rpcSRC_ACT_MISSING);
return PFR_PJ_INVALID;
}
if (jvParams.isMember (jss::destination_account))
if (! jvParams.isMember(jss::destination_account))
{
raDstAccount = parseBase58<AccountID>(
jvParams[jss::destination_account].asString());
if (! raDstAccount)
{
jvStatus = rpcError (rpcDST_ACT_MALFORMED);
return PFR_PJ_INVALID;
}
}
else if (complete)
{
jvStatus = rpcError (rpcDST_ACT_MISSING);
jvStatus = rpcError(rpcDST_ACT_MISSING);
return PFR_PJ_INVALID;
}
if (jvParams.isMember (jss::destination_amount))
if (! jvParams.isMember(jss::destination_amount))
{
if (! amountFromJsonNoThrow (
saDstAmount, jvParams[jss::destination_amount]) ||
(saDstAmount.getCurrency ().isZero () &&
saDstAmount.getIssuer ().isNonZero ()) ||
(saDstAmount.getCurrency () == badCurrency ()) ||
saDstAmount <= zero)
jvStatus = rpcError(rpcDST_AMT_MISSING);
return PFR_PJ_INVALID;
}
raSrcAccount = parseBase58<AccountID>(
jvParams[jss::source_account].asString());
if (! raSrcAccount)
{
jvStatus = rpcError (rpcSRC_ACT_MALFORMED);
return PFR_PJ_INVALID;
}
raDstAccount = parseBase58<AccountID>(
jvParams[jss::destination_account].asString());
if (! raDstAccount)
{
jvStatus = rpcError (rpcDST_ACT_MALFORMED);
return PFR_PJ_INVALID;
}
if (! amountFromJsonNoThrow (
saDstAmount, jvParams[jss::destination_amount]))
{
jvStatus = rpcError (rpcDST_AMT_MALFORMED);
return PFR_PJ_INVALID;
}
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_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);
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;
}
}
else if (complete)
{
jvStatus = rpcError (rpcDST_ACT_MISSING);
return PFR_PJ_INVALID;
}
if (jvParams.isMember (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;
}
sciSourceCurrencies.insert ({uCur, uIss});
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;
}
// 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
<< 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;
}
found = true;
jvArray.append (jvEntry);
}
else
{
m_journal.debug << iIdentifier << " rippleCalc returns "
<< transHuman (rc.result ());
}
}
else
{
m_journal.debug << iIdentifier << " No paths found";
}
JLOG(m_journal.debug)
<< iIdentifier
<< " Trying to find paths: "
<< STAmount(i, 1).getFullText();
if (findPaths(cache, fp, i, jvArray))
found = true;
}
iLastLevel = iLevel;