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 xrpl {
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 =
38 context.params.isMember(jss::url_username) ? context.params[jss::url_username].asString() : "";
39 std::string strPassword =
40 context.params.isMember(jss::url_password) ? context.params[jss::url_password].asString() : "";
41
42 // DEPRECATED
43 if (context.params.isMember(jss::username))
44 strUsername = context.params[jss::username].asString();
45
46 // DEPRECATED
47 if (context.params.isMember(jss::password))
48 strPassword = context.params[jss::password].asString();
49
50 ispSub = context.netOps.findRpcSub(strUrl);
51 if (!ispSub)
52 {
53 JLOG(context.j.debug()) << "doSubscribe: building: " << strUrl;
54 try
55 {
56 auto rspSub = make_RPCSub(
57 context.app.getOPs(),
58 context.app.getIOContext(),
59 context.app.getJobQueue(),
60 strUrl,
61 strUsername,
62 strPassword,
63 context.app.logs());
64 ispSub = context.netOps.addRpcSub(strUrl, std::dynamic_pointer_cast<InfoSub>(rspSub));
65 }
66 catch (std::runtime_error& ex)
67 {
68 return RPC::make_param_error(ex.what());
69 }
70 }
71 else
72 {
73 JLOG(context.j.trace()) << "doSubscribe: reusing: " << strUrl;
74
75 if (auto rpcSub = std::dynamic_pointer_cast<RPCSub>(ispSub))
76 {
77 // Why do we need to check isMember against jss::username and
78 // jss::password here instead of just setting the username and
79 // the password? What about url_username and url_password?
80 if (context.params.isMember(jss::username))
81 rpcSub->setUsername(strUsername);
82
83 if (context.params.isMember(jss::password))
84 rpcSub->setPassword(strPassword);
85 }
86 }
87 }
88 else
89 {
90 ispSub = context.infoSub;
91 }
92 ispSub->setApiVersion(context.apiVersion);
93
94 if (context.params.isMember(jss::streams))
95 {
96 if (!context.params[jss::streams].isArray())
97 {
98 JLOG(context.j.info()) << "doSubscribe: streams requires an array.";
100 }
101
102 for (auto const& it : context.params[jss::streams])
103 {
104 if (!it.isString())
106
107 std::string streamName = it.asString();
108 if (streamName == "server")
109 {
110 context.netOps.subServer(ispSub, jvResult, context.role == Role::ADMIN);
111 }
112 else if (streamName == "ledger")
113 {
114 context.netOps.subLedger(ispSub, jvResult);
115 }
116 else if (streamName == "book_changes")
117 {
118 context.netOps.subBookChanges(ispSub);
119 }
120 else if (streamName == "manifests")
121 {
122 context.netOps.subManifests(ispSub);
123 }
124 else if (streamName == "transactions")
125 {
126 context.netOps.subTransactions(ispSub);
127 }
128 else if (streamName == "transactions_proposed" || streamName == "rt_transactions") // DEPRECATED
129 {
130 context.netOps.subRTTransactions(ispSub);
131 }
132 else if (streamName == "validations")
133 {
134 context.netOps.subValidations(ispSub);
135 }
136 else if (streamName == "peer_status")
137 {
138 if (context.role != Role::ADMIN)
140 context.netOps.subPeerStatus(ispSub);
141 }
142 else if (streamName == "consensus")
143 {
144 context.netOps.subConsensus(ispSub);
145 }
146 else
147 {
149 }
150 }
151 }
152
153 auto accountsProposed =
154 context.params.isMember(jss::accounts_proposed) ? jss::accounts_proposed : jss::rt_accounts; // DEPRECATED
155 if (context.params.isMember(accountsProposed))
156 {
157 if (!context.params[accountsProposed].isArray())
159
160 auto ids = RPC::parseAccountIds(context.params[accountsProposed]);
161 if (ids.empty())
163 context.netOps.subAccount(ispSub, ids, true);
164 }
165
166 if (context.params.isMember(jss::accounts))
167 {
168 if (!context.params[jss::accounts].isArray())
170
171 auto ids = RPC::parseAccountIds(context.params[jss::accounts]);
172 if (ids.empty())
174 context.netOps.subAccount(ispSub, ids, false);
175 JLOG(context.j.debug()) << "doSubscribe: accounts: " << ids.size();
176 }
177
178 if (context.params.isMember(jss::account_history_tx_stream))
179 {
180 if (!context.app.config().useTxTables())
181 return rpcError(rpcNOT_ENABLED);
182
184 auto const& req = context.params[jss::account_history_tx_stream];
185 if (!req.isMember(jss::account) || !req[jss::account].isString())
187
188 auto const id = parseBase58<AccountID>(req[jss::account].asString());
189 if (!id)
191
192 if (auto result = context.netOps.subAccountHistory(ispSub, *id); result != rpcSUCCESS)
193 {
194 return rpcError(result);
195 }
196
197 jvResult[jss::warning] =
198 "account_history_tx_stream is an experimental feature and likely "
199 "to be removed in the future";
200 JLOG(context.j.debug()) << "doSubscribe: account_history_tx_stream: " << toBase58(*id);
201 }
202
203 if (context.params.isMember(jss::books))
204 {
205 if (!context.params[jss::books].isArray())
207
208 for (auto& j : context.params[jss::books])
209 {
210 if (!j.isObject() || !j.isMember(jss::taker_pays) || !j.isMember(jss::taker_gets) ||
211 !j[jss::taker_pays].isObjectOrNull() || !j[jss::taker_gets].isObjectOrNull())
213
214 Book book;
215 Json::Value taker_pays = j[jss::taker_pays];
216 Json::Value taker_gets = j[jss::taker_gets];
217
218 // Parse mandatory currency.
219 if (!taker_pays.isMember(jss::currency) ||
220 !to_currency(book.in.currency, taker_pays[jss::currency].asString()))
221 {
222 JLOG(context.j.info()) << "Bad taker_pays currency.";
224 }
225
226 // Parse optional issuer.
227 if (((taker_pays.isMember(jss::issuer)) &&
228 (!taker_pays[jss::issuer].isString() ||
229 !to_issuer(book.in.account, taker_pays[jss::issuer].asString())))
230 // Don't allow illegal issuers.
231 || (!book.in.currency != !book.in.account) || 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(book.out.currency, taker_gets[jss::currency].asString()))
240 {
241 JLOG(context.j.info()) << "Bad taker_gets currency.";
243 }
244
245 // Parse optional issuer.
246 if (((taker_gets.isMember(jss::issuer)) &&
247 (!taker_gets[jss::issuer].isString() ||
248 !to_issuer(book.out.account, taker_gets[jss::issuer].asString())))
249 // Don't allow illegal issuers.
250 || (!book.out.currency != !book.out.account) || noAccount() == book.out.account)
251 {
252 JLOG(context.j.info()) << "Bad taker_gets issuer.";
254 }
255
256 if (book.in.currency == book.out.currency && book.in.account == book.out.account)
257 {
258 JLOG(context.j.info()) << "taker_gets same as taker_pays.";
259 return rpcError(rpcBAD_MARKET);
260 }
261
263
264 if (j.isMember(jss::taker))
265 {
266 takerID = parseBase58<AccountID>(j[jss::taker].asString());
267 if (!takerID)
268 return rpcError(rpcBAD_ISSUER);
269 }
270
271 if (j.isMember(jss::domain))
272 {
273 uint256 domain;
274 if (!j[jss::domain].isString() || !domain.parseHex(j[jss::domain].asString()))
275 {
277 }
278 else
279 {
280 book.domain = domain;
281 }
282 }
283
284 if (!isConsistent(book))
285 {
286 JLOG(context.j.warn()) << "Bad market: " << book;
287 return rpcError(rpcBAD_MARKET);
288 }
289
290 context.netOps.subBook(ispSub, book);
291
292 // both_sides is deprecated.
293 bool const both = (j.isMember(jss::both) && j[jss::both].asBool()) ||
294 (j.isMember(jss::both_sides) && j[jss::both_sides].asBool());
295
296 if (both)
297 context.netOps.subBook(ispSub, reversed(book));
298
299 // state_now is deprecated.
300 if ((j.isMember(jss::snapshot) && j[jss::snapshot].asBool()) ||
301 (j.isMember(jss::state_now) && j[jss::state_now].asBool()))
302 {
305 if (lpLedger)
306 {
307 Json::Value const jvMarker = Json::Value(Json::nullValue);
309
310 auto add = [&](Json::StaticString field) {
311 context.netOps.getBookPage(
312 lpLedger,
313 field == jss::asks ? reversed(book) : book,
314 takerID ? *takerID : noAccount(),
315 false,
317 jvMarker,
318 jvOffers);
319
320 if (jvResult.isMember(field))
321 {
322 Json::Value& results(jvResult[field]);
323 for (auto const& e : jvOffers[jss::offers])
324 results.append(e);
325 }
326 else
327 {
328 jvResult[field] = jvOffers[jss::offers];
329 }
330 };
331
332 if (both)
333 {
334 add(jss::bids);
335 add(jss::asks);
336 }
337 else
338 {
339 add(jss::offers);
340 }
341 }
342 }
343 }
344 }
345
346 return jvResult;
347}
348
349} // namespace xrpl
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:301
Stream info() const
Definition Journal.h:307
Stream trace() const
Severity stream access functions.
Definition Journal.h:295
Stream warn() const
Definition Journal.h:313
virtual Config & config()=0
virtual LedgerMaster & getLedgerMaster()=0
virtual boost::asio::io_context & getIOContext()=0
virtual Logs & logs()=0
virtual JobQueue & getJobQueue()=0
virtual NetworkOPs & getOPs()=0
Specifies an order book.
Definition Book.h:17
Issue in
Definition Book.h:19
std::optional< uint256 > domain
Definition Book.h:21
Issue out
Definition Book.h:20
bool useTxTables() const
Definition Config.h:318
virtual bool subTransactions(ref ispListener)=0
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 subPeerStatus(ref ispListener)=0
virtual bool subConsensus(ref ispListener)=0
virtual void subAccount(ref ispListener, hash_set< AccountID > const &vnaAccountIDs, bool realTime)=0
virtual bool subBook(ref ispListener, Book const &)=0
virtual bool subValidations(ref ispListener)=0
virtual bool subRTTransactions(ref ispListener)=0
virtual bool subBookChanges(ref ispListener)=0
virtual pointer addRpcSub(std::string const &strUrl, ref rspEntry)=0
virtual bool subServer(ref ispListener, Json::Value &jvResult, bool admin)=0
virtual bool subManifests(ref ispListener)=0
virtual bool subLedger(ref ispListener, Json::Value &jvResult)=0
virtual pointer findRpcSub(std::string const &strUrl)=0
void setApiVersion(unsigned int apiVersion)
Definition InfoSub.cpp:119
Currency currency
Definition Issue.h:16
AccountID account
Definition Issue.h:17
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:472
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.
hash_set< AccountID > parseAccountIds(Json::Value const &jvArray)
Parses an array of account IDs from a JSON value.
Json::Value make_param_error(std::string const &message)
Returns a new json object that indicates invalid parameters.
Definition ErrorCodes.h:215
Charge const feeMediumBurdenRPC
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
bool isConsistent(Book const &book)
Definition Book.cpp:10
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:92
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:173
Book reversed(Book const &book)
Definition Book.cpp:29
Json::Value rpcError(error_code_i iError)
Definition RPCErr.cpp:12
bool to_issuer(AccountID &, std::string const &)
Convert hex or base58 string to AccountID.
AccountID const & noAccount()
A placeholder for empty accounts.
bool to_currency(Currency &, std::string const &)
Tries to convert a string to a Currency, returns true on success.
Definition UintTypes.cpp:62
@ rpcSRC_CUR_MALFORMED
Definition ErrorCodes.h:105
@ rpcSTREAM_MALFORMED
Definition ErrorCodes.h:107
@ rpcNO_PERMISSION
Definition ErrorCodes.h:34
@ rpcBAD_MARKET
Definition ErrorCodes.h:78
@ rpcSRC_ISR_MALFORMED
Definition ErrorCodes.h:106
@ rpcNOT_ENABLED
Definition ErrorCodes.h:40
@ rpcDST_AMT_MALFORMED
Definition ErrorCodes.h:87
@ rpcDOMAIN_MALFORMED
Definition ErrorCodes.h:139
@ rpcDST_ISR_MALFORMED
Definition ErrorCodes.h:89
@ rpcBAD_ISSUER
Definition ErrorCodes.h:77
@ rpcINVALID_PARAMS
Definition ErrorCodes.h:65
@ rpcACT_MALFORMED
Definition ErrorCodes.h:71
@ rpcSUCCESS
Definition ErrorCodes.h:25
beast::Journal const j
Definition Context.h:21
Application & app
Definition Context.h:22
InfoSub::pointer infoSub
Definition Context.h:29
Resource::Charge & loadType
Definition Context.h:23
unsigned int apiVersion
Definition Context.h:30
NetworkOPs & netOps
Definition Context.h:24
Json::Value params
Definition Context.h:44
T what(T... args)