TER SetTrust::preflight (PreflightContext const& ctx) { auto const ret = preflight1 (ctx); if (!isTesSuccess (ret)) return ret; auto& tx = ctx.tx; auto& j = ctx.j; std::uint32_t const uTxFlags = tx.getFlags (); if (uTxFlags & tfTrustSetMask) { JLOG(j.trace) << "Malformed transaction: Invalid flags set."; return temINVALID_FLAG; } STAmount const saLimitAmount (tx.getFieldAmount (sfLimitAmount)); if (!isLegalNet (saLimitAmount)) return temBAD_AMOUNT; if (saLimitAmount.native ()) { JLOG(j.trace) << "Malformed transaction: specifies native limit " << saLimitAmount.getFullText (); return temBAD_LIMIT; } if (badCurrency() == saLimitAmount.getCurrency ()) { JLOG(j.trace) << "Malformed transaction: specifies XRP as IOU"; return temBAD_CURRENCY; } if (saLimitAmount < zero) { JLOG(j.trace) << "Malformed transaction: Negative credit limit."; return temBAD_LIMIT; } // Check if destination makes sense. auto const& issuer = saLimitAmount.getIssuer (); if (!issuer || issuer == noAccount()) { JLOG(j.trace) << "Malformed transaction: no destination account."; return temDST_NEEDED; } return preflight2 (ctx); }
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) { 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; }
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; }
// 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; }
TER SetTrust::doApply () { TER terResult = tesSUCCESS; STAmount const saLimitAmount (mTxn.getFieldAmount (sfLimitAmount)); bool const bQualityIn (mTxn.isFieldPresent (sfQualityIn)); bool const bQualityOut (mTxn.isFieldPresent (sfQualityOut)); Currency const currency (saLimitAmount.getCurrency ()); Account uDstAccountID (saLimitAmount.getIssuer ()); // true, iff current is high account. bool const bHigh = mTxnAccountID > uDstAccountID; std::uint32_t uQualityIn (bQualityIn ? mTxn.getFieldU32 (sfQualityIn) : 0); std::uint32_t uQualityOut (bQualityOut ? mTxn.getFieldU32 (sfQualityOut) : 0); if (!saLimitAmount.isLegalNet ()) return temBAD_AMOUNT; if (bQualityIn && QUALITY_ONE == uQualityIn) uQualityIn = 0; if (bQualityOut && QUALITY_ONE == uQualityOut) uQualityOut = 0; std::uint32_t const uTxFlags = mTxn.getFlags (); if (uTxFlags & tfTrustSetMask) { m_journal.trace << "Malformed transaction: Invalid flags set."; return temINVALID_FLAG; } bool const bSetAuth = (uTxFlags & tfSetfAuth); bool const bSetNoRipple = (uTxFlags & tfSetNoRipple); bool const bClearNoRipple = (uTxFlags & tfClearNoRipple); bool const bSetFreeze = (uTxFlags & tfSetFreeze); bool const bClearFreeze = (uTxFlags & tfClearFreeze); if (bSetAuth && !(mTxnAccount->getFieldU32 (sfFlags) & lsfRequireAuth)) { m_journal.trace << "Retry: Auth not required."; return tefNO_AUTH_REQUIRED; } if (saLimitAmount.isNative ()) { m_journal.trace << "Malformed transaction: Native credit limit: " << saLimitAmount.getFullText (); return temBAD_LIMIT; } if (saLimitAmount < zero) { m_journal.trace << "Malformed transaction: Negative credit limit."; return temBAD_LIMIT; } // Check if destination makes sense. if (!uDstAccountID || uDstAccountID == noAccount()) { m_journal.trace << "Malformed transaction: Destination account not specified."; return temDST_NEEDED; } if (mTxnAccountID == uDstAccountID) { SLE::pointer selDelete ( mEngine->entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex ( mTxnAccountID, uDstAccountID, currency))); if (selDelete) { m_journal.warning << "Clearing redundant line."; return mEngine->view ().trustDelete ( selDelete, mTxnAccountID, uDstAccountID); } else { m_journal.trace << "Malformed transaction: Can not extend credit to self."; return temDST_IS_SRC; } } SLE::pointer sleDst (mEngine->entryCache ( ltACCOUNT_ROOT, Ledger::getAccountRootIndex (uDstAccountID))); if (!sleDst) { m_journal.trace << "Delay transaction: Destination account does not exist."; return tecNO_DST; } std::uint32_t const uOwnerCount (mTxnAccount->getFieldU32 (sfOwnerCount)); // The reserve required to create the line. std::uint64_t const uReserveCreate = (uOwnerCount < 2) ? 0 : mEngine->getLedger ()->getReserve (uOwnerCount + 1); STAmount saLimitAllow = saLimitAmount; saLimitAllow.setIssuer (mTxnAccountID); SLE::pointer sleRippleState (mEngine->entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex (mTxnAccountID, uDstAccountID, currency))); if (sleRippleState) { STAmount saLowBalance; STAmount saLowLimit; STAmount saHighBalance; STAmount saHighLimit; std::uint32_t uLowQualityIn; std::uint32_t uLowQualityOut; std::uint32_t uHighQualityIn; std::uint32_t uHighQualityOut; auto const& uLowAccountID = !bHigh ? mTxnAccountID : uDstAccountID; auto const& uHighAccountID = bHigh ? mTxnAccountID : uDstAccountID; SLE::ref sleLowAccount = !bHigh ? mTxnAccount : sleDst; SLE::ref sleHighAccount = bHigh ? mTxnAccount : sleDst; // // Balances // saLowBalance = sleRippleState->getFieldAmount (sfBalance); saHighBalance = -saLowBalance; // // Limits // sleRippleState->setFieldAmount (!bHigh ? sfLowLimit : sfHighLimit, saLimitAllow); saLowLimit = !bHigh ? saLimitAllow : sleRippleState->getFieldAmount (sfLowLimit); saHighLimit = bHigh ? saLimitAllow : sleRippleState->getFieldAmount (sfHighLimit); // // Quality in // if (!bQualityIn) { // Not setting. Just get it. uLowQualityIn = sleRippleState->getFieldU32 (sfLowQualityIn); uHighQualityIn = sleRippleState->getFieldU32 (sfHighQualityIn); } else if (uQualityIn) { // Setting. sleRippleState->setFieldU32 (!bHigh ? sfLowQualityIn : sfHighQualityIn, uQualityIn); uLowQualityIn = !bHigh ? uQualityIn : sleRippleState->getFieldU32 (sfLowQualityIn); uHighQualityIn = bHigh ? uQualityIn : sleRippleState->getFieldU32 (sfHighQualityIn); } else { // Clearing. sleRippleState->makeFieldAbsent (!bHigh ? sfLowQualityIn : sfHighQualityIn); uLowQualityIn = !bHigh ? 0 : sleRippleState->getFieldU32 (sfLowQualityIn); uHighQualityIn = bHigh ? 0 : sleRippleState->getFieldU32 (sfHighQualityIn); } if (QUALITY_ONE == uLowQualityIn) uLowQualityIn = 0; if (QUALITY_ONE == uHighQualityIn) uHighQualityIn = 0; // // Quality out // if (!bQualityOut) { // Not setting. Just get it. uLowQualityOut = sleRippleState->getFieldU32 (sfLowQualityOut); uHighQualityOut = sleRippleState->getFieldU32 (sfHighQualityOut); } else if (uQualityOut) { // Setting. sleRippleState->setFieldU32 (!bHigh ? sfLowQualityOut : sfHighQualityOut, uQualityOut); uLowQualityOut = !bHigh ? uQualityOut : sleRippleState->getFieldU32 (sfLowQualityOut); uHighQualityOut = bHigh ? uQualityOut : sleRippleState->getFieldU32 (sfHighQualityOut); } else { // Clearing. sleRippleState->makeFieldAbsent (!bHigh ? sfLowQualityOut : sfHighQualityOut); uLowQualityOut = !bHigh ? 0 : sleRippleState->getFieldU32 (sfLowQualityOut); uHighQualityOut = bHigh ? 0 : sleRippleState->getFieldU32 (sfHighQualityOut); } std::uint32_t const uFlagsIn (sleRippleState->getFieldU32 (sfFlags)); std::uint32_t uFlagsOut (uFlagsIn); if (bSetNoRipple && !bClearNoRipple && (bHigh ? saHighBalance : saLowBalance) >= zero) { uFlagsOut |= (bHigh ? lsfHighNoRipple : lsfLowNoRipple); } else if (bClearNoRipple && !bSetNoRipple) { uFlagsOut &= ~(bHigh ? lsfHighNoRipple : lsfLowNoRipple); } if (bSetFreeze && !bClearFreeze && !mTxnAccount->isFlag (lsfNoFreeze)) { uFlagsOut |= (bHigh ? lsfHighFreeze : lsfLowFreeze); } else if (bClearFreeze && !bSetFreeze) { uFlagsOut &= ~(bHigh ? lsfHighFreeze : lsfLowFreeze); } if (QUALITY_ONE == uLowQualityOut) uLowQualityOut = 0; if (QUALITY_ONE == uHighQualityOut) uHighQualityOut = 0; bool const bLowReserveSet = uLowQualityIn || uLowQualityOut || (uFlagsOut & lsfLowNoRipple) || (uFlagsOut & lsfLowFreeze) || saLowLimit || saLowBalance > zero; bool const bLowReserveClear = !bLowReserveSet; bool const bHighReserveSet = uHighQualityIn || uHighQualityOut || (uFlagsOut & lsfHighNoRipple) || (uFlagsOut & lsfHighFreeze) || saHighLimit || saHighBalance > zero; bool const bHighReserveClear = !bHighReserveSet; bool const bDefault = bLowReserveClear && bHighReserveClear; bool const bLowReserved = (uFlagsIn & lsfLowReserve); bool const bHighReserved = (uFlagsIn & lsfHighReserve); bool bReserveIncrease = false; if (bSetAuth) { uFlagsOut |= (bHigh ? lsfHighAuth : lsfLowAuth); } if (bLowReserveSet && !bLowReserved) { // Set reserve for low account. mEngine->view ().ownerCountAdjust (uLowAccountID, 1, sleLowAccount); uFlagsOut |= lsfLowReserve; if (!bHigh) bReserveIncrease = true; } if (bLowReserveClear && bLowReserved) { // Clear reserve for low account. mEngine->view ().ownerCountAdjust (uLowAccountID, -1, sleLowAccount); uFlagsOut &= ~lsfLowReserve; } if (bHighReserveSet && !bHighReserved) { // Set reserve for high account. mEngine->view ().ownerCountAdjust (uHighAccountID, 1, sleHighAccount); uFlagsOut |= lsfHighReserve; if (bHigh) bReserveIncrease = true; } if (bHighReserveClear && bHighReserved) { // Clear reserve for high account. mEngine->view ().ownerCountAdjust (uHighAccountID, -1, sleHighAccount); uFlagsOut &= ~lsfHighReserve; } if (uFlagsIn != uFlagsOut) sleRippleState->setFieldU32 (sfFlags, uFlagsOut); if (bDefault || badCurrency() == currency) { // Delete. terResult = mEngine->view ().trustDelete (sleRippleState, uLowAccountID, uHighAccountID); } else if (bReserveIncrease && mPriorBalance.getNValue () < uReserveCreate) // Reserve is not scaled by load. { m_journal.trace << "Delay transaction: Insufficent reserve to add trust line."; // Another transaction could provide XRP to the account and then // this transaction would succeed. terResult = tecINSUF_RESERVE_LINE; } else { mEngine->entryModify (sleRippleState); m_journal.trace << "Modify ripple line"; } } // Line does not exist. else if (!saLimitAmount // Setting default limit. && (!bQualityIn || !uQualityIn) // Not setting quality in or setting default quality in. && (!bQualityOut || !uQualityOut)) // Not setting quality out or setting default quality out. { m_journal.trace << "Redundant: Setting non-existent ripple line to defaults."; return tecNO_LINE_REDUNDANT; } else if (mPriorBalance.getNValue () < uReserveCreate) // Reserve is not scaled by load. { m_journal.trace << "Delay transaction: Line does not exist. Insufficent reserve to create line."; // Another transaction could create the account and then this transaction would succeed. terResult = tecNO_LINE_INSUF_RESERVE; } else if (badCurrency() == currency) { terResult = temBAD_CURRENCY; } else { // Zero balance in currency. STAmount saBalance ({currency, noAccount()}); uint256 index (Ledger::getRippleStateIndex ( mTxnAccountID, uDstAccountID, currency)); m_journal.trace << "doTrustSet: Creating ripple line: " << to_string (index); // Create a new ripple line. terResult = mEngine->view ().trustCreate ( bHigh, mTxnAccountID, uDstAccountID, index, mTxnAccount, bSetAuth, bSetNoRipple && !bClearNoRipple, bSetFreeze && !bClearFreeze, saBalance, saLimitAllow, // Limit for who is being charged. uQualityIn, uQualityOut); } return terResult; }
TER SetTrust::doApply () { TER terResult = tesSUCCESS; STAmount const saLimitAmount (tx().getFieldAmount (sfLimitAmount)); bool const bQualityIn (tx().isFieldPresent (sfQualityIn)); bool const bQualityOut (tx().isFieldPresent (sfQualityOut)); Currency const currency (saLimitAmount.getCurrency ()); AccountID uDstAccountID (saLimitAmount.getIssuer ()); // true, iff current is high account. bool const bHigh = account_ > uDstAccountID; auto const sle = view().peek( keylet::account(account_)); std::uint32_t const uOwnerCount = sle->getFieldU32 (sfOwnerCount); // The reserve required to create the line. Note that we allow up to // two trust lines without requiring a reserve because being able to // exchange currencies is a powerful Ripple feature. // // This is also a security feature: if you're a gateway and you want to // be able to let someone use your services, you would otherwise have to // give them enough XRP to cover the incremental reserve for their trust // line. If they had no intention of using your services, they could use // the XRP for their own purposes. So we make it possible for gateways // to fund accounts in a way where there's no incentive to trick them // into creating an account you have no intention of using. XRPAmount const reserveCreate ((uOwnerCount < 2) ? XRPAmount (zero) : view().fees().accountReserve(uOwnerCount + 1)); std::uint32_t uQualityIn (bQualityIn ? tx().getFieldU32 (sfQualityIn) : 0); std::uint32_t uQualityOut (bQualityOut ? tx().getFieldU32 (sfQualityOut) : 0); if (bQualityOut && QUALITY_ONE == uQualityOut) uQualityOut = 0; std::uint32_t const uTxFlags = tx().getFlags (); bool const bSetAuth = (uTxFlags & tfSetfAuth); bool const bSetNoRipple = (uTxFlags & tfSetNoRipple); bool const bClearNoRipple = (uTxFlags & tfClearNoRipple); bool const bSetFreeze = (uTxFlags & tfSetFreeze); bool const bClearFreeze = (uTxFlags & tfClearFreeze); auto viewJ = ctx_.app.journal ("View"); if (bSetAuth && !(sle->getFieldU32 (sfFlags) & lsfRequireAuth)) { j_.trace << "Retry: Auth not required."; return tefNO_AUTH_REQUIRED; } if (account_ == uDstAccountID) { // The only purpose here is to allow a mistakenly created // trust line to oneself to be deleted. If no such trust // lines exist now, why not remove this code and simply // return an error? SLE::pointer sleDelete = view().peek ( keylet::line(account_, uDstAccountID, currency)); if (sleDelete) { j_.warning << "Clearing redundant line."; return trustDelete (view(), sleDelete, account_, uDstAccountID, viewJ); } else { j_.trace << "Malformed transaction: Can not extend credit to self."; return temDST_IS_SRC; } } SLE::pointer sleDst = view().peek (keylet::account(uDstAccountID)); if (!sleDst) { j_.trace << "Delay transaction: Destination account does not exist."; return tecNO_DST; } STAmount saLimitAllow = saLimitAmount; saLimitAllow.setIssuer (account_); SLE::pointer sleRippleState = view().peek ( keylet::line(account_, uDstAccountID, currency)); if (sleRippleState) { STAmount saLowBalance; STAmount saLowLimit; STAmount saHighBalance; STAmount saHighLimit; std::uint32_t uLowQualityIn; std::uint32_t uLowQualityOut; std::uint32_t uHighQualityIn; std::uint32_t uHighQualityOut; auto const& uLowAccountID = !bHigh ? account_ : uDstAccountID; auto const& uHighAccountID = bHigh ? account_ : uDstAccountID; SLE::ref sleLowAccount = !bHigh ? sle : sleDst; SLE::ref sleHighAccount = bHigh ? sle : sleDst; // // Balances // saLowBalance = sleRippleState->getFieldAmount (sfBalance); saHighBalance = -saLowBalance; // // Limits // sleRippleState->setFieldAmount (!bHigh ? sfLowLimit : sfHighLimit, saLimitAllow); saLowLimit = !bHigh ? saLimitAllow : sleRippleState->getFieldAmount (sfLowLimit); saHighLimit = bHigh ? saLimitAllow : sleRippleState->getFieldAmount (sfHighLimit); // // Quality in // if (!bQualityIn) { // Not setting. Just get it. uLowQualityIn = sleRippleState->getFieldU32 (sfLowQualityIn); uHighQualityIn = sleRippleState->getFieldU32 (sfHighQualityIn); } else if (uQualityIn) { // Setting. sleRippleState->setFieldU32 (!bHigh ? sfLowQualityIn : sfHighQualityIn, uQualityIn); uLowQualityIn = !bHigh ? uQualityIn : sleRippleState->getFieldU32 (sfLowQualityIn); uHighQualityIn = bHigh ? uQualityIn : sleRippleState->getFieldU32 (sfHighQualityIn); } else { // Clearing. sleRippleState->makeFieldAbsent (!bHigh ? sfLowQualityIn : sfHighQualityIn); uLowQualityIn = !bHigh ? 0 : sleRippleState->getFieldU32 (sfLowQualityIn); uHighQualityIn = bHigh ? 0 : sleRippleState->getFieldU32 (sfHighQualityIn); } if (QUALITY_ONE == uLowQualityIn) uLowQualityIn = 0; if (QUALITY_ONE == uHighQualityIn) uHighQualityIn = 0; // // Quality out // if (!bQualityOut) { // Not setting. Just get it. uLowQualityOut = sleRippleState->getFieldU32 (sfLowQualityOut); uHighQualityOut = sleRippleState->getFieldU32 (sfHighQualityOut); } else if (uQualityOut) { // Setting. sleRippleState->setFieldU32 (!bHigh ? sfLowQualityOut : sfHighQualityOut, uQualityOut); uLowQualityOut = !bHigh ? uQualityOut : sleRippleState->getFieldU32 (sfLowQualityOut); uHighQualityOut = bHigh ? uQualityOut : sleRippleState->getFieldU32 (sfHighQualityOut); } else { // Clearing. sleRippleState->makeFieldAbsent (!bHigh ? sfLowQualityOut : sfHighQualityOut); uLowQualityOut = !bHigh ? 0 : sleRippleState->getFieldU32 (sfLowQualityOut); uHighQualityOut = bHigh ? 0 : sleRippleState->getFieldU32 (sfHighQualityOut); } std::uint32_t const uFlagsIn (sleRippleState->getFieldU32 (sfFlags)); std::uint32_t uFlagsOut (uFlagsIn); if (bSetNoRipple && !bClearNoRipple && (bHigh ? saHighBalance : saLowBalance) >= zero) { uFlagsOut |= (bHigh ? lsfHighNoRipple : lsfLowNoRipple); } else if (bClearNoRipple && !bSetNoRipple) { uFlagsOut &= ~(bHigh ? lsfHighNoRipple : lsfLowNoRipple); } if (bSetFreeze && !bClearFreeze && !sle->isFlag (lsfNoFreeze)) { uFlagsOut |= (bHigh ? lsfHighFreeze : lsfLowFreeze); } else if (bClearFreeze && !bSetFreeze) { uFlagsOut &= ~(bHigh ? lsfHighFreeze : lsfLowFreeze); } if (QUALITY_ONE == uLowQualityOut) uLowQualityOut = 0; if (QUALITY_ONE == uHighQualityOut) uHighQualityOut = 0; bool const bLowDefRipple = sleLowAccount->getFlags() & lsfDefaultRipple; bool const bHighDefRipple = sleHighAccount->getFlags() & lsfDefaultRipple; bool const bLowReserveSet = uLowQualityIn || uLowQualityOut || ((uFlagsOut & lsfLowNoRipple) == 0) != bLowDefRipple || (uFlagsOut & lsfLowFreeze) || saLowLimit || saLowBalance > zero; bool const bLowReserveClear = !bLowReserveSet; bool const bHighReserveSet = uHighQualityIn || uHighQualityOut || ((uFlagsOut & lsfHighNoRipple) == 0) != bHighDefRipple || (uFlagsOut & lsfHighFreeze) || saHighLimit || saHighBalance > zero; bool const bHighReserveClear = !bHighReserveSet; bool const bDefault = bLowReserveClear && bHighReserveClear; bool const bLowReserved = (uFlagsIn & lsfLowReserve); bool const bHighReserved = (uFlagsIn & lsfHighReserve); bool bReserveIncrease = false; if (bSetAuth) { uFlagsOut |= (bHigh ? lsfHighAuth : lsfLowAuth); } if (bLowReserveSet && !bLowReserved) { // Set reserve for low account. adjustOwnerCount(view(), sleLowAccount, 1, viewJ); uFlagsOut |= lsfLowReserve; if (!bHigh) bReserveIncrease = true; } if (bLowReserveClear && bLowReserved) { // Clear reserve for low account. adjustOwnerCount(view(), sleLowAccount, -1, viewJ); uFlagsOut &= ~lsfLowReserve; } if (bHighReserveSet && !bHighReserved) { // Set reserve for high account. adjustOwnerCount(view(), sleHighAccount, 1, viewJ); uFlagsOut |= lsfHighReserve; if (bHigh) bReserveIncrease = true; } if (bHighReserveClear && bHighReserved) { // Clear reserve for high account. adjustOwnerCount(view(), sleHighAccount, -1, viewJ); uFlagsOut &= ~lsfHighReserve; } if (uFlagsIn != uFlagsOut) sleRippleState->setFieldU32 (sfFlags, uFlagsOut); if (bDefault || badCurrency() == currency) { // Delete. terResult = trustDelete (view(), sleRippleState, uLowAccountID, uHighAccountID, viewJ); } // Reserve is not scaled by load. else if (bReserveIncrease && mPriorBalance < reserveCreate) { j_.trace << "Delay transaction: Insufficent reserve to add trust line."; // Another transaction could provide XRP to the account and then // this transaction would succeed. terResult = tecINSUF_RESERVE_LINE; } else { view().update (sleRippleState); j_.trace << "Modify ripple line"; } } // Line does not exist. else if (! saLimitAmount && // Setting default limit. (! bQualityIn || ! uQualityIn) && // Not setting quality in or setting default quality in. (! bQualityOut || ! uQualityOut) && // Not setting quality out or setting default quality out. (! ((view().flags() & tapENABLE_TESTING) || view().rules().enabled(featureTrustSetAuth, ctx_.config.features)) || ! bSetAuth)) { j_.trace << "Redundant: Setting non-existent ripple line to defaults."; return tecNO_LINE_REDUNDANT; } else if (mPriorBalance < reserveCreate) // Reserve is not scaled by load. { j_.trace << "Delay transaction: Line does not exist. Insufficent reserve to create line."; // Another transaction could create the account and then this transaction would succeed. terResult = tecNO_LINE_INSUF_RESERVE; } else { // Zero balance in currency. STAmount saBalance ({currency, noAccount()}); uint256 index (getRippleStateIndex ( account_, uDstAccountID, currency)); j_.trace << "doTrustSet: Creating ripple line: " << to_string (index); // Create a new ripple line. terResult = trustCreate (view(), bHigh, account_, uDstAccountID, index, sle, bSetAuth, bSetNoRipple && !bClearNoRipple, bSetFreeze && !bClearFreeze, saBalance, saLimitAllow, // Limit for who is being charged. uQualityIn, uQualityOut, viewJ); } return terResult; }
// 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 (jss::source_account)) { jvResult = rpcError (rpcSRC_ACT_MISSING); } else if (!context.params[jss::source_account].isString () || !raSrc.setAccountID ( context.params[jss::source_account].asString ())) { jvResult = rpcError (rpcSRC_ACT_MALFORMED); } else if (!context.params.isMember (jss::destination_account)) { jvResult = rpcError (rpcDST_ACT_MISSING); } else if (!context.params[jss::destination_account].isString () || !raDst.setAccountID ( context.params[jss::destination_account].asString ())) { jvResult = rpcError (rpcDST_ACT_MALFORMED); } else if ( // Parse saDstAmount. !context.params.isMember (jss::destination_amount) || ! amountFromJsonNoThrow(saDstAmount, context.params[jss::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 (jss::source_currencies) && (!context.params[jss::source_currencies].isArray () || !context.params[jss::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 (jss::source_currencies)) { jvSrcCurrencies = context.params[jss::source_currencies]; } else { jvSrcCurrencies = buildSrcCurrencies(raSrc, cache); } // 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[jss::destination_currencies] = jvDestCur; jvResult[jss::destination_account] = raDst.humanAccountID (); int level = getConfig().PATH_SEARCH_OLD; if ((getConfig().PATH_SEARCH_MAX > level) && !getApp().getFeeTrack().isLoadedLocal()) { ++level; } if (context.params.isMember(jss::search_depth) && context.params[jss::search_depth].isIntegral()) { int rLev = context.params[jss::search_depth].asInt (); if ((rLev < level) || (context.role == Role::ADMIN)) level = rLev; } auto contextPaths = context.params.isMember(jss::paths) ? boost::optional<Json::Value>(context.params[jss::paths]) : boost::optional<Json::Value>(boost::none); auto pathFindResult = ripplePathFind(cache, raSrc, raDst, saDstAmount, lpLedger, jvSrcCurrencies, contextPaths, level); if (!pathFindResult.first) return pathFindResult.second; // Each alternative differs by source currency. jvResult[jss::alternatives] = pathFindResult.second; } WriteLog (lsDEBUG, RPCHandler) << boost::str (boost::format ("ripple_path_find< %s") % jvResult); 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); }