Json::Value doBookOffers (RPC::Context& context) { // VFALCO TODO Here is a terrible place for this kind of business // logic. It needs to be moved elsewhere and documented, // and encapsulated into a function. if (getApp().getJobQueue ().getJobCountGE (jtCLIENT) > 200) return rpcError (rpcTOO_BUSY); Ledger::pointer lpLedger; Json::Value jvResult ( RPC::lookupLedger (context.params, lpLedger, context.netOps)); if (!lpLedger) return jvResult; if (!context.params.isMember (jss::taker_pays)) return RPC::missing_field_error (jss::taker_pays); if (!context.params.isMember (jss::taker_gets)) return RPC::missing_field_error (jss::taker_gets); if (!context.params[jss::taker_pays].isObject ()) return RPC::object_field_error (jss::taker_pays); if (!context.params[jss::taker_gets].isObject ()) return RPC::object_field_error (jss::taker_gets); Json::Value const& taker_pays (context.params[jss::taker_pays]); if (!taker_pays.isMember (jss::currency)) return RPC::missing_field_error ("taker_pays.currency"); if (! taker_pays [jss::currency].isString ()) return RPC::expected_field_error ("taker_pays.currency", "string"); Json::Value const& taker_gets = context.params[jss::taker_gets]; if (! taker_gets.isMember (jss::currency)) return RPC::missing_field_error ("taker_gets.currency"); if (! taker_gets [jss::currency].isString ()) return RPC::expected_field_error ("taker_gets.currency", "string"); Currency pay_currency; if (!to_currency (pay_currency, taker_pays [jss::currency].asString ())) { WriteLog (lsINFO, RPCHandler) << "Bad taker_pays currency."; return RPC::make_error (rpcSRC_CUR_MALFORMED, "Invalid field 'taker_pays.currency', bad currency."); } Currency get_currency; if (!to_currency (get_currency, taker_gets [jss::currency].asString ())) { WriteLog (lsINFO, RPCHandler) << "Bad taker_gets currency."; return RPC::make_error (rpcDST_AMT_MALFORMED, "Invalid field 'taker_gets.currency', bad currency."); } AccountID pay_issuer; if (taker_pays.isMember (jss::issuer)) { if (! taker_pays [jss::issuer].isString()) return RPC::expected_field_error ("taker_pays.issuer", "string"); if (!to_issuer( pay_issuer, taker_pays [jss::issuer].asString ())) return RPC::make_error (rpcSRC_ISR_MALFORMED, "Invalid field 'taker_pays.issuer', bad issuer."); if (pay_issuer == noAccount ()) return RPC::make_error (rpcSRC_ISR_MALFORMED, "Invalid field 'taker_pays.issuer', bad issuer account one."); } else { pay_issuer = xdvAccount (); } if (isXDV (pay_currency) && ! isXDV (pay_issuer)) return RPC::make_error ( rpcSRC_ISR_MALFORMED, "Unneeded field 'taker_pays.issuer' for " "XDV currency specification."); if (!isXDV (pay_currency) && isXDV (pay_issuer)) return RPC::make_error (rpcSRC_ISR_MALFORMED, "Invalid field 'taker_pays.issuer', expected non-XDV issuer."); AccountID get_issuer; if (taker_gets.isMember (jss::issuer)) { if (! taker_gets [jss::issuer].isString()) return RPC::expected_field_error ("taker_gets.issuer", "string"); if (! to_issuer ( get_issuer, taker_gets [jss::issuer].asString ())) return RPC::make_error (rpcDST_ISR_MALFORMED, "Invalid field 'taker_gets.issuer', bad issuer."); if (get_issuer == noAccount ()) return RPC::make_error (rpcDST_ISR_MALFORMED, "Invalid field 'taker_gets.issuer', bad issuer account one."); } else { get_issuer = xdvAccount (); } if (isXDV (get_currency) && ! isXDV (get_issuer)) return RPC::make_error (rpcDST_ISR_MALFORMED, "Unneeded field 'taker_gets.issuer' for " "XDV currency specification."); if (!isXDV (get_currency) && isXDV (get_issuer)) return RPC::make_error (rpcDST_ISR_MALFORMED, "Invalid field 'taker_gets.issuer', expected non-XDV issuer."); DivvyAddress raTakerID; if (context.params.isMember (jss::taker)) { if (! context.params [jss::taker].isString ()) return RPC::expected_field_error (jss::taker, "string"); if (! raTakerID.setAccountID (context.params [jss::taker].asString ())) return RPC::invalid_field_error (jss::taker); } else { raTakerID.setAccountID (noAccount()); } if (pay_currency == get_currency && pay_issuer == get_issuer) { WriteLog (lsINFO, RPCHandler) << "taker_gets same as taker_pays."; return RPC::make_error (rpcBAD_MARKET); } unsigned int iLimit; if (context.params.isMember (jss::limit)) { auto const& jvLimit (context.params[jss::limit]); if (! jvLimit.isIntegral ()) return RPC::expected_field_error (jss::limit, "unsigned integer"); iLimit = jvLimit.isUInt () ? jvLimit.asUInt () : std::max (0, jvLimit.asInt ()); } else { iLimit = 0; } bool const bProof (context.params.isMember (jss::proof)); Json::Value const jvMarker (context.params.isMember (jss::marker) ? context.params[jss::marker] : Json::Value (Json::nullValue)); context.netOps.getBookPage ( context.role == Role::ADMIN, lpLedger, {{pay_currency, pay_issuer}, {get_currency, get_issuer}}, raTakerID.getAccountID (), bProof, iLimit, jvMarker, jvResult); context.loadType = Resource::feeMediumBurdenRPC; return jvResult; }
Json::Value doSubscribe (RPC::Context& context) { auto lock = getApp().masterLock(); // FIXME: This needs to release the master lock immediately // Subscriptions need to be protected by their own lock InfoSub::pointer ispSub; Json::Value jvResult (Json::objectValue); std::uint32_t uLedgerIndex = context.params_.isMember (jss::ledger_index) && context.params_[jss::ledger_index].isNumeric () ? context.params_[jss::ledger_index].asUInt () : 0; if (!context.infoSub_ && !context.params_.isMember ("url")) { // Must be a JSON-RPC call. WriteLog (lsINFO, RPCHandler) << "doSubscribe: RPC subscribe requires a url"; return rpcError (rpcINVALID_PARAMS); } if (context.params_.isMember ("url")) { if (context.role_ != Config::ADMIN) return rpcError (rpcNO_PERMISSION); std::string strUrl = context.params_["url"].asString (); std::string strUsername = context.params_.isMember ("url_username") ? context.params_["url_username"].asString () : ""; std::string strPassword = context.params_.isMember ("url_password") ? context.params_["url_password"].asString () : ""; // DEPRECATED if (context.params_.isMember ("username")) strUsername = context.params_["username"].asString (); // DEPRECATED if (context.params_.isMember ("password")) strPassword = context.params_["password"].asString (); ispSub = context.netOps_.findRpcSub (strUrl); if (!ispSub) { WriteLog (lsDEBUG, RPCHandler) << "doSubscribe: building: " << strUrl; RPCSub::pointer rspSub = RPCSub::New (getApp ().getOPs (), getApp ().getIOService (), getApp ().getJobQueue (), strUrl, strUsername, strPassword); ispSub = context.netOps_.addRpcSub ( strUrl, std::dynamic_pointer_cast<InfoSub> (rspSub)); } else { WriteLog (lsTRACE, RPCHandler) << "doSubscribe: reusing: " << strUrl; if (context.params_.isMember ("username")) dynamic_cast<RPCSub*> (&*ispSub)->setUsername (strUsername); if (context.params_.isMember ("password")) dynamic_cast<RPCSub*> (&*ispSub)->setPassword (strPassword); } } else { ispSub = context.infoSub_; } if (!context.params_.isMember ("streams")) { } else if (!context.params_["streams"].isArray ()) { WriteLog (lsINFO, RPCHandler) << "doSubscribe: streams requires an array."; return rpcError (rpcINVALID_PARAMS); } else { for (auto& it: context.params_["streams"]) { if (it.isString ()) { std::string streamName = it.asString (); if (streamName == "server") { context.netOps_.subServer (ispSub, jvResult, context.role_ == Config::ADMIN); } else if (streamName == "ledger") { context.netOps_.subLedger (ispSub, jvResult); } else if (streamName == "transactions") { context.netOps_.subTransactions (ispSub); } else if (streamName == "transactions_proposed" || streamName == "rt_transactions") // DEPRECATED { context.netOps_.subRTTransactions (ispSub); } else { jvResult[jss::error] = "unknownStream"; } } else { jvResult[jss::error] = "malformedStream"; } } } std::string strAccountsProposed = context.params_.isMember ("accounts_proposed") ? "accounts_proposed" : "rt_accounts"; // DEPRECATED if (!context.params_.isMember (strAccountsProposed)) { } else if (!context.params_[strAccountsProposed].isArray ()) { return rpcError (rpcINVALID_PARAMS); } else { auto ids = RPC::parseAccountIds (context.params_[strAccountsProposed]); if (ids.empty ()) jvResult[jss::error] = "malformedAccount"; else context.netOps_.subAccount (ispSub, ids, uLedgerIndex, true); } if (!context.params_.isMember ("accounts")) { } else if (!context.params_["accounts"].isArray ()) { return rpcError (rpcINVALID_PARAMS); } else { auto ids = RPC::parseAccountIds (context.params_["accounts"]); if (ids.empty ()) { jvResult[jss::error] = "malformedAccount"; } else { context.netOps_.subAccount (ispSub, ids, uLedgerIndex, false); WriteLog (lsDEBUG, RPCHandler) << "doSubscribe: accounts: " << ids.size (); } } bool bHaveMasterLock = true; if (!context.params_.isMember ("books")) { } else if (!context.params_["books"].isArray ()) { return rpcError (rpcINVALID_PARAMS); } else { for (auto& j: context.params_["books"]) { if (!j.isObject () || !j.isMember (jss::taker_pays) || !j.isMember (jss::taker_gets) || !j[jss::taker_pays].isObject () || !j[jss::taker_gets].isObject ()) return rpcError (rpcINVALID_PARAMS); Book book; bool bBoth = (j.isMember ("both") && j["both"].asBool ()) || (j.isMember ("both_sides") && j["both_sides"].asBool ()); bool bSnapshot = (j.isMember ("snapshot") && j["snapshot"].asBool ()) || (j.isMember ("state_now") && j["state_now"].asBool ()); // TODO(tom): both_sides and state_now are apparently deprecated... // where is this documented? Json::Value taker_pays = j[jss::taker_pays]; Json::Value taker_gets = j[jss::taker_gets]; // Parse mandatory currency. if (!taker_pays.isMember (jss::currency) || !to_currency (book.in.currency, taker_pays[jss::currency].asString ())) { WriteLog (lsINFO, RPCHandler) << "Bad taker_pays currency."; return rpcError (rpcSRC_CUR_MALFORMED); } // Parse optional issuer. else if (((taker_pays.isMember (jss::issuer)) && (!taker_pays[jss::issuer].isString () || !to_issuer (book.in.account, taker_pays[jss::issuer].asString ()))) // Don't allow illegal issuers. || (!book.in.currency != !book.in.account) || noAccount() == book.in.account) { WriteLog (lsINFO, RPCHandler) << "Bad taker_pays issuer."; return rpcError (rpcSRC_ISR_MALFORMED); } // Parse mandatory currency. if (!taker_gets.isMember (jss::currency) || !to_currency (book.out.currency, taker_gets[jss::currency].asString ())) { WriteLog (lsINFO, RPCHandler) << "Bad taker_pays currency."; return rpcError (rpcSRC_CUR_MALFORMED); } // Parse optional issuer. else if (((taker_gets.isMember (jss::issuer)) && (!taker_gets[jss::issuer].isString () || !to_issuer (book.out.account, taker_gets[jss::issuer].asString ()))) // Don't allow illegal issuers. || (!book.out.currency != !book.out.account) || noAccount() == book.out.account) { WriteLog (lsINFO, RPCHandler) << "Bad taker_gets issuer."; return rpcError (rpcDST_ISR_MALFORMED); } if (book.in.currency == book.out.currency && book.in.account == book.out.account) { WriteLog (lsINFO, RPCHandler) << "taker_gets same as taker_pays."; return rpcError (rpcBAD_MARKET); } RippleAddress raTakerID; if (!j.isMember ("taker")) raTakerID.setAccountID (noAccount()); else if (!raTakerID.setAccountID (j["taker"].asString ())) return rpcError (rpcBAD_ISSUER); if (!isConsistent (book) || !isConsistentVBC(book)) { WriteLog (lsWARNING, RPCHandler) << "Bad market: " << book; return rpcError (rpcBAD_MARKET); } context.netOps_.subBook (ispSub, book); if (bBoth) context.netOps_.subBook (ispSub, book); if (bSnapshot) { if (bHaveMasterLock) { lock->unlock (); bHaveMasterLock = false; } context.loadType_ = Resource::feeMediumBurdenRPC; auto lpLedger = getApp().getLedgerMaster (). getPublishedLedger (); if (lpLedger) { const Json::Value jvMarker = Json::Value (Json::nullValue); if (bBoth) { Json::Value jvBids (Json::objectValue); Json::Value jvAsks (Json::objectValue); context.netOps_.getBookPage ( lpLedger, book, raTakerID.getAccountID (), false, 0, jvMarker, jvBids); if (jvBids.isMember (jss::offers)) jvResult[jss::bids] = jvBids[jss::offers]; context.netOps_.getBookPage ( lpLedger, book, raTakerID.getAccountID (), false, 0, jvMarker, jvAsks); if (jvAsks.isMember (jss::offers)) jvResult[jss::asks] = jvAsks[jss::offers]; } else { context.netOps_.getBookPage ( lpLedger, book, raTakerID.getAccountID (), false, 0, jvMarker, jvResult); } } } } } return jvResult; }
Json::Value doSubscribe (RPC::Context& context) { InfoSub::pointer ispSub; Json::Value jvResult (Json::objectValue); if (!context.infoSub && !context.params.isMember (jss::url)) { // Must be a JSON-RPC call. WriteLog (lsINFO, RPCHandler) << "doSubscribe: RPC subscribe requires a url"; return rpcError (rpcINVALID_PARAMS); } if (context.params.isMember (jss::url)) { if (context.role != Role::ADMIN) return rpcError (rpcNO_PERMISSION); std::string strUrl = context.params[jss::url].asString (); std::string strUsername = context.params.isMember (jss::url_username) ? context.params[jss::url_username].asString () : ""; std::string strPassword = context.params.isMember (jss::url_password) ? context.params[jss::url_password].asString () : ""; // DEPRECATED if (context.params.isMember (jss::username)) strUsername = context.params[jss::username].asString (); // DEPRECATED if (context.params.isMember (jss::password)) strPassword = context.params[jss::password].asString (); ispSub = context.netOps.findRpcSub (strUrl); if (!ispSub) { WriteLog (lsDEBUG, RPCHandler) << "doSubscribe: building: " << strUrl; RPCSub::pointer rspSub = RPCSub::New (getApp ().getOPs (), getApp ().getIOService (), getApp ().getJobQueue (), strUrl, strUsername, strPassword); ispSub = context.netOps.addRpcSub ( strUrl, std::dynamic_pointer_cast<InfoSub> (rspSub)); } else { WriteLog (lsTRACE, RPCHandler) << "doSubscribe: reusing: " << strUrl; if (context.params.isMember (jss::username)) dynamic_cast<RPCSub*> (&*ispSub)->setUsername (strUsername); if (context.params.isMember (jss::password)) dynamic_cast<RPCSub*> (&*ispSub)->setPassword (strPassword); } } else { ispSub = context.infoSub; } if (!context.params.isMember (jss::streams)) { } else if (!context.params[jss::streams].isArray ()) { WriteLog (lsINFO, RPCHandler) << "doSubscribe: streams requires an array."; return rpcError (rpcINVALID_PARAMS); } else { for (auto& it: context.params[jss::streams]) { if (it.isString ()) { std::string streamName = it.asString (); if (streamName == "server") { context.netOps.subServer (ispSub, jvResult, context.role == Role::ADMIN); } else if (streamName == "ledger") { context.netOps.subLedger (ispSub, jvResult); } else if (streamName == "transactions") { context.netOps.subTransactions (ispSub); } else if (streamName == "transactions_proposed" || streamName == "rt_transactions") // DEPRECATED { context.netOps.subRTTransactions (ispSub); } else { jvResult[jss::error] = "unknownStream"; } } else { jvResult[jss::error] = "malformedStream"; } } } auto strAccountsProposed = context.params.isMember (jss::accounts_proposed) ? jss::accounts_proposed : jss::rt_accounts; // DEPRECATED if (!context.params.isMember (strAccountsProposed)) { } else if (!context.params[strAccountsProposed].isArray ()) { return rpcError (rpcINVALID_PARAMS); } else { auto ids = RPC::parseAccountIds (context.params[strAccountsProposed]); if (ids.empty ()) jvResult[jss::error] = "malformedAccount"; else context.netOps.subAccount (ispSub, ids, true); } if (!context.params.isMember (jss::accounts)) { } else if (!context.params[jss::accounts].isArray ()) { return rpcError (rpcINVALID_PARAMS); } else { auto ids = RPC::parseAccountIds (context.params[jss::accounts]); if (ids.empty ()) { jvResult[jss::error] = "malformedAccount"; } else { context.netOps.subAccount (ispSub, ids, false); WriteLog (lsDEBUG, RPCHandler) << "doSubscribe: accounts: " << ids.size (); } } if (!context.params.isMember (jss::books)) { } else if (!context.params[jss::books].isArray ()) { return rpcError (rpcINVALID_PARAMS); } else { for (auto& j: context.params[jss::books]) { if (!j.isObject () || !j.isMember (jss::taker_pays) || !j.isMember (jss::taker_gets) || !j[jss::taker_pays].isObject () || !j[jss::taker_gets].isObject ()) return rpcError (rpcINVALID_PARAMS); Book book; bool bBoth = (j.isMember (jss::both) && j[jss::both].asBool ()) || (j.isMember (jss::both_sides) && j[jss::both_sides].asBool ()); bool bSnapshot = (j.isMember (jss::snapshot) && j[jss::snapshot].asBool ()) || (j.isMember (jss::state_now) && j[jss::state_now].asBool ()); // TODO(tom): both_sides and state_now are apparently deprecated... // where is this documented? Json::Value taker_pays = j[jss::taker_pays]; Json::Value taker_gets = j[jss::taker_gets]; // Parse mandatory currency. if (!taker_pays.isMember (jss::currency) || !to_currency (book.in.currency, taker_pays[jss::currency].asString ())) { WriteLog (lsINFO, RPCHandler) << "Bad taker_pays currency."; return rpcError (rpcSRC_CUR_MALFORMED); } // Parse optional issuer. else if (((taker_pays.isMember (jss::issuer)) && (!taker_pays[jss::issuer].isString () || !to_issuer (book.in.account, taker_pays[jss::issuer].asString ()))) // Don't allow illegal issuers. || (!book.in.currency != !book.in.account) || noAccount() == book.in.account) { WriteLog (lsINFO, RPCHandler) << "Bad taker_pays issuer."; return rpcError (rpcSRC_ISR_MALFORMED); } // Parse mandatory currency. if (!taker_gets.isMember (jss::currency) || !to_currency (book.out.currency, taker_gets[jss::currency].asString ())) { WriteLog (lsINFO, RPCHandler) << "Bad taker_pays currency."; return rpcError (rpcSRC_CUR_MALFORMED); } // Parse optional issuer. else if (((taker_gets.isMember (jss::issuer)) && (!taker_gets[jss::issuer].isString () || !to_issuer (book.out.account, taker_gets[jss::issuer].asString ()))) // Don't allow illegal issuers. || (!book.out.currency != !book.out.account) || noAccount() == book.out.account) { WriteLog (lsINFO, RPCHandler) << "Bad taker_gets issuer."; return rpcError (rpcDST_ISR_MALFORMED); } if (book.in.currency == book.out.currency && book.in.account == book.out.account) { WriteLog (lsINFO, RPCHandler) << "taker_gets same as taker_pays."; return rpcError (rpcBAD_MARKET); } RippleAddress raTakerID; if (!j.isMember (jss::taker)) raTakerID.setAccountID (noAccount()); else if (!raTakerID.setAccountID (j[jss::taker].asString ())) return rpcError (rpcBAD_ISSUER); if (!isConsistent (book)) { WriteLog (lsWARNING, RPCHandler) << "Bad market: " << book; return rpcError (rpcBAD_MARKET); } context.netOps.subBook (ispSub, book); if (bBoth) context.netOps.subBook (ispSub, reversed (book)); if (bSnapshot) { context.loadType = Resource::feeMediumBurdenRPC; auto lpLedger = getApp().getLedgerMaster (). getPublishedLedger (); if (lpLedger) { const Json::Value jvMarker = Json::Value (Json::nullValue); Json::Value jvOffers (Json::objectValue); auto add = [&](Json::StaticString field) { context.netOps.getBookPage (context.role == Role::ADMIN, lpLedger, field == jss::asks ? reversed (book) : book, raTakerID.getAccountID(), false, 0, jvMarker, jvOffers); if (jvResult.isMember (field)) { Json::Value& results (jvResult[field]); for (auto const& e : jvOffers[jss::offers]) results.append (e); } else { jvResult[field] = jvOffers[jss::offers]; } }; if (bBoth) { add (jss::bids); add (jss::asks); } else { add (jss::offers); } } } } } return jvResult; }
int PathRequest::parseJson (const Json::Value& jvParams, bool complete) { int ret = PFR_PJ_NOCHANGE; if (jvParams.isMember ("source_account")) { if (!raSrcAccount.setAccountID (jvParams["source_account"].asString ())) { jvStatus = rpcError (rpcSRC_ACT_MALFORMED); return PFR_PJ_INVALID; } } else if (complete) { jvStatus = rpcError (rpcSRC_ACT_MISSING); return PFR_PJ_INVALID; } if (jvParams.isMember ("destination_account")) { if (!raDstAccount.setAccountID (jvParams["destination_account"].asString ())) { jvStatus = rpcError (rpcDST_ACT_MALFORMED); return PFR_PJ_INVALID; } } else if (complete) { jvStatus = rpcError (rpcDST_ACT_MISSING); return PFR_PJ_INVALID; } if (jvParams.isMember ("destination_amount")) { if (!saDstAmount.bSetJson (jvParams["destination_amount"]) || (saDstAmount.getCurrency ().isZero () && saDstAmount.getIssuer ().isNonZero ()) || (saDstAmount.getCurrency () == badCurrency()) || saDstAmount <= zero) { jvStatus = rpcError (rpcDST_AMT_MALFORMED); return PFR_PJ_INVALID; } } else if (complete) { jvStatus = rpcError (rpcDST_ACT_MISSING); return PFR_PJ_INVALID; } if (jvParams.isMember ("source_currencies")) { const Json::Value& jvSrcCur = jvParams["source_currencies"]; if (!jvSrcCur.isArray ()) { jvStatus = rpcError (rpcSRC_CUR_MALFORMED); return PFR_PJ_INVALID; } sciSourceCurrencies.clear (); for (unsigned i = 0; i < jvSrcCur.size (); ++i) { const Json::Value& jvCur = jvSrcCur[i]; Currency uCur; Account uIss; if (!jvCur.isObject() || !jvCur.isMember ("currency") || !to_currency (uCur, jvCur["currency"].asString ())) { jvStatus = rpcError (rpcSRC_CUR_MALFORMED); return PFR_PJ_INVALID; } if (jvCur.isMember ("issuer") && !to_issuer (uIss, jvCur["issuer"].asString ())) { jvStatus = rpcError (rpcSRC_ISR_MALFORMED); } if (uCur.isZero () && uIss.isNonZero ()) { jvStatus = rpcError (rpcSRC_CUR_MALFORMED); return PFR_PJ_INVALID; } sciSourceCurrencies.insert ({uCur, uIss}); } } if (jvParams.isMember ("id")) jvId = jvParams["id"]; return ret; }
// This interface is deprecated. Json::Value doRipplePathFind (RPC::Context& context) { RPC::LegacyPathFind lpf (context.role == Role::ADMIN); if (!lpf.isOk ()) return rpcError (rpcTOO_BUSY); context.loadType = Resource::feeHighBurdenRPC; RippleAddress raSrc; RippleAddress raDst; STAmount saDstAmount; Ledger::pointer lpLedger; Json::Value jvResult; if (getConfig().RUN_STANDALONE || context.params.isMember(jss::ledger) || context.params.isMember(jss::ledger_index) || context.params.isMember(jss::ledger_hash)) { // The caller specified a ledger jvResult = RPC::lookupLedger ( context.params, lpLedger, context.netOps); if (!lpLedger) return jvResult; } if (!context.params.isMember ("source_account")) { jvResult = rpcError (rpcSRC_ACT_MISSING); } else if (!context.params["source_account"].isString () || !raSrc.setAccountID ( context.params["source_account"].asString ())) { jvResult = rpcError (rpcSRC_ACT_MALFORMED); } else if (!context.params.isMember ("destination_account")) { jvResult = rpcError (rpcDST_ACT_MISSING); } else if (!context.params["destination_account"].isString () || !raDst.setAccountID ( context.params["destination_account"].asString ())) { jvResult = rpcError (rpcDST_ACT_MALFORMED); } else if ( // Parse saDstAmount. !context.params.isMember ("destination_amount") || ! amountFromJsonNoThrow(saDstAmount, context.params["destination_amount"]) || saDstAmount <= zero || (!isXRP(saDstAmount.getCurrency ()) && (!saDstAmount.getIssuer () || noAccount() == saDstAmount.getIssuer ()))) { WriteLog (lsINFO, RPCHandler) << "Bad destination_amount."; jvResult = rpcError (rpcINVALID_PARAMS); } else if ( // Checks on source_currencies. context.params.isMember ("source_currencies") && (!context.params["source_currencies"].isArray () || !context.params["source_currencies"].size ()) // Don't allow empty currencies. ) { WriteLog (lsINFO, RPCHandler) << "Bad source_currencies."; jvResult = rpcError (rpcINVALID_PARAMS); } else { context.loadType = Resource::feeHighBurdenRPC; RippleLineCache::pointer cache; if (lpLedger) { // The caller specified a ledger lpLedger = std::make_shared<Ledger> (std::ref (*lpLedger), false); cache = std::make_shared<RippleLineCache>(lpLedger); } else { // The closed ledger is recent and any nodes made resident // have the best chance to persist lpLedger = context.netOps.getClosedLedger(); cache = getApp().getPathRequests().getLineCache(lpLedger, false); } Json::Value jvSrcCurrencies; if (context.params.isMember ("source_currencies")) { jvSrcCurrencies = context.params["source_currencies"]; } else { auto currencies = accountSourceCurrencies (raSrc, cache, true); jvSrcCurrencies = Json::Value (Json::arrayValue); for (auto const& uCurrency: currencies) { Json::Value jvCurrency (Json::objectValue); jvCurrency["currency"] = to_string(uCurrency); jvSrcCurrencies.append (jvCurrency); } } // Fill in currencies destination will accept Json::Value jvDestCur (Json::arrayValue); // TODO(tom): this could be optimized the same way that // PathRequest::doUpdate() is - if we don't obsolete this code first. auto usDestCurrID = accountDestCurrencies (raDst, cache, true); for (auto const& uCurrency: usDestCurrID) jvDestCur.append (to_string (uCurrency)); jvResult["destination_currencies"] = jvDestCur; jvResult["destination_account"] = raDst.humanAccountID (); Json::Value jvArray (Json::arrayValue); int level = getConfig().PATH_SEARCH_OLD; if ((getConfig().PATH_SEARCH_MAX > level) && !getApp().getFeeTrack().isLoadedLocal()) { ++level; } if (context.params.isMember("search_depth") && context.params["search_depth"].isIntegral()) { int rLev = context.params["search_depth"].asInt (); if ((rLev < level) || (context.role == Role::ADMIN)) level = rLev; } FindPaths fp ( cache, raSrc.getAccountID(), raDst.getAccountID(), saDstAmount, level, 4); // max paths for (unsigned int i = 0; i != jvSrcCurrencies.size (); ++i) { Json::Value jvSource = jvSrcCurrencies[i]; Currency uSrcCurrencyID; Account uSrcIssuerID; if (!jvSource.isObject ()) return rpcError (rpcINVALID_PARAMS); // Parse mandatory currency. if (!jvSource.isMember ("currency") || !to_currency ( 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 () || !to_issuer (uSrcIssuerID, jvSource["issuer"].asString ())) || (uSrcIssuerID.isZero () != uSrcCurrencyID.isZero ()) || (noAccount() == uSrcIssuerID))) { WriteLog (lsINFO, RPCHandler) << "Bad issuer."; return rpcError (rpcSRC_ISR_MALFORMED); } STPathSet spsComputed; if (context.params.isMember("paths")) { Json::Value pathSet = Json::objectValue; pathSet["Paths"] = context.params["paths"]; STParsedJSONObject paths ("pathSet", pathSet); if (paths.object.get() == nullptr) return paths.error; else { spsComputed = paths.object.get()->getFieldPathSet (sfPaths); WriteLog (lsTRACE, RPCHandler) << "ripple_path_find: Paths: " << spsComputed.getJson (0); } } STPath fullLiquidityPath; auto valid = fp.findPathsForIssue ( {uSrcCurrencyID, uSrcIssuerID}, spsComputed, fullLiquidityPath); if (!valid) { WriteLog (lsWARNING, RPCHandler) << "ripple_path_find: No paths found."; } else { auto& issuer = isXRP (uSrcIssuerID) ? isXRP (uSrcCurrencyID) ? // Default to source account. xrpAccount() : Account (raSrc.getAccountID ()) : uSrcIssuerID; // Use specifed issuer. STAmount saMaxAmount ({uSrcCurrencyID, issuer}, 1); saMaxAmount.negate (); LedgerEntrySet lesSandbox (lpLedger, tapNONE); auto rc = path::RippleCalc::rippleCalculate ( lesSandbox, 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. WriteLog (lsWARNING, RPCHandler) << "ripple_path_find:" << " saMaxAmount=" << saMaxAmount << " saDstAmount=" << saDstAmount << " saMaxAmountAct=" << rc.actualAmountIn << " saDstAmountAct=" << rc.actualAmountOut; if (fullLiquidityPath.size() > 0 && (rc.result() == terNO_LINE || rc.result() == tecPATH_PARTIAL)) { WriteLog (lsDEBUG, PathRequest) << "Trying with an extra path element"; spsComputed.push_back (fullLiquidityPath); lesSandbox.clear (); rc = path::RippleCalc::rippleCalculate ( lesSandbox, 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. WriteLog (lsDEBUG, PathRequest) << "Extra path element gives " << transHuman (rc.result ()); } if (rc.result () == tesSUCCESS) { 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.) jvEntry["source_amount"] = rc.actualAmountIn.getJson (0); jvEntry["paths_canonical"] = Json::arrayValue; jvEntry["paths_computed"] = spsComputed.getJson (0); jvArray.append (jvEntry); } else { std::string strToken; std::string strHuman; transResultInfo (rc.result (), strToken, strHuman); WriteLog (lsDEBUG, RPCHandler) << "ripple_path_find: " << 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; }
// FIXME: This leaks RPCSub objects for JSON-RPC. Shouldn't matter for anyone // sane. Json::Value doUnsubscribe (RPC::Context& context) { InfoSub::pointer ispSub; Json::Value jvResult (Json::objectValue); if (!context.infoSub && !context.params.isMember (jss::url)) { // Must be a JSON-RPC call. return rpcError (rpcINVALID_PARAMS); } if (context.params.isMember (jss::url)) { if (context.role != Role::ADMIN) return rpcError (rpcNO_PERMISSION); std::string strUrl = context.params[jss::url].asString (); ispSub = context.netOps.findRpcSub (strUrl); if (!ispSub) return jvResult; } else { ispSub = context.infoSub; } if (context.params.isMember (jss::streams)) { for (auto& it: context.params[jss::streams]) { if (it.isString ()) { std::string streamName = it.asString (); if (streamName == "server") context.netOps.unsubServer (ispSub->getSeq ()); else if (streamName == "ledger") context.netOps.unsubLedger (ispSub->getSeq ()); else if (streamName == "transactions") context.netOps.unsubTransactions (ispSub->getSeq ()); else if (streamName == "transactions_proposed" || streamName == "rt_transactions") // DEPRECATED context.netOps.unsubRTTransactions (ispSub->getSeq ()); else jvResult[jss::error] = "Unknown stream: " + streamName; } else { jvResult[jss::error] = "malformedSteam"; } } } if (context.params.isMember (jss::accounts_proposed) || context.params.isMember (jss::rt_accounts)) { auto accounts = RPC::parseAccountIds ( context.params.isMember (jss::accounts_proposed) ? context.params[jss::accounts_proposed] : context.params[jss::rt_accounts]); // DEPRECATED if (accounts.empty ()) jvResult[jss::error] = "malformedAccount"; else context.netOps.unsubAccount (ispSub, accounts, true); } if (context.params.isMember (jss::accounts)) { auto accounts = RPC::parseAccountIds (context.params[jss::accounts]); if (accounts.empty ()) jvResult[jss::error] = "malformedAccount"; else context.netOps.unsubAccount (ispSub, accounts, false); } if (!context.params.isMember (jss::books)) { } else if (!context.params[jss::books].isArray ()) { return rpcError (rpcINVALID_PARAMS); } else { for (auto& jv: context.params[jss::books]) { if (!jv.isObject () || !jv.isMember (jss::taker_pays) || !jv.isMember (jss::taker_gets) || !jv[jss::taker_pays].isObject () || !jv[jss::taker_gets].isObject ()) return rpcError (rpcINVALID_PARAMS); bool bBoth = (jv.isMember (jss::both) && jv[jss::both].asBool ()) || (jv.isMember (jss::both_sides) && jv[jss::both_sides].asBool ()); // both_sides is deprecated. Json::Value taker_pays = jv[jss::taker_pays]; Json::Value taker_gets = jv[jss::taker_gets]; Book book; // Parse mandatory currency. if (!taker_pays.isMember (jss::currency) || !to_currency ( book.in.currency, taker_pays[jss::currency].asString ())) { WriteLog (lsINFO, RPCHandler) << "Bad taker_pays currency."; return rpcError (rpcSRC_CUR_MALFORMED); } // Parse optional issuer. else if (((taker_pays.isMember (jss::issuer)) && (!taker_pays[jss::issuer].isString () || !to_issuer ( book.in.account, taker_pays[jss::issuer].asString ()))) // Don't allow illegal issuers. || !isConsistent (book.in) || noAccount() == book.in.account) { WriteLog (lsINFO, RPCHandler) << "Bad taker_pays issuer."; return rpcError (rpcSRC_ISR_MALFORMED); } // Parse mandatory currency. if (!taker_gets.isMember (jss::currency) || !to_currency (book.out.currency, taker_gets[jss::currency].asString ())) { WriteLog (lsINFO, RPCHandler) << "Bad taker_pays currency."; return rpcError (rpcSRC_CUR_MALFORMED); } // Parse optional issuer. else if (((taker_gets.isMember (jss::issuer)) && (!taker_gets[jss::issuer].isString () || !to_issuer (book.out.account, taker_gets[jss::issuer].asString ()))) // Don't allow illegal issuers. || !isConsistent (book.out) || noAccount() == book.out.account) { WriteLog (lsINFO, RPCHandler) << "Bad taker_gets issuer."; return rpcError (rpcDST_ISR_MALFORMED); } if (book.in == book.out) { WriteLog (lsINFO, RPCHandler) << "taker_gets same as taker_pays."; return rpcError (rpcBAD_MARKET); } context.netOps.unsubBook (ispSub->getSeq (), book); if (bBoth) context.netOps.unsubBook (ispSub->getSeq (), book); } } return jvResult; }
Json::Value doSubscribe (RPC::Context& context) { InfoSub::pointer ispSub; Json::Value jvResult (Json::objectValue); if (! context.infoSub && ! context.params.isMember(jss::url)) { // Must be a JSON-RPC call. JLOG(context.j.info) << "doSubscribe: RPC subscribe requires a url"; return rpcError (rpcINVALID_PARAMS); } if (context.params.isMember(jss::url)) { if (context.role != Role::ADMIN) return rpcError(rpcNO_PERMISSION); std::string strUrl = context.params[jss::url].asString (); std::string strUsername = context.params.isMember (jss::url_username) ? context.params[jss::url_username].asString () : ""; std::string strPassword = context.params.isMember (jss::url_password) ? context.params[jss::url_password].asString () : ""; // DEPRECATED if (context.params.isMember (jss::username)) strUsername = context.params[jss::username].asString (); // DEPRECATED if (context.params.isMember (jss::password)) strPassword = context.params[jss::password].asString (); ispSub = context.netOps.findRpcSub(strUrl); if (! ispSub) { JLOG (context.j.debug) << "doSubscribe: building: " << strUrl; auto rspSub = make_RPCSub (context.app.getOPs (), context.app.getIOService (), context.app.getJobQueue (), strUrl, strUsername, strPassword, context.app.logs ()); ispSub = context.netOps.addRpcSub ( strUrl, std::dynamic_pointer_cast<InfoSub> (rspSub)); } else { JLOG (context.j.trace) << "doSubscribe: reusing: " << strUrl; if (auto rpcSub = std::dynamic_pointer_cast<RPCSub> (ispSub)) { // Why do we need to check isMember against jss::username and // jss::password here instead of just setting the username and // the password? What about url_username and url_password? if (context.params.isMember (jss::username)) rpcSub->setUsername (strUsername); if (context.params.isMember (jss::password)) rpcSub->setPassword (strPassword); } } } else { ispSub = context.infoSub; } if (context.params.isMember (jss::streams)) { if (! context.params[jss::streams].isArray ()) { JLOG (context.j.info) << "doSubscribe: streams requires an array."; return rpcError (rpcINVALID_PARAMS); } for (auto const& it: context.params[jss::streams]) { if (! it.isString()) return rpcError(rpcSTREAM_MALFORMED); std::string streamName = it.asString (); if (streamName == "server") { context.netOps.subServer (ispSub, jvResult, context.role == Role::ADMIN); } else if (streamName == "ledger") { context.netOps.subLedger (ispSub, jvResult); } else if (streamName == "transactions") { context.netOps.subTransactions (ispSub); } else if (streamName == "transactions_proposed" || streamName == "rt_transactions") // DEPRECATED { context.netOps.subRTTransactions (ispSub); } else if (streamName == "validations") { context.netOps.subValidations (ispSub); } else if (streamName == "peer_status") { if (context.role != Role::ADMIN) return rpcError(rpcNO_PERMISSION); context.netOps.subPeerStatus (ispSub); } else { return rpcError(rpcSTREAM_MALFORMED); } } } auto accountsProposed = context.params.isMember(jss::accounts_proposed) ? jss::accounts_proposed : jss::rt_accounts; // DEPRECATED if (context.params.isMember(accountsProposed)) { if (! context.params[accountsProposed].isArray()) return rpcError(rpcINVALID_PARAMS); auto ids = RPC::parseAccountIds(context.params[accountsProposed]); if (ids.empty()) return rpcError(rpcACT_MALFORMED); context.netOps.subAccount(ispSub, ids, true); } if (context.params.isMember(jss::accounts)) { if (! context.params[jss::accounts].isArray()) return rpcError(rpcINVALID_PARAMS); auto ids = RPC::parseAccountIds(context.params[jss::accounts]); if (ids.empty()) return rpcError(rpcACT_MALFORMED); context.netOps.subAccount(ispSub, ids, false); JLOG(context.j.debug) << "doSubscribe: accounts: " << ids.size(); } if (context.params.isMember(jss::books)) { if (! context.params[jss::books].isArray()) return rpcError (rpcINVALID_PARAMS); for (auto& j: context.params[jss::books]) { if (!j.isObject () || !j.isMember (jss::taker_pays) || !j.isMember (jss::taker_gets) || !j[jss::taker_pays].isObject () || !j[jss::taker_gets].isObject ()) return rpcError (rpcINVALID_PARAMS); Book book; bool bBoth = (j.isMember (jss::both) && j[jss::both].asBool ()) || (j.isMember (jss::both_sides) && j[jss::both_sides].asBool ()); bool bSnapshot = (j.isMember (jss::snapshot) && j[jss::snapshot].asBool ()) || (j.isMember (jss::state_now) && j[jss::state_now].asBool ()); // TODO(tom): both_sides and state_now are apparently deprecated... // where is this documented? Json::Value taker_pays = j[jss::taker_pays]; Json::Value taker_gets = j[jss::taker_gets]; // Parse mandatory currency. if (!taker_pays.isMember (jss::currency) || !to_currency (book.in.currency, taker_pays[jss::currency].asString ())) { JLOG (context.j.info) << "Bad taker_pays currency."; return rpcError (rpcSRC_CUR_MALFORMED); } // Parse optional issuer. else if (((taker_pays.isMember (jss::issuer)) && (!taker_pays[jss::issuer].isString () || !to_issuer (book.in.account, taker_pays[jss::issuer].asString ()))) // Don't allow illegal issuers. || (!book.in.currency != !book.in.account) || noAccount() == book.in.account) { JLOG (context.j.info) << "Bad taker_pays issuer."; return rpcError (rpcSRC_ISR_MALFORMED); } // Parse mandatory currency. if (!taker_gets.isMember (jss::currency) || !to_currency (book.out.currency, taker_gets[jss::currency].asString ())) { JLOG (context.j.info) << "Bad taker_pays currency."; return rpcError (rpcSRC_CUR_MALFORMED); } // Parse optional issuer. else if (((taker_gets.isMember (jss::issuer)) && (!taker_gets[jss::issuer].isString () || !to_issuer (book.out.account, taker_gets[jss::issuer].asString ()))) // Don't allow illegal issuers. || (!book.out.currency != !book.out.account) || noAccount() == book.out.account) { JLOG (context.j.info) << "Bad taker_gets issuer."; return rpcError (rpcDST_ISR_MALFORMED); } if (book.in.currency == book.out.currency && book.in.account == book.out.account) { JLOG (context.j.info) << "taker_gets same as taker_pays."; return rpcError (rpcBAD_MARKET); } boost::optional<AccountID> takerID; if (j.isMember (jss::taker)) { takerID = parseBase58<AccountID>( j[jss::taker].asString()); if (! takerID) return rpcError (rpcBAD_ISSUER); } if (!isConsistent (book)) { JLOG (context.j.warning) << "Bad market: " << book; return rpcError (rpcBAD_MARKET); } context.netOps.subBook (ispSub, book); if (bBoth) context.netOps.subBook (ispSub, book); if (bSnapshot) { context.loadType = Resource::feeMediumBurdenRPC; std::shared_ptr<ReadView const> lpLedger = context.app.getLedgerMaster().getPublishedLedger(); if (lpLedger) { const Json::Value jvMarker = Json::Value (Json::nullValue); Json::Value jvOffers (Json::objectValue); auto add = [&](Json::StaticString field) { context.netOps.getBookPage (isUnlimited (context.role), lpLedger, field == jss::asks ? reversed (book) : book, takerID ? *takerID : noAccount(), false, 0, jvMarker, jvOffers); if (jvResult.isMember (field)) { Json::Value& results (jvResult[field]); for (auto const& e : jvOffers[jss::offers]) results.append (e); } else { jvResult[field] = jvOffers[jss::offers]; } }; if (bBoth) { add (jss::bids); add (jss::asks); } else { add (jss::offers); } } } } } return jvResult; }
Json::Value doUnsubscribe (RPC::Context& context) { InfoSub::pointer ispSub; Json::Value jvResult (Json::objectValue); bool removeUrl {false}; if (! context.infoSub && ! context.params.isMember(jss::url)) { // Must be a JSON-RPC call. return rpcError(rpcINVALID_PARAMS); } if (context.params.isMember(jss::url)) { if (context.role != Role::ADMIN) return rpcError(rpcNO_PERMISSION); std::string strUrl = context.params[jss::url].asString (); ispSub = context.netOps.findRpcSub (strUrl); if (! ispSub) return jvResult; removeUrl = true; } else { ispSub = context.infoSub; } if (context.params.isMember (jss::streams)) { if (! context.params[jss::streams].isArray ()) return rpcError (rpcINVALID_PARAMS); for (auto& it: context.params[jss::streams]) { if (! it.isString()) return rpcError(rpcSTREAM_MALFORMED); std::string streamName = it.asString (); if (streamName == "server") { context.netOps.unsubServer (ispSub->getSeq ()); } else if (streamName == "ledger") { context.netOps.unsubLedger (ispSub->getSeq ()); } else if (streamName == "manifests") { context.netOps.unsubManifests (ispSub->getSeq ()); } else if (streamName == "transactions") { context.netOps.unsubTransactions (ispSub->getSeq ()); } else if (streamName == "transactions_proposed" || streamName == "rt_transactions") // DEPRECATED { context.netOps.unsubRTTransactions (ispSub->getSeq ()); } else if (streamName == "validations") { context.netOps.unsubValidations (ispSub->getSeq ()); } else if (streamName == "peer_status") { context.netOps.unsubPeerStatus (ispSub->getSeq ()); } else { return rpcError(rpcSTREAM_MALFORMED); } } } auto accountsProposed = context.params.isMember(jss::accounts_proposed) ? jss::accounts_proposed : jss::rt_accounts; // DEPRECATED if (context.params.isMember(accountsProposed)) { if (! context.params[accountsProposed].isArray()) return rpcError(rpcINVALID_PARAMS); auto ids = RPC::parseAccountIds(context.params[accountsProposed]); if (ids.empty()) return rpcError(rpcACT_MALFORMED); context.netOps.unsubAccount(ispSub, ids, true); } if (context.params.isMember(jss::accounts)) { if (! context.params[jss::accounts].isArray()) return rpcError(rpcINVALID_PARAMS); auto ids = RPC::parseAccountIds(context.params[jss::accounts]); if (ids.empty()) return rpcError(rpcACT_MALFORMED); context.netOps.unsubAccount(ispSub, ids, false); } if (context.params.isMember(jss::books)) { if (! context.params[jss::books].isArray()) return rpcError(rpcINVALID_PARAMS); for (auto& jv: context.params[jss::books]) { if (! jv.isObject() || ! jv.isMember(jss::taker_pays) || ! jv.isMember(jss::taker_gets) || ! jv[jss::taker_pays].isObject() || ! jv[jss::taker_gets].isObject()) { return rpcError(rpcINVALID_PARAMS); } Json::Value taker_pays = jv[jss::taker_pays]; Json::Value taker_gets = jv[jss::taker_gets]; Book book; // Parse mandatory currency. if (!taker_pays.isMember (jss::currency) || !to_currency ( book.in.currency, taker_pays[jss::currency].asString ())) { JLOG (context.j.info()) << "Bad taker_pays currency."; return rpcError (rpcSRC_CUR_MALFORMED); } // Parse optional issuer. else if (((taker_pays.isMember (jss::issuer)) && (!taker_pays[jss::issuer].isString () || !to_issuer ( book.in.account, taker_pays[jss::issuer].asString ()))) // Don't allow illegal issuers. || !isConsistent (book.in) || noAccount() == book.in.account) { JLOG (context.j.info()) << "Bad taker_pays issuer."; return rpcError (rpcSRC_ISR_MALFORMED); } // Parse mandatory currency. if (!taker_gets.isMember (jss::currency) || !to_currency (book.out.currency, taker_gets[jss::currency].asString ())) { JLOG (context.j.info()) << "Bad taker_gets currency."; return rpcError (rpcDST_AMT_MALFORMED); } // Parse optional issuer. else if (((taker_gets.isMember (jss::issuer)) && (!taker_gets[jss::issuer].isString () || !to_issuer (book.out.account, taker_gets[jss::issuer].asString ()))) // Don't allow illegal issuers. || !isConsistent (book.out) || noAccount() == book.out.account) { JLOG (context.j.info()) << "Bad taker_gets issuer."; return rpcError (rpcDST_ISR_MALFORMED); } if (book.in == book.out) { JLOG (context.j.info()) << "taker_gets same as taker_pays."; return rpcError (rpcBAD_MARKET); } context.netOps.unsubBook (ispSub->getSeq (), book); // both_sides is deprecated. if ((jv.isMember(jss::both) && jv[jss::both].asBool()) || (jv.isMember(jss::both_sides) && jv[jss::both_sides].asBool())) { context.netOps.unsubBook(ispSub->getSeq(), reversed(book)); } } } if (removeUrl) { context.netOps.tryRemoveRpcSub(context.params[jss::url].asString ()); } return jvResult; }
std::pair<bool, Json::Value> ripplePathFind(RippleLineCache::pointer const& cache, RippleAddress const& raSrc, RippleAddress const& raDst, STAmount const& saDstAmount, Ledger::pointer const& lpLedger, Json::Value const& jvSrcCurrencies, boost::optional<Json::Value> const& contextPaths, int const& level) { FindPaths fp( cache, raSrc.getAccountID(), raDst.getAccountID(), saDstAmount, level, 4); // max paths Json::Value jvArray(Json::arrayValue); for (unsigned int i = 0; i != jvSrcCurrencies.size(); ++i) { Json::Value jvSource = jvSrcCurrencies[i]; Currency uSrcCurrencyID; Account uSrcIssuerID; if (!jvSource.isObject()) return std::make_pair(false, rpcError(rpcINVALID_PARAMS)); // Parse mandatory currency. if (!jvSource.isMember(jss::currency) || !to_currency( uSrcCurrencyID, jvSource[jss::currency].asString())) { WriteLog(lsINFO, RPCHandler) << "Bad currency."; return std::make_pair(false, rpcError(rpcSRC_CUR_MALFORMED)); } if (uSrcCurrencyID.isNonZero()) uSrcIssuerID = raSrc.getAccountID(); // Parse optional issuer. if (jvSource.isMember(jss::issuer) && ((!jvSource[jss::issuer].isString() || !to_issuer(uSrcIssuerID, jvSource[jss::issuer].asString())) || (uSrcIssuerID.isZero() != uSrcCurrencyID.isZero()) || (noAccount() == uSrcIssuerID))) { WriteLog(lsINFO, RPCHandler) << "Bad issuer."; return std::make_pair(false, rpcError(rpcSRC_ISR_MALFORMED)); } STPathSet spsComputed; if (contextPaths) { Json::Value pathSet = Json::objectValue; pathSet[jss::Paths] = contextPaths.get(); STParsedJSONObject paths("pathSet", pathSet); if (paths.object.get() == nullptr) return std::make_pair(false, paths.error); else { spsComputed = paths.object.get()->getFieldPathSet(sfPaths); WriteLog(lsTRACE, RPCHandler) << "ripple_path_find: Paths: " << spsComputed.getJson(0); } } STPath fullLiquidityPath; auto valid = fp.findPathsForIssue( { uSrcCurrencyID, uSrcIssuerID }, spsComputed, fullLiquidityPath); if (!valid) { WriteLog(lsWARNING, RPCHandler) << "ripple_path_find: No paths found."; } else { auto& issuer = isXRP(uSrcIssuerID) ? isXRP(uSrcCurrencyID) ? // Default to source account. xrpAccount() : Account(raSrc.getAccountID()) : uSrcIssuerID; // Use specifed issuer. STAmount saMaxAmount({ uSrcCurrencyID, issuer }, 1); saMaxAmount.negate(); LedgerEntrySet lesSandbox(lpLedger, tapNONE); auto rc = path::RippleCalc::rippleCalculate( lesSandbox, 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. WriteLog(lsWARNING, RPCHandler) << "ripple_path_find:" << " saMaxAmount=" << saMaxAmount << " saDstAmount=" << saDstAmount << " saMaxAmountAct=" << rc.actualAmountIn << " saDstAmountAct=" << rc.actualAmountOut; if (fullLiquidityPath.size() > 0 && (rc.result() == terNO_LINE || rc.result() == tecPATH_PARTIAL)) { WriteLog(lsDEBUG, PathRequest) << "Trying with an extra path element"; spsComputed.push_back(fullLiquidityPath); lesSandbox.clear(); rc = path::RippleCalc::rippleCalculate( lesSandbox, 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. WriteLog(lsDEBUG, PathRequest) << "Extra path element gives " << transHuman(rc.result()); } if (rc.result() == tesSUCCESS) { 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.) jvEntry[jss::source_amount] = rc.actualAmountIn.getJson(0); jvEntry[jss::paths_canonical] = Json::arrayValue; jvEntry[jss::paths_computed] = spsComputed.getJson(0); jvArray.append(jvEntry); } else { std::string strToken; std::string strHuman; transResultInfo(rc.result(), strToken, strHuman); WriteLog(lsDEBUG, RPCHandler) << "ripple_path_find: " << strToken << " " << strHuman << " " << spsComputed.getJson(0); } } } return std::make_pair(true, jvArray); }
int PathRequest::parseJson (Json::Value const& jvParams, bool complete) { int ret = PFR_PJ_NOCHANGE; if (jvParams.isMember (jss::source_account)) { raSrcAccount = parseBase58<AccountID>( jvParams[jss::source_account].asString()); if (! raSrcAccount) { jvStatus = rpcError (rpcSRC_ACT_MALFORMED); return PFR_PJ_INVALID; } } else if (complete) { jvStatus = rpcError (rpcSRC_ACT_MISSING); return PFR_PJ_INVALID; } if (jvParams.isMember (jss::destination_account)) { raDstAccount = parseBase58<AccountID>( jvParams[jss::destination_account].asString()); if (! raDstAccount) { jvStatus = rpcError (rpcDST_ACT_MALFORMED); return PFR_PJ_INVALID; } } else if (complete) { jvStatus = rpcError (rpcDST_ACT_MISSING); return PFR_PJ_INVALID; } if (jvParams.isMember (jss::destination_amount)) { if (! amountFromJsonNoThrow ( saDstAmount, jvParams[jss::destination_amount]) || (saDstAmount.getCurrency ().isZero () && saDstAmount.getIssuer ().isNonZero ()) || (saDstAmount.getCurrency () == badCurrency ()) || saDstAmount <= zero) { jvStatus = rpcError (rpcDST_AMT_MALFORMED); return PFR_PJ_INVALID; } } else if (complete) { jvStatus = rpcError (rpcDST_ACT_MISSING); return PFR_PJ_INVALID; } if (jvParams.isMember (jss::source_currencies)) { Json::Value const& jvSrcCur = jvParams[jss::source_currencies]; if (!jvSrcCur.isArray ()) { jvStatus = rpcError (rpcSRC_CUR_MALFORMED); return PFR_PJ_INVALID; } sciSourceCurrencies.clear (); for (unsigned i = 0; i < jvSrcCur.size (); ++i) { Json::Value const& jvCur = jvSrcCur[i]; Currency uCur; AccountID uIss; if (!jvCur.isObject() || !jvCur.isMember (jss::currency) || !to_currency (uCur, jvCur[jss::currency].asString ())) { jvStatus = rpcError (rpcSRC_CUR_MALFORMED); return PFR_PJ_INVALID; } if (jvCur.isMember (jss::issuer) && !to_issuer (uIss, jvCur[jss::issuer].asString ())) { jvStatus = rpcError (rpcSRC_ISR_MALFORMED); } if (uCur.isZero () && uIss.isNonZero ()) { jvStatus = rpcError (rpcSRC_CUR_MALFORMED); return PFR_PJ_INVALID; } if (uCur.isNonZero() && uIss.isZero()) { uIss = *raSrcAccount; } sciSourceCurrencies.insert ({uCur, uIss}); } } if (jvParams.isMember ("id")) jvId = jvParams["id"]; return ret; }