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