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