rippled
Loading...
Searching...
No Matches
Simulate_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012-2017 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <test/jtx.h>
21#include <test/jtx/Env.h>
22#include <test/jtx/envconfig.h>
23
24#include <xrpld/app/rdb/backend/SQLiteDatabase.h>
25#include <xrpld/rpc/CTID.h>
26
27#include <xrpl/protocol/ErrorCodes.h>
28#include <xrpl/protocol/STBase.h>
29#include <xrpl/protocol/STParsedJSON.h>
30#include <xrpl/protocol/jss.h>
31#include <xrpl/protocol/serialize.h>
32
33#include <optional>
34#include <tuple>
35
36namespace ripple {
37
38namespace test {
39
41{
42 void
44 Json::Value const& result,
45 Json::Value const& tx,
46 int const expectedSequence,
47 std::string const& expectedFee)
48 {
49 BEAST_EXPECT(result[jss::applied] == false);
50 BEAST_EXPECT(result.isMember(jss::engine_result));
51 BEAST_EXPECT(result.isMember(jss::engine_result_code));
52 BEAST_EXPECT(result.isMember(jss::engine_result_message));
53 BEAST_EXPECT(
54 result.isMember(jss::tx_json) || result.isMember(jss::tx_blob));
55
56 Json::Value tx_json;
57 if (result.isMember(jss::tx_json))
58 {
59 tx_json = result[jss::tx_json];
60 }
61 else
62 {
63 auto const unHexed = strUnHex(result[jss::tx_blob].asString());
64 SerialIter sitTrans(makeSlice(*unHexed));
65 tx_json = STObject(std::ref(sitTrans), sfGeneric)
67 }
68 BEAST_EXPECT(tx_json[jss::TransactionType] == tx[jss::TransactionType]);
69 BEAST_EXPECT(tx_json[jss::Account] == tx[jss::Account]);
70 BEAST_EXPECT(
71 tx_json[jss::SigningPubKey] == tx.get(jss::SigningPubKey, ""));
72 BEAST_EXPECT(
73 tx_json[jss::TxnSignature] == tx.get(jss::TxnSignature, ""));
74 BEAST_EXPECT(tx_json[jss::Fee] == tx.get(jss::Fee, expectedFee));
75 BEAST_EXPECT(
76 tx_json[jss::Sequence] == tx.get(jss::Sequence, expectedSequence));
77 }
78
79 void
81 Json::Value const& result,
82 Json::Value const& tx,
83 int const expectedSequence,
84 XRPAmount const& expectedFee)
85 {
87 result, tx, expectedSequence, expectedFee.jsonClipped().asString());
88 }
89
90 void
92 jtx::Env& env,
93 Json::Value const& tx,
94 std::function<void(Json::Value const&, Json::Value const&)> const&
95 validate,
96 bool testSerialized = true)
97 {
98 env.close();
99
100 Json::Value params;
101 params[jss::tx_json] = tx;
102 validate(env.rpc("json", "simulate", to_string(params)), tx);
103
104 params[jss::binary] = true;
105 validate(env.rpc("json", "simulate", to_string(params)), tx);
106 validate(env.rpc("simulate", to_string(tx)), tx);
107 validate(env.rpc("simulate", to_string(tx), "binary"), tx);
108
109 if (testSerialized)
110 {
111 // This cannot be tested in the multisign autofill scenario
112 // It is technically not a valid STObject, so the following line
113 // will crash
114 STParsedJSONObject const parsed(std::string(jss::tx_json), tx);
115 auto const tx_blob =
116 strHex(parsed.object->getSerializer().peekData());
117 if (BEAST_EXPECT(parsed.object.has_value()))
118 {
119 Json::Value params;
120 params[jss::tx_blob] = tx_blob;
121 validate(env.rpc("json", "simulate", to_string(params)), tx);
122 params[jss::binary] = true;
123 validate(env.rpc("json", "simulate", to_string(params)), tx);
124 }
125 validate(env.rpc("simulate", tx_blob), tx);
126 validate(env.rpc("simulate", tx_blob, "binary"), tx);
127 }
128
129 BEAST_EXPECTS(
130 env.current()->txCount() == 0,
131 std::to_string(env.current()->txCount()));
132 }
133
136 {
137 if (txResult.isMember(jss::meta_blob))
138 {
139 auto unHexed = strUnHex(txResult[jss::meta_blob].asString());
140 SerialIter sitTrans(makeSlice(*unHexed));
141 return STObject(std::ref(sitTrans), sfGeneric)
143 }
144
145 return txResult[jss::meta];
146 }
147
148 void
150 {
151 testcase("Test parameter errors");
152
153 using namespace jtx;
154 Env env(*this);
155 Account const alice("alice");
156
157 {
158 // No params
159 Json::Value const params = Json::objectValue;
160 auto const resp = env.rpc("json", "simulate", to_string(params));
161 BEAST_EXPECT(
162 resp[jss::result][jss::error_message] ==
163 "Neither `tx_blob` nor `tx_json` included.");
164 }
165 {
166 // Providing both `tx_json` and `tx_blob`
168 params[jss::tx_json] = Json::objectValue;
169 params[jss::tx_blob] = "1200";
170
171 auto const resp = env.rpc("json", "simulate", to_string(params));
172 BEAST_EXPECT(
173 resp[jss::result][jss::error_message] ==
174 "Can only include one of `tx_blob` and `tx_json`.");
175 }
176 {
177 // `binary` isn't a boolean
179 params[jss::tx_blob] = "1200";
180 params[jss::binary] = "100";
181 auto const resp = env.rpc("json", "simulate", to_string(params));
182 BEAST_EXPECT(
183 resp[jss::result][jss::error_message] ==
184 "Invalid field 'binary'.");
185 }
186 {
187 // Invalid `tx_blob`
189 params[jss::tx_blob] = "12";
190
191 auto const resp = env.rpc("json", "simulate", to_string(params));
192 BEAST_EXPECT(
193 resp[jss::result][jss::error_message] ==
194 "Invalid field 'tx_blob'.");
195 }
196 {
197 // Empty `tx_json`
199 params[jss::tx_json] = Json::objectValue;
200
201 auto const resp = env.rpc("json", "simulate", to_string(params));
202 BEAST_EXPECT(
203 resp[jss::result][jss::error_message] ==
204 "Missing field 'tx.TransactionType'.");
205 }
206 {
207 // No tx.Account
210 tx_json[jss::TransactionType] = jss::Payment;
211 params[jss::tx_json] = tx_json;
212
213 auto const resp = env.rpc("json", "simulate", to_string(params));
214 BEAST_EXPECT(
215 resp[jss::result][jss::error_message] ==
216 "Missing field 'tx.Account'.");
217 }
218 {
219 // Empty `tx_blob`
221 params[jss::tx_blob] = "";
222
223 auto const resp = env.rpc("json", "simulate", to_string(params));
224 BEAST_EXPECT(
225 resp[jss::result][jss::error_message] ==
226 "Invalid field 'tx_blob'.");
227 }
228 {
229 // Non-string `tx_blob`
230 Json::Value params;
231 params[jss::tx_blob] = 1.1;
232
233 auto const resp = env.rpc("json", "simulate", to_string(params));
234 BEAST_EXPECT(
235 resp[jss::result][jss::error_message] ==
236 "Invalid field 'tx_blob'.");
237 }
238 {
239 // Non-object `tx_json`
241 params[jss::tx_json] = "";
242
243 auto const resp = env.rpc("json", "simulate", to_string(params));
244 BEAST_EXPECT(
245 resp[jss::result][jss::error_message] ==
246 "Invalid field 'tx_json', not object.");
247 }
248 {
249 // `seed` field included
251 params[jss::seed] = "doesnt_matter";
253 tx_json[jss::TransactionType] = jss::AccountSet;
254 tx_json[jss::Account] = env.master.human();
255 params[jss::tx_json] = tx_json;
256 auto const resp = env.rpc("json", "simulate", to_string(params));
257 BEAST_EXPECT(
258 resp[jss::result][jss::error_message] ==
259 "Invalid field 'seed'.");
260 }
261 {
262 // `secret` field included
264 params[jss::secret] = "doesnt_matter";
266 tx_json[jss::TransactionType] = jss::AccountSet;
267 tx_json[jss::Account] = env.master.human();
268 params[jss::tx_json] = tx_json;
269 auto const resp = env.rpc("json", "simulate", to_string(params));
270 BEAST_EXPECT(
271 resp[jss::result][jss::error_message] ==
272 "Invalid field 'secret'.");
273 }
274 {
275 // `seed_hex` field included
277 params[jss::seed_hex] = "doesnt_matter";
279 tx_json[jss::TransactionType] = jss::AccountSet;
280 tx_json[jss::Account] = env.master.human();
281 params[jss::tx_json] = tx_json;
282 auto const resp = env.rpc("json", "simulate", to_string(params));
283 BEAST_EXPECT(
284 resp[jss::result][jss::error_message] ==
285 "Invalid field 'seed_hex'.");
286 }
287 {
288 // `passphrase` field included
290 params[jss::passphrase] = "doesnt_matter";
292 tx_json[jss::TransactionType] = jss::AccountSet;
293 tx_json[jss::Account] = env.master.human();
294 params[jss::tx_json] = tx_json;
295 auto const resp = env.rpc("json", "simulate", to_string(params));
296 BEAST_EXPECT(
297 resp[jss::result][jss::error_message] ==
298 "Invalid field 'passphrase'.");
299 }
300 {
301 // Invalid transaction
304 tx_json[jss::TransactionType] = jss::Payment;
305 tx_json[jss::Account] = env.master.human();
306 params[jss::tx_json] = tx_json;
307
308 auto const resp = env.rpc("json", "simulate", to_string(params));
309 BEAST_EXPECT(
310 resp[jss::result][jss::error_exception] ==
311 "Field 'Destination' is required but missing.");
312 }
313 {
314 // Bad account
315 Json::Value params;
317 tx_json[jss::TransactionType] = jss::AccountSet;
318 tx_json[jss::Account] = "badAccount";
319 params[jss::tx_json] = tx_json;
320
321 auto const resp = env.rpc("json", "simulate", to_string(params));
322 BEAST_EXPECTS(
323 resp[jss::result][jss::error] == "srcActMalformed",
324 resp[jss::result][jss::error].toStyledString());
325 BEAST_EXPECT(
326 resp[jss::result][jss::error_message] ==
327 "Invalid field 'tx.Account'.");
328 }
329 {
330 // Account doesn't exist for Sequence autofill
331 Json::Value params;
333 tx_json[jss::TransactionType] = jss::AccountSet;
334 tx_json[jss::Account] = alice.human();
335 params[jss::tx_json] = tx_json;
336
337 auto const resp = env.rpc("json", "simulate", to_string(params));
338 BEAST_EXPECT(
339 resp[jss::result][jss::error_message] ==
340 "Source account not found.");
341 }
342 {
343 // Invalid Signers field
344 Json::Value params;
346 tx_json[jss::TransactionType] = jss::AccountSet;
347 tx_json[jss::Account] = env.master.human();
348 tx_json[sfSigners] = "1";
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] ==
354 "Invalid field 'tx.Signers'.");
355 }
356 {
357 // Invalid Signers field
358 Json::Value params;
360 tx_json[jss::TransactionType] = jss::AccountSet;
361 tx_json[jss::Account] = env.master.human();
362 tx_json[sfSigners] = Json::arrayValue;
363 tx_json[sfSigners].append("1");
364 params[jss::tx_json] = tx_json;
365
366 auto const resp = env.rpc("json", "simulate", to_string(params));
367 BEAST_EXPECT(
368 resp[jss::result][jss::error_message] ==
369 "Invalid field 'tx.Signers[0]'.");
370 }
371 {
372 // Invalid transaction
373 Json::Value params;
375 tx_json[jss::TransactionType] = jss::AccountSet;
376 tx_json[jss::Account] = env.master.human();
377 tx_json["foo"] = "bar";
378 params[jss::tx_json] = tx_json;
379
380 auto const resp = env.rpc("json", "simulate", to_string(params));
381 BEAST_EXPECT(
382 resp[jss::result][jss::error_message] ==
383 "Field 'tx_json.foo' is unknown.");
384 }
385 {
386 // non-`"binary"` second param for CLI
388 tx_json[jss::TransactionType] = jss::AccountSet;
389 tx_json[jss::Account] = alice.human();
390 auto const resp = env.rpc("simulate", to_string(tx_json), "1");
391 BEAST_EXPECT(resp[jss::error_message] == "Invalid parameters.");
392 }
393 {
394 // Signed transaction
395 Json::Value params;
397 tx_json[jss::TransactionType] = jss::AccountSet;
398 tx_json[jss::Account] = env.master.human();
399 tx_json[jss::TxnSignature] = "1200ABCD";
400 params[jss::tx_json] = tx_json;
401
402 auto const resp = env.rpc("json", "simulate", to_string(params));
403 BEAST_EXPECT(
404 resp[jss::result][jss::error_message] ==
405 "Transaction should not be signed.");
406 }
407 {
408 // Signed multisig transaction
409 Json::Value params;
411 tx_json[jss::TransactionType] = jss::AccountSet;
412 tx_json[jss::Account] = env.master.human();
413 tx_json[sfSigners] = Json::arrayValue;
414 {
416 signer[jss::Account] = alice.human();
417 signer[jss::SigningPubKey] = alice.human();
418 signer[jss::TxnSignature] = "1200ABCD";
419 Json::Value signerOuter;
420 signerOuter[sfSigner] = signer;
421 tx_json[sfSigners].append(signerOuter);
422 }
423 params[jss::tx_json] = tx_json;
424
425 auto const resp = env.rpc("json", "simulate", to_string(params));
426 BEAST_EXPECT(
427 resp[jss::result][jss::error_message] ==
428 "Transaction should not be signed.");
429 }
430 }
431
432 void
434 {
435 testcase("Fee failure");
436
437 using namespace jtx;
438
439 Env env(*this, envconfig([](std::unique_ptr<Config> cfg) {
440 cfg->section("transaction_queue")
441 .set("minimum_txn_in_ledger_standalone", "3");
442 return cfg;
443 }));
444
445 Account const alice{"alice"};
446 env.fund(XRP(1000000), alice);
447 env.close();
448
449 // fill queue
450 auto metrics = env.app().getTxQ().getMetrics(*env.current());
451 for (int i = metrics.txInLedger; i <= metrics.txPerLedger; ++i)
452 env(noop(alice));
453
454 {
455 Json::Value params;
456 params[jss::tx_json] = noop(alice);
457
458 auto const resp = env.rpc("json", "simulate", to_string(params));
459 auto const result = resp[jss::result];
460 if (BEAST_EXPECT(result.isMember(jss::error)))
461 {
462 BEAST_EXPECT(result[jss::error] == "highFee");
463 BEAST_EXPECT(result[jss::error_code] == rpcHIGH_FEE);
464 }
465 }
466 }
467
468 void
470 {
471 testcase("Successful transaction");
472
473 using namespace jtx;
474 Env env{*this, envconfig([&](std::unique_ptr<Config> cfg) {
475 cfg->NETWORK_ID = 0;
476 return cfg;
477 })};
478 static auto const newDomain = "123ABC";
479
480 {
481 auto validateOutput = [&](Json::Value const& resp,
482 Json::Value const& tx) {
483 auto result = resp[jss::result];
485 result, tx, 1, env.current()->fees().base);
486
487 BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS");
488 BEAST_EXPECT(result[jss::engine_result_code] == 0);
489 BEAST_EXPECT(
490 result[jss::engine_result_message] ==
491 "The simulated transaction would have been applied.");
492
493 if (BEAST_EXPECT(
494 result.isMember(jss::meta) ||
495 result.isMember(jss::meta_blob)))
496 {
497 Json::Value const metadata = getJsonMetadata(result);
498
499 if (BEAST_EXPECT(
500 metadata.isMember(sfAffectedNodes.jsonName)))
501 {
502 BEAST_EXPECT(
503 metadata[sfAffectedNodes.jsonName].size() == 1);
504 auto node = metadata[sfAffectedNodes.jsonName][0u];
505 if (BEAST_EXPECT(
506 node.isMember(sfModifiedNode.jsonName)))
507 {
508 auto modifiedNode = node[sfModifiedNode];
509 BEAST_EXPECT(
510 modifiedNode[sfLedgerEntryType] ==
511 "AccountRoot");
512 auto finalFields = modifiedNode[sfFinalFields];
513 BEAST_EXPECT(finalFields[sfDomain] == newDomain);
514 }
515 }
516 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
517 BEAST_EXPECT(
518 metadata[sfTransactionResult.jsonName] == "tesSUCCESS");
519 }
520 };
521
522 Json::Value tx;
523
524 tx[jss::Account] = env.master.human();
525 tx[jss::TransactionType] = jss::AccountSet;
526 tx[sfDomain] = newDomain;
527
528 // test with autofill
529 testTx(env, tx, validateOutput);
530
531 tx[sfSigningPubKey] = "";
532 tx[sfTxnSignature] = "";
533 tx[sfSequence] = 1;
534 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
535
536 // test without autofill
537 testTx(env, tx, validateOutput);
538 }
539 }
540
541 void
543 {
544 testcase("Transaction non-tec failure");
545
546 using namespace jtx;
547 Env env(*this);
548 Account const alice("alice");
549
550 {
551 std::function<void(Json::Value const&, Json::Value const&)> const&
552 testSimulation = [&](Json::Value const& resp,
553 Json::Value const& tx) {
554 auto result = resp[jss::result];
556 result, tx, 1, env.current()->fees().base);
557
558 BEAST_EXPECT(result[jss::engine_result] == "temBAD_AMOUNT");
559 BEAST_EXPECT(result[jss::engine_result_code] == -298);
560 BEAST_EXPECT(
561 result[jss::engine_result_message] ==
562 "Malformed: Bad amount.");
563
564 BEAST_EXPECT(
565 !result.isMember(jss::meta) &&
566 !result.isMember(jss::meta_blob));
567 };
568
569 Json::Value tx;
570
571 tx[jss::Account] = env.master.human();
572 tx[jss::TransactionType] = jss::Payment;
573 tx[sfDestination] = alice.human();
574 tx[sfAmount] = "0"; // invalid amount
575
576 // test with autofill
577 testTx(env, tx, testSimulation);
578
579 tx[sfSigningPubKey] = "";
580 tx[sfTxnSignature] = "";
581 tx[sfSequence] = 1;
582 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
583
584 // test without autofill
585 testTx(env, tx, testSimulation);
586 }
587 }
588
589 void
591 {
592 testcase("Transaction tec failure");
593
594 using namespace jtx;
595 Env env(*this);
596 Account const alice("alice");
597
598 {
599 std::function<void(Json::Value const&, Json::Value const&)> const&
600 testSimulation = [&](Json::Value const& resp,
601 Json::Value const& tx) {
602 auto result = resp[jss::result];
604 result, tx, 1, env.current()->fees().base);
605
606 BEAST_EXPECT(
607 result[jss::engine_result] == "tecNO_DST_INSUF_XRP");
608 BEAST_EXPECT(result[jss::engine_result_code] == 125);
609 BEAST_EXPECT(
610 result[jss::engine_result_message] ==
611 "Destination does not exist. Too little XRP sent to "
612 "create "
613 "it.");
614
615 if (BEAST_EXPECT(
616 result.isMember(jss::meta) ||
617 result.isMember(jss::meta_blob)))
618 {
619 Json::Value const metadata = getJsonMetadata(result);
620
621 if (BEAST_EXPECT(
622 metadata.isMember(sfAffectedNodes.jsonName)))
623 {
624 BEAST_EXPECT(
625 metadata[sfAffectedNodes.jsonName].size() == 1);
626 auto node = metadata[sfAffectedNodes.jsonName][0u];
627 if (BEAST_EXPECT(
628 node.isMember(sfModifiedNode.jsonName)))
629 {
630 auto modifiedNode = node[sfModifiedNode];
631 BEAST_EXPECT(
632 modifiedNode[sfLedgerEntryType] ==
633 "AccountRoot");
634 auto finalFields = modifiedNode[sfFinalFields];
635 BEAST_EXPECT(
636 finalFields[sfBalance] ==
638 100'000'000'000'000'000 -
639 env.current()->fees().base.drops()));
640 }
641 }
642 BEAST_EXPECT(
643 metadata[sfTransactionIndex.jsonName] == 0);
644 BEAST_EXPECT(
645 metadata[sfTransactionResult.jsonName] ==
646 "tecNO_DST_INSUF_XRP");
647 }
648 };
649
650 Json::Value tx;
651
652 tx[jss::Account] = env.master.human();
653 tx[jss::TransactionType] = jss::Payment;
654 tx[sfDestination] = alice.human();
655 tx[sfAmount] = "1"; // not enough to create an account
656
657 // test with autofill
658 testTx(env, tx, testSimulation);
659
660 tx[sfSigningPubKey] = "";
661 tx[sfTxnSignature] = "";
662 tx[sfSequence] = 1;
663 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
664
665 // test without autofill
666 testTx(env, tx, testSimulation);
667 }
668 }
669
670 void
672 {
673 testcase("Successful multi-signed transaction");
674
675 using namespace jtx;
676 Env env(*this);
677 static auto const newDomain = "123ABC";
678 Account const alice("alice");
679 Account const becky("becky");
680 Account const carol("carol");
681 env.fund(XRP(10000), alice);
682 env.close();
683
684 // set up valid multisign
685 env(signers(alice, 1, {{becky, 1}, {carol, 1}}));
686 env.close();
687
688 {
689 auto validateOutput = [&](Json::Value const& resp,
690 Json::Value const& tx) {
691 auto result = resp[jss::result];
693 result, tx, env.seq(alice), env.current()->fees().base * 2);
694
695 BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS");
696 BEAST_EXPECT(result[jss::engine_result_code] == 0);
697 BEAST_EXPECT(
698 result[jss::engine_result_message] ==
699 "The simulated transaction would have been applied.");
700
701 if (BEAST_EXPECT(
702 result.isMember(jss::meta) ||
703 result.isMember(jss::meta_blob)))
704 {
705 Json::Value const metadata = getJsonMetadata(result);
706
707 if (BEAST_EXPECT(
708 metadata.isMember(sfAffectedNodes.jsonName)))
709 {
710 BEAST_EXPECT(
711 metadata[sfAffectedNodes.jsonName].size() == 1);
712 auto node = metadata[sfAffectedNodes.jsonName][0u];
713 if (BEAST_EXPECT(
714 node.isMember(sfModifiedNode.jsonName)))
715 {
716 auto modifiedNode = node[sfModifiedNode];
717 BEAST_EXPECT(
718 modifiedNode[sfLedgerEntryType] ==
719 "AccountRoot");
720 auto finalFields = modifiedNode[sfFinalFields];
721 BEAST_EXPECT(finalFields[sfDomain] == newDomain);
722 }
723 }
724 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
725 BEAST_EXPECT(
726 metadata[sfTransactionResult.jsonName] == "tesSUCCESS");
727 }
728 };
729
730 Json::Value tx;
731
732 tx[jss::Account] = alice.human();
733 tx[jss::TransactionType] = jss::AccountSet;
734 tx[sfDomain] = newDomain;
735 tx[sfSigners] = Json::arrayValue;
736 {
738 signer[jss::Account] = becky.human();
739 Json::Value signerOuter;
740 signerOuter[sfSigner] = signer;
741 tx[sfSigners].append(signerOuter);
742 }
743
744 // test with autofill
745 testTx(env, tx, validateOutput, false);
746
747 tx[sfSigningPubKey] = "";
748 tx[sfTxnSignature] = "";
749 tx[sfSequence] = env.seq(alice);
750 // transaction requires a non-base fee
751 tx[sfFee] =
752 (env.current()->fees().base * 2).jsonClipped().asString();
753 tx[sfSigners][0u][sfSigner][jss::SigningPubKey] =
754 strHex(becky.pk().slice());
755 tx[sfSigners][0u][sfSigner][jss::TxnSignature] = "";
756
757 // test without autofill
758 testTx(env, tx, validateOutput);
759 }
760 }
761
762 void
764 {
765 testcase("Transaction with a key-related failure");
766
767 using namespace jtx;
768 Env env(*this);
769 static auto const newDomain = "123ABC";
770 Account const alice{"alice"};
771 env(regkey(env.master, alice));
772 env(fset(env.master, asfDisableMaster), sig(env.master));
773 env.close();
774
775 {
776 std::function<void(Json::Value const&, Json::Value const&)> const&
777 testSimulation =
778 [&](Json::Value const& resp, Json::Value const& tx) {
779 auto result = resp[jss::result];
781 result,
782 tx,
783 env.seq(env.master),
784 env.current()->fees().base);
785
786 BEAST_EXPECT(
787 result[jss::engine_result] == "tefMASTER_DISABLED");
788 BEAST_EXPECT(result[jss::engine_result_code] == -188);
789 BEAST_EXPECT(
790 result[jss::engine_result_message] ==
791 "Master key is disabled.");
792
793 BEAST_EXPECT(
794 !result.isMember(jss::meta) &&
795 !result.isMember(jss::meta_blob));
796 };
797
798 Json::Value tx;
799
800 tx[jss::Account] = env.master.human();
801 tx[jss::TransactionType] = jss::AccountSet;
802 tx[sfDomain] = newDomain;
803
804 // test with autofill
805 testTx(env, tx, testSimulation);
806
807 tx[sfSigningPubKey] = "";
808 tx[sfTxnSignature] = "";
809 tx[sfSequence] = env.seq(env.master);
810 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
811
812 // test without autofill
813 testTx(env, tx, testSimulation);
814 }
815 }
816
817 void
819 {
820 testcase("Multi-signed transaction with a bad public key");
821
822 using namespace jtx;
823 Env env(*this);
824 static auto const newDomain = "123ABC";
825 Account const alice("alice");
826 Account const becky("becky");
827 Account const carol("carol");
828 Account const dylan("dylan");
829 env.fund(XRP(10000), alice);
830 env.close();
831
832 // set up valid multisign
833 env(signers(alice, 1, {{becky, 1}, {carol, 1}}));
834
835 {
836 auto validateOutput = [&](Json::Value const& resp,
837 Json::Value const& tx) {
838 auto result = resp[jss::result];
840 result, tx, env.seq(alice), env.current()->fees().base * 2);
841
842 BEAST_EXPECTS(
843 result[jss::engine_result] == "tefBAD_SIGNATURE",
844 result[jss::engine_result].toStyledString());
845 BEAST_EXPECT(result[jss::engine_result_code] == -186);
846 BEAST_EXPECT(
847 result[jss::engine_result_message] ==
848 "A signature is provided for a non-signer.");
849
850 BEAST_EXPECT(
851 !result.isMember(jss::meta) &&
852 !result.isMember(jss::meta_blob));
853 };
854
855 Json::Value tx;
856
857 tx[jss::Account] = alice.human();
858 tx[jss::TransactionType] = jss::AccountSet;
859 tx[sfDomain] = newDomain;
860 tx[sfSigners] = Json::arrayValue;
861 {
863 signer[jss::Account] = becky.human();
864 signer[jss::SigningPubKey] = strHex(dylan.pk().slice());
865 Json::Value signerOuter;
866 signerOuter[sfSigner] = signer;
867 tx[sfSigners].append(signerOuter);
868 }
869
870 // test with autofill
871 testTx(env, tx, validateOutput, false);
872
873 tx[sfSigningPubKey] = "";
874 tx[sfTxnSignature] = "";
875 tx[sfSequence] = env.seq(alice);
876 // transaction requires a non-base fee
877 tx[sfFee] =
878 (env.current()->fees().base * 2).jsonClipped().asString();
879 tx[sfSigners][0u][sfSigner][jss::TxnSignature] = "";
880
881 // test without autofill
882 testTx(env, tx, validateOutput);
883 }
884 }
885
886 void
888 {
889 testcase("Credentials aren't actually deleted on `tecEXPIRED`");
890
891 // scenario setup
892
893 using namespace jtx;
894 Env env(*this);
895
896 Account const subject{"subject"};
897 Account const issuer{"issuer"};
898
899 env.fund(XRP(10000), subject, issuer);
900 env.close();
901
902 auto const credType = "123ABC";
903
904 auto jv = credentials::create(subject, issuer, credType);
905 uint32_t const t =
906 env.current()->info().parentCloseTime.time_since_epoch().count();
907 jv[sfExpiration.jsonName] = t;
908 env(jv);
909 env.close();
910
911 {
912 auto validateOutput = [&](Json::Value const& resp,
913 Json::Value const& tx) {
914 auto result = resp[jss::result];
916 result, tx, env.seq(subject), env.current()->fees().base);
917
918 BEAST_EXPECT(result[jss::engine_result] == "tecEXPIRED");
919 BEAST_EXPECT(result[jss::engine_result_code] == 148);
920 BEAST_EXPECT(
921 result[jss::engine_result_message] ==
922 "Expiration time is passed.");
923
924 if (BEAST_EXPECT(
925 result.isMember(jss::meta) ||
926 result.isMember(jss::meta_blob)))
927 {
928 Json::Value const metadata = getJsonMetadata(result);
929
930 if (BEAST_EXPECT(
931 metadata.isMember(sfAffectedNodes.jsonName)))
932 {
933 BEAST_EXPECT(
934 metadata[sfAffectedNodes.jsonName].size() == 5);
935
936 try
937 {
938 bool found = false;
939 for (auto const& node :
940 metadata[sfAffectedNodes.jsonName])
941 {
942 if (node.isMember(sfDeletedNode.jsonName) &&
943 node[sfDeletedNode.jsonName]
944 [sfLedgerEntryType.jsonName]
945 .asString() == "Credential")
946 {
947 auto const deleted =
948 node[sfDeletedNode.jsonName]
949 [sfFinalFields.jsonName];
950 found = deleted[jss::Issuer] ==
951 issuer.human() &&
952 deleted[jss::Subject] ==
953 subject.human() &&
954 deleted["CredentialType"] ==
955 strHex(std::string_view(credType));
956 break;
957 }
958 }
959 BEAST_EXPECT(found);
960 }
961 catch (...)
962 {
963 fail();
964 }
965 }
966 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
967 BEAST_EXPECT(
968 metadata[sfTransactionResult.jsonName] == "tecEXPIRED");
969 }
970 };
971
972 Json::Value tx = credentials::accept(subject, issuer, credType);
973
974 // test with autofill
975 testTx(env, tx, validateOutput);
976
977 tx[sfSigningPubKey] = "";
978 tx[sfTxnSignature] = "";
979 tx[sfSequence] = env.seq(subject);
980 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
981
982 // test without autofill
983 testTx(env, tx, validateOutput);
984 }
985
986 // check that expired credentials weren't deleted
987 auto const jle =
988 credentials::ledgerEntry(env, subject, issuer, credType);
989 BEAST_EXPECT(
990 jle.isObject() && jle.isMember(jss::result) &&
991 !jle[jss::result].isMember(jss::error) &&
992 jle[jss::result].isMember(jss::node) &&
993 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
994 jle[jss::result][jss::node]["LedgerEntryType"] == jss::Credential &&
995 jle[jss::result][jss::node][jss::Issuer] == issuer.human() &&
996 jle[jss::result][jss::node][jss::Subject] == subject.human() &&
997 jle[jss::result][jss::node]["CredentialType"] ==
998 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,
1018 Json::Value const& tx) {
1019 auto result = resp[jss::result];
1021 result, tx, 1, env.current()->fees().base);
1022
1023 BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS");
1024 BEAST_EXPECT(result[jss::engine_result_code] == 0);
1025 BEAST_EXPECT(
1026 result[jss::engine_result_message] ==
1027 "The simulated transaction would have been applied.");
1028
1029 if (BEAST_EXPECT(
1030 result.isMember(jss::meta) ||
1031 result.isMember(jss::meta_blob)))
1032 {
1033 Json::Value const metadata = getJsonMetadata(result);
1034
1035 if (BEAST_EXPECT(
1036 metadata.isMember(sfAffectedNodes.jsonName)))
1037 {
1038 BEAST_EXPECT(
1039 metadata[sfAffectedNodes.jsonName].size() == 1);
1040 auto node = metadata[sfAffectedNodes.jsonName][0u];
1041 if (BEAST_EXPECT(
1042 node.isMember(sfModifiedNode.jsonName)))
1043 {
1044 auto modifiedNode = node[sfModifiedNode];
1045 BEAST_EXPECT(
1046 modifiedNode[sfLedgerEntryType] ==
1047 "AccountRoot");
1048 auto finalFields = modifiedNode[sfFinalFields];
1049 BEAST_EXPECT(finalFields[sfDomain] == newDomain);
1050 }
1051 }
1052 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
1053 BEAST_EXPECT(
1054 metadata[sfTransactionResult.jsonName] == "tesSUCCESS");
1055 }
1056 };
1057
1058 Json::Value tx;
1059
1060 tx[jss::Account] = env.master.human();
1061 tx[jss::TransactionType] = jss::AccountSet;
1062 tx[sfDomain] = newDomain;
1063
1064 // test with autofill
1065 testTx(env, tx, validateOutput);
1066
1067 tx[sfSigningPubKey] = "";
1068 tx[sfTxnSignature] = "";
1069 tx[sfSequence] = 1;
1070 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
1071 tx[sfNetworkID] = 1025;
1072
1073 // test without autofill
1074 testTx(env, tx, validateOutput);
1075 }
1076 }
1077
1078public:
1079 void
1080 run() override
1081 {
1083 testFeeError();
1092 }
1093};
1094
1095BEAST_DEFINE_TESTSUITE(Simulate, rpc, ripple);
1096
1097} // namespace test
1098
1099} // namespace ripple
Represents a JSON value.
Definition: json_value.h:148
Value get(UInt index, const Value &defaultValue) const
If the array contains at least index+1 elements, returns the element value, otherwise returns default...
Definition: json_value.cpp:847
UInt size() const
Number of values in array or object.
Definition: json_value.cpp:712
Value & append(const Value &value)
Append value to array at the end.
Definition: json_value.cpp:897
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:475
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:949
A testsuite class.
Definition: suite.h:55
testcase_t testcase
Memberspace for declaring test cases.
Definition: suite.h:155
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition: suite.h:533
virtual TxQ & getTxQ()=0
Slice slice() const noexcept
Definition: PublicKey.h:123
Json::Value getJson(JsonOptions options) const override
Definition: STObject.cpp:825
Holds the serialized result of parsing an input JSON object.
Definition: STParsedJSON.h:33
std::optional< STObject > object
The STObject if the parse was successful.
Definition: STParsedJSON.h:51
Metrics getMetrics(OpenView const &view) const
Returns fee metrics in reference fee level units.
Definition: TxQ.cpp:1778
Json::Value jsonClipped() const
Definition: XRPAmount.h:218
Json::Value getJsonMetadata(Json::Value txResult) const
void testTx(jtx::Env &env, Json::Value const &tx, std::function< void(Json::Value const &, Json::Value const &)> const &validate, bool testSerialized=true)
void run() override
Runs the suite.
void checkBasicReturnValidity(Json::Value const &result, Json::Value const &tx, int const expectedSequence, XRPAmount const &expectedFee)
void checkBasicReturnValidity(Json::Value const &result, Json::Value const &tx, int const expectedSequence, std::string const &expectedFee)
Immutable cryptographic account descriptor.
Definition: Account.h:39
PublicKey const & pk() const
Return the public key.
Definition: Account.h:90
std::string const & human() const
Returns the human readable public key.
Definition: Account.h:114
A transaction testing environment.
Definition: Env.h:120
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition: Env.cpp:212
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:328
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:117
Account const & master
Definition: Env.h:124
Application & app()
Definition: Env.h:258
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:767
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition: rpc.h:35
Set the regular signature on a JTx.
Definition: sig.h:35
@ arrayValue
array value (ordered list)
Definition: json_value.h:43
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:44
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: credentials.cpp:32
Json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: credentials.cpp:50
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: credentials.cpp:83
std::uint32_t ownerCount(Env const &env, Account const &account)
Definition: TestHelpers.cpp:54
Json::Value regkey(Account const &account, disabled_t)
Disable the regular key.
Definition: regkey.cpp:29
Json::Value signers(Account const &account, std::uint32_t quorum, std::vector< signer > const &v)
Definition: multisign.cpp:34
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition: flags.cpp:29
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:54
Json::Value noop(Account const &account)
The null transaction.
Definition: noop.h:31
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
std::optional< Blob > strUnHex(std::size_t strSize, Iterator begin, Iterator end)
@ rpcHIGH_FEE
Definition: ErrorCodes.h:58
SField const sfGeneric
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,...
Definition: BasicConfig.h:315
constexpr std::uint32_t asfDisableMaster
Definition: TxFlags.h:79
std::string strHex(FwdIt begin, FwdIt end)
Definition: strHex.h:30
std::enable_if_t< std::is_same< T, char >::value||std::is_same< T, unsigned char >::value, Slice > makeSlice(std::array< T, N > const &a)
Definition: Slice.h:244
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
T ref(T... args)
A signer in a SignerList.
Definition: multisign.h:38
T to_string(T... args)