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