mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-25 05:25:55 +00:00
2301 lines
61 KiB
C++
2301 lines
61 KiB
C++
//
|
|
// carries out the RPC
|
|
//
|
|
|
|
#include "Log.h"
|
|
#include "NetworkOPs.h"
|
|
#include "RPCHandler.h"
|
|
#include "Application.h"
|
|
#include "RippleLines.h"
|
|
#include "Wallet.h"
|
|
#include "RippleAddress.h"
|
|
#include "RippleCalc.h"
|
|
#include "RPCErr.h"
|
|
#include "AccountState.h"
|
|
#include "NicknameState.h"
|
|
#include "InstanceCounter.h"
|
|
|
|
#include "Pathfinder.h"
|
|
#include <boost/foreach.hpp>
|
|
#include <openssl/md5.h>
|
|
|
|
SETUP_LOG();
|
|
|
|
RPCHandler::RPCHandler(NetworkOPs* netOps)
|
|
{
|
|
mNetOps=netOps;
|
|
mInfoSub=NULL;
|
|
}
|
|
|
|
RPCHandler::RPCHandler(NetworkOPs* netOps, InfoSub* infoSub)
|
|
{
|
|
mNetOps=netOps;
|
|
mInfoSub=infoSub;
|
|
}
|
|
|
|
int RPCHandler::getParamCount(Json::Value params)
|
|
{ // If non-array, only counts strings
|
|
if (params.isNull()) return 0;
|
|
if (params.isArray()) return params.size();
|
|
if (!params.isConvertibleTo(Json::stringValue))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
bool RPCHandler::extractString(std::string& param, const Json::Value& params, int index)
|
|
{
|
|
if (params.isNull()) return false;
|
|
|
|
if (index!=0)
|
|
{
|
|
if (!params.isArray() || !params.isValidIndex(index))
|
|
return false;
|
|
Json::Value p(params.get(index, Json::nullValue));
|
|
if (p.isNull() || !p.isConvertibleTo(Json::stringValue))
|
|
return false;
|
|
param = p.asString();
|
|
return true;
|
|
}
|
|
|
|
if (params.isArray())
|
|
{
|
|
if ( (!params.isValidIndex(0)) || (!params[0u].isConvertibleTo(Json::stringValue)) )
|
|
return false;
|
|
param = params[0u].asString();
|
|
return true;
|
|
}
|
|
|
|
if (!params.isConvertibleTo(Json::stringValue))
|
|
return false;
|
|
param = params.asString();
|
|
return true;
|
|
}
|
|
|
|
// Look up the master public generator for a regular seed so we may index source accounts ids.
|
|
// --> naRegularSeed
|
|
// <-- naMasterGenerator
|
|
Json::Value RPCHandler::getMasterGenerator(const uint256& uLedger, 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(uLedger, na0Public.getAccountID());
|
|
|
|
if (!sleGen)
|
|
{
|
|
// No account has been claimed or has had it password set for seed.
|
|
return rpcError(rpcNO_ACCOUNT);
|
|
}
|
|
|
|
std::vector<unsigned char> vucCipher = sleGen->getFieldVL(sfGenerator);
|
|
std::vector<unsigned char> 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(const uint256& uLedger,
|
|
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(uLedger, naSrcAccountID);
|
|
if (!asSrc)
|
|
{
|
|
return rpcError(rpcSRC_ACT_MISSING);
|
|
}
|
|
|
|
RippleAddress naMasterGenerator;
|
|
|
|
if (asSrc->bHaveAuthorizedKey())
|
|
{
|
|
Json::Value obj = getMasterGenerator(uLedger, 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 != theConfig.ACCOUNT_PROBE_MAX)
|
|
{
|
|
naMasterAccountPublic.setAccountPublic(naMasterGenerator, iIndex);
|
|
|
|
cLog(lsDEBUG) << "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->bHaveAuthorizedKey() && (asSrc->getAuthorizedKey().getAccountID() != naAccountPublic.getAccountID()))
|
|
{
|
|
// std::cerr << "iIndex: " << iIndex << std::endl;
|
|
// std::cerr << "sfAuthorizedKey: " << strHex(asSrc->getAuthorizedKey().getAccountID()) << std::endl;
|
|
// std::cerr << "naAccountPublic: " << strHex(naAccountPublic.getAccountID()) << std::endl;
|
|
|
|
return rpcError(rpcPASSWD_CHANGED);
|
|
}
|
|
|
|
saSrcBalance = asSrc->getBalance();
|
|
|
|
if (saSrcBalance < saFee)
|
|
{
|
|
cLog(lsINFO) << "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.
|
|
// <-- bIndex: true if iIndex > 0 and used the index.
|
|
Json::Value RPCHandler::accountFromString(const uint256& uLedger, RippleAddress& naAccount, bool& bIndex, const std::string& strIdent, const int iIndex)
|
|
{
|
|
RippleAddress naSeed;
|
|
|
|
if (naAccount.setAccountPublic(strIdent) || naAccount.setAccountID(strIdent))
|
|
{
|
|
// Got the account.
|
|
bIndex = false;
|
|
}
|
|
// 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(uLedger, naRegular0Public.getAccountID());
|
|
if (!sleGen)
|
|
{
|
|
// Didn't find a generator map, assume it is a master generator.
|
|
nothing();
|
|
}
|
|
else
|
|
{
|
|
// Found master public key.
|
|
std::vector<unsigned char> vucCipher = sleGen->getFieldVL(sfGenerator);
|
|
std::vector<unsigned char> 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::doAcceptLedger(Json::Value jvRequest)
|
|
{
|
|
if (!theConfig.RUN_STANDALONE)
|
|
return rpcError(rpcNOT_STANDALONE);
|
|
|
|
Json::Value jvResult(Json::objectValue);
|
|
|
|
jvResult["newLedger"] = theApp->getOPs().acceptLedger();
|
|
|
|
return jvResult;
|
|
}
|
|
|
|
// { 'ident' : _indent_, 'index' : _index_ // optional }
|
|
Json::Value RPCHandler::doAccountInfo(Json::Value jvRequest)
|
|
{
|
|
// cLog(lsDEBUG) << "doAccountInfo: " << jvRequest;
|
|
|
|
if (!jvRequest.isMember("ident"))
|
|
return rpcError(rpcINVALID_PARAMS);
|
|
|
|
std::string strIdent = jvRequest["ident"].asString();
|
|
bool bIndex;
|
|
int iIndex = jvRequest.isMember("index") ? jvRequest["index"].asUInt() : 0;
|
|
RippleAddress naAccount;
|
|
|
|
Json::Value jvResult;
|
|
|
|
// Get info on account.
|
|
|
|
uint256 uAccepted = mNetOps->getClosedLedgerHash();
|
|
Json::Value jAccepted = accountFromString(uAccepted, naAccount, bIndex, strIdent, iIndex);
|
|
|
|
if (jAccepted.empty())
|
|
{
|
|
AccountState::pointer asAccepted = mNetOps->getAccountState(uAccepted, naAccount);
|
|
|
|
if (asAccepted)
|
|
asAccepted->addJson(jAccepted);
|
|
}
|
|
|
|
jvResult["accepted"] = jAccepted;
|
|
|
|
Json::Value jCurrent = accountFromString(uint256(0), naAccount, bIndex, strIdent, iIndex);
|
|
|
|
if (jCurrent.empty())
|
|
{
|
|
AccountState::pointer asCurrent = mNetOps->getAccountState(uint256(0), naAccount);
|
|
|
|
if (asCurrent)
|
|
asCurrent->addJson(jCurrent);
|
|
}
|
|
|
|
jvResult["current"] = jCurrent;
|
|
|
|
#if 0
|
|
if (!jAccepted && !asCurrent)
|
|
{
|
|
jvResult["account"] = naAccount.humanAccountID();
|
|
jvResult["status"] = "NotFound";
|
|
if (bIndex)
|
|
jvResult["index"] = iIndex;
|
|
}
|
|
#endif
|
|
return jvResult;
|
|
}
|
|
|
|
// {
|
|
// ip: <string>,
|
|
// port: <number>
|
|
// }
|
|
// XXX Might allow domain for manual connections.
|
|
Json::Value RPCHandler::doConnect(Json::Value jvParams)
|
|
{
|
|
if (theConfig.RUN_STANDALONE)
|
|
return "cannot connect in standalone mode";
|
|
|
|
std::string strIp = jvParams["ip"].asString();
|
|
int iPort = jvParams.isMember("port") ? jvParams["port"].asInt() : -1;
|
|
|
|
// XXX Validate legal IP and port
|
|
theApp->getConnectionPool().connectTo(strIp, iPort);
|
|
|
|
return "connecting";
|
|
}
|
|
|
|
// data_delete <key>
|
|
Json::Value RPCHandler::doDataDelete(Json::Value params)
|
|
{
|
|
std::string strKey = params[0u].asString();
|
|
|
|
Json::Value ret = Json::Value(Json::objectValue);
|
|
|
|
if (theApp->getWallet().dataDelete(strKey))
|
|
{
|
|
ret["key"] = strKey;
|
|
}
|
|
else
|
|
{
|
|
ret = rpcError(rpcINTERNAL);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// data_fetch <key>
|
|
Json::Value RPCHandler::doDataFetch(Json::Value params)
|
|
{
|
|
std::string strKey = params[0u].asString();
|
|
std::string strValue;
|
|
|
|
Json::Value ret = Json::Value(Json::objectValue);
|
|
|
|
ret["key"] = strKey;
|
|
if (theApp->getWallet().dataFetch(strKey, strValue))
|
|
ret["value"] = strValue;
|
|
|
|
return ret;
|
|
}
|
|
|
|
// data_store <key> <value>
|
|
Json::Value RPCHandler::doDataStore(Json::Value params)
|
|
{
|
|
std::string strKey = params[0u].asString();
|
|
std::string strValue = params[1u].asString();
|
|
|
|
Json::Value ret = Json::Value(Json::objectValue);
|
|
|
|
if (theApp->getWallet().dataStore(strKey, strValue))
|
|
{
|
|
ret["key"] = strKey;
|
|
ret["value"] = strValue;
|
|
}
|
|
else
|
|
{
|
|
ret = rpcError(rpcINTERNAL);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
|
|
// owner_info <account>|<nickname>|<account_public_key>
|
|
// owner_info <seed>|<pass_phrase>|<key> [<index>]
|
|
Json::Value RPCHandler::doOwnerInfo(Json::Value params)
|
|
{
|
|
std::string strIdent = params[0u].asString();
|
|
bool bIndex;
|
|
int iIndex = 2 == params.size() ? lexical_cast_s<int>(params[1u].asString()) : 0;
|
|
RippleAddress naAccount;
|
|
|
|
Json::Value ret;
|
|
|
|
// Get info on account.
|
|
|
|
uint256 uAccepted = mNetOps->getClosedLedgerHash();
|
|
Json::Value jAccepted = accountFromString(uAccepted, naAccount, bIndex, strIdent, iIndex);
|
|
|
|
ret["accepted"] = jAccepted.empty() ? mNetOps->getOwnerInfo(uAccepted, naAccount) : jAccepted;
|
|
|
|
Json::Value jCurrent = accountFromString(uint256(0), naAccount, bIndex, strIdent, iIndex);
|
|
|
|
ret["current"] = jCurrent.empty() ? mNetOps->getOwnerInfo(uint256(0), naAccount) : jCurrent;
|
|
|
|
return ret;
|
|
}
|
|
|
|
Json::Value RPCHandler::doPeers(Json::Value)
|
|
{
|
|
Json::Value obj(Json::objectValue);
|
|
|
|
obj["peers"]=theApp->getConnectionPool().getPeersJson();
|
|
|
|
return obj;
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
/* 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 = lexical_cast_s<uint32>(params[6u].asString());
|
|
|
|
if (iArgs >= 8 && "false" != params[7u].asString())
|
|
bSubmit = true;
|
|
|
|
Log::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, theConfig.FEE_DEFAULT, asSrcA, naMasterGeneratorA);
|
|
|
|
if (!jvObjA.empty())
|
|
return jvObjA;
|
|
|
|
Transaction::pointer tpOfferA = Transaction::sharedOfferCreate(
|
|
naAccountPublicA, naAccountPrivateA,
|
|
naAccountA, // naSourceAccount,
|
|
asSrcA->getSeq(), // uSeq
|
|
theConfig.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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// ripple_lines_get <account>|<nickname>|<account_public_key> [<index>]
|
|
Json::Value RPCHandler::doRippleLinesGet(Json::Value params)
|
|
{
|
|
// uint256 uAccepted = mNetOps->getClosedLedgerHash();
|
|
|
|
std::string strIdent = params[0u].asString();
|
|
bool bIndex;
|
|
int iIndex = 2 == params.size() ? lexical_cast_s<int>(params[1u].asString()) : 0;
|
|
|
|
RippleAddress naAccount;
|
|
|
|
Json::Value ret;
|
|
|
|
ret = accountFromString(uint256(0), naAccount, bIndex, strIdent, iIndex);
|
|
|
|
if (!ret.empty())
|
|
return ret;
|
|
|
|
// Get info on account.
|
|
ret = Json::Value(Json::objectValue);
|
|
|
|
ret["account"] = naAccount.humanAccountID();
|
|
if (bIndex)
|
|
ret["index"] = iIndex;
|
|
|
|
AccountState::pointer as = mNetOps->getAccountState(uint256(0), naAccount);
|
|
if (as)
|
|
{
|
|
Json::Value jsonLines(Json::arrayValue);
|
|
|
|
ret["account"] = naAccount.humanAccountID();
|
|
|
|
// XXX This is wrong, we do access the current ledger and do need to worry about changes.
|
|
// We access a committed ledger and need not worry about changes.
|
|
|
|
RippleLines rippleLines(naAccount.getAccountID());
|
|
BOOST_FOREACH(RippleState::pointer line, rippleLines.getLines())
|
|
{
|
|
STAmount saBalance = line->getBalance();
|
|
STAmount saLimit = line->getLimit();
|
|
STAmount saLimitPeer = line->getLimitPeer();
|
|
|
|
Json::Value jPeer = Json::Value(Json::objectValue);
|
|
|
|
//jPeer["node"] = uNode.ToString();
|
|
|
|
jPeer["account"] = line->getAccountIDPeer().humanAccountID();
|
|
// 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());
|
|
|
|
jsonLines.append(jPeer);
|
|
}
|
|
ret["lines"] = jsonLines;
|
|
}
|
|
else
|
|
{
|
|
ret = rpcError(rpcACT_NOT_FOUND);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// TODO:
|
|
// - Add support for specifying non-endpoint issuer.
|
|
// - Return fully expanded path with proof.
|
|
// - Allows clients to verify path exists.
|
|
// - Return canonicalized path.
|
|
// - From a trusted server, allows clients to use path without manipulation.
|
|
Json::Value RPCHandler::doRipplePathFind(Json::Value jvRequest)
|
|
{
|
|
Json::Value jvResult(Json::objectValue);
|
|
RippleAddress raSrc;
|
|
RippleAddress raDst;
|
|
STAmount saDstAmount;
|
|
|
|
if (
|
|
// Parse raSrc.
|
|
!jvRequest.isMember("source_account")
|
|
|| !jvRequest["source_account"].isString()
|
|
|| !raSrc.setAccountID(jvRequest["source_account"].asString()))
|
|
{
|
|
cLog(lsINFO) << "Bad source_account.";
|
|
jvResult = rpcError(rpcINVALID_PARAMS);
|
|
}
|
|
else if (
|
|
// Parse raDst.
|
|
!jvRequest.isMember("destination_account")
|
|
|| !jvRequest["destination_account"].isString()
|
|
|| !raDst.setAccountID(jvRequest["destination_account"].asString()))
|
|
{
|
|
cLog(lsINFO) << "Bad destination_account.";
|
|
jvResult = rpcError(rpcINVALID_PARAMS);
|
|
}
|
|
else if (
|
|
// Parse saDstAmount.
|
|
!jvRequest.isMember("destination_amount")
|
|
|| !saDstAmount.bSetJson(jvRequest["destination_amount"])
|
|
|| (!!saDstAmount.getCurrency() && (!saDstAmount.getIssuer() || ACCOUNT_ONE == saDstAmount.getIssuer())))
|
|
{
|
|
cLog(lsINFO) << "Bad destination_amount.";
|
|
jvResult = rpcError(rpcINVALID_PARAMS);
|
|
}
|
|
else if (
|
|
// Checks on source_currencies.
|
|
!jvRequest.isMember("source_currencies")
|
|
|| !jvRequest["source_currencies"].isArray()
|
|
|| !jvRequest["source_currencies"].size()
|
|
)
|
|
{
|
|
cLog(lsINFO) << "Bad source_currencies.";
|
|
jvResult = rpcError(rpcINVALID_PARAMS);
|
|
}
|
|
else
|
|
{
|
|
Json::Value jvSrcCurrencies = jvRequest["source_currencies"];
|
|
Json::Value jvArray(Json::arrayValue);
|
|
|
|
Ledger::pointer lpCurrent = mNetOps->getCurrentLedger();
|
|
|
|
ScopedUnlock su(theApp->getMasterLock()); // As long as we have a locked copy of the ledger, we can unlock.
|
|
|
|
LedgerEntrySet lesSnapshot(lpCurrent);
|
|
|
|
for (unsigned int i=0; i != jvSrcCurrencies.size(); ++i) {
|
|
Json::Value jvSource = jvSrcCurrencies[i];
|
|
uint160 uSrcCurrencyID;
|
|
uint160 uSrcIssuerID = raSrc.getAccountID();
|
|
|
|
if (
|
|
// Parse currency.
|
|
!jvSource.isMember("currency")
|
|
|| !STAmount::currencyFromString(uSrcCurrencyID, jvSource["currency"].asString())
|
|
|
|
// Parse issuer.
|
|
|| ((jvSource.isMember("issuer"))
|
|
&& (!jvSource["issuer"].isString()
|
|
|| !STAmount::issuerFromString(uSrcIssuerID, jvSource["issuer"].asString())))
|
|
|
|
// Don't allow illegal issuers.
|
|
|| !uSrcIssuerID
|
|
|| ACCOUNT_ONE == uSrcIssuerID)
|
|
{
|
|
cLog(lsINFO) << "Bad currency/issuer.";
|
|
return rpcError(rpcINVALID_PARAMS);
|
|
}
|
|
|
|
STPathSet spsComputed;
|
|
std::vector<PathState::pointer> vpsExpanded;
|
|
|
|
Pathfinder pf(raSrc, raDst, uSrcCurrencyID, uSrcIssuerID, saDstAmount);
|
|
|
|
if (!pf.findPaths(5, 1, spsComputed))
|
|
{
|
|
cLog(lsDEBUG) << "ripple_path_find: No paths found.";
|
|
}
|
|
else
|
|
{
|
|
STAmount saMaxAmountAct;
|
|
STAmount saDstAmountAct;
|
|
STAmount saMaxAmount(
|
|
uSrcCurrencyID,
|
|
!!uSrcIssuerID
|
|
? uSrcIssuerID
|
|
: !!uSrcCurrencyID
|
|
? raSrc.getAccountID()
|
|
: ACCOUNT_XRP,
|
|
1);
|
|
saMaxAmount.negate();
|
|
|
|
cLog(lsDEBUG) << "ripple_path_find: PATHS: " << spsComputed.size();
|
|
|
|
TER terResult =
|
|
RippleCalc::rippleCalc(
|
|
lesSnapshot,
|
|
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 achive delivery goal.
|
|
false, // --> Don't limit quality. Average quality is wanted for normal payments.
|
|
false, // --> Allow direct ripple.
|
|
true); // --> Stand alone mode, no point in deleting unfundeds.
|
|
|
|
cLog(lsDEBUG)
|
|
<< boost::str(boost::format("ripple_path_find: saMaxAmount=%s saDstAmount=%s saMaxAmountAct=%s saDstAmountAct=%s")
|
|
% saMaxAmount
|
|
% saDstAmount
|
|
% saMaxAmountAct
|
|
% saDstAmountAct);
|
|
|
|
if (tesSUCCESS == terResult)
|
|
{
|
|
Json::Value jvEntry(Json::objectValue);
|
|
|
|
STPathSet spsCanonical;
|
|
|
|
RippleCalc::setCanonical(spsCanonical, vpsExpanded);
|
|
|
|
jvEntry["source_amount"] = saMaxAmountAct.getJson(0);
|
|
// jvEntry["paths_expanded"] = spsExpanded.getJson(0);
|
|
jvEntry["paths_canonical"] = spsCanonical.getJson(0);
|
|
|
|
jvArray.append(jvEntry);
|
|
}
|
|
else
|
|
{
|
|
std::string strToken;
|
|
std::string strHuman;
|
|
|
|
transResultInfo(terResult, strToken, strHuman);
|
|
|
|
cLog(lsDEBUG)
|
|
<< boost::str(boost::format("ripple_path_find: %s %s %s")
|
|
% strToken
|
|
% strHuman
|
|
% spsComputed.getJson(0));
|
|
}
|
|
}
|
|
}
|
|
|
|
jvResult["alternatives"] = jvArray;
|
|
}
|
|
|
|
cLog(lsDEBUG)
|
|
<< boost::str(boost::format("ripple_path_find< %s")
|
|
% jvResult);
|
|
|
|
return jvResult;
|
|
}
|
|
|
|
// {
|
|
// tx_json: <object>,
|
|
// secret: <secret>
|
|
// }
|
|
Json::Value RPCHandler::doSubmit(Json::Value jvRequest)
|
|
{
|
|
Json::Value jvResult;
|
|
RippleAddress naSeed;
|
|
RippleAddress raSrcAddressID;
|
|
|
|
if (!jvRequest.isMember("secret") || !jvRequest.isMember("tx_json"))
|
|
{
|
|
return rpcError(rpcINVALID_PARAMS);
|
|
}
|
|
|
|
Json::Value txJSON = jvRequest["tx_json"];
|
|
|
|
if (!naSeed.setSeedGeneric(jvRequest["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);
|
|
}
|
|
|
|
AccountState::pointer asSrc = mNetOps->getAccountState(uint256(0), raSrcAddressID);
|
|
if (!asSrc) return rpcError(rpcSRC_ACT_MALFORMED);
|
|
|
|
if (!txJSON.isMember("Fee")
|
|
&& ("OfferCreate" == txJSON["TransactionType"].asString()
|
|
|| "OfferCancel" == txJSON["TransactionType"].asString()
|
|
|| "TrustSet" == txJSON["TransactionType"].asString()))
|
|
{
|
|
txJSON["Fee"] = (int) theConfig.FEE_DEFAULT;
|
|
}
|
|
|
|
if ("Payment" == txJSON["TransactionType"].asString())
|
|
{
|
|
|
|
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("Fee"))
|
|
{
|
|
if (mNetOps->getAccountState(uint256(0), dstAccountID))
|
|
txJSON["Fee"] = (int) theConfig.FEE_DEFAULT;
|
|
else
|
|
txJSON["Fee"] = (int) theConfig.FEE_ACCOUNT_CREATE;
|
|
}
|
|
|
|
if (!txJSON.isMember("Paths") && txJSON.isMember("Amount") && jvRequest.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());
|
|
}
|
|
|
|
Pathfinder pf(raSrcAddressID, dstAccountID, saSendMax.getCurrency(), saSendMax.getIssuer(), saSend);
|
|
|
|
if (!pf.findPaths(5, 1, spsPaths))
|
|
{
|
|
cLog(lsDEBUG) << "payment: build_path: No paths found.";
|
|
|
|
return rpcError(rpcNO_PATH);
|
|
}
|
|
else
|
|
{
|
|
cLog(lsDEBUG) << "payment: build_path: " << spsPaths.getJson(0);
|
|
}
|
|
|
|
if (!spsPaths.isEmpty())
|
|
{
|
|
txJSON["Paths"]=spsPaths.getJson(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!txJSON.isMember("Sequence")) txJSON["Sequence"]=asSrc->getSeq();
|
|
if (!txJSON.isMember("Flags")) txJSON["Flags"]=0;
|
|
|
|
Ledger::pointer lpCurrent = mNetOps->getCurrentLedger();
|
|
SLE::pointer sleAccountRoot = mNetOps->getSLE(lpCurrent, Ledger::getAccountRootIndex(raSrcAddressID.getAccountID()));
|
|
|
|
if (!sleAccountRoot)
|
|
{
|
|
// XXX Ignore transactions for accounts not created.
|
|
return rpcError(rpcSRC_ACT_MISSING);
|
|
}
|
|
|
|
bool bHaveAuthKey = false;
|
|
RippleAddress naAuthorizedPublic;
|
|
|
|
|
|
RippleAddress naSecret = RippleAddress::createSeedGeneric(jvRequest["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 != theConfig.ACCOUNT_PROBE_MAX)
|
|
{
|
|
naMasterAccountPublic.setAccountPublic(naMasterGenerator, iIndex);
|
|
|
|
Log(lsWARNING) << "authorize: " << iIndex << " : " << naMasterAccountPublic.humanAccountID() << " : " << raSrcAddressID.humanAccountID();
|
|
|
|
bFound = raSrcAddressID.getAccountID() == naMasterAccountPublic.getAccountID();
|
|
if (!bFound)
|
|
++iIndex;
|
|
}
|
|
|
|
if (!bFound)
|
|
{
|
|
return rpcError(rpcSRC_ACT_MISSING);
|
|
}
|
|
|
|
// 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())
|
|
{
|
|
// std::cerr << "iIndex: " << iIndex << std::endl;
|
|
// std::cerr << "sfAuthorizedKey: " << strHex(asSrc->getAuthorizedKey().getAccountID()) << std::endl;
|
|
// std::cerr << "naAccountPublic: " << strHex(naAccountPublic.getAccountID()) << std::endl;
|
|
|
|
return rpcError(rpcSRC_ACT_MISSING);
|
|
}
|
|
|
|
std::auto_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;
|
|
}
|
|
|
|
// FIXME: 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
|
|
{
|
|
tpTrans = mNetOps->submitTransactionSync(tpTrans); // FIXME: Should use asynch interface
|
|
|
|
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);
|
|
|
|
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::doServerInfo(Json::Value)
|
|
{
|
|
Json::Value ret(Json::objectValue);
|
|
|
|
ret["info"] = theApp->getOPs().getServerInfo();
|
|
|
|
return ret;
|
|
}
|
|
|
|
Json::Value RPCHandler::doTxHistory(Json::Value params)
|
|
{
|
|
if (params.size() == 1)
|
|
{
|
|
unsigned int startIndex = params[0u].asInt();
|
|
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 = theApp->getTxnDB()->getDB();
|
|
ScopedLock sl (theApp->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;
|
|
}
|
|
|
|
return rpcError(rpcSRC_ACT_MALFORMED);
|
|
}
|
|
|
|
Json::Value RPCHandler::doTx(Json::Value params)
|
|
{
|
|
// tx <txID>
|
|
// tx <account>
|
|
|
|
std::string param1, param2;
|
|
if (!extractString(param1, params, 0))
|
|
{
|
|
return rpcError(rpcINVALID_PARAMS);
|
|
}
|
|
|
|
if (Transaction::isHexTxID(param1))
|
|
{ // transaction by ID
|
|
Json::Value ret;
|
|
uint256 txid(param1);
|
|
|
|
Transaction::pointer txn = theApp->getMasterTransaction().fetch(txid, true);
|
|
|
|
if (!txn) return rpcError(rpcTXN_NOT_FOUND);
|
|
|
|
return txn->getJson(0);
|
|
}
|
|
|
|
return rpcError(rpcNOT_IMPL);
|
|
}
|
|
|
|
Json::Value RPCHandler::doLedgerClosed(Json::Value)
|
|
{
|
|
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)
|
|
{
|
|
Json::Value jvResult;
|
|
jvResult["ledger_current_index"] = mNetOps->getCurrentLedgerID();
|
|
return jvResult;
|
|
}
|
|
|
|
// ledger [id|ledger_current|lastclosed] [full]
|
|
// {
|
|
// ledger: 'ledger_current' | 'ledger_closed' | <hex> | <number>, // optional
|
|
// full: true | false // optional, defaults to false.
|
|
// }
|
|
Json::Value RPCHandler::doLedger(Json::Value jvParams)
|
|
{
|
|
if (!jvParams.isMember("ledger"))
|
|
{
|
|
Json::Value ret(Json::objectValue), current(Json::objectValue), closed(Json::objectValue);
|
|
|
|
theApp->getLedgerMaster().getCurrentLedger()->addJson(current, 0);
|
|
theApp->getLedgerMaster().getClosedLedger()->addJson(closed, 0);
|
|
|
|
ret["open"] = current;
|
|
ret["closed"] = closed;
|
|
|
|
return ret;
|
|
}
|
|
|
|
std::string strLedger = jvParams["ledger"].asString();
|
|
Ledger::pointer ledger;
|
|
|
|
if (strLedger == "ledger_current")
|
|
ledger = theApp->getLedgerMaster().getCurrentLedger();
|
|
else if (strLedger == "ledger_closed")
|
|
ledger = theApp->getLedgerMaster().getClosedLedger();
|
|
else if (strLedger.size() > 12)
|
|
ledger = theApp->getLedgerMaster().getLedgerByHash(uint256(strLedger));
|
|
else
|
|
ledger = theApp->getLedgerMaster().getLedgerBySeq(jvParams["ledger"].asUInt());
|
|
|
|
if (!ledger)
|
|
return rpcError(rpcLGR_NOT_FOUND);
|
|
|
|
bool full = jvParams.isMember("full") && jvParams["full"].asBool();
|
|
|
|
Json::Value ret(Json::objectValue);
|
|
|
|
ledger->addJson(ret, full ? LEDGER_JSON_FULL : 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
// { account: <account>, ledger: <integer> }
|
|
// { account: <account>, ledger_min: <integer>, ledger_max: <integer> }
|
|
Json::Value RPCHandler::doAccountTransactions(Json::Value jvRequest)
|
|
{
|
|
RippleAddress raAccount;
|
|
uint32 minLedger;
|
|
uint32 maxLedger;
|
|
|
|
if (!jvRequest.isMember("account"))
|
|
return rpcError(rpcINVALID_PARAMS);
|
|
|
|
if (!raAccount.setAccountID(jvRequest["account"].asString()))
|
|
return rpcError(rpcACT_MALFORMED);
|
|
|
|
if (jvRequest.isMember("ledger"))
|
|
{
|
|
minLedger = maxLedger = jvRequest["ledger"].asUInt();
|
|
}
|
|
else if (jvRequest.isMember("ledger_min") && jvRequest.isMember("ledger_max"))
|
|
{
|
|
minLedger = jvRequest["ledger_min"].asUInt();
|
|
maxLedger = jvRequest["ledger_max"].asUInt();
|
|
}
|
|
else
|
|
{
|
|
return rpcError(rpcLGR_IDX_MALFORMED);
|
|
}
|
|
|
|
if ((maxLedger < minLedger) || (maxLedger == 0))
|
|
{
|
|
return rpcError(rpcLGR_IDXS_INVALID);
|
|
}
|
|
|
|
#ifndef DEBUG
|
|
try
|
|
{
|
|
#endif
|
|
std::vector< std::pair<Transaction::pointer, TransactionMetaSet::pointer> > txns = mNetOps->getAccountTxs(raAccount, minLedger, maxLedger);
|
|
Json::Value ret(Json::objectValue);
|
|
ret["account"] = raAccount.humanAccountID();
|
|
Json::Value ledgers(Json::arrayValue);
|
|
|
|
// uint32 currentLedger = 0;
|
|
for (std::vector< std::pair<Transaction::pointer, TransactionMetaSet::pointer> >::iterator it = txns.begin(), end = txns.end(); it != end; ++it)
|
|
{
|
|
Json::Value obj(Json::objectValue);
|
|
if(it->first) obj["tx"]=it->first->getJson(1);
|
|
if(it->second) obj["meta"]=it->second->getJson(0);
|
|
ret["transactions"].append(obj);
|
|
}
|
|
return ret;
|
|
#ifndef DEBUG
|
|
}
|
|
catch (...)
|
|
{
|
|
return rpcError(rpcINTERNAL);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// validation_create [<pass_phrase>|<seed>|<seed_key>]
|
|
//
|
|
// NOTE: It is poor security to specify secret information on the command line. This information might be saved in the command
|
|
// shell history file (e.g. .bash_history) and it may be leaked via the process status command (i.e. ps).
|
|
Json::Value RPCHandler::doValidationCreate(Json::Value params) {
|
|
RippleAddress naSeed;
|
|
Json::Value obj(Json::objectValue);
|
|
|
|
if (params.empty())
|
|
{
|
|
std::cerr << "Creating random validation seed." << std::endl;
|
|
|
|
naSeed.setSeedRandom(); // Get a random seed.
|
|
}
|
|
else if (!naSeed.setSeedGeneric(params[0u].asString()))
|
|
{
|
|
return rpcError(rpcBAD_SEED);
|
|
}
|
|
|
|
obj["validation_public_key"] = RippleAddress::createNodePublic(naSeed).humanNodePublic();
|
|
obj["validation_seed"] = naSeed.humanSeed();
|
|
obj["validation_key"] = naSeed.humanSeed1751();
|
|
|
|
return obj;
|
|
}
|
|
|
|
// validation_seed [<pass_phrase>|<seed>|<seed_key>]
|
|
//
|
|
// NOTE: It is poor security to specify secret information on the command line. This information might be saved in the command
|
|
// shell history file (e.g. .bash_history) and it may be leaked via the process status command (i.e. ps).
|
|
Json::Value RPCHandler::doValidationSeed(Json::Value params) {
|
|
Json::Value obj(Json::objectValue);
|
|
|
|
if (params.empty())
|
|
{
|
|
std::cerr << "Unset validation seed." << std::endl;
|
|
|
|
theConfig.VALIDATION_SEED.clear();
|
|
theConfig.VALIDATION_PUB.clear();
|
|
theConfig.VALIDATION_PRIV.clear();
|
|
}
|
|
else if (!theConfig.VALIDATION_SEED.setSeedGeneric(params[0u].asString()))
|
|
{
|
|
theConfig.VALIDATION_PUB.clear();
|
|
theConfig.VALIDATION_PRIV.clear();
|
|
return rpcError(rpcBAD_SEED);
|
|
}
|
|
else
|
|
{
|
|
theConfig.VALIDATION_PUB = RippleAddress::createNodePublic(theConfig.VALIDATION_SEED);
|
|
theConfig.VALIDATION_PRIV = RippleAddress::createNodePrivate(theConfig.VALIDATION_SEED);
|
|
obj["validation_public_key"] = theConfig.VALIDATION_PUB.humanNodePublic();
|
|
obj["validation_seed"] = theConfig.VALIDATION_SEED.humanSeed();
|
|
obj["validation_key"] = theConfig.VALIDATION_SEED.humanSeed1751();
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
Json::Value RPCHandler::accounts(const uint256& uLedger, 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(uLedger, naAccount);
|
|
if (as)
|
|
{
|
|
Json::Value jsonAccount(Json::objectValue);
|
|
|
|
as->addJson(jsonAccount);
|
|
|
|
jsonAccounts.append(jsonAccount);
|
|
}
|
|
else
|
|
{
|
|
uIndex = 0;
|
|
}
|
|
} while (uIndex);
|
|
|
|
return jsonAccounts;
|
|
}
|
|
|
|
// wallet_accounts <seed>
|
|
Json::Value RPCHandler::doWalletAccounts(Json::Value params)
|
|
{
|
|
RippleAddress naSeed;
|
|
|
|
if (!naSeed.setSeedGeneric(params[0u].asString()))
|
|
{
|
|
return rpcError(rpcBAD_SEED);
|
|
}
|
|
|
|
// Try the seed as a master seed.
|
|
RippleAddress naMasterGenerator = RippleAddress::createGeneratorPublic(naSeed);
|
|
|
|
Json::Value jsonAccounts = accounts(uint256(0), naMasterGenerator);
|
|
|
|
if (jsonAccounts.empty())
|
|
{
|
|
// No account via seed as master, try seed a regular.
|
|
Json::Value ret = getMasterGenerator(uint256(0), naSeed, naMasterGenerator);
|
|
|
|
if (!ret.empty())
|
|
return ret;
|
|
|
|
ret["accounts"] = accounts(uint256(0), 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)
|
|
{
|
|
return Log::rotateLog();
|
|
}
|
|
|
|
// wallet_propose [<passphrase>]
|
|
// <passphrase> is only for testing. Master seeds should only be generated randomly.
|
|
Json::Value RPCHandler::doWalletPropose(Json::Value params)
|
|
{
|
|
RippleAddress naSeed;
|
|
RippleAddress naAccount;
|
|
|
|
if (params.empty())
|
|
{
|
|
naSeed.setSeedRandom();
|
|
}
|
|
else
|
|
{
|
|
naSeed = RippleAddress::createSeedGeneric(params[0u].asString());
|
|
}
|
|
|
|
RippleAddress naGenerator = RippleAddress::createGeneratorPublic(naSeed);
|
|
naAccount.setAccountPublic(naGenerator, 0);
|
|
|
|
Json::Value obj(Json::objectValue);
|
|
|
|
obj["master_seed"] = naSeed.humanSeed();
|
|
//obj["master_key"] = naSeed.humanSeed1751();
|
|
obj["account_id"] = naAccount.humanAccountID();
|
|
|
|
return obj;
|
|
}
|
|
|
|
// wallet_seed [<seed>|<passphrase>|<passkey>]
|
|
Json::Value RPCHandler::doWalletSeed(Json::Value params)
|
|
{
|
|
RippleAddress naSeed;
|
|
|
|
if (params.size()
|
|
&& !naSeed.setSeedGeneric(params[0u].asString()))
|
|
{
|
|
return rpcError(rpcBAD_SEED);
|
|
}
|
|
else
|
|
{
|
|
RippleAddress naAccount;
|
|
|
|
if (!params.size())
|
|
{
|
|
naSeed.setSeedRandom();
|
|
}
|
|
|
|
RippleAddress naGenerator = RippleAddress::createGeneratorPublic(naSeed);
|
|
|
|
naAccount.setAccountPublic(naGenerator, 0);
|
|
|
|
Json::Value obj(Json::objectValue);
|
|
|
|
obj["seed"] = naSeed.humanSeed();
|
|
obj["key"] = naSeed.humanSeed1751();
|
|
|
|
return obj;
|
|
}
|
|
}
|
|
|
|
|
|
// TODO: for now this simply checks if this is the admin account
|
|
// TODO: need to prevent them hammering this over and over
|
|
// TODO: maybe a better way is only allow admin from local host
|
|
Json::Value RPCHandler::doLogin(Json::Value params)
|
|
{
|
|
std::string username = params[0u].asString();
|
|
std::string password = params[1u].asString();
|
|
|
|
if (username == theConfig.RPC_USER && password == theConfig.RPC_PASSWORD)
|
|
{
|
|
//mRole=ADMIN;
|
|
return "logged in";
|
|
}
|
|
else
|
|
{
|
|
return "nope";
|
|
}
|
|
}
|
|
|
|
Json::Value RPCHandler::doGetCounts(Json::Value params)
|
|
{
|
|
int minCount = 10;
|
|
if (params.size() > 0)
|
|
minCount = params[0u].asInt();
|
|
|
|
std::vector<InstanceType::InstanceCount> count = InstanceType::getInstanceCounts(minCount);
|
|
|
|
Json::Value ret(Json::objectValue);
|
|
BOOST_FOREACH(InstanceType::InstanceCount& it, count)
|
|
ret[it.first] = it.second;
|
|
return ret;
|
|
}
|
|
|
|
Json::Value RPCHandler::doLogLevel(Json::Value params)
|
|
{
|
|
if (params.size() == 0)
|
|
{ // get log severities
|
|
Json::Value ret = Json::objectValue;
|
|
|
|
ret["base"] = Log::severityToString(Log::getMinSeverity());
|
|
|
|
std::vector< std::pair<std::string, std::string> > logTable = LogPartition::getSeverities();
|
|
typedef std::pair<std::string, std::string> stringPair;
|
|
BOOST_FOREACH(const stringPair& it, logTable)
|
|
ret[it.first] = it.second;
|
|
return ret;
|
|
}
|
|
|
|
if (params.size() == 1)
|
|
{ // set base log severity
|
|
LogSeverity sv = Log::stringToSeverity(params[0u].asString());
|
|
if (sv == lsINVALID)
|
|
return rpcError(rpcINVALID_PARAMS);
|
|
Log::setMinSeverity(sv,true);
|
|
return rpcError(rpcSUCCESS);
|
|
}
|
|
|
|
if (params.size() == 2)
|
|
{ // set partition severity
|
|
LogSeverity sv = Log::stringToSeverity(params[1u].asString());
|
|
if (sv == lsINVALID)
|
|
return rpcError(rpcINVALID_PARAMS);
|
|
if (params[2u].asString() == "base")
|
|
Log::setMinSeverity(sv,false);
|
|
else if (!LogPartition::setSeverity(params[0u].asString(), sv))
|
|
return rpcError(rpcINVALID_PARAMS);
|
|
return rpcError(rpcSUCCESS);
|
|
}
|
|
|
|
assert(false);
|
|
return rpcError(rpcINVALID_PARAMS);
|
|
}
|
|
|
|
// {
|
|
// node: <domain>|<node_public>,
|
|
// comment: <comment> // optional
|
|
// }
|
|
Json::Value RPCHandler::doUnlAdd(Json::Value jvParams)
|
|
{
|
|
std::string strNode = jvParams.isMember("node") ? jvParams["node"].asString() : "";
|
|
std::string strComment = jvParams.isMember("comment") ? jvParams["comment"].asString() : "";
|
|
|
|
RippleAddress raNodePublic;
|
|
|
|
if (raNodePublic.setNodePublic(strNode))
|
|
{
|
|
theApp->getUNL().nodeAddPublic(raNodePublic, UniqueNodeList::vsManual, strComment);
|
|
|
|
return "adding node by public key";
|
|
}
|
|
else
|
|
{
|
|
theApp->getUNL().nodeAddDomain(strNode, UniqueNodeList::vsManual, strComment);
|
|
|
|
return "adding node by domain";
|
|
}
|
|
}
|
|
|
|
// {
|
|
// node: <domain>|<public_key>
|
|
// }
|
|
Json::Value RPCHandler::doUnlDelete(Json::Value jvParams)
|
|
{
|
|
std::string strNode = jvParams[0u].asString();
|
|
|
|
RippleAddress raNodePublic;
|
|
|
|
if (raNodePublic.setNodePublic(strNode))
|
|
{
|
|
theApp->getUNL().nodeRemovePublic(raNodePublic);
|
|
|
|
return "removing node by public key";
|
|
}
|
|
else
|
|
{
|
|
theApp->getUNL().nodeRemoveDomain(strNode);
|
|
|
|
return "removing node by domain";
|
|
}
|
|
}
|
|
|
|
Json::Value RPCHandler::doUnlList(Json::Value params)
|
|
{
|
|
Json::Value obj(Json::objectValue);
|
|
|
|
obj["unl"]=theApp->getUNL().getUnlJson();
|
|
|
|
return obj;
|
|
}
|
|
|
|
// Populate the UNL from a local validators.txt file.
|
|
Json::Value RPCHandler::doUnlLoad(Json::Value params)
|
|
{
|
|
if (theConfig.VALIDATORS_FILE.empty() || !theApp->getUNL().nodeLoad(theConfig.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)
|
|
{
|
|
theApp->getUNL().nodeNetwork();
|
|
|
|
return "fetching";
|
|
}
|
|
|
|
// unl_reset
|
|
Json::Value RPCHandler::doUnlReset(Json::Value params)
|
|
{
|
|
theApp->getUNL().nodeReset();
|
|
|
|
return "removing nodes";
|
|
}
|
|
|
|
// unl_score
|
|
Json::Value RPCHandler::doUnlScore(Json::Value params)
|
|
{
|
|
theApp->getUNL().nodeScore();
|
|
|
|
return "scoring requested";
|
|
}
|
|
|
|
Json::Value RPCHandler::doStop(Json::Value)
|
|
{
|
|
theApp->stop();
|
|
|
|
return SYSTEM_NAME " server stopping";
|
|
}
|
|
|
|
Json::Value RPCHandler::doLedgerAccept(Json::Value)
|
|
{
|
|
Json::Value jvResult;
|
|
|
|
if (!theConfig.RUN_STANDALONE)
|
|
{
|
|
jvResult["error"] = "notStandAlone";
|
|
}
|
|
else
|
|
{
|
|
mNetOps->acceptLedger();
|
|
|
|
jvResult["ledger_current_index"] = mNetOps->getCurrentLedgerID();
|
|
}
|
|
|
|
return jvResult;
|
|
}
|
|
|
|
Json::Value RPCHandler::doTransactionEntry(Json::Value jvRequest)
|
|
{
|
|
Json::Value jvResult;
|
|
|
|
if (!jvRequest.isMember("tx_hash"))
|
|
{
|
|
jvResult["error"] = "fieldNotFoundTransaction";
|
|
}
|
|
if (!jvRequest.isMember("ledger_hash"))
|
|
{
|
|
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(jvRequest["tx_hash"].asString());
|
|
|
|
uint256 uLedgerID;
|
|
// XXX Relying on trusted WSS client. Would be better to have a strict routine, returning success or failure.
|
|
uLedgerID.SetHex(jvRequest["ledger_hash"].asString());
|
|
|
|
Ledger::pointer lpLedger = theApp->getLedgerMaster().getLedgerByHash(uLedgerID);
|
|
|
|
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);
|
|
jvResult["metadata"] = tmTrans->getJson(0);
|
|
// 'accounts'
|
|
// 'engine_...'
|
|
// 'ledger_...'
|
|
}
|
|
}
|
|
}
|
|
|
|
return jvResult;
|
|
}
|
|
|
|
Json::Value RPCHandler::lookupLedger(Json::Value jvRequest, Ledger::pointer& lpLedger)
|
|
{
|
|
Json::Value jvResult;
|
|
|
|
uint256 uLedger = jvRequest.isMember("ledger_hash") ? uint256(jvRequest["ledger_hash"].asString()) : 0;
|
|
uint32 uLedgerIndex = jvRequest.isMember("ledger_index") && jvRequest["ledger_index"].isNumeric() ? jvRequest["ledger_index"].asUInt() : 0;
|
|
|
|
if (!!uLedger)
|
|
{
|
|
// Ledger directly specified.
|
|
lpLedger = mNetOps->getLedgerByHash(uLedger);
|
|
|
|
if (!lpLedger)
|
|
{
|
|
jvResult["error"] = "ledgerNotFound";
|
|
return jvResult;
|
|
}
|
|
|
|
uLedgerIndex = lpLedger->getLedgerSeq(); // Set the current index, override if needed.
|
|
}
|
|
else if (!!uLedgerIndex)
|
|
{
|
|
lpLedger = mNetOps->getLedgerBySeq(uLedgerIndex);
|
|
|
|
if (!lpLedger)
|
|
{
|
|
jvResult["error"] = "ledgerNotFound"; // ledger_index from future?
|
|
return jvResult;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Default to current ledger.
|
|
lpLedger = mNetOps->getCurrentLedger();
|
|
uLedgerIndex = lpLedger->getLedgerSeq(); // Set the current index.
|
|
}
|
|
|
|
if (lpLedger->isClosed())
|
|
{
|
|
if (!!uLedger)
|
|
jvResult["ledger_hash"] = uLedger.ToString();
|
|
|
|
jvResult["ledger_index"] = uLedgerIndex;
|
|
}
|
|
else
|
|
{
|
|
jvResult["ledger_current_index"] = uLedgerIndex;
|
|
}
|
|
|
|
return jvResult;
|
|
}
|
|
|
|
Json::Value RPCHandler::doLedgerEntry(Json::Value jvRequest)
|
|
{
|
|
Ledger::pointer lpLedger;
|
|
Json::Value jvResult = lookupLedger(jvRequest, lpLedger);
|
|
|
|
if (!lpLedger)
|
|
return jvResult;
|
|
|
|
uint256 uNodeIndex;
|
|
bool bNodeBinary = false;
|
|
|
|
if (jvRequest.isMember("index"))
|
|
{
|
|
// XXX Needs to provide proof.
|
|
uNodeIndex.SetHex(jvRequest["index"].asString());
|
|
bNodeBinary = true;
|
|
}
|
|
else if (jvRequest.isMember("account_root"))
|
|
{
|
|
RippleAddress naAccount;
|
|
|
|
if (!naAccount.setAccountID(jvRequest["account_root"].asString())
|
|
|| !naAccount.getAccountID())
|
|
{
|
|
jvResult["error"] = "malformedAddress";
|
|
}
|
|
else
|
|
{
|
|
uNodeIndex = Ledger::getAccountRootIndex(naAccount.getAccountID());
|
|
}
|
|
}
|
|
else if (jvRequest.isMember("directory"))
|
|
{
|
|
|
|
if (!jvRequest.isObject())
|
|
{
|
|
uNodeIndex.SetHex(jvRequest["directory"].asString());
|
|
}
|
|
else if (jvRequest["directory"].isMember("sub_index")
|
|
&& !jvRequest["directory"]["sub_index"].isIntegral())
|
|
{
|
|
jvResult["error"] = "malformedRequest";
|
|
}
|
|
else
|
|
{
|
|
uint64 uSubIndex = jvRequest["directory"].isMember("sub_index")
|
|
? jvRequest["directory"]["sub_index"].asUInt()
|
|
: 0;
|
|
|
|
if (jvRequest["directory"].isMember("dir_root"))
|
|
{
|
|
uint256 uDirRoot;
|
|
|
|
uDirRoot.SetHex(jvRequest["dir_root"].asString());
|
|
|
|
uNodeIndex = Ledger::getDirNodeIndex(uDirRoot, uSubIndex);
|
|
}
|
|
else if (jvRequest["directory"].isMember("owner"))
|
|
{
|
|
RippleAddress naOwnerID;
|
|
|
|
if (!naOwnerID.setAccountID(jvRequest["directory"]["owner"].asString()))
|
|
{
|
|
jvResult["error"] = "malformedAddress";
|
|
}
|
|
else
|
|
{
|
|
uint256 uDirRoot = Ledger::getOwnerDirIndex(naOwnerID.getAccountID());
|
|
|
|
uNodeIndex = Ledger::getDirNodeIndex(uDirRoot, uSubIndex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
jvResult["error"] = "malformedRequest";
|
|
}
|
|
}
|
|
}
|
|
else if (jvRequest.isMember("generator"))
|
|
{
|
|
RippleAddress naGeneratorID;
|
|
|
|
if (!jvRequest.isObject())
|
|
{
|
|
uNodeIndex.SetHex(jvRequest["generator"].asString());
|
|
}
|
|
else if (!jvRequest["generator"].isMember("regular_seed"))
|
|
{
|
|
jvResult["error"] = "malformedRequest";
|
|
}
|
|
else if (!naGeneratorID.setSeedGeneric(jvRequest["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 (jvRequest.isMember("offer"))
|
|
{
|
|
RippleAddress naAccountID;
|
|
|
|
if (!jvRequest.isObject())
|
|
{
|
|
uNodeIndex.SetHex(jvRequest["offer"].asString());
|
|
}
|
|
else if (!jvRequest["offer"].isMember("account")
|
|
|| !jvRequest["offer"].isMember("seq")
|
|
|| !jvRequest["offer"]["seq"].isIntegral())
|
|
{
|
|
jvResult["error"] = "malformedRequest";
|
|
}
|
|
else if (!naAccountID.setAccountID(jvRequest["offer"]["account"].asString()))
|
|
{
|
|
jvResult["error"] = "malformedAddress";
|
|
}
|
|
else
|
|
{
|
|
uint32 uSequence = jvRequest["offer"]["seq"].asUInt();
|
|
|
|
uNodeIndex = Ledger::getOfferIndex(naAccountID.getAccountID(), uSequence);
|
|
}
|
|
}
|
|
else if (jvRequest.isMember("ripple_state"))
|
|
{
|
|
RippleAddress naA;
|
|
RippleAddress naB;
|
|
uint160 uCurrency;
|
|
Json::Value jvRippleState = jvRequest["ripple_state"];
|
|
|
|
if (!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()
|
|
) {
|
|
|
|
cLog(lsINFO)
|
|
<< 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)
|
|
{
|
|
SLE::pointer sleNode = mNetOps->getSLE(lpLedger, uNodeIndex);
|
|
|
|
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;
|
|
}
|
|
|
|
Json::Value RPCHandler::doLedgerHeader(Json::Value jvRequest)
|
|
{
|
|
Ledger::pointer lpLedger;
|
|
Json::Value jvResult = lookupLedger(jvRequest, lpLedger);
|
|
|
|
if (!lpLedger)
|
|
return jvResult;
|
|
|
|
Serializer s;
|
|
|
|
lpLedger->addRaw(s);
|
|
|
|
jvResult["ledger_data"] = strHex(s.peekData());
|
|
|
|
if (mRole == ADMIN)
|
|
lpLedger->addJson(jvResult, 0);
|
|
|
|
return jvRequest;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
server : Sends a message anytime the server status changes such as network connectivity.
|
|
ledger : Sends a message at every ledger close.
|
|
transactions : Sends a message for every transaction that makes it into a ledger.
|
|
rt_transactions
|
|
accounts
|
|
rt_accounts
|
|
*/
|
|
Json::Value RPCHandler::doSubscribe(Json::Value jvRequest)
|
|
{
|
|
Json::Value jvResult(Json::objectValue);
|
|
|
|
if (jvRequest.isMember("streams"))
|
|
{
|
|
for (Json::Value::iterator it = jvRequest["streams"].begin(); it != jvRequest["streams"].end(); it++)
|
|
{
|
|
if ((*it).isString())
|
|
{
|
|
std::string streamName=(*it).asString();
|
|
|
|
if(streamName=="server")
|
|
{
|
|
mNetOps->subServer(mInfoSub, jvResult);
|
|
}else if(streamName=="ledger")
|
|
{
|
|
mNetOps->subLedger(mInfoSub, jvResult);
|
|
}else if(streamName=="transactions")
|
|
{
|
|
mNetOps->subTransactions(mInfoSub);
|
|
}else if(streamName=="rt_transactions")
|
|
{
|
|
mNetOps->subRTTransactions(mInfoSub);
|
|
}else
|
|
{
|
|
jvResult["error"] = str(boost::format("Unknown stream: %s") % streamName);
|
|
}
|
|
}else
|
|
{
|
|
jvResult["error"] = "malformedSteam";
|
|
}
|
|
}
|
|
}
|
|
|
|
if (jvRequest.isMember("rt_accounts"))
|
|
{
|
|
boost::unordered_set<RippleAddress> usnaAccoundIds = parseAccountIds(jvRequest["rt_accounts"]);
|
|
|
|
if (usnaAccoundIds.empty())
|
|
{
|
|
jvResult["error"] = "malformedAccount";
|
|
}else
|
|
{
|
|
BOOST_FOREACH(const RippleAddress& naAccountID, usnaAccoundIds)
|
|
{
|
|
mInfoSub->insertSubAccountInfo(naAccountID);
|
|
}
|
|
|
|
mNetOps->subAccount(mInfoSub, usnaAccoundIds, true);
|
|
}
|
|
}
|
|
|
|
if (jvRequest.isMember("accounts"))
|
|
{
|
|
boost::unordered_set<RippleAddress> usnaAccoundIds = parseAccountIds(jvRequest["accounts"]);
|
|
|
|
if (usnaAccoundIds.empty())
|
|
{
|
|
jvResult["error"] = "malformedAccount";
|
|
}else
|
|
{
|
|
BOOST_FOREACH(const RippleAddress& naAccountID, usnaAccoundIds)
|
|
{
|
|
mInfoSub->insertSubAccountInfo(naAccountID);
|
|
}
|
|
|
|
mNetOps->subAccount(mInfoSub, usnaAccoundIds, false);
|
|
}
|
|
}
|
|
|
|
return jvResult;
|
|
}
|
|
|
|
Json::Value RPCHandler::doUnsubscribe(Json::Value jvRequest)
|
|
{
|
|
Json::Value jvResult(Json::objectValue);
|
|
|
|
if (jvRequest.isMember("streams"))
|
|
{
|
|
for (Json::Value::iterator it = jvRequest["streams"].begin(); it != jvRequest["streams"].end(); it++)
|
|
{
|
|
if ((*it).isString() )
|
|
{
|
|
std::string streamName=(*it).asString();
|
|
|
|
if(streamName=="server")
|
|
{
|
|
mNetOps->unsubServer(mInfoSub);
|
|
}else if(streamName=="ledger")
|
|
{
|
|
mNetOps->unsubLedger(mInfoSub);
|
|
}else if(streamName=="transactions")
|
|
{
|
|
mNetOps->unsubTransactions(mInfoSub);
|
|
}else if(streamName=="rt_transactions")
|
|
{
|
|
mNetOps->unsubRTTransactions(mInfoSub);
|
|
}else
|
|
{
|
|
jvResult["error"] = str(boost::format("Unknown stream: %s") % streamName);
|
|
}
|
|
}else
|
|
{
|
|
jvResult["error"] = "malformedSteam";
|
|
}
|
|
}
|
|
}
|
|
|
|
if (jvRequest.isMember("rt_accounts"))
|
|
{
|
|
boost::unordered_set<RippleAddress> usnaAccoundIds = parseAccountIds(jvRequest["rt_accounts"]);
|
|
|
|
if (usnaAccoundIds.empty())
|
|
{
|
|
jvResult["error"] = "malformedAccount";
|
|
}else
|
|
{
|
|
BOOST_FOREACH(const RippleAddress& naAccountID, usnaAccoundIds)
|
|
{
|
|
mInfoSub->insertSubAccountInfo(naAccountID);
|
|
}
|
|
|
|
mNetOps->unsubAccount(mInfoSub, usnaAccoundIds,true);
|
|
}
|
|
}
|
|
|
|
if (jvRequest.isMember("accounts"))
|
|
{
|
|
boost::unordered_set<RippleAddress> usnaAccoundIds = parseAccountIds(jvRequest["accounts"]);
|
|
|
|
if (usnaAccoundIds.empty())
|
|
{
|
|
jvResult["error"] = "malformedAccount";
|
|
}else
|
|
{
|
|
BOOST_FOREACH(const RippleAddress& naAccountID, usnaAccoundIds)
|
|
{
|
|
mInfoSub->insertSubAccountInfo(naAccountID);
|
|
}
|
|
|
|
mNetOps->unsubAccount(mInfoSub, usnaAccoundIds,false);
|
|
}
|
|
}
|
|
|
|
return jvResult;
|
|
}
|
|
|
|
// Provide the JSON-RPC "result" value.
|
|
Json::Value RPCHandler::doRpcCommand(const std::string& strCommand, Json::Value& jvParams, int iRole)
|
|
{
|
|
// cLog(lsTRACE) << "doRpcCommand:" << strCommand << ":" << jvParams;
|
|
|
|
if (!jvParams.isArray() || jvParams.size() > 1)
|
|
return rpcError(rpcINVALID_PARAMS);
|
|
|
|
Json::Value jvRequest = jvParams[0u];
|
|
|
|
jvRequest["command"] = strCommand;
|
|
|
|
Json::Value jvResult = doCommand(jvRequest, iRole);
|
|
|
|
// Always report "status". On an error report the request as received.
|
|
if (jvResult.isMember("error"))
|
|
{
|
|
jvResult["status"] = "error";
|
|
jvResult["request"] = jvRequest;
|
|
|
|
} else {
|
|
jvResult["status"] = "success";
|
|
}
|
|
|
|
return jvResult;
|
|
}
|
|
|
|
Json::Value RPCHandler::doCommand(Json::Value& jvParams, int iRole)
|
|
{
|
|
if (!jvParams.isMember("command"))
|
|
return rpcError(rpcINVALID_PARAMS);
|
|
|
|
std::string strCommand = jvParams["command"].asString();
|
|
|
|
cLog(lsTRACE) << "COMMAND:" << strCommand;
|
|
cLog(lsTRACE) << "REQUEST:" << jvParams;
|
|
|
|
LoadEvent::autoptr le(theApp->getJobQueue().getLoadEventAP(jtRPC));
|
|
|
|
mRole = iRole;
|
|
|
|
static struct {
|
|
const char* pCommand;
|
|
doFuncPtr dfpFunc;
|
|
int iMinParams;
|
|
int iMaxParams;
|
|
bool bAdminRequired;
|
|
bool bEvented;
|
|
unsigned int iOptions;
|
|
} commandsA[] = {
|
|
// Request-response methods
|
|
{ "accept_ledger", &RPCHandler::doAcceptLedger, -1, -1, true, false, optCurrent },
|
|
{ "account_info", &RPCHandler::doAccountInfo, -1, -1, false, false, optCurrent },
|
|
{ "account_tx", &RPCHandler::doAccountTransactions, -1, -1, false, false, optNetwork },
|
|
{ "connect", &RPCHandler::doConnect, 1, 2, true, false, optNone },
|
|
{ "data_delete", &RPCHandler::doDataDelete, 1, 1, true, false, optNone },
|
|
{ "data_fetch", &RPCHandler::doDataFetch, 1, 1, true, false, optNone },
|
|
{ "data_store", &RPCHandler::doDataStore, 2, 2, true, false, optNone },
|
|
{ "get_counts", &RPCHandler::doGetCounts, 0, 1, true, false, optNone },
|
|
{ "ledger", &RPCHandler::doLedger, -1, -1, false, false, optNetwork },
|
|
{ "ledger_accept", &RPCHandler::doLedgerAccept, -1, -1, true, false, optCurrent },
|
|
{ "ledger_closed", &RPCHandler::doLedgerClosed, -1, -1, false, false, optClosed },
|
|
{ "ledger_current", &RPCHandler::doLedgerCurrent, -1, -1, false, false, optCurrent },
|
|
{ "ledger_entry", &RPCHandler::doLedgerEntry, -1, -1, false, false, optCurrent },
|
|
{ "ledger_header", &RPCHandler::doLedgerHeader, -1, -1, false, false, optCurrent },
|
|
{ "log_level", &RPCHandler::doLogLevel, 0, 2, true, false, optNone },
|
|
{ "logrotate", &RPCHandler::doLogRotate, -1, -1, true, false, optNone },
|
|
{ "nickname_info", &RPCHandler::doNicknameInfo, 1, 1, false, false, optCurrent },
|
|
{ "owner_info", &RPCHandler::doOwnerInfo, 1, 2, false, false, optCurrent },
|
|
{ "peers", &RPCHandler::doPeers, -1, -1, true, false, optNone },
|
|
{ "profile", &RPCHandler::doProfile, 1, 9, false, false, optCurrent },
|
|
{ "ripple_lines_get", &RPCHandler::doRippleLinesGet, 1, 2, false, false, optCurrent },
|
|
{ "ripple_path_find", &RPCHandler::doRipplePathFind, -1, -1, false, false, optCurrent },
|
|
{ "submit", &RPCHandler::doSubmit, -1, -1, false, false, optCurrent },
|
|
{ "server_info", &RPCHandler::doServerInfo, -1, -1, true, false, optNone },
|
|
{ "stop", &RPCHandler::doStop, -1, -1, true, false, optNone },
|
|
{ "transaction_entry", &RPCHandler::doTransactionEntry, -1, -1, false, false, optCurrent },
|
|
{ "tx", &RPCHandler::doTx, 1, 1, true, false, optNone },
|
|
{ "tx_history", &RPCHandler::doTxHistory, 1, 1, false, false, optNone },
|
|
|
|
{ "unl_add", &RPCHandler::doUnlAdd, -1, -1, true, false, optNone },
|
|
{ "unl_delete", &RPCHandler::doUnlDelete, -1, -1, true, false, optNone },
|
|
{ "unl_list", &RPCHandler::doUnlList, -1, -1, true, false, optNone },
|
|
{ "unl_load", &RPCHandler::doUnlLoad, -1, -1, true, false, optNone },
|
|
{ "unl_network", &RPCHandler::doUnlNetwork, -1, -1, true, false, optNone },
|
|
{ "unl_reset", &RPCHandler::doUnlReset, -1, -1, true, false, optNone },
|
|
{ "unl_score", &RPCHandler::doUnlScore, -1, -1, true, false, optNone },
|
|
|
|
{ "validation_create", &RPCHandler::doValidationCreate, 0, 1, false, false, optNone },
|
|
{ "validation_seed", &RPCHandler::doValidationSeed, 0, 1, false, false, optNone },
|
|
|
|
{ "wallet_accounts", &RPCHandler::doWalletAccounts, 1, 1, false, false, optCurrent },
|
|
{ "wallet_propose", &RPCHandler::doWalletPropose, 0, 1, false, false, optNone },
|
|
{ "wallet_seed", &RPCHandler::doWalletSeed, 0, 1, false, false, optNone },
|
|
|
|
{ "login", &RPCHandler::doLogin, 2, 2, true, false, optNone },
|
|
|
|
// Evented methods
|
|
{ "subscribe", &RPCHandler::doSubscribe, -1, -1, false, true, optNone },
|
|
{ "unsubscribe", &RPCHandler::doUnsubscribe, -1, -1, false, true, optNone },
|
|
};
|
|
|
|
int i = NUMBER(commandsA);
|
|
|
|
while (i-- && strCommand != commandsA[i].pCommand)
|
|
;
|
|
|
|
if (i < 0)
|
|
{
|
|
return rpcError(rpcUNKNOWN_COMMAND);
|
|
}
|
|
else if (commandsA[i].bAdminRequired && mRole != ADMIN)
|
|
{
|
|
return rpcError(rpcNO_PERMISSION);
|
|
}
|
|
else if (commandsA[i].bEvented && mInfoSub == NULL)
|
|
{
|
|
return rpcError(rpcNO_EVENTS);
|
|
}
|
|
else if (commandsA[i].iMinParams >= 0
|
|
? commandsA[i].iMaxParams
|
|
? (jvParams.size() < commandsA[i].iMinParams
|
|
|| (commandsA[i].iMaxParams >= 0 && jvParams.size() > commandsA[i].iMaxParams))
|
|
: false
|
|
: jvParams.isArray())
|
|
{
|
|
cLog(lsDEBUG) << "params.size: " << jvParams.size() << " array: " << jvParams.isArray();
|
|
return rpcError(rpcINVALID_PARAMS);
|
|
}
|
|
else if ((commandsA[i].iOptions & optNetwork) && !mNetOps->available())
|
|
{
|
|
return rpcError(rpcNO_NETWORK);
|
|
}
|
|
// XXX Should verify we have a current ledger.
|
|
|
|
boost::recursive_mutex::scoped_lock sl(theApp->getMasterLock());
|
|
if ((commandsA[i].iOptions & optCurrent) && false)
|
|
{
|
|
return rpcError(rpcNO_CURRENT);
|
|
}
|
|
else if ((commandsA[i].iOptions & optClosed) && !mNetOps->getClosedLedger())
|
|
{
|
|
return rpcError(rpcNO_CLOSED);
|
|
}
|
|
else
|
|
{
|
|
try {
|
|
Json::Value jvRaw = (this->*(commandsA[i].dfpFunc))(jvParams);
|
|
|
|
// 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)
|
|
{
|
|
cLog(lsINFO) << "Caught throw: " << e.what();
|
|
|
|
return rpcError(rpcINTERNAL);
|
|
}
|
|
}
|
|
}
|
|
|
|
// vim:ts=4
|