Files
rippled/src/ripple_app/rpc/RPCHandler.cpp
2014-01-10 23:09:26 -08:00

4146 lines
138 KiB
C++

//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
//
// Carries out the RPC.
//
SETUP_LOG (RPCHandler)
RPCHandler::RPCHandler (NetworkOPs* netOps)
: mNetOps (netOps)
, mRole (Config::FORBID)
{
}
RPCHandler::RPCHandler (NetworkOPs* netOps, InfoSub::pointer infoSub)
: mNetOps (netOps)
, mInfoSub (infoSub)
, mRole (Config::FORBID)
{
}
class LegacyPathFind
{
public:
LegacyPathFind (bool isAdmin) : m_isOkay (false)
{
if (isAdmin)
++inProgress;
else
{
if ((getApp().getJobQueue ().getJobCountGE (jtCLIENT) > 50) ||
getApp().getFeeTrack().isLoadedLocal ())
return;
do
{
int prevVal = inProgress.load();
if (prevVal >= maxInProgress)
return;
if (inProgress.compare_exchange_strong (prevVal, prevVal + 1,
std::memory_order_release, std::memory_order_relaxed))
break;
}
while (1);
}
m_isOkay = true;
}
~LegacyPathFind ()
{
if (m_isOkay)
--inProgress;
}
bool isOkay ()
{
return m_isOkay;
}
private:
static std::atomic <int> inProgress;
static int maxInProgress;
bool m_isOkay;
};
std::atomic <int> LegacyPathFind::inProgress (0);
int LegacyPathFind::maxInProgress (2);
Json::Value RPCHandler::transactionSign (Json::Value params, bool bSubmit, bool bFailHard, Application::ScopedLockType& mlh)
{
if (getApp().getFeeTrack().isLoadedCluster() && (mRole != Config::ADMIN))
return rpcError(rpcTOO_BUSY);
Json::Value jvResult;
RippleAddress naSeed;
RippleAddress raSrcAddressID;
bool bOffline = params.isMember ("offline") && params["offline"].asBool ();
WriteLog (lsDEBUG, RPCHandler) << boost::str (boost::format ("transactionSign: %s") % params);
if (!bOffline && !getConfig ().RUN_STANDALONE && (getApp().getLedgerMaster().getValidatedLedgerAge() > 120))
{
return rpcError (rpcNO_CURRENT);
}
if (!params.isMember ("secret") || !params.isMember ("tx_json"))
{
return rpcError (rpcINVALID_PARAMS);
}
Json::Value txJSON = params["tx_json"];
if (!txJSON.isObject ())
{
return rpcError (rpcINVALID_PARAMS);
}
if (!naSeed.setSeedGeneric (params["secret"].asString ()))
{
return rpcError (rpcBAD_SEED);
}
if (!txJSON.isMember ("Account"))
{
return rpcError (rpcSRC_ACT_MISSING);
}
if (!raSrcAddressID.setAccountID (txJSON["Account"].asString ()))
{
return rpcError (rpcSRC_ACT_MALFORMED);
}
if (!txJSON.isMember ("TransactionType"))
{
return rpcError (rpcINVALID_PARAMS);
}
std::string sType = txJSON["TransactionType"].asString ();
Ledger::pointer lSnapshot = mNetOps->getCurrentLedger ();
AccountState::pointer asSrc = bOffline
? AccountState::pointer () // Don't look up address if offline.
: mNetOps->getAccountState (lSnapshot, raSrcAddressID);
mlh.unlock();
if (!bOffline && !asSrc)
{
// If not offline and did not find account, error.
WriteLog (lsDEBUG, RPCHandler) << boost::str (boost::format ("transactionSign: Failed to find source account in current ledger: %s")
% raSrcAddressID.humanAccountID ());
return rpcError (rpcSRC_ACT_NOT_FOUND);
}
if (!txJSON.isMember ("Fee")
&& (
"AccountSet" == sType
|| "Payment" == sType
|| "OfferCreate" == sType
|| "OfferCancel" == sType
|| "TrustSet" == sType))
{
// feeReq = lSnapshot->scaleFeeLoad(,
txJSON["Fee"] = (int) getConfig ().FEE_DEFAULT;
}
if ("Payment" == sType)
{
RippleAddress dstAccountID;
if (!txJSON.isMember ("Destination"))
{
return rpcError (rpcDST_ACT_MISSING);
}
if (!dstAccountID.setAccountID (txJSON["Destination"].asString ()))
{
return rpcError (rpcDST_ACT_MALFORMED);
}
if (txJSON.isMember ("Paths") && params.isMember ("build_path"))
{
// Asking to build a path when providing one is an error.
return rpcError (rpcINVALID_PARAMS);
}
if (!txJSON.isMember ("Paths") && txJSON.isMember ("Amount") && params.isMember ("build_path"))
{
// Need a ripple path.
STPathSet spsPaths;
uint160 uSrcCurrencyID;
uint160 uSrcIssuerID;
STAmount saSendMax;
STAmount saSend;
if (!txJSON.isMember ("Amount") // Amount required.
|| !saSend.bSetJson (txJSON["Amount"])) // Must be valid.
return rpcError (rpcDST_AMT_MALFORMED);
if (txJSON.isMember ("SendMax"))
{
if (!saSendMax.bSetJson (txJSON["SendMax"]))
return rpcError (rpcINVALID_PARAMS);
}
else
{
// If no SendMax, default to Amount with sender as issuer.
saSendMax = saSend;
saSendMax.setIssuer (raSrcAddressID.getAccountID ());
}
if (saSendMax.isNative () && saSend.isNative ())
{
// Asking to build a path for XRP to XRP is an error.
return rpcError (rpcINVALID_PARAMS);
}
{
LegacyPathFind lpf (mRole == Config::ADMIN);
if (!lpf.isOkay ())
return rpcError (rpcTOO_BUSY);
bool bValid;
RippleLineCache::pointer cache = boost::make_shared<RippleLineCache> (lSnapshot);
Pathfinder pf (cache, raSrcAddressID, dstAccountID,
saSendMax.getCurrency (), saSendMax.getIssuer (), saSend, bValid);
STPath extraPath;
if (!bValid || !pf.findPaths (getConfig ().PATH_SEARCH_OLD, 4, spsPaths, extraPath))
{
WriteLog (lsDEBUG, RPCHandler) << "transactionSign: build_path: No paths found.";
return rpcError (rpcNO_PATH);
}
else
{
WriteLog (lsDEBUG, RPCHandler) << "transactionSign: build_path: " << spsPaths.getJson (0);
}
if (!spsPaths.isEmpty ())
{
txJSON["Paths"] = spsPaths.getJson (0);
}
}
}
}
if (!txJSON.isMember ("Fee")
&& (
"AccountSet" == txJSON["TransactionType"].asString ()
|| "OfferCreate" == txJSON["TransactionType"].asString ()
|| "OfferCancel" == txJSON["TransactionType"].asString ()
|| "TrustSet" == txJSON["TransactionType"].asString ()))
{
txJSON["Fee"] = (int) getConfig ().FEE_DEFAULT;
}
if (!txJSON.isMember ("Sequence"))
{
if (bOffline)
{
// If offline, Sequence is mandatory.
return rpcError (rpcINVALID_PARAMS);
}
else
{
txJSON["Sequence"] = asSrc->getSeq ();
}
}
if (!txJSON.isMember ("Flags")) txJSON["Flags"] = 0;
if (!bOffline)
{
SLE::pointer sleAccountRoot = mNetOps->getSLEi (lSnapshot, Ledger::getAccountRootIndex (raSrcAddressID.getAccountID ()));
if (!sleAccountRoot)
{
// XXX Ignore transactions for accounts not created.
return rpcError (rpcSRC_ACT_NOT_FOUND);
}
}
bool bHaveAuthKey = false;
RippleAddress naAuthorizedPublic;
RippleAddress naSecret = RippleAddress::createSeedGeneric (params["secret"].asString ());
RippleAddress naMasterGenerator = RippleAddress::createGeneratorPublic (naSecret);
// Find the index of Account from the master generator, so we can generate the public and private keys.
RippleAddress naMasterAccountPublic;
unsigned int iIndex = 0;
bool bFound = false;
// Don't look at ledger entries to determine if the account exists. Don't want to leak to thin server that these accounts are
// related.
while (!bFound && iIndex != getConfig ().ACCOUNT_PROBE_MAX)
{
naMasterAccountPublic.setAccountPublic (naMasterGenerator, iIndex);
WriteLog (lsWARNING, RPCHandler) << "authorize: " << iIndex << " : " << naMasterAccountPublic.humanAccountID () << " : " << raSrcAddressID.humanAccountID ();
bFound = raSrcAddressID.getAccountID () == naMasterAccountPublic.getAccountID ();
if (!bFound)
++iIndex;
}
if (!bFound)
{
return rpcError (rpcBAD_SECRET);
}
// Use the generator to determine the associated public and private keys.
RippleAddress naGenerator = RippleAddress::createGeneratorPublic (naSecret);
RippleAddress naAccountPublic = RippleAddress::createAccountPublic (naGenerator, iIndex);
RippleAddress naAccountPrivate = RippleAddress::createAccountPrivate (naGenerator, naSecret, iIndex);
if (bHaveAuthKey
// The generated pair must match authorized...
&& naAuthorizedPublic.getAccountID () != naAccountPublic.getAccountID ()
// ... or the master key must have been used.
&& raSrcAddressID.getAccountID () != naAccountPublic.getAccountID ())
{
// Log::out() << "iIndex: " << iIndex;
// Log::out() << "sfAuthorizedKey: " << strHex(asSrc->getAuthorizedKey().getAccountID());
// Log::out() << "naAccountPublic: " << strHex(naAccountPublic.getAccountID());
return rpcError (rpcSRC_ACT_NOT_FOUND);
}
std::unique_ptr<STObject> sopTrans;
try
{
sopTrans = STObject::parseJson (txJSON);
}
catch (std::exception& e)
{
jvResult["error"] = "malformedTransaction";
jvResult["error_exception"] = e.what ();
return jvResult;
}
sopTrans->setFieldVL (sfSigningPubKey, naAccountPublic.getAccountPublic ());
SerializedTransaction::pointer stpTrans;
try
{
stpTrans = boost::make_shared<SerializedTransaction> (*sopTrans);
}
catch (std::exception& e)
{
jvResult["error"] = "invalidTransaction";
jvResult["error_exception"] = e.what ();
return jvResult;
}
if (params.isMember ("debug_signing"))
{
jvResult["tx_unsigned"] = strHex (stpTrans->getSerializer ().peekData ());
jvResult["tx_signing_hash"] = stpTrans->getSigningHash ().ToString ();
}
// FIXME: For performance, transactions should not be signed in this code path.
stpTrans->sign (naAccountPrivate);
Transaction::pointer tpTrans;
try
{
tpTrans = boost::make_shared<Transaction> (stpTrans, false);
}
catch (std::exception& e)
{
jvResult["error"] = "internalTransaction";
jvResult["error_exception"] = e.what ();
return jvResult;
}
try
{
// FIXME: For performance, should use asynch interface
tpTrans = mNetOps->submitTransactionSync (tpTrans, mRole == Config::ADMIN, bFailHard, bSubmit);
if (!tpTrans)
{
jvResult["error"] = "invalidTransaction";
jvResult["error_exception"] = "Unable to sterilize transaction.";
return jvResult;
}
}
catch (std::exception& e)
{
jvResult["error"] = "internalSubmit";
jvResult["error_exception"] = e.what ();
return jvResult;
}
try
{
jvResult["tx_json"] = tpTrans->getJson (0);
jvResult["tx_blob"] = strHex (tpTrans->getSTransaction ()->getSerializer ().peekData ());
if (temUNCERTAIN != tpTrans->getResult ())
{
std::string sToken;
std::string sHuman;
transResultInfo (tpTrans->getResult (), sToken, sHuman);
jvResult["engine_result"] = sToken;
jvResult["engine_result_code"] = tpTrans->getResult ();
jvResult["engine_result_message"] = sHuman;
}
return jvResult;
}
catch (std::exception& e)
{
jvResult["error"] = "internalJson";
jvResult["error_exception"] = e.what ();
return jvResult;
}
}
// Look up the master public generator for a regular seed so we may index source accounts ids.
// --> naRegularSeed
// <-- naMasterGenerator
Json::Value RPCHandler::getMasterGenerator (Ledger::ref lrLedger, const RippleAddress& naRegularSeed, RippleAddress& naMasterGenerator)
{
RippleAddress na0Public; // To find the generator's index.
RippleAddress na0Private; // To decrypt the master generator's cipher.
RippleAddress naGenerator = RippleAddress::createGeneratorPublic (naRegularSeed);
na0Public.setAccountPublic (naGenerator, 0);
na0Private.setAccountPrivate (naGenerator, naRegularSeed, 0);
SLE::pointer sleGen = mNetOps->getGenerator (lrLedger, na0Public.getAccountID ());
if (!sleGen)
{
// No account has been claimed or has had it password set for seed.
return rpcError (rpcNO_ACCOUNT);
}
Blob vucCipher = sleGen->getFieldVL (sfGenerator);
Blob vucMasterGenerator = na0Private.accountPrivateDecrypt (na0Public, vucCipher);
if (vucMasterGenerator.empty ())
{
return rpcError (rpcFAIL_GEN_DECRPYT);
}
naMasterGenerator.setGenerator (vucMasterGenerator);
return Json::Value (Json::objectValue);
}
// Given a seed and a source account get the regular public and private key for authorizing transactions.
// - Make sure the source account can pay.
// --> naRegularSeed : To find the generator
// --> naSrcAccountID : Account we want the public and private regular keys to.
// <-- naAccountPublic : Regular public key for naSrcAccountID
// <-- naAccountPrivate : Regular private key for naSrcAccountID
// <-- saSrcBalance: Balance minus fee.
// --> naVerifyGenerator : If provided, the found master public generator must match.
// XXX Be more lenient, allow use of master generator on claimed accounts.
Json::Value RPCHandler::authorize (Ledger::ref lrLedger,
const RippleAddress& naRegularSeed, const RippleAddress& naSrcAccountID,
RippleAddress& naAccountPublic, RippleAddress& naAccountPrivate,
STAmount& saSrcBalance, const STAmount& saFee, AccountState::pointer& asSrc,
const RippleAddress& naVerifyGenerator)
{
// Source/paying account must exist.
asSrc = mNetOps->getAccountState (lrLedger, naSrcAccountID);
if (!asSrc)
{
return rpcError (rpcSRC_ACT_NOT_FOUND);
}
RippleAddress naMasterGenerator;
if (asSrc->haveAuthorizedKey ())
{
Json::Value obj = getMasterGenerator (lrLedger, naRegularSeed, naMasterGenerator);
if (!obj.empty ())
return obj;
}
else
{
// Try the seed as a master seed.
naMasterGenerator = RippleAddress::createGeneratorPublic (naRegularSeed);
}
// If naVerifyGenerator is provided, make sure it is the master generator.
if (naVerifyGenerator.isValid () && naMasterGenerator != naVerifyGenerator)
{
return rpcError (rpcWRONG_SEED);
}
// Find the index of the account from the master generator, so we can generate the public and private keys.
RippleAddress naMasterAccountPublic;
unsigned int iIndex = 0;
bool bFound = false;
// Don't look at ledger entries to determine if the account exists. Don't want to leak to thin server that these accounts are
// related.
while (!bFound && iIndex != getConfig ().ACCOUNT_PROBE_MAX)
{
naMasterAccountPublic.setAccountPublic (naMasterGenerator, iIndex);
WriteLog (lsDEBUG, RPCHandler) << "authorize: " << iIndex << " : " << naMasterAccountPublic.humanAccountID () << " : " << naSrcAccountID.humanAccountID ();
bFound = naSrcAccountID.getAccountID () == naMasterAccountPublic.getAccountID ();
if (!bFound)
++iIndex;
}
if (!bFound)
{
return rpcError (rpcACT_NOT_FOUND);
}
// Use the regular generator to determine the associated public and private keys.
RippleAddress naGenerator = RippleAddress::createGeneratorPublic (naRegularSeed);
naAccountPublic.setAccountPublic (naGenerator, iIndex);
naAccountPrivate.setAccountPrivate (naGenerator, naRegularSeed, iIndex);
if (asSrc->haveAuthorizedKey () && (asSrc->getAuthorizedKey ().getAccountID () != naAccountPublic.getAccountID ()))
{
// Log::out() << "iIndex: " << iIndex;
// Log::out() << "sfAuthorizedKey: " << strHex(asSrc->getAuthorizedKey().getAccountID());
// Log::out() << "naAccountPublic: " << strHex(naAccountPublic.getAccountID());
return rpcError (rpcPASSWD_CHANGED);
}
saSrcBalance = asSrc->getBalance ();
if (saSrcBalance < saFee)
{
WriteLog (lsINFO, RPCHandler) << "authorize: Insufficient funds for fees: fee=" << saFee.getText () << " balance=" << saSrcBalance.getText ();
return rpcError (rpcINSUF_FUNDS);
}
else
{
saSrcBalance -= saFee;
}
return Json::Value ();
}
// --> strIdent: public key, account ID, or regular seed.
// --> bStrict: Only allow account id or public key.
// <-- bIndex: true if iIndex > 0 and used the index.
Json::Value RPCHandler::accountFromString (Ledger::ref lrLedger, RippleAddress& naAccount, bool& bIndex, const std::string& strIdent, const int iIndex, const bool bStrict)
{
RippleAddress naSeed;
if (naAccount.setAccountPublic (strIdent) || naAccount.setAccountID (strIdent))
{
// Got the account.
bIndex = false;
}
else if (bStrict)
{
return naAccount.setAccountID (strIdent, Base58::getBitcoinAlphabet ())
? rpcError (rpcACT_BITCOIN)
: rpcError (rpcACT_MALFORMED);
}
// Must be a seed.
else if (!naSeed.setSeedGeneric (strIdent))
{
return rpcError (rpcBAD_SEED);
}
else
{
// We allow the use of the seeds to access #0.
// This is poor practice and merely for debuging convenience.
RippleAddress naRegular0Public;
RippleAddress naRegular0Private;
RippleAddress naGenerator = RippleAddress::createGeneratorPublic (naSeed);
naRegular0Public.setAccountPublic (naGenerator, 0);
naRegular0Private.setAccountPrivate (naGenerator, naSeed, 0);
// uint160 uGeneratorID = naRegular0Public.getAccountID();
SLE::pointer sleGen = mNetOps->getGenerator (lrLedger, naRegular0Public.getAccountID ());
if (!sleGen)
{
// Didn't find a generator map, assume it is a master generator.
nothing ();
}
else
{
// Found master public key.
Blob vucCipher = sleGen->getFieldVL (sfGenerator);
Blob vucMasterGenerator = naRegular0Private.accountPrivateDecrypt (naRegular0Public, vucCipher);
if (vucMasterGenerator.empty ())
{
rpcError (rpcNO_GEN_DECRPYT);
}
naGenerator.setGenerator (vucMasterGenerator);
}
bIndex = !iIndex;
naAccount.setAccountPublic (naGenerator, iIndex);
}
return Json::Value (Json::objectValue);
}
Json::Value RPCHandler::doAccountCurrencies (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
Ledger::pointer lpLedger;
Json::Value jvResult = lookupLedger (params, lpLedger);
if (!lpLedger)
return jvResult;
if (lpLedger->isImmutable ())
masterLockHolder.unlock ();
if (!params.isMember ("account") && !params.isMember ("ident"))
return rpcError (rpcINVALID_PARAMS);
std::string strIdent = params.isMember ("account") ? params["account"].asString () : params["ident"].asString ();
bool bIndex;
int iIndex = params.isMember ("account_index") ? params["account_index"].asUInt () : 0;
bool bStrict = params.isMember ("strict") && params["strict"].asBool ();
RippleAddress naAccount;
// Get info on account.
Json::Value jvAccepted = accountFromString (lpLedger, naAccount, bIndex, strIdent, iIndex, bStrict);
if (!jvAccepted.empty ())
return jvAccepted;
std::set<uint160> send, receive;
AccountItems rippleLines (naAccount.getAccountID (), lpLedger, AccountItem::pointer (new RippleState ()));
BOOST_FOREACH(AccountItem::ref item, rippleLines.getItems ())
{
RippleState* rspEntry = (RippleState*) item.get ();
const STAmount& saBalance = rspEntry->getBalance ();
if (saBalance < rspEntry->getLimit ())
receive.insert (saBalance.getCurrency ());
if ((-saBalance) < rspEntry->getLimitPeer ())
send.insert (saBalance.getCurrency ());
}
send.erase (CURRENCY_BAD);
receive.erase (CURRENCY_BAD);
Json::Value& sendCurrencies = (jvResult["send_currencies"] = Json::arrayValue);
BOOST_FOREACH(uint160 const& c, send)
{
sendCurrencies.append (STAmount::createHumanCurrency (c));
}
Json::Value& recvCurrencies = (jvResult["receive_currencies"] = Json::arrayValue);
BOOST_FOREACH(uint160 const& c, receive)
{
recvCurrencies.append (STAmount::createHumanCurrency (c));
}
return jvResult;
}
// {
// account: <indent>,
// account_index : <index> // optional
// strict: <bool> // true, only allow public keys and addresses. false, default.
// ledger_hash : <ledger>
// ledger_index : <ledger_index>
// }
Json::Value RPCHandler::doAccountInfo (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
Ledger::pointer lpLedger;
Json::Value jvResult = lookupLedger (params, lpLedger);
if (!lpLedger)
return jvResult;
if (!params.isMember ("account") && !params.isMember ("ident"))
return rpcError (rpcINVALID_PARAMS);
std::string strIdent = params.isMember ("account") ? params["account"].asString () : params["ident"].asString ();
bool bIndex;
int iIndex = params.isMember ("account_index") ? params["account_index"].asUInt () : 0;
bool bStrict = params.isMember ("strict") && params["strict"].asBool ();
RippleAddress naAccount;
// Get info on account.
Json::Value jvAccepted = accountFromString (lpLedger, naAccount, bIndex, strIdent, iIndex, bStrict);
if (!jvAccepted.empty ())
return jvAccepted;
AccountState::pointer asAccepted = mNetOps->getAccountState (lpLedger, naAccount);
if (asAccepted)
{
asAccepted->addJson (jvAccepted);
jvResult["account_data"] = jvAccepted;
}
else
{
jvResult["account"] = naAccount.humanAccountID ();
jvResult = rpcError (rpcACT_NOT_FOUND, jvResult);
}
return jvResult;
}
Json::Value RPCHandler::doBlackList (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
masterLockHolder.unlock();
if (params.isMember("threshold"))
return getApp().getResourceManager().getJson(params["threshold"].asInt());
else
return getApp().getResourceManager().getJson();
}
// {
// ip: <string>,
// port: <number>
// }
// XXX Might allow domain for manual connections.
Json::Value RPCHandler::doConnect (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
if (getConfig ().RUN_STANDALONE)
return "cannot connect in standalone mode";
if (!params.isMember ("ip"))
return rpcError (rpcINVALID_PARAMS);
std::string strIp = params["ip"].asString ();
int iPort = params.isMember ("port") ? params["port"].asInt () : -1;
// XXX Validate legal IP and port
getApp().getPeers ().connectTo (strIp, iPort);
return "connecting";
}
#if ENABLE_INSECURE
// {
// key: <string>
// }
Json::Value RPCHandler::doDataDelete (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
if (!params.isMember ("key"))
return rpcError (rpcINVALID_PARAMS);
std::string strKey = params["key"].asString ();
Json::Value ret = Json::Value (Json::objectValue);
if (getApp().getLocalCredentials ().dataDelete (strKey))
{
ret["key"] = strKey;
}
else
{
ret = rpcError (rpcINTERNAL);
}
return ret;
}
#endif
#if ENABLE_INSECURE
// {
// key: <string>
// }
Json::Value RPCHandler::doDataFetch (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
if (!params.isMember ("key"))
return rpcError (rpcINVALID_PARAMS);
std::string strKey = params["key"].asString ();
std::string strValue;
Json::Value ret = Json::Value (Json::objectValue);
ret["key"] = strKey;
if (getApp().getLocalCredentials ().dataFetch (strKey, strValue))
ret["value"] = strValue;
return ret;
}
#endif
#if ENABLE_INSECURE
// {
// key: <string>
// value: <string>
// }
Json::Value RPCHandler::doDataStore (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
if (!params.isMember ("key")
|| !params.isMember ("value"))
return rpcError (rpcINVALID_PARAMS);
std::string strKey = params["key"].asString ();
std::string strValue = params["value"].asString ();
Json::Value ret = Json::Value (Json::objectValue);
if (getApp().getLocalCredentials ().dataStore (strKey, strValue))
{
ret["key"] = strKey;
ret["value"] = strValue;
}
else
{
ret = rpcError (rpcINTERNAL);
}
return ret;
}
#endif
#if 0
// XXX Needs to be revised for new paradigm
// nickname_info <nickname>
// Note: Nicknames are not automatically looked up by commands as they are advisory and can be changed.
Json::Value RPCHandler::doNicknameInfo (Json::Value params)
{
std::string strNickname = params[0u].asString ();
boost::trim (strNickname);
if (strNickname.empty ())
{
return rpcError (rpcNICKNAME_MALFORMED);
}
NicknameState::pointer nsSrc = mNetOps->getNicknameState (uint256 (0), strNickname);
if (!nsSrc)
{
return rpcError (rpcNICKNAME_MISSING);
}
Json::Value ret (Json::objectValue);
ret["nickname"] = strNickname;
nsSrc->addJson (ret);
return ret;
}
#endif
// {
// 'ident' : <indent>,
// 'account_index' : <index> // optional
// }
// XXX This would be better if it took the ledger.
Json::Value RPCHandler::doOwnerInfo (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
if (!params.isMember ("account") && !params.isMember ("ident"))
return rpcError (rpcINVALID_PARAMS);
std::string strIdent = params.isMember ("account") ? params["account"].asString () : params["ident"].asString ();
bool bIndex;
int iIndex = params.isMember ("account_index") ? params["account_index"].asUInt () : 0;
RippleAddress raAccount;
Json::Value ret;
// Get info on account.
Json::Value jAccepted = accountFromString (mNetOps->getClosedLedger (), raAccount, bIndex, strIdent, iIndex, false);
ret["accepted"] = jAccepted.empty () ? mNetOps->getOwnerInfo (mNetOps->getClosedLedger (), raAccount) : jAccepted;
Json::Value jCurrent = accountFromString (mNetOps->getCurrentLedger (), raAccount, bIndex, strIdent, iIndex, false);
ret["current"] = jCurrent.empty () ? mNetOps->getOwnerInfo (mNetOps->getCurrentLedger (), raAccount) : jCurrent;
return ret;
}
Json::Value RPCHandler::doPeers (Json::Value, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
Json::Value jvResult (Json::objectValue);
jvResult["peers"] = getApp().getPeers ().getPeersJson ();
getApp().getUNL().addClusterStatus(jvResult);
return jvResult;
}
Json::Value RPCHandler::doPing (Json::Value, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
return Json::Value (Json::objectValue);
}
Json::Value RPCHandler::doPrint (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
masterLockHolder.unlock ();
JsonPropertyStream stream;
if (params.isObject() && params["params"].isArray() && params["params"][0u].isString ())
getApp().write (stream, params["params"][0u].asString());
else
getApp().write (stream);
return stream.top();
}
// profile offers <pass_a> <account_a> <currency_offer_a> <account_b> <currency_offer_b> <count> [submit]
// profile 0:offers 1:pass_a 2:account_a 3:currency_offer_a 4:account_b 5:currency_offer_b 6:<count> 7:[submit]
// issuer is the offering account
// --> submit: 'submit|true|false': defaults to false
// Prior to running allow each to have a credit line of what they will be getting from the other account.
Json::Value RPCHandler::doProfile (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
/* need to fix now that sharedOfferCreate is gone
int iArgs = params.size();
RippleAddress naSeedA;
RippleAddress naAccountA;
uint160 uCurrencyOfferA;
RippleAddress naSeedB;
RippleAddress naAccountB;
uint160 uCurrencyOfferB;
uint32 iCount = 100;
bool bSubmit = false;
if (iArgs < 6 || "offers" != params[0u].asString())
{
return rpcError(rpcINVALID_PARAMS);
}
if (!naSeedA.setSeedGeneric(params[1u].asString())) // <pass_a>
return rpcError(rpcINVALID_PARAMS);
naAccountA.setAccountID(params[2u].asString()); // <account_a>
if (!STAmount::currencyFromString(uCurrencyOfferA, params[3u].asString())) // <currency_offer_a>
return rpcError(rpcINVALID_PARAMS);
naAccountB.setAccountID(params[4u].asString()); // <account_b>
if (!STAmount::currencyFromString(uCurrencyOfferB, params[5u].asString())) // <currency_offer_b>
return rpcError(rpcINVALID_PARAMS);
iCount = lexicalCast <uint32>(params[6u].asString());
if (iArgs >= 8 && "false" != params[7u].asString())
bSubmit = true;
LogSink::get()->setMinSeverity(lsFATAL,true);
boost::posix_time::ptime ptStart(boost::posix_time::microsec_clock::local_time());
for(unsigned int n=0; n<iCount; n++)
{
RippleAddress naMasterGeneratorA;
RippleAddress naAccountPublicA;
RippleAddress naAccountPrivateA;
AccountState::pointer asSrcA;
STAmount saSrcBalanceA;
Json::Value jvObjA = authorize(uint256(0), naSeedA, naAccountA, naAccountPublicA, naAccountPrivateA,
saSrcBalanceA, getConfig ().FEE_DEFAULT, asSrcA, naMasterGeneratorA);
if (!jvObjA.empty())
return jvObjA;
Transaction::pointer tpOfferA = Transaction::sharedOfferCreate(
naAccountPublicA, naAccountPrivateA,
naAccountA, // naSourceAccount,
asSrcA->getSeq(), // uSeq
getConfig ().FEE_DEFAULT,
0, // uSourceTag,
false, // bPassive
STAmount(uCurrencyOfferA, naAccountA.getAccountID(), 1), // saTakerPays
STAmount(uCurrencyOfferB, naAccountB.getAccountID(), 1+n), // saTakerGets
0); // uExpiration
if (bSubmit)
tpOfferA = mNetOps->submitTransactionSync(tpOfferA); // FIXME: Don't use synch interface
}
boost::posix_time::ptime ptEnd(boost::posix_time::microsec_clock::local_time());
boost::posix_time::time_duration tdInterval = ptEnd-ptStart;
long lMicroseconds = tdInterval.total_microseconds();
int iTransactions = iCount;
float fRate = lMicroseconds ? iTransactions/(lMicroseconds/1000000.0) : 0.0;
Json::Value obj(Json::objectValue);
obj["transactions"] = iTransactions;
obj["submit"] = bSubmit;
obj["start"] = boost::posix_time::to_simple_string(ptStart);
obj["end"] = boost::posix_time::to_simple_string(ptEnd);
obj["interval"] = boost::posix_time::to_simple_string(tdInterval);
obj["rate_per_second"] = fRate;
*/
Json::Value obj (Json::objectValue);
return obj;
}
// {
// // if either of these parameters is set, a custom generator is used
// difficulty: <number> // optional
// secret: <secret> // optional
// }
Json::Value RPCHandler::doProofCreate (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
masterLockHolder.unlock ();
// XXX: Add ability to create proof with arbitrary time
Json::Value jvResult (Json::objectValue);
if (params.isMember ("difficulty") || params.isMember ("secret"))
{
// VFALCO TODO why aren't we using the app's factory?
std::unique_ptr <ProofOfWorkFactory> pgGen (ProofOfWorkFactory::New ());
if (params.isMember ("difficulty"))
{
if (!params["difficulty"].isIntegral ())
return rpcError (rpcINVALID_PARAMS);
int iDifficulty = params["difficulty"].asInt ();
if (iDifficulty < 0 || iDifficulty > ProofOfWorkFactory::kMaxDifficulty)
return rpcError (rpcINVALID_PARAMS);
pgGen->setDifficulty (iDifficulty);
}
if (params.isMember ("secret"))
{
uint256 uSecret (params["secret"].asString ());
pgGen->setSecret (uSecret);
}
jvResult["token"] = pgGen->getProof ().getToken ();
jvResult["secret"] = pgGen->getSecret ().GetHex ();
}
else
{
jvResult["token"] = getApp().getProofOfWorkFactory ().getProof ().getToken ();
}
return jvResult;
}
// {
// token: <token>
// }
Json::Value RPCHandler::doProofSolve (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
masterLockHolder.unlock ();
Json::Value jvResult;
if (!params.isMember ("token"))
return rpcError (rpcINVALID_PARAMS);
std::string strToken = params["token"].asString ();
if (!ProofOfWork::validateToken (strToken))
return rpcError (rpcINVALID_PARAMS);
ProofOfWork powProof (strToken);
uint256 uSolution = powProof.solve ();
jvResult["solution"] = uSolution.GetHex ();
return jvResult;
}
// {
// token: <token>
// solution: <solution>
// // if either of these parameters is set, a custom verifier is used
// difficulty: <number> // optional
// secret: <secret> // optional
// }
Json::Value RPCHandler::doProofVerify (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
masterLockHolder.unlock ();
// XXX Add ability to check proof against arbitrary time
Json::Value jvResult;
if (!params.isMember ("token"))
return rpcError (rpcINVALID_PARAMS);
if (!params.isMember ("solution"))
return rpcError (rpcINVALID_PARAMS);
std::string strToken = params["token"].asString ();
uint256 uSolution (params["solution"].asString ());
PowResult prResult;
if (params.isMember ("difficulty") || params.isMember ("secret"))
{
// VFALCO TODO why aren't we using the app's factory?
std::unique_ptr <ProofOfWorkFactory> pgGen (ProofOfWorkFactory::New ());
if (params.isMember ("difficulty"))
{
if (!params["difficulty"].isIntegral ())
return rpcError (rpcINVALID_PARAMS);
int iDifficulty = params["difficulty"].asInt ();
if (iDifficulty < 0 || iDifficulty > ProofOfWorkFactory::kMaxDifficulty)
return rpcError (rpcINVALID_PARAMS);
pgGen->setDifficulty (iDifficulty);
}
if (params.isMember ("secret"))
{
uint256 uSecret (params["secret"].asString ());
pgGen->setSecret (uSecret);
}
prResult = pgGen->checkProof (strToken, uSolution);
jvResult["secret"] = pgGen->getSecret ().GetHex ();
}
else
{
// XXX Proof should not be marked as used from this
prResult = getApp().getProofOfWorkFactory ().checkProof (strToken, uSolution);
}
std::string sToken;
std::string sHuman;
ProofOfWork::calcResultInfo (prResult, sToken, sHuman);
jvResult["proof_result"] = sToken;
jvResult["proof_result_code"] = prResult;
jvResult["proof_result_message"] = sHuman;
return jvResult;
}
// {
// account: <account>|<nickname>|<account_public_key>
// account_index: <number> // optional, defaults to 0.
// ledger_hash : <ledger>
// ledger_index : <ledger_index>
// }
Json::Value RPCHandler::doAccountLines (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
Ledger::pointer lpLedger;
Json::Value jvResult = lookupLedger (params, lpLedger);
if (!lpLedger)
return jvResult;
bool bUnlocked = false;
if (lpLedger->isImmutable ())
{
masterLockHolder.unlock ();
bUnlocked = true;
}
if (!params.isMember ("account"))
return rpcError (rpcINVALID_PARAMS);
std::string strIdent = params["account"].asString ();
bool bIndex = params.isMember ("account_index");
int iIndex = bIndex ? params["account_index"].asUInt () : 0;
RippleAddress raAccount;
jvResult = accountFromString (lpLedger, raAccount, bIndex, strIdent, iIndex, false);
if (!jvResult.empty ())
return jvResult;
std::string strPeer = params.isMember ("peer") ? params["peer"].asString () : "";
bool bPeerIndex = params.isMember ("peer_index");
int iPeerIndex = bIndex ? params["peer_index"].asUInt () : 0;
RippleAddress raPeer;
if (!strPeer.empty ())
{
jvResult["peer"] = raAccount.humanAccountID ();
if (bPeerIndex)
jvResult["peer_index"] = iPeerIndex;
jvResult = accountFromString (lpLedger, raPeer, bPeerIndex, strPeer, iPeerIndex, false);
if (!jvResult.empty ())
return jvResult;
}
if (lpLedger->hasAccount (raAccount))
{
AccountItems rippleLines (raAccount.getAccountID (), lpLedger, AccountItem::pointer (new RippleState ()));
if (!bUnlocked)
masterLockHolder.unlock ();
jvResult["account"] = raAccount.humanAccountID ();
Json::Value& jsonLines = (jvResult["lines"] = Json::arrayValue);
BOOST_FOREACH (AccountItem::ref item, rippleLines.getItems ())
{
RippleState* line = (RippleState*)item.get ();
if (!raPeer.isValid () || raPeer.getAccountID () == line->getAccountIDPeer ())
{
const STAmount& saBalance = line->getBalance ();
const STAmount& saLimit = line->getLimit ();
const STAmount& saLimitPeer = line->getLimitPeer ();
Json::Value& jPeer = jsonLines.append (Json::objectValue);
jPeer["account"] = RippleAddress::createHumanAccountID (line->getAccountIDPeer ());
// Amount reported is positive if current account holds other account's IOUs.
// Amount reported is negative if other account holds current account's IOUs.
jPeer["balance"] = saBalance.getText ();
jPeer["currency"] = saBalance.getHumanCurrency ();
jPeer["limit"] = saLimit.getText ();
jPeer["limit_peer"] = saLimitPeer.getText ();
jPeer["quality_in"] = static_cast<Json::UInt> (line->getQualityIn ());
jPeer["quality_out"] = static_cast<Json::UInt> (line->getQualityOut ());
if (line->getAuth())
jPeer["authorized"] = true;
if (line->getAuthPeer())
jPeer["peer_authorized"] = true;
if (line->getNoRipple())
jPeer["no_ripple"] = true;
if (line->getNoRipplePeer())
jPeer["no_ripple_peer"] = true;
}
}
loadType = Resource::feeMediumBurdenRPC;
}
else
{
jvResult = rpcError (rpcACT_NOT_FOUND);
}
return jvResult;
}
static void offerAdder (Json::Value& jvLines, SLE::ref offer)
{
if (offer->getType () == ltOFFER)
{
Json::Value& obj = jvLines.append (Json::objectValue);
offer->getFieldAmount (sfTakerPays).setJson (obj["taker_pays"]);
offer->getFieldAmount (sfTakerGets).setJson (obj["taker_gets"]);
obj["seq"] = offer->getFieldU32 (sfSequence);
obj["flags"] = offer->getFieldU32 (sfFlags);
}
}
// {
// account: <account>|<nickname>|<account_public_key>
// account_index: <number> // optional, defaults to 0.
// ledger_hash : <ledger>
// ledger_index : <ledger_index>
// }
Json::Value RPCHandler::doAccountOffers (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
Ledger::pointer lpLedger;
Json::Value jvResult = lookupLedger (params, lpLedger);
if (!lpLedger)
return jvResult;
bool bUnlocked = false;
if (lpLedger->isImmutable ())
{
masterLockHolder.unlock ();
bUnlocked = true;
}
if (!params.isMember ("account"))
return rpcError (rpcINVALID_PARAMS);
std::string strIdent = params["account"].asString ();
bool bIndex = params.isMember ("account_index");
int iIndex = bIndex ? params["account_index"].asUInt () : 0;
RippleAddress raAccount;
jvResult = accountFromString (lpLedger, raAccount, bIndex, strIdent, iIndex, false);
if (!jvResult.empty ())
return jvResult;
// Get info on account.
jvResult["account"] = raAccount.humanAccountID ();
if (bIndex)
jvResult["account_index"] = iIndex;
if (!lpLedger->hasAccount (raAccount))
return rpcError (rpcACT_NOT_FOUND);
Json::Value& jvsOffers = (jvResult["offers"] = Json::arrayValue);
lpLedger->visitAccountItems (raAccount.getAccountID (), BIND_TYPE (&offerAdder, boost::ref (jvsOffers), P_1));
if (!bUnlocked)
masterLockHolder.unlock ();
loadType = Resource::feeMediumBurdenRPC;
return jvResult;
}
// {
// "ledger_hash" : ledger, // Optional.
// "ledger_index" : ledger_index, // Optional.
// "taker_gets" : { "currency": currency, "issuer" : address },
// "taker_pays" : { "currency": currency, "issuer" : address },
// "taker" : address, // Optional.
// "marker" : element, // Optional.
// "limit" : integer, // Optional.
// "proof" : boolean // Defaults to false.
// }
Json::Value RPCHandler::doBookOffers (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
if (getApp().getJobQueue ().getJobCountGE (jtCLIENT) > 200)
{
return rpcError (rpcTOO_BUSY);
}
Ledger::pointer lpLedger;
Json::Value jvResult = lookupLedger (params, lpLedger);
if (!lpLedger)
return jvResult;
if (lpLedger->isImmutable ())
masterLockHolder.unlock ();
if (!params.isMember ("taker_pays") || !params.isMember ("taker_gets") || !params["taker_pays"].isObject () || !params["taker_gets"].isObject ())
return rpcError (rpcINVALID_PARAMS);
uint160 uTakerPaysCurrencyID;
uint160 uTakerPaysIssuerID;
const Json::Value& jvTakerPays = params["taker_pays"];
// Parse mandatory currency.
if (!jvTakerPays.isMember ("currency")
|| !STAmount::currencyFromString (uTakerPaysCurrencyID, jvTakerPays["currency"].asString ()))
{
WriteLog (lsINFO, RPCHandler) << "Bad taker_pays currency.";
return rpcError (rpcSRC_CUR_MALFORMED);
}
// Parse optional issuer.
else if (((jvTakerPays.isMember ("issuer"))
&& (!jvTakerPays["issuer"].isString ()
|| !STAmount::issuerFromString (uTakerPaysIssuerID, jvTakerPays["issuer"].asString ())))
// Don't allow illegal issuers.
|| (!uTakerPaysCurrencyID != !uTakerPaysIssuerID)
|| ACCOUNT_ONE == uTakerPaysIssuerID)
{
WriteLog (lsINFO, RPCHandler) << "Bad taker_pays issuer.";
return rpcError (rpcSRC_ISR_MALFORMED);
}
uint160 uTakerGetsCurrencyID;
uint160 uTakerGetsIssuerID;
const Json::Value& jvTakerGets = params["taker_gets"];
// Parse mandatory currency.
if (!jvTakerGets.isMember ("currency")
|| !STAmount::currencyFromString (uTakerGetsCurrencyID, jvTakerGets["currency"].asString ()))
{
WriteLog (lsINFO, RPCHandler) << "Bad taker_pays currency.";
return rpcError (rpcSRC_CUR_MALFORMED);
}
// Parse optional issuer.
else if (((jvTakerGets.isMember ("issuer"))
&& (!jvTakerGets["issuer"].isString ()
|| !STAmount::issuerFromString (uTakerGetsIssuerID, jvTakerGets["issuer"].asString ())))
// Don't allow illegal issuers.
|| (!uTakerGetsCurrencyID != !uTakerGetsIssuerID)
|| ACCOUNT_ONE == uTakerGetsIssuerID)
{
WriteLog (lsINFO, RPCHandler) << "Bad taker_gets issuer.";
return rpcError (rpcDST_ISR_MALFORMED);
}
if (uTakerPaysCurrencyID == uTakerGetsCurrencyID
&& uTakerPaysIssuerID == uTakerGetsIssuerID)
{
WriteLog (lsINFO, RPCHandler) << "taker_gets same as taker_pays.";
return rpcError (rpcBAD_MARKET);
}
RippleAddress raTakerID;
if (!params.isMember ("taker"))
{
raTakerID.setAccountID (ACCOUNT_ONE);
}
else if (!raTakerID.setAccountID (params["taker"].asString ()))
{
return rpcError (rpcBAD_ISSUER);
}
const bool bProof = params.isMember ("proof");
const unsigned int iLimit = params.isMember ("limit") ? params["limit"].asUInt () : 0;
const Json::Value jvMarker = params.isMember ("marker") ? params["marker"] : Json::Value (Json::nullValue);
mNetOps->getBookPage (lpLedger, uTakerPaysCurrencyID, uTakerPaysIssuerID, uTakerGetsCurrencyID, uTakerGetsIssuerID, raTakerID.getAccountID (), bProof, iLimit, jvMarker, jvResult);
loadType = Resource::feeMediumBurdenRPC;
return jvResult;
}
// Result:
// {
// random: <uint256>
// }
Json::Value RPCHandler::doRandom (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
masterLockHolder.unlock ();
uint256 uRandom;
try
{
RandomNumbers::getInstance ().fillBytes (uRandom.begin (), uRandom.size ());
Json::Value jvResult;
jvResult["random"] = uRandom.ToString ();
return jvResult;
}
catch (...)
{
return rpcError (rpcINTERNAL);
}
}
Json::Value RPCHandler::doPathFind (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
Ledger::pointer lpLedger = mNetOps->getClosedLedger();
masterLockHolder.unlock();
if (!params.isMember ("subcommand") || !params["subcommand"].isString ())
return rpcError (rpcINVALID_PARAMS);
if (!mInfoSub)
return rpcError (rpcNO_EVENTS);
std::string sSubCommand = params["subcommand"].asString ();
if (sSubCommand == "create")
{
loadType = Resource::feeHighBurdenRPC;
mInfoSub->clearPathRequest ();
return getApp().getPathRequests().makePathRequest (mInfoSub, lpLedger, params);
}
if (sSubCommand == "close")
{
PathRequest::pointer request = mInfoSub->getPathRequest ();
if (!request)
return rpcError (rpcNO_PF_REQUEST);
mInfoSub->clearPathRequest ();
return request->doClose (params);
}
if (sSubCommand == "status")
{
PathRequest::pointer request = mInfoSub->getPathRequest ();
if (!request)
return rpcNO_PF_REQUEST;
return request->doStatus (params);
}
return rpcError (rpcINVALID_PARAMS);
}
// This interface is deprecated.
Json::Value RPCHandler::doRipplePathFind (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
LegacyPathFind lpf (mRole == Config::ADMIN);
if (!lpf.isOkay ())
return rpcError (rpcTOO_BUSY);
loadType = Resource::feeHighBurdenRPC;
RippleAddress raSrc;
RippleAddress raDst;
STAmount saDstAmount;
Ledger::pointer lpLedger;
Json::Value jvResult;
if (getConfig().RUN_STANDALONE || params.isMember("ledger") || params.isMember("ledger_index") || params.isMember("ledger_hash"))
{ // The caller specified a ledger
jvResult = lookupLedger (params, lpLedger);
if (!lpLedger)
return jvResult;
}
if (!params.isMember ("source_account"))
{
jvResult = rpcError (rpcSRC_ACT_MISSING);
}
else if (!params["source_account"].isString ()
|| !raSrc.setAccountID (params["source_account"].asString ()))
{
jvResult = rpcError (rpcSRC_ACT_MALFORMED);
}
else if (!params.isMember ("destination_account"))
{
jvResult = rpcError (rpcDST_ACT_MISSING);
}
else if (!params["destination_account"].isString ()
|| !raDst.setAccountID (params["destination_account"].asString ()))
{
jvResult = rpcError (rpcDST_ACT_MALFORMED);
}
else if (
// Parse saDstAmount.
!params.isMember ("destination_amount")
|| !saDstAmount.bSetJson (params["destination_amount"])
|| !saDstAmount.isPositive()
|| (!!saDstAmount.getCurrency () && (!saDstAmount.getIssuer () || ACCOUNT_ONE == saDstAmount.getIssuer ())))
{
WriteLog (lsINFO, RPCHandler) << "Bad destination_amount.";
jvResult = rpcError (rpcINVALID_PARAMS);
}
else if (
// Checks on source_currencies.
params.isMember ("source_currencies")
&& (!params["source_currencies"].isArray ()
|| !params["source_currencies"].size ()) // Don't allow empty currencies.
)
{
WriteLog (lsINFO, RPCHandler) << "Bad source_currencies.";
jvResult = rpcError (rpcINVALID_PARAMS);
}
else
{
loadType = Resource::feeHighBurdenRPC;
RippleLineCache::pointer cache;
if (lpLedger)
{ // The caller specified a ledger
lpLedger = boost::make_shared<Ledger> (boost::ref (*lpLedger), false);
cache = boost::make_shared<RippleLineCache>(lpLedger);
}
else
{ // Use the default ledger and cache
lpLedger = mNetOps->getValidatedLedger();
cache = getApp().getPathRequests().getLineCache(lpLedger, false);
}
masterLockHolder.unlock (); // As long as we have a locked copy of the ledger, we can unlock.
Json::Value jvSrcCurrencies;
if (params.isMember ("source_currencies"))
{
jvSrcCurrencies = params["source_currencies"];
}
else
{
boost::unordered_set<uint160> usCurrencies = usAccountSourceCurrencies (raSrc, lpLedger, true);
jvSrcCurrencies = Json::Value (Json::arrayValue);
BOOST_FOREACH (const uint160 & uCurrency, usCurrencies)
{
Json::Value jvCurrency (Json::objectValue);
jvCurrency["currency"] = STAmount::createHumanCurrency (uCurrency);
jvSrcCurrencies.append (jvCurrency);
}
}
// Fill in currencies destination will accept
Json::Value jvDestCur (Json::arrayValue);
boost::unordered_set<uint160> usDestCurrID = usAccountDestCurrencies (raDst, lpLedger, true);
BOOST_FOREACH (const uint160 & uCurrency, usDestCurrID)
jvDestCur.append (STAmount::createHumanCurrency (uCurrency));
jvResult["destination_currencies"] = jvDestCur;
jvResult["destination_account"] = raDst.humanAccountID ();
Json::Value jvArray (Json::arrayValue);
for (unsigned int i = 0; i != jvSrcCurrencies.size (); ++i)
{
Json::Value jvSource = jvSrcCurrencies[i];
uint160 uSrcCurrencyID;
uint160 uSrcIssuerID;
if (!jvSource.isObject ())
return rpcError (rpcINVALID_PARAMS);
// Parse mandatory currency.
if (!jvSource.isMember ("currency")
|| !STAmount::currencyFromString (uSrcCurrencyID, jvSource["currency"].asString ()))
{
WriteLog (lsINFO, RPCHandler) << "Bad currency.";
return rpcError (rpcSRC_CUR_MALFORMED);
}
if (uSrcCurrencyID.isNonZero ())
uSrcIssuerID = raSrc.getAccountID ();
// Parse optional issuer.
if (jvSource.isMember ("issuer") &&
((!jvSource["issuer"].isString () ||
!STAmount::issuerFromString (uSrcIssuerID, jvSource["issuer"].asString ())) ||
(uSrcIssuerID.isZero () != uSrcCurrencyID.isZero ()) ||
(ACCOUNT_ONE == uSrcIssuerID)))
{
WriteLog (lsINFO, RPCHandler) << "Bad issuer.";
return rpcError (rpcSRC_ISR_MALFORMED);
}
STPathSet spsComputed;
bool bValid;
Pathfinder pf (cache, raSrc, raDst, uSrcCurrencyID, uSrcIssuerID, saDstAmount, bValid);
int level = getConfig().PATH_SEARCH_OLD;
if ((getConfig().PATH_SEARCH_MAX > level) && !getApp().getFeeTrack().isLoadedLocal())
++level;
STPath extraPath;
if (!bValid || !pf.findPaths (level, 4, spsComputed, extraPath))
{
WriteLog (lsWARNING, RPCHandler) << "ripple_path_find: No paths found.";
}
else
{
std::vector<PathState::pointer> vpsExpanded;
STAmount saMaxAmountAct;
STAmount saDstAmountAct;
STAmount saMaxAmount (
uSrcCurrencyID,
!!uSrcIssuerID
? uSrcIssuerID // Use specifed issuer.
: !!uSrcCurrencyID // Default to source account.
? raSrc.getAccountID ()
: ACCOUNT_XRP,
1);
saMaxAmount.negate ();
LedgerEntrySet lesSandbox (lpLedger, tapNONE);
TER terResult =
RippleCalc::rippleCalc (
lesSandbox,
saMaxAmountAct, // <--
saDstAmountAct, // <--
vpsExpanded, // <--
saMaxAmount, // --> Amount to send is unlimited to get an estimate.
saDstAmount, // --> Amount to deliver.
raDst.getAccountID (), // --> Account to deliver to.
raSrc.getAccountID (), // --> Account sending from.
spsComputed, // --> Path set.
false, // --> Don't allow partial payment. This is for normal fill or kill payments.
// Must achieve delivery goal.
false, // --> Don't limit quality. Average quality is wanted for normal payments.
false, // --> Allow direct ripple to be added to path set. to path set.
true); // --> Stand alone mode, no point in deleting unfundeds.
// WriteLog (lsDEBUG, RPCHandler) << "ripple_path_find: PATHS IN: " << spsComputed.size() << " : " << spsComputed.getJson(0);
// WriteLog (lsDEBUG, RPCHandler) << "ripple_path_find: PATHS EXP: " << vpsExpanded.size();
WriteLog (lsWARNING, RPCHandler)
<< boost::str (boost::format ("ripple_path_find: saMaxAmount=%s saDstAmount=%s saMaxAmountAct=%s saDstAmountAct=%s")
% saMaxAmount
% saDstAmount
% saMaxAmountAct
% saDstAmountAct);
if ((extraPath.size() > 0) && ((terResult == terNO_LINE) || (terResult == tecPATH_PARTIAL)))
{
WriteLog (lsDEBUG, PathRequest) << "Trying with an extra path element";
spsComputed.addPath(extraPath);
vpsExpanded.clear ();
lesSandbox.clear ();
terResult = RippleCalc::rippleCalc (lesSandbox, saMaxAmountAct, saDstAmountAct,
vpsExpanded, saMaxAmount, saDstAmount,
raDst.getAccountID (), raSrc.getAccountID (),
spsComputed, false, false, false, true);
WriteLog (lsDEBUG, PathRequest) << "Extra path element gives " << transHuman (terResult);
}
if (tesSUCCESS == terResult)
{
Json::Value jvEntry (Json::objectValue);
STPathSet spsCanonical;
// Reuse the expanded as it would need to be calcuated anyway to produce the canonical.
// (At least unless we make a direct canonical.)
// RippleCalc::setCanonical(spsCanonical, vpsExpanded, false);
jvEntry["source_amount"] = saMaxAmountAct.getJson (0);
// jvEntry["paths_expanded"] = vpsExpanded.getJson(0);
jvEntry["paths_canonical"] = Json::arrayValue; // spsCanonical.getJson(0);
jvEntry["paths_computed"] = spsComputed.getJson (0);
jvArray.append (jvEntry);
}
else
{
std::string strToken;
std::string strHuman;
transResultInfo (terResult, strToken, strHuman);
WriteLog (lsDEBUG, RPCHandler)
<< boost::str (boost::format ("ripple_path_find: %s %s %s")
% strToken
% strHuman
% spsComputed.getJson (0));
}
}
}
// Each alternative differs by source currency.
jvResult["alternatives"] = jvArray;
}
WriteLog (lsDEBUG, RPCHandler)
<< boost::str (boost::format ("ripple_path_find< %s")
% jvResult);
return jvResult;
}
// {
// tx_json: <object>,
// secret: <secret>
// }
Json::Value RPCHandler::doSign (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
loadType = Resource::feeHighBurdenRPC;
bool bFailHard = params.isMember ("fail_hard") && params["fail_hard"].asBool ();
return transactionSign (params, false, bFailHard, masterLockHolder);
}
// {
// tx_json: <object>,
// secret: <secret>
// }
Json::Value RPCHandler::doSubmit (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
loadType = Resource::feeMediumBurdenRPC;
if (!params.isMember ("tx_blob"))
{
bool bFailHard = params.isMember ("fail_hard") && params["fail_hard"].asBool ();
return transactionSign (params, true, bFailHard, masterLockHolder);
}
Json::Value jvResult;
Blob vucBlob (strUnHex (params["tx_blob"].asString ()));
if (!vucBlob.size ())
{
return rpcError (rpcINVALID_PARAMS);
}
Serializer sTrans (vucBlob);
SerializerIterator sitTrans (sTrans);
SerializedTransaction::pointer stpTrans;
try
{
stpTrans = boost::make_shared<SerializedTransaction> (boost::ref (sitTrans));
}
catch (std::exception& e)
{
jvResult["error"] = "invalidTransaction";
jvResult["error_exception"] = e.what ();
return jvResult;
}
Transaction::pointer tpTrans;
try
{
tpTrans = boost::make_shared<Transaction> (stpTrans, false);
}
catch (std::exception& e)
{
jvResult["error"] = "internalTransaction";
jvResult["error_exception"] = e.what ();
return jvResult;
}
try
{
(void) mNetOps->processTransaction (tpTrans, mRole == Config::ADMIN,
params.isMember ("fail_hard") && params["fail_hard"].asBool ());
}
catch (std::exception& e)
{
jvResult["error"] = "internalSubmit";
jvResult["error_exception"] = e.what ();
return jvResult;
}
masterLockHolder.unlock ();
try
{
jvResult["tx_json"] = tpTrans->getJson (0);
jvResult["tx_blob"] = strHex (tpTrans->getSTransaction ()->getSerializer ().peekData ());
if (temUNCERTAIN != tpTrans->getResult ())
{
std::string sToken;
std::string sHuman;
transResultInfo (tpTrans->getResult (), sToken, sHuman);
jvResult["engine_result"] = sToken;
jvResult["engine_result_code"] = tpTrans->getResult ();
jvResult["engine_result_message"] = sHuman;
}
return jvResult;
}
catch (std::exception& e)
{
jvResult["error"] = "internalJson";
jvResult["error_exception"] = e.what ();
return jvResult;
}
}
Json::Value RPCHandler::doConsensusInfo (Json::Value, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
Json::Value ret (Json::objectValue);
ret["info"] = mNetOps->getConsensusInfo ();
return ret;
}
Json::Value RPCHandler::doFetchInfo (Json::Value jvParams, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
masterLockHolder.unlock ();
Json::Value ret (Json::objectValue);
if (jvParams.isMember("clear") && jvParams["clear"].asBool())
{
mNetOps->clearLedgerFetch();
ret["clear"] = true;
}
ret["info"] = mNetOps->getLedgerFetchInfo();
return ret;
}
Json::Value RPCHandler::doServerInfo (Json::Value, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
Json::Value ret (Json::objectValue);
ret["info"] = mNetOps->getServerInfo (true, mRole == Config::ADMIN);
return ret;
}
Json::Value RPCHandler::doServerState (Json::Value, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
Json::Value ret (Json::objectValue);
ret["state"] = mNetOps->getServerInfo (false, mRole == Config::ADMIN);
return ret;
}
// {
// start: <index>
// }
Json::Value RPCHandler::doTxHistory (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
loadType = Resource::feeMediumBurdenRPC;
masterLockHolder.unlock ();
if (!params.isMember ("start"))
return rpcError (rpcINVALID_PARAMS);
unsigned int startIndex = params["start"].asUInt ();
if ((startIndex > 10000) && (mRole != Config::ADMIN))
return rpcError (rpcNO_PERMISSION);
Json::Value obj;
Json::Value txs;
obj["index"] = startIndex;
std::string sql =
boost::str (boost::format ("SELECT * FROM Transactions ORDER BY LedgerSeq desc LIMIT %u,20")
% startIndex);
{
Database* db = getApp().getTxnDB ()->getDB ();
DeprecatedScopedLock sl (getApp().getTxnDB ()->getDBLock ());
SQL_FOREACH (db, sql)
{
Transaction::pointer trans = Transaction::transactionFromSQL (db, false);
if (trans) txs.append (trans->getJson (0));
}
}
obj["txs"] = txs;
return obj;
}
// {
// transaction: <hex>
// }
Json::Value RPCHandler::doTx (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
masterLockHolder.unlock ();
if (!params.isMember ("transaction"))
return rpcError (rpcINVALID_PARAMS);
bool binary = params.isMember ("binary") && params["binary"].asBool ();
std::string strTransaction = params["transaction"].asString ();
if (Transaction::isHexTxID (strTransaction))
{
// transaction by ID
uint256 txid (strTransaction);
Transaction::pointer txn = getApp().getMasterTransaction ().fetch (txid, true);
if (!txn)
return rpcError (rpcTXN_NOT_FOUND);
#ifdef READY_FOR_NEW_TX_FORMAT
Json::Value ret;
ret["transaction"] = txn->getJson (0, binary);
#else
Json::Value ret = txn->getJson (0, binary);
#endif
if (txn->getLedger () != 0)
{
Ledger::pointer lgr = mNetOps->getLedgerBySeq (txn->getLedger ());
if (lgr)
{
bool okay = false;
if (binary)
{
std::string meta;
if (lgr->getMetaHex (txid, meta))
{
ret["meta"] = meta;
okay = true;
}
}
else
{
TransactionMetaSet::pointer set;
if (lgr->getTransactionMeta (txid, set))
{
okay = true;
ret["meta"] = set->getJson (0);
}
}
if (okay)
ret["validated"] = mNetOps->isValidated (lgr);
}
}
return ret;
}
return rpcError (rpcNOT_IMPL);
}
Json::Value RPCHandler::doLedgerClosed (Json::Value, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
Json::Value jvResult;
uint256 uLedger = mNetOps->getClosedLedgerHash ();
jvResult["ledger_index"] = mNetOps->getLedgerID (uLedger);
jvResult["ledger_hash"] = uLedger.ToString ();
//jvResult["ledger_time"] = uLedger.
return jvResult;
}
Json::Value RPCHandler::doLedgerCurrent (Json::Value, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
Json::Value jvResult;
jvResult["ledger_current_index"] = mNetOps->getCurrentLedgerID ();
return jvResult;
}
// ledger [id|index|current|closed] [full]
// {
// ledger: 'current' | 'closed' | <uint256> | <number>, // optional
// full: true | false // optional, defaults to false.
// }
Json::Value RPCHandler::doLedger (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
if (!params.isMember ("ledger") && !params.isMember ("ledger_hash") && !params.isMember ("ledger_index"))
{
Json::Value ret (Json::objectValue), current (Json::objectValue), closed (Json::objectValue);
getApp().getLedgerMaster ().getCurrentLedger ()->addJson (current, 0);
getApp().getLedgerMaster ().getClosedLedger ()->addJson (closed, 0);
ret["open"] = current;
ret["closed"] = closed;
return ret;
}
Ledger::pointer lpLedger;
Json::Value jvResult = lookupLedger (params, lpLedger);
if (!lpLedger)
return jvResult;
if (lpLedger->isImmutable ())
masterLockHolder.unlock ();
bool bFull = params.isMember ("full") && params["full"].asBool ();
bool bTransactions = params.isMember ("transactions") && params["transactions"].asBool ();
bool bAccounts = params.isMember ("accounts") && params["accounts"].asBool ();
bool bExpand = params.isMember ("expand") && params["expand"].asBool ();
int iOptions = (bFull ? LEDGER_JSON_FULL : 0)
| (bExpand ? LEDGER_JSON_EXPAND : 0)
| (bTransactions ? LEDGER_JSON_DUMP_TXRP : 0)
| (bAccounts ? LEDGER_JSON_DUMP_STATE : 0);
if (bFull || bAccounts | bExpand)
{
if (getApp().getFeeTrack().isLoadedLocal() && (mRole != Config::ADMIN))
{
WriteLog (lsDEBUG, Peer) << "Too busy to give full ledger";
return rpcError(rpcTOO_BUSY);
}
loadType = Resource::feeHighBurdenRPC;
}
Json::Value ret (Json::objectValue);
lpLedger->addJson (ret, iOptions);
return ret;
}
// Temporary switching code until the old account_tx is removed
Json::Value RPCHandler::doAccountTxSwitch (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
if (params.isMember("offset") || params.isMember("count") || params.isMember("descending") ||
params.isMember("ledger_max") || params.isMember("ledger_min"))
return doAccountTxOld(params, loadType, masterLockHolder);
return doAccountTx(params, loadType, masterLockHolder);
}
// {
// account: account,
// ledger_index_min: ledger_index,
// ledger_index_max: ledger_index,
// binary: boolean, // optional, defaults to false
// count: boolean, // optional, defaults to false
// descending: boolean, // optional, defaults to false
// offset: integer, // optional, defaults to 0
// limit: integer // optional
// }
Json::Value RPCHandler::doAccountTxOld (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
RippleAddress raAccount;
uint32 offset = params.isMember ("offset") ? params["offset"].asUInt () : 0;
int limit = params.isMember ("limit") ? params["limit"].asUInt () : -1;
bool bBinary = params.isMember ("binary") && params["binary"].asBool ();
bool bDescending = params.isMember ("descending") && params["descending"].asBool ();
bool bCount = params.isMember ("count") && params["count"].asBool ();
uint32 uLedgerMin;
uint32 uLedgerMax;
uint32 uValidatedMin;
uint32 uValidatedMax;
bool bValidated = mNetOps->getValidatedRange (uValidatedMin, uValidatedMax);
if (!params.isMember ("account"))
return rpcError (rpcINVALID_PARAMS);
if (!raAccount.setAccountID (params["account"].asString ()))
return rpcError (rpcACT_MALFORMED);
if (offset > 3000)
return rpcError (rpcATX_DEPRECATED);
loadType = Resource::feeHighBurdenRPC;
// DEPRECATED
if (params.isMember ("ledger_min"))
{
params["ledger_index_min"] = params["ledger_min"];
bDescending = true;
}
// DEPRECATED
if (params.isMember ("ledger_max"))
{
params["ledger_index_max"] = params["ledger_max"];
bDescending = true;
}
if (params.isMember ("ledger_index_min") || params.isMember ("ledger_index_max"))
{
int64 iLedgerMin = params.isMember ("ledger_index_min") ? params["ledger_index_min"].asInt () : -1;
int64 iLedgerMax = params.isMember ("ledger_index_max") ? params["ledger_index_max"].asInt () : -1;
if (!bValidated && (iLedgerMin == -1 || iLedgerMax == -1))
{
// Don't have a validated ledger range.
return rpcError (rpcLGR_IDXS_INVALID);
}
uLedgerMin = iLedgerMin == -1 ? uValidatedMin : iLedgerMin;
uLedgerMax = iLedgerMax == -1 ? uValidatedMax : iLedgerMax;
if (uLedgerMax < uLedgerMin)
{
return rpcError (rpcLGR_IDXS_INVALID);
}
}
else
{
Ledger::pointer l;
Json::Value ret = lookupLedger (params, l);
if (!l)
return ret;
uLedgerMin = uLedgerMax = l->getLedgerSeq ();
}
int count = 0;
#ifndef BEAST_DEBUG
try
{
#endif
masterLockHolder.unlock ();
Json::Value ret (Json::objectValue);
ret["account"] = raAccount.humanAccountID ();
Json::Value& jvTxns = (ret["transactions"] = Json::arrayValue);
if (bBinary)
{
std::vector<NetworkOPs::txnMetaLedgerType> txns =
mNetOps->getAccountTxsB (raAccount, uLedgerMin, uLedgerMax, bDescending, offset, limit, mRole == Config::ADMIN);
for (std::vector<NetworkOPs::txnMetaLedgerType>::const_iterator it = txns.begin (), end = txns.end ();
it != end; ++it)
{
++count;
Json::Value& jvObj = jvTxns.append (Json::objectValue);
uint32 uLedgerIndex = it->get<2> ();
jvObj["tx_blob"] = it->get<0> ();
jvObj["meta"] = it->get<1> ();
jvObj["ledger_index"] = uLedgerIndex;
jvObj["validated"] = bValidated && uValidatedMin <= uLedgerIndex && uValidatedMax >= uLedgerIndex;
}
}
else
{
std::vector< std::pair<Transaction::pointer, TransactionMetaSet::pointer> > txns = mNetOps->getAccountTxs (raAccount, uLedgerMin, uLedgerMax, bDescending, offset, limit, mRole == Config::ADMIN);
for (std::vector< std::pair<Transaction::pointer, TransactionMetaSet::pointer> >::iterator it = txns.begin (), end = txns.end (); it != end; ++it)
{
++count;
Json::Value& jvObj = jvTxns.append (Json::objectValue);
if (it->first)
jvObj["tx"] = it->first->getJson (1);
if (it->second)
{
uint32 uLedgerIndex = it->second->getLgrSeq ();
jvObj["meta"] = it->second->getJson (0);
jvObj["validated"] = bValidated && uValidatedMin <= uLedgerIndex && uValidatedMax >= uLedgerIndex;
}
}
}
//Add information about the original query
ret["ledger_index_min"] = uLedgerMin;
ret["ledger_index_max"] = uLedgerMax;
ret["validated"] = bValidated && uValidatedMin <= uLedgerMin && uValidatedMax >= uLedgerMax;
ret["offset"] = offset;
// We no longer return the full count but only the count of returned transactions
// Computing this count was two expensive and this API is deprecated anyway
if (bCount)
ret["count"] = count;
if (params.isMember ("limit"))
ret["limit"] = limit;
return ret;
#ifndef BEAST_DEBUG
}
catch (...)
{
return rpcError (rpcINTERNAL);
}
#endif
}
// {
// account: account,
// ledger_index_min: ledger_index // optional, defaults to earliest
// ledger_index_max: ledger_index, // optional, defaults to latest
// binary: boolean, // optional, defaults to false
// forward: boolean, // optional, defaults to false
// limit: integer, // optional
// marker: opaque // optional, resume previous query
// }
Json::Value RPCHandler::doAccountTx (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
RippleAddress raAccount;
int limit = params.isMember ("limit") ? params["limit"].asUInt () : -1;
bool bBinary = params.isMember ("binary") && params["binary"].asBool ();
bool bForward = params.isMember ("forward") && params["forward"].asBool ();
uint32 uLedgerMin;
uint32 uLedgerMax;
uint32 uValidatedMin;
uint32 uValidatedMax;
bool bValidated = mNetOps->getValidatedRange (uValidatedMin, uValidatedMax);
if (!bValidated)
{
// Don't have a validated ledger range.
return rpcError (rpcLGR_IDXS_INVALID);
}
if (!params.isMember ("account"))
return rpcError (rpcINVALID_PARAMS);
if (!raAccount.setAccountID (params["account"].asString ()))
return rpcError (rpcACT_MALFORMED);
loadType = Resource::feeMediumBurdenRPC;
if (params.isMember ("ledger_index_min") || params.isMember ("ledger_index_max"))
{
int64 iLedgerMin = params.isMember ("ledger_index_min") ? params["ledger_index_min"].asInt () : -1;
int64 iLedgerMax = params.isMember ("ledger_index_max") ? params["ledger_index_max"].asInt () : -1;
uLedgerMin = iLedgerMin == -1 ? uValidatedMin : iLedgerMin;
uLedgerMax = iLedgerMax == -1 ? uValidatedMax : iLedgerMax;
if (uLedgerMax < uLedgerMin)
{
return rpcError (rpcLGR_IDXS_INVALID);
}
}
else
{
Ledger::pointer l;
Json::Value ret = lookupLedger (params, l);
if (!l)
return ret;
uLedgerMin = uLedgerMax = l->getLedgerSeq ();
}
Json::Value resumeToken;
if (params.isMember("marker"))
{
resumeToken = params["marker"];
}
#ifndef BEAST_DEBUG
try
{
#endif
masterLockHolder.unlock ();
Json::Value ret (Json::objectValue);
ret["account"] = raAccount.humanAccountID ();
Json::Value& jvTxns = (ret["transactions"] = Json::arrayValue);
if (bBinary)
{
std::vector<NetworkOPs::txnMetaLedgerType> txns =
mNetOps->getTxsAccountB (raAccount, uLedgerMin, uLedgerMax, bForward, resumeToken, limit, mRole == Config::ADMIN);
for (std::vector<NetworkOPs::txnMetaLedgerType>::const_iterator it = txns.begin (), end = txns.end ();
it != end; ++it)
{
Json::Value& jvObj = jvTxns.append (Json::objectValue);
uint32 uLedgerIndex = it->get<2> ();
jvObj["tx_blob"] = it->get<0> ();
jvObj["meta"] = it->get<1> ();
jvObj["ledger_index"] = uLedgerIndex;
jvObj["validated"] = bValidated && uValidatedMin <= uLedgerIndex && uValidatedMax >= uLedgerIndex;
}
}
else
{
std::vector< std::pair<Transaction::pointer, TransactionMetaSet::pointer> > txns =
mNetOps->getTxsAccount (raAccount, uLedgerMin, uLedgerMax, bForward, resumeToken, limit, mRole == Config::ADMIN);
for (std::vector< std::pair<Transaction::pointer, TransactionMetaSet::pointer> >::iterator it = txns.begin (), end = txns.end (); it != end; ++it)
{
Json::Value& jvObj = jvTxns.append (Json::objectValue);
if (it->first)
jvObj["tx"] = it->first->getJson (1);
if (it->second)
{
uint32 uLedgerIndex = it->second->getLgrSeq ();
jvObj["meta"] = it->second->getJson (0);
jvObj["validated"] = bValidated && uValidatedMin <= uLedgerIndex && uValidatedMax >= uLedgerIndex;
}
}
}
//Add information about the original query
ret["ledger_index_min"] = uLedgerMin;
ret["ledger_index_max"] = uLedgerMax;
if (params.isMember ("limit"))
ret["limit"] = limit;
if (!resumeToken.isNull())
ret["marker"] = resumeToken;
return ret;
#ifndef BEAST_DEBUG
}
catch (...)
{
return rpcError (rpcINTERNAL);
}
#endif
}
// {
// secret: <string> // optional
// }
//
// This command requires Config::ADMIN access because it makes no sense to ask an untrusted server for this.
Json::Value RPCHandler::doValidationCreate (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
RippleAddress raSeed;
Json::Value obj (Json::objectValue);
if (!params.isMember ("secret"))
{
WriteLog (lsDEBUG, RPCHandler) << "Creating random validation seed.";
raSeed.setSeedRandom (); // Get a random seed.
}
else if (!raSeed.setSeedGeneric (params["secret"].asString ()))
{
return rpcError (rpcBAD_SEED);
}
obj["validation_public_key"] = RippleAddress::createNodePublic (raSeed).humanNodePublic ();
obj["validation_seed"] = raSeed.humanSeed ();
obj["validation_key"] = raSeed.humanSeed1751 ();
return obj;
}
// {
// secret: <string>
// }
Json::Value RPCHandler::doValidationSeed (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
Json::Value obj (Json::objectValue);
if (!params.isMember ("secret"))
{
Log::out() << "Unset validation seed.";
getConfig ().VALIDATION_SEED.clear ();
getConfig ().VALIDATION_PUB.clear ();
getConfig ().VALIDATION_PRIV.clear ();
}
else if (!getConfig ().VALIDATION_SEED.setSeedGeneric (params["secret"].asString ()))
{
getConfig ().VALIDATION_PUB.clear ();
getConfig ().VALIDATION_PRIV.clear ();
return rpcError (rpcBAD_SEED);
}
else
{
getConfig ().VALIDATION_PUB = RippleAddress::createNodePublic (getConfig ().VALIDATION_SEED);
getConfig ().VALIDATION_PRIV = RippleAddress::createNodePrivate (getConfig ().VALIDATION_SEED);
obj["validation_public_key"] = getConfig ().VALIDATION_PUB.humanNodePublic ();
obj["validation_seed"] = getConfig ().VALIDATION_SEED.humanSeed ();
obj["validation_key"] = getConfig ().VALIDATION_SEED.humanSeed1751 ();
}
return obj;
}
Json::Value RPCHandler::accounts (Ledger::ref lrLedger, const RippleAddress& naMasterGenerator)
{
Json::Value jsonAccounts (Json::arrayValue);
// YYY Don't want to leak to thin server that these accounts are related.
// YYY Would be best to alternate requests to servers and to cache results.
unsigned int uIndex = 0;
do
{
RippleAddress naAccount;
naAccount.setAccountPublic (naMasterGenerator, uIndex++);
AccountState::pointer as = mNetOps->getAccountState (lrLedger, naAccount);
if (as)
{
Json::Value jsonAccount (Json::objectValue);
as->addJson (jsonAccount);
jsonAccounts.append (jsonAccount);
}
else
{
uIndex = 0;
}
}
while (uIndex);
return jsonAccounts;
}
// {
// seed: <string>
// ledger_hash : <ledger>
// ledger_index : <ledger_index>
// }
Json::Value RPCHandler::doWalletAccounts (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
Ledger::pointer lpLedger;
Json::Value jvResult = lookupLedger (params, lpLedger);
if (!lpLedger)
return jvResult;
RippleAddress naSeed;
if (!params.isMember ("seed") || !naSeed.setSeedGeneric (params["seed"].asString ()))
{
return rpcError (rpcBAD_SEED);
}
// Try the seed as a master seed.
RippleAddress naMasterGenerator = RippleAddress::createGeneratorPublic (naSeed);
Json::Value jsonAccounts = accounts (lpLedger, naMasterGenerator);
if (jsonAccounts.empty ())
{
// No account via seed as master, try seed a regular.
Json::Value ret = getMasterGenerator (lpLedger, naSeed, naMasterGenerator);
if (!ret.empty ())
return ret;
ret["accounts"] = accounts (lpLedger, naMasterGenerator);
return ret;
}
else
{
// Had accounts via seed as master, return them.
Json::Value ret (Json::objectValue);
ret["accounts"] = jsonAccounts;
return ret;
}
}
Json::Value RPCHandler::doLogRotate (Json::Value, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
return LogSink::get()->rotateLog ();
}
// {
// passphrase: <string>
// }
Json::Value RPCHandler::doWalletPropose (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
masterLockHolder.unlock ();
RippleAddress naSeed;
RippleAddress naAccount;
if (params.isMember ("passphrase"))
{
naSeed = RippleAddress::createSeedGeneric (params["passphrase"].asString ());
}
else
{
naSeed.setSeedRandom ();
}
RippleAddress naGenerator = RippleAddress::createGeneratorPublic (naSeed);
naAccount.setAccountPublic (naGenerator, 0);
Json::Value obj (Json::objectValue);
obj["master_seed"] = naSeed.humanSeed ();
obj["master_seed_hex"] = naSeed.getSeed ().ToString ();
//obj["master_key"] = naSeed.humanSeed1751();
obj["account_id"] = naAccount.humanAccountID ();
return obj;
}
// {
// secret: <string>
// }
Json::Value RPCHandler::doWalletSeed (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
RippleAddress raSeed;
bool bSecret = params.isMember ("secret");
if (bSecret && !raSeed.setSeedGeneric (params["secret"].asString ()))
{
return rpcError (rpcBAD_SEED);
}
else
{
RippleAddress raAccount;
if (!bSecret)
{
raSeed.setSeedRandom ();
}
RippleAddress raGenerator = RippleAddress::createGeneratorPublic (raSeed);
raAccount.setAccountPublic (raGenerator, 0);
Json::Value obj (Json::objectValue);
obj["seed"] = raSeed.humanSeed ();
obj["key"] = raSeed.humanSeed1751 ();
return obj;
}
}
#if ENABLE_INSECURE
// TODO: for now this simply checks if this is the Config::ADMIN account
// TODO: need to prevent them hammering this over and over
// TODO: maybe a better way is only allow Config::ADMIN from local host
// {
// username: <string>,
// password: <string>
// }
Json::Value RPCHandler::doLogin (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
if (!params.isMember ("username")
|| !params.isMember ("password"))
return rpcError (rpcINVALID_PARAMS);
if (params["username"].asString () == getConfig ().RPC_USER && params["password"].asString () == getConfig ().RPC_PASSWORD)
{
//mRole=ADMIN;
return "logged in";
}
else
{
return "nope";
}
}
#endif
static void textTime (std::string& text, int& seconds, const char* unitName, int unitVal)
{
int i = seconds / unitVal;
if (i == 0)
return;
seconds -= unitVal * i;
if (!text.empty ())
text += ", ";
text += lexicalCastThrow <std::string> (i);
text += " ";
text += unitName;
if (i > 1)
text += "s";
}
Json::Value RPCHandler::doFeature (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& mlh)
{
if (!params.isMember ("feature"))
{
Json::Value jvReply = Json::objectValue;
jvReply["features"] = getApp().getFeatureTable ().getJson (0);
return jvReply;
}
uint256 uFeature = getApp().getFeatureTable ().getFeature (params["feature"].asString ());
if (uFeature.isZero ())
{
uFeature.SetHex (params["feature"].asString ());
if (uFeature.isZero ())
return rpcError (rpcBAD_FEATURE);
}
if (!params.isMember ("vote"))
return getApp().getFeatureTable ().getJson (uFeature);
// WRITEME
return rpcError (rpcNOT_SUPPORTED);
}
// {
// min_count: <number> // optional, defaults to 10
// }
Json::Value RPCHandler::doGetCounts (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
int minCount = 10;
if (params.isMember ("min_count"))
minCount = params["min_count"].asUInt ();
CountedObjects::List objectCounts = CountedObjects::getInstance ().getCounts (minCount);
Json::Value ret (Json::objectValue);
BOOST_FOREACH (CountedObjects::Entry& it, objectCounts)
{
ret [it.first] = it.second;
}
int dbKB = getApp().getLedgerDB ()->getDB ()->getKBUsedAll ();
if (dbKB > 0)
ret["dbKBTotal"] = dbKB;
dbKB = getApp().getLedgerDB ()->getDB ()->getKBUsedDB ();
if (dbKB > 0)
ret["dbKBLedger"] = dbKB;
dbKB = getApp().getTxnDB ()->getDB ()->getKBUsedDB ();
if (dbKB > 0)
ret["dbKBTransaction"] = dbKB;
ret["write_load"] = getApp().getNodeStore ().getWriteLoad ();
ret["SLE_hit_rate"] = getApp().getSLECache ().getHitRate ();
ret["node_hit_rate"] = getApp().getNodeStore ().getCacheHitRate ();
ret["ledger_hit_rate"] = getApp().getLedgerMaster ().getCacheHitRate ();
ret["AL_hit_rate"] = AcceptedLedger::getCacheHitRate ();
ret["fullbelow_size"] = SHAMap::getFullBelowSize ();
ret["treenode_size"] = SHAMap::getTreeNodeSize ();
std::string uptime;
int s = UptimeTimer::getInstance ().getElapsedSeconds ();
textTime (uptime, s, "year", 365 * 24 * 60 * 60);
textTime (uptime, s, "day", 24 * 60 * 60);
textTime (uptime, s, "hour", 60 * 60);
textTime (uptime, s, "minute", 60);
textTime (uptime, s, "second", 1);
ret["uptime"] = uptime;
return ret;
}
Json::Value RPCHandler::doLogLevel (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
// log_level
if (!params.isMember ("severity"))
{
// get log severities
Json::Value ret (Json::objectValue);
Json::Value lev (Json::objectValue);
lev["base"] = Log::severityToString (LogSink::get()->getMinSeverity ());
std::vector< std::pair<std::string, std::string> > logTable = LogPartition::getSeverities ();
typedef std::map<std::string, std::string>::value_type stringPair;
BOOST_FOREACH (const stringPair & it, logTable)
lev[it.first] = it.second;
ret["levels"] = lev;
return ret;
}
LogSeverity sv = Log::stringToSeverity (params["severity"].asString ());
if (sv == lsINVALID)
return rpcError (rpcINVALID_PARAMS);
// log_level severity
if (!params.isMember ("partition"))
{
// set base log severity
LogSink::get()->setMinSeverity (sv, true);
return Json::objectValue;
}
// log_level partition severity base?
if (params.isMember ("partition"))
{
// set partition severity
std::string partition (params["partition"].asString ());
if (boost::iequals (partition, "base"))
LogSink::get()->setMinSeverity (sv, false);
else if (!LogPartition::setSeverity (partition, sv))
return rpcError (rpcINVALID_PARAMS);
return Json::objectValue;
}
return rpcError (rpcINVALID_PARAMS);
}
// {
// node: <domain>|<node_public>,
// comment: <comment> // optional
// }
Json::Value RPCHandler::doUnlAdd (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
std::string strNode = params.isMember ("node") ? params["node"].asString () : "";
std::string strComment = params.isMember ("comment") ? params["comment"].asString () : "";
RippleAddress raNodePublic;
if (raNodePublic.setNodePublic (strNode))
{
getApp().getUNL ().nodeAddPublic (raNodePublic, UniqueNodeList::vsManual, strComment);
return "adding node by public key";
}
else
{
getApp().getUNL ().nodeAddDomain (strNode, UniqueNodeList::vsManual, strComment);
return "adding node by domain";
}
}
// {
// node: <domain>|<public_key>
// }
Json::Value RPCHandler::doUnlDelete (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
if (!params.isMember ("node"))
return rpcError (rpcINVALID_PARAMS);
std::string strNode = params["node"].asString ();
RippleAddress raNodePublic;
if (raNodePublic.setNodePublic (strNode))
{
getApp().getUNL ().nodeRemovePublic (raNodePublic);
return "removing node by public key";
}
else
{
getApp().getUNL ().nodeRemoveDomain (strNode);
return "removing node by domain";
}
}
Json::Value RPCHandler::doUnlList (Json::Value, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
Json::Value obj (Json::objectValue);
obj["unl"] = getApp().getUNL ().getUnlJson ();
return obj;
}
// Populate the UNL from a local validators.txt file.
Json::Value RPCHandler::doUnlLoad (Json::Value, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
if (getConfig ().VALIDATORS_FILE.empty () || !getApp().getUNL ().nodeLoad (getConfig ().VALIDATORS_FILE))
{
return rpcError (rpcLOAD_FAILED);
}
return "loading";
}
// Populate the UNL from ripple.com's validators.txt file.
Json::Value RPCHandler::doUnlNetwork (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
getApp().getUNL ().nodeNetwork ();
return "fetching";
}
// unl_reset
Json::Value RPCHandler::doUnlReset (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
getApp().getUNL ().nodeReset ();
return "removing nodes";
}
// unl_score
Json::Value RPCHandler::doUnlScore (Json::Value, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
getApp().getUNL ().nodeScore ();
return "scoring requested";
}
Json::Value RPCHandler::doSMS (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
if (!params.isMember ("text"))
return rpcError (rpcINVALID_PARAMS);
HTTPClient::sendSMS (getApp().getIOService (), params["text"].asString ());
return "sms dispatched";
}
Json::Value RPCHandler::doStop (Json::Value, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
getApp().signalStop ();
return SYSTEM_NAME " server stopping";
}
Json::Value RPCHandler::doLedgerAccept (Json::Value, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
Json::Value jvResult;
if (!getConfig ().RUN_STANDALONE)
{
jvResult["error"] = "notStandAlone";
}
else
{
mNetOps->acceptLedger ();
jvResult["ledger_current_index"] = mNetOps->getCurrentLedgerID ();
}
return jvResult;
}
Json::Value RPCHandler::doLedgerCleaner (Json::Value parameters, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
masterLockHolder.unlock();
getApp().getLedgerMaster().doLedgerCleaner (parameters);
return "Cleaner configured";
}
// {
// ledger_hash : <ledger>,
// ledger_index : <ledger_index>
// }
// XXX In this case, not specify either ledger does not mean ledger current. It means any ledger.
Json::Value RPCHandler::doTransactionEntry (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
Ledger::pointer lpLedger;
Json::Value jvResult = lookupLedger (params, lpLedger);
if (!lpLedger)
return jvResult;
if (lpLedger->isImmutable())
masterLockHolder.unlock();
if (!params.isMember ("tx_hash"))
{
jvResult["error"] = "fieldNotFoundTransaction";
}
else if (!params.isMember ("ledger_hash") && !params.isMember ("ledger_index"))
{
// We don't work on ledger current.
jvResult["error"] = "notYetImplemented"; // XXX We don't support any transaction yet.
}
else
{
uint256 uTransID;
// XXX Relying on trusted WSS client. Would be better to have a strict routine, returning success or failure.
uTransID.SetHex (params["tx_hash"].asString ());
if (!lpLedger)
{
jvResult["error"] = "ledgerNotFound";
}
else
{
Transaction::pointer tpTrans;
TransactionMetaSet::pointer tmTrans;
if (!lpLedger->getTransaction (uTransID, tpTrans, tmTrans))
{
jvResult["error"] = "transactionNotFound";
}
else
{
jvResult["tx_json"] = tpTrans->getJson (0);
if (tmTrans)
jvResult["metadata"] = tmTrans->getJson (0);
// 'accounts'
// 'engine_...'
// 'ledger_...'
}
}
}
return jvResult;
}
Json::Value RPCHandler::lookupLedger (Json::Value params, Ledger::pointer& lpLedger)
{
Json::Value jvResult;
uint256 uLedger = params.isMember ("ledger_hash") ? uint256 (params["ledger_hash"].asString ()) : 0;
int32 iLedgerIndex = params.isMember ("ledger_index") && params["ledger_index"].isNumeric () ? params["ledger_index"].asInt () : LEDGER_CURRENT;
std::string strLedger;
if (params.isMember ("ledger_index") && !params["ledger_index"].isNumeric ())
strLedger = params["ledger_index"].asString ();
// Support for DEPRECATED "ledger".
if (!params.isMember ("ledger"))
{
nothing ();
}
else if (params["ledger"].asString ().size () > 12)
{
uLedger = uint256 (params["ledger"].asString ());
}
else if (params["ledger"].isNumeric ())
{
iLedgerIndex = params["ledger"].asInt ();
}
else
{
strLedger = params["ledger"].asString ();
}
if (strLedger == "current")
{
iLedgerIndex = LEDGER_CURRENT;
}
else if (strLedger == "closed")
{
iLedgerIndex = LEDGER_CLOSED;
}
else if (strLedger == "validated")
{
iLedgerIndex = LEDGER_VALIDATED;
}
if (!!uLedger)
{
// Ledger directly specified.
lpLedger = mNetOps->getLedgerByHash (uLedger);
if (!lpLedger)
{
jvResult["error"] = "ledgerNotFound";
return jvResult;
}
iLedgerIndex = lpLedger->getLedgerSeq (); // Set the current index, override if needed.
}
switch (iLedgerIndex)
{
case LEDGER_CURRENT:
lpLedger = mNetOps->getCurrentLedger ();
iLedgerIndex = lpLedger->getLedgerSeq ();
assert (lpLedger->isImmutable () && !lpLedger->isClosed ());
break;
case LEDGER_CLOSED:
lpLedger = getApp().getLedgerMaster ().getClosedLedger ();
iLedgerIndex = lpLedger->getLedgerSeq ();
assert (lpLedger->isImmutable () && lpLedger->isClosed ());
break;
case LEDGER_VALIDATED:
lpLedger = mNetOps->getValidatedLedger ();
iLedgerIndex = lpLedger->getLedgerSeq ();
assert (lpLedger->isImmutable () && lpLedger->isClosed ());
break;
}
if (iLedgerIndex <= 0)
{
jvResult["error"] = "ledgerNotFound";
return jvResult;
}
if (!lpLedger)
{
lpLedger = mNetOps->getLedgerBySeq (iLedgerIndex);
if (!lpLedger)
{
jvResult["error"] = "ledgerNotFound"; // ledger_index from future?
return jvResult;
}
}
if (lpLedger->isClosed ())
{
if (!!uLedger)
jvResult["ledger_hash"] = uLedger.ToString ();
jvResult["ledger_index"] = iLedgerIndex;
}
else
{
jvResult["ledger_current_index"] = iLedgerIndex;
}
return jvResult;
}
// {
// ledger_hash : <ledger>
// ledger_index : <ledger_index>
// ...
// }
Json::Value RPCHandler::doLedgerEntry (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
Ledger::pointer lpLedger;
Json::Value jvResult = lookupLedger (params, lpLedger);
if (!lpLedger)
return jvResult;
if (lpLedger->isImmutable ())
masterLockHolder.unlock ();
uint256 uNodeIndex;
bool bNodeBinary = false;
if (params.isMember ("index"))
{
// XXX Needs to provide proof.
uNodeIndex.SetHex (params["index"].asString ());
bNodeBinary = true;
}
else if (params.isMember ("account_root"))
{
RippleAddress naAccount;
if (!naAccount.setAccountID (params["account_root"].asString ())
|| !naAccount.getAccountID ())
{
jvResult["error"] = "malformedAddress";
}
else
{
uNodeIndex = Ledger::getAccountRootIndex (naAccount.getAccountID ());
}
}
else if (params.isMember ("directory"))
{
if (!params["directory"].isObject ())
{
uNodeIndex.SetHex (params["directory"].asString ());
}
else if (params["directory"].isMember ("sub_index")
&& !params["directory"]["sub_index"].isIntegral ())
{
jvResult["error"] = "malformedRequest";
}
else
{
uint64 uSubIndex = params["directory"].isMember ("sub_index")
? params["directory"]["sub_index"].asUInt ()
: 0;
if (params["directory"].isMember ("dir_root"))
{
uint256 uDirRoot;
uDirRoot.SetHex (params["dir_root"].asString ());
uNodeIndex = Ledger::getDirNodeIndex (uDirRoot, uSubIndex);
}
else if (params["directory"].isMember ("owner"))
{
RippleAddress naOwnerID;
if (!naOwnerID.setAccountID (params["directory"]["owner"].asString ()))
{
jvResult["error"] = "malformedAddress";
}
else
{
uint256 uDirRoot = Ledger::getOwnerDirIndex (naOwnerID.getAccountID ());
uNodeIndex = Ledger::getDirNodeIndex (uDirRoot, uSubIndex);
}
}
else
{
jvResult["error"] = "malformedRequest";
}
}
}
else if (params.isMember ("generator"))
{
RippleAddress naGeneratorID;
if (!params["generator"].isObject ())
{
uNodeIndex.SetHex (params["generator"].asString ());
}
else if (!params["generator"].isMember ("regular_seed"))
{
jvResult["error"] = "malformedRequest";
}
else if (!naGeneratorID.setSeedGeneric (params["generator"]["regular_seed"].asString ()))
{
jvResult["error"] = "malformedAddress";
}
else
{
RippleAddress na0Public; // To find the generator's index.
RippleAddress naGenerator = RippleAddress::createGeneratorPublic (naGeneratorID);
na0Public.setAccountPublic (naGenerator, 0);
uNodeIndex = Ledger::getGeneratorIndex (na0Public.getAccountID ());
}
}
else if (params.isMember ("offer"))
{
RippleAddress naAccountID;
if (!params["offer"].isObject ())
{
uNodeIndex.SetHex (params["offer"].asString ());
}
else if (!params["offer"].isMember ("account")
|| !params["offer"].isMember ("seq")
|| !params["offer"]["seq"].isIntegral ())
{
jvResult["error"] = "malformedRequest";
}
else if (!naAccountID.setAccountID (params["offer"]["account"].asString ()))
{
jvResult["error"] = "malformedAddress";
}
else
{
uint32 uSequence = params["offer"]["seq"].asUInt ();
uNodeIndex = Ledger::getOfferIndex (naAccountID.getAccountID (), uSequence);
}
}
else if (params.isMember ("ripple_state"))
{
RippleAddress naA;
RippleAddress naB;
uint160 uCurrency;
Json::Value jvRippleState = params["ripple_state"];
if (!jvRippleState.isObject ()
|| !jvRippleState.isMember ("currency")
|| !jvRippleState.isMember ("accounts")
|| !jvRippleState["accounts"].isArray ()
|| 2 != jvRippleState["accounts"].size ()
|| !jvRippleState["accounts"][0u].isString ()
|| !jvRippleState["accounts"][1u].isString ()
|| jvRippleState["accounts"][0u].asString () == jvRippleState["accounts"][1u].asString ()
)
{
WriteLog (lsINFO, RPCHandler)
<< boost::str (boost::format ("ledger_entry: ripple_state: accounts: %d currency: %d array: %d size: %d equal: %d")
% jvRippleState.isMember ("accounts")
% jvRippleState.isMember ("currency")
% jvRippleState["accounts"].isArray ()
% jvRippleState["accounts"].size ()
% (jvRippleState["accounts"][0u].asString () == jvRippleState["accounts"][1u].asString ())
);
jvResult["error"] = "malformedRequest";
}
else if (!naA.setAccountID (jvRippleState["accounts"][0u].asString ())
|| !naB.setAccountID (jvRippleState["accounts"][1u].asString ()))
{
jvResult["error"] = "malformedAddress";
}
else if (!STAmount::currencyFromString (uCurrency, jvRippleState["currency"].asString ()))
{
jvResult["error"] = "malformedCurrency";
}
else
{
uNodeIndex = Ledger::getRippleStateIndex (naA, naB, uCurrency);
}
}
else
{
jvResult["error"] = "unknownOption";
}
if (uNodeIndex.isNonZero ())
{
SLE::pointer sleNode = mNetOps->getSLEi (lpLedger, uNodeIndex);
if (params.isMember("binary"))
bNodeBinary = params["binary"].asBool();
if (!sleNode)
{
// Not found.
// XXX Should also provide proof.
jvResult["error"] = "entryNotFound";
}
else if (bNodeBinary)
{
// XXX Should also provide proof.
Serializer s;
sleNode->add (s);
jvResult["node_binary"] = strHex (s.peekData ());
jvResult["index"] = uNodeIndex.ToString ();
}
else
{
jvResult["node"] = sleNode->getJson (0);
jvResult["index"] = uNodeIndex.ToString ();
}
}
return jvResult;
}
// {
// ledger_hash : <ledger>
// ledger_index : <ledger_index>
// }
Json::Value RPCHandler::doLedgerHeader (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
Ledger::pointer lpLedger;
Json::Value jvResult = lookupLedger (params, lpLedger);
if (!lpLedger)
return jvResult;
Serializer s;
lpLedger->addRaw (s);
jvResult["ledger_data"] = strHex (s.peekData ());
// This information isn't verified, they should only use it if they trust us.
lpLedger->addJson (jvResult, 0);
return jvResult;
}
boost::unordered_set<RippleAddress> RPCHandler::parseAccountIds (const Json::Value& jvArray)
{
boost::unordered_set<RippleAddress> usnaResult;
for (Json::Value::const_iterator it = jvArray.begin (); it != jvArray.end (); it++)
{
RippleAddress naString;
if (! (*it).isString () || !naString.setAccountID ((*it).asString ()))
{
usnaResult.clear ();
break;
}
else
{
(void) usnaResult.insert (naString);
}
}
return usnaResult;
}
Json::Value RPCHandler::doSubscribe (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
InfoSub::pointer ispSub;
Json::Value jvResult (Json::objectValue);
uint32 uLedgerIndex = params.isMember ("ledger_index") && params["ledger_index"].isNumeric ()
? params["ledger_index"].asUInt ()
: 0;
if (!mInfoSub && !params.isMember ("url"))
{
// Must be a JSON-RPC call.
WriteLog (lsINFO, RPCHandler) << boost::str (boost::format ("doSubscribe: RPC subscribe requires a url"));
return rpcError (rpcINVALID_PARAMS);
}
if (params.isMember ("url"))
{
if (mRole != Config::ADMIN)
return rpcError (rpcNO_PERMISSION);
std::string strUrl = params["url"].asString ();
std::string strUsername = params.isMember ("url_username") ? params["url_username"].asString () : "";
std::string strPassword = params.isMember ("url_password") ? params["url_password"].asString () : "";
// DEPRECATED
if (params.isMember ("username"))
strUsername = params["username"].asString ();
// DEPRECATED
if (params.isMember ("password"))
strPassword = params["password"].asString ();
ispSub = mNetOps->findRpcSub (strUrl);
if (!ispSub)
{
WriteLog (lsDEBUG, RPCHandler) << boost::str (boost::format ("doSubscribe: building: %s") % strUrl);
RPCSub::pointer rspSub = RPCSub::New (getApp ().getOPs (),
getApp ().getIOService (), getApp ().getJobQueue (),
strUrl, strUsername, strPassword);
ispSub = mNetOps->addRpcSub (strUrl, boost::dynamic_pointer_cast<InfoSub> (rspSub));
}
else
{
WriteLog (lsTRACE, RPCHandler) << boost::str (boost::format ("doSubscribe: reusing: %s") % strUrl);
if (params.isMember ("username"))
dynamic_cast<RPCSub*> (&*ispSub)->setUsername (strUsername);
if (params.isMember ("password"))
dynamic_cast<RPCSub*> (&*ispSub)->setPassword (strPassword);
}
}
else
{
ispSub = mInfoSub;
}
if (!params.isMember ("streams"))
{
nothing ();
}
else if (!params["streams"].isArray ())
{
WriteLog (lsINFO, RPCHandler) << boost::str (boost::format ("doSubscribe: streams requires an array."));
return rpcError (rpcINVALID_PARAMS);
}
else
{
for (Json::Value::iterator it = params["streams"].begin (); it != params["streams"].end (); it++)
{
if ((*it).isString ())
{
std::string streamName = (*it).asString ();
if (streamName == "server")
{
mNetOps->subServer (ispSub, jvResult);
}
else if (streamName == "ledger")
{
mNetOps->subLedger (ispSub, jvResult);
}
else if (streamName == "transactions")
{
mNetOps->subTransactions (ispSub);
}
else if (streamName == "transactions_proposed"
|| streamName == "rt_transactions") // DEPRECATED
{
mNetOps->subRTTransactions (ispSub);
}
else
{
jvResult["error"] = "unknownStream";
}
}
else
{
jvResult["error"] = "malformedStream";
}
}
}
std::string strAccountsProposed = params.isMember ("accounts_proposed")
? "accounts_proposed"
: "rt_accounts"; // DEPRECATED
if (!params.isMember (strAccountsProposed))
{
nothing ();
}
else if (!params[strAccountsProposed].isArray ())
{
return rpcError (rpcINVALID_PARAMS);
}
else
{
boost::unordered_set<RippleAddress> usnaAccoundIds = parseAccountIds (params[strAccountsProposed]);
if (usnaAccoundIds.empty ())
{
jvResult["error"] = "malformedAccount";
}
else
{
mNetOps->subAccount (ispSub, usnaAccoundIds, uLedgerIndex, true);
}
}
if (!params.isMember ("accounts"))
{
nothing ();
}
else if (!params["accounts"].isArray ())
{
return rpcError (rpcINVALID_PARAMS);
}
else
{
boost::unordered_set<RippleAddress> usnaAccoundIds = parseAccountIds (params["accounts"]);
if (usnaAccoundIds.empty ())
{
jvResult["error"] = "malformedAccount";
}
else
{
mNetOps->subAccount (ispSub, usnaAccoundIds, uLedgerIndex, false);
WriteLog (lsDEBUG, RPCHandler) << boost::str (boost::format ("doSubscribe: accounts: %d") % usnaAccoundIds.size ());
}
}
if (!params.isMember ("books"))
{
nothing ();
}
else if (!params["books"].isArray ())
{
return rpcError (rpcINVALID_PARAMS);
}
else
{
for (Json::Value::iterator it = params["books"].begin (); it != params["books"].end (); it++)
{
Json::Value& jvSubRequest = *it;
if (!jvSubRequest.isObject ()
|| !jvSubRequest.isMember ("taker_pays")
|| !jvSubRequest.isMember ("taker_gets")
|| !jvSubRequest["taker_pays"].isObject ()
|| !jvSubRequest["taker_gets"].isObject ())
return rpcError (rpcINVALID_PARAMS);
RippleCurrency uTakerPaysCurrencyID;
RippleIssuer uTakerPaysIssuerID;
RippleCurrency uTakerGetsCurrencyID;
RippleIssuer uTakerGetsIssuerID;
bool bBoth = (jvSubRequest.isMember ("both") && jvSubRequest["both"].asBool ())
|| (jvSubRequest.isMember ("both_sides") && jvSubRequest["both_sides"].asBool ()); // DEPRECATED
bool bSnapshot = (jvSubRequest.isMember ("snapshot") && jvSubRequest["snapshot"].asBool ())
|| (jvSubRequest.isMember ("state_now") && jvSubRequest["state_now"].asBool ()); // DEPRECATED
Json::Value jvTakerPays = jvSubRequest["taker_pays"];
Json::Value jvTakerGets = jvSubRequest["taker_gets"];
// Parse mandatory currency.
if (!jvTakerPays.isMember ("currency")
|| !STAmount::currencyFromString (uTakerPaysCurrencyID, jvTakerPays["currency"].asString ()))
{
WriteLog (lsINFO, RPCHandler) << "Bad taker_pays currency.";
return rpcError (rpcSRC_CUR_MALFORMED);
}
// Parse optional issuer.
else if (((jvTakerPays.isMember ("issuer"))
&& (!jvTakerPays["issuer"].isString ()
|| !STAmount::issuerFromString (uTakerPaysIssuerID, jvTakerPays["issuer"].asString ())))
// Don't allow illegal issuers.
|| (!uTakerPaysCurrencyID != !uTakerPaysIssuerID)
|| ACCOUNT_ONE == uTakerPaysIssuerID)
{
WriteLog (lsINFO, RPCHandler) << "Bad taker_pays issuer.";
return rpcError (rpcSRC_ISR_MALFORMED);
}
// Parse mandatory currency.
if (!jvTakerGets.isMember ("currency")
|| !STAmount::currencyFromString (uTakerGetsCurrencyID, jvTakerGets["currency"].asString ()))
{
WriteLog (lsINFO, RPCHandler) << "Bad taker_pays currency.";
return rpcError (rpcSRC_CUR_MALFORMED);
}
// Parse optional issuer.
else if (((jvTakerGets.isMember ("issuer"))
&& (!jvTakerGets["issuer"].isString ()
|| !STAmount::issuerFromString (uTakerGetsIssuerID, jvTakerGets["issuer"].asString ())))
// Don't allow illegal issuers.
|| (!uTakerGetsCurrencyID != !uTakerGetsIssuerID)
|| ACCOUNT_ONE == uTakerGetsIssuerID)
{
WriteLog (lsINFO, RPCHandler) << "Bad taker_gets issuer.";
return rpcError (rpcDST_ISR_MALFORMED);
}
if (uTakerPaysCurrencyID == uTakerGetsCurrencyID
&& uTakerPaysIssuerID == uTakerGetsIssuerID)
{
WriteLog (lsINFO, RPCHandler) << "taker_gets same as taker_pays.";
return rpcError (rpcBAD_MARKET);
}
RippleAddress raTakerID;
if (!jvSubRequest.isMember ("taker"))
{
raTakerID.setAccountID (ACCOUNT_ONE);
}
else if (!raTakerID.setAccountID (jvSubRequest["taker"].asString ()))
{
return rpcError (rpcBAD_ISSUER);
}
if (!Ledger::isValidBook (uTakerPaysCurrencyID, uTakerPaysIssuerID, uTakerGetsCurrencyID, uTakerGetsIssuerID))
{
WriteLog (lsWARNING, RPCHandler) << "Bad market: " <<
uTakerPaysCurrencyID << ":" << uTakerPaysIssuerID << " -> " <<
uTakerGetsCurrencyID << ":" << uTakerGetsIssuerID;
return rpcError (rpcBAD_MARKET);
}
mNetOps->subBook (ispSub, uTakerPaysCurrencyID, uTakerGetsCurrencyID, uTakerPaysIssuerID, uTakerGetsIssuerID);
if (bBoth) mNetOps->subBook (ispSub, uTakerGetsCurrencyID, uTakerPaysCurrencyID, uTakerGetsIssuerID, uTakerPaysIssuerID);
if (bSnapshot)
{
loadType = Resource::feeMediumBurdenRPC;
Ledger::pointer lpLedger = getApp().getLedgerMaster ().getPublishedLedger ();
if (lpLedger)
{
const Json::Value jvMarker = Json::Value (Json::nullValue);
if (bBoth)
{
Json::Value jvBids (Json::objectValue);
Json::Value jvAsks (Json::objectValue);
mNetOps->getBookPage (lpLedger, uTakerPaysCurrencyID, uTakerPaysIssuerID, uTakerGetsCurrencyID, uTakerGetsIssuerID, raTakerID.getAccountID (), false, 0, jvMarker, jvBids);
if (jvBids.isMember ("offers")) jvResult["bids"] = jvBids["offers"];
mNetOps->getBookPage (lpLedger, uTakerGetsCurrencyID, uTakerGetsIssuerID, uTakerPaysCurrencyID, uTakerPaysIssuerID, raTakerID.getAccountID (), false, 0, jvMarker, jvAsks);
if (jvAsks.isMember ("offers")) jvResult["asks"] = jvAsks["offers"];
}
else
{
mNetOps->getBookPage (lpLedger, uTakerPaysCurrencyID, uTakerPaysIssuerID, uTakerGetsCurrencyID, uTakerGetsIssuerID, raTakerID.getAccountID (), false, 0, jvMarker, jvResult);
}
}
}
}
}
return jvResult;
}
// FIXME: This leaks RPCSub objects for JSON-RPC. Shouldn't matter for anyone sane.
Json::Value RPCHandler::doUnsubscribe (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
InfoSub::pointer ispSub;
Json::Value jvResult (Json::objectValue);
if (!mInfoSub && !params.isMember ("url"))
{
// Must be a JSON-RPC call.
return rpcError (rpcINVALID_PARAMS);
}
if (params.isMember ("url"))
{
if (mRole != Config::ADMIN)
return rpcError (rpcNO_PERMISSION);
std::string strUrl = params["url"].asString ();
ispSub = mNetOps->findRpcSub (strUrl);
if (!ispSub)
return jvResult;
}
else
{
ispSub = mInfoSub;
}
if (params.isMember ("streams"))
{
for (Json::Value::iterator it = params["streams"].begin (); it != params["streams"].end (); it++)
{
if ((*it).isString ())
{
std::string streamName = (*it).asString ();
if (streamName == "server")
{
mNetOps->unsubServer (ispSub->getSeq ());
}
else if (streamName == "ledger")
{
mNetOps->unsubLedger (ispSub->getSeq ());
}
else if (streamName == "transactions")
{
mNetOps->unsubTransactions (ispSub->getSeq ());
}
else if (streamName == "transactions_proposed"
|| streamName == "rt_transactions") // DEPRECATED
{
mNetOps->unsubRTTransactions (ispSub->getSeq ());
}
else
{
jvResult["error"] = str (boost::format ("Unknown stream: %s") % streamName);
}
}
else
{
jvResult["error"] = "malformedSteam";
}
}
}
if (params.isMember ("accounts_proposed") || params.isMember ("rt_accounts"))
{
boost::unordered_set<RippleAddress> usnaAccoundIds = parseAccountIds (
params.isMember ("accounts_proposed")
? params["accounts_proposed"]
: params["rt_accounts"]); // DEPRECATED
if (usnaAccoundIds.empty ())
{
jvResult["error"] = "malformedAccount";
}
else
{
mNetOps->unsubAccount (ispSub->getSeq (), usnaAccoundIds, true);
}
}
if (params.isMember ("accounts"))
{
boost::unordered_set<RippleAddress> usnaAccoundIds = parseAccountIds (params["accounts"]);
if (usnaAccoundIds.empty ())
{
jvResult["error"] = "malformedAccount";
}
else
{
mNetOps->unsubAccount (ispSub->getSeq (), usnaAccoundIds, false);
}
}
if (!params.isMember ("books"))
{
nothing ();
}
else if (!params["books"].isArray ())
{
return rpcError (rpcINVALID_PARAMS);
}
else
{
for (Json::Value::iterator it = params["books"].begin (); it != params["books"].end (); it++)
{
Json::Value& jvSubRequest = *it;
if (!jvSubRequest.isObject ()
|| !jvSubRequest.isMember ("taker_pays")
|| !jvSubRequest.isMember ("taker_gets")
|| !jvSubRequest["taker_pays"].isObject ()
|| !jvSubRequest["taker_gets"].isObject ())
return rpcError (rpcINVALID_PARAMS);
uint160 uTakerPaysCurrencyID;
uint160 uTakerPaysIssuerID;
uint160 uTakerGetsCurrencyID;
uint160 uTakerGetsIssuerID;
bool bBoth = (jvSubRequest.isMember ("both") && jvSubRequest["both"].asBool ())
|| (jvSubRequest.isMember ("both_sides") && jvSubRequest["both_sides"].asBool ()); // DEPRECATED
Json::Value jvTakerPays = jvSubRequest["taker_pays"];
Json::Value jvTakerGets = jvSubRequest["taker_gets"];
// Parse mandatory currency.
if (!jvTakerPays.isMember ("currency")
|| !STAmount::currencyFromString (uTakerPaysCurrencyID, jvTakerPays["currency"].asString ()))
{
WriteLog (lsINFO, RPCHandler) << "Bad taker_pays currency.";
return rpcError (rpcSRC_CUR_MALFORMED);
}
// Parse optional issuer.
else if (((jvTakerPays.isMember ("issuer"))
&& (!jvTakerPays["issuer"].isString ()
|| !STAmount::issuerFromString (uTakerPaysIssuerID, jvTakerPays["issuer"].asString ())))
// Don't allow illegal issuers.
|| (!uTakerPaysCurrencyID != !uTakerPaysIssuerID)
|| ACCOUNT_ONE == uTakerPaysIssuerID)
{
WriteLog (lsINFO, RPCHandler) << "Bad taker_pays issuer.";
return rpcError (rpcSRC_ISR_MALFORMED);
}
// Parse mandatory currency.
if (!jvTakerGets.isMember ("currency")
|| !STAmount::currencyFromString (uTakerGetsCurrencyID, jvTakerGets["currency"].asString ()))
{
WriteLog (lsINFO, RPCHandler) << "Bad taker_pays currency.";
return rpcError (rpcSRC_CUR_MALFORMED);
}
// Parse optional issuer.
else if (((jvTakerGets.isMember ("issuer"))
&& (!jvTakerGets["issuer"].isString ()
|| !STAmount::issuerFromString (uTakerGetsIssuerID, jvTakerGets["issuer"].asString ())))
// Don't allow illegal issuers.
|| (!uTakerGetsCurrencyID != !uTakerGetsIssuerID)
|| ACCOUNT_ONE == uTakerGetsIssuerID)
{
WriteLog (lsINFO, RPCHandler) << "Bad taker_gets issuer.";
return rpcError (rpcDST_ISR_MALFORMED);
}
if (uTakerPaysCurrencyID == uTakerGetsCurrencyID
&& uTakerPaysIssuerID == uTakerGetsIssuerID)
{
WriteLog (lsINFO, RPCHandler) << "taker_gets same as taker_pays.";
return rpcError (rpcBAD_MARKET);
}
mNetOps->unsubBook (ispSub->getSeq (), uTakerPaysCurrencyID, uTakerGetsCurrencyID, uTakerPaysIssuerID, uTakerGetsIssuerID);
if (bBoth) mNetOps->unsubBook (ispSub->getSeq (), uTakerGetsCurrencyID, uTakerPaysCurrencyID, uTakerGetsIssuerID, uTakerPaysIssuerID);
}
}
return jvResult;
}
// Provide the JSON-RPC "result" value.
//
// JSON-RPC provides a method and an array of params. JSON-RPC is used as a transport for a command and a request object. The
// command is the method. The request object is supplied as the first element of the params.
Json::Value RPCHandler::doRpcCommand (const std::string& strMethod, Json::Value const& jvParams, int iRole, Resource::Charge& loadType)
{
WriteLog (lsTRACE, RPCHandler) << "doRpcCommand:" << strMethod << ":" << jvParams;
if (!jvParams.isArray () || jvParams.size () > 1)
return rpcError (rpcINVALID_PARAMS);
Json::Value params = jvParams.size () ? jvParams[0u] : Json::Value (Json::objectValue);
if (!params.isObject ())
return rpcError (rpcINVALID_PARAMS);
// Provide the JSON-RPC method as the field "command" in the request.
params["command"] = strMethod;
Json::Value jvResult;
#if RIPPLE_USE_RPC_SERVICE_MANAGER
std::pair <bool, Json::Value> result (getApp().
getRPCServiceManager().call (strMethod, params));
if (result.first)
jvResult = result.second;
else
#endif
jvResult = doCommand (params, iRole, loadType);
// Always report "status". On an error report the request as received.
if (jvResult.isMember ("error"))
{
jvResult["status"] = "error";
jvResult["request"] = params;
}
else
{
jvResult["status"] = "success";
}
return jvResult;
}
Json::Value RPCHandler::doInternal (Json::Value params, Resource::Charge& loadType, Application::ScopedLockType& masterLockHolder)
{
// Used for debug or special-purpose RPC commands
if (!params.isMember ("internal_command"))
return rpcError (rpcINVALID_PARAMS);
return RPCInternalHandler::runHandler (params["internal_command"].asString (), params["params"]);
}
Json::Value RPCHandler::doCommand (const Json::Value& params, int iRole, Resource::Charge& loadType)
{
if (iRole != Config::ADMIN)
{
// VFALCO NOTE Should we also add up the jtRPC jobs?
//
int jc = getApp().getJobQueue ().getJobCountGE (jtCLIENT);
if (jc > 500)
{
WriteLog (lsDEBUG, RPCHandler) << "Too busy for command: " << jc;
return rpcError (rpcTOO_BUSY);
}
}
if (!params.isMember ("command"))
return rpcError (rpcCOMMAND_MISSING);
std::string strCommand = params["command"].asString ();
WriteLog (lsTRACE, RPCHandler) << "COMMAND:" << strCommand;
WriteLog (lsTRACE, RPCHandler) << "REQUEST:" << params;
mRole = iRole;
static struct
{
const char* pCommand;
doFuncPtr dfpFunc;
bool bAdminRequired;
unsigned int iOptions;
} commandsA[] =
{
// Request-response methods
{ "account_info", &RPCHandler::doAccountInfo, false, optCurrent },
{ "account_currencies", &RPCHandler::doAccountCurrencies, false, optCurrent },
{ "account_lines", &RPCHandler::doAccountLines, false, optCurrent },
{ "account_offers", &RPCHandler::doAccountOffers, false, optCurrent },
{ "account_tx", &RPCHandler::doAccountTxSwitch, false, optNetwork },
{ "blacklist", &RPCHandler::doBlackList, true, optNone },
{ "book_offers", &RPCHandler::doBookOffers, false, optCurrent },
{ "connect", &RPCHandler::doConnect, true, optNone },
{ "consensus_info", &RPCHandler::doConsensusInfo, true, optNone },
{ "get_counts", &RPCHandler::doGetCounts, true, optNone },
{ "internal", &RPCHandler::doInternal, true, optNone },
{ "feature", &RPCHandler::doFeature, true, optNone },
{ "fetch_info", &RPCHandler::doFetchInfo, true, optNone },
{ "ledger", &RPCHandler::doLedger, false, optNetwork },
{ "ledger_accept", &RPCHandler::doLedgerAccept, true, optCurrent },
{ "ledger_cleaner", &RPCHandler::doLedgerCleaner, true, optNetwork },
{ "ledger_closed", &RPCHandler::doLedgerClosed, false, optClosed },
{ "ledger_current", &RPCHandler::doLedgerCurrent, false, optCurrent },
{ "ledger_entry", &RPCHandler::doLedgerEntry, false, optCurrent },
{ "ledger_header", &RPCHandler::doLedgerHeader, false, optCurrent },
{ "log_level", &RPCHandler::doLogLevel, true, optNone },
{ "logrotate", &RPCHandler::doLogRotate, true, optNone },
// { "nickname_info", &RPCHandler::doNicknameInfo, false, optCurrent },
{ "owner_info", &RPCHandler::doOwnerInfo, false, optCurrent },
{ "peers", &RPCHandler::doPeers, true, optNone },
{ "path_find", &RPCHandler::doPathFind, false, optCurrent },
{ "ping", &RPCHandler::doPing, false, optNone },
{ "print", &RPCHandler::doPrint, true, optNone },
// { "profile", &RPCHandler::doProfile, false, optCurrent },
{ "proof_create", &RPCHandler::doProofCreate, true, optNone },
{ "proof_solve", &RPCHandler::doProofSolve, true, optNone },
{ "proof_verify", &RPCHandler::doProofVerify, true, optNone },
{ "random", &RPCHandler::doRandom, false, optNone },
{ "ripple_path_find", &RPCHandler::doRipplePathFind, false, optCurrent },
{ "sign", &RPCHandler::doSign, false, optNone },
{ "submit", &RPCHandler::doSubmit, false, optCurrent },
{ "server_info", &RPCHandler::doServerInfo, false, optNone },
{ "server_state", &RPCHandler::doServerState, false, optNone },
{ "sms", &RPCHandler::doSMS, true, optNone },
{ "stop", &RPCHandler::doStop, true, optNone },
{ "transaction_entry", &RPCHandler::doTransactionEntry, false, optCurrent },
{ "tx", &RPCHandler::doTx, false, optNetwork },
{ "tx_history", &RPCHandler::doTxHistory, false, optNone },
{ "unl_add", &RPCHandler::doUnlAdd, true, optNone },
{ "unl_delete", &RPCHandler::doUnlDelete, true, optNone },
{ "unl_list", &RPCHandler::doUnlList, true, optNone },
{ "unl_load", &RPCHandler::doUnlLoad, true, optNone },
{ "unl_network", &RPCHandler::doUnlNetwork, true, optNone },
{ "unl_reset", &RPCHandler::doUnlReset, true, optNone },
{ "unl_score", &RPCHandler::doUnlScore, true, optNone },
{ "validation_create", &RPCHandler::doValidationCreate, true, optNone },
{ "validation_seed", &RPCHandler::doValidationSeed, true, optNone },
{ "wallet_accounts", &RPCHandler::doWalletAccounts, false, optCurrent },
{ "wallet_propose", &RPCHandler::doWalletPropose, true, optNone },
{ "wallet_seed", &RPCHandler::doWalletSeed, true, optNone },
#if ENABLE_INSECURE
// XXX Unnecessary commands which should be removed.
{ "login", &RPCHandler::doLogin, true, optNone },
{ "data_delete", &RPCHandler::doDataDelete, true, optNone },
{ "data_fetch", &RPCHandler::doDataFetch, true, optNone },
{ "data_store", &RPCHandler::doDataStore, true, optNone },
#endif
// Evented methods
{ "subscribe", &RPCHandler::doSubscribe, false, optNone },
{ "unsubscribe", &RPCHandler::doUnsubscribe, false, optNone },
};
int i = NUMBER (commandsA);
while (i-- && strCommand != commandsA[i].pCommand)
;
if (i < 0)
{
return rpcError (rpcUNKNOWN_COMMAND);
}
else if (commandsA[i].bAdminRequired && mRole != Config::ADMIN)
{
return rpcError (rpcNO_PERMISSION);
}
{
Application::ScopedLockType lock (getApp().getMasterLock (), __FILE__, __LINE__);
if ((commandsA[i].iOptions & optNetwork) && (mNetOps->getOperatingMode () < NetworkOPs::omSYNCING))
{
WriteLog (lsINFO, RPCHandler) << "Insufficient network mode for RPC: " << mNetOps->strOperatingMode ();
return rpcError (rpcNO_NETWORK);
}
if (!getConfig ().RUN_STANDALONE && (commandsA[i].iOptions & optCurrent) && (getApp().getLedgerMaster().getValidatedLedgerAge() > 120))
{
return rpcError (rpcNO_CURRENT);
}
else if ((commandsA[i].iOptions & optClosed) && !mNetOps->getClosedLedger ())
{
return rpcError (rpcNO_CLOSED);
}
else
{
try
{
LoadEvent::autoptr ev = getApp().getJobQueue().getLoadEventAP(
jtGENERIC, std::string("cmd:") + strCommand);
Json::Value jvRaw = (this->* (commandsA[i].dfpFunc)) (params, loadType, lock);
// Regularize result.
if (jvRaw.isObject ())
{
// Got an object.
return jvRaw;
}
else
{
// Probably got a string.
Json::Value jvResult (Json::objectValue);
jvResult["message"] = jvRaw;
return jvResult;
}
}
catch (std::exception& e)
{
WriteLog (lsINFO, RPCHandler) << "Caught throw: " << e.what ();
if (loadType == Resource::feeReferenceRPC)
loadType = Resource::feeExceptionRPC;
return rpcError (rpcINTERNAL);
}
}
}
}
RPCInternalHandler* RPCInternalHandler::sHeadHandler = NULL;
RPCInternalHandler::RPCInternalHandler (const std::string& name, handler_t Handler) : mName (name), mHandler (Handler)
{
mNextHandler = sHeadHandler;
sHeadHandler = this;
}
Json::Value RPCInternalHandler::runHandler (const std::string& name, const Json::Value& params)
{
RPCInternalHandler* h = sHeadHandler;
while (h != NULL)
{
if (name == h->mName)
{
WriteLog (lsWARNING, RPCHandler) << "Internal command " << name << ": " << params;
Json::Value ret = h->mHandler (params);
WriteLog (lsWARNING, RPCHandler) << "Internal command returns: " << ret;
return ret;
}
h = h->mNextHandler;
}
return rpcError (rpcBAD_SYNTAX);
}
// vim:ts=4