1#include <test/jtx/Env.h>
2#include <test/jtx/WSClient.h>
3#include <test/jtx/amount.h>
4#include <test/jtx/batch.h>
5#include <test/jtx/domain.h>
6#include <test/jtx/envconfig.h>
7#include <test/jtx/escrow.h>
8#include <test/jtx/fee.h>
9#include <test/jtx/mpt.h>
10#include <test/jtx/offer.h>
11#include <test/jtx/owners.h>
12#include <test/jtx/paths.h>
13#include <test/jtx/pay.h>
14#include <test/jtx/permissioned_dex.h>
15#include <test/jtx/sendmax.h>
16#include <test/jtx/seq.h>
17#include <test/jtx/sig.h>
18#include <test/jtx/tags.h>
19#include <test/jtx/token.h>
20#include <test/jtx/txflags.h>
22#include <xrpld/app/main/LoadManager.h>
23#include <xrpld/core/Config.h>
25#include <xrpl/basics/UnorderedContainers.h>
26#include <xrpl/basics/base_uint.h>
27#include <xrpl/basics/strHex.h>
28#include <xrpl/beast/unit_test/suite.h>
29#include <xrpl/config/Constants.h>
30#include <xrpl/core/NetworkIDService.h>
31#include <xrpl/json/json_value.h>
32#include <xrpl/json/to_string.h>
33#include <xrpl/protocol/AccountID.h>
34#include <xrpl/protocol/Feature.h>
35#include <xrpl/protocol/Indexes.h>
36#include <xrpl/protocol/KeyType.h>
37#include <xrpl/protocol/PublicKey.h>
38#include <xrpl/protocol/SField.h>
39#include <xrpl/protocol/STValidation.h>
40#include <xrpl/protocol/SecretKey.h>
41#include <xrpl/protocol/Seed.h>
42#include <xrpl/protocol/SeqProxy.h>
43#include <xrpl/protocol/TxFlags.h>
44#include <xrpl/protocol/UintTypes.h>
45#include <xrpl/protocol/XRPAmount.h>
46#include <xrpl/protocol/jss.h>
47#include <xrpl/protocol/tokens.h>
48#include <xrpl/server/LoadFeeTrack.h>
49#include <xrpl/server/NetworkOPs.h>
73 using namespace std::chrono_literals;
82 stream[jss::streams].append(
"server");
83 auto jv = wsc->invoke(
"subscribe", stream);
84 if (wsc->version() == 2)
86 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] ==
"2.0");
87 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] ==
"2.0");
88 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
90 BEAST_EXPECT(jv[jss::status] ==
"success");
102 for (
int i = 0; i < 5; ++i)
108 wsc->findMsg(5s, [&](
auto const& jv) { return jv[jss::type] ==
"serverStatus"; }));
113 auto jv = wsc->invoke(
"unsubscribe", stream);
114 if (wsc->version() == 2)
116 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] ==
"2.0");
117 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] ==
"2.0");
118 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
120 BEAST_EXPECT(jv[jss::status] ==
"success");
126 for (
int i = 0; i < 5; ++i)
131 auto jvo = wsc->getMsg(10ms);
132 BEAST_EXPECTS(!jvo,
"getMsg: " +
to_string(jvo.value()));
139 using namespace std::chrono_literals;
148 stream[jss::streams].append(
"ledger");
149 auto jv = wsc->invoke(
"subscribe", stream);
150 if (wsc->version() == 2)
152 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] ==
"2.0");
153 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] ==
"2.0");
154 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
156 BEAST_EXPECT(jv[jss::result][jss::ledger_index] == 2);
166 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
167 return jv[jss::ledger_index] == 3 &&
168 jv[jss::network_id] == env.app().getNetworkIDService().getNetworkID();
177 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
178 return jv[jss::ledger_index] == 4 &&
179 jv[jss::network_id] == env.app().getNetworkIDService().getNetworkID();
184 auto jv = wsc->invoke(
"unsubscribe", stream);
185 if (wsc->version() == 2)
187 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] ==
"2.0");
188 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] ==
"2.0");
189 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
191 BEAST_EXPECT(jv[jss::status] ==
"success");
197 using namespace std::chrono_literals;
200 auto baseFee = env.
current()->fees().base.drops();
207 stream[jss::streams].append(
"transactions");
208 auto jv = wsc->invoke(
"subscribe", stream);
209 if (wsc->version() == 2)
211 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] ==
"2.0");
212 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] ==
"2.0");
213 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
215 BEAST_EXPECT(jv[jss::status] ==
"success");
223 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
224 return jv[jss::meta][
"AffectedNodes"][1u][
"CreatedNode"][
"NewFields"]
225 [jss::Account] == Account(
"alice").human() &&
226 jv[jss::transaction][jss::TransactionType] == jss::Payment &&
227 jv[jss::transaction][jss::DeliverMax] ==
228 std::to_string(10000000000 + baseFee) &&
229 jv[jss::transaction][jss::Fee] == std::to_string(baseFee) &&
230 jv[jss::transaction][jss::Sequence] == 1;
234 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
235 return jv[jss::meta][
"AffectedNodes"][0u][
"ModifiedNode"][
"FinalFields"]
236 [jss::Account] == Account(
"alice").human();
243 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
244 return jv[jss::meta][
"AffectedNodes"][1u][
"CreatedNode"][
"NewFields"]
246 == Account(
"bob").human() &&
247 jv[jss::transaction][jss::TransactionType]
249 jv[jss::transaction][jss::DeliverMax]
250 == std::to_string(10000000000 + baseFee) &&
251 jv[jss::transaction][jss::Fee]
252 == std::to_string(baseFee) &&
253 jv[jss::transaction][jss::Sequence]
258 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
259 return jv[jss::meta][
"AffectedNodes"][0u][
"ModifiedNode"][
"FinalFields"]
260 [jss::Account] == Account(
"bob").human();
266 auto jv = wsc->invoke(
"unsubscribe", stream);
267 if (wsc->version() == 2)
269 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] ==
"2.0");
270 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] ==
"2.0");
271 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
273 BEAST_EXPECT(jv[jss::status] ==
"success");
280 stream[jss::accounts].append(
Account(
"alice").human());
281 auto jv = wsc->invoke(
"subscribe", stream);
282 if (wsc->version() == 2)
284 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] ==
"2.0");
285 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] ==
"2.0");
286 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
288 BEAST_EXPECT(jv[jss::status] ==
"success");
295 BEAST_EXPECT(!wsc->getMsg(10ms));
302 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
303 return jv[jss::meta][
"AffectedNodes"][1u][
"ModifiedNode"][
"FinalFields"]
304 [jss::Account] == Account(
"alice").human();
307 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
308 return jv[jss::meta][
"AffectedNodes"][1u][
"CreatedNode"][
"NewFields"][
"LowLimit"]
309 [jss::issuer] == Account(
"alice").human();
314 auto jv = wsc->invoke(
"unsubscribe", stream);
315 if (wsc->version() == 2)
317 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] ==
"2.0");
318 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] ==
"2.0");
319 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
321 BEAST_EXPECT(jv[jss::status] ==
"success");
327 testcase(
"transactions API version 2");
329 using namespace std::chrono_literals;
332 cfg->fees.referenceFee = 10;
341 stream[jss::api_version] = 2;
343 stream[jss::streams].append(
"transactions");
344 auto jv = wsc->invoke(
"subscribe", stream);
345 if (wsc->version() == 2)
347 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] ==
"2.0");
348 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] ==
"2.0");
349 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
351 BEAST_EXPECT(jv[jss::status] ==
"success");
355 env.fund(
XRP(10000),
"alice");
356 BEAST_EXPECT(env.syncClose());
359 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
360 return jv[jss::meta][
"AffectedNodes"][1u][
"CreatedNode"][
"NewFields"]
362 == Account(
"alice").human() &&
363 jv[jss::close_time_iso]
364 ==
"2000-01-01T00:00:10Z" &&
365 jv[jss::validated] == true &&
366 jv[jss::ledger_hash] ==
367 "0F1A9E0C109ADEF6DA2BDE19217C12BBEC57174CBDBD212B0EBDC1CEDB"
369 !jv[jss::inLedger] &&
370 jv[jss::ledger_index] == 3 &&
371 jv[jss::tx_json][jss::TransactionType]
373 jv[jss::tx_json][jss::DeliverMax]
375 !jv[jss::tx_json].isMember(jss::Amount) &&
376 jv[jss::tx_json][jss::Fee]
378 jv[jss::tx_json][jss::Sequence]
383 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
384 return jv[jss::meta][
"AffectedNodes"][0u][
"ModifiedNode"][
"FinalFields"]
385 [jss::Account] == Account(
"alice").human();
391 auto jv = wsc->invoke(
"unsubscribe", stream);
392 if (wsc->version() == 2)
394 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] ==
"2.0");
395 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] ==
"2.0");
396 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
398 BEAST_EXPECT(jv[jss::status] ==
"success");
413 stream[jss::streams].append(
"manifests");
414 auto jv = wsc->invoke(
"subscribe", stream);
415 if (wsc->version() == 2)
417 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] ==
"2.0");
418 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] ==
"2.0");
419 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
421 BEAST_EXPECT(jv[jss::status] ==
"success");
425 auto jv = wsc->invoke(
"unsubscribe", stream);
426 if (wsc->version() == 2)
428 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] ==
"2.0");
429 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] ==
"2.0");
430 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
432 BEAST_EXPECT(jv[jss::status] ==
"success");
444 auto const parsedseed =
446 if (BEAST_EXPECT(parsedseed); not parsedseed.has_value())
460 stream[jss::streams].append(
"validations");
461 auto jv = wsc->invoke(
"subscribe", stream);
462 if (wsc->version() == 2)
464 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] ==
"2.0");
465 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] ==
"2.0");
466 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
468 BEAST_EXPECT(jv[jss::status] ==
"success");
473 auto validValidationFields = [&env, &valPublicKey](
json::Value const& jv) {
474 if (jv[jss::type] !=
"validationReceived")
477 if (jv[jss::validation_public_key].asString() != valPublicKey)
489 if (jv[jss::full] !=
true)
492 if (jv.isMember(jss::load_fee))
495 if (!jv.isMember(jss::signature))
498 if (!jv.isMember(jss::signing_time))
501 if (!jv.isMember(jss::cookie))
504 if (!jv.isMember(jss::validated_hash))
508 if (!jv.isMember(jss::network_id) || jv[jss::network_id] != netID)
528 while (env.
closed()->header().seq < 300)
531 using namespace std::chrono_literals;
532 BEAST_EXPECT(wsc->findMsg(5s, validValidationFields));
537 auto jv = wsc->invoke(
"unsubscribe", stream);
538 if (wsc->version() == 2)
540 BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] ==
"2.0");
541 BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] ==
"2.0");
542 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
544 BEAST_EXPECT(jv[jss::status] ==
"success");
555 jv[jss::url] =
"http://localhost/events";
556 jv[jss::url_username] =
"admin";
557 jv[jss::url_password] =
"password";
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");
563 jv[jss::streams][0u] =
"ledger";
564 jr = env.
rpc(
"json",
"subscribe",
to_string(jv))[jss::result];
565 BEAST_EXPECT(jr[jss::status] ==
"success");
568 jr = env.
rpc(
"json",
"unsubscribe",
to_string(jv))[jss::result];
569 BEAST_EXPECT(jr[jss::status] ==
"success");
571 jv[jss::streams][0u] =
"validations";
572 jr = env.
rpc(
"json",
"unsubscribe",
to_string(jv))[jss::result];
573 BEAST_EXPECT(jr[jss::status] ==
"success");
580 auto const method = subscribe ?
"subscribe" :
"unsubscribe";
581 testcase <<
"Error cases for " << method;
587 auto const jr = env.
rpc(
"json", method,
"{}")[jss::result];
588 BEAST_EXPECT(jr[jss::error] ==
"invalidParams");
589 BEAST_EXPECT(jr[jss::error_message] ==
"Invalid parameters.");
594 jv[jss::url] =
"not-a-url";
595 jv[jss::username] =
"admin";
596 jv[jss::password] =
"password";
597 auto const jr = env.
rpc(
"json", method,
to_string(jv))[jss::result];
600 BEAST_EXPECT(jr[jss::error] ==
"invalidParams");
601 BEAST_EXPECT(jr[jss::error_message] ==
"Failed to parse url.");
609 jv[jss::url] =
"ftp://scheme.not.supported.tld";
610 auto const jr = env.
rpc(
"json", method,
to_string(jv))[jss::result];
613 BEAST_EXPECT(jr[jss::error] ==
"invalidParams");
614 BEAST_EXPECT(jr[jss::error_message] ==
"Only http and https is supported.");
621 jv[jss::url] =
"no-url";
622 auto const jr = envNonadmin.
rpc(
"json", method,
to_string(jv))[jss::result];
623 BEAST_EXPECT(jr[jss::error] ==
"noPermission");
624 BEAST_EXPECT(jr[jss::error_message] ==
"You don't have permission for this command.");
636 for (
auto const& f : {jss::accounts_proposed, jss::accounts})
638 for (
auto const& nonArray : nonArrays)
642 auto const jr = wsc->invoke(method, jv)[jss::result];
643 BEAST_EXPECT(jr[jss::error] ==
"invalidParams");
644 BEAST_EXPECT(jr[jss::error_message] ==
"Invalid parameters.");
650 auto const jr = wsc->invoke(method, jv)[jss::result];
651 BEAST_EXPECT(jr[jss::error] ==
"actMalformed");
652 BEAST_EXPECT(jr[jss::error_message] ==
"Account malformed.");
656 for (
auto const& nonArray : nonArrays)
659 jv[jss::mpt_issuances] = nonArray;
660 auto jr = wsc->invoke(method, jv)[jss::result];
661 BEAST_EXPECT(jr[jss::error] ==
"invalidParams");
662 BEAST_EXPECT(jr[jss::error_message] ==
"Invalid parameters.");
668 jv[jss::mpt_issuances][0u] = 1;
669 auto jr = wsc->invoke(method, jv)[jss::result];
670 BEAST_EXPECT(jr[jss::error] ==
"invalidParams");
671 BEAST_EXPECT(jr[jss::error_message] ==
"Invalid parameters.");
677 jv[jss::mpt_issuances][0u] =
"not-an-mpt-issuance-id";
678 auto jr = wsc->invoke(method, jv)[jss::result];
679 BEAST_EXPECT(jr[jss::error] ==
"invalidParams");
680 BEAST_EXPECT(jr[jss::error_message] ==
"Invalid parameters.");
686 jv[jss::mpt_issuances][0u] =
"0123456789ABCDEF";
687 auto jr = wsc->invoke(method, jv)[jss::result];
688 BEAST_EXPECT(jr[jss::error] ==
"invalidParams");
689 BEAST_EXPECT(jr[jss::error_message] ==
"Invalid parameters.");
692 for (
auto const& nonArray : nonArrays)
695 jv[jss::books] = nonArray;
696 auto const jr = wsc->invoke(method, jv)[jss::result];
697 BEAST_EXPECT(jr[jss::error] ==
"invalidParams");
698 BEAST_EXPECT(jr[jss::error_message] ==
"Invalid parameters.");
704 jv[jss::books][0u] = 1;
705 auto const jr = wsc->invoke(method, jv)[jss::result];
706 BEAST_EXPECT(jr[jss::error] ==
"invalidParams");
707 BEAST_EXPECT(jr[jss::error_message] ==
"Invalid parameters.");
716 auto const jr = wsc->invoke(method, jv)[jss::result];
718 BEAST_EXPECT(jr[jss::error] ==
"srcCurMalformed");
719 BEAST_EXPECT(jr[jss::error_message] ==
"Source currency is malformed.");
728 jv[jss::books][0u][jss::taker_pays][jss::currency] =
"ZZZZ";
729 auto const jr = wsc->invoke(method, jv)[jss::result];
730 BEAST_EXPECT(jr[jss::error] ==
"srcCurMalformed");
731 BEAST_EXPECT(jr[jss::error_message] ==
"Source currency is malformed.");
740 jv[jss::books][0u][jss::taker_pays][jss::currency] =
"USD";
741 jv[jss::books][0u][jss::taker_pays][jss::issuer] = 1;
742 auto const jr = wsc->invoke(method, jv)[jss::result];
743 BEAST_EXPECT(jr[jss::error] ==
"srcIsrMalformed");
744 BEAST_EXPECT(jr[jss::error_message] ==
"Source issuer is malformed.");
753 jv[jss::books][0u][jss::taker_pays][jss::currency] =
"USD";
754 jv[jss::books][0u][jss::taker_pays][jss::issuer] =
Account{
"gateway"}.
human() +
"DEAD";
755 auto const jr = wsc->invoke(method, jv)[jss::result];
756 BEAST_EXPECT(jr[jss::error] ==
"srcIsrMalformed");
757 BEAST_EXPECT(jr[jss::error_message] ==
"Source issuer is malformed.");
764 jv[jss::books][0u][jss::taker_pays] =
767 auto const jr = wsc->invoke(method, jv)[jss::result];
769 BEAST_EXPECT(jr[jss::error] ==
"dstAmtMalformed");
771 jr[jss::error_message] ==
"Destination amount/currency/issuer is malformed.");
778 jv[jss::books][0u][jss::taker_pays] =
780 jv[jss::books][0u][jss::taker_gets][jss::currency] =
"ZZZZ";
781 auto const jr = wsc->invoke(method, jv)[jss::result];
784 BEAST_EXPECT(jr[jss::error] ==
"dstAmtMalformed");
786 jr[jss::error_message] ==
"Destination amount/currency/issuer is malformed.");
793 jv[jss::books][0u][jss::taker_pays] =
795 jv[jss::books][0u][jss::taker_gets][jss::currency] =
"USD";
796 jv[jss::books][0u][jss::taker_gets][jss::issuer] = 1;
797 auto const jr = wsc->invoke(method, jv)[jss::result];
798 BEAST_EXPECT(jr[jss::error] ==
"dstIsrMalformed");
799 BEAST_EXPECT(jr[jss::error_message] ==
"Destination issuer is malformed.");
806 jv[jss::books][0u][jss::taker_pays] =
808 jv[jss::books][0u][jss::taker_gets][jss::currency] =
"USD";
809 jv[jss::books][0u][jss::taker_gets][jss::issuer] =
Account{
"gateway"}.
human() +
"DEAD";
810 auto const jr = wsc->invoke(method, jv)[jss::result];
811 BEAST_EXPECT(jr[jss::error] ==
"dstIsrMalformed");
812 BEAST_EXPECT(jr[jss::error_message] ==
"Destination issuer is malformed.");
819 jv[jss::books][0u][jss::taker_pays] =
821 jv[jss::books][0u][jss::taker_gets] =
823 auto const jr = wsc->invoke(method, jv)[jss::result];
824 BEAST_EXPECT(jr[jss::error] ==
"badMarket");
825 BEAST_EXPECT(jr[jss::error_message] ==
"No such market.");
828 for (
auto const& nonArray : nonArrays)
831 jv[jss::streams] = nonArray;
832 auto const jr = wsc->invoke(method, jv)[jss::result];
833 BEAST_EXPECT(jr[jss::error] ==
"invalidParams");
834 BEAST_EXPECT(jr[jss::error_message] ==
"Invalid parameters.");
840 jv[jss::streams][0u] = 1;
841 auto const jr = wsc->invoke(method, jv)[jss::result];
842 BEAST_EXPECT(jr[jss::error] ==
"malformedStream");
843 BEAST_EXPECT(jr[jss::error_message] ==
"Stream malformed.");
849 jv[jss::streams][0u] =
"not_a_stream";
850 auto const jr = wsc->invoke(method, jv)[jss::result];
851 BEAST_EXPECT(jr[jss::error] ==
"malformedStream");
852 BEAST_EXPECT(jr[jss::error_message] ==
"Stream malformed.");
862 jv[jss::books][0u][jss::taker_pays] =
864 jv[jss::books][0u][jss::taker_gets][jss::currency] =
"XRP";
865 jv[jss::books][0u][jss::taker] = 1;
866 auto const jr = wsc->invoke(method, jv)[jss::result];
867 BEAST_EXPECTS(jr[jss::error] ==
"actMalformed", jr.toStyledString());
868 BEAST_EXPECT(jr[jss::error_message] ==
"Account malformed.");
876 jv[jss::books][0u][jss::taker_pays] =
878 jv[jss::books][0u][jss::taker_gets][jss::currency] =
"XRP";
879 jv[jss::books][0u][jss::taker] =
"not_an_account";
880 auto const jr = wsc->invoke(method, jv)[jss::result];
881 BEAST_EXPECTS(jr[jss::error] ==
"actMalformed", jr.toStyledString());
882 BEAST_EXPECT(jr[jss::error_message] ==
"Account malformed.");
890 jv[jss::books][0u][jss::taker_pays] =
892 jv[jss::books][0u][jss::taker_gets][jss::currency] =
"XRP";
893 jv[jss::books][0u][jss::taker] =
Account{
"alice"}.
human() +
"DEAD";
894 auto const jr = wsc->invoke(method, jv)[jss::result];
895 BEAST_EXPECTS(jr[jss::error] ==
"actMalformed", jr.toStyledString());
896 BEAST_EXPECT(jr[jss::error_message] ==
"Account malformed.");
906 using namespace std::chrono_literals;
919 auto goodSubRPC = [](
json::Value const& subReply) ->
bool {
920 return subReply.isMember(jss::result) && subReply[jss::result].isMember(jss::status) &&
921 subReply[jss::result][jss::status] == jss::success;
934 bool firstFlag =
false;
936 for (
int i = 0; i < numReplies; ++i)
939 auto reply = wsc.
getMsg(timeout);
943 if (r.isMember(jss::account_history_tx_index))
944 idx = r[jss::account_history_tx_index].asInt();
945 if (r.isMember(jss::account_history_tx_first))
947 bool const boundary = r.isMember(jss::account_history_boundary);
948 int const ledgerIdx = r[jss::ledger_index].asInt();
949 if (r.isMember(jss::transaction) && r[jss::transaction].isMember(jss::hash))
951 auto t{r[jss::transaction]};
952 v.emplace_back(idx, t[jss::hash].asString(), boundary, ledgerIdx);
956 return {
false, firstFlag};
959 return {
true, firstFlag};
966 auto sendPayments = [
this](
975 for (
int i = 0; i < newTxns; ++i)
977 auto& from = (i % 2 == 0) ? a : b;
978 auto& to = (i % 2 == 0) ? b : a;
984 for (
int i = 0; i < ledgersToClose; ++i)
994 auto hashCompare = [](IdxHashVec
const& accountVec,
995 IdxHashVec
const& txHistoryVec,
996 bool sizeCompare) ->
bool {
997 if (accountVec.empty() || txHistoryVec.empty())
999 if (sizeCompare && accountVec.size() != (txHistoryVec.size()))
1003 for (
auto const& tx : txHistoryVec)
1005 txHistoryMap.
emplace(std::get<1>(tx), std::get<0>(tx));
1009 if (i >= accountVec.size())
1011 auto it = txHistoryMap.
find(std::get<1>(accountVec[i]));
1012 if (it == txHistoryMap.
end())
1017 auto firstHistoryIndex = getHistoryIndex(0);
1018 if (!firstHistoryIndex)
1020 for (
std::size_t i = 1; i < accountVec.size(); ++i)
1022 if (
auto idx = getHistoryIndex(i); !idx || *idx != *firstHistoryIndex + i)
1058 auto checkBoundary = [](IdxHashVec
const& vec,
bool ) {
1059 size_t const numTx = vec.size();
1060 for (
size_t i = 0; i < numTx; ++i)
1062 auto [idx, hash, boundary, ledger] = vec[i];
1063 if ((i + 1 == numTx || ledger != std::get<3>(vec[i + 1])) != boundary)
1082 request[jss::account_history_tx_stream][jss::account] = alice.
human();
1083 auto jv = wscTxHistory->invoke(
"subscribe", request);
1084 if (!BEAST_EXPECT(goodSubRPC(jv)))
1087 jv = wscTxHistory->invoke(
"subscribe", request);
1088 BEAST_EXPECT(!goodSubRPC(jv));
1093 request[jss::account_history_tx_stream][jss::stop_history_tx_only] =
true;
1094 jv = wscTxHistory->invoke(
"unsubscribe", request);
1095 if (!BEAST_EXPECT(goodSubRPC(jv)))
1098 sendPayments(env, env.
master, alice, 1, 1, 123456);
1101 auto r = getTxHash(*wscTxHistory, vec, 1);
1102 if (!BEAST_EXPECT(r.first && r.second))
1108 request[jss::account_history_tx_stream][jss::stop_history_tx_only] =
false;
1109 jv = wscTxHistory->invoke(
"unsubscribe", request);
1110 BEAST_EXPECT(goodSubRPC(jv));
1112 sendPayments(env, env.
master, alice, 1, 1);
1113 r = getTxHash(*wscTxHistory, vec, 1, 10ms);
1114 BEAST_EXPECT(!r.first);
1125 request[jss::account_history_tx_stream][jss::account] =
1126 "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
1127 auto jv = wscTxHistory->invoke(
"subscribe", request);
1128 if (!BEAST_EXPECT(goodSubRPC(jv)))
1130 IdxHashVec genesisFullHistoryVec;
1132 if (!BEAST_EXPECT(!getTxHash(*wscTxHistory, genesisFullHistoryVec, 1, 10ms).first))
1139 sendPayments(env, env.
master, bob, 1, 1, 654321);
1141 auto r = getTxHash(*wscTxHistory, genesisFullHistoryVec, 1);
1142 if (!BEAST_EXPECT(r.first && r.second))
1145 request[jss::account_history_tx_stream][jss::account] = bob.
human();
1146 jv = wscTxHistory->invoke(
"subscribe", request);
1147 if (!BEAST_EXPECT(goodSubRPC(jv)))
1149 IdxHashVec bobFullHistoryVec;
1151 r = getTxHash(*wscTxHistory, bobFullHistoryVec, 1);
1152 if (!BEAST_EXPECT(r.first && r.second))
1155 std::get<1>(bobFullHistoryVec.back()) == std::get<1>(genesisFullHistoryVec.back()));
1160 jv = wscTxHistory->invoke(
"unsubscribe", request);
1161 if (!BEAST_EXPECT(goodSubRPC(jv)))
1163 request[jss::account_history_tx_stream][jss::account] =
1164 "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
1165 jv = wscTxHistory->invoke(
"unsubscribe", request);
1166 BEAST_EXPECT(goodSubRPC(jv));
1172 sendPayments(env, env.
master, bob, 30, 300);
1174 request[jss::account_history_tx_stream][jss::account] = bob.
human();
1175 jv = wscTxHistory->invoke(
"subscribe", request);
1177 bobFullHistoryVec.
clear();
1178 BEAST_EXPECT(getTxHash(*wscTxHistory, bobFullHistoryVec, 31).second);
1179 jv = wscTxHistory->invoke(
"unsubscribe", request);
1181 request[jss::account_history_tx_stream][jss::account] =
1182 "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
1183 jv = wscTxHistory->invoke(
"subscribe", request);
1184 genesisFullHistoryVec.
clear();
1186 BEAST_EXPECT(getTxHash(*wscTxHistory, genesisFullHistoryVec, 31).second);
1187 jv = wscTxHistory->invoke(
"unsubscribe", request);
1190 std::get<1>(bobFullHistoryVec.back()) == std::get<1>(genesisFullHistoryVec.back()));
1203 env.
fund(
XRP(222222), accounts);
1209 stream[jss::accounts].append(alice.
human());
1210 auto jv = wscAccount->invoke(
"subscribe", stream);
1212 sendPayments(env, alice, bob, 5, 1);
1213 sendPayments(env, alice, bob, 5, 1);
1214 IdxHashVec accountVec;
1215 if (!BEAST_EXPECT(getTxHash(*wscAccount, accountVec, 10).first))
1221 request[jss::account_history_tx_stream][jss::account] = alice.
human();
1222 jv = wscTxHistory->invoke(
"subscribe", request);
1225 IdxHashVec txHistoryVec;
1226 if (!BEAST_EXPECT(getTxHash(*wscTxHistory, txHistoryVec, 10).first))
1228 if (!BEAST_EXPECT(hashCompare(accountVec, txHistoryVec,
true)))
1233 if (!BEAST_EXPECT(checkBoundary(txHistoryVec,
false)))
1238 IdxHashVec initFundTxns;
1239 if (!BEAST_EXPECT(getTxHash(*wscTxHistory, initFundTxns, 10).second) ||
1240 !BEAST_EXPECT(checkBoundary(initFundTxns,
false)))
1245 sendPayments(env, alice, bob, 10, 1);
1246 if (!BEAST_EXPECT(getTxHash(*wscAccount, accountVec, 10).first))
1248 if (!BEAST_EXPECT(getTxHash(*wscTxHistory, txHistoryVec, 10).first))
1250 if (!BEAST_EXPECT(hashCompare(accountVec, txHistoryVec,
true)))
1255 if (!BEAST_EXPECT(checkBoundary(txHistoryVec,
false)))
1258 wscTxHistory->invoke(
"unsubscribe", request);
1259 wscAccount->invoke(
"unsubscribe", stream);
1268 auto const usdA = alice[
"USD"];
1271 env.
fund(
XRP(333333), accounts);
1272 env.
trust(usdA(20000), carol);
1275 auto mixedPayments = [&]() ->
int {
1276 sendPayments(env, alice, carol, 1, 0);
1277 env(
pay(alice, carol, usdA(100)));
1285 request[jss::account_history_tx_stream][jss::account] = carol.
human();
1287 auto jv = ws->invoke(
"subscribe", request);
1292 getTxHash(*ws, tempVec, 100, 1000ms);
1295 auto count = mixedPayments();
1297 if (!BEAST_EXPECT(getTxHash(*ws, vec1, count).first))
1299 ws->invoke(
"unsubscribe", request);
1308 env.
fund(
XRP(444444), accounts);
1312 auto oneRound = [&](
int numPayments) {
1313 return sendPayments(env, alice, carol, numPayments, 300);
1319 request[jss::account_history_tx_stream][jss::account] = carol.
human();
1321 auto jv = wscLong->invoke(
"subscribe", request);
1326 getTxHash(*wscLong, tempVec, 100, 1000ms);
1330 for (
int kk = 2; kk < 10; ++kk)
1332 auto count = oneRound(kk);
1334 if (!BEAST_EXPECT(getTxHash(*wscLong, vec1, count).first))
1339 auto jv = wscShort->invoke(
"subscribe", request);
1341 if (!BEAST_EXPECT(getTxHash(*wscShort, vec2, count).first))
1343 if (!BEAST_EXPECT(hashCompare(vec1, vec2,
true)))
1345 wscShort->invoke(
"unsubscribe", request);
1354 using namespace jtx;
1355 using namespace std::chrono_literals;
1358 featurePermissionedDEX};
1362 auto const alice = permDex.
alice;
1363 auto const bob = permDex.
bob;
1364 auto const carol = permDex.
carol;
1365 auto const domainID = permDex.
domainID;
1366 auto const gw = permDex.
gw;
1367 auto const usd = permDex.
usd;
1373 streams[jss::streams][0u] =
"book_changes";
1375 auto jv = wsc->invoke(
"subscribe", streams);
1376 if (!BEAST_EXPECT(jv[jss::status] ==
"success"))
1384 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
1385 if (jv[jss::changes].size() != 1)
1388 auto const jrOffer = jv[jss::changes][0u];
1389 return (jv[jss::changes][0u][jss::domain]).asString() == strHex(domainID) &&
1390 jrOffer[jss::currency_a].asString() ==
"XRP_drops" &&
1391 jrOffer[jss::volume_a].asString() ==
"5000000" &&
1392 jrOffer[jss::currency_b].asString() ==
"rHUKYAZyUFn8PCZWbPfwHfbVQXTYrYKkHb/USD" &&
1393 jrOffer[jss::volume_b].asString() ==
"5";
1413 testcase(
"Test synthetic fields from Subscribe response");
1416 using namespace std::chrono_literals;
1420 Account const broker{
"broker"};
1423 env.
fund(
XRP(10000), alice, bob, broker);
1429 stream[jss::streams].append(
"transactions");
1430 auto jv = wsc->invoke(
"subscribe", stream);
1435 auto verifyNFTokenID = [&](
UInt256 const& actualNftID) {
1436 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
1438 BEAST_EXPECT(nftID.parseHex(jv[jss::meta][jss::nftoken_id].asString()));
1439 return nftID == actualNftID;
1446 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
1447 std::vector<UInt256> metaIDs;
1449 jv[jss::meta][jss::nftoken_ids].begin(),
1450 jv[jss::meta][jss::nftoken_ids].end(),
1451 std::back_inserter(metaIDs),
1452 [this](json::Value id) {
1454 BEAST_EXPECT(nftID.parseHex(id.asString()));
1462 BEAST_EXPECT(metaIDs.size() == actualNftIDs.size());
1466 for (
size_t i = 0; i < metaIDs.size(); ++i)
1467 BEAST_EXPECT(metaIDs[i] == actualNftIDs[i]);
1474 auto verifyNFTokenOfferID = [&](
UInt256 const& offerID) {
1475 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
1476 UInt256 metaOfferID;
1477 BEAST_EXPECT(metaOfferID.parseHex(jv[jss::meta][jss::offer_id].asString()));
1478 return metaOfferID == offerID;
1487 env(
token::mint(alice, 0u), Txflags(tfTransferable));
1488 BEAST_EXPECT(env.syncClose());
1489 verifyNFTokenID(nftId1);
1492 env(
token::mint(alice, 0u), Txflags(tfTransferable));
1493 BEAST_EXPECT(env.syncClose());
1494 verifyNFTokenID(nftId2);
1499 UInt256 const aliceOfferIndex1 =
1502 BEAST_EXPECT(env.syncClose());
1503 verifyNFTokenOfferID(aliceOfferIndex1);
1505 UInt256 const aliceOfferIndex2 =
1508 BEAST_EXPECT(env.syncClose());
1509 verifyNFTokenOfferID(aliceOfferIndex2);
1515 BEAST_EXPECT(env.syncClose());
1516 verifyNFTokenIDsInCancelOffer({nftId1, nftId2});
1520 auto const bobBuyOfferIndex =
1523 BEAST_EXPECT(env.syncClose());
1524 verifyNFTokenOfferID(bobBuyOfferIndex);
1529 BEAST_EXPECT(env.syncClose());
1530 verifyNFTokenID(nftId1);
1537 env(
token::mint(alice, 0u), Txflags(tfTransferable));
1538 BEAST_EXPECT(env.syncClose());
1539 verifyNFTokenID(nftId);
1542 UInt256 const offerAliceToBroker =
1545 token::Destination(broker),
1546 Txflags(tfSellNFToken));
1547 BEAST_EXPECT(env.syncClose());
1548 verifyNFTokenOfferID(offerAliceToBroker);
1551 UInt256 const offerBobToBroker =
1554 BEAST_EXPECT(env.syncClose());
1555 verifyNFTokenOfferID(offerBobToBroker);
1559 BEAST_EXPECT(env.syncClose());
1560 verifyNFTokenID(nftId);
1568 env(
token::mint(alice, 0u), Txflags(tfTransferable));
1569 BEAST_EXPECT(env.syncClose());
1570 verifyNFTokenID(nftId);
1573 UInt256 const aliceOfferIndex1 =
1576 BEAST_EXPECT(env.syncClose());
1577 verifyNFTokenOfferID(aliceOfferIndex1);
1579 UInt256 const aliceOfferIndex2 =
1582 BEAST_EXPECT(env.syncClose());
1583 verifyNFTokenOfferID(aliceOfferIndex2);
1588 BEAST_EXPECT(env.syncClose());
1589 verifyNFTokenIDsInCancelOffer({nftId});
1592 if (features[featureNFTokenMintOffer])
1594 UInt256 const aliceMintWithOfferIndex1 =
1597 BEAST_EXPECT(env.syncClose());
1598 verifyNFTokenOfferID(aliceMintWithOfferIndex1);
1621 static std::vector<std::string>
1644 for (
auto const& a : accts)
1645 jv[jss::accounts].
append(a);
1656 for (
auto const& a : accts)
1657 jv[jss::accounts_proposed].
append(a);
1688 for (
auto const&
id : mptIDs)
1689 jv[jss::mpt_issuances].
append(
id);
1698 using namespace jtx;
1703 book[jss::taker_gets][jss::currency] =
"XRP";
1705 book[jss::taker_pays][jss::currency] =
"USD";
1706 book[jss::taker_pays][jss::issuer] =
Account(
"alice").human();
1716 jv[jss::account_history_tx_stream][jss::account] = acct;
1728 cfg->maxSubscriptionsPerConnection = cap;
1738 testcase(
"subscription cap rejects an over-cap request");
1740 using namespace jtx;
1747 BEAST_EXPECT(jr[jss::error] ==
"invalidParams");
1748 BEAST_EXPECT(jr[jss::error_message] ==
"Too many subscriptions for this connection.");
1757 testcase(
"re-subscribe at the cap is not over-counted");
1759 using namespace jtx;
1767 BEAST_EXPECTS(r[jss::status] ==
"success",
to_string(r));
1774 BEAST_EXPECTS(r[jss::status] ==
"success",
to_string(r));
1784 testcase(
"books cap is independent of account count");
1786 using namespace jtx;
1797 BEAST_EXPECTS(r[jss::status] ==
"success",
to_string(r));
1804 BEAST_EXPECTS(r[jss::status] ==
"success",
to_string(r));
1815 testcase(
"multi-field subscribe does not partially subscribe");
1817 using namespace jtx;
1826 req[jss::accounts].
append(a);
1828 auto const jr = wsc->invoke(
"subscribe", req)[jss::result];
1829 BEAST_EXPECT(jr[jss::error] ==
"invalidParams");
1830 BEAST_EXPECT(jr[jss::error_message] ==
"Too many subscriptions for this connection.");
1838 BEAST_EXPECTS(r[jss::status] ==
"success",
to_string(r));
1853 testcase(
"account_history re-subscribe at the cap is not over-counted");
1855 using namespace jtx;
1867 BEAST_EXPECTS(r[jss::status] ==
"success",
to_string(r));
1879 BEAST_EXPECT(jr[jss::error] ==
"invalidParams");
1880 BEAST_EXPECT(jr[jss::error_message] ==
"Invalid parameters.");
1881 BEAST_EXPECT(jr[jss::error_message] !=
"Too many subscriptions for this connection.");
1892 testcase(
"account_history net-new subscribe is rejected at the cap");
1894 using namespace jtx;
1898 env.
fund(
XRP(10000), alice, bob);
1906 BEAST_EXPECTS(r[jss::status] ==
"success",
to_string(r));
1914 BEAST_EXPECT(jr[jss::error] ==
"invalidParams");
1915 BEAST_EXPECT(jr[jss::error_message] ==
"Too many subscriptions for this connection.");
1927 testcase(
"mpt_issuances cap rejects an over-cap request");
1929 using namespace jtx;
1935 auto const jr = wsc->invoke(
1937 BEAST_EXPECT(jr[jss::error] ==
"invalidParams");
1938 BEAST_EXPECT(jr[jss::error_message] ==
"Too many subscriptions for this connection.");
1946 BEAST_EXPECTS(r[jss::status] ==
"success",
to_string(r));
1956 testcase(
"mpt_issuances re-subscribe at the cap is not over-counted");
1958 using namespace jtx;
1966 BEAST_EXPECTS(r[jss::status] ==
"success",
to_string(r));
1973 BEAST_EXPECTS(r[jss::status] ==
"success",
to_string(r));
1978 auto const jr = wsc->invoke(
1980 BEAST_EXPECT(jr[jss::error] ==
"invalidParams");
1981 BEAST_EXPECT(jr[jss::error_message] ==
"Too many subscriptions for this connection.");
1990 testcase(
"mpt_issuances and accounts share one cap");
1992 using namespace jtx;
2000 BEAST_EXPECTS(r[jss::status] ==
"success",
to_string(r));
2002 auto const jr = wsc->invoke(
2004 BEAST_EXPECT(jr[jss::error] ==
"invalidParams");
2005 BEAST_EXPECT(jr[jss::error_message] ==
"Too many subscriptions for this connection.");
2010 BEAST_EXPECTS(r2[jss::status] ==
"success",
to_string(r2));
2021 BEAST_EXPECTS(r[jss::status] ==
"success",
to_string(r));
2025 BEAST_EXPECT(jr[jss::error] ==
"invalidParams");
2026 BEAST_EXPECT(jr[jss::error_message] ==
"Too many subscriptions for this connection.");
2037 testcase(
"mpt_issuances unsubscribe frees cap allowance");
2039 using namespace jtx;
2046 BEAST_EXPECTS(r[jss::status] ==
"success",
to_string(r));
2051 BEAST_EXPECTS(r[jss::status] ==
"success",
to_string(r));
2058 BEAST_EXPECTS(r[jss::status] ==
"success",
to_string(r));
2069 testcase(
"async teardown does not stall publishing");
2071 using namespace std::chrono_literals;
2072 using namespace jtx;
2086 auto const r = wscLive->invoke(
"subscribe", jv);
2087 BEAST_EXPECTS(r[jss::status] ==
"success",
to_string(r));
2098 BEAST_EXPECTS(r[jss::status] ==
"success",
to_string(r));
2111 BEAST_EXPECT(info.isMember(jss::server_state));
2120 BEAST_EXPECT(wscLive->findMsg(5s, [&](
auto const& jv) {
2121 return jv.isMember(jss::transaction) &&
2122 jv[jss::transaction][jss::TransactionType] == jss::Payment &&
2123 jv[jss::transaction][jss::Destination] == alice.human();
2137 testcase(
"re-subscribe after disconnect still delivers");
2139 using namespace std::chrono_literals;
2140 using namespace jtx;
2152 bulk.push_back(alice.
human());
2154 BEAST_EXPECTS(r[jss::status] ==
"success",
to_string(r));
2165 auto const r = wscB->invoke(
"subscribe", jv);
2166 BEAST_EXPECTS(r[jss::status] ==
"success",
to_string(r));
2174 BEAST_EXPECT(wscB->findMsg(5s, [&](
auto const& jv) {
2175 return jv.isMember(jss::transaction) &&
2176 jv[jss::transaction][jss::TransactionType] == jss::Payment &&
2177 jv[jss::transaction][jss::Destination] == alice.human();
2189 using namespace jtx;
2190 using namespace std::chrono_literals;
2201 MPTTester mptAlice(env, alice, {.holders = {bob}});
2202 MPTTester mptCarol(env, carol, {.holders = {dan}});
2206 {.transferFee = 10'000, .ownerCount = 1, .flags = tfMPTCanTransfer | tfMPTCanLock});
2207 mptCarol.create({.ownerCount = 1, .flags = tfMPTCanTransfer});
2212 stream[jss::mpt_issuances].append(
to_string(mptAlice.issuanceID()));
2213 stream[jss::mpt_issuances].append(
to_string(mptCarol.issuanceID()));
2214 auto jv = wsc->invoke(
"subscribe", stream);
2215 BEAST_EXPECT(jv[jss::status] ==
"success");
2218 mptAlice.authorize({.account = bob});
2219 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
2220 return jv[jss::engine_result] ==
"tesSUCCESS" &&
2221 jv[jss::transaction][jss::Account] == bob.human() &&
2222 jv[jss::transaction][jss::Flags] == 0 &&
2223 jv[jss::transaction][sfMPTokenIssuanceID.jsonName] ==
2224 to_string(mptAlice.issuanceID()) &&
2225 jv[jss::transaction][jss::TransactionType] ==
"MPTokenAuthorize" &&
2226 jv[jss::type] ==
"mptTransaction";
2230 mptCarol.authorize({.account = dan});
2231 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
2232 return jv[jss::engine_result] ==
"tesSUCCESS" &&
2233 jv[jss::transaction][jss::Account] == dan.human() &&
2234 jv[jss::transaction][jss::Flags] == 0 &&
2235 jv[jss::transaction][sfMPTokenIssuanceID.jsonName] ==
2236 to_string(mptCarol.issuanceID()) &&
2237 jv[jss::transaction][jss::Sequence] == 5 &&
2238 jv[jss::transaction][jss::TransactionType] ==
"MPTokenAuthorize" &&
2239 jv[jss::type] ==
"mptTransaction";
2243 mptAlice.pay(alice, bob, 2000);
2244 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
2245 return jv[jss::engine_result] ==
"tesSUCCESS" &&
2246 jv[jss::transaction][jss::Account] == alice.human() &&
2247 jv[jss::transaction][jss::Amount][jss::mpt_issuance_id] ==
2248 to_string(mptAlice.issuanceID()) &&
2249 jv[jss::transaction][jss::Amount][jss::value] ==
"2000" &&
2250 jv[jss::transaction][jss::DeliverMax][jss::mpt_issuance_id] ==
2251 to_string(mptAlice.issuanceID()) &&
2252 jv[jss::transaction][jss::DeliverMax][jss::value] ==
"2000" &&
2253 jv[jss::transaction][jss::Destination] == bob.human() &&
2254 jv[jss::transaction][jss::Flags] == tfFullyCanonicalSig &&
2255 jv[jss::transaction][jss::TransactionType] ==
"Payment" &&
2256 jv[jss::type] ==
"mptTransaction";
2260 mptCarol.pay(carol, dan, 1000);
2261 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
2262 return jv[jss::engine_result] ==
"tesSUCCESS" &&
2263 jv[jss::transaction][jss::Account] == carol.human() &&
2264 jv[jss::transaction][jss::Amount][jss::mpt_issuance_id] ==
2265 to_string(mptCarol.issuanceID()) &&
2266 jv[jss::transaction][jss::Amount][jss::value] ==
"1000" &&
2267 jv[jss::transaction][jss::DeliverMax][jss::mpt_issuance_id] ==
2268 to_string(mptCarol.issuanceID()) &&
2269 jv[jss::transaction][jss::DeliverMax][jss::value] ==
"1000" &&
2270 jv[jss::transaction][jss::Destination] == dan.human() &&
2271 jv[jss::transaction][jss::Flags] == tfFullyCanonicalSig &&
2272 jv[jss::transaction][jss::Sequence] == 6 &&
2273 jv[jss::transaction][jss::TransactionType] ==
"Payment" &&
2274 jv[jss::type] ==
"mptTransaction";
2278 mptAlice.set({.account = alice, .flags = tfMPTLock});
2279 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
2280 return jv[jss::engine_result] ==
"tesSUCCESS" &&
2281 jv[jss::transaction][jss::Account] == alice.human() &&
2282 jv[jss::transaction][jss::Flags] == tfMPTLock &&
2283 jv[jss::transaction][sfMPTokenIssuanceID.jsonName] ==
2284 to_string(mptAlice.issuanceID()) &&
2285 jv[jss::transaction][jss::TransactionType] ==
"MPTokenIssuanceSet" &&
2286 jv[jss::type] ==
"mptTransaction";
2290 mptAlice.set({.account = alice, .flags = tfMPTUnlock});
2291 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
2292 return jv[jss::engine_result] ==
"tesSUCCESS" &&
2293 jv[jss::transaction][jss::Account] == alice.human() &&
2294 jv[jss::transaction][jss::Flags] == tfMPTUnlock &&
2295 jv[jss::transaction][sfMPTokenIssuanceID.jsonName] ==
2296 to_string(mptAlice.issuanceID()) &&
2297 jv[jss::transaction][jss::TransactionType] ==
"MPTokenIssuanceSet" &&
2298 jv[jss::type] ==
"mptTransaction";
2305 unsubStream[jss::mpt_issuances].
append(
to_string(mptAlice.issuanceID()));
2306 auto unsubJv = wsc->invoke(
"unsubscribe", unsubStream);
2307 BEAST_EXPECT(unsubJv[jss::status] ==
"success");
2310 mptAlice.pay(bob, alice, 500);
2313 BEAST_EXPECT(!wsc->findMsg(5s, [&](
auto const& jv) {
2314 return jv[jss::transaction][jss::Amount][jss::mpt_issuance_id] ==
2315 to_string(mptAlice.issuanceID()) &&
2316 jv[jss::transaction][jss::Amount][jss::value] ==
"500";
2320 mptCarol.pay(dan, carol, 100);
2323 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
2324 return jv[jss::engine_result] ==
"tesSUCCESS" &&
2325 jv[jss::transaction][jss::Account] == dan.human() &&
2326 jv[jss::transaction][jss::Amount][jss::mpt_issuance_id] ==
2327 to_string(mptCarol.issuanceID()) &&
2328 jv[jss::transaction][jss::Amount][jss::value] ==
"100" &&
2329 jv[jss::transaction][jss::DeliverMax][jss::mpt_issuance_id] ==
2330 to_string(mptCarol.issuanceID()) &&
2331 jv[jss::transaction][jss::DeliverMax][jss::value] ==
"100" &&
2332 jv[jss::transaction][jss::Destination] == carol.human() &&
2333 jv[jss::transaction][jss::Flags] == tfFullyCanonicalSig &&
2334 jv[jss::transaction][jss::TransactionType] ==
"Payment" &&
2335 jv[jss::type] ==
"mptTransaction";
2346 using namespace jtx;
2347 using namespace std::chrono_literals;
2358 mptAlice1.
create({.ownerCount = 1});
2359 auto const mptID1 = mptAlice1.issuanceID();
2362 mptAlice2.
create({.ownerCount = 2});
2363 auto const mptID2 = mptAlice2.issuanceID();
2368 stream[jss::mpt_issuances].append(
to_string(mptID1));
2369 stream[jss::mpt_issuances].append(
to_string(mptID2));
2370 auto const jv = wsc->invoke(
"subscribe", stream);
2371 BEAST_EXPECT(jv[jss::status] ==
"success");
2374 auto const seq = env.
seq(alice);
2376 env(
batch::outer(alice, seq, batchFee, tfAllOrNothing),
2377 batch::Inner(mptAlice1.destroyJV({.issuer = alice, .id = mptID1}), seq + 1),
2378 batch::Inner(mptAlice2.destroyJV({.issuer = alice, .id = mptID2}), seq + 2));
2382 for (
auto const& mptID : {mptID1, mptID2})
2384 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
2385 return jv[jss::engine_result] ==
"tesSUCCESS" &&
2386 jv[jss::transaction][jss::Account] == alice.human() &&
2387 jv[jss::transaction][sfMPTokenIssuanceID.jsonName] == to_string(mptID) &&
2388 jv[jss::transaction][jss::TransactionType] ==
"MPTokenIssuanceDestroy" &&
2389 jv[jss::type] ==
"mptTransaction";
2395 BEAST_EXPECT(!wsc->findMsg(1s, [](
auto const& jv) {
2396 return jv[jss::transaction][jss::TransactionType] ==
"Batch";
2404 BEAST_EXPECT(wsc->invoke(
"unsubscribe", unsubStream)[jss::status] ==
"success");
2411 using namespace jtx;
2412 using namespace std::chrono_literals;
2419 MPTTester mpt(env, alice, {.holders = {bob, carol}});
2423 auto const subscribed =
2425 BEAST_EXPECT(subscribed[jss::status] ==
"success");
2427 auto expectTransaction = [&](
std::string const& type) {
2428 BEAST_EXPECT(wsc->findMsg(5s, [&](
auto const& jv) {
2429 return jv[jss::engine_result] ==
"tesSUCCESS" &&
2430 jv[jss::transaction][jss::TransactionType] == type &&
2431 jv[jss::type] ==
"mptTransaction";
2438 expectTransaction(
"OfferCreate");
2440 env(
offer(carol, mpt(5),
XRP(10)));
2442 expectTransaction(
"OfferCreate");
2445 auto const baseFee = env.
current()->fees().base;
2448 Fee(baseFee * 150));
2450 expectTransaction(
"EscrowCreate");
TestcaseT testcase
Memberspace for declaring test cases.
Value & append(Value const &value)
Append value to array at the end.
void clear()
Remove all object members and array elements.
virtual Config & config()=0
virtual std::uint32_t getNetworkID() const noexcept=0
Get the configured network ID.
virtual json::Value getServerInfo(bool human, bool admin, bool counters)=0
virtual void reportFeeChange()=0
static constexpr SeqProxy rawSequence(std::uint32_t v)
Factory function to return a sequence-based SeqProxy.
virtual NetworkOPs & getOPs()=0
virtual LoadManager & getLoadManager()=0
virtual NetworkIDService & getNetworkIDService()=0
virtual LoadFeeTrack & getFeeTrack()=0
static json::Value oneBookRequest()
void run() override
Runs the suite.
void testTransactionsAPIv1()
static std::vector< std::string > makeAccountStrings(std::size_t count, std::uint32_t seed=1)
void testMPTReSubscribeNotOvercounted()
void testMPTSharesCapWithAccounts()
static json::Value accountsRequest(std::vector< std::string > const &accts)
void testReSubscribeNotOvercounted()
void testHistoryCapRejectsNetNew()
void testMultiFieldNoPartialSubscribe()
void testValidations(FeatureBitset features)
void testSubscriptionCapRejects()
static json::Value accountHistoryRequest(std::string const &acct)
void testHistoryTxStream()
void testSubMPTAmountFields()
static auto cappedConfig(std::size_t cap)
void testBooksCapIndependentOfAccounts()
void testResubscribeAfterDisconnect()
void testHistoryReSubscribeNotOvercounted()
void testSubBookChanges()
void testNFToken(FeatureBitset features)
void testMPTUnsubscribeFreesCap()
void testTransactionsAPIv2()
static std::vector< std::string > makeMPTIssuanceStrings(std::size_t count, std::uint32_t seed=1)
static json::Value accountsProposedRequest(std::vector< std::string > const &accts)
void testSubErrors(bool subscribe)
void testAsyncTeardownDoesNotStall()
static json::Value mptIssuancesRequest(std::vector< std::string > const &mptIDs)
virtual std::optional< json::Value > getMsg(std::chrono::milliseconds const &timeout=std::chrono::milliseconds{0})=0
Retrieve a message.
Immutable cryptographic account descriptor.
std::string const & human() const
Returns the human readable public key.
A transaction testing environment.
bool syncClose(std::chrono::steady_clock::duration timeout=std::chrono::seconds{1})
Close and advance the ledger, then synchronize with the server's io_context to ensure all async opera...
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
void memoize(Account const &account)
Associate AccountID with account.
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
NetClock::time_point now()
Returns the current network time.
Test helper for creating, mutating, and asserting MPT and confidential MPT ledger state.
void create(MPTCreate const &arg=MPTCreate{}, std::source_location const &loc=std::source_location::current())
Sets the SendMax on a JTx.
Set the regular signature on a JTx.
Adds an inner Batch transaction to a JTx and autofills it.
@ UInt
unsigned integer value
@ Int
signed integer value
@ Array
array value (ordered list)
@ Object
object value (collection of name/value pairs).
Keylet nftokenOffer(AccountID const &owner, SeqProxy const &seq)
An offer from an account to buy or sell an NFT.
API version numbers used in later API versions.
json::Value outer(jtx::Account const &account, uint32_t seq, STAmount const &fee, std::uint32_t flags)
Build an outer Batch transaction JSON object.
XRPAmount calcBatchFee(jtx::Env const &env, uint32_t const &numSigners, uint32_t const &txns=0)
Calculate the expected outer Batch transaction fee.
json::Value create(AccountID const &account, AccountID const &to, STAmount const &amount)
auto const kFinishTime
Set the "FinishAfter" time tag on a JTx.
json::Value mint(jtx::Account const &account, std::uint32_t nfTokenTaxon)
Mint an NFToken.
json::Value brokerOffers(jtx::Account const &account, UInt256 const &buyOfferIndex, UInt256 const &sellOfferIndex)
Broker two NFToken offers.
json::Value createOffer(jtx::Account const &account, UInt256 const &nftokenID, STAmount const &amount)
Create an NFTokenOffer.
json::Value cancelOffer(jtx::Account const &account, std::initializer_list< UInt256 > const &nftokenOffers)
Cancel NFTokenOffers.
UInt256 getNextID(jtx::Env const &env, jtx::Account const &issuer, std::uint32_t nfTokenTaxon, std::uint16_t flags, std::uint16_t xferFee)
Get the next NFTokenID that will be issued.
json::Value acceptBuyOffer(jtx::Account const &account, UInt256 const &offerIndex)
Accept an NFToken buy offer.
json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
XrpT const XRP
Converts to XRP Issue or STAmount.
FeatureBitset testableAmendments()
std::unique_ptr< Config > singleThreadIo(std::unique_ptr< Config >)
json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
std::unique_ptr< Config > noAdmin(std::unique_ptr< Config >)
adjust config so no admin ports are enabled
static AutofillT const kAutofill
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
std::unique_ptr< Config > validator(std::unique_ptr< Config >, std::string const &)
adjust configuration with params needed to be a validator
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
std::unique_ptr< WSClient > makeWSClient(Config const &cfg, bool v2, unsigned rpcVersion, std::unordered_map< std::string, std::string > const &headers)
Returns a client operating through WebSockets/S.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
PublicKey derivePublicKey(KeyType type, SecretKey const &sk)
Derive the public key from a secret key.
bool isFlagLedger(LedgerIndex seq)
Returns true if the given ledgerIndex is a flag ledgerIndex.
std::optional< AccountID > parseBase58(std::string const &s)
Parse AccountID from checked, base58 string.
BaseUInt< 256 > Domain
Domain is a 256-bit hash representing a specific domain.
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
SecretKey generateSecretKey(KeyType type, Seed const &seed)
Generate a new secret key deterministically.
std::string to_string(BaseUInt< Bits, Tag > const &a)
BaseUInt< 192 > MPTID
MPTID is a 192-bit value representing MPT Issuance ID, which is a concatenation of a 32-bit sequence ...
constexpr std::uint32_t kVfFullValidation
constexpr std::uint32_t kVfFullyCanonicalSig
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
std::unordered_map< Key, Value, Hash, Pred, Allocator > HashMap
static constexpr auto kValidationSeed
Arguments for initializing funded MPT test accounts and issuance.
Set the sequence number on a JTx.