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/Feature.h>
24 #include <ripple/protocol/jss.h>
25 #include <test/jtx.h>
26 #include <test/jtx/WSClient.h>
27 #include <test/jtx/envconfig.h>
28 
29 namespace ripple {
30 namespace test {
31 
32 class Subscribe_test : public beast::unit_test::suite
33 {
34 public:
35  void
37  {
38  using namespace std::chrono_literals;
39  using namespace jtx;
40  Env env(*this);
41  auto wsc = makeWSClient(env.app().config());
42  Json::Value stream;
43 
44  {
45  // RPC subscribe to server stream
46  stream[jss::streams] = Json::arrayValue;
47  stream[jss::streams].append("server");
48  auto jv = wsc->invoke("subscribe", stream);
49  if (wsc->version() == 2)
50  {
51  BEAST_EXPECT(
52  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
53  BEAST_EXPECT(
54  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
55  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
56  }
57  BEAST_EXPECT(jv[jss::status] == "success");
58  }
59 
60  // here we forcibly stop the load manager because it can (rarely but
61  // every-so-often) cause fees to raise or lower AFTER we've called the
62  // first findMsg but BEFORE we unsubscribe, thus causing the final
63  // findMsg check to fail since there is one unprocessed ws msg created
64  // by the loadmanager
65  env.app().getLoadManager().stop();
66  {
67  // Raise fee to cause an update
68  auto& feeTrack = env.app().getFeeTrack();
69  for (int i = 0; i < 5; ++i)
70  feeTrack.raiseLocalFee();
71  env.app().getOPs().reportFeeChange();
72 
73  // Check stream update
74  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
75  return jv[jss::type] == "serverStatus";
76  }));
77  }
78 
79  {
80  // RPC unsubscribe
81  auto jv = wsc->invoke("unsubscribe", stream);
82  if (wsc->version() == 2)
83  {
84  BEAST_EXPECT(
85  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
86  BEAST_EXPECT(
87  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
88  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
89  }
90  BEAST_EXPECT(jv[jss::status] == "success");
91  }
92 
93  {
94  // Raise fee to cause an update
95  auto& feeTrack = env.app().getFeeTrack();
96  for (int i = 0; i < 5; ++i)
97  feeTrack.raiseLocalFee();
98  env.app().getOPs().reportFeeChange();
99 
100  // Check stream update
101  auto jvo = wsc->getMsg(10ms);
102  BEAST_EXPECTS(!jvo, "getMsg: " + to_string(jvo.value()));
103  }
104  }
105 
106  void
108  {
109  using namespace std::chrono_literals;
110  using namespace jtx;
111  Env env(*this);
112  auto wsc = makeWSClient(env.app().config());
113  Json::Value stream;
114 
115  {
116  // RPC subscribe to ledger stream
117  stream[jss::streams] = Json::arrayValue;
118  stream[jss::streams].append("ledger");
119  auto jv = wsc->invoke("subscribe", stream);
120  if (wsc->version() == 2)
121  {
122  BEAST_EXPECT(
123  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
124  BEAST_EXPECT(
125  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
126  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
127  }
128  BEAST_EXPECT(jv[jss::result][jss::ledger_index] == 2);
129  }
130 
131  {
132  // Accept a ledger
133  env.close();
134 
135  // Check stream update
136  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
137  return jv[jss::ledger_index] == 3;
138  }));
139  }
140 
141  {
142  // Accept another ledger
143  env.close();
144 
145  // Check stream update
146  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
147  return jv[jss::ledger_index] == 4;
148  }));
149  }
150 
151  // RPC unsubscribe
152  auto jv = wsc->invoke("unsubscribe", stream);
153  if (wsc->version() == 2)
154  {
155  BEAST_EXPECT(
156  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
157  BEAST_EXPECT(
158  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
159  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
160  }
161  BEAST_EXPECT(jv[jss::status] == "success");
162  }
163 
164  void
166  {
167  using namespace std::chrono_literals;
168  using namespace jtx;
169  Env env(*this);
170  auto wsc = makeWSClient(env.app().config());
171  Json::Value stream;
172 
173  {
174  // RPC subscribe to transactions stream
175  stream[jss::streams] = Json::arrayValue;
176  stream[jss::streams].append("transactions");
177  auto jv = wsc->invoke("subscribe", stream);
178  if (wsc->version() == 2)
179  {
180  BEAST_EXPECT(
181  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
182  BEAST_EXPECT(
183  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
184  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
185  }
186  BEAST_EXPECT(jv[jss::status] == "success");
187  }
188 
189  {
190  env.fund(XRP(10000), "alice");
191  env.close();
192 
193  // Check stream update for payment transaction
194  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
195  return jv[jss::meta]["AffectedNodes"][1u]["CreatedNode"]
196  ["NewFields"][jss::Account] ==
197  Account("alice").human();
198  }));
199 
200  // Check stream update for accountset transaction
201  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
202  return jv[jss::meta]["AffectedNodes"][0u]["ModifiedNode"]
203  ["FinalFields"][jss::Account] ==
204  Account("alice").human();
205  }));
206 
207  env.fund(XRP(10000), "bob");
208  env.close();
209 
210  // Check stream update for payment transaction
211  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
212  return jv[jss::meta]["AffectedNodes"][1u]["CreatedNode"]
213  ["NewFields"][jss::Account] == Account("bob").human();
214  }));
215 
216  // Check stream update for accountset transaction
217  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
218  return jv[jss::meta]["AffectedNodes"][0u]["ModifiedNode"]
219  ["FinalFields"][jss::Account] ==
220  Account("bob").human();
221  }));
222  }
223 
224  {
225  // RPC unsubscribe
226  auto jv = wsc->invoke("unsubscribe", stream);
227  if (wsc->version() == 2)
228  {
229  BEAST_EXPECT(
230  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
231  BEAST_EXPECT(
232  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
233  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
234  }
235  BEAST_EXPECT(jv[jss::status] == "success");
236  }
237 
238  {
239  // RPC subscribe to accounts stream
240  stream = Json::objectValue;
241  stream[jss::accounts] = Json::arrayValue;
242  stream[jss::accounts].append(Account("alice").human());
243  auto jv = wsc->invoke("subscribe", stream);
244  if (wsc->version() == 2)
245  {
246  BEAST_EXPECT(
247  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
248  BEAST_EXPECT(
249  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
250  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
251  }
252  BEAST_EXPECT(jv[jss::status] == "success");
253  }
254 
255  {
256  // Transaction that does not affect stream
257  env.fund(XRP(10000), "carol");
258  env.close();
259  BEAST_EXPECT(!wsc->getMsg(10ms));
260 
261  // Transactions concerning alice
262  env.trust(Account("bob")["USD"](100), "alice");
263  env.close();
264 
265  // Check stream updates
266  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
267  return jv[jss::meta]["AffectedNodes"][1u]["ModifiedNode"]
268  ["FinalFields"][jss::Account] ==
269  Account("alice").human();
270  }));
271 
272  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
273  return jv[jss::meta]["AffectedNodes"][1u]["CreatedNode"]
274  ["NewFields"]["LowLimit"][jss::issuer] ==
275  Account("alice").human();
276  }));
277  }
278 
279  // RPC unsubscribe
280  auto jv = wsc->invoke("unsubscribe", stream);
281  if (wsc->version() == 2)
282  {
283  BEAST_EXPECT(
284  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
285  BEAST_EXPECT(
286  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
287  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
288  }
289  BEAST_EXPECT(jv[jss::status] == "success");
290  }
291 
292  void
294  {
295  using namespace jtx;
296  Env env(*this);
297  auto wsc = makeWSClient(env.app().config());
298  Json::Value stream;
299 
300  {
301  // RPC subscribe to manifests stream
302  stream[jss::streams] = Json::arrayValue;
303  stream[jss::streams].append("manifests");
304  auto jv = wsc->invoke("subscribe", stream);
305  if (wsc->version() == 2)
306  {
307  BEAST_EXPECT(
308  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
309  BEAST_EXPECT(
310  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
311  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
312  }
313  BEAST_EXPECT(jv[jss::status] == "success");
314  }
315 
316  // RPC unsubscribe
317  auto jv = wsc->invoke("unsubscribe", stream);
318  if (wsc->version() == 2)
319  {
320  BEAST_EXPECT(
321  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
322  BEAST_EXPECT(
323  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
324  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
325  }
326  BEAST_EXPECT(jv[jss::status] == "success");
327  }
328 
329  void
331  {
332  using namespace jtx;
333 
334  Env env{*this, envconfig(validator, ""), features};
335  auto& cfg = env.app().config();
336  if (!BEAST_EXPECT(cfg.section(SECTION_VALIDATION_SEED).empty()))
337  return;
338  auto const parsedseed =
339  parseBase58<Seed>(cfg.section(SECTION_VALIDATION_SEED).values()[0]);
340  if (!BEAST_EXPECT(parsedseed))
341  return;
342 
343  std::string const valPublicKey = toBase58(
347  generateSecretKey(KeyType::secp256k1, *parsedseed)));
348 
349  auto wsc = makeWSClient(env.app().config());
350  Json::Value stream;
351 
352  {
353  // RPC subscribe to validations stream
354  stream[jss::streams] = Json::arrayValue;
355  stream[jss::streams].append("validations");
356  auto jv = wsc->invoke("subscribe", stream);
357  if (wsc->version() == 2)
358  {
359  BEAST_EXPECT(
360  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
361  BEAST_EXPECT(
362  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
363  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
364  }
365  BEAST_EXPECT(jv[jss::status] == "success");
366  }
367 
368  {
369  // Lambda to check ledger validations from the stream.
370  auto validValidationFields = [&env, &valPublicKey](
371  Json::Value const& jv) {
372  if (jv[jss::type] != "validationReceived")
373  return false;
374 
375  if (jv[jss::validation_public_key].asString() != valPublicKey)
376  return false;
377 
378  if (jv[jss::ledger_hash] !=
379  to_string(env.closed()->info().hash))
380  return false;
381 
382  if (jv[jss::ledger_index] !=
383  std::to_string(env.closed()->info().seq))
384  return false;
385 
386  if (jv[jss::flags] != (vfFullyCanonicalSig | vfFullValidation))
387  return false;
388 
389  if (jv[jss::full] != true)
390  return false;
391 
392  if (jv.isMember(jss::load_fee))
393  return false;
394 
395  if (!jv.isMember(jss::signature))
396  return false;
397 
398  if (!jv.isMember(jss::signing_time))
399  return false;
400 
401  if (!jv.isMember(jss::cookie))
402  return false;
403 
404  if (!jv.isMember(jss::validated_hash))
405  return false;
406 
407  // Certain fields are only added on a flag ledger.
408  bool const isFlagLedger =
409  (env.closed()->info().seq + 1) % 256 == 0;
410 
411  if (jv.isMember(jss::server_version) != isFlagLedger)
412  return false;
413 
414  if (jv.isMember(jss::reserve_base) != isFlagLedger)
415  return false;
416 
417  if (jv.isMember(jss::reserve_inc) != isFlagLedger)
418  return false;
419 
420  return true;
421  };
422 
423  // Check stream update. Look at enough stream entries so we see
424  // at least one flag ledger.
425  while (env.closed()->info().seq < 300)
426  {
427  env.close();
428  using namespace std::chrono_literals;
429  BEAST_EXPECT(wsc->findMsg(5s, validValidationFields));
430  }
431  }
432 
433  // RPC unsubscribe
434  auto jv = wsc->invoke("unsubscribe", stream);
435  if (wsc->version() == 2)
436  {
437  BEAST_EXPECT(
438  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
439  BEAST_EXPECT(
440  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
441  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
442  }
443  BEAST_EXPECT(jv[jss::status] == "success");
444  }
445 
446  void
448  {
449  using namespace jtx;
450  testcase("Subscribe by url");
451  Env env{*this};
452 
453  Json::Value jv;
454  jv[jss::url] = "http://localhost/events";
455  jv[jss::url_username] = "admin";
456  jv[jss::url_password] = "password";
457  jv[jss::streams] = Json::arrayValue;
458  jv[jss::streams][0u] = "validations";
459  auto jr = env.rpc("json", "subscribe", to_string(jv))[jss::result];
460  BEAST_EXPECT(jr[jss::status] == "success");
461 
462  jv[jss::streams][0u] = "ledger";
463  jr = env.rpc("json", "subscribe", to_string(jv))[jss::result];
464  BEAST_EXPECT(jr[jss::status] == "success");
465 
466  jr = env.rpc("json", "unsubscribe", to_string(jv))[jss::result];
467  BEAST_EXPECT(jr[jss::status] == "success");
468 
469  jv[jss::streams][0u] = "validations";
470  jr = env.rpc("json", "unsubscribe", to_string(jv))[jss::result];
471  BEAST_EXPECT(jr[jss::status] == "success");
472  }
473 
474  void
475  testSubErrors(bool subscribe)
476  {
477  using namespace jtx;
478  auto const method = subscribe ? "subscribe" : "unsubscribe";
479  testcase << "Error cases for " << method;
480 
481  Env env{*this};
482  auto wsc = makeWSClient(env.app().config());
483 
484  {
485  auto jr = env.rpc("json", method, "{}")[jss::result];
486  BEAST_EXPECT(jr[jss::error] == "invalidParams");
487  BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
488  }
489 
490  {
491  Json::Value jv;
492  jv[jss::url] = "not-a-url";
493  jv[jss::username] = "admin";
494  jv[jss::password] = "password";
495  auto jr = env.rpc("json", method, to_string(jv))[jss::result];
496  if (subscribe)
497  {
498  BEAST_EXPECT(jr[jss::error] == "invalidParams");
499  BEAST_EXPECT(jr[jss::error_message] == "Failed to parse url.");
500  }
501  // else TODO: why isn't this an error for unsubscribe ?
502  // (findRpcSub returns null)
503  }
504 
505  {
506  Json::Value jv;
507  jv[jss::url] = "ftp://scheme.not.supported.tld";
508  auto jr = env.rpc("json", method, to_string(jv))[jss::result];
509  if (subscribe)
510  {
511  BEAST_EXPECT(jr[jss::error] == "invalidParams");
512  BEAST_EXPECT(
513  jr[jss::error_message] ==
514  "Only http and https is supported.");
515  }
516  }
517 
518  {
519  Env env_nonadmin{*this, no_admin(envconfig(port_increment, 3))};
520  Json::Value jv;
521  jv[jss::url] = "no-url";
522  auto jr =
523  env_nonadmin.rpc("json", method, to_string(jv))[jss::result];
524  BEAST_EXPECT(jr[jss::error] == "noPermission");
525  BEAST_EXPECT(
526  jr[jss::error_message] ==
527  "You don't have permission for this command.");
528  }
529 
530  std::initializer_list<Json::Value> const nonArrays{
535  "",
538 
539  for (auto const& f : {jss::accounts_proposed, jss::accounts})
540  {
541  for (auto const& nonArray : nonArrays)
542  {
543  Json::Value jv;
544  jv[f] = nonArray;
545  auto jr = wsc->invoke(method, jv)[jss::result];
546  BEAST_EXPECT(jr[jss::error] == "invalidParams");
547  BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
548  }
549 
550  {
551  Json::Value jv;
552  jv[f] = Json::arrayValue;
553  auto jr = wsc->invoke(method, jv)[jss::result];
554  BEAST_EXPECT(jr[jss::error] == "actMalformed");
555  BEAST_EXPECT(jr[jss::error_message] == "Account malformed.");
556  }
557  }
558 
559  for (auto const& nonArray : nonArrays)
560  {
561  Json::Value jv;
562  jv[jss::books] = nonArray;
563  auto jr = wsc->invoke(method, jv)[jss::result];
564  BEAST_EXPECT(jr[jss::error] == "invalidParams");
565  BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
566  }
567 
568  {
569  Json::Value jv;
570  jv[jss::books] = Json::arrayValue;
571  jv[jss::books][0u] = 1;
572  auto jr = wsc->invoke(method, jv)[jss::result];
573  BEAST_EXPECT(jr[jss::error] == "invalidParams");
574  BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
575  }
576 
577  {
578  Json::Value jv;
579  jv[jss::books] = Json::arrayValue;
580  jv[jss::books][0u] = Json::objectValue;
581  jv[jss::books][0u][jss::taker_gets] = Json::objectValue;
582  jv[jss::books][0u][jss::taker_pays] = Json::objectValue;
583  auto jr = wsc->invoke(method, jv)[jss::result];
584  BEAST_EXPECT(jr[jss::error] == "srcCurMalformed");
585  BEAST_EXPECT(
586  jr[jss::error_message] == "Source currency is malformed.");
587  }
588 
589  {
590  Json::Value jv;
591  jv[jss::books] = Json::arrayValue;
592  jv[jss::books][0u] = Json::objectValue;
593  jv[jss::books][0u][jss::taker_gets] = Json::objectValue;
594  jv[jss::books][0u][jss::taker_pays] = Json::objectValue;
595  jv[jss::books][0u][jss::taker_pays][jss::currency] = "ZZZZ";
596  auto jr = wsc->invoke(method, jv)[jss::result];
597  BEAST_EXPECT(jr[jss::error] == "srcCurMalformed");
598  BEAST_EXPECT(
599  jr[jss::error_message] == "Source currency is malformed.");
600  }
601 
602  {
603  Json::Value jv;
604  jv[jss::books] = Json::arrayValue;
605  jv[jss::books][0u] = Json::objectValue;
606  jv[jss::books][0u][jss::taker_gets] = Json::objectValue;
607  jv[jss::books][0u][jss::taker_pays] = Json::objectValue;
608  jv[jss::books][0u][jss::taker_pays][jss::currency] = "USD";
609  jv[jss::books][0u][jss::taker_pays][jss::issuer] = 1;
610  auto jr = wsc->invoke(method, jv)[jss::result];
611  BEAST_EXPECT(jr[jss::error] == "srcIsrMalformed");
612  BEAST_EXPECT(
613  jr[jss::error_message] == "Source issuer is malformed.");
614  }
615 
616  {
617  Json::Value jv;
618  jv[jss::books] = Json::arrayValue;
619  jv[jss::books][0u] = Json::objectValue;
620  jv[jss::books][0u][jss::taker_gets] = Json::objectValue;
621  jv[jss::books][0u][jss::taker_pays] = Json::objectValue;
622  jv[jss::books][0u][jss::taker_pays][jss::currency] = "USD";
623  jv[jss::books][0u][jss::taker_pays][jss::issuer] =
624  Account{"gateway"}.human() + "DEAD";
625  auto jr = wsc->invoke(method, jv)[jss::result];
626  BEAST_EXPECT(jr[jss::error] == "srcIsrMalformed");
627  BEAST_EXPECT(
628  jr[jss::error_message] == "Source issuer is malformed.");
629  }
630 
631  {
632  Json::Value jv;
633  jv[jss::books] = Json::arrayValue;
634  jv[jss::books][0u] = Json::objectValue;
635  jv[jss::books][0u][jss::taker_pays] =
636  Account{"gateway"}["USD"](1).value().getJson(
638  jv[jss::books][0u][jss::taker_gets] = Json::objectValue;
639  auto jr = wsc->invoke(method, jv)[jss::result];
640  // NOTE: this error is slightly incongruous with the
641  // equivalent source currency error
642  BEAST_EXPECT(jr[jss::error] == "dstAmtMalformed");
643  BEAST_EXPECT(
644  jr[jss::error_message] ==
645  "Destination amount/currency/issuer is malformed.");
646  }
647 
648  {
649  Json::Value jv;
650  jv[jss::books] = Json::arrayValue;
651  jv[jss::books][0u] = Json::objectValue;
652  jv[jss::books][0u][jss::taker_pays] =
653  Account{"gateway"}["USD"](1).value().getJson(
655  jv[jss::books][0u][jss::taker_gets][jss::currency] = "ZZZZ";
656  auto jr = wsc->invoke(method, jv)[jss::result];
657  // NOTE: this error is slightly incongruous with the
658  // equivalent source currency error
659  BEAST_EXPECT(jr[jss::error] == "dstAmtMalformed");
660  BEAST_EXPECT(
661  jr[jss::error_message] ==
662  "Destination amount/currency/issuer is malformed.");
663  }
664 
665  {
666  Json::Value jv;
667  jv[jss::books] = Json::arrayValue;
668  jv[jss::books][0u] = Json::objectValue;
669  jv[jss::books][0u][jss::taker_pays] =
670  Account{"gateway"}["USD"](1).value().getJson(
672  jv[jss::books][0u][jss::taker_gets][jss::currency] = "USD";
673  jv[jss::books][0u][jss::taker_gets][jss::issuer] = 1;
674  auto jr = wsc->invoke(method, jv)[jss::result];
675  BEAST_EXPECT(jr[jss::error] == "dstIsrMalformed");
676  BEAST_EXPECT(
677  jr[jss::error_message] == "Destination issuer is malformed.");
678  }
679 
680  {
681  Json::Value jv;
682  jv[jss::books] = Json::arrayValue;
683  jv[jss::books][0u] = Json::objectValue;
684  jv[jss::books][0u][jss::taker_pays] =
685  Account{"gateway"}["USD"](1).value().getJson(
687  jv[jss::books][0u][jss::taker_gets][jss::currency] = "USD";
688  jv[jss::books][0u][jss::taker_gets][jss::issuer] =
689  Account{"gateway"}.human() + "DEAD";
690  auto jr = wsc->invoke(method, jv)[jss::result];
691  BEAST_EXPECT(jr[jss::error] == "dstIsrMalformed");
692  BEAST_EXPECT(
693  jr[jss::error_message] == "Destination issuer is malformed.");
694  }
695 
696  {
697  Json::Value jv;
698  jv[jss::books] = Json::arrayValue;
699  jv[jss::books][0u] = Json::objectValue;
700  jv[jss::books][0u][jss::taker_pays] =
701  Account{"gateway"}["USD"](1).value().getJson(
703  jv[jss::books][0u][jss::taker_gets] =
704  Account{"gateway"}["USD"](1).value().getJson(
706  auto jr = wsc->invoke(method, jv)[jss::result];
707  BEAST_EXPECT(jr[jss::error] == "badMarket");
708  BEAST_EXPECT(jr[jss::error_message] == "No such market.");
709  }
710 
711  for (auto const& nonArray : nonArrays)
712  {
713  Json::Value jv;
714  jv[jss::streams] = nonArray;
715  auto jr = wsc->invoke(method, jv)[jss::result];
716  BEAST_EXPECT(jr[jss::error] == "invalidParams");
717  BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
718  }
719 
720  {
721  Json::Value jv;
722  jv[jss::streams] = Json::arrayValue;
723  jv[jss::streams][0u] = 1;
724  auto jr = wsc->invoke(method, jv)[jss::result];
725  BEAST_EXPECT(jr[jss::error] == "malformedStream");
726  BEAST_EXPECT(jr[jss::error_message] == "Stream malformed.");
727  }
728 
729  {
730  Json::Value jv;
731  jv[jss::streams] = Json::arrayValue;
732  jv[jss::streams][0u] = "not_a_stream";
733  auto jr = wsc->invoke(method, jv)[jss::result];
734  BEAST_EXPECT(jr[jss::error] == "malformedStream");
735  BEAST_EXPECT(jr[jss::error_message] == "Stream malformed.");
736  }
737  }
738 
739  void
741  {
742  testcase("HistoryTxStream");
743 
744  using namespace std::chrono_literals;
745  using namespace jtx;
746  using IdxHashVec = std::vector<std::pair<int, std::string>>;
747 
748  Account alice("alice");
749  Account bob("bob");
750  Account carol("carol");
751  Account david("david");
753 
754  /*
755  * return true if the subscribe or unsubscribe result is a success
756  */
757  auto goodSubRPC = [](Json::Value const& subReply) -> bool {
758  return subReply.isMember(jss::result) &&
759  subReply[jss::result].isMember(jss::status) &&
760  subReply[jss::result][jss::status] == jss::success;
761  };
762 
763  /*
764  * try to receive txns from the tx stream subscription via the WSClient.
765  * return {true, true} if received numReplies replies and also
766  * received a tx with the account_history_tx_first == true
767  */
768  auto getTxHash = [](WSClient& wsc,
769  IdxHashVec& v,
770  int numReplies) -> std::pair<bool, bool> {
771  bool first_flag = false;
772 
773  for (int i = 0; i < numReplies; ++i)
774  {
775  std::uint32_t idx{0};
776  auto reply = wsc.getMsg(100ms);
777  if (reply)
778  {
779  auto r = *reply;
780  if (r.isMember(jss::account_history_tx_index))
781  idx = r[jss::account_history_tx_index].asInt();
782  if (r.isMember(jss::account_history_tx_first))
783  first_flag = true;
784  if (r.isMember(jss::transaction) &&
785  r[jss::transaction].isMember(jss::hash))
786  {
787  v.emplace_back(
788  idx, r[jss::transaction][jss::hash].asString());
789  continue;
790  }
791  }
792  return {false, first_flag};
793  }
794 
795  return {true, first_flag};
796  };
797 
798  /*
799  * send payments between the two accounts a and b,
800  * and close ledgersToClose ledgers
801  */
802  auto sendPayments = [](Env& env,
803  Account const& a,
804  Account const& b,
805  int newTxns,
806  std::uint32_t ledgersToClose,
807  int numXRP = 10) {
808  env.memoize(a);
809  env.memoize(b);
810  for (int i = 0; i < newTxns; ++i)
811  {
812  auto& from = (i % 2 == 0) ? a : b;
813  auto& to = (i % 2 == 0) ? b : a;
814  env.apply(
815  pay(from, to, jtx::XRP(numXRP)),
819  }
820  for (int i = 0; i < ledgersToClose; ++i)
821  env.close();
822  return newTxns;
823  };
824 
825  /*
826  * Check if txHistoryVec has every item of accountVec,
827  * and in the same order.
828  * If sizeCompare is false, txHistoryVec is allowed to be larger.
829  */
830  auto hashCompare = [](IdxHashVec const& accountVec,
831  IdxHashVec const& txHistoryVec,
832  bool sizeCompare) -> bool {
833  if (accountVec.empty() || txHistoryVec.empty())
834  return false;
835  if (sizeCompare && accountVec.size() != (txHistoryVec.size()))
836  return false;
837 
838  hash_map<std::string, int> txHistoryMap;
839  for (auto const& tx : txHistoryVec)
840  {
841  txHistoryMap.emplace(tx.second, tx.first);
842  }
843 
844  auto getHistoryIndex = [&](std::size_t i) -> std::optional<int> {
845  if (i >= accountVec.size())
846  return {};
847  auto it = txHistoryMap.find(accountVec[i].second);
848  if (it == txHistoryMap.end())
849  return {};
850  return it->second;
851  };
852 
853  auto firstHistoryIndex = getHistoryIndex(0);
854  if (!firstHistoryIndex)
855  return false;
856  for (std::size_t i = 1; i < accountVec.size(); ++i)
857  {
858  if (auto idx = getHistoryIndex(i);
859  !idx || *idx != *firstHistoryIndex + i)
860  return false;
861  }
862  return true;
863  };
864 
866 
867  {
868  /*
869  * subscribe to an account twice with same WS client,
870  * the second should fail
871  *
872  * also test subscribe to the account before it is created
873  */
874  Env env(*this);
875  auto wscTxHistory = makeWSClient(env.app().config());
876  Json::Value request;
877  request[jss::account_history_tx_stream] = Json::objectValue;
878  request[jss::account_history_tx_stream][jss::account] =
879  alice.human();
880  auto jv = wscTxHistory->invoke("subscribe", request);
881  if (!BEAST_EXPECT(goodSubRPC(jv)))
882  return;
883  jv = wscTxHistory->invoke("subscribe", request);
884  BEAST_EXPECT(!goodSubRPC(jv));
885 
886  /*
887  * unsubscribe history only, future txns should still be streamed
888  */
889  request[jss::account_history_tx_stream][jss::stop_history_tx_only] =
890  true;
891  jv = wscTxHistory->invoke("unsubscribe", request);
892  if (!BEAST_EXPECT(goodSubRPC(jv)))
893  return;
894 
895  sendPayments(env, env.master, alice, 1, 1, 123456);
896 
897  IdxHashVec vec;
898  auto r = getTxHash(*wscTxHistory, vec, 1);
899  if (!BEAST_EXPECT(r.first && r.second))
900  return;
901 
902  /*
903  * unsubscribe, future txns should not be streamed
904  */
905  request[jss::account_history_tx_stream][jss::stop_history_tx_only] =
906  false;
907  jv = wscTxHistory->invoke("unsubscribe", request);
908  BEAST_EXPECT(goodSubRPC(jv));
909 
910  sendPayments(env, env.master, alice, 1, 1);
911  r = getTxHash(*wscTxHistory, vec, 1);
912  BEAST_EXPECT(!r.first);
913  }
914 
915  {
916  /*
917  * subscribe genesis account tx history without txns
918  * subscribe to bob's account after it is created
919  */
920  Env env(*this);
921  auto wscTxHistory = makeWSClient(env.app().config());
922  Json::Value request;
923  request[jss::account_history_tx_stream] = Json::objectValue;
924  request[jss::account_history_tx_stream][jss::account] =
925  "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
926  auto jv = wscTxHistory->invoke("subscribe", request);
927  if (!BEAST_EXPECT(goodSubRPC(jv)))
928  return;
929  IdxHashVec genesisFullHistoryVec;
930  if (!BEAST_EXPECT(
931  !getTxHash(*wscTxHistory, genesisFullHistoryVec, 1).first))
932  return;
933 
934  /*
935  * create bob's account with one tx
936  * the two subscriptions should both stream it
937  */
938  sendPayments(env, env.master, bob, 1, 1, 654321);
939 
940  auto r = getTxHash(*wscTxHistory, genesisFullHistoryVec, 1);
941  if (!BEAST_EXPECT(r.first && r.second))
942  return;
943 
944  request[jss::account_history_tx_stream][jss::account] = bob.human();
945  jv = wscTxHistory->invoke("subscribe", request);
946  if (!BEAST_EXPECT(goodSubRPC(jv)))
947  return;
948  IdxHashVec bobFullHistoryVec;
949  r = getTxHash(*wscTxHistory, bobFullHistoryVec, 1);
950  if (!BEAST_EXPECT(r.first && r.second))
951  return;
952  BEAST_EXPECT(
953  bobFullHistoryVec.back().second ==
954  genesisFullHistoryVec.back().second);
955 
956  /*
957  * unsubscribe to prepare next test
958  */
959  jv = wscTxHistory->invoke("unsubscribe", request);
960  if (!BEAST_EXPECT(goodSubRPC(jv)))
961  return;
962  request[jss::account_history_tx_stream][jss::account] =
963  "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
964  jv = wscTxHistory->invoke("unsubscribe", request);
965  BEAST_EXPECT(goodSubRPC(jv));
966 
967  /*
968  * add more txns, then subscribe bob tx history and
969  * genesis account tx history. Their earliest txns should match.
970  */
971  sendPayments(env, env.master, bob, 30, 300);
972  wscTxHistory = makeWSClient(env.app().config());
973  request[jss::account_history_tx_stream][jss::account] = bob.human();
974  jv = wscTxHistory->invoke("subscribe", request);
975 
976  bobFullHistoryVec.clear();
977  BEAST_EXPECT(
978  getTxHash(*wscTxHistory, bobFullHistoryVec, 31).second);
979  jv = wscTxHistory->invoke("unsubscribe", request);
980 
981  request[jss::account_history_tx_stream][jss::account] =
982  "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
983  jv = wscTxHistory->invoke("subscribe", request);
984  genesisFullHistoryVec.clear();
985  BEAST_EXPECT(
986  getTxHash(*wscTxHistory, genesisFullHistoryVec, 31).second);
987  jv = wscTxHistory->invoke("unsubscribe", request);
988 
989  BEAST_EXPECT(
990  bobFullHistoryVec.back().second ==
991  genesisFullHistoryVec.back().second);
992  }
993 
994  {
995  /*
996  * subscribe account and subscribe account tx history
997  * and compare txns streamed
998  */
999  Env env(*this);
1000  auto wscAccount = makeWSClient(env.app().config());
1001  auto wscTxHistory = makeWSClient(env.app().config());
1002 
1003  std::array<Account, 2> accounts = {alice, bob};
1004  env.fund(XRP(222222), accounts);
1005  env.close();
1006 
1007  // subscribe account
1008  Json::Value stream = Json::objectValue;
1009  stream[jss::accounts] = Json::arrayValue;
1010  stream[jss::accounts].append(alice.human());
1011  auto jv = wscAccount->invoke("subscribe", stream);
1012 
1013  sendPayments(env, alice, bob, 5, 1);
1014  sendPayments(env, alice, bob, 5, 1);
1015  IdxHashVec accountVec;
1016  if (!BEAST_EXPECT(getTxHash(*wscAccount, accountVec, 10).first))
1017  return;
1018 
1019  // subscribe account tx history
1020  Json::Value request;
1021  request[jss::account_history_tx_stream] = Json::objectValue;
1022  request[jss::account_history_tx_stream][jss::account] =
1023  alice.human();
1024  jv = wscTxHistory->invoke("subscribe", request);
1025 
1026  // compare historical txns
1027  IdxHashVec txHistoryVec;
1028  if (!BEAST_EXPECT(getTxHash(*wscTxHistory, txHistoryVec, 10).first))
1029  return;
1030  if (!BEAST_EXPECT(hashCompare(accountVec, txHistoryVec, true)))
1031  return;
1032 
1033  {
1034  // take out all history txns from stream to prepare next test
1035  IdxHashVec initFundTxns;
1036  if (!BEAST_EXPECT(
1037  getTxHash(*wscTxHistory, initFundTxns, 10).second))
1038  return;
1039  }
1040 
1041  // compare future txns
1042  sendPayments(env, alice, bob, 10, 1);
1043  if (!BEAST_EXPECT(getTxHash(*wscAccount, accountVec, 10).first))
1044  return;
1045  if (!BEAST_EXPECT(getTxHash(*wscTxHistory, txHistoryVec, 10).first))
1046  return;
1047  if (!BEAST_EXPECT(hashCompare(accountVec, txHistoryVec, true)))
1048  return;
1049  wscTxHistory->invoke("unsubscribe", request);
1050  wscAccount->invoke("unsubscribe", stream);
1051  }
1052 
1053  {
1054  /*
1055  * alice issues USD to carol
1056  * mix USD and XRP payments
1057  */
1058  Env env(*this);
1059  auto const USD_a = alice["USD"];
1060 
1061  std::array<Account, 2> accounts = {alice, carol};
1062  env.fund(XRP(333333), accounts);
1063  env.trust(USD_a(20000), carol);
1064  env.close();
1065 
1066  auto mixedPayments = [&]() -> int {
1067  sendPayments(env, alice, carol, 1, 0);
1068  env(pay(alice, carol, USD_a(100)));
1069  env.close();
1070  return 2;
1071  };
1072 
1073  // subscribe
1074  Json::Value request;
1075  request[jss::account_history_tx_stream] = Json::objectValue;
1076  request[jss::account_history_tx_stream][jss::account] =
1077  carol.human();
1078  auto ws = makeWSClient(env.app().config());
1079  auto jv = ws->invoke("subscribe", request);
1080  {
1081  // take out existing txns from the stream
1082  IdxHashVec tempVec;
1083  getTxHash(*ws, tempVec, 100);
1084  }
1085 
1086  auto count = mixedPayments();
1087  IdxHashVec vec1;
1088  if (!BEAST_EXPECT(getTxHash(*ws, vec1, count).first))
1089  return;
1090  ws->invoke("unsubscribe", request);
1091  }
1092 
1093  {
1094  /*
1095  * long transaction history
1096  */
1097  Env env(*this);
1098  std::array<Account, 2> accounts = {alice, carol};
1099  env.fund(XRP(444444), accounts);
1100  env.close();
1101 
1102  // many payments, and close lots of ledgers
1103  auto oneRound = [&](int numPayments) {
1104  return sendPayments(env, alice, carol, numPayments, 300);
1105  };
1106 
1107  // subscribe
1108  Json::Value request;
1109  request[jss::account_history_tx_stream] = Json::objectValue;
1110  request[jss::account_history_tx_stream][jss::account] =
1111  carol.human();
1112  auto wscLong = makeWSClient(env.app().config());
1113  auto jv = wscLong->invoke("subscribe", request);
1114  {
1115  // take out existing txns from the stream
1116  IdxHashVec tempVec;
1117  getTxHash(*wscLong, tempVec, 100);
1118  }
1119 
1120  // repeat the payments many rounds
1121  for (int kk = 2; kk < 10; ++kk)
1122  {
1123  auto count = oneRound(kk);
1124  IdxHashVec vec1;
1125  if (!BEAST_EXPECT(getTxHash(*wscLong, vec1, count).first))
1126  return;
1127 
1128  // another subscribe, only for this round
1129  auto wscShort = makeWSClient(env.app().config());
1130  auto jv = wscShort->invoke("subscribe", request);
1131  IdxHashVec vec2;
1132  if (!BEAST_EXPECT(getTxHash(*wscShort, vec2, count).first))
1133  return;
1134  if (!BEAST_EXPECT(hashCompare(vec1, vec2, true)))
1135  return;
1136  wscShort->invoke("unsubscribe", request);
1137  }
1138  }
1139  }
1140 
1141  void
1142  run() override
1143  {
1144  using namespace test::jtx;
1146  FeatureBitset const xrpFees{featureXRPFees};
1147 
1148  testServer();
1149  testLedger();
1150  testTransactions();
1151  testManifests();
1152  testValidations(all - xrpFees);
1154  testSubErrors(true);
1155  testSubErrors(false);
1156  testSubByUrl();
1158  }
1159 };
1160 
1161 BEAST_DEFINE_TESTSUITE(Subscribe, app, ripple);
1162 
1163 } // namespace test
1164 } // 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:969
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:107
ripple::featureXRPFees
const uint256 featureXRPFees
std::string
STL class.
ripple::test::jtx::Env::apply
void apply(JsonValue &&jv, FN const &... fN)
Apply funclets and submit.
Definition: Env.h:498
Json::arrayValue
@ arrayValue
array value (ordered list)
Definition: json_value.h:42
Json::booleanValue
@ booleanValue
bool value
Definition: json_value.h:41
std::pair
ripple::TxSearched::all
@ all
ripple::test::Subscribe_test::testHistoryTxStream
void testHistoryTxStream()
Definition: Subscribe_test.cpp:740
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:116
std::vector
STL class.
std::unordered_map::find
T find(T... args)
ripple::toBase58
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:104
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:125
std::unordered_map::emplace
T emplace(T... args)
ripple::test::jtx::Account::human
std::string const & human() const
Returns the human readable public key.
Definition: Account.h:113
Json::realValue
@ realValue
double value
Definition: json_value.h:39
ripple::test::jtx::Env::app
Application & app()
Definition: Env.h:241
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:259
ripple::Application::getOPs
virtual NetworkOPs & getOPs()=0
ripple::test::jtx::autofill
static const autofill_t autofill
Definition: tags.h:42
std::string::clear
T clear(T... args)
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:32
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:79
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)
std::array
STL class.
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:36
ripple::generateSecretKey
SecretKey generateSecretKey(KeyType type, Seed const &seed)
Generate a new secret key deterministically.
Definition: SecretKey.cpp:291
ripple::test::jtx::supported_amendments
FeatureBitset supported_amendments()
Definition: Env.h:70
std::uint32_t
ripple::test::Subscribe_test::run
void run() override
Definition: Subscribe_test.cpp:1142
ripple::test::jtx::sig
Set the regular signature on a JTx.
Definition: sig.h:34
ripple::test::jtx::fee
Set the fee on a JTx.
Definition: fee.h:35
ripple::KeyType::secp256k1
@ secp256k1
ripple::test::WSClient
Definition: WSClient.h:33
ripple::test::jtx::seq
Set the sequence number on a JTx.
Definition: seq.h:33
ripple::test::WSClient::getMsg
virtual std::optional< Json::Value > getMsg(std::chrono::milliseconds const &timeout=std::chrono::milliseconds{ 0})=0
Retrieve a message.
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:165
ripple::test::Subscribe_test::testManifests
void testManifests()
Definition: Subscribe_test.cpp:293
Json::Value::clear
void clear()
Remove all object members and array elements.
Definition: json_value.cpp:753
ripple::LoadFeeTrack::raiseLocalFee
bool raiseLocalFee()
Definition: LoadFeeTrack.cpp:37
ripple::test::jtx::pay
Json::Value pay(Account const &account, Account const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:29
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:228
ripple::test::jtx::Env::master
Account const & master
Definition: Env.h:121
Json::nullValue
@ nullValue
'null' value
Definition: json_value.h:36
ripple::FeatureBitset
Definition: Feature.h:113
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
std::optional< int >
ripple::test::Subscribe_test::testSubByUrl
void testSubByUrl()
Definition: Subscribe_test.cpp:447
std::size_t
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:41
ripple::test::jtx::Account
Immutable cryptographic account descriptor.
Definition: Account.h:37
ripple::test::Subscribe_test::testValidations
void testValidations(FeatureBitset features)
Definition: Subscribe_test.cpp:330
std::unordered_map::end
T end(T... args)
ripple::vfFullValidation
constexpr std::uint32_t vfFullValidation
Definition: STValidation.h:39
ripple::test::Subscribe_test::testSubErrors
void testSubErrors(bool subscribe)
Definition: Subscribe_test.cpp:475
ripple::test::jtx::Env::memoize
void memoize(Account const &account)
Associate AccountID with account.
Definition: Env.cpp:156
std::unordered_map
STL class.
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:116
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)