rippled
Loading...
Searching...
No Matches
Subscribe.cpp
1#include <xrpld/app/ledger/LedgerMaster.h>
2#include <xrpld/app/main/Application.h>
3#include <xrpld/app/misc/NetworkOPs.h>
4#include <xrpld/rpc/Context.h>
5#include <xrpld/rpc/RPCSub.h>
6#include <xrpld/rpc/Role.h>
7#include <xrpld/rpc/detail/RPCHelpers.h>
8
9#include <xrpl/basics/Log.h>
10#include <xrpl/ledger/ReadView.h>
11#include <xrpl/protocol/ErrorCodes.h>
12#include <xrpl/protocol/RPCErr.h>
13#include <xrpl/protocol/jss.h>
14#include <xrpl/resource/Fees.h>
15
16namespace ripple {
17
20{
21 InfoSub::pointer ispSub;
23
24 if (!context.infoSub && !context.params.isMember(jss::url))
25 {
26 // Must be a JSON-RPC call.
27 JLOG(context.j.info()) << "doSubscribe: RPC subscribe requires a url";
29 }
30
31 if (context.params.isMember(jss::url))
32 {
33 if (context.role != Role::ADMIN)
35
36 std::string strUrl = context.params[jss::url].asString();
37 std::string strUsername = context.params.isMember(jss::url_username)
38 ? context.params[jss::url_username].asString()
39 : "";
40 std::string strPassword = context.params.isMember(jss::url_password)
41 ? context.params[jss::url_password].asString()
42 : "";
43
44 // DEPRECATED
45 if (context.params.isMember(jss::username))
46 strUsername = context.params[jss::username].asString();
47
48 // DEPRECATED
49 if (context.params.isMember(jss::password))
50 strPassword = context.params[jss::password].asString();
51
52 ispSub = context.netOps.findRpcSub(strUrl);
53 if (!ispSub)
54 {
55 JLOG(context.j.debug()) << "doSubscribe: building: " << strUrl;
56 try
57 {
58 auto rspSub = make_RPCSub(
59 context.app.getOPs(),
60 context.app.getIOContext(),
61 context.app.getJobQueue(),
62 strUrl,
63 strUsername,
64 strPassword,
65 context.app.logs());
66 ispSub = context.netOps.addRpcSub(
68 }
69 catch (std::runtime_error& ex)
70 {
71 return RPC::make_param_error(ex.what());
72 }
73 }
74 else
75 {
76 JLOG(context.j.trace()) << "doSubscribe: reusing: " << strUrl;
77
78 if (auto rpcSub = std::dynamic_pointer_cast<RPCSub>(ispSub))
79 {
80 // Why do we need to check isMember against jss::username and
81 // jss::password here instead of just setting the username and
82 // the password? What about url_username and url_password?
83 if (context.params.isMember(jss::username))
84 rpcSub->setUsername(strUsername);
85
86 if (context.params.isMember(jss::password))
87 rpcSub->setPassword(strPassword);
88 }
89 }
90 }
91 else
92 {
93 ispSub = context.infoSub;
94 }
95 ispSub->setApiVersion(context.apiVersion);
96
97 if (context.params.isMember(jss::streams))
98 {
99 if (!context.params[jss::streams].isArray())
100 {
101 JLOG(context.j.info()) << "doSubscribe: streams requires an array.";
103 }
104
105 for (auto const& it : context.params[jss::streams])
106 {
107 if (!it.isString())
109
110 std::string streamName = it.asString();
111 if (streamName == "server")
112 {
113 context.netOps.subServer(
114 ispSub, jvResult, context.role == Role::ADMIN);
115 }
116 else if (streamName == "ledger")
117 {
118 context.netOps.subLedger(ispSub, jvResult);
119 }
120 else if (streamName == "book_changes")
121 {
122 context.netOps.subBookChanges(ispSub);
123 }
124 else if (streamName == "manifests")
125 {
126 context.netOps.subManifests(ispSub);
127 }
128 else if (streamName == "transactions")
129 {
130 context.netOps.subTransactions(ispSub);
131 }
132 else if (
133 streamName == "transactions_proposed" ||
134 streamName == "rt_transactions") // DEPRECATED
135 {
136 context.netOps.subRTTransactions(ispSub);
137 }
138 else if (streamName == "validations")
139 {
140 context.netOps.subValidations(ispSub);
141 }
142 else if (streamName == "peer_status")
143 {
144 if (context.role != Role::ADMIN)
146 context.netOps.subPeerStatus(ispSub);
147 }
148 else if (streamName == "consensus")
149 {
150 context.netOps.subConsensus(ispSub);
151 }
152 else
153 {
155 }
156 }
157 }
158
159 auto accountsProposed = context.params.isMember(jss::accounts_proposed)
160 ? jss::accounts_proposed
161 : jss::rt_accounts; // DEPRECATED
162 if (context.params.isMember(accountsProposed))
163 {
164 if (!context.params[accountsProposed].isArray())
166
167 auto ids = RPC::parseAccountIds(context.params[accountsProposed]);
168 if (ids.empty())
170 context.netOps.subAccount(ispSub, ids, true);
171 }
172
173 if (context.params.isMember(jss::accounts))
174 {
175 if (!context.params[jss::accounts].isArray())
177
178 auto ids = RPC::parseAccountIds(context.params[jss::accounts]);
179 if (ids.empty())
181 context.netOps.subAccount(ispSub, ids, false);
182 JLOG(context.j.debug()) << "doSubscribe: accounts: " << ids.size();
183 }
184
185 if (context.params.isMember(jss::account_history_tx_stream))
186 {
187 if (!context.app.config().useTxTables())
188 return rpcError(rpcNOT_ENABLED);
189
191 auto const& req = context.params[jss::account_history_tx_stream];
192 if (!req.isMember(jss::account) || !req[jss::account].isString())
194
195 auto const id = parseBase58<AccountID>(req[jss::account].asString());
196 if (!id)
198
199 if (auto result = context.netOps.subAccountHistory(ispSub, *id);
200 result != rpcSUCCESS)
201 {
202 return rpcError(result);
203 }
204
205 jvResult[jss::warning] =
206 "account_history_tx_stream is an experimental feature and likely "
207 "to be removed in the future";
208 JLOG(context.j.debug())
209 << "doSubscribe: account_history_tx_stream: " << toBase58(*id);
210 }
211
212 if (context.params.isMember(jss::books))
213 {
214 if (!context.params[jss::books].isArray())
216
217 for (auto& j : context.params[jss::books])
218 {
219 if (!j.isObject() || !j.isMember(jss::taker_pays) ||
220 !j.isMember(jss::taker_gets) ||
221 !j[jss::taker_pays].isObjectOrNull() ||
222 !j[jss::taker_gets].isObjectOrNull())
224
225 Book book;
226 Json::Value taker_pays = j[jss::taker_pays];
227 Json::Value taker_gets = j[jss::taker_gets];
228
229 // Parse mandatory currency.
230 if (!taker_pays.isMember(jss::currency) ||
232 book.in.currency, taker_pays[jss::currency].asString()))
233 {
234 JLOG(context.j.info()) << "Bad taker_pays currency.";
236 }
237
238 // Parse optional issuer.
239 if (((taker_pays.isMember(jss::issuer)) &&
240 (!taker_pays[jss::issuer].isString() ||
241 !to_issuer(
242 book.in.account, taker_pays[jss::issuer].asString())))
243 // Don't allow illegal issuers.
244 || (!book.in.currency != !book.in.account) ||
245 noAccount() == book.in.account)
246 {
247 JLOG(context.j.info()) << "Bad taker_pays issuer.";
249 }
250
251 // Parse mandatory currency.
252 if (!taker_gets.isMember(jss::currency) ||
254 book.out.currency, taker_gets[jss::currency].asString()))
255 {
256 JLOG(context.j.info()) << "Bad taker_gets currency.";
258 }
259
260 // Parse optional issuer.
261 if (((taker_gets.isMember(jss::issuer)) &&
262 (!taker_gets[jss::issuer].isString() ||
263 !to_issuer(
264 book.out.account, taker_gets[jss::issuer].asString())))
265 // Don't allow illegal issuers.
266 || (!book.out.currency != !book.out.account) ||
267 noAccount() == book.out.account)
268 {
269 JLOG(context.j.info()) << "Bad taker_gets issuer.";
271 }
272
273 if (book.in.currency == book.out.currency &&
274 book.in.account == book.out.account)
275 {
276 JLOG(context.j.info()) << "taker_gets same as taker_pays.";
277 return rpcError(rpcBAD_MARKET);
278 }
279
281
282 if (j.isMember(jss::taker))
283 {
284 takerID = parseBase58<AccountID>(j[jss::taker].asString());
285 if (!takerID)
286 return rpcError(rpcBAD_ISSUER);
287 }
288
289 if (j.isMember(jss::domain))
290 {
291 uint256 domain;
292 if (!j[jss::domain].isString() ||
293 !domain.parseHex(j[jss::domain].asString()))
294 {
296 }
297 else
298 {
299 book.domain = domain;
300 }
301 }
302
303 if (!isConsistent(book))
304 {
305 JLOG(context.j.warn()) << "Bad market: " << book;
306 return rpcError(rpcBAD_MARKET);
307 }
308
309 context.netOps.subBook(ispSub, book);
310
311 // both_sides is deprecated.
312 bool const both =
313 (j.isMember(jss::both) && j[jss::both].asBool()) ||
314 (j.isMember(jss::both_sides) && j[jss::both_sides].asBool());
315
316 if (both)
317 context.netOps.subBook(ispSub, reversed(book));
318
319 // state_now is deprecated.
320 if ((j.isMember(jss::snapshot) && j[jss::snapshot].asBool()) ||
321 (j.isMember(jss::state_now) && j[jss::state_now].asBool()))
322 {
326 if (lpLedger)
327 {
328 Json::Value const jvMarker = Json::Value(Json::nullValue);
330
331 auto add = [&](Json::StaticString field) {
332 context.netOps.getBookPage(
333 lpLedger,
334 field == jss::asks ? reversed(book) : book,
335 takerID ? *takerID : noAccount(),
336 false,
338 jvMarker,
339 jvOffers);
340
341 if (jvResult.isMember(field))
342 {
343 Json::Value& results(jvResult[field]);
344 for (auto const& e : jvOffers[jss::offers])
345 results.append(e);
346 }
347 else
348 {
349 jvResult[field] = jvOffers[jss::offers];
350 }
351 };
352
353 if (both)
354 {
355 add(jss::bids);
356 add(jss::asks);
357 }
358 else
359 {
360 add(jss::offers);
361 }
362 }
363 }
364 }
365 }
366
367 return jvResult;
368}
369
370} // namespace ripple
Lightweight wrapper to tag static string.
Definition json_value.h:45
Represents a JSON value.
Definition json_value.h:131
bool isArray() const
Value & append(Value const &value)
Append value to array at the end.
bool isString() const
std::string asString() const
Returns the unquoted string value.
bool isMember(char const *key) const
Return true if the object has a member named key.
Stream debug() const
Definition Journal.h:309
Stream info() const
Definition Journal.h:315
Stream trace() const
Severity stream access functions.
Definition Journal.h:303
Stream warn() const
Definition Journal.h:321
virtual Config & config()=0
virtual JobQueue & getJobQueue()=0
virtual NetworkOPs & getOPs()=0
virtual LedgerMaster & getLedgerMaster()=0
virtual Logs & logs()=0
virtual boost::asio::io_context & getIOContext()=0
Specifies an order book.
Definition Book.h:17
Issue in
Definition Book.h:19
Issue out
Definition Book.h:20
std::optional< uint256 > domain
Definition Book.h:21
bool useTxTables() const
Definition Config.h:323
virtual error_code_i subAccountHistory(ref ispListener, AccountID const &account)=0
subscribe an account's new transactions and retrieve the account's historical transactions
virtual bool subValidations(ref ispListener)=0
virtual bool subBook(ref ispListener, Book const &)=0
virtual bool subServer(ref ispListener, Json::Value &jvResult, bool admin)=0
virtual bool subBookChanges(ref ispListener)=0
virtual bool subConsensus(ref ispListener)=0
virtual bool subManifests(ref ispListener)=0
virtual pointer findRpcSub(std::string const &strUrl)=0
virtual bool subPeerStatus(ref ispListener)=0
virtual bool subTransactions(ref ispListener)=0
virtual bool subLedger(ref ispListener, Json::Value &jvResult)=0
virtual pointer addRpcSub(std::string const &strUrl, ref rspEntry)=0
virtual void subAccount(ref ispListener, hash_set< AccountID > const &vnaAccountIDs, bool realTime)=0
virtual bool subRTTransactions(ref ispListener)=0
void setApiVersion(unsigned int apiVersion)
Definition InfoSub.cpp:120
AccountID account
Definition Issue.h:17
Currency currency
Definition Issue.h:16
std::shared_ptr< ReadView const > getPublishedLedger()
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
constexpr bool parseHex(std::string_view sv)
Parse a hex string into a base_uint.
Definition base_uint.h:484
T is_same_v
@ nullValue
'null' value
Definition json_value.h:20
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:27
static LimitRange constexpr bookOffers
Limits for the book_offers command.
Json::Value make_param_error(std::string const &message)
Returns a new json object that indicates invalid parameters.
Definition ErrorCodes.h:252
hash_set< AccountID > parseAccountIds(Json::Value const &jvArray)
Charge const feeMediumBurdenRPC
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
AccountID const & noAccount()
A placeholder for empty accounts.
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
bool isConsistent(Book const &book)
Definition Book.cpp:10
bool to_issuer(AccountID &, std::string const &)
Convert hex or base58 string to AccountID.
@ rpcBAD_ISSUER
Definition ErrorCodes.h:77
@ rpcACT_MALFORMED
Definition ErrorCodes.h:71
@ rpcBAD_MARKET
Definition ErrorCodes.h:78
@ rpcSUCCESS
Definition ErrorCodes.h:25
@ rpcDOMAIN_MALFORMED
Definition ErrorCodes.h:139
@ rpcDST_ISR_MALFORMED
Definition ErrorCodes.h:89
@ rpcINVALID_PARAMS
Definition ErrorCodes.h:65
@ rpcSTREAM_MALFORMED
Definition ErrorCodes.h:107
@ rpcSRC_ISR_MALFORMED
Definition ErrorCodes.h:106
@ rpcDST_AMT_MALFORMED
Definition ErrorCodes.h:87
@ rpcNOT_ENABLED
Definition ErrorCodes.h:40
@ rpcSRC_CUR_MALFORMED
Definition ErrorCodes.h:105
@ rpcNO_PERMISSION
Definition ErrorCodes.h:34
Book reversed(Book const &book)
Definition Book.cpp:30
Json::Value rpcError(int iError)
Definition RPCErr.cpp:12
Json::Value doSubscribe(RPC::JsonContext &)
Definition Subscribe.cpp:19
std::shared_ptr< RPCSub > make_RPCSub(InfoSub::Source &source, boost::asio::io_context &io_context, JobQueue &jobQueue, std::string const &strUrl, std::string const &strUsername, std::string const &strPassword, Logs &logs)
Definition RPCSub.cpp:189
bool to_currency(Currency &, std::string const &)
Tries to convert a string to a Currency, returns true on success.
Definition UintTypes.cpp:65
unsigned int apiVersion
Definition Context.h:30
Resource::Charge & loadType
Definition Context.h:23
Application & app
Definition Context.h:22
InfoSub::pointer infoSub
Definition Context.h:29
beast::Journal const j
Definition Context.h:21
NetworkOPs & netOps
Definition Context.h:24
T what(T... args)