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