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