rippled
Subscribe.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2012-2014 Ripple Labs Inc.
5 
6  Permission to use, copy, modify, and/or distribute this software for any
7  purpose with or without fee is hereby granted, provided that the above
8  copyright notice and this permission notice appear in all copies.
9 
10  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 //==============================================================================
19 
20 #include <ripple/app/ledger/LedgerMaster.h>
21 #include <ripple/app/main/Application.h>
22 #include <ripple/app/misc/NetworkOPs.h>
23 #include <ripple/basics/Log.h>
24 #include <ripple/ledger/ReadView.h>
25 #include <ripple/net/RPCErr.h>
26 #include <ripple/net/RPCSub.h>
27 #include <ripple/protocol/ErrorCodes.h>
28 #include <ripple/protocol/jss.h>
29 #include <ripple/resource/Fees.h>
30 #include <ripple/rpc/Context.h>
31 #include <ripple/rpc/Role.h>
32 #include <ripple/rpc/impl/RPCHelpers.h>
33 
34 namespace ripple {
35 
38 {
39  InfoSub::pointer ispSub;
41 
42  if (!context.infoSub && !context.params.isMember(jss::url))
43  {
44  // Must be a JSON-RPC call.
45  JLOG(context.j.info()) << "doSubscribe: RPC subscribe requires a url";
47  }
48 
49  if (context.params.isMember(jss::url))
50  {
51  if (context.role != Role::ADMIN)
52  return rpcError(rpcNO_PERMISSION);
53 
54  std::string strUrl = context.params[jss::url].asString();
55  std::string strUsername = context.params.isMember(jss::url_username)
56  ? context.params[jss::url_username].asString()
57  : "";
58  std::string strPassword = context.params.isMember(jss::url_password)
59  ? context.params[jss::url_password].asString()
60  : "";
61 
62  // DEPRECATED
63  if (context.params.isMember(jss::username))
64  strUsername = context.params[jss::username].asString();
65 
66  // DEPRECATED
67  if (context.params.isMember(jss::password))
68  strPassword = context.params[jss::password].asString();
69 
70  ispSub = context.netOps.findRpcSub(strUrl);
71  if (!ispSub)
72  {
73  JLOG(context.j.debug()) << "doSubscribe: building: " << strUrl;
74  try
75  {
76  auto rspSub = make_RPCSub(
77  context.app.getOPs(),
78  context.app.getIOService(),
79  context.app.getJobQueue(),
80  strUrl,
81  strUsername,
82  strPassword,
83  context.app.logs());
84  ispSub = context.netOps.addRpcSub(
85  strUrl, std::dynamic_pointer_cast<InfoSub>(rspSub));
86  }
87  catch (std::runtime_error& ex)
88  {
89  return RPC::make_param_error(ex.what());
90  }
91  }
92  else
93  {
94  JLOG(context.j.trace()) << "doSubscribe: reusing: " << strUrl;
95 
96  if (auto rpcSub = std::dynamic_pointer_cast<RPCSub>(ispSub))
97  {
98  // Why do we need to check isMember against jss::username and
99  // jss::password here instead of just setting the username and
100  // the password? What about url_username and url_password?
101  if (context.params.isMember(jss::username))
102  rpcSub->setUsername(strUsername);
103 
104  if (context.params.isMember(jss::password))
105  rpcSub->setPassword(strPassword);
106  }
107  }
108  }
109  else
110  {
111  ispSub = context.infoSub;
112  }
113 
114  if (context.params.isMember(jss::streams))
115  {
116  if (!context.params[jss::streams].isArray())
117  {
118  JLOG(context.j.info()) << "doSubscribe: streams requires an array.";
119  return rpcError(rpcINVALID_PARAMS);
120  }
121 
122  for (auto const& it : context.params[jss::streams])
123  {
124  if (!it.isString())
126 
127  std::string streamName = it.asString();
128  if (streamName == "server")
129  {
130  context.netOps.subServer(
131  ispSub, jvResult, context.role == Role::ADMIN);
132  }
133  else if (streamName == "ledger")
134  {
135  context.netOps.subLedger(ispSub, jvResult);
136  }
137  else if (streamName == "manifests")
138  {
139  context.netOps.subManifests(ispSub);
140  }
141  else if (streamName == "transactions")
142  {
143  context.netOps.subTransactions(ispSub);
144  }
145  else if (
146  streamName == "transactions_proposed" ||
147  streamName == "rt_transactions") // DEPRECATED
148  {
149  context.netOps.subRTTransactions(ispSub);
150  }
151  else if (streamName == "validations")
152  {
153  context.netOps.subValidations(ispSub);
154  }
155  else if (streamName == "peer_status")
156  {
157  if (context.role != Role::ADMIN)
158  return rpcError(rpcNO_PERMISSION);
159  context.netOps.subPeerStatus(ispSub);
160  }
161  else if (streamName == "consensus")
162  {
163  context.netOps.subConsensus(ispSub);
164  }
165  else
166  {
168  }
169  }
170  }
171 
172  auto accountsProposed = context.params.isMember(jss::accounts_proposed)
173  ? jss::accounts_proposed
174  : jss::rt_accounts; // DEPRECATED
175  if (context.params.isMember(accountsProposed))
176  {
177  if (!context.params[accountsProposed].isArray())
178  return rpcError(rpcINVALID_PARAMS);
179 
180  auto ids = RPC::parseAccountIds(context.params[accountsProposed]);
181  if (ids.empty())
182  return rpcError(rpcACT_MALFORMED);
183  context.netOps.subAccount(ispSub, ids, true);
184  }
185 
186  if (context.params.isMember(jss::accounts))
187  {
188  if (!context.params[jss::accounts].isArray())
189  return rpcError(rpcINVALID_PARAMS);
190 
191  auto ids = RPC::parseAccountIds(context.params[jss::accounts]);
192  if (ids.empty())
193  return rpcError(rpcACT_MALFORMED);
194  context.netOps.subAccount(ispSub, ids, false);
195  JLOG(context.j.debug()) << "doSubscribe: accounts: " << ids.size();
196  }
197 
198  if (context.params.isMember(jss::books))
199  {
200  if (!context.params[jss::books].isArray())
201  return rpcError(rpcINVALID_PARAMS);
202 
203  for (auto& j : context.params[jss::books])
204  {
205  if (!j.isObject() || !j.isMember(jss::taker_pays) ||
206  !j.isMember(jss::taker_gets) ||
207  !j[jss::taker_pays].isObjectOrNull() ||
208  !j[jss::taker_gets].isObjectOrNull())
209  return rpcError(rpcINVALID_PARAMS);
210 
211  Book book;
212  Json::Value taker_pays = j[jss::taker_pays];
213  Json::Value taker_gets = j[jss::taker_gets];
214 
215  // Parse mandatory currency.
216  if (!taker_pays.isMember(jss::currency) ||
217  !to_currency(
218  book.in.currency, taker_pays[jss::currency].asString()))
219  {
220  JLOG(context.j.info()) << "Bad taker_pays currency.";
222  }
223 
224  // Parse optional issuer.
225  if (((taker_pays.isMember(jss::issuer)) &&
226  (!taker_pays[jss::issuer].isString() ||
227  !to_issuer(
228  book.in.account, taker_pays[jss::issuer].asString())))
229  // Don't allow illegal issuers.
230  || (!book.in.currency != !book.in.account) ||
231  noAccount() == book.in.account)
232  {
233  JLOG(context.j.info()) << "Bad taker_pays issuer.";
235  }
236 
237  // Parse mandatory currency.
238  if (!taker_gets.isMember(jss::currency) ||
239  !to_currency(
240  book.out.currency, taker_gets[jss::currency].asString()))
241  {
242  JLOG(context.j.info()) << "Bad taker_gets currency.";
244  }
245 
246  // Parse optional issuer.
247  if (((taker_gets.isMember(jss::issuer)) &&
248  (!taker_gets[jss::issuer].isString() ||
249  !to_issuer(
250  book.out.account, taker_gets[jss::issuer].asString())))
251  // Don't allow illegal issuers.
252  || (!book.out.currency != !book.out.account) ||
253  noAccount() == book.out.account)
254  {
255  JLOG(context.j.info()) << "Bad taker_gets issuer.";
257  }
258 
259  if (book.in.currency == book.out.currency &&
260  book.in.account == book.out.account)
261  {
262  JLOG(context.j.info()) << "taker_gets same as taker_pays.";
263  return rpcError(rpcBAD_MARKET);
264  }
265 
266  boost::optional<AccountID> takerID;
267 
268  if (j.isMember(jss::taker))
269  {
270  takerID = parseBase58<AccountID>(j[jss::taker].asString());
271  if (!takerID)
272  return rpcError(rpcBAD_ISSUER);
273  }
274 
275  if (!isConsistent(book))
276  {
277  JLOG(context.j.warn()) << "Bad market: " << book;
278  return rpcError(rpcBAD_MARKET);
279  }
280 
281  context.netOps.subBook(ispSub, book);
282 
283  // both_sides is deprecated.
284  bool const both =
285  (j.isMember(jss::both) && j[jss::both].asBool()) ||
286  (j.isMember(jss::both_sides) && j[jss::both_sides].asBool());
287 
288  if (both)
289  context.netOps.subBook(ispSub, reversed(book));
290 
291  // state_now is deprecated.
292  if ((j.isMember(jss::snapshot) && j[jss::snapshot].asBool()) ||
293  (j.isMember(jss::state_now) && j[jss::state_now].asBool()))
294  {
298  if (lpLedger)
299  {
300  const Json::Value jvMarker = Json::Value(Json::nullValue);
301  Json::Value jvOffers(Json::objectValue);
302 
303  auto add = [&](Json::StaticString field) {
304  context.netOps.getBookPage(
305  lpLedger,
306  field == jss::asks ? reversed(book) : book,
307  takerID ? *takerID : noAccount(),
308  false,
310  jvMarker,
311  jvOffers);
312 
313  if (jvResult.isMember(field))
314  {
315  Json::Value& results(jvResult[field]);
316  for (auto const& e : jvOffers[jss::offers])
317  results.append(e);
318  }
319  else
320  {
321  jvResult[field] = jvOffers[jss::offers];
322  }
323  };
324 
325  if (both)
326  {
327  add(jss::bids);
328  add(jss::asks);
329  }
330  else
331  {
332  add(jss::offers);
333  }
334  }
335  }
336  }
337  }
338 
339  return jvResult;
340 }
341 
342 } // namespace ripple
ripple::to_currency
bool to_currency(Currency &currency, std::string const &code)
Tries to convert a string to a Currency, returns true on success.
Definition: UintTypes.cpp:83
ripple::RPC::Context::infoSub
InfoSub::pointer infoSub
Definition: Context.h:49
ripple::RPC::JsonContext
Definition: Context.h:53
ripple::rpcDST_AMT_MALFORMED
@ rpcDST_AMT_MALFORMED
Definition: ErrorCodes.h:106
ripple::LedgerMaster::getPublishedLedger
std::shared_ptr< ReadView const > getPublishedLedger()
Definition: LedgerMaster.cpp:1549
std::string
STL class.
std::shared_ptr< InfoSub >
ripple::rpcINVALID_PARAMS
@ rpcINVALID_PARAMS
Definition: ErrorCodes.h:84
Json::Value::isString
bool isString() const
Definition: json_value.cpp:1009
ripple::isConsistent
bool isConsistent(Book const &book)
Definition: Book.cpp:25
beast::Journal::trace
Stream trace() const
Severity stream access functions.
Definition: Journal.h:309
ripple::Book::out
Issue out
Definition: Book.h:36
ripple::Resource::feeMediumBurdenRPC
const Charge feeMediumBurdenRPC
ripple::RPC::Context::loadType
Resource::Charge & loadType
Definition: Context.h:43
ripple::InfoSub::Source::subValidations
virtual bool subValidations(ref ispListener)=0
ripple::Issue::currency
Currency currency
Definition: Issue.h:37
beast::Journal::warn
Stream warn() const
Definition: Journal.h:327
ripple::RPC::Tuning::LimitRange::rdefault
unsigned int rdefault
Definition: rpc/impl/Tuning.h:33
ripple::InfoSub::Source::subRTTransactions
virtual bool subRTTransactions(ref ispListener)=0
ripple::InfoSub::Source::subTransactions
virtual bool subTransactions(ref ispListener)=0
ripple::RPC::Context::role
Role role
Definition: Context.h:47
ripple::RPC::Tuning::bookOffers
static constexpr LimitRange bookOffers
Limits for the book_offers command.
Definition: rpc/impl/Tuning.h:49
ripple::Application::getOPs
virtual NetworkOPs & getOPs()=0
ripple::InfoSub::Source::subBook
virtual bool subBook(ref ispListener, Book const &)=0
ripple::InfoSub::Source::findRpcSub
virtual pointer findRpcSub(std::string const &strUrl)=0
ripple::RPC::Context::j
const beast::Journal j
Definition: Context.h:41
ripple::InfoSub::Source::subManifests
virtual bool subManifests(ref ispListener)=0
Json::Value::append
Value & append(const Value &value)
Append value to array at the end.
Definition: json_value.cpp:882
ripple::Role::ADMIN
@ ADMIN
Json::objectValue
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:43
ripple::Application::getLedgerMaster
virtual LedgerMaster & getLedgerMaster()=0
ripple::InfoSub::Source::subLedger
virtual bool subLedger(ref ispListener, Json::Value &jvResult)=0
ripple::NetworkOPs::getBookPage
virtual void getBookPage(std::shared_ptr< ReadView const > &lpLedger, Book const &book, AccountID const &uTakerID, bool const bProof, unsigned int iLimit, Json::Value const &jvMarker, Json::Value &jvResult)=0
ripple::reversed
Book reversed(Book const &book)
Definition: Book.cpp:45
ripple::Application::getJobQueue
virtual JobQueue & getJobQueue()=0
ripple::rpcSRC_ISR_MALFORMED
@ rpcSRC_ISR_MALFORMED
Definition: ErrorCodes.h:125
ripple::InfoSub::Source::addRpcSub
virtual pointer addRpcSub(std::string const &strUrl, ref rspEntry)=0
ripple::RPC::Context::app
Application & app
Definition: Context.h:42
beast::Journal::info
Stream info() const
Definition: Journal.h:321
ripple::rpcSTREAM_MALFORMED
@ rpcSTREAM_MALFORMED
Definition: ErrorCodes.h:126
ripple::Application::logs
virtual Logs & logs()=0
std::runtime_error
STL class.
Json::Value::isMember
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:932
ripple::rpcDST_ISR_MALFORMED
@ rpcDST_ISR_MALFORMED
Definition: ErrorCodes.h:108
ripple::rpcError
Json::Value rpcError(int iError, Json::Value jvResult)
Definition: RPCErr.cpp:29
ripple::make_RPCSub
std::shared_ptr< RPCSub > make_RPCSub(InfoSub::Source &source, boost::asio::io_service &io_service, JobQueue &jobQueue, std::string const &strUrl, std::string const &strUsername, std::string const &strPassword, Logs &logs)
Definition: RPCSub.cpp:213
ripple::RPC::Context::netOps
NetworkOPs & netOps
Definition: Context.h:44
ripple::Application::getIOService
virtual boost::asio::io_service & getIOService()=0
Json::Value::isArray
bool isArray() const
Definition: json_value.cpp:1015
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::rpcACT_MALFORMED
@ rpcACT_MALFORMED
Definition: ErrorCodes.h:90
ripple::rpcNO_PERMISSION
@ rpcNO_PERMISSION
Definition: ErrorCodes.h:53
Json::StaticString
Lightweight wrapper to tag static string.
Definition: json_value.h:60
Json::nullValue
@ nullValue
'null' value
Definition: json_value.h:36
beast::Journal::debug
Stream debug() const
Definition: Journal.h:315
ripple::Book
Specifies an order book.
Definition: Book.h:32
ripple::rpcBAD_MARKET
@ rpcBAD_MARKET
Definition: ErrorCodes.h:97
ripple::rpcSRC_CUR_MALFORMED
@ rpcSRC_CUR_MALFORMED
Definition: ErrorCodes.h:124
ripple::InfoSub::Source::subConsensus
virtual bool subConsensus(ref ispListener)=0
ripple::RPC::parseAccountIds
hash_set< AccountID > parseAccountIds(Json::Value const &jvArray)
Definition: RPCHelpers.cpp:523
ripple::InfoSub::Source::subPeerStatus
virtual bool subPeerStatus(ref ispListener)=0
ripple::RPC::make_param_error
Json::Value make_param_error(std::string const &message)
Returns a new json object that indicates invalid parameters.
Definition: ErrorCodes.h:226
ripple::rpcBAD_ISSUER
@ rpcBAD_ISSUER
Definition: ErrorCodes.h:96
ripple::RPC::JsonContext::params
Json::Value params
Definition: Context.h:64
ripple::InfoSub::Source::subServer
virtual bool subServer(ref ispListener, Json::Value &jvResult, bool admin)=0
ripple::InfoSub::Source::subAccount
virtual void subAccount(ref ispListener, hash_set< AccountID > const &vnaAccountIDs, bool realTime)=0
ripple::noAccount
AccountID const & noAccount()
A placeholder for empty accounts.
Definition: AccountID.cpp:97
ripple::Book::in
Issue in
Definition: Book.h:35
ripple::Issue::account
AccountID account
Definition: Issue.h:38
std::runtime_error::what
T what(T... args)
Json::Value
Represents a JSON value.
Definition: json_value.h:145
ripple::to_issuer
bool to_issuer(AccountID &, std::string const &)
Convert hex or base58 string to AccountID.
Definition: AccountID.cpp:104
Json::Value::asString
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:469
ripple::doSubscribe
Json::Value doSubscribe(RPC::JsonContext &)
Definition: Subscribe.cpp:37