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  if (context.app.config().reporting())
132  context.netOps.subServer(
133  ispSub, jvResult, context.role == Role::ADMIN);
134  }
135  else if (streamName == "ledger")
136  {
137  context.netOps.subLedger(ispSub, jvResult);
138  }
139  else if (streamName == "book_changes")
140  {
141  context.netOps.subBookChanges(ispSub);
142  }
143  else if (streamName == "manifests")
144  {
145  context.netOps.subManifests(ispSub);
146  }
147  else if (streamName == "transactions")
148  {
149  context.netOps.subTransactions(ispSub);
150  }
151  else if (
152  streamName == "transactions_proposed" ||
153  streamName == "rt_transactions") // DEPRECATED
154  {
155  context.netOps.subRTTransactions(ispSub);
156  }
157  else if (streamName == "validations")
158  {
159  context.netOps.subValidations(ispSub);
160  }
161  else if (streamName == "peer_status")
162  {
163  if (context.app.config().reporting())
165  if (context.role != Role::ADMIN)
166  return rpcError(rpcNO_PERMISSION);
167  context.netOps.subPeerStatus(ispSub);
168  }
169  else if (streamName == "consensus")
170  {
171  if (context.app.config().reporting())
173  context.netOps.subConsensus(ispSub);
174  }
175  else
176  {
178  }
179  }
180  }
181 
182  auto accountsProposed = context.params.isMember(jss::accounts_proposed)
183  ? jss::accounts_proposed
184  : jss::rt_accounts; // DEPRECATED
185  if (context.params.isMember(accountsProposed))
186  {
187  if (!context.params[accountsProposed].isArray())
188  return rpcError(rpcINVALID_PARAMS);
189 
190  auto ids = RPC::parseAccountIds(context.params[accountsProposed]);
191  if (ids.empty())
192  return rpcError(rpcACT_MALFORMED);
193  context.netOps.subAccount(ispSub, ids, true);
194  }
195 
196  if (context.params.isMember(jss::accounts))
197  {
198  if (!context.params[jss::accounts].isArray())
199  return rpcError(rpcINVALID_PARAMS);
200 
201  auto ids = RPC::parseAccountIds(context.params[jss::accounts]);
202  if (ids.empty())
203  return rpcError(rpcACT_MALFORMED);
204  context.netOps.subAccount(ispSub, ids, false);
205  JLOG(context.j.debug()) << "doSubscribe: accounts: " << ids.size();
206  }
207 
208  if (context.params.isMember(jss::account_history_tx_stream))
209  {
210  if (!context.app.config().useTxTables())
211  return rpcError(rpcNOT_ENABLED);
212 
214  auto const& req = context.params[jss::account_history_tx_stream];
215  if (!req.isMember(jss::account) || !req[jss::account].isString())
216  return rpcError(rpcINVALID_PARAMS);
217 
218  auto const id = parseBase58<AccountID>(req[jss::account].asString());
219  if (!id)
220  return rpcError(rpcINVALID_PARAMS);
221 
222  if (auto result = context.netOps.subAccountHistory(ispSub, *id);
223  result != rpcSUCCESS)
224  {
225  return rpcError(result);
226  }
227 
228  jvResult[jss::warning] =
229  "account_history_tx_stream is an experimental feature and likely "
230  "to be removed in the future";
231  JLOG(context.j.debug())
232  << "doSubscribe: account_history_tx_stream: " << toBase58(*id);
233  }
234 
235  if (context.params.isMember(jss::books))
236  {
237  if (!context.params[jss::books].isArray())
238  return rpcError(rpcINVALID_PARAMS);
239 
240  for (auto& j : context.params[jss::books])
241  {
242  if (!j.isObject() || !j.isMember(jss::taker_pays) ||
243  !j.isMember(jss::taker_gets) ||
244  !j[jss::taker_pays].isObjectOrNull() ||
245  !j[jss::taker_gets].isObjectOrNull())
246  return rpcError(rpcINVALID_PARAMS);
247 
248  Book book;
249  Json::Value taker_pays = j[jss::taker_pays];
250  Json::Value taker_gets = j[jss::taker_gets];
251 
252  // Parse mandatory currency.
253  if (!taker_pays.isMember(jss::currency) ||
254  !to_currency(
255  book.in.currency, taker_pays[jss::currency].asString()))
256  {
257  JLOG(context.j.info()) << "Bad taker_pays currency.";
259  }
260 
261  // Parse optional issuer.
262  if (((taker_pays.isMember(jss::issuer)) &&
263  (!taker_pays[jss::issuer].isString() ||
264  !to_issuer(
265  book.in.account, taker_pays[jss::issuer].asString())))
266  // Don't allow illegal issuers.
267  || (!book.in.currency != !book.in.account) ||
268  noAccount() == book.in.account)
269  {
270  JLOG(context.j.info()) << "Bad taker_pays issuer.";
272  }
273 
274  // Parse mandatory currency.
275  if (!taker_gets.isMember(jss::currency) ||
276  !to_currency(
277  book.out.currency, taker_gets[jss::currency].asString()))
278  {
279  JLOG(context.j.info()) << "Bad taker_gets currency.";
281  }
282 
283  // Parse optional issuer.
284  if (((taker_gets.isMember(jss::issuer)) &&
285  (!taker_gets[jss::issuer].isString() ||
286  !to_issuer(
287  book.out.account, taker_gets[jss::issuer].asString())))
288  // Don't allow illegal issuers.
289  || (!book.out.currency != !book.out.account) ||
290  noAccount() == book.out.account)
291  {
292  JLOG(context.j.info()) << "Bad taker_gets issuer.";
294  }
295 
296  if (book.in.currency == book.out.currency &&
297  book.in.account == book.out.account)
298  {
299  JLOG(context.j.info()) << "taker_gets same as taker_pays.";
300  return rpcError(rpcBAD_MARKET);
301  }
302 
303  std::optional<AccountID> takerID;
304 
305  if (j.isMember(jss::taker))
306  {
307  takerID = parseBase58<AccountID>(j[jss::taker].asString());
308  if (!takerID)
309  return rpcError(rpcBAD_ISSUER);
310  }
311 
312  if (!isConsistent(book))
313  {
314  JLOG(context.j.warn()) << "Bad market: " << book;
315  return rpcError(rpcBAD_MARKET);
316  }
317 
318  context.netOps.subBook(ispSub, book);
319 
320  // both_sides is deprecated.
321  bool const both =
322  (j.isMember(jss::both) && j[jss::both].asBool()) ||
323  (j.isMember(jss::both_sides) && j[jss::both_sides].asBool());
324 
325  if (both)
326  context.netOps.subBook(ispSub, reversed(book));
327 
328  // state_now is deprecated.
329  if ((j.isMember(jss::snapshot) && j[jss::snapshot].asBool()) ||
330  (j.isMember(jss::state_now) && j[jss::state_now].asBool()))
331  {
335  if (lpLedger)
336  {
337  const Json::Value jvMarker = Json::Value(Json::nullValue);
338  Json::Value jvOffers(Json::objectValue);
339 
340  auto add = [&](Json::StaticString field) {
341  context.netOps.getBookPage(
342  lpLedger,
343  field == jss::asks ? reversed(book) : book,
344  takerID ? *takerID : noAccount(),
345  false,
347  jvMarker,
348  jvOffers);
349 
350  if (jvResult.isMember(field))
351  {
352  Json::Value& results(jvResult[field]);
353  for (auto const& e : jvOffers[jss::offers])
354  results.append(e);
355  }
356  else
357  {
358  jvResult[field] = jvOffers[jss::offers];
359  }
360  };
361 
362  if (both)
363  {
364  add(jss::bids);
365  add(jss::asks);
366  }
367  else
368  {
369  add(jss::offers);
370  }
371  }
372  }
373  }
374  }
375 
376  return jvResult;
377 }
378 
379 } // 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:80
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:1694
std::string
STL class.
std::shared_ptr
STL class.
ripple::rpcINVALID_PARAMS
@ rpcINVALID_PARAMS
Definition: ErrorCodes.h:84
Json::Value::isString
bool isString() const
Definition: json_value.cpp:1009
ripple::rpcError
Json::Value rpcError(int iError)
Definition: RPCErr.cpp:29
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:37
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
ripple::toBase58
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:104
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::subBookChanges
virtual bool subBookChanges(ref ispListener)=0
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::InfoSub::Source::subAccountHistory
virtual error_code_i subAccountHistory(ref ispListener, AccountID const &account)=0
subscribe an account's new transactions and retrieve the account's historical transactions
ripple::rpcREPORTING_UNSUPPORTED
@ rpcREPORTING_UNSUPPORTED
Definition: ErrorCodes.h:141
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
ripple::rpcSUCCESS
@ rpcSUCCESS
Definition: ErrorCodes.h:44
Json::Value::append
Value & append(const Value &value)
Append value to array at the end.
Definition: json_value.cpp:882
ripple::Config::reporting
bool reporting() const
Definition: Config.h:337
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::Application::config
virtual Config & config()=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::Config::useTxTables
bool useTxTables() const
Definition: Config.h:343
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.
ripple::rpcNOT_ENABLED
@ rpcNOT_ENABLED
Definition: ErrorCodes.h:59
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::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:215
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
std::optional
beast::Journal::debug
Stream debug() const
Definition: Journal.h:315
ripple::Book
Specifies an order book.
Definition: Book.h:33
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:711
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:250
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:175
ripple::Book::in
Issue in
Definition: Book.h:36
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:182
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