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