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