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