//------------------------------------------------------------------------------ /* Copyright (c) 2011-2013, OpenCoin, Inc. */ //============================================================================== // // Carries out the RPC. // SETUP_LOG (RPCHandler) int iAdminGet (const Json::Value& params, const std::string& strRemoteIp) { int iRole; bool bPasswordSupplied = params.isMember ("admin_user") || params.isMember ("admin_password"); bool bPasswordRequired = !getConfig ().RPC_ADMIN_USER.empty () || !getConfig ().RPC_ADMIN_PASSWORD.empty (); bool bPasswordWrong = bPasswordSupplied ? bPasswordRequired // Supplied, required, and incorrect. ? getConfig ().RPC_ADMIN_USER != (params.isMember ("admin_user") ? params["admin_user"].asString () : "") || getConfig ().RPC_ADMIN_PASSWORD != (params.isMember ("admin_user") ? params["admin_password"].asString () : "") // Supplied and not required. : true : false; // Meets IP restriction for admin. bool bAdminIP = false; BOOST_FOREACH (const std::string & strAllowIp, getConfig ().RPC_ADMIN_ALLOW) { if (strAllowIp == strRemoteIp) bAdminIP = true; } if (bPasswordWrong // Wrong || (bPasswordSupplied && !bAdminIP)) // Supplied and doesn't meet IP filter. { iRole = RPCHandler::FORBID; } // If supplied, password is correct. else { // Allow admin, if from admin IP and no password is required or it was supplied and correct. iRole = bAdminIP && (!bPasswordRequired || bPasswordSupplied) ? RPCHandler::ADMIN : RPCHandler::GUEST; } return iRole; } RPCHandler::RPCHandler (NetworkOPs* netOps) : mNetOps (netOps), mRole (FORBID) { ; } RPCHandler::RPCHandler (NetworkOPs* netOps, InfoSub::pointer infoSub) : mNetOps (netOps), mInfoSub (infoSub), mRole (FORBID) { ; } Json::Value RPCHandler::transactionSign (Json::Value params, bool bSubmit, bool bFailHard, Application::ScopedLockType& mlh) { if (getApp().getFeeTrack().isLoadedCluster() && (mRole != 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 && (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->getCurrentSnapshot (); 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); } { bool bValid; RippleLineCache::pointer cache = boost::make_shared (lSnapshot); Pathfinder pf (cache, raSrcAddressID, dstAccountID, saSendMax.getCurrency (), saSendMax.getIssuer (), saSend, bValid); if (!bValid || !pf.findPaths (getConfig ().PATH_SEARCH_SIZE, 3, spsPaths)) { 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); } UPTR_T 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 (*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 (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 == 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); } // { // account: , // account_index : // optional // strict: // true, only allow public keys and addresses. false, default. // ledger_hash : // ledger_index : // } Json::Value RPCHandler::doAccountInfo (Json::Value params, LoadType* 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 = rpcError (rpcACT_NOT_FOUND); } return jvResult; } // { // ip: , // port: // } // XXX Might allow domain for manual connections. Json::Value RPCHandler::doConnect (Json::Value params, LoadType* 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: // } Json::Value RPCHandler::doDataDelete (Json::Value params, LoadType* 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: // } Json::Value RPCHandler::doDataFetch (Json::Value params, LoadType* 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: // value: // } Json::Value RPCHandler::doDataStore (Json::Value params, LoadType* 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 // 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' : , // 'account_index' : // optional // } // XXX This would be better if it took the ledger. Json::Value RPCHandler::doOwnerInfo (Json::Value params, LoadType* 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, LoadType* 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, LoadType* loadType, Application::ScopedLockType& masterLockHolder) { return Json::Value (Json::objectValue); } // profile offers [submit] // profile 0:offers 1:pass_a 2:account_a 3:currency_offer_a 4:account_b 5:currency_offer_b 6: 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, LoadType* 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())) // return rpcError(rpcINVALID_PARAMS); naAccountA.setAccountID(params[2u].asString()); // if (!STAmount::currencyFromString(uCurrencyOfferA, params[3u].asString())) // return rpcError(rpcINVALID_PARAMS); naAccountB.setAccountID(params[4u].asString()); // if (!STAmount::currencyFromString(uCurrencyOfferB, params[5u].asString())) // return rpcError(rpcINVALID_PARAMS); iCount = lexicalCast (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; ngetSeq(), // 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: // optional // secret: // optional // } Json::Value RPCHandler::doProofCreate (Json::Value params, LoadType* 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? beast::ScopedPointer pgGen (IProofOfWorkFactory::New ()); if (params.isMember ("difficulty")) { if (!params["difficulty"].isIntegral ()) return rpcError (rpcINVALID_PARAMS); int iDifficulty = params["difficulty"].asInt (); if (iDifficulty < 0 || iDifficulty > ProofOfWork::sMaxDifficulty) 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: // } Json::Value RPCHandler::doProofSolve (Json::Value params, LoadType* 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: // solution: // // if either of these parameters is set, a custom verifier is used // difficulty: // optional // secret: // optional // } Json::Value RPCHandler::doProofVerify (Json::Value params, LoadType* 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? beast::ScopedPointer pgGen (IProofOfWorkFactory::New ()); if (params.isMember ("difficulty")) { if (!params["difficulty"].isIntegral ()) return rpcError (rpcINVALID_PARAMS); int iDifficulty = params["difficulty"].asInt (); if (iDifficulty < 0 || iDifficulty > ProofOfWork::sMaxDifficulty) 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; powResultInfo (prResult, sToken, sHuman); jvResult["proof_result"] = sToken; jvResult["proof_result_code"] = prResult; jvResult["proof_result_message"] = sHuman; return jvResult; } // { // account: || // account_index: // optional, defaults to 0. // ledger_hash : // ledger_index : // } Json::Value RPCHandler::doAccountLines (Json::Value params, LoadType* 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)) { jvResult["account"] = raAccount.humanAccountID (); Json::Value& jsonLines = (jvResult["lines"] = Json::arrayValue); AccountItems rippleLines (raAccount.getAccountID (), lpLedger, AccountItem::pointer (new RippleState ())); 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 (line->getQualityIn ()); jPeer["quality_out"] = static_cast (line->getQualityOut ()); if (line->getAuth()) jPeer["authorized"] = true; if (line->getAuthPeer()) jPeer["peer_authorized"] = true; } } if (!bUnlocked) masterLockHolder.unlock (); } 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_index: // optional, defaults to 0. // ledger_hash : // ledger_index : // } Json::Value RPCHandler::doAccountOffers (Json::Value params, LoadType* 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 (); 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, LoadType* 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); return jvResult; } // Result: // { // random: // } Json::Value RPCHandler::doRandom (Json::Value params, LoadType* 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, LoadType* loadType, Application::ScopedLockType& masterLockHolder) { 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") { mInfoSub->clearPathRequest (); PathRequest::pointer request = boost::make_shared (mInfoSub); Json::Value result = request->doCreate (mNetOps->getClosedLedger (), params); if (request->isValid ()) { mInfoSub->setPathRequest (request); getApp().getLedgerMaster ().newPathRequest (); } return result; } 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); } // 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 params, LoadType* loadType, Application::ScopedLockType& masterLockHolder) { int jc = getApp().getJobQueue ().getJobCountGE (jtCLIENT); if (jc > 200) { WriteLog (lsDEBUG, RPCHandler) << "Too busy for RPF: " << jc; return rpcError (rpcTOO_BUSY); } RippleAddress raSrc; RippleAddress raDst; STAmount saDstAmount; Ledger::pointer lpLedger; Json::Value 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.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 { Json::Value jvSrcCurrencies; if (params.isMember ("source_currencies")) { jvSrcCurrencies = params["source_currencies"]; } else { boost::unordered_set 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); } } *loadType = LT_RPCBurden; Ledger::pointer lSnapShot = boost::make_shared (boost::ref (*lpLedger), false); masterLockHolder.unlock (); // As long as we have a locked copy of the ledger, we can unlock. // Fill in currencies destination will accept Json::Value jvDestCur (Json::arrayValue); boost::unordered_set 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); RippleLineCache::pointer cache = boost::make_shared (lSnapShot); 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); if (!bValid || !pf.findPaths (getConfig ().PATH_SEARCH_SIZE, 3, spsComputed)) { WriteLog (lsWARNING, RPCHandler) << "ripple_path_find: No paths found."; } else { std::vector 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 (lSnapShot, 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 (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: , // secret: // } Json::Value RPCHandler::doSign (Json::Value params, LoadType* loadType, Application::ScopedLockType& masterLockHolder) { *loadType = LT_RPCBurden; bool bFailHard = params.isMember ("fail_hard") && params["fail_hard"].asBool (); return transactionSign (params, false, bFailHard, masterLockHolder); } // { // tx_json: , // secret: // } Json::Value RPCHandler::doSubmit (Json::Value params, LoadType* loadType, Application::ScopedLockType& masterLockHolder) { 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); } *loadType = LT_RPCBurden; Serializer sTrans (vucBlob); SerializerIterator sitTrans (sTrans); SerializedTransaction::pointer stpTrans; try { stpTrans = boost::make_shared (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 (stpTrans, false); } catch (std::exception& e) { jvResult["error"] = "internalTransaction"; jvResult["error_exception"] = e.what (); return jvResult; } try { (void) mNetOps->processTransaction (tpTrans, mRole == 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, LoadType* loadType, Application::ScopedLockType& masterLockHolder) { Json::Value ret (Json::objectValue); ret["info"] = mNetOps->getConsensusInfo (); return ret; } Json::Value RPCHandler::doFetchInfo (Json::Value jvParams, LoadType* 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, LoadType* loadType, Application::ScopedLockType& masterLockHolder) { Json::Value ret (Json::objectValue); ret["info"] = mNetOps->getServerInfo (true, mRole == ADMIN); return ret; } Json::Value RPCHandler::doServerState (Json::Value, LoadType* loadType, Application::ScopedLockType& masterLockHolder) { Json::Value ret (Json::objectValue); ret["state"] = mNetOps->getServerInfo (false, mRole == ADMIN); return ret; } // { // start: // } Json::Value RPCHandler::doTxHistory (Json::Value params, LoadType* loadType, Application::ScopedLockType& masterLockHolder) { masterLockHolder.unlock (); if (!params.isMember ("start")) return rpcError (rpcINVALID_PARAMS); unsigned int startIndex = params["start"].asUInt (); 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 (); ScopedLock 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: // } Json::Value RPCHandler::doTx (Json::Value params, LoadType* loadType, Application::ScopedLockType& masterLockHolder) { 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, LoadType* 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, LoadType* loadType, Application::ScopedLockType& masterLockHolder) { Json::Value jvResult; jvResult["ledger_current_index"] = mNetOps->getCurrentLedgerID (); return jvResult; } // ledger [id|index|current|closed] [full] // { // ledger: 'current' | 'closed' | | , // optional // full: true | false // optional, defaults to false. // } Json::Value RPCHandler::doLedger (Json::Value params, LoadType* 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); Json::Value ret (Json::objectValue); lpLedger->addJson (ret, iOptions); return ret; } // { // 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::doAccountTransactions (Json::Value params, LoadType* 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); // 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 (); } #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 txns = mNetOps->getAccountTxsB (raAccount, uLedgerMin, uLedgerMax, bDescending, offset, limit, mRole == ADMIN); for (std::vector::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 > txns = mNetOps->getAccountTxs (raAccount, uLedgerMin, uLedgerMax, bDescending, offset, limit, mRole == ADMIN); for (std::vector< std::pair >::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; ret["validated"] = bValidated && uValidatedMin <= uLedgerMin && uValidatedMax >= uLedgerMax; ret["offset"] = offset; if (bCount) ret["count"] = mNetOps->countAccountTxs (raAccount, uLedgerMin, uLedgerMax); if (params.isMember ("limit")) ret["limit"] = limit; return ret; #ifndef BEAST_DEBUG } catch (...) { return rpcError (rpcINTERNAL); } #endif } // { // secret: // optional // } // // This command requires admin access because it makes no sense to ask an untrusted server for this. Json::Value RPCHandler::doValidationCreate (Json::Value params, LoadType* 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: // } Json::Value RPCHandler::doValidationSeed (Json::Value params, LoadType* 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: // ledger_hash : // ledger_index : // } Json::Value RPCHandler::doWalletAccounts (Json::Value params, LoadType* 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, LoadType* loadType, Application::ScopedLockType& masterLockHolder) { return Log::rotateLog (); } // { // passphrase: // } Json::Value RPCHandler::doWalletPropose (Json::Value params, LoadType* 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: // } Json::Value RPCHandler::doWalletSeed (Json::Value params, LoadType* 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 admin account // TODO: need to prevent them hammering this over and over // TODO: maybe a better way is only allow admin from local host // { // username: , // password: // } Json::Value RPCHandler::doLogin (Json::Value params, LoadType* 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 (i); text += " "; text += unitName; if (i > 1) text += "s"; } Json::Value RPCHandler::doFeature (Json::Value params, LoadType* 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: // optional, defaults to 10 // } Json::Value RPCHandler::doGetCounts (Json::Value params, LoadType* 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 (); 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, LoadType* 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 (Log::getMinSeverity ()); std::vector< std::pair > logTable = LogPartition::getSeverities (); typedef std::map::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 Log::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")) Log::setMinSeverity (sv, false); else if (!LogPartition::setSeverity (partition, sv)) return rpcError (rpcINVALID_PARAMS); return Json::objectValue; } return rpcError (rpcINVALID_PARAMS); } // { // node: |, // comment: // optional // } Json::Value RPCHandler::doUnlAdd (Json::Value params, LoadType* 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: | // } Json::Value RPCHandler::doUnlDelete (Json::Value params, LoadType* 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, LoadType* 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, LoadType* 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, LoadType* loadType, Application::ScopedLockType& masterLockHolder) { getApp().getUNL ().nodeNetwork (); return "fetching"; } // unl_reset Json::Value RPCHandler::doUnlReset (Json::Value params, LoadType* loadType, Application::ScopedLockType& masterLockHolder) { getApp().getUNL ().nodeReset (); return "removing nodes"; } // unl_score Json::Value RPCHandler::doUnlScore (Json::Value, LoadType* loadType, Application::ScopedLockType& masterLockHolder) { getApp().getUNL ().nodeScore (); return "scoring requested"; } Json::Value RPCHandler::doSMS (Json::Value params, LoadType* loadType, Application::ScopedLockType& masterLockHolder) { if (!params.isMember ("text")) return rpcError (rpcINVALID_PARAMS); HttpsClient::sendSMS (getApp().getIOService (), params["text"].asString ()); return "sms dispatched"; } Json::Value RPCHandler::doStop (Json::Value, LoadType* loadType, Application::ScopedLockType& masterLockHolder) { getApp().stop (); return SYSTEM_NAME " server stopping"; } Json::Value RPCHandler::doLedgerAccept (Json::Value, LoadType* 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; } // { // ledger_hash : , // 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, LoadType* loadType, Application::ScopedLockType& masterLockHolder) { Ledger::pointer lpLedger; Json::Value jvResult = lookupLedger (params, lpLedger); if (!lpLedger) return jvResult; 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); 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->getCurrentSnapshot (); 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_index : // ... // } Json::Value RPCHandler::doLedgerEntry (Json::Value params, LoadType* 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 (!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_index : // } Json::Value RPCHandler::doLedgerHeader (Json::Value params, LoadType* 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 RPCHandler::parseAccountIds (const Json::Value& jvArray) { boost::unordered_set 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, LoadType* 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 != 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 = boost::make_shared (strUrl, strUsername, strPassword); ispSub = mNetOps->addRpcSub (strUrl, boost::dynamic_pointer_cast (rspSub)); } else { WriteLog (lsTRACE, RPCHandler) << boost::str (boost::format ("doSubscribe: reusing: %s") % strUrl); if (params.isMember ("username")) dynamic_cast (&*ispSub)->setUsername (strUsername); if (params.isMember ("password")) dynamic_cast (&*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 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 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); 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 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) { Ledger::pointer lpLedger = getApp().getLedgerMaster ().getPublishedLedger (); 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, LoadType* 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 != 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 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 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, LoadType* 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 = 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, LoadType* 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, LoadType* loadType) { if (iRole != ADMIN) { 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_lines", &RPCHandler::doAccountLines, false, optCurrent }, { "account_offers", &RPCHandler::doAccountOffers, false, optCurrent }, { "account_tx", &RPCHandler::doAccountTransactions, false, optNetwork }, { "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_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 }, // { "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 != 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 { 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 == LT_RPCReference) *loadType = LT_RPCException; 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