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