rippled
Loading...
Searching...
No Matches
Simulate_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012-2017 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <test/jtx.h>
21#include <test/jtx/Env.h>
22#include <test/jtx/envconfig.h>
23
24#include <xrpld/app/rdb/backend/SQLiteDatabase.h>
25#include <xrpld/rpc/CTID.h>
26
27#include <xrpl/protocol/ErrorCodes.h>
28#include <xrpl/protocol/STBase.h>
29#include <xrpl/protocol/STParsedJSON.h>
30#include <xrpl/protocol/jss.h>
31#include <xrpl/protocol/serialize.h>
32
33#include <optional>
34#include <tuple>
35
36namespace ripple {
37
38namespace test {
39
41{
42 void
44 Json::Value const& result,
45 Json::Value const& tx,
46 int const expectedSequence,
47 std::string const& expectedFee)
48 {
49 BEAST_EXPECT(result[jss::applied] == false);
50 BEAST_EXPECT(result.isMember(jss::engine_result));
51 BEAST_EXPECT(result.isMember(jss::engine_result_code));
52 BEAST_EXPECT(result.isMember(jss::engine_result_message));
53 BEAST_EXPECT(
54 result.isMember(jss::tx_json) || result.isMember(jss::tx_blob));
55
56 Json::Value tx_json;
57 if (result.isMember(jss::tx_json))
58 {
59 tx_json = result[jss::tx_json];
60 }
61 else
62 {
63 auto const unHexed = strUnHex(result[jss::tx_blob].asString());
64 SerialIter sitTrans(makeSlice(*unHexed));
65 tx_json = STObject(std::ref(sitTrans), sfGeneric)
67 }
68 BEAST_EXPECT(tx_json[jss::TransactionType] == tx[jss::TransactionType]);
69 BEAST_EXPECT(tx_json[jss::Account] == tx[jss::Account]);
70 BEAST_EXPECT(
71 tx_json[jss::SigningPubKey] == tx.get(jss::SigningPubKey, ""));
72 BEAST_EXPECT(
73 tx_json[jss::TxnSignature] == tx.get(jss::TxnSignature, ""));
74 BEAST_EXPECT(tx_json[jss::Fee] == tx.get(jss::Fee, expectedFee));
75 BEAST_EXPECT(
76 tx_json[jss::Sequence] == tx.get(jss::Sequence, expectedSequence));
77 }
78
79 void
81 Json::Value const& result,
82 Json::Value const& tx,
83 int const expectedSequence,
84 XRPAmount const& expectedFee)
85 {
87 result, tx, expectedSequence, expectedFee.jsonClipped().asString());
88 }
89
90 void
92 jtx::Env& env,
93 Json::Value const& tx,
94 std::function<void(Json::Value const&, Json::Value const&)> const&
95 validate,
96 bool testSerialized = true)
97 {
98 env.close();
99
100 Json::Value params;
101 params[jss::tx_json] = tx;
102 validate(env.rpc("json", "simulate", to_string(params)), tx);
103
104 params[jss::binary] = true;
105 validate(env.rpc("json", "simulate", to_string(params)), tx);
106 validate(env.rpc("simulate", to_string(tx)), tx);
107 validate(env.rpc("simulate", to_string(tx), "binary"), tx);
108
109 if (testSerialized)
110 {
111 // This cannot be tested in the multisign autofill scenario
112 // It is technically not a valid STObject, so the following line
113 // will crash
114 STParsedJSONObject const parsed(std::string(jss::tx_json), tx);
115 auto const tx_blob =
116 strHex(parsed.object->getSerializer().peekData());
117 if (BEAST_EXPECT(parsed.object.has_value()))
118 {
119 Json::Value params;
120 params[jss::tx_blob] = tx_blob;
121 validate(env.rpc("json", "simulate", to_string(params)), tx);
122 params[jss::binary] = true;
123 validate(env.rpc("json", "simulate", to_string(params)), tx);
124 }
125 validate(env.rpc("simulate", tx_blob), tx);
126 validate(env.rpc("simulate", tx_blob, "binary"), tx);
127 }
128
129 BEAST_EXPECTS(
130 env.current()->txCount() == 0,
131 std::to_string(env.current()->txCount()));
132 }
133
134 void
136 jtx::Env& env,
137 Json::Value const& tx,
138 std::function<void(
139 Json::Value const&,
140 Json::Value const&,
141 Json::Value const&)> const& validate,
142 Json::Value const& expectedMetadataKey,
143 bool testSerialized = true)
144 {
145 env.close();
146
147 Json::Value params;
148 params[jss::tx_json] = tx;
149 validate(
150 env.rpc("json", "simulate", to_string(params)),
151 tx,
152 expectedMetadataKey);
153 validate(env.rpc("simulate", to_string(tx)), tx, expectedMetadataKey);
154
155 BEAST_EXPECTS(
156 env.current()->txCount() == 0,
157 std::to_string(env.current()->txCount()));
158 }
159
162 {
163 if (txResult.isMember(jss::meta_blob))
164 {
165 auto unHexed = strUnHex(txResult[jss::meta_blob].asString());
166 SerialIter sitTrans(makeSlice(*unHexed));
167 return STObject(std::ref(sitTrans), sfGeneric)
169 }
170
171 return txResult[jss::meta];
172 }
173
174 void
176 {
177 testcase("Test parameter errors");
178
179 using namespace jtx;
180 Env env(*this);
181 Account const alice("alice");
182
183 {
184 // No params
185 Json::Value const params = Json::objectValue;
186 auto const resp = env.rpc("json", "simulate", to_string(params));
187 BEAST_EXPECT(
188 resp[jss::result][jss::error_message] ==
189 "Neither `tx_blob` nor `tx_json` included.");
190 }
191 {
192 // Providing both `tx_json` and `tx_blob`
194 params[jss::tx_json] = Json::objectValue;
195 params[jss::tx_blob] = "1200";
196
197 auto const resp = env.rpc("json", "simulate", to_string(params));
198 BEAST_EXPECT(
199 resp[jss::result][jss::error_message] ==
200 "Can only include one of `tx_blob` and `tx_json`.");
201 }
202 {
203 // `binary` isn't a boolean
205 params[jss::tx_blob] = "1200";
206 params[jss::binary] = "100";
207 auto const resp = env.rpc("json", "simulate", to_string(params));
208 BEAST_EXPECT(
209 resp[jss::result][jss::error_message] ==
210 "Invalid field 'binary'.");
211 }
212 {
213 // Invalid `tx_blob`
215 params[jss::tx_blob] = "12";
216
217 auto const resp = env.rpc("json", "simulate", to_string(params));
218 BEAST_EXPECT(
219 resp[jss::result][jss::error_message] ==
220 "Invalid field 'tx_blob'.");
221 }
222 {
223 // Empty `tx_json`
225 params[jss::tx_json] = Json::objectValue;
226
227 auto const resp = env.rpc("json", "simulate", to_string(params));
228 BEAST_EXPECT(
229 resp[jss::result][jss::error_message] ==
230 "Missing field 'tx.TransactionType'.");
231 }
232 {
233 // No tx.Account
236 tx_json[jss::TransactionType] = jss::Payment;
237 params[jss::tx_json] = tx_json;
238
239 auto const resp = env.rpc("json", "simulate", to_string(params));
240 BEAST_EXPECT(
241 resp[jss::result][jss::error_message] ==
242 "Missing field 'tx.Account'.");
243 }
244 {
245 // Empty `tx_blob`
247 params[jss::tx_blob] = "";
248
249 auto const resp = env.rpc("json", "simulate", to_string(params));
250 BEAST_EXPECT(
251 resp[jss::result][jss::error_message] ==
252 "Invalid field 'tx_blob'.");
253 }
254 {
255 // Non-string `tx_blob`
256 Json::Value params;
257 params[jss::tx_blob] = 1.1;
258
259 auto const resp = env.rpc("json", "simulate", to_string(params));
260 BEAST_EXPECT(
261 resp[jss::result][jss::error_message] ==
262 "Invalid field 'tx_blob'.");
263 }
264 {
265 // Non-object `tx_json`
267 params[jss::tx_json] = "";
268
269 auto const resp = env.rpc("json", "simulate", to_string(params));
270 BEAST_EXPECT(
271 resp[jss::result][jss::error_message] ==
272 "Invalid field 'tx_json', not object.");
273 }
274 {
275 // `seed` field included
277 params[jss::seed] = "doesnt_matter";
279 tx_json[jss::TransactionType] = jss::AccountSet;
280 tx_json[jss::Account] = env.master.human();
281 params[jss::tx_json] = tx_json;
282 auto const resp = env.rpc("json", "simulate", to_string(params));
283 BEAST_EXPECT(
284 resp[jss::result][jss::error_message] ==
285 "Invalid field 'seed'.");
286 }
287 {
288 // `secret` field included
290 params[jss::secret] = "doesnt_matter";
292 tx_json[jss::TransactionType] = jss::AccountSet;
293 tx_json[jss::Account] = env.master.human();
294 params[jss::tx_json] = tx_json;
295 auto const resp = env.rpc("json", "simulate", to_string(params));
296 BEAST_EXPECT(
297 resp[jss::result][jss::error_message] ==
298 "Invalid field 'secret'.");
299 }
300 {
301 // `seed_hex` field included
303 params[jss::seed_hex] = "doesnt_matter";
305 tx_json[jss::TransactionType] = jss::AccountSet;
306 tx_json[jss::Account] = env.master.human();
307 params[jss::tx_json] = tx_json;
308 auto const resp = env.rpc("json", "simulate", to_string(params));
309 BEAST_EXPECT(
310 resp[jss::result][jss::error_message] ==
311 "Invalid field 'seed_hex'.");
312 }
313 {
314 // `passphrase` field included
316 params[jss::passphrase] = "doesnt_matter";
318 tx_json[jss::TransactionType] = jss::AccountSet;
319 tx_json[jss::Account] = env.master.human();
320 params[jss::tx_json] = tx_json;
321 auto const resp = env.rpc("json", "simulate", to_string(params));
322 BEAST_EXPECT(
323 resp[jss::result][jss::error_message] ==
324 "Invalid field 'passphrase'.");
325 }
326 {
327 // Invalid transaction
330 tx_json[jss::TransactionType] = jss::Payment;
331 tx_json[jss::Account] = env.master.human();
332 params[jss::tx_json] = tx_json;
333
334 auto const resp = env.rpc("json", "simulate", to_string(params));
335 BEAST_EXPECT(
336 resp[jss::result][jss::error_exception] ==
337 "Field 'Destination' is required but missing.");
338 }
339 {
340 // Bad account
341 Json::Value params;
343 tx_json[jss::TransactionType] = jss::AccountSet;
344 tx_json[jss::Account] = "badAccount";
345 params[jss::tx_json] = tx_json;
346
347 auto const resp = env.rpc("json", "simulate", to_string(params));
348 BEAST_EXPECTS(
349 resp[jss::result][jss::error] == "srcActMalformed",
350 resp[jss::result][jss::error].toStyledString());
351 BEAST_EXPECT(
352 resp[jss::result][jss::error_message] ==
353 "Invalid field 'tx.Account'.");
354 }
355 {
356 // Account doesn't exist for Sequence autofill
357 Json::Value params;
359 tx_json[jss::TransactionType] = jss::AccountSet;
360 tx_json[jss::Account] = alice.human();
361 params[jss::tx_json] = tx_json;
362
363 auto const resp = env.rpc("json", "simulate", to_string(params));
364 BEAST_EXPECT(
365 resp[jss::result][jss::error_message] ==
366 "Source account not found.");
367 }
368 {
369 // Invalid Signers field
370 Json::Value params;
372 tx_json[jss::TransactionType] = jss::AccountSet;
373 tx_json[jss::Account] = env.master.human();
374 tx_json[sfSigners] = "1";
375 params[jss::tx_json] = tx_json;
376
377 auto const resp = env.rpc("json", "simulate", to_string(params));
378 BEAST_EXPECT(
379 resp[jss::result][jss::error_message] ==
380 "Invalid field 'tx.Signers'.");
381 }
382 {
383 // Invalid Signers field
384 Json::Value params;
386 tx_json[jss::TransactionType] = jss::AccountSet;
387 tx_json[jss::Account] = env.master.human();
388 tx_json[sfSigners] = Json::arrayValue;
389 tx_json[sfSigners].append("1");
390 params[jss::tx_json] = tx_json;
391
392 auto const resp = env.rpc("json", "simulate", to_string(params));
393 BEAST_EXPECT(
394 resp[jss::result][jss::error_message] ==
395 "Invalid field 'tx.Signers[0]'.");
396 }
397 {
398 // Invalid transaction
399 Json::Value params;
401 tx_json[jss::TransactionType] = jss::AccountSet;
402 tx_json[jss::Account] = env.master.human();
403 tx_json["foo"] = "bar";
404 params[jss::tx_json] = tx_json;
405
406 auto const resp = env.rpc("json", "simulate", to_string(params));
407 BEAST_EXPECT(
408 resp[jss::result][jss::error_message] ==
409 "Field 'tx_json.foo' is unknown.");
410 }
411 {
412 // non-`"binary"` second param for CLI
414 tx_json[jss::TransactionType] = jss::AccountSet;
415 tx_json[jss::Account] = alice.human();
416 auto const resp = env.rpc("simulate", to_string(tx_json), "1");
417 BEAST_EXPECT(resp[jss::error_message] == "Invalid parameters.");
418 }
419 {
420 // Signed transaction
421 Json::Value params;
423 tx_json[jss::TransactionType] = jss::AccountSet;
424 tx_json[jss::Account] = env.master.human();
425 tx_json[jss::TxnSignature] = "1200ABCD";
426 params[jss::tx_json] = tx_json;
427
428 auto const resp = env.rpc("json", "simulate", to_string(params));
429 BEAST_EXPECT(
430 resp[jss::result][jss::error_message] ==
431 "Transaction should not be signed.");
432 }
433 {
434 // Signed multisig transaction
435 Json::Value params;
437 tx_json[jss::TransactionType] = jss::AccountSet;
438 tx_json[jss::Account] = env.master.human();
439 tx_json[sfSigners] = Json::arrayValue;
440 {
442 signer[jss::Account] = alice.human();
443 signer[jss::SigningPubKey] = alice.human();
444 signer[jss::TxnSignature] = "1200ABCD";
445 Json::Value signerOuter;
446 signerOuter[sfSigner] = signer;
447 tx_json[sfSigners].append(signerOuter);
448 }
449 params[jss::tx_json] = tx_json;
450
451 auto const resp = env.rpc("json", "simulate", to_string(params));
452 BEAST_EXPECT(
453 resp[jss::result][jss::error_message] ==
454 "Transaction should not be signed.");
455 }
456 }
457
458 void
460 {
461 testcase("Fee failure");
462
463 using namespace jtx;
464
465 Env env(*this, envconfig([](std::unique_ptr<Config> cfg) {
466 cfg->section("transaction_queue")
467 .set("minimum_txn_in_ledger_standalone", "3");
468 return cfg;
469 }));
470
471 Account const alice{"alice"};
472 env.fund(XRP(1000000), alice);
473 env.close();
474
475 // fill queue
476 auto metrics = env.app().getTxQ().getMetrics(*env.current());
477 for (int i = metrics.txInLedger; i <= metrics.txPerLedger; ++i)
478 env(noop(alice));
479
480 {
481 Json::Value params;
482 params[jss::tx_json] = noop(alice);
483
484 auto const resp = env.rpc("json", "simulate", to_string(params));
485 auto const result = resp[jss::result];
486 if (BEAST_EXPECT(result.isMember(jss::error)))
487 {
488 BEAST_EXPECT(result[jss::error] == "highFee");
489 BEAST_EXPECT(result[jss::error_code] == rpcHIGH_FEE);
490 }
491 }
492 }
493
494 void
496 {
497 testcase("Invalid transaction type");
498
499 using namespace jtx;
500
501 Env env(*this);
502
503 Account const alice{"alice"};
504 Account const bob{"bob"};
505 env.fund(XRP(1000000), alice, bob);
506 env.close();
507
508 auto const batchFee = batch::calcBatchFee(env, 0, 2);
509 auto const seq = env.seq(alice);
510 auto jt = env.jtnofill(
511 batch::outer(alice, env.seq(alice), batchFee, tfAllOrNothing),
512 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
513 batch::inner(pay(alice, bob, XRP(10)), seq + 1));
514
515 jt.jv.removeMember(jss::TxnSignature);
516 Json::Value params;
517 params[jss::tx_json] = jt.jv;
518 auto const resp = env.rpc("json", "simulate", to_string(params));
519 BEAST_EXPECT(resp[jss::result][jss::error] == "notImpl");
520 BEAST_EXPECT(
521 resp[jss::result][jss::error_message] == "Not implemented.");
522 }
523
524 void
526 {
527 testcase("Successful transaction");
528
529 using namespace jtx;
530 Env env{*this, envconfig([&](std::unique_ptr<Config> cfg) {
531 cfg->NETWORK_ID = 0;
532 return cfg;
533 })};
534 static auto const newDomain = "123ABC";
535
536 {
537 auto validateOutput = [&](Json::Value const& resp,
538 Json::Value const& tx) {
539 auto result = resp[jss::result];
541 result, tx, 1, env.current()->fees().base);
542
543 BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS");
544 BEAST_EXPECT(result[jss::engine_result_code] == 0);
545 BEAST_EXPECT(
546 result[jss::engine_result_message] ==
547 "The simulated transaction would have been applied.");
548
549 if (BEAST_EXPECT(
550 result.isMember(jss::meta) ||
551 result.isMember(jss::meta_blob)))
552 {
553 Json::Value const metadata = getJsonMetadata(result);
554
555 if (BEAST_EXPECT(
556 metadata.isMember(sfAffectedNodes.jsonName)))
557 {
558 BEAST_EXPECT(
559 metadata[sfAffectedNodes.jsonName].size() == 1);
560 auto node = metadata[sfAffectedNodes.jsonName][0u];
561 if (BEAST_EXPECT(
562 node.isMember(sfModifiedNode.jsonName)))
563 {
564 auto modifiedNode = node[sfModifiedNode];
565 BEAST_EXPECT(
566 modifiedNode[sfLedgerEntryType] ==
567 "AccountRoot");
568 auto finalFields = modifiedNode[sfFinalFields];
569 BEAST_EXPECT(finalFields[sfDomain] == newDomain);
570 }
571 }
572 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
573 BEAST_EXPECT(
574 metadata[sfTransactionResult.jsonName] == "tesSUCCESS");
575 }
576 };
577
578 Json::Value tx;
579
580 tx[jss::Account] = env.master.human();
581 tx[jss::TransactionType] = jss::AccountSet;
582 tx[sfDomain] = newDomain;
583
584 // test with autofill
585 testTx(env, tx, validateOutput);
586
587 tx[sfSigningPubKey] = "";
588 tx[sfTxnSignature] = "";
589 tx[sfSequence] = 1;
590 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
591
592 // test without autofill
593 testTx(env, tx, validateOutput);
594 }
595 }
596
597 void
599 {
600 testcase("Transaction non-tec failure");
601
602 using namespace jtx;
603 Env env(*this);
604 Account const alice("alice");
605
606 {
607 std::function<void(Json::Value const&, Json::Value const&)> const&
608 testSimulation = [&](Json::Value const& resp,
609 Json::Value const& tx) {
610 auto result = resp[jss::result];
612 result, tx, 1, env.current()->fees().base);
613
614 BEAST_EXPECT(result[jss::engine_result] == "temBAD_AMOUNT");
615 BEAST_EXPECT(result[jss::engine_result_code] == -298);
616 BEAST_EXPECT(
617 result[jss::engine_result_message] ==
618 "Malformed: Bad amount.");
619
620 BEAST_EXPECT(
621 !result.isMember(jss::meta) &&
622 !result.isMember(jss::meta_blob));
623 };
624
625 Json::Value tx;
626
627 tx[jss::Account] = env.master.human();
628 tx[jss::TransactionType] = jss::Payment;
629 tx[sfDestination] = alice.human();
630 tx[sfAmount] = "0"; // invalid amount
631
632 // test with autofill
633 testTx(env, tx, testSimulation);
634
635 tx[sfSigningPubKey] = "";
636 tx[sfTxnSignature] = "";
637 tx[sfSequence] = 1;
638 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
639
640 // test without autofill
641 testTx(env, tx, testSimulation);
642 }
643 }
644
645 void
647 {
648 testcase("Transaction tec failure");
649
650 using namespace jtx;
651 Env env(*this);
652 Account const alice("alice");
653
654 {
655 std::function<void(Json::Value const&, Json::Value const&)> const&
656 testSimulation = [&](Json::Value const& resp,
657 Json::Value const& tx) {
658 auto result = resp[jss::result];
660 result, tx, 1, env.current()->fees().base);
661
662 BEAST_EXPECT(
663 result[jss::engine_result] == "tecNO_DST_INSUF_XRP");
664 BEAST_EXPECT(result[jss::engine_result_code] == 125);
665 BEAST_EXPECT(
666 result[jss::engine_result_message] ==
667 "Destination does not exist. Too little XRP sent to "
668 "create "
669 "it.");
670
671 if (BEAST_EXPECT(
672 result.isMember(jss::meta) ||
673 result.isMember(jss::meta_blob)))
674 {
675 Json::Value const metadata = getJsonMetadata(result);
676
677 if (BEAST_EXPECT(
678 metadata.isMember(sfAffectedNodes.jsonName)))
679 {
680 BEAST_EXPECT(
681 metadata[sfAffectedNodes.jsonName].size() == 1);
682 auto node = metadata[sfAffectedNodes.jsonName][0u];
683 if (BEAST_EXPECT(
684 node.isMember(sfModifiedNode.jsonName)))
685 {
686 auto modifiedNode = node[sfModifiedNode];
687 BEAST_EXPECT(
688 modifiedNode[sfLedgerEntryType] ==
689 "AccountRoot");
690 auto finalFields = modifiedNode[sfFinalFields];
691 BEAST_EXPECT(
692 finalFields[sfBalance] ==
694 100'000'000'000'000'000 -
695 env.current()->fees().base.drops()));
696 }
697 }
698 BEAST_EXPECT(
699 metadata[sfTransactionIndex.jsonName] == 0);
700 BEAST_EXPECT(
701 metadata[sfTransactionResult.jsonName] ==
702 "tecNO_DST_INSUF_XRP");
703 }
704 };
705
706 Json::Value tx;
707
708 tx[jss::Account] = env.master.human();
709 tx[jss::TransactionType] = jss::Payment;
710 tx[sfDestination] = alice.human();
711 tx[sfAmount] = "1"; // not enough to create an account
712
713 // test with autofill
714 testTx(env, tx, testSimulation);
715
716 tx[sfSigningPubKey] = "";
717 tx[sfTxnSignature] = "";
718 tx[sfSequence] = 1;
719 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
720
721 // test without autofill
722 testTx(env, tx, testSimulation);
723 }
724 }
725
726 void
728 {
729 testcase("Successful multi-signed transaction");
730
731 using namespace jtx;
732 Env env(*this);
733 static auto const newDomain = "123ABC";
734 Account const alice("alice");
735 Account const becky("becky");
736 Account const carol("carol");
737 env.fund(XRP(10000), alice);
738 env.close();
739
740 // set up valid multisign
741 env(signers(alice, 1, {{becky, 1}, {carol, 1}}));
742 env.close();
743
744 {
745 auto validateOutput = [&](Json::Value const& resp,
746 Json::Value const& tx) {
747 auto result = resp[jss::result];
749 result,
750 tx,
751 env.seq(alice),
752 tx.isMember(jss::Signers) ? env.current()->fees().base * 2
753 : env.current()->fees().base);
754
755 BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS");
756 BEAST_EXPECT(result[jss::engine_result_code] == 0);
757 BEAST_EXPECT(
758 result[jss::engine_result_message] ==
759 "The simulated transaction would have been applied.");
760
761 if (BEAST_EXPECT(
762 result.isMember(jss::meta) ||
763 result.isMember(jss::meta_blob)))
764 {
765 Json::Value const metadata = getJsonMetadata(result);
766
767 if (BEAST_EXPECT(
768 metadata.isMember(sfAffectedNodes.jsonName)))
769 {
770 BEAST_EXPECT(
771 metadata[sfAffectedNodes.jsonName].size() == 1);
772 auto node = metadata[sfAffectedNodes.jsonName][0u];
773 if (BEAST_EXPECT(
774 node.isMember(sfModifiedNode.jsonName)))
775 {
776 auto modifiedNode = node[sfModifiedNode];
777 BEAST_EXPECT(
778 modifiedNode[sfLedgerEntryType] ==
779 "AccountRoot");
780 auto finalFields = modifiedNode[sfFinalFields];
781 BEAST_EXPECT(finalFields[sfDomain] == newDomain);
782 }
783 }
784 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
785 BEAST_EXPECT(
786 metadata[sfTransactionResult.jsonName] == "tesSUCCESS");
787 }
788 };
789
790 Json::Value tx;
791
792 tx[jss::Account] = alice.human();
793 tx[jss::TransactionType] = jss::AccountSet;
794 tx[sfDomain] = newDomain;
795
796 // test with autofill
797 testTx(env, tx, validateOutput, false);
798
799 tx[sfSigners] = Json::arrayValue;
800 {
802 signer[jss::Account] = becky.human();
803 Json::Value signerOuter;
804 signerOuter[sfSigner] = signer;
805 tx[sfSigners].append(signerOuter);
806 }
807
808 // test with just signer accounts
809 testTx(env, tx, validateOutput, false);
810
811 tx[sfSigningPubKey] = "";
812 tx[sfTxnSignature] = "";
813 tx[sfSequence] = env.seq(alice);
814 // transaction requires a non-base fee
815 tx[sfFee] =
816 (env.current()->fees().base * 2).jsonClipped().asString();
817 tx[sfSigners][0u][sfSigner][jss::SigningPubKey] = "";
818 tx[sfSigners][0u][sfSigner][jss::TxnSignature] = "";
819
820 // test without autofill
821 testTx(env, tx, validateOutput);
822 }
823 }
824
825 void
827 {
828 testcase("Transaction with a key-related failure");
829
830 using namespace jtx;
831 Env env(*this);
832 static auto const newDomain = "123ABC";
833 Account const alice{"alice"};
834 env(regkey(env.master, alice));
835 env(fset(env.master, asfDisableMaster), sig(env.master));
836 env.close();
837
838 {
839 std::function<void(Json::Value const&, Json::Value const&)> const&
840 testSimulation =
841 [&](Json::Value const& resp, Json::Value const& tx) {
842 auto result = resp[jss::result];
844 result,
845 tx,
846 env.seq(env.master),
847 env.current()->fees().base);
848
849 BEAST_EXPECT(
850 result[jss::engine_result] == "tefMASTER_DISABLED");
851 BEAST_EXPECT(result[jss::engine_result_code] == -188);
852 BEAST_EXPECT(
853 result[jss::engine_result_message] ==
854 "Master key is disabled.");
855
856 BEAST_EXPECT(
857 !result.isMember(jss::meta) &&
858 !result.isMember(jss::meta_blob));
859 };
860
861 Json::Value tx;
862
863 tx[jss::Account] = env.master.human();
864 tx[jss::TransactionType] = jss::AccountSet;
865 tx[sfDomain] = newDomain;
866 // master key is disabled, so this is invalid
867 tx[jss::SigningPubKey] = strHex(env.master.pk().slice());
868
869 // test with autofill
870 testTx(env, tx, testSimulation);
871
872 tx[sfTxnSignature] = "";
873 tx[sfSequence] = env.seq(env.master);
874 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
875
876 // test without autofill
877 testTx(env, tx, testSimulation);
878 }
879 }
880
881 void
883 {
884 testcase(
885 "Transaction with both single-signing SigningPubKey and "
886 "multi-signing Signers");
887
888 using namespace jtx;
889 Env env(*this);
890 static auto const newDomain = "123ABC";
891 Account const alice("alice");
892 Account const becky("becky");
893 Account const carol("carol");
894 env.fund(XRP(10000), alice);
895 env.close();
896
897 // set up valid multisign
898 env(signers(alice, 1, {{becky, 1}, {carol, 1}}));
899 env.close();
900
901 {
902 std::function<void(Json::Value const&, Json::Value const&)> const&
903 testSimulation = [&](Json::Value const& resp,
904 Json::Value const& tx) {
905 auto result = resp[jss::result];
907 result,
908 tx,
909 env.seq(env.master),
910 env.current()->fees().base * 2);
911
912 BEAST_EXPECT(result[jss::engine_result] == "temINVALID");
913 BEAST_EXPECT(result[jss::engine_result_code] == -277);
914 BEAST_EXPECT(
915 result[jss::engine_result_message] ==
916 "The transaction is ill-formed.");
917
918 BEAST_EXPECT(
919 !result.isMember(jss::meta) &&
920 !result.isMember(jss::meta_blob));
921 };
922
923 Json::Value tx;
924
925 tx[jss::Account] = env.master.human();
926 tx[jss::TransactionType] = jss::AccountSet;
927 tx[sfDomain] = newDomain;
928 // master key is disabled, so this is invalid
929 tx[jss::SigningPubKey] = strHex(env.master.pk().slice());
930 tx[sfSigners] = Json::arrayValue;
931 {
933 signer[jss::Account] = becky.human();
934 Json::Value signerOuter;
935 signerOuter[sfSigner] = signer;
936 tx[sfSigners].append(signerOuter);
937 }
938
939 // test with autofill
940 testTx(env, tx, testSimulation, false);
941
942 tx[sfTxnSignature] = "";
943 tx[sfSequence] = env.seq(env.master);
944 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
945 tx[sfSigners][0u][sfSigner][jss::SigningPubKey] =
946 strHex(becky.pk().slice());
947 tx[sfSigners][0u][sfSigner][jss::TxnSignature] = "";
948
949 // test without autofill
950 testTx(env, tx, testSimulation);
951 }
952 }
953
954 void
956 {
957 testcase("Multi-signed transaction with a bad public key");
958
959 using namespace jtx;
960 Env env(*this);
961 static auto const newDomain = "123ABC";
962 Account const alice("alice");
963 Account const becky("becky");
964 Account const carol("carol");
965 Account const dylan("dylan");
966 env.fund(XRP(10000), alice);
967 env.close();
968
969 // set up valid multisign
970 env(signers(alice, 1, {{becky, 1}, {carol, 1}}));
971
972 {
973 auto validateOutput = [&](Json::Value const& resp,
974 Json::Value const& tx) {
975 auto result = resp[jss::result];
977 result, tx, env.seq(alice), env.current()->fees().base * 2);
978
979 BEAST_EXPECTS(
980 result[jss::engine_result] == "tefBAD_SIGNATURE",
981 result[jss::engine_result].toStyledString());
982 BEAST_EXPECT(result[jss::engine_result_code] == -186);
983 BEAST_EXPECT(
984 result[jss::engine_result_message] ==
985 "A signature is provided for a non-signer.");
986
987 BEAST_EXPECT(
988 !result.isMember(jss::meta) &&
989 !result.isMember(jss::meta_blob));
990 };
991
992 Json::Value tx;
993
994 tx[jss::Account] = alice.human();
995 tx[jss::TransactionType] = jss::AccountSet;
996 tx[sfDomain] = newDomain;
997 tx[sfSigners] = Json::arrayValue;
998 {
1000 signer[jss::Account] = becky.human();
1001 signer[jss::SigningPubKey] = strHex(dylan.pk().slice());
1002 Json::Value signerOuter;
1003 signerOuter[sfSigner] = signer;
1004 tx[sfSigners].append(signerOuter);
1005 }
1006
1007 // test with autofill
1008 testTx(env, tx, validateOutput, false);
1009
1010 tx[sfSigningPubKey] = "";
1011 tx[sfTxnSignature] = "";
1012 tx[sfSequence] = env.seq(alice);
1013 // transaction requires a non-base fee
1014 tx[sfFee] =
1015 (env.current()->fees().base * 2).jsonClipped().asString();
1016 tx[sfSigners][0u][sfSigner][jss::TxnSignature] = "";
1017
1018 // test without autofill
1019 testTx(env, tx, validateOutput);
1020 }
1021 }
1022
1023 void
1025 {
1026 testcase("Credentials aren't actually deleted on `tecEXPIRED`");
1027
1028 // scenario setup
1029
1030 using namespace jtx;
1031 Env env(*this);
1032
1033 Account const subject{"subject"};
1034 Account const issuer{"issuer"};
1035
1036 env.fund(XRP(10000), subject, issuer);
1037 env.close();
1038
1039 auto const credType = "123ABC";
1040
1041 auto jv = credentials::create(subject, issuer, credType);
1042 uint32_t const t =
1043 env.current()->info().parentCloseTime.time_since_epoch().count();
1044 jv[sfExpiration.jsonName] = t;
1045 env(jv);
1046 env.close();
1047
1048 {
1049 auto validateOutput = [&](Json::Value const& resp,
1050 Json::Value const& tx) {
1051 auto result = resp[jss::result];
1053 result, tx, env.seq(subject), env.current()->fees().base);
1054
1055 BEAST_EXPECT(result[jss::engine_result] == "tecEXPIRED");
1056 BEAST_EXPECT(result[jss::engine_result_code] == 148);
1057 BEAST_EXPECT(
1058 result[jss::engine_result_message] ==
1059 "Expiration time is passed.");
1060
1061 if (BEAST_EXPECT(
1062 result.isMember(jss::meta) ||
1063 result.isMember(jss::meta_blob)))
1064 {
1065 Json::Value const metadata = getJsonMetadata(result);
1066
1067 if (BEAST_EXPECT(
1068 metadata.isMember(sfAffectedNodes.jsonName)))
1069 {
1070 BEAST_EXPECT(
1071 metadata[sfAffectedNodes.jsonName].size() == 5);
1072
1073 try
1074 {
1075 bool found = false;
1076 for (auto const& node :
1077 metadata[sfAffectedNodes.jsonName])
1078 {
1079 if (node.isMember(sfDeletedNode.jsonName) &&
1080 node[sfDeletedNode.jsonName]
1081 [sfLedgerEntryType.jsonName]
1082 .asString() == "Credential")
1083 {
1084 auto const deleted =
1085 node[sfDeletedNode.jsonName]
1086 [sfFinalFields.jsonName];
1087 found = deleted[jss::Issuer] ==
1088 issuer.human() &&
1089 deleted[jss::Subject] ==
1090 subject.human() &&
1091 deleted["CredentialType"] ==
1092 strHex(std::string_view(credType));
1093 break;
1094 }
1095 }
1096 BEAST_EXPECT(found);
1097 }
1098 catch (...)
1099 {
1100 fail();
1101 }
1102 }
1103 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
1104 BEAST_EXPECT(
1105 metadata[sfTransactionResult.jsonName] == "tecEXPIRED");
1106 }
1107 };
1108
1109 Json::Value tx = credentials::accept(subject, issuer, credType);
1110
1111 // test with autofill
1112 testTx(env, tx, validateOutput);
1113
1114 tx[sfSigningPubKey] = "";
1115 tx[sfTxnSignature] = "";
1116 tx[sfSequence] = env.seq(subject);
1117 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
1118
1119 // test without autofill
1120 testTx(env, tx, validateOutput);
1121 }
1122
1123 // check that expired credentials weren't deleted
1124 auto const jle =
1125 credentials::ledgerEntry(env, subject, issuer, credType);
1126 BEAST_EXPECT(
1127 jle.isObject() && jle.isMember(jss::result) &&
1128 !jle[jss::result].isMember(jss::error) &&
1129 jle[jss::result].isMember(jss::node) &&
1130 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
1131 jle[jss::result][jss::node]["LedgerEntryType"] == jss::Credential &&
1132 jle[jss::result][jss::node][jss::Issuer] == issuer.human() &&
1133 jle[jss::result][jss::node][jss::Subject] == subject.human() &&
1134 jle[jss::result][jss::node]["CredentialType"] ==
1135 strHex(std::string_view(credType)));
1136
1137 BEAST_EXPECT(ownerCount(env, issuer) == 1);
1138 BEAST_EXPECT(ownerCount(env, subject) == 0);
1139 }
1140
1141 void
1143 {
1144 testcase("Successful transaction with a custom network ID");
1145
1146 using namespace jtx;
1147 Env env{*this, envconfig([&](std::unique_ptr<Config> cfg) {
1148 cfg->NETWORK_ID = 1025;
1149 return cfg;
1150 })};
1151 static auto const newDomain = "123ABC";
1152
1153 {
1154 auto validateOutput = [&](Json::Value const& resp,
1155 Json::Value const& tx) {
1156 auto result = resp[jss::result];
1158 result, tx, 1, env.current()->fees().base);
1159
1160 BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS");
1161 BEAST_EXPECT(result[jss::engine_result_code] == 0);
1162 BEAST_EXPECT(
1163 result[jss::engine_result_message] ==
1164 "The simulated transaction would have been applied.");
1165
1166 if (BEAST_EXPECT(
1167 result.isMember(jss::meta) ||
1168 result.isMember(jss::meta_blob)))
1169 {
1170 Json::Value const metadata = getJsonMetadata(result);
1171
1172 if (BEAST_EXPECT(
1173 metadata.isMember(sfAffectedNodes.jsonName)))
1174 {
1175 BEAST_EXPECT(
1176 metadata[sfAffectedNodes.jsonName].size() == 1);
1177 auto node = metadata[sfAffectedNodes.jsonName][0u];
1178 if (BEAST_EXPECT(
1179 node.isMember(sfModifiedNode.jsonName)))
1180 {
1181 auto modifiedNode = node[sfModifiedNode];
1182 BEAST_EXPECT(
1183 modifiedNode[sfLedgerEntryType] ==
1184 "AccountRoot");
1185 auto finalFields = modifiedNode[sfFinalFields];
1186 BEAST_EXPECT(finalFields[sfDomain] == newDomain);
1187 }
1188 }
1189 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
1190 BEAST_EXPECT(
1191 metadata[sfTransactionResult.jsonName] == "tesSUCCESS");
1192 }
1193 };
1194
1195 Json::Value tx;
1196
1197 tx[jss::Account] = env.master.human();
1198 tx[jss::TransactionType] = jss::AccountSet;
1199 tx[sfDomain] = newDomain;
1200
1201 // test with autofill
1202 testTx(env, tx, validateOutput);
1203
1204 tx[sfSigningPubKey] = "";
1205 tx[sfTxnSignature] = "";
1206 tx[sfSequence] = 1;
1207 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
1208 tx[sfNetworkID] = 1025;
1209
1210 // test without autofill
1211 testTx(env, tx, validateOutput);
1212 }
1213 }
1214
1215 void
1217 {
1218 testcase("Successful transaction with additional metadata");
1219
1220 using namespace jtx;
1221 Env env{*this, envconfig([&](std::unique_ptr<Config> cfg) {
1222 cfg->NETWORK_ID = 1025;
1223 return cfg;
1224 })};
1225
1226 Account const alice("alice");
1227
1228 env.fund(XRP(10000), alice);
1229 env.close();
1230
1231 {
1232 auto validateOutput = [&](Json::Value const& resp,
1233 Json::Value const& tx,
1234 Json::Value const& expectedMetadataKey) {
1235 auto result = resp[jss::result];
1236
1237 BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS");
1238 BEAST_EXPECT(result[jss::engine_result_code] == 0);
1239 BEAST_EXPECT(
1240 result[jss::engine_result_message] ==
1241 "The simulated transaction would have been applied.");
1242
1243 if (BEAST_EXPECT(
1244 result.isMember(jss::meta) ||
1245 result.isMember(jss::meta_blob)))
1246 {
1247 Json::Value const metadata = getJsonMetadata(result);
1248
1249 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
1250 BEAST_EXPECT(
1251 metadata[sfTransactionResult.jsonName] == "tesSUCCESS");
1252 BEAST_EXPECT(
1253 metadata.isMember(expectedMetadataKey.asString()));
1254 }
1255 };
1256
1257 {
1258 Json::Value tx;
1259 tx[jss::Account] = env.master.human();
1260 tx[jss::TransactionType] = jss::Payment;
1261 tx[sfDestination] = alice.human();
1262 tx[sfAmount] = "100";
1263
1264 // test delivered amount
1266 env, tx, validateOutput, jss::delivered_amount);
1267 }
1268
1269 {
1270 Json::Value tx;
1271 tx[jss::Account] = env.master.human();
1272 tx[jss::TransactionType] = jss::NFTokenMint;
1273 tx[sfNFTokenTaxon] = 1;
1274
1275 // test nft synthetic
1277 env, tx, validateOutput, jss::nftoken_id);
1278 }
1279
1280 {
1281 Json::Value tx;
1282 tx[jss::Account] = env.master.human();
1283 tx[jss::TransactionType] = jss::MPTokenIssuanceCreate;
1284
1285 // test mpt issuance id
1287 env, tx, validateOutput, jss::mpt_issuance_id);
1288 }
1289 }
1290 }
1291
1292public:
1293 void
1310};
1311
1312BEAST_DEFINE_TESTSUITE(Simulate, rpc, ripple);
1313
1314} // namespace test
1315
1316} // namespace ripple
Represents a JSON value.
Definition json_value.h:149
Value & append(Value const &value)
Append value to array at the end.
UInt size() const
Number of values in array or object.
Value removeMember(char const *key)
Remove and return the named member.
std::string asString() const
Returns the unquoted string value.
bool isMember(char const *key) const
Return true if the object has a member named key.
Value get(UInt index, Value const &defaultValue) const
If the array contains at least index+1 elements, returns the element value, otherwise returns default...
A testsuite class.
Definition suite.h:55
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:155
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:533
virtual TxQ & getTxQ()=0
Slice slice() const noexcept
Definition PublicKey.h:122
Json::Value getJson(JsonOptions=JsonOptions::none) const override
Definition STObject.cpp:825
Holds the serialized result of parsing an input JSON object.
std::optional< STObject > object
The STObject if the parse was successful.
Metrics getMetrics(OpenView const &view) const
Returns fee metrics in reference fee level units.
Definition TxQ.cpp:1778
Json::Value jsonClipped() const
Definition XRPAmount.h:218
Json::Value getJsonMetadata(Json::Value txResult) const
void testTx(jtx::Env &env, Json::Value const &tx, std::function< void(Json::Value const &, Json::Value const &)> const &validate, bool testSerialized=true)
void run() override
Runs the suite.
void checkBasicReturnValidity(Json::Value const &result, Json::Value const &tx, int const expectedSequence, XRPAmount const &expectedFee)
void checkBasicReturnValidity(Json::Value const &result, Json::Value const &tx, int const expectedSequence, std::string const &expectedFee)
void testTxJsonMetadataField(jtx::Env &env, Json::Value const &tx, std::function< void(Json::Value const &, Json::Value const &, Json::Value const &)> const &validate, Json::Value const &expectedMetadataKey, bool testSerialized=true)
Immutable cryptographic account descriptor.
Definition Account.h:39
PublicKey const & pk() const
Return the public key.
Definition Account.h:94
std::string const & human() const
Returns the human readable public key.
Definition Account.h:118
A transaction testing environment.
Definition Env.h:121
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:268
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:331
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:121
Account const & master
Definition Env.h:125
Application & app()
Definition Env.h:261
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition Env.h:791
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:289
JTx jtnofill(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition Env.h:520
Adds a new Batch Txn on a JTx and autofills.
Definition batch.h:61
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition rpc.h:35
Set the regular signature on a JTx.
Definition sig.h:35
@ arrayValue
array value (ordered list)
Definition json_value.h:44
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:45
Json::Value outer(jtx::Account const &account, uint32_t seq, STAmount const &fee, std::uint32_t flags)
Batch.
Definition batch.cpp:49
XRPAmount calcBatchFee(jtx::Env const &env, uint32_t const &numSigners, uint32_t const &txns=0)
Calculate Batch Fee.
Definition batch.cpp:38
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:32
Json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:48
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:78
std::uint32_t ownerCount(Env const &env, Account const &account)
Json::Value regkey(Account const &account, disabled_t)
Disable the regular key.
Definition regkey.cpp:29
Json::Value signers(Account const &account, std::uint32_t quorum, std::vector< signer > const &v)
Definition multisign.cpp:34
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:29
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:30
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:54
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:111
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
constexpr std::uint32_t tfAllOrNothing
Definition TxFlags.h:276
std::optional< Blob > strUnHex(std::size_t strSize, Iterator begin, Iterator end)
@ rpcHIGH_FEE
Definition ErrorCodes.h:58
bool set(T &target, std::string const &name, Section const &section)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
constexpr std::uint32_t asfDisableMaster
Definition TxFlags.h:80
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:30
std::enable_if_t< std::is_same< T, char >::value||std::is_same< T, unsigned char >::value, Slice > makeSlice(std::array< T, N > const &a)
Definition Slice.h:244
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630
SField const sfGeneric
T ref(T... args)
Json::Value jv
Definition JTx.h:46
Set the sequence number on a JTx.
Definition seq.h:34
A signer in a SignerList.
Definition multisign.h:39
T to_string(T... args)