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