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  bool xrpFees = env.closed()->rules().enabled(featureXRPFees);
415  if ((!xrpFees &&
416  jv.isMember(jss::reserve_base) != isFlagLedger) ||
417  (xrpFees && jv.isMember(jss::reserve_base)))
418  return false;
419 
420  if ((!xrpFees &&
421  jv.isMember(jss::reserve_inc) != isFlagLedger) ||
422  (xrpFees && jv.isMember(jss::reserve_inc)))
423  return false;
424 
425  if ((xrpFees &&
426  jv.isMember(jss::reserve_base_drops) != isFlagLedger) ||
427  (!xrpFees && jv.isMember(jss::reserve_base_drops)))
428  return false;
429 
430  if ((xrpFees &&
431  jv.isMember(jss::reserve_inc_drops) != isFlagLedger) ||
432  (!xrpFees && jv.isMember(jss::reserve_inc_drops)))
433  return false;
434 
435  return true;
436  };
437 
438  // Check stream update. Look at enough stream entries so we see
439  // at least one flag ledger.
440  while (env.closed()->info().seq < 300)
441  {
442  env.close();
443  using namespace std::chrono_literals;
444  BEAST_EXPECT(wsc->findMsg(5s, validValidationFields));
445  }
446  }
447 
448  // RPC unsubscribe
449  auto jv = wsc->invoke("unsubscribe", stream);
450  if (wsc->version() == 2)
451  {
452  BEAST_EXPECT(
453  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
454  BEAST_EXPECT(
455  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
456  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
457  }
458  BEAST_EXPECT(jv[jss::status] == "success");
459  }
460 
461  void
463  {
464  using namespace jtx;
465  testcase("Subscribe by url");
466  Env env{*this};
467 
468  Json::Value jv;
469  jv[jss::url] = "http://localhost/events";
470  jv[jss::url_username] = "admin";
471  jv[jss::url_password] = "password";
472  jv[jss::streams] = Json::arrayValue;
473  jv[jss::streams][0u] = "validations";
474  auto jr = env.rpc("json", "subscribe", to_string(jv))[jss::result];
475  BEAST_EXPECT(jr[jss::status] == "success");
476 
477  jv[jss::streams][0u] = "ledger";
478  jr = env.rpc("json", "subscribe", to_string(jv))[jss::result];
479  BEAST_EXPECT(jr[jss::status] == "success");
480 
481  jr = env.rpc("json", "unsubscribe", to_string(jv))[jss::result];
482  BEAST_EXPECT(jr[jss::status] == "success");
483 
484  jv[jss::streams][0u] = "validations";
485  jr = env.rpc("json", "unsubscribe", to_string(jv))[jss::result];
486  BEAST_EXPECT(jr[jss::status] == "success");
487  }
488 
489  void
490  testSubErrors(bool subscribe)
491  {
492  using namespace jtx;
493  auto const method = subscribe ? "subscribe" : "unsubscribe";
494  testcase << "Error cases for " << method;
495 
496  Env env{*this};
497  auto wsc = makeWSClient(env.app().config());
498 
499  {
500  auto jr = env.rpc("json", method, "{}")[jss::result];
501  BEAST_EXPECT(jr[jss::error] == "invalidParams");
502  BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
503  }
504 
505  {
506  Json::Value jv;
507  jv[jss::url] = "not-a-url";
508  jv[jss::username] = "admin";
509  jv[jss::password] = "password";
510  auto jr = env.rpc("json", method, to_string(jv))[jss::result];
511  if (subscribe)
512  {
513  BEAST_EXPECT(jr[jss::error] == "invalidParams");
514  BEAST_EXPECT(jr[jss::error_message] == "Failed to parse url.");
515  }
516  // else TODO: why isn't this an error for unsubscribe ?
517  // (findRpcSub returns null)
518  }
519 
520  {
521  Json::Value jv;
522  jv[jss::url] = "ftp://scheme.not.supported.tld";
523  auto jr = env.rpc("json", method, to_string(jv))[jss::result];
524  if (subscribe)
525  {
526  BEAST_EXPECT(jr[jss::error] == "invalidParams");
527  BEAST_EXPECT(
528  jr[jss::error_message] ==
529  "Only http and https is supported.");
530  }
531  }
532 
533  {
534  Env env_nonadmin{*this, no_admin(envconfig(port_increment, 3))};
535  Json::Value jv;
536  jv[jss::url] = "no-url";
537  auto jr =
538  env_nonadmin.rpc("json", method, to_string(jv))[jss::result];
539  BEAST_EXPECT(jr[jss::error] == "noPermission");
540  BEAST_EXPECT(
541  jr[jss::error_message] ==
542  "You don't have permission for this command.");
543  }
544 
545  std::initializer_list<Json::Value> const nonArrays{
550  "",
553 
554  for (auto const& f : {jss::accounts_proposed, jss::accounts})
555  {
556  for (auto const& nonArray : nonArrays)
557  {
558  Json::Value jv;
559  jv[f] = nonArray;
560  auto jr = wsc->invoke(method, jv)[jss::result];
561  BEAST_EXPECT(jr[jss::error] == "invalidParams");
562  BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
563  }
564 
565  {
566  Json::Value jv;
567  jv[f] = Json::arrayValue;
568  auto jr = wsc->invoke(method, jv)[jss::result];
569  BEAST_EXPECT(jr[jss::error] == "actMalformed");
570  BEAST_EXPECT(jr[jss::error_message] == "Account malformed.");
571  }
572  }
573 
574  for (auto const& nonArray : nonArrays)
575  {
576  Json::Value jv;
577  jv[jss::books] = nonArray;
578  auto jr = wsc->invoke(method, jv)[jss::result];
579  BEAST_EXPECT(jr[jss::error] == "invalidParams");
580  BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
581  }
582 
583  {
584  Json::Value jv;
585  jv[jss::books] = Json::arrayValue;
586  jv[jss::books][0u] = 1;
587  auto jr = wsc->invoke(method, jv)[jss::result];
588  BEAST_EXPECT(jr[jss::error] == "invalidParams");
589  BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
590  }
591 
592  {
593  Json::Value jv;
594  jv[jss::books] = Json::arrayValue;
595  jv[jss::books][0u] = Json::objectValue;
596  jv[jss::books][0u][jss::taker_gets] = Json::objectValue;
597  jv[jss::books][0u][jss::taker_pays] = Json::objectValue;
598  auto jr = wsc->invoke(method, jv)[jss::result];
599  BEAST_EXPECT(jr[jss::error] == "srcCurMalformed");
600  BEAST_EXPECT(
601  jr[jss::error_message] == "Source currency is malformed.");
602  }
603 
604  {
605  Json::Value jv;
606  jv[jss::books] = Json::arrayValue;
607  jv[jss::books][0u] = Json::objectValue;
608  jv[jss::books][0u][jss::taker_gets] = Json::objectValue;
609  jv[jss::books][0u][jss::taker_pays] = Json::objectValue;
610  jv[jss::books][0u][jss::taker_pays][jss::currency] = "ZZZZ";
611  auto jr = wsc->invoke(method, jv)[jss::result];
612  BEAST_EXPECT(jr[jss::error] == "srcCurMalformed");
613  BEAST_EXPECT(
614  jr[jss::error_message] == "Source currency is malformed.");
615  }
616 
617  {
618  Json::Value jv;
619  jv[jss::books] = Json::arrayValue;
620  jv[jss::books][0u] = Json::objectValue;
621  jv[jss::books][0u][jss::taker_gets] = Json::objectValue;
622  jv[jss::books][0u][jss::taker_pays] = Json::objectValue;
623  jv[jss::books][0u][jss::taker_pays][jss::currency] = "USD";
624  jv[jss::books][0u][jss::taker_pays][jss::issuer] = 1;
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_gets] = Json::objectValue;
636  jv[jss::books][0u][jss::taker_pays] = Json::objectValue;
637  jv[jss::books][0u][jss::taker_pays][jss::currency] = "USD";
638  jv[jss::books][0u][jss::taker_pays][jss::issuer] =
639  Account{"gateway"}.human() + "DEAD";
640  auto jr = wsc->invoke(method, jv)[jss::result];
641  BEAST_EXPECT(jr[jss::error] == "srcIsrMalformed");
642  BEAST_EXPECT(
643  jr[jss::error_message] == "Source issuer is malformed.");
644  }
645 
646  {
647  Json::Value jv;
648  jv[jss::books] = Json::arrayValue;
649  jv[jss::books][0u] = Json::objectValue;
650  jv[jss::books][0u][jss::taker_pays] =
651  Account{"gateway"}["USD"](1).value().getJson(
653  jv[jss::books][0u][jss::taker_gets] = Json::objectValue;
654  auto jr = wsc->invoke(method, jv)[jss::result];
655  // NOTE: this error is slightly incongruous with the
656  // equivalent source currency error
657  BEAST_EXPECT(jr[jss::error] == "dstAmtMalformed");
658  BEAST_EXPECT(
659  jr[jss::error_message] ==
660  "Destination amount/currency/issuer is malformed.");
661  }
662 
663  {
664  Json::Value jv;
665  jv[jss::books] = Json::arrayValue;
666  jv[jss::books][0u] = Json::objectValue;
667  jv[jss::books][0u][jss::taker_pays] =
668  Account{"gateway"}["USD"](1).value().getJson(
670  jv[jss::books][0u][jss::taker_gets][jss::currency] = "ZZZZ";
671  auto jr = wsc->invoke(method, jv)[jss::result];
672  // NOTE: this error is slightly incongruous with the
673  // equivalent source currency error
674  BEAST_EXPECT(jr[jss::error] == "dstAmtMalformed");
675  BEAST_EXPECT(
676  jr[jss::error_message] ==
677  "Destination amount/currency/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] = 1;
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][jss::currency] = "USD";
703  jv[jss::books][0u][jss::taker_gets][jss::issuer] =
704  Account{"gateway"}.human() + "DEAD";
705  auto jr = wsc->invoke(method, jv)[jss::result];
706  BEAST_EXPECT(jr[jss::error] == "dstIsrMalformed");
707  BEAST_EXPECT(
708  jr[jss::error_message] == "Destination issuer is malformed.");
709  }
710 
711  {
712  Json::Value jv;
713  jv[jss::books] = Json::arrayValue;
714  jv[jss::books][0u] = Json::objectValue;
715  jv[jss::books][0u][jss::taker_pays] =
716  Account{"gateway"}["USD"](1).value().getJson(
718  jv[jss::books][0u][jss::taker_gets] =
719  Account{"gateway"}["USD"](1).value().getJson(
721  auto jr = wsc->invoke(method, jv)[jss::result];
722  BEAST_EXPECT(jr[jss::error] == "badMarket");
723  BEAST_EXPECT(jr[jss::error_message] == "No such market.");
724  }
725 
726  for (auto const& nonArray : nonArrays)
727  {
728  Json::Value jv;
729  jv[jss::streams] = nonArray;
730  auto jr = wsc->invoke(method, jv)[jss::result];
731  BEAST_EXPECT(jr[jss::error] == "invalidParams");
732  BEAST_EXPECT(jr[jss::error_message] == "Invalid parameters.");
733  }
734 
735  {
736  Json::Value jv;
737  jv[jss::streams] = Json::arrayValue;
738  jv[jss::streams][0u] = 1;
739  auto jr = wsc->invoke(method, jv)[jss::result];
740  BEAST_EXPECT(jr[jss::error] == "malformedStream");
741  BEAST_EXPECT(jr[jss::error_message] == "Stream malformed.");
742  }
743 
744  {
745  Json::Value jv;
746  jv[jss::streams] = Json::arrayValue;
747  jv[jss::streams][0u] = "not_a_stream";
748  auto jr = wsc->invoke(method, jv)[jss::result];
749  BEAST_EXPECT(jr[jss::error] == "malformedStream");
750  BEAST_EXPECT(jr[jss::error_message] == "Stream malformed.");
751  }
752  }
753 
754  void
756  {
757  testcase("HistoryTxStream");
758 
759  using namespace std::chrono_literals;
760  using namespace jtx;
761  using IdxHashVec = std::vector<std::pair<int, std::string>>;
762 
763  Account alice("alice");
764  Account bob("bob");
765  Account carol("carol");
766  Account david("david");
768 
769  /*
770  * return true if the subscribe or unsubscribe result is a success
771  */
772  auto goodSubRPC = [](Json::Value const& subReply) -> bool {
773  return subReply.isMember(jss::result) &&
774  subReply[jss::result].isMember(jss::status) &&
775  subReply[jss::result][jss::status] == jss::success;
776  };
777 
778  /*
779  * try to receive txns from the tx stream subscription via the WSClient.
780  * return {true, true} if received numReplies replies and also
781  * received a tx with the account_history_tx_first == true
782  */
783  auto getTxHash = [](WSClient& wsc,
784  IdxHashVec& v,
785  int numReplies) -> std::pair<bool, bool> {
786  bool first_flag = false;
787 
788  for (int i = 0; i < numReplies; ++i)
789  {
790  std::uint32_t idx{0};
791  auto reply = wsc.getMsg(100ms);
792  if (reply)
793  {
794  auto r = *reply;
795  if (r.isMember(jss::account_history_tx_index))
796  idx = r[jss::account_history_tx_index].asInt();
797  if (r.isMember(jss::account_history_tx_first))
798  first_flag = true;
799  if (r.isMember(jss::transaction) &&
800  r[jss::transaction].isMember(jss::hash))
801  {
802  v.emplace_back(
803  idx, r[jss::transaction][jss::hash].asString());
804  continue;
805  }
806  }
807  return {false, first_flag};
808  }
809 
810  return {true, first_flag};
811  };
812 
813  /*
814  * send payments between the two accounts a and b,
815  * and close ledgersToClose ledgers
816  */
817  auto sendPayments = [](Env& env,
818  Account const& a,
819  Account const& b,
820  int newTxns,
821  std::uint32_t ledgersToClose,
822  int numXRP = 10) {
823  env.memoize(a);
824  env.memoize(b);
825  for (int i = 0; i < newTxns; ++i)
826  {
827  auto& from = (i % 2 == 0) ? a : b;
828  auto& to = (i % 2 == 0) ? b : a;
829  env.apply(
830  pay(from, to, jtx::XRP(numXRP)),
834  }
835  for (int i = 0; i < ledgersToClose; ++i)
836  env.close();
837  return newTxns;
838  };
839 
840  /*
841  * Check if txHistoryVec has every item of accountVec,
842  * and in the same order.
843  * If sizeCompare is false, txHistoryVec is allowed to be larger.
844  */
845  auto hashCompare = [](IdxHashVec const& accountVec,
846  IdxHashVec const& txHistoryVec,
847  bool sizeCompare) -> bool {
848  if (accountVec.empty() || txHistoryVec.empty())
849  return false;
850  if (sizeCompare && accountVec.size() != (txHistoryVec.size()))
851  return false;
852 
853  hash_map<std::string, int> txHistoryMap;
854  for (auto const& tx : txHistoryVec)
855  {
856  txHistoryMap.emplace(tx.second, tx.first);
857  }
858 
859  auto getHistoryIndex = [&](std::size_t i) -> std::optional<int> {
860  if (i >= accountVec.size())
861  return {};
862  auto it = txHistoryMap.find(accountVec[i].second);
863  if (it == txHistoryMap.end())
864  return {};
865  return it->second;
866  };
867 
868  auto firstHistoryIndex = getHistoryIndex(0);
869  if (!firstHistoryIndex)
870  return false;
871  for (std::size_t i = 1; i < accountVec.size(); ++i)
872  {
873  if (auto idx = getHistoryIndex(i);
874  !idx || *idx != *firstHistoryIndex + i)
875  return false;
876  }
877  return true;
878  };
879 
881 
882  {
883  /*
884  * subscribe to an account twice with same WS client,
885  * the second should fail
886  *
887  * also test subscribe to the account before it is created
888  */
889  Env env(*this);
890  auto wscTxHistory = makeWSClient(env.app().config());
891  Json::Value request;
892  request[jss::account_history_tx_stream] = Json::objectValue;
893  request[jss::account_history_tx_stream][jss::account] =
894  alice.human();
895  auto jv = wscTxHistory->invoke("subscribe", request);
896  if (!BEAST_EXPECT(goodSubRPC(jv)))
897  return;
898  jv = wscTxHistory->invoke("subscribe", request);
899  BEAST_EXPECT(!goodSubRPC(jv));
900 
901  /*
902  * unsubscribe history only, future txns should still be streamed
903  */
904  request[jss::account_history_tx_stream][jss::stop_history_tx_only] =
905  true;
906  jv = wscTxHistory->invoke("unsubscribe", request);
907  if (!BEAST_EXPECT(goodSubRPC(jv)))
908  return;
909 
910  sendPayments(env, env.master, alice, 1, 1, 123456);
911 
912  IdxHashVec vec;
913  auto r = getTxHash(*wscTxHistory, vec, 1);
914  if (!BEAST_EXPECT(r.first && r.second))
915  return;
916 
917  /*
918  * unsubscribe, future txns should not be streamed
919  */
920  request[jss::account_history_tx_stream][jss::stop_history_tx_only] =
921  false;
922  jv = wscTxHistory->invoke("unsubscribe", request);
923  BEAST_EXPECT(goodSubRPC(jv));
924 
925  sendPayments(env, env.master, alice, 1, 1);
926  r = getTxHash(*wscTxHistory, vec, 1);
927  BEAST_EXPECT(!r.first);
928  }
929 
930  {
931  /*
932  * subscribe genesis account tx history without txns
933  * subscribe to bob's account after it is created
934  */
935  Env env(*this);
936  auto wscTxHistory = makeWSClient(env.app().config());
937  Json::Value request;
938  request[jss::account_history_tx_stream] = Json::objectValue;
939  request[jss::account_history_tx_stream][jss::account] =
940  "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
941  auto jv = wscTxHistory->invoke("subscribe", request);
942  if (!BEAST_EXPECT(goodSubRPC(jv)))
943  return;
944  IdxHashVec genesisFullHistoryVec;
945  if (!BEAST_EXPECT(
946  !getTxHash(*wscTxHistory, genesisFullHistoryVec, 1).first))
947  return;
948 
949  /*
950  * create bob's account with one tx
951  * the two subscriptions should both stream it
952  */
953  sendPayments(env, env.master, bob, 1, 1, 654321);
954 
955  auto r = getTxHash(*wscTxHistory, genesisFullHistoryVec, 1);
956  if (!BEAST_EXPECT(r.first && r.second))
957  return;
958 
959  request[jss::account_history_tx_stream][jss::account] = bob.human();
960  jv = wscTxHistory->invoke("subscribe", request);
961  if (!BEAST_EXPECT(goodSubRPC(jv)))
962  return;
963  IdxHashVec bobFullHistoryVec;
964  r = getTxHash(*wscTxHistory, bobFullHistoryVec, 1);
965  if (!BEAST_EXPECT(r.first && r.second))
966  return;
967  BEAST_EXPECT(
968  bobFullHistoryVec.back().second ==
969  genesisFullHistoryVec.back().second);
970 
971  /*
972  * unsubscribe to prepare next test
973  */
974  jv = wscTxHistory->invoke("unsubscribe", request);
975  if (!BEAST_EXPECT(goodSubRPC(jv)))
976  return;
977  request[jss::account_history_tx_stream][jss::account] =
978  "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
979  jv = wscTxHistory->invoke("unsubscribe", request);
980  BEAST_EXPECT(goodSubRPC(jv));
981 
982  /*
983  * add more txns, then subscribe bob tx history and
984  * genesis account tx history. Their earliest txns should match.
985  */
986  sendPayments(env, env.master, bob, 30, 300);
987  wscTxHistory = makeWSClient(env.app().config());
988  request[jss::account_history_tx_stream][jss::account] = bob.human();
989  jv = wscTxHistory->invoke("subscribe", request);
990 
991  bobFullHistoryVec.clear();
992  BEAST_EXPECT(
993  getTxHash(*wscTxHistory, bobFullHistoryVec, 31).second);
994  jv = wscTxHistory->invoke("unsubscribe", request);
995 
996  request[jss::account_history_tx_stream][jss::account] =
997  "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
998  jv = wscTxHistory->invoke("subscribe", request);
999  genesisFullHistoryVec.clear();
1000  BEAST_EXPECT(
1001  getTxHash(*wscTxHistory, genesisFullHistoryVec, 31).second);
1002  jv = wscTxHistory->invoke("unsubscribe", request);
1003 
1004  BEAST_EXPECT(
1005  bobFullHistoryVec.back().second ==
1006  genesisFullHistoryVec.back().second);
1007  }
1008 
1009  {
1010  /*
1011  * subscribe account and subscribe account tx history
1012  * and compare txns streamed
1013  */
1014  Env env(*this);
1015  auto wscAccount = makeWSClient(env.app().config());
1016  auto wscTxHistory = makeWSClient(env.app().config());
1017 
1018  std::array<Account, 2> accounts = {alice, bob};
1019  env.fund(XRP(222222), accounts);
1020  env.close();
1021 
1022  // subscribe account
1023  Json::Value stream = Json::objectValue;
1024  stream[jss::accounts] = Json::arrayValue;
1025  stream[jss::accounts].append(alice.human());
1026  auto jv = wscAccount->invoke("subscribe", stream);
1027 
1028  sendPayments(env, alice, bob, 5, 1);
1029  sendPayments(env, alice, bob, 5, 1);
1030  IdxHashVec accountVec;
1031  if (!BEAST_EXPECT(getTxHash(*wscAccount, accountVec, 10).first))
1032  return;
1033 
1034  // subscribe account tx history
1035  Json::Value request;
1036  request[jss::account_history_tx_stream] = Json::objectValue;
1037  request[jss::account_history_tx_stream][jss::account] =
1038  alice.human();
1039  jv = wscTxHistory->invoke("subscribe", request);
1040 
1041  // compare historical txns
1042  IdxHashVec txHistoryVec;
1043  if (!BEAST_EXPECT(getTxHash(*wscTxHistory, txHistoryVec, 10).first))
1044  return;
1045  if (!BEAST_EXPECT(hashCompare(accountVec, txHistoryVec, true)))
1046  return;
1047 
1048  {
1049  // take out all history txns from stream to prepare next test
1050  IdxHashVec initFundTxns;
1051  if (!BEAST_EXPECT(
1052  getTxHash(*wscTxHistory, initFundTxns, 10).second))
1053  return;
1054  }
1055 
1056  // compare future txns
1057  sendPayments(env, alice, bob, 10, 1);
1058  if (!BEAST_EXPECT(getTxHash(*wscAccount, accountVec, 10).first))
1059  return;
1060  if (!BEAST_EXPECT(getTxHash(*wscTxHistory, txHistoryVec, 10).first))
1061  return;
1062  if (!BEAST_EXPECT(hashCompare(accountVec, txHistoryVec, true)))
1063  return;
1064  wscTxHistory->invoke("unsubscribe", request);
1065  wscAccount->invoke("unsubscribe", stream);
1066  }
1067 
1068  {
1069  /*
1070  * alice issues USD to carol
1071  * mix USD and XRP payments
1072  */
1073  Env env(*this);
1074  auto const USD_a = alice["USD"];
1075 
1076  std::array<Account, 2> accounts = {alice, carol};
1077  env.fund(XRP(333333), accounts);
1078  env.trust(USD_a(20000), carol);
1079  env.close();
1080 
1081  auto mixedPayments = [&]() -> int {
1082  sendPayments(env, alice, carol, 1, 0);
1083  env(pay(alice, carol, USD_a(100)));
1084  env.close();
1085  return 2;
1086  };
1087 
1088  // subscribe
1089  Json::Value request;
1090  request[jss::account_history_tx_stream] = Json::objectValue;
1091  request[jss::account_history_tx_stream][jss::account] =
1092  carol.human();
1093  auto ws = makeWSClient(env.app().config());
1094  auto jv = ws->invoke("subscribe", request);
1095  {
1096  // take out existing txns from the stream
1097  IdxHashVec tempVec;
1098  getTxHash(*ws, tempVec, 100);
1099  }
1100 
1101  auto count = mixedPayments();
1102  IdxHashVec vec1;
1103  if (!BEAST_EXPECT(getTxHash(*ws, vec1, count).first))
1104  return;
1105  ws->invoke("unsubscribe", request);
1106  }
1107 
1108  {
1109  /*
1110  * long transaction history
1111  */
1112  Env env(*this);
1113  std::array<Account, 2> accounts = {alice, carol};
1114  env.fund(XRP(444444), accounts);
1115  env.close();
1116 
1117  // many payments, and close lots of ledgers
1118  auto oneRound = [&](int numPayments) {
1119  return sendPayments(env, alice, carol, numPayments, 300);
1120  };
1121 
1122  // subscribe
1123  Json::Value request;
1124  request[jss::account_history_tx_stream] = Json::objectValue;
1125  request[jss::account_history_tx_stream][jss::account] =
1126  carol.human();
1127  auto wscLong = makeWSClient(env.app().config());
1128  auto jv = wscLong->invoke("subscribe", request);
1129  {
1130  // take out existing txns from the stream
1131  IdxHashVec tempVec;
1132  getTxHash(*wscLong, tempVec, 100);
1133  }
1134 
1135  // repeat the payments many rounds
1136  for (int kk = 2; kk < 10; ++kk)
1137  {
1138  auto count = oneRound(kk);
1139  IdxHashVec vec1;
1140  if (!BEAST_EXPECT(getTxHash(*wscLong, vec1, count).first))
1141  return;
1142 
1143  // another subscribe, only for this round
1144  auto wscShort = makeWSClient(env.app().config());
1145  auto jv = wscShort->invoke("subscribe", request);
1146  IdxHashVec vec2;
1147  if (!BEAST_EXPECT(getTxHash(*wscShort, vec2, count).first))
1148  return;
1149  if (!BEAST_EXPECT(hashCompare(vec1, vec2, true)))
1150  return;
1151  wscShort->invoke("unsubscribe", request);
1152  }
1153  }
1154  }
1155 
1156  void
1157  run() override
1158  {
1159  using namespace test::jtx;
1161  FeatureBitset const xrpFees{featureXRPFees};
1162 
1163  testServer();
1164  testLedger();
1165  testTransactions();
1166  testManifests();
1167  testValidations(all - xrpFees);
1169  testSubErrors(true);
1170  testSubErrors(false);
1171  testSubByUrl();
1173  }
1174 };
1175 
1176 BEAST_DEFINE_TESTSUITE(Subscribe, app, ripple);
1177 
1178 } // namespace test
1179 } // 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:940
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:755
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:107
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:116
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:256
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: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)
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:1157
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:225
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:462
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:490
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)