rippled
Subscribe_test.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2012, 2013 Ripple Labs Inc.
5  Permission to use, copy, modify, and/or distribute this software for any
6  purpose with or without fee is hereby granted, provided that the above
7  copyright notice and this permission notice appear in all copies.
8  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16 //==============================================================================
17 
18 #include <ripple/app/main/LoadManager.h>
19 #include <ripple/app/misc/LoadFeeTrack.h>
20 #include <ripple/app/misc/NetworkOPs.h>
21 #include <ripple/beast/unit_test.h>
22 #include <ripple/core/ConfigSections.h>
23 #include <ripple/protocol/jss.h>
24 #include <test/jtx.h>
25 #include <test/jtx/WSClient.h>
26 #include <test/jtx/envconfig.h>
27 
28 namespace ripple {
29 namespace test {
30 
31 class Subscribe_test : public beast::unit_test::suite
32 {
33 public:
34  void
36  {
37  using namespace std::chrono_literals;
38  using namespace jtx;
39  Env env(*this);
40  auto wsc = makeWSClient(env.app().config());
41  Json::Value stream;
42 
43  {
44  // RPC subscribe to server stream
45  stream[jss::streams] = Json::arrayValue;
46  stream[jss::streams].append("server");
47  auto jv = wsc->invoke("subscribe", stream);
48  if (wsc->version() == 2)
49  {
50  BEAST_EXPECT(
51  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
52  BEAST_EXPECT(
53  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
54  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
55  }
56  BEAST_EXPECT(jv[jss::status] == "success");
57  }
58 
59  // here we forcibly stop the load manager because it can (rarely but
60  // every-so-often) cause fees to raise or lower AFTER we've called the
61  // first findMsg but BEFORE we unsubscribe, thus causing the final
62  // findMsg check to fail since there is one unprocessed ws msg created
63  // by the loadmanager
64  env.app().getLoadManager().stop();
65  {
66  // Raise fee to cause an update
67  auto& feeTrack = env.app().getFeeTrack();
68  for (int i = 0; i < 5; ++i)
69  feeTrack.raiseLocalFee();
70  env.app().getOPs().reportFeeChange();
71 
72  // Check stream update
73  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
74  return jv[jss::type] == "serverStatus";
75  }));
76  }
77 
78  {
79  // RPC unsubscribe
80  auto jv = wsc->invoke("unsubscribe", stream);
81  if (wsc->version() == 2)
82  {
83  BEAST_EXPECT(
84  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
85  BEAST_EXPECT(
86  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
87  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
88  }
89  BEAST_EXPECT(jv[jss::status] == "success");
90  }
91 
92  {
93  // Raise fee to cause an update
94  auto& feeTrack = env.app().getFeeTrack();
95  for (int i = 0; i < 5; ++i)
96  feeTrack.raiseLocalFee();
97  env.app().getOPs().reportFeeChange();
98 
99  // Check stream update
100  auto jvo = wsc->getMsg(10ms);
101  BEAST_EXPECTS(!jvo, "getMsg: " + to_string(jvo.value()));
102  }
103  }
104 
105  void
107  {
108  using namespace std::chrono_literals;
109  using namespace jtx;
110  Env env(*this);
111  auto wsc = makeWSClient(env.app().config());
112  Json::Value stream;
113 
114  {
115  // RPC subscribe to ledger stream
116  stream[jss::streams] = Json::arrayValue;
117  stream[jss::streams].append("ledger");
118  auto jv = wsc->invoke("subscribe", stream);
119  if (wsc->version() == 2)
120  {
121  BEAST_EXPECT(
122  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
123  BEAST_EXPECT(
124  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
125  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
126  }
127  BEAST_EXPECT(jv[jss::result][jss::ledger_index] == 2);
128  }
129 
130  {
131  // Accept a ledger
132  env.close();
133 
134  // Check stream update
135  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
136  return jv[jss::ledger_index] == 3;
137  }));
138  }
139 
140  {
141  // Accept another ledger
142  env.close();
143 
144  // Check stream update
145  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
146  return jv[jss::ledger_index] == 4;
147  }));
148  }
149 
150  // RPC unsubscribe
151  auto jv = wsc->invoke("unsubscribe", stream);
152  if (wsc->version() == 2)
153  {
154  BEAST_EXPECT(
155  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
156  BEAST_EXPECT(
157  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
158  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
159  }
160  BEAST_EXPECT(jv[jss::status] == "success");
161  }
162 
163  void
165  {
166  using namespace std::chrono_literals;
167  using namespace jtx;
168  Env env(*this);
169  auto wsc = makeWSClient(env.app().config());
170  Json::Value stream;
171 
172  {
173  // RPC subscribe to transactions stream
174  stream[jss::streams] = Json::arrayValue;
175  stream[jss::streams].append("transactions");
176  auto jv = wsc->invoke("subscribe", stream);
177  if (wsc->version() == 2)
178  {
179  BEAST_EXPECT(
180  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
181  BEAST_EXPECT(
182  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
183  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
184  }
185  BEAST_EXPECT(jv[jss::status] == "success");
186  }
187 
188  {
189  env.fund(XRP(10000), "alice");
190  env.close();
191 
192  // Check stream update for payment transaction
193  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
194  return jv[jss::meta]["AffectedNodes"][1u]["CreatedNode"]
195  ["NewFields"][jss::Account] ==
196  Account("alice").human();
197  }));
198 
199  // Check stream update for accountset transaction
200  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
201  return jv[jss::meta]["AffectedNodes"][0u]["ModifiedNode"]
202  ["FinalFields"][jss::Account] ==
203  Account("alice").human();
204  }));
205 
206  env.fund(XRP(10000), "bob");
207  env.close();
208 
209  // Check stream update for payment transaction
210  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
211  return jv[jss::meta]["AffectedNodes"][1u]["CreatedNode"]
212  ["NewFields"][jss::Account] == Account("bob").human();
213  }));
214 
215  // Check stream update for accountset transaction
216  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
217  return jv[jss::meta]["AffectedNodes"][0u]["ModifiedNode"]
218  ["FinalFields"][jss::Account] ==
219  Account("bob").human();
220  }));
221  }
222 
223  {
224  // RPC unsubscribe
225  auto jv = wsc->invoke("unsubscribe", stream);
226  if (wsc->version() == 2)
227  {
228  BEAST_EXPECT(
229  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
230  BEAST_EXPECT(
231  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
232  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
233  }
234  BEAST_EXPECT(jv[jss::status] == "success");
235  }
236 
237  {
238  // RPC subscribe to accounts stream
239  stream = Json::objectValue;
240  stream[jss::accounts] = Json::arrayValue;
241  stream[jss::accounts].append(Account("alice").human());
242  auto jv = wsc->invoke("subscribe", stream);
243  if (wsc->version() == 2)
244  {
245  BEAST_EXPECT(
246  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
247  BEAST_EXPECT(
248  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
249  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
250  }
251  BEAST_EXPECT(jv[jss::status] == "success");
252  }
253 
254  {
255  // Transaction that does not affect stream
256  env.fund(XRP(10000), "carol");
257  env.close();
258  BEAST_EXPECT(!wsc->getMsg(10ms));
259 
260  // Transactions concerning alice
261  env.trust(Account("bob")["USD"](100), "alice");
262  env.close();
263 
264  // Check stream updates
265  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
266  return jv[jss::meta]["AffectedNodes"][1u]["ModifiedNode"]
267  ["FinalFields"][jss::Account] ==
268  Account("alice").human();
269  }));
270 
271  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
272  return jv[jss::meta]["AffectedNodes"][1u]["CreatedNode"]
273  ["NewFields"]["LowLimit"][jss::issuer] ==
274  Account("alice").human();
275  }));
276  }
277 
278  // RPC unsubscribe
279  auto jv = wsc->invoke("unsubscribe", stream);
280  if (wsc->version() == 2)
281  {
282  BEAST_EXPECT(
283  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
284  BEAST_EXPECT(
285  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
286  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
287  }
288  BEAST_EXPECT(jv[jss::status] == "success");
289  }
290 
291  void
293  {
294  using namespace jtx;
295  Env env(*this);
296  auto wsc = makeWSClient(env.app().config());
297  Json::Value stream;
298 
299  {
300  // RPC subscribe to manifests stream
301  stream[jss::streams] = Json::arrayValue;
302  stream[jss::streams].append("manifests");
303  auto jv = wsc->invoke("subscribe", stream);
304  if (wsc->version() == 2)
305  {
306  BEAST_EXPECT(
307  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
308  BEAST_EXPECT(
309  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
310  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
311  }
312  BEAST_EXPECT(jv[jss::status] == "success");
313  }
314 
315  // RPC unsubscribe
316  auto jv = wsc->invoke("unsubscribe", stream);
317  if (wsc->version() == 2)
318  {
319  BEAST_EXPECT(
320  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
321  BEAST_EXPECT(
322  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
323  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
324  }
325  BEAST_EXPECT(jv[jss::status] == "success");
326  }
327 
328  void
330  {
331  using namespace jtx;
332 
333  Env env{*this, envconfig(validator, "")};
334  auto& cfg = env.app().config();
335  if (!BEAST_EXPECT(cfg.section(SECTION_VALIDATION_SEED).empty()))
336  return;
337  auto const parsedseed =
338  parseBase58<Seed>(cfg.section(SECTION_VALIDATION_SEED).values()[0]);
339  if (!BEAST_EXPECT(parsedseed))
340  return;
341 
342  std::string const valPublicKey = toBase58(
346  generateSecretKey(KeyType::secp256k1, *parsedseed)));
347 
348  auto wsc = makeWSClient(env.app().config());
349  Json::Value stream;
350 
351  {
352  // RPC subscribe to validations stream
353  stream[jss::streams] = Json::arrayValue;
354  stream[jss::streams].append("validations");
355  auto jv = wsc->invoke("subscribe", stream);
356  if (wsc->version() == 2)
357  {
358  BEAST_EXPECT(
359  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
360  BEAST_EXPECT(
361  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
362  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
363  }
364  BEAST_EXPECT(jv[jss::status] == "success");
365  }
366 
367  {
368  // Lambda to check ledger validations from the stream.
369  auto validValidationFields = [&env, &valPublicKey](
370  Json::Value const& jv) {
371  if (jv[jss::type] != "validationReceived")
372  return false;
373 
374  if (jv[jss::validation_public_key].asString() != valPublicKey)
375  return false;
376 
377  if (jv[jss::ledger_hash] !=
378  to_string(env.closed()->info().hash))
379  return false;
380 
381  if (jv[jss::ledger_index] !=
382  std::to_string(env.closed()->info().seq))
383  return false;
384 
385  if (jv[jss::flags] != (vfFullyCanonicalSig | vfFullValidation))
386  return false;
387 
388  if (jv[jss::full] != true)
389  return false;
390 
391  if (jv.isMember(jss::load_fee))
392  return false;
393 
394  if (!jv.isMember(jss::signature))
395  return false;
396 
397  if (!jv.isMember(jss::signing_time))
398  return false;
399 
400  if (!jv.isMember(jss::cookie))
401  return false;
402 
403  if (!jv.isMember(jss::validated_hash))
404  return false;
405 
406  // Certain fields are only added on a flag ledger.
407  bool const isFlagLedger =
408  (env.closed()->info().seq + 1) % 256 == 0;
409 
410  if (jv.isMember(jss::server_version) != isFlagLedger)
411  return false;
412 
413  if (jv.isMember(jss::reserve_base) != isFlagLedger)
414  return false;
415 
416  if (jv.isMember(jss::reserve_inc) != isFlagLedger)
417  return false;
418 
419  return true;
420  };
421 
422  // Check stream update. Look at enough stream entries so we see
423  // at least one flag ledger.
424  while (env.closed()->info().seq < 300)
425  {
426  env.close();
427  using namespace std::chrono_literals;
428  BEAST_EXPECT(wsc->findMsg(5s, validValidationFields));
429  }
430  }
431 
432  // RPC unsubscribe
433  auto jv = wsc->invoke("unsubscribe", stream);
434  if (wsc->version() == 2)
435  {
436  BEAST_EXPECT(
437  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
438  BEAST_EXPECT(
439  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
440  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
441  }
442  BEAST_EXPECT(jv[jss::status] == "success");
443  }
444 
445  void
447  {
448  using namespace jtx;
449  testcase("Subscribe by url");
450  Env env{*this};
451 
452  Json::Value jv;
453  jv[jss::url] = "http://localhost/events";
454  jv[jss::url_username] = "admin";
455  jv[jss::url_password] = "password";
456  jv[jss::streams] = Json::arrayValue;
457  jv[jss::streams][0u] = "validations";
458  auto jr = env.rpc("json", "subscribe", to_string(jv))[jss::result];
459  BEAST_EXPECT(jr[jss::status] == "success");
460 
461  jv[jss::streams][0u] = "ledger";
462  jr = env.rpc("json", "subscribe", to_string(jv))[jss::result];
463  BEAST_EXPECT(jr[jss::status] == "success");
464 
465  jr = env.rpc("json", "unsubscribe", to_string(jv))[jss::result];
466  BEAST_EXPECT(jr[jss::status] == "success");
467 
468  jv[jss::streams][0u] = "validations";
469  jr = env.rpc("json", "unsubscribe", to_string(jv))[jss::result];
470  BEAST_EXPECT(jr[jss::status] == "success");
471  }
472 
473  void
474  testSubErrors(bool subscribe)
475  {
476  using namespace jtx;
477  auto const method = subscribe ? "subscribe" : "unsubscribe";
478  testcase << "Error cases for " << method;
479 
480  Env env{*this};
481  auto wsc = makeWSClient(env.app().config());
482 
483  {
484  auto jr = env.rpc("json", method, "{}")[jss::result];
485  BEAST_EXPECT(jr[jss::error] == "invalidParams");
486  BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
487  }
488 
489  {
490  Json::Value jv;
491  jv[jss::url] = "not-a-url";
492  jv[jss::username] = "admin";
493  jv[jss::password] = "password";
494  auto jr = env.rpc("json", method, to_string(jv))[jss::result];
495  if (subscribe)
496  {
497  BEAST_EXPECT(jr[jss::error] == "invalidParams");
498  BEAST_EXPECT(jr[jss::error_message] == "Failed to parse url.");
499  }
500  // else TODO: why isn't this an error for unsubscribe ?
501  // (findRpcSub returns null)
502  }
503 
504  {
505  Json::Value jv;
506  jv[jss::url] = "ftp://scheme.not.supported.tld";
507  auto jr = env.rpc("json", method, to_string(jv))[jss::result];
508  if (subscribe)
509  {
510  BEAST_EXPECT(jr[jss::error] == "invalidParams");
511  BEAST_EXPECT(
512  jr[jss::error_message] ==
513  "Only http and https is supported.");
514  }
515  }
516 
517  {
518  Env env_nonadmin{*this, no_admin(envconfig(port_increment, 3))};
519  Json::Value jv;
520  jv[jss::url] = "no-url";
521  auto jr =
522  env_nonadmin.rpc("json", method, to_string(jv))[jss::result];
523  BEAST_EXPECT(jr[jss::error] == "noPermission");
524  BEAST_EXPECT(
525  jr[jss::error_message] ==
526  "You don't have permission for this command.");
527  }
528 
529  std::initializer_list<Json::Value> const nonArrays{
534  "",
537 
538  for (auto const& f : {jss::accounts_proposed, jss::accounts})
539  {
540  for (auto const& nonArray : nonArrays)
541  {
542  Json::Value jv;
543  jv[f] = nonArray;
544  auto jr = wsc->invoke(method, jv)[jss::result];
545  BEAST_EXPECT(jr[jss::error] == "invalidParams");
546  BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
547  }
548 
549  {
550  Json::Value jv;
551  jv[f] = Json::arrayValue;
552  auto jr = wsc->invoke(method, jv)[jss::result];
553  BEAST_EXPECT(jr[jss::error] == "actMalformed");
554  BEAST_EXPECT(jr[jss::error_message] == "Account malformed.");
555  }
556  }
557 
558  for (auto const& nonArray : nonArrays)
559  {
560  Json::Value jv;
561  jv[jss::books] = nonArray;
562  auto jr = wsc->invoke(method, jv)[jss::result];
563  BEAST_EXPECT(jr[jss::error] == "invalidParams");
564  BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
565  }
566 
567  {
568  Json::Value jv;
569  jv[jss::books] = Json::arrayValue;
570  jv[jss::books][0u] = 1;
571  auto jr = wsc->invoke(method, jv)[jss::result];
572  BEAST_EXPECT(jr[jss::error] == "invalidParams");
573  BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
574  }
575 
576  {
577  Json::Value jv;
578  jv[jss::books] = Json::arrayValue;
579  jv[jss::books][0u] = Json::objectValue;
580  jv[jss::books][0u][jss::taker_gets] = Json::objectValue;
581  jv[jss::books][0u][jss::taker_pays] = Json::objectValue;
582  auto jr = wsc->invoke(method, jv)[jss::result];
583  BEAST_EXPECT(jr[jss::error] == "srcCurMalformed");
584  BEAST_EXPECT(
585  jr[jss::error_message] == "Source currency is malformed.");
586  }
587 
588  {
589  Json::Value jv;
590  jv[jss::books] = Json::arrayValue;
591  jv[jss::books][0u] = Json::objectValue;
592  jv[jss::books][0u][jss::taker_gets] = Json::objectValue;
593  jv[jss::books][0u][jss::taker_pays] = Json::objectValue;
594  jv[jss::books][0u][jss::taker_pays][jss::currency] = "ZZZZ";
595  auto jr = wsc->invoke(method, jv)[jss::result];
596  BEAST_EXPECT(jr[jss::error] == "srcCurMalformed");
597  BEAST_EXPECT(
598  jr[jss::error_message] == "Source currency is malformed.");
599  }
600 
601  {
602  Json::Value jv;
603  jv[jss::books] = Json::arrayValue;
604  jv[jss::books][0u] = Json::objectValue;
605  jv[jss::books][0u][jss::taker_gets] = Json::objectValue;
606  jv[jss::books][0u][jss::taker_pays] = Json::objectValue;
607  jv[jss::books][0u][jss::taker_pays][jss::currency] = "USD";
608  jv[jss::books][0u][jss::taker_pays][jss::issuer] = 1;
609  auto jr = wsc->invoke(method, jv)[jss::result];
610  BEAST_EXPECT(jr[jss::error] == "srcIsrMalformed");
611  BEAST_EXPECT(
612  jr[jss::error_message] == "Source issuer is malformed.");
613  }
614 
615  {
616  Json::Value jv;
617  jv[jss::books] = Json::arrayValue;
618  jv[jss::books][0u] = Json::objectValue;
619  jv[jss::books][0u][jss::taker_gets] = Json::objectValue;
620  jv[jss::books][0u][jss::taker_pays] = Json::objectValue;
621  jv[jss::books][0u][jss::taker_pays][jss::currency] = "USD";
622  jv[jss::books][0u][jss::taker_pays][jss::issuer] =
623  Account{"gateway"}.human() + "DEAD";
624  auto jr = wsc->invoke(method, jv)[jss::result];
625  BEAST_EXPECT(jr[jss::error] == "srcIsrMalformed");
626  BEAST_EXPECT(
627  jr[jss::error_message] == "Source issuer is malformed.");
628  }
629 
630  {
631  Json::Value jv;
632  jv[jss::books] = Json::arrayValue;
633  jv[jss::books][0u] = Json::objectValue;
634  jv[jss::books][0u][jss::taker_pays] =
635  Account{"gateway"}["USD"](1).value().getJson(
637  jv[jss::books][0u][jss::taker_gets] = Json::objectValue;
638  auto jr = wsc->invoke(method, jv)[jss::result];
639  // NOTE: this error is slightly incongruous with the
640  // equivalent source currency error
641  BEAST_EXPECT(jr[jss::error] == "dstAmtMalformed");
642  BEAST_EXPECT(
643  jr[jss::error_message] ==
644  "Destination amount/currency/issuer is malformed.");
645  }
646 
647  {
648  Json::Value jv;
649  jv[jss::books] = Json::arrayValue;
650  jv[jss::books][0u] = Json::objectValue;
651  jv[jss::books][0u][jss::taker_pays] =
652  Account{"gateway"}["USD"](1).value().getJson(
654  jv[jss::books][0u][jss::taker_gets][jss::currency] = "ZZZZ";
655  auto jr = wsc->invoke(method, jv)[jss::result];
656  // NOTE: this error is slightly incongruous with the
657  // equivalent source currency error
658  BEAST_EXPECT(jr[jss::error] == "dstAmtMalformed");
659  BEAST_EXPECT(
660  jr[jss::error_message] ==
661  "Destination amount/currency/issuer is malformed.");
662  }
663 
664  {
665  Json::Value jv;
666  jv[jss::books] = Json::arrayValue;
667  jv[jss::books][0u] = Json::objectValue;
668  jv[jss::books][0u][jss::taker_pays] =
669  Account{"gateway"}["USD"](1).value().getJson(
671  jv[jss::books][0u][jss::taker_gets][jss::currency] = "USD";
672  jv[jss::books][0u][jss::taker_gets][jss::issuer] = 1;
673  auto jr = wsc->invoke(method, jv)[jss::result];
674  BEAST_EXPECT(jr[jss::error] == "dstIsrMalformed");
675  BEAST_EXPECT(
676  jr[jss::error_message] == "Destination issuer is malformed.");
677  }
678 
679  {
680  Json::Value jv;
681  jv[jss::books] = Json::arrayValue;
682  jv[jss::books][0u] = Json::objectValue;
683  jv[jss::books][0u][jss::taker_pays] =
684  Account{"gateway"}["USD"](1).value().getJson(
686  jv[jss::books][0u][jss::taker_gets][jss::currency] = "USD";
687  jv[jss::books][0u][jss::taker_gets][jss::issuer] =
688  Account{"gateway"}.human() + "DEAD";
689  auto jr = wsc->invoke(method, jv)[jss::result];
690  BEAST_EXPECT(jr[jss::error] == "dstIsrMalformed");
691  BEAST_EXPECT(
692  jr[jss::error_message] == "Destination issuer is malformed.");
693  }
694 
695  {
696  Json::Value jv;
697  jv[jss::books] = Json::arrayValue;
698  jv[jss::books][0u] = Json::objectValue;
699  jv[jss::books][0u][jss::taker_pays] =
700  Account{"gateway"}["USD"](1).value().getJson(
702  jv[jss::books][0u][jss::taker_gets] =
703  Account{"gateway"}["USD"](1).value().getJson(
705  auto jr = wsc->invoke(method, jv)[jss::result];
706  BEAST_EXPECT(jr[jss::error] == "badMarket");
707  BEAST_EXPECT(jr[jss::error_message] == "No such market.");
708  }
709 
710  for (auto const& nonArray : nonArrays)
711  {
712  Json::Value jv;
713  jv[jss::streams] = nonArray;
714  auto jr = wsc->invoke(method, jv)[jss::result];
715  BEAST_EXPECT(jr[jss::error] == "invalidParams");
716  BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
717  }
718 
719  {
720  Json::Value jv;
721  jv[jss::streams] = Json::arrayValue;
722  jv[jss::streams][0u] = 1;
723  auto jr = wsc->invoke(method, jv)[jss::result];
724  BEAST_EXPECT(jr[jss::error] == "malformedStream");
725  BEAST_EXPECT(jr[jss::error_message] == "Stream malformed.");
726  }
727 
728  {
729  Json::Value jv;
730  jv[jss::streams] = Json::arrayValue;
731  jv[jss::streams][0u] = "not_a_stream";
732  auto jr = wsc->invoke(method, jv)[jss::result];
733  BEAST_EXPECT(jr[jss::error] == "malformedStream");
734  BEAST_EXPECT(jr[jss::error_message] == "Stream malformed.");
735  }
736  }
737 
738  void
739  run() override
740  {
741  testServer();
742  testLedger();
744  testManifests();
745  testValidations();
746  testSubErrors(true);
747  testSubErrors(false);
748  testSubByUrl();
749  }
750 };
751 
752 BEAST_DEFINE_TESTSUITE(Subscribe, app, ripple);
753 
754 } // namespace test
755 } // namespace ripple
ripple::JsonOptions::include_date
@ include_date
ripple::isFlagLedger
bool isFlagLedger(LedgerIndex seq)
Returns true if the given ledgerIndex is a flag ledgerIndex.
Definition: Ledger.cpp:910
ripple::test::jtx::XRP
const XRP_t XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
ripple::test::Subscribe_test::testLedger
void testLedger()
Definition: Subscribe_test.cpp:106
std::string
STL class.
Json::arrayValue
@ arrayValue
array value (ordered list)
Definition: json_value.h:42
Json::booleanValue
@ booleanValue
bool value
Definition: json_value.h:41
ripple::test::jtx::validator
std::unique_ptr< Config > validator(std::unique_ptr< Config >, std::string const &)
adjust configuration with params needed to be a validator
Definition: envconfig.cpp:89
ripple::toBase58
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:29
ripple::test::jtx::port_increment
std::unique_ptr< Config > port_increment(std::unique_ptr< Config >, int)
adjust the default configured server ports by a specified value
Definition: envconfig.cpp:98
ripple::test::jtx::Account::human
std::string const & human() const
Returns the human readable public key.
Definition: Account.h:109
Json::realValue
@ realValue
double value
Definition: json_value.h:39
ripple::test::jtx::Env::app
Application & app()
Definition: Env.h:240
ripple::test::jtx::envconfig
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:49
ripple::test::jtx::Env::trust
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:256
ripple::Application::getOPs
virtual NetworkOPs & getOPs()=0
ripple::Application::getFeeTrack
virtual LoadFeeTrack & getFeeTrack()=0
ripple::vfFullyCanonicalSig
constexpr std::uint32_t vfFullyCanonicalSig
Definition: STValidation.h:42
Json::uintValue
@ uintValue
unsigned integer value
Definition: json_value.h:38
ripple::test::Subscribe_test
Definition: Subscribe_test.cpp:31
ripple::test::jtx::no_admin
std::unique_ptr< Config > no_admin(std::unique_ptr< Config >)
adjust config so no admin ports are enabled
Definition: envconfig.cpp:70
ripple::Application::getLoadManager
virtual LoadManager & getLoadManager()=0
Json::objectValue
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:43
ripple::LoadManager::stop
void stop()
Definition: LoadManager.cpp:82
ripple::derivePublicKey
PublicKey derivePublicKey(KeyType type, SecretKey const &sk)
Derive the public key from a secret key.
Definition: SecretKey.cpp:313
ripple::Application::config
virtual Config & config()=0
ripple::NetworkOPs::reportFeeChange
virtual void reportFeeChange()=0
std::to_string
T to_string(T... args)
ripple::test::jtx::Env::close
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:121
ripple::test::Subscribe_test::testServer
void testServer()
Definition: Subscribe_test.cpp:35
ripple::generateSecretKey
SecretKey generateSecretKey(KeyType type, Seed const &seed)
Generate a new secret key deterministically.
Definition: SecretKey.cpp:291
ripple::test::Subscribe_test::run
void run() override
Definition: Subscribe_test.cpp:739
ripple::KeyType::secp256k1
@ secp256k1
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::test::Subscribe_test::testTransactions
void testTransactions()
Definition: Subscribe_test.cpp:164
ripple::test::Subscribe_test::testManifests
void testManifests()
Definition: Subscribe_test.cpp:292
ripple::LoadFeeTrack::raiseLocalFee
bool raiseLocalFee()
Definition: LoadFeeTrack.cpp:37
Json::intValue
@ intValue
signed integer value
Definition: json_value.h:37
ripple::test::jtx::Env::fund
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:225
Json::nullValue
@ nullValue
'null' value
Definition: json_value.h:36
ripple::test::makeWSClient
std::unique_ptr< WSClient > makeWSClient(Config const &cfg, bool v2, unsigned rpc_version, std::unordered_map< std::string, std::string > const &headers)
Returns a client operating through WebSockets/S.
Definition: WSClient.cpp:300
ripple::TokenType::NodePublic
@ NodePublic
ripple::test::Subscribe_test::testSubByUrl
void testSubByUrl()
Definition: Subscribe_test.cpp:446
ripple::to_string
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
Definition: app/misc/impl/Manifest.cpp:38
ripple::test::jtx::Account
Immutable cryptographic account descriptor.
Definition: Account.h:37
ripple::vfFullValidation
constexpr std::uint32_t vfFullValidation
Definition: STValidation.h:39
ripple::test::Subscribe_test::testSubErrors
void testSubErrors(bool subscribe)
Definition: Subscribe_test.cpp:474
ripple::test::Subscribe_test::testValidations
void testValidations()
Definition: Subscribe_test.cpp:329
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:115
Json::Value
Represents a JSON value.
Definition: json_value.h:145
std::initializer_list
ripple::test::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(DeliverMin, app, ripple)