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