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