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